@orbat-mapper/control-measures 0.15.1 → 0.17.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.
@@ -237,6 +237,11 @@ function vecNorm(v) {
237
237
  const m = vecMag(v);
238
238
  return m === 0 ? [0, 0] : [v[0] / m, v[1] / m];
239
239
  }
240
+ function vecRotate(v, radians) {
241
+ const cosine = Math.cos(radians);
242
+ const sine = Math.sin(radians);
243
+ return [v[0] * cosine - v[1] * sine, v[0] * sine + v[1] * cosine];
244
+ }
240
245
  function midpoint(a, b) {
241
246
  return vecScale(vecAdd(a, b), .5);
242
247
  }
@@ -300,10 +305,10 @@ function createWidthOptionHandles(config) {
300
305
  }];
301
306
  },
302
307
  drag({ controlPoints, options, handleId, position }) {
303
- const geometry = config.geometry(controlPoints, options);
304
- if (!geometry || geometry.referenceHalfWidth < 1e-6) return void 0;
305
308
  const rear = handleId === REAR_WIDTH_OPTION_HANDLE_ID;
306
309
  if (!rear && !(handleId === "shaft-width")) return void 0;
310
+ const geometry = config.geometry(controlPoints, options);
311
+ if (!geometry || geometry.referenceHalfWidth < 1e-6) return void 0;
307
312
  const from = rear ? geometry.spine[0] : geometry.spine.at(-2);
308
313
  const to = rear ? geometry.spine[1] : geometry.spine.at(-1);
309
314
  if (!from || !to) return void 0;
@@ -780,6 +785,18 @@ const disruptDrawRule = createMidpointPerpendicularDrawRule({
780
785
  normal: "left"
781
786
  });
782
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
783
800
  //#region src/draw-rules/area17.ts
784
801
  /**
785
802
  * Area17 anchor draw rule for Penetrate.
@@ -1357,6 +1374,23 @@ const line32DrawRule = {
1357
1374
  }
1358
1375
  };
1359
1376
  //#endregion
1377
+ //#region src/draw-rules/line33.ts
1378
+ /**
1379
+ * Line33 anchor draw rule for the Pursuit mission task.
1380
+ *
1381
+ * PT. 1 and PT. 2 define the straight portion. PT. 3 is constrained to the
1382
+ * perpendicular axis through PT. 2, where its signed distance determines the
1383
+ * semicircular arc's diameter and which side of the straight line it occupies.
1384
+ */
1385
+ const line33DrawRule = createMidpointPerpendicularDrawRule({
1386
+ id: "line33:pursuit",
1387
+ origin: "p2",
1388
+ defaultDistanceRatio: .6,
1389
+ minimumUserPoints: 3,
1390
+ minimumPreviewPoints: 2,
1391
+ finishOnLastPointRepeat: true
1392
+ });
1393
+ //#endregion
1360
1394
  //#region src/draw-rules/polyline1.ts
1361
1395
  /**
1362
1396
  * Polyline1 anchors for the Infiltration Lane.
@@ -4564,7 +4598,7 @@ const AREA = defineControlMeasure({
4564
4598
  //#endregion
4565
4599
  //#region src/generators/cm15-maneuver-areas/area-defense.ts
4566
4600
  const DEFAULT_AREA_DEFENSE_OPTIONS = {
4567
- arrowOpeningAngle: 30,
4601
+ openingAngle: 30,
4568
4602
  labelOpeningAngle: 20,
4569
4603
  barbCount: 6,
4570
4604
  barbLengthRatio: .16,
@@ -4594,7 +4628,7 @@ const AREA_DEFENSE_METADATA = {
4594
4628
  capturesLabelSize: true,
4595
4629
  params: [
4596
4630
  {
4597
- key: "arrowOpeningAngle",
4631
+ key: "openingAngle",
4598
4632
  presentationTier: "advanced",
4599
4633
  label: "Arrow opening",
4600
4634
  description: "Angular opening between the two arrow tips, in degrees",
@@ -4675,7 +4709,7 @@ function createAreaDefense(coordinates, options = {}) {
4675
4709
  features: []
4676
4710
  };
4677
4711
  const upperTipAngle = Math.atan2(radiusVector[1], radiusVector[0]);
4678
- const arrowHalf = Math.min(120, Math.max(5, resolved.arrowOpeningAngle)) * Math.PI / 360;
4712
+ const arrowHalf = Math.min(120, Math.max(5, resolved.openingAngle)) * Math.PI / 360;
4679
4713
  const labelHalf = Math.min(90, Math.max(5, resolved.labelOpeningAngle)) * Math.PI / 360;
4680
4714
  const labelCenter = upperTipAngle + arrowHalf - Math.PI;
4681
4715
  const upperStart = labelCenter + labelHalf;
@@ -6111,7 +6145,7 @@ const DEFAULT_ATTACK_HELICOPTER_OPTIONS = {
6111
6145
  symbolHeightRatio: .45,
6112
6146
  triangleSizeRatio: .25,
6113
6147
  bottomBarWidthRatio: 1.5,
6114
- bowtieWidthRatio: .8
6148
+ bowTieWidthRatio: .8
6115
6149
  };
6116
6150
  const ATTACK_HELICOPTER_METADATA = {
6117
6151
  id: "attack-helicopter",
@@ -6163,7 +6197,7 @@ const ATTACK_HELICOPTER_METADATA = {
6163
6197
  step: .1
6164
6198
  },
6165
6199
  {
6166
- key: "bowtieWidthRatio",
6200
+ key: "bowTieWidthRatio",
6167
6201
  presentationTier: "advanced",
6168
6202
  label: "Bowtie width",
6169
6203
  description: "Size of the central bowtie relative to the reference triangle.",
@@ -6232,7 +6266,7 @@ function createAttackHelicopter(coordinates, options = {}) {
6232
6266
  const symbolHeightRatio = options.symbolHeightRatio ?? DEFAULT_ATTACK_HELICOPTER_OPTIONS.symbolHeightRatio;
6233
6267
  const triangleSizeRatio = options.triangleSizeRatio ?? DEFAULT_ATTACK_HELICOPTER_OPTIONS.triangleSizeRatio;
6234
6268
  const bottomBarWidthRatio = options.bottomBarWidthRatio ?? DEFAULT_ATTACK_HELICOPTER_OPTIONS.bottomBarWidthRatio;
6235
- const bowtieWidthRatio = options.bowtieWidthRatio ?? DEFAULT_ATTACK_HELICOPTER_OPTIONS.bowtieWidthRatio;
6269
+ const bowTieWidthRatio = options.bowTieWidthRatio ?? DEFAULT_ATTACK_HELICOPTER_OPTIONS.bowTieWidthRatio;
6236
6270
  const baseUnit = headWidth * .45;
6237
6271
  const shaftLength = headWidth * symbolHeightRatio;
6238
6272
  const triHeight = baseUnit * triangleSizeRatio;
@@ -6278,7 +6312,7 @@ function createAttackHelicopter(coordinates, options = {}) {
6278
6312
  coordinates: [unproject(pBarLeft[0], pBarLeft[1]), unproject(pBarRight[0], pBarRight[1])]
6279
6313
  }
6280
6314
  };
6281
- const bowTieSize = refTriHeight * bowtieWidthRatio;
6315
+ const bowTieSize = refTriHeight * bowTieWidthRatio;
6282
6316
  const bowTieHalfWidth = bowTieSize * .6;
6283
6317
  const pBowTieRightBaseCenter = vecAdd(center, vecScale(u, bowTieSize));
6284
6318
  const pBowTieRightTop = vecAdd(pBowTieRightBaseCenter, vecScale(n, bowTieHalfWidth));
@@ -9791,12 +9825,189 @@ const CLEAR = defineControlMeasure({
9791
9825
  rule: line23DrawRule
9792
9826
  });
9793
9827
  //#endregion
9828
+ //#region src/generators/cm15-maneuver-areas/maneuver-arrow-task-shared.ts
9829
+ const DEFAULT_MANEUVER_ARROW_TASK_AMPLIFIER_OPTIONS = {
9830
+ labelPosition: .25,
9831
+ dtgPosition: .62,
9832
+ labelPadding: 0
9833
+ };
9834
+ const DEFAULT_MANEUVER_ARROW_TASK_OPTIONS = {
9835
+ shaftWidthRatio: DEFAULT_SHAFT_WIDTH_RATIO$1,
9836
+ rearWidthRatio: DEFAULT_REAR_WIDTH_RATIO,
9837
+ smooth: false,
9838
+ smoothResolution: 5,
9839
+ crossbarLengthRatio: 1.1,
9840
+ ...DEFAULT_MANEUVER_ARROW_TASK_AMPLIFIER_OPTIONS
9841
+ };
9842
+ function createManeuverArrowTaskAmplifierLabels(geometry, options, textAmplifiers) {
9843
+ const resolved = {
9844
+ ...DEFAULT_MANEUVER_ARROW_TASK_AMPLIFIER_OPTIONS,
9845
+ ...options
9846
+ };
9847
+ const centerline = geometry.shaftCenterline;
9848
+ const { segments, totalLength } = polylineSegments(centerline);
9849
+ if (segments.length === 0) return [];
9850
+ const labels = [];
9851
+ const sizeProps = labelSizeProps(resolved);
9852
+ const labelFrame = pointAlongPolyline(segments, totalLength, clamp01(resolved.labelPosition));
9853
+ if (labelFrame && textAmplifiers.T) pushLabel(labels, labelFrame.point, textAmplifiers.T, labelRotationAlong(labelFrame.along), sizeProps, void 0, "T");
9854
+ const dtgFrame = pointAlongPolyline(segments, totalLength, clamp01(resolved.dtgPosition));
9855
+ if (dtgFrame) {
9856
+ const rowOffset = resolveLabelOffsetMeters(resolved, .55 + Math.max(0, resolved.labelPadding));
9857
+ const rotation = labelRotationAlong(dtgFrame.along);
9858
+ if (textAmplifiers.W) pushLabel(labels, vecAdd(dtgFrame.point, vecScale(dtgFrame.perp, rowOffset)), textAmplifiers.W, rotation, sizeProps, void 0, "W");
9859
+ if (textAmplifiers.W1) pushLabel(labels, vecSub(dtgFrame.point, vecScale(dtgFrame.perp, rowOffset)), textAmplifiers.W1, rotation, sizeProps, void 0, "W1");
9860
+ }
9861
+ return labels;
9862
+ }
9863
+ function createManeuverArrowTask(coordinates, options, textAmplifiers, config) {
9864
+ const merged = {
9865
+ ...DEFAULT_MANEUVER_ARROW_TASK_OPTIONS,
9866
+ ...options
9867
+ };
9868
+ const resolved = {
9869
+ ...merged,
9870
+ rearWidthRatio: resolveRearWidthRatio(options, merged.shaftWidthRatio)
9871
+ };
9872
+ const { geometry } = processAttackGeometry(coordinates, resolved);
9873
+ if (!geometry) return {
9874
+ type: "FeatureCollection",
9875
+ features: []
9876
+ };
9877
+ const outline = [
9878
+ ...geometry.shaftLeft,
9879
+ geometry.headRing[0],
9880
+ geometry.headRing[1],
9881
+ geometry.headRing[2],
9882
+ ...[...geometry.shaftRight].reverse()
9883
+ ];
9884
+ const centerline = geometry.shaftCenterline;
9885
+ const { segments, totalLength } = polylineSegments(centerline);
9886
+ if (segments.length === 0) return {
9887
+ type: "FeatureCollection",
9888
+ features: []
9889
+ };
9890
+ const tipVector = vecSub(geometry.ptTip, geometry.ptBase);
9891
+ const tipDirection = vecNorm(tipVector);
9892
+ const headWidth = vecMag(vecSub(geometry.headRing[0], geometry.headRing[2]));
9893
+ let crossbarCenter;
9894
+ let crossbarAlong;
9895
+ let crossbarBaseWidth = headWidth;
9896
+ if (config.crossbarAt === "tip") {
9897
+ crossbarCenter = geometry.ptTip;
9898
+ crossbarAlong = tipDirection;
9899
+ } else {
9900
+ const shaftPosition = Number.isFinite(config.crossbarAt.shaftPosition) ? clamp01(config.crossbarAt.shaftPosition) : 0;
9901
+ const crossbarFrame = pointAlongPolyline(segments, totalLength, shaftPosition);
9902
+ crossbarCenter = crossbarFrame.point;
9903
+ crossbarAlong = crossbarFrame.along;
9904
+ const rearWidth = vecMag(vecSub(geometry.shaftLeft[0], geometry.shaftRight[0]));
9905
+ crossbarBaseWidth = rearWidth + (vecMag(vecSub(geometry.shaftLeft.at(-1), geometry.shaftRight.at(-1))) - rearWidth) * shaftPosition;
9906
+ }
9907
+ const crossbarLength = crossbarBaseWidth * Math.max(1, resolved.crossbarLengthRatio);
9908
+ const crossbarPerp = [-crossbarAlong[1], crossbarAlong[0]];
9909
+ const crossbar = [vecAdd(crossbarCenter, vecScale(crossbarPerp, crossbarLength / 2)), vecSub(crossbarCenter, vecScale(crossbarPerp, crossbarLength / 2))];
9910
+ const features = [{
9911
+ type: "Feature",
9912
+ properties: { part: config.part },
9913
+ geometry: {
9914
+ type: "MultiLineString",
9915
+ coordinates: [outline, crossbar].map((line) => line.map((point) => unproject(point[0], point[1])))
9916
+ }
9917
+ }];
9918
+ const sizeProps = labelSizeProps(resolved);
9919
+ const codePoint = vecAdd(geometry.ptBase, vecScale(tipVector, .55));
9920
+ const labels = [{
9921
+ type: "Feature",
9922
+ properties: {
9923
+ part: "label",
9924
+ labelPlacement: false,
9925
+ text: config.code,
9926
+ rotation: labelRotationAlong(tipDirection),
9927
+ ...sizeProps
9928
+ },
9929
+ geometry: {
9930
+ type: "Point",
9931
+ coordinates: unproject(codePoint[0], codePoint[1])
9932
+ }
9933
+ }];
9934
+ features.push(...labels, ...createManeuverArrowTaskAmplifierLabels(geometry, resolved, textAmplifiers));
9935
+ return {
9936
+ type: "FeatureCollection",
9937
+ features
9938
+ };
9939
+ }
9940
+ const MANEUVER_ARROW_TASK_AMPLIFIER_PARAMS = [
9941
+ {
9942
+ key: "labelPosition",
9943
+ presentationTier: "advanced",
9944
+ label: "Designation position",
9945
+ description: "Position of Field T along the shaft, from tail (0) to head (1)",
9946
+ type: "number",
9947
+ min: 0,
9948
+ max: 1,
9949
+ step: .01
9950
+ },
9951
+ {
9952
+ key: "dtgPosition",
9953
+ presentationTier: "advanced",
9954
+ label: "Date-time position",
9955
+ description: "Position of Fields W/W1 along the shaft, from tail (0) to head (1)",
9956
+ type: "number",
9957
+ min: 0,
9958
+ max: 1,
9959
+ step: .01
9960
+ },
9961
+ {
9962
+ key: "labelPadding",
9963
+ presentationTier: "advanced",
9964
+ label: "Label padding",
9965
+ description: "Extra spacing between the W and W1 rows, in label heights",
9966
+ type: "number",
9967
+ min: 0,
9968
+ max: 2,
9969
+ step: .05
9970
+ }
9971
+ ];
9972
+ const MANEUVER_ARROW_TASK_PARAMS = [{
9973
+ key: "crossbarLengthRatio",
9974
+ presentationTier: "advanced",
9975
+ label: "Straight line length",
9976
+ description: "Crossbar length as a multiple of the arrowhead base width",
9977
+ type: "number",
9978
+ min: 1,
9979
+ max: 2.5,
9980
+ step: .1
9981
+ }, ...MANEUVER_ARROW_TASK_AMPLIFIER_PARAMS];
9982
+ const MANEUVER_ARROW_TASK_TEXT_AMPLIFIERS = [
9983
+ {
9984
+ key: "T",
9985
+ label: "Unique designation",
9986
+ description: "Field T — the maneuver's unique designation.",
9987
+ placeholder: "DAVID",
9988
+ maxLength: 21
9989
+ },
9990
+ {
9991
+ key: "W",
9992
+ label: "Start date-time group",
9993
+ description: "Field W — the maneuver's start date-time group.",
9994
+ placeholder: "080400ZOCT2008"
9995
+ },
9996
+ {
9997
+ key: "W1",
9998
+ label: "End date-time group",
9999
+ description: "Field W1 — the maneuver's end date-time group.",
10000
+ placeholder: "120300ZOCT2008"
10001
+ }
10002
+ ];
10003
+ //#endregion
9794
10004
  //#region src/generators/cm15-maneuver-areas/supportingAttack.ts
9795
10005
  const DEFAULT_SUPPORTING_ATTACK_OPTIONS = {
9796
10006
  shaftWidthRatio: DEFAULT_SHAFT_WIDTH_RATIO$1,
9797
10007
  rearWidthRatio: DEFAULT_REAR_WIDTH_RATIO,
9798
10008
  smooth: false,
9799
- smoothResolution: 5
10009
+ smoothResolution: 5,
10010
+ ...DEFAULT_MANEUVER_ARROW_TASK_AMPLIFIER_OPTIONS
9800
10011
  };
9801
10012
  const SUPPORTING_ATTACK_METADATA = {
9802
10013
  id: "supporting-attack",
@@ -9808,14 +10019,16 @@ const SUPPORTING_ATTACK_METADATA = {
9808
10019
  value: "151404",
9809
10020
  minCoordinates: 3,
9810
10021
  geometry: "line",
9811
- geometryTypes: ["LineString"],
10022
+ geometryTypes: ["LineString", "Point"],
9812
10023
  paints: {
9813
10024
  stroke: true,
9814
10025
  fill: "none",
9815
- text: false
10026
+ text: true
9816
10027
  },
9817
10028
  drawRule: "Axis1",
9818
- params: ATTACK_SHAFT_PARAMS
10029
+ capturesLabelSize: true,
10030
+ params: [...ATTACK_SHAFT_PARAMS, ...MANEUVER_ARROW_TASK_AMPLIFIER_PARAMS],
10031
+ textAmplifiers: MANEUVER_ARROW_TASK_TEXT_AMPLIFIERS
9819
10032
  };
9820
10033
  /**
9821
10034
  * Generates a GeoJSON FeatureCollection for a Supporting Attack tactical symbol.
@@ -9827,7 +10040,7 @@ const SUPPORTING_ATTACK_METADATA = {
9827
10040
  * @param options - Configuration options.
9828
10041
  * @returns A GeoJSON FeatureCollection<LineString>.
9829
10042
  */
9830
- function createSupportingAttack(coordinates, options = {}) {
10043
+ function createSupportingAttack(coordinates, options = {}, textAmplifiers = {}) {
9831
10044
  const { geometry } = processAttackGeometry(coordinates, options);
9832
10045
  if (!geometry) return {
9833
10046
  type: "FeatureCollection",
@@ -9842,7 +10055,7 @@ function createSupportingAttack(coordinates, options = {}) {
9842
10055
  type: "LineString",
9843
10056
  coordinates: supportingAttackOutline(geometry)
9844
10057
  }
9845
- }]
10058
+ }, ...createManeuverArrowTaskAmplifierLabels(geometry, options, textAmplifiers)]
9846
10059
  };
9847
10060
  }
9848
10061
  /**
@@ -9867,7 +10080,21 @@ const SUPPORTING_ATTACK = defineControlMeasure({
9867
10080
  generator: createSupportingAttack,
9868
10081
  defaultOptions: DEFAULT_SUPPORTING_ATTACK_OPTIONS,
9869
10082
  rule: axis1DrawRule,
9870
- optionHandles: variableWidthAttackOptionHandles
10083
+ optionHandles: variableWidthAttackOptionHandles,
10084
+ previewSample: {
10085
+ controlPoints: [
10086
+ [1, 0],
10087
+ [-.4, -.35],
10088
+ [-1, -.8],
10089
+ [.45, .55]
10090
+ ],
10091
+ textAmplifiers: {
10092
+ T: "DAVID",
10093
+ W: "080400Z",
10094
+ W1: "120300Z"
10095
+ },
10096
+ options: { labelSize: 80 }
10097
+ }
9871
10098
  });
9872
10099
  //#endregion
9873
10100
  //#region src/generators/cm34-mission-tasks/counterattack.ts
@@ -9917,14 +10144,20 @@ const COUNTERATTACK_METADATA = {
9917
10144
  };
9918
10145
  /** Generates the Counterattack mission task (340600). */
9919
10146
  function createCounterattack(coordinates, options = {}, textAmplifiers = {}) {
9920
- const outline = createSupportingAttack(coordinates, options);
9921
10147
  const { geometry } = processAttackGeometry(coordinates, options);
9922
- if (!geometry || outline.features.length === 0) return outline;
10148
+ if (!geometry) return {
10149
+ type: "FeatureCollection",
10150
+ features: []
10151
+ };
9923
10152
  const dashedOutline = {
9924
- ...outline.features[0],
10153
+ type: "Feature",
9925
10154
  properties: {
9926
10155
  part: "counterattack",
9927
10156
  style: { strokeDash: [...COUNTERATTACK_DASH] }
10157
+ },
10158
+ geometry: {
10159
+ type: "LineString",
10160
+ coordinates: supportingAttackOutline(geometry)
9928
10161
  }
9929
10162
  };
9930
10163
  const { segments, totalLength } = polylineSegments(geometry.shaftCenterline);
@@ -10180,7 +10413,7 @@ function buildFieldOfFireV(coordinates, hSize, hWidth) {
10180
10413
  };
10181
10414
  }
10182
10415
  //#endregion
10183
- //#region src/generators/cm15-maneuver-areas/searchArea.ts
10416
+ //#region src/tactical-arrow-utils.ts
10184
10417
  const DEFAULT_ARROWHEAD_LENGTH_RATIO = .12;
10185
10418
  const DEFAULT_ARROWHEAD_WIDTH_RATIO = .5;
10186
10419
  const DEFAULT_ZIGZAG_START = .55;
@@ -10196,183 +10429,17 @@ const DEFAULT_TACTICAL_ARROW_OPTIONS = {
10196
10429
  zigzagEnd: DEFAULT_ZIGZAG_END,
10197
10430
  zigzagAmplitudeRatio: DEFAULT_ZIGZAG_AMPLITUDE_RATIO
10198
10431
  };
10199
- const SEARCH_AREA_METADATA = {
10200
- id: "search-area",
10201
- name: "Search Area",
10202
- description: "Designates an area to examine systematically for specified persons, forces, materiel, or information.",
10203
- entity: "Maneuver Areas",
10204
- entityType: "Search Area",
10205
- value: "152200",
10206
- minCoordinates: 3,
10207
- maxCoordinates: 3,
10208
- geometry: "area",
10209
- geometryTypes: ["MultiLineString", "MultiPolygon"],
10210
- paints: {
10211
- stroke: true,
10212
- fill: "fixed",
10213
- text: false
10214
- },
10215
- drawRule: "Area21",
10216
- params: [
10217
- {
10218
- key: "arrowheadLengthRatio",
10219
- presentationTier: "advanced",
10220
- label: "Arrowhead length",
10221
- description: "Arrowhead length as a ratio of the total leg length",
10222
- type: "number",
10223
- min: 0,
10224
- max: .5,
10225
- step: .01
10226
- },
10227
- {
10228
- key: "arrowheadWidthRatio",
10229
- presentationTier: "advanced",
10230
- label: "Arrowhead width",
10231
- description: "Arrowhead base width as a multiple of the arrowhead length",
10232
- type: "number",
10233
- min: .05,
10234
- max: 2,
10235
- step: .1
10236
- },
10237
- {
10238
- key: "zigzagStart",
10239
- presentationTier: "advanced",
10240
- label: "Zigzag start",
10241
- description: "Point along the leg where the zigzag begins (0-1).",
10242
- type: "number",
10243
- min: 0,
10244
- max: 1,
10245
- step: .05
10246
- },
10247
- {
10248
- key: "zigzagEnd",
10249
- presentationTier: "advanced",
10250
- label: "Zigzag end",
10251
- description: "Point along the leg where the zigzag returns (0-1).",
10252
- type: "number",
10253
- min: 0,
10254
- max: 1,
10255
- step: .05
10256
- },
10257
- {
10258
- key: "zigzagAmplitudeRatio",
10259
- presentationTier: "advanced",
10260
- label: "Zigzag amplitude",
10261
- description: "Perpendicular kink offset as a ratio of leg length.",
10262
- type: "number",
10263
- min: 0,
10264
- max: .3,
10265
- step: .01
10266
- }
10267
- ]
10268
- };
10269
- /**
10270
- * Generates a GeoJSON FeatureCollection of a tactical "lightning" arrow.
10271
- * Handles internal projection to maintain correct perpendicularity.
10272
- *
10273
- * @param coordinates - [Apex Point (PT1), Top Tail (PT2), Bottom Tail (PT3)]
10274
- * @param options - Geometric configuration parameters
10275
- * @returns A FeatureCollection containing a MultiLineString for bolts and a MultiPolygon for heads
10276
- */
10277
- function createSearchArea(coordinates, options = {}) {
10278
- const { arrowheadLengthRatio = DEFAULT_ARROWHEAD_LENGTH_RATIO, arrowheadWidthRatio = DEFAULT_ARROWHEAD_WIDTH_RATIO, zigzagStart = DEFAULT_ZIGZAG_START, zigzagEnd = DEFAULT_ZIGZAG_END, zigzagAmplitudeRatio = DEFAULT_ZIGZAG_AMPLITUDE_RATIO } = options;
10279
- const safeArrowheadLengthRatio = Math.max(0, arrowheadLengthRatio);
10280
- const safeArrowheadWidthRatio = Math.max(0, arrowheadWidthRatio);
10281
- const safeZigzagStart = clamp01(zigzagStart);
10282
- const safeZigzagEnd = clamp01(zigzagEnd);
10283
- const safeZigzagAmplitudeRatio = Math.max(0, zigzagAmplitudeRatio);
10284
- const [c0, c1, c2] = coordinates;
10285
- const [p1, p2, p3] = [
10286
- project(c0[0], c0[1]),
10287
- project(c1[0], c1[1]),
10288
- project(c2[0], c2[1])
10289
- ];
10290
- const shaftSegments = [];
10291
- const headPolygons = [];
10292
- const addLeg = (origin, tip, opposite) => {
10293
- const v = vecSub(tip, origin);
10294
- const totalLen = vecMag(v);
10295
- if (totalLen < 1e-6) return;
10296
- const u = vecNorm(v);
10297
- let n = [-u[1], u[0]];
10298
- const toOpp = vecSub(opposite, origin);
10299
- if (vecDot(n, toOpp) > 0) n = vecScale(n, -1);
10300
- const offset = totalLen * safeZigzagAmplitudeRatio;
10301
- const ptA = vecAdd(vecAdd(origin, vecScale(v, safeZigzagStart)), vecScale(n, offset));
10302
- const ptB = vecAdd(vecAdd(origin, vecScale(v, safeZigzagEnd)), vecScale(n, -offset));
10303
- const fv = vecSub(tip, ptB);
10304
- const fLen = vecMag(fv);
10305
- let fu = vecNorm(fv);
10306
- if (fLen < 1e-6) fu = u;
10307
- const headLen = totalLen * safeArrowheadLengthRatio;
10308
- const pBase = vecSub(tip, vecScale(fu, headLen));
10309
- const wingWidth = headLen * safeArrowheadWidthRatio;
10310
- const perp = [-fu[1], fu[0]];
10311
- const pWing1 = vecAdd(pBase, vecScale(perp, wingWidth));
10312
- const pWing2 = vecSub(pBase, vecScale(perp, wingWidth));
10313
- shaftSegments.push([
10314
- origin,
10315
- ptA,
10316
- ptB,
10317
- pBase
10318
- ].map((p) => unproject(p[0], p[1])));
10319
- headPolygons.push([[
10320
- tip,
10321
- pWing1,
10322
- pWing2,
10323
- tip
10324
- ].map((p) => unproject(p[0], p[1]))]);
10325
- };
10326
- addLeg(p1, p2, p3);
10327
- addLeg(p1, p3, p2);
10328
- const features = [];
10329
- if (shaftSegments.length > 0) features.push({
10330
- type: "Feature",
10331
- properties: { part: "shaft" },
10332
- geometry: {
10333
- type: "MultiLineString",
10334
- coordinates: shaftSegments
10335
- }
10336
- });
10337
- if (headPolygons.length > 0) features.push({
10338
- type: "Feature",
10339
- properties: {
10340
- part: "head",
10341
- fill: true,
10342
- style: SOLID_ACCENT_FILL
10343
- },
10344
- geometry: {
10345
- type: "MultiPolygon",
10346
- coordinates: headPolygons
10347
- }
10348
- });
10349
- return {
10350
- type: "FeatureCollection",
10351
- features
10352
- };
10353
- }
10354
- const SEARCH_AREA = defineControlMeasure({
10355
- metadata: SEARCH_AREA_METADATA,
10356
- generator: (controlPoints, options) => createSearchArea(asThreePoints(controlPoints), options),
10357
- defaultOptions: DEFAULT_TACTICAL_ARROW_OPTIONS,
10358
- rule: searchAreaDrawRule
10359
- });
10360
- //#endregion
10361
- //#region src/generators/cm34-mission-tasks/security-task-shared.ts
10362
- const DEFAULT_SECURITY_TASK_OPTIONS = {
10363
- ...DEFAULT_TACTICAL_ARROW_OPTIONS,
10364
- labelInsetRatio: .2
10365
- };
10366
- const SECURITY_TASK_PARAMS = [
10367
- {
10368
- key: "arrowheadLengthRatio",
10369
- presentationTier: "advanced",
10370
- label: "Arrowhead length",
10371
- description: "Arrowhead length as a ratio of the total leg length",
10372
- type: "number",
10373
- min: 0,
10374
- max: .5,
10375
- step: .01
10432
+ /** The param descriptors every tactical-arrow leg exposes, in presentation order. */
10433
+ const TACTICAL_ARROW_PARAMS = [
10434
+ {
10435
+ key: "arrowheadLengthRatio",
10436
+ presentationTier: "advanced",
10437
+ label: "Arrowhead length",
10438
+ description: "Arrowhead length as a ratio of the total leg length",
10439
+ type: "number",
10440
+ min: 0,
10441
+ max: .5,
10442
+ step: .01
10376
10443
  },
10377
10444
  {
10378
10445
  key: "arrowheadWidthRatio",
@@ -10413,19 +10480,39 @@ const SECURITY_TASK_PARAMS = [
10413
10480
  min: 0,
10414
10481
  max: .3,
10415
10482
  step: .01
10416
- },
10417
- {
10418
- key: "labelInsetRatio",
10419
- presentationTier: "advanced",
10420
- label: "Letter position",
10421
- description: "Inset of each task letter from PT. 2 or PT. 3 across the center opening",
10422
- type: "number",
10423
- min: .05,
10424
- max: .45,
10425
- step: .01
10426
10483
  }
10427
10484
  ];
10428
10485
  /**
10486
+ * The single home of the tactical-arrow option contract: fills in the defaults
10487
+ * and applies the clamps the leg geometry relies on, so every measure carrying
10488
+ * a lightning leg reads the same ranges.
10489
+ */
10490
+ function resolveTacticalArrowOptions(options) {
10491
+ return {
10492
+ arrowheadLengthRatio: Math.max(0, options.arrowheadLengthRatio ?? DEFAULT_ARROWHEAD_LENGTH_RATIO),
10493
+ arrowheadWidthRatio: Math.max(0, options.arrowheadWidthRatio ?? DEFAULT_ARROWHEAD_WIDTH_RATIO),
10494
+ zigzagStart: clamp01(options.zigzagStart ?? DEFAULT_ZIGZAG_START),
10495
+ zigzagEnd: clamp01(options.zigzagEnd ?? DEFAULT_ZIGZAG_END),
10496
+ zigzagAmplitudeRatio: Math.max(0, options.zigzagAmplitudeRatio ?? DEFAULT_ZIGZAG_AMPLITUDE_RATIO)
10497
+ };
10498
+ }
10499
+ //#endregion
10500
+ //#region src/generators/cm34-mission-tasks/security-task-shared.ts
10501
+ const DEFAULT_SECURITY_TASK_OPTIONS = {
10502
+ ...DEFAULT_TACTICAL_ARROW_OPTIONS,
10503
+ labelInsetRatio: .2
10504
+ };
10505
+ const SECURITY_TASK_PARAMS = [...TACTICAL_ARROW_PARAMS, {
10506
+ key: "labelInsetRatio",
10507
+ presentationTier: "advanced",
10508
+ label: "Letter position",
10509
+ description: "Inset of each task letter from PT. 2 or PT. 3 across the center opening",
10510
+ type: "number",
10511
+ min: .05,
10512
+ max: .45,
10513
+ step: .01
10514
+ }];
10515
+ /**
10429
10516
  * Shared Line26 geometry for the Cover, Guard, and Screen task family.
10430
10517
  * PT. 1/PT. 4 are the outward tips; PT. 2/PT. 3 are the inner shaft ends.
10431
10518
  * The opening between PT. 2 and PT. 3 is deliberately left clear for the
@@ -10443,13 +10530,7 @@ function createSecurityTaskSymbol(coordinates, config, options = {}) {
10443
10530
  ...DEFAULT_SECURITY_TASK_OPTIONS,
10444
10531
  ...options
10445
10532
  };
10446
- const arrowOptions = {
10447
- arrowheadLengthRatio: Math.max(0, resolved.arrowheadLengthRatio),
10448
- arrowheadWidthRatio: Math.max(0, resolved.arrowheadWidthRatio),
10449
- zigzagStart: clamp(resolved.zigzagStart, 0, 1),
10450
- zigzagEnd: clamp(resolved.zigzagEnd, 0, 1),
10451
- zigzagAmplitudeRatio: Math.max(0, resolved.zigzagAmplitudeRatio)
10452
- };
10533
+ const arrowOptions = resolveTacticalArrowOptions(resolved);
10453
10534
  const lines = [createTacticalArrow(p2, p1, p3, arrowOptions), createTacticalArrow(p3, p4, p2, arrowOptions)].flatMap((arrow) => arrow ?? []);
10454
10535
  if (lines.length === 0) return {
10455
10536
  type: "FeatureCollection",
@@ -10641,18 +10722,16 @@ function createDelaySymbol(coordinates, options = {}) {
10641
10722
  createArrowHead$1(p1, vecScale(dirLine, -1), lineLength * (options.arrowheadLengthRatio ?? DEFAULT_DELAY_OPTIONS.arrowheadLengthRatio), lineLength * (options.arrowheadWidthRatio ?? DEFAULT_DELAY_OPTIONS.arrowheadWidthRatio))
10642
10723
  ];
10643
10724
  if (diameter >= 1e-6) lines.push(createSemicircleArc(p2, diameterEnd, dirLine, options.arcSegments ?? DEFAULT_DELAY_OPTIONS.arcSegments));
10644
- const lineFeature = {
10645
- type: "Feature",
10646
- properties: { part: "delay" },
10647
- geometry: {
10648
- type: "MultiLineString",
10649
- coordinates: lines
10650
- }
10651
- };
10652
- const lineRotation = Math.atan2(dirLine[1], dirLine[0]);
10653
10725
  return {
10654
10726
  type: "FeatureCollection",
10655
- features: [lineFeature, createLabelFeature(labelPoint, "D", normalizeRadians(Math.PI + lineRotation), {
10727
+ features: [{
10728
+ type: "Feature",
10729
+ properties: { part: "delay" },
10730
+ geometry: {
10731
+ type: "MultiLineString",
10732
+ coordinates: lines
10733
+ }
10734
+ }, createLabelFeature(labelPoint, "D", labelRotationAlong(dirLine), {
10656
10735
  labelSize: options.labelSize,
10657
10736
  labelSizePixels: options.labelSizePixels
10658
10737
  })]
@@ -12713,7 +12792,7 @@ function createInfiltrationLane(coordinates, options = {}, textAmplifiers = {})
12713
12792
  type: "Feature",
12714
12793
  properties: {
12715
12794
  part: "label",
12716
- labelPlacement: false,
12795
+ labelPlacementKey: "T",
12717
12796
  amplifierField: "T",
12718
12797
  text: textAmplifiers.T,
12719
12798
  rotation: labelRotationAlong(labelFrame.along),
@@ -13662,188 +13741,27 @@ const FORTIFIED_AREA = defineControlMeasure({
13662
13741
  }
13663
13742
  });
13664
13743
  //#endregion
13665
- //#region src/generators/cm15-maneuver-areas/maneuver-arrow-task-shared.ts
13666
- const DEFAULT_MANEUVER_ARROW_TASK_OPTIONS = {
13667
- shaftWidthRatio: DEFAULT_SHAFT_WIDTH_RATIO$1,
13668
- rearWidthRatio: DEFAULT_REAR_WIDTH_RATIO,
13669
- smooth: false,
13670
- smoothResolution: 5,
13671
- crossbarLengthRatio: 1.1,
13672
- labelPosition: .25,
13673
- dtgPosition: .62,
13674
- labelPadding: 0
13675
- };
13676
- function createManeuverArrowTask(coordinates, options, textAmplifiers, config) {
13677
- const merged = {
13678
- ...DEFAULT_MANEUVER_ARROW_TASK_OPTIONS,
13679
- ...options
13680
- };
13681
- const resolved = {
13682
- ...merged,
13683
- rearWidthRatio: resolveRearWidthRatio(options, merged.shaftWidthRatio)
13684
- };
13685
- const { geometry } = processAttackGeometry(coordinates, resolved);
13686
- if (!geometry) return {
13687
- type: "FeatureCollection",
13688
- features: []
13689
- };
13690
- const outline = [
13691
- ...geometry.shaftLeft,
13692
- geometry.headRing[0],
13693
- geometry.headRing[1],
13694
- geometry.headRing[2],
13695
- ...[...geometry.shaftRight].reverse()
13696
- ];
13697
- const centerline = geometry.shaftCenterline;
13698
- const { segments, totalLength } = polylineSegments(centerline);
13699
- if (segments.length === 0) return {
13700
- type: "FeatureCollection",
13701
- features: []
13702
- };
13703
- const tipVector = vecSub(geometry.ptTip, geometry.ptBase);
13704
- const tipDirection = vecNorm(tipVector);
13705
- const headWidth = vecMag(vecSub(geometry.headRing[0], geometry.headRing[2]));
13706
- let crossbarCenter;
13707
- let crossbarAlong;
13708
- let crossbarBaseWidth = headWidth;
13709
- if (config.crossbarAt === "tip") {
13710
- crossbarCenter = geometry.ptTip;
13711
- crossbarAlong = tipDirection;
13712
- } else {
13713
- const shaftPosition = Number.isFinite(config.crossbarAt.shaftPosition) ? clamp01(config.crossbarAt.shaftPosition) : 0;
13714
- const crossbarFrame = pointAlongPolyline(segments, totalLength, shaftPosition);
13715
- crossbarCenter = crossbarFrame.point;
13716
- crossbarAlong = crossbarFrame.along;
13717
- const rearWidth = vecMag(vecSub(geometry.shaftLeft[0], geometry.shaftRight[0]));
13718
- crossbarBaseWidth = rearWidth + (vecMag(vecSub(geometry.shaftLeft.at(-1), geometry.shaftRight.at(-1))) - rearWidth) * shaftPosition;
13719
- }
13720
- const crossbarLength = crossbarBaseWidth * Math.max(1, resolved.crossbarLengthRatio);
13721
- const crossbarPerp = [-crossbarAlong[1], crossbarAlong[0]];
13722
- const crossbar = [vecAdd(crossbarCenter, vecScale(crossbarPerp, crossbarLength / 2)), vecSub(crossbarCenter, vecScale(crossbarPerp, crossbarLength / 2))];
13723
- const features = [{
13724
- type: "Feature",
13725
- properties: { part: config.part },
13726
- geometry: {
13727
- type: "MultiLineString",
13728
- coordinates: [outline, crossbar].map((line) => line.map((point) => unproject(point[0], point[1])))
13729
- }
13730
- }];
13731
- const sizeProps = labelSizeProps(resolved);
13732
- const codePoint = vecAdd(geometry.ptBase, vecScale(tipVector, .55));
13733
- const labels = [{
13734
- type: "Feature",
13735
- properties: {
13736
- part: "label",
13737
- labelPlacement: false,
13738
- text: config.code,
13739
- rotation: labelRotationAlong(tipDirection),
13740
- ...sizeProps
13741
- },
13742
- geometry: {
13743
- type: "Point",
13744
- coordinates: unproject(codePoint[0], codePoint[1])
13745
- }
13746
- }];
13747
- const labelFrame = pointAlongPolyline(segments, totalLength, clamp01(resolved.labelPosition));
13748
- if (labelFrame && textAmplifiers.T) pushLabel(labels, labelFrame.point, textAmplifiers.T, labelRotationAlong(labelFrame.along), sizeProps, void 0, "T");
13749
- const dtgFrame = pointAlongPolyline(segments, totalLength, clamp01(resolved.dtgPosition));
13750
- if (dtgFrame) {
13751
- const rowOffset = resolveLabelOffsetMeters(resolved, .55 + Math.max(0, resolved.labelPadding));
13752
- const rotation = labelRotationAlong(dtgFrame.along);
13753
- if (textAmplifiers.W) pushLabel(labels, vecAdd(dtgFrame.point, vecScale(dtgFrame.perp, rowOffset)), textAmplifiers.W, rotation, sizeProps, void 0, "W");
13754
- if (textAmplifiers.W1) pushLabel(labels, vecSub(dtgFrame.point, vecScale(dtgFrame.perp, rowOffset)), textAmplifiers.W1, rotation, sizeProps, void 0, "W1");
13755
- }
13756
- features.push(...labels);
13757
- return {
13758
- type: "FeatureCollection",
13759
- features
13760
- };
13761
- }
13762
- const MANEUVER_ARROW_TASK_PARAMS = [
13763
- {
13764
- key: "crossbarLengthRatio",
13765
- presentationTier: "advanced",
13766
- label: "Straight line length",
13767
- description: "Crossbar length as a multiple of the arrowhead base width",
13768
- type: "number",
13769
- min: 1,
13770
- max: 2.5,
13771
- step: .1
13772
- },
13773
- {
13774
- key: "labelPosition",
13775
- presentationTier: "advanced",
13776
- label: "Designation position",
13777
- description: "Position of Field T along the shaft, from tail (0) to head (1)",
13778
- type: "number",
13779
- min: 0,
13780
- max: 1,
13781
- step: .01
13782
- },
13783
- {
13784
- key: "dtgPosition",
13785
- presentationTier: "advanced",
13786
- label: "Date-time position",
13787
- description: "Position of Fields W/W1 along the shaft, from tail (0) to head (1)",
13788
- type: "number",
13789
- min: 0,
13790
- max: 1,
13791
- step: .01
13792
- },
13793
- {
13794
- key: "labelPadding",
13795
- presentationTier: "advanced",
13796
- label: "Label padding",
13797
- description: "Extra spacing between the W and W1 rows, in label heights",
13798
- type: "number",
13799
- min: 0,
13800
- max: 2,
13801
- step: .05
13802
- }
13803
- ];
13804
- const MANEUVER_ARROW_TASK_TEXT_AMPLIFIERS = [
13805
- {
13806
- key: "T",
13807
- label: "Unique designation",
13808
- description: "Field T — the maneuver's unique designation.",
13809
- placeholder: "DAVID",
13810
- maxLength: 21
13811
- },
13812
- {
13813
- key: "W",
13814
- label: "Start date-time group",
13815
- description: "Field W — the maneuver's start date-time group.",
13816
- placeholder: "080400ZOCT2008"
13817
- },
13818
- {
13819
- key: "W1",
13820
- label: "End date-time group",
13821
- description: "Field W1 — the maneuver's end date-time group.",
13822
- placeholder: "120300ZOCT2008"
13823
- }
13824
- ];
13825
- //#endregion
13826
- //#region src/generators/cm15-maneuver-areas/frontal-attack.ts
13827
- const DEFAULT_FRONTAL_ATTACK_OPTIONS = DEFAULT_MANEUVER_ARROW_TASK_OPTIONS;
13828
- const FRONTAL_ATTACK_METADATA = {
13829
- id: "frontal-attack",
13830
- name: "Frontal Attack",
13831
- description: "An offensive maneuver in which the main action is directed against the front.",
13832
- entity: "Maneuver Areas",
13833
- entityType: "Frontal Attack",
13834
- value: "152700",
13835
- minCoordinates: 3,
13836
- geometry: "line",
13837
- geometryTypes: ["MultiLineString", "Point"],
13838
- paints: {
13839
- stroke: true,
13840
- fill: "none",
13841
- text: true
13842
- },
13843
- drawRule: "Axis1",
13844
- capturesLabelSize: true,
13845
- params: [...ATTACK_SHAFT_PARAMS, ...MANEUVER_ARROW_TASK_PARAMS],
13846
- textAmplifiers: MANEUVER_ARROW_TASK_TEXT_AMPLIFIERS
13744
+ //#region src/generators/cm15-maneuver-areas/frontal-attack.ts
13745
+ const DEFAULT_FRONTAL_ATTACK_OPTIONS = DEFAULT_MANEUVER_ARROW_TASK_OPTIONS;
13746
+ const FRONTAL_ATTACK_METADATA = {
13747
+ id: "frontal-attack",
13748
+ name: "Frontal Attack",
13749
+ description: "An offensive maneuver in which the main action is directed against the front.",
13750
+ entity: "Maneuver Areas",
13751
+ entityType: "Frontal Attack",
13752
+ value: "152700",
13753
+ minCoordinates: 3,
13754
+ geometry: "line",
13755
+ geometryTypes: ["MultiLineString", "Point"],
13756
+ paints: {
13757
+ stroke: true,
13758
+ fill: "none",
13759
+ text: true
13760
+ },
13761
+ drawRule: "Axis1",
13762
+ capturesLabelSize: true,
13763
+ params: [...ATTACK_SHAFT_PARAMS, ...MANEUVER_ARROW_TASK_PARAMS],
13764
+ textAmplifiers: MANEUVER_ARROW_TASK_TEXT_AMPLIFIERS
13847
13765
  };
13848
13766
  function createFrontalAttack(coordinates, options = {}, textAmplifiers = {}) {
13849
13767
  return createManeuverArrowTask(coordinates, options, textAmplifiers, {
@@ -13874,6 +13792,49 @@ const FRONTAL_ATTACK = defineControlMeasure({
13874
13792
  }
13875
13793
  });
13876
13794
  //#endregion
13795
+ //#region src/generators/cm34-mission-tasks/forward-passage-of-lines.ts
13796
+ const DEFAULT_FORWARD_PASSAGE_OF_LINES_OPTIONS = DEFAULT_DELAY_OPTIONS;
13797
+ const FORWARD_PASSAGE_OF_LINES_METADATA = {
13798
+ id: "forward-passage-of-lines",
13799
+ name: "Forward Passage of Lines",
13800
+ description: "Passes one force through another force's positions toward the enemy.",
13801
+ entity: "Mission Tasks",
13802
+ entityType: "Forward Passage of Lines",
13803
+ value: "344100",
13804
+ minCoordinates: 3,
13805
+ maxCoordinates: 3,
13806
+ geometry: "line",
13807
+ geometryTypes: ["MultiLineString", "Point"],
13808
+ paints: {
13809
+ stroke: true,
13810
+ fill: "none",
13811
+ text: true
13812
+ },
13813
+ drawRule: "Line24",
13814
+ capturesLabelSize: true,
13815
+ params: DELAY_PARAMS
13816
+ };
13817
+ function createForwardPassageOfLinesSymbol(coordinates, options = {}) {
13818
+ return createDelayVariantSymbol(coordinates, {
13819
+ glyph: "P(F)",
13820
+ part: "forward-passage-of-lines"
13821
+ }, options);
13822
+ }
13823
+ const FORWARD_PASSAGE_OF_LINES = defineControlMeasure({
13824
+ metadata: FORWARD_PASSAGE_OF_LINES_METADATA,
13825
+ generator: createForwardPassageOfLinesSymbol,
13826
+ defaultOptions: DEFAULT_FORWARD_PASSAGE_OF_LINES_OPTIONS,
13827
+ rule: line24DrawRule,
13828
+ previewSample: {
13829
+ controlPoints: [
13830
+ [1, -.4],
13831
+ [-1, -.4],
13832
+ [0, .6]
13833
+ ],
13834
+ options: { labelGapRatio: .42 }
13835
+ }
13836
+ });
13837
+ //#endregion
13877
13838
  //#region src/generators/cm34-mission-tasks/isolate.ts
13878
13839
  /**
13879
13840
  * Default options for the ISOLATE symbol.
@@ -14012,6 +13973,327 @@ const ISOLATE = defineControlMeasure({
14012
13973
  rule: isolateDrawRule
14013
13974
  });
14014
13975
  //#endregion
13976
+ //#region src/generators/cm34-mission-tasks/movement-to-contact.ts
13977
+ /** Default bolt length as a multiple of the shaft width at the arrow base. */
13978
+ const MOVEMENT_TO_CONTACT_BOLT_LENGTH_RATIO = 1;
13979
+ const DEFAULT_MOVEMENT_TO_CONTACT_BOLT_ANGLE = 45;
13980
+ const MOVEMENT_TO_CONTACT_BOLT_HANDLE_ID = "bolt-tip";
13981
+ const MOVEMENT_TO_CONTACT_STEM_SETBACK_RATIO = .22;
13982
+ const MOVEMENT_TO_CONTACT_STEM_OFFSET_RATIO = .55;
13983
+ const DEFAULT_MOVEMENT_TO_CONTACT_OPTIONS = {
13984
+ shaftWidthRatio: DEFAULT_SHAFT_WIDTH_RATIO$1,
13985
+ rearWidthRatio: DEFAULT_REAR_WIDTH_RATIO,
13986
+ smooth: false,
13987
+ smoothResolution: 5,
13988
+ ...DEFAULT_TACTICAL_ARROW_OPTIONS,
13989
+ ...DEFAULT_MANEUVER_ARROW_TASK_AMPLIFIER_OPTIONS,
13990
+ boltLengthRatio: MOVEMENT_TO_CONTACT_BOLT_LENGTH_RATIO,
13991
+ boltAngle: DEFAULT_MOVEMENT_TO_CONTACT_BOLT_ANGLE
13992
+ };
13993
+ const MOVEMENT_TO_CONTACT_METADATA = {
13994
+ id: "movement-to-contact",
13995
+ name: "Movement to Contact",
13996
+ description: "Develops the situation and establishes or regains contact with the enemy.",
13997
+ entity: "Mission Tasks",
13998
+ entityType: "Movement to Contact",
13999
+ value: "342900",
14000
+ minCoordinates: 3,
14001
+ geometry: "line",
14002
+ geometryTypes: [
14003
+ "MultiLineString",
14004
+ "MultiPolygon",
14005
+ "Point"
14006
+ ],
14007
+ paints: {
14008
+ stroke: true,
14009
+ fill: "fixed",
14010
+ text: true
14011
+ },
14012
+ drawRule: "Axis1",
14013
+ capturesLabelSize: true,
14014
+ params: [
14015
+ ...ATTACK_SHAFT_PARAMS,
14016
+ {
14017
+ key: "boltLengthRatio",
14018
+ presentationTier: "advanced",
14019
+ label: "Bolt length",
14020
+ description: "Bolt length as a multiple of the shaft width at the arrow base (2 or more is recommended).",
14021
+ type: "number",
14022
+ min: .1,
14023
+ max: 6,
14024
+ step: .1
14025
+ },
14026
+ {
14027
+ key: "boltAngle",
14028
+ presentationTier: "advanced",
14029
+ label: "Bolt angle",
14030
+ description: "Angle between each bolt and the main arrow axis, in degrees.",
14031
+ type: "number",
14032
+ min: 0,
14033
+ max: 90,
14034
+ step: 1
14035
+ },
14036
+ ...TACTICAL_ARROW_PARAMS,
14037
+ ...MANEUVER_ARROW_TASK_AMPLIFIER_PARAMS
14038
+ ],
14039
+ textAmplifiers: MANEUVER_ARROW_TASK_TEXT_AMPLIFIERS
14040
+ };
14041
+ function createBolt({ tip, arrowDirection, arrowBaseWidth, boltLengthRatio, boltAngle, side, options }) {
14042
+ const arrowNormal = [-arrowDirection[1], arrowDirection[0]];
14043
+ const direction = vecRotate(arrowDirection, side * (boltAngle * Math.PI / 180));
14044
+ const boltNormal = [-direction[1], direction[0]];
14045
+ const origin = vecAdd(vecAdd(tip, vecScale(arrowDirection, -arrowBaseWidth * MOVEMENT_TO_CONTACT_STEM_SETBACK_RATIO)), vecScale(arrowNormal, side * arrowBaseWidth * MOVEMENT_TO_CONTACT_STEM_OFFSET_RATIO));
14046
+ const boltLength = arrowBaseWidth * boltLengthRatio;
14047
+ const boltTip = vecAdd(origin, vecScale(direction, boltLength));
14048
+ const zigzagAmplitude = boltLength * options.zigzagAmplitudeRatio;
14049
+ const firstKink = vecAdd(vecAdd(origin, vecScale(direction, boltLength * options.zigzagStart)), vecScale(boltNormal, zigzagAmplitude));
14050
+ const secondKink = vecAdd(vecAdd(origin, vecScale(direction, boltLength * options.zigzagEnd)), vecScale(boltNormal, -zigzagAmplitude));
14051
+ const finalSegment = vecSub(boltTip, secondKink);
14052
+ const finalDirection = vecMag(finalSegment) >= 1e-6 ? vecNorm(finalSegment) : direction;
14053
+ const headLength = boltLength * options.arrowheadLengthRatio;
14054
+ return {
14055
+ origin,
14056
+ tip: boltTip,
14057
+ shaft: [
14058
+ origin,
14059
+ firstKink,
14060
+ secondKink,
14061
+ vecAdd(boltTip, vecScale(finalDirection, -headLength))
14062
+ ],
14063
+ head: createArrowHeadRing(boltTip, finalDirection, headLength, headLength * options.arrowheadWidthRatio * 2)
14064
+ };
14065
+ }
14066
+ function resolveMovementToContactGeometry(coordinates, options) {
14067
+ const { geometry } = processAttackGeometry(coordinates, options);
14068
+ if (!geometry) return null;
14069
+ const direction = vecNorm(vecSub(geometry.ptTip, geometry.ptBase));
14070
+ const arrowBaseWidth = vecMag(vecSub(geometry.shaftLeft.at(-1), geometry.shaftRight.at(-1)));
14071
+ const boltLengthRatio = Math.max(0, options.boltLengthRatio ?? DEFAULT_MOVEMENT_TO_CONTACT_OPTIONS.boltLengthRatio);
14072
+ const boltAngle = clamp(options.boltAngle ?? DEFAULT_MOVEMENT_TO_CONTACT_OPTIONS.boltAngle, 0, 90);
14073
+ const boltOptions = resolveTacticalArrowOptions(options);
14074
+ const create = (side) => createBolt({
14075
+ tip: geometry.ptTip,
14076
+ arrowDirection: direction,
14077
+ arrowBaseWidth,
14078
+ boltLengthRatio,
14079
+ boltAngle,
14080
+ side,
14081
+ options: boltOptions
14082
+ });
14083
+ return {
14084
+ geometry,
14085
+ direction,
14086
+ arrowBaseWidth,
14087
+ bolts: [create(1), create(-1)]
14088
+ };
14089
+ }
14090
+ /** Generates the Movement to Contact mission task (342900). */
14091
+ function createMovementToContact(coordinates, options = {}, textAmplifiers = {}) {
14092
+ const resolved = resolveMovementToContactGeometry(coordinates, options);
14093
+ if (!resolved) return {
14094
+ type: "FeatureCollection",
14095
+ features: []
14096
+ };
14097
+ const { geometry, bolts } = resolved;
14098
+ return {
14099
+ type: "FeatureCollection",
14100
+ features: [
14101
+ {
14102
+ type: "Feature",
14103
+ properties: { part: "body-and-bolts" },
14104
+ geometry: {
14105
+ type: "MultiLineString",
14106
+ coordinates: [supportingAttackOutline(geometry), ...bolts.map((bolt) => bolt.shaft.map((point) => unproject(point[0], point[1])))]
14107
+ }
14108
+ },
14109
+ {
14110
+ type: "Feature",
14111
+ properties: {
14112
+ part: "bolt-heads",
14113
+ fill: true,
14114
+ style: SOLID_ACCENT_FILL
14115
+ },
14116
+ geometry: {
14117
+ type: "MultiPolygon",
14118
+ coordinates: bolts.map((bolt) => [bolt.head])
14119
+ }
14120
+ },
14121
+ ...createManeuverArrowTaskAmplifierLabels(geometry, options, textAmplifiers)
14122
+ ]
14123
+ };
14124
+ }
14125
+ const MOVEMENT_TO_CONTACT = defineControlMeasure({
14126
+ metadata: MOVEMENT_TO_CONTACT_METADATA,
14127
+ generator: createMovementToContact,
14128
+ defaultOptions: DEFAULT_MOVEMENT_TO_CONTACT_OPTIONS,
14129
+ rule: axis1DrawRule,
14130
+ optionHandles: {
14131
+ get(controlPoints, options) {
14132
+ const widthHandles = variableWidthAttackOptionHandles.get(controlPoints, options);
14133
+ const resolved = resolveMovementToContactGeometry(controlPoints, options);
14134
+ if (!resolved) return widthHandles;
14135
+ return [...widthHandles, {
14136
+ id: MOVEMENT_TO_CONTACT_BOLT_HANDLE_ID,
14137
+ position: unproject(resolved.bolts[0].tip[0], resolved.bolts[0].tip[1])
14138
+ }];
14139
+ },
14140
+ drag(event) {
14141
+ if (event.handleId !== MOVEMENT_TO_CONTACT_BOLT_HANDLE_ID) return variableWidthAttackOptionHandles.drag(event);
14142
+ const resolved = resolveMovementToContactGeometry(event.controlPoints, event.options);
14143
+ if (!resolved || resolved.arrowBaseWidth < 1e-6) return void 0;
14144
+ const fromOrigin = vecSub(project(event.position[0], event.position[1]), resolved.bolts[0].origin);
14145
+ const length = vecMag(fromOrigin);
14146
+ if (length < 1e-6) return { boltLengthRatio: 0 };
14147
+ const cross = resolved.direction[0] * fromOrigin[1] - resolved.direction[1] * fromOrigin[0];
14148
+ const dot = resolved.direction[0] * fromOrigin[0] + resolved.direction[1] * fromOrigin[1];
14149
+ return {
14150
+ boltLengthRatio: length / resolved.arrowBaseWidth,
14151
+ boltAngle: clamp(Math.abs(Math.atan2(cross, dot) * 180 / Math.PI), 0, 90)
14152
+ };
14153
+ }
14154
+ },
14155
+ previewSample: {
14156
+ controlPoints: [
14157
+ [1, 0],
14158
+ [-.4, -.35],
14159
+ [-1, -.8],
14160
+ [.45, .55]
14161
+ ],
14162
+ textAmplifiers: {
14163
+ T: "DAVID",
14164
+ W: "080400Z",
14165
+ W1: "120300Z"
14166
+ },
14167
+ options: { labelSize: 80 }
14168
+ }
14169
+ });
14170
+ //#endregion
14171
+ //#region src/generators/cm34-mission-tasks/occupy.ts
14172
+ const DEFAULT_OCCUPY_OPTIONS = {
14173
+ openingAngle: 30,
14174
+ resolution: 64,
14175
+ xSizeRatio: .2,
14176
+ xAspectRatio: 1.5,
14177
+ labelPadding: .2
14178
+ };
14179
+ const OCCUPY_METADATA = {
14180
+ id: "occupy",
14181
+ name: "Occupy",
14182
+ description: "Moves a friendly force into an area so it can control that area.",
14183
+ entity: "Mission Tasks",
14184
+ entityType: "Occupy",
14185
+ value: "341700",
14186
+ minCoordinates: 2,
14187
+ maxCoordinates: 2,
14188
+ geometry: "area",
14189
+ geometryTypes: ["MultiLineString", "Point"],
14190
+ paints: {
14191
+ stroke: true,
14192
+ fill: "none",
14193
+ text: true
14194
+ },
14195
+ drawRule: "Area16",
14196
+ capturesLabelSize: true,
14197
+ params: [
14198
+ {
14199
+ key: "openingAngle",
14200
+ presentationTier: "advanced",
14201
+ label: "Opening",
14202
+ description: "Angular width of the opening on the friendly side, in degrees",
14203
+ type: "number",
14204
+ min: 5,
14205
+ max: 180,
14206
+ step: 1
14207
+ },
14208
+ ...ARC_RESOLUTION_PARAMS,
14209
+ {
14210
+ key: "xSizeRatio",
14211
+ presentationTier: "advanced",
14212
+ label: "X size",
14213
+ description: "Overall size of the terminal X as a ratio of the symbol radius",
14214
+ type: "number",
14215
+ min: .02,
14216
+ max: .75,
14217
+ step: .01
14218
+ },
14219
+ {
14220
+ key: "xAspectRatio",
14221
+ presentationTier: "advanced",
14222
+ label: "X aspect ratio",
14223
+ description: "Width-to-height ratio of the terminal X",
14224
+ type: "number",
14225
+ min: .25,
14226
+ max: 4,
14227
+ step: .05
14228
+ },
14229
+ {
14230
+ key: "labelPadding",
14231
+ presentationTier: "advanced",
14232
+ label: "O padding",
14233
+ description: "Extra boundary clearance on each side of O, as a ratio of its height",
14234
+ type: "number",
14235
+ min: 0,
14236
+ max: 2,
14237
+ step: .05
14238
+ }
14239
+ ]
14240
+ };
14241
+ const TAU$1 = Math.PI * 2;
14242
+ const MIN_LABEL_GAP_METERS$1 = 1;
14243
+ /** Generates the Occupy mission task symbol (341700). */
14244
+ function createOccupySymbol(coordinates, options = {}) {
14245
+ const center = project(coordinates[0][0], coordinates[0][1]);
14246
+ const radiusVector = vecSub(project(coordinates[1][0], coordinates[1][1]), center);
14247
+ const radius = vecMag(radiusVector);
14248
+ if (radius < 1e-6) return {
14249
+ type: "FeatureCollection",
14250
+ features: []
14251
+ };
14252
+ const resolved = {
14253
+ ...DEFAULT_OCCUPY_OPTIONS,
14254
+ ...options
14255
+ };
14256
+ const bulgeVector = [radiusVector[1], -radiusVector[0]];
14257
+ const halfTurnResolution = normalizeArcResolution(resolved.resolution) / 2;
14258
+ const boundarySpan = TAU$1 - Math.min(180, Math.max(5, resolved.openingAngle)) * Math.PI / 180;
14259
+ const labelSweep = boundarySpan / 2;
14260
+ const labelHeight = resolveLabelOffsetMeters(options, 1, radius * .12);
14261
+ const labelPadding = Math.max(0, resolved.labelPadding);
14262
+ const labelHalfGap = Math.min(boundarySpan / 4, Math.max(labelHeight * (LABEL_HALF_GLYPH_RATIO + labelPadding), MIN_LABEL_GAP_METERS$1) / radius);
14263
+ const sampleArc = (from, to) => sampleProjectedArc(center, radiusVector, bulgeVector, from, to, halfTurnResolution);
14264
+ const lines = [sampleArc(0, labelSweep - labelHalfGap), sampleArc(labelSweep + labelHalfGap, boundarySpan)];
14265
+ const terminal = arcPointAt(center, radiusVector, bulgeVector, boundarySpan);
14266
+ const radial = vecScale(vecSub(terminal, center), 1 / radius);
14267
+ const tangent = [radial[1], -radial[0]];
14268
+ const halfHeight = radius * Math.max(0, resolved.xSizeRatio) / 2;
14269
+ const radialHalfWidth = vecScale(radial, halfHeight * Math.max(.25, resolved.xAspectRatio));
14270
+ const tangentHalfHeight = vecScale(tangent, halfHeight);
14271
+ const stroke = (diagonal) => [vecAdd(terminal, diagonal), vecSub(terminal, diagonal)].map((point) => unproject(point[0], point[1]));
14272
+ lines.push(stroke(vecAdd(radialHalfWidth, tangentHalfHeight)), stroke(vecSub(radialHalfWidth, tangentHalfHeight)));
14273
+ const labelPoint = arcPointAt(center, radiusVector, bulgeVector, labelSweep);
14274
+ return {
14275
+ type: "FeatureCollection",
14276
+ features: [{
14277
+ type: "Feature",
14278
+ properties: { part: "occupy" },
14279
+ geometry: {
14280
+ type: "MultiLineString",
14281
+ coordinates: lines
14282
+ }
14283
+ }, createLabelFeature(labelPoint, "O", Math.PI, options)]
14284
+ };
14285
+ }
14286
+ const OCCUPY = defineControlMeasure({
14287
+ metadata: OCCUPY_METADATA,
14288
+ generator: createOccupySymbol,
14289
+ defaultOptions: DEFAULT_OCCUPY_OPTIONS,
14290
+ rule: occupyDrawRule,
14291
+ previewSample: {
14292
+ controlPoints: [[0, 0], [-.8, .2]],
14293
+ options: { labelSize: 90 }
14294
+ }
14295
+ });
14296
+ //#endregion
14015
14297
  //#region src/generators/cm34-mission-tasks/penetrate.ts
14016
14298
  const DEFAULT_PENETRATE_OPTIONS = {
14017
14299
  arrowheadLengthRatio: .15,
@@ -14130,6 +14412,155 @@ const PENETRATE = defineControlMeasure({
14130
14412
  ] }
14131
14413
  });
14132
14414
  //#endregion
14415
+ //#region src/generators/cm34-mission-tasks/pursuit.ts
14416
+ const DEFAULT_PURSUIT_OPTIONS = {
14417
+ arrowheadLengthRatio: .1,
14418
+ arrowheadWidthRatio: .12,
14419
+ terminalBarLengthRatio: 1.5,
14420
+ labelPosition: .6,
14421
+ labelGapRatio: .18,
14422
+ resolution: 64
14423
+ };
14424
+ const PURSUIT_METADATA = {
14425
+ id: "pursuit",
14426
+ name: "Pursuit",
14427
+ description: "A mission task to catch or cut off a hostile force attempting to escape, with the aim of destroying it.",
14428
+ entity: "Mission Tasks",
14429
+ entityType: "Pursuit",
14430
+ value: "344000",
14431
+ minCoordinates: 3,
14432
+ maxCoordinates: 3,
14433
+ geometry: "line",
14434
+ geometryTypes: ["MultiLineString", "Point"],
14435
+ paints: {
14436
+ stroke: true,
14437
+ fill: "none",
14438
+ text: true
14439
+ },
14440
+ drawRule: "Line33",
14441
+ capturesLabelSize: true,
14442
+ params: [
14443
+ {
14444
+ key: "arrowheadLengthRatio",
14445
+ presentationTier: "advanced",
14446
+ label: "Arrowhead length",
14447
+ description: "Arrowhead length as a ratio of the arc diameter",
14448
+ type: "number",
14449
+ min: .01,
14450
+ max: .5,
14451
+ step: .01
14452
+ },
14453
+ {
14454
+ key: "arrowheadWidthRatio",
14455
+ presentationTier: "advanced",
14456
+ label: "Arrowhead width",
14457
+ description: "Arrowhead width as a ratio of the arc diameter",
14458
+ type: "number",
14459
+ min: .01,
14460
+ max: .5,
14461
+ step: .01
14462
+ },
14463
+ {
14464
+ key: "terminalBarLengthRatio",
14465
+ presentationTier: "advanced",
14466
+ label: "Terminal bar length",
14467
+ description: "Terminal bar length as a multiple of the arrowhead width",
14468
+ type: "number",
14469
+ min: .1,
14470
+ max: 10,
14471
+ step: .1
14472
+ },
14473
+ {
14474
+ key: "labelPosition",
14475
+ presentationTier: "advanced",
14476
+ label: "Label position",
14477
+ description: "Position of the P label along the straight portion",
14478
+ type: "number",
14479
+ min: 0,
14480
+ max: 1,
14481
+ step: .01
14482
+ },
14483
+ {
14484
+ key: "labelGapRatio",
14485
+ presentationTier: "advanced",
14486
+ label: "Label gap",
14487
+ description: "Size of the straight-line cutout holding the P label",
14488
+ type: "number",
14489
+ min: 0,
14490
+ max: 1,
14491
+ step: .01
14492
+ },
14493
+ ...ARC_RESOLUTION_PARAMS
14494
+ ]
14495
+ };
14496
+ /** Generates the Pursuit mission task symbol (344000). */
14497
+ function createPursuit(coordinates, options = {}) {
14498
+ const [c1, c2, c3] = coordinates;
14499
+ const p1 = project(c1[0], c1[1]);
14500
+ const p2 = project(c2[0], c2[1]);
14501
+ const rawP3 = project(c3[0], c3[1]);
14502
+ const frame = createProjectedBaselineFrame(p1, p2, {
14503
+ origin: "p2",
14504
+ normal: "right"
14505
+ });
14506
+ if (!frame) return {
14507
+ type: "FeatureCollection",
14508
+ features: []
14509
+ };
14510
+ const { direction: straightDirection, normal: diameterAxis, length: straightLength } = frame;
14511
+ const signedDiameter = vecDot(vecSub(rawP3, frame.origin), diameterAxis);
14512
+ const diameter = Math.abs(signedDiameter);
14513
+ if (diameter < 1e-6) return {
14514
+ type: "FeatureCollection",
14515
+ features: []
14516
+ };
14517
+ const p3 = vecAdd(p2, vecScale(diameterAxis, signedDiameter));
14518
+ const labelPoint = vecAdd(p1, vecScale(straightDirection, straightLength * clamp01(options.labelPosition ?? DEFAULT_PURSUIT_OPTIONS.labelPosition)));
14519
+ const gapHalf = straightLength * clamp01(options.labelGapRatio ?? DEFAULT_PURSUIT_OPTIONS.labelGapRatio) / 2;
14520
+ const gapStart = vecAdd(labelPoint, vecScale(straightDirection, -gapHalf));
14521
+ const gapEnd = vecAdd(labelPoint, vecScale(straightDirection, gapHalf));
14522
+ const center = midpoint(p2, p3);
14523
+ const arc = sampleProjectedArc(center, vecSub(p2, center), vecScale(straightDirection, diameter / 2), 0, Math.PI, normalizeArcResolution(options.resolution) / 2);
14524
+ const arrowheadLength = diameter * Math.max(0, options.arrowheadLengthRatio ?? DEFAULT_PURSUIT_OPTIONS.arrowheadLengthRatio);
14525
+ const arrowheadWidth = diameter * Math.max(0, options.arrowheadWidthRatio ?? DEFAULT_PURSUIT_OPTIONS.arrowheadWidthRatio);
14526
+ const arrowhead = createArrowHead$1(p3, vecScale(straightDirection, -1), arrowheadLength, arrowheadWidth);
14527
+ const terminalBarHalf = arrowheadWidth * Math.max(0, options.terminalBarLengthRatio ?? DEFAULT_PURSUIT_OPTIONS.terminalBarLengthRatio) / 2;
14528
+ const terminalStart = vecAdd(p3, vecScale(diameterAxis, terminalBarHalf));
14529
+ const terminalEnd = vecSub(p3, vecScale(diameterAxis, terminalBarHalf));
14530
+ const terminalBar = [unproject(terminalStart[0], terminalStart[1]), unproject(terminalEnd[0], terminalEnd[1])];
14531
+ return {
14532
+ type: "FeatureCollection",
14533
+ features: [{
14534
+ type: "Feature",
14535
+ properties: { part: "pursuit" },
14536
+ geometry: {
14537
+ type: "MultiLineString",
14538
+ coordinates: [
14539
+ [c1, unproject(gapStart[0], gapStart[1])],
14540
+ [unproject(gapEnd[0], gapEnd[1]), c2],
14541
+ arc,
14542
+ arrowhead,
14543
+ terminalBar
14544
+ ]
14545
+ }
14546
+ }, createLabelFeature(labelPoint, "P", labelRotationAlong(straightDirection), options)]
14547
+ };
14548
+ }
14549
+ const PURSUIT = defineControlMeasure({
14550
+ metadata: PURSUIT_METADATA,
14551
+ generator: createPursuit,
14552
+ defaultOptions: DEFAULT_PURSUIT_OPTIONS,
14553
+ rule: line33DrawRule,
14554
+ previewSample: {
14555
+ controlPoints: [
14556
+ [-1, .4],
14557
+ [1, .4],
14558
+ [0, -.6]
14559
+ ],
14560
+ options: { labelSize: 100 }
14561
+ }
14562
+ });
14563
+ //#endregion
14133
14564
  //#region src/generators/cm34-mission-tasks/rearward-passage-of-lines.ts
14134
14565
  const DEFAULT_REARWARD_PASSAGE_OF_LINES_OPTIONS = DEFAULT_DELAY_OPTIONS;
14135
14566
  const REARWARD_PASSAGE_OF_LINES_METADATA = {
@@ -14262,6 +14693,58 @@ const DEFAULT_SEIZE_OPTIONS = {
14262
14693
  curveSegments: 48,
14263
14694
  circleSegments: 64
14264
14695
  };
14696
+ const SEIZE_PARAMS = [
14697
+ {
14698
+ key: "arrowheadLengthRatio",
14699
+ presentationTier: "advanced",
14700
+ label: "Arrowhead length",
14701
+ description: "Arrowhead length as a ratio of the PT. 2-to-PT. 4 span",
14702
+ type: "number",
14703
+ min: .01,
14704
+ max: .5,
14705
+ step: .01
14706
+ },
14707
+ {
14708
+ key: "arrowheadWidthRatio",
14709
+ presentationTier: "advanced",
14710
+ label: "Arrowhead width",
14711
+ description: "Arrowhead base width as a ratio of the PT. 2-to-PT. 4 span",
14712
+ type: "number",
14713
+ min: .01,
14714
+ max: .5,
14715
+ step: .01
14716
+ },
14717
+ {
14718
+ key: "labelGapRatio",
14719
+ presentationTier: "advanced",
14720
+ label: "Label gap",
14721
+ description: "Size of the curve gap holding the S label",
14722
+ type: "number",
14723
+ min: 0,
14724
+ max: .5,
14725
+ step: .01
14726
+ },
14727
+ {
14728
+ key: "curveSegments",
14729
+ presentationTier: "advanced",
14730
+ label: "Curve segments",
14731
+ description: "Number of straight segments used to approximate the circular arc",
14732
+ type: "number",
14733
+ min: 4,
14734
+ max: 128,
14735
+ step: 1
14736
+ },
14737
+ {
14738
+ key: "circleSegments",
14739
+ presentationTier: "advanced",
14740
+ label: "Circle segments",
14741
+ description: "Number of straight segments used to approximate the assigned-unit circle",
14742
+ type: "number",
14743
+ min: 12,
14744
+ max: 128,
14745
+ step: 1
14746
+ }
14747
+ ];
14265
14748
  const SEIZE_METADATA = {
14266
14749
  id: "seize",
14267
14750
  name: "Seize",
@@ -14280,58 +14763,7 @@ const SEIZE_METADATA = {
14280
14763
  },
14281
14764
  drawRule: "Line27",
14282
14765
  capturesLabelSize: true,
14283
- params: [
14284
- {
14285
- key: "arrowheadLengthRatio",
14286
- presentationTier: "advanced",
14287
- label: "Arrowhead length",
14288
- description: "Arrowhead length as a ratio of the PT. 2-to-PT. 4 span",
14289
- type: "number",
14290
- min: .01,
14291
- max: .5,
14292
- step: .01
14293
- },
14294
- {
14295
- key: "arrowheadWidthRatio",
14296
- presentationTier: "advanced",
14297
- label: "Arrowhead width",
14298
- description: "Arrowhead base width as a ratio of the PT. 2-to-PT. 4 span",
14299
- type: "number",
14300
- min: .01,
14301
- max: .5,
14302
- step: .01
14303
- },
14304
- {
14305
- key: "labelGapRatio",
14306
- presentationTier: "advanced",
14307
- label: "Label gap",
14308
- description: "Size of the curve gap holding the S label",
14309
- type: "number",
14310
- min: 0,
14311
- max: .5,
14312
- step: .01
14313
- },
14314
- {
14315
- key: "curveSegments",
14316
- presentationTier: "advanced",
14317
- label: "Curve segments",
14318
- description: "Number of straight segments used to approximate the circular arc",
14319
- type: "number",
14320
- min: 4,
14321
- max: 128,
14322
- step: 1
14323
- },
14324
- {
14325
- key: "circleSegments",
14326
- presentationTier: "advanced",
14327
- label: "Circle segments",
14328
- description: "Number of straight segments used to approximate the assigned-unit circle",
14329
- type: "number",
14330
- min: 12,
14331
- max: 128,
14332
- step: 1
14333
- }
14334
- ]
14766
+ params: SEIZE_PARAMS
14335
14767
  };
14336
14768
  function positiveAngle(angle) {
14337
14769
  const normalized = angle % (Math.PI * 2);
@@ -14361,8 +14793,8 @@ function threePointArc(start, middle, end) {
14361
14793
  sweepAngle: positiveAngle(middleAngle - startAngle) <= counterclockwiseSweep ? counterclockwiseSweep : counterclockwiseSweep - Math.PI * 2
14362
14794
  };
14363
14795
  }
14364
- /** Generates the Seize mission task symbol (342300). */
14365
- function createSeizeSymbol(coordinates, options = {}) {
14796
+ /** Shared geometry for mission tasks that use the Seize graphic. */
14797
+ function createSeizeLikeSymbol(coordinates, options = {}, label = "S", part = "seize") {
14366
14798
  const [c1, c2, c3, c4] = coordinates;
14367
14799
  const center = project(c1[0], c1[1]);
14368
14800
  const start = project(c2[0], c2[1]);
@@ -14423,23 +14855,28 @@ function createSeizeSymbol(coordinates, options = {}) {
14423
14855
  const angle = startAngle + index / circleSegments * Math.PI * 2;
14424
14856
  circle.push(toPosition(vecAdd(center, [radius * Math.cos(angle), radius * Math.sin(angle)])));
14425
14857
  }
14858
+ const lines = [
14859
+ circle,
14860
+ firstArcPart,
14861
+ secondArcPart,
14862
+ createArrowHead$1(tip, arrowDirection, span * Math.max(0, options.arrowheadLengthRatio ?? DEFAULT_SEIZE_OPTIONS.arrowheadLengthRatio), span * Math.max(0, options.arrowheadWidthRatio ?? DEFAULT_SEIZE_OPTIONS.arrowheadWidthRatio))
14863
+ ];
14426
14864
  return {
14427
14865
  type: "FeatureCollection",
14428
14866
  features: [{
14429
14867
  type: "Feature",
14430
- properties: { part: "seize" },
14868
+ properties: { part },
14431
14869
  geometry: {
14432
14870
  type: "MultiLineString",
14433
- coordinates: [
14434
- circle,
14435
- firstArcPart,
14436
- secondArcPart,
14437
- createArrowHead$1(tip, arrowDirection, span * Math.max(0, options.arrowheadLengthRatio ?? DEFAULT_SEIZE_OPTIONS.arrowheadLengthRatio), span * Math.max(0, options.arrowheadWidthRatio ?? DEFAULT_SEIZE_OPTIONS.arrowheadWidthRatio))
14438
- ]
14871
+ coordinates: lines
14439
14872
  }
14440
- }, createLabelFeature(labelPoint, "S", labelRotationAlong(tangentAtLabel), options)]
14873
+ }, createLabelFeature(labelPoint, label, labelRotationAlong(tangentAtLabel), options)]
14441
14874
  };
14442
14875
  }
14876
+ /** Generates the Seize mission task symbol (342300). */
14877
+ function createSeizeSymbol(coordinates, options = {}) {
14878
+ return createSeizeLikeSymbol(coordinates, options);
14879
+ }
14443
14880
  const SEIZE = defineControlMeasure({
14444
14881
  metadata: SEIZE_METADATA,
14445
14882
  generator: createSeizeSymbol,
@@ -14456,6 +14893,48 @@ const SEIZE = defineControlMeasure({
14456
14893
  }
14457
14894
  });
14458
14895
  //#endregion
14896
+ //#region src/generators/cm34-mission-tasks/evacuate.ts
14897
+ const DEFAULT_EVACUATE_OPTIONS = DEFAULT_SEIZE_OPTIONS;
14898
+ const EVACUATE_METADATA = {
14899
+ id: "evacuate",
14900
+ name: "Evacuate",
14901
+ description: "Removes personnel or materiel from a threatened area.",
14902
+ entity: "Mission Tasks",
14903
+ entityType: "Evacuation",
14904
+ value: "344500",
14905
+ minCoordinates: 4,
14906
+ maxCoordinates: 4,
14907
+ geometry: "line",
14908
+ geometryTypes: ["MultiLineString", "Point"],
14909
+ paints: {
14910
+ stroke: true,
14911
+ fill: "none",
14912
+ text: true
14913
+ },
14914
+ drawRule: "Line27",
14915
+ capturesLabelSize: true,
14916
+ params: SEIZE_PARAMS
14917
+ };
14918
+ /** Generates the Evacuate mission task symbol (344500). */
14919
+ function createEvacuateSymbol(coordinates, options = {}) {
14920
+ return createSeizeLikeSymbol(coordinates, options, "E", "evacuate");
14921
+ }
14922
+ const EVACUATE = defineControlMeasure({
14923
+ metadata: EVACUATE_METADATA,
14924
+ generator: createEvacuateSymbol,
14925
+ defaultOptions: DEFAULT_EVACUATE_OPTIONS,
14926
+ rule: line27DrawRule,
14927
+ previewSample: {
14928
+ controlPoints: [
14929
+ [-.8, .35],
14930
+ [-.45, .35],
14931
+ [.15, .25],
14932
+ [.85, -.55]
14933
+ ],
14934
+ options: { labelSize: 100 }
14935
+ }
14936
+ });
14937
+ //#endregion
14459
14938
  //#region src/generators/cm34-mission-tasks/withdraw.ts
14460
14939
  const DEFAULT_WITHDRAW_OPTIONS = DEFAULT_DELAY_OPTIONS;
14461
14940
  const WITHDRAW_METADATA = {
@@ -14534,6 +15013,216 @@ const WITHDRAW_UNDER_PRESSURE = defineControlMeasure({
14534
15013
  }
14535
15014
  });
14536
15015
  //#endregion
15016
+ //#region src/generators/cm27-protection-areas/turn.ts
15017
+ const DEFAULT_TURN_OPTIONS = {
15018
+ arrowheadLengthRatio: .16,
15019
+ arrowheadWidthRatio: .16,
15020
+ arcSegments: 64
15021
+ };
15022
+ const TURN_PARAMS = [
15023
+ {
15024
+ key: "arrowheadLengthRatio",
15025
+ presentationTier: "advanced",
15026
+ label: "Arrowhead length",
15027
+ description: "Arrowhead length as a ratio of the rear-to-tip distance",
15028
+ type: "number",
15029
+ min: .01,
15030
+ max: .5,
15031
+ step: .01
15032
+ },
15033
+ {
15034
+ key: "arrowheadWidthRatio",
15035
+ presentationTier: "advanced",
15036
+ label: "Arrowhead width",
15037
+ description: "Arrowhead base width as a ratio of the rear-to-tip distance",
15038
+ type: "number",
15039
+ min: .01,
15040
+ max: .5,
15041
+ step: .01
15042
+ },
15043
+ {
15044
+ key: "arcSegments",
15045
+ presentationTier: "advanced",
15046
+ label: "Arc segments",
15047
+ description: "Number of line segments approximating the quarter-circle shaft.",
15048
+ type: "number",
15049
+ min: 4,
15050
+ max: 128,
15051
+ step: 1
15052
+ }
15053
+ ];
15054
+ const TURN_METADATA = {
15055
+ id: "turn",
15056
+ name: "Turn",
15057
+ 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.",
15058
+ entity: "Protection Areas",
15059
+ entityType: "Obstacle Effects",
15060
+ entitySubtype: "Turn",
15061
+ value: "270504",
15062
+ minCoordinates: 2,
15063
+ geometry: "line",
15064
+ geometryTypes: ["MultiLineString", "MultiPolygon"],
15065
+ paints: {
15066
+ stroke: true,
15067
+ fill: "fixed",
15068
+ text: false
15069
+ },
15070
+ drawRule: "Line10",
15071
+ params: TURN_PARAMS
15072
+ };
15073
+ /**
15074
+ * Shared geometry for the Turn graphic: a quarter-circle shaft from PT2
15075
+ * (rear) to the arrowhead base, with the arrowhead continuing tangent to the
15076
+ * arc and placing its tip at PT1. PT3 selects which side of the PT2 -> PT1
15077
+ * chord contains the arc.
15078
+ *
15079
+ * `labelGapRatio` opens a gap centered on the arc midpoint, sized as a ratio
15080
+ * of the arc sweep, for measures that carry a doctrinal glyph there.
15081
+ */
15082
+ function createTurnLikeSymbol(coordinates, options = {}, labelGapRatio = 0) {
15083
+ const tip = project(coordinates[0][0], coordinates[0][1]);
15084
+ const rear = project(coordinates[1][0], coordinates[1][1]);
15085
+ const side = quarterArcSide(rear, tip, coordinates[2] ? project(coordinates[2][0], coordinates[2][1]) : void 0);
15086
+ const rearToTip = vecSub(tip, rear);
15087
+ const totalLength = vecMag(rearToTip);
15088
+ if (totalLength < 1e-6) return { features: [] };
15089
+ const requestedHeadLength = totalLength * (options.arrowheadLengthRatio ?? DEFAULT_TURN_OPTIONS.arrowheadLengthRatio);
15090
+ const tangentComponent = Math.min(Math.max(requestedHeadLength, EPSILON), totalLength * .95) / Math.SQRT2;
15091
+ const arcChordLength = Math.sqrt(totalLength * totalLength - tangentComponent * tangentComponent) - tangentComponent;
15092
+ const chordOffset = Math.atan2(tangentComponent, arcChordLength + tangentComponent);
15093
+ const headBase = vecAdd(rear, vecScale(rotate(vecNorm(rearToTip), side * chordOffset), arcChordLength));
15094
+ const arc = createQuarterArc(rear, headBase, side);
15095
+ if (!arc) return { features: [] };
15096
+ const segmentCount = Math.round(Math.min(128, Math.max(4, options.arcSegments ?? DEFAULT_TURN_OPTIONS.arcSegments)));
15097
+ const sampleArc = (from, to) => {
15098
+ const count = Math.max(1, Math.ceil(segmentCount * (to - from)));
15099
+ const points = [];
15100
+ for (let i = 0; i <= count; i++) {
15101
+ const progress = from + (to - from) * i / count;
15102
+ const angle = arc.startAngle + arc.sweepAngle * progress;
15103
+ points.push(unproject(arc.center[0] + arc.radius * Math.cos(angle), arc.center[1] + arc.radius * Math.sin(angle)));
15104
+ }
15105
+ return points;
15106
+ };
15107
+ const gapHalf = clamp01(labelGapRatio) / 2;
15108
+ const arcParts = gapHalf > 0 ? [sampleArc(0, .5 - gapHalf), sampleArc(.5 + gapHalf, 1)] : [sampleArc(0, 1)];
15109
+ const sweepSign = Math.sign(arc.sweepAngle);
15110
+ const midAngle = arc.startAngle + arc.sweepAngle / 2;
15111
+ const labelAnchor = {
15112
+ point: arc.midpoint,
15113
+ tangent: [-Math.sin(midAngle) * sweepSign, Math.cos(midAngle) * sweepSign]
15114
+ };
15115
+ const headWidth = totalLength * (options.arrowheadWidthRatio ?? DEFAULT_TURN_OPTIONS.arrowheadWidthRatio);
15116
+ const arrowHead = createArrowHeadRing(tip, vecNorm(vecSub(tip, headBase)), vecMag(vecSub(tip, headBase)), headWidth);
15117
+ return {
15118
+ features: [{
15119
+ type: "Feature",
15120
+ properties: { part: "arc" },
15121
+ geometry: {
15122
+ type: "MultiLineString",
15123
+ coordinates: arcParts
15124
+ }
15125
+ }, {
15126
+ type: "Feature",
15127
+ properties: {
15128
+ part: "arrowhead",
15129
+ fill: true,
15130
+ style: SOLID_ACCENT_FILL
15131
+ },
15132
+ geometry: {
15133
+ type: "MultiPolygon",
15134
+ coordinates: [[[...arrowHead]]]
15135
+ }
15136
+ }],
15137
+ labelAnchor
15138
+ };
15139
+ }
15140
+ /**
15141
+ * Generates a quarter-circle shaft from PT2 (rear) to the arrowhead base.
15142
+ * The arrowhead continues tangent to the arc and places its tip at PT1.
15143
+ * PT3 selects which side of the PT2 -> PT1 chord contains the arc.
15144
+ */
15145
+ function createTurn(coordinates, options = {}) {
15146
+ return {
15147
+ type: "FeatureCollection",
15148
+ features: createTurnLikeSymbol(coordinates, options).features
15149
+ };
15150
+ }
15151
+ function rotate(vector, angle) {
15152
+ const cos = Math.cos(angle);
15153
+ const sin = Math.sin(angle);
15154
+ return [vector[0] * cos - vector[1] * sin, vector[0] * sin + vector[1] * cos];
15155
+ }
15156
+ const TURN = defineControlMeasure({
15157
+ metadata: TURN_METADATA,
15158
+ generator: createTurn,
15159
+ defaultOptions: DEFAULT_TURN_OPTIONS,
15160
+ rule: turnDrawRule
15161
+ });
15162
+ //#endregion
15163
+ //#region src/generators/cm34-mission-tasks/turn.ts
15164
+ const DEFAULT_TURN_MISSION_TASK_OPTIONS = {
15165
+ ...DEFAULT_TURN_OPTIONS,
15166
+ arrowheadLengthRatio: .14,
15167
+ arrowheadWidthRatio: .14,
15168
+ labelGapRatio: .18
15169
+ };
15170
+ const TURN_MISSION_TASK_METADATA = {
15171
+ id: "turn-mission-task",
15172
+ name: "Turn",
15173
+ description: "Causes an enemy force to move from one avenue of approach to another or into an engagement area.",
15174
+ entity: "Mission Tasks",
15175
+ entityType: "Turn",
15176
+ value: "344700",
15177
+ minCoordinates: 2,
15178
+ geometry: "line",
15179
+ geometryTypes: [
15180
+ "MultiLineString",
15181
+ "MultiPolygon",
15182
+ "Point"
15183
+ ],
15184
+ paints: {
15185
+ stroke: true,
15186
+ fill: "fixed",
15187
+ text: true
15188
+ },
15189
+ drawRule: "Line10",
15190
+ capturesLabelSize: true,
15191
+ params: [...TURN_PARAMS, {
15192
+ key: "labelGapRatio",
15193
+ presentationTier: "advanced",
15194
+ label: "Label gap",
15195
+ description: "Size of the arc gap holding the doctrinal 'T' label",
15196
+ type: "number",
15197
+ min: 0,
15198
+ max: 1,
15199
+ step: .01
15200
+ }]
15201
+ };
15202
+ /** Generates the TURN mission task symbol (344700). */
15203
+ function createTurnMissionTaskSymbol(coordinates, options = {}) {
15204
+ const { features, labelAnchor } = createTurnLikeSymbol(coordinates, {
15205
+ arrowheadLengthRatio: options.arrowheadLengthRatio ?? DEFAULT_TURN_MISSION_TASK_OPTIONS.arrowheadLengthRatio,
15206
+ arrowheadWidthRatio: options.arrowheadWidthRatio ?? DEFAULT_TURN_MISSION_TASK_OPTIONS.arrowheadWidthRatio,
15207
+ arcSegments: options.arcSegments ?? DEFAULT_TURN_MISSION_TASK_OPTIONS.arcSegments
15208
+ }, options.labelGapRatio ?? DEFAULT_TURN_MISSION_TASK_OPTIONS.labelGapRatio);
15209
+ if (!labelAnchor) return {
15210
+ type: "FeatureCollection",
15211
+ features: []
15212
+ };
15213
+ const label = createLabelFeature(labelAnchor.point, "T", labelRotationAlong(labelAnchor.tangent), options);
15214
+ return {
15215
+ type: "FeatureCollection",
15216
+ features: [...features, label]
15217
+ };
15218
+ }
15219
+ const TURN_MISSION_TASK = defineControlMeasure({
15220
+ metadata: TURN_MISSION_TASK_METADATA,
15221
+ generator: createTurnMissionTaskSymbol,
15222
+ defaultOptions: DEFAULT_TURN_MISSION_TASK_OPTIONS,
15223
+ rule: turnDrawRule
15224
+ });
15225
+ //#endregion
14537
15226
  //#region src/generators/cm15-maneuver-areas/jointTacticalActionArea.ts
14538
15227
  /** Default options for the Joint tactical action area control measure. */
14539
15228
  const DEFAULT_JOINT_TACTICAL_ACTION_AREA_OPTIONS = {
@@ -14898,7 +15587,8 @@ const DEFAULT_MAIN_ATTACK_OPTIONS = {
14898
15587
  shaftWidthRatio: DEFAULT_SHAFT_WIDTH_RATIO$1,
14899
15588
  rearWidthRatio: DEFAULT_REAR_WIDTH_RATIO,
14900
15589
  smooth: false,
14901
- smoothResolution: 5
15590
+ smoothResolution: 5,
15591
+ ...DEFAULT_MANEUVER_ARROW_TASK_AMPLIFIER_OPTIONS
14902
15592
  };
14903
15593
  const MAIN_ATTACK_METADATA = {
14904
15594
  id: "main-attack",
@@ -14910,14 +15600,16 @@ const MAIN_ATTACK_METADATA = {
14910
15600
  value: "151403",
14911
15601
  minCoordinates: 3,
14912
15602
  geometry: "line",
14913
- geometryTypes: ["MultiLineString"],
15603
+ geometryTypes: ["MultiLineString", "Point"],
14914
15604
  paints: {
14915
15605
  stroke: true,
14916
15606
  fill: "none",
14917
- text: false
15607
+ text: true
14918
15608
  },
14919
15609
  drawRule: "Axis1",
14920
- params: ATTACK_SHAFT_PARAMS
15610
+ capturesLabelSize: true,
15611
+ params: [...ATTACK_SHAFT_PARAMS, ...MANEUVER_ARROW_TASK_AMPLIFIER_PARAMS],
15612
+ textAmplifiers: MANEUVER_ARROW_TASK_TEXT_AMPLIFIERS
14921
15613
  };
14922
15614
  /**
14923
15615
  * Generates a GeoJSON FeatureCollection for a Main Attack tactical symbol.
@@ -14937,7 +15629,7 @@ const MAIN_ATTACK_METADATA = {
14937
15629
  * @param options - Configuration for ratios and properties.
14938
15630
  * @returns A GeoJSON FeatureCollection containing a single MultiLineString.
14939
15631
  */
14940
- function createMainAttack(coordinates, options = {}) {
15632
+ function createMainAttack(coordinates, options = {}, textAmplifiers = {}) {
14941
15633
  const { geometry } = processAttackGeometry(coordinates, options);
14942
15634
  if (!geometry) return {
14943
15635
  type: "FeatureCollection",
@@ -14956,7 +15648,7 @@ function createMainAttack(coordinates, options = {}) {
14956
15648
  geometry.headRing.map((p) => unproject(p[0], p[1]))
14957
15649
  ]
14958
15650
  }
14959
- }]
15651
+ }, ...createManeuverArrowTaskAmplifierLabels(geometry, options, textAmplifiers)]
14960
15652
  };
14961
15653
  }
14962
15654
  const MAIN_ATTACK = defineControlMeasure({
@@ -14964,7 +15656,21 @@ const MAIN_ATTACK = defineControlMeasure({
14964
15656
  generator: createMainAttack,
14965
15657
  defaultOptions: DEFAULT_MAIN_ATTACK_OPTIONS,
14966
15658
  rule: axis1DrawRule,
14967
- optionHandles: variableWidthAttackOptionHandles
15659
+ optionHandles: variableWidthAttackOptionHandles,
15660
+ previewSample: {
15661
+ controlPoints: [
15662
+ [1, 0],
15663
+ [-.4, -.35],
15664
+ [-1, -.8],
15665
+ [.45, .55]
15666
+ ],
15667
+ textAmplifiers: {
15668
+ T: "DAVID",
15669
+ W: "080400Z",
15670
+ W1: "120300Z"
15671
+ },
15672
+ options: { labelSize: 80 }
15673
+ }
14968
15674
  });
14969
15675
  //#endregion
14970
15676
  //#region src/generators/cm27-protection-areas/mine-types.ts
@@ -16303,6 +17009,113 @@ const SECONDARY_DIRECTION_OF_FIRE = defineControlMeasure({
16303
17009
  ] }
16304
17010
  });
16305
17011
  //#endregion
17012
+ //#region src/generators/cm15-maneuver-areas/searchArea.ts
17013
+ const SEARCH_AREA_METADATA = {
17014
+ id: "search-area",
17015
+ name: "Search Area",
17016
+ description: "Designates an area to examine systematically for specified persons, forces, materiel, or information.",
17017
+ entity: "Maneuver Areas",
17018
+ entityType: "Search Area",
17019
+ value: "152200",
17020
+ minCoordinates: 3,
17021
+ maxCoordinates: 3,
17022
+ geometry: "area",
17023
+ geometryTypes: ["MultiLineString", "MultiPolygon"],
17024
+ paints: {
17025
+ stroke: true,
17026
+ fill: "fixed",
17027
+ text: false
17028
+ },
17029
+ drawRule: "Area21",
17030
+ params: TACTICAL_ARROW_PARAMS
17031
+ };
17032
+ /**
17033
+ * Generates a GeoJSON FeatureCollection of a tactical "lightning" arrow.
17034
+ * Handles internal projection to maintain correct perpendicularity.
17035
+ *
17036
+ * @param coordinates - [Apex Point (PT1), Top Tail (PT2), Bottom Tail (PT3)]
17037
+ * @param options - Geometric configuration parameters
17038
+ * @returns A FeatureCollection containing a MultiLineString for bolts and a MultiPolygon for heads
17039
+ */
17040
+ function createSearchArea(coordinates, options = {}) {
17041
+ const { arrowheadLengthRatio: safeArrowheadLengthRatio, arrowheadWidthRatio: safeArrowheadWidthRatio, zigzagStart: safeZigzagStart, zigzagEnd: safeZigzagEnd, zigzagAmplitudeRatio: safeZigzagAmplitudeRatio } = resolveTacticalArrowOptions(options);
17042
+ const [c0, c1, c2] = coordinates;
17043
+ const [p1, p2, p3] = [
17044
+ project(c0[0], c0[1]),
17045
+ project(c1[0], c1[1]),
17046
+ project(c2[0], c2[1])
17047
+ ];
17048
+ const shaftSegments = [];
17049
+ const headPolygons = [];
17050
+ const addLeg = (origin, tip, opposite) => {
17051
+ const v = vecSub(tip, origin);
17052
+ const totalLen = vecMag(v);
17053
+ if (totalLen < 1e-6) return;
17054
+ const u = vecNorm(v);
17055
+ let n = [-u[1], u[0]];
17056
+ const toOpp = vecSub(opposite, origin);
17057
+ if (vecDot(n, toOpp) > 0) n = vecScale(n, -1);
17058
+ const offset = totalLen * safeZigzagAmplitudeRatio;
17059
+ const ptA = vecAdd(vecAdd(origin, vecScale(v, safeZigzagStart)), vecScale(n, offset));
17060
+ const ptB = vecAdd(vecAdd(origin, vecScale(v, safeZigzagEnd)), vecScale(n, -offset));
17061
+ const fv = vecSub(tip, ptB);
17062
+ const fLen = vecMag(fv);
17063
+ let fu = vecNorm(fv);
17064
+ if (fLen < 1e-6) fu = u;
17065
+ const headLen = totalLen * safeArrowheadLengthRatio;
17066
+ const pBase = vecSub(tip, vecScale(fu, headLen));
17067
+ const wingWidth = headLen * safeArrowheadWidthRatio;
17068
+ const perp = [-fu[1], fu[0]];
17069
+ const pWing1 = vecAdd(pBase, vecScale(perp, wingWidth));
17070
+ const pWing2 = vecSub(pBase, vecScale(perp, wingWidth));
17071
+ shaftSegments.push([
17072
+ origin,
17073
+ ptA,
17074
+ ptB,
17075
+ pBase
17076
+ ].map((p) => unproject(p[0], p[1])));
17077
+ headPolygons.push([[
17078
+ tip,
17079
+ pWing1,
17080
+ pWing2,
17081
+ tip
17082
+ ].map((p) => unproject(p[0], p[1]))]);
17083
+ };
17084
+ addLeg(p1, p2, p3);
17085
+ addLeg(p1, p3, p2);
17086
+ const features = [];
17087
+ if (shaftSegments.length > 0) features.push({
17088
+ type: "Feature",
17089
+ properties: { part: "shaft" },
17090
+ geometry: {
17091
+ type: "MultiLineString",
17092
+ coordinates: shaftSegments
17093
+ }
17094
+ });
17095
+ if (headPolygons.length > 0) features.push({
17096
+ type: "Feature",
17097
+ properties: {
17098
+ part: "head",
17099
+ fill: true,
17100
+ style: SOLID_ACCENT_FILL
17101
+ },
17102
+ geometry: {
17103
+ type: "MultiPolygon",
17104
+ coordinates: headPolygons
17105
+ }
17106
+ });
17107
+ return {
17108
+ type: "FeatureCollection",
17109
+ features
17110
+ };
17111
+ }
17112
+ const SEARCH_AREA = defineControlMeasure({
17113
+ metadata: SEARCH_AREA_METADATA,
17114
+ generator: (controlPoints, options) => createSearchArea(asThreePoints(controlPoints), options),
17115
+ defaultOptions: DEFAULT_TACTICAL_ARROW_OPTIONS,
17116
+ rule: searchAreaDrawRule
17117
+ });
17118
+ //#endregion
16306
17119
  //#region src/generators/cm15-maneuver-areas/strongPoint.ts
16307
17120
  const DEFAULT_SMOOTH_RESOLUTION = 16;
16308
17121
  /** Default options for the Strong Point control measure. */
@@ -16893,130 +17706,6 @@ const SUPPORT_BY_FIRE = defineControlMeasure({
16893
17706
  rule: supportByFireDrawRule
16894
17707
  });
16895
17708
  //#endregion
16896
- //#region src/generators/cm27-protection-areas/turn.ts
16897
- const DEFAULT_TURN_OPTIONS = {
16898
- arrowheadLengthRatio: .16,
16899
- arrowheadWidthRatio: .16,
16900
- arcSegments: 64
16901
- };
16902
- const TURN_METADATA = {
16903
- id: "turn",
16904
- name: "Turn",
16905
- 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.",
16906
- entity: "Protection Areas",
16907
- entityType: "Obstacle Effects",
16908
- entitySubtype: "Turn",
16909
- value: "270504",
16910
- minCoordinates: 2,
16911
- geometry: "line",
16912
- geometryTypes: ["MultiLineString", "MultiPolygon"],
16913
- paints: {
16914
- stroke: true,
16915
- fill: "fixed",
16916
- text: false
16917
- },
16918
- drawRule: "Line10",
16919
- params: [
16920
- {
16921
- key: "arrowheadLengthRatio",
16922
- presentationTier: "advanced",
16923
- label: "Arrowhead length",
16924
- description: "Arrowhead length as a ratio of the rear-to-tip distance",
16925
- type: "number",
16926
- min: .01,
16927
- max: .5,
16928
- step: .01
16929
- },
16930
- {
16931
- key: "arrowheadWidthRatio",
16932
- presentationTier: "advanced",
16933
- label: "Arrowhead width",
16934
- description: "Arrowhead base width as a ratio of the rear-to-tip distance",
16935
- type: "number",
16936
- min: .01,
16937
- max: .5,
16938
- step: .01
16939
- },
16940
- {
16941
- key: "arcSegments",
16942
- presentationTier: "advanced",
16943
- label: "Arc segments",
16944
- description: "Number of line segments approximating the quarter-circle shaft.",
16945
- type: "number",
16946
- min: 4,
16947
- max: 128,
16948
- step: 1
16949
- }
16950
- ]
16951
- };
16952
- /**
16953
- * Generates a quarter-circle shaft from PT2 (rear) to the arrowhead base.
16954
- * The arrowhead continues tangent to the arc and places its tip at PT1.
16955
- * PT3 selects which side of the PT2 -> PT1 chord contains the arc.
16956
- */
16957
- function createTurn(coordinates, options = {}) {
16958
- const tip = project(coordinates[0][0], coordinates[0][1]);
16959
- const rear = project(coordinates[1][0], coordinates[1][1]);
16960
- const side = quarterArcSide(rear, tip, coordinates[2] ? project(coordinates[2][0], coordinates[2][1]) : void 0);
16961
- const rearToTip = vecSub(tip, rear);
16962
- const totalLength = vecMag(rearToTip);
16963
- if (totalLength < 1e-6) return {
16964
- type: "FeatureCollection",
16965
- features: []
16966
- };
16967
- const requestedHeadLength = totalLength * (options.arrowheadLengthRatio ?? DEFAULT_TURN_OPTIONS.arrowheadLengthRatio);
16968
- const tangentComponent = Math.min(Math.max(requestedHeadLength, EPSILON), totalLength * .95) / Math.SQRT2;
16969
- const arcChordLength = Math.sqrt(totalLength * totalLength - tangentComponent * tangentComponent) - tangentComponent;
16970
- const chordOffset = Math.atan2(tangentComponent, arcChordLength + tangentComponent);
16971
- const headBase = vecAdd(rear, vecScale(rotate(vecNorm(rearToTip), side * chordOffset), arcChordLength));
16972
- const arc = createQuarterArc(rear, headBase, side);
16973
- if (!arc) return {
16974
- type: "FeatureCollection",
16975
- features: []
16976
- };
16977
- const segmentCount = Math.round(Math.min(128, Math.max(4, options.arcSegments ?? DEFAULT_TURN_OPTIONS.arcSegments)));
16978
- const arcCoordinates = [];
16979
- for (let i = 0; i <= segmentCount; i++) {
16980
- const angle = arc.startAngle + arc.sweepAngle * i / segmentCount;
16981
- arcCoordinates.push(unproject(arc.center[0] + arc.radius * Math.cos(angle), arc.center[1] + arc.radius * Math.sin(angle)));
16982
- }
16983
- const headWidth = totalLength * (options.arrowheadWidthRatio ?? DEFAULT_TURN_OPTIONS.arrowheadWidthRatio);
16984
- const arrowHead = createArrowHeadRing(tip, vecNorm(vecSub(tip, headBase)), vecMag(vecSub(tip, headBase)), headWidth);
16985
- return {
16986
- type: "FeatureCollection",
16987
- features: [{
16988
- type: "Feature",
16989
- properties: { part: "arc" },
16990
- geometry: {
16991
- type: "MultiLineString",
16992
- coordinates: [arcCoordinates]
16993
- }
16994
- }, {
16995
- type: "Feature",
16996
- properties: {
16997
- part: "arrowhead",
16998
- fill: true,
16999
- style: SOLID_ACCENT_FILL
17000
- },
17001
- geometry: {
17002
- type: "MultiPolygon",
17003
- coordinates: [[[...arrowHead]]]
17004
- }
17005
- }]
17006
- };
17007
- }
17008
- function rotate(vector, angle) {
17009
- const cos = Math.cos(angle);
17010
- const sin = Math.sin(angle);
17011
- return [vector[0] * cos - vector[1] * sin, vector[0] * sin + vector[1] * cos];
17012
- }
17013
- const TURN = defineControlMeasure({
17014
- metadata: TURN_METADATA,
17015
- generator: createTurn,
17016
- defaultOptions: DEFAULT_TURN_OPTIONS,
17017
- rule: turnDrawRule
17018
- });
17019
- //#endregion
17020
17709
  //#region src/generators/cm15-maneuver-areas/turning-movement.ts
17021
17710
  const DEFAULT_CROSSBAR_POSITION = 0;
17022
17711
  const DEFAULT_TURNING_MOVEMENT_OPTIONS = {
@@ -17163,16 +17852,22 @@ const DEFINITIONS = {
17163
17852
  disengage: DISENGAGE,
17164
17853
  "disrupt-mission-task": DISRUPT_MISSION_TASK,
17165
17854
  envelopment: ENVELOPMENT,
17855
+ "forward-passage-of-lines": FORWARD_PASSAGE_OF_LINES,
17166
17856
  infiltration: INFILTRATION,
17167
17857
  guard: GUARD,
17168
17858
  isolate: ISOLATE,
17859
+ "movement-to-contact": MOVEMENT_TO_CONTACT,
17860
+ occupy: OCCUPY,
17169
17861
  penetrate: PENETRATE,
17862
+ pursuit: PURSUIT,
17170
17863
  "rearward-passage-of-lines": REARWARD_PASSAGE_OF_LINES,
17171
17864
  retire: RETIRE,
17172
17865
  screen: SCREEN,
17173
17866
  seize: SEIZE,
17867
+ evacuate: EVACUATE,
17174
17868
  withdraw: WITHDRAW,
17175
17869
  "withdraw-under-pressure": WITHDRAW_UNDER_PRESSURE,
17870
+ "turn-mission-task": TURN_MISSION_TASK,
17176
17871
  "antitank-ditch-under-construction": ANTITANK_DITCH_UNDER_CONSTRUCTION,
17177
17872
  "antitank-ditch-completed": ANTITANK_DITCH_COMPLETED,
17178
17873
  "antitank-wall": ANTITANK_WALL,
@@ -17597,4 +18292,4 @@ function assertNever(value) {
17597
18292
  throw new Error(`Unhandled control measure kind: ${String(value)}`);
17598
18293
  }
17599
18294
  //#endregion
17600
- export { DEFAULT_ENCIRCLEMENT_OPTIONS as $, DEFAULT_PORTRAYAL as $t, DEFAULT_RETIRE_OPTIONS as A, pointOnMidpointPerpendicularAxis as An, DEFAULT_BLOCK_OPTIONS as At, DEFAULT_LIMIT_OF_ADVANCE_OPTIONS as B, getMetersPerPixel as Bn, DEFAULT_ENGAGEMENT_AREA_OPTIONS as Bt, DEFAULT_MAIN_ATTACK_OPTIONS as C, blockDrawRule as Cn, DEFAULT_BREACH_OPTIONS as Ct, DEFAULT_WITHDRAW_OPTIONS as D, computeDefaultMidpointPerpendicularPoint as Dn, DEFAULT_LIGHT_LINE_OPTIONS as Dt, DEFAULT_WITHDRAW_UNDER_PRESSURE_OPTIONS as E, area27DrawRule as En, DEFAULT_ENGINEER_WORK_LINE_OPTIONS as Et, DEFAULT_FORTIFIED_LINE_OPTIONS as F, haversineDistance as Fn, DEFAULT_ANTITANK_WALL_OPTIONS as Ft, DEFAULT_HOLDING_LINE_OPTIONS as G, TEXT_AMPLIFIER_FIELDS as Gt, DEFAULT_HANDOVER_LINE_OPTIONS as H, DEFAULT_DIRECTION_OF_SUPPORTING_ATTACK_OPTIONS as Ht, DEFAULT_PROBABLE_LINE_OF_DEPLOYMENT_OPTIONS as I, project as In, DEFAULT_ANTITANK_DITCH_OPTIONS as It, DEFAULT_PHASE_LINE_OPTIONS as J, resolveAmplifierPlacement as Jt, DEFAULT_INFILTRATION_LANE_OPTIONS as K, canonicalTextAmplifierKey as Kt, PROBABLE_LINE_OF_DEPLOYMENT_DASH as L, sphericalBearing as Ln, DEFAULT_ASSEMBLY_AREA_OPTIONS as Lt, DEFAULT_PENETRATE_OPTIONS as M, createBaselineFrame as Mn, DEFAULT_BATTLE_POSITION_OPTIONS as Mt, DEFAULT_FRONTAL_ATTACK_OPTIONS as N, calculateMetrics as Nn, DEFAULT_ATTACK_HELICOPTER_OPTIONS as Nt, DEFAULT_SEIZE_OPTIONS as O, createMidpointPerpendicularDrawRule as On, DEFAULT_BOUNDARY_OPTIONS as Ot, DEFAULT_FORTIFIED_AREA_OPTIONS as P, computeInitialWidthPoint as Pn, DEFAULT_ATTACK_BY_FIRE_OPTIONS as Pt, DEFAULT_FINAL_PROTECTIVE_FIRE_OPTIONS as Q, DEFAULT_LINE_JOIN as Qt, DEFAULT_LINE_OF_DEPARTURE_CONTACT_OPTIONS as R, unproject as Rn, DEFAULT_AREA_OF_OPERATIONS_OPTIONS as Rt, DEFAULT_MINEFIELD_OPTIONS as S, disruptDrawRule as Sn, DEFAULT_BYPASS_OPTIONS as St, DEFAULT_JOINT_TACTICAL_ACTION_AREA_OPTIONS as T, containDrawRule as Tn, DEFAULT_GENERIC_C2_LINE_OPTIONS as Tt, DEFAULT_FORWARD_EDGE_OF_BATTLE_AREA_OPTIONS as U, DEFAULT_DIRECTION_OF_MAIN_ATTACK_OPTIONS as Ut, DEFAULT_BATTLE_HANDOVER_LINE_OPTIONS as V, roundToFixed as Vn, DEFAULT_AREA_DEFENSE_OPTIONS as Vt, DEFAULT_RELEASE_LINE_OPTIONS as W, DEFAULT_DIRECTION_OF_ATTACK_AVIATION_OPTIONS as Wt, DEFAULT_FIX_MISSION_TASK_OPTIONS as X, DEFAULT_LABEL_SIZE_CLAMP_CSS_PIXELS as Xt, DEFAULT_FLOT_OPTIONS as Y, DEFAULT_LABEL_HEIGHT_CSS_PIXELS as Yt, DEFAULT_FIX_OPTIONS as Z, DEFAULT_LINE_CAP as Zt, DEFAULT_PRINCIPAL_DIRECTION_OF_FIRE_OPTIONS as _, dynamicPointDrawRule as _n, DEFAULT_GENERIC_POLYGON_OPTIONS as _t, DEFINITIONS as a, rectangleDrawRule as an, DEFAULT_COVER_OPTIONS as at, DEFAULT_OBSTACLE_BYPASS_EASY_OPTIONS as b, point12DrawRule as bn, DEFAULT_CLASSIC_ARROW_OPTIONS as bt, getDefaultOptions as c, polyline1DrawRule as cn, DEFAULT_COUNTERATTACK_OPTIONS as ct, DEFAULT_TURN_OPTIONS as d, line24DrawRule as dn, DEFAULT_RADAR_SEARCH_DOCTRINE_OPTIONS as dt, DEFAULT_STROKE_DASH_CSS_PIXELS as en, DEFAULT_DISRUPT_OPTIONS as et, DEFAULT_SUPPORT_BY_FIRE_OPTIONS as f, line23DrawRule as fn, RADAR_SEARCH_DOCTRINE_FILL_COLOR as ft, SECONDARY_DIRECTION_OF_FIRE_DASH as g, attackByFireDrawRule as gn, DEFAULT_GENERIC_RECTANGLE_OPTIONS as gt, DEFAULT_SECONDARY_DIRECTION_OF_FIRE_OPTIONS as h, ambushDrawRule as hn, DEFAULT_GENERIC_SECTOR_OPTIONS as ht, CONTROL_MEASURE_METADATA as i, DEFAULT_AIRBORNE_ATTACK_OPTIONS as in, DEFAULT_DELAY_OPTIONS as it, DEFAULT_REARWARD_PASSAGE_OF_LINES_OPTIONS as j, snapToMidpointPerpendicular as jn, DEFAULT_CONTAIN_OPTIONS as jt, DEFAULT_SCREEN_OPTIONS as k, getMidpointPerpendicularSignedDistance as kn, DEFAULT_BLOCK_ARROW_OPTIONS as kt, listControlMeasureMetadata as l, line27DrawRule as ln, DEFAULT_SUPPORTING_ATTACK_OPTIONS as lt, DEFAULT_STRONG_POINT_OPTIONS as m, line1DrawRule as mn, DEFAULT_GENERIC_TEXT_OPTIONS as mt, resolveStyleHints as n, DEFAULT_SYMBOL_COLOR as nn, DEFAULT_DISRUPT_MISSION_TASK_OPTIONS as nt, getControlMeasureMetadata as o, axis1DrawRule as on, DEFAULT_TACTICAL_ARROW_OPTIONS as ot, DEFAULT_RETAIN_OPTIONS as p, turnDrawRule as pn, RADAR_SEARCH_DOCTRINE_STROKE_COLOR as pt, DEFAULT_BRIDGEHEAD_LINE_OPTIONS as q, normalizeTextAmplifiers as qt, CONTROL_MEASURE_IDS as r, DEFAULT_AMBUSH_OPTIONS as rn, DEFAULT_DISENGAGE_OPTIONS as rt, getControlMeasureMetadataByValue as s, supportByFireDrawRule as sn, DEFAULT_COUNTERATTACK_BY_FIRE_OPTIONS as st, renderControlMeasure as t, DEFAULT_STROKE_WIDTH_CSS_PIXELS as tn, DEFAULT_GUARD_OPTIONS as tt, DEFAULT_TURNING_MOVEMENT_OPTIONS as u, line26DrawRule as un, DEFAULT_CLEAR_OPTIONS as ut, DEFAULT_PICKUP_ZONE_OPTIONS as v, sectorDrawRule as vn, DEFAULT_GENERIC_LINE_OPTIONS as vt, DEFAULT_LANDING_ZONE_OPTIONS as w, centerRadiusDrawRule as wn, DEFAULT_BLOCK_MISSION_TASK_OPTIONS as wt, DEFAULT_OBSTACLE_BYPASS_DIFFICULT_OPTIONS as x, penetrateDrawRule as xn, DEFAULT_CANALIZE_OPTIONS as xt, DEFAULT_OBSTACLE_BYPASS_IMPOSSIBLE_OPTIONS as y, staticPointDrawRule as yn, DEFAULT_GENERIC_CIRCLE_OPTIONS as yt, DEFAULT_LINE_OF_DEPARTURE_OPTIONS as z, EPSILON as zn, DEFAULT_MOBILE_DEFENSE_OPTIONS as zt };
18295
+ export { DEFAULT_FIX_MISSION_TASK_OPTIONS as $, DEFAULT_LABEL_SIZE_CLAMP_CSS_PIXELS as $t, DEFAULT_SCREEN_OPTIONS as A, area27DrawRule as An, DEFAULT_LIGHT_LINE_OPTIONS as At, PROBABLE_LINE_OF_DEPLOYMENT_DASH as B, project as Bn, DEFAULT_ASSEMBLY_AREA_OPTIONS as Bt, DEFAULT_LANDING_ZONE_OPTIONS as C, point12DrawRule as Cn, DEFAULT_CLASSIC_ARROW_OPTIONS as Ct, DEFAULT_WITHDRAW_OPTIONS as D, blockDrawRule as Dn, DEFAULT_BLOCK_MISSION_TASK_OPTIONS as Dt, DEFAULT_WITHDRAW_UNDER_PRESSURE_OPTIONS as E, disruptDrawRule as En, DEFAULT_BREACH_OPTIONS as Et, DEFAULT_MOVEMENT_TO_CONTACT_OPTIONS as F, snapToMidpointPerpendicular as Fn, DEFAULT_BATTLE_POSITION_OPTIONS as Ft, DEFAULT_HANDOVER_LINE_OPTIONS as G, roundToFixed as Gn, DEFAULT_DIRECTION_OF_SUPPORTING_ATTACK_OPTIONS as Gt, DEFAULT_LINE_OF_DEPARTURE_OPTIONS as H, unproject as Hn, DEFAULT_MOBILE_DEFENSE_OPTIONS as Ht, DEFAULT_FRONTAL_ATTACK_OPTIONS as I, createBaselineFrame as In, DEFAULT_ATTACK_HELICOPTER_OPTIONS as It, DEFAULT_HOLDING_LINE_OPTIONS as J, TEXT_AMPLIFIER_FIELDS as Jt, DEFAULT_FORWARD_EDGE_OF_BATTLE_AREA_OPTIONS as K, DEFAULT_DIRECTION_OF_MAIN_ATTACK_OPTIONS as Kt, DEFAULT_FORTIFIED_AREA_OPTIONS as L, calculateMetrics as Ln, DEFAULT_ATTACK_BY_FIRE_OPTIONS as Lt, DEFAULT_REARWARD_PASSAGE_OF_LINES_OPTIONS as M, createMidpointPerpendicularDrawRule as Mn, DEFAULT_BLOCK_ARROW_OPTIONS as Mt, DEFAULT_PENETRATE_OPTIONS as N, getMidpointPerpendicularSignedDistance as Nn, DEFAULT_BLOCK_OPTIONS as Nt, DEFAULT_EVACUATE_OPTIONS as O, centerRadiusDrawRule as On, DEFAULT_GENERIC_C2_LINE_OPTIONS as Ot, DEFAULT_OCCUPY_OPTIONS as P, pointOnMidpointPerpendicularAxis as Pn, DEFAULT_CONTAIN_OPTIONS as Pt, DEFAULT_FLOT_OPTIONS as Q, DEFAULT_LABEL_HEIGHT_CSS_PIXELS as Qt, DEFAULT_FORTIFIED_LINE_OPTIONS as R, computeInitialWidthPoint as Rn, DEFAULT_ANTITANK_WALL_OPTIONS as Rt, DEFAULT_MAIN_ATTACK_OPTIONS as S, staticPointDrawRule as Sn, DEFAULT_GENERIC_CIRCLE_OPTIONS as St, DEFAULT_TURN_OPTIONS as T, occupyDrawRule as Tn, DEFAULT_BYPASS_OPTIONS as Tt, DEFAULT_LIMIT_OF_ADVANCE_OPTIONS as U, EPSILON as Un, DEFAULT_ENGAGEMENT_AREA_OPTIONS as Ut, DEFAULT_LINE_OF_DEPARTURE_CONTACT_OPTIONS as V, sphericalBearing as Vn, DEFAULT_AREA_OF_OPERATIONS_OPTIONS as Vt, DEFAULT_BATTLE_HANDOVER_LINE_OPTIONS as W, getMetersPerPixel as Wn, DEFAULT_AREA_DEFENSE_OPTIONS as Wt, DEFAULT_BRIDGEHEAD_LINE_OPTIONS as X, normalizeTextAmplifiers as Xt, DEFAULT_INFILTRATION_LANE_OPTIONS as Y, canonicalTextAmplifierKey as Yt, DEFAULT_PHASE_LINE_OPTIONS as Z, resolveAmplifierPlacement as Zt, DEFAULT_PICKUP_ZONE_OPTIONS as _, line1DrawRule as _n, DEFAULT_GENERIC_TEXT_OPTIONS as _t, DEFINITIONS as a, DEFAULT_SYMBOL_COLOR as an, DEFAULT_DISRUPT_MISSION_TASK_OPTIONS as at, DEFAULT_OBSTACLE_BYPASS_DIFFICULT_OPTIONS as b, dynamicPointDrawRule as bn, DEFAULT_GENERIC_POLYGON_OPTIONS as bt, getDefaultOptions as c, rectangleDrawRule as cn, DEFAULT_COVER_OPTIONS as ct, DEFAULT_SUPPORT_BY_FIRE_OPTIONS as d, polyline1DrawRule as dn, DEFAULT_COUNTERATTACK_OPTIONS as dt, DEFAULT_LINE_CAP as en, DEFAULT_FIX_OPTIONS as et, DEFAULT_RETAIN_OPTIONS as f, line27DrawRule as fn, DEFAULT_SUPPORTING_ATTACK_OPTIONS as ft, DEFAULT_PRINCIPAL_DIRECTION_OF_FIRE_OPTIONS as g, turnDrawRule as gn, RADAR_SEARCH_DOCTRINE_STROKE_COLOR as gt, SECONDARY_DIRECTION_OF_FIRE_DASH as h, line23DrawRule as hn, RADAR_SEARCH_DOCTRINE_FILL_COLOR as ht, CONTROL_MEASURE_METADATA as i, DEFAULT_STROKE_WIDTH_CSS_PIXELS as in, DEFAULT_GUARD_OPTIONS as it, DEFAULT_RETIRE_OPTIONS as j, computeDefaultMidpointPerpendicularPoint as jn, DEFAULT_BOUNDARY_OPTIONS as jt, DEFAULT_SEIZE_OPTIONS as k, containDrawRule as kn, DEFAULT_ENGINEER_WORK_LINE_OPTIONS as kt, listControlMeasureMetadata as l, axis1DrawRule as ln, DEFAULT_TACTICAL_ARROW_OPTIONS as lt, DEFAULT_SECONDARY_DIRECTION_OF_FIRE_OPTIONS as m, line24DrawRule as mn, DEFAULT_RADAR_SEARCH_DOCTRINE_OPTIONS as mt, resolveStyleHints as n, DEFAULT_PORTRAYAL as nn, DEFAULT_ENCIRCLEMENT_OPTIONS as nt, getControlMeasureMetadata as o, DEFAULT_AMBUSH_OPTIONS as on, DEFAULT_DISENGAGE_OPTIONS as ot, DEFAULT_STRONG_POINT_OPTIONS as p, line26DrawRule as pn, DEFAULT_CLEAR_OPTIONS as pt, DEFAULT_RELEASE_LINE_OPTIONS as q, DEFAULT_DIRECTION_OF_ATTACK_AVIATION_OPTIONS as qt, CONTROL_MEASURE_IDS as r, DEFAULT_STROKE_DASH_CSS_PIXELS as rn, DEFAULT_DISRUPT_OPTIONS as rt, getControlMeasureMetadataByValue as s, DEFAULT_AIRBORNE_ATTACK_OPTIONS as sn, DEFAULT_DELAY_OPTIONS as st, renderControlMeasure as t, DEFAULT_LINE_JOIN as tn, DEFAULT_FINAL_PROTECTIVE_FIRE_OPTIONS as tt, DEFAULT_TURNING_MOVEMENT_OPTIONS as u, supportByFireDrawRule as un, DEFAULT_COUNTERATTACK_BY_FIRE_OPTIONS as ut, DEFAULT_OBSTACLE_BYPASS_IMPOSSIBLE_OPTIONS as v, ambushDrawRule as vn, DEFAULT_GENERIC_SECTOR_OPTIONS as vt, DEFAULT_JOINT_TACTICAL_ACTION_AREA_OPTIONS as w, penetrateDrawRule as wn, DEFAULT_CANALIZE_OPTIONS as wt, DEFAULT_MINEFIELD_OPTIONS as x, sectorDrawRule as xn, DEFAULT_GENERIC_LINE_OPTIONS as xt, DEFAULT_OBSTACLE_BYPASS_EASY_OPTIONS as y, attackByFireDrawRule as yn, DEFAULT_GENERIC_RECTANGLE_OPTIONS as yt, DEFAULT_PROBABLE_LINE_OF_DEPLOYMENT_OPTIONS as z, haversineDistance as zn, DEFAULT_ANTITANK_DITCH_OPTIONS as zt };