query-coords
本文回答:
svc.*QueryFeature系列返回的坐标到底在哪个空间、.geom/.geojson/.points/.envelop各是什么、怎么正确叠到图上。(与 backend/query-features 互补:那边讲接口用法,这里讲坐标空间。)
一、服务端返回什么——各字段坐标空间对照
查询接口(pointQueryFeature/rectQueryFeature/conditionQueryFeature/exprQueryFeature)返回 res.result[]。以 sys_cad2000 为例,每个字段的坐标空间并不一样:
| 字段 | 类型 | 坐标空间 | 何时有值 |
|---|---|---|---|
geojson |
string(GeoJSON) | 原始墨卡托(默认)/ CAD 坐标(toMapCoordinate:true,或内存模式结果 isgeocoord:true) |
条件查询仅 includegeom:true 时才有值(只传 realgeom 无效;多条且无 realgeom 时为外包矩形多边形);点/矩形查询总有值;指定 fields 时须包含 geojson |
geom |
object | 渲染经纬度(SDK _processQueryResult 转换产物) |
geojson 非空 且 isMapCoordinate!==true 时才生成;同时附 isline/isPoint/isPolygon(⚠️ isPolygon 有 SDK bug,见 §三) |
envelop |
string WKT POLYGON((...)) |
原始墨卡托(值可为负的百万级,别当 CAD!) | 实体外包矩形,几乎总有;map.getEnvelopBounds(envelop) 解析为 CAD 空间 GeoBounds |
bounds |
string "[x1,y1,x2,y2]" |
CAD 坐标 | 外包矩形,几乎总有 |
points |
string "x,y;x,y" |
CAD 坐标 | 顶点串 |
location/center/insertionPoint/origin |
string "x,y" |
CAD 坐标 | 点状实体/文字 |
isMapCoordinate |
bool | — | toMapCoordinate:true 时服务端置 true |
isEnvelop |
bool | — | 该结果只给了外包矩形(geojson 为空)时多为 true |
isgeocoord |
bool | — | 响应顶层字段:内存模式查询置 true,表示 geojson 已是 CAD 坐标(非墨卡托) |
请求参数对返回的影响:
| 参数 | 效果 |
|---|---|
geom:true |
几何查询模式;不等于返回 geojson |
includegeom:true |
返回 geojson(多条时可能只是 envelop 几何) |
realgeom:true |
每条都返回真实实体 geojson(配合 includegeom) |
toMapCoordinate:true |
geojson 变 CAD 坐标 + isMapCoordinate:true;.geom 不生成 |
| 查询落空 | 没有 result 数组:条件查询为 {recordCount:0},点/矩形查询多为 {}——res.result ?? [] 兜底 |
二、SDK 侧 _processQueryResult 做了什么(Service.js)
const isgeocoord = param.isgeocoord; // 响应顶层字段,不是每条结果上的
const p = this.currentMapParam();
if (p) {
prj = new GeoProjection(p.bounds); // 当前图范围投影
if (!isgeocoord && p.mapBounds) mapPrj = new GeoProjection(p.mapBounds); // 注意 !isgeocoord 才建
}
if (!isgeocoord && Array.isArray(param.metadata?.mapBounds)) // 必须是数组
mapPrj = prj = new GeoProjection(GeoBounds.fromArray(param.metadata.mapBounds));
然后对每个 result 处理(cb 优先级最高,见 §五.8),伪代码:
if (!data[d].geojson) → continue(envelop 模式不产生 .geom)
if (res.isMapCoordinate === true) → continue(不生成 .geom)
else 解析 res.geojson 到新对象,逐坐标转换后写入 res.geom:
isgeocoord → prj ? prj.toLngLat(cad) : 原样保留 // CAD → 渲染经纬度
否则 → (mapPrj && prj) ? prj.toLngLat(mapPrj.fromMercator(mkt)) // 墨卡托→CAD→渲染经纬度
: Projection.mercator2LngLat(mkt) // 纯墨卡托→经纬度
然后写类型标记:
if (!isLine && res.isEnvelop) isLine = true; // ⚠️ envelop 结果被强制标记成线
result[d].isline = isLine; result[d].isPoint = isPoint;
if (result[d].isline === false && result[d].isline === false) result[d].isPolygon = true;
// ↑ SDK 源码把 isline 判断了两次(本该判 isPoint),见 §三警告
注意:被逐点改写的是
JSON.parse(res.geojson)出来的临时对象(赋给.geom);.geojson字符串本身保持原始空间不变——两者坐标空间不同,别混用。若
svc从未openMap(currentMapParam()为空):prj/mapPrj均为 null——isgeocoord结果(内存模式)原样保留 CAD,其余走mercator2LngLat得到真实经纬度(此时.geom就不是"渲染经纬度"了)。查询前确保map.attach(svc, prj)/svc.openMap已建立上下文。
结论:res.result[i].geom 永远是"渲染经纬度"坐标——CAD 底图下是 GeoProjection 归一化经纬度,互联网底图下是真实经纬度。可直接:
map.getSource('x').setData({ type:'Feature', geometry: ent.geom.geometries[0] });
// 或点:
marker.setLngLat(ent.geom.geometries[0].coordinates);
而 toMapCoordinate:true 的结果没有 .geom,要自己 JSON.parse(res.geojson),得到 CAD 坐标。
三、⚠️ isPolygon 不可信(SDK bug,Service.js 源码核实)
判定条件重复写了 isline === false(本意应是 isPoint === false),所以纯点实体也会被打上 isPolygon: true。另外 isEnvelop 的结果会被强制 isline = true。
判断几何类型请:先看 isPoint,再看 isline,或直接读 geom.geometries[i].type。
四、三种典型用法
4.1 只要位置点画 Marker(最省流量)
const res = await svc.conditionQueryFeature({
condition: "name='13' and text like '%泵房%'",
fields: "objectid,points,location,text",
});
for (const e of res.result ?? []) { // 落空时 result 可能不存在!
const [x, y] = (e.location ?? e.points).split(';')[0].split(',').map(Number); // CAD
new vjmap.Marker().setLngLat(map.toLngLat([x, y])).addTo(map); // CAD→渲染经纬度
}
4.2 要真实几何高亮/GeoJSON 图层
const res = await svc.conditionQueryFeature({
condition: "name='11' and layerindex=3",
includegeom: true, realgeom: true, // 关键:不传只有 envelop,geojson 为空、.geom 不生成
});
const features = (res.result ?? []).filter(e => e.geom).map(e => ({
type: 'Feature', properties: { objectid: e.objectid }, geometry: e.geom.geometries[0],
}));
map.addSource('hl', { type:'geojson', data:{ type:'FeatureCollection', features } }); // .geom 已是渲染经纬度
4.3 要 CAD 坐标做计算/转 mapext 实体
const res = await svc.conditionQueryFeature({
condition: "...",
toMapCoordinate: true, // 关键:geojson 直接给 CAD 地图坐标
includegeom: true, realgeom: true, // 同样必须,否则 geojson 是 ""
});
for (const e of res.result ?? []) {
if (!e.geojson) continue;
const gj = JSON.parse(e.geojson); // CAD 坐标 GeoJSON(e.geom 不存在!)
// mapext: featuresToEntities 期望 CAD 坐标 → 直接构 Feature(见 cad-mapper.md §六)
// vjmap 叠加: map.toLngLat(gj) 转渲染经纬度
}
五、易错点
toMapCoordinate:true后去读result[i].geom→undefined;该JSON.parse(result[i].geojson)。- 默认查询的
.geom已是渲染经纬度,不要再map.toLngLat——二次转换。 points/location/bounds字符串是 CAD 坐标(不是经纬度),split 后若要上图须toLngLat。envelop是 WKT 字符串且坐标是原始墨卡托(负的百万级数值),不是 CAD——要包络用bounds字段(CAD"[x1,y1,x2,y2]")或map.getEnvelopBounds(envelop)(内部prj.fromMercator转成 CAD)。- 不传
includegeom/realgeom,多条结果的geojson是空串 →.geom不生成、只有 envelop/bounds/location。要真实几何必传。 - 查询落空时没有
result(条件查询为{recordCount:0},点/矩形查询多为{})——res.result ?? []兜底。 - 互联网底图上跑
conditionQueryFeature(查某张 CAD 图):.geom默认转出的是该 CAD 图的渲染经纬度,不是你的互联网底图经纬度——要叠到互联网底图请用toMapCoordinate:true拿 CAD 坐标,再走cmdTransform→GCJ02/WGS84(见 internet-basemap-align §四之三)。 cb回调(四个查询接口都有:pointQueryFeature(param, cb)、rectQueryFeature、conditionQueryFeature、exprQueryFeature;另外processQueryResult(param, cb)与cmdCreateEntitiesGeomData(param, cb)也走同一处理管线,签名(point:[x,y]) => [x,y]|null|undefined)可对每个坐标点自定义处理,返回非空值就跳过 SDK 的默认转换——这是"自定义结果坐标系"的钩子(比如直接在这里做 CAD→GCJ02)。- 查询入参
x/y/bounds传渲染经纬度 → 入参要 CAD:map.fromLngLat(e.lngLat)先转。经纬度值(如 116,39)当 CAD 传给点查询 →recordCount:0。 enableLayerClickHighlight(svc, cb)内部已做fromLngLat→ 点查询 →.geom回填到高亮 source,全程无需手动转。