diff --git a/public/resources/elevation-control.js b/public/resources/elevation-control.js
new file mode 100644
index 0000000..a1fda61
--- /dev/null
+++ b/public/resources/elevation-control.js
@@ -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;
+ }
+ };
diff --git a/public/templates/data.tmpl b/public/templates/data.tmpl
index e4ac4e0..70d3a22 100644
--- a/public/templates/data.tmpl
+++ b/public/templates/data.tmpl
@@ -9,17 +9,19 @@
+
{{/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 = '';
diff --git a/src/serve_data.js b/src/serve_data.js
index d9948e9..cd2e6bb 100644
--- a/src/serve_data.js
+++ b/src/serve_data.js
@@ -203,44 +203,43 @@ export const serve_data = {
if (tileJSON.minzoom == null || tileJSON.maxzoom == null) {
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;
}
- tileCenter = [y, x, y + 0.1, x + 0.1];
- const { minX, minY } = new SphericalMercator().xyz(tileCenter, z);
+ if (zoom > tileJSON.maxzoom) {
+ zoom = tileJSON.maxzoom;
+ }
+ bbox = [x, y, x + 0.1, y + 0.1];
+ const { minX, minY } = new SphericalMercator().xyz(bbox, zoom);
xy = [minX, minY];
}
const fetchTile = await fetchTileData(
source,
sourceType,
- z,
+ zoom,
xy[0],
xy[1],
);
@@ -253,24 +252,39 @@ 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 xPixel = Math.floor(xy[0]);
- const yPixel = Math.floor(xy[1]);
+ const long = bbox[0];
+ const lat = bbox[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');
}
- 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') {
elevation = -10000 + (red * 256 * 256 + green * 256 + blue) * 0.1;
@@ -281,14 +295,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,
}),
);
@@ -403,6 +417,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);
@@ -424,6 +439,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';