@orbat-mapper/control-measures 0.6.0 → 0.7.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-DCgWC2Q8.d.mts → index-B3LgMKpn.d.mts} +109 -86
- package/dist/index.d.mts +3 -2
- package/dist/index.mjs +3 -2
- package/dist/patterns-CcQmmOuJ.d.mts +116 -0
- package/dist/patterns.d.mts +2 -0
- package/dist/patterns.mjs +205 -0
- package/dist/preview/index.d.mts +15 -11
- package/dist/preview/index.mjs +67 -84
- package/dist/{renderControlMeasure-D6VScGE0.mjs → renderControlMeasure-GsV4DV3r.mjs} +437 -199
- package/media/limited-access-area.svg +1 -1
- package/media/mined-area.svg +1 -0
- package/media/minefield-dynamic.svg +1 -0
- package/media/minefield.svg +1 -0
- package/media/no-fire-area-irregular.svg +1 -1
- package/package.json +2 -1
|
@@ -689,6 +689,81 @@ const staticPointDrawRule = {
|
|
|
689
689
|
id: "point2:static-anchor"
|
|
690
690
|
};
|
|
691
691
|
//#endregion
|
|
692
|
+
//#region src/draw-rules/sector.ts
|
|
693
|
+
const SECTOR_ANGLE_SNAP_RADIANS = Math.PI / 36;
|
|
694
|
+
/**
|
|
695
|
+
* Sector — anchor + inner-left edge + outer-right edge. The two radial points
|
|
696
|
+
* independently control a radius and a true-north azimuth. Dragging the anchor
|
|
697
|
+
* translates both edge points so the sector retains its size and orientation.
|
|
698
|
+
*/
|
|
699
|
+
function derive$9(points) {
|
|
700
|
+
return clonePositions(points.slice(0, 3));
|
|
701
|
+
}
|
|
702
|
+
function guidePoints$1(points) {
|
|
703
|
+
return points.length < 3 ? clonePositions(points) : [];
|
|
704
|
+
}
|
|
705
|
+
/** Snap one radial Sector handle to a true-north angular increment. */
|
|
706
|
+
function snapSectorAngle(points, activePointIndex, incrementRadians) {
|
|
707
|
+
const next = clonePositions(points);
|
|
708
|
+
if (activePointIndex !== 1 && activePointIndex !== 2) return next;
|
|
709
|
+
const anchor = next[0];
|
|
710
|
+
const point = next[activePointIndex];
|
|
711
|
+
if (!anchor || !point) return next;
|
|
712
|
+
const range = haversineDistance(anchor, point);
|
|
713
|
+
const bearing = sphericalBearing(anchor, point);
|
|
714
|
+
next[activePointIndex] = [...destinationPoint(anchor, range, Math.round(bearing / incrementRadians) * incrementRadians), ...point.slice(2)];
|
|
715
|
+
return next;
|
|
716
|
+
}
|
|
717
|
+
function constrainAngles(points, activePointIndex) {
|
|
718
|
+
return snapSectorAngle(points, activePointIndex, SECTOR_ANGLE_SNAP_RADIANS);
|
|
719
|
+
}
|
|
720
|
+
const sectorDrawRule = {
|
|
721
|
+
id: "sector:anchor-radii",
|
|
722
|
+
minimumUserPoints: 3,
|
|
723
|
+
canonicalPointCount: 3,
|
|
724
|
+
showGuide: true,
|
|
725
|
+
guidePoints: guidePoints$1,
|
|
726
|
+
constrainAngles,
|
|
727
|
+
derive: derive$9,
|
|
728
|
+
transform(event) {
|
|
729
|
+
const { previous, next, activePointIndex } = event;
|
|
730
|
+
if (activePointIndex === 0 && previous.length >= 3 && next.length >= 3) {
|
|
731
|
+
const dx = next[0][0] - previous[0][0];
|
|
732
|
+
const dy = next[0][1] - previous[0][1];
|
|
733
|
+
return [
|
|
734
|
+
clonePosition(next[0]),
|
|
735
|
+
[previous[1][0] + dx, previous[1][1] + dy],
|
|
736
|
+
[previous[2][0] + dx, previous[2][1] + dy]
|
|
737
|
+
];
|
|
738
|
+
}
|
|
739
|
+
return derive$9(next);
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
//#endregion
|
|
743
|
+
//#region src/draw-rules/point18.ts
|
|
744
|
+
const WHOLE_DEGREE_RADIANS = Math.PI / 180;
|
|
745
|
+
function snapToWholeDegrees(points) {
|
|
746
|
+
let snapped = snapSectorAngle(points, 1, WHOLE_DEGREE_RADIANS);
|
|
747
|
+
if (snapped.length >= 3) snapped = snapSectorAngle(snapped, 2, WHOLE_DEGREE_RADIANS);
|
|
748
|
+
return snapped;
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Point18 — doctrinally a numeric, single-anchor symbol. The interactive
|
|
752
|
+
* authoring model exposes those dimensions as Sector-style radial handles:
|
|
753
|
+
* P1 is the anchor, P2 fixes the start range/left bearing, and P3 fixes the
|
|
754
|
+
* stop range/right bearing.
|
|
755
|
+
*/
|
|
756
|
+
const dynamicPointDrawRule = {
|
|
757
|
+
...sectorDrawRule,
|
|
758
|
+
id: "point18:anchor-radii",
|
|
759
|
+
derive(points) {
|
|
760
|
+
return snapToWholeDegrees(sectorDrawRule.derive(points));
|
|
761
|
+
},
|
|
762
|
+
transform(event) {
|
|
763
|
+
return snapSectorAngle(sectorDrawRule.transform(event), event.activePointIndex, WHOLE_DEGREE_RADIANS);
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
//#endregion
|
|
692
767
|
//#region src/draw-rules/area7.ts
|
|
693
768
|
/**
|
|
694
769
|
* Area7 anchor draw rule for Attack By Fire.
|
|
@@ -716,20 +791,20 @@ const ambushDrawRule = createMidpointPerpendicularDrawRule({
|
|
|
716
791
|
});
|
|
717
792
|
//#endregion
|
|
718
793
|
//#region src/draw-rules/line1.ts
|
|
719
|
-
function derive$
|
|
794
|
+
function derive$8(points) {
|
|
720
795
|
return clonePositions(points);
|
|
721
796
|
}
|
|
722
797
|
const line1DrawRule = {
|
|
723
798
|
id: "line1",
|
|
724
799
|
minimumUserPoints: 2,
|
|
725
|
-
derive: derive$
|
|
800
|
+
derive: derive$8,
|
|
726
801
|
transform(event) {
|
|
727
|
-
return derive$
|
|
802
|
+
return derive$8(event.next);
|
|
728
803
|
}
|
|
729
804
|
};
|
|
730
805
|
//#endregion
|
|
731
806
|
//#region src/draw-rules/line3.ts
|
|
732
|
-
function derive$
|
|
807
|
+
function derive$7(points) {
|
|
733
808
|
return clonePositions(points);
|
|
734
809
|
}
|
|
735
810
|
/**
|
|
@@ -744,23 +819,23 @@ const line3DrawRule = {
|
|
|
744
819
|
id: "line3",
|
|
745
820
|
minimumUserPoints: 3,
|
|
746
821
|
canonicalPointCount: 3,
|
|
747
|
-
derive: derive$
|
|
822
|
+
derive: derive$7,
|
|
748
823
|
transform(event) {
|
|
749
|
-
return derive$
|
|
824
|
+
return derive$7(event.next);
|
|
750
825
|
}
|
|
751
826
|
};
|
|
752
827
|
//#endregion
|
|
753
828
|
//#region src/draw-rules/line9.ts
|
|
754
|
-
function derive$
|
|
829
|
+
function derive$6(points) {
|
|
755
830
|
return clonePositions(points);
|
|
756
831
|
}
|
|
757
832
|
const line9DrawRule = {
|
|
758
833
|
id: "line9",
|
|
759
834
|
minimumUserPoints: 2,
|
|
760
835
|
canonicalPointCount: 2,
|
|
761
|
-
derive: derive$
|
|
836
|
+
derive: derive$6,
|
|
762
837
|
transform(event) {
|
|
763
|
-
return derive$
|
|
838
|
+
return derive$6(event.next);
|
|
764
839
|
}
|
|
765
840
|
};
|
|
766
841
|
//#endregion
|
|
@@ -867,7 +942,7 @@ const line24DrawRule = createMidpointPerpendicularDrawRule({
|
|
|
867
942
|
});
|
|
868
943
|
//#endregion
|
|
869
944
|
//#region src/draw-rules/line26.ts
|
|
870
|
-
function derive$
|
|
945
|
+
function derive$5(points) {
|
|
871
946
|
if (points.length === 2) {
|
|
872
947
|
const [p1, p2] = points;
|
|
873
948
|
const dx = p2[0] - p1[0];
|
|
@@ -906,9 +981,9 @@ const line26DrawRule = {
|
|
|
906
981
|
minimumUserPoints: 4,
|
|
907
982
|
minimumPreviewPoints: 2,
|
|
908
983
|
canonicalPointCount: 4,
|
|
909
|
-
derive: derive$
|
|
984
|
+
derive: derive$5,
|
|
910
985
|
transform(event) {
|
|
911
|
-
return derive$
|
|
986
|
+
return derive$5(event.next);
|
|
912
987
|
}
|
|
913
988
|
};
|
|
914
989
|
//#endregion
|
|
@@ -921,7 +996,7 @@ function deriveArcPoint(p1, p2, p4) {
|
|
|
921
996
|
if (!arc) return [(p2[0] + p4[0]) / 2, (p2[1] + p4[1]) / 2];
|
|
922
997
|
return unproject(arc.midpoint[0], arc.midpoint[1]);
|
|
923
998
|
}
|
|
924
|
-
function derive$
|
|
999
|
+
function derive$4(points) {
|
|
925
1000
|
if (points.length < 2) return clonePositions(points);
|
|
926
1001
|
const [p1, p2] = points;
|
|
927
1002
|
if (points.length === 2) {
|
|
@@ -960,7 +1035,7 @@ const line27DrawRule = {
|
|
|
960
1035
|
minimumUserPoints: 3,
|
|
961
1036
|
minimumPreviewPoints: 2,
|
|
962
1037
|
canonicalPointCount: 4,
|
|
963
|
-
derive: derive$
|
|
1038
|
+
derive: derive$4,
|
|
964
1039
|
transform(event) {
|
|
965
1040
|
const { previous, next, activePointIndex } = event;
|
|
966
1041
|
if (activePointIndex === 0 && previous.length >= 4 && next.length >= 4) {
|
|
@@ -993,7 +1068,7 @@ const line27DrawRule = {
|
|
|
993
1068
|
};
|
|
994
1069
|
//#endregion
|
|
995
1070
|
//#region src/draw-rules/area8.ts
|
|
996
|
-
function derive$
|
|
1071
|
+
function derive$3(points) {
|
|
997
1072
|
if (points.length < 2) return clonePositions(points);
|
|
998
1073
|
if (points.length !== 2) return clonePositions(points.slice(0, 4));
|
|
999
1074
|
const p1 = clonePosition(points[0]);
|
|
@@ -1017,16 +1092,16 @@ const supportByFireDrawRule = {
|
|
|
1017
1092
|
id: "area8:support-by-fire",
|
|
1018
1093
|
minimumUserPoints: 2,
|
|
1019
1094
|
canonicalPointCount: 4,
|
|
1020
|
-
derive: derive$
|
|
1095
|
+
derive: derive$3,
|
|
1021
1096
|
transform(event) {
|
|
1022
1097
|
const { next } = event;
|
|
1023
1098
|
if (next.length === 4) return clonePositions(next);
|
|
1024
|
-
return derive$
|
|
1099
|
+
return derive$3(next);
|
|
1025
1100
|
}
|
|
1026
1101
|
};
|
|
1027
1102
|
//#endregion
|
|
1028
1103
|
//#region src/draw-rules/axis1.ts
|
|
1029
|
-
function derive$
|
|
1104
|
+
function derive$2(points) {
|
|
1030
1105
|
if (points.length < 2) return clonePositions(points);
|
|
1031
1106
|
if (points.length === 2) {
|
|
1032
1107
|
const tip = clonePosition(points[0]);
|
|
@@ -1064,10 +1139,10 @@ const axis1DrawRule = {
|
|
|
1064
1139
|
id: "axis1",
|
|
1065
1140
|
minimumUserPoints: 2,
|
|
1066
1141
|
trailingFixedSlots: 1,
|
|
1067
|
-
derive: derive$
|
|
1142
|
+
derive: derive$2,
|
|
1068
1143
|
transform(event) {
|
|
1069
1144
|
const { previous, next, activePointIndex } = event;
|
|
1070
|
-
if (next.length < 3 || previous.length < 3) return derive$
|
|
1145
|
+
if (next.length < 3 || previous.length < 3) return derive$2(next);
|
|
1071
1146
|
if (!(activePointIndex === 0 || activePointIndex === 1)) return clonePositions(next);
|
|
1072
1147
|
const metrics = calculateMetrics(previous);
|
|
1073
1148
|
if (!metrics) return clonePositions(next);
|
|
@@ -1120,7 +1195,7 @@ const counterattackByFireDrawRule = {
|
|
|
1120
1195
|
};
|
|
1121
1196
|
//#endregion
|
|
1122
1197
|
//#region src/draw-rules/area21.ts
|
|
1123
|
-
function derive$
|
|
1198
|
+
function derive$1(points) {
|
|
1124
1199
|
if (points.length === 2) return clonePositions([
|
|
1125
1200
|
points[0],
|
|
1126
1201
|
points[1],
|
|
@@ -1133,9 +1208,9 @@ const searchAreaDrawRule = {
|
|
|
1133
1208
|
minimumUserPoints: 3,
|
|
1134
1209
|
minimumPreviewPoints: 2,
|
|
1135
1210
|
canonicalPointCount: 3,
|
|
1136
|
-
derive: derive$
|
|
1211
|
+
derive: derive$1,
|
|
1137
1212
|
transform(event) {
|
|
1138
|
-
return derive$
|
|
1213
|
+
return derive$1(event.next);
|
|
1139
1214
|
}
|
|
1140
1215
|
};
|
|
1141
1216
|
//#endregion
|
|
@@ -1145,7 +1220,7 @@ const searchAreaDrawRule = {
|
|
|
1145
1220
|
* edge. P3 is the next corner, constrained to the perpendicular through P2.
|
|
1146
1221
|
* The generator derives the fourth corner.
|
|
1147
1222
|
*/
|
|
1148
|
-
function derive
|
|
1223
|
+
function derive(points) {
|
|
1149
1224
|
if (points.length < 2) return clonePositions(points);
|
|
1150
1225
|
const p1 = clonePosition(points[0]);
|
|
1151
1226
|
const p2 = clonePosition(points[1]);
|
|
@@ -1164,8 +1239,8 @@ function derive$1(points) {
|
|
|
1164
1239
|
frame?.pointAtNormalDistance(frame.signedNormalDistance(points[2])) ?? clonePosition(points[2])
|
|
1165
1240
|
];
|
|
1166
1241
|
}
|
|
1167
|
-
function guidePoints
|
|
1168
|
-
const canonical = derive
|
|
1242
|
+
function guidePoints(points) {
|
|
1243
|
+
const canonical = derive(points);
|
|
1169
1244
|
if (canonical.length < 3) return canonical;
|
|
1170
1245
|
const [c1, c2, c3] = canonical;
|
|
1171
1246
|
if (!c1 || !c2 || !c3) return canonical;
|
|
@@ -1191,16 +1266,16 @@ const rectangleDrawRule = {
|
|
|
1191
1266
|
minimumPreviewPoints: 2,
|
|
1192
1267
|
canonicalPointCount: 3,
|
|
1193
1268
|
showGuide: true,
|
|
1194
|
-
guidePoints
|
|
1195
|
-
derive
|
|
1269
|
+
guidePoints,
|
|
1270
|
+
derive,
|
|
1196
1271
|
transform(event) {
|
|
1197
1272
|
const { previous, next, activePointIndex } = event;
|
|
1198
|
-
if (previous.length < 3 || next.length < 3) return derive
|
|
1273
|
+
if (previous.length < 3 || next.length < 3) return derive(next);
|
|
1199
1274
|
const frame = createBaselineFrame(previous[0], previous[1], {
|
|
1200
1275
|
origin: "p2",
|
|
1201
1276
|
normal: "right"
|
|
1202
1277
|
});
|
|
1203
|
-
if (!frame) return derive
|
|
1278
|
+
if (!frame) return derive(next);
|
|
1204
1279
|
if (activePointIndex === 0) return [
|
|
1205
1280
|
snapToAxis(next[0], frame.p2, frame.direction),
|
|
1206
1281
|
clonePosition(previous[1]),
|
|
@@ -1225,53 +1300,6 @@ const rectangleDrawRule = {
|
|
|
1225
1300
|
p3
|
|
1226
1301
|
];
|
|
1227
1302
|
}
|
|
1228
|
-
return derive$1(next);
|
|
1229
|
-
}
|
|
1230
|
-
};
|
|
1231
|
-
//#endregion
|
|
1232
|
-
//#region src/draw-rules/sector.ts
|
|
1233
|
-
const SECTOR_ANGLE_SNAP_RADIANS = Math.PI / 36;
|
|
1234
|
-
/**
|
|
1235
|
-
* Sector — anchor + inner-left edge + outer-right edge. The two radial points
|
|
1236
|
-
* independently control a radius and a true-north azimuth. Dragging the anchor
|
|
1237
|
-
* translates both edge points so the sector retains its size and orientation.
|
|
1238
|
-
*/
|
|
1239
|
-
function derive(points) {
|
|
1240
|
-
return clonePositions(points.slice(0, 3));
|
|
1241
|
-
}
|
|
1242
|
-
function guidePoints(points) {
|
|
1243
|
-
return points.length < 3 ? clonePositions(points) : [];
|
|
1244
|
-
}
|
|
1245
|
-
function constrainAngles(points, activePointIndex) {
|
|
1246
|
-
const next = clonePositions(points);
|
|
1247
|
-
if (activePointIndex !== 1 && activePointIndex !== 2) return next;
|
|
1248
|
-
const anchor = next[0];
|
|
1249
|
-
const point = next[activePointIndex];
|
|
1250
|
-
if (!anchor || !point) return next;
|
|
1251
|
-
const range = haversineDistance(anchor, point);
|
|
1252
|
-
const bearing = sphericalBearing(anchor, point);
|
|
1253
|
-
next[activePointIndex] = [...destinationPoint(anchor, range, Math.round(bearing / SECTOR_ANGLE_SNAP_RADIANS) * SECTOR_ANGLE_SNAP_RADIANS), ...point.slice(2)];
|
|
1254
|
-
return next;
|
|
1255
|
-
}
|
|
1256
|
-
const sectorDrawRule = {
|
|
1257
|
-
id: "sector:anchor-radii",
|
|
1258
|
-
minimumUserPoints: 3,
|
|
1259
|
-
canonicalPointCount: 3,
|
|
1260
|
-
showGuide: true,
|
|
1261
|
-
guidePoints,
|
|
1262
|
-
constrainAngles,
|
|
1263
|
-
derive,
|
|
1264
|
-
transform(event) {
|
|
1265
|
-
const { previous, next, activePointIndex } = event;
|
|
1266
|
-
if (activePointIndex === 0 && previous.length >= 3 && next.length >= 3) {
|
|
1267
|
-
const dx = next[0][0] - previous[0][0];
|
|
1268
|
-
const dy = next[0][1] - previous[0][1];
|
|
1269
|
-
return [
|
|
1270
|
-
clonePosition(next[0]),
|
|
1271
|
-
[previous[1][0] + dx, previous[1][1] + dy],
|
|
1272
|
-
[previous[2][0] + dx, previous[2][1] + dy]
|
|
1273
|
-
];
|
|
1274
|
-
}
|
|
1275
1303
|
return derive(next);
|
|
1276
1304
|
}
|
|
1277
1305
|
};
|
|
@@ -2103,6 +2131,43 @@ function buildGappedLine(verts, gaps) {
|
|
|
2103
2131
|
return parts;
|
|
2104
2132
|
}
|
|
2105
2133
|
//#endregion
|
|
2134
|
+
//#region src/style.ts
|
|
2135
|
+
/**
|
|
2136
|
+
* Per-feature style hint pinning a filled part to a solid interior. Attach to
|
|
2137
|
+
* a generator's intrinsic doctrinal accents — arrowheads, teeth, echelon
|
|
2138
|
+
* glyphs, barbs, blades — so a patterned (`hatch`, `dots`, …) fill set at the graphicsStyle
|
|
2139
|
+
* or measure layer never bleeds into a small silhouette and ruins its
|
|
2140
|
+
* legibility. The renderer only reads style hints, so one shared instance is
|
|
2141
|
+
* safe to reuse across every accent feature.
|
|
2142
|
+
*/
|
|
2143
|
+
const SOLID_ACCENT_FILL = { fillPattern: "solid" };
|
|
2144
|
+
/**
|
|
2145
|
+
* Ultimate fallback for the symbol color when no `color` or per-channel
|
|
2146
|
+
* override is supplied at any layer. Keeps a zero-config render monocolor
|
|
2147
|
+
* black and guarantees filled parts still render filled. See ADR-0011.
|
|
2148
|
+
*/
|
|
2149
|
+
const DEFAULT_SYMBOL_COLOR = "#000000";
|
|
2150
|
+
//#endregion
|
|
2151
|
+
//#region src/portrayal.ts
|
|
2152
|
+
const DEFAULT_STROKE_WIDTH_CSS_PIXELS = 2;
|
|
2153
|
+
const DEFAULT_STROKE_DASH_CSS_PIXELS = Object.freeze([]);
|
|
2154
|
+
const DEFAULT_LINE_CAP = "round";
|
|
2155
|
+
const DEFAULT_LINE_JOIN = "round";
|
|
2156
|
+
const DEFAULT_LABEL_HEIGHT_CSS_PIXELS = 14;
|
|
2157
|
+
const DEFAULT_LABEL_SIZE_CLAMP_CSS_PIXELS = {
|
|
2158
|
+
min: 8,
|
|
2159
|
+
max: 24
|
|
2160
|
+
};
|
|
2161
|
+
/** Shared absent-value portrayal defaults; render output remains sparse. */
|
|
2162
|
+
const DEFAULT_PORTRAYAL = Object.freeze({
|
|
2163
|
+
symbolColor: DEFAULT_SYMBOL_COLOR,
|
|
2164
|
+
strokeWidthCssPixels: 2,
|
|
2165
|
+
strokeDashCssPixels: DEFAULT_STROKE_DASH_CSS_PIXELS,
|
|
2166
|
+
lineCap: DEFAULT_LINE_CAP,
|
|
2167
|
+
lineJoin: DEFAULT_LINE_JOIN,
|
|
2168
|
+
labelHeightCssPixels: 14
|
|
2169
|
+
});
|
|
2170
|
+
//#endregion
|
|
2106
2171
|
//#region src/internal/angle-utils.ts
|
|
2107
2172
|
/**
|
|
2108
2173
|
* Wraps an angle (radians) into the half-open range (-π, π].
|
|
@@ -2613,6 +2678,39 @@ const LABEL_CHAR_ASPECT_RATIO = .6;
|
|
|
2613
2678
|
function estimatedTextWidth(text, textHeightMeters) {
|
|
2614
2679
|
return text.length * LABEL_CHAR_ASPECT_RATIO * textHeightMeters;
|
|
2615
2680
|
}
|
|
2681
|
+
/** Resolve the label's final display size, then measure and convert it to construction metres. */
|
|
2682
|
+
function resolveTextMetrics(text, options, context, style = "regular") {
|
|
2683
|
+
const legacyHeight = resolveLabelOffsetMeters(options, 1);
|
|
2684
|
+
const constructionScale = context?.constructionMetersPerCssPixel;
|
|
2685
|
+
if (!(constructionScale !== void 0 && constructionScale > 0)) return {
|
|
2686
|
+
width: estimatedTextWidth(text, legacyHeight),
|
|
2687
|
+
height: legacyHeight
|
|
2688
|
+
};
|
|
2689
|
+
let sizeCssPixels;
|
|
2690
|
+
if (options.labelSizePixels !== void 0) sizeCssPixels = options.labelSizePixels;
|
|
2691
|
+
else if (options.labelSize !== void 0) {
|
|
2692
|
+
const requested = options.labelSize / constructionScale;
|
|
2693
|
+
const requestedBand = context?.labelSizeClampCssPixels;
|
|
2694
|
+
const band = requestedBand && Number.isFinite(requestedBand.min) && requestedBand.min >= 0 && Number.isFinite(requestedBand.max) && requestedBand.max >= 0 ? requestedBand : DEFAULT_LABEL_SIZE_CLAMP_CSS_PIXELS;
|
|
2695
|
+
const min = Math.min(band.min, band.max);
|
|
2696
|
+
const max = Math.max(band.min, band.max);
|
|
2697
|
+
sizeCssPixels = Math.min(max, Math.max(min, requested));
|
|
2698
|
+
} else sizeCssPixels = 14;
|
|
2699
|
+
const fallbackHeight = sizeCssPixels * constructionScale;
|
|
2700
|
+
const measured = context?.measureText?.({
|
|
2701
|
+
text,
|
|
2702
|
+
style,
|
|
2703
|
+
sizeCssPixels
|
|
2704
|
+
});
|
|
2705
|
+
if (!measured || !Number.isFinite(measured.widthCssPixels) || measured.widthCssPixels < 0 || !Number.isFinite(measured.heightCssPixels) || measured.heightCssPixels < 0) return {
|
|
2706
|
+
width: estimatedTextWidth(text, fallbackHeight),
|
|
2707
|
+
height: fallbackHeight
|
|
2708
|
+
};
|
|
2709
|
+
return {
|
|
2710
|
+
width: measured.widthCssPixels * constructionScale,
|
|
2711
|
+
height: measured.heightCssPixels * constructionScale
|
|
2712
|
+
};
|
|
2713
|
+
}
|
|
2616
2714
|
/** The end-label text for a phase-line naming, or `""` when unnamed. */
|
|
2617
2715
|
function phaseLineLabelText(name, includePrefix) {
|
|
2618
2716
|
if (!name) return "";
|
|
@@ -2624,17 +2722,17 @@ function phaseLineLabelText(name, includePrefix) {
|
|
|
2624
2722
|
* {@link pushEndLabels}. When another beyond-tip label is present, its
|
|
2625
2723
|
* estimated width is added so the hostile marker remains outermost.
|
|
2626
2724
|
*/
|
|
2627
|
-
function pushHostileEndLabels(out, verts, hostileText, options = {}, occupiedTextForEnd = "") {
|
|
2725
|
+
function pushHostileEndLabels(out, verts, hostileText, options = {}, occupiedTextForEnd = "", context) {
|
|
2628
2726
|
if (!hostileText) return;
|
|
2629
2727
|
const frames = endFrames(verts);
|
|
2630
2728
|
const sizeProps = labelSizeProps(options);
|
|
2631
|
-
const textHeight = resolveLabelOffsetMeters(options, 1);
|
|
2632
|
-
const clearance = resolveLabelOffsetMeters(options, END_LABEL_CLEARANCE_RATIO + Math.max(0, options.labelPadding ?? 0));
|
|
2633
2729
|
for (const end of ["start", "end"]) {
|
|
2634
2730
|
const frame = frames[end];
|
|
2635
2731
|
if (!frame) continue;
|
|
2636
2732
|
const occupiedText = typeof occupiedTextForEnd === "string" ? occupiedTextForEnd : occupiedTextForEnd(end);
|
|
2637
|
-
const
|
|
2733
|
+
const metrics = resolveTextMetrics(occupiedText || hostileText, options, context);
|
|
2734
|
+
const clearance = (END_LABEL_CLEARANCE_RATIO + Math.max(0, options.labelPadding ?? 0)) * metrics.height;
|
|
2735
|
+
const distance = clearance + (occupiedText ? metrics.width + clearance : 0);
|
|
2638
2736
|
pushLabel(out, vecAdd(frame.point, vecScale(frame.along, distance)), hostileText, labelRotationAlong(frame.along), sizeProps, frame.along[0] >= 0 ? "start" : "end", "N", `label:N:${end}`);
|
|
2639
2737
|
}
|
|
2640
2738
|
}
|
|
@@ -2693,7 +2791,7 @@ function pushEndLabelsAbove(out, verts, textForEnd, options = {}) {
|
|
|
2693
2791
|
* (via {@link pushEndLabels} or {@link pushEndLabelsAbove}, per `placement`)
|
|
2694
2792
|
* as a complete FeatureCollection.
|
|
2695
2793
|
*/
|
|
2696
|
-
function fixedLabelLineFeatures(positions, options, textForEnd, hostileText) {
|
|
2794
|
+
function fixedLabelLineFeatures(positions, options, textForEnd, hostileText, context) {
|
|
2697
2795
|
const { part, placement = "beyond", phaseLineName, includePrefix = true, smooth, smoothResolution, ...sizeOptions } = options;
|
|
2698
2796
|
const verts = smoothLineVerts(positions.map((p) => project(p[0], p[1])), {
|
|
2699
2797
|
smooth,
|
|
@@ -2713,7 +2811,7 @@ function fixedLabelLineFeatures(positions, options, textForEnd, hostileText) {
|
|
|
2713
2811
|
else pushEndLabels(labelFeatures, verts, textForEnd, sizeOptions);
|
|
2714
2812
|
const nameText = phaseLineLabelText(phaseLineName?.trim(), includePrefix);
|
|
2715
2813
|
if (nameText) pushEndLabels(labelFeatures, verts, nameText, sizeOptions);
|
|
2716
|
-
pushHostileEndLabels(labelFeatures, verts, hostileText, sizeOptions, nameText || (placement === "beyond" ? textForEnd : ""));
|
|
2814
|
+
pushHostileEndLabels(labelFeatures, verts, hostileText, sizeOptions, nameText || (placement === "beyond" ? textForEnd : ""), context);
|
|
2717
2815
|
features.push(...labelFeatures);
|
|
2718
2816
|
return {
|
|
2719
2817
|
type: "FeatureCollection",
|
|
@@ -2879,10 +2977,9 @@ function nonNegative$1(value, fallback) {
|
|
|
2879
2977
|
* later `buildGappedLine` walk); `segments`/`totalLength` describe the full
|
|
2880
2978
|
* route that anchors the default label placement.
|
|
2881
2979
|
*/
|
|
2882
|
-
function directionLabelGap(verts, segments, totalLength, fraction, text, options, placement = {}) {
|
|
2980
|
+
function directionLabelGap(verts, segments, totalLength, fraction, text, options, placement = {}, context) {
|
|
2883
2981
|
if (!text) return void 0;
|
|
2884
|
-
const textHeight =
|
|
2885
|
-
const textWidth = estimatedTextWidth(text, textHeight);
|
|
2982
|
+
const { width: textWidth, height: textHeight } = resolveTextMetrics(text, options, context);
|
|
2886
2983
|
const clearance = (LABEL_GAP_CLEARANCE_RATIO + Math.max(0, options.labelPadding ?? 0)) * textHeight;
|
|
2887
2984
|
const frame = pointAlongPolyline(segments, totalLength, fraction);
|
|
2888
2985
|
const rotation = placement.rotation ?? (frame ? labelRotationAlong(frame.along) : 0);
|
|
@@ -2943,7 +3040,7 @@ function createDirectionOfAttackLine(coordinates, options, textAmplifiers, confi
|
|
|
2943
3040
|
shaftVerts.push(innerTip);
|
|
2944
3041
|
}
|
|
2945
3042
|
const labelPosition = clampLinePosition(options.labelPosition, DEFAULT_DIRECTION_LABEL_POSITION);
|
|
2946
|
-
const labelGap = directionLabelGap(shaftVerts, segments, totalLength, labelPosition, textAmplifiers.T, options, resolveAmplifierPlacement(context.amplifierPlacements?.T));
|
|
3043
|
+
const labelGap = directionLabelGap(shaftVerts, segments, totalLength, labelPosition, textAmplifiers.T, options, resolveAmplifierPlacement(context.amplifierPlacements?.T), context);
|
|
2947
3044
|
const lines = [...buildGappedLine(shaftVerts, labelGap ? [labelGap] : []), arrowhead.map((point) => unproject(point[0], point[1]))];
|
|
2948
3045
|
const features = [{
|
|
2949
3046
|
type: "Feature",
|
|
@@ -3227,7 +3324,7 @@ function createDirectionOfAttackAviation(coordinates, options = {}, textAmplifie
|
|
|
3227
3324
|
const bowTieB = vecSub(bowTieStart, vecScale(bowTiePerp, bowTieHalfWidth));
|
|
3228
3325
|
const bowTieC = vecAdd(bowTieEnd, vecScale(bowTiePerp, bowTieHalfWidth));
|
|
3229
3326
|
const bowTieD = vecSub(bowTieEnd, vecScale(bowTiePerp, bowTieHalfWidth));
|
|
3230
|
-
const labelGap = directionLabelGap(verts, segments, totalLength, labelPosition, textAmplifiers.T, resolved, resolveAmplifierPlacement(context.amplifierPlacements?.T));
|
|
3327
|
+
const labelGap = directionLabelGap(verts, segments, totalLength, labelPosition, textAmplifiers.T, resolved, resolveAmplifierPlacement(context.amplifierPlacements?.T), context);
|
|
3231
3328
|
const features = [{
|
|
3232
3329
|
type: "Feature",
|
|
3233
3330
|
properties: { part: "direction-of-attack-aviation" },
|
|
@@ -3807,9 +3904,12 @@ function ringLabelAnchor(verts) {
|
|
|
3807
3904
|
* overlaps the finite contacted edge; intrinsic markers always mask. Callers
|
|
3808
3905
|
* own marker names, anchors, rotation, clearance, placement keys, and fields.
|
|
3809
3906
|
*/
|
|
3810
|
-
function pushBoundaryMarkers(out, maskVerts, text, anchors, options, amplifierPlacements) {
|
|
3907
|
+
function pushBoundaryMarkers(out, maskVerts, text, anchors, options, amplifierPlacements, context, labelOptions) {
|
|
3811
3908
|
const { rotation, textHeight, clearance, sizeProps } = options;
|
|
3812
|
-
const
|
|
3909
|
+
const metrics = resolveTextMetrics(text, labelOptions ?? { labelSize: textHeight }, context);
|
|
3910
|
+
const textWidth = metrics.width;
|
|
3911
|
+
const resolvedTextHeight = metrics.height;
|
|
3912
|
+
const resolvedClearance = textHeight > 0 ? clearance * (resolvedTextHeight / textHeight) : clearance;
|
|
3813
3913
|
const gaps = [];
|
|
3814
3914
|
for (const anchor of anchors) {
|
|
3815
3915
|
const labelPlacementKey = options.placementKeyPrefix ? `${options.placementKeyPrefix}:${anchor.side}` : void 0;
|
|
@@ -3819,21 +3919,21 @@ function pushBoundaryMarkers(out, maskVerts, text, anchors, options, amplifierPl
|
|
|
3819
3919
|
pushLabel(out, labelPoint, text, options.followPlacementRotation ? boxRotation : rotation, sizeProps, void 0, options.amplifierField, labelPlacementKey);
|
|
3820
3920
|
const contact = placement.position !== void 0 || options.rescanDefaultAnchors === true || anchor.contact === void 0 ? nearestPointOnPolyline(maskVerts, labelPoint) : anchor.contact;
|
|
3821
3921
|
if (!contact) continue;
|
|
3822
|
-
if (placement.position && !labelBoxIntersectsSegment(labelPoint, ...contact.edge, textWidth,
|
|
3823
|
-
const tangentExtent = labelHalfExtent(contact.along, textWidth,
|
|
3922
|
+
if (placement.position && !labelBoxIntersectsSegment(labelPoint, ...contact.edge, textWidth, resolvedTextHeight, boxRotation)) continue;
|
|
3923
|
+
const tangentExtent = labelHalfExtent(contact.along, textWidth, resolvedTextHeight, boxRotation) + resolvedClearance;
|
|
3824
3924
|
pushWrappedGap(gaps, contact.arc - tangentExtent, contact.arc + tangentExtent, contact.totalArc);
|
|
3825
3925
|
}
|
|
3826
3926
|
return gaps;
|
|
3827
3927
|
}
|
|
3828
3928
|
/** Emits movable Field N markers using the generic boundary-marker knockout path. */
|
|
3829
|
-
function pushHostileMarkers(out, maskVerts, text, anchors, options, amplifierPlacements) {
|
|
3929
|
+
function pushHostileMarkers(out, maskVerts, text, anchors, options, amplifierPlacements, context, labelOptions) {
|
|
3830
3930
|
return pushBoundaryMarkers(out, maskVerts, text, anchors, {
|
|
3831
3931
|
...options,
|
|
3832
3932
|
placementKeyPrefix: "label:N",
|
|
3833
3933
|
amplifierField: "N"
|
|
3834
|
-
}, amplifierPlacements);
|
|
3934
|
+
}, amplifierPlacements, context, labelOptions);
|
|
3835
3935
|
}
|
|
3836
|
-
function pushAreaLabels(out, ringVerts, texts, options = {}, amplifierPlacements, maskBoundary) {
|
|
3936
|
+
function pushAreaLabels(out, ringVerts, texts, options = {}, amplifierPlacements, maskBoundary, context) {
|
|
3837
3937
|
if (ringVerts.length < 2) return [];
|
|
3838
3938
|
const sizeProps = labelSizeProps(options);
|
|
3839
3939
|
const padding = Math.max(0, options.labelPadding ?? 0);
|
|
@@ -3876,7 +3976,7 @@ function pushAreaLabels(out, ringVerts, texts, options = {}, amplifierPlacements
|
|
|
3876
3976
|
clearance: (ENY_CLEARANCE_RATIO + padding) * textHeight + Math.max(0, maskBoundary?.extraClearanceMeters ?? 0),
|
|
3877
3977
|
sizeProps,
|
|
3878
3978
|
rescanDefaultAnchors: maskRingVerts !== ringVerts
|
|
3879
|
-
}, amplifierPlacements);
|
|
3979
|
+
}, amplifierPlacements, context, options);
|
|
3880
3980
|
}
|
|
3881
3981
|
/**
|
|
3882
3982
|
* Boundary + fill features for a pattern-filled area (e.g. Limited Access Area,
|
|
@@ -3955,11 +4055,11 @@ function boundaryFeature(verts, gaps) {
|
|
|
3955
4055
|
* echelon-masked boundary, ADR-0023) instead of a closed `Polygon` — the
|
|
3956
4056
|
* common (no `N`) case is unaffected.
|
|
3957
4057
|
*/
|
|
3958
|
-
function labeledAreaFeatures(positions, options, texts, amplifierPlacements) {
|
|
4058
|
+
function labeledAreaFeatures(positions, options, texts, amplifierPlacements, context) {
|
|
3959
4059
|
const { smooth = false, smoothResolution = DEFAULT_SMOOTH_RESOLUTION$9, ...labelOptions } = options;
|
|
3960
4060
|
const verts = buildClosedRing(positions, smooth, smoothResolution, DEFAULT_SMOOTH_RESOLUTION$9);
|
|
3961
4061
|
const labelFeatures = [];
|
|
3962
|
-
const features = [boundaryFeature(verts, pushAreaLabels(labelFeatures, verts, texts, labelOptions, amplifierPlacements))];
|
|
4062
|
+
const features = [boundaryFeature(verts, pushAreaLabels(labelFeatures, verts, texts, labelOptions, amplifierPlacements, void 0, context))];
|
|
3963
4063
|
features.push(...labelFeatures);
|
|
3964
4064
|
return {
|
|
3965
4065
|
type: "FeatureCollection",
|
|
@@ -4003,7 +4103,7 @@ const AREA_METADATA = {
|
|
|
4003
4103
|
* marker, per {@link labeledAreaFeatures}.
|
|
4004
4104
|
*/
|
|
4005
4105
|
function createArea(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
4006
|
-
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, ""), context.amplifierPlacements);
|
|
4106
|
+
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, ""), context.amplifierPlacements, context);
|
|
4007
4107
|
}
|
|
4008
4108
|
const AREA = defineControlMeasure({
|
|
4009
4109
|
metadata: AREA_METADATA,
|
|
@@ -4022,23 +4122,6 @@ const AREA = defineControlMeasure({
|
|
|
4022
4122
|
}
|
|
4023
4123
|
});
|
|
4024
4124
|
//#endregion
|
|
4025
|
-
//#region src/style.ts
|
|
4026
|
-
/**
|
|
4027
|
-
* Per-feature style hint pinning a filled part to a solid interior. Attach to
|
|
4028
|
-
* a generator's intrinsic doctrinal accents — arrowheads, teeth, echelon
|
|
4029
|
-
* glyphs, barbs, blades — so a patterned (`hatch`, `dots`, …) fill set at the graphicsStyle
|
|
4030
|
-
* or measure layer never bleeds into a small silhouette and ruins its
|
|
4031
|
-
* legibility. The renderer only reads style hints, so one shared instance is
|
|
4032
|
-
* safe to reuse across every accent feature.
|
|
4033
|
-
*/
|
|
4034
|
-
const SOLID_ACCENT_FILL = { fillPattern: "solid" };
|
|
4035
|
-
/**
|
|
4036
|
-
* Ultimate fallback for the symbol color when no `color` or per-channel
|
|
4037
|
-
* override is supplied at any layer. Keeps a zero-config render monocolor
|
|
4038
|
-
* black and guarantees filled parts still render filled. See ADR-0011.
|
|
4039
|
-
*/
|
|
4040
|
-
const DEFAULT_SYMBOL_COLOR = "#000000";
|
|
4041
|
-
//#endregion
|
|
4042
4125
|
//#region src/generators/cm15-maneuver-areas/area-defense.ts
|
|
4043
4126
|
const DEFAULT_AREA_DEFENSE_OPTIONS = {
|
|
4044
4127
|
arrowOpeningAngle: 30,
|
|
@@ -4283,7 +4366,7 @@ const AREA_OF_OPERATIONS_METADATA = {
|
|
|
4283
4366
|
* `H`/`W`/`W1`/`N` amplifier rows, per {@link labeledAreaFeatures}.
|
|
4284
4367
|
*/
|
|
4285
4368
|
function createAreaOfOperations(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
4286
|
-
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "AO"), context.amplifierPlacements);
|
|
4369
|
+
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "AO"), context.amplifierPlacements, context);
|
|
4287
4370
|
}
|
|
4288
4371
|
const AREA_OF_OPERATIONS = defineControlMeasure({
|
|
4289
4372
|
metadata: AREA_OF_OPERATIONS_METADATA,
|
|
@@ -4342,7 +4425,7 @@ const NAMED_AREA_OF_INTEREST_METADATA = {
|
|
|
4342
4425
|
* an `N` (Field N) ENY marker, per {@link labeledAreaFeatures}.
|
|
4343
4426
|
*/
|
|
4344
4427
|
function createNamedAreaOfInterest(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
4345
|
-
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "NAI"), context.amplifierPlacements);
|
|
4428
|
+
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "NAI"), context.amplifierPlacements, context);
|
|
4346
4429
|
}
|
|
4347
4430
|
const NAMED_AREA_OF_INTEREST = defineControlMeasure({
|
|
4348
4431
|
metadata: NAMED_AREA_OF_INTEREST_METADATA,
|
|
@@ -4401,7 +4484,7 @@ const TARGET_AREA_OF_INTEREST_METADATA = {
|
|
|
4401
4484
|
* an `N` (Field N) ENY marker, per {@link labeledAreaFeatures}.
|
|
4402
4485
|
*/
|
|
4403
4486
|
function createTargetAreaOfInterest(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
4404
|
-
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "TAI"), context.amplifierPlacements);
|
|
4487
|
+
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "TAI"), context.amplifierPlacements, context);
|
|
4405
4488
|
}
|
|
4406
4489
|
const TARGET_AREA_OF_INTEREST = defineControlMeasure({
|
|
4407
4490
|
metadata: TARGET_AREA_OF_INTEREST_METADATA,
|
|
@@ -4496,7 +4579,7 @@ function createAirfieldZone(positions, options = {}, textAmplifiers = {}, contex
|
|
|
4496
4579
|
const { smooth = DEFAULT_AIRFIELD_ZONE_OPTIONS.smooth, smoothResolution = DEFAULT_AIRFIELD_ZONE_OPTIONS.smoothResolution, glyphSizeRatio = DEFAULT_AIRFIELD_ZONE_OPTIONS.glyphSizeRatio, ...labelOptions } = options;
|
|
4497
4580
|
const verts = buildClosedRing(positions, smooth, smoothResolution, DEFAULT_SMOOTH_RESOLUTION$8);
|
|
4498
4581
|
const enyLabels = [];
|
|
4499
|
-
const features = [boundaryFeature(verts, textAmplifiers.N ? pushAreaLabels(enyLabels, verts, { hostile: textAmplifiers.N }, labelOptions, context.amplifierPlacements) : [])];
|
|
4582
|
+
const features = [boundaryFeature(verts, textAmplifiers.N ? pushAreaLabels(enyLabels, verts, { hostile: textAmplifiers.N }, labelOptions, context.amplifierPlacements, void 0, context) : [])];
|
|
4500
4583
|
let minX = Infinity;
|
|
4501
4584
|
let maxX = -Infinity;
|
|
4502
4585
|
for (const [x] of verts) {
|
|
@@ -4572,13 +4655,13 @@ const DEFAULT_ECHELON_LABELED_AREA_OPTIONS = {
|
|
|
4572
4655
|
* before carving the boundary, so either (or both) opens it into a gapped
|
|
4573
4656
|
* `MultiLineString`; with neither, the boundary is a closed `Polygon`.
|
|
4574
4657
|
*/
|
|
4575
|
-
function echelonLabeledAreaFeatures(positions, options, texts, amplifierPlacements) {
|
|
4658
|
+
function echelonLabeledAreaFeatures(positions, options, texts, amplifierPlacements, context) {
|
|
4576
4659
|
const { echelon = DEFAULT_ECHELON_LABELED_AREA_OPTIONS.echelon, echelonSize = DEFAULT_ECHELON_LABELED_AREA_OPTIONS.echelonSize, echelonSizePixels, echelonPadding = DEFAULT_ECHELON_LABELED_AREA_OPTIONS.echelonPadding, echelonPosition = DEFAULT_ECHELON_LABELED_AREA_OPTIONS.echelonPosition, echelonAnchor, metersPerPixel, smooth = DEFAULT_ECHELON_LABELED_AREA_OPTIONS.smooth, smoothResolution = DEFAULT_ECHELON_LABELED_AREA_OPTIONS.smoothResolution, ...labelOptions } = options;
|
|
4577
4660
|
const h = resolveEchelonHeight(echelonSize, echelonSizePixels, metersPerPixel);
|
|
4578
4661
|
const verts = buildClosedRing(positions, smooth, smoothResolution, DEFAULT_SMOOTH_RESOLUTION$7);
|
|
4579
4662
|
const { strokes, fills, gaps: echelonGaps } = placeEchelon(buildAreaPerimeter(verts), h, echelon, echelonPadding, echelonPosition, echelonAnchor);
|
|
4580
4663
|
const labelFeatures = [];
|
|
4581
|
-
const enyGaps = pushAreaLabels(labelFeatures, verts, texts, labelOptions, amplifierPlacements);
|
|
4664
|
+
const enyGaps = pushAreaLabels(labelFeatures, verts, texts, labelOptions, amplifierPlacements, void 0, context);
|
|
4582
4665
|
const features = [boundaryFeature(verts, [...echelonGaps, ...enyGaps])];
|
|
4583
4666
|
if (strokes.length > 0) features.push({
|
|
4584
4667
|
type: "Feature",
|
|
@@ -4654,7 +4737,7 @@ const BASE_CAMP_METADATA = {
|
|
|
4654
4737
|
* boundary, per {@link echelonLabeledAreaFeatures}.
|
|
4655
4738
|
*/
|
|
4656
4739
|
function createBaseCamp(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
4657
|
-
return echelonLabeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "BC"), context.amplifierPlacements);
|
|
4740
|
+
return echelonLabeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "BC"), context.amplifierPlacements, context);
|
|
4658
4741
|
}
|
|
4659
4742
|
const BASE_CAMP = defineControlMeasure({
|
|
4660
4743
|
metadata: BASE_CAMP_METADATA,
|
|
@@ -4712,7 +4795,7 @@ const GUERRILLA_BASE_METADATA = {
|
|
|
4712
4795
|
* straddling the boundary, per {@link echelonLabeledAreaFeatures}.
|
|
4713
4796
|
*/
|
|
4714
4797
|
function createGuerrillaBase(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
4715
|
-
return echelonLabeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "GB"), context.amplifierPlacements);
|
|
4798
|
+
return echelonLabeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "GB"), context.amplifierPlacements, context);
|
|
4716
4799
|
}
|
|
4717
4800
|
const GUERRILLA_BASE = defineControlMeasure({
|
|
4718
4801
|
metadata: GUERRILLA_BASE_METADATA,
|
|
@@ -4774,7 +4857,7 @@ const GENERIC_C2_AREA_METADATA = {
|
|
|
4774
4857
|
* `H`/`W`/`W1`/`N` amplifier rows — no prefix, per {@link labeledAreaFeatures}.
|
|
4775
4858
|
*/
|
|
4776
4859
|
function createGenericC2Area(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
4777
|
-
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers), context.amplifierPlacements);
|
|
4860
|
+
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers), context.amplifierPlacements, context);
|
|
4778
4861
|
}
|
|
4779
4862
|
const GENERIC_C2_AREA = defineControlMeasure({
|
|
4780
4863
|
metadata: GENERIC_C2_AREA_METADATA,
|
|
@@ -4829,7 +4912,7 @@ const ASSEMBLY_AREA_METADATA = {
|
|
|
4829
4912
|
* `H`/`W`/`W1`/`N` amplifier rows, per {@link labeledAreaFeatures}.
|
|
4830
4913
|
*/
|
|
4831
4914
|
function createAssemblyArea(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
4832
|
-
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "AA"), context.amplifierPlacements);
|
|
4915
|
+
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "AA"), context.amplifierPlacements, context);
|
|
4833
4916
|
}
|
|
4834
4917
|
const ASSEMBLY_AREA = defineControlMeasure({
|
|
4835
4918
|
metadata: ASSEMBLY_AREA_METADATA,
|
|
@@ -5021,13 +5104,13 @@ function ditchPaths(projectedPoints, options) {
|
|
|
5021
5104
|
function buildAntitankDitchTriangles(basePath, options) {
|
|
5022
5105
|
return generateAntitankDitchTriangles(basePath, calculateAntitankDitchHeight(options), options.toothWidthRatio ?? DEFAULT_TOOTH_WIDTH_RATIO$1);
|
|
5023
5106
|
}
|
|
5024
|
-
function createAntitankDitchUnderConstruction(positions, options = {}, textAmplifiers = {}) {
|
|
5107
|
+
function createAntitankDitchUnderConstruction(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
5025
5108
|
const { smoothedPath, basePath } = ditchPaths(positions.map((position) => project(position[0], position[1])), options);
|
|
5026
5109
|
const triangles = buildAntitankDitchTriangles(basePath, options);
|
|
5027
5110
|
const baseLine = options.smooth ? basePath.map((point) => unproject(point[0], point[1])) : positions.map((position) => [position[0], position[1]]);
|
|
5028
5111
|
const triangleLines = triangles.map((triangle) => triangle.map((point) => unproject(point[0], point[1])));
|
|
5029
5112
|
const labels = [];
|
|
5030
|
-
pushHostileEndLabels(labels, smoothedPath, textAmplifiers.N, options);
|
|
5113
|
+
pushHostileEndLabels(labels, smoothedPath, textAmplifiers.N, options, "", context);
|
|
5031
5114
|
return {
|
|
5032
5115
|
type: "FeatureCollection",
|
|
5033
5116
|
features: [{
|
|
@@ -5040,13 +5123,13 @@ function createAntitankDitchUnderConstruction(positions, options = {}, textAmpli
|
|
|
5040
5123
|
}, ...labels]
|
|
5041
5124
|
};
|
|
5042
5125
|
}
|
|
5043
|
-
function createAntitankDitchCompleted(positions, options = {}, textAmplifiers = {}) {
|
|
5126
|
+
function createAntitankDitchCompleted(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
5044
5127
|
const { smoothedPath, basePath } = ditchPaths(positions.map((position) => project(position[0], position[1])), options);
|
|
5045
5128
|
const polygons = buildAntitankDitchTriangles(basePath, options).map((triangle) => {
|
|
5046
5129
|
return [[...triangle, triangle[0]].map((point) => unproject(point[0], point[1]))];
|
|
5047
5130
|
});
|
|
5048
5131
|
const labels = [];
|
|
5049
|
-
pushHostileEndLabels(labels, smoothedPath, textAmplifiers.N, options);
|
|
5132
|
+
pushHostileEndLabels(labels, smoothedPath, textAmplifiers.N, options, "", context);
|
|
5050
5133
|
return {
|
|
5051
5134
|
type: "FeatureCollection",
|
|
5052
5135
|
features: [{
|
|
@@ -5136,7 +5219,7 @@ function generateAntitankWallPoints(projectedPoints, toothHeight, toothWidthRati
|
|
|
5136
5219
|
}
|
|
5137
5220
|
return wallPoints;
|
|
5138
5221
|
}
|
|
5139
|
-
function createAntitankWall(positions, options = {}, textAmplifiers = {}) {
|
|
5222
|
+
function createAntitankWall(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
5140
5223
|
const toothHeight = calculateAntitankDitchHeight(options);
|
|
5141
5224
|
const toothWidthRatio = options.toothWidthRatio ?? DEFAULT_TOOTH_WIDTH_RATIO;
|
|
5142
5225
|
const toothSpacingRatio = options.toothSpacingRatio ?? DEFAULT_TOOTH_SPACING_RATIO;
|
|
@@ -5144,7 +5227,7 @@ function createAntitankWall(positions, options = {}, textAmplifiers = {}) {
|
|
|
5144
5227
|
const smoothedPath = smoothLineVerts(projectedPoints, options, 16);
|
|
5145
5228
|
const wallLine = generateAntitankWallPoints(options.smooth && projectedPoints.length >= 3 ? evenlySpacePath(smoothedPath, Math.max(EPSILON, toothWidthRatio) * toothHeight) : smoothedPath, toothHeight, toothWidthRatio, toothSpacingRatio).map((point) => unproject(point[0], point[1]));
|
|
5146
5229
|
const labels = [];
|
|
5147
|
-
pushHostileEndLabels(labels, smoothedPath, textAmplifiers.N, options);
|
|
5230
|
+
pushHostileEndLabels(labels, smoothedPath, textAmplifiers.N, options, "", context);
|
|
5148
5231
|
return {
|
|
5149
5232
|
type: "FeatureCollection",
|
|
5150
5233
|
features: [{
|
|
@@ -5591,7 +5674,7 @@ function createBattlePosition(positions, options = {}, textAmplifiers = {}, cont
|
|
|
5591
5674
|
const enyGaps = pushAreaLabels(labelFeatures, verts, {
|
|
5592
5675
|
name: textAmplifiers.T,
|
|
5593
5676
|
hostile: textAmplifiers.N
|
|
5594
|
-
}, options, context.amplifierPlacements);
|
|
5677
|
+
}, options, context.amplifierPlacements, void 0, context);
|
|
5595
5678
|
const boundaryCoords = buildGappedLine(verts, [...gaps, ...enyGaps]);
|
|
5596
5679
|
const features = [];
|
|
5597
5680
|
if (boundaryCoords.length > 0) features.push({
|
|
@@ -6189,7 +6272,7 @@ const BOUNDARY_METADATA = {
|
|
|
6189
6272
|
* @param textAmplifiers - Normalized text amplifiers (ADR-0027): `T`/`AS` label
|
|
6190
6273
|
* unit 1 (left-of-travel side), `T1`/`AS1` label unit 2 (right-of-travel side).
|
|
6191
6274
|
*/
|
|
6192
|
-
function createBoundary(positions, options = {}, textAmplifiers = {}) {
|
|
6275
|
+
function createBoundary(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
6193
6276
|
const { echelon = DEFAULT_BOUNDARY_OPTIONS.echelon, echelonSize = DEFAULT_BOUNDARY_OPTIONS.echelonSize, echelonSizePixels, echelonPadding = DEFAULT_BOUNDARY_OPTIONS.echelonPadding, metersPerPixel, labelRepetitions = DEFAULT_BOUNDARY_OPTIONS.labelRepetitions, labelSpacing = DEFAULT_BOUNDARY_OPTIONS.labelSpacing, labelPosition = DEFAULT_BOUNDARY_OPTIONS.labelPosition, labelPadding = DEFAULT_BOUNDARY_OPTIONS.labelPadding, labelOrientation = DEFAULT_BOUNDARY_OPTIONS.labelOrientation, smooth = DEFAULT_BOUNDARY_OPTIONS.smooth, smoothResolution = DEFAULT_BOUNDARY_OPTIONS.smoothResolution } = options;
|
|
6194
6277
|
const unit1Designator = textAmplifiers.T ?? "";
|
|
6195
6278
|
const unit1Country = textAmplifiers.AS ?? "";
|
|
@@ -6273,7 +6356,7 @@ function createBoundary(positions, options = {}, textAmplifiers = {}) {
|
|
|
6273
6356
|
labelSize: h,
|
|
6274
6357
|
labelPadding
|
|
6275
6358
|
};
|
|
6276
|
-
pushHostileEndLabels(labelFeatures, verts, textAmplifiers.N, hostileLabelOptions);
|
|
6359
|
+
pushHostileEndLabels(labelFeatures, verts, textAmplifiers.N, hostileLabelOptions, "", context);
|
|
6277
6360
|
const boundaryCoords = buildGappedLine(verts, gaps);
|
|
6278
6361
|
const features = [];
|
|
6279
6362
|
if (boundaryCoords.length > 0) features.push({
|
|
@@ -6389,12 +6472,12 @@ const LIGHT_LINE_METADATA = {
|
|
|
6389
6472
|
* Creates a Light line control measure: a polyline through `positions`
|
|
6390
6473
|
* labeled "LL" above the line near each end.
|
|
6391
6474
|
*/
|
|
6392
|
-
function createLightLine(positions, options = {}, textAmplifiers = {}) {
|
|
6475
|
+
function createLightLine(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
6393
6476
|
return fixedLabelLineFeatures(positions, {
|
|
6394
6477
|
part: "light-line",
|
|
6395
6478
|
placement: "above",
|
|
6396
6479
|
...options
|
|
6397
|
-
}, "LL", textAmplifiers.N);
|
|
6480
|
+
}, "LL", textAmplifiers.N, context);
|
|
6398
6481
|
}
|
|
6399
6482
|
const LIGHT_LINE = defineControlMeasure({
|
|
6400
6483
|
metadata: LIGHT_LINE_METADATA,
|
|
@@ -6500,12 +6583,12 @@ const CENTER_LABEL_OFFSET_RATIO = .7;
|
|
|
6500
6583
|
* above the line, T1/AS1 below). No center group is emitted when all four
|
|
6501
6584
|
* amplifiers are empty.
|
|
6502
6585
|
*/
|
|
6503
|
-
function createEngineerWorkLine(positions, options = {}, textAmplifiers = {}) {
|
|
6586
|
+
function createEngineerWorkLine(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
6504
6587
|
const features = [...fixedLabelLineFeatures(positions, {
|
|
6505
6588
|
part: "engineer-work-line",
|
|
6506
6589
|
placement: "above",
|
|
6507
6590
|
...options
|
|
6508
|
-
}, "EWL", textAmplifiers.N).features];
|
|
6591
|
+
}, "EWL", textAmplifiers.N, context).features];
|
|
6509
6592
|
const { segments, totalLength } = polylineSegments(smoothLineVerts(positions.map((p) => project(p[0], p[1])), options, 12));
|
|
6510
6593
|
const frame = pointAlongPolyline(segments, totalLength, .5);
|
|
6511
6594
|
if (frame) {
|
|
@@ -6642,7 +6725,7 @@ const GENERIC_C2_LINE_METADATA = {
|
|
|
6642
6725
|
* (inset from the tip, anchored toward the line's interior), one at
|
|
6643
6726
|
* `base + perp·offset` and the other at `base − perp·offset`.
|
|
6644
6727
|
*/
|
|
6645
|
-
function createGenericC2Line(positions, options = {}, textAmplifiers = {}) {
|
|
6728
|
+
function createGenericC2Line(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
6646
6729
|
const { phaseLineName, includePrefix = true, smooth, smoothResolution, ...sizeOptions } = options;
|
|
6647
6730
|
const verts = smoothLineVerts(positions.map((p) => project(p[0], p[1])), {
|
|
6648
6731
|
smooth,
|
|
@@ -6674,7 +6757,7 @@ function createGenericC2Line(positions, options = {}, textAmplifiers = {}) {
|
|
|
6674
6757
|
}
|
|
6675
6758
|
const nameText = phaseLineLabelText(trimmedPhaseLineName, includePrefix);
|
|
6676
6759
|
if (nameText) pushEndLabels(labelFeatures, verts, nameText, sizeOptions);
|
|
6677
|
-
pushHostileEndLabels(labelFeatures, verts, textAmplifiers.N, sizeOptions, nameText);
|
|
6760
|
+
pushHostileEndLabels(labelFeatures, verts, textAmplifiers.N, sizeOptions, nameText, context);
|
|
6678
6761
|
features.push(...labelFeatures);
|
|
6679
6762
|
}
|
|
6680
6763
|
return {
|
|
@@ -7526,6 +7609,8 @@ const ARC_RESOLUTION_PARAMS = [{
|
|
|
7526
7609
|
max: 360,
|
|
7527
7610
|
step: 1
|
|
7528
7611
|
}];
|
|
7612
|
+
//#endregion
|
|
7613
|
+
//#region src/generators/cm99-generic-graphics/params.ts
|
|
7529
7614
|
const SMOOTH_PATH_PARAMS = [{
|
|
7530
7615
|
key: "smooth",
|
|
7531
7616
|
label: "Smooth",
|
|
@@ -7862,6 +7947,41 @@ const GENERIC_RECTANGLE = defineControlMeasure({
|
|
|
7862
7947
|
] }
|
|
7863
7948
|
});
|
|
7864
7949
|
//#endregion
|
|
7950
|
+
//#region src/internal/annular-sector.ts
|
|
7951
|
+
/**
|
|
7952
|
+
* Clockwise sweep in degrees from `leftAzimuth` to `rightAzimuth`. A non-zero
|
|
7953
|
+
* raw difference that normalizes to 0 means a full 360° fan.
|
|
7954
|
+
*
|
|
7955
|
+
* `collapseToleranceDegrees` (default 0, i.e. off) treats a near-zero or
|
|
7956
|
+
* near-360 sweep as a collapsed sector. Callers whose handle coordinates are
|
|
7957
|
+
* rounded need this: two handles placed on the same ray can recover bearings a
|
|
7958
|
+
* few thousandths of a degree apart, which would otherwise read as an
|
|
7959
|
+
* almost-360° fan.
|
|
7960
|
+
*/
|
|
7961
|
+
function clockwiseSweepDegrees(leftAzimuth, rightAzimuth, collapseToleranceDegrees = 0) {
|
|
7962
|
+
const rawSweep = rightAzimuth - leftAzimuth;
|
|
7963
|
+
const sweep = normalizeDegrees(rawSweep);
|
|
7964
|
+
if (collapseToleranceDegrees > 0 && Math.min(sweep, 360 - sweep) < collapseToleranceDegrees) return 0;
|
|
7965
|
+
return sweep === 0 && rawSweep !== 0 ? 360 : sweep;
|
|
7966
|
+
}
|
|
7967
|
+
/** Samples an arc of `sweepDegrees` at `radius`, starting from `leftAzimuth`. */
|
|
7968
|
+
function sampleArc(anchor, radius, leftAzimuth, sweepDegrees, resolution) {
|
|
7969
|
+
const segmentCount = Math.max(1, Math.ceil(sweepDegrees / 360 * resolution));
|
|
7970
|
+
return Array.from({ length: segmentCount + 1 }, (_, index) => {
|
|
7971
|
+
return destinationPoint(anchor, radius, (leftAzimuth + index / segmentCount * sweepDegrees) * Math.PI / 180);
|
|
7972
|
+
});
|
|
7973
|
+
}
|
|
7974
|
+
/** Closed ring tracing the outer arc, back along the inner arc, and closed. */
|
|
7975
|
+
function annularSectorRing(anchor, innerRadius, outerRadius, leftAzimuth, sweepDegrees, resolution) {
|
|
7976
|
+
const outerArc = sampleArc(anchor, outerRadius, leftAzimuth, sweepDegrees, resolution);
|
|
7977
|
+
const innerArc = sampleArc(anchor, innerRadius, leftAzimuth, sweepDegrees, resolution).reverse();
|
|
7978
|
+
return [
|
|
7979
|
+
...outerArc,
|
|
7980
|
+
...innerArc,
|
|
7981
|
+
outerArc[0]
|
|
7982
|
+
];
|
|
7983
|
+
}
|
|
7984
|
+
//#endregion
|
|
7865
7985
|
//#region src/generators/cm99-generic-graphics/sector.ts
|
|
7866
7986
|
const DEFAULT_GENERIC_SECTOR_OPTIONS = {
|
|
7867
7987
|
filled: false,
|
|
@@ -7887,17 +8007,6 @@ const GENERIC_SECTOR_METADATA = {
|
|
|
7887
8007
|
drawRule: "Sector",
|
|
7888
8008
|
params: [...FILLED_AREA_PARAMS, ...ARC_RESOLUTION_PARAMS]
|
|
7889
8009
|
};
|
|
7890
|
-
function clockwiseSweepDegrees(leftAzimuth, rightAzimuth) {
|
|
7891
|
-
const rawSweep = rightAzimuth - leftAzimuth;
|
|
7892
|
-
const sweep = normalizeDegrees(rawSweep);
|
|
7893
|
-
return sweep === 0 && rawSweep !== 0 ? 360 : sweep;
|
|
7894
|
-
}
|
|
7895
|
-
function sampleArc(anchor, radius, leftAzimuth, sweepDegrees, resolution) {
|
|
7896
|
-
const segmentCount = Math.max(1, Math.ceil(sweepDegrees / 360 * resolution));
|
|
7897
|
-
return Array.from({ length: segmentCount + 1 }, (_, index) => {
|
|
7898
|
-
return destinationPoint(anchor, radius, (leftAzimuth + index / segmentCount * sweepDegrees) * Math.PI / 180);
|
|
7899
|
-
});
|
|
7900
|
-
}
|
|
7901
8010
|
function createGenericSector(coordinates, options = {}) {
|
|
7902
8011
|
const anchor = coordinates[0];
|
|
7903
8012
|
const innerLeftPoint = coordinates[1];
|
|
@@ -7906,11 +8015,13 @@ function createGenericSector(coordinates, options = {}) {
|
|
|
7906
8015
|
type: "FeatureCollection",
|
|
7907
8016
|
features: []
|
|
7908
8017
|
};
|
|
7909
|
-
const
|
|
7910
|
-
const
|
|
8018
|
+
const firstRadius = haversineDistance(anchor, innerLeftPoint);
|
|
8019
|
+
const secondRadius = haversineDistance(anchor, outerRightPoint);
|
|
8020
|
+
const innerRadius = Math.min(firstRadius, secondRadius);
|
|
8021
|
+
const outerRadius = Math.max(firstRadius, secondRadius);
|
|
7911
8022
|
const leftAzimuth = normalizeDegrees(sphericalBearing(anchor, innerLeftPoint) * 180 / Math.PI);
|
|
7912
8023
|
const rightAzimuth = normalizeDegrees(sphericalBearing(anchor, outerRightPoint) * 180 / Math.PI);
|
|
7913
|
-
if (outerRadius
|
|
8024
|
+
if (outerRadius - innerRadius < 1e-6 || innerRadius < 1e-6) return {
|
|
7914
8025
|
type: "FeatureCollection",
|
|
7915
8026
|
features: []
|
|
7916
8027
|
};
|
|
@@ -7919,13 +8030,7 @@ function createGenericSector(coordinates, options = {}) {
|
|
|
7919
8030
|
type: "FeatureCollection",
|
|
7920
8031
|
features: []
|
|
7921
8032
|
};
|
|
7922
|
-
const
|
|
7923
|
-
const outerArc = sampleArc(anchor, outerRadius, leftAzimuth, sweepDegrees, resolution);
|
|
7924
|
-
const ring = [
|
|
7925
|
-
...outerArc,
|
|
7926
|
-
...sampleArc(anchor, innerRadius, leftAzimuth, sweepDegrees, resolution).reverse(),
|
|
7927
|
-
outerArc[0]
|
|
7928
|
-
];
|
|
8033
|
+
const ring = annularSectorRing(anchor, innerRadius, outerRadius, leftAzimuth, sweepDegrees, normalizeArcResolution(options.resolution));
|
|
7929
8034
|
return {
|
|
7930
8035
|
type: "FeatureCollection",
|
|
7931
8036
|
features: [{
|
|
@@ -8128,6 +8233,114 @@ const GENERIC_TEXT = defineControlMeasure({
|
|
|
8128
8233
|
}
|
|
8129
8234
|
});
|
|
8130
8235
|
//#endregion
|
|
8236
|
+
//#region src/generators/cm20-maritime-control-areas/radar-search-doctrine.ts
|
|
8237
|
+
const DEFAULT_RADAR_SEARCH_DOCTRINE_OPTIONS = { resolution: 64 };
|
|
8238
|
+
/** Doctrinal RSD colors from the symbol specification. */
|
|
8239
|
+
const RADAR_SEARCH_DOCTRINE_STROKE_COLOR = "rgb(51, 136, 136)";
|
|
8240
|
+
const RADAR_SEARCH_DOCTRINE_FILL_COLOR = "rgba(51, 136, 136, 0.25)";
|
|
8241
|
+
const RADAR_SEARCH_DOCTRINE_METADATA = {
|
|
8242
|
+
id: "radar-search-doctrine",
|
|
8243
|
+
name: "Radar Search Doctrine",
|
|
8244
|
+
description: "A maritime radar search area defined by an axis and annular-sector limits.",
|
|
8245
|
+
entity: "Maritime Control Areas",
|
|
8246
|
+
entityType: "Radar Search Doctrine",
|
|
8247
|
+
value: "200700",
|
|
8248
|
+
minCoordinates: 3,
|
|
8249
|
+
maxCoordinates: 3,
|
|
8250
|
+
geometry: "area",
|
|
8251
|
+
geometryTypes: ["Polygon", "Point"],
|
|
8252
|
+
paints: {
|
|
8253
|
+
stroke: true,
|
|
8254
|
+
fill: "fixed",
|
|
8255
|
+
text: true
|
|
8256
|
+
},
|
|
8257
|
+
drawRule: "Point18",
|
|
8258
|
+
capturesLabelSize: true,
|
|
8259
|
+
params: ARC_RESOLUTION_PARAMS,
|
|
8260
|
+
textAmplifiers: [{
|
|
8261
|
+
key: "T",
|
|
8262
|
+
label: "Unique designation",
|
|
8263
|
+
description: "Field T — designation centered in the search area along its axis.",
|
|
8264
|
+
placeholder: "FF",
|
|
8265
|
+
maxLength: 20
|
|
8266
|
+
}]
|
|
8267
|
+
};
|
|
8268
|
+
/**
|
|
8269
|
+
* GeoJSON positions are rounded by `destinationPoint`, so two handles placed on
|
|
8270
|
+
* the same ray can recover bearings a few thousandths of a degree apart. Treat
|
|
8271
|
+
* that numerical noise as a collapsed sector, not an almost-360° fan.
|
|
8272
|
+
*/
|
|
8273
|
+
const SWEEP_COLLAPSE_TOLERANCE_DEGREES = .01;
|
|
8274
|
+
function createRadarSearchDoctrine(coordinates, options = {}, textAmplifiers = {}) {
|
|
8275
|
+
const anchor = coordinates[0];
|
|
8276
|
+
const startLimit = coordinates[1];
|
|
8277
|
+
const stopLimit = coordinates[2];
|
|
8278
|
+
const firstRange = haversineDistance(anchor, startLimit);
|
|
8279
|
+
const secondRange = haversineDistance(anchor, stopLimit);
|
|
8280
|
+
const startRange = Math.min(firstRange, secondRange);
|
|
8281
|
+
const stopRange = Math.max(firstRange, secondRange);
|
|
8282
|
+
const leftAzimuth = normalizeDegrees(sphericalBearing(anchor, startLimit) * 180 / Math.PI);
|
|
8283
|
+
const sweepDegrees = clockwiseSweepDegrees(leftAzimuth, normalizeDegrees(sphericalBearing(anchor, stopLimit) * 180 / Math.PI), SWEEP_COLLAPSE_TOLERANCE_DEGREES);
|
|
8284
|
+
if (stopRange - startRange < 1e-6 || sweepDegrees < 1e-6) return {
|
|
8285
|
+
type: "FeatureCollection",
|
|
8286
|
+
features: []
|
|
8287
|
+
};
|
|
8288
|
+
const axis = normalizeDegrees(leftAzimuth + sweepDegrees / 2) * Math.PI / 180;
|
|
8289
|
+
const ring = annularSectorRing(anchor, startRange, stopRange, leftAzimuth, sweepDegrees, normalizeArcResolution(options.resolution));
|
|
8290
|
+
const features = [{
|
|
8291
|
+
type: "Feature",
|
|
8292
|
+
properties: {
|
|
8293
|
+
part: "search-area",
|
|
8294
|
+
fill: true,
|
|
8295
|
+
style: {
|
|
8296
|
+
strokeColor: RADAR_SEARCH_DOCTRINE_STROKE_COLOR,
|
|
8297
|
+
fillColor: RADAR_SEARCH_DOCTRINE_FILL_COLOR,
|
|
8298
|
+
fillPattern: "solid"
|
|
8299
|
+
}
|
|
8300
|
+
},
|
|
8301
|
+
geometry: {
|
|
8302
|
+
type: "Polygon",
|
|
8303
|
+
coordinates: [ring]
|
|
8304
|
+
}
|
|
8305
|
+
}];
|
|
8306
|
+
if (textAmplifiers.T) {
|
|
8307
|
+
const labelAnchor = destinationPoint(anchor, (startRange + stopRange) / 2, axis);
|
|
8308
|
+
features.push({
|
|
8309
|
+
type: "Feature",
|
|
8310
|
+
properties: {
|
|
8311
|
+
part: "label",
|
|
8312
|
+
text: textAmplifiers.T,
|
|
8313
|
+
amplifierField: "T",
|
|
8314
|
+
labelPlacementKey: "T",
|
|
8315
|
+
...labelSizeProps(options),
|
|
8316
|
+
rotation: labelRotationAlong([Math.cos(axis), -Math.sin(axis)])
|
|
8317
|
+
},
|
|
8318
|
+
geometry: {
|
|
8319
|
+
type: "Point",
|
|
8320
|
+
coordinates: labelAnchor
|
|
8321
|
+
}
|
|
8322
|
+
});
|
|
8323
|
+
}
|
|
8324
|
+
return {
|
|
8325
|
+
type: "FeatureCollection",
|
|
8326
|
+
features
|
|
8327
|
+
};
|
|
8328
|
+
}
|
|
8329
|
+
const RADAR_SEARCH_DOCTRINE = defineControlMeasure({
|
|
8330
|
+
metadata: RADAR_SEARCH_DOCTRINE_METADATA,
|
|
8331
|
+
generator: createRadarSearchDoctrine,
|
|
8332
|
+
defaultOptions: DEFAULT_RADAR_SEARCH_DOCTRINE_OPTIONS,
|
|
8333
|
+
rule: dynamicPointDrawRule,
|
|
8334
|
+
previewSample: {
|
|
8335
|
+
controlPoints: [
|
|
8336
|
+
[0, -1],
|
|
8337
|
+
[-.55, -.15],
|
|
8338
|
+
[1, .45]
|
|
8339
|
+
],
|
|
8340
|
+
textAmplifiers: { T: "FF" }
|
|
8341
|
+
}
|
|
8342
|
+
});
|
|
8343
|
+
//#endregion
|
|
8131
8344
|
//#region src/generators/cm34-mission-tasks/clear.ts
|
|
8132
8345
|
/**
|
|
8133
8346
|
* Default options for the CLEAR symbol.
|
|
@@ -9699,7 +9912,7 @@ const DROP_ZONE_METADATA = {
|
|
|
9699
9912
|
* (Field N) ENY marker, per {@link labeledAreaFeatures}.
|
|
9700
9913
|
*/
|
|
9701
9914
|
function createDropZone(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
9702
|
-
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "DZ"), context.amplifierPlacements);
|
|
9915
|
+
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "DZ"), context.amplifierPlacements, context);
|
|
9703
9916
|
}
|
|
9704
9917
|
const DROP_ZONE = defineControlMeasure({
|
|
9705
9918
|
metadata: DROP_ZONE_METADATA,
|
|
@@ -9835,7 +10048,7 @@ function createEncirclement(positions, options = {}, textAmplifiers = {}, contex
|
|
|
9835
10048
|
const verts = buildClosedRing(positions, smooth, smoothResolution, DEFAULT_SMOOTH_RESOLUTION$2);
|
|
9836
10049
|
const perimeter = buildAreaPerimeter(verts);
|
|
9837
10050
|
const labelFeatures = [];
|
|
9838
|
-
const gaps = pushAreaLabels(labelFeatures, verts, { hostile: textAmplifiers.N }, options, context.amplifierPlacements);
|
|
10051
|
+
const gaps = pushAreaLabels(labelFeatures, verts, { hostile: textAmplifiers.N }, options, context.amplifierPlacements, void 0, context);
|
|
9839
10052
|
const radius = meanRadius(verts);
|
|
9840
10053
|
const barbLength = radius * Math.max(0, barbLengthRatio);
|
|
9841
10054
|
const barbs = buildBarbs(perimeter, radius * Math.max(0, barbSpacingRatio), barbLength, gaps);
|
|
@@ -9916,7 +10129,7 @@ const EXTRACTION_ZONE_METADATA = {
|
|
|
9916
10129
|
* an `N` (Field N) ENY marker, per {@link labeledAreaFeatures}.
|
|
9917
10130
|
*/
|
|
9918
10131
|
function createExtractionZone(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
9919
|
-
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "EZ"), context.amplifierPlacements);
|
|
10132
|
+
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "EZ"), context.amplifierPlacements, context);
|
|
9920
10133
|
}
|
|
9921
10134
|
const EXTRACTION_ZONE = defineControlMeasure({
|
|
9922
10135
|
metadata: EXTRACTION_ZONE_METADATA,
|
|
@@ -10588,7 +10801,7 @@ function processSegment(p1, p2, radius, arcSegments, isFirstSegment) {
|
|
|
10588
10801
|
* );
|
|
10589
10802
|
* ```
|
|
10590
10803
|
*/
|
|
10591
|
-
function createFLOT(positions, options = {}, textAmplifiers = {}) {
|
|
10804
|
+
function createFLOT(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
10592
10805
|
const { radius = DEFAULT_RADIUS, radiusPixels, metersPerPixel, arcSegments = DEFAULT_ARC_SEGMENTS, smooth = DEFAULT_FLOT_OPTIONS.smooth, smoothResolution = DEFAULT_FLOT_OPTIONS.smoothResolution } = options;
|
|
10593
10806
|
let effectiveRadius = radius;
|
|
10594
10807
|
if (radiusPixels !== void 0 && metersPerPixel !== void 0 && metersPerPixel > 0) effectiveRadius = radiusPixels * metersPerPixel;
|
|
@@ -10626,7 +10839,7 @@ function createFLOT(positions, options = {}, textAmplifiers = {}) {
|
|
|
10626
10839
|
}
|
|
10627
10840
|
};
|
|
10628
10841
|
const labels = [];
|
|
10629
|
-
pushHostileEndLabels(labels, smoothedPath, textAmplifiers.N, options);
|
|
10842
|
+
pushHostileEndLabels(labels, smoothedPath, textAmplifiers.N, options, "", context);
|
|
10630
10843
|
return {
|
|
10631
10844
|
type: "FeatureCollection",
|
|
10632
10845
|
features: [feature, ...labels]
|
|
@@ -10695,14 +10908,14 @@ const PHASE_LINE_METADATA = {
|
|
|
10695
10908
|
* Creates a Phase line control measure: a polyline through `positions`
|
|
10696
10909
|
* labeled at each end with `T` (optionally "PL"-prefixed).
|
|
10697
10910
|
*/
|
|
10698
|
-
function createPhaseLine(positions, options = {}, textAmplifiers = {}) {
|
|
10911
|
+
function createPhaseLine(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
10699
10912
|
const { includePrefix = true, ...sizeOptions } = options;
|
|
10700
10913
|
const designation = textAmplifiers.T ?? "";
|
|
10701
10914
|
const text = includePrefix ? designation.length > 0 ? `PL ${designation}` : "PL" : designation;
|
|
10702
10915
|
return fixedLabelLineFeatures(positions, {
|
|
10703
10916
|
part: "phase-line",
|
|
10704
10917
|
...sizeOptions
|
|
10705
|
-
}, text, textAmplifiers.N);
|
|
10918
|
+
}, text, textAmplifiers.N, context);
|
|
10706
10919
|
}
|
|
10707
10920
|
const PHASE_LINE = defineControlMeasure({
|
|
10708
10921
|
metadata: PHASE_LINE_METADATA,
|
|
@@ -10774,12 +10987,12 @@ const BRIDGEHEAD_LINE_METADATA = {
|
|
|
10774
10987
|
textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
|
|
10775
10988
|
};
|
|
10776
10989
|
/** Creates a Bridgehead line labeled "BL" above both ends. */
|
|
10777
|
-
function createBridgeheadLine(positions, options = {}, textAmplifiers = {}) {
|
|
10990
|
+
function createBridgeheadLine(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
10778
10991
|
return fixedLabelLineFeatures(positions, {
|
|
10779
10992
|
part: "bridgehead-line",
|
|
10780
10993
|
placement: "above",
|
|
10781
10994
|
...options
|
|
10782
|
-
}, "BL", textAmplifiers.N);
|
|
10995
|
+
}, "BL", textAmplifiers.N, context);
|
|
10783
10996
|
}
|
|
10784
10997
|
const BRIDGEHEAD_LINE = defineControlMeasure({
|
|
10785
10998
|
metadata: BRIDGEHEAD_LINE_METADATA,
|
|
@@ -10850,12 +11063,12 @@ const HOLDING_LINE_METADATA = {
|
|
|
10850
11063
|
textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
|
|
10851
11064
|
};
|
|
10852
11065
|
/** Creates a Holding line labeled "HL" above both ends. */
|
|
10853
|
-
function createHoldingLine(positions, options = {}, textAmplifiers = {}) {
|
|
11066
|
+
function createHoldingLine(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
10854
11067
|
return fixedLabelLineFeatures(positions, {
|
|
10855
11068
|
part: "holding-line",
|
|
10856
11069
|
placement: "above",
|
|
10857
11070
|
...options
|
|
10858
|
-
}, "HL", textAmplifiers.N);
|
|
11071
|
+
}, "HL", textAmplifiers.N, context);
|
|
10859
11072
|
}
|
|
10860
11073
|
const HOLDING_LINE = defineControlMeasure({
|
|
10861
11074
|
metadata: HOLDING_LINE_METADATA,
|
|
@@ -10926,12 +11139,12 @@ const RELEASE_LINE_METADATA = {
|
|
|
10926
11139
|
textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
|
|
10927
11140
|
};
|
|
10928
11141
|
/** Creates a Release line labeled "RL" above both ends. */
|
|
10929
|
-
function createReleaseLine(positions, options = {}, textAmplifiers = {}) {
|
|
11142
|
+
function createReleaseLine(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
10930
11143
|
return fixedLabelLineFeatures(positions, {
|
|
10931
11144
|
part: "release-line",
|
|
10932
11145
|
placement: "above",
|
|
10933
11146
|
...options
|
|
10934
|
-
}, "RL", textAmplifiers.N);
|
|
11147
|
+
}, "RL", textAmplifiers.N, context);
|
|
10935
11148
|
}
|
|
10936
11149
|
const RELEASE_LINE = defineControlMeasure({
|
|
10937
11150
|
metadata: RELEASE_LINE_METADATA,
|
|
@@ -11005,12 +11218,12 @@ const FORWARD_EDGE_OF_BATTLE_AREA_METADATA = {
|
|
|
11005
11218
|
* Creates a Forward edge of the battle area control measure: a polyline
|
|
11006
11219
|
* through `positions` labeled "FEBA" above the line near each end.
|
|
11007
11220
|
*/
|
|
11008
|
-
function createForwardEdgeOfBattleArea(positions, options = {}, textAmplifiers = {}) {
|
|
11221
|
+
function createForwardEdgeOfBattleArea(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
11009
11222
|
return fixedLabelLineFeatures(positions, {
|
|
11010
11223
|
part: "forward-edge-of-battle-area",
|
|
11011
11224
|
placement: "above",
|
|
11012
11225
|
...options
|
|
11013
|
-
}, "FEBA", textAmplifiers.N);
|
|
11226
|
+
}, "FEBA", textAmplifiers.N, context);
|
|
11014
11227
|
}
|
|
11015
11228
|
const FORWARD_EDGE_OF_BATTLE_AREA = defineControlMeasure({
|
|
11016
11229
|
metadata: FORWARD_EDGE_OF_BATTLE_AREA_METADATA,
|
|
@@ -11084,12 +11297,12 @@ const HANDOVER_LINE_METADATA = {
|
|
|
11084
11297
|
* Creates a Handover line control measure: a polyline through `positions`
|
|
11085
11298
|
* labeled "HOL" above the line near each end.
|
|
11086
11299
|
*/
|
|
11087
|
-
function createHandoverLine(positions, options = {}, textAmplifiers = {}) {
|
|
11300
|
+
function createHandoverLine(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
11088
11301
|
return fixedLabelLineFeatures(positions, {
|
|
11089
11302
|
part: "handover-line",
|
|
11090
11303
|
placement: "above",
|
|
11091
11304
|
...options
|
|
11092
|
-
}, "HOL", textAmplifiers.N);
|
|
11305
|
+
}, "HOL", textAmplifiers.N, context);
|
|
11093
11306
|
}
|
|
11094
11307
|
const HANDOVER_LINE = defineControlMeasure({
|
|
11095
11308
|
metadata: HANDOVER_LINE_METADATA,
|
|
@@ -11163,12 +11376,12 @@ const BATTLE_HANDOVER_LINE_METADATA = {
|
|
|
11163
11376
|
* Creates a Battle handover line control measure: a polyline through
|
|
11164
11377
|
* `positions` labeled "BHL" above the line near each end.
|
|
11165
11378
|
*/
|
|
11166
|
-
function createBattleHandoverLine(positions, options = {}, textAmplifiers = {}) {
|
|
11379
|
+
function createBattleHandoverLine(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
11167
11380
|
return fixedLabelLineFeatures(positions, {
|
|
11168
11381
|
part: "battle-handover-line",
|
|
11169
11382
|
placement: "above",
|
|
11170
11383
|
...options
|
|
11171
|
-
}, "BHL", textAmplifiers.N);
|
|
11384
|
+
}, "BHL", textAmplifiers.N, context);
|
|
11172
11385
|
}
|
|
11173
11386
|
const BATTLE_HANDOVER_LINE = defineControlMeasure({
|
|
11174
11387
|
metadata: BATTLE_HANDOVER_LINE_METADATA,
|
|
@@ -11320,7 +11533,7 @@ function generateFortifiedPoints(projectedPoints, effectiveSize) {
|
|
|
11320
11533
|
* @param options - Configuration options for the graphic
|
|
11321
11534
|
* @returns A GeoJSON FeatureCollection containing the castellated LineString
|
|
11322
11535
|
*/
|
|
11323
|
-
function createFortifiedLine(positions, options = {}, textAmplifiers = {}) {
|
|
11536
|
+
function createFortifiedLine(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
11324
11537
|
const { smooth = DEFAULT_FORTIFIED_LINE_OPTIONS.smooth, smoothResolution = DEFAULT_FORTIFIED_LINE_OPTIONS.smoothResolution } = options;
|
|
11325
11538
|
const safeSize = calculateEffectiveSize(options);
|
|
11326
11539
|
const projectedPoints = positions.map((p) => project(p[0], p[1]));
|
|
@@ -11334,7 +11547,7 @@ function createFortifiedLine(positions, options = {}, textAmplifiers = {}) {
|
|
|
11334
11547
|
}
|
|
11335
11548
|
};
|
|
11336
11549
|
const labels = [];
|
|
11337
|
-
pushHostileEndLabels(labels, smoothedPath, textAmplifiers.N, options);
|
|
11550
|
+
pushHostileEndLabels(labels, smoothedPath, textAmplifiers.N, options, "", context);
|
|
11338
11551
|
return {
|
|
11339
11552
|
type: "FeatureCollection",
|
|
11340
11553
|
features: [feature, ...labels]
|
|
@@ -11418,7 +11631,7 @@ function createFortifiedArea(positions, options = {}, textAmplifiers = {}, conte
|
|
|
11418
11631
|
}, options, context.amplifierPlacements, {
|
|
11419
11632
|
ringVerts: boundaryPoints,
|
|
11420
11633
|
extraClearanceMeters: 3 * safeSize
|
|
11421
|
-
})),
|
|
11634
|
+
}, context)),
|
|
11422
11635
|
properties: {}
|
|
11423
11636
|
}];
|
|
11424
11637
|
features.push(...labelFeatures);
|
|
@@ -12343,7 +12556,7 @@ const JOINT_TACTICAL_ACTION_AREA_METADATA = {
|
|
|
12343
12556
|
* {@link labeledAreaFeatures}.
|
|
12344
12557
|
*/
|
|
12345
12558
|
function createJointTacticalActionArea(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
12346
|
-
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "JTAA -", ""), context.amplifierPlacements);
|
|
12559
|
+
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "JTAA -", ""), context.amplifierPlacements, context);
|
|
12347
12560
|
}
|
|
12348
12561
|
const JOINT_TACTICAL_ACTION_AREA = defineControlMeasure({
|
|
12349
12562
|
metadata: JOINT_TACTICAL_ACTION_AREA_METADATA,
|
|
@@ -12402,7 +12615,7 @@ const LANDING_ZONE_METADATA = {
|
|
|
12402
12615
|
* amplifier rows, per {@link labeledAreaFeatures}.
|
|
12403
12616
|
*/
|
|
12404
12617
|
function createLandingZone(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
12405
|
-
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "LZ"), context.amplifierPlacements);
|
|
12618
|
+
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "LZ"), context.amplifierPlacements, context);
|
|
12406
12619
|
}
|
|
12407
12620
|
const LANDING_ZONE = defineControlMeasure({
|
|
12408
12621
|
metadata: LANDING_ZONE_METADATA,
|
|
@@ -12473,7 +12686,7 @@ function createLimitedAccessArea(positions, options = {}, textAmplifiers = {}, c
|
|
|
12473
12686
|
}];
|
|
12474
12687
|
return {
|
|
12475
12688
|
type: "FeatureCollection",
|
|
12476
|
-
features: [...patternedAreaFeatures(verts, pushAreaLabels(labelFeatures, verts, { hostile: textAmplifiers.N }, options, context.amplifierPlacements), "reverse-hatch"), ...labelFeatures]
|
|
12689
|
+
features: [...patternedAreaFeatures(verts, pushAreaLabels(labelFeatures, verts, { hostile: textAmplifiers.N }, options, context.amplifierPlacements, void 0, context), "reverse-hatch"), ...labelFeatures]
|
|
12477
12690
|
};
|
|
12478
12691
|
}
|
|
12479
12692
|
const LIMITED_ACCESS_AREA = defineControlMeasure({
|
|
@@ -12584,7 +12797,7 @@ function createNoFireAreaIrregular(positions, options = {}, textAmplifiers = {},
|
|
|
12584
12797
|
}
|
|
12585
12798
|
return {
|
|
12586
12799
|
type: "FeatureCollection",
|
|
12587
|
-
features: [...patternedAreaFeatures(verts, pushAreaLabels(labels, verts, { hostile: textAmplifiers.N }, options, context.amplifierPlacements), "reverse-hatch"), ...labels]
|
|
12800
|
+
features: [...patternedAreaFeatures(verts, pushAreaLabels(labels, verts, { hostile: textAmplifiers.N }, options, context.amplifierPlacements, void 0, context), "reverse-hatch"), ...labels]
|
|
12588
12801
|
};
|
|
12589
12802
|
}
|
|
12590
12803
|
const NO_FIRE_AREA_IRREGULAR = defineControlMeasure({
|
|
@@ -12929,7 +13142,7 @@ function createMinefield(coordinates, options = {}, textAmplifiers = {}, context
|
|
|
12929
13142
|
clearance: ENY_CLEARANCE_RATIO * labelSize,
|
|
12930
13143
|
sizeProps,
|
|
12931
13144
|
followPlacementRotation: true
|
|
12932
|
-
}, context.amplifierPlacements);
|
|
13145
|
+
}, context.amplifierPlacements, context, { labelSize });
|
|
12933
13146
|
if (textAmplifiers.W) pushLabel(labelFeatures, offset(center, 0, -halfHeight - width * LABEL_CLEARANCE_RATIO), textAmplifiers.W, labelRotation, sizeProps, void 0, "W");
|
|
12934
13147
|
const features = [{
|
|
12935
13148
|
type: "Feature",
|
|
@@ -13146,7 +13359,7 @@ function createMineArea(positions, options, minedArea, textAmplifiers, context)
|
|
|
13146
13359
|
textHeight: resolveLabelOffsetMeters(options, 1),
|
|
13147
13360
|
clearance: resolveLabelOffsetMeters(options, .12 + labelPadding),
|
|
13148
13361
|
sizeProps: labelSizeProps(options)
|
|
13149
|
-
}) : [], ...pushAreaLabels(labelFeatures, ring, { hostile: textAmplifiers.N }, options, context.amplifierPlacements)], mineFillPattern(mineType)), ...labelFeatures]
|
|
13362
|
+
}, void 0, context, options) : [], ...pushAreaLabels(labelFeatures, ring, { hostile: textAmplifiers.N }, options, context.amplifierPlacements, void 0, context)], mineFillPattern(mineType)), ...labelFeatures]
|
|
13150
13363
|
};
|
|
13151
13364
|
}
|
|
13152
13365
|
function createDynamicMinefield(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
@@ -13649,7 +13862,7 @@ const PICKUP_ZONE_METADATA = {
|
|
|
13649
13862
|
* amplifier rows, per {@link labeledAreaFeatures}.
|
|
13650
13863
|
*/
|
|
13651
13864
|
function createPickupZone(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
13652
|
-
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "PZ"), context.amplifierPlacements);
|
|
13865
|
+
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "PZ"), context.amplifierPlacements, context);
|
|
13653
13866
|
}
|
|
13654
13867
|
const PICKUP_ZONE = defineControlMeasure({
|
|
13655
13868
|
metadata: PICKUP_ZONE_METADATA,
|
|
@@ -13704,7 +13917,7 @@ const ASSAULT_POSITION_METADATA = {
|
|
|
13704
13917
|
* an `N` (Field N) ENY marker, per {@link labeledAreaFeatures}.
|
|
13705
13918
|
*/
|
|
13706
13919
|
function createAssaultPosition(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
13707
|
-
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "ASLT"), context.amplifierPlacements);
|
|
13920
|
+
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "ASLT"), context.amplifierPlacements, context);
|
|
13708
13921
|
}
|
|
13709
13922
|
const ASSAULT_POSITION = defineControlMeasure({
|
|
13710
13923
|
metadata: ASSAULT_POSITION_METADATA,
|
|
@@ -13759,7 +13972,7 @@ const ATTACK_POSITION_METADATA = {
|
|
|
13759
13972
|
* an `N` (Field N) ENY marker, per {@link labeledAreaFeatures}.
|
|
13760
13973
|
*/
|
|
13761
13974
|
function createAttackPosition(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
13762
|
-
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "ATK"), context.amplifierPlacements);
|
|
13975
|
+
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "ATK"), context.amplifierPlacements, context);
|
|
13763
13976
|
}
|
|
13764
13977
|
const ATTACK_POSITION = defineControlMeasure({
|
|
13765
13978
|
metadata: ATTACK_POSITION_METADATA,
|
|
@@ -13814,7 +14027,7 @@ const OBJECTIVE_AREA_METADATA = {
|
|
|
13814
14027
|
* an `N` (Field N) ENY marker, per {@link labeledAreaFeatures}.
|
|
13815
14028
|
*/
|
|
13816
14029
|
function createObjectiveArea(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
13817
|
-
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "OBJ"), context.amplifierPlacements);
|
|
14030
|
+
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "OBJ"), context.amplifierPlacements, context);
|
|
13818
14031
|
}
|
|
13819
14032
|
const OBJECTIVE_AREA = defineControlMeasure({
|
|
13820
14033
|
metadata: OBJECTIVE_AREA_METADATA,
|
|
@@ -14097,7 +14310,7 @@ function createStrongPoint(positions, options = {}, textAmplifiers = {}, context
|
|
|
14097
14310
|
const enyGaps = pushAreaLabels(labelFeatures, verts, {
|
|
14098
14311
|
name: textAmplifiers.T,
|
|
14099
14312
|
hostile: textAmplifiers.N
|
|
14100
|
-
}, options, context.amplifierPlacements);
|
|
14313
|
+
}, options, context.amplifierPlacements, void 0, context);
|
|
14101
14314
|
const allGaps = [...gaps, ...enyGaps];
|
|
14102
14315
|
const boundaryCoords = buildGappedLine(verts, allGaps);
|
|
14103
14316
|
const tics = buildStrongPointTics(perimeter, allGaps, h);
|
|
@@ -14203,7 +14416,7 @@ const SUBMARINE_ACTION_AREA_METADATA = {
|
|
|
14203
14416
|
* `W`/`W1`/`N` amplifier rows, per {@link labeledAreaFeatures}.
|
|
14204
14417
|
*/
|
|
14205
14418
|
function createSubmarineActionArea(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
14206
|
-
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "SAA -", ""), context.amplifierPlacements);
|
|
14419
|
+
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "SAA -", ""), context.amplifierPlacements, context);
|
|
14207
14420
|
}
|
|
14208
14421
|
const SUBMARINE_ACTION_AREA = defineControlMeasure({
|
|
14209
14422
|
metadata: SUBMARINE_ACTION_AREA_METADATA,
|
|
@@ -14263,7 +14476,7 @@ const SUBMARINE_GENERATED_ACTION_AREA_METADATA = {
|
|
|
14263
14476
|
* (Field T) plus `W`/`W1`/`N` amplifier rows, per {@link labeledAreaFeatures}.
|
|
14264
14477
|
*/
|
|
14265
14478
|
function createSubmarineGeneratedActionArea(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
14266
|
-
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "SGSA -", ""), context.amplifierPlacements);
|
|
14479
|
+
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "SGSA -", ""), context.amplifierPlacements, context);
|
|
14267
14480
|
}
|
|
14268
14481
|
const SUBMARINE_GENERATED_ACTION_AREA = defineControlMeasure({
|
|
14269
14482
|
metadata: SUBMARINE_GENERATED_ACTION_AREA_METADATA,
|
|
@@ -14669,6 +14882,7 @@ const DEFINITIONS = {
|
|
|
14669
14882
|
circle: GENERIC_CIRCLE,
|
|
14670
14883
|
sector: GENERIC_SECTOR,
|
|
14671
14884
|
text: GENERIC_TEXT,
|
|
14885
|
+
"radar-search-doctrine": RADAR_SEARCH_DOCTRINE,
|
|
14672
14886
|
"airborne-attack": AIRBORNE_ATTACK,
|
|
14673
14887
|
"attack-helicopter": ATTACK_HELICOPTER,
|
|
14674
14888
|
"support-by-fire": SUPPORT_BY_FIRE,
|
|
@@ -14960,7 +15174,31 @@ function dispatchControlMeasure(cm, opts) {
|
|
|
14960
15174
|
if (!validateInputContract(cm.controlPoints, definition.metadata, opts?.validationMode)) return EMPTY_COLLECTION;
|
|
14961
15175
|
const generator = definition.generator;
|
|
14962
15176
|
const textAmplifiers = normalizeTextAmplifiers(cm.textAmplifiers);
|
|
14963
|
-
|
|
15177
|
+
const constructionMetersPerCssPixel = resolveConstructionScale(cm.controlPoints, opts?.context?.groundMetersPerCssPixel);
|
|
15178
|
+
const authoredOptions = cm.options ?? {};
|
|
15179
|
+
const generatorOptions = constructionMetersPerCssPixel === void 0 ? authoredOptions : {
|
|
15180
|
+
...authoredOptions,
|
|
15181
|
+
metersPerPixel: constructionMetersPerCssPixel
|
|
15182
|
+
};
|
|
15183
|
+
return generator(cm.controlPoints, generatorOptions, textAmplifiers, {
|
|
15184
|
+
amplifierPlacements: cm.amplifierPlacements,
|
|
15185
|
+
constructionMetersPerCssPixel,
|
|
15186
|
+
labelSizeClampCssPixels: opts?.context?.labelSizeClampCssPixels,
|
|
15187
|
+
measureText: opts?.context?.measureText
|
|
15188
|
+
});
|
|
15189
|
+
}
|
|
15190
|
+
/** Convert true-ground scale to the Web Mercator metres legacy generators construct in. */
|
|
15191
|
+
function resolveConstructionScale(points, groundMetersPerCssPixel) {
|
|
15192
|
+
if (!(groundMetersPerCssPixel !== void 0 && groundMetersPerCssPixel > 0)) return void 0;
|
|
15193
|
+
let minLatitude = Infinity;
|
|
15194
|
+
let maxLatitude = -Infinity;
|
|
15195
|
+
for (const point of points) {
|
|
15196
|
+
minLatitude = Math.min(minLatitude, point[1]);
|
|
15197
|
+
maxLatitude = Math.max(maxLatitude, point[1]);
|
|
15198
|
+
}
|
|
15199
|
+
const midpointLatitude = (minLatitude + maxLatitude) / 2;
|
|
15200
|
+
const mercatorScale = Math.cos(midpointLatitude * Math.PI / 180);
|
|
15201
|
+
return groundMetersPerCssPixel / Math.max(mercatorScale, Number.EPSILON);
|
|
14964
15202
|
}
|
|
14965
15203
|
/**
|
|
14966
15204
|
* The control-point *input contract*: `controlPoints` must be present and an
|
|
@@ -15099,4 +15337,4 @@ function assertNever(value) {
|
|
|
15099
15337
|
throw new Error(`Unhandled control measure kind: ${String(value)}`);
|
|
15100
15338
|
}
|
|
15101
15339
|
//#endregion
|
|
15102
|
-
export { DEFAULT_TACTICAL_ARROW_OPTIONS as $,
|
|
15340
|
+
export { DEFAULT_TACTICAL_ARROW_OPTIONS as $, line26DrawRule as $t, DEFAULT_REARWARD_PASSAGE_OF_LINES_OPTIONS as A, DEFAULT_AREA_OF_OPERATIONS_OPTIONS as At, DEFAULT_BRIDGEHEAD_LINE_OPTIONS as B, DEFAULT_LABEL_SIZE_CLAMP_CSS_PIXELS as Bt, DEFAULT_LANDING_ZONE_OPTIONS as C, project as Cn, DEFAULT_BLOCK_OPTIONS as Ct, DEFAULT_SEIZE_OPTIONS as D, getMetersPerPixel as Dn, DEFAULT_ANTITANK_WALL_OPTIONS as Dt, DEFAULT_WITHDRAW_OPTIONS as E, EPSILON as En, DEFAULT_ATTACK_BY_FIRE_OPTIONS as Et, DEFAULT_BATTLE_HANDOVER_LINE_OPTIONS as F, TEXT_AMPLIFIER_FIELDS as Ft, DEFAULT_FINAL_PROTECTIVE_FIRE_OPTIONS as G, DEFAULT_STROKE_WIDTH_CSS_PIXELS as Gt, DEFAULT_FLOT_OPTIONS as H, DEFAULT_LINE_JOIN as Ht, DEFAULT_HANDOVER_LINE_OPTIONS as I, canonicalTextAmplifierKey as It, DEFAULT_GUARD_OPTIONS as J, DEFAULT_AIRBORNE_ATTACK_OPTIONS as Jt, DEFAULT_ENCIRCLEMENT_OPTIONS as K, DEFAULT_SYMBOL_COLOR as Kt, DEFAULT_FORWARD_EDGE_OF_BATTLE_AREA_OPTIONS as L, normalizeTextAmplifiers as Lt, DEFAULT_FRONTAL_ATTACK_OPTIONS as M, DEFAULT_DIRECTION_OF_SUPPORTING_ATTACK_OPTIONS as Mt, DEFAULT_FORTIFIED_AREA_OPTIONS as N, DEFAULT_DIRECTION_OF_MAIN_ATTACK_OPTIONS as Nt, DEFAULT_SCREEN_OPTIONS as O, roundToFixed as On, DEFAULT_ANTITANK_DITCH_OPTIONS as Ot, DEFAULT_FORTIFIED_LINE_OPTIONS as P, DEFAULT_DIRECTION_OF_ATTACK_AVIATION_OPTIONS as Pt, DEFAULT_COVER_OPTIONS as Q, line27DrawRule as Qt, DEFAULT_RELEASE_LINE_OPTIONS as R, resolveAmplifierPlacement as Rt, DEFAULT_MAIN_ATTACK_OPTIONS as S, haversineDistance as Sn, DEFAULT_BLOCK_ARROW_OPTIONS as St, DEFAULT_WITHDRAW_UNDER_PRESSURE_OPTIONS as T, unproject as Tn, DEFAULT_ATTACK_HELICOPTER_OPTIONS as Tt, DEFAULT_FIX_MISSION_TASK_OPTIONS as U, DEFAULT_PORTRAYAL as Ut, DEFAULT_PHASE_LINE_OPTIONS as V, DEFAULT_LINE_CAP as Vt, DEFAULT_FIX_OPTIONS as W, DEFAULT_STROKE_DASH_CSS_PIXELS as Wt, DEFAULT_DISENGAGE_OPTIONS as X, axis1DrawRule as Xt, DEFAULT_DISRUPT_MISSION_TASK_OPTIONS as Y, rectangleDrawRule as Yt, DEFAULT_DELAY_OPTIONS as Z, supportByFireDrawRule as Zt, DEFAULT_PICKUP_ZONE_OPTIONS as _, pointOnMidpointPerpendicularAxis as _n, DEFAULT_BLOCK_MISSION_TASK_OPTIONS as _t, DEFINITIONS as a, attackByFireDrawRule as an, RADAR_SEARCH_DOCTRINE_FILL_COLOR as at, DEFAULT_OBSTACLE_BYPASS_DIFFICULT_OPTIONS as b, calculateMetrics as bn, DEFAULT_LIGHT_LINE_OPTIONS as bt, getDefaultOptions as c, staticPointDrawRule as cn, DEFAULT_GENERIC_SECTOR_OPTIONS as ct, DEFAULT_TURN_OPTIONS as d, centerRadiusDrawRule as dn, DEFAULT_GENERIC_LINE_OPTIONS as dt, line24DrawRule as en, DEFAULT_COUNTERATTACK_BY_FIRE_OPTIONS as et, DEFAULT_SUPPORT_BY_FIRE_OPTIONS as f, disruptDrawRule as fn, DEFAULT_GENERIC_CIRCLE_OPTIONS as ft, DEFAULT_PRINCIPAL_DIRECTION_OF_FIRE_OPTIONS as g, getMidpointPerpendicularSignedDistance as gn, DEFAULT_BREACH_OPTIONS as gt, SECONDARY_DIRECTION_OF_FIRE_DASH as h, createMidpointPerpendicularDrawRule as hn, DEFAULT_BYPASS_OPTIONS as ht, CONTROL_MEASURE_METADATA as i, ambushDrawRule as in, DEFAULT_RADAR_SEARCH_DOCTRINE_OPTIONS as it, DEFAULT_PENETRATE_OPTIONS as j, DEFAULT_AREA_DEFENSE_OPTIONS as jt, DEFAULT_RETIRE_OPTIONS as k, DEFAULT_ASSEMBLY_AREA_OPTIONS as kt, listControlMeasureMetadata as l, point12DrawRule as ln, DEFAULT_GENERIC_RECTANGLE_OPTIONS as lt, DEFAULT_SECONDARY_DIRECTION_OF_FIRE_OPTIONS as m, computeDefaultMidpointPerpendicularPoint as mn, DEFAULT_CANALIZE_OPTIONS as mt, resolveStyleHints as n, turnDrawRule as nn, DEFAULT_SUPPORTING_ATTACK_OPTIONS as nt, getControlMeasureMetadata as o, dynamicPointDrawRule as on, RADAR_SEARCH_DOCTRINE_STROKE_COLOR as ot, DEFAULT_STRONG_POINT_OPTIONS as p, blockDrawRule as pn, DEFAULT_CLASSIC_ARROW_OPTIONS as pt, DEFAULT_DISRUPT_OPTIONS as q, DEFAULT_AMBUSH_OPTIONS as qt, CONTROL_MEASURE_IDS as r, line1DrawRule as rn, DEFAULT_CLEAR_OPTIONS as rt, getControlMeasureMetadataByValue as s, sectorDrawRule as sn, DEFAULT_GENERIC_TEXT_OPTIONS as st, renderControlMeasure as t, line23DrawRule as tn, DEFAULT_COUNTERATTACK_OPTIONS as tt, DEFAULT_TURNING_MOVEMENT_OPTIONS as u, penetrateDrawRule as un, DEFAULT_GENERIC_POLYGON_OPTIONS as ut, DEFAULT_OBSTACLE_BYPASS_IMPOSSIBLE_OPTIONS as v, snapToMidpointPerpendicular as vn, DEFAULT_GENERIC_C2_LINE_OPTIONS as vt, DEFAULT_JOINT_TACTICAL_ACTION_AREA_OPTIONS as w, sphericalBearing as wn, DEFAULT_BATTLE_POSITION_OPTIONS as wt, DEFAULT_MINEFIELD_OPTIONS as x, computeInitialWidthPoint as xn, DEFAULT_BOUNDARY_OPTIONS as xt, DEFAULT_OBSTACLE_BYPASS_EASY_OPTIONS as y, createBaselineFrame as yn, DEFAULT_ENGINEER_WORK_LINE_OPTIONS as yt, DEFAULT_HOLDING_LINE_OPTIONS as z, DEFAULT_LABEL_HEIGHT_CSS_PIXELS as zt };
|