211 lines
6.7 KiB
HTML
211 lines
6.7 KiB
HTML
<!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
|
||
map.addLayer({
|
||
id: 'city-labels',
|
||
type: 'symbol',
|
||
source: 'cities',
|
||
layout: {
|
||
'text-field': ['get', 'NAME'],
|
||
'text-font': ['Noto Sans Regular'],
|
||
'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, 6, 1000000, 8, 500000, 10, 100000]]
|
||
});
|
||
|
||
// 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);
|
||
});
|
||
|
||
['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
|
||
});
|
||
|
||
// Clusters
|
||
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'
|
||
}
|
||
});
|
||
|
||
// Unclustered POIs with pup icon
|
||
map.loadImage('https://pups.boop.no/data/pupicon64.png', (error, image) => {
|
||
if (error) {
|
||
console.error("Failed to load pup icon", error);
|
||
return;
|
||
}
|
||
if (!map.hasImage('pup')) map.addImage('pup', image, { sdf: false });
|
||
|
||
map.addLayer({
|
||
id: 'unclustered-point',
|
||
type: 'symbol',
|
||
source: 'pois',
|
||
filter: ['!', ['has','point_count']],
|
||
layout: {
|
||
'icon-image': 'pup',
|
||
'icon-size': 1.0,
|
||
'icon-allow-overlap': true,
|
||
'icon-ignore-placement': true
|
||
}
|
||
});
|
||
});
|
||
|
||
map.loadImage('https://pups.boop.no/data/pup_icon_64x64.png', (error, image) => {
|
||
if (error) {
|
||
console.error("Failed to load pup icon", error);
|
||
return;
|
||
}
|
||
console.log("Image loaded:", image);
|
||
if (!map.hasImage('pup')) {
|
||
map.addImage('pup', image, { sdf: false });
|
||
console.log("Image added to map");
|
||
}
|
||
});
|
||
|
||
|
||
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>
|