Fix Elevation API and extend Elevation Preview (#1432)
* add elevation info in preview * fix various issues - pmtile coordinates - pixel calculation - swapped lat / long - allow tileSize to be configured - allow any zoom for coordinates * apply linter changes * remove not used entry * drop baseUrl in favor of public_url * fix map not using full body * remove bounds check for coordinates --------- Co-authored-by: Miko <miko@none>
This commit is contained in:
parent
9d222c1dec
commit
4a783446cd
3 changed files with 118 additions and 40 deletions
51
public/resources/elevation-control.js
Normal file
51
public/resources/elevation-control.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
class ElevationInfoControl {
|
||||
constructor(options) {
|
||||
this.url = options["url"];
|
||||
}
|
||||
|
||||
getDefaultPosition() {
|
||||
const defaultPosition = "bottom-left";
|
||||
return defaultPosition;
|
||||
}
|
||||
|
||||
onAdd(map) {
|
||||
this.map = map;
|
||||
this.controlContainer = document.createElement("div");
|
||||
this.controlContainer.classList.add("maplibregl-ctrl");
|
||||
this.controlContainer.classList.add("maplibregl-ctrl-group");
|
||||
this.controlContainer.classList.add("maplibre-ctrl-elevation");
|
||||
this.controlContainer.textContent = "Elevation: Click on Map";
|
||||
|
||||
map.on('click', (e) => {
|
||||
var url = this.url;
|
||||
var coord = {"z": Math.floor(map.getZoom()), "x": e.lngLat["lng"], "y": e.lngLat["lat"]};
|
||||
for(var key in coord) {
|
||||
url = url.replace(new RegExp('{'+ key +'}','g'), coord[key]);
|
||||
}
|
||||
|
||||
let request = new XMLHttpRequest();
|
||||
request.open("GET", url, true);
|
||||
request.onload = () => {
|
||||
if (request.status !== 200) {
|
||||
this.controlContainer.textContent = "Elevation: No value";
|
||||
} else {
|
||||
this.controlContainer.textContent = `Elevation: ${JSON.parse(request.responseText).elevation} (${JSON.stringify(coord)})`;
|
||||
}
|
||||
}
|
||||
request.send();
|
||||
});
|
||||
return this.controlContainer;
|
||||
}
|
||||
|
||||
onRemove() {
|
||||
if (
|
||||
!this.controlContainer ||
|
||||
!this.controlContainer.parentNode ||
|
||||
!this.map
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.controlContainer.parentNode.removeChild(this.controlContainer);
|
||||
this.map = undefined;
|
||||
}
|
||||
};
|
|
@ -9,17 +9,19 @@
|
|||
<link rel="stylesheet" type="text/css" href="{{public_url}}maplibre-gl-inspect.css{{&key_query}}" />
|
||||
<script src="{{public_url}}maplibre-gl.js{{&key_query}}"></script>
|
||||
<script src="{{public_url}}maplibre-gl-inspect.js{{&key_query}}"></script>
|
||||
<script src="{{public_url}}elevation-control.js{{&key_query}}"></script>
|
||||
<style>
|
||||
body {background:#fff;color:#333;font-family:Arial, sans-serif;}
|
||||
{{^is_terrain}}
|
||||
#map {position:absolute;top:0;left:0;right:250px;bottom:0;}
|
||||
{{/is_terrain}}
|
||||
{{#is_terrain}}
|
||||
#map { position:absolute; top:0; bottom:0; width:100%; }
|
||||
#map { position:absolute; top:0; bottom:0; left:0; right:0; }
|
||||
{{/is_terrain}}
|
||||
h1 {position:absolute;top:5px;right:0;width:240px;margin:0;line-height:20px;font-size:20px;}
|
||||
#layerList {position:absolute;top:35px;right:0;bottom:0;width:240px;overflow:auto;}
|
||||
#layerList div div {width:15px;height:15px;display:inline-block;}
|
||||
.maplibre-ctrl-elevation { padding-left: 5px; padding-right: 5px; }
|
||||
</style>
|
||||
{{/use_maplibre}}
|
||||
{{^use_maplibre}}
|
||||
|
@ -69,6 +71,7 @@
|
|||
};
|
||||
{{/is_terrain}}
|
||||
{{#is_terrain}}
|
||||
|
||||
var style = {
|
||||
version: 8,
|
||||
sources: {
|
||||
|
@ -86,11 +89,11 @@
|
|||
"terrain": {
|
||||
"source": "terrain"
|
||||
},
|
||||
layers: [
|
||||
"layers": [
|
||||
{
|
||||
"id": "background",
|
||||
"paint": {
|
||||
{{^if is_terrainrgb}}
|
||||
{{#if is_terrainrgb}}
|
||||
"background-color": "hsl(190, 99%, 63%)"
|
||||
{{else}}
|
||||
"background-color": "hsl(0, 100%, 25%)"
|
||||
|
@ -118,24 +121,34 @@
|
|||
maxPitch: 85,
|
||||
style: style
|
||||
});
|
||||
|
||||
map.addControl(new maplibregl.NavigationControl({
|
||||
visualizePitch: true,
|
||||
showZoom: true,
|
||||
showCompass: true
|
||||
}));
|
||||
{{#is_terrain}}
|
||||
|
||||
map.addControl(
|
||||
new maplibregl.TerrainControl({
|
||||
source: "terrain",
|
||||
})
|
||||
);
|
||||
|
||||
map.addControl(
|
||||
new ElevationInfoControl({
|
||||
url: "{{public_url}}data/{{id}}/elevation/{z}/{x}/{y}"
|
||||
})
|
||||
);
|
||||
{{/is_terrain}}
|
||||
{{^is_terrain}}
|
||||
|
||||
var inspect = new MaplibreInspect({
|
||||
showInspectMap: true,
|
||||
showInspectButton: false
|
||||
});
|
||||
map.addControl(inspect);
|
||||
|
||||
map.on('styledata', function() {
|
||||
var layerList = document.getElementById('layerList');
|
||||
layerList.innerHTML = '';
|
||||
|
|
|
@ -208,51 +208,49 @@ export const serve_data = {
|
|||
return res.status(404).send(JSON.stringify(tileJSON));
|
||||
}
|
||||
|
||||
const TILE_SIZE = 256;
|
||||
let tileCenter;
|
||||
const TILE_SIZE = tileJSON.tileSize || 512;
|
||||
let bbox;
|
||||
let xy;
|
||||
var zoom = z;
|
||||
|
||||
if (Number.isInteger(x) && Number.isInteger(y)) {
|
||||
const intX = parseInt(req.params.x, 10);
|
||||
const intY = parseInt(req.params.y, 10);
|
||||
|
||||
if (
|
||||
z < tileJSON.minzoom ||
|
||||
z > tileJSON.maxzoom ||
|
||||
zoom < tileJSON.minzoom ||
|
||||
zoom > tileJSON.maxzoom ||
|
||||
intX < 0 ||
|
||||
intY < 0 ||
|
||||
intX >= Math.pow(2, z) ||
|
||||
intY >= Math.pow(2, z)
|
||||
intX >= Math.pow(2, zoom) ||
|
||||
intY >= Math.pow(2, zoom)
|
||||
) {
|
||||
return res.status(404).send('Out of bounds');
|
||||
}
|
||||
xy = [intX, intY];
|
||||
tileCenter = new SphericalMercator().bbox(intX, intY, z);
|
||||
bbox = new SphericalMercator().bbox(intX, intY, zoom);
|
||||
} else {
|
||||
if (
|
||||
z < tileJSON.minzoom ||
|
||||
z > tileJSON.maxzoom ||
|
||||
x < -180 ||
|
||||
y < -90 ||
|
||||
x > 180 ||
|
||||
y > 90
|
||||
) {
|
||||
return res.status(404).send('Out of bounds');
|
||||
//no zoom limit with coordinates
|
||||
if (zoom < tileJSON.minzoom) {
|
||||
zoom = tileJSON.minzoom;
|
||||
}
|
||||
if (zoom > tileJSON.maxzoom) {
|
||||
zoom = tileJSON.maxzoom;
|
||||
}
|
||||
|
||||
tileCenter = [y, x, y + 0.1, x + 0.1];
|
||||
const { minX, minY } = new SphericalMercator().xyz(tileCenter, z);
|
||||
bbox = [x, y, x + 0.1, y + 0.1];
|
||||
const { minX, minY } = new SphericalMercator().xyz(bbox, zoom);
|
||||
xy = [minX, minY];
|
||||
}
|
||||
|
||||
let data;
|
||||
if (sourceType === 'pmtiles') {
|
||||
const tileinfo = await getPMtilesTile(source, z, x, y);
|
||||
const tileinfo = await getPMtilesTile(source, zoom, xy[0], xy[1]);
|
||||
if (!tileinfo?.data) return res.status(204).send();
|
||||
data = tileinfo.data;
|
||||
} else {
|
||||
data = await new Promise((resolve, reject) => {
|
||||
source.getTile(z, xy[0], xy[1], (err, tileData) => {
|
||||
source.getTile(zoom, xy[0], xy[1], (err, tileData) => {
|
||||
if (err) {
|
||||
return /does not exist/.test(err.message)
|
||||
? resolve(null)
|
||||
|
@ -271,29 +269,43 @@ export const serve_data = {
|
|||
const canvas = createCanvas(TILE_SIZE, TILE_SIZE);
|
||||
const context = canvas.getContext('2d');
|
||||
context.drawImage(image, 0, 0);
|
||||
const imgdata = context.getImageData(0, 0, TILE_SIZE, TILE_SIZE);
|
||||
|
||||
const arrayWidth = imgdata.width;
|
||||
const arrayHeight = imgdata.height;
|
||||
const bytesPerPixel = 4;
|
||||
const long = bbox[0];
|
||||
const lat = bbox[1];
|
||||
|
||||
const xPixel = Math.floor(xy[0]);
|
||||
const yPixel = Math.floor(xy[1]);
|
||||
// calculate pixel coordinate of tile,
|
||||
// see https://developers.google.com/maps/documentation/javascript/examples/map-coordinates
|
||||
let siny = Math.sin((lat * Math.PI) / 180);
|
||||
// Truncating to 0.9999 effectively limits latitude to 89.189. This is
|
||||
// about a third of a tile past the edge of the world tile.
|
||||
siny = Math.min(Math.max(siny, -0.9999), 0.9999);
|
||||
|
||||
const xWorld = TILE_SIZE * (0.5 + long / 360);
|
||||
const yWorld =
|
||||
TILE_SIZE *
|
||||
(0.5 - Math.log((1 + siny) / (1 - siny)) / (4 * Math.PI));
|
||||
|
||||
const scale = 1 << zoom;
|
||||
|
||||
const xTile = Math.floor((xWorld * scale) / TILE_SIZE);
|
||||
const yTile = Math.floor((yWorld * scale) / TILE_SIZE);
|
||||
|
||||
const xPixel = Math.floor(xWorld * scale) - xTile * TILE_SIZE;
|
||||
const yPixel = Math.floor(yWorld * scale) - yTile * TILE_SIZE;
|
||||
|
||||
if (
|
||||
xPixel < 0 ||
|
||||
yPixel < 0 ||
|
||||
xPixel >= arrayWidth ||
|
||||
yPixel >= arrayHeight
|
||||
xPixel >= TILE_SIZE ||
|
||||
yPixel >= TILE_SIZE
|
||||
) {
|
||||
return reject('Out of bounds Pixel');
|
||||
return reject('Pixel is out of bounds');
|
||||
}
|
||||
|
||||
const index = (yPixel * arrayWidth + xPixel) * bytesPerPixel;
|
||||
|
||||
const red = imgdata.data[index];
|
||||
const green = imgdata.data[index + 1];
|
||||
const blue = imgdata.data[index + 2];
|
||||
const imgdata = context.getImageData(xPixel, yPixel, 1, 1);
|
||||
const red = imgdata.data[0];
|
||||
const green = imgdata.data[1];
|
||||
const blue = imgdata.data[2];
|
||||
|
||||
let elevation;
|
||||
if (encoding === 'mapbox') {
|
||||
|
@ -307,14 +319,14 @@ export const serve_data = {
|
|||
|
||||
resolve(
|
||||
res.status(200).send({
|
||||
z,
|
||||
z: zoom,
|
||||
x: xy[0],
|
||||
y: xy[1],
|
||||
red,
|
||||
green,
|
||||
blue,
|
||||
latitude: tileCenter[0],
|
||||
longitude: tileCenter[1],
|
||||
latitude: lat,
|
||||
longitude: long,
|
||||
elevation,
|
||||
}),
|
||||
);
|
||||
|
@ -406,6 +418,7 @@ export const serve_data = {
|
|||
const metadata = await getPMtilesInfo(source);
|
||||
|
||||
tileJSON['encoding'] = params['encoding'];
|
||||
tileJSON['tileSize'] = params['tileSize'];
|
||||
tileJSON['name'] = id;
|
||||
tileJSON['format'] = 'pbf';
|
||||
Object.assign(tileJSON, metadata);
|
||||
|
@ -427,6 +440,7 @@ export const serve_data = {
|
|||
const info = await mbw.getInfo();
|
||||
source = mbw.getMbTiles();
|
||||
tileJSON['encoding'] = params['encoding'];
|
||||
tileJSON['tileSize'] = params['tileSize'];
|
||||
tileJSON['name'] = id;
|
||||
tileJSON['format'] = 'pbf';
|
||||
|
||||
|
|
Loading…
Reference in a new issue