Skip to content

Commit

Permalink
Redo, it worked
Browse files Browse the repository at this point in the history
  • Loading branch information
lucianpls committed Jul 22, 2024
1 parent 409d374 commit 50275bf
Show file tree
Hide file tree
Showing 4 changed files with 370 additions and 0 deletions.
131 changes: 131 additions & 0 deletions docs/LercLayer.js
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;
}

})
238 changes: 238 additions & 0 deletions docs/index.html
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>
Loading

0 comments on commit 50275bf

Please sign in to comment.