@orbat-mapper/tactical-draw-adapter-maplibre 0.5.0 → 0.6.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.
- package/dist/index.d.mts +42 -0
- package/dist/index.mjs +103 -22
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -143,6 +143,13 @@ interface MapLibreLayerHandle {
|
|
|
143
143
|
sourceId: string;
|
|
144
144
|
/** MapLibre layer ids created for this logical layer (fill, line, circle, symbol) */
|
|
145
145
|
mlLayerIds: string[];
|
|
146
|
+
/**
|
|
147
|
+
* Anchor for overlay-band bundle layers while terrain is active — this
|
|
148
|
+
* handle's own `-circle` layer (ADR-0057). Stored explicitly rather than
|
|
149
|
+
* derived from `mlLayerIds` by position/suffix so the terrain-banding code
|
|
150
|
+
* doesn't depend on `createLayer`'s layer-creation order.
|
|
151
|
+
*/
|
|
152
|
+
overlayAnchorId: string;
|
|
146
153
|
groupBundles: Map<string, MapLibreGroupBundle>;
|
|
147
154
|
groupOrder: string[];
|
|
148
155
|
nextGroupBundleId: number;
|
|
@@ -214,13 +221,33 @@ interface MapLibreAdapterOptions {
|
|
|
214
221
|
* the refit until the animation ends.
|
|
215
222
|
*/
|
|
216
223
|
viewChangeMode?: "continuous" | "settle";
|
|
224
|
+
/**
|
|
225
|
+
* While MapLibre terrain is active, paint fill/line layers in one band below
|
|
226
|
+
* circle/icon/text/pin layers so terrain draping stays in few passes
|
|
227
|
+
* (ADR-0057). `false` keeps exact per-graphic paint order under terrain, at
|
|
228
|
+
* one draping pass per graphic. @default true
|
|
229
|
+
*/
|
|
230
|
+
terrainBanding?: boolean;
|
|
217
231
|
}
|
|
218
232
|
declare class MapLibreAdapter extends BaseMapAdapter<MapLibreLayerHandle, MapLibreEventToken, MapLibreViewToken> {
|
|
219
233
|
private map;
|
|
220
234
|
private readonly viewChangeMode;
|
|
221
235
|
private readonly pendingIconImages;
|
|
222
236
|
readonly scalesGroundPointSymbolsNatively = true;
|
|
237
|
+
private terrainActive;
|
|
238
|
+
private terrainBanding;
|
|
239
|
+
private readonly handleTerrainChange;
|
|
223
240
|
constructor(map: maplibregl.Map, options?: MapLibreAdapterOptions);
|
|
241
|
+
/** Switches `terrainBanding` at runtime, re-banding every group if the effective state changes. */
|
|
242
|
+
setTerrainBanding(enabled: boolean): void;
|
|
243
|
+
protected destroyEngineResources(): void;
|
|
244
|
+
/**
|
|
245
|
+
* Insertion anchor for a new bundle layer: below the handle's layers, or —
|
|
246
|
+
* for an overlay-band layer under terrain — below the overlay anchor, so it
|
|
247
|
+
* never lands between draped layers and fragments a render-to-texture stack
|
|
248
|
+
* (ADR-0057).
|
|
249
|
+
*/
|
|
250
|
+
private insertionAnchor;
|
|
224
251
|
private toContinuousLng;
|
|
225
252
|
protected createLayer(layerId: LayerId, style?: StyleOptions): MapLibreLayerHandle;
|
|
226
253
|
protected destroyLayer(handle: MapLibreLayerHandle): void;
|
|
@@ -234,6 +261,21 @@ declare class MapLibreAdapter extends BaseMapAdapter<MapLibreLayerHandle, MapLib
|
|
|
234
261
|
private updateSurfaceSizing;
|
|
235
262
|
private buildGroupLayer;
|
|
236
263
|
private updateGroupBundleSource;
|
|
264
|
+
/**
|
|
265
|
+
* Reorders bundle layers below `handle.mlLayerIds`. Walks groups back to
|
|
266
|
+
* front with a `beforeId` cursor, the same LIS-based stable-subset
|
|
267
|
+
* optimization as before.
|
|
268
|
+
*
|
|
269
|
+
* While terrain is active (ADR-0057), bundle layers instead split into two
|
|
270
|
+
* independently-tracked bands so every graphic contributes at most two
|
|
271
|
+
* render-to-texture stacks instead of one per graphic: a draped band
|
|
272
|
+
* (image overlays, fill/fill-pattern/line surfaces) anchored below the
|
|
273
|
+
* handle's own `-fill` layer, and an overlay band (the pin layer,
|
|
274
|
+
* circle/icon/text surfaces) anchored below the handle's own `-circle`
|
|
275
|
+
* layer. Without terrain, both cursors stay equal throughout, collapsing
|
|
276
|
+
* back to the single-cursor algorithm and the exact atomic per-graphic
|
|
277
|
+
* order (ADR-0039).
|
|
278
|
+
*/
|
|
237
279
|
private orderGroupBundles;
|
|
238
280
|
private nativeLayerIds;
|
|
239
281
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -1305,13 +1305,44 @@ function coordinatesEqual(a, b) {
|
|
|
1305
1305
|
}
|
|
1306
1306
|
return true;
|
|
1307
1307
|
}
|
|
1308
|
-
|
|
1309
|
-
|
|
1308
|
+
/**
|
|
1309
|
+
* Paint channels MapLibre renders into a terrain render-to-texture stack
|
|
1310
|
+
* (background/fill/line/raster/hillshade) rather than directly onto the
|
|
1311
|
+
* screen. See ADR-0057.
|
|
1312
|
+
*/
|
|
1313
|
+
const DRAPED_CHANNELS = new Set([
|
|
1314
|
+
"fill",
|
|
1315
|
+
"fill-pattern",
|
|
1316
|
+
"line"
|
|
1317
|
+
]);
|
|
1318
|
+
function isDrapedChannel(channel) {
|
|
1319
|
+
return DRAPED_CHANNELS.has(channel);
|
|
1320
|
+
}
|
|
1321
|
+
/**
|
|
1322
|
+
* Bottom-most layer of a bundle, used as the next (earlier) group's anchor in
|
|
1323
|
+
* `orderGroupBundles`. With a `band` (terrain active, ADR-0057), only that
|
|
1324
|
+
* band's layers count: image overlays are always draped, the pin layer is
|
|
1325
|
+
* always overlay, and paint surfaces follow `isDrapedChannel`.
|
|
1326
|
+
*/
|
|
1327
|
+
function firstBundleLayerId(bundle, band) {
|
|
1328
|
+
if (bundle.pinLayer && band !== "draped") return bundle.pinLayer.id;
|
|
1329
|
+
if (band !== "overlay") for (const overlay of bundle.overlays.values()) return overlay.layerId;
|
|
1310
1330
|
for (const key of bundle.paintOrder) {
|
|
1311
1331
|
const surface = bundle.paintSurfaces.get(key);
|
|
1312
|
-
if (surface)
|
|
1332
|
+
if (!surface) continue;
|
|
1333
|
+
if (!band || isDrapedChannel(surface.channel) === (band === "draped")) return surface.layerId;
|
|
1313
1334
|
}
|
|
1314
1335
|
}
|
|
1336
|
+
/** Hides an always-empty handle-level anchor layer; see `createLayer`. */
|
|
1337
|
+
function hiddenLayer(layer) {
|
|
1338
|
+
return {
|
|
1339
|
+
...layer,
|
|
1340
|
+
layout: {
|
|
1341
|
+
..."layout" in layer ? layer.layout : {},
|
|
1342
|
+
visibility: "none"
|
|
1343
|
+
}
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1315
1346
|
function sameOrder(a, b) {
|
|
1316
1347
|
if (a.length !== b.length) return false;
|
|
1317
1348
|
for (let index = 0; index < a.length; index++) if (a[index] !== b[index]) return false;
|
|
@@ -1915,10 +1946,38 @@ var MapLibreAdapter = class extends BaseMapAdapter {
|
|
|
1915
1946
|
viewChangeMode;
|
|
1916
1947
|
pendingIconImages = /* @__PURE__ */ new Set();
|
|
1917
1948
|
scalesGroundPointSymbolsNatively = true;
|
|
1949
|
+
terrainActive = false;
|
|
1950
|
+
terrainBanding;
|
|
1951
|
+
handleTerrainChange = () => {
|
|
1952
|
+
const active = this.terrainBanding && !!this.map.getTerrain?.();
|
|
1953
|
+
if (active === this.terrainActive) return;
|
|
1954
|
+
this.terrainActive = active;
|
|
1955
|
+
for (const handle of this.layers.values()) this.orderGroupBundles(handle, handle.groupOrder, handle.groupOrder, new Set(handle.groupOrder));
|
|
1956
|
+
};
|
|
1918
1957
|
constructor(map, options = {}) {
|
|
1919
1958
|
super();
|
|
1920
1959
|
this.map = map;
|
|
1921
1960
|
this.viewChangeMode = options.viewChangeMode ?? "continuous";
|
|
1961
|
+
this.terrainBanding = options.terrainBanding ?? true;
|
|
1962
|
+
this.handleTerrainChange();
|
|
1963
|
+
this.map.on("terrain", this.handleTerrainChange);
|
|
1964
|
+
}
|
|
1965
|
+
/** Switches `terrainBanding` at runtime, re-banding every group if the effective state changes. */
|
|
1966
|
+
setTerrainBanding(enabled) {
|
|
1967
|
+
this.terrainBanding = enabled;
|
|
1968
|
+
this.handleTerrainChange();
|
|
1969
|
+
}
|
|
1970
|
+
destroyEngineResources() {
|
|
1971
|
+
this.map.off("terrain", this.handleTerrainChange);
|
|
1972
|
+
}
|
|
1973
|
+
/**
|
|
1974
|
+
* Insertion anchor for a new bundle layer: below the handle's layers, or —
|
|
1975
|
+
* for an overlay-band layer under terrain — below the overlay anchor, so it
|
|
1976
|
+
* never lands between draped layers and fragments a render-to-texture stack
|
|
1977
|
+
* (ADR-0057).
|
|
1978
|
+
*/
|
|
1979
|
+
insertionAnchor(handle, draped) {
|
|
1980
|
+
return this.terrainActive && !draped ? handle.overlayAnchorId : handle.mlLayerIds[0];
|
|
1922
1981
|
}
|
|
1923
1982
|
toContinuousLng(lng) {
|
|
1924
1983
|
return nearestWorldCopyLng(lng, this.map.getCenter().lng);
|
|
@@ -1932,25 +1991,25 @@ var MapLibreAdapter = class extends BaseMapAdapter {
|
|
|
1932
1991
|
});
|
|
1933
1992
|
const mlLayerIds = [];
|
|
1934
1993
|
const fillId = `${layerId}-fill`;
|
|
1935
|
-
this.map.addLayer({
|
|
1994
|
+
this.map.addLayer(hiddenLayer({
|
|
1936
1995
|
id: fillId,
|
|
1937
1996
|
type: "fill",
|
|
1938
1997
|
source: sourceId,
|
|
1939
1998
|
filter: buildFillFilter(false),
|
|
1940
1999
|
paint: buildFillPaint(style)
|
|
1941
|
-
});
|
|
2000
|
+
}));
|
|
1942
2001
|
mlLayerIds.push(fillId);
|
|
1943
2002
|
const fillPatternId = `${layerId}-fill-pattern`;
|
|
1944
|
-
this.map.addLayer({
|
|
2003
|
+
this.map.addLayer(hiddenLayer({
|
|
1945
2004
|
id: fillPatternId,
|
|
1946
2005
|
type: "fill",
|
|
1947
2006
|
source: sourceId,
|
|
1948
2007
|
filter: buildFillFilter(true),
|
|
1949
2008
|
paint: FILL_PATTERN_PAINT
|
|
1950
|
-
});
|
|
2009
|
+
}));
|
|
1951
2010
|
mlLayerIds.push(fillPatternId);
|
|
1952
2011
|
const lineId = `${layerId}-line`;
|
|
1953
|
-
this.map.addLayer({
|
|
2012
|
+
this.map.addLayer(hiddenLayer({
|
|
1954
2013
|
id: lineId,
|
|
1955
2014
|
type: "line",
|
|
1956
2015
|
source: sourceId,
|
|
@@ -1972,28 +2031,29 @@ var MapLibreAdapter = class extends BaseMapAdapter {
|
|
|
1972
2031
|
"line-cap": "round",
|
|
1973
2032
|
"line-join": "round"
|
|
1974
2033
|
}
|
|
1975
|
-
});
|
|
2034
|
+
}));
|
|
1976
2035
|
mlLayerIds.push(lineId);
|
|
1977
2036
|
const circleId = `${layerId}-circle`;
|
|
1978
|
-
this.map.addLayer({
|
|
2037
|
+
this.map.addLayer(hiddenLayer({
|
|
1979
2038
|
id: circleId,
|
|
1980
2039
|
type: "circle",
|
|
1981
2040
|
source: sourceId,
|
|
1982
2041
|
filter: buildCirclePointFilter(style),
|
|
1983
2042
|
paint: buildCirclePaint(style)
|
|
1984
|
-
});
|
|
2043
|
+
}));
|
|
1985
2044
|
mlLayerIds.push(circleId);
|
|
1986
2045
|
const iconId = `${layerId}-icon`;
|
|
1987
|
-
this.map.addLayer(buildIconLayer(iconId, sourceId));
|
|
2046
|
+
this.map.addLayer(hiddenLayer(buildIconLayer(iconId, sourceId)));
|
|
1988
2047
|
mlLayerIds.push(iconId);
|
|
1989
2048
|
if (style?.text) {
|
|
1990
2049
|
const symbolId = `${layerId}-symbol`;
|
|
1991
|
-
this.map.addLayer(buildSymbolLayer(symbolId, sourceId, style.text));
|
|
2050
|
+
this.map.addLayer(hiddenLayer(buildSymbolLayer(symbolId, sourceId, style.text)));
|
|
1992
2051
|
mlLayerIds.push(symbolId);
|
|
1993
2052
|
}
|
|
1994
2053
|
return {
|
|
1995
2054
|
sourceId,
|
|
1996
2055
|
mlLayerIds,
|
|
2056
|
+
overlayAnchorId: circleId,
|
|
1997
2057
|
groupBundles: /* @__PURE__ */ new Map(),
|
|
1998
2058
|
groupOrder: [],
|
|
1999
2059
|
nextGroupBundleId: 0,
|
|
@@ -2062,7 +2122,7 @@ var MapLibreAdapter = class extends BaseMapAdapter {
|
|
|
2062
2122
|
if (group.pinBatch) {
|
|
2063
2123
|
if (!bundle.pinLayer) {
|
|
2064
2124
|
const custom = new PinLayer(`${bundle.sourceId}-pins`, handle.pinResources, this.emitPinStatus);
|
|
2065
|
-
this.map.addLayer(custom, handle
|
|
2125
|
+
this.map.addLayer(custom, this.insertionAnchor(handle, false));
|
|
2066
2126
|
bundle.pinLayer = custom;
|
|
2067
2127
|
bundlesNeedingOrder.add(group.id);
|
|
2068
2128
|
}
|
|
@@ -2236,7 +2296,7 @@ var MapLibreAdapter = class extends BaseMapAdapter {
|
|
|
2236
2296
|
featureId
|
|
2237
2297
|
]
|
|
2238
2298
|
};
|
|
2239
|
-
this.map.addLayer(layer, handle
|
|
2299
|
+
this.map.addLayer(layer, this.insertionAnchor(handle, isDrapedChannel(channel)));
|
|
2240
2300
|
bundle.paintSurfaces.set(key, {
|
|
2241
2301
|
layerId,
|
|
2242
2302
|
channel,
|
|
@@ -2332,23 +2392,44 @@ var MapLibreAdapter = class extends BaseMapAdapter {
|
|
|
2332
2392
|
bundle.sourceInitialized = true;
|
|
2333
2393
|
if (isPromiseLike(result)) Promise.resolve(result).catch(() => {});
|
|
2334
2394
|
}
|
|
2395
|
+
/**
|
|
2396
|
+
* Reorders bundle layers below `handle.mlLayerIds`. Walks groups back to
|
|
2397
|
+
* front with a `beforeId` cursor, the same LIS-based stable-subset
|
|
2398
|
+
* optimization as before.
|
|
2399
|
+
*
|
|
2400
|
+
* While terrain is active (ADR-0057), bundle layers instead split into two
|
|
2401
|
+
* independently-tracked bands so every graphic contributes at most two
|
|
2402
|
+
* render-to-texture stacks instead of one per graphic: a draped band
|
|
2403
|
+
* (image overlays, fill/fill-pattern/line surfaces) anchored below the
|
|
2404
|
+
* handle's own `-fill` layer, and an overlay band (the pin layer,
|
|
2405
|
+
* circle/icon/text surfaces) anchored below the handle's own `-circle`
|
|
2406
|
+
* layer. Without terrain, both cursors stay equal throughout, collapsing
|
|
2407
|
+
* back to the single-cursor algorithm and the exact atomic per-graphic
|
|
2408
|
+
* order (ADR-0039).
|
|
2409
|
+
*/
|
|
2335
2410
|
orderGroupBundles(handle, previousGroupIds, desiredGroupIds, forcedGroupIds) {
|
|
2336
|
-
|
|
2337
|
-
if (!
|
|
2411
|
+
const rootBeforeId = handle.mlLayerIds[0];
|
|
2412
|
+
if (!rootBeforeId) return;
|
|
2413
|
+
let drapedBeforeId = rootBeforeId;
|
|
2414
|
+
let overlayBeforeId = this.terrainActive ? handle.overlayAnchorId : rootBeforeId;
|
|
2338
2415
|
const stableGroupIds = sameOrder(previousGroupIds, desiredGroupIds) ? null : longestStableGroupIds(previousGroupIds, desiredGroupIds);
|
|
2339
2416
|
for (let index = desiredGroupIds.length - 1; index >= 0; index--) {
|
|
2340
2417
|
const groupId = desiredGroupIds[index];
|
|
2341
2418
|
const bundle = handle.groupBundles.get(groupId);
|
|
2342
2419
|
if (!bundle) continue;
|
|
2343
2420
|
if (forcedGroupIds.has(groupId) || stableGroupIds !== null && !stableGroupIds.has(groupId)) {
|
|
2344
|
-
if (bundle.pinLayer && this.map.getLayer(bundle.pinLayer.id)) this.map.moveLayer(bundle.pinLayer.id,
|
|
2345
|
-
for (const overlay of bundle.overlays.values()) if (this.map.getLayer(overlay.layerId)) this.map.moveLayer(overlay.layerId,
|
|
2421
|
+
if (bundle.pinLayer && this.map.getLayer(bundle.pinLayer.id)) this.map.moveLayer(bundle.pinLayer.id, overlayBeforeId);
|
|
2422
|
+
for (const overlay of bundle.overlays.values()) if (this.map.getLayer(overlay.layerId)) this.map.moveLayer(overlay.layerId, drapedBeforeId);
|
|
2346
2423
|
for (const key of bundle.paintOrder) {
|
|
2347
2424
|
const surface = bundle.paintSurfaces.get(key);
|
|
2348
|
-
if (surface
|
|
2425
|
+
if (!surface || !this.map.getLayer(surface.layerId)) continue;
|
|
2426
|
+
this.map.moveLayer(surface.layerId, isDrapedChannel(surface.channel) ? drapedBeforeId : overlayBeforeId);
|
|
2349
2427
|
}
|
|
2350
2428
|
}
|
|
2351
|
-
|
|
2429
|
+
if (this.terrainActive) {
|
|
2430
|
+
drapedBeforeId = firstBundleLayerId(bundle, "draped") ?? drapedBeforeId;
|
|
2431
|
+
overlayBeforeId = firstBundleLayerId(bundle, "overlay") ?? overlayBeforeId;
|
|
2432
|
+
} else drapedBeforeId = overlayBeforeId = firstBundleLayerId(bundle) ?? drapedBeforeId;
|
|
2352
2433
|
}
|
|
2353
2434
|
}
|
|
2354
2435
|
nativeLayerIds(handle) {
|
|
@@ -2589,7 +2670,7 @@ var MapLibreAdapter = class extends BaseMapAdapter {
|
|
|
2589
2670
|
for (const bundle of handle.groupBundles.values()) for (const surface of bundle.paintSurfaces.values()) this.applyChannelStyle(surface.layerId, surface.channel, style, resolvedStyle, surface.featureId);
|
|
2590
2671
|
if (style.text && !handle.mlLayerIds.some((id) => id.endsWith("-symbol"))) {
|
|
2591
2672
|
const symbolId = `${layerId}-symbol`;
|
|
2592
|
-
this.map.addLayer(buildSymbolLayer(symbolId, handle.sourceId, resolvedStyle.text));
|
|
2673
|
+
this.map.addLayer(hiddenLayer(buildSymbolLayer(symbolId, handle.sourceId, resolvedStyle.text)));
|
|
2593
2674
|
handle.mlLayerIds.push(symbolId);
|
|
2594
2675
|
}
|
|
2595
2676
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orbat-mapper/tactical-draw-adapter-maplibre",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
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",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@orbat-mapper/control-measures": "^0.27.0",
|
|
41
|
-
"@orbat-mapper/tactical-draw": "^0.10.
|
|
41
|
+
"@orbat-mapper/tactical-draw": "^0.10.1"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"maplibre-gl": ">=5.8.0 <7.0.0"
|