@orbat-mapper/tactical-draw-adapter-maplibre 0.3.1 → 0.3.3

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/README.md CHANGED
@@ -5,6 +5,13 @@
5
5
  contract, so the draw/edit sessions, draw rules, gestures, and handle
6
6
  rendering from `tactical-draw` run unchanged on a MapLibre map.
7
7
 
8
+ Each Graphic keeps one stable GeoJSON source and an ordered bundle of native
9
+ paint surfaces. A surface represents one feature's fill, outline, point, icon,
10
+ or text operation, which lets feature-array order remain authoritative across
11
+ MapLibre's separate layer types. This costs up to one native layer per paint
12
+ operation, while content updates reuse the source and surfaces and pure
13
+ reorders move those layers without recreating unrelated Graphic bundles.
14
+
8
15
  `maplibre-gl` 5.8 or 6.x is a peer dependency — your app supplies the version. MapLibre 6
9
16
  applications must also follow MapLibre's
10
17
  [v5-to-v6 setup guidance](https://maplibre.org/maplibre-gl-js/docs/guides/v5-to-v6-migration-guide/),
package/dist/index.d.mts CHANGED
@@ -15,7 +15,9 @@ interface MapLibreLayerHandle {
15
15
  type PaintChannel = "fill" | "fill-pattern" | "line" | "circle" | "icon" | "text";
16
16
  interface MapLibreGroupBundle {
17
17
  readonly sourceId: string;
18
- readonly layerIds: Map<PaintChannel, string>;
18
+ readonly paintSurfaces: Map<string, MapLibrePaintSurface>;
19
+ paintOrder: string[];
20
+ nextPaintSurfaceId: number;
19
21
  features: readonly Feature[];
20
22
  /** Features most recently submitted to the native source, after MapLibre-specific promotion. */
21
23
  sourceFeatures: readonly Feature[];
@@ -34,6 +36,11 @@ interface MapLibreGroupBundle {
34
36
  requiredIconIds: ReadonlySet<string>;
35
37
  overlays: Map<string, MapLibreImageOverlayHandle>;
36
38
  }
39
+ interface MapLibrePaintSurface {
40
+ readonly layerId: string;
41
+ readonly channel: PaintChannel;
42
+ readonly featureId: string | number;
43
+ }
37
44
  /**
38
45
  * Last-applied scalar state for a rendered image overlay, used to skip
39
46
  * MapLibre's `ImageSource.updateImage` (which re-fetches and re-decodes the
@@ -73,7 +80,7 @@ declare class MapLibreAdapter extends BaseMapAdapter<MapLibreLayerHandle, MapLib
73
80
  private reconcileGroupImageOverlays;
74
81
  private clearGroupBundles;
75
82
  private requiredIconIds;
76
- private reconcileGroupChannels;
83
+ private reconcileGroupPaintSurfaces;
77
84
  private buildGroupLayer;
78
85
  private updateGroupBundleSource;
79
86
  private orderGroupBundles;
package/dist/index.mjs CHANGED
@@ -16,19 +16,11 @@ function coordinatesEqual(a, b) {
16
16
  }
17
17
  return true;
18
18
  }
19
- const PAINT_CHANNELS = [
20
- "fill",
21
- "fill-pattern",
22
- "line",
23
- "circle",
24
- "icon",
25
- "text"
26
- ];
27
19
  function firstBundleLayerId(bundle) {
28
20
  for (const overlay of bundle.overlays.values()) return overlay.layerId;
29
- for (const channel of PAINT_CHANNELS) {
30
- const layerId = bundle.layerIds.get(channel);
31
- if (layerId) return layerId;
21
+ for (const key of bundle.paintOrder) {
22
+ const surface = bundle.paintSurfaces.get(key);
23
+ if (surface) return surface.layerId;
32
24
  }
33
25
  }
34
26
  function sameOrder(a, b) {
@@ -771,22 +763,20 @@ function featureStyle(feature) {
771
763
  const style = feature.properties?.style;
772
764
  return style && typeof style === "object" ? style : {};
773
765
  }
774
- function groupPaintChannels(features, style) {
775
- const channels = /* @__PURE__ */ new Set();
776
- for (const feature of features) {
777
- if (geometryIncludes(feature, POLYGON_GEOMETRIES)) {
778
- const fillPattern = featureStyle(feature).fillPattern;
779
- if (featureStyle(feature).fillColor !== void 0 || style?.fillColor !== void 0 || isHatchPattern(fillPattern)) channels.add(isHatchPattern(fillPattern) ? "fill-pattern" : "fill");
780
- }
781
- if (geometryIncludes(feature, LINE_GEOMETRIES)) channels.add("line");
782
- if (!geometryIncludes(feature, POINT_GEOMETRIES)) continue;
783
- const properties = feature.properties ?? {};
784
- const hasIcon = properties.iconId != null;
785
- const hasText = style?.text !== void 0 && (style.text.labelProperty ? properties[style.text.labelProperty] != null : style.text.text !== void 0);
786
- if (hasIcon) channels.add("icon");
787
- if (hasText) channels.add("text");
788
- if (!hasIcon && !hasText) channels.add("circle");
766
+ function featurePaintChannels(feature, style) {
767
+ const channels = [];
768
+ if (geometryIncludes(feature, POLYGON_GEOMETRIES)) {
769
+ const fillPattern = featureStyle(feature).fillPattern;
770
+ if (featureStyle(feature).fillColor !== void 0 || style?.fillColor !== void 0 || isHatchPattern(fillPattern)) channels.push(isHatchPattern(fillPattern) ? "fill-pattern" : "fill");
789
771
  }
772
+ if (geometryIncludes(feature, LINE_GEOMETRIES)) channels.push("line");
773
+ if (!geometryIncludes(feature, POINT_GEOMETRIES)) return channels;
774
+ const properties = feature.properties ?? {};
775
+ const hasIcon = properties.iconId != null;
776
+ const hasText = style?.text !== void 0 && (style.text.labelProperty ? properties[style.text.labelProperty] != null : style.text.text !== void 0);
777
+ if (hasIcon) channels.push("icon");
778
+ if (hasText) channels.push("text");
779
+ if (!hasIcon && !hasText) channels.push("circle");
790
780
  return channels;
791
781
  }
792
782
  var MapLibreAdapter = class extends BaseMapAdapter {
@@ -917,7 +907,7 @@ var MapLibreAdapter = class extends BaseMapAdapter {
917
907
  bundle.revision = group.revision;
918
908
  bundle.requiredIconIds = requiredIconIds;
919
909
  if (this.reconcileGroupImageOverlays(handle, bundle, group.imageOverlays ?? [])) bundlesNeedingOrder.add(group.id);
920
- if (this.reconcileGroupChannels(handle, bundle, groupPaintChannels(group.features, handle.style))) bundlesNeedingOrder.add(group.id);
910
+ if (this.reconcileGroupPaintSurfaces(handle, bundle, group.features)) bundlesNeedingOrder.add(group.id);
921
911
  if (contentChanged || resourcesChanged) this.updateGroupBundleSource(bundle);
922
912
  }
923
913
  handle.groupOrder = groups.map((group) => group.id);
@@ -932,7 +922,9 @@ var MapLibreAdapter = class extends BaseMapAdapter {
932
922
  });
933
923
  return {
934
924
  sourceId,
935
- layerIds: /* @__PURE__ */ new Map(),
925
+ paintSurfaces: /* @__PURE__ */ new Map(),
926
+ paintOrder: [],
927
+ nextPaintSurfaceId: 0,
936
928
  features: [],
937
929
  sourceFeatures: [],
938
930
  sourceInitialized: false,
@@ -946,7 +938,7 @@ var MapLibreAdapter = class extends BaseMapAdapter {
946
938
  if (this.map.getLayer(overlay.layerId)) this.map.removeLayer(overlay.layerId);
947
939
  if (this.map.getSource(overlay.sourceId)) this.map.removeSource(overlay.sourceId);
948
940
  }
949
- for (const layerId of bundle.layerIds.values()) if (this.map.getLayer(layerId)) this.map.removeLayer(layerId);
941
+ for (const surface of bundle.paintSurfaces.values()) if (this.map.getLayer(surface.layerId)) this.map.removeLayer(surface.layerId);
950
942
  if (this.map.getSource(bundle.sourceId)) this.map.removeSource(bundle.sourceId);
951
943
  }
952
944
  reconcileGroupImageOverlays(handle, bundle, overlays) {
@@ -1016,22 +1008,43 @@ var MapLibreAdapter = class extends BaseMapAdapter {
1016
1008
  }));
1017
1009
  return new Set(group.iconResources.map((resource) => resource.id).filter((resourceId) => referenced.has(resourceId)));
1018
1010
  }
1019
- reconcileGroupChannels(handle, bundle, desiredChannels) {
1020
- let changed = false;
1021
- for (const channel of PAINT_CHANNELS) {
1022
- const layerId = bundle.layerIds.get(channel);
1023
- if (layerId && !desiredChannels.has(channel)) {
1024
- if (this.map.getLayer(layerId)) this.map.removeLayer(layerId);
1025
- bundle.layerIds.delete(channel);
1026
- changed = true;
1027
- } else if (!layerId && desiredChannels.has(channel)) {
1028
- const nextLayerId = `${bundle.sourceId}-${channel}`;
1029
- this.map.addLayer(this.buildGroupLayer(nextLayerId, bundle.sourceId, channel, handle.style), handle.mlLayerIds[0]);
1030
- bundle.layerIds.set(channel, nextLayerId);
1031
- changed = true;
1032
- }
1011
+ reconcileGroupPaintSurfaces(handle, bundle, features) {
1012
+ const desired = features.flatMap((feature) => featurePaintChannels(feature, handle.style).map((channel) => ({
1013
+ key: `${typeof feature.id}:${JSON.stringify(feature.id)}:${channel}`,
1014
+ featureId: feature.id,
1015
+ channel
1016
+ })));
1017
+ const desiredKeys = new Set(desired.map(({ key }) => key));
1018
+ let structureChanged = false;
1019
+ for (const [key, surface] of bundle.paintSurfaces) {
1020
+ if (desiredKeys.has(key)) continue;
1021
+ if (this.map.getLayer(surface.layerId)) this.map.removeLayer(surface.layerId);
1022
+ bundle.paintSurfaces.delete(key);
1023
+ structureChanged = true;
1033
1024
  }
1034
- return changed;
1025
+ for (const { key, featureId, channel } of desired) {
1026
+ if (bundle.paintSurfaces.has(key)) continue;
1027
+ const layerId = `${bundle.sourceId}-surface-${++bundle.nextPaintSurfaceId}-${channel}`;
1028
+ const layer = {
1029
+ ...this.buildGroupLayer(layerId, bundle.sourceId, channel, handle.style),
1030
+ filter: [
1031
+ "==",
1032
+ "_fid",
1033
+ featureId
1034
+ ]
1035
+ };
1036
+ this.map.addLayer(layer, handle.mlLayerIds[0]);
1037
+ bundle.paintSurfaces.set(key, {
1038
+ layerId,
1039
+ channel,
1040
+ featureId
1041
+ });
1042
+ structureChanged = true;
1043
+ }
1044
+ const desiredOrder = desired.map(({ key }) => key);
1045
+ const orderChanged = !sameOrder(bundle.paintOrder, desiredOrder);
1046
+ bundle.paintOrder = desiredOrder;
1047
+ return structureChanged || orderChanged;
1035
1048
  }
1036
1049
  buildGroupLayer(id, sourceId, channel, style) {
1037
1050
  switch (channel) {
@@ -1107,9 +1120,9 @@ var MapLibreAdapter = class extends BaseMapAdapter {
1107
1120
  if (!bundle) continue;
1108
1121
  if (forcedGroupIds.has(groupId) || stableGroupIds !== null && !stableGroupIds.has(groupId)) {
1109
1122
  for (const overlay of bundle.overlays.values()) if (this.map.getLayer(overlay.layerId)) this.map.moveLayer(overlay.layerId, beforeId);
1110
- for (const channel of PAINT_CHANNELS) {
1111
- const layerId = bundle.layerIds.get(channel);
1112
- if (layerId && this.map.getLayer(layerId)) this.map.moveLayer(layerId, beforeId);
1123
+ for (const key of bundle.paintOrder) {
1124
+ const surface = bundle.paintSurfaces.get(key);
1125
+ if (surface && this.map.getLayer(surface.layerId)) this.map.moveLayer(surface.layerId, beforeId);
1113
1126
  }
1114
1127
  }
1115
1128
  beforeId = firstBundleLayerId(bundle) ?? beforeId;
@@ -1118,7 +1131,7 @@ var MapLibreAdapter = class extends BaseMapAdapter {
1118
1131
  nativeLayerIds(handle) {
1119
1132
  return [
1120
1133
  ...handle.mlLayerIds,
1121
- ...[...handle.groupBundles.values()].flatMap((bundle) => [...bundle.layerIds.values()]),
1134
+ ...[...handle.groupBundles.values()].flatMap((bundle) => [...bundle.paintSurfaces.values()].map((surface) => surface.layerId)),
1122
1135
  ...[...handle.groupBundles.values()].flatMap((bundle) => [...bundle.overlays.values()].map((overlay) => overlay.layerId))
1123
1136
  ];
1124
1137
  }
@@ -1346,9 +1359,9 @@ var MapLibreAdapter = class extends BaseMapAdapter {
1346
1359
  if (channel) this.applyChannelStyle(mlId, channel, style, resolvedStyle);
1347
1360
  }
1348
1361
  const bundlesNeedingOrder = /* @__PURE__ */ new Set();
1349
- for (const [groupId, bundle] of handle.groupBundles) if (this.reconcileGroupChannels(handle, bundle, groupPaintChannels(bundle.features, resolvedStyle))) bundlesNeedingOrder.add(groupId);
1362
+ for (const [groupId, bundle] of handle.groupBundles) if (this.reconcileGroupPaintSurfaces(handle, bundle, bundle.features)) bundlesNeedingOrder.add(groupId);
1350
1363
  this.orderGroupBundles(handle, handle.groupOrder, handle.groupOrder, bundlesNeedingOrder);
1351
- for (const bundle of handle.groupBundles.values()) for (const [channel, mlId] of bundle.layerIds) this.applyChannelStyle(mlId, channel, style, resolvedStyle);
1364
+ for (const bundle of handle.groupBundles.values()) for (const surface of bundle.paintSurfaces.values()) this.applyChannelStyle(surface.layerId, surface.channel, style, resolvedStyle, surface.featureId);
1352
1365
  if (style.text && !handle.mlLayerIds.some((id) => id.endsWith("-symbol"))) {
1353
1366
  const symbolId = `${layerId}-symbol`;
1354
1367
  this.map.addLayer(buildSymbolLayer(symbolId, handle.sourceId, resolvedStyle.text));
@@ -1363,7 +1376,7 @@ var MapLibreAdapter = class extends BaseMapAdapter {
1363
1376
  if (layerId.endsWith("-icon")) return "icon";
1364
1377
  if (layerId.endsWith("-symbol")) return "text";
1365
1378
  }
1366
- applyChannelStyle(layerId, channel, styleUpdate, resolvedStyle) {
1379
+ applyChannelStyle(layerId, channel, styleUpdate, resolvedStyle, featureId) {
1367
1380
  if (!this.map.getLayer(layerId)) return;
1368
1381
  switch (channel) {
1369
1382
  case "fill":
@@ -1385,7 +1398,11 @@ var MapLibreAdapter = class extends BaseMapAdapter {
1385
1398
  this.map.setPaintProperty(layerId, "circle-color", paint["circle-color"]);
1386
1399
  this.map.setPaintProperty(layerId, "circle-stroke-color", paint["circle-stroke-color"]);
1387
1400
  this.map.setPaintProperty(layerId, "circle-stroke-width", paint["circle-stroke-width"]);
1388
- this.map.setFilter(layerId, buildCirclePointFilter(resolvedStyle));
1401
+ this.map.setFilter(layerId, featureId === void 0 ? buildCirclePointFilter(resolvedStyle) : [
1402
+ "==",
1403
+ "_fid",
1404
+ featureId
1405
+ ]);
1389
1406
  break;
1390
1407
  }
1391
1408
  case "icon": break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orbat-mapper/tactical-draw-adapter-maplibre",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
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/control-measures": "^0.6.0",
41
- "@orbat-mapper/tactical-draw": "^0.4.0"
40
+ "@orbat-mapper/control-measures": "^0.8.0",
41
+ "@orbat-mapper/tactical-draw": "^0.4.2"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "maplibre-gl": ">=5.8.0 <7.0.0"