Skip to content

Commit

Permalink
Add support for mapzen terrain (#6110)
Browse files Browse the repository at this point in the history
* Added the encoding parameter to the raster-dem source type

* Changes proposed by @mollymerp

* Changed the raster-dem description and expanded the encoding parameter one

* Changed the description in the style spec reference

* Fixes linter errors

* Changed ts type

* Proposed changes

* Bug

* Added a default value when the RasterDEMTileWorkerSource is called without params

* Adds default value to pass unit tests

* Explicit encoding type and _ to private functions

* add guard for invalid encoding parameter and unit test

* add terrarium render test

* address review comments
  • Loading branch information
mollymerp authored Feb 12, 2018
1 parent c015885 commit f13c86e
Show file tree
Hide file tree
Showing 11 changed files with 205 additions and 23 deletions.
104 changes: 100 additions & 4 deletions debug/hillshade.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,52 @@
</head>

<body>

<style>
#menu {
background: #fff;
position: absolute;
z-index: 1;
top: 10px;
right: 10px;
border-radius: 3px;
width: 120px;
border: 1px solid rgba(0,0,0,0.4);
font-family: 'Open Sans', sans-serif;
}

#menu a {
font-size: 13px;
color: #404040;
display: block;
margin: 0;
padding: 0;
padding: 10px;
text-decoration: none;
border-bottom: 1px solid rgba(0,0,0,0.25);
text-align: center;
}

#menu a:last-child {
border: none;
}

#menu a:hover {
background-color: #f8f8f8;
color: #404040;
}

#menu a.active {
background-color: #3887be;
color: #ffffff;
}

#menu a.active:hover {
background: #3074a4;
}
</style>

<nav id="menu"></nav>
<div id='map'></div>

<script src='/dist/mapbox-gl-dev.js'></script>
Expand All @@ -26,19 +72,69 @@
});

map.on('load', function () {
map.addSource('dem', {

map.addSource('mapbox-dem', {
"type": "raster-dem",
"url": "mapbox://mapbox.terrain-rgb"
"url": "mapbox://mapbox.terrain-rgb",
"tileSize": 256
});
map.addLayer({
"id": "hillshading",
"source": "dem",
"id": "Mapbox data",
"source": "mapbox-dem",
"type": "hillshade"
// insert below waterway-river-canal-shadow;
// where hillshading sits in the Mapbox Outdoors style
}, 'waterway-river-canal-shadow');

map.addSource('terrarium-dem', {
"type": "raster-dem",
"encoding": "terrarium",
"tiles": [
"https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png"
],
"tileSize": 256
});
map.addLayer({
"id": "Terrarium data",
"source": "terrarium-dem",
"type": "hillshade",
"layout": {
"visibility": "none"
}
// insert below waterway-river-canal-shadow;
// where hillshading sits in the Mapbox Outdoors style
}, 'waterway-river-canal-shadow');

});

var toggleableLayerIds = ['Mapbox data', 'Terrarium data'];

for (var i = 0; i < toggleableLayerIds.length; i++) {
var id = toggleableLayerIds[i];

var link = document.createElement('a');
link.href = '#';
link.className = (i === 0) ? 'active' : '';
link.textContent = id;

link.onclick = function (e) {
var clickedLayer = this.textContent;
e.preventDefault();
e.stopPropagation();

if (this.className === '') {
var activeLayer = document.getElementsByClassName('active')[0];
activeLayer.className = '';
map.setLayoutProperty(activeLayer.textContent, 'visibility', 'none');
this.className = 'active';
map.setLayoutProperty(clickedLayer, 'visibility', 'visible');
}
};

var layers = document.getElementById('menu');
layers.appendChild(link);
}

</script>
</body>
</html>
3 changes: 2 additions & 1 deletion flow-typed/style-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ declare type RasterDEMSourceSpecification = {
"minzoom"?: number,
"maxzoom"?: number,
"tileSize"?: number,
"attribution"?: string
"attribution"?: string,
"encoding"?: "terrarium" | "mapbox"
}

declare type GeojsonSourceSpecification = {|
Expand Down
38 changes: 27 additions & 11 deletions src/data/dem_data.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,22 +57,14 @@ class DEMData {
this.loaded = !!data;
}

loadFromImage(data: RGBAImage) {
loadFromImage(data: RGBAImage, encoding: "mapbox" | "terrarium") {
if (data.height !== data.width) throw new RangeError('DEM tiles must be square');

if (encoding && encoding !== "mapbox" && encoding !== "terrarium") return util.warnOnce(`"${encoding}" is not a valid encoding type. Valid types include "mapbox" and "terrarium".`);
// 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));
}
}
this._unpackData(level, pixels, encoding || "mapbox");

// 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
Expand All @@ -95,6 +87,30 @@ class DEMData {
this.loaded = true;
}

_unpackMapbox(r: number, g: number, b: number) {
// unpacking formula for mapbox.terrain-rgb:
// https://www.mapbox.com/help/access-elevation-data/#mapbox-terrain-rgb
return ((r * 256 * 256 + g * 256.0 + b) / 10.0 - 10000.0);
}

_unpackTerrarium(r: number, g: number, b: number) {
// unpacking formula for mapzen terrarium:
// https://aws.amazon.com/public-datasets/terrain/
return ((r * 256 + g + b / 256) - 32768.0);
}

_unpackData(level: Level, pixels: Uint8Array | Uint8ClampedArray, encoding: string) {
const unpackFunctions = {"mapbox": this._unpackMapbox, "terrarium": this._unpackTerrarium};
const unpack = unpackFunctions[encoding];
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;
level.set(x, y, this.scale * unpack(pixels[j], pixels[j + 1], pixels[j + 2]));
}
}
}

getPixels() {
return new RGBAImage({width: this.level.dim + 2 * this.level.border, height: this.level.dim + 2 * this.level.border}, new Uint8Array(this.level.data.buffer));
}
Expand Down
7 changes: 6 additions & 1 deletion src/source/raster_dem_tile_source.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@ import type {Callback} from '../types/callback';


class RasterDEMTileSource extends RasterTileSource implements Source {
encoding: "mapbox" | "terrarium";

constructor(id: string, options: RasterDEMSourceSpecification, dispatcher: Dispatcher, eventedParent: Evented) {
super(id, options, dispatcher, eventedParent);
this.type = 'raster-dem';
this.maxzoom = 22;
this._options = util.extend({}, options);
this.encoding = options.encoding || "mapbox";
}

serialize() {
Expand All @@ -29,6 +32,7 @@ class RasterDEMTileSource extends RasterTileSource implements Source {
tileSize: this.tileSize,
tiles: this.tiles,
bounds: this.bounds,
encoding: this.encoding
};
}

Expand All @@ -55,7 +59,8 @@ class RasterDEMTileSource extends RasterTileSource implements Source {
uid: tile.uid,
coord: tile.tileID,
source: this.id,
rawImageData: rawImageData
rawImageData: rawImageData,
encoding: this.encoding
};

if (!tile.workerID || tile.state === 'expired') {
Expand Down
5 changes: 3 additions & 2 deletions src/source/raster_dem_tile_worker_source.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ class RasterDEMTileWorkerSource {
}

loadTile(params: WorkerDEMTileParameters, callback: WorkerDEMTileCallback) {
const uid = params.uid;
const uid = params.uid,
encoding = params.encoding;

const dem = new DEMData(uid);
this.loading[uid] = dem;
dem.loadFromImage(params.rawImageData);
dem.loadFromImage(params.rawImageData, encoding);
delete this.loading[uid];

this.loaded = this.loaded || {};
Expand Down
3 changes: 2 additions & 1 deletion src/source/worker_source.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ export type WorkerTileParameters = TileParameters & {

export type WorkerDEMTileParameters = TileParameters & {
coord: { z: number, x: number, y: number, w: number },
rawImageData: RGBAImage
rawImageData: RGBAImage,
encoding: "mapbox" | "terrarium"
};

export type WorkerTileResult = {
Expand Down
17 changes: 15 additions & 2 deletions src/style-spec/reference/v8.json
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@
"type": "enum",
"values": {
"raster-dem": {
"doc": "A raster DEM source using Mapbox Terrain RGB"
"doc": "A RGB-encoded raster DEM source"
}
},
"doc": "The type of the source."
Expand Down Expand Up @@ -272,6 +272,19 @@
"type": "string",
"doc": "Contains an attribution to be displayed when the map is shown to a user."
},
"encoding": {
"type": "enum",
"values": {
"terrarium": {
"doc": "Terrarium format PNG tiles. See https://aws.amazon.com/es/public-datasets/terrain/ for more info."
},
"mapbox": {
"doc": "Mapbox Terrain RGB tiles. See https://www.mapbox.com/help/access-elevation-data/#mapbox-terrain-rgb for more info."
}
},
"default": "mapbox",
"doc": "The encoding used by this source. Mapbox Terrain RGB is used by default"
},
"*": {
"type": "*",
"doc": "Other keys to configure the data source."
Expand Down Expand Up @@ -502,7 +515,7 @@
}
},
"hillshade": {
"doc": "Client-side hillshading visualization based on DEM data. Currently, the implementation only supports Mapbox Terrain RGB tiles",
"doc": "Client-side hillshading visualization based on DEM data. Currently, the implementation only supports Mapbox Terrain RGB and Mapzen Terrarium tiles.",
"sdk-support": {
"basic functionality": {
"js": "0.43.0"
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"version": 8,
"metadata": {
"test": {
"height": 64,
"width": 64
}
},
"center": [-113.26903, 35.995],
"zoom": 11,
"sources": {
"source": {
"type": "raster-dem",
"tiles": [
"local://tiles/{z}-{x}-{y}.terrarium.png"
],
"maxzoom": 15,
"tileSize": 256,
"encoding": "terrarium"
}
},
"layers": [
{
"id": "background",
"type": "background",
"paint": {
"background-color": "white"
}
},
{
"id": "hillshade",
"type": "hillshade",
"source": "source"
}
]
}
Binary file added test/integration/tiles/12-759-1608.terrarium.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 14 additions & 1 deletion test/unit/data/dem_data.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

const test = require('mapbox-gl-js-test').test;
const util = require('../../../src/util/util');
const {DEMData, Level} = require('../../../src/data/dem_data');
const {RGBAImage} = require('../../../src/util/image');
const {serialize, deserialize} = require('../../../src/util/web_worker_transfer');
Expand Down Expand Up @@ -48,7 +49,7 @@ test('Level', (t)=>{
});


test('DEMData constructor', (t) => {
test('DEMData', (t) => {
t.test('constructor', (t) => {
const dem = new DEMData(0, 1);
t.false(dem.loaded);
Expand Down Expand Up @@ -77,6 +78,18 @@ test('DEMData constructor', (t) => {
t.end();
});

t.test('loadFromImage with invalid encoding', (t) => {
const dem = new DEMData(0, 1);
t.stub(util, 'warnOnce');
t.false(dem.loaded);
t.equal(dem.uid, 0);

dem.loadFromImage({width: 4, height: 4, data: new Uint8ClampedArray(4 * 4 * 4)}, "derp");
t.ok(util.warnOnce.calledOnce);
t.ok(util.warnOnce.getCall(0).calledWithMatch(/"derp" is not a valid encoding type/));
t.end();
});

t.end();
});

Expand Down

0 comments on commit f13c86e

Please sign in to comment.