Skip to content

Commit

Permalink
remove Level subclass of DEMData to match native (#7021)
Browse files Browse the repository at this point in the history
  • Loading branch information
mollymerp authored and mourner committed Jul 25, 2018
1 parent 22a0345 commit 11d09c7
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 199 deletions.
140 changes: 55 additions & 85 deletions src/data/dem_data.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,10 @@ import { RGBAImage } from '../util/image';
import { warnOnce, clamp } from '../util/util';
import { register } from '../util/web_worker_transfer';

export 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', 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
// loaded from a image tile, we decode the pixel values using the appropriate decoding formula, but we store the
// elevation data 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
Expand All @@ -46,48 +16,63 @@ register('Level', Level);

export default class DEMData {
uid: string;
scale: number;
level: Level;
loaded: boolean;
data: Int32Array;
border: number;
stride: number;
dim: number;

constructor(uid: string, scale: ?number, data: ?Level) {
constructor(uid: string, data: RGBAImage, encoding: "mapbox" | "terrarium") {
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(1, 0);
this.loaded = !!data;
}

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 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;
const dim = this.dim = data.height;
this.border = Math.max(Math.ceil(data.height / 2), 1);
this.stride = this.dim + 2 * this.border;
this.data = new Int32Array(this.stride * this.stride);

this._unpackData(level, pixels, encoding || "mapbox");
const pixels = data.data;
const unpack = encoding === "terrarium" ? this._unpackTerrarium : this._unpackMapbox;
for (let y = 0; y < dim; y++) {
for (let x = 0; x < dim; x++) {
const i = y * dim + x;
const j = i * 4;
this.set(x, y, unpack(pixels[j], pixels[j + 1], pixels[j + 2]));
}
}

// 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++) {
for (let x = 0; x < dim; x++) {
// left vertical border
level.set(-1, x, level.get(0, x));
this.set(-1, x, this.get(0, x));
// right vertical border
level.set(level.dim, x, level.get(level.dim - 1, x));
this.set(dim, x, this.get(dim - 1, x));
// left horizontal border
level.set(x, -1, level.get(x, 0));
this.set(x, -1, this.get(x, 0));
// right horizontal border
level.set(x, level.dim, level.get(x, level.dim - 1));
this.set(x, dim, this.get(x, 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;
this.set(-1, -1, this.get(0, 0));
this.set(dim, -1, this.get(dim - 1, 0));
this.set(-1, dim, this.get(0, dim - 1));
this.set(dim, dim, this.get(dim - 1, dim - 1));
}

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);
}

_unpackMapbox(r: number, g: number, b: number) {
Expand All @@ -102,32 +87,17 @@ export default class DEMData {
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));
return new RGBAImage({width: this.dim + 2 * this.border, height: this.dim + 2 * this.border}, new Uint8Array(this.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)');
if (this.dim !== borderTile.dim) throw new Error('dem dimension mismatch');

let _xMin = dx * t.dim,
_xMax = dx * t.dim + t.dim,
_yMin = dy * t.dim,
_yMax = dy * t.dim + t.dim;
let _xMin = dx * this.dim,
_xMax = dx * this.dim + this.dim,
_yMin = dy * this.dim,
_yMax = dy * this.dim + this.dim;

switch (dx) {
case -1:
Expand All @@ -147,16 +117,16 @@ export default class DEMData {
break;
}

const xMin = clamp(_xMin, -t.border, t.dim + t.border);
const xMax = clamp(_xMax, -t.border, t.dim + t.border);
const yMin = clamp(_yMin, -t.border, t.dim + t.border);
const yMax = clamp(_yMax, -t.border, t.dim + t.border);
const xMin = clamp(_xMin, -this.border, this.dim + this.border);
const xMax = clamp(_xMax, -this.border, this.dim + this.border);
const yMin = clamp(_yMin, -this.border, this.dim + this.border);
const yMax = clamp(_yMax, -this.border, this.dim + this.border);

const ox = -dx * t.dim;
const oy = -dy * t.dim;
const ox = -dx * this.dim;
const oy = -dy * this.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));
this.set(x, y, borderTile.get(x + ox, y + oy));
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/render/draw_hillshade.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ function prepareHillshade(painter, tile, sourceMaxZoom) {
// base 10 - 0, 1, 6, 236 (this order is reversed in the resulting array via the overflow.
// first 8 bits represent 236, so the r component of the texture pixel will be 236 etc.)
// base 2 - 0000 0000, 0000 0001, 0000 0110, 1110 1100
if (tile.dem && tile.dem.level) {
const tileSize = tile.dem.level.dim;
if (tile.dem && tile.dem.data) {
const tileSize = tile.dem.dim;

const pixelData = tile.dem.getPixels();
context.activeTexture.set(gl.TEXTURE1);
Expand Down
11 changes: 2 additions & 9 deletions src/source/raster_dem_tile_worker_source.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,15 @@ import type {

class RasterDEMTileWorkerSource {
actor: Actor;
loading: {[string]: DEMData};
loaded: {[string]: DEMData};

constructor() {
this.loading = {};
this.loaded = {};
}

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

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

this.loaded = this.loaded || {};
this.loaded[uid] = dem;
Expand Down
Loading

0 comments on commit 11d09c7

Please sign in to comment.