terrain_pmtiles/public/index.html
2026-02-18 22:04:45 +01:00

200 lines
No EOL
6.4 KiB
HTML

<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="utf-8" />
<title>Basemap Protomaps + PMTiles (locale)</title>
<!-- CSS MapLibre -->
<link rel="stylesheet" href="/assets/maplibre/maplibre-gl.css">
<style>
html,body,#map{height:100%;margin:0}
.ctrl{
position:absolute;z-index:10;top:8px;left:8px;
background:rgba(255,255,255,.85);padding:6px 8px;border-radius:6px;
font:13px/1.2 system-ui,-apple-system,Segoe UI,Roboto,sans-serif
}
.ctrl label{display:inline-flex;gap:.4rem;align-items:center;cursor:pointer}
</style>
</head>
<body>
<div id="map"></div>
<div class="ctrl">
<label title="Amplificazione verticale del terreno">
Exaggeration:
<input id="exag" type="range" min="0.8" max="2.5" step="0.1" value="1.6">
<span id="exagv">1.6</span>x
</label>
</div>
<!-- Librerie locali -->
<script src="/assets/maplibre/maplibre-gl.js"></script>
<script src="/assets/pmtiles/pmtiles.js"></script>
<script src="/assets/basemaps/basemaps.js" crossorigin="anonymous"></script>
<!-- Plugin per curve di livello (on-the-fly) -->
<script src="https://unpkg.com/maplibre-contour@0.1.0/dist/index.min.js"></script>
<script>
// 1) PMTiles protocol
const protocol = new pmtiles.Protocol({ metadata: true });
maplibregl.addProtocol("pmtiles", protocol.tile);
// 2) Basemap vettoriale (MVT) in PMTiles sotto /tiles
const PMTILES_URL = "tiles/planet.pmtiles"; // basemap MVT
protocol.add(new pmtiles.PMTiles(PMTILES_URL));
// 3) Asset locali
const origin = window.location.origin;
const spriteUrl = `${origin}/assets/sprites/v4/light`;
const glyphsUrl = `${origin}/assets/fonts/{fontstack}/{range}.pbf`;
//const glyphsUrl = 'https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf';
const terrainDEM = `${origin}/data/terrain.pmtiles`; // DEM Terrarium (PMTiles locale)
// 4) Mappa
const map = window.map = new maplibregl.Map({
container: "map",
style: {
version: 8,
glyphs: glyphsUrl,
sprite: spriteUrl,
sources: {
protomaps: {
type: "vector",
url: "pmtiles://" + PMTILES_URL,
attribution: 'Protomaps © OpenStreetMap'
}
},
layers: basemaps.layers("protomaps", basemaps.namedFlavor("light"), { lang: "en" })
},
center: [12.05, 44.22],
zoom: 8,
pitch: 60,
maxPitch: 85
});
// Fallback icone mancanti nello sprite (es. "townhall")
map.on('styleimagemissing', (e) => {
const id = e.id;
if (!map.hasImage(id)) {
const empty = new Uint8ClampedArray([0,0,0,0]); // 1x1 trasparente
map.addImage(id, { width:1, height:1, data: empty });
}
});
map.on('load', () => {
// === A) Terreno 3D locale (PMTiles) + hillshade su sorgente separata ===
map.addSource('terrain-dem', {
type: 'raster-dem',
url: 'pmtiles://' + terrainDEM,
encoding: 'terrarium',
tileSize: 256
});
map.addSource('hillshade-dem', {
type: 'raster-dem',
url: 'pmtiles://' + terrainDEM,
encoding: 'terrarium',
tileSize: 256
});
map.setTerrain({ source: 'terrain-dem', exaggeration: 1.6 });
const labelLayerId = map.getStyle().layers.find(l => l.type === 'symbol')?.id;
map.addLayer({
id: 'hillshade',
type: 'hillshade',
source: 'hillshade-dem',
paint: {
'hillshade-exaggeration': 0.5,
'hillshade-illumination-direction': 315
}
}, labelLayerId);
// === B) Curve di livello on-the-fly (DEM online Z/X/Y) ===
// - Usa AWS Joerd Terrarium per test immediato (Z/X/Y).
// - maplibre-contour genera isoipse in WebWorker e le espone come vector tiles.
const demSource = new mlcontour.DemSource({
url: 'https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png',
encoding: 'terrarium',
maxzoom: 12,
worker: true
});
demSource.setupMaplibre(maplibregl);
// (facoltativo) hillshade che riusa la cache del plugin:
map.addSource('hills-contour-cache', {
type: 'raster-dem',
tiles: [demSource.sharedDemProtocolUrl],
tileSize: 512,
maxzoom: 12
});
// Sorgente vettoriale delle curve (fornita dal plugin)
map.addSource('contourSource', {
type: 'vector',
tiles: [
demSource.contourProtocolUrl({
multiplier: 1, // 1 = metri (3.28084 per piedi)
thresholds: {
11: [200, 1000],
12: [100, 500],
13: [50, 200],
14: [25, 100],
15: [10, 50]
},
contourLayer: 'contours',
elevationKey: 'ele',
levelKey: 'level' // 0 = minore, 1 = maggiore
})
],
maxzoom: 15
});
// Linee isoipse
map.addLayer({
id: 'contours',
type: 'line',
source: 'contourSource',
'source-layer': 'contours',
paint: {
'line-color': '#6b6b6b',
'line-opacity': 0.6,
'line-width': ['match', ['get', 'level'], 1, 1.2, 0.6]
}
}, labelLayerId);
// Etichette solo sulle maggiori
map.addLayer({
id: 'contour-labels',
type: 'symbol',
source: 'contourSource',
'source-layer': 'contours',
filter: ['==', ['get', 'level'], 1],
layout: {
'symbol-placement': 'line',
'text-field': ['number-format', ['get', 'ele'], {}],
'text-size': 11,
'text-font': ['Noto Sans Bold']
},
paint: { 'text-halo-color': 'white', 'text-halo-width': 1 }
}, labelLayerId);
// Slider con THROTTLE (riduce ricalcoli durante il drag)
const exIn = document.getElementById('exag');
const exSp = document.getElementById('exagv');
let tId = null, pending = null;
const APPLY_EVERY_MS = 150;
const applyExag = (v) => {
exSp.textContent = v.toFixed(1);
map.setTerrain({ source: 'terrain-dem', exaggeration: v });
};
exIn.addEventListener('input', (e) => {
pending = parseFloat(e.target.value);
if (tId) return;
tId = setTimeout(() => { applyExag(pending); tId = null; }, APPLY_EVERY_MS);
});
exIn.addEventListener('change', (e) => applyExag(parseFloat(e.target.value)));
});
map.addControl(new maplibregl.NavigationControl({ visualizePitch: true }), "top-right");
</script>
</body>
</html>