add elevation api for terrain tiles

This commit is contained in:
Miko 2024-10-13 21:30:27 +02:00
parent f60de38304
commit 619c7f31e1
2 changed files with 143 additions and 8 deletions

View file

@ -7,6 +7,9 @@ import clone from 'clone';
import express from 'express';
import Pbf from 'pbf';
import { VectorTile } from '@mapbox/vector-tile';
import SphericalMercator from '@mapbox/sphericalmercator';
import { Image, createCanvas } from 'canvas';
import sharp from 'sharp';
import { fixTileJSONCenter, getTileUrls, isValidHttpUrl } from './utils.js';
import {
@ -162,6 +165,130 @@ export const serve_data = {
},
);
app.get(
'^/:id/elevation/:z([0-9]+)/:x([-.0-9]+)/:y([-.0-9]+)',
async (req, res, next) => {
const item = repo[req.params.id];
if (!item) {
return res.sendStatus(404);
}
if (
item.source._info.encoding != "terrarium" &&
item.source._info.encoding != "mapbox"
) {
return res.status(404).send('Missing encoding');
}
const tileJSONFormat = item.tileJSON.format;
const z = req.params.z | 0;
var x = req.params.x;
var y = req.params.y;
if (item.sourceType === 'pmtiles') {
return res.status(404).send('Invalid format');
} else if (item.sourceType === 'mbtiles') {
const mercator = new SphericalMercator();
let tileCenter;
let xy;
const int = /^-?[0-9]+$/;
if (int.test(x) && int.test(y)) {
//console.log("int")
if (
z < item.tileJSON.minzoom ||
0 ||
x < 0 ||
y < 0 ||
z > item.tileJSON.maxzoom ||
x >= Math.pow(2, z) ||
y >= Math.pow(2, z)
) {
return res.status(404).send('Out of bounds');
}
xy = [x | 0, y | 0];
tileCenter = mercator.bbox(x, y, z);
} else {
//console.log("float")
if (
z < item.tileJSON.minzoom ||
x < -180 ||
y < -90 ||
z > item.tileJSON.maxzoom ||
x > 180 ||
y > 90
) {
return res.status(404).send('Out of bounds');
}
x = parseFloat(x);
y = parseFloat(y);
tileCenter = [y, x, y + 0.1, x + 0.1];
xy = mercator.xyz(tileCenter, z);
xy = [xy.minX, xy.minY];
}
//console.log(tileCenter + " ## " + xy);
item.source.getTile(z, xy[0], xy[1], async (err, data, headers) => {
if (err) {
if (/does not exist/.test(err.message)) {
return res.status(204).send();
} else {
return res
.status(500)
.header('Content-Type', 'text/plain')
.send(err.message);
}
} else {
if (data == null) {
return res.status(404).send('Not found');
} else {
if (tileJSONFormat === 'pbf') {
return res.status(404).send('Invalid format');
}
var image = new Image();
image.onload = function () {
const imgSize = 256;
var canvas = createCanvas(imgSize, imgSize);
var context = canvas.getContext('2d');
context.drawImage(image, 0, 0);
var imgdata = context.getImageData(0, 0, imgSize, imgSize);
var index = (xy[1] * imgdata.width + xy[0]) * 4;
var red = imgdata.data[index];
var green = imgdata.data[index + 1];
var blue = imgdata.data[index + 2];
let elevation;
if (item.source._info.encoding == "mapbox") {
elevation = -10000 + ((red * 256 * 256 + green * 256 + blue) * 0.1);
} else if (item.source._info.encoding == "terrarium") {
elevation = (red * 256 + green + blue / 256) - 32768;
} else {
elevation = "invalid encoding";
}
//"index": index, "length": imgdata.data.length,
return res.status(200).send({ "z": z, "x": xy[0], "y": xy[1], "red": red, "green": green, "blue": blue, "latitude": tileCenter[0], "longitude": tileCenter[1], "elevation": elevation });
};
image.onerror = err => { return res.status(500).header('Content-Type', 'text/plain').send(err.message); }
//image.onerror = err => { return res.status(200).header('Content-Type', 'image/webp').send(data); }
if (item.source._info.format == "webp") {
const img = await sharp(data).toFormat('png').toBuffer();
image.src = img;
} else {
image.src = data;
}
}
}
});
}
},
);
app.get('/:id.json', (req, res, next) => {
const item = repo[req.params.id];
if (!item) {
@ -245,6 +372,7 @@ export const serve_data = {
const mbw = await openMbTilesWrapper(inputFile);
const info = await mbw.getInfo();
source = mbw.getMbTiles();
info["encoding"] = params["encoding"];
tileJSON['name'] = id;
tileJSON['format'] = 'pbf';

View file

@ -498,14 +498,6 @@ function start(opts) {
)}/${center[0].toFixed(5)}`;
}
data.is_vector = tileJSON.format === 'pbf';
if (!data.is_vector) {
if (center) {
const centerPx = mercator.px([center[0], center[1]], center[2]);
data.thumbnail = `${center[2]}/${Math.floor(centerPx[0] / 256)}/${Math.floor(centerPx[1] / 256)}.${tileJSON.format}`;
}
}
const tileSize = undefined;
data.xyz_link = getTileUrls(
req,
@ -519,6 +511,21 @@ function start(opts) {
},
)[0];
data.is_vector = tileJSON.format === 'pbf';
if (!data.is_vector) {
if ((tileJSON.encoding === 'terrarium' || tileJSON.encoding === 'mapbox')) {
data.elevation_link = getTileUrls(
req,
tileJSON.tiles,
`data/${id}/elevation`
)[0];
};
if (center) {
const centerPx = mercator.px([center[0], center[1]], center[2]);
data.thumbnail = `${center[2]}/${Math.floor(centerPx[0] / 256)}/${Math.floor(centerPx[1] / 256)}.${tileJSON.format}`;
}
}
if (data.filesize) {
let suffix = 'kB';
let size = parseInt(tileJSON.filesize, 10) / 1024;