Dies ist eine alte Version des Dokuments!
Siehe auch OpenRouteService.org, Tileserver Docker Github Repo, Tileserver auf Ubuntu22.04 installieren, MapLibre, OSRM, Nominatim, OpenTripPlanner, PBF, PMTiles, MBTiles, Osmium, GTFS
Geofabrik PBF Extract oder den ganzen Planeten
curl https://download.geofabrik.de/europe/austria-latest.osm.pbf -o austria-latest.osm.pbf curl https://download.geofabrik.de/europe/austria.poly -o austria.poly
import folium # Koordinaten für den Kartenmittelpunkt latitude = 48.2082 longitude = 16.3738 # Karte erstellen map_osm = folium.Map(location=[latitude, longitude], zoom_start=12) # Marker hinzufügen folium.Marker([latitude, longitude], popup='Wien').add_to(map_osm) # Karte anzeigen map_osm.save('map.html')
import folium import overpy # Koordinaten für den Kartenmittelpunkt latitude = 48.2082 longitude = 16.3738 # Overpass API-Abfrage erstellen api = overpy.Overpass() # Overpass API-Abfrage ausführen result = api.query(f'node(around:2000, {latitude}, {longitude})["highway"];out;') # Karte erstellen map_osm = folium.Map(location=[latitude, longitude], zoom_start=14) # Straßennamen hinzufügen for node in result.nodes: if 'name' in node.tags: folium.Marker([node.lat, node.lon], popup=node.tags['name']).add_to(map_osm) # Karte anzeigen map_osm.save('map.html')
import requests # Overpass API-Abfrage für Straßen in Wien overpass_url = "http://overpass-api.de/api/interpreter" query = ''' [out:xml]; area["name"="Wien"]->.a; way(area.a)["highway"]; out; ''' response = requests.get(overpass_url, params={'data': query}) # Daten lokal speichern with open('wien_streets.osm', 'wb') as file: file.write(response.content)
import folium import xml.etree.ElementTree as ET # XML-Daten laden tree = ET.parse('wien_streets.osm') root = tree.getroot() # Koordinaten für den Kartenmittelpunkt latitude = 48.2082 longitude = 16.3738 # Karte erstellen map_osm = folium.Map(location=[latitude, longitude], zoom_start=14) # Straßennamen hinzufügen for way in root.findall(".//way"): street_name = None coords = [] for tag in way.findall(".//tag"): if tag.get('k') == 'name': street_name = tag.get('v') break if street_name: for nd in way.findall(".//nd"): ref = nd.get('ref') node = root.find(f".//node[@id='{ref}']") if node is not None: lat = float(node.get('lat')) lon = float(node.get('lon')) coords.append([lat, lon]) if coords: folium.PolyLine(coords, color="blue", weight=2, popup=street_name).add_to(map_osm) # Karte anzeigen map_osm.save('map.html')
Marker setzen
<!DOCTYPE html> <html> <head> <title>Wien Karte mit Marker</title> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" /> <style> #map { height: 100vh; } </style> </head> <body> <div id="map"></div> <script src="https://unpkg.com/leaflet/dist/leaflet.js"></script> <script> const map = L.map('map').setView([48.2082, 16.3738], 13); // Wien-Zentrum // OSM-Tiles L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© OpenStreetMap-Mitwirkende' }).addTo(map); // Marker hinzufügen const marker = L.marker([48.2082, 16.3738]).addTo(map); // Wien-Zentrum marker.bindPopup("<b>Wien!</b><br>Hauptstadt von Österreich").openPopup(); // Weitere Marker hinzufügen (z. B. Stephansdom) const stephansdom = L.marker([48.2064, 16.3705]).addTo(map); // Stephansdom stephansdom.bindPopup("<b>Stephansdom</b><br>Berühmte Kirche in Wien").openPopup(); </script> </body> </html>
Marker mit Adresse setzen (Geocoder)
<!DOCTYPE html> <html> <head> <title>Wien Karte mit Adresse und Marker</title> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" /> <link rel="stylesheet" href="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.css" /> <style> #map { height: 100vh; } </style> </head> <body> <div id="map"></div> <script src="https://unpkg.com/leaflet/dist/leaflet.js"></script> <script src="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.js"></script> <script> const map = L.map('map').setView([48.2082, 16.3738], 13); // Wien-Zentrum // OSM-Tiles L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© OpenStreetMap-Mitwirkende' }).addTo(map); // Geocoding: Adresse eingeben und Marker setzen const geocoder = L.Control.Geocoder.nominatim(); // Beispiel: Adresse "Stephansdom, Wien" in Koordinaten umwandeln und Marker setzen geocoder.geocode("Stephansdom, Wien", function(results) { const latlng = results[0].center; const marker = L.marker(latlng).addTo(map); marker.bindPopup("<b>Stephansdom</b><br>Berühmte Kirche in Wien").openPopup(); map.setView(latlng, 16); // Karte auf Marker zentrieren }); </script> </body> </html>
<!DOCTYPE html> <html> <head> <title>Wien Karte mit Adresse und Marker</title> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" /> <style> #map { height: 100vh; } </style> </head> <body> <div id="map"></div> <script src="https://unpkg.com/leaflet/dist/leaflet.js"></script> <script> // Initialisiere die Karte mit Wien als Mittelpunkt const map = L.map('map').setView([48.2082, 16.3738], 13); // OSM-Tiles L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© OpenStreetMap-Mitwirkende' }).addTo(map); // Geocoding API (Nominatim) für eine Adresse function geocodeAddress(address) { fetch(`https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(address)}`) .then(response => response.json()) .then(data => { if (data.length > 0) { const latlng = [data[0].lat, data[0].lon]; // Koordinaten der ersten Antwort const marker = L.marker(latlng).addTo(map); marker.bindPopup(`<b>${address}</b><br>Gefunden in OSM`).openPopup(); map.setView(latlng, 16); // Karte auf den Marker zentrieren } else { alert("Adresse nicht gefunden!"); } }) .catch(error => console.error('Geocoding-Fehler:', error)); } // Beispiel: Geocode für "Stephansdom, Wien" geocodeAddress("Stephansdom, Wien"); </script> </body> </html>
<!DOCTYPE html> <html> <head> <title>Wien Karte mit Adresse, Marker und Weg</title> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" /> <link rel="stylesheet" href="https://unpkg.com/leaflet-routing-machine/dist/leaflet-routing-machine.css" /> <style> #map { height: 100vh; } </style> </head> <body> <div id="map"></div> <script src="https://unpkg.com/leaflet/dist/leaflet.js"></script> <script src="https://unpkg.com/leaflet-routing-machine/dist/leaflet-routing-machine.js"></script> <script> // Initialisiere die Karte mit Wien als Mittelpunkt const map = L.map('map').setView([48.2082, 16.3738], 13); // OSM-Tiles L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© OpenStreetMap-Mitwirkende' }).addTo(map); // Geocoding API (Nominatim) für eine Adresse function geocodeAddress(address) { return fetch(`https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(address)}`) .then(response => response.json()) .then(data => { if (data.length > 0) { const latlng = [data[0].lat, data[0].lon]; return latlng; } else { alert("Adresse nicht gefunden!"); return null; } }) .catch(error => { console.error('Geocoding-Fehler:', error); return null; }); } // Beispiel: Geocode für "Stephansdom, Wien" und "Prater, Wien" async function drawRoute() { const startAddress = "Stephansdom, Wien"; const endAddress = "Prater, Wien"; const startCoords = await geocodeAddress(startAddress); const endCoords = await geocodeAddress(endAddress); if (startCoords && endCoords) { // Route mit Leaflet Routing Machine zeichnen L.Routing.control({ waypoints: [ L.latLng(startCoords), L.latLng(endCoords) ], routeWhileDragging: true }).addTo(map); } } // Route zeichnen drawRoute(); </script> </body> </html>
Entweder direkt MBTiles von OpenMapTiles herunterladen und zu PMTiles konvertieren. Man kann aber auch das ganze PBF File herunterladen.
pmtiles convert austria.mbtiles austria.pmtiles
Website (mit verschiedenen Features)
<!DOCTYPE html> <html lang="de"> <head> <meta charset="utf-8" /> <title>Österreich-Karte Pro</title> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="stylesheet" href="https://unpkg.com/maplibre-gl@5.21.0/dist/maplibre-gl.css" /> <script src="https://unpkg.com/maplibre-gl@5.21.0/dist/maplibre-gl.js"></script> <script src="https://unpkg.com/pmtiles@3.2.0/dist/pmtiles.js"></script> <style> html, body { height: 100%; margin: 0; font-family: Inter, Arial, sans-serif; } #app { display: grid; grid-template-columns: 380px 1fr; height: 100%; } #sidebar { background: #111827; color: #f9fafb; padding: 18px; overflow: auto; box-sizing: border-box; border-right: 1px solid #1f2937; } #map { height: 100%; width: 100%; } h1 { font-size: 22px; margin: 0 0 6px 0; } .sub { color: #9ca3af; font-size: 13px; margin-bottom: 18px; } .group { background: #1f2937; border: 1px solid #374151; border-radius: 12px; padding: 14px; margin-bottom: 14px; } .group h2 { font-size: 15px; margin: 0 0 12px 0; color: #fff; } label { display: block; font-size: 12px; color: #cbd5e1; margin: 8px 0 6px; } input, button { width: 100%; box-sizing: border-box; border-radius: 10px; border: 1px solid #4b5563; padding: 10px 12px; font-size: 14px; } input { background: #111827; color: #fff; } button { background: #2563eb; color: white; border: none; cursor: pointer; font-weight: 600; margin-top: 10px; } button:hover { background: #1d4ed8; } .btn-secondary { background: #374151; } .btn-secondary:hover { background: #4b5563; } .row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; } .mini { font-size: 12px; color: #cbd5e1; line-height: 1.4; } .status { margin-top: 10px; padding: 10px 12px; border-radius: 10px; background: #0f172a; color: #e5e7eb; font-size: 13px; min-height: 18px; } .result-box { margin-top: 10px; padding: 10px 12px; border-radius: 10px; background: #0b1220; color: #f8fafc; font-size: 13px; line-height: 1.5; } .chip { display: inline-block; background: #0b1220; color: #bfdbfe; border: 1px solid #1d4ed8; padding: 6px 8px; border-radius: 999px; font-size: 12px; margin: 4px 6px 0 0; } .marker-pin { width: 18px; height: 18px; border-radius: 50%; border: 3px solid white; box-shadow: 0 0 0 2px rgba(0,0,0,0.25); } .marker-start { background: #16a34a; } .marker-end { background: #dc2626; } .marker-free { background: #7c3aed; } .marker-user { background: #2563eb; } #directions ol { margin: 8px 0 0 18px; padding: 0; } #directions li { margin-bottom: 8px; } @media (max-width: 900px) { #app { grid-template-columns: 1fr; grid-template-rows: 420px 1fr; } } </style> </head> <body> <div id="app"> <aside id="sidebar"> <h1>Österreich-Karte Pro</h1> <div class="sub">PMTiles + MapLibre + Geocoding + Routing + Textanweisungen</div> <div class="group"> <h2>Route planen</h2> <label for="startInput">Start</label> <input id="startInput" type="text" placeholder="z. B. Liebenstraße 36, Graz" /> <label for="endInput">Ziel</label> <input id="endInput" type="text" placeholder="z. B. Hauptplatz 1, Linz" /> <div class="row"> <button id="searchStartBtn">Start suchen</button> <button id="searchEndBtn">Ziel suchen</button> </div> <button id="routeBtn">Route berechnen</button> <button id="swapBtn" class="btn-secondary">Start/Ziel tauschen</button> <button id="clearRouteBtn" class="btn-secondary">Route löschen</button> <button id="copyLinkBtn" class="btn-secondary">Link kopieren</button> <div id="routeInfo" class="result-box">Noch keine Route.</div> </div> <div class="group"> <h2>Wegbeschreibung</h2> <div id="directions" class="result-box">Noch keine Wegbeschreibung.</div> </div> <div class="group"> <h2>Meine Position</h2> <button id="locateBtn">Zu meiner GPS-Position</button> <div class="mini">Auf localhost funktioniert Geolocation in Browsern meist direkt. Auf echter Domain meist nur mit HTTPS.</div> </div> <div class="group"> <h2>Freie Marker</h2> <button id="enableMarkerModeBtn" class="btn-secondary">Klick-Marker einschalten</button> <button id="clearMarkersBtn" class="btn-secondary">Freie Marker löschen</button> <div class="mini">Wenn aktiv, setzt ein Klick auf die Karte einen eigenen Marker.</div> <div id="markerModeState" class="status">Klick-Marker: aus</div> </div> <div class="group"> <h2>Suche & Treffer</h2> <div id="searchInfo" class="result-box">Noch keine Suche.</div> </div> <div class="group"> <h2>Hinweise</h2> <div class="mini"> Dieses Beispiel nutzt öffentliche Geocoding- und Routing-Endpoints. Für Produktion solltest du Geocoding und Routing selbst hosten oder einen passenden Anbieter verwenden. </div> <div class="chip">PMTiles lokal</div> <div class="chip">Geocoding via Nominatim</div> <div class="chip">Routing via OSRM</div> <div class="chip">Link via GET</div> </div> <div id="status" class="status">Bereit.</div> </aside> <div id="map"></div> </div> <script> const PMTILES_URL = "http://localhost/at.pmtiles"; const NOMINATIM_SEARCH_URL = "https://nominatim.openstreetmap.org/search"; const OSRM_ROUTE_BASE = "https://router.project-osrm.org/route/v1/driving"; const AUSTRIA_BBOX = [9.4, 46.3, 17.3, 49.1]; const protocol = new pmtiles.Protocol(); maplibregl.addProtocol("pmtiles", protocol.tile); const archive = new pmtiles.PMTiles(PMTILES_URL); protocol.add(archive); let map; let startMarker = null; let endMarker = null; let userMarker = null; let freeMarkers = []; let markerMode = false; let startCoord = null; let endCoord = null; const statusEl = document.getElementById("status"); const searchInfoEl = document.getElementById("searchInfo"); const routeInfoEl = document.getElementById("routeInfo"); const directionsEl = document.getElementById("directions"); const markerModeStateEl = document.getElementById("markerModeState"); function setStatus(msg) { statusEl.textContent = msg; } function makeMarkerElement(cls) { const el = document.createElement("div"); el.className = `marker-pin ${cls}`; return el; } function formatKm(meters) { if (meters < 1000) return `${Math.round(meters)} m`; return `${(meters / 1000).toFixed(1)} km`; } function formatDuration(seconds) { const h = Math.floor(seconds / 3600); const m = Math.round((seconds % 3600) / 60); if (h > 0) return `${h} h ${m} min`; return `${m} min`; } function formatMeters(m) { if (m < 1000) return `${Math.round(m)} m`; return `${(m / 1000).toFixed(1)} km`; } function setSearchInfo(title, item) { const name = item.display_name || "Kein Name"; const lat = Number(item.lat).toFixed(6); const lon = Number(item.lon).toFixed(6); searchInfoEl.innerHTML = ` <b>${title}</b><br> ${name}<br> <span style="color:#93c5fd">Lat:</span> ${lat}, <span style="color:#93c5fd">Lon:</span> ${lon} `; } function getInitialView(defaultLng, defaultLat, defaultZoom) { const params = new URLSearchParams(window.location.search); const lng = parseFloat(params.get("lng")); const lat = parseFloat(params.get("lat")); const z = parseFloat(params.get("z")); return { lng: Number.isFinite(lng) ? lng : defaultLng, lat: Number.isFinite(lat) ? lat : defaultLat, zoom: Number.isFinite(z) ? z : defaultZoom }; } function updateUrlFromMap() { if (!map) return; const center = map.getCenter(); const zoom = map.getZoom(); const url = new URL(window.location.href); url.searchParams.set("lng", center.lng.toFixed(6)); url.searchParams.set("lat", center.lat.toFixed(6)); url.searchParams.set("z", zoom.toFixed(2)); history.replaceState(null, "", url.toString()); } function updateUrlRouteParams() { const url = new URL(window.location.href); if (startCoord) { url.searchParams.set("start", `${startCoord[0].toFixed(6)},${startCoord[1].toFixed(6)}`); } else { url.searchParams.delete("start"); } if (endCoord) { url.searchParams.set("end", `${endCoord[0].toFixed(6)},${endCoord[1].toFixed(6)}`); } else { url.searchParams.delete("end"); } history.replaceState(null, "", url.toString()); } function readCoordParam(name) { const params = new URLSearchParams(window.location.search); const raw = params.get(name); if (!raw) return null; const parts = raw.split(","); if (parts.length !== 2) return null; const lng = parseFloat(parts[0]); const lat = parseFloat(parts[1]); if (!Number.isFinite(lng) || !Number.isFinite(lat)) return null; return [lng, lat]; } async function geocodeAddress(query, roleLabel) { if (!query || !query.trim()) { throw new Error(`${roleLabel}: Bitte Adresse eingeben.`); } setStatus(`${roleLabel}: Suche läuft...`); const url = new URL(NOMINATIM_SEARCH_URL); url.searchParams.set("q", query); url.searchParams.set("format", "jsonv2"); url.searchParams.set("limit", "5"); url.searchParams.set("addressdetails", "1"); url.searchParams.set("countrycodes", "at"); url.searchParams.set("bounded", "1"); url.searchParams.set("viewbox", AUSTRIA_BBOX.join(",")); const res = await fetch(url.toString(), { headers: { "Accept": "application/json" } }); if (!res.ok) { throw new Error(`Geocoding fehlgeschlagen (${res.status}).`); } const results = await res.json(); if (!Array.isArray(results) || results.length === 0) { throw new Error(`Keine Treffer für "${query}" gefunden.`); } const best = results[0]; setStatus(`${roleLabel}: Treffer gefunden.`); setSearchInfo(roleLabel, best); return best; } function setStartPoint(lng, lat, labelHtml = "<b>Start</b>") { startCoord = [lng, lat]; if (startMarker) startMarker.remove(); startMarker = new maplibregl.Marker({ element: makeMarkerElement("marker-start"), draggable: true }) .setLngLat(startCoord) .setPopup(new maplibregl.Popup().setHTML(labelHtml)) .addTo(map); startMarker.on("dragend", () => { const ll = startMarker.getLngLat(); startCoord = [ll.lng, ll.lat]; updateUrlRouteParams(); updateUrlFromMap(); setStatus("Startpunkt verschoben."); }); updateUrlRouteParams(); updateUrlFromMap(); } function setEndPoint(lng, lat, labelHtml = "<b>Ziel</b>") { endCoord = [lng, lat]; if (endMarker) endMarker.remove(); endMarker = new maplibregl.Marker({ element: makeMarkerElement("marker-end"), draggable: true }) .setLngLat(endCoord) .setPopup(new maplibregl.Popup().setHTML(labelHtml)) .addTo(map); endMarker.on("dragend", () => { const ll = endMarker.getLngLat(); endCoord = [ll.lng, ll.lat]; updateUrlRouteParams(); updateUrlFromMap(); setStatus("Zielpunkt verschoben."); }); updateUrlRouteParams(); updateUrlFromMap(); } function ensureAccuracyLayers() { if (map.getSource("user-accuracy")) return; map.addSource("user-accuracy", { type: "geojson", data: { type: "FeatureCollection", features: [] } }); map.addLayer({ id: "user-accuracy-fill", type: "fill", source: "user-accuracy", paint: { "fill-color": "#3b82f6", "fill-opacity": 0.12 } }); map.addLayer({ id: "user-accuracy-line", type: "line", source: "user-accuracy", paint: { "line-color": "#2563eb", "line-width": 2 } }); } function circleGeoJSON(center, radiusMeters, points = 64) { const [lng, lat] = center; const coords = []; const earthRadius = 6378137; for (let i = 0; i <= points; i++) { const angle = (i * 360 / points) * Math.PI / 180; const dx = radiusMeters * Math.cos(angle); const dy = radiusMeters * Math.sin(angle); const dLat = (dy / earthRadius) * (180 / Math.PI); const dLng = (dx / (earthRadius * Math.cos(lat * Math.PI / 180))) * (180 / Math.PI); coords.push([lng + dLng, lat + dLat]); } return { type: "Feature", geometry: { type: "Polygon", coordinates: [coords] } }; } async function locateMe() { if (!navigator.geolocation) { alert("Geolocation wird von deinem Browser nicht unterstützt."); return; } setStatus("Standort wird ermittelt..."); navigator.geolocation.getCurrentPosition( (position) => { const lng = position.coords.longitude; const lat = position.coords.latitude; const accuracy = position.coords.accuracy; map.flyTo({ center: [lng, lat], zoom: 16, essential: true }); if (userMarker) userMarker.remove(); userMarker = new maplibregl.Marker({ element: makeMarkerElement("marker-user") }) .setLngLat([lng, lat]) .setPopup( new maplibregl.Popup().setHTML( `<b>Deine Position</b><br>Genauigkeit: ${Math.round(accuracy)} m` ) ) .addTo(map); ensureAccuracyLayers(); map.getSource("user-accuracy").setData(circleGeoJSON([lng, lat], accuracy)); updateUrlFromMap(); setStatus(`Standort gefunden. Genauigkeit ca. ${Math.round(accuracy)} m.`); }, (err) => { console.error(err); alert("Standort konnte nicht ermittelt werden."); setStatus("Standort konnte nicht ermittelt werden."); }, { enableHighAccuracy: true, timeout: 10000, maximumAge: 0 } ); } function modifierToGerman(mod) { const map = { "left": "links", "right": "rechts", "straight": "geradeaus", "slight left": "leicht links", "slight right": "leicht rechts", "sharp left": "scharf links", "sharp right": "scharf rechts", "uturn": "wenden" }; return map[mod] || mod || ""; } function stepToGerman(step, index) { const type = step.maneuver?.type || ""; const modifier = modifierToGerman(step.maneuver?.modifier); const roadName = step.name ? ` auf ${step.name}` : ""; const dist = formatMeters(step.distance || 0); if (type === "depart") { return `${index + 1}. Starten Sie${roadName}.`; } if (type === "arrive") { return `${index + 1}. Sie haben Ihr Ziel erreicht.`; } if (type === "turn") { return `${index + 1}. In ${dist} ${modifier} abbiegen${roadName}.`; } if (type === "continue") { return `${index + 1}. ${dist}${roadName} folgen, dann ${modifier}.`; } if (type === "new name") { return `${index + 1}. Dem Straßenverlauf ${dist}${roadName} folgen.`; } if (type === "merge") { return `${index + 1}. In ${dist} ${modifier} einfädeln${roadName}.`; } if (type === "fork") { return `${index + 1}. Bei der Gabelung ${modifier} halten${roadName}.`; } if (type === "on ramp") { return `${index + 1}. In ${dist} die Auffahrt nehmen${roadName}.`; } if (type === "off ramp") { return `${index + 1}. In ${dist} die Abfahrt nehmen${roadName}.`; } if (type === "end of road") { return `${index + 1}. Am Ende der Straße ${modifier} abbiegen${roadName}.`; } if (type === "roundabout") { const exit = step.maneuver?.exit; if (exit) { return `${index + 1}. In ${dist} in den Kreisverkehr einfahren und die ${exit}. Ausfahrt nehmen${roadName}.`; } return `${index + 1}. In ${dist} in den Kreisverkehr einfahren${roadName}.`; } if (type === "exit roundabout") { const exit = step.maneuver?.exit; if (exit) { return `${index + 1}. Kreisverkehr an der ${exit}. Ausfahrt verlassen${roadName}.`; } return `${index + 1}. Kreisverkehr verlassen${roadName}.`; } return `${index + 1}. ${dist}${roadName}.`; } function renderTextDirections(route) { const allSteps = []; for (const leg of route.legs || []) { for (const step of leg.steps || []) { allSteps.push(step); } } if (!allSteps.length) { directionsEl.innerHTML = "<p>Keine Textanweisungen vorhanden.</p>"; return; } const html = allSteps .map((step, i) => `<li>${stepToGerman(step, i)}</li>`) .join(""); directionsEl.innerHTML = `<ul>${html}</ul>`; } async function drawRoute() { if (!startCoord || !endCoord) { alert("Bitte zuerst Start und Ziel setzen."); return; } setStatus("Route wird berechnet..."); const url = `${OSRM_ROUTE_BASE}/` + `${startCoord[0]},${startCoord[1]};${endCoord[0]},${endCoord[1]}` + `?overview=full&geometries=geojson&steps=true`; const res = await fetch(url); if (!res.ok) { throw new Error(`Routing fehlgeschlagen (${res.status}).`); } const data = await res.json(); if (!data.routes || !data.routes.length) { throw new Error("Keine Route gefunden."); } const route = data.routes[0]; const routeFeature = { type: "Feature", geometry: route.geometry, properties: {} }; if (map.getSource("route")) { map.getSource("route").setData(routeFeature); } else { map.addSource("route", { type: "geojson", data: routeFeature }); map.addLayer({ id: "route-line-outline", type: "line", source: "route", layout: { "line-cap": "round", "line-join": "round" }, paint: { "line-color": "#ffffff", "line-width": 8 } }); map.addLayer({ id: "route-line", type: "line", source: "route", layout: { "line-cap": "round", "line-join": "round" }, paint: { "line-color": "#2563eb", "line-width": 5 } }); } const bounds = new maplibregl.LngLatBounds(); route.geometry.coordinates.forEach(c => bounds.extend(c)); map.fitBounds(bounds, { padding: 60, maxZoom: 16 }); routeInfoEl.innerHTML = ` <b>Route gefunden</b><br> Distanz: ${formatKm(route.distance)}<br> Dauer: ${formatDuration(route.duration)} `; renderTextDirections(route); updateUrlRouteParams(); updateUrlFromMap(); setStatus("Route berechnet."); } function clearRoute() { if (map.getLayer("route-line")) map.removeLayer("route-line"); if (map.getLayer("route-line-outline")) map.removeLayer("route-line-outline"); if (map.getSource("route")) map.removeSource("route"); startCoord = null; endCoord = null; if (startMarker) { startMarker.remove(); startMarker = null; } if (endMarker) { endMarker.remove(); endMarker = null; } document.getElementById("startInput").value = ""; document.getElementById("endInput").value = ""; routeInfoEl.textContent = "Noch keine Route."; directionsEl.textContent = "Noch keine Wegbeschreibung."; updateUrlRouteParams(); updateUrlFromMap(); setStatus("Route gelöscht."); } function clearFreeMarkers() { freeMarkers.forEach(m => m.remove()); freeMarkers = []; setStatus("Freie Marker gelöscht."); } function swapStartEnd() { const startText = document.getElementById("startInput").value; const endText = document.getElementById("endInput").value; document.getElementById("startInput").value = endText; document.getElementById("endInput").value = startText; const oldStartCoord = startCoord; const oldEndCoord = endCoord; startCoord = oldEndCoord; endCoord = oldStartCoord; const oldStartMarker = startMarker; const oldEndMarker = endMarker; startMarker = oldEndMarker; endMarker = oldStartMarker; if (startMarker && startCoord) startMarker.setLngLat(startCoord); if (endMarker && endCoord) endMarker.setLngLat(endCoord); if (startMarker) { startMarker.setPopup(new maplibregl.Popup().setHTML("<b>Start</b>")); } if (endMarker) { endMarker.setPopup(new maplibregl.Popup().setHTML("<b>Ziel</b>")); } updateUrlRouteParams(); updateUrlFromMap(); setStatus("Start und Ziel getauscht."); } archive.getHeader().then((h) => { const initialView = getInitialView(h.centerLon, h.centerLat, 7); map = new maplibregl.Map({ container: "map", center: [initialView.lng, initialView.lat], zoom: initialView.zoom, pitch: 0, //60, bearing: 0, //-20, style: { version: 8, sources: { omt: { type: "vector", url: `pmtiles://${PMTILES_URL}`, attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' } }, layers: [ { id: "background", type: "background", paint: { "background-color": "#f2efe9" } }, { id: "landcover", type: "fill", source: "omt", "source-layer": "landcover", paint: { "fill-color": [ "match", ["get", "class"], "forest", "#d8e8c8", "wood", "#d8e8c8", "grass", "#e6efd8", "#e9efe3" ] } }, { id: "landuse", type: "fill", source: "omt", "source-layer": "landuse", paint: { "fill-color": [ "match", ["get", "class"], "residential", "#ece7e1", "industrial", "#e3ddd5", "commercial", "#e9e1d6", "farmland", "#eef3d6", "#ebe7df" ], "fill-opacity": 0.7 } }, { id: "parks", type: "fill", source: "omt", "source-layer": "park", paint: { "fill-color": "#d7edc2" } }, { id: "water", type: "fill", source: "omt", "source-layer": "water", paint: { "fill-color": "#bcdff5" } }, { id: "waterway", type: "line", source: "omt", "source-layer": "waterway", paint: { "line-color": "#8cc7ee", "line-width": [ "interpolate", ["linear"], ["zoom"], 6, 0.5, 10, 1.2, 14, 2.2 ] } }, { id: "boundaries", type: "line", source: "omt", "source-layer": "boundary", paint: { "line-color": "#9a8f80", "line-width": [ "interpolate", ["linear"], ["zoom"], 5, 0.4, 10, 1.0, 14, 1.5 ] } }, { id: "roads", type: "line", source: "omt", "source-layer": "transportation", paint: { "line-color": [ "match", ["get", "class"], "motorway", "#d28c8c", "trunk", "#d9a07b", "primary", "#e0b26f", "secondary", "#ead39c", "tertiary", "#ffffff", "minor", "#ffffff", "service", "#f7f7f7", "#ffffff" ], "line-width": [ "interpolate", ["linear"], ["zoom"], 5, 0.4, 8, 1.0, 10, 1.8, 12, 3.0, 14, 5.0 ] } }, { id: "buildings", type: "fill", source: "omt", "source-layer": "building", minzoom: 15, paint: { "fill-color": "#d9d0c7", "fill-outline-color": "#c8beb5" } }, { id: "buildings-3d", type: "fill-extrusion", source: "omt", "source-layer": "building", minzoom: 10, paint: { "fill-extrusion-color": "#d9d0c7", "fill-extrusion-height": [ "coalesce", ["get", "render_height"], 8 ], "fill-extrusion-base": [ "coalesce", ["get", "render_min_height"], 0 ], "fill-extrusion-opacity": 0.85 } }, { id: "water-names", type: "symbol", source: "omt", "source-layer": "water_name", minzoom: 8, layout: { "text-field": ["coalesce", ["get", "name:de"], ["get", "name_de"], ["get", "name"]], "text-size": 11 }, paint: { "text-color": "#2b6e99", "text-halo-color": "#ffffff", "text-halo-width": 1 } }, { id: "road-names", type: "symbol", source: "omt", "source-layer": "transportation_name", minzoom: 7, layout: { "symbol-placement": "line", "text-field": ["coalesce", ["get", "name:de"], ["get", "name_de"], ["get", "name"]], "text-size": 14, "text-allow-overlap": true }, paint: { "text-color": "#555", "text-halo-color": "#ffffff", "text-halo-width": 1.0 } }, /* { id: "road-names-major", type: "symbol", source: "omt", "source-layer": "transportation_name", minzoom: 10, filter: [ "match", ["get", "class"], ["motorway", "trunk", "primary", "secondary"], true, false ], layout: { "symbol-placement": "line", "text-field": ["coalesce", ["get", "name:de"], ["get", "name_de"], ["get", "name"]], "text-size": [ "interpolate", ["linear"], ["zoom"], 9, 10, 11, 11, 13, 13, 15, 14 ], "text-letter-spacing": 0.02 }, paint: { "text-color": "#4b5563", "text-halo-color": "#ffffff", "text-halo-width": 1.5 } }, { id: "road-names-medium", type: "symbol", source: "omt", "source-layer": "transportation_name", minzoom: 11, filter: [ "match", ["get", "class"], ["tertiary", "minor"], true, false ], layout: { "symbol-placement": "line", "text-field": ["coalesce", ["get", "name:de"], ["get", "name_de"], ["get", "name"]], "text-size": [ "interpolate", ["linear"], ["zoom"], 11, 10, 13, 11, 15, 12 ] }, paint: { "text-color": "#555", "text-halo-color": "#ffffff", "text-halo-width": 1.3 } }, { id: "road-names-small", type: "symbol", source: "omt", "source-layer": "transportation_name", minzoom: 12, filter: [ "match", ["get", "class"], ["service", "track", "path"], true, false ], layout: { "symbol-placement": "line", "text-field": ["coalesce", ["get", "name:de"], ["get", "name_de"], ["get", "name"]], "text-size": 14, "text-allow-overlap": true }, paint: { "text-color": "#666", "text-halo-color": "#ffffff", "text-halo-width": 1.1 } },*/ { id: "house-numbers", type: "symbol", source: "omt", "source-layer": "housenumber", minzoom: 17, layout: { "text-field": ["get", "housenumber"], "text-size": 12, "text-allow-overlap": false }, paint: { "text-color": "#6b3f1f", "text-halo-color": "#ffffff", "text-halo-width": 1 } }, { id: "place-names", type: "symbol", source: "omt", "source-layer": "place", layout: { "text-field": ["coalesce", ["get", "name:de"], ["get", "name_de"], ["get", "name"]], "text-size": [ "interpolate", ["linear"], ["zoom"], 5, 10, 8, 12, 10, 14, 14, 16 ] }, paint: { "text-color": "#222", "text-halo-color": "#ffffff", "text-halo-width": 1.5 } }, { id: "poi-labels", type: "symbol", source: "omt", "source-layer": "poi", minzoom: 10, /*filter: [ "match", ["get", "class"], ["hospital", "school", "railway", "attraction", "museum"], true, false ],*/ layout: { "text-field": ["coalesce", ["get", "name:de"], ["get", "name_de"], ["get", "name"]], "text-size": 16, "icon-image": "circle", "text-offset": [0, 0.8], "text-anchor": "top" }, paint: { "text-color": "#7c2d12", "text-halo-color": "#ffffff", "text-halo-width": 1.2 } } /* { id: "poi-dots", type: "circle", source: "omt", "source-layer": "poi", minzoom: 16, paint: { "circle-radius": 4, "circle-color": "#c2410c", "circle-stroke-color": "#ffffff", "circle-stroke-width": 1 } } */ ] } }); map.addControl(new maplibregl.NavigationControl(), "top-right"); map.on("moveend", () => { updateUrlFromMap(); }); map.on("click", (e) => { if (!markerMode) return; const marker = new maplibregl.Marker({ element: makeMarkerElement("marker-free"), draggable: true }) .setLngLat([e.lngLat.lng, e.lngLat.lat]) .setPopup( new maplibregl.Popup().setHTML( `<b>Freier Marker</b><br>Lng: ${e.lngLat.lng.toFixed(6)}<br>Lat: ${e.lngLat.lat.toFixed(6)}` ) ) .addTo(map); freeMarkers.push(marker); setStatus(`Freier Marker gesetzt. Gesamt: ${freeMarkers.length}`); }); map.on("click", "poi-dots", (e) => { const f = e.features[0]; const props = f.properties; const lng = e.lngLat.lng.toFixed(6); const lat = e.lngLat.lat.toFixed(6); const name = props["name:de"] || props["name_de"] || props["name"] || "POI"; const cls = props["class"] || "-"; const sub = props["subclass"] || "-"; new maplibregl.Popup() .setLngLat(e.lngLat) .setHTML(` <b>${name}</b><br> Klasse: ${cls}/${sub}<br> Koordinaten: ${lat}, ${lng} `) .addTo(map); }); map.on("mouseenter", "poi-dots", () => { map.getCanvas().style.cursor = "pointer"; }); map.on("mouseleave", "poi-dots", () => { map.getCanvas().style.cursor = ""; }); const startFromUrl = readCoordParam("start"); const endFromUrl = readCoordParam("end"); if (startFromUrl) { setStartPoint(startFromUrl[0], startFromUrl[1], "<b>Start</b><br>Aus Link geladen"); } if (endFromUrl) { setEndPoint(endFromUrl[0], endFromUrl[1], "<b>Ziel</b><br>Aus Link geladen"); } if (startFromUrl && endFromUrl) { drawRoute().catch(console.error); } document.getElementById("searchStartBtn").addEventListener("click", async () => { try { const q = document.getElementById("startInput").value; const hit = await geocodeAddress(q, "Start"); const lng = Number(hit.lon); const lat = Number(hit.lat); setStartPoint(lng, lat, `<b>Start</b><br>${hit.display_name}`); map.flyTo({ center: [lng, lat], zoom: 16 }); } catch (err) { console.error(err); alert(err.message); setStatus(err.message); } }); document.getElementById("searchEndBtn").addEventListener("click", async () => { try { const q = document.getElementById("endInput").value; const hit = await geocodeAddress(q, "Ziel"); const lng = Number(hit.lon); const lat = Number(hit.lat); setEndPoint(lng, lat, `<b>Ziel</b><br>${hit.display_name}`); map.flyTo({ center: [lng, lat], zoom: 16 }); } catch (err) { console.error(err); alert(err.message); setStatus(err.message); } }); document.getElementById("routeBtn").addEventListener("click", async () => { try { await drawRoute(); } catch (err) { console.error(err); alert(err.message); setStatus(err.message); } }); document.getElementById("clearRouteBtn").addEventListener("click", () => { clearRoute(); }); document.getElementById("swapBtn").addEventListener("click", () => { swapStartEnd(); }); document.getElementById("locateBtn").addEventListener("click", locateMe); document.getElementById("enableMarkerModeBtn").addEventListener("click", () => { markerMode = !markerMode; markerModeStateEl.textContent = `Klick-Marker: ${markerMode ? "an" : "aus"}`; setStatus(`Klick-Marker ${markerMode ? "aktiviert" : "deaktiviert"}.`); }); document.getElementById("clearMarkersBtn").addEventListener("click", clearFreeMarkers); document.getElementById("copyLinkBtn").addEventListener("click", async () => { try { updateUrlFromMap(); updateUrlRouteParams(); await navigator.clipboard.writeText(window.location.href); setStatus("Link in die Zwischenablage kopiert."); } catch (err) { console.error(err); setStatus("Link konnte nicht kopiert werden."); } }); document.getElementById("startInput").addEventListener("keydown", (e) => { if (e.key === "Enter") document.getElementById("searchStartBtn").click(); }); document.getElementById("endInput").addEventListener("keydown", (e) => { if (e.key === "Enter") document.getElementById("searchEndBtn").click(); }); updateUrlFromMap(); setStatus("Karte geladen."); }).catch((err) => { console.error(err); setStatus("Fehler beim Laden der PMTiles-Datei."); }); </script> </body> </html>