@orbat-mapper/control-measures 0.17.0 → 0.19.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/README.md +5 -0
- package/dist/{index-DPu0u2KK.d.mts → index-CGoJeN1B.d.mts} +180 -156
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/preview/index.d.mts +1 -1
- package/dist/preview/index.mjs +1 -1
- package/dist/{renderControlMeasure-Bh62ZdEY.mjs → renderControlMeasure-C2YRB8uJ.mjs} +1504 -661
- package/media/cordon-and-knock.svg +1 -0
- package/media/cordon-and-search.svg +1 -0
- package/media/follow-and-assume.svg +1 -0
- package/media/follow-and-support.svg +1 -0
- package/media/isolate.svg +1 -1
- package/media/secure.svg +1 -0
- package/package.json +1 -1
|
@@ -673,7 +673,7 @@ function frameFor(p1, p2) {
|
|
|
673
673
|
}
|
|
674
674
|
//#endregion
|
|
675
675
|
//#region src/draw-rules/area1.ts
|
|
676
|
-
function derive$
|
|
676
|
+
function derive$15(points) {
|
|
677
677
|
return clonePositions(points);
|
|
678
678
|
}
|
|
679
679
|
/**
|
|
@@ -693,9 +693,9 @@ const area1DrawRule = {
|
|
|
693
693
|
id: "area1",
|
|
694
694
|
minimumUserPoints: 3,
|
|
695
695
|
closedRing: true,
|
|
696
|
-
derive: derive$
|
|
696
|
+
derive: derive$15,
|
|
697
697
|
transform(event) {
|
|
698
|
-
return derive$
|
|
698
|
+
return derive$15(event.next);
|
|
699
699
|
}
|
|
700
700
|
};
|
|
701
701
|
//#endregion
|
|
@@ -735,14 +735,14 @@ const containDrawRule = createMidpointPerpendicularDrawRule({
|
|
|
735
735
|
* that fixes both the radius and the bearing of the symbol's opening. Both
|
|
736
736
|
* points are user-clicked, so the canonical array is just the (clamped) input.
|
|
737
737
|
*/
|
|
738
|
-
function derive$
|
|
738
|
+
function derive$14(points) {
|
|
739
739
|
return clonePositions(points.slice(0, 2));
|
|
740
740
|
}
|
|
741
741
|
const centerRadiusDrawRule = {
|
|
742
742
|
id: "area15:center-radius",
|
|
743
743
|
minimumUserPoints: 2,
|
|
744
744
|
canonicalPointCount: 2,
|
|
745
|
-
derive: derive$
|
|
745
|
+
derive: derive$14,
|
|
746
746
|
transform(event) {
|
|
747
747
|
const { previous, next, activePointIndex } = event;
|
|
748
748
|
if (activePointIndex === 0 && previous.length >= 2 && next.length >= 2) {
|
|
@@ -751,7 +751,7 @@ const centerRadiusDrawRule = {
|
|
|
751
751
|
const origin = previous[1];
|
|
752
752
|
return [clonePosition(next[0]), [origin[0] + dx, origin[1] + dy]];
|
|
753
753
|
}
|
|
754
|
-
return derive$
|
|
754
|
+
return derive$14(next);
|
|
755
755
|
}
|
|
756
756
|
};
|
|
757
757
|
/** Backward-compatible doctrinal name for Area15's shared center-radius rule. */
|
|
@@ -810,6 +810,72 @@ const penetrateDrawRule = createMidpointPerpendicularDrawRule({
|
|
|
810
810
|
minimumPreviewPoints: 2
|
|
811
811
|
});
|
|
812
812
|
//#endregion
|
|
813
|
+
//#region src/draw-rules/area18.ts
|
|
814
|
+
const DEFAULT_OFFSET_RATIO = .6;
|
|
815
|
+
function createArea18DrawRule(id, normal) {
|
|
816
|
+
function derive(points) {
|
|
817
|
+
if (points.length < 2) return clonePositions(points);
|
|
818
|
+
const p1 = points[0];
|
|
819
|
+
const p2 = points[1];
|
|
820
|
+
if (points.length >= 4) return clonePositions(points.slice(0, 4));
|
|
821
|
+
const firstTip = project(p1[0], p1[1]);
|
|
822
|
+
const firstEnd = project(p2[0], p2[1]);
|
|
823
|
+
const frame = createProjectedBaselineFrame(firstTip, firstEnd, {
|
|
824
|
+
origin: "p1",
|
|
825
|
+
normal
|
|
826
|
+
});
|
|
827
|
+
let point4;
|
|
828
|
+
let p4;
|
|
829
|
+
if (points.length === 3) {
|
|
830
|
+
p4 = points[2];
|
|
831
|
+
point4 = project(p4[0], p4[1]);
|
|
832
|
+
} else if (frame) {
|
|
833
|
+
point4 = vecAdd(frame.origin, vecScale(frame.normal, frame.length * DEFAULT_OFFSET_RATIO));
|
|
834
|
+
p4 = unproject(point4[0], point4[1]);
|
|
835
|
+
} else {
|
|
836
|
+
point4 = firstTip;
|
|
837
|
+
p4 = [...p1];
|
|
838
|
+
}
|
|
839
|
+
const point3 = vecAdd(point4, vecSub(firstEnd, firstTip));
|
|
840
|
+
return clonePositions([
|
|
841
|
+
p1,
|
|
842
|
+
p2,
|
|
843
|
+
unproject(point3[0], point3[1]),
|
|
844
|
+
p4
|
|
845
|
+
]);
|
|
846
|
+
}
|
|
847
|
+
return {
|
|
848
|
+
id,
|
|
849
|
+
minimumUserPoints: 2,
|
|
850
|
+
minimumPreviewPoints: 2,
|
|
851
|
+
canonicalPointCount: 4,
|
|
852
|
+
derive,
|
|
853
|
+
transform(event) {
|
|
854
|
+
return clonePositions(event.next.slice(0, 4));
|
|
855
|
+
}
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Area18 anchor draw rule for Relief in Place.
|
|
860
|
+
*
|
|
861
|
+
* The user clicks PT.1 and PT.2. Those points derive the complete default
|
|
862
|
+
* hairpin for commit. Once committed, PT.1 through PT.4 are all independent,
|
|
863
|
+
* draggable anchors.
|
|
864
|
+
*/
|
|
865
|
+
const reliefInPlaceDrawRule = createArea18DrawRule("area18:relief-in-place", "right");
|
|
866
|
+
/** Demonstrate uses the mirrored Area18 default, with its turn above P1→P2. */
|
|
867
|
+
const demonstrateDrawRule = createArea18DrawRule("area18:demonstrate", "left");
|
|
868
|
+
//#endregion
|
|
869
|
+
//#region src/draw-rules/area19.ts
|
|
870
|
+
/**
|
|
871
|
+
* Area19 — center + start/radius point, mechanically identical to Area15.
|
|
872
|
+
* The distinct id preserves the doctrinal draw-rule identity in metadata.
|
|
873
|
+
*/
|
|
874
|
+
const secureDrawRule = {
|
|
875
|
+
...centerRadiusDrawRule,
|
|
876
|
+
id: "area19:secure"
|
|
877
|
+
};
|
|
878
|
+
//#endregion
|
|
813
879
|
//#region src/draw-rules/point12.ts
|
|
814
880
|
/**
|
|
815
881
|
* Point12 anchor draw rule for the obstacle-bypass family.
|
|
@@ -827,16 +893,16 @@ const point12DrawRule = createMidpointPerpendicularDrawRule({ id: "point12:obsta
|
|
|
827
893
|
* Point1 — a single anchor point (e.g. Text's center). One user click commits
|
|
828
894
|
* the measure; dragging the point simply translates it.
|
|
829
895
|
*/
|
|
830
|
-
function derive$
|
|
896
|
+
function derive$13(points) {
|
|
831
897
|
return clonePositions(points.slice(0, 1));
|
|
832
898
|
}
|
|
833
899
|
const pointDrawRule = {
|
|
834
900
|
id: "point1:anchor",
|
|
835
901
|
minimumUserPoints: 1,
|
|
836
902
|
canonicalPointCount: 1,
|
|
837
|
-
derive: derive$
|
|
903
|
+
derive: derive$13,
|
|
838
904
|
transform(event) {
|
|
839
|
-
return derive$
|
|
905
|
+
return derive$13(event.next);
|
|
840
906
|
}
|
|
841
907
|
};
|
|
842
908
|
//#endregion
|
|
@@ -863,7 +929,7 @@ const SECTOR_ANGLE_SNAP_RADIANS = Math.PI / 36;
|
|
|
863
929
|
* independently control a radius and a true-north azimuth. Dragging the anchor
|
|
864
930
|
* translates both edge points so the sector retains its size and orientation.
|
|
865
931
|
*/
|
|
866
|
-
function derive$
|
|
932
|
+
function derive$12(points) {
|
|
867
933
|
return clonePositions(points.slice(0, 3));
|
|
868
934
|
}
|
|
869
935
|
function guidePoints$2(points) {
|
|
@@ -891,7 +957,7 @@ const sectorDrawRule = {
|
|
|
891
957
|
showGuide: true,
|
|
892
958
|
guidePoints: guidePoints$2,
|
|
893
959
|
constrainAngles,
|
|
894
|
-
derive: derive$
|
|
960
|
+
derive: derive$12,
|
|
895
961
|
transform(event) {
|
|
896
962
|
const { previous, next, activePointIndex } = event;
|
|
897
963
|
if (activePointIndex === 0 && previous.length >= 3 && next.length >= 3) {
|
|
@@ -903,7 +969,7 @@ const sectorDrawRule = {
|
|
|
903
969
|
[previous[2][0] + dx, previous[2][1] + dy]
|
|
904
970
|
];
|
|
905
971
|
}
|
|
906
|
-
return derive$
|
|
972
|
+
return derive$12(next);
|
|
907
973
|
}
|
|
908
974
|
};
|
|
909
975
|
//#endregion
|
|
@@ -957,6 +1023,56 @@ const ambushDrawRule = createMidpointPerpendicularDrawRule({
|
|
|
957
1023
|
defaultDistanceRatio: -.5
|
|
958
1024
|
});
|
|
959
1025
|
//#endregion
|
|
1026
|
+
//#region src/draw-rules/line30.ts
|
|
1027
|
+
const DEFAULT_FIN_LENGTH_RATIO = .25;
|
|
1028
|
+
/** PT. 3 on the 45-degree fin ray leaving `p2`, `side` picking which fin. */
|
|
1029
|
+
function finAnchor(p2, direction, normal, length, side) {
|
|
1030
|
+
const anchor = vecAdd(p2, vecScale(vecNorm(vecAdd(vecScale(direction, -1), vecScale(normal, side))), length));
|
|
1031
|
+
return unproject(anchor[0], anchor[1]);
|
|
1032
|
+
}
|
|
1033
|
+
/**
|
|
1034
|
+
* Snaps PT. 3 onto the 45-degree fin ray on whichever side of the PT. 1 - PT. 2
|
|
1035
|
+
* axis the raw point falls. `finRatio` replaces the distance carried by the raw
|
|
1036
|
+
* PT. 3 with a share of the axis length, which is how dragging PT. 1 or PT. 2
|
|
1037
|
+
* keeps the fin proportional instead of pinning its absolute size.
|
|
1038
|
+
*/
|
|
1039
|
+
function derive$11(points, finRatio) {
|
|
1040
|
+
const derived = clonePositions(points.slice(0, 3));
|
|
1041
|
+
if (derived.length < 2) return derived;
|
|
1042
|
+
const p1 = project(derived[0][0], derived[0][1]);
|
|
1043
|
+
const p2 = project(derived[1][0], derived[1][1]);
|
|
1044
|
+
const axis = vecSub(p1, p2);
|
|
1045
|
+
const axisLength = vecMag(axis);
|
|
1046
|
+
if (axisLength === 0) return derived;
|
|
1047
|
+
const direction = vecNorm(axis);
|
|
1048
|
+
const normal = [-direction[1], direction[0]];
|
|
1049
|
+
if (derived.length === 2) {
|
|
1050
|
+
const length = axisLength * (finRatio ?? DEFAULT_FIN_LENGTH_RATIO);
|
|
1051
|
+
derived.push(finAnchor(p2, direction, normal, length, 1));
|
|
1052
|
+
return derived;
|
|
1053
|
+
}
|
|
1054
|
+
const offset = vecSub(project(derived[2][0], derived[2][1]), p2);
|
|
1055
|
+
const side = vecDot(offset, normal) < 0 ? -1 : 1;
|
|
1056
|
+
derived[2] = finAnchor(p2, direction, normal, finRatio === void 0 ? vecMag(offset) : axisLength * finRatio, side);
|
|
1057
|
+
return derived;
|
|
1058
|
+
}
|
|
1059
|
+
/** Line30 anchors for Exploit/Exploitation; PT. 3 controls the 45-degree fin length. */
|
|
1060
|
+
const line30DrawRule = {
|
|
1061
|
+
id: "line30:exploitation",
|
|
1062
|
+
minimumUserPoints: 2,
|
|
1063
|
+
minimumPreviewPoints: 2,
|
|
1064
|
+
canonicalPointCount: 3,
|
|
1065
|
+
derive: derive$11,
|
|
1066
|
+
transform(event) {
|
|
1067
|
+
const { activePointIndex, next, previous } = event;
|
|
1068
|
+
if (!(activePointIndex === 0 || activePointIndex === 1) || next.length < 3 || previous.length < 3) return derive$11(next);
|
|
1069
|
+
const oldP2 = project(previous[1][0], previous[1][1]);
|
|
1070
|
+
const oldAxisLength = vecMag(vecSub(project(previous[0][0], previous[0][1]), oldP2));
|
|
1071
|
+
const oldFinLength = vecMag(vecSub(project(previous[2][0], previous[2][1]), oldP2));
|
|
1072
|
+
return derive$11(next, oldAxisLength === 0 ? DEFAULT_FIN_LENGTH_RATIO : oldFinLength / oldAxisLength);
|
|
1073
|
+
}
|
|
1074
|
+
};
|
|
1075
|
+
//#endregion
|
|
960
1076
|
//#region src/draw-rules/line1.ts
|
|
961
1077
|
function derive$10(points) {
|
|
962
1078
|
return clonePositions(points);
|
|
@@ -1646,6 +1762,16 @@ const rectangleDrawRule = {
|
|
|
1646
1762
|
}
|
|
1647
1763
|
};
|
|
1648
1764
|
//#endregion
|
|
1765
|
+
//#region src/draw-rules/line25.ts
|
|
1766
|
+
/** Line25: PT. 1 is the arrow tip; PT. 2 is the rear of the symbol. */
|
|
1767
|
+
const line25DrawRule = {
|
|
1768
|
+
id: "line25",
|
|
1769
|
+
minimumUserPoints: 2,
|
|
1770
|
+
canonicalPointCount: 2,
|
|
1771
|
+
derive: (points) => clonePositions(points.slice(0, 2)),
|
|
1772
|
+
transform: (event) => clonePositions(event.next.slice(0, 2))
|
|
1773
|
+
};
|
|
1774
|
+
//#endregion
|
|
1649
1775
|
//#region src/internal/echelon.ts
|
|
1650
1776
|
/**
|
|
1651
1777
|
* APP-6 echelon (Field B) glyph vocabulary, shared by the control measures that
|
|
@@ -3015,6 +3141,70 @@ function labelBoxIntersectsSegment(anchor, a, b, textWidth, textHeight, rotation
|
|
|
3015
3141
|
}
|
|
3016
3142
|
return true;
|
|
3017
3143
|
}
|
|
3144
|
+
/**
|
|
3145
|
+
* Splits the polyline `points` into the pieces that fall outside a
|
|
3146
|
+
* `2*halfWidth × 2*halfHeight` label box centered at `anchor` with stored
|
|
3147
|
+
* `rotation` — the clipping counterpart to {@link labelBoxIntersectsSegment},
|
|
3148
|
+
* for ornament geometry (barbs, ticks) that must break around a glyph rather
|
|
3149
|
+
* than merely decide whether it collides.
|
|
3150
|
+
*
|
|
3151
|
+
* Each segment is clipped in the box's own frame with a two-slab parametric
|
|
3152
|
+
* test; pieces that rejoin across a shared vertex are merged so a polyline
|
|
3153
|
+
* crossing the box mid-span stays one line string per surviving run instead of
|
|
3154
|
+
* fragmenting at every interior vertex.
|
|
3155
|
+
*/
|
|
3156
|
+
function clipPolylineOutsideLabelBox(points, anchor, halfWidth, halfHeight, rotation) {
|
|
3157
|
+
const [widthAxis, heightAxis] = labelBoxAxes(rotation);
|
|
3158
|
+
const out = [];
|
|
3159
|
+
const append = (piece) => {
|
|
3160
|
+
if (piece.length < 2 || vecMag(vecSub(piece[1], piece[0])) < 1e-6) return;
|
|
3161
|
+
const previous = out.at(-1);
|
|
3162
|
+
if (previous && vecMag(vecSub(previous.at(-1), piece[0])) < 1e-6) previous.push(...piece.slice(1));
|
|
3163
|
+
else out.push(piece);
|
|
3164
|
+
};
|
|
3165
|
+
for (let i = 1; i < points.length; i++) {
|
|
3166
|
+
const a = points[i - 1];
|
|
3167
|
+
const b = points[i];
|
|
3168
|
+
const delta = vecSub(b, a);
|
|
3169
|
+
const relative = vecSub(a, anchor);
|
|
3170
|
+
const origin = [vecDot(relative, widthAxis), vecDot(relative, heightAxis)];
|
|
3171
|
+
const direction = [vecDot(delta, widthAxis), vecDot(delta, heightAxis)];
|
|
3172
|
+
let enter = 0;
|
|
3173
|
+
let exit = 1;
|
|
3174
|
+
let intersects = true;
|
|
3175
|
+
for (const [coordinate, rate, halfExtent] of [[
|
|
3176
|
+
origin[0],
|
|
3177
|
+
direction[0],
|
|
3178
|
+
halfWidth
|
|
3179
|
+
], [
|
|
3180
|
+
origin[1],
|
|
3181
|
+
direction[1],
|
|
3182
|
+
halfHeight
|
|
3183
|
+
]]) {
|
|
3184
|
+
if (Math.abs(rate) < 1e-6) {
|
|
3185
|
+
if (Math.abs(coordinate) > halfExtent) intersects = false;
|
|
3186
|
+
continue;
|
|
3187
|
+
}
|
|
3188
|
+
const first = (-halfExtent - coordinate) / rate;
|
|
3189
|
+
const second = (halfExtent - coordinate) / rate;
|
|
3190
|
+
enter = Math.max(enter, Math.min(first, second));
|
|
3191
|
+
exit = Math.min(exit, Math.max(first, second));
|
|
3192
|
+
if (enter > exit) intersects = false;
|
|
3193
|
+
}
|
|
3194
|
+
if (!intersects || exit < 0 || enter > 1) {
|
|
3195
|
+
append([a, b]);
|
|
3196
|
+
continue;
|
|
3197
|
+
}
|
|
3198
|
+
const clippedEnter = Math.max(0, enter);
|
|
3199
|
+
const clippedExit = Math.min(1, exit);
|
|
3200
|
+
const pointAt = (t) => t <= 0 ? a : t >= 1 ? b : vecAdd(a, vecScale(delta, t));
|
|
3201
|
+
if (clippedEnter > 0) append([a, pointAt(clippedEnter)]);
|
|
3202
|
+
if (clippedExit < 1) append([pointAt(clippedExit), b]);
|
|
3203
|
+
}
|
|
3204
|
+
return out;
|
|
3205
|
+
}
|
|
3206
|
+
/** Default extra clearance for an intrinsic label that interrupts a stroke. */
|
|
3207
|
+
const DEFAULT_LABEL_KNOCKOUT_PADDING = .3;
|
|
3018
3208
|
/** Field N descriptor shared by Line1 graphics that opt into hostile endpoint markers. */
|
|
3019
3209
|
const HOSTILE_LINE_AMPLIFIER = {
|
|
3020
3210
|
key: "N",
|
|
@@ -3023,12 +3213,12 @@ const HOSTILE_LINE_AMPLIFIER = {
|
|
|
3023
3213
|
placeholder: "ENY",
|
|
3024
3214
|
maxLength: 3
|
|
3025
3215
|
};
|
|
3026
|
-
/** Label-padding control shared by
|
|
3027
|
-
const
|
|
3216
|
+
/** Label-padding control shared by label-bearing graphics. */
|
|
3217
|
+
const LABEL_PADDING_PARAM = {
|
|
3028
3218
|
key: "labelPadding",
|
|
3029
3219
|
presentationTier: "advanced",
|
|
3030
3220
|
label: "Label padding",
|
|
3031
|
-
description: "Extra clearance
|
|
3221
|
+
description: "Extra clearance around labels, as a ratio of the label height",
|
|
3032
3222
|
type: "number",
|
|
3033
3223
|
min: 0,
|
|
3034
3224
|
max: 2,
|
|
@@ -3136,8 +3326,8 @@ function estimatedTextWidth(text, textHeightMeters) {
|
|
|
3136
3326
|
return text.length * LABEL_CHAR_ASPECT_RATIO * textHeightMeters;
|
|
3137
3327
|
}
|
|
3138
3328
|
/** Resolve the label's final display size, then measure and convert it to construction metres. */
|
|
3139
|
-
function resolveTextMetrics(text, options, context, style = "regular") {
|
|
3140
|
-
const legacyHeight = resolveLabelOffsetMeters(options, 1);
|
|
3329
|
+
function resolveTextMetrics(text, options, context, style = "regular", fallbackHeightMeters = 50) {
|
|
3330
|
+
const legacyHeight = resolveLabelOffsetMeters(options, 1, fallbackHeightMeters);
|
|
3141
3331
|
const constructionScale = context?.constructionMetersPerCssPixel;
|
|
3142
3332
|
if (!(constructionScale !== void 0 && constructionScale > 0)) return {
|
|
3143
3333
|
width: estimatedTextWidth(text, legacyHeight),
|
|
@@ -3168,6 +3358,28 @@ function resolveTextMetrics(text, options, context, style = "regular") {
|
|
|
3168
3358
|
height: measured.heightCssPixels * constructionScale
|
|
3169
3359
|
};
|
|
3170
3360
|
}
|
|
3361
|
+
/**
|
|
3362
|
+
* Half-length of the knockout required for a label along `direction`.
|
|
3363
|
+
*
|
|
3364
|
+
* The text box itself is always cleared; `labelPadding` adds symmetric clearance expressed in
|
|
3365
|
+
* resolved label heights. Keeping this calculation here gives every generator the same public
|
|
3366
|
+
* label-clearance interface while hiding font measurement and rotated-box projection details.
|
|
3367
|
+
*/
|
|
3368
|
+
function labelKnockoutHalfExtent(text, direction, rotation, options, context, defaultPadding = 0) {
|
|
3369
|
+
const metrics = resolveTextMetrics(text, options, context);
|
|
3370
|
+
const padding = Math.max(0, options.labelPadding ?? defaultPadding) * metrics.height;
|
|
3371
|
+
return labelHalfExtent(direction, metrics.width, metrics.height, rotation) + padding;
|
|
3372
|
+
}
|
|
3373
|
+
/**
|
|
3374
|
+
* {@link labelKnockoutHalfExtent} expressed as a fraction of an arc's angular
|
|
3375
|
+
* sweep, for generators that carve their knockout in arc-progress space rather
|
|
3376
|
+
* than in meters. Clamped to `0.5` so the gap can never exceed the arc.
|
|
3377
|
+
*/
|
|
3378
|
+
function arcKnockoutHalfFraction(halfExtentMeters, radius, sweepAngle) {
|
|
3379
|
+
const arcLength = radius * Math.abs(sweepAngle);
|
|
3380
|
+
if (!(arcLength > 1e-6)) return .5;
|
|
3381
|
+
return Math.min(.5, Math.max(0, halfExtentMeters) / arcLength);
|
|
3382
|
+
}
|
|
3171
3383
|
/** The end-label text for a phase-line naming, or `""` when unnamed. */
|
|
3172
3384
|
function phaseLineLabelText(name, includePrefix) {
|
|
3173
3385
|
if (!name) return "";
|
|
@@ -3632,6 +3844,7 @@ const DIRECTION_OF_ATTACK_AVIATION_METADATA = {
|
|
|
3632
3844
|
},
|
|
3633
3845
|
drawRule: "Line1",
|
|
3634
3846
|
capturesLabelSize: true,
|
|
3847
|
+
labelGeometryUsesResolvedMetrics: ["T"],
|
|
3635
3848
|
params: [
|
|
3636
3849
|
...SMOOTH_LINE_PARAMS,
|
|
3637
3850
|
{
|
|
@@ -3870,6 +4083,7 @@ const DIRECTION_OF_MAIN_ATTACK_METADATA = {
|
|
|
3870
4083
|
},
|
|
3871
4084
|
drawRule: "Line1",
|
|
3872
4085
|
capturesLabelSize: true,
|
|
4086
|
+
labelGeometryUsesResolvedMetrics: ["T"],
|
|
3873
4087
|
params: [
|
|
3874
4088
|
...SMOOTH_LINE_PARAMS,
|
|
3875
4089
|
...DIRECTION_OF_ATTACK_PARAMS,
|
|
@@ -3938,6 +4152,7 @@ const DIRECTION_OF_SUPPORTING_ATTACK_METADATA = {
|
|
|
3938
4152
|
},
|
|
3939
4153
|
drawRule: "Line1",
|
|
3940
4154
|
capturesLabelSize: true,
|
|
4155
|
+
labelGeometryUsesResolvedMetrics: ["T"],
|
|
3941
4156
|
params: [...SMOOTH_LINE_PARAMS, ...DIRECTION_OF_ATTACK_PARAMS],
|
|
3942
4157
|
textAmplifiers: DIRECTION_OF_ATTACK_TEXT_AMPLIFIERS
|
|
3943
4158
|
};
|
|
@@ -4568,6 +4783,7 @@ const AREA_METADATA = {
|
|
|
4568
4783
|
},
|
|
4569
4784
|
drawRule: "Area1",
|
|
4570
4785
|
capturesLabelSize: true,
|
|
4786
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
4571
4787
|
params: LABELED_AREA_PARAMS,
|
|
4572
4788
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
4573
4789
|
};
|
|
@@ -4831,6 +5047,7 @@ const ENGAGEMENT_AREA_METADATA = {
|
|
|
4831
5047
|
},
|
|
4832
5048
|
drawRule: "Area1",
|
|
4833
5049
|
capturesLabelSize: true,
|
|
5050
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
4834
5051
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
4835
5052
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
4836
5053
|
};
|
|
@@ -4897,6 +5114,26 @@ function sampleProjectedArc(center, startRadius, bulgeRadius, start, end, resolu
|
|
|
4897
5114
|
return unproject(point[0], point[1]);
|
|
4898
5115
|
});
|
|
4899
5116
|
}
|
|
5117
|
+
/**
|
|
5118
|
+
* Samples the 180-degree arc standing on the diameter `start`→`end`, bulging
|
|
5119
|
+
* toward `bulgeDir`. Shared by the mission tasks whose shaft turns back on
|
|
5120
|
+
* itself (Delay, Relief in Place, Demonstrate); `requestedSegments` is the
|
|
5121
|
+
* segment count for the half-turn.
|
|
5122
|
+
*/
|
|
5123
|
+
function createSemicircleArc(start, end, bulgeDir, requestedSegments) {
|
|
5124
|
+
const diameterVector = vecSub(end, start);
|
|
5125
|
+
const diameter = vecMag(diameterVector);
|
|
5126
|
+
if (diameter < 1e-6) return [unproject(start[0], start[1])];
|
|
5127
|
+
const radius = diameter / 2;
|
|
5128
|
+
const center = vecAdd(start, vecScale(diameterVector, .5));
|
|
5129
|
+
const startRadius = vecScale(diameterVector, -.5);
|
|
5130
|
+
const bulgeRadius = vecScale(bulgeDir, radius);
|
|
5131
|
+
const segments = Math.max(4, Math.round(requestedSegments));
|
|
5132
|
+
return Array.from({ length: segments + 1 }, (_, index) => {
|
|
5133
|
+
const point = arcPointAt(center, startRadius, bulgeRadius, index / segments * Math.PI);
|
|
5134
|
+
return unproject(point[0], point[1]);
|
|
5135
|
+
});
|
|
5136
|
+
}
|
|
4900
5137
|
//#endregion
|
|
4901
5138
|
//#region src/generators/cm15-maneuver-areas/mobile-defense.ts
|
|
4902
5139
|
const DEFAULT_MOBILE_DEFENSE_OPTIONS = { labelPadding: 0 };
|
|
@@ -4922,6 +5159,7 @@ const MOBILE_DEFENSE_METADATA = {
|
|
|
4922
5159
|
},
|
|
4923
5160
|
drawRule: "Area27",
|
|
4924
5161
|
capturesLabelSize: true,
|
|
5162
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
4925
5163
|
params: [{
|
|
4926
5164
|
key: "labelPadding",
|
|
4927
5165
|
presentationTier: "advanced",
|
|
@@ -5098,6 +5336,7 @@ const AREA_OF_OPERATIONS_METADATA = {
|
|
|
5098
5336
|
},
|
|
5099
5337
|
drawRule: "Area1",
|
|
5100
5338
|
capturesLabelSize: true,
|
|
5339
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
5101
5340
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
5102
5341
|
textAmplifiers: AREA_TEXT_AMPLIFIERS
|
|
5103
5342
|
};
|
|
@@ -5160,6 +5399,7 @@ const NAMED_AREA_OF_INTEREST_METADATA = {
|
|
|
5160
5399
|
},
|
|
5161
5400
|
drawRule: "Area1",
|
|
5162
5401
|
capturesLabelSize: true,
|
|
5402
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
5163
5403
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
5164
5404
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
5165
5405
|
};
|
|
@@ -5219,6 +5459,7 @@ const TARGET_AREA_OF_INTEREST_METADATA = {
|
|
|
5219
5459
|
},
|
|
5220
5460
|
drawRule: "Area1",
|
|
5221
5461
|
capturesLabelSize: true,
|
|
5462
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
5222
5463
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
5223
5464
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
5224
5465
|
};
|
|
@@ -5287,6 +5528,7 @@ const AIRFIELD_ZONE_METADATA = {
|
|
|
5287
5528
|
},
|
|
5288
5529
|
drawRule: "Area1",
|
|
5289
5530
|
capturesLabelSize: true,
|
|
5531
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
5290
5532
|
params: [
|
|
5291
5533
|
...AREA_SMOOTH_PARAMS,
|
|
5292
5534
|
{
|
|
@@ -5471,6 +5713,7 @@ const BASE_CAMP_METADATA = {
|
|
|
5471
5713
|
},
|
|
5472
5714
|
drawRule: "Area1",
|
|
5473
5715
|
capturesLabelSize: true,
|
|
5716
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
5474
5717
|
params: PREFIXED_ECHELON_AREA_PARAMS,
|
|
5475
5718
|
textAmplifiers: AREA_TEXT_AMPLIFIERS
|
|
5476
5719
|
};
|
|
@@ -5529,6 +5772,7 @@ const GUERRILLA_BASE_METADATA = {
|
|
|
5529
5772
|
},
|
|
5530
5773
|
drawRule: "Area1",
|
|
5531
5774
|
capturesLabelSize: true,
|
|
5775
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
5532
5776
|
params: PREFIXED_ECHELON_AREA_PARAMS,
|
|
5533
5777
|
textAmplifiers: AREA_TEXT_AMPLIFIERS
|
|
5534
5778
|
};
|
|
@@ -5592,6 +5836,7 @@ const GENERIC_C2_AREA_METADATA = {
|
|
|
5592
5836
|
},
|
|
5593
5837
|
drawRule: "Area1",
|
|
5594
5838
|
capturesLabelSize: true,
|
|
5839
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
5595
5840
|
params: LABELED_AREA_PARAMS,
|
|
5596
5841
|
textAmplifiers: AREA_TEXT_AMPLIFIERS
|
|
5597
5842
|
};
|
|
@@ -5647,6 +5892,7 @@ const ASSEMBLY_AREA_METADATA = {
|
|
|
5647
5892
|
},
|
|
5648
5893
|
drawRule: "Area1",
|
|
5649
5894
|
capturesLabelSize: true,
|
|
5895
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
5650
5896
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
5651
5897
|
textAmplifiers: AREA_TEXT_AMPLIFIERS
|
|
5652
5898
|
};
|
|
@@ -5698,7 +5944,7 @@ const FORTIFIED_PARAMS = [
|
|
|
5698
5944
|
step: 1,
|
|
5699
5945
|
unit: "m"
|
|
5700
5946
|
},
|
|
5701
|
-
|
|
5947
|
+
LABEL_PADDING_PARAM
|
|
5702
5948
|
];
|
|
5703
5949
|
const ANTITANK_DITCH_PARAMS = [
|
|
5704
5950
|
...SMOOTH_LINE_PARAMS,
|
|
@@ -5733,7 +5979,7 @@ const ANTITANK_DITCH_PARAMS = [
|
|
|
5733
5979
|
step: .05,
|
|
5734
5980
|
unit: "x"
|
|
5735
5981
|
},
|
|
5736
|
-
|
|
5982
|
+
LABEL_PADDING_PARAM
|
|
5737
5983
|
];
|
|
5738
5984
|
const ANTITANK_WALL_PARAMS = [...ANTITANK_DITCH_PARAMS, {
|
|
5739
5985
|
key: "toothSpacingRatio",
|
|
@@ -5776,6 +6022,7 @@ const ANTITANK_DITCH_UNDER_CONSTRUCTION_METADATA = {
|
|
|
5776
6022
|
},
|
|
5777
6023
|
drawRule: "Line1",
|
|
5778
6024
|
capturesLabelSize: true,
|
|
6025
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
5779
6026
|
params: ANTITANK_DITCH_PARAMS,
|
|
5780
6027
|
textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
|
|
5781
6028
|
};
|
|
@@ -5797,6 +6044,7 @@ const ANTITANK_DITCH_COMPLETED_METADATA = {
|
|
|
5797
6044
|
},
|
|
5798
6045
|
drawRule: "Line1",
|
|
5799
6046
|
capturesLabelSize: true,
|
|
6047
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
5800
6048
|
params: ANTITANK_DITCH_PARAMS,
|
|
5801
6049
|
textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
|
|
5802
6050
|
};
|
|
@@ -5929,6 +6177,7 @@ const ANTITANK_WALL_METADATA = {
|
|
|
5929
6177
|
},
|
|
5930
6178
|
drawRule: "Line1",
|
|
5931
6179
|
capturesLabelSize: true,
|
|
6180
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
5932
6181
|
params: ANTITANK_WALL_PARAMS,
|
|
5933
6182
|
textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
|
|
5934
6183
|
};
|
|
@@ -6395,6 +6644,7 @@ const BATTLE_POSITION_METADATA = {
|
|
|
6395
6644
|
},
|
|
6396
6645
|
drawRule: "Area1",
|
|
6397
6646
|
capturesLabelSize: true,
|
|
6647
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
6398
6648
|
params: ECHELON_AREA_PARAMS,
|
|
6399
6649
|
textAmplifiers: AREA_UNIT_DESIGNATION_HOSTILE_AMPLIFIERS
|
|
6400
6650
|
};
|
|
@@ -6538,6 +6788,7 @@ const BATTLE_POSITION_PREPARED_METADATA = {
|
|
|
6538
6788
|
},
|
|
6539
6789
|
drawRule: "Area1",
|
|
6540
6790
|
capturesLabelSize: true,
|
|
6791
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
6541
6792
|
params: PREFIXED_ECHELON_AREA_PARAMS,
|
|
6542
6793
|
textAmplifiers: AREA_UNIT_DESIGNATION_HOSTILE_AMPLIFIERS
|
|
6543
6794
|
};
|
|
@@ -6606,6 +6857,7 @@ const CONTAIN_METADATA = {
|
|
|
6606
6857
|
},
|
|
6607
6858
|
drawRule: "Area5",
|
|
6608
6859
|
capturesLabelSize: true,
|
|
6860
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
6609
6861
|
textAmplifiers: [AREA_HOSTILE_AMPLIFIER],
|
|
6610
6862
|
params: [{
|
|
6611
6863
|
key: "labelPadding",
|
|
@@ -7501,6 +7753,7 @@ const BOUNDARY_METADATA = {
|
|
|
7501
7753
|
text: true
|
|
7502
7754
|
},
|
|
7503
7755
|
drawRule: "Line1",
|
|
7756
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
7504
7757
|
params: [
|
|
7505
7758
|
{
|
|
7506
7759
|
key: "smooth",
|
|
@@ -7919,6 +8172,7 @@ const GENERIC_C2_LINE_METADATA = {
|
|
|
7919
8172
|
},
|
|
7920
8173
|
drawRule: "Line1",
|
|
7921
8174
|
capturesLabelSize: true,
|
|
8175
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
7922
8176
|
params: [
|
|
7923
8177
|
...SMOOTH_LINE_PARAMS,
|
|
7924
8178
|
...echelonLineParams("line"),
|
|
@@ -7940,7 +8194,7 @@ const GENERIC_C2_LINE_METADATA = {
|
|
|
7940
8194
|
type: "boolean",
|
|
7941
8195
|
visibleWhen: (opts) => Boolean(String(opts.phaseLineName ?? "").trim())
|
|
7942
8196
|
},
|
|
7943
|
-
|
|
8197
|
+
LABEL_PADDING_PARAM
|
|
7944
8198
|
],
|
|
7945
8199
|
textAmplifiers: [
|
|
7946
8200
|
{
|
|
@@ -8167,7 +8421,7 @@ function createLabelFeature(point, text, rotation, labelSize = {}) {
|
|
|
8167
8421
|
* @param config - Per-measure end caps, label glyph, and sizing
|
|
8168
8422
|
* @returns GeoJSON FeatureCollection containing MultiLineString and Point features
|
|
8169
8423
|
*/
|
|
8170
|
-
function createBracketSymbol(coordinates, config) {
|
|
8424
|
+
function createBracketSymbol(coordinates, config, context) {
|
|
8171
8425
|
const [c1, c2, c3] = coordinates;
|
|
8172
8426
|
const p1 = project(c1[0], c1[1]);
|
|
8173
8427
|
const p2 = project(c2[0], c2[1]);
|
|
@@ -8187,7 +8441,10 @@ function createBracketSymbol(coordinates, config) {
|
|
|
8187
8441
|
const rearMidpoint = vecAdd(frontMidpoint, vecScale(perpDir, perpDistance));
|
|
8188
8442
|
const rearTop = vecSub(rearMidpoint, halfFront);
|
|
8189
8443
|
const rearBottom = vecAdd(rearMidpoint, halfFront);
|
|
8190
|
-
const
|
|
8444
|
+
const rearLineRotation = Math.atan2(dirFront[1], dirFront[0]);
|
|
8445
|
+
const renderedLabelRotation = Math.PI - (rearLineRotation - Math.PI / 2);
|
|
8446
|
+
const labelRotation = normalizeRadians(Math.PI - keepTextLeftToRight(renderedLabelRotation));
|
|
8447
|
+
const gapHalf = Math.min(frontLength / 2, labelKnockoutHalfExtent(config.labelText, dirFront, labelRotation, config.labelOptions ?? {}, context));
|
|
8191
8448
|
const gapPt1 = vecAdd(rearMidpoint, vecScale(dirFront, -gapHalf));
|
|
8192
8449
|
const gapPt2 = vecAdd(rearMidpoint, vecScale(dirFront, gapHalf));
|
|
8193
8450
|
const [topCap, bottomCap] = config.buildEndCaps({
|
|
@@ -8198,39 +8455,35 @@ function createBracketSymbol(coordinates, config) {
|
|
|
8198
8455
|
frontLength,
|
|
8199
8456
|
rearTop
|
|
8200
8457
|
});
|
|
8201
|
-
const mainFeature = {
|
|
8202
|
-
type: "Feature",
|
|
8203
|
-
properties: { part: config.part },
|
|
8204
|
-
geometry: {
|
|
8205
|
-
type: "MultiLineString",
|
|
8206
|
-
coordinates: [
|
|
8207
|
-
topCap,
|
|
8208
|
-
[
|
|
8209
|
-
unproject(p1[0], p1[1]),
|
|
8210
|
-
unproject(rearTop[0], rearTop[1]),
|
|
8211
|
-
unproject(gapPt1[0], gapPt1[1])
|
|
8212
|
-
],
|
|
8213
|
-
[
|
|
8214
|
-
unproject(gapPt2[0], gapPt2[1]),
|
|
8215
|
-
unproject(rearBottom[0], rearBottom[1]),
|
|
8216
|
-
unproject(p2[0], p2[1])
|
|
8217
|
-
],
|
|
8218
|
-
bottomCap
|
|
8219
|
-
]
|
|
8220
|
-
}
|
|
8221
|
-
};
|
|
8222
|
-
const rearLineRotation = Math.atan2(dirFront[1], dirFront[0]);
|
|
8223
|
-
const renderedLabelRotation = Math.PI - (rearLineRotation - Math.PI / 2);
|
|
8224
|
-
const labelRotation = normalizeRadians(Math.PI - keepTextLeftToRight(renderedLabelRotation));
|
|
8225
8458
|
return {
|
|
8226
8459
|
type: "FeatureCollection",
|
|
8227
|
-
features: [
|
|
8460
|
+
features: [{
|
|
8461
|
+
type: "Feature",
|
|
8462
|
+
properties: { part: config.part },
|
|
8463
|
+
geometry: {
|
|
8464
|
+
type: "MultiLineString",
|
|
8465
|
+
coordinates: [
|
|
8466
|
+
topCap,
|
|
8467
|
+
[
|
|
8468
|
+
unproject(p1[0], p1[1]),
|
|
8469
|
+
unproject(rearTop[0], rearTop[1]),
|
|
8470
|
+
unproject(gapPt1[0], gapPt1[1])
|
|
8471
|
+
],
|
|
8472
|
+
[
|
|
8473
|
+
unproject(gapPt2[0], gapPt2[1]),
|
|
8474
|
+
unproject(rearBottom[0], rearBottom[1]),
|
|
8475
|
+
unproject(p2[0], p2[1])
|
|
8476
|
+
],
|
|
8477
|
+
bottomCap
|
|
8478
|
+
]
|
|
8479
|
+
}
|
|
8480
|
+
}, createLabelFeature(rearMidpoint, config.labelText, labelRotation, config.labelOptions)]
|
|
8228
8481
|
};
|
|
8229
8482
|
}
|
|
8230
8483
|
//#endregion
|
|
8231
8484
|
//#region src/generators/cm34-mission-tasks/block.ts
|
|
8232
8485
|
const DEFAULT_STEM_RATIO = 1.5;
|
|
8233
|
-
const DEFAULT_BLOCK_MISSION_TASK_OPTIONS = {
|
|
8486
|
+
const DEFAULT_BLOCK_MISSION_TASK_OPTIONS = { labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING };
|
|
8234
8487
|
const BLOCK_MISSION_TASK_METADATA = {
|
|
8235
8488
|
id: "block-mission-task",
|
|
8236
8489
|
name: "Block",
|
|
@@ -8248,16 +8501,8 @@ const BLOCK_MISSION_TASK_METADATA = {
|
|
|
8248
8501
|
},
|
|
8249
8502
|
drawRule: "Area11",
|
|
8250
8503
|
capturesLabelSize: true,
|
|
8251
|
-
|
|
8252
|
-
|
|
8253
|
-
presentationTier: "advanced",
|
|
8254
|
-
label: "Label gap",
|
|
8255
|
-
description: "Size of the shaft gap holding the 'B' label, as a ratio of the front length",
|
|
8256
|
-
type: "number",
|
|
8257
|
-
min: 0,
|
|
8258
|
-
max: 1,
|
|
8259
|
-
step: .01
|
|
8260
|
-
}]
|
|
8504
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
8505
|
+
params: [LABEL_PADDING_PARAM]
|
|
8261
8506
|
};
|
|
8262
8507
|
/**
|
|
8263
8508
|
* Generates a GeoJSON FeatureCollection for a BLOCK mission task symbol (340100).
|
|
@@ -8269,8 +8514,7 @@ const BLOCK_MISSION_TASK_METADATA = {
|
|
|
8269
8514
|
* @param options - Configuration options
|
|
8270
8515
|
* @returns GeoJSON FeatureCollection containing MultiLineString and Point features
|
|
8271
8516
|
*/
|
|
8272
|
-
function createBlockMissionTaskSymbol(coordinates, options = {}) {
|
|
8273
|
-
const labelGapRatio = options.labelGapRatio ?? DEFAULT_BLOCK_MISSION_TASK_OPTIONS.labelGapRatio;
|
|
8517
|
+
function createBlockMissionTaskSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
8274
8518
|
const p1 = project(coordinates[0][0], coordinates[0][1]);
|
|
8275
8519
|
const p2 = project(coordinates[1][0], coordinates[1][1]);
|
|
8276
8520
|
const frame = createProjectedBaselineFrame(p1, p2, {
|
|
@@ -8287,12 +8531,12 @@ function createBlockMissionTaskSymbol(coordinates, options = {}) {
|
|
|
8287
8531
|
const pStemEnd = vecAdd(mid, vecScale(frame.normal, stemDistance));
|
|
8288
8532
|
const labelPoint = vecAdd(mid, vecScale(frame.normal, stemDistance / 2));
|
|
8289
8533
|
const stemDirection = vecScale(frame.normal, stemDistance < 0 ? -1 : 1);
|
|
8290
|
-
const gapHalf = Math.min(frame.length * labelGapRatio / 2, Math.abs(stemDistance) / 2);
|
|
8291
|
-
const gapStart = vecAdd(labelPoint, vecScale(stemDirection, -gapHalf));
|
|
8292
|
-
const gapEnd = vecAdd(labelPoint, vecScale(stemDirection, gapHalf));
|
|
8293
8534
|
const shaftRotation = Math.atan2(frame.normal[1], frame.normal[0]);
|
|
8294
8535
|
const renderedLabelRotation = Math.PI - shaftRotation;
|
|
8295
8536
|
const labelRotation = normalizeRadians(Math.PI - keepTextLeftToRight(renderedLabelRotation));
|
|
8537
|
+
const gapHalf = Math.min(labelKnockoutHalfExtent("B", stemDirection, labelRotation, options, context, DEFAULT_BLOCK_MISSION_TASK_OPTIONS.labelPadding), Math.abs(stemDistance) / 2);
|
|
8538
|
+
const gapStart = vecAdd(labelPoint, vecScale(stemDirection, -gapHalf));
|
|
8539
|
+
const gapEnd = vecAdd(labelPoint, vecScale(stemDirection, gapHalf));
|
|
8296
8540
|
return {
|
|
8297
8541
|
type: "FeatureCollection",
|
|
8298
8542
|
features: [{
|
|
@@ -8327,7 +8571,7 @@ const BLOCK_MISSION_TASK = defineControlMeasure({
|
|
|
8327
8571
|
const DEFAULT_BREACH_OPTIONS = {
|
|
8328
8572
|
tickLengthRatio: .15,
|
|
8329
8573
|
tickAngle: 45,
|
|
8330
|
-
|
|
8574
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING
|
|
8331
8575
|
};
|
|
8332
8576
|
const BREACH_METADATA = {
|
|
8333
8577
|
id: "breach",
|
|
@@ -8347,6 +8591,7 @@ const BREACH_METADATA = {
|
|
|
8347
8591
|
},
|
|
8348
8592
|
drawRule: "Point12",
|
|
8349
8593
|
capturesLabelSize: true,
|
|
8594
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
8350
8595
|
params: [
|
|
8351
8596
|
{
|
|
8352
8597
|
key: "tickLengthRatio",
|
|
@@ -8368,16 +8613,7 @@ const BREACH_METADATA = {
|
|
|
8368
8613
|
max: 90,
|
|
8369
8614
|
step: 1
|
|
8370
8615
|
},
|
|
8371
|
-
|
|
8372
|
-
key: "labelGapRatio",
|
|
8373
|
-
presentationTier: "advanced",
|
|
8374
|
-
label: "Label gap",
|
|
8375
|
-
description: "Size of the rear-line gap holding the 'B' label, as a ratio of the front length",
|
|
8376
|
-
type: "number",
|
|
8377
|
-
min: 0,
|
|
8378
|
-
max: 1,
|
|
8379
|
-
step: .01
|
|
8380
|
-
}
|
|
8616
|
+
LABEL_PADDING_PARAM
|
|
8381
8617
|
]
|
|
8382
8618
|
};
|
|
8383
8619
|
/**
|
|
@@ -8391,16 +8627,15 @@ const BREACH_METADATA = {
|
|
|
8391
8627
|
* @param options - Configuration options
|
|
8392
8628
|
* @returns GeoJSON FeatureCollection containing MultiLineString and Point features
|
|
8393
8629
|
*/
|
|
8394
|
-
function createBreachSymbol(coordinates, options = {}) {
|
|
8630
|
+
function createBreachSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
8395
8631
|
const tickLengthRatio = options.tickLengthRatio ?? DEFAULT_BREACH_OPTIONS.tickLengthRatio;
|
|
8396
8632
|
const tickAngle = options.tickAngle ?? DEFAULT_BREACH_OPTIONS.tickAngle;
|
|
8397
8633
|
return createBracketSymbol(coordinates, {
|
|
8398
8634
|
part: "breach",
|
|
8399
8635
|
labelText: "B",
|
|
8400
|
-
|
|
8401
|
-
|
|
8402
|
-
|
|
8403
|
-
labelSizePixels: options.labelSizePixels
|
|
8636
|
+
labelOptions: {
|
|
8637
|
+
...options,
|
|
8638
|
+
labelPadding: options.labelPadding ?? DEFAULT_BREACH_OPTIONS.labelPadding
|
|
8404
8639
|
},
|
|
8405
8640
|
buildEndCaps: ({ p1, p2, dirFront, perpDir, frontLength }) => {
|
|
8406
8641
|
const halfTick = frontLength * tickLengthRatio / 2;
|
|
@@ -8416,7 +8651,7 @@ function createBreachSymbol(coordinates, options = {}) {
|
|
|
8416
8651
|
const tick2End = vecAdd(p2, vecScale(tick2Dir, halfTick));
|
|
8417
8652
|
return [[unproject(tick1Start[0], tick1Start[1]), unproject(tick1End[0], tick1End[1])], [unproject(tick2Start[0], tick2Start[1]), unproject(tick2End[0], tick2End[1])]];
|
|
8418
8653
|
}
|
|
8419
|
-
});
|
|
8654
|
+
}, context);
|
|
8420
8655
|
}
|
|
8421
8656
|
const BREACH = defineControlMeasure({
|
|
8422
8657
|
metadata: BREACH_METADATA,
|
|
@@ -8431,7 +8666,7 @@ const BREACH = defineControlMeasure({
|
|
|
8431
8666
|
*/
|
|
8432
8667
|
const DEFAULT_BYPASS_OPTIONS = {
|
|
8433
8668
|
arrowheadLengthRatio: .12,
|
|
8434
|
-
|
|
8669
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING
|
|
8435
8670
|
};
|
|
8436
8671
|
const BYPASS_METADATA = {
|
|
8437
8672
|
id: "bypass",
|
|
@@ -8451,6 +8686,7 @@ const BYPASS_METADATA = {
|
|
|
8451
8686
|
},
|
|
8452
8687
|
drawRule: "Point12",
|
|
8453
8688
|
capturesLabelSize: true,
|
|
8689
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
8454
8690
|
params: [{
|
|
8455
8691
|
key: "arrowheadLengthRatio",
|
|
8456
8692
|
presentationTier: "advanced",
|
|
@@ -8460,16 +8696,7 @@ const BYPASS_METADATA = {
|
|
|
8460
8696
|
min: .01,
|
|
8461
8697
|
max: 1,
|
|
8462
8698
|
step: .01
|
|
8463
|
-
},
|
|
8464
|
-
key: "labelGapRatio",
|
|
8465
|
-
presentationTier: "advanced",
|
|
8466
|
-
label: "Label gap",
|
|
8467
|
-
description: "Size of the rear-line gap holding the 'B' label, as a ratio of the front length",
|
|
8468
|
-
type: "number",
|
|
8469
|
-
min: 0,
|
|
8470
|
-
max: 1,
|
|
8471
|
-
step: .01
|
|
8472
|
-
}]
|
|
8699
|
+
}, LABEL_PADDING_PARAM]
|
|
8473
8700
|
};
|
|
8474
8701
|
/**
|
|
8475
8702
|
* Generates a GeoJSON FeatureCollection for a BYPASS mission task symbol (340300).
|
|
@@ -8482,22 +8709,21 @@ const BYPASS_METADATA = {
|
|
|
8482
8709
|
* @param options - Configuration options
|
|
8483
8710
|
* @returns GeoJSON FeatureCollection containing MultiLineString and Point features
|
|
8484
8711
|
*/
|
|
8485
|
-
function createBypassSymbol(coordinates, options = {}) {
|
|
8712
|
+
function createBypassSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
8486
8713
|
const arrowheadLengthRatio = options.arrowheadLengthRatio ?? DEFAULT_BYPASS_OPTIONS.arrowheadLengthRatio;
|
|
8487
8714
|
return createBracketSymbol(coordinates, {
|
|
8488
8715
|
part: "bypass",
|
|
8489
8716
|
labelText: "B",
|
|
8490
|
-
|
|
8491
|
-
|
|
8492
|
-
|
|
8493
|
-
labelSizePixels: options.labelSizePixels
|
|
8717
|
+
labelOptions: {
|
|
8718
|
+
...options,
|
|
8719
|
+
labelPadding: options.labelPadding ?? DEFAULT_BYPASS_OPTIONS.labelPadding
|
|
8494
8720
|
},
|
|
8495
8721
|
buildEndCaps: ({ p1, p2, frontLength, rearTop }) => {
|
|
8496
8722
|
const arrowheadLength = frontLength * arrowheadLengthRatio;
|
|
8497
8723
|
const arrowDir = vecNorm(vecSub(p1, rearTop));
|
|
8498
8724
|
return [createArrowHead$1(p1, arrowDir, arrowheadLength, arrowheadLength), createArrowHead$1(p2, arrowDir, arrowheadLength, arrowheadLength)];
|
|
8499
8725
|
}
|
|
8500
|
-
});
|
|
8726
|
+
}, context);
|
|
8501
8727
|
}
|
|
8502
8728
|
const BYPASS = defineControlMeasure({
|
|
8503
8729
|
metadata: BYPASS_METADATA,
|
|
@@ -8513,7 +8739,7 @@ const BYPASS = defineControlMeasure({
|
|
|
8513
8739
|
const DEFAULT_CANALIZE_OPTIONS = {
|
|
8514
8740
|
tickLengthRatio: .15,
|
|
8515
8741
|
tickAngle: 45,
|
|
8516
|
-
|
|
8742
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING
|
|
8517
8743
|
};
|
|
8518
8744
|
const CANALIZE_METADATA = {
|
|
8519
8745
|
id: "canalize",
|
|
@@ -8533,6 +8759,7 @@ const CANALIZE_METADATA = {
|
|
|
8533
8759
|
},
|
|
8534
8760
|
drawRule: "Point12",
|
|
8535
8761
|
capturesLabelSize: true,
|
|
8762
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
8536
8763
|
params: [
|
|
8537
8764
|
{
|
|
8538
8765
|
key: "tickLengthRatio",
|
|
@@ -8554,16 +8781,7 @@ const CANALIZE_METADATA = {
|
|
|
8554
8781
|
max: 90,
|
|
8555
8782
|
step: 1
|
|
8556
8783
|
},
|
|
8557
|
-
|
|
8558
|
-
key: "labelGapRatio",
|
|
8559
|
-
presentationTier: "advanced",
|
|
8560
|
-
label: "Label gap",
|
|
8561
|
-
description: "Size of the rear-line gap holding the 'C' label, as a ratio of the front length",
|
|
8562
|
-
type: "number",
|
|
8563
|
-
min: 0,
|
|
8564
|
-
max: 1,
|
|
8565
|
-
step: .01
|
|
8566
|
-
}
|
|
8784
|
+
LABEL_PADDING_PARAM
|
|
8567
8785
|
]
|
|
8568
8786
|
};
|
|
8569
8787
|
/**
|
|
@@ -8577,16 +8795,15 @@ const CANALIZE_METADATA = {
|
|
|
8577
8795
|
* @param options - Configuration options
|
|
8578
8796
|
* @returns GeoJSON FeatureCollection containing MultiLineString and Point features
|
|
8579
8797
|
*/
|
|
8580
|
-
function createCanalizeSymbol(coordinates, options = {}) {
|
|
8798
|
+
function createCanalizeSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
8581
8799
|
const tickLengthRatio = options.tickLengthRatio ?? DEFAULT_CANALIZE_OPTIONS.tickLengthRatio;
|
|
8582
8800
|
const tickAngle = options.tickAngle ?? DEFAULT_CANALIZE_OPTIONS.tickAngle;
|
|
8583
8801
|
return createBracketSymbol(coordinates, {
|
|
8584
8802
|
part: "canalize",
|
|
8585
8803
|
labelText: "C",
|
|
8586
|
-
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
labelSizePixels: options.labelSizePixels
|
|
8804
|
+
labelOptions: {
|
|
8805
|
+
...options,
|
|
8806
|
+
labelPadding: options.labelPadding ?? DEFAULT_CANALIZE_OPTIONS.labelPadding
|
|
8590
8807
|
},
|
|
8591
8808
|
buildEndCaps: ({ p1, p2, dirFront, perpDir, frontLength }) => {
|
|
8592
8809
|
const halfTick = frontLength * tickLengthRatio / 2;
|
|
@@ -8601,7 +8818,7 @@ function createCanalizeSymbol(coordinates, options = {}) {
|
|
|
8601
8818
|
const tick2End = vecAdd(p2, vecScale(tick2Dir, halfTick));
|
|
8602
8819
|
return [[unproject(tick1Start[0], tick1Start[1]), unproject(tick1End[0], tick1End[1])], [unproject(tick2Start[0], tick2Start[1]), unproject(tick2End[0], tick2End[1])]];
|
|
8603
8820
|
}
|
|
8604
|
-
});
|
|
8821
|
+
}, context);
|
|
8605
8822
|
}
|
|
8606
8823
|
const CANALIZE = defineControlMeasure({
|
|
8607
8824
|
metadata: CANALIZE_METADATA,
|
|
@@ -9670,12 +9887,12 @@ const DEFAULT_CLEAR_OPTIONS = {
|
|
|
9670
9887
|
arrowInsetRatio: .15,
|
|
9671
9888
|
arrowheadLengthRatio: .15,
|
|
9672
9889
|
arrowheadWidthRatio: .2,
|
|
9673
|
-
|
|
9890
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING
|
|
9674
9891
|
};
|
|
9675
9892
|
const CLEAR_METADATA = {
|
|
9676
9893
|
id: "clear",
|
|
9677
9894
|
name: "Clear",
|
|
9678
|
-
description: "Eliminates enemy forces within an assigned area by destroying, capturing, or forcing their withdrawal so they cannot offer organized resistance.",
|
|
9895
|
+
description: "Eliminates all enemy forces within an assigned area by destroying, capturing, or forcing their withdrawal so they cannot offer organized resistance.",
|
|
9679
9896
|
entity: "Mission Tasks",
|
|
9680
9897
|
entityType: "Clear",
|
|
9681
9898
|
value: "340500",
|
|
@@ -9690,6 +9907,7 @@ const CLEAR_METADATA = {
|
|
|
9690
9907
|
},
|
|
9691
9908
|
drawRule: "Line23",
|
|
9692
9909
|
capturesLabelSize: true,
|
|
9910
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
9693
9911
|
params: [
|
|
9694
9912
|
{
|
|
9695
9913
|
key: "arrowInsetRatio",
|
|
@@ -9721,16 +9939,7 @@ const CLEAR_METADATA = {
|
|
|
9721
9939
|
max: 1,
|
|
9722
9940
|
step: .01
|
|
9723
9941
|
},
|
|
9724
|
-
|
|
9725
|
-
key: "labelGapRatio",
|
|
9726
|
-
presentationTier: "advanced",
|
|
9727
|
-
label: "Label gap",
|
|
9728
|
-
description: "Size of the shaft gap holding the 'C' label, as a ratio of the shaft length",
|
|
9729
|
-
type: "number",
|
|
9730
|
-
min: 0,
|
|
9731
|
-
max: 1,
|
|
9732
|
-
step: .01
|
|
9733
|
-
}
|
|
9942
|
+
LABEL_PADDING_PARAM
|
|
9734
9943
|
]
|
|
9735
9944
|
};
|
|
9736
9945
|
/**
|
|
@@ -9754,11 +9963,10 @@ const CLEAR_METADATA = {
|
|
|
9754
9963
|
* @param options - Configuration options
|
|
9755
9964
|
* @returns GeoJSON FeatureCollection containing MultiLineString and Point features
|
|
9756
9965
|
*/
|
|
9757
|
-
function createClearSymbol(coordinates, options = {}) {
|
|
9966
|
+
function createClearSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
9758
9967
|
const arrowInsetRatio = Math.min(.49, Math.max(0, options.arrowInsetRatio ?? DEFAULT_CLEAR_OPTIONS.arrowInsetRatio));
|
|
9759
9968
|
const arrowheadLengthRatio = options.arrowheadLengthRatio ?? DEFAULT_CLEAR_OPTIONS.arrowheadLengthRatio;
|
|
9760
9969
|
const arrowheadWidthRatio = options.arrowheadWidthRatio ?? DEFAULT_CLEAR_OPTIONS.arrowheadWidthRatio;
|
|
9761
|
-
const labelGapRatio = options.labelGapRatio ?? DEFAULT_CLEAR_OPTIONS.labelGapRatio;
|
|
9762
9970
|
const [c1, c2, c3] = coordinates;
|
|
9763
9971
|
const p1 = project(c1[0], c1[1]);
|
|
9764
9972
|
const p2 = project(c2[0], c2[1]);
|
|
@@ -9777,6 +9985,9 @@ function createClearSymbol(coordinates, options = {}) {
|
|
|
9777
9985
|
const arrowDir = vecScale(perpToRear, -1);
|
|
9778
9986
|
const arrowheadLength = height * arrowheadLengthRatio;
|
|
9779
9987
|
const arrowheadWidth = height * arrowheadWidthRatio;
|
|
9988
|
+
const lineRotation = Math.atan2(dirLine[1], dirLine[0]);
|
|
9989
|
+
const renderedLabelRotation = Math.PI - (lineRotation - Math.PI / 2);
|
|
9990
|
+
const labelRotation = normalizeRadians(Math.PI - keepTextLeftToRight(renderedLabelRotation));
|
|
9780
9991
|
const tipAt = (t) => vecAdd(p1, vecScale(dirLine, t * height));
|
|
9781
9992
|
const lines = [[unproject(p1[0], p1[1]), unproject(p2[0], p2[1])]];
|
|
9782
9993
|
const fullTs = [
|
|
@@ -9790,7 +10001,7 @@ function createClearSymbol(coordinates, options = {}) {
|
|
|
9790
10001
|
const rearEnd = vecAdd(tip, vecScale(perpToRear, shaftLength));
|
|
9791
10002
|
if (i === labelIndex && shaftLength > 1e-6) {
|
|
9792
10003
|
const labelCenter = vecAdd(tip, vecScale(perpToRear, shaftLength * .5));
|
|
9793
|
-
const gapHalf = shaftLength
|
|
10004
|
+
const gapHalf = Math.min(shaftLength / 2, labelKnockoutHalfExtent("C", perpToRear, labelRotation, options, context, DEFAULT_CLEAR_OPTIONS.labelPadding));
|
|
9794
10005
|
const gapRear = vecAdd(labelCenter, vecScale(perpToRear, gapHalf));
|
|
9795
10006
|
const gapTip = vecAdd(labelCenter, vecScale(perpToRear, -gapHalf));
|
|
9796
10007
|
lines.push([unproject(rearEnd[0], rearEnd[1]), unproject(gapRear[0], gapRear[1])]);
|
|
@@ -9806,9 +10017,6 @@ function createClearSymbol(coordinates, options = {}) {
|
|
|
9806
10017
|
coordinates: lines
|
|
9807
10018
|
}
|
|
9808
10019
|
};
|
|
9809
|
-
const lineRotation = Math.atan2(dirLine[1], dirLine[0]);
|
|
9810
|
-
const renderedLabelRotation = Math.PI - (lineRotation - Math.PI / 2);
|
|
9811
|
-
const labelRotation = normalizeRadians(Math.PI - keepTextLeftToRight(renderedLabelRotation));
|
|
9812
10020
|
const labelTip = tipAt(fullTs[labelIndex]);
|
|
9813
10021
|
return {
|
|
9814
10022
|
type: "FeatureCollection",
|
|
@@ -9825,57 +10033,307 @@ const CLEAR = defineControlMeasure({
|
|
|
9825
10033
|
rule: line23DrawRule
|
|
9826
10034
|
});
|
|
9827
10035
|
//#endregion
|
|
9828
|
-
//#region src/generators/
|
|
9829
|
-
|
|
9830
|
-
|
|
9831
|
-
|
|
9832
|
-
|
|
9833
|
-
|
|
9834
|
-
|
|
9835
|
-
|
|
9836
|
-
|
|
9837
|
-
smooth: false,
|
|
9838
|
-
smoothResolution: 5,
|
|
9839
|
-
crossbarLengthRatio: 1.1,
|
|
9840
|
-
...DEFAULT_MANEUVER_ARROW_TASK_AMPLIFIER_OPTIONS
|
|
10036
|
+
//#region src/generators/cm34-mission-tasks/isolate.ts
|
|
10037
|
+
/**
|
|
10038
|
+
* Default options for the ISOLATE symbol.
|
|
10039
|
+
*/
|
|
10040
|
+
const DEFAULT_ISOLATE_OPTIONS = {
|
|
10041
|
+
openingAngle: 30,
|
|
10042
|
+
barbCount: 9,
|
|
10043
|
+
barbLengthRatio: .18,
|
|
10044
|
+
labelPadding: .2
|
|
9841
10045
|
};
|
|
9842
|
-
|
|
9843
|
-
|
|
9844
|
-
|
|
9845
|
-
|
|
9846
|
-
|
|
9847
|
-
|
|
9848
|
-
|
|
9849
|
-
|
|
9850
|
-
|
|
9851
|
-
|
|
9852
|
-
|
|
9853
|
-
|
|
9854
|
-
|
|
9855
|
-
|
|
9856
|
-
|
|
9857
|
-
|
|
9858
|
-
|
|
9859
|
-
|
|
10046
|
+
const ISOLATE_VARIANT_PARAMS = [
|
|
10047
|
+
{
|
|
10048
|
+
key: "openingAngle",
|
|
10049
|
+
presentationTier: "advanced",
|
|
10050
|
+
label: "Opening",
|
|
10051
|
+
description: "Angular width of the gap on the friendly side, in degrees",
|
|
10052
|
+
type: "number",
|
|
10053
|
+
min: 0,
|
|
10054
|
+
max: 180,
|
|
10055
|
+
step: 1
|
|
10056
|
+
},
|
|
10057
|
+
{
|
|
10058
|
+
key: "barbCount",
|
|
10059
|
+
presentationTier: "advanced",
|
|
10060
|
+
label: "Barbs",
|
|
10061
|
+
description: "Number of inward-pointing barbs spaced along the closed arc",
|
|
10062
|
+
type: "number",
|
|
10063
|
+
min: 1,
|
|
10064
|
+
max: 24,
|
|
10065
|
+
step: 1
|
|
10066
|
+
},
|
|
10067
|
+
{
|
|
10068
|
+
key: "barbLengthRatio",
|
|
10069
|
+
presentationTier: "advanced",
|
|
10070
|
+
label: "Barb length",
|
|
10071
|
+
description: "Length of each inward barb, as a ratio of the symbol radius",
|
|
10072
|
+
type: "number",
|
|
10073
|
+
min: .01,
|
|
10074
|
+
max: 1,
|
|
10075
|
+
step: .01
|
|
9860
10076
|
}
|
|
9861
|
-
|
|
10077
|
+
];
|
|
10078
|
+
function isolateVariantParams(labelText) {
|
|
10079
|
+
return [...ISOLATE_VARIANT_PARAMS, {
|
|
10080
|
+
...LABEL_PADDING_PARAM,
|
|
10081
|
+
label: `${labelText} padding`,
|
|
10082
|
+
description: `Extra boundary clearance on each side of ${labelText}, as a ratio of its height`
|
|
10083
|
+
}];
|
|
9862
10084
|
}
|
|
9863
|
-
|
|
9864
|
-
|
|
9865
|
-
|
|
9866
|
-
|
|
9867
|
-
|
|
9868
|
-
|
|
9869
|
-
|
|
9870
|
-
|
|
9871
|
-
|
|
9872
|
-
|
|
9873
|
-
|
|
9874
|
-
|
|
9875
|
-
|
|
9876
|
-
|
|
9877
|
-
|
|
9878
|
-
|
|
10085
|
+
const ISOLATE_METADATA = {
|
|
10086
|
+
id: "isolate",
|
|
10087
|
+
name: "Isolate",
|
|
10088
|
+
description: "Seals an enemy off physically and psychologically from sources of support, denies freedom of movement, and prevents contact with other enemy forces.",
|
|
10089
|
+
entity: "Mission Tasks",
|
|
10090
|
+
entityType: "Isolate",
|
|
10091
|
+
value: "341500",
|
|
10092
|
+
minCoordinates: 2,
|
|
10093
|
+
maxCoordinates: 2,
|
|
10094
|
+
geometry: "line",
|
|
10095
|
+
geometryTypes: ["MultiLineString", "Point"],
|
|
10096
|
+
paints: {
|
|
10097
|
+
stroke: true,
|
|
10098
|
+
fill: "none",
|
|
10099
|
+
text: true
|
|
10100
|
+
},
|
|
10101
|
+
drawRule: "Area15",
|
|
10102
|
+
capturesLabelSize: true,
|
|
10103
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
10104
|
+
params: isolateVariantParams("I")
|
|
10105
|
+
};
|
|
10106
|
+
const LABEL_ROTATION = Math.PI;
|
|
10107
|
+
/**
|
|
10108
|
+
* Generates a GeoJSON FeatureCollection for an ISOLATE mission task symbol (341500).
|
|
10109
|
+
*
|
|
10110
|
+
* A near-complete circle centered on PT. 1 whose radius reaches PT. 2. The arc
|
|
10111
|
+
* starts (bare) at PT. 2's bearing and sweeps clockwise around the closed
|
|
10112
|
+
* portion to a single arrow tip at the far end; the wedge-shaped gap (default
|
|
10113
|
+
* 30°) between that arrow tip and PT. 2 is the opening — the "friendly side".
|
|
10114
|
+
* Inward-pointing arrowhead barbs are spaced along the closed arc, set in from
|
|
10115
|
+
* both ends so they never touch PT. 2 or the arrow tip, conveying the
|
|
10116
|
+
* sealing-off of the enclosed force. A doctrinal "I" interrupts the closed arc
|
|
10117
|
+
* opposite the friendly-side opening.
|
|
10118
|
+
*
|
|
10119
|
+
* Input Points:
|
|
10120
|
+
* - PT. 1: Center point
|
|
10121
|
+
* - PT. 2: Start point — fixes the radius and the bearing of the opening
|
|
10122
|
+
*
|
|
10123
|
+
* @param coordinates - [PT. 1, PT. 2] as Position arrays
|
|
10124
|
+
* @param options - Configuration options
|
|
10125
|
+
* @returns GeoJSON FeatureCollection containing MultiLineString and Point features
|
|
10126
|
+
*/
|
|
10127
|
+
function createIsolateSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
10128
|
+
return createIsolateVariantSymbol(coordinates, options, {
|
|
10129
|
+
part: "isolate",
|
|
10130
|
+
labelText: "I"
|
|
10131
|
+
}, context);
|
|
10132
|
+
}
|
|
10133
|
+
/** Shared geometry for Isolate and its doctrinal cordon variants. */
|
|
10134
|
+
function createIsolateVariantSymbol(coordinates, options, config, context = {}) {
|
|
10135
|
+
const openingAngle = options.openingAngle ?? DEFAULT_ISOLATE_OPTIONS.openingAngle;
|
|
10136
|
+
const barbCount = options.barbCount ?? DEFAULT_ISOLATE_OPTIONS.barbCount;
|
|
10137
|
+
const barbLengthRatio = options.barbLengthRatio ?? DEFAULT_ISOLATE_OPTIONS.barbLengthRatio;
|
|
10138
|
+
const center = project(coordinates[0][0], coordinates[0][1]);
|
|
10139
|
+
const vRadius = vecSub(project(coordinates[1][0], coordinates[1][1]), center);
|
|
10140
|
+
const radius = vecMag(vRadius);
|
|
10141
|
+
if (radius < 1e-6) return {
|
|
10142
|
+
type: "FeatureCollection",
|
|
10143
|
+
features: []
|
|
10144
|
+
};
|
|
10145
|
+
const startAngle = Math.atan2(vRadius[1], vRadius[0]);
|
|
10146
|
+
const openingRad = Math.min(Math.max(openingAngle, 0), 359) * Math.PI / 180;
|
|
10147
|
+
const arcSpan = 2 * Math.PI - openingRad;
|
|
10148
|
+
const endAngle = startAngle - arcSpan;
|
|
10149
|
+
const pointOnCircle = (angle, r = radius) => vecAdd(center, [r * Math.cos(angle), r * Math.sin(angle)]);
|
|
10150
|
+
const labelSweep = arcSpan / 2;
|
|
10151
|
+
const labelMetrics = resolveTextMetrics(config.labelText, options, context, "regular", radius * .12);
|
|
10152
|
+
const labelPadding = Math.max(0, options.labelPadding ?? DEFAULT_ISOLATE_OPTIONS.labelPadding);
|
|
10153
|
+
const labelHalfWidth = Math.max(labelMetrics.width / 2, labelMetrics.height * LABEL_HALF_GLYPH_RATIO);
|
|
10154
|
+
const labelHalfGap = Math.min(arcSpan / 4, Math.max(labelHalfWidth + labelMetrics.height * labelPadding, 1) / radius);
|
|
10155
|
+
const labelPoint = pointOnCircle(startAngle - labelSweep);
|
|
10156
|
+
const labelBoxHalfWidth = labelMetrics.width / 2 + labelMetrics.height * labelPadding;
|
|
10157
|
+
const labelBoxHalfHeight = labelMetrics.height / 2 + labelMetrics.height * labelPadding;
|
|
10158
|
+
const sampleArc = (from, to) => {
|
|
10159
|
+
const span = to - from;
|
|
10160
|
+
const segments = Math.max(2, Math.round(64 * span / (2 * Math.PI)));
|
|
10161
|
+
const arc = [];
|
|
10162
|
+
for (let i = 0; i <= segments; i++) {
|
|
10163
|
+
const p = pointOnCircle(startAngle - from - span * i / segments);
|
|
10164
|
+
arc.push(unproject(p[0], p[1]));
|
|
10165
|
+
}
|
|
10166
|
+
return arc;
|
|
10167
|
+
};
|
|
10168
|
+
const barbLength = radius * barbLengthRatio;
|
|
10169
|
+
const lines = [sampleArc(0, labelSweep - labelHalfGap), sampleArc(labelSweep + labelHalfGap, arcSpan)];
|
|
10170
|
+
const tipDir = [Math.sin(endAngle), -Math.cos(endAngle)];
|
|
10171
|
+
lines.push(createArrowHead$1(pointOnCircle(endAngle), tipDir, barbLength, barbLength));
|
|
10172
|
+
const halfWidth = Math.asin(Math.min(1, barbLength / 2 / radius));
|
|
10173
|
+
const tipRadius = Math.max(0, radius - barbLength);
|
|
10174
|
+
const steps = Math.max(1, barbCount);
|
|
10175
|
+
for (let k = 1; k <= steps; k++) {
|
|
10176
|
+
const angle = startAngle - arcSpan * k / (steps + 1);
|
|
10177
|
+
const left = pointOnCircle(angle - halfWidth);
|
|
10178
|
+
const right = pointOnCircle(angle + halfWidth);
|
|
10179
|
+
const tip = pointOnCircle(angle, tipRadius);
|
|
10180
|
+
const barbLines = config.knockoutBarbs ? clipPolylineOutsideLabelBox([
|
|
10181
|
+
left,
|
|
10182
|
+
tip,
|
|
10183
|
+
right
|
|
10184
|
+
], labelPoint, labelBoxHalfWidth, labelBoxHalfHeight, LABEL_ROTATION) : [[
|
|
10185
|
+
left,
|
|
10186
|
+
tip,
|
|
10187
|
+
right
|
|
10188
|
+
]];
|
|
10189
|
+
for (const barb of barbLines) lines.push(barb.map((point) => unproject(point[0], point[1])));
|
|
10190
|
+
}
|
|
10191
|
+
return {
|
|
10192
|
+
type: "FeatureCollection",
|
|
10193
|
+
features: [{
|
|
10194
|
+
type: "Feature",
|
|
10195
|
+
properties: { part: config.part },
|
|
10196
|
+
geometry: {
|
|
10197
|
+
type: "MultiLineString",
|
|
10198
|
+
coordinates: lines
|
|
10199
|
+
}
|
|
10200
|
+
}, createLabelFeature(labelPoint, config.labelText, LABEL_ROTATION, options)]
|
|
10201
|
+
};
|
|
10202
|
+
}
|
|
10203
|
+
const ISOLATE = defineControlMeasure({
|
|
10204
|
+
metadata: ISOLATE_METADATA,
|
|
10205
|
+
generator: createIsolateSymbol,
|
|
10206
|
+
defaultOptions: DEFAULT_ISOLATE_OPTIONS,
|
|
10207
|
+
rule: isolateDrawRule
|
|
10208
|
+
});
|
|
10209
|
+
//#endregion
|
|
10210
|
+
//#region src/generators/cm34-mission-tasks/cordon-and-knock.ts
|
|
10211
|
+
const DEFAULT_CORDON_AND_KNOCK_OPTIONS = DEFAULT_ISOLATE_OPTIONS;
|
|
10212
|
+
const CORDON_AND_KNOCK_METADATA = {
|
|
10213
|
+
id: "cordon-and-knock",
|
|
10214
|
+
name: "Cordon and Knock",
|
|
10215
|
+
description: "Isolates a target area and requests or demands access before searching it.",
|
|
10216
|
+
entity: "Mission Tasks",
|
|
10217
|
+
entityType: "Cordon and Knock",
|
|
10218
|
+
value: "342600",
|
|
10219
|
+
minCoordinates: 2,
|
|
10220
|
+
maxCoordinates: 2,
|
|
10221
|
+
geometry: "line",
|
|
10222
|
+
geometryTypes: ["MultiLineString", "Point"],
|
|
10223
|
+
paints: {
|
|
10224
|
+
stroke: true,
|
|
10225
|
+
fill: "none",
|
|
10226
|
+
text: true
|
|
10227
|
+
},
|
|
10228
|
+
drawRule: "Area15",
|
|
10229
|
+
capturesLabelSize: true,
|
|
10230
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
10231
|
+
params: isolateVariantParams("C/K")
|
|
10232
|
+
};
|
|
10233
|
+
/** Generates the Cordon and Knock mission-task symbol (342600). */
|
|
10234
|
+
function createCordonAndKnockSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
10235
|
+
return createIsolateVariantSymbol(coordinates, options, {
|
|
10236
|
+
part: "cordon-and-knock",
|
|
10237
|
+
labelText: "C/K",
|
|
10238
|
+
knockoutBarbs: true
|
|
10239
|
+
}, context);
|
|
10240
|
+
}
|
|
10241
|
+
const CORDON_AND_KNOCK = defineControlMeasure({
|
|
10242
|
+
metadata: CORDON_AND_KNOCK_METADATA,
|
|
10243
|
+
generator: createCordonAndKnockSymbol,
|
|
10244
|
+
defaultOptions: DEFAULT_CORDON_AND_KNOCK_OPTIONS,
|
|
10245
|
+
rule: isolateDrawRule
|
|
10246
|
+
});
|
|
10247
|
+
//#endregion
|
|
10248
|
+
//#region src/generators/cm34-mission-tasks/cordon-and-search.ts
|
|
10249
|
+
const DEFAULT_CORDON_AND_SEARCH_OPTIONS = DEFAULT_ISOLATE_OPTIONS;
|
|
10250
|
+
const CORDON_AND_SEARCH_METADATA = {
|
|
10251
|
+
id: "cordon-and-search",
|
|
10252
|
+
name: "Cordon and Search",
|
|
10253
|
+
description: "Isolates and searches a target area to capture or destroy enemy forces and contraband.",
|
|
10254
|
+
entity: "Mission Tasks",
|
|
10255
|
+
entityType: "Cordon and Search",
|
|
10256
|
+
value: "342700",
|
|
10257
|
+
minCoordinates: 2,
|
|
10258
|
+
maxCoordinates: 2,
|
|
10259
|
+
geometry: "line",
|
|
10260
|
+
geometryTypes: ["MultiLineString", "Point"],
|
|
10261
|
+
paints: {
|
|
10262
|
+
stroke: true,
|
|
10263
|
+
fill: "none",
|
|
10264
|
+
text: true
|
|
10265
|
+
},
|
|
10266
|
+
drawRule: "Area15",
|
|
10267
|
+
capturesLabelSize: true,
|
|
10268
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
10269
|
+
params: isolateVariantParams("C/S")
|
|
10270
|
+
};
|
|
10271
|
+
/** Generates the Cordon and Search mission-task symbol (342700). */
|
|
10272
|
+
function createCordonAndSearchSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
10273
|
+
return createIsolateVariantSymbol(coordinates, options, {
|
|
10274
|
+
part: "cordon-and-search",
|
|
10275
|
+
labelText: "C/S",
|
|
10276
|
+
knockoutBarbs: true
|
|
10277
|
+
}, context);
|
|
10278
|
+
}
|
|
10279
|
+
const CORDON_AND_SEARCH = defineControlMeasure({
|
|
10280
|
+
metadata: CORDON_AND_SEARCH_METADATA,
|
|
10281
|
+
generator: createCordonAndSearchSymbol,
|
|
10282
|
+
defaultOptions: DEFAULT_CORDON_AND_SEARCH_OPTIONS,
|
|
10283
|
+
rule: isolateDrawRule
|
|
10284
|
+
});
|
|
10285
|
+
//#endregion
|
|
10286
|
+
//#region src/generators/cm15-maneuver-areas/maneuver-arrow-task-shared.ts
|
|
10287
|
+
const DEFAULT_MANEUVER_ARROW_TASK_AMPLIFIER_OPTIONS = {
|
|
10288
|
+
labelPosition: .25,
|
|
10289
|
+
dtgPosition: .62,
|
|
10290
|
+
labelPadding: 0
|
|
10291
|
+
};
|
|
10292
|
+
const DEFAULT_MANEUVER_ARROW_TASK_OPTIONS = {
|
|
10293
|
+
shaftWidthRatio: DEFAULT_SHAFT_WIDTH_RATIO$1,
|
|
10294
|
+
rearWidthRatio: DEFAULT_REAR_WIDTH_RATIO,
|
|
10295
|
+
smooth: false,
|
|
10296
|
+
smoothResolution: 5,
|
|
10297
|
+
crossbarLengthRatio: 1.1,
|
|
10298
|
+
...DEFAULT_MANEUVER_ARROW_TASK_AMPLIFIER_OPTIONS
|
|
10299
|
+
};
|
|
10300
|
+
function createManeuverArrowTaskAmplifierLabels(geometry, options, textAmplifiers) {
|
|
10301
|
+
const resolved = {
|
|
10302
|
+
...DEFAULT_MANEUVER_ARROW_TASK_AMPLIFIER_OPTIONS,
|
|
10303
|
+
...options
|
|
10304
|
+
};
|
|
10305
|
+
const centerline = geometry.shaftCenterline;
|
|
10306
|
+
const { segments, totalLength } = polylineSegments(centerline);
|
|
10307
|
+
if (segments.length === 0) return [];
|
|
10308
|
+
const labels = [];
|
|
10309
|
+
const sizeProps = labelSizeProps(resolved);
|
|
10310
|
+
const labelFrame = pointAlongPolyline(segments, totalLength, clamp01(resolved.labelPosition));
|
|
10311
|
+
if (labelFrame && textAmplifiers.T) pushLabel(labels, labelFrame.point, textAmplifiers.T, labelRotationAlong(labelFrame.along), sizeProps, void 0, "T");
|
|
10312
|
+
const dtgFrame = pointAlongPolyline(segments, totalLength, clamp01(resolved.dtgPosition));
|
|
10313
|
+
if (dtgFrame) {
|
|
10314
|
+
const rowOffset = resolveLabelOffsetMeters(resolved, .55 + Math.max(0, resolved.labelPadding));
|
|
10315
|
+
const rotation = labelRotationAlong(dtgFrame.along);
|
|
10316
|
+
if (textAmplifiers.W) pushLabel(labels, vecAdd(dtgFrame.point, vecScale(dtgFrame.perp, rowOffset)), textAmplifiers.W, rotation, sizeProps, void 0, "W");
|
|
10317
|
+
if (textAmplifiers.W1) pushLabel(labels, vecSub(dtgFrame.point, vecScale(dtgFrame.perp, rowOffset)), textAmplifiers.W1, rotation, sizeProps, void 0, "W1");
|
|
10318
|
+
}
|
|
10319
|
+
return labels;
|
|
10320
|
+
}
|
|
10321
|
+
function createManeuverArrowTask(coordinates, options, textAmplifiers, config) {
|
|
10322
|
+
const merged = {
|
|
10323
|
+
...DEFAULT_MANEUVER_ARROW_TASK_OPTIONS,
|
|
10324
|
+
...options
|
|
10325
|
+
};
|
|
10326
|
+
const resolved = {
|
|
10327
|
+
...merged,
|
|
10328
|
+
rearWidthRatio: resolveRearWidthRatio(options, merged.shaftWidthRatio)
|
|
10329
|
+
};
|
|
10330
|
+
const { geometry } = processAttackGeometry(coordinates, resolved);
|
|
10331
|
+
if (!geometry) return {
|
|
10332
|
+
type: "FeatureCollection",
|
|
10333
|
+
features: []
|
|
10334
|
+
};
|
|
10335
|
+
const outline = [
|
|
10336
|
+
...geometry.shaftLeft,
|
|
9879
10337
|
geometry.headRing[0],
|
|
9880
10338
|
geometry.headRing[1],
|
|
9881
10339
|
geometry.headRing[2],
|
|
@@ -10110,7 +10568,7 @@ const COUNTERATTACK_DASH = [6, 4];
|
|
|
10110
10568
|
const COUNTERATTACK_METADATA = {
|
|
10111
10569
|
id: "counterattack",
|
|
10112
10570
|
name: "Counterattack",
|
|
10113
|
-
description: "
|
|
10571
|
+
description: "Strikes an attacking enemy force with defending forces to deny its objectives and regain the initiative.",
|
|
10114
10572
|
entity: "Mission Tasks",
|
|
10115
10573
|
entityType: "Counterattack",
|
|
10116
10574
|
value: "340600",
|
|
@@ -10211,7 +10669,7 @@ const COUNTERATTACK_BY_FIRE_HEAD_RATIO = .18;
|
|
|
10211
10669
|
const COUNTERATTACK_BY_FIRE_METADATA = {
|
|
10212
10670
|
id: "counterattack-by-fire",
|
|
10213
10671
|
name: "Counterattack by Fire",
|
|
10214
|
-
description: "
|
|
10672
|
+
description: "Uses primarily direct and indirect fires against an attacking enemy force to defeat or destroy it, restore a position, or block a penetration.",
|
|
10215
10673
|
entity: "Mission Tasks",
|
|
10216
10674
|
entityType: "Counterattack by Fire",
|
|
10217
10675
|
value: "340700",
|
|
@@ -10587,7 +11045,7 @@ const DEFAULT_COVER_OPTIONS = DEFAULT_SECURITY_TASK_OPTIONS;
|
|
|
10587
11045
|
const COVER_METADATA = {
|
|
10588
11046
|
id: "cover",
|
|
10589
11047
|
name: "Cover",
|
|
10590
|
-
description: "Protects the main body by fighting to gain time
|
|
11048
|
+
description: "Protects the main body by fighting to gain time and preventing enemy ground observation and direct fire. Operates independently of the main body.",
|
|
10591
11049
|
entity: "Mission Tasks",
|
|
10592
11050
|
entityType: "Security",
|
|
10593
11051
|
entitySubtype: "Cover",
|
|
@@ -10631,7 +11089,7 @@ const COVER = defineControlMeasure({
|
|
|
10631
11089
|
const DEFAULT_DELAY_OPTIONS = {
|
|
10632
11090
|
arrowheadLengthRatio: .12,
|
|
10633
11091
|
arrowheadWidthRatio: .12,
|
|
10634
|
-
|
|
11092
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING,
|
|
10635
11093
|
arcSegments: 32
|
|
10636
11094
|
};
|
|
10637
11095
|
const DELAY_PARAMS = [
|
|
@@ -10655,16 +11113,7 @@ const DELAY_PARAMS = [
|
|
|
10655
11113
|
max: 1,
|
|
10656
11114
|
step: .01
|
|
10657
11115
|
},
|
|
10658
|
-
|
|
10659
|
-
key: "labelGapRatio",
|
|
10660
|
-
presentationTier: "advanced",
|
|
10661
|
-
label: "Label gap",
|
|
10662
|
-
description: "Size of the shaft gap holding the task label, as a ratio of the shaft length",
|
|
10663
|
-
type: "number",
|
|
10664
|
-
min: 0,
|
|
10665
|
-
max: 1,
|
|
10666
|
-
step: .01
|
|
10667
|
-
},
|
|
11116
|
+
LABEL_PADDING_PARAM,
|
|
10668
11117
|
{
|
|
10669
11118
|
key: "arcSegments",
|
|
10670
11119
|
presentationTier: "advanced",
|
|
@@ -10694,9 +11143,16 @@ const DELAY_METADATA = {
|
|
|
10694
11143
|
},
|
|
10695
11144
|
drawRule: "Line24",
|
|
10696
11145
|
capturesLabelSize: true,
|
|
11146
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
10697
11147
|
params: DELAY_PARAMS
|
|
10698
11148
|
};
|
|
10699
|
-
function createDelaySymbol(coordinates, options = {}) {
|
|
11149
|
+
function createDelaySymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
11150
|
+
return createDelayTaskSymbol(coordinates, {
|
|
11151
|
+
glyph: "D",
|
|
11152
|
+
part: "delay"
|
|
11153
|
+
}, options, context);
|
|
11154
|
+
}
|
|
11155
|
+
function createDelayTaskSymbol(coordinates, config, options, context) {
|
|
10700
11156
|
const [c1, c2, c3] = coordinates;
|
|
10701
11157
|
const p1 = project(c1[0], c1[1]);
|
|
10702
11158
|
const p2 = project(c2[0], c2[1]);
|
|
@@ -10712,8 +11168,9 @@ function createDelaySymbol(coordinates, options = {}) {
|
|
|
10712
11168
|
const signedDiameter = vecDot(vecSub(p3, p2), diameterAxis);
|
|
10713
11169
|
const diameterEnd = vecAdd(p2, vecScale(diameterAxis, signedDiameter));
|
|
10714
11170
|
const diameter = Math.abs(signedDiameter);
|
|
10715
|
-
const gapHalf = lineLength * Math.min(1, Math.max(0, options.labelGapRatio ?? DEFAULT_DELAY_OPTIONS.labelGapRatio)) / 2;
|
|
10716
11171
|
const labelPoint = vecAdd(p1, vecScale(dirLine, lineLength / 2));
|
|
11172
|
+
const labelRotation = labelRotationAlong(dirLine);
|
|
11173
|
+
const gapHalf = Math.min(lineLength / 2, labelKnockoutHalfExtent(config.glyph, dirLine, labelRotation, options, context, DEFAULT_DELAY_OPTIONS.labelPadding));
|
|
10717
11174
|
const gapStart = vecAdd(labelPoint, vecScale(dirLine, -gapHalf));
|
|
10718
11175
|
const gapEnd = vecAdd(labelPoint, vecScale(dirLine, gapHalf));
|
|
10719
11176
|
const lines = [
|
|
@@ -10726,52 +11183,23 @@ function createDelaySymbol(coordinates, options = {}) {
|
|
|
10726
11183
|
type: "FeatureCollection",
|
|
10727
11184
|
features: [{
|
|
10728
11185
|
type: "Feature",
|
|
10729
|
-
properties: { part:
|
|
11186
|
+
properties: { part: config.part },
|
|
10730
11187
|
geometry: {
|
|
10731
11188
|
type: "MultiLineString",
|
|
10732
11189
|
coordinates: lines
|
|
10733
11190
|
}
|
|
10734
|
-
}, createLabelFeature(labelPoint,
|
|
11191
|
+
}, createLabelFeature(labelPoint, config.glyph, labelRotation, {
|
|
10735
11192
|
labelSize: options.labelSize,
|
|
10736
11193
|
labelSizePixels: options.labelSizePixels
|
|
10737
11194
|
})]
|
|
10738
11195
|
};
|
|
10739
11196
|
}
|
|
10740
|
-
function createSemicircleArc(start, end, bulgeDir, requestedSegments) {
|
|
10741
|
-
const diameterVector = vecSub(end, start);
|
|
10742
|
-
const diameter = vecMag(diameterVector);
|
|
10743
|
-
if (diameter < 1e-6) return [unproject(start[0], start[1])];
|
|
10744
|
-
const diameterDir = vecNorm(diameterVector);
|
|
10745
|
-
const center = vecAdd(start, vecScale(diameterVector, .5));
|
|
10746
|
-
const radius = diameter / 2;
|
|
10747
|
-
const segments = Math.max(4, Math.round(requestedSegments));
|
|
10748
|
-
const points = [];
|
|
10749
|
-
for (let i = 0; i <= segments; i += 1) {
|
|
10750
|
-
const theta = i / segments * Math.PI;
|
|
10751
|
-
const point = vecAdd(center, vecAdd(vecScale(diameterDir, -Math.cos(theta) * radius), vecScale(bulgeDir, Math.sin(theta) * radius)));
|
|
10752
|
-
points.push(unproject(point[0], point[1]));
|
|
10753
|
-
}
|
|
10754
|
-
return points;
|
|
10755
|
-
}
|
|
10756
11197
|
/**
|
|
10757
11198
|
* Line24 mission tasks that share Delay's geometry and differ only in the
|
|
10758
11199
|
* glyph shown in the shaft gap and the `part` tag on the line feature.
|
|
10759
11200
|
*/
|
|
10760
|
-
function createDelayVariantSymbol(coordinates, config, options = {}) {
|
|
10761
|
-
return
|
|
10762
|
-
type: "FeatureCollection",
|
|
10763
|
-
features: createDelaySymbol(coordinates, options).features.map((feature) => ({
|
|
10764
|
-
...feature,
|
|
10765
|
-
properties: feature.geometry.type === "Point" ? {
|
|
10766
|
-
...feature.properties,
|
|
10767
|
-
part: "label",
|
|
10768
|
-
text: config.glyph
|
|
10769
|
-
} : {
|
|
10770
|
-
...feature.properties,
|
|
10771
|
-
part: config.part
|
|
10772
|
-
}
|
|
10773
|
-
}))
|
|
10774
|
-
};
|
|
11201
|
+
function createDelayVariantSymbol(coordinates, config, options = {}, context = {}) {
|
|
11202
|
+
return createDelayTaskSymbol(coordinates, config, options, context);
|
|
10775
11203
|
}
|
|
10776
11204
|
const DELAY = defineControlMeasure({
|
|
10777
11205
|
metadata: DELAY_METADATA,
|
|
@@ -10780,6 +11208,122 @@ const DELAY = defineControlMeasure({
|
|
|
10780
11208
|
rule: line24DrawRule
|
|
10781
11209
|
});
|
|
10782
11210
|
//#endregion
|
|
11211
|
+
//#region src/generators/cm34-mission-tasks/hairpin.ts
|
|
11212
|
+
/**
|
|
11213
|
+
* Shared scaffold for the Area18 "hairpin" mission tasks (Relief in Place,
|
|
11214
|
+
* Demonstrate). Owns the doctrinal geometry both measures share: two parallel
|
|
11215
|
+
* arms with an arrow tip at each open end (PT. 1 and PT. 3), joined by a
|
|
11216
|
+
* semicircular turn across PT. 2 → PT. 3.
|
|
11217
|
+
*
|
|
11218
|
+
* Both tips use the same option ratios, each scaled by the length of its own
|
|
11219
|
+
* arm (PT. 1/PT. 2 and PT. 3/PT. 4). Only glyph placement differs between the
|
|
11220
|
+
* measures, which each supply via {@link HairpinSymbolConfig.buildLabel}.
|
|
11221
|
+
*/
|
|
11222
|
+
function createHairpinSymbol(coordinates, config, options = {}) {
|
|
11223
|
+
const [c1, c2, c3, c4] = coordinates;
|
|
11224
|
+
const p1 = project(c1[0], c1[1]);
|
|
11225
|
+
const p2 = project(c2[0], c2[1]);
|
|
11226
|
+
const p3 = project(c3[0], c3[1]);
|
|
11227
|
+
const p4 = project(c4[0], c4[1]);
|
|
11228
|
+
const upperShaft = vecSub(p2, p1);
|
|
11229
|
+
const upperLength = vecMag(upperShaft);
|
|
11230
|
+
const lowerShaft = vecSub(p3, p4);
|
|
11231
|
+
const lowerLength = vecMag(lowerShaft);
|
|
11232
|
+
if (upperLength < 1e-6 || lowerLength < 1e-6) return {
|
|
11233
|
+
type: "FeatureCollection",
|
|
11234
|
+
features: []
|
|
11235
|
+
};
|
|
11236
|
+
const upperDirection = vecNorm(upperShaft);
|
|
11237
|
+
const lowerDirection = vecNorm(lowerShaft);
|
|
11238
|
+
const arrowheadLengthRatio = Math.max(0, options.arrowheadLengthRatio ?? DEFAULT_DELAY_OPTIONS.arrowheadLengthRatio);
|
|
11239
|
+
const arrowheadWidthRatio = Math.max(0, options.arrowheadWidthRatio ?? DEFAULT_DELAY_OPTIONS.arrowheadWidthRatio);
|
|
11240
|
+
const { label, upperArm } = config.buildLabel({
|
|
11241
|
+
points: [
|
|
11242
|
+
p1,
|
|
11243
|
+
p2,
|
|
11244
|
+
p3,
|
|
11245
|
+
p4
|
|
11246
|
+
],
|
|
11247
|
+
upperDirection,
|
|
11248
|
+
upperLength,
|
|
11249
|
+
upperArm: [[unproject(p1[0], p1[1]), unproject(p2[0], p2[1])]]
|
|
11250
|
+
});
|
|
11251
|
+
return {
|
|
11252
|
+
type: "FeatureCollection",
|
|
11253
|
+
features: [{
|
|
11254
|
+
type: "Feature",
|
|
11255
|
+
properties: { part: config.part },
|
|
11256
|
+
geometry: {
|
|
11257
|
+
type: "MultiLineString",
|
|
11258
|
+
coordinates: [
|
|
11259
|
+
...upperArm,
|
|
11260
|
+
createSemicircleArc(p2, p3, upperDirection, options.arcSegments ?? DEFAULT_DELAY_OPTIONS.arcSegments),
|
|
11261
|
+
[unproject(p4[0], p4[1]), unproject(p3[0], p3[1])],
|
|
11262
|
+
createArrowHead$1(p1, vecScale(upperDirection, -1), upperLength * arrowheadLengthRatio, upperLength * arrowheadWidthRatio),
|
|
11263
|
+
createArrowHead$1(p3, lowerDirection, lowerLength * arrowheadLengthRatio, lowerLength * arrowheadWidthRatio)
|
|
11264
|
+
]
|
|
11265
|
+
}
|
|
11266
|
+
}, label]
|
|
11267
|
+
};
|
|
11268
|
+
}
|
|
11269
|
+
//#endregion
|
|
11270
|
+
//#region src/generators/cm34-mission-tasks/demonstrate.ts
|
|
11271
|
+
const DEFAULT_DEMONSTRATE_OPTIONS = DEFAULT_DELAY_OPTIONS;
|
|
11272
|
+
const DEMONSTRATE_METADATA = {
|
|
11273
|
+
id: "demonstrate",
|
|
11274
|
+
name: "Demonstrate/Demonstration",
|
|
11275
|
+
description: "Deceives the enemy with a show of force in an area where no decision is sought, without intending direct or indirect contact.",
|
|
11276
|
+
entity: "Mission Tasks",
|
|
11277
|
+
entityType: "Demonstrate/Demonstration",
|
|
11278
|
+
value: "343300",
|
|
11279
|
+
minCoordinates: 4,
|
|
11280
|
+
maxCoordinates: 4,
|
|
11281
|
+
geometry: "line",
|
|
11282
|
+
geometryTypes: ["MultiLineString", "Point"],
|
|
11283
|
+
paints: {
|
|
11284
|
+
stroke: true,
|
|
11285
|
+
fill: "none",
|
|
11286
|
+
text: true
|
|
11287
|
+
},
|
|
11288
|
+
drawRule: "Area18",
|
|
11289
|
+
capturesLabelSize: true,
|
|
11290
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
11291
|
+
params: DELAY_PARAMS
|
|
11292
|
+
};
|
|
11293
|
+
/** Generates the DEMONSTRATE/DEMONSTRATION mission task symbol (343300). */
|
|
11294
|
+
function createDemonstrateSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
11295
|
+
return createHairpinSymbol(coordinates, {
|
|
11296
|
+
part: "demonstrate",
|
|
11297
|
+
buildLabel: ({ points, upperDirection, upperLength }) => {
|
|
11298
|
+
const [p1, p2] = points;
|
|
11299
|
+
const labelPoint = vecAdd(p1, vecScale(upperDirection, upperLength / 2));
|
|
11300
|
+
const rotation = labelRotationAlong(upperDirection);
|
|
11301
|
+
const gapHalf = Math.min(upperLength / 2, labelKnockoutHalfExtent("DEM", upperDirection, rotation, options, context, DEFAULT_DEMONSTRATE_OPTIONS.labelPadding));
|
|
11302
|
+
const gapStart = vecAdd(labelPoint, vecScale(upperDirection, -gapHalf));
|
|
11303
|
+
const gapEnd = vecAdd(labelPoint, vecScale(upperDirection, gapHalf));
|
|
11304
|
+
return {
|
|
11305
|
+
upperArm: [[unproject(p1[0], p1[1]), unproject(gapStart[0], gapStart[1])], [unproject(gapEnd[0], gapEnd[1]), unproject(p2[0], p2[1])]],
|
|
11306
|
+
label: createLabelFeature(labelPoint, "DEM", rotation, {
|
|
11307
|
+
labelSize: options.labelSize,
|
|
11308
|
+
labelSizePixels: options.labelSizePixels
|
|
11309
|
+
})
|
|
11310
|
+
};
|
|
11311
|
+
}
|
|
11312
|
+
}, options);
|
|
11313
|
+
}
|
|
11314
|
+
const DEMONSTRATE = defineControlMeasure({
|
|
11315
|
+
metadata: DEMONSTRATE_METADATA,
|
|
11316
|
+
generator: createDemonstrateSymbol,
|
|
11317
|
+
defaultOptions: DEFAULT_DEMONSTRATE_OPTIONS,
|
|
11318
|
+
rule: demonstrateDrawRule,
|
|
11319
|
+
previewSample: { controlPoints: [
|
|
11320
|
+
[-1, -.45],
|
|
11321
|
+
[.45, -.45],
|
|
11322
|
+
[.45, .45],
|
|
11323
|
+
[-1, .45]
|
|
11324
|
+
] }
|
|
11325
|
+
});
|
|
11326
|
+
//#endregion
|
|
10783
11327
|
//#region src/generators/cm34-mission-tasks/disengage.ts
|
|
10784
11328
|
const DEFAULT_DISENGAGE_OPTIONS = DEFAULT_DELAY_OPTIONS;
|
|
10785
11329
|
const DISENGAGE_METADATA = {
|
|
@@ -10800,27 +11344,25 @@ const DISENGAGE_METADATA = {
|
|
|
10800
11344
|
},
|
|
10801
11345
|
drawRule: "Line24",
|
|
10802
11346
|
capturesLabelSize: true,
|
|
11347
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
10803
11348
|
params: DELAY_PARAMS
|
|
10804
11349
|
};
|
|
10805
|
-
function createDisengageSymbol(coordinates, options = {}) {
|
|
11350
|
+
function createDisengageSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
10806
11351
|
return createDelayVariantSymbol(coordinates, {
|
|
10807
11352
|
glyph: "DIS",
|
|
10808
11353
|
part: "disengage"
|
|
10809
|
-
}, options);
|
|
11354
|
+
}, options, context);
|
|
10810
11355
|
}
|
|
10811
11356
|
const DISENGAGE = defineControlMeasure({
|
|
10812
11357
|
metadata: DISENGAGE_METADATA,
|
|
10813
11358
|
generator: createDisengageSymbol,
|
|
10814
11359
|
defaultOptions: DEFAULT_DISENGAGE_OPTIONS,
|
|
10815
11360
|
rule: line24DrawRule,
|
|
10816
|
-
previewSample: {
|
|
10817
|
-
|
|
10818
|
-
|
|
10819
|
-
|
|
10820
|
-
|
|
10821
|
-
],
|
|
10822
|
-
options: { labelGapRatio: .36 }
|
|
10823
|
-
}
|
|
11361
|
+
previewSample: { controlPoints: [
|
|
11362
|
+
[-1, -.4],
|
|
11363
|
+
[1, -.4],
|
|
11364
|
+
[0, .6]
|
|
11365
|
+
] }
|
|
10824
11366
|
});
|
|
10825
11367
|
//#endregion
|
|
10826
11368
|
//#region src/generators/cm34-mission-tasks/disrupt.ts
|
|
@@ -10830,7 +11372,7 @@ const DEFAULT_DISRUPT_MISSION_TASK_OPTIONS = {
|
|
|
10830
11372
|
shaftLengthRatio: .25,
|
|
10831
11373
|
arrowheadLengthRatio: .15,
|
|
10832
11374
|
arrowheadWidthRatio: .2,
|
|
10833
|
-
|
|
11375
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING
|
|
10834
11376
|
};
|
|
10835
11377
|
const DISRUPT_MISSION_TASK_METADATA = {
|
|
10836
11378
|
id: "disrupt-mission-task",
|
|
@@ -10850,6 +11392,7 @@ const DISRUPT_MISSION_TASK_METADATA = {
|
|
|
10850
11392
|
},
|
|
10851
11393
|
drawRule: "Area12",
|
|
10852
11394
|
capturesLabelSize: true,
|
|
11395
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
10853
11396
|
params: [
|
|
10854
11397
|
{
|
|
10855
11398
|
key: "middleArrowLengthRatio",
|
|
@@ -10901,20 +11444,11 @@ const DISRUPT_MISSION_TASK_METADATA = {
|
|
|
10901
11444
|
max: 1,
|
|
10902
11445
|
step: .01
|
|
10903
11446
|
},
|
|
10904
|
-
|
|
10905
|
-
key: "labelGapRatio",
|
|
10906
|
-
presentationTier: "advanced",
|
|
10907
|
-
label: "Label gap",
|
|
10908
|
-
description: "Size of the middle-shaft gap holding the 'D' label",
|
|
10909
|
-
type: "number",
|
|
10910
|
-
min: 0,
|
|
10911
|
-
max: 1,
|
|
10912
|
-
step: .01
|
|
10913
|
-
}
|
|
11447
|
+
LABEL_PADDING_PARAM
|
|
10914
11448
|
]
|
|
10915
11449
|
};
|
|
10916
11450
|
/** Generates the DISRUPT mission task symbol (341000). */
|
|
10917
|
-
function createDisruptMissionTaskSymbol(coordinates, options = {}) {
|
|
11451
|
+
function createDisruptMissionTaskSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
10918
11452
|
const p1 = project(coordinates[0][0], coordinates[0][1]);
|
|
10919
11453
|
const p2 = project(coordinates[1][0], coordinates[1][1]);
|
|
10920
11454
|
const frame = createProjectedBaselineFrame(p1, p2, {
|
|
@@ -10946,8 +11480,8 @@ function createDisruptMissionTaskSymbol(coordinates, options = {}) {
|
|
|
10946
11480
|
const bottomTip = vecAdd(p2, vecScale(arrowDirection, bottomLength));
|
|
10947
11481
|
const rearEnd = vecAdd(middle, vecScale(arrowDirection, -rearLength));
|
|
10948
11482
|
const labelPoint = vecScale(vecAdd(middle, middleTip), .5);
|
|
10949
|
-
const
|
|
10950
|
-
const gapHalf = Math.min(
|
|
11483
|
+
const labelRotation = labelRotationAlong(arrowDirection);
|
|
11484
|
+
const gapHalf = Math.min(labelKnockoutHalfExtent("D", arrowDirection, labelRotation, options, context, DEFAULT_DISRUPT_MISSION_TASK_OPTIONS.labelPadding), middleLength / 2);
|
|
10951
11485
|
const gapStart = vecAdd(labelPoint, vecScale(arrowDirection, -gapHalf));
|
|
10952
11486
|
const gapEnd = vecAdd(labelPoint, vecScale(arrowDirection, gapHalf));
|
|
10953
11487
|
const p1LonLat = unproject(p1[0], p1[1]);
|
|
@@ -10970,7 +11504,7 @@ function createDisruptMissionTaskSymbol(coordinates, options = {}) {
|
|
|
10970
11504
|
createArrowHead$1(bottomTip, arrowDirection, headLength, headWidth)
|
|
10971
11505
|
]
|
|
10972
11506
|
}
|
|
10973
|
-
}, createLabelFeature(labelPoint, "D",
|
|
11507
|
+
}, createLabelFeature(labelPoint, "D", labelRotation, {
|
|
10974
11508
|
labelSize: options.labelSize,
|
|
10975
11509
|
labelSizePixels: options.labelSizePixels
|
|
10976
11510
|
})]
|
|
@@ -10989,13 +11523,13 @@ const DEFAULT_ENVELOPMENT_OPTIONS = {
|
|
|
10989
11523
|
arrowheadLengthRatio: .1,
|
|
10990
11524
|
arrowheadWidthRatio: .12,
|
|
10991
11525
|
labelPosition: .6,
|
|
10992
|
-
|
|
11526
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING,
|
|
10993
11527
|
resolution: 64
|
|
10994
11528
|
};
|
|
10995
11529
|
const ENVELOPMENT_METADATA = {
|
|
10996
11530
|
id: "envelopment",
|
|
10997
11531
|
name: "Envelopment",
|
|
10998
|
-
description: "
|
|
11532
|
+
description: "Avoids the enemy’s principal defenses by attacking along an assailable flank to strike the flank or rear.",
|
|
10999
11533
|
entity: "Mission Tasks",
|
|
11000
11534
|
entityType: "Envelopment",
|
|
11001
11535
|
value: "343500",
|
|
@@ -11010,6 +11544,7 @@ const ENVELOPMENT_METADATA = {
|
|
|
11010
11544
|
},
|
|
11011
11545
|
drawRule: "Line31",
|
|
11012
11546
|
capturesLabelSize: true,
|
|
11547
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
11013
11548
|
params: [
|
|
11014
11549
|
{
|
|
11015
11550
|
key: "arrowheadLengthRatio",
|
|
@@ -11041,76 +11576,351 @@ const ENVELOPMENT_METADATA = {
|
|
|
11041
11576
|
max: 1,
|
|
11042
11577
|
step: .01
|
|
11043
11578
|
},
|
|
11044
|
-
|
|
11045
|
-
key: "labelGapRatio",
|
|
11046
|
-
presentationTier: "advanced",
|
|
11047
|
-
label: "Label gap",
|
|
11048
|
-
description: "Size of the straight-line cutout holding the E label",
|
|
11049
|
-
type: "number",
|
|
11050
|
-
min: 0,
|
|
11051
|
-
max: 1,
|
|
11052
|
-
step: .01
|
|
11053
|
-
},
|
|
11579
|
+
LABEL_PADDING_PARAM,
|
|
11054
11580
|
...ARC_RESOLUTION_PARAMS
|
|
11055
11581
|
]
|
|
11056
11582
|
};
|
|
11057
|
-
/** Generates the Envelopment mission task symbol (343500). */
|
|
11058
|
-
function createEnvelopment(coordinates, options = {}) {
|
|
11059
|
-
const [c1, c2, c3, c4] = coordinates;
|
|
11060
|
-
const p1 = project(c1[0], c1[1]);
|
|
11061
|
-
const p2 = project(c2[0], c2[1]);
|
|
11062
|
-
const diameterAnchor = project(c3[0], c3[1]);
|
|
11063
|
-
const p4 = project(c4[0], c4[1]);
|
|
11064
|
-
const straight = vecSub(p2, p1);
|
|
11065
|
-
const straightLength = vecMag(straight);
|
|
11066
|
-
const diameter = vecMag(vecSub(diameterAnchor, p2));
|
|
11067
|
-
if (straightLength < 1e-6 || diameter < 1e-6) return {
|
|
11583
|
+
/** Generates the Envelopment mission task symbol (343500). */
|
|
11584
|
+
function createEnvelopment(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
11585
|
+
const [c1, c2, c3, c4] = coordinates;
|
|
11586
|
+
const p1 = project(c1[0], c1[1]);
|
|
11587
|
+
const p2 = project(c2[0], c2[1]);
|
|
11588
|
+
const diameterAnchor = project(c3[0], c3[1]);
|
|
11589
|
+
const p4 = project(c4[0], c4[1]);
|
|
11590
|
+
const straight = vecSub(p2, p1);
|
|
11591
|
+
const straightLength = vecMag(straight);
|
|
11592
|
+
const diameter = vecMag(vecSub(diameterAnchor, p2));
|
|
11593
|
+
if (straightLength < 1e-6 || diameter < 1e-6) return {
|
|
11594
|
+
type: "FeatureCollection",
|
|
11595
|
+
features: []
|
|
11596
|
+
};
|
|
11597
|
+
const straightDirection = vecNorm(straight);
|
|
11598
|
+
const p3 = vecAdd(p2, vecScale(straightDirection, diameter));
|
|
11599
|
+
const labelPosition = clamp01(options.labelPosition ?? DEFAULT_ENVELOPMENT_OPTIONS.labelPosition);
|
|
11600
|
+
const labelPoint = vecAdd(p1, vecScale(straight, labelPosition));
|
|
11601
|
+
const labelRotation = labelRotationAlong(straightDirection);
|
|
11602
|
+
const gapHalf = Math.min(straightLength * Math.min(labelPosition, 1 - labelPosition), labelKnockoutHalfExtent("E", straightDirection, labelRotation, options, context, DEFAULT_ENVELOPMENT_OPTIONS.labelPadding));
|
|
11603
|
+
const gapStart = vecAdd(labelPoint, vecScale(straightDirection, -gapHalf));
|
|
11604
|
+
const gapEnd = vecAdd(labelPoint, vecScale(straightDirection, gapHalf));
|
|
11605
|
+
const center = midpoint(p2, p3);
|
|
11606
|
+
const startRadius = vecSub(p2, center);
|
|
11607
|
+
let bulgeRadius = [-startRadius[1], startRadius[0]];
|
|
11608
|
+
if (vecDot(vecSub(p4, center), bulgeRadius) < 0) bulgeRadius = vecScale(bulgeRadius, -1);
|
|
11609
|
+
const arc = sampleProjectedArc(center, startRadius, bulgeRadius, 0, Math.PI, normalizeArcResolution(options.resolution) / 2);
|
|
11610
|
+
const arrowhead = createArrowHead$1(p3, vecNorm(vecScale(bulgeRadius, -1)), diameter * Math.max(0, options.arrowheadLengthRatio ?? DEFAULT_ENVELOPMENT_OPTIONS.arrowheadLengthRatio), diameter * Math.max(0, options.arrowheadWidthRatio ?? DEFAULT_ENVELOPMENT_OPTIONS.arrowheadWidthRatio));
|
|
11611
|
+
return {
|
|
11612
|
+
type: "FeatureCollection",
|
|
11613
|
+
features: [{
|
|
11614
|
+
type: "Feature",
|
|
11615
|
+
properties: { part: "envelopment" },
|
|
11616
|
+
geometry: {
|
|
11617
|
+
type: "MultiLineString",
|
|
11618
|
+
coordinates: [
|
|
11619
|
+
[c1, unproject(gapStart[0], gapStart[1])],
|
|
11620
|
+
[unproject(gapEnd[0], gapEnd[1]), c2],
|
|
11621
|
+
arc,
|
|
11622
|
+
arrowhead
|
|
11623
|
+
]
|
|
11624
|
+
}
|
|
11625
|
+
}, createLabelFeature(labelPoint, "E", labelRotation, options)]
|
|
11626
|
+
};
|
|
11627
|
+
}
|
|
11628
|
+
const ENVELOPMENT = defineControlMeasure({
|
|
11629
|
+
metadata: ENVELOPMENT_METADATA,
|
|
11630
|
+
generator: createEnvelopment,
|
|
11631
|
+
defaultOptions: DEFAULT_ENVELOPMENT_OPTIONS,
|
|
11632
|
+
rule: line31DrawRule,
|
|
11633
|
+
previewSample: {
|
|
11634
|
+
controlPoints: [
|
|
11635
|
+
[-.9, -.25],
|
|
11636
|
+
[-.15, -.25],
|
|
11637
|
+
[.85, -.25],
|
|
11638
|
+
[.35, .45]
|
|
11639
|
+
],
|
|
11640
|
+
options: { labelSize: 100 }
|
|
11641
|
+
}
|
|
11642
|
+
});
|
|
11643
|
+
//#endregion
|
|
11644
|
+
//#region src/generators/cm34-mission-tasks/exploitation.ts
|
|
11645
|
+
const EXPLOITATION_FIN_DASH = [6, 4];
|
|
11646
|
+
const DEFAULT_EXPLOITATION_OPTIONS = {};
|
|
11647
|
+
const EXPLOITATION_METADATA = {
|
|
11648
|
+
id: "exploitation",
|
|
11649
|
+
name: "Exploit/Exploitation",
|
|
11650
|
+
description: "Follows a successful attack to disorganize the enemy in depth and capitalize on its reduced ability to react.",
|
|
11651
|
+
entity: "Mission Tasks",
|
|
11652
|
+
entityType: "Exploit/Exploitation",
|
|
11653
|
+
value: "343100",
|
|
11654
|
+
minCoordinates: 3,
|
|
11655
|
+
maxCoordinates: 3,
|
|
11656
|
+
geometry: "line",
|
|
11657
|
+
geometryTypes: ["MultiLineString"],
|
|
11658
|
+
paints: {
|
|
11659
|
+
stroke: true,
|
|
11660
|
+
fill: "none",
|
|
11661
|
+
text: false
|
|
11662
|
+
},
|
|
11663
|
+
drawRule: "Line30"
|
|
11664
|
+
};
|
|
11665
|
+
/** Generates the Exploit/Exploitation mission task symbol (343100). */
|
|
11666
|
+
function createExploitation(coordinates, _options = {}) {
|
|
11667
|
+
const [c1, c2, c3] = coordinates;
|
|
11668
|
+
const p1 = project(c1[0], c1[1]);
|
|
11669
|
+
const p2 = project(c2[0], c2[1]);
|
|
11670
|
+
const p3 = project(c3[0], c3[1]);
|
|
11671
|
+
const axis = vecSub(p1, p2);
|
|
11672
|
+
const axisLength = vecMag(axis);
|
|
11673
|
+
const finLength = vecMag(vecSub(p3, p2));
|
|
11674
|
+
if (axisLength < 1e-6 || finLength < 1e-6) return {
|
|
11675
|
+
type: "FeatureCollection",
|
|
11676
|
+
features: []
|
|
11677
|
+
};
|
|
11678
|
+
const direction = vecNorm(axis);
|
|
11679
|
+
const chevron = (tip) => createArrowHead$1(tip, direction, finLength * Math.SQRT1_2, finLength * Math.SQRT2);
|
|
11680
|
+
const [finLeft, , finRight] = chevron(p2);
|
|
11681
|
+
return {
|
|
11682
|
+
type: "FeatureCollection",
|
|
11683
|
+
features: [{
|
|
11684
|
+
type: "Feature",
|
|
11685
|
+
properties: { part: "exploitation" },
|
|
11686
|
+
geometry: {
|
|
11687
|
+
type: "MultiLineString",
|
|
11688
|
+
coordinates: [[c2, c1], chevron(p1)]
|
|
11689
|
+
}
|
|
11690
|
+
}, {
|
|
11691
|
+
type: "Feature",
|
|
11692
|
+
properties: {
|
|
11693
|
+
part: "fins",
|
|
11694
|
+
style: { strokeDash: [...EXPLOITATION_FIN_DASH] }
|
|
11695
|
+
},
|
|
11696
|
+
geometry: {
|
|
11697
|
+
type: "MultiLineString",
|
|
11698
|
+
coordinates: [[c2, finLeft], [c2, finRight]]
|
|
11699
|
+
}
|
|
11700
|
+
}]
|
|
11701
|
+
};
|
|
11702
|
+
}
|
|
11703
|
+
const EXPLOITATION = defineControlMeasure({
|
|
11704
|
+
metadata: EXPLOITATION_METADATA,
|
|
11705
|
+
generator: createExploitation,
|
|
11706
|
+
defaultOptions: DEFAULT_EXPLOITATION_OPTIONS,
|
|
11707
|
+
rule: line30DrawRule,
|
|
11708
|
+
previewSample: { controlPoints: [
|
|
11709
|
+
[.8, 0],
|
|
11710
|
+
[-.6, 0],
|
|
11711
|
+
[-.9, .3]
|
|
11712
|
+
] }
|
|
11713
|
+
});
|
|
11714
|
+
//#endregion
|
|
11715
|
+
//#region src/generators/cm34-mission-tasks/follow-shared.ts
|
|
11716
|
+
const DEFAULT_FOLLOW_OPTIONS = { arrowheadSize: 1 };
|
|
11717
|
+
const FOLLOW_ARROWHEAD_SIZE_PARAM = {
|
|
11718
|
+
key: "arrowheadSize",
|
|
11719
|
+
presentationTier: "advanced",
|
|
11720
|
+
label: "Arrowhead size",
|
|
11721
|
+
description: "Arrowhead scale relative to its default size; the tip stays at point 1.",
|
|
11722
|
+
type: "number",
|
|
11723
|
+
min: .1,
|
|
11724
|
+
max: 3,
|
|
11725
|
+
step: .1
|
|
11726
|
+
};
|
|
11727
|
+
const FOLLOW_TEXT_AMPLIFIERS = [{
|
|
11728
|
+
key: "T",
|
|
11729
|
+
label: "Unique designation",
|
|
11730
|
+
description: "Field T — optional unique designation inside the rear shape.",
|
|
11731
|
+
maxLength: 21
|
|
11732
|
+
}];
|
|
11733
|
+
/** Both anchors are independent, so the preview reads left-to-right through PT. 1. */
|
|
11734
|
+
const FOLLOW_PREVIEW_SAMPLE = { controlPoints: [[1, 0], [-1, 0]] };
|
|
11735
|
+
const FOLLOW_AND_ASSUME_VARIANT = {
|
|
11736
|
+
body: [
|
|
11737
|
+
[0, .09],
|
|
11738
|
+
[.24, .09],
|
|
11739
|
+
[.32, 0],
|
|
11740
|
+
[.24, -.09],
|
|
11741
|
+
[0, -.09],
|
|
11742
|
+
[0, .09]
|
|
11743
|
+
],
|
|
11744
|
+
shaftEnd: .95,
|
|
11745
|
+
shaftDash: [6, 4],
|
|
11746
|
+
arrowhead: {
|
|
11747
|
+
filled: false,
|
|
11748
|
+
points: [
|
|
11749
|
+
[1, 0],
|
|
11750
|
+
[.78, .22],
|
|
11751
|
+
[.78, .18],
|
|
11752
|
+
[.96, 0],
|
|
11753
|
+
[.78, -.18],
|
|
11754
|
+
[.78, -.22],
|
|
11755
|
+
[1, 0]
|
|
11756
|
+
]
|
|
11757
|
+
},
|
|
11758
|
+
labelX: .14
|
|
11759
|
+
};
|
|
11760
|
+
const FOLLOW_AND_SUPPORT_VARIANT = {
|
|
11761
|
+
body: [
|
|
11762
|
+
[0, .08],
|
|
11763
|
+
[.26, .08],
|
|
11764
|
+
[.32, 0],
|
|
11765
|
+
[.26, -.08],
|
|
11766
|
+
[0, -.08],
|
|
11767
|
+
[.08, 0],
|
|
11768
|
+
[0, .08]
|
|
11769
|
+
],
|
|
11770
|
+
shaftEnd: .81,
|
|
11771
|
+
arrowhead: {
|
|
11772
|
+
filled: true,
|
|
11773
|
+
points: [
|
|
11774
|
+
[1, 0],
|
|
11775
|
+
[.81, .075],
|
|
11776
|
+
[.81, -.075],
|
|
11777
|
+
[1, 0]
|
|
11778
|
+
]
|
|
11779
|
+
},
|
|
11780
|
+
labelX: .17
|
|
11781
|
+
};
|
|
11782
|
+
function createFollowSymbol(coordinates, config, options, textAmplifiers) {
|
|
11783
|
+
const tip = project(coordinates[0][0], coordinates[0][1]);
|
|
11784
|
+
const rear = project(coordinates[1][0], coordinates[1][1]);
|
|
11785
|
+
const dx = tip[0] - rear[0];
|
|
11786
|
+
const dy = tip[1] - rear[1];
|
|
11787
|
+
if (Math.hypot(dx, dy) < 1e-6) return {
|
|
11068
11788
|
type: "FeatureCollection",
|
|
11069
11789
|
features: []
|
|
11070
11790
|
};
|
|
11071
|
-
const
|
|
11072
|
-
const
|
|
11073
|
-
const
|
|
11074
|
-
const
|
|
11075
|
-
const
|
|
11076
|
-
const
|
|
11077
|
-
|
|
11078
|
-
const startRadius = vecSub(p2, center);
|
|
11079
|
-
let bulgeRadius = [-startRadius[1], startRadius[0]];
|
|
11080
|
-
if (vecDot(vecSub(p4, center), bulgeRadius) < 0) bulgeRadius = vecScale(bulgeRadius, -1);
|
|
11081
|
-
const arc = sampleProjectedArc(center, startRadius, bulgeRadius, 0, Math.PI, normalizeArcResolution(options.resolution) / 2);
|
|
11082
|
-
const arrowhead = createArrowHead$1(p3, vecNorm(vecScale(bulgeRadius, -1)), diameter * Math.max(0, options.arrowheadLengthRatio ?? DEFAULT_ENVELOPMENT_OPTIONS.arrowheadLengthRatio), diameter * Math.max(0, options.arrowheadWidthRatio ?? DEFAULT_ENVELOPMENT_OPTIONS.arrowheadWidthRatio));
|
|
11083
|
-
return {
|
|
11084
|
-
type: "FeatureCollection",
|
|
11085
|
-
features: [{
|
|
11791
|
+
const at = (x, y) => unproject(rear[0] + x * dx - y * dy, rear[1] + x * dy + y * dx);
|
|
11792
|
+
const requestedSize = options.arrowheadSize ?? DEFAULT_FOLLOW_OPTIONS.arrowheadSize;
|
|
11793
|
+
const size = Number.isFinite(requestedSize) ? clamp(requestedSize, FOLLOW_ARROWHEAD_SIZE_PARAM.min, FOLLOW_ARROWHEAD_SIZE_PARAM.max) : DEFAULT_FOLLOW_OPTIONS.arrowheadSize;
|
|
11794
|
+
const headAt = (x, y) => at(1 + (x - 1) * size, y * size);
|
|
11795
|
+
const head = config.arrowhead.points.map(([x, y]) => headAt(x, y));
|
|
11796
|
+
const features = [
|
|
11797
|
+
{
|
|
11086
11798
|
type: "Feature",
|
|
11087
|
-
properties: { part: "
|
|
11799
|
+
properties: { part: "body" },
|
|
11088
11800
|
geometry: {
|
|
11089
|
-
type: "
|
|
11090
|
-
coordinates: [
|
|
11091
|
-
|
|
11092
|
-
|
|
11093
|
-
|
|
11094
|
-
|
|
11095
|
-
|
|
11801
|
+
type: "LineString",
|
|
11802
|
+
coordinates: config.body.map(([x, y]) => at(x, y))
|
|
11803
|
+
}
|
|
11804
|
+
},
|
|
11805
|
+
{
|
|
11806
|
+
type: "Feature",
|
|
11807
|
+
properties: {
|
|
11808
|
+
part: "shaft",
|
|
11809
|
+
...config.shaftDash ? { style: { strokeDash: [...config.shaftDash] } } : {}
|
|
11810
|
+
},
|
|
11811
|
+
geometry: {
|
|
11812
|
+
type: "LineString",
|
|
11813
|
+
coordinates: [at(.32, 0), headAt(config.shaftEnd, 0)]
|
|
11814
|
+
}
|
|
11815
|
+
},
|
|
11816
|
+
config.arrowhead.filled ? {
|
|
11817
|
+
type: "Feature",
|
|
11818
|
+
properties: {
|
|
11819
|
+
part: "arrowhead",
|
|
11820
|
+
fill: true,
|
|
11821
|
+
style: { fillPattern: "solid" }
|
|
11822
|
+
},
|
|
11823
|
+
geometry: {
|
|
11824
|
+
type: "Polygon",
|
|
11825
|
+
coordinates: [head]
|
|
11826
|
+
}
|
|
11827
|
+
} : {
|
|
11828
|
+
type: "Feature",
|
|
11829
|
+
properties: { part: "arrowhead" },
|
|
11830
|
+
geometry: {
|
|
11831
|
+
type: "LineString",
|
|
11832
|
+
coordinates: head
|
|
11096
11833
|
}
|
|
11097
|
-
}
|
|
11834
|
+
}
|
|
11835
|
+
];
|
|
11836
|
+
const designation = textAmplifiers.T?.trim();
|
|
11837
|
+
if (designation) features.push({
|
|
11838
|
+
type: "Feature",
|
|
11839
|
+
properties: {
|
|
11840
|
+
part: "label",
|
|
11841
|
+
labelPlacement: false,
|
|
11842
|
+
amplifierField: "T",
|
|
11843
|
+
text: designation,
|
|
11844
|
+
rotation: labelRotationAlong([dx, dy]),
|
|
11845
|
+
...labelSizeProps(options)
|
|
11846
|
+
},
|
|
11847
|
+
geometry: {
|
|
11848
|
+
type: "Point",
|
|
11849
|
+
coordinates: at(config.labelX, 0)
|
|
11850
|
+
}
|
|
11851
|
+
});
|
|
11852
|
+
return {
|
|
11853
|
+
type: "FeatureCollection",
|
|
11854
|
+
features
|
|
11098
11855
|
};
|
|
11099
11856
|
}
|
|
11100
|
-
|
|
11101
|
-
|
|
11102
|
-
|
|
11103
|
-
|
|
11104
|
-
|
|
11105
|
-
|
|
11106
|
-
|
|
11107
|
-
|
|
11108
|
-
|
|
11109
|
-
|
|
11110
|
-
|
|
11857
|
+
//#endregion
|
|
11858
|
+
//#region src/generators/cm34-mission-tasks/follow-and-assume.ts
|
|
11859
|
+
function createFollowAndAssume(coordinates, options = {}, textAmplifiers = {}) {
|
|
11860
|
+
return createFollowSymbol(coordinates, FOLLOW_AND_ASSUME_VARIANT, options, textAmplifiers);
|
|
11861
|
+
}
|
|
11862
|
+
const FOLLOW_AND_ASSUME = defineControlMeasure({
|
|
11863
|
+
metadata: {
|
|
11864
|
+
id: "follow-and-assume",
|
|
11865
|
+
name: "Follow and Assume",
|
|
11866
|
+
description: "Follows and supports a lead force during an offensive operation and continues the mission if the lead force cannot continue.",
|
|
11867
|
+
entity: "Mission Tasks",
|
|
11868
|
+
entityType: "Follow and Assume",
|
|
11869
|
+
value: "341200",
|
|
11870
|
+
minCoordinates: 2,
|
|
11871
|
+
maxCoordinates: 2,
|
|
11872
|
+
geometry: "line",
|
|
11873
|
+
geometryTypes: ["LineString", "Point"],
|
|
11874
|
+
paints: {
|
|
11875
|
+
stroke: true,
|
|
11876
|
+
fill: "none",
|
|
11877
|
+
text: true
|
|
11878
|
+
},
|
|
11879
|
+
drawRule: "Line25",
|
|
11880
|
+
capturesLabelSize: true,
|
|
11881
|
+
params: [FOLLOW_ARROWHEAD_SIZE_PARAM],
|
|
11882
|
+
textAmplifiers: FOLLOW_TEXT_AMPLIFIERS
|
|
11883
|
+
},
|
|
11884
|
+
generator: createFollowAndAssume,
|
|
11885
|
+
defaultOptions: DEFAULT_FOLLOW_OPTIONS,
|
|
11886
|
+
rule: line25DrawRule,
|
|
11887
|
+
previewSample: FOLLOW_PREVIEW_SAMPLE
|
|
11888
|
+
});
|
|
11889
|
+
//#endregion
|
|
11890
|
+
//#region src/generators/cm34-mission-tasks/follow-and-support.ts
|
|
11891
|
+
function createFollowAndSupport(coordinates, options = {}, textAmplifiers = {}) {
|
|
11892
|
+
return createFollowSymbol(coordinates, FOLLOW_AND_SUPPORT_VARIANT, options, textAmplifiers);
|
|
11893
|
+
}
|
|
11894
|
+
const FOLLOW_AND_SUPPORT = defineControlMeasure({
|
|
11895
|
+
metadata: {
|
|
11896
|
+
id: "follow-and-support",
|
|
11897
|
+
name: "Follow and Support",
|
|
11898
|
+
description: "Follows and supports a lead force during an offensive operation, taking on tasks that would otherwise slow the lead force’s advance.",
|
|
11899
|
+
entity: "Mission Tasks",
|
|
11900
|
+
entityType: "Follow and Support",
|
|
11901
|
+
value: "341300",
|
|
11902
|
+
minCoordinates: 2,
|
|
11903
|
+
maxCoordinates: 2,
|
|
11904
|
+
geometry: "line",
|
|
11905
|
+
geometryTypes: [
|
|
11906
|
+
"LineString",
|
|
11907
|
+
"Polygon",
|
|
11908
|
+
"Point"
|
|
11111
11909
|
],
|
|
11112
|
-
|
|
11113
|
-
|
|
11910
|
+
paints: {
|
|
11911
|
+
stroke: true,
|
|
11912
|
+
fill: "fixed",
|
|
11913
|
+
text: true
|
|
11914
|
+
},
|
|
11915
|
+
drawRule: "Line25",
|
|
11916
|
+
capturesLabelSize: true,
|
|
11917
|
+
params: [FOLLOW_ARROWHEAD_SIZE_PARAM],
|
|
11918
|
+
textAmplifiers: FOLLOW_TEXT_AMPLIFIERS
|
|
11919
|
+
},
|
|
11920
|
+
generator: createFollowAndSupport,
|
|
11921
|
+
defaultOptions: DEFAULT_FOLLOW_OPTIONS,
|
|
11922
|
+
rule: line25DrawRule,
|
|
11923
|
+
previewSample: FOLLOW_PREVIEW_SAMPLE
|
|
11114
11924
|
});
|
|
11115
11925
|
//#endregion
|
|
11116
11926
|
//#region src/generators/cm34-mission-tasks/infiltration.ts
|
|
@@ -11118,7 +11928,7 @@ const DEFAULT_INFILTRATION_OPTIONS = {
|
|
|
11118
11928
|
arrowheadLengthRatio: .1,
|
|
11119
11929
|
arrowheadWidthRatio: .1,
|
|
11120
11930
|
labelPosition: .6,
|
|
11121
|
-
|
|
11931
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING,
|
|
11122
11932
|
resolution: 64
|
|
11123
11933
|
};
|
|
11124
11934
|
const INFILTRATION_METADATA = {
|
|
@@ -11139,6 +11949,7 @@ const INFILTRATION_METADATA = {
|
|
|
11139
11949
|
},
|
|
11140
11950
|
drawRule: "Line32",
|
|
11141
11951
|
capturesLabelSize: true,
|
|
11952
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
11142
11953
|
params: [
|
|
11143
11954
|
{
|
|
11144
11955
|
key: "arrowheadLengthRatio",
|
|
@@ -11170,16 +11981,7 @@ const INFILTRATION_METADATA = {
|
|
|
11170
11981
|
max: 1,
|
|
11171
11982
|
step: .01
|
|
11172
11983
|
},
|
|
11173
|
-
|
|
11174
|
-
key: "labelGapRatio",
|
|
11175
|
-
presentationTier: "advanced",
|
|
11176
|
-
label: "Label gap",
|
|
11177
|
-
description: "Size of the initial-line cutout holding the IN label",
|
|
11178
|
-
type: "number",
|
|
11179
|
-
min: 0,
|
|
11180
|
-
max: 1,
|
|
11181
|
-
step: .01
|
|
11182
|
-
},
|
|
11984
|
+
LABEL_PADDING_PARAM,
|
|
11183
11985
|
...ARC_RESOLUTION_PARAMS
|
|
11184
11986
|
]
|
|
11185
11987
|
};
|
|
@@ -11205,7 +12007,7 @@ function tangentDirection(p1, p2, p3) {
|
|
|
11205
12007
|
return direction;
|
|
11206
12008
|
}
|
|
11207
12009
|
/** Generates the Infiltration mission task symbol (343800). */
|
|
11208
|
-
function createInfiltration(coordinates, options = {}) {
|
|
12010
|
+
function createInfiltration(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
11209
12011
|
const [c1, c2, c3] = coordinates;
|
|
11210
12012
|
const p1 = project(c1[0], c1[1]);
|
|
11211
12013
|
const p2 = project(c2[0], c2[1]);
|
|
@@ -11238,8 +12040,10 @@ function createInfiltration(coordinates, options = {}) {
|
|
|
11238
12040
|
const arcResolution = normalizeArcResolution(options.resolution) / 2;
|
|
11239
12041
|
const firstArc = sampleProjectedArc(firstCenter, sideRadius, alongRadius, 0, Math.PI / 2, arcResolution);
|
|
11240
12042
|
const secondArc = sampleProjectedArc(secondCenter, vecScale(alongRadius, -1), vecScale(sideRadius, -1), 0, Math.PI / 2, arcResolution);
|
|
11241
|
-
const
|
|
11242
|
-
const
|
|
12043
|
+
const labelPosition = clamp01(options.labelPosition ?? DEFAULT_INFILTRATION_OPTIONS.labelPosition);
|
|
12044
|
+
const labelPoint = vecAdd(p1, vecScale(straight, labelPosition));
|
|
12045
|
+
const labelRotation = labelRotationAlong(along);
|
|
12046
|
+
const gapHalf = Math.min(straightLength * Math.min(labelPosition, 1 - labelPosition), labelKnockoutHalfExtent("IN", along, labelRotation, options, context, DEFAULT_INFILTRATION_OPTIONS.labelPadding));
|
|
11243
12047
|
const gapStart = vecAdd(labelPoint, vecScale(along, -gapHalf));
|
|
11244
12048
|
const gapEnd = vecAdd(labelPoint, vecScale(along, gapHalf));
|
|
11245
12049
|
const arrowhead = createArrowHead$1(p3, along, spanLength * Math.max(0, options.arrowheadLengthRatio ?? DEFAULT_INFILTRATION_OPTIONS.arrowheadLengthRatio), spanLength * Math.max(0, options.arrowheadWidthRatio ?? DEFAULT_INFILTRATION_OPTIONS.arrowheadWidthRatio));
|
|
@@ -11259,7 +12063,7 @@ function createInfiltration(coordinates, options = {}) {
|
|
|
11259
12063
|
arrowhead
|
|
11260
12064
|
]
|
|
11261
12065
|
}
|
|
11262
|
-
}, createLabelFeature(labelPoint, "IN",
|
|
12066
|
+
}, createLabelFeature(labelPoint, "IN", labelRotation, options)]
|
|
11263
12067
|
};
|
|
11264
12068
|
}
|
|
11265
12069
|
const INFILTRATION = defineControlMeasure({
|
|
@@ -11282,7 +12086,7 @@ const DEFAULT_GUARD_OPTIONS = DEFAULT_SECURITY_TASK_OPTIONS;
|
|
|
11282
12086
|
const GUARD_METADATA = {
|
|
11283
12087
|
id: "guard",
|
|
11284
12088
|
name: "Guard",
|
|
11285
|
-
description: "Protects the main body by fighting to gain time
|
|
12089
|
+
description: "Protects the main body by fighting to gain time and preventing enemy ground observation and direct fire. Operates within range of the main body’s supporting fires.",
|
|
11286
12090
|
entity: "Mission Tasks",
|
|
11287
12091
|
entityType: "Security",
|
|
11288
12092
|
entitySubtype: "Guard",
|
|
@@ -11546,6 +12350,7 @@ const DROP_ZONE_METADATA = {
|
|
|
11546
12350
|
},
|
|
11547
12351
|
drawRule: "Area1",
|
|
11548
12352
|
capturesLabelSize: true,
|
|
12353
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
11549
12354
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
11550
12355
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
11551
12356
|
};
|
|
@@ -11610,6 +12415,7 @@ const ENCIRCLEMENT_METADATA = {
|
|
|
11610
12415
|
},
|
|
11611
12416
|
drawRule: "Area1",
|
|
11612
12417
|
capturesLabelSize: true,
|
|
12418
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
11613
12419
|
params: ENCIRCLEMENT_PARAMS,
|
|
11614
12420
|
textAmplifiers: [{
|
|
11615
12421
|
key: "N",
|
|
@@ -11763,6 +12569,7 @@ const EXTRACTION_ZONE_METADATA = {
|
|
|
11763
12569
|
},
|
|
11764
12570
|
drawRule: "Area1",
|
|
11765
12571
|
capturesLabelSize: true,
|
|
12572
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
11766
12573
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
11767
12574
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
11768
12575
|
};
|
|
@@ -12127,7 +12934,7 @@ const DEFAULT_FIX_MISSION_TASK_OPTIONS = {
|
|
|
12127
12934
|
zigzagEndRatio: .8,
|
|
12128
12935
|
zigzagPeakCount: 5,
|
|
12129
12936
|
labelPositionRatio: .09,
|
|
12130
|
-
|
|
12937
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING
|
|
12131
12938
|
};
|
|
12132
12939
|
const FIX_MISSION_TASK_METADATA = {
|
|
12133
12940
|
id: "fix-mission-task",
|
|
@@ -12147,6 +12954,7 @@ const FIX_MISSION_TASK_METADATA = {
|
|
|
12147
12954
|
},
|
|
12148
12955
|
drawRule: "Line9",
|
|
12149
12956
|
capturesLabelSize: true,
|
|
12957
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
12150
12958
|
params: [
|
|
12151
12959
|
{
|
|
12152
12960
|
key: "arrowheadLengthRatio",
|
|
@@ -12208,20 +13016,11 @@ const FIX_MISSION_TASK_METADATA = {
|
|
|
12208
13016
|
max: 1,
|
|
12209
13017
|
step: .01
|
|
12210
13018
|
},
|
|
12211
|
-
|
|
12212
|
-
key: "labelGapRatio",
|
|
12213
|
-
presentationTier: "advanced",
|
|
12214
|
-
label: "Label gap",
|
|
12215
|
-
description: "Size of the shaft gap holding the F label",
|
|
12216
|
-
type: "number",
|
|
12217
|
-
min: 0,
|
|
12218
|
-
max: 1,
|
|
12219
|
-
step: .01
|
|
12220
|
-
}
|
|
13019
|
+
LABEL_PADDING_PARAM
|
|
12221
13020
|
]
|
|
12222
13021
|
};
|
|
12223
13022
|
/** Generates the Fix mission task symbol (341100). */
|
|
12224
|
-
function createFixMissionTaskSymbol(coordinates, options = {}) {
|
|
13023
|
+
function createFixMissionTaskSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
12225
13024
|
const c1 = coordinates[0];
|
|
12226
13025
|
const c2 = coordinates[1];
|
|
12227
13026
|
const tip = project(c1[0], c1[1]);
|
|
@@ -12254,12 +13053,12 @@ function createFixMissionTaskSymbol(coordinates, options = {}) {
|
|
|
12254
13053
|
const exitRatio = Math.min(1, zigzagEnd + toothStep / 2);
|
|
12255
13054
|
const zigzagEntry = vecAdd(tail, vecScale(along, axisLength * entryRatio));
|
|
12256
13055
|
const zigzagExit = vecAdd(tail, vecScale(along, axisLength * exitRatio));
|
|
12257
|
-
const
|
|
12258
|
-
const labelGap = axisLength * clamp01(options.labelGapRatio ?? DEFAULT_FIX_MISSION_TASK_OPTIONS.labelGapRatio);
|
|
12259
|
-
const labelDistance = axisLength * labelPosition;
|
|
13056
|
+
const labelDistance = axisLength * clamp01(options.labelPositionRatio ?? DEFAULT_FIX_MISSION_TASK_OPTIONS.labelPositionRatio);
|
|
12260
13057
|
const labelCenter = vecAdd(tail, vecScale(along, labelDistance));
|
|
12261
|
-
const
|
|
12262
|
-
const
|
|
13058
|
+
const labelRotation = labelRotationAlong(along);
|
|
13059
|
+
const gapHalf = labelKnockoutHalfExtent("F", along, labelRotation, options, context, DEFAULT_FIX_MISSION_TASK_OPTIONS.labelPadding);
|
|
13060
|
+
const gapStartDistance = Math.max(0, labelDistance - gapHalf);
|
|
13061
|
+
const gapEndDistance = Math.min(axisLength, labelDistance + gapHalf);
|
|
12263
13062
|
const gapStart = vecAdd(tail, vecScale(along, gapStartDistance));
|
|
12264
13063
|
const gapEnd = vecAdd(tail, vecScale(along, gapEndDistance));
|
|
12265
13064
|
return {
|
|
@@ -12281,7 +13080,7 @@ function createFixMissionTaskSymbol(coordinates, options = {}) {
|
|
|
12281
13080
|
createArrowHead$1(tip, along, arrowheadLength, arrowheadWidth)
|
|
12282
13081
|
]
|
|
12283
13082
|
}
|
|
12284
|
-
}, createLabelFeature(labelCenter, "F",
|
|
13083
|
+
}, createLabelFeature(labelCenter, "F", labelRotation, options)]
|
|
12285
13084
|
};
|
|
12286
13085
|
}
|
|
12287
13086
|
const FIX_MISSION_TASK = defineControlMeasure({
|
|
@@ -12326,6 +13125,7 @@ const FLOT_METADATA = {
|
|
|
12326
13125
|
},
|
|
12327
13126
|
drawRule: "Line1",
|
|
12328
13127
|
capturesLabelSize: true,
|
|
13128
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
12329
13129
|
params: [
|
|
12330
13130
|
...SMOOTH_LINE_PARAMS,
|
|
12331
13131
|
{
|
|
@@ -12348,7 +13148,7 @@ const FLOT_METADATA = {
|
|
|
12348
13148
|
step: 1,
|
|
12349
13149
|
unit: "m"
|
|
12350
13150
|
},
|
|
12351
|
-
|
|
13151
|
+
LABEL_PADDING_PARAM,
|
|
12352
13152
|
{
|
|
12353
13153
|
key: "arcSegments",
|
|
12354
13154
|
presentationTier: "advanced",
|
|
@@ -12670,7 +13470,7 @@ function phaseLineNameParams(placeholder) {
|
|
|
12670
13470
|
type: "boolean",
|
|
12671
13471
|
visibleWhen: (opts) => Boolean(String(opts.phaseLineName ?? "").trim())
|
|
12672
13472
|
},
|
|
12673
|
-
|
|
13473
|
+
LABEL_PADDING_PARAM
|
|
12674
13474
|
];
|
|
12675
13475
|
}
|
|
12676
13476
|
//#endregion
|
|
@@ -12855,6 +13655,7 @@ const AIRHEAD_LINE_METADATA = {
|
|
|
12855
13655
|
},
|
|
12856
13656
|
drawRule: "Area1",
|
|
12857
13657
|
capturesLabelSize: true,
|
|
13658
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
12858
13659
|
params: LABELED_AREA_PARAMS,
|
|
12859
13660
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
12860
13661
|
};
|
|
@@ -13508,6 +14309,7 @@ const FORTIFIED_LINE_METADATA = {
|
|
|
13508
14309
|
},
|
|
13509
14310
|
drawRule: "Line1",
|
|
13510
14311
|
capturesLabelSize: true,
|
|
14312
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
13511
14313
|
params: FORTIFIED_PARAMS,
|
|
13512
14314
|
textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
|
|
13513
14315
|
};
|
|
@@ -13668,6 +14470,7 @@ const FORTIFIED_AREA_METADATA = {
|
|
|
13668
14470
|
},
|
|
13669
14471
|
drawRule: "Area1",
|
|
13670
14472
|
capturesLabelSize: true,
|
|
14473
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
13671
14474
|
params: [...FORTIFIED_AREA_PARAMS, AREA_LABEL_PADDING_PARAM],
|
|
13672
14475
|
textAmplifiers: AREA_TEXT_AMPLIFIERS
|
|
13673
14476
|
};
|
|
@@ -13782,195 +14585,55 @@ const FRONTAL_ATTACK = defineControlMeasure({
|
|
|
13782
14585
|
[-.4, -.35],
|
|
13783
14586
|
[-1, -.8],
|
|
13784
14587
|
[.45, .55]
|
|
13785
|
-
],
|
|
13786
|
-
textAmplifiers: {
|
|
13787
|
-
T: "FOX",
|
|
13788
|
-
W: "080400Z",
|
|
13789
|
-
W1: "120300Z"
|
|
13790
|
-
},
|
|
13791
|
-
options: { labelSize: 80 }
|
|
13792
|
-
}
|
|
13793
|
-
});
|
|
13794
|
-
//#endregion
|
|
13795
|
-
//#region src/generators/cm34-mission-tasks/forward-passage-of-lines.ts
|
|
13796
|
-
const DEFAULT_FORWARD_PASSAGE_OF_LINES_OPTIONS = DEFAULT_DELAY_OPTIONS;
|
|
13797
|
-
const FORWARD_PASSAGE_OF_LINES_METADATA = {
|
|
13798
|
-
id: "forward-passage-of-lines",
|
|
13799
|
-
name: "Forward Passage of Lines",
|
|
13800
|
-
description: "Passes one force through another force's positions toward the enemy.",
|
|
13801
|
-
entity: "Mission Tasks",
|
|
13802
|
-
entityType: "Forward Passage of Lines",
|
|
13803
|
-
value: "344100",
|
|
13804
|
-
minCoordinates: 3,
|
|
13805
|
-
maxCoordinates: 3,
|
|
13806
|
-
geometry: "line",
|
|
13807
|
-
geometryTypes: ["MultiLineString", "Point"],
|
|
13808
|
-
paints: {
|
|
13809
|
-
stroke: true,
|
|
13810
|
-
fill: "none",
|
|
13811
|
-
text: true
|
|
13812
|
-
},
|
|
13813
|
-
drawRule: "Line24",
|
|
13814
|
-
capturesLabelSize: true,
|
|
13815
|
-
params: DELAY_PARAMS
|
|
13816
|
-
};
|
|
13817
|
-
function createForwardPassageOfLinesSymbol(coordinates, options = {}) {
|
|
13818
|
-
return createDelayVariantSymbol(coordinates, {
|
|
13819
|
-
glyph: "P(F)",
|
|
13820
|
-
part: "forward-passage-of-lines"
|
|
13821
|
-
}, options);
|
|
13822
|
-
}
|
|
13823
|
-
const FORWARD_PASSAGE_OF_LINES = defineControlMeasure({
|
|
13824
|
-
metadata: FORWARD_PASSAGE_OF_LINES_METADATA,
|
|
13825
|
-
generator: createForwardPassageOfLinesSymbol,
|
|
13826
|
-
defaultOptions: DEFAULT_FORWARD_PASSAGE_OF_LINES_OPTIONS,
|
|
13827
|
-
rule: line24DrawRule,
|
|
13828
|
-
previewSample: {
|
|
13829
|
-
controlPoints: [
|
|
13830
|
-
[1, -.4],
|
|
13831
|
-
[-1, -.4],
|
|
13832
|
-
[0, .6]
|
|
13833
|
-
],
|
|
13834
|
-
options: { labelGapRatio: .42 }
|
|
13835
|
-
}
|
|
13836
|
-
});
|
|
13837
|
-
//#endregion
|
|
13838
|
-
//#region src/generators/cm34-mission-tasks/isolate.ts
|
|
13839
|
-
/**
|
|
13840
|
-
* Default options for the ISOLATE symbol.
|
|
13841
|
-
*/
|
|
13842
|
-
const DEFAULT_ISOLATE_OPTIONS = {
|
|
13843
|
-
openingAngle: 30,
|
|
13844
|
-
barbCount: 9,
|
|
13845
|
-
barbLengthRatio: .18
|
|
13846
|
-
};
|
|
13847
|
-
const ISOLATE_METADATA = {
|
|
13848
|
-
id: "isolate",
|
|
13849
|
-
name: "Isolate",
|
|
13850
|
-
description: "Seals an enemy off from support, denies freedom of movement, and prevents contact with other enemy forces while offensive action continues against it.",
|
|
13851
|
-
entity: "Mission Tasks",
|
|
13852
|
-
entityType: "Isolate",
|
|
13853
|
-
value: "341500",
|
|
13854
|
-
minCoordinates: 2,
|
|
13855
|
-
maxCoordinates: 2,
|
|
13856
|
-
geometry: "line",
|
|
13857
|
-
geometryTypes: ["MultiLineString"],
|
|
13858
|
-
paints: {
|
|
13859
|
-
stroke: true,
|
|
13860
|
-
fill: "none",
|
|
13861
|
-
text: false
|
|
13862
|
-
},
|
|
13863
|
-
drawRule: "Area15",
|
|
13864
|
-
params: [
|
|
13865
|
-
{
|
|
13866
|
-
key: "openingAngle",
|
|
13867
|
-
presentationTier: "advanced",
|
|
13868
|
-
label: "Opening",
|
|
13869
|
-
description: "Angular width of the gap on the friendly side, in degrees",
|
|
13870
|
-
type: "number",
|
|
13871
|
-
min: 0,
|
|
13872
|
-
max: 180,
|
|
13873
|
-
step: 1
|
|
13874
|
-
},
|
|
13875
|
-
{
|
|
13876
|
-
key: "barbCount",
|
|
13877
|
-
presentationTier: "advanced",
|
|
13878
|
-
label: "Barbs",
|
|
13879
|
-
description: "Number of inward-pointing barbs spaced along the closed arc",
|
|
13880
|
-
type: "number",
|
|
13881
|
-
min: 1,
|
|
13882
|
-
max: 24,
|
|
13883
|
-
step: 1
|
|
13884
|
-
},
|
|
13885
|
-
{
|
|
13886
|
-
key: "barbLengthRatio",
|
|
13887
|
-
presentationTier: "advanced",
|
|
13888
|
-
label: "Barb length",
|
|
13889
|
-
description: "Length of each inward barb, as a ratio of the symbol radius",
|
|
13890
|
-
type: "number",
|
|
13891
|
-
min: .01,
|
|
13892
|
-
max: 1,
|
|
13893
|
-
step: .01
|
|
13894
|
-
}
|
|
13895
|
-
]
|
|
13896
|
-
};
|
|
13897
|
-
/**
|
|
13898
|
-
* Generates a GeoJSON FeatureCollection for an ISOLATE mission task symbol (341500).
|
|
13899
|
-
*
|
|
13900
|
-
* A near-complete circle centered on PT. 1 whose radius reaches PT. 2. The arc
|
|
13901
|
-
* starts (bare) at PT. 2's bearing and sweeps clockwise around the closed
|
|
13902
|
-
* portion to a single arrow tip at the far end; the wedge-shaped gap (default
|
|
13903
|
-
* 30°) between that arrow tip and PT. 2 is the opening — the "friendly side".
|
|
13904
|
-
* Inward-pointing arrowhead barbs are spaced along the closed arc, set in from
|
|
13905
|
-
* both ends so they never touch PT. 2 or the arrow tip, conveying the
|
|
13906
|
-
* sealing-off of the enclosed force. Unlike the bracket mission tasks, isolate
|
|
13907
|
-
* carries no glyph label.
|
|
13908
|
-
*
|
|
13909
|
-
* Input Points:
|
|
13910
|
-
* - PT. 1: Center point
|
|
13911
|
-
* - PT. 2: Start point — fixes the radius and the bearing of the opening
|
|
13912
|
-
*
|
|
13913
|
-
* @param coordinates - [PT. 1, PT. 2] as Position arrays
|
|
13914
|
-
* @param options - Configuration options
|
|
13915
|
-
* @returns GeoJSON FeatureCollection containing a single MultiLineString feature
|
|
13916
|
-
*/
|
|
13917
|
-
function createIsolateSymbol(coordinates, options = {}) {
|
|
13918
|
-
const openingAngle = options.openingAngle ?? DEFAULT_ISOLATE_OPTIONS.openingAngle;
|
|
13919
|
-
const barbCount = options.barbCount ?? DEFAULT_ISOLATE_OPTIONS.barbCount;
|
|
13920
|
-
const barbLengthRatio = options.barbLengthRatio ?? DEFAULT_ISOLATE_OPTIONS.barbLengthRatio;
|
|
13921
|
-
const center = project(coordinates[0][0], coordinates[0][1]);
|
|
13922
|
-
const vRadius = vecSub(project(coordinates[1][0], coordinates[1][1]), center);
|
|
13923
|
-
const radius = vecMag(vRadius);
|
|
13924
|
-
if (radius < 1e-6) return {
|
|
13925
|
-
type: "FeatureCollection",
|
|
13926
|
-
features: []
|
|
13927
|
-
};
|
|
13928
|
-
const startAngle = Math.atan2(vRadius[1], vRadius[0]);
|
|
13929
|
-
const openingRad = Math.min(Math.max(openingAngle, 0), 359) * Math.PI / 180;
|
|
13930
|
-
const arcSpan = 2 * Math.PI - openingRad;
|
|
13931
|
-
const endAngle = startAngle - arcSpan;
|
|
13932
|
-
const pointOnCircle = (angle, r = radius) => vecAdd(center, [r * Math.cos(angle), r * Math.sin(angle)]);
|
|
13933
|
-
const segments = Math.max(8, Math.round(64 * arcSpan / (2 * Math.PI)));
|
|
13934
|
-
const arc = [];
|
|
13935
|
-
for (let i = 0; i <= segments; i++) {
|
|
13936
|
-
const p = pointOnCircle(startAngle - arcSpan * i / segments);
|
|
13937
|
-
arc.push(unproject(p[0], p[1]));
|
|
13938
|
-
}
|
|
13939
|
-
const barbLength = radius * barbLengthRatio;
|
|
13940
|
-
const lines = [arc];
|
|
13941
|
-
const tipDir = [Math.sin(endAngle), -Math.cos(endAngle)];
|
|
13942
|
-
lines.push(createArrowHead$1(pointOnCircle(endAngle), tipDir, barbLength, barbLength));
|
|
13943
|
-
const halfWidth = Math.asin(Math.min(1, barbLength / 2 / radius));
|
|
13944
|
-
const tipRadius = Math.max(0, radius - barbLength);
|
|
13945
|
-
const steps = Math.max(1, barbCount);
|
|
13946
|
-
for (let k = 1; k <= steps; k++) {
|
|
13947
|
-
const angle = startAngle - arcSpan * k / (steps + 1);
|
|
13948
|
-
const left = pointOnCircle(angle - halfWidth);
|
|
13949
|
-
const right = pointOnCircle(angle + halfWidth);
|
|
13950
|
-
const tip = pointOnCircle(angle, tipRadius);
|
|
13951
|
-
lines.push([
|
|
13952
|
-
unproject(left[0], left[1]),
|
|
13953
|
-
unproject(tip[0], tip[1]),
|
|
13954
|
-
unproject(right[0], right[1])
|
|
13955
|
-
]);
|
|
14588
|
+
],
|
|
14589
|
+
textAmplifiers: {
|
|
14590
|
+
T: "FOX",
|
|
14591
|
+
W: "080400Z",
|
|
14592
|
+
W1: "120300Z"
|
|
14593
|
+
},
|
|
14594
|
+
options: { labelSize: 80 }
|
|
13956
14595
|
}
|
|
13957
|
-
|
|
13958
|
-
|
|
13959
|
-
|
|
13960
|
-
|
|
13961
|
-
|
|
13962
|
-
|
|
13963
|
-
|
|
13964
|
-
|
|
13965
|
-
|
|
13966
|
-
|
|
13967
|
-
|
|
14596
|
+
});
|
|
14597
|
+
//#endregion
|
|
14598
|
+
//#region src/generators/cm34-mission-tasks/forward-passage-of-lines.ts
|
|
14599
|
+
const DEFAULT_FORWARD_PASSAGE_OF_LINES_OPTIONS = DEFAULT_DELAY_OPTIONS;
|
|
14600
|
+
const FORWARD_PASSAGE_OF_LINES_METADATA = {
|
|
14601
|
+
id: "forward-passage-of-lines",
|
|
14602
|
+
name: "Forward Passage of Lines",
|
|
14603
|
+
description: "Passes one force through another force's positions toward the enemy.",
|
|
14604
|
+
entity: "Mission Tasks",
|
|
14605
|
+
entityType: "Forward Passage of Lines",
|
|
14606
|
+
value: "344100",
|
|
14607
|
+
minCoordinates: 3,
|
|
14608
|
+
maxCoordinates: 3,
|
|
14609
|
+
geometry: "line",
|
|
14610
|
+
geometryTypes: ["MultiLineString", "Point"],
|
|
14611
|
+
paints: {
|
|
14612
|
+
stroke: true,
|
|
14613
|
+
fill: "none",
|
|
14614
|
+
text: true
|
|
14615
|
+
},
|
|
14616
|
+
drawRule: "Line24",
|
|
14617
|
+
capturesLabelSize: true,
|
|
14618
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
14619
|
+
params: DELAY_PARAMS
|
|
14620
|
+
};
|
|
14621
|
+
function createForwardPassageOfLinesSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
14622
|
+
return createDelayVariantSymbol(coordinates, {
|
|
14623
|
+
glyph: "P(F)",
|
|
14624
|
+
part: "forward-passage-of-lines"
|
|
14625
|
+
}, options, context);
|
|
13968
14626
|
}
|
|
13969
|
-
const
|
|
13970
|
-
metadata:
|
|
13971
|
-
generator:
|
|
13972
|
-
defaultOptions:
|
|
13973
|
-
rule:
|
|
14627
|
+
const FORWARD_PASSAGE_OF_LINES = defineControlMeasure({
|
|
14628
|
+
metadata: FORWARD_PASSAGE_OF_LINES_METADATA,
|
|
14629
|
+
generator: createForwardPassageOfLinesSymbol,
|
|
14630
|
+
defaultOptions: DEFAULT_FORWARD_PASSAGE_OF_LINES_OPTIONS,
|
|
14631
|
+
rule: line24DrawRule,
|
|
14632
|
+
previewSample: { controlPoints: [
|
|
14633
|
+
[1, -.4],
|
|
14634
|
+
[-1, -.4],
|
|
14635
|
+
[0, .6]
|
|
14636
|
+
] }
|
|
13974
14637
|
});
|
|
13975
14638
|
//#endregion
|
|
13976
14639
|
//#region src/generators/cm34-mission-tasks/movement-to-contact.ts
|
|
@@ -14179,7 +14842,7 @@ const DEFAULT_OCCUPY_OPTIONS = {
|
|
|
14179
14842
|
const OCCUPY_METADATA = {
|
|
14180
14843
|
id: "occupy",
|
|
14181
14844
|
name: "Occupy",
|
|
14182
|
-
description: "Moves
|
|
14845
|
+
description: "Moves into and controls an area without enemy opposition.",
|
|
14183
14846
|
entity: "Mission Tasks",
|
|
14184
14847
|
entityType: "Occupy",
|
|
14185
14848
|
value: "341700",
|
|
@@ -14194,6 +14857,7 @@ const OCCUPY_METADATA = {
|
|
|
14194
14857
|
},
|
|
14195
14858
|
drawRule: "Area16",
|
|
14196
14859
|
capturesLabelSize: true,
|
|
14860
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
14197
14861
|
params: [
|
|
14198
14862
|
{
|
|
14199
14863
|
key: "openingAngle",
|
|
@@ -14238,10 +14902,10 @@ const OCCUPY_METADATA = {
|
|
|
14238
14902
|
}
|
|
14239
14903
|
]
|
|
14240
14904
|
};
|
|
14241
|
-
const TAU$
|
|
14242
|
-
const MIN_LABEL_GAP_METERS$
|
|
14905
|
+
const TAU$2 = Math.PI * 2;
|
|
14906
|
+
const MIN_LABEL_GAP_METERS$2 = 1;
|
|
14243
14907
|
/** Generates the Occupy mission task symbol (341700). */
|
|
14244
|
-
function createOccupySymbol(coordinates, options = {}) {
|
|
14908
|
+
function createOccupySymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
14245
14909
|
const center = project(coordinates[0][0], coordinates[0][1]);
|
|
14246
14910
|
const radiusVector = vecSub(project(coordinates[1][0], coordinates[1][1]), center);
|
|
14247
14911
|
const radius = vecMag(radiusVector);
|
|
@@ -14255,11 +14919,11 @@ function createOccupySymbol(coordinates, options = {}) {
|
|
|
14255
14919
|
};
|
|
14256
14920
|
const bulgeVector = [radiusVector[1], -radiusVector[0]];
|
|
14257
14921
|
const halfTurnResolution = normalizeArcResolution(resolved.resolution) / 2;
|
|
14258
|
-
const boundarySpan = TAU$
|
|
14922
|
+
const boundarySpan = TAU$2 - Math.min(180, Math.max(5, resolved.openingAngle)) * Math.PI / 180;
|
|
14259
14923
|
const labelSweep = boundarySpan / 2;
|
|
14260
|
-
const labelHeight =
|
|
14924
|
+
const labelHeight = resolveTextMetrics("O", options, context, "regular", radius * .12).height;
|
|
14261
14925
|
const labelPadding = Math.max(0, resolved.labelPadding);
|
|
14262
|
-
const labelHalfGap = Math.min(boundarySpan / 4, Math.max(labelHeight * (LABEL_HALF_GLYPH_RATIO + labelPadding), MIN_LABEL_GAP_METERS$
|
|
14926
|
+
const labelHalfGap = Math.min(boundarySpan / 4, Math.max(labelHeight * (LABEL_HALF_GLYPH_RATIO + labelPadding), MIN_LABEL_GAP_METERS$2) / radius);
|
|
14263
14927
|
const sampleArc = (from, to) => sampleProjectedArc(center, radiusVector, bulgeVector, from, to, halfTurnResolution);
|
|
14264
14928
|
const lines = [sampleArc(0, labelSweep - labelHalfGap), sampleArc(labelSweep + labelHalfGap, boundarySpan)];
|
|
14265
14929
|
const terminal = arcPointAt(center, radiusVector, bulgeVector, boundarySpan);
|
|
@@ -14298,12 +14962,12 @@ const OCCUPY = defineControlMeasure({
|
|
|
14298
14962
|
const DEFAULT_PENETRATE_OPTIONS = {
|
|
14299
14963
|
arrowheadLengthRatio: .15,
|
|
14300
14964
|
arrowheadWidthRatio: .2,
|
|
14301
|
-
|
|
14965
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING
|
|
14302
14966
|
};
|
|
14303
14967
|
const PENETRATE_METADATA = {
|
|
14304
14968
|
id: "penetrate",
|
|
14305
14969
|
name: "Penetrate",
|
|
14306
|
-
description: "
|
|
14970
|
+
description: "Attacks on a narrow front to break the continuity of an enemy defense and enable the isolation and defeat of enemy forces in detail.",
|
|
14307
14971
|
entity: "Mission Tasks",
|
|
14308
14972
|
entityType: "Penetrate",
|
|
14309
14973
|
value: "341800",
|
|
@@ -14318,6 +14982,7 @@ const PENETRATE_METADATA = {
|
|
|
14318
14982
|
},
|
|
14319
14983
|
drawRule: "Area17",
|
|
14320
14984
|
capturesLabelSize: true,
|
|
14985
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
14321
14986
|
params: [
|
|
14322
14987
|
{
|
|
14323
14988
|
key: "arrowheadLengthRatio",
|
|
@@ -14339,20 +15004,11 @@ const PENETRATE_METADATA = {
|
|
|
14339
15004
|
max: 1,
|
|
14340
15005
|
step: .01
|
|
14341
15006
|
},
|
|
14342
|
-
|
|
14343
|
-
key: "labelGapRatio",
|
|
14344
|
-
presentationTier: "advanced",
|
|
14345
|
-
label: "Label gap",
|
|
14346
|
-
description: "Size of the shaft gap holding the 'P' label",
|
|
14347
|
-
type: "number",
|
|
14348
|
-
min: 0,
|
|
14349
|
-
max: 1,
|
|
14350
|
-
step: .01
|
|
14351
|
-
}
|
|
15007
|
+
LABEL_PADDING_PARAM
|
|
14352
15008
|
]
|
|
14353
15009
|
};
|
|
14354
15010
|
/** Generates the PENETRATE mission task symbol (341800). */
|
|
14355
|
-
function createPenetrateSymbol(coordinates, options = {}) {
|
|
15011
|
+
function createPenetrateSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
14356
15012
|
const frame = createProjectedBaselineFrame(project(coordinates[0][0], coordinates[0][1]), project(coordinates[1][0], coordinates[1][1]), {
|
|
14357
15013
|
origin: "midpoint",
|
|
14358
15014
|
normal: "right"
|
|
@@ -14372,29 +15028,29 @@ function createPenetrateSymbol(coordinates, options = {}) {
|
|
|
14372
15028
|
const tail = vecAdd(frame.origin, vecScale(frame.normal, signedTailDistance));
|
|
14373
15029
|
const arrowDirection = vecScale(vecSub(tip, tail), 1 / shaftLength);
|
|
14374
15030
|
const labelPoint = vecScale(vecAdd(tail, tip), .5);
|
|
14375
|
-
const
|
|
15031
|
+
const shaftRotation = Math.atan2(arrowDirection[1], arrowDirection[0]);
|
|
15032
|
+
const renderedLabelRotation = Math.PI - shaftRotation;
|
|
15033
|
+
const labelRotation = normalizeRadians(Math.PI - keepTextLeftToRight(renderedLabelRotation));
|
|
15034
|
+
const gapHalf = Math.min(shaftLength / 2, labelKnockoutHalfExtent("P", arrowDirection, labelRotation, options, context, DEFAULT_PENETRATE_OPTIONS.labelPadding));
|
|
14376
15035
|
const gapStart = vecAdd(labelPoint, vecScale(arrowDirection, -gapHalf));
|
|
14377
15036
|
const gapEnd = vecAdd(labelPoint, vecScale(arrowDirection, gapHalf));
|
|
14378
15037
|
const headLength = frame.length * Math.max(0, options.arrowheadLengthRatio ?? DEFAULT_PENETRATE_OPTIONS.arrowheadLengthRatio);
|
|
14379
15038
|
const headWidth = frame.length * Math.max(0, options.arrowheadWidthRatio ?? DEFAULT_PENETRATE_OPTIONS.arrowheadWidthRatio);
|
|
14380
|
-
const lineFeature = {
|
|
14381
|
-
type: "Feature",
|
|
14382
|
-
properties: { part: "penetrate" },
|
|
14383
|
-
geometry: {
|
|
14384
|
-
type: "MultiLineString",
|
|
14385
|
-
coordinates: [
|
|
14386
|
-
[coordinates[0], coordinates[1]],
|
|
14387
|
-
[unproject(tail[0], tail[1]), unproject(gapStart[0], gapStart[1])],
|
|
14388
|
-
[unproject(gapEnd[0], gapEnd[1]), unproject(tip[0], tip[1])],
|
|
14389
|
-
createArrowHead$1(tip, arrowDirection, headLength, headWidth)
|
|
14390
|
-
]
|
|
14391
|
-
}
|
|
14392
|
-
};
|
|
14393
|
-
const shaftRotation = Math.atan2(arrowDirection[1], arrowDirection[0]);
|
|
14394
|
-
const renderedLabelRotation = Math.PI - shaftRotation;
|
|
14395
15039
|
return {
|
|
14396
15040
|
type: "FeatureCollection",
|
|
14397
|
-
features: [
|
|
15041
|
+
features: [{
|
|
15042
|
+
type: "Feature",
|
|
15043
|
+
properties: { part: "penetrate" },
|
|
15044
|
+
geometry: {
|
|
15045
|
+
type: "MultiLineString",
|
|
15046
|
+
coordinates: [
|
|
15047
|
+
[coordinates[0], coordinates[1]],
|
|
15048
|
+
[unproject(tail[0], tail[1]), unproject(gapStart[0], gapStart[1])],
|
|
15049
|
+
[unproject(gapEnd[0], gapEnd[1]), unproject(tip[0], tip[1])],
|
|
15050
|
+
createArrowHead$1(tip, arrowDirection, headLength, headWidth)
|
|
15051
|
+
]
|
|
15052
|
+
}
|
|
15053
|
+
}, createLabelFeature(labelPoint, "P", labelRotation, {
|
|
14398
15054
|
labelSize: options.labelSize,
|
|
14399
15055
|
labelSizePixels: options.labelSizePixels
|
|
14400
15056
|
})]
|
|
@@ -14418,13 +15074,13 @@ const DEFAULT_PURSUIT_OPTIONS = {
|
|
|
14418
15074
|
arrowheadWidthRatio: .12,
|
|
14419
15075
|
terminalBarLengthRatio: 1.5,
|
|
14420
15076
|
labelPosition: .6,
|
|
14421
|
-
|
|
15077
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING,
|
|
14422
15078
|
resolution: 64
|
|
14423
15079
|
};
|
|
14424
15080
|
const PURSUIT_METADATA = {
|
|
14425
15081
|
id: "pursuit",
|
|
14426
15082
|
name: "Pursuit",
|
|
14427
|
-
description: "
|
|
15083
|
+
description: "Catches or cuts off a disorganized enemy force attempting to escape, with the aim of destroying it.",
|
|
14428
15084
|
entity: "Mission Tasks",
|
|
14429
15085
|
entityType: "Pursuit",
|
|
14430
15086
|
value: "344000",
|
|
@@ -14439,6 +15095,7 @@ const PURSUIT_METADATA = {
|
|
|
14439
15095
|
},
|
|
14440
15096
|
drawRule: "Line33",
|
|
14441
15097
|
capturesLabelSize: true,
|
|
15098
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
14442
15099
|
params: [
|
|
14443
15100
|
{
|
|
14444
15101
|
key: "arrowheadLengthRatio",
|
|
@@ -14480,21 +15137,12 @@ const PURSUIT_METADATA = {
|
|
|
14480
15137
|
max: 1,
|
|
14481
15138
|
step: .01
|
|
14482
15139
|
},
|
|
14483
|
-
|
|
14484
|
-
key: "labelGapRatio",
|
|
14485
|
-
presentationTier: "advanced",
|
|
14486
|
-
label: "Label gap",
|
|
14487
|
-
description: "Size of the straight-line cutout holding the P label",
|
|
14488
|
-
type: "number",
|
|
14489
|
-
min: 0,
|
|
14490
|
-
max: 1,
|
|
14491
|
-
step: .01
|
|
14492
|
-
},
|
|
15140
|
+
LABEL_PADDING_PARAM,
|
|
14493
15141
|
...ARC_RESOLUTION_PARAMS
|
|
14494
15142
|
]
|
|
14495
15143
|
};
|
|
14496
15144
|
/** Generates the Pursuit mission task symbol (344000). */
|
|
14497
|
-
function createPursuit(coordinates, options = {}) {
|
|
15145
|
+
function createPursuit(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
14498
15146
|
const [c1, c2, c3] = coordinates;
|
|
14499
15147
|
const p1 = project(c1[0], c1[1]);
|
|
14500
15148
|
const p2 = project(c2[0], c2[1]);
|
|
@@ -14515,8 +15163,10 @@ function createPursuit(coordinates, options = {}) {
|
|
|
14515
15163
|
features: []
|
|
14516
15164
|
};
|
|
14517
15165
|
const p3 = vecAdd(p2, vecScale(diameterAxis, signedDiameter));
|
|
14518
|
-
const
|
|
14519
|
-
const
|
|
15166
|
+
const labelPosition = clamp01(options.labelPosition ?? DEFAULT_PURSUIT_OPTIONS.labelPosition);
|
|
15167
|
+
const labelPoint = vecAdd(p1, vecScale(straightDirection, straightLength * labelPosition));
|
|
15168
|
+
const labelRotation = labelRotationAlong(straightDirection);
|
|
15169
|
+
const gapHalf = Math.min(straightLength * Math.min(labelPosition, 1 - labelPosition), labelKnockoutHalfExtent("P", straightDirection, labelRotation, options, context, DEFAULT_PURSUIT_OPTIONS.labelPadding));
|
|
14520
15170
|
const gapStart = vecAdd(labelPoint, vecScale(straightDirection, -gapHalf));
|
|
14521
15171
|
const gapEnd = vecAdd(labelPoint, vecScale(straightDirection, gapHalf));
|
|
14522
15172
|
const center = midpoint(p2, p3);
|
|
@@ -14543,7 +15193,7 @@ function createPursuit(coordinates, options = {}) {
|
|
|
14543
15193
|
terminalBar
|
|
14544
15194
|
]
|
|
14545
15195
|
}
|
|
14546
|
-
}, createLabelFeature(labelPoint, "P",
|
|
15196
|
+
}, createLabelFeature(labelPoint, "P", labelRotation, options)]
|
|
14547
15197
|
};
|
|
14548
15198
|
}
|
|
14549
15199
|
const PURSUIT = defineControlMeasure({
|
|
@@ -14566,7 +15216,7 @@ const DEFAULT_REARWARD_PASSAGE_OF_LINES_OPTIONS = DEFAULT_DELAY_OPTIONS;
|
|
|
14566
15216
|
const REARWARD_PASSAGE_OF_LINES_METADATA = {
|
|
14567
15217
|
id: "rearward-passage-of-lines",
|
|
14568
15218
|
name: "Rearward Passage of Lines",
|
|
14569
|
-
description: "Passes one force through another force
|
|
15219
|
+
description: "Passes one force through another force’s positions while moving away from the enemy.",
|
|
14570
15220
|
entity: "Mission Tasks",
|
|
14571
15221
|
entityType: "Rearward Passage of Lines",
|
|
14572
15222
|
value: "344200",
|
|
@@ -14581,27 +15231,80 @@ const REARWARD_PASSAGE_OF_LINES_METADATA = {
|
|
|
14581
15231
|
},
|
|
14582
15232
|
drawRule: "Line24",
|
|
14583
15233
|
capturesLabelSize: true,
|
|
15234
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
14584
15235
|
params: DELAY_PARAMS
|
|
14585
15236
|
};
|
|
14586
|
-
function createRearwardPassageOfLinesSymbol(coordinates, options = {}) {
|
|
15237
|
+
function createRearwardPassageOfLinesSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
14587
15238
|
return createDelayVariantSymbol(coordinates, {
|
|
14588
15239
|
glyph: "P(R)",
|
|
14589
15240
|
part: "rearward-passage-of-lines"
|
|
14590
|
-
}, options);
|
|
15241
|
+
}, options, context);
|
|
14591
15242
|
}
|
|
14592
15243
|
const REARWARD_PASSAGE_OF_LINES = defineControlMeasure({
|
|
14593
15244
|
metadata: REARWARD_PASSAGE_OF_LINES_METADATA,
|
|
14594
15245
|
generator: createRearwardPassageOfLinesSymbol,
|
|
14595
15246
|
defaultOptions: DEFAULT_REARWARD_PASSAGE_OF_LINES_OPTIONS,
|
|
14596
15247
|
rule: line24DrawRule,
|
|
14597
|
-
previewSample: {
|
|
14598
|
-
|
|
14599
|
-
|
|
14600
|
-
|
|
14601
|
-
|
|
14602
|
-
|
|
14603
|
-
|
|
14604
|
-
|
|
15248
|
+
previewSample: { controlPoints: [
|
|
15249
|
+
[-1, -.4],
|
|
15250
|
+
[1, -.4],
|
|
15251
|
+
[0, .6]
|
|
15252
|
+
] }
|
|
15253
|
+
});
|
|
15254
|
+
//#endregion
|
|
15255
|
+
//#region src/generators/cm34-mission-tasks/relief-in-place.ts
|
|
15256
|
+
const DEFAULT_RELIEF_IN_PLACE_OPTIONS = {
|
|
15257
|
+
arrowheadLengthRatio: DEFAULT_DELAY_OPTIONS.arrowheadLengthRatio,
|
|
15258
|
+
arrowheadWidthRatio: DEFAULT_DELAY_OPTIONS.arrowheadWidthRatio,
|
|
15259
|
+
arcSegments: DEFAULT_DELAY_OPTIONS.arcSegments
|
|
15260
|
+
};
|
|
15261
|
+
const RELIEF_IN_PLACE_METADATA = {
|
|
15262
|
+
id: "relief-in-place",
|
|
15263
|
+
name: "Relief in Place",
|
|
15264
|
+
description: "Replaces all or part of a unit in an area and transfers responsibility for its mission and assigned area to the incoming unit.",
|
|
15265
|
+
entity: "Mission Tasks",
|
|
15266
|
+
entityType: "Relief in Place",
|
|
15267
|
+
value: "341900",
|
|
15268
|
+
minCoordinates: 4,
|
|
15269
|
+
maxCoordinates: 4,
|
|
15270
|
+
geometry: "line",
|
|
15271
|
+
geometryTypes: ["MultiLineString", "Point"],
|
|
15272
|
+
paints: {
|
|
15273
|
+
stroke: true,
|
|
15274
|
+
fill: "none",
|
|
15275
|
+
text: true
|
|
15276
|
+
},
|
|
15277
|
+
drawRule: "Area18",
|
|
15278
|
+
capturesLabelSize: true,
|
|
15279
|
+
params: DELAY_PARAMS.filter((param) => param.key !== "labelPadding")
|
|
15280
|
+
};
|
|
15281
|
+
/** Generates the RELIEF IN PLACE mission task symbol (341900). */
|
|
15282
|
+
function createReliefInPlaceSymbol(coordinates, options = {}) {
|
|
15283
|
+
return createHairpinSymbol(coordinates, {
|
|
15284
|
+
part: "relief-in-place",
|
|
15285
|
+
buildLabel: ({ points, upperDirection, upperArm }) => ({
|
|
15286
|
+
upperArm,
|
|
15287
|
+
label: createLabelFeature(averagePoints(points), "RIP", labelRotationAlong(upperDirection), {
|
|
15288
|
+
labelSize: options.labelSize,
|
|
15289
|
+
labelSizePixels: options.labelSizePixels
|
|
15290
|
+
})
|
|
15291
|
+
})
|
|
15292
|
+
}, options);
|
|
15293
|
+
}
|
|
15294
|
+
function averagePoints(points) {
|
|
15295
|
+
return vecScale(points.reduce((sum, point) => vecAdd(sum, point), [0, 0]), 1 / points.length);
|
|
15296
|
+
}
|
|
15297
|
+
const RELIEF_IN_PLACE = defineControlMeasure({
|
|
15298
|
+
metadata: RELIEF_IN_PLACE_METADATA,
|
|
15299
|
+
generator: createReliefInPlaceSymbol,
|
|
15300
|
+
defaultOptions: DEFAULT_RELIEF_IN_PLACE_OPTIONS,
|
|
15301
|
+
rule: reliefInPlaceDrawRule,
|
|
15302
|
+
previewSample: { controlPoints: [
|
|
15303
|
+
[-1, .45],
|
|
15304
|
+
[.45, .45],
|
|
15305
|
+
[.45, -.45],
|
|
15306
|
+
[-1, -.45]
|
|
15307
|
+
] }
|
|
14605
15308
|
});
|
|
14606
15309
|
//#endregion
|
|
14607
15310
|
//#region src/generators/cm34-mission-tasks/retire.ts
|
|
@@ -14624,14 +15327,15 @@ const RETIRE_METADATA = {
|
|
|
14624
15327
|
},
|
|
14625
15328
|
drawRule: "Line24",
|
|
14626
15329
|
capturesLabelSize: true,
|
|
15330
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
14627
15331
|
params: DELAY_PARAMS
|
|
14628
15332
|
};
|
|
14629
15333
|
/** Generates the RETIRE/RETIREMENT mission task symbol (342000). */
|
|
14630
|
-
function createRetireSymbol(coordinates, options = {}) {
|
|
15334
|
+
function createRetireSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
14631
15335
|
return createDelayVariantSymbol(coordinates, {
|
|
14632
15336
|
glyph: "R",
|
|
14633
15337
|
part: "retire"
|
|
14634
|
-
}, options);
|
|
15338
|
+
}, options, context);
|
|
14635
15339
|
}
|
|
14636
15340
|
const RETIRE = defineControlMeasure({
|
|
14637
15341
|
metadata: RETIRE_METADATA,
|
|
@@ -14685,11 +15389,137 @@ const SCREEN = defineControlMeasure({
|
|
|
14685
15389
|
}
|
|
14686
15390
|
});
|
|
14687
15391
|
//#endregion
|
|
15392
|
+
//#region src/generators/cm34-mission-tasks/secure.ts
|
|
15393
|
+
const DEFAULT_SECURE_OPTIONS = {
|
|
15394
|
+
openingAngle: 30,
|
|
15395
|
+
resolution: 64,
|
|
15396
|
+
arrowheadLengthRatio: .2,
|
|
15397
|
+
arrowheadWidthRatio: .4,
|
|
15398
|
+
labelPadding: .2
|
|
15399
|
+
};
|
|
15400
|
+
const SECURE_METADATA = {
|
|
15401
|
+
id: "secure",
|
|
15402
|
+
name: "Secure",
|
|
15403
|
+
description: "Prevents a unit, facility, or geographical location from being damaged or destroyed by enemy action.",
|
|
15404
|
+
entity: "Mission Tasks",
|
|
15405
|
+
entityType: "Secure",
|
|
15406
|
+
value: "342100",
|
|
15407
|
+
minCoordinates: 2,
|
|
15408
|
+
maxCoordinates: 2,
|
|
15409
|
+
geometry: "area",
|
|
15410
|
+
geometryTypes: ["MultiLineString", "Point"],
|
|
15411
|
+
paints: {
|
|
15412
|
+
stroke: true,
|
|
15413
|
+
fill: "none",
|
|
15414
|
+
text: true
|
|
15415
|
+
},
|
|
15416
|
+
drawRule: "Area19",
|
|
15417
|
+
capturesLabelSize: true,
|
|
15418
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
15419
|
+
params: [
|
|
15420
|
+
{
|
|
15421
|
+
key: "openingAngle",
|
|
15422
|
+
presentationTier: "advanced",
|
|
15423
|
+
label: "Opening",
|
|
15424
|
+
description: "Angular width of the opening on the friendly side, in degrees",
|
|
15425
|
+
type: "number",
|
|
15426
|
+
min: 5,
|
|
15427
|
+
max: 180,
|
|
15428
|
+
step: 1
|
|
15429
|
+
},
|
|
15430
|
+
...ARC_RESOLUTION_PARAMS,
|
|
15431
|
+
{
|
|
15432
|
+
key: "arrowheadLengthRatio",
|
|
15433
|
+
presentationTier: "advanced",
|
|
15434
|
+
label: "Arrowhead length",
|
|
15435
|
+
description: "Arrowhead length as a ratio of the symbol radius",
|
|
15436
|
+
type: "number",
|
|
15437
|
+
min: 0,
|
|
15438
|
+
max: 1,
|
|
15439
|
+
step: .01
|
|
15440
|
+
},
|
|
15441
|
+
{
|
|
15442
|
+
key: "arrowheadWidthRatio",
|
|
15443
|
+
presentationTier: "advanced",
|
|
15444
|
+
label: "Arrowhead width",
|
|
15445
|
+
description: "Arrowhead width as a ratio of the symbol radius",
|
|
15446
|
+
type: "number",
|
|
15447
|
+
min: 0,
|
|
15448
|
+
max: 1,
|
|
15449
|
+
step: .01
|
|
15450
|
+
},
|
|
15451
|
+
{
|
|
15452
|
+
key: "labelPadding",
|
|
15453
|
+
presentationTier: "advanced",
|
|
15454
|
+
label: "S padding",
|
|
15455
|
+
description: "Extra boundary clearance on each side of S, as a ratio of its height",
|
|
15456
|
+
type: "number",
|
|
15457
|
+
min: 0,
|
|
15458
|
+
max: 2,
|
|
15459
|
+
step: .05
|
|
15460
|
+
}
|
|
15461
|
+
]
|
|
15462
|
+
};
|
|
15463
|
+
const TAU$1 = Math.PI * 2;
|
|
15464
|
+
const MIN_LABEL_GAP_METERS$1 = 1;
|
|
15465
|
+
/** Generates the Secure mission task symbol (342100). */
|
|
15466
|
+
function createSecureSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
15467
|
+
const center = project(coordinates[0][0], coordinates[0][1]);
|
|
15468
|
+
const radiusVector = vecSub(project(coordinates[1][0], coordinates[1][1]), center);
|
|
15469
|
+
const radius = vecMag(radiusVector);
|
|
15470
|
+
if (radius < 1e-6) return {
|
|
15471
|
+
type: "FeatureCollection",
|
|
15472
|
+
features: []
|
|
15473
|
+
};
|
|
15474
|
+
const resolved = {
|
|
15475
|
+
...DEFAULT_SECURE_OPTIONS,
|
|
15476
|
+
...options
|
|
15477
|
+
};
|
|
15478
|
+
const bulgeVector = [radiusVector[1], -radiusVector[0]];
|
|
15479
|
+
const halfTurnResolution = normalizeArcResolution(resolved.resolution) / 2;
|
|
15480
|
+
const boundarySpan = TAU$1 - Math.min(180, Math.max(5, resolved.openingAngle)) * Math.PI / 180;
|
|
15481
|
+
const labelSweep = boundarySpan / 2;
|
|
15482
|
+
const labelHeight = resolveTextMetrics("S", options, context, "regular", radius * .12).height;
|
|
15483
|
+
const labelPadding = Math.max(0, resolved.labelPadding);
|
|
15484
|
+
const labelHalfGap = Math.min(boundarySpan / 4, Math.max(labelHeight * (LABEL_HALF_GLYPH_RATIO + labelPadding), MIN_LABEL_GAP_METERS$1) / radius);
|
|
15485
|
+
const sampleArc = (from, to) => sampleProjectedArc(center, radiusVector, bulgeVector, from, to, halfTurnResolution);
|
|
15486
|
+
const terminal = arcPointAt(center, radiusVector, bulgeVector, boundarySpan);
|
|
15487
|
+
const radial = vecScale(vecSub(terminal, center), 1 / radius);
|
|
15488
|
+
const tangent = [radial[1], -radial[0]];
|
|
15489
|
+
const lines = [
|
|
15490
|
+
sampleArc(0, labelSweep - labelHalfGap),
|
|
15491
|
+
sampleArc(labelSweep + labelHalfGap, boundarySpan),
|
|
15492
|
+
createArrowHead$1(terminal, tangent, radius * Math.max(0, resolved.arrowheadLengthRatio), radius * Math.max(0, resolved.arrowheadWidthRatio))
|
|
15493
|
+
];
|
|
15494
|
+
const labelPoint = arcPointAt(center, radiusVector, bulgeVector, labelSweep);
|
|
15495
|
+
return {
|
|
15496
|
+
type: "FeatureCollection",
|
|
15497
|
+
features: [{
|
|
15498
|
+
type: "Feature",
|
|
15499
|
+
properties: { part: "secure" },
|
|
15500
|
+
geometry: {
|
|
15501
|
+
type: "MultiLineString",
|
|
15502
|
+
coordinates: lines
|
|
15503
|
+
}
|
|
15504
|
+
}, createLabelFeature(labelPoint, "S", Math.PI, options)]
|
|
15505
|
+
};
|
|
15506
|
+
}
|
|
15507
|
+
const SECURE = defineControlMeasure({
|
|
15508
|
+
metadata: SECURE_METADATA,
|
|
15509
|
+
generator: createSecureSymbol,
|
|
15510
|
+
defaultOptions: DEFAULT_SECURE_OPTIONS,
|
|
15511
|
+
rule: secureDrawRule,
|
|
15512
|
+
previewSample: {
|
|
15513
|
+
controlPoints: [[0, 0], [-.8, .05]],
|
|
15514
|
+
options: { labelSize: 90 }
|
|
15515
|
+
}
|
|
15516
|
+
});
|
|
15517
|
+
//#endregion
|
|
14688
15518
|
//#region src/generators/cm34-mission-tasks/seize.ts
|
|
14689
15519
|
const DEFAULT_SEIZE_OPTIONS = {
|
|
14690
15520
|
arrowheadLengthRatio: .1,
|
|
14691
15521
|
arrowheadWidthRatio: .12,
|
|
14692
|
-
|
|
15522
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING,
|
|
14693
15523
|
curveSegments: 48,
|
|
14694
15524
|
circleSegments: 64
|
|
14695
15525
|
};
|
|
@@ -14714,16 +15544,7 @@ const SEIZE_PARAMS = [
|
|
|
14714
15544
|
max: .5,
|
|
14715
15545
|
step: .01
|
|
14716
15546
|
},
|
|
14717
|
-
|
|
14718
|
-
key: "labelGapRatio",
|
|
14719
|
-
presentationTier: "advanced",
|
|
14720
|
-
label: "Label gap",
|
|
14721
|
-
description: "Size of the curve gap holding the S label",
|
|
14722
|
-
type: "number",
|
|
14723
|
-
min: 0,
|
|
14724
|
-
max: .5,
|
|
14725
|
-
step: .01
|
|
14726
|
-
},
|
|
15547
|
+
LABEL_PADDING_PARAM,
|
|
14727
15548
|
{
|
|
14728
15549
|
key: "curveSegments",
|
|
14729
15550
|
presentationTier: "advanced",
|
|
@@ -14748,7 +15569,7 @@ const SEIZE_PARAMS = [
|
|
|
14748
15569
|
const SEIZE_METADATA = {
|
|
14749
15570
|
id: "seize",
|
|
14750
15571
|
name: "Seize",
|
|
14751
|
-
description: "Takes possession of a designated area using overwhelming force and
|
|
15572
|
+
description: "Takes possession of a designated area using overwhelming force, overcoming enemy opposition and preventing enemy direct fire on the objective.",
|
|
14752
15573
|
entity: "Mission Tasks",
|
|
14753
15574
|
entityType: "Seize",
|
|
14754
15575
|
value: "342300",
|
|
@@ -14763,6 +15584,7 @@ const SEIZE_METADATA = {
|
|
|
14763
15584
|
},
|
|
14764
15585
|
drawRule: "Line27",
|
|
14765
15586
|
capturesLabelSize: true,
|
|
15587
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
14766
15588
|
params: SEIZE_PARAMS
|
|
14767
15589
|
};
|
|
14768
15590
|
function positiveAngle(angle) {
|
|
@@ -14794,7 +15616,7 @@ function threePointArc(start, middle, end) {
|
|
|
14794
15616
|
};
|
|
14795
15617
|
}
|
|
14796
15618
|
/** Shared geometry for mission tasks that use the Seize graphic. */
|
|
14797
|
-
function createSeizeLikeSymbol(coordinates, options = {}, label = "S", part = "seize") {
|
|
15619
|
+
function createSeizeLikeSymbol(coordinates, options = {}, label = "S", part = "seize", context = {}) {
|
|
14798
15620
|
const [c1, c2, c3, c4] = coordinates;
|
|
14799
15621
|
const center = project(c1[0], c1[1]);
|
|
14800
15622
|
const start = project(c2[0], c2[1]);
|
|
@@ -14815,8 +15637,14 @@ function createSeizeLikeSymbol(coordinates, options = {}, label = "S", part = "s
|
|
|
14815
15637
|
let arrowDirection;
|
|
14816
15638
|
let labelPoint;
|
|
14817
15639
|
let tangentAtLabel;
|
|
15640
|
+
let labelRotation;
|
|
14818
15641
|
if (arc) {
|
|
14819
|
-
const
|
|
15642
|
+
const labelAngle = arc.startAngle + arc.sweepAngle / 2;
|
|
15643
|
+
const sweepSign = Math.sign(arc.sweepAngle);
|
|
15644
|
+
labelPoint = vecAdd(arc.center, [arc.radius * Math.cos(labelAngle), arc.radius * Math.sin(labelAngle)]);
|
|
15645
|
+
tangentAtLabel = [-Math.sin(labelAngle) * sweepSign, Math.cos(labelAngle) * sweepSign];
|
|
15646
|
+
labelRotation = labelRotationAlong(tangentAtLabel);
|
|
15647
|
+
const gapHalf = arcKnockoutHalfFraction(labelKnockoutHalfExtent(label, tangentAtLabel, labelRotation, options, context, DEFAULT_SEIZE_OPTIONS.labelPadding), arc.radius, arc.sweepAngle);
|
|
14820
15648
|
const gapStart = .5 - gapHalf;
|
|
14821
15649
|
const gapEnd = .5 + gapHalf;
|
|
14822
15650
|
const sampleArc = (from, to) => {
|
|
@@ -14832,22 +15660,19 @@ function createSeizeLikeSymbol(coordinates, options = {}, label = "S", part = "s
|
|
|
14832
15660
|
firstArcPart = sampleArc(0, gapStart);
|
|
14833
15661
|
secondArcPart = sampleArc(gapEnd, 1);
|
|
14834
15662
|
const endAngle = arc.startAngle + arc.sweepAngle;
|
|
14835
|
-
const sweepSign = Math.sign(arc.sweepAngle);
|
|
14836
15663
|
arrowDirection = [-Math.sin(endAngle) * sweepSign, Math.cos(endAngle) * sweepSign];
|
|
14837
|
-
const labelAngle = arc.startAngle + arc.sweepAngle / 2;
|
|
14838
|
-
labelPoint = vecAdd(arc.center, [arc.radius * Math.cos(labelAngle), arc.radius * Math.sin(labelAngle)]);
|
|
14839
|
-
tangentAtLabel = [-Math.sin(labelAngle) * sweepSign, Math.cos(labelAngle) * sweepSign];
|
|
14840
15664
|
} else {
|
|
14841
15665
|
labelPoint = vecScale(vecAdd(start, tip), .5);
|
|
14842
|
-
const gapHalf = span * clamp01(options.labelGapRatio ?? DEFAULT_SEIZE_OPTIONS.labelGapRatio) / 2;
|
|
14843
15666
|
const direction = vecNorm(vecSub(tip, start));
|
|
15667
|
+
tangentAtLabel = direction;
|
|
15668
|
+
labelRotation = labelRotationAlong(tangentAtLabel);
|
|
15669
|
+
const gapHalf = Math.min(span / 2, labelKnockoutHalfExtent(label, direction, labelRotation, options, context, DEFAULT_SEIZE_OPTIONS.labelPadding));
|
|
14844
15670
|
const before = vecAdd(labelPoint, vecScale(direction, -gapHalf));
|
|
14845
15671
|
const after = vecAdd(labelPoint, vecScale(direction, gapHalf));
|
|
14846
15672
|
firstArcPart = [toPosition(start), toPosition(before)];
|
|
14847
15673
|
secondArcPart = [toPosition(after), toPosition(tip)];
|
|
14848
15674
|
const terminal = vecSub(tip, curvePoint);
|
|
14849
15675
|
arrowDirection = vecMag(terminal) >= 1e-6 ? vecNorm(terminal) : vecNorm(vecSub(tip, start));
|
|
14850
|
-
tangentAtLabel = direction;
|
|
14851
15676
|
}
|
|
14852
15677
|
const startAngle = Math.atan2(radiusVector[1], radiusVector[0]);
|
|
14853
15678
|
const circle = [];
|
|
@@ -14870,12 +15695,12 @@ function createSeizeLikeSymbol(coordinates, options = {}, label = "S", part = "s
|
|
|
14870
15695
|
type: "MultiLineString",
|
|
14871
15696
|
coordinates: lines
|
|
14872
15697
|
}
|
|
14873
|
-
}, createLabelFeature(labelPoint, label,
|
|
15698
|
+
}, createLabelFeature(labelPoint, label, labelRotation, options)]
|
|
14874
15699
|
};
|
|
14875
15700
|
}
|
|
14876
15701
|
/** Generates the Seize mission task symbol (342300). */
|
|
14877
|
-
function createSeizeSymbol(coordinates, options = {}) {
|
|
14878
|
-
return createSeizeLikeSymbol(coordinates, options);
|
|
15702
|
+
function createSeizeSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
15703
|
+
return createSeizeLikeSymbol(coordinates, options, "S", "seize", context);
|
|
14879
15704
|
}
|
|
14880
15705
|
const SEIZE = defineControlMeasure({
|
|
14881
15706
|
metadata: SEIZE_METADATA,
|
|
@@ -14913,11 +15738,12 @@ const EVACUATE_METADATA = {
|
|
|
14913
15738
|
},
|
|
14914
15739
|
drawRule: "Line27",
|
|
14915
15740
|
capturesLabelSize: true,
|
|
15741
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
14916
15742
|
params: SEIZE_PARAMS
|
|
14917
15743
|
};
|
|
14918
15744
|
/** Generates the Evacuate mission task symbol (344500). */
|
|
14919
|
-
function createEvacuateSymbol(coordinates, options = {}) {
|
|
14920
|
-
return createSeizeLikeSymbol(coordinates, options, "E", "evacuate");
|
|
15745
|
+
function createEvacuateSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
15746
|
+
return createSeizeLikeSymbol(coordinates, options, "E", "evacuate", context);
|
|
14921
15747
|
}
|
|
14922
15748
|
const EVACUATE = defineControlMeasure({
|
|
14923
15749
|
metadata: EVACUATE_METADATA,
|
|
@@ -14955,13 +15781,14 @@ const WITHDRAW_METADATA = {
|
|
|
14955
15781
|
},
|
|
14956
15782
|
drawRule: "Line24",
|
|
14957
15783
|
capturesLabelSize: true,
|
|
15784
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
14958
15785
|
params: DELAY_PARAMS
|
|
14959
15786
|
};
|
|
14960
|
-
function createWithdrawSymbol(coordinates, options = {}) {
|
|
15787
|
+
function createWithdrawSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
14961
15788
|
return createDelayVariantSymbol(coordinates, {
|
|
14962
15789
|
glyph: "W",
|
|
14963
15790
|
part: "withdraw"
|
|
14964
|
-
}, options);
|
|
15791
|
+
}, options, context);
|
|
14965
15792
|
}
|
|
14966
15793
|
const WITHDRAW = defineControlMeasure({
|
|
14967
15794
|
metadata: WITHDRAW_METADATA,
|
|
@@ -14990,27 +15817,25 @@ const WITHDRAW_UNDER_PRESSURE_METADATA = {
|
|
|
14990
15817
|
},
|
|
14991
15818
|
drawRule: "Line24",
|
|
14992
15819
|
capturesLabelSize: true,
|
|
15820
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
14993
15821
|
params: DELAY_PARAMS
|
|
14994
15822
|
};
|
|
14995
|
-
function createWithdrawUnderPressureSymbol(coordinates, options = {}) {
|
|
15823
|
+
function createWithdrawUnderPressureSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
14996
15824
|
return createDelayVariantSymbol(coordinates, {
|
|
14997
15825
|
glyph: "WP",
|
|
14998
15826
|
part: "withdraw-under-pressure"
|
|
14999
|
-
}, options);
|
|
15827
|
+
}, options, context);
|
|
15000
15828
|
}
|
|
15001
15829
|
const WITHDRAW_UNDER_PRESSURE = defineControlMeasure({
|
|
15002
15830
|
metadata: WITHDRAW_UNDER_PRESSURE_METADATA,
|
|
15003
15831
|
generator: createWithdrawUnderPressureSymbol,
|
|
15004
15832
|
defaultOptions: DEFAULT_WITHDRAW_UNDER_PRESSURE_OPTIONS,
|
|
15005
15833
|
rule: line24DrawRule,
|
|
15006
|
-
previewSample: {
|
|
15007
|
-
|
|
15008
|
-
|
|
15009
|
-
|
|
15010
|
-
|
|
15011
|
-
],
|
|
15012
|
-
options: { labelGapRatio: .3 }
|
|
15013
|
-
}
|
|
15834
|
+
previewSample: { controlPoints: [
|
|
15835
|
+
[-1, -.4],
|
|
15836
|
+
[1, -.4],
|
|
15837
|
+
[0, .6]
|
|
15838
|
+
] }
|
|
15014
15839
|
});
|
|
15015
15840
|
//#endregion
|
|
15016
15841
|
//#region src/generators/cm27-protection-areas/turn.ts
|
|
@@ -15076,10 +15901,10 @@ const TURN_METADATA = {
|
|
|
15076
15901
|
* arc and placing its tip at PT1. PT3 selects which side of the PT2 -> PT1
|
|
15077
15902
|
* chord contains the arc.
|
|
15078
15903
|
*
|
|
15079
|
-
* `
|
|
15080
|
-
*
|
|
15904
|
+
* `labelGapHalfLength` optionally resolves a meter-denominated half-gap from the arc midpoint
|
|
15905
|
+
* anchor for measures that carry a doctrinal glyph there.
|
|
15081
15906
|
*/
|
|
15082
|
-
function createTurnLikeSymbol(coordinates, options = {},
|
|
15907
|
+
function createTurnLikeSymbol(coordinates, options = {}, labelGapHalfLength) {
|
|
15083
15908
|
const tip = project(coordinates[0][0], coordinates[0][1]);
|
|
15084
15909
|
const rear = project(coordinates[1][0], coordinates[1][1]);
|
|
15085
15910
|
const side = quarterArcSide(rear, tip, coordinates[2] ? project(coordinates[2][0], coordinates[2][1]) : void 0);
|
|
@@ -15104,14 +15929,14 @@ function createTurnLikeSymbol(coordinates, options = {}, labelGapRatio = 0) {
|
|
|
15104
15929
|
}
|
|
15105
15930
|
return points;
|
|
15106
15931
|
};
|
|
15107
|
-
const gapHalf = clamp01(labelGapRatio) / 2;
|
|
15108
|
-
const arcParts = gapHalf > 0 ? [sampleArc(0, .5 - gapHalf), sampleArc(.5 + gapHalf, 1)] : [sampleArc(0, 1)];
|
|
15109
15932
|
const sweepSign = Math.sign(arc.sweepAngle);
|
|
15110
15933
|
const midAngle = arc.startAngle + arc.sweepAngle / 2;
|
|
15111
15934
|
const labelAnchor = {
|
|
15112
15935
|
point: arc.midpoint,
|
|
15113
15936
|
tangent: [-Math.sin(midAngle) * sweepSign, Math.cos(midAngle) * sweepSign]
|
|
15114
15937
|
};
|
|
15938
|
+
const gapHalf = arcKnockoutHalfFraction(labelGapHalfLength?.(labelAnchor) ?? 0, arc.radius, arc.sweepAngle);
|
|
15939
|
+
const arcParts = gapHalf > 0 ? [sampleArc(0, .5 - gapHalf), sampleArc(.5 + gapHalf, 1)] : [sampleArc(0, 1)];
|
|
15115
15940
|
const headWidth = totalLength * (options.arrowheadWidthRatio ?? DEFAULT_TURN_OPTIONS.arrowheadWidthRatio);
|
|
15116
15941
|
const arrowHead = createArrowHeadRing(tip, vecNorm(vecSub(tip, headBase)), vecMag(vecSub(tip, headBase)), headWidth);
|
|
15117
15942
|
return {
|
|
@@ -15165,12 +15990,12 @@ const DEFAULT_TURN_MISSION_TASK_OPTIONS = {
|
|
|
15165
15990
|
...DEFAULT_TURN_OPTIONS,
|
|
15166
15991
|
arrowheadLengthRatio: .14,
|
|
15167
15992
|
arrowheadWidthRatio: .14,
|
|
15168
|
-
|
|
15993
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING
|
|
15169
15994
|
};
|
|
15170
15995
|
const TURN_MISSION_TASK_METADATA = {
|
|
15171
15996
|
id: "turn-mission-task",
|
|
15172
15997
|
name: "Turn",
|
|
15173
|
-
description: "
|
|
15998
|
+
description: "Forces an enemy force from one avenue of approach or movement corridor to another.",
|
|
15174
15999
|
entity: "Mission Tasks",
|
|
15175
16000
|
entityType: "Turn",
|
|
15176
16001
|
value: "344700",
|
|
@@ -15188,24 +16013,19 @@ const TURN_MISSION_TASK_METADATA = {
|
|
|
15188
16013
|
},
|
|
15189
16014
|
drawRule: "Line10",
|
|
15190
16015
|
capturesLabelSize: true,
|
|
15191
|
-
|
|
15192
|
-
|
|
15193
|
-
presentationTier: "advanced",
|
|
15194
|
-
label: "Label gap",
|
|
15195
|
-
description: "Size of the arc gap holding the doctrinal 'T' label",
|
|
15196
|
-
type: "number",
|
|
15197
|
-
min: 0,
|
|
15198
|
-
max: 1,
|
|
15199
|
-
step: .01
|
|
15200
|
-
}]
|
|
16016
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
16017
|
+
params: [...TURN_PARAMS, LABEL_PADDING_PARAM]
|
|
15201
16018
|
};
|
|
15202
16019
|
/** Generates the TURN mission task symbol (344700). */
|
|
15203
|
-
function createTurnMissionTaskSymbol(coordinates, options = {}) {
|
|
16020
|
+
function createTurnMissionTaskSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
15204
16021
|
const { features, labelAnchor } = createTurnLikeSymbol(coordinates, {
|
|
15205
16022
|
arrowheadLengthRatio: options.arrowheadLengthRatio ?? DEFAULT_TURN_MISSION_TASK_OPTIONS.arrowheadLengthRatio,
|
|
15206
16023
|
arrowheadWidthRatio: options.arrowheadWidthRatio ?? DEFAULT_TURN_MISSION_TASK_OPTIONS.arrowheadWidthRatio,
|
|
15207
16024
|
arcSegments: options.arcSegments ?? DEFAULT_TURN_MISSION_TASK_OPTIONS.arcSegments
|
|
15208
|
-
},
|
|
16025
|
+
}, (anchor) => {
|
|
16026
|
+
const rotation = labelRotationAlong(anchor.tangent);
|
|
16027
|
+
return labelKnockoutHalfExtent("T", anchor.tangent, rotation, options, context, DEFAULT_TURN_MISSION_TASK_OPTIONS.labelPadding);
|
|
16028
|
+
});
|
|
15209
16029
|
if (!labelAnchor) return {
|
|
15210
16030
|
type: "FeatureCollection",
|
|
15211
16031
|
features: []
|
|
@@ -15251,6 +16071,7 @@ const JOINT_TACTICAL_ACTION_AREA_METADATA = {
|
|
|
15251
16071
|
},
|
|
15252
16072
|
drawRule: "Area1",
|
|
15253
16073
|
capturesLabelSize: true,
|
|
16074
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
15254
16075
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
15255
16076
|
textAmplifiers: AREA_TEXT_AMPLIFIERS
|
|
15256
16077
|
};
|
|
@@ -15311,6 +16132,7 @@ const LANDING_ZONE_METADATA = {
|
|
|
15311
16132
|
},
|
|
15312
16133
|
drawRule: "Area1",
|
|
15313
16134
|
capturesLabelSize: true,
|
|
16135
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
15314
16136
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
15315
16137
|
textAmplifiers: AREA_TEXT_AMPLIFIERS
|
|
15316
16138
|
};
|
|
@@ -15366,6 +16188,7 @@ const PENETRATION_BOX_METADATA = {
|
|
|
15366
16188
|
},
|
|
15367
16189
|
drawRule: "Area1",
|
|
15368
16190
|
capturesLabelSize: true,
|
|
16191
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
15369
16192
|
params: LABELED_AREA_PARAMS,
|
|
15370
16193
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
15371
16194
|
};
|
|
@@ -15417,6 +16240,7 @@ const LIMITED_ACCESS_AREA_METADATA = {
|
|
|
15417
16240
|
},
|
|
15418
16241
|
drawRule: "Area1",
|
|
15419
16242
|
capturesLabelSize: true,
|
|
16243
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
15420
16244
|
params: [...AREA_SMOOTH_PARAMS, AREA_LABEL_PADDING_PARAM],
|
|
15421
16245
|
textAmplifiers: [AREA_HOSTILE_AMPLIFIER]
|
|
15422
16246
|
};
|
|
@@ -15512,6 +16336,7 @@ const NO_FIRE_AREA_IRREGULAR_METADATA = {
|
|
|
15512
16336
|
},
|
|
15513
16337
|
drawRule: "Area1",
|
|
15514
16338
|
capturesLabelSize: true,
|
|
16339
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
15515
16340
|
params: [...AREA_SMOOTH_PARAMS, AREA_LABEL_PADDING_PARAM],
|
|
15516
16341
|
textAmplifiers: [...NO_FIRE_AREA_TEXT_AMPLIFIERS, AREA_HOSTILE_AMPLIFIER]
|
|
15517
16342
|
};
|
|
@@ -15757,6 +16582,7 @@ const MINEFIELD_METADATA = {
|
|
|
15757
16582
|
text: true
|
|
15758
16583
|
},
|
|
15759
16584
|
drawRule: "Point2",
|
|
16585
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
15760
16586
|
params: [
|
|
15761
16587
|
MINE_TYPE_PARAM,
|
|
15762
16588
|
{
|
|
@@ -16059,6 +16885,7 @@ const COMMON_METADATA = {
|
|
|
16059
16885
|
},
|
|
16060
16886
|
drawRule: "Area1",
|
|
16061
16887
|
capturesLabelSize: true,
|
|
16888
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
16062
16889
|
params: [
|
|
16063
16890
|
MINE_TYPE_PARAM,
|
|
16064
16891
|
...AREA_SMOOTH_PARAMS,
|
|
@@ -16081,7 +16908,8 @@ const MINED_AREA_METADATA = {
|
|
|
16081
16908
|
name: "Mined Area",
|
|
16082
16909
|
description: "An area dangerous because of the presence or suspected presence of mines.",
|
|
16083
16910
|
entityType: "Mined Area",
|
|
16084
|
-
value: "270800"
|
|
16911
|
+
value: "270800",
|
|
16912
|
+
labelGeometryUsesResolvedMetrics: true
|
|
16085
16913
|
};
|
|
16086
16914
|
function axisIntersections(ring, axis, target) {
|
|
16087
16915
|
const other = axis === 0 ? 1 : 0;
|
|
@@ -16628,6 +17456,7 @@ const PICKUP_ZONE_METADATA = {
|
|
|
16628
17456
|
},
|
|
16629
17457
|
drawRule: "Area1",
|
|
16630
17458
|
capturesLabelSize: true,
|
|
17459
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
16631
17460
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
16632
17461
|
textAmplifiers: AREA_TEXT_AMPLIFIERS
|
|
16633
17462
|
};
|
|
@@ -16683,6 +17512,7 @@ const ASSAULT_POSITION_METADATA = {
|
|
|
16683
17512
|
},
|
|
16684
17513
|
drawRule: "Area1",
|
|
16685
17514
|
capturesLabelSize: true,
|
|
17515
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
16686
17516
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
16687
17517
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
16688
17518
|
};
|
|
@@ -16738,6 +17568,7 @@ const ATTACK_POSITION_METADATA = {
|
|
|
16738
17568
|
},
|
|
16739
17569
|
drawRule: "Area1",
|
|
16740
17570
|
capturesLabelSize: true,
|
|
17571
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
16741
17572
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
16742
17573
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
16743
17574
|
};
|
|
@@ -16793,6 +17624,7 @@ const OBJECTIVE_AREA_METADATA = {
|
|
|
16793
17624
|
},
|
|
16794
17625
|
drawRule: "Area1",
|
|
16795
17626
|
capturesLabelSize: true,
|
|
17627
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
16796
17628
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
16797
17629
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
16798
17630
|
};
|
|
@@ -17142,6 +17974,7 @@ const STRONG_POINT_METADATA = {
|
|
|
17142
17974
|
},
|
|
17143
17975
|
drawRule: "Area1",
|
|
17144
17976
|
capturesLabelSize: true,
|
|
17977
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
17145
17978
|
params: ECHELON_AREA_PARAMS,
|
|
17146
17979
|
textAmplifiers: AREA_UNIT_DESIGNATION_HOSTILE_AMPLIFIERS
|
|
17147
17980
|
};
|
|
@@ -17463,6 +18296,7 @@ const SUBMARINE_ACTION_AREA_METADATA = {
|
|
|
17463
18296
|
},
|
|
17464
18297
|
drawRule: "Area1",
|
|
17465
18298
|
capturesLabelSize: true,
|
|
18299
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
17466
18300
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
17467
18301
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_NO_ADDITIONAL_INFO
|
|
17468
18302
|
};
|
|
@@ -17523,6 +18357,7 @@ const SUBMARINE_GENERATED_ACTION_AREA_METADATA = {
|
|
|
17523
18357
|
},
|
|
17524
18358
|
drawRule: "Area1",
|
|
17525
18359
|
capturesLabelSize: true,
|
|
18360
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
17526
18361
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
17527
18362
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_NO_ADDITIONAL_INFO
|
|
17528
18363
|
};
|
|
@@ -17845,13 +18680,19 @@ const DEFINITIONS = {
|
|
|
17845
18680
|
bypass: BYPASS,
|
|
17846
18681
|
canalize: CANALIZE,
|
|
17847
18682
|
clear: CLEAR,
|
|
18683
|
+
"cordon-and-knock": CORDON_AND_KNOCK,
|
|
18684
|
+
"cordon-and-search": CORDON_AND_SEARCH,
|
|
17848
18685
|
counterattack: COUNTERATTACK,
|
|
17849
18686
|
"counterattack-by-fire": COUNTERATTACK_BY_FIRE,
|
|
17850
18687
|
cover: COVER,
|
|
17851
18688
|
delay: DELAY,
|
|
18689
|
+
demonstrate: DEMONSTRATE,
|
|
17852
18690
|
disengage: DISENGAGE,
|
|
17853
18691
|
"disrupt-mission-task": DISRUPT_MISSION_TASK,
|
|
17854
18692
|
envelopment: ENVELOPMENT,
|
|
18693
|
+
exploitation: EXPLOITATION,
|
|
18694
|
+
"follow-and-assume": FOLLOW_AND_ASSUME,
|
|
18695
|
+
"follow-and-support": FOLLOW_AND_SUPPORT,
|
|
17855
18696
|
"forward-passage-of-lines": FORWARD_PASSAGE_OF_LINES,
|
|
17856
18697
|
infiltration: INFILTRATION,
|
|
17857
18698
|
guard: GUARD,
|
|
@@ -17861,8 +18702,10 @@ const DEFINITIONS = {
|
|
|
17861
18702
|
penetrate: PENETRATE,
|
|
17862
18703
|
pursuit: PURSUIT,
|
|
17863
18704
|
"rearward-passage-of-lines": REARWARD_PASSAGE_OF_LINES,
|
|
18705
|
+
"relief-in-place": RELIEF_IN_PLACE,
|
|
17864
18706
|
retire: RETIRE,
|
|
17865
18707
|
screen: SCREEN,
|
|
18708
|
+
secure: SECURE,
|
|
17866
18709
|
seize: SEIZE,
|
|
17867
18710
|
evacuate: EVACUATE,
|
|
17868
18711
|
withdraw: WITHDRAW,
|
|
@@ -18292,4 +19135,4 @@ function assertNever(value) {
|
|
|
18292
19135
|
throw new Error(`Unhandled control measure kind: ${String(value)}`);
|
|
18293
19136
|
}
|
|
18294
19137
|
//#endregion
|
|
18295
|
-
export {
|
|
19138
|
+
export { DEFAULT_PHASE_LINE_OPTIONS as $, getMetersPerPixel as $n, DEFAULT_DIRECTION_OF_SUPPORTING_ATTACK_OPTIONS as $t, DEFAULT_SECURE_OPTIONS as A, staticPointDrawRule as An, DEFAULT_GENERIC_CIRCLE_OPTIONS as At, DEFAULT_FORTIFIED_LINE_OPTIONS as B, computeDefaultMidpointPerpendicularPoint as Bn, DEFAULT_BLOCK_ARROW_OPTIONS as Bt, DEFAULT_LANDING_ZONE_OPTIONS as C, line23DrawRule as Cn, RADAR_SEARCH_DOCTRINE_FILL_COLOR as Ct, DEFAULT_WITHDRAW_OPTIONS as D, attackByFireDrawRule as Dn, DEFAULT_GENERIC_RECTANGLE_OPTIONS as Dt, DEFAULT_WITHDRAW_UNDER_PRESSURE_OPTIONS as E, ambushDrawRule as En, DEFAULT_GENERIC_SECTOR_OPTIONS as Et, DEFAULT_PENETRATE_OPTIONS as F, disruptDrawRule as Fn, DEFAULT_BLOCK_MISSION_TASK_OPTIONS as Ft, DEFAULT_LIMIT_OF_ADVANCE_OPTIONS as G, createBaselineFrame as Gn, DEFAULT_ATTACK_BY_FIRE_OPTIONS as Gt, PROBABLE_LINE_OF_DEPLOYMENT_DASH as H, getMidpointPerpendicularSignedDistance as Hn, DEFAULT_CONTAIN_OPTIONS as Ht, DEFAULT_OCCUPY_OPTIONS as I, blockDrawRule as In, DEFAULT_GENERIC_C2_LINE_OPTIONS as It, DEFAULT_FORWARD_EDGE_OF_BATTLE_AREA_OPTIONS as J, haversineDistance as Jn, DEFAULT_ASSEMBLY_AREA_OPTIONS as Jt, DEFAULT_BATTLE_HANDOVER_LINE_OPTIONS as K, calculateMetrics as Kn, DEFAULT_ANTITANK_WALL_OPTIONS as Kt, DEFAULT_MOVEMENT_TO_CONTACT_OPTIONS as L, centerRadiusDrawRule as Ln, DEFAULT_ENGINEER_WORK_LINE_OPTIONS as Lt, DEFAULT_RETIRE_OPTIONS as M, secureDrawRule as Mn, DEFAULT_CANALIZE_OPTIONS as Mt, DEFAULT_RELIEF_IN_PLACE_OPTIONS as N, penetrateDrawRule as Nn, DEFAULT_BYPASS_OPTIONS as Nt, DEFAULT_EVACUATE_OPTIONS as O, dynamicPointDrawRule as On, DEFAULT_GENERIC_POLYGON_OPTIONS as Ot, DEFAULT_REARWARD_PASSAGE_OF_LINES_OPTIONS as P, occupyDrawRule as Pn, DEFAULT_BREACH_OPTIONS as Pt, DEFAULT_BRIDGEHEAD_LINE_OPTIONS as Q, EPSILON as Qn, DEFAULT_AREA_DEFENSE_OPTIONS as Qt, DEFAULT_FRONTAL_ATTACK_OPTIONS as R, containDrawRule as Rn, DEFAULT_LIGHT_LINE_OPTIONS as Rt, DEFAULT_MAIN_ATTACK_OPTIONS as S, line24DrawRule as Sn, DEFAULT_RADAR_SEARCH_DOCTRINE_OPTIONS as St, DEFAULT_TURN_OPTIONS as T, line1DrawRule as Tn, DEFAULT_GENERIC_TEXT_OPTIONS as Tt, DEFAULT_LINE_OF_DEPARTURE_CONTACT_OPTIONS as U, pointOnMidpointPerpendicularAxis as Un, DEFAULT_BATTLE_POSITION_OPTIONS as Ut, DEFAULT_PROBABLE_LINE_OF_DEPLOYMENT_OPTIONS as V, createMidpointPerpendicularDrawRule as Vn, DEFAULT_BLOCK_OPTIONS as Vt, DEFAULT_LINE_OF_DEPARTURE_OPTIONS as W, snapToMidpointPerpendicular as Wn, DEFAULT_ATTACK_HELICOPTER_OPTIONS as Wt, DEFAULT_HOLDING_LINE_OPTIONS as X, sphericalBearing as Xn, DEFAULT_MOBILE_DEFENSE_OPTIONS as Xt, DEFAULT_RELEASE_LINE_OPTIONS as Y, project as Yn, DEFAULT_AREA_OF_OPERATIONS_OPTIONS as Yt, DEFAULT_INFILTRATION_LANE_OPTIONS as Z, unproject as Zn, DEFAULT_ENGAGEMENT_AREA_OPTIONS as Zt, DEFAULT_PICKUP_ZONE_OPTIONS as _, axis1DrawRule as _n, DEFAULT_SUPPORTING_ATTACK_OPTIONS as _t, DEFINITIONS as a, resolveAmplifierPlacement as an, DEFAULT_DISRUPT_OPTIONS as at, DEFAULT_OBSTACLE_BYPASS_DIFFICULT_OPTIONS as b, line27DrawRule as bn, DEFAULT_ISOLATE_OPTIONS as bt, getDefaultOptions as c, DEFAULT_LINE_CAP as cn, createFollowAndAssume as ct, DEFAULT_SUPPORT_BY_FIRE_OPTIONS as d, DEFAULT_STROKE_DASH_CSS_PIXELS as dn, DEFAULT_DEMONSTRATE_OPTIONS as dt, DEFAULT_DIRECTION_OF_MAIN_ATTACK_OPTIONS as en, roundToFixed as er, DEFAULT_FLOT_OPTIONS as et, DEFAULT_RETAIN_OPTIONS as f, DEFAULT_STROKE_WIDTH_CSS_PIXELS as fn, DEFAULT_DELAY_OPTIONS as ft, DEFAULT_PRINCIPAL_DIRECTION_OF_FIRE_OPTIONS as g, rectangleDrawRule as gn, DEFAULT_COUNTERATTACK_OPTIONS as gt, SECONDARY_DIRECTION_OF_FIRE_DASH as h, DEFAULT_AIRBORNE_ATTACK_OPTIONS as hn, DEFAULT_COUNTERATTACK_BY_FIRE_OPTIONS as ht, CONTROL_MEASURE_METADATA as i, normalizeTextAmplifiers as in, DEFAULT_ENCIRCLEMENT_OPTIONS as it, DEFAULT_SCREEN_OPTIONS as j, point12DrawRule as jn, DEFAULT_CLASSIC_ARROW_OPTIONS as jt, DEFAULT_SEIZE_OPTIONS as k, sectorDrawRule as kn, DEFAULT_GENERIC_LINE_OPTIONS as kt, listControlMeasureMetadata as l, DEFAULT_LINE_JOIN as ln, DEFAULT_DISRUPT_MISSION_TASK_OPTIONS as lt, DEFAULT_SECONDARY_DIRECTION_OF_FIRE_OPTIONS as m, DEFAULT_AMBUSH_OPTIONS as mn, DEFAULT_TACTICAL_ARROW_OPTIONS as mt, resolveStyleHints as n, TEXT_AMPLIFIER_FIELDS as nn, DEFAULT_FIX_OPTIONS as nt, getControlMeasureMetadata as o, DEFAULT_LABEL_HEIGHT_CSS_PIXELS as on, DEFAULT_GUARD_OPTIONS as ot, DEFAULT_STRONG_POINT_OPTIONS as p, DEFAULT_SYMBOL_COLOR as pn, DEFAULT_COVER_OPTIONS as pt, DEFAULT_HANDOVER_LINE_OPTIONS as q, computeInitialWidthPoint as qn, DEFAULT_ANTITANK_DITCH_OPTIONS as qt, CONTROL_MEASURE_IDS as r, canonicalTextAmplifierKey as rn, DEFAULT_FINAL_PROTECTIVE_FIRE_OPTIONS as rt, getControlMeasureMetadataByValue as s, DEFAULT_LABEL_SIZE_CLAMP_CSS_PIXELS as sn, createFollowAndSupport as st, renderControlMeasure as t, DEFAULT_DIRECTION_OF_ATTACK_AVIATION_OPTIONS as tn, DEFAULT_FIX_MISSION_TASK_OPTIONS as tt, DEFAULT_TURNING_MOVEMENT_OPTIONS as u, DEFAULT_PORTRAYAL as un, DEFAULT_DISENGAGE_OPTIONS as ut, DEFAULT_OBSTACLE_BYPASS_IMPOSSIBLE_OPTIONS as v, supportByFireDrawRule as vn, DEFAULT_CORDON_AND_SEARCH_OPTIONS as vt, DEFAULT_JOINT_TACTICAL_ACTION_AREA_OPTIONS as w, turnDrawRule as wn, RADAR_SEARCH_DOCTRINE_STROKE_COLOR as wt, DEFAULT_MINEFIELD_OPTIONS as x, line26DrawRule as xn, DEFAULT_CLEAR_OPTIONS as xt, DEFAULT_OBSTACLE_BYPASS_EASY_OPTIONS as y, polyline1DrawRule as yn, DEFAULT_CORDON_AND_KNOCK_OPTIONS as yt, DEFAULT_FORTIFIED_AREA_OPTIONS as z, area27DrawRule as zn, DEFAULT_BOUNDARY_OPTIONS as zt };
|