@orbat-mapper/control-measures 0.5.2 → 0.7.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 +1 -0
- package/dist/{index-B9foUCfy.d.mts → index-B3LgMKpn.d.mts} +133 -83
- package/dist/index.d.mts +3 -2
- package/dist/index.mjs +3 -2
- package/dist/patterns-CcQmmOuJ.d.mts +116 -0
- package/dist/patterns.d.mts +2 -0
- package/dist/patterns.mjs +205 -0
- package/dist/preview/index.d.mts +15 -11
- package/dist/preview/index.mjs +67 -84
- package/dist/{renderControlMeasure-DaBe00Or.mjs → renderControlMeasure-GsV4DV3r.mjs} +510 -118
- package/media/limited-access-area.svg +1 -1
- package/media/mined-area.svg +1 -0
- package/media/minefield-dynamic.svg +1 -0
- package/media/minefield.svg +1 -0
- package/media/no-fire-area-irregular.svg +1 -1
- package/media/sector.svg +1 -0
- package/package.json +2 -1
|
@@ -560,7 +560,7 @@ function samePosition(a, b) {
|
|
|
560
560
|
}
|
|
561
561
|
//#endregion
|
|
562
562
|
//#region src/draw-rules/area1.ts
|
|
563
|
-
function derive$
|
|
563
|
+
function derive$12(points) {
|
|
564
564
|
return clonePositions(points);
|
|
565
565
|
}
|
|
566
566
|
/**
|
|
@@ -580,9 +580,9 @@ const area1DrawRule = {
|
|
|
580
580
|
id: "area1",
|
|
581
581
|
minimumUserPoints: 3,
|
|
582
582
|
closedRing: true,
|
|
583
|
-
derive: derive$
|
|
583
|
+
derive: derive$12,
|
|
584
584
|
transform(event) {
|
|
585
|
-
return derive$
|
|
585
|
+
return derive$12(event.next);
|
|
586
586
|
}
|
|
587
587
|
};
|
|
588
588
|
//#endregion
|
|
@@ -608,14 +608,14 @@ const disruptDrawRule = createMidpointPerpendicularDrawRule({
|
|
|
608
608
|
* that fixes both the radius and the bearing of the symbol's opening. Both
|
|
609
609
|
* points are user-clicked, so the canonical array is just the (clamped) input.
|
|
610
610
|
*/
|
|
611
|
-
function derive$
|
|
611
|
+
function derive$11(points) {
|
|
612
612
|
return clonePositions(points.slice(0, 2));
|
|
613
613
|
}
|
|
614
614
|
const centerRadiusDrawRule = {
|
|
615
615
|
id: "area15:center-radius",
|
|
616
616
|
minimumUserPoints: 2,
|
|
617
617
|
canonicalPointCount: 2,
|
|
618
|
-
derive: derive$
|
|
618
|
+
derive: derive$11,
|
|
619
619
|
transform(event) {
|
|
620
620
|
const { previous, next, activePointIndex } = event;
|
|
621
621
|
if (activePointIndex === 0 && previous.length >= 2 && next.length >= 2) {
|
|
@@ -624,7 +624,7 @@ const centerRadiusDrawRule = {
|
|
|
624
624
|
const origin = previous[1];
|
|
625
625
|
return [clonePosition(next[0]), [origin[0] + dx, origin[1] + dy]];
|
|
626
626
|
}
|
|
627
|
-
return derive$
|
|
627
|
+
return derive$11(next);
|
|
628
628
|
}
|
|
629
629
|
};
|
|
630
630
|
/** Backward-compatible doctrinal name for Area15's shared center-radius rule. */
|
|
@@ -660,16 +660,16 @@ const point12DrawRule = createMidpointPerpendicularDrawRule({ id: "point12:obsta
|
|
|
660
660
|
* Point1 — a single anchor point (e.g. Text's center). One user click commits
|
|
661
661
|
* the measure; dragging the point simply translates it.
|
|
662
662
|
*/
|
|
663
|
-
function derive$
|
|
663
|
+
function derive$10(points) {
|
|
664
664
|
return clonePositions(points.slice(0, 1));
|
|
665
665
|
}
|
|
666
666
|
const pointDrawRule = {
|
|
667
667
|
id: "point1:anchor",
|
|
668
668
|
minimumUserPoints: 1,
|
|
669
669
|
canonicalPointCount: 1,
|
|
670
|
-
derive: derive$
|
|
670
|
+
derive: derive$10,
|
|
671
671
|
transform(event) {
|
|
672
|
-
return derive$
|
|
672
|
+
return derive$10(event.next);
|
|
673
673
|
}
|
|
674
674
|
};
|
|
675
675
|
//#endregion
|
|
@@ -689,6 +689,81 @@ const staticPointDrawRule = {
|
|
|
689
689
|
id: "point2:static-anchor"
|
|
690
690
|
};
|
|
691
691
|
//#endregion
|
|
692
|
+
//#region src/draw-rules/sector.ts
|
|
693
|
+
const SECTOR_ANGLE_SNAP_RADIANS = Math.PI / 36;
|
|
694
|
+
/**
|
|
695
|
+
* Sector — anchor + inner-left edge + outer-right edge. The two radial points
|
|
696
|
+
* independently control a radius and a true-north azimuth. Dragging the anchor
|
|
697
|
+
* translates both edge points so the sector retains its size and orientation.
|
|
698
|
+
*/
|
|
699
|
+
function derive$9(points) {
|
|
700
|
+
return clonePositions(points.slice(0, 3));
|
|
701
|
+
}
|
|
702
|
+
function guidePoints$1(points) {
|
|
703
|
+
return points.length < 3 ? clonePositions(points) : [];
|
|
704
|
+
}
|
|
705
|
+
/** Snap one radial Sector handle to a true-north angular increment. */
|
|
706
|
+
function snapSectorAngle(points, activePointIndex, incrementRadians) {
|
|
707
|
+
const next = clonePositions(points);
|
|
708
|
+
if (activePointIndex !== 1 && activePointIndex !== 2) return next;
|
|
709
|
+
const anchor = next[0];
|
|
710
|
+
const point = next[activePointIndex];
|
|
711
|
+
if (!anchor || !point) return next;
|
|
712
|
+
const range = haversineDistance(anchor, point);
|
|
713
|
+
const bearing = sphericalBearing(anchor, point);
|
|
714
|
+
next[activePointIndex] = [...destinationPoint(anchor, range, Math.round(bearing / incrementRadians) * incrementRadians), ...point.slice(2)];
|
|
715
|
+
return next;
|
|
716
|
+
}
|
|
717
|
+
function constrainAngles(points, activePointIndex) {
|
|
718
|
+
return snapSectorAngle(points, activePointIndex, SECTOR_ANGLE_SNAP_RADIANS);
|
|
719
|
+
}
|
|
720
|
+
const sectorDrawRule = {
|
|
721
|
+
id: "sector:anchor-radii",
|
|
722
|
+
minimumUserPoints: 3,
|
|
723
|
+
canonicalPointCount: 3,
|
|
724
|
+
showGuide: true,
|
|
725
|
+
guidePoints: guidePoints$1,
|
|
726
|
+
constrainAngles,
|
|
727
|
+
derive: derive$9,
|
|
728
|
+
transform(event) {
|
|
729
|
+
const { previous, next, activePointIndex } = event;
|
|
730
|
+
if (activePointIndex === 0 && previous.length >= 3 && next.length >= 3) {
|
|
731
|
+
const dx = next[0][0] - previous[0][0];
|
|
732
|
+
const dy = next[0][1] - previous[0][1];
|
|
733
|
+
return [
|
|
734
|
+
clonePosition(next[0]),
|
|
735
|
+
[previous[1][0] + dx, previous[1][1] + dy],
|
|
736
|
+
[previous[2][0] + dx, previous[2][1] + dy]
|
|
737
|
+
];
|
|
738
|
+
}
|
|
739
|
+
return derive$9(next);
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
//#endregion
|
|
743
|
+
//#region src/draw-rules/point18.ts
|
|
744
|
+
const WHOLE_DEGREE_RADIANS = Math.PI / 180;
|
|
745
|
+
function snapToWholeDegrees(points) {
|
|
746
|
+
let snapped = snapSectorAngle(points, 1, WHOLE_DEGREE_RADIANS);
|
|
747
|
+
if (snapped.length >= 3) snapped = snapSectorAngle(snapped, 2, WHOLE_DEGREE_RADIANS);
|
|
748
|
+
return snapped;
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Point18 — doctrinally a numeric, single-anchor symbol. The interactive
|
|
752
|
+
* authoring model exposes those dimensions as Sector-style radial handles:
|
|
753
|
+
* P1 is the anchor, P2 fixes the start range/left bearing, and P3 fixes the
|
|
754
|
+
* stop range/right bearing.
|
|
755
|
+
*/
|
|
756
|
+
const dynamicPointDrawRule = {
|
|
757
|
+
...sectorDrawRule,
|
|
758
|
+
id: "point18:anchor-radii",
|
|
759
|
+
derive(points) {
|
|
760
|
+
return snapToWholeDegrees(sectorDrawRule.derive(points));
|
|
761
|
+
},
|
|
762
|
+
transform(event) {
|
|
763
|
+
return snapSectorAngle(sectorDrawRule.transform(event), event.activePointIndex, WHOLE_DEGREE_RADIANS);
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
//#endregion
|
|
692
767
|
//#region src/draw-rules/area7.ts
|
|
693
768
|
/**
|
|
694
769
|
* Area7 anchor draw rule for Attack By Fire.
|
|
@@ -2056,6 +2131,43 @@ function buildGappedLine(verts, gaps) {
|
|
|
2056
2131
|
return parts;
|
|
2057
2132
|
}
|
|
2058
2133
|
//#endregion
|
|
2134
|
+
//#region src/style.ts
|
|
2135
|
+
/**
|
|
2136
|
+
* Per-feature style hint pinning a filled part to a solid interior. Attach to
|
|
2137
|
+
* a generator's intrinsic doctrinal accents — arrowheads, teeth, echelon
|
|
2138
|
+
* glyphs, barbs, blades — so a patterned (`hatch`, `dots`, …) fill set at the graphicsStyle
|
|
2139
|
+
* or measure layer never bleeds into a small silhouette and ruins its
|
|
2140
|
+
* legibility. The renderer only reads style hints, so one shared instance is
|
|
2141
|
+
* safe to reuse across every accent feature.
|
|
2142
|
+
*/
|
|
2143
|
+
const SOLID_ACCENT_FILL = { fillPattern: "solid" };
|
|
2144
|
+
/**
|
|
2145
|
+
* Ultimate fallback for the symbol color when no `color` or per-channel
|
|
2146
|
+
* override is supplied at any layer. Keeps a zero-config render monocolor
|
|
2147
|
+
* black and guarantees filled parts still render filled. See ADR-0011.
|
|
2148
|
+
*/
|
|
2149
|
+
const DEFAULT_SYMBOL_COLOR = "#000000";
|
|
2150
|
+
//#endregion
|
|
2151
|
+
//#region src/portrayal.ts
|
|
2152
|
+
const DEFAULT_STROKE_WIDTH_CSS_PIXELS = 2;
|
|
2153
|
+
const DEFAULT_STROKE_DASH_CSS_PIXELS = Object.freeze([]);
|
|
2154
|
+
const DEFAULT_LINE_CAP = "round";
|
|
2155
|
+
const DEFAULT_LINE_JOIN = "round";
|
|
2156
|
+
const DEFAULT_LABEL_HEIGHT_CSS_PIXELS = 14;
|
|
2157
|
+
const DEFAULT_LABEL_SIZE_CLAMP_CSS_PIXELS = {
|
|
2158
|
+
min: 8,
|
|
2159
|
+
max: 24
|
|
2160
|
+
};
|
|
2161
|
+
/** Shared absent-value portrayal defaults; render output remains sparse. */
|
|
2162
|
+
const DEFAULT_PORTRAYAL = Object.freeze({
|
|
2163
|
+
symbolColor: DEFAULT_SYMBOL_COLOR,
|
|
2164
|
+
strokeWidthCssPixels: 2,
|
|
2165
|
+
strokeDashCssPixels: DEFAULT_STROKE_DASH_CSS_PIXELS,
|
|
2166
|
+
lineCap: DEFAULT_LINE_CAP,
|
|
2167
|
+
lineJoin: DEFAULT_LINE_JOIN,
|
|
2168
|
+
labelHeightCssPixels: 14
|
|
2169
|
+
});
|
|
2170
|
+
//#endregion
|
|
2059
2171
|
//#region src/internal/angle-utils.ts
|
|
2060
2172
|
/**
|
|
2061
2173
|
* Wraps an angle (radians) into the half-open range (-π, π].
|
|
@@ -2566,6 +2678,39 @@ const LABEL_CHAR_ASPECT_RATIO = .6;
|
|
|
2566
2678
|
function estimatedTextWidth(text, textHeightMeters) {
|
|
2567
2679
|
return text.length * LABEL_CHAR_ASPECT_RATIO * textHeightMeters;
|
|
2568
2680
|
}
|
|
2681
|
+
/** Resolve the label's final display size, then measure and convert it to construction metres. */
|
|
2682
|
+
function resolveTextMetrics(text, options, context, style = "regular") {
|
|
2683
|
+
const legacyHeight = resolveLabelOffsetMeters(options, 1);
|
|
2684
|
+
const constructionScale = context?.constructionMetersPerCssPixel;
|
|
2685
|
+
if (!(constructionScale !== void 0 && constructionScale > 0)) return {
|
|
2686
|
+
width: estimatedTextWidth(text, legacyHeight),
|
|
2687
|
+
height: legacyHeight
|
|
2688
|
+
};
|
|
2689
|
+
let sizeCssPixels;
|
|
2690
|
+
if (options.labelSizePixels !== void 0) sizeCssPixels = options.labelSizePixels;
|
|
2691
|
+
else if (options.labelSize !== void 0) {
|
|
2692
|
+
const requested = options.labelSize / constructionScale;
|
|
2693
|
+
const requestedBand = context?.labelSizeClampCssPixels;
|
|
2694
|
+
const band = requestedBand && Number.isFinite(requestedBand.min) && requestedBand.min >= 0 && Number.isFinite(requestedBand.max) && requestedBand.max >= 0 ? requestedBand : DEFAULT_LABEL_SIZE_CLAMP_CSS_PIXELS;
|
|
2695
|
+
const min = Math.min(band.min, band.max);
|
|
2696
|
+
const max = Math.max(band.min, band.max);
|
|
2697
|
+
sizeCssPixels = Math.min(max, Math.max(min, requested));
|
|
2698
|
+
} else sizeCssPixels = 14;
|
|
2699
|
+
const fallbackHeight = sizeCssPixels * constructionScale;
|
|
2700
|
+
const measured = context?.measureText?.({
|
|
2701
|
+
text,
|
|
2702
|
+
style,
|
|
2703
|
+
sizeCssPixels
|
|
2704
|
+
});
|
|
2705
|
+
if (!measured || !Number.isFinite(measured.widthCssPixels) || measured.widthCssPixels < 0 || !Number.isFinite(measured.heightCssPixels) || measured.heightCssPixels < 0) return {
|
|
2706
|
+
width: estimatedTextWidth(text, fallbackHeight),
|
|
2707
|
+
height: fallbackHeight
|
|
2708
|
+
};
|
|
2709
|
+
return {
|
|
2710
|
+
width: measured.widthCssPixels * constructionScale,
|
|
2711
|
+
height: measured.heightCssPixels * constructionScale
|
|
2712
|
+
};
|
|
2713
|
+
}
|
|
2569
2714
|
/** The end-label text for a phase-line naming, or `""` when unnamed. */
|
|
2570
2715
|
function phaseLineLabelText(name, includePrefix) {
|
|
2571
2716
|
if (!name) return "";
|
|
@@ -2577,17 +2722,17 @@ function phaseLineLabelText(name, includePrefix) {
|
|
|
2577
2722
|
* {@link pushEndLabels}. When another beyond-tip label is present, its
|
|
2578
2723
|
* estimated width is added so the hostile marker remains outermost.
|
|
2579
2724
|
*/
|
|
2580
|
-
function pushHostileEndLabels(out, verts, hostileText, options = {}, occupiedTextForEnd = "") {
|
|
2725
|
+
function pushHostileEndLabels(out, verts, hostileText, options = {}, occupiedTextForEnd = "", context) {
|
|
2581
2726
|
if (!hostileText) return;
|
|
2582
2727
|
const frames = endFrames(verts);
|
|
2583
2728
|
const sizeProps = labelSizeProps(options);
|
|
2584
|
-
const textHeight = resolveLabelOffsetMeters(options, 1);
|
|
2585
|
-
const clearance = resolveLabelOffsetMeters(options, END_LABEL_CLEARANCE_RATIO + Math.max(0, options.labelPadding ?? 0));
|
|
2586
2729
|
for (const end of ["start", "end"]) {
|
|
2587
2730
|
const frame = frames[end];
|
|
2588
2731
|
if (!frame) continue;
|
|
2589
2732
|
const occupiedText = typeof occupiedTextForEnd === "string" ? occupiedTextForEnd : occupiedTextForEnd(end);
|
|
2590
|
-
const
|
|
2733
|
+
const metrics = resolveTextMetrics(occupiedText || hostileText, options, context);
|
|
2734
|
+
const clearance = (END_LABEL_CLEARANCE_RATIO + Math.max(0, options.labelPadding ?? 0)) * metrics.height;
|
|
2735
|
+
const distance = clearance + (occupiedText ? metrics.width + clearance : 0);
|
|
2591
2736
|
pushLabel(out, vecAdd(frame.point, vecScale(frame.along, distance)), hostileText, labelRotationAlong(frame.along), sizeProps, frame.along[0] >= 0 ? "start" : "end", "N", `label:N:${end}`);
|
|
2592
2737
|
}
|
|
2593
2738
|
}
|
|
@@ -2646,7 +2791,7 @@ function pushEndLabelsAbove(out, verts, textForEnd, options = {}) {
|
|
|
2646
2791
|
* (via {@link pushEndLabels} or {@link pushEndLabelsAbove}, per `placement`)
|
|
2647
2792
|
* as a complete FeatureCollection.
|
|
2648
2793
|
*/
|
|
2649
|
-
function fixedLabelLineFeatures(positions, options, textForEnd, hostileText) {
|
|
2794
|
+
function fixedLabelLineFeatures(positions, options, textForEnd, hostileText, context) {
|
|
2650
2795
|
const { part, placement = "beyond", phaseLineName, includePrefix = true, smooth, smoothResolution, ...sizeOptions } = options;
|
|
2651
2796
|
const verts = smoothLineVerts(positions.map((p) => project(p[0], p[1])), {
|
|
2652
2797
|
smooth,
|
|
@@ -2666,7 +2811,7 @@ function fixedLabelLineFeatures(positions, options, textForEnd, hostileText) {
|
|
|
2666
2811
|
else pushEndLabels(labelFeatures, verts, textForEnd, sizeOptions);
|
|
2667
2812
|
const nameText = phaseLineLabelText(phaseLineName?.trim(), includePrefix);
|
|
2668
2813
|
if (nameText) pushEndLabels(labelFeatures, verts, nameText, sizeOptions);
|
|
2669
|
-
pushHostileEndLabels(labelFeatures, verts, hostileText, sizeOptions, nameText || (placement === "beyond" ? textForEnd : ""));
|
|
2814
|
+
pushHostileEndLabels(labelFeatures, verts, hostileText, sizeOptions, nameText || (placement === "beyond" ? textForEnd : ""), context);
|
|
2670
2815
|
features.push(...labelFeatures);
|
|
2671
2816
|
return {
|
|
2672
2817
|
type: "FeatureCollection",
|
|
@@ -2832,10 +2977,9 @@ function nonNegative$1(value, fallback) {
|
|
|
2832
2977
|
* later `buildGappedLine` walk); `segments`/`totalLength` describe the full
|
|
2833
2978
|
* route that anchors the default label placement.
|
|
2834
2979
|
*/
|
|
2835
|
-
function directionLabelGap(verts, segments, totalLength, fraction, text, options, placement = {}) {
|
|
2980
|
+
function directionLabelGap(verts, segments, totalLength, fraction, text, options, placement = {}, context) {
|
|
2836
2981
|
if (!text) return void 0;
|
|
2837
|
-
const textHeight =
|
|
2838
|
-
const textWidth = estimatedTextWidth(text, textHeight);
|
|
2982
|
+
const { width: textWidth, height: textHeight } = resolveTextMetrics(text, options, context);
|
|
2839
2983
|
const clearance = (LABEL_GAP_CLEARANCE_RATIO + Math.max(0, options.labelPadding ?? 0)) * textHeight;
|
|
2840
2984
|
const frame = pointAlongPolyline(segments, totalLength, fraction);
|
|
2841
2985
|
const rotation = placement.rotation ?? (frame ? labelRotationAlong(frame.along) : 0);
|
|
@@ -2896,7 +3040,7 @@ function createDirectionOfAttackLine(coordinates, options, textAmplifiers, confi
|
|
|
2896
3040
|
shaftVerts.push(innerTip);
|
|
2897
3041
|
}
|
|
2898
3042
|
const labelPosition = clampLinePosition(options.labelPosition, DEFAULT_DIRECTION_LABEL_POSITION);
|
|
2899
|
-
const labelGap = directionLabelGap(shaftVerts, segments, totalLength, labelPosition, textAmplifiers.T, options, resolveAmplifierPlacement(context.amplifierPlacements?.T));
|
|
3043
|
+
const labelGap = directionLabelGap(shaftVerts, segments, totalLength, labelPosition, textAmplifiers.T, options, resolveAmplifierPlacement(context.amplifierPlacements?.T), context);
|
|
2900
3044
|
const lines = [...buildGappedLine(shaftVerts, labelGap ? [labelGap] : []), arrowhead.map((point) => unproject(point[0], point[1]))];
|
|
2901
3045
|
const features = [{
|
|
2902
3046
|
type: "Feature",
|
|
@@ -3180,7 +3324,7 @@ function createDirectionOfAttackAviation(coordinates, options = {}, textAmplifie
|
|
|
3180
3324
|
const bowTieB = vecSub(bowTieStart, vecScale(bowTiePerp, bowTieHalfWidth));
|
|
3181
3325
|
const bowTieC = vecAdd(bowTieEnd, vecScale(bowTiePerp, bowTieHalfWidth));
|
|
3182
3326
|
const bowTieD = vecSub(bowTieEnd, vecScale(bowTiePerp, bowTieHalfWidth));
|
|
3183
|
-
const labelGap = directionLabelGap(verts, segments, totalLength, labelPosition, textAmplifiers.T, resolved, resolveAmplifierPlacement(context.amplifierPlacements?.T));
|
|
3327
|
+
const labelGap = directionLabelGap(verts, segments, totalLength, labelPosition, textAmplifiers.T, resolved, resolveAmplifierPlacement(context.amplifierPlacements?.T), context);
|
|
3184
3328
|
const features = [{
|
|
3185
3329
|
type: "Feature",
|
|
3186
3330
|
properties: { part: "direction-of-attack-aviation" },
|
|
@@ -3760,9 +3904,12 @@ function ringLabelAnchor(verts) {
|
|
|
3760
3904
|
* overlaps the finite contacted edge; intrinsic markers always mask. Callers
|
|
3761
3905
|
* own marker names, anchors, rotation, clearance, placement keys, and fields.
|
|
3762
3906
|
*/
|
|
3763
|
-
function pushBoundaryMarkers(out, maskVerts, text, anchors, options, amplifierPlacements) {
|
|
3907
|
+
function pushBoundaryMarkers(out, maskVerts, text, anchors, options, amplifierPlacements, context, labelOptions) {
|
|
3764
3908
|
const { rotation, textHeight, clearance, sizeProps } = options;
|
|
3765
|
-
const
|
|
3909
|
+
const metrics = resolveTextMetrics(text, labelOptions ?? { labelSize: textHeight }, context);
|
|
3910
|
+
const textWidth = metrics.width;
|
|
3911
|
+
const resolvedTextHeight = metrics.height;
|
|
3912
|
+
const resolvedClearance = textHeight > 0 ? clearance * (resolvedTextHeight / textHeight) : clearance;
|
|
3766
3913
|
const gaps = [];
|
|
3767
3914
|
for (const anchor of anchors) {
|
|
3768
3915
|
const labelPlacementKey = options.placementKeyPrefix ? `${options.placementKeyPrefix}:${anchor.side}` : void 0;
|
|
@@ -3772,21 +3919,21 @@ function pushBoundaryMarkers(out, maskVerts, text, anchors, options, amplifierPl
|
|
|
3772
3919
|
pushLabel(out, labelPoint, text, options.followPlacementRotation ? boxRotation : rotation, sizeProps, void 0, options.amplifierField, labelPlacementKey);
|
|
3773
3920
|
const contact = placement.position !== void 0 || options.rescanDefaultAnchors === true || anchor.contact === void 0 ? nearestPointOnPolyline(maskVerts, labelPoint) : anchor.contact;
|
|
3774
3921
|
if (!contact) continue;
|
|
3775
|
-
if (placement.position && !labelBoxIntersectsSegment(labelPoint, ...contact.edge, textWidth,
|
|
3776
|
-
const tangentExtent = labelHalfExtent(contact.along, textWidth,
|
|
3922
|
+
if (placement.position && !labelBoxIntersectsSegment(labelPoint, ...contact.edge, textWidth, resolvedTextHeight, boxRotation)) continue;
|
|
3923
|
+
const tangentExtent = labelHalfExtent(contact.along, textWidth, resolvedTextHeight, boxRotation) + resolvedClearance;
|
|
3777
3924
|
pushWrappedGap(gaps, contact.arc - tangentExtent, contact.arc + tangentExtent, contact.totalArc);
|
|
3778
3925
|
}
|
|
3779
3926
|
return gaps;
|
|
3780
3927
|
}
|
|
3781
3928
|
/** Emits movable Field N markers using the generic boundary-marker knockout path. */
|
|
3782
|
-
function pushHostileMarkers(out, maskVerts, text, anchors, options, amplifierPlacements) {
|
|
3929
|
+
function pushHostileMarkers(out, maskVerts, text, anchors, options, amplifierPlacements, context, labelOptions) {
|
|
3783
3930
|
return pushBoundaryMarkers(out, maskVerts, text, anchors, {
|
|
3784
3931
|
...options,
|
|
3785
3932
|
placementKeyPrefix: "label:N",
|
|
3786
3933
|
amplifierField: "N"
|
|
3787
|
-
}, amplifierPlacements);
|
|
3934
|
+
}, amplifierPlacements, context, labelOptions);
|
|
3788
3935
|
}
|
|
3789
|
-
function pushAreaLabels(out, ringVerts, texts, options = {}, amplifierPlacements, maskBoundary) {
|
|
3936
|
+
function pushAreaLabels(out, ringVerts, texts, options = {}, amplifierPlacements, maskBoundary, context) {
|
|
3790
3937
|
if (ringVerts.length < 2) return [];
|
|
3791
3938
|
const sizeProps = labelSizeProps(options);
|
|
3792
3939
|
const padding = Math.max(0, options.labelPadding ?? 0);
|
|
@@ -3829,7 +3976,7 @@ function pushAreaLabels(out, ringVerts, texts, options = {}, amplifierPlacements
|
|
|
3829
3976
|
clearance: (ENY_CLEARANCE_RATIO + padding) * textHeight + Math.max(0, maskBoundary?.extraClearanceMeters ?? 0),
|
|
3830
3977
|
sizeProps,
|
|
3831
3978
|
rescanDefaultAnchors: maskRingVerts !== ringVerts
|
|
3832
|
-
}, amplifierPlacements);
|
|
3979
|
+
}, amplifierPlacements, context, options);
|
|
3833
3980
|
}
|
|
3834
3981
|
/**
|
|
3835
3982
|
* Boundary + fill features for a pattern-filled area (e.g. Limited Access Area,
|
|
@@ -3908,11 +4055,11 @@ function boundaryFeature(verts, gaps) {
|
|
|
3908
4055
|
* echelon-masked boundary, ADR-0023) instead of a closed `Polygon` — the
|
|
3909
4056
|
* common (no `N`) case is unaffected.
|
|
3910
4057
|
*/
|
|
3911
|
-
function labeledAreaFeatures(positions, options, texts, amplifierPlacements) {
|
|
4058
|
+
function labeledAreaFeatures(positions, options, texts, amplifierPlacements, context) {
|
|
3912
4059
|
const { smooth = false, smoothResolution = DEFAULT_SMOOTH_RESOLUTION$9, ...labelOptions } = options;
|
|
3913
4060
|
const verts = buildClosedRing(positions, smooth, smoothResolution, DEFAULT_SMOOTH_RESOLUTION$9);
|
|
3914
4061
|
const labelFeatures = [];
|
|
3915
|
-
const features = [boundaryFeature(verts, pushAreaLabels(labelFeatures, verts, texts, labelOptions, amplifierPlacements))];
|
|
4062
|
+
const features = [boundaryFeature(verts, pushAreaLabels(labelFeatures, verts, texts, labelOptions, amplifierPlacements, void 0, context))];
|
|
3916
4063
|
features.push(...labelFeatures);
|
|
3917
4064
|
return {
|
|
3918
4065
|
type: "FeatureCollection",
|
|
@@ -3956,7 +4103,7 @@ const AREA_METADATA = {
|
|
|
3956
4103
|
* marker, per {@link labeledAreaFeatures}.
|
|
3957
4104
|
*/
|
|
3958
4105
|
function createArea(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
3959
|
-
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, ""), context.amplifierPlacements);
|
|
4106
|
+
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, ""), context.amplifierPlacements, context);
|
|
3960
4107
|
}
|
|
3961
4108
|
const AREA = defineControlMeasure({
|
|
3962
4109
|
metadata: AREA_METADATA,
|
|
@@ -3975,23 +4122,6 @@ const AREA = defineControlMeasure({
|
|
|
3975
4122
|
}
|
|
3976
4123
|
});
|
|
3977
4124
|
//#endregion
|
|
3978
|
-
//#region src/style.ts
|
|
3979
|
-
/**
|
|
3980
|
-
* Per-feature style hint pinning a filled part to a solid interior. Attach to
|
|
3981
|
-
* a generator's intrinsic doctrinal accents — arrowheads, teeth, echelon
|
|
3982
|
-
* glyphs, barbs, blades — so a patterned (`hatch`, `dots`, …) fill set at the graphicsStyle
|
|
3983
|
-
* or measure layer never bleeds into a small silhouette and ruins its
|
|
3984
|
-
* legibility. The renderer only reads style hints, so one shared instance is
|
|
3985
|
-
* safe to reuse across every accent feature.
|
|
3986
|
-
*/
|
|
3987
|
-
const SOLID_ACCENT_FILL = { fillPattern: "solid" };
|
|
3988
|
-
/**
|
|
3989
|
-
* Ultimate fallback for the symbol color when no `color` or per-channel
|
|
3990
|
-
* override is supplied at any layer. Keeps a zero-config render monocolor
|
|
3991
|
-
* black and guarantees filled parts still render filled. See ADR-0011.
|
|
3992
|
-
*/
|
|
3993
|
-
const DEFAULT_SYMBOL_COLOR = "#000000";
|
|
3994
|
-
//#endregion
|
|
3995
4125
|
//#region src/generators/cm15-maneuver-areas/area-defense.ts
|
|
3996
4126
|
const DEFAULT_AREA_DEFENSE_OPTIONS = {
|
|
3997
4127
|
arrowOpeningAngle: 30,
|
|
@@ -4078,7 +4208,7 @@ const AREA_DEFENSE_METADATA = {
|
|
|
4078
4208
|
function pointOnCircle(center, radius, angle) {
|
|
4079
4209
|
return vecAdd(center, [radius * Math.cos(angle), radius * Math.sin(angle)]);
|
|
4080
4210
|
}
|
|
4081
|
-
function sampleArc(center, radius, start, end) {
|
|
4211
|
+
function sampleArc$1(center, radius, start, end) {
|
|
4082
4212
|
const span = end - start;
|
|
4083
4213
|
const segments = Math.max(8, Math.round(48 * Math.abs(span) / Math.PI));
|
|
4084
4214
|
return Array.from({ length: segments + 1 }, (_, index) => pointOnCircle(center, radius, start + span * index / segments));
|
|
@@ -4112,8 +4242,8 @@ function createAreaDefense(coordinates, options = {}) {
|
|
|
4112
4242
|
const upperEnd = upperTipAngle;
|
|
4113
4243
|
const lowerStart = labelCenter - labelHalf;
|
|
4114
4244
|
const lowerEnd = upperTipAngle + arrowHalf * 2 - Math.PI * 2;
|
|
4115
|
-
const upperArc = sampleArc(center, radius, upperStart, upperEnd);
|
|
4116
|
-
const lowerArc = sampleArc(center, radius, lowerStart, lowerEnd);
|
|
4245
|
+
const upperArc = sampleArc$1(center, radius, upperStart, upperEnd);
|
|
4246
|
+
const lowerArc = sampleArc$1(center, radius, lowerStart, lowerEnd);
|
|
4117
4247
|
const arrowLength = radius * Math.max(0, resolved.arrowheadLengthRatio);
|
|
4118
4248
|
const upperDirection = [-Math.sin(upperEnd), Math.cos(upperEnd)];
|
|
4119
4249
|
const lowerDirection = [Math.sin(lowerEnd), -Math.cos(lowerEnd)];
|
|
@@ -4236,7 +4366,7 @@ const AREA_OF_OPERATIONS_METADATA = {
|
|
|
4236
4366
|
* `H`/`W`/`W1`/`N` amplifier rows, per {@link labeledAreaFeatures}.
|
|
4237
4367
|
*/
|
|
4238
4368
|
function createAreaOfOperations(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
4239
|
-
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "AO"), context.amplifierPlacements);
|
|
4369
|
+
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "AO"), context.amplifierPlacements, context);
|
|
4240
4370
|
}
|
|
4241
4371
|
const AREA_OF_OPERATIONS = defineControlMeasure({
|
|
4242
4372
|
metadata: AREA_OF_OPERATIONS_METADATA,
|
|
@@ -4295,7 +4425,7 @@ const NAMED_AREA_OF_INTEREST_METADATA = {
|
|
|
4295
4425
|
* an `N` (Field N) ENY marker, per {@link labeledAreaFeatures}.
|
|
4296
4426
|
*/
|
|
4297
4427
|
function createNamedAreaOfInterest(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
4298
|
-
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "NAI"), context.amplifierPlacements);
|
|
4428
|
+
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "NAI"), context.amplifierPlacements, context);
|
|
4299
4429
|
}
|
|
4300
4430
|
const NAMED_AREA_OF_INTEREST = defineControlMeasure({
|
|
4301
4431
|
metadata: NAMED_AREA_OF_INTEREST_METADATA,
|
|
@@ -4354,7 +4484,7 @@ const TARGET_AREA_OF_INTEREST_METADATA = {
|
|
|
4354
4484
|
* an `N` (Field N) ENY marker, per {@link labeledAreaFeatures}.
|
|
4355
4485
|
*/
|
|
4356
4486
|
function createTargetAreaOfInterest(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
4357
|
-
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "TAI"), context.amplifierPlacements);
|
|
4487
|
+
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "TAI"), context.amplifierPlacements, context);
|
|
4358
4488
|
}
|
|
4359
4489
|
const TARGET_AREA_OF_INTEREST = defineControlMeasure({
|
|
4360
4490
|
metadata: TARGET_AREA_OF_INTEREST_METADATA,
|
|
@@ -4449,7 +4579,7 @@ function createAirfieldZone(positions, options = {}, textAmplifiers = {}, contex
|
|
|
4449
4579
|
const { smooth = DEFAULT_AIRFIELD_ZONE_OPTIONS.smooth, smoothResolution = DEFAULT_AIRFIELD_ZONE_OPTIONS.smoothResolution, glyphSizeRatio = DEFAULT_AIRFIELD_ZONE_OPTIONS.glyphSizeRatio, ...labelOptions } = options;
|
|
4450
4580
|
const verts = buildClosedRing(positions, smooth, smoothResolution, DEFAULT_SMOOTH_RESOLUTION$8);
|
|
4451
4581
|
const enyLabels = [];
|
|
4452
|
-
const features = [boundaryFeature(verts, textAmplifiers.N ? pushAreaLabels(enyLabels, verts, { hostile: textAmplifiers.N }, labelOptions, context.amplifierPlacements) : [])];
|
|
4582
|
+
const features = [boundaryFeature(verts, textAmplifiers.N ? pushAreaLabels(enyLabels, verts, { hostile: textAmplifiers.N }, labelOptions, context.amplifierPlacements, void 0, context) : [])];
|
|
4453
4583
|
let minX = Infinity;
|
|
4454
4584
|
let maxX = -Infinity;
|
|
4455
4585
|
for (const [x] of verts) {
|
|
@@ -4525,13 +4655,13 @@ const DEFAULT_ECHELON_LABELED_AREA_OPTIONS = {
|
|
|
4525
4655
|
* before carving the boundary, so either (or both) opens it into a gapped
|
|
4526
4656
|
* `MultiLineString`; with neither, the boundary is a closed `Polygon`.
|
|
4527
4657
|
*/
|
|
4528
|
-
function echelonLabeledAreaFeatures(positions, options, texts, amplifierPlacements) {
|
|
4658
|
+
function echelonLabeledAreaFeatures(positions, options, texts, amplifierPlacements, context) {
|
|
4529
4659
|
const { echelon = DEFAULT_ECHELON_LABELED_AREA_OPTIONS.echelon, echelonSize = DEFAULT_ECHELON_LABELED_AREA_OPTIONS.echelonSize, echelonSizePixels, echelonPadding = DEFAULT_ECHELON_LABELED_AREA_OPTIONS.echelonPadding, echelonPosition = DEFAULT_ECHELON_LABELED_AREA_OPTIONS.echelonPosition, echelonAnchor, metersPerPixel, smooth = DEFAULT_ECHELON_LABELED_AREA_OPTIONS.smooth, smoothResolution = DEFAULT_ECHELON_LABELED_AREA_OPTIONS.smoothResolution, ...labelOptions } = options;
|
|
4530
4660
|
const h = resolveEchelonHeight(echelonSize, echelonSizePixels, metersPerPixel);
|
|
4531
4661
|
const verts = buildClosedRing(positions, smooth, smoothResolution, DEFAULT_SMOOTH_RESOLUTION$7);
|
|
4532
4662
|
const { strokes, fills, gaps: echelonGaps } = placeEchelon(buildAreaPerimeter(verts), h, echelon, echelonPadding, echelonPosition, echelonAnchor);
|
|
4533
4663
|
const labelFeatures = [];
|
|
4534
|
-
const enyGaps = pushAreaLabels(labelFeatures, verts, texts, labelOptions, amplifierPlacements);
|
|
4664
|
+
const enyGaps = pushAreaLabels(labelFeatures, verts, texts, labelOptions, amplifierPlacements, void 0, context);
|
|
4535
4665
|
const features = [boundaryFeature(verts, [...echelonGaps, ...enyGaps])];
|
|
4536
4666
|
if (strokes.length > 0) features.push({
|
|
4537
4667
|
type: "Feature",
|
|
@@ -4607,7 +4737,7 @@ const BASE_CAMP_METADATA = {
|
|
|
4607
4737
|
* boundary, per {@link echelonLabeledAreaFeatures}.
|
|
4608
4738
|
*/
|
|
4609
4739
|
function createBaseCamp(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
4610
|
-
return echelonLabeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "BC"), context.amplifierPlacements);
|
|
4740
|
+
return echelonLabeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "BC"), context.amplifierPlacements, context);
|
|
4611
4741
|
}
|
|
4612
4742
|
const BASE_CAMP = defineControlMeasure({
|
|
4613
4743
|
metadata: BASE_CAMP_METADATA,
|
|
@@ -4665,7 +4795,7 @@ const GUERRILLA_BASE_METADATA = {
|
|
|
4665
4795
|
* straddling the boundary, per {@link echelonLabeledAreaFeatures}.
|
|
4666
4796
|
*/
|
|
4667
4797
|
function createGuerrillaBase(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
4668
|
-
return echelonLabeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "GB"), context.amplifierPlacements);
|
|
4798
|
+
return echelonLabeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "GB"), context.amplifierPlacements, context);
|
|
4669
4799
|
}
|
|
4670
4800
|
const GUERRILLA_BASE = defineControlMeasure({
|
|
4671
4801
|
metadata: GUERRILLA_BASE_METADATA,
|
|
@@ -4727,7 +4857,7 @@ const GENERIC_C2_AREA_METADATA = {
|
|
|
4727
4857
|
* `H`/`W`/`W1`/`N` amplifier rows — no prefix, per {@link labeledAreaFeatures}.
|
|
4728
4858
|
*/
|
|
4729
4859
|
function createGenericC2Area(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
4730
|
-
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers), context.amplifierPlacements);
|
|
4860
|
+
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers), context.amplifierPlacements, context);
|
|
4731
4861
|
}
|
|
4732
4862
|
const GENERIC_C2_AREA = defineControlMeasure({
|
|
4733
4863
|
metadata: GENERIC_C2_AREA_METADATA,
|
|
@@ -4782,7 +4912,7 @@ const ASSEMBLY_AREA_METADATA = {
|
|
|
4782
4912
|
* `H`/`W`/`W1`/`N` amplifier rows, per {@link labeledAreaFeatures}.
|
|
4783
4913
|
*/
|
|
4784
4914
|
function createAssemblyArea(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
4785
|
-
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "AA"), context.amplifierPlacements);
|
|
4915
|
+
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "AA"), context.amplifierPlacements, context);
|
|
4786
4916
|
}
|
|
4787
4917
|
const ASSEMBLY_AREA = defineControlMeasure({
|
|
4788
4918
|
metadata: ASSEMBLY_AREA_METADATA,
|
|
@@ -4974,13 +5104,13 @@ function ditchPaths(projectedPoints, options) {
|
|
|
4974
5104
|
function buildAntitankDitchTriangles(basePath, options) {
|
|
4975
5105
|
return generateAntitankDitchTriangles(basePath, calculateAntitankDitchHeight(options), options.toothWidthRatio ?? DEFAULT_TOOTH_WIDTH_RATIO$1);
|
|
4976
5106
|
}
|
|
4977
|
-
function createAntitankDitchUnderConstruction(positions, options = {}, textAmplifiers = {}) {
|
|
5107
|
+
function createAntitankDitchUnderConstruction(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
4978
5108
|
const { smoothedPath, basePath } = ditchPaths(positions.map((position) => project(position[0], position[1])), options);
|
|
4979
5109
|
const triangles = buildAntitankDitchTriangles(basePath, options);
|
|
4980
5110
|
const baseLine = options.smooth ? basePath.map((point) => unproject(point[0], point[1])) : positions.map((position) => [position[0], position[1]]);
|
|
4981
5111
|
const triangleLines = triangles.map((triangle) => triangle.map((point) => unproject(point[0], point[1])));
|
|
4982
5112
|
const labels = [];
|
|
4983
|
-
pushHostileEndLabels(labels, smoothedPath, textAmplifiers.N, options);
|
|
5113
|
+
pushHostileEndLabels(labels, smoothedPath, textAmplifiers.N, options, "", context);
|
|
4984
5114
|
return {
|
|
4985
5115
|
type: "FeatureCollection",
|
|
4986
5116
|
features: [{
|
|
@@ -4993,13 +5123,13 @@ function createAntitankDitchUnderConstruction(positions, options = {}, textAmpli
|
|
|
4993
5123
|
}, ...labels]
|
|
4994
5124
|
};
|
|
4995
5125
|
}
|
|
4996
|
-
function createAntitankDitchCompleted(positions, options = {}, textAmplifiers = {}) {
|
|
5126
|
+
function createAntitankDitchCompleted(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
4997
5127
|
const { smoothedPath, basePath } = ditchPaths(positions.map((position) => project(position[0], position[1])), options);
|
|
4998
5128
|
const polygons = buildAntitankDitchTriangles(basePath, options).map((triangle) => {
|
|
4999
5129
|
return [[...triangle, triangle[0]].map((point) => unproject(point[0], point[1]))];
|
|
5000
5130
|
});
|
|
5001
5131
|
const labels = [];
|
|
5002
|
-
pushHostileEndLabels(labels, smoothedPath, textAmplifiers.N, options);
|
|
5132
|
+
pushHostileEndLabels(labels, smoothedPath, textAmplifiers.N, options, "", context);
|
|
5003
5133
|
return {
|
|
5004
5134
|
type: "FeatureCollection",
|
|
5005
5135
|
features: [{
|
|
@@ -5089,7 +5219,7 @@ function generateAntitankWallPoints(projectedPoints, toothHeight, toothWidthRati
|
|
|
5089
5219
|
}
|
|
5090
5220
|
return wallPoints;
|
|
5091
5221
|
}
|
|
5092
|
-
function createAntitankWall(positions, options = {}, textAmplifiers = {}) {
|
|
5222
|
+
function createAntitankWall(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
5093
5223
|
const toothHeight = calculateAntitankDitchHeight(options);
|
|
5094
5224
|
const toothWidthRatio = options.toothWidthRatio ?? DEFAULT_TOOTH_WIDTH_RATIO;
|
|
5095
5225
|
const toothSpacingRatio = options.toothSpacingRatio ?? DEFAULT_TOOTH_SPACING_RATIO;
|
|
@@ -5097,7 +5227,7 @@ function createAntitankWall(positions, options = {}, textAmplifiers = {}) {
|
|
|
5097
5227
|
const smoothedPath = smoothLineVerts(projectedPoints, options, 16);
|
|
5098
5228
|
const wallLine = generateAntitankWallPoints(options.smooth && projectedPoints.length >= 3 ? evenlySpacePath(smoothedPath, Math.max(EPSILON, toothWidthRatio) * toothHeight) : smoothedPath, toothHeight, toothWidthRatio, toothSpacingRatio).map((point) => unproject(point[0], point[1]));
|
|
5099
5229
|
const labels = [];
|
|
5100
|
-
pushHostileEndLabels(labels, smoothedPath, textAmplifiers.N, options);
|
|
5230
|
+
pushHostileEndLabels(labels, smoothedPath, textAmplifiers.N, options, "", context);
|
|
5101
5231
|
return {
|
|
5102
5232
|
type: "FeatureCollection",
|
|
5103
5233
|
features: [{
|
|
@@ -5544,7 +5674,7 @@ function createBattlePosition(positions, options = {}, textAmplifiers = {}, cont
|
|
|
5544
5674
|
const enyGaps = pushAreaLabels(labelFeatures, verts, {
|
|
5545
5675
|
name: textAmplifiers.T,
|
|
5546
5676
|
hostile: textAmplifiers.N
|
|
5547
|
-
}, options, context.amplifierPlacements);
|
|
5677
|
+
}, options, context.amplifierPlacements, void 0, context);
|
|
5548
5678
|
const boundaryCoords = buildGappedLine(verts, [...gaps, ...enyGaps]);
|
|
5549
5679
|
const features = [];
|
|
5550
5680
|
if (boundaryCoords.length > 0) features.push({
|
|
@@ -6142,7 +6272,7 @@ const BOUNDARY_METADATA = {
|
|
|
6142
6272
|
* @param textAmplifiers - Normalized text amplifiers (ADR-0027): `T`/`AS` label
|
|
6143
6273
|
* unit 1 (left-of-travel side), `T1`/`AS1` label unit 2 (right-of-travel side).
|
|
6144
6274
|
*/
|
|
6145
|
-
function createBoundary(positions, options = {}, textAmplifiers = {}) {
|
|
6275
|
+
function createBoundary(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
6146
6276
|
const { echelon = DEFAULT_BOUNDARY_OPTIONS.echelon, echelonSize = DEFAULT_BOUNDARY_OPTIONS.echelonSize, echelonSizePixels, echelonPadding = DEFAULT_BOUNDARY_OPTIONS.echelonPadding, metersPerPixel, labelRepetitions = DEFAULT_BOUNDARY_OPTIONS.labelRepetitions, labelSpacing = DEFAULT_BOUNDARY_OPTIONS.labelSpacing, labelPosition = DEFAULT_BOUNDARY_OPTIONS.labelPosition, labelPadding = DEFAULT_BOUNDARY_OPTIONS.labelPadding, labelOrientation = DEFAULT_BOUNDARY_OPTIONS.labelOrientation, smooth = DEFAULT_BOUNDARY_OPTIONS.smooth, smoothResolution = DEFAULT_BOUNDARY_OPTIONS.smoothResolution } = options;
|
|
6147
6277
|
const unit1Designator = textAmplifiers.T ?? "";
|
|
6148
6278
|
const unit1Country = textAmplifiers.AS ?? "";
|
|
@@ -6226,7 +6356,7 @@ function createBoundary(positions, options = {}, textAmplifiers = {}) {
|
|
|
6226
6356
|
labelSize: h,
|
|
6227
6357
|
labelPadding
|
|
6228
6358
|
};
|
|
6229
|
-
pushHostileEndLabels(labelFeatures, verts, textAmplifiers.N, hostileLabelOptions);
|
|
6359
|
+
pushHostileEndLabels(labelFeatures, verts, textAmplifiers.N, hostileLabelOptions, "", context);
|
|
6230
6360
|
const boundaryCoords = buildGappedLine(verts, gaps);
|
|
6231
6361
|
const features = [];
|
|
6232
6362
|
if (boundaryCoords.length > 0) features.push({
|
|
@@ -6342,12 +6472,12 @@ const LIGHT_LINE_METADATA = {
|
|
|
6342
6472
|
* Creates a Light line control measure: a polyline through `positions`
|
|
6343
6473
|
* labeled "LL" above the line near each end.
|
|
6344
6474
|
*/
|
|
6345
|
-
function createLightLine(positions, options = {}, textAmplifiers = {}) {
|
|
6475
|
+
function createLightLine(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
6346
6476
|
return fixedLabelLineFeatures(positions, {
|
|
6347
6477
|
part: "light-line",
|
|
6348
6478
|
placement: "above",
|
|
6349
6479
|
...options
|
|
6350
|
-
}, "LL", textAmplifiers.N);
|
|
6480
|
+
}, "LL", textAmplifiers.N, context);
|
|
6351
6481
|
}
|
|
6352
6482
|
const LIGHT_LINE = defineControlMeasure({
|
|
6353
6483
|
metadata: LIGHT_LINE_METADATA,
|
|
@@ -6453,12 +6583,12 @@ const CENTER_LABEL_OFFSET_RATIO = .7;
|
|
|
6453
6583
|
* above the line, T1/AS1 below). No center group is emitted when all four
|
|
6454
6584
|
* amplifiers are empty.
|
|
6455
6585
|
*/
|
|
6456
|
-
function createEngineerWorkLine(positions, options = {}, textAmplifiers = {}) {
|
|
6586
|
+
function createEngineerWorkLine(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
6457
6587
|
const features = [...fixedLabelLineFeatures(positions, {
|
|
6458
6588
|
part: "engineer-work-line",
|
|
6459
6589
|
placement: "above",
|
|
6460
6590
|
...options
|
|
6461
|
-
}, "EWL", textAmplifiers.N).features];
|
|
6591
|
+
}, "EWL", textAmplifiers.N, context).features];
|
|
6462
6592
|
const { segments, totalLength } = polylineSegments(smoothLineVerts(positions.map((p) => project(p[0], p[1])), options, 12));
|
|
6463
6593
|
const frame = pointAlongPolyline(segments, totalLength, .5);
|
|
6464
6594
|
if (frame) {
|
|
@@ -6595,7 +6725,7 @@ const GENERIC_C2_LINE_METADATA = {
|
|
|
6595
6725
|
* (inset from the tip, anchored toward the line's interior), one at
|
|
6596
6726
|
* `base + perp·offset` and the other at `base − perp·offset`.
|
|
6597
6727
|
*/
|
|
6598
|
-
function createGenericC2Line(positions, options = {}, textAmplifiers = {}) {
|
|
6728
|
+
function createGenericC2Line(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
6599
6729
|
const { phaseLineName, includePrefix = true, smooth, smoothResolution, ...sizeOptions } = options;
|
|
6600
6730
|
const verts = smoothLineVerts(positions.map((p) => project(p[0], p[1])), {
|
|
6601
6731
|
smooth,
|
|
@@ -6627,7 +6757,7 @@ function createGenericC2Line(positions, options = {}, textAmplifiers = {}) {
|
|
|
6627
6757
|
}
|
|
6628
6758
|
const nameText = phaseLineLabelText(trimmedPhaseLineName, includePrefix);
|
|
6629
6759
|
if (nameText) pushEndLabels(labelFeatures, verts, nameText, sizeOptions);
|
|
6630
|
-
pushHostileEndLabels(labelFeatures, verts, textAmplifiers.N, sizeOptions, nameText);
|
|
6760
|
+
pushHostileEndLabels(labelFeatures, verts, textAmplifiers.N, sizeOptions, nameText, context);
|
|
6631
6761
|
features.push(...labelFeatures);
|
|
6632
6762
|
}
|
|
6633
6763
|
return {
|
|
@@ -7465,6 +7595,20 @@ function normalizeSmoothResolution(value) {
|
|
|
7465
7595
|
if (!Number.isFinite(value)) return DEFAULT_SMOOTH_RESOLUTION$3;
|
|
7466
7596
|
return Math.min(MAX_SMOOTH_RESOLUTION, Math.max(MIN_SMOOTH_RESOLUTION, Math.round(value)));
|
|
7467
7597
|
}
|
|
7598
|
+
function normalizeArcResolution(value) {
|
|
7599
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return 64;
|
|
7600
|
+
return Math.min(360, Math.max(4, Math.round(value)));
|
|
7601
|
+
}
|
|
7602
|
+
const ARC_RESOLUTION_PARAMS = [{
|
|
7603
|
+
key: "resolution",
|
|
7604
|
+
presentationTier: "advanced",
|
|
7605
|
+
label: "Resolution",
|
|
7606
|
+
description: "Number of segments in a full 360° sweep.",
|
|
7607
|
+
type: "number",
|
|
7608
|
+
min: 4,
|
|
7609
|
+
max: 360,
|
|
7610
|
+
step: 1
|
|
7611
|
+
}];
|
|
7468
7612
|
//#endregion
|
|
7469
7613
|
//#region src/generators/cm99-generic-graphics/params.ts
|
|
7470
7614
|
const SMOOTH_PATH_PARAMS = [{
|
|
@@ -7491,7 +7635,6 @@ const FILLED_AREA_PARAMS = [{
|
|
|
7491
7635
|
}];
|
|
7492
7636
|
//#endregion
|
|
7493
7637
|
//#region src/generators/cm99-generic-graphics/circle.ts
|
|
7494
|
-
const CIRCLE_SEGMENTS$1 = 64;
|
|
7495
7638
|
const MAX_SEGMENT_ANGLE = 6;
|
|
7496
7639
|
const MAX_SUBDIVISION_DEPTH = 5;
|
|
7497
7640
|
/**
|
|
@@ -7517,7 +7660,10 @@ const densifySegment = (center, radius, bearingA, posA, bearingB, posB, depth, o
|
|
|
7517
7660
|
densifySegment(center, radius, midBearing, midPos, bearingB, posB, depth + 1, out);
|
|
7518
7661
|
} else out.push(posA);
|
|
7519
7662
|
};
|
|
7520
|
-
const DEFAULT_GENERIC_CIRCLE_OPTIONS = {
|
|
7663
|
+
const DEFAULT_GENERIC_CIRCLE_OPTIONS = {
|
|
7664
|
+
filled: false,
|
|
7665
|
+
resolution: 64
|
|
7666
|
+
};
|
|
7521
7667
|
const GENERIC_CIRCLE_METADATA = {
|
|
7522
7668
|
id: "circle",
|
|
7523
7669
|
name: "Circle",
|
|
@@ -7536,7 +7682,7 @@ const GENERIC_CIRCLE_METADATA = {
|
|
|
7536
7682
|
text: false
|
|
7537
7683
|
},
|
|
7538
7684
|
drawRule: "Area15",
|
|
7539
|
-
params: FILLED_AREA_PARAMS
|
|
7685
|
+
params: [...FILLED_AREA_PARAMS, ...ARC_RESOLUTION_PARAMS]
|
|
7540
7686
|
};
|
|
7541
7687
|
function createGenericCircle(coordinates, options = {}) {
|
|
7542
7688
|
const center = coordinates[0];
|
|
@@ -7546,12 +7692,13 @@ function createGenericCircle(coordinates, options = {}) {
|
|
|
7546
7692
|
type: "FeatureCollection",
|
|
7547
7693
|
features: []
|
|
7548
7694
|
};
|
|
7695
|
+
const resolution = normalizeArcResolution(options.resolution);
|
|
7549
7696
|
const startBearing = sphericalBearing(center, radiusPoint);
|
|
7550
7697
|
const ring = [];
|
|
7551
7698
|
let previousBearing = startBearing;
|
|
7552
7699
|
let previousPoint = destinationPoint(center, radius, previousBearing);
|
|
7553
|
-
for (let index = 1; index <=
|
|
7554
|
-
const bearing = startBearing - index /
|
|
7700
|
+
for (let index = 1; index <= resolution; index++) {
|
|
7701
|
+
const bearing = startBearing - index / resolution * 2 * Math.PI;
|
|
7555
7702
|
const point = destinationPoint(center, radius, bearing);
|
|
7556
7703
|
densifySegment(center, radius, previousBearing, previousPoint, bearing, point, 0, ring);
|
|
7557
7704
|
previousBearing = bearing;
|
|
@@ -7800,6 +7947,117 @@ const GENERIC_RECTANGLE = defineControlMeasure({
|
|
|
7800
7947
|
] }
|
|
7801
7948
|
});
|
|
7802
7949
|
//#endregion
|
|
7950
|
+
//#region src/internal/annular-sector.ts
|
|
7951
|
+
/**
|
|
7952
|
+
* Clockwise sweep in degrees from `leftAzimuth` to `rightAzimuth`. A non-zero
|
|
7953
|
+
* raw difference that normalizes to 0 means a full 360° fan.
|
|
7954
|
+
*
|
|
7955
|
+
* `collapseToleranceDegrees` (default 0, i.e. off) treats a near-zero or
|
|
7956
|
+
* near-360 sweep as a collapsed sector. Callers whose handle coordinates are
|
|
7957
|
+
* rounded need this: two handles placed on the same ray can recover bearings a
|
|
7958
|
+
* few thousandths of a degree apart, which would otherwise read as an
|
|
7959
|
+
* almost-360° fan.
|
|
7960
|
+
*/
|
|
7961
|
+
function clockwiseSweepDegrees(leftAzimuth, rightAzimuth, collapseToleranceDegrees = 0) {
|
|
7962
|
+
const rawSweep = rightAzimuth - leftAzimuth;
|
|
7963
|
+
const sweep = normalizeDegrees(rawSweep);
|
|
7964
|
+
if (collapseToleranceDegrees > 0 && Math.min(sweep, 360 - sweep) < collapseToleranceDegrees) return 0;
|
|
7965
|
+
return sweep === 0 && rawSweep !== 0 ? 360 : sweep;
|
|
7966
|
+
}
|
|
7967
|
+
/** Samples an arc of `sweepDegrees` at `radius`, starting from `leftAzimuth`. */
|
|
7968
|
+
function sampleArc(anchor, radius, leftAzimuth, sweepDegrees, resolution) {
|
|
7969
|
+
const segmentCount = Math.max(1, Math.ceil(sweepDegrees / 360 * resolution));
|
|
7970
|
+
return Array.from({ length: segmentCount + 1 }, (_, index) => {
|
|
7971
|
+
return destinationPoint(anchor, radius, (leftAzimuth + index / segmentCount * sweepDegrees) * Math.PI / 180);
|
|
7972
|
+
});
|
|
7973
|
+
}
|
|
7974
|
+
/** Closed ring tracing the outer arc, back along the inner arc, and closed. */
|
|
7975
|
+
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();
|
|
7978
|
+
return [
|
|
7979
|
+
...outerArc,
|
|
7980
|
+
...innerArc,
|
|
7981
|
+
outerArc[0]
|
|
7982
|
+
];
|
|
7983
|
+
}
|
|
7984
|
+
//#endregion
|
|
7985
|
+
//#region src/generators/cm99-generic-graphics/sector.ts
|
|
7986
|
+
const DEFAULT_GENERIC_SECTOR_OPTIONS = {
|
|
7987
|
+
filled: false,
|
|
7988
|
+
resolution: 64
|
|
7989
|
+
};
|
|
7990
|
+
const GENERIC_SECTOR_METADATA = {
|
|
7991
|
+
id: "sector",
|
|
7992
|
+
name: "Sector",
|
|
7993
|
+
description: "A non-doctrinal range-fan sector defined by an anchor and two radial edge points.",
|
|
7994
|
+
entity: "Generic Graphics",
|
|
7995
|
+
entityType: "Basic Shape",
|
|
7996
|
+
entitySubtype: "Sector",
|
|
7997
|
+
value: "990105",
|
|
7998
|
+
minCoordinates: 3,
|
|
7999
|
+
maxCoordinates: 3,
|
|
8000
|
+
geometry: "area",
|
|
8001
|
+
geometryTypes: ["Polygon"],
|
|
8002
|
+
paints: {
|
|
8003
|
+
stroke: true,
|
|
8004
|
+
fill: "user",
|
|
8005
|
+
text: false
|
|
8006
|
+
},
|
|
8007
|
+
drawRule: "Sector",
|
|
8008
|
+
params: [...FILLED_AREA_PARAMS, ...ARC_RESOLUTION_PARAMS]
|
|
8009
|
+
};
|
|
8010
|
+
function createGenericSector(coordinates, options = {}) {
|
|
8011
|
+
const anchor = coordinates[0];
|
|
8012
|
+
const innerLeftPoint = coordinates[1];
|
|
8013
|
+
const outerRightPoint = coordinates[2];
|
|
8014
|
+
if (!anchor || !innerLeftPoint || !outerRightPoint) return {
|
|
8015
|
+
type: "FeatureCollection",
|
|
8016
|
+
features: []
|
|
8017
|
+
};
|
|
8018
|
+
const firstRadius = haversineDistance(anchor, innerLeftPoint);
|
|
8019
|
+
const secondRadius = haversineDistance(anchor, outerRightPoint);
|
|
8020
|
+
const innerRadius = Math.min(firstRadius, secondRadius);
|
|
8021
|
+
const outerRadius = Math.max(firstRadius, secondRadius);
|
|
8022
|
+
const leftAzimuth = normalizeDegrees(sphericalBearing(anchor, innerLeftPoint) * 180 / Math.PI);
|
|
8023
|
+
const rightAzimuth = normalizeDegrees(sphericalBearing(anchor, outerRightPoint) * 180 / Math.PI);
|
|
8024
|
+
if (outerRadius - innerRadius < 1e-6 || innerRadius < 1e-6) return {
|
|
8025
|
+
type: "FeatureCollection",
|
|
8026
|
+
features: []
|
|
8027
|
+
};
|
|
8028
|
+
const sweepDegrees = clockwiseSweepDegrees(leftAzimuth, rightAzimuth);
|
|
8029
|
+
if (sweepDegrees === 0) return {
|
|
8030
|
+
type: "FeatureCollection",
|
|
8031
|
+
features: []
|
|
8032
|
+
};
|
|
8033
|
+
const ring = annularSectorRing(anchor, innerRadius, outerRadius, leftAzimuth, sweepDegrees, normalizeArcResolution(options.resolution));
|
|
8034
|
+
return {
|
|
8035
|
+
type: "FeatureCollection",
|
|
8036
|
+
features: [{
|
|
8037
|
+
type: "Feature",
|
|
8038
|
+
properties: {
|
|
8039
|
+
part: "sector",
|
|
8040
|
+
fill: options.filled ?? DEFAULT_GENERIC_SECTOR_OPTIONS.filled
|
|
8041
|
+
},
|
|
8042
|
+
geometry: {
|
|
8043
|
+
type: "Polygon",
|
|
8044
|
+
coordinates: [ring]
|
|
8045
|
+
}
|
|
8046
|
+
}]
|
|
8047
|
+
};
|
|
8048
|
+
}
|
|
8049
|
+
const GENERIC_SECTOR = defineControlMeasure({
|
|
8050
|
+
metadata: GENERIC_SECTOR_METADATA,
|
|
8051
|
+
generator: createGenericSector,
|
|
8052
|
+
defaultOptions: DEFAULT_GENERIC_SECTOR_OPTIONS,
|
|
8053
|
+
rule: sectorDrawRule,
|
|
8054
|
+
previewSample: { controlPoints: [
|
|
8055
|
+
[-.8, -.7],
|
|
8056
|
+
[-.5, .2],
|
|
8057
|
+
[.7, -.1]
|
|
8058
|
+
] }
|
|
8059
|
+
});
|
|
8060
|
+
//#endregion
|
|
7803
8061
|
//#region src/generators/cm99-generic-graphics/text.ts
|
|
7804
8062
|
const DEFAULT_GENERIC_TEXT_OPTIONS = {
|
|
7805
8063
|
text: "Text",
|
|
@@ -7975,6 +8233,114 @@ const GENERIC_TEXT = defineControlMeasure({
|
|
|
7975
8233
|
}
|
|
7976
8234
|
});
|
|
7977
8235
|
//#endregion
|
|
8236
|
+
//#region src/generators/cm20-maritime-control-areas/radar-search-doctrine.ts
|
|
8237
|
+
const DEFAULT_RADAR_SEARCH_DOCTRINE_OPTIONS = { resolution: 64 };
|
|
8238
|
+
/** Doctrinal RSD colors from the symbol specification. */
|
|
8239
|
+
const RADAR_SEARCH_DOCTRINE_STROKE_COLOR = "rgb(51, 136, 136)";
|
|
8240
|
+
const RADAR_SEARCH_DOCTRINE_FILL_COLOR = "rgba(51, 136, 136, 0.25)";
|
|
8241
|
+
const RADAR_SEARCH_DOCTRINE_METADATA = {
|
|
8242
|
+
id: "radar-search-doctrine",
|
|
8243
|
+
name: "Radar Search Doctrine",
|
|
8244
|
+
description: "A maritime radar search area defined by an axis and annular-sector limits.",
|
|
8245
|
+
entity: "Maritime Control Areas",
|
|
8246
|
+
entityType: "Radar Search Doctrine",
|
|
8247
|
+
value: "200700",
|
|
8248
|
+
minCoordinates: 3,
|
|
8249
|
+
maxCoordinates: 3,
|
|
8250
|
+
geometry: "area",
|
|
8251
|
+
geometryTypes: ["Polygon", "Point"],
|
|
8252
|
+
paints: {
|
|
8253
|
+
stroke: true,
|
|
8254
|
+
fill: "fixed",
|
|
8255
|
+
text: true
|
|
8256
|
+
},
|
|
8257
|
+
drawRule: "Point18",
|
|
8258
|
+
capturesLabelSize: true,
|
|
8259
|
+
params: ARC_RESOLUTION_PARAMS,
|
|
8260
|
+
textAmplifiers: [{
|
|
8261
|
+
key: "T",
|
|
8262
|
+
label: "Unique designation",
|
|
8263
|
+
description: "Field T — designation centered in the search area along its axis.",
|
|
8264
|
+
placeholder: "FF",
|
|
8265
|
+
maxLength: 20
|
|
8266
|
+
}]
|
|
8267
|
+
};
|
|
8268
|
+
/**
|
|
8269
|
+
* GeoJSON positions are rounded by `destinationPoint`, so two handles placed on
|
|
8270
|
+
* the same ray can recover bearings a few thousandths of a degree apart. Treat
|
|
8271
|
+
* that numerical noise as a collapsed sector, not an almost-360° fan.
|
|
8272
|
+
*/
|
|
8273
|
+
const SWEEP_COLLAPSE_TOLERANCE_DEGREES = .01;
|
|
8274
|
+
function createRadarSearchDoctrine(coordinates, options = {}, textAmplifiers = {}) {
|
|
8275
|
+
const anchor = coordinates[0];
|
|
8276
|
+
const startLimit = coordinates[1];
|
|
8277
|
+
const stopLimit = coordinates[2];
|
|
8278
|
+
const firstRange = haversineDistance(anchor, startLimit);
|
|
8279
|
+
const secondRange = haversineDistance(anchor, stopLimit);
|
|
8280
|
+
const startRange = Math.min(firstRange, secondRange);
|
|
8281
|
+
const stopRange = Math.max(firstRange, secondRange);
|
|
8282
|
+
const leftAzimuth = normalizeDegrees(sphericalBearing(anchor, startLimit) * 180 / Math.PI);
|
|
8283
|
+
const sweepDegrees = clockwiseSweepDegrees(leftAzimuth, normalizeDegrees(sphericalBearing(anchor, stopLimit) * 180 / Math.PI), SWEEP_COLLAPSE_TOLERANCE_DEGREES);
|
|
8284
|
+
if (stopRange - startRange < 1e-6 || sweepDegrees < 1e-6) return {
|
|
8285
|
+
type: "FeatureCollection",
|
|
8286
|
+
features: []
|
|
8287
|
+
};
|
|
8288
|
+
const axis = normalizeDegrees(leftAzimuth + sweepDegrees / 2) * Math.PI / 180;
|
|
8289
|
+
const ring = annularSectorRing(anchor, startRange, stopRange, leftAzimuth, sweepDegrees, normalizeArcResolution(options.resolution));
|
|
8290
|
+
const features = [{
|
|
8291
|
+
type: "Feature",
|
|
8292
|
+
properties: {
|
|
8293
|
+
part: "search-area",
|
|
8294
|
+
fill: true,
|
|
8295
|
+
style: {
|
|
8296
|
+
strokeColor: RADAR_SEARCH_DOCTRINE_STROKE_COLOR,
|
|
8297
|
+
fillColor: RADAR_SEARCH_DOCTRINE_FILL_COLOR,
|
|
8298
|
+
fillPattern: "solid"
|
|
8299
|
+
}
|
|
8300
|
+
},
|
|
8301
|
+
geometry: {
|
|
8302
|
+
type: "Polygon",
|
|
8303
|
+
coordinates: [ring]
|
|
8304
|
+
}
|
|
8305
|
+
}];
|
|
8306
|
+
if (textAmplifiers.T) {
|
|
8307
|
+
const labelAnchor = destinationPoint(anchor, (startRange + stopRange) / 2, axis);
|
|
8308
|
+
features.push({
|
|
8309
|
+
type: "Feature",
|
|
8310
|
+
properties: {
|
|
8311
|
+
part: "label",
|
|
8312
|
+
text: textAmplifiers.T,
|
|
8313
|
+
amplifierField: "T",
|
|
8314
|
+
labelPlacementKey: "T",
|
|
8315
|
+
...labelSizeProps(options),
|
|
8316
|
+
rotation: labelRotationAlong([Math.cos(axis), -Math.sin(axis)])
|
|
8317
|
+
},
|
|
8318
|
+
geometry: {
|
|
8319
|
+
type: "Point",
|
|
8320
|
+
coordinates: labelAnchor
|
|
8321
|
+
}
|
|
8322
|
+
});
|
|
8323
|
+
}
|
|
8324
|
+
return {
|
|
8325
|
+
type: "FeatureCollection",
|
|
8326
|
+
features
|
|
8327
|
+
};
|
|
8328
|
+
}
|
|
8329
|
+
const RADAR_SEARCH_DOCTRINE = defineControlMeasure({
|
|
8330
|
+
metadata: RADAR_SEARCH_DOCTRINE_METADATA,
|
|
8331
|
+
generator: createRadarSearchDoctrine,
|
|
8332
|
+
defaultOptions: DEFAULT_RADAR_SEARCH_DOCTRINE_OPTIONS,
|
|
8333
|
+
rule: dynamicPointDrawRule,
|
|
8334
|
+
previewSample: {
|
|
8335
|
+
controlPoints: [
|
|
8336
|
+
[0, -1],
|
|
8337
|
+
[-.55, -.15],
|
|
8338
|
+
[1, .45]
|
|
8339
|
+
],
|
|
8340
|
+
textAmplifiers: { T: "FF" }
|
|
8341
|
+
}
|
|
8342
|
+
});
|
|
8343
|
+
//#endregion
|
|
7978
8344
|
//#region src/generators/cm34-mission-tasks/clear.ts
|
|
7979
8345
|
/**
|
|
7980
8346
|
* Default options for the CLEAR symbol.
|
|
@@ -9546,7 +9912,7 @@ const DROP_ZONE_METADATA = {
|
|
|
9546
9912
|
* (Field N) ENY marker, per {@link labeledAreaFeatures}.
|
|
9547
9913
|
*/
|
|
9548
9914
|
function createDropZone(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
9549
|
-
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "DZ"), context.amplifierPlacements);
|
|
9915
|
+
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "DZ"), context.amplifierPlacements, context);
|
|
9550
9916
|
}
|
|
9551
9917
|
const DROP_ZONE = defineControlMeasure({
|
|
9552
9918
|
metadata: DROP_ZONE_METADATA,
|
|
@@ -9682,7 +10048,7 @@ function createEncirclement(positions, options = {}, textAmplifiers = {}, contex
|
|
|
9682
10048
|
const verts = buildClosedRing(positions, smooth, smoothResolution, DEFAULT_SMOOTH_RESOLUTION$2);
|
|
9683
10049
|
const perimeter = buildAreaPerimeter(verts);
|
|
9684
10050
|
const labelFeatures = [];
|
|
9685
|
-
const gaps = pushAreaLabels(labelFeatures, verts, { hostile: textAmplifiers.N }, options, context.amplifierPlacements);
|
|
10051
|
+
const gaps = pushAreaLabels(labelFeatures, verts, { hostile: textAmplifiers.N }, options, context.amplifierPlacements, void 0, context);
|
|
9686
10052
|
const radius = meanRadius(verts);
|
|
9687
10053
|
const barbLength = radius * Math.max(0, barbLengthRatio);
|
|
9688
10054
|
const barbs = buildBarbs(perimeter, radius * Math.max(0, barbSpacingRatio), barbLength, gaps);
|
|
@@ -9763,7 +10129,7 @@ const EXTRACTION_ZONE_METADATA = {
|
|
|
9763
10129
|
* an `N` (Field N) ENY marker, per {@link labeledAreaFeatures}.
|
|
9764
10130
|
*/
|
|
9765
10131
|
function createExtractionZone(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
9766
|
-
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "EZ"), context.amplifierPlacements);
|
|
10132
|
+
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "EZ"), context.amplifierPlacements, context);
|
|
9767
10133
|
}
|
|
9768
10134
|
const EXTRACTION_ZONE = defineControlMeasure({
|
|
9769
10135
|
metadata: EXTRACTION_ZONE_METADATA,
|
|
@@ -10435,7 +10801,7 @@ function processSegment(p1, p2, radius, arcSegments, isFirstSegment) {
|
|
|
10435
10801
|
* );
|
|
10436
10802
|
* ```
|
|
10437
10803
|
*/
|
|
10438
|
-
function createFLOT(positions, options = {}, textAmplifiers = {}) {
|
|
10804
|
+
function createFLOT(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
10439
10805
|
const { radius = DEFAULT_RADIUS, radiusPixels, metersPerPixel, arcSegments = DEFAULT_ARC_SEGMENTS, smooth = DEFAULT_FLOT_OPTIONS.smooth, smoothResolution = DEFAULT_FLOT_OPTIONS.smoothResolution } = options;
|
|
10440
10806
|
let effectiveRadius = radius;
|
|
10441
10807
|
if (radiusPixels !== void 0 && metersPerPixel !== void 0 && metersPerPixel > 0) effectiveRadius = radiusPixels * metersPerPixel;
|
|
@@ -10473,7 +10839,7 @@ function createFLOT(positions, options = {}, textAmplifiers = {}) {
|
|
|
10473
10839
|
}
|
|
10474
10840
|
};
|
|
10475
10841
|
const labels = [];
|
|
10476
|
-
pushHostileEndLabels(labels, smoothedPath, textAmplifiers.N, options);
|
|
10842
|
+
pushHostileEndLabels(labels, smoothedPath, textAmplifiers.N, options, "", context);
|
|
10477
10843
|
return {
|
|
10478
10844
|
type: "FeatureCollection",
|
|
10479
10845
|
features: [feature, ...labels]
|
|
@@ -10542,14 +10908,14 @@ const PHASE_LINE_METADATA = {
|
|
|
10542
10908
|
* Creates a Phase line control measure: a polyline through `positions`
|
|
10543
10909
|
* labeled at each end with `T` (optionally "PL"-prefixed).
|
|
10544
10910
|
*/
|
|
10545
|
-
function createPhaseLine(positions, options = {}, textAmplifiers = {}) {
|
|
10911
|
+
function createPhaseLine(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
10546
10912
|
const { includePrefix = true, ...sizeOptions } = options;
|
|
10547
10913
|
const designation = textAmplifiers.T ?? "";
|
|
10548
10914
|
const text = includePrefix ? designation.length > 0 ? `PL ${designation}` : "PL" : designation;
|
|
10549
10915
|
return fixedLabelLineFeatures(positions, {
|
|
10550
10916
|
part: "phase-line",
|
|
10551
10917
|
...sizeOptions
|
|
10552
|
-
}, text, textAmplifiers.N);
|
|
10918
|
+
}, text, textAmplifiers.N, context);
|
|
10553
10919
|
}
|
|
10554
10920
|
const PHASE_LINE = defineControlMeasure({
|
|
10555
10921
|
metadata: PHASE_LINE_METADATA,
|
|
@@ -10621,12 +10987,12 @@ const BRIDGEHEAD_LINE_METADATA = {
|
|
|
10621
10987
|
textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
|
|
10622
10988
|
};
|
|
10623
10989
|
/** Creates a Bridgehead line labeled "BL" above both ends. */
|
|
10624
|
-
function createBridgeheadLine(positions, options = {}, textAmplifiers = {}) {
|
|
10990
|
+
function createBridgeheadLine(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
10625
10991
|
return fixedLabelLineFeatures(positions, {
|
|
10626
10992
|
part: "bridgehead-line",
|
|
10627
10993
|
placement: "above",
|
|
10628
10994
|
...options
|
|
10629
|
-
}, "BL", textAmplifiers.N);
|
|
10995
|
+
}, "BL", textAmplifiers.N, context);
|
|
10630
10996
|
}
|
|
10631
10997
|
const BRIDGEHEAD_LINE = defineControlMeasure({
|
|
10632
10998
|
metadata: BRIDGEHEAD_LINE_METADATA,
|
|
@@ -10697,12 +11063,12 @@ const HOLDING_LINE_METADATA = {
|
|
|
10697
11063
|
textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
|
|
10698
11064
|
};
|
|
10699
11065
|
/** Creates a Holding line labeled "HL" above both ends. */
|
|
10700
|
-
function createHoldingLine(positions, options = {}, textAmplifiers = {}) {
|
|
11066
|
+
function createHoldingLine(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
10701
11067
|
return fixedLabelLineFeatures(positions, {
|
|
10702
11068
|
part: "holding-line",
|
|
10703
11069
|
placement: "above",
|
|
10704
11070
|
...options
|
|
10705
|
-
}, "HL", textAmplifiers.N);
|
|
11071
|
+
}, "HL", textAmplifiers.N, context);
|
|
10706
11072
|
}
|
|
10707
11073
|
const HOLDING_LINE = defineControlMeasure({
|
|
10708
11074
|
metadata: HOLDING_LINE_METADATA,
|
|
@@ -10773,12 +11139,12 @@ const RELEASE_LINE_METADATA = {
|
|
|
10773
11139
|
textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
|
|
10774
11140
|
};
|
|
10775
11141
|
/** Creates a Release line labeled "RL" above both ends. */
|
|
10776
|
-
function createReleaseLine(positions, options = {}, textAmplifiers = {}) {
|
|
11142
|
+
function createReleaseLine(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
10777
11143
|
return fixedLabelLineFeatures(positions, {
|
|
10778
11144
|
part: "release-line",
|
|
10779
11145
|
placement: "above",
|
|
10780
11146
|
...options
|
|
10781
|
-
}, "RL", textAmplifiers.N);
|
|
11147
|
+
}, "RL", textAmplifiers.N, context);
|
|
10782
11148
|
}
|
|
10783
11149
|
const RELEASE_LINE = defineControlMeasure({
|
|
10784
11150
|
metadata: RELEASE_LINE_METADATA,
|
|
@@ -10852,12 +11218,12 @@ const FORWARD_EDGE_OF_BATTLE_AREA_METADATA = {
|
|
|
10852
11218
|
* Creates a Forward edge of the battle area control measure: a polyline
|
|
10853
11219
|
* through `positions` labeled "FEBA" above the line near each end.
|
|
10854
11220
|
*/
|
|
10855
|
-
function createForwardEdgeOfBattleArea(positions, options = {}, textAmplifiers = {}) {
|
|
11221
|
+
function createForwardEdgeOfBattleArea(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
10856
11222
|
return fixedLabelLineFeatures(positions, {
|
|
10857
11223
|
part: "forward-edge-of-battle-area",
|
|
10858
11224
|
placement: "above",
|
|
10859
11225
|
...options
|
|
10860
|
-
}, "FEBA", textAmplifiers.N);
|
|
11226
|
+
}, "FEBA", textAmplifiers.N, context);
|
|
10861
11227
|
}
|
|
10862
11228
|
const FORWARD_EDGE_OF_BATTLE_AREA = defineControlMeasure({
|
|
10863
11229
|
metadata: FORWARD_EDGE_OF_BATTLE_AREA_METADATA,
|
|
@@ -10931,12 +11297,12 @@ const HANDOVER_LINE_METADATA = {
|
|
|
10931
11297
|
* Creates a Handover line control measure: a polyline through `positions`
|
|
10932
11298
|
* labeled "HOL" above the line near each end.
|
|
10933
11299
|
*/
|
|
10934
|
-
function createHandoverLine(positions, options = {}, textAmplifiers = {}) {
|
|
11300
|
+
function createHandoverLine(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
10935
11301
|
return fixedLabelLineFeatures(positions, {
|
|
10936
11302
|
part: "handover-line",
|
|
10937
11303
|
placement: "above",
|
|
10938
11304
|
...options
|
|
10939
|
-
}, "HOL", textAmplifiers.N);
|
|
11305
|
+
}, "HOL", textAmplifiers.N, context);
|
|
10940
11306
|
}
|
|
10941
11307
|
const HANDOVER_LINE = defineControlMeasure({
|
|
10942
11308
|
metadata: HANDOVER_LINE_METADATA,
|
|
@@ -11010,12 +11376,12 @@ const BATTLE_HANDOVER_LINE_METADATA = {
|
|
|
11010
11376
|
* Creates a Battle handover line control measure: a polyline through
|
|
11011
11377
|
* `positions` labeled "BHL" above the line near each end.
|
|
11012
11378
|
*/
|
|
11013
|
-
function createBattleHandoverLine(positions, options = {}, textAmplifiers = {}) {
|
|
11379
|
+
function createBattleHandoverLine(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
11014
11380
|
return fixedLabelLineFeatures(positions, {
|
|
11015
11381
|
part: "battle-handover-line",
|
|
11016
11382
|
placement: "above",
|
|
11017
11383
|
...options
|
|
11018
|
-
}, "BHL", textAmplifiers.N);
|
|
11384
|
+
}, "BHL", textAmplifiers.N, context);
|
|
11019
11385
|
}
|
|
11020
11386
|
const BATTLE_HANDOVER_LINE = defineControlMeasure({
|
|
11021
11387
|
metadata: BATTLE_HANDOVER_LINE_METADATA,
|
|
@@ -11167,7 +11533,7 @@ function generateFortifiedPoints(projectedPoints, effectiveSize) {
|
|
|
11167
11533
|
* @param options - Configuration options for the graphic
|
|
11168
11534
|
* @returns A GeoJSON FeatureCollection containing the castellated LineString
|
|
11169
11535
|
*/
|
|
11170
|
-
function createFortifiedLine(positions, options = {}, textAmplifiers = {}) {
|
|
11536
|
+
function createFortifiedLine(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
11171
11537
|
const { smooth = DEFAULT_FORTIFIED_LINE_OPTIONS.smooth, smoothResolution = DEFAULT_FORTIFIED_LINE_OPTIONS.smoothResolution } = options;
|
|
11172
11538
|
const safeSize = calculateEffectiveSize(options);
|
|
11173
11539
|
const projectedPoints = positions.map((p) => project(p[0], p[1]));
|
|
@@ -11181,7 +11547,7 @@ function createFortifiedLine(positions, options = {}, textAmplifiers = {}) {
|
|
|
11181
11547
|
}
|
|
11182
11548
|
};
|
|
11183
11549
|
const labels = [];
|
|
11184
|
-
pushHostileEndLabels(labels, smoothedPath, textAmplifiers.N, options);
|
|
11550
|
+
pushHostileEndLabels(labels, smoothedPath, textAmplifiers.N, options, "", context);
|
|
11185
11551
|
return {
|
|
11186
11552
|
type: "FeatureCollection",
|
|
11187
11553
|
features: [feature, ...labels]
|
|
@@ -11265,7 +11631,7 @@ function createFortifiedArea(positions, options = {}, textAmplifiers = {}, conte
|
|
|
11265
11631
|
}, options, context.amplifierPlacements, {
|
|
11266
11632
|
ringVerts: boundaryPoints,
|
|
11267
11633
|
extraClearanceMeters: 3 * safeSize
|
|
11268
|
-
})),
|
|
11634
|
+
}, context)),
|
|
11269
11635
|
properties: {}
|
|
11270
11636
|
}];
|
|
11271
11637
|
features.push(...labelFeatures);
|
|
@@ -12190,7 +12556,7 @@ const JOINT_TACTICAL_ACTION_AREA_METADATA = {
|
|
|
12190
12556
|
* {@link labeledAreaFeatures}.
|
|
12191
12557
|
*/
|
|
12192
12558
|
function createJointTacticalActionArea(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
12193
|
-
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "JTAA -", ""), context.amplifierPlacements);
|
|
12559
|
+
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "JTAA -", ""), context.amplifierPlacements, context);
|
|
12194
12560
|
}
|
|
12195
12561
|
const JOINT_TACTICAL_ACTION_AREA = defineControlMeasure({
|
|
12196
12562
|
metadata: JOINT_TACTICAL_ACTION_AREA_METADATA,
|
|
@@ -12249,7 +12615,7 @@ const LANDING_ZONE_METADATA = {
|
|
|
12249
12615
|
* amplifier rows, per {@link labeledAreaFeatures}.
|
|
12250
12616
|
*/
|
|
12251
12617
|
function createLandingZone(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
12252
|
-
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "LZ"), context.amplifierPlacements);
|
|
12618
|
+
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "LZ"), context.amplifierPlacements, context);
|
|
12253
12619
|
}
|
|
12254
12620
|
const LANDING_ZONE = defineControlMeasure({
|
|
12255
12621
|
metadata: LANDING_ZONE_METADATA,
|
|
@@ -12320,7 +12686,7 @@ function createLimitedAccessArea(positions, options = {}, textAmplifiers = {}, c
|
|
|
12320
12686
|
}];
|
|
12321
12687
|
return {
|
|
12322
12688
|
type: "FeatureCollection",
|
|
12323
|
-
features: [...patternedAreaFeatures(verts, pushAreaLabels(labelFeatures, verts, { hostile: textAmplifiers.N }, options, context.amplifierPlacements), "reverse-hatch"), ...labelFeatures]
|
|
12689
|
+
features: [...patternedAreaFeatures(verts, pushAreaLabels(labelFeatures, verts, { hostile: textAmplifiers.N }, options, context.amplifierPlacements, void 0, context), "reverse-hatch"), ...labelFeatures]
|
|
12324
12690
|
};
|
|
12325
12691
|
}
|
|
12326
12692
|
const LIMITED_ACCESS_AREA = defineControlMeasure({
|
|
@@ -12431,7 +12797,7 @@ function createNoFireAreaIrregular(positions, options = {}, textAmplifiers = {},
|
|
|
12431
12797
|
}
|
|
12432
12798
|
return {
|
|
12433
12799
|
type: "FeatureCollection",
|
|
12434
|
-
features: [...patternedAreaFeatures(verts, pushAreaLabels(labels, verts, { hostile: textAmplifiers.N }, options, context.amplifierPlacements), "reverse-hatch"), ...labels]
|
|
12800
|
+
features: [...patternedAreaFeatures(verts, pushAreaLabels(labels, verts, { hostile: textAmplifiers.N }, options, context.amplifierPlacements, void 0, context), "reverse-hatch"), ...labels]
|
|
12435
12801
|
};
|
|
12436
12802
|
}
|
|
12437
12803
|
const NO_FIRE_AREA_IRREGULAR = defineControlMeasure({
|
|
@@ -12776,7 +13142,7 @@ function createMinefield(coordinates, options = {}, textAmplifiers = {}, context
|
|
|
12776
13142
|
clearance: ENY_CLEARANCE_RATIO * labelSize,
|
|
12777
13143
|
sizeProps,
|
|
12778
13144
|
followPlacementRotation: true
|
|
12779
|
-
}, context.amplifierPlacements);
|
|
13145
|
+
}, context.amplifierPlacements, context, { labelSize });
|
|
12780
13146
|
if (textAmplifiers.W) pushLabel(labelFeatures, offset(center, 0, -halfHeight - width * LABEL_CLEARANCE_RATIO), textAmplifiers.W, labelRotation, sizeProps, void 0, "W");
|
|
12781
13147
|
const features = [{
|
|
12782
13148
|
type: "Feature",
|
|
@@ -12993,7 +13359,7 @@ function createMineArea(positions, options, minedArea, textAmplifiers, context)
|
|
|
12993
13359
|
textHeight: resolveLabelOffsetMeters(options, 1),
|
|
12994
13360
|
clearance: resolveLabelOffsetMeters(options, .12 + labelPadding),
|
|
12995
13361
|
sizeProps: labelSizeProps(options)
|
|
12996
|
-
}) : [], ...pushAreaLabels(labelFeatures, ring, { hostile: textAmplifiers.N }, options, context.amplifierPlacements)], mineFillPattern(mineType)), ...labelFeatures]
|
|
13362
|
+
}, void 0, context, options) : [], ...pushAreaLabels(labelFeatures, ring, { hostile: textAmplifiers.N }, options, context.amplifierPlacements, void 0, context)], mineFillPattern(mineType)), ...labelFeatures]
|
|
12997
13363
|
};
|
|
12998
13364
|
}
|
|
12999
13365
|
function createDynamicMinefield(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
@@ -13496,7 +13862,7 @@ const PICKUP_ZONE_METADATA = {
|
|
|
13496
13862
|
* amplifier rows, per {@link labeledAreaFeatures}.
|
|
13497
13863
|
*/
|
|
13498
13864
|
function createPickupZone(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
13499
|
-
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "PZ"), context.amplifierPlacements);
|
|
13865
|
+
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "PZ"), context.amplifierPlacements, context);
|
|
13500
13866
|
}
|
|
13501
13867
|
const PICKUP_ZONE = defineControlMeasure({
|
|
13502
13868
|
metadata: PICKUP_ZONE_METADATA,
|
|
@@ -13551,7 +13917,7 @@ const ASSAULT_POSITION_METADATA = {
|
|
|
13551
13917
|
* an `N` (Field N) ENY marker, per {@link labeledAreaFeatures}.
|
|
13552
13918
|
*/
|
|
13553
13919
|
function createAssaultPosition(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
13554
|
-
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "ASLT"), context.amplifierPlacements);
|
|
13920
|
+
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "ASLT"), context.amplifierPlacements, context);
|
|
13555
13921
|
}
|
|
13556
13922
|
const ASSAULT_POSITION = defineControlMeasure({
|
|
13557
13923
|
metadata: ASSAULT_POSITION_METADATA,
|
|
@@ -13606,7 +13972,7 @@ const ATTACK_POSITION_METADATA = {
|
|
|
13606
13972
|
* an `N` (Field N) ENY marker, per {@link labeledAreaFeatures}.
|
|
13607
13973
|
*/
|
|
13608
13974
|
function createAttackPosition(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
13609
|
-
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "ATK"), context.amplifierPlacements);
|
|
13975
|
+
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "ATK"), context.amplifierPlacements, context);
|
|
13610
13976
|
}
|
|
13611
13977
|
const ATTACK_POSITION = defineControlMeasure({
|
|
13612
13978
|
metadata: ATTACK_POSITION_METADATA,
|
|
@@ -13661,7 +14027,7 @@ const OBJECTIVE_AREA_METADATA = {
|
|
|
13661
14027
|
* an `N` (Field N) ENY marker, per {@link labeledAreaFeatures}.
|
|
13662
14028
|
*/
|
|
13663
14029
|
function createObjectiveArea(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
13664
|
-
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "OBJ"), context.amplifierPlacements);
|
|
14030
|
+
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "OBJ"), context.amplifierPlacements, context);
|
|
13665
14031
|
}
|
|
13666
14032
|
const OBJECTIVE_AREA = defineControlMeasure({
|
|
13667
14033
|
metadata: OBJECTIVE_AREA_METADATA,
|
|
@@ -13944,7 +14310,7 @@ function createStrongPoint(positions, options = {}, textAmplifiers = {}, context
|
|
|
13944
14310
|
const enyGaps = pushAreaLabels(labelFeatures, verts, {
|
|
13945
14311
|
name: textAmplifiers.T,
|
|
13946
14312
|
hostile: textAmplifiers.N
|
|
13947
|
-
}, options, context.amplifierPlacements);
|
|
14313
|
+
}, options, context.amplifierPlacements, void 0, context);
|
|
13948
14314
|
const allGaps = [...gaps, ...enyGaps];
|
|
13949
14315
|
const boundaryCoords = buildGappedLine(verts, allGaps);
|
|
13950
14316
|
const tics = buildStrongPointTics(perimeter, allGaps, h);
|
|
@@ -14050,7 +14416,7 @@ const SUBMARINE_ACTION_AREA_METADATA = {
|
|
|
14050
14416
|
* `W`/`W1`/`N` amplifier rows, per {@link labeledAreaFeatures}.
|
|
14051
14417
|
*/
|
|
14052
14418
|
function createSubmarineActionArea(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
14053
|
-
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "SAA -", ""), context.amplifierPlacements);
|
|
14419
|
+
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "SAA -", ""), context.amplifierPlacements, context);
|
|
14054
14420
|
}
|
|
14055
14421
|
const SUBMARINE_ACTION_AREA = defineControlMeasure({
|
|
14056
14422
|
metadata: SUBMARINE_ACTION_AREA_METADATA,
|
|
@@ -14110,7 +14476,7 @@ const SUBMARINE_GENERATED_ACTION_AREA_METADATA = {
|
|
|
14110
14476
|
* (Field T) plus `W`/`W1`/`N` amplifier rows, per {@link labeledAreaFeatures}.
|
|
14111
14477
|
*/
|
|
14112
14478
|
function createSubmarineGeneratedActionArea(positions, options = {}, textAmplifiers = {}, context = {}) {
|
|
14113
|
-
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "SGSA -", ""), context.amplifierPlacements);
|
|
14479
|
+
return labeledAreaFeatures(positions, options, composeAreaLabelTexts(textAmplifiers, "SGSA -", ""), context.amplifierPlacements, context);
|
|
14114
14480
|
}
|
|
14115
14481
|
const SUBMARINE_GENERATED_ACTION_AREA = defineControlMeasure({
|
|
14116
14482
|
metadata: SUBMARINE_GENERATED_ACTION_AREA_METADATA,
|
|
@@ -14514,7 +14880,9 @@ const DEFINITIONS = {
|
|
|
14514
14880
|
polygon: GENERIC_POLYGON,
|
|
14515
14881
|
rectangle: GENERIC_RECTANGLE,
|
|
14516
14882
|
circle: GENERIC_CIRCLE,
|
|
14883
|
+
sector: GENERIC_SECTOR,
|
|
14517
14884
|
text: GENERIC_TEXT,
|
|
14885
|
+
"radar-search-doctrine": RADAR_SEARCH_DOCTRINE,
|
|
14518
14886
|
"airborne-attack": AIRBORNE_ATTACK,
|
|
14519
14887
|
"attack-helicopter": ATTACK_HELICOPTER,
|
|
14520
14888
|
"support-by-fire": SUPPORT_BY_FIRE,
|
|
@@ -14806,7 +15174,31 @@ function dispatchControlMeasure(cm, opts) {
|
|
|
14806
15174
|
if (!validateInputContract(cm.controlPoints, definition.metadata, opts?.validationMode)) return EMPTY_COLLECTION;
|
|
14807
15175
|
const generator = definition.generator;
|
|
14808
15176
|
const textAmplifiers = normalizeTextAmplifiers(cm.textAmplifiers);
|
|
14809
|
-
|
|
15177
|
+
const constructionMetersPerCssPixel = resolveConstructionScale(cm.controlPoints, opts?.context?.groundMetersPerCssPixel);
|
|
15178
|
+
const authoredOptions = cm.options ?? {};
|
|
15179
|
+
const generatorOptions = constructionMetersPerCssPixel === void 0 ? authoredOptions : {
|
|
15180
|
+
...authoredOptions,
|
|
15181
|
+
metersPerPixel: constructionMetersPerCssPixel
|
|
15182
|
+
};
|
|
15183
|
+
return generator(cm.controlPoints, generatorOptions, textAmplifiers, {
|
|
15184
|
+
amplifierPlacements: cm.amplifierPlacements,
|
|
15185
|
+
constructionMetersPerCssPixel,
|
|
15186
|
+
labelSizeClampCssPixels: opts?.context?.labelSizeClampCssPixels,
|
|
15187
|
+
measureText: opts?.context?.measureText
|
|
15188
|
+
});
|
|
15189
|
+
}
|
|
15190
|
+
/** Convert true-ground scale to the Web Mercator metres legacy generators construct in. */
|
|
15191
|
+
function resolveConstructionScale(points, groundMetersPerCssPixel) {
|
|
15192
|
+
if (!(groundMetersPerCssPixel !== void 0 && groundMetersPerCssPixel > 0)) return void 0;
|
|
15193
|
+
let minLatitude = Infinity;
|
|
15194
|
+
let maxLatitude = -Infinity;
|
|
15195
|
+
for (const point of points) {
|
|
15196
|
+
minLatitude = Math.min(minLatitude, point[1]);
|
|
15197
|
+
maxLatitude = Math.max(maxLatitude, point[1]);
|
|
15198
|
+
}
|
|
15199
|
+
const midpointLatitude = (minLatitude + maxLatitude) / 2;
|
|
15200
|
+
const mercatorScale = Math.cos(midpointLatitude * Math.PI / 180);
|
|
15201
|
+
return groundMetersPerCssPixel / Math.max(mercatorScale, Number.EPSILON);
|
|
14810
15202
|
}
|
|
14811
15203
|
/**
|
|
14812
15204
|
* The control-point *input contract*: `controlPoints` must be present and an
|
|
@@ -14945,4 +15337,4 @@ function assertNever(value) {
|
|
|
14945
15337
|
throw new Error(`Unhandled control measure kind: ${String(value)}`);
|
|
14946
15338
|
}
|
|
14947
15339
|
//#endregion
|
|
14948
|
-
export { DEFAULT_TACTICAL_ARROW_OPTIONS as $,
|
|
15340
|
+
export { DEFAULT_TACTICAL_ARROW_OPTIONS as $, line26DrawRule as $t, DEFAULT_REARWARD_PASSAGE_OF_LINES_OPTIONS as A, DEFAULT_AREA_OF_OPERATIONS_OPTIONS as At, DEFAULT_BRIDGEHEAD_LINE_OPTIONS as B, DEFAULT_LABEL_SIZE_CLAMP_CSS_PIXELS as Bt, DEFAULT_LANDING_ZONE_OPTIONS as C, project as Cn, DEFAULT_BLOCK_OPTIONS as Ct, DEFAULT_SEIZE_OPTIONS as D, getMetersPerPixel as Dn, DEFAULT_ANTITANK_WALL_OPTIONS as Dt, DEFAULT_WITHDRAW_OPTIONS as E, EPSILON as En, DEFAULT_ATTACK_BY_FIRE_OPTIONS as Et, DEFAULT_BATTLE_HANDOVER_LINE_OPTIONS as F, TEXT_AMPLIFIER_FIELDS as Ft, DEFAULT_FINAL_PROTECTIVE_FIRE_OPTIONS as G, DEFAULT_STROKE_WIDTH_CSS_PIXELS as Gt, DEFAULT_FLOT_OPTIONS as H, DEFAULT_LINE_JOIN as Ht, DEFAULT_HANDOVER_LINE_OPTIONS as I, canonicalTextAmplifierKey as It, DEFAULT_GUARD_OPTIONS as J, DEFAULT_AIRBORNE_ATTACK_OPTIONS as Jt, DEFAULT_ENCIRCLEMENT_OPTIONS as K, DEFAULT_SYMBOL_COLOR as Kt, DEFAULT_FORWARD_EDGE_OF_BATTLE_AREA_OPTIONS as L, normalizeTextAmplifiers as Lt, DEFAULT_FRONTAL_ATTACK_OPTIONS as M, DEFAULT_DIRECTION_OF_SUPPORTING_ATTACK_OPTIONS as Mt, DEFAULT_FORTIFIED_AREA_OPTIONS as N, DEFAULT_DIRECTION_OF_MAIN_ATTACK_OPTIONS as Nt, DEFAULT_SCREEN_OPTIONS as O, roundToFixed as On, DEFAULT_ANTITANK_DITCH_OPTIONS as Ot, DEFAULT_FORTIFIED_LINE_OPTIONS as P, DEFAULT_DIRECTION_OF_ATTACK_AVIATION_OPTIONS as Pt, DEFAULT_COVER_OPTIONS as Q, line27DrawRule as Qt, DEFAULT_RELEASE_LINE_OPTIONS as R, resolveAmplifierPlacement as Rt, DEFAULT_MAIN_ATTACK_OPTIONS as S, haversineDistance as Sn, DEFAULT_BLOCK_ARROW_OPTIONS as St, DEFAULT_WITHDRAW_UNDER_PRESSURE_OPTIONS as T, unproject as Tn, DEFAULT_ATTACK_HELICOPTER_OPTIONS as Tt, DEFAULT_FIX_MISSION_TASK_OPTIONS as U, DEFAULT_PORTRAYAL as Ut, DEFAULT_PHASE_LINE_OPTIONS as V, DEFAULT_LINE_CAP as Vt, DEFAULT_FIX_OPTIONS as W, DEFAULT_STROKE_DASH_CSS_PIXELS as Wt, DEFAULT_DISENGAGE_OPTIONS as X, axis1DrawRule as Xt, DEFAULT_DISRUPT_MISSION_TASK_OPTIONS as Y, rectangleDrawRule as Yt, DEFAULT_DELAY_OPTIONS as Z, supportByFireDrawRule as Zt, DEFAULT_PICKUP_ZONE_OPTIONS as _, pointOnMidpointPerpendicularAxis as _n, DEFAULT_BLOCK_MISSION_TASK_OPTIONS as _t, DEFINITIONS as a, attackByFireDrawRule as an, RADAR_SEARCH_DOCTRINE_FILL_COLOR as at, DEFAULT_OBSTACLE_BYPASS_DIFFICULT_OPTIONS as b, calculateMetrics as bn, DEFAULT_LIGHT_LINE_OPTIONS as bt, getDefaultOptions as c, staticPointDrawRule as cn, DEFAULT_GENERIC_SECTOR_OPTIONS as ct, DEFAULT_TURN_OPTIONS as d, centerRadiusDrawRule as dn, DEFAULT_GENERIC_LINE_OPTIONS as dt, line24DrawRule as en, DEFAULT_COUNTERATTACK_BY_FIRE_OPTIONS as et, DEFAULT_SUPPORT_BY_FIRE_OPTIONS as f, disruptDrawRule as fn, DEFAULT_GENERIC_CIRCLE_OPTIONS as ft, DEFAULT_PRINCIPAL_DIRECTION_OF_FIRE_OPTIONS as g, getMidpointPerpendicularSignedDistance as gn, DEFAULT_BREACH_OPTIONS as gt, SECONDARY_DIRECTION_OF_FIRE_DASH as h, createMidpointPerpendicularDrawRule as hn, DEFAULT_BYPASS_OPTIONS as ht, CONTROL_MEASURE_METADATA as i, ambushDrawRule as in, DEFAULT_RADAR_SEARCH_DOCTRINE_OPTIONS as it, DEFAULT_PENETRATE_OPTIONS as j, DEFAULT_AREA_DEFENSE_OPTIONS as jt, DEFAULT_RETIRE_OPTIONS as k, DEFAULT_ASSEMBLY_AREA_OPTIONS as kt, listControlMeasureMetadata as l, point12DrawRule as ln, DEFAULT_GENERIC_RECTANGLE_OPTIONS as lt, DEFAULT_SECONDARY_DIRECTION_OF_FIRE_OPTIONS as m, computeDefaultMidpointPerpendicularPoint as mn, DEFAULT_CANALIZE_OPTIONS as mt, resolveStyleHints as n, turnDrawRule as nn, DEFAULT_SUPPORTING_ATTACK_OPTIONS as nt, getControlMeasureMetadata as o, dynamicPointDrawRule as on, RADAR_SEARCH_DOCTRINE_STROKE_COLOR as ot, DEFAULT_STRONG_POINT_OPTIONS as p, blockDrawRule as pn, DEFAULT_CLASSIC_ARROW_OPTIONS as pt, DEFAULT_DISRUPT_OPTIONS as q, DEFAULT_AMBUSH_OPTIONS as qt, CONTROL_MEASURE_IDS as r, line1DrawRule as rn, DEFAULT_CLEAR_OPTIONS as rt, getControlMeasureMetadataByValue as s, sectorDrawRule as sn, DEFAULT_GENERIC_TEXT_OPTIONS as st, renderControlMeasure as t, line23DrawRule as tn, DEFAULT_COUNTERATTACK_OPTIONS as tt, DEFAULT_TURNING_MOVEMENT_OPTIONS as u, penetrateDrawRule as un, DEFAULT_GENERIC_POLYGON_OPTIONS as ut, DEFAULT_OBSTACLE_BYPASS_IMPOSSIBLE_OPTIONS as v, snapToMidpointPerpendicular as vn, DEFAULT_GENERIC_C2_LINE_OPTIONS as vt, DEFAULT_JOINT_TACTICAL_ACTION_AREA_OPTIONS as w, sphericalBearing as wn, DEFAULT_BATTLE_POSITION_OPTIONS as wt, DEFAULT_MINEFIELD_OPTIONS as x, computeInitialWidthPoint as xn, DEFAULT_BOUNDARY_OPTIONS as xt, DEFAULT_OBSTACLE_BYPASS_EASY_OPTIONS as y, createBaselineFrame as yn, DEFAULT_ENGINEER_WORK_LINE_OPTIONS as yt, DEFAULT_HOLDING_LINE_OPTIONS as z, DEFAULT_LABEL_HEIGHT_CSS_PIXELS as zt };
|