@orbat-mapper/tactical-draw-adapter-maplibre 0.2.0-alpha.14 → 0.2.0-alpha.16

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.
Files changed (2) hide show
  1. package/dist/index.mjs +134 -15
  2. package/package.json +3 -3
package/dist/index.mjs CHANGED
@@ -139,19 +139,29 @@ function buildTextSizeExpression(text) {
139
139
  14
140
140
  ];
141
141
  const sizeAtZoom = (zoom) => {
142
+ const groundPx = [
143
+ "*",
144
+ ["get", "textSizeMeters"],
145
+ Math.pow(2, zoom + 9) / WEB_MERCATOR_CIRCUMFERENCE
146
+ ];
142
147
  return [
143
148
  "case",
144
149
  ["has", "textSizeMeters"],
145
150
  [
146
- "min",
147
- maxPx,
151
+ "case",
152
+ ["has", "textMaxSizePixels"],
148
153
  [
149
- "max",
150
- minPx,
154
+ "min",
155
+ ["get", "textMaxSizePixels"],
156
+ groundPx
157
+ ],
158
+ [
159
+ "min",
160
+ maxPx,
151
161
  [
152
- "*",
153
- ["get", "textSizeMeters"],
154
- Math.pow(2, zoom + 9) / WEB_MERCATOR_CIRCUMFERENCE
162
+ "max",
163
+ minPx,
164
+ groundPx
155
165
  ]
156
166
  ]
157
167
  ],
@@ -166,6 +176,50 @@ function buildTextSizeExpression(text) {
166
176
  ];
167
177
  }
168
178
  /**
179
+ * Companion to `buildTextSizeExpression` (ADR-0037): drives `text-opacity` to
180
+ * 0 once a ground-anchored Text graphic's derived on-screen size exceeds its
181
+ * `textMaxSizePixels` threshold, 1 otherwise (including every feature without
182
+ * a threshold, e.g. amplifier labels). Layout properties like `text-size`
183
+ * cannot represent "hidden", so hiding is a paint-property concern; `step`
184
+ * (rather than `interpolate`) is enough since opacity only ever needs to
185
+ * flip at a stop, evaluated at the same per-zoom granularity as the size
186
+ * expression.
187
+ */
188
+ const TEXT_OPACITY_EXPRESSION = (() => {
189
+ const opacityAtZoom = (zoom) => {
190
+ return [
191
+ "case",
192
+ [
193
+ "all",
194
+ ["has", "textSizeMeters"],
195
+ ["has", "textMaxSizePixels"]
196
+ ],
197
+ [
198
+ "case",
199
+ [
200
+ ">",
201
+ [
202
+ "*",
203
+ ["get", "textSizeMeters"],
204
+ Math.pow(2, zoom + 9) / WEB_MERCATOR_CIRCUMFERENCE
205
+ ],
206
+ ["get", "textMaxSizePixels"]
207
+ ],
208
+ 0,
209
+ 1
210
+ ],
211
+ 1
212
+ ];
213
+ };
214
+ const stops = Array.from({ length: 25 }, (_, zoom) => zoom);
215
+ return [
216
+ "step",
217
+ ["zoom"],
218
+ opacityAtZoom(0),
219
+ ...stops.slice(1).flatMap((zoom) => [zoom, opacityAtZoom(zoom)])
220
+ ];
221
+ })();
222
+ /**
169
223
  * Per-feature icon size, same zoom-stop scheme as `buildTextSizeExpression`:
170
224
  * a ground-sized point symbol carries `iconSizeMeters`/`iconOctagonSize` (and
171
225
  * optional `iconMinPixels`/`iconMaxPixels`), so the meters→pixels clamp is
@@ -224,6 +278,26 @@ function buildIconSizeExpression() {
224
278
  * `properties.style.fillColor` overlay. Mirrors `buildCirclePaint`.
225
279
  */
226
280
  /**
281
+ * Per-feature `text-field` value: the layer's literal text, or the feature's
282
+ * `labelProperty` value, uppercased when the feature's `textStyle` is
283
+ * `"caps"`. Factored out of `buildSymbolLayer` so `setLayerStyle`'s
284
+ * incremental `text-field` update (below) can reuse it instead of
285
+ * regressing to a caps-unaware plain `["get", ...]`.
286
+ */
287
+ function buildTextFieldExpression(text) {
288
+ const rawTextField = text.labelProperty ? ["get", text.labelProperty] : text.text ?? "";
289
+ return text.labelProperty ? [
290
+ "case",
291
+ [
292
+ "==",
293
+ ["get", "textStyle"],
294
+ "caps"
295
+ ],
296
+ ["upcase", rawTextField],
297
+ rawTextField
298
+ ] : rawTextField;
299
+ }
300
+ /**
227
301
  * Build the layout and paint objects for a symbol (text) layer.
228
302
  * Centralises the rotation convention so `createLayer` and `setLayerStyle`
229
303
  * stay in sync. Rotation follows the OpenLayers convention: `π − r` radians
@@ -256,6 +330,11 @@ function buildSymbolLayer(symbolId, sourceId, text) {
256
330
  ],
257
331
  "#000"
258
332
  ];
333
+ const hasTextBackground = [
334
+ "boolean",
335
+ ["get", "textBackground"],
336
+ false
337
+ ];
259
338
  const textAnchor = text.labelProperty ? [
260
339
  "case",
261
340
  [
@@ -272,16 +351,48 @@ function buildSymbolLayer(symbolId, sourceId, text) {
272
351
  "right",
273
352
  "center"
274
353
  ] : "center";
354
+ const textJustify = text.labelProperty ? [
355
+ "case",
356
+ [
357
+ "==",
358
+ ["get", "textJustify"],
359
+ "left"
360
+ ],
361
+ "left",
362
+ [
363
+ "==",
364
+ ["get", "textJustify"],
365
+ "right"
366
+ ],
367
+ "right",
368
+ "center"
369
+ ] : "center";
275
370
  return {
276
371
  id: symbolId,
277
372
  type: "symbol",
278
373
  source: sourceId,
279
374
  layout: {
280
- "text-field": text.labelProperty ? ["get", text.labelProperty] : text.text ?? "",
281
- "text-font": ["Open Sans Regular"],
375
+ "text-field": buildTextFieldExpression(text),
376
+ "text-font": text.labelProperty ? [
377
+ "case",
378
+ [
379
+ "==",
380
+ ["get", "textStyle"],
381
+ "italic"
382
+ ],
383
+ ["literal", ["Open Sans Italic"]],
384
+ [
385
+ "==",
386
+ ["get", "textStyle"],
387
+ "light"
388
+ ],
389
+ ["literal", ["Open Sans Light"]],
390
+ ["literal", ["Open Sans Regular"]]
391
+ ] : ["Open Sans Regular"],
282
392
  "text-size": buildTextSizeExpression(text),
283
393
  "text-rotate": textRotate,
284
394
  "text-anchor": textAnchor,
395
+ "text-justify": textJustify,
285
396
  "text-max-width": 9999,
286
397
  "text-rotation-alignment": "map",
287
398
  "text-pitch-alignment": "map",
@@ -289,10 +400,19 @@ function buildSymbolLayer(symbolId, sourceId, text) {
289
400
  },
290
401
  paint: {
291
402
  "text-color": textColor,
292
- ...text.strokeColor ? {
293
- "text-halo-color": text.strokeColor,
294
- "text-halo-width": text.strokeWidth ?? 1
295
- } : {}
403
+ "text-halo-color": text.strokeColor ? text.strokeColor : [
404
+ "case",
405
+ hasTextBackground,
406
+ "#ffffff",
407
+ "rgba(255,255,255,0)"
408
+ ],
409
+ "text-halo-width": text.strokeColor ? text.strokeWidth ?? 1 : [
410
+ "case",
411
+ hasTextBackground,
412
+ 3,
413
+ 0
414
+ ],
415
+ "text-opacity": TEXT_OPACITY_EXPRESSION
296
416
  }
297
417
  };
298
418
  }
@@ -782,8 +902,7 @@ var MapLibreAdapter = class extends BaseMapAdapter {
782
902
  this.map.setPaintProperty(mlId, "circle-stroke-width", paint["circle-stroke-width"]);
783
903
  this.map.setFilter(mlId, buildCirclePointFilter(style));
784
904
  } else if (type === "symbol" && style.text) {
785
- if (style.text.text) this.map.setLayoutProperty(mlId, "text-field", style.text.text);
786
- else if (style.text.labelProperty) this.map.setLayoutProperty(mlId, "text-field", ["get", style.text.labelProperty]);
905
+ if (style.text.text || style.text.labelProperty) this.map.setLayoutProperty(mlId, "text-field", buildTextFieldExpression(style.text));
787
906
  if (style.text.fillColor) this.map.setPaintProperty(mlId, "text-color", style.text.fillColor);
788
907
  if (style.text.strokeColor) this.map.setPaintProperty(mlId, "text-halo-color", style.text.strokeColor);
789
908
  if (style.text.strokeWidth !== void 0) this.map.setPaintProperty(mlId, "text-halo-width", style.text.strokeWidth);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orbat-mapper/tactical-draw-adapter-maplibre",
3
- "version": "0.2.0-alpha.14",
3
+ "version": "0.2.0-alpha.16",
4
4
  "description": "MapLibre GL JS engine adapter for @orbat-mapper/tactical-draw. Draw and edit tactical control measures (MIL-STD-2525 / APP-6) on a MapLibre map.",
5
5
  "license": "MIT",
6
6
  "author": "Orbat Mapper",
@@ -37,8 +37,8 @@
37
37
  "access": "public"
38
38
  },
39
39
  "dependencies": {
40
- "@orbat-mapper/control-measures": "^0.2.0-alpha.21",
41
- "@orbat-mapper/tactical-draw": "^0.2.0-alpha.29"
40
+ "@orbat-mapper/control-measures": "^0.2.0-alpha.24",
41
+ "@orbat-mapper/tactical-draw": "^0.2.0-alpha.31"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "maplibre-gl": ">=4.0.0 <6.0.0"