-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
370 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
var LercLayer = L.GridLayer.extend({ | ||
createTile: function (coords, done) { | ||
var error; | ||
var tile = L.DomUtil.create('canvas', 'leaflet-tile'); | ||
tile.width = this.options.tileSize; | ||
tile.height = this.options.tileSize; | ||
tile.zoom = coords.z; | ||
|
||
var xhr = new XMLHttpRequest(); | ||
xhr.responseType = "arraybuffer"; | ||
// var url = 'https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/' + 'Terrain3D/ImageServer/tile/' + coords.z + '/' + coords.y + '/' + coords.x; | ||
var url = 'https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/' + 'TopoBathy3D/ImageServer/tile/' + coords.z + '/' + coords.y + '/' + coords.x; | ||
|
||
xhr.open("Get", url, true); | ||
xhr.send(); | ||
|
||
// var that = this; | ||
|
||
xhr.onreadystatechange = function (evt) { | ||
if (evt.target.readyState == 4 && evt.target.status == 200) { | ||
tile.decodedPixels = Lerc.decode(new Uint8Array(xhr.response)); | ||
if (tile.decodedPixels) | ||
this.draw(tile); | ||
else | ||
error = "Unrecognized data"; | ||
done(error, tile); | ||
} | ||
}.bind(this); | ||
|
||
return tile; | ||
}, | ||
|
||
draw: function (tile) { | ||
|
||
let width = tile.decodedPixels.width - 1; | ||
let height = tile.decodedPixels.height - 1; | ||
let min = +slider.noUiSlider.get()[0]; | ||
let max = +slider.noUiSlider.get()[1]; | ||
let values = tile.decodedPixels.data; | ||
|
||
let ptrVAL = Lerc._malloc(values.buffer.byteLength); | ||
let ptrRGBA = Lerc._malloc(width * height * 4); | ||
|
||
// Copy the data to wasm | ||
// writeArray only works with 8bit arrays | ||
// Lerc.writeArrayToMemory(new Uint8Array(values.buffer, 0, values.buffer.byteLength), ptrVAL); | ||
// This is direct, the HEAP has multiple views as different types, the offsets have to be adjusted | ||
Lerc.HEAPF32.set(values, ptrVAL / 4); | ||
|
||
// Call wasm to convert it to streched RGBAt | ||
let retval = Lerc.topixel8(ptrVAL, values.buffer.byteLength, min, max, ptrRGBA); | ||
if (0 == retval) { | ||
console.log("Error converting"); | ||
// Free the buffers for now | ||
Lerc._free(ptrVAL); | ||
Lerc._free(ptrRGBA); | ||
return; // ?? | ||
} | ||
|
||
// Apply a palette if defined | ||
if (this.palette) { | ||
let ppal = Lerc._malloc(256 * 4); | ||
Lerc.HEAPU32.set(this.palette, ppal / 4); | ||
retval = Lerc.applypalette(ppal, ptrRGBA, width * height); | ||
Lerc._free(ppal); | ||
} | ||
|
||
// Hillshade needs to know pixel size | ||
// Comment out these three lines to skip hillshade | ||
let pixel_size = 40_000_000 * (2 ** (-8 -tile.zoom)); | ||
let sun_angle = +this.sunAngle / 180 * Math.PI || Math.PI / 4; | ||
retval = Lerc.hillshade(ptrVAL, values.buffer.byteLength, pixel_size, sun_angle, ptrRGBA); | ||
|
||
if (0 == retval) { | ||
console.log("Error converting"); | ||
// Free the buffers for now | ||
Lerc._free(ptrVAL); | ||
Lerc._free(ptrRGBA); | ||
return; // ?? | ||
} | ||
|
||
// Done with the input copy | ||
Lerc._free(ptrVAL); | ||
|
||
// display the RGBA image | ||
let ctx = tile.getContext('2d'); | ||
let imageData = ctx.createImageData(width, height); | ||
|
||
// Copy the RGBA pixels from wasm to imageData.data | ||
new Uint32Array(imageData.data.buffer).set( | ||
new Uint32Array(Lerc.HEAP8.buffer, ptrRGBA, imageData.data.length / 4)); // view | ||
|
||
// This looks simpler, although it might trigger the clamping | ||
// imageData.data.set(new Uint8Array(Lerc.HEAPU8.buffer, ptrRGBA, imageData.data.length)); | ||
|
||
ctx.putImageData(imageData, 0, 0); | ||
Lerc._free(ptrRGBA); | ||
}, | ||
|
||
// Build a palette by linear interpolation beween points | ||
buildPalette : function(points) | ||
{ | ||
let palette = new Uint32Array(256); | ||
|
||
let j = -1; | ||
let slope = 0; | ||
for (let i = 0; i < 256; i++) { | ||
if (i == 0 || points.index[j + 1] < i) { | ||
j++; | ||
let f = 1 / (points.index[j + 1] - points.index[j]); | ||
slope = { | ||
red : f * ((points.values[j+1] & 0xff) - (points.values[j] & 0xff)), | ||
green : f * (((points.values[j+1] >> 8) & 0xff) - ((points.values[j] >> 8) & 0xff)), | ||
blue : f *(((points.values[j+1] >> 16) & 0xff) - ((points.values[j] >> 16) & 0xff)), | ||
alpha : f * (((points.values[j+1] >> 24) & 0xff) - ((points.values[j] >> 24) & 0xff)), | ||
} | ||
} | ||
|
||
// i is between j and j+1 | ||
let l = i - points.index[j]; | ||
let v = points.values[j]; | ||
red = 0xff & ((v & 0xff) + l * slope.red); | ||
green = 0xff & (((v >> 8) & 0xff) + l * slope.green); | ||
blue = 0xff & (((v >> 16) & 0xff) + l * slope.blue); | ||
alpha = 0xff & (((v >> 24) & 0xff) + l * slope.alpha); | ||
palette[i] = (alpha << 24) | (blue << 16) | (green << 8) | red; | ||
}; | ||
return palette; | ||
} | ||
|
||
}) |
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,238 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<meta charset=utf-8 /> | ||
<title>Data in Leaflet</title> | ||
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' /> | ||
|
||
<!-- Load Leaflet from CDN--> | ||
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" /> | ||
<script src="https://unpkg.com/[email protected]/dist/leaflet-src.js"></script> | ||
|
||
<!-- load esri leaflet and its geocoder for address/place search --> | ||
<script src="https://unpkg.com/[email protected]/dist/esri-leaflet.js"></script> | ||
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/esri-leaflet-geocoder.css"> | ||
<script src="https://unpkg.com/[email protected]/dist/esri-leaflet-geocoder.js"></script> | ||
|
||
<!-- slider library--> | ||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/noUiSlider/8.2.1/nouislider.min.css" /> | ||
<script src="https://cdnjs.cloudflare.com/ajax/libs/noUiSlider/8.2.1/nouislider.min.js"></script> | ||
|
||
<!--load the lerc decoder --> | ||
<script src="https://unpkg.com/[email protected]/LercDecode.js"></script> | ||
|
||
<!--This is the wasm lerc1 decoder--> | ||
<script src="lerc1dec.js"></script> | ||
|
||
<!--This should be moved to lerc1dec.js--> | ||
<script> | ||
var Lerc = Module; | ||
Lerc.onRuntimeInitialized = () => { | ||
// void *data, size_t sz, --> json image : { width, height, needs_ndv, precision | message} | ||
Lerc.getwh = this.cwrap('getwh', 'number', ['number', 'number']); | ||
// void *data, size_t sz, ndv, outbuffer, outsize, message -> success | ||
Lerc.lercDecode = this.cwrap('decode', 'number', [ | ||
'number', 'number', 'number', | ||
'number', 'number', 'number' | ||
]); | ||
|
||
// float *src, size_t sz(257*257*4), min, max, int *dst -> success | ||
Lerc.topixel8 = this.cwrap('topixel8', 'number', | ||
['number', 'number', 'number', 'number', 'number']); | ||
|
||
// float *src, size_t sz(257*257*4), pixel size, sun angle, int *dst -> success | ||
Lerc.hillshade = this.cwrap('hillshade', 'number', | ||
['number', 'number', 'number', 'number', 'number']); | ||
|
||
// apply the RGBA palette to lower byte of pixels, in place | ||
// i32 palette[256], i32 *pixels, i32 numpixels | ||
Lerc.applypalette = this.cwrap('applypalette', 'number' | ||
['number', 'number', 'number']); | ||
|
||
// get the Lerc blob info -> object | ||
Lerc.getInfo = function (data) { | ||
if (data.length > 100) | ||
data = data.slice(0, 100) | ||
let workBuffer = this._malloc(data.length); | ||
this.writeArrayToMemory(data, workBuffer); | ||
let cresult = this.getwh(workBuffer, data.length); | ||
if (cresult == 0) // Error, not valid lerc1 | ||
return null; | ||
let response = this.UTF8ToString(cresult); | ||
this._free(cresult); | ||
this._free(workBuffer); | ||
return JSON.parse(response) | ||
}; | ||
|
||
Lerc.wasm_decode = function(data, ndv) { | ||
let image = this.getInfo(data); | ||
if (!image) { | ||
console.log("Invalid lerc1 data"); | ||
return null; | ||
} | ||
|
||
raw = this._malloc(data.length); | ||
this.writeArrayToMemory(data, raw); | ||
outsize = image.width * image.height * 4; // Always float32 | ||
bufptr = this._malloc(outsize); | ||
message = this._malloc(1024); | ||
decoded = this.lercDecode(raw, data.length, ndv, bufptr, outsize, message); | ||
if (decoded) { | ||
// A view, remember to call clean when done with it | ||
image.data = new Float32Array(this.HEAP8.buffer, bufptr, outsize / 4); | ||
image.bufptr = bufptr; | ||
image.clean = function() { | ||
delete this.data; | ||
Lerc._free(bufptr); | ||
delete image.bufptr; | ||
} | ||
} else { | ||
image.error = this.UTF8ToString(message); | ||
this._free(bufptr); | ||
console.log(image.error); | ||
}; | ||
|
||
this._free(message); | ||
this._free(raw); | ||
if (image.error) | ||
console.log(image.error); | ||
return image; | ||
} | ||
|
||
// Wrapper for wasm_decode, returns array copy and cleans up wasm heap | ||
Lerc.decode = function(data, ndv) { | ||
image = this.wasm_decode(data, ndv); | ||
values = new Float32Array(image.data); // copy | ||
image.clean(); // Free heap array | ||
image.data = values; // replace it with copy | ||
delete image.clean; | ||
return image; | ||
} | ||
} | ||
|
||
</script> | ||
|
||
<!-- load our plugin --> | ||
<script src="LercLayer.js"></script> | ||
|
||
<style> | ||
body { | ||
margin:0; | ||
padding:0; | ||
} | ||
|
||
#map { | ||
position: absolute; | ||
top:0; | ||
bottom:0; | ||
right:0;left:0; | ||
} | ||
|
||
#info-pane { | ||
position: absolute; | ||
top: 10px; | ||
right: 10px; | ||
min-width: 200px; | ||
z-index: 500; | ||
padding: 1em; | ||
background: white; | ||
} | ||
|
||
.noUi-connect { | ||
background: #ccc; | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
|
||
<div id="map"></div> | ||
<div id="info-pane" class="leaflet-bar"> | ||
<div id="sungle"> | ||
<input type="range" min="40" max="140" value="45" class="slider" id="sunAngle"> | ||
</div> | ||
<div id="slider"></div> | ||
<br><label id="min">0 meters</label> | ||
<br><label id="max">4000 meters</label> | ||
<hr> | ||
<div id="pixel-value">Esri Tiled Elevation Service</div> | ||
</div> | ||
|
||
<script> | ||
// create a UI slider for the end user to toggle the pixel range to display | ||
var slider = document.getElementById('slider'); | ||
noUiSlider.create(slider, { | ||
start: [0, 4000], | ||
step: 100, | ||
connect: true, | ||
range: { 'min': -8000, 'max': 8000 } | ||
}); | ||
|
||
// When the slider value changes, update the input and span | ||
slider.noUiSlider.on('set', function (values, handle) { | ||
document.getElementById('min').innerHTML = parseInt(values[0], 10) + ' meters'; | ||
document.getElementById('max').innerHTML = parseInt(values[1], 10) + ' meters'; | ||
|
||
// redraw the tiles without fetching the from the server | ||
for (var key in lercElevation._tiles) { | ||
lercElevation.draw(lercElevation._tiles[key].el); | ||
} | ||
}); | ||
|
||
let sungle = document.getElementById('sunAngle'); | ||
sungle.oninput = function() { | ||
lercElevation.sunAngle = +this.value; | ||
for (var key in lercElevation._tiles) { | ||
lercElevation.draw(lercElevation._tiles[key].el); | ||
} | ||
}; | ||
|
||
let southWest = L.latLng(-90, -179); | ||
let northEast = L.latLng(90, 179); | ||
let worldBounds = L.latLngBounds(southWest, northEast); | ||
|
||
// set up the map | ||
var map = L.map('map', { | ||
noWrap: true, | ||
minZoom: 3, | ||
maxBounds: worldBounds | ||
}).setView([30, 45], 3); | ||
|
||
let lercElevation = new LercLayer({ | ||
noWrap: true, | ||
attribution: 'USGS, <a href="https://github.com/Esri/lerc">LERC</a>', | ||
tileSize: 256, | ||
|
||
}); | ||
|
||
map.on('mousemove', function (e) { | ||
// the gather the x/y and z of the tile url | ||
var layerPoint = map.project(e.latlng).floor(); | ||
var tilePoint = layerPoint.divideBy(256).floor(); | ||
tilePoint.z = map.getZoom(); | ||
// the tile data block | ||
var block = lercElevation._tiles[tilePoint.x + ':' + tilePoint.y + ':' + tilePoint.z].el.decodedPixels; | ||
|
||
// Read the data value from the block if it exists | ||
if (block) { | ||
var pointInTile = layerPoint.subtract(tilePoint.multiplyBy(256)); | ||
document.getElementById('pixel-value').innerHTML = "current elevation: " | ||
+ Math.round(block.data[pointInTile.y * block.width + pointInTile.x]) | ||
+ " meters"; | ||
} else { | ||
document.getElementById('pixel-value').innerHTML = "Elevation: undefined"; | ||
} | ||
}); | ||
|
||
lercElevation.palette = lercElevation.buildPalette( | ||
{ | ||
index: [0, 64, 128, 192, 255], | ||
values: [0xFFFF0000, 0xff404040, 0xff20ff20, 0xff4040c0, 0xffffffff], | ||
} | ||
); | ||
|
||
L.esri.Geocoding.geosearch().addTo(map); | ||
lercElevation.addTo(map); | ||
|
||
</script> | ||
</body> | ||
</html> |
Oops, something went wrong.