@orbat-mapper/tactical-draw-adapter-maplibre 0.2.0-alpha.19 → 0.2.0-alpha.20
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 +25 -8
- package/dist/index.mjs +304 -78
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
|
-
import { BaseMapAdapter, EditPointerDriver,
|
|
1
|
+
import { BaseMapAdapter, EditPointerDriver, FeatureGroup, IconResource, LayerId, MapCursor, NativeEventEntry, PickHandler, PickOptions, PixelCoordinate, StyleOptions } from "@orbat-mapper/tactical-draw";
|
|
2
2
|
import maplibregl from "maplibre-gl";
|
|
3
|
-
import { Position } from "geojson";
|
|
3
|
+
import { Feature, Position } from "geojson";
|
|
4
4
|
|
|
5
5
|
//#region src/ml-adapter.d.ts
|
|
6
6
|
interface MapLibreLayerHandle {
|
|
7
7
|
sourceId: string;
|
|
8
8
|
/** MapLibre layer ids created for this logical layer (fill, line, circle, symbol) */
|
|
9
9
|
mlLayerIds: string[];
|
|
10
|
+
groupBundles: Map<string, MapLibreGroupBundle>;
|
|
11
|
+
groupOrder: string[];
|
|
12
|
+
nextGroupBundleId: number;
|
|
10
13
|
style?: StyleOptions;
|
|
11
14
|
}
|
|
15
|
+
type PaintChannel = "fill" | "fill-pattern" | "line" | "circle" | "icon" | "text";
|
|
16
|
+
interface MapLibreGroupBundle {
|
|
17
|
+
readonly sourceId: string;
|
|
18
|
+
readonly layerIds: Map<PaintChannel, string>;
|
|
19
|
+
features: readonly Feature[];
|
|
20
|
+
featureSignature: string;
|
|
21
|
+
requiredIconIds: ReadonlySet<string>;
|
|
22
|
+
}
|
|
12
23
|
type MapLibreEventToken = (e: any) => void;
|
|
13
24
|
interface MapLibreViewToken {
|
|
14
25
|
fn: () => void;
|
|
@@ -25,17 +36,21 @@ declare class MapLibreAdapter extends BaseMapAdapter<MapLibreLayerHandle, MapLib
|
|
|
25
36
|
private map;
|
|
26
37
|
private readonly viewChangeMode;
|
|
27
38
|
private readonly pendingIconImages;
|
|
28
|
-
private readonly pendingLayerUpdates;
|
|
29
|
-
private readonly deferredLayerRemovals;
|
|
30
39
|
readonly scalesGroundPointSymbolsNatively = true;
|
|
31
40
|
constructor(map: maplibregl.Map, options?: MapLibreAdapterOptions);
|
|
32
41
|
private toContinuousLng;
|
|
33
42
|
protected createLayer(layerId: LayerId, style?: StyleOptions): MapLibreLayerHandle;
|
|
34
43
|
protected destroyLayer(handle: MapLibreLayerHandle): void;
|
|
35
|
-
protected
|
|
36
|
-
private
|
|
37
|
-
private
|
|
38
|
-
|
|
44
|
+
protected applyFeatureGroups(layerId: LayerId, groups: readonly FeatureGroup[]): void;
|
|
45
|
+
private createGroupBundle;
|
|
46
|
+
private destroyGroupBundle;
|
|
47
|
+
private clearGroupBundles;
|
|
48
|
+
private requiredIconIds;
|
|
49
|
+
private reconcileGroupChannels;
|
|
50
|
+
private buildGroupLayer;
|
|
51
|
+
private updateGroupBundleSource;
|
|
52
|
+
private orderGroupBundles;
|
|
53
|
+
private nativeLayerIds;
|
|
39
54
|
/**
|
|
40
55
|
* Promote a source feature for MapLibre: stamp `_fid` (so data-driven paint
|
|
41
56
|
* and picks can key on the caller's feature id) and, for a hatch-patterned
|
|
@@ -62,6 +77,8 @@ declare class MapLibreAdapter extends BaseMapAdapter<MapLibreLayerHandle, MapLib
|
|
|
62
77
|
private viewChangeEvents;
|
|
63
78
|
createEditPointerDriver(): EditPointerDriver;
|
|
64
79
|
setLayerStyle(layerId: LayerId, style: StyleOptions): void;
|
|
80
|
+
private legacyPaintChannel;
|
|
81
|
+
private applyChannelStyle;
|
|
65
82
|
toLonLat(coordinate: number[]): Position;
|
|
66
83
|
fromLonLat(position: Position): number[];
|
|
67
84
|
getPixelFromCoordinate(position: Position): PixelCoordinate | null;
|
package/dist/index.mjs
CHANGED
|
@@ -8,6 +8,57 @@ const DEFAULT_LINE_WIDTH = 2;
|
|
|
8
8
|
function isHatchPattern(value) {
|
|
9
9
|
return HATCH_PATTERNS.includes(value);
|
|
10
10
|
}
|
|
11
|
+
const PAINT_CHANNELS = [
|
|
12
|
+
"fill",
|
|
13
|
+
"fill-pattern",
|
|
14
|
+
"line",
|
|
15
|
+
"circle",
|
|
16
|
+
"icon",
|
|
17
|
+
"text"
|
|
18
|
+
];
|
|
19
|
+
function firstBundleLayerId(bundle) {
|
|
20
|
+
for (const channel of PAINT_CHANNELS) {
|
|
21
|
+
const layerId = bundle.layerIds.get(channel);
|
|
22
|
+
if (layerId) return layerId;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Find the largest subset of bundles whose relative order already matches the
|
|
27
|
+
* desired stack. Reorders then move only the remaining bundles.
|
|
28
|
+
*/
|
|
29
|
+
function longestStableGroupIds(previousGroupIds, desiredGroupIds) {
|
|
30
|
+
const previousIndices = new Map(previousGroupIds.map((groupId, index) => [groupId, index]));
|
|
31
|
+
const sequence = desiredGroupIds.flatMap((groupId) => {
|
|
32
|
+
const previousIndex = previousIndices.get(groupId);
|
|
33
|
+
return previousIndex === void 0 ? [] : [{
|
|
34
|
+
groupId,
|
|
35
|
+
previousIndex
|
|
36
|
+
}];
|
|
37
|
+
});
|
|
38
|
+
const tails = [];
|
|
39
|
+
const tailSequenceIndices = [];
|
|
40
|
+
const predecessors = new Array(sequence.length).fill(-1);
|
|
41
|
+
for (let sequenceIndex = 0; sequenceIndex < sequence.length; sequenceIndex++) {
|
|
42
|
+
const previousIndex = sequence[sequenceIndex].previousIndex;
|
|
43
|
+
let low = 0;
|
|
44
|
+
let high = tails.length;
|
|
45
|
+
while (low < high) {
|
|
46
|
+
const middle = low + high >>> 1;
|
|
47
|
+
if (tails[middle] < previousIndex) low = middle + 1;
|
|
48
|
+
else high = middle;
|
|
49
|
+
}
|
|
50
|
+
if (low > 0) predecessors[sequenceIndex] = tailSequenceIndices[low - 1];
|
|
51
|
+
tails[low] = previousIndex;
|
|
52
|
+
tailSequenceIndices[low] = sequenceIndex;
|
|
53
|
+
}
|
|
54
|
+
const stableGroupIds = /* @__PURE__ */ new Set();
|
|
55
|
+
let sequenceIndex = tailSequenceIndices.at(-1);
|
|
56
|
+
while (sequenceIndex !== void 0 && sequenceIndex >= 0) {
|
|
57
|
+
stableGroupIds.add(sequence[sequenceIndex].groupId);
|
|
58
|
+
sequenceIndex = predecessors[sequenceIndex];
|
|
59
|
+
}
|
|
60
|
+
return stableGroupIds;
|
|
61
|
+
}
|
|
11
62
|
function isPromiseLike(value) {
|
|
12
63
|
return (typeof value === "object" || typeof value === "function") && value !== null && typeof value.then === "function";
|
|
13
64
|
}
|
|
@@ -595,12 +646,46 @@ function buildLinePaint(style) {
|
|
|
595
646
|
]
|
|
596
647
|
};
|
|
597
648
|
}
|
|
649
|
+
function geometryIncludes(feature, geometryTypes) {
|
|
650
|
+
const geometry = feature.geometry;
|
|
651
|
+
if (!geometry) return false;
|
|
652
|
+
if (geometry.type === "GeometryCollection") return geometry.geometries.some((candidate) => geometryTypes.has(candidate.type));
|
|
653
|
+
return geometryTypes.has(geometry.type);
|
|
654
|
+
}
|
|
655
|
+
const POLYGON_GEOMETRIES = new Set(["Polygon", "MultiPolygon"]);
|
|
656
|
+
const LINE_GEOMETRIES = new Set([
|
|
657
|
+
"LineString",
|
|
658
|
+
"MultiLineString",
|
|
659
|
+
"Polygon",
|
|
660
|
+
"MultiPolygon"
|
|
661
|
+
]);
|
|
662
|
+
const POINT_GEOMETRIES = new Set(["Point", "MultiPoint"]);
|
|
663
|
+
function featureStyle(feature) {
|
|
664
|
+
const style = feature.properties?.style;
|
|
665
|
+
return style && typeof style === "object" ? style : {};
|
|
666
|
+
}
|
|
667
|
+
function groupPaintChannels(features, style) {
|
|
668
|
+
const channels = /* @__PURE__ */ new Set();
|
|
669
|
+
for (const feature of features) {
|
|
670
|
+
if (geometryIncludes(feature, POLYGON_GEOMETRIES)) {
|
|
671
|
+
const fillPattern = featureStyle(feature).fillPattern;
|
|
672
|
+
if (featureStyle(feature).fillColor !== void 0 || style?.fillColor !== void 0 || isHatchPattern(fillPattern)) channels.add(isHatchPattern(fillPattern) ? "fill-pattern" : "fill");
|
|
673
|
+
}
|
|
674
|
+
if (geometryIncludes(feature, LINE_GEOMETRIES)) channels.add("line");
|
|
675
|
+
if (!geometryIncludes(feature, POINT_GEOMETRIES)) continue;
|
|
676
|
+
const properties = feature.properties ?? {};
|
|
677
|
+
const hasIcon = properties.iconId != null;
|
|
678
|
+
const hasText = style?.text !== void 0 && (style.text.labelProperty ? properties[style.text.labelProperty] != null : style.text.text !== void 0);
|
|
679
|
+
if (hasIcon) channels.add("icon");
|
|
680
|
+
if (hasText) channels.add("text");
|
|
681
|
+
if (!hasIcon && !hasText) channels.add("circle");
|
|
682
|
+
}
|
|
683
|
+
return channels;
|
|
684
|
+
}
|
|
598
685
|
var MapLibreAdapter = class extends BaseMapAdapter {
|
|
599
686
|
map;
|
|
600
687
|
viewChangeMode;
|
|
601
688
|
pendingIconImages = /* @__PURE__ */ new Set();
|
|
602
|
-
pendingLayerUpdates = /* @__PURE__ */ new Map();
|
|
603
|
-
deferredLayerRemovals = /* @__PURE__ */ new Map();
|
|
604
689
|
scalesGroundPointSymbolsNatively = true;
|
|
605
690
|
constructor(map, options = {}) {
|
|
606
691
|
super();
|
|
@@ -681,71 +766,176 @@ var MapLibreAdapter = class extends BaseMapAdapter {
|
|
|
681
766
|
return {
|
|
682
767
|
sourceId,
|
|
683
768
|
mlLayerIds,
|
|
769
|
+
groupBundles: /* @__PURE__ */ new Map(),
|
|
770
|
+
groupOrder: [],
|
|
771
|
+
nextGroupBundleId: 0,
|
|
684
772
|
style
|
|
685
773
|
};
|
|
686
774
|
}
|
|
687
775
|
destroyLayer(handle) {
|
|
688
|
-
|
|
689
|
-
if (layerHandle !== handle) continue;
|
|
690
|
-
this.pendingLayerUpdates.get(layerId)?.cancel();
|
|
691
|
-
this.pendingLayerUpdates.delete(layerId);
|
|
692
|
-
this.deferredLayerRemovals.delete(layerId);
|
|
693
|
-
break;
|
|
694
|
-
}
|
|
776
|
+
this.clearGroupBundles(handle);
|
|
695
777
|
for (const mlId of handle.mlLayerIds) if (this.map.getLayer(mlId)) this.map.removeLayer(mlId);
|
|
696
778
|
if (this.map.getSource(handle.sourceId)) this.map.removeSource(handle.sourceId);
|
|
697
779
|
}
|
|
698
|
-
|
|
699
|
-
const deferred = this.deferredLayerRemovals.get(layerId);
|
|
700
|
-
const isPureRemoval = ops.adds.length === 0 && ops.updates.length === 0 && (ops.removes?.length ?? 0) > 0;
|
|
701
|
-
if (deferred && isPureRemoval) {
|
|
702
|
-
deferred.after.then(() => this.flushDeferredLayerRemoval(layerId, deferred), () => this.flushDeferredLayerRemoval(layerId, deferred));
|
|
703
|
-
return;
|
|
704
|
-
}
|
|
705
|
-
if (deferred) this.deferredLayerRemovals.delete(layerId);
|
|
706
|
-
this.updateSourceData(layerId, ops.full);
|
|
707
|
-
}
|
|
708
|
-
updateSourceData(layerId, features) {
|
|
780
|
+
applyFeatureGroups(layerId, groups) {
|
|
709
781
|
const handle = this.layers.get(layerId);
|
|
710
782
|
if (!handle) return;
|
|
711
|
-
const
|
|
712
|
-
|
|
713
|
-
const
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
783
|
+
const desiredGroupIds = new Set(groups.map((group) => group.id));
|
|
784
|
+
const previousGroupOrder = handle.groupOrder.filter((groupId) => desiredGroupIds.has(groupId));
|
|
785
|
+
const bundlesNeedingOrder = /* @__PURE__ */ new Set();
|
|
786
|
+
for (const [groupId, bundle] of handle.groupBundles) {
|
|
787
|
+
if (desiredGroupIds.has(groupId)) continue;
|
|
788
|
+
this.destroyGroupBundle(bundle);
|
|
789
|
+
handle.groupBundles.delete(groupId);
|
|
790
|
+
}
|
|
791
|
+
for (const group of groups) {
|
|
792
|
+
let bundle = handle.groupBundles.get(group.id);
|
|
793
|
+
const isNewBundle = bundle === void 0;
|
|
794
|
+
if (!bundle) {
|
|
795
|
+
bundle = this.createGroupBundle(layerId, handle);
|
|
796
|
+
handle.groupBundles.set(group.id, bundle);
|
|
797
|
+
bundlesNeedingOrder.add(group.id);
|
|
798
|
+
}
|
|
799
|
+
const featureSignature = JSON.stringify(group.features);
|
|
800
|
+
const contentChanged = isNewBundle || bundle.featureSignature !== featureSignature;
|
|
801
|
+
const requiredIconIds = this.requiredIconIds(group);
|
|
802
|
+
const resourcesChanged = bundle.requiredIconIds.size !== requiredIconIds.size || [...bundle.requiredIconIds].some((id) => !requiredIconIds.has(id));
|
|
803
|
+
bundle.features = group.features;
|
|
804
|
+
bundle.featureSignature = featureSignature;
|
|
805
|
+
bundle.requiredIconIds = requiredIconIds;
|
|
806
|
+
if (this.reconcileGroupChannels(handle, bundle, groupPaintChannels(group.features, handle.style))) bundlesNeedingOrder.add(group.id);
|
|
807
|
+
if (contentChanged || resourcesChanged) this.updateGroupBundleSource(bundle);
|
|
808
|
+
}
|
|
809
|
+
handle.groupOrder = groups.map((group) => group.id);
|
|
810
|
+
this.orderGroupBundles(handle, previousGroupOrder, handle.groupOrder, bundlesNeedingOrder);
|
|
811
|
+
}
|
|
812
|
+
createGroupBundle(layerId, handle) {
|
|
813
|
+
const sourceId = `${layerId}-group-${++handle.nextGroupBundleId}-src`;
|
|
814
|
+
this.map.addSource(sourceId, {
|
|
815
|
+
type: "geojson",
|
|
816
|
+
data: emptyFC(),
|
|
817
|
+
promoteId: "_fid"
|
|
717
818
|
});
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
819
|
+
return {
|
|
820
|
+
sourceId,
|
|
821
|
+
layerIds: /* @__PURE__ */ new Map(),
|
|
822
|
+
features: [],
|
|
823
|
+
featureSignature: "[]",
|
|
824
|
+
requiredIconIds: /* @__PURE__ */ new Set()
|
|
722
825
|
};
|
|
723
|
-
|
|
826
|
+
}
|
|
827
|
+
destroyGroupBundle(bundle) {
|
|
828
|
+
for (const layerId of bundle.layerIds.values()) if (this.map.getLayer(layerId)) this.map.removeLayer(layerId);
|
|
829
|
+
if (this.map.getSource(bundle.sourceId)) this.map.removeSource(bundle.sourceId);
|
|
830
|
+
}
|
|
831
|
+
clearGroupBundles(handle) {
|
|
832
|
+
for (const bundle of handle.groupBundles.values()) this.destroyGroupBundle(bundle);
|
|
833
|
+
handle.groupBundles.clear();
|
|
834
|
+
handle.groupOrder = [];
|
|
835
|
+
}
|
|
836
|
+
requiredIconIds(group) {
|
|
837
|
+
const referenced = new Set(group.features.flatMap((feature) => {
|
|
838
|
+
const iconId = feature.properties?.iconId;
|
|
839
|
+
return typeof iconId === "string" ? [iconId] : [];
|
|
840
|
+
}));
|
|
841
|
+
return new Set(group.iconResources.map((resource) => resource.id).filter((resourceId) => referenced.has(resourceId)));
|
|
842
|
+
}
|
|
843
|
+
reconcileGroupChannels(handle, bundle, desiredChannels) {
|
|
844
|
+
let changed = false;
|
|
845
|
+
for (const channel of PAINT_CHANNELS) {
|
|
846
|
+
const layerId = bundle.layerIds.get(channel);
|
|
847
|
+
if (layerId && !desiredChannels.has(channel)) {
|
|
848
|
+
if (this.map.getLayer(layerId)) this.map.removeLayer(layerId);
|
|
849
|
+
bundle.layerIds.delete(channel);
|
|
850
|
+
changed = true;
|
|
851
|
+
} else if (!layerId && desiredChannels.has(channel)) {
|
|
852
|
+
const nextLayerId = `${bundle.sourceId}-${channel}`;
|
|
853
|
+
this.map.addLayer(this.buildGroupLayer(nextLayerId, bundle.sourceId, channel, handle.style), handle.mlLayerIds[0]);
|
|
854
|
+
bundle.layerIds.set(channel, nextLayerId);
|
|
855
|
+
changed = true;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
return changed;
|
|
859
|
+
}
|
|
860
|
+
buildGroupLayer(id, sourceId, channel, style) {
|
|
861
|
+
switch (channel) {
|
|
862
|
+
case "fill": return {
|
|
863
|
+
id,
|
|
864
|
+
type: "fill",
|
|
865
|
+
source: sourceId,
|
|
866
|
+
filter: buildFillFilter(false),
|
|
867
|
+
paint: buildFillPaint(style)
|
|
868
|
+
};
|
|
869
|
+
case "fill-pattern": return {
|
|
870
|
+
id,
|
|
871
|
+
type: "fill",
|
|
872
|
+
source: sourceId,
|
|
873
|
+
filter: buildFillFilter(true),
|
|
874
|
+
paint: FILL_PATTERN_PAINT
|
|
875
|
+
};
|
|
876
|
+
case "line": return {
|
|
877
|
+
id,
|
|
878
|
+
type: "line",
|
|
879
|
+
source: sourceId,
|
|
880
|
+
filter: [
|
|
881
|
+
"any",
|
|
882
|
+
[
|
|
883
|
+
"==",
|
|
884
|
+
"$type",
|
|
885
|
+
"LineString"
|
|
886
|
+
],
|
|
887
|
+
[
|
|
888
|
+
"==",
|
|
889
|
+
"$type",
|
|
890
|
+
"Polygon"
|
|
891
|
+
]
|
|
892
|
+
],
|
|
893
|
+
paint: buildLinePaint(style),
|
|
894
|
+
layout: {
|
|
895
|
+
"line-cap": "round",
|
|
896
|
+
"line-join": "round"
|
|
897
|
+
}
|
|
898
|
+
};
|
|
899
|
+
case "circle": return {
|
|
900
|
+
id,
|
|
901
|
+
type: "circle",
|
|
902
|
+
source: sourceId,
|
|
903
|
+
filter: buildCirclePointFilter(style),
|
|
904
|
+
paint: buildCirclePaint(style)
|
|
905
|
+
};
|
|
906
|
+
case "icon": return buildIconLayer(id, sourceId);
|
|
907
|
+
case "text":
|
|
908
|
+
if (!style?.text) throw new Error("Cannot create a MapLibre text channel without text style");
|
|
909
|
+
return buildSymbolLayer(id, sourceId, style.text);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
updateGroupBundleSource(bundle) {
|
|
913
|
+
const source = this.map.getSource(bundle.sourceId);
|
|
914
|
+
if (!source) return;
|
|
915
|
+
const features = [...bundle.requiredIconIds].every((id) => this.map.hasImage(id)) ? bundle.features.map((feature) => this.promoteFeature(feature)) : [];
|
|
724
916
|
const result = source.setData({
|
|
725
917
|
type: "FeatureCollection",
|
|
726
|
-
features
|
|
918
|
+
features
|
|
727
919
|
}, true);
|
|
728
|
-
|
|
729
|
-
if (supportsCompletionPromise) source.off("data", onSourceData);
|
|
730
|
-
const completion = supportsCompletionPromise ? Promise.resolve(result) : sourceDataCompletion;
|
|
731
|
-
const pending = {
|
|
732
|
-
completion,
|
|
733
|
-
cancel() {
|
|
734
|
-
source.off("data", onSourceData);
|
|
735
|
-
}
|
|
736
|
-
};
|
|
737
|
-
this.pendingLayerUpdates.set(layerId, pending);
|
|
738
|
-
completion.catch(() => {});
|
|
920
|
+
if (isPromiseLike(result)) Promise.resolve(result).catch(() => {});
|
|
739
921
|
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
922
|
+
orderGroupBundles(handle, previousGroupIds, desiredGroupIds, forcedGroupIds) {
|
|
923
|
+
let beforeId = handle.mlLayerIds[0];
|
|
924
|
+
if (!beforeId) return;
|
|
925
|
+
const stableGroupIds = longestStableGroupIds(previousGroupIds, desiredGroupIds);
|
|
926
|
+
for (let index = desiredGroupIds.length - 1; index >= 0; index--) {
|
|
927
|
+
const groupId = desiredGroupIds[index];
|
|
928
|
+
const bundle = handle.groupBundles.get(groupId);
|
|
929
|
+
if (!bundle) continue;
|
|
930
|
+
if (!stableGroupIds.has(groupId) || forcedGroupIds.has(groupId)) for (const channel of PAINT_CHANNELS) {
|
|
931
|
+
const layerId = bundle.layerIds.get(channel);
|
|
932
|
+
if (layerId && this.map.getLayer(layerId)) this.map.moveLayer(layerId, beforeId);
|
|
933
|
+
}
|
|
934
|
+
beforeId = firstBundleLayerId(bundle) ?? beforeId;
|
|
935
|
+
}
|
|
744
936
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
if (!pending || !this.layers.has(layerId)) return;
|
|
748
|
-
this.deferredLayerRemovals.set(layerId, { after: pending.completion });
|
|
937
|
+
nativeLayerIds(handle) {
|
|
938
|
+
return [...handle.mlLayerIds, ...[...handle.groupBundles.values()].flatMap((bundle) => [...bundle.layerIds.values()])];
|
|
749
939
|
}
|
|
750
940
|
/**
|
|
751
941
|
* Promote a source feature for MapLibre: stamp `_fid` (so data-driven paint
|
|
@@ -799,6 +989,7 @@ var MapLibreAdapter = class extends BaseMapAdapter {
|
|
|
799
989
|
if (!this.pendingIconImages.has(resource.id)) return;
|
|
800
990
|
this.pendingIconImages.delete(resource.id);
|
|
801
991
|
if (!this.map.hasImage(resource.id)) this.map.addImage(resource.id, image);
|
|
992
|
+
for (const handle of this.layers.values()) for (const bundle of handle.groupBundles.values()) if (bundle.requiredIconIds.has(resource.id)) this.updateGroupBundleSource(bundle);
|
|
802
993
|
};
|
|
803
994
|
image.onerror = () => this.pendingIconImages.delete(resource.id);
|
|
804
995
|
image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(centeredSvg(resource))}`;
|
|
@@ -956,39 +1147,72 @@ var MapLibreAdapter = class extends BaseMapAdapter {
|
|
|
956
1147
|
setLayerStyle(layerId, style) {
|
|
957
1148
|
const handle = this.layers.get(layerId);
|
|
958
1149
|
if (!handle) return;
|
|
959
|
-
|
|
1150
|
+
const resolvedStyle = {
|
|
1151
|
+
...handle.style,
|
|
1152
|
+
...style,
|
|
1153
|
+
...style.text ? { text: {
|
|
1154
|
+
...handle.style?.text,
|
|
1155
|
+
...style.text
|
|
1156
|
+
} } : {}
|
|
1157
|
+
};
|
|
1158
|
+
handle.style = resolvedStyle;
|
|
960
1159
|
for (const mlId of handle.mlLayerIds) {
|
|
961
|
-
const
|
|
962
|
-
if (
|
|
963
|
-
const type = layer.type;
|
|
964
|
-
if (type === "fill") {
|
|
965
|
-
if (mlId.endsWith("-fill-pattern")) continue;
|
|
966
|
-
this.map.setPaintProperty(mlId, "fill-color", buildFillPaint(style)["fill-color"]);
|
|
967
|
-
} else if (type === "line") {
|
|
968
|
-
const paint = buildLinePaint(style);
|
|
969
|
-
if (style.strokeColor !== void 0) this.map.setPaintProperty(mlId, "line-color", paint["line-color"]);
|
|
970
|
-
if (style.strokeWidth !== void 0) this.map.setPaintProperty(mlId, "line-width", paint["line-width"]);
|
|
971
|
-
if (style.strokeDash !== void 0) this.map.setPaintProperty(mlId, "line-dasharray", paint["line-dasharray"]);
|
|
972
|
-
} else if (type === "circle") {
|
|
973
|
-
const paint = buildCirclePaint(style);
|
|
974
|
-
this.map.setPaintProperty(mlId, "circle-radius", paint["circle-radius"]);
|
|
975
|
-
this.map.setPaintProperty(mlId, "circle-color", paint["circle-color"]);
|
|
976
|
-
this.map.setPaintProperty(mlId, "circle-stroke-color", paint["circle-stroke-color"]);
|
|
977
|
-
this.map.setPaintProperty(mlId, "circle-stroke-width", paint["circle-stroke-width"]);
|
|
978
|
-
this.map.setFilter(mlId, buildCirclePointFilter(style));
|
|
979
|
-
} else if (type === "symbol" && style.text) {
|
|
980
|
-
if (style.text.text || style.text.labelProperty) this.map.setLayoutProperty(mlId, "text-field", buildTextFieldExpression(style.text));
|
|
981
|
-
if (style.text.fillColor) this.map.setPaintProperty(mlId, "text-color", style.text.fillColor);
|
|
982
|
-
if (style.text.strokeColor) this.map.setPaintProperty(mlId, "text-halo-color", buildTextHaloColorExpression(style.text));
|
|
983
|
-
if (style.text.strokeWidth !== void 0) this.map.setPaintProperty(mlId, "text-halo-width", buildTextHaloWidthExpression(style.text));
|
|
984
|
-
}
|
|
1160
|
+
const channel = this.legacyPaintChannel(mlId);
|
|
1161
|
+
if (channel) this.applyChannelStyle(mlId, channel, style, resolvedStyle);
|
|
985
1162
|
}
|
|
1163
|
+
const bundlesNeedingOrder = /* @__PURE__ */ new Set();
|
|
1164
|
+
for (const [groupId, bundle] of handle.groupBundles) if (this.reconcileGroupChannels(handle, bundle, groupPaintChannels(bundle.features, resolvedStyle))) bundlesNeedingOrder.add(groupId);
|
|
1165
|
+
this.orderGroupBundles(handle, handle.groupOrder, handle.groupOrder, bundlesNeedingOrder);
|
|
1166
|
+
for (const bundle of handle.groupBundles.values()) for (const [channel, mlId] of bundle.layerIds) this.applyChannelStyle(mlId, channel, style, resolvedStyle);
|
|
986
1167
|
if (style.text && !handle.mlLayerIds.some((id) => id.endsWith("-symbol"))) {
|
|
987
1168
|
const symbolId = `${layerId}-symbol`;
|
|
988
|
-
this.map.addLayer(buildSymbolLayer(symbolId, handle.sourceId,
|
|
1169
|
+
this.map.addLayer(buildSymbolLayer(symbolId, handle.sourceId, resolvedStyle.text));
|
|
989
1170
|
handle.mlLayerIds.push(symbolId);
|
|
990
1171
|
}
|
|
991
1172
|
}
|
|
1173
|
+
legacyPaintChannel(layerId) {
|
|
1174
|
+
if (layerId.endsWith("-fill-pattern")) return "fill-pattern";
|
|
1175
|
+
if (layerId.endsWith("-fill")) return "fill";
|
|
1176
|
+
if (layerId.endsWith("-line")) return "line";
|
|
1177
|
+
if (layerId.endsWith("-circle")) return "circle";
|
|
1178
|
+
if (layerId.endsWith("-icon")) return "icon";
|
|
1179
|
+
if (layerId.endsWith("-symbol")) return "text";
|
|
1180
|
+
}
|
|
1181
|
+
applyChannelStyle(layerId, channel, styleUpdate, resolvedStyle) {
|
|
1182
|
+
if (!this.map.getLayer(layerId)) return;
|
|
1183
|
+
switch (channel) {
|
|
1184
|
+
case "fill":
|
|
1185
|
+
if (styleUpdate.fillColor === void 0) break;
|
|
1186
|
+
this.map.setPaintProperty(layerId, "fill-color", buildFillPaint(resolvedStyle)["fill-color"]);
|
|
1187
|
+
break;
|
|
1188
|
+
case "fill-pattern": break;
|
|
1189
|
+
case "line": {
|
|
1190
|
+
const paint = buildLinePaint(resolvedStyle);
|
|
1191
|
+
if (styleUpdate.strokeColor !== void 0) this.map.setPaintProperty(layerId, "line-color", paint["line-color"]);
|
|
1192
|
+
if (styleUpdate.strokeWidth !== void 0) this.map.setPaintProperty(layerId, "line-width", paint["line-width"]);
|
|
1193
|
+
if (styleUpdate.strokeDash !== void 0) this.map.setPaintProperty(layerId, "line-dasharray", paint["line-dasharray"]);
|
|
1194
|
+
break;
|
|
1195
|
+
}
|
|
1196
|
+
case "circle": {
|
|
1197
|
+
if (!(styleUpdate.pointRadius !== void 0 || styleUpdate.pointFillColor !== void 0 || styleUpdate.fillColor !== void 0 || styleUpdate.strokeColor !== void 0 || styleUpdate.strokeWidth !== void 0 || styleUpdate.text !== void 0)) break;
|
|
1198
|
+
const paint = buildCirclePaint(resolvedStyle);
|
|
1199
|
+
this.map.setPaintProperty(layerId, "circle-radius", paint["circle-radius"]);
|
|
1200
|
+
this.map.setPaintProperty(layerId, "circle-color", paint["circle-color"]);
|
|
1201
|
+
this.map.setPaintProperty(layerId, "circle-stroke-color", paint["circle-stroke-color"]);
|
|
1202
|
+
this.map.setPaintProperty(layerId, "circle-stroke-width", paint["circle-stroke-width"]);
|
|
1203
|
+
this.map.setFilter(layerId, buildCirclePointFilter(resolvedStyle));
|
|
1204
|
+
break;
|
|
1205
|
+
}
|
|
1206
|
+
case "icon": break;
|
|
1207
|
+
case "text":
|
|
1208
|
+
if (!styleUpdate.text || !resolvedStyle.text) break;
|
|
1209
|
+
if (styleUpdate.text.text || styleUpdate.text.labelProperty) this.map.setLayoutProperty(layerId, "text-field", buildTextFieldExpression(resolvedStyle.text));
|
|
1210
|
+
if (styleUpdate.text.fillColor) this.map.setPaintProperty(layerId, "text-color", resolvedStyle.text.fillColor);
|
|
1211
|
+
if (styleUpdate.text.strokeColor) this.map.setPaintProperty(layerId, "text-halo-color", buildTextHaloColorExpression(resolvedStyle.text));
|
|
1212
|
+
if (styleUpdate.text.strokeWidth !== void 0) this.map.setPaintProperty(layerId, "text-halo-width", buildTextHaloWidthExpression(resolvedStyle.text));
|
|
1213
|
+
break;
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
992
1216
|
toLonLat(coordinate) {
|
|
993
1217
|
return unproject(coordinate[0], coordinate[1]);
|
|
994
1218
|
}
|
|
@@ -1048,7 +1272,7 @@ var MapLibreAdapter = class extends BaseMapAdapter {
|
|
|
1048
1272
|
const tol = pickHitTolerance(originalEvent);
|
|
1049
1273
|
const { x, y } = e.point;
|
|
1050
1274
|
const bbox = [[x - tol, y - tol], [x + tol, y + tol]];
|
|
1051
|
-
const relevantFeatures = mergeNativeAndCachedPickFeatures(this.map.queryRenderedFeatures(bbox, { layers: handle
|
|
1275
|
+
const relevantFeatures = mergeNativeAndCachedPickFeatures(this.map.queryRenderedFeatures(bbox, { layers: this.nativeLayerIds(handle) }).map((feature) => ({
|
|
1052
1276
|
type: "Feature",
|
|
1053
1277
|
id: feature.id,
|
|
1054
1278
|
geometry: feature.geometry,
|
|
@@ -1058,10 +1282,12 @@ var MapLibreAdapter = class extends BaseMapAdapter {
|
|
|
1058
1282
|
const candidate = evaluatePickCandidates(relevantFeatures.map(({ feature, nativeHit, visualOrder }) => {
|
|
1059
1283
|
const iconId = feature.properties?.iconId;
|
|
1060
1284
|
const icon = typeof iconId === "string" ? this.getIconResource(iconId) : void 0;
|
|
1285
|
+
const featureGroupOrder = this.getFeatureGroupOrder(layerId, feature.id);
|
|
1061
1286
|
return {
|
|
1062
1287
|
feature,
|
|
1063
1288
|
visualOrder,
|
|
1064
1289
|
nativeHit,
|
|
1290
|
+
...featureGroupOrder !== void 0 ? { featureGroupOrder } : {},
|
|
1065
1291
|
...icon ? { icon } : {},
|
|
1066
1292
|
layerStyle: {
|
|
1067
1293
|
...handle.style,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orbat-mapper/tactical-draw-adapter-maplibre",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.20",
|
|
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.2.0-alpha.25",
|
|
41
|
-
"@orbat-mapper/tactical-draw": "^0.2.0-alpha.
|
|
41
|
+
"@orbat-mapper/tactical-draw": "^0.2.0-alpha.35"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"maplibre-gl": ">=5.8.0 <6.0.0"
|