v0.3, 16-10-2025 pupicon added

This commit is contained in:
2025-10-16 21:32:19 +01:00
parent fcba00eb3e
commit 95029dfe49
1097 changed files with 5244 additions and 78 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -0,0 +1,236 @@
<!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': ['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, // 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>

View File

@@ -0,0 +1,210 @@
<!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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -0,0 +1,26 @@
/* Pupmap style.css */
body, html {
margin: 0;
padding: 0;
height: 100%;
}
#map {
width: 100%;
height: 100%;
}
#searchBox {
position: absolute;
top: 10px;
left: 10px;
z-index: 999;
width: 220px;
padding: 6px;
border-radius: 4px;
border: 1px solid #ccc;
background: #fff;
font-size: 14px;
}

View File

@@ -0,0 +1,83 @@
{
"version": 8,
"name": "Pupmap by BoopLabs v2025-10-03_1300",
"glyphs":"/basemaps-assets-main/fonts/{fontstack}/{range}.pbf",
"sprite": "https://pups.boop.no/sprites/sprite",
"sources": {
"basemap": {
"type": "vector",
"url": "pmtiles://data/world.pmtiles"
}
},
"layers": [
{
"id": "background",
"type": "background",
"paint": {
"background-color": "#C6F1DC"
}
},
{
"id": "earth",
"type": "fill",
"source": "basemap",
"source-layer": "earth",
"paint": {
"fill-color": "#97E3C1"
}
},
{
"id": "land",
"type": "fill",
"source": "basemap",
"source-layer": "landcover",
"paint": {
"fill-color": "#C6F1DC"
}
},
{
"id": "landuse",
"type": "fill",
"source": "basemap",
"source-layer": "landuse",
"paint": {
"fill-color": "#97E3C1"
}
},
{
"id": "water",
"type": "fill",
"source": "basemap",
"source-layer": "water",
"paint": {
"fill-color": "#7CD5E9"
}
},
{
"id": "boundaries2",
"type": "line",
"source": "basemap",
"source-layer": "boundaries",
"paint": {
"line-color": "#F08650",
"line-width": 1
}
},
{
"id": "boundaries",
"type": "line",
"source": "basemap",
"source-layer": "boundaries",
"filter": [
"<=",
"pmap:min_admin_level",
1
],
"paint": {
"line-color": "#000000",
"line-width": 2
}
}
]
}