@orbat-mapper/control-measures 0.2.0-alpha.31 → 0.2.0-alpha.33

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.
@@ -7918,6 +7918,170 @@ const CLEAR = defineControlMeasure({
7918
7918
  rule: line23DrawRule
7919
7919
  });
7920
7920
  //#endregion
7921
+ //#region src/generators/cm15-maneuver-areas/supportingAttack.ts
7922
+ const DEFAULT_SUPPORTING_ATTACK_OPTIONS = {
7923
+ shaftWidthRatio: DEFAULT_SHAFT_WIDTH_RATIO,
7924
+ smooth: false,
7925
+ smoothResolution: 5
7926
+ };
7927
+ const SUPPORTING_ATTACK_METADATA = {
7928
+ id: "supporting-attack",
7929
+ name: "Supporting Attack",
7930
+ description: "Supports the main attack by fixing, deceiving, or preventing enemy forces from interfering with it.",
7931
+ entity: "Maneuver Areas",
7932
+ entityType: "Axis of Advance",
7933
+ entitySubtype: "Supporting Attack",
7934
+ value: "151404",
7935
+ minCoordinates: 3,
7936
+ geometry: "line",
7937
+ geometryTypes: ["LineString"],
7938
+ paints: {
7939
+ stroke: true,
7940
+ fill: "none",
7941
+ text: false
7942
+ },
7943
+ drawRule: "Axis1",
7944
+ params: ATTACK_SHAFT_PARAMS
7945
+ };
7946
+ /**
7947
+ * Generates a GeoJSON FeatureCollection for a Supporting Attack tactical symbol.
7948
+ *
7949
+ * The symbol differs from Main Attack in that it is a single LineString
7950
+ * outlining the arrow shape, rather than a filled Polygon header + Shaft lines.
7951
+ *
7952
+ * @param coordinates - An array of GeoJSON Positions.
7953
+ * @param options - Configuration options.
7954
+ * @returns A GeoJSON FeatureCollection<LineString>.
7955
+ */
7956
+ function createSupportingAttack(coordinates, options = {}) {
7957
+ const { geometry } = processAttackGeometry(coordinates, options);
7958
+ if (!geometry) return {
7959
+ type: "FeatureCollection",
7960
+ features: []
7961
+ };
7962
+ return {
7963
+ type: "FeatureCollection",
7964
+ features: [{
7965
+ type: "Feature",
7966
+ properties: {},
7967
+ geometry: {
7968
+ type: "LineString",
7969
+ coordinates: [
7970
+ ...geometry.shaftLeft,
7971
+ geometry.headRing[0],
7972
+ geometry.headRing[1],
7973
+ geometry.headRing[2],
7974
+ ...[...geometry.shaftRight].reverse()
7975
+ ].map((p) => unproject(p[0], p[1]))
7976
+ }
7977
+ }]
7978
+ };
7979
+ }
7980
+ const SUPPORTING_ATTACK = defineControlMeasure({
7981
+ metadata: SUPPORTING_ATTACK_METADATA,
7982
+ generator: createSupportingAttack,
7983
+ defaultOptions: DEFAULT_SUPPORTING_ATTACK_OPTIONS,
7984
+ rule: axis1DrawRule
7985
+ });
7986
+ //#endregion
7987
+ //#region src/generators/cm34-mission-tasks/counterattack.ts
7988
+ const DEFAULT_COUNTERATTACK_OPTIONS = {
7989
+ shaftWidthRatio: DEFAULT_SHAFT_WIDTH_RATIO,
7990
+ smooth: false,
7991
+ smoothResolution: 5,
7992
+ labelPosition: 1
7993
+ };
7994
+ /** Intrinsic dash and gap lengths for the complete Counterattack outline. */
7995
+ const COUNTERATTACK_DASH = [6, 4];
7996
+ const COUNTERATTACK_METADATA = {
7997
+ id: "counterattack",
7998
+ name: "Counterattack",
7999
+ description: "A variation of attack by a defending force against an attacking enemy force",
8000
+ entity: "Mission Tasks",
8001
+ entityType: "Counterattack",
8002
+ value: "340600",
8003
+ minCoordinates: 3,
8004
+ geometry: "line",
8005
+ geometryTypes: ["LineString", "Point"],
8006
+ paints: {
8007
+ stroke: true,
8008
+ fill: "none",
8009
+ text: true
8010
+ },
8011
+ drawRule: "Axis1",
8012
+ capturesLabelSize: true,
8013
+ params: [...ATTACK_SHAFT_PARAMS, {
8014
+ key: "labelPosition",
8015
+ label: "Label position",
8016
+ description: "Position of the CATK label along the shaft, from tail (0) to head (1)",
8017
+ type: "number",
8018
+ min: 0,
8019
+ max: 1,
8020
+ step: .01
8021
+ }],
8022
+ textAmplifiers: [{
8023
+ key: "T",
8024
+ label: "Unique designation",
8025
+ description: "Field T — the counterattack's unique designation.",
8026
+ placeholder: "1",
8027
+ maxLength: 21
8028
+ }]
8029
+ };
8030
+ /** Generates the Counterattack mission task (340600). */
8031
+ function createCounterattack(coordinates, options = {}, textAmplifiers = {}) {
8032
+ const outline = createSupportingAttack(coordinates, options);
8033
+ const { geometry } = processAttackGeometry(coordinates, options);
8034
+ if (!geometry || outline.features.length === 0) return outline;
8035
+ const dashedOutline = {
8036
+ ...outline.features[0],
8037
+ properties: {
8038
+ part: "counterattack",
8039
+ style: { strokeDash: [...COUNTERATTACK_DASH] }
8040
+ }
8041
+ };
8042
+ const { segments, totalLength } = polylineSegments(geometry.shaftCenterline);
8043
+ const frame = pointAlongPolyline(segments, totalLength, options.labelPosition ?? DEFAULT_COUNTERATTACK_OPTIONS.labelPosition);
8044
+ if (!frame) return {
8045
+ type: "FeatureCollection",
8046
+ features: [dashedOutline]
8047
+ };
8048
+ const designation = textAmplifiers.T?.trim();
8049
+ return {
8050
+ type: "FeatureCollection",
8051
+ features: [dashedOutline, {
8052
+ type: "Feature",
8053
+ properties: {
8054
+ part: "label",
8055
+ labelPlacement: false,
8056
+ amplifierField: "T",
8057
+ text: designation ? `CATK ${designation}` : "CATK",
8058
+ rotation: labelRotationAlong(frame.along),
8059
+ ...labelSizeProps(options)
8060
+ },
8061
+ geometry: {
8062
+ type: "Point",
8063
+ coordinates: unproject(frame.point[0], frame.point[1])
8064
+ }
8065
+ }]
8066
+ };
8067
+ }
8068
+ const COUNTERATTACK = defineControlMeasure({
8069
+ metadata: COUNTERATTACK_METADATA,
8070
+ generator: createCounterattack,
8071
+ defaultOptions: DEFAULT_COUNTERATTACK_OPTIONS,
8072
+ rule: axis1DrawRule,
8073
+ previewSample: {
8074
+ controlPoints: [
8075
+ [1, 0],
8076
+ [-.4, -.35],
8077
+ [-1, -.8],
8078
+ [.45, .55]
8079
+ ],
8080
+ textAmplifiers: { T: "1" },
8081
+ options: { labelSize: 80 }
8082
+ }
8083
+ });
8084
+ //#endregion
7921
8085
  //#region src/internal/field-of-fire.ts
7922
8086
  /**
7923
8087
  * Builds the open (stroked) arrowhead at the outward tip of one arm. The two
@@ -9578,6 +9742,301 @@ const PHASE_LINE = defineControlMeasure({
9578
9742
  }
9579
9743
  });
9580
9744
  //#endregion
9745
+ //#region src/generators/cm14-maneuver-lines/bridgehead-line.ts
9746
+ /** Default options for the Bridgehead line control measure. */
9747
+ const DEFAULT_BRIDGEHEAD_LINE_OPTIONS = {
9748
+ includePrefix: true,
9749
+ smooth: false,
9750
+ smoothResolution: 12
9751
+ };
9752
+ const BRIDGEHEAD_LINE_METADATA = {
9753
+ id: "bridgehead-line",
9754
+ name: "Bridgehead line",
9755
+ description: "The planned, temporary limit of the objective area in the development of a bridgehead.",
9756
+ entity: "Maneuver Lines",
9757
+ entityType: "Bridgehead Line",
9758
+ value: "141400",
9759
+ minCoordinates: 2,
9760
+ geometry: "line",
9761
+ geometryTypes: ["LineString", "Point"],
9762
+ paints: {
9763
+ stroke: true,
9764
+ fill: "none",
9765
+ text: true
9766
+ },
9767
+ drawRule: "Line1",
9768
+ capturesLabelSize: true,
9769
+ params: [
9770
+ ...SMOOTH_LINE_PARAMS,
9771
+ {
9772
+ key: "phaseLineName",
9773
+ label: "Phase line name",
9774
+ description: "Name the line as a phase line — rendered \"PL <name>\" beyond each end of the line.",
9775
+ type: "text",
9776
+ placeholder: "CAT",
9777
+ maxLength: 21,
9778
+ field: "T"
9779
+ },
9780
+ {
9781
+ key: "includePrefix",
9782
+ label: "Show PL prefix",
9783
+ description: "Prefix the phase line name with \"PL\" (or show the bare name).",
9784
+ type: "boolean",
9785
+ visibleWhen: (opts) => Boolean(String(opts.phaseLineName ?? "").trim())
9786
+ },
9787
+ {
9788
+ key: "labelPadding",
9789
+ label: "Label padding",
9790
+ description: "Extra clearance between the line and its labels, as a ratio of the label height",
9791
+ type: "number",
9792
+ min: 0,
9793
+ max: 2,
9794
+ step: .05
9795
+ }
9796
+ ],
9797
+ textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
9798
+ };
9799
+ /** Creates a Bridgehead line labeled "BL" above both ends. */
9800
+ function createBridgeheadLine(positions, options = {}, textAmplifiers = {}) {
9801
+ return fixedLabelLineFeatures(positions, {
9802
+ part: "bridgehead-line",
9803
+ placement: "above",
9804
+ ...options
9805
+ }, "BL", textAmplifiers.N);
9806
+ }
9807
+ const BRIDGEHEAD_LINE = defineControlMeasure({
9808
+ metadata: BRIDGEHEAD_LINE_METADATA,
9809
+ generator: createBridgeheadLine,
9810
+ defaultOptions: DEFAULT_BRIDGEHEAD_LINE_OPTIONS,
9811
+ rule: line1DrawRule,
9812
+ previewSample: {
9813
+ controlPoints: [[-1, 0], [1, 0]],
9814
+ options: { labelSize: 96 }
9815
+ }
9816
+ });
9817
+ //#endregion
9818
+ //#region src/generators/cm14-maneuver-lines/holding-line.ts
9819
+ /** Default options for the Holding line control measure. */
9820
+ const DEFAULT_HOLDING_LINE_OPTIONS = {
9821
+ includePrefix: true,
9822
+ smooth: false,
9823
+ smoothResolution: 12
9824
+ };
9825
+ const HOLDING_LINE_METADATA = {
9826
+ id: "holding-line",
9827
+ name: "Holding line",
9828
+ description: "In retrograde river-crossing operations, the outer limit of the area established between the enemy and the water obstacle to preclude direct and observed indirect fires into the crossings.",
9829
+ entity: "Maneuver Lines",
9830
+ entityType: "Holding Line",
9831
+ value: "141500",
9832
+ minCoordinates: 2,
9833
+ geometry: "line",
9834
+ geometryTypes: ["LineString", "Point"],
9835
+ paints: {
9836
+ stroke: true,
9837
+ fill: "none",
9838
+ text: true
9839
+ },
9840
+ drawRule: "Line1",
9841
+ capturesLabelSize: true,
9842
+ params: [
9843
+ ...SMOOTH_LINE_PARAMS,
9844
+ {
9845
+ key: "phaseLineName",
9846
+ label: "Phase line name",
9847
+ description: "Name the line as a phase line — rendered \"PL <name>\" beyond each end of the line.",
9848
+ type: "text",
9849
+ placeholder: "DOG",
9850
+ maxLength: 21,
9851
+ field: "T"
9852
+ },
9853
+ {
9854
+ key: "includePrefix",
9855
+ label: "Show PL prefix",
9856
+ description: "Prefix the phase line name with \"PL\" (or show the bare name).",
9857
+ type: "boolean",
9858
+ visibleWhen: (opts) => Boolean(String(opts.phaseLineName ?? "").trim())
9859
+ },
9860
+ {
9861
+ key: "labelPadding",
9862
+ label: "Label padding",
9863
+ description: "Extra clearance between the line and its labels, as a ratio of the label height",
9864
+ type: "number",
9865
+ min: 0,
9866
+ max: 2,
9867
+ step: .05
9868
+ }
9869
+ ],
9870
+ textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
9871
+ };
9872
+ /** Creates a Holding line labeled "HL" above both ends. */
9873
+ function createHoldingLine(positions, options = {}, textAmplifiers = {}) {
9874
+ return fixedLabelLineFeatures(positions, {
9875
+ part: "holding-line",
9876
+ placement: "above",
9877
+ ...options
9878
+ }, "HL", textAmplifiers.N);
9879
+ }
9880
+ const HOLDING_LINE = defineControlMeasure({
9881
+ metadata: HOLDING_LINE_METADATA,
9882
+ generator: createHoldingLine,
9883
+ defaultOptions: DEFAULT_HOLDING_LINE_OPTIONS,
9884
+ rule: line1DrawRule,
9885
+ previewSample: {
9886
+ controlPoints: [[-1, 0], [1, 0]],
9887
+ options: { labelSize: 96 }
9888
+ }
9889
+ });
9890
+ //#endregion
9891
+ //#region src/generators/cm14-maneuver-lines/release-line.ts
9892
+ /** Default options for the Release line control measure. */
9893
+ const DEFAULT_RELEASE_LINE_OPTIONS = {
9894
+ includePrefix: true,
9895
+ smooth: false,
9896
+ smoothResolution: 12
9897
+ };
9898
+ const RELEASE_LINE_METADATA = {
9899
+ id: "release-line",
9900
+ name: "Release line",
9901
+ description: "A phase line used in river-crossing operations that delineates a change in the headquarters controlling movement.",
9902
+ entity: "Maneuver Lines",
9903
+ entityType: "Release Line",
9904
+ value: "141600",
9905
+ minCoordinates: 2,
9906
+ geometry: "line",
9907
+ geometryTypes: ["LineString", "Point"],
9908
+ paints: {
9909
+ stroke: true,
9910
+ fill: "none",
9911
+ text: true
9912
+ },
9913
+ drawRule: "Line1",
9914
+ capturesLabelSize: true,
9915
+ params: [
9916
+ ...SMOOTH_LINE_PARAMS,
9917
+ {
9918
+ key: "phaseLineName",
9919
+ label: "Phase line name",
9920
+ description: "Name the line as a phase line — rendered \"PL <name>\" beyond each end of the line.",
9921
+ type: "text",
9922
+ placeholder: "WIND",
9923
+ maxLength: 21,
9924
+ field: "T"
9925
+ },
9926
+ {
9927
+ key: "includePrefix",
9928
+ label: "Show PL prefix",
9929
+ description: "Prefix the phase line name with \"PL\" (or show the bare name).",
9930
+ type: "boolean",
9931
+ visibleWhen: (opts) => Boolean(String(opts.phaseLineName ?? "").trim())
9932
+ },
9933
+ {
9934
+ key: "labelPadding",
9935
+ label: "Label padding",
9936
+ description: "Extra clearance between the line and its labels, as a ratio of the label height",
9937
+ type: "number",
9938
+ min: 0,
9939
+ max: 2,
9940
+ step: .05
9941
+ }
9942
+ ],
9943
+ textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
9944
+ };
9945
+ /** Creates a Release line labeled "RL" above both ends. */
9946
+ function createReleaseLine(positions, options = {}, textAmplifiers = {}) {
9947
+ return fixedLabelLineFeatures(positions, {
9948
+ part: "release-line",
9949
+ placement: "above",
9950
+ ...options
9951
+ }, "RL", textAmplifiers.N);
9952
+ }
9953
+ const RELEASE_LINE = defineControlMeasure({
9954
+ metadata: RELEASE_LINE_METADATA,
9955
+ generator: createReleaseLine,
9956
+ defaultOptions: DEFAULT_RELEASE_LINE_OPTIONS,
9957
+ rule: line1DrawRule,
9958
+ previewSample: {
9959
+ controlPoints: [[-1, 0], [1, 0]],
9960
+ options: { labelSize: 96 }
9961
+ }
9962
+ });
9963
+ //#endregion
9964
+ //#region src/generators/cm14-maneuver-lines/forward-edge-of-battle-area.ts
9965
+ /** Default options for the Forward edge of the battle area control measure. */
9966
+ const DEFAULT_FORWARD_EDGE_OF_BATTLE_AREA_OPTIONS = {
9967
+ includePrefix: true,
9968
+ smooth: false,
9969
+ smoothResolution: 12
9970
+ };
9971
+ const FORWARD_EDGE_OF_BATTLE_AREA_METADATA = {
9972
+ id: "forward-edge-of-battle-area",
9973
+ name: "Forward edge of the battle area",
9974
+ description: "The foremost limits of a series of areas in which ground combat units are deployed, excluding the areas in which covering or screening forces operate.",
9975
+ entity: "Maneuver Lines",
9976
+ entityType: "Forward Edge of Battle Area",
9977
+ value: "140400",
9978
+ minCoordinates: 2,
9979
+ geometry: "line",
9980
+ geometryTypes: ["LineString", "Point"],
9981
+ paints: {
9982
+ stroke: true,
9983
+ fill: "none",
9984
+ text: true
9985
+ },
9986
+ drawRule: "Line1",
9987
+ capturesLabelSize: true,
9988
+ params: [
9989
+ ...SMOOTH_LINE_PARAMS,
9990
+ {
9991
+ key: "phaseLineName",
9992
+ label: "Phase line name",
9993
+ description: "Name the line as a phase line — rendered \"PL <name>\" beyond each end of the line.",
9994
+ type: "text",
9995
+ placeholder: "MOCHA",
9996
+ maxLength: 21,
9997
+ field: "T"
9998
+ },
9999
+ {
10000
+ key: "includePrefix",
10001
+ label: "Show PL prefix",
10002
+ description: "Prefix the phase line name with \"PL\" (or show the bare name).",
10003
+ type: "boolean",
10004
+ visibleWhen: (opts) => Boolean(String(opts.phaseLineName ?? "").trim())
10005
+ },
10006
+ {
10007
+ key: "labelPadding",
10008
+ label: "Label padding",
10009
+ description: "Extra clearance between the line and its labels, as a ratio of the label height",
10010
+ type: "number",
10011
+ min: 0,
10012
+ max: 2,
10013
+ step: .05
10014
+ }
10015
+ ],
10016
+ textAmplifiers: [HOSTILE_LINE_AMPLIFIER]
10017
+ };
10018
+ /**
10019
+ * Creates a Forward edge of the battle area control measure: a polyline
10020
+ * through `positions` labeled "FEBA" above the line near each end.
10021
+ */
10022
+ function createForwardEdgeOfBattleArea(positions, options = {}, textAmplifiers = {}) {
10023
+ return fixedLabelLineFeatures(positions, {
10024
+ part: "forward-edge-of-battle-area",
10025
+ placement: "above",
10026
+ ...options
10027
+ }, "FEBA", textAmplifiers.N);
10028
+ }
10029
+ const FORWARD_EDGE_OF_BATTLE_AREA = defineControlMeasure({
10030
+ metadata: FORWARD_EDGE_OF_BATTLE_AREA_METADATA,
10031
+ generator: createForwardEdgeOfBattleArea,
10032
+ defaultOptions: DEFAULT_FORWARD_EDGE_OF_BATTLE_AREA_OPTIONS,
10033
+ rule: line1DrawRule,
10034
+ previewSample: {
10035
+ controlPoints: [[-1, 0], [1, 0]],
10036
+ options: { labelSize: 96 }
10037
+ }
10038
+ });
10039
+ //#endregion
9581
10040
  //#region src/generators/cm14-maneuver-lines/handover-line.ts
9582
10041
  /** Default options for the Handover line control measure. */
9583
10042
  const DEFAULT_HANDOVER_LINE_OPTIONS = {
@@ -10035,8 +10494,9 @@ function createManeuverArrowTask(coordinates, options, textAmplifiers, config) {
10035
10494
  crossbarCenter = geometry.ptTip;
10036
10495
  crossbarAlong = tipDirection;
10037
10496
  } else {
10038
- crossbarCenter = centerline[0];
10039
- crossbarAlong = segments[0].along;
10497
+ const crossbarFrame = pointAlongPolyline(segments, totalLength, Number.isFinite(config.crossbarAt.shaftPosition) ? clamp01(config.crossbarAt.shaftPosition) : 0);
10498
+ crossbarCenter = crossbarFrame.point;
10499
+ crossbarAlong = crossbarFrame.along;
10040
10500
  }
10041
10501
  const crossbarPerp = [-crossbarAlong[1], crossbarAlong[0]];
10042
10502
  const crossbar = [vecAdd(crossbarCenter, vecScale(crossbarPerp, crossbarLength / 2)), vecSub(crossbarCenter, vecScale(crossbarPerp, crossbarLength / 2))];
@@ -11483,6 +11943,103 @@ const PRINCIPAL_DIRECTION_OF_FIRE = defineControlMeasure({
11483
11943
  ] }
11484
11944
  });
11485
11945
  //#endregion
11946
+ //#region src/generators/cm14-maneuver-lines/secondaryDirectionOfFire.ts
11947
+ const DEFAULT_SECONDARY_DIRECTION_OF_FIRE_OPTIONS = { ...DEFAULT_PRINCIPAL_DIRECTION_OF_FIRE_OPTIONS };
11948
+ /** Dash and gap lengths used by the two fire-direction arms. */
11949
+ const SECONDARY_DIRECTION_OF_FIRE_DASH = [6, 4];
11950
+ const SECONDARY_DIRECTION_OF_FIRE_METADATA = {
11951
+ id: "secondary-direction-of-fire",
11952
+ name: "Secondary Direction of Fire",
11953
+ description: "A direction or sector, in addition to the principal direction, toward which a weapon or unit directs fire for a tactical purpose.",
11954
+ entity: "Maneuver Lines",
11955
+ entityType: "Field of Fire",
11956
+ entitySubtype: "Secondary Direction of Fire",
11957
+ value: "140504",
11958
+ minCoordinates: 2,
11959
+ maxCoordinates: 3,
11960
+ geometry: "line",
11961
+ geometryTypes: ["MultiLineString"],
11962
+ paints: {
11963
+ stroke: true,
11964
+ fill: "none",
11965
+ text: false
11966
+ },
11967
+ drawRule: "Line3",
11968
+ params: [{
11969
+ key: "arrowheadLengthRatio",
11970
+ label: "Arrowhead length",
11971
+ description: "Arrowhead length as a ratio of each arm's length",
11972
+ type: "number",
11973
+ min: .01,
11974
+ max: .5,
11975
+ step: .01
11976
+ }, {
11977
+ key: "arrowheadWidthRatio",
11978
+ label: "Arrowhead width",
11979
+ description: "Arrowhead base width as a multiple of the arrowhead length",
11980
+ type: "number",
11981
+ min: .05,
11982
+ max: 2,
11983
+ step: .01
11984
+ }]
11985
+ };
11986
+ /**
11987
+ * Creates the same V-shaped field-of-fire geometry as Principal Direction of
11988
+ * Fire, with dashed arms and solid open arrowheads.
11989
+ *
11990
+ * @param coordinates - [PT 1 (vertex), PT 2 (tip), PT 3 (tip)]
11991
+ * @param options - Arrowhead sizing parameters
11992
+ */
11993
+ function createSecondaryDirectionOfFire(coordinates, options = {}) {
11994
+ const { arrowheadLengthRatio, arrowheadWidthRatio } = {
11995
+ ...DEFAULT_SECONDARY_DIRECTION_OF_FIRE_OPTIONS,
11996
+ ...options
11997
+ };
11998
+ const v = buildFieldOfFireV(coordinates, Math.max(0, arrowheadLengthRatio), Math.max(0, arrowheadWidthRatio));
11999
+ if (!v) return {
12000
+ type: "FeatureCollection",
12001
+ features: []
12002
+ };
12003
+ const unprojectLines = (lines) => lines.map((line) => line.map((coordinate) => unproject(coordinate[0], coordinate[1])));
12004
+ const arms = v.lines.filter((_, index) => index % 2 === 0);
12005
+ const arrowheads = v.lines.filter((_, index) => index % 2 === 1);
12006
+ return {
12007
+ type: "FeatureCollection",
12008
+ features: [{
12009
+ type: "Feature",
12010
+ properties: {
12011
+ part: "arms",
12012
+ style: { strokeDash: [...SECONDARY_DIRECTION_OF_FIRE_DASH] }
12013
+ },
12014
+ geometry: {
12015
+ type: "MultiLineString",
12016
+ coordinates: unprojectLines(arms)
12017
+ }
12018
+ }, {
12019
+ type: "Feature",
12020
+ properties: {
12021
+ part: "arrowheads",
12022
+ style: { strokeDash: [] }
12023
+ },
12024
+ geometry: {
12025
+ type: "MultiLineString",
12026
+ coordinates: unprojectLines(arrowheads)
12027
+ }
12028
+ }]
12029
+ };
12030
+ }
12031
+ const SECONDARY_DIRECTION_OF_FIRE = defineControlMeasure({
12032
+ metadata: SECONDARY_DIRECTION_OF_FIRE_METADATA,
12033
+ generator: createSecondaryDirectionOfFire,
12034
+ defaultOptions: DEFAULT_SECONDARY_DIRECTION_OF_FIRE_OPTIONS,
12035
+ rule: line3DrawRule,
12036
+ previewSample: { controlPoints: [
12037
+ [0, -.6],
12038
+ [-1, .4],
12039
+ [1, .4]
12040
+ ] }
12041
+ });
12042
+ //#endregion
11486
12043
  //#region src/generators/cm15-maneuver-areas/strongPoint.ts
11487
12044
  const DEFAULT_SMOOTH_RESOLUTION = 16;
11488
12045
  /** Default options for the Strong Point control measure. */
@@ -11899,72 +12456,6 @@ const SUPPORT_BY_FIRE = defineControlMeasure({
11899
12456
  rule: supportByFireDrawRule
11900
12457
  });
11901
12458
  //#endregion
11902
- //#region src/generators/cm15-maneuver-areas/supportingAttack.ts
11903
- const DEFAULT_SUPPORTING_ATTACK_OPTIONS = {
11904
- shaftWidthRatio: DEFAULT_SHAFT_WIDTH_RATIO,
11905
- smooth: false,
11906
- smoothResolution: 5
11907
- };
11908
- const SUPPORTING_ATTACK_METADATA = {
11909
- id: "supporting-attack",
11910
- name: "Supporting Attack",
11911
- description: "Supports the main attack by fixing, deceiving, or preventing enemy forces from interfering with it.",
11912
- entity: "Maneuver Areas",
11913
- entityType: "Axis of Advance",
11914
- entitySubtype: "Supporting Attack",
11915
- value: "151404",
11916
- minCoordinates: 3,
11917
- geometry: "line",
11918
- geometryTypes: ["LineString"],
11919
- paints: {
11920
- stroke: true,
11921
- fill: "none",
11922
- text: false
11923
- },
11924
- drawRule: "Axis1",
11925
- params: ATTACK_SHAFT_PARAMS
11926
- };
11927
- /**
11928
- * Generates a GeoJSON FeatureCollection for a Supporting Attack tactical symbol.
11929
- *
11930
- * The symbol differs from Main Attack in that it is a single LineString
11931
- * outlining the arrow shape, rather than a filled Polygon header + Shaft lines.
11932
- *
11933
- * @param coordinates - An array of GeoJSON Positions.
11934
- * @param options - Configuration options.
11935
- * @returns A GeoJSON FeatureCollection<LineString>.
11936
- */
11937
- function createSupportingAttack(coordinates, options = {}) {
11938
- const { geometry } = processAttackGeometry(coordinates, options);
11939
- if (!geometry) return {
11940
- type: "FeatureCollection",
11941
- features: []
11942
- };
11943
- return {
11944
- type: "FeatureCollection",
11945
- features: [{
11946
- type: "Feature",
11947
- properties: {},
11948
- geometry: {
11949
- type: "LineString",
11950
- coordinates: [
11951
- ...geometry.shaftLeft,
11952
- geometry.headRing[0],
11953
- geometry.headRing[1],
11954
- geometry.headRing[2],
11955
- ...[...geometry.shaftRight].reverse()
11956
- ].map((p) => unproject(p[0], p[1]))
11957
- }
11958
- }]
11959
- };
11960
- }
11961
- const SUPPORTING_ATTACK = defineControlMeasure({
11962
- metadata: SUPPORTING_ATTACK_METADATA,
11963
- generator: createSupportingAttack,
11964
- defaultOptions: DEFAULT_SUPPORTING_ATTACK_OPTIONS,
11965
- rule: axis1DrawRule
11966
- });
11967
- //#endregion
11968
12459
  //#region src/generators/cm27-protection-areas/turn.ts
11969
12460
  const DEFAULT_TURN_OPTIONS = {
11970
12461
  arrowheadLengthRatio: .16,
@@ -12087,7 +12578,20 @@ const TURN = defineControlMeasure({
12087
12578
  });
12088
12579
  //#endregion
12089
12580
  //#region src/generators/cm15-maneuver-areas/turning-movement.ts
12090
- const DEFAULT_TURNING_MOVEMENT_OPTIONS = DEFAULT_MANEUVER_ARROW_TASK_OPTIONS;
12581
+ const DEFAULT_CROSSBAR_POSITION = 0;
12582
+ const DEFAULT_TURNING_MOVEMENT_OPTIONS = {
12583
+ ...DEFAULT_MANEUVER_ARROW_TASK_OPTIONS,
12584
+ crossbarPosition: DEFAULT_CROSSBAR_POSITION
12585
+ };
12586
+ const TURNING_MOVEMENT_PARAMS = [{
12587
+ key: "crossbarPosition",
12588
+ label: "Crossbar position",
12589
+ description: "Position along the shaft, from tail (0) to arrowhead base (1)",
12590
+ type: "number",
12591
+ min: 0,
12592
+ max: 1,
12593
+ step: .01
12594
+ }];
12091
12595
  const TURNING_MOVEMENT_METADATA = {
12092
12596
  id: "turning-movement",
12093
12597
  name: "Turning Movement",
@@ -12105,14 +12609,18 @@ const TURNING_MOVEMENT_METADATA = {
12105
12609
  },
12106
12610
  drawRule: "Axis1",
12107
12611
  capturesLabelSize: true,
12108
- params: [...ATTACK_SHAFT_PARAMS, ...MANEUVER_ARROW_TASK_PARAMS],
12612
+ params: [
12613
+ ...ATTACK_SHAFT_PARAMS,
12614
+ ...MANEUVER_ARROW_TASK_PARAMS,
12615
+ ...TURNING_MOVEMENT_PARAMS
12616
+ ],
12109
12617
  textAmplifiers: MANEUVER_ARROW_TASK_TEXT_AMPLIFIERS
12110
12618
  };
12111
12619
  function createTurningMovement(coordinates, options = {}, textAmplifiers = {}) {
12112
12620
  return createManeuverArrowTask(coordinates, options, textAmplifiers, {
12113
12621
  part: "turning-movement",
12114
12622
  code: "T",
12115
- crossbarAt: "tail"
12623
+ crossbarAt: { shaftPosition: options.crossbarPosition ?? DEFAULT_CROSSBAR_POSITION }
12116
12624
  });
12117
12625
  }
12118
12626
  //#endregion
@@ -12137,6 +12645,7 @@ const DEFINITIONS = {
12137
12645
  "direction-of-main-attack": DIRECTION_OF_MAIN_ATTACK,
12138
12646
  "direction-of-supporting-attack": DIRECTION_OF_SUPPORTING_ATTACK,
12139
12647
  "principal-direction-of-fire": PRINCIPAL_DIRECTION_OF_FIRE,
12648
+ "secondary-direction-of-fire": SECONDARY_DIRECTION_OF_FIRE,
12140
12649
  "final-protective-fire-left": FINAL_PROTECTIVE_FIRE_LEFT,
12141
12650
  "final-protective-fire-right": FINAL_PROTECTIVE_FIRE_RIGHT,
12142
12651
  "search-area": SEARCH_AREA,
@@ -12178,6 +12687,10 @@ const DEFINITIONS = {
12178
12687
  "attack-by-fire": ATTACK_BY_FIRE,
12179
12688
  flot: FLOT,
12180
12689
  "phase-line": PHASE_LINE,
12690
+ "forward-edge-of-battle-area": FORWARD_EDGE_OF_BATTLE_AREA,
12691
+ "bridgehead-line": BRIDGEHEAD_LINE,
12692
+ "holding-line": HOLDING_LINE,
12693
+ "release-line": RELEASE_LINE,
12181
12694
  "handover-line": HANDOVER_LINE,
12182
12695
  "battle-handover-line": BATTLE_HANDOVER_LINE,
12183
12696
  "block-mission-task": BLOCK_MISSION_TASK,
@@ -12185,6 +12698,7 @@ const DEFINITIONS = {
12185
12698
  bypass: BYPASS,
12186
12699
  canalize: CANALIZE,
12187
12700
  clear: CLEAR,
12701
+ counterattack: COUNTERATTACK,
12188
12702
  cover: COVER,
12189
12703
  delay: DELAY,
12190
12704
  guard: GUARD,
@@ -12586,4 +13100,4 @@ function assertNever(value) {
12586
13100
  throw new Error(`Unhandled control measure kind: ${String(value)}`);
12587
13101
  }
12588
13102
  //#endregion
12589
- export { DEFAULT_BOUNDARY_OPTIONS as $, DEFAULT_FLOT_OPTIONS as A, centerRadiusDrawRule as At, DEFAULT_GENERIC_TEXT_OPTIONS as B, computeInitialWidthPoint as Bt, DEFAULT_SCREEN_OPTIONS as C, line24DrawRule as Ct, DEFAULT_BATTLE_HANDOVER_LINE_OPTIONS as D, ambushDrawRule as Dt, DEFAULT_FORTIFIED_LINE_OPTIONS as E, line1DrawRule as Et, DEFAULT_GUARD_OPTIONS as F, getMidpointPerpendicularSignedDistance as Ft, DEFAULT_CLASSIC_ARROW_OPTIONS as G, getMetersPerPixel as Gt, DEFAULT_GENERIC_POLYGON_OPTIONS as H, project as Ht, DEFAULT_DELAY_OPTIONS as I, pointOnMidpointPerpendicularAxis as It, DEFAULT_BREACH_OPTIONS as J, DEFAULT_CANALIZE_OPTIONS as K, roundToFixed as Kt, DEFAULT_COVER_OPTIONS as L, snapToMidpointPerpendicular as Lt, DEFAULT_FINAL_PROTECTIVE_FIRE_OPTIONS as M, blockDrawRule as Mt, DEFAULT_ENCIRCLEMENT_OPTIONS as N, computeDefaultMidpointPerpendicularPoint as Nt, DEFAULT_HANDOVER_LINE_OPTIONS as O, attackByFireDrawRule as Ot, DEFAULT_DISRUPT_OPTIONS as P, createMidpointPerpendicularDrawRule as Pt, DEFAULT_LIGHT_LINE_OPTIONS as Q, DEFAULT_TACTICAL_ARROW_OPTIONS as R, createBaselineFrame as Rt, DEFAULT_JOINT_TACTICAL_ACTION_AREA_OPTIONS as S, line26DrawRule as St, DEFAULT_FORTIFIED_AREA_OPTIONS as T, turnDrawRule as Tt, DEFAULT_GENERIC_LINE_OPTIONS as U, unproject as Ut, DEFAULT_GENERIC_RECTANGLE_OPTIONS as V, haversineDistance as Vt, DEFAULT_GENERIC_CIRCLE_OPTIONS as W, EPSILON as Wt, DEFAULT_GENERIC_C2_LINE_OPTIONS as X, DEFAULT_BLOCK_MISSION_TASK_OPTIONS as Y, DEFAULT_ENGINEER_WORK_LINE_OPTIONS as Z, DEFAULT_OBSTACLE_BYPASS_IMPOSSIBLE_OPTIONS as _, DEFAULT_AMBUSH_OPTIONS as _t, DEFINITIONS as a, DEFAULT_ANTITANK_WALL_OPTIONS as at, DEFAULT_MAIN_ATTACK_OPTIONS as b, axis1DrawRule as bt, getDefaultOptions as c, DEFAULT_AREA_OF_OPERATIONS_OPTIONS as ct, DEFAULT_TURN_OPTIONS as d, DEFAULT_DIRECTION_OF_MAIN_ATTACK_OPTIONS as dt, DEFAULT_BLOCK_ARROW_OPTIONS as et, DEFAULT_SUPPORTING_ATTACK_OPTIONS as f, DEFAULT_DIRECTION_OF_ATTACK_AVIATION_OPTIONS as ft, DEFAULT_PICKUP_ZONE_OPTIONS as g, resolveAmplifierPlacement as gt, DEFAULT_PRINCIPAL_DIRECTION_OF_FIRE_OPTIONS as h, normalizeTextAmplifiers as ht, CONTROL_MEASURE_METADATA as i, DEFAULT_ATTACK_BY_FIRE_OPTIONS as it, DEFAULT_FIX_OPTIONS as j, disruptDrawRule as jt, DEFAULT_PHASE_LINE_OPTIONS as k, point12DrawRule as kt, listControlMeasureMetadata as l, DEFAULT_AREA_DEFENSE_OPTIONS as lt, DEFAULT_STRONG_POINT_OPTIONS as m, canonicalTextAmplifierKey as mt, resolveStyleHints as n, DEFAULT_BATTLE_POSITION_OPTIONS as nt, getControlMeasureMetadata as o, DEFAULT_ANTITANK_DITCH_OPTIONS as ot, DEFAULT_SUPPORT_BY_FIRE_OPTIONS as p, TEXT_AMPLIFIER_FIELDS as pt, DEFAULT_BYPASS_OPTIONS as q, CONTROL_MEASURE_IDS as r, DEFAULT_ATTACK_HELICOPTER_OPTIONS as rt, getControlMeasureMetadataByValue as s, DEFAULT_ASSEMBLY_AREA_OPTIONS as st, renderControlMeasure as t, DEFAULT_BLOCK_OPTIONS as tt, DEFAULT_TURNING_MOVEMENT_OPTIONS as u, DEFAULT_DIRECTION_OF_SUPPORTING_ATTACK_OPTIONS as ut, DEFAULT_OBSTACLE_BYPASS_EASY_OPTIONS as v, DEFAULT_AIRBORNE_ATTACK_OPTIONS as vt, DEFAULT_FRONTAL_ATTACK_OPTIONS as w, line23DrawRule as wt, DEFAULT_LANDING_ZONE_OPTIONS as x, supportByFireDrawRule as xt, DEFAULT_OBSTACLE_BYPASS_DIFFICULT_OPTIONS as y, rectangleDrawRule as yt, DEFAULT_CLEAR_OPTIONS as z, calculateMetrics as zt };
13103
+ export { DEFAULT_CANALIZE_OPTIONS as $, roundToFixed as $t, DEFAULT_FORWARD_EDGE_OF_BATTLE_AREA_OPTIONS as A, line24DrawRule as At, DEFAULT_GUARD_OPTIONS as B, computeDefaultMidpointPerpendicularPoint as Bt, DEFAULT_JOINT_TACTICAL_ACTION_AREA_OPTIONS as C, resolveAmplifierPlacement as Ct, DEFAULT_FORTIFIED_LINE_OPTIONS as D, axis1DrawRule as Dt, DEFAULT_FORTIFIED_AREA_OPTIONS as E, rectangleDrawRule as Et, DEFAULT_FLOT_OPTIONS as F, attackByFireDrawRule as Ft, DEFAULT_SUPPORTING_ATTACK_OPTIONS as G, createBaselineFrame as Gt, DEFAULT_COVER_OPTIONS as H, getMidpointPerpendicularSignedDistance as Ht, DEFAULT_FIX_OPTIONS as I, point12DrawRule as It, DEFAULT_GENERIC_RECTANGLE_OPTIONS as J, haversineDistance as Jt, DEFAULT_CLEAR_OPTIONS as K, calculateMetrics as Kt, DEFAULT_FINAL_PROTECTIVE_FIRE_OPTIONS as L, centerRadiusDrawRule as Lt, DEFAULT_HOLDING_LINE_OPTIONS as M, turnDrawRule as Mt, DEFAULT_BRIDGEHEAD_LINE_OPTIONS as N, line1DrawRule as Nt, DEFAULT_BATTLE_HANDOVER_LINE_OPTIONS as O, supportByFireDrawRule as Ot, DEFAULT_PHASE_LINE_OPTIONS as P, ambushDrawRule as Pt, DEFAULT_CLASSIC_ARROW_OPTIONS as Q, getMetersPerPixel as Qt, DEFAULT_ENCIRCLEMENT_OPTIONS as R, disruptDrawRule as Rt, DEFAULT_LANDING_ZONE_OPTIONS as S, normalizeTextAmplifiers as St, DEFAULT_FRONTAL_ATTACK_OPTIONS as T, DEFAULT_AIRBORNE_ATTACK_OPTIONS as Tt, DEFAULT_TACTICAL_ARROW_OPTIONS as U, pointOnMidpointPerpendicularAxis as Ut, DEFAULT_DELAY_OPTIONS as V, createMidpointPerpendicularDrawRule as Vt, DEFAULT_COUNTERATTACK_OPTIONS as W, snapToMidpointPerpendicular as Wt, DEFAULT_GENERIC_LINE_OPTIONS as X, unproject as Xt, DEFAULT_GENERIC_POLYGON_OPTIONS as Y, project as Yt, DEFAULT_GENERIC_CIRCLE_OPTIONS as Z, EPSILON as Zt, DEFAULT_PICKUP_ZONE_OPTIONS as _, DEFAULT_DIRECTION_OF_SUPPORTING_ATTACK_OPTIONS as _t, DEFINITIONS as a, DEFAULT_LIGHT_LINE_OPTIONS as at, DEFAULT_OBSTACLE_BYPASS_DIFFICULT_OPTIONS as b, TEXT_AMPLIFIER_FIELDS as bt, getDefaultOptions as c, DEFAULT_BLOCK_OPTIONS as ct, DEFAULT_TURN_OPTIONS as d, DEFAULT_ATTACK_BY_FIRE_OPTIONS as dt, DEFAULT_BYPASS_OPTIONS as et, DEFAULT_SUPPORT_BY_FIRE_OPTIONS as f, DEFAULT_ANTITANK_WALL_OPTIONS as ft, DEFAULT_PRINCIPAL_DIRECTION_OF_FIRE_OPTIONS as g, DEFAULT_AREA_DEFENSE_OPTIONS as gt, SECONDARY_DIRECTION_OF_FIRE_DASH as h, DEFAULT_AREA_OF_OPERATIONS_OPTIONS as ht, CONTROL_MEASURE_METADATA as i, DEFAULT_ENGINEER_WORK_LINE_OPTIONS as it, DEFAULT_RELEASE_LINE_OPTIONS as j, line23DrawRule as jt, DEFAULT_HANDOVER_LINE_OPTIONS as k, line26DrawRule as kt, listControlMeasureMetadata as l, DEFAULT_BATTLE_POSITION_OPTIONS as lt, DEFAULT_SECONDARY_DIRECTION_OF_FIRE_OPTIONS as m, DEFAULT_ASSEMBLY_AREA_OPTIONS as mt, resolveStyleHints as n, DEFAULT_BLOCK_MISSION_TASK_OPTIONS as nt, getControlMeasureMetadata as o, DEFAULT_BOUNDARY_OPTIONS as ot, DEFAULT_STRONG_POINT_OPTIONS as p, DEFAULT_ANTITANK_DITCH_OPTIONS as pt, DEFAULT_GENERIC_TEXT_OPTIONS as q, computeInitialWidthPoint as qt, CONTROL_MEASURE_IDS as r, DEFAULT_GENERIC_C2_LINE_OPTIONS as rt, getControlMeasureMetadataByValue as s, DEFAULT_BLOCK_ARROW_OPTIONS as st, renderControlMeasure as t, DEFAULT_BREACH_OPTIONS as tt, DEFAULT_TURNING_MOVEMENT_OPTIONS as u, DEFAULT_ATTACK_HELICOPTER_OPTIONS as ut, DEFAULT_OBSTACLE_BYPASS_IMPOSSIBLE_OPTIONS as v, DEFAULT_DIRECTION_OF_MAIN_ATTACK_OPTIONS as vt, DEFAULT_SCREEN_OPTIONS as w, DEFAULT_AMBUSH_OPTIONS as wt, DEFAULT_MAIN_ATTACK_OPTIONS as x, canonicalTextAmplifierKey as xt, DEFAULT_OBSTACLE_BYPASS_EASY_OPTIONS as y, DEFAULT_DIRECTION_OF_ATTACK_AVIATION_OPTIONS as yt, DEFAULT_DISRUPT_OPTIONS as z, blockDrawRule as zt };