maplibre-gl 3.2.1 → 3.3.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.
Files changed (88) hide show
  1. package/build/generate-struct-arrays.ts +6 -3
  2. package/build/generate-style-code.ts +7 -8
  3. package/dist/maplibre-gl-csp-worker.js +1 -1
  4. package/dist/maplibre-gl-csp-worker.js.map +1 -1
  5. package/dist/maplibre-gl-csp.js +1 -1
  6. package/dist/maplibre-gl-csp.js.map +1 -1
  7. package/dist/maplibre-gl-dev.js +565 -243
  8. package/dist/maplibre-gl-dev.js.map +1 -1
  9. package/dist/maplibre-gl.d.ts +111 -57
  10. package/dist/maplibre-gl.js +4 -4
  11. package/dist/maplibre-gl.js.map +1 -1
  12. package/package.json +20 -20
  13. package/src/data/array_types.g.ts +110 -24
  14. package/src/data/bucket/circle_bucket.ts +1 -0
  15. package/src/data/bucket/line_bucket.ts +1 -0
  16. package/src/data/bucket/symbol_attributes.ts +7 -1
  17. package/src/data/bucket/symbol_bucket.ts +5 -1
  18. package/src/data/feature_index.ts +1 -0
  19. package/src/data/program_configuration.ts +1 -0
  20. package/src/data/segment.ts +2 -0
  21. package/src/geo/transform.ts +1 -0
  22. package/src/gl/context.ts +1 -0
  23. package/src/gl/framebuffer.ts +1 -0
  24. package/src/gl/index_buffer.ts +1 -0
  25. package/src/gl/render_pool.ts +2 -1
  26. package/src/gl/vertex_buffer.ts +1 -0
  27. package/src/render/draw_symbol.ts +8 -9
  28. package/src/render/image_atlas.ts +1 -0
  29. package/src/render/line_atlas.ts +1 -0
  30. package/src/render/painter.ts +1 -0
  31. package/src/render/program.ts +1 -0
  32. package/src/render/render_to_texture.ts +31 -14
  33. package/src/render/terrain.ts +3 -0
  34. package/src/render/texture.ts +1 -0
  35. package/src/render/uniform_binding.ts +2 -0
  36. package/src/render/vertex_array_object.ts +1 -0
  37. package/src/shaders/symbol_sdf.fragment.glsl +9 -3
  38. package/src/shaders/symbol_sdf.fragment.glsl.g.ts +1 -1
  39. package/src/source/canvas_source.ts +1 -3
  40. package/src/source/geojson_source.ts +1 -3
  41. package/src/source/image_source.ts +2 -4
  42. package/src/source/source_cache.ts +1 -0
  43. package/src/source/source_state.ts +1 -0
  44. package/src/source/terrain_source_cache.ts +1 -0
  45. package/src/source/tile.ts +1 -0
  46. package/src/source/tile_cache.ts +1 -1
  47. package/src/source/tile_id.ts +1 -0
  48. package/src/source/vector_tile_worker_source.test.ts +79 -0
  49. package/src/source/vector_tile_worker_source.ts +26 -1
  50. package/src/source/worker_source.ts +1 -0
  51. package/src/style/evaluation_parameters.ts +1 -0
  52. package/src/style/properties.ts +18 -0
  53. package/src/style/style.ts +1 -0
  54. package/src/style/style_glyph.ts +1 -0
  55. package/src/style/style_layer/background_style_layer_properties.g.ts +1 -6
  56. package/src/style/style_layer/circle_style_layer_properties.g.ts +1 -6
  57. package/src/style/style_layer/fill_extrusion_style_layer_properties.g.ts +1 -6
  58. package/src/style/style_layer/fill_style_layer_properties.g.ts +1 -6
  59. package/src/style/style_layer/heatmap_style_layer_properties.g.ts +1 -6
  60. package/src/style/style_layer/hillshade_style_layer_properties.g.ts +1 -6
  61. package/src/style/style_layer/line_style_layer_properties.g.ts +1 -6
  62. package/src/style/style_layer/raster_style_layer_properties.g.ts +1 -6
  63. package/src/style/style_layer/symbol_style_layer_properties.g.ts +4 -6
  64. package/src/style/style_layer/variable_text_anchor.test.ts +117 -0
  65. package/src/style/style_layer/variable_text_anchor.ts +163 -0
  66. package/src/symbol/collision_index.ts +1 -0
  67. package/src/symbol/grid_index.ts +1 -0
  68. package/src/symbol/placement.ts +52 -40
  69. package/src/symbol/symbol_layout.ts +42 -116
  70. package/src/ui/camera.ts +8 -8
  71. package/src/ui/handler/box_zoom.ts +1 -3
  72. package/src/ui/handler/click_zoom.ts +1 -3
  73. package/src/ui/handler/keyboard.ts +1 -3
  74. package/src/ui/handler/scroll_zoom.ts +1 -3
  75. package/src/ui/handler/shim/dblclick_zoom.ts +1 -3
  76. package/src/ui/handler/shim/drag_pan.ts +1 -3
  77. package/src/ui/handler/shim/drag_rotate.ts +1 -3
  78. package/src/ui/handler/shim/two_fingers_touch.ts +1 -3
  79. package/src/ui/handler/transform-provider.ts +1 -0
  80. package/src/ui/handler/two_fingers_touch.ts +1 -3
  81. package/src/ui/map.ts +7 -6
  82. package/src/util/ajax.test.ts +33 -0
  83. package/src/util/ajax.ts +5 -0
  84. package/src/util/image.ts +1 -0
  85. package/src/util/image_request.ts +2 -2
  86. package/src/util/performance.ts +1 -2
  87. package/src/util/struct_array.ts +5 -1
  88. package/src/util/test/mock_fetch.ts +51 -0
@@ -22,6 +22,7 @@ export type CrossFaded<T> = {
22
22
  };
23
23
 
24
24
  /**
25
+ * @internal
25
26
  * Implementations of the `Property` interface:
26
27
  *
27
28
  * * Hold metadata about a property that's independent of any specific value: stuff like the type of the value,
@@ -49,6 +50,7 @@ export interface Property<T, R> {
49
50
  }
50
51
 
51
52
  /**
53
+ * @internal
52
54
  * `PropertyValue` represents the value part of a property key-value unit. It's used to represent both
53
55
  * paint and layout property values, and regardless of whether or not their property supports data-driven
54
56
  * expressions.
@@ -95,6 +97,7 @@ export type TransitionParameters = {
95
97
  };
96
98
 
97
99
  /**
100
+ * @internal
98
101
  * Paint properties are _transitionable_: they can change in a fluid manner, interpolating or cross-fading between
99
102
  * old and new value. The duration of the transition, and the delay before it begins, is configurable.
100
103
  *
@@ -125,6 +128,7 @@ class TransitionablePropertyValue<T, R> {
125
128
  }
126
129
 
127
130
  /**
131
+ * @internal
128
132
  * `Transitionable` stores a map of all (property name, `TransitionablePropertyValue`) pairs for paint properties of a
129
133
  * given layer type. It can calculate the `TransitioningPropertyValue`s for all of them at once, producing a
130
134
  * `Transitioning` instance for the same set of properties.
@@ -196,6 +200,7 @@ export class Transitionable<Props> {
196
200
  }
197
201
 
198
202
  /**
203
+ * @internal
199
204
  * `TransitioningPropertyValue` implements the first of two intermediate steps in the evaluation chain of a paint
200
205
  * property value. In this step, transitions between old and new values are handled: as long as the transition is in
201
206
  * progress, `TransitioningPropertyValue` maintains a reference to the prior value, and interpolates between it and
@@ -256,6 +261,7 @@ class TransitioningPropertyValue<T, R> {
256
261
  }
257
262
 
258
263
  /**
264
+ * @internal
259
265
  * `Transitioning` stores a map of all (property name, `TransitioningPropertyValue`) pairs for paint properties of a
260
266
  * given layer type. It can calculate the possibly-evaluated values for all of them at once, producing a
261
267
  * `PossiblyEvaluated` instance for the same set of properties.
@@ -311,6 +317,10 @@ export class Layout<Props> {
311
317
  this._values = (Object.create(properties.defaultPropertyValues) as any);
312
318
  }
313
319
 
320
+ hasValue<S extends keyof Props>(name: S) {
321
+ return this._values[name].value !== undefined;
322
+ }
323
+
314
324
  getValue<S extends keyof Props>(name: S) {
315
325
  return clone(this._values[name].value);
316
326
  }
@@ -370,6 +380,7 @@ type PossiblyEvaluatedValue<T> = {
370
380
  } | SourceExpression | CompositeExpression;
371
381
 
372
382
  /**
383
+ * @internal
373
384
  * `PossiblyEvaluatedPropertyValue` is used for data-driven paint and layout property values. It holds a
374
385
  * `PossiblyEvaluatedValue` and the `GlobalProperties` that were used to generate it. You're not allowed to supply
375
386
  * a different set of `GlobalProperties` when performing the final evaluation because they would be ignored in the
@@ -409,6 +420,7 @@ export class PossiblyEvaluatedPropertyValue<T> {
409
420
  }
410
421
 
411
422
  /**
423
+ * @internal
412
424
  * `PossiblyEvaluated` stores a map of all (property name, `R`) pairs for paint or layout properties of a
413
425
  * given layer type.
414
426
  */
@@ -427,6 +439,7 @@ export class PossiblyEvaluated<Props, PossibleEvaluatedProps> {
427
439
  }
428
440
 
429
441
  /**
442
+ * @internal
430
443
  * An implementation of `Property` for properties that do not permit data-driven (source or composite) expressions.
431
444
  * This restriction allows us to declare statically that the result of possibly evaluating this kind of property
432
445
  * is in fact always the scalar type `T`, and can be used without further evaluating the value on a per-feature basis.
@@ -455,6 +468,7 @@ export class DataConstantProperty<T> implements Property<T, T> {
455
468
  }
456
469
 
457
470
  /**
471
+ * @internal
458
472
  * An implementation of `Property` for properties that permit data-driven (source or composite) expressions.
459
473
  * The result of possibly evaluating this kind of property is `PossiblyEvaluatedPropertyValue<T>`; obtaining
460
474
  * a scalar value `T` requires further evaluation on a per-feature basis.
@@ -529,6 +543,7 @@ export class DataDrivenProperty<T> implements Property<T, PossiblyEvaluatedPrope
529
543
  }
530
544
 
531
545
  /**
546
+ * @internal
532
547
  * An implementation of `Property` for data driven `line-pattern` which are transitioned by cross-fading
533
548
  * rather than interpolation.
534
549
  */
@@ -594,6 +609,7 @@ export class CrossFadedDataDrivenProperty<T> extends DataDrivenProperty<CrossFad
594
609
  }
595
610
  }
596
611
  /**
612
+ * @internal
597
613
  * An implementation of `Property` for `*-pattern` and `line-dasharray`, which are transitioned by cross-fading
598
614
  * rather than interpolation.
599
615
  */
@@ -635,6 +651,7 @@ export class CrossFadedProperty<T> implements Property<T, CrossFaded<T>> {
635
651
  }
636
652
 
637
653
  /**
654
+ * @internal
638
655
  * An implementation of `Property` for `heatmap-color` and `line-gradient`. Interpolation is a no-op, and
639
656
  * evaluation returns a boolean value in order to indicate its presence, but the real
640
657
  * evaluation happens in StyleLayer classes.
@@ -660,6 +677,7 @@ export class ColorRampProperty implements Property<Color, boolean> {
660
677
  }
661
678
 
662
679
  /**
680
+ * @internal
663
681
  * `Properties` holds objects containing default values for the layout or paint property set of a given
664
682
  * layer type. These objects are immutable, and they are used as the prototypes for the `_values` members of
665
683
  * `Transitionable`, `Transitioning`, `Layout`, and `PossiblyEvaluated`. This allows these classes to avoid
@@ -562,6 +562,7 @@ export class Style extends Evented {
562
562
  }
563
563
 
564
564
  /**
565
+ * @internal
565
566
  * Apply queued style updates in a batch and recalculate zoom-dependent paint properties.
566
567
  */
567
568
  update(parameters: EvaluationParameters) {
@@ -12,6 +12,7 @@ export type GlyphMetrics = {
12
12
  };
13
13
 
14
14
  /**
15
+ * @internal
15
16
  * A style glyph type
16
17
  */
17
18
  export type StyleGlyph = {
@@ -14,12 +14,7 @@ import {
14
14
  CrossFaded
15
15
  } from '../properties';
16
16
 
17
- import type {Color} from '@maplibre/maplibre-gl-style-spec';
18
- import type {Padding} from '@maplibre/maplibre-gl-style-spec';
19
-
20
- import type {Formatted} from '@maplibre/maplibre-gl-style-spec';
21
-
22
- import type {ResolvedImage} from '@maplibre/maplibre-gl-style-spec';
17
+ import type {Color, Formatted, Padding, ResolvedImage, VariableAnchorOffsetCollection} from '@maplibre/maplibre-gl-style-spec';
23
18
  import {StylePropertySpecification} from '@maplibre/maplibre-gl-style-spec';
24
19
 
25
20
 
@@ -14,12 +14,7 @@ import {
14
14
  CrossFaded
15
15
  } from '../properties';
16
16
 
17
- import type {Color} from '@maplibre/maplibre-gl-style-spec';
18
- import type {Padding} from '@maplibre/maplibre-gl-style-spec';
19
-
20
- import type {Formatted} from '@maplibre/maplibre-gl-style-spec';
21
-
22
- import type {ResolvedImage} from '@maplibre/maplibre-gl-style-spec';
17
+ import type {Color, Formatted, Padding, ResolvedImage, VariableAnchorOffsetCollection} from '@maplibre/maplibre-gl-style-spec';
23
18
  import {StylePropertySpecification} from '@maplibre/maplibre-gl-style-spec';
24
19
 
25
20
  export type CircleLayoutProps = {
@@ -14,12 +14,7 @@ import {
14
14
  CrossFaded
15
15
  } from '../properties';
16
16
 
17
- import type {Color} from '@maplibre/maplibre-gl-style-spec';
18
- import type {Padding} from '@maplibre/maplibre-gl-style-spec';
19
-
20
- import type {Formatted} from '@maplibre/maplibre-gl-style-spec';
21
-
22
- import type {ResolvedImage} from '@maplibre/maplibre-gl-style-spec';
17
+ import type {Color, Formatted, Padding, ResolvedImage, VariableAnchorOffsetCollection} from '@maplibre/maplibre-gl-style-spec';
23
18
  import {StylePropertySpecification} from '@maplibre/maplibre-gl-style-spec';
24
19
 
25
20
 
@@ -14,12 +14,7 @@ import {
14
14
  CrossFaded
15
15
  } from '../properties';
16
16
 
17
- import type {Color} from '@maplibre/maplibre-gl-style-spec';
18
- import type {Padding} from '@maplibre/maplibre-gl-style-spec';
19
-
20
- import type {Formatted} from '@maplibre/maplibre-gl-style-spec';
21
-
22
- import type {ResolvedImage} from '@maplibre/maplibre-gl-style-spec';
17
+ import type {Color, Formatted, Padding, ResolvedImage, VariableAnchorOffsetCollection} from '@maplibre/maplibre-gl-style-spec';
23
18
  import {StylePropertySpecification} from '@maplibre/maplibre-gl-style-spec';
24
19
 
25
20
  export type FillLayoutProps = {
@@ -14,12 +14,7 @@ import {
14
14
  CrossFaded
15
15
  } from '../properties';
16
16
 
17
- import type {Color} from '@maplibre/maplibre-gl-style-spec';
18
- import type {Padding} from '@maplibre/maplibre-gl-style-spec';
19
-
20
- import type {Formatted} from '@maplibre/maplibre-gl-style-spec';
21
-
22
- import type {ResolvedImage} from '@maplibre/maplibre-gl-style-spec';
17
+ import type {Color, Formatted, Padding, ResolvedImage, VariableAnchorOffsetCollection} from '@maplibre/maplibre-gl-style-spec';
23
18
  import {StylePropertySpecification} from '@maplibre/maplibre-gl-style-spec';
24
19
 
25
20
 
@@ -14,12 +14,7 @@ import {
14
14
  CrossFaded
15
15
  } from '../properties';
16
16
 
17
- import type {Color} from '@maplibre/maplibre-gl-style-spec';
18
- import type {Padding} from '@maplibre/maplibre-gl-style-spec';
19
-
20
- import type {Formatted} from '@maplibre/maplibre-gl-style-spec';
21
-
22
- import type {ResolvedImage} from '@maplibre/maplibre-gl-style-spec';
17
+ import type {Color, Formatted, Padding, ResolvedImage, VariableAnchorOffsetCollection} from '@maplibre/maplibre-gl-style-spec';
23
18
  import {StylePropertySpecification} from '@maplibre/maplibre-gl-style-spec';
24
19
 
25
20
 
@@ -14,12 +14,7 @@ import {
14
14
  CrossFaded
15
15
  } from '../properties';
16
16
 
17
- import type {Color} from '@maplibre/maplibre-gl-style-spec';
18
- import type {Padding} from '@maplibre/maplibre-gl-style-spec';
19
-
20
- import type {Formatted} from '@maplibre/maplibre-gl-style-spec';
21
-
22
- import type {ResolvedImage} from '@maplibre/maplibre-gl-style-spec';
17
+ import type {Color, Formatted, Padding, ResolvedImage, VariableAnchorOffsetCollection} from '@maplibre/maplibre-gl-style-spec';
23
18
  import {StylePropertySpecification} from '@maplibre/maplibre-gl-style-spec';
24
19
 
25
20
  export type LineLayoutProps = {
@@ -14,12 +14,7 @@ import {
14
14
  CrossFaded
15
15
  } from '../properties';
16
16
 
17
- import type {Color} from '@maplibre/maplibre-gl-style-spec';
18
- import type {Padding} from '@maplibre/maplibre-gl-style-spec';
19
-
20
- import type {Formatted} from '@maplibre/maplibre-gl-style-spec';
21
-
22
- import type {ResolvedImage} from '@maplibre/maplibre-gl-style-spec';
17
+ import type {Color, Formatted, Padding, ResolvedImage, VariableAnchorOffsetCollection} from '@maplibre/maplibre-gl-style-spec';
23
18
  import {StylePropertySpecification} from '@maplibre/maplibre-gl-style-spec';
24
19
 
25
20
 
@@ -14,12 +14,7 @@ import {
14
14
  CrossFaded
15
15
  } from '../properties';
16
16
 
17
- import type {Color} from '@maplibre/maplibre-gl-style-spec';
18
- import type {Padding} from '@maplibre/maplibre-gl-style-spec';
19
-
20
- import type {Formatted} from '@maplibre/maplibre-gl-style-spec';
21
-
22
- import type {ResolvedImage} from '@maplibre/maplibre-gl-style-spec';
17
+ import type {Color, Formatted, Padding, ResolvedImage, VariableAnchorOffsetCollection} from '@maplibre/maplibre-gl-style-spec';
23
18
  import {StylePropertySpecification} from '@maplibre/maplibre-gl-style-spec';
24
19
 
25
20
  import {
@@ -58,6 +53,7 @@ export type SymbolLayoutProps = {
58
53
  "text-justify": DataDrivenProperty<"auto" | "left" | "center" | "right">,
59
54
  "text-radial-offset": DataDrivenProperty<number>,
60
55
  "text-variable-anchor": DataConstantProperty<Array<"center" | "left" | "right" | "top" | "bottom" | "top-left" | "top-right" | "bottom-left" | "bottom-right">>,
56
+ "text-variable-anchor-offset": DataDrivenProperty<VariableAnchorOffsetCollection>,
61
57
  "text-anchor": DataDrivenProperty<"center" | "left" | "right" | "top" | "bottom" | "top-left" | "top-right" | "bottom-left" | "bottom-right">,
62
58
  "text-max-angle": DataConstantProperty<number>,
63
59
  "text-writing-mode": DataConstantProperty<Array<"horizontal" | "vertical">>,
@@ -104,6 +100,7 @@ export type SymbolLayoutPropsPossiblyEvaluated = {
104
100
  "text-justify": PossiblyEvaluatedPropertyValue<"auto" | "left" | "center" | "right">,
105
101
  "text-radial-offset": PossiblyEvaluatedPropertyValue<number>,
106
102
  "text-variable-anchor": Array<"center" | "left" | "right" | "top" | "bottom" | "top-left" | "top-right" | "bottom-left" | "bottom-right">,
103
+ "text-variable-anchor-offset": PossiblyEvaluatedPropertyValue<VariableAnchorOffsetCollection>,
107
104
  "text-anchor": PossiblyEvaluatedPropertyValue<"center" | "left" | "right" | "top" | "bottom" | "top-left" | "top-right" | "bottom-left" | "bottom-right">,
108
105
  "text-max-angle": number,
109
106
  "text-writing-mode": Array<"horizontal" | "vertical">,
@@ -151,6 +148,7 @@ const getLayout = () => layout = layout || new Properties({
151
148
  "text-justify": new DataDrivenProperty(styleSpec["layout_symbol"]["text-justify"] as any as StylePropertySpecification),
152
149
  "text-radial-offset": new DataDrivenProperty(styleSpec["layout_symbol"]["text-radial-offset"] as any as StylePropertySpecification),
153
150
  "text-variable-anchor": new DataConstantProperty(styleSpec["layout_symbol"]["text-variable-anchor"] as any as StylePropertySpecification),
151
+ "text-variable-anchor-offset": new DataDrivenProperty(styleSpec["layout_symbol"]["text-variable-anchor-offset"] as any as StylePropertySpecification),
154
152
  "text-anchor": new DataDrivenProperty(styleSpec["layout_symbol"]["text-anchor"] as any as StylePropertySpecification),
155
153
  "text-max-angle": new DataConstantProperty(styleSpec["layout_symbol"]["text-max-angle"] as any as StylePropertySpecification),
156
154
  "text-writing-mode": new DataConstantProperty(styleSpec["layout_symbol"]["text-writing-mode"] as any as StylePropertySpecification),
@@ -0,0 +1,117 @@
1
+ import {EvaluationParameters} from '../evaluation_parameters';
2
+ import {ZoomHistory} from '../zoom_history';
3
+ import {SymbolStyleLayer} from './symbol_style_layer';
4
+ import {INVALID_TEXT_OFFSET, evaluateVariableOffset, getTextVariableAnchorOffset} from './variable_text_anchor';
5
+
6
+ describe('evaluateVariableOffset', () => {
7
+ test('fromRadialOffset', () => {
8
+ // Radial offset mode is invoked by using INVALID_TEXT_OFFSET as the Y value
9
+ const srcOffset = [10, INVALID_TEXT_OFFSET] as [number, number];
10
+
11
+ expect(evaluateVariableOffset('center', srcOffset)).toEqual([0, 0]);
12
+
13
+ // Top/bottom offsets are shifted by the default baseline (7)
14
+ expect(evaluateVariableOffset('top', srcOffset)).toEqual([0, 3]);
15
+ expect(evaluateVariableOffset('bottom', srcOffset)).toEqual([0, -3]);
16
+ expect(evaluateVariableOffset('left', srcOffset)).toEqual([10, 0]);
17
+ expect(evaluateVariableOffset('right', srcOffset)).toEqual([-10, 0]);
18
+
19
+ const hypotenuse = 10 / Math.SQRT2;
20
+ expect(evaluateVariableOffset('top-left', srcOffset)).toEqual([expect.closeTo(hypotenuse), expect.closeTo(hypotenuse - 7)]);
21
+ expect(evaluateVariableOffset('top-right', srcOffset)).toEqual([expect.closeTo(-hypotenuse), expect.closeTo(hypotenuse - 7)]);
22
+ expect(evaluateVariableOffset('bottom-left', srcOffset)).toEqual([expect.closeTo(hypotenuse), expect.closeTo(-hypotenuse + 7)]);
23
+ expect(evaluateVariableOffset('bottom-right', srcOffset)).toEqual([expect.closeTo(-hypotenuse), expect.closeTo(-hypotenuse + 7)]);
24
+ });
25
+
26
+ test('fromTextOffset', () => {
27
+ const srcOffset = [10, -10] as [number, number];
28
+
29
+ expect(evaluateVariableOffset('center', srcOffset)).toEqual([0, 0]);
30
+
31
+ // Top/bottom offsets are shifted by the default baseline (7)
32
+ expect(evaluateVariableOffset('top', srcOffset)).toEqual([0, 3]);
33
+ expect(evaluateVariableOffset('bottom', srcOffset)).toEqual([0, -3]);
34
+ expect(evaluateVariableOffset('left', srcOffset)).toEqual([10, 0]);
35
+ expect(evaluateVariableOffset('right', srcOffset)).toEqual([-10, 0]);
36
+ expect(evaluateVariableOffset('top-left', srcOffset)).toEqual([10, 3]);
37
+ expect(evaluateVariableOffset('top-right', srcOffset)).toEqual([-10, 3]);
38
+ expect(evaluateVariableOffset('bottom-left', srcOffset)).toEqual([10, -3]);
39
+ expect(evaluateVariableOffset('bottom-right', srcOffset)).toEqual([-10, -3]);
40
+ });
41
+ });
42
+
43
+ function createSymbolLayer(layerProperties) {
44
+ const layer = new SymbolStyleLayer(layerProperties);
45
+ layer.recalculate({zoom: 0, zoomHistory: {} as ZoomHistory} as EvaluationParameters, []);
46
+ return layer;
47
+ }
48
+
49
+ describe('getTextVariableAnchorOffset', () => {
50
+ test('defaults - no props set', () => {
51
+ const props = {};
52
+ const layer = createSymbolLayer(props);
53
+
54
+ expect(getTextVariableAnchorOffset(layer, null, null)).toBeNull();
55
+ });
56
+
57
+ test('text-variable-anchor-offset set', () => {
58
+ const props = {layout: {'text-variable-anchor-offset': ['top', [1, 1], 'bottom', [2, 2]]}};
59
+ const layer = createSymbolLayer(props);
60
+
61
+ const offset = getTextVariableAnchorOffset(layer, null, null);
62
+ expect(offset).toBeDefined();
63
+ // Offset converted to EMs, accounting for baseline shift on Y axis
64
+ expect(offset.toString()).toBe('["top",[24,17],"bottom",[48,55]]');
65
+ });
66
+
67
+ test('text-variable-anchor set', () => {
68
+ const props = {layout: {'text-variable-anchor': ['top']}};
69
+ const layer = createSymbolLayer(props);
70
+
71
+ const offset = getTextVariableAnchorOffset(layer, null, null);
72
+ expect(offset).toBeDefined();
73
+ // Default offset (0, 0) converted to EMs, accounting for baseline shift on Y axis
74
+ expect(offset.toString()).toBe('["top",[0,-7]]');
75
+ });
76
+
77
+ test('text-variable-anchor and text-offset set', () => {
78
+ const props = {layout: {'text-variable-anchor': ['top'], 'text-offset': [1, 1]}};
79
+ const layer = createSymbolLayer(props);
80
+
81
+ const offset = getTextVariableAnchorOffset(layer, null, null);
82
+ expect(offset).toBeDefined();
83
+ // Offset converted to EMs, accounting for baseline shift on Y axis
84
+ expect(offset.toString()).toBe('["top",[0,17]]');
85
+ });
86
+
87
+ test('text-variable-anchor and text-radial-offset set', () => {
88
+ const props = {layout: {'text-variable-anchor': ['top'], 'text-radial-offset': 2}};
89
+ const layer = createSymbolLayer(props);
90
+
91
+ const offset = getTextVariableAnchorOffset(layer, null, null);
92
+ expect(offset).toBeDefined();
93
+ // Offset converted to EMs, accounting for baseline shift on Y axis
94
+ expect(offset.toString()).toBe('["top",[0,41]]');
95
+ });
96
+
97
+ test('text-variable-anchor, text-offset, and text-radial-offset set', () => {
98
+ const props = {layout: {'text-variable-anchor': ['top'], 'text-offset': [1, 1], 'text-radial-offset': 2}};
99
+ const layer = createSymbolLayer(props);
100
+
101
+ const offset = getTextVariableAnchorOffset(layer, null, null);
102
+ expect(offset).toBeDefined();
103
+ // Offset converted to EMs, accounting for baseline shift on Y axis
104
+ expect(offset.toString()).toBe('["top",[0,41]]');
105
+ });
106
+
107
+ test('text-variable-anchor and text-variable-anchor-offset set', () => {
108
+ const props = {layout: {'text-variable-anchor-offset': ['top', [1, 1]], 'text-variable-anchor': ['bottom']}};
109
+ const layer = createSymbolLayer(props);
110
+
111
+ const offset = getTextVariableAnchorOffset(layer, null, null);
112
+ expect(offset).toBeDefined();
113
+ // Offset converted to EMs, accounting for baseline shift on Y axis
114
+ expect(offset.toString()).toBe('["top",[24,17]]');
115
+ });
116
+ });
117
+
@@ -0,0 +1,163 @@
1
+ import {VariableAnchorOffsetCollection, VariableAnchorOffsetCollectionSpecification} from '@maplibre/maplibre-gl-style-spec';
2
+ import {SymbolFeature} from '../../data/bucket/symbol_bucket';
3
+ import {CanonicalTileID} from '../../source/tile_id';
4
+ import ONE_EM from '../../symbol/one_em';
5
+ import {SymbolStyleLayer} from './symbol_style_layer';
6
+
7
+ export enum TextAnchorEnum {
8
+ 'center' = 1,
9
+ 'left' = 2,
10
+ 'right' = 3,
11
+ 'top' = 4,
12
+ 'bottom' = 5,
13
+ 'top-left' = 6,
14
+ 'top-right' = 7,
15
+ 'bottom-left' = 8,
16
+ 'bottom-right' = 9
17
+ }
18
+
19
+ export type TextAnchor = keyof typeof TextAnchorEnum;
20
+
21
+ // The radial offset is to the edge of the text box
22
+ // In the horizontal direction, the edge of the text box is where glyphs start
23
+ // But in the vertical direction, the glyphs appear to "start" at the baseline
24
+ // We don't actually load baseline data, but we assume an offset of ONE_EM - 17
25
+ // (see "yOffset" in shaping.js)
26
+ const baselineOffset = 7;
27
+ export const INVALID_TEXT_OFFSET = Number.POSITIVE_INFINITY;
28
+
29
+ export function evaluateVariableOffset(anchor: TextAnchor, offset: [number, number]): [number, number] {
30
+
31
+ function fromRadialOffset(anchor: TextAnchor, radialOffset: number): [number, number] {
32
+ let x = 0, y = 0;
33
+ if (radialOffset < 0) radialOffset = 0; // Ignore negative offset.
34
+ // solve for r where r^2 + r^2 = radialOffset^2
35
+ const hypotenuse = radialOffset / Math.sqrt(2);
36
+ switch (anchor) {
37
+ case 'top-right':
38
+ case 'top-left':
39
+ y = hypotenuse - baselineOffset;
40
+ break;
41
+ case 'bottom-right':
42
+ case 'bottom-left':
43
+ y = -hypotenuse + baselineOffset;
44
+ break;
45
+ case 'bottom':
46
+ y = -radialOffset + baselineOffset;
47
+ break;
48
+ case 'top':
49
+ y = radialOffset - baselineOffset;
50
+ break;
51
+ }
52
+
53
+ switch (anchor) {
54
+ case 'top-right':
55
+ case 'bottom-right':
56
+ x = -hypotenuse;
57
+ break;
58
+ case 'top-left':
59
+ case 'bottom-left':
60
+ x = hypotenuse;
61
+ break;
62
+ case 'left':
63
+ x = radialOffset;
64
+ break;
65
+ case 'right':
66
+ x = -radialOffset;
67
+ break;
68
+ }
69
+
70
+ return [x, y];
71
+ }
72
+
73
+ function fromTextOffset(anchor: TextAnchor, offsetX: number, offsetY: number): [number, number] {
74
+ let x = 0, y = 0;
75
+ // Use absolute offset values.
76
+ offsetX = Math.abs(offsetX);
77
+ offsetY = Math.abs(offsetY);
78
+
79
+ switch (anchor) {
80
+ case 'top-right':
81
+ case 'top-left':
82
+ case 'top':
83
+ y = offsetY - baselineOffset;
84
+ break;
85
+ case 'bottom-right':
86
+ case 'bottom-left':
87
+ case 'bottom':
88
+ y = -offsetY + baselineOffset;
89
+ break;
90
+ }
91
+
92
+ switch (anchor) {
93
+ case 'top-right':
94
+ case 'bottom-right':
95
+ case 'right':
96
+ x = -offsetX;
97
+ break;
98
+ case 'top-left':
99
+ case 'bottom-left':
100
+ case 'left':
101
+ x = offsetX;
102
+ break;
103
+ }
104
+
105
+ return [x, y];
106
+ }
107
+
108
+ return (offset[1] !== INVALID_TEXT_OFFSET) ? fromTextOffset(anchor, offset[0], offset[1]) : fromRadialOffset(anchor, offset[0]);
109
+ }
110
+
111
+ // Helper to support both text-variable-anchor and text-variable-anchor-offset. Offset values converted from EMs to PXs
112
+ export function getTextVariableAnchorOffset(layer: SymbolStyleLayer, feature: SymbolFeature, canonical: CanonicalTileID): VariableAnchorOffsetCollection | null {
113
+ const layout = layer.layout;
114
+ // If style specifies text-variable-anchor-offset, just return it
115
+ const variableAnchorOffset = layout.get('text-variable-anchor-offset')?.evaluate(feature, {}, canonical);
116
+
117
+ if (variableAnchorOffset) {
118
+ const sourceValues = variableAnchorOffset.values;
119
+ const destValues: VariableAnchorOffsetCollectionSpecification = [];
120
+
121
+ // Convert offsets from EM to PX, and apply baseline shift
122
+ for (let i = 0; i < sourceValues.length; i += 2) {
123
+ const anchor = destValues[i] = sourceValues[i] as TextAnchor;
124
+ const offset = (sourceValues[i + 1] as [number, number]).map(t => t * ONE_EM) as [number, number];
125
+
126
+ if (anchor.startsWith('top')) {
127
+ offset[1] -= baselineOffset;
128
+ } else if (anchor.startsWith('bottom')) {
129
+ offset[1] += baselineOffset;
130
+ }
131
+
132
+ destValues[i + 1] = offset;
133
+ }
134
+
135
+ return new VariableAnchorOffsetCollection(destValues);
136
+ }
137
+
138
+ // If style specifies text-variable-anchor, convert to the new format
139
+ const variableAnchor = layout.get('text-variable-anchor');
140
+
141
+ if (variableAnchor) {
142
+ let textOffset: [number, number];
143
+ const unevaluatedLayout = layer._unevaluatedLayout;
144
+
145
+ // The style spec says don't use `text-offset` and `text-radial-offset` together
146
+ // but doesn't actually specify what happens if you use both. We go with the radial offset.
147
+ if (unevaluatedLayout.getValue('text-radial-offset') !== undefined) {
148
+ textOffset = [layout.get('text-radial-offset').evaluate(feature, {}, canonical) * ONE_EM, INVALID_TEXT_OFFSET];
149
+ } else {
150
+ textOffset = layout.get('text-offset').evaluate(feature, {}, canonical).map(t => t * ONE_EM) as [number, number];
151
+ }
152
+
153
+ const anchorOffsets: VariableAnchorOffsetCollectionSpecification = [];
154
+
155
+ for (const anchor of variableAnchor) {
156
+ anchorOffsets.push(anchor, evaluateVariableOffset(anchor, textOffset));
157
+ }
158
+
159
+ return new VariableAnchorOffsetCollection(anchorOffsets);
160
+ }
161
+
162
+ return null;
163
+ }
@@ -33,6 +33,7 @@ export type FeatureKey = {
33
33
  };
34
34
 
35
35
  /**
36
+ * @internal
36
37
  * A collision index used to prevent symbols from overlapping. It keep tracks of
37
38
  * where previous symbols have been placed and is used to check if a new
38
39
  * symbol overlaps with any previously added symbols.
@@ -48,6 +48,7 @@ function overlapAllowed(overlapA: OverlapMode, overlapB: OverlapMode): boolean {
48
48
  }
49
49
 
50
50
  /**
51
+ * @internal
51
52
  * GridIndex is a data structure for testing the intersection of
52
53
  * circles and rectangles in a 2d plane.
53
54
  * It is optimized for rapid insertion and querying.