跳到主要内容

绘制与编辑 API

JS SDK 与原生 C ABI 共用的编辑会话、点线面绘制、顶点操作、吸附和撤销。

入口与范围

浏览器使用 MapView.editing;原生使用 sqsm_map_edit_json。两者使用同一份 Rust 命令协议,不需要加载空间引擎。能力查询中的 editing: true 表示绑定已提供此入口;自定义 WASM 必须重新构建并包含 editCall。

本接口编辑 map.addGeoJSON 或 sqsm_map_add_geojson 返回的数字图层。字符串样式图层、聚合和实时图源应先取出数据,建立独立的数字图层再编辑;addSource 图源的数据更新仍使用 GeoJSON 图源 API。这些入口的编号不能互换。

编辑限二维俯视视图,同一地图只能有一个会话。编辑期间不要切换投影、替换图层或移除编辑对象。所有地理坐标为 WGS84 [经度, 纬度],所有屏幕点、矩形和捕捉半径为 CSS/逻辑像素,原生同样如此;库会换算设备像素比。墨卡托下拒绝超过约 ±85.051129° 的纬度。

这是编辑数据与辅助交互的接口。宿主负责工具栏、指针/键盘绑定、绘制过程预览、完成和取消按钮;调用 draw 提交一份完整几何,不会自动启用一套鼠标绘制控件。复杂几何计算、通用坐标转换和文件写出仍由空间引擎或宿主完成。

此页对应开发源码快照;使用匹配的 JS、Worker、WASM 或动态库,不能据此假定已发布的 npm 版本包含新接口。

JS:绘制、修改与保存结果

在绘制与编辑演示中可直接画点线面、拖动节点、撤销重做和导出成果;演示同时提供可下载的完整 HTML。

import { MapView } from '@sqsmap/core';

const map = await MapView.create(document.getElementById('map')!);
const layer = map.addGeoJSON('采集成果', {
  type: 'FeatureCollection', features: []
});
await map.flush(); // 等待图层实际添加到地图/Worker
await map.editing.begin(layer);
await map.editing.draw({
  type: 'Polygon',
  coordinates: [[[116.4, 39.9], [116.41, 39.9], [116.41, 39.91]]]
}, { name: '地块一' }); // 自动补上闭合坐标;一次调用是一条撤销记录

const { vertices } = await map.editing.vertices(0);
await map.editing.moveVertex(0, vertices[0]!, [116.399, 39.899]);
await map.editing.undo();
await map.editing.redo();

const result = await map.editing.getData(); // 必须在结束会话前读取
await map.editing.apply(); // 结果保留在地图中,结束会话;不写磁盘
const url = URL.createObjectURL(new Blob([JSON.stringify(result)], {
  type: 'application/geo+json'
}));
const a = document.createElement('a');
a.href = url;
a.download = '成果.geojson';
a.click();
setTimeout(() => URL.revokeObjectURL(url), 1000);
// 如需放弃本会话的所有改动,改为 await map.editing.discard()。

新增的图层为空时不会自动改变相机;宿主可先设置作业区域。要采点,可在开始会话后通过指针事件中的 event.lngLat 取坐标,按需调用 snap,再用 draw({type:'Point', coordinates:...}) 提交。勾绘线面时由宿主累计点列,完成时一次提交,拖动时仅预览、松开后调用一次移动接口,即可保持一次操作一条历史。

JS 方法与 C 命令字段

map.editing.execute(command) 可直接发送下表命令;C 传入完全相同的 JSON 对象,不再套 args 数组。所有写操作成功返回 JSON null;JS 的便利方法均返回 Promise,必须等待完成后再读取结果。

JS 方法/命令 methodJSON 字段结果与语义
begin(layerId) / beginlayerId开始会话,保存恢复检查点
apply() / apply无保留数据并结束会话
discard() / discard无恢复会话开始时的数据并结束
state() / state无layerId、canUndo、canRedo、undoCount、redoCount、undoLabel、redoLabel;无会话时编号为 null、计数为零
undo()、redo()无撤销/重做会话内一步;没有记录时不修改数据
getData() / getData无当前编辑图层的经纬度 FeatureCollection;属性采用核心编辑模型的字符串值
draw(geometry, properties?) / drawgeometry、可选 properties绘制 Point、LineString 或 Polygon,面环自动闭合
append(features) / appendfeatures一次添加一组 GeoJSON Feature;去掉输入 id,由核心分配要素身份
replace(features, label) / replacefeatures、label一条历史替换全层;供宿主计算完成后写回成果,说明为 1~256 UTF-8 字节
vertices(feature, limit?) / verticesfeature、可选 limit{vertices, totalCount, truncated};默认 10000,最多 100000 个
hitVertex、hitSegmentfeature、point:[x,y]、radius命中的顶点/线段起点地址,未命中为 null
featuresInBox(bounds) / featuresInBoxbounds:[左,上,右,下]屏幕矩形命中的要素序号
verticesInBox(feature, bounds) / verticesInBoxfeature、bounds框内顶点,目标要素最多 100000 个顶点
select(features) / selectfeatures高亮要素序号数组,空数组清除高亮
moveVertex(feature, vertex, coordinate, topology?)同名字段移动一个节点;topology:true 联动邻接要素的共享节点
moveVertices(feature, vertices, from, to)同名字段对选中顶点施加显示坐标中 from → to 的统一位移
insertVertex(feature, vertex, coordinate)同名字段在指定线段起点之后插入节点
deleteVertices(feature, vertices, topology?)同名字段删除节点,可联动共享节点;破坏环最小节点数时拒绝整次操作
deleteFeatures(features)features一次删除一组要素
translate(features, from, to)同名字段整组平移要素,一条历史
setAttribute(feature, field, value)同名字段,value 为字符串修改已有字段;字段缺失、序号失效或值未改变时拒绝
snap(coordinate, radius, options?) / snapcoordinate、radius、可选 vertex、segment、gridMeters顶点/线段默认均开启,可加米制网格;未命中为 null,临时编辑叠加层不参与捕捉
trace(from, to, radius) / trace同名字段沿已有线或面环追踪,返回经纬度点列,未找到路径为 null

顶点结构为 {partIndex, vertexIndex, coordinate:[lng,lat], closed}。要素序号、部件序号、顶点序号均从零开始;它们是当前数据的位置地址,删除、替换或撤销后应重新查询,不要当作永久身份保存。命中线段返回该线段的前一顶点地址。闭合环由核心维护。

draw 只接受二维点、线、面,线至少两个点,面环闭合后至少四个坐标。append、replace 也接受合法的二维多部件/集合几何。每条请求最多 16 MiB;捕捉半径为 0~1024 逻辑像素;网格间距必须大于零。核心编辑检查点另有 64 MiB 恢复快照预算。

C ABI

#include "sqs_map.h"
#include <stdio.h>
#include <string.h>

static int edit(sqsm_map_t map, const char *json, int print) {
    sqsm_map_bytes_t out = {0};
    int status = sqsm_map_edit_json(map, (const uint8_t *)json,
                                    (uint64_t)strlen(json), &out);
    if (status != 0) {
        fprintf(stderr, "%s\n", sqsm_map_last_error());
        return status;
    }
    if (print) fwrite(out.data, 1, (size_t)out.len, stdout);
    sqsm_map_bytes_free(out.allocation);
    return 0;
}

/* map 与 layer 来自 sqsm_map_create / sqsm_map_add_geojson。 */
static int draw_point(sqsm_map_t map, uint64_t layer) {
    char begin[128];
    snprintf(begin, sizeof begin, "{\"method\":\"begin\",\"layerId\":%llu}",
             (unsigned long long)layer);
    if (edit(map, begin, 0)) return 1;
    if (edit(map, "{\"method\":\"draw\",\"geometry\":{\"type\":\"Point\","
                  "\"coordinates\":[116.4,39.9]},\"properties\":{\"name\":\"A\"}}", 0))
        return edit(map, "{\"method\":\"discard\"}", 0), 1;
    if (edit(map, "{\"method\":\"getData\"}", 1)) return 1;
    return edit(map, "{\"method\":\"apply\"}", 0);
}

调用必须在地图创建线程上进行;json 是 UTF-8,len 不包含终止零字节。out 在失败时清空,成功后归调用方独立持有,使用 sqsm_map_bytes_free 释放。写操作会在改变数据前分配返回缓冲区,输出预算不足时不修改数据。修改本身不触发文件保存或应用层确认框,宿主按自身编辑流程决定何时允许应用/丢弃。

可下载完整 C 示例,使用匹配头文件与动态库编译。

需要地图重绘时继续执行原有宿主渲染循环;编辑写命令进入原生命令确认日志,状态变化沿用 sqs.layerschange。JS SDK 在确认写操作后请求重绘,主线程与 Worker 使用相同路径。公开头文件及原生签名参考应与动态库配套。