-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add raster-dem source type and hillshade layer type (#5286)
* add raster-dem source type and hillshade layer type * add hillshade shaders fix shader * implement raster-dem source loading * render hillshading to map use new output format of browser.getImageData, flow * flow changes 🌊 abandon flow for raster dem worker lint n flow make eslint and flow temporarily happy * add render tests add unit tests update style-spec tests clean * use raster clipping masks for raster-dem sources * use RangeError instead of assertions * add dem_data tests dem_data test dem_data types * scale slope calculations by pixel latitude * use Texture and RenderTexture classes * remove shader debug code * use separate renderpass to render terrain prepare textures * flow happy for raster-dem worker sources * oops * more raster_dem_tile tests * dont hardcode tilesize and test dem serialize/deserialize * remove unnecessary light uniform component, update shader * tweak zoom-based hillshade exaggeration * fix debug page for safari/firefox * use segmented raster mask vertex buffers * follow new linting rule * initially populate tile borders with existing data to avoid border flashes as data backfills * only create one texture/rendertexture per tile * fix dem test * save and reuse tile textures AND fix missing tiles 😂 * use highp precision to fix android rendering * clean up * 📝 add explanatory comments * fix test borked in rebase * fix tile state assignment * refactor based on review comments * ensure DEMData Levels are always square, use a single Level instead of an array * remove hardcoded tilesize * use dim instead of data dimensions * update shaders+spec based on nicki's recs * fix rebase 🤦♀️ * use new Color class output of getPaintValue and updated source schema * check for accurate backfilled data, clarify loop * use new Property interface, fix style-spec error * try LAB color space * use layer-specific lighting for hillshade * use new WebWorkerTransfer update tests for WebWorkerTransfer * clean up and remove unused code * Add HillshadeLayer benchmark * use gl state tracking * address final review comments * add HillshadeStyleLayer to TypedStyleLayer
- Loading branch information
Showing
56 changed files
with
1,876 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<title>Mapbox GL JS debug page</title> | ||
<meta charset='utf-8'> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> | ||
<link rel='stylesheet' href='/dist/mapbox-gl.css' /> | ||
<style> | ||
body { margin: 0; padding: 0; } | ||
html, body, #map { height: 100%; } | ||
</style> | ||
</head> | ||
|
||
<body> | ||
<div id='map'></div> | ||
|
||
<script src='/dist/mapbox-gl-dev.js'></script> | ||
<script src='/debug/access_token_generated.js'></script> | ||
<script> | ||
var hillshadeStyle = { | ||
"version": 8, | ||
"light": { | ||
"position": [1.15, 335, 60] | ||
}, | ||
"sources": { | ||
"streets": { | ||
"url": 'mapbox://mapbox.mapbox-streets-v7', | ||
"type": 'vector' | ||
}, | ||
"rasterTerrain": { | ||
"type": "raster-dem", | ||
"url": "mapbox://mapbox.terrain-rgb" | ||
} | ||
}, | ||
"layers": [ | ||
{ | ||
"id": "background", "type": "background", | ||
"paint": { | ||
"background-color": "#fff" | ||
} | ||
}, | ||
{ | ||
"id":"water", | ||
"type": "fill", | ||
"source":"streets", | ||
"source-layer":"water", | ||
"paint": { | ||
"fill-color":"#7bcdff" | ||
} | ||
}, | ||
{ | ||
"id": "terrain", | ||
"source": "rasterTerrain", | ||
"type": "hillshade" | ||
}, | ||
{ | ||
"id":"water2", | ||
"type": "line", | ||
"source":"streets", | ||
"source-layer":"water", | ||
"paint": { | ||
"line-color":"#4a99ca", | ||
"line-width": 1 | ||
} | ||
}, | ||
] | ||
}; | ||
var map = window.map = new mapboxgl.Map({ | ||
container: 'map', | ||
zoom: 12, | ||
center: [-112.9487, 36.2628], | ||
style: hillshadeStyle, | ||
hash: true | ||
}); | ||
</script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
// @flow | ||
const {RGBAImage} = require('../util/image'); | ||
const util = require('../util/util'); | ||
const {register} = require('../util/web_worker_transfer'); | ||
|
||
export type SerializedDEMData = { | ||
uid: string, | ||
scale: number, | ||
dim: number, | ||
level: ArrayBuffer | ||
}; | ||
|
||
class Level { | ||
dim: number; | ||
border: number; | ||
stride: number; | ||
data: Int32Array; | ||
|
||
constructor(dim: number, border: number, data: ?Int32Array) { | ||
if (dim <= 0) throw new RangeError('Level must have positive dimension'); | ||
this.dim = dim; | ||
this.border = border; | ||
this.stride = this.dim + 2 * this.border; | ||
this.data = data || new Int32Array((this.dim + 2 * this.border) * (this.dim + 2 * this.border)); | ||
} | ||
|
||
set(x: number, y: number, value: number) { | ||
this.data[this._idx(x, y)] = value + 65536; | ||
} | ||
|
||
get(x: number, y: number) { | ||
return this.data[this._idx(x, y)] - 65536; | ||
} | ||
|
||
_idx(x: number, y: number) { | ||
if (x < -this.border || x >= this.dim + this.border || y < -this.border || y >= this.dim + this.border) throw new RangeError('out of range source coordinates for DEM data'); | ||
return (y + this.border) * this.stride + (x + this.border); | ||
} | ||
} | ||
|
||
register(Level); | ||
|
||
// DEMData is a data structure for decoding, backfilling, and storing elevation data for processing in the hillshade shaders | ||
// data can be populated either from a pngraw image tile or from serliazed data sent back from a worker. When data is initially | ||
// loaded from a image tile, we decode the pixel values using the mapbox terrain-rgb tileset decoding formula, but we store the | ||
// elevation data in a Level as an Int32 value. we add 65536 (2^16) to eliminate negative values and enable the use of | ||
// integer overflow when creating the texture used in the hillshadePrepare step. | ||
|
||
// DEMData also handles the backfilling of data from a tile's neighboring tiles. This is necessary because we use a pixel's 8 | ||
// surrounding pixel values to compute the slope at that pixel, and we cannot accurately calculate the slope at pixels on a | ||
// tile's edge without backfilling from neighboring tiles. | ||
|
||
class DEMData { | ||
uid: string; | ||
scale: number; | ||
level: Level; | ||
loaded: boolean; | ||
|
||
constructor(uid: string, scale: ?number, data: ?Level) { | ||
this.uid = uid; | ||
this.scale = scale || 1; | ||
// if no data is provided, use a temporary empty level to satisfy flow | ||
this.level = data || new Level(256, 512); | ||
this.loaded = !!data; | ||
} | ||
|
||
loadFromImage(data: RGBAImage) { | ||
if (data.height !== data.width) throw new RangeError('DEM tiles must be square'); | ||
|
||
// Build level 0 | ||
const level = this.level = new Level(data.width, data.width / 2); | ||
const pixels = data.data; | ||
|
||
// unpack | ||
for (let y = 0; y < level.dim; y++) { | ||
for (let x = 0; x < level.dim; x++) { | ||
const i = y * level.dim + x; | ||
const j = i * 4; | ||
// decoding per https://blog.mapbox.com/global-elevation-data-6689f1d0ba65 | ||
level.set(x, y, this.scale * ((pixels[j] * 256 * 256 + pixels[j + 1] * 256.0 + pixels[j + 2]) / 10.0 - 10000.0)); | ||
} | ||
} | ||
|
||
// in order to avoid flashing seams between tiles, here we are initially populating a 1px border of pixels around the image | ||
// with the data of the nearest pixel from the image. this data is eventually replaced when the tile's neighboring | ||
// tiles are loaded and the accurate data can be backfilled using DEMData#backfillBorder | ||
for (let x = 0; x < level.dim; x++) { | ||
// left vertical border | ||
level.set(-1, x, level.get(0, x)); | ||
// right vertical border | ||
level.set(level.dim, x, level.get(level.dim - 1, x)); | ||
// left horizontal border | ||
level.set(x, -1, level.get(x, 0)); | ||
// right horizontal border | ||
level.set(x, level.dim, level.get(x, level.dim - 1)); | ||
} | ||
// corners | ||
level.set(-1, -1, level.get(0, 0)); | ||
level.set(level.dim, -1, level.get(level.dim - 1, 0)); | ||
level.set(-1, level.dim, level.get(0, level.dim - 1)); | ||
level.set(level.dim, level.dim, level.get(level.dim - 1, level.dim - 1)); | ||
this.loaded = true; | ||
} | ||
|
||
getPixels() { | ||
return RGBAImage.create({width: this.level.dim + 2 * this.level.border, height: this.level.dim + 2 * this.level.border}, new Uint8Array(this.level.data.buffer)); | ||
} | ||
|
||
backfillBorder(borderTile: DEMData, dx: number, dy: number) { | ||
const t = this.level; | ||
const o = borderTile.level; | ||
|
||
if (t.dim !== o.dim) throw new Error('level mismatch (dem dimension)'); | ||
|
||
let _xMin = dx * t.dim, | ||
_xMax = dx * t.dim + t.dim, | ||
_yMin = dy * t.dim, | ||
_yMax = dy * t.dim + t.dim; | ||
|
||
switch (dx) { | ||
case -1: | ||
_xMin = _xMax - 1; | ||
break; | ||
case 1: | ||
_xMax = _xMin + 1; | ||
break; | ||
} | ||
|
||
switch (dy) { | ||
case -1: | ||
_yMin = _yMax - 1; | ||
break; | ||
case 1: | ||
_yMax = _yMin + 1; | ||
break; | ||
} | ||
|
||
const xMin = util.clamp(_xMin, -t.border, t.dim + t.border); | ||
const xMax = util.clamp(_xMax, -t.border, t.dim + t.border); | ||
const yMin = util.clamp(_yMin, -t.border, t.dim + t.border); | ||
const yMax = util.clamp(_yMax, -t.border, t.dim + t.border); | ||
|
||
const ox = -dx * t.dim; | ||
const oy = -dy * t.dim; | ||
for (let y = yMin; y < yMax; y++) { | ||
for (let x = xMin; x < xMax; x++) { | ||
t.set(x, y, o.get(x + ox, y + oy)); | ||
} | ||
} | ||
} | ||
} | ||
|
||
register(DEMData); | ||
module.exports = {DEMData, Level}; | ||
|
Oops, something went wrong.