@orbat-mapper/control-measures 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{index-DFZ2BoXt.d.mts → index-1i5OZMcS.d.mts} +54 -40
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/preview/index.d.mts +1 -1
- package/dist/preview/index.mjs +3 -2
- package/dist/{renderControlMeasure-qnu0Feey.mjs → renderControlMeasure-B61Tgq3f.mjs} +635 -424
- package/package.json +1 -1
|
@@ -237,6 +237,11 @@ function vecNorm(v) {
|
|
|
237
237
|
const m = vecMag(v);
|
|
238
238
|
return m === 0 ? [0, 0] : [v[0] / m, v[1] / m];
|
|
239
239
|
}
|
|
240
|
+
function vecRotate(v, radians) {
|
|
241
|
+
const cosine = Math.cos(radians);
|
|
242
|
+
const sine = Math.sin(radians);
|
|
243
|
+
return [v[0] * cosine - v[1] * sine, v[0] * sine + v[1] * cosine];
|
|
244
|
+
}
|
|
240
245
|
function midpoint(a, b) {
|
|
241
246
|
return vecScale(vecAdd(a, b), .5);
|
|
242
247
|
}
|
|
@@ -300,10 +305,10 @@ function createWidthOptionHandles(config) {
|
|
|
300
305
|
}];
|
|
301
306
|
},
|
|
302
307
|
drag({ controlPoints, options, handleId, position }) {
|
|
303
|
-
const geometry = config.geometry(controlPoints, options);
|
|
304
|
-
if (!geometry || geometry.referenceHalfWidth < 1e-6) return void 0;
|
|
305
308
|
const rear = handleId === REAR_WIDTH_OPTION_HANDLE_ID;
|
|
306
309
|
if (!rear && !(handleId === "shaft-width")) return void 0;
|
|
310
|
+
const geometry = config.geometry(controlPoints, options);
|
|
311
|
+
if (!geometry || geometry.referenceHalfWidth < 1e-6) return void 0;
|
|
307
312
|
const from = rear ? geometry.spine[0] : geometry.spine.at(-2);
|
|
308
313
|
const to = rear ? geometry.spine[1] : geometry.spine.at(-1);
|
|
309
314
|
if (!from || !to) return void 0;
|
|
@@ -9791,12 +9796,189 @@ const CLEAR = defineControlMeasure({
|
|
|
9791
9796
|
rule: line23DrawRule
|
|
9792
9797
|
});
|
|
9793
9798
|
//#endregion
|
|
9799
|
+
//#region src/generators/cm15-maneuver-areas/maneuver-arrow-task-shared.ts
|
|
9800
|
+
const DEFAULT_MANEUVER_ARROW_TASK_AMPLIFIER_OPTIONS = {
|
|
9801
|
+
labelPosition: .25,
|
|
9802
|
+
dtgPosition: .62,
|
|
9803
|
+
labelPadding: 0
|
|
9804
|
+
};
|
|
9805
|
+
const DEFAULT_MANEUVER_ARROW_TASK_OPTIONS = {
|
|
9806
|
+
shaftWidthRatio: DEFAULT_SHAFT_WIDTH_RATIO$1,
|
|
9807
|
+
rearWidthRatio: DEFAULT_REAR_WIDTH_RATIO,
|
|
9808
|
+
smooth: false,
|
|
9809
|
+
smoothResolution: 5,
|
|
9810
|
+
crossbarLengthRatio: 1.1,
|
|
9811
|
+
...DEFAULT_MANEUVER_ARROW_TASK_AMPLIFIER_OPTIONS
|
|
9812
|
+
};
|
|
9813
|
+
function createManeuverArrowTaskAmplifierLabels(geometry, options, textAmplifiers) {
|
|
9814
|
+
const resolved = {
|
|
9815
|
+
...DEFAULT_MANEUVER_ARROW_TASK_AMPLIFIER_OPTIONS,
|
|
9816
|
+
...options
|
|
9817
|
+
};
|
|
9818
|
+
const centerline = geometry.shaftCenterline;
|
|
9819
|
+
const { segments, totalLength } = polylineSegments(centerline);
|
|
9820
|
+
if (segments.length === 0) return [];
|
|
9821
|
+
const labels = [];
|
|
9822
|
+
const sizeProps = labelSizeProps(resolved);
|
|
9823
|
+
const labelFrame = pointAlongPolyline(segments, totalLength, clamp01(resolved.labelPosition));
|
|
9824
|
+
if (labelFrame && textAmplifiers.T) pushLabel(labels, labelFrame.point, textAmplifiers.T, labelRotationAlong(labelFrame.along), sizeProps, void 0, "T");
|
|
9825
|
+
const dtgFrame = pointAlongPolyline(segments, totalLength, clamp01(resolved.dtgPosition));
|
|
9826
|
+
if (dtgFrame) {
|
|
9827
|
+
const rowOffset = resolveLabelOffsetMeters(resolved, .55 + Math.max(0, resolved.labelPadding));
|
|
9828
|
+
const rotation = labelRotationAlong(dtgFrame.along);
|
|
9829
|
+
if (textAmplifiers.W) pushLabel(labels, vecAdd(dtgFrame.point, vecScale(dtgFrame.perp, rowOffset)), textAmplifiers.W, rotation, sizeProps, void 0, "W");
|
|
9830
|
+
if (textAmplifiers.W1) pushLabel(labels, vecSub(dtgFrame.point, vecScale(dtgFrame.perp, rowOffset)), textAmplifiers.W1, rotation, sizeProps, void 0, "W1");
|
|
9831
|
+
}
|
|
9832
|
+
return labels;
|
|
9833
|
+
}
|
|
9834
|
+
function createManeuverArrowTask(coordinates, options, textAmplifiers, config) {
|
|
9835
|
+
const merged = {
|
|
9836
|
+
...DEFAULT_MANEUVER_ARROW_TASK_OPTIONS,
|
|
9837
|
+
...options
|
|
9838
|
+
};
|
|
9839
|
+
const resolved = {
|
|
9840
|
+
...merged,
|
|
9841
|
+
rearWidthRatio: resolveRearWidthRatio(options, merged.shaftWidthRatio)
|
|
9842
|
+
};
|
|
9843
|
+
const { geometry } = processAttackGeometry(coordinates, resolved);
|
|
9844
|
+
if (!geometry) return {
|
|
9845
|
+
type: "FeatureCollection",
|
|
9846
|
+
features: []
|
|
9847
|
+
};
|
|
9848
|
+
const outline = [
|
|
9849
|
+
...geometry.shaftLeft,
|
|
9850
|
+
geometry.headRing[0],
|
|
9851
|
+
geometry.headRing[1],
|
|
9852
|
+
geometry.headRing[2],
|
|
9853
|
+
...[...geometry.shaftRight].reverse()
|
|
9854
|
+
];
|
|
9855
|
+
const centerline = geometry.shaftCenterline;
|
|
9856
|
+
const { segments, totalLength } = polylineSegments(centerline);
|
|
9857
|
+
if (segments.length === 0) return {
|
|
9858
|
+
type: "FeatureCollection",
|
|
9859
|
+
features: []
|
|
9860
|
+
};
|
|
9861
|
+
const tipVector = vecSub(geometry.ptTip, geometry.ptBase);
|
|
9862
|
+
const tipDirection = vecNorm(tipVector);
|
|
9863
|
+
const headWidth = vecMag(vecSub(geometry.headRing[0], geometry.headRing[2]));
|
|
9864
|
+
let crossbarCenter;
|
|
9865
|
+
let crossbarAlong;
|
|
9866
|
+
let crossbarBaseWidth = headWidth;
|
|
9867
|
+
if (config.crossbarAt === "tip") {
|
|
9868
|
+
crossbarCenter = geometry.ptTip;
|
|
9869
|
+
crossbarAlong = tipDirection;
|
|
9870
|
+
} else {
|
|
9871
|
+
const shaftPosition = Number.isFinite(config.crossbarAt.shaftPosition) ? clamp01(config.crossbarAt.shaftPosition) : 0;
|
|
9872
|
+
const crossbarFrame = pointAlongPolyline(segments, totalLength, shaftPosition);
|
|
9873
|
+
crossbarCenter = crossbarFrame.point;
|
|
9874
|
+
crossbarAlong = crossbarFrame.along;
|
|
9875
|
+
const rearWidth = vecMag(vecSub(geometry.shaftLeft[0], geometry.shaftRight[0]));
|
|
9876
|
+
crossbarBaseWidth = rearWidth + (vecMag(vecSub(geometry.shaftLeft.at(-1), geometry.shaftRight.at(-1))) - rearWidth) * shaftPosition;
|
|
9877
|
+
}
|
|
9878
|
+
const crossbarLength = crossbarBaseWidth * Math.max(1, resolved.crossbarLengthRatio);
|
|
9879
|
+
const crossbarPerp = [-crossbarAlong[1], crossbarAlong[0]];
|
|
9880
|
+
const crossbar = [vecAdd(crossbarCenter, vecScale(crossbarPerp, crossbarLength / 2)), vecSub(crossbarCenter, vecScale(crossbarPerp, crossbarLength / 2))];
|
|
9881
|
+
const features = [{
|
|
9882
|
+
type: "Feature",
|
|
9883
|
+
properties: { part: config.part },
|
|
9884
|
+
geometry: {
|
|
9885
|
+
type: "MultiLineString",
|
|
9886
|
+
coordinates: [outline, crossbar].map((line) => line.map((point) => unproject(point[0], point[1])))
|
|
9887
|
+
}
|
|
9888
|
+
}];
|
|
9889
|
+
const sizeProps = labelSizeProps(resolved);
|
|
9890
|
+
const codePoint = vecAdd(geometry.ptBase, vecScale(tipVector, .55));
|
|
9891
|
+
const labels = [{
|
|
9892
|
+
type: "Feature",
|
|
9893
|
+
properties: {
|
|
9894
|
+
part: "label",
|
|
9895
|
+
labelPlacement: false,
|
|
9896
|
+
text: config.code,
|
|
9897
|
+
rotation: labelRotationAlong(tipDirection),
|
|
9898
|
+
...sizeProps
|
|
9899
|
+
},
|
|
9900
|
+
geometry: {
|
|
9901
|
+
type: "Point",
|
|
9902
|
+
coordinates: unproject(codePoint[0], codePoint[1])
|
|
9903
|
+
}
|
|
9904
|
+
}];
|
|
9905
|
+
features.push(...labels, ...createManeuverArrowTaskAmplifierLabels(geometry, resolved, textAmplifiers));
|
|
9906
|
+
return {
|
|
9907
|
+
type: "FeatureCollection",
|
|
9908
|
+
features
|
|
9909
|
+
};
|
|
9910
|
+
}
|
|
9911
|
+
const MANEUVER_ARROW_TASK_AMPLIFIER_PARAMS = [
|
|
9912
|
+
{
|
|
9913
|
+
key: "labelPosition",
|
|
9914
|
+
presentationTier: "advanced",
|
|
9915
|
+
label: "Designation position",
|
|
9916
|
+
description: "Position of Field T along the shaft, from tail (0) to head (1)",
|
|
9917
|
+
type: "number",
|
|
9918
|
+
min: 0,
|
|
9919
|
+
max: 1,
|
|
9920
|
+
step: .01
|
|
9921
|
+
},
|
|
9922
|
+
{
|
|
9923
|
+
key: "dtgPosition",
|
|
9924
|
+
presentationTier: "advanced",
|
|
9925
|
+
label: "Date-time position",
|
|
9926
|
+
description: "Position of Fields W/W1 along the shaft, from tail (0) to head (1)",
|
|
9927
|
+
type: "number",
|
|
9928
|
+
min: 0,
|
|
9929
|
+
max: 1,
|
|
9930
|
+
step: .01
|
|
9931
|
+
},
|
|
9932
|
+
{
|
|
9933
|
+
key: "labelPadding",
|
|
9934
|
+
presentationTier: "advanced",
|
|
9935
|
+
label: "Label padding",
|
|
9936
|
+
description: "Extra spacing between the W and W1 rows, in label heights",
|
|
9937
|
+
type: "number",
|
|
9938
|
+
min: 0,
|
|
9939
|
+
max: 2,
|
|
9940
|
+
step: .05
|
|
9941
|
+
}
|
|
9942
|
+
];
|
|
9943
|
+
const MANEUVER_ARROW_TASK_PARAMS = [{
|
|
9944
|
+
key: "crossbarLengthRatio",
|
|
9945
|
+
presentationTier: "advanced",
|
|
9946
|
+
label: "Straight line length",
|
|
9947
|
+
description: "Crossbar length as a multiple of the arrowhead base width",
|
|
9948
|
+
type: "number",
|
|
9949
|
+
min: 1,
|
|
9950
|
+
max: 2.5,
|
|
9951
|
+
step: .1
|
|
9952
|
+
}, ...MANEUVER_ARROW_TASK_AMPLIFIER_PARAMS];
|
|
9953
|
+
const MANEUVER_ARROW_TASK_TEXT_AMPLIFIERS = [
|
|
9954
|
+
{
|
|
9955
|
+
key: "T",
|
|
9956
|
+
label: "Unique designation",
|
|
9957
|
+
description: "Field T — the maneuver's unique designation.",
|
|
9958
|
+
placeholder: "DAVID",
|
|
9959
|
+
maxLength: 21
|
|
9960
|
+
},
|
|
9961
|
+
{
|
|
9962
|
+
key: "W",
|
|
9963
|
+
label: "Start date-time group",
|
|
9964
|
+
description: "Field W — the maneuver's start date-time group.",
|
|
9965
|
+
placeholder: "080400ZOCT2008"
|
|
9966
|
+
},
|
|
9967
|
+
{
|
|
9968
|
+
key: "W1",
|
|
9969
|
+
label: "End date-time group",
|
|
9970
|
+
description: "Field W1 — the maneuver's end date-time group.",
|
|
9971
|
+
placeholder: "120300ZOCT2008"
|
|
9972
|
+
}
|
|
9973
|
+
];
|
|
9974
|
+
//#endregion
|
|
9794
9975
|
//#region src/generators/cm15-maneuver-areas/supportingAttack.ts
|
|
9795
9976
|
const DEFAULT_SUPPORTING_ATTACK_OPTIONS = {
|
|
9796
9977
|
shaftWidthRatio: DEFAULT_SHAFT_WIDTH_RATIO$1,
|
|
9797
9978
|
rearWidthRatio: DEFAULT_REAR_WIDTH_RATIO,
|
|
9798
9979
|
smooth: false,
|
|
9799
|
-
smoothResolution: 5
|
|
9980
|
+
smoothResolution: 5,
|
|
9981
|
+
...DEFAULT_MANEUVER_ARROW_TASK_AMPLIFIER_OPTIONS
|
|
9800
9982
|
};
|
|
9801
9983
|
const SUPPORTING_ATTACK_METADATA = {
|
|
9802
9984
|
id: "supporting-attack",
|
|
@@ -9808,14 +9990,16 @@ const SUPPORTING_ATTACK_METADATA = {
|
|
|
9808
9990
|
value: "151404",
|
|
9809
9991
|
minCoordinates: 3,
|
|
9810
9992
|
geometry: "line",
|
|
9811
|
-
geometryTypes: ["LineString"],
|
|
9993
|
+
geometryTypes: ["LineString", "Point"],
|
|
9812
9994
|
paints: {
|
|
9813
9995
|
stroke: true,
|
|
9814
9996
|
fill: "none",
|
|
9815
|
-
text:
|
|
9997
|
+
text: true
|
|
9816
9998
|
},
|
|
9817
9999
|
drawRule: "Axis1",
|
|
9818
|
-
|
|
10000
|
+
capturesLabelSize: true,
|
|
10001
|
+
params: [...ATTACK_SHAFT_PARAMS, ...MANEUVER_ARROW_TASK_AMPLIFIER_PARAMS],
|
|
10002
|
+
textAmplifiers: MANEUVER_ARROW_TASK_TEXT_AMPLIFIERS
|
|
9819
10003
|
};
|
|
9820
10004
|
/**
|
|
9821
10005
|
* Generates a GeoJSON FeatureCollection for a Supporting Attack tactical symbol.
|
|
@@ -9827,7 +10011,7 @@ const SUPPORTING_ATTACK_METADATA = {
|
|
|
9827
10011
|
* @param options - Configuration options.
|
|
9828
10012
|
* @returns A GeoJSON FeatureCollection<LineString>.
|
|
9829
10013
|
*/
|
|
9830
|
-
function createSupportingAttack(coordinates, options = {}) {
|
|
10014
|
+
function createSupportingAttack(coordinates, options = {}, textAmplifiers = {}) {
|
|
9831
10015
|
const { geometry } = processAttackGeometry(coordinates, options);
|
|
9832
10016
|
if (!geometry) return {
|
|
9833
10017
|
type: "FeatureCollection",
|
|
@@ -9842,7 +10026,7 @@ function createSupportingAttack(coordinates, options = {}) {
|
|
|
9842
10026
|
type: "LineString",
|
|
9843
10027
|
coordinates: supportingAttackOutline(geometry)
|
|
9844
10028
|
}
|
|
9845
|
-
}]
|
|
10029
|
+
}, ...createManeuverArrowTaskAmplifierLabels(geometry, options, textAmplifiers)]
|
|
9846
10030
|
};
|
|
9847
10031
|
}
|
|
9848
10032
|
/**
|
|
@@ -9867,7 +10051,21 @@ const SUPPORTING_ATTACK = defineControlMeasure({
|
|
|
9867
10051
|
generator: createSupportingAttack,
|
|
9868
10052
|
defaultOptions: DEFAULT_SUPPORTING_ATTACK_OPTIONS,
|
|
9869
10053
|
rule: axis1DrawRule,
|
|
9870
|
-
optionHandles: variableWidthAttackOptionHandles
|
|
10054
|
+
optionHandles: variableWidthAttackOptionHandles,
|
|
10055
|
+
previewSample: {
|
|
10056
|
+
controlPoints: [
|
|
10057
|
+
[1, 0],
|
|
10058
|
+
[-.4, -.35],
|
|
10059
|
+
[-1, -.8],
|
|
10060
|
+
[.45, .55]
|
|
10061
|
+
],
|
|
10062
|
+
textAmplifiers: {
|
|
10063
|
+
T: "DAVID",
|
|
10064
|
+
W: "080400Z",
|
|
10065
|
+
W1: "120300Z"
|
|
10066
|
+
},
|
|
10067
|
+
options: { labelSize: 80 }
|
|
10068
|
+
}
|
|
9871
10069
|
});
|
|
9872
10070
|
//#endregion
|
|
9873
10071
|
//#region src/generators/cm34-mission-tasks/counterattack.ts
|
|
@@ -9917,14 +10115,20 @@ const COUNTERATTACK_METADATA = {
|
|
|
9917
10115
|
};
|
|
9918
10116
|
/** Generates the Counterattack mission task (340600). */
|
|
9919
10117
|
function createCounterattack(coordinates, options = {}, textAmplifiers = {}) {
|
|
9920
|
-
const outline = createSupportingAttack(coordinates, options);
|
|
9921
10118
|
const { geometry } = processAttackGeometry(coordinates, options);
|
|
9922
|
-
if (!geometry
|
|
10119
|
+
if (!geometry) return {
|
|
10120
|
+
type: "FeatureCollection",
|
|
10121
|
+
features: []
|
|
10122
|
+
};
|
|
9923
10123
|
const dashedOutline = {
|
|
9924
|
-
|
|
10124
|
+
type: "Feature",
|
|
9925
10125
|
properties: {
|
|
9926
10126
|
part: "counterattack",
|
|
9927
10127
|
style: { strokeDash: [...COUNTERATTACK_DASH] }
|
|
10128
|
+
},
|
|
10129
|
+
geometry: {
|
|
10130
|
+
type: "LineString",
|
|
10131
|
+
coordinates: supportingAttackOutline(geometry)
|
|
9928
10132
|
}
|
|
9929
10133
|
};
|
|
9930
10134
|
const { segments, totalLength } = polylineSegments(geometry.shaftCenterline);
|
|
@@ -10180,7 +10384,7 @@ function buildFieldOfFireV(coordinates, hSize, hWidth) {
|
|
|
10180
10384
|
};
|
|
10181
10385
|
}
|
|
10182
10386
|
//#endregion
|
|
10183
|
-
//#region src/
|
|
10387
|
+
//#region src/tactical-arrow-utils.ts
|
|
10184
10388
|
const DEFAULT_ARROWHEAD_LENGTH_RATIO = .12;
|
|
10185
10389
|
const DEFAULT_ARROWHEAD_WIDTH_RATIO = .5;
|
|
10186
10390
|
const DEFAULT_ZIGZAG_START = .55;
|
|
@@ -10196,183 +10400,17 @@ const DEFAULT_TACTICAL_ARROW_OPTIONS = {
|
|
|
10196
10400
|
zigzagEnd: DEFAULT_ZIGZAG_END,
|
|
10197
10401
|
zigzagAmplitudeRatio: DEFAULT_ZIGZAG_AMPLITUDE_RATIO
|
|
10198
10402
|
};
|
|
10199
|
-
|
|
10200
|
-
|
|
10201
|
-
|
|
10202
|
-
|
|
10203
|
-
|
|
10204
|
-
|
|
10205
|
-
|
|
10206
|
-
|
|
10207
|
-
|
|
10208
|
-
|
|
10209
|
-
|
|
10210
|
-
paints: {
|
|
10211
|
-
stroke: true,
|
|
10212
|
-
fill: "fixed",
|
|
10213
|
-
text: false
|
|
10214
|
-
},
|
|
10215
|
-
drawRule: "Area21",
|
|
10216
|
-
params: [
|
|
10217
|
-
{
|
|
10218
|
-
key: "arrowheadLengthRatio",
|
|
10219
|
-
presentationTier: "advanced",
|
|
10220
|
-
label: "Arrowhead length",
|
|
10221
|
-
description: "Arrowhead length as a ratio of the total leg length",
|
|
10222
|
-
type: "number",
|
|
10223
|
-
min: 0,
|
|
10224
|
-
max: .5,
|
|
10225
|
-
step: .01
|
|
10226
|
-
},
|
|
10227
|
-
{
|
|
10228
|
-
key: "arrowheadWidthRatio",
|
|
10229
|
-
presentationTier: "advanced",
|
|
10230
|
-
label: "Arrowhead width",
|
|
10231
|
-
description: "Arrowhead base width as a multiple of the arrowhead length",
|
|
10232
|
-
type: "number",
|
|
10233
|
-
min: .05,
|
|
10234
|
-
max: 2,
|
|
10235
|
-
step: .1
|
|
10236
|
-
},
|
|
10237
|
-
{
|
|
10238
|
-
key: "zigzagStart",
|
|
10239
|
-
presentationTier: "advanced",
|
|
10240
|
-
label: "Zigzag start",
|
|
10241
|
-
description: "Point along the leg where the zigzag begins (0-1).",
|
|
10242
|
-
type: "number",
|
|
10243
|
-
min: 0,
|
|
10244
|
-
max: 1,
|
|
10245
|
-
step: .05
|
|
10246
|
-
},
|
|
10247
|
-
{
|
|
10248
|
-
key: "zigzagEnd",
|
|
10249
|
-
presentationTier: "advanced",
|
|
10250
|
-
label: "Zigzag end",
|
|
10251
|
-
description: "Point along the leg where the zigzag returns (0-1).",
|
|
10252
|
-
type: "number",
|
|
10253
|
-
min: 0,
|
|
10254
|
-
max: 1,
|
|
10255
|
-
step: .05
|
|
10256
|
-
},
|
|
10257
|
-
{
|
|
10258
|
-
key: "zigzagAmplitudeRatio",
|
|
10259
|
-
presentationTier: "advanced",
|
|
10260
|
-
label: "Zigzag amplitude",
|
|
10261
|
-
description: "Perpendicular kink offset as a ratio of leg length.",
|
|
10262
|
-
type: "number",
|
|
10263
|
-
min: 0,
|
|
10264
|
-
max: .3,
|
|
10265
|
-
step: .01
|
|
10266
|
-
}
|
|
10267
|
-
]
|
|
10268
|
-
};
|
|
10269
|
-
/**
|
|
10270
|
-
* Generates a GeoJSON FeatureCollection of a tactical "lightning" arrow.
|
|
10271
|
-
* Handles internal projection to maintain correct perpendicularity.
|
|
10272
|
-
*
|
|
10273
|
-
* @param coordinates - [Apex Point (PT1), Top Tail (PT2), Bottom Tail (PT3)]
|
|
10274
|
-
* @param options - Geometric configuration parameters
|
|
10275
|
-
* @returns A FeatureCollection containing a MultiLineString for bolts and a MultiPolygon for heads
|
|
10276
|
-
*/
|
|
10277
|
-
function createSearchArea(coordinates, options = {}) {
|
|
10278
|
-
const { arrowheadLengthRatio = DEFAULT_ARROWHEAD_LENGTH_RATIO, arrowheadWidthRatio = DEFAULT_ARROWHEAD_WIDTH_RATIO, zigzagStart = DEFAULT_ZIGZAG_START, zigzagEnd = DEFAULT_ZIGZAG_END, zigzagAmplitudeRatio = DEFAULT_ZIGZAG_AMPLITUDE_RATIO } = options;
|
|
10279
|
-
const safeArrowheadLengthRatio = Math.max(0, arrowheadLengthRatio);
|
|
10280
|
-
const safeArrowheadWidthRatio = Math.max(0, arrowheadWidthRatio);
|
|
10281
|
-
const safeZigzagStart = clamp01(zigzagStart);
|
|
10282
|
-
const safeZigzagEnd = clamp01(zigzagEnd);
|
|
10283
|
-
const safeZigzagAmplitudeRatio = Math.max(0, zigzagAmplitudeRatio);
|
|
10284
|
-
const [c0, c1, c2] = coordinates;
|
|
10285
|
-
const [p1, p2, p3] = [
|
|
10286
|
-
project(c0[0], c0[1]),
|
|
10287
|
-
project(c1[0], c1[1]),
|
|
10288
|
-
project(c2[0], c2[1])
|
|
10289
|
-
];
|
|
10290
|
-
const shaftSegments = [];
|
|
10291
|
-
const headPolygons = [];
|
|
10292
|
-
const addLeg = (origin, tip, opposite) => {
|
|
10293
|
-
const v = vecSub(tip, origin);
|
|
10294
|
-
const totalLen = vecMag(v);
|
|
10295
|
-
if (totalLen < 1e-6) return;
|
|
10296
|
-
const u = vecNorm(v);
|
|
10297
|
-
let n = [-u[1], u[0]];
|
|
10298
|
-
const toOpp = vecSub(opposite, origin);
|
|
10299
|
-
if (vecDot(n, toOpp) > 0) n = vecScale(n, -1);
|
|
10300
|
-
const offset = totalLen * safeZigzagAmplitudeRatio;
|
|
10301
|
-
const ptA = vecAdd(vecAdd(origin, vecScale(v, safeZigzagStart)), vecScale(n, offset));
|
|
10302
|
-
const ptB = vecAdd(vecAdd(origin, vecScale(v, safeZigzagEnd)), vecScale(n, -offset));
|
|
10303
|
-
const fv = vecSub(tip, ptB);
|
|
10304
|
-
const fLen = vecMag(fv);
|
|
10305
|
-
let fu = vecNorm(fv);
|
|
10306
|
-
if (fLen < 1e-6) fu = u;
|
|
10307
|
-
const headLen = totalLen * safeArrowheadLengthRatio;
|
|
10308
|
-
const pBase = vecSub(tip, vecScale(fu, headLen));
|
|
10309
|
-
const wingWidth = headLen * safeArrowheadWidthRatio;
|
|
10310
|
-
const perp = [-fu[1], fu[0]];
|
|
10311
|
-
const pWing1 = vecAdd(pBase, vecScale(perp, wingWidth));
|
|
10312
|
-
const pWing2 = vecSub(pBase, vecScale(perp, wingWidth));
|
|
10313
|
-
shaftSegments.push([
|
|
10314
|
-
origin,
|
|
10315
|
-
ptA,
|
|
10316
|
-
ptB,
|
|
10317
|
-
pBase
|
|
10318
|
-
].map((p) => unproject(p[0], p[1])));
|
|
10319
|
-
headPolygons.push([[
|
|
10320
|
-
tip,
|
|
10321
|
-
pWing1,
|
|
10322
|
-
pWing2,
|
|
10323
|
-
tip
|
|
10324
|
-
].map((p) => unproject(p[0], p[1]))]);
|
|
10325
|
-
};
|
|
10326
|
-
addLeg(p1, p2, p3);
|
|
10327
|
-
addLeg(p1, p3, p2);
|
|
10328
|
-
const features = [];
|
|
10329
|
-
if (shaftSegments.length > 0) features.push({
|
|
10330
|
-
type: "Feature",
|
|
10331
|
-
properties: { part: "shaft" },
|
|
10332
|
-
geometry: {
|
|
10333
|
-
type: "MultiLineString",
|
|
10334
|
-
coordinates: shaftSegments
|
|
10335
|
-
}
|
|
10336
|
-
});
|
|
10337
|
-
if (headPolygons.length > 0) features.push({
|
|
10338
|
-
type: "Feature",
|
|
10339
|
-
properties: {
|
|
10340
|
-
part: "head",
|
|
10341
|
-
fill: true,
|
|
10342
|
-
style: SOLID_ACCENT_FILL
|
|
10343
|
-
},
|
|
10344
|
-
geometry: {
|
|
10345
|
-
type: "MultiPolygon",
|
|
10346
|
-
coordinates: headPolygons
|
|
10347
|
-
}
|
|
10348
|
-
});
|
|
10349
|
-
return {
|
|
10350
|
-
type: "FeatureCollection",
|
|
10351
|
-
features
|
|
10352
|
-
};
|
|
10353
|
-
}
|
|
10354
|
-
const SEARCH_AREA = defineControlMeasure({
|
|
10355
|
-
metadata: SEARCH_AREA_METADATA,
|
|
10356
|
-
generator: (controlPoints, options) => createSearchArea(asThreePoints(controlPoints), options),
|
|
10357
|
-
defaultOptions: DEFAULT_TACTICAL_ARROW_OPTIONS,
|
|
10358
|
-
rule: searchAreaDrawRule
|
|
10359
|
-
});
|
|
10360
|
-
//#endregion
|
|
10361
|
-
//#region src/generators/cm34-mission-tasks/security-task-shared.ts
|
|
10362
|
-
const DEFAULT_SECURITY_TASK_OPTIONS = {
|
|
10363
|
-
...DEFAULT_TACTICAL_ARROW_OPTIONS,
|
|
10364
|
-
labelInsetRatio: .2
|
|
10365
|
-
};
|
|
10366
|
-
const SECURITY_TASK_PARAMS = [
|
|
10367
|
-
{
|
|
10368
|
-
key: "arrowheadLengthRatio",
|
|
10369
|
-
presentationTier: "advanced",
|
|
10370
|
-
label: "Arrowhead length",
|
|
10371
|
-
description: "Arrowhead length as a ratio of the total leg length",
|
|
10372
|
-
type: "number",
|
|
10373
|
-
min: 0,
|
|
10374
|
-
max: .5,
|
|
10375
|
-
step: .01
|
|
10403
|
+
/** The param descriptors every tactical-arrow leg exposes, in presentation order. */
|
|
10404
|
+
const TACTICAL_ARROW_PARAMS = [
|
|
10405
|
+
{
|
|
10406
|
+
key: "arrowheadLengthRatio",
|
|
10407
|
+
presentationTier: "advanced",
|
|
10408
|
+
label: "Arrowhead length",
|
|
10409
|
+
description: "Arrowhead length as a ratio of the total leg length",
|
|
10410
|
+
type: "number",
|
|
10411
|
+
min: 0,
|
|
10412
|
+
max: .5,
|
|
10413
|
+
step: .01
|
|
10376
10414
|
},
|
|
10377
10415
|
{
|
|
10378
10416
|
key: "arrowheadWidthRatio",
|
|
@@ -10413,19 +10451,39 @@ const SECURITY_TASK_PARAMS = [
|
|
|
10413
10451
|
min: 0,
|
|
10414
10452
|
max: .3,
|
|
10415
10453
|
step: .01
|
|
10416
|
-
},
|
|
10417
|
-
{
|
|
10418
|
-
key: "labelInsetRatio",
|
|
10419
|
-
presentationTier: "advanced",
|
|
10420
|
-
label: "Letter position",
|
|
10421
|
-
description: "Inset of each task letter from PT. 2 or PT. 3 across the center opening",
|
|
10422
|
-
type: "number",
|
|
10423
|
-
min: .05,
|
|
10424
|
-
max: .45,
|
|
10425
|
-
step: .01
|
|
10426
10454
|
}
|
|
10427
10455
|
];
|
|
10428
10456
|
/**
|
|
10457
|
+
* The single home of the tactical-arrow option contract: fills in the defaults
|
|
10458
|
+
* and applies the clamps the leg geometry relies on, so every measure carrying
|
|
10459
|
+
* a lightning leg reads the same ranges.
|
|
10460
|
+
*/
|
|
10461
|
+
function resolveTacticalArrowOptions(options) {
|
|
10462
|
+
return {
|
|
10463
|
+
arrowheadLengthRatio: Math.max(0, options.arrowheadLengthRatio ?? DEFAULT_ARROWHEAD_LENGTH_RATIO),
|
|
10464
|
+
arrowheadWidthRatio: Math.max(0, options.arrowheadWidthRatio ?? DEFAULT_ARROWHEAD_WIDTH_RATIO),
|
|
10465
|
+
zigzagStart: clamp01(options.zigzagStart ?? DEFAULT_ZIGZAG_START),
|
|
10466
|
+
zigzagEnd: clamp01(options.zigzagEnd ?? DEFAULT_ZIGZAG_END),
|
|
10467
|
+
zigzagAmplitudeRatio: Math.max(0, options.zigzagAmplitudeRatio ?? DEFAULT_ZIGZAG_AMPLITUDE_RATIO)
|
|
10468
|
+
};
|
|
10469
|
+
}
|
|
10470
|
+
//#endregion
|
|
10471
|
+
//#region src/generators/cm34-mission-tasks/security-task-shared.ts
|
|
10472
|
+
const DEFAULT_SECURITY_TASK_OPTIONS = {
|
|
10473
|
+
...DEFAULT_TACTICAL_ARROW_OPTIONS,
|
|
10474
|
+
labelInsetRatio: .2
|
|
10475
|
+
};
|
|
10476
|
+
const SECURITY_TASK_PARAMS = [...TACTICAL_ARROW_PARAMS, {
|
|
10477
|
+
key: "labelInsetRatio",
|
|
10478
|
+
presentationTier: "advanced",
|
|
10479
|
+
label: "Letter position",
|
|
10480
|
+
description: "Inset of each task letter from PT. 2 or PT. 3 across the center opening",
|
|
10481
|
+
type: "number",
|
|
10482
|
+
min: .05,
|
|
10483
|
+
max: .45,
|
|
10484
|
+
step: .01
|
|
10485
|
+
}];
|
|
10486
|
+
/**
|
|
10429
10487
|
* Shared Line26 geometry for the Cover, Guard, and Screen task family.
|
|
10430
10488
|
* PT. 1/PT. 4 are the outward tips; PT. 2/PT. 3 are the inner shaft ends.
|
|
10431
10489
|
* The opening between PT. 2 and PT. 3 is deliberately left clear for the
|
|
@@ -10443,13 +10501,7 @@ function createSecurityTaskSymbol(coordinates, config, options = {}) {
|
|
|
10443
10501
|
...DEFAULT_SECURITY_TASK_OPTIONS,
|
|
10444
10502
|
...options
|
|
10445
10503
|
};
|
|
10446
|
-
const arrowOptions =
|
|
10447
|
-
arrowheadLengthRatio: Math.max(0, resolved.arrowheadLengthRatio),
|
|
10448
|
-
arrowheadWidthRatio: Math.max(0, resolved.arrowheadWidthRatio),
|
|
10449
|
-
zigzagStart: clamp(resolved.zigzagStart, 0, 1),
|
|
10450
|
-
zigzagEnd: clamp(resolved.zigzagEnd, 0, 1),
|
|
10451
|
-
zigzagAmplitudeRatio: Math.max(0, resolved.zigzagAmplitudeRatio)
|
|
10452
|
-
};
|
|
10504
|
+
const arrowOptions = resolveTacticalArrowOptions(resolved);
|
|
10453
10505
|
const lines = [createTacticalArrow(p2, p1, p3, arrowOptions), createTacticalArrow(p3, p4, p2, arrowOptions)].flatMap((arrow) => arrow ?? []);
|
|
10454
10506
|
if (lines.length === 0) return {
|
|
10455
10507
|
type: "FeatureCollection",
|
|
@@ -12713,7 +12765,7 @@ function createInfiltrationLane(coordinates, options = {}, textAmplifiers = {})
|
|
|
12713
12765
|
type: "Feature",
|
|
12714
12766
|
properties: {
|
|
12715
12767
|
part: "label",
|
|
12716
|
-
|
|
12768
|
+
labelPlacementKey: "T",
|
|
12717
12769
|
amplifierField: "T",
|
|
12718
12770
|
text: textAmplifiers.T,
|
|
12719
12771
|
rotation: labelRotationAlong(labelFrame.along),
|
|
@@ -13662,215 +13714,54 @@ const FORTIFIED_AREA = defineControlMeasure({
|
|
|
13662
13714
|
}
|
|
13663
13715
|
});
|
|
13664
13716
|
//#endregion
|
|
13665
|
-
//#region src/generators/cm15-maneuver-areas/
|
|
13666
|
-
const
|
|
13667
|
-
|
|
13668
|
-
|
|
13669
|
-
|
|
13670
|
-
|
|
13671
|
-
|
|
13672
|
-
|
|
13673
|
-
|
|
13674
|
-
|
|
13717
|
+
//#region src/generators/cm15-maneuver-areas/frontal-attack.ts
|
|
13718
|
+
const DEFAULT_FRONTAL_ATTACK_OPTIONS = DEFAULT_MANEUVER_ARROW_TASK_OPTIONS;
|
|
13719
|
+
const FRONTAL_ATTACK_METADATA = {
|
|
13720
|
+
id: "frontal-attack",
|
|
13721
|
+
name: "Frontal Attack",
|
|
13722
|
+
description: "An offensive maneuver in which the main action is directed against the front.",
|
|
13723
|
+
entity: "Maneuver Areas",
|
|
13724
|
+
entityType: "Frontal Attack",
|
|
13725
|
+
value: "152700",
|
|
13726
|
+
minCoordinates: 3,
|
|
13727
|
+
geometry: "line",
|
|
13728
|
+
geometryTypes: ["MultiLineString", "Point"],
|
|
13729
|
+
paints: {
|
|
13730
|
+
stroke: true,
|
|
13731
|
+
fill: "none",
|
|
13732
|
+
text: true
|
|
13733
|
+
},
|
|
13734
|
+
drawRule: "Axis1",
|
|
13735
|
+
capturesLabelSize: true,
|
|
13736
|
+
params: [...ATTACK_SHAFT_PARAMS, ...MANEUVER_ARROW_TASK_PARAMS],
|
|
13737
|
+
textAmplifiers: MANEUVER_ARROW_TASK_TEXT_AMPLIFIERS
|
|
13675
13738
|
};
|
|
13676
|
-
function
|
|
13677
|
-
|
|
13678
|
-
|
|
13679
|
-
|
|
13680
|
-
|
|
13681
|
-
|
|
13682
|
-
|
|
13683
|
-
|
|
13684
|
-
|
|
13685
|
-
|
|
13686
|
-
|
|
13687
|
-
|
|
13688
|
-
|
|
13689
|
-
|
|
13690
|
-
|
|
13691
|
-
|
|
13692
|
-
|
|
13693
|
-
|
|
13694
|
-
|
|
13695
|
-
|
|
13696
|
-
|
|
13697
|
-
|
|
13698
|
-
|
|
13699
|
-
|
|
13700
|
-
|
|
13701
|
-
|
|
13702
|
-
};
|
|
13703
|
-
const tipVector = vecSub(geometry.ptTip, geometry.ptBase);
|
|
13704
|
-
const tipDirection = vecNorm(tipVector);
|
|
13705
|
-
const headWidth = vecMag(vecSub(geometry.headRing[0], geometry.headRing[2]));
|
|
13706
|
-
let crossbarCenter;
|
|
13707
|
-
let crossbarAlong;
|
|
13708
|
-
let crossbarBaseWidth = headWidth;
|
|
13709
|
-
if (config.crossbarAt === "tip") {
|
|
13710
|
-
crossbarCenter = geometry.ptTip;
|
|
13711
|
-
crossbarAlong = tipDirection;
|
|
13712
|
-
} else {
|
|
13713
|
-
const shaftPosition = Number.isFinite(config.crossbarAt.shaftPosition) ? clamp01(config.crossbarAt.shaftPosition) : 0;
|
|
13714
|
-
const crossbarFrame = pointAlongPolyline(segments, totalLength, shaftPosition);
|
|
13715
|
-
crossbarCenter = crossbarFrame.point;
|
|
13716
|
-
crossbarAlong = crossbarFrame.along;
|
|
13717
|
-
const rearWidth = vecMag(vecSub(geometry.shaftLeft[0], geometry.shaftRight[0]));
|
|
13718
|
-
crossbarBaseWidth = rearWidth + (vecMag(vecSub(geometry.shaftLeft.at(-1), geometry.shaftRight.at(-1))) - rearWidth) * shaftPosition;
|
|
13719
|
-
}
|
|
13720
|
-
const crossbarLength = crossbarBaseWidth * Math.max(1, resolved.crossbarLengthRatio);
|
|
13721
|
-
const crossbarPerp = [-crossbarAlong[1], crossbarAlong[0]];
|
|
13722
|
-
const crossbar = [vecAdd(crossbarCenter, vecScale(crossbarPerp, crossbarLength / 2)), vecSub(crossbarCenter, vecScale(crossbarPerp, crossbarLength / 2))];
|
|
13723
|
-
const features = [{
|
|
13724
|
-
type: "Feature",
|
|
13725
|
-
properties: { part: config.part },
|
|
13726
|
-
geometry: {
|
|
13727
|
-
type: "MultiLineString",
|
|
13728
|
-
coordinates: [outline, crossbar].map((line) => line.map((point) => unproject(point[0], point[1])))
|
|
13729
|
-
}
|
|
13730
|
-
}];
|
|
13731
|
-
const sizeProps = labelSizeProps(resolved);
|
|
13732
|
-
const codePoint = vecAdd(geometry.ptBase, vecScale(tipVector, .55));
|
|
13733
|
-
const labels = [{
|
|
13734
|
-
type: "Feature",
|
|
13735
|
-
properties: {
|
|
13736
|
-
part: "label",
|
|
13737
|
-
labelPlacement: false,
|
|
13738
|
-
text: config.code,
|
|
13739
|
-
rotation: labelRotationAlong(tipDirection),
|
|
13740
|
-
...sizeProps
|
|
13741
|
-
},
|
|
13742
|
-
geometry: {
|
|
13743
|
-
type: "Point",
|
|
13744
|
-
coordinates: unproject(codePoint[0], codePoint[1])
|
|
13745
|
-
}
|
|
13746
|
-
}];
|
|
13747
|
-
const labelFrame = pointAlongPolyline(segments, totalLength, clamp01(resolved.labelPosition));
|
|
13748
|
-
if (labelFrame && textAmplifiers.T) pushLabel(labels, labelFrame.point, textAmplifiers.T, labelRotationAlong(labelFrame.along), sizeProps, void 0, "T");
|
|
13749
|
-
const dtgFrame = pointAlongPolyline(segments, totalLength, clamp01(resolved.dtgPosition));
|
|
13750
|
-
if (dtgFrame) {
|
|
13751
|
-
const rowOffset = resolveLabelOffsetMeters(resolved, .55 + Math.max(0, resolved.labelPadding));
|
|
13752
|
-
const rotation = labelRotationAlong(dtgFrame.along);
|
|
13753
|
-
if (textAmplifiers.W) pushLabel(labels, vecAdd(dtgFrame.point, vecScale(dtgFrame.perp, rowOffset)), textAmplifiers.W, rotation, sizeProps, void 0, "W");
|
|
13754
|
-
if (textAmplifiers.W1) pushLabel(labels, vecSub(dtgFrame.point, vecScale(dtgFrame.perp, rowOffset)), textAmplifiers.W1, rotation, sizeProps, void 0, "W1");
|
|
13755
|
-
}
|
|
13756
|
-
features.push(...labels);
|
|
13757
|
-
return {
|
|
13758
|
-
type: "FeatureCollection",
|
|
13759
|
-
features
|
|
13760
|
-
};
|
|
13761
|
-
}
|
|
13762
|
-
const MANEUVER_ARROW_TASK_PARAMS = [
|
|
13763
|
-
{
|
|
13764
|
-
key: "crossbarLengthRatio",
|
|
13765
|
-
presentationTier: "advanced",
|
|
13766
|
-
label: "Straight line length",
|
|
13767
|
-
description: "Crossbar length as a multiple of the arrowhead base width",
|
|
13768
|
-
type: "number",
|
|
13769
|
-
min: 1,
|
|
13770
|
-
max: 2.5,
|
|
13771
|
-
step: .1
|
|
13772
|
-
},
|
|
13773
|
-
{
|
|
13774
|
-
key: "labelPosition",
|
|
13775
|
-
presentationTier: "advanced",
|
|
13776
|
-
label: "Designation position",
|
|
13777
|
-
description: "Position of Field T along the shaft, from tail (0) to head (1)",
|
|
13778
|
-
type: "number",
|
|
13779
|
-
min: 0,
|
|
13780
|
-
max: 1,
|
|
13781
|
-
step: .01
|
|
13782
|
-
},
|
|
13783
|
-
{
|
|
13784
|
-
key: "dtgPosition",
|
|
13785
|
-
presentationTier: "advanced",
|
|
13786
|
-
label: "Date-time position",
|
|
13787
|
-
description: "Position of Fields W/W1 along the shaft, from tail (0) to head (1)",
|
|
13788
|
-
type: "number",
|
|
13789
|
-
min: 0,
|
|
13790
|
-
max: 1,
|
|
13791
|
-
step: .01
|
|
13792
|
-
},
|
|
13793
|
-
{
|
|
13794
|
-
key: "labelPadding",
|
|
13795
|
-
presentationTier: "advanced",
|
|
13796
|
-
label: "Label padding",
|
|
13797
|
-
description: "Extra spacing between the W and W1 rows, in label heights",
|
|
13798
|
-
type: "number",
|
|
13799
|
-
min: 0,
|
|
13800
|
-
max: 2,
|
|
13801
|
-
step: .05
|
|
13802
|
-
}
|
|
13803
|
-
];
|
|
13804
|
-
const MANEUVER_ARROW_TASK_TEXT_AMPLIFIERS = [
|
|
13805
|
-
{
|
|
13806
|
-
key: "T",
|
|
13807
|
-
label: "Unique designation",
|
|
13808
|
-
description: "Field T — the maneuver's unique designation.",
|
|
13809
|
-
placeholder: "DAVID",
|
|
13810
|
-
maxLength: 21
|
|
13811
|
-
},
|
|
13812
|
-
{
|
|
13813
|
-
key: "W",
|
|
13814
|
-
label: "Start date-time group",
|
|
13815
|
-
description: "Field W — the maneuver's start date-time group.",
|
|
13816
|
-
placeholder: "080400ZOCT2008"
|
|
13817
|
-
},
|
|
13818
|
-
{
|
|
13819
|
-
key: "W1",
|
|
13820
|
-
label: "End date-time group",
|
|
13821
|
-
description: "Field W1 — the maneuver's end date-time group.",
|
|
13822
|
-
placeholder: "120300ZOCT2008"
|
|
13823
|
-
}
|
|
13824
|
-
];
|
|
13825
|
-
//#endregion
|
|
13826
|
-
//#region src/generators/cm15-maneuver-areas/frontal-attack.ts
|
|
13827
|
-
const DEFAULT_FRONTAL_ATTACK_OPTIONS = DEFAULT_MANEUVER_ARROW_TASK_OPTIONS;
|
|
13828
|
-
const FRONTAL_ATTACK_METADATA = {
|
|
13829
|
-
id: "frontal-attack",
|
|
13830
|
-
name: "Frontal Attack",
|
|
13831
|
-
description: "An offensive maneuver in which the main action is directed against the front.",
|
|
13832
|
-
entity: "Maneuver Areas",
|
|
13833
|
-
entityType: "Frontal Attack",
|
|
13834
|
-
value: "152700",
|
|
13835
|
-
minCoordinates: 3,
|
|
13836
|
-
geometry: "line",
|
|
13837
|
-
geometryTypes: ["MultiLineString", "Point"],
|
|
13838
|
-
paints: {
|
|
13839
|
-
stroke: true,
|
|
13840
|
-
fill: "none",
|
|
13841
|
-
text: true
|
|
13842
|
-
},
|
|
13843
|
-
drawRule: "Axis1",
|
|
13844
|
-
capturesLabelSize: true,
|
|
13845
|
-
params: [...ATTACK_SHAFT_PARAMS, ...MANEUVER_ARROW_TASK_PARAMS],
|
|
13846
|
-
textAmplifiers: MANEUVER_ARROW_TASK_TEXT_AMPLIFIERS
|
|
13847
|
-
};
|
|
13848
|
-
function createFrontalAttack(coordinates, options = {}, textAmplifiers = {}) {
|
|
13849
|
-
return createManeuverArrowTask(coordinates, options, textAmplifiers, {
|
|
13850
|
-
part: "frontal-attack",
|
|
13851
|
-
code: "A",
|
|
13852
|
-
crossbarAt: "tip"
|
|
13853
|
-
});
|
|
13854
|
-
}
|
|
13855
|
-
const FRONTAL_ATTACK = defineControlMeasure({
|
|
13856
|
-
metadata: FRONTAL_ATTACK_METADATA,
|
|
13857
|
-
generator: createFrontalAttack,
|
|
13858
|
-
defaultOptions: DEFAULT_FRONTAL_ATTACK_OPTIONS,
|
|
13859
|
-
rule: axis1DrawRule,
|
|
13860
|
-
optionHandles: variableWidthAttackOptionHandles,
|
|
13861
|
-
previewSample: {
|
|
13862
|
-
controlPoints: [
|
|
13863
|
-
[1, 0],
|
|
13864
|
-
[-.4, -.35],
|
|
13865
|
-
[-1, -.8],
|
|
13866
|
-
[.45, .55]
|
|
13867
|
-
],
|
|
13868
|
-
textAmplifiers: {
|
|
13869
|
-
T: "FOX",
|
|
13870
|
-
W: "080400Z",
|
|
13871
|
-
W1: "120300Z"
|
|
13872
|
-
},
|
|
13873
|
-
options: { labelSize: 80 }
|
|
13739
|
+
function createFrontalAttack(coordinates, options = {}, textAmplifiers = {}) {
|
|
13740
|
+
return createManeuverArrowTask(coordinates, options, textAmplifiers, {
|
|
13741
|
+
part: "frontal-attack",
|
|
13742
|
+
code: "A",
|
|
13743
|
+
crossbarAt: "tip"
|
|
13744
|
+
});
|
|
13745
|
+
}
|
|
13746
|
+
const FRONTAL_ATTACK = defineControlMeasure({
|
|
13747
|
+
metadata: FRONTAL_ATTACK_METADATA,
|
|
13748
|
+
generator: createFrontalAttack,
|
|
13749
|
+
defaultOptions: DEFAULT_FRONTAL_ATTACK_OPTIONS,
|
|
13750
|
+
rule: axis1DrawRule,
|
|
13751
|
+
optionHandles: variableWidthAttackOptionHandles,
|
|
13752
|
+
previewSample: {
|
|
13753
|
+
controlPoints: [
|
|
13754
|
+
[1, 0],
|
|
13755
|
+
[-.4, -.35],
|
|
13756
|
+
[-1, -.8],
|
|
13757
|
+
[.45, .55]
|
|
13758
|
+
],
|
|
13759
|
+
textAmplifiers: {
|
|
13760
|
+
T: "FOX",
|
|
13761
|
+
W: "080400Z",
|
|
13762
|
+
W1: "120300Z"
|
|
13763
|
+
},
|
|
13764
|
+
options: { labelSize: 80 }
|
|
13874
13765
|
}
|
|
13875
13766
|
});
|
|
13876
13767
|
//#endregion
|
|
@@ -14012,6 +13903,201 @@ const ISOLATE = defineControlMeasure({
|
|
|
14012
13903
|
rule: isolateDrawRule
|
|
14013
13904
|
});
|
|
14014
13905
|
//#endregion
|
|
13906
|
+
//#region src/generators/cm34-mission-tasks/movement-to-contact.ts
|
|
13907
|
+
/** Default bolt length as a multiple of the shaft width at the arrow base. */
|
|
13908
|
+
const MOVEMENT_TO_CONTACT_BOLT_LENGTH_RATIO = 1;
|
|
13909
|
+
const DEFAULT_MOVEMENT_TO_CONTACT_BOLT_ANGLE = 45;
|
|
13910
|
+
const MOVEMENT_TO_CONTACT_BOLT_HANDLE_ID = "bolt-tip";
|
|
13911
|
+
const MOVEMENT_TO_CONTACT_STEM_SETBACK_RATIO = .22;
|
|
13912
|
+
const MOVEMENT_TO_CONTACT_STEM_OFFSET_RATIO = .55;
|
|
13913
|
+
const DEFAULT_MOVEMENT_TO_CONTACT_OPTIONS = {
|
|
13914
|
+
shaftWidthRatio: DEFAULT_SHAFT_WIDTH_RATIO$1,
|
|
13915
|
+
rearWidthRatio: DEFAULT_REAR_WIDTH_RATIO,
|
|
13916
|
+
smooth: false,
|
|
13917
|
+
smoothResolution: 5,
|
|
13918
|
+
...DEFAULT_TACTICAL_ARROW_OPTIONS,
|
|
13919
|
+
...DEFAULT_MANEUVER_ARROW_TASK_AMPLIFIER_OPTIONS,
|
|
13920
|
+
boltLengthRatio: MOVEMENT_TO_CONTACT_BOLT_LENGTH_RATIO,
|
|
13921
|
+
boltAngle: DEFAULT_MOVEMENT_TO_CONTACT_BOLT_ANGLE
|
|
13922
|
+
};
|
|
13923
|
+
const MOVEMENT_TO_CONTACT_METADATA = {
|
|
13924
|
+
id: "movement-to-contact",
|
|
13925
|
+
name: "Movement to Contact",
|
|
13926
|
+
description: "Develops the situation and establishes or regains contact with the enemy.",
|
|
13927
|
+
entity: "Mission Tasks",
|
|
13928
|
+
entityType: "Movement to Contact",
|
|
13929
|
+
value: "342900",
|
|
13930
|
+
minCoordinates: 3,
|
|
13931
|
+
geometry: "line",
|
|
13932
|
+
geometryTypes: [
|
|
13933
|
+
"MultiLineString",
|
|
13934
|
+
"MultiPolygon",
|
|
13935
|
+
"Point"
|
|
13936
|
+
],
|
|
13937
|
+
paints: {
|
|
13938
|
+
stroke: true,
|
|
13939
|
+
fill: "fixed",
|
|
13940
|
+
text: true
|
|
13941
|
+
},
|
|
13942
|
+
drawRule: "Axis1",
|
|
13943
|
+
capturesLabelSize: true,
|
|
13944
|
+
params: [
|
|
13945
|
+
...ATTACK_SHAFT_PARAMS,
|
|
13946
|
+
{
|
|
13947
|
+
key: "boltLengthRatio",
|
|
13948
|
+
presentationTier: "advanced",
|
|
13949
|
+
label: "Bolt length",
|
|
13950
|
+
description: "Bolt length as a multiple of the shaft width at the arrow base (2 or more is recommended).",
|
|
13951
|
+
type: "number",
|
|
13952
|
+
min: .1,
|
|
13953
|
+
max: 6,
|
|
13954
|
+
step: .1
|
|
13955
|
+
},
|
|
13956
|
+
{
|
|
13957
|
+
key: "boltAngle",
|
|
13958
|
+
presentationTier: "advanced",
|
|
13959
|
+
label: "Bolt angle",
|
|
13960
|
+
description: "Angle between each bolt and the main arrow axis, in degrees.",
|
|
13961
|
+
type: "number",
|
|
13962
|
+
min: 0,
|
|
13963
|
+
max: 90,
|
|
13964
|
+
step: 1
|
|
13965
|
+
},
|
|
13966
|
+
...TACTICAL_ARROW_PARAMS,
|
|
13967
|
+
...MANEUVER_ARROW_TASK_AMPLIFIER_PARAMS
|
|
13968
|
+
],
|
|
13969
|
+
textAmplifiers: MANEUVER_ARROW_TASK_TEXT_AMPLIFIERS
|
|
13970
|
+
};
|
|
13971
|
+
function createBolt({ tip, arrowDirection, arrowBaseWidth, boltLengthRatio, boltAngle, side, options }) {
|
|
13972
|
+
const arrowNormal = [-arrowDirection[1], arrowDirection[0]];
|
|
13973
|
+
const direction = vecRotate(arrowDirection, side * (boltAngle * Math.PI / 180));
|
|
13974
|
+
const boltNormal = [-direction[1], direction[0]];
|
|
13975
|
+
const origin = vecAdd(vecAdd(tip, vecScale(arrowDirection, -arrowBaseWidth * MOVEMENT_TO_CONTACT_STEM_SETBACK_RATIO)), vecScale(arrowNormal, side * arrowBaseWidth * MOVEMENT_TO_CONTACT_STEM_OFFSET_RATIO));
|
|
13976
|
+
const boltLength = arrowBaseWidth * boltLengthRatio;
|
|
13977
|
+
const boltTip = vecAdd(origin, vecScale(direction, boltLength));
|
|
13978
|
+
const zigzagAmplitude = boltLength * options.zigzagAmplitudeRatio;
|
|
13979
|
+
const firstKink = vecAdd(vecAdd(origin, vecScale(direction, boltLength * options.zigzagStart)), vecScale(boltNormal, zigzagAmplitude));
|
|
13980
|
+
const secondKink = vecAdd(vecAdd(origin, vecScale(direction, boltLength * options.zigzagEnd)), vecScale(boltNormal, -zigzagAmplitude));
|
|
13981
|
+
const finalSegment = vecSub(boltTip, secondKink);
|
|
13982
|
+
const finalDirection = vecMag(finalSegment) >= 1e-6 ? vecNorm(finalSegment) : direction;
|
|
13983
|
+
const headLength = boltLength * options.arrowheadLengthRatio;
|
|
13984
|
+
return {
|
|
13985
|
+
origin,
|
|
13986
|
+
tip: boltTip,
|
|
13987
|
+
shaft: [
|
|
13988
|
+
origin,
|
|
13989
|
+
firstKink,
|
|
13990
|
+
secondKink,
|
|
13991
|
+
vecAdd(boltTip, vecScale(finalDirection, -headLength))
|
|
13992
|
+
],
|
|
13993
|
+
head: createArrowHeadRing(boltTip, finalDirection, headLength, headLength * options.arrowheadWidthRatio * 2)
|
|
13994
|
+
};
|
|
13995
|
+
}
|
|
13996
|
+
function resolveMovementToContactGeometry(coordinates, options) {
|
|
13997
|
+
const { geometry } = processAttackGeometry(coordinates, options);
|
|
13998
|
+
if (!geometry) return null;
|
|
13999
|
+
const direction = vecNorm(vecSub(geometry.ptTip, geometry.ptBase));
|
|
14000
|
+
const arrowBaseWidth = vecMag(vecSub(geometry.shaftLeft.at(-1), geometry.shaftRight.at(-1)));
|
|
14001
|
+
const boltLengthRatio = Math.max(0, options.boltLengthRatio ?? DEFAULT_MOVEMENT_TO_CONTACT_OPTIONS.boltLengthRatio);
|
|
14002
|
+
const boltAngle = clamp(options.boltAngle ?? DEFAULT_MOVEMENT_TO_CONTACT_OPTIONS.boltAngle, 0, 90);
|
|
14003
|
+
const boltOptions = resolveTacticalArrowOptions(options);
|
|
14004
|
+
const create = (side) => createBolt({
|
|
14005
|
+
tip: geometry.ptTip,
|
|
14006
|
+
arrowDirection: direction,
|
|
14007
|
+
arrowBaseWidth,
|
|
14008
|
+
boltLengthRatio,
|
|
14009
|
+
boltAngle,
|
|
14010
|
+
side,
|
|
14011
|
+
options: boltOptions
|
|
14012
|
+
});
|
|
14013
|
+
return {
|
|
14014
|
+
geometry,
|
|
14015
|
+
direction,
|
|
14016
|
+
arrowBaseWidth,
|
|
14017
|
+
bolts: [create(1), create(-1)]
|
|
14018
|
+
};
|
|
14019
|
+
}
|
|
14020
|
+
/** Generates the Movement to Contact mission task (342900). */
|
|
14021
|
+
function createMovementToContact(coordinates, options = {}, textAmplifiers = {}) {
|
|
14022
|
+
const resolved = resolveMovementToContactGeometry(coordinates, options);
|
|
14023
|
+
if (!resolved) return {
|
|
14024
|
+
type: "FeatureCollection",
|
|
14025
|
+
features: []
|
|
14026
|
+
};
|
|
14027
|
+
const { geometry, bolts } = resolved;
|
|
14028
|
+
return {
|
|
14029
|
+
type: "FeatureCollection",
|
|
14030
|
+
features: [
|
|
14031
|
+
{
|
|
14032
|
+
type: "Feature",
|
|
14033
|
+
properties: { part: "body-and-bolts" },
|
|
14034
|
+
geometry: {
|
|
14035
|
+
type: "MultiLineString",
|
|
14036
|
+
coordinates: [supportingAttackOutline(geometry), ...bolts.map((bolt) => bolt.shaft.map((point) => unproject(point[0], point[1])))]
|
|
14037
|
+
}
|
|
14038
|
+
},
|
|
14039
|
+
{
|
|
14040
|
+
type: "Feature",
|
|
14041
|
+
properties: {
|
|
14042
|
+
part: "bolt-heads",
|
|
14043
|
+
fill: true,
|
|
14044
|
+
style: SOLID_ACCENT_FILL
|
|
14045
|
+
},
|
|
14046
|
+
geometry: {
|
|
14047
|
+
type: "MultiPolygon",
|
|
14048
|
+
coordinates: bolts.map((bolt) => [bolt.head])
|
|
14049
|
+
}
|
|
14050
|
+
},
|
|
14051
|
+
...createManeuverArrowTaskAmplifierLabels(geometry, options, textAmplifiers)
|
|
14052
|
+
]
|
|
14053
|
+
};
|
|
14054
|
+
}
|
|
14055
|
+
const MOVEMENT_TO_CONTACT = defineControlMeasure({
|
|
14056
|
+
metadata: MOVEMENT_TO_CONTACT_METADATA,
|
|
14057
|
+
generator: createMovementToContact,
|
|
14058
|
+
defaultOptions: DEFAULT_MOVEMENT_TO_CONTACT_OPTIONS,
|
|
14059
|
+
rule: axis1DrawRule,
|
|
14060
|
+
optionHandles: {
|
|
14061
|
+
get(controlPoints, options) {
|
|
14062
|
+
const widthHandles = variableWidthAttackOptionHandles.get(controlPoints, options);
|
|
14063
|
+
const resolved = resolveMovementToContactGeometry(controlPoints, options);
|
|
14064
|
+
if (!resolved) return widthHandles;
|
|
14065
|
+
return [...widthHandles, {
|
|
14066
|
+
id: MOVEMENT_TO_CONTACT_BOLT_HANDLE_ID,
|
|
14067
|
+
position: unproject(resolved.bolts[0].tip[0], resolved.bolts[0].tip[1])
|
|
14068
|
+
}];
|
|
14069
|
+
},
|
|
14070
|
+
drag(event) {
|
|
14071
|
+
if (event.handleId !== MOVEMENT_TO_CONTACT_BOLT_HANDLE_ID) return variableWidthAttackOptionHandles.drag(event);
|
|
14072
|
+
const resolved = resolveMovementToContactGeometry(event.controlPoints, event.options);
|
|
14073
|
+
if (!resolved || resolved.arrowBaseWidth < 1e-6) return void 0;
|
|
14074
|
+
const fromOrigin = vecSub(project(event.position[0], event.position[1]), resolved.bolts[0].origin);
|
|
14075
|
+
const length = vecMag(fromOrigin);
|
|
14076
|
+
if (length < 1e-6) return { boltLengthRatio: 0 };
|
|
14077
|
+
const cross = resolved.direction[0] * fromOrigin[1] - resolved.direction[1] * fromOrigin[0];
|
|
14078
|
+
const dot = resolved.direction[0] * fromOrigin[0] + resolved.direction[1] * fromOrigin[1];
|
|
14079
|
+
return {
|
|
14080
|
+
boltLengthRatio: length / resolved.arrowBaseWidth,
|
|
14081
|
+
boltAngle: clamp(Math.abs(Math.atan2(cross, dot) * 180 / Math.PI), 0, 90)
|
|
14082
|
+
};
|
|
14083
|
+
}
|
|
14084
|
+
},
|
|
14085
|
+
previewSample: {
|
|
14086
|
+
controlPoints: [
|
|
14087
|
+
[1, 0],
|
|
14088
|
+
[-.4, -.35],
|
|
14089
|
+
[-1, -.8],
|
|
14090
|
+
[.45, .55]
|
|
14091
|
+
],
|
|
14092
|
+
textAmplifiers: {
|
|
14093
|
+
T: "DAVID",
|
|
14094
|
+
W: "080400Z",
|
|
14095
|
+
W1: "120300Z"
|
|
14096
|
+
},
|
|
14097
|
+
options: { labelSize: 80 }
|
|
14098
|
+
}
|
|
14099
|
+
});
|
|
14100
|
+
//#endregion
|
|
14015
14101
|
//#region src/generators/cm34-mission-tasks/penetrate.ts
|
|
14016
14102
|
const DEFAULT_PENETRATE_OPTIONS = {
|
|
14017
14103
|
arrowheadLengthRatio: .15,
|
|
@@ -14898,7 +14984,8 @@ const DEFAULT_MAIN_ATTACK_OPTIONS = {
|
|
|
14898
14984
|
shaftWidthRatio: DEFAULT_SHAFT_WIDTH_RATIO$1,
|
|
14899
14985
|
rearWidthRatio: DEFAULT_REAR_WIDTH_RATIO,
|
|
14900
14986
|
smooth: false,
|
|
14901
|
-
smoothResolution: 5
|
|
14987
|
+
smoothResolution: 5,
|
|
14988
|
+
...DEFAULT_MANEUVER_ARROW_TASK_AMPLIFIER_OPTIONS
|
|
14902
14989
|
};
|
|
14903
14990
|
const MAIN_ATTACK_METADATA = {
|
|
14904
14991
|
id: "main-attack",
|
|
@@ -14910,14 +14997,16 @@ const MAIN_ATTACK_METADATA = {
|
|
|
14910
14997
|
value: "151403",
|
|
14911
14998
|
minCoordinates: 3,
|
|
14912
14999
|
geometry: "line",
|
|
14913
|
-
geometryTypes: ["MultiLineString"],
|
|
15000
|
+
geometryTypes: ["MultiLineString", "Point"],
|
|
14914
15001
|
paints: {
|
|
14915
15002
|
stroke: true,
|
|
14916
15003
|
fill: "none",
|
|
14917
|
-
text:
|
|
15004
|
+
text: true
|
|
14918
15005
|
},
|
|
14919
15006
|
drawRule: "Axis1",
|
|
14920
|
-
|
|
15007
|
+
capturesLabelSize: true,
|
|
15008
|
+
params: [...ATTACK_SHAFT_PARAMS, ...MANEUVER_ARROW_TASK_AMPLIFIER_PARAMS],
|
|
15009
|
+
textAmplifiers: MANEUVER_ARROW_TASK_TEXT_AMPLIFIERS
|
|
14921
15010
|
};
|
|
14922
15011
|
/**
|
|
14923
15012
|
* Generates a GeoJSON FeatureCollection for a Main Attack tactical symbol.
|
|
@@ -14937,7 +15026,7 @@ const MAIN_ATTACK_METADATA = {
|
|
|
14937
15026
|
* @param options - Configuration for ratios and properties.
|
|
14938
15027
|
* @returns A GeoJSON FeatureCollection containing a single MultiLineString.
|
|
14939
15028
|
*/
|
|
14940
|
-
function createMainAttack(coordinates, options = {}) {
|
|
15029
|
+
function createMainAttack(coordinates, options = {}, textAmplifiers = {}) {
|
|
14941
15030
|
const { geometry } = processAttackGeometry(coordinates, options);
|
|
14942
15031
|
if (!geometry) return {
|
|
14943
15032
|
type: "FeatureCollection",
|
|
@@ -14956,7 +15045,7 @@ function createMainAttack(coordinates, options = {}) {
|
|
|
14956
15045
|
geometry.headRing.map((p) => unproject(p[0], p[1]))
|
|
14957
15046
|
]
|
|
14958
15047
|
}
|
|
14959
|
-
}]
|
|
15048
|
+
}, ...createManeuverArrowTaskAmplifierLabels(geometry, options, textAmplifiers)]
|
|
14960
15049
|
};
|
|
14961
15050
|
}
|
|
14962
15051
|
const MAIN_ATTACK = defineControlMeasure({
|
|
@@ -14964,7 +15053,21 @@ const MAIN_ATTACK = defineControlMeasure({
|
|
|
14964
15053
|
generator: createMainAttack,
|
|
14965
15054
|
defaultOptions: DEFAULT_MAIN_ATTACK_OPTIONS,
|
|
14966
15055
|
rule: axis1DrawRule,
|
|
14967
|
-
optionHandles: variableWidthAttackOptionHandles
|
|
15056
|
+
optionHandles: variableWidthAttackOptionHandles,
|
|
15057
|
+
previewSample: {
|
|
15058
|
+
controlPoints: [
|
|
15059
|
+
[1, 0],
|
|
15060
|
+
[-.4, -.35],
|
|
15061
|
+
[-1, -.8],
|
|
15062
|
+
[.45, .55]
|
|
15063
|
+
],
|
|
15064
|
+
textAmplifiers: {
|
|
15065
|
+
T: "DAVID",
|
|
15066
|
+
W: "080400Z",
|
|
15067
|
+
W1: "120300Z"
|
|
15068
|
+
},
|
|
15069
|
+
options: { labelSize: 80 }
|
|
15070
|
+
}
|
|
14968
15071
|
});
|
|
14969
15072
|
//#endregion
|
|
14970
15073
|
//#region src/generators/cm27-protection-areas/mine-types.ts
|
|
@@ -16303,6 +16406,113 @@ const SECONDARY_DIRECTION_OF_FIRE = defineControlMeasure({
|
|
|
16303
16406
|
] }
|
|
16304
16407
|
});
|
|
16305
16408
|
//#endregion
|
|
16409
|
+
//#region src/generators/cm15-maneuver-areas/searchArea.ts
|
|
16410
|
+
const SEARCH_AREA_METADATA = {
|
|
16411
|
+
id: "search-area",
|
|
16412
|
+
name: "Search Area",
|
|
16413
|
+
description: "Designates an area to examine systematically for specified persons, forces, materiel, or information.",
|
|
16414
|
+
entity: "Maneuver Areas",
|
|
16415
|
+
entityType: "Search Area",
|
|
16416
|
+
value: "152200",
|
|
16417
|
+
minCoordinates: 3,
|
|
16418
|
+
maxCoordinates: 3,
|
|
16419
|
+
geometry: "area",
|
|
16420
|
+
geometryTypes: ["MultiLineString", "MultiPolygon"],
|
|
16421
|
+
paints: {
|
|
16422
|
+
stroke: true,
|
|
16423
|
+
fill: "fixed",
|
|
16424
|
+
text: false
|
|
16425
|
+
},
|
|
16426
|
+
drawRule: "Area21",
|
|
16427
|
+
params: TACTICAL_ARROW_PARAMS
|
|
16428
|
+
};
|
|
16429
|
+
/**
|
|
16430
|
+
* Generates a GeoJSON FeatureCollection of a tactical "lightning" arrow.
|
|
16431
|
+
* Handles internal projection to maintain correct perpendicularity.
|
|
16432
|
+
*
|
|
16433
|
+
* @param coordinates - [Apex Point (PT1), Top Tail (PT2), Bottom Tail (PT3)]
|
|
16434
|
+
* @param options - Geometric configuration parameters
|
|
16435
|
+
* @returns A FeatureCollection containing a MultiLineString for bolts and a MultiPolygon for heads
|
|
16436
|
+
*/
|
|
16437
|
+
function createSearchArea(coordinates, options = {}) {
|
|
16438
|
+
const { arrowheadLengthRatio: safeArrowheadLengthRatio, arrowheadWidthRatio: safeArrowheadWidthRatio, zigzagStart: safeZigzagStart, zigzagEnd: safeZigzagEnd, zigzagAmplitudeRatio: safeZigzagAmplitudeRatio } = resolveTacticalArrowOptions(options);
|
|
16439
|
+
const [c0, c1, c2] = coordinates;
|
|
16440
|
+
const [p1, p2, p3] = [
|
|
16441
|
+
project(c0[0], c0[1]),
|
|
16442
|
+
project(c1[0], c1[1]),
|
|
16443
|
+
project(c2[0], c2[1])
|
|
16444
|
+
];
|
|
16445
|
+
const shaftSegments = [];
|
|
16446
|
+
const headPolygons = [];
|
|
16447
|
+
const addLeg = (origin, tip, opposite) => {
|
|
16448
|
+
const v = vecSub(tip, origin);
|
|
16449
|
+
const totalLen = vecMag(v);
|
|
16450
|
+
if (totalLen < 1e-6) return;
|
|
16451
|
+
const u = vecNorm(v);
|
|
16452
|
+
let n = [-u[1], u[0]];
|
|
16453
|
+
const toOpp = vecSub(opposite, origin);
|
|
16454
|
+
if (vecDot(n, toOpp) > 0) n = vecScale(n, -1);
|
|
16455
|
+
const offset = totalLen * safeZigzagAmplitudeRatio;
|
|
16456
|
+
const ptA = vecAdd(vecAdd(origin, vecScale(v, safeZigzagStart)), vecScale(n, offset));
|
|
16457
|
+
const ptB = vecAdd(vecAdd(origin, vecScale(v, safeZigzagEnd)), vecScale(n, -offset));
|
|
16458
|
+
const fv = vecSub(tip, ptB);
|
|
16459
|
+
const fLen = vecMag(fv);
|
|
16460
|
+
let fu = vecNorm(fv);
|
|
16461
|
+
if (fLen < 1e-6) fu = u;
|
|
16462
|
+
const headLen = totalLen * safeArrowheadLengthRatio;
|
|
16463
|
+
const pBase = vecSub(tip, vecScale(fu, headLen));
|
|
16464
|
+
const wingWidth = headLen * safeArrowheadWidthRatio;
|
|
16465
|
+
const perp = [-fu[1], fu[0]];
|
|
16466
|
+
const pWing1 = vecAdd(pBase, vecScale(perp, wingWidth));
|
|
16467
|
+
const pWing2 = vecSub(pBase, vecScale(perp, wingWidth));
|
|
16468
|
+
shaftSegments.push([
|
|
16469
|
+
origin,
|
|
16470
|
+
ptA,
|
|
16471
|
+
ptB,
|
|
16472
|
+
pBase
|
|
16473
|
+
].map((p) => unproject(p[0], p[1])));
|
|
16474
|
+
headPolygons.push([[
|
|
16475
|
+
tip,
|
|
16476
|
+
pWing1,
|
|
16477
|
+
pWing2,
|
|
16478
|
+
tip
|
|
16479
|
+
].map((p) => unproject(p[0], p[1]))]);
|
|
16480
|
+
};
|
|
16481
|
+
addLeg(p1, p2, p3);
|
|
16482
|
+
addLeg(p1, p3, p2);
|
|
16483
|
+
const features = [];
|
|
16484
|
+
if (shaftSegments.length > 0) features.push({
|
|
16485
|
+
type: "Feature",
|
|
16486
|
+
properties: { part: "shaft" },
|
|
16487
|
+
geometry: {
|
|
16488
|
+
type: "MultiLineString",
|
|
16489
|
+
coordinates: shaftSegments
|
|
16490
|
+
}
|
|
16491
|
+
});
|
|
16492
|
+
if (headPolygons.length > 0) features.push({
|
|
16493
|
+
type: "Feature",
|
|
16494
|
+
properties: {
|
|
16495
|
+
part: "head",
|
|
16496
|
+
fill: true,
|
|
16497
|
+
style: SOLID_ACCENT_FILL
|
|
16498
|
+
},
|
|
16499
|
+
geometry: {
|
|
16500
|
+
type: "MultiPolygon",
|
|
16501
|
+
coordinates: headPolygons
|
|
16502
|
+
}
|
|
16503
|
+
});
|
|
16504
|
+
return {
|
|
16505
|
+
type: "FeatureCollection",
|
|
16506
|
+
features
|
|
16507
|
+
};
|
|
16508
|
+
}
|
|
16509
|
+
const SEARCH_AREA = defineControlMeasure({
|
|
16510
|
+
metadata: SEARCH_AREA_METADATA,
|
|
16511
|
+
generator: (controlPoints, options) => createSearchArea(asThreePoints(controlPoints), options),
|
|
16512
|
+
defaultOptions: DEFAULT_TACTICAL_ARROW_OPTIONS,
|
|
16513
|
+
rule: searchAreaDrawRule
|
|
16514
|
+
});
|
|
16515
|
+
//#endregion
|
|
16306
16516
|
//#region src/generators/cm15-maneuver-areas/strongPoint.ts
|
|
16307
16517
|
const DEFAULT_SMOOTH_RESOLUTION = 16;
|
|
16308
16518
|
/** Default options for the Strong Point control measure. */
|
|
@@ -17166,6 +17376,7 @@ const DEFINITIONS = {
|
|
|
17166
17376
|
infiltration: INFILTRATION,
|
|
17167
17377
|
guard: GUARD,
|
|
17168
17378
|
isolate: ISOLATE,
|
|
17379
|
+
"movement-to-contact": MOVEMENT_TO_CONTACT,
|
|
17169
17380
|
penetrate: PENETRATE,
|
|
17170
17381
|
"rearward-passage-of-lines": REARWARD_PASSAGE_OF_LINES,
|
|
17171
17382
|
retire: RETIRE,
|
|
@@ -17597,4 +17808,4 @@ function assertNever(value) {
|
|
|
17597
17808
|
throw new Error(`Unhandled control measure kind: ${String(value)}`);
|
|
17598
17809
|
}
|
|
17599
17810
|
//#endregion
|
|
17600
|
-
export {
|
|
17811
|
+
export { DEFAULT_FINAL_PROTECTIVE_FIRE_OPTIONS as $, DEFAULT_LINE_JOIN as $t, DEFAULT_RETIRE_OPTIONS as A, getMidpointPerpendicularSignedDistance as An, DEFAULT_BLOCK_ARROW_OPTIONS as At, DEFAULT_LINE_OF_DEPARTURE_OPTIONS as B, EPSILON as Bn, DEFAULT_MOBILE_DEFENSE_OPTIONS as Bt, DEFAULT_MAIN_ATTACK_OPTIONS as C, disruptDrawRule as Cn, DEFAULT_BYPASS_OPTIONS as Ct, DEFAULT_WITHDRAW_OPTIONS as D, area27DrawRule as Dn, DEFAULT_ENGINEER_WORK_LINE_OPTIONS as Dt, DEFAULT_WITHDRAW_UNDER_PRESSURE_OPTIONS as E, containDrawRule as En, DEFAULT_GENERIC_C2_LINE_OPTIONS as Et, DEFAULT_FORTIFIED_AREA_OPTIONS as F, computeInitialWidthPoint as Fn, DEFAULT_ATTACK_BY_FIRE_OPTIONS as Ft, DEFAULT_RELEASE_LINE_OPTIONS as G, DEFAULT_DIRECTION_OF_ATTACK_AVIATION_OPTIONS as Gt, DEFAULT_BATTLE_HANDOVER_LINE_OPTIONS as H, roundToFixed as Hn, DEFAULT_AREA_DEFENSE_OPTIONS as Ht, DEFAULT_FORTIFIED_LINE_OPTIONS as I, haversineDistance as In, DEFAULT_ANTITANK_WALL_OPTIONS as It, DEFAULT_BRIDGEHEAD_LINE_OPTIONS as J, normalizeTextAmplifiers as Jt, DEFAULT_HOLDING_LINE_OPTIONS as K, TEXT_AMPLIFIER_FIELDS as Kt, DEFAULT_PROBABLE_LINE_OF_DEPLOYMENT_OPTIONS as L, project as Ln, DEFAULT_ANTITANK_DITCH_OPTIONS as Lt, DEFAULT_PENETRATE_OPTIONS as M, snapToMidpointPerpendicular as Mn, DEFAULT_CONTAIN_OPTIONS as Mt, DEFAULT_MOVEMENT_TO_CONTACT_OPTIONS as N, createBaselineFrame as Nn, DEFAULT_BATTLE_POSITION_OPTIONS as Nt, DEFAULT_SEIZE_OPTIONS as O, computeDefaultMidpointPerpendicularPoint as On, DEFAULT_LIGHT_LINE_OPTIONS as Ot, DEFAULT_FRONTAL_ATTACK_OPTIONS as P, calculateMetrics as Pn, DEFAULT_ATTACK_HELICOPTER_OPTIONS as Pt, DEFAULT_FIX_OPTIONS as Q, DEFAULT_LINE_CAP as Qt, PROBABLE_LINE_OF_DEPLOYMENT_DASH as R, sphericalBearing as Rn, DEFAULT_ASSEMBLY_AREA_OPTIONS as Rt, DEFAULT_MINEFIELD_OPTIONS as S, penetrateDrawRule as Sn, DEFAULT_CANALIZE_OPTIONS as St, DEFAULT_JOINT_TACTICAL_ACTION_AREA_OPTIONS as T, centerRadiusDrawRule as Tn, DEFAULT_BLOCK_MISSION_TASK_OPTIONS as Tt, DEFAULT_HANDOVER_LINE_OPTIONS as U, DEFAULT_DIRECTION_OF_SUPPORTING_ATTACK_OPTIONS as Ut, DEFAULT_LIMIT_OF_ADVANCE_OPTIONS as V, getMetersPerPixel as Vn, DEFAULT_ENGAGEMENT_AREA_OPTIONS as Vt, DEFAULT_FORWARD_EDGE_OF_BATTLE_AREA_OPTIONS as W, DEFAULT_DIRECTION_OF_MAIN_ATTACK_OPTIONS as Wt, DEFAULT_FLOT_OPTIONS as X, DEFAULT_LABEL_HEIGHT_CSS_PIXELS as Xt, DEFAULT_PHASE_LINE_OPTIONS as Y, resolveAmplifierPlacement as Yt, DEFAULT_FIX_MISSION_TASK_OPTIONS as Z, DEFAULT_LABEL_SIZE_CLAMP_CSS_PIXELS as Zt, DEFAULT_PRINCIPAL_DIRECTION_OF_FIRE_OPTIONS as _, attackByFireDrawRule as _n, DEFAULT_GENERIC_RECTANGLE_OPTIONS as _t, DEFINITIONS as a, DEFAULT_AIRBORNE_ATTACK_OPTIONS as an, DEFAULT_DELAY_OPTIONS as at, DEFAULT_OBSTACLE_BYPASS_EASY_OPTIONS as b, staticPointDrawRule as bn, DEFAULT_GENERIC_CIRCLE_OPTIONS as bt, getDefaultOptions as c, supportByFireDrawRule as cn, DEFAULT_COUNTERATTACK_BY_FIRE_OPTIONS as ct, DEFAULT_TURN_OPTIONS as d, line26DrawRule as dn, DEFAULT_CLEAR_OPTIONS as dt, DEFAULT_PORTRAYAL as en, DEFAULT_ENCIRCLEMENT_OPTIONS as et, DEFAULT_SUPPORT_BY_FIRE_OPTIONS as f, line24DrawRule as fn, DEFAULT_RADAR_SEARCH_DOCTRINE_OPTIONS as ft, SECONDARY_DIRECTION_OF_FIRE_DASH as g, ambushDrawRule as gn, DEFAULT_GENERIC_SECTOR_OPTIONS as gt, DEFAULT_SECONDARY_DIRECTION_OF_FIRE_OPTIONS as h, line1DrawRule as hn, DEFAULT_GENERIC_TEXT_OPTIONS as ht, CONTROL_MEASURE_METADATA as i, DEFAULT_AMBUSH_OPTIONS as in, DEFAULT_DISENGAGE_OPTIONS as it, DEFAULT_REARWARD_PASSAGE_OF_LINES_OPTIONS as j, pointOnMidpointPerpendicularAxis as jn, DEFAULT_BLOCK_OPTIONS as jt, DEFAULT_SCREEN_OPTIONS as k, createMidpointPerpendicularDrawRule as kn, DEFAULT_BOUNDARY_OPTIONS as kt, listControlMeasureMetadata as l, polyline1DrawRule as ln, DEFAULT_COUNTERATTACK_OPTIONS as lt, DEFAULT_STRONG_POINT_OPTIONS as m, turnDrawRule as mn, RADAR_SEARCH_DOCTRINE_STROKE_COLOR as mt, resolveStyleHints as n, DEFAULT_STROKE_WIDTH_CSS_PIXELS as nn, DEFAULT_GUARD_OPTIONS as nt, getControlMeasureMetadata as o, rectangleDrawRule as on, DEFAULT_COVER_OPTIONS as ot, DEFAULT_RETAIN_OPTIONS as p, line23DrawRule as pn, RADAR_SEARCH_DOCTRINE_FILL_COLOR as pt, DEFAULT_INFILTRATION_LANE_OPTIONS as q, canonicalTextAmplifierKey as qt, CONTROL_MEASURE_IDS as r, DEFAULT_SYMBOL_COLOR as rn, DEFAULT_DISRUPT_MISSION_TASK_OPTIONS as rt, getControlMeasureMetadataByValue as s, axis1DrawRule as sn, DEFAULT_TACTICAL_ARROW_OPTIONS as st, renderControlMeasure as t, DEFAULT_STROKE_DASH_CSS_PIXELS as tn, DEFAULT_DISRUPT_OPTIONS as tt, DEFAULT_TURNING_MOVEMENT_OPTIONS as u, line27DrawRule as un, DEFAULT_SUPPORTING_ATTACK_OPTIONS as ut, DEFAULT_PICKUP_ZONE_OPTIONS as v, dynamicPointDrawRule as vn, DEFAULT_GENERIC_POLYGON_OPTIONS as vt, DEFAULT_LANDING_ZONE_OPTIONS as w, blockDrawRule as wn, DEFAULT_BREACH_OPTIONS as wt, DEFAULT_OBSTACLE_BYPASS_DIFFICULT_OPTIONS as x, point12DrawRule as xn, DEFAULT_CLASSIC_ARROW_OPTIONS as xt, DEFAULT_OBSTACLE_BYPASS_IMPOSSIBLE_OPTIONS as y, sectorDrawRule as yn, DEFAULT_GENERIC_LINE_OPTIONS as yt, DEFAULT_LINE_OF_DEPARTURE_CONTACT_OPTIONS as z, unproject as zn, DEFAULT_AREA_OF_OPERATIONS_OPTIONS as zt };
|