@orbat-mapper/control-measures 0.2.0-alpha.2 → 0.2.0-alpha.4

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.
@@ -339,6 +339,23 @@ interface BattlePositionOptions {
339
339
  */
340
340
  declare function createBattlePosition(positions: Position[], options?: BattlePositionOptions): FeatureCollection<MultiLineString | MultiPolygon>;
341
341
  //#endregion
342
+ //#region src/generators/cm15-maneuver-areas/strongPoint.d.ts
343
+ /** Configuration options for the Strong Point control measure. */
344
+ type StrongPointOptions = BattlePositionOptions;
345
+ /** Default options for the Strong Point control measure. */
346
+ declare const DEFAULT_STRONG_POINT_OPTIONS: StrongPointOptions;
347
+ /**
348
+ * Creates a Strong Point control measure: a Battle Position boundary/echelon
349
+ * with outward tics whose length and spacing are the Field B echelon height.
350
+ * Shares the perimeter and echelon placement with Battle Position via the
351
+ * `area-echelon` primitive (single source of truth for ring and gap).
352
+ *
353
+ * @param positions - Area anchor points (>=3); the input contract is enforced
354
+ * at the render seam (ADR-0014).
355
+ * @param options - {@link StrongPointOptions}.
356
+ */
357
+ declare function createStrongPoint(positions: Position[], options?: StrongPointOptions): FeatureCollection<MultiLineString | MultiPolygon>;
358
+ //#endregion
342
359
  //#region src/generators/cm14-maneuver-lines/principalDirectionOfFire.d.ts
343
360
  /**
344
361
  * Configuration options for the Principal Direction of Fire tactical symbol.
@@ -390,6 +407,47 @@ type FpfProps = {
390
407
  declare function createFinalProtectiveFireLeft(coordinates: Position[], options?: FinalProtectiveFireOptions): FeatureCollection<MultiLineString | Polygon, FpfProps>;
391
408
  declare function createFinalProtectiveFireRight(coordinates: Position[], options?: FinalProtectiveFireOptions): FeatureCollection<MultiLineString | Polygon, FpfProps>;
392
409
  //#endregion
410
+ //#region src/generators/cm15-maneuver-areas/encirclement.d.ts
411
+ /** Configuration options for the Encirclement control measure. */
412
+ interface EncirclementOptions {
413
+ /**
414
+ * Spacing between adjacent barbs as a ratio of the symbol's mean radius;
415
+ * smaller values pack the barbs more densely. The barbs are then distributed
416
+ * evenly so they close the ring exactly.
417
+ * @default 0.4
418
+ */
419
+ barbSpacingRatio?: number;
420
+ /**
421
+ * Barb length (height/amplitude) as a ratio of the symbol's mean radius.
422
+ * @default 0.16
423
+ */
424
+ barbLengthRatio?: number;
425
+ /**
426
+ * Round the boundary by curving it through the control points (closed
427
+ * centripetal Catmull-Rom) before tracing the barbs.
428
+ * @default true
429
+ */
430
+ smooth?: boolean;
431
+ /**
432
+ * Number of samples per segment when `smooth` is enabled.
433
+ * @default 16
434
+ */
435
+ smoothResolution?: number;
436
+ }
437
+ /** Default options for the Encirclement control measure. */
438
+ declare const DEFAULT_ENCIRCLEMENT_OPTIONS: Required<EncirclementOptions>;
439
+ /**
440
+ * Creates an Encirclement tactical graphic: a complete smooth boundary ring
441
+ * with outward arrowhead barbs resting on it. The boundary is an unfilled
442
+ * Polygon; the barbs are open "V" strokes (a MultiLineString) whose base is the
443
+ * boundary arc itself.
444
+ *
445
+ * @param positions - Area anchor points (>=3); the input contract is enforced
446
+ * at the render seam (ADR-0014).
447
+ * @param options - {@link EncirclementOptions}.
448
+ */
449
+ declare function createEncirclement(positions: Position[], options?: EncirclementOptions): FeatureCollection<Polygon | MultiLineString>;
450
+ //#endregion
393
451
  //#region src/projection.d.ts
394
452
  type Point2D = [number, number];
395
453
  /**
@@ -1193,6 +1251,7 @@ declare function createObstacleBypassImpossible(coordinates: Position[], options
1193
1251
  declare const DEFINITIONS: {
1194
1252
  boundary: ControlMeasureDefinition<"boundary", typeof createBoundary>;
1195
1253
  "battle-position": ControlMeasureDefinition<"battle-position", typeof createBattlePosition>;
1254
+ "strong-point": ControlMeasureDefinition<"strong-point", typeof createStrongPoint>;
1196
1255
  ambush: ControlMeasureDefinition<"ambush", (controlPoints: import("geojson").Position[], options?: AmbushOptions) => import("geojson").FeatureCollection<import("geojson").MultiLineString, Record<string, never>>>;
1197
1256
  "principal-direction-of-fire": ControlMeasureDefinition<"principal-direction-of-fire", typeof createPrincipalDirectionOfFire>;
1198
1257
  "final-protective-fire-left": ControlMeasureDefinition<"final-protective-fire-left", typeof createFinalProtectiveFireLeft>;
@@ -1201,6 +1260,7 @@ declare const DEFINITIONS: {
1201
1260
  part: "shaft" | "head";
1202
1261
  fill?: boolean;
1203
1262
  }>>;
1263
+ encirclement: ControlMeasureDefinition<"encirclement", typeof createEncirclement>;
1204
1264
  "main-attack": ControlMeasureDefinition<"main-attack", typeof createMainAttack>;
1205
1265
  "supporting-attack": ControlMeasureDefinition<"supporting-attack", typeof createSupportingAttack>;
1206
1266
  "classic-arrow": ControlMeasureDefinition<"classic-arrow", typeof createClassicArrow>;
@@ -1650,4 +1710,4 @@ interface TacticalArrowOptions {
1650
1710
  */
1651
1711
  declare const DEFAULT_TACTICAL_ARROW_OPTIONS: Required<TacticalArrowOptions>;
1652
1712
  //#endregion
1653
- export { listControlMeasureMetadata as $, DEFAULT_FINAL_PROTECTIVE_FIRE_OPTIONS as $t, SimpleStyleProps as A, BreachOptions as At, controlMeasureIdFromFeature as B, AttackHelicopterOptions as Bt, BaselineFrameNormal as C, DelayOptions as Ct, EPSILON as D, DEFAULT_CANALIZE_OPTIONS as Dt, createBaselineFrame as E, CanalizeOptions as Et, renderControlMeasure as F, FLOTOptions as Ft, CONTROL_MEASURE_IDS as G, SupportingAttackOptions as Gt, cloneControlMeasure as H, AirborneAttackOptions as Ht, ControlMeasureRender as I, AttackByFireOptions as It, ControlMeasureKind as J, calculateMetrics as Jt, CONTROL_MEASURE_METADATA as K, DEFAULT_MAIN_ATTACK_OPTIONS as Kt, ControlMeasureSnapshot as L, DEFAULT_ATTACK_BY_FIRE_OPTIONS as Lt, toSimpleStyle as M, BlockMissionTaskOptions as Mt, resolveStyleHints as N, DEFAULT_BLOCK_MISSION_TASK_OPTIONS as Nt, getMetersPerPixel as O, BypassOptions as Ot, RenderOptions as P, DEFAULT_FLOT_OPTIONS as Pt, getDefaultOptions as Q, unproject as Qt, FeaturePartProps as R, DEFAULT_SUPPORT_BY_FIRE_OPTIONS as Rt, BaselineFrame as S, DEFAULT_DELAY_OPTIONS as St, BaselineFrameOrigin as T, DEFAULT_CLEAR_OPTIONS as Tt, isKind as U, DEFAULT_AIRBORNE_ATTACK_OPTIONS as Ut, ControlMeasure as V, DEFAULT_ATTACK_HELICOPTER_OPTIONS as Vt, ControlMeasureStyle as W, DEFAULT_SUPPORTING_ATTACK_OPTIONS as Wt, getControlMeasureMetadata as X, Point2D as Xt, OptionsByKind as Y, computeInitialWidthPoint as Yt, getControlMeasureMetadataByValue as Z, project as Zt, computeDefaultMidpointPerpendicularPoint as _, FortifiedLineOptions as _t, axis1DrawRule as a, ControlMeasureMetadata as an, ObstacleBypassEasyOptions as at, pointOnMidpointPerpendicularAxis as b, AntitankDitchOptions as bt, line23DrawRule as c, ControlMeasureDrawRule as cn, DEFAULT_FIX_OPTIONS as ct, ambushDrawRule as d, DisruptOptions as dt, FinalProtectiveFireOptions as en, DEFAULT_OBSTACLE_BYPASS_IMPOSSIBLE_OPTIONS as et, attackByFireDrawRule as f, BlockOptions as ft, MidpointPerpendicularDrawRuleOptions as g, DEFAULT_FORTIFIED_LINE_OPTIONS as gt, blockDrawRule as h, FortifiedAreaOptions as ht, DEFAULT_AMBUSH_OPTIONS as i, ControlMeasureGeometryType as in, DEFAULT_OBSTACLE_BYPASS_EASY_OPTIONS as it, SimpleStyleRender as j, DEFAULT_BREACH_OPTIONS as jt, roundToFixed as k, DEFAULT_BYPASS_OPTIONS as kt, turnDrawRule as l, FixOptions as lt, disruptDrawRule as m, DEFAULT_FORTIFIED_AREA_OPTIONS as mt, TacticalArrowOptions as n, PrincipalDirectionOfFireOptions as nn, DEFAULT_OBSTACLE_BYPASS_DIFFICULT_OPTIONS as nt, supportByFireDrawRule as o, ParamDescriptor as on, DEFAULT_TURN_OPTIONS as ot, point12DrawRule as p, DEFAULT_BLOCK_OPTIONS as pt, ControlMeasureId as q, MainAttackOptions as qt, AmbushOptions as r, ControlMeasureGeometry as rn, ObstacleBypassDifficultOptions as rt, line24DrawRule as s, AnchorTransformEvent as sn, TurnOptions as st, DEFAULT_TACTICAL_ARROW_OPTIONS as t, DEFAULT_PRINCIPAL_DIRECTION_OF_FIRE_OPTIONS as tn, ObstacleBypassImpossibleOptions as tt, line1DrawRule as u, DEFAULT_DISRUPT_OPTIONS as ut, createMidpointPerpendicularDrawRule as v, AntitankWallOptions as vt, BaselineFrameOptions as w, ClearOptions as wt, snapToMidpointPerpendicular as x, DEFAULT_ANTITANK_DITCH_OPTIONS as xt, getMidpointPerpendicularSignedDistance as y, DEFAULT_ANTITANK_WALL_OPTIONS as yt, StyleHints as z, SupportByFireOptions as zt };
1713
+ export { listControlMeasureMetadata as $, DEFAULT_ENCIRCLEMENT_OPTIONS as $t, SimpleStyleProps as A, BreachOptions as At, controlMeasureIdFromFeature as B, AttackHelicopterOptions as Bt, BaselineFrameNormal as C, DelayOptions as Ct, EPSILON as D, DEFAULT_CANALIZE_OPTIONS as Dt, createBaselineFrame as E, CanalizeOptions as Et, renderControlMeasure as F, FLOTOptions as Ft, CONTROL_MEASURE_IDS as G, SupportingAttackOptions as Gt, cloneControlMeasure as H, AirborneAttackOptions as Ht, ControlMeasureRender as I, AttackByFireOptions as It, ControlMeasureKind as J, calculateMetrics as Jt, CONTROL_MEASURE_METADATA as K, DEFAULT_MAIN_ATTACK_OPTIONS as Kt, ControlMeasureSnapshot as L, DEFAULT_ATTACK_BY_FIRE_OPTIONS as Lt, toSimpleStyle as M, BlockMissionTaskOptions as Mt, resolveStyleHints as N, DEFAULT_BLOCK_MISSION_TASK_OPTIONS as Nt, getMetersPerPixel as O, BypassOptions as Ot, RenderOptions as P, DEFAULT_FLOT_OPTIONS as Pt, getDefaultOptions as Q, unproject as Qt, FeaturePartProps as R, DEFAULT_SUPPORT_BY_FIRE_OPTIONS as Rt, BaselineFrame as S, DEFAULT_DELAY_OPTIONS as St, BaselineFrameOrigin as T, DEFAULT_CLEAR_OPTIONS as Tt, isKind as U, DEFAULT_AIRBORNE_ATTACK_OPTIONS as Ut, ControlMeasure as V, DEFAULT_ATTACK_HELICOPTER_OPTIONS as Vt, ControlMeasureStyle as W, DEFAULT_SUPPORTING_ATTACK_OPTIONS as Wt, getControlMeasureMetadata as X, Point2D as Xt, OptionsByKind as Y, computeInitialWidthPoint as Yt, getControlMeasureMetadataByValue as Z, project as Zt, computeDefaultMidpointPerpendicularPoint as _, FortifiedLineOptions as _t, axis1DrawRule as a, DEFAULT_STRONG_POINT_OPTIONS as an, ObstacleBypassEasyOptions as at, pointOnMidpointPerpendicularAxis as b, AntitankDitchOptions as bt, line23DrawRule as c, ControlMeasureGeometryType as cn, DEFAULT_FIX_OPTIONS as ct, ambushDrawRule as d, AnchorTransformEvent as dn, DisruptOptions as dt, EncirclementOptions as en, DEFAULT_OBSTACLE_BYPASS_IMPOSSIBLE_OPTIONS as et, attackByFireDrawRule as f, ControlMeasureDrawRule as fn, BlockOptions as ft, MidpointPerpendicularDrawRuleOptions as g, DEFAULT_FORTIFIED_LINE_OPTIONS as gt, blockDrawRule as h, FortifiedAreaOptions as ht, DEFAULT_AMBUSH_OPTIONS as i, PrincipalDirectionOfFireOptions as in, DEFAULT_OBSTACLE_BYPASS_EASY_OPTIONS as it, SimpleStyleRender as j, DEFAULT_BREACH_OPTIONS as jt, roundToFixed as k, DEFAULT_BYPASS_OPTIONS as kt, turnDrawRule as l, ControlMeasureMetadata as ln, FixOptions as lt, disruptDrawRule as m, DEFAULT_FORTIFIED_AREA_OPTIONS as mt, TacticalArrowOptions as n, FinalProtectiveFireOptions as nn, DEFAULT_OBSTACLE_BYPASS_DIFFICULT_OPTIONS as nt, supportByFireDrawRule as o, StrongPointOptions as on, DEFAULT_TURN_OPTIONS as ot, point12DrawRule as p, DEFAULT_BLOCK_OPTIONS as pt, ControlMeasureId as q, MainAttackOptions as qt, AmbushOptions as r, DEFAULT_PRINCIPAL_DIRECTION_OF_FIRE_OPTIONS as rn, ObstacleBypassDifficultOptions as rt, line24DrawRule as s, ControlMeasureGeometry as sn, TurnOptions as st, DEFAULT_TACTICAL_ARROW_OPTIONS as t, DEFAULT_FINAL_PROTECTIVE_FIRE_OPTIONS as tn, ObstacleBypassImpossibleOptions as tt, line1DrawRule as u, ParamDescriptor as un, DEFAULT_DISRUPT_OPTIONS as ut, createMidpointPerpendicularDrawRule as v, AntitankWallOptions as vt, BaselineFrameOptions as w, ClearOptions as wt, snapToMidpointPerpendicular as x, DEFAULT_ANTITANK_DITCH_OPTIONS as xt, getMidpointPerpendicularSignedDistance as y, DEFAULT_ANTITANK_WALL_OPTIONS as yt, StyleHints as z, SupportByFireOptions as zt };
package/dist/index.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { $ as listControlMeasureMetadata, $t as DEFAULT_FINAL_PROTECTIVE_FIRE_OPTIONS, A as SimpleStyleProps, At as BreachOptions, B as controlMeasureIdFromFeature, Bt as AttackHelicopterOptions, C as BaselineFrameNormal, Ct as DelayOptions, D as EPSILON, Dt as DEFAULT_CANALIZE_OPTIONS, E as createBaselineFrame, Et as CanalizeOptions, F as renderControlMeasure, Ft as FLOTOptions, G as CONTROL_MEASURE_IDS, Gt as SupportingAttackOptions, H as cloneControlMeasure, Ht as AirborneAttackOptions, I as ControlMeasureRender, It as AttackByFireOptions, J as ControlMeasureKind, Jt as calculateMetrics, K as CONTROL_MEASURE_METADATA, Kt as DEFAULT_MAIN_ATTACK_OPTIONS, L as ControlMeasureSnapshot, Lt as DEFAULT_ATTACK_BY_FIRE_OPTIONS, M as toSimpleStyle, Mt as BlockMissionTaskOptions, N as resolveStyleHints, Nt as DEFAULT_BLOCK_MISSION_TASK_OPTIONS, O as getMetersPerPixel, Ot as BypassOptions, P as RenderOptions, Pt as DEFAULT_FLOT_OPTIONS, Q as getDefaultOptions, Qt as unproject, R as FeaturePartProps, Rt as DEFAULT_SUPPORT_BY_FIRE_OPTIONS, S as BaselineFrame, St as DEFAULT_DELAY_OPTIONS, T as BaselineFrameOrigin, Tt as DEFAULT_CLEAR_OPTIONS, U as isKind, Ut as DEFAULT_AIRBORNE_ATTACK_OPTIONS, V as ControlMeasure, Vt as DEFAULT_ATTACK_HELICOPTER_OPTIONS, W as ControlMeasureStyle, Wt as DEFAULT_SUPPORTING_ATTACK_OPTIONS, X as getControlMeasureMetadata, Xt as Point2D, Y as OptionsByKind, Yt as computeInitialWidthPoint, Z as getControlMeasureMetadataByValue, Zt as project, _ as computeDefaultMidpointPerpendicularPoint, _t as FortifiedLineOptions, a as axis1DrawRule, an as ControlMeasureMetadata, at as ObstacleBypassEasyOptions, b as pointOnMidpointPerpendicularAxis, bt as AntitankDitchOptions, c as line23DrawRule, cn as ControlMeasureDrawRule, ct as DEFAULT_FIX_OPTIONS, d as ambushDrawRule, dt as DisruptOptions, en as FinalProtectiveFireOptions, et as DEFAULT_OBSTACLE_BYPASS_IMPOSSIBLE_OPTIONS, f as attackByFireDrawRule, ft as BlockOptions, g as MidpointPerpendicularDrawRuleOptions, gt as DEFAULT_FORTIFIED_LINE_OPTIONS, h as blockDrawRule, ht as FortifiedAreaOptions, i as DEFAULT_AMBUSH_OPTIONS, in as ControlMeasureGeometryType, it as DEFAULT_OBSTACLE_BYPASS_EASY_OPTIONS, j as SimpleStyleRender, jt as DEFAULT_BREACH_OPTIONS, k as roundToFixed, kt as DEFAULT_BYPASS_OPTIONS, l as turnDrawRule, lt as FixOptions, m as disruptDrawRule, mt as DEFAULT_FORTIFIED_AREA_OPTIONS, n as TacticalArrowOptions, nn as PrincipalDirectionOfFireOptions, nt as DEFAULT_OBSTACLE_BYPASS_DIFFICULT_OPTIONS, o as supportByFireDrawRule, on as ParamDescriptor, ot as DEFAULT_TURN_OPTIONS, p as point12DrawRule, pt as DEFAULT_BLOCK_OPTIONS, q as ControlMeasureId, qt as MainAttackOptions, r as AmbushOptions, rn as ControlMeasureGeometry, rt as ObstacleBypassDifficultOptions, s as line24DrawRule, sn as AnchorTransformEvent, st as TurnOptions, t as DEFAULT_TACTICAL_ARROW_OPTIONS, tn as DEFAULT_PRINCIPAL_DIRECTION_OF_FIRE_OPTIONS, tt as ObstacleBypassImpossibleOptions, u as line1DrawRule, ut as DEFAULT_DISRUPT_OPTIONS, v as createMidpointPerpendicularDrawRule, vt as AntitankWallOptions, w as BaselineFrameOptions, wt as ClearOptions, x as snapToMidpointPerpendicular, xt as DEFAULT_ANTITANK_DITCH_OPTIONS, y as getMidpointPerpendicularSignedDistance, yt as DEFAULT_ANTITANK_WALL_OPTIONS, z as StyleHints, zt as SupportByFireOptions } from "./index-D7uBPw7l.mjs";
2
- export { type AirborneAttackOptions, type AmbushOptions, type AnchorTransformEvent, type AntitankDitchOptions, type AntitankWallOptions, type AttackByFireOptions, type AttackHelicopterOptions, type BaselineFrame, type BaselineFrameNormal, type BaselineFrameOptions, type BaselineFrameOrigin, type BlockMissionTaskOptions, type BlockOptions, type BreachOptions, type BypassOptions, CONTROL_MEASURE_IDS, CONTROL_MEASURE_METADATA, type CanalizeOptions, type ClearOptions, type ControlMeasure, type ControlMeasureDrawRule, type ControlMeasureGeometry, type ControlMeasureGeometryType, type ControlMeasureId, type ControlMeasureKind, type ControlMeasureMetadata, type ControlMeasureRender, type ControlMeasureSnapshot, type ControlMeasureStyle, DEFAULT_AIRBORNE_ATTACK_OPTIONS, DEFAULT_AMBUSH_OPTIONS, DEFAULT_ANTITANK_DITCH_OPTIONS, DEFAULT_ANTITANK_WALL_OPTIONS, DEFAULT_ATTACK_BY_FIRE_OPTIONS, DEFAULT_ATTACK_HELICOPTER_OPTIONS, DEFAULT_BLOCK_MISSION_TASK_OPTIONS, DEFAULT_BLOCK_OPTIONS, DEFAULT_BREACH_OPTIONS, DEFAULT_BYPASS_OPTIONS, DEFAULT_CANALIZE_OPTIONS, DEFAULT_CLEAR_OPTIONS, DEFAULT_DELAY_OPTIONS, DEFAULT_DISRUPT_OPTIONS, DEFAULT_FINAL_PROTECTIVE_FIRE_OPTIONS, DEFAULT_FIX_OPTIONS, DEFAULT_FLOT_OPTIONS, DEFAULT_FORTIFIED_AREA_OPTIONS, DEFAULT_FORTIFIED_LINE_OPTIONS, DEFAULT_MAIN_ATTACK_OPTIONS, DEFAULT_OBSTACLE_BYPASS_DIFFICULT_OPTIONS, DEFAULT_OBSTACLE_BYPASS_EASY_OPTIONS, DEFAULT_OBSTACLE_BYPASS_IMPOSSIBLE_OPTIONS, DEFAULT_PRINCIPAL_DIRECTION_OF_FIRE_OPTIONS, DEFAULT_SUPPORTING_ATTACK_OPTIONS, DEFAULT_SUPPORT_BY_FIRE_OPTIONS, DEFAULT_TACTICAL_ARROW_OPTIONS, DEFAULT_TURN_OPTIONS, type DelayOptions, type DisruptOptions, EPSILON, type FLOTOptions, type FeaturePartProps, type FinalProtectiveFireOptions, type FixOptions, type FortifiedAreaOptions, type FortifiedLineOptions, type MainAttackOptions, type MidpointPerpendicularDrawRuleOptions, type ObstacleBypassDifficultOptions, type ObstacleBypassEasyOptions, type ObstacleBypassImpossibleOptions, type OptionsByKind, type ParamDescriptor, type Point2D, type PrincipalDirectionOfFireOptions, type RenderOptions, type SimpleStyleProps, type SimpleStyleRender, type StyleHints, type SupportByFireOptions, type SupportingAttackOptions, type TacticalArrowOptions, type TurnOptions, ambushDrawRule, attackByFireDrawRule, axis1DrawRule, blockDrawRule, calculateMetrics, cloneControlMeasure, computeDefaultMidpointPerpendicularPoint, computeInitialWidthPoint, controlMeasureIdFromFeature, createBaselineFrame, createMidpointPerpendicularDrawRule, disruptDrawRule, getControlMeasureMetadata, getControlMeasureMetadataByValue, getDefaultOptions, getMetersPerPixel, getMidpointPerpendicularSignedDistance, isKind, line1DrawRule, line23DrawRule, line24DrawRule, listControlMeasureMetadata, point12DrawRule, pointOnMidpointPerpendicularAxis, project, renderControlMeasure, resolveStyleHints, roundToFixed, snapToMidpointPerpendicular, supportByFireDrawRule, toSimpleStyle, turnDrawRule, unproject };
1
+ import { $ as listControlMeasureMetadata, $t as DEFAULT_ENCIRCLEMENT_OPTIONS, A as SimpleStyleProps, At as BreachOptions, B as controlMeasureIdFromFeature, Bt as AttackHelicopterOptions, C as BaselineFrameNormal, Ct as DelayOptions, D as EPSILON, Dt as DEFAULT_CANALIZE_OPTIONS, E as createBaselineFrame, Et as CanalizeOptions, F as renderControlMeasure, Ft as FLOTOptions, G as CONTROL_MEASURE_IDS, Gt as SupportingAttackOptions, H as cloneControlMeasure, Ht as AirborneAttackOptions, I as ControlMeasureRender, It as AttackByFireOptions, J as ControlMeasureKind, Jt as calculateMetrics, K as CONTROL_MEASURE_METADATA, Kt as DEFAULT_MAIN_ATTACK_OPTIONS, L as ControlMeasureSnapshot, Lt as DEFAULT_ATTACK_BY_FIRE_OPTIONS, M as toSimpleStyle, Mt as BlockMissionTaskOptions, N as resolveStyleHints, Nt as DEFAULT_BLOCK_MISSION_TASK_OPTIONS, O as getMetersPerPixel, Ot as BypassOptions, P as RenderOptions, Pt as DEFAULT_FLOT_OPTIONS, Q as getDefaultOptions, Qt as unproject, R as FeaturePartProps, Rt as DEFAULT_SUPPORT_BY_FIRE_OPTIONS, S as BaselineFrame, St as DEFAULT_DELAY_OPTIONS, T as BaselineFrameOrigin, Tt as DEFAULT_CLEAR_OPTIONS, U as isKind, Ut as DEFAULT_AIRBORNE_ATTACK_OPTIONS, V as ControlMeasure, Vt as DEFAULT_ATTACK_HELICOPTER_OPTIONS, W as ControlMeasureStyle, Wt as DEFAULT_SUPPORTING_ATTACK_OPTIONS, X as getControlMeasureMetadata, Xt as Point2D, Y as OptionsByKind, Yt as computeInitialWidthPoint, Z as getControlMeasureMetadataByValue, Zt as project, _ as computeDefaultMidpointPerpendicularPoint, _t as FortifiedLineOptions, a as axis1DrawRule, an as DEFAULT_STRONG_POINT_OPTIONS, at as ObstacleBypassEasyOptions, b as pointOnMidpointPerpendicularAxis, bt as AntitankDitchOptions, c as line23DrawRule, cn as ControlMeasureGeometryType, ct as DEFAULT_FIX_OPTIONS, d as ambushDrawRule, dn as AnchorTransformEvent, dt as DisruptOptions, en as EncirclementOptions, et as DEFAULT_OBSTACLE_BYPASS_IMPOSSIBLE_OPTIONS, f as attackByFireDrawRule, fn as ControlMeasureDrawRule, ft as BlockOptions, g as MidpointPerpendicularDrawRuleOptions, gt as DEFAULT_FORTIFIED_LINE_OPTIONS, h as blockDrawRule, ht as FortifiedAreaOptions, i as DEFAULT_AMBUSH_OPTIONS, in as PrincipalDirectionOfFireOptions, it as DEFAULT_OBSTACLE_BYPASS_EASY_OPTIONS, j as SimpleStyleRender, jt as DEFAULT_BREACH_OPTIONS, k as roundToFixed, kt as DEFAULT_BYPASS_OPTIONS, l as turnDrawRule, ln as ControlMeasureMetadata, lt as FixOptions, m as disruptDrawRule, mt as DEFAULT_FORTIFIED_AREA_OPTIONS, n as TacticalArrowOptions, nn as FinalProtectiveFireOptions, nt as DEFAULT_OBSTACLE_BYPASS_DIFFICULT_OPTIONS, o as supportByFireDrawRule, on as StrongPointOptions, ot as DEFAULT_TURN_OPTIONS, p as point12DrawRule, pt as DEFAULT_BLOCK_OPTIONS, q as ControlMeasureId, qt as MainAttackOptions, r as AmbushOptions, rn as DEFAULT_PRINCIPAL_DIRECTION_OF_FIRE_OPTIONS, rt as ObstacleBypassDifficultOptions, s as line24DrawRule, sn as ControlMeasureGeometry, st as TurnOptions, t as DEFAULT_TACTICAL_ARROW_OPTIONS, tn as DEFAULT_FINAL_PROTECTIVE_FIRE_OPTIONS, tt as ObstacleBypassImpossibleOptions, u as line1DrawRule, un as ParamDescriptor, ut as DEFAULT_DISRUPT_OPTIONS, v as createMidpointPerpendicularDrawRule, vt as AntitankWallOptions, w as BaselineFrameOptions, wt as ClearOptions, x as snapToMidpointPerpendicular, xt as DEFAULT_ANTITANK_DITCH_OPTIONS, y as getMidpointPerpendicularSignedDistance, yt as DEFAULT_ANTITANK_WALL_OPTIONS, z as StyleHints, zt as SupportByFireOptions } from "./index-BJYbQlzo.mjs";
2
+ export { type AirborneAttackOptions, type AmbushOptions, type AnchorTransformEvent, type AntitankDitchOptions, type AntitankWallOptions, type AttackByFireOptions, type AttackHelicopterOptions, type BaselineFrame, type BaselineFrameNormal, type BaselineFrameOptions, type BaselineFrameOrigin, type BlockMissionTaskOptions, type BlockOptions, type BreachOptions, type BypassOptions, CONTROL_MEASURE_IDS, CONTROL_MEASURE_METADATA, type CanalizeOptions, type ClearOptions, type ControlMeasure, type ControlMeasureDrawRule, type ControlMeasureGeometry, type ControlMeasureGeometryType, type ControlMeasureId, type ControlMeasureKind, type ControlMeasureMetadata, type ControlMeasureRender, type ControlMeasureSnapshot, type ControlMeasureStyle, DEFAULT_AIRBORNE_ATTACK_OPTIONS, DEFAULT_AMBUSH_OPTIONS, DEFAULT_ANTITANK_DITCH_OPTIONS, DEFAULT_ANTITANK_WALL_OPTIONS, DEFAULT_ATTACK_BY_FIRE_OPTIONS, DEFAULT_ATTACK_HELICOPTER_OPTIONS, DEFAULT_BLOCK_MISSION_TASK_OPTIONS, DEFAULT_BLOCK_OPTIONS, DEFAULT_BREACH_OPTIONS, DEFAULT_BYPASS_OPTIONS, DEFAULT_CANALIZE_OPTIONS, DEFAULT_CLEAR_OPTIONS, DEFAULT_DELAY_OPTIONS, DEFAULT_DISRUPT_OPTIONS, DEFAULT_ENCIRCLEMENT_OPTIONS, DEFAULT_FINAL_PROTECTIVE_FIRE_OPTIONS, DEFAULT_FIX_OPTIONS, DEFAULT_FLOT_OPTIONS, DEFAULT_FORTIFIED_AREA_OPTIONS, DEFAULT_FORTIFIED_LINE_OPTIONS, DEFAULT_MAIN_ATTACK_OPTIONS, DEFAULT_OBSTACLE_BYPASS_DIFFICULT_OPTIONS, DEFAULT_OBSTACLE_BYPASS_EASY_OPTIONS, DEFAULT_OBSTACLE_BYPASS_IMPOSSIBLE_OPTIONS, DEFAULT_PRINCIPAL_DIRECTION_OF_FIRE_OPTIONS, DEFAULT_STRONG_POINT_OPTIONS, DEFAULT_SUPPORTING_ATTACK_OPTIONS, DEFAULT_SUPPORT_BY_FIRE_OPTIONS, DEFAULT_TACTICAL_ARROW_OPTIONS, DEFAULT_TURN_OPTIONS, type DelayOptions, type DisruptOptions, EPSILON, type EncirclementOptions, type FLOTOptions, type FeaturePartProps, type FinalProtectiveFireOptions, type FixOptions, type FortifiedAreaOptions, type FortifiedLineOptions, type MainAttackOptions, type MidpointPerpendicularDrawRuleOptions, type ObstacleBypassDifficultOptions, type ObstacleBypassEasyOptions, type ObstacleBypassImpossibleOptions, type OptionsByKind, type ParamDescriptor, type Point2D, type PrincipalDirectionOfFireOptions, type RenderOptions, type SimpleStyleProps, type SimpleStyleRender, type StrongPointOptions, type StyleHints, type SupportByFireOptions, type SupportingAttackOptions, type TacticalArrowOptions, type TurnOptions, ambushDrawRule, attackByFireDrawRule, axis1DrawRule, blockDrawRule, calculateMetrics, cloneControlMeasure, computeDefaultMidpointPerpendicularPoint, computeInitialWidthPoint, controlMeasureIdFromFeature, createBaselineFrame, createMidpointPerpendicularDrawRule, disruptDrawRule, getControlMeasureMetadata, getControlMeasureMetadataByValue, getDefaultOptions, getMetersPerPixel, getMidpointPerpendicularSignedDistance, isKind, line1DrawRule, line23DrawRule, line24DrawRule, listControlMeasureMetadata, point12DrawRule, pointOnMidpointPerpendicularAxis, project, renderControlMeasure, resolveStyleHints, roundToFixed, snapToMidpointPerpendicular, supportByFireDrawRule, toSimpleStyle, turnDrawRule, unproject };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as snapToMidpointPerpendicular, A as DEFAULT_BLOCK_MISSION_TASK_OPTIONS, B as line24DrawRule, C as DEFAULT_FINAL_PROTECTIVE_FIRE_OPTIONS, D as DEFAULT_CANALIZE_OPTIONS, E as DEFAULT_CLEAR_OPTIONS, F as DEFAULT_ANTITANK_DITCH_OPTIONS, G as attackByFireDrawRule, H as turnDrawRule, I as DEFAULT_AMBUSH_OPTIONS, J as blockDrawRule, K as point12DrawRule, L as DEFAULT_AIRBORNE_ATTACK_OPTIONS, M as DEFAULT_ATTACK_HELICOPTER_OPTIONS, N as DEFAULT_ATTACK_BY_FIRE_OPTIONS, O as DEFAULT_BYPASS_OPTIONS, P as DEFAULT_ANTITANK_WALL_OPTIONS, Q as pointOnMidpointPerpendicularAxis, R as axis1DrawRule, S as DEFAULT_FIX_OPTIONS, T as DEFAULT_DELAY_OPTIONS, U as line1DrawRule, V as line23DrawRule, W as ambushDrawRule, X as createMidpointPerpendicularDrawRule, Y as computeDefaultMidpointPerpendicularPoint, Z as getMidpointPerpendicularSignedDistance, _ as DEFAULT_OBSTACLE_BYPASS_DIFFICULT_OPTIONS, at as EPSILON, b as DEFAULT_FORTIFIED_LINE_OPTIONS, c as getDefaultOptions, d as DEFAULT_SUPPORTING_ATTACK_OPTIONS, et as createBaselineFrame, f as DEFAULT_SUPPORT_BY_FIRE_OPTIONS, g as DEFAULT_OBSTACLE_BYPASS_EASY_OPTIONS, h as DEFAULT_OBSTACLE_BYPASS_IMPOSSIBLE_OPTIONS, i as CONTROL_MEASURE_METADATA, it as unproject, j as DEFAULT_BLOCK_OPTIONS, k as DEFAULT_BREACH_OPTIONS, l as listControlMeasureMetadata, m as DEFAULT_PRINCIPAL_DIRECTION_OF_FIRE_OPTIONS, n as resolveStyleHints, nt as computeInitialWidthPoint, o as getControlMeasureMetadata, ot as getMetersPerPixel, p as DEFAULT_TACTICAL_ARROW_OPTIONS, q as disruptDrawRule, r as CONTROL_MEASURE_IDS, rt as project, s as getControlMeasureMetadataByValue, st as roundToFixed, t as renderControlMeasure, tt as calculateMetrics, u as DEFAULT_TURN_OPTIONS, v as DEFAULT_MAIN_ATTACK_OPTIONS, w as DEFAULT_DISRUPT_OPTIONS, x as DEFAULT_FLOT_OPTIONS, y as DEFAULT_FORTIFIED_AREA_OPTIONS, z as supportByFireDrawRule } from "./renderControlMeasure-C7pY-TcL.mjs";
1
+ import { $ as getMidpointPerpendicularSignedDistance, A as DEFAULT_BYPASS_OPTIONS, B as axis1DrawRule, C as DEFAULT_FIX_OPTIONS, D as DEFAULT_DELAY_OPTIONS, E as DEFAULT_DISRUPT_OPTIONS, F as DEFAULT_ATTACK_BY_FIRE_OPTIONS, G as line1DrawRule, H as line24DrawRule, I as DEFAULT_ANTITANK_WALL_OPTIONS, J as point12DrawRule, K as ambushDrawRule, L as DEFAULT_ANTITANK_DITCH_OPTIONS, M as DEFAULT_BLOCK_MISSION_TASK_OPTIONS, N as DEFAULT_BLOCK_OPTIONS, O as DEFAULT_CLEAR_OPTIONS, P as DEFAULT_ATTACK_HELICOPTER_OPTIONS, Q as createMidpointPerpendicularDrawRule, R as DEFAULT_AMBUSH_OPTIONS, S as DEFAULT_FLOT_OPTIONS, T as DEFAULT_ENCIRCLEMENT_OPTIONS, U as line23DrawRule, V as supportByFireDrawRule, W as turnDrawRule, X as blockDrawRule, Y as disruptDrawRule, Z as computeDefaultMidpointPerpendicularPoint, _ as DEFAULT_OBSTACLE_BYPASS_EASY_OPTIONS, at as project, b as DEFAULT_FORTIFIED_AREA_OPTIONS, c as getDefaultOptions, ct as getMetersPerPixel, d as DEFAULT_SUPPORTING_ATTACK_OPTIONS, et as pointOnMidpointPerpendicularAxis, f as DEFAULT_SUPPORT_BY_FIRE_OPTIONS, g as DEFAULT_OBSTACLE_BYPASS_IMPOSSIBLE_OPTIONS, h as DEFAULT_PRINCIPAL_DIRECTION_OF_FIRE_OPTIONS, i as CONTROL_MEASURE_METADATA, it as computeInitialWidthPoint, j as DEFAULT_BREACH_OPTIONS, k as DEFAULT_CANALIZE_OPTIONS, l as listControlMeasureMetadata, lt as roundToFixed, m as DEFAULT_TACTICAL_ARROW_OPTIONS, n as resolveStyleHints, nt as createBaselineFrame, o as getControlMeasureMetadata, ot as unproject, p as DEFAULT_STRONG_POINT_OPTIONS, q as attackByFireDrawRule, r as CONTROL_MEASURE_IDS, rt as calculateMetrics, s as getControlMeasureMetadataByValue, st as EPSILON, t as renderControlMeasure, tt as snapToMidpointPerpendicular, u as DEFAULT_TURN_OPTIONS, v as DEFAULT_OBSTACLE_BYPASS_DIFFICULT_OPTIONS, w as DEFAULT_FINAL_PROTECTIVE_FIRE_OPTIONS, x as DEFAULT_FORTIFIED_LINE_OPTIONS, y as DEFAULT_MAIN_ATTACK_OPTIONS, z as DEFAULT_AIRBORNE_ATTACK_OPTIONS } from "./renderControlMeasure-CONa8M6M.mjs";
2
2
  //#region src/instance.ts
3
3
  function isKind(cm, kind) {
4
4
  return cm.kind === kind;
@@ -99,4 +99,4 @@ function toHexChannel(value) {
99
99
  return Math.max(0, Math.min(255, Math.round(value))).toString(16).padStart(2, "0");
100
100
  }
101
101
  //#endregion
102
- export { CONTROL_MEASURE_IDS, CONTROL_MEASURE_METADATA, DEFAULT_AIRBORNE_ATTACK_OPTIONS, DEFAULT_AMBUSH_OPTIONS, DEFAULT_ANTITANK_DITCH_OPTIONS, DEFAULT_ANTITANK_WALL_OPTIONS, DEFAULT_ATTACK_BY_FIRE_OPTIONS, DEFAULT_ATTACK_HELICOPTER_OPTIONS, DEFAULT_BLOCK_MISSION_TASK_OPTIONS, DEFAULT_BLOCK_OPTIONS, DEFAULT_BREACH_OPTIONS, DEFAULT_BYPASS_OPTIONS, DEFAULT_CANALIZE_OPTIONS, DEFAULT_CLEAR_OPTIONS, DEFAULT_DELAY_OPTIONS, DEFAULT_DISRUPT_OPTIONS, DEFAULT_FINAL_PROTECTIVE_FIRE_OPTIONS, DEFAULT_FIX_OPTIONS, DEFAULT_FLOT_OPTIONS, DEFAULT_FORTIFIED_AREA_OPTIONS, DEFAULT_FORTIFIED_LINE_OPTIONS, DEFAULT_MAIN_ATTACK_OPTIONS, DEFAULT_OBSTACLE_BYPASS_DIFFICULT_OPTIONS, DEFAULT_OBSTACLE_BYPASS_EASY_OPTIONS, DEFAULT_OBSTACLE_BYPASS_IMPOSSIBLE_OPTIONS, DEFAULT_PRINCIPAL_DIRECTION_OF_FIRE_OPTIONS, DEFAULT_SUPPORTING_ATTACK_OPTIONS, DEFAULT_SUPPORT_BY_FIRE_OPTIONS, DEFAULT_TACTICAL_ARROW_OPTIONS, DEFAULT_TURN_OPTIONS, EPSILON, ambushDrawRule, attackByFireDrawRule, axis1DrawRule, blockDrawRule, calculateMetrics, cloneControlMeasure, computeDefaultMidpointPerpendicularPoint, computeInitialWidthPoint, controlMeasureIdFromFeature, createBaselineFrame, createMidpointPerpendicularDrawRule, disruptDrawRule, getControlMeasureMetadata, getControlMeasureMetadataByValue, getDefaultOptions, getMetersPerPixel, getMidpointPerpendicularSignedDistance, isKind, line1DrawRule, line23DrawRule, line24DrawRule, listControlMeasureMetadata, point12DrawRule, pointOnMidpointPerpendicularAxis, project, renderControlMeasure, resolveStyleHints, roundToFixed, snapToMidpointPerpendicular, supportByFireDrawRule, toSimpleStyle, turnDrawRule, unproject };
102
+ export { CONTROL_MEASURE_IDS, CONTROL_MEASURE_METADATA, DEFAULT_AIRBORNE_ATTACK_OPTIONS, DEFAULT_AMBUSH_OPTIONS, DEFAULT_ANTITANK_DITCH_OPTIONS, DEFAULT_ANTITANK_WALL_OPTIONS, DEFAULT_ATTACK_BY_FIRE_OPTIONS, DEFAULT_ATTACK_HELICOPTER_OPTIONS, DEFAULT_BLOCK_MISSION_TASK_OPTIONS, DEFAULT_BLOCK_OPTIONS, DEFAULT_BREACH_OPTIONS, DEFAULT_BYPASS_OPTIONS, DEFAULT_CANALIZE_OPTIONS, DEFAULT_CLEAR_OPTIONS, DEFAULT_DELAY_OPTIONS, DEFAULT_DISRUPT_OPTIONS, DEFAULT_ENCIRCLEMENT_OPTIONS, DEFAULT_FINAL_PROTECTIVE_FIRE_OPTIONS, DEFAULT_FIX_OPTIONS, DEFAULT_FLOT_OPTIONS, DEFAULT_FORTIFIED_AREA_OPTIONS, DEFAULT_FORTIFIED_LINE_OPTIONS, DEFAULT_MAIN_ATTACK_OPTIONS, DEFAULT_OBSTACLE_BYPASS_DIFFICULT_OPTIONS, DEFAULT_OBSTACLE_BYPASS_EASY_OPTIONS, DEFAULT_OBSTACLE_BYPASS_IMPOSSIBLE_OPTIONS, DEFAULT_PRINCIPAL_DIRECTION_OF_FIRE_OPTIONS, DEFAULT_STRONG_POINT_OPTIONS, DEFAULT_SUPPORTING_ATTACK_OPTIONS, DEFAULT_SUPPORT_BY_FIRE_OPTIONS, DEFAULT_TACTICAL_ARROW_OPTIONS, DEFAULT_TURN_OPTIONS, EPSILON, ambushDrawRule, attackByFireDrawRule, axis1DrawRule, blockDrawRule, calculateMetrics, cloneControlMeasure, computeDefaultMidpointPerpendicularPoint, computeInitialWidthPoint, controlMeasureIdFromFeature, createBaselineFrame, createMidpointPerpendicularDrawRule, disruptDrawRule, getControlMeasureMetadata, getControlMeasureMetadataByValue, getDefaultOptions, getMetersPerPixel, getMidpointPerpendicularSignedDistance, isKind, line1DrawRule, line23DrawRule, line24DrawRule, listControlMeasureMetadata, point12DrawRule, pointOnMidpointPerpendicularAxis, project, renderControlMeasure, resolveStyleHints, roundToFixed, snapToMidpointPerpendicular, supportByFireDrawRule, toSimpleStyle, turnDrawRule, unproject };
@@ -1,4 +1,4 @@
1
- import { q as ControlMeasureId } from "../index-D7uBPw7l.mjs";
1
+ import { q as ControlMeasureId } from "../index-BJYbQlzo.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-C7pY-TcL.mjs";
1
+ import { a as DEFINITIONS, t as renderControlMeasure } from "../renderControlMeasure-CONa8M6M.mjs";
2
2
  //#region src/preview/index.ts
3
3
  /**
4
4
  * Unitless `previewSample` points (~`[-1, 1]`) are scaled by this degree offset
@@ -483,6 +483,32 @@ function samePosition(a, b) {
483
483
  return a[0] === b[0] && a[1] === b[1] && a[2] === b[2];
484
484
  }
485
485
  //#endregion
486
+ //#region src/draw-rules/area1.ts
487
+ function derive$7(points) {
488
+ return clonePositions(points);
489
+ }
490
+ /**
491
+ * Area1 anchor draw rule — a free closed area (Battle Position, Strong Point,
492
+ * Encirclement, Fortified Area).
493
+ *
494
+ * Every anchor is a fully free, user-clicked ring vertex; the rule imposes no
495
+ * constraint and derives no extra slots, so `derive`/`transform` pass the
496
+ * points through unchanged. It is the area analog of the free-line {@link
497
+ * line1DrawRule}: variable-length (no `canonicalPointCount`) with a three-point
498
+ * floor to close a polygon. Behaviorally a no-op over the bare variable-length
499
+ * path — it exists so these drawable areas expose a named rule on
500
+ * `metadata.rule` rather than leaving it `undefined`, matching every other
501
+ * doctrinal draw rule. Ring closure is a generator concern, not a draw-rule one.
502
+ */
503
+ const area1DrawRule = {
504
+ id: "area1",
505
+ minimumUserPoints: 3,
506
+ derive: derive$7,
507
+ transform(event) {
508
+ return derive$7(event.next);
509
+ }
510
+ };
511
+ //#endregion
486
512
  //#region src/draw-rules/area11.ts
487
513
  const blockDrawRule = createMidpointPerpendicularDrawRule({ id: "area11:block" });
488
514
  //#endregion
@@ -875,11 +901,30 @@ const FIRE_ARROW_PARAMS = [
875
901
  step: 1
876
902
  }
877
903
  ];
878
- const FORTIFIED_AREA_PARAMS = [
904
+ const FORTIFIED_AREA_SIZE_PARAMS = [{
905
+ key: "sizePixels",
906
+ label: "Size",
907
+ description: "Size of the fortified-area teeth, in pixels",
908
+ type: "number",
909
+ min: 5,
910
+ max: 50,
911
+ step: 1,
912
+ unit: "px"
913
+ }, {
914
+ key: "size",
915
+ label: "Size",
916
+ description: "Size of the fortified-area teeth, in meters",
917
+ type: "number",
918
+ min: 1,
919
+ max: 500,
920
+ step: 1,
921
+ unit: "m"
922
+ }];
923
+ const ENCIRCLEMENT_PARAMS = [
879
924
  {
880
925
  key: "smooth",
881
926
  label: "Smooth",
882
- description: "Round the area's corners by curving the boundary through the control points.",
927
+ description: "Round the boundary by curving it through the control points.",
883
928
  type: "boolean"
884
929
  },
885
930
  {
@@ -892,25 +937,43 @@ const FORTIFIED_AREA_PARAMS = [
892
937
  step: 1,
893
938
  visibleWhen: (opts) => Boolean(opts.smooth)
894
939
  },
895
- ...[{
896
- key: "sizePixels",
897
- label: "Size",
898
- description: "Size of the fortified-area teeth, in pixels",
940
+ {
941
+ key: "barbSpacingRatio",
942
+ label: "Spacing",
943
+ description: "Spacing between barbs as a ratio of the symbol radius (smaller is denser).",
899
944
  type: "number",
900
- min: 5,
901
- max: 50,
902
- step: 1,
903
- unit: "px"
904
- }, {
905
- key: "size",
906
- label: "Size",
907
- description: "Size of the fortified-area teeth, in meters",
945
+ min: .15,
946
+ max: 1.5,
947
+ step: .05
948
+ },
949
+ {
950
+ key: "barbLengthRatio",
951
+ label: "Barb length",
952
+ description: "Barb height as a ratio of the symbol radius (amplitude).",
908
953
  type: "number",
909
- min: 1,
910
- max: 500,
954
+ min: .02,
955
+ max: .5,
956
+ step: .01
957
+ }
958
+ ];
959
+ const FORTIFIED_AREA_PARAMS = [
960
+ {
961
+ key: "smooth",
962
+ label: "Smooth",
963
+ description: "Round the area's corners by curving the boundary through the control points.",
964
+ type: "boolean"
965
+ },
966
+ {
967
+ key: "smoothResolution",
968
+ label: "Smooth resolution",
969
+ description: "Number of samples per segment when smooth mode is enabled.",
970
+ type: "number",
971
+ min: 2,
972
+ max: 64,
911
973
  step: 1,
912
- unit: "m"
913
- }]
974
+ visibleWhen: (opts) => Boolean(opts.smooth)
975
+ },
976
+ ...FORTIFIED_AREA_SIZE_PARAMS
914
977
  ];
915
978
  //#endregion
916
979
  //#region src/generators/cm15-maneuver-areas/airborneAttack.ts
@@ -2181,8 +2244,121 @@ function closedCatmullRom(points, samples) {
2181
2244
  return out;
2182
2245
  }
2183
2246
  //#endregion
2247
+ //#region src/internal/area-echelon.ts
2248
+ /** Mean of the ring's distinct vertices, used as the outward-normal reference. */
2249
+ function ringCentroid(verts, count) {
2250
+ let cx = 0;
2251
+ let cy = 0;
2252
+ for (let i = 0; i < count; i++) {
2253
+ cx += verts[i][0];
2254
+ cy += verts[i][1];
2255
+ }
2256
+ return [cx / count, cy / count];
2257
+ }
2258
+ /**
2259
+ * Projects `positions` and returns a closed ring (first == last), optionally
2260
+ * smoothed through the control points with a closed centripetal Catmull-Rom
2261
+ * spline. A trailing vertex that duplicates the first (already-closed input)
2262
+ * is dropped before closing/smoothing.
2263
+ */
2264
+ function buildClosedRing(positions, smooth, smoothResolution, defaultSmoothResolution) {
2265
+ const projected = positions.map((p) => project(p[0], p[1]));
2266
+ const ringPts = projected.length > 1 && Math.abs(projected[0][0] - projected[projected.length - 1][0]) < 1e-6 && Math.abs(projected[0][1] - projected[projected.length - 1][1]) < 1e-6 ? projected.slice(0, -1) : projected;
2267
+ return smooth ? closedCatmullRom(ringPts, normalizeSmoothResolution$2(smoothResolution, defaultSmoothResolution)) : [...ringPts, ringPts[0]];
2268
+ }
2269
+ /**
2270
+ * Walks a closed ring into per-segment metadata (unit direction, outward
2271
+ * normal, cumulative arc length) plus the total perimeter and the arc-length
2272
+ * anchor of the bottom edge — the segment whose midpoint sits lowest (min
2273
+ * projected y), the side opposite the front.
2274
+ */
2275
+ function buildAreaPerimeter(verts) {
2276
+ const centroid = ringCentroid(verts, verts.length - 1);
2277
+ const segs = [];
2278
+ let totalLength = 0;
2279
+ let anchorTarget = 0;
2280
+ let lowest = Infinity;
2281
+ for (let i = 0; i < verts.length - 1; i++) {
2282
+ const a = verts[i];
2283
+ const b = verts[i + 1];
2284
+ const edge = vecSub(b, a);
2285
+ const length = vecMag(edge);
2286
+ if (length < 1e-6) continue;
2287
+ const along = vecNorm(edge);
2288
+ const mid = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2];
2289
+ let perp = [-along[1], along[0]];
2290
+ if (perp[0] * (mid[0] - centroid[0]) + perp[1] * (mid[1] - centroid[1]) < 0) perp = [-perp[0], -perp[1]];
2291
+ segs.push({
2292
+ start: a,
2293
+ along,
2294
+ perp,
2295
+ length,
2296
+ arcStart: totalLength
2297
+ });
2298
+ if (mid[1] < lowest) {
2299
+ lowest = mid[1];
2300
+ anchorTarget = totalLength + length / 2;
2301
+ }
2302
+ totalLength += length;
2303
+ }
2304
+ return {
2305
+ verts,
2306
+ segs,
2307
+ totalLength,
2308
+ anchorTarget
2309
+ };
2310
+ }
2311
+ /** Resolves the arc-length `target` to a point and the segment frame it lands on. */
2312
+ function pointOnRing(segs, target) {
2313
+ let seg = segs[segs.length - 1];
2314
+ for (const candidate of segs) if (target < candidate.arcStart + candidate.length) {
2315
+ seg = candidate;
2316
+ break;
2317
+ }
2318
+ return {
2319
+ point: vecAdd(seg.start, vecScale(seg.along, target - seg.arcStart)),
2320
+ seg
2321
+ };
2322
+ }
2323
+ /**
2324
+ * Effective echelon glyph height in meters. Screen-anchored when a pixel size
2325
+ * is supplied with a live resolution; ground-anchored (meters) otherwise.
2326
+ * Clamped strictly positive.
2327
+ */
2328
+ function resolveEchelonHeight(echelonSize, echelonSizePixels, metersPerPixel) {
2329
+ let h = echelonSize;
2330
+ if (echelonSizePixels !== void 0 && metersPerPixel !== void 0 && metersPerPixel > 0) h = echelonSizePixels * metersPerPixel;
2331
+ return Math.max(EPSILON, h);
2332
+ }
2333
+ /**
2334
+ * Places the Field B echelon glyph on the perimeter and computes its masking
2335
+ * gap, anchored to the bottom edge and slid by `echelonPosition` (a signed
2336
+ * fraction of the perimeter, wrapping at the seam). Returns empty geometry
2337
+ * when the echelon resolves to none or the ring is degenerate.
2338
+ */
2339
+ function placeEchelon(perimeter, h, echelon, echelonPadding, echelonPosition) {
2340
+ const { segs, totalLength, anchorTarget } = perimeter;
2341
+ const spec = resolveEchelon(echelon);
2342
+ if (!spec || segs.length === 0 || totalLength < 1e-6) return {
2343
+ strokes: [],
2344
+ fills: [],
2345
+ gaps: []
2346
+ };
2347
+ const target = ((anchorTarget + echelonPosition * totalLength) % totalLength + totalLength) % totalLength;
2348
+ const { point, seg } = pointOnRing(segs, target);
2349
+ const { strokes, fills, width } = buildEchelonGlyph(point, seg.along, seg.perp, h, spec);
2350
+ const gaps = [];
2351
+ const halfGap = width / 2 + Math.max(0, echelonPadding) * h;
2352
+ pushWrappedGap(gaps, target - halfGap, target + halfGap, totalLength);
2353
+ return {
2354
+ strokes,
2355
+ fills,
2356
+ gaps
2357
+ };
2358
+ }
2359
+ //#endregion
2184
2360
  //#region src/generators/cm15-maneuver-areas/battlePosition.ts
2185
- const DEFAULT_SMOOTH_RESOLUTION$4 = 16;
2361
+ const DEFAULT_SMOOTH_RESOLUTION$6 = 16;
2186
2362
  /** Default options for the Battle Position control measure. */
2187
2363
  const DEFAULT_BATTLE_POSITION_OPTIONS = {
2188
2364
  echelon: "battalion",
@@ -2191,7 +2367,7 @@ const DEFAULT_BATTLE_POSITION_OPTIONS = {
2191
2367
  echelonPadding: .3,
2192
2368
  echelonPosition: 0,
2193
2369
  smooth: false,
2194
- smoothResolution: DEFAULT_SMOOTH_RESOLUTION$4
2370
+ smoothResolution: DEFAULT_SMOOTH_RESOLUTION$6
2195
2371
  };
2196
2372
  const BATTLE_POSITION_METADATA = {
2197
2373
  id: "battle-position",
@@ -2268,16 +2444,6 @@ const BATTLE_POSITION_METADATA = {
2268
2444
  }
2269
2445
  ]
2270
2446
  };
2271
- /** Mean of the ring's distinct vertices, used as the outward-normal reference. */
2272
- function ringCentroid(verts, count) {
2273
- let cx = 0;
2274
- let cy = 0;
2275
- for (let i = 0; i < count; i++) {
2276
- cx += verts[i][0];
2277
- cy += verts[i][1];
2278
- }
2279
- return [cx / count, cy / count];
2280
- }
2281
2447
  /**
2282
2448
  * Creates a Battle Position control measure: a closed area through `positions`
2283
2449
  * carrying a single echelon (Field B) glyph straddling its boundary — anchored
@@ -2289,59 +2455,9 @@ function ringCentroid(verts, count) {
2289
2455
  */
2290
2456
  function createBattlePosition(positions, options = {}) {
2291
2457
  const { echelon = DEFAULT_BATTLE_POSITION_OPTIONS.echelon, echelonSize = DEFAULT_BATTLE_POSITION_OPTIONS.echelonSize, echelonSizePixels, echelonPadding = DEFAULT_BATTLE_POSITION_OPTIONS.echelonPadding, echelonPosition = DEFAULT_BATTLE_POSITION_OPTIONS.echelonPosition, metersPerPixel, smooth = DEFAULT_BATTLE_POSITION_OPTIONS.smooth, smoothResolution = DEFAULT_BATTLE_POSITION_OPTIONS.smoothResolution } = options;
2292
- let h = echelonSize;
2293
- if (echelonSizePixels !== void 0 && metersPerPixel !== void 0 && metersPerPixel > 0) h = echelonSizePixels * metersPerPixel;
2294
- h = Math.max(EPSILON, h);
2295
- const spec = resolveEchelon(echelon);
2296
- const projected = positions.map((p) => project(p[0], p[1]));
2297
- const ringPts = projected.length > 1 && Math.abs(projected[0][0] - projected[projected.length - 1][0]) < 1e-6 && Math.abs(projected[0][1] - projected[projected.length - 1][1]) < 1e-6 ? projected.slice(0, -1) : projected;
2298
- const verts = smooth ? closedCatmullRom(ringPts, normalizeSmoothResolution$2(smoothResolution, DEFAULT_SMOOTH_RESOLUTION$4)) : [...ringPts, ringPts[0]];
2299
- const glyphStrokes = [];
2300
- const glyphFills = [];
2301
- const gaps = [];
2302
- if (spec) {
2303
- const centroid = ringCentroid(verts, verts.length - 1);
2304
- const segs = [];
2305
- let totalLength = 0;
2306
- let anchorTarget = 0;
2307
- let lowest = Infinity;
2308
- for (let i = 0; i < verts.length - 1; i++) {
2309
- const a = verts[i];
2310
- const b = verts[i + 1];
2311
- const seg = vecSub(b, a);
2312
- const L = vecMag(seg);
2313
- if (L < 1e-6) continue;
2314
- const along = vecNorm(seg);
2315
- const mid = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2];
2316
- let perp = [-along[1], along[0]];
2317
- if (perp[0] * (mid[0] - centroid[0]) + perp[1] * (mid[1] - centroid[1]) < 0) perp = [-perp[0], -perp[1]];
2318
- segs.push({
2319
- start: a,
2320
- along,
2321
- perp,
2322
- length: L,
2323
- acc: totalLength
2324
- });
2325
- if (mid[1] < lowest) {
2326
- lowest = mid[1];
2327
- anchorTarget = totalLength + L / 2;
2328
- }
2329
- totalLength += L;
2330
- }
2331
- if (segs.length > 0 && totalLength >= 1e-6) {
2332
- const target = ((anchorTarget + echelonPosition * totalLength) % totalLength + totalLength) % totalLength;
2333
- let seg = segs[segs.length - 1];
2334
- for (const candidate of segs) if (target < candidate.acc + candidate.length) {
2335
- seg = candidate;
2336
- break;
2337
- }
2338
- const { strokes, fills, width } = buildEchelonGlyph(vecAdd(seg.start, vecScale(seg.along, target - seg.acc)), seg.along, seg.perp, h, spec);
2339
- glyphStrokes.push(...strokes);
2340
- glyphFills.push(...fills);
2341
- const halfGap = width / 2 + Math.max(0, echelonPadding) * h;
2342
- pushWrappedGap(gaps, target - halfGap, target + halfGap, totalLength);
2343
- }
2344
- }
2458
+ const h = resolveEchelonHeight(echelonSize, echelonSizePixels, metersPerPixel);
2459
+ const verts = buildClosedRing(positions, smooth, smoothResolution, DEFAULT_SMOOTH_RESOLUTION$6);
2460
+ const { strokes, fills, gaps } = placeEchelon(buildAreaPerimeter(verts), h, echelon, echelonPadding, echelonPosition);
2345
2461
  const boundaryCoords = buildGappedLine(verts, gaps);
2346
2462
  const features = [];
2347
2463
  if (boundaryCoords.length > 0) features.push({
@@ -2352,15 +2468,15 @@ function createBattlePosition(positions, options = {}) {
2352
2468
  coordinates: boundaryCoords
2353
2469
  }
2354
2470
  });
2355
- if (glyphStrokes.length > 0) features.push({
2471
+ if (strokes.length > 0) features.push({
2356
2472
  type: "Feature",
2357
2473
  properties: { part: "echelon" },
2358
2474
  geometry: {
2359
2475
  type: "MultiLineString",
2360
- coordinates: glyphStrokes
2476
+ coordinates: strokes
2361
2477
  }
2362
2478
  });
2363
- if (glyphFills.length > 0) features.push({
2479
+ if (fills.length > 0) features.push({
2364
2480
  type: "Feature",
2365
2481
  properties: {
2366
2482
  part: "echelon",
@@ -2368,7 +2484,7 @@ function createBattlePosition(positions, options = {}) {
2368
2484
  },
2369
2485
  geometry: {
2370
2486
  type: "MultiPolygon",
2371
- coordinates: glyphFills.map((ring) => [ring])
2487
+ coordinates: fills.map((ring) => [ring])
2372
2488
  }
2373
2489
  });
2374
2490
  return {
@@ -2380,6 +2496,7 @@ const BATTLE_POSITION = defineControlMeasure({
2380
2496
  metadata: BATTLE_POSITION_METADATA,
2381
2497
  generator: createBattlePosition,
2382
2498
  defaultOptions: DEFAULT_BATTLE_POSITION_OPTIONS,
2499
+ rule: area1DrawRule,
2383
2500
  previewSample: {
2384
2501
  controlPoints: [
2385
2502
  [-1, -.5],
@@ -2387,7 +2504,10 @@ const BATTLE_POSITION = defineControlMeasure({
2387
2504
  [1, .5],
2388
2505
  [-1, .5]
2389
2506
  ],
2390
- options: { echelonSize: 375 }
2507
+ options: {
2508
+ echelonSize: 200,
2509
+ smooth: true
2510
+ }
2391
2511
  }
2392
2512
  });
2393
2513
  //#endregion
@@ -2458,7 +2578,7 @@ const BLOCK = defineControlMeasure({
2458
2578
  });
2459
2579
  //#endregion
2460
2580
  //#region src/generators/cm99-generic-arrows/blockArrow.ts
2461
- const DEFAULT_SMOOTH_RESOLUTION$3 = 12;
2581
+ const DEFAULT_SMOOTH_RESOLUTION$5 = 12;
2462
2582
  const MIN_SMOOTH_RESOLUTION$1 = 2;
2463
2583
  const MAX_SMOOTH_RESOLUTION$1 = 64;
2464
2584
  const DEFAULT_BLOCK_ARROW_OPTIONS = {
@@ -2467,7 +2587,7 @@ const DEFAULT_BLOCK_ARROW_OPTIONS = {
2467
2587
  arrowheadWidthRatio: .18,
2468
2588
  arrowheadLengthRatio: .22,
2469
2589
  smooth: false,
2470
- smoothResolution: DEFAULT_SMOOTH_RESOLUTION$3,
2590
+ smoothResolution: DEFAULT_SMOOTH_RESOLUTION$5,
2471
2591
  filled: true
2472
2592
  };
2473
2593
  const BLOCK_ARROW_METADATA = {
@@ -2704,7 +2824,7 @@ function axis1Geometry(coordinates) {
2704
2824
  };
2705
2825
  }
2706
2826
  function normalizeSmoothResolution$1(value) {
2707
- if (!Number.isFinite(value)) return DEFAULT_SMOOTH_RESOLUTION$3;
2827
+ if (!Number.isFinite(value)) return DEFAULT_SMOOTH_RESOLUTION$5;
2708
2828
  return Math.min(MAX_SMOOTH_RESOLUTION$1, Math.max(MIN_SMOOTH_RESOLUTION$1, Math.round(value)));
2709
2829
  }
2710
2830
  //#endregion
@@ -2730,7 +2850,7 @@ function keepTextLeftToRight(rotation) {
2730
2850
  }
2731
2851
  //#endregion
2732
2852
  //#region src/generators/cm11-command-control-lines/boundary.ts
2733
- const DEFAULT_SMOOTH_RESOLUTION$2 = 12;
2853
+ const DEFAULT_SMOOTH_RESOLUTION$4 = 12;
2734
2854
  /** Default options for the Boundary control measure. */
2735
2855
  const DEFAULT_BOUNDARY_OPTIONS = {
2736
2856
  echelon: "battalion",
@@ -2744,7 +2864,7 @@ const DEFAULT_BOUNDARY_OPTIONS = {
2744
2864
  unit2Designator: "",
2745
2865
  unit2Country: "",
2746
2866
  smooth: false,
2747
- smoothResolution: DEFAULT_SMOOTH_RESOLUTION$2
2867
+ smoothResolution: DEFAULT_SMOOTH_RESOLUTION$4
2748
2868
  };
2749
2869
  const BOUNDARY_METADATA = {
2750
2870
  id: "boundary",
@@ -2897,7 +3017,7 @@ function createBoundary(positions, options = {}) {
2897
3017
  if (labelSizeZoom !== void 0) labelSize.textSizeZoom = labelSizeZoom;
2898
3018
  }
2899
3019
  const rawVerts = positions.map((p) => project(p[0], p[1]));
2900
- const verts = smooth ? catmullRom(rawVerts, normalizeSmoothResolution$2(smoothResolution, DEFAULT_SMOOTH_RESOLUTION$2)) : rawVerts;
3020
+ const verts = smooth ? catmullRom(rawVerts, normalizeSmoothResolution$2(smoothResolution, DEFAULT_SMOOTH_RESOLUTION$4)) : rawVerts;
2901
3021
  const glyphStrokes = [];
2902
3022
  const glyphFills = [];
2903
3023
  const labelFeatures = [];
@@ -3488,13 +3608,13 @@ const CANALIZE = defineControlMeasure({
3488
3608
  });
3489
3609
  //#endregion
3490
3610
  //#region src/generators/cm99-generic-arrows/classicArrow.ts
3491
- const DEFAULT_SMOOTH_RESOLUTION$1 = 12;
3611
+ const DEFAULT_SMOOTH_RESOLUTION$3 = 12;
3492
3612
  const MIN_SMOOTH_RESOLUTION = 2;
3493
3613
  const MAX_SMOOTH_RESOLUTION = 64;
3494
3614
  const DEFAULT_CLASSIC_ARROW_OPTIONS = {
3495
3615
  arrowheadStyle: "triangle",
3496
3616
  smooth: false,
3497
- smoothResolution: DEFAULT_SMOOTH_RESOLUTION$1,
3617
+ smoothResolution: DEFAULT_SMOOTH_RESOLUTION$3,
3498
3618
  arrowheadLengthRatio: .2,
3499
3619
  arrowheadWidthRatio: .14
3500
3620
  };
@@ -3781,7 +3901,7 @@ const CLASSIC_ARROW = defineControlMeasure({
3781
3901
  rule: line1DrawRule
3782
3902
  });
3783
3903
  function normalizeSmoothResolution(value) {
3784
- if (!Number.isFinite(value)) return DEFAULT_SMOOTH_RESOLUTION$1;
3904
+ if (!Number.isFinite(value)) return DEFAULT_SMOOTH_RESOLUTION$3;
3785
3905
  return Math.min(MAX_SMOOTH_RESOLUTION, Math.max(MIN_SMOOTH_RESOLUTION, Math.round(value)));
3786
3906
  }
3787
3907
  //#endregion
@@ -4271,6 +4391,139 @@ const DISRUPT = defineControlMeasure({
4271
4391
  previewSample: { controlPoints: [[0, .4], [0, -.4]] }
4272
4392
  });
4273
4393
  //#endregion
4394
+ //#region src/generators/cm15-maneuver-areas/encirclement.ts
4395
+ const DEFAULT_BARB_SPACING_RATIO = .4;
4396
+ const DEFAULT_BARB_LENGTH_RATIO = .16;
4397
+ const DEFAULT_SMOOTH_RESOLUTION$2 = 16;
4398
+ /** Default options for the Encirclement control measure. */
4399
+ const DEFAULT_ENCIRCLEMENT_OPTIONS = {
4400
+ barbSpacingRatio: DEFAULT_BARB_SPACING_RATIO,
4401
+ barbLengthRatio: DEFAULT_BARB_LENGTH_RATIO,
4402
+ smooth: true,
4403
+ smoothResolution: DEFAULT_SMOOTH_RESOLUTION$2
4404
+ };
4405
+ const ENCIRCLEMENT_METADATA = {
4406
+ id: "encirclement",
4407
+ name: "Encirclement",
4408
+ description: "The loss of freedom of maneuver resulting from enemy control of all ground routes of evacuation and reinforcement.",
4409
+ entity: "Maneuver Areas",
4410
+ entityType: "Encirclement",
4411
+ value: "151800",
4412
+ minCoordinates: 3,
4413
+ geometry: "area",
4414
+ geometryTypes: ["Polygon", "MultiLineString"],
4415
+ drawRule: "Area1",
4416
+ params: ENCIRCLEMENT_PARAMS
4417
+ };
4418
+ /** Mean distance from the ring centroid to its distinct vertices. */
4419
+ function meanRadius(verts) {
4420
+ const n = verts.length - 1;
4421
+ if (n <= 0) return 0;
4422
+ let cx = 0;
4423
+ let cy = 0;
4424
+ for (let i = 0; i < n; i++) {
4425
+ cx += verts[i][0];
4426
+ cy += verts[i][1];
4427
+ }
4428
+ cx /= n;
4429
+ cy /= n;
4430
+ let sum = 0;
4431
+ for (let i = 0; i < n; i++) sum += Math.hypot(verts[i][0] - cx, verts[i][1] - cy);
4432
+ return sum / n;
4433
+ }
4434
+ /**
4435
+ * Builds the outward barbs as open "V" strokes sitting on the boundary, evenly
4436
+ * spaced around the perimeter. The requested `spacing` (arc length) is rounded
4437
+ * to a whole number of barbs so they close the ring exactly. Each barb runs
4438
+ * base-start → apex → base-end with the apex pushed out along the local outward
4439
+ * normal; the base ends are sampled on the ring at ±`barbLength/2` of arc so the
4440
+ * barb's base width equals its length (a clean arrowhead, as in Isolate) and
4441
+ * follows the boundary curvature. The base half-width is capped so dense barbs
4442
+ * never overlap.
4443
+ */
4444
+ function buildBarbs(perimeter, spacing, barbLength) {
4445
+ const { segs, totalLength } = perimeter;
4446
+ if (segs.length === 0 || totalLength < 1e-6 || barbLength < 1e-6 || spacing < 1e-6) return [];
4447
+ const count = Math.max(3, Math.round(totalLength / spacing));
4448
+ const half = Math.min(barbLength / 2, totalLength / (2 * count));
4449
+ const at = (arc) => pointOnRing(segs, (arc % totalLength + totalLength) % totalLength);
4450
+ const barbs = [];
4451
+ for (let k = 0; k < count; k++) {
4452
+ const center = k * totalLength / count;
4453
+ const baseStart = at(center - half).point;
4454
+ const { point, seg } = at(center);
4455
+ const apex = vecAdd(point, vecScale(seg.perp, barbLength));
4456
+ const baseEnd = at(center + half).point;
4457
+ barbs.push([
4458
+ baseStart,
4459
+ apex,
4460
+ baseEnd
4461
+ ].map((p) => unproject(p[0], p[1])));
4462
+ }
4463
+ return barbs;
4464
+ }
4465
+ /**
4466
+ * Creates an Encirclement tactical graphic: a complete smooth boundary ring
4467
+ * with outward arrowhead barbs resting on it. The boundary is an unfilled
4468
+ * Polygon; the barbs are open "V" strokes (a MultiLineString) whose base is the
4469
+ * boundary arc itself.
4470
+ *
4471
+ * @param positions - Area anchor points (>=3); the input contract is enforced
4472
+ * at the render seam (ADR-0014).
4473
+ * @param options - {@link EncirclementOptions}.
4474
+ */
4475
+ function createEncirclement(positions, options = {}) {
4476
+ const { barbSpacingRatio = DEFAULT_BARB_SPACING_RATIO, barbLengthRatio = DEFAULT_BARB_LENGTH_RATIO, smooth = DEFAULT_ENCIRCLEMENT_OPTIONS.smooth, smoothResolution = DEFAULT_ENCIRCLEMENT_OPTIONS.smoothResolution } = options;
4477
+ const verts = buildClosedRing(positions, smooth, smoothResolution, DEFAULT_SMOOTH_RESOLUTION$2);
4478
+ const perimeter = buildAreaPerimeter(verts);
4479
+ const radius = meanRadius(verts);
4480
+ const barbLength = radius * Math.max(0, barbLengthRatio);
4481
+ const barbs = buildBarbs(perimeter, radius * Math.max(0, barbSpacingRatio), barbLength);
4482
+ const features = [{
4483
+ type: "Feature",
4484
+ properties: { part: "boundary" },
4485
+ geometry: {
4486
+ type: "Polygon",
4487
+ coordinates: [verts.map((p) => unproject(p[0], p[1]))]
4488
+ }
4489
+ }];
4490
+ if (barbs.length > 0) features.push({
4491
+ type: "Feature",
4492
+ properties: { part: "barbs" },
4493
+ geometry: {
4494
+ type: "MultiLineString",
4495
+ coordinates: barbs
4496
+ }
4497
+ });
4498
+ return {
4499
+ type: "FeatureCollection",
4500
+ features
4501
+ };
4502
+ }
4503
+ const ENCIRCLEMENT = defineControlMeasure({
4504
+ metadata: ENCIRCLEMENT_METADATA,
4505
+ generator: createEncirclement,
4506
+ defaultOptions: DEFAULT_ENCIRCLEMENT_OPTIONS,
4507
+ rule: area1DrawRule,
4508
+ previewSample: {
4509
+ controlPoints: [
4510
+ [-1, -.5],
4511
+ [-.4, -.85],
4512
+ [.4, -.85],
4513
+ [1, -.5],
4514
+ [1, .5],
4515
+ [.4, .85],
4516
+ [-.4, .85],
4517
+ [-1, .5]
4518
+ ],
4519
+ options: {
4520
+ barbSpacingRatio: .4,
4521
+ barbLengthRatio: .18,
4522
+ smooth: true
4523
+ }
4524
+ }
4525
+ });
4526
+ //#endregion
4274
4527
  //#region src/internal/field-of-fire.ts
4275
4528
  /**
4276
4529
  * Builds the open (stroked) arrowhead at the outward tip of one arm. The two
@@ -4810,7 +5063,7 @@ const FLOT = defineControlMeasure({
4810
5063
  //#region src/generators/cm29-protection-lines/fortifiedLine.ts
4811
5064
  const DEFAULT_SIZE = 50;
4812
5065
  const DEFAULT_SIZE_PIXELS = 10;
4813
- const DEFAULT_SMOOTH_RESOLUTION = 16;
5066
+ const DEFAULT_SMOOTH_RESOLUTION$1 = 16;
4814
5067
  /**
4815
5068
  * Default options for the Fortified Line graphic.
4816
5069
  */
@@ -4818,7 +5071,7 @@ const DEFAULT_FORTIFIED_LINE_OPTIONS = {
4818
5071
  size: DEFAULT_SIZE,
4819
5072
  sizePixels: DEFAULT_SIZE_PIXELS,
4820
5073
  smooth: false,
4821
- smoothResolution: DEFAULT_SMOOTH_RESOLUTION
5074
+ smoothResolution: DEFAULT_SMOOTH_RESOLUTION$1
4822
5075
  };
4823
5076
  const FORTIFIED_LINE_METADATA = {
4824
5077
  id: "fortified-line",
@@ -4950,7 +5203,7 @@ function createFortifiedLine(positions, options = {}) {
4950
5203
  properties: {},
4951
5204
  geometry: {
4952
5205
  type: "LineString",
4953
- coordinates: generateFortifiedPoints(smooth ? catmullRom(projectedPoints, normalizeSmoothResolution$2(smoothResolution, DEFAULT_SMOOTH_RESOLUTION)) : projectedPoints, safeSize).map((p) => unproject(p[0], p[1]))
5206
+ coordinates: generateFortifiedPoints(smooth ? catmullRom(projectedPoints, normalizeSmoothResolution$2(smoothResolution, DEFAULT_SMOOTH_RESOLUTION$1)) : projectedPoints, safeSize).map((p) => unproject(p[0], p[1]))
4954
5207
  }
4955
5208
  }]
4956
5209
  };
@@ -5018,6 +5271,7 @@ const FORTIFIED_AREA = defineControlMeasure({
5018
5271
  metadata: FORTIFIED_AREA_METADATA,
5019
5272
  generator: createFortifiedArea,
5020
5273
  defaultOptions: DEFAULT_FORTIFIED_AREA_OPTIONS,
5274
+ rule: area1DrawRule,
5021
5275
  previewSample: { controlPoints: [
5022
5276
  [-1, -.5],
5023
5277
  [1, -.5],
@@ -5875,6 +6129,192 @@ const SEARCH_AREA = defineControlMeasure({
5875
6129
  rule: searchAreaDrawRule
5876
6130
  });
5877
6131
  //#endregion
6132
+ //#region src/generators/cm15-maneuver-areas/strongPoint.ts
6133
+ const DEFAULT_SMOOTH_RESOLUTION = 16;
6134
+ /** Default options for the Strong Point control measure. */
6135
+ const DEFAULT_STRONG_POINT_OPTIONS = { ...DEFAULT_BATTLE_POSITION_OPTIONS };
6136
+ const STRONG_POINT_METADATA = {
6137
+ id: "strong-point",
6138
+ name: "Strong Point",
6139
+ description: "A key point in a defensive position, usually strongly fortified and heavily armed, around which other positions are grouped for its protection.",
6140
+ entity: "Maneuver Areas",
6141
+ entityType: "Battle Position",
6142
+ entitySubtype: "Strong Point",
6143
+ value: "151203",
6144
+ minCoordinates: 3,
6145
+ geometry: "area",
6146
+ geometryTypes: ["MultiLineString", "MultiPolygon"],
6147
+ drawRule: "Area1",
6148
+ params: [
6149
+ {
6150
+ key: "smooth",
6151
+ label: "Smooth",
6152
+ description: "Round the area's corners by curving the boundary through the control points.",
6153
+ type: "boolean"
6154
+ },
6155
+ {
6156
+ key: "smoothResolution",
6157
+ label: "Smooth resolution",
6158
+ description: "Number of samples per segment when smooth mode is enabled.",
6159
+ type: "number",
6160
+ min: 2,
6161
+ max: 64,
6162
+ step: 1,
6163
+ visibleWhen: (opts) => Boolean(opts.smooth)
6164
+ },
6165
+ {
6166
+ key: "echelon",
6167
+ label: "Echelon",
6168
+ description: "Field B unit-size amplifier, drawn on the boundary.",
6169
+ type: "enum",
6170
+ options: ECHELON_PARAM_OPTIONS
6171
+ },
6172
+ {
6173
+ key: "echelonSizePixels",
6174
+ label: "Echelon size",
6175
+ description: "Echelon glyph height in screen pixels.",
6176
+ type: "number",
6177
+ min: 8,
6178
+ max: 48,
6179
+ step: 1,
6180
+ unit: "px"
6181
+ },
6182
+ {
6183
+ key: "echelonSize",
6184
+ label: "Echelon size",
6185
+ description: "Echelon glyph height in meters.",
6186
+ type: "number",
6187
+ min: 50,
6188
+ max: 2e4,
6189
+ step: 50,
6190
+ unit: "m"
6191
+ },
6192
+ {
6193
+ key: "echelonPadding",
6194
+ label: "Echelon padding",
6195
+ description: "Gap on each side of the echelon, as a ratio of its height.",
6196
+ type: "number",
6197
+ min: 0,
6198
+ max: 2,
6199
+ step: .05
6200
+ },
6201
+ {
6202
+ key: "echelonPosition",
6203
+ label: "Echelon position",
6204
+ description: "Slide the echelon along the boundary, as a fraction of the perimeter from the bottom edge.",
6205
+ type: "number",
6206
+ min: -.5,
6207
+ max: .5,
6208
+ step: .01
6209
+ }
6210
+ ]
6211
+ };
6212
+ /** True when the arc-length `target` falls inside any echelon masking gap. */
6213
+ function inGap(target, gaps) {
6214
+ return gaps.some((gap) => target >= gap.g0 && target <= gap.g1);
6215
+ }
6216
+ /**
6217
+ * Outward perimeter tics: a stroke every `h` meters along the ring, each `h`
6218
+ * meters long along the segment's outward normal. Tic length, spacing, and the
6219
+ * first-tic phase are all the Field B echelon height `h`. Targets inside an
6220
+ * echelon gap are suppressed so the tics don't collide with the glyph.
6221
+ *
6222
+ * The `target` grows monotonically, so a single forward segment cursor walks
6223
+ * the perimeter in O(segments + tics) instead of rescanning per tic.
6224
+ */
6225
+ function buildStrongPointTics(perimeter, gaps, h) {
6226
+ const { segs, totalLength } = perimeter;
6227
+ if (segs.length === 0 || totalLength < 1e-6) return [];
6228
+ const tics = [];
6229
+ let cursor = 0;
6230
+ for (let target = h / 2; target < totalLength; target += h) {
6231
+ if (inGap(target, gaps)) continue;
6232
+ while (cursor < segs.length - 1 && target >= segs[cursor].arcStart + segs[cursor].length) cursor++;
6233
+ const seg = segs[cursor];
6234
+ const point = vecAdd(seg.start, vecScale(seg.along, target - seg.arcStart));
6235
+ const end = vecAdd(point, vecScale(seg.perp, h));
6236
+ tics.push([unproject(point[0], point[1]), unproject(end[0], end[1])]);
6237
+ }
6238
+ return tics;
6239
+ }
6240
+ /**
6241
+ * Creates a Strong Point control measure: a Battle Position boundary/echelon
6242
+ * with outward tics whose length and spacing are the Field B echelon height.
6243
+ * Shares the perimeter and echelon placement with Battle Position via the
6244
+ * `area-echelon` primitive (single source of truth for ring and gap).
6245
+ *
6246
+ * @param positions - Area anchor points (>=3); the input contract is enforced
6247
+ * at the render seam (ADR-0014).
6248
+ * @param options - {@link StrongPointOptions}.
6249
+ */
6250
+ function createStrongPoint(positions, options = {}) {
6251
+ const { echelon = DEFAULT_STRONG_POINT_OPTIONS.echelon, echelonSize = DEFAULT_STRONG_POINT_OPTIONS.echelonSize, echelonSizePixels, echelonPadding = DEFAULT_STRONG_POINT_OPTIONS.echelonPadding, echelonPosition = DEFAULT_STRONG_POINT_OPTIONS.echelonPosition, metersPerPixel, smooth = DEFAULT_STRONG_POINT_OPTIONS.smooth, smoothResolution = DEFAULT_STRONG_POINT_OPTIONS.smoothResolution } = options;
6252
+ const h = resolveEchelonHeight(echelonSize, echelonSizePixels, metersPerPixel);
6253
+ const verts = buildClosedRing(positions, smooth, smoothResolution, DEFAULT_SMOOTH_RESOLUTION);
6254
+ const perimeter = buildAreaPerimeter(verts);
6255
+ const { strokes, fills, gaps } = placeEchelon(perimeter, h, echelon, echelonPadding, echelonPosition);
6256
+ const boundaryCoords = buildGappedLine(verts, gaps);
6257
+ const tics = buildStrongPointTics(perimeter, gaps, h);
6258
+ const features = [];
6259
+ if (boundaryCoords.length > 0) features.push({
6260
+ type: "Feature",
6261
+ properties: { part: "boundary" },
6262
+ geometry: {
6263
+ type: "MultiLineString",
6264
+ coordinates: boundaryCoords
6265
+ }
6266
+ });
6267
+ if (tics.length > 0) features.push({
6268
+ type: "Feature",
6269
+ properties: { part: "tics" },
6270
+ geometry: {
6271
+ type: "MultiLineString",
6272
+ coordinates: tics
6273
+ }
6274
+ });
6275
+ if (strokes.length > 0) features.push({
6276
+ type: "Feature",
6277
+ properties: { part: "echelon" },
6278
+ geometry: {
6279
+ type: "MultiLineString",
6280
+ coordinates: strokes
6281
+ }
6282
+ });
6283
+ if (fills.length > 0) features.push({
6284
+ type: "Feature",
6285
+ properties: {
6286
+ part: "echelon",
6287
+ fill: true
6288
+ },
6289
+ geometry: {
6290
+ type: "MultiPolygon",
6291
+ coordinates: fills.map((ring) => [ring])
6292
+ }
6293
+ });
6294
+ return {
6295
+ type: "FeatureCollection",
6296
+ features
6297
+ };
6298
+ }
6299
+ const STRONG_POINT = defineControlMeasure({
6300
+ metadata: STRONG_POINT_METADATA,
6301
+ generator: createStrongPoint,
6302
+ defaultOptions: DEFAULT_STRONG_POINT_OPTIONS,
6303
+ rule: area1DrawRule,
6304
+ previewSample: {
6305
+ controlPoints: [
6306
+ [-1, -.5],
6307
+ [1, -.5],
6308
+ [1, .5],
6309
+ [-1, .5]
6310
+ ],
6311
+ options: {
6312
+ echelonSize: 200,
6313
+ smooth: true
6314
+ }
6315
+ }
6316
+ });
6317
+ //#endregion
5878
6318
  //#region src/generators/cm15-maneuver-areas/supportByFire.ts
5879
6319
  const DEFAULT_SUPPORT_BY_FIRE_OPTIONS = {
5880
6320
  arrowheadWidthRatio: .3,
@@ -6203,11 +6643,13 @@ function rotate(vector, angle) {
6203
6643
  const DEFINITIONS = {
6204
6644
  boundary: BOUNDARY,
6205
6645
  "battle-position": BATTLE_POSITION,
6646
+ "strong-point": STRONG_POINT,
6206
6647
  ambush: AMBUSH,
6207
6648
  "principal-direction-of-fire": PRINCIPAL_DIRECTION_OF_FIRE,
6208
6649
  "final-protective-fire-left": FINAL_PROTECTIVE_FIRE_LEFT,
6209
6650
  "final-protective-fire-right": FINAL_PROTECTIVE_FIRE_RIGHT,
6210
6651
  "search-area": SEARCH_AREA,
6652
+ encirclement: ENCIRCLEMENT,
6211
6653
  "main-attack": MAIN_ATTACK,
6212
6654
  "supporting-attack": SUPPORTING_ATTACK,
6213
6655
  "classic-arrow": CLASSIC_ARROW,
@@ -6462,4 +6904,4 @@ function assertNever(value) {
6462
6904
  throw new Error(`Unhandled control measure kind: ${String(value)}`);
6463
6905
  }
6464
6906
  //#endregion
6465
- export { snapToMidpointPerpendicular as $, DEFAULT_BLOCK_MISSION_TASK_OPTIONS as A, line24DrawRule as B, DEFAULT_FINAL_PROTECTIVE_FIRE_OPTIONS as C, DEFAULT_CANALIZE_OPTIONS as D, DEFAULT_CLEAR_OPTIONS as E, DEFAULT_ANTITANK_DITCH_OPTIONS as F, attackByFireDrawRule as G, turnDrawRule as H, DEFAULT_AMBUSH_OPTIONS as I, blockDrawRule as J, point12DrawRule as K, DEFAULT_AIRBORNE_ATTACK_OPTIONS as L, DEFAULT_ATTACK_HELICOPTER_OPTIONS as M, DEFAULT_ATTACK_BY_FIRE_OPTIONS as N, DEFAULT_BYPASS_OPTIONS as O, DEFAULT_ANTITANK_WALL_OPTIONS as P, pointOnMidpointPerpendicularAxis as Q, axis1DrawRule as R, DEFAULT_FIX_OPTIONS as S, DEFAULT_DELAY_OPTIONS as T, line1DrawRule as U, line23DrawRule as V, ambushDrawRule as W, createMidpointPerpendicularDrawRule as X, computeDefaultMidpointPerpendicularPoint as Y, getMidpointPerpendicularSignedDistance as Z, DEFAULT_OBSTACLE_BYPASS_DIFFICULT_OPTIONS as _, DEFINITIONS as a, EPSILON as at, DEFAULT_FORTIFIED_LINE_OPTIONS as b, getDefaultOptions as c, DEFAULT_SUPPORTING_ATTACK_OPTIONS as d, createBaselineFrame as et, DEFAULT_SUPPORT_BY_FIRE_OPTIONS as f, DEFAULT_OBSTACLE_BYPASS_EASY_OPTIONS as g, DEFAULT_OBSTACLE_BYPASS_IMPOSSIBLE_OPTIONS as h, CONTROL_MEASURE_METADATA as i, unproject as it, DEFAULT_BLOCK_OPTIONS as j, DEFAULT_BREACH_OPTIONS as k, listControlMeasureMetadata as l, DEFAULT_PRINCIPAL_DIRECTION_OF_FIRE_OPTIONS as m, resolveStyleHints as n, computeInitialWidthPoint as nt, getControlMeasureMetadata as o, getMetersPerPixel as ot, DEFAULT_TACTICAL_ARROW_OPTIONS as p, disruptDrawRule as q, CONTROL_MEASURE_IDS as r, project as rt, getControlMeasureMetadataByValue as s, roundToFixed as st, renderControlMeasure as t, calculateMetrics as tt, DEFAULT_TURN_OPTIONS as u, DEFAULT_MAIN_ATTACK_OPTIONS as v, DEFAULT_DISRUPT_OPTIONS as w, DEFAULT_FLOT_OPTIONS as x, DEFAULT_FORTIFIED_AREA_OPTIONS as y, supportByFireDrawRule as z };
6907
+ export { getMidpointPerpendicularSignedDistance as $, DEFAULT_BYPASS_OPTIONS as A, axis1DrawRule as B, DEFAULT_FIX_OPTIONS as C, DEFAULT_DELAY_OPTIONS as D, DEFAULT_DISRUPT_OPTIONS as E, DEFAULT_ATTACK_BY_FIRE_OPTIONS as F, line1DrawRule as G, line24DrawRule as H, DEFAULT_ANTITANK_WALL_OPTIONS as I, point12DrawRule as J, ambushDrawRule as K, DEFAULT_ANTITANK_DITCH_OPTIONS as L, DEFAULT_BLOCK_MISSION_TASK_OPTIONS as M, DEFAULT_BLOCK_OPTIONS as N, DEFAULT_CLEAR_OPTIONS as O, DEFAULT_ATTACK_HELICOPTER_OPTIONS as P, createMidpointPerpendicularDrawRule as Q, DEFAULT_AMBUSH_OPTIONS as R, DEFAULT_FLOT_OPTIONS as S, DEFAULT_ENCIRCLEMENT_OPTIONS as T, line23DrawRule as U, supportByFireDrawRule as V, turnDrawRule as W, blockDrawRule as X, disruptDrawRule as Y, computeDefaultMidpointPerpendicularPoint as Z, DEFAULT_OBSTACLE_BYPASS_EASY_OPTIONS as _, DEFINITIONS as a, project as at, DEFAULT_FORTIFIED_AREA_OPTIONS as b, getDefaultOptions as c, getMetersPerPixel as ct, DEFAULT_SUPPORTING_ATTACK_OPTIONS as d, pointOnMidpointPerpendicularAxis as et, DEFAULT_SUPPORT_BY_FIRE_OPTIONS as f, DEFAULT_OBSTACLE_BYPASS_IMPOSSIBLE_OPTIONS as g, DEFAULT_PRINCIPAL_DIRECTION_OF_FIRE_OPTIONS as h, CONTROL_MEASURE_METADATA as i, computeInitialWidthPoint as it, DEFAULT_BREACH_OPTIONS as j, DEFAULT_CANALIZE_OPTIONS as k, listControlMeasureMetadata as l, roundToFixed as lt, DEFAULT_TACTICAL_ARROW_OPTIONS as m, resolveStyleHints as n, createBaselineFrame as nt, getControlMeasureMetadata as o, unproject as ot, DEFAULT_STRONG_POINT_OPTIONS as p, attackByFireDrawRule as q, CONTROL_MEASURE_IDS as r, calculateMetrics as rt, getControlMeasureMetadataByValue as s, EPSILON as st, renderControlMeasure as t, snapToMidpointPerpendicular as tt, DEFAULT_TURN_OPTIONS as u, DEFAULT_OBSTACLE_BYPASS_DIFFICULT_OPTIONS as v, DEFAULT_FINAL_PROTECTIVE_FIRE_OPTIONS as w, DEFAULT_FORTIFIED_LINE_OPTIONS as x, DEFAULT_MAIN_ATTACK_OPTIONS as y, DEFAULT_AIRBORNE_ATTACK_OPTIONS as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orbat-mapper/control-measures",
3
- "version": "0.2.0-alpha.2",
3
+ "version": "0.2.0-alpha.4",
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",