唯杰地图 logo唯杰地图
VJMAP
EN

select-feature

vjmap/references/interaction/select-feature.md 7.3 KB195 行
原文

本文回答:选择 CAD 实体 —— 点选 / 框选 / 多边形选,高亮与对象 ID。

一、前提

选择 CAD 原始实体(区别于 Draw / GeoJSON 要素)必须:

  • svc.openMap({ mapopenway: vjmap.MapOpenWay.GeomRender })
    • 内存模式(Memory)不存实体 → 无法选择
  • 后端要返回 geom 字段

二、点选:hover 高亮 + click 查询

栅格样式

map.enableLayerClickHighlight(svc, (e) => {
  if (!e) return;
  console.log(`name=${e.name}, id=${e.objectid}, layer=${e.layerindex}`);
});

矢量样式

const layers = svc.getMapLayers();
const { entTypeIdMap } = await svc.getConstData();

map.enableVectorLayerHoverHighlight((event, feature, layer) => {
  const prop = feature.properties;
  console.log(`feature=${feature.id}, layer=${layers[prop.layer].name}, type=${entTypeIdMap[prop.type]}`);
});

主动查询一点

map.on("click", async (e) => {
  const pt = map.fromLngLat(e.lngLat);
  const q = await svc.pointQueryFeature({
    x: pt.x, y: pt.y,
    pixelsize: 5,
    zoom: map.getZoom()
  });
  console.log(q);
});

三、框选 (Rectangle)

const r = await vjmap.Draw.actionDrawRectangle(map, {
  api: { getSnapFeatures: snapObj }
});
if (r.cancel) return;

const [p1, p2, p3, p4] = map.fromLngLat(r.features[0].geometry.coordinates[0]);
const bounds = [p1.x, p1.y, p3.x, p3.y];

const features = await getHighlightEntities(map, bounds, /*useGeomCoord*/ true);

推荐:用 conditionQueryFeature 内置的范围参数(官方示例 22hightselentity.js 的做法;bounds 就是上面 map.fromLngLat 得到的地图坐标,无需手写空间 SQL):

const res = await svc.conditionQueryFeature({
  condition: "",          // 可再叠加属性条件
  bounds,                 // [x1, y1, x2, y2];多边形传点数组 [[x, y], ...]
  isContains: true,       // true = 完全包含;false(默认)= 相交
  includegeom: true,      // 返回几何
  realgeom: true,         // 每条都返回真实几何(否则多条时只给外包矩形)
  fields: "",
  limit: 100000
});

手写空间 SQL 的参考实现如下(getHighlightEntities 不是包内导出函数,需自己写):

async function getHighlightEntities(map, [x1, y1, x2, y2], useGeomCoord, disableHighlight, includeWholeEntity, disabledSet) {
  const svc = map.getService();
  // ⚠️ ST_MakeEnvelope 所有形态都返回 0——该谓词在后端
  // 不被支持,老资料里的 `geom::geometry && ST_MakeEnvelope(...)` 配方已失效。
  // 可用配方:ST_Intersects(geom, ST_GeomFromText(wkt, 0)),坐标必须是**实体坐标域**
  const wkt = `POLYGON((${x1} ${y1}, ${x2} ${y1}, ${x2} ${y2}, ${x1} ${y2}, ${x1} ${y1}))`;
  const res = await svc.conditionQueryFeature({
    condition: `ST_Intersects(geom, ST_GeomFromText('${wkt}', 0))`,
    fields: "objectid,name,envelop,layer,color",
    geom: true,
    limit: 100000
  });
  // 根据 res.result 生成 GeoJSON features, 再高亮
  return { features: ... };
}

// ⚠️ 三个坐标/语法坑位:
// 1) geom 条件里的 WKT 必须是**实体坐标域**(envelop 字段同一空间,百万级 CAD 原始
//    坐标),不是 drawBounds/鼠标 lngLat 的显示域——两域不重叠,混用必 0 命中。
// 2) ST_Intersects/ST_Within 第二参必须写 ST_GeomFromText('...', 0)——
//    把裸 WKT 字符串直接传参会使空间条件退化为永真,返回**全表**(554 条)。
// 3) 简单矩形框选(显示域)用 `rectQueryFeature({x1,y1,x2,y2})` 更稳——
//    它用的是地图显示坐标域,与鼠标框选一致。

实际项目可引入 vjmapext 用 mapcadLayer.inputManager.getSelections(...)(vjmapext 没有名为 selectEntities 的导出函数)。

四、多边形选(Polygon)

const r = await vjmap.Draw.actionDrawPolygon(map, { api: { getSnapFeatures: snapObj } });
if (r.cancel) return;
const ring = map.fromLngLat(r.features[0].geometry.coordinates[0]);

// 推荐:内置多边形范围查询(官方 22hightselentitypoly.js)
const hit = await svc.conditionQueryFeature({
  condition: "", bounds: ring.map(p => [p.x, p.y]), isContains: true,
  includegeom: true, realgeom: true, fields: "", limit: 100000
});

// 或手写空间 SQL:

// 转 WKT
const wkt = `POLYGON((${ring.map(p => `${p.x} ${p.y}`).join(",")}))`;
const res = await svc.conditionQueryFeature({
  condition: `ST_Within(geom, ST_GeomFromText('${wkt}', 0))`,
  fields: "objectid,name,envelop",
  geom: true,
  limit: 100000
});

五、连续选择(actionSelect 风格)

参考 draw-action.md 的 selectFeatures 函数。关键点:

  • 循环里每次 actionDrawRectangle / actionDrawPoint
  • 右键 → map.fire("keyup", { keyCode: 13 }) 触发结束
  • 累积所选要素

六、高亮方式

  1. 内置点击高亮:map.enableLayerClickHighlight(svc, cb) 内部用 <prefix>-source(默认 highlight-source)存高亮要素——注意 vjmap.HighlightFeatureStyle/highlight_feature_source 并不是真实导出名,要自定义样式就对 highlight-source 这个 source 自己配 layer。
  2. 自己画:对选中 features 建一个 new vjmap.Polygon / vjmap.Polyline,addTo(map)。
  3. 矢量样式用 feature-state:
    map.setFeatureState({ source: "layer-src", sourceLayer: "...", id: 123 }, { selected: true });
    // 样式中用 ["feature-state", "selected"]
    

七、清除选择

没有内置的 clearHighlight 方法,按高亮的实现方式分别清:

// 1) 内置点击高亮(enableLayerClickHighlight 用 <prefix>-source 存高亮要素,默认前缀 "highlight")
// 只清当前高亮、保留点击高亮模式:
map.getSource("highlight-source")
   ?.setData({ type: "FeatureCollection", features: [] });
// 彻底关掉:会连同 highlight-source 及其线/面图层一起删除并解绑 click(之后无需再 setData)
map.disableLayerClickHighlight();

// 2) feature-state 方式:逐条或整块清
map.removeFeatureState({ source: "layer-src", sourceLayer: "..." });        // 省 id = 清整个 source
map.removeFeatureState({ source: "layer-src", sourceLayer: "...", id }, "selected");

// 3) 自己画的高亮图层/实体
map.removeSourceEx("my-sel");   // vjmap 扩展:连同用到它的 layer 一起移除

八、批量 objectid 查询

const ids = [10, 20, 30];
const res = await svc.conditionQueryFeature({
  condition: `objectid in (${ids.join(",")})`,
  fields: "objectid,name,envelop",
  geom: true
});

九、图层显示过滤

选择前限制"只能选某图层":

const disableSet = new Set(["LAYER_A", "LAYER_B"]);
const features = await getHighlightEntities(map, bounds, true, undefined, undefined, disableSet);

十、常见坑

  1. 内存模式选不了 → 切换 MapOpenWay.GeomRender 或 mapopenway: "GeomRender"。
  2. 框大了查询超时 → 后端 limit 加保护。
  3. 栅格 + 矢量两套样式共存时 hover 不对:查看 map.hasVectorLayer() 决定走哪条分支。
  4. PostGIS envelope 查询速度慢 → 服务端 geom 字段建 GIST 索引(内置)。