@orbat-mapper/control-measures 0.16.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-1i5OZMcS.d.mts → index-ARQKlooW.d.mts} +228 -157
- 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-B61Tgq3f.mjs → renderControlMeasure-DGnX8ELk.mjs} +1883 -779
- 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. */
|
|
@@ -785,6 +785,18 @@ const disruptDrawRule = createMidpointPerpendicularDrawRule({
|
|
|
785
785
|
normal: "left"
|
|
786
786
|
});
|
|
787
787
|
//#endregion
|
|
788
|
+
//#region src/draw-rules/area16.ts
|
|
789
|
+
/**
|
|
790
|
+
* Area16 — center + start/radius point, mechanically identical to Area15. It
|
|
791
|
+
* re-labels the shared behaviour rather than aliasing it because a definition's
|
|
792
|
+
* `rule.id` must carry its `metadata.drawRule` as a prefix (see the registry
|
|
793
|
+
* invariant in `__tests__/metadata-registry.spec.ts`, ADR-0013).
|
|
794
|
+
*/
|
|
795
|
+
const occupyDrawRule = {
|
|
796
|
+
...centerRadiusDrawRule,
|
|
797
|
+
id: "area16:center-radius"
|
|
798
|
+
};
|
|
799
|
+
//#endregion
|
|
788
800
|
//#region src/draw-rules/area17.ts
|
|
789
801
|
/**
|
|
790
802
|
* Area17 anchor draw rule for Penetrate.
|
|
@@ -798,6 +810,72 @@ const penetrateDrawRule = createMidpointPerpendicularDrawRule({
|
|
|
798
810
|
minimumPreviewPoints: 2
|
|
799
811
|
});
|
|
800
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
|
|
801
879
|
//#region src/draw-rules/point12.ts
|
|
802
880
|
/**
|
|
803
881
|
* Point12 anchor draw rule for the obstacle-bypass family.
|
|
@@ -815,16 +893,16 @@ const point12DrawRule = createMidpointPerpendicularDrawRule({ id: "point12:obsta
|
|
|
815
893
|
* Point1 — a single anchor point (e.g. Text's center). One user click commits
|
|
816
894
|
* the measure; dragging the point simply translates it.
|
|
817
895
|
*/
|
|
818
|
-
function derive$
|
|
896
|
+
function derive$13(points) {
|
|
819
897
|
return clonePositions(points.slice(0, 1));
|
|
820
898
|
}
|
|
821
899
|
const pointDrawRule = {
|
|
822
900
|
id: "point1:anchor",
|
|
823
901
|
minimumUserPoints: 1,
|
|
824
902
|
canonicalPointCount: 1,
|
|
825
|
-
derive: derive$
|
|
903
|
+
derive: derive$13,
|
|
826
904
|
transform(event) {
|
|
827
|
-
return derive$
|
|
905
|
+
return derive$13(event.next);
|
|
828
906
|
}
|
|
829
907
|
};
|
|
830
908
|
//#endregion
|
|
@@ -851,7 +929,7 @@ const SECTOR_ANGLE_SNAP_RADIANS = Math.PI / 36;
|
|
|
851
929
|
* independently control a radius and a true-north azimuth. Dragging the anchor
|
|
852
930
|
* translates both edge points so the sector retains its size and orientation.
|
|
853
931
|
*/
|
|
854
|
-
function derive$
|
|
932
|
+
function derive$12(points) {
|
|
855
933
|
return clonePositions(points.slice(0, 3));
|
|
856
934
|
}
|
|
857
935
|
function guidePoints$2(points) {
|
|
@@ -879,7 +957,7 @@ const sectorDrawRule = {
|
|
|
879
957
|
showGuide: true,
|
|
880
958
|
guidePoints: guidePoints$2,
|
|
881
959
|
constrainAngles,
|
|
882
|
-
derive: derive$
|
|
960
|
+
derive: derive$12,
|
|
883
961
|
transform(event) {
|
|
884
962
|
const { previous, next, activePointIndex } = event;
|
|
885
963
|
if (activePointIndex === 0 && previous.length >= 3 && next.length >= 3) {
|
|
@@ -891,7 +969,7 @@ const sectorDrawRule = {
|
|
|
891
969
|
[previous[2][0] + dx, previous[2][1] + dy]
|
|
892
970
|
];
|
|
893
971
|
}
|
|
894
|
-
return derive$
|
|
972
|
+
return derive$12(next);
|
|
895
973
|
}
|
|
896
974
|
};
|
|
897
975
|
//#endregion
|
|
@@ -945,6 +1023,56 @@ const ambushDrawRule = createMidpointPerpendicularDrawRule({
|
|
|
945
1023
|
defaultDistanceRatio: -.5
|
|
946
1024
|
});
|
|
947
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
|
|
948
1076
|
//#region src/draw-rules/line1.ts
|
|
949
1077
|
function derive$10(points) {
|
|
950
1078
|
return clonePositions(points);
|
|
@@ -1362,6 +1490,23 @@ const line32DrawRule = {
|
|
|
1362
1490
|
}
|
|
1363
1491
|
};
|
|
1364
1492
|
//#endregion
|
|
1493
|
+
//#region src/draw-rules/line33.ts
|
|
1494
|
+
/**
|
|
1495
|
+
* Line33 anchor draw rule for the Pursuit mission task.
|
|
1496
|
+
*
|
|
1497
|
+
* PT. 1 and PT. 2 define the straight portion. PT. 3 is constrained to the
|
|
1498
|
+
* perpendicular axis through PT. 2, where its signed distance determines the
|
|
1499
|
+
* semicircular arc's diameter and which side of the straight line it occupies.
|
|
1500
|
+
*/
|
|
1501
|
+
const line33DrawRule = createMidpointPerpendicularDrawRule({
|
|
1502
|
+
id: "line33:pursuit",
|
|
1503
|
+
origin: "p2",
|
|
1504
|
+
defaultDistanceRatio: .6,
|
|
1505
|
+
minimumUserPoints: 3,
|
|
1506
|
+
minimumPreviewPoints: 2,
|
|
1507
|
+
finishOnLastPointRepeat: true
|
|
1508
|
+
});
|
|
1509
|
+
//#endregion
|
|
1365
1510
|
//#region src/draw-rules/polyline1.ts
|
|
1366
1511
|
/**
|
|
1367
1512
|
* Polyline1 anchors for the Infiltration Lane.
|
|
@@ -2986,6 +3131,70 @@ function labelBoxIntersectsSegment(anchor, a, b, textWidth, textHeight, rotation
|
|
|
2986
3131
|
}
|
|
2987
3132
|
return true;
|
|
2988
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;
|
|
2989
3198
|
/** Field N descriptor shared by Line1 graphics that opt into hostile endpoint markers. */
|
|
2990
3199
|
const HOSTILE_LINE_AMPLIFIER = {
|
|
2991
3200
|
key: "N",
|
|
@@ -2994,12 +3203,12 @@ const HOSTILE_LINE_AMPLIFIER = {
|
|
|
2994
3203
|
placeholder: "ENY",
|
|
2995
3204
|
maxLength: 3
|
|
2996
3205
|
};
|
|
2997
|
-
/** Label-padding control shared by
|
|
2998
|
-
const
|
|
3206
|
+
/** Label-padding control shared by label-bearing graphics. */
|
|
3207
|
+
const LABEL_PADDING_PARAM = {
|
|
2999
3208
|
key: "labelPadding",
|
|
3000
3209
|
presentationTier: "advanced",
|
|
3001
3210
|
label: "Label padding",
|
|
3002
|
-
description: "Extra clearance
|
|
3211
|
+
description: "Extra clearance around labels, as a ratio of the label height",
|
|
3003
3212
|
type: "number",
|
|
3004
3213
|
min: 0,
|
|
3005
3214
|
max: 2,
|
|
@@ -3107,8 +3316,8 @@ function estimatedTextWidth(text, textHeightMeters) {
|
|
|
3107
3316
|
return text.length * LABEL_CHAR_ASPECT_RATIO * textHeightMeters;
|
|
3108
3317
|
}
|
|
3109
3318
|
/** Resolve the label's final display size, then measure and convert it to construction metres. */
|
|
3110
|
-
function resolveTextMetrics(text, options, context, style = "regular") {
|
|
3111
|
-
const legacyHeight = resolveLabelOffsetMeters(options, 1);
|
|
3319
|
+
function resolveTextMetrics(text, options, context, style = "regular", fallbackHeightMeters = 50) {
|
|
3320
|
+
const legacyHeight = resolveLabelOffsetMeters(options, 1, fallbackHeightMeters);
|
|
3112
3321
|
const constructionScale = context?.constructionMetersPerCssPixel;
|
|
3113
3322
|
if (!(constructionScale !== void 0 && constructionScale > 0)) return {
|
|
3114
3323
|
width: estimatedTextWidth(text, legacyHeight),
|
|
@@ -3139,6 +3348,28 @@ function resolveTextMetrics(text, options, context, style = "regular") {
|
|
|
3139
3348
|
height: measured.heightCssPixels * constructionScale
|
|
3140
3349
|
};
|
|
3141
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
|
+
}
|
|
3142
3373
|
/** The end-label text for a phase-line naming, or `""` when unnamed. */
|
|
3143
3374
|
function phaseLineLabelText(name, includePrefix) {
|
|
3144
3375
|
if (!name) return "";
|
|
@@ -3603,6 +3834,7 @@ const DIRECTION_OF_ATTACK_AVIATION_METADATA = {
|
|
|
3603
3834
|
},
|
|
3604
3835
|
drawRule: "Line1",
|
|
3605
3836
|
capturesLabelSize: true,
|
|
3837
|
+
labelGeometryUsesResolvedMetrics: ["T"],
|
|
3606
3838
|
params: [
|
|
3607
3839
|
...SMOOTH_LINE_PARAMS,
|
|
3608
3840
|
{
|
|
@@ -3841,6 +4073,7 @@ const DIRECTION_OF_MAIN_ATTACK_METADATA = {
|
|
|
3841
4073
|
},
|
|
3842
4074
|
drawRule: "Line1",
|
|
3843
4075
|
capturesLabelSize: true,
|
|
4076
|
+
labelGeometryUsesResolvedMetrics: ["T"],
|
|
3844
4077
|
params: [
|
|
3845
4078
|
...SMOOTH_LINE_PARAMS,
|
|
3846
4079
|
...DIRECTION_OF_ATTACK_PARAMS,
|
|
@@ -3909,6 +4142,7 @@ const DIRECTION_OF_SUPPORTING_ATTACK_METADATA = {
|
|
|
3909
4142
|
},
|
|
3910
4143
|
drawRule: "Line1",
|
|
3911
4144
|
capturesLabelSize: true,
|
|
4145
|
+
labelGeometryUsesResolvedMetrics: ["T"],
|
|
3912
4146
|
params: [...SMOOTH_LINE_PARAMS, ...DIRECTION_OF_ATTACK_PARAMS],
|
|
3913
4147
|
textAmplifiers: DIRECTION_OF_ATTACK_TEXT_AMPLIFIERS
|
|
3914
4148
|
};
|
|
@@ -4539,6 +4773,7 @@ const AREA_METADATA = {
|
|
|
4539
4773
|
},
|
|
4540
4774
|
drawRule: "Area1",
|
|
4541
4775
|
capturesLabelSize: true,
|
|
4776
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
4542
4777
|
params: LABELED_AREA_PARAMS,
|
|
4543
4778
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
4544
4779
|
};
|
|
@@ -4569,7 +4804,7 @@ const AREA = defineControlMeasure({
|
|
|
4569
4804
|
//#endregion
|
|
4570
4805
|
//#region src/generators/cm15-maneuver-areas/area-defense.ts
|
|
4571
4806
|
const DEFAULT_AREA_DEFENSE_OPTIONS = {
|
|
4572
|
-
|
|
4807
|
+
openingAngle: 30,
|
|
4573
4808
|
labelOpeningAngle: 20,
|
|
4574
4809
|
barbCount: 6,
|
|
4575
4810
|
barbLengthRatio: .16,
|
|
@@ -4599,7 +4834,7 @@ const AREA_DEFENSE_METADATA = {
|
|
|
4599
4834
|
capturesLabelSize: true,
|
|
4600
4835
|
params: [
|
|
4601
4836
|
{
|
|
4602
|
-
key: "
|
|
4837
|
+
key: "openingAngle",
|
|
4603
4838
|
presentationTier: "advanced",
|
|
4604
4839
|
label: "Arrow opening",
|
|
4605
4840
|
description: "Angular opening between the two arrow tips, in degrees",
|
|
@@ -4680,7 +4915,7 @@ function createAreaDefense(coordinates, options = {}) {
|
|
|
4680
4915
|
features: []
|
|
4681
4916
|
};
|
|
4682
4917
|
const upperTipAngle = Math.atan2(radiusVector[1], radiusVector[0]);
|
|
4683
|
-
const arrowHalf = Math.min(120, Math.max(5, resolved.
|
|
4918
|
+
const arrowHalf = Math.min(120, Math.max(5, resolved.openingAngle)) * Math.PI / 360;
|
|
4684
4919
|
const labelHalf = Math.min(90, Math.max(5, resolved.labelOpeningAngle)) * Math.PI / 360;
|
|
4685
4920
|
const labelCenter = upperTipAngle + arrowHalf - Math.PI;
|
|
4686
4921
|
const upperStart = labelCenter + labelHalf;
|
|
@@ -4802,6 +5037,7 @@ const ENGAGEMENT_AREA_METADATA = {
|
|
|
4802
5037
|
},
|
|
4803
5038
|
drawRule: "Area1",
|
|
4804
5039
|
capturesLabelSize: true,
|
|
5040
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
4805
5041
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
4806
5042
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
4807
5043
|
};
|
|
@@ -4868,6 +5104,26 @@ function sampleProjectedArc(center, startRadius, bulgeRadius, start, end, resolu
|
|
|
4868
5104
|
return unproject(point[0], point[1]);
|
|
4869
5105
|
});
|
|
4870
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
|
+
}
|
|
4871
5127
|
//#endregion
|
|
4872
5128
|
//#region src/generators/cm15-maneuver-areas/mobile-defense.ts
|
|
4873
5129
|
const DEFAULT_MOBILE_DEFENSE_OPTIONS = { labelPadding: 0 };
|
|
@@ -4893,6 +5149,7 @@ const MOBILE_DEFENSE_METADATA = {
|
|
|
4893
5149
|
},
|
|
4894
5150
|
drawRule: "Area27",
|
|
4895
5151
|
capturesLabelSize: true,
|
|
5152
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
4896
5153
|
params: [{
|
|
4897
5154
|
key: "labelPadding",
|
|
4898
5155
|
presentationTier: "advanced",
|
|
@@ -5069,6 +5326,7 @@ const AREA_OF_OPERATIONS_METADATA = {
|
|
|
5069
5326
|
},
|
|
5070
5327
|
drawRule: "Area1",
|
|
5071
5328
|
capturesLabelSize: true,
|
|
5329
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
5072
5330
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
5073
5331
|
textAmplifiers: AREA_TEXT_AMPLIFIERS
|
|
5074
5332
|
};
|
|
@@ -5131,6 +5389,7 @@ const NAMED_AREA_OF_INTEREST_METADATA = {
|
|
|
5131
5389
|
},
|
|
5132
5390
|
drawRule: "Area1",
|
|
5133
5391
|
capturesLabelSize: true,
|
|
5392
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
5134
5393
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
5135
5394
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
5136
5395
|
};
|
|
@@ -5190,6 +5449,7 @@ const TARGET_AREA_OF_INTEREST_METADATA = {
|
|
|
5190
5449
|
},
|
|
5191
5450
|
drawRule: "Area1",
|
|
5192
5451
|
capturesLabelSize: true,
|
|
5452
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
5193
5453
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
5194
5454
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
5195
5455
|
};
|
|
@@ -5258,6 +5518,7 @@ const AIRFIELD_ZONE_METADATA = {
|
|
|
5258
5518
|
},
|
|
5259
5519
|
drawRule: "Area1",
|
|
5260
5520
|
capturesLabelSize: true,
|
|
5521
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
5261
5522
|
params: [
|
|
5262
5523
|
...AREA_SMOOTH_PARAMS,
|
|
5263
5524
|
{
|
|
@@ -5442,6 +5703,7 @@ const BASE_CAMP_METADATA = {
|
|
|
5442
5703
|
},
|
|
5443
5704
|
drawRule: "Area1",
|
|
5444
5705
|
capturesLabelSize: true,
|
|
5706
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
5445
5707
|
params: PREFIXED_ECHELON_AREA_PARAMS,
|
|
5446
5708
|
textAmplifiers: AREA_TEXT_AMPLIFIERS
|
|
5447
5709
|
};
|
|
@@ -5500,6 +5762,7 @@ const GUERRILLA_BASE_METADATA = {
|
|
|
5500
5762
|
},
|
|
5501
5763
|
drawRule: "Area1",
|
|
5502
5764
|
capturesLabelSize: true,
|
|
5765
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
5503
5766
|
params: PREFIXED_ECHELON_AREA_PARAMS,
|
|
5504
5767
|
textAmplifiers: AREA_TEXT_AMPLIFIERS
|
|
5505
5768
|
};
|
|
@@ -5563,6 +5826,7 @@ const GENERIC_C2_AREA_METADATA = {
|
|
|
5563
5826
|
},
|
|
5564
5827
|
drawRule: "Area1",
|
|
5565
5828
|
capturesLabelSize: true,
|
|
5829
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
5566
5830
|
params: LABELED_AREA_PARAMS,
|
|
5567
5831
|
textAmplifiers: AREA_TEXT_AMPLIFIERS
|
|
5568
5832
|
};
|
|
@@ -5618,6 +5882,7 @@ const ASSEMBLY_AREA_METADATA = {
|
|
|
5618
5882
|
},
|
|
5619
5883
|
drawRule: "Area1",
|
|
5620
5884
|
capturesLabelSize: true,
|
|
5885
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
5621
5886
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
5622
5887
|
textAmplifiers: AREA_TEXT_AMPLIFIERS
|
|
5623
5888
|
};
|
|
@@ -5669,7 +5934,7 @@ const FORTIFIED_PARAMS = [
|
|
|
5669
5934
|
step: 1,
|
|
5670
5935
|
unit: "m"
|
|
5671
5936
|
},
|
|
5672
|
-
|
|
5937
|
+
LABEL_PADDING_PARAM
|
|
5673
5938
|
];
|
|
5674
5939
|
const ANTITANK_DITCH_PARAMS = [
|
|
5675
5940
|
...SMOOTH_LINE_PARAMS,
|
|
@@ -5704,7 +5969,7 @@ const ANTITANK_DITCH_PARAMS = [
|
|
|
5704
5969
|
step: .05,
|
|
5705
5970
|
unit: "x"
|
|
5706
5971
|
},
|
|
5707
|
-
|
|
5972
|
+
LABEL_PADDING_PARAM
|
|
5708
5973
|
];
|
|
5709
5974
|
const ANTITANK_WALL_PARAMS = [...ANTITANK_DITCH_PARAMS, {
|
|
5710
5975
|
key: "toothSpacingRatio",
|
|
@@ -5747,6 +6012,7 @@ const ANTITANK_DITCH_UNDER_CONSTRUCTION_METADATA = {
|
|
|
5747
6012
|
},
|
|
5748
6013
|
drawRule: "Line1",
|
|
5749
6014
|
capturesLabelSize: true,
|
|
6015
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
5750
6016
|
params: ANTITANK_DITCH_PARAMS,
|
|
5751
6017
|
textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
|
|
5752
6018
|
};
|
|
@@ -5768,6 +6034,7 @@ const ANTITANK_DITCH_COMPLETED_METADATA = {
|
|
|
5768
6034
|
},
|
|
5769
6035
|
drawRule: "Line1",
|
|
5770
6036
|
capturesLabelSize: true,
|
|
6037
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
5771
6038
|
params: ANTITANK_DITCH_PARAMS,
|
|
5772
6039
|
textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
|
|
5773
6040
|
};
|
|
@@ -5900,6 +6167,7 @@ const ANTITANK_WALL_METADATA = {
|
|
|
5900
6167
|
},
|
|
5901
6168
|
drawRule: "Line1",
|
|
5902
6169
|
capturesLabelSize: true,
|
|
6170
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
5903
6171
|
params: ANTITANK_WALL_PARAMS,
|
|
5904
6172
|
textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
|
|
5905
6173
|
};
|
|
@@ -6116,7 +6384,7 @@ const DEFAULT_ATTACK_HELICOPTER_OPTIONS = {
|
|
|
6116
6384
|
symbolHeightRatio: .45,
|
|
6117
6385
|
triangleSizeRatio: .25,
|
|
6118
6386
|
bottomBarWidthRatio: 1.5,
|
|
6119
|
-
|
|
6387
|
+
bowTieWidthRatio: .8
|
|
6120
6388
|
};
|
|
6121
6389
|
const ATTACK_HELICOPTER_METADATA = {
|
|
6122
6390
|
id: "attack-helicopter",
|
|
@@ -6168,7 +6436,7 @@ const ATTACK_HELICOPTER_METADATA = {
|
|
|
6168
6436
|
step: .1
|
|
6169
6437
|
},
|
|
6170
6438
|
{
|
|
6171
|
-
key: "
|
|
6439
|
+
key: "bowTieWidthRatio",
|
|
6172
6440
|
presentationTier: "advanced",
|
|
6173
6441
|
label: "Bowtie width",
|
|
6174
6442
|
description: "Size of the central bowtie relative to the reference triangle.",
|
|
@@ -6237,7 +6505,7 @@ function createAttackHelicopter(coordinates, options = {}) {
|
|
|
6237
6505
|
const symbolHeightRatio = options.symbolHeightRatio ?? DEFAULT_ATTACK_HELICOPTER_OPTIONS.symbolHeightRatio;
|
|
6238
6506
|
const triangleSizeRatio = options.triangleSizeRatio ?? DEFAULT_ATTACK_HELICOPTER_OPTIONS.triangleSizeRatio;
|
|
6239
6507
|
const bottomBarWidthRatio = options.bottomBarWidthRatio ?? DEFAULT_ATTACK_HELICOPTER_OPTIONS.bottomBarWidthRatio;
|
|
6240
|
-
const
|
|
6508
|
+
const bowTieWidthRatio = options.bowTieWidthRatio ?? DEFAULT_ATTACK_HELICOPTER_OPTIONS.bowTieWidthRatio;
|
|
6241
6509
|
const baseUnit = headWidth * .45;
|
|
6242
6510
|
const shaftLength = headWidth * symbolHeightRatio;
|
|
6243
6511
|
const triHeight = baseUnit * triangleSizeRatio;
|
|
@@ -6283,7 +6551,7 @@ function createAttackHelicopter(coordinates, options = {}) {
|
|
|
6283
6551
|
coordinates: [unproject(pBarLeft[0], pBarLeft[1]), unproject(pBarRight[0], pBarRight[1])]
|
|
6284
6552
|
}
|
|
6285
6553
|
};
|
|
6286
|
-
const bowTieSize = refTriHeight *
|
|
6554
|
+
const bowTieSize = refTriHeight * bowTieWidthRatio;
|
|
6287
6555
|
const bowTieHalfWidth = bowTieSize * .6;
|
|
6288
6556
|
const pBowTieRightBaseCenter = vecAdd(center, vecScale(u, bowTieSize));
|
|
6289
6557
|
const pBowTieRightTop = vecAdd(pBowTieRightBaseCenter, vecScale(n, bowTieHalfWidth));
|
|
@@ -6366,6 +6634,7 @@ const BATTLE_POSITION_METADATA = {
|
|
|
6366
6634
|
},
|
|
6367
6635
|
drawRule: "Area1",
|
|
6368
6636
|
capturesLabelSize: true,
|
|
6637
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
6369
6638
|
params: ECHELON_AREA_PARAMS,
|
|
6370
6639
|
textAmplifiers: AREA_UNIT_DESIGNATION_HOSTILE_AMPLIFIERS
|
|
6371
6640
|
};
|
|
@@ -6509,6 +6778,7 @@ const BATTLE_POSITION_PREPARED_METADATA = {
|
|
|
6509
6778
|
},
|
|
6510
6779
|
drawRule: "Area1",
|
|
6511
6780
|
capturesLabelSize: true,
|
|
6781
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
6512
6782
|
params: PREFIXED_ECHELON_AREA_PARAMS,
|
|
6513
6783
|
textAmplifiers: AREA_UNIT_DESIGNATION_HOSTILE_AMPLIFIERS
|
|
6514
6784
|
};
|
|
@@ -6577,6 +6847,7 @@ const CONTAIN_METADATA = {
|
|
|
6577
6847
|
},
|
|
6578
6848
|
drawRule: "Area5",
|
|
6579
6849
|
capturesLabelSize: true,
|
|
6850
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
6580
6851
|
textAmplifiers: [AREA_HOSTILE_AMPLIFIER],
|
|
6581
6852
|
params: [{
|
|
6582
6853
|
key: "labelPadding",
|
|
@@ -7472,6 +7743,7 @@ const BOUNDARY_METADATA = {
|
|
|
7472
7743
|
text: true
|
|
7473
7744
|
},
|
|
7474
7745
|
drawRule: "Line1",
|
|
7746
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
7475
7747
|
params: [
|
|
7476
7748
|
{
|
|
7477
7749
|
key: "smooth",
|
|
@@ -7890,6 +8162,7 @@ const GENERIC_C2_LINE_METADATA = {
|
|
|
7890
8162
|
},
|
|
7891
8163
|
drawRule: "Line1",
|
|
7892
8164
|
capturesLabelSize: true,
|
|
8165
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
7893
8166
|
params: [
|
|
7894
8167
|
...SMOOTH_LINE_PARAMS,
|
|
7895
8168
|
...echelonLineParams("line"),
|
|
@@ -7911,7 +8184,7 @@ const GENERIC_C2_LINE_METADATA = {
|
|
|
7911
8184
|
type: "boolean",
|
|
7912
8185
|
visibleWhen: (opts) => Boolean(String(opts.phaseLineName ?? "").trim())
|
|
7913
8186
|
},
|
|
7914
|
-
|
|
8187
|
+
LABEL_PADDING_PARAM
|
|
7915
8188
|
],
|
|
7916
8189
|
textAmplifiers: [
|
|
7917
8190
|
{
|
|
@@ -8138,7 +8411,7 @@ function createLabelFeature(point, text, rotation, labelSize = {}) {
|
|
|
8138
8411
|
* @param config - Per-measure end caps, label glyph, and sizing
|
|
8139
8412
|
* @returns GeoJSON FeatureCollection containing MultiLineString and Point features
|
|
8140
8413
|
*/
|
|
8141
|
-
function createBracketSymbol(coordinates, config) {
|
|
8414
|
+
function createBracketSymbol(coordinates, config, context) {
|
|
8142
8415
|
const [c1, c2, c3] = coordinates;
|
|
8143
8416
|
const p1 = project(c1[0], c1[1]);
|
|
8144
8417
|
const p2 = project(c2[0], c2[1]);
|
|
@@ -8158,7 +8431,10 @@ function createBracketSymbol(coordinates, config) {
|
|
|
8158
8431
|
const rearMidpoint = vecAdd(frontMidpoint, vecScale(perpDir, perpDistance));
|
|
8159
8432
|
const rearTop = vecSub(rearMidpoint, halfFront);
|
|
8160
8433
|
const rearBottom = vecAdd(rearMidpoint, halfFront);
|
|
8161
|
-
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));
|
|
8162
8438
|
const gapPt1 = vecAdd(rearMidpoint, vecScale(dirFront, -gapHalf));
|
|
8163
8439
|
const gapPt2 = vecAdd(rearMidpoint, vecScale(dirFront, gapHalf));
|
|
8164
8440
|
const [topCap, bottomCap] = config.buildEndCaps({
|
|
@@ -8169,39 +8445,35 @@ function createBracketSymbol(coordinates, config) {
|
|
|
8169
8445
|
frontLength,
|
|
8170
8446
|
rearTop
|
|
8171
8447
|
});
|
|
8172
|
-
const mainFeature = {
|
|
8173
|
-
type: "Feature",
|
|
8174
|
-
properties: { part: config.part },
|
|
8175
|
-
geometry: {
|
|
8176
|
-
type: "MultiLineString",
|
|
8177
|
-
coordinates: [
|
|
8178
|
-
topCap,
|
|
8179
|
-
[
|
|
8180
|
-
unproject(p1[0], p1[1]),
|
|
8181
|
-
unproject(rearTop[0], rearTop[1]),
|
|
8182
|
-
unproject(gapPt1[0], gapPt1[1])
|
|
8183
|
-
],
|
|
8184
|
-
[
|
|
8185
|
-
unproject(gapPt2[0], gapPt2[1]),
|
|
8186
|
-
unproject(rearBottom[0], rearBottom[1]),
|
|
8187
|
-
unproject(p2[0], p2[1])
|
|
8188
|
-
],
|
|
8189
|
-
bottomCap
|
|
8190
|
-
]
|
|
8191
|
-
}
|
|
8192
|
-
};
|
|
8193
|
-
const rearLineRotation = Math.atan2(dirFront[1], dirFront[0]);
|
|
8194
|
-
const renderedLabelRotation = Math.PI - (rearLineRotation - Math.PI / 2);
|
|
8195
|
-
const labelRotation = normalizeRadians(Math.PI - keepTextLeftToRight(renderedLabelRotation));
|
|
8196
8448
|
return {
|
|
8197
8449
|
type: "FeatureCollection",
|
|
8198
|
-
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)]
|
|
8199
8471
|
};
|
|
8200
8472
|
}
|
|
8201
8473
|
//#endregion
|
|
8202
8474
|
//#region src/generators/cm34-mission-tasks/block.ts
|
|
8203
8475
|
const DEFAULT_STEM_RATIO = 1.5;
|
|
8204
|
-
const DEFAULT_BLOCK_MISSION_TASK_OPTIONS = {
|
|
8476
|
+
const DEFAULT_BLOCK_MISSION_TASK_OPTIONS = { labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING };
|
|
8205
8477
|
const BLOCK_MISSION_TASK_METADATA = {
|
|
8206
8478
|
id: "block-mission-task",
|
|
8207
8479
|
name: "Block",
|
|
@@ -8219,16 +8491,8 @@ const BLOCK_MISSION_TASK_METADATA = {
|
|
|
8219
8491
|
},
|
|
8220
8492
|
drawRule: "Area11",
|
|
8221
8493
|
capturesLabelSize: true,
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
presentationTier: "advanced",
|
|
8225
|
-
label: "Label gap",
|
|
8226
|
-
description: "Size of the shaft gap holding the 'B' label, as a ratio of the front length",
|
|
8227
|
-
type: "number",
|
|
8228
|
-
min: 0,
|
|
8229
|
-
max: 1,
|
|
8230
|
-
step: .01
|
|
8231
|
-
}]
|
|
8494
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
8495
|
+
params: [LABEL_PADDING_PARAM]
|
|
8232
8496
|
};
|
|
8233
8497
|
/**
|
|
8234
8498
|
* Generates a GeoJSON FeatureCollection for a BLOCK mission task symbol (340100).
|
|
@@ -8240,8 +8504,7 @@ const BLOCK_MISSION_TASK_METADATA = {
|
|
|
8240
8504
|
* @param options - Configuration options
|
|
8241
8505
|
* @returns GeoJSON FeatureCollection containing MultiLineString and Point features
|
|
8242
8506
|
*/
|
|
8243
|
-
function createBlockMissionTaskSymbol(coordinates, options = {}) {
|
|
8244
|
-
const labelGapRatio = options.labelGapRatio ?? DEFAULT_BLOCK_MISSION_TASK_OPTIONS.labelGapRatio;
|
|
8507
|
+
function createBlockMissionTaskSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
8245
8508
|
const p1 = project(coordinates[0][0], coordinates[0][1]);
|
|
8246
8509
|
const p2 = project(coordinates[1][0], coordinates[1][1]);
|
|
8247
8510
|
const frame = createProjectedBaselineFrame(p1, p2, {
|
|
@@ -8258,12 +8521,12 @@ function createBlockMissionTaskSymbol(coordinates, options = {}) {
|
|
|
8258
8521
|
const pStemEnd = vecAdd(mid, vecScale(frame.normal, stemDistance));
|
|
8259
8522
|
const labelPoint = vecAdd(mid, vecScale(frame.normal, stemDistance / 2));
|
|
8260
8523
|
const stemDirection = vecScale(frame.normal, stemDistance < 0 ? -1 : 1);
|
|
8261
|
-
const gapHalf = Math.min(frame.length * labelGapRatio / 2, Math.abs(stemDistance) / 2);
|
|
8262
|
-
const gapStart = vecAdd(labelPoint, vecScale(stemDirection, -gapHalf));
|
|
8263
|
-
const gapEnd = vecAdd(labelPoint, vecScale(stemDirection, gapHalf));
|
|
8264
8524
|
const shaftRotation = Math.atan2(frame.normal[1], frame.normal[0]);
|
|
8265
8525
|
const renderedLabelRotation = Math.PI - shaftRotation;
|
|
8266
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));
|
|
8267
8530
|
return {
|
|
8268
8531
|
type: "FeatureCollection",
|
|
8269
8532
|
features: [{
|
|
@@ -8298,7 +8561,7 @@ const BLOCK_MISSION_TASK = defineControlMeasure({
|
|
|
8298
8561
|
const DEFAULT_BREACH_OPTIONS = {
|
|
8299
8562
|
tickLengthRatio: .15,
|
|
8300
8563
|
tickAngle: 45,
|
|
8301
|
-
|
|
8564
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING
|
|
8302
8565
|
};
|
|
8303
8566
|
const BREACH_METADATA = {
|
|
8304
8567
|
id: "breach",
|
|
@@ -8318,6 +8581,7 @@ const BREACH_METADATA = {
|
|
|
8318
8581
|
},
|
|
8319
8582
|
drawRule: "Point12",
|
|
8320
8583
|
capturesLabelSize: true,
|
|
8584
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
8321
8585
|
params: [
|
|
8322
8586
|
{
|
|
8323
8587
|
key: "tickLengthRatio",
|
|
@@ -8339,16 +8603,7 @@ const BREACH_METADATA = {
|
|
|
8339
8603
|
max: 90,
|
|
8340
8604
|
step: 1
|
|
8341
8605
|
},
|
|
8342
|
-
|
|
8343
|
-
key: "labelGapRatio",
|
|
8344
|
-
presentationTier: "advanced",
|
|
8345
|
-
label: "Label gap",
|
|
8346
|
-
description: "Size of the rear-line gap holding the 'B' label, as a ratio of the front length",
|
|
8347
|
-
type: "number",
|
|
8348
|
-
min: 0,
|
|
8349
|
-
max: 1,
|
|
8350
|
-
step: .01
|
|
8351
|
-
}
|
|
8606
|
+
LABEL_PADDING_PARAM
|
|
8352
8607
|
]
|
|
8353
8608
|
};
|
|
8354
8609
|
/**
|
|
@@ -8362,16 +8617,15 @@ const BREACH_METADATA = {
|
|
|
8362
8617
|
* @param options - Configuration options
|
|
8363
8618
|
* @returns GeoJSON FeatureCollection containing MultiLineString and Point features
|
|
8364
8619
|
*/
|
|
8365
|
-
function createBreachSymbol(coordinates, options = {}) {
|
|
8620
|
+
function createBreachSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
8366
8621
|
const tickLengthRatio = options.tickLengthRatio ?? DEFAULT_BREACH_OPTIONS.tickLengthRatio;
|
|
8367
8622
|
const tickAngle = options.tickAngle ?? DEFAULT_BREACH_OPTIONS.tickAngle;
|
|
8368
8623
|
return createBracketSymbol(coordinates, {
|
|
8369
8624
|
part: "breach",
|
|
8370
8625
|
labelText: "B",
|
|
8371
|
-
|
|
8372
|
-
|
|
8373
|
-
|
|
8374
|
-
labelSizePixels: options.labelSizePixels
|
|
8626
|
+
labelOptions: {
|
|
8627
|
+
...options,
|
|
8628
|
+
labelPadding: options.labelPadding ?? DEFAULT_BREACH_OPTIONS.labelPadding
|
|
8375
8629
|
},
|
|
8376
8630
|
buildEndCaps: ({ p1, p2, dirFront, perpDir, frontLength }) => {
|
|
8377
8631
|
const halfTick = frontLength * tickLengthRatio / 2;
|
|
@@ -8387,7 +8641,7 @@ function createBreachSymbol(coordinates, options = {}) {
|
|
|
8387
8641
|
const tick2End = vecAdd(p2, vecScale(tick2Dir, halfTick));
|
|
8388
8642
|
return [[unproject(tick1Start[0], tick1Start[1]), unproject(tick1End[0], tick1End[1])], [unproject(tick2Start[0], tick2Start[1]), unproject(tick2End[0], tick2End[1])]];
|
|
8389
8643
|
}
|
|
8390
|
-
});
|
|
8644
|
+
}, context);
|
|
8391
8645
|
}
|
|
8392
8646
|
const BREACH = defineControlMeasure({
|
|
8393
8647
|
metadata: BREACH_METADATA,
|
|
@@ -8402,7 +8656,7 @@ const BREACH = defineControlMeasure({
|
|
|
8402
8656
|
*/
|
|
8403
8657
|
const DEFAULT_BYPASS_OPTIONS = {
|
|
8404
8658
|
arrowheadLengthRatio: .12,
|
|
8405
|
-
|
|
8659
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING
|
|
8406
8660
|
};
|
|
8407
8661
|
const BYPASS_METADATA = {
|
|
8408
8662
|
id: "bypass",
|
|
@@ -8422,6 +8676,7 @@ const BYPASS_METADATA = {
|
|
|
8422
8676
|
},
|
|
8423
8677
|
drawRule: "Point12",
|
|
8424
8678
|
capturesLabelSize: true,
|
|
8679
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
8425
8680
|
params: [{
|
|
8426
8681
|
key: "arrowheadLengthRatio",
|
|
8427
8682
|
presentationTier: "advanced",
|
|
@@ -8431,16 +8686,7 @@ const BYPASS_METADATA = {
|
|
|
8431
8686
|
min: .01,
|
|
8432
8687
|
max: 1,
|
|
8433
8688
|
step: .01
|
|
8434
|
-
},
|
|
8435
|
-
key: "labelGapRatio",
|
|
8436
|
-
presentationTier: "advanced",
|
|
8437
|
-
label: "Label gap",
|
|
8438
|
-
description: "Size of the rear-line gap holding the 'B' label, as a ratio of the front length",
|
|
8439
|
-
type: "number",
|
|
8440
|
-
min: 0,
|
|
8441
|
-
max: 1,
|
|
8442
|
-
step: .01
|
|
8443
|
-
}]
|
|
8689
|
+
}, LABEL_PADDING_PARAM]
|
|
8444
8690
|
};
|
|
8445
8691
|
/**
|
|
8446
8692
|
* Generates a GeoJSON FeatureCollection for a BYPASS mission task symbol (340300).
|
|
@@ -8453,22 +8699,21 @@ const BYPASS_METADATA = {
|
|
|
8453
8699
|
* @param options - Configuration options
|
|
8454
8700
|
* @returns GeoJSON FeatureCollection containing MultiLineString and Point features
|
|
8455
8701
|
*/
|
|
8456
|
-
function createBypassSymbol(coordinates, options = {}) {
|
|
8702
|
+
function createBypassSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
8457
8703
|
const arrowheadLengthRatio = options.arrowheadLengthRatio ?? DEFAULT_BYPASS_OPTIONS.arrowheadLengthRatio;
|
|
8458
8704
|
return createBracketSymbol(coordinates, {
|
|
8459
8705
|
part: "bypass",
|
|
8460
8706
|
labelText: "B",
|
|
8461
|
-
|
|
8462
|
-
|
|
8463
|
-
|
|
8464
|
-
labelSizePixels: options.labelSizePixels
|
|
8707
|
+
labelOptions: {
|
|
8708
|
+
...options,
|
|
8709
|
+
labelPadding: options.labelPadding ?? DEFAULT_BYPASS_OPTIONS.labelPadding
|
|
8465
8710
|
},
|
|
8466
8711
|
buildEndCaps: ({ p1, p2, frontLength, rearTop }) => {
|
|
8467
8712
|
const arrowheadLength = frontLength * arrowheadLengthRatio;
|
|
8468
8713
|
const arrowDir = vecNorm(vecSub(p1, rearTop));
|
|
8469
8714
|
return [createArrowHead$1(p1, arrowDir, arrowheadLength, arrowheadLength), createArrowHead$1(p2, arrowDir, arrowheadLength, arrowheadLength)];
|
|
8470
8715
|
}
|
|
8471
|
-
});
|
|
8716
|
+
}, context);
|
|
8472
8717
|
}
|
|
8473
8718
|
const BYPASS = defineControlMeasure({
|
|
8474
8719
|
metadata: BYPASS_METADATA,
|
|
@@ -8484,7 +8729,7 @@ const BYPASS = defineControlMeasure({
|
|
|
8484
8729
|
const DEFAULT_CANALIZE_OPTIONS = {
|
|
8485
8730
|
tickLengthRatio: .15,
|
|
8486
8731
|
tickAngle: 45,
|
|
8487
|
-
|
|
8732
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING
|
|
8488
8733
|
};
|
|
8489
8734
|
const CANALIZE_METADATA = {
|
|
8490
8735
|
id: "canalize",
|
|
@@ -8504,6 +8749,7 @@ const CANALIZE_METADATA = {
|
|
|
8504
8749
|
},
|
|
8505
8750
|
drawRule: "Point12",
|
|
8506
8751
|
capturesLabelSize: true,
|
|
8752
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
8507
8753
|
params: [
|
|
8508
8754
|
{
|
|
8509
8755
|
key: "tickLengthRatio",
|
|
@@ -8525,16 +8771,7 @@ const CANALIZE_METADATA = {
|
|
|
8525
8771
|
max: 90,
|
|
8526
8772
|
step: 1
|
|
8527
8773
|
},
|
|
8528
|
-
|
|
8529
|
-
key: "labelGapRatio",
|
|
8530
|
-
presentationTier: "advanced",
|
|
8531
|
-
label: "Label gap",
|
|
8532
|
-
description: "Size of the rear-line gap holding the 'C' label, as a ratio of the front length",
|
|
8533
|
-
type: "number",
|
|
8534
|
-
min: 0,
|
|
8535
|
-
max: 1,
|
|
8536
|
-
step: .01
|
|
8537
|
-
}
|
|
8774
|
+
LABEL_PADDING_PARAM
|
|
8538
8775
|
]
|
|
8539
8776
|
};
|
|
8540
8777
|
/**
|
|
@@ -8548,16 +8785,15 @@ const CANALIZE_METADATA = {
|
|
|
8548
8785
|
* @param options - Configuration options
|
|
8549
8786
|
* @returns GeoJSON FeatureCollection containing MultiLineString and Point features
|
|
8550
8787
|
*/
|
|
8551
|
-
function createCanalizeSymbol(coordinates, options = {}) {
|
|
8788
|
+
function createCanalizeSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
8552
8789
|
const tickLengthRatio = options.tickLengthRatio ?? DEFAULT_CANALIZE_OPTIONS.tickLengthRatio;
|
|
8553
8790
|
const tickAngle = options.tickAngle ?? DEFAULT_CANALIZE_OPTIONS.tickAngle;
|
|
8554
8791
|
return createBracketSymbol(coordinates, {
|
|
8555
8792
|
part: "canalize",
|
|
8556
8793
|
labelText: "C",
|
|
8557
|
-
|
|
8558
|
-
|
|
8559
|
-
|
|
8560
|
-
labelSizePixels: options.labelSizePixels
|
|
8794
|
+
labelOptions: {
|
|
8795
|
+
...options,
|
|
8796
|
+
labelPadding: options.labelPadding ?? DEFAULT_CANALIZE_OPTIONS.labelPadding
|
|
8561
8797
|
},
|
|
8562
8798
|
buildEndCaps: ({ p1, p2, dirFront, perpDir, frontLength }) => {
|
|
8563
8799
|
const halfTick = frontLength * tickLengthRatio / 2;
|
|
@@ -8572,7 +8808,7 @@ function createCanalizeSymbol(coordinates, options = {}) {
|
|
|
8572
8808
|
const tick2End = vecAdd(p2, vecScale(tick2Dir, halfTick));
|
|
8573
8809
|
return [[unproject(tick1Start[0], tick1Start[1]), unproject(tick1End[0], tick1End[1])], [unproject(tick2Start[0], tick2Start[1]), unproject(tick2End[0], tick2End[1])]];
|
|
8574
8810
|
}
|
|
8575
|
-
});
|
|
8811
|
+
}, context);
|
|
8576
8812
|
}
|
|
8577
8813
|
const CANALIZE = defineControlMeasure({
|
|
8578
8814
|
metadata: CANALIZE_METADATA,
|
|
@@ -9641,7 +9877,7 @@ const DEFAULT_CLEAR_OPTIONS = {
|
|
|
9641
9877
|
arrowInsetRatio: .15,
|
|
9642
9878
|
arrowheadLengthRatio: .15,
|
|
9643
9879
|
arrowheadWidthRatio: .2,
|
|
9644
|
-
|
|
9880
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING
|
|
9645
9881
|
};
|
|
9646
9882
|
const CLEAR_METADATA = {
|
|
9647
9883
|
id: "clear",
|
|
@@ -9661,6 +9897,7 @@ const CLEAR_METADATA = {
|
|
|
9661
9897
|
},
|
|
9662
9898
|
drawRule: "Line23",
|
|
9663
9899
|
capturesLabelSize: true,
|
|
9900
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
9664
9901
|
params: [
|
|
9665
9902
|
{
|
|
9666
9903
|
key: "arrowInsetRatio",
|
|
@@ -9692,16 +9929,7 @@ const CLEAR_METADATA = {
|
|
|
9692
9929
|
max: 1,
|
|
9693
9930
|
step: .01
|
|
9694
9931
|
},
|
|
9695
|
-
|
|
9696
|
-
key: "labelGapRatio",
|
|
9697
|
-
presentationTier: "advanced",
|
|
9698
|
-
label: "Label gap",
|
|
9699
|
-
description: "Size of the shaft gap holding the 'C' label, as a ratio of the shaft length",
|
|
9700
|
-
type: "number",
|
|
9701
|
-
min: 0,
|
|
9702
|
-
max: 1,
|
|
9703
|
-
step: .01
|
|
9704
|
-
}
|
|
9932
|
+
LABEL_PADDING_PARAM
|
|
9705
9933
|
]
|
|
9706
9934
|
};
|
|
9707
9935
|
/**
|
|
@@ -9725,11 +9953,10 @@ const CLEAR_METADATA = {
|
|
|
9725
9953
|
* @param options - Configuration options
|
|
9726
9954
|
* @returns GeoJSON FeatureCollection containing MultiLineString and Point features
|
|
9727
9955
|
*/
|
|
9728
|
-
function createClearSymbol(coordinates, options = {}) {
|
|
9956
|
+
function createClearSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
9729
9957
|
const arrowInsetRatio = Math.min(.49, Math.max(0, options.arrowInsetRatio ?? DEFAULT_CLEAR_OPTIONS.arrowInsetRatio));
|
|
9730
9958
|
const arrowheadLengthRatio = options.arrowheadLengthRatio ?? DEFAULT_CLEAR_OPTIONS.arrowheadLengthRatio;
|
|
9731
9959
|
const arrowheadWidthRatio = options.arrowheadWidthRatio ?? DEFAULT_CLEAR_OPTIONS.arrowheadWidthRatio;
|
|
9732
|
-
const labelGapRatio = options.labelGapRatio ?? DEFAULT_CLEAR_OPTIONS.labelGapRatio;
|
|
9733
9960
|
const [c1, c2, c3] = coordinates;
|
|
9734
9961
|
const p1 = project(c1[0], c1[1]);
|
|
9735
9962
|
const p2 = project(c2[0], c2[1]);
|
|
@@ -9748,6 +9975,9 @@ function createClearSymbol(coordinates, options = {}) {
|
|
|
9748
9975
|
const arrowDir = vecScale(perpToRear, -1);
|
|
9749
9976
|
const arrowheadLength = height * arrowheadLengthRatio;
|
|
9750
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));
|
|
9751
9981
|
const tipAt = (t) => vecAdd(p1, vecScale(dirLine, t * height));
|
|
9752
9982
|
const lines = [[unproject(p1[0], p1[1]), unproject(p2[0], p2[1])]];
|
|
9753
9983
|
const fullTs = [
|
|
@@ -9761,7 +9991,7 @@ function createClearSymbol(coordinates, options = {}) {
|
|
|
9761
9991
|
const rearEnd = vecAdd(tip, vecScale(perpToRear, shaftLength));
|
|
9762
9992
|
if (i === labelIndex && shaftLength > 1e-6) {
|
|
9763
9993
|
const labelCenter = vecAdd(tip, vecScale(perpToRear, shaftLength * .5));
|
|
9764
|
-
const gapHalf = shaftLength
|
|
9994
|
+
const gapHalf = Math.min(shaftLength / 2, labelKnockoutHalfExtent("C", perpToRear, labelRotation, options, context, DEFAULT_CLEAR_OPTIONS.labelPadding));
|
|
9765
9995
|
const gapRear = vecAdd(labelCenter, vecScale(perpToRear, gapHalf));
|
|
9766
9996
|
const gapTip = vecAdd(labelCenter, vecScale(perpToRear, -gapHalf));
|
|
9767
9997
|
lines.push([unproject(rearEnd[0], rearEnd[1]), unproject(gapRear[0], gapRear[1])]);
|
|
@@ -9777,9 +10007,6 @@ function createClearSymbol(coordinates, options = {}) {
|
|
|
9777
10007
|
coordinates: lines
|
|
9778
10008
|
}
|
|
9779
10009
|
};
|
|
9780
|
-
const lineRotation = Math.atan2(dirLine[1], dirLine[0]);
|
|
9781
|
-
const renderedLabelRotation = Math.PI - (lineRotation - Math.PI / 2);
|
|
9782
|
-
const labelRotation = normalizeRadians(Math.PI - keepTextLeftToRight(renderedLabelRotation));
|
|
9783
10010
|
const labelTip = tipAt(fullTs[labelIndex]);
|
|
9784
10011
|
return {
|
|
9785
10012
|
type: "FeatureCollection",
|
|
@@ -9796,44 +10023,294 @@ const CLEAR = defineControlMeasure({
|
|
|
9796
10023
|
rule: line23DrawRule
|
|
9797
10024
|
});
|
|
9798
10025
|
//#endregion
|
|
9799
|
-
//#region src/generators/
|
|
9800
|
-
|
|
9801
|
-
|
|
9802
|
-
|
|
9803
|
-
|
|
9804
|
-
|
|
9805
|
-
|
|
9806
|
-
|
|
9807
|
-
|
|
9808
|
-
smooth: false,
|
|
9809
|
-
smoothResolution: 5,
|
|
9810
|
-
crossbarLengthRatio: 1.1,
|
|
9811
|
-
...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
|
|
9812
10035
|
};
|
|
9813
|
-
|
|
9814
|
-
|
|
9815
|
-
|
|
9816
|
-
|
|
9817
|
-
|
|
9818
|
-
|
|
9819
|
-
|
|
9820
|
-
|
|
9821
|
-
|
|
9822
|
-
|
|
9823
|
-
|
|
9824
|
-
|
|
9825
|
-
|
|
9826
|
-
|
|
9827
|
-
|
|
9828
|
-
|
|
9829
|
-
|
|
9830
|
-
|
|
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
|
|
9831
10066
|
}
|
|
9832
|
-
|
|
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
|
+
}];
|
|
9833
10074
|
}
|
|
9834
|
-
|
|
9835
|
-
|
|
9836
|
-
|
|
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,
|
|
9837
10314
|
...options
|
|
9838
10315
|
};
|
|
9839
10316
|
const resolved = {
|
|
@@ -10602,7 +11079,7 @@ const COVER = defineControlMeasure({
|
|
|
10602
11079
|
const DEFAULT_DELAY_OPTIONS = {
|
|
10603
11080
|
arrowheadLengthRatio: .12,
|
|
10604
11081
|
arrowheadWidthRatio: .12,
|
|
10605
|
-
|
|
11082
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING,
|
|
10606
11083
|
arcSegments: 32
|
|
10607
11084
|
};
|
|
10608
11085
|
const DELAY_PARAMS = [
|
|
@@ -10626,16 +11103,7 @@ const DELAY_PARAMS = [
|
|
|
10626
11103
|
max: 1,
|
|
10627
11104
|
step: .01
|
|
10628
11105
|
},
|
|
10629
|
-
|
|
10630
|
-
key: "labelGapRatio",
|
|
10631
|
-
presentationTier: "advanced",
|
|
10632
|
-
label: "Label gap",
|
|
10633
|
-
description: "Size of the shaft gap holding the task label, as a ratio of the shaft length",
|
|
10634
|
-
type: "number",
|
|
10635
|
-
min: 0,
|
|
10636
|
-
max: 1,
|
|
10637
|
-
step: .01
|
|
10638
|
-
},
|
|
11106
|
+
LABEL_PADDING_PARAM,
|
|
10639
11107
|
{
|
|
10640
11108
|
key: "arcSegments",
|
|
10641
11109
|
presentationTier: "advanced",
|
|
@@ -10665,9 +11133,16 @@ const DELAY_METADATA = {
|
|
|
10665
11133
|
},
|
|
10666
11134
|
drawRule: "Line24",
|
|
10667
11135
|
capturesLabelSize: true,
|
|
11136
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
10668
11137
|
params: DELAY_PARAMS
|
|
10669
11138
|
};
|
|
10670
|
-
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) {
|
|
10671
11146
|
const [c1, c2, c3] = coordinates;
|
|
10672
11147
|
const p1 = project(c1[0], c1[1]);
|
|
10673
11148
|
const p2 = project(c2[0], c2[1]);
|
|
@@ -10683,8 +11158,9 @@ function createDelaySymbol(coordinates, options = {}) {
|
|
|
10683
11158
|
const signedDiameter = vecDot(vecSub(p3, p2), diameterAxis);
|
|
10684
11159
|
const diameterEnd = vecAdd(p2, vecScale(diameterAxis, signedDiameter));
|
|
10685
11160
|
const diameter = Math.abs(signedDiameter);
|
|
10686
|
-
const gapHalf = lineLength * Math.min(1, Math.max(0, options.labelGapRatio ?? DEFAULT_DELAY_OPTIONS.labelGapRatio)) / 2;
|
|
10687
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));
|
|
10688
11164
|
const gapStart = vecAdd(labelPoint, vecScale(dirLine, -gapHalf));
|
|
10689
11165
|
const gapEnd = vecAdd(labelPoint, vecScale(dirLine, gapHalf));
|
|
10690
11166
|
const lines = [
|
|
@@ -10693,58 +11169,27 @@ function createDelaySymbol(coordinates, options = {}) {
|
|
|
10693
11169
|
createArrowHead$1(p1, vecScale(dirLine, -1), lineLength * (options.arrowheadLengthRatio ?? DEFAULT_DELAY_OPTIONS.arrowheadLengthRatio), lineLength * (options.arrowheadWidthRatio ?? DEFAULT_DELAY_OPTIONS.arrowheadWidthRatio))
|
|
10694
11170
|
];
|
|
10695
11171
|
if (diameter >= 1e-6) lines.push(createSemicircleArc(p2, diameterEnd, dirLine, options.arcSegments ?? DEFAULT_DELAY_OPTIONS.arcSegments));
|
|
10696
|
-
const lineFeature = {
|
|
10697
|
-
type: "Feature",
|
|
10698
|
-
properties: { part: "delay" },
|
|
10699
|
-
geometry: {
|
|
10700
|
-
type: "MultiLineString",
|
|
10701
|
-
coordinates: lines
|
|
10702
|
-
}
|
|
10703
|
-
};
|
|
10704
|
-
const lineRotation = Math.atan2(dirLine[1], dirLine[0]);
|
|
10705
11172
|
return {
|
|
10706
11173
|
type: "FeatureCollection",
|
|
10707
|
-
features: [
|
|
11174
|
+
features: [{
|
|
11175
|
+
type: "Feature",
|
|
11176
|
+
properties: { part: config.part },
|
|
11177
|
+
geometry: {
|
|
11178
|
+
type: "MultiLineString",
|
|
11179
|
+
coordinates: lines
|
|
11180
|
+
}
|
|
11181
|
+
}, createLabelFeature(labelPoint, config.glyph, labelRotation, {
|
|
10708
11182
|
labelSize: options.labelSize,
|
|
10709
11183
|
labelSizePixels: options.labelSizePixels
|
|
10710
11184
|
})]
|
|
10711
11185
|
};
|
|
10712
11186
|
}
|
|
10713
|
-
function createSemicircleArc(start, end, bulgeDir, requestedSegments) {
|
|
10714
|
-
const diameterVector = vecSub(end, start);
|
|
10715
|
-
const diameter = vecMag(diameterVector);
|
|
10716
|
-
if (diameter < 1e-6) return [unproject(start[0], start[1])];
|
|
10717
|
-
const diameterDir = vecNorm(diameterVector);
|
|
10718
|
-
const center = vecAdd(start, vecScale(diameterVector, .5));
|
|
10719
|
-
const radius = diameter / 2;
|
|
10720
|
-
const segments = Math.max(4, Math.round(requestedSegments));
|
|
10721
|
-
const points = [];
|
|
10722
|
-
for (let i = 0; i <= segments; i += 1) {
|
|
10723
|
-
const theta = i / segments * Math.PI;
|
|
10724
|
-
const point = vecAdd(center, vecAdd(vecScale(diameterDir, -Math.cos(theta) * radius), vecScale(bulgeDir, Math.sin(theta) * radius)));
|
|
10725
|
-
points.push(unproject(point[0], point[1]));
|
|
10726
|
-
}
|
|
10727
|
-
return points;
|
|
10728
|
-
}
|
|
10729
11187
|
/**
|
|
10730
11188
|
* Line24 mission tasks that share Delay's geometry and differ only in the
|
|
10731
11189
|
* glyph shown in the shaft gap and the `part` tag on the line feature.
|
|
10732
11190
|
*/
|
|
10733
|
-
function createDelayVariantSymbol(coordinates, config, options = {}) {
|
|
10734
|
-
return
|
|
10735
|
-
type: "FeatureCollection",
|
|
10736
|
-
features: createDelaySymbol(coordinates, options).features.map((feature) => ({
|
|
10737
|
-
...feature,
|
|
10738
|
-
properties: feature.geometry.type === "Point" ? {
|
|
10739
|
-
...feature.properties,
|
|
10740
|
-
part: "label",
|
|
10741
|
-
text: config.glyph
|
|
10742
|
-
} : {
|
|
10743
|
-
...feature.properties,
|
|
10744
|
-
part: config.part
|
|
10745
|
-
}
|
|
10746
|
-
}))
|
|
10747
|
-
};
|
|
11191
|
+
function createDelayVariantSymbol(coordinates, config, options = {}, context = {}) {
|
|
11192
|
+
return createDelayTaskSymbol(coordinates, config, options, context);
|
|
10748
11193
|
}
|
|
10749
11194
|
const DELAY = defineControlMeasure({
|
|
10750
11195
|
metadata: DELAY_METADATA,
|
|
@@ -10753,6 +11198,122 @@ const DELAY = defineControlMeasure({
|
|
|
10753
11198
|
rule: line24DrawRule
|
|
10754
11199
|
});
|
|
10755
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
|
|
10756
11317
|
//#region src/generators/cm34-mission-tasks/disengage.ts
|
|
10757
11318
|
const DEFAULT_DISENGAGE_OPTIONS = DEFAULT_DELAY_OPTIONS;
|
|
10758
11319
|
const DISENGAGE_METADATA = {
|
|
@@ -10773,27 +11334,25 @@ const DISENGAGE_METADATA = {
|
|
|
10773
11334
|
},
|
|
10774
11335
|
drawRule: "Line24",
|
|
10775
11336
|
capturesLabelSize: true,
|
|
11337
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
10776
11338
|
params: DELAY_PARAMS
|
|
10777
11339
|
};
|
|
10778
|
-
function createDisengageSymbol(coordinates, options = {}) {
|
|
11340
|
+
function createDisengageSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
10779
11341
|
return createDelayVariantSymbol(coordinates, {
|
|
10780
11342
|
glyph: "DIS",
|
|
10781
11343
|
part: "disengage"
|
|
10782
|
-
}, options);
|
|
11344
|
+
}, options, context);
|
|
10783
11345
|
}
|
|
10784
11346
|
const DISENGAGE = defineControlMeasure({
|
|
10785
11347
|
metadata: DISENGAGE_METADATA,
|
|
10786
11348
|
generator: createDisengageSymbol,
|
|
10787
11349
|
defaultOptions: DEFAULT_DISENGAGE_OPTIONS,
|
|
10788
11350
|
rule: line24DrawRule,
|
|
10789
|
-
previewSample: {
|
|
10790
|
-
|
|
10791
|
-
|
|
10792
|
-
|
|
10793
|
-
|
|
10794
|
-
],
|
|
10795
|
-
options: { labelGapRatio: .36 }
|
|
10796
|
-
}
|
|
11351
|
+
previewSample: { controlPoints: [
|
|
11352
|
+
[-1, -.4],
|
|
11353
|
+
[1, -.4],
|
|
11354
|
+
[0, .6]
|
|
11355
|
+
] }
|
|
10797
11356
|
});
|
|
10798
11357
|
//#endregion
|
|
10799
11358
|
//#region src/generators/cm34-mission-tasks/disrupt.ts
|
|
@@ -10803,7 +11362,7 @@ const DEFAULT_DISRUPT_MISSION_TASK_OPTIONS = {
|
|
|
10803
11362
|
shaftLengthRatio: .25,
|
|
10804
11363
|
arrowheadLengthRatio: .15,
|
|
10805
11364
|
arrowheadWidthRatio: .2,
|
|
10806
|
-
|
|
11365
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING
|
|
10807
11366
|
};
|
|
10808
11367
|
const DISRUPT_MISSION_TASK_METADATA = {
|
|
10809
11368
|
id: "disrupt-mission-task",
|
|
@@ -10823,6 +11382,7 @@ const DISRUPT_MISSION_TASK_METADATA = {
|
|
|
10823
11382
|
},
|
|
10824
11383
|
drawRule: "Area12",
|
|
10825
11384
|
capturesLabelSize: true,
|
|
11385
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
10826
11386
|
params: [
|
|
10827
11387
|
{
|
|
10828
11388
|
key: "middleArrowLengthRatio",
|
|
@@ -10874,20 +11434,11 @@ const DISRUPT_MISSION_TASK_METADATA = {
|
|
|
10874
11434
|
max: 1,
|
|
10875
11435
|
step: .01
|
|
10876
11436
|
},
|
|
10877
|
-
|
|
10878
|
-
key: "labelGapRatio",
|
|
10879
|
-
presentationTier: "advanced",
|
|
10880
|
-
label: "Label gap",
|
|
10881
|
-
description: "Size of the middle-shaft gap holding the 'D' label",
|
|
10882
|
-
type: "number",
|
|
10883
|
-
min: 0,
|
|
10884
|
-
max: 1,
|
|
10885
|
-
step: .01
|
|
10886
|
-
}
|
|
11437
|
+
LABEL_PADDING_PARAM
|
|
10887
11438
|
]
|
|
10888
11439
|
};
|
|
10889
11440
|
/** Generates the DISRUPT mission task symbol (341000). */
|
|
10890
|
-
function createDisruptMissionTaskSymbol(coordinates, options = {}) {
|
|
11441
|
+
function createDisruptMissionTaskSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
10891
11442
|
const p1 = project(coordinates[0][0], coordinates[0][1]);
|
|
10892
11443
|
const p2 = project(coordinates[1][0], coordinates[1][1]);
|
|
10893
11444
|
const frame = createProjectedBaselineFrame(p1, p2, {
|
|
@@ -10919,8 +11470,8 @@ function createDisruptMissionTaskSymbol(coordinates, options = {}) {
|
|
|
10919
11470
|
const bottomTip = vecAdd(p2, vecScale(arrowDirection, bottomLength));
|
|
10920
11471
|
const rearEnd = vecAdd(middle, vecScale(arrowDirection, -rearLength));
|
|
10921
11472
|
const labelPoint = vecScale(vecAdd(middle, middleTip), .5);
|
|
10922
|
-
const
|
|
10923
|
-
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);
|
|
10924
11475
|
const gapStart = vecAdd(labelPoint, vecScale(arrowDirection, -gapHalf));
|
|
10925
11476
|
const gapEnd = vecAdd(labelPoint, vecScale(arrowDirection, gapHalf));
|
|
10926
11477
|
const p1LonLat = unproject(p1[0], p1[1]);
|
|
@@ -10943,7 +11494,7 @@ function createDisruptMissionTaskSymbol(coordinates, options = {}) {
|
|
|
10943
11494
|
createArrowHead$1(bottomTip, arrowDirection, headLength, headWidth)
|
|
10944
11495
|
]
|
|
10945
11496
|
}
|
|
10946
|
-
}, createLabelFeature(labelPoint, "D",
|
|
11497
|
+
}, createLabelFeature(labelPoint, "D", labelRotation, {
|
|
10947
11498
|
labelSize: options.labelSize,
|
|
10948
11499
|
labelSizePixels: options.labelSizePixels
|
|
10949
11500
|
})]
|
|
@@ -10962,7 +11513,7 @@ const DEFAULT_ENVELOPMENT_OPTIONS = {
|
|
|
10962
11513
|
arrowheadLengthRatio: .1,
|
|
10963
11514
|
arrowheadWidthRatio: .12,
|
|
10964
11515
|
labelPosition: .6,
|
|
10965
|
-
|
|
11516
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING,
|
|
10966
11517
|
resolution: 64
|
|
10967
11518
|
};
|
|
10968
11519
|
const ENVELOPMENT_METADATA = {
|
|
@@ -10983,6 +11534,7 @@ const ENVELOPMENT_METADATA = {
|
|
|
10983
11534
|
},
|
|
10984
11535
|
drawRule: "Line31",
|
|
10985
11536
|
capturesLabelSize: true,
|
|
11537
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
10986
11538
|
params: [
|
|
10987
11539
|
{
|
|
10988
11540
|
key: "arrowheadLengthRatio",
|
|
@@ -11014,21 +11566,12 @@ const ENVELOPMENT_METADATA = {
|
|
|
11014
11566
|
max: 1,
|
|
11015
11567
|
step: .01
|
|
11016
11568
|
},
|
|
11017
|
-
|
|
11018
|
-
key: "labelGapRatio",
|
|
11019
|
-
presentationTier: "advanced",
|
|
11020
|
-
label: "Label gap",
|
|
11021
|
-
description: "Size of the straight-line cutout holding the E label",
|
|
11022
|
-
type: "number",
|
|
11023
|
-
min: 0,
|
|
11024
|
-
max: 1,
|
|
11025
|
-
step: .01
|
|
11026
|
-
},
|
|
11569
|
+
LABEL_PADDING_PARAM,
|
|
11027
11570
|
...ARC_RESOLUTION_PARAMS
|
|
11028
11571
|
]
|
|
11029
11572
|
};
|
|
11030
11573
|
/** Generates the Envelopment mission task symbol (343500). */
|
|
11031
|
-
function createEnvelopment(coordinates, options = {}) {
|
|
11574
|
+
function createEnvelopment(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
11032
11575
|
const [c1, c2, c3, c4] = coordinates;
|
|
11033
11576
|
const p1 = project(c1[0], c1[1]);
|
|
11034
11577
|
const p2 = project(c2[0], c2[1]);
|
|
@@ -11043,8 +11586,10 @@ function createEnvelopment(coordinates, options = {}) {
|
|
|
11043
11586
|
};
|
|
11044
11587
|
const straightDirection = vecNorm(straight);
|
|
11045
11588
|
const p3 = vecAdd(p2, vecScale(straightDirection, diameter));
|
|
11046
|
-
const
|
|
11047
|
-
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));
|
|
11048
11593
|
const gapStart = vecAdd(labelPoint, vecScale(straightDirection, -gapHalf));
|
|
11049
11594
|
const gapEnd = vecAdd(labelPoint, vecScale(straightDirection, gapHalf));
|
|
11050
11595
|
const center = midpoint(p2, p3);
|
|
@@ -11067,7 +11612,7 @@ function createEnvelopment(coordinates, options = {}) {
|
|
|
11067
11612
|
arrowhead
|
|
11068
11613
|
]
|
|
11069
11614
|
}
|
|
11070
|
-
}, createLabelFeature(labelPoint, "E",
|
|
11615
|
+
}, createLabelFeature(labelPoint, "E", labelRotation, options)]
|
|
11071
11616
|
};
|
|
11072
11617
|
}
|
|
11073
11618
|
const ENVELOPMENT = defineControlMeasure({
|
|
@@ -11086,12 +11631,83 @@ const ENVELOPMENT = defineControlMeasure({
|
|
|
11086
11631
|
}
|
|
11087
11632
|
});
|
|
11088
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
|
|
11089
11705
|
//#region src/generators/cm34-mission-tasks/infiltration.ts
|
|
11090
11706
|
const DEFAULT_INFILTRATION_OPTIONS = {
|
|
11091
11707
|
arrowheadLengthRatio: .1,
|
|
11092
11708
|
arrowheadWidthRatio: .1,
|
|
11093
11709
|
labelPosition: .6,
|
|
11094
|
-
|
|
11710
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING,
|
|
11095
11711
|
resolution: 64
|
|
11096
11712
|
};
|
|
11097
11713
|
const INFILTRATION_METADATA = {
|
|
@@ -11112,6 +11728,7 @@ const INFILTRATION_METADATA = {
|
|
|
11112
11728
|
},
|
|
11113
11729
|
drawRule: "Line32",
|
|
11114
11730
|
capturesLabelSize: true,
|
|
11731
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
11115
11732
|
params: [
|
|
11116
11733
|
{
|
|
11117
11734
|
key: "arrowheadLengthRatio",
|
|
@@ -11143,16 +11760,7 @@ const INFILTRATION_METADATA = {
|
|
|
11143
11760
|
max: 1,
|
|
11144
11761
|
step: .01
|
|
11145
11762
|
},
|
|
11146
|
-
|
|
11147
|
-
key: "labelGapRatio",
|
|
11148
|
-
presentationTier: "advanced",
|
|
11149
|
-
label: "Label gap",
|
|
11150
|
-
description: "Size of the initial-line cutout holding the IN label",
|
|
11151
|
-
type: "number",
|
|
11152
|
-
min: 0,
|
|
11153
|
-
max: 1,
|
|
11154
|
-
step: .01
|
|
11155
|
-
},
|
|
11763
|
+
LABEL_PADDING_PARAM,
|
|
11156
11764
|
...ARC_RESOLUTION_PARAMS
|
|
11157
11765
|
]
|
|
11158
11766
|
};
|
|
@@ -11178,7 +11786,7 @@ function tangentDirection(p1, p2, p3) {
|
|
|
11178
11786
|
return direction;
|
|
11179
11787
|
}
|
|
11180
11788
|
/** Generates the Infiltration mission task symbol (343800). */
|
|
11181
|
-
function createInfiltration(coordinates, options = {}) {
|
|
11789
|
+
function createInfiltration(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
11182
11790
|
const [c1, c2, c3] = coordinates;
|
|
11183
11791
|
const p1 = project(c1[0], c1[1]);
|
|
11184
11792
|
const p2 = project(c2[0], c2[1]);
|
|
@@ -11211,8 +11819,10 @@ function createInfiltration(coordinates, options = {}) {
|
|
|
11211
11819
|
const arcResolution = normalizeArcResolution(options.resolution) / 2;
|
|
11212
11820
|
const firstArc = sampleProjectedArc(firstCenter, sideRadius, alongRadius, 0, Math.PI / 2, arcResolution);
|
|
11213
11821
|
const secondArc = sampleProjectedArc(secondCenter, vecScale(alongRadius, -1), vecScale(sideRadius, -1), 0, Math.PI / 2, arcResolution);
|
|
11214
|
-
const
|
|
11215
|
-
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));
|
|
11216
11826
|
const gapStart = vecAdd(labelPoint, vecScale(along, -gapHalf));
|
|
11217
11827
|
const gapEnd = vecAdd(labelPoint, vecScale(along, gapHalf));
|
|
11218
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));
|
|
@@ -11232,7 +11842,7 @@ function createInfiltration(coordinates, options = {}) {
|
|
|
11232
11842
|
arrowhead
|
|
11233
11843
|
]
|
|
11234
11844
|
}
|
|
11235
|
-
}, createLabelFeature(labelPoint, "IN",
|
|
11845
|
+
}, createLabelFeature(labelPoint, "IN", labelRotation, options)]
|
|
11236
11846
|
};
|
|
11237
11847
|
}
|
|
11238
11848
|
const INFILTRATION = defineControlMeasure({
|
|
@@ -11519,6 +12129,7 @@ const DROP_ZONE_METADATA = {
|
|
|
11519
12129
|
},
|
|
11520
12130
|
drawRule: "Area1",
|
|
11521
12131
|
capturesLabelSize: true,
|
|
12132
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
11522
12133
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
11523
12134
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
11524
12135
|
};
|
|
@@ -11583,6 +12194,7 @@ const ENCIRCLEMENT_METADATA = {
|
|
|
11583
12194
|
},
|
|
11584
12195
|
drawRule: "Area1",
|
|
11585
12196
|
capturesLabelSize: true,
|
|
12197
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
11586
12198
|
params: ENCIRCLEMENT_PARAMS,
|
|
11587
12199
|
textAmplifiers: [{
|
|
11588
12200
|
key: "N",
|
|
@@ -11736,6 +12348,7 @@ const EXTRACTION_ZONE_METADATA = {
|
|
|
11736
12348
|
},
|
|
11737
12349
|
drawRule: "Area1",
|
|
11738
12350
|
capturesLabelSize: true,
|
|
12351
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
11739
12352
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
11740
12353
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
11741
12354
|
};
|
|
@@ -12100,7 +12713,7 @@ const DEFAULT_FIX_MISSION_TASK_OPTIONS = {
|
|
|
12100
12713
|
zigzagEndRatio: .8,
|
|
12101
12714
|
zigzagPeakCount: 5,
|
|
12102
12715
|
labelPositionRatio: .09,
|
|
12103
|
-
|
|
12716
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING
|
|
12104
12717
|
};
|
|
12105
12718
|
const FIX_MISSION_TASK_METADATA = {
|
|
12106
12719
|
id: "fix-mission-task",
|
|
@@ -12120,6 +12733,7 @@ const FIX_MISSION_TASK_METADATA = {
|
|
|
12120
12733
|
},
|
|
12121
12734
|
drawRule: "Line9",
|
|
12122
12735
|
capturesLabelSize: true,
|
|
12736
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
12123
12737
|
params: [
|
|
12124
12738
|
{
|
|
12125
12739
|
key: "arrowheadLengthRatio",
|
|
@@ -12181,20 +12795,11 @@ const FIX_MISSION_TASK_METADATA = {
|
|
|
12181
12795
|
max: 1,
|
|
12182
12796
|
step: .01
|
|
12183
12797
|
},
|
|
12184
|
-
|
|
12185
|
-
key: "labelGapRatio",
|
|
12186
|
-
presentationTier: "advanced",
|
|
12187
|
-
label: "Label gap",
|
|
12188
|
-
description: "Size of the shaft gap holding the F label",
|
|
12189
|
-
type: "number",
|
|
12190
|
-
min: 0,
|
|
12191
|
-
max: 1,
|
|
12192
|
-
step: .01
|
|
12193
|
-
}
|
|
12798
|
+
LABEL_PADDING_PARAM
|
|
12194
12799
|
]
|
|
12195
12800
|
};
|
|
12196
12801
|
/** Generates the Fix mission task symbol (341100). */
|
|
12197
|
-
function createFixMissionTaskSymbol(coordinates, options = {}) {
|
|
12802
|
+
function createFixMissionTaskSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
12198
12803
|
const c1 = coordinates[0];
|
|
12199
12804
|
const c2 = coordinates[1];
|
|
12200
12805
|
const tip = project(c1[0], c1[1]);
|
|
@@ -12227,12 +12832,12 @@ function createFixMissionTaskSymbol(coordinates, options = {}) {
|
|
|
12227
12832
|
const exitRatio = Math.min(1, zigzagEnd + toothStep / 2);
|
|
12228
12833
|
const zigzagEntry = vecAdd(tail, vecScale(along, axisLength * entryRatio));
|
|
12229
12834
|
const zigzagExit = vecAdd(tail, vecScale(along, axisLength * exitRatio));
|
|
12230
|
-
const
|
|
12231
|
-
const labelGap = axisLength * clamp01(options.labelGapRatio ?? DEFAULT_FIX_MISSION_TASK_OPTIONS.labelGapRatio);
|
|
12232
|
-
const labelDistance = axisLength * labelPosition;
|
|
12835
|
+
const labelDistance = axisLength * clamp01(options.labelPositionRatio ?? DEFAULT_FIX_MISSION_TASK_OPTIONS.labelPositionRatio);
|
|
12233
12836
|
const labelCenter = vecAdd(tail, vecScale(along, labelDistance));
|
|
12234
|
-
const
|
|
12235
|
-
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);
|
|
12236
12841
|
const gapStart = vecAdd(tail, vecScale(along, gapStartDistance));
|
|
12237
12842
|
const gapEnd = vecAdd(tail, vecScale(along, gapEndDistance));
|
|
12238
12843
|
return {
|
|
@@ -12254,7 +12859,7 @@ function createFixMissionTaskSymbol(coordinates, options = {}) {
|
|
|
12254
12859
|
createArrowHead$1(tip, along, arrowheadLength, arrowheadWidth)
|
|
12255
12860
|
]
|
|
12256
12861
|
}
|
|
12257
|
-
}, createLabelFeature(labelCenter, "F",
|
|
12862
|
+
}, createLabelFeature(labelCenter, "F", labelRotation, options)]
|
|
12258
12863
|
};
|
|
12259
12864
|
}
|
|
12260
12865
|
const FIX_MISSION_TASK = defineControlMeasure({
|
|
@@ -12299,6 +12904,7 @@ const FLOT_METADATA = {
|
|
|
12299
12904
|
},
|
|
12300
12905
|
drawRule: "Line1",
|
|
12301
12906
|
capturesLabelSize: true,
|
|
12907
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
12302
12908
|
params: [
|
|
12303
12909
|
...SMOOTH_LINE_PARAMS,
|
|
12304
12910
|
{
|
|
@@ -12321,7 +12927,7 @@ const FLOT_METADATA = {
|
|
|
12321
12927
|
step: 1,
|
|
12322
12928
|
unit: "m"
|
|
12323
12929
|
},
|
|
12324
|
-
|
|
12930
|
+
LABEL_PADDING_PARAM,
|
|
12325
12931
|
{
|
|
12326
12932
|
key: "arcSegments",
|
|
12327
12933
|
presentationTier: "advanced",
|
|
@@ -12643,7 +13249,7 @@ function phaseLineNameParams(placeholder) {
|
|
|
12643
13249
|
type: "boolean",
|
|
12644
13250
|
visibleWhen: (opts) => Boolean(String(opts.phaseLineName ?? "").trim())
|
|
12645
13251
|
},
|
|
12646
|
-
|
|
13252
|
+
LABEL_PADDING_PARAM
|
|
12647
13253
|
];
|
|
12648
13254
|
}
|
|
12649
13255
|
//#endregion
|
|
@@ -12828,6 +13434,7 @@ const AIRHEAD_LINE_METADATA = {
|
|
|
12828
13434
|
},
|
|
12829
13435
|
drawRule: "Area1",
|
|
12830
13436
|
capturesLabelSize: true,
|
|
13437
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
12831
13438
|
params: LABELED_AREA_PARAMS,
|
|
12832
13439
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
12833
13440
|
};
|
|
@@ -13481,6 +14088,7 @@ const FORTIFIED_LINE_METADATA = {
|
|
|
13481
14088
|
},
|
|
13482
14089
|
drawRule: "Line1",
|
|
13483
14090
|
capturesLabelSize: true,
|
|
14091
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
13484
14092
|
params: FORTIFIED_PARAMS,
|
|
13485
14093
|
textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
|
|
13486
14094
|
};
|
|
@@ -13641,6 +14249,7 @@ const FORTIFIED_AREA_METADATA = {
|
|
|
13641
14249
|
},
|
|
13642
14250
|
drawRule: "Area1",
|
|
13643
14251
|
capturesLabelSize: true,
|
|
14252
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
13644
14253
|
params: [...FORTIFIED_AREA_PARAMS, AREA_LABEL_PADDING_PARAM],
|
|
13645
14254
|
textAmplifiers: AREA_TEXT_AMPLIFIERS
|
|
13646
14255
|
};
|
|
@@ -13765,142 +14374,45 @@ const FRONTAL_ATTACK = defineControlMeasure({
|
|
|
13765
14374
|
}
|
|
13766
14375
|
});
|
|
13767
14376
|
//#endregion
|
|
13768
|
-
//#region src/generators/cm34-mission-tasks/
|
|
13769
|
-
|
|
13770
|
-
|
|
13771
|
-
|
|
13772
|
-
|
|
13773
|
-
|
|
13774
|
-
barbCount: 9,
|
|
13775
|
-
barbLengthRatio: .18
|
|
13776
|
-
};
|
|
13777
|
-
const ISOLATE_METADATA = {
|
|
13778
|
-
id: "isolate",
|
|
13779
|
-
name: "Isolate",
|
|
13780
|
-
description: "Seals an enemy off from support, denies freedom of movement, and prevents contact with other enemy forces while offensive action continues against it.",
|
|
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.",
|
|
13781
14383
|
entity: "Mission Tasks",
|
|
13782
|
-
entityType: "
|
|
13783
|
-
value: "
|
|
13784
|
-
minCoordinates:
|
|
13785
|
-
maxCoordinates:
|
|
14384
|
+
entityType: "Forward Passage of Lines",
|
|
14385
|
+
value: "344100",
|
|
14386
|
+
minCoordinates: 3,
|
|
14387
|
+
maxCoordinates: 3,
|
|
13786
14388
|
geometry: "line",
|
|
13787
|
-
geometryTypes: ["MultiLineString"],
|
|
14389
|
+
geometryTypes: ["MultiLineString", "Point"],
|
|
13788
14390
|
paints: {
|
|
13789
14391
|
stroke: true,
|
|
13790
14392
|
fill: "none",
|
|
13791
|
-
text:
|
|
14393
|
+
text: true
|
|
13792
14394
|
},
|
|
13793
|
-
drawRule: "
|
|
13794
|
-
|
|
13795
|
-
|
|
13796
|
-
|
|
13797
|
-
presentationTier: "advanced",
|
|
13798
|
-
label: "Opening",
|
|
13799
|
-
description: "Angular width of the gap on the friendly side, in degrees",
|
|
13800
|
-
type: "number",
|
|
13801
|
-
min: 0,
|
|
13802
|
-
max: 180,
|
|
13803
|
-
step: 1
|
|
13804
|
-
},
|
|
13805
|
-
{
|
|
13806
|
-
key: "barbCount",
|
|
13807
|
-
presentationTier: "advanced",
|
|
13808
|
-
label: "Barbs",
|
|
13809
|
-
description: "Number of inward-pointing barbs spaced along the closed arc",
|
|
13810
|
-
type: "number",
|
|
13811
|
-
min: 1,
|
|
13812
|
-
max: 24,
|
|
13813
|
-
step: 1
|
|
13814
|
-
},
|
|
13815
|
-
{
|
|
13816
|
-
key: "barbLengthRatio",
|
|
13817
|
-
presentationTier: "advanced",
|
|
13818
|
-
label: "Barb length",
|
|
13819
|
-
description: "Length of each inward barb, as a ratio of the symbol radius",
|
|
13820
|
-
type: "number",
|
|
13821
|
-
min: .01,
|
|
13822
|
-
max: 1,
|
|
13823
|
-
step: .01
|
|
13824
|
-
}
|
|
13825
|
-
]
|
|
14395
|
+
drawRule: "Line24",
|
|
14396
|
+
capturesLabelSize: true,
|
|
14397
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
14398
|
+
params: DELAY_PARAMS
|
|
13826
14399
|
};
|
|
13827
|
-
|
|
13828
|
-
|
|
13829
|
-
|
|
13830
|
-
|
|
13831
|
-
|
|
13832
|
-
|
|
13833
|
-
|
|
13834
|
-
|
|
13835
|
-
|
|
13836
|
-
|
|
13837
|
-
|
|
13838
|
-
|
|
13839
|
-
|
|
13840
|
-
|
|
13841
|
-
|
|
13842
|
-
|
|
13843
|
-
* @param coordinates - [PT. 1, PT. 2] as Position arrays
|
|
13844
|
-
* @param options - Configuration options
|
|
13845
|
-
* @returns GeoJSON FeatureCollection containing a single MultiLineString feature
|
|
13846
|
-
*/
|
|
13847
|
-
function createIsolateSymbol(coordinates, options = {}) {
|
|
13848
|
-
const openingAngle = options.openingAngle ?? DEFAULT_ISOLATE_OPTIONS.openingAngle;
|
|
13849
|
-
const barbCount = options.barbCount ?? DEFAULT_ISOLATE_OPTIONS.barbCount;
|
|
13850
|
-
const barbLengthRatio = options.barbLengthRatio ?? DEFAULT_ISOLATE_OPTIONS.barbLengthRatio;
|
|
13851
|
-
const center = project(coordinates[0][0], coordinates[0][1]);
|
|
13852
|
-
const vRadius = vecSub(project(coordinates[1][0], coordinates[1][1]), center);
|
|
13853
|
-
const radius = vecMag(vRadius);
|
|
13854
|
-
if (radius < 1e-6) return {
|
|
13855
|
-
type: "FeatureCollection",
|
|
13856
|
-
features: []
|
|
13857
|
-
};
|
|
13858
|
-
const startAngle = Math.atan2(vRadius[1], vRadius[0]);
|
|
13859
|
-
const openingRad = Math.min(Math.max(openingAngle, 0), 359) * Math.PI / 180;
|
|
13860
|
-
const arcSpan = 2 * Math.PI - openingRad;
|
|
13861
|
-
const endAngle = startAngle - arcSpan;
|
|
13862
|
-
const pointOnCircle = (angle, r = radius) => vecAdd(center, [r * Math.cos(angle), r * Math.sin(angle)]);
|
|
13863
|
-
const segments = Math.max(8, Math.round(64 * arcSpan / (2 * Math.PI)));
|
|
13864
|
-
const arc = [];
|
|
13865
|
-
for (let i = 0; i <= segments; i++) {
|
|
13866
|
-
const p = pointOnCircle(startAngle - arcSpan * i / segments);
|
|
13867
|
-
arc.push(unproject(p[0], p[1]));
|
|
13868
|
-
}
|
|
13869
|
-
const barbLength = radius * barbLengthRatio;
|
|
13870
|
-
const lines = [arc];
|
|
13871
|
-
const tipDir = [Math.sin(endAngle), -Math.cos(endAngle)];
|
|
13872
|
-
lines.push(createArrowHead$1(pointOnCircle(endAngle), tipDir, barbLength, barbLength));
|
|
13873
|
-
const halfWidth = Math.asin(Math.min(1, barbLength / 2 / radius));
|
|
13874
|
-
const tipRadius = Math.max(0, radius - barbLength);
|
|
13875
|
-
const steps = Math.max(1, barbCount);
|
|
13876
|
-
for (let k = 1; k <= steps; k++) {
|
|
13877
|
-
const angle = startAngle - arcSpan * k / (steps + 1);
|
|
13878
|
-
const left = pointOnCircle(angle - halfWidth);
|
|
13879
|
-
const right = pointOnCircle(angle + halfWidth);
|
|
13880
|
-
const tip = pointOnCircle(angle, tipRadius);
|
|
13881
|
-
lines.push([
|
|
13882
|
-
unproject(left[0], left[1]),
|
|
13883
|
-
unproject(tip[0], tip[1]),
|
|
13884
|
-
unproject(right[0], right[1])
|
|
13885
|
-
]);
|
|
13886
|
-
}
|
|
13887
|
-
return {
|
|
13888
|
-
type: "FeatureCollection",
|
|
13889
|
-
features: [{
|
|
13890
|
-
type: "Feature",
|
|
13891
|
-
properties: { part: "isolate" },
|
|
13892
|
-
geometry: {
|
|
13893
|
-
type: "MultiLineString",
|
|
13894
|
-
coordinates: lines
|
|
13895
|
-
}
|
|
13896
|
-
}]
|
|
13897
|
-
};
|
|
13898
|
-
}
|
|
13899
|
-
const ISOLATE = defineControlMeasure({
|
|
13900
|
-
metadata: ISOLATE_METADATA,
|
|
13901
|
-
generator: createIsolateSymbol,
|
|
13902
|
-
defaultOptions: DEFAULT_ISOLATE_OPTIONS,
|
|
13903
|
-
rule: isolateDrawRule
|
|
14400
|
+
function createForwardPassageOfLinesSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
14401
|
+
return createDelayVariantSymbol(coordinates, {
|
|
14402
|
+
glyph: "P(F)",
|
|
14403
|
+
part: "forward-passage-of-lines"
|
|
14404
|
+
}, options, context);
|
|
14405
|
+
}
|
|
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
|
+
] }
|
|
13904
14416
|
});
|
|
13905
14417
|
//#endregion
|
|
13906
14418
|
//#region src/generators/cm34-mission-tasks/movement-to-contact.ts
|
|
@@ -14098,77 +14610,196 @@ const MOVEMENT_TO_CONTACT = defineControlMeasure({
|
|
|
14098
14610
|
}
|
|
14099
14611
|
});
|
|
14100
14612
|
//#endregion
|
|
14101
|
-
//#region src/generators/cm34-mission-tasks/
|
|
14102
|
-
const
|
|
14103
|
-
|
|
14104
|
-
|
|
14105
|
-
|
|
14106
|
-
|
|
14107
|
-
|
|
14108
|
-
|
|
14109
|
-
|
|
14110
|
-
|
|
14613
|
+
//#region src/generators/cm34-mission-tasks/occupy.ts
|
|
14614
|
+
const DEFAULT_OCCUPY_OPTIONS = {
|
|
14615
|
+
openingAngle: 30,
|
|
14616
|
+
resolution: 64,
|
|
14617
|
+
xSizeRatio: .2,
|
|
14618
|
+
xAspectRatio: 1.5,
|
|
14619
|
+
labelPadding: .2
|
|
14620
|
+
};
|
|
14621
|
+
const OCCUPY_METADATA = {
|
|
14622
|
+
id: "occupy",
|
|
14623
|
+
name: "Occupy",
|
|
14624
|
+
description: "Moves a friendly force into an area so it can control that area.",
|
|
14111
14625
|
entity: "Mission Tasks",
|
|
14112
|
-
entityType: "
|
|
14113
|
-
value: "
|
|
14114
|
-
minCoordinates:
|
|
14115
|
-
maxCoordinates:
|
|
14116
|
-
geometry: "
|
|
14626
|
+
entityType: "Occupy",
|
|
14627
|
+
value: "341700",
|
|
14628
|
+
minCoordinates: 2,
|
|
14629
|
+
maxCoordinates: 2,
|
|
14630
|
+
geometry: "area",
|
|
14117
14631
|
geometryTypes: ["MultiLineString", "Point"],
|
|
14118
14632
|
paints: {
|
|
14119
14633
|
stroke: true,
|
|
14120
14634
|
fill: "none",
|
|
14121
14635
|
text: true
|
|
14122
14636
|
},
|
|
14123
|
-
drawRule: "
|
|
14637
|
+
drawRule: "Area16",
|
|
14124
14638
|
capturesLabelSize: true,
|
|
14639
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
14125
14640
|
params: [
|
|
14126
14641
|
{
|
|
14127
|
-
key: "
|
|
14642
|
+
key: "openingAngle",
|
|
14128
14643
|
presentationTier: "advanced",
|
|
14129
|
-
label: "
|
|
14130
|
-
description: "
|
|
14644
|
+
label: "Opening",
|
|
14645
|
+
description: "Angular width of the opening on the friendly side, in degrees",
|
|
14131
14646
|
type: "number",
|
|
14132
|
-
min:
|
|
14133
|
-
max:
|
|
14134
|
-
step:
|
|
14647
|
+
min: 5,
|
|
14648
|
+
max: 180,
|
|
14649
|
+
step: 1
|
|
14135
14650
|
},
|
|
14651
|
+
...ARC_RESOLUTION_PARAMS,
|
|
14136
14652
|
{
|
|
14137
|
-
key: "
|
|
14653
|
+
key: "xSizeRatio",
|
|
14138
14654
|
presentationTier: "advanced",
|
|
14139
|
-
label: "
|
|
14140
|
-
description: "
|
|
14655
|
+
label: "X size",
|
|
14656
|
+
description: "Overall size of the terminal X as a ratio of the symbol radius",
|
|
14141
14657
|
type: "number",
|
|
14142
|
-
min: .
|
|
14143
|
-
max:
|
|
14658
|
+
min: .02,
|
|
14659
|
+
max: .75,
|
|
14144
14660
|
step: .01
|
|
14145
14661
|
},
|
|
14146
14662
|
{
|
|
14147
|
-
key: "
|
|
14663
|
+
key: "xAspectRatio",
|
|
14664
|
+
presentationTier: "advanced",
|
|
14665
|
+
label: "X aspect ratio",
|
|
14666
|
+
description: "Width-to-height ratio of the terminal X",
|
|
14667
|
+
type: "number",
|
|
14668
|
+
min: .25,
|
|
14669
|
+
max: 4,
|
|
14670
|
+
step: .05
|
|
14671
|
+
},
|
|
14672
|
+
{
|
|
14673
|
+
key: "labelPadding",
|
|
14148
14674
|
presentationTier: "advanced",
|
|
14149
|
-
label: "
|
|
14150
|
-
description: "
|
|
14675
|
+
label: "O padding",
|
|
14676
|
+
description: "Extra boundary clearance on each side of O, as a ratio of its height",
|
|
14151
14677
|
type: "number",
|
|
14152
14678
|
min: 0,
|
|
14153
|
-
max:
|
|
14154
|
-
step: .
|
|
14679
|
+
max: 2,
|
|
14680
|
+
step: .05
|
|
14155
14681
|
}
|
|
14156
14682
|
]
|
|
14157
14683
|
};
|
|
14158
|
-
|
|
14159
|
-
|
|
14160
|
-
|
|
14161
|
-
|
|
14162
|
-
|
|
14163
|
-
|
|
14164
|
-
|
|
14165
|
-
|
|
14166
|
-
|
|
14167
|
-
|
|
14168
|
-
|
|
14169
|
-
const
|
|
14170
|
-
|
|
14171
|
-
|
|
14684
|
+
const TAU$2 = Math.PI * 2;
|
|
14685
|
+
const MIN_LABEL_GAP_METERS$2 = 1;
|
|
14686
|
+
/** Generates the Occupy mission task symbol (341700). */
|
|
14687
|
+
function createOccupySymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
14688
|
+
const center = project(coordinates[0][0], coordinates[0][1]);
|
|
14689
|
+
const radiusVector = vecSub(project(coordinates[1][0], coordinates[1][1]), center);
|
|
14690
|
+
const radius = vecMag(radiusVector);
|
|
14691
|
+
if (radius < 1e-6) return {
|
|
14692
|
+
type: "FeatureCollection",
|
|
14693
|
+
features: []
|
|
14694
|
+
};
|
|
14695
|
+
const resolved = {
|
|
14696
|
+
...DEFAULT_OCCUPY_OPTIONS,
|
|
14697
|
+
...options
|
|
14698
|
+
};
|
|
14699
|
+
const bulgeVector = [radiusVector[1], -radiusVector[0]];
|
|
14700
|
+
const halfTurnResolution = normalizeArcResolution(resolved.resolution) / 2;
|
|
14701
|
+
const boundarySpan = TAU$2 - Math.min(180, Math.max(5, resolved.openingAngle)) * Math.PI / 180;
|
|
14702
|
+
const labelSweep = boundarySpan / 2;
|
|
14703
|
+
const labelHeight = resolveTextMetrics("O", options, context, "regular", radius * .12).height;
|
|
14704
|
+
const labelPadding = Math.max(0, resolved.labelPadding);
|
|
14705
|
+
const labelHalfGap = Math.min(boundarySpan / 4, Math.max(labelHeight * (LABEL_HALF_GLYPH_RATIO + labelPadding), MIN_LABEL_GAP_METERS$2) / radius);
|
|
14706
|
+
const sampleArc = (from, to) => sampleProjectedArc(center, radiusVector, bulgeVector, from, to, halfTurnResolution);
|
|
14707
|
+
const lines = [sampleArc(0, labelSweep - labelHalfGap), sampleArc(labelSweep + labelHalfGap, boundarySpan)];
|
|
14708
|
+
const terminal = arcPointAt(center, radiusVector, bulgeVector, boundarySpan);
|
|
14709
|
+
const radial = vecScale(vecSub(terminal, center), 1 / radius);
|
|
14710
|
+
const tangent = [radial[1], -radial[0]];
|
|
14711
|
+
const halfHeight = radius * Math.max(0, resolved.xSizeRatio) / 2;
|
|
14712
|
+
const radialHalfWidth = vecScale(radial, halfHeight * Math.max(.25, resolved.xAspectRatio));
|
|
14713
|
+
const tangentHalfHeight = vecScale(tangent, halfHeight);
|
|
14714
|
+
const stroke = (diagonal) => [vecAdd(terminal, diagonal), vecSub(terminal, diagonal)].map((point) => unproject(point[0], point[1]));
|
|
14715
|
+
lines.push(stroke(vecAdd(radialHalfWidth, tangentHalfHeight)), stroke(vecSub(radialHalfWidth, tangentHalfHeight)));
|
|
14716
|
+
const labelPoint = arcPointAt(center, radiusVector, bulgeVector, labelSweep);
|
|
14717
|
+
return {
|
|
14718
|
+
type: "FeatureCollection",
|
|
14719
|
+
features: [{
|
|
14720
|
+
type: "Feature",
|
|
14721
|
+
properties: { part: "occupy" },
|
|
14722
|
+
geometry: {
|
|
14723
|
+
type: "MultiLineString",
|
|
14724
|
+
coordinates: lines
|
|
14725
|
+
}
|
|
14726
|
+
}, createLabelFeature(labelPoint, "O", Math.PI, options)]
|
|
14727
|
+
};
|
|
14728
|
+
}
|
|
14729
|
+
const OCCUPY = defineControlMeasure({
|
|
14730
|
+
metadata: OCCUPY_METADATA,
|
|
14731
|
+
generator: createOccupySymbol,
|
|
14732
|
+
defaultOptions: DEFAULT_OCCUPY_OPTIONS,
|
|
14733
|
+
rule: occupyDrawRule,
|
|
14734
|
+
previewSample: {
|
|
14735
|
+
controlPoints: [[0, 0], [-.8, .2]],
|
|
14736
|
+
options: { labelSize: 90 }
|
|
14737
|
+
}
|
|
14738
|
+
});
|
|
14739
|
+
//#endregion
|
|
14740
|
+
//#region src/generators/cm34-mission-tasks/penetrate.ts
|
|
14741
|
+
const DEFAULT_PENETRATE_OPTIONS = {
|
|
14742
|
+
arrowheadLengthRatio: .15,
|
|
14743
|
+
arrowheadWidthRatio: .2,
|
|
14744
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING
|
|
14745
|
+
};
|
|
14746
|
+
const PENETRATE_METADATA = {
|
|
14747
|
+
id: "penetrate",
|
|
14748
|
+
name: "Penetrate",
|
|
14749
|
+
description: "Breaks through an enemy defense and disrupts the continuity of the defensive system.",
|
|
14750
|
+
entity: "Mission Tasks",
|
|
14751
|
+
entityType: "Penetrate",
|
|
14752
|
+
value: "341800",
|
|
14753
|
+
minCoordinates: 3,
|
|
14754
|
+
maxCoordinates: 3,
|
|
14755
|
+
geometry: "line",
|
|
14756
|
+
geometryTypes: ["MultiLineString", "Point"],
|
|
14757
|
+
paints: {
|
|
14758
|
+
stroke: true,
|
|
14759
|
+
fill: "none",
|
|
14760
|
+
text: true
|
|
14761
|
+
},
|
|
14762
|
+
drawRule: "Area17",
|
|
14763
|
+
capturesLabelSize: true,
|
|
14764
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
14765
|
+
params: [
|
|
14766
|
+
{
|
|
14767
|
+
key: "arrowheadLengthRatio",
|
|
14768
|
+
presentationTier: "advanced",
|
|
14769
|
+
label: "Arrowhead length",
|
|
14770
|
+
description: "Arrowhead length as a ratio of the barrier length",
|
|
14771
|
+
type: "number",
|
|
14772
|
+
min: .01,
|
|
14773
|
+
max: 1,
|
|
14774
|
+
step: .01
|
|
14775
|
+
},
|
|
14776
|
+
{
|
|
14777
|
+
key: "arrowheadWidthRatio",
|
|
14778
|
+
presentationTier: "advanced",
|
|
14779
|
+
label: "Arrowhead width",
|
|
14780
|
+
description: "Arrowhead base width as a ratio of the barrier length",
|
|
14781
|
+
type: "number",
|
|
14782
|
+
min: .01,
|
|
14783
|
+
max: 1,
|
|
14784
|
+
step: .01
|
|
14785
|
+
},
|
|
14786
|
+
LABEL_PADDING_PARAM
|
|
14787
|
+
]
|
|
14788
|
+
};
|
|
14789
|
+
/** Generates the PENETRATE mission task symbol (341800). */
|
|
14790
|
+
function createPenetrateSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
14791
|
+
const frame = createProjectedBaselineFrame(project(coordinates[0][0], coordinates[0][1]), project(coordinates[1][0], coordinates[1][1]), {
|
|
14792
|
+
origin: "midpoint",
|
|
14793
|
+
normal: "right"
|
|
14794
|
+
});
|
|
14795
|
+
if (!frame) return {
|
|
14796
|
+
type: "FeatureCollection",
|
|
14797
|
+
features: []
|
|
14798
|
+
};
|
|
14799
|
+
const c3 = coordinates[2];
|
|
14800
|
+
const signedTailDistance = c3 ? frame.signedNormalDistance(c3) : frame.length * 1.5;
|
|
14801
|
+
const shaftLength = Math.abs(signedTailDistance);
|
|
14802
|
+
if (shaftLength < 1e-6) return {
|
|
14172
14803
|
type: "FeatureCollection",
|
|
14173
14804
|
features: []
|
|
14174
14805
|
};
|
|
@@ -14176,29 +14807,29 @@ function createPenetrateSymbol(coordinates, options = {}) {
|
|
|
14176
14807
|
const tail = vecAdd(frame.origin, vecScale(frame.normal, signedTailDistance));
|
|
14177
14808
|
const arrowDirection = vecScale(vecSub(tip, tail), 1 / shaftLength);
|
|
14178
14809
|
const labelPoint = vecScale(vecAdd(tail, tip), .5);
|
|
14179
|
-
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));
|
|
14180
14814
|
const gapStart = vecAdd(labelPoint, vecScale(arrowDirection, -gapHalf));
|
|
14181
14815
|
const gapEnd = vecAdd(labelPoint, vecScale(arrowDirection, gapHalf));
|
|
14182
14816
|
const headLength = frame.length * Math.max(0, options.arrowheadLengthRatio ?? DEFAULT_PENETRATE_OPTIONS.arrowheadLengthRatio);
|
|
14183
14817
|
const headWidth = frame.length * Math.max(0, options.arrowheadWidthRatio ?? DEFAULT_PENETRATE_OPTIONS.arrowheadWidthRatio);
|
|
14184
|
-
const lineFeature = {
|
|
14185
|
-
type: "Feature",
|
|
14186
|
-
properties: { part: "penetrate" },
|
|
14187
|
-
geometry: {
|
|
14188
|
-
type: "MultiLineString",
|
|
14189
|
-
coordinates: [
|
|
14190
|
-
[coordinates[0], coordinates[1]],
|
|
14191
|
-
[unproject(tail[0], tail[1]), unproject(gapStart[0], gapStart[1])],
|
|
14192
|
-
[unproject(gapEnd[0], gapEnd[1]), unproject(tip[0], tip[1])],
|
|
14193
|
-
createArrowHead$1(tip, arrowDirection, headLength, headWidth)
|
|
14194
|
-
]
|
|
14195
|
-
}
|
|
14196
|
-
};
|
|
14197
|
-
const shaftRotation = Math.atan2(arrowDirection[1], arrowDirection[0]);
|
|
14198
|
-
const renderedLabelRotation = Math.PI - shaftRotation;
|
|
14199
14818
|
return {
|
|
14200
14819
|
type: "FeatureCollection",
|
|
14201
|
-
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, {
|
|
14202
14833
|
labelSize: options.labelSize,
|
|
14203
14834
|
labelSizePixels: options.labelSizePixels
|
|
14204
14835
|
})]
|
|
@@ -14216,6 +14847,149 @@ const PENETRATE = defineControlMeasure({
|
|
|
14216
14847
|
] }
|
|
14217
14848
|
});
|
|
14218
14849
|
//#endregion
|
|
14850
|
+
//#region src/generators/cm34-mission-tasks/pursuit.ts
|
|
14851
|
+
const DEFAULT_PURSUIT_OPTIONS = {
|
|
14852
|
+
arrowheadLengthRatio: .1,
|
|
14853
|
+
arrowheadWidthRatio: .12,
|
|
14854
|
+
terminalBarLengthRatio: 1.5,
|
|
14855
|
+
labelPosition: .6,
|
|
14856
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING,
|
|
14857
|
+
resolution: 64
|
|
14858
|
+
};
|
|
14859
|
+
const PURSUIT_METADATA = {
|
|
14860
|
+
id: "pursuit",
|
|
14861
|
+
name: "Pursuit",
|
|
14862
|
+
description: "A mission task to catch or cut off a hostile force attempting to escape, with the aim of destroying it.",
|
|
14863
|
+
entity: "Mission Tasks",
|
|
14864
|
+
entityType: "Pursuit",
|
|
14865
|
+
value: "344000",
|
|
14866
|
+
minCoordinates: 3,
|
|
14867
|
+
maxCoordinates: 3,
|
|
14868
|
+
geometry: "line",
|
|
14869
|
+
geometryTypes: ["MultiLineString", "Point"],
|
|
14870
|
+
paints: {
|
|
14871
|
+
stroke: true,
|
|
14872
|
+
fill: "none",
|
|
14873
|
+
text: true
|
|
14874
|
+
},
|
|
14875
|
+
drawRule: "Line33",
|
|
14876
|
+
capturesLabelSize: true,
|
|
14877
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
14878
|
+
params: [
|
|
14879
|
+
{
|
|
14880
|
+
key: "arrowheadLengthRatio",
|
|
14881
|
+
presentationTier: "advanced",
|
|
14882
|
+
label: "Arrowhead length",
|
|
14883
|
+
description: "Arrowhead length as a ratio of the arc diameter",
|
|
14884
|
+
type: "number",
|
|
14885
|
+
min: .01,
|
|
14886
|
+
max: .5,
|
|
14887
|
+
step: .01
|
|
14888
|
+
},
|
|
14889
|
+
{
|
|
14890
|
+
key: "arrowheadWidthRatio",
|
|
14891
|
+
presentationTier: "advanced",
|
|
14892
|
+
label: "Arrowhead width",
|
|
14893
|
+
description: "Arrowhead width as a ratio of the arc diameter",
|
|
14894
|
+
type: "number",
|
|
14895
|
+
min: .01,
|
|
14896
|
+
max: .5,
|
|
14897
|
+
step: .01
|
|
14898
|
+
},
|
|
14899
|
+
{
|
|
14900
|
+
key: "terminalBarLengthRatio",
|
|
14901
|
+
presentationTier: "advanced",
|
|
14902
|
+
label: "Terminal bar length",
|
|
14903
|
+
description: "Terminal bar length as a multiple of the arrowhead width",
|
|
14904
|
+
type: "number",
|
|
14905
|
+
min: .1,
|
|
14906
|
+
max: 10,
|
|
14907
|
+
step: .1
|
|
14908
|
+
},
|
|
14909
|
+
{
|
|
14910
|
+
key: "labelPosition",
|
|
14911
|
+
presentationTier: "advanced",
|
|
14912
|
+
label: "Label position",
|
|
14913
|
+
description: "Position of the P label along the straight portion",
|
|
14914
|
+
type: "number",
|
|
14915
|
+
min: 0,
|
|
14916
|
+
max: 1,
|
|
14917
|
+
step: .01
|
|
14918
|
+
},
|
|
14919
|
+
LABEL_PADDING_PARAM,
|
|
14920
|
+
...ARC_RESOLUTION_PARAMS
|
|
14921
|
+
]
|
|
14922
|
+
};
|
|
14923
|
+
/** Generates the Pursuit mission task symbol (344000). */
|
|
14924
|
+
function createPursuit(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
14925
|
+
const [c1, c2, c3] = coordinates;
|
|
14926
|
+
const p1 = project(c1[0], c1[1]);
|
|
14927
|
+
const p2 = project(c2[0], c2[1]);
|
|
14928
|
+
const rawP3 = project(c3[0], c3[1]);
|
|
14929
|
+
const frame = createProjectedBaselineFrame(p1, p2, {
|
|
14930
|
+
origin: "p2",
|
|
14931
|
+
normal: "right"
|
|
14932
|
+
});
|
|
14933
|
+
if (!frame) return {
|
|
14934
|
+
type: "FeatureCollection",
|
|
14935
|
+
features: []
|
|
14936
|
+
};
|
|
14937
|
+
const { direction: straightDirection, normal: diameterAxis, length: straightLength } = frame;
|
|
14938
|
+
const signedDiameter = vecDot(vecSub(rawP3, frame.origin), diameterAxis);
|
|
14939
|
+
const diameter = Math.abs(signedDiameter);
|
|
14940
|
+
if (diameter < 1e-6) return {
|
|
14941
|
+
type: "FeatureCollection",
|
|
14942
|
+
features: []
|
|
14943
|
+
};
|
|
14944
|
+
const p3 = vecAdd(p2, vecScale(diameterAxis, signedDiameter));
|
|
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));
|
|
14949
|
+
const gapStart = vecAdd(labelPoint, vecScale(straightDirection, -gapHalf));
|
|
14950
|
+
const gapEnd = vecAdd(labelPoint, vecScale(straightDirection, gapHalf));
|
|
14951
|
+
const center = midpoint(p2, p3);
|
|
14952
|
+
const arc = sampleProjectedArc(center, vecSub(p2, center), vecScale(straightDirection, diameter / 2), 0, Math.PI, normalizeArcResolution(options.resolution) / 2);
|
|
14953
|
+
const arrowheadLength = diameter * Math.max(0, options.arrowheadLengthRatio ?? DEFAULT_PURSUIT_OPTIONS.arrowheadLengthRatio);
|
|
14954
|
+
const arrowheadWidth = diameter * Math.max(0, options.arrowheadWidthRatio ?? DEFAULT_PURSUIT_OPTIONS.arrowheadWidthRatio);
|
|
14955
|
+
const arrowhead = createArrowHead$1(p3, vecScale(straightDirection, -1), arrowheadLength, arrowheadWidth);
|
|
14956
|
+
const terminalBarHalf = arrowheadWidth * Math.max(0, options.terminalBarLengthRatio ?? DEFAULT_PURSUIT_OPTIONS.terminalBarLengthRatio) / 2;
|
|
14957
|
+
const terminalStart = vecAdd(p3, vecScale(diameterAxis, terminalBarHalf));
|
|
14958
|
+
const terminalEnd = vecSub(p3, vecScale(diameterAxis, terminalBarHalf));
|
|
14959
|
+
const terminalBar = [unproject(terminalStart[0], terminalStart[1]), unproject(terminalEnd[0], terminalEnd[1])];
|
|
14960
|
+
return {
|
|
14961
|
+
type: "FeatureCollection",
|
|
14962
|
+
features: [{
|
|
14963
|
+
type: "Feature",
|
|
14964
|
+
properties: { part: "pursuit" },
|
|
14965
|
+
geometry: {
|
|
14966
|
+
type: "MultiLineString",
|
|
14967
|
+
coordinates: [
|
|
14968
|
+
[c1, unproject(gapStart[0], gapStart[1])],
|
|
14969
|
+
[unproject(gapEnd[0], gapEnd[1]), c2],
|
|
14970
|
+
arc,
|
|
14971
|
+
arrowhead,
|
|
14972
|
+
terminalBar
|
|
14973
|
+
]
|
|
14974
|
+
}
|
|
14975
|
+
}, createLabelFeature(labelPoint, "P", labelRotation, options)]
|
|
14976
|
+
};
|
|
14977
|
+
}
|
|
14978
|
+
const PURSUIT = defineControlMeasure({
|
|
14979
|
+
metadata: PURSUIT_METADATA,
|
|
14980
|
+
generator: createPursuit,
|
|
14981
|
+
defaultOptions: DEFAULT_PURSUIT_OPTIONS,
|
|
14982
|
+
rule: line33DrawRule,
|
|
14983
|
+
previewSample: {
|
|
14984
|
+
controlPoints: [
|
|
14985
|
+
[-1, .4],
|
|
14986
|
+
[1, .4],
|
|
14987
|
+
[0, -.6]
|
|
14988
|
+
],
|
|
14989
|
+
options: { labelSize: 100 }
|
|
14990
|
+
}
|
|
14991
|
+
});
|
|
14992
|
+
//#endregion
|
|
14219
14993
|
//#region src/generators/cm34-mission-tasks/rearward-passage-of-lines.ts
|
|
14220
14994
|
const DEFAULT_REARWARD_PASSAGE_OF_LINES_OPTIONS = DEFAULT_DELAY_OPTIONS;
|
|
14221
14995
|
const REARWARD_PASSAGE_OF_LINES_METADATA = {
|
|
@@ -14236,27 +15010,80 @@ const REARWARD_PASSAGE_OF_LINES_METADATA = {
|
|
|
14236
15010
|
},
|
|
14237
15011
|
drawRule: "Line24",
|
|
14238
15012
|
capturesLabelSize: true,
|
|
15013
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
14239
15014
|
params: DELAY_PARAMS
|
|
14240
15015
|
};
|
|
14241
|
-
function createRearwardPassageOfLinesSymbol(coordinates, options = {}) {
|
|
15016
|
+
function createRearwardPassageOfLinesSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
14242
15017
|
return createDelayVariantSymbol(coordinates, {
|
|
14243
15018
|
glyph: "P(R)",
|
|
14244
15019
|
part: "rearward-passage-of-lines"
|
|
14245
|
-
}, options);
|
|
15020
|
+
}, options, context);
|
|
14246
15021
|
}
|
|
14247
15022
|
const REARWARD_PASSAGE_OF_LINES = defineControlMeasure({
|
|
14248
15023
|
metadata: REARWARD_PASSAGE_OF_LINES_METADATA,
|
|
14249
15024
|
generator: createRearwardPassageOfLinesSymbol,
|
|
14250
15025
|
defaultOptions: DEFAULT_REARWARD_PASSAGE_OF_LINES_OPTIONS,
|
|
14251
15026
|
rule: line24DrawRule,
|
|
14252
|
-
previewSample: {
|
|
14253
|
-
|
|
14254
|
-
|
|
14255
|
-
|
|
14256
|
-
|
|
14257
|
-
|
|
14258
|
-
|
|
14259
|
-
|
|
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
|
+
] }
|
|
14260
15087
|
});
|
|
14261
15088
|
//#endregion
|
|
14262
15089
|
//#region src/generators/cm34-mission-tasks/retire.ts
|
|
@@ -14279,14 +15106,15 @@ const RETIRE_METADATA = {
|
|
|
14279
15106
|
},
|
|
14280
15107
|
drawRule: "Line24",
|
|
14281
15108
|
capturesLabelSize: true,
|
|
15109
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
14282
15110
|
params: DELAY_PARAMS
|
|
14283
15111
|
};
|
|
14284
15112
|
/** Generates the RETIRE/RETIREMENT mission task symbol (342000). */
|
|
14285
|
-
function createRetireSymbol(coordinates, options = {}) {
|
|
15113
|
+
function createRetireSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
14286
15114
|
return createDelayVariantSymbol(coordinates, {
|
|
14287
15115
|
glyph: "R",
|
|
14288
15116
|
part: "retire"
|
|
14289
|
-
}, options);
|
|
15117
|
+
}, options, context);
|
|
14290
15118
|
}
|
|
14291
15119
|
const RETIRE = defineControlMeasure({
|
|
14292
15120
|
metadata: RETIRE_METADATA,
|
|
@@ -14340,85 +15168,204 @@ const SCREEN = defineControlMeasure({
|
|
|
14340
15168
|
}
|
|
14341
15169
|
});
|
|
14342
15170
|
//#endregion
|
|
14343
|
-
//#region src/generators/cm34-mission-tasks/
|
|
14344
|
-
const
|
|
14345
|
-
|
|
14346
|
-
|
|
14347
|
-
|
|
14348
|
-
|
|
14349
|
-
|
|
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
|
|
14350
15178
|
};
|
|
14351
|
-
const
|
|
14352
|
-
id: "
|
|
14353
|
-
name: "
|
|
14354
|
-
description: "
|
|
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.",
|
|
14355
15183
|
entity: "Mission Tasks",
|
|
14356
|
-
entityType: "
|
|
14357
|
-
value: "
|
|
14358
|
-
minCoordinates:
|
|
14359
|
-
maxCoordinates:
|
|
14360
|
-
geometry: "
|
|
15184
|
+
entityType: "Secure",
|
|
15185
|
+
value: "342100",
|
|
15186
|
+
minCoordinates: 2,
|
|
15187
|
+
maxCoordinates: 2,
|
|
15188
|
+
geometry: "area",
|
|
14361
15189
|
geometryTypes: ["MultiLineString", "Point"],
|
|
14362
15190
|
paints: {
|
|
14363
15191
|
stroke: true,
|
|
14364
15192
|
fill: "none",
|
|
14365
15193
|
text: true
|
|
14366
15194
|
},
|
|
14367
|
-
drawRule: "
|
|
15195
|
+
drawRule: "Area19",
|
|
14368
15196
|
capturesLabelSize: true,
|
|
15197
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
14369
15198
|
params: [
|
|
14370
15199
|
{
|
|
14371
|
-
key: "
|
|
15200
|
+
key: "openingAngle",
|
|
14372
15201
|
presentationTier: "advanced",
|
|
14373
|
-
label: "
|
|
14374
|
-
description: "
|
|
15202
|
+
label: "Opening",
|
|
15203
|
+
description: "Angular width of the opening on the friendly side, in degrees",
|
|
14375
15204
|
type: "number",
|
|
14376
|
-
min:
|
|
14377
|
-
max:
|
|
14378
|
-
step:
|
|
15205
|
+
min: 5,
|
|
15206
|
+
max: 180,
|
|
15207
|
+
step: 1
|
|
14379
15208
|
},
|
|
15209
|
+
...ARC_RESOLUTION_PARAMS,
|
|
14380
15210
|
{
|
|
14381
|
-
key: "
|
|
15211
|
+
key: "arrowheadLengthRatio",
|
|
14382
15212
|
presentationTier: "advanced",
|
|
14383
|
-
label: "Arrowhead
|
|
14384
|
-
description: "Arrowhead
|
|
15213
|
+
label: "Arrowhead length",
|
|
15214
|
+
description: "Arrowhead length as a ratio of the symbol radius",
|
|
14385
15215
|
type: "number",
|
|
14386
|
-
min:
|
|
14387
|
-
max:
|
|
15216
|
+
min: 0,
|
|
15217
|
+
max: 1,
|
|
14388
15218
|
step: .01
|
|
14389
15219
|
},
|
|
14390
15220
|
{
|
|
14391
|
-
key: "
|
|
15221
|
+
key: "arrowheadWidthRatio",
|
|
14392
15222
|
presentationTier: "advanced",
|
|
14393
|
-
label: "
|
|
14394
|
-
description: "
|
|
15223
|
+
label: "Arrowhead width",
|
|
15224
|
+
description: "Arrowhead width as a ratio of the symbol radius",
|
|
14395
15225
|
type: "number",
|
|
14396
15226
|
min: 0,
|
|
14397
|
-
max:
|
|
15227
|
+
max: 1,
|
|
14398
15228
|
step: .01
|
|
14399
15229
|
},
|
|
14400
15230
|
{
|
|
14401
|
-
key: "
|
|
14402
|
-
presentationTier: "advanced",
|
|
14403
|
-
label: "Curve segments",
|
|
14404
|
-
description: "Number of straight segments used to approximate the circular arc",
|
|
14405
|
-
type: "number",
|
|
14406
|
-
min: 4,
|
|
14407
|
-
max: 128,
|
|
14408
|
-
step: 1
|
|
14409
|
-
},
|
|
14410
|
-
{
|
|
14411
|
-
key: "circleSegments",
|
|
15231
|
+
key: "labelPadding",
|
|
14412
15232
|
presentationTier: "advanced",
|
|
14413
|
-
label: "
|
|
14414
|
-
description: "
|
|
15233
|
+
label: "S padding",
|
|
15234
|
+
description: "Extra boundary clearance on each side of S, as a ratio of its height",
|
|
14415
15235
|
type: "number",
|
|
14416
|
-
min:
|
|
14417
|
-
max:
|
|
14418
|
-
step:
|
|
15236
|
+
min: 0,
|
|
15237
|
+
max: 2,
|
|
15238
|
+
step: .05
|
|
14419
15239
|
}
|
|
14420
15240
|
]
|
|
14421
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
|
|
15297
|
+
//#region src/generators/cm34-mission-tasks/seize.ts
|
|
15298
|
+
const DEFAULT_SEIZE_OPTIONS = {
|
|
15299
|
+
arrowheadLengthRatio: .1,
|
|
15300
|
+
arrowheadWidthRatio: .12,
|
|
15301
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING,
|
|
15302
|
+
curveSegments: 48,
|
|
15303
|
+
circleSegments: 64
|
|
15304
|
+
};
|
|
15305
|
+
const SEIZE_PARAMS = [
|
|
15306
|
+
{
|
|
15307
|
+
key: "arrowheadLengthRatio",
|
|
15308
|
+
presentationTier: "advanced",
|
|
15309
|
+
label: "Arrowhead length",
|
|
15310
|
+
description: "Arrowhead length as a ratio of the PT. 2-to-PT. 4 span",
|
|
15311
|
+
type: "number",
|
|
15312
|
+
min: .01,
|
|
15313
|
+
max: .5,
|
|
15314
|
+
step: .01
|
|
15315
|
+
},
|
|
15316
|
+
{
|
|
15317
|
+
key: "arrowheadWidthRatio",
|
|
15318
|
+
presentationTier: "advanced",
|
|
15319
|
+
label: "Arrowhead width",
|
|
15320
|
+
description: "Arrowhead base width as a ratio of the PT. 2-to-PT. 4 span",
|
|
15321
|
+
type: "number",
|
|
15322
|
+
min: .01,
|
|
15323
|
+
max: .5,
|
|
15324
|
+
step: .01
|
|
15325
|
+
},
|
|
15326
|
+
LABEL_PADDING_PARAM,
|
|
15327
|
+
{
|
|
15328
|
+
key: "curveSegments",
|
|
15329
|
+
presentationTier: "advanced",
|
|
15330
|
+
label: "Curve segments",
|
|
15331
|
+
description: "Number of straight segments used to approximate the circular arc",
|
|
15332
|
+
type: "number",
|
|
15333
|
+
min: 4,
|
|
15334
|
+
max: 128,
|
|
15335
|
+
step: 1
|
|
15336
|
+
},
|
|
15337
|
+
{
|
|
15338
|
+
key: "circleSegments",
|
|
15339
|
+
presentationTier: "advanced",
|
|
15340
|
+
label: "Circle segments",
|
|
15341
|
+
description: "Number of straight segments used to approximate the assigned-unit circle",
|
|
15342
|
+
type: "number",
|
|
15343
|
+
min: 12,
|
|
15344
|
+
max: 128,
|
|
15345
|
+
step: 1
|
|
15346
|
+
}
|
|
15347
|
+
];
|
|
15348
|
+
const SEIZE_METADATA = {
|
|
15349
|
+
id: "seize",
|
|
15350
|
+
name: "Seize",
|
|
15351
|
+
description: "Takes possession of a designated area using overwhelming force and establishes control of it.",
|
|
15352
|
+
entity: "Mission Tasks",
|
|
15353
|
+
entityType: "Seize",
|
|
15354
|
+
value: "342300",
|
|
15355
|
+
minCoordinates: 4,
|
|
15356
|
+
maxCoordinates: 4,
|
|
15357
|
+
geometry: "line",
|
|
15358
|
+
geometryTypes: ["MultiLineString", "Point"],
|
|
15359
|
+
paints: {
|
|
15360
|
+
stroke: true,
|
|
15361
|
+
fill: "none",
|
|
15362
|
+
text: true
|
|
15363
|
+
},
|
|
15364
|
+
drawRule: "Line27",
|
|
15365
|
+
capturesLabelSize: true,
|
|
15366
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
15367
|
+
params: SEIZE_PARAMS
|
|
15368
|
+
};
|
|
14422
15369
|
function positiveAngle(angle) {
|
|
14423
15370
|
const normalized = angle % (Math.PI * 2);
|
|
14424
15371
|
return normalized < 0 ? normalized + Math.PI * 2 : normalized;
|
|
@@ -14447,8 +15394,8 @@ function threePointArc(start, middle, end) {
|
|
|
14447
15394
|
sweepAngle: positiveAngle(middleAngle - startAngle) <= counterclockwiseSweep ? counterclockwiseSweep : counterclockwiseSweep - Math.PI * 2
|
|
14448
15395
|
};
|
|
14449
15396
|
}
|
|
14450
|
-
/**
|
|
14451
|
-
function
|
|
15397
|
+
/** Shared geometry for mission tasks that use the Seize graphic. */
|
|
15398
|
+
function createSeizeLikeSymbol(coordinates, options = {}, label = "S", part = "seize", context = {}) {
|
|
14452
15399
|
const [c1, c2, c3, c4] = coordinates;
|
|
14453
15400
|
const center = project(c1[0], c1[1]);
|
|
14454
15401
|
const start = project(c2[0], c2[1]);
|
|
@@ -14469,8 +15416,14 @@ function createSeizeSymbol(coordinates, options = {}) {
|
|
|
14469
15416
|
let arrowDirection;
|
|
14470
15417
|
let labelPoint;
|
|
14471
15418
|
let tangentAtLabel;
|
|
15419
|
+
let labelRotation;
|
|
14472
15420
|
if (arc) {
|
|
14473
|
-
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);
|
|
14474
15427
|
const gapStart = .5 - gapHalf;
|
|
14475
15428
|
const gapEnd = .5 + gapHalf;
|
|
14476
15429
|
const sampleArc = (from, to) => {
|
|
@@ -14486,22 +15439,19 @@ function createSeizeSymbol(coordinates, options = {}) {
|
|
|
14486
15439
|
firstArcPart = sampleArc(0, gapStart);
|
|
14487
15440
|
secondArcPart = sampleArc(gapEnd, 1);
|
|
14488
15441
|
const endAngle = arc.startAngle + arc.sweepAngle;
|
|
14489
|
-
const sweepSign = Math.sign(arc.sweepAngle);
|
|
14490
15442
|
arrowDirection = [-Math.sin(endAngle) * sweepSign, Math.cos(endAngle) * sweepSign];
|
|
14491
|
-
const labelAngle = arc.startAngle + arc.sweepAngle / 2;
|
|
14492
|
-
labelPoint = vecAdd(arc.center, [arc.radius * Math.cos(labelAngle), arc.radius * Math.sin(labelAngle)]);
|
|
14493
|
-
tangentAtLabel = [-Math.sin(labelAngle) * sweepSign, Math.cos(labelAngle) * sweepSign];
|
|
14494
15443
|
} else {
|
|
14495
15444
|
labelPoint = vecScale(vecAdd(start, tip), .5);
|
|
14496
|
-
const gapHalf = span * clamp01(options.labelGapRatio ?? DEFAULT_SEIZE_OPTIONS.labelGapRatio) / 2;
|
|
14497
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));
|
|
14498
15449
|
const before = vecAdd(labelPoint, vecScale(direction, -gapHalf));
|
|
14499
15450
|
const after = vecAdd(labelPoint, vecScale(direction, gapHalf));
|
|
14500
15451
|
firstArcPart = [toPosition(start), toPosition(before)];
|
|
14501
15452
|
secondArcPart = [toPosition(after), toPosition(tip)];
|
|
14502
15453
|
const terminal = vecSub(tip, curvePoint);
|
|
14503
15454
|
arrowDirection = vecMag(terminal) >= 1e-6 ? vecNorm(terminal) : vecNorm(vecSub(tip, start));
|
|
14504
|
-
tangentAtLabel = direction;
|
|
14505
15455
|
}
|
|
14506
15456
|
const startAngle = Math.atan2(radiusVector[1], radiusVector[0]);
|
|
14507
15457
|
const circle = [];
|
|
@@ -14509,115 +15459,367 @@ function createSeizeSymbol(coordinates, options = {}) {
|
|
|
14509
15459
|
const angle = startAngle + index / circleSegments * Math.PI * 2;
|
|
14510
15460
|
circle.push(toPosition(vecAdd(center, [radius * Math.cos(angle), radius * Math.sin(angle)])));
|
|
14511
15461
|
}
|
|
15462
|
+
const lines = [
|
|
15463
|
+
circle,
|
|
15464
|
+
firstArcPart,
|
|
15465
|
+
secondArcPart,
|
|
15466
|
+
createArrowHead$1(tip, arrowDirection, span * Math.max(0, options.arrowheadLengthRatio ?? DEFAULT_SEIZE_OPTIONS.arrowheadLengthRatio), span * Math.max(0, options.arrowheadWidthRatio ?? DEFAULT_SEIZE_OPTIONS.arrowheadWidthRatio))
|
|
15467
|
+
];
|
|
15468
|
+
return {
|
|
15469
|
+
type: "FeatureCollection",
|
|
15470
|
+
features: [{
|
|
15471
|
+
type: "Feature",
|
|
15472
|
+
properties: { part },
|
|
15473
|
+
geometry: {
|
|
15474
|
+
type: "MultiLineString",
|
|
15475
|
+
coordinates: lines
|
|
15476
|
+
}
|
|
15477
|
+
}, createLabelFeature(labelPoint, label, labelRotation, options)]
|
|
15478
|
+
};
|
|
15479
|
+
}
|
|
15480
|
+
/** Generates the Seize mission task symbol (342300). */
|
|
15481
|
+
function createSeizeSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
15482
|
+
return createSeizeLikeSymbol(coordinates, options, "S", "seize", context);
|
|
15483
|
+
}
|
|
15484
|
+
const SEIZE = defineControlMeasure({
|
|
15485
|
+
metadata: SEIZE_METADATA,
|
|
15486
|
+
generator: createSeizeSymbol,
|
|
15487
|
+
defaultOptions: DEFAULT_SEIZE_OPTIONS,
|
|
15488
|
+
rule: line27DrawRule,
|
|
15489
|
+
previewSample: {
|
|
15490
|
+
controlPoints: [
|
|
15491
|
+
[-.8, .35],
|
|
15492
|
+
[-.45, .35],
|
|
15493
|
+
[.15, .25],
|
|
15494
|
+
[.85, -.55]
|
|
15495
|
+
],
|
|
15496
|
+
options: { labelSize: 100 }
|
|
15497
|
+
}
|
|
15498
|
+
});
|
|
15499
|
+
//#endregion
|
|
15500
|
+
//#region src/generators/cm34-mission-tasks/evacuate.ts
|
|
15501
|
+
const DEFAULT_EVACUATE_OPTIONS = DEFAULT_SEIZE_OPTIONS;
|
|
15502
|
+
const EVACUATE_METADATA = {
|
|
15503
|
+
id: "evacuate",
|
|
15504
|
+
name: "Evacuate",
|
|
15505
|
+
description: "Removes personnel or materiel from a threatened area.",
|
|
15506
|
+
entity: "Mission Tasks",
|
|
15507
|
+
entityType: "Evacuation",
|
|
15508
|
+
value: "344500",
|
|
15509
|
+
minCoordinates: 4,
|
|
15510
|
+
maxCoordinates: 4,
|
|
15511
|
+
geometry: "line",
|
|
15512
|
+
geometryTypes: ["MultiLineString", "Point"],
|
|
15513
|
+
paints: {
|
|
15514
|
+
stroke: true,
|
|
15515
|
+
fill: "none",
|
|
15516
|
+
text: true
|
|
15517
|
+
},
|
|
15518
|
+
drawRule: "Line27",
|
|
15519
|
+
capturesLabelSize: true,
|
|
15520
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
15521
|
+
params: SEIZE_PARAMS
|
|
15522
|
+
};
|
|
15523
|
+
/** Generates the Evacuate mission task symbol (344500). */
|
|
15524
|
+
function createEvacuateSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
15525
|
+
return createSeizeLikeSymbol(coordinates, options, "E", "evacuate", context);
|
|
15526
|
+
}
|
|
15527
|
+
const EVACUATE = defineControlMeasure({
|
|
15528
|
+
metadata: EVACUATE_METADATA,
|
|
15529
|
+
generator: createEvacuateSymbol,
|
|
15530
|
+
defaultOptions: DEFAULT_EVACUATE_OPTIONS,
|
|
15531
|
+
rule: line27DrawRule,
|
|
15532
|
+
previewSample: {
|
|
15533
|
+
controlPoints: [
|
|
15534
|
+
[-.8, .35],
|
|
15535
|
+
[-.45, .35],
|
|
15536
|
+
[.15, .25],
|
|
15537
|
+
[.85, -.55]
|
|
15538
|
+
],
|
|
15539
|
+
options: { labelSize: 100 }
|
|
15540
|
+
}
|
|
15541
|
+
});
|
|
15542
|
+
//#endregion
|
|
15543
|
+
//#region src/generators/cm34-mission-tasks/withdraw.ts
|
|
15544
|
+
const DEFAULT_WITHDRAW_OPTIONS = DEFAULT_DELAY_OPTIONS;
|
|
15545
|
+
const WITHDRAW_METADATA = {
|
|
15546
|
+
id: "withdraw",
|
|
15547
|
+
name: "Withdraw",
|
|
15548
|
+
description: "Disengages a force from the enemy and moves it away from enemy contact.",
|
|
15549
|
+
entity: "Mission Tasks",
|
|
15550
|
+
entityType: "Withdraw",
|
|
15551
|
+
value: "342400",
|
|
15552
|
+
minCoordinates: 3,
|
|
15553
|
+
maxCoordinates: 3,
|
|
15554
|
+
geometry: "line",
|
|
15555
|
+
geometryTypes: ["MultiLineString", "Point"],
|
|
15556
|
+
paints: {
|
|
15557
|
+
stroke: true,
|
|
15558
|
+
fill: "none",
|
|
15559
|
+
text: true
|
|
15560
|
+
},
|
|
15561
|
+
drawRule: "Line24",
|
|
15562
|
+
capturesLabelSize: true,
|
|
15563
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
15564
|
+
params: DELAY_PARAMS
|
|
15565
|
+
};
|
|
15566
|
+
function createWithdrawSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
15567
|
+
return createDelayVariantSymbol(coordinates, {
|
|
15568
|
+
glyph: "W",
|
|
15569
|
+
part: "withdraw"
|
|
15570
|
+
}, options, context);
|
|
15571
|
+
}
|
|
15572
|
+
const WITHDRAW = defineControlMeasure({
|
|
15573
|
+
metadata: WITHDRAW_METADATA,
|
|
15574
|
+
generator: createWithdrawSymbol,
|
|
15575
|
+
defaultOptions: DEFAULT_WITHDRAW_OPTIONS,
|
|
15576
|
+
rule: line24DrawRule
|
|
15577
|
+
});
|
|
15578
|
+
//#endregion
|
|
15579
|
+
//#region src/generators/cm34-mission-tasks/withdraw-under-pressure.ts
|
|
15580
|
+
const DEFAULT_WITHDRAW_UNDER_PRESSURE_OPTIONS = DEFAULT_DELAY_OPTIONS;
|
|
15581
|
+
const WITHDRAW_UNDER_PRESSURE_METADATA = {
|
|
15582
|
+
id: "withdraw-under-pressure",
|
|
15583
|
+
name: "Withdraw Under Pressure",
|
|
15584
|
+
description: "Disengages a force from the enemy and moves it away while under enemy pressure.",
|
|
15585
|
+
entity: "Mission Tasks",
|
|
15586
|
+
entityType: "Withdraw Under Pressure",
|
|
15587
|
+
value: "342500",
|
|
15588
|
+
minCoordinates: 3,
|
|
15589
|
+
maxCoordinates: 3,
|
|
15590
|
+
geometry: "line",
|
|
15591
|
+
geometryTypes: ["MultiLineString", "Point"],
|
|
15592
|
+
paints: {
|
|
15593
|
+
stroke: true,
|
|
15594
|
+
fill: "none",
|
|
15595
|
+
text: true
|
|
15596
|
+
},
|
|
15597
|
+
drawRule: "Line24",
|
|
15598
|
+
capturesLabelSize: true,
|
|
15599
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
15600
|
+
params: DELAY_PARAMS
|
|
15601
|
+
};
|
|
15602
|
+
function createWithdrawUnderPressureSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
15603
|
+
return createDelayVariantSymbol(coordinates, {
|
|
15604
|
+
glyph: "WP",
|
|
15605
|
+
part: "withdraw-under-pressure"
|
|
15606
|
+
}, options, context);
|
|
15607
|
+
}
|
|
15608
|
+
const WITHDRAW_UNDER_PRESSURE = defineControlMeasure({
|
|
15609
|
+
metadata: WITHDRAW_UNDER_PRESSURE_METADATA,
|
|
15610
|
+
generator: createWithdrawUnderPressureSymbol,
|
|
15611
|
+
defaultOptions: DEFAULT_WITHDRAW_UNDER_PRESSURE_OPTIONS,
|
|
15612
|
+
rule: line24DrawRule,
|
|
15613
|
+
previewSample: { controlPoints: [
|
|
15614
|
+
[-1, -.4],
|
|
15615
|
+
[1, -.4],
|
|
15616
|
+
[0, .6]
|
|
15617
|
+
] }
|
|
15618
|
+
});
|
|
15619
|
+
//#endregion
|
|
15620
|
+
//#region src/generators/cm27-protection-areas/turn.ts
|
|
15621
|
+
const DEFAULT_TURN_OPTIONS = {
|
|
15622
|
+
arrowheadLengthRatio: .16,
|
|
15623
|
+
arrowheadWidthRatio: .16,
|
|
15624
|
+
arcSegments: 64
|
|
15625
|
+
};
|
|
15626
|
+
const TURN_PARAMS = [
|
|
15627
|
+
{
|
|
15628
|
+
key: "arrowheadLengthRatio",
|
|
15629
|
+
presentationTier: "advanced",
|
|
15630
|
+
label: "Arrowhead length",
|
|
15631
|
+
description: "Arrowhead length as a ratio of the rear-to-tip distance",
|
|
15632
|
+
type: "number",
|
|
15633
|
+
min: .01,
|
|
15634
|
+
max: .5,
|
|
15635
|
+
step: .01
|
|
15636
|
+
},
|
|
15637
|
+
{
|
|
15638
|
+
key: "arrowheadWidthRatio",
|
|
15639
|
+
presentationTier: "advanced",
|
|
15640
|
+
label: "Arrowhead width",
|
|
15641
|
+
description: "Arrowhead base width as a ratio of the rear-to-tip distance",
|
|
15642
|
+
type: "number",
|
|
15643
|
+
min: .01,
|
|
15644
|
+
max: .5,
|
|
15645
|
+
step: .01
|
|
15646
|
+
},
|
|
15647
|
+
{
|
|
15648
|
+
key: "arcSegments",
|
|
15649
|
+
presentationTier: "advanced",
|
|
15650
|
+
label: "Arc segments",
|
|
15651
|
+
description: "Number of line segments approximating the quarter-circle shaft.",
|
|
15652
|
+
type: "number",
|
|
15653
|
+
min: 4,
|
|
15654
|
+
max: 128,
|
|
15655
|
+
step: 1
|
|
15656
|
+
}
|
|
15657
|
+
];
|
|
15658
|
+
const TURN_METADATA = {
|
|
15659
|
+
id: "turn",
|
|
15660
|
+
name: "Turn",
|
|
15661
|
+
description: "Diverts an enemy formation from one avenue of approach to another or into an engagement area. The arrow indicates the desired direction of turn.",
|
|
15662
|
+
entity: "Protection Areas",
|
|
15663
|
+
entityType: "Obstacle Effects",
|
|
15664
|
+
entitySubtype: "Turn",
|
|
15665
|
+
value: "270504",
|
|
15666
|
+
minCoordinates: 2,
|
|
15667
|
+
geometry: "line",
|
|
15668
|
+
geometryTypes: ["MultiLineString", "MultiPolygon"],
|
|
15669
|
+
paints: {
|
|
15670
|
+
stroke: true,
|
|
15671
|
+
fill: "fixed",
|
|
15672
|
+
text: false
|
|
15673
|
+
},
|
|
15674
|
+
drawRule: "Line10",
|
|
15675
|
+
params: TURN_PARAMS
|
|
15676
|
+
};
|
|
15677
|
+
/**
|
|
15678
|
+
* Shared geometry for the Turn graphic: a quarter-circle shaft from PT2
|
|
15679
|
+
* (rear) to the arrowhead base, with the arrowhead continuing tangent to the
|
|
15680
|
+
* arc and placing its tip at PT1. PT3 selects which side of the PT2 -> PT1
|
|
15681
|
+
* chord contains the arc.
|
|
15682
|
+
*
|
|
15683
|
+
* `labelGapHalfLength` optionally resolves a meter-denominated half-gap from the arc midpoint
|
|
15684
|
+
* anchor for measures that carry a doctrinal glyph there.
|
|
15685
|
+
*/
|
|
15686
|
+
function createTurnLikeSymbol(coordinates, options = {}, labelGapHalfLength) {
|
|
15687
|
+
const tip = project(coordinates[0][0], coordinates[0][1]);
|
|
15688
|
+
const rear = project(coordinates[1][0], coordinates[1][1]);
|
|
15689
|
+
const side = quarterArcSide(rear, tip, coordinates[2] ? project(coordinates[2][0], coordinates[2][1]) : void 0);
|
|
15690
|
+
const rearToTip = vecSub(tip, rear);
|
|
15691
|
+
const totalLength = vecMag(rearToTip);
|
|
15692
|
+
if (totalLength < 1e-6) return { features: [] };
|
|
15693
|
+
const requestedHeadLength = totalLength * (options.arrowheadLengthRatio ?? DEFAULT_TURN_OPTIONS.arrowheadLengthRatio);
|
|
15694
|
+
const tangentComponent = Math.min(Math.max(requestedHeadLength, EPSILON), totalLength * .95) / Math.SQRT2;
|
|
15695
|
+
const arcChordLength = Math.sqrt(totalLength * totalLength - tangentComponent * tangentComponent) - tangentComponent;
|
|
15696
|
+
const chordOffset = Math.atan2(tangentComponent, arcChordLength + tangentComponent);
|
|
15697
|
+
const headBase = vecAdd(rear, vecScale(rotate(vecNorm(rearToTip), side * chordOffset), arcChordLength));
|
|
15698
|
+
const arc = createQuarterArc(rear, headBase, side);
|
|
15699
|
+
if (!arc) return { features: [] };
|
|
15700
|
+
const segmentCount = Math.round(Math.min(128, Math.max(4, options.arcSegments ?? DEFAULT_TURN_OPTIONS.arcSegments)));
|
|
15701
|
+
const sampleArc = (from, to) => {
|
|
15702
|
+
const count = Math.max(1, Math.ceil(segmentCount * (to - from)));
|
|
15703
|
+
const points = [];
|
|
15704
|
+
for (let i = 0; i <= count; i++) {
|
|
15705
|
+
const progress = from + (to - from) * i / count;
|
|
15706
|
+
const angle = arc.startAngle + arc.sweepAngle * progress;
|
|
15707
|
+
points.push(unproject(arc.center[0] + arc.radius * Math.cos(angle), arc.center[1] + arc.radius * Math.sin(angle)));
|
|
15708
|
+
}
|
|
15709
|
+
return points;
|
|
15710
|
+
};
|
|
15711
|
+
const sweepSign = Math.sign(arc.sweepAngle);
|
|
15712
|
+
const midAngle = arc.startAngle + arc.sweepAngle / 2;
|
|
15713
|
+
const labelAnchor = {
|
|
15714
|
+
point: arc.midpoint,
|
|
15715
|
+
tangent: [-Math.sin(midAngle) * sweepSign, Math.cos(midAngle) * sweepSign]
|
|
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)];
|
|
15719
|
+
const headWidth = totalLength * (options.arrowheadWidthRatio ?? DEFAULT_TURN_OPTIONS.arrowheadWidthRatio);
|
|
15720
|
+
const arrowHead = createArrowHeadRing(tip, vecNorm(vecSub(tip, headBase)), vecMag(vecSub(tip, headBase)), headWidth);
|
|
14512
15721
|
return {
|
|
14513
|
-
type: "FeatureCollection",
|
|
14514
15722
|
features: [{
|
|
14515
15723
|
type: "Feature",
|
|
14516
|
-
properties: { part: "
|
|
15724
|
+
properties: { part: "arc" },
|
|
14517
15725
|
geometry: {
|
|
14518
15726
|
type: "MultiLineString",
|
|
14519
|
-
coordinates:
|
|
14520
|
-
|
|
14521
|
-
|
|
14522
|
-
|
|
14523
|
-
|
|
14524
|
-
|
|
15727
|
+
coordinates: arcParts
|
|
15728
|
+
}
|
|
15729
|
+
}, {
|
|
15730
|
+
type: "Feature",
|
|
15731
|
+
properties: {
|
|
15732
|
+
part: "arrowhead",
|
|
15733
|
+
fill: true,
|
|
15734
|
+
style: SOLID_ACCENT_FILL
|
|
15735
|
+
},
|
|
15736
|
+
geometry: {
|
|
15737
|
+
type: "MultiPolygon",
|
|
15738
|
+
coordinates: [[[...arrowHead]]]
|
|
14525
15739
|
}
|
|
14526
|
-
},
|
|
15740
|
+
}],
|
|
15741
|
+
labelAnchor
|
|
14527
15742
|
};
|
|
14528
15743
|
}
|
|
14529
|
-
|
|
14530
|
-
|
|
14531
|
-
|
|
14532
|
-
|
|
14533
|
-
|
|
14534
|
-
|
|
14535
|
-
|
|
14536
|
-
|
|
14537
|
-
|
|
14538
|
-
|
|
14539
|
-
[.85, -.55]
|
|
14540
|
-
],
|
|
14541
|
-
options: { labelSize: 100 }
|
|
14542
|
-
}
|
|
14543
|
-
});
|
|
14544
|
-
//#endregion
|
|
14545
|
-
//#region src/generators/cm34-mission-tasks/withdraw.ts
|
|
14546
|
-
const DEFAULT_WITHDRAW_OPTIONS = DEFAULT_DELAY_OPTIONS;
|
|
14547
|
-
const WITHDRAW_METADATA = {
|
|
14548
|
-
id: "withdraw",
|
|
14549
|
-
name: "Withdraw",
|
|
14550
|
-
description: "Disengages a force from the enemy and moves it away from enemy contact.",
|
|
14551
|
-
entity: "Mission Tasks",
|
|
14552
|
-
entityType: "Withdraw",
|
|
14553
|
-
value: "342400",
|
|
14554
|
-
minCoordinates: 3,
|
|
14555
|
-
maxCoordinates: 3,
|
|
14556
|
-
geometry: "line",
|
|
14557
|
-
geometryTypes: ["MultiLineString", "Point"],
|
|
14558
|
-
paints: {
|
|
14559
|
-
stroke: true,
|
|
14560
|
-
fill: "none",
|
|
14561
|
-
text: true
|
|
14562
|
-
},
|
|
14563
|
-
drawRule: "Line24",
|
|
14564
|
-
capturesLabelSize: true,
|
|
14565
|
-
params: DELAY_PARAMS
|
|
14566
|
-
};
|
|
14567
|
-
function createWithdrawSymbol(coordinates, options = {}) {
|
|
14568
|
-
return createDelayVariantSymbol(coordinates, {
|
|
14569
|
-
glyph: "W",
|
|
14570
|
-
part: "withdraw"
|
|
14571
|
-
}, options);
|
|
15744
|
+
/**
|
|
15745
|
+
* Generates a quarter-circle shaft from PT2 (rear) to the arrowhead base.
|
|
15746
|
+
* The arrowhead continues tangent to the arc and places its tip at PT1.
|
|
15747
|
+
* PT3 selects which side of the PT2 -> PT1 chord contains the arc.
|
|
15748
|
+
*/
|
|
15749
|
+
function createTurn(coordinates, options = {}) {
|
|
15750
|
+
return {
|
|
15751
|
+
type: "FeatureCollection",
|
|
15752
|
+
features: createTurnLikeSymbol(coordinates, options).features
|
|
15753
|
+
};
|
|
14572
15754
|
}
|
|
14573
|
-
|
|
14574
|
-
|
|
14575
|
-
|
|
14576
|
-
|
|
14577
|
-
|
|
15755
|
+
function rotate(vector, angle) {
|
|
15756
|
+
const cos = Math.cos(angle);
|
|
15757
|
+
const sin = Math.sin(angle);
|
|
15758
|
+
return [vector[0] * cos - vector[1] * sin, vector[0] * sin + vector[1] * cos];
|
|
15759
|
+
}
|
|
15760
|
+
const TURN = defineControlMeasure({
|
|
15761
|
+
metadata: TURN_METADATA,
|
|
15762
|
+
generator: createTurn,
|
|
15763
|
+
defaultOptions: DEFAULT_TURN_OPTIONS,
|
|
15764
|
+
rule: turnDrawRule
|
|
14578
15765
|
});
|
|
14579
15766
|
//#endregion
|
|
14580
|
-
//#region src/generators/cm34-mission-tasks/
|
|
14581
|
-
const
|
|
14582
|
-
|
|
14583
|
-
|
|
14584
|
-
|
|
14585
|
-
|
|
15767
|
+
//#region src/generators/cm34-mission-tasks/turn.ts
|
|
15768
|
+
const DEFAULT_TURN_MISSION_TASK_OPTIONS = {
|
|
15769
|
+
...DEFAULT_TURN_OPTIONS,
|
|
15770
|
+
arrowheadLengthRatio: .14,
|
|
15771
|
+
arrowheadWidthRatio: .14,
|
|
15772
|
+
labelPadding: DEFAULT_LABEL_KNOCKOUT_PADDING
|
|
15773
|
+
};
|
|
15774
|
+
const TURN_MISSION_TASK_METADATA = {
|
|
15775
|
+
id: "turn-mission-task",
|
|
15776
|
+
name: "Turn",
|
|
15777
|
+
description: "Causes an enemy force to move from one avenue of approach to another or into an engagement area.",
|
|
14586
15778
|
entity: "Mission Tasks",
|
|
14587
|
-
entityType: "
|
|
14588
|
-
value: "
|
|
14589
|
-
minCoordinates:
|
|
14590
|
-
maxCoordinates: 3,
|
|
15779
|
+
entityType: "Turn",
|
|
15780
|
+
value: "344700",
|
|
15781
|
+
minCoordinates: 2,
|
|
14591
15782
|
geometry: "line",
|
|
14592
|
-
geometryTypes: [
|
|
15783
|
+
geometryTypes: [
|
|
15784
|
+
"MultiLineString",
|
|
15785
|
+
"MultiPolygon",
|
|
15786
|
+
"Point"
|
|
15787
|
+
],
|
|
14593
15788
|
paints: {
|
|
14594
15789
|
stroke: true,
|
|
14595
|
-
fill: "
|
|
15790
|
+
fill: "fixed",
|
|
14596
15791
|
text: true
|
|
14597
15792
|
},
|
|
14598
|
-
drawRule: "
|
|
15793
|
+
drawRule: "Line10",
|
|
14599
15794
|
capturesLabelSize: true,
|
|
14600
|
-
|
|
14601
|
-
|
|
14602
|
-
|
|
14603
|
-
|
|
14604
|
-
|
|
14605
|
-
|
|
14606
|
-
|
|
15795
|
+
labelGeometryUsesResolvedMetrics: true,
|
|
15796
|
+
params: [...TURN_PARAMS, LABEL_PADDING_PARAM]
|
|
15797
|
+
};
|
|
15798
|
+
/** Generates the TURN mission task symbol (344700). */
|
|
15799
|
+
function createTurnMissionTaskSymbol(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
15800
|
+
const { features, labelAnchor } = createTurnLikeSymbol(coordinates, {
|
|
15801
|
+
arrowheadLengthRatio: options.arrowheadLengthRatio ?? DEFAULT_TURN_MISSION_TASK_OPTIONS.arrowheadLengthRatio,
|
|
15802
|
+
arrowheadWidthRatio: options.arrowheadWidthRatio ?? DEFAULT_TURN_MISSION_TASK_OPTIONS.arrowheadWidthRatio,
|
|
15803
|
+
arcSegments: options.arcSegments ?? DEFAULT_TURN_MISSION_TASK_OPTIONS.arcSegments
|
|
15804
|
+
}, (anchor) => {
|
|
15805
|
+
const rotation = labelRotationAlong(anchor.tangent);
|
|
15806
|
+
return labelKnockoutHalfExtent("T", anchor.tangent, rotation, options, context, DEFAULT_TURN_MISSION_TASK_OPTIONS.labelPadding);
|
|
15807
|
+
});
|
|
15808
|
+
if (!labelAnchor) return {
|
|
15809
|
+
type: "FeatureCollection",
|
|
15810
|
+
features: []
|
|
15811
|
+
};
|
|
15812
|
+
const label = createLabelFeature(labelAnchor.point, "T", labelRotationAlong(labelAnchor.tangent), options);
|
|
15813
|
+
return {
|
|
15814
|
+
type: "FeatureCollection",
|
|
15815
|
+
features: [...features, label]
|
|
15816
|
+
};
|
|
14607
15817
|
}
|
|
14608
|
-
const
|
|
14609
|
-
metadata:
|
|
14610
|
-
generator:
|
|
14611
|
-
defaultOptions:
|
|
14612
|
-
rule:
|
|
14613
|
-
previewSample: {
|
|
14614
|
-
controlPoints: [
|
|
14615
|
-
[-1, -.4],
|
|
14616
|
-
[1, -.4],
|
|
14617
|
-
[0, .6]
|
|
14618
|
-
],
|
|
14619
|
-
options: { labelGapRatio: .3 }
|
|
14620
|
-
}
|
|
15818
|
+
const TURN_MISSION_TASK = defineControlMeasure({
|
|
15819
|
+
metadata: TURN_MISSION_TASK_METADATA,
|
|
15820
|
+
generator: createTurnMissionTaskSymbol,
|
|
15821
|
+
defaultOptions: DEFAULT_TURN_MISSION_TASK_OPTIONS,
|
|
15822
|
+
rule: turnDrawRule
|
|
14621
15823
|
});
|
|
14622
15824
|
//#endregion
|
|
14623
15825
|
//#region src/generators/cm15-maneuver-areas/jointTacticalActionArea.ts
|
|
@@ -14648,6 +15850,7 @@ const JOINT_TACTICAL_ACTION_AREA_METADATA = {
|
|
|
14648
15850
|
},
|
|
14649
15851
|
drawRule: "Area1",
|
|
14650
15852
|
capturesLabelSize: true,
|
|
15853
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
14651
15854
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
14652
15855
|
textAmplifiers: AREA_TEXT_AMPLIFIERS
|
|
14653
15856
|
};
|
|
@@ -14708,6 +15911,7 @@ const LANDING_ZONE_METADATA = {
|
|
|
14708
15911
|
},
|
|
14709
15912
|
drawRule: "Area1",
|
|
14710
15913
|
capturesLabelSize: true,
|
|
15914
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
14711
15915
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
14712
15916
|
textAmplifiers: AREA_TEXT_AMPLIFIERS
|
|
14713
15917
|
};
|
|
@@ -14763,6 +15967,7 @@ const PENETRATION_BOX_METADATA = {
|
|
|
14763
15967
|
},
|
|
14764
15968
|
drawRule: "Area1",
|
|
14765
15969
|
capturesLabelSize: true,
|
|
15970
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
14766
15971
|
params: LABELED_AREA_PARAMS,
|
|
14767
15972
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
14768
15973
|
};
|
|
@@ -14814,6 +16019,7 @@ const LIMITED_ACCESS_AREA_METADATA = {
|
|
|
14814
16019
|
},
|
|
14815
16020
|
drawRule: "Area1",
|
|
14816
16021
|
capturesLabelSize: true,
|
|
16022
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
14817
16023
|
params: [...AREA_SMOOTH_PARAMS, AREA_LABEL_PADDING_PARAM],
|
|
14818
16024
|
textAmplifiers: [AREA_HOSTILE_AMPLIFIER]
|
|
14819
16025
|
};
|
|
@@ -14909,6 +16115,7 @@ const NO_FIRE_AREA_IRREGULAR_METADATA = {
|
|
|
14909
16115
|
},
|
|
14910
16116
|
drawRule: "Area1",
|
|
14911
16117
|
capturesLabelSize: true,
|
|
16118
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
14912
16119
|
params: [...AREA_SMOOTH_PARAMS, AREA_LABEL_PADDING_PARAM],
|
|
14913
16120
|
textAmplifiers: [...NO_FIRE_AREA_TEXT_AMPLIFIERS, AREA_HOSTILE_AMPLIFIER]
|
|
14914
16121
|
};
|
|
@@ -15154,6 +16361,7 @@ const MINEFIELD_METADATA = {
|
|
|
15154
16361
|
text: true
|
|
15155
16362
|
},
|
|
15156
16363
|
drawRule: "Point2",
|
|
16364
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
15157
16365
|
params: [
|
|
15158
16366
|
MINE_TYPE_PARAM,
|
|
15159
16367
|
{
|
|
@@ -15456,6 +16664,7 @@ const COMMON_METADATA = {
|
|
|
15456
16664
|
},
|
|
15457
16665
|
drawRule: "Area1",
|
|
15458
16666
|
capturesLabelSize: true,
|
|
16667
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
15459
16668
|
params: [
|
|
15460
16669
|
MINE_TYPE_PARAM,
|
|
15461
16670
|
...AREA_SMOOTH_PARAMS,
|
|
@@ -15478,7 +16687,8 @@ const MINED_AREA_METADATA = {
|
|
|
15478
16687
|
name: "Mined Area",
|
|
15479
16688
|
description: "An area dangerous because of the presence or suspected presence of mines.",
|
|
15480
16689
|
entityType: "Mined Area",
|
|
15481
|
-
value: "270800"
|
|
16690
|
+
value: "270800",
|
|
16691
|
+
labelGeometryUsesResolvedMetrics: true
|
|
15482
16692
|
};
|
|
15483
16693
|
function axisIntersections(ring, axis, target) {
|
|
15484
16694
|
const other = axis === 0 ? 1 : 0;
|
|
@@ -16025,6 +17235,7 @@ const PICKUP_ZONE_METADATA = {
|
|
|
16025
17235
|
},
|
|
16026
17236
|
drawRule: "Area1",
|
|
16027
17237
|
capturesLabelSize: true,
|
|
17238
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
16028
17239
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
16029
17240
|
textAmplifiers: AREA_TEXT_AMPLIFIERS
|
|
16030
17241
|
};
|
|
@@ -16080,6 +17291,7 @@ const ASSAULT_POSITION_METADATA = {
|
|
|
16080
17291
|
},
|
|
16081
17292
|
drawRule: "Area1",
|
|
16082
17293
|
capturesLabelSize: true,
|
|
17294
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
16083
17295
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
16084
17296
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
16085
17297
|
};
|
|
@@ -16135,6 +17347,7 @@ const ATTACK_POSITION_METADATA = {
|
|
|
16135
17347
|
},
|
|
16136
17348
|
drawRule: "Area1",
|
|
16137
17349
|
capturesLabelSize: true,
|
|
17350
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
16138
17351
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
16139
17352
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
16140
17353
|
};
|
|
@@ -16190,6 +17403,7 @@ const OBJECTIVE_AREA_METADATA = {
|
|
|
16190
17403
|
},
|
|
16191
17404
|
drawRule: "Area1",
|
|
16192
17405
|
capturesLabelSize: true,
|
|
17406
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
16193
17407
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
16194
17408
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
16195
17409
|
};
|
|
@@ -16539,6 +17753,7 @@ const STRONG_POINT_METADATA = {
|
|
|
16539
17753
|
},
|
|
16540
17754
|
drawRule: "Area1",
|
|
16541
17755
|
capturesLabelSize: true,
|
|
17756
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
16542
17757
|
params: ECHELON_AREA_PARAMS,
|
|
16543
17758
|
textAmplifiers: AREA_UNIT_DESIGNATION_HOSTILE_AMPLIFIERS
|
|
16544
17759
|
};
|
|
@@ -16860,6 +18075,7 @@ const SUBMARINE_ACTION_AREA_METADATA = {
|
|
|
16860
18075
|
},
|
|
16861
18076
|
drawRule: "Area1",
|
|
16862
18077
|
capturesLabelSize: true,
|
|
18078
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
16863
18079
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
16864
18080
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_NO_ADDITIONAL_INFO
|
|
16865
18081
|
};
|
|
@@ -16920,6 +18136,7 @@ const SUBMARINE_GENERATED_ACTION_AREA_METADATA = {
|
|
|
16920
18136
|
},
|
|
16921
18137
|
drawRule: "Area1",
|
|
16922
18138
|
capturesLabelSize: true,
|
|
18139
|
+
labelGeometryUsesResolvedMetrics: ["N"],
|
|
16923
18140
|
params: PREFIXED_LABELED_AREA_PARAMS,
|
|
16924
18141
|
textAmplifiers: AREA_TEXT_AMPLIFIERS_NO_ADDITIONAL_INFO
|
|
16925
18142
|
};
|
|
@@ -17103,130 +18320,6 @@ const SUPPORT_BY_FIRE = defineControlMeasure({
|
|
|
17103
18320
|
rule: supportByFireDrawRule
|
|
17104
18321
|
});
|
|
17105
18322
|
//#endregion
|
|
17106
|
-
//#region src/generators/cm27-protection-areas/turn.ts
|
|
17107
|
-
const DEFAULT_TURN_OPTIONS = {
|
|
17108
|
-
arrowheadLengthRatio: .16,
|
|
17109
|
-
arrowheadWidthRatio: .16,
|
|
17110
|
-
arcSegments: 64
|
|
17111
|
-
};
|
|
17112
|
-
const TURN_METADATA = {
|
|
17113
|
-
id: "turn",
|
|
17114
|
-
name: "Turn",
|
|
17115
|
-
description: "Diverts an enemy formation from one avenue of approach to another or into an engagement area. The arrow indicates the desired direction of turn.",
|
|
17116
|
-
entity: "Protection Areas",
|
|
17117
|
-
entityType: "Obstacle Effects",
|
|
17118
|
-
entitySubtype: "Turn",
|
|
17119
|
-
value: "270504",
|
|
17120
|
-
minCoordinates: 2,
|
|
17121
|
-
geometry: "line",
|
|
17122
|
-
geometryTypes: ["MultiLineString", "MultiPolygon"],
|
|
17123
|
-
paints: {
|
|
17124
|
-
stroke: true,
|
|
17125
|
-
fill: "fixed",
|
|
17126
|
-
text: false
|
|
17127
|
-
},
|
|
17128
|
-
drawRule: "Line10",
|
|
17129
|
-
params: [
|
|
17130
|
-
{
|
|
17131
|
-
key: "arrowheadLengthRatio",
|
|
17132
|
-
presentationTier: "advanced",
|
|
17133
|
-
label: "Arrowhead length",
|
|
17134
|
-
description: "Arrowhead length as a ratio of the rear-to-tip distance",
|
|
17135
|
-
type: "number",
|
|
17136
|
-
min: .01,
|
|
17137
|
-
max: .5,
|
|
17138
|
-
step: .01
|
|
17139
|
-
},
|
|
17140
|
-
{
|
|
17141
|
-
key: "arrowheadWidthRatio",
|
|
17142
|
-
presentationTier: "advanced",
|
|
17143
|
-
label: "Arrowhead width",
|
|
17144
|
-
description: "Arrowhead base width as a ratio of the rear-to-tip distance",
|
|
17145
|
-
type: "number",
|
|
17146
|
-
min: .01,
|
|
17147
|
-
max: .5,
|
|
17148
|
-
step: .01
|
|
17149
|
-
},
|
|
17150
|
-
{
|
|
17151
|
-
key: "arcSegments",
|
|
17152
|
-
presentationTier: "advanced",
|
|
17153
|
-
label: "Arc segments",
|
|
17154
|
-
description: "Number of line segments approximating the quarter-circle shaft.",
|
|
17155
|
-
type: "number",
|
|
17156
|
-
min: 4,
|
|
17157
|
-
max: 128,
|
|
17158
|
-
step: 1
|
|
17159
|
-
}
|
|
17160
|
-
]
|
|
17161
|
-
};
|
|
17162
|
-
/**
|
|
17163
|
-
* Generates a quarter-circle shaft from PT2 (rear) to the arrowhead base.
|
|
17164
|
-
* The arrowhead continues tangent to the arc and places its tip at PT1.
|
|
17165
|
-
* PT3 selects which side of the PT2 -> PT1 chord contains the arc.
|
|
17166
|
-
*/
|
|
17167
|
-
function createTurn(coordinates, options = {}) {
|
|
17168
|
-
const tip = project(coordinates[0][0], coordinates[0][1]);
|
|
17169
|
-
const rear = project(coordinates[1][0], coordinates[1][1]);
|
|
17170
|
-
const side = quarterArcSide(rear, tip, coordinates[2] ? project(coordinates[2][0], coordinates[2][1]) : void 0);
|
|
17171
|
-
const rearToTip = vecSub(tip, rear);
|
|
17172
|
-
const totalLength = vecMag(rearToTip);
|
|
17173
|
-
if (totalLength < 1e-6) return {
|
|
17174
|
-
type: "FeatureCollection",
|
|
17175
|
-
features: []
|
|
17176
|
-
};
|
|
17177
|
-
const requestedHeadLength = totalLength * (options.arrowheadLengthRatio ?? DEFAULT_TURN_OPTIONS.arrowheadLengthRatio);
|
|
17178
|
-
const tangentComponent = Math.min(Math.max(requestedHeadLength, EPSILON), totalLength * .95) / Math.SQRT2;
|
|
17179
|
-
const arcChordLength = Math.sqrt(totalLength * totalLength - tangentComponent * tangentComponent) - tangentComponent;
|
|
17180
|
-
const chordOffset = Math.atan2(tangentComponent, arcChordLength + tangentComponent);
|
|
17181
|
-
const headBase = vecAdd(rear, vecScale(rotate(vecNorm(rearToTip), side * chordOffset), arcChordLength));
|
|
17182
|
-
const arc = createQuarterArc(rear, headBase, side);
|
|
17183
|
-
if (!arc) return {
|
|
17184
|
-
type: "FeatureCollection",
|
|
17185
|
-
features: []
|
|
17186
|
-
};
|
|
17187
|
-
const segmentCount = Math.round(Math.min(128, Math.max(4, options.arcSegments ?? DEFAULT_TURN_OPTIONS.arcSegments)));
|
|
17188
|
-
const arcCoordinates = [];
|
|
17189
|
-
for (let i = 0; i <= segmentCount; i++) {
|
|
17190
|
-
const angle = arc.startAngle + arc.sweepAngle * i / segmentCount;
|
|
17191
|
-
arcCoordinates.push(unproject(arc.center[0] + arc.radius * Math.cos(angle), arc.center[1] + arc.radius * Math.sin(angle)));
|
|
17192
|
-
}
|
|
17193
|
-
const headWidth = totalLength * (options.arrowheadWidthRatio ?? DEFAULT_TURN_OPTIONS.arrowheadWidthRatio);
|
|
17194
|
-
const arrowHead = createArrowHeadRing(tip, vecNorm(vecSub(tip, headBase)), vecMag(vecSub(tip, headBase)), headWidth);
|
|
17195
|
-
return {
|
|
17196
|
-
type: "FeatureCollection",
|
|
17197
|
-
features: [{
|
|
17198
|
-
type: "Feature",
|
|
17199
|
-
properties: { part: "arc" },
|
|
17200
|
-
geometry: {
|
|
17201
|
-
type: "MultiLineString",
|
|
17202
|
-
coordinates: [arcCoordinates]
|
|
17203
|
-
}
|
|
17204
|
-
}, {
|
|
17205
|
-
type: "Feature",
|
|
17206
|
-
properties: {
|
|
17207
|
-
part: "arrowhead",
|
|
17208
|
-
fill: true,
|
|
17209
|
-
style: SOLID_ACCENT_FILL
|
|
17210
|
-
},
|
|
17211
|
-
geometry: {
|
|
17212
|
-
type: "MultiPolygon",
|
|
17213
|
-
coordinates: [[[...arrowHead]]]
|
|
17214
|
-
}
|
|
17215
|
-
}]
|
|
17216
|
-
};
|
|
17217
|
-
}
|
|
17218
|
-
function rotate(vector, angle) {
|
|
17219
|
-
const cos = Math.cos(angle);
|
|
17220
|
-
const sin = Math.sin(angle);
|
|
17221
|
-
return [vector[0] * cos - vector[1] * sin, vector[0] * sin + vector[1] * cos];
|
|
17222
|
-
}
|
|
17223
|
-
const TURN = defineControlMeasure({
|
|
17224
|
-
metadata: TURN_METADATA,
|
|
17225
|
-
generator: createTurn,
|
|
17226
|
-
defaultOptions: DEFAULT_TURN_OPTIONS,
|
|
17227
|
-
rule: turnDrawRule
|
|
17228
|
-
});
|
|
17229
|
-
//#endregion
|
|
17230
18323
|
//#region src/generators/cm15-maneuver-areas/turning-movement.ts
|
|
17231
18324
|
const DEFAULT_CROSSBAR_POSITION = 0;
|
|
17232
18325
|
const DEFAULT_TURNING_MOVEMENT_OPTIONS = {
|
|
@@ -17366,24 +18459,35 @@ const DEFINITIONS = {
|
|
|
17366
18459
|
bypass: BYPASS,
|
|
17367
18460
|
canalize: CANALIZE,
|
|
17368
18461
|
clear: CLEAR,
|
|
18462
|
+
"cordon-and-knock": CORDON_AND_KNOCK,
|
|
18463
|
+
"cordon-and-search": CORDON_AND_SEARCH,
|
|
17369
18464
|
counterattack: COUNTERATTACK,
|
|
17370
18465
|
"counterattack-by-fire": COUNTERATTACK_BY_FIRE,
|
|
17371
18466
|
cover: COVER,
|
|
17372
18467
|
delay: DELAY,
|
|
18468
|
+
demonstrate: DEMONSTRATE,
|
|
17373
18469
|
disengage: DISENGAGE,
|
|
17374
18470
|
"disrupt-mission-task": DISRUPT_MISSION_TASK,
|
|
17375
18471
|
envelopment: ENVELOPMENT,
|
|
18472
|
+
exploitation: EXPLOITATION,
|
|
18473
|
+
"forward-passage-of-lines": FORWARD_PASSAGE_OF_LINES,
|
|
17376
18474
|
infiltration: INFILTRATION,
|
|
17377
18475
|
guard: GUARD,
|
|
17378
18476
|
isolate: ISOLATE,
|
|
17379
18477
|
"movement-to-contact": MOVEMENT_TO_CONTACT,
|
|
18478
|
+
occupy: OCCUPY,
|
|
17380
18479
|
penetrate: PENETRATE,
|
|
18480
|
+
pursuit: PURSUIT,
|
|
17381
18481
|
"rearward-passage-of-lines": REARWARD_PASSAGE_OF_LINES,
|
|
18482
|
+
"relief-in-place": RELIEF_IN_PLACE,
|
|
17382
18483
|
retire: RETIRE,
|
|
17383
18484
|
screen: SCREEN,
|
|
18485
|
+
secure: SECURE,
|
|
17384
18486
|
seize: SEIZE,
|
|
18487
|
+
evacuate: EVACUATE,
|
|
17385
18488
|
withdraw: WITHDRAW,
|
|
17386
18489
|
"withdraw-under-pressure": WITHDRAW_UNDER_PRESSURE,
|
|
18490
|
+
"turn-mission-task": TURN_MISSION_TASK,
|
|
17387
18491
|
"antitank-ditch-under-construction": ANTITANK_DITCH_UNDER_CONSTRUCTION,
|
|
17388
18492
|
"antitank-ditch-completed": ANTITANK_DITCH_COMPLETED,
|
|
17389
18493
|
"antitank-wall": ANTITANK_WALL,
|
|
@@ -17808,4 +18912,4 @@ function assertNever(value) {
|
|
|
17808
18912
|
throw new Error(`Unhandled control measure kind: ${String(value)}`);
|
|
17809
18913
|
}
|
|
17810
18914
|
//#endregion
|
|
17811
|
-
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 };
|