Skip to content

Commit

Permalink
feature: add layer control to map viewer html template (#1051)
Browse files Browse the repository at this point in the history
* make placemarks toggleable with a button or the 'b' key

---------

Co-authored-by: Vincent Sarago <[email protected]>
  • Loading branch information
hrodmn and vincentsarago authored Dec 12, 2024
1 parent 4937dfa commit fb1bb1e
Show file tree
Hide file tree
Showing 2 changed files with 223 additions and 140 deletions.
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

### titiler.core
* Add layer control to map viewer template (author @hrodmn, https://github.com/developmentseed/titiler/pull/1051)

## 0.19.2 (2024-11-28)

### Misc
Expand Down
360 changes: 220 additions & 140 deletions src/titiler/core/titiler/core/templates/map.html
Original file line number Diff line number Diff line change
@@ -1,146 +1,226 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<title>TiTiler Map Viewer</title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css"
integrity="sha384-o/2yZuJZWGJ4s/adjxVW71R+EO/LyCwdQfP5UWSgX/w87iiTXuvDZaejd3TsN7mf"
crossorigin="anonymous"/>
<script src="https://unpkg.com/[email protected]/dist/leaflet.js"
integrity="sha384-okbbMvvx/qfQkmiQKfd5VifbKZ/W8p1qIsWvE1ROPUfHWsDcC8/BnHohF7vPg2T6"
crossorigin="anonymous"></script>
<script src="https://unpkg.com/[email protected]/dist/proj4.js"
integrity="sha384-R7x++v2MKcATI+D1/GJsn636xbHca492Sdpm8BD36lj5vdWB9+OUBpM1oKkrzqv9"
crossorigin="anonymous"></script>
<script src="https://unpkg.com/[email protected]/src/proj4leaflet.js"
integrity="sha384-aDnBHDK9AhLbrYhThBxEVMriFbix8Sz2059IlD3HbZhz7+WNmz+pSkOcI7MY72cE"
crossorigin="anonymous"></script>
<style>
body { margin:0; padding:0; width:100%; height:100%; background-color: #e5e5e5;}
#map { position:absolute; top:0; bottom:0; width:100%; }
</style>
</head>
<body>

<div id='map'></div>
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>TiTiler Map Viewer</title>
<meta
name="viewport"
content="initial-scale=1,maximum-scale=1,user-scalable=no"
/>
<link
rel="stylesheet"
href="https://unpkg.com/[email protected]/dist/leaflet.css"
integrity="sha384-o/2yZuJZWGJ4s/adjxVW71R+EO/LyCwdQfP5UWSgX/w87iiTXuvDZaejd3TsN7mf"
crossorigin="anonymous"
/>
<script
src="https://unpkg.com/[email protected]/dist/leaflet.js"
integrity="sha384-okbbMvvx/qfQkmiQKfd5VifbKZ/W8p1qIsWvE1ROPUfHWsDcC8/BnHohF7vPg2T6"
crossorigin="anonymous"
></script>
<script
src="https://unpkg.com/[email protected]/dist/proj4.js"
integrity="sha384-R7x++v2MKcATI+D1/GJsn636xbHca492Sdpm8BD36lj5vdWB9+OUBpM1oKkrzqv9"
crossorigin="anonymous"
></script>
<script
src="https://unpkg.com/[email protected]/src/proj4leaflet.js"
integrity="sha384-aDnBHDK9AhLbrYhThBxEVMriFbix8Sz2059IlD3HbZhz7+WNmz+pSkOcI7MY72cE"
crossorigin="anonymous"
></script>
<style>
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background-color: #e5e5e5;
}
#map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
</style>
</head>
<body>
<div id="map"></div>

<script type="text/javascript">

const bboxPolygon = (bounds) => {
var LL_EPSILON = 1e-6
return {
'type': 'Feature',
'geometry': {
'type': 'Polygon',
'coordinates': [[
[bounds[0] + LL_EPSILON, bounds[1] + LL_EPSILON],
[bounds[2] - LL_EPSILON, bounds[1] + LL_EPSILON],
[bounds[2] - LL_EPSILON, bounds[3] - LL_EPSILON],
[bounds[0] + LL_EPSILON, bounds[3] - LL_EPSILON],
[bounds[0] + LL_EPSILON, bounds[1] + LL_EPSILON]
]]
},
'properties': {}
}
}

var crs = new L.Proj.CRS(
'{{ tms.crs.srs }}',
'{{ tms.crs.to_proj4() }}', {
origin: [{{ tms.xy_bbox.left }}, {{ tms.xy_bbox.top }}],
bounds: L.bounds(
L.Point({{ tms.xy_bbox.left}}, {{ tms.xy_bbox.bottom }}),
L.Point({{ tms.xy_bbox.right}}, {{ tms.xy_bbox.top }})
),
resolutions: {{ resolutions|safe }},
}
);


var map = L.map('map', {
crs: crs,
minZoom: {{ tms.minzoom }},
maxZoom: {{ tms.maxzoom }},
attributionControl: false
});

L.control.attribution({prefix: '<a href="https://leafletjs.com" target="_blank">Leaflet</a> | <a href="https://developmentseed.org/titiler" target="_blank">Titiler</a>'}).addTo(map)

const nullIsland = L.marker([0, 0]).addTo(map);
const madrid = L.marker([40, -3]).addTo(map);
const london = L.marker([51.50722, -0.1275]).addTo(map)
const auckland = L.marker([-36.864664, 174.792059]).addTo(map);
const seattle = L.marker([47.596842, -122.333087]).addTo(map);

if ("{{ tms.id }}" === "WebMercatorQuad") {
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
}

fetch('{{ tilejson_endpoint|safe }}')
.then(res => {
if (res.ok) return res.json()
throw new Error('Network response was not ok.')
})
.then(data => {
console.log(data)

let bounds = [...data.bounds]

var left = bounds[0],
bottom = bounds[1],
right = bounds[2],
top = bounds[3];

if (left < right) {
left = Math.max(left, {{ tms.bbox.left }})
right = Math.min(right, {{ tms.bbox.right }})
}
bottom = Math.max(bottom, {{ tms.bbox.bottom }})
top = Math.min(top, {{ tms.bbox.top }})
console.log(left, bottom, right, top)

let geo;
// handle files that span accross dateline
if (left > right) {
geo = {
"type": "FeatureCollection",
"features": [
bboxPolygon([-180, bottom, right, top]),
bboxPolygon([left, bottom, 180, top]),
]
}
} else {
geo = {
"type": "FeatureCollection",
"features": [bboxPolygon([left, bottom, right, top])]
const bboxPolygon = (bounds) => {
var LL_EPSILON = 1e-6
return {
'type': 'Feature',
'geometry': {
'type': 'Polygon',
'coordinates': [[
[bounds[0] + LL_EPSILON, bounds[1] + LL_EPSILON],
[bounds[2] - LL_EPSILON, bounds[1] + LL_EPSILON],
[bounds[2] - LL_EPSILON, bounds[3] - LL_EPSILON],
[bounds[0] + LL_EPSILON, bounds[3] - LL_EPSILON],
[bounds[0] + LL_EPSILON, bounds[1] + LL_EPSILON]
]]
},
'properties': {}
}
}
}
console.log(geo)

var aoi = L.geoJSON(geo, {
color: '#3bb2d0', fill: false
}).addTo(map);
map.fitBounds(aoi.getBounds());

// Bounds crossing dateline
if (left > right) {
left = right - 360
}

L.tileLayer(
data.tiles[0], {
maxNativeZoom: data.maxzoom,
minNativeZoom: data.minzoom,
bounds: L.latLngBounds([bottom, left], [top, right]),

var crs = new L.Proj.CRS(
'{{ tms.crs.srs }}',
'{{ tms.crs.to_proj4() }}', {
origin: [{{ tms.xy_bbox.left }}, {{ tms.xy_bbox.top }}],
bounds: L.bounds(
L.Point({{ tms.xy_bbox.left}}, {{ tms.xy_bbox.bottom }}),
L.Point({{ tms.xy_bbox.right}}, {{ tms.xy_bbox.top }})
),
resolutions: {{ resolutions|safe }},
}
);


var map = L.map('map', {
crs: crs,
minZoom: {{ tms.minzoom }},
maxZoom: {{ tms.maxzoom }},
attributionControl: false
});

L.control.attribution({prefix: '<a href="https://leafletjs.com" target="_blank">Leaflet</a> | <a href="https://developmentseed.org/titiler" target="_blank">Titiler</a>'}).addTo(map)

let baseLayers = {};
let overlayLayers = {};
let activeBaseLayer = null;

if ("{{ tms.id }}" === "WebMercatorQuad") {

const esriSatelliteLayer = L.tileLayer(
'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
attribution: 'Tiles &copy; ESRI &mdash; Source: Esri, DigitalGlobe, GeoEye, i-cubed, USDA FSA, USGS, AEX, Getmapping, Aerogrid, IGN, IGP, swisstopo, and the GIS User Community',
maxZoom: 19
}
);
esriSatelliteLayer.addTo(map);
baseLayers["Esri Satellite"] = esriSatelliteLayer;

const osmLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
});
osmLayer.addTo(map);
baseLayers["OpenStreetMap"] = osmLayer;
activeBaseLayer = osmLayer;
}
).addTo(map);
})
.catch(err => {
console.warn(err)
})

// Add some placemarks for use in validating display with custom TMS
var nullIsland = L.marker([0, 0]),
madrid = L.marker([40, -3]),
london = L.marker([51.50722, -0.1275]),
auckland = L.marker([-36.864664, 174.792059]),
seattle = L.marker([47.596842, -122.333087]);

var cities = L.layerGroup([nullIsland, london, auckland, seattle]);

document.addEventListener('keydown', function(e) {
if (e.key === 'b' || e.key === 'B') {
if (map.hasLayer(cities)) {
map.removeLayer(cities);
} else {
map.addLayer(cities);
}
}
});

const toggleButton = L.control({position: 'bottomright'});
toggleButton.onAdd = function(map) {
const div = L.DomUtil.create('div', 'toggle-cities');
div.innerHTML = `
<button style="
font-size: 10px;
padding: 2px 5px;
background: rgba(255, 255, 255, 0.8);
border: 1px solid #ccc;
border-radius: 3px;
cursor: pointer;
">⚲</button>
`;
div.onclick = function() {
if (map.hasLayer(cities)) {
map.removeLayer(cities);
} else {
map.addLayer(cities);
}
};
return div;
};
toggleButton.addTo(map);

// Add the tile layer!
fetch('{{ tilejson_endpoint|safe }}')
.then(res => {
if (res.ok) return res.json()
throw new Error('Network response was not ok.')
})
.then(data => {
console.log(data);

let bounds = [...data.bounds];

var left = bounds[0],
bottom = bounds[1],
right = bounds[2],
top = bounds[3];

if (left < right) {
left = Math.max(left, {{ tms.bbox.left }});
right = Math.min(right, {{ tms.bbox.right }});
}
bottom = Math.max(bottom, {{ tms.bbox.bottom }});
top = Math.min(top, {{ tms.bbox.top }});

let geo;
if (left > right) {
geo = {
"type": "FeatureCollection",
"features": [
bboxPolygon([-180, bottom, right, top]),
bboxPolygon([left, bottom, 180, top]),
]
};
} else {
geo = {
"type": "FeatureCollection",
"features": [bboxPolygon([left, bottom, right, top])]
};
}

var aoi = L.geoJSON(geo, {
color: '#3bb2d0', fill: false
}).addTo(map);
map.fitBounds(aoi.getBounds());

if (left > right) {
left = right - 360;
}

const tileLayer = L.tileLayer(
data.tiles[0], {
maxNativeZoom: data.maxzoom,
minNativeZoom: data.minzoom,
bounds: L.latLngBounds([bottom, left], [top, right]),
}
);

overlayLayers["TiTiler Layer"] = tileLayer;

tileLayer.addTo(map);

// Add L.control.layers with base and overlay layers
L.control.layers(baseLayers, overlayLayers, {collapsed: false}).addTo(map);
})
.catch(err => {
console.warn(err);
});
</script>
</body>
</html>
</body>
</html>

0 comments on commit fb1bb1e

Please sign in to comment.