maplibre-gl 2.1.8 → 2.2.0-pre.2

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 (121) hide show
  1. package/build/generate-style-spec.ts +2 -0
  2. package/dist/maplibre-gl-csp-worker.js +1 -1
  3. package/dist/maplibre-gl-csp-worker.js.map +1 -1
  4. package/dist/maplibre-gl-csp.js +1 -1
  5. package/dist/maplibre-gl-csp.js.map +1 -1
  6. package/dist/maplibre-gl-dev.js +1517 -290
  7. package/dist/maplibre-gl.css +1 -1
  8. package/dist/maplibre-gl.d.ts +3476 -3112
  9. package/dist/maplibre-gl.js +4 -4
  10. package/dist/maplibre-gl.js.map +1 -1
  11. package/dist/package.json +1 -1
  12. package/package.json +15 -15
  13. package/src/css/maplibre-gl.css +20 -0
  14. package/src/css/svg/maplibregl-ctrl-terrain.svg +7 -0
  15. package/src/data/bucket/fill_extrusion_attributes.ts +4 -0
  16. package/src/data/bucket/fill_extrusion_bucket.ts +28 -4
  17. package/src/data/dem_data.test.ts +14 -1
  18. package/src/data/dem_data.ts +13 -0
  19. package/src/geo/transform.test.ts +56 -1
  20. package/src/geo/transform.ts +199 -47
  21. package/src/index.ts +2 -0
  22. package/src/render/draw_background.ts +6 -6
  23. package/src/render/draw_circle.ts +6 -2
  24. package/src/render/draw_collision_debug.ts +5 -1
  25. package/src/render/draw_debug.ts +5 -5
  26. package/src/render/draw_fill.ts +5 -2
  27. package/src/render/draw_fill_extrusion.ts +3 -2
  28. package/src/render/draw_heatmap.ts +2 -3
  29. package/src/render/draw_hillshade.ts +8 -7
  30. package/src/render/draw_line.ts +7 -5
  31. package/src/render/draw_raster.ts +8 -6
  32. package/src/render/draw_symbol.test.ts +34 -10
  33. package/src/render/draw_symbol.ts +23 -12
  34. package/src/render/draw_terrain.ts +123 -0
  35. package/src/render/painter.ts +52 -14
  36. package/src/render/program/hillshade_program.ts +7 -2
  37. package/src/render/program/line_program.ts +24 -10
  38. package/src/render/program/program_uniforms.ts +5 -1
  39. package/src/render/program/terrain_program.ts +83 -0
  40. package/src/render/program.ts +29 -5
  41. package/src/render/render_to_texture.test.ts +41 -0
  42. package/src/render/render_to_texture.ts +154 -0
  43. package/src/render/terrain.test.ts +53 -0
  44. package/src/render/terrain.ts +369 -0
  45. package/src/render/vertex_array_object.ts +21 -4
  46. package/src/shaders/_prelude.vertex.glsl +76 -0
  47. package/src/shaders/_prelude.vertex.glsl.g.ts +1 -1
  48. package/src/shaders/circle.fragment.glsl +2 -1
  49. package/src/shaders/circle.fragment.glsl.g.ts +1 -1
  50. package/src/shaders/circle.vertex.glsl +6 -2
  51. package/src/shaders/circle.vertex.glsl.g.ts +1 -1
  52. package/src/shaders/collision_box.vertex.glsl +1 -1
  53. package/src/shaders/collision_box.vertex.glsl.g.ts +1 -1
  54. package/src/shaders/debug.vertex.glsl +1 -1
  55. package/src/shaders/debug.vertex.glsl.g.ts +1 -1
  56. package/src/shaders/fill_extrusion.vertex.glsl +16 -2
  57. package/src/shaders/fill_extrusion.vertex.glsl.g.ts +1 -1
  58. package/src/shaders/fill_extrusion_pattern.vertex.glsl +15 -2
  59. package/src/shaders/fill_extrusion_pattern.vertex.glsl.g.ts +1 -1
  60. package/src/shaders/line.vertex.glsl +7 -3
  61. package/src/shaders/line.vertex.glsl.g.ts +1 -1
  62. package/src/shaders/line_gradient.vertex.glsl +7 -3
  63. package/src/shaders/line_gradient.vertex.glsl.g.ts +1 -1
  64. package/src/shaders/line_pattern.vertex.glsl +7 -3
  65. package/src/shaders/line_pattern.vertex.glsl.g.ts +1 -1
  66. package/src/shaders/line_sdf.vertex.glsl +7 -4
  67. package/src/shaders/line_sdf.vertex.glsl.g.ts +1 -1
  68. package/src/shaders/shaders.ts +11 -1
  69. package/src/shaders/symbol_icon.vertex.glsl +8 -8
  70. package/src/shaders/symbol_icon.vertex.glsl.g.ts +1 -1
  71. package/src/shaders/symbol_sdf.vertex.glsl +8 -5
  72. package/src/shaders/symbol_sdf.vertex.glsl.g.ts +1 -1
  73. package/src/shaders/symbol_text_and_icon.vertex.glsl +8 -5
  74. package/src/shaders/symbol_text_and_icon.vertex.glsl.g.ts +1 -1
  75. package/src/shaders/terrain.fragment.glsl +7 -0
  76. package/src/shaders/terrain.fragment.glsl.g.ts +2 -0
  77. package/src/shaders/terrain.vertex.glsl +12 -0
  78. package/src/shaders/terrain.vertex.glsl.g.ts +2 -0
  79. package/src/shaders/terrain_coords.fragment.glsl +11 -0
  80. package/src/shaders/terrain_coords.fragment.glsl.g.ts +2 -0
  81. package/src/shaders/terrain_depth.fragment.glsl +15 -0
  82. package/src/shaders/terrain_depth.fragment.glsl.g.ts +2 -0
  83. package/src/source/canvas_source.test.ts +1 -1
  84. package/src/source/geojson_source.test.ts +25 -0
  85. package/src/source/geojson_source.ts +1 -8
  86. package/src/source/geojson_worker_source.test.ts +19 -23
  87. package/src/source/geojson_worker_source.ts +19 -70
  88. package/src/source/raster_dem_tile_source.ts +4 -3
  89. package/src/source/raster_dem_tile_worker_source.ts +0 -1
  90. package/src/source/source_cache.test.ts +83 -0
  91. package/src/source/source_cache.ts +72 -11
  92. package/src/source/terrain_source_cache.test.ts +89 -0
  93. package/src/source/terrain_source_cache.ts +201 -0
  94. package/src/source/tile.ts +15 -0
  95. package/src/source/tile_id.ts +9 -0
  96. package/src/style/pauseable_placement.ts +3 -1
  97. package/src/style/style.test.ts +16 -0
  98. package/src/style/style.ts +57 -3
  99. package/src/style/validate_style.ts +2 -0
  100. package/src/style-spec/CHANGELOG.md +6 -0
  101. package/src/style-spec/error/validation_error.ts +1 -1
  102. package/src/style-spec/package.json +2 -2
  103. package/src/style-spec/reference/v8.json +42 -0
  104. package/src/style-spec/types.g.ts +7 -0
  105. package/src/style-spec/validate/validate.ts +2 -0
  106. package/src/style-spec/validate/validate_terrain.test.ts +46 -0
  107. package/src/style-spec/validate/validate_terrain.ts +41 -0
  108. package/src/style-spec/validate_style.min.ts +2 -0
  109. package/src/style-spec/validate_style.ts +1 -0
  110. package/src/symbol/collision_index.ts +28 -12
  111. package/src/symbol/placement.ts +24 -9
  112. package/src/symbol/projection.ts +42 -27
  113. package/src/ui/camera.ts +2 -0
  114. package/src/ui/control/terrain_control.ts +77 -0
  115. package/src/ui/default_locale.ts +3 -2
  116. package/src/ui/events.ts +18 -3
  117. package/src/ui/handler_manager.ts +33 -3
  118. package/src/ui/map.ts +36 -6
  119. package/src/ui/marker.test.ts +21 -0
  120. package/src/ui/marker.ts +14 -0
  121. package/src/util/primitives.ts +14 -11
@@ -21,6 +21,8 @@ import type Transform from '../geo/transform';
21
21
  import type {TileState} from './tile';
22
22
  import type {Callback} from '../types/callback';
23
23
  import type {SourceSpecification} from '../style-spec/types.g';
24
+ import type {MapSourceDataEvent} from '../ui/events';
25
+ import Terrain from '../render/terrain';
24
26
 
25
27
  /**
26
28
  * `SourceCache` is responsible for
@@ -56,7 +58,10 @@ class SourceCache extends Evented {
56
58
  _shouldReloadOnResume: boolean;
57
59
  _coveredTiles: {[_: string]: boolean};
58
60
  transform: Transform;
61
+ terrain: Terrain;
59
62
  used: boolean;
63
+ usedForTerrain: boolean;
64
+ tileSize: number;
60
65
  _state: SourceFeatureState;
61
66
  _loadedParentTiles: {[_: string]: Tile};
62
67
 
@@ -68,7 +73,7 @@ class SourceCache extends Evented {
68
73
  this.id = id;
69
74
  this.dispatcher = dispatcher;
70
75
 
71
- this.on('data', (e) => {
76
+ this.on('data', (e: MapSourceDataEvent) => {
72
77
  // this._sourceLoaded signifies that the TileJSON is loaded if applicable.
73
78
  // if the source type does not come with a TileJSON, the flag signifies the
74
79
  // source data has loaded (i.e geojson has been tiled on the worker and is ready)
@@ -79,7 +84,7 @@ class SourceCache extends Evented {
79
84
  if (this._sourceLoaded && !this._paused && e.dataType === 'source' && e.sourceDataType === 'content') {
80
85
  this.reload();
81
86
  if (this.transform) {
82
- this.update(this.transform);
87
+ this.update(this.transform, this.terrain);
83
88
  }
84
89
  }
85
90
  });
@@ -152,7 +157,7 @@ class SourceCache extends Evented {
152
157
  this._paused = false;
153
158
  this._shouldReloadOnResume = false;
154
159
  if (shouldReload) this.reload();
155
- if (this.transform) this.update(this.transform);
160
+ if (this.transform) this.update(this.transform, this.terrain);
156
161
  }
157
162
 
158
163
  _loadTile(tile: Tile, callback: Callback<void>) {
@@ -263,7 +268,7 @@ class SourceCache extends Evented {
263
268
  tile.state = 'errored';
264
269
  if ((err as any).status !== 404) this._source.fire(new ErrorEvent(err, {tile}));
265
270
  // continue to try loading parent/children tiles if a tile doesn't exist (404)
266
- else this.update(this.transform);
271
+ else this.update(this.transform, this.terrain);
267
272
  return;
268
273
  }
269
274
 
@@ -295,6 +300,7 @@ class SourceCache extends Evented {
295
300
 
296
301
  function fillBorder(tile, borderTile) {
297
302
  tile.needsHillshadePrepare = true;
303
+ tile.needsTerrainPrepare = true;
298
304
  let dx = borderTile.tileID.canonical.x - tile.tileID.canonical.x;
299
305
  const dy = borderTile.tileID.canonical.y - tile.tileID.canonical.y;
300
306
  const dim = Math.pow(2, tile.tileID.canonical.z);
@@ -486,8 +492,9 @@ class SourceCache extends Evented {
486
492
  * are inside the viewport.
487
493
  * @private
488
494
  */
489
- update(transform: Transform) {
495
+ update(transform: Transform, terrain: Terrain) {
490
496
  this.transform = transform;
497
+ this.terrain = terrain;
491
498
  if (!this._sourceLoaded || this._paused) { return; }
492
499
 
493
500
  this.updateCacheSize(transform);
@@ -498,18 +505,19 @@ class SourceCache extends Evented {
498
505
  this._coveredTiles = {};
499
506
 
500
507
  let idealTileIDs;
501
- if (!this.used) {
508
+ if (!this.used && !this.usedForTerrain) {
502
509
  idealTileIDs = [];
503
510
  } else if (this._source.tileID) {
504
511
  idealTileIDs = transform.getVisibleUnwrappedCoordinates(this._source.tileID)
505
512
  .map((unwrapped) => new OverscaledTileID(unwrapped.canonical.z, unwrapped.wrap, unwrapped.canonical.z, unwrapped.canonical.x, unwrapped.canonical.y));
506
513
  } else {
507
514
  idealTileIDs = transform.coveringTiles({
508
- tileSize: this._source.tileSize,
515
+ tileSize: this.usedForTerrain ? this.tileSize : this._source.tileSize,
509
516
  minzoom: this._source.minzoom,
510
517
  maxzoom: this._source.maxzoom,
511
- roundZoom: this._source.roundZoom,
512
- reparseOverscaled: this._source.reparseOverscaled
518
+ roundZoom: this.usedForTerrain ? false : this._source.roundZoom,
519
+ reparseOverscaled: this._source.reparseOverscaled,
520
+ terrain
513
521
  });
514
522
 
515
523
  if (this._source.hasTile) {
@@ -522,6 +530,21 @@ class SourceCache extends Evented {
522
530
  const minCoveringZoom = Math.max(zoom - SourceCache.maxOverzooming, this._source.minzoom);
523
531
  const maxCoveringZoom = Math.max(zoom + SourceCache.maxUnderzooming, this._source.minzoom);
524
532
 
533
+ // When sourcecache is used for terrain also load parent tiles to avoid flickering when zooming out
534
+ if (this.usedForTerrain) {
535
+ const parents = {};
536
+ for (const tileID of idealTileIDs) {
537
+ if (tileID.canonical.z > this._source.minzoom) {
538
+ const parent = tileID.scaledTo(tileID.canonical.z - 1);
539
+ parents[parent.key] = parent;
540
+ // load very low zoom to calculate tile visability in transform.coveringTiles and high zoomlevels correct
541
+ const parent2 = tileID.scaledTo(Math.max(this._source.minzoom, Math.min(tileID.canonical.z, 5)));
542
+ parents[parent2.key] = parent2;
543
+ }
544
+ }
545
+ idealTileIDs = idealTileIDs.concat(Object.values(parents));
546
+ }
547
+
525
548
  // Retain is a list of tiles that we shouldn't delete, even if they are not
526
549
  // the most ideal tile for the current viewport. This may include tiles like
527
550
  // parent or child tiles that are *already* loaded.
@@ -558,6 +581,44 @@ class SourceCache extends Evented {
558
581
  retain[id] = parentsForFading[id];
559
582
  }
560
583
  }
584
+
585
+ // disable fading logic in terrain3D mode to avoid rendering two tiles on the same place
586
+ if (terrain) {
587
+ const idealRasterTileIDs: {[_: string]: OverscaledTileID} = {};
588
+ const missingTileIDs: {[_: string]: OverscaledTileID} = {};
589
+ for (const tileID of idealTileIDs) {
590
+ if (this._tiles[tileID.key].hasData())
591
+ idealRasterTileIDs[tileID.key] = tileID;
592
+ else
593
+ missingTileIDs[tileID.key] = tileID;
594
+ }
595
+ // search for a complete set of children for each missing tile
596
+ for (const key in missingTileIDs) {
597
+ const children = missingTileIDs[key].children(this._source.maxzoom);
598
+ if (this._tiles[children[0].key] && this._tiles[children[1].key] && this._tiles[children[2].key] && this._tiles[children[3].key]) {
599
+ idealRasterTileIDs[children[0].key] = retain[children[0].key] = children[0];
600
+ idealRasterTileIDs[children[1].key] = retain[children[1].key] = children[1];
601
+ idealRasterTileIDs[children[2].key] = retain[children[2].key] = children[2];
602
+ idealRasterTileIDs[children[3].key] = retain[children[3].key] = children[3];
603
+ delete missingTileIDs[key];
604
+ }
605
+ }
606
+ // search for parent for each missing tile
607
+ for (const key in missingTileIDs) {
608
+ const parent = this.findLoadedParent(missingTileIDs[key], this._source.minzoom);
609
+ if (parent) {
610
+ idealRasterTileIDs[parent.tileID.key] = retain[parent.tileID.key] = parent.tileID;
611
+ // remove idealTiles which would be rendered twice
612
+ for (const key in idealRasterTileIDs) {
613
+ if (idealRasterTileIDs[key].isChildOf(parent.tileID)) delete idealRasterTileIDs[key];
614
+ }
615
+ }
616
+ }
617
+ // cover all tiles which are not needed
618
+ for (const key in this._tiles) {
619
+ if (!idealRasterTileIDs[key]) this._coveredTiles[key] = true;
620
+ }
621
+ }
561
622
  }
562
623
 
563
624
  for (const retainedId in retain) {
@@ -819,8 +880,8 @@ class SourceCache extends Evented {
819
880
  transform.getCameraQueryGeometry(pointQueryGeometry) :
820
881
  pointQueryGeometry;
821
882
 
822
- const queryGeometry = pointQueryGeometry.map((p) => transform.pointCoordinate(p));
823
- const cameraQueryGeometry = cameraPointQueryGeometry.map((p) => transform.pointCoordinate(p));
883
+ const queryGeometry = pointQueryGeometry.map((p: Point) => transform.pointCoordinate(p, this.terrain));
884
+ const cameraQueryGeometry = cameraPointQueryGeometry.map((p: Point) => transform.pointCoordinate(p, this.terrain));
824
885
 
825
886
  const ids = this.getIds();
826
887
 
@@ -0,0 +1,89 @@
1
+ import TerrainSourceCache from './terrain_source_cache';
2
+ import Style from '../style/style';
3
+ import {RequestManager} from '../util/request_manager';
4
+ import Dispatcher from '../util/dispatcher';
5
+ import {fakeServer, FakeServer} from 'nise';
6
+ import Transform from '../geo/transform';
7
+ import {Evented} from '../util/evented';
8
+ import Painter from '../render/painter';
9
+ import Context from '../gl/context';
10
+ import gl from 'gl';
11
+ import RasterDEMTileSource from './raster_dem_tile_source';
12
+
13
+ const context = new Context(gl(10, 10));
14
+ const transform = new Transform();
15
+
16
+ class StubMap extends Evented {
17
+ transform: Transform;
18
+ painter: Painter;
19
+ _requestManager: RequestManager;
20
+
21
+ constructor() {
22
+ super();
23
+ this.transform = transform;
24
+ this._requestManager = {
25
+ transformRequest: (url) => {
26
+ return {url};
27
+ }
28
+ } as any as RequestManager;
29
+ }
30
+ }
31
+
32
+ function createSource(options, transformCallback?) {
33
+ const source = new RasterDEMTileSource('id', options, {send() {}} as any as Dispatcher, null);
34
+ source.onAdd({
35
+ transform,
36
+ _getMapId: () => 1,
37
+ _requestManager: new RequestManager(transformCallback),
38
+ getPixelRatio() { return 1; }
39
+ } as any);
40
+
41
+ source.on('error', (e) => {
42
+ throw e.error;
43
+ });
44
+
45
+ return source;
46
+ }
47
+
48
+ describe('TerrainSourceCache', () => {
49
+ let server: FakeServer;
50
+ let style: Style;
51
+ let tsc: TerrainSourceCache;
52
+
53
+ beforeAll(done => {
54
+ global.fetch = null;
55
+ server = fakeServer.create();
56
+ server.respondWith('/source.json', JSON.stringify({
57
+ minzoom: 0,
58
+ maxzoom: 22,
59
+ attribution: 'MapLibre',
60
+ tiles: ['http://example.com/{z}/{x}/{y}.pngraw'],
61
+ bounds: [-47, -7, -45, -5]
62
+ }));
63
+ const map = new StubMap();
64
+ style = new Style(map as any);
65
+ style.map.painter = {style, context} as any;
66
+ style.on('style.load', () => {
67
+ const source = createSource({url: '/source.json'});
68
+ server.respond();
69
+ style.addSource('terrain', source as any);
70
+ tsc = new TerrainSourceCache(style.sourceCaches.terrain);
71
+ done();
72
+ });
73
+ style.loadJSON({
74
+ 'version': 8,
75
+ 'sources': {},
76
+ 'layers': []
77
+ });
78
+ });
79
+
80
+ afterAll(() => {
81
+ server.restore();
82
+ });
83
+
84
+ test('#constructor', () => {
85
+ expect(tsc.sourceCache.usedForTerrain).toBeTruthy();
86
+ expect(tsc.sourceCache.tileSize).toBe(tsc.tileSize * 2 ** tsc.deltaZoom);
87
+ });
88
+
89
+ });
@@ -0,0 +1,201 @@
1
+ import {OverscaledTileID} from './tile_id';
2
+ import Tile from './tile';
3
+ import EXTENT from '../data/extent';
4
+ import {mat4} from 'gl-matrix';
5
+ import {Evented} from '../util/evented';
6
+ import type Transform from '../geo/transform';
7
+ import type SourceCache from '../source/source_cache';
8
+ import Painter from '../render/painter';
9
+ import Terrain from '../render/terrain';
10
+
11
+ /**
12
+ * This class is a helper for the Terrain-class, it:
13
+ * - loads raster-dem tiles
14
+ * - manages all renderToTexture tiles.
15
+ * - caches previous rendered tiles.
16
+ * - finds all necessary renderToTexture tiles for a OverscaledTileID area
17
+ * - finds the corresponding raster-dem tile for OverscaledTileID
18
+ */
19
+
20
+ export default class TerrainSourceCache extends Evented {
21
+ // source-cache for the raster-dem source.
22
+ sourceCache: SourceCache;
23
+ // stores all render-to-texture tiles.
24
+ _tiles: {[_: string]: Tile};
25
+ // contains a list of tileID-keys for the current scene. (only for performance)
26
+ _renderableTilesKeys: Array<string>;
27
+ // raster-dem-tile for a TileID cache.
28
+ _sourceTileCache: {[_: string]: string};
29
+ // minimum zoomlevel to render the terrain.
30
+ minzoom: number;
31
+ // maximum zoomlevel to render the terrain.
32
+ maxzoom: number;
33
+ // render-to-texture tileSize in scene.
34
+ tileSize: number;
35
+ // raster-dem tiles will load for performance the actualZoom - deltaZoom zoom-level.
36
+ deltaZoom: number;
37
+ // each time a render-to-texture tile is rendered, its tileID.key is stored into this array
38
+ renderHistory: Array<string>;
39
+ // maximal size of render-history
40
+ renderHistorySize: number;
41
+
42
+ constructor(sourceCache: SourceCache) {
43
+ super();
44
+ this.sourceCache = sourceCache;
45
+ this._tiles = {};
46
+ this._renderableTilesKeys = [];
47
+ this._sourceTileCache = {};
48
+ this.renderHistory = [];
49
+ this.minzoom = 0;
50
+ this.maxzoom = 22;
51
+ this.tileSize = 512;
52
+ this.deltaZoom = 1;
53
+ this.renderHistorySize = 150;
54
+ sourceCache.usedForTerrain = true;
55
+ sourceCache.tileSize = this.tileSize * 2 ** this.deltaZoom;
56
+ }
57
+
58
+ destruct() {
59
+ this.sourceCache.usedForTerrain = false;
60
+ this.sourceCache.tileSize = null;
61
+ for (const key in this._tiles) {
62
+ const tile = this._tiles[key];
63
+ tile.textures.forEach(t => t.destroy());
64
+ tile.textures = [];
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Load Terrain Tiles, create internal render-to-texture tiles, free GPU memory.
70
+ * @param {Transform} transform - the operation to do
71
+ * @param {Terrain} terrain - the terrain
72
+ */
73
+ update(transform: Transform, terrain: Terrain): void {
74
+ // load raster-dem tiles for the current scene.
75
+ this.sourceCache.update(transform, terrain);
76
+ // create internal render-to-texture tiles for the current scene.
77
+ this._renderableTilesKeys = [];
78
+ for (const tileID of transform.coveringTiles({
79
+ tileSize: this.tileSize,
80
+ minzoom: this.minzoom,
81
+ maxzoom: this.maxzoom,
82
+ reparseOverscaled: false,
83
+ terrain
84
+ })) {
85
+ this._renderableTilesKeys.push(tileID.key);
86
+ if (!this._tiles[tileID.key]) {
87
+ tileID.posMatrix = new Float64Array(16) as any;
88
+ mat4.ortho(tileID.posMatrix, 0, EXTENT, 0, EXTENT, 0, 1);
89
+ this._tiles[tileID.key] = new Tile(tileID, this.tileSize);
90
+ }
91
+ }
92
+ }
93
+
94
+ /**
95
+ * This method should called before each render-to-texture step to free old cached tiles
96
+ * @param {Painter} painter - the painter
97
+ */
98
+ removeOutdated(painter: Painter) {
99
+ // create lookuptable for actual needed tiles
100
+ const tileIDs = {};
101
+ for (const key of this._renderableTilesKeys) tileIDs[key] = true;
102
+ // remove duplicates from renderHistory
103
+ this.renderHistory = this.renderHistory.filter((i, p) => this.renderHistory.indexOf(i) === p);
104
+ // free (GPU) memory from previously rendered not needed tiles
105
+ while (this.renderHistory.length > this.renderHistorySize) {
106
+ const tile = this.sourceCache._tiles[this.renderHistory.shift()];
107
+ if (tile && !tileIDs[tile.tileID.key]) {
108
+ tile.clearTextures(painter);
109
+ delete this.sourceCache._tiles[tile.tileID.key];
110
+ }
111
+ }
112
+ }
113
+
114
+ /**
115
+ * get a list of tiles, which are loaded and should be rendered in the current scene
116
+ * @returns {Array<Tile>} the renderable tiles
117
+ */
118
+ getRenderableTiles(): Array<Tile> {
119
+ return this._renderableTilesKeys.map(key => this.getTileByID(key));
120
+ }
121
+
122
+ /**
123
+ * get terrain tile by the TileID key
124
+ * @param id - the tile id
125
+ * @returns {Tile} - the tile
126
+ */
127
+ getTileByID(id: string): Tile {
128
+ return this._tiles[id];
129
+ }
130
+
131
+ /**
132
+ * searches for the corresponding current renderable terrain-tiles
133
+ * @param {OverscaledTileID} tileID - the tile to look for
134
+ * @returns {[_:string]: Tile} - the tiles that were found
135
+ */
136
+ getTerrainCoords(tileID: OverscaledTileID): {[_: string]: OverscaledTileID} {
137
+ const coords = {};
138
+ for (const key of this._renderableTilesKeys) {
139
+ const _tileID = this._tiles[key].tileID;
140
+ if (_tileID.canonical.equals(tileID.canonical)) {
141
+ const coord = tileID.clone();
142
+ coord.posMatrix = new Float64Array(16) as any;
143
+ mat4.ortho(coord.posMatrix, 0, EXTENT, 0, EXTENT, 0, 1);
144
+ coords[key] = coord;
145
+ } else if (_tileID.canonical.isChildOf(tileID.canonical)) {
146
+ const coord = tileID.clone();
147
+ coord.posMatrix = new Float64Array(16) as any;
148
+ const dz = _tileID.canonical.z - tileID.canonical.z;
149
+ const dx = _tileID.canonical.x - (_tileID.canonical.x >> dz << dz);
150
+ const dy = _tileID.canonical.y - (_tileID.canonical.y >> dz << dz);
151
+ const size = EXTENT >> dz;
152
+ mat4.ortho(coord.posMatrix, 0, size, 0, size, 0, 1);
153
+ mat4.translate(coord.posMatrix, coord.posMatrix, [-dx * size, -dy * size, 0]);
154
+ coords[key] = coord;
155
+ } else if (tileID.canonical.isChildOf(_tileID.canonical)) {
156
+ const coord = tileID.clone();
157
+ coord.posMatrix = new Float64Array(16) as any;
158
+ const dz = tileID.canonical.z - _tileID.canonical.z;
159
+ const dx = tileID.canonical.x - (tileID.canonical.x >> dz << dz);
160
+ const dy = tileID.canonical.y - (tileID.canonical.y >> dz << dz);
161
+ const size = EXTENT >> dz;
162
+ mat4.ortho(coord.posMatrix, 0, EXTENT, 0, EXTENT, 0, 1);
163
+ mat4.translate(coord.posMatrix, coord.posMatrix, [dx * size, dy * size, 0]);
164
+ mat4.scale(coord.posMatrix, coord.posMatrix, [1 / (2 ** dz), 1 / (2 ** dz), 0]);
165
+ coords[key] = coord;
166
+ }
167
+ }
168
+ return coords;
169
+ }
170
+
171
+ /**
172
+ * find the covering raster-dem tile
173
+ * @param {OverscaledTileID} tileID - the tile to look for
174
+ * @param {boolean} searchForDEM Optinal parameter to search for (parent) souretiles with loaded dem.
175
+ * @returns {Tile} - the tile
176
+ */
177
+ getSourceTile(tileID: OverscaledTileID, searchForDEM?: boolean): Tile {
178
+ const source = this.sourceCache._source;
179
+ let z = tileID.overscaledZ - this.deltaZoom;
180
+ if (z > source.maxzoom) z = source.maxzoom;
181
+ if (z < source.minzoom) return null;
182
+ // cache for tileID to terrain-tileID
183
+ if (!this._sourceTileCache[tileID.key])
184
+ this._sourceTileCache[tileID.key] = tileID.scaledTo(z).key;
185
+ let tile = this.sourceCache.getTileByID(this._sourceTileCache[tileID.key]);
186
+ // during tile-loading phase look if parent tiles (with loaded dem) are available.
187
+ if (!(tile && tile.dem) && searchForDEM)
188
+ while (z > source.minzoom && !(tile && tile.dem))
189
+ tile = this.sourceCache.getTileByID(tileID.scaledTo(z--).key);
190
+ return tile;
191
+ }
192
+
193
+ /**
194
+ * get a list of tiles, loaded after a spezific time. This is used to update depth & coords framebuffers.
195
+ * @param {Date} time - the time
196
+ * @returns {Array<Tile>} - the relevant tiles
197
+ */
198
+ tilesAfterTime(time = Date.now()): Array<Tile> {
199
+ return Object.values(this._tiles).filter(t => t.timeLoaded >= time);
200
+ }
201
+ }
@@ -65,6 +65,7 @@ class Tile {
65
65
  expiredRequestCount: number;
66
66
  state: TileState;
67
67
  timeAdded: any;
68
+ timeLoaded: any;
68
69
  fadeEndTime: any;
69
70
  collisionBoxArray: CollisionBoxArray;
70
71
  redoWhenDone: boolean;
@@ -75,8 +76,10 @@ class Tile {
75
76
 
76
77
  neighboringTiles: any;
77
78
  dem: DEMData;
79
+ demMatrix: mat4;
78
80
  aborted: boolean;
79
81
  needsHillshadePrepare: boolean;
82
+ needsTerrainPrepare: boolean;
80
83
  request: Cancelable;
81
84
  texture: any;
82
85
  fbo: Framebuffer;
@@ -90,6 +93,8 @@ class Tile {
90
93
  hasSymbolBuckets: boolean;
91
94
  hasRTLText: boolean;
92
95
  dependencies: any;
96
+ textures: Array<Texture>;
97
+ textureCoords: {[_: string]: string}; // remeber all coords rendered to textures
93
98
 
94
99
  /**
95
100
  * @param {OverscaledTileID} tileID
@@ -107,6 +112,8 @@ class Tile {
107
112
  this.hasSymbolBuckets = false;
108
113
  this.hasRTLText = false;
109
114
  this.dependencies = {};
115
+ this.textures = [];
116
+ this.textureCoords = {};
110
117
 
111
118
  // Counts the number of times a response was already expired when
112
119
  // received. We're using this to add a delay when making a new request
@@ -129,6 +136,14 @@ class Tile {
129
136
  return this.state === 'errored' || this.state === 'loaded' || this.state === 'reloading';
130
137
  }
131
138
 
139
+ clearTextures(painter: any) {
140
+ if (this.demTexture) painter.saveTileTexture(this.demTexture);
141
+ this.textures.forEach(t => painter.saveTileTexture(t));
142
+ this.demTexture = null;
143
+ this.textures = [];
144
+ this.textureCoords = {};
145
+ }
146
+
132
147
  /**
133
148
  * Given a data object with a 'buffers' property, load it into
134
149
  * this tile's elementGroups and buffers properties and set loaded
@@ -42,6 +42,11 @@ export class CanonicalTileID {
42
42
  .replace(/{bbox-epsg-3857}/g, bbox);
43
43
  }
44
44
 
45
+ isChildOf(parent: CanonicalTileID) {
46
+ const dz = this.z - parent.z;
47
+ return dz > 0 && parent.x === (this.x >> dz) && parent.y === (this.y >> dz);
48
+ }
49
+
45
50
  getTilePoint(coord: MercatorCoordinate) {
46
51
  const tilesAtZoom = Math.pow(2, this.z);
47
52
  return new Point(
@@ -81,6 +86,10 @@ export class OverscaledTileID {
81
86
  this.key = calculateKey(wrap, overscaledZ, z, x, y);
82
87
  }
83
88
 
89
+ clone() {
90
+ return new OverscaledTileID(this.overscaledZ, this.wrap, this.canonical.z, this.canonical.x, this.canonical.y);
91
+ }
92
+
84
93
  equals(id: OverscaledTileID) {
85
94
  return this.overscaledZ === id.overscaledZ && this.wrap === id.wrap && this.canonical.equals(id.canonical);
86
95
  }
@@ -7,6 +7,7 @@ import type StyleLayer from './style_layer';
7
7
  import type SymbolStyleLayer from './style_layer/symbol_style_layer';
8
8
  import type Tile from '../source/tile';
9
9
  import type {BucketPart} from '../symbol/placement';
10
+ import Terrain from '../render/terrain';
10
11
 
11
12
  class LayerPlacement {
12
13
  _sortAcrossTiles: boolean;
@@ -69,6 +70,7 @@ class PauseablePlacement {
69
70
 
70
71
  constructor(
71
72
  transform: Transform,
73
+ terrain: Terrain,
72
74
  order: Array<string>,
73
75
  forceFullPlacement: boolean,
74
76
  showCollisionBoxes: boolean,
@@ -76,7 +78,7 @@ class PauseablePlacement {
76
78
  crossSourceCollisions: boolean,
77
79
  prevPlacement?: Placement
78
80
  ) {
79
- this.placement = new Placement(transform, fadeDuration, crossSourceCollisions, prevPlacement);
81
+ this.placement = new Placement(transform, terrain, fadeDuration, crossSourceCollisions, prevPlacement);
80
82
  this._currentPlacementIndex = order.length - 1;
81
83
  this._forceFullPlacement = forceFullPlacement;
82
84
  this._showCollisionBoxes = showCollisionBoxes;
@@ -379,6 +379,22 @@ describe('Style#loadJSON', () => {
379
379
  style._layers.background.fire(new Event('error', {mapLibre: true}));
380
380
  });
381
381
  });
382
+
383
+ test('sets terrain if defined', (done) => {
384
+ const map = getStubMap();
385
+ const style = new Style(map);
386
+ map.transform.updateElevation = jest.fn();
387
+ style.loadJSON(createStyleJSON({
388
+ sources: {'source-id': createGeoJSONSource()},
389
+ terrain: {source: 'source-id', exaggeration: 0.33}
390
+ }));
391
+
392
+ style.on('style.load', () => {
393
+ expect(style.terrain).toBeDefined();
394
+ expect(map.transform.updateElevation).toHaveBeenCalled();
395
+ done();
396
+ });
397
+ });
382
398
  });
383
399
 
384
400
  describe('Style#_remove', () => {