@orbat-mapper/control-measures 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/dist/{index-B3LgMKpn.d.mts → index-nz-U5HlG.d.mts} +173 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/preview/index.d.mts +1 -1
- package/dist/preview/index.mjs +1 -1
- package/dist/{renderControlMeasure-GsV4DV3r.mjs → renderControlMeasure-D9mfP_gv.mjs} +1214 -80
- package/media/airhead-line.svg +1 -0
- package/media/battle-position-prepared.svg +1 -0
- package/media/contain.svg +1 -0
- package/media/cover.svg +1 -1
- package/media/engagement-area.svg +1 -0
- package/media/final-coordination-line.svg +1 -0
- package/media/guard.svg +1 -1
- package/media/limit-of-advance.svg +1 -0
- package/media/line-of-departure-contact.svg +1 -0
- package/media/line-of-departure.svg +1 -0
- package/media/mined-area.svg +1 -1
- package/media/mobile-defense.svg +1 -0
- package/media/penetration-box.svg +1 -0
- package/media/probable-line-of-deployment.svg +1 -0
- package/media/radar-search-doctrine.svg +1 -0
- package/media/retain.svg +1 -0
- package/media/screen.svg +1 -1
- package/media/sector.svg +1 -1
- package/package.json +1 -1
|
@@ -586,20 +586,34 @@ const area1DrawRule = {
|
|
|
586
586
|
}
|
|
587
587
|
};
|
|
588
588
|
//#endregion
|
|
589
|
-
//#region src/draw-rules/
|
|
590
|
-
const blockDrawRule = createMidpointPerpendicularDrawRule({ id: "area11:block" });
|
|
591
|
-
//#endregion
|
|
592
|
-
//#region src/draw-rules/area12.ts
|
|
589
|
+
//#region src/draw-rules/area27.ts
|
|
593
590
|
/**
|
|
594
|
-
*
|
|
591
|
+
* Area27 anchor draw rule for Mobile Defense.
|
|
595
592
|
*
|
|
596
|
-
*
|
|
597
|
-
*
|
|
593
|
+
* PT. 1 is the arrow tip and PT. 2 is the end of the straight-line portion.
|
|
594
|
+
* PT. 3 fixes the diameter and side of the 180-degree arc, constrained to the
|
|
595
|
+
* perpendicular axis through PT. 2 so the arc stays perpendicular to the
|
|
596
|
+
* action line when any of the three handles is edited.
|
|
598
597
|
*/
|
|
599
|
-
const
|
|
600
|
-
id: "
|
|
601
|
-
|
|
602
|
-
|
|
598
|
+
const area27DrawRule = createMidpointPerpendicularDrawRule({
|
|
599
|
+
id: "area27:mobile-defense",
|
|
600
|
+
minimumUserPoints: 3,
|
|
601
|
+
minimumPreviewPoints: 2,
|
|
602
|
+
origin: "p2",
|
|
603
|
+
normal: "left",
|
|
604
|
+
defaultDistanceRatio: .6
|
|
605
|
+
});
|
|
606
|
+
//#endregion
|
|
607
|
+
//#region src/draw-rules/area5.ts
|
|
608
|
+
/**
|
|
609
|
+
* Area5 — PT. 1/PT. 2 define the open diameter and PT. 3 defines the arrow end.
|
|
610
|
+
* The arrow stays on the perpendicular bisector of the diameter.
|
|
611
|
+
*/
|
|
612
|
+
const containDrawRule = createMidpointPerpendicularDrawRule({
|
|
613
|
+
id: "area5:contain",
|
|
614
|
+
minimumUserPoints: 3,
|
|
615
|
+
defaultDistanceRatio: -1.5,
|
|
616
|
+
minimumPreviewPoints: 2
|
|
603
617
|
});
|
|
604
618
|
//#endregion
|
|
605
619
|
//#region src/draw-rules/area15.ts
|
|
@@ -630,6 +644,34 @@ const centerRadiusDrawRule = {
|
|
|
630
644
|
/** Backward-compatible doctrinal name for Area15's shared center-radius rule. */
|
|
631
645
|
const isolateDrawRule = centerRadiusDrawRule;
|
|
632
646
|
//#endregion
|
|
647
|
+
//#region src/draw-rules/area6.ts
|
|
648
|
+
/**
|
|
649
|
+
* Area6 — center + start point, mechanically identical to Area15. It re-labels
|
|
650
|
+
* the shared behaviour rather than aliasing it because a definition's `rule.id`
|
|
651
|
+
* must carry its `metadata.drawRule` as a prefix (see the registry invariant in
|
|
652
|
+
* `__tests__/metadata-registry.spec.ts`, ADR-0013).
|
|
653
|
+
*/
|
|
654
|
+
const retainDrawRule = {
|
|
655
|
+
...centerRadiusDrawRule,
|
|
656
|
+
id: "area6:center-radius"
|
|
657
|
+
};
|
|
658
|
+
//#endregion
|
|
659
|
+
//#region src/draw-rules/area11.ts
|
|
660
|
+
const blockDrawRule = createMidpointPerpendicularDrawRule({ id: "area11:block" });
|
|
661
|
+
//#endregion
|
|
662
|
+
//#region src/draw-rules/area12.ts
|
|
663
|
+
/**
|
|
664
|
+
* Area12 anchor draw rule for Disrupt.
|
|
665
|
+
*
|
|
666
|
+
* P1/P2 are the free spine endpoints. P3 is the longest-arrow tip, constrained
|
|
667
|
+
* to the perpendicular axis through P1 (not the baseline midpoint).
|
|
668
|
+
*/
|
|
669
|
+
const disruptDrawRule = createMidpointPerpendicularDrawRule({
|
|
670
|
+
id: "area12:disrupt",
|
|
671
|
+
origin: "p1",
|
|
672
|
+
normal: "left"
|
|
673
|
+
});
|
|
674
|
+
//#endregion
|
|
633
675
|
//#region src/draw-rules/area17.ts
|
|
634
676
|
/**
|
|
635
677
|
* Area17 anchor draw rule for Penetrate.
|
|
@@ -2674,6 +2716,13 @@ function pushEndLabels(out, verts, textForEnd, options = {}) {
|
|
|
2674
2716
|
* measurement is available at generator time.
|
|
2675
2717
|
*/
|
|
2676
2718
|
const LABEL_CHAR_ASPECT_RATIO = .6;
|
|
2719
|
+
/**
|
|
2720
|
+
* Half a single glyph's width, as a ratio of its height — the base boundary
|
|
2721
|
+
* gap a measure reserves for one intrinsic capital letter (Retain's R,
|
|
2722
|
+
* Contain's C). Wider than half `LABEL_CHAR_ASPECT_RATIO` because these
|
|
2723
|
+
* glyphs are stamped alone rather than averaged over a word.
|
|
2724
|
+
*/
|
|
2725
|
+
const LABEL_HALF_GLYPH_RATIO = .45;
|
|
2677
2726
|
/** Estimated rendered width of `text` given its resolved label height in meters. */
|
|
2678
2727
|
function estimatedTextWidth(text, textHeightMeters) {
|
|
2679
2728
|
return text.length * LABEL_CHAR_ASPECT_RATIO * textHeightMeters;
|
|
@@ -2792,7 +2841,7 @@ function pushEndLabelsAbove(out, verts, textForEnd, options = {}) {
|
|
|
2792
2841
|
* as a complete FeatureCollection.
|
|
2793
2842
|
*/
|
|
2794
2843
|
function fixedLabelLineFeatures(positions, options, textForEnd, hostileText, context) {
|
|
2795
|
-
const { part, placement = "beyond", phaseLineName, includePrefix = true, smooth, smoothResolution, ...sizeOptions } = options;
|
|
2844
|
+
const { part, placement = "beyond", phaseLineName, includePrefix = true, lineStyle, smooth, smoothResolution, ...sizeOptions } = options;
|
|
2796
2845
|
const verts = smoothLineVerts(positions.map((p) => project(p[0], p[1])), {
|
|
2797
2846
|
smooth,
|
|
2798
2847
|
smoothResolution
|
|
@@ -2800,7 +2849,10 @@ function fixedLabelLineFeatures(positions, options, textForEnd, hostileText, con
|
|
|
2800
2849
|
const lineCoordinates = smooth ? verts.map((v) => unproject(v[0], v[1])) : positions;
|
|
2801
2850
|
const features = [{
|
|
2802
2851
|
type: "Feature",
|
|
2803
|
-
properties: { part }
|
|
2852
|
+
properties: lineStyle === void 0 ? { part } : {
|
|
2853
|
+
part,
|
|
2854
|
+
style: lineStyle
|
|
2855
|
+
},
|
|
2804
2856
|
geometry: {
|
|
2805
2857
|
type: "LineString",
|
|
2806
2858
|
coordinates: lineCoordinates
|
|
@@ -4205,13 +4257,13 @@ const AREA_DEFENSE_METADATA = {
|
|
|
4205
4257
|
}
|
|
4206
4258
|
]
|
|
4207
4259
|
};
|
|
4208
|
-
function pointOnCircle(center, radius, angle) {
|
|
4260
|
+
function pointOnCircle$1(center, radius, angle) {
|
|
4209
4261
|
return vecAdd(center, [radius * Math.cos(angle), radius * Math.sin(angle)]);
|
|
4210
4262
|
}
|
|
4211
|
-
function sampleArc$
|
|
4263
|
+
function sampleArc$2(center, radius, start, end) {
|
|
4212
4264
|
const span = end - start;
|
|
4213
4265
|
const segments = Math.max(8, Math.round(48 * Math.abs(span) / Math.PI));
|
|
4214
|
-
return Array.from({ length: segments + 1 }, (_, index) => pointOnCircle(center, radius, start + span * index / segments));
|
|
4266
|
+
return Array.from({ length: segments + 1 }, (_, index) => pointOnCircle$1(center, radius, start + span * index / segments));
|
|
4215
4267
|
}
|
|
4216
4268
|
function openArrowhead(tip, direction, length) {
|
|
4217
4269
|
const base = vecSub(tip, [direction[0] * length, direction[1] * length]);
|
|
@@ -4242,8 +4294,8 @@ function createAreaDefense(coordinates, options = {}) {
|
|
|
4242
4294
|
const upperEnd = upperTipAngle;
|
|
4243
4295
|
const lowerStart = labelCenter - labelHalf;
|
|
4244
4296
|
const lowerEnd = upperTipAngle + arrowHalf * 2 - Math.PI * 2;
|
|
4245
|
-
const upperArc = sampleArc$
|
|
4246
|
-
const lowerArc = sampleArc$
|
|
4297
|
+
const upperArc = sampleArc$2(center, radius, upperStart, upperEnd);
|
|
4298
|
+
const lowerArc = sampleArc$2(center, radius, lowerStart, lowerEnd);
|
|
4247
4299
|
const arrowLength = radius * Math.max(0, resolved.arrowheadLengthRatio);
|
|
4248
4300
|
const upperDirection = [-Math.sin(upperEnd), Math.cos(upperEnd)];
|
|
4249
4301
|
const lowerDirection = [Math.sin(lowerEnd), -Math.cos(lowerEnd)];
|
|
@@ -4268,17 +4320,17 @@ function createAreaDefense(coordinates, options = {}) {
|
|
|
4268
4320
|
const barbPolygons = [];
|
|
4269
4321
|
for (const arc of barbArcs) for (let ordinal = 1; ordinal <= arc.count; ordinal++) {
|
|
4270
4322
|
const angle = arc.start + (arc.end - arc.start) * ordinal / (arc.count + 1);
|
|
4271
|
-
const left = pointOnCircle(center, radius, angle - barbHalfAngle);
|
|
4272
|
-
const right = pointOnCircle(center, radius, angle + barbHalfAngle);
|
|
4323
|
+
const left = pointOnCircle$1(center, radius, angle - barbHalfAngle);
|
|
4324
|
+
const right = pointOnCircle$1(center, radius, angle + barbHalfAngle);
|
|
4273
4325
|
const ring = [
|
|
4274
4326
|
left,
|
|
4275
|
-
pointOnCircle(center, radius + barbLength, angle),
|
|
4327
|
+
pointOnCircle$1(center, radius + barbLength, angle),
|
|
4276
4328
|
right,
|
|
4277
4329
|
left
|
|
4278
4330
|
].map((point) => unproject(point[0], point[1]));
|
|
4279
4331
|
barbPolygons.push([ring]);
|
|
4280
4332
|
}
|
|
4281
|
-
const labelPoint = pointOnCircle(center, radius, labelCenter);
|
|
4333
|
+
const labelPoint = pointOnCircle$1(center, radius, labelCenter);
|
|
4282
4334
|
return {
|
|
4283
4335
|
type: "FeatureCollection",
|
|
4284
4336
|
features: [
|
|
@@ -4330,6 +4382,273 @@ const AREA_DEFENSE = defineControlMeasure({
|
|
|
4330
4382
|
}
|
|
4331
4383
|
});
|
|
4332
4384
|
//#endregion
|
|
4385
|
+
//#region src/generators/cm15-maneuver-areas/engagementArea.ts
|
|
4386
|
+
/** Default options for the Engagement area control measure. */
|
|
4387
|
+
const DEFAULT_ENGAGEMENT_AREA_OPTIONS = {
|
|
4388
|
+
smooth: false,
|
|
4389
|
+
smoothResolution: 16
|
|
4390
|
+
};
|
|
4391
|
+
const ENGAGEMENT_AREA_METADATA = {
|
|
4392
|
+
id: "engagement-area",
|
|
4393
|
+
name: "Engagement area",
|
|
4394
|
+
description: "An area where the commander intends to contain and destroy an enemy force with the massed effects of all available weapons and supporting systems.",
|
|
4395
|
+
entity: "Maneuver Areas",
|
|
4396
|
+
entityType: "Engagement Area",
|
|
4397
|
+
value: "151300",
|
|
4398
|
+
minCoordinates: 3,
|
|
4399
|
+
geometry: "area",
|
|
4400
|
+
geometryTypes: [
|
|
4401
|
+
"Polygon",
|
|
4402
|
+
"MultiLineString",
|
|
4403
|
+
"Point"
|
|
4404
|
+
],
|
|
4405
|
+
paints: {
|
|
4406
|
+
stroke: true,
|
|
4407
|
+
fill: "none",
|
|
4408
|
+
text: true
|
|
4409
|
+
},
|
|
4410
|
+
drawRule: "Area1",
|
|
4411
|
+
capturesLabelSize: true,
|
|
4412
|
+
params: LABELED_AREA_PARAMS,
|
|
4413
|
+
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
4414
|
+
};
|
|
4415
|
+
/**
|
|
4416
|
+
* Creates an Engagement area control measure: a closed area through
|
|
4417
|
+
* `positions` labeled at its center with the doctrinal `EA` designation plus
|
|
4418
|
+
* an optional Field T unique designation and Field N ENY marker.
|
|
4419
|
+
*/
|
|
4420
|
+
function createEngagementArea(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
4421
|
+
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "EA"), context.amplifierPlacements, context);
|
|
4422
|
+
}
|
|
4423
|
+
const ENGAGEMENT_AREA = defineControlMeasure({
|
|
4424
|
+
metadata: ENGAGEMENT_AREA_METADATA,
|
|
4425
|
+
generator: createEngagementArea,
|
|
4426
|
+
defaultOptions: DEFAULT_ENGAGEMENT_AREA_OPTIONS,
|
|
4427
|
+
rule: area1DrawRule,
|
|
4428
|
+
previewSample: {
|
|
4429
|
+
controlPoints: [
|
|
4430
|
+
[-1, -.5],
|
|
4431
|
+
[1, -.5],
|
|
4432
|
+
[1, .5],
|
|
4433
|
+
[-1, .5]
|
|
4434
|
+
],
|
|
4435
|
+
textAmplifiers: { T: "ALPHA" },
|
|
4436
|
+
options: { labelSize: 96 }
|
|
4437
|
+
}
|
|
4438
|
+
});
|
|
4439
|
+
//#endregion
|
|
4440
|
+
//#region src/internal/arrowhead.ts
|
|
4441
|
+
/**
|
|
4442
|
+
* Builds a closed, filled arrowhead triangle ring in unprojected coordinates.
|
|
4443
|
+
*
|
|
4444
|
+
* `tip` is the point of the arrow, `dir` the unit direction the arrow points,
|
|
4445
|
+
* `length` the distance from tip back to the base, and `width` the base width.
|
|
4446
|
+
* The returned ring is `[left, tip, right, left]`.
|
|
4447
|
+
*/
|
|
4448
|
+
function createArrowHeadRing(tip, dir, length, width) {
|
|
4449
|
+
const perp = [-dir[1], dir[0]];
|
|
4450
|
+
const base = vecSub(tip, vecScale(dir, length));
|
|
4451
|
+
const left = vecAdd(base, vecScale(perp, width / 2));
|
|
4452
|
+
const right = vecSub(base, vecScale(perp, width / 2));
|
|
4453
|
+
return [
|
|
4454
|
+
unproject(left[0], left[1]),
|
|
4455
|
+
unproject(tip[0], tip[1]),
|
|
4456
|
+
unproject(right[0], right[1]),
|
|
4457
|
+
unproject(left[0], left[1])
|
|
4458
|
+
];
|
|
4459
|
+
}
|
|
4460
|
+
//#endregion
|
|
4461
|
+
//#region src/internal/projected-arc.ts
|
|
4462
|
+
/** The arc point at `angle`, in projected meters. */
|
|
4463
|
+
function arcPointAt(center, startRadius, bulgeRadius, angle) {
|
|
4464
|
+
return vecAdd(center, vecAdd(vecScale(startRadius, Math.cos(angle)), vecScale(bulgeRadius, Math.sin(angle))));
|
|
4465
|
+
}
|
|
4466
|
+
/**
|
|
4467
|
+
* Samples the arc from `start` to `end` radians as unprojected positions.
|
|
4468
|
+
* `resolution` is the segment count a full half-turn would use, so the density
|
|
4469
|
+
* of a partial arc stays proportional to how much of the circle it covers.
|
|
4470
|
+
*/
|
|
4471
|
+
function sampleProjectedArc(center, startRadius, bulgeRadius, start, end, resolution) {
|
|
4472
|
+
const segments = Math.max(2, Math.ceil(resolution * Math.abs(end - start) / Math.PI));
|
|
4473
|
+
return Array.from({ length: segments + 1 }, (_, index) => {
|
|
4474
|
+
const point = arcPointAt(center, startRadius, bulgeRadius, start + (end - start) * index / segments);
|
|
4475
|
+
return unproject(point[0], point[1]);
|
|
4476
|
+
});
|
|
4477
|
+
}
|
|
4478
|
+
//#endregion
|
|
4479
|
+
//#region src/generators/cm15-maneuver-areas/mobile-defense.ts
|
|
4480
|
+
const DEFAULT_MOBILE_DEFENSE_OPTIONS = { labelPadding: 0 };
|
|
4481
|
+
const MOBILE_DEFENSE_METADATA = {
|
|
4482
|
+
id: "mobile-defense",
|
|
4483
|
+
name: "Mobile Defense",
|
|
4484
|
+
description: "A defensive maneuver that uses terrain, fires, and maneuver to defeat an attacking enemy force.",
|
|
4485
|
+
entity: "Maneuver Areas",
|
|
4486
|
+
entityType: "Mobile Defense",
|
|
4487
|
+
value: "152800",
|
|
4488
|
+
minCoordinates: 3,
|
|
4489
|
+
maxCoordinates: 3,
|
|
4490
|
+
geometry: "area",
|
|
4491
|
+
geometryTypes: [
|
|
4492
|
+
"MultiLineString",
|
|
4493
|
+
"MultiPolygon",
|
|
4494
|
+
"Point"
|
|
4495
|
+
],
|
|
4496
|
+
paints: {
|
|
4497
|
+
stroke: true,
|
|
4498
|
+
fill: "fixed",
|
|
4499
|
+
text: true
|
|
4500
|
+
},
|
|
4501
|
+
drawRule: "Area27",
|
|
4502
|
+
capturesLabelSize: true,
|
|
4503
|
+
params: [{
|
|
4504
|
+
key: "labelPadding",
|
|
4505
|
+
presentationTier: "advanced",
|
|
4506
|
+
label: "Label padding",
|
|
4507
|
+
description: "Extra clearance around MD, as a ratio of label height",
|
|
4508
|
+
type: "number",
|
|
4509
|
+
min: 0,
|
|
4510
|
+
max: 2,
|
|
4511
|
+
step: .05
|
|
4512
|
+
}]
|
|
4513
|
+
};
|
|
4514
|
+
const ARC_RESOLUTION = 48;
|
|
4515
|
+
const STRAIGHT_ARROW_LENGTH_RATIO = .12;
|
|
4516
|
+
const STRAIGHT_ARROW_WIDTH_RATIO = .12;
|
|
4517
|
+
const TOP_STRAIGHT_BARB_POSITION_RATIO = .25;
|
|
4518
|
+
const FIRST_BARB_POSITION_RATIO = .29;
|
|
4519
|
+
const FIRST_BARB_CLEARANCE_RATIO = .01;
|
|
4520
|
+
const BARB_WIDTH_RATIO = .45;
|
|
4521
|
+
const BARB_LENGTH_RATIO = .42;
|
|
4522
|
+
const ARC_BARB_BASE_SEGMENTS = 8;
|
|
4523
|
+
function createBarb(center, along, outward, width, length) {
|
|
4524
|
+
const left = vecAdd(center, vecScale(along, width / 2));
|
|
4525
|
+
const right = vecSub(center, vecScale(along, width / 2));
|
|
4526
|
+
const tip = vecAdd(center, vecScale(outward, length));
|
|
4527
|
+
return [
|
|
4528
|
+
toPosition(left),
|
|
4529
|
+
toPosition(tip),
|
|
4530
|
+
toPosition(right),
|
|
4531
|
+
toPosition(left)
|
|
4532
|
+
];
|
|
4533
|
+
}
|
|
4534
|
+
function createArcBarb(center, startRadius, bulgeRadius, angle, halfWidthAngle, length) {
|
|
4535
|
+
const point = arcPointAt(center, startRadius, bulgeRadius, angle);
|
|
4536
|
+
const outward = vecNorm(vecSub(point, center));
|
|
4537
|
+
const baseStart = arcPointAt(center, startRadius, bulgeRadius, angle - halfWidthAngle);
|
|
4538
|
+
const baseEnd = arcPointAt(center, startRadius, bulgeRadius, angle + halfWidthAngle);
|
|
4539
|
+
const sagitta = vecMag(startRadius) * (1 - Math.cos(halfWidthAngle));
|
|
4540
|
+
const tip = vecAdd(point, vecScale(outward, Math.max(0, length - sagitta)));
|
|
4541
|
+
const curvedBase = Array.from({ length: 9 }, (_, index) => toPosition(arcPointAt(center, startRadius, bulgeRadius, angle - halfWidthAngle + 2 * halfWidthAngle * index / ARC_BARB_BASE_SEGMENTS)));
|
|
4542
|
+
return [
|
|
4543
|
+
toPosition(baseStart),
|
|
4544
|
+
toPosition(tip),
|
|
4545
|
+
toPosition(baseEnd),
|
|
4546
|
+
...curvedBase.slice(1, -1).reverse(),
|
|
4547
|
+
toPosition(baseStart)
|
|
4548
|
+
];
|
|
4549
|
+
}
|
|
4550
|
+
function createMobileDefense(coordinates, options = {}, _textAmplifiers = {}, context = {}) {
|
|
4551
|
+
const p1 = project(coordinates[0][0], coordinates[0][1]);
|
|
4552
|
+
const p2 = project(coordinates[1][0], coordinates[1][1]);
|
|
4553
|
+
const p3 = project(coordinates[2][0], coordinates[2][1]);
|
|
4554
|
+
const lineVector = vecSub(p2, p1);
|
|
4555
|
+
const diameterVector = vecSub(p3, p2);
|
|
4556
|
+
const lineLength = vecMag(lineVector);
|
|
4557
|
+
const diameter = vecMag(diameterVector);
|
|
4558
|
+
if (lineLength < 1e-6 || diameter < 1e-6) return {
|
|
4559
|
+
type: "FeatureCollection",
|
|
4560
|
+
features: []
|
|
4561
|
+
};
|
|
4562
|
+
const lineDirection = vecNorm(lineVector);
|
|
4563
|
+
const lineNormal = [-lineDirection[1], lineDirection[0]];
|
|
4564
|
+
const center = vecAdd(p2, vecScale(diameterVector, .5));
|
|
4565
|
+
const startRadius = vecScale(diameterVector, .5);
|
|
4566
|
+
const bulgeRadius = [startRadius[1], -startRadius[0]];
|
|
4567
|
+
const arrowLength = lineLength * STRAIGHT_ARROW_LENGTH_RATIO;
|
|
4568
|
+
const arrowWidth = lineLength * STRAIGHT_ARROW_WIDTH_RATIO;
|
|
4569
|
+
const topStart = vecAdd(vecAdd(p1, diameterVector), vecScale(lineDirection, arrowLength));
|
|
4570
|
+
const labelPoint = arcPointAt(center, startRadius, bulgeRadius, Math.PI / 2);
|
|
4571
|
+
const metrics = resolveTextMetrics("MD", options, context);
|
|
4572
|
+
const padding = Math.max(0, options.labelPadding ?? DEFAULT_MOBILE_DEFENSE_OPTIONS.labelPadding);
|
|
4573
|
+
const halfGap = Math.min(Math.PI * diameter / 4 - EPSILON, metrics.height / 2 + metrics.height * padding);
|
|
4574
|
+
const gapAngle = Math.max(0, halfGap / (diameter / 2));
|
|
4575
|
+
const upperArc = sampleProjectedArc(center, startRadius, bulgeRadius, 0, Math.PI / 2 - gapAngle, ARC_RESOLUTION);
|
|
4576
|
+
const lowerArc = sampleProjectedArc(center, startRadius, bulgeRadius, Math.PI / 2 + gapAngle, Math.PI, ARC_RESOLUTION);
|
|
4577
|
+
const boundary = [
|
|
4578
|
+
[toPosition(topStart), toPosition(p3)],
|
|
4579
|
+
[toPosition(p1), toPosition(p2)],
|
|
4580
|
+
upperArc,
|
|
4581
|
+
lowerArc,
|
|
4582
|
+
createArrowHeadRing(p1, vecScale(lineDirection, -1), arrowLength, arrowWidth).slice(0, -1)
|
|
4583
|
+
];
|
|
4584
|
+
const firstBarbClearance = lineLength * FIRST_BARB_CLEARANCE_RATIO;
|
|
4585
|
+
const availableStraightLength = lineLength - arrowLength - firstBarbClearance;
|
|
4586
|
+
const barbScale = Math.min(diameter, availableStraightLength / BARB_WIDTH_RATIO);
|
|
4587
|
+
const barbWidth = barbScale * BARB_WIDTH_RATIO;
|
|
4588
|
+
const barbLength = barbScale * BARB_LENGTH_RATIO;
|
|
4589
|
+
const firstBarbDistance = Math.min(lineLength - barbWidth / 2, Math.max(lineLength * FIRST_BARB_POSITION_RATIO, arrowLength + barbWidth / 2 + firstBarbClearance));
|
|
4590
|
+
const arcBarbHalfWidthAngle = Math.asin(Math.min(1, barbWidth / diameter));
|
|
4591
|
+
const barbs = [
|
|
4592
|
+
[createBarb(vecAdd(topStart, vecScale(vecSub(p3, topStart), TOP_STRAIGHT_BARB_POSITION_RATIO)), lineDirection, lineNormal, barbWidth, barbLength)],
|
|
4593
|
+
[createBarb(vecAdd(p1, vecScale(lineDirection, firstBarbDistance)), lineDirection, vecScale(lineNormal, -1), barbWidth, barbLength)],
|
|
4594
|
+
[createArcBarb(center, startRadius, bulgeRadius, arcBarbHalfWidthAngle, arcBarbHalfWidthAngle, barbLength)],
|
|
4595
|
+
[createArcBarb(center, startRadius, bulgeRadius, Math.PI - arcBarbHalfWidthAngle, arcBarbHalfWidthAngle, barbLength)]
|
|
4596
|
+
];
|
|
4597
|
+
return {
|
|
4598
|
+
type: "FeatureCollection",
|
|
4599
|
+
features: [
|
|
4600
|
+
{
|
|
4601
|
+
type: "Feature",
|
|
4602
|
+
properties: { part: "mobile-defense" },
|
|
4603
|
+
geometry: {
|
|
4604
|
+
type: "MultiLineString",
|
|
4605
|
+
coordinates: boundary
|
|
4606
|
+
}
|
|
4607
|
+
},
|
|
4608
|
+
{
|
|
4609
|
+
type: "Feature",
|
|
4610
|
+
properties: {
|
|
4611
|
+
part: "barbs",
|
|
4612
|
+
fill: true,
|
|
4613
|
+
style: SOLID_ACCENT_FILL
|
|
4614
|
+
},
|
|
4615
|
+
geometry: {
|
|
4616
|
+
type: "MultiPolygon",
|
|
4617
|
+
coordinates: barbs
|
|
4618
|
+
}
|
|
4619
|
+
},
|
|
4620
|
+
{
|
|
4621
|
+
type: "Feature",
|
|
4622
|
+
properties: {
|
|
4623
|
+
part: "label",
|
|
4624
|
+
labelPlacement: false,
|
|
4625
|
+
text: "MD",
|
|
4626
|
+
rotation: labelRotationAlong(lineDirection),
|
|
4627
|
+
...labelSizeProps(options)
|
|
4628
|
+
},
|
|
4629
|
+
geometry: {
|
|
4630
|
+
type: "Point",
|
|
4631
|
+
coordinates: toPosition(labelPoint)
|
|
4632
|
+
}
|
|
4633
|
+
}
|
|
4634
|
+
]
|
|
4635
|
+
};
|
|
4636
|
+
}
|
|
4637
|
+
const MOBILE_DEFENSE = defineControlMeasure({
|
|
4638
|
+
metadata: MOBILE_DEFENSE_METADATA,
|
|
4639
|
+
generator: createMobileDefense,
|
|
4640
|
+
defaultOptions: DEFAULT_MOBILE_DEFENSE_OPTIONS,
|
|
4641
|
+
rule: area27DrawRule,
|
|
4642
|
+
previewSample: {
|
|
4643
|
+
controlPoints: [
|
|
4644
|
+
[-1, -.25],
|
|
4645
|
+
[.65, -.25],
|
|
4646
|
+
[.65, .35]
|
|
4647
|
+
],
|
|
4648
|
+
options: { labelSize: 90 }
|
|
4649
|
+
}
|
|
4650
|
+
});
|
|
4651
|
+
//#endregion
|
|
4333
4652
|
//#region src/generators/cm12-command-control-areas/area-of-operations.ts
|
|
4334
4653
|
/** Default options for the Area of operations control measure. */
|
|
4335
4654
|
const DEFAULT_AREA_OF_OPERATIONS_OPTIONS = {
|
|
@@ -5653,33 +5972,36 @@ const BATTLE_POSITION_METADATA = {
|
|
|
5653
5972
|
textAmplifiers: AREA_UNIT_DESIGNATION_HOSTILE_AMPLIFIERS
|
|
5654
5973
|
};
|
|
5655
5974
|
/**
|
|
5656
|
-
*
|
|
5657
|
-
*
|
|
5658
|
-
*
|
|
5975
|
+
* Shared body of Battle Position and its dashed sibling, Battle Position
|
|
5976
|
+
* Prepared (P) but not Occupied: the closed (optionally smoothed) ring, the
|
|
5977
|
+
* Field B echelon glyph straddling the boundary with its masking gap, the
|
|
5978
|
+
* centered Field T designation and Field N "ENY" markers, and the gapped
|
|
5979
|
+
* boundary that merges every mask (ADR-0023). The two measures differ only in
|
|
5980
|
+
* the `namePrefix` put in front of Field T and the intrinsic `boundaryStyle`
|
|
5981
|
+
* the boundary feature carries, so both are parameters here — mirroring
|
|
5982
|
+
* `echelon-labeled-area.ts`'s role for Base Camp / Guerrilla Base.
|
|
5659
5983
|
*
|
|
5660
|
-
*
|
|
5661
|
-
*
|
|
5662
|
-
* @param options - {@link BattlePositionOptions}.
|
|
5663
|
-
* @param textAmplifiers - Field T (designation, centered in the area) plus an
|
|
5664
|
-
* optional Field N "ENY" hostile marker at the west/east edges (no H/W rows —
|
|
5665
|
-
* this measure's template pairs the designation with the Field B glyph, not
|
|
5666
|
-
* the plain L-15 boundary template).
|
|
5984
|
+
* Strong Point deliberately keeps its own copy: its perimeter tics are new
|
|
5985
|
+
* geometry, not a parameter swap.
|
|
5667
5986
|
*/
|
|
5668
|
-
function
|
|
5987
|
+
function battlePositionAreaFeatures(positions, options, textAmplifiers, context, variant = {}) {
|
|
5669
5988
|
const { echelon = DEFAULT_BATTLE_POSITION_OPTIONS.echelon, echelonSize = DEFAULT_BATTLE_POSITION_OPTIONS.echelonSize, echelonSizePixels, echelonPadding = DEFAULT_BATTLE_POSITION_OPTIONS.echelonPadding, echelonPosition = DEFAULT_BATTLE_POSITION_OPTIONS.echelonPosition, echelonAnchor, metersPerPixel, smooth = DEFAULT_BATTLE_POSITION_OPTIONS.smooth, smoothResolution = DEFAULT_BATTLE_POSITION_OPTIONS.smoothResolution } = options;
|
|
5670
5989
|
const h = resolveEchelonHeight(echelonSize, echelonSizePixels, metersPerPixel);
|
|
5671
5990
|
const verts = buildClosedRing(positions, smooth, smoothResolution, DEFAULT_SMOOTH_RESOLUTION$6);
|
|
5672
5991
|
const { strokes, fills, gaps } = placeEchelon(buildAreaPerimeter(verts), h, echelon, echelonPadding, echelonPosition, echelonAnchor);
|
|
5673
5992
|
const labelFeatures = [];
|
|
5674
5993
|
const enyGaps = pushAreaLabels(labelFeatures, verts, {
|
|
5675
|
-
name: textAmplifiers.T,
|
|
5994
|
+
name: variant.namePrefix ? composeAreaName(variant.namePrefix, textAmplifiers.T) : textAmplifiers.T,
|
|
5676
5995
|
hostile: textAmplifiers.N
|
|
5677
5996
|
}, options, context.amplifierPlacements, void 0, context);
|
|
5678
5997
|
const boundaryCoords = buildGappedLine(verts, [...gaps, ...enyGaps]);
|
|
5679
5998
|
const features = [];
|
|
5680
5999
|
if (boundaryCoords.length > 0) features.push({
|
|
5681
6000
|
type: "Feature",
|
|
5682
|
-
properties:
|
|
6001
|
+
properties: variant.boundaryStyle ? {
|
|
6002
|
+
part: "boundary",
|
|
6003
|
+
style: variant.boundaryStyle
|
|
6004
|
+
} : { part: "boundary" },
|
|
5683
6005
|
geometry: {
|
|
5684
6006
|
type: "MultiLineString",
|
|
5685
6007
|
coordinates: boundaryCoords
|
|
@@ -5712,6 +6034,22 @@ function createBattlePosition(positions, options = {}, textAmplifiers = {}, cont
|
|
|
5712
6034
|
};
|
|
5713
6035
|
}
|
|
5714
6036
|
/**
|
|
6037
|
+
* Creates a Battle Position control measure: a closed area through `positions`
|
|
6038
|
+
* carrying a single echelon (Field B) glyph straddling its boundary — anchored
|
|
6039
|
+
* to the bottom edge by default, slidable via `echelonPosition`.
|
|
6040
|
+
*
|
|
6041
|
+
* @param positions - Area anchor points (≥3); the input contract is enforced at
|
|
6042
|
+
* the render seam (ADR-0014).
|
|
6043
|
+
* @param options - {@link BattlePositionOptions}.
|
|
6044
|
+
* @param textAmplifiers - Field T (designation, centered in the area) plus an
|
|
6045
|
+
* optional Field N "ENY" hostile marker at the west/east edges (no H/W rows —
|
|
6046
|
+
* this measure's template pairs the designation with the Field B glyph, not
|
|
6047
|
+
* the plain L-15 boundary template).
|
|
6048
|
+
*/
|
|
6049
|
+
function createBattlePosition(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
6050
|
+
return battlePositionAreaFeatures(positions, options, textAmplifiers, context);
|
|
6051
|
+
}
|
|
6052
|
+
/**
|
|
5715
6053
|
* `freezeOrientation` hook (ADR-0023) shared by Battle Position and Strong
|
|
5716
6054
|
* Point: pins the echelon anchor to its current bottom-edge fraction so a
|
|
5717
6055
|
* subsequent rigid rotation of `controlPoints` (e.g. the transform box's
|
|
@@ -5749,32 +6087,276 @@ const BATTLE_POSITION = defineControlMeasure({
|
|
|
5749
6087
|
}
|
|
5750
6088
|
});
|
|
5751
6089
|
//#endregion
|
|
5752
|
-
//#region src/generators/
|
|
5753
|
-
|
|
5754
|
-
const
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
6090
|
+
//#region src/generators/cm15-maneuver-areas/battlePositionPrepared.ts
|
|
6091
|
+
/** Intrinsic dash pattern of the prepared-but-not-occupied boundary. */
|
|
6092
|
+
const BATTLE_POSITION_PREPARED_DASH = [6, 4];
|
|
6093
|
+
/** Default options for the Battle Position Prepared control measure. */
|
|
6094
|
+
const DEFAULT_BATTLE_POSITION_PREPARED_OPTIONS = { ...DEFAULT_BATTLE_POSITION_OPTIONS };
|
|
6095
|
+
const BATTLE_POSITION_PREPARED_METADATA = {
|
|
6096
|
+
id: "battle-position-prepared",
|
|
6097
|
+
name: "Battle Position Prepared (P) but not Occupied",
|
|
6098
|
+
description: "A battle position that has been prepared for occupation but is not yet occupied, drawn with a dashed boundary and a (P)-prefixed designation.",
|
|
6099
|
+
entity: "Maneuver Areas",
|
|
6100
|
+
entityType: "Battle Position",
|
|
6101
|
+
entitySubtype: "Battle Position Prepared (P) but not Occupied",
|
|
6102
|
+
value: "151202",
|
|
6103
|
+
minCoordinates: 3,
|
|
6104
|
+
geometry: "area",
|
|
6105
|
+
geometryTypes: [
|
|
6106
|
+
"MultiLineString",
|
|
6107
|
+
"MultiPolygon",
|
|
6108
|
+
"Point"
|
|
6109
|
+
],
|
|
5765
6110
|
paints: {
|
|
5766
6111
|
stroke: true,
|
|
5767
|
-
fill: "
|
|
5768
|
-
text:
|
|
6112
|
+
fill: "fixed",
|
|
6113
|
+
text: true
|
|
5769
6114
|
},
|
|
5770
|
-
drawRule: "
|
|
6115
|
+
drawRule: "Area1",
|
|
6116
|
+
capturesLabelSize: true,
|
|
6117
|
+
params: ECHELON_AREA_PARAMS,
|
|
6118
|
+
textAmplifiers: AREA_UNIT_DESIGNATION_HOSTILE_AMPLIFIERS
|
|
5771
6119
|
};
|
|
5772
|
-
const DEFAULT_BLOCK_OPTIONS = {};
|
|
5773
6120
|
/**
|
|
5774
|
-
*
|
|
6121
|
+
* Creates a Battle Position Prepared (P) but not Occupied control measure: a
|
|
6122
|
+
* Battle Position whose boundary carries an intrinsic dash and whose centered
|
|
6123
|
+
* designation is prefixed with `(P)`. Delegates to
|
|
6124
|
+
* {@link battlePositionAreaFeatures}, the shared Battle Position body.
|
|
5775
6125
|
*
|
|
5776
|
-
* @param
|
|
5777
|
-
*
|
|
6126
|
+
* @param positions - Area anchor points (≥3); the input contract is enforced at
|
|
6127
|
+
* the render seam (ADR-0014).
|
|
6128
|
+
* @param options - {@link BattlePositionPreparedOptions}.
|
|
6129
|
+
* @param textAmplifiers - Field T (designation, centered in the area and
|
|
6130
|
+
* prefixed with `(P)`) plus an optional Field N "ENY" hostile marker at the
|
|
6131
|
+
* west/east edges.
|
|
6132
|
+
*/
|
|
6133
|
+
function createBattlePositionPrepared(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
6134
|
+
return battlePositionAreaFeatures(positions, options, textAmplifiers, context, {
|
|
6135
|
+
namePrefix: "(P)",
|
|
6136
|
+
boundaryStyle: { strokeDash: [...BATTLE_POSITION_PREPARED_DASH] }
|
|
6137
|
+
});
|
|
6138
|
+
}
|
|
6139
|
+
const BATTLE_POSITION_PREPARED = defineControlMeasure({
|
|
6140
|
+
metadata: BATTLE_POSITION_PREPARED_METADATA,
|
|
6141
|
+
generator: createBattlePositionPrepared,
|
|
6142
|
+
defaultOptions: DEFAULT_BATTLE_POSITION_PREPARED_OPTIONS,
|
|
6143
|
+
rule: area1DrawRule,
|
|
6144
|
+
freezeOrientation: freezeEchelonAnchor,
|
|
6145
|
+
previewSample: {
|
|
6146
|
+
controlPoints: [
|
|
6147
|
+
[-1, -.5],
|
|
6148
|
+
[1, -.5],
|
|
6149
|
+
[1, .5],
|
|
6150
|
+
[-1, .5]
|
|
6151
|
+
],
|
|
6152
|
+
textAmplifiers: { T: "MARS" },
|
|
6153
|
+
options: {
|
|
6154
|
+
echelonSize: 200,
|
|
6155
|
+
smooth: true,
|
|
6156
|
+
labelSize: 96
|
|
6157
|
+
}
|
|
6158
|
+
}
|
|
6159
|
+
});
|
|
6160
|
+
//#endregion
|
|
6161
|
+
//#region src/generators/cm15-maneuver-areas/contain.ts
|
|
6162
|
+
const DEFAULT_CONTAIN_OPTIONS = {
|
|
6163
|
+
labelPadding: .2,
|
|
6164
|
+
resolution: 32
|
|
6165
|
+
};
|
|
6166
|
+
const CONTAIN_METADATA = {
|
|
6167
|
+
id: "contain",
|
|
6168
|
+
name: "Contain",
|
|
6169
|
+
description: "Stops, holds, or surrounds enemy forces and prevents them from withdrawing or using them elsewhere.",
|
|
6170
|
+
entity: "Maneuver Areas",
|
|
6171
|
+
entityType: "Battle Position",
|
|
6172
|
+
entitySubtype: "Contain",
|
|
6173
|
+
value: "151204",
|
|
6174
|
+
minCoordinates: 3,
|
|
6175
|
+
maxCoordinates: 3,
|
|
6176
|
+
geometry: "area",
|
|
6177
|
+
geometryTypes: ["MultiLineString", "Point"],
|
|
6178
|
+
paints: {
|
|
6179
|
+
stroke: true,
|
|
6180
|
+
fill: "none",
|
|
6181
|
+
text: true
|
|
6182
|
+
},
|
|
6183
|
+
drawRule: "Area5",
|
|
6184
|
+
capturesLabelSize: true,
|
|
6185
|
+
textAmplifiers: [AREA_HOSTILE_AMPLIFIER],
|
|
6186
|
+
params: [{
|
|
6187
|
+
key: "labelPadding",
|
|
6188
|
+
presentationTier: "advanced",
|
|
6189
|
+
label: "Label padding",
|
|
6190
|
+
description: "Extra clearance around C and ENY, as a ratio of label height",
|
|
6191
|
+
type: "number",
|
|
6192
|
+
min: 0,
|
|
6193
|
+
max: 2,
|
|
6194
|
+
step: .05
|
|
6195
|
+
}, {
|
|
6196
|
+
key: "resolution",
|
|
6197
|
+
presentationTier: "advanced",
|
|
6198
|
+
label: "Resolution",
|
|
6199
|
+
description: "Number of segments used to approximate the semicircle",
|
|
6200
|
+
type: "number",
|
|
6201
|
+
min: 8,
|
|
6202
|
+
max: 128,
|
|
6203
|
+
step: 1
|
|
6204
|
+
}]
|
|
6205
|
+
};
|
|
6206
|
+
/** Where the ENY label sits along the arrow, as a ratio of its full length. */
|
|
6207
|
+
const HOSTILE_LABEL_POSITION_RATIO = .55;
|
|
6208
|
+
/** Creates the Contain semicircle, inward tics, center-pointing arrow, and C label. */
|
|
6209
|
+
function createContain(coordinates, options = {}, textAmplifiers = {}, context = {}) {
|
|
6210
|
+
const p1 = project(coordinates[0][0], coordinates[0][1]);
|
|
6211
|
+
const p2 = project(coordinates[1][0], coordinates[1][1]);
|
|
6212
|
+
const p3 = project(coordinates[2][0], coordinates[2][1]);
|
|
6213
|
+
const diameterLength = vecMag(vecSub(p2, p1));
|
|
6214
|
+
if (diameterLength < 1e-6) return {
|
|
6215
|
+
type: "FeatureCollection",
|
|
6216
|
+
features: []
|
|
6217
|
+
};
|
|
6218
|
+
const center = [(p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2];
|
|
6219
|
+
const arrowVector = vecSub(p3, center);
|
|
6220
|
+
const arrowLength = vecMag(arrowVector);
|
|
6221
|
+
if (arrowLength < 1e-6) return {
|
|
6222
|
+
type: "FeatureCollection",
|
|
6223
|
+
features: []
|
|
6224
|
+
};
|
|
6225
|
+
const radius = diameterLength / 2;
|
|
6226
|
+
const openingDirection = vecNorm(arrowVector);
|
|
6227
|
+
const labelRotation = labelRotationAlong(openingDirection);
|
|
6228
|
+
const startRadius = vecSub(p1, center);
|
|
6229
|
+
const bulgeRadius = vecScale(openingDirection, -radius);
|
|
6230
|
+
const h = Math.min(radius * .45, Math.max(EPSILON, resolveLabelOffsetMeters(options, 1, radius * .12)));
|
|
6231
|
+
const labelPadding = Math.max(0, options.labelPadding ?? DEFAULT_CONTAIN_OPTIONS.labelPadding);
|
|
6232
|
+
const resolution = Math.round(Math.min(128, Math.max(8, options.resolution ?? DEFAULT_CONTAIN_OPTIONS.resolution)));
|
|
6233
|
+
const hostileText = textAmplifiers.N ?? "ENY";
|
|
6234
|
+
const labelHalfAngle = Math.min(Math.PI / 3, h * (LABEL_HALF_GLYPH_RATIO + labelPadding) / radius);
|
|
6235
|
+
const labelAngle = Math.PI / 2;
|
|
6236
|
+
const boundary = [sampleProjectedArc(center, startRadius, bulgeRadius, 0, labelAngle - labelHalfAngle, resolution), sampleProjectedArc(center, startRadius, bulgeRadius, labelAngle + labelHalfAngle, Math.PI, resolution)];
|
|
6237
|
+
const tics = [];
|
|
6238
|
+
const spacingAngle = h / radius;
|
|
6239
|
+
for (let angle = spacingAngle / 2; angle < Math.PI; angle += spacingAngle) {
|
|
6240
|
+
if (Math.abs(angle - labelAngle) <= labelHalfAngle) continue;
|
|
6241
|
+
const outer = arcPointAt(center, startRadius, bulgeRadius, angle);
|
|
6242
|
+
const inner = vecAdd(outer, vecScale(vecNorm(vecSub(center, outer)), h));
|
|
6243
|
+
tics.push([unproject(outer[0], outer[1]), unproject(inner[0], inner[1])]);
|
|
6244
|
+
}
|
|
6245
|
+
const arrowhead = createArrowHeadRing(center, vecScale(openingDirection, -1), h, h).slice(0, 3);
|
|
6246
|
+
const arrow = [];
|
|
6247
|
+
const hostilePoint = vecAdd(center, vecScale(arrowVector, HOSTILE_LABEL_POSITION_RATIO));
|
|
6248
|
+
if (hostileText) {
|
|
6249
|
+
const metrics = resolveTextMetrics(hostileText, options, context);
|
|
6250
|
+
const maximumHalfGap = arrowLength * Math.min(HOSTILE_LABEL_POSITION_RATIO, 1 - HOSTILE_LABEL_POSITION_RATIO);
|
|
6251
|
+
const halfGap = Math.min(maximumHalfGap, metrics.width / 2 + labelPadding * metrics.height);
|
|
6252
|
+
const farGapEdge = vecAdd(hostilePoint, vecScale(openingDirection, halfGap));
|
|
6253
|
+
const nearGapEdge = vecAdd(hostilePoint, vecScale(openingDirection, -halfGap));
|
|
6254
|
+
arrow.push([unproject(p3[0], p3[1]), unproject(farGapEdge[0], farGapEdge[1])], [unproject(nearGapEdge[0], nearGapEdge[1]), unproject(center[0], center[1])]);
|
|
6255
|
+
} else arrow.push([unproject(p3[0], p3[1]), unproject(center[0], center[1])]);
|
|
6256
|
+
arrow.push(arrowhead);
|
|
6257
|
+
const labelPoint = arcPointAt(center, startRadius, bulgeRadius, labelAngle);
|
|
6258
|
+
const sizeProps = labelSizeProps(options);
|
|
6259
|
+
const features = [
|
|
6260
|
+
{
|
|
6261
|
+
type: "Feature",
|
|
6262
|
+
properties: { part: "boundary" },
|
|
6263
|
+
geometry: {
|
|
6264
|
+
type: "MultiLineString",
|
|
6265
|
+
coordinates: boundary
|
|
6266
|
+
}
|
|
6267
|
+
},
|
|
6268
|
+
{
|
|
6269
|
+
type: "Feature",
|
|
6270
|
+
properties: { part: "tics" },
|
|
6271
|
+
geometry: {
|
|
6272
|
+
type: "MultiLineString",
|
|
6273
|
+
coordinates: tics
|
|
6274
|
+
}
|
|
6275
|
+
},
|
|
6276
|
+
{
|
|
6277
|
+
type: "Feature",
|
|
6278
|
+
properties: { part: "arrow" },
|
|
6279
|
+
geometry: {
|
|
6280
|
+
type: "MultiLineString",
|
|
6281
|
+
coordinates: arrow
|
|
6282
|
+
}
|
|
6283
|
+
},
|
|
6284
|
+
{
|
|
6285
|
+
type: "Feature",
|
|
6286
|
+
properties: {
|
|
6287
|
+
part: "label",
|
|
6288
|
+
labelPlacement: false,
|
|
6289
|
+
text: "C",
|
|
6290
|
+
rotation: labelRotation,
|
|
6291
|
+
...sizeProps
|
|
6292
|
+
},
|
|
6293
|
+
geometry: {
|
|
6294
|
+
type: "Point",
|
|
6295
|
+
coordinates: unproject(labelPoint[0], labelPoint[1])
|
|
6296
|
+
}
|
|
6297
|
+
}
|
|
6298
|
+
];
|
|
6299
|
+
if (hostileText) features.push({
|
|
6300
|
+
type: "Feature",
|
|
6301
|
+
properties: {
|
|
6302
|
+
part: "label",
|
|
6303
|
+
labelPlacement: false,
|
|
6304
|
+
amplifierField: "N",
|
|
6305
|
+
text: hostileText,
|
|
6306
|
+
rotation: labelRotation,
|
|
6307
|
+
...sizeProps
|
|
6308
|
+
},
|
|
6309
|
+
geometry: {
|
|
6310
|
+
type: "Point",
|
|
6311
|
+
coordinates: unproject(hostilePoint[0], hostilePoint[1])
|
|
6312
|
+
}
|
|
6313
|
+
});
|
|
6314
|
+
return {
|
|
6315
|
+
type: "FeatureCollection",
|
|
6316
|
+
features
|
|
6317
|
+
};
|
|
6318
|
+
}
|
|
6319
|
+
const CONTAIN = defineControlMeasure({
|
|
6320
|
+
metadata: CONTAIN_METADATA,
|
|
6321
|
+
generator: createContain,
|
|
6322
|
+
defaultOptions: DEFAULT_CONTAIN_OPTIONS,
|
|
6323
|
+
rule: containDrawRule,
|
|
6324
|
+
previewSample: {
|
|
6325
|
+
controlPoints: [
|
|
6326
|
+
[0, .7],
|
|
6327
|
+
[0, -.7],
|
|
6328
|
+
[1.5, 0]
|
|
6329
|
+
],
|
|
6330
|
+
options: { labelSize: 90 }
|
|
6331
|
+
}
|
|
6332
|
+
});
|
|
6333
|
+
//#endregion
|
|
6334
|
+
//#region src/generators/cm27-protection-areas/block.ts
|
|
6335
|
+
const DEFAULT_STEM_RATIO$1 = 1.5;
|
|
6336
|
+
const BLOCK_METADATA = {
|
|
6337
|
+
id: "block",
|
|
6338
|
+
name: "Block",
|
|
6339
|
+
description: "Integrates fires and obstacles to stop an attacker along an avenue of approach or prevent it from passing through an engagement area. Establishes a limit of enemy advance.",
|
|
6340
|
+
entity: "Protection Areas",
|
|
6341
|
+
entityType: "Obstacle Effects",
|
|
6342
|
+
entitySubtype: "Block",
|
|
6343
|
+
value: "270501",
|
|
6344
|
+
minCoordinates: 2,
|
|
6345
|
+
geometry: "line",
|
|
6346
|
+
geometryTypes: ["LineString"],
|
|
6347
|
+
paints: {
|
|
6348
|
+
stroke: true,
|
|
6349
|
+
fill: "none",
|
|
6350
|
+
text: false
|
|
6351
|
+
},
|
|
6352
|
+
drawRule: "Area11"
|
|
6353
|
+
};
|
|
6354
|
+
const DEFAULT_BLOCK_OPTIONS = {};
|
|
6355
|
+
/**
|
|
6356
|
+
* Generates a GeoJSON FeatureCollection for a "Block" tactical symbol (T-shape).
|
|
6357
|
+
*
|
|
6358
|
+
* @param coordinates - An array of GeoJSON Positions (Lon/Lat).
|
|
6359
|
+
* Expects exactly 3 points:
|
|
5778
6360
|
* - PT1 & PT2: Endpoints of the vertical line.
|
|
5779
6361
|
* - PT3: Defines the endpoint of the horizontal line (stem).
|
|
5780
6362
|
* @param options - Configuration options.
|
|
@@ -7965,7 +8547,7 @@ function clockwiseSweepDegrees(leftAzimuth, rightAzimuth, collapseToleranceDegre
|
|
|
7965
8547
|
return sweep === 0 && rawSweep !== 0 ? 360 : sweep;
|
|
7966
8548
|
}
|
|
7967
8549
|
/** Samples an arc of `sweepDegrees` at `radius`, starting from `leftAzimuth`. */
|
|
7968
|
-
function sampleArc(anchor, radius, leftAzimuth, sweepDegrees, resolution) {
|
|
8550
|
+
function sampleArc$1(anchor, radius, leftAzimuth, sweepDegrees, resolution) {
|
|
7969
8551
|
const segmentCount = Math.max(1, Math.ceil(sweepDegrees / 360 * resolution));
|
|
7970
8552
|
return Array.from({ length: segmentCount + 1 }, (_, index) => {
|
|
7971
8553
|
return destinationPoint(anchor, radius, (leftAzimuth + index / segmentCount * sweepDegrees) * Math.PI / 180);
|
|
@@ -7973,8 +8555,8 @@ function sampleArc(anchor, radius, leftAzimuth, sweepDegrees, resolution) {
|
|
|
7973
8555
|
}
|
|
7974
8556
|
/** Closed ring tracing the outer arc, back along the inner arc, and closed. */
|
|
7975
8557
|
function annularSectorRing(anchor, innerRadius, outerRadius, leftAzimuth, sweepDegrees, resolution) {
|
|
7976
|
-
const outerArc = sampleArc(anchor, outerRadius, leftAzimuth, sweepDegrees, resolution);
|
|
7977
|
-
const innerArc = sampleArc(anchor, innerRadius, leftAzimuth, sweepDegrees, resolution).reverse();
|
|
8558
|
+
const outerArc = sampleArc$1(anchor, outerRadius, leftAzimuth, sweepDegrees, resolution);
|
|
8559
|
+
const innerArc = sampleArc$1(anchor, innerRadius, leftAzimuth, sweepDegrees, resolution).reverse();
|
|
7978
8560
|
return [
|
|
7979
8561
|
...outerArc,
|
|
7980
8562
|
...innerArc,
|
|
@@ -8680,27 +9262,6 @@ const COUNTERATTACK = defineControlMeasure({
|
|
|
8680
9262
|
}
|
|
8681
9263
|
});
|
|
8682
9264
|
//#endregion
|
|
8683
|
-
//#region src/internal/arrowhead.ts
|
|
8684
|
-
/**
|
|
8685
|
-
* Builds a closed, filled arrowhead triangle ring in unprojected coordinates.
|
|
8686
|
-
*
|
|
8687
|
-
* `tip` is the point of the arrow, `dir` the unit direction the arrow points,
|
|
8688
|
-
* `length` the distance from tip back to the base, and `width` the base width.
|
|
8689
|
-
* The returned ring is `[left, tip, right, left]`.
|
|
8690
|
-
*/
|
|
8691
|
-
function createArrowHeadRing(tip, dir, length, width) {
|
|
8692
|
-
const perp = [-dir[1], dir[0]];
|
|
8693
|
-
const base = vecSub(tip, vecScale(dir, length));
|
|
8694
|
-
const left = vecAdd(base, vecScale(perp, width / 2));
|
|
8695
|
-
const right = vecSub(base, vecScale(perp, width / 2));
|
|
8696
|
-
return [
|
|
8697
|
-
unproject(left[0], left[1]),
|
|
8698
|
-
unproject(tip[0], tip[1]),
|
|
8699
|
-
unproject(right[0], right[1]),
|
|
8700
|
-
unproject(left[0], left[1])
|
|
8701
|
-
];
|
|
8702
|
-
}
|
|
8703
|
-
//#endregion
|
|
8704
9265
|
//#region src/generators/cm34-mission-tasks/counterattack-by-fire.ts
|
|
8705
9266
|
const DEFAULT_COUNTERATTACK_BY_FIRE_OPTIONS = { ...DEFAULT_COUNTERATTACK_OPTIONS };
|
|
8706
9267
|
/** Length of the solid fire arrowhead relative to the recessed P1 extension. */
|
|
@@ -11005,6 +11566,155 @@ const BRIDGEHEAD_LINE = defineControlMeasure({
|
|
|
11005
11566
|
}
|
|
11006
11567
|
});
|
|
11007
11568
|
//#endregion
|
|
11569
|
+
//#region src/generators/cm14-maneuver-lines/params.ts
|
|
11570
|
+
/** Phase-line naming controls shared by the fixed-label Line1 maneuver lines. */
|
|
11571
|
+
function phaseLineNameParams(placeholder) {
|
|
11572
|
+
return [
|
|
11573
|
+
{
|
|
11574
|
+
key: "phaseLineName",
|
|
11575
|
+
presentationTier: "primary",
|
|
11576
|
+
semanticRole: "doctrinal",
|
|
11577
|
+
label: "Phase line name",
|
|
11578
|
+
description: "Name the line as a phase line — rendered \"PL <name>\" beyond each end of the line.",
|
|
11579
|
+
type: "text",
|
|
11580
|
+
placeholder,
|
|
11581
|
+
maxLength: 21,
|
|
11582
|
+
field: "T"
|
|
11583
|
+
},
|
|
11584
|
+
{
|
|
11585
|
+
key: "includePrefix",
|
|
11586
|
+
label: "Show PL prefix",
|
|
11587
|
+
description: "Prefix the phase line name with \"PL\" (or show the bare name).",
|
|
11588
|
+
type: "boolean",
|
|
11589
|
+
visibleWhen: (opts) => Boolean(String(opts.phaseLineName ?? "").trim())
|
|
11590
|
+
},
|
|
11591
|
+
LINE_LABEL_PADDING_PARAM
|
|
11592
|
+
];
|
|
11593
|
+
}
|
|
11594
|
+
//#endregion
|
|
11595
|
+
//#region src/generators/cm14-maneuver-lines/final-coordination-line.ts
|
|
11596
|
+
/** Default options for the Final coordination line control measure. */
|
|
11597
|
+
const DEFAULT_FINAL_COORDINATION_LINE_OPTIONS = {
|
|
11598
|
+
includePrefix: true,
|
|
11599
|
+
smooth: false,
|
|
11600
|
+
smoothResolution: 12
|
|
11601
|
+
};
|
|
11602
|
+
const FINAL_COORDINATION_LINE_METADATA = {
|
|
11603
|
+
id: "final-coordination-line",
|
|
11604
|
+
name: "Final coordination line",
|
|
11605
|
+
description: "A line close to the enemy position used to coordinate the lifting or shifting of supporting fires with the final deployment of maneuver elements.",
|
|
11606
|
+
entity: "Maneuver Lines",
|
|
11607
|
+
entityType: "Final Coordination Line",
|
|
11608
|
+
value: "140700",
|
|
11609
|
+
minCoordinates: 2,
|
|
11610
|
+
geometry: "line",
|
|
11611
|
+
geometryTypes: ["LineString", "Point"],
|
|
11612
|
+
paints: {
|
|
11613
|
+
stroke: true,
|
|
11614
|
+
fill: "none",
|
|
11615
|
+
text: true
|
|
11616
|
+
},
|
|
11617
|
+
drawRule: "Line1",
|
|
11618
|
+
capturesLabelSize: true,
|
|
11619
|
+
params: [...SMOOTH_LINE_PARAMS, ...phaseLineNameParams("OPAL")],
|
|
11620
|
+
textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
|
|
11621
|
+
};
|
|
11622
|
+
/** Creates a Final coordination line labeled "FCL" above both ends. */
|
|
11623
|
+
function createFinalCoordinationLine(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
11624
|
+
return fixedLabelLineFeatures(positions, {
|
|
11625
|
+
part: "final-coordination-line",
|
|
11626
|
+
placement: "above",
|
|
11627
|
+
...options
|
|
11628
|
+
}, "FCL", textAmplifiers.N, context);
|
|
11629
|
+
}
|
|
11630
|
+
const FINAL_COORDINATION_LINE = defineControlMeasure({
|
|
11631
|
+
metadata: FINAL_COORDINATION_LINE_METADATA,
|
|
11632
|
+
generator: createFinalCoordinationLine,
|
|
11633
|
+
defaultOptions: DEFAULT_FINAL_COORDINATION_LINE_OPTIONS,
|
|
11634
|
+
rule: line1DrawRule,
|
|
11635
|
+
previewSample: {
|
|
11636
|
+
controlPoints: [[-1, 0], [1, 0]],
|
|
11637
|
+
options: { labelSize: 96 }
|
|
11638
|
+
}
|
|
11639
|
+
});
|
|
11640
|
+
//#endregion
|
|
11641
|
+
//#region src/generators/cm14-maneuver-lines/airhead-line.ts
|
|
11642
|
+
/** Clearance between the boundary's southernmost point and the caption, as a
|
|
11643
|
+
* ratio of the caption's height. */
|
|
11644
|
+
const CAPTION_CLEARANCE_RATIO = .9;
|
|
11645
|
+
/** Default options for the Airhead line control measure. */
|
|
11646
|
+
const DEFAULT_AIRHEAD_LINE_OPTIONS = {
|
|
11647
|
+
smooth: false,
|
|
11648
|
+
smoothResolution: 16
|
|
11649
|
+
};
|
|
11650
|
+
const AIRHEAD_LINE_METADATA = {
|
|
11651
|
+
id: "airhead-line",
|
|
11652
|
+
name: "Airhead line",
|
|
11653
|
+
description: "A line denoting the limits of the objective area for an airborne assault.",
|
|
11654
|
+
entity: "Maneuver Lines",
|
|
11655
|
+
entityType: "Airhead Line",
|
|
11656
|
+
value: "141300",
|
|
11657
|
+
minCoordinates: 3,
|
|
11658
|
+
geometry: "area",
|
|
11659
|
+
geometryTypes: [
|
|
11660
|
+
"Polygon",
|
|
11661
|
+
"MultiLineString",
|
|
11662
|
+
"Point"
|
|
11663
|
+
],
|
|
11664
|
+
paints: {
|
|
11665
|
+
stroke: true,
|
|
11666
|
+
fill: "none",
|
|
11667
|
+
text: true
|
|
11668
|
+
},
|
|
11669
|
+
drawRule: "Area1",
|
|
11670
|
+
capturesLabelSize: true,
|
|
11671
|
+
params: LABELED_AREA_PARAMS,
|
|
11672
|
+
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
11673
|
+
};
|
|
11674
|
+
/** Creates an Airhead line: a closed boundary captioned "AIRHEAD LINE" below. */
|
|
11675
|
+
function createAirheadLine(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
11676
|
+
const { smooth = false, smoothResolution = 16, ...labelOptions } = options;
|
|
11677
|
+
const verts = buildClosedRing(positions, smooth, smoothResolution, 16);
|
|
11678
|
+
const labelFeatures = [];
|
|
11679
|
+
const gaps = pushAreaLabels(labelFeatures, verts, composeAreaLabelTexts(textAmplifiers, ""), labelOptions, context.amplifierPlacements, void 0, context);
|
|
11680
|
+
const [cx] = ringCentroid(verts);
|
|
11681
|
+
const minY = Math.min(...verts.map((vertex) => vertex[1]));
|
|
11682
|
+
const clearance = resolveLabelOffsetMeters(labelOptions, CAPTION_CLEARANCE_RATIO + Math.max(0, labelOptions.labelPadding ?? 0));
|
|
11683
|
+
labelFeatures.push({
|
|
11684
|
+
type: "Feature",
|
|
11685
|
+
properties: {
|
|
11686
|
+
part: "label",
|
|
11687
|
+
labelPlacementKey: "label:airhead-line",
|
|
11688
|
+
text: "AIRHEAD LINE",
|
|
11689
|
+
rotation: HORIZONTAL_LABEL_ROTATION,
|
|
11690
|
+
...labelSizeProps(labelOptions)
|
|
11691
|
+
},
|
|
11692
|
+
geometry: {
|
|
11693
|
+
type: "Point",
|
|
11694
|
+
coordinates: unproject(cx, minY - clearance)
|
|
11695
|
+
}
|
|
11696
|
+
});
|
|
11697
|
+
return {
|
|
11698
|
+
type: "FeatureCollection",
|
|
11699
|
+
features: [boundaryFeature(verts, gaps), ...labelFeatures]
|
|
11700
|
+
};
|
|
11701
|
+
}
|
|
11702
|
+
const AIRHEAD_LINE = defineControlMeasure({
|
|
11703
|
+
metadata: AIRHEAD_LINE_METADATA,
|
|
11704
|
+
generator: createAirheadLine,
|
|
11705
|
+
defaultOptions: DEFAULT_AIRHEAD_LINE_OPTIONS,
|
|
11706
|
+
rule: area1DrawRule,
|
|
11707
|
+
previewSample: {
|
|
11708
|
+
controlPoints: [
|
|
11709
|
+
[-1, -.4],
|
|
11710
|
+
[1, -.4],
|
|
11711
|
+
[1, .6],
|
|
11712
|
+
[-1, .6]
|
|
11713
|
+
],
|
|
11714
|
+
options: { labelSize: 96 }
|
|
11715
|
+
}
|
|
11716
|
+
});
|
|
11717
|
+
//#endregion
|
|
11008
11718
|
//#region src/generators/cm14-maneuver-lines/holding-line.ts
|
|
11009
11719
|
/** Default options for the Holding line control measure. */
|
|
11010
11720
|
const DEFAULT_HOLDING_LINE_OPTIONS = {
|
|
@@ -11394,6 +12104,193 @@ const BATTLE_HANDOVER_LINE = defineControlMeasure({
|
|
|
11394
12104
|
}
|
|
11395
12105
|
});
|
|
11396
12106
|
//#endregion
|
|
12107
|
+
//#region src/generators/cm14-maneuver-lines/limit-of-advance.ts
|
|
12108
|
+
/** Default options for the Limit of advance control measure. */
|
|
12109
|
+
const DEFAULT_LIMIT_OF_ADVANCE_OPTIONS = {
|
|
12110
|
+
includePrefix: true,
|
|
12111
|
+
smooth: false,
|
|
12112
|
+
smoothResolution: 12
|
|
12113
|
+
};
|
|
12114
|
+
const LIMIT_OF_ADVANCE_METADATA = {
|
|
12115
|
+
id: "limit-of-advance",
|
|
12116
|
+
name: "Limit of advance",
|
|
12117
|
+
description: "An easily recognized terrain feature beyond which attacking elements will not advance.",
|
|
12118
|
+
entity: "Maneuver Lines",
|
|
12119
|
+
entityType: "Limit of Advance",
|
|
12120
|
+
value: "140900",
|
|
12121
|
+
minCoordinates: 2,
|
|
12122
|
+
geometry: "line",
|
|
12123
|
+
geometryTypes: ["LineString", "Point"],
|
|
12124
|
+
paints: {
|
|
12125
|
+
stroke: true,
|
|
12126
|
+
fill: "none",
|
|
12127
|
+
text: true
|
|
12128
|
+
},
|
|
12129
|
+
drawRule: "Line1",
|
|
12130
|
+
capturesLabelSize: true,
|
|
12131
|
+
params: [...SMOOTH_LINE_PARAMS, ...phaseLineNameParams("RUBY")],
|
|
12132
|
+
textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
|
|
12133
|
+
};
|
|
12134
|
+
/** Creates a Limit of advance line labeled "LOA" above both ends. */
|
|
12135
|
+
function createLimitOfAdvance(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
12136
|
+
return fixedLabelLineFeatures(positions, {
|
|
12137
|
+
part: "limit-of-advance",
|
|
12138
|
+
placement: "above",
|
|
12139
|
+
...options
|
|
12140
|
+
}, "LOA", textAmplifiers.N, context);
|
|
12141
|
+
}
|
|
12142
|
+
const LIMIT_OF_ADVANCE = defineControlMeasure({
|
|
12143
|
+
metadata: LIMIT_OF_ADVANCE_METADATA,
|
|
12144
|
+
generator: createLimitOfAdvance,
|
|
12145
|
+
defaultOptions: DEFAULT_LIMIT_OF_ADVANCE_OPTIONS,
|
|
12146
|
+
rule: line1DrawRule,
|
|
12147
|
+
previewSample: {
|
|
12148
|
+
controlPoints: [[-1, 0], [1, 0]],
|
|
12149
|
+
options: { labelSize: 96 }
|
|
12150
|
+
}
|
|
12151
|
+
});
|
|
12152
|
+
//#endregion
|
|
12153
|
+
//#region src/generators/cm14-maneuver-lines/line-of-departure.ts
|
|
12154
|
+
/** Default options for the Line of departure control measure. */
|
|
12155
|
+
const DEFAULT_LINE_OF_DEPARTURE_OPTIONS = {
|
|
12156
|
+
includePrefix: true,
|
|
12157
|
+
smooth: false,
|
|
12158
|
+
smoothResolution: 12
|
|
12159
|
+
};
|
|
12160
|
+
const LINE_OF_DEPARTURE_METADATA = {
|
|
12161
|
+
id: "line-of-departure",
|
|
12162
|
+
name: "Line of departure",
|
|
12163
|
+
description: "A line designated to coordinate the departure of attacking elements.",
|
|
12164
|
+
entity: "Maneuver Lines",
|
|
12165
|
+
entityType: "Line of Departure",
|
|
12166
|
+
value: "141000",
|
|
12167
|
+
minCoordinates: 2,
|
|
12168
|
+
geometry: "line",
|
|
12169
|
+
geometryTypes: ["LineString", "Point"],
|
|
12170
|
+
paints: {
|
|
12171
|
+
stroke: true,
|
|
12172
|
+
fill: "none",
|
|
12173
|
+
text: true
|
|
12174
|
+
},
|
|
12175
|
+
drawRule: "Line1",
|
|
12176
|
+
capturesLabelSize: true,
|
|
12177
|
+
params: [...SMOOTH_LINE_PARAMS, ...phaseLineNameParams("JADE")],
|
|
12178
|
+
textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
|
|
12179
|
+
};
|
|
12180
|
+
/** Creates a Line of departure labeled "LD" above both ends. */
|
|
12181
|
+
function createLineOfDeparture(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
12182
|
+
return fixedLabelLineFeatures(positions, {
|
|
12183
|
+
part: "line-of-departure",
|
|
12184
|
+
placement: "above",
|
|
12185
|
+
...options
|
|
12186
|
+
}, "LD", textAmplifiers.N, context);
|
|
12187
|
+
}
|
|
12188
|
+
const LINE_OF_DEPARTURE = defineControlMeasure({
|
|
12189
|
+
metadata: LINE_OF_DEPARTURE_METADATA,
|
|
12190
|
+
generator: createLineOfDeparture,
|
|
12191
|
+
defaultOptions: DEFAULT_LINE_OF_DEPARTURE_OPTIONS,
|
|
12192
|
+
rule: line1DrawRule,
|
|
12193
|
+
previewSample: {
|
|
12194
|
+
controlPoints: [[-1, 0], [1, 0]],
|
|
12195
|
+
options: { labelSize: 96 }
|
|
12196
|
+
}
|
|
12197
|
+
});
|
|
12198
|
+
//#endregion
|
|
12199
|
+
//#region src/generators/cm14-maneuver-lines/line-of-departure-contact.ts
|
|
12200
|
+
/** Default options for the Line of departure / line of contact control measure. */
|
|
12201
|
+
const DEFAULT_LINE_OF_DEPARTURE_CONTACT_OPTIONS = {
|
|
12202
|
+
includePrefix: true,
|
|
12203
|
+
smooth: false,
|
|
12204
|
+
smoothResolution: 12
|
|
12205
|
+
};
|
|
12206
|
+
const LINE_OF_DEPARTURE_CONTACT_METADATA = {
|
|
12207
|
+
id: "line-of-departure-contact",
|
|
12208
|
+
name: "Line of departure / line of contact",
|
|
12209
|
+
description: "The designation of forward friendly positions as the line of departure when opposing forces are in contact.",
|
|
12210
|
+
entity: "Maneuver Lines",
|
|
12211
|
+
entityType: "Line of Departure / Line of Contact",
|
|
12212
|
+
value: "141100",
|
|
12213
|
+
minCoordinates: 2,
|
|
12214
|
+
geometry: "line",
|
|
12215
|
+
geometryTypes: ["LineString", "Point"],
|
|
12216
|
+
paints: {
|
|
12217
|
+
stroke: true,
|
|
12218
|
+
fill: "none",
|
|
12219
|
+
text: true
|
|
12220
|
+
},
|
|
12221
|
+
drawRule: "Line1",
|
|
12222
|
+
capturesLabelSize: true,
|
|
12223
|
+
params: [...SMOOTH_LINE_PARAMS, ...phaseLineNameParams("AMBER")],
|
|
12224
|
+
textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
|
|
12225
|
+
};
|
|
12226
|
+
/** Creates a Line of departure / line of contact labeled "LD/LC" above both ends. */
|
|
12227
|
+
function createLineOfDepartureContact(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
12228
|
+
return fixedLabelLineFeatures(positions, {
|
|
12229
|
+
part: "line-of-departure-contact",
|
|
12230
|
+
placement: "above",
|
|
12231
|
+
...options
|
|
12232
|
+
}, "LD/LC", textAmplifiers.N, context);
|
|
12233
|
+
}
|
|
12234
|
+
const LINE_OF_DEPARTURE_CONTACT = defineControlMeasure({
|
|
12235
|
+
metadata: LINE_OF_DEPARTURE_CONTACT_METADATA,
|
|
12236
|
+
generator: createLineOfDepartureContact,
|
|
12237
|
+
defaultOptions: DEFAULT_LINE_OF_DEPARTURE_CONTACT_OPTIONS,
|
|
12238
|
+
rule: line1DrawRule,
|
|
12239
|
+
previewSample: {
|
|
12240
|
+
controlPoints: [[-1, 0], [1, 0]],
|
|
12241
|
+
options: { labelSize: 96 }
|
|
12242
|
+
}
|
|
12243
|
+
});
|
|
12244
|
+
//#endregion
|
|
12245
|
+
//#region src/generators/cm14-maneuver-lines/probable-line-of-deployment.ts
|
|
12246
|
+
/** Default options for the Probable line of deployment control measure. */
|
|
12247
|
+
const DEFAULT_PROBABLE_LINE_OF_DEPLOYMENT_OPTIONS = {
|
|
12248
|
+
includePrefix: true,
|
|
12249
|
+
smooth: false,
|
|
12250
|
+
smoothResolution: 12
|
|
12251
|
+
};
|
|
12252
|
+
/** Intrinsic doctrinal dash pattern for Probable Line of Deployment. */
|
|
12253
|
+
const PROBABLE_LINE_OF_DEPLOYMENT_DASH = [6, 4];
|
|
12254
|
+
const PROBABLE_LINE_OF_DEPLOYMENT_METADATA = {
|
|
12255
|
+
id: "probable-line-of-deployment",
|
|
12256
|
+
name: "Probable line of deployment",
|
|
12257
|
+
description: "A line selected on the ground, usually the last covered and concealed position prior to the objective and forward of the line of departure, where attacking units deploy prior to beginning an assault.",
|
|
12258
|
+
entity: "Maneuver Lines",
|
|
12259
|
+
entityType: "Probable Line of Deployment",
|
|
12260
|
+
value: "141200",
|
|
12261
|
+
minCoordinates: 2,
|
|
12262
|
+
geometry: "line",
|
|
12263
|
+
geometryTypes: ["LineString", "Point"],
|
|
12264
|
+
paints: {
|
|
12265
|
+
stroke: true,
|
|
12266
|
+
fill: "none",
|
|
12267
|
+
text: true
|
|
12268
|
+
},
|
|
12269
|
+
drawRule: "Line1",
|
|
12270
|
+
capturesLabelSize: true,
|
|
12271
|
+
params: [...SMOOTH_LINE_PARAMS, ...phaseLineNameParams("ONYX")],
|
|
12272
|
+
textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
|
|
12273
|
+
};
|
|
12274
|
+
/** Creates a Probable line of deployment labeled "PLD" above both ends, with a dashed stroke. */
|
|
12275
|
+
function createProbableLineOfDeployment(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
12276
|
+
return fixedLabelLineFeatures(positions, {
|
|
12277
|
+
part: "probable-line-of-deployment",
|
|
12278
|
+
placement: "above",
|
|
12279
|
+
lineStyle: { strokeDash: [...PROBABLE_LINE_OF_DEPLOYMENT_DASH] },
|
|
12280
|
+
...options
|
|
12281
|
+
}, "PLD", textAmplifiers.N, context);
|
|
12282
|
+
}
|
|
12283
|
+
const PROBABLE_LINE_OF_DEPLOYMENT = defineControlMeasure({
|
|
12284
|
+
metadata: PROBABLE_LINE_OF_DEPLOYMENT_METADATA,
|
|
12285
|
+
generator: createProbableLineOfDeployment,
|
|
12286
|
+
defaultOptions: DEFAULT_PROBABLE_LINE_OF_DEPLOYMENT_OPTIONS,
|
|
12287
|
+
rule: line1DrawRule,
|
|
12288
|
+
previewSample: {
|
|
12289
|
+
controlPoints: [[-1, 0], [1, 0]],
|
|
12290
|
+
options: { labelSize: 96 }
|
|
12291
|
+
}
|
|
12292
|
+
});
|
|
12293
|
+
//#endregion
|
|
11397
12294
|
//#region src/generators/cm29-protection-lines/fortifiedLine.ts
|
|
11398
12295
|
const DEFAULT_TOOTH_SIZE = 50;
|
|
11399
12296
|
const DEFAULT_TOOTH_SIZE_PIXELS = 10;
|
|
@@ -12634,6 +13531,57 @@ const LANDING_ZONE = defineControlMeasure({
|
|
|
12634
13531
|
}
|
|
12635
13532
|
});
|
|
12636
13533
|
//#endregion
|
|
13534
|
+
//#region src/generators/cm15-maneuver-areas/penetration-box.ts
|
|
13535
|
+
/** Default options for the Penetration box control measure. */
|
|
13536
|
+
const DEFAULT_PENETRATION_BOX_OPTIONS = {
|
|
13537
|
+
smooth: false,
|
|
13538
|
+
smoothResolution: 16
|
|
13539
|
+
};
|
|
13540
|
+
const PENETRATION_BOX_METADATA = {
|
|
13541
|
+
id: "penetration-box",
|
|
13542
|
+
name: "Penetration box",
|
|
13543
|
+
description: "An area in which a force ruptures the enemy's defense on a narrow front to break the continuity of the defensive system.",
|
|
13544
|
+
entity: "Maneuver Areas",
|
|
13545
|
+
entityType: "Penetration Box",
|
|
13546
|
+
value: "151900",
|
|
13547
|
+
minCoordinates: 3,
|
|
13548
|
+
geometry: "area",
|
|
13549
|
+
geometryTypes: [
|
|
13550
|
+
"Polygon",
|
|
13551
|
+
"MultiLineString",
|
|
13552
|
+
"Point"
|
|
13553
|
+
],
|
|
13554
|
+
paints: {
|
|
13555
|
+
stroke: true,
|
|
13556
|
+
fill: "none",
|
|
13557
|
+
text: true
|
|
13558
|
+
},
|
|
13559
|
+
drawRule: "Area1",
|
|
13560
|
+
capturesLabelSize: true,
|
|
13561
|
+
params: LABELED_AREA_PARAMS,
|
|
13562
|
+
textAmplifiers: AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE
|
|
13563
|
+
};
|
|
13564
|
+
/** Creates a Penetration box: a bare closed area through `positions`. */
|
|
13565
|
+
function createPenetrationBox(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
13566
|
+
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, ""), context.amplifierPlacements, context);
|
|
13567
|
+
}
|
|
13568
|
+
const PENETRATION_BOX = defineControlMeasure({
|
|
13569
|
+
metadata: PENETRATION_BOX_METADATA,
|
|
13570
|
+
generator: createPenetrationBox,
|
|
13571
|
+
defaultOptions: DEFAULT_PENETRATION_BOX_OPTIONS,
|
|
13572
|
+
rule: area1DrawRule,
|
|
13573
|
+
previewSample: {
|
|
13574
|
+
controlPoints: [
|
|
13575
|
+
[-1, -.5],
|
|
13576
|
+
[1, -.5],
|
|
13577
|
+
[1, .5],
|
|
13578
|
+
[-1, .5]
|
|
13579
|
+
],
|
|
13580
|
+
textAmplifiers: {},
|
|
13581
|
+
options: { labelSize: 96 }
|
|
13582
|
+
}
|
|
13583
|
+
});
|
|
13584
|
+
//#endregion
|
|
12637
13585
|
//#region src/generators/cm15-maneuver-areas/limitedAccessArea.ts
|
|
12638
13586
|
/** Default options for the Limited Access Area control measure. */
|
|
12639
13587
|
const DEFAULT_LIMITED_ACCESS_AREA_OPTIONS = {
|
|
@@ -14379,6 +15327,180 @@ const STRONG_POINT = defineControlMeasure({
|
|
|
14379
15327
|
}
|
|
14380
15328
|
});
|
|
14381
15329
|
//#endregion
|
|
15330
|
+
//#region src/generators/cm15-maneuver-areas/retain.ts
|
|
15331
|
+
const DEFAULT_RETAIN_OPTIONS = {
|
|
15332
|
+
openingAngle: 30,
|
|
15333
|
+
arrowheadLengthRatio: .1,
|
|
15334
|
+
arrowheadWidthRatio: .12,
|
|
15335
|
+
labelPadding: 0
|
|
15336
|
+
};
|
|
15337
|
+
const RETAIN_METADATA = {
|
|
15338
|
+
id: "retain",
|
|
15339
|
+
name: "Retain",
|
|
15340
|
+
description: "Ensures that a terrain feature controlled by a friendly force remains secure.",
|
|
15341
|
+
entity: "Maneuver Areas",
|
|
15342
|
+
entityType: "Battle Position",
|
|
15343
|
+
entitySubtype: "Retain",
|
|
15344
|
+
value: "151205",
|
|
15345
|
+
minCoordinates: 2,
|
|
15346
|
+
maxCoordinates: 2,
|
|
15347
|
+
geometry: "area",
|
|
15348
|
+
geometryTypes: ["MultiLineString", "Point"],
|
|
15349
|
+
paints: {
|
|
15350
|
+
stroke: true,
|
|
15351
|
+
fill: "none",
|
|
15352
|
+
text: true
|
|
15353
|
+
},
|
|
15354
|
+
drawRule: "Area6",
|
|
15355
|
+
capturesLabelSize: true,
|
|
15356
|
+
params: [
|
|
15357
|
+
{
|
|
15358
|
+
key: "openingAngle",
|
|
15359
|
+
presentationTier: "advanced",
|
|
15360
|
+
label: "Opening",
|
|
15361
|
+
description: "Angular opening on the friendly side, in degrees",
|
|
15362
|
+
type: "number",
|
|
15363
|
+
min: 5,
|
|
15364
|
+
max: 120,
|
|
15365
|
+
step: 1
|
|
15366
|
+
},
|
|
15367
|
+
{
|
|
15368
|
+
key: "arrowheadLengthRatio",
|
|
15369
|
+
presentationTier: "advanced",
|
|
15370
|
+
label: "Arrowhead length",
|
|
15371
|
+
description: "Open-arrowhead length as a ratio of the symbol radius",
|
|
15372
|
+
type: "number",
|
|
15373
|
+
min: .02,
|
|
15374
|
+
max: .5,
|
|
15375
|
+
step: .01
|
|
15376
|
+
},
|
|
15377
|
+
{
|
|
15378
|
+
key: "arrowheadWidthRatio",
|
|
15379
|
+
presentationTier: "advanced",
|
|
15380
|
+
label: "Arrowhead width",
|
|
15381
|
+
description: "Open-arrowhead base width as a ratio of the symbol radius",
|
|
15382
|
+
type: "number",
|
|
15383
|
+
min: .02,
|
|
15384
|
+
max: .5,
|
|
15385
|
+
step: .01
|
|
15386
|
+
},
|
|
15387
|
+
{
|
|
15388
|
+
key: "labelPadding",
|
|
15389
|
+
presentationTier: "advanced",
|
|
15390
|
+
label: "R padding",
|
|
15391
|
+
description: "Extra boundary clearance on each side of R, as a ratio of its height",
|
|
15392
|
+
type: "number",
|
|
15393
|
+
min: 0,
|
|
15394
|
+
max: 2,
|
|
15395
|
+
step: .05
|
|
15396
|
+
}
|
|
15397
|
+
]
|
|
15398
|
+
};
|
|
15399
|
+
const TAU = Math.PI * 2;
|
|
15400
|
+
/** Smallest visible boundary gap, in projected meters, for tiny label heights. */
|
|
15401
|
+
const MIN_LABEL_GAP_METERS = 1;
|
|
15402
|
+
function pointOnCircle(center, radius, angle) {
|
|
15403
|
+
return vecAdd(center, [radius * Math.cos(angle), radius * Math.sin(angle)]);
|
|
15404
|
+
}
|
|
15405
|
+
function sampleArc(center, radius, start, end) {
|
|
15406
|
+
const segments = Math.max(8, Math.ceil(48 * Math.abs(end - start) / Math.PI));
|
|
15407
|
+
return Array.from({ length: segments + 1 }, (_, index) => {
|
|
15408
|
+
const point = pointOnCircle(center, radius, start + (end - start) * index / segments);
|
|
15409
|
+
return unproject(point[0], point[1]);
|
|
15410
|
+
});
|
|
15411
|
+
}
|
|
15412
|
+
/** Creates the Retain boundary, outward tics, and intrinsic R label. */
|
|
15413
|
+
function createRetain(coordinates, options = {}) {
|
|
15414
|
+
const center = project(coordinates[0][0], coordinates[0][1]);
|
|
15415
|
+
const radiusVector = vecSub(project(coordinates[1][0], coordinates[1][1]), center);
|
|
15416
|
+
const radius = vecMag(radiusVector);
|
|
15417
|
+
if (radius < 1e-6) return {
|
|
15418
|
+
type: "FeatureCollection",
|
|
15419
|
+
features: []
|
|
15420
|
+
};
|
|
15421
|
+
const resolved = {
|
|
15422
|
+
...DEFAULT_RETAIN_OPTIONS,
|
|
15423
|
+
...options
|
|
15424
|
+
};
|
|
15425
|
+
const startAngle = Math.atan2(radiusVector[1], radiusVector[0]);
|
|
15426
|
+
const opening = Math.min(120, Math.max(5, resolved.openingAngle)) * Math.PI / 180;
|
|
15427
|
+
const h = resolveLabelOffsetMeters(options, 1, radius * .1);
|
|
15428
|
+
const labelPadding = Math.max(0, resolved.labelPadding);
|
|
15429
|
+
const boundarySpan = TAU - opening;
|
|
15430
|
+
const labelDistance = Math.PI - opening / 2;
|
|
15431
|
+
const labelCenter = startAngle - labelDistance;
|
|
15432
|
+
const labelHalfGap = Math.min(Math.PI / 2, Math.max(h * (LABEL_HALF_GLYPH_RATIO + labelPadding), MIN_LABEL_GAP_METERS) / radius);
|
|
15433
|
+
const labelStart = startAngle - labelDistance + labelHalfGap;
|
|
15434
|
+
const labelEnd = startAngle - labelDistance - labelHalfGap;
|
|
15435
|
+
const boundaryEnd = startAngle - boundarySpan;
|
|
15436
|
+
const arrowTip = pointOnCircle(center, radius, boundaryEnd);
|
|
15437
|
+
const arrowheadLength = radius * Math.max(0, resolved.arrowheadLengthRatio);
|
|
15438
|
+
const arrowheadWidth = radius * Math.max(0, resolved.arrowheadWidthRatio);
|
|
15439
|
+
const boundary = [
|
|
15440
|
+
sampleArc(center, radius, startAngle, labelStart),
|
|
15441
|
+
sampleArc(center, radius, labelEnd, boundaryEnd),
|
|
15442
|
+
createArrowHeadRing(arrowTip, [Math.sin(boundaryEnd), -Math.cos(boundaryEnd)], arrowheadLength, arrowheadWidth).slice(0, 3)
|
|
15443
|
+
];
|
|
15444
|
+
const tics = [];
|
|
15445
|
+
const spacingAngle = Math.max(h / radius, Math.PI / 180);
|
|
15446
|
+
const arrowClearanceAngle = (arrowheadLength + arrowheadWidth / 2 + h / 2) / radius;
|
|
15447
|
+
for (let distance = spacingAngle / 2; distance < boundarySpan - arrowClearanceAngle; distance += spacingAngle) {
|
|
15448
|
+
if (distance > labelDistance - labelHalfGap && distance < labelDistance + labelHalfGap) continue;
|
|
15449
|
+
const angle = startAngle - distance;
|
|
15450
|
+
const cos = Math.cos(angle);
|
|
15451
|
+
const sin = Math.sin(angle);
|
|
15452
|
+
const innerX = center[0] + radius * cos;
|
|
15453
|
+
const innerY = center[1] + radius * sin;
|
|
15454
|
+
tics.push([unproject(innerX, innerY), unproject(innerX + h * cos, innerY + h * sin)]);
|
|
15455
|
+
}
|
|
15456
|
+
const labelPoint = pointOnCircle(center, radius, labelCenter);
|
|
15457
|
+
return {
|
|
15458
|
+
type: "FeatureCollection",
|
|
15459
|
+
features: [
|
|
15460
|
+
{
|
|
15461
|
+
type: "Feature",
|
|
15462
|
+
properties: { part: "boundary" },
|
|
15463
|
+
geometry: {
|
|
15464
|
+
type: "MultiLineString",
|
|
15465
|
+
coordinates: boundary
|
|
15466
|
+
}
|
|
15467
|
+
},
|
|
15468
|
+
{
|
|
15469
|
+
type: "Feature",
|
|
15470
|
+
properties: { part: "tics" },
|
|
15471
|
+
geometry: {
|
|
15472
|
+
type: "MultiLineString",
|
|
15473
|
+
coordinates: tics
|
|
15474
|
+
}
|
|
15475
|
+
},
|
|
15476
|
+
{
|
|
15477
|
+
type: "Feature",
|
|
15478
|
+
properties: {
|
|
15479
|
+
part: "label",
|
|
15480
|
+
labelPlacement: false,
|
|
15481
|
+
text: "R",
|
|
15482
|
+
rotation: Math.PI,
|
|
15483
|
+
...labelSizeProps(options)
|
|
15484
|
+
},
|
|
15485
|
+
geometry: {
|
|
15486
|
+
type: "Point",
|
|
15487
|
+
coordinates: unproject(labelPoint[0], labelPoint[1])
|
|
15488
|
+
}
|
|
15489
|
+
}
|
|
15490
|
+
]
|
|
15491
|
+
};
|
|
15492
|
+
}
|
|
15493
|
+
const RETAIN = defineControlMeasure({
|
|
15494
|
+
metadata: RETAIN_METADATA,
|
|
15495
|
+
generator: createRetain,
|
|
15496
|
+
defaultOptions: DEFAULT_RETAIN_OPTIONS,
|
|
15497
|
+
rule: retainDrawRule,
|
|
15498
|
+
previewSample: {
|
|
15499
|
+
controlPoints: [[0, 0], [-.8, .2]],
|
|
15500
|
+
options: { labelSize: 90 }
|
|
15501
|
+
}
|
|
15502
|
+
});
|
|
15503
|
+
//#endregion
|
|
14382
15504
|
//#region src/generators/cm15-maneuver-areas/submarineActionArea.ts
|
|
14383
15505
|
/** Default options for the Submarine action area control measure. */
|
|
14384
15506
|
const DEFAULT_SUBMARINE_ACTION_AREA_OPTIONS = {
|
|
@@ -14837,7 +15959,10 @@ const DEFINITIONS = {
|
|
|
14837
15959
|
"engineer-work-line": ENGINEER_WORK_LINE,
|
|
14838
15960
|
"generic-c2-line": GENERIC_C2_LINE,
|
|
14839
15961
|
"battle-position": BATTLE_POSITION,
|
|
15962
|
+
"battle-position-prepared": BATTLE_POSITION_PREPARED,
|
|
15963
|
+
contain: CONTAIN,
|
|
14840
15964
|
"strong-point": STRONG_POINT,
|
|
15965
|
+
retain: RETAIN,
|
|
14841
15966
|
ambush: AMBUSH,
|
|
14842
15967
|
"direction-of-attack-aviation": DIRECTION_OF_ATTACK_AVIATION,
|
|
14843
15968
|
"direction-of-main-attack": DIRECTION_OF_MAIN_ATTACK,
|
|
@@ -14850,6 +15975,8 @@ const DEFINITIONS = {
|
|
|
14850
15975
|
encirclement: ENCIRCLEMENT,
|
|
14851
15976
|
area: AREA,
|
|
14852
15977
|
"area-defense": AREA_DEFENSE,
|
|
15978
|
+
"engagement-area": ENGAGEMENT_AREA,
|
|
15979
|
+
"mobile-defense": MOBILE_DEFENSE,
|
|
14853
15980
|
"assembly-area": ASSEMBLY_AREA,
|
|
14854
15981
|
"joint-tactical-action-area": JOINT_TACTICAL_ACTION_AREA,
|
|
14855
15982
|
"submarine-action-area": SUBMARINE_ACTION_AREA,
|
|
@@ -14858,6 +15985,7 @@ const DEFINITIONS = {
|
|
|
14858
15985
|
"extraction-zone": EXTRACTION_ZONE,
|
|
14859
15986
|
"landing-zone": LANDING_ZONE,
|
|
14860
15987
|
"limited-access-area": LIMITED_ACCESS_AREA,
|
|
15988
|
+
"penetration-box": PENETRATION_BOX,
|
|
14861
15989
|
"no-fire-area-irregular": NO_FIRE_AREA_IRREGULAR,
|
|
14862
15990
|
"minefield-dynamic": DYNAMIC_MINEFIELD,
|
|
14863
15991
|
"mined-area": MINED_AREA,
|
|
@@ -14891,10 +16019,16 @@ const DEFINITIONS = {
|
|
|
14891
16019
|
"phase-line": PHASE_LINE,
|
|
14892
16020
|
"forward-edge-of-battle-area": FORWARD_EDGE_OF_BATTLE_AREA,
|
|
14893
16021
|
"bridgehead-line": BRIDGEHEAD_LINE,
|
|
16022
|
+
"final-coordination-line": FINAL_COORDINATION_LINE,
|
|
16023
|
+
"airhead-line": AIRHEAD_LINE,
|
|
14894
16024
|
"holding-line": HOLDING_LINE,
|
|
14895
16025
|
"release-line": RELEASE_LINE,
|
|
14896
16026
|
"handover-line": HANDOVER_LINE,
|
|
14897
16027
|
"battle-handover-line": BATTLE_HANDOVER_LINE,
|
|
16028
|
+
"limit-of-advance": LIMIT_OF_ADVANCE,
|
|
16029
|
+
"line-of-departure": LINE_OF_DEPARTURE,
|
|
16030
|
+
"line-of-departure-contact": LINE_OF_DEPARTURE_CONTACT,
|
|
16031
|
+
"probable-line-of-deployment": PROBABLE_LINE_OF_DEPLOYMENT,
|
|
14898
16032
|
"block-mission-task": BLOCK_MISSION_TASK,
|
|
14899
16033
|
breach: BREACH,
|
|
14900
16034
|
bypass: BYPASS,
|
|
@@ -15337,4 +16471,4 @@ function assertNever(value) {
|
|
|
15337
16471
|
throw new Error(`Unhandled control measure kind: ${String(value)}`);
|
|
15338
16472
|
}
|
|
15339
16473
|
//#endregion
|
|
15340
|
-
export {
|
|
16474
|
+
export { DEFAULT_DISRUPT_OPTIONS as $, DEFAULT_STROKE_DASH_CSS_PIXELS as $t, DEFAULT_RETIRE_OPTIONS as A, createBaselineFrame as An, DEFAULT_CONTAIN_OPTIONS as At, DEFAULT_LIMIT_OF_ADVANCE_OPTIONS as B, DEFAULT_AREA_DEFENSE_OPTIONS as Bt, DEFAULT_MAIN_ATTACK_OPTIONS as C, containDrawRule as Cn, DEFAULT_BLOCK_MISSION_TASK_OPTIONS as Ct, DEFAULT_WITHDRAW_OPTIONS as D, getMidpointPerpendicularSignedDistance as Dn, DEFAULT_BOUNDARY_OPTIONS as Dt, DEFAULT_WITHDRAW_UNDER_PRESSURE_OPTIONS as E, createMidpointPerpendicularDrawRule as En, DEFAULT_LIGHT_LINE_OPTIONS as Et, DEFAULT_FORTIFIED_LINE_OPTIONS as F, sphericalBearing as Fn, DEFAULT_ANTITANK_DITCH_OPTIONS as Ft, DEFAULT_HOLDING_LINE_OPTIONS as G, canonicalTextAmplifierKey as Gt, DEFAULT_HANDOVER_LINE_OPTIONS as H, DEFAULT_DIRECTION_OF_MAIN_ATTACK_OPTIONS as Ht, DEFAULT_PROBABLE_LINE_OF_DEPLOYMENT_OPTIONS as I, unproject as In, DEFAULT_ASSEMBLY_AREA_OPTIONS as It, DEFAULT_FLOT_OPTIONS as J, DEFAULT_LABEL_HEIGHT_CSS_PIXELS as Jt, DEFAULT_BRIDGEHEAD_LINE_OPTIONS as K, normalizeTextAmplifiers as Kt, PROBABLE_LINE_OF_DEPLOYMENT_DASH as L, EPSILON as Ln, DEFAULT_AREA_OF_OPERATIONS_OPTIONS as Lt, DEFAULT_PENETRATE_OPTIONS as M, computeInitialWidthPoint as Mn, DEFAULT_ATTACK_HELICOPTER_OPTIONS as Mt, DEFAULT_FRONTAL_ATTACK_OPTIONS as N, haversineDistance as Nn, DEFAULT_ATTACK_BY_FIRE_OPTIONS as Nt, DEFAULT_SEIZE_OPTIONS as O, pointOnMidpointPerpendicularAxis as On, DEFAULT_BLOCK_ARROW_OPTIONS as Ot, DEFAULT_FORTIFIED_AREA_OPTIONS as P, project as Pn, DEFAULT_ANTITANK_WALL_OPTIONS as Pt, DEFAULT_ENCIRCLEMENT_OPTIONS as Q, DEFAULT_PORTRAYAL as Qt, DEFAULT_LINE_OF_DEPARTURE_CONTACT_OPTIONS as R, getMetersPerPixel as Rn, DEFAULT_MOBILE_DEFENSE_OPTIONS as Rt, DEFAULT_MINEFIELD_OPTIONS as S, centerRadiusDrawRule as Sn, DEFAULT_BREACH_OPTIONS as St, DEFAULT_JOINT_TACTICAL_ACTION_AREA_OPTIONS as T, computeDefaultMidpointPerpendicularPoint as Tn, DEFAULT_ENGINEER_WORK_LINE_OPTIONS as Tt, DEFAULT_FORWARD_EDGE_OF_BATTLE_AREA_OPTIONS as U, DEFAULT_DIRECTION_OF_ATTACK_AVIATION_OPTIONS as Ut, DEFAULT_BATTLE_HANDOVER_LINE_OPTIONS as V, DEFAULT_DIRECTION_OF_SUPPORTING_ATTACK_OPTIONS as Vt, DEFAULT_RELEASE_LINE_OPTIONS as W, TEXT_AMPLIFIER_FIELDS as Wt, DEFAULT_FIX_OPTIONS as X, DEFAULT_LINE_CAP as Xt, DEFAULT_FIX_MISSION_TASK_OPTIONS as Y, DEFAULT_LABEL_SIZE_CLAMP_CSS_PIXELS as Yt, DEFAULT_FINAL_PROTECTIVE_FIRE_OPTIONS as Z, DEFAULT_LINE_JOIN as Zt, DEFAULT_PRINCIPAL_DIRECTION_OF_FIRE_OPTIONS as _, staticPointDrawRule as _n, DEFAULT_GENERIC_LINE_OPTIONS as _t, DEFINITIONS as a, axis1DrawRule as an, DEFAULT_TACTICAL_ARROW_OPTIONS as at, DEFAULT_OBSTACLE_BYPASS_EASY_OPTIONS as b, disruptDrawRule as bn, DEFAULT_CANALIZE_OPTIONS as bt, getDefaultOptions as c, line26DrawRule as cn, DEFAULT_SUPPORTING_ATTACK_OPTIONS as ct, DEFAULT_TURN_OPTIONS as d, turnDrawRule as dn, RADAR_SEARCH_DOCTRINE_FILL_COLOR as dt, DEFAULT_STROKE_WIDTH_CSS_PIXELS as en, DEFAULT_GUARD_OPTIONS as et, DEFAULT_SUPPORT_BY_FIRE_OPTIONS as f, line1DrawRule as fn, RADAR_SEARCH_DOCTRINE_STROKE_COLOR as ft, SECONDARY_DIRECTION_OF_FIRE_DASH as g, sectorDrawRule as gn, DEFAULT_GENERIC_POLYGON_OPTIONS as gt, DEFAULT_SECONDARY_DIRECTION_OF_FIRE_OPTIONS as h, dynamicPointDrawRule as hn, DEFAULT_GENERIC_RECTANGLE_OPTIONS as ht, CONTROL_MEASURE_METADATA as i, rectangleDrawRule as in, DEFAULT_COVER_OPTIONS as it, DEFAULT_REARWARD_PASSAGE_OF_LINES_OPTIONS as j, calculateMetrics as jn, DEFAULT_BATTLE_POSITION_OPTIONS as jt, DEFAULT_SCREEN_OPTIONS as k, snapToMidpointPerpendicular as kn, DEFAULT_BLOCK_OPTIONS as kt, listControlMeasureMetadata as l, line24DrawRule as ln, DEFAULT_CLEAR_OPTIONS as lt, DEFAULT_STRONG_POINT_OPTIONS as m, attackByFireDrawRule as mn, DEFAULT_GENERIC_SECTOR_OPTIONS as mt, resolveStyleHints as n, DEFAULT_AMBUSH_OPTIONS as nn, DEFAULT_DISENGAGE_OPTIONS as nt, getControlMeasureMetadata as o, supportByFireDrawRule as on, DEFAULT_COUNTERATTACK_BY_FIRE_OPTIONS as ot, DEFAULT_RETAIN_OPTIONS as p, ambushDrawRule as pn, DEFAULT_GENERIC_TEXT_OPTIONS as pt, DEFAULT_PHASE_LINE_OPTIONS as q, resolveAmplifierPlacement as qt, CONTROL_MEASURE_IDS as r, DEFAULT_AIRBORNE_ATTACK_OPTIONS as rn, DEFAULT_DELAY_OPTIONS as rt, getControlMeasureMetadataByValue as s, line27DrawRule as sn, DEFAULT_COUNTERATTACK_OPTIONS as st, renderControlMeasure as t, DEFAULT_SYMBOL_COLOR as tn, DEFAULT_DISRUPT_MISSION_TASK_OPTIONS as tt, DEFAULT_TURNING_MOVEMENT_OPTIONS as u, line23DrawRule as un, DEFAULT_RADAR_SEARCH_DOCTRINE_OPTIONS as ut, DEFAULT_PICKUP_ZONE_OPTIONS as v, point12DrawRule as vn, DEFAULT_GENERIC_CIRCLE_OPTIONS as vt, DEFAULT_LANDING_ZONE_OPTIONS as w, area27DrawRule as wn, DEFAULT_GENERIC_C2_LINE_OPTIONS as wt, DEFAULT_OBSTACLE_BYPASS_DIFFICULT_OPTIONS as x, blockDrawRule as xn, DEFAULT_BYPASS_OPTIONS as xt, DEFAULT_OBSTACLE_BYPASS_IMPOSSIBLE_OPTIONS as y, penetrateDrawRule as yn, DEFAULT_CLASSIC_ARROW_OPTIONS as yt, DEFAULT_LINE_OF_DEPARTURE_OPTIONS as z, roundToFixed as zn, DEFAULT_ENGAGEMENT_AREA_OPTIONS as zt };
|