@orbat-mapper/tactical-draw-adapter-maplibre 0.2.0 → 0.2.2

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 CHANGED
@@ -32,6 +32,19 @@ interface MapLibreGroupBundle {
32
32
  */
33
33
  revision?: number;
34
34
  requiredIconIds: ReadonlySet<string>;
35
+ overlays: Map<string, MapLibreImageOverlayHandle>;
36
+ }
37
+ /**
38
+ * Last-applied scalar state for a rendered image overlay, used to skip
39
+ * MapLibre's `ImageSource.updateImage` (which re-fetches and re-decodes the
40
+ * raster) whenever nothing but the position changed.
41
+ */
42
+ interface MapLibreImageOverlayHandle {
43
+ sourceId: string;
44
+ layerId: string;
45
+ url: string;
46
+ opacity: number;
47
+ coordinates: readonly [Position, Position, Position, Position];
35
48
  }
36
49
  type MapLibreEventToken = (e: any) => void;
37
50
  interface MapLibreViewToken {
@@ -57,6 +70,7 @@ declare class MapLibreAdapter extends BaseMapAdapter<MapLibreLayerHandle, MapLib
57
70
  protected applyFeatureGroups(layerId: LayerId, groups: readonly FeatureGroup[]): void;
58
71
  private createGroupBundle;
59
72
  private destroyGroupBundle;
73
+ private reconcileGroupImageOverlays;
60
74
  private clearGroupBundles;
61
75
  private requiredIconIds;
62
76
  private reconcileGroupChannels;
package/dist/index.mjs CHANGED
@@ -8,6 +8,14 @@ const DEFAULT_LINE_WIDTH = 2;
8
8
  function isHatchPattern(value) {
9
9
  return HATCH_PATTERNS.includes(value);
10
10
  }
11
+ function coordinatesEqual(a, b) {
12
+ for (let index = 0; index < 4; index++) {
13
+ const pa = a[index];
14
+ const pb = b[index];
15
+ if (pa[0] !== pb[0] || pa[1] !== pb[1]) return false;
16
+ }
17
+ return true;
18
+ }
11
19
  const PAINT_CHANNELS = [
12
20
  "fill",
13
21
  "fill-pattern",
@@ -17,6 +25,7 @@ const PAINT_CHANNELS = [
17
25
  "text"
18
26
  ];
19
27
  function firstBundleLayerId(bundle) {
28
+ for (const overlay of bundle.overlays.values()) return overlay.layerId;
20
29
  for (const channel of PAINT_CHANNELS) {
21
30
  const layerId = bundle.layerIds.get(channel);
22
31
  if (layerId) return layerId;
@@ -907,6 +916,7 @@ var MapLibreAdapter = class extends BaseMapAdapter {
907
916
  bundle.features = group.features;
908
917
  bundle.revision = group.revision;
909
918
  bundle.requiredIconIds = requiredIconIds;
919
+ if (this.reconcileGroupImageOverlays(handle, bundle, group.imageOverlays ?? [])) bundlesNeedingOrder.add(group.id);
910
920
  if (this.reconcileGroupChannels(handle, bundle, groupPaintChannels(group.features, handle.style))) bundlesNeedingOrder.add(group.id);
911
921
  if (contentChanged || resourcesChanged) this.updateGroupBundleSource(bundle);
912
922
  }
@@ -927,13 +937,73 @@ var MapLibreAdapter = class extends BaseMapAdapter {
927
937
  sourceFeatures: [],
928
938
  sourceInitialized: false,
929
939
  featureSignature: "[]",
930
- requiredIconIds: /* @__PURE__ */ new Set()
940
+ requiredIconIds: /* @__PURE__ */ new Set(),
941
+ overlays: /* @__PURE__ */ new Map()
931
942
  };
932
943
  }
933
944
  destroyGroupBundle(bundle) {
945
+ for (const overlay of bundle.overlays.values()) {
946
+ if (this.map.getLayer(overlay.layerId)) this.map.removeLayer(overlay.layerId);
947
+ if (this.map.getSource(overlay.sourceId)) this.map.removeSource(overlay.sourceId);
948
+ }
934
949
  for (const layerId of bundle.layerIds.values()) if (this.map.getLayer(layerId)) this.map.removeLayer(layerId);
935
950
  if (this.map.getSource(bundle.sourceId)) this.map.removeSource(bundle.sourceId);
936
951
  }
952
+ reconcileGroupImageOverlays(handle, bundle, overlays) {
953
+ let structureChanged = false;
954
+ const desiredIds = new Set(overlays.map((overlay) => overlay.id));
955
+ for (const [id, native] of bundle.overlays) {
956
+ if (desiredIds.has(id)) continue;
957
+ if (this.map.getLayer(native.layerId)) this.map.removeLayer(native.layerId);
958
+ if (this.map.getSource(native.sourceId)) this.map.removeSource(native.sourceId);
959
+ bundle.overlays.delete(id);
960
+ structureChanged = true;
961
+ }
962
+ for (const overlay of overlays) {
963
+ const existing = bundle.overlays.get(overlay.id);
964
+ const url = overlay.url;
965
+ if (!existing) {
966
+ const suffix = `${bundle.sourceId}-${encodeURIComponent(overlay.id)}`;
967
+ const sourceId = `${suffix}-image-src`;
968
+ const layerId = `${suffix}-image`;
969
+ this.map.addSource(sourceId, {
970
+ type: "image",
971
+ url,
972
+ coordinates: overlay.coordinates
973
+ });
974
+ this.map.addLayer({
975
+ id: layerId,
976
+ type: "raster",
977
+ source: sourceId,
978
+ paint: { "raster-opacity": overlay.opacity }
979
+ }, handle.mlLayerIds[0]);
980
+ bundle.overlays.set(overlay.id, {
981
+ sourceId,
982
+ layerId,
983
+ url,
984
+ opacity: overlay.opacity,
985
+ coordinates: overlay.coordinates
986
+ });
987
+ structureChanged = true;
988
+ continue;
989
+ }
990
+ const urlChanged = existing.url !== url;
991
+ const coordinatesChanged = !coordinatesEqual(existing.coordinates, overlay.coordinates);
992
+ const opacityChanged = existing.opacity !== overlay.opacity;
993
+ if (!urlChanged && !coordinatesChanged && !opacityChanged) continue;
994
+ const source = this.map.getSource(existing.sourceId);
995
+ if (urlChanged) source?.updateImage?.({
996
+ url,
997
+ coordinates: overlay.coordinates.map((point) => [...point])
998
+ });
999
+ else if (coordinatesChanged) source?.setCoordinates?.(overlay.coordinates.map((point) => [...point]));
1000
+ if (opacityChanged) this.map.setPaintProperty(existing.layerId, "raster-opacity", overlay.opacity);
1001
+ existing.url = url;
1002
+ existing.opacity = overlay.opacity;
1003
+ existing.coordinates = overlay.coordinates;
1004
+ }
1005
+ return structureChanged;
1006
+ }
937
1007
  clearGroupBundles(handle) {
938
1008
  for (const bundle of handle.groupBundles.values()) this.destroyGroupBundle(bundle);
939
1009
  handle.groupBundles.clear();
@@ -1035,15 +1105,22 @@ var MapLibreAdapter = class extends BaseMapAdapter {
1035
1105
  const groupId = desiredGroupIds[index];
1036
1106
  const bundle = handle.groupBundles.get(groupId);
1037
1107
  if (!bundle) continue;
1038
- if (forcedGroupIds.has(groupId) || stableGroupIds !== null && !stableGroupIds.has(groupId)) for (const channel of PAINT_CHANNELS) {
1039
- const layerId = bundle.layerIds.get(channel);
1040
- if (layerId && this.map.getLayer(layerId)) this.map.moveLayer(layerId, beforeId);
1108
+ if (forcedGroupIds.has(groupId) || stableGroupIds !== null && !stableGroupIds.has(groupId)) {
1109
+ 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);
1113
+ }
1041
1114
  }
1042
1115
  beforeId = firstBundleLayerId(bundle) ?? beforeId;
1043
1116
  }
1044
1117
  }
1045
1118
  nativeLayerIds(handle) {
1046
- return [...handle.mlLayerIds, ...[...handle.groupBundles.values()].flatMap((bundle) => [...bundle.layerIds.values()])];
1119
+ return [
1120
+ ...handle.mlLayerIds,
1121
+ ...[...handle.groupBundles.values()].flatMap((bundle) => [...bundle.layerIds.values()]),
1122
+ ...[...handle.groupBundles.values()].flatMap((bundle) => [...bundle.overlays.values()].map((overlay) => overlay.layerId))
1123
+ ];
1047
1124
  }
1048
1125
  /**
1049
1126
  * Promote a source feature for MapLibre: stamp `_fid` (so data-driven paint
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orbat-mapper/tactical-draw-adapter-maplibre",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
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.2.0",
41
- "@orbat-mapper/tactical-draw": "^0.2.0"
40
+ "@orbat-mapper/control-measures": "^0.3.1",
41
+ "@orbat-mapper/tactical-draw": "^0.3.0"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "maplibre-gl": ">=5.8.0 <6.0.0"