maplibre-gl 2.3.0 → 2.4.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 (79) hide show
  1. package/build/generate-query-test-fixtures.ts +3 -2
  2. package/build/generate-struct-arrays.ts +0 -2
  3. package/build/generate-style-spec.ts +12 -17
  4. package/build/rollup_plugins.ts +0 -4
  5. package/dist/maplibre-gl-csp-worker.js +1 -1
  6. package/dist/maplibre-gl-csp.js +1 -1
  7. package/dist/maplibre-gl-dev.js +179 -1369
  8. package/dist/maplibre-gl.d.ts +15 -16
  9. package/dist/maplibre-gl.js +4 -4
  10. package/dist/style-spec/index.d.ts +3 -16
  11. package/package.json +20 -21
  12. package/src/data/array_types.g.ts +0 -5
  13. package/src/data/bucket/fill_bucket.ts +0 -2
  14. package/src/data/bucket/fill_extrusion_bucket.ts +0 -2
  15. package/src/data/feature_position_map.ts +1 -2
  16. package/src/gl/framebuffer.ts +3 -2
  17. package/src/gl/index_buffer.ts +1 -2
  18. package/src/gl/vertex_buffer.ts +1 -2
  19. package/src/index.ts +1 -3
  20. package/src/render/draw_debug.ts +1 -1
  21. package/src/render/draw_fill.ts +9 -1
  22. package/src/render/draw_fill_extrusion.ts +8 -1
  23. package/src/render/draw_heatmap.ts +5 -3
  24. package/src/render/draw_hillshade.ts +17 -2
  25. package/src/render/image_manager.ts +5 -7
  26. package/src/render/program/line_program.ts +1 -1
  27. package/src/render/program/pattern.ts +0 -2
  28. package/src/render/program.ts +12 -9
  29. package/src/render/vertex_array_object.ts +1 -3
  30. package/src/source/geojson_worker_source.ts +0 -4
  31. package/src/source/query_features.ts +0 -3
  32. package/src/source/rtl_text_plugin.ts +3 -4
  33. package/src/source/source_cache.ts +0 -2
  34. package/src/source/tile_id.ts +8 -8
  35. package/src/source/worker.ts +0 -7
  36. package/src/source/worker_tile.ts +3 -2
  37. package/src/style/format_section_override.ts +1 -2
  38. package/src/style/properties.ts +1 -3
  39. package/src/style/style.ts +6 -5
  40. package/src/style/style_layer/custom_style_layer.ts +1 -2
  41. package/src/style/style_layer/symbol_style_layer.ts +5 -8
  42. package/src/style-spec/CHANGELOG.md +2 -2
  43. package/src/style-spec/expression/compound_expression.ts +0 -4
  44. package/src/style-spec/expression/definitions/assertion.ts +2 -4
  45. package/src/style-spec/expression/definitions/case.ts +1 -2
  46. package/src/style-spec/expression/definitions/coalesce.ts +1 -3
  47. package/src/style-spec/expression/definitions/coercion.ts +2 -4
  48. package/src/style-spec/expression/definitions/match.ts +0 -3
  49. package/src/style-spec/expression/index.ts +0 -2
  50. package/src/style-spec/expression/values.ts +0 -2
  51. package/src/style-spec/feature_filter/feature_filter.test.ts +39 -2
  52. package/src/style-spec/function/convert.ts +1 -3
  53. package/src/style-spec/types.g.ts +12 -17
  54. package/src/symbol/collision_index.ts +0 -3
  55. package/src/symbol/path_interpolator.ts +0 -2
  56. package/src/symbol/placement.ts +14 -9
  57. package/src/symbol/shaping.ts +0 -4
  58. package/src/ui/camera.test.ts +64 -1
  59. package/src/ui/camera.ts +36 -10
  60. package/src/ui/control/attribution_control.test.ts +62 -0
  61. package/src/ui/control/attribution_control.ts +4 -2
  62. package/src/ui/control/geolocate_control.ts +4 -6
  63. package/src/ui/control/terrain_control.test.ts +60 -0
  64. package/src/ui/handler/handler_util.ts +1 -2
  65. package/src/ui/handler/map_event.test.ts +65 -1
  66. package/src/ui/handler/map_event.ts +4 -1
  67. package/src/ui/handler/scroll_zoom.ts +0 -2
  68. package/src/ui/handler_manager.ts +0 -2
  69. package/src/ui/map.test.ts +105 -0
  70. package/src/ui/map.ts +8 -1
  71. package/src/util/ajax.ts +1 -2
  72. package/src/util/color_ramp.ts +1 -2
  73. package/src/util/dictionary_coder.ts +1 -3
  74. package/src/util/dispatcher.ts +1 -4
  75. package/src/util/dom.ts +0 -3
  76. package/src/util/image.ts +1 -3
  77. package/src/util/struct_array.ts +0 -5
  78. package/src/util/task_queue.ts +1 -3
  79. package/src/util/web_worker_transfer.ts +5 -6
@@ -1,6 +1,5 @@
1
1
  import {clamp} from '../util/util';
2
2
  import Point from '@mapbox/point-geometry';
3
- import assert from 'assert';
4
3
 
5
4
  class PathInterpolator {
6
5
  points: Array<Point>;
@@ -30,7 +29,6 @@ class PathInterpolator {
30
29
  }
31
30
 
32
31
  lerp(t: number): Point {
33
- assert(this.points.length > 0);
34
32
  if (this.points.length === 1) {
35
33
  return this.points[0];
36
34
  }
@@ -6,7 +6,6 @@ import * as projection from './projection';
6
6
  import {getAnchorJustification, evaluateVariableOffset} from './symbol_layout';
7
7
  import {getAnchorAlignment, WritingMode} from './shaping';
8
8
  import {mat4} from 'gl-matrix';
9
- import assert from 'assert';
10
9
  import pixelsToTileUnits from '../source/pixels_to_tile_units';
11
10
  import Point from '@mapbox/point-geometry';
12
11
  import type Transform from '../geo/transform';
@@ -23,6 +22,7 @@ import type FeatureIndex from '../data/feature_index';
23
22
  import type {OverscaledTileID} from '../source/tile_id';
24
23
  import type {TextAnchor} from './symbol_layout';
25
24
  import Terrain from '../render/terrain';
25
+ import {warnOnce} from '../util/util';
26
26
 
27
27
  class OpacityState {
28
28
  opacity: number;
@@ -391,7 +391,7 @@ export class Placement {
391
391
  this.prevPlacement.placements[symbolInstance.crossTileID].text) {
392
392
  prevAnchor = this.prevPlacement.variableOffsets[symbolInstance.crossTileID].anchor;
393
393
  }
394
- assert(symbolInstance.crossTileID !== 0);
394
+ if (symbolInstance.crossTileID === 0) throw new Error('symbolInstance.crossTileID can\'t be 0');
395
395
  this.variableOffsets[symbolInstance.crossTileID] = {
396
396
  textOffset,
397
397
  width,
@@ -676,7 +676,10 @@ export class Placement {
676
676
  getElevation
677
677
  );
678
678
 
679
- assert(!placedGlyphCircles.circles.length || (!placedGlyphCircles.collisionDetected || showCollisionBoxes));
679
+ if (placedGlyphCircles.circles.length && placedGlyphCircles.collisionDetected && !showCollisionBoxes) {
680
+ warnOnce('Collisions detected, but collision boxes are not shown');
681
+ }
682
+
680
683
  // If text-overlap is set to 'always', force "placedCircles" to true
681
684
  // In theory there should always be at least one circle placed
682
685
  // in this case, but for now quirks in text-anchor
@@ -781,15 +784,15 @@ export class Placement {
781
784
  }
782
785
  }
783
786
 
784
- assert(symbolInstance.crossTileID !== 0);
785
- assert(bucket.bucketInstanceId !== 0);
787
+ if (symbolInstance.crossTileID === 0) throw new Error('symbolInstance.crossTileID can\'t be 0');
788
+ if (bucket.bucketInstanceId === 0) throw new Error('bucket.bucketInstanceId can\'t be 0');
786
789
 
787
790
  this.placements[symbolInstance.crossTileID] = new JointPlacement(placeText || alwaysShowText, placeIcon || alwaysShowIcon, offscreen || bucket.justReloaded);
788
791
  seenCrossTileIDs[symbolInstance.crossTileID] = true;
789
792
  };
790
793
 
791
794
  if (zOrderByViewportY) {
792
- assert(bucketPart.symbolInstanceStart === 0);
795
+ if (bucketPart.symbolInstanceStart !== 0) throw new Error('bucket.bucketInstanceId should be 0');
793
796
  const symbolIndexes = bucket.getSortedSymbolIndexes(this.transform.angle);
794
797
  for (let i = symbolIndexes.length - 1; i >= 0; --i) {
795
798
  const symbolIndex = symbolIndexes[i];
@@ -920,7 +923,9 @@ export class Placement {
920
923
  // this.lastPlacementChangeTime is the time of the last commit() that
921
924
  // resulted in a placement change -- in other words, the start time of
922
925
  // the last symbol fade animation
923
- assert(!prevPlacement || prevPlacement.lastPlacementChangeTime !== undefined);
926
+ if (prevPlacement && prevPlacement.lastPlacementChangeTime === undefined) {
927
+ throw new Error('Last placement time for previous placement is not defined');
928
+ }
924
929
  if (placementChanged) {
925
930
  this.lastPlacementChangeTime = now;
926
931
  } else if (typeof this.lastPlacementChangeTime !== 'number') {
@@ -1133,8 +1138,8 @@ export class Placement {
1133
1138
  bucket.textCollisionBox.collisionVertexBuffer.updateData(bucket.textCollisionBox.collisionVertexArray);
1134
1139
  }
1135
1140
 
1136
- assert(bucket.text.opacityVertexArray.length === bucket.text.layoutVertexArray.length / 4);
1137
- assert(bucket.icon.opacityVertexArray.length === bucket.icon.layoutVertexArray.length / 4);
1141
+ if (bucket.text.opacityVertexArray.length !== bucket.text.layoutVertexArray.length / 4) throw new Error(`bucket.text.opacityVertexArray.length (= ${bucket.text.opacityVertexArray.length}) !== bucket.text.layoutVertexArray.length (= ${bucket.text.layoutVertexArray.length}) / 4`);
1142
+ if (bucket.icon.opacityVertexArray.length !== bucket.icon.layoutVertexArray.length / 4) throw new Error(`bucket.icon.opacityVertexArray.length (= ${bucket.icon.opacityVertexArray.length}) !== bucket.icon.layoutVertexArray.length (= ${bucket.icon.layoutVertexArray.length}) / 4`);
1138
1143
 
1139
1144
  // Push generated collision circles to the bucket for debug rendering
1140
1145
  if (bucket.bucketInstanceId in this.collisionCircleArrays) {
@@ -1,4 +1,3 @@
1
- import assert from 'assert';
2
1
  import {
3
2
  charHasUprightVerticalOrientation,
4
3
  charAllowsIdeographicBreaking,
@@ -811,9 +810,6 @@ function fitIconToText(
811
810
  iconOffset: [number, number],
812
811
  fontScale: number
813
812
  ): PositionedIcon {
814
- assert(textFit !== 'none');
815
- assert(Array.isArray(padding) && padding.length === 4);
816
- assert(Array.isArray(iconOffset) && iconOffset.length === 2);
817
813
 
818
814
  const image = shapedIcon.image;
819
815
 
@@ -1,9 +1,10 @@
1
- import Camera from '../ui/camera';
1
+ import Camera, {CameraOptions} from '../ui/camera';
2
2
  import Transform from '../geo/transform';
3
3
  import TaskQueue, {TaskID} from '../util/task_queue';
4
4
  import browser from '../util/browser';
5
5
  import {fixedLngLat, fixedNum} from '../../test/unit/lib/fixed';
6
6
  import {setMatchMedia} from '../util/test/util';
7
+ import {mercatorZfromAltitude} from '../geo/mercator_coordinate';
7
8
 
8
9
  beforeEach(() => {
9
10
  setMatchMedia();
@@ -55,6 +56,68 @@ function assertTransitionTime(done, camera, min, max) {
55
56
  });
56
57
  }
57
58
 
59
+ describe('#calculateCameraOptionsFromTo', () => {
60
+ // Choose initial zoom to avoid center being constrained by mercator latitude limits.
61
+ const camera = createCamera({zoom: 1});
62
+
63
+ test('look at north', () => {
64
+ const cameraOptions: CameraOptions = camera.calculateCameraOptionsFromTo({lng: 1, lat: 0}, 0, {lng: 1, lat: 1});
65
+ expect(cameraOptions).toBeDefined();
66
+ expect(cameraOptions.center).toBeDefined();
67
+ expect(cameraOptions.bearing).toBeCloseTo(0);
68
+ });
69
+
70
+ test('look at west', () => {
71
+ const cameraOptions = camera.calculateCameraOptionsFromTo({lng: 1, lat: 0}, 0, {lng: 0, lat: 0});
72
+ expect(cameraOptions).toBeDefined();
73
+ expect(cameraOptions.bearing).toBeCloseTo(-90);
74
+ });
75
+
76
+ test('pitch 45', () => {
77
+ // altitude same as grounddistance => 45°
78
+ // distance between lng x and lng x+1 is 111.2km at same lat
79
+ const cameraOptions: CameraOptions = camera.calculateCameraOptionsFromTo({lng: 1, lat: 0}, 111200, {lng: 0, lat: 0});
80
+ expect(cameraOptions).toBeDefined();
81
+ expect(cameraOptions.pitch).toBeCloseTo(45);
82
+ });
83
+
84
+ test('pitch 90', () => {
85
+ const cameraOptions = camera.calculateCameraOptionsFromTo({lng: 1, lat: 0}, 0, {lng: 0, lat: 0});
86
+ expect(cameraOptions).toBeDefined();
87
+ expect(cameraOptions.pitch).toBeCloseTo(90);
88
+ });
89
+
90
+ test('pitch 153.435', () => {
91
+
92
+ // distance between lng x and lng x+1 is 111.2km at same lat
93
+ // (elevation difference of cam and center) / 2 = grounddistance =>
94
+ // acos(111.2 / sqrt(111.2² + (111.2 * 2)²)) = acos(1/sqrt(5)) => 63.435 + 90 (looking up) = 153.435
95
+ const cameraOptions: CameraOptions = camera.calculateCameraOptionsFromTo({lng: 1, lat: 0}, 111200, {lng: 0, lat: 0}, 111200 * 3);
96
+ expect(cameraOptions).toBeDefined();
97
+ expect(cameraOptions.pitch).toBeCloseTo(153.435);
98
+ });
99
+
100
+ test('zoom distance 1000', () => {
101
+ const expectedZoom = Math.log2(camera.transform.cameraToCenterDistance / mercatorZfromAltitude(1000, 0) / camera.transform.tileSize);
102
+ const cameraOptions = camera.calculateCameraOptionsFromTo({lng: 0, lat: 0}, 0, {lng: 0, lat: 0}, 1000);
103
+
104
+ expect(cameraOptions).toBeDefined();
105
+ expect(cameraOptions.zoom).toBeCloseTo(expectedZoom);
106
+ });
107
+
108
+ test('zoom distance 1 lng (111.2km), 111.2km altitude away', () => {
109
+ const expectedZoom = Math.log2(camera.transform.cameraToCenterDistance / mercatorZfromAltitude(Math.hypot(111200, 111200), 0) / camera.transform.tileSize);
110
+ const cameraOptions = camera.calculateCameraOptionsFromTo({lng: 0, lat: 0}, 0, {lng: 1, lat: 0}, 111200);
111
+
112
+ expect(cameraOptions).toBeDefined();
113
+ expect(cameraOptions.zoom).toBeCloseTo(expectedZoom);
114
+ });
115
+
116
+ test('same To as From error', () => {
117
+ expect(() => { camera.calculateCameraOptionsFromTo({lng: 0, lat: 0}, 0, {lng: 0, lat: 0}, 0); }).toThrow();
118
+ });
119
+ });
120
+
58
121
  describe('#jumpTo', () => {
59
122
  // Choose initial zoom to avoid center being constrained by mercator latitude limits.
60
123
  const camera = createCamera({zoom: 1});
package/src/ui/camera.ts CHANGED
@@ -5,7 +5,6 @@ import LngLat from '../geo/lng_lat';
5
5
  import LngLatBounds from '../geo/lng_lat_bounds';
6
6
  import Point from '@mapbox/point-geometry';
7
7
  import {Event, Evented} from '../util/evented';
8
- import assert from 'assert';
9
8
  import {Debug} from '../util/debug';
10
9
 
11
10
  import type Transform from '../geo/transform';
@@ -13,6 +12,7 @@ import type {LngLatLike} from '../geo/lng_lat';
13
12
  import type {LngLatBoundsLike} from '../geo/lng_lat_bounds';
14
13
  import type {TaskID} from '../util/task_queue';
15
14
  import type {PaddingOptions} from '../geo/edge_insets';
15
+ import MercatorCoordinate from '../geo/mercator_coordinate';
16
16
 
17
17
  /**
18
18
  * A [Point](https://github.com/mapbox/point-geometry) or an array of two numbers representing `x` and `y` screen coordinates in pixels.
@@ -775,6 +775,41 @@ abstract class Camera extends Evented {
775
775
  return this.fire(new Event('moveend', eventData));
776
776
  }
777
777
 
778
+ /**
779
+ * Calculates pitch, zoom and bearing for looking at @param newCenter with the camera position being @param newCenter
780
+ * and returns them as Cameraoptions.
781
+ * @memberof Map#
782
+ * @param from The camera to look from
783
+ * @param altitudeFrom The altitude of the camera to look from
784
+ * @param to The center to look at
785
+ * @param altitudeTo Optional altitude of the center to look at. If none given the ground height will be used.
786
+ * @returns {CameraOptions} the calculated camera options
787
+ */
788
+ calculateCameraOptionsFromTo(from: LngLat, altitudeFrom: number, to: LngLat, altitudeTo: number = 0) : CameraOptions {
789
+ const fromMerc = MercatorCoordinate.fromLngLat(from, altitudeFrom);
790
+ const toMerc = MercatorCoordinate.fromLngLat(to, altitudeTo);
791
+ const dx = toMerc.x - fromMerc.x;
792
+ const dy = toMerc.y - fromMerc.y;
793
+ const dz = toMerc.z - fromMerc.z;
794
+
795
+ const distance3D = Math.hypot(dx, dy, dz);
796
+ if (distance3D === 0) throw new Error('Can\'t calculate camera options with same From and To');
797
+
798
+ const groundDistance = Math.hypot(dx, dy);
799
+
800
+ const zoom = this.transform.scaleZoom(this.transform.cameraToCenterDistance / distance3D / this.transform.tileSize);
801
+ const bearing = (Math.atan2(dx, -dy) * 180) / Math.PI;
802
+ let pitch = (Math.acos(groundDistance / distance3D) * 180) / Math.PI;
803
+ pitch = dz < 0 ? 90 - pitch : 90 + pitch;
804
+
805
+ return {
806
+ center: toMerc.toLngLat(),
807
+ zoom,
808
+ pitch,
809
+ bearing
810
+ };
811
+ }
812
+
778
813
  /**
779
814
  * Changes any combination of `center`, `zoom`, `bearing`, `pitch`, and `padding` with an animated transition
780
815
  * between old and new values. The map will retain its current values for any
@@ -1273,19 +1308,10 @@ function addAssertions(camera: Camera) { //eslint-disable-line
1273
1308
  inProgress[name] = false;
1274
1309
 
1275
1310
  camera.on(`${name}start`, () => {
1276
- assert(!inProgress[name], `"${name}start" fired twice without a "${name}end"`);
1277
1311
  inProgress[name] = true;
1278
- assert(inProgress.move);
1279
- });
1280
-
1281
- camera.on(name, () => {
1282
- assert(inProgress[name]);
1283
- assert(inProgress.move);
1284
1312
  });
1285
1313
 
1286
1314
  camera.on(`${name}end`, () => {
1287
- assert(inProgress.move);
1288
- assert(inProgress[name]);
1289
1315
  inProgress[name] = false;
1290
1316
  });
1291
1317
  });
@@ -1,6 +1,7 @@
1
1
  import AttributionControl from './attribution_control';
2
2
  import {createMap as globalCreateMap, setWebGlContext, setPerformance, setMatchMedia} from '../../util/test/util';
3
3
  import simulate from '../../../test/unit/lib/simulate_interaction';
4
+ import {fakeServer} from 'nise';
4
5
 
5
6
  function createMap() {
6
7
 
@@ -272,6 +273,67 @@ describe('AttributionControl', () => {
272
273
  });
273
274
  });
274
275
 
276
+ test('does not show attributions for sources that are used for terrain when they are not in use', done => {
277
+ global.fetch = null;
278
+ const server = fakeServer.create();
279
+ server.respondWith('/source.json', JSON.stringify({
280
+ minzoom: 5,
281
+ maxzoom: 12,
282
+ attribution: 'Terrain',
283
+ tiles: ['http://example.com/{z}/{x}/{y}.pngraw'],
284
+ bounds: [-47, -7, -45, -5]
285
+ }));
286
+
287
+ const attribution = new AttributionControl();
288
+ map.addControl(attribution);
289
+
290
+ map.on('load', () => {
291
+ map.addSource('1', {type: 'raster-dem', url: '/source.json'});
292
+ server.respond();
293
+ });
294
+
295
+ let times = 0;
296
+ map.on('data', (e) => {
297
+ if (e.dataType === 'source' && e.sourceDataType === 'visibility') {
298
+ if (++times === 1) {
299
+ expect(attribution._innerContainer.innerHTML).toBe('');
300
+ done();
301
+ }
302
+ }
303
+ });
304
+ });
305
+
306
+ test('shows attributions for sources that are used for terrain', done => {
307
+ global.fetch = null;
308
+ const server = fakeServer.create();
309
+ server.respondWith('/source.json', JSON.stringify({
310
+ minzoom: 5,
311
+ maxzoom: 12,
312
+ attribution: 'Terrain',
313
+ tiles: ['http://example.com/{z}/{x}/{y}.pngraw'],
314
+ bounds: [-47, -7, -45, -5]
315
+ }));
316
+
317
+ const attribution = new AttributionControl();
318
+ map.addControl(attribution);
319
+
320
+ map.on('load', () => {
321
+ map.addSource('1', {type: 'raster-dem', url: '/source.json'});
322
+ server.respond();
323
+ map.setTerrain({source: '1'});
324
+ });
325
+
326
+ let times = 0;
327
+ map.on('data', (e) => {
328
+ if (e.dataType === 'source' && e.sourceDataType === 'visibility') {
329
+ if (++times === 1) {
330
+ expect(attribution._innerContainer.innerHTML).toBe('Terrain');
331
+ done();
332
+ }
333
+ }
334
+ });
335
+ });
336
+
275
337
  test('toggles attributions for sources whose visibility changes when zooming', done => {
276
338
  const attribution = new AttributionControl();
277
339
  map.addControl(attribution);
@@ -63,6 +63,7 @@ class AttributionControl implements IControl {
63
63
 
64
64
  this._map.on('styledata', this._updateData);
65
65
  this._map.on('sourcedata', this._updateData);
66
+ this._map.on('terrain', this._updateData);
66
67
  this._map.on('resize', this._updateCompact);
67
68
  this._map.on('drag', this._updateCompactMinimize);
68
69
 
@@ -74,6 +75,7 @@ class AttributionControl implements IControl {
74
75
 
75
76
  this._map.off('styledata', this._updateData);
76
77
  this._map.off('sourcedata', this._updateData);
78
+ this._map.off('terrain', this._updateData);
77
79
  this._map.off('resize', this._updateCompact);
78
80
  this._map.off('drag', this._updateCompactMinimize);
79
81
 
@@ -101,7 +103,7 @@ class AttributionControl implements IControl {
101
103
  }
102
104
 
103
105
  _updateData(e: any) {
104
- if (e && (e.sourceDataType === 'metadata' || e.sourceDataType === 'visibility' || e.dataType === 'style')) {
106
+ if (e && (e.sourceDataType === 'metadata' || e.sourceDataType === 'visibility' || e.dataType === 'style' || e.type === 'terrain')) {
105
107
  this._updateAttributions();
106
108
  }
107
109
  }
@@ -131,7 +133,7 @@ class AttributionControl implements IControl {
131
133
  const sourceCaches = this._map.style.sourceCaches;
132
134
  for (const id in sourceCaches) {
133
135
  const sourceCache = sourceCaches[id];
134
- if (sourceCache.used) {
136
+ if (sourceCache.used || sourceCache.usedForTerrain) {
135
137
  const source = sourceCache.getSource();
136
138
  if (source.attribution && attributions.indexOf(source.attribution) < 0) {
137
139
  attributions.push(source.attribution);
@@ -1,7 +1,6 @@
1
1
  import {Event, Evented} from '../../util/evented';
2
2
  import DOM from '../../util/dom';
3
3
  import {extend, bindAll, warnOnce} from '../../util/util';
4
- import assert from 'assert';
5
4
  import LngLat from '../../geo/lng_lat';
6
5
  import Marker from '../marker';
7
6
 
@@ -195,7 +194,7 @@ class GeolocateControl extends Evented implements IControl {
195
194
  case 'ACTIVE_ERROR':
196
195
  break;
197
196
  default:
198
- assert(false, `Unexpected watchState ${this._watchState}`);
197
+ throw new Error(`Unexpected watchState ${this._watchState}`);
199
198
  }
200
199
  }
201
200
 
@@ -244,7 +243,7 @@ class GeolocateControl extends Evented implements IControl {
244
243
  this._geolocateButton.classList.add('maplibregl-ctrl-geolocate-background', 'mapboxgl-ctrl-geolocate-background');
245
244
  break;
246
245
  default:
247
- assert(false, `Unexpected watchState ${this._watchState}`);
246
+ throw new Error(`Unexpected watchState ${this._watchState}`);
248
247
  }
249
248
  }
250
249
 
@@ -306,7 +305,6 @@ class GeolocateControl extends Evented implements IControl {
306
305
  }
307
306
 
308
307
  _updateCircleRadius() {
309
- assert(this._circleElement);
310
308
  const y = this._map._container.clientHeight / 2;
311
309
  const a = this._map.unproject([0, y]);
312
310
  const b = this._map.unproject([1, y]);
@@ -485,7 +483,7 @@ class GeolocateControl extends Evented implements IControl {
485
483
  this.fire(new Event('trackuserlocationstart'));
486
484
  break;
487
485
  default:
488
- assert(false, `Unexpected watchState ${this._watchState}`);
486
+ throw new Error(`Unexpected watchState ${this._watchState}`);
489
487
  }
490
488
 
491
489
  // incoming state setup
@@ -500,7 +498,7 @@ class GeolocateControl extends Evented implements IControl {
500
498
  case 'OFF':
501
499
  break;
502
500
  default:
503
- assert(false, `Unexpected watchState ${this._watchState}`);
501
+ throw new Error(`Unexpected watchState ${this._watchState}`);
504
502
  }
505
503
 
506
504
  // manage geolocation.watchPosition / geolocation.clearWatch
@@ -0,0 +1,60 @@
1
+ import TerrainControl from './terrain_control';
2
+ import {createMap as globalCreateMap, setWebGlContext, setPerformance, setMatchMedia} from '../../util/test/util';
3
+
4
+ function createMap() {
5
+
6
+ return globalCreateMap({
7
+ attributionControl: false,
8
+ style: {
9
+ version: 8,
10
+ sources: {
11
+ terrain: {
12
+ minzoom: 5,
13
+ maxzoom: 12,
14
+ attribution: 'MapLibre',
15
+ tiles: ['http://example.com/{z}/{x}/{y}.pngraw'],
16
+ bounds: [-47, -7, -45, -5]
17
+ },
18
+ },
19
+ layers: [],
20
+ owner: 'mapblibre',
21
+ id: 'demotiles',
22
+ },
23
+ hash: true
24
+ }, undefined);
25
+ }
26
+
27
+ let map;
28
+
29
+ beforeEach(() => {
30
+ setWebGlContext();
31
+ setPerformance();
32
+ setMatchMedia();
33
+ map = createMap();
34
+ });
35
+
36
+ afterEach(() => {
37
+ map.remove();
38
+ });
39
+
40
+ describe('TerrainControl', () => {
41
+ test('appears in top-right by default', () => {
42
+ map.addControl(new TerrainControl({source: 'terrain'}));
43
+
44
+ expect(
45
+ map.getContainer().querySelectorAll('.maplibregl-ctrl-top-right .maplibregl-ctrl-terrain')
46
+ ).toHaveLength(1);
47
+ });
48
+
49
+ test('appears in the position specified by the position option', () => {
50
+ map.addControl(new TerrainControl({source: 'terrain'}), 'bottom-right');
51
+
52
+ expect(
53
+ map.getContainer().querySelectorAll('.maplibregl-ctrl-bottom-right .maplibregl-ctrl-terrain')
54
+ ).toHaveLength(1);
55
+
56
+ expect(
57
+ map.getContainer().querySelectorAll('.maplibregl-ctrl-top-right .maplibregl-ctrl-terrain')
58
+ ).toHaveLength(0);
59
+ });
60
+ });
@@ -1,8 +1,7 @@
1
- import assert from 'assert';
2
1
  import Point from '@mapbox/point-geometry';
3
2
 
4
3
  export function indexTouches(touches: Array<Touch>, points: Array<Point>) {
5
- assert(touches.length === points.length);
4
+ if (touches.length !== points.length) throw new Error(`The number of touches and points are not equal - touches ${touches.length}, points ${points.length}`);
6
5
  const obj = {};
7
6
  for (let i = 0; i < touches.length; i++) {
8
7
  obj[touches[i].identifier] = points[i];
@@ -4,7 +4,7 @@ import simulate from '../../../test/unit/lib/simulate_interaction';
4
4
  import {setMatchMedia, setPerformance, setWebGlContext} from '../../util/test/util';
5
5
 
6
6
  function createMap() {
7
- return new Map({interactive: false, container: DOM.create('div', '', window.document.body)} as any as MapOptions);
7
+ return new Map({interactive: true, container: DOM.create('div', '', window.document.body)} as any as MapOptions);
8
8
  }
9
9
 
10
10
  beforeEach(() => {
@@ -93,4 +93,68 @@ describe('map events', () => {
93
93
 
94
94
  map.remove();
95
95
  });
96
+
97
+ test('MapEvent handler fires contextmenu on MacOS/Linux, but only at mouseup', () => {
98
+ const map = createMap();
99
+ const target = map.getCanvas();
100
+ map.dragPan.enable();
101
+
102
+ const contextmenu = jest.fn();
103
+
104
+ map.on('contextmenu', contextmenu);
105
+
106
+ simulate.mousedown(map.getCanvas(), {target, button: 2, clientX: 10, clientY: 10});
107
+ simulate.contextmenu(map.getCanvas(), {target}); // triggered immediately after mousedown
108
+ expect(contextmenu).toHaveBeenCalledTimes(0);
109
+ simulate.mouseup(map.getCanvas(), {target, button: 2, clientX: 10, clientY: 10});
110
+ expect(contextmenu).toHaveBeenCalledTimes(1);
111
+ });
112
+
113
+ test('MapEvent handler does not fire contextmenu on MacOS/Linux, when moved', () => {
114
+ const map = createMap();
115
+ const target = map.getCanvas();
116
+ map.dragPan.enable();
117
+
118
+ const contextmenu = jest.fn();
119
+
120
+ map.on('contextmenu', contextmenu);
121
+
122
+ simulate.mousedown(map.getCanvas(), {target, button: 2, clientX: 10, clientY: 10});
123
+ simulate.contextmenu(map.getCanvas(), {target}); // triggered immediately after mousedown
124
+ simulate.mousemove(map.getCanvas(), {target, buttons: 2, clientX: 50, clientY: 10});
125
+ simulate.mouseup(map.getCanvas(), {target, button: 2, clientX: 70, clientY: 10});
126
+ expect(contextmenu).toHaveBeenCalledTimes(0);
127
+ });
128
+
129
+ test('MapEvent handler fires contextmenu on Windows', () => {
130
+ const map = createMap();
131
+ const target = map.getCanvas();
132
+ map.dragPan.enable();
133
+
134
+ const contextmenu = jest.fn();
135
+
136
+ map.on('contextmenu', contextmenu);
137
+
138
+ simulate.mousedown(map.getCanvas(), {target, button: 2, clientX: 10, clientY: 10});
139
+ simulate.mouseup(map.getCanvas(), {target, button: 2, clientX: 10, clientY: 10});
140
+ expect(contextmenu).toHaveBeenCalledTimes(0);
141
+ simulate.contextmenu(map.getCanvas(), {target, button: 2, clientX: 10, clientY: 10}); // triggered only after mouseup
142
+ expect(contextmenu).toHaveBeenCalledTimes(1);
143
+ });
144
+
145
+ test('MapEvent handler does not fire contextmenu on Windows, when moved', () => {
146
+ const map = createMap();
147
+ const target = map.getCanvas();
148
+ map.dragPan.enable();
149
+
150
+ const contextmenu = jest.fn();
151
+
152
+ map.on('contextmenu', contextmenu);
153
+
154
+ simulate.mousedown(map.getCanvas(), {target, button: 2, clientX: 10, clientY: 10});
155
+ simulate.mousemove(map.getCanvas(), {target, buttons: 2, clientX: 50, clientY: 10});
156
+ simulate.mouseup(map.getCanvas(), {target, button: 2, clientX: 50, clientY: 10});
157
+ simulate.contextmenu(map.getCanvas(), {target, button: 2, clientX: 10, clientY: 10}); // triggered only after mouseup
158
+ expect(contextmenu).toHaveBeenCalledTimes(0);
159
+ });
96
160
  });
@@ -104,6 +104,7 @@ export class MapEventHandler implements Handler {
104
104
  export class BlockableMapEventHandler {
105
105
  _map: Map;
106
106
  _delayContextMenu: boolean;
107
+ _ignoreContextMenu: boolean;
107
108
  _contextMenuEvent: MouseEvent;
108
109
 
109
110
  constructor(map: Map) {
@@ -112,6 +113,7 @@ export class BlockableMapEventHandler {
112
113
 
113
114
  reset() {
114
115
  this._delayContextMenu = false;
116
+ this._ignoreContextMenu = true;
115
117
  delete this._contextMenuEvent;
116
118
  }
117
119
 
@@ -122,6 +124,7 @@ export class BlockableMapEventHandler {
122
124
 
123
125
  mousedown() {
124
126
  this._delayContextMenu = true;
127
+ this._ignoreContextMenu = false;
125
128
  }
126
129
 
127
130
  mouseup() {
@@ -135,7 +138,7 @@ export class BlockableMapEventHandler {
135
138
  if (this._delayContextMenu) {
136
139
  // Mac: contextmenu fired on mousedown; we save it until mouseup for consistency's sake
137
140
  this._contextMenuEvent = e;
138
- } else {
141
+ } else if (!this._ignoreContextMenu) {
139
142
  // Windows: contextmenu fired on mouseup, so fire event now
140
143
  this._map.fire(new MapMouseEvent(e.type, this._map, e));
141
144
  }
@@ -1,4 +1,3 @@
1
- import assert from 'assert';
2
1
  import DOM from '../../util/dom';
3
2
 
4
3
  import {ease as _ease, bindAll, bezier} from '../../util/util';
@@ -282,7 +281,6 @@ class ScrollZoomHandler {
282
281
  let finished = false;
283
282
  let zoom;
284
283
  if (this._type === 'wheel' && startZoom && easing) {
285
- assert(easing && typeof startZoom === 'number');
286
284
 
287
285
  const t = Math.min((browser.now() - this._lastWheelEventTime) / 200, 1);
288
286
  const k = easing(t);
@@ -19,7 +19,6 @@ import TouchZoomRotateHandler from './handler/shim/touch_zoom_rotate';
19
19
  import {bindAll, extend} from '../util/util';
20
20
  import Point from '@mapbox/point-geometry';
21
21
  import LngLat from '../geo/lng_lat';
22
- import assert from 'assert';
23
22
 
24
23
  export type InputEvent = MouseEvent | TouchEvent | KeyboardEvent | WheelEvent;
25
24
 
@@ -299,7 +298,6 @@ class HandlerManager {
299
298
  }
300
299
 
301
300
  this._updatingCamera = true;
302
- assert(e.timeStamp !== undefined);
303
301
 
304
302
  const inputEvent = e.type === 'renderFrame' ? undefined : (e as any as InputEvent);
305
303