Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Elevation API and extend Elevation Preview #1432

Merged
merged 7 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions public/resources/elevation-control.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
class ElevationInfoControl {
Dismissed Show dismissed Hide dismissed
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;
}
};
19 changes: 16 additions & 3 deletions public/templates/data.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
Expand Down Expand Up @@ -69,6 +71,7 @@
};
{{/is_terrain}}
{{#is_terrain}}

var style = {
version: 8,
sources: {
Expand All @@ -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%)"
Expand Down Expand Up @@ -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 = '';
Expand Down
88 changes: 51 additions & 37 deletions src/serve_data.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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];

// 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 xPixel = Math.floor(xy[0]);
const yPixel = Math.floor(xy[1]);
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') {
Expand All @@ -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,
}),
);
Expand Down Expand Up @@ -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);
Expand All @@ -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';

Expand Down
Loading