Skip to content

Commit 6fa592c

Browse files
prozessor13flotherHarelM
authored
correct coveringTiles when terrain is enabled (#1300)
* Fixes #1241 - correct coveringTiles when terrain is enabled * fix lint * Add unit test for calculating min/max elevation (#1316) * Add unit test for calculating min/max elevation * Use correct method name * Move min/max elevation value calc to Terrain class * Alter Terrain.getMinMaxElevation to take tileID arg * Fix unit tests after merge from main * Fix lint * Add render test for 3D terrain (#1320) * Add terrain style spec integration test Tests that the root level terrain object must be in the following form: "terrain": { "source": string, "exaggeration": number, "elevationOffset": number } * Add work-in-progress render test for 3D terrain * Fix terrain render test so it passes The original test was failing because there were no terrain tiles available at the zoom being requested. Now it's testing at zoom 13, which means requesting terrain tiles at zoom 12, which are available to the tests. * Update terrain render test to test fix for #1241 The fix for #1241, commit 352bc03, ensures that elevationOffset is taken into account when gauging which tiles are required to render 3D terrain. If we add an extreme offset then we have a test that fails on the current main branch but passes after the fix. * Add changelog comment Co-authored-by: Matt Riggott <flother@users.noreply.github.com> Co-authored-by: HarelM <harel.mazor@gmail.com>
1 parent c9947fd commit 6fa592c

10 files changed

Lines changed: 221 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
- Handle maxBounds which cross the meridian at longitude ±180° (#1298, #1299)
1111
- Hide arrow displayed in default `summary` styles on the attribution control (#1258)
1212
- Fix memory usage in terrain 3D (#1291, #1302)
13+
- Fix disappearence of closest tiles when 3D terrain is enabled (#1241, #1300)
1314

1415
## 2.2.0-pre.2
1516

src/geo/transform.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -438,12 +438,9 @@ class Transform {
438438
let quadrant = it.aabb.quadrant(i);
439439
if (options.terrain) {
440440
const tileID = new OverscaledTileID(childZ, it.wrap, childZ, childX, childY);
441-
const tile = options.terrain.getTerrainData(tileID).tile;
442-
let minElevation = this.elevation, maxElevation = this.elevation;
443-
if (tile && tile.dem) {
444-
minElevation = tile.dem.min * options.terrain.exaggeration;
445-
maxElevation = tile.dem.max * options.terrain.exaggeration;
446-
}
441+
const minMax = options.terrain.getMinMaxElevation(tileID);
442+
const minElevation = minMax.minElevation ?? this.elevation;
443+
const maxElevation = minMax.maxElevation ?? this.elevation;
447444
quadrant = new Aabb(
448445
[quadrant.min[0], quadrant.min[1], minElevation] as vec3,
449446
[quadrant.max[0], quadrant.max[1], maxElevation] as vec3

src/render/terrain.test.ts

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import {RGBAImage} from '../util/image';
66
import Texture from './texture';
77
import type Style from '../style/style';
88
import type SourceCache from '../source/source_cache';
9+
import {OverscaledTileID} from '../source/tile_id';
910
import type {TerrainSpecification} from '../style-spec/types.g';
11+
import type DEMData from '../data/dem_data';
1012
import TileCache from '../source/tile_cache';
1113
import Tile from '../source/tile';
1214

@@ -26,7 +28,7 @@ describe('Terrain', () => {
2628
} as SourceCache;
2729
const getTileByID = (tileID) : Tile => {
2830
if (tileID !== 'abcd') {
29-
return null;
31+
return null as any as Tile;
3032
}
3133
return {
3234
tileID: {
@@ -36,7 +38,7 @@ describe('Terrain', () => {
3638
z: 0
3739
}
3840
}
39-
};
41+
} as any as Tile;
4042
};
4143
const terrain = new Terrain(style, sourceCache, {} as any as TerrainSpecification);
4244
terrain.sourceCache.getTileByID = getTileByID;
@@ -52,4 +54,102 @@ describe('Terrain', () => {
5254

5355
expect(coordinate).not.toBeNull();
5456
});
57+
58+
test('Calculate tile minimum and maximum elevation', () => {
59+
const tileID = new OverscaledTileID(5, 0, 5, 17, 11);
60+
const tile = new Tile(tileID, 256);
61+
tile.dem = {
62+
min: 0,
63+
max: 100,
64+
getPixels: () => new RGBAImage({width: 1, height: 1}, new Uint8Array(1 * 4)),
65+
getUnpackVector: () => [6553.6, 25.6, 0.1, 10000.0],
66+
} as any as DEMData;
67+
const style = {
68+
map: {
69+
painter: {
70+
context: new Context(gl(1, 1)),
71+
width: 1,
72+
height: 1,
73+
getTileTexture: () => null
74+
}
75+
}
76+
} as any as Style;
77+
const sourceCache = {
78+
_source: {maxzoom: 12},
79+
_cache: {max: 10},
80+
getTileByID: () => {
81+
return tile;
82+
},
83+
} as any as SourceCache;
84+
const terrain = new Terrain(
85+
style,
86+
sourceCache,
87+
{exaggeration: 2, elevationOffset: 50} as any as TerrainSpecification,
88+
);
89+
90+
terrain.sourceCache._tiles[tileID.key] = tile;
91+
const {minElevation, maxElevation} = terrain.getMinMaxElevation(tileID);
92+
93+
expect(minElevation).toBe(100);
94+
expect(maxElevation).toBe(300);
95+
});
96+
97+
test('Return null elevation values when no tile', () => {
98+
const tileID = new OverscaledTileID(5, 0, 5, 17, 11);
99+
const style = {
100+
map: {
101+
painter: {
102+
context: new Context(gl(1, 1)),
103+
width: 1,
104+
height: 1,
105+
}
106+
}
107+
} as any as Style;
108+
const sourceCache = {
109+
_source: {maxzoom: 12},
110+
_cache: {max: 10},
111+
getTileByID: () => null,
112+
} as any as SourceCache;
113+
const terrain = new Terrain(
114+
style,
115+
sourceCache,
116+
{exaggeration: 2, elevationOffset: 50} as any as TerrainSpecification,
117+
);
118+
119+
const minMaxNoTile = terrain.getMinMaxElevation(tileID);
120+
121+
expect(minMaxNoTile.minElevation).toBeNull();
122+
expect(minMaxNoTile.maxElevation).toBeNull();
123+
});
124+
125+
test('Return null elevation values when no DEM', () => {
126+
const tileID = new OverscaledTileID(5, 0, 5, 17, 11);
127+
const tile = new Tile(tileID, 256);
128+
tile.dem = null as any as DEMData;
129+
const style = {
130+
map: {
131+
painter: {
132+
context: new Context(gl(1, 1)),
133+
width: 1,
134+
height: 1,
135+
}
136+
}
137+
} as any as Style;
138+
const sourceCache = {
139+
_source: {maxzoom: 12},
140+
_cache: {max: 10},
141+
getTileByID: () => {
142+
return tile;
143+
},
144+
} as any as SourceCache;
145+
const terrain = new Terrain(
146+
style,
147+
sourceCache,
148+
{exaggeration: 2, elevationOffset: 50} as any as TerrainSpecification,
149+
);
150+
const minMaxNoDEM = terrain.getMinMaxElevation(tileID);
151+
152+
expect(minMaxNoDEM.minElevation).toBeNull();
153+
expect(minMaxNoDEM.maxElevation).toBeNull();
154+
});
55155
});

src/render/terrain.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,4 +366,22 @@ export default class Terrain {
366366
return this._mesh;
367367
}
368368

369+
/**
370+
* Get the minimum and maximum elevation contained in a tile. This includes any elevation offset
371+
* and exaggeration included in the terrain.
372+
*
373+
* @param tileID Id of the tile to be used as a source for the min/max elevation
374+
* @returns {Object} Minimum and maximum elevation found in the tile, including the terrain's
375+
* elevation offset and exaggeration
376+
*/
377+
getMinMaxElevation(tileID: OverscaledTileID): {minElevation: number | null; maxElevation: number | null} {
378+
const tile = this.getTerrainData(tileID).tile;
379+
const minMax = {minElevation: null, maxElevation: null};
380+
if (tile && tile.dem) {
381+
minMax.minElevation = (tile.dem.min + this.elevationOffset) * this.exaggeration;
382+
minMax.maxElevation = (tile.dem.max + this.elevationOffset) * this.exaggeration;
383+
}
384+
return minMax;
385+
}
386+
369387
}

src/source/terrain_source_cache.test.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import Painter from '../render/painter';
99
import Context from '../gl/context';
1010
import gl from 'gl';
1111
import RasterDEMTileSource from './raster_dem_tile_source';
12+
import {OverscaledTileID} from './tile_id';
13+
import Tile from './tile';
14+
import DEMData from '../data/dem_data';
1215

1316
const context = new Context(gl(10, 10));
1417
const transform = new Transform();
@@ -54,8 +57,8 @@ describe('TerrainSourceCache', () => {
5457
global.fetch = null;
5558
server = fakeServer.create();
5659
server.respondWith('/source.json', JSON.stringify({
57-
minzoom: 0,
58-
maxzoom: 22,
60+
minzoom: 5,
61+
maxzoom: 12,
5962
attribution: 'MapLibre',
6063
tiles: ['http://example.com/{z}/{x}/{y}.pngraw'],
6164
bounds: [-47, -7, -45, -5]
@@ -86,4 +89,16 @@ describe('TerrainSourceCache', () => {
8689
expect(tsc.sourceCache.tileSize).toBe(tsc.tileSize * 2 ** tsc.deltaZoom);
8790
});
8891

92+
test('#getSourceTile', () => {
93+
const tileID = new OverscaledTileID(5, 0, 5, 17, 11);
94+
const tile = new Tile(tileID, 256);
95+
tile.dem = {} as DEMData;
96+
tsc.sourceCache._tiles[tileID.key] = tile;
97+
expect(tsc.deltaZoom).toBe(1);
98+
expect(tsc.getSourceTile(tileID)).toBeFalsy();
99+
expect(tsc.getSourceTile(tileID.children(12)[0])).toBeTruthy();
100+
expect(tsc.getSourceTile(tileID.children(12)[0].children(12)[0])).toBeFalsy();
101+
expect(tsc.getSourceTile(tileID.children(12)[0].children(12)[0], true)).toBeTruthy();
102+
});
103+
89104
});

src/source/terrain_source_cache.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ export default class TerrainSourceCache extends Evented {
189189
let tile = this.sourceCache.getTileByID(this._sourceTileCache[tileID.key]);
190190
// during tile-loading phase look if parent tiles (with loaded dem) are available.
191191
if (!(tile && tile.dem) && searchForDEM)
192-
while (z > source.minzoom && !(tile && tile.dem))
192+
while (z >= source.minzoom && !(tile && tile.dem))
193193
tile = this.sourceCache.getTileByID(tileID.scaledTo(z--).key);
194194
return tile;
195195
}
57.7 KB
Loading
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"version": 8,
3+
"metadata": {
4+
"test": {
5+
"height": 256,
6+
"width": 256
7+
}
8+
},
9+
"center": [-113.33496, 35.96022],
10+
"zoom": 13,
11+
"pitch": 60,
12+
"sources": {
13+
"terrain": {
14+
"type": "raster-dem",
15+
"tiles": ["local://tiles/{z}-{x}-{y}.terrain.png"],
16+
"maxzoom": 15,
17+
"tileSize": 256
18+
},
19+
"satellite": {
20+
"type": "raster",
21+
"tiles": ["local://tiles/{z}-{x}-{y}.satellite.png"],
22+
"maxzoom": 17,
23+
"tileSize": 256
24+
}
25+
},
26+
"layers": [
27+
{
28+
"id": "background",
29+
"type": "background",
30+
"paint": {
31+
"background-color": "white"
32+
}
33+
},
34+
{
35+
"id": "raster",
36+
"type": "raster",
37+
"source": "satellite",
38+
"paint": {
39+
"raster-opacity": 1.0
40+
}
41+
}
42+
],
43+
"terrain": {
44+
"source": "terrain",
45+
"exaggeration": 2,
46+
"elevationOffset": 2000
47+
}
48+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"version": 8,
3+
"sources": {
4+
"terrain": {
5+
"type": "raster-dem",
6+
"tiles": ["local://tiles/{z}-{x}-{y}.terrain.png"],
7+
"maxzoom": 15,
8+
"tileSize": 256
9+
}
10+
},
11+
"layers": [],
12+
"terrain": {
13+
"source": 123,
14+
"exaggeration": "2",
15+
"elevationOffset": "50"
16+
}
17+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[
2+
{
3+
"message": "source: string expected, number found",
4+
"line": 13
5+
},
6+
{
7+
"message": "exaggeration: number expected, string found",
8+
"line": 14
9+
},
10+
{
11+
"message": "elevationOffset: number expected, string found",
12+
"line": 15
13+
}
14+
]

0 commit comments

Comments
 (0)