maplibre-gl 6.2.0 → 6.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 (124) hide show
  1. package/dist/maplibre-gl-dev.mjs +522 -223
  2. package/dist/maplibre-gl-dev.mjs.map +1 -1
  3. package/dist/maplibre-gl-shared-dev.mjs +103 -58
  4. package/dist/maplibre-gl-shared-dev.mjs.map +1 -1
  5. package/dist/maplibre-gl-shared.mjs +2 -2
  6. package/dist/maplibre-gl-shared.mjs.map +1 -1
  7. package/dist/maplibre-gl-worker-dev.mjs +23 -49
  8. package/dist/maplibre-gl-worker-dev.mjs.map +1 -1
  9. package/dist/maplibre-gl-worker.mjs +2 -2
  10. package/dist/maplibre-gl-worker.mjs.map +1 -1
  11. package/dist/maplibre-gl.css +1 -1
  12. package/dist/maplibre-gl.d.ts +214 -59
  13. package/dist/maplibre-gl.mjs +44 -44
  14. package/dist/maplibre-gl.mjs.map +1 -1
  15. package/package.json +10 -20
  16. package/src/data/feature_index.bench.ts +21 -0
  17. package/src/data/segment.test.ts +2 -2
  18. package/src/geo/edge_insets.test.ts +1 -1
  19. package/src/geo/lng_lat.test.ts +7 -7
  20. package/src/geo/lng_lat_bounds.test.ts +4 -4
  21. package/src/geo/mercator_coordinate.test.ts +2 -2
  22. package/src/geo/projection/camera_helper.ts +5 -0
  23. package/src/geo/projection/globe_transform.test.ts +25 -0
  24. package/src/geo/projection/globe_transform.ts +7 -3
  25. package/src/geo/projection/mercator_camera_helper.ts +1 -1
  26. package/src/geo/projection/mercator_transform.ts +21 -5
  27. package/src/geo/projection/vertical_perspective_camera_helper.ts +60 -31
  28. package/src/geo/projection/vertical_perspective_transform.ts +7 -1
  29. package/src/geo/transform_interface.ts +13 -1
  30. package/src/index.ts +7 -2
  31. package/src/render/image_atlas.test.ts +72 -0
  32. package/src/render/image_atlas.ts +17 -2
  33. package/src/render/image_manager.test.ts +46 -0
  34. package/src/render/image_manager.ts +9 -2
  35. package/src/render/painter.ts +1 -12
  36. package/src/render/update_pattern_positions_in_program.test.ts +1 -1
  37. package/src/shaders/glsl/_prelude.vertex.glsl +2 -3
  38. package/src/shaders/glsl/_prelude.vertex.glsl.g.ts +1 -1
  39. package/src/shaders/glsl/circle.vertex.glsl +4 -4
  40. package/src/shaders/glsl/circle.vertex.glsl.g.ts +1 -1
  41. package/src/shaders/glsl/fill_extrusion.vertex.glsl +3 -3
  42. package/src/shaders/glsl/fill_extrusion.vertex.glsl.g.ts +1 -1
  43. package/src/shaders/glsl/fill_extrusion_pattern.vertex.glsl +5 -5
  44. package/src/shaders/glsl/fill_extrusion_pattern.vertex.glsl.g.ts +1 -1
  45. package/src/shaders/glsl/heatmap.vertex.glsl +4 -4
  46. package/src/shaders/glsl/heatmap.vertex.glsl.g.ts +1 -1
  47. package/src/shaders/glsl/line.vertex.glsl +7 -7
  48. package/src/shaders/glsl/line.vertex.glsl.g.ts +1 -1
  49. package/src/shaders/glsl/line_gradient.vertex.glsl +6 -6
  50. package/src/shaders/glsl/line_gradient.vertex.glsl.g.ts +1 -1
  51. package/src/shaders/glsl/line_gradient_sdf.vertex.glsl +7 -7
  52. package/src/shaders/glsl/line_gradient_sdf.vertex.glsl.g.ts +1 -1
  53. package/src/shaders/glsl/line_pattern.vertex.glsl +7 -7
  54. package/src/shaders/glsl/line_pattern.vertex.glsl.g.ts +1 -1
  55. package/src/shaders/glsl/line_sdf.vertex.glsl +7 -7
  56. package/src/shaders/glsl/line_sdf.vertex.glsl.g.ts +1 -1
  57. package/src/shaders/glsl/raster.fragment.glsl +4 -4
  58. package/src/shaders/glsl/raster.fragment.glsl.g.ts +1 -1
  59. package/src/shaders/glsl/raster.vertex.glsl +11 -6
  60. package/src/shaders/glsl/raster.vertex.glsl.g.ts +1 -1
  61. package/src/shaders/glsl/symbol_icon.vertex.glsl +5 -5
  62. package/src/shaders/glsl/symbol_icon.vertex.glsl.g.ts +1 -1
  63. package/src/shaders/glsl/symbol_sdf.vertex.glsl +5 -5
  64. package/src/shaders/glsl/symbol_sdf.vertex.glsl.g.ts +1 -1
  65. package/src/shaders/glsl/symbol_text_and_icon.vertex.glsl +6 -6
  66. package/src/shaders/glsl/symbol_text_and_icon.vertex.glsl.g.ts +1 -1
  67. package/src/source/canvas_source.test.ts +31 -0
  68. package/src/source/canvas_source.ts +2 -1
  69. package/src/source/geojson_worker_source.test.ts +88 -0
  70. package/src/source/geojson_worker_source.ts +22 -39
  71. package/src/source/image_source.test.ts +205 -7
  72. package/src/source/image_source.ts +118 -4
  73. package/src/source/raster_dem_tile_worker_source.test.ts +1 -1
  74. package/src/source/vector_tile_worker_source.test.ts +94 -2
  75. package/src/source/vector_tile_worker_source.ts +18 -27
  76. package/src/source/video_source.test.ts +12 -8
  77. package/src/source/worker_tile.bench.ts +10 -0
  78. package/src/source/worker_tile.ts +0 -3
  79. package/src/style/create_style_layer.bench.ts +15 -0
  80. package/src/style/properties.bench.ts +81 -0
  81. package/src/style/style.test.ts +11 -10
  82. package/src/style/style_image.ts +67 -9
  83. package/src/style/style_layer.bench.ts +65 -0
  84. package/src/style/style_layer.test.ts +1 -1
  85. package/src/style/style_layer.ts +2 -2
  86. package/src/style/validate_style.bench.ts +11 -0
  87. package/src/style/validate_style.test.ts +2 -2
  88. package/src/symbol/anchor.test.ts +2 -2
  89. package/src/symbol/collision_index.bench.ts +116 -0
  90. package/src/symbol/cross_tile_symbol_index.bench.ts +49 -0
  91. package/src/symbol/shaping.test.ts +2 -1
  92. package/src/symbol/symbol_layout.bench.ts +30 -0
  93. package/src/tile/tile_id.test.ts +1 -1
  94. package/src/tile/tile_manager.ts +2 -2
  95. package/src/ui/control/fullscreen_control.test.ts +10 -10
  96. package/src/ui/control/geolocate_control.test.ts +3 -5
  97. package/src/ui/control/globe_control.test.ts +6 -6
  98. package/src/ui/events.ts +1 -1
  99. package/src/ui/handler/scroll_zoom.test.ts +50 -0
  100. package/src/ui/handler/two_fingers_touch.test.ts +3 -3
  101. package/src/ui/handler_manager.test.ts +243 -3
  102. package/src/ui/handler_manager.ts +79 -16
  103. package/src/ui/map.ts +17 -3
  104. package/src/ui/map_tests/map_cross_window.test.ts +2 -2
  105. package/src/ui/map_tests/map_events.test.ts +3 -3
  106. package/src/ui/map_tests/map_images.test.ts +29 -0
  107. package/src/ui/map_tests/map_style.test.ts +9 -8
  108. package/src/ui/map_tests/map_webgl.test.ts +29 -0
  109. package/src/ui/marker.test.ts +26 -42
  110. package/src/ui/popup.test.ts +14 -14
  111. package/src/util/evented.test.ts +33 -28
  112. package/src/util/evented.ts +69 -43
  113. package/src/util/test/null_gl.ts +10 -0
  114. package/src/util/test/util.ts +2 -1
  115. package/src/util/util.test.ts +1 -1
  116. package/src/util/web_worker_transfer.test.ts +5 -5
  117. package/src/webgl/context.ts +20 -0
  118. package/src/webgl/draw/draw_raster.ts +11 -6
  119. package/src/webgl/program/raster_program.ts +11 -0
  120. package/src/webgl/program.ts +31 -16
  121. package/src/webgl/texture.test.ts +16 -1
  122. package/src/webgl/texture.ts +3 -0
  123. package/src/webgl/vertex_buffer.test.ts +17 -2
  124. package/src/webgl/vertex_buffer.ts +24 -13
@@ -1,8 +1,13 @@
1
1
  import {describe, test, expect, vi} from 'vitest';
2
2
  import {Event, Evented} from './evented.ts';
3
3
 
4
+ class StubbedEvent extends Event<'a' | 'b'> {
5
+ foo?: string;
6
+ foz?: string;
7
+ }
8
+
4
9
  // `Evented` is abstract, so use a minimal concrete subclass to exercise its behavior.
5
- class StubbedEvented extends Evented {}
10
+ class StubbedEvented extends Evented<Record<StubbedEvent['type'], StubbedEvent>> {}
6
11
 
7
12
  describe('Evented', () => {
8
13
 
@@ -10,8 +15,8 @@ describe('Evented', () => {
10
15
  const evented = new StubbedEvented();
11
16
  const listener = vi.fn();
12
17
  evented.on('a', listener);
13
- evented.fire(new Event('a'));
14
- evented.fire(new Event('a'));
18
+ evented.fire(new StubbedEvent('a'));
19
+ evented.fire(new StubbedEvent('a'));
15
20
  expect(listener).toHaveBeenCalledTimes(2);
16
21
  });
17
22
 
@@ -19,8 +24,8 @@ describe('Evented', () => {
19
24
  const evented = new StubbedEvented();
20
25
  const listener = vi.fn();
21
26
  evented.once('a', listener);
22
- evented.fire(new Event('a'));
23
- evented.fire(new Event('a'));
27
+ evented.fire(new StubbedEvent('a'));
28
+ evented.fire(new StubbedEvent('a'));
24
29
  expect(listener).toHaveBeenCalledTimes(1);
25
30
  expect(evented.listens('a')).toBeFalsy();
26
31
  });
@@ -29,17 +34,17 @@ describe('Evented', () => {
29
34
  const evented = new StubbedEvented();
30
35
  const listener = vi.fn();
31
36
  const subscription = evented.on('a', listener);
32
- evented.fire(new Event('a'));
37
+ evented.fire(new StubbedEvent('a'));
33
38
  subscription.unsubscribe();
34
- evented.fire(new Event('a'));
39
+ evented.fire(new StubbedEvent('a'));
35
40
  expect(listener).toHaveBeenCalledTimes(1);
36
41
  });
37
42
 
38
43
  test('returns a promise when no listener is provided to "once" method', async () => {
39
44
  const evented = new StubbedEvented();
40
45
  const promise = evented.once('a');
41
- evented.fire(new Event('a'));
42
- evented.fire(new Event('a'));
46
+ evented.fire(new StubbedEvent('a'));
47
+ evented.fire(new StubbedEvent('a'));
43
48
  await promise;
44
49
  expect(evented.listens('a')).toBeFalsy();
45
50
 
@@ -50,7 +55,7 @@ describe('Evented', () => {
50
55
  evented.on('a', (data) => {
51
56
  expect(data.foo).toBe('bar');
52
57
  });
53
- evented.fire(new Event('a', {foo: 'bar'}));
58
+ evented.fire(new StubbedEvent('a', {foo: 'bar'}));
54
59
 
55
60
  });
56
61
 
@@ -59,7 +64,7 @@ describe('Evented', () => {
59
64
  evented.on('a', (data) => {
60
65
  expect(data.target).toBe(evented);
61
66
  });
62
- evented.fire(new Event('a'));
67
+ evented.fire(new StubbedEvent('a'));
63
68
 
64
69
  });
65
70
 
@@ -68,7 +73,7 @@ describe('Evented', () => {
68
73
  evented.on('a', (data) => {
69
74
  expect(data.type).toBe('a');
70
75
  });
71
- evented.fire(new Event('a'));
76
+ evented.fire(new StubbedEvent('a'));
72
77
 
73
78
  });
74
79
 
@@ -77,7 +82,7 @@ describe('Evented', () => {
77
82
  const listener = vi.fn();
78
83
  evented.on('a', listener);
79
84
  evented.off('a', listener);
80
- evented.fire(new Event('a'));
85
+ evented.fire(new StubbedEvent('a'));
81
86
  expect(listener).not.toHaveBeenCalled();
82
87
  });
83
88
 
@@ -86,7 +91,7 @@ describe('Evented', () => {
86
91
  const listener = vi.fn();
87
92
  evented.once('a', listener);
88
93
  evented.off('a', listener);
89
- evented.fire(new Event('a'));
94
+ evented.fire(new StubbedEvent('a'));
90
95
  expect(listener).not.toHaveBeenCalled();
91
96
  });
92
97
 
@@ -95,9 +100,9 @@ describe('Evented', () => {
95
100
  const listener = vi.fn();
96
101
  evented.once('a', () => {
97
102
  listener();
98
- evented.fire(new Event('a'));
103
+ evented.fire(new StubbedEvent('a'));
99
104
  });
100
- evented.fire(new Event('a'));
105
+ evented.fire(new StubbedEvent('a'));
101
106
  expect(listener).toHaveBeenCalledTimes(1);
102
107
  });
103
108
 
@@ -124,7 +129,7 @@ describe('Evented', () => {
124
129
  evented.on('a', () => {
125
130
  evented.on('a', nestedListener);
126
131
  });
127
- evented.fire(new Event('a'));
132
+ evented.fire(new StubbedEvent('a'));
128
133
 
129
134
  expect(nestedListener).not.toHaveBeenCalled();
130
135
  });
@@ -147,7 +152,7 @@ describe('Evented', () => {
147
152
  evented.on('a', listenerA);
148
153
  evented.on('a', listenerB);
149
154
  evented.on('a', listenerA);
150
- evented.fire(new Event('a'));
155
+ evented.fire(new StubbedEvent('a'));
151
156
  expect(listenerA).toHaveBeenCalledTimes(1);
152
157
  expect(order).toEqual(['A', 'B']);
153
158
 
@@ -162,8 +167,8 @@ describe('evented parents', () => {
162
167
  const eventedSink = new StubbedEvented();
163
168
  eventedSource.setEventedParent(eventedSink);
164
169
  eventedSink.on('a', listener);
165
- eventedSource.fire(new Event('a'));
166
- eventedSource.fire(new Event('a'));
170
+ eventedSource.fire(new StubbedEvent('a'));
171
+ eventedSource.fire(new StubbedEvent('a'));
167
172
  expect(listener).toHaveBeenCalledTimes(2);
168
173
  });
169
174
 
@@ -174,7 +179,7 @@ describe('evented parents', () => {
174
179
  eventedSink.on('a', (data) => {
175
180
  expect(data.foo).toBe('bar');
176
181
  });
177
- eventedSource.fire(new Event('a', {foo: 'bar'}));
182
+ eventedSource.fire(new StubbedEvent('a', {foo: 'bar'}));
178
183
 
179
184
  });
180
185
 
@@ -185,7 +190,7 @@ describe('evented parents', () => {
185
190
  eventedSink.on('a', (data) => {
186
191
  expect(data.foz).toBe('baz');
187
192
  });
188
- eventedSource.fire(new Event('a', {foo: 'bar'}));
193
+ eventedSource.fire(new StubbedEvent('a', {foo: 'bar'}));
189
194
 
190
195
  });
191
196
 
@@ -196,7 +201,7 @@ describe('evented parents', () => {
196
201
  eventedSink.on('a', (data) => {
197
202
  expect(data.foz).toBe('baz');
198
203
  });
199
- eventedSource.fire(new Event('a', {foo: 'bar'}));
204
+ eventedSource.fire(new StubbedEvent('a', {foo: 'bar'}));
200
205
 
201
206
  });
202
207
 
@@ -208,7 +213,7 @@ describe('evented parents', () => {
208
213
  eventedSink.on('a', (data) => {
209
214
  expect(data.target).toBe(eventedSource);
210
215
  });
211
- eventedSource.fire(new Event('a'));
216
+ eventedSource.fire(new StubbedEvent('a'));
212
217
 
213
218
  });
214
219
 
@@ -219,7 +224,7 @@ describe('evented parents', () => {
219
224
  eventedSink.on('a', listener);
220
225
  eventedSource.setEventedParent(eventedSink);
221
226
  eventedSource.setEventedParent(null);
222
- eventedSource.fire(new Event('a'));
227
+ eventedSource.fire(new StubbedEvent('a'));
223
228
  expect(listener).not.toHaveBeenCalled();
224
229
  });
225
230
 
@@ -236,11 +241,11 @@ describe('evented parents', () => {
236
241
  const eventedSource = new StubbedEvented();
237
242
  const eventedParent = new StubbedEvented();
238
243
  let i = 0;
239
- eventedSource.setEventedParent(eventedParent, () => i++);
244
+ eventedSource.setEventedParent(eventedParent, () => ({counter: i++}));
240
245
  eventedSource.on('a', () => {});
241
- eventedSource.fire(new Event('a'));
246
+ eventedSource.fire(new StubbedEvent('a'));
242
247
  expect(i).toBe(1);
243
- eventedSource.fire(new Event('a'));
248
+ eventedSource.fire(new StubbedEvent('a'));
244
249
  expect(i).toBe(2);
245
250
 
246
251
  });
@@ -3,11 +3,26 @@ import {extend, type Subscription} from './util.ts';
3
3
  /**
4
4
  * A listener method used as a callback to events
5
5
  */
6
- export type Listener = (a: any) => any;
6
+ export type Listener<E extends Event = Event> = (event: E) => any;
7
7
 
8
- type Listeners<EventType extends Record<string, any>> = {[_ in keyof EventType]?: Listener[]};
8
+ /**
9
+ * A mapping between event names and the event each of them carries.
10
+ */
11
+ export type EventTypeMap = Record<string, Event>;
12
+
13
+ /**
14
+ * The event names of an {@link EventTypeMap}.
15
+ */
16
+ export type EventNames<EventType extends EventTypeMap> = Extract<keyof EventType, string>;
9
17
 
10
- function _addEventListener<T extends Record<string, any>>(type: keyof T, listener: Listener, listenerList: Listeners<T>) {
18
+ /**
19
+ * Properties merged into every event bubbled up to an evented parent.
20
+ */
21
+ export type EventedParentData = Record<string, unknown>;
22
+
23
+ type Listeners<EventType extends EventTypeMap> = {[K in keyof EventType]?: Array<Listener<EventType[K]>>};
24
+
25
+ function _addEventListener<T extends EventTypeMap, K extends EventNames<T>>(type: K, listener: Listener<T[K]>, listenerList: Listeners<T>) {
11
26
  const listenerExists = listenerList[type]?.includes(listener);
12
27
  if (!listenerExists) {
13
28
  listenerList[type] ||= [];
@@ -15,7 +30,7 @@ function _addEventListener<T extends Record<string, any>>(type: keyof T, listene
15
30
  }
16
31
  }
17
32
 
18
- function _removeEventListener<T extends Record<string, any>>(type: keyof T, listener: Listener, listenerList: Listeners<T>) {
33
+ function _removeEventListener<T extends EventTypeMap, K extends EventNames<T>>(type: K, listener: Listener<T[K]>, listenerList: Listeners<T>) {
19
34
  if (listenerList?.[type]) {
20
35
  const index = listenerList[type].indexOf(listener);
21
36
  if (index !== -1) {
@@ -27,15 +42,15 @@ function _removeEventListener<T extends Record<string, any>>(type: keyof T, list
27
42
  /**
28
43
  * The event class
29
44
  */
30
- export class Event {
31
- readonly type: string;
45
+ export class Event<TType extends string = string> {
46
+ readonly type: TType;
32
47
  /**
33
48
  * The object that fired the event. Set when the event is fired, and narrowed to a more
34
49
  * specific type (e.g. `Map`, `Marker`) by the event subclasses.
35
50
  */
36
51
  target?: unknown;
37
52
 
38
- constructor(type: string, data: any = {}) {
53
+ constructor(type: TType, data: object = {}) {
39
54
  extend(this, data);
40
55
  this.type = type;
41
56
  }
@@ -48,24 +63,31 @@ type ErrorLike = {
48
63
  /**
49
64
  * An error event
50
65
  */
51
- export class ErrorEvent extends Event {
66
+ export class ErrorEvent extends Event<'error'> {
52
67
  error: ErrorLike;
53
68
 
54
- constructor(error: ErrorLike, data: any = {}) {
69
+ constructor(error: ErrorLike, data: object = {}) {
55
70
  super('error', extend({error}, data));
56
71
  }
57
72
  }
58
73
 
74
+ /**
75
+ * The event map of an {@link Evented} that only reports errors.
76
+ */
77
+ export type ErrorEventType = {
78
+ error: ErrorEvent;
79
+ };
80
+
59
81
  /**
60
82
  * Methods mixed in to other classes for event capabilities.
61
83
  *
62
84
  * @group Event Related
63
85
  */
64
- export abstract class Evented<EventType extends Record<string, any> = Record<string, any>> {
65
- _listeners: Listeners<EventType>;
66
- _oneTimeListeners: Listeners<EventType>;
67
- _eventedParent: Evented;
68
- _eventedParentData: any | (() => any);
86
+ export abstract class Evented<EventType extends EventTypeMap = EventTypeMap> {
87
+ _listeners?: Listeners<EventType>;
88
+ _oneTimeListeners?: Listeners<EventType>;
89
+ _eventedParent?: Evented;
90
+ _eventedParentData?: EventedParentData | (() => EventedParentData);
69
91
 
70
92
  /**
71
93
  * Adds a listener to a specified event type.
@@ -75,7 +97,7 @@ export abstract class Evented<EventType extends Record<string, any> = Record<str
75
97
  * The listener function is called with the data object passed to `fire`,
76
98
  * extended with `target` and `type` properties.
77
99
  */
78
- on<T extends keyof EventType>(type: T, listener: (event: EventType[T]) => void): Subscription {
100
+ on<T extends EventNames<EventType>>(type: T, listener: (event: EventType[T]) => void): Subscription {
79
101
  this._listeners ||= {};
80
102
  _addEventListener(type, listener, this._listeners);
81
103
 
@@ -92,7 +114,7 @@ export abstract class Evented<EventType extends Record<string, any> = Record<str
92
114
  * @param type - The event type to remove listeners for.
93
115
  * @param listener - The listener function to remove.
94
116
  */
95
- off<T extends keyof EventType>(type: T, listener: (event: EventType[T]) => void): this {
117
+ off<T extends EventNames<EventType>>(type: T, listener: (event: EventType[T]) => void): this {
96
118
  _removeEventListener(type, listener, this._listeners);
97
119
  _removeEventListener(type, listener, this._oneTimeListeners);
98
120
 
@@ -107,7 +129,7 @@ export abstract class Evented<EventType extends Record<string, any> = Record<str
107
129
  * @param type - The event type to listen for.
108
130
  * @returns a promise that resolves with the event
109
131
  */
110
- once<T extends keyof EventType>(type: T): Promise<EventType[T]>;
132
+ once<T extends EventNames<EventType>>(type: T): Promise<EventType[T]>;
111
133
  /**
112
134
  * Adds a listener that will be called only once to a specified event type.
113
135
  *
@@ -117,8 +139,8 @@ export abstract class Evented<EventType extends Record<string, any> = Record<str
117
139
  * @param listener - The function to be called when the event is fired the first time.
118
140
  * @returns `this` when a listener is provided
119
141
  */
120
- once<T extends keyof EventType>(type: T, listener: (event: EventType[T]) => void): this;
121
- once<T extends keyof EventType>(type: T, listener?: (event: EventType[T]) => void): this | Promise<EventType[T]> {
142
+ once<T extends EventNames<EventType>>(type: T, listener: (event: EventType[T]) => void): this;
143
+ once<T extends EventNames<EventType>>(type: T, listener?: (event: EventType[T]) => void): this | Promise<EventType[T]> {
122
144
  if (!listener) {
123
145
  return new Promise((resolve) => this.once(type, resolve));
124
146
  }
@@ -128,44 +150,48 @@ export abstract class Evented<EventType extends Record<string, any> = Record<str
128
150
  return this;
129
151
  }
130
152
 
131
- fire(event: Event | string, properties?: any): this {
132
- // Compatibility with (type: string, properties: Object) signature from previous versions.
133
- // See https://github.com/mapbox/mapbox-gl-js/issues/6522,
134
- // https://github.com/mapbox/mapbox-gl-draw/issues/766
135
- if (typeof event === 'string') {
136
- event = new Event(event, properties || {});
137
- }
138
-
139
- const type = event.type;
153
+ /**
154
+ * Calls every listener registered for the event's type.
155
+ */
156
+ fire(event: EventType[EventNames<EventType>]): this;
157
+ /**
158
+ * Compatibility with the (type: string, properties: Object) signature from previous versions.
159
+ * See https://github.com/mapbox/mapbox-gl-js/issues/6522,
160
+ * https://github.com/mapbox/mapbox-gl-draw/issues/766
161
+ */
162
+ fire(type: EventNames<EventType>, properties?: object): this;
163
+ fire(event: EventType[EventNames<EventType>] | EventNames<EventType>, properties?: object): this {
164
+ const firedEvent: Event = typeof event === 'string' ? new Event(event, properties || {}) : event;
165
+ const type = firedEvent.type as EventNames<EventType>;
140
166
 
141
167
  if (this.listens(type)) {
142
- event.target = this;
168
+ firedEvent.target = this;
143
169
 
144
170
  // make sure adding or removing listeners inside other listeners won't cause an infinite loop
145
- const listeners = this._listeners?.[type] ? this._listeners[type].slice() : [];
171
+ const listeners = this._listeners?.[type]?.slice() ?? [];
146
172
  for (const listener of listeners) {
147
- listener.call(this, event);
173
+ listener.call(this, firedEvent);
148
174
  }
149
175
 
150
- const oneTimeListeners = this._oneTimeListeners?.[type] ? this._oneTimeListeners[type].slice() : [];
176
+ const oneTimeListeners = this._oneTimeListeners?.[type]?.slice() ?? [];
151
177
  for (const listener of oneTimeListeners) {
152
178
  _removeEventListener(type, listener, this._oneTimeListeners);
153
- listener.call(this, event);
179
+ listener.call(this, firedEvent);
154
180
  }
155
181
 
156
182
  const parent = this._eventedParent;
157
183
  if (parent) {
158
184
  extend(
159
- event,
185
+ firedEvent,
160
186
  typeof this._eventedParentData === 'function' ? this._eventedParentData() : this._eventedParentData
161
187
  );
162
- parent.fire(event);
188
+ parent.fire(firedEvent);
163
189
  }
164
190
 
165
191
  // To ensure that no error events are dropped, print them to the
166
192
  // console if they have no listeners.
167
- } else if (event instanceof ErrorEvent) {
168
- console.error(event.error);
193
+ } else if (firedEvent instanceof ErrorEvent) {
194
+ console.error(firedEvent.error);
169
195
  }
170
196
 
171
197
  return this;
@@ -177,18 +203,18 @@ export abstract class Evented<EventType extends Record<string, any> = Record<str
177
203
  * @param type - The event type
178
204
  * @returns `true` if there is at least one registered listener for specified event type, `false` otherwise
179
205
  */
180
- listens(type: string): boolean {
181
- return (
182
- (this._listeners?.[type]?.length > 0) ||
183
- (this._oneTimeListeners?.[type]?.length > 0) ||
184
- (this._eventedParent?.listens(type))
206
+ listens(type: EventNames<EventType>): boolean {
207
+ return Boolean(
208
+ this._listeners?.[type]?.length ||
209
+ this._oneTimeListeners?.[type]?.length ||
210
+ this._eventedParent?.listens(type)
185
211
  );
186
212
  }
187
213
 
188
214
  /**
189
215
  * Bubble all events fired by this instance of Evented to this parent instance of Evented.
190
216
  */
191
- setEventedParent(parent?: Evented | null, data?: any | (() => any)): this {
217
+ setEventedParent(parent?: Evented | null, data?: EventedParentData | (() => EventedParentData)): this {
192
218
  this._eventedParent = parent;
193
219
  this._eventedParentData = data;
194
220
 
@@ -70,6 +70,16 @@ export class NullWebGL2RenderingContext {
70
70
  INT: number = 0x1404;
71
71
  UNSIGNED_INT: number = 0x1405;
72
72
  FLOAT: number = 0x1406;
73
+ FLOAT_VEC2: number = 0x8B50;
74
+ FLOAT_VEC3: number = 0x8B51;
75
+ FLOAT_VEC4: number = 0x8B52;
76
+ INT_VEC2: number = 0x8B53;
77
+ INT_VEC3: number = 0x8B54;
78
+ INT_VEC4: number = 0x8B55;
79
+ UNSIGNED_INT_VEC2: number = 0x8DC6;
80
+ UNSIGNED_INT_VEC3: number = 0x8DC7;
81
+ UNSIGNED_INT_VEC4: number = 0x8DC8;
82
+ ACTIVE_ATTRIBUTES: number = 0x8B89;
73
83
  ALPHA: number = 0x1906;
74
84
  RGB: number = 0x1907;
75
85
  RGBA: number = 0x1908;
@@ -5,6 +5,7 @@ import {extend} from '../../util/util.ts';
5
5
  import {type Dispatcher} from '../../util/dispatcher.ts';
6
6
  import {type IActor} from '../actor.ts';
7
7
  import {Evented} from '../evented.ts';
8
+ import {type SourceEventType} from '../../ui/events.ts';
8
9
  import {type SourceSpecification, type StyleSpecification, type TerrainSpecification} from '@maplibre/maplibre-gl-style-spec';
9
10
  import {MercatorTransform} from '../../geo/projection/mercator_transform.ts';
10
11
  import {RequestManager} from '../request_manager.ts';
@@ -197,7 +198,7 @@ export const sleep: (milliseconds?: number) => Promise<void> = (milliseconds: nu
197
198
  return new Promise<void>(resolve => setTimeout(resolve, milliseconds));
198
199
  };
199
200
 
200
- export function waitForMetadataEvent(source: Evented): Promise<void> {
201
+ export function waitForMetadataEvent(source: Evented<SourceEventType>): Promise<void> {
201
202
  return new Promise((resolve) => {
202
203
  source.on('data', (e) => {
203
204
  if (e.sourceDataType === 'metadata') {
@@ -78,7 +78,7 @@ describe('util', () => {
78
78
 
79
79
  test('bezier', () => {
80
80
  const curve = bezier(0, 0, 0.25, 1);
81
- expect(curve instanceof Function).toBeTruthy();
81
+ expect(curve).toBeInstanceOf(Function);
82
82
  expect(curve(0)).toBe(0);
83
83
  expect(curve(1)).toBe(1);
84
84
  expect(curve(0.5)).toBe(0.8230854638965502);
@@ -39,8 +39,8 @@ describe('web worker transfer', () => {
39
39
  const serializableMock = new SerializableMock(10);
40
40
  const transferables = [];
41
41
  const deserialized = deserialize(serialize(serializableMock, transferables)) as SerializableMock;
42
- expect(deserialize(serialize(serializableMock, transferables)) instanceof SerializableMock).toBeTruthy();
43
- expect(serializableMock.dataView instanceof DataView).toBeTruthy();
42
+ expect(deserialize(serialize(serializableMock, transferables))).toBeInstanceOf(SerializableMock);
43
+ expect(serializableMock.dataView).toBeInstanceOf(DataView);
44
44
 
45
45
  expect(serializableMock !== deserialized).toBeTruthy();
46
46
  expect(deserialized.constructor === SerializableMock).toBeTruthy();
@@ -51,7 +51,7 @@ describe('web worker transfer', () => {
51
51
  expect(transferables[1] === serializableMock.dataView.buffer).toBeTruthy();
52
52
  expect(deserialized._cached === undefined).toBeTruthy();
53
53
  expect(deserialized.squared() === 100).toBeTruthy();
54
- expect(deserialized.dataView instanceof DataView).toBeTruthy();
54
+ expect(deserialized.dataView).toBeInstanceOf(DataView);
55
55
  expect(deserialized.array).toEqual(serializableMock.array);
56
56
  });
57
57
 
@@ -61,7 +61,7 @@ describe('web worker transfer', () => {
61
61
  register('Anon', Klass);
62
62
  const x = new Klass();
63
63
  const deserialized = deserialize(serialize(x));
64
- expect(deserialized instanceof Klass).toBeTruthy();
64
+ expect(deserialized).toBeInstanceOf(Klass);
65
65
  });
66
66
 
67
67
  test('null', () => {
@@ -94,7 +94,7 @@ describe('web worker transfer', () => {
94
94
  expect(!customSerialization._deserialized).toBeTruthy();
95
95
 
96
96
  const deserialized = deserialize(serialize(customSerialization)) as CustomSerialization;
97
- expect(deserialize(serialize(customSerialization)) instanceof CustomSerialization).toBeTruthy();
97
+ expect(deserialize(serialize(customSerialization))).toBeInstanceOf(CustomSerialization);
98
98
  expect(deserialized.id).toBe(customSerialization.id);
99
99
  expect(deserialized._deserialized).toBeTruthy();
100
100
  });
@@ -177,6 +177,26 @@ export class Context {
177
177
  this.pixelStoreUnpackFlipY.dirty = true;
178
178
  }
179
179
 
180
+ /**
181
+ * Reset some GL state to default values before handing users the raw context, as we do for
182
+ * custom layers and WebGL style images, to avoid hard-to-debug bugs in their code.
183
+ *
184
+ * MapLibre restores all of its own state afterwards, so the only state worth resetting first
185
+ * is state users would be surprised to find dirty: `CULL_FACE`, `TEXTURE0` and the three
186
+ * `UNPACK_` settings, whose defaults are meaningful enough that most code assumes them.
187
+ * The vertex array is unbound rather than reset, so that MapLibre never has to track it and
188
+ * a user's `vertexAttribPointer` calls cannot land on one of ours.
189
+ */
190
+ setCustomLayerDefaults(): void {
191
+ this.unbindVAO();
192
+
193
+ this.cullFace.setDefault();
194
+ this.activeTexture.setDefault();
195
+ this.pixelStoreUnpack.setDefault();
196
+ this.pixelStoreUnpackPremultiplyAlpha.setDefault();
197
+ this.pixelStoreUnpackFlipY.setDefault();
198
+ }
199
+
180
200
  createIndexBuffer(array: TriangleIndexArray | LineIndexArray | LineStripIndexArray, dynamicDraw?: boolean): IndexBuffer {
181
201
  return new IndexBuffer(this, array, dynamicDraw);
182
202
  }
@@ -5,7 +5,11 @@ import {now} from '../../util/time_control.ts';
5
5
  import {StencilMode} from '../stencil_mode.ts';
6
6
  import {DepthMode} from '../depth_mode.ts';
7
7
  import {CullFaceMode} from '../cull_face_mode.ts';
8
- import {rasterUniformValues} from '../program/raster_program.ts';
8
+ import {
9
+ identityPerspectiveTransform,
10
+ rasterUniformValues,
11
+ type RasterPerspectiveTransform
12
+ } from '../program/raster_program.ts';
9
13
  import {EXTENT} from '../../data/extent.ts';
10
14
  import {FadingDirections} from '../../tile/tile.ts';
11
15
  import Point from '@mapbox/point-geometry';
@@ -59,16 +63,16 @@ export function drawRaster(painter: Painter, tileManager: TileManager, layer: Ra
59
63
  // Stencil mask and two-pass is not used for ImageSource sources regardless of projection.
60
64
  if (source instanceof ImageSource) {
61
65
  // Image source - no stencil is used
62
- drawTiles(painter, tileManager, layer, tileIDs, null, false, false, source.tileCoords, source.flippedWindingOrder, isRenderingToTexture);
66
+ drawTiles(painter, tileManager, layer, tileIDs, null, false, false, source.tileCoords, source.perspectiveTransform, source.flippedWindingOrder, isRenderingToTexture);
63
67
  } else if (useSubdivision) {
64
68
  // Two-pass rendering
65
69
  const [stencilBorderless, stencilBorders, coords] = painter.stencilConfigForOverlapTwoPass(tileIDs);
66
- drawTiles(painter, tileManager, layer, coords, stencilBorderless, false, true, cornerCoords, false, isRenderingToTexture); // draw without borders
67
- drawTiles(painter, tileManager, layer, coords, stencilBorders, true, true, cornerCoords, false, isRenderingToTexture); // draw with borders
70
+ drawTiles(painter, tileManager, layer, coords, stencilBorderless, false, true, cornerCoords, identityPerspectiveTransform, false, isRenderingToTexture); // draw without borders
71
+ drawTiles(painter, tileManager, layer, coords, stencilBorders, true, true, cornerCoords, identityPerspectiveTransform, false, isRenderingToTexture); // draw with borders
68
72
  } else {
69
73
  // Simple rendering
70
74
  const [stencil, coords] = painter.getStencilConfigForOverlapAndUpdateStencilID(tileIDs);
71
- drawTiles(painter, tileManager, layer, coords, stencil, false, true, cornerCoords, false, isRenderingToTexture);
75
+ drawTiles(painter, tileManager, layer, coords, stencil, false, true, cornerCoords, identityPerspectiveTransform, false, isRenderingToTexture);
72
76
  }
73
77
  }
74
78
 
@@ -81,6 +85,7 @@ function drawTiles(
81
85
  useBorder: boolean,
82
86
  allowPoles: boolean,
83
87
  corners: Point[],
88
+ perspectiveTransform: RasterPerspectiveTransform,
84
89
  flipCullfaceMode: boolean = false,
85
90
  isRenderingToTexture: boolean = false) {
86
91
  const minTileZ = coords[coords.length - 1].overscaledZ;
@@ -133,7 +138,7 @@ function drawTiles(
133
138
 
134
139
  const terrainData = painter.getTerrainDataForTile(coord, isRenderingToTexture);
135
140
  const projectionData = transform.getProjectionData({overscaledTileID: coord, aligned: align, applyGlobeMatrix: !isRenderingToTexture, applyTerrainMatrix: true});
136
- const uniformValues = rasterUniformValues(parentTopLeft, parentScaleBy, fadeValues.fadeMix, layer, corners);
141
+ const uniformValues = rasterUniformValues(parentTopLeft, parentScaleBy, fadeValues.fadeMix, layer, corners, perspectiveTransform);
137
142
 
138
143
  const mesh = projection.getMeshFromTileID(context, coord.canonical, useBorder, allowPoles, 'raster');
139
144
  const stencilMode = stencilModes ? stencilModes[coord.overscaledZ] : StencilMode.disabled;
@@ -5,10 +5,18 @@ import type {UniformValues, UniformLocations} from '../uniform_binding.ts';
5
5
  import type {RasterStyleLayer} from '../../style/style_layer/raster_style_layer.ts';
6
6
  import type Point from '@mapbox/point-geometry';
7
7
 
8
+ export type RasterPerspectiveTransform = [number, number, number];
9
+
10
+ /**
11
+ * Affine texture coordinates represented as a homogeneous transform.
12
+ */
13
+ export const identityPerspectiveTransform: RasterPerspectiveTransform = [0, 0, 1];
14
+
8
15
  export type RasterUniformsType = {
9
16
  'u_tl_parent': Uniform2f;
10
17
  'u_scale_parent': Uniform1f;
11
18
  'u_buffer_scale': Uniform1f;
19
+ 'u_perspective_transform': Uniform3f;
12
20
  'u_fade_t': Uniform1f;
13
21
  'u_opacity': Uniform1f;
14
22
  'u_image0': Uniform1i;
@@ -26,6 +34,7 @@ const rasterUniforms = (context: Context, locations: UniformLocations): RasterUn
26
34
  'u_tl_parent': new Uniform2f(context, locations.u_tl_parent),
27
35
  'u_scale_parent': new Uniform1f(context, locations.u_scale_parent),
28
36
  'u_buffer_scale': new Uniform1f(context, locations.u_buffer_scale),
37
+ 'u_perspective_transform': new Uniform3f(context, locations.u_perspective_transform),
29
38
  'u_fade_t': new Uniform1f(context, locations.u_fade_t),
30
39
  'u_opacity': new Uniform1f(context, locations.u_opacity),
31
40
  'u_image0': new Uniform1i(context, locations.u_image0),
@@ -48,6 +57,7 @@ const rasterUniformValues = (
48
57
  },
49
58
  layer: RasterStyleLayer,
50
59
  cornerCoords: Point[],
60
+ perspectiveTransform: RasterPerspectiveTransform,
51
61
  ): UniformValues<RasterUniformsType> => ({
52
62
  'u_tl_parent': parentTL,
53
63
  'u_scale_parent': parentScaleBy,
@@ -56,6 +66,7 @@ const rasterUniformValues = (
56
66
  // so that the texture coordinares for poles always lie beyond the edge of the texture.
57
67
  // Right now the coordinates are placed right at the texture border.
58
68
  'u_buffer_scale': 1,
69
+ 'u_perspective_transform': perspectiveTransform,
59
70
  'u_fade_t': fade.mix,
60
71
  'u_opacity': fade.opacity * layer.paint.get('raster-opacity'),
61
72
  'u_image0': 0,