Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add raster-dem source type and hillshade layer type #5286

Merged
merged 48 commits into from
Dec 2, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
1e1635a
add raster-dem source type and hillshade layer type
Sep 1, 2017
0c64aaf
add hillshade shaders
Sep 1, 2017
c1598e8
implement raster-dem source loading
Sep 1, 2017
e321a14
render hillshading to map
Sep 1, 2017
0bf3b3d
flow changes :ocean:
Sep 2, 2017
ed673f4
add render tests
Sep 13, 2017
e248a33
use raster clipping masks for raster-dem sources
Sep 20, 2017
cb694cb
use RangeError instead of assertions
Sep 21, 2017
2acb6f0
scale slope calculations by pixel latitude
Sep 21, 2017
9af9228
use Texture and RenderTexture classes
Sep 21, 2017
140741b
remove shader debug code
Sep 21, 2017
f1b8028
use separate renderpass to render terrain prepare textures
Sep 21, 2017
ffbc809
add dem_data tests
Sep 21, 2017
47a4b7b
flow happy for raster-dem worker sources
Sep 22, 2017
130adea
oops
Sep 23, 2017
0b6b294
more raster_dem_tile tests
Sep 26, 2017
f7f5d79
dont hardcode tilesize and test dem serialize/deserialize
Sep 26, 2017
23b8b33
remove unnecessary light uniform component, update shader
Sep 27, 2017
d1994d2
tweak zoom-based hillshade exaggeration
Sep 27, 2017
abcb7e2
fix debug page for safari/firefox
Sep 27, 2017
2e48483
use segmented raster mask vertex buffers
Oct 3, 2017
8c1550b
follow new linting rule
Oct 3, 2017
3b9cdf4
initially populate tile borders with existing data to avoid border fl…
Oct 3, 2017
30d9161
only create one texture/rendertexture per tile
Oct 4, 2017
889db08
fix dem test
Oct 4, 2017
fa326fb
save and reuse tile textures AND fix missing tiles :joy:
Oct 5, 2017
ff08249
use highp precision to fix android rendering
Oct 5, 2017
123e795
clean up
Oct 5, 2017
9becbf1
:memo: add explanatory comments
Oct 16, 2017
7fd7ae8
fix test borked in rebase
Oct 16, 2017
2e0a1c9
fix tile state assignment
Oct 16, 2017
0183b8b
refactor based on review comments
Oct 17, 2017
c625027
ensure DEMData Levels are always square, use a single Level instead o…
Oct 17, 2017
b6f81f8
remove hardcoded tilesize
Oct 25, 2017
0155898
use dim instead of data dimensions
Nov 1, 2017
069ed18
update shaders+spec based on nicki's recs
Nov 2, 2017
2ffb98f
fix rebase :woman_facepalming:
Nov 2, 2017
fafc617
use new Color class output of getPaintValue and updated source schema
Nov 8, 2017
eb3caad
check for accurate backfilled data, clarify loop
Nov 13, 2017
9f9844f
use new Property interface, fix style-spec error
Nov 17, 2017
71b266b
try LAB color space
Nov 18, 2017
3b6d073
use layer-specific lighting for hillshade
Nov 21, 2017
515fd94
use new WebWorkerTransfer
Nov 30, 2017
858421c
clean up and remove unused code
Nov 30, 2017
1cef50b
Add HillshadeLayer benchmark
Nov 30, 2017
b922b7d
use gl state tracking
Nov 30, 2017
33aa66b
address final review comments
Dec 1, 2017
a3deabf
add HillshadeStyleLayer to TypedStyleLayer
Dec 1, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions bench/benchmarks/layers.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,26 @@ class LayerHeatmap extends LayerBenchmark {
}
}

class LayerHillshade extends LayerBenchmark {
constructor() {
super();

this.layerStyle = Object.assign({}, style, {
sources: {
'terrain-rgb': {
'type': 'raster-dem',
'url': 'mapbox://mapbox.terrain-rgb'
}
},
layers: generateLayers({
'id': 'layer',
'type': 'hillshade',
'source': 'terrain-rgb',
})
});
}
}

class LayerLine extends LayerBenchmark {
constructor() {
super();
Expand Down Expand Up @@ -202,6 +222,7 @@ module.exports = [
LayerFill,
LayerFillExtrusion,
LayerHeatmap,
LayerHillshade,
LayerLine,
LayerRaster,
LayerSymbol
Expand Down
77 changes: 77 additions & 0 deletions debug/terrain-rendering.html
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>
25 changes: 24 additions & 1 deletion flow-typed/style-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ declare type VectorSourceSpecification = {
}

declare type RasterSourceSpecification = {
"type": "raster",
"type": "raster" | "raster-dem",
"url"?: string,
"tiles"?: Array<string>,
"bounds"?: [number, number, number, number],
Expand Down Expand Up @@ -345,6 +345,28 @@ declare type RasterLayerSpecification = {|
|}
|}

declare type HillshadeLayerSpecification = {|
"id": string,
"type": "hillshade",
"metadata"?: mixed,
"source": string,
"source-layer"?: string,
"minzoom"?: number,
"maxzoom"?: number,
"filter"?: FilterSpecification,
"layout"?: {|
"visibility"?: "visible" | "none"
|},
"paint"?: {|
"hillshade-illumination-direction"?: PropertyValueSpecification<number>,
"hillshade-illumination-anchor"?: PropertyValueSpecification<"map" | "viewport">,
"hillshade-exaggeration"?: PropertyValueSpecification<number>,
"hillshade-shadow-color"?: PropertyValueSpecification<ColorSpecification>,
"hillshade-highlight-color"?: PropertyValueSpecification<ColorSpecification>,
"hillshade-accent-color"?: PropertyValueSpecification<ColorSpecification>
|}
|}

declare type BackgroundLayerSpecification = {|
"id": string,
"type": "background",
Expand All @@ -369,5 +391,6 @@ declare type LayerSpecification =
| HeatmapLayerSpecification
| FillExtrusionLayerSpecification
| RasterLayerSpecification
| HillshadeLayerSpecification
| BackgroundLayerSpecification;

155 changes: 155 additions & 0 deletions src/data/dem_data.js
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};

Loading