Files
pupmap/www/index.html
2025-10-03 22:48:16 +01:00

238 lines
6.6 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Pupmap by BoopLabs v2025-10-03_2300</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="style.css">
<link href="https://unpkg.com/maplibre-gl/dist/maplibre-gl.css" rel="stylesheet"/>
<script src="https://unpkg.com/maplibre-gl/dist/maplibre-gl.js"></script>
<script src="https://unpkg.com/pmtiles/dist/pmtiles.js"></script>
</head>
<body>
<input id="searchBox" placeholder="Search place..." />
<div id="map"></div>
<script>
const protocol = new pmtiles.Protocol();
maplibregl.addProtocol("pmtiles", protocol.tile);
const map = new maplibregl.Map({
container: 'map',
style: 'style.json',
center: [10, 60],
zoom: 4
});
map.on('load', () => {
// Roads
map.addSource('roads', {
type: 'geojson',
data: 'data/roads_europe_clean.geojson'
});
map.addLayer({
id: 'roads-minor',
type: 'line',
source: 'roads',
filter: ['==', ['get', 'type'], 'minor'],
paint: {
'line-color': '#cccccc',
'line-width': 1,
'line-opacity': 0.5
}
});
map.addLayer({
id: 'roads-major',
type: 'line',
source: 'roads',
filter: ['==', ['get', 'type'], 'major'],
paint: {
'line-color': '#555555',
'line-width': 2,
'line-opacity': 0.7
}
});
// Cities
map.addSource('cities', {
type: 'geojson',
data: 'data/world_cities_europe_clean.geojson'
});
map.addLayer({
id: 'city-points-small',
type: 'circle',
source: 'cities',
filter: ['<', ['get','POP_MAX'], 100000],
paint: {
'circle-radius': 3,
'circle-color': '#999999',
'circle-stroke-width': 1,
'circle-stroke-color': '#666666'
}
});
map.addLayer({
id: 'city-points-medium',
type: 'circle',
source: 'cities',
filter: ['all', ['>=', ['get','POP_MAX'], 100000], ['<', ['get','POP_MAX'], 500000]],
paint: {
'circle-radius': 5,
'circle-color': '#777777',
'circle-stroke-width': 1,
'circle-stroke-color': '#555555'
}
});
map.addLayer({
id: 'city-points-large',
type: 'circle',
source: 'cities',
filter: ['>=', ['get','POP_MAX'], 500000],
paint: {
'circle-radius': 7,
'circle-color': '#444444',
'circle-stroke-width': 1,
'circle-stroke-color': '#222222'
}
});
// City labels, zoom + population based
map.addLayer({
id: 'city-labels',
type: 'symbol',
source: 'cities',
layout: {
'text-field': ['get', 'NAME'],
'text-font': ['Open Sans Bold', 'Arial Unicode MS Bold'],
'text-size': [
'interpolate', ['linear'], ['zoom'],
4, 10,
6, 12,
10, 16
],
'text-offset': [0, 1.2],
'text-anchor': 'top'
},
paint: {
'text-color': '#222222',
'text-halo-color': '#ffffff',
'text-halo-width': 1
},
filter: [
'>',
['get','POP_MAX'],
['interpolate', ['linear'], ['zoom'],
4, 2000000, // at zoom 4, only megacities
6, 1000000, // at zoom 6, large cities
8, 500000, // at zoom 8, medium cities
10, 100000 // at zoom 10+, small cities
]
]
});
// City popups
map.on('click', ['city-points-small','city-points-medium','city-points-large'], (e) => {
const coordinates = e.features[0].geometry.coordinates.slice();
const cityName = e.features[0].properties.NAME || "Unknown City";
const population = e.features[0].properties.POP_MAX || "n/a";
new maplibregl.Popup()
.setLngLat(coordinates)
.setHTML(`<strong>${cityName}</strong><br/>Population: ${population}`)
.addTo(map);
});
// Cursor for city dots
['city-points-small','city-points-medium','city-points-large'].forEach(layer => {
map.on('mouseenter', layer, () => { map.getCanvas().style.cursor = 'pointer'; });
map.on('mouseleave', layer, () => { map.getCanvas().style.cursor = ''; });
});
// POIs
map.addSource('pois', {
type: 'geojson',
data: 'data/pois.geojson',
cluster: true,
clusterMaxZoom: 14,
clusterRadius: 50
});
map.addLayer({
id: 'clusters',
type: 'circle',
source: 'pois',
filter: ['has', 'point_count'],
paint: {
'circle-color': '#FF0000',
'circle-radius': ['step', ['get','point_count'], 15, 10, 20, 50, 25],
'circle-opacity': 0.7,
'circle-stroke-width': 1,
'circle-stroke-color': '#fff'
}
});
map.addLayer({
id: 'unclustered-point',
type: 'circle',
source: 'pois',
filter: ['!', ['has','point_count']],
paint: {
'circle-color': '#FF0000',
'circle-radius': 8,
'circle-opacity': 0.7,
'circle-stroke-width': 1,
'circle-stroke-color': '#fff'
}
});
map.on('click', 'unclustered-point', (e) => {
const coordinates = e.features[0].geometry.coordinates.slice();
const popupContent = e.features[0].properties.popup || e.features[0].properties.names.join(', ');
new maplibregl.Popup()
.setLngLat(coordinates)
.setHTML(`<strong>${popupContent}</strong>`)
.addTo(map);
});
map.on('click', 'clusters', (e) => {
const features = map.queryRenderedFeatures(e.point, { layers: ['clusters'] });
const clusterId = features[0].properties.cluster_id;
map.getSource('pois').getClusterExpansionZoom(clusterId, (err, zoom) => {
if (err) return;
map.easeTo({ center: features[0].geometry.coordinates, zoom: zoom });
});
});
['clusters', 'unclustered-point'].forEach(layer => {
map.on('mouseenter', layer, () => { map.getCanvas().style.cursor = 'pointer'; });
map.on('mouseleave', layer, () => { map.getCanvas().style.cursor = ''; });
});
});
// Nominatim search
document.getElementById('searchBox').addEventListener('keypress', async function(e){
if(e.key==='Enter'){
const query = e.target.value;
const url = `http://yourhost:7070/search?q=${encodeURIComponent(query)}&format=json`;
try{
const res = await fetch(url);
const results = await res.json();
if(results.length>0){
const loc = results[0];
map.flyTo({ center: [loc.lon, loc.lat], zoom: 12 });
}else{ alert("No results found"); }
}catch(err){ alert("Error connecting to search API"); console.error(err);}
}
});
</script>
</body>
</html>