@orbat-mapper/tactical-draw-adapter-maplibre 0.8.2 → 0.8.4
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.
- package/dist/index.d.mts +13 -3
- package/dist/index.mjs +56 -11
- package/package.json +3 -3
package/dist/index.d.mts
CHANGED
|
@@ -31,6 +31,12 @@ declare class PinResources {
|
|
|
31
31
|
revision: number;
|
|
32
32
|
private key;
|
|
33
33
|
private resources;
|
|
34
|
+
/**
|
|
35
|
+
* Decoded SVGs by resource id. Decoding does not depend on scale or pixel
|
|
36
|
+
* ratio (the atlas scales on draw), so a roster change re-decodes only new
|
|
37
|
+
* icons. Pruned to the current roster in `setRoster`.
|
|
38
|
+
*/
|
|
39
|
+
private readonly decoded;
|
|
34
40
|
private pending?;
|
|
35
41
|
private textures;
|
|
36
42
|
private context?;
|
|
@@ -45,6 +51,7 @@ declare class PinResources {
|
|
|
45
51
|
*/
|
|
46
52
|
setRoster(pins: readonly PinRender[], pixelRatio?: number): void;
|
|
47
53
|
load(gl: WebGL2RenderingContext): Promise<PinResourceSnapshot | undefined>;
|
|
54
|
+
private decode;
|
|
48
55
|
dispose(): void;
|
|
49
56
|
}
|
|
50
57
|
//#endregion
|
|
@@ -299,6 +306,8 @@ declare class MapLibreAdapter extends BaseMapAdapter<MapLibreLayerHandle, MapLib
|
|
|
299
306
|
private map;
|
|
300
307
|
private readonly viewChangeMode;
|
|
301
308
|
private readonly pendingIconImages;
|
|
309
|
+
/** `icon-offset` per registered icon resource id, computed once per resource. */
|
|
310
|
+
private readonly iconOffsets;
|
|
302
311
|
readonly scalesGroundPointSymbolsNatively = true;
|
|
303
312
|
private terrainActive;
|
|
304
313
|
private terrainBanding;
|
|
@@ -354,11 +363,12 @@ declare class MapLibreAdapter extends BaseMapAdapter<MapLibreLayerHandle, MapLib
|
|
|
354
363
|
/**
|
|
355
364
|
* Promote a source feature for MapLibre: stamp `_fid` (so data-driven paint
|
|
356
365
|
* and picks can key on the caller's feature id) and, for a hatch-patterned
|
|
357
|
-
* Polygon, `_fillPatternImageId` (registering the tile image on demand)
|
|
358
|
-
*
|
|
359
|
-
* untouched.
|
|
366
|
+
* Polygon, `_fillPatternImageId` (registering the tile image on demand), and
|
|
367
|
+
* for an icon point, `_iconOffset` (cached per resource). At most one object
|
|
368
|
+
* spread — a feature needing none of these passes through untouched.
|
|
360
369
|
*/
|
|
361
370
|
private promoteFeature;
|
|
371
|
+
private resolveIconOffset;
|
|
362
372
|
/**
|
|
363
373
|
* Resolve a hatch-patterned Polygon's `style.fillPattern`/`fillColor` to its
|
|
364
374
|
* hatch-tile image id, registering the image on demand. Returns `undefined`
|
package/dist/index.mjs
CHANGED
|
@@ -63,6 +63,12 @@ var PinResources = class {
|
|
|
63
63
|
revision = 0;
|
|
64
64
|
key = "";
|
|
65
65
|
resources = /* @__PURE__ */ new Map();
|
|
66
|
+
/**
|
|
67
|
+
* Decoded SVGs by resource id. Decoding does not depend on scale or pixel
|
|
68
|
+
* ratio (the atlas scales on draw), so a roster change re-decodes only new
|
|
69
|
+
* icons. Pruned to the current roster in `setRoster`.
|
|
70
|
+
*/
|
|
71
|
+
decoded = /* @__PURE__ */ new Map();
|
|
66
72
|
pending;
|
|
67
73
|
textures = [];
|
|
68
74
|
context;
|
|
@@ -97,6 +103,7 @@ var PinResources = class {
|
|
|
97
103
|
}
|
|
98
104
|
const key = JSON.stringify([...resources].map(([id, value]) => [id, value.scale]).sort(([a], [b]) => a.localeCompare(b)));
|
|
99
105
|
if (key === this.key) return;
|
|
106
|
+
for (const id of this.decoded.keys()) if (!resources.has(id)) this.decoded.delete(id);
|
|
100
107
|
this.key = key;
|
|
101
108
|
this.resources = resources;
|
|
102
109
|
this.revision++;
|
|
@@ -114,7 +121,7 @@ var PinResources = class {
|
|
|
114
121
|
const side = Math.max(256, Math.sqrt(area) * 1.3, ...sizes.flatMap((s) => [s.w, s.h]));
|
|
115
122
|
const atlas = new PinAtlas(Math.min(2048, gl.getParameter(gl.MAX_TEXTURE_SIZE), 2 ** Math.ceil(Math.log2(side))));
|
|
116
123
|
const icons = [...this.resources.values()];
|
|
117
|
-
const images = await Promise.all(icons.map(({ pin }) =>
|
|
124
|
+
const images = await Promise.all(icons.map(({ pin }) => this.decode(pin.resource)));
|
|
118
125
|
if (this.disposed || revision !== this.revision) return;
|
|
119
126
|
icons.forEach(({ pin, scale }, i) => atlas.add(pin.resource, scale, images[i]));
|
|
120
127
|
const textures = [];
|
|
@@ -146,6 +153,17 @@ var PinResources = class {
|
|
|
146
153
|
};
|
|
147
154
|
})();
|
|
148
155
|
}
|
|
156
|
+
decode(resource) {
|
|
157
|
+
let image = this.decoded.get(resource.id);
|
|
158
|
+
if (!image) {
|
|
159
|
+
image = decodeIcon(resource);
|
|
160
|
+
image.catch(() => {
|
|
161
|
+
if (this.decoded.get(resource.id) === image) this.decoded.delete(resource.id);
|
|
162
|
+
});
|
|
163
|
+
this.decoded.set(resource.id, image);
|
|
164
|
+
}
|
|
165
|
+
return image;
|
|
166
|
+
}
|
|
149
167
|
dispose() {
|
|
150
168
|
this.disposed = true;
|
|
151
169
|
this.revision++;
|
|
@@ -154,6 +172,7 @@ var PinResources = class {
|
|
|
154
172
|
for (const texture of this.textures) this.context?.deleteTexture(texture);
|
|
155
173
|
this.textures = [];
|
|
156
174
|
this.resources.clear();
|
|
175
|
+
this.decoded.clear();
|
|
157
176
|
this.pending = void 0;
|
|
158
177
|
}
|
|
159
178
|
};
|
|
@@ -1484,6 +1503,8 @@ function buildIconSizing(props) {
|
|
|
1484
1503
|
//#region src/ml-adapter.ts
|
|
1485
1504
|
/** Feature property key for the resolved hatch-pattern image id (see `promoteFeature`). */
|
|
1486
1505
|
const FILL_PATTERN_IMAGE_ID_PROPERTY = "_fillPatternImageId";
|
|
1506
|
+
/** Feature property key for a point symbol's `icon-offset` (see `promoteFeature`). */
|
|
1507
|
+
const ICON_OFFSET_PROPERTY = "_iconOffset";
|
|
1487
1508
|
const DEFAULT_LINE_COLOR = "#000";
|
|
1488
1509
|
const DEFAULT_LINE_WIDTH = 2;
|
|
1489
1510
|
/**
|
|
@@ -1765,6 +1786,11 @@ function buildIconLayer(id, source, iconSize = buildIconSizeExpression()) {
|
|
|
1765
1786
|
],
|
|
1766
1787
|
180 / Math.PI
|
|
1767
1788
|
],
|
|
1789
|
+
"icon-offset": [
|
|
1790
|
+
"coalesce",
|
|
1791
|
+
["get", ICON_OFFSET_PROPERTY],
|
|
1792
|
+
["literal", [0, 0]]
|
|
1793
|
+
],
|
|
1768
1794
|
"icon-rotation-alignment": "viewport",
|
|
1769
1795
|
"icon-pitch-alignment": "viewport",
|
|
1770
1796
|
"icon-allow-overlap": true,
|
|
@@ -1778,12 +1804,14 @@ function buildIconLayer(id, source, iconSize = buildIconSizeExpression()) {
|
|
|
1778
1804
|
] }
|
|
1779
1805
|
};
|
|
1780
1806
|
}
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
const
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1807
|
+
/** The icon image: the resource's own canvas, no padding (see `iconOffset`). */
|
|
1808
|
+
function iconSvg(resource) {
|
|
1809
|
+
const { width, height } = resource;
|
|
1810
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">${resource.svg}</svg>`;
|
|
1811
|
+
}
|
|
1812
|
+
/** `icon-offset` placing a cropped icon's `resource.anchor` on the point (image px). */
|
|
1813
|
+
function iconOffset(resource) {
|
|
1814
|
+
return [resource.width / 2 - resource.anchor[0], resource.height / 2 - resource.anchor[1]];
|
|
1787
1815
|
}
|
|
1788
1816
|
/**
|
|
1789
1817
|
* Per-feature icon size, using integer zoom stops:
|
|
@@ -2164,6 +2192,8 @@ var MapLibreAdapter = class extends BaseMapAdapter {
|
|
|
2164
2192
|
map;
|
|
2165
2193
|
viewChangeMode;
|
|
2166
2194
|
pendingIconImages = /* @__PURE__ */ new Set();
|
|
2195
|
+
/** `icon-offset` per registered icon resource id, computed once per resource. */
|
|
2196
|
+
iconOffsets = /* @__PURE__ */ new Map();
|
|
2167
2197
|
scalesGroundPointSymbolsNatively = true;
|
|
2168
2198
|
terrainActive = false;
|
|
2169
2199
|
terrainBanding;
|
|
@@ -2696,15 +2726,17 @@ var MapLibreAdapter = class extends BaseMapAdapter {
|
|
|
2696
2726
|
/**
|
|
2697
2727
|
* Promote a source feature for MapLibre: stamp `_fid` (so data-driven paint
|
|
2698
2728
|
* and picks can key on the caller's feature id) and, for a hatch-patterned
|
|
2699
|
-
* Polygon, `_fillPatternImageId` (registering the tile image on demand)
|
|
2700
|
-
*
|
|
2701
|
-
* untouched.
|
|
2729
|
+
* Polygon, `_fillPatternImageId` (registering the tile image on demand), and
|
|
2730
|
+
* for an icon point, `_iconOffset` (cached per resource). At most one object
|
|
2731
|
+
* spread — a feature needing none of these passes through untouched.
|
|
2702
2732
|
*/
|
|
2703
2733
|
promoteFeature(feature) {
|
|
2704
2734
|
const extra = {};
|
|
2705
2735
|
if (feature.id !== void 0) extra._fid = feature.id;
|
|
2706
2736
|
const imageId = this.resolveFillPatternImage(feature);
|
|
2707
2737
|
if (imageId !== void 0) extra[FILL_PATTERN_IMAGE_ID_PROPERTY] = imageId;
|
|
2738
|
+
const offset = this.resolveIconOffset(feature);
|
|
2739
|
+
if (offset !== void 0) extra[ICON_OFFSET_PROPERTY] = offset;
|
|
2708
2740
|
if (Object.keys(extra).length === 0) return feature;
|
|
2709
2741
|
return {
|
|
2710
2742
|
...feature,
|
|
@@ -2714,6 +2746,18 @@ var MapLibreAdapter = class extends BaseMapAdapter {
|
|
|
2714
2746
|
}
|
|
2715
2747
|
};
|
|
2716
2748
|
}
|
|
2749
|
+
resolveIconOffset(feature) {
|
|
2750
|
+
const iconId = feature.properties?.iconId;
|
|
2751
|
+
if (typeof iconId !== "string") return void 0;
|
|
2752
|
+
let offset = this.iconOffsets.get(iconId);
|
|
2753
|
+
if (offset === void 0) {
|
|
2754
|
+
const resource = this.getIconResource(iconId);
|
|
2755
|
+
if (!resource) return void 0;
|
|
2756
|
+
offset = iconOffset(resource);
|
|
2757
|
+
this.iconOffsets.set(iconId, offset);
|
|
2758
|
+
}
|
|
2759
|
+
return offset;
|
|
2760
|
+
}
|
|
2717
2761
|
/**
|
|
2718
2762
|
* Resolve a hatch-patterned Polygon's `style.fillPattern`/`fillColor` to its
|
|
2719
2763
|
* hatch-tile image id, registering the image on demand. Returns `undefined`
|
|
@@ -2748,11 +2792,12 @@ var MapLibreAdapter = class extends BaseMapAdapter {
|
|
|
2748
2792
|
for (const handle of this.layers.values()) for (const bundle of handle.groupBundles.values()) if (bundle.requiredIconIds.has(resource.id)) this.updateGroupBundleSource(bundle);
|
|
2749
2793
|
};
|
|
2750
2794
|
image.onerror = () => this.pendingIconImages.delete(resource.id);
|
|
2751
|
-
image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(
|
|
2795
|
+
image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(iconSvg(resource))}`;
|
|
2752
2796
|
}
|
|
2753
2797
|
}
|
|
2754
2798
|
removeIconResource(id) {
|
|
2755
2799
|
this.pendingIconImages.delete(id);
|
|
2800
|
+
this.iconOffsets.delete(id);
|
|
2756
2801
|
if (this.map.hasImage(id)) this.map.removeImage(id);
|
|
2757
2802
|
}
|
|
2758
2803
|
attachNativeEvent(type, onEvent) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orbat-mapper/tactical-draw-adapter-maplibre",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.4",
|
|
4
4
|
"description": "MapLibre GL JS engine adapter for @orbat-mapper/tactical-draw. Draw and edit tactical control measures (MIL-STD-2525 / APP-6) on a MapLibre map.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Orbat Mapper",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"access": "public"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@orbat-mapper/
|
|
41
|
-
"@orbat-mapper/
|
|
40
|
+
"@orbat-mapper/tactical-draw": "^0.14.0",
|
|
41
|
+
"@orbat-mapper/control-measures": "^0.32.0"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"maplibre-gl": ">=5.8.0 <7.0.0"
|