Drawing and editing API
Entry points and scope
Use MapView.editing in the browser and sqsm_map_edit_json in a native host. Both dispatch the same Rust command protocol without a spatial engine. The editing: true capability reports availability; custom WASM bindings must be rebuilt with editCall.
Editing targets a numeric layer returned by map.addGeoJSON or sqsm_map_add_geojson. For a string style layer, clustered source or live source, first obtain its data and create an independent numeric layer. Updating an addSource source still uses the GeoJSON source API. These IDs belong to different registries.
Editing requires a 2D overhead view and allows one session per map. Finish editing before changing projection, replacing the target layer or removing it. Geographic coordinates are WGS84 [longitude, latitude]; screen points, rectangles and snapping radii use CSS/logical pixels on both platforms. The library applies device pixel ratio. Mercator rejects latitudes beyond approximately ±85.051129°.
The host supplies toolbars, pointer/keyboard bindings, in-progress previews, and finish/cancel controls. draw commits a complete geometry; it does not install mouse drawing controls. Complex geometry computation, general CRS conversion and file writing remain host/spatial-engine responsibilities.
This guide describes a development-source snapshot. Use matching JS, Worker, WASM or native binaries; it does not imply that an already published npm release includes these methods.
JS: draw, edit and save the result
Try the drawing and editing demo to draw features, drag vertices, undo/redo and export results. The example includes a downloadable standalone HTML page.
import { MapView } from '@sqsmap/core';
const map = await MapView.create(document.getElementById('map')!);
const layer = map.addGeoJSON('Survey', {
type: 'FeatureCollection', features: []
});
await map.flush(); // Wait for the layer to reach the map/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: 'Parcel A' }); // Rings close automatically; one undo step.
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(); // Read before ending the session.
await map.editing.apply(); // Keep map data and end the session; no disk write.
const url = URL.createObjectURL(new Blob([JSON.stringify(result)], {
type: 'application/geo+json'
}));
const a = document.createElement('a');
a.href = url;
a.download = 'survey.geojson';
a.click();
setTimeout(() => URL.revokeObjectURL(url), 1000);
// To revert the whole session, use await map.editing.discard() instead.
An empty layer leaves the camera unchanged; set the work area first. For point collection, obtain event.lngLat from a pointer event, optionally call snap, then submit draw({type:'Point', coordinates:...}). For lines/polygons, collect positions in the host and submit once on completion. Preview a drag while moving and submit one move on release to keep one undo entry per gesture.
JS methods and C command fields
map.editing.execute(command) accepts the following protocol directly. Pass the identical JSON object to C, without an args envelope. Successful writes return JSON null. Every JS convenience method returns a Promise; await writes before reading results.
| JS method / method field | JSON fields | Result and behavior |
|---|---|---|
begin(layerId) / begin | layerId | Start a session and retain a recovery checkpoint |
apply() / apply | None | Keep changes and end the session |
discard() / discard | None | Restore the initial data and end the session |
state() / state | None | layerId, canUndo, canRedo, undoCount, redoCount, undoLabel, redoLabel; null ID and zero counts outside a session |
undo(), redo() | None | Undo/redo one session step; no change when the stack is empty |
getData() / getData | None | Current WGS84 FeatureCollection; attributes follow the core editor's string-valued model |
draw(geometry, properties?) / draw | geometry, optional properties | Draw Point, LineString or Polygon; polygon rings close automatically |
append(features) / append | features | Add a batch of GeoJSON Features; input IDs are removed and the core assigns identities |
replace(features, label) / replace | features, label | Replace the layer in one undo step, for host-computed results; label is 1–256 UTF-8 bytes |
vertices(feature, limit?) / vertices | feature, optional limit | {vertices, totalCount, truncated}; default 10000, maximum 100000 |
hitVertex, hitSegment | feature, point:[x,y], radius | Vertex or segment-start address; null on a miss |
featuresInBox(bounds) / featuresInBox | bounds:[left,top,right,bottom] | Feature indices intersecting the screen rectangle |
verticesInBox(feature, bounds) / verticesInBox | feature, bounds | Vertices inside the rectangle; target feature is limited to 100000 vertices |
select(features) / select | features | Highlight feature indices; an empty array clears selection |
moveVertex(feature, vertex, coordinate, topology?) | Matching field names | Move one vertex; topology:true moves shared vertices of adjacent features |
moveVertices(feature, vertices, from, to) | Matching field names | Apply the same display-coordinate displacement from from to to to selected vertices |
insertVertex(feature, vertex, coordinate) | Matching field names | Insert after the specified segment-start vertex |
deleteVertices(feature, vertices, topology?) | Matching field names | Delete vertices, optionally shared; reject the whole operation if a ring would have too few vertices |
deleteFeatures(features) | features | Delete a feature batch in one step |
translate(features, from, to) | Matching field names | Translate a feature batch in one step |
setAttribute(feature, field, value) | Matching field names; string value | Update an existing field; reject a missing field, stale feature index or unchanged value |
snap(coordinate, radius, options?) / snap | coordinate, radius, optional vertex, segment, gridMeters | Vertex/segment snapping defaults on; optional metric grid; null on a miss; temporary edit overlays are excluded |
trace(from, to, radius) / trace | Matching field names | Trace an existing line or polygon ring; WGS84 positions or null if no path is found |
A vertex is {partIndex, vertexIndex, coordinate:[lng,lat], closed}. Feature, part and vertex indices are zero based and address the current data. Requery after deletion, replacement or history changes; these addresses are not permanent identities. A segment hit returns its preceding vertex address. The core maintains ring closure.
draw accepts 2D points, lines and polygons. A line needs at least two positions; a closed ring needs at least four. append and replace also accept valid 2D multi-geometries and geometry collections. Each request is limited to 16 MiB, snapping radii to 0–1024 logical pixels, and grid spacing must be positive. The recovery checkpoint has a separate 64 MiB snapshot budget.
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 and layer come from 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);
}
Call on the map's creating thread. Input is UTF-8 and len excludes a terminating NUL. Failure clears out; successful output is independently owned and must be freed with sqsm_map_bytes_free. Write output is allocated before mutation, so output-budget failure cannot apply a write. Edits do not save files or show application confirmation dialogs; the host controls when users may apply or discard changes.
Download the complete C example and compile against the matching header and library.
Continue the existing native render loop to display edits. Native writes enter the command acknowledgement journal and use sqs.layerschange for state notification. The JS SDK requests repaint after acknowledged writes and uses the same path in main-thread and Worker modes. Use the matching public header and native signatures.