@orbat-mapper/control-measures 0.2.0-alpha.17 → 0.2.0-alpha.19

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.
@@ -587,7 +587,7 @@ declare const DEFAULT_LIGHT_LINE_OPTIONS: LightLineOptions;
587
587
  * Creates a Light line control measure: a polyline through `positions`
588
588
  * labeled "LL" above the line near each end.
589
589
  */
590
- declare function createLightLine(positions: Position[], options?: LightLineOptions): FeatureCollection<LineString | Point>;
590
+ declare function createLightLine(positions: Position[], options?: LightLineOptions, textAmplifiers?: CanonicalTextAmplifiers): FeatureCollection<LineString | Point>;
591
591
  //#endregion
592
592
  //#region src/generators/cm11-command-control-lines/engineer-work-line.d.ts
593
593
  /** Configuration options for the Engineer work line control measure. */
@@ -1508,7 +1508,7 @@ declare function createAttackByFire(coordinates: Position[], options?: AttackByF
1508
1508
  /**
1509
1509
  * Configuration options for the FLOT (Forward Line of Own Troops) geometry.
1510
1510
  */
1511
- interface FLOTOptions extends SmoothLineOptions {
1511
+ interface FLOTOptions extends SmoothLineOptions, LineLabelOptions {
1512
1512
  /**
1513
1513
  * The radius of the semicircular arcs in meters.
1514
1514
  * Semicircles are placed tangent to each other (diameter = 2 * radius).
@@ -1540,7 +1540,7 @@ interface FLOTOptions extends SmoothLineOptions {
1540
1540
  /**
1541
1541
  * Default options for the FLOT graphic.
1542
1542
  */
1543
- declare const DEFAULT_FLOT_OPTIONS: Required<Omit<FLOTOptions, "metersPerPixel">>;
1543
+ declare const DEFAULT_FLOT_OPTIONS: Required<Omit<FLOTOptions, keyof LineLabelOptions>>;
1544
1544
  /**
1545
1545
  * Creates a FLOT (Forward Line of Own Troops) tactical graphic.
1546
1546
  *
@@ -1560,7 +1560,7 @@ declare const DEFAULT_FLOT_OPTIONS: Required<Omit<FLOTOptions, "metersPerPixel">
1560
1560
  * );
1561
1561
  * ```
1562
1562
  */
1563
- declare function createFLOT(positions: Position[], options?: FLOTOptions): FeatureCollection<LineString>;
1563
+ declare function createFLOT(positions: Position[], options?: FLOTOptions, textAmplifiers?: CanonicalTextAmplifiers): FeatureCollection<LineString | Point>;
1564
1564
  //#endregion
1565
1565
  //#region src/generators/cm14-maneuver-lines/phase-line.d.ts
1566
1566
  /** Configuration options for the Phase line control measure. */
@@ -1606,7 +1606,7 @@ declare const DEFAULT_HANDOVER_LINE_OPTIONS: HandoverLineOptions;
1606
1606
  * Creates a Handover line control measure: a polyline through `positions`
1607
1607
  * labeled "HOL" above the line near each end.
1608
1608
  */
1609
- declare function createHandoverLine(positions: Position[], options?: HandoverLineOptions): FeatureCollection<LineString | Point>;
1609
+ declare function createHandoverLine(positions: Position[], options?: HandoverLineOptions, textAmplifiers?: CanonicalTextAmplifiers): FeatureCollection<LineString | Point>;
1610
1610
  //#endregion
1611
1611
  //#region src/generators/cm14-maneuver-lines/battle-handover-line.d.ts
1612
1612
  /** Configuration options for the Battle handover line control measure. */
@@ -1633,7 +1633,7 @@ declare const DEFAULT_BATTLE_HANDOVER_LINE_OPTIONS: BattleHandoverLineOptions;
1633
1633
  * Creates a Battle handover line control measure: a polyline through
1634
1634
  * `positions` labeled "BHL" above the line near each end.
1635
1635
  */
1636
- declare function createBattleHandoverLine(positions: Position[], options?: BattleHandoverLineOptions): FeatureCollection<LineString | Point>;
1636
+ declare function createBattleHandoverLine(positions: Position[], options?: BattleHandoverLineOptions, textAmplifiers?: CanonicalTextAmplifiers): FeatureCollection<LineString | Point>;
1637
1637
  //#endregion
1638
1638
  //#region src/generators/cm34-mission-tasks/block.d.ts
1639
1639
  interface BlockMissionTaskOptions {
@@ -1902,7 +1902,7 @@ declare const DEFAULT_SCREEN_OPTIONS: Required<Omit<SecurityTaskOptions, "labelS
1902
1902
  declare function createScreenSymbol(coordinates: Position[], options?: ScreenOptions): FeatureCollection<MultiLineString | Point>;
1903
1903
  //#endregion
1904
1904
  //#region src/generators/cm29-protection-lines/antitankDitch.d.ts
1905
- interface AntitankDitchOptions extends SmoothLineOptions {
1905
+ interface AntitankDitchOptions extends SmoothLineOptions, LineLabelOptions {
1906
1906
  /**
1907
1907
  * The tooth depth in meters. Target tooth spacing is roughly 1.15x this depth.
1908
1908
  * Ignored when toothHeightPixels is provided with metersPerPixel.
@@ -1927,14 +1927,9 @@ interface AntitankDitchOptions extends SmoothLineOptions {
1927
1927
  */
1928
1928
  metersPerPixel?: number;
1929
1929
  }
1930
- declare const DEFAULT_ANTITANK_DITCH_OPTIONS: Required<Omit<AntitankDitchOptions, "metersPerPixel">>;
1931
- declare function createAntitankDitchUnderConstruction(positions: Position[], options?: AntitankDitchOptions): FeatureCollection<MultiLineString, {
1932
- part: "ditch";
1933
- }>;
1934
- declare function createAntitankDitchCompleted(positions: Position[], options?: AntitankDitchOptions): FeatureCollection<MultiPolygon, {
1935
- part: "teeth";
1936
- fill: boolean;
1937
- }>;
1930
+ declare const DEFAULT_ANTITANK_DITCH_OPTIONS: Required<Omit<AntitankDitchOptions, keyof LineLabelOptions>>;
1931
+ declare function createAntitankDitchUnderConstruction(positions: Position[], options?: AntitankDitchOptions, textAmplifiers?: CanonicalTextAmplifiers): FeatureCollection<MultiLineString | Point>;
1932
+ declare function createAntitankDitchCompleted(positions: Position[], options?: AntitankDitchOptions, textAmplifiers?: CanonicalTextAmplifiers): FeatureCollection<MultiPolygon | Point>;
1938
1933
  //#endregion
1939
1934
  //#region src/generators/cm29-protection-lines/antitankWall.d.ts
1940
1935
  interface AntitankWallOptions extends AntitankDitchOptions {
@@ -1945,16 +1940,14 @@ interface AntitankWallOptions extends AntitankDitchOptions {
1945
1940
  */
1946
1941
  toothSpacingRatio?: number;
1947
1942
  }
1948
- declare const DEFAULT_ANTITANK_WALL_OPTIONS: Required<Omit<AntitankWallOptions, "metersPerPixel">>;
1949
- declare function createAntitankWall(positions: Position[], options?: AntitankWallOptions): FeatureCollection<MultiLineString, {
1950
- part: "wall";
1951
- }>;
1943
+ declare const DEFAULT_ANTITANK_WALL_OPTIONS: Required<Omit<AntitankWallOptions, keyof LineLabelOptions>>;
1944
+ declare function createAntitankWall(positions: Position[], options?: AntitankWallOptions, textAmplifiers?: CanonicalTextAmplifiers): FeatureCollection<MultiLineString | Point>;
1952
1945
  //#endregion
1953
1946
  //#region src/generators/cm29-protection-lines/fortifiedLine.d.ts
1954
1947
  /**
1955
1948
  * Configuration options for the Fortified Line geometry.
1956
1949
  */
1957
- interface FortifiedLineOptions {
1950
+ interface FortifiedLineOptions extends LineLabelOptions {
1958
1951
  /**
1959
1952
  * The size of the square teeth in meters.
1960
1953
  * This controls both the width and height of the castellated pattern.
@@ -1994,7 +1987,7 @@ interface FortifiedLineOptions {
1994
1987
  /**
1995
1988
  * Default options for the Fortified Line graphic.
1996
1989
  */
1997
- declare const DEFAULT_FORTIFIED_LINE_OPTIONS: Required<Omit<FortifiedLineOptions, "metersPerPixel">>;
1990
+ declare const DEFAULT_FORTIFIED_LINE_OPTIONS: Required<Omit<FortifiedLineOptions, keyof LineLabelOptions>>;
1998
1991
  /**
1999
1992
  * Creates a Fortified Line tactical graphic.
2000
1993
  *
@@ -2005,7 +1998,7 @@ declare const DEFAULT_FORTIFIED_LINE_OPTIONS: Required<Omit<FortifiedLineOptions
2005
1998
  * @param options - Configuration options for the graphic
2006
1999
  * @returns A GeoJSON FeatureCollection containing the castellated LineString
2007
2000
  */
2008
- declare function createFortifiedLine(positions: Position[], options?: FortifiedLineOptions): FeatureCollection<LineString>;
2001
+ declare function createFortifiedLine(positions: Position[], options?: FortifiedLineOptions, textAmplifiers?: CanonicalTextAmplifiers): FeatureCollection<LineString | Point>;
2009
2002
  //#endregion
2010
2003
  //#region src/generators/cm15-maneuver-areas/fortifiedArea.d.ts
2011
2004
  /**
@@ -2032,11 +2025,14 @@ declare const DEFAULT_FORTIFIED_AREA_OPTIONS: FortifiedAreaOptions;
2032
2025
  * @param textAmplifiers - Field T (bare, no prefix — "TANGO", not "FA TANGO")
2033
2026
  * plus Field H/W/W1, centered on the pre-teeth boundary ring (see
2034
2027
  * `boundaryInput` below), and an optional Field N "ENY" hostile marker on
2035
- * that ring's west/east edges (placed but not boundary-masked — see below).
2036
- * @returns A GeoJSON FeatureCollection containing the castellated Polygon
2037
- * plus any label Points
2038
- */
2039
- declare function createFortifiedArea(positions: Position[], options?: FortifiedAreaOptions, textAmplifiers?: CanonicalTextAmplifiers): FeatureCollection<Polygon | Point>;
2028
+ * that ring's west/east edges, masking the castellated boundary beneath it.
2029
+ * @param context - Persisted amplifier placements used to move or restore each
2030
+ * ENY cutout with its independently dragged label.
2031
+ * @returns A GeoJSON FeatureCollection containing the castellated boundary
2032
+ * (a Polygon normally, or a gapped MultiLineString with Field N) plus any
2033
+ * label Points
2034
+ */
2035
+ declare function createFortifiedArea(positions: Position[], options?: FortifiedAreaOptions, textAmplifiers?: CanonicalTextAmplifiers, context?: ControlMeasureGeneratorContext): FeatureCollection<Polygon | MultiLineString | Point>;
2040
2036
  //#endregion
2041
2037
  //#region src/generators/cm15-maneuver-areas/maneuver-arrow-task-shared.d.ts
2042
2038
  interface ManeuverArrowTaskOptions extends AttackOptions, LabelSizeOptions {
package/dist/index.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { $ as getDefaultOptions, $n as DEFAULT_STRONG_POINT_OPTIONS, $t as DEFAULT_FLOT_OPTIONS, A as roundToFixed, An as DEFAULT_AREA_OF_OPERATIONS_OPTIONS, Ar as normalizeTextAmplifiers, At as GuardOptions, B as StyleHints, Bn as DEFAULT_AREA_DEFENSE_OPTIONS, Bt as DEFAULT_CANALIZE_OPTIONS, C as BaselineFrame, Cn as DEFAULT_SUPPORTING_ATTACK_OPTIONS, Cr as GeneratedLabelKey, Ct as AntitankWallOptions, D as createBaselineFrame, Dn as calculateMetrics, Dr as TextAmplifierKey, Dt as DEFAULT_SCREEN_OPTIONS, E as BaselineFrameOrigin, En as MainAttackOptions, Er as TextAmplifierField, Et as DEFAULT_ANTITANK_DITCH_OPTIONS, F as freezeOrientationOptions, Fn as DEFAULT_JOINT_TACTICAL_ACTION_AREA_OPTIONS, Ft as DEFAULT_TACTICAL_ARROW_OPTIONS, G as ControlMeasureStyle, Gn as DEFAULT_PRINCIPAL_DIRECTION_OF_FIRE_OPTIONS, Gt as BlockMissionTaskOptions, H as ControlMeasure, Hn as EncirclementOptions, Ht as DEFAULT_BYPASS_OPTIONS, I as RenderOptions, In as JointTacticalActionAreaOptions, It as TacticalArrowOptions, J as ControlMeasureId, Jn as DirectionOfSupportingAttackOptions, Jt as DEFAULT_BATTLE_HANDOVER_LINE_OPTIONS, K as CONTROL_MEASURE_IDS, Kn as PrincipalDirectionOfFireOptions, Kt as DEFAULT_BLOCK_MISSION_TASK_OPTIONS, L as renderControlMeasure, Ln as AssemblyAreaOptions, Lt as ClearOptions, M as SimpleStyleRender, Mn as PickupZoneOptions, Mr as AnchorTransformEvent, Mt as DelayOptions, N as toSimpleStyle, Nn as DEFAULT_LANDING_ZONE_OPTIONS, Nr as ControlMeasureDrawRule, Nt as CoverOptions, O as EPSILON, On as computeInitialWidthPoint, Or as TextAmplifiers, Ot as ScreenOptions, P as resolveStyleHints, Pn as LandingZoneOptions, Pt as DEFAULT_COVER_OPTIONS, Q as getControlMeasureMetadataByValue, Qn as DirectionOfAttackAviationOptions, Qt as PhaseLineOptions, R as ControlMeasureRender, Rn as DEFAULT_ASSEMBLY_AREA_OPTIONS, Rt as DEFAULT_CLEAR_OPTIONS, S as snapToMidpointPerpendicular, Sn as DEFAULT_CLASSIC_ARROW_OPTIONS, Sr as CanonicalTextAmplifiers, St as FortifiedLineOptions, T as BaselineFrameOptions, Tn as DEFAULT_MAIN_ATTACK_OPTIONS, Tr as TEXT_AMPLIFIER_FIELDS, Tt as AntitankDitchOptions, U as cloneControlMeasure, Un as DEFAULT_FINAL_PROTECTIVE_FIRE_OPTIONS, Ut as BreachOptions, V as controlMeasureIdFromFeature, Vn as DEFAULT_ENCIRCLEMENT_OPTIONS, Vt as BypassOptions, W as isKind, Wn as FinalProtectiveFireOptions, Wt as DEFAULT_BREACH_OPTIONS, X as OptionsByKind, Xn as DirectionOfMainAttackOptions, Xt as HandoverLineOptions, Y as ControlMeasureKind, Yn as DEFAULT_DIRECTION_OF_MAIN_ATTACK_OPTIONS, Yt as DEFAULT_HANDOVER_LINE_OPTIONS, Z as getControlMeasureMetadata, Zn as DEFAULT_DIRECTION_OF_ATTACK_AVIATION_OPTIONS, Zt as DEFAULT_PHASE_LINE_OPTIONS, _ as MidpointPerpendicularDrawRuleOptions, _n as BlockArrowHeadStyle, _r as ControlMeasureMetadata, _t as DEFAULT_FRONTAL_ATTACK_OPTIONS, a as supportByFireDrawRule, an as AttackHelicopterOptions, ar as DEFAULT_ENGINEER_WORK_LINE_OPTIONS, at as DEFAULT_OBSTACLE_BYPASS_EASY_OPTIONS, b as getMidpointPerpendicularSignedDistance, bn as ClassicArrowHeadStyle, br as AmplifierPlacement, bt as FortifiedAreaOptions, c as line23DrawRule, cn as DEFAULT_AIRBORNE_ATTACK_OPTIONS, cr as LightLineOptions, ct as TurnOptions, d as ambushDrawRule, dn as DEFAULT_GENERIC_RECTANGLE_OPTIONS, dr as project, dt as DEFAULT_DISRUPT_OPTIONS, en as FLOTOptions, er as StrongPointOptions, et as listControlMeasureMetadata, f as attackByFireDrawRule, fn as GenericRectangleOptions, fr as unproject, ft as DisruptOptions, g as blockDrawRule, gn as GenericLineOptions, gr as ControlMeasureGeometryType, gt as TurningMovementOptions, h as disruptDrawRule, hn as DEFAULT_GENERIC_LINE_OPTIONS, hr as ControlMeasureGeometry, ht as DEFAULT_TURNING_MOVEMENT_OPTIONS, i as axis1DrawRule, in as SupportByFireOptions, ir as GenericC2LineOptions, it as ObstacleBypassDifficultOptions, j as SimpleStyleProps, jn as DEFAULT_PICKUP_ZONE_OPTIONS, jr as resolveAmplifierPlacement, jt as DEFAULT_DELAY_OPTIONS, k as getMetersPerPixel, kn as AreaOfOperationsOptions, kr as canonicalTextAmplifierKey, kt as DEFAULT_GUARD_OPTIONS, l as turnDrawRule, ln as DEFAULT_GENERIC_CIRCLE_OPTIONS, lr as LabelSizeOptions, lt as DEFAULT_FIX_OPTIONS, m as centerRadiusDrawRule, mn as GenericPolygonOptions, mr as DEFAULT_BOUNDARY_OPTIONS, mt as DEFAULT_BLOCK_OPTIONS, n as DEFAULT_AMBUSH_OPTIONS, nn as DEFAULT_ATTACK_BY_FIRE_OPTIONS, nr as DEFAULT_BATTLE_POSITION_OPTIONS, nt as ObstacleBypassImpossibleOptions, o as line26DrawRule, on as DEFAULT_ATTACK_HELICOPTER_OPTIONS, or as EngineerWorkLineOptions, ot as ObstacleBypassEasyOptions, p as point12DrawRule, pn as DEFAULT_GENERIC_POLYGON_OPTIONS, pr as BoundaryOptions, pt as BlockOptions, q as CONTROL_MEASURE_METADATA, qn as DEFAULT_DIRECTION_OF_SUPPORTING_ATTACK_OPTIONS, qt as BattleHandoverLineOptions, r as rectangleDrawRule, rn as DEFAULT_SUPPORT_BY_FIRE_OPTIONS, rr as DEFAULT_GENERIC_C2_LINE_OPTIONS, rt as DEFAULT_OBSTACLE_BYPASS_DIFFICULT_OPTIONS, s as line24DrawRule, sn as AirborneAttackOptions, sr as DEFAULT_LIGHT_LINE_OPTIONS, st as DEFAULT_TURN_OPTIONS, t as AmbushOptions, tn as AttackByFireOptions, tr as BattlePositionOptions, tt as DEFAULT_OBSTACLE_BYPASS_IMPOSSIBLE_OPTIONS, u as line1DrawRule, un as GenericCircleOptions, ur as Point2D, ut as FixOptions, v as computeDefaultMidpointPerpendicularPoint, vn as BlockArrowOptions, vr as ParamDescriptor, vt as FrontalAttackOptions, w as BaselineFrameNormal, wn as SupportingAttackOptions, wr as LabelPlacementOverride, wt as DEFAULT_ANTITANK_WALL_OPTIONS, x as pointOnMidpointPerpendicularAxis, xn as ClassicArrowOptions, xr as AmplifierPlacements, xt as DEFAULT_FORTIFIED_LINE_OPTIONS, y as createMidpointPerpendicularDrawRule, yn as DEFAULT_BLOCK_ARROW_OPTIONS, yr as TextAmplifierDescriptor, yt as DEFAULT_FORTIFIED_AREA_OPTIONS, z as FeaturePartProps, zn as AreaDefenseOptions, zt as CanalizeOptions } from "./index-9CY6UM92.mjs";
1
+ import { $ as getDefaultOptions, $n as DEFAULT_STRONG_POINT_OPTIONS, $t as DEFAULT_FLOT_OPTIONS, A as roundToFixed, An as DEFAULT_AREA_OF_OPERATIONS_OPTIONS, Ar as normalizeTextAmplifiers, At as GuardOptions, B as StyleHints, Bn as DEFAULT_AREA_DEFENSE_OPTIONS, Bt as DEFAULT_CANALIZE_OPTIONS, C as BaselineFrame, Cn as DEFAULT_SUPPORTING_ATTACK_OPTIONS, Cr as GeneratedLabelKey, Ct as AntitankWallOptions, D as createBaselineFrame, Dn as calculateMetrics, Dr as TextAmplifierKey, Dt as DEFAULT_SCREEN_OPTIONS, E as BaselineFrameOrigin, En as MainAttackOptions, Er as TextAmplifierField, Et as DEFAULT_ANTITANK_DITCH_OPTIONS, F as freezeOrientationOptions, Fn as DEFAULT_JOINT_TACTICAL_ACTION_AREA_OPTIONS, Ft as DEFAULT_TACTICAL_ARROW_OPTIONS, G as ControlMeasureStyle, Gn as DEFAULT_PRINCIPAL_DIRECTION_OF_FIRE_OPTIONS, Gt as BlockMissionTaskOptions, H as ControlMeasure, Hn as EncirclementOptions, Ht as DEFAULT_BYPASS_OPTIONS, I as RenderOptions, In as JointTacticalActionAreaOptions, It as TacticalArrowOptions, J as ControlMeasureId, Jn as DirectionOfSupportingAttackOptions, Jt as DEFAULT_BATTLE_HANDOVER_LINE_OPTIONS, K as CONTROL_MEASURE_IDS, Kn as PrincipalDirectionOfFireOptions, Kt as DEFAULT_BLOCK_MISSION_TASK_OPTIONS, L as renderControlMeasure, Ln as AssemblyAreaOptions, Lt as ClearOptions, M as SimpleStyleRender, Mn as PickupZoneOptions, Mr as AnchorTransformEvent, Mt as DelayOptions, N as toSimpleStyle, Nn as DEFAULT_LANDING_ZONE_OPTIONS, Nr as ControlMeasureDrawRule, Nt as CoverOptions, O as EPSILON, On as computeInitialWidthPoint, Or as TextAmplifiers, Ot as ScreenOptions, P as resolveStyleHints, Pn as LandingZoneOptions, Pt as DEFAULT_COVER_OPTIONS, Q as getControlMeasureMetadataByValue, Qn as DirectionOfAttackAviationOptions, Qt as PhaseLineOptions, R as ControlMeasureRender, Rn as DEFAULT_ASSEMBLY_AREA_OPTIONS, Rt as DEFAULT_CLEAR_OPTIONS, S as snapToMidpointPerpendicular, Sn as DEFAULT_CLASSIC_ARROW_OPTIONS, Sr as CanonicalTextAmplifiers, St as FortifiedLineOptions, T as BaselineFrameOptions, Tn as DEFAULT_MAIN_ATTACK_OPTIONS, Tr as TEXT_AMPLIFIER_FIELDS, Tt as AntitankDitchOptions, U as cloneControlMeasure, Un as DEFAULT_FINAL_PROTECTIVE_FIRE_OPTIONS, Ut as BreachOptions, V as controlMeasureIdFromFeature, Vn as DEFAULT_ENCIRCLEMENT_OPTIONS, Vt as BypassOptions, W as isKind, Wn as FinalProtectiveFireOptions, Wt as DEFAULT_BREACH_OPTIONS, X as OptionsByKind, Xn as DirectionOfMainAttackOptions, Xt as HandoverLineOptions, Y as ControlMeasureKind, Yn as DEFAULT_DIRECTION_OF_MAIN_ATTACK_OPTIONS, Yt as DEFAULT_HANDOVER_LINE_OPTIONS, Z as getControlMeasureMetadata, Zn as DEFAULT_DIRECTION_OF_ATTACK_AVIATION_OPTIONS, Zt as DEFAULT_PHASE_LINE_OPTIONS, _ as MidpointPerpendicularDrawRuleOptions, _n as BlockArrowHeadStyle, _r as ControlMeasureMetadata, _t as DEFAULT_FRONTAL_ATTACK_OPTIONS, a as supportByFireDrawRule, an as AttackHelicopterOptions, ar as DEFAULT_ENGINEER_WORK_LINE_OPTIONS, at as DEFAULT_OBSTACLE_BYPASS_EASY_OPTIONS, b as getMidpointPerpendicularSignedDistance, bn as ClassicArrowHeadStyle, br as AmplifierPlacement, bt as FortifiedAreaOptions, c as line23DrawRule, cn as DEFAULT_AIRBORNE_ATTACK_OPTIONS, cr as LightLineOptions, ct as TurnOptions, d as ambushDrawRule, dn as DEFAULT_GENERIC_RECTANGLE_OPTIONS, dr as project, dt as DEFAULT_DISRUPT_OPTIONS, en as FLOTOptions, er as StrongPointOptions, et as listControlMeasureMetadata, f as attackByFireDrawRule, fn as GenericRectangleOptions, fr as unproject, ft as DisruptOptions, g as blockDrawRule, gn as GenericLineOptions, gr as ControlMeasureGeometryType, gt as TurningMovementOptions, h as disruptDrawRule, hn as DEFAULT_GENERIC_LINE_OPTIONS, hr as ControlMeasureGeometry, ht as DEFAULT_TURNING_MOVEMENT_OPTIONS, i as axis1DrawRule, in as SupportByFireOptions, ir as GenericC2LineOptions, it as ObstacleBypassDifficultOptions, j as SimpleStyleProps, jn as DEFAULT_PICKUP_ZONE_OPTIONS, jr as resolveAmplifierPlacement, jt as DEFAULT_DELAY_OPTIONS, k as getMetersPerPixel, kn as AreaOfOperationsOptions, kr as canonicalTextAmplifierKey, kt as DEFAULT_GUARD_OPTIONS, l as turnDrawRule, ln as DEFAULT_GENERIC_CIRCLE_OPTIONS, lr as LabelSizeOptions, lt as DEFAULT_FIX_OPTIONS, m as centerRadiusDrawRule, mn as GenericPolygonOptions, mr as DEFAULT_BOUNDARY_OPTIONS, mt as DEFAULT_BLOCK_OPTIONS, n as DEFAULT_AMBUSH_OPTIONS, nn as DEFAULT_ATTACK_BY_FIRE_OPTIONS, nr as DEFAULT_BATTLE_POSITION_OPTIONS, nt as ObstacleBypassImpossibleOptions, o as line26DrawRule, on as DEFAULT_ATTACK_HELICOPTER_OPTIONS, or as EngineerWorkLineOptions, ot as ObstacleBypassEasyOptions, p as point12DrawRule, pn as DEFAULT_GENERIC_POLYGON_OPTIONS, pr as BoundaryOptions, pt as BlockOptions, q as CONTROL_MEASURE_METADATA, qn as DEFAULT_DIRECTION_OF_SUPPORTING_ATTACK_OPTIONS, qt as BattleHandoverLineOptions, r as rectangleDrawRule, rn as DEFAULT_SUPPORT_BY_FIRE_OPTIONS, rr as DEFAULT_GENERIC_C2_LINE_OPTIONS, rt as DEFAULT_OBSTACLE_BYPASS_DIFFICULT_OPTIONS, s as line24DrawRule, sn as AirborneAttackOptions, sr as DEFAULT_LIGHT_LINE_OPTIONS, st as DEFAULT_TURN_OPTIONS, t as AmbushOptions, tn as AttackByFireOptions, tr as BattlePositionOptions, tt as DEFAULT_OBSTACLE_BYPASS_IMPOSSIBLE_OPTIONS, u as line1DrawRule, un as GenericCircleOptions, ur as Point2D, ut as FixOptions, v as computeDefaultMidpointPerpendicularPoint, vn as BlockArrowOptions, vr as ParamDescriptor, vt as FrontalAttackOptions, w as BaselineFrameNormal, wn as SupportingAttackOptions, wr as LabelPlacementOverride, wt as DEFAULT_ANTITANK_WALL_OPTIONS, x as pointOnMidpointPerpendicularAxis, xn as ClassicArrowOptions, xr as AmplifierPlacements, xt as DEFAULT_FORTIFIED_LINE_OPTIONS, y as createMidpointPerpendicularDrawRule, yn as DEFAULT_BLOCK_ARROW_OPTIONS, yr as TextAmplifierDescriptor, yt as DEFAULT_FORTIFIED_AREA_OPTIONS, z as FeaturePartProps, zn as AreaDefenseOptions, zt as CanalizeOptions } from "./index-BJwZXCKb.mjs";
2
2
  export { type AirborneAttackOptions, type AmbushOptions, type AmplifierPlacement, type AmplifierPlacements, type AnchorTransformEvent, type AntitankDitchOptions, type AntitankWallOptions, type AreaDefenseOptions, type AreaOfOperationsOptions, type AssemblyAreaOptions, type AttackByFireOptions, type AttackHelicopterOptions, type BaselineFrame, type BaselineFrameNormal, type BaselineFrameOptions, type BaselineFrameOrigin, type BattleHandoverLineOptions, type BattlePositionOptions, type BlockArrowHeadStyle, type BlockArrowOptions, type BlockMissionTaskOptions, type BlockOptions, type BoundaryOptions, type BreachOptions, type BypassOptions, CONTROL_MEASURE_IDS, CONTROL_MEASURE_METADATA, type CanalizeOptions, type CanonicalTextAmplifiers, type ClassicArrowHeadStyle, type ClassicArrowOptions, type ClearOptions, type ControlMeasure, type ControlMeasureDrawRule, type ControlMeasureGeometry, type ControlMeasureGeometryType, type ControlMeasureId, type ControlMeasureKind, type ControlMeasureMetadata, type ControlMeasureRender, type ControlMeasureStyle, type CoverOptions, DEFAULT_AIRBORNE_ATTACK_OPTIONS, DEFAULT_AMBUSH_OPTIONS, DEFAULT_ANTITANK_DITCH_OPTIONS, DEFAULT_ANTITANK_WALL_OPTIONS, DEFAULT_AREA_DEFENSE_OPTIONS, DEFAULT_AREA_OF_OPERATIONS_OPTIONS, DEFAULT_ASSEMBLY_AREA_OPTIONS, DEFAULT_ATTACK_BY_FIRE_OPTIONS, DEFAULT_ATTACK_HELICOPTER_OPTIONS, DEFAULT_BATTLE_HANDOVER_LINE_OPTIONS, DEFAULT_BATTLE_POSITION_OPTIONS, DEFAULT_BLOCK_ARROW_OPTIONS, DEFAULT_BLOCK_MISSION_TASK_OPTIONS, DEFAULT_BLOCK_OPTIONS, DEFAULT_BOUNDARY_OPTIONS, DEFAULT_BREACH_OPTIONS, DEFAULT_BYPASS_OPTIONS, DEFAULT_CANALIZE_OPTIONS, DEFAULT_CLASSIC_ARROW_OPTIONS, DEFAULT_CLEAR_OPTIONS, DEFAULT_COVER_OPTIONS, DEFAULT_DELAY_OPTIONS, DEFAULT_DIRECTION_OF_ATTACK_AVIATION_OPTIONS, DEFAULT_DIRECTION_OF_MAIN_ATTACK_OPTIONS, DEFAULT_DIRECTION_OF_SUPPORTING_ATTACK_OPTIONS, DEFAULT_DISRUPT_OPTIONS, DEFAULT_ENCIRCLEMENT_OPTIONS, DEFAULT_ENGINEER_WORK_LINE_OPTIONS, DEFAULT_FINAL_PROTECTIVE_FIRE_OPTIONS, DEFAULT_FIX_OPTIONS, DEFAULT_FLOT_OPTIONS, DEFAULT_FORTIFIED_AREA_OPTIONS, DEFAULT_FORTIFIED_LINE_OPTIONS, DEFAULT_FRONTAL_ATTACK_OPTIONS, DEFAULT_GENERIC_C2_LINE_OPTIONS, DEFAULT_GENERIC_CIRCLE_OPTIONS, DEFAULT_GENERIC_LINE_OPTIONS, DEFAULT_GENERIC_POLYGON_OPTIONS, DEFAULT_GENERIC_RECTANGLE_OPTIONS, DEFAULT_GUARD_OPTIONS, DEFAULT_HANDOVER_LINE_OPTIONS, DEFAULT_JOINT_TACTICAL_ACTION_AREA_OPTIONS, DEFAULT_LANDING_ZONE_OPTIONS, DEFAULT_LIGHT_LINE_OPTIONS, DEFAULT_MAIN_ATTACK_OPTIONS, DEFAULT_OBSTACLE_BYPASS_DIFFICULT_OPTIONS, DEFAULT_OBSTACLE_BYPASS_EASY_OPTIONS, DEFAULT_OBSTACLE_BYPASS_IMPOSSIBLE_OPTIONS, DEFAULT_PHASE_LINE_OPTIONS, DEFAULT_PICKUP_ZONE_OPTIONS, DEFAULT_PRINCIPAL_DIRECTION_OF_FIRE_OPTIONS, DEFAULT_SCREEN_OPTIONS, DEFAULT_STRONG_POINT_OPTIONS, DEFAULT_SUPPORTING_ATTACK_OPTIONS, DEFAULT_SUPPORT_BY_FIRE_OPTIONS, DEFAULT_TACTICAL_ARROW_OPTIONS, DEFAULT_TURNING_MOVEMENT_OPTIONS, DEFAULT_TURN_OPTIONS, type DelayOptions, type DirectionOfAttackAviationOptions, type DirectionOfMainAttackOptions, type DirectionOfSupportingAttackOptions, type DisruptOptions, EPSILON, type EncirclementOptions, type EngineerWorkLineOptions, type FLOTOptions, type FeaturePartProps, type FinalProtectiveFireOptions, type FixOptions, type FortifiedAreaOptions, type FortifiedLineOptions, type FrontalAttackOptions, type GeneratedLabelKey, type GenericC2LineOptions, type GenericCircleOptions, type GenericLineOptions, type GenericPolygonOptions, type GenericRectangleOptions, type GuardOptions, type HandoverLineOptions, type JointTacticalActionAreaOptions, type LabelPlacementOverride, type LabelSizeOptions, type LandingZoneOptions, type LightLineOptions, type MainAttackOptions, type MidpointPerpendicularDrawRuleOptions, type ObstacleBypassDifficultOptions, type ObstacleBypassEasyOptions, type ObstacleBypassImpossibleOptions, type OptionsByKind, type ParamDescriptor, type PhaseLineOptions, type PickupZoneOptions, type Point2D, type PrincipalDirectionOfFireOptions, type RenderOptions, type ScreenOptions, type SimpleStyleProps, type SimpleStyleRender, type StrongPointOptions, type StyleHints, type SupportByFireOptions, type SupportingAttackOptions, TEXT_AMPLIFIER_FIELDS, type TacticalArrowOptions, type TextAmplifierDescriptor, type TextAmplifierField, type TextAmplifierKey, type TextAmplifiers, type TurnOptions, type TurningMovementOptions, ambushDrawRule, attackByFireDrawRule, axis1DrawRule, blockDrawRule, calculateMetrics, canonicalTextAmplifierKey, centerRadiusDrawRule, cloneControlMeasure, computeDefaultMidpointPerpendicularPoint, computeInitialWidthPoint, controlMeasureIdFromFeature, createBaselineFrame, createMidpointPerpendicularDrawRule, disruptDrawRule, freezeOrientationOptions, getControlMeasureMetadata, getControlMeasureMetadataByValue, getDefaultOptions, getMetersPerPixel, getMidpointPerpendicularSignedDistance, isKind, line1DrawRule, line23DrawRule, line24DrawRule, line26DrawRule, listControlMeasureMetadata, normalizeTextAmplifiers, point12DrawRule, pointOnMidpointPerpendicularAxis, project, rectangleDrawRule, renderControlMeasure, resolveAmplifierPlacement, resolveStyleHints, roundToFixed, snapToMidpointPerpendicular, supportByFireDrawRule, toSimpleStyle, turnDrawRule, unproject };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as DEFAULT_BLOCK_ARROW_OPTIONS, A as DEFAULT_FLOT_OPTIONS, At as disruptDrawRule, B as DEFAULT_GENERIC_RECTANGLE_OPTIONS, Bt as project, C as DEFAULT_SCREEN_OPTIONS, Ct as line23DrawRule, D as DEFAULT_BATTLE_HANDOVER_LINE_OPTIONS, Dt as attackByFireDrawRule, E as DEFAULT_FORTIFIED_LINE_OPTIONS, Et as ambushDrawRule, F as DEFAULT_GUARD_OPTIONS, Ft as pointOnMidpointPerpendicularAxis, G as DEFAULT_CANALIZE_OPTIONS, H as DEFAULT_GENERIC_LINE_OPTIONS, Ht as EPSILON, I as DEFAULT_DELAY_OPTIONS, It as snapToMidpointPerpendicular, J as DEFAULT_BLOCK_MISSION_TASK_OPTIONS, K as DEFAULT_BYPASS_OPTIONS, L as DEFAULT_COVER_OPTIONS, Lt as createBaselineFrame, M as DEFAULT_FINAL_PROTECTIVE_FIRE_OPTIONS, Mt as computeDefaultMidpointPerpendicularPoint, N as DEFAULT_ENCIRCLEMENT_OPTIONS, Nt as createMidpointPerpendicularDrawRule, O as DEFAULT_HANDOVER_LINE_OPTIONS, Ot as point12DrawRule, P as DEFAULT_DISRUPT_OPTIONS, Pt as getMidpointPerpendicularSignedDistance, Q as DEFAULT_BOUNDARY_OPTIONS, R as DEFAULT_TACTICAL_ARROW_OPTIONS, Rt as calculateMetrics, S as DEFAULT_JOINT_TACTICAL_ACTION_AREA_OPTIONS, St as line24DrawRule, T as DEFAULT_FORTIFIED_AREA_OPTIONS, Tt as line1DrawRule, U as DEFAULT_GENERIC_CIRCLE_OPTIONS, Ut as getMetersPerPixel, V as DEFAULT_GENERIC_POLYGON_OPTIONS, Vt as unproject, W as DEFAULT_CLASSIC_ARROW_OPTIONS, Wt as roundToFixed, X as DEFAULT_ENGINEER_WORK_LINE_OPTIONS, Y as DEFAULT_GENERIC_C2_LINE_OPTIONS, Z as DEFAULT_LIGHT_LINE_OPTIONS, _ as DEFAULT_OBSTACLE_BYPASS_IMPOSSIBLE_OPTIONS, _t as DEFAULT_AIRBORNE_ATTACK_OPTIONS, a as DEFINITIONS, at as DEFAULT_ANTITANK_DITCH_OPTIONS, b as DEFAULT_MAIN_ATTACK_OPTIONS, bt as supportByFireDrawRule, c as getDefaultOptions, ct as DEFAULT_AREA_DEFENSE_OPTIONS, d as DEFAULT_TURN_OPTIONS, dt as normalizeTextAmplifiers, et as DEFAULT_BLOCK_OPTIONS, f as DEFAULT_SUPPORTING_ATTACK_OPTIONS, ft as resolveAmplifierPlacement, g as DEFAULT_PICKUP_ZONE_OPTIONS, gt as DEFAULT_AMBUSH_OPTIONS, h as DEFAULT_PRINCIPAL_DIRECTION_OF_FIRE_OPTIONS, ht as DEFAULT_DIRECTION_OF_ATTACK_AVIATION_OPTIONS, i as CONTROL_MEASURE_METADATA, it as DEFAULT_ANTITANK_WALL_OPTIONS, j as DEFAULT_FIX_OPTIONS, jt as blockDrawRule, k as DEFAULT_PHASE_LINE_OPTIONS, kt as centerRadiusDrawRule, l as listControlMeasureMetadata, lt as TEXT_AMPLIFIER_FIELDS, m as DEFAULT_STRONG_POINT_OPTIONS, mt as DEFAULT_DIRECTION_OF_MAIN_ATTACK_OPTIONS, n as resolveStyleHints, nt as DEFAULT_ATTACK_HELICOPTER_OPTIONS, o as getControlMeasureMetadata, ot as DEFAULT_ASSEMBLY_AREA_OPTIONS, p as DEFAULT_SUPPORT_BY_FIRE_OPTIONS, pt as DEFAULT_DIRECTION_OF_SUPPORTING_ATTACK_OPTIONS, q as DEFAULT_BREACH_OPTIONS, r as CONTROL_MEASURE_IDS, rt as DEFAULT_ATTACK_BY_FIRE_OPTIONS, s as getControlMeasureMetadataByValue, st as DEFAULT_AREA_OF_OPERATIONS_OPTIONS, t as renderControlMeasure, tt as DEFAULT_BATTLE_POSITION_OPTIONS, u as DEFAULT_TURNING_MOVEMENT_OPTIONS, ut as canonicalTextAmplifierKey, v as DEFAULT_OBSTACLE_BYPASS_EASY_OPTIONS, vt as rectangleDrawRule, w as DEFAULT_FRONTAL_ATTACK_OPTIONS, wt as turnDrawRule, x as DEFAULT_LANDING_ZONE_OPTIONS, xt as line26DrawRule, y as DEFAULT_OBSTACLE_BYPASS_DIFFICULT_OPTIONS, yt as axis1DrawRule, z as DEFAULT_CLEAR_OPTIONS, zt as computeInitialWidthPoint } from "./renderControlMeasure-BVbkVj2S.mjs";
1
+ import { $ as DEFAULT_BLOCK_ARROW_OPTIONS, A as DEFAULT_FLOT_OPTIONS, At as disruptDrawRule, B as DEFAULT_GENERIC_RECTANGLE_OPTIONS, Bt as project, C as DEFAULT_SCREEN_OPTIONS, Ct as line23DrawRule, D as DEFAULT_BATTLE_HANDOVER_LINE_OPTIONS, Dt as attackByFireDrawRule, E as DEFAULT_FORTIFIED_LINE_OPTIONS, Et as ambushDrawRule, F as DEFAULT_GUARD_OPTIONS, Ft as pointOnMidpointPerpendicularAxis, G as DEFAULT_CANALIZE_OPTIONS, H as DEFAULT_GENERIC_LINE_OPTIONS, Ht as EPSILON, I as DEFAULT_DELAY_OPTIONS, It as snapToMidpointPerpendicular, J as DEFAULT_BLOCK_MISSION_TASK_OPTIONS, K as DEFAULT_BYPASS_OPTIONS, L as DEFAULT_COVER_OPTIONS, Lt as createBaselineFrame, M as DEFAULT_FINAL_PROTECTIVE_FIRE_OPTIONS, Mt as computeDefaultMidpointPerpendicularPoint, N as DEFAULT_ENCIRCLEMENT_OPTIONS, Nt as createMidpointPerpendicularDrawRule, O as DEFAULT_HANDOVER_LINE_OPTIONS, Ot as point12DrawRule, P as DEFAULT_DISRUPT_OPTIONS, Pt as getMidpointPerpendicularSignedDistance, Q as DEFAULT_BOUNDARY_OPTIONS, R as DEFAULT_TACTICAL_ARROW_OPTIONS, Rt as calculateMetrics, S as DEFAULT_JOINT_TACTICAL_ACTION_AREA_OPTIONS, St as line24DrawRule, T as DEFAULT_FORTIFIED_AREA_OPTIONS, Tt as line1DrawRule, U as DEFAULT_GENERIC_CIRCLE_OPTIONS, Ut as getMetersPerPixel, V as DEFAULT_GENERIC_POLYGON_OPTIONS, Vt as unproject, W as DEFAULT_CLASSIC_ARROW_OPTIONS, Wt as roundToFixed, X as DEFAULT_ENGINEER_WORK_LINE_OPTIONS, Y as DEFAULT_GENERIC_C2_LINE_OPTIONS, Z as DEFAULT_LIGHT_LINE_OPTIONS, _ as DEFAULT_OBSTACLE_BYPASS_IMPOSSIBLE_OPTIONS, _t as DEFAULT_AIRBORNE_ATTACK_OPTIONS, a as DEFINITIONS, at as DEFAULT_ANTITANK_DITCH_OPTIONS, b as DEFAULT_MAIN_ATTACK_OPTIONS, bt as supportByFireDrawRule, c as getDefaultOptions, ct as DEFAULT_AREA_DEFENSE_OPTIONS, d as DEFAULT_TURN_OPTIONS, dt as normalizeTextAmplifiers, et as DEFAULT_BLOCK_OPTIONS, f as DEFAULT_SUPPORTING_ATTACK_OPTIONS, ft as resolveAmplifierPlacement, g as DEFAULT_PICKUP_ZONE_OPTIONS, gt as DEFAULT_AMBUSH_OPTIONS, h as DEFAULT_PRINCIPAL_DIRECTION_OF_FIRE_OPTIONS, ht as DEFAULT_DIRECTION_OF_ATTACK_AVIATION_OPTIONS, i as CONTROL_MEASURE_METADATA, it as DEFAULT_ANTITANK_WALL_OPTIONS, j as DEFAULT_FIX_OPTIONS, jt as blockDrawRule, k as DEFAULT_PHASE_LINE_OPTIONS, kt as centerRadiusDrawRule, l as listControlMeasureMetadata, lt as TEXT_AMPLIFIER_FIELDS, m as DEFAULT_STRONG_POINT_OPTIONS, mt as DEFAULT_DIRECTION_OF_MAIN_ATTACK_OPTIONS, n as resolveStyleHints, nt as DEFAULT_ATTACK_HELICOPTER_OPTIONS, o as getControlMeasureMetadata, ot as DEFAULT_ASSEMBLY_AREA_OPTIONS, p as DEFAULT_SUPPORT_BY_FIRE_OPTIONS, pt as DEFAULT_DIRECTION_OF_SUPPORTING_ATTACK_OPTIONS, q as DEFAULT_BREACH_OPTIONS, r as CONTROL_MEASURE_IDS, rt as DEFAULT_ATTACK_BY_FIRE_OPTIONS, s as getControlMeasureMetadataByValue, st as DEFAULT_AREA_OF_OPERATIONS_OPTIONS, t as renderControlMeasure, tt as DEFAULT_BATTLE_POSITION_OPTIONS, u as DEFAULT_TURNING_MOVEMENT_OPTIONS, ut as canonicalTextAmplifierKey, v as DEFAULT_OBSTACLE_BYPASS_EASY_OPTIONS, vt as rectangleDrawRule, w as DEFAULT_FRONTAL_ATTACK_OPTIONS, wt as turnDrawRule, x as DEFAULT_LANDING_ZONE_OPTIONS, xt as line26DrawRule, y as DEFAULT_OBSTACLE_BYPASS_DIFFICULT_OPTIONS, yt as axis1DrawRule, z as DEFAULT_CLEAR_OPTIONS, zt as computeInitialWidthPoint } from "./renderControlMeasure-1e_k10Kq.mjs";
2
2
  //#region src/freeze-orientation.ts
3
3
  /**
4
4
  * Dispatches to a measure kind's `freezeOrientation` hook (see
@@ -1,4 +1,4 @@
1
- import { J as ControlMeasureId, Or as TextAmplifiers } from "../index-9CY6UM92.mjs";
1
+ import { J as ControlMeasureId, Or as TextAmplifiers } from "../index-BJwZXCKb.mjs";
2
2
  import { FeatureCollection, Geometry } from "geojson";
3
3
 
4
4
  //#region src/preview/index.d.ts
@@ -1,4 +1,4 @@
1
- import { a as DEFINITIONS, t as renderControlMeasure } from "../renderControlMeasure-BVbkVj2S.mjs";
1
+ import { a as DEFINITIONS, t as renderControlMeasure } from "../renderControlMeasure-1e_k10Kq.mjs";
2
2
  //#region src/preview/index.ts
3
3
  /**
4
4
  * Unitless `previewSample` points (~`[-1, 1]`) are scaled by this degree offset
@@ -273,6 +273,7 @@ function calculateSymbolGeometry(ptTip, ptWidth, spine, shaftRatio, smooth, smoo
273
273
  ];
274
274
  const fullSpine = [...remainingSpine, shaftEndCenter];
275
275
  return {
276
+ shaftCenterline: fullSpine,
276
277
  shaftLeft: offsetPolyline(fullSpine, shaftHalfWidth, smooth, smoothResolution),
277
278
  shaftRight: offsetPolyline(fullSpine, -shaftHalfWidth, smooth, smoothResolution),
278
279
  headRing,
@@ -2124,6 +2125,24 @@ function endTangents(verts) {
2124
2125
  }
2125
2126
  };
2126
2127
  }
2128
+ /** Field N descriptor shared by Line1 graphics that opt into hostile endpoint markers. */
2129
+ const HOSTILE_LINE_AMPLIFIER = {
2130
+ key: "N",
2131
+ label: "Hostile (ENY) marker",
2132
+ description: "Field N — literal \"ENY\" marker beyond both ends of the line.",
2133
+ placeholder: "ENY",
2134
+ maxLength: 3
2135
+ };
2136
+ /** Label-padding control shared by newly label-bearing Line1 graphics. */
2137
+ const LINE_LABEL_PADDING_PARAM = {
2138
+ key: "labelPadding",
2139
+ label: "Label padding",
2140
+ description: "Extra clearance between the line and its labels, as a ratio of the label height",
2141
+ type: "number",
2142
+ min: 0,
2143
+ max: 2,
2144
+ step: .05
2145
+ };
2127
2146
  /**
2128
2147
  * Resolves {@link LabelSizeOptions} into the single text-size property the
2129
2148
  * label feature carries (ADR-0028): `textSizePixels` when screen-anchored
@@ -2209,6 +2228,41 @@ function pushEndLabels(out, verts, textForEnd, options = {}) {
2209
2228
  }
2210
2229
  }
2211
2230
  /**
2231
+ * Approximate sans-serif character width/height ratio, used to estimate a
2232
+ * label's rendered text width from its character count when no live text
2233
+ * measurement is available at generator time.
2234
+ */
2235
+ const LABEL_CHAR_ASPECT_RATIO = .6;
2236
+ /** Estimated rendered width of `text` given its resolved label height in meters. */
2237
+ function estimatedTextWidth(text, textHeightMeters) {
2238
+ return text.length * LABEL_CHAR_ASPECT_RATIO * textHeightMeters;
2239
+ }
2240
+ /** The end-label text for a phase-line naming, or `""` when unnamed. */
2241
+ function phaseLineLabelText(name, includePrefix) {
2242
+ if (!name) return "";
2243
+ return includePrefix ? `PL ${name}` : name;
2244
+ }
2245
+ /**
2246
+ * Places a Field N hostile marker beyond each end of `verts`, following the
2247
+ * same tangent, rotation, anchor, size, and padding conventions as
2248
+ * {@link pushEndLabels}. When another beyond-tip label is present, its
2249
+ * estimated width is added so the hostile marker remains outermost.
2250
+ */
2251
+ function pushHostileEndLabels(out, verts, hostileText, options = {}, occupiedTextForEnd = "") {
2252
+ if (!hostileText) return;
2253
+ const frames = endFrames(verts);
2254
+ const sizeProps = labelSizeProps(options);
2255
+ const textHeight = resolveLabelOffsetMeters(options, 1);
2256
+ const clearance = resolveLabelOffsetMeters(options, END_LABEL_CLEARANCE_RATIO + Math.max(0, options.labelPadding ?? 0));
2257
+ for (const end of ["start", "end"]) {
2258
+ const frame = frames[end];
2259
+ if (!frame) continue;
2260
+ const occupiedText = typeof occupiedTextForEnd === "string" ? occupiedTextForEnd : occupiedTextForEnd(end);
2261
+ const distance = clearance + (occupiedText ? estimatedTextWidth(occupiedText, textHeight) + clearance : 0);
2262
+ pushLabel(out, vecAdd(frame.point, vecScale(frame.along, distance)), hostileText, labelRotationAlong(frame.along), sizeProps, frame.along[0] >= 0 ? "start" : "end", "N", `label:N:${end}`);
2263
+ }
2264
+ }
2265
+ /**
2212
2266
  * Resolves the {@link AboveEndFrame} at each tip of `verts` (projected meters):
2213
2267
  * a base point inset from the tip toward the interior, the segment's own
2214
2268
  * natural `perp` (so "above" = `+perp` reads the same at both ends), the
@@ -2263,7 +2317,7 @@ function pushEndLabelsAbove(out, verts, textForEnd, options = {}) {
2263
2317
  * (via {@link pushEndLabels} or {@link pushEndLabelsAbove}, per `placement`)
2264
2318
  * as a complete FeatureCollection.
2265
2319
  */
2266
- function fixedLabelLineFeatures(positions, options, textForEnd) {
2320
+ function fixedLabelLineFeatures(positions, options, textForEnd, hostileText) {
2267
2321
  const { part, placement = "beyond", phaseLineName, includePrefix = true, smooth, smoothResolution, ...sizeOptions } = options;
2268
2322
  const verts = smoothLineVerts(positions.map((p) => project(p[0], p[1])), {
2269
2323
  smooth,
@@ -2281,8 +2335,9 @@ function fixedLabelLineFeatures(positions, options, textForEnd) {
2281
2335
  const labelFeatures = [];
2282
2336
  if (placement === "above") pushEndLabelsAbove(labelFeatures, verts, textForEnd, sizeOptions);
2283
2337
  else pushEndLabels(labelFeatures, verts, textForEnd, sizeOptions);
2284
- const trimmedPhaseLineName = phaseLineName?.trim();
2285
- if (trimmedPhaseLineName) pushEndLabels(labelFeatures, verts, includePrefix ? `PL ${trimmedPhaseLineName}` : trimmedPhaseLineName, sizeOptions);
2338
+ const nameText = phaseLineLabelText(phaseLineName?.trim(), includePrefix);
2339
+ if (nameText) pushEndLabels(labelFeatures, verts, nameText, sizeOptions);
2340
+ pushHostileEndLabels(labelFeatures, verts, hostileText, sizeOptions, nameText || (placement === "beyond" ? textForEnd : ""));
2286
2341
  features.push(...labelFeatures);
2287
2342
  return {
2288
2343
  type: "FeatureCollection",
@@ -2294,8 +2349,6 @@ function fixedLabelLineFeatures(positions, options, textForEnd) {
2294
2349
  const DEFAULT_DIRECTION_ARROWHEAD_LENGTH_RATIO = .12;
2295
2350
  const DEFAULT_DIRECTION_ARROWHEAD_WIDTH_RATIO = 1.15;
2296
2351
  const DEFAULT_DIRECTION_LABEL_POSITION = .5;
2297
- /** Approximate sans-serif character width/height ratio used for the T knockout. */
2298
- const LABEL_CHAR_ASPECT_RATIO = .6;
2299
2352
  /** Clear space on each side of T, as a ratio of the resolved label height. */
2300
2353
  const LABEL_GAP_CLEARANCE_RATIO = .3;
2301
2354
  function nonNegative$1(value, fallback) {
@@ -2305,7 +2358,7 @@ function nonNegative$1(value, fallback) {
2305
2358
  function directionLabelGap(totalLength, fraction, text, options) {
2306
2359
  if (!text) return void 0;
2307
2360
  const textHeight = resolveLabelOffsetMeters(options, 1);
2308
- const textWidth = text.length * LABEL_CHAR_ASPECT_RATIO * textHeight;
2361
+ const textWidth = estimatedTextWidth(text, textHeight);
2309
2362
  const clearance = (LABEL_GAP_CLEARANCE_RATIO + Math.max(0, options.labelPadding ?? 0)) * textHeight;
2310
2363
  const halfGap = textWidth / 2 + clearance;
2311
2364
  const center = fraction * totalLength;
@@ -3086,11 +3139,6 @@ const ROW_PITCH_RATIO = 1.2;
3086
3139
  * ENY marker's own half-width/half-height when carving its boundary
3087
3140
  * knockout gap (see {@link pushAreaLabels}). */
3088
3141
  const ENY_CLEARANCE_RATIO = .3;
3089
- /** Approximate sans-serif character aspect ratio (width/height) used to
3090
- * estimate an ENY marker's rendered text width from its character count,
3091
- * for sizing the boundary knockout gap — no live text-measurement is
3092
- * available at generator time. */
3093
- const ENY_CHAR_ASPECT_RATIO = .6;
3094
3142
  const DEFAULT_SMOOTH_RESOLUTION$9 = 16;
3095
3143
  /**
3096
3144
  * Composes an area's main label text from a `prefix` (e.g. `"AO"`) and a
@@ -3249,7 +3297,7 @@ function labelHalfExtent(direction, textWidth, textHeight, rotation) {
3249
3297
  * its width):
3250
3298
  * `halfExtent = 0.5 * (textWidth·|d.x| + textHeight·|d.y|) + clearance`, where
3251
3299
  * `textHeight = resolveLabelOffsetMeters(options, 1)`,
3252
- * `textWidth = text.length * ENY_CHAR_ASPECT_RATIO * textHeight`, and
3300
+ * `textWidth = text.length * LABEL_CHAR_ASPECT_RATIO * textHeight`, and
3253
3301
  * `clearance = (ENY_CLEARANCE_RATIO + labelPadding) * textHeight` — mirroring
3254
3302
  * {@link placeEchelon}'s glyph-width masking gap (ADR-0023). Fewer than two
3255
3303
  * crossings (e.g. a degenerate ring, or a concave shape whose centroid
@@ -3257,11 +3305,17 @@ function labelHalfExtent(direction, textWidth, textHeight, rotation) {
3257
3305
  * guessing their default placements.
3258
3306
  *
3259
3307
  * A ring with fewer than 2 vertices is degenerate — nothing is placed.
3308
+ *
3309
+ * `maskBoundary` may supply a different rendered boundary to mask while
3310
+ * keeping label placement anchored to `ringVerts`. This is used by Fortified
3311
+ * Area, whose labels are centered against its smooth pre-teeth ring while the
3312
+ * visible boundary follows a longer castellated trace.
3260
3313
  */
3261
- function pushAreaLabels(out, ringVerts, texts, options = {}, amplifierPlacements) {
3314
+ function pushAreaLabels(out, ringVerts, texts, options = {}, amplifierPlacements, maskBoundary) {
3262
3315
  if (ringVerts.length < 2) return [];
3263
3316
  const sizeProps = labelSizeProps(options);
3264
3317
  const padding = Math.max(0, options.labelPadding ?? 0);
3318
+ const maskRingVerts = maskBoundary?.ringVerts ?? ringVerts;
3265
3319
  const [cx, cy] = ringCentroid(ringVerts);
3266
3320
  const rows = [
3267
3321
  [texts.name, "T"],
@@ -3284,15 +3338,16 @@ function pushAreaLabels(out, ringVerts, texts, options = {}, amplifierPlacements
3284
3338
  if (crossings.length >= 2) {
3285
3339
  const byX = [...crossings].sort((a, b) => a.x - b.x);
3286
3340
  const textHeight = resolveLabelOffsetMeters(options, 1);
3287
- const textWidth = texts.hostile.length * ENY_CHAR_ASPECT_RATIO * textHeight;
3288
- const clearance = (ENY_CLEARANCE_RATIO + padding) * textHeight;
3341
+ const textWidth = estimatedTextWidth(texts.hostile, textHeight);
3342
+ const clearance = (ENY_CLEARANCE_RATIO + padding) * textHeight + Math.max(0, maskBoundary?.extraClearanceMeters ?? 0);
3289
3343
  for (const [side, crossing] of [["west", byX[0]], ["east", byX[byX.length - 1]]]) {
3290
3344
  const labelPlacementKey = `label:N:${side}`;
3291
3345
  const placement = resolveAmplifierPlacement(amplifierPlacements?.[labelPlacementKey]);
3292
3346
  const rotation = placement.rotation ?? HORIZONTAL_LABEL_ROTATION;
3293
3347
  const labelPoint = placement.position ? project(placement.position[0], placement.position[1]) : [crossing.x, cy];
3294
3348
  pushLabel(out, labelPoint, texts.hostile, HORIZONTAL_LABEL_ROTATION, sizeProps, void 0, "N", labelPlacementKey);
3295
- const contact = placement.position ? nearestPointOnRing(ringVerts, labelPoint) : {
3349
+ const usesDifferentMaskRing = maskRingVerts !== ringVerts;
3350
+ const contact = placement.position || usesDifferentMaskRing ? nearestPointOnRing(maskRingVerts, labelPoint) : {
3296
3351
  along: crossing.along,
3297
3352
  arc: crossing.arc,
3298
3353
  distance: 0,
@@ -3300,7 +3355,7 @@ function pushAreaLabels(out, ringVerts, texts, options = {}, amplifierPlacements
3300
3355
  };
3301
3356
  if (!contact) continue;
3302
3357
  const normal = [-contact.along[1], contact.along[0]];
3303
- if (contact.distance > labelHalfExtent(normal, textWidth, textHeight, rotation)) continue;
3358
+ if (placement.position && contact.distance > labelHalfExtent(normal, textWidth, textHeight, rotation)) continue;
3304
3359
  const tangentExtent = labelHalfExtent(contact.along, textWidth, textHeight, rotation) + clearance;
3305
3360
  pushWrappedGap(gaps, contact.arc - tangentExtent, contact.arc + tangentExtent, contact.totalArc);
3306
3361
  }
@@ -4184,7 +4239,8 @@ const FORTIFIED_PARAMS = [
4184
4239
  max: 500,
4185
4240
  step: 1,
4186
4241
  unit: "m"
4187
- }
4242
+ },
4243
+ LINE_LABEL_PADDING_PARAM
4188
4244
  ];
4189
4245
  const ANTITANK_DITCH_PARAMS = [
4190
4246
  ...SMOOTH_LINE_PARAMS,
@@ -4217,7 +4273,8 @@ const ANTITANK_DITCH_PARAMS = [
4217
4273
  max: 3,
4218
4274
  step: .05,
4219
4275
  unit: "x"
4220
- }
4276
+ },
4277
+ LINE_LABEL_PADDING_PARAM
4221
4278
  ];
4222
4279
  const ANTITANK_WALL_PARAMS = [...ANTITANK_DITCH_PARAMS, {
4223
4280
  key: "toothSpacingRatio",
@@ -4251,9 +4308,11 @@ const ANTITANK_DITCH_UNDER_CONSTRUCTION_METADATA = {
4251
4308
  value: "290201",
4252
4309
  minCoordinates: 2,
4253
4310
  geometry: "line",
4254
- geometryTypes: ["MultiLineString"],
4311
+ geometryTypes: ["MultiLineString", "Point"],
4255
4312
  drawRule: "Line1",
4256
- params: ANTITANK_DITCH_PARAMS
4313
+ capturesLabelSize: true,
4314
+ params: ANTITANK_DITCH_PARAMS,
4315
+ textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
4257
4316
  };
4258
4317
  const ANTITANK_DITCH_COMPLETED_METADATA = {
4259
4318
  id: "antitank-ditch-completed",
@@ -4265,9 +4324,11 @@ const ANTITANK_DITCH_COMPLETED_METADATA = {
4265
4324
  value: "290202",
4266
4325
  minCoordinates: 2,
4267
4326
  geometry: "line",
4268
- geometryTypes: ["MultiPolygon"],
4327
+ geometryTypes: ["MultiPolygon", "Point"],
4269
4328
  drawRule: "Line1",
4270
- params: ANTITANK_DITCH_PARAMS
4329
+ capturesLabelSize: true,
4330
+ params: ANTITANK_DITCH_PARAMS,
4331
+ textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
4271
4332
  };
4272
4333
  function calculateAntitankDitchHeight(options) {
4273
4334
  const { toothHeight = DEFAULT_TOOTH_HEIGHT, toothHeightPixels, metersPerPixel } = options;
@@ -4316,9 +4377,13 @@ function smoothedDitchBasePath(positions, options) {
4316
4377
  function buildAntitankDitchTriangles(basePath, options) {
4317
4378
  return generateAntitankDitchTriangles(basePath, calculateAntitankDitchHeight(options), options.toothWidthRatio ?? DEFAULT_TOOTH_WIDTH_RATIO$1);
4318
4379
  }
4319
- function createAntitankDitchUnderConstruction(positions, options = {}) {
4380
+ function createAntitankDitchUnderConstruction(positions, options = {}, textAmplifiers = {}) {
4320
4381
  const basePath = smoothedDitchBasePath(positions, options);
4321
4382
  const triangles = buildAntitankDitchTriangles(basePath, options);
4383
+ const baseLine = options.smooth ? basePath.map((point) => unproject(point[0], point[1])) : positions.map((position) => [position[0], position[1]]);
4384
+ const triangleLines = triangles.map((triangle) => triangle.map((point) => unproject(point[0], point[1])));
4385
+ const labels = [];
4386
+ pushHostileEndLabels(labels, basePath, textAmplifiers.N, options);
4322
4387
  return {
4323
4388
  type: "FeatureCollection",
4324
4389
  features: [{
@@ -4326,12 +4391,18 @@ function createAntitankDitchUnderConstruction(positions, options = {}) {
4326
4391
  properties: { part: "ditch" },
4327
4392
  geometry: {
4328
4393
  type: "MultiLineString",
4329
- coordinates: [options.smooth ? basePath.map((point) => unproject(point[0], point[1])) : positions.map((position) => [position[0], position[1]]), ...triangles.map((triangle) => triangle.map((point) => unproject(point[0], point[1])))]
4394
+ coordinates: [baseLine, ...triangleLines]
4330
4395
  }
4331
- }]
4396
+ }, ...labels]
4332
4397
  };
4333
4398
  }
4334
- function createAntitankDitchCompleted(positions, options = {}) {
4399
+ function createAntitankDitchCompleted(positions, options = {}, textAmplifiers = {}) {
4400
+ const basePath = smoothedDitchBasePath(positions, options);
4401
+ const polygons = buildAntitankDitchTriangles(basePath, options).map((triangle) => {
4402
+ return [[...triangle, triangle[0]].map((point) => unproject(point[0], point[1]))];
4403
+ });
4404
+ const labels = [];
4405
+ pushHostileEndLabels(labels, basePath, textAmplifiers.N, options);
4335
4406
  return {
4336
4407
  type: "FeatureCollection",
4337
4408
  features: [{
@@ -4342,11 +4413,9 @@ function createAntitankDitchCompleted(positions, options = {}) {
4342
4413
  },
4343
4414
  geometry: {
4344
4415
  type: "MultiPolygon",
4345
- coordinates: buildAntitankDitchTriangles(smoothedDitchBasePath(positions, options), options).map((triangle) => {
4346
- return [[...triangle, triangle[0]].map((point) => unproject(point[0], point[1]))];
4347
- })
4416
+ coordinates: polygons
4348
4417
  }
4349
- }]
4418
+ }, ...labels]
4350
4419
  };
4351
4420
  }
4352
4421
  const ANTITANK_DITCH_UNDER_CONSTRUCTION = defineControlMeasure({
@@ -4380,9 +4449,11 @@ const ANTITANK_WALL_METADATA = {
4380
4449
  value: "290204",
4381
4450
  minCoordinates: 2,
4382
4451
  geometry: "line",
4383
- geometryTypes: ["MultiLineString"],
4452
+ geometryTypes: ["MultiLineString", "Point"],
4384
4453
  drawRule: "Line1",
4385
- params: ANTITANK_WALL_PARAMS
4454
+ capturesLabelSize: true,
4455
+ params: ANTITANK_WALL_PARAMS,
4456
+ textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
4386
4457
  };
4387
4458
  function generateAntitankWallPoints(projectedPoints, toothHeight, toothWidthRatio = DEFAULT_TOOTH_WIDTH_RATIO, toothSpacingRatio = DEFAULT_TOOTH_SPACING_RATIO) {
4388
4459
  if (projectedPoints.length < 2) return projectedPoints;
@@ -4415,12 +4486,16 @@ function generateAntitankWallPoints(projectedPoints, toothHeight, toothWidthRati
4415
4486
  }
4416
4487
  return wallPoints;
4417
4488
  }
4418
- function createAntitankWall(positions, options = {}) {
4489
+ function createAntitankWall(positions, options = {}, textAmplifiers = {}) {
4419
4490
  const toothHeight = calculateAntitankDitchHeight(options);
4420
4491
  const toothWidthRatio = options.toothWidthRatio ?? DEFAULT_TOOTH_WIDTH_RATIO;
4421
4492
  const toothSpacingRatio = options.toothSpacingRatio ?? DEFAULT_TOOTH_SPACING_RATIO;
4422
4493
  const projectedPoints = positions.map((position) => project(position[0], position[1]));
4423
4494
  const smoothedPath = smoothLineVerts(projectedPoints, options, 16);
4495
+ const basePath = options.smooth && projectedPoints.length >= 3 ? evenlySpacePath(smoothedPath, Math.max(EPSILON, toothWidthRatio) * toothHeight) : smoothedPath;
4496
+ const wallLine = generateAntitankWallPoints(basePath, toothHeight, toothWidthRatio, toothSpacingRatio).map((point) => unproject(point[0], point[1]));
4497
+ const labels = [];
4498
+ pushHostileEndLabels(labels, basePath, textAmplifiers.N, options);
4424
4499
  return {
4425
4500
  type: "FeatureCollection",
4426
4501
  features: [{
@@ -4428,9 +4503,9 @@ function createAntitankWall(positions, options = {}) {
4428
4503
  properties: { part: "wall" },
4429
4504
  geometry: {
4430
4505
  type: "MultiLineString",
4431
- coordinates: [generateAntitankWallPoints(options.smooth && projectedPoints.length >= 3 ? evenlySpacePath(smoothedPath, Math.max(EPSILON, toothWidthRatio) * toothHeight) : smoothedPath, toothHeight, toothWidthRatio, toothSpacingRatio).map((point) => unproject(point[0], point[1]))]
4506
+ coordinates: [wallLine]
4432
4507
  }
4433
- }]
4508
+ }, ...labels]
4434
4509
  };
4435
4510
  }
4436
4511
  const ANTITANK_WALL = defineControlMeasure({
@@ -5403,7 +5478,8 @@ const BOUNDARY_METADATA = {
5403
5478
  label: "Country (other side)",
5404
5479
  description: "Field AS1 — country code of the unit on the right-of-travel side.",
5405
5480
  maxLength: 8
5406
- }
5481
+ },
5482
+ HOSTILE_LINE_AMPLIFIER
5407
5483
  ]
5408
5484
  };
5409
5485
  /**
@@ -5428,7 +5504,8 @@ function createBoundary(positions, options = {}, textAmplifiers = {}) {
5428
5504
  const reps = resolveLabelRepetitions(labelRepetitions);
5429
5505
  const singleGroupPosition = clampLinePosition(labelPosition, DEFAULT_BOUNDARY_OPTIONS.labelPosition);
5430
5506
  const spec = resolveEchelon(echelon);
5431
- const labelSize = echelonSizePixels !== void 0 && metersPerPixel !== void 0 && metersPerPixel > 0 ? { textSizePixels: echelonSizePixels } : { textSizeMeters: h };
5507
+ const screenAnchored = echelonSizePixels !== void 0 && metersPerPixel !== void 0 && metersPerPixel > 0;
5508
+ const labelSize = screenAnchored ? { textSizePixels: echelonSizePixels } : { textSizeMeters: h };
5432
5509
  const rawVerts = positions.map((p) => project(p[0], p[1]));
5433
5510
  const verts = smooth ? catmullRom(rawVerts, normalizeSmoothResolution$2(smoothResolution, DEFAULT_SMOOTH_RESOLUTION$4)) : rawVerts;
5434
5511
  const glyphStrokes = [];
@@ -5491,6 +5568,15 @@ function createBoundary(positions, options = {}, textAmplifiers = {}) {
5491
5568
  labelSize
5492
5569
  }, units);
5493
5570
  }
5571
+ const hostileLabelOptions = screenAnchored ? {
5572
+ labelSizePixels: echelonSizePixels,
5573
+ metersPerPixel,
5574
+ labelPadding
5575
+ } : {
5576
+ labelSize: h,
5577
+ labelPadding
5578
+ };
5579
+ pushHostileEndLabels(labelFeatures, verts, textAmplifiers.N, hostileLabelOptions);
5494
5580
  const boundaryCoords = buildGappedLine(verts, gaps);
5495
5581
  const features = [];
5496
5582
  if (boundaryCoords.length > 0) features.push({
@@ -5590,18 +5676,19 @@ const LIGHT_LINE_METADATA = {
5590
5676
  max: 2,
5591
5677
  step: .05
5592
5678
  }
5593
- ]
5679
+ ],
5680
+ textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
5594
5681
  };
5595
5682
  /**
5596
5683
  * Creates a Light line control measure: a polyline through `positions`
5597
5684
  * labeled "LL" above the line near each end.
5598
5685
  */
5599
- function createLightLine(positions, options = {}) {
5686
+ function createLightLine(positions, options = {}, textAmplifiers = {}) {
5600
5687
  return fixedLabelLineFeatures(positions, {
5601
5688
  part: "light-line",
5602
5689
  placement: "above",
5603
5690
  ...options
5604
- }, "LL");
5691
+ }, "LL", textAmplifiers.N);
5605
5692
  }
5606
5693
  const LIGHT_LINE = defineControlMeasure({
5607
5694
  metadata: LIGHT_LINE_METADATA,
@@ -5685,7 +5772,8 @@ const ENGINEER_WORK_LINE_METADATA = {
5685
5772
  label: "Country (other side)",
5686
5773
  description: "Field AS1 — country code of the engineer unit on the right-of-travel side.",
5687
5774
  maxLength: 8
5688
- }
5775
+ },
5776
+ HOSTILE_LINE_AMPLIFIER
5689
5777
  ]
5690
5778
  };
5691
5779
  /** Ratio of resolved label height used as the center label group's perpendicular offset. */
@@ -5703,7 +5791,7 @@ function createEngineerWorkLine(positions, options = {}, textAmplifiers = {}) {
5703
5791
  part: "engineer-work-line",
5704
5792
  placement: "above",
5705
5793
  ...options
5706
- }, "EWL").features];
5794
+ }, "EWL", textAmplifiers.N).features];
5707
5795
  const { segments, totalLength } = polylineSegments(smoothLineVerts(positions.map((p) => project(p[0], p[1])), options, 12));
5708
5796
  const frame = pointAlongPolyline(segments, totalLength, .5);
5709
5797
  if (frame) {
@@ -5820,7 +5908,8 @@ const GENERIC_C2_LINE_METADATA = {
5820
5908
  label: "Effective to (DTG)",
5821
5909
  description: "Field W1 — date-time group the line ceases to be effective, shown below it near each end.",
5822
5910
  maxLength: 16
5823
- }
5911
+ },
5912
+ HOSTILE_LINE_AMPLIFIER
5824
5913
  ]
5825
5914
  };
5826
5915
  /**
@@ -5848,7 +5937,7 @@ function createGenericC2Line(positions, options = {}, textAmplifiers = {}) {
5848
5937
  const above = [textAmplifiers.H, textAmplifiers.T].filter((v) => Boolean(v)).join(" ");
5849
5938
  const below = [textAmplifiers.W, textAmplifiers.W1].filter((v) => Boolean(v)).join(" - ");
5850
5939
  const trimmedPhaseLineName = phaseLineName?.trim();
5851
- if (above.length > 0 || below.length > 0 || trimmedPhaseLineName) {
5940
+ if (above.length > 0 || below.length > 0 || trimmedPhaseLineName || textAmplifiers.N) {
5852
5941
  const labelFeatures = [];
5853
5942
  if (above.length > 0 || below.length > 0) {
5854
5943
  const frames = aboveEndFrames(verts, sizeOptions);
@@ -5861,7 +5950,9 @@ function createGenericC2Line(positions, options = {}, textAmplifiers = {}) {
5861
5950
  if (below.length > 0) pushLabel(labelFeatures, vecAdd(base, vecScale(perp, -offset)), below, rotation, sizeProps, textAnchor);
5862
5951
  }
5863
5952
  }
5864
- if (trimmedPhaseLineName) pushEndLabels(labelFeatures, verts, includePrefix ? `PL ${trimmedPhaseLineName}` : trimmedPhaseLineName, sizeOptions);
5953
+ const nameText = phaseLineLabelText(trimmedPhaseLineName, includePrefix);
5954
+ if (nameText) pushEndLabels(labelFeatures, verts, nameText, sizeOptions);
5955
+ pushHostileEndLabels(labelFeatures, verts, textAmplifiers.N, sizeOptions, nameText);
5865
5956
  features.push(...labelFeatures);
5866
5957
  }
5867
5958
  return {
@@ -8434,8 +8525,9 @@ const FLOT_METADATA = {
8434
8525
  value: "140100",
8435
8526
  minCoordinates: 2,
8436
8527
  geometry: "line",
8437
- geometryTypes: ["LineString"],
8528
+ geometryTypes: ["LineString", "Point"],
8438
8529
  drawRule: "Line1",
8530
+ capturesLabelSize: true,
8439
8531
  params: [
8440
8532
  ...SMOOTH_LINE_PARAMS,
8441
8533
  {
@@ -8458,6 +8550,7 @@ const FLOT_METADATA = {
8458
8550
  step: 1,
8459
8551
  unit: "m"
8460
8552
  },
8553
+ LINE_LABEL_PADDING_PARAM,
8461
8554
  {
8462
8555
  key: "arcSegments",
8463
8556
  label: "Arc segments",
@@ -8467,7 +8560,8 @@ const FLOT_METADATA = {
8467
8560
  max: 32,
8468
8561
  step: 2
8469
8562
  }
8470
- ]
8563
+ ],
8564
+ textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
8471
8565
  };
8472
8566
  /**
8473
8567
  * Generates points for a true semicircle arc along a segment.
@@ -8551,7 +8645,7 @@ function processSegment(p1, p2, radius, arcSegments, isFirstSegment) {
8551
8645
  * );
8552
8646
  * ```
8553
8647
  */
8554
- function createFLOT(positions, options = {}) {
8648
+ function createFLOT(positions, options = {}, textAmplifiers = {}) {
8555
8649
  const { radius = DEFAULT_RADIUS, radiusPixels, metersPerPixel, arcSegments = DEFAULT_ARC_SEGMENTS, smooth = DEFAULT_FLOT_OPTIONS.smooth, smoothResolution = DEFAULT_FLOT_OPTIONS.smoothResolution } = options;
8556
8650
  let effectiveRadius = radius;
8557
8651
  if (radiusPixels !== void 0 && metersPerPixel !== void 0 && metersPerPixel > 0) effectiveRadius = radiusPixels * metersPerPixel;
@@ -8580,16 +8674,19 @@ function createFLOT(positions, options = {}) {
8580
8674
  const lastGenerated = allPoints[allPoints.length - 1];
8581
8675
  if (vecMag(vecSub(lastOriginal, lastGenerated)) > 1e-6) allPoints.push(lastOriginal);
8582
8676
  }
8677
+ const feature = {
8678
+ type: "Feature",
8679
+ properties: {},
8680
+ geometry: {
8681
+ type: "LineString",
8682
+ coordinates: allPoints.map((p) => unproject(p[0], p[1]))
8683
+ }
8684
+ };
8685
+ const labels = [];
8686
+ pushHostileEndLabels(labels, allPoints, textAmplifiers.N, options);
8583
8687
  return {
8584
8688
  type: "FeatureCollection",
8585
- features: [{
8586
- type: "Feature",
8587
- properties: {},
8588
- geometry: {
8589
- type: "LineString",
8590
- coordinates: allPoints.map((p) => unproject(p[0], p[1]))
8591
- }
8592
- }]
8689
+ features: [feature, ...labels]
8593
8690
  };
8594
8691
  }
8595
8692
  const FLOT = defineControlMeasure({
@@ -8643,7 +8740,7 @@ const PHASE_LINE_METADATA = {
8643
8740
  description: "Field T — the phase line's unique designation.",
8644
8741
  placeholder: "ECHO",
8645
8742
  maxLength: 21
8646
- }]
8743
+ }, HOSTILE_LINE_AMPLIFIER]
8647
8744
  };
8648
8745
  /**
8649
8746
  * Creates a Phase line control measure: a polyline through `positions`
@@ -8656,7 +8753,7 @@ function createPhaseLine(positions, options = {}, textAmplifiers = {}) {
8656
8753
  return fixedLabelLineFeatures(positions, {
8657
8754
  part: "phase-line",
8658
8755
  ...sizeOptions
8659
- }, text);
8756
+ }, text, textAmplifiers.N);
8660
8757
  }
8661
8758
  const PHASE_LINE = defineControlMeasure({
8662
8759
  metadata: PHASE_LINE_METADATA,
@@ -8716,18 +8813,19 @@ const HANDOVER_LINE_METADATA = {
8716
8813
  max: 2,
8717
8814
  step: .05
8718
8815
  }
8719
- ]
8816
+ ],
8817
+ textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
8720
8818
  };
8721
8819
  /**
8722
8820
  * Creates a Handover line control measure: a polyline through `positions`
8723
8821
  * labeled "HOL" above the line near each end.
8724
8822
  */
8725
- function createHandoverLine(positions, options = {}) {
8823
+ function createHandoverLine(positions, options = {}, textAmplifiers = {}) {
8726
8824
  return fixedLabelLineFeatures(positions, {
8727
8825
  part: "handover-line",
8728
8826
  placement: "above",
8729
8827
  ...options
8730
- }, "HOL");
8828
+ }, "HOL", textAmplifiers.N);
8731
8829
  }
8732
8830
  const HANDOVER_LINE = defineControlMeasure({
8733
8831
  metadata: HANDOVER_LINE_METADATA,
@@ -8786,18 +8884,19 @@ const BATTLE_HANDOVER_LINE_METADATA = {
8786
8884
  max: 2,
8787
8885
  step: .05
8788
8886
  }
8789
- ]
8887
+ ],
8888
+ textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
8790
8889
  };
8791
8890
  /**
8792
8891
  * Creates a Battle handover line control measure: a polyline through
8793
8892
  * `positions` labeled "BHL" above the line near each end.
8794
8893
  */
8795
- function createBattleHandoverLine(positions, options = {}) {
8894
+ function createBattleHandoverLine(positions, options = {}, textAmplifiers = {}) {
8796
8895
  return fixedLabelLineFeatures(positions, {
8797
8896
  part: "battle-handover-line",
8798
8897
  placement: "above",
8799
8898
  ...options
8800
- }, "BHL");
8899
+ }, "BHL", textAmplifiers.N);
8801
8900
  }
8802
8901
  const BATTLE_HANDOVER_LINE = defineControlMeasure({
8803
8902
  metadata: BATTLE_HANDOVER_LINE_METADATA,
@@ -8832,9 +8931,11 @@ const FORTIFIED_LINE_METADATA = {
8832
8931
  value: "290900",
8833
8932
  minCoordinates: 2,
8834
8933
  geometry: "line",
8835
- geometryTypes: ["LineString"],
8934
+ geometryTypes: ["LineString", "Point"],
8836
8935
  drawRule: "Line1",
8837
- params: FORTIFIED_PARAMS
8936
+ capturesLabelSize: true,
8937
+ params: FORTIFIED_PARAMS,
8938
+ textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
8838
8939
  };
8839
8940
  /**
8840
8941
  * Generates points for a square wave "tooth" along a segment.
@@ -8942,20 +9043,24 @@ function generateFortifiedPoints(projectedPoints, effectiveSize) {
8942
9043
  * @param options - Configuration options for the graphic
8943
9044
  * @returns A GeoJSON FeatureCollection containing the castellated LineString
8944
9045
  */
8945
- function createFortifiedLine(positions, options = {}) {
9046
+ function createFortifiedLine(positions, options = {}, textAmplifiers = {}) {
8946
9047
  const { smooth = DEFAULT_FORTIFIED_LINE_OPTIONS.smooth, smoothResolution = DEFAULT_FORTIFIED_LINE_OPTIONS.smoothResolution } = options;
8947
9048
  const safeSize = calculateEffectiveSize(options);
8948
9049
  const projectedPoints = positions.map((p) => project(p[0], p[1]));
9050
+ const allPoints = generateFortifiedPoints(smooth ? evenlySpacePath(catmullRom(projectedPoints, normalizeSmoothResolution$2(smoothResolution, DEFAULT_SMOOTH_RESOLUTION$1)), 2 * safeSize) : projectedPoints, safeSize);
9051
+ const feature = {
9052
+ type: "Feature",
9053
+ properties: {},
9054
+ geometry: {
9055
+ type: "LineString",
9056
+ coordinates: allPoints.map((p) => unproject(p[0], p[1]))
9057
+ }
9058
+ };
9059
+ const labels = [];
9060
+ pushHostileEndLabels(labels, allPoints, textAmplifiers.N, options);
8949
9061
  return {
8950
9062
  type: "FeatureCollection",
8951
- features: [{
8952
- type: "Feature",
8953
- properties: {},
8954
- geometry: {
8955
- type: "LineString",
8956
- coordinates: generateFortifiedPoints(smooth ? evenlySpacePath(catmullRom(projectedPoints, normalizeSmoothResolution$2(smoothResolution, DEFAULT_SMOOTH_RESOLUTION$1)), 2 * safeSize) : projectedPoints, safeSize).map((p) => unproject(p[0], p[1]))
8957
- }
8958
- }]
9063
+ features: [feature, ...labels]
8959
9064
  };
8960
9065
  }
8961
9066
  const FORTIFIED_LINE = defineControlMeasure({
@@ -8977,7 +9082,11 @@ const FORTIFIED_AREA_METADATA = {
8977
9082
  value: "151000",
8978
9083
  minCoordinates: 3,
8979
9084
  geometry: "area",
8980
- geometryTypes: ["Polygon", "Point"],
9085
+ geometryTypes: [
9086
+ "Polygon",
9087
+ "MultiLineString",
9088
+ "Point"
9089
+ ],
8981
9090
  drawRule: "Area1",
8982
9091
  capturesLabelSize: true,
8983
9092
  params: [...FORTIFIED_AREA_PARAMS, AREA_LABEL_PADDING_PARAM],
@@ -8996,11 +9105,14 @@ const FORTIFIED_AREA_METADATA = {
8996
9105
  * @param textAmplifiers - Field T (bare, no prefix — "TANGO", not "FA TANGO")
8997
9106
  * plus Field H/W/W1, centered on the pre-teeth boundary ring (see
8998
9107
  * `boundaryInput` below), and an optional Field N "ENY" hostile marker on
8999
- * that ring's west/east edges (placed but not boundary-masked — see below).
9000
- * @returns A GeoJSON FeatureCollection containing the castellated Polygon
9001
- * plus any label Points
9002
- */
9003
- function createFortifiedArea(positions, options = {}, textAmplifiers = {}) {
9108
+ * that ring's west/east edges, masking the castellated boundary beneath it.
9109
+ * @param context - Persisted amplifier placements used to move or restore each
9110
+ * ENY cutout with its independently dragged label.
9111
+ * @returns A GeoJSON FeatureCollection containing the castellated boundary
9112
+ * (a Polygon normally, or a gapped MultiLineString with Field N) plus any
9113
+ * label Points
9114
+ */
9115
+ function createFortifiedArea(positions, options = {}, textAmplifiers = {}, context = {}) {
9004
9116
  const { smooth = DEFAULT_FORTIFIED_AREA_OPTIONS.smooth, smoothResolution = DEFAULT_FORTIFIED_AREA_OPTIONS.smoothResolution } = options;
9005
9117
  const safeSize = calculateEffectiveSize(options);
9006
9118
  const projected = positions.map((p) => project(p[0], p[1]));
@@ -9013,22 +9125,20 @@ function createFortifiedArea(positions, options = {}, textAmplifiers = {}) {
9013
9125
  if (vecMag(vecSub(firstPt, lastPt)) > 1e-6) boundaryPoints.push(firstPt);
9014
9126
  else boundaryPoints[boundaryPoints.length - 1] = firstPt;
9015
9127
  }
9128
+ const labelFeatures = [];
9016
9129
  const features = [{
9017
- type: "Feature",
9018
- properties: {},
9019
- geometry: {
9020
- type: "Polygon",
9021
- coordinates: [boundaryPoints.map((p) => unproject(p[0], p[1]))]
9022
- }
9130
+ ...boundaryFeature(boundaryPoints, pushAreaLabels(labelFeatures, boundaryInput, {
9131
+ name: textAmplifiers.T,
9132
+ additionalInformation: textAmplifiers.H,
9133
+ dtgStart: textAmplifiers.W,
9134
+ dtgEnd: textAmplifiers.W1,
9135
+ hostile: textAmplifiers.N
9136
+ }, options, context.amplifierPlacements, {
9137
+ ringVerts: boundaryPoints,
9138
+ extraClearanceMeters: 3 * safeSize
9139
+ })),
9140
+ properties: {}
9023
9141
  }];
9024
- const labelFeatures = [];
9025
- pushAreaLabels(labelFeatures, boundaryInput, {
9026
- name: textAmplifiers.T,
9027
- additionalInformation: textAmplifiers.H,
9028
- dtgStart: textAmplifiers.W,
9029
- dtgEnd: textAmplifiers.W1,
9030
- hostile: textAmplifiers.N
9031
- }, options);
9032
9142
  features.push(...labelFeatures);
9033
9143
  return {
9034
9144
  type: "FeatureCollection",
@@ -9062,9 +9172,6 @@ const DEFAULT_MANEUVER_ARROW_TASK_OPTIONS = {
9062
9172
  dtgPosition: .62,
9063
9173
  labelPadding: 0
9064
9174
  };
9065
- function midpoint(a, b) {
9066
- return vecScale(vecAdd(a, b), .5);
9067
- }
9068
9175
  function createManeuverArrowTask(coordinates, options, textAmplifiers, config) {
9069
9176
  const resolved = {
9070
9177
  ...DEFAULT_MANEUVER_ARROW_TASK_OPTIONS,
@@ -9082,7 +9189,7 @@ function createManeuverArrowTask(coordinates, options, textAmplifiers, config) {
9082
9189
  geometry.headRing[2],
9083
9190
  ...[...geometry.shaftRight].reverse()
9084
9191
  ];
9085
- const centerline = geometry.shaftLeft.map((left, index) => midpoint(left, geometry.shaftRight[index]));
9192
+ const centerline = geometry.shaftCenterline;
9086
9193
  const { segments, totalLength } = polylineSegments(centerline);
9087
9194
  if (segments.length === 0) return {
9088
9195
  type: "FeatureCollection",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orbat-mapper/control-measures",
3
- "version": "0.2.0-alpha.17",
3
+ "version": "0.2.0-alpha.19",
4
4
  "description": "Library for drawing tactical graphics and control measures according to MIL-STD-2525 and APP-6 standards.",
5
5
  "license": "MIT",
6
6
  "author": "Orbat Mapper",