唯杰地图 logo唯杰地图
VJMAP
EN

draw-action

vjmap/references/interaction/draw-action.md 6.8 KB188 行
原文

本文回答:vjmap.Draw.actionDraw* 一次性绘制 API —— Promise 风格,无工具栏。

一、API 清单

函数 返回
actionDraw(map, mode, options) 通用
actionDrawPoint(map, options) 点
actionDrawLineSting(map, options) 折线
actionDrawPolygon(map, options) 多边形
actionDrawRectangle(map, options) 矩形
actionDrawSlantRectangle(map, options) 斜矩形
actionDrawCircle(map, options) 圆
actionSelect(map, draw, options) 选择实体

actionDraw* 的 Promise 从不 reject:画成时 resolve 为 draw.create 事件对象 { features: [Feature], styleId, state, ... }(不是带 type 的 FeatureCollection,但 res.features[0] 可用);未画成就退出(ESC、点数不足按 Enter、切换模式等)resolve 为 { cancel: true }(按 Enter 结束时另带 isEnterKey: true)。actionSelect resolve 为 draw.getSelected()(FeatureCollection),只在右键(draw.simpleselect.contextmenu)或开了 selectSingle 且选中后才结束。

二、通用 options

{
  api: { getSnapFeatures: snapObj },   // 捕捉
  updatecoordinate: (e) => { /* 实时更新 */ },
  contextMenu: (e) => { /* 右键菜单 */ },
  statusChange: (e) => {
    if (e.isStart)   { /* 开始绘制 */ }
    if (e.isEnd)     { /* 结束绘制 */ }
  },
  pointCount: N,       // 画到 N 个点自动结束(线 / 多边形)
  isClosed: true,      // 自动闭合(线)
  selectSingle: true,  // actionSelect:选一个即返回
  styles: [...]        // 自定义样式
}

三、典型流程

// 画一条线
const res = await vjmap.Draw.actionDrawLineSting(map, {
  api: { getSnapFeatures: snapObj },
  updatecoordinate: (e) => {
    if (!e.lnglat) return;
    // 实时回显
  }
});
if (res.cancel) return;   // 用户 ESC

// 拿到点
const coords = res.features[0].geometry.coordinates;   // [lng, lat]
const cadPts = map.fromLngLat(coords);                  // CAD 坐标

四、绘直线(两点)

const res = await vjmap.Draw.actionDrawLineSting(map, { pointCount: 2 });

五、绘闭合折线

const res = await vjmap.Draw.actionDrawLineSting(map, { isClosed: true });

六、自定义绘制时样式

const opts = vjmap.cloneDeep(vjmap.Draw.defaultOptions());
const styles = opts.styles;
styles[7].paint["line-width"] = ["case", ["has", "line_width"], ["get", "line_width"], 8];
styles[7].paint["line-color"] = "#0ff";

const res = await vjmap.Draw.actionDrawLineSting(map, { styles });

七、选择实体(不是 Draw 的实体,而是 CAD 实体)

两种不同的"选择":

  • actionSelect(map, draw, options) —— 选 Draw 里的要素。
  • 自定义 selectFeatures(map, ...) —— 选 CAD 图上的实体,需要 MapOpenWay.GeomRender。

CAD 实体框选 / 点选

async function selectFeatures(map, isPointSel) {
  if (map.getService().currentMapParam()?.mapopenway == vjmap.MapOpenWay.Memory) {
    console.error("内存打开模式不支持选择实体");
    return;
  }

  const snapObj = {};
  getMapSnapPoints(map, snapObj);

  const selected = [];
  while (true) {
    let co;
    if (isPointSel) {
      const r = await vjmap.Draw.actionDrawPoint(map, {
        api: { getSnapFeatures: snapObj },
        contextMenu: () => map.fire("keyup", { keyCode: 13 })   // 右键 = 结束
      });
      if (r.cancel) break;
      const pt = r.features[0].geometry.coordinates;
      co = [pt, pt, pt, pt];
    } else {
      const r = await vjmap.Draw.actionDrawRectangle(map, {
        api: { getSnapFeatures: snapObj },
        contextMenu: () => map.fire("keyup", { keyCode: 13 })
      });
      if (r.cancel) break;
      co = r.features[0].geometry.coordinates[0];
    }
    co = map.fromLngLat(co);
    const bounds = [co[0].x, co[0].y, co[2].x, co[1].y];
    const geom = await getHighlightEntities(map, bounds, true);
    selected.push(...geom.features);
  }
  return selected;
}

getHighlightEntities 内部会调用 svc.conditionQueryFeature(条件查询 geom 字段=true)。

八、在 Action 模式里手动输入坐标

await vjmap.Draw.actionDrawPoint(map, {
  statusChange: (e) => {
    if (e.isStart) window.currentStyleId = e.styleId;
  }
});

// 在其他地方(如键盘输入 x,y 回车)
map.fire("draw.input.click", {
  lngLat: map.toLngLat([x, y]),
  styleId: window.currentStyleId
});

九、旋转所选实体(复合动作)

async function selectRotate(map, draw) {
  const sel = await vjmap.Draw.actionSelect(map, draw);
  if (sel.features.length === 0) return;

  const base = await vjmap.Draw.actionDrawPoint(map, {});
  if (base.cancel) return;

  const oldSel = vjmap.cloneDeep(sel.features);
  const pt0 = map.fromLngLat(base.features[0].geometry.coordinates);

  const endRes = await vjmap.Draw.actionDrawPoint(map, {
    updatecoordinate: (e) => {
      if (!e.lnglat) return;
      const pt = map.fromLngLat(e.lnglat);
      const angle = pt.angleTo(pt0);
      // 把 oldSel 每个几何围绕 pt0 旋转 angle
      const updated = vjmap.cloneDeep(oldSel);
      for (const f of updated) {
        const ng = vjmap.transform.convert(f, g => {
          const p = map.fromLngLat(g);
          return map.toLngLat(p.roateAround(angle, pt0));
        });
        draw.setFeatureProperty(f.id, "coordinates", ng);
      }
      draw.forceRefresh();
    }
  });

  if (endRes.cancel) {
    for (const f of oldSel) draw.setFeatureProperty(f.id, "coordinates", f);
    draw.forceRefresh();
  }
}

十、常见坑

  1. updatecoordinate 里修改业务状态 → 每帧都会触发,注意节流。
  2. 连续绘制 → while (true) { const r = await action...; if (r.cancel) break; }。
  3. "右键确认" 需要在 contextMenu 里 fire Enter:map.fire("keyup", { keyCode: 13 })。
  4. Promise 无法 await 但 ctx 仍在绘制:可以保存状态并用 setTimeout 清理。
  5. 控件事件监听是延迟挂载的:actionDraw* 返回 Promise 时控件已创建,但其 mousemove/mouseup/keyup 监听要等 map.loaded() 才注册(内部 connect() 门控,瓦片未就绪时会拖很久)。程序化/合成事件驱动绘制前必须等连接完成——轮询 map.loaded() 或等本控件 draw 源出现(源名 vjmap-map-draw-(hot|cold)_<styleId>,styleId 在 opts.drawContext.options.styleId)。否则早期点击/按键静默丢失。
  6. 点吸附到上一个已提交顶点 = 结束绘制:tr.onClick 里 snapped==lastVertex 时直接 changeMode(simple_select)(等同双击结束的语义)。同样,顶点不足(折线 <2 个提交点)按 Enter 会 resolve {cancel:true} 而非 features。