@orbat-mapper/control-measures 0.16.0 → 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.
@@ -785,6 +785,18 @@ const disruptDrawRule = createMidpointPerpendicularDrawRule({
785
785
  normal: "left"
786
786
  });
787
787
  //#endregion
788
+ //#region src/draw-rules/area16.ts
789
+ /**
790
+ * Area16 — center + start/radius point, mechanically identical to Area15. It
791
+ * re-labels the shared behaviour rather than aliasing it because a definition's
792
+ * `rule.id` must carry its `metadata.drawRule` as a prefix (see the registry
793
+ * invariant in `__tests__/metadata-registry.spec.ts`, ADR-0013).
794
+ */
795
+ const occupyDrawRule = {
796
+ ...centerRadiusDrawRule,
797
+ id: "area16:center-radius"
798
+ };
799
+ //#endregion
788
800
  //#region src/draw-rules/area17.ts
789
801
  /**
790
802
  * Area17 anchor draw rule for Penetrate.
@@ -1362,6 +1374,23 @@ const line32DrawRule = {
1362
1374
  }
1363
1375
  };
1364
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
1365
1394
  //#region src/draw-rules/polyline1.ts
1366
1395
  /**
1367
1396
  * Polyline1 anchors for the Infiltration Lane.
@@ -4569,7 +4598,7 @@ const AREA = defineControlMeasure({
4569
4598
  //#endregion
4570
4599
  //#region src/generators/cm15-maneuver-areas/area-defense.ts
4571
4600
  const DEFAULT_AREA_DEFENSE_OPTIONS = {
4572
- arrowOpeningAngle: 30,
4601
+ openingAngle: 30,
4573
4602
  labelOpeningAngle: 20,
4574
4603
  barbCount: 6,
4575
4604
  barbLengthRatio: .16,
@@ -4599,7 +4628,7 @@ const AREA_DEFENSE_METADATA = {
4599
4628
  capturesLabelSize: true,
4600
4629
  params: [
4601
4630
  {
4602
- key: "arrowOpeningAngle",
4631
+ key: "openingAngle",
4603
4632
  presentationTier: "advanced",
4604
4633
  label: "Arrow opening",
4605
4634
  description: "Angular opening between the two arrow tips, in degrees",
@@ -4680,7 +4709,7 @@ function createAreaDefense(coordinates, options = {}) {
4680
4709
  features: []
4681
4710
  };
4682
4711
  const upperTipAngle = Math.atan2(radiusVector[1], radiusVector[0]);
4683
- 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;
4684
4713
  const labelHalf = Math.min(90, Math.max(5, resolved.labelOpeningAngle)) * Math.PI / 360;
4685
4714
  const labelCenter = upperTipAngle + arrowHalf - Math.PI;
4686
4715
  const upperStart = labelCenter + labelHalf;
@@ -6116,7 +6145,7 @@ const DEFAULT_ATTACK_HELICOPTER_OPTIONS = {
6116
6145
  symbolHeightRatio: .45,
6117
6146
  triangleSizeRatio: .25,
6118
6147
  bottomBarWidthRatio: 1.5,
6119
- bowtieWidthRatio: .8
6148
+ bowTieWidthRatio: .8
6120
6149
  };
6121
6150
  const ATTACK_HELICOPTER_METADATA = {
6122
6151
  id: "attack-helicopter",
@@ -6168,7 +6197,7 @@ const ATTACK_HELICOPTER_METADATA = {
6168
6197
  step: .1
6169
6198
  },
6170
6199
  {
6171
- key: "bowtieWidthRatio",
6200
+ key: "bowTieWidthRatio",
6172
6201
  presentationTier: "advanced",
6173
6202
  label: "Bowtie width",
6174
6203
  description: "Size of the central bowtie relative to the reference triangle.",
@@ -6237,7 +6266,7 @@ function createAttackHelicopter(coordinates, options = {}) {
6237
6266
  const symbolHeightRatio = options.symbolHeightRatio ?? DEFAULT_ATTACK_HELICOPTER_OPTIONS.symbolHeightRatio;
6238
6267
  const triangleSizeRatio = options.triangleSizeRatio ?? DEFAULT_ATTACK_HELICOPTER_OPTIONS.triangleSizeRatio;
6239
6268
  const bottomBarWidthRatio = options.bottomBarWidthRatio ?? DEFAULT_ATTACK_HELICOPTER_OPTIONS.bottomBarWidthRatio;
6240
- const bowtieWidthRatio = options.bowtieWidthRatio ?? DEFAULT_ATTACK_HELICOPTER_OPTIONS.bowtieWidthRatio;
6269
+ const bowTieWidthRatio = options.bowTieWidthRatio ?? DEFAULT_ATTACK_HELICOPTER_OPTIONS.bowTieWidthRatio;
6241
6270
  const baseUnit = headWidth * .45;
6242
6271
  const shaftLength = headWidth * symbolHeightRatio;
6243
6272
  const triHeight = baseUnit * triangleSizeRatio;
@@ -6283,7 +6312,7 @@ function createAttackHelicopter(coordinates, options = {}) {
6283
6312
  coordinates: [unproject(pBarLeft[0], pBarLeft[1]), unproject(pBarRight[0], pBarRight[1])]
6284
6313
  }
6285
6314
  };
6286
- const bowTieSize = refTriHeight * bowtieWidthRatio;
6315
+ const bowTieSize = refTriHeight * bowTieWidthRatio;
6287
6316
  const bowTieHalfWidth = bowTieSize * .6;
6288
6317
  const pBowTieRightBaseCenter = vecAdd(center, vecScale(u, bowTieSize));
6289
6318
  const pBowTieRightTop = vecAdd(pBowTieRightBaseCenter, vecScale(n, bowTieHalfWidth));
@@ -10693,18 +10722,16 @@ function createDelaySymbol(coordinates, options = {}) {
10693
10722
  createArrowHead$1(p1, vecScale(dirLine, -1), lineLength * (options.arrowheadLengthRatio ?? DEFAULT_DELAY_OPTIONS.arrowheadLengthRatio), lineLength * (options.arrowheadWidthRatio ?? DEFAULT_DELAY_OPTIONS.arrowheadWidthRatio))
10694
10723
  ];
10695
10724
  if (diameter >= 1e-6) lines.push(createSemicircleArc(p2, diameterEnd, dirLine, options.arcSegments ?? DEFAULT_DELAY_OPTIONS.arcSegments));
10696
- const lineFeature = {
10697
- type: "Feature",
10698
- properties: { part: "delay" },
10699
- geometry: {
10700
- type: "MultiLineString",
10701
- coordinates: lines
10702
- }
10703
- };
10704
- const lineRotation = Math.atan2(dirLine[1], dirLine[0]);
10705
10725
  return {
10706
10726
  type: "FeatureCollection",
10707
- 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), {
10708
10735
  labelSize: options.labelSize,
10709
10736
  labelSizePixels: options.labelSizePixels
10710
10737
  })]
@@ -13765,6 +13792,49 @@ const FRONTAL_ATTACK = defineControlMeasure({
13765
13792
  }
13766
13793
  });
13767
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
13768
13838
  //#region src/generators/cm34-mission-tasks/isolate.ts
13769
13839
  /**
13770
13840
  * Default options for the ISOLATE symbol.
@@ -14098,6 +14168,132 @@ const MOVEMENT_TO_CONTACT = defineControlMeasure({
14098
14168
  }
14099
14169
  });
14100
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
14101
14297
  //#region src/generators/cm34-mission-tasks/penetrate.ts
14102
14298
  const DEFAULT_PENETRATE_OPTIONS = {
14103
14299
  arrowheadLengthRatio: .15,
@@ -14216,6 +14412,155 @@ const PENETRATE = defineControlMeasure({
14216
14412
  ] }
14217
14413
  });
14218
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
14219
14564
  //#region src/generators/cm34-mission-tasks/rearward-passage-of-lines.ts
14220
14565
  const DEFAULT_REARWARD_PASSAGE_OF_LINES_OPTIONS = DEFAULT_DELAY_OPTIONS;
14221
14566
  const REARWARD_PASSAGE_OF_LINES_METADATA = {
@@ -14348,76 +14693,77 @@ const DEFAULT_SEIZE_OPTIONS = {
14348
14693
  curveSegments: 48,
14349
14694
  circleSegments: 64
14350
14695
  };
14351
- const SEIZE_METADATA = {
14352
- id: "seize",
14353
- name: "Seize",
14354
- description: "Takes possession of a designated area using overwhelming force and establishes control of it.",
14355
- entity: "Mission Tasks",
14356
- entityType: "Seize",
14357
- value: "342300",
14358
- minCoordinates: 4,
14359
- maxCoordinates: 4,
14360
- geometry: "line",
14361
- geometryTypes: ["MultiLineString", "Point"],
14362
- paints: {
14363
- stroke: true,
14364
- fill: "none",
14365
- text: true
14366
- },
14367
- drawRule: "Line27",
14368
- capturesLabelSize: true,
14369
- params: [
14370
- {
14371
- key: "arrowheadLengthRatio",
14372
- presentationTier: "advanced",
14373
- label: "Arrowhead length",
14374
- description: "Arrowhead length as a ratio of the PT. 2-to-PT. 4 span",
14375
- type: "number",
14376
- min: .01,
14377
- max: .5,
14378
- step: .01
14379
- },
14380
- {
14381
- key: "arrowheadWidthRatio",
14382
- presentationTier: "advanced",
14383
- label: "Arrowhead width",
14384
- description: "Arrowhead base width as a ratio of the PT. 2-to-PT. 4 span",
14385
- type: "number",
14386
- min: .01,
14387
- max: .5,
14388
- step: .01
14389
- },
14390
- {
14391
- key: "labelGapRatio",
14392
- presentationTier: "advanced",
14393
- label: "Label gap",
14394
- description: "Size of the curve gap holding the S label",
14395
- type: "number",
14396
- min: 0,
14397
- max: .5,
14398
- step: .01
14399
- },
14400
- {
14401
- key: "curveSegments",
14402
- presentationTier: "advanced",
14403
- label: "Curve segments",
14404
- description: "Number of straight segments used to approximate the circular arc",
14405
- type: "number",
14406
- min: 4,
14407
- max: 128,
14408
- step: 1
14409
- },
14410
- {
14411
- key: "circleSegments",
14412
- presentationTier: "advanced",
14413
- label: "Circle segments",
14414
- description: "Number of straight segments used to approximate the assigned-unit circle",
14415
- type: "number",
14416
- min: 12,
14417
- max: 128,
14418
- step: 1
14419
- }
14420
- ]
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
+ ];
14748
+ const SEIZE_METADATA = {
14749
+ id: "seize",
14750
+ name: "Seize",
14751
+ description: "Takes possession of a designated area using overwhelming force and establishes control of it.",
14752
+ entity: "Mission Tasks",
14753
+ entityType: "Seize",
14754
+ value: "342300",
14755
+ minCoordinates: 4,
14756
+ maxCoordinates: 4,
14757
+ geometry: "line",
14758
+ geometryTypes: ["MultiLineString", "Point"],
14759
+ paints: {
14760
+ stroke: true,
14761
+ fill: "none",
14762
+ text: true
14763
+ },
14764
+ drawRule: "Line27",
14765
+ capturesLabelSize: true,
14766
+ params: SEIZE_PARAMS
14421
14767
  };
14422
14768
  function positiveAngle(angle) {
14423
14769
  const normalized = angle % (Math.PI * 2);
@@ -14447,8 +14793,8 @@ function threePointArc(start, middle, end) {
14447
14793
  sweepAngle: positiveAngle(middleAngle - startAngle) <= counterclockwiseSweep ? counterclockwiseSweep : counterclockwiseSweep - Math.PI * 2
14448
14794
  };
14449
14795
  }
14450
- /** Generates the Seize mission task symbol (342300). */
14451
- function createSeizeSymbol(coordinates, options = {}) {
14796
+ /** Shared geometry for mission tasks that use the Seize graphic. */
14797
+ function createSeizeLikeSymbol(coordinates, options = {}, label = "S", part = "seize") {
14452
14798
  const [c1, c2, c3, c4] = coordinates;
14453
14799
  const center = project(c1[0], c1[1]);
14454
14800
  const start = project(c2[0], c2[1]);
@@ -14509,23 +14855,28 @@ function createSeizeSymbol(coordinates, options = {}) {
14509
14855
  const angle = startAngle + index / circleSegments * Math.PI * 2;
14510
14856
  circle.push(toPosition(vecAdd(center, [radius * Math.cos(angle), radius * Math.sin(angle)])));
14511
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
+ ];
14512
14864
  return {
14513
14865
  type: "FeatureCollection",
14514
14866
  features: [{
14515
14867
  type: "Feature",
14516
- properties: { part: "seize" },
14868
+ properties: { part },
14517
14869
  geometry: {
14518
14870
  type: "MultiLineString",
14519
- coordinates: [
14520
- circle,
14521
- firstArcPart,
14522
- secondArcPart,
14523
- createArrowHead$1(tip, arrowDirection, span * Math.max(0, options.arrowheadLengthRatio ?? DEFAULT_SEIZE_OPTIONS.arrowheadLengthRatio), span * Math.max(0, options.arrowheadWidthRatio ?? DEFAULT_SEIZE_OPTIONS.arrowheadWidthRatio))
14524
- ]
14871
+ coordinates: lines
14525
14872
  }
14526
- }, createLabelFeature(labelPoint, "S", labelRotationAlong(tangentAtLabel), options)]
14873
+ }, createLabelFeature(labelPoint, label, labelRotationAlong(tangentAtLabel), options)]
14527
14874
  };
14528
14875
  }
14876
+ /** Generates the Seize mission task symbol (342300). */
14877
+ function createSeizeSymbol(coordinates, options = {}) {
14878
+ return createSeizeLikeSymbol(coordinates, options);
14879
+ }
14529
14880
  const SEIZE = defineControlMeasure({
14530
14881
  metadata: SEIZE_METADATA,
14531
14882
  generator: createSeizeSymbol,
@@ -14542,6 +14893,48 @@ const SEIZE = defineControlMeasure({
14542
14893
  }
14543
14894
  });
14544
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
14545
14938
  //#region src/generators/cm34-mission-tasks/withdraw.ts
14546
14939
  const DEFAULT_WITHDRAW_OPTIONS = DEFAULT_DELAY_OPTIONS;
14547
14940
  const WITHDRAW_METADATA = {
@@ -14620,6 +15013,216 @@ const WITHDRAW_UNDER_PRESSURE = defineControlMeasure({
14620
15013
  }
14621
15014
  });
14622
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
14623
15226
  //#region src/generators/cm15-maneuver-areas/jointTacticalActionArea.ts
14624
15227
  /** Default options for the Joint tactical action area control measure. */
14625
15228
  const DEFAULT_JOINT_TACTICAL_ACTION_AREA_OPTIONS = {
@@ -17103,130 +17706,6 @@ const SUPPORT_BY_FIRE = defineControlMeasure({
17103
17706
  rule: supportByFireDrawRule
17104
17707
  });
17105
17708
  //#endregion
17106
- //#region src/generators/cm27-protection-areas/turn.ts
17107
- const DEFAULT_TURN_OPTIONS = {
17108
- arrowheadLengthRatio: .16,
17109
- arrowheadWidthRatio: .16,
17110
- arcSegments: 64
17111
- };
17112
- const TURN_METADATA = {
17113
- id: "turn",
17114
- name: "Turn",
17115
- description: "Diverts an enemy formation from one avenue of approach to another or into an engagement area. The arrow indicates the desired direction of turn.",
17116
- entity: "Protection Areas",
17117
- entityType: "Obstacle Effects",
17118
- entitySubtype: "Turn",
17119
- value: "270504",
17120
- minCoordinates: 2,
17121
- geometry: "line",
17122
- geometryTypes: ["MultiLineString", "MultiPolygon"],
17123
- paints: {
17124
- stroke: true,
17125
- fill: "fixed",
17126
- text: false
17127
- },
17128
- drawRule: "Line10",
17129
- params: [
17130
- {
17131
- key: "arrowheadLengthRatio",
17132
- presentationTier: "advanced",
17133
- label: "Arrowhead length",
17134
- description: "Arrowhead length as a ratio of the rear-to-tip distance",
17135
- type: "number",
17136
- min: .01,
17137
- max: .5,
17138
- step: .01
17139
- },
17140
- {
17141
- key: "arrowheadWidthRatio",
17142
- presentationTier: "advanced",
17143
- label: "Arrowhead width",
17144
- description: "Arrowhead base width as a ratio of the rear-to-tip distance",
17145
- type: "number",
17146
- min: .01,
17147
- max: .5,
17148
- step: .01
17149
- },
17150
- {
17151
- key: "arcSegments",
17152
- presentationTier: "advanced",
17153
- label: "Arc segments",
17154
- description: "Number of line segments approximating the quarter-circle shaft.",
17155
- type: "number",
17156
- min: 4,
17157
- max: 128,
17158
- step: 1
17159
- }
17160
- ]
17161
- };
17162
- /**
17163
- * Generates a quarter-circle shaft from PT2 (rear) to the arrowhead base.
17164
- * The arrowhead continues tangent to the arc and places its tip at PT1.
17165
- * PT3 selects which side of the PT2 -> PT1 chord contains the arc.
17166
- */
17167
- function createTurn(coordinates, options = {}) {
17168
- const tip = project(coordinates[0][0], coordinates[0][1]);
17169
- const rear = project(coordinates[1][0], coordinates[1][1]);
17170
- const side = quarterArcSide(rear, tip, coordinates[2] ? project(coordinates[2][0], coordinates[2][1]) : void 0);
17171
- const rearToTip = vecSub(tip, rear);
17172
- const totalLength = vecMag(rearToTip);
17173
- if (totalLength < 1e-6) return {
17174
- type: "FeatureCollection",
17175
- features: []
17176
- };
17177
- const requestedHeadLength = totalLength * (options.arrowheadLengthRatio ?? DEFAULT_TURN_OPTIONS.arrowheadLengthRatio);
17178
- const tangentComponent = Math.min(Math.max(requestedHeadLength, EPSILON), totalLength * .95) / Math.SQRT2;
17179
- const arcChordLength = Math.sqrt(totalLength * totalLength - tangentComponent * tangentComponent) - tangentComponent;
17180
- const chordOffset = Math.atan2(tangentComponent, arcChordLength + tangentComponent);
17181
- const headBase = vecAdd(rear, vecScale(rotate(vecNorm(rearToTip), side * chordOffset), arcChordLength));
17182
- const arc = createQuarterArc(rear, headBase, side);
17183
- if (!arc) return {
17184
- type: "FeatureCollection",
17185
- features: []
17186
- };
17187
- const segmentCount = Math.round(Math.min(128, Math.max(4, options.arcSegments ?? DEFAULT_TURN_OPTIONS.arcSegments)));
17188
- const arcCoordinates = [];
17189
- for (let i = 0; i <= segmentCount; i++) {
17190
- const angle = arc.startAngle + arc.sweepAngle * i / segmentCount;
17191
- arcCoordinates.push(unproject(arc.center[0] + arc.radius * Math.cos(angle), arc.center[1] + arc.radius * Math.sin(angle)));
17192
- }
17193
- const headWidth = totalLength * (options.arrowheadWidthRatio ?? DEFAULT_TURN_OPTIONS.arrowheadWidthRatio);
17194
- const arrowHead = createArrowHeadRing(tip, vecNorm(vecSub(tip, headBase)), vecMag(vecSub(tip, headBase)), headWidth);
17195
- return {
17196
- type: "FeatureCollection",
17197
- features: [{
17198
- type: "Feature",
17199
- properties: { part: "arc" },
17200
- geometry: {
17201
- type: "MultiLineString",
17202
- coordinates: [arcCoordinates]
17203
- }
17204
- }, {
17205
- type: "Feature",
17206
- properties: {
17207
- part: "arrowhead",
17208
- fill: true,
17209
- style: SOLID_ACCENT_FILL
17210
- },
17211
- geometry: {
17212
- type: "MultiPolygon",
17213
- coordinates: [[[...arrowHead]]]
17214
- }
17215
- }]
17216
- };
17217
- }
17218
- function rotate(vector, angle) {
17219
- const cos = Math.cos(angle);
17220
- const sin = Math.sin(angle);
17221
- return [vector[0] * cos - vector[1] * sin, vector[0] * sin + vector[1] * cos];
17222
- }
17223
- const TURN = defineControlMeasure({
17224
- metadata: TURN_METADATA,
17225
- generator: createTurn,
17226
- defaultOptions: DEFAULT_TURN_OPTIONS,
17227
- rule: turnDrawRule
17228
- });
17229
- //#endregion
17230
17709
  //#region src/generators/cm15-maneuver-areas/turning-movement.ts
17231
17710
  const DEFAULT_CROSSBAR_POSITION = 0;
17232
17711
  const DEFAULT_TURNING_MOVEMENT_OPTIONS = {
@@ -17373,17 +17852,22 @@ const DEFINITIONS = {
17373
17852
  disengage: DISENGAGE,
17374
17853
  "disrupt-mission-task": DISRUPT_MISSION_TASK,
17375
17854
  envelopment: ENVELOPMENT,
17855
+ "forward-passage-of-lines": FORWARD_PASSAGE_OF_LINES,
17376
17856
  infiltration: INFILTRATION,
17377
17857
  guard: GUARD,
17378
17858
  isolate: ISOLATE,
17379
17859
  "movement-to-contact": MOVEMENT_TO_CONTACT,
17860
+ occupy: OCCUPY,
17380
17861
  penetrate: PENETRATE,
17862
+ pursuit: PURSUIT,
17381
17863
  "rearward-passage-of-lines": REARWARD_PASSAGE_OF_LINES,
17382
17864
  retire: RETIRE,
17383
17865
  screen: SCREEN,
17384
17866
  seize: SEIZE,
17867
+ evacuate: EVACUATE,
17385
17868
  withdraw: WITHDRAW,
17386
17869
  "withdraw-under-pressure": WITHDRAW_UNDER_PRESSURE,
17870
+ "turn-mission-task": TURN_MISSION_TASK,
17387
17871
  "antitank-ditch-under-construction": ANTITANK_DITCH_UNDER_CONSTRUCTION,
17388
17872
  "antitank-ditch-completed": ANTITANK_DITCH_COMPLETED,
17389
17873
  "antitank-wall": ANTITANK_WALL,
@@ -17808,4 +18292,4 @@ function assertNever(value) {
17808
18292
  throw new Error(`Unhandled control measure kind: ${String(value)}`);
17809
18293
  }
17810
18294
  //#endregion
17811
- export { DEFAULT_FINAL_PROTECTIVE_FIRE_OPTIONS as $, DEFAULT_LINE_JOIN as $t, DEFAULT_RETIRE_OPTIONS as A, getMidpointPerpendicularSignedDistance as An, DEFAULT_BLOCK_ARROW_OPTIONS as At, DEFAULT_LINE_OF_DEPARTURE_OPTIONS as B, EPSILON as Bn, DEFAULT_MOBILE_DEFENSE_OPTIONS as Bt, DEFAULT_MAIN_ATTACK_OPTIONS as C, disruptDrawRule as Cn, DEFAULT_BYPASS_OPTIONS as Ct, DEFAULT_WITHDRAW_OPTIONS as D, area27DrawRule as Dn, DEFAULT_ENGINEER_WORK_LINE_OPTIONS as Dt, DEFAULT_WITHDRAW_UNDER_PRESSURE_OPTIONS as E, containDrawRule as En, DEFAULT_GENERIC_C2_LINE_OPTIONS as Et, DEFAULT_FORTIFIED_AREA_OPTIONS as F, computeInitialWidthPoint as Fn, DEFAULT_ATTACK_BY_FIRE_OPTIONS as Ft, DEFAULT_RELEASE_LINE_OPTIONS as G, DEFAULT_DIRECTION_OF_ATTACK_AVIATION_OPTIONS as Gt, DEFAULT_BATTLE_HANDOVER_LINE_OPTIONS as H, roundToFixed as Hn, DEFAULT_AREA_DEFENSE_OPTIONS as Ht, DEFAULT_FORTIFIED_LINE_OPTIONS as I, haversineDistance as In, DEFAULT_ANTITANK_WALL_OPTIONS as It, DEFAULT_BRIDGEHEAD_LINE_OPTIONS as J, normalizeTextAmplifiers as Jt, DEFAULT_HOLDING_LINE_OPTIONS as K, TEXT_AMPLIFIER_FIELDS as Kt, DEFAULT_PROBABLE_LINE_OF_DEPLOYMENT_OPTIONS as L, project as Ln, DEFAULT_ANTITANK_DITCH_OPTIONS as Lt, DEFAULT_PENETRATE_OPTIONS as M, snapToMidpointPerpendicular as Mn, DEFAULT_CONTAIN_OPTIONS as Mt, DEFAULT_MOVEMENT_TO_CONTACT_OPTIONS as N, createBaselineFrame as Nn, DEFAULT_BATTLE_POSITION_OPTIONS as Nt, DEFAULT_SEIZE_OPTIONS as O, computeDefaultMidpointPerpendicularPoint as On, DEFAULT_LIGHT_LINE_OPTIONS as Ot, DEFAULT_FRONTAL_ATTACK_OPTIONS as P, calculateMetrics as Pn, DEFAULT_ATTACK_HELICOPTER_OPTIONS as Pt, DEFAULT_FIX_OPTIONS as Q, DEFAULT_LINE_CAP as Qt, PROBABLE_LINE_OF_DEPLOYMENT_DASH as R, sphericalBearing as Rn, DEFAULT_ASSEMBLY_AREA_OPTIONS as Rt, DEFAULT_MINEFIELD_OPTIONS as S, penetrateDrawRule as Sn, DEFAULT_CANALIZE_OPTIONS as St, DEFAULT_JOINT_TACTICAL_ACTION_AREA_OPTIONS as T, centerRadiusDrawRule as Tn, DEFAULT_BLOCK_MISSION_TASK_OPTIONS as Tt, DEFAULT_HANDOVER_LINE_OPTIONS as U, DEFAULT_DIRECTION_OF_SUPPORTING_ATTACK_OPTIONS as Ut, DEFAULT_LIMIT_OF_ADVANCE_OPTIONS as V, getMetersPerPixel as Vn, DEFAULT_ENGAGEMENT_AREA_OPTIONS as Vt, DEFAULT_FORWARD_EDGE_OF_BATTLE_AREA_OPTIONS as W, DEFAULT_DIRECTION_OF_MAIN_ATTACK_OPTIONS as Wt, DEFAULT_FLOT_OPTIONS as X, DEFAULT_LABEL_HEIGHT_CSS_PIXELS as Xt, DEFAULT_PHASE_LINE_OPTIONS as Y, resolveAmplifierPlacement as Yt, DEFAULT_FIX_MISSION_TASK_OPTIONS as Z, DEFAULT_LABEL_SIZE_CLAMP_CSS_PIXELS as Zt, DEFAULT_PRINCIPAL_DIRECTION_OF_FIRE_OPTIONS as _, attackByFireDrawRule as _n, DEFAULT_GENERIC_RECTANGLE_OPTIONS as _t, DEFINITIONS as a, DEFAULT_AIRBORNE_ATTACK_OPTIONS as an, DEFAULT_DELAY_OPTIONS as at, DEFAULT_OBSTACLE_BYPASS_EASY_OPTIONS as b, staticPointDrawRule as bn, DEFAULT_GENERIC_CIRCLE_OPTIONS as bt, getDefaultOptions as c, supportByFireDrawRule as cn, DEFAULT_COUNTERATTACK_BY_FIRE_OPTIONS as ct, DEFAULT_TURN_OPTIONS as d, line26DrawRule as dn, DEFAULT_CLEAR_OPTIONS as dt, DEFAULT_PORTRAYAL as en, DEFAULT_ENCIRCLEMENT_OPTIONS as et, DEFAULT_SUPPORT_BY_FIRE_OPTIONS as f, line24DrawRule as fn, DEFAULT_RADAR_SEARCH_DOCTRINE_OPTIONS as ft, SECONDARY_DIRECTION_OF_FIRE_DASH as g, ambushDrawRule as gn, DEFAULT_GENERIC_SECTOR_OPTIONS as gt, DEFAULT_SECONDARY_DIRECTION_OF_FIRE_OPTIONS as h, line1DrawRule as hn, DEFAULT_GENERIC_TEXT_OPTIONS as ht, CONTROL_MEASURE_METADATA as i, DEFAULT_AMBUSH_OPTIONS as in, DEFAULT_DISENGAGE_OPTIONS as it, DEFAULT_REARWARD_PASSAGE_OF_LINES_OPTIONS as j, pointOnMidpointPerpendicularAxis as jn, DEFAULT_BLOCK_OPTIONS as jt, DEFAULT_SCREEN_OPTIONS as k, createMidpointPerpendicularDrawRule as kn, DEFAULT_BOUNDARY_OPTIONS as kt, listControlMeasureMetadata as l, polyline1DrawRule as ln, DEFAULT_COUNTERATTACK_OPTIONS as lt, DEFAULT_STRONG_POINT_OPTIONS as m, turnDrawRule as mn, RADAR_SEARCH_DOCTRINE_STROKE_COLOR as mt, resolveStyleHints as n, DEFAULT_STROKE_WIDTH_CSS_PIXELS as nn, DEFAULT_GUARD_OPTIONS as nt, getControlMeasureMetadata as o, rectangleDrawRule as on, DEFAULT_COVER_OPTIONS as ot, DEFAULT_RETAIN_OPTIONS as p, line23DrawRule as pn, RADAR_SEARCH_DOCTRINE_FILL_COLOR as pt, DEFAULT_INFILTRATION_LANE_OPTIONS as q, canonicalTextAmplifierKey as qt, CONTROL_MEASURE_IDS as r, DEFAULT_SYMBOL_COLOR as rn, DEFAULT_DISRUPT_MISSION_TASK_OPTIONS as rt, getControlMeasureMetadataByValue as s, axis1DrawRule as sn, DEFAULT_TACTICAL_ARROW_OPTIONS as st, renderControlMeasure as t, DEFAULT_STROKE_DASH_CSS_PIXELS as tn, DEFAULT_DISRUPT_OPTIONS as tt, DEFAULT_TURNING_MOVEMENT_OPTIONS as u, line27DrawRule as un, DEFAULT_SUPPORTING_ATTACK_OPTIONS as ut, DEFAULT_PICKUP_ZONE_OPTIONS as v, dynamicPointDrawRule as vn, DEFAULT_GENERIC_POLYGON_OPTIONS as vt, DEFAULT_LANDING_ZONE_OPTIONS as w, blockDrawRule as wn, DEFAULT_BREACH_OPTIONS as wt, DEFAULT_OBSTACLE_BYPASS_DIFFICULT_OPTIONS as x, point12DrawRule as xn, DEFAULT_CLASSIC_ARROW_OPTIONS as xt, DEFAULT_OBSTACLE_BYPASS_IMPOSSIBLE_OPTIONS as y, sectorDrawRule as yn, DEFAULT_GENERIC_LINE_OPTIONS as yt, DEFAULT_LINE_OF_DEPARTURE_CONTACT_OPTIONS as z, unproject as zn, DEFAULT_AREA_OF_OPERATIONS_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 };