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

Feature: Terrain Preview and simple Elevation Query #1425

Merged
merged 14 commits into from
Jan 2, 2025
21 changes: 19 additions & 2 deletions docs/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -238,9 +238,26 @@ For example::
}
}


The data source does not need to be specified here unless you explicitly want to serve the raw data.

Serving Terrain Tiles
--------------

If you serve terrain tiles, it is possible to configure an ``encoding`` with ``mapbox`` or ``terrarium`` to enable a terrain preview mode and the ``elevation`` api for the ``data`` endpoint.

For example::

"data": {
"terrain1": {
"mbtiles": "terrain1.mbtiles",
"encoding": "mapbox"
},
"terrain2": {
"pmtiles": "terrain2.pmtiles"
"encoding": "terrarium"
}
}

Referencing local files from style JSON
=======================================

Expand Down Expand Up @@ -283,7 +300,7 @@ For example::
"source3": {
"url": "pmtiles://https://foo.lan/source3.pmtiles",
"type": "vector"
},
}
}

Alternatively, you can use ``pmtiles://{source2}`` to reference existing data object from the config.
Expand Down
8 changes: 8 additions & 0 deletions docs/endpoints.rst
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,14 @@ Source data

* TileJSON at ``/data/{id}.json``

* If terrain mbtile data is served and ``encoding`` is configured (see config) the elevation can be queried

* by ``/data/{id}/elevation/{z}/{x}/{y}`` for the tile

* or ``/data/{id}/elevation/{z}/{long}/{lat}`` for the coordinate

* the result will be a json object like ``{"z":7,"x":68,"y":45,"red":134,"green":66,"blue":0,"latitude":11.84069,"longitude":46.04798,"elevation":1602}``

Static files
===========
* Static files are served at ``/files/{filename}``
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"type": "module",
"scripts": {
"test": "mocha test/**.js --timeout 10000 --exit",
"test-docker": "xvfb-run npm test",
"lint:yml": "yamllint --schema=CORE_SCHEMA *.{yml,yaml}",
"lint:js": "npm run lint:eslint && npm run lint:prettier",
"lint:js:fix": "npm run lint:eslint:fix && npm run lint:prettier:fix",
Expand Down
2 changes: 1 addition & 1 deletion public/resources/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ section {
}
.details h3 {
font-size: 18px;
margin-top: 25px;
margin-top: 5px;
}
.details p {
padding: 0;
Expand Down
150 changes: 108 additions & 42 deletions public/templates/data.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,25 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{name}} - TileServer GL</title>
{{#is_vector}}
{{#use_maplibre}}
<link rel="stylesheet" type="text/css" href="{{public_url}}maplibre-gl.css{{&key_query}}" />
<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>
<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%; }
{{/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;}
</style>
{{/is_vector}}
{{^is_vector}}
{{/use_maplibre}}
{{^use_maplibre}}
<link rel="stylesheet" type="text/css" href="{{public_url}}leaflet.css{{&key_query}}" />
<script src="{{public_url}}leaflet.js{{&key_query}}"></script>
<script src="{{public_url}}leaflet-hash.js{{&key_query}}"></script>
Expand All @@ -37,23 +42,22 @@
background-image: url({{public_url}}images/marker-icon.png{{&key_query}});
}
</style>
{{/is_vector}}
{{/use_maplibre}}
</head>
<body>
{{#is_vector}}
{{#use_maplibre}}
<h1>{{name}}</h1>
<div id="map"></div>
{{^is_terrain}}
<div id="layerList"></div>
<pre id="propertyList"></pre>
{{/is_terrain}}
<script>
var keyMatch = location.search.match(/[\?\&]key=([^&]+)/i);
var keyParam = keyMatch ? '?key=' + keyMatch[1] : '';

var map = new maplibregl.Map({
container: 'map',
hash: true,
maxPitch: 85,
style: {
var keyMatch = location.search.match(/[\?\&]key=([^&]+)/i);
var keyParam = keyMatch ? '?key=' + keyMatch[1] : '';

{{^is_terrain}}
var style = {
version: 8,
sources: {
'vector_layer_': {
Expand All @@ -62,37 +66,99 @@
}
},
layers: []
}
});
map.addControl(new maplibregl.NavigationControl());
var inspect = new MaplibreInspect({
showInspectMap: true,
showInspectButton: false
});
map.addControl(inspect);
map.on('styledata', function() {
var layerList = document.getElementById('layerList');
layerList.innerHTML = '';
Object.keys(inspect.sources).forEach(function(sourceId) {
var layerIds = inspect.sources[sourceId];
layerIds.forEach(function(layerId) {
var item = document.createElement('div');
item.innerHTML = '<div style="' +
'background:' + inspect.assignLayerColor(layerId) + ';' +
'"></div> ' + layerId;
layerList.appendChild(item);
});
})
});
};
{{/is_terrain}}
{{#is_terrain}}
var style = {
version: 8,
sources: {
"terrain": {
"type": "raster-dem",
"url": "{{public_url}}data/{{id}}.json",
"encoding": "{{terrain_encoding}}"
},
"hillshade": {
"type": "raster-dem",
"url": "{{public_url}}data/{{id}}.json",
"encoding": "{{terrain_encoding}}"
}
},
"terrain": {
"source": "terrain"
},
layers: [
{
"id": "background",
"paint": {
{{^if is_terrainrgb}}
"background-color": "hsl(190, 99%, 63%)"
{{else}}
"background-color": "hsl(0, 100%, 25%)"
{{/if}}
},
"type": "background"
},
{
"id": "hillshade",
"source": "hillshade",
"type": "hillshade",
"paint": {
"hillshade-shadow-color": "hsl(39, 21%, 33%)",
"hillshade-illumination-direction": 315,
"hillshade-exaggeration": 0.8
}
}
]
};
{{/is_terrain}}

var map = new maplibregl.Map({
container: 'map',
hash: true,
maxPitch: 85,
style: style
});
map.addControl(new maplibregl.NavigationControl({
visualizePitch: true,
showZoom: true,
showCompass: true
}));
{{#is_terrain}}
map.addControl(
new maplibregl.TerrainControl({
source: "terrain",
})
);
{{/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 = '';
Object.keys(inspect.sources).forEach(function(sourceId) {
var layerIds = inspect.sources[sourceId];
layerIds.forEach(function(layerId) {
var item = document.createElement('div');
item.innerHTML = '<div style="' +
'background:' + inspect.assignLayerColor(layerId) + ';' +
'"></div> ' + layerId;
layerList.appendChild(item);
});
})
});
{{/is_terrain}}
</script>
{{/is_vector}}
{{^is_vector}}
{{/use_maplibre}}
{{^use_maplibre}}
<h1 style="display:none;">{{name}}</h1>
<div id='map'></div>
<script>
var keyMatch = location.search.match(/[\?\&]key=([^&]+)/i);
var keyParam = keyMatch ? '?key=' + keyMatch[1] : '';

var keyMatch = location.search.match(/[\?\&]key=([^&]+)/i);
var keyParam = keyMatch ? '?key=' + keyMatch[1] : '';
var map = L.map('map', { zoomControl: false });
new L.Control.Zoom({ position: 'topright' }).addTo(map);

Expand Down Expand Up @@ -129,7 +195,7 @@
attribution: tile_attribution
}).addTo(map);
}

map.eachLayer(function(layer) {
// do not add scale prefix even if retina display is detected
layer.scalePrefix = '.';
Expand All @@ -141,6 +207,6 @@
new L.Hash(map);
}, 0);
</script>
{{/is_vector}}
{{/use_maplibre}}
</body>
</html>
25 changes: 18 additions & 7 deletions public/templates/index.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@
<title>TileServer GL - Server for vector and raster maps with GL styles</title>
<link rel="stylesheet" type="text/css" href="{{public_url}}index.css{{&key_query}}" />
<script>
function toggle_xyz(id) {
function toggle_link(id, link) {
var el = document.getElementById(id);
var s = el.style;
s.display = s.display == 'none' ? 'inline-block' : 'none';
if (s.display == 'none') {
s.display = 'inline-block';
} else if (el.value == link) {
s.display = 'none';
}
el.value = link;
el.setSelectionRange(0, el.value.length);
return false;
}
Expand Down Expand Up @@ -37,7 +42,7 @@
<div class="filter-details">
<h3>Filter styles and data by name or identifier</h3>
<!-- filter input , needs to call filter() when content changes...-->
<input id="filter" type="text" oninput="filter()" placeholder="Start typing name or identifier" />
<input id="filter" type="text" oninput="filter()" placeholder="Start typing name or identifier" autofocus />
</div>
</div>
</div>
Expand Down Expand Up @@ -66,8 +71,8 @@
| <a href="{{public_url}}styles/{{@key}}/wmts.xml{{&../key_query}}">WMTS</a>
{{/if}}
{{#if xyz_link}}
| <a href="#" onclick="return toggle_xyz('xyz_style_{{@key}}');">XYZ</a>
<input id="xyz_style_{{@key}}" type="text" value="{{&xyz_link}}" style="display:none;" />
| <a href="#" onclick="return toggle_link('xyz_style_{{@key}}', '{{&xyz_link}}');">XYZ</a>
<input id="xyz_style_{{@key}}" type="text" value="" style="display:none;" />
{{/if}}
</p>
</div>
Expand Down Expand Up @@ -105,9 +110,12 @@
<p class="services">
services: <a href="{{public_url}}data/{{@key}}.json{{&../key_query}}">TileJSON</a>
{{#if xyz_link}}
| <a href="#" onclick="return toggle_xyz('xyz_data_{{@key}}');">XYZ</a>
<input id="xyz_data_{{@key}}" type="text" value="{{&xyz_link}}" style="display:none;" />
| <a href="#" onclick="return toggle_link('link_data_{{@key}}', '{{&xyz_link}}');">XYZ</a>
{{/if}}
{{#if elevation_link}}
| <a href="#" onclick="return toggle_link('link_data_{{@key}}', '{{&elevation_link}}');">Elevation</a>
{{/if}}
<input id="link_data_{{@key}}" type="text" value="" style="display:none;" />
</p>
</div>
<div class="viewers">
Expand All @@ -116,6 +124,9 @@
{{/is_vector}}
{{^is_vector}}
<a class="btn" href="{{public_url}}data/{{@key}}/{{&../key_query}}{{viewer_hash}}">View</a>
{{#elevation_link}}
<a class="btn" href="{{public_url}}data/preview/{{@key}}/{{&../key_query}}{{viewer_hash}}">Preview Terrain</a>
{{/elevation_link}}
{{/is_vector}}
</div>
</div>
Expand Down
Loading
Loading