@orbat-mapper/control-measures 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{index-DCgWC2Q8.d.mts → index-B3LgMKpn.d.mts} +109 -86
- package/dist/index.d.mts +3 -2
- package/dist/index.mjs +3 -2
- package/dist/patterns-CcQmmOuJ.d.mts +116 -0
- package/dist/patterns.d.mts +2 -0
- package/dist/patterns.mjs +205 -0
- package/dist/preview/index.d.mts +15 -11
- package/dist/preview/index.mjs +67 -84
- package/dist/{renderControlMeasure-D6VScGE0.mjs → renderControlMeasure-GsV4DV3r.mjs} +437 -199
- package/media/limited-access-area.svg +1 -1
- package/media/mined-area.svg +1 -0
- package/media/minefield-dynamic.svg +1 -0
- package/media/minefield.svg +1 -0
- package/media/no-fire-area-irregular.svg +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
//#region src/style.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Framework-agnostic style hints attached to a `ControlMeasure`.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors the per-feature subset of what map adapters render. The renderer
|
|
6
|
+
* fans these into each emitted feature's `properties.style` ([[StyleHints]]).
|
|
7
|
+
* Adapters merge them over their layer-level style; generator-emitted hints
|
|
8
|
+
* on individual features win over both.
|
|
9
|
+
*/
|
|
10
|
+
interface ControlMeasureStyle {
|
|
11
|
+
/**
|
|
12
|
+
* The single symbol color. Control measures are monocolor: one color paints
|
|
13
|
+
* every part, stroked or filled. Input-only — resolved into concrete
|
|
14
|
+
* `strokeColor` / `fillColor` per feature by the renderer and never emitted
|
|
15
|
+
* on output. `strokeColor` / `fillColor` act as optional per-channel
|
|
16
|
+
* overrides. See ADR-0011.
|
|
17
|
+
*/
|
|
18
|
+
color?: string;
|
|
19
|
+
strokeColor?: string;
|
|
20
|
+
strokeWidth?: number;
|
|
21
|
+
strokeDash?: number[];
|
|
22
|
+
fillColor?: string;
|
|
23
|
+
/**
|
|
24
|
+
* How a filled part's interior is painted. `"solid"` is explicit so a
|
|
25
|
+
* measure can override a patterned layer default (e.g. a hatch fill set
|
|
26
|
+
* at the façade). Pattern lines are drawn in the resolved fill color —
|
|
27
|
+
* alpha included, there is no separate opacity field. Renderers that
|
|
28
|
+
* cannot draw a pattern degrade to solid.
|
|
29
|
+
*/
|
|
30
|
+
fillPattern?: FillPattern;
|
|
31
|
+
/**
|
|
32
|
+
* Display-time graphic opacity multiplier in `[0, 1]`; absent = fully
|
|
33
|
+
* opaque. Unlike `color`'s alpha, this fades the whole graphic — stroke,
|
|
34
|
+
* fill, patterns, and baked labels — as one knob, independent of the
|
|
35
|
+
* authored color. The resolver folds it into each resolved part's color
|
|
36
|
+
* alpha (`final alpha = opacity × color alpha`) rather than adding a new
|
|
37
|
+
* paint channel, so adapters and `toSimpleStyle` need no changes.
|
|
38
|
+
* Display-only: hit-testing and selection reason about color alpha, not
|
|
39
|
+
* this multiplier, so a graphic at `opacity: 0` stays fully selectable.
|
|
40
|
+
* Out-of-range values clamp to `[0, 1]`. See ADR-0040.
|
|
41
|
+
*/
|
|
42
|
+
opacity?: number;
|
|
43
|
+
/**
|
|
44
|
+
* Render a contrast-aware halo behind label glyphs so text stays legible on
|
|
45
|
+
* busy backgrounds. Cascades like any other hint (graphicsStyle → measure →
|
|
46
|
+
* generator), letting a host switch halos on for every labeled measure
|
|
47
|
+
* rather than only the generic Text graphic.
|
|
48
|
+
*
|
|
49
|
+
* Text-only: the renderer applies it to features that carry `text` and drops
|
|
50
|
+
* it everywhere else, so a layer-level `true` never leaks onto plain stroked
|
|
51
|
+
* or filled parts. The halo *color* is not authored here — it is derived per
|
|
52
|
+
* feature from the resolved text color, so each label gets a halo that
|
|
53
|
+
* actually contrasts with it. Unlike the other hints this one is consumed by
|
|
54
|
+
* the renderer and lifted to a top-level `textHalo` feature property; it is
|
|
55
|
+
* never emitted on `properties.style`. See ADR-0042.
|
|
56
|
+
*/
|
|
57
|
+
textHalo?: boolean;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* How a filled part's interior is painted. See `ControlMeasureStyle.fillPattern`.
|
|
61
|
+
*/
|
|
62
|
+
type FillPattern = "solid" | "hatch" | "reverse-hatch" | "cross-hatch" | "horizontal" | "vertical" | "dots" | "mine-unspecified" | "mine-antipersonnel" | "mine-antipersonnel-directional" | "mine-antitank" | "mine-antitank-antihandling" | "mine-wide-area-antitank" | "mine-cluster";
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/patterns.d.ts
|
|
65
|
+
type PatternId = Exclude<FillPattern, "solid">;
|
|
66
|
+
type PatternPoint = readonly [x: number, y: number];
|
|
67
|
+
interface PatternStroke {
|
|
68
|
+
readonly width: number;
|
|
69
|
+
readonly lineCap: "butt" | "round";
|
|
70
|
+
readonly dash?: readonly number[];
|
|
71
|
+
}
|
|
72
|
+
interface PatternPaint {
|
|
73
|
+
readonly fill?: "color";
|
|
74
|
+
readonly stroke?: PatternStroke;
|
|
75
|
+
}
|
|
76
|
+
type PatternPathOperation = {
|
|
77
|
+
readonly type: "move";
|
|
78
|
+
readonly to: PatternPoint;
|
|
79
|
+
} | {
|
|
80
|
+
readonly type: "line";
|
|
81
|
+
readonly to: PatternPoint;
|
|
82
|
+
} | {
|
|
83
|
+
readonly type: "cubic";
|
|
84
|
+
readonly control1: PatternPoint;
|
|
85
|
+
readonly control2: PatternPoint;
|
|
86
|
+
readonly to: PatternPoint;
|
|
87
|
+
} | {
|
|
88
|
+
readonly type: "close";
|
|
89
|
+
};
|
|
90
|
+
type FillPatternCommand = {
|
|
91
|
+
readonly type: "path";
|
|
92
|
+
readonly operations: readonly PatternPathOperation[];
|
|
93
|
+
readonly paint: PatternPaint;
|
|
94
|
+
} | {
|
|
95
|
+
readonly type: "circle";
|
|
96
|
+
readonly center: PatternPoint;
|
|
97
|
+
readonly radius: number;
|
|
98
|
+
readonly paint: PatternPaint;
|
|
99
|
+
};
|
|
100
|
+
interface FillPatternDefinition {
|
|
101
|
+
/** Stable identity matching the public FillPattern vocabulary. */
|
|
102
|
+
readonly id: PatternId;
|
|
103
|
+
/** Coordinates and paint widths are expressed in CSS pixels at 1x. */
|
|
104
|
+
readonly units: "css-pixel";
|
|
105
|
+
readonly tile: {
|
|
106
|
+
readonly width: number;
|
|
107
|
+
readonly height: number;
|
|
108
|
+
};
|
|
109
|
+
readonly commands: readonly FillPatternCommand[];
|
|
110
|
+
}
|
|
111
|
+
declare const FILL_PATTERN_DEFINITIONS: Readonly<Record<PatternId, FillPatternDefinition>>;
|
|
112
|
+
/** Runtime vocabulary derived from the exhaustive definition record. */
|
|
113
|
+
declare const NON_SOLID_FILL_PATTERNS: PatternId[];
|
|
114
|
+
declare function getFillPatternDefinition(pattern: PatternId): FillPatternDefinition;
|
|
115
|
+
//#endregion
|
|
116
|
+
export { PatternId as a, PatternPoint as c, ControlMeasureStyle as d, FillPattern as f, NON_SOLID_FILL_PATTERNS as i, PatternStroke as l, FillPatternCommand as n, PatternPaint as o, FillPatternDefinition as r, PatternPathOperation as s, FILL_PATTERN_DEFINITIONS as t, getFillPatternDefinition as u };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as PatternId, c as PatternPoint, i as NON_SOLID_FILL_PATTERNS, l as PatternStroke, n as FillPatternCommand, o as PatternPaint, r as FillPatternDefinition, s as PatternPathOperation, t as FILL_PATTERN_DEFINITIONS, u as getFillPatternDefinition } from "./patterns-CcQmmOuJ.mjs";
|
|
2
|
+
export { FILL_PATTERN_DEFINITIONS, FillPatternCommand, FillPatternDefinition, NON_SOLID_FILL_PATTERNS, PatternId, PatternPaint, PatternPathOperation, PatternPoint, PatternStroke, getFillPatternDefinition };
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
//#region src/patterns.ts
|
|
2
|
+
const stroke = (width = 1, dash) => ({
|
|
3
|
+
width,
|
|
4
|
+
lineCap: "butt",
|
|
5
|
+
...dash ? { dash } : {}
|
|
6
|
+
});
|
|
7
|
+
const move = (x, y) => ({
|
|
8
|
+
type: "move",
|
|
9
|
+
to: [x, y]
|
|
10
|
+
});
|
|
11
|
+
const line = (x, y) => ({
|
|
12
|
+
type: "line",
|
|
13
|
+
to: [x, y]
|
|
14
|
+
});
|
|
15
|
+
const cubic = (x1, y1, x2, y2, x, y) => ({
|
|
16
|
+
type: "cubic",
|
|
17
|
+
control1: [x1, y1],
|
|
18
|
+
control2: [x2, y2],
|
|
19
|
+
to: [x, y]
|
|
20
|
+
});
|
|
21
|
+
const path = (operations, paint = { stroke: stroke() }) => ({
|
|
22
|
+
type: "path",
|
|
23
|
+
operations,
|
|
24
|
+
paint
|
|
25
|
+
});
|
|
26
|
+
const circle = (x, y, radius, paint) => ({
|
|
27
|
+
type: "circle",
|
|
28
|
+
center: [x, y],
|
|
29
|
+
radius,
|
|
30
|
+
paint
|
|
31
|
+
});
|
|
32
|
+
const HATCH = path([
|
|
33
|
+
move(-1, -1),
|
|
34
|
+
line(9, 9),
|
|
35
|
+
move(7, -1),
|
|
36
|
+
line(9, 1),
|
|
37
|
+
move(-1, 7),
|
|
38
|
+
line(1, 9)
|
|
39
|
+
]);
|
|
40
|
+
const REVERSE_HATCH = path([
|
|
41
|
+
move(9, -1),
|
|
42
|
+
line(-1, 9),
|
|
43
|
+
move(1, -1),
|
|
44
|
+
line(-1, 1),
|
|
45
|
+
move(9, 7),
|
|
46
|
+
line(7, 9)
|
|
47
|
+
]);
|
|
48
|
+
const MINE_DISC = circle(16, 16, 5, { fill: "color" });
|
|
49
|
+
const AP_RAYS = [
|
|
50
|
+
move(12.867, 12.1),
|
|
51
|
+
line(9.333, 7.667),
|
|
52
|
+
move(19.133, 12.1),
|
|
53
|
+
line(22.667, 7.667)
|
|
54
|
+
];
|
|
55
|
+
const FILL_PATTERN_DEFINITIONS = {
|
|
56
|
+
hatch: {
|
|
57
|
+
id: "hatch",
|
|
58
|
+
units: "css-pixel",
|
|
59
|
+
tile: {
|
|
60
|
+
width: 8,
|
|
61
|
+
height: 8
|
|
62
|
+
},
|
|
63
|
+
commands: [HATCH]
|
|
64
|
+
},
|
|
65
|
+
"reverse-hatch": {
|
|
66
|
+
id: "reverse-hatch",
|
|
67
|
+
units: "css-pixel",
|
|
68
|
+
tile: {
|
|
69
|
+
width: 8,
|
|
70
|
+
height: 8
|
|
71
|
+
},
|
|
72
|
+
commands: [REVERSE_HATCH]
|
|
73
|
+
},
|
|
74
|
+
"cross-hatch": {
|
|
75
|
+
id: "cross-hatch",
|
|
76
|
+
units: "css-pixel",
|
|
77
|
+
tile: {
|
|
78
|
+
width: 8,
|
|
79
|
+
height: 8
|
|
80
|
+
},
|
|
81
|
+
commands: [HATCH, REVERSE_HATCH]
|
|
82
|
+
},
|
|
83
|
+
horizontal: {
|
|
84
|
+
id: "horizontal",
|
|
85
|
+
units: "css-pixel",
|
|
86
|
+
tile: {
|
|
87
|
+
width: 8,
|
|
88
|
+
height: 8
|
|
89
|
+
},
|
|
90
|
+
commands: [path([move(-1, 4), line(9, 4)])]
|
|
91
|
+
},
|
|
92
|
+
vertical: {
|
|
93
|
+
id: "vertical",
|
|
94
|
+
units: "css-pixel",
|
|
95
|
+
tile: {
|
|
96
|
+
width: 8,
|
|
97
|
+
height: 8
|
|
98
|
+
},
|
|
99
|
+
commands: [path([move(4, -1), line(4, 9)])]
|
|
100
|
+
},
|
|
101
|
+
dots: {
|
|
102
|
+
id: "dots",
|
|
103
|
+
units: "css-pixel",
|
|
104
|
+
tile: {
|
|
105
|
+
width: 8,
|
|
106
|
+
height: 8
|
|
107
|
+
},
|
|
108
|
+
commands: [circle(4, 4, 1.25, { fill: "color" })]
|
|
109
|
+
},
|
|
110
|
+
"mine-unspecified": {
|
|
111
|
+
id: "mine-unspecified",
|
|
112
|
+
units: "css-pixel",
|
|
113
|
+
tile: {
|
|
114
|
+
width: 32,
|
|
115
|
+
height: 32
|
|
116
|
+
},
|
|
117
|
+
commands: [circle(16, 16, 5, { stroke: stroke() })]
|
|
118
|
+
},
|
|
119
|
+
"mine-antipersonnel": {
|
|
120
|
+
id: "mine-antipersonnel",
|
|
121
|
+
units: "css-pixel",
|
|
122
|
+
tile: {
|
|
123
|
+
width: 32,
|
|
124
|
+
height: 32
|
|
125
|
+
},
|
|
126
|
+
commands: [MINE_DISC, path(AP_RAYS)]
|
|
127
|
+
},
|
|
128
|
+
"mine-antipersonnel-directional": {
|
|
129
|
+
id: "mine-antipersonnel-directional",
|
|
130
|
+
units: "css-pixel",
|
|
131
|
+
tile: {
|
|
132
|
+
width: 32,
|
|
133
|
+
height: 32
|
|
134
|
+
},
|
|
135
|
+
commands: [MINE_DISC, path([
|
|
136
|
+
...AP_RAYS,
|
|
137
|
+
move(21, 16),
|
|
138
|
+
line(22.1, 16),
|
|
139
|
+
move(23.667, 15.333),
|
|
140
|
+
line(24.333, 15.9),
|
|
141
|
+
line(23.667, 16.667),
|
|
142
|
+
{ type: "close" }
|
|
143
|
+
])]
|
|
144
|
+
},
|
|
145
|
+
"mine-antitank": {
|
|
146
|
+
id: "mine-antitank",
|
|
147
|
+
units: "css-pixel",
|
|
148
|
+
tile: {
|
|
149
|
+
width: 32,
|
|
150
|
+
height: 32
|
|
151
|
+
},
|
|
152
|
+
commands: [MINE_DISC]
|
|
153
|
+
},
|
|
154
|
+
"mine-antitank-antihandling": {
|
|
155
|
+
id: "mine-antitank-antihandling",
|
|
156
|
+
units: "css-pixel",
|
|
157
|
+
tile: {
|
|
158
|
+
width: 32,
|
|
159
|
+
height: 32
|
|
160
|
+
},
|
|
161
|
+
commands: [MINE_DISC, path([
|
|
162
|
+
move(16, 21),
|
|
163
|
+
line(16, 25.333),
|
|
164
|
+
line(18, 23.333)
|
|
165
|
+
])]
|
|
166
|
+
},
|
|
167
|
+
"mine-wide-area-antitank": {
|
|
168
|
+
id: "mine-wide-area-antitank",
|
|
169
|
+
units: "css-pixel",
|
|
170
|
+
tile: {
|
|
171
|
+
width: 32,
|
|
172
|
+
height: 32
|
|
173
|
+
},
|
|
174
|
+
commands: [MINE_DISC, path([
|
|
175
|
+
move(9.333, 21),
|
|
176
|
+
line(12, 24.333),
|
|
177
|
+
line(13.833, 20.667),
|
|
178
|
+
move(18.167, 20.667),
|
|
179
|
+
line(20, 24.333),
|
|
180
|
+
line(22.667, 21)
|
|
181
|
+
])]
|
|
182
|
+
},
|
|
183
|
+
"mine-cluster": {
|
|
184
|
+
id: "mine-cluster",
|
|
185
|
+
units: "css-pixel",
|
|
186
|
+
tile: {
|
|
187
|
+
width: 32,
|
|
188
|
+
height: 32
|
|
189
|
+
},
|
|
190
|
+
commands: [path([
|
|
191
|
+
move(22, 19.333),
|
|
192
|
+
line(10, 19.333),
|
|
193
|
+
cubic(10, 16, 11, 12.667, 16, 12.667),
|
|
194
|
+
cubic(21, 12.667, 22, 16, 22, 19.333),
|
|
195
|
+
{ type: "close" }
|
|
196
|
+
], { stroke: stroke(1, [2.5, 1.667]) })]
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
/** Runtime vocabulary derived from the exhaustive definition record. */
|
|
200
|
+
const NON_SOLID_FILL_PATTERNS = Object.keys(FILL_PATTERN_DEFINITIONS);
|
|
201
|
+
function getFillPatternDefinition(pattern) {
|
|
202
|
+
return FILL_PATTERN_DEFINITIONS[pattern];
|
|
203
|
+
}
|
|
204
|
+
//#endregion
|
|
205
|
+
export { FILL_PATTERN_DEFINITIONS, NON_SOLID_FILL_PATTERNS, getFillPatternDefinition };
|
package/dist/preview/index.d.mts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Ki as TextAmplifiers, X as ControlMeasureId } from "../index-B3LgMKpn.mjs";
|
|
2
|
+
import { f as FillPattern } from "../patterns-CcQmmOuJ.mjs";
|
|
2
3
|
import { FeatureCollection, Geometry } from "geojson";
|
|
3
4
|
|
|
4
5
|
//#region src/preview/index.d.ts
|
|
5
6
|
/** The hatch fill patterns a preview can render — the `FillPattern` vocabulary
|
|
6
7
|
* minus `"solid"` (a plain fill, not a pattern). */
|
|
7
8
|
type PreviewFillPattern = Exclude<FillPattern, "solid">;
|
|
8
|
-
/** Side length (SVG units) of the repeating
|
|
9
|
+
/** Side length (SVG units) of the repeating non-mine tile in {@link PREVIEW_FILL_PATTERNS}. */
|
|
9
10
|
declare const PREVIEW_PATTERN_TILE = 8;
|
|
10
11
|
/**
|
|
11
|
-
* SVG
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* source so a new pattern is defined once, not copied per renderer.
|
|
12
|
+
* SVG-oriented projection of the public renderer-neutral pattern definitions.
|
|
13
|
+
* Mine tiles are shown at half scale so compact previews retain their historic
|
|
14
|
+
* 16-unit cadence; all geometry and paint metadata still come from the same
|
|
15
|
+
* canonical 32-unit definition used by live and raster renderers.
|
|
16
16
|
*/
|
|
17
17
|
declare const PREVIEW_FILL_PATTERNS: readonly {
|
|
18
18
|
readonly id: PreviewFillPattern;
|
|
@@ -23,7 +23,7 @@ declare const PREVIEW_FILL_PATTERNS: readonly {
|
|
|
23
23
|
readonly dots?: readonly (readonly [number, number])[];
|
|
24
24
|
}[];
|
|
25
25
|
/** Radius (SVG units) of a {@link PREVIEW_FILL_PATTERNS} dot. */
|
|
26
|
-
declare const PREVIEW_PATTERN_DOT_RADIUS
|
|
26
|
+
declare const PREVIEW_PATTERN_DOT_RADIUS: number;
|
|
27
27
|
/**
|
|
28
28
|
* Renders a measure's representative preview to GeoJSON. Pure — no map engine,
|
|
29
29
|
* so it runs identically at build time (docs catalog, README generator) and in
|
|
@@ -39,14 +39,18 @@ declare function renderRepresentative(id: ControlMeasureId, overrides?: {
|
|
|
39
39
|
textAmplifiers?: TextAmplifiers;
|
|
40
40
|
options?: Record<string, unknown>;
|
|
41
41
|
}): FeatureCollection<Geometry, unknown>;
|
|
42
|
-
/** A
|
|
43
|
-
*
|
|
42
|
+
/** A preview primitive. Consumers supply stroke width, radius, font, and
|
|
43
|
+
* fallback colors when they draw it. */
|
|
44
44
|
interface PreviewShape {
|
|
45
45
|
type: "polyline" | "polygon" | "circle" | "text";
|
|
46
46
|
/** SVG path data for `polyline`/`polygon`. */
|
|
47
47
|
d?: string;
|
|
48
48
|
/** Intrinsic dash pattern carried by a stroked feature. Empty means solid. */
|
|
49
49
|
strokeDash?: number[];
|
|
50
|
+
/** Non-default resolved stroke/text color; absent uses the consumer default. */
|
|
51
|
+
strokeColor?: string;
|
|
52
|
+
/** Non-default resolved fill color; absent uses the consumer default. */
|
|
53
|
+
fillColor?: string;
|
|
50
54
|
/** Center for `circle`/`text`. */
|
|
51
55
|
cx?: number;
|
|
52
56
|
cy?: number;
|
|
@@ -92,7 +96,7 @@ interface ProjectDimensions {
|
|
|
92
96
|
}
|
|
93
97
|
/**
|
|
94
98
|
* Fits a rendered `FeatureCollection` into a `width`×`height` viewBox (inset by
|
|
95
|
-
* `pad`) and emits
|
|
99
|
+
* `pad`) and emits preview-ready {@link PreviewShape}s. North is up (y is
|
|
96
100
|
* flipped). Returns `ok: false` for an empty/degenerate render so consumers can
|
|
97
101
|
* show a geometry-typed fallback glyph.
|
|
98
102
|
*/
|
package/dist/preview/index.mjs
CHANGED
|
@@ -1,84 +1,56 @@
|
|
|
1
|
-
import { a as DEFINITIONS, t as renderControlMeasure } from "../renderControlMeasure-
|
|
1
|
+
import { a as DEFINITIONS, t as renderControlMeasure } from "../renderControlMeasure-GsV4DV3r.mjs";
|
|
2
|
+
import { FILL_PATTERN_DEFINITIONS, NON_SOLID_FILL_PATTERNS } from "../patterns.mjs";
|
|
2
3
|
//#region src/preview/index.ts
|
|
3
|
-
/** Side length (SVG units) of the repeating
|
|
4
|
+
/** Side length (SVG units) of the repeating non-mine tile in {@link PREVIEW_FILL_PATTERNS}. */
|
|
4
5
|
const PREVIEW_PATTERN_TILE = 8;
|
|
6
|
+
function svgNumber(value) {
|
|
7
|
+
return String(Number(value.toFixed(3)));
|
|
8
|
+
}
|
|
9
|
+
function previewPathData(command, scale) {
|
|
10
|
+
if (command.type === "circle") {
|
|
11
|
+
const [cx, cy] = command.center;
|
|
12
|
+
const x0 = (cx + command.radius) * scale;
|
|
13
|
+
const x1 = (cx - command.radius) * scale;
|
|
14
|
+
const y = cy * scale;
|
|
15
|
+
const radius = command.radius * scale;
|
|
16
|
+
return `M${svgNumber(x0)},${svgNumber(y)} A${svgNumber(radius)},${svgNumber(radius)} 0 1,1 ${svgNumber(x1)},${svgNumber(y)} A${svgNumber(radius)},${svgNumber(radius)} 0 1,1 ${svgNumber(x0)},${svgNumber(y)}${command.paint.fill ? " Z" : ""}`;
|
|
17
|
+
}
|
|
18
|
+
const point = ([x, y]) => `${svgNumber(x * scale)},${svgNumber(y * scale)}`;
|
|
19
|
+
const operation = (op) => {
|
|
20
|
+
switch (op.type) {
|
|
21
|
+
case "move": return `M${point(op.to)}`;
|
|
22
|
+
case "line": return `L${point(op.to)}`;
|
|
23
|
+
case "cubic": return `C${point(op.control1)} ${point(op.control2)} ${point(op.to)}`;
|
|
24
|
+
case "close": return "Z";
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
return command.operations.map(operation).join(" ");
|
|
28
|
+
}
|
|
5
29
|
/**
|
|
6
|
-
* SVG
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* source so a new pattern is defined once, not copied per renderer.
|
|
30
|
+
* SVG-oriented projection of the public renderer-neutral pattern definitions.
|
|
31
|
+
* Mine tiles are shown at half scale so compact previews retain their historic
|
|
32
|
+
* 16-unit cadence; all geometry and paint metadata still come from the same
|
|
33
|
+
* canonical 32-unit definition used by live and raster renderers.
|
|
11
34
|
*/
|
|
12
|
-
const PREVIEW_FILL_PATTERNS =
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
{
|
|
18
|
-
id
|
|
19
|
-
paths:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
id: "horizontal",
|
|
27
|
-
paths: ["M-1,4 L9,4"]
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
id: "vertical",
|
|
31
|
-
paths: ["M4,-1 L4,9"]
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
id: "dots",
|
|
35
|
-
paths: [],
|
|
36
|
-
dots: [[4, 4]]
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
id: "mine-unspecified",
|
|
40
|
-
tileSize: 16,
|
|
41
|
-
paths: ["M10.5,8 A2.5,2.5 0 1,1 5.5,8 A2.5,2.5 0 1,1 10.5,8"]
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
id: "mine-antipersonnel",
|
|
45
|
-
tileSize: 16,
|
|
46
|
-
paths: ["M6.433,6.05 L4.667,3.833 M9.567,6.05 L11.333,3.833"],
|
|
47
|
-
filledPaths: ["M10.5,8 A2.5,2.5 0 1,1 5.5,8 A2.5,2.5 0 1,1 10.5,8 Z"]
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
id: "mine-antipersonnel-directional",
|
|
51
|
-
tileSize: 16,
|
|
52
|
-
paths: ["M6.433,6.05 L4.667,3.833 M9.567,6.05 L11.333,3.833 M10.5,8 L11.05,8 M11.833,7.667 L12.167,7.95 L11.833,8.333 Z"],
|
|
53
|
-
filledPaths: ["M10.5,8 A2.5,2.5 0 1,1 5.5,8 A2.5,2.5 0 1,1 10.5,8 Z"]
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
id: "mine-antitank",
|
|
57
|
-
tileSize: 16,
|
|
58
|
-
paths: [],
|
|
59
|
-
filledPaths: ["M10.5,8 A2.5,2.5 0 1,1 5.5,8 A2.5,2.5 0 1,1 10.5,8 Z"]
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
id: "mine-antitank-antihandling",
|
|
63
|
-
tileSize: 16,
|
|
64
|
-
paths: ["M8,10.5 L8,12.667 L9,11.667"],
|
|
65
|
-
filledPaths: ["M10.5,8 A2.5,2.5 0 1,1 5.5,8 A2.5,2.5 0 1,1 10.5,8 Z"]
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
id: "mine-wide-area-antitank",
|
|
69
|
-
tileSize: 16,
|
|
70
|
-
paths: ["M4.667,10.5 L6,12.167 L6.917,10.333 M9.083,10.333 L10,12.167 L11.333,10.5"],
|
|
71
|
-
filledPaths: ["M10.5,8 A2.5,2.5 0 1,1 5.5,8 A2.5,2.5 0 1,1 10.5,8 Z"]
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
id: "mine-cluster",
|
|
75
|
-
tileSize: 16,
|
|
76
|
-
paths: ["M11,9.667 L5,9.667 C5,8 5.5,6.333 8,6.333 C10.5,6.333 11,8 11,9.667 Z"],
|
|
77
|
-
strokeDash: [1.25, .833]
|
|
78
|
-
}
|
|
79
|
-
];
|
|
35
|
+
const PREVIEW_FILL_PATTERNS = NON_SOLID_FILL_PATTERNS.map((id) => {
|
|
36
|
+
const definition = FILL_PATTERN_DEFINITIONS[id];
|
|
37
|
+
const scale = id.startsWith("mine-") ? .5 : 1;
|
|
38
|
+
const dotCommand = id === "dots" ? definition.commands[0] : void 0;
|
|
39
|
+
const strokeDash = definition.commands.map((command) => command.paint.stroke?.dash).find((dash) => dash !== void 0);
|
|
40
|
+
return {
|
|
41
|
+
id,
|
|
42
|
+
paths: definition.commands.filter((command) => command.paint.stroke).map((command) => previewPathData(command, scale)),
|
|
43
|
+
...definition.commands.some((command) => command.paint.fill && id !== "dots") ? { filledPaths: definition.commands.filter((command) => command.paint.fill).map((command) => previewPathData(command, scale)) } : {},
|
|
44
|
+
...strokeDash ? { strokeDash: strokeDash.map((value) => value * scale) } : {},
|
|
45
|
+
...dotCommand?.type === "circle" ? { dots: [[dotCommand.center[0] * scale, dotCommand.center[1] * scale]] } : {},
|
|
46
|
+
...scale !== 1 ? { tileSize: definition.tile.width * scale } : {}
|
|
47
|
+
};
|
|
48
|
+
});
|
|
80
49
|
/** Radius (SVG units) of a {@link PREVIEW_FILL_PATTERNS} dot. */
|
|
81
|
-
const PREVIEW_PATTERN_DOT_RADIUS =
|
|
50
|
+
const PREVIEW_PATTERN_DOT_RADIUS = (() => {
|
|
51
|
+
const command = FILL_PATTERN_DEFINITIONS.dots.commands[0];
|
|
52
|
+
return command?.type === "circle" ? command.radius : 0;
|
|
53
|
+
})();
|
|
82
54
|
/**
|
|
83
55
|
* Unitless `previewSample` points (~`[-1, 1]`) are scaled by this degree offset
|
|
84
56
|
* before reaching a generator. Most generators operate in projected meters, so
|
|
@@ -169,7 +141,7 @@ function renderRepresentative(id, overrides) {
|
|
|
169
141
|
}
|
|
170
142
|
/**
|
|
171
143
|
* Fits a rendered `FeatureCollection` into a `width`×`height` viewBox (inset by
|
|
172
|
-
* `pad`) and emits
|
|
144
|
+
* `pad`) and emits preview-ready {@link PreviewShape}s. North is up (y is
|
|
173
145
|
* flipped). Returns `ok: false` for an empty/degenerate render so consumers can
|
|
174
146
|
* show a geometry-typed fallback glyph.
|
|
175
147
|
*/
|
|
@@ -395,6 +367,8 @@ function collectShapes(g, tx, out, bboxArea, textLimits, props) {
|
|
|
395
367
|
const styleProps = labelProps.style && typeof labelProps.style === "object" ? labelProps.style : {};
|
|
396
368
|
const fillPattern = PREVIEW_FILL_PATTERNS.some((p) => p.id === styleProps.fillPattern) ? styleProps.fillPattern : void 0;
|
|
397
369
|
const strokeDash = Array.isArray(styleProps.strokeDash) ? styleProps.strokeDash.filter((value) => typeof value === "number") : void 0;
|
|
370
|
+
const strokeColor = typeof styleProps.strokeColor === "string" && styleProps.strokeColor !== "#000000" ? styleProps.strokeColor : void 0;
|
|
371
|
+
const fillColor = typeof styleProps.fillColor === "string" && styleProps.fillColor !== "#000000" ? styleProps.fillColor : void 0;
|
|
398
372
|
const explicitlyFilled = typeof styleProps.fillColor === "string";
|
|
399
373
|
const polygonFilled = (ring) => bboxArea > 0 && ringArea(ring) / bboxArea < .25;
|
|
400
374
|
switch (g.type) {
|
|
@@ -413,12 +387,14 @@ function collectShapes(g, tx, out, bboxArea, textLimits, props) {
|
|
|
413
387
|
textBackground,
|
|
414
388
|
textHalo,
|
|
415
389
|
textJustify,
|
|
416
|
-
textStyle
|
|
390
|
+
textStyle,
|
|
391
|
+
strokeColor
|
|
417
392
|
});
|
|
418
393
|
} else out.push({
|
|
419
394
|
type: "circle",
|
|
420
395
|
cx: x,
|
|
421
|
-
cy: y
|
|
396
|
+
cy: y,
|
|
397
|
+
fillColor
|
|
422
398
|
});
|
|
423
399
|
break;
|
|
424
400
|
}
|
|
@@ -428,7 +404,8 @@ function collectShapes(g, tx, out, bboxArea, textLimits, props) {
|
|
|
428
404
|
out.push({
|
|
429
405
|
type: "circle",
|
|
430
406
|
cx: x,
|
|
431
|
-
cy: y
|
|
407
|
+
cy: y,
|
|
408
|
+
fillColor
|
|
432
409
|
});
|
|
433
410
|
});
|
|
434
411
|
break;
|
|
@@ -436,14 +413,16 @@ function collectShapes(g, tx, out, bboxArea, textLimits, props) {
|
|
|
436
413
|
out.push({
|
|
437
414
|
type: "polyline",
|
|
438
415
|
d: pathFromRing(g.coordinates, tx, false),
|
|
439
|
-
strokeDash
|
|
416
|
+
strokeDash,
|
|
417
|
+
strokeColor
|
|
440
418
|
});
|
|
441
419
|
break;
|
|
442
420
|
case "MultiLineString":
|
|
443
421
|
g.coordinates.forEach((line) => out.push({
|
|
444
422
|
type: "polyline",
|
|
445
423
|
d: pathFromRing(line, tx, false),
|
|
446
|
-
strokeDash
|
|
424
|
+
strokeDash,
|
|
425
|
+
strokeColor
|
|
447
426
|
}));
|
|
448
427
|
break;
|
|
449
428
|
case "Polygon":
|
|
@@ -451,7 +430,9 @@ function collectShapes(g, tx, out, bboxArea, textLimits, props) {
|
|
|
451
430
|
type: "polygon",
|
|
452
431
|
d: pathFromRing(ring, tx, true),
|
|
453
432
|
filled: explicitlyFilled || polygonFilled(ring),
|
|
454
|
-
fillPattern
|
|
433
|
+
fillPattern,
|
|
434
|
+
strokeColor,
|
|
435
|
+
fillColor
|
|
455
436
|
}));
|
|
456
437
|
break;
|
|
457
438
|
case "MultiPolygon":
|
|
@@ -459,7 +440,9 @@ function collectShapes(g, tx, out, bboxArea, textLimits, props) {
|
|
|
459
440
|
type: "polygon",
|
|
460
441
|
d: pathFromRing(ring, tx, true),
|
|
461
442
|
filled: polygonFilled(ring),
|
|
462
|
-
fillPattern
|
|
443
|
+
fillPattern,
|
|
444
|
+
strokeColor,
|
|
445
|
+
fillColor
|
|
463
446
|
})));
|
|
464
447
|
break;
|
|
465
448
|
case "GeometryCollection":
|