Hier werden die Unterschiede zwischen zwei Versionen angezeigt.
| Beide Seiten der vorigen Revision Vorhergehende Überarbeitung Nächste Überarbeitung | Vorhergehende Überarbeitung | ||
|
osmium [2026/03/30 16:19] jango [Linux] |
osmium [2026/04/05 13:50] (aktuell) jango [Linux] |
||
|---|---|---|---|
| Zeile 1: | Zeile 1: | ||
| - | Osmium ist ein CLI-Tool für die Arbeit mit OSM-Daten und basiert auf der C-Bibliothek libosmium. Es kann mit mehreren OSM-Datenformaten arbeiten, vor allem: | + | Osmium ist ein CLI-Tool für die Arbeit mit [[OpenStreetMap|OSM]]-Daten und basiert auf der C-Bibliothek libosmium. Es kann mit mehreren OSM-Datenformaten arbeiten, vor allem: |
| * .osm.pbf → binäres [[PBF]]-Format | * .osm.pbf → binäres [[PBF]]-Format | ||
| Zeile 8: | Zeile 8: | ||
| <box green> | <box green> | ||
| - | =====Linux===== | + | Siehe auch osmosis, osmconvert |
| + | =====Buslinien extrahieren===== | ||
| + | Buslinien aus [[PBF]] extrahieren. | ||
| + | |||
| + | ====1==== | ||
| < | < | ||
| + | # relationen in eine neue pbf schreiben | ||
| + | osmium tags-filter wien.osm.pbf \ | ||
| + | n/ | ||
| + | n/ | ||
| + | r/route=bus \ | ||
| + | -R \ | ||
| + | -o bus-relevant.osm.pbf -O | ||
| + | </ | ||
| + | Ein Python script | ||
| + | <code python> | ||
| + | # | ||
| + | import json | ||
| + | import subprocess | ||
| + | import sys | ||
| + | import xml.etree.ElementTree as ET | ||
| + | from collections import defaultdict | ||
| + | |||
| + | |||
| + | def fail(msg, code=1): | ||
| + | print(msg, file=sys.stderr) | ||
| + | sys.exit(code) | ||
| + | |||
| + | |||
| + | if len(sys.argv) != 3: | ||
| + | fail(" | ||
| + | |||
| + | input_pbf = sys.argv[1] | ||
| + | output_geojson = sys.argv[2] | ||
| + | |||
| + | try: | ||
| + | proc = subprocess.run( | ||
| + | [" | ||
| + | capture_output=True, | ||
| + | text=True, | ||
| + | check=False, | ||
| + | ) | ||
| + | except FileNotFoundError: | ||
| + | fail(" | ||
| + | |||
| + | if proc.returncode != 0: | ||
| + | fail(f" | ||
| + | |||
| + | if not proc.stdout.strip(): | ||
| + | fail(" | ||
| + | |||
| + | try: | ||
| + | root = ET.fromstring(proc.stdout) | ||
| + | except ET.ParseError as e: | ||
| + | fail(f" | ||
| + | |||
| + | # Haltestellen-Nodes sammeln | ||
| + | stops = {} | ||
| + | for node in root.findall(" | ||
| + | node_id = node.get(" | ||
| + | lat = node.get(" | ||
| + | lon = node.get(" | ||
| + | if node_id is None or lat is None or lon is None: | ||
| + | continue | ||
| + | |||
| + | tags = {tag.get(" | ||
| + | is_bus_stop = tags.get(" | ||
| + | is_platform = tags.get(" | ||
| + | |||
| + | if is_bus_stop or is_platform: | ||
| + | stops[node_id] = { | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | |||
| + | # Routenrelationen -> Haltestellen mappen | ||
| + | routes_by_stop = defaultdict(list) | ||
| + | |||
| + | for rel in root.findall(" | ||
| + | rel_tags = {tag.get(" | ||
| + | |||
| + | if rel_tags.get(" | ||
| + | continue | ||
| + | |||
| + | route_info = { | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | |||
| + | for member in rel.findall(" | ||
| + | if member.get(" | ||
| + | continue | ||
| + | |||
| + | ref = member.get(" | ||
| + | role = member.get(" | ||
| + | |||
| + | if ref in stops: | ||
| + | entry = dict(route_info) | ||
| + | entry[" | ||
| + | routes_by_stop[ref].append(entry) | ||
| + | |||
| + | features = [] | ||
| + | |||
| + | for stop_id, stop in stops.items(): | ||
| + | routes = routes_by_stop.get(stop_id, | ||
| + | |||
| + | # doppelte Routen vermeiden | ||
| + | seen = set() | ||
| + | unique_routes = [] | ||
| + | for r in routes: | ||
| + | key = ( | ||
| + | r.get(" | ||
| + | r.get(" | ||
| + | r.get(" | ||
| + | r.get(" | ||
| + | ) | ||
| + | if key not in seen: | ||
| + | seen.add(key) | ||
| + | unique_routes.append(r) | ||
| + | |||
| + | route_refs = sorted({r[" | ||
| + | route_names = sorted({r[" | ||
| + | |||
| + | properties = dict(stop[" | ||
| + | properties[" | ||
| + | properties[" | ||
| + | properties[" | ||
| + | properties[" | ||
| + | properties[" | ||
| + | |||
| + | features.append({ | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | }, | ||
| + | " | ||
| + | }) | ||
| + | |||
| + | geojson = { | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | |||
| + | try: | ||
| + | with open(output_geojson, | ||
| + | json.dump(geojson, | ||
| + | except OSError as e: | ||
| + | fail(f" | ||
| + | |||
| + | print(f" | ||
| + | print(f" | ||
| + | print(f" | ||
| + | </ | ||
| + | |||
| + | Aufrufen | ||
| + | < | ||
| + | python3 osm_bus_stops_to_geojson.py bus-relevant.osm.pbf bus-stops.geojson | ||
| + | </ | ||
| + | |||
| + | Die geojson hat dann so eine struktur | ||
| + | <code json> | ||
| + | { | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | }, | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | "13A: Alser Straße - Hauptbahnhof", | ||
| + | "59A: Bhf. Meidling - Oper" | ||
| + | ], | ||
| + | " | ||
| + | { | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | ] | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | |||
| + | ====2==== | ||
| + | < | ||
| + | osmium tags-filter wien.osm.pbf \ | ||
| + | n/ | ||
| + | r/route=bus \ | ||
| + | -o step1.osm.pbf -O | ||
| + | |||
| + | osmium tags-filter -i step1.osm.pbf \ | ||
| + | n/ | ||
| + | -o bus-relevant.osm.pbf -O | ||
| + | </ | ||
| + | |||
| + | <code python> | ||
| + | # | ||
| + | import json | ||
| + | import math | ||
| + | import subprocess | ||
| + | import sys | ||
| + | import xml.etree.ElementTree as ET | ||
| + | from collections import defaultdict | ||
| + | |||
| + | if len(sys.argv) != 3: | ||
| + | print(" | ||
| + | sys.exit(1) | ||
| + | |||
| + | input_pbf = sys.argv[1] | ||
| + | output_geojson = sys.argv[2] | ||
| + | |||
| + | proc = subprocess.Popen( | ||
| + | [" | ||
| + | stdout=subprocess.PIPE, | ||
| + | stderr=subprocess.PIPE, | ||
| + | text=True, | ||
| + | ) | ||
| + | |||
| + | nodes = {} # node_id -> (lon, lat) | ||
| + | stop_nodes = {} # node_id -> stop feature candidate | ||
| + | route_ways = {} # way_id -> dict(tags, node_refs) | ||
| + | route_memberships_by_stop = defaultdict(list) | ||
| + | route_relations = [] | ||
| + | |||
| + | def tags_from_elem(elem): | ||
| + | return {t.attrib[" | ||
| + | |||
| + | def is_stop_node(tags): | ||
| + | return ( | ||
| + | tags.get(" | ||
| + | or tags.get(" | ||
| + | ) | ||
| + | |||
| + | def normalize_name(value): | ||
| + | if not value: | ||
| + | return "" | ||
| + | return " " | ||
| + | |||
| + | def rounded_coord(value, | ||
| + | return round(float(value), | ||
| + | |||
| + | def make_stop_candidate(osm_id, | ||
| + | return { | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | |||
| + | def stop_priority(stop): | ||
| + | """ | ||
| + | Bevorzugung für den repräsentativen Punkt je Haltestelle. | ||
| + | Niedriger = besser. | ||
| + | """ | ||
| + | pt = stop.get(" | ||
| + | hw = stop.get(" | ||
| + | |||
| + | if hw == " | ||
| + | return 0 | ||
| + | if pt == " | ||
| + | return 1 | ||
| + | if pt == " | ||
| + | return 2 | ||
| + | return 9 | ||
| + | |||
| + | context = ET.iterparse(proc.stdout, | ||
| + | |||
| + | for event, elem in context: | ||
| + | if elem.tag == " | ||
| + | node_id = elem.attrib[" | ||
| + | lon = float(elem.attrib[" | ||
| + | lat = float(elem.attrib[" | ||
| + | nodes[node_id] = (lon, lat) | ||
| + | |||
| + | tags = tags_from_elem(elem) | ||
| + | if is_stop_node(tags): | ||
| + | stop_nodes[node_id] = make_stop_candidate(node_id, | ||
| + | |||
| + | elem.clear() | ||
| + | |||
| + | elif elem.tag == " | ||
| + | way_id = elem.attrib[" | ||
| + | tags = tags_from_elem(elem) | ||
| + | node_refs = [nd.attrib[" | ||
| + | route_ways[way_id] = {" | ||
| + | elem.clear() | ||
| + | |||
| + | elif elem.tag == " | ||
| + | tags = tags_from_elem(elem) | ||
| + | |||
| + | if tags.get(" | ||
| + | rel_id = int(elem.attrib[" | ||
| + | members = elem.findall(" | ||
| + | |||
| + | route_way_ids = [] | ||
| + | for member in members: | ||
| + | mtype = member.attrib.get(" | ||
| + | mref = member.attrib.get(" | ||
| + | role = member.attrib.get(" | ||
| + | |||
| + | if mtype == " | ||
| + | route_memberships_by_stop[mref].append({ | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | }) | ||
| + | |||
| + | elif mtype == " | ||
| + | route_way_ids.append(mref) | ||
| + | |||
| + | route_relations.append({ | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | }) | ||
| + | |||
| + | elem.clear() | ||
| + | |||
| + | stderr = proc.stderr.read() | ||
| + | ret = proc.wait() | ||
| + | if ret != 0: | ||
| + | print(stderr, | ||
| + | sys.exit(ret) | ||
| + | |||
| + | # ------------------------------------------------- | ||
| + | # Stop-Kandidaten mit Routen anreichern | ||
| + | # ------------------------------------------------- | ||
| + | |||
| + | enriched_stops = [] | ||
| + | |||
| + | for node_id, stop in stop_nodes.items(): | ||
| + | memberships = route_memberships_by_stop.get(node_id, | ||
| + | |||
| + | seen = set() | ||
| + | bus_routes = [] | ||
| + | bus_lines = [] | ||
| + | bus_route_names = [] | ||
| + | |||
| + | for r in memberships: | ||
| + | key = (r[" | ||
| + | if key in seen: | ||
| + | continue | ||
| + | seen.add(key) | ||
| + | bus_routes.append(r) | ||
| + | |||
| + | if r[" | ||
| + | bus_lines.append(r[" | ||
| + | |||
| + | if r[" | ||
| + | bus_route_names.append(r[" | ||
| + | |||
| + | stop[" | ||
| + | stop[" | ||
| + | stop[" | ||
| + | enriched_stops.append(stop) | ||
| + | |||
| + | # ------------------------------------------------- | ||
| + | # Haltestellen zusammenfassen | ||
| + | # ------------------------------------------------- | ||
| + | |||
| + | grouped = defaultdict(list) | ||
| + | |||
| + | for stop in enriched_stops: | ||
| + | name_key = normalize_name(stop.get(" | ||
| + | local_ref_key = (stop.get(" | ||
| + | coord_key = (rounded_coord(stop[" | ||
| + | |||
| + | # Falls name fehlt, immerhin local_ref + Nähe verwenden | ||
| + | # Falls local_ref auch fehlt, name + Nähe | ||
| + | group_key = ( | ||
| + | name_key, | ||
| + | local_ref_key, | ||
| + | coord_key, | ||
| + | ) | ||
| + | grouped[group_key].append(stop) | ||
| + | |||
| + | features = [] | ||
| + | |||
| + | for group_key, group_stops in grouped.items(): | ||
| + | # repräsentativen Punkt wählen | ||
| + | group_stops_sorted = sorted( | ||
| + | group_stops, | ||
| + | key=lambda s: ( | ||
| + | stop_priority(s), | ||
| + | 0 if s.get(" | ||
| + | s[" | ||
| + | ) | ||
| + | ) | ||
| + | rep = group_stops_sorted[0] | ||
| + | |||
| + | # Linien / Routen deduplizieren gruppenweit | ||
| + | seen_route_ids = set() | ||
| + | merged_routes = [] | ||
| + | merged_lines = [] | ||
| + | merged_route_names = [] | ||
| + | |||
| + | for stop in group_stops: | ||
| + | for r in stop.get(" | ||
| + | rid = r.get(" | ||
| + | if rid in seen_route_ids: | ||
| + | continue | ||
| + | seen_route_ids.add(rid) | ||
| + | merged_routes.append(r) | ||
| + | |||
| + | line_ref = r.get(" | ||
| + | if line_ref and line_ref not in merged_lines: | ||
| + | merged_lines.append(line_ref) | ||
| + | |||
| + | route_name = r.get(" | ||
| + | if route_name and route_name not in merged_route_names: | ||
| + | merged_route_names.append(route_name) | ||
| + | |||
| + | merged_lines.sort(key=lambda x: str(x)) | ||
| + | merged_route_names.sort(key=lambda x: str(x)) | ||
| + | |||
| + | feature = { | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | }, | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | }, | ||
| + | } | ||
| + | |||
| + | features.append(feature) | ||
| + | |||
| + | # ------------------------------------------------- | ||
| + | # Bus-Routen als GeoJSON drinlassen | ||
| + | # ------------------------------------------------- | ||
| + | |||
| + | for rel in route_relations: | ||
| + | segments = [] | ||
| + | |||
| + | for way_id in rel[" | ||
| + | way = route_ways.get(way_id) | ||
| + | if not way: | ||
| + | continue | ||
| + | |||
| + | coords = [nodes[nid] for nid in way[" | ||
| + | if len(coords) >= 2: | ||
| + | segments.append([list(c) for c in coords]) | ||
| + | |||
| + | if not segments: | ||
| + | continue | ||
| + | |||
| + | if len(segments) == 1: | ||
| + | geometry = { | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | else: | ||
| + | geometry = { | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | |||
| + | tags = rel[" | ||
| + | features.append({ | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | }, | ||
| + | }) | ||
| + | |||
| + | geojson = { | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | |||
| + | with open(output_geojson, | ||
| + | json.dump(geojson, | ||
| + | |||
| + | n_stops = sum(1 for f in features if f[" | ||
| + | n_routes = sum(1 for f in features if f[" | ||
| + | |||
| + | print(f" | ||
| + | print(f" | ||
| + | print(f" | ||
| + | </ | ||
| + | |||
| + | ====HTML==== | ||
| + | |||
| + | < | ||
| + | let busGeojson = await fetch(" | ||
| + | if (!r.ok) throw new Error(`bus-stops.geojson konnte nicht geladen werden (${r.status})`); | ||
| + | return r.json(); | ||
| + | }); | ||
| + | |||
| + | console.log(" | ||
| + | |||
| + | map.addSource(" | ||
| + | type: " | ||
| + | data: busGeojson | ||
| + | }); | ||
| + | |||
| + | // Quelle für die ausgewählte Route | ||
| + | map.addSource(" | ||
| + | type: " | ||
| + | data: { | ||
| + | type: " | ||
| + | features: [] | ||
| + | } | ||
| + | }); | ||
| + | |||
| + | // Nur die ausgewählte Linie zeichnen | ||
| + | map.addLayer({ | ||
| + | id: " | ||
| + | type: " | ||
| + | source: " | ||
| + | layout: { | ||
| + | " | ||
| + | " | ||
| + | }, | ||
| + | paint: { | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | }); | ||
| + | |||
| + | map.addLayer({ | ||
| + | id: " | ||
| + | type: " | ||
| + | source: " | ||
| + | layout: { | ||
| + | " | ||
| + | " | ||
| + | }, | ||
| + | paint: { | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | }); | ||
| + | |||
| + | // NUR Stops anzeigen | ||
| + | map.addLayer({ | ||
| + | id: " | ||
| + | type: " | ||
| + | source: " | ||
| + | minzoom: 12, | ||
| + | filter: [ | ||
| + | " | ||
| + | [" | ||
| + | [" | ||
| + | [" | ||
| + | ], | ||
| + | paint: { | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | }); | ||
| + | |||
| + | function parseJsonArray(value) { | ||
| + | if (Array.isArray(value)) return value; | ||
| + | if (typeof value !== " | ||
| + | |||
| + | try { | ||
| + | const parsed = JSON.parse(value); | ||
| + | return Array.isArray(parsed) ? parsed : []; | ||
| + | } catch (err) { | ||
| + | console.warn(" | ||
| + | return []; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | function parseJsonObjectArray(value) { | ||
| + | if (Array.isArray(value)) return value; | ||
| + | if (typeof value !== " | ||
| + | |||
| + | try { | ||
| + | const parsed = JSON.parse(value); | ||
| + | return Array.isArray(parsed) ? parsed : []; | ||
| + | } catch (err) { | ||
| + | console.warn(" | ||
| + | return []; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | function getBusRouteFeatureByRelationId(routeId) { | ||
| + | if (!busGeojson? | ||
| + | |||
| + | return busGeojson.features.find((f) => { | ||
| + | const p = f.properties || {}; | ||
| + | return p.feature_kind === " | ||
| + | }) || null; | ||
| + | } | ||
| + | |||
| + | function clearSelectedBusRoute() { | ||
| + | const src = map.getSource(" | ||
| + | if (!src) return; | ||
| + | |||
| + | src.setData({ | ||
| + | type: " | ||
| + | features: [] | ||
| + | }); | ||
| + | } | ||
| + | |||
| + | function showSelectedBusRoute(routeId) { | ||
| + | const feature = getBusRouteFeatureByRelationId(routeId); | ||
| + | if (!feature) { | ||
| + | console.warn(" | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | const src = map.getSource(" | ||
| + | if (!src) return; | ||
| + | |||
| + | src.setData({ | ||
| + | type: " | ||
| + | features: [feature] | ||
| + | }); | ||
| + | |||
| + | const bounds = new maplibregl.LngLatBounds(); | ||
| + | |||
| + | if (feature.geometry? | ||
| + | feature.geometry.coordinates.forEach((coord) => bounds.extend(coord)); | ||
| + | } else if (feature.geometry? | ||
| + | feature.geometry.coordinates.forEach((line) => { | ||
| + | line.forEach((coord) => bounds.extend(coord)); | ||
| + | }); | ||
| + | } | ||
| + | |||
| + | if (!bounds.isEmpty()) { | ||
| + | map.fitBounds(bounds, | ||
| + | } | ||
| + | } | ||
| + | |||
| + | function buildBusStopPopupHtml(props, | ||
| + | const name = props.name || " | ||
| + | const network = props.network || " | ||
| + | const osmId = props.osm_id || " | ||
| + | |||
| + | const busRoutes = parseJsonObjectArray(props.bus_routes); | ||
| + | const busLines = parseJsonArray(props.bus_lines); | ||
| + | |||
| + | let connectionsHtml = "< | ||
| + | |||
| + | if (busRoutes.length) { | ||
| + | connectionsHtml = ` | ||
| + | <div | ||
| + | style=" | ||
| + | max-height: | ||
| + | overflow-y: | ||
| + | border:1px solid #e2e8f0; | ||
| + | border-radius: | ||
| + | background:# | ||
| + | padding: | ||
| + | margin-top: | ||
| + | " | ||
| + | > | ||
| + | <div style=" | ||
| + | ${busRoutes.map((route) => ` | ||
| + | <button | ||
| + | type=" | ||
| + | class=" | ||
| + | data-route-id=" | ||
| + | style=" | ||
| + | display: | ||
| + | width:100%; | ||
| + | text-align: | ||
| + | border:1px solid #cbd5e1; | ||
| + | background:# | ||
| + | border-radius: | ||
| + | padding:8px 10px; | ||
| + | cursor: | ||
| + | overflow: | ||
| + | " | ||
| + | > | ||
| + | <div style=" | ||
| + | ${escapeHtml(String(route.line_ref || " | ||
| + | </ | ||
| + | <div | ||
| + | style=" | ||
| + | font-size: | ||
| + | line-height: | ||
| + | color:# | ||
| + | word-break: | ||
| + | " | ||
| + | > | ||
| + | ${escapeHtml(String(route.route_name || " | ||
| + | </ | ||
| + | </ | ||
| + | `).join("" | ||
| + | </ | ||
| + | </ | ||
| + | `; | ||
| + | } else if (busLines.length) { | ||
| + | connectionsHtml = ` | ||
| + | <div | ||
| + | style=" | ||
| + | max-height: | ||
| + | overflow-y: | ||
| + | border:1px solid #e2e8f0; | ||
| + | border-radius: | ||
| + | background:# | ||
| + | padding: | ||
| + | margin-top: | ||
| + | font-size: | ||
| + | line-height: | ||
| + | " | ||
| + | > | ||
| + | ${busLines.map(line => ` | ||
| + | <div style=" | ||
| + | ${escapeHtml(String(line))} | ||
| + | </ | ||
| + | `).join("" | ||
| + | </ | ||
| + | `; | ||
| + | } | ||
| + | |||
| + | return ` | ||
| + | <div style=" | ||
| + | <div style=" | ||
| + | <div style=" | ||
| + | ${escapeHtml(name)} | ||
| + | </ | ||
| + | <div style=" | ||
| + | Netzwerk: ${escapeHtml(network)} | ||
| + | </ | ||
| + | </ | ||
| + | |||
| + | <div style=" | ||
| + | OSMID: ${escapeHtml(String(osmId))}< | ||
| + | Koordinaten: | ||
| + | </ | ||
| + | |||
| + | <div style=" | ||
| + | <div style=" | ||
| + | ${connectionsHtml} | ||
| + | </ | ||
| + | |||
| + | <div style=" | ||
| + | <button | ||
| + | type=" | ||
| + | id=" | ||
| + | style=" | ||
| + | border:1px solid #cbd5e1; | ||
| + | background:# | ||
| + | border-radius: | ||
| + | padding:6px 10px; | ||
| + | cursor: | ||
| + | width:100%; | ||
| + | " | ||
| + | > | ||
| + | Route ausblenden | ||
| + | </ | ||
| + | </ | ||
| + | </ | ||
| + | `; | ||
| + | } | ||
| + | |||
| + | map.on(" | ||
| + | const f = e.features? | ||
| + | if (!f) return; | ||
| + | |||
| + | const props = f.properties || {}; | ||
| + | const lng = e.lngLat.lng.toFixed(6); | ||
| + | const lat = e.lngLat.lat.toFixed(6); | ||
| + | |||
| + | const popup = new maplibregl.Popup() | ||
| + | .setLngLat(e.lngLat) | ||
| + | .setHTML(buildBusStopPopupHtml(props, | ||
| + | .addTo(map); | ||
| + | |||
| + | const popupEl = popup.getElement(); | ||
| + | if (!popupEl) return; | ||
| + | |||
| + | popupEl.querySelectorAll(" | ||
| + | btn.addEventListener(" | ||
| + | const routeId = btn.dataset.routeId; | ||
| + | if (!routeId) return; | ||
| + | showSelectedBusRoute(routeId); | ||
| + | }); | ||
| + | }); | ||
| + | |||
| + | popupEl.querySelector("# | ||
| + | clearSelectedBusRoute(); | ||
| + | }); | ||
| + | }); | ||
| + | |||
| + | map.on(" | ||
| + | map.getCanvas().style.cursor = " | ||
| + | }); | ||
| + | |||
| + | map.on(" | ||
| + | map.getCanvas().style.cursor = ""; | ||
| + | }); | ||
| + | </ | ||
| + | =====Linux===== | ||
| + | |||
| + | <code bash> | ||
| sudo apt update | sudo apt update | ||
| sudo apt install osmium-tool | sudo apt install osmium-tool | ||
| osmium extract --bbox 16.20, | osmium extract --bbox 16.20, | ||
| + | |||
| osmium tags-filter wien.osm.pbf n/ | osmium tags-filter wien.osm.pbf n/ | ||
| + | |||
| osmium sort edited.osm.pbf -o edited-sorted.osm.pbf | osmium sort edited.osm.pbf -o edited-sorted.osm.pbf | ||
| + | |||
| osmium renumber edited-sorted.osm.pbf -o edited-renumbered.osm.pbf | osmium renumber edited-sorted.osm.pbf -o edited-renumbered.osm.pbf | ||
| + | |||
| osmium fileinfo -e edited-renumbered.osm.pbf | osmium fileinfo -e edited-renumbered.osm.pbf | ||
| - | </ | ||
| + | osmium extract -b 16.36, | ||
| + | </ | ||
| + | Siehe [[https:// | ||
| + | < | ||
| + | # bbox | ||
| + | at - 9.54063, | ||
| + | vienna - 16.20, | ||
| + | </ | ||
| =====Python===== | =====Python===== | ||
| Zeile 259: | Zeile 1131: | ||
| * [[https:// | * [[https:// | ||
| * [[https:// | * [[https:// | ||
| + | * [[https:// | ||