@orbat-mapper/control-measures 0.17.0 → 0.18.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 +3 -0
- package/dist/{index-DPu0u2KK.d.mts → index-ARQKlooW.d.mts} +164 -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-DGnX8ELk.mjs} +1229 -609
- package/media/cordon-and-knock.svg +1 -0
- package/media/cordon-and-search.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);
|
|
@@ -3015,6 +3131,70 @@ function labelBoxIntersectsSegment(anchor, a, b, textWidth, textHeight, rotation
|
|
|
3015
3131
|
}
|
|
3016
3132
|
return true;
|
|
3017
3133
|
}
|
|
3134
|
+
/**
|
|
3135
|
+
* Splits the polyline `points` into the pieces that fall outside a
|
|
3136
|
+
* `2*halfWidth × 2*halfHeight` label box centered at `anchor` with stored
|
|
3137
|
+
* `rotation` — the clipping counterpart to {@link labelBoxIntersectsSegment},
|
|
3138
|
+
* for ornament geometry (barbs, ticks) that must break around a glyph rather
|
|
3139
|
+
* than merely decide whether it collides.
|
|
3140
|
+
*
|
|
3141
|
+
* Each segment is clipped in the box's own frame with a two-slab parametric
|
|
3142
|
+
* test; pieces that rejoin across a shared vertex are merged so a polyline
|
|
3143
|
+
* crossing the box mid-span stays one line string per surviving run instead of
|
|
3144
|
+
* fragmenting at every interior vertex.
|
|
3145
|
+
*/
|
|
3146
|
+
function clipPolylineOutsideLabelBox(points, anchor, halfWidth, halfHeight, rotation) {
|
|
3147
|
+
const [widthAxis, heightAxis] = labelBoxAxes(rotation);
|
|
3148
|
+
const out = [];
|
|
3149
|
+
const append = (piece) => {
|
|
3150
|
+
if (piece.length < 2 || vecMag(vecSub(piece[1], piece[0])) < 1e-6) return;
|
|
3151
|
+
const previous = out.at(-1);
|
|
3152
|
+
if (previous && vecMag(vecSub(previous.at(-1), piece[0])) < 1e-6) previous.push(...piece.slice(1));
|
|
3153
|
+
else out.push(piece);
|
|
3154
|
+
};
|
|
3155
|
+
for (let i = 1; i < points.length; i++) {
|
|
3156
|
+
const a = points[i - 1];
|
|
3157
|
+
const b = points[i];
|
|
3158
|
+
const delta = vecSub(b, a);
|
|
3159
|
+
const relative = vecSub(a, anchor);
|
|
3160
|
+
const origin = [vecDot(relative, widthAxis), vecDot(relative, heightAxis)];
|
|
3161
|
+
const direction = [vecDot(delta, widthAxis), vecDot(delta, heightAxis)];
|
|
3162
|
+
let enter = 0;
|
|
3163
|
+
let exit = 1;
|
|
3164
|
+
let intersects = true;
|
|
3165
|
+
for (const [coordinate, rate, halfExtent] of [[
|
|
3166
|
+
origin[0],
|
|
3167
|
+
direction[0],
|
|
3168
|
+
halfWidth
|
|
3169
|
+
], [
|
|
3170
|
+
origin[1],
|
|
3171
|
+
direction[1],
|
|
3172
|
+
halfHeight
|
|
3173
|
+
]]) {
|
|
3174
|
+
if (Math.abs(rate) < 1e-6) {
|
|
3175
|
+
if (Math.abs(coordinate) > halfExtent) intersects = false;
|
|
3176
|
+
continue;
|
|
3177
|
+
}
|
|
3178
|
+
const first = (-halfExtent - coordinate) / rate;
|
|
3179
|
+
const second = (halfExtent - coordinate) / rate;
|
|
3180
|
+
enter = Math.max(enter, Math.min(first, second));
|
|
3181
|
+
exit = Math.min(exit, Math.max(first, second));
|
|
3182
|
+
if (enter > exit) intersects = false;
|
|
3183
|
+
}
|
|
3184
|
+
if (!intersects || exit < 0 || enter > 1) {
|
|
3185
|
+
append([a, b]);
|
|
3186
|
+
continue;
|
|
3187
|
+
}
|
|
3188
|
+
const clippedEnter = Math.max(0, enter);
|
|
3189
|
+
const clippedExit = Math.min(1, exit);
|
|
3190
|
+
const pointAt = (t) => t <= 0 ? a : t >= 1 ? b : vecAdd(a, vecScale(delta, t));
|
|
3191
|
+
if (clippedEnter > 0) append([a, pointAt(clippedEnter)]);
|
|
3192
|
+
if (clippedExit < 1) append([pointAt(clippedExit), b]);
|
|
3193
|
+
}
|
|
3194
|
+
return out;
|
|
3195
|
+
}
|
|
3196
|
+
/** Default extra clearance for an intrinsic label that interrupts a stroke. */
|
|
3197
|
+
const DEFAULT_LABEL_KNOCKOUT_PADDING = .3;
|
|
3018
3198
|
/** Field N descriptor shared by Line1 graphics that opt into hostile endpoint markers. */
|
|
3019
3199
|
const HOSTILE_LINE_AMPLIFIER = {
|
|
3020
3200
|
key: "N",
|
|
@@ -3023,12 +3203,12 @@ const HOSTILE_LINE_AMPLIFIER = {
|
|
|
3023
3203
|
placeholder: "ENY",
|
|
3024
3204
|
maxLength: 3
|
|
3025
3205
|
};
|
|
3026
|
-
/** Label-padding control shared by
|
|
3027
|
-
const
|
|
3206
|
+
/** Label-padding control shared by label-bearing graphics. */
|
|
3207
|
+
const LABEL_PADDING_PARAM = {
|
|
3028
3208
|
key: "labelPadding",
|
|
3029
3209
|
presentationTier: "advanced",
|
|
3030
3210
|
label: "Label padding",
|
|
3031
|
-
description: "Extra clearance
|
|
3211
|
+
description: "Extra clearance around labels, as a ratio of the label height",
|
|
3032
3212
|
type: "number",
|
|
3033
3213
|
min: 0,
|
|
3034
3214
|
max: 2,
|
|
@@ -3136,8 +3316,8 @@ function estimatedTextWidth(text, textHeightMeters) {
|
|
|
3136
3316
|
return text.length * LABEL_CHAR_ASPECT_RATIO * textHeightMeters;
|
|
3137
3317
|
}
|
|
3138
3318
|
/** 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);
|
|
3319
|
+
function resolveTextMetrics(text, options, context, style = "regular", fallbackHeightMeters = 50) {
|
|
3320
|
+
const legacyHeight = resolveLabelOffsetMeters(options, 1, fallbackHeightMeters);
|
|
3141
3321
|
const constructionScale = context?.constructionMetersPerCssPixel;
|
|
3142
3322
|
if (!(constructionScale !== void 0 && constructionScale > 0)) return {
|
|
3143
3323
|
width: estimatedTextWidth(text, legacyHeight),
|
|
@@ -3168,6 +3348,28 @@ function resolveTextMetrics(text, options, context, style = "regular") {
|
|
|
3168
3348
|
height: measured.heightCssPixels * constructionScale
|
|
3169
3349
|
};
|
|
3170
3350
|
}
|
|
3351
|
+
/**
|
|
3352
|
+
* Half-length of the knockout required for a label along `direction`.
|
|
3353
|
+
*
|
|
3354
|
+
* The text box itself is always cleared; `labelPadding` adds symmetric clearance expressed in
|
|
3355
|
+
* resolved label heights. Keeping this calculation here gives every generator the same public
|
|
3356
|
+
* label-clearance interface while hiding font measurement and rotated-box projection details.
|
|
3357
|
+
*/
|
|
3358
|
+
function labelKnockoutHalfExtent(text, direction, rotation, options, context, defaultPadding = 0) {
|
|
3359
|
+
const metrics = resolveTextMetrics(text, options, context);
|
|
3360
|
+
const padding = Math.max(0, options.labelPadding ?? defaultPadding) * metrics.height;
|
|
3361
|
+
return labelHalfExtent(direction, metrics.width, metrics.height, rotation) + padding;
|
|
3362
|
+
}
|
|
3363
|
+
/**
|
|
3364
|
+
* {@link labelKnockoutHalfExtent} expressed as a fraction of an arc's angular
|
|
3365
|
+
* sweep, for generators that carve their knockout in arc-progress space rather
|
|
3366
|
+
* than in meters. Clamped to `0.5` so the gap can never exceed the arc.
|
|
3367
|
+
*/
|
|
3368
|
+
function arcKnockoutHalfFraction(halfExtentMeters, radius, sweepAngle) {
|
|
3369
|
+
const arcLength = radius * Math.abs(sweepAngle);
|
|
3370
|
+
if (!(arcLength > 1e-6)) return .5;
|
|
3371
|
+
return Math.min(.5, Math.max(0, halfExtentMeters) / arcLength);
|
|
3372
|
+
}
|
|
3171
3373
|
/** The end-label text for a phase-line naming, or `""` when unnamed. */
|
|
3172
3374
|
function phaseLineLabelText(name, includePrefix) {
|
|
3173
3375
|
if (!name) return "";
|
|
@@ -3632,6 +3834,7 @@ const DIRECTION_OF_ATTACK_AVIATION_METADATA = {
|
|
|
3632
3834
|
},
|
|
3633
3835
|
drawRule: "Line1",
|
|
3634
3836
|
capturesLabelSize: true,
|
|
3837
|
+
labelGeometryUsesResolvedMetrics: ["T"],
|
|
3635
3838
|
params: [
|
|
3636
3839
|
...SMOOTH_LINE_PARAMS,
|
|
3637
3840
|
{
|
|
@@ -3870,6 +4073,7 @@ const DIRECTION_OF_MAIN_ATTACK_METADATA = {
|
|
|
3870
4073
|
},
|
|
3871
4074
|
drawRule: "Line1",
|
|
3872
4075
|
capturesLabelSize: true,
|
|
4076
|
+
labelGeometryUsesResolvedMetrics: ["T"],
|
|
3873
4077
|
params: [
|
|
3874
4078
|
...SMOOTH_LINE_PARAMS,
|
|
3875
4079
|
...DIRECTION_OF_ATTACK_PARAMS,
|
|
@@ -3938,6 +4142,7 @@ const DIRECTION_OF_SUPPORTING_ATTACK_METADATA = {
|
|
|
3938
4142
|
},
|
|
3939
4143
|
drawRule: "Line1",
|
|
3940
4144
|
capturesLabelSize: true,
|
|
4145
|
+
labelGeometryUsesResolvedMetrics: ["T"],
|
|
3941
4146
|
params: [...SMOOTH_LINE_PARAMS, ...DIRECTION_OF_ATTACK_PARAMS],
|
|
3942
4147
|
textAmplifiers: DIRECTION_OF_ATTACK_TEXT_AMPLIFIERS
|
|
3943
4148
|
};
|
|
@@ -4568,6 +4773,7 @@ const AREA_METADATA = {
|
|
|
4568
4773
|
},
|
|
4569
4774
|
drawRule: "Area1",
|
|
4570
4775
|
capturesLabelSize: true,
|
|
4776
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
4571
4777
|
params: LABELED_AREA_PARAMS,
|
|
4572
4778
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
4573
4779
|
};
|
|
@@ -4831,6 +5037,7 @@ const ENGAGEMENT_AREA_METADATA = {
|
|
|
4831
5037
|
},
|
|
4832
5038
|
drawRule: "Area1",
|
|
4833
5039
|
capturesLabelSize: true,
|
|
5040
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
4834
5041
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
4835
5042
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
4836
5043
|
};
|
|
@@ -4897,6 +5104,26 @@ function sampleProjectedArc(center, startRadius, bulgeRadius, start, end, resolu
|
|
|
4897
5104
|
return unproject(point[0], point[1]);
|
|
4898
5105
|
});
|
|
4899
5106
|
}
|
|
5107
|
+
/**
|
|
5108
|
+
* Samples the 180-degree arc standing on the diameter `start`→`end`, bulging
|
|
5109
|
+
* toward `bulgeDir`. Shared by the mission tasks whose shaft turns back on
|
|
5110
|
+
* itself (Delay, Relief in Place, Demonstrate); `requestedSegments` is the
|
|
5111
|
+
* segment count for the half-turn.
|
|
5112
|
+
*/
|
|
5113
|
+
function createSemicircleArc(start, end, bulgeDir, requestedSegments) {
|
|
5114
|
+
const diameterVector = vecSub(end, start);
|
|
5115
|
+
const diameter = vecMag(diameterVector);
|
|
5116
|
+
if (diameter < 1e-6) return [unproject(start[0], start[1])];
|
|
5117
|
+
const radius = diameter / 2;
|
|
5118
|
+
const center = vecAdd(start, vecScale(diameterVector, .5));
|
|
5119
|
+
const startRadius = vecScale(diameterVector, -.5);
|
|
5120
|
+
const bulgeRadius = vecScale(bulgeDir, radius);
|
|
5121
|
+
const segments = Math.max(4, Math.round(requestedSegments));
|
|
5122
|
+
return Array.from({ length: segments + 1 }, (_, index) => {
|
|
5123
|
+
const point = arcPointAt(center, startRadius, bulgeRadius, index / segments * Math.PI);
|
|
5124
|
+
return unproject(point[0], point[1]);
|
|
5125
|
+
});
|
|
5126
|
+
}
|
|
4900
5127
|
//#endregion
|
|
4901
5128
|
//#region src/generators/cm15-maneuver-areas/mobile-defense.ts
|
|
4902
5129
|
const DEFAULT_MOBILE_DEFENSE_OPTIONS = { labelPadding: 0 };
|
|
@@ -4922,6 +5149,7 @@ const MOBILE_DEFENSE_METADATA = {
|
|
|
4922
5149
|
},
|
|
4923
5150
|
drawRule: "Area27",
|
|
4924
5151
|
capturesLabelSize: true,
|
|
5152
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
4925
5153
|
params: [{
|
|
4926
5154
|
key: "labelPadding",
|
|
4927
5155
|
presentationTier: "advanced",
|
|
@@ -5098,6 +5326,7 @@ const AREA_OF_OPERATIONS_METADATA = {
|
|
|
5098
5326
|
},
|
|
5099
5327
|
drawRule: "Area1",
|
|
5100
5328
|
capturesLabelSize: true,
|
|
5329
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
5101
5330
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
5102
5331
|
textAmplifiers: AREA_TEXT_AMPLIFIERS
|
|
5103
5332
|
};
|
|
@@ -5160,6 +5389,7 @@ const NAMED_AREA_OF_INTEREST_METADATA = {
|
|
|
5160
5389
|
},
|
|
5161
5390
|
drawRule: "Area1",
|
|
5162
5391
|
capturesLabelSize: true,
|
|
5392
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
5163
5393
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
5164
5394
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
5165
5395
|
};
|
|
@@ -5219,6 +5449,7 @@ const TARGET_AREA_OF_INTEREST_METADATA = {
|
|
|
5219
5449
|
},
|
|
5220
5450
|
drawRule: "Area1",
|
|
5221
5451
|
capturesLabelSize: true,
|
|
5452
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
5222
5453
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
5223
5454
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
5224
5455
|
};
|
|
@@ -5287,6 +5518,7 @@ const AIRFIELD_ZONE_METADATA = {
|
|
|
5287
5518
|
},
|
|
5288
5519
|
drawRule: "Area1",
|
|
5289
5520
|
capturesLabelSize: true,
|
|
5521
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
5290
5522
|
params: [
|
|
5291
5523
|
...AREA_SMOOTH_PARAMS,
|
|
5292
5524
|
{
|
|
@@ -5471,6 +5703,7 @@ const BASE_CAMP_METADATA = {
|
|
|
5471
5703
|
},
|
|
5472
5704
|
drawRule: "Area1",
|
|
5473
5705
|
capturesLabelSize: true,
|
|
5706
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
5474
5707
|
params: PREFIXED_ECHELON_AREA_PARAMS,
|
|
5475
5708
|
textAmplifiers: AREA_TEXT_AMPLIFIERS
|
|
5476
5709
|
};
|
|
@@ -5529,6 +5762,7 @@ const GUERRILLA_BASE_METADATA = {
|
|
|
5529
5762
|
},
|
|
5530
5763
|
drawRule: "Area1",
|
|
5531
5764
|
capturesLabelSize: true,
|
|
5765
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
5532
5766
|
params: PREFIXED_ECHELON_AREA_PARAMS,
|
|
5533
5767
|
textAmplifiers: AREA_TEXT_AMPLIFIERS
|
|
5534
5768
|
};
|
|
@@ -5592,6 +5826,7 @@ const GENERIC_C2_AREA_METADATA = {
|
|
|
5592
5826
|
},
|
|
5593
5827
|
drawRule: "Area1",
|
|
5594
5828
|
capturesLabelSize: true,
|
|
5829
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
5595
5830
|
params: LABELED_AREA_PARAMS,
|
|
5596
5831
|
textAmplifiers: AREA_TEXT_AMPLIFIERS
|
|
5597
5832
|
};
|
|
@@ -5647,6 +5882,7 @@ const ASSEMBLY_AREA_METADATA = {
|
|
|
5647
5882
|
},
|
|
5648
5883
|
drawRule: "Area1",
|
|
5649
5884
|
capturesLabelSize: true,
|
|
5885
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
5650
5886
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
5651
5887
|
textAmplifiers: AREA_TEXT_AMPLIFIERS
|
|
5652
5888
|
};
|
|
@@ -5698,7 +5934,7 @@ const FORTIFIED_PARAMS = [
|
|
|
5698
5934
|
step: 1,
|
|
5699
5935
|
unit: "m"
|
|
5700
5936
|
},
|
|
5701
|
-
|
|
5937
|
+
LABEL_PADDING_PARAM
|
|
5702
5938
|
];
|
|
5703
5939
|
const ANTITANK_DITCH_PARAMS = [
|
|
5704
5940
|
...SMOOTH_LINE_PARAMS,
|
|
@@ -5733,7 +5969,7 @@ const ANTITANK_DITCH_PARAMS = [
|
|
|
5733
5969
|
step: .05,
|
|
5734
5970
|
unit: "x"
|
|
5735
5971
|
},
|
|
5736
|
-
|
|
5972
|
+
LABEL_PADDING_PARAM
|
|
5737
5973
|
];
|
|
5738
5974
|
const ANTITANK_WALL_PARAMS = [...ANTITANK_DITCH_PARAMS, {
|
|
5739
5975
|
key: "toothSpacingRatio",
|
|
@@ -5776,6 +6012,7 @@ const ANTITANK_DITCH_UNDER_CONSTRUCTION_METADATA = {
|
|
|
5776
6012
|
},
|
|
5777
6013
|
drawRule: "Line1",
|
|
5778
6014
|
capturesLabelSize: true,
|
|
6015
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
5779
6016
|
params: ANTITANK_DITCH_PARAMS,
|
|
5780
6017
|
textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
|
|
5781
6018
|
};
|
|
@@ -5797,6 +6034,7 @@ const ANTITANK_DITCH_COMPLETED_METADATA = {
|
|
|
5797
6034
|
},
|
|
5798
6035
|
drawRule: "Line1",
|
|
5799
6036
|
capturesLabelSize: true,
|
|
6037
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
5800
6038
|
params: ANTITANK_DITCH_PARAMS,
|
|
5801
6039
|
textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
|
|
5802
6040
|
};
|
|
@@ -5929,6 +6167,7 @@ const ANTITANK_WALL_METADATA = {
|
|
|
5929
6167
|
},
|
|
5930
6168
|
drawRule: "Line1",
|
|
5931
6169
|
capturesLabelSize: true,
|
|
6170
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
5932
6171
|
params: ANTITANK_WALL_PARAMS,
|
|
5933
6172
|
textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
|
|
5934
6173
|
};
|
|
@@ -6395,6 +6634,7 @@ const BATTLE_POSITION_METADATA = {
|
|
|
6395
6634
|
},
|
|
6396
6635
|
drawRule: "Area1",
|
|
6397
6636
|
capturesLabelSize: true,
|
|
6637
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
6398
6638
|
params: ECHELON_AREA_PARAMS,
|
|
6399
6639
|
textAmplifiers: AREA_UNIT_DESIGNATION_HOSTILE_AMPLIFIERS
|
|
6400
6640
|
};
|
|
@@ -6538,6 +6778,7 @@ const BATTLE_POSITION_PREPARED_METADATA = {
|
|
|
6538
6778
|
},
|
|
6539
6779
|
drawRule: "Area1",
|
|
6540
6780
|
capturesLabelSize: true,
|
|
6781
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
6541
6782
|
params: PREFIXED_ECHELON_AREA_PARAMS,
|
|
6542
6783
|
textAmplifiers: AREA_UNIT_DESIGNATION_HOSTILE_AMPLIFIERS
|
|
6543
6784
|
};
|
|
@@ -6606,6 +6847,7 @@ const CONTAIN_METADATA = {
|
|
|
6606
6847
|
},
|
|
6607
6848
|
drawRule: "Area5",
|
|
6608
6849
|
capturesLabelSize: true,
|
|
6850
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
6609
6851
|
textAmplifiers: [AREA_HOSTILE_AMPLIFIER],
|
|
6610
6852
|
params: [{
|
|
6611
6853
|
key: "labelPadding",
|
|
@@ -7501,6 +7743,7 @@ const BOUNDARY_METADATA = {
|
|
|
7501
7743
|
text: true
|
|
7502
7744
|
},
|
|
7503
7745
|
drawRule: "Line1",
|
|
7746
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
7504
7747
|
params: [
|
|
7505
7748
|
{
|
|
7506
7749
|
key: "smooth",
|
|
@@ -7919,6 +8162,7 @@ const GENERIC_C2_LINE_METADATA = {
|
|
|
7919
8162
|
},
|
|
7920
8163
|
drawRule: "Line1",
|
|
7921
8164
|
capturesLabelSize: true,
|
|
8165
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
7922
8166
|
params: [
|
|
7923
8167
|
...SMOOTH_LINE_PARAMS,
|
|
7924
8168
|
...echelonLineParams("line"),
|
|
@@ -7940,7 +8184,7 @@ const GENERIC_C2_LINE_METADATA = {
|
|
|
7940
8184
|
type: "boolean",
|
|
7941
8185
|
visibleWhen: (opts) => Boolean(String(opts.phaseLineName ?? "").trim())
|
|
7942
8186
|
},
|
|
7943
|
-
|
|
8187
|
+
LABEL_PADDING_PARAM
|
|
7944
8188
|
],
|
|
7945
8189
|
textAmplifiers: [
|
|
7946
8190
|
{
|
|
@@ -8167,7 +8411,7 @@ function createLabelFeature(point, text, rotation, labelSize = {}) {
|
|
|
8167
8411
|
* @param config - Per-measure end caps, label glyph, and sizing
|
|
8168
8412
|
* @returns GeoJSON FeatureCollection containing MultiLineString and Point features
|
|
8169
8413
|
*/
|
|
8170
|
-
function createBracketSymbol(coordinates, config) {
|
|
8414
|
+
function createBracketSymbol(coordinates, config, context) {
|
|
8171
8415
|
const [c1, c2, c3] = coordinates;
|
|
8172
8416
|
const p1 = project(c1[0], c1[1]);
|
|
8173
8417
|
const p2 = project(c2[0], c2[1]);
|
|
@@ -8187,7 +8431,10 @@ function createBracketSymbol(coordinates, config) {
|
|
|
8187
8431
|
const rearMidpoint = vecAdd(frontMidpoint, vecScale(perpDir, perpDistance));
|
|
8188
8432
|
const rearTop = vecSub(rearMidpoint, halfFront);
|
|
8189
8433
|
const rearBottom = vecAdd(rearMidpoint, halfFront);
|
|
8190
|
-
const
|
|
8434
|
+
const rearLineRotation = Math.atan2(dirFront[1], dirFront[0]);
|
|
8435
|
+
const renderedLabelRotation = Math.PI - (rearLineRotation - Math.PI / 2);
|
|
8436
|
+
const labelRotation = normalizeRadians(Math.PI - keepTextLeftToRight(renderedLabelRotation));
|
|
8437
|
+
const gapHalf = Math.min(frontLength / 2, labelKnockoutHalfExtent(config.labelText, dirFront, labelRotation, config.labelOptions ?? {}, context));
|
|
8191
8438
|
const gapPt1 = vecAdd(rearMidpoint, vecScale(dirFront, -gapHalf));
|
|
8192
8439
|
const gapPt2 = vecAdd(rearMidpoint, vecScale(dirFront, gapHalf));
|
|
8193
8440
|
const [topCap, bottomCap] = config.buildEndCaps({
|
|
@@ -8198,39 +8445,35 @@ function createBracketSymbol(coordinates, config) {
|
|
|
8198
8445
|
frontLength,
|
|
8199
8446
|
rearTop
|
|
8200
8447
|
});
|
|
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
8448
|
return {
|
|
8226
8449
|
type: "FeatureCollection",
|
|
8227
|
-
features: [
|
|
8450
|
+
features: [{
|
|
8451
|
+
type: "Feature",
|
|
8452
|
+
properties: { part: config.part },
|
|
8453
|
+
geometry: {
|
|
8454
|
+
type: "MultiLineString",
|
|
8455
|
+
coordinates: [
|
|
8456
|
+
topCap,
|
|
8457
|
+
[
|
|
8458
|
+
unproject(p1[0], p1[1]),
|
|
8459
|
+
unproject(rearTop[0], rearTop[1]),
|
|
8460
|
+
unproject(gapPt1[0], gapPt1[1])
|
|
8461
|
+
],
|
|
8462
|
+
[
|
|
8463
|
+
unproject(gapPt2[0], gapPt2[1]),
|
|
8464
|
+
unproject(rearBottom[0], rearBottom[1]),
|
|
8465
|
+
unproject(p2[0], p2[1])
|
|
8466
|
+
],
|
|
8467
|
+
bottomCap
|
|
8468
|
+
]
|
|
8469
|
+
}
|
|
8470
|
+
}, createLabelFeature(rearMidpoint, config.labelText, labelRotation, config.labelOptions)]
|
|
8228
8471
|
};
|
|
8229
8472
|
}
|
|
8230
8473
|
//#endregion
|
|
8231
8474
|
//#region src/generators/cm34-mission-tasks/block.ts
|
|
8232
8475
|
const DEFAULT_STEM_RATIO = 1.5;
|
|
8233
|
-
const DEFAULT_BLOCK_MISSION_TASK_OPTIONS = {
|
|
8476
|
+
const DEFAULT_BLOCK_MISSION_TASK_OPTIONS = { labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING };
|
|
8234
8477
|
const BLOCK_MISSION_TASK_METADATA = {
|
|
8235
8478
|
id: "block-mission-task",
|
|
8236
8479
|
name: "Block",
|
|
@@ -8248,16 +8491,8 @@ const BLOCK_MISSION_TASK_METADATA = {
|
|
|
8248
8491
|
},
|
|
8249
8492
|
drawRule: "Area11",
|
|
8250
8493
|
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
|
-
}]
|
|
8494
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
8495
|
+
params: [LABEL_PADDING_PARAM]
|
|
8261
8496
|
};
|
|
8262
8497
|
/**
|
|
8263
8498
|
* Generates a GeoJSON FeatureCollection for a BLOCK mission task symbol (340100).
|
|
@@ -8269,8 +8504,7 @@ const BLOCK_MISSION_TASK_METADATA = {
|
|
|
8269
8504
|
* @param options - Configuration options
|
|
8270
8505
|
* @returns GeoJSON FeatureCollection containing MultiLineString and Point features
|
|
8271
8506
|
*/
|
|
8272
|
-
function createBlockMissionTaskSymbol(coordinates, options = {}) {
|
|
8273
|
-
const labelGapRatio = options.labelGapRatio ?? DEFAULT_BLOCK_MISSION_TASK_OPTIONS.labelGapRatio;
|
|
8507
|
+
function createBlockMissionTaskSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
8274
8508
|
const p1 = project(coordinates[0][0], coordinates[0][1]);
|
|
8275
8509
|
const p2 = project(coordinates[1][0], coordinates[1][1]);
|
|
8276
8510
|
const frame = createProjectedBaselineFrame(p1, p2, {
|
|
@@ -8287,12 +8521,12 @@ function createBlockMissionTaskSymbol(coordinates, options = {}) {
|
|
|
8287
8521
|
const pStemEnd = vecAdd(mid, vecScale(frame.normal, stemDistance));
|
|
8288
8522
|
const labelPoint = vecAdd(mid, vecScale(frame.normal, stemDistance / 2));
|
|
8289
8523
|
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
8524
|
const shaftRotation = Math.atan2(frame.normal[1], frame.normal[0]);
|
|
8294
8525
|
const renderedLabelRotation = Math.PI - shaftRotation;
|
|
8295
8526
|
const labelRotation = normalizeRadians(Math.PI - keepTextLeftToRight(renderedLabelRotation));
|
|
8527
|
+
const gapHalf = Math.min(labelKnockoutHalfExtent("B", stemDirection, labelRotation, options, context, DEFAULT_BLOCK_MISSION_TASK_OPTIONS.labelPadding), Math.abs(stemDistance) / 2);
|
|
8528
|
+
const gapStart = vecAdd(labelPoint, vecScale(stemDirection, -gapHalf));
|
|
8529
|
+
const gapEnd = vecAdd(labelPoint, vecScale(stemDirection, gapHalf));
|
|
8296
8530
|
return {
|
|
8297
8531
|
type: "FeatureCollection",
|
|
8298
8532
|
features: [{
|
|
@@ -8327,7 +8561,7 @@ const BLOCK_MISSION_TASK = defineControlMeasure({
|
|
|
8327
8561
|
const DEFAULT_BREACH_OPTIONS = {
|
|
8328
8562
|
tickLengthRatio: .15,
|
|
8329
8563
|
tickAngle: 45,
|
|
8330
|
-
|
|
8564
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING
|
|
8331
8565
|
};
|
|
8332
8566
|
const BREACH_METADATA = {
|
|
8333
8567
|
id: "breach",
|
|
@@ -8347,6 +8581,7 @@ const BREACH_METADATA = {
|
|
|
8347
8581
|
},
|
|
8348
8582
|
drawRule: "Point12",
|
|
8349
8583
|
capturesLabelSize: true,
|
|
8584
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
8350
8585
|
params: [
|
|
8351
8586
|
{
|
|
8352
8587
|
key: "tickLengthRatio",
|
|
@@ -8368,16 +8603,7 @@ const BREACH_METADATA = {
|
|
|
8368
8603
|
max: 90,
|
|
8369
8604
|
step: 1
|
|
8370
8605
|
},
|
|
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
|
-
}
|
|
8606
|
+
LABEL_PADDING_PARAM
|
|
8381
8607
|
]
|
|
8382
8608
|
};
|
|
8383
8609
|
/**
|
|
@@ -8391,16 +8617,15 @@ const BREACH_METADATA = {
|
|
|
8391
8617
|
* @param options - Configuration options
|
|
8392
8618
|
* @returns GeoJSON FeatureCollection containing MultiLineString and Point features
|
|
8393
8619
|
*/
|
|
8394
|
-
function createBreachSymbol(coordinates, options = {}) {
|
|
8620
|
+
function createBreachSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
8395
8621
|
const tickLengthRatio = options.tickLengthRatio ?? DEFAULT_BREACH_OPTIONS.tickLengthRatio;
|
|
8396
8622
|
const tickAngle = options.tickAngle ?? DEFAULT_BREACH_OPTIONS.tickAngle;
|
|
8397
8623
|
return createBracketSymbol(coordinates, {
|
|
8398
8624
|
part: "breach",
|
|
8399
8625
|
labelText: "B",
|
|
8400
|
-
|
|
8401
|
-
|
|
8402
|
-
|
|
8403
|
-
labelSizePixels: options.labelSizePixels
|
|
8626
|
+
labelOptions: {
|
|
8627
|
+
...options,
|
|
8628
|
+
labelPadding: options.labelPadding ?? DEFAULT_BREACH_OPTIONS.labelPadding
|
|
8404
8629
|
},
|
|
8405
8630
|
buildEndCaps: ({ p1, p2, dirFront, perpDir, frontLength }) => {
|
|
8406
8631
|
const halfTick = frontLength * tickLengthRatio / 2;
|
|
@@ -8416,7 +8641,7 @@ function createBreachSymbol(coordinates, options = {}) {
|
|
|
8416
8641
|
const tick2End = vecAdd(p2, vecScale(tick2Dir, halfTick));
|
|
8417
8642
|
return [[unproject(tick1Start[0], tick1Start[1]), unproject(tick1End[0], tick1End[1])], [unproject(tick2Start[0], tick2Start[1]), unproject(tick2End[0], tick2End[1])]];
|
|
8418
8643
|
}
|
|
8419
|
-
});
|
|
8644
|
+
}, context);
|
|
8420
8645
|
}
|
|
8421
8646
|
const BREACH = defineControlMeasure({
|
|
8422
8647
|
metadata: BREACH_METADATA,
|
|
@@ -8431,7 +8656,7 @@ const BREACH = defineControlMeasure({
|
|
|
8431
8656
|
*/
|
|
8432
8657
|
const DEFAULT_BYPASS_OPTIONS = {
|
|
8433
8658
|
arrowheadLengthRatio: .12,
|
|
8434
|
-
|
|
8659
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING
|
|
8435
8660
|
};
|
|
8436
8661
|
const BYPASS_METADATA = {
|
|
8437
8662
|
id: "bypass",
|
|
@@ -8451,6 +8676,7 @@ const BYPASS_METADATA = {
|
|
|
8451
8676
|
},
|
|
8452
8677
|
drawRule: "Point12",
|
|
8453
8678
|
capturesLabelSize: true,
|
|
8679
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
8454
8680
|
params: [{
|
|
8455
8681
|
key: "arrowheadLengthRatio",
|
|
8456
8682
|
presentationTier: "advanced",
|
|
@@ -8460,16 +8686,7 @@ const BYPASS_METADATA = {
|
|
|
8460
8686
|
min: .01,
|
|
8461
8687
|
max: 1,
|
|
8462
8688
|
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
|
-
}]
|
|
8689
|
+
}, LABEL_PADDING_PARAM]
|
|
8473
8690
|
};
|
|
8474
8691
|
/**
|
|
8475
8692
|
* Generates a GeoJSON FeatureCollection for a BYPASS mission task symbol (340300).
|
|
@@ -8482,22 +8699,21 @@ const BYPASS_METADATA = {
|
|
|
8482
8699
|
* @param options - Configuration options
|
|
8483
8700
|
* @returns GeoJSON FeatureCollection containing MultiLineString and Point features
|
|
8484
8701
|
*/
|
|
8485
|
-
function createBypassSymbol(coordinates, options = {}) {
|
|
8702
|
+
function createBypassSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
8486
8703
|
const arrowheadLengthRatio = options.arrowheadLengthRatio ?? DEFAULT_BYPASS_OPTIONS.arrowheadLengthRatio;
|
|
8487
8704
|
return createBracketSymbol(coordinates, {
|
|
8488
8705
|
part: "bypass",
|
|
8489
8706
|
labelText: "B",
|
|
8490
|
-
|
|
8491
|
-
|
|
8492
|
-
|
|
8493
|
-
labelSizePixels: options.labelSizePixels
|
|
8707
|
+
labelOptions: {
|
|
8708
|
+
...options,
|
|
8709
|
+
labelPadding: options.labelPadding ?? DEFAULT_BYPASS_OPTIONS.labelPadding
|
|
8494
8710
|
},
|
|
8495
8711
|
buildEndCaps: ({ p1, p2, frontLength, rearTop }) => {
|
|
8496
8712
|
const arrowheadLength = frontLength * arrowheadLengthRatio;
|
|
8497
8713
|
const arrowDir = vecNorm(vecSub(p1, rearTop));
|
|
8498
8714
|
return [createArrowHead$1(p1, arrowDir, arrowheadLength, arrowheadLength), createArrowHead$1(p2, arrowDir, arrowheadLength, arrowheadLength)];
|
|
8499
8715
|
}
|
|
8500
|
-
});
|
|
8716
|
+
}, context);
|
|
8501
8717
|
}
|
|
8502
8718
|
const BYPASS = defineControlMeasure({
|
|
8503
8719
|
metadata: BYPASS_METADATA,
|
|
@@ -8513,7 +8729,7 @@ const BYPASS = defineControlMeasure({
|
|
|
8513
8729
|
const DEFAULT_CANALIZE_OPTIONS = {
|
|
8514
8730
|
tickLengthRatio: .15,
|
|
8515
8731
|
tickAngle: 45,
|
|
8516
|
-
|
|
8732
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING
|
|
8517
8733
|
};
|
|
8518
8734
|
const CANALIZE_METADATA = {
|
|
8519
8735
|
id: "canalize",
|
|
@@ -8533,6 +8749,7 @@ const CANALIZE_METADATA = {
|
|
|
8533
8749
|
},
|
|
8534
8750
|
drawRule: "Point12",
|
|
8535
8751
|
capturesLabelSize: true,
|
|
8752
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
8536
8753
|
params: [
|
|
8537
8754
|
{
|
|
8538
8755
|
key: "tickLengthRatio",
|
|
@@ -8554,16 +8771,7 @@ const CANALIZE_METADATA = {
|
|
|
8554
8771
|
max: 90,
|
|
8555
8772
|
step: 1
|
|
8556
8773
|
},
|
|
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
|
-
}
|
|
8774
|
+
LABEL_PADDING_PARAM
|
|
8567
8775
|
]
|
|
8568
8776
|
};
|
|
8569
8777
|
/**
|
|
@@ -8577,16 +8785,15 @@ const CANALIZE_METADATA = {
|
|
|
8577
8785
|
* @param options - Configuration options
|
|
8578
8786
|
* @returns GeoJSON FeatureCollection containing MultiLineString and Point features
|
|
8579
8787
|
*/
|
|
8580
|
-
function createCanalizeSymbol(coordinates, options = {}) {
|
|
8788
|
+
function createCanalizeSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
8581
8789
|
const tickLengthRatio = options.tickLengthRatio ?? DEFAULT_CANALIZE_OPTIONS.tickLengthRatio;
|
|
8582
8790
|
const tickAngle = options.tickAngle ?? DEFAULT_CANALIZE_OPTIONS.tickAngle;
|
|
8583
8791
|
return createBracketSymbol(coordinates, {
|
|
8584
8792
|
part: "canalize",
|
|
8585
8793
|
labelText: "C",
|
|
8586
|
-
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
labelSizePixels: options.labelSizePixels
|
|
8794
|
+
labelOptions: {
|
|
8795
|
+
...options,
|
|
8796
|
+
labelPadding: options.labelPadding ?? DEFAULT_CANALIZE_OPTIONS.labelPadding
|
|
8590
8797
|
},
|
|
8591
8798
|
buildEndCaps: ({ p1, p2, dirFront, perpDir, frontLength }) => {
|
|
8592
8799
|
const halfTick = frontLength * tickLengthRatio / 2;
|
|
@@ -8601,7 +8808,7 @@ function createCanalizeSymbol(coordinates, options = {}) {
|
|
|
8601
8808
|
const tick2End = vecAdd(p2, vecScale(tick2Dir, halfTick));
|
|
8602
8809
|
return [[unproject(tick1Start[0], tick1Start[1]), unproject(tick1End[0], tick1End[1])], [unproject(tick2Start[0], tick2Start[1]), unproject(tick2End[0], tick2End[1])]];
|
|
8603
8810
|
}
|
|
8604
|
-
});
|
|
8811
|
+
}, context);
|
|
8605
8812
|
}
|
|
8606
8813
|
const CANALIZE = defineControlMeasure({
|
|
8607
8814
|
metadata: CANALIZE_METADATA,
|
|
@@ -9670,7 +9877,7 @@ const DEFAULT_CLEAR_OPTIONS = {
|
|
|
9670
9877
|
arrowInsetRatio: .15,
|
|
9671
9878
|
arrowheadLengthRatio: .15,
|
|
9672
9879
|
arrowheadWidthRatio: .2,
|
|
9673
|
-
|
|
9880
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING
|
|
9674
9881
|
};
|
|
9675
9882
|
const CLEAR_METADATA = {
|
|
9676
9883
|
id: "clear",
|
|
@@ -9690,6 +9897,7 @@ const CLEAR_METADATA = {
|
|
|
9690
9897
|
},
|
|
9691
9898
|
drawRule: "Line23",
|
|
9692
9899
|
capturesLabelSize: true,
|
|
9900
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
9693
9901
|
params: [
|
|
9694
9902
|
{
|
|
9695
9903
|
key: "arrowInsetRatio",
|
|
@@ -9721,16 +9929,7 @@ const CLEAR_METADATA = {
|
|
|
9721
9929
|
max: 1,
|
|
9722
9930
|
step: .01
|
|
9723
9931
|
},
|
|
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
|
-
}
|
|
9932
|
+
LABEL_PADDING_PARAM
|
|
9734
9933
|
]
|
|
9735
9934
|
};
|
|
9736
9935
|
/**
|
|
@@ -9754,11 +9953,10 @@ const CLEAR_METADATA = {
|
|
|
9754
9953
|
* @param options - Configuration options
|
|
9755
9954
|
* @returns GeoJSON FeatureCollection containing MultiLineString and Point features
|
|
9756
9955
|
*/
|
|
9757
|
-
function createClearSymbol(coordinates, options = {}) {
|
|
9956
|
+
function createClearSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
9758
9957
|
const arrowInsetRatio = Math.min(.49, Math.max(0, options.arrowInsetRatio ?? DEFAULT_CLEAR_OPTIONS.arrowInsetRatio));
|
|
9759
9958
|
const arrowheadLengthRatio = options.arrowheadLengthRatio ?? DEFAULT_CLEAR_OPTIONS.arrowheadLengthRatio;
|
|
9760
9959
|
const arrowheadWidthRatio = options.arrowheadWidthRatio ?? DEFAULT_CLEAR_OPTIONS.arrowheadWidthRatio;
|
|
9761
|
-
const labelGapRatio = options.labelGapRatio ?? DEFAULT_CLEAR_OPTIONS.labelGapRatio;
|
|
9762
9960
|
const [c1, c2, c3] = coordinates;
|
|
9763
9961
|
const p1 = project(c1[0], c1[1]);
|
|
9764
9962
|
const p2 = project(c2[0], c2[1]);
|
|
@@ -9777,6 +9975,9 @@ function createClearSymbol(coordinates, options = {}) {
|
|
|
9777
9975
|
const arrowDir = vecScale(perpToRear, -1);
|
|
9778
9976
|
const arrowheadLength = height * arrowheadLengthRatio;
|
|
9779
9977
|
const arrowheadWidth = height * arrowheadWidthRatio;
|
|
9978
|
+
const lineRotation = Math.atan2(dirLine[1], dirLine[0]);
|
|
9979
|
+
const renderedLabelRotation = Math.PI - (lineRotation - Math.PI / 2);
|
|
9980
|
+
const labelRotation = normalizeRadians(Math.PI - keepTextLeftToRight(renderedLabelRotation));
|
|
9780
9981
|
const tipAt = (t) => vecAdd(p1, vecScale(dirLine, t * height));
|
|
9781
9982
|
const lines = [[unproject(p1[0], p1[1]), unproject(p2[0], p2[1])]];
|
|
9782
9983
|
const fullTs = [
|
|
@@ -9790,7 +9991,7 @@ function createClearSymbol(coordinates, options = {}) {
|
|
|
9790
9991
|
const rearEnd = vecAdd(tip, vecScale(perpToRear, shaftLength));
|
|
9791
9992
|
if (i === labelIndex && shaftLength > 1e-6) {
|
|
9792
9993
|
const labelCenter = vecAdd(tip, vecScale(perpToRear, shaftLength * .5));
|
|
9793
|
-
const gapHalf = shaftLength
|
|
9994
|
+
const gapHalf = Math.min(shaftLength / 2, labelKnockoutHalfExtent("C", perpToRear, labelRotation, options, context, DEFAULT_CLEAR_OPTIONS.labelPadding));
|
|
9794
9995
|
const gapRear = vecAdd(labelCenter, vecScale(perpToRear, gapHalf));
|
|
9795
9996
|
const gapTip = vecAdd(labelCenter, vecScale(perpToRear, -gapHalf));
|
|
9796
9997
|
lines.push([unproject(rearEnd[0], rearEnd[1]), unproject(gapRear[0], gapRear[1])]);
|
|
@@ -9806,9 +10007,6 @@ function createClearSymbol(coordinates, options = {}) {
|
|
|
9806
10007
|
coordinates: lines
|
|
9807
10008
|
}
|
|
9808
10009
|
};
|
|
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
10010
|
const labelTip = tipAt(fullTs[labelIndex]);
|
|
9813
10011
|
return {
|
|
9814
10012
|
type: "FeatureCollection",
|
|
@@ -9825,61 +10023,311 @@ const CLEAR = defineControlMeasure({
|
|
|
9825
10023
|
rule: line23DrawRule
|
|
9826
10024
|
});
|
|
9827
10025
|
//#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
|
|
10026
|
+
//#region src/generators/cm34-mission-tasks/isolate.ts
|
|
10027
|
+
/**
|
|
10028
|
+
* Default options for the ISOLATE symbol.
|
|
10029
|
+
*/
|
|
10030
|
+
const DEFAULT_ISOLATE_OPTIONS = {
|
|
10031
|
+
openingAngle: 30,
|
|
10032
|
+
barbCount: 9,
|
|
10033
|
+
barbLengthRatio: .18,
|
|
10034
|
+
labelPadding: .2
|
|
9841
10035
|
};
|
|
9842
|
-
|
|
9843
|
-
|
|
9844
|
-
|
|
9845
|
-
|
|
9846
|
-
|
|
9847
|
-
|
|
9848
|
-
|
|
9849
|
-
|
|
9850
|
-
|
|
9851
|
-
|
|
9852
|
-
|
|
9853
|
-
|
|
9854
|
-
|
|
9855
|
-
|
|
9856
|
-
|
|
9857
|
-
|
|
9858
|
-
|
|
9859
|
-
|
|
10036
|
+
const ISOLATE_VARIANT_PARAMS = [
|
|
10037
|
+
{
|
|
10038
|
+
key: "openingAngle",
|
|
10039
|
+
presentationTier: "advanced",
|
|
10040
|
+
label: "Opening",
|
|
10041
|
+
description: "Angular width of the gap on the friendly side, in degrees",
|
|
10042
|
+
type: "number",
|
|
10043
|
+
min: 0,
|
|
10044
|
+
max: 180,
|
|
10045
|
+
step: 1
|
|
10046
|
+
},
|
|
10047
|
+
{
|
|
10048
|
+
key: "barbCount",
|
|
10049
|
+
presentationTier: "advanced",
|
|
10050
|
+
label: "Barbs",
|
|
10051
|
+
description: "Number of inward-pointing barbs spaced along the closed arc",
|
|
10052
|
+
type: "number",
|
|
10053
|
+
min: 1,
|
|
10054
|
+
max: 24,
|
|
10055
|
+
step: 1
|
|
10056
|
+
},
|
|
10057
|
+
{
|
|
10058
|
+
key: "barbLengthRatio",
|
|
10059
|
+
presentationTier: "advanced",
|
|
10060
|
+
label: "Barb length",
|
|
10061
|
+
description: "Length of each inward barb, as a ratio of the symbol radius",
|
|
10062
|
+
type: "number",
|
|
10063
|
+
min: .01,
|
|
10064
|
+
max: 1,
|
|
10065
|
+
step: .01
|
|
9860
10066
|
}
|
|
9861
|
-
|
|
10067
|
+
];
|
|
10068
|
+
function isolateVariantParams(labelText) {
|
|
10069
|
+
return [...ISOLATE_VARIANT_PARAMS, {
|
|
10070
|
+
...LABEL_PADDING_PARAM,
|
|
10071
|
+
label: `${labelText} padding`,
|
|
10072
|
+
description: `Extra boundary clearance on each side of ${labelText}, as a ratio of its height`
|
|
10073
|
+
}];
|
|
9862
10074
|
}
|
|
9863
|
-
|
|
9864
|
-
|
|
9865
|
-
|
|
9866
|
-
|
|
9867
|
-
|
|
9868
|
-
|
|
9869
|
-
|
|
9870
|
-
|
|
9871
|
-
|
|
9872
|
-
|
|
9873
|
-
|
|
9874
|
-
|
|
9875
|
-
|
|
9876
|
-
|
|
9877
|
-
|
|
9878
|
-
|
|
9879
|
-
|
|
9880
|
-
|
|
9881
|
-
|
|
9882
|
-
|
|
10075
|
+
const ISOLATE_METADATA = {
|
|
10076
|
+
id: "isolate",
|
|
10077
|
+
name: "Isolate",
|
|
10078
|
+
description: "Seals an enemy off from support, denies freedom of movement, and prevents contact with other enemy forces while offensive action continues against it.",
|
|
10079
|
+
entity: "Mission Tasks",
|
|
10080
|
+
entityType: "Isolate",
|
|
10081
|
+
value: "341500",
|
|
10082
|
+
minCoordinates: 2,
|
|
10083
|
+
maxCoordinates: 2,
|
|
10084
|
+
geometry: "line",
|
|
10085
|
+
geometryTypes: ["MultiLineString", "Point"],
|
|
10086
|
+
paints: {
|
|
10087
|
+
stroke: true,
|
|
10088
|
+
fill: "none",
|
|
10089
|
+
text: true
|
|
10090
|
+
},
|
|
10091
|
+
drawRule: "Area15",
|
|
10092
|
+
capturesLabelSize: true,
|
|
10093
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
10094
|
+
params: isolateVariantParams("I")
|
|
10095
|
+
};
|
|
10096
|
+
const LABEL_ROTATION = Math.PI;
|
|
10097
|
+
/**
|
|
10098
|
+
* Generates a GeoJSON FeatureCollection for an ISOLATE mission task symbol (341500).
|
|
10099
|
+
*
|
|
10100
|
+
* A near-complete circle centered on PT. 1 whose radius reaches PT. 2. The arc
|
|
10101
|
+
* starts (bare) at PT. 2's bearing and sweeps clockwise around the closed
|
|
10102
|
+
* portion to a single arrow tip at the far end; the wedge-shaped gap (default
|
|
10103
|
+
* 30°) between that arrow tip and PT. 2 is the opening — the "friendly side".
|
|
10104
|
+
* Inward-pointing arrowhead barbs are spaced along the closed arc, set in from
|
|
10105
|
+
* both ends so they never touch PT. 2 or the arrow tip, conveying the
|
|
10106
|
+
* sealing-off of the enclosed force. A doctrinal "I" interrupts the closed arc
|
|
10107
|
+
* opposite the friendly-side opening.
|
|
10108
|
+
*
|
|
10109
|
+
* Input Points:
|
|
10110
|
+
* - PT. 1: Center point
|
|
10111
|
+
* - PT. 2: Start point — fixes the radius and the bearing of the opening
|
|
10112
|
+
*
|
|
10113
|
+
* @param coordinates - [PT. 1, PT. 2] as Position arrays
|
|
10114
|
+
* @param options - Configuration options
|
|
10115
|
+
* @returns GeoJSON FeatureCollection containing MultiLineString and Point features
|
|
10116
|
+
*/
|
|
10117
|
+
function createIsolateSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
10118
|
+
return createIsolateVariantSymbol(coordinates, options, {
|
|
10119
|
+
part: "isolate",
|
|
10120
|
+
labelText: "I"
|
|
10121
|
+
}, context);
|
|
10122
|
+
}
|
|
10123
|
+
/** Shared geometry for Isolate and its doctrinal cordon variants. */
|
|
10124
|
+
function createIsolateVariantSymbol(coordinates, options, config, context = {}) {
|
|
10125
|
+
const openingAngle = options.openingAngle ?? DEFAULT_ISOLATE_OPTIONS.openingAngle;
|
|
10126
|
+
const barbCount = options.barbCount ?? DEFAULT_ISOLATE_OPTIONS.barbCount;
|
|
10127
|
+
const barbLengthRatio = options.barbLengthRatio ?? DEFAULT_ISOLATE_OPTIONS.barbLengthRatio;
|
|
10128
|
+
const center = project(coordinates[0][0], coordinates[0][1]);
|
|
10129
|
+
const vRadius = vecSub(project(coordinates[1][0], coordinates[1][1]), center);
|
|
10130
|
+
const radius = vecMag(vRadius);
|
|
10131
|
+
if (radius < 1e-6) return {
|
|
10132
|
+
type: "FeatureCollection",
|
|
10133
|
+
features: []
|
|
10134
|
+
};
|
|
10135
|
+
const startAngle = Math.atan2(vRadius[1], vRadius[0]);
|
|
10136
|
+
const openingRad = Math.min(Math.max(openingAngle, 0), 359) * Math.PI / 180;
|
|
10137
|
+
const arcSpan = 2 * Math.PI - openingRad;
|
|
10138
|
+
const endAngle = startAngle - arcSpan;
|
|
10139
|
+
const pointOnCircle = (angle, r = radius) => vecAdd(center, [r * Math.cos(angle), r * Math.sin(angle)]);
|
|
10140
|
+
const labelSweep = arcSpan / 2;
|
|
10141
|
+
const labelMetrics = resolveTextMetrics(config.labelText, options, context, "regular", radius * .12);
|
|
10142
|
+
const labelPadding = Math.max(0, options.labelPadding ?? DEFAULT_ISOLATE_OPTIONS.labelPadding);
|
|
10143
|
+
const labelHalfWidth = Math.max(labelMetrics.width / 2, labelMetrics.height * LABEL_HALF_GLYPH_RATIO);
|
|
10144
|
+
const labelHalfGap = Math.min(arcSpan / 4, Math.max(labelHalfWidth + labelMetrics.height * labelPadding, 1) / radius);
|
|
10145
|
+
const labelPoint = pointOnCircle(startAngle - labelSweep);
|
|
10146
|
+
const labelBoxHalfWidth = labelMetrics.width / 2 + labelMetrics.height * labelPadding;
|
|
10147
|
+
const labelBoxHalfHeight = labelMetrics.height / 2 + labelMetrics.height * labelPadding;
|
|
10148
|
+
const sampleArc = (from, to) => {
|
|
10149
|
+
const span = to - from;
|
|
10150
|
+
const segments = Math.max(2, Math.round(64 * span / (2 * Math.PI)));
|
|
10151
|
+
const arc = [];
|
|
10152
|
+
for (let i = 0; i <= segments; i++) {
|
|
10153
|
+
const p = pointOnCircle(startAngle - from - span * i / segments);
|
|
10154
|
+
arc.push(unproject(p[0], p[1]));
|
|
10155
|
+
}
|
|
10156
|
+
return arc;
|
|
10157
|
+
};
|
|
10158
|
+
const barbLength = radius * barbLengthRatio;
|
|
10159
|
+
const lines = [sampleArc(0, labelSweep - labelHalfGap), sampleArc(labelSweep + labelHalfGap, arcSpan)];
|
|
10160
|
+
const tipDir = [Math.sin(endAngle), -Math.cos(endAngle)];
|
|
10161
|
+
lines.push(createArrowHead$1(pointOnCircle(endAngle), tipDir, barbLength, barbLength));
|
|
10162
|
+
const halfWidth = Math.asin(Math.min(1, barbLength / 2 / radius));
|
|
10163
|
+
const tipRadius = Math.max(0, radius - barbLength);
|
|
10164
|
+
const steps = Math.max(1, barbCount);
|
|
10165
|
+
for (let k = 1; k <= steps; k++) {
|
|
10166
|
+
const angle = startAngle - arcSpan * k / (steps + 1);
|
|
10167
|
+
const left = pointOnCircle(angle - halfWidth);
|
|
10168
|
+
const right = pointOnCircle(angle + halfWidth);
|
|
10169
|
+
const tip = pointOnCircle(angle, tipRadius);
|
|
10170
|
+
const barbLines = config.knockoutBarbs ? clipPolylineOutsideLabelBox([
|
|
10171
|
+
left,
|
|
10172
|
+
tip,
|
|
10173
|
+
right
|
|
10174
|
+
], labelPoint, labelBoxHalfWidth, labelBoxHalfHeight, LABEL_ROTATION) : [[
|
|
10175
|
+
left,
|
|
10176
|
+
tip,
|
|
10177
|
+
right
|
|
10178
|
+
]];
|
|
10179
|
+
for (const barb of barbLines) lines.push(barb.map((point) => unproject(point[0], point[1])));
|
|
10180
|
+
}
|
|
10181
|
+
return {
|
|
10182
|
+
type: "FeatureCollection",
|
|
10183
|
+
features: [{
|
|
10184
|
+
type: "Feature",
|
|
10185
|
+
properties: { part: config.part },
|
|
10186
|
+
geometry: {
|
|
10187
|
+
type: "MultiLineString",
|
|
10188
|
+
coordinates: lines
|
|
10189
|
+
}
|
|
10190
|
+
}, createLabelFeature(labelPoint, config.labelText, LABEL_ROTATION, options)]
|
|
10191
|
+
};
|
|
10192
|
+
}
|
|
10193
|
+
const ISOLATE = defineControlMeasure({
|
|
10194
|
+
metadata: ISOLATE_METADATA,
|
|
10195
|
+
generator: createIsolateSymbol,
|
|
10196
|
+
defaultOptions: DEFAULT_ISOLATE_OPTIONS,
|
|
10197
|
+
rule: isolateDrawRule
|
|
10198
|
+
});
|
|
10199
|
+
//#endregion
|
|
10200
|
+
//#region src/generators/cm34-mission-tasks/cordon-and-knock.ts
|
|
10201
|
+
const DEFAULT_CORDON_AND_KNOCK_OPTIONS = DEFAULT_ISOLATE_OPTIONS;
|
|
10202
|
+
const CORDON_AND_KNOCK_METADATA = {
|
|
10203
|
+
id: "cordon-and-knock",
|
|
10204
|
+
name: "Cordon and Knock",
|
|
10205
|
+
description: "Isolates an area while forces call on its occupants to cooperate before conducting further action.",
|
|
10206
|
+
entity: "Mission Tasks",
|
|
10207
|
+
entityType: "Cordon and Knock",
|
|
10208
|
+
value: "342600",
|
|
10209
|
+
minCoordinates: 2,
|
|
10210
|
+
maxCoordinates: 2,
|
|
10211
|
+
geometry: "line",
|
|
10212
|
+
geometryTypes: ["MultiLineString", "Point"],
|
|
10213
|
+
paints: {
|
|
10214
|
+
stroke: true,
|
|
10215
|
+
fill: "none",
|
|
10216
|
+
text: true
|
|
10217
|
+
},
|
|
10218
|
+
drawRule: "Area15",
|
|
10219
|
+
capturesLabelSize: true,
|
|
10220
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
10221
|
+
params: isolateVariantParams("C/K")
|
|
10222
|
+
};
|
|
10223
|
+
/** Generates the Cordon and Knock mission-task symbol (342600). */
|
|
10224
|
+
function createCordonAndKnockSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
10225
|
+
return createIsolateVariantSymbol(coordinates, options, {
|
|
10226
|
+
part: "cordon-and-knock",
|
|
10227
|
+
labelText: "C/K",
|
|
10228
|
+
knockoutBarbs: true
|
|
10229
|
+
}, context);
|
|
10230
|
+
}
|
|
10231
|
+
const CORDON_AND_KNOCK = defineControlMeasure({
|
|
10232
|
+
metadata: CORDON_AND_KNOCK_METADATA,
|
|
10233
|
+
generator: createCordonAndKnockSymbol,
|
|
10234
|
+
defaultOptions: DEFAULT_CORDON_AND_KNOCK_OPTIONS,
|
|
10235
|
+
rule: isolateDrawRule
|
|
10236
|
+
});
|
|
10237
|
+
//#endregion
|
|
10238
|
+
//#region src/generators/cm34-mission-tasks/cordon-and-search.ts
|
|
10239
|
+
const DEFAULT_CORDON_AND_SEARCH_OPTIONS = DEFAULT_ISOLATE_OPTIONS;
|
|
10240
|
+
const CORDON_AND_SEARCH_METADATA = {
|
|
10241
|
+
id: "cordon-and-search",
|
|
10242
|
+
name: "Cordon and Search",
|
|
10243
|
+
description: "Isolates an area so forces can systematically search it and its occupants.",
|
|
10244
|
+
entity: "Mission Tasks",
|
|
10245
|
+
entityType: "Cordon and Search",
|
|
10246
|
+
value: "342700",
|
|
10247
|
+
minCoordinates: 2,
|
|
10248
|
+
maxCoordinates: 2,
|
|
10249
|
+
geometry: "line",
|
|
10250
|
+
geometryTypes: ["MultiLineString", "Point"],
|
|
10251
|
+
paints: {
|
|
10252
|
+
stroke: true,
|
|
10253
|
+
fill: "none",
|
|
10254
|
+
text: true
|
|
10255
|
+
},
|
|
10256
|
+
drawRule: "Area15",
|
|
10257
|
+
capturesLabelSize: true,
|
|
10258
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
10259
|
+
params: isolateVariantParams("C/S")
|
|
10260
|
+
};
|
|
10261
|
+
/** Generates the Cordon and Search mission-task symbol (342700). */
|
|
10262
|
+
function createCordonAndSearchSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
10263
|
+
return createIsolateVariantSymbol(coordinates, options, {
|
|
10264
|
+
part: "cordon-and-search",
|
|
10265
|
+
labelText: "C/S",
|
|
10266
|
+
knockoutBarbs: true
|
|
10267
|
+
}, context);
|
|
10268
|
+
}
|
|
10269
|
+
const CORDON_AND_SEARCH = defineControlMeasure({
|
|
10270
|
+
metadata: CORDON_AND_SEARCH_METADATA,
|
|
10271
|
+
generator: createCordonAndSearchSymbol,
|
|
10272
|
+
defaultOptions: DEFAULT_CORDON_AND_SEARCH_OPTIONS,
|
|
10273
|
+
rule: isolateDrawRule
|
|
10274
|
+
});
|
|
10275
|
+
//#endregion
|
|
10276
|
+
//#region src/generators/cm15-maneuver-areas/maneuver-arrow-task-shared.ts
|
|
10277
|
+
const DEFAULT_MANEUVER_ARROW_TASK_AMPLIFIER_OPTIONS = {
|
|
10278
|
+
labelPosition: .25,
|
|
10279
|
+
dtgPosition: .62,
|
|
10280
|
+
labelPadding: 0
|
|
10281
|
+
};
|
|
10282
|
+
const DEFAULT_MANEUVER_ARROW_TASK_OPTIONS = {
|
|
10283
|
+
shaftWidthRatio: DEFAULT_SHAFT_WIDTH_RATIO$1,
|
|
10284
|
+
rearWidthRatio: DEFAULT_REAR_WIDTH_RATIO,
|
|
10285
|
+
smooth: false,
|
|
10286
|
+
smoothResolution: 5,
|
|
10287
|
+
crossbarLengthRatio: 1.1,
|
|
10288
|
+
...DEFAULT_MANEUVER_ARROW_TASK_AMPLIFIER_OPTIONS
|
|
10289
|
+
};
|
|
10290
|
+
function createManeuverArrowTaskAmplifierLabels(geometry, options, textAmplifiers) {
|
|
10291
|
+
const resolved = {
|
|
10292
|
+
...DEFAULT_MANEUVER_ARROW_TASK_AMPLIFIER_OPTIONS,
|
|
10293
|
+
...options
|
|
10294
|
+
};
|
|
10295
|
+
const centerline = geometry.shaftCenterline;
|
|
10296
|
+
const { segments, totalLength } = polylineSegments(centerline);
|
|
10297
|
+
if (segments.length === 0) return [];
|
|
10298
|
+
const labels = [];
|
|
10299
|
+
const sizeProps = labelSizeProps(resolved);
|
|
10300
|
+
const labelFrame = pointAlongPolyline(segments, totalLength, clamp01(resolved.labelPosition));
|
|
10301
|
+
if (labelFrame && textAmplifiers.T) pushLabel(labels, labelFrame.point, textAmplifiers.T, labelRotationAlong(labelFrame.along), sizeProps, void 0, "T");
|
|
10302
|
+
const dtgFrame = pointAlongPolyline(segments, totalLength, clamp01(resolved.dtgPosition));
|
|
10303
|
+
if (dtgFrame) {
|
|
10304
|
+
const rowOffset = resolveLabelOffsetMeters(resolved, .55 + Math.max(0, resolved.labelPadding));
|
|
10305
|
+
const rotation = labelRotationAlong(dtgFrame.along);
|
|
10306
|
+
if (textAmplifiers.W) pushLabel(labels, vecAdd(dtgFrame.point, vecScale(dtgFrame.perp, rowOffset)), textAmplifiers.W, rotation, sizeProps, void 0, "W");
|
|
10307
|
+
if (textAmplifiers.W1) pushLabel(labels, vecSub(dtgFrame.point, vecScale(dtgFrame.perp, rowOffset)), textAmplifiers.W1, rotation, sizeProps, void 0, "W1");
|
|
10308
|
+
}
|
|
10309
|
+
return labels;
|
|
10310
|
+
}
|
|
10311
|
+
function createManeuverArrowTask(coordinates, options, textAmplifiers, config) {
|
|
10312
|
+
const merged = {
|
|
10313
|
+
...DEFAULT_MANEUVER_ARROW_TASK_OPTIONS,
|
|
10314
|
+
...options
|
|
10315
|
+
};
|
|
10316
|
+
const resolved = {
|
|
10317
|
+
...merged,
|
|
10318
|
+
rearWidthRatio: resolveRearWidthRatio(options, merged.shaftWidthRatio)
|
|
10319
|
+
};
|
|
10320
|
+
const { geometry } = processAttackGeometry(coordinates, resolved);
|
|
10321
|
+
if (!geometry) return {
|
|
10322
|
+
type: "FeatureCollection",
|
|
10323
|
+
features: []
|
|
10324
|
+
};
|
|
10325
|
+
const outline = [
|
|
10326
|
+
...geometry.shaftLeft,
|
|
10327
|
+
geometry.headRing[0],
|
|
10328
|
+
geometry.headRing[1],
|
|
10329
|
+
geometry.headRing[2],
|
|
10330
|
+
...[...geometry.shaftRight].reverse()
|
|
9883
10331
|
];
|
|
9884
10332
|
const centerline = geometry.shaftCenterline;
|
|
9885
10333
|
const { segments, totalLength } = polylineSegments(centerline);
|
|
@@ -10631,7 +11079,7 @@ const COVER = defineControlMeasure({
|
|
|
10631
11079
|
const DEFAULT_DELAY_OPTIONS = {
|
|
10632
11080
|
arrowheadLengthRatio: .12,
|
|
10633
11081
|
arrowheadWidthRatio: .12,
|
|
10634
|
-
|
|
11082
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING,
|
|
10635
11083
|
arcSegments: 32
|
|
10636
11084
|
};
|
|
10637
11085
|
const DELAY_PARAMS = [
|
|
@@ -10655,16 +11103,7 @@ const DELAY_PARAMS = [
|
|
|
10655
11103
|
max: 1,
|
|
10656
11104
|
step: .01
|
|
10657
11105
|
},
|
|
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
|
-
},
|
|
11106
|
+
LABEL_PADDING_PARAM,
|
|
10668
11107
|
{
|
|
10669
11108
|
key: "arcSegments",
|
|
10670
11109
|
presentationTier: "advanced",
|
|
@@ -10694,9 +11133,16 @@ const DELAY_METADATA = {
|
|
|
10694
11133
|
},
|
|
10695
11134
|
drawRule: "Line24",
|
|
10696
11135
|
capturesLabelSize: true,
|
|
11136
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
10697
11137
|
params: DELAY_PARAMS
|
|
10698
11138
|
};
|
|
10699
|
-
function createDelaySymbol(coordinates, options = {}) {
|
|
11139
|
+
function createDelaySymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
11140
|
+
return createDelayTaskSymbol(coordinates, {
|
|
11141
|
+
glyph: "D",
|
|
11142
|
+
part: "delay"
|
|
11143
|
+
}, options, context);
|
|
11144
|
+
}
|
|
11145
|
+
function createDelayTaskSymbol(coordinates, config, options, context) {
|
|
10700
11146
|
const [c1, c2, c3] = coordinates;
|
|
10701
11147
|
const p1 = project(c1[0], c1[1]);
|
|
10702
11148
|
const p2 = project(c2[0], c2[1]);
|
|
@@ -10712,8 +11158,9 @@ function createDelaySymbol(coordinates, options = {}) {
|
|
|
10712
11158
|
const signedDiameter = vecDot(vecSub(p3, p2), diameterAxis);
|
|
10713
11159
|
const diameterEnd = vecAdd(p2, vecScale(diameterAxis, signedDiameter));
|
|
10714
11160
|
const diameter = Math.abs(signedDiameter);
|
|
10715
|
-
const gapHalf = lineLength * Math.min(1, Math.max(0, options.labelGapRatio ?? DEFAULT_DELAY_OPTIONS.labelGapRatio)) / 2;
|
|
10716
11161
|
const labelPoint = vecAdd(p1, vecScale(dirLine, lineLength / 2));
|
|
11162
|
+
const labelRotation = labelRotationAlong(dirLine);
|
|
11163
|
+
const gapHalf = Math.min(lineLength / 2, labelKnockoutHalfExtent(config.glyph, dirLine, labelRotation, options, context, DEFAULT_DELAY_OPTIONS.labelPadding));
|
|
10717
11164
|
const gapStart = vecAdd(labelPoint, vecScale(dirLine, -gapHalf));
|
|
10718
11165
|
const gapEnd = vecAdd(labelPoint, vecScale(dirLine, gapHalf));
|
|
10719
11166
|
const lines = [
|
|
@@ -10726,52 +11173,23 @@ function createDelaySymbol(coordinates, options = {}) {
|
|
|
10726
11173
|
type: "FeatureCollection",
|
|
10727
11174
|
features: [{
|
|
10728
11175
|
type: "Feature",
|
|
10729
|
-
properties: { part:
|
|
11176
|
+
properties: { part: config.part },
|
|
10730
11177
|
geometry: {
|
|
10731
11178
|
type: "MultiLineString",
|
|
10732
11179
|
coordinates: lines
|
|
10733
11180
|
}
|
|
10734
|
-
}, createLabelFeature(labelPoint,
|
|
11181
|
+
}, createLabelFeature(labelPoint, config.glyph, labelRotation, {
|
|
10735
11182
|
labelSize: options.labelSize,
|
|
10736
11183
|
labelSizePixels: options.labelSizePixels
|
|
10737
11184
|
})]
|
|
10738
11185
|
};
|
|
10739
11186
|
}
|
|
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
11187
|
/**
|
|
10757
11188
|
* Line24 mission tasks that share Delay's geometry and differ only in the
|
|
10758
11189
|
* glyph shown in the shaft gap and the `part` tag on the line feature.
|
|
10759
11190
|
*/
|
|
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
|
-
};
|
|
11191
|
+
function createDelayVariantSymbol(coordinates, config, options = {}, context = {}) {
|
|
11192
|
+
return createDelayTaskSymbol(coordinates, config, options, context);
|
|
10775
11193
|
}
|
|
10776
11194
|
const DELAY = defineControlMeasure({
|
|
10777
11195
|
metadata: DELAY_METADATA,
|
|
@@ -10780,6 +11198,122 @@ const DELAY = defineControlMeasure({
|
|
|
10780
11198
|
rule: line24DrawRule
|
|
10781
11199
|
});
|
|
10782
11200
|
//#endregion
|
|
11201
|
+
//#region src/generators/cm34-mission-tasks/hairpin.ts
|
|
11202
|
+
/**
|
|
11203
|
+
* Shared scaffold for the Area18 "hairpin" mission tasks (Relief in Place,
|
|
11204
|
+
* Demonstrate). Owns the doctrinal geometry both measures share: two parallel
|
|
11205
|
+
* arms with an arrow tip at each open end (PT. 1 and PT. 3), joined by a
|
|
11206
|
+
* semicircular turn across PT. 2 → PT. 3.
|
|
11207
|
+
*
|
|
11208
|
+
* Both tips use the same option ratios, each scaled by the length of its own
|
|
11209
|
+
* arm (PT. 1/PT. 2 and PT. 3/PT. 4). Only glyph placement differs between the
|
|
11210
|
+
* measures, which each supply via {@link HairpinSymbolConfig.buildLabel}.
|
|
11211
|
+
*/
|
|
11212
|
+
function createHairpinSymbol(coordinates, config, options = {}) {
|
|
11213
|
+
const [c1, c2, c3, c4] = coordinates;
|
|
11214
|
+
const p1 = project(c1[0], c1[1]);
|
|
11215
|
+
const p2 = project(c2[0], c2[1]);
|
|
11216
|
+
const p3 = project(c3[0], c3[1]);
|
|
11217
|
+
const p4 = project(c4[0], c4[1]);
|
|
11218
|
+
const upperShaft = vecSub(p2, p1);
|
|
11219
|
+
const upperLength = vecMag(upperShaft);
|
|
11220
|
+
const lowerShaft = vecSub(p3, p4);
|
|
11221
|
+
const lowerLength = vecMag(lowerShaft);
|
|
11222
|
+
if (upperLength < 1e-6 || lowerLength < 1e-6) return {
|
|
11223
|
+
type: "FeatureCollection",
|
|
11224
|
+
features: []
|
|
11225
|
+
};
|
|
11226
|
+
const upperDirection = vecNorm(upperShaft);
|
|
11227
|
+
const lowerDirection = vecNorm(lowerShaft);
|
|
11228
|
+
const arrowheadLengthRatio = Math.max(0, options.arrowheadLengthRatio ?? DEFAULT_DELAY_OPTIONS.arrowheadLengthRatio);
|
|
11229
|
+
const arrowheadWidthRatio = Math.max(0, options.arrowheadWidthRatio ?? DEFAULT_DELAY_OPTIONS.arrowheadWidthRatio);
|
|
11230
|
+
const { label, upperArm } = config.buildLabel({
|
|
11231
|
+
points: [
|
|
11232
|
+
p1,
|
|
11233
|
+
p2,
|
|
11234
|
+
p3,
|
|
11235
|
+
p4
|
|
11236
|
+
],
|
|
11237
|
+
upperDirection,
|
|
11238
|
+
upperLength,
|
|
11239
|
+
upperArm: [[unproject(p1[0], p1[1]), unproject(p2[0], p2[1])]]
|
|
11240
|
+
});
|
|
11241
|
+
return {
|
|
11242
|
+
type: "FeatureCollection",
|
|
11243
|
+
features: [{
|
|
11244
|
+
type: "Feature",
|
|
11245
|
+
properties: { part: config.part },
|
|
11246
|
+
geometry: {
|
|
11247
|
+
type: "MultiLineString",
|
|
11248
|
+
coordinates: [
|
|
11249
|
+
...upperArm,
|
|
11250
|
+
createSemicircleArc(p2, p3, upperDirection, options.arcSegments ?? DEFAULT_DELAY_OPTIONS.arcSegments),
|
|
11251
|
+
[unproject(p4[0], p4[1]), unproject(p3[0], p3[1])],
|
|
11252
|
+
createArrowHead$1(p1, vecScale(upperDirection, -1), upperLength * arrowheadLengthRatio, upperLength * arrowheadWidthRatio),
|
|
11253
|
+
createArrowHead$1(p3, lowerDirection, lowerLength * arrowheadLengthRatio, lowerLength * arrowheadWidthRatio)
|
|
11254
|
+
]
|
|
11255
|
+
}
|
|
11256
|
+
}, label]
|
|
11257
|
+
};
|
|
11258
|
+
}
|
|
11259
|
+
//#endregion
|
|
11260
|
+
//#region src/generators/cm34-mission-tasks/demonstrate.ts
|
|
11261
|
+
const DEFAULT_DEMONSTRATE_OPTIONS = DEFAULT_DELAY_OPTIONS;
|
|
11262
|
+
const DEMONSTRATE_METADATA = {
|
|
11263
|
+
id: "demonstrate",
|
|
11264
|
+
name: "Demonstrate/Demonstration",
|
|
11265
|
+
description: "Uses a show of force to deceive the enemy without seeking decisive engagement.",
|
|
11266
|
+
entity: "Mission Tasks",
|
|
11267
|
+
entityType: "Demonstrate/Demonstration",
|
|
11268
|
+
value: "343300",
|
|
11269
|
+
minCoordinates: 4,
|
|
11270
|
+
maxCoordinates: 4,
|
|
11271
|
+
geometry: "line",
|
|
11272
|
+
geometryTypes: ["MultiLineString", "Point"],
|
|
11273
|
+
paints: {
|
|
11274
|
+
stroke: true,
|
|
11275
|
+
fill: "none",
|
|
11276
|
+
text: true
|
|
11277
|
+
},
|
|
11278
|
+
drawRule: "Area18",
|
|
11279
|
+
capturesLabelSize: true,
|
|
11280
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
11281
|
+
params: DELAY_PARAMS
|
|
11282
|
+
};
|
|
11283
|
+
/** Generates the DEMONSTRATE/DEMONSTRATION mission task symbol (343300). */
|
|
11284
|
+
function createDemonstrateSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
11285
|
+
return createHairpinSymbol(coordinates, {
|
|
11286
|
+
part: "demonstrate",
|
|
11287
|
+
buildLabel: ({ points, upperDirection, upperLength }) => {
|
|
11288
|
+
const [p1, p2] = points;
|
|
11289
|
+
const labelPoint = vecAdd(p1, vecScale(upperDirection, upperLength / 2));
|
|
11290
|
+
const rotation = labelRotationAlong(upperDirection);
|
|
11291
|
+
const gapHalf = Math.min(upperLength / 2, labelKnockoutHalfExtent("DEM", upperDirection, rotation, options, context, DEFAULT_DEMONSTRATE_OPTIONS.labelPadding));
|
|
11292
|
+
const gapStart = vecAdd(labelPoint, vecScale(upperDirection, -gapHalf));
|
|
11293
|
+
const gapEnd = vecAdd(labelPoint, vecScale(upperDirection, gapHalf));
|
|
11294
|
+
return {
|
|
11295
|
+
upperArm: [[unproject(p1[0], p1[1]), unproject(gapStart[0], gapStart[1])], [unproject(gapEnd[0], gapEnd[1]), unproject(p2[0], p2[1])]],
|
|
11296
|
+
label: createLabelFeature(labelPoint, "DEM", rotation, {
|
|
11297
|
+
labelSize: options.labelSize,
|
|
11298
|
+
labelSizePixels: options.labelSizePixels
|
|
11299
|
+
})
|
|
11300
|
+
};
|
|
11301
|
+
}
|
|
11302
|
+
}, options);
|
|
11303
|
+
}
|
|
11304
|
+
const DEMONSTRATE = defineControlMeasure({
|
|
11305
|
+
metadata: DEMONSTRATE_METADATA,
|
|
11306
|
+
generator: createDemonstrateSymbol,
|
|
11307
|
+
defaultOptions: DEFAULT_DEMONSTRATE_OPTIONS,
|
|
11308
|
+
rule: demonstrateDrawRule,
|
|
11309
|
+
previewSample: { controlPoints: [
|
|
11310
|
+
[-1, -.45],
|
|
11311
|
+
[.45, -.45],
|
|
11312
|
+
[.45, .45],
|
|
11313
|
+
[-1, .45]
|
|
11314
|
+
] }
|
|
11315
|
+
});
|
|
11316
|
+
//#endregion
|
|
10783
11317
|
//#region src/generators/cm34-mission-tasks/disengage.ts
|
|
10784
11318
|
const DEFAULT_DISENGAGE_OPTIONS = DEFAULT_DELAY_OPTIONS;
|
|
10785
11319
|
const DISENGAGE_METADATA = {
|
|
@@ -10800,27 +11334,25 @@ const DISENGAGE_METADATA = {
|
|
|
10800
11334
|
},
|
|
10801
11335
|
drawRule: "Line24",
|
|
10802
11336
|
capturesLabelSize: true,
|
|
11337
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
10803
11338
|
params: DELAY_PARAMS
|
|
10804
11339
|
};
|
|
10805
|
-
function createDisengageSymbol(coordinates, options = {}) {
|
|
11340
|
+
function createDisengageSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
10806
11341
|
return createDelayVariantSymbol(coordinates, {
|
|
10807
11342
|
glyph: "DIS",
|
|
10808
11343
|
part: "disengage"
|
|
10809
|
-
}, options);
|
|
11344
|
+
}, options, context);
|
|
10810
11345
|
}
|
|
10811
11346
|
const DISENGAGE = defineControlMeasure({
|
|
10812
11347
|
metadata: DISENGAGE_METADATA,
|
|
10813
11348
|
generator: createDisengageSymbol,
|
|
10814
11349
|
defaultOptions: DEFAULT_DISENGAGE_OPTIONS,
|
|
10815
11350
|
rule: line24DrawRule,
|
|
10816
|
-
previewSample: {
|
|
10817
|
-
|
|
10818
|
-
|
|
10819
|
-
|
|
10820
|
-
|
|
10821
|
-
],
|
|
10822
|
-
options: { labelGapRatio: .36 }
|
|
10823
|
-
}
|
|
11351
|
+
previewSample: { controlPoints: [
|
|
11352
|
+
[-1, -.4],
|
|
11353
|
+
[1, -.4],
|
|
11354
|
+
[0, .6]
|
|
11355
|
+
] }
|
|
10824
11356
|
});
|
|
10825
11357
|
//#endregion
|
|
10826
11358
|
//#region src/generators/cm34-mission-tasks/disrupt.ts
|
|
@@ -10830,7 +11362,7 @@ const DEFAULT_DISRUPT_MISSION_TASK_OPTIONS = {
|
|
|
10830
11362
|
shaftLengthRatio: .25,
|
|
10831
11363
|
arrowheadLengthRatio: .15,
|
|
10832
11364
|
arrowheadWidthRatio: .2,
|
|
10833
|
-
|
|
11365
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING
|
|
10834
11366
|
};
|
|
10835
11367
|
const DISRUPT_MISSION_TASK_METADATA = {
|
|
10836
11368
|
id: "disrupt-mission-task",
|
|
@@ -10850,6 +11382,7 @@ const DISRUPT_MISSION_TASK_METADATA = {
|
|
|
10850
11382
|
},
|
|
10851
11383
|
drawRule: "Area12",
|
|
10852
11384
|
capturesLabelSize: true,
|
|
11385
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
10853
11386
|
params: [
|
|
10854
11387
|
{
|
|
10855
11388
|
key: "middleArrowLengthRatio",
|
|
@@ -10901,20 +11434,11 @@ const DISRUPT_MISSION_TASK_METADATA = {
|
|
|
10901
11434
|
max: 1,
|
|
10902
11435
|
step: .01
|
|
10903
11436
|
},
|
|
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
|
-
}
|
|
11437
|
+
LABEL_PADDING_PARAM
|
|
10914
11438
|
]
|
|
10915
11439
|
};
|
|
10916
11440
|
/** Generates the DISRUPT mission task symbol (341000). */
|
|
10917
|
-
function createDisruptMissionTaskSymbol(coordinates, options = {}) {
|
|
11441
|
+
function createDisruptMissionTaskSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
10918
11442
|
const p1 = project(coordinates[0][0], coordinates[0][1]);
|
|
10919
11443
|
const p2 = project(coordinates[1][0], coordinates[1][1]);
|
|
10920
11444
|
const frame = createProjectedBaselineFrame(p1, p2, {
|
|
@@ -10946,8 +11470,8 @@ function createDisruptMissionTaskSymbol(coordinates, options = {}) {
|
|
|
10946
11470
|
const bottomTip = vecAdd(p2, vecScale(arrowDirection, bottomLength));
|
|
10947
11471
|
const rearEnd = vecAdd(middle, vecScale(arrowDirection, -rearLength));
|
|
10948
11472
|
const labelPoint = vecScale(vecAdd(middle, middleTip), .5);
|
|
10949
|
-
const
|
|
10950
|
-
const gapHalf = Math.min(
|
|
11473
|
+
const labelRotation = labelRotationAlong(arrowDirection);
|
|
11474
|
+
const gapHalf = Math.min(labelKnockoutHalfExtent("D", arrowDirection, labelRotation, options, context, DEFAULT_DISRUPT_MISSION_TASK_OPTIONS.labelPadding), middleLength / 2);
|
|
10951
11475
|
const gapStart = vecAdd(labelPoint, vecScale(arrowDirection, -gapHalf));
|
|
10952
11476
|
const gapEnd = vecAdd(labelPoint, vecScale(arrowDirection, gapHalf));
|
|
10953
11477
|
const p1LonLat = unproject(p1[0], p1[1]);
|
|
@@ -10970,7 +11494,7 @@ function createDisruptMissionTaskSymbol(coordinates, options = {}) {
|
|
|
10970
11494
|
createArrowHead$1(bottomTip, arrowDirection, headLength, headWidth)
|
|
10971
11495
|
]
|
|
10972
11496
|
}
|
|
10973
|
-
}, createLabelFeature(labelPoint, "D",
|
|
11497
|
+
}, createLabelFeature(labelPoint, "D", labelRotation, {
|
|
10974
11498
|
labelSize: options.labelSize,
|
|
10975
11499
|
labelSizePixels: options.labelSizePixels
|
|
10976
11500
|
})]
|
|
@@ -10989,7 +11513,7 @@ const DEFAULT_ENVELOPMENT_OPTIONS = {
|
|
|
10989
11513
|
arrowheadLengthRatio: .1,
|
|
10990
11514
|
arrowheadWidthRatio: .12,
|
|
10991
11515
|
labelPosition: .6,
|
|
10992
|
-
|
|
11516
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING,
|
|
10993
11517
|
resolution: 64
|
|
10994
11518
|
};
|
|
10995
11519
|
const ENVELOPMENT_METADATA = {
|
|
@@ -11010,6 +11534,7 @@ const ENVELOPMENT_METADATA = {
|
|
|
11010
11534
|
},
|
|
11011
11535
|
drawRule: "Line31",
|
|
11012
11536
|
capturesLabelSize: true,
|
|
11537
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
11013
11538
|
params: [
|
|
11014
11539
|
{
|
|
11015
11540
|
key: "arrowheadLengthRatio",
|
|
@@ -11041,21 +11566,12 @@ const ENVELOPMENT_METADATA = {
|
|
|
11041
11566
|
max: 1,
|
|
11042
11567
|
step: .01
|
|
11043
11568
|
},
|
|
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
|
-
},
|
|
11569
|
+
LABEL_PADDING_PARAM,
|
|
11054
11570
|
...ARC_RESOLUTION_PARAMS
|
|
11055
11571
|
]
|
|
11056
11572
|
};
|
|
11057
11573
|
/** Generates the Envelopment mission task symbol (343500). */
|
|
11058
|
-
function createEnvelopment(coordinates, options = {}) {
|
|
11574
|
+
function createEnvelopment(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
11059
11575
|
const [c1, c2, c3, c4] = coordinates;
|
|
11060
11576
|
const p1 = project(c1[0], c1[1]);
|
|
11061
11577
|
const p2 = project(c2[0], c2[1]);
|
|
@@ -11070,8 +11586,10 @@ function createEnvelopment(coordinates, options = {}) {
|
|
|
11070
11586
|
};
|
|
11071
11587
|
const straightDirection = vecNorm(straight);
|
|
11072
11588
|
const p3 = vecAdd(p2, vecScale(straightDirection, diameter));
|
|
11073
|
-
const
|
|
11074
|
-
const
|
|
11589
|
+
const labelPosition = clamp01(options.labelPosition ?? DEFAULT_ENVELOPMENT_OPTIONS.labelPosition);
|
|
11590
|
+
const labelPoint = vecAdd(p1, vecScale(straight, labelPosition));
|
|
11591
|
+
const labelRotation = labelRotationAlong(straightDirection);
|
|
11592
|
+
const gapHalf = Math.min(straightLength * Math.min(labelPosition, 1 - labelPosition), labelKnockoutHalfExtent("E", straightDirection, labelRotation, options, context, DEFAULT_ENVELOPMENT_OPTIONS.labelPadding));
|
|
11075
11593
|
const gapStart = vecAdd(labelPoint, vecScale(straightDirection, -gapHalf));
|
|
11076
11594
|
const gapEnd = vecAdd(labelPoint, vecScale(straightDirection, gapHalf));
|
|
11077
11595
|
const center = midpoint(p2, p3);
|
|
@@ -11094,7 +11612,7 @@ function createEnvelopment(coordinates, options = {}) {
|
|
|
11094
11612
|
arrowhead
|
|
11095
11613
|
]
|
|
11096
11614
|
}
|
|
11097
|
-
}, createLabelFeature(labelPoint, "E",
|
|
11615
|
+
}, createLabelFeature(labelPoint, "E", labelRotation, options)]
|
|
11098
11616
|
};
|
|
11099
11617
|
}
|
|
11100
11618
|
const ENVELOPMENT = defineControlMeasure({
|
|
@@ -11113,12 +11631,83 @@ const ENVELOPMENT = defineControlMeasure({
|
|
|
11113
11631
|
}
|
|
11114
11632
|
});
|
|
11115
11633
|
//#endregion
|
|
11634
|
+
//#region src/generators/cm34-mission-tasks/exploitation.ts
|
|
11635
|
+
const EXPLOITATION_FIN_DASH = [6, 4];
|
|
11636
|
+
const DEFAULT_EXPLOITATION_OPTIONS = {};
|
|
11637
|
+
const EXPLOITATION_METADATA = {
|
|
11638
|
+
id: "exploitation",
|
|
11639
|
+
name: "Exploit/Exploitation",
|
|
11640
|
+
description: "The arrow points in the direction of the action; its tip may indicate where the action is to conclude, and its base marks the tasked unit’s current or projected location.",
|
|
11641
|
+
entity: "Mission Tasks",
|
|
11642
|
+
entityType: "Exploit/Exploitation",
|
|
11643
|
+
value: "343100",
|
|
11644
|
+
minCoordinates: 3,
|
|
11645
|
+
maxCoordinates: 3,
|
|
11646
|
+
geometry: "line",
|
|
11647
|
+
geometryTypes: ["MultiLineString"],
|
|
11648
|
+
paints: {
|
|
11649
|
+
stroke: true,
|
|
11650
|
+
fill: "none",
|
|
11651
|
+
text: false
|
|
11652
|
+
},
|
|
11653
|
+
drawRule: "Line30"
|
|
11654
|
+
};
|
|
11655
|
+
/** Generates the Exploit/Exploitation mission task symbol (343100). */
|
|
11656
|
+
function createExploitation(coordinates, _options = {}) {
|
|
11657
|
+
const [c1, c2, c3] = coordinates;
|
|
11658
|
+
const p1 = project(c1[0], c1[1]);
|
|
11659
|
+
const p2 = project(c2[0], c2[1]);
|
|
11660
|
+
const p3 = project(c3[0], c3[1]);
|
|
11661
|
+
const axis = vecSub(p1, p2);
|
|
11662
|
+
const axisLength = vecMag(axis);
|
|
11663
|
+
const finLength = vecMag(vecSub(p3, p2));
|
|
11664
|
+
if (axisLength < 1e-6 || finLength < 1e-6) return {
|
|
11665
|
+
type: "FeatureCollection",
|
|
11666
|
+
features: []
|
|
11667
|
+
};
|
|
11668
|
+
const direction = vecNorm(axis);
|
|
11669
|
+
const chevron = (tip) => createArrowHead$1(tip, direction, finLength * Math.SQRT1_2, finLength * Math.SQRT2);
|
|
11670
|
+
const [finLeft, , finRight] = chevron(p2);
|
|
11671
|
+
return {
|
|
11672
|
+
type: "FeatureCollection",
|
|
11673
|
+
features: [{
|
|
11674
|
+
type: "Feature",
|
|
11675
|
+
properties: { part: "exploitation" },
|
|
11676
|
+
geometry: {
|
|
11677
|
+
type: "MultiLineString",
|
|
11678
|
+
coordinates: [[c2, c1], chevron(p1)]
|
|
11679
|
+
}
|
|
11680
|
+
}, {
|
|
11681
|
+
type: "Feature",
|
|
11682
|
+
properties: {
|
|
11683
|
+
part: "fins",
|
|
11684
|
+
style: { strokeDash: [...EXPLOITATION_FIN_DASH] }
|
|
11685
|
+
},
|
|
11686
|
+
geometry: {
|
|
11687
|
+
type: "MultiLineString",
|
|
11688
|
+
coordinates: [[c2, finLeft], [c2, finRight]]
|
|
11689
|
+
}
|
|
11690
|
+
}]
|
|
11691
|
+
};
|
|
11692
|
+
}
|
|
11693
|
+
const EXPLOITATION = defineControlMeasure({
|
|
11694
|
+
metadata: EXPLOITATION_METADATA,
|
|
11695
|
+
generator: createExploitation,
|
|
11696
|
+
defaultOptions: DEFAULT_EXPLOITATION_OPTIONS,
|
|
11697
|
+
rule: line30DrawRule,
|
|
11698
|
+
previewSample: { controlPoints: [
|
|
11699
|
+
[.8, 0],
|
|
11700
|
+
[-.6, 0],
|
|
11701
|
+
[-.9, .3]
|
|
11702
|
+
] }
|
|
11703
|
+
});
|
|
11704
|
+
//#endregion
|
|
11116
11705
|
//#region src/generators/cm34-mission-tasks/infiltration.ts
|
|
11117
11706
|
const DEFAULT_INFILTRATION_OPTIONS = {
|
|
11118
11707
|
arrowheadLengthRatio: .1,
|
|
11119
11708
|
arrowheadWidthRatio: .1,
|
|
11120
11709
|
labelPosition: .6,
|
|
11121
|
-
|
|
11710
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING,
|
|
11122
11711
|
resolution: 64
|
|
11123
11712
|
};
|
|
11124
11713
|
const INFILTRATION_METADATA = {
|
|
@@ -11139,6 +11728,7 @@ const INFILTRATION_METADATA = {
|
|
|
11139
11728
|
},
|
|
11140
11729
|
drawRule: "Line32",
|
|
11141
11730
|
capturesLabelSize: true,
|
|
11731
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
11142
11732
|
params: [
|
|
11143
11733
|
{
|
|
11144
11734
|
key: "arrowheadLengthRatio",
|
|
@@ -11170,16 +11760,7 @@ const INFILTRATION_METADATA = {
|
|
|
11170
11760
|
max: 1,
|
|
11171
11761
|
step: .01
|
|
11172
11762
|
},
|
|
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
|
-
},
|
|
11763
|
+
LABEL_PADDING_PARAM,
|
|
11183
11764
|
...ARC_RESOLUTION_PARAMS
|
|
11184
11765
|
]
|
|
11185
11766
|
};
|
|
@@ -11205,7 +11786,7 @@ function tangentDirection(p1, p2, p3) {
|
|
|
11205
11786
|
return direction;
|
|
11206
11787
|
}
|
|
11207
11788
|
/** Generates the Infiltration mission task symbol (343800). */
|
|
11208
|
-
function createInfiltration(coordinates, options = {}) {
|
|
11789
|
+
function createInfiltration(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
11209
11790
|
const [c1, c2, c3] = coordinates;
|
|
11210
11791
|
const p1 = project(c1[0], c1[1]);
|
|
11211
11792
|
const p2 = project(c2[0], c2[1]);
|
|
@@ -11238,8 +11819,10 @@ function createInfiltration(coordinates, options = {}) {
|
|
|
11238
11819
|
const arcResolution = normalizeArcResolution(options.resolution) / 2;
|
|
11239
11820
|
const firstArc = sampleProjectedArc(firstCenter, sideRadius, alongRadius, 0, Math.PI / 2, arcResolution);
|
|
11240
11821
|
const secondArc = sampleProjectedArc(secondCenter, vecScale(alongRadius, -1), vecScale(sideRadius, -1), 0, Math.PI / 2, arcResolution);
|
|
11241
|
-
const
|
|
11242
|
-
const
|
|
11822
|
+
const labelPosition = clamp01(options.labelPosition ?? DEFAULT_INFILTRATION_OPTIONS.labelPosition);
|
|
11823
|
+
const labelPoint = vecAdd(p1, vecScale(straight, labelPosition));
|
|
11824
|
+
const labelRotation = labelRotationAlong(along);
|
|
11825
|
+
const gapHalf = Math.min(straightLength * Math.min(labelPosition, 1 - labelPosition), labelKnockoutHalfExtent("IN", along, labelRotation, options, context, DEFAULT_INFILTRATION_OPTIONS.labelPadding));
|
|
11243
11826
|
const gapStart = vecAdd(labelPoint, vecScale(along, -gapHalf));
|
|
11244
11827
|
const gapEnd = vecAdd(labelPoint, vecScale(along, gapHalf));
|
|
11245
11828
|
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 +11842,7 @@ function createInfiltration(coordinates, options = {}) {
|
|
|
11259
11842
|
arrowhead
|
|
11260
11843
|
]
|
|
11261
11844
|
}
|
|
11262
|
-
}, createLabelFeature(labelPoint, "IN",
|
|
11845
|
+
}, createLabelFeature(labelPoint, "IN", labelRotation, options)]
|
|
11263
11846
|
};
|
|
11264
11847
|
}
|
|
11265
11848
|
const INFILTRATION = defineControlMeasure({
|
|
@@ -11546,6 +12129,7 @@ const DROP_ZONE_METADATA = {
|
|
|
11546
12129
|
},
|
|
11547
12130
|
drawRule: "Area1",
|
|
11548
12131
|
capturesLabelSize: true,
|
|
12132
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
11549
12133
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
11550
12134
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
11551
12135
|
};
|
|
@@ -11610,6 +12194,7 @@ const ENCIRCLEMENT_METADATA = {
|
|
|
11610
12194
|
},
|
|
11611
12195
|
drawRule: "Area1",
|
|
11612
12196
|
capturesLabelSize: true,
|
|
12197
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
11613
12198
|
params: ENCIRCLEMENT_PARAMS,
|
|
11614
12199
|
textAmplifiers: [{
|
|
11615
12200
|
key: "N",
|
|
@@ -11763,6 +12348,7 @@ const EXTRACTION_ZONE_METADATA = {
|
|
|
11763
12348
|
},
|
|
11764
12349
|
drawRule: "Area1",
|
|
11765
12350
|
capturesLabelSize: true,
|
|
12351
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
11766
12352
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
11767
12353
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
11768
12354
|
};
|
|
@@ -12127,7 +12713,7 @@ const DEFAULT_FIX_MISSION_TASK_OPTIONS = {
|
|
|
12127
12713
|
zigzagEndRatio: .8,
|
|
12128
12714
|
zigzagPeakCount: 5,
|
|
12129
12715
|
labelPositionRatio: .09,
|
|
12130
|
-
|
|
12716
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING
|
|
12131
12717
|
};
|
|
12132
12718
|
const FIX_MISSION_TASK_METADATA = {
|
|
12133
12719
|
id: "fix-mission-task",
|
|
@@ -12147,6 +12733,7 @@ const FIX_MISSION_TASK_METADATA = {
|
|
|
12147
12733
|
},
|
|
12148
12734
|
drawRule: "Line9",
|
|
12149
12735
|
capturesLabelSize: true,
|
|
12736
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
12150
12737
|
params: [
|
|
12151
12738
|
{
|
|
12152
12739
|
key: "arrowheadLengthRatio",
|
|
@@ -12208,20 +12795,11 @@ const FIX_MISSION_TASK_METADATA = {
|
|
|
12208
12795
|
max: 1,
|
|
12209
12796
|
step: .01
|
|
12210
12797
|
},
|
|
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
|
-
}
|
|
12798
|
+
LABEL_PADDING_PARAM
|
|
12221
12799
|
]
|
|
12222
12800
|
};
|
|
12223
12801
|
/** Generates the Fix mission task symbol (341100). */
|
|
12224
|
-
function createFixMissionTaskSymbol(coordinates, options = {}) {
|
|
12802
|
+
function createFixMissionTaskSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
12225
12803
|
const c1 = coordinates[0];
|
|
12226
12804
|
const c2 = coordinates[1];
|
|
12227
12805
|
const tip = project(c1[0], c1[1]);
|
|
@@ -12254,12 +12832,12 @@ function createFixMissionTaskSymbol(coordinates, options = {}) {
|
|
|
12254
12832
|
const exitRatio = Math.min(1, zigzagEnd + toothStep / 2);
|
|
12255
12833
|
const zigzagEntry = vecAdd(tail, vecScale(along, axisLength * entryRatio));
|
|
12256
12834
|
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;
|
|
12835
|
+
const labelDistance = axisLength * clamp01(options.labelPositionRatio ?? DEFAULT_FIX_MISSION_TASK_OPTIONS.labelPositionRatio);
|
|
12260
12836
|
const labelCenter = vecAdd(tail, vecScale(along, labelDistance));
|
|
12261
|
-
const
|
|
12262
|
-
const
|
|
12837
|
+
const labelRotation = labelRotationAlong(along);
|
|
12838
|
+
const gapHalf = labelKnockoutHalfExtent("F", along, labelRotation, options, context, DEFAULT_FIX_MISSION_TASK_OPTIONS.labelPadding);
|
|
12839
|
+
const gapStartDistance = Math.max(0, labelDistance - gapHalf);
|
|
12840
|
+
const gapEndDistance = Math.min(axisLength, labelDistance + gapHalf);
|
|
12263
12841
|
const gapStart = vecAdd(tail, vecScale(along, gapStartDistance));
|
|
12264
12842
|
const gapEnd = vecAdd(tail, vecScale(along, gapEndDistance));
|
|
12265
12843
|
return {
|
|
@@ -12281,7 +12859,7 @@ function createFixMissionTaskSymbol(coordinates, options = {}) {
|
|
|
12281
12859
|
createArrowHead$1(tip, along, arrowheadLength, arrowheadWidth)
|
|
12282
12860
|
]
|
|
12283
12861
|
}
|
|
12284
|
-
}, createLabelFeature(labelCenter, "F",
|
|
12862
|
+
}, createLabelFeature(labelCenter, "F", labelRotation, options)]
|
|
12285
12863
|
};
|
|
12286
12864
|
}
|
|
12287
12865
|
const FIX_MISSION_TASK = defineControlMeasure({
|
|
@@ -12326,6 +12904,7 @@ const FLOT_METADATA = {
|
|
|
12326
12904
|
},
|
|
12327
12905
|
drawRule: "Line1",
|
|
12328
12906
|
capturesLabelSize: true,
|
|
12907
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
12329
12908
|
params: [
|
|
12330
12909
|
...SMOOTH_LINE_PARAMS,
|
|
12331
12910
|
{
|
|
@@ -12348,7 +12927,7 @@ const FLOT_METADATA = {
|
|
|
12348
12927
|
step: 1,
|
|
12349
12928
|
unit: "m"
|
|
12350
12929
|
},
|
|
12351
|
-
|
|
12930
|
+
LABEL_PADDING_PARAM,
|
|
12352
12931
|
{
|
|
12353
12932
|
key: "arcSegments",
|
|
12354
12933
|
presentationTier: "advanced",
|
|
@@ -12670,7 +13249,7 @@ function phaseLineNameParams(placeholder) {
|
|
|
12670
13249
|
type: "boolean",
|
|
12671
13250
|
visibleWhen: (opts) => Boolean(String(opts.phaseLineName ?? "").trim())
|
|
12672
13251
|
},
|
|
12673
|
-
|
|
13252
|
+
LABEL_PADDING_PARAM
|
|
12674
13253
|
];
|
|
12675
13254
|
}
|
|
12676
13255
|
//#endregion
|
|
@@ -12855,6 +13434,7 @@ const AIRHEAD_LINE_METADATA = {
|
|
|
12855
13434
|
},
|
|
12856
13435
|
drawRule: "Area1",
|
|
12857
13436
|
capturesLabelSize: true,
|
|
13437
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
12858
13438
|
params: LABELED_AREA_PARAMS,
|
|
12859
13439
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
12860
13440
|
};
|
|
@@ -13508,6 +14088,7 @@ const FORTIFIED_LINE_METADATA = {
|
|
|
13508
14088
|
},
|
|
13509
14089
|
drawRule: "Line1",
|
|
13510
14090
|
capturesLabelSize: true,
|
|
14091
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
13511
14092
|
params: FORTIFIED_PARAMS,
|
|
13512
14093
|
textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
|
|
13513
14094
|
};
|
|
@@ -13668,6 +14249,7 @@ const FORTIFIED_AREA_METADATA = {
|
|
|
13668
14249
|
},
|
|
13669
14250
|
drawRule: "Area1",
|
|
13670
14251
|
capturesLabelSize: true,
|
|
14252
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
13671
14253
|
params: [...FORTIFIED_AREA_PARAMS, AREA_LABEL_PADDING_PARAM],
|
|
13672
14254
|
textAmplifiers: AREA_TEXT_AMPLIFIERS
|
|
13673
14255
|
};
|
|
@@ -13782,195 +14364,55 @@ const FRONTAL_ATTACK = defineControlMeasure({
|
|
|
13782
14364
|
[-.4, -.35],
|
|
13783
14365
|
[-1, -.8],
|
|
13784
14366
|
[.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
|
-
]);
|
|
14367
|
+
],
|
|
14368
|
+
textAmplifiers: {
|
|
14369
|
+
T: "FOX",
|
|
14370
|
+
W: "080400Z",
|
|
14371
|
+
W1: "120300Z"
|
|
14372
|
+
},
|
|
14373
|
+
options: { labelSize: 80 }
|
|
13956
14374
|
}
|
|
13957
|
-
|
|
13958
|
-
|
|
13959
|
-
|
|
13960
|
-
|
|
13961
|
-
|
|
13962
|
-
|
|
13963
|
-
|
|
13964
|
-
|
|
13965
|
-
|
|
13966
|
-
|
|
13967
|
-
|
|
14375
|
+
});
|
|
14376
|
+
//#endregion
|
|
14377
|
+
//#region src/generators/cm34-mission-tasks/forward-passage-of-lines.ts
|
|
14378
|
+
const DEFAULT_FORWARD_PASSAGE_OF_LINES_OPTIONS = DEFAULT_DELAY_OPTIONS;
|
|
14379
|
+
const FORWARD_PASSAGE_OF_LINES_METADATA = {
|
|
14380
|
+
id: "forward-passage-of-lines",
|
|
14381
|
+
name: "Forward Passage of Lines",
|
|
14382
|
+
description: "Passes one force through another force's positions toward the enemy.",
|
|
14383
|
+
entity: "Mission Tasks",
|
|
14384
|
+
entityType: "Forward Passage of Lines",
|
|
14385
|
+
value: "344100",
|
|
14386
|
+
minCoordinates: 3,
|
|
14387
|
+
maxCoordinates: 3,
|
|
14388
|
+
geometry: "line",
|
|
14389
|
+
geometryTypes: ["MultiLineString", "Point"],
|
|
14390
|
+
paints: {
|
|
14391
|
+
stroke: true,
|
|
14392
|
+
fill: "none",
|
|
14393
|
+
text: true
|
|
14394
|
+
},
|
|
14395
|
+
drawRule: "Line24",
|
|
14396
|
+
capturesLabelSize: true,
|
|
14397
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
14398
|
+
params: DELAY_PARAMS
|
|
14399
|
+
};
|
|
14400
|
+
function createForwardPassageOfLinesSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
14401
|
+
return createDelayVariantSymbol(coordinates, {
|
|
14402
|
+
glyph: "P(F)",
|
|
14403
|
+
part: "forward-passage-of-lines"
|
|
14404
|
+
}, options, context);
|
|
13968
14405
|
}
|
|
13969
|
-
const
|
|
13970
|
-
metadata:
|
|
13971
|
-
generator:
|
|
13972
|
-
defaultOptions:
|
|
13973
|
-
rule:
|
|
14406
|
+
const FORWARD_PASSAGE_OF_LINES = defineControlMeasure({
|
|
14407
|
+
metadata: FORWARD_PASSAGE_OF_LINES_METADATA,
|
|
14408
|
+
generator: createForwardPassageOfLinesSymbol,
|
|
14409
|
+
defaultOptions: DEFAULT_FORWARD_PASSAGE_OF_LINES_OPTIONS,
|
|
14410
|
+
rule: line24DrawRule,
|
|
14411
|
+
previewSample: { controlPoints: [
|
|
14412
|
+
[1, -.4],
|
|
14413
|
+
[-1, -.4],
|
|
14414
|
+
[0, .6]
|
|
14415
|
+
] }
|
|
13974
14416
|
});
|
|
13975
14417
|
//#endregion
|
|
13976
14418
|
//#region src/generators/cm34-mission-tasks/movement-to-contact.ts
|
|
@@ -14194,6 +14636,7 @@ const OCCUPY_METADATA = {
|
|
|
14194
14636
|
},
|
|
14195
14637
|
drawRule: "Area16",
|
|
14196
14638
|
capturesLabelSize: true,
|
|
14639
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
14197
14640
|
params: [
|
|
14198
14641
|
{
|
|
14199
14642
|
key: "openingAngle",
|
|
@@ -14238,10 +14681,10 @@ const OCCUPY_METADATA = {
|
|
|
14238
14681
|
}
|
|
14239
14682
|
]
|
|
14240
14683
|
};
|
|
14241
|
-
const TAU$
|
|
14242
|
-
const MIN_LABEL_GAP_METERS$
|
|
14684
|
+
const TAU$2 = Math.PI * 2;
|
|
14685
|
+
const MIN_LABEL_GAP_METERS$2 = 1;
|
|
14243
14686
|
/** Generates the Occupy mission task symbol (341700). */
|
|
14244
|
-
function createOccupySymbol(coordinates, options = {}) {
|
|
14687
|
+
function createOccupySymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
14245
14688
|
const center = project(coordinates[0][0], coordinates[0][1]);
|
|
14246
14689
|
const radiusVector = vecSub(project(coordinates[1][0], coordinates[1][1]), center);
|
|
14247
14690
|
const radius = vecMag(radiusVector);
|
|
@@ -14255,11 +14698,11 @@ function createOccupySymbol(coordinates, options = {}) {
|
|
|
14255
14698
|
};
|
|
14256
14699
|
const bulgeVector = [radiusVector[1], -radiusVector[0]];
|
|
14257
14700
|
const halfTurnResolution = normalizeArcResolution(resolved.resolution) / 2;
|
|
14258
|
-
const boundarySpan = TAU$
|
|
14701
|
+
const boundarySpan = TAU$2 - Math.min(180, Math.max(5, resolved.openingAngle)) * Math.PI / 180;
|
|
14259
14702
|
const labelSweep = boundarySpan / 2;
|
|
14260
|
-
const labelHeight =
|
|
14703
|
+
const labelHeight = resolveTextMetrics("O", options, context, "regular", radius * .12).height;
|
|
14261
14704
|
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$
|
|
14705
|
+
const labelHalfGap = Math.min(boundarySpan / 4, Math.max(labelHeight * (LABEL_HALF_GLYPH_RATIO + labelPadding), MIN_LABEL_GAP_METERS$2) / radius);
|
|
14263
14706
|
const sampleArc = (from, to) => sampleProjectedArc(center, radiusVector, bulgeVector, from, to, halfTurnResolution);
|
|
14264
14707
|
const lines = [sampleArc(0, labelSweep - labelHalfGap), sampleArc(labelSweep + labelHalfGap, boundarySpan)];
|
|
14265
14708
|
const terminal = arcPointAt(center, radiusVector, bulgeVector, boundarySpan);
|
|
@@ -14298,7 +14741,7 @@ const OCCUPY = defineControlMeasure({
|
|
|
14298
14741
|
const DEFAULT_PENETRATE_OPTIONS = {
|
|
14299
14742
|
arrowheadLengthRatio: .15,
|
|
14300
14743
|
arrowheadWidthRatio: .2,
|
|
14301
|
-
|
|
14744
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING
|
|
14302
14745
|
};
|
|
14303
14746
|
const PENETRATE_METADATA = {
|
|
14304
14747
|
id: "penetrate",
|
|
@@ -14318,6 +14761,7 @@ const PENETRATE_METADATA = {
|
|
|
14318
14761
|
},
|
|
14319
14762
|
drawRule: "Area17",
|
|
14320
14763
|
capturesLabelSize: true,
|
|
14764
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
14321
14765
|
params: [
|
|
14322
14766
|
{
|
|
14323
14767
|
key: "arrowheadLengthRatio",
|
|
@@ -14339,20 +14783,11 @@ const PENETRATE_METADATA = {
|
|
|
14339
14783
|
max: 1,
|
|
14340
14784
|
step: .01
|
|
14341
14785
|
},
|
|
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
|
-
}
|
|
14786
|
+
LABEL_PADDING_PARAM
|
|
14352
14787
|
]
|
|
14353
14788
|
};
|
|
14354
14789
|
/** Generates the PENETRATE mission task symbol (341800). */
|
|
14355
|
-
function createPenetrateSymbol(coordinates, options = {}) {
|
|
14790
|
+
function createPenetrateSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
14356
14791
|
const frame = createProjectedBaselineFrame(project(coordinates[0][0], coordinates[0][1]), project(coordinates[1][0], coordinates[1][1]), {
|
|
14357
14792
|
origin: "midpoint",
|
|
14358
14793
|
normal: "right"
|
|
@@ -14372,29 +14807,29 @@ function createPenetrateSymbol(coordinates, options = {}) {
|
|
|
14372
14807
|
const tail = vecAdd(frame.origin, vecScale(frame.normal, signedTailDistance));
|
|
14373
14808
|
const arrowDirection = vecScale(vecSub(tip, tail), 1 / shaftLength);
|
|
14374
14809
|
const labelPoint = vecScale(vecAdd(tail, tip), .5);
|
|
14375
|
-
const
|
|
14810
|
+
const shaftRotation = Math.atan2(arrowDirection[1], arrowDirection[0]);
|
|
14811
|
+
const renderedLabelRotation = Math.PI - shaftRotation;
|
|
14812
|
+
const labelRotation = normalizeRadians(Math.PI - keepTextLeftToRight(renderedLabelRotation));
|
|
14813
|
+
const gapHalf = Math.min(shaftLength / 2, labelKnockoutHalfExtent("P", arrowDirection, labelRotation, options, context, DEFAULT_PENETRATE_OPTIONS.labelPadding));
|
|
14376
14814
|
const gapStart = vecAdd(labelPoint, vecScale(arrowDirection, -gapHalf));
|
|
14377
14815
|
const gapEnd = vecAdd(labelPoint, vecScale(arrowDirection, gapHalf));
|
|
14378
14816
|
const headLength = frame.length * Math.max(0, options.arrowheadLengthRatio ?? DEFAULT_PENETRATE_OPTIONS.arrowheadLengthRatio);
|
|
14379
14817
|
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
14818
|
return {
|
|
14396
14819
|
type: "FeatureCollection",
|
|
14397
|
-
features: [
|
|
14820
|
+
features: [{
|
|
14821
|
+
type: "Feature",
|
|
14822
|
+
properties: { part: "penetrate" },
|
|
14823
|
+
geometry: {
|
|
14824
|
+
type: "MultiLineString",
|
|
14825
|
+
coordinates: [
|
|
14826
|
+
[coordinates[0], coordinates[1]],
|
|
14827
|
+
[unproject(tail[0], tail[1]), unproject(gapStart[0], gapStart[1])],
|
|
14828
|
+
[unproject(gapEnd[0], gapEnd[1]), unproject(tip[0], tip[1])],
|
|
14829
|
+
createArrowHead$1(tip, arrowDirection, headLength, headWidth)
|
|
14830
|
+
]
|
|
14831
|
+
}
|
|
14832
|
+
}, createLabelFeature(labelPoint, "P", labelRotation, {
|
|
14398
14833
|
labelSize: options.labelSize,
|
|
14399
14834
|
labelSizePixels: options.labelSizePixels
|
|
14400
14835
|
})]
|
|
@@ -14418,7 +14853,7 @@ const DEFAULT_PURSUIT_OPTIONS = {
|
|
|
14418
14853
|
arrowheadWidthRatio: .12,
|
|
14419
14854
|
terminalBarLengthRatio: 1.5,
|
|
14420
14855
|
labelPosition: .6,
|
|
14421
|
-
|
|
14856
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING,
|
|
14422
14857
|
resolution: 64
|
|
14423
14858
|
};
|
|
14424
14859
|
const PURSUIT_METADATA = {
|
|
@@ -14439,6 +14874,7 @@ const PURSUIT_METADATA = {
|
|
|
14439
14874
|
},
|
|
14440
14875
|
drawRule: "Line33",
|
|
14441
14876
|
capturesLabelSize: true,
|
|
14877
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
14442
14878
|
params: [
|
|
14443
14879
|
{
|
|
14444
14880
|
key: "arrowheadLengthRatio",
|
|
@@ -14480,21 +14916,12 @@ const PURSUIT_METADATA = {
|
|
|
14480
14916
|
max: 1,
|
|
14481
14917
|
step: .01
|
|
14482
14918
|
},
|
|
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
|
-
},
|
|
14919
|
+
LABEL_PADDING_PARAM,
|
|
14493
14920
|
...ARC_RESOLUTION_PARAMS
|
|
14494
14921
|
]
|
|
14495
14922
|
};
|
|
14496
14923
|
/** Generates the Pursuit mission task symbol (344000). */
|
|
14497
|
-
function createPursuit(coordinates, options = {}) {
|
|
14924
|
+
function createPursuit(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
14498
14925
|
const [c1, c2, c3] = coordinates;
|
|
14499
14926
|
const p1 = project(c1[0], c1[1]);
|
|
14500
14927
|
const p2 = project(c2[0], c2[1]);
|
|
@@ -14515,8 +14942,10 @@ function createPursuit(coordinates, options = {}) {
|
|
|
14515
14942
|
features: []
|
|
14516
14943
|
};
|
|
14517
14944
|
const p3 = vecAdd(p2, vecScale(diameterAxis, signedDiameter));
|
|
14518
|
-
const
|
|
14519
|
-
const
|
|
14945
|
+
const labelPosition = clamp01(options.labelPosition ?? DEFAULT_PURSUIT_OPTIONS.labelPosition);
|
|
14946
|
+
const labelPoint = vecAdd(p1, vecScale(straightDirection, straightLength * labelPosition));
|
|
14947
|
+
const labelRotation = labelRotationAlong(straightDirection);
|
|
14948
|
+
const gapHalf = Math.min(straightLength * Math.min(labelPosition, 1 - labelPosition), labelKnockoutHalfExtent("P", straightDirection, labelRotation, options, context, DEFAULT_PURSUIT_OPTIONS.labelPadding));
|
|
14520
14949
|
const gapStart = vecAdd(labelPoint, vecScale(straightDirection, -gapHalf));
|
|
14521
14950
|
const gapEnd = vecAdd(labelPoint, vecScale(straightDirection, gapHalf));
|
|
14522
14951
|
const center = midpoint(p2, p3);
|
|
@@ -14543,7 +14972,7 @@ function createPursuit(coordinates, options = {}) {
|
|
|
14543
14972
|
terminalBar
|
|
14544
14973
|
]
|
|
14545
14974
|
}
|
|
14546
|
-
}, createLabelFeature(labelPoint, "P",
|
|
14975
|
+
}, createLabelFeature(labelPoint, "P", labelRotation, options)]
|
|
14547
14976
|
};
|
|
14548
14977
|
}
|
|
14549
14978
|
const PURSUIT = defineControlMeasure({
|
|
@@ -14581,27 +15010,80 @@ const REARWARD_PASSAGE_OF_LINES_METADATA = {
|
|
|
14581
15010
|
},
|
|
14582
15011
|
drawRule: "Line24",
|
|
14583
15012
|
capturesLabelSize: true,
|
|
15013
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
14584
15014
|
params: DELAY_PARAMS
|
|
14585
15015
|
};
|
|
14586
|
-
function createRearwardPassageOfLinesSymbol(coordinates, options = {}) {
|
|
15016
|
+
function createRearwardPassageOfLinesSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
14587
15017
|
return createDelayVariantSymbol(coordinates, {
|
|
14588
15018
|
glyph: "P(R)",
|
|
14589
15019
|
part: "rearward-passage-of-lines"
|
|
14590
|
-
}, options);
|
|
15020
|
+
}, options, context);
|
|
14591
15021
|
}
|
|
14592
15022
|
const REARWARD_PASSAGE_OF_LINES = defineControlMeasure({
|
|
14593
15023
|
metadata: REARWARD_PASSAGE_OF_LINES_METADATA,
|
|
14594
15024
|
generator: createRearwardPassageOfLinesSymbol,
|
|
14595
15025
|
defaultOptions: DEFAULT_REARWARD_PASSAGE_OF_LINES_OPTIONS,
|
|
14596
15026
|
rule: line24DrawRule,
|
|
14597
|
-
previewSample: {
|
|
14598
|
-
|
|
14599
|
-
|
|
14600
|
-
|
|
14601
|
-
|
|
14602
|
-
|
|
14603
|
-
|
|
14604
|
-
|
|
15027
|
+
previewSample: { controlPoints: [
|
|
15028
|
+
[-1, -.4],
|
|
15029
|
+
[1, -.4],
|
|
15030
|
+
[0, .6]
|
|
15031
|
+
] }
|
|
15032
|
+
});
|
|
15033
|
+
//#endregion
|
|
15034
|
+
//#region src/generators/cm34-mission-tasks/relief-in-place.ts
|
|
15035
|
+
const DEFAULT_RELIEF_IN_PLACE_OPTIONS = {
|
|
15036
|
+
arrowheadLengthRatio: DEFAULT_DELAY_OPTIONS.arrowheadLengthRatio,
|
|
15037
|
+
arrowheadWidthRatio: DEFAULT_DELAY_OPTIONS.arrowheadWidthRatio,
|
|
15038
|
+
arcSegments: DEFAULT_DELAY_OPTIONS.arcSegments
|
|
15039
|
+
};
|
|
15040
|
+
const RELIEF_IN_PLACE_METADATA = {
|
|
15041
|
+
id: "relief-in-place",
|
|
15042
|
+
name: "Relief in Place",
|
|
15043
|
+
description: "Replaces all or part of a unit in an area with an incoming unit while maintaining the original unit's responsibilities.",
|
|
15044
|
+
entity: "Mission Tasks",
|
|
15045
|
+
entityType: "Relief in Place",
|
|
15046
|
+
value: "341900",
|
|
15047
|
+
minCoordinates: 4,
|
|
15048
|
+
maxCoordinates: 4,
|
|
15049
|
+
geometry: "line",
|
|
15050
|
+
geometryTypes: ["MultiLineString", "Point"],
|
|
15051
|
+
paints: {
|
|
15052
|
+
stroke: true,
|
|
15053
|
+
fill: "none",
|
|
15054
|
+
text: true
|
|
15055
|
+
},
|
|
15056
|
+
drawRule: "Area18",
|
|
15057
|
+
capturesLabelSize: true,
|
|
15058
|
+
params: DELAY_PARAMS.filter((param) => param.key !== "labelPadding")
|
|
15059
|
+
};
|
|
15060
|
+
/** Generates the RELIEF IN PLACE mission task symbol (341900). */
|
|
15061
|
+
function createReliefInPlaceSymbol(coordinates, options = {}) {
|
|
15062
|
+
return createHairpinSymbol(coordinates, {
|
|
15063
|
+
part: "relief-in-place",
|
|
15064
|
+
buildLabel: ({ points, upperDirection, upperArm }) => ({
|
|
15065
|
+
upperArm,
|
|
15066
|
+
label: createLabelFeature(averagePoints(points), "RIP", labelRotationAlong(upperDirection), {
|
|
15067
|
+
labelSize: options.labelSize,
|
|
15068
|
+
labelSizePixels: options.labelSizePixels
|
|
15069
|
+
})
|
|
15070
|
+
})
|
|
15071
|
+
}, options);
|
|
15072
|
+
}
|
|
15073
|
+
function averagePoints(points) {
|
|
15074
|
+
return vecScale(points.reduce((sum, point) => vecAdd(sum, point), [0, 0]), 1 / points.length);
|
|
15075
|
+
}
|
|
15076
|
+
const RELIEF_IN_PLACE = defineControlMeasure({
|
|
15077
|
+
metadata: RELIEF_IN_PLACE_METADATA,
|
|
15078
|
+
generator: createReliefInPlaceSymbol,
|
|
15079
|
+
defaultOptions: DEFAULT_RELIEF_IN_PLACE_OPTIONS,
|
|
15080
|
+
rule: reliefInPlaceDrawRule,
|
|
15081
|
+
previewSample: { controlPoints: [
|
|
15082
|
+
[-1, .45],
|
|
15083
|
+
[.45, .45],
|
|
15084
|
+
[.45, -.45],
|
|
15085
|
+
[-1, -.45]
|
|
15086
|
+
] }
|
|
14605
15087
|
});
|
|
14606
15088
|
//#endregion
|
|
14607
15089
|
//#region src/generators/cm34-mission-tasks/retire.ts
|
|
@@ -14624,14 +15106,15 @@ const RETIRE_METADATA = {
|
|
|
14624
15106
|
},
|
|
14625
15107
|
drawRule: "Line24",
|
|
14626
15108
|
capturesLabelSize: true,
|
|
15109
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
14627
15110
|
params: DELAY_PARAMS
|
|
14628
15111
|
};
|
|
14629
15112
|
/** Generates the RETIRE/RETIREMENT mission task symbol (342000). */
|
|
14630
|
-
function createRetireSymbol(coordinates, options = {}) {
|
|
15113
|
+
function createRetireSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
14631
15114
|
return createDelayVariantSymbol(coordinates, {
|
|
14632
15115
|
glyph: "R",
|
|
14633
15116
|
part: "retire"
|
|
14634
|
-
}, options);
|
|
15117
|
+
}, options, context);
|
|
14635
15118
|
}
|
|
14636
15119
|
const RETIRE = defineControlMeasure({
|
|
14637
15120
|
metadata: RETIRE_METADATA,
|
|
@@ -14685,11 +15168,137 @@ const SCREEN = defineControlMeasure({
|
|
|
14685
15168
|
}
|
|
14686
15169
|
});
|
|
14687
15170
|
//#endregion
|
|
15171
|
+
//#region src/generators/cm34-mission-tasks/secure.ts
|
|
15172
|
+
const DEFAULT_SECURE_OPTIONS = {
|
|
15173
|
+
openingAngle: 30,
|
|
15174
|
+
resolution: 64,
|
|
15175
|
+
arrowheadLengthRatio: .2,
|
|
15176
|
+
arrowheadWidthRatio: .4,
|
|
15177
|
+
labelPadding: .2
|
|
15178
|
+
};
|
|
15179
|
+
const SECURE_METADATA = {
|
|
15180
|
+
id: "secure",
|
|
15181
|
+
name: "Secure",
|
|
15182
|
+
description: "Prevents a unit, facility, or geographical location from being damaged or destroyed by enemy action.",
|
|
15183
|
+
entity: "Mission Tasks",
|
|
15184
|
+
entityType: "Secure",
|
|
15185
|
+
value: "342100",
|
|
15186
|
+
minCoordinates: 2,
|
|
15187
|
+
maxCoordinates: 2,
|
|
15188
|
+
geometry: "area",
|
|
15189
|
+
geometryTypes: ["MultiLineString", "Point"],
|
|
15190
|
+
paints: {
|
|
15191
|
+
stroke: true,
|
|
15192
|
+
fill: "none",
|
|
15193
|
+
text: true
|
|
15194
|
+
},
|
|
15195
|
+
drawRule: "Area19",
|
|
15196
|
+
capturesLabelSize: true,
|
|
15197
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
15198
|
+
params: [
|
|
15199
|
+
{
|
|
15200
|
+
key: "openingAngle",
|
|
15201
|
+
presentationTier: "advanced",
|
|
15202
|
+
label: "Opening",
|
|
15203
|
+
description: "Angular width of the opening on the friendly side, in degrees",
|
|
15204
|
+
type: "number",
|
|
15205
|
+
min: 5,
|
|
15206
|
+
max: 180,
|
|
15207
|
+
step: 1
|
|
15208
|
+
},
|
|
15209
|
+
...ARC_RESOLUTION_PARAMS,
|
|
15210
|
+
{
|
|
15211
|
+
key: "arrowheadLengthRatio",
|
|
15212
|
+
presentationTier: "advanced",
|
|
15213
|
+
label: "Arrowhead length",
|
|
15214
|
+
description: "Arrowhead length as a ratio of the symbol radius",
|
|
15215
|
+
type: "number",
|
|
15216
|
+
min: 0,
|
|
15217
|
+
max: 1,
|
|
15218
|
+
step: .01
|
|
15219
|
+
},
|
|
15220
|
+
{
|
|
15221
|
+
key: "arrowheadWidthRatio",
|
|
15222
|
+
presentationTier: "advanced",
|
|
15223
|
+
label: "Arrowhead width",
|
|
15224
|
+
description: "Arrowhead width as a ratio of the symbol radius",
|
|
15225
|
+
type: "number",
|
|
15226
|
+
min: 0,
|
|
15227
|
+
max: 1,
|
|
15228
|
+
step: .01
|
|
15229
|
+
},
|
|
15230
|
+
{
|
|
15231
|
+
key: "labelPadding",
|
|
15232
|
+
presentationTier: "advanced",
|
|
15233
|
+
label: "S padding",
|
|
15234
|
+
description: "Extra boundary clearance on each side of S, as a ratio of its height",
|
|
15235
|
+
type: "number",
|
|
15236
|
+
min: 0,
|
|
15237
|
+
max: 2,
|
|
15238
|
+
step: .05
|
|
15239
|
+
}
|
|
15240
|
+
]
|
|
15241
|
+
};
|
|
15242
|
+
const TAU$1 = Math.PI * 2;
|
|
15243
|
+
const MIN_LABEL_GAP_METERS$1 = 1;
|
|
15244
|
+
/** Generates the Secure mission task symbol (342100). */
|
|
15245
|
+
function createSecureSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
15246
|
+
const center = project(coordinates[0][0], coordinates[0][1]);
|
|
15247
|
+
const radiusVector = vecSub(project(coordinates[1][0], coordinates[1][1]), center);
|
|
15248
|
+
const radius = vecMag(radiusVector);
|
|
15249
|
+
if (radius < 1e-6) return {
|
|
15250
|
+
type: "FeatureCollection",
|
|
15251
|
+
features: []
|
|
15252
|
+
};
|
|
15253
|
+
const resolved = {
|
|
15254
|
+
...DEFAULT_SECURE_OPTIONS,
|
|
15255
|
+
...options
|
|
15256
|
+
};
|
|
15257
|
+
const bulgeVector = [radiusVector[1], -radiusVector[0]];
|
|
15258
|
+
const halfTurnResolution = normalizeArcResolution(resolved.resolution) / 2;
|
|
15259
|
+
const boundarySpan = TAU$1 - Math.min(180, Math.max(5, resolved.openingAngle)) * Math.PI / 180;
|
|
15260
|
+
const labelSweep = boundarySpan / 2;
|
|
15261
|
+
const labelHeight = resolveTextMetrics("S", options, context, "regular", radius * .12).height;
|
|
15262
|
+
const labelPadding = Math.max(0, resolved.labelPadding);
|
|
15263
|
+
const labelHalfGap = Math.min(boundarySpan / 4, Math.max(labelHeight * (LABEL_HALF_GLYPH_RATIO + labelPadding), MIN_LABEL_GAP_METERS$1) / radius);
|
|
15264
|
+
const sampleArc = (from, to) => sampleProjectedArc(center, radiusVector, bulgeVector, from, to, halfTurnResolution);
|
|
15265
|
+
const terminal = arcPointAt(center, radiusVector, bulgeVector, boundarySpan);
|
|
15266
|
+
const radial = vecScale(vecSub(terminal, center), 1 / radius);
|
|
15267
|
+
const tangent = [radial[1], -radial[0]];
|
|
15268
|
+
const lines = [
|
|
15269
|
+
sampleArc(0, labelSweep - labelHalfGap),
|
|
15270
|
+
sampleArc(labelSweep + labelHalfGap, boundarySpan),
|
|
15271
|
+
createArrowHead$1(terminal, tangent, radius * Math.max(0, resolved.arrowheadLengthRatio), radius * Math.max(0, resolved.arrowheadWidthRatio))
|
|
15272
|
+
];
|
|
15273
|
+
const labelPoint = arcPointAt(center, radiusVector, bulgeVector, labelSweep);
|
|
15274
|
+
return {
|
|
15275
|
+
type: "FeatureCollection",
|
|
15276
|
+
features: [{
|
|
15277
|
+
type: "Feature",
|
|
15278
|
+
properties: { part: "secure" },
|
|
15279
|
+
geometry: {
|
|
15280
|
+
type: "MultiLineString",
|
|
15281
|
+
coordinates: lines
|
|
15282
|
+
}
|
|
15283
|
+
}, createLabelFeature(labelPoint, "S", Math.PI, options)]
|
|
15284
|
+
};
|
|
15285
|
+
}
|
|
15286
|
+
const SECURE = defineControlMeasure({
|
|
15287
|
+
metadata: SECURE_METADATA,
|
|
15288
|
+
generator: createSecureSymbol,
|
|
15289
|
+
defaultOptions: DEFAULT_SECURE_OPTIONS,
|
|
15290
|
+
rule: secureDrawRule,
|
|
15291
|
+
previewSample: {
|
|
15292
|
+
controlPoints: [[0, 0], [-.8, .05]],
|
|
15293
|
+
options: { labelSize: 90 }
|
|
15294
|
+
}
|
|
15295
|
+
});
|
|
15296
|
+
//#endregion
|
|
14688
15297
|
//#region src/generators/cm34-mission-tasks/seize.ts
|
|
14689
15298
|
const DEFAULT_SEIZE_OPTIONS = {
|
|
14690
15299
|
arrowheadLengthRatio: .1,
|
|
14691
15300
|
arrowheadWidthRatio: .12,
|
|
14692
|
-
|
|
15301
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING,
|
|
14693
15302
|
curveSegments: 48,
|
|
14694
15303
|
circleSegments: 64
|
|
14695
15304
|
};
|
|
@@ -14714,16 +15323,7 @@ const SEIZE_PARAMS = [
|
|
|
14714
15323
|
max: .5,
|
|
14715
15324
|
step: .01
|
|
14716
15325
|
},
|
|
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
|
-
},
|
|
15326
|
+
LABEL_PADDING_PARAM,
|
|
14727
15327
|
{
|
|
14728
15328
|
key: "curveSegments",
|
|
14729
15329
|
presentationTier: "advanced",
|
|
@@ -14763,6 +15363,7 @@ const SEIZE_METADATA = {
|
|
|
14763
15363
|
},
|
|
14764
15364
|
drawRule: "Line27",
|
|
14765
15365
|
capturesLabelSize: true,
|
|
15366
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
14766
15367
|
params: SEIZE_PARAMS
|
|
14767
15368
|
};
|
|
14768
15369
|
function positiveAngle(angle) {
|
|
@@ -14794,7 +15395,7 @@ function threePointArc(start, middle, end) {
|
|
|
14794
15395
|
};
|
|
14795
15396
|
}
|
|
14796
15397
|
/** Shared geometry for mission tasks that use the Seize graphic. */
|
|
14797
|
-
function createSeizeLikeSymbol(coordinates, options = {}, label = "S", part = "seize") {
|
|
15398
|
+
function createSeizeLikeSymbol(coordinates, options = {}, label = "S", part = "seize", context = {}) {
|
|
14798
15399
|
const [c1, c2, c3, c4] = coordinates;
|
|
14799
15400
|
const center = project(c1[0], c1[1]);
|
|
14800
15401
|
const start = project(c2[0], c2[1]);
|
|
@@ -14815,8 +15416,14 @@ function createSeizeLikeSymbol(coordinates, options = {}, label = "S", part = "s
|
|
|
14815
15416
|
let arrowDirection;
|
|
14816
15417
|
let labelPoint;
|
|
14817
15418
|
let tangentAtLabel;
|
|
15419
|
+
let labelRotation;
|
|
14818
15420
|
if (arc) {
|
|
14819
|
-
const
|
|
15421
|
+
const labelAngle = arc.startAngle + arc.sweepAngle / 2;
|
|
15422
|
+
const sweepSign = Math.sign(arc.sweepAngle);
|
|
15423
|
+
labelPoint = vecAdd(arc.center, [arc.radius * Math.cos(labelAngle), arc.radius * Math.sin(labelAngle)]);
|
|
15424
|
+
tangentAtLabel = [-Math.sin(labelAngle) * sweepSign, Math.cos(labelAngle) * sweepSign];
|
|
15425
|
+
labelRotation = labelRotationAlong(tangentAtLabel);
|
|
15426
|
+
const gapHalf = arcKnockoutHalfFraction(labelKnockoutHalfExtent(label, tangentAtLabel, labelRotation, options, context, DEFAULT_SEIZE_OPTIONS.labelPadding), arc.radius, arc.sweepAngle);
|
|
14820
15427
|
const gapStart = .5 - gapHalf;
|
|
14821
15428
|
const gapEnd = .5 + gapHalf;
|
|
14822
15429
|
const sampleArc = (from, to) => {
|
|
@@ -14832,22 +15439,19 @@ function createSeizeLikeSymbol(coordinates, options = {}, label = "S", part = "s
|
|
|
14832
15439
|
firstArcPart = sampleArc(0, gapStart);
|
|
14833
15440
|
secondArcPart = sampleArc(gapEnd, 1);
|
|
14834
15441
|
const endAngle = arc.startAngle + arc.sweepAngle;
|
|
14835
|
-
const sweepSign = Math.sign(arc.sweepAngle);
|
|
14836
15442
|
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
15443
|
} else {
|
|
14841
15444
|
labelPoint = vecScale(vecAdd(start, tip), .5);
|
|
14842
|
-
const gapHalf = span * clamp01(options.labelGapRatio ?? DEFAULT_SEIZE_OPTIONS.labelGapRatio) / 2;
|
|
14843
15445
|
const direction = vecNorm(vecSub(tip, start));
|
|
15446
|
+
tangentAtLabel = direction;
|
|
15447
|
+
labelRotation = labelRotationAlong(tangentAtLabel);
|
|
15448
|
+
const gapHalf = Math.min(span / 2, labelKnockoutHalfExtent(label, direction, labelRotation, options, context, DEFAULT_SEIZE_OPTIONS.labelPadding));
|
|
14844
15449
|
const before = vecAdd(labelPoint, vecScale(direction, -gapHalf));
|
|
14845
15450
|
const after = vecAdd(labelPoint, vecScale(direction, gapHalf));
|
|
14846
15451
|
firstArcPart = [toPosition(start), toPosition(before)];
|
|
14847
15452
|
secondArcPart = [toPosition(after), toPosition(tip)];
|
|
14848
15453
|
const terminal = vecSub(tip, curvePoint);
|
|
14849
15454
|
arrowDirection = vecMag(terminal) >= 1e-6 ? vecNorm(terminal) : vecNorm(vecSub(tip, start));
|
|
14850
|
-
tangentAtLabel = direction;
|
|
14851
15455
|
}
|
|
14852
15456
|
const startAngle = Math.atan2(radiusVector[1], radiusVector[0]);
|
|
14853
15457
|
const circle = [];
|
|
@@ -14870,12 +15474,12 @@ function createSeizeLikeSymbol(coordinates, options = {}, label = "S", part = "s
|
|
|
14870
15474
|
type: "MultiLineString",
|
|
14871
15475
|
coordinates: lines
|
|
14872
15476
|
}
|
|
14873
|
-
}, createLabelFeature(labelPoint, label,
|
|
15477
|
+
}, createLabelFeature(labelPoint, label, labelRotation, options)]
|
|
14874
15478
|
};
|
|
14875
15479
|
}
|
|
14876
15480
|
/** Generates the Seize mission task symbol (342300). */
|
|
14877
|
-
function createSeizeSymbol(coordinates, options = {}) {
|
|
14878
|
-
return createSeizeLikeSymbol(coordinates, options);
|
|
15481
|
+
function createSeizeSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
15482
|
+
return createSeizeLikeSymbol(coordinates, options, "S", "seize", context);
|
|
14879
15483
|
}
|
|
14880
15484
|
const SEIZE = defineControlMeasure({
|
|
14881
15485
|
metadata: SEIZE_METADATA,
|
|
@@ -14913,11 +15517,12 @@ const EVACUATE_METADATA = {
|
|
|
14913
15517
|
},
|
|
14914
15518
|
drawRule: "Line27",
|
|
14915
15519
|
capturesLabelSize: true,
|
|
15520
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
14916
15521
|
params: SEIZE_PARAMS
|
|
14917
15522
|
};
|
|
14918
15523
|
/** Generates the Evacuate mission task symbol (344500). */
|
|
14919
|
-
function createEvacuateSymbol(coordinates, options = {}) {
|
|
14920
|
-
return createSeizeLikeSymbol(coordinates, options, "E", "evacuate");
|
|
15524
|
+
function createEvacuateSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
15525
|
+
return createSeizeLikeSymbol(coordinates, options, "E", "evacuate", context);
|
|
14921
15526
|
}
|
|
14922
15527
|
const EVACUATE = defineControlMeasure({
|
|
14923
15528
|
metadata: EVACUATE_METADATA,
|
|
@@ -14955,13 +15560,14 @@ const WITHDRAW_METADATA = {
|
|
|
14955
15560
|
},
|
|
14956
15561
|
drawRule: "Line24",
|
|
14957
15562
|
capturesLabelSize: true,
|
|
15563
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
14958
15564
|
params: DELAY_PARAMS
|
|
14959
15565
|
};
|
|
14960
|
-
function createWithdrawSymbol(coordinates, options = {}) {
|
|
15566
|
+
function createWithdrawSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
14961
15567
|
return createDelayVariantSymbol(coordinates, {
|
|
14962
15568
|
glyph: "W",
|
|
14963
15569
|
part: "withdraw"
|
|
14964
|
-
}, options);
|
|
15570
|
+
}, options, context);
|
|
14965
15571
|
}
|
|
14966
15572
|
const WITHDRAW = defineControlMeasure({
|
|
14967
15573
|
metadata: WITHDRAW_METADATA,
|
|
@@ -14990,27 +15596,25 @@ const WITHDRAW_UNDER_PRESSURE_METADATA = {
|
|
|
14990
15596
|
},
|
|
14991
15597
|
drawRule: "Line24",
|
|
14992
15598
|
capturesLabelSize: true,
|
|
15599
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
14993
15600
|
params: DELAY_PARAMS
|
|
14994
15601
|
};
|
|
14995
|
-
function createWithdrawUnderPressureSymbol(coordinates, options = {}) {
|
|
15602
|
+
function createWithdrawUnderPressureSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
14996
15603
|
return createDelayVariantSymbol(coordinates, {
|
|
14997
15604
|
glyph: "WP",
|
|
14998
15605
|
part: "withdraw-under-pressure"
|
|
14999
|
-
}, options);
|
|
15606
|
+
}, options, context);
|
|
15000
15607
|
}
|
|
15001
15608
|
const WITHDRAW_UNDER_PRESSURE = defineControlMeasure({
|
|
15002
15609
|
metadata: WITHDRAW_UNDER_PRESSURE_METADATA,
|
|
15003
15610
|
generator: createWithdrawUnderPressureSymbol,
|
|
15004
15611
|
defaultOptions: DEFAULT_WITHDRAW_UNDER_PRESSURE_OPTIONS,
|
|
15005
15612
|
rule: line24DrawRule,
|
|
15006
|
-
previewSample: {
|
|
15007
|
-
|
|
15008
|
-
|
|
15009
|
-
|
|
15010
|
-
|
|
15011
|
-
],
|
|
15012
|
-
options: { labelGapRatio: .3 }
|
|
15013
|
-
}
|
|
15613
|
+
previewSample: { controlPoints: [
|
|
15614
|
+
[-1, -.4],
|
|
15615
|
+
[1, -.4],
|
|
15616
|
+
[0, .6]
|
|
15617
|
+
] }
|
|
15014
15618
|
});
|
|
15015
15619
|
//#endregion
|
|
15016
15620
|
//#region src/generators/cm27-protection-areas/turn.ts
|
|
@@ -15076,10 +15680,10 @@ const TURN_METADATA = {
|
|
|
15076
15680
|
* arc and placing its tip at PT1. PT3 selects which side of the PT2 -> PT1
|
|
15077
15681
|
* chord contains the arc.
|
|
15078
15682
|
*
|
|
15079
|
-
* `
|
|
15080
|
-
*
|
|
15683
|
+
* `labelGapHalfLength` optionally resolves a meter-denominated half-gap from the arc midpoint
|
|
15684
|
+
* anchor for measures that carry a doctrinal glyph there.
|
|
15081
15685
|
*/
|
|
15082
|
-
function createTurnLikeSymbol(coordinates, options = {},
|
|
15686
|
+
function createTurnLikeSymbol(coordinates, options = {}, labelGapHalfLength) {
|
|
15083
15687
|
const tip = project(coordinates[0][0], coordinates[0][1]);
|
|
15084
15688
|
const rear = project(coordinates[1][0], coordinates[1][1]);
|
|
15085
15689
|
const side = quarterArcSide(rear, tip, coordinates[2] ? project(coordinates[2][0], coordinates[2][1]) : void 0);
|
|
@@ -15104,14 +15708,14 @@ function createTurnLikeSymbol(coordinates, options = {}, labelGapRatio = 0) {
|
|
|
15104
15708
|
}
|
|
15105
15709
|
return points;
|
|
15106
15710
|
};
|
|
15107
|
-
const gapHalf = clamp01(labelGapRatio) / 2;
|
|
15108
|
-
const arcParts = gapHalf > 0 ? [sampleArc(0, .5 - gapHalf), sampleArc(.5 + gapHalf, 1)] : [sampleArc(0, 1)];
|
|
15109
15711
|
const sweepSign = Math.sign(arc.sweepAngle);
|
|
15110
15712
|
const midAngle = arc.startAngle + arc.sweepAngle / 2;
|
|
15111
15713
|
const labelAnchor = {
|
|
15112
15714
|
point: arc.midpoint,
|
|
15113
15715
|
tangent: [-Math.sin(midAngle) * sweepSign, Math.cos(midAngle) * sweepSign]
|
|
15114
15716
|
};
|
|
15717
|
+
const gapHalf = arcKnockoutHalfFraction(labelGapHalfLength?.(labelAnchor) ?? 0, arc.radius, arc.sweepAngle);
|
|
15718
|
+
const arcParts = gapHalf > 0 ? [sampleArc(0, .5 - gapHalf), sampleArc(.5 + gapHalf, 1)] : [sampleArc(0, 1)];
|
|
15115
15719
|
const headWidth = totalLength * (options.arrowheadWidthRatio ?? DEFAULT_TURN_OPTIONS.arrowheadWidthRatio);
|
|
15116
15720
|
const arrowHead = createArrowHeadRing(tip, vecNorm(vecSub(tip, headBase)), vecMag(vecSub(tip, headBase)), headWidth);
|
|
15117
15721
|
return {
|
|
@@ -15165,7 +15769,7 @@ const DEFAULT_TURN_MISSION_TASK_OPTIONS = {
|
|
|
15165
15769
|
...DEFAULT_TURN_OPTIONS,
|
|
15166
15770
|
arrowheadLengthRatio: .14,
|
|
15167
15771
|
arrowheadWidthRatio: .14,
|
|
15168
|
-
|
|
15772
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING
|
|
15169
15773
|
};
|
|
15170
15774
|
const TURN_MISSION_TASK_METADATA = {
|
|
15171
15775
|
id: "turn-mission-task",
|
|
@@ -15188,24 +15792,19 @@ const TURN_MISSION_TASK_METADATA = {
|
|
|
15188
15792
|
},
|
|
15189
15793
|
drawRule: "Line10",
|
|
15190
15794
|
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
|
-
}]
|
|
15795
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
15796
|
+
params: [...TURN_PARAMS, LABEL_PADDING_PARAM]
|
|
15201
15797
|
};
|
|
15202
15798
|
/** Generates the TURN mission task symbol (344700). */
|
|
15203
|
-
function createTurnMissionTaskSymbol(coordinates, options = {}) {
|
|
15799
|
+
function createTurnMissionTaskSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
15204
15800
|
const { features, labelAnchor } = createTurnLikeSymbol(coordinates, {
|
|
15205
15801
|
arrowheadLengthRatio: options.arrowheadLengthRatio ?? DEFAULT_TURN_MISSION_TASK_OPTIONS.arrowheadLengthRatio,
|
|
15206
15802
|
arrowheadWidthRatio: options.arrowheadWidthRatio ?? DEFAULT_TURN_MISSION_TASK_OPTIONS.arrowheadWidthRatio,
|
|
15207
15803
|
arcSegments: options.arcSegments ?? DEFAULT_TURN_MISSION_TASK_OPTIONS.arcSegments
|
|
15208
|
-
},
|
|
15804
|
+
}, (anchor) => {
|
|
15805
|
+
const rotation = labelRotationAlong(anchor.tangent);
|
|
15806
|
+
return labelKnockoutHalfExtent("T", anchor.tangent, rotation, options, context, DEFAULT_TURN_MISSION_TASK_OPTIONS.labelPadding);
|
|
15807
|
+
});
|
|
15209
15808
|
if (!labelAnchor) return {
|
|
15210
15809
|
type: "FeatureCollection",
|
|
15211
15810
|
features: []
|
|
@@ -15251,6 +15850,7 @@ const JOINT_TACTICAL_ACTION_AREA_METADATA = {
|
|
|
15251
15850
|
},
|
|
15252
15851
|
drawRule: "Area1",
|
|
15253
15852
|
capturesLabelSize: true,
|
|
15853
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
15254
15854
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
15255
15855
|
textAmplifiers: AREA_TEXT_AMPLIFIERS
|
|
15256
15856
|
};
|
|
@@ -15311,6 +15911,7 @@ const LANDING_ZONE_METADATA = {
|
|
|
15311
15911
|
},
|
|
15312
15912
|
drawRule: "Area1",
|
|
15313
15913
|
capturesLabelSize: true,
|
|
15914
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
15314
15915
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
15315
15916
|
textAmplifiers: AREA_TEXT_AMPLIFIERS
|
|
15316
15917
|
};
|
|
@@ -15366,6 +15967,7 @@ const PENETRATION_BOX_METADATA = {
|
|
|
15366
15967
|
},
|
|
15367
15968
|
drawRule: "Area1",
|
|
15368
15969
|
capturesLabelSize: true,
|
|
15970
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
15369
15971
|
params: LABELED_AREA_PARAMS,
|
|
15370
15972
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
15371
15973
|
};
|
|
@@ -15417,6 +16019,7 @@ const LIMITED_ACCESS_AREA_METADATA = {
|
|
|
15417
16019
|
},
|
|
15418
16020
|
drawRule: "Area1",
|
|
15419
16021
|
capturesLabelSize: true,
|
|
16022
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
15420
16023
|
params: [...AREA_SMOOTH_PARAMS, AREA_LABEL_PADDING_PARAM],
|
|
15421
16024
|
textAmplifiers: [AREA_HOSTILE_AMPLIFIER]
|
|
15422
16025
|
};
|
|
@@ -15512,6 +16115,7 @@ const NO_FIRE_AREA_IRREGULAR_METADATA = {
|
|
|
15512
16115
|
},
|
|
15513
16116
|
drawRule: "Area1",
|
|
15514
16117
|
capturesLabelSize: true,
|
|
16118
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
15515
16119
|
params: [...AREA_SMOOTH_PARAMS, AREA_LABEL_PADDING_PARAM],
|
|
15516
16120
|
textAmplifiers: [...NO_FIRE_AREA_TEXT_AMPLIFIERS, AREA_HOSTILE_AMPLIFIER]
|
|
15517
16121
|
};
|
|
@@ -15757,6 +16361,7 @@ const MINEFIELD_METADATA = {
|
|
|
15757
16361
|
text: true
|
|
15758
16362
|
},
|
|
15759
16363
|
drawRule: "Point2",
|
|
16364
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
15760
16365
|
params: [
|
|
15761
16366
|
MINE_TYPE_PARAM,
|
|
15762
16367
|
{
|
|
@@ -16059,6 +16664,7 @@ const COMMON_METADATA = {
|
|
|
16059
16664
|
},
|
|
16060
16665
|
drawRule: "Area1",
|
|
16061
16666
|
capturesLabelSize: true,
|
|
16667
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
16062
16668
|
params: [
|
|
16063
16669
|
MINE_TYPE_PARAM,
|
|
16064
16670
|
...AREA_SMOOTH_PARAMS,
|
|
@@ -16081,7 +16687,8 @@ const MINED_AREA_METADATA = {
|
|
|
16081
16687
|
name: "Mined Area",
|
|
16082
16688
|
description: "An area dangerous because of the presence or suspected presence of mines.",
|
|
16083
16689
|
entityType: "Mined Area",
|
|
16084
|
-
value: "270800"
|
|
16690
|
+
value: "270800",
|
|
16691
|
+
labelGeometryUsesResolvedMetrics: true
|
|
16085
16692
|
};
|
|
16086
16693
|
function axisIntersections(ring, axis, target) {
|
|
16087
16694
|
const other = axis === 0 ? 1 : 0;
|
|
@@ -16628,6 +17235,7 @@ const PICKUP_ZONE_METADATA = {
|
|
|
16628
17235
|
},
|
|
16629
17236
|
drawRule: "Area1",
|
|
16630
17237
|
capturesLabelSize: true,
|
|
17238
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
16631
17239
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
16632
17240
|
textAmplifiers: AREA_TEXT_AMPLIFIERS
|
|
16633
17241
|
};
|
|
@@ -16683,6 +17291,7 @@ const ASSAULT_POSITION_METADATA = {
|
|
|
16683
17291
|
},
|
|
16684
17292
|
drawRule: "Area1",
|
|
16685
17293
|
capturesLabelSize: true,
|
|
17294
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
16686
17295
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
16687
17296
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
16688
17297
|
};
|
|
@@ -16738,6 +17347,7 @@ const ATTACK_POSITION_METADATA = {
|
|
|
16738
17347
|
},
|
|
16739
17348
|
drawRule: "Area1",
|
|
16740
17349
|
capturesLabelSize: true,
|
|
17350
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
16741
17351
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
16742
17352
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
16743
17353
|
};
|
|
@@ -16793,6 +17403,7 @@ const OBJECTIVE_AREA_METADATA = {
|
|
|
16793
17403
|
},
|
|
16794
17404
|
drawRule: "Area1",
|
|
16795
17405
|
capturesLabelSize: true,
|
|
17406
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
16796
17407
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
16797
17408
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
16798
17409
|
};
|
|
@@ -17142,6 +17753,7 @@ const STRONG_POINT_METADATA = {
|
|
|
17142
17753
|
},
|
|
17143
17754
|
drawRule: "Area1",
|
|
17144
17755
|
capturesLabelSize: true,
|
|
17756
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
17145
17757
|
params: ECHELON_AREA_PARAMS,
|
|
17146
17758
|
textAmplifiers: AREA_UNIT_DESIGNATION_HOSTILE_AMPLIFIERS
|
|
17147
17759
|
};
|
|
@@ -17463,6 +18075,7 @@ const SUBMARINE_ACTION_AREA_METADATA = {
|
|
|
17463
18075
|
},
|
|
17464
18076
|
drawRule: "Area1",
|
|
17465
18077
|
capturesLabelSize: true,
|
|
18078
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
17466
18079
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
17467
18080
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_NO_ADDITIONAL_INFO
|
|
17468
18081
|
};
|
|
@@ -17523,6 +18136,7 @@ const SUBMARINE_GENERATED_ACTION_AREA_METADATA = {
|
|
|
17523
18136
|
},
|
|
17524
18137
|
drawRule: "Area1",
|
|
17525
18138
|
capturesLabelSize: true,
|
|
18139
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
17526
18140
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
17527
18141
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_NO_ADDITIONAL_INFO
|
|
17528
18142
|
};
|
|
@@ -17845,13 +18459,17 @@ const DEFINITIONS = {
|
|
|
17845
18459
|
bypass: BYPASS,
|
|
17846
18460
|
canalize: CANALIZE,
|
|
17847
18461
|
clear: CLEAR,
|
|
18462
|
+
"cordon-and-knock": CORDON_AND_KNOCK,
|
|
18463
|
+
"cordon-and-search": CORDON_AND_SEARCH,
|
|
17848
18464
|
counterattack: COUNTERATTACK,
|
|
17849
18465
|
"counterattack-by-fire": COUNTERATTACK_BY_FIRE,
|
|
17850
18466
|
cover: COVER,
|
|
17851
18467
|
delay: DELAY,
|
|
18468
|
+
demonstrate: DEMONSTRATE,
|
|
17852
18469
|
disengage: DISENGAGE,
|
|
17853
18470
|
"disrupt-mission-task": DISRUPT_MISSION_TASK,
|
|
17854
18471
|
envelopment: ENVELOPMENT,
|
|
18472
|
+
exploitation: EXPLOITATION,
|
|
17855
18473
|
"forward-passage-of-lines": FORWARD_PASSAGE_OF_LINES,
|
|
17856
18474
|
infiltration: INFILTRATION,
|
|
17857
18475
|
guard: GUARD,
|
|
@@ -17861,8 +18479,10 @@ const DEFINITIONS = {
|
|
|
17861
18479
|
penetrate: PENETRATE,
|
|
17862
18480
|
pursuit: PURSUIT,
|
|
17863
18481
|
"rearward-passage-of-lines": REARWARD_PASSAGE_OF_LINES,
|
|
18482
|
+
"relief-in-place": RELIEF_IN_PLACE,
|
|
17864
18483
|
retire: RETIRE,
|
|
17865
18484
|
screen: SCREEN,
|
|
18485
|
+
secure: SECURE,
|
|
17866
18486
|
seize: SEIZE,
|
|
17867
18487
|
evacuate: EVACUATE,
|
|
17868
18488
|
withdraw: WITHDRAW,
|
|
@@ -18292,4 +18912,4 @@ function assertNever(value) {
|
|
|
18292
18912
|
throw new Error(`Unhandled control measure kind: ${String(value)}`);
|
|
18293
18913
|
}
|
|
18294
18914
|
//#endregion
|
|
18295
|
-
export {
|
|
18915
|
+
export { DEFAULT_PHASE_LINE_OPTIONS as $, DEFAULT_DIRECTION_OF_ATTACK_AVIATION_OPTIONS as $t, DEFAULT_SECURE_OPTIONS as A, secureDrawRule as An, DEFAULT_CANALIZE_OPTIONS as At, DEFAULT_FORTIFIED_LINE_OPTIONS as B, getMidpointPerpendicularSignedDistance as Bn, DEFAULT_CONTAIN_OPTIONS as Bt, DEFAULT_LANDING_ZONE_OPTIONS as C, line1DrawRule as Cn, DEFAULT_GENERIC_TEXT_OPTIONS as Ct, DEFAULT_WITHDRAW_OPTIONS as D, sectorDrawRule as Dn, DEFAULT_GENERIC_LINE_OPTIONS as Dt, DEFAULT_WITHDRAW_UNDER_PRESSURE_OPTIONS as E, dynamicPointDrawRule as En, DEFAULT_GENERIC_POLYGON_OPTIONS as Et, DEFAULT_PENETRATE_OPTIONS as F, centerRadiusDrawRule as Fn, DEFAULT_ENGINEER_WORK_LINE_OPTIONS as Ft, DEFAULT_LIMIT_OF_ADVANCE_OPTIONS as G, computeInitialWidthPoint as Gn, DEFAULT_ANTITANK_DITCH_OPTIONS as Gt, PROBABLE_LINE_OF_DEPLOYMENT_DASH as H, snapToMidpointPerpendicular as Hn, DEFAULT_ATTACK_HELICOPTER_OPTIONS as Ht, DEFAULT_OCCUPY_OPTIONS as I, containDrawRule as In, DEFAULT_LIGHT_LINE_OPTIONS as It, DEFAULT_FORWARD_EDGE_OF_BATTLE_AREA_OPTIONS as J, sphericalBearing as Jn, DEFAULT_MOBILE_DEFENSE_OPTIONS as Jt, DEFAULT_BATTLE_HANDOVER_LINE_OPTIONS as K, haversineDistance as Kn, DEFAULT_ASSEMBLY_AREA_OPTIONS as Kt, DEFAULT_MOVEMENT_TO_CONTACT_OPTIONS as L, area27DrawRule as Ln, DEFAULT_BOUNDARY_OPTIONS as Lt, DEFAULT_RETIRE_OPTIONS as M, occupyDrawRule as Mn, DEFAULT_BREACH_OPTIONS as Mt, DEFAULT_RELIEF_IN_PLACE_OPTIONS as N, disruptDrawRule as Nn, DEFAULT_BLOCK_MISSION_TASK_OPTIONS as Nt, DEFAULT_EVACUATE_OPTIONS as O, staticPointDrawRule as On, DEFAULT_GENERIC_CIRCLE_OPTIONS as Ot, DEFAULT_REARWARD_PASSAGE_OF_LINES_OPTIONS as P, blockDrawRule as Pn, DEFAULT_GENERIC_C2_LINE_OPTIONS as Pt, DEFAULT_BRIDGEHEAD_LINE_OPTIONS as Q, roundToFixed as Qn, DEFAULT_DIRECTION_OF_MAIN_ATTACK_OPTIONS as Qt, DEFAULT_FRONTAL_ATTACK_OPTIONS as R, computeDefaultMidpointPerpendicularPoint as Rn, DEFAULT_BLOCK_ARROW_OPTIONS as Rt, DEFAULT_MAIN_ATTACK_OPTIONS as S, turnDrawRule as Sn, RADAR_SEARCH_DOCTRINE_STROKE_COLOR as St, DEFAULT_TURN_OPTIONS as T, attackByFireDrawRule as Tn, DEFAULT_GENERIC_RECTANGLE_OPTIONS as Tt, DEFAULT_LINE_OF_DEPARTURE_CONTACT_OPTIONS as U, createBaselineFrame as Un, DEFAULT_ATTACK_BY_FIRE_OPTIONS as Ut, DEFAULT_PROBABLE_LINE_OF_DEPLOYMENT_OPTIONS as V, pointOnMidpointPerpendicularAxis as Vn, DEFAULT_BATTLE_POSITION_OPTIONS as Vt, DEFAULT_LINE_OF_DEPARTURE_OPTIONS as W, calculateMetrics as Wn, DEFAULT_ANTITANK_WALL_OPTIONS as Wt, DEFAULT_HOLDING_LINE_OPTIONS as X, EPSILON as Xn, DEFAULT_AREA_DEFENSE_OPTIONS as Xt, DEFAULT_RELEASE_LINE_OPTIONS as Y, unproject as Yn, DEFAULT_ENGAGEMENT_AREA_OPTIONS as Yt, DEFAULT_INFILTRATION_LANE_OPTIONS as Z, getMetersPerPixel as Zn, DEFAULT_DIRECTION_OF_SUPPORTING_ATTACK_OPTIONS as Zt, DEFAULT_PICKUP_ZONE_OPTIONS as _, polyline1DrawRule as _n, DEFAULT_CORDON_AND_KNOCK_OPTIONS as _t, DEFINITIONS as a, DEFAULT_LABEL_SIZE_CLAMP_CSS_PIXELS as an, DEFAULT_DISRUPT_OPTIONS as at, DEFAULT_OBSTACLE_BYPASS_DIFFICULT_OPTIONS as b, line24DrawRule as bn, DEFAULT_RADAR_SEARCH_DOCTRINE_OPTIONS as bt, getDefaultOptions as c, DEFAULT_PORTRAYAL as cn, DEFAULT_DISENGAGE_OPTIONS as ct, DEFAULT_SUPPORT_BY_FIRE_OPTIONS as d, DEFAULT_SYMBOL_COLOR as dn, DEFAULT_COVER_OPTIONS as dt, TEXT_AMPLIFIER_FIELDS as en, DEFAULT_FLOT_OPTIONS as et, DEFAULT_RETAIN_OPTIONS as f, DEFAULT_AMBUSH_OPTIONS as fn, DEFAULT_TACTICAL_ARROW_OPTIONS as ft, DEFAULT_PRINCIPAL_DIRECTION_OF_FIRE_OPTIONS as g, supportByFireDrawRule as gn, DEFAULT_CORDON_AND_SEARCH_OPTIONS as gt, SECONDARY_DIRECTION_OF_FIRE_DASH as h, axis1DrawRule as hn, DEFAULT_SUPPORTING_ATTACK_OPTIONS as ht, CONTROL_MEASURE_METADATA as i, DEFAULT_LABEL_HEIGHT_CSS_PIXELS as in, DEFAULT_ENCIRCLEMENT_OPTIONS as it, DEFAULT_SCREEN_OPTIONS as j, penetrateDrawRule as jn, DEFAULT_BYPASS_OPTIONS as jt, DEFAULT_SEIZE_OPTIONS as k, point12DrawRule as kn, DEFAULT_CLASSIC_ARROW_OPTIONS as kt, listControlMeasureMetadata as l, DEFAULT_STROKE_DASH_CSS_PIXELS as ln, DEFAULT_DEMONSTRATE_OPTIONS as lt, DEFAULT_SECONDARY_DIRECTION_OF_FIRE_OPTIONS as m, rectangleDrawRule as mn, DEFAULT_COUNTERATTACK_OPTIONS as mt, resolveStyleHints as n, normalizeTextAmplifiers as nn, DEFAULT_FIX_OPTIONS as nt, getControlMeasureMetadata as o, DEFAULT_LINE_CAP as on, DEFAULT_GUARD_OPTIONS as ot, DEFAULT_STRONG_POINT_OPTIONS as p, DEFAULT_AIRBORNE_ATTACK_OPTIONS as pn, DEFAULT_COUNTERATTACK_BY_FIRE_OPTIONS as pt, DEFAULT_HANDOVER_LINE_OPTIONS as q, project as qn, DEFAULT_AREA_OF_OPERATIONS_OPTIONS as qt, CONTROL_MEASURE_IDS as r, resolveAmplifierPlacement as rn, DEFAULT_FINAL_PROTECTIVE_FIRE_OPTIONS as rt, getControlMeasureMetadataByValue as s, DEFAULT_LINE_JOIN as sn, DEFAULT_DISRUPT_MISSION_TASK_OPTIONS as st, renderControlMeasure as t, canonicalTextAmplifierKey as tn, DEFAULT_FIX_MISSION_TASK_OPTIONS as tt, DEFAULT_TURNING_MOVEMENT_OPTIONS as u, DEFAULT_STROKE_WIDTH_CSS_PIXELS as un, DEFAULT_DELAY_OPTIONS as ut, DEFAULT_OBSTACLE_BYPASS_IMPOSSIBLE_OPTIONS as v, line27DrawRule as vn, DEFAULT_ISOLATE_OPTIONS as vt, DEFAULT_JOINT_TACTICAL_ACTION_AREA_OPTIONS as w, ambushDrawRule as wn, DEFAULT_GENERIC_SECTOR_OPTIONS as wt, DEFAULT_MINEFIELD_OPTIONS as x, line23DrawRule as xn, RADAR_SEARCH_DOCTRINE_FILL_COLOR as xt, DEFAULT_OBSTACLE_BYPASS_EASY_OPTIONS as y, line26DrawRule as yn, DEFAULT_CLEAR_OPTIONS as yt, DEFAULT_FORTIFIED_AREA_OPTIONS as z, createMidpointPerpendicularDrawRule as zn, DEFAULT_BLOCK_OPTIONS as zt };
|