@orbat-mapper/control-measures 0.8.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-QyKWkgNo.d.mts → index-nz-U5HlG.d.mts} +138 -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-BHIGHuop.mjs → renderControlMeasure-D9mfP_gv.mjs} +785 -49
- 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,6 +586,24 @@ const area1DrawRule = {
|
|
|
586
586
|
}
|
|
587
587
|
};
|
|
588
588
|
//#endregion
|
|
589
|
+
//#region src/draw-rules/area27.ts
|
|
590
|
+
/**
|
|
591
|
+
* Area27 anchor draw rule for Mobile Defense.
|
|
592
|
+
*
|
|
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.
|
|
597
|
+
*/
|
|
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
|
|
589
607
|
//#region src/draw-rules/area5.ts
|
|
590
608
|
/**
|
|
591
609
|
* Area5 — PT. 1/PT. 2 define the open diameter and PT. 3 defines the arrow end.
|
|
@@ -2823,7 +2841,7 @@ function pushEndLabelsAbove(out, verts, textForEnd, options = {}) {
|
|
|
2823
2841
|
* as a complete FeatureCollection.
|
|
2824
2842
|
*/
|
|
2825
2843
|
function fixedLabelLineFeatures(positions, options, textForEnd, hostileText, context) {
|
|
2826
|
-
const { part, placement = "beyond", phaseLineName, includePrefix = true, smooth, smoothResolution, ...sizeOptions } = options;
|
|
2844
|
+
const { part, placement = "beyond", phaseLineName, includePrefix = true, lineStyle, smooth, smoothResolution, ...sizeOptions } = options;
|
|
2827
2845
|
const verts = smoothLineVerts(positions.map((p) => project(p[0], p[1])), {
|
|
2828
2846
|
smooth,
|
|
2829
2847
|
smoothResolution
|
|
@@ -2831,7 +2849,10 @@ function fixedLabelLineFeatures(positions, options, textForEnd, hostileText, con
|
|
|
2831
2849
|
const lineCoordinates = smooth ? verts.map((v) => unproject(v[0], v[1])) : positions;
|
|
2832
2850
|
const features = [{
|
|
2833
2851
|
type: "Feature",
|
|
2834
|
-
properties: { part }
|
|
2852
|
+
properties: lineStyle === void 0 ? { part } : {
|
|
2853
|
+
part,
|
|
2854
|
+
style: lineStyle
|
|
2855
|
+
},
|
|
2835
2856
|
geometry: {
|
|
2836
2857
|
type: "LineString",
|
|
2837
2858
|
coordinates: lineCoordinates
|
|
@@ -4361,6 +4382,273 @@ const AREA_DEFENSE = defineControlMeasure({
|
|
|
4361
4382
|
}
|
|
4362
4383
|
});
|
|
4363
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
|
|
4364
4652
|
//#region src/generators/cm12-command-control-areas/area-of-operations.ts
|
|
4365
4653
|
/** Default options for the Area of operations control measure. */
|
|
4366
4654
|
const DEFAULT_AREA_OF_OPERATIONS_OPTIONS = {
|
|
@@ -5684,33 +5972,36 @@ const BATTLE_POSITION_METADATA = {
|
|
|
5684
5972
|
textAmplifiers: AREA_UNIT_DESIGNATION_HOSTILE_AMPLIFIERS
|
|
5685
5973
|
};
|
|
5686
5974
|
/**
|
|
5687
|
-
*
|
|
5688
|
-
*
|
|
5689
|
-
*
|
|
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.
|
|
5690
5983
|
*
|
|
5691
|
-
*
|
|
5692
|
-
*
|
|
5693
|
-
* @param options - {@link BattlePositionOptions}.
|
|
5694
|
-
* @param textAmplifiers - Field T (designation, centered in the area) plus an
|
|
5695
|
-
* optional Field N "ENY" hostile marker at the west/east edges (no H/W rows —
|
|
5696
|
-
* this measure's template pairs the designation with the Field B glyph, not
|
|
5697
|
-
* 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.
|
|
5698
5986
|
*/
|
|
5699
|
-
function
|
|
5987
|
+
function battlePositionAreaFeatures(positions, options, textAmplifiers, context, variant = {}) {
|
|
5700
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;
|
|
5701
5989
|
const h = resolveEchelonHeight(echelonSize, echelonSizePixels, metersPerPixel);
|
|
5702
5990
|
const verts = buildClosedRing(positions, smooth, smoothResolution, DEFAULT_SMOOTH_RESOLUTION$6);
|
|
5703
5991
|
const { strokes, fills, gaps } = placeEchelon(buildAreaPerimeter(verts), h, echelon, echelonPadding, echelonPosition, echelonAnchor);
|
|
5704
5992
|
const labelFeatures = [];
|
|
5705
5993
|
const enyGaps = pushAreaLabels(labelFeatures, verts, {
|
|
5706
|
-
name: textAmplifiers.T,
|
|
5994
|
+
name: variant.namePrefix ? composeAreaName(variant.namePrefix, textAmplifiers.T) : textAmplifiers.T,
|
|
5707
5995
|
hostile: textAmplifiers.N
|
|
5708
5996
|
}, options, context.amplifierPlacements, void 0, context);
|
|
5709
5997
|
const boundaryCoords = buildGappedLine(verts, [...gaps, ...enyGaps]);
|
|
5710
5998
|
const features = [];
|
|
5711
5999
|
if (boundaryCoords.length > 0) features.push({
|
|
5712
6000
|
type: "Feature",
|
|
5713
|
-
properties:
|
|
6001
|
+
properties: variant.boundaryStyle ? {
|
|
6002
|
+
part: "boundary",
|
|
6003
|
+
style: variant.boundaryStyle
|
|
6004
|
+
} : { part: "boundary" },
|
|
5714
6005
|
geometry: {
|
|
5715
6006
|
type: "MultiLineString",
|
|
5716
6007
|
coordinates: boundaryCoords
|
|
@@ -5743,6 +6034,22 @@ function createBattlePosition(positions, options = {}, textAmplifiers = {}, cont
|
|
|
5743
6034
|
};
|
|
5744
6035
|
}
|
|
5745
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
|
+
/**
|
|
5746
6053
|
* `freezeOrientation` hook (ADR-0023) shared by Battle Position and Strong
|
|
5747
6054
|
* Point: pins the echelon anchor to its current bottom-edge fraction so a
|
|
5748
6055
|
* subsequent rigid rotation of `controlPoints` (e.g. the transform box's
|
|
@@ -5780,44 +6087,76 @@ const BATTLE_POSITION = defineControlMeasure({
|
|
|
5780
6087
|
}
|
|
5781
6088
|
});
|
|
5782
6089
|
//#endregion
|
|
5783
|
-
//#region src/
|
|
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
|
+
],
|
|
6110
|
+
paints: {
|
|
6111
|
+
stroke: true,
|
|
6112
|
+
fill: "fixed",
|
|
6113
|
+
text: true
|
|
6114
|
+
},
|
|
6115
|
+
drawRule: "Area1",
|
|
6116
|
+
capturesLabelSize: true,
|
|
6117
|
+
params: ECHELON_AREA_PARAMS,
|
|
6118
|
+
textAmplifiers: AREA_UNIT_DESIGNATION_HOSTILE_AMPLIFIERS
|
|
6119
|
+
};
|
|
5784
6120
|
/**
|
|
5785
|
-
*
|
|
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.
|
|
5786
6125
|
*
|
|
5787
|
-
*
|
|
5788
|
-
*
|
|
5789
|
-
*
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
const base = vecSub(tip, vecScale(dir, length));
|
|
5794
|
-
const left = vecAdd(base, vecScale(perp, width / 2));
|
|
5795
|
-
const right = vecSub(base, vecScale(perp, width / 2));
|
|
5796
|
-
return [
|
|
5797
|
-
unproject(left[0], left[1]),
|
|
5798
|
-
unproject(tip[0], tip[1]),
|
|
5799
|
-
unproject(right[0], right[1]),
|
|
5800
|
-
unproject(left[0], left[1])
|
|
5801
|
-
];
|
|
5802
|
-
}
|
|
5803
|
-
//#endregion
|
|
5804
|
-
//#region src/internal/projected-arc.ts
|
|
5805
|
-
/** The arc point at `angle`, in projected meters. */
|
|
5806
|
-
function arcPointAt(center, startRadius, bulgeRadius, angle) {
|
|
5807
|
-
return vecAdd(center, vecAdd(vecScale(startRadius, Math.cos(angle)), vecScale(bulgeRadius, Math.sin(angle))));
|
|
5808
|
-
}
|
|
5809
|
-
/**
|
|
5810
|
-
* Samples the arc from `start` to `end` radians as unprojected positions.
|
|
5811
|
-
* `resolution` is the segment count a full half-turn would use, so the density
|
|
5812
|
-
* of a partial arc stays proportional to how much of the circle it covers.
|
|
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.
|
|
5813
6132
|
*/
|
|
5814
|
-
function
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
return unproject(point[0], point[1]);
|
|
6133
|
+
function createBattlePositionPrepared(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
6134
|
+
return battlePositionAreaFeatures(positions, options, textAmplifiers, context, {
|
|
6135
|
+
namePrefix: "(P)",
|
|
6136
|
+
boundaryStyle: { strokeDash: [...BATTLE_POSITION_PREPARED_DASH] }
|
|
5819
6137
|
});
|
|
5820
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
|
+
});
|
|
5821
6160
|
//#endregion
|
|
5822
6161
|
//#region src/generators/cm15-maneuver-areas/contain.ts
|
|
5823
6162
|
const DEFAULT_CONTAIN_OPTIONS = {
|
|
@@ -11227,6 +11566,155 @@ const BRIDGEHEAD_LINE = defineControlMeasure({
|
|
|
11227
11566
|
}
|
|
11228
11567
|
});
|
|
11229
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
|
|
11230
11718
|
//#region src/generators/cm14-maneuver-lines/holding-line.ts
|
|
11231
11719
|
/** Default options for the Holding line control measure. */
|
|
11232
11720
|
const DEFAULT_HOLDING_LINE_OPTIONS = {
|
|
@@ -11616,6 +12104,193 @@ const BATTLE_HANDOVER_LINE = defineControlMeasure({
|
|
|
11616
12104
|
}
|
|
11617
12105
|
});
|
|
11618
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
|
|
11619
12294
|
//#region src/generators/cm29-protection-lines/fortifiedLine.ts
|
|
11620
12295
|
const DEFAULT_TOOTH_SIZE = 50;
|
|
11621
12296
|
const DEFAULT_TOOTH_SIZE_PIXELS = 10;
|
|
@@ -12856,6 +13531,57 @@ const LANDING_ZONE = defineControlMeasure({
|
|
|
12856
13531
|
}
|
|
12857
13532
|
});
|
|
12858
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
|
|
12859
13585
|
//#region src/generators/cm15-maneuver-areas/limitedAccessArea.ts
|
|
12860
13586
|
/** Default options for the Limited Access Area control measure. */
|
|
12861
13587
|
const DEFAULT_LIMITED_ACCESS_AREA_OPTIONS = {
|
|
@@ -15233,6 +15959,7 @@ const DEFINITIONS = {
|
|
|
15233
15959
|
"engineer-work-line": ENGINEER_WORK_LINE,
|
|
15234
15960
|
"generic-c2-line": GENERIC_C2_LINE,
|
|
15235
15961
|
"battle-position": BATTLE_POSITION,
|
|
15962
|
+
"battle-position-prepared": BATTLE_POSITION_PREPARED,
|
|
15236
15963
|
contain: CONTAIN,
|
|
15237
15964
|
"strong-point": STRONG_POINT,
|
|
15238
15965
|
retain: RETAIN,
|
|
@@ -15248,6 +15975,8 @@ const DEFINITIONS = {
|
|
|
15248
15975
|
encirclement: ENCIRCLEMENT,
|
|
15249
15976
|
area: AREA,
|
|
15250
15977
|
"area-defense": AREA_DEFENSE,
|
|
15978
|
+
"engagement-area": ENGAGEMENT_AREA,
|
|
15979
|
+
"mobile-defense": MOBILE_DEFENSE,
|
|
15251
15980
|
"assembly-area": ASSEMBLY_AREA,
|
|
15252
15981
|
"joint-tactical-action-area": JOINT_TACTICAL_ACTION_AREA,
|
|
15253
15982
|
"submarine-action-area": SUBMARINE_ACTION_AREA,
|
|
@@ -15256,6 +15985,7 @@ const DEFINITIONS = {
|
|
|
15256
15985
|
"extraction-zone": EXTRACTION_ZONE,
|
|
15257
15986
|
"landing-zone": LANDING_ZONE,
|
|
15258
15987
|
"limited-access-area": LIMITED_ACCESS_AREA,
|
|
15988
|
+
"penetration-box": PENETRATION_BOX,
|
|
15259
15989
|
"no-fire-area-irregular": NO_FIRE_AREA_IRREGULAR,
|
|
15260
15990
|
"minefield-dynamic": DYNAMIC_MINEFIELD,
|
|
15261
15991
|
"mined-area": MINED_AREA,
|
|
@@ -15289,10 +16019,16 @@ const DEFINITIONS = {
|
|
|
15289
16019
|
"phase-line": PHASE_LINE,
|
|
15290
16020
|
"forward-edge-of-battle-area": FORWARD_EDGE_OF_BATTLE_AREA,
|
|
15291
16021
|
"bridgehead-line": BRIDGEHEAD_LINE,
|
|
16022
|
+
"final-coordination-line": FINAL_COORDINATION_LINE,
|
|
16023
|
+
"airhead-line": AIRHEAD_LINE,
|
|
15292
16024
|
"holding-line": HOLDING_LINE,
|
|
15293
16025
|
"release-line": RELEASE_LINE,
|
|
15294
16026
|
"handover-line": HANDOVER_LINE,
|
|
15295
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,
|
|
15296
16032
|
"block-mission-task": BLOCK_MISSION_TASK,
|
|
15297
16033
|
breach: BREACH,
|
|
15298
16034
|
bypass: BYPASS,
|
|
@@ -15735,4 +16471,4 @@ function assertNever(value) {
|
|
|
15735
16471
|
throw new Error(`Unhandled control measure kind: ${String(value)}`);
|
|
15736
16472
|
}
|
|
15737
16473
|
//#endregion
|
|
15738
|
-
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 };
|