@zwishing/emap 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/LICENSE +373 -0
  3. package/README.md +294 -0
  4. package/SECURITY.md +56 -0
  5. package/dist/adapter/mapshaper-adapter.d.ts +282 -0
  6. package/dist/core/drag-pan-handler.d.ts +28 -0
  7. package/dist/core/events.d.ts +16 -0
  8. package/dist/core/interactions.d.ts +20 -0
  9. package/dist/core/mapshaper-worker-pool.d.ts +151 -0
  10. package/dist/core/tween.d.ts +26 -0
  11. package/dist/edit/commands/composite.d.ts +16 -0
  12. package/dist/edit/commands/dataset-replace.d.ts +43 -0
  13. package/dist/edit/commands/feature-affine.d.ts +72 -0
  14. package/dist/edit/commands/feature-create.d.ts +47 -0
  15. package/dist/edit/commands/feature-delete.d.ts +72 -0
  16. package/dist/edit/commands/feature-property-change.d.ts +34 -0
  17. package/dist/edit/commands/feature-translate.d.ts +55 -0
  18. package/dist/edit/commands/field-add.d.ts +24 -0
  19. package/dist/edit/commands/field-remove.d.ts +20 -0
  20. package/dist/edit/commands/field-rename.d.ts +19 -0
  21. package/dist/edit/commands/split-shared-arcs.d.ts +71 -0
  22. package/dist/edit/commands/vertex-delete.d.ts +26 -0
  23. package/dist/edit/commands/vertex-insert.d.ts +26 -0
  24. package/dist/edit/commands/vertex-move.d.ts +45 -0
  25. package/dist/edit/edit-command.d.ts +72 -0
  26. package/dist/edit/edit-history.d.ts +130 -0
  27. package/dist/edit/transaction.d.ts +59 -0
  28. package/dist/emap-worker.js +1 -0
  29. package/dist/emap.css +157 -0
  30. package/dist/emap.js +5 -0
  31. package/dist/emap.mjs +5 -0
  32. package/dist/geo/bounds.d.ts +18 -0
  33. package/dist/geo/crs-resolver.d.ts +35 -0
  34. package/dist/geo/projection.d.ts +28 -0
  35. package/dist/geo/transform.d.ts +19 -0
  36. package/dist/geo/viewport.d.ts +52 -0
  37. package/dist/index.d.ts +86 -0
  38. package/dist/map/attribute-ops.d.ts +61 -0
  39. package/dist/map/command-args.d.ts +28 -0
  40. package/dist/map/edit-sessions.d.ts +97 -0
  41. package/dist/map/edit-state-store.d.ts +41 -0
  42. package/dist/map/emap-host.d.ts +79 -0
  43. package/dist/map/feature-accessor.d.ts +43 -0
  44. package/dist/map/feature-query.d.ts +58 -0
  45. package/dist/map/highlight-manager.d.ts +17 -0
  46. package/dist/map/layer-registry.d.ts +33 -0
  47. package/dist/map/layer.d.ts +29 -0
  48. package/dist/map/map.d.ts +386 -0
  49. package/dist/map/mapshaper-ops.d.ts +56 -0
  50. package/dist/map/op-result.d.ts +46 -0
  51. package/dist/map/ops/_context.d.ts +41 -0
  52. package/dist/map/ops/_runner.d.ts +55 -0
  53. package/dist/map/ops/affine.d.ts +4 -0
  54. package/dist/map/ops/buffer.d.ts +4 -0
  55. package/dist/map/ops/check-geometry.d.ts +4 -0
  56. package/dist/map/ops/clean.d.ts +4 -0
  57. package/dist/map/ops/clip-erase.d.ts +5 -0
  58. package/dist/map/ops/data-fill.d.ts +4 -0
  59. package/dist/map/ops/dissolve.d.ts +20 -0
  60. package/dist/map/ops/divide.d.ts +4 -0
  61. package/dist/map/ops/drop-layer.d.ts +4 -0
  62. package/dist/map/ops/each-filter.d.ts +5 -0
  63. package/dist/map/ops/explode.d.ts +4 -0
  64. package/dist/map/ops/filter-fields.d.ts +4 -0
  65. package/dist/map/ops/filter-geom.d.ts +4 -0
  66. package/dist/map/ops/filter-islands.d.ts +4 -0
  67. package/dist/map/ops/filter-slivers.d.ts +4 -0
  68. package/dist/map/ops/innerlines.d.ts +4 -0
  69. package/dist/map/ops/intersection-points.d.ts +4 -0
  70. package/dist/map/ops/join-table.d.ts +4 -0
  71. package/dist/map/ops/lines.d.ts +4 -0
  72. package/dist/map/ops/merge-layers.d.ts +4 -0
  73. package/dist/map/ops/mosaic.d.ts +4 -0
  74. package/dist/map/ops/points.d.ts +4 -0
  75. package/dist/map/ops/polygons.d.ts +4 -0
  76. package/dist/map/ops/project.d.ts +4 -0
  77. package/dist/map/ops/rebuild-topology.d.ts +4 -0
  78. package/dist/map/ops/rename-fields.d.ts +4 -0
  79. package/dist/map/ops/rename-layer.d.ts +4 -0
  80. package/dist/map/ops/simplify.d.ts +4 -0
  81. package/dist/map/ops/snap.d.ts +4 -0
  82. package/dist/map/ops/sort-features.d.ts +4 -0
  83. package/dist/map/ops/split-layer.d.ts +4 -0
  84. package/dist/map/ops/union.d.ts +4 -0
  85. package/dist/map/ops/unique-features.d.ts +4 -0
  86. package/dist/map/selection.d.ts +73 -0
  87. package/dist/map/types.d.ts +1072 -0
  88. package/dist/map/worker-routing.d.ts +40 -0
  89. package/dist/mapshaper-vendor.js +1 -0
  90. package/dist/renderer/canvas-painter.d.ts +50 -0
  91. package/dist/renderer/edit-overlay-renderer.d.ts +22 -0
  92. package/dist/renderer/painter.d.ts +52 -0
  93. package/dist/shim.d.ts +1 -0
  94. package/dist/source/display-arcs.d.ts +49 -0
  95. package/dist/source/layer-utils.d.ts +12 -0
  96. package/dist/source/mapshaper-runner.d.ts +22 -0
  97. package/dist/source/source.d.ts +80 -0
  98. package/dist/source/topology-source.d.ts +145 -0
  99. package/dist/types/mapshaper-types.d.ts +182 -0
  100. package/dist/ui/basemap-control.d.ts +35 -0
  101. package/dist/ui/box-select-control.d.ts +67 -0
  102. package/dist/ui/control.d.ts +6 -0
  103. package/dist/ui/draw-feature-control.d.ts +82 -0
  104. package/dist/ui/edit-toolbar.d.ts +27 -0
  105. package/dist/ui/history-control.d.ts +29 -0
  106. package/dist/ui/lasso-select-control.d.ts +96 -0
  107. package/dist/ui/navigation-control.d.ts +16 -0
  108. package/dist/ui/simplify-control.d.ts +40 -0
  109. package/dist/ui/status-control.d.ts +23 -0
  110. package/dist/ui/vertex-edit-control.d.ts +111 -0
  111. package/dist/validation/builtin/topology.d.ts +19 -0
  112. package/dist/validation/registry.d.ts +23 -0
  113. package/dist/validation/validator.d.ts +47 -0
  114. package/package.json +90 -0
@@ -0,0 +1,17 @@
1
+ import type { CorePainter } from '../renderer/painter';
2
+ import { AffineTransform } from '../geo/transform';
3
+ import { TopologySource } from '../source/source';
4
+ import type { HighlightStyle, HighlightFeatureRef, QueriedFeature } from './map';
5
+ /**
6
+ * Manages feature highlighting state and rendering.
7
+ * Extracted from Emap to isolate highlight concerns.
8
+ */
9
+ export declare class HighlightManager {
10
+ private _highlighted;
11
+ private _highlightStyle;
12
+ get size(): number;
13
+ setHighlightedFeatures(features: Array<QueriedFeature | HighlightFeatureRef>, style?: HighlightStyle): void;
14
+ clearHighlightedFeatures(): boolean;
15
+ setHighlightStyle(style: HighlightStyle): boolean;
16
+ renderHighlights(painter: CorePainter, sources: globalThis.Map<string, TopologySource>, transform: AffineTransform, lineScale: number, visibleLayers?: Set<string>): void;
17
+ }
@@ -0,0 +1,33 @@
1
+ import type { TopologySource } from '../source/source';
2
+ import type { MapshaperLayer } from '../types/mapshaper-types';
3
+ /**
4
+ * Resolve `layerId` against an ad-hoc layer list. Callable as a free
5
+ * function so ops that take a dataset snapshot (and don't have a host
6
+ * reference) can share the same matching rule. Use {@link LayerRegistry.resolve}
7
+ * when you have a `(sourceId, layerId)` tuple instead.
8
+ */
9
+ export declare function resolveLayerInList(layers: ReadonlyArray<MapshaperLayer>, layerId: string): MapshaperLayer | null;
10
+ export declare class LayerRegistry {
11
+ private readonly _sources;
12
+ constructor(_sources: globalThis.Map<string, TopologySource>);
13
+ /**
14
+ * Resolve `(sourceId, layerId)` to the underlying MapshaperLayer, or
15
+ * `null` when neither the source nor any layer name/id match.
16
+ *
17
+ * `layerId` is matched in this priority order:
18
+ * 1. The canonical id from `getLayerId(layer, idx)` — i.e. layer.name,
19
+ * then layer.id, then the positional `'layer-N'` fallback.
20
+ * 2. `layer.name` (exact match).
21
+ * 3. `layer.id` (exact match).
22
+ *
23
+ * Returns the FIRST hit; in a well-formed dataset every layer's
24
+ * `getLayerId` is unique so the priority list is theoretical.
25
+ */
26
+ resolve(sourceId: string, layerId: string): MapshaperLayer | null;
27
+ /**
28
+ * Resolve a layer id against an ad-hoc layer list — used by ops that
29
+ * snapshot the dataset before a mapshaper command runs (the source's
30
+ * registered dataset has already advanced).
31
+ */
32
+ resolveInLayers(layers: ReadonlyArray<MapshaperLayer>, layerId: string): MapshaperLayer | null;
33
+ }
@@ -0,0 +1,29 @@
1
+ import type { StrokeStyle, PointStyle } from '../types/mapshaper-types';
2
+ export type LayerType = 'fill' | 'line' | 'circle';
3
+ export interface FillPaint {
4
+ 'fill-color'?: string;
5
+ 'fill-opacity'?: number;
6
+ }
7
+ export interface LinePaint {
8
+ 'line-color'?: string;
9
+ 'line-width'?: number;
10
+ }
11
+ export interface CirclePaint {
12
+ 'circle-color'?: string;
13
+ 'circle-radius'?: number;
14
+ 'circle-fill'?: boolean;
15
+ }
16
+ export type LayerPaint = FillPaint | LinePaint | CirclePaint;
17
+ export interface LayerLayout {
18
+ visibility?: 'visible' | 'none';
19
+ }
20
+ export interface LayerSpecification {
21
+ id: string;
22
+ type: LayerType;
23
+ source: string;
24
+ 'source-layer'?: string;
25
+ paint?: LayerPaint;
26
+ layout?: LayerLayout;
27
+ }
28
+ export declare function resolveStrokeStyle(paint: LinePaint | FillPaint | undefined, lineScale: number): StrokeStyle;
29
+ export declare function resolvePointStyle(paint: CirclePaint | undefined): PointStyle;
@@ -0,0 +1,386 @@
1
+ import { EventDispatcher } from '../core/events';
2
+ import { Bounds } from '../geo/bounds';
3
+ import { AffineTransform } from '../geo/transform';
4
+ import { Painter } from '../renderer/painter';
5
+ import { MapshaperOps } from './mapshaper-ops';
6
+ import { EditSessions } from './edit-sessions';
7
+ import { AttributeOps } from './attribute-ops';
8
+ import { type EditMode } from './edit-state-store';
9
+ import { LayerRegistry } from './layer-registry';
10
+ import { TopologySource } from '../source/source';
11
+ import { MapshaperWorkerPool } from '../core/mapshaper-worker-pool';
12
+ import { FeatureAccessor } from './feature-accessor';
13
+ import { ValidatorRegistry } from '../validation/registry';
14
+ import { Selection, SelectMode, SelectionDiff } from './selection';
15
+ import { EditHistory } from '../edit/edit-history';
16
+ import type { EditCommand } from '../edit/edit-command';
17
+ import { Transaction, type TransactionInternal } from '../edit/transaction';
18
+ import { Control, ControlPosition } from '../ui/control';
19
+ import type { LayerSpecification } from './layer';
20
+ import type { MapshaperLayer } from '../types/mapshaper-types';
21
+ export * from './types';
22
+ import { type ExpressionEvaluationPolicy, type MapOptions, type QueryFeaturesOptions, type QueriedFeature, type FeatureRef, type HighlightFeatureRef, type ExportSnapshotOptions, type LoadSnapshotOptions, type HighlightStyle, type EditVertexState, type EditDrawState } from './types';
23
+ export declare class Emap extends EventDispatcher {
24
+ private _container;
25
+ private _canvasContainer;
26
+ private _canvas;
27
+ private _painter;
28
+ private _editOverlay;
29
+ private _viewport;
30
+ private _projection;
31
+ _sources: globalThis.Map<string, TopologySource>;
32
+ /**
33
+ * Single resolution surface for `(sourceId, layerId) → MapshaperLayer`.
34
+ * Exposed to managers via `EmapHostInternal.layers`. Reads `_sources`
35
+ * lazily so it stays in sync with `addSource`/`removeSource` without
36
+ * needing its own listener. Constructed on first access so test paths
37
+ * that bypass the constructor via `Object.create(Emap.prototype)` still
38
+ * see a working registry once they `setPrivate(map, '_sources', ...)`.
39
+ */
40
+ private _layersCache?;
41
+ get layers(): LayerRegistry;
42
+ private _sourceListeners;
43
+ private _layers;
44
+ private _effectiveLayersCache;
45
+ private _defaultLayersDirty;
46
+ private _layerArcFlagsCache;
47
+ private _mouseWheel;
48
+ private _dragPan;
49
+ private _pendingRender;
50
+ private _rafId;
51
+ private _controlContainers;
52
+ private _controls;
53
+ private _highlightManager;
54
+ private _featureQuery;
55
+ /**
56
+ * Read-side feature accessor — `engine.features.get(ref)` returns one
57
+ * feature's geometry + frozen properties; `iter(sourceId, layerId)`
58
+ * walks a layer; `count(...)` is O(1). Constructed lazily in the
59
+ * Emap constructor so test paths that bypass it via
60
+ * `Object.create(Emap.prototype)` still see a working accessor once
61
+ * they wire up the host.
62
+ */
63
+ features: FeatureAccessor;
64
+ _selection: Selection;
65
+ _history: EditHistory;
66
+ /**
67
+ * Currently active transaction, or `null`. Mutated by
68
+ * `Transaction`'s constructor / commit / rollback. Exposed via the
69
+ * `EmapHostInternal` contract so the runner can record per-source
70
+ * snapshots without threading a transaction param through every op
71
+ * signature.
72
+ */
73
+ _activeTransaction: TransactionInternal | null;
74
+ /**
75
+ * Post-commit validator registry. Validators registered here run
76
+ * after every `historychange` event; failures surface via
77
+ * `validationfailed` (no auto-undo — apps decide how to react).
78
+ *
79
+ * Public-facing alias is `engine.validators` (see initialiser below).
80
+ */
81
+ readonly _validators: ValidatorRegistry;
82
+ /** Public alias for the validator registry. */
83
+ get validators(): ValidatorRegistry;
84
+ private _historyShortcutUnbind;
85
+ private _ops?;
86
+ private _editSession?;
87
+ private _attributes?;
88
+ /** Mapshaper layer ops (clip / dissolve / buffer / project / ...). */
89
+ get ops(): MapshaperOps;
90
+ /** Selection editing (translate / rotate / scale + live drag sessions). */
91
+ get editSession(): EditSessions;
92
+ /** Per-feature property and per-layer field editing. */
93
+ get attributes(): AttributeOps;
94
+ private _editState;
95
+ _workerPool: MapshaperWorkerPool | null;
96
+ _workerMode: boolean | 'auto';
97
+ _workerThreshold: number;
98
+ /**
99
+ * Optional caller-supplied routing decision. When set, `_runDatasetCommand`
100
+ * forwards every command-info bundle to it and uses the boolean result
101
+ * verbatim. Pass `MapOptions.workerRouting` to install one. Read by
102
+ * `MapshaperOps` via the host.
103
+ */
104
+ _workerRouting?: import('./worker-routing').WorkerRoutingFn;
105
+ private _expressionPolicy;
106
+ constructor(options: MapOptions);
107
+ /**
108
+ * Hard-cancel any in-flight worker jobs. Safe to call when no worker
109
+ * pool exists (no-op). The pool stays usable — subsequent dataset-replace
110
+ * ops re-spawn a fresh worker.
111
+ *
112
+ * mapshaper isn't interruptible mid-command, so this is the only sound
113
+ * abandonment path: terminate the worker, lose any other queued jobs as
114
+ * collateral, and let the next op start over.
115
+ */
116
+ cancelWorkerJobs(): void;
117
+ setExpressionPolicy(policy: ExpressionEvaluationPolicy): void;
118
+ getExpressionPolicy(): ExpressionEvaluationPolicy;
119
+ _allowExpressionEvaluation(values: Array<string | undefined>, operation: string): boolean;
120
+ getCanvasContainer(): HTMLElement;
121
+ getEditMode(): EditMode;
122
+ setEditMode(mode: EditMode): void;
123
+ setEditVertexState(state: EditVertexState): void;
124
+ clearEditVertexState(): void;
125
+ setEditDrawState(state: EditDrawState): void;
126
+ clearEditDrawState(): void;
127
+ private _attachEventListeners;
128
+ private _createCanvasContainer;
129
+ private _updateSize;
130
+ addSource(id: string, source: TopologySource): void;
131
+ removeSource(id: string): void;
132
+ getSource(id: string): TopologySource | undefined;
133
+ /**
134
+ * Pack every topology source on this map into one `.msx` snapshot Blob via
135
+ * mapshaper's session pack pipeline. Source IDs are preserved in a custom
136
+ * `emap.sources` field on the packed object so {@link loadSnapshot} can
137
+ * re-register sources under their original IDs.
138
+ *
139
+ * Sources that have no dataset yet (load not completed) are skipped.
140
+ *
141
+ * The output file is a valid mapshaper `.msx` — vanilla mapshaper will
142
+ * load it (just dropping the `emap` metadata).
143
+ */
144
+ exportSnapshot(opts?: ExportSnapshotOptions): Promise<Blob>;
145
+ /**
146
+ * Restore one or more topology sources from a `.msx` snapshot via
147
+ * mapshaper's session unpack pipeline. Returns the list of registered
148
+ * source IDs in registration order.
149
+ *
150
+ * ID resolution priority (per restored dataset, in order):
151
+ * 1. `opts.idPrefix` + ID from snapshot's `emap.sources[i].id` (if metadata present)
152
+ * 2. `opts.idPrefix` + `opts.defaultIds[i]` (if supplied)
153
+ * 3. `opts.idPrefix` + `snapshot-<i>` fallback
154
+ *
155
+ * On ID collision: `replace: true` (default) removes the existing source
156
+ * first; `replace: false` rejects without registering anything.
157
+ *
158
+ * Edit history and selection are cleared because feature IDs in the
159
+ * replayed history would point to features that no longer exist after
160
+ * dataset replacement. Same semantics as the dataset-replace family
161
+ * (clip, dissolve, mosaic, ...).
162
+ */
163
+ loadSnapshot(input: Blob | Uint8Array | ArrayBuffer, opts?: LoadSnapshotOptions): Promise<string[]>;
164
+ addLayer(layer: LayerSpecification, before?: string): void;
165
+ removeLayer(id: string): void;
166
+ getLayer(id: string): LayerSpecification | undefined;
167
+ getLayers(): LayerSpecification[];
168
+ setLayerVisibility(id: string, visibility: 'visible' | 'none'): void;
169
+ setPaintProperty(id: string, name: string, value: unknown): void;
170
+ moveLayer(id: string, beforeId?: string): void;
171
+ private _getEffectiveLayers;
172
+ private _resolveSourceLayer;
173
+ /**
174
+ * Generates a boolean flag array for arcs that belong to a specific source layer.
175
+ * Cached to prevent walking shapes on every frame.
176
+ *
177
+ * Only meaningful for path layers (polygon/polyline). Point layers have no
178
+ * arc references, so iterating their shapes contributes nothing — caller
179
+ * should filter by `isPathLayer` before invoking.
180
+ */
181
+ private _getLayerArcFlags;
182
+ private _getVisibleLayerKeys;
183
+ getPainter(): Painter;
184
+ getCRS(): string;
185
+ setCRS(crs: string): void;
186
+ reproject(targetCrs: string): Promise<void>;
187
+ getCenter(): [number, number];
188
+ getZoom(): number;
189
+ getTransform(): AffineTransform;
190
+ getExtent(): Bounds;
191
+ setExtent(bounds: Bounds): void;
192
+ reset(): void;
193
+ addControl(control: Control, position?: ControlPosition): void;
194
+ removeControl(control: Control): void;
195
+ zoomIn(): void;
196
+ zoomOut(): void;
197
+ _scheduleRender(): void;
198
+ private _doRender;
199
+ render(): void;
200
+ private _renderLayers;
201
+ private _renderLineLayer;
202
+ private _renderFillLayer;
203
+ private _combineArcFilters;
204
+ private _renderHighlight;
205
+ project(x: number, y: number): [number, number];
206
+ unproject(px: number, py: number): [number, number];
207
+ resize(): void;
208
+ queryFeatures(geometryOrOptions?: [number, number] | [[number, number], [number, number]] | QueryFeaturesOptions, options?: QueryFeaturesOptions): QueriedFeature[];
209
+ /**
210
+ * Eagerly build the per-layer Flatbush spatial index for one source (or
211
+ * every registered source when `sourceId` is omitted). Useful for paying
212
+ * the build cost in idle time before the user's first hit-test.
213
+ *
214
+ * Idempotent — already-cached layer indexes are skipped. No-op for
215
+ * sources without a dataset.
216
+ *
217
+ * Subsequent `queryFeatures` calls automatically use whatever's
218
+ * cached, regardless of whether `prebuildSpatialIndex` was called.
219
+ */
220
+ prebuildSpatialIndex(sourceId?: string): void;
221
+ /**
222
+ * Drop cached per-layer spatial indexes for one source (or all sources
223
+ * when `sourceId` is omitted).
224
+ *
225
+ * Required after in-place geometry edits (`translateSelected`,
226
+ * `rotateSelected`, vertex commands) that mutate arc coordinates without
227
+ * changing shape or arc counts — the cache key wouldn't notice and
228
+ * subsequent queries would return prefilter-stale results.
229
+ *
230
+ * Dataset-replace ops (clip / dissolve / etc.) automatically invalidate
231
+ * via shape-count or arc-count change; this method is only needed for
232
+ * in-place edits.
233
+ */
234
+ clearSpatialIndex(sourceId?: string): void;
235
+ _clearSpatialIndexesForSources(sourceIds: Iterable<string>): void;
236
+ setHighlightedFeatures(features: Array<QueriedFeature | HighlightFeatureRef>, style?: HighlightStyle): void;
237
+ clearHighlightedFeatures(): void;
238
+ setHighlightStyle(style: HighlightStyle): void;
239
+ /**
240
+ * Mutate the persistent feature selection.
241
+ *
242
+ * @param features Refs to apply. `QueriedFeature` is structurally
243
+ * compatible with `FeatureRef`, so results from `queryFeatures` can
244
+ * be passed directly.
245
+ * @param options.mode `'replace'` (default) wipes prior contents,
246
+ * `'add'` unions, `'toggle'` flips membership.
247
+ *
248
+ * Fires a `selectionchange` event with `{ selected, added, removed }` only
249
+ * when the set actually changes.
250
+ */
251
+ select(features: ReadonlyArray<FeatureRef>, options?: {
252
+ mode?: SelectMode;
253
+ }): void;
254
+ /** Remove the listed features from the selection. No-op if absent. */
255
+ deselect(features: ReadonlyArray<FeatureRef>): void;
256
+ /** Empty the selection. No-op (and no event) if it was already empty. */
257
+ clearSelection(): void;
258
+ /** Snapshot of the current selection as a flat list of refs. */
259
+ getSelection(): FeatureRef[];
260
+ /** Test whether a single feature is currently selected. */
261
+ isSelected(feature: FeatureRef): boolean;
262
+ _fireSelectionChange(diff: SelectionDiff): void;
263
+ /**
264
+ * Delete every currently-selected feature in one undoable history step.
265
+ *
266
+ * Returns `true` when at least one feature was deleted, `false` when the
267
+ * selection was empty (in which case nothing is pushed and no events fire).
268
+ *
269
+ * Implementation matches mapshaper's `deleteFeature` / `insertFeature`
270
+ * pair: shape + data record are spliced out (and back in on undo). Within
271
+ * each layer, deletes proceed in descending-id order so each delete's
272
+ * index stays valid; a single Ctrl+Z reverses the entire batch.
273
+ *
274
+ * The selection is cleared on success — the deleted refs no longer exist.
275
+ */
276
+ deleteSelected(): boolean;
277
+ /**
278
+ * Drop derived caches (line-layer arc flags + spatial index) for the given
279
+ * sources. Call after any edit that mutates `layer.shapes` so subsequent
280
+ * frames and `queryFeatures` calls don't reference stale features.
281
+ */
282
+ _invalidateShapeCachesForSources(sourceIds: Iterable<string>): void;
283
+ /**
284
+ * Push an executed `EditCommand` onto the undo stack.
285
+ *
286
+ * Per the memento pattern, the caller is expected to have already performed
287
+ * the work; the command captures what changed so the operation can be
288
+ * reversed on undo. `pushCommand` does NOT call `cmd.do()`. It clears any
289
+ * pending redo entries and fires `historychange`.
290
+ */
291
+ pushCommand(cmd: EditCommand): void;
292
+ /**
293
+ * Reverse the most recent command. No-op when there is nothing to undo.
294
+ * Fires `historychange` and schedules a re-render after the reversal.
295
+ */
296
+ undo(): boolean;
297
+ /**
298
+ * Re-apply the most recently undone command. No-op when there is nothing
299
+ * to redo. Fires `historychange` and schedules a re-render.
300
+ */
301
+ redo(): boolean;
302
+ /** Drop every shape-derived cache. Used by undo/redo where per-source
303
+ * invalidation isn't tracked. */
304
+ private _invalidateAllShapeCaches;
305
+ canUndo(): boolean;
306
+ canRedo(): boolean;
307
+ /** Empty both undo and redo stacks. Fires `historychange` if non-empty. */
308
+ clearHistory(): void;
309
+ /**
310
+ * Begin an atomic multi-step edit. Every op or edit command pushed
311
+ * during the returned transaction's lifetime is staged; on
312
+ * `commit(label)` they collapse into a single undo entry, on
313
+ * `rollback()` every touched source's dataset is restored to its
314
+ * pre-transaction state and the selection is reverted.
315
+ *
316
+ * Throws if a transaction is already active — nesting isn't supported.
317
+ *
318
+ * @example
319
+ * const tx = engine.beginTransaction();
320
+ * const r1 = await engine.ops.clipLayer({ source, target, mask });
321
+ * if (!r1.ok) { tx.rollback(); return; }
322
+ * const r2 = await engine.ops.dissolveLayer({ source, target, field: 'STATE' });
323
+ * if (!r2.ok) { tx.rollback(); return; }
324
+ * await tx.commit('Process boundaries');
325
+ */
326
+ beginTransaction(): Transaction;
327
+ /**
328
+ * Bind Ctrl/Cmd+Z (undo) and Ctrl/Cmd+Shift+Z / Ctrl+Y (redo) on the given
329
+ * element (default: the canvas container). Returns an unbind function. Calls
330
+ * to this method are idempotent — subsequent calls unbind the prior binding.
331
+ */
332
+ bindHistoryShortcuts(target?: HTMLElement): () => void;
333
+ _fireHistoryChange(): void;
334
+ /**
335
+ * Internal — fan out every `'after-commit'` validator and fire
336
+ * `validationfailed` for the subset whose report is `!ok`. Skipped
337
+ * cheaply when no validators are registered (no microtask, no
338
+ * allocations).
339
+ *
340
+ * Ignored when the host has been removed mid-flight; the validator
341
+ * may have started against a dataset that's no longer the current
342
+ * one but its result is irrelevant once the host is gone.
343
+ */
344
+ private _runAfterCommitValidators;
345
+ /**
346
+ * Clip a layer to the polygons in a mask layer using mapshaper's `-clip`
347
+ * pipeline. Target and mask must live in the same source. Returns `true`
348
+ * on success, `false` for unresolvable source / target / mask.
349
+ *
350
+ * The operation is recorded as a single undoable history entry. Note that
351
+ * dataset-replace operations clear prior fine-grained edit history (see
352
+ * design.md for the rationale).
353
+ */
354
+ /**
355
+ * Translate every selected feature by `(dx, dy)` in source-data coordinates.
356
+ * Returns `false` for an empty selection or zero offset.
357
+ *
358
+ * Lightweight in-place mutation — does not replace the dataset, does not
359
+ * clear the selection, composes freely with other commands in one undo
360
+ * timeline. For multi-source selections, one `FeatureTranslateCommand` is
361
+ * built per source and they are wrapped in a single `CompositeCommand` so
362
+ * one undo reverses the whole nudge.
363
+ *
364
+ * Known limitation: arcs shared with unselected features will be translated
365
+ * along with the selection, distorting the unselected neighbours. Safe for
366
+ * isolated features and contiguous polygon subsets; documented in the spec.
367
+ */
368
+ /**
369
+ * @deprecated Prefer `emap.layers.resolve(sourceId, layerId)`. Kept as a
370
+ * thin shim while the composed managers migrate over.
371
+ */
372
+ _resolveLayer(sourceId: string, layerId: string): MapshaperLayer | null;
373
+ /**
374
+ * Tear the map down: cancel pending renders, detach every `data` listener
375
+ * we attached to sources, release each registered control, kill the worker
376
+ * pool, and unmount the canvas. Idempotent — calling it twice is safe.
377
+ *
378
+ * After `remove()` the instance must not be reused. Public methods that
379
+ * touch DOM / timers are left in place for now (they will fail naturally
380
+ * when the underlying resources are gone), but `_scheduleRender` is
381
+ * guarded so post-teardown event firings don't queue another frame.
382
+ */
383
+ remove(): void;
384
+ /** Read by composed managers via `EmapHostInternal._removed`. */
385
+ _removed: boolean;
386
+ }
@@ -0,0 +1,56 @@
1
+ import type { EmapHostInternal } from './emap-host';
2
+ import type { OpResult } from './op-result';
3
+ import type { AffineLayerOptions, ApplyExpressionOptions, BufferOptions, CleanOptions, CheckGeometryOptions, ClipEraseOptions, DataFillOptions, DivideOptions, DropLayerOptions, DissolveOptions, ExplodeOptions, FilterFieldsOptions, FilterGeomOptions, FilterIslandsOptions, FilterOptions, FilterSliversOptions, GeometryCheckReport, InnerlinesOptions, IntersectionPointsLayerOptions, JoinTableOptions, LinesOptions, MergeLayersOptions, MosaicOptions, PointsOptions, PolygonsOptions, ProjectOptions, RebuildTopologyOptions, RenameFieldsOptions, RenameLayerOptions, SimplifyOptions, SnapOptions, SortFeaturesOptions, SplitLayerOptions, UniqueFeaturesOptions, UnionOptions } from './types';
4
+ /**
5
+ * Mapshaper command manager. Owned by Emap and exposed as `emap.ops`.
6
+ * Every method delegates straight to the corresponding free function
7
+ * under `src/map/ops/`. Construction takes the host; the per-call
8
+ * `OpContext` is rebuilt on each access of `_ctx` so test paths that
9
+ * substitute the manager via `Object.create(MapshaperOps.prototype)`
10
+ * still see a working context once they wire up the host.
11
+ */
12
+ export declare class MapshaperOps {
13
+ private _emap;
14
+ constructor(_emap: EmapHostInternal);
15
+ /**
16
+ * Lazily-built `OpContext` handed to every op function. Recomputed
17
+ * on every call so nothing captures a stale `this`.
18
+ */
19
+ private get _ctx();
20
+ clipLayer(opts: ClipEraseOptions): Promise<OpResult>;
21
+ eraseLayer(opts: ClipEraseOptions): Promise<OpResult>;
22
+ dissolveLayer(opts: DissolveOptions): Promise<OpResult>;
23
+ bufferLayer(opts: BufferOptions): Promise<OpResult>;
24
+ applyExpression(opts: ApplyExpressionOptions): Promise<OpResult>;
25
+ filterFeatures(opts: FilterOptions): Promise<OpResult>;
26
+ filterIslands(opts: FilterIslandsOptions): Promise<OpResult>;
27
+ filterSlivers(opts: FilterSliversOptions): Promise<OpResult>;
28
+ filterGeom(opts: FilterGeomOptions): Promise<OpResult>;
29
+ explodeLayer(opts: ExplodeOptions): Promise<OpResult>;
30
+ innerlinesLayer(opts: InnerlinesOptions): Promise<OpResult>;
31
+ snapLayer(opts: SnapOptions): Promise<OpResult>;
32
+ cleanLayer(opts: CleanOptions): Promise<OpResult>;
33
+ mosaicLayer(opts: MosaicOptions): Promise<OpResult>;
34
+ unionLayers(opts: UnionOptions): Promise<OpResult>;
35
+ divideLayer(opts: DivideOptions): Promise<OpResult>;
36
+ pointsLayer(opts: PointsOptions): Promise<OpResult>;
37
+ linesLayer(opts: LinesOptions): Promise<OpResult>;
38
+ polygonsLayer(opts: PolygonsOptions): Promise<OpResult>;
39
+ simplifyLayer(opts: SimplifyOptions): Promise<OpResult>;
40
+ projectLayer(opts: ProjectOptions): Promise<OpResult>;
41
+ affineLayer(opts: AffineLayerOptions): Promise<OpResult>;
42
+ renameLayer(opts: RenameLayerOptions): Promise<OpResult>;
43
+ mergeLayers(opts: MergeLayersOptions): Promise<OpResult>;
44
+ splitLayer(opts: SplitLayerOptions): Promise<OpResult>;
45
+ dropLayer(opts: DropLayerOptions): Promise<OpResult>;
46
+ sortFeatures(opts: SortFeaturesOptions): Promise<OpResult>;
47
+ uniqueFeatures(opts: UniqueFeaturesOptions): Promise<OpResult>;
48
+ filterFields(opts: FilterFieldsOptions): Promise<OpResult>;
49
+ renameFields(opts: RenameFieldsOptions): Promise<OpResult>;
50
+ joinTable(opts: JoinTableOptions): Promise<OpResult>;
51
+ dataFill(opts: DataFillOptions): Promise<OpResult>;
52
+ rebuildTopology(opts: RebuildTopologyOptions): Promise<OpResult>;
53
+ checkGeometry(opts: CheckGeometryOptions): Promise<OpResult<GeometryCheckReport>>;
54
+ intersectionPointsLayer(opts: IntersectionPointsLayerOptions): Promise<OpResult>;
55
+ mergeSelected(): Promise<OpResult>;
56
+ }
@@ -0,0 +1,46 @@
1
+ /** Categories of failure surfaced by `MapshaperOps` methods. */
2
+ export type OpError = {
3
+ kind: 'not-found';
4
+ what: 'source' | 'layer' | 'mask' | 'divider' | 'field';
5
+ id: string;
6
+ } | {
7
+ kind: 'validation';
8
+ field: string;
9
+ reason: string;
10
+ } | {
11
+ kind: 'expression-disabled';
12
+ operation: string;
13
+ } | {
14
+ kind: 'mapshaper';
15
+ cause: unknown;
16
+ cmd?: string;
17
+ operation?: string;
18
+ } | {
19
+ kind: 'host-removed';
20
+ };
21
+ /**
22
+ * Sum type returned by every op. `ok: true` carries an optional `value`
23
+ * (most ops have nothing to return; some — like `checkGeometry` — carry
24
+ * a report); `ok: false` carries the structured `error`.
25
+ */
26
+ export type OpResult<T = void> = {
27
+ ok: true;
28
+ value: T;
29
+ } | {
30
+ ok: false;
31
+ error: OpError;
32
+ };
33
+ /** Singleton "successful, no payload" result. Cheap to return. */
34
+ export declare const OK: OpResult<void>;
35
+ /** Build a successful result with a payload value. */
36
+ export declare function okValue<T>(value: T): OpResult<T>;
37
+ export declare const Err: Readonly<{
38
+ notFound(what: "source" | "layer" | "mask" | "divider" | "field", id: string): OpResult<never>;
39
+ validation(field: string, reason: string): OpResult<never>;
40
+ expressionDisabled(operation: string): OpResult<never>;
41
+ mapshaper(cause: unknown, opts?: {
42
+ cmd?: string;
43
+ operation?: string;
44
+ }): OpResult<never>;
45
+ hostRemoved(): OpResult<never>;
46
+ }>;
@@ -0,0 +1,41 @@
1
+ import type { EmapHostInternal } from '../emap-host';
2
+ import type { MapshaperDataset } from '../../types/mapshaper-types';
3
+ import type { OpResult } from '../op-result';
4
+ import type { ParsedCommand } from '../../adapter/mapshaper-adapter';
5
+ /**
6
+ * Run a CLI-style mapshaper command against a source's dataset, snapshotting
7
+ * for undo and pushing a `DatasetReplaceCommand`. Provided to ops via the
8
+ * `OpContext` rather than as a method on a class so unit tests can mock the
9
+ * whole pipeline by injecting a stub function.
10
+ */
11
+ export type RunDatasetCommand = (sourceId: string, cmd: string, label: string, inputFiles?: Record<string, Uint8Array | string>) => Promise<OpResult>;
12
+ /**
13
+ * PR-4b parsed-command runner. Same dataset-replace pipeline as
14
+ * {@link RunDatasetCommand} but accepts pre-built `ParsedCommand[]`. New
15
+ * ops should prefer this — it skips the CLI parser, eliminating an entire
16
+ * class of escaping bugs.
17
+ */
18
+ export type RunDatasetCommandParsed = (sourceId: string, commands: ParsedCommand[], label: string, inputFiles?: Record<string, Uint8Array | string>) => Promise<OpResult>;
19
+ /**
20
+ * Worker-only path used by `intersectionPointsLayer` / `rebuildTopology`
21
+ * which bypass `parseCommands` and call mapshaper internals directly to
22
+ * produce the `after` dataset. Centralised here so the dataset-replace
23
+ * bookkeeping (history barrier flag, selection clear, render schedule)
24
+ * stays consistent across every op that produces an `after` snapshot.
25
+ */
26
+ export type ApplyDatasetReplace = (sourceId: string, before: MapshaperDataset, after: MapshaperDataset, label: string) => OpResult;
27
+ /**
28
+ * Everything an op function needs from the host. Intentionally narrow —
29
+ * adding new fields here is a deliberate signal that the op surface is
30
+ * growing.
31
+ */
32
+ export interface OpContext {
33
+ /** Internal host surface (sources, history, selection, worker config…). */
34
+ host: EmapHostInternal;
35
+ /** Shared CLI-command runner; see {@link RunDatasetCommand}. */
36
+ runDatasetCommand: RunDatasetCommand;
37
+ /** Parsed-command runner (PR-4b); see {@link RunDatasetCommandParsed}. */
38
+ runDatasetCommandParsed: RunDatasetCommandParsed;
39
+ /** Direct dataset-replace path; see {@link ApplyDatasetReplace}. */
40
+ applyDatasetReplace: ApplyDatasetReplace;
41
+ }
@@ -0,0 +1,55 @@
1
+ import type { MapshaperDataset } from '../../types/mapshaper-types';
2
+ import type { EmapHostInternal } from '../emap-host';
3
+ import { type OpResult } from '../op-result';
4
+ import { type ParsedCommand } from '../../adapter/mapshaper-adapter';
5
+ /**
6
+ * What a dataset-replace op CHANGES about the dataset. Drives the
7
+ * selection-preservation policy in `_applyDatasetReplace`:
8
+ *
9
+ * - `shape-count` — features added / removed / reordered. Selection
10
+ * refs by feature id are stale; clear.
11
+ * - `topology-only` — arc storage rebuilt (clean / snap / simplify /
12
+ * rebuildTopology / mosaic). Feature ids may
13
+ * shift; clear conservatively.
14
+ * - `attribute-only` — only data-table fields changed (each / join /
15
+ * rename-fields / filter-fields / sort / etc.).
16
+ * Feature ids stay; preserve selection.
17
+ * - `crs-only` — coords reprojected, ids unchanged. Preserve.
18
+ * - `unknown` — no information — fall back to clearing for
19
+ * safety. Treated identically to `shape-count`.
20
+ */
21
+ export type OpEffect = 'shape-count' | 'topology-only' | 'attribute-only' | 'crs-only' | 'unknown';
22
+ /**
23
+ * Infer the effect category of a CLI command. Considers every `-NAME`
24
+ * head in the command string and returns the **strongest** effect (in
25
+ * the order `shape-count` > `topology-only` > `crs-only` > `attribute-only`).
26
+ * `target` (a no-op selector head) is ignored.
27
+ */
28
+ export declare function inferEffect(cmd: string): OpEffect;
29
+ /**
30
+ * Same as {@link inferEffect} but consuming a pre-extracted heads array.
31
+ * Used by the parsed-command runner so we don't reconstruct then re-parse
32
+ * a CLI string just to classify the effect.
33
+ */
34
+ export declare function inferEffectForHeads(heads: string[]): OpEffect;
35
+ export declare function _runDatasetCommand(host: EmapHostInternal, sourceId: string, cmd: string, label: string, inputFiles?: Record<string, Uint8Array | string>): Promise<OpResult>;
36
+ /**
37
+ * PR-4b parsed-command path. Same dataset-replace pipeline as
38
+ * {@link _runDatasetCommand} but consumes a pre-built `ParsedCommand[]`
39
+ * instead of a CLI string. Eliminates the escape/re-parse round-trip
40
+ * and the class of bugs that come with it.
41
+ */
42
+ export declare function _runDatasetCommandParsed(host: EmapHostInternal, sourceId: string, commands: ParsedCommand[], label: string, inputFiles?: Record<string, Uint8Array | string>): Promise<OpResult>;
43
+ /**
44
+ * Stage C in isolation: swap the source dataset, push the
45
+ * DatasetReplaceCommand (which flags prior commands stale), conditionally
46
+ * clear selection (PR-10 — depends on `effect`), schedule a render. Used
47
+ * directly by ops that produce an `after` dataset via mapshaper internal
48
+ * primitives instead of the CLI runner — `intersectionPointsLayer`,
49
+ * `rebuildTopology`.
50
+ *
51
+ * `effect` defaults to `'unknown'` (= clear selection) for safety. Ops
52
+ * that know they only changed attributes / CRS should pass the more
53
+ * specific value so the UI's selection survives the operation.
54
+ */
55
+ export declare function _applyDatasetReplace(host: EmapHostInternal, sourceId: string, before: MapshaperDataset, after: MapshaperDataset, label: string, effect?: OpEffect): OpResult;