Benutzer-Werkzeuge

Webseiten-Werkzeuge


osmium

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 eher zum Anzeigen und Exportieren gedacht als zum Bearbeiten

Siehe auch osmosis, osmconvert

Buslinien extrahieren

Buslinien aus PBF extrahieren.

1

# relationen in eine neue pbf schreiben
osmium tags-filter wien.osm.pbf \
  n/highway=bus_stop \
  n/public_transport=platform \
  r/route=bus \
  -R \
  -o bus-relevant.osm.pbf -O

Ein Python script

#!/usr/bin/env python3
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("Aufruf: python3 osm_bus_stops_to_geojson.py input.osm.pbf output.geojson")
 
input_pbf = sys.argv[1]
output_geojson = sys.argv[2]
 
try:
    proc = subprocess.run(
        ["osmium", "cat", input_pbf, "-f", "osm"],
        capture_output=True,
        text=True,
        check=False,
    )
except FileNotFoundError:
    fail("Fehler: 'osmium' wurde nicht gefunden.")
 
if proc.returncode != 0:
    fail(f"Fehler bei 'osmium cat':\n{proc.stderr}")
 
if not proc.stdout.strip():
    fail("Fehler: 'osmium cat' hat keine Ausgabe geliefert.")
 
try:
    root = ET.fromstring(proc.stdout)
except ET.ParseError as e:
    fail(f"XML Parse Error: {e}")
 
# Haltestellen-Nodes sammeln
stops = {}
for node in root.findall("node"):
    node_id = node.get("id")
    lat = node.get("lat")
    lon = node.get("lon")
    if node_id is None or lat is None or lon is None:
        continue
 
    tags = {tag.get("k"): tag.get("v") for tag in node.findall("tag")}
    is_bus_stop = tags.get("highway") == "bus_stop"
    is_platform = tags.get("public_transport") == "platform"
 
    if is_bus_stop or is_platform:
        stops[node_id] = {
            "id": node_id,
            "lat": float(lat),
            "lon": float(lon),
            "tags": tags,
        }
 
# Routenrelationen -> Haltestellen mappen
routes_by_stop = defaultdict(list)
 
for rel in root.findall("relation"):
    rel_tags = {tag.get("k"): tag.get("v") for tag in rel.findall("tag")}
 
    if rel_tags.get("route") != "bus":
        continue
 
    route_info = {
        "relation_id": rel.get("id"),
        "ref": rel_tags.get("ref"),
        "name": rel_tags.get("name"),
        "from": rel_tags.get("from"),
        "to": rel_tags.get("to"),
        "operator": rel_tags.get("operator"),
        "network": rel_tags.get("network"),
    }
 
    for member in rel.findall("member"):
        if member.get("type") != "node":
            continue
 
        ref = member.get("ref")
        role = member.get("role", "")
 
        if ref in stops:
            entry = dict(route_info)
            entry["member_role"] = role
            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("relation_id"),
            r.get("ref"),
            r.get("name"),
            r.get("member_role"),
        )
        if key not in seen:
            seen.add(key)
            unique_routes.append(r)
 
    route_refs = sorted({r["ref"] for r in unique_routes if r.get("ref")})
    route_names = sorted({r["name"] for r in unique_routes if r.get("name")})
 
    properties = dict(stop["tags"])
    properties["osm_id"] = stop["id"]
    properties["osm_type"] = "node"
    properties["route_refs"] = route_refs
    properties["route_names"] = route_names
    properties["routes"] = unique_routes
 
    features.append({
        "type": "Feature",
        "geometry": {
            "type": "Point",
            "coordinates": [stop["lon"], stop["lat"]],
        },
        "properties": properties,
    })
 
geojson = {
    "type": "FeatureCollection",
    "features": features,
}
 
try:
    with open(output_geojson, "w", encoding="utf-8") as f:
        json.dump(geojson, f, ensure_ascii=False, indent=2)
except OSError as e:
    fail(f"Fehler beim Schreiben von '{output_geojson}': {e}")
 
print(f"GeoJSON geschrieben: {output_geojson}")
print(f"Haltestellen: {len(features)}")
print(f"Haltestellen mit Routeninfo: {sum(1 for f in features if f['properties']['routes'])}")

Aufrufen

python3 osm_bus_stops_to_geojson.py bus-relevant.osm.pbf bus-stops.geojson

Die geojson hat dann so eine struktur

{
  "type": "Feature",
  "geometry": {
    "type": "Point",
    "coordinates": [16.3738, 48.2082]
  },
  "properties": {
    "name": "Karlsplatz",
    "highway": "bus_stop",
    "osm_id": "123456789",
    "route_refs": ["13A", "59A"],
    "route_names": [
      "13A: Alser Straße - Hauptbahnhof",
      "59A: Bhf. Meidling - Oper"
    ],
    "routes": [
      {
        "relation_id": "111",
        "ref": "13A",
        "name": "13A: Alser Straße - Hauptbahnhof",
        "from": "Alser Straße",
        "to": "Hauptbahnhof",
        "operator": "Wiener Linien",
        "network": "Wiener Linien",
        "member_role": "platform"
      }
    ]
  }
}

2

osmium tags-filter wien.osm.pbf \
  n/highway=bus_stop \
  r/route=bus \
  -o step1.osm.pbf -O

osmium tags-filter -i step1.osm.pbf \
  n/public_transport=platform \
  -o bus-relevant.osm.pbf -O
#!/usr/bin/env python3
import json
import math
import subprocess
import sys
import xml.etree.ElementTree as ET
from collections import defaultdict
 
if len(sys.argv) != 3:
    print("Aufruf: python3 osm_bus_geojson.py input.osm.pbf output.geojson")
    sys.exit(1)
 
input_pbf = sys.argv[1]
output_geojson = sys.argv[2]
 
proc = subprocess.Popen(
    ["osmium", "cat", input_pbf, "-f", "osm"],
    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["k"]: t.attrib["v"] for t in elem.findall("tag")}
 
def is_stop_node(tags):
    return (
        tags.get("highway") == "bus_stop"
        or tags.get("public_transport") in {"platform", "stop_position"}
    )
 
def normalize_name(value):
    if not value:
        return ""
    return " ".join(value.strip().lower().split())
 
def rounded_coord(value, digits=5):
    return round(float(value), digits)
 
def make_stop_candidate(osm_id, lon, lat, tags):
    return {
        "osm_id": int(osm_id),
        "lon": float(lon),
        "lat": float(lat),
        "name": tags.get("name"),
        "highway": tags.get("highway"),
        "public_transport": tags.get("public_transport"),
        "ref": tags.get("ref"),
        "local_ref": tags.get("local_ref"),
        "network": tags.get("network"),
        "operator": tags.get("operator"),
        "shelter": tags.get("shelter"),
        "bench": tags.get("bench"),
        "bin": tags.get("bin"),
        "tactile_paving": tags.get("tactile_paving"),
        "wheelchair": tags.get("wheelchair"),
        "raw_tags": tags,
    }
 
def stop_priority(stop):
    """
    Bevorzugung für den repräsentativen Punkt je Haltestelle.
    Niedriger = besser.
    """
    pt = stop.get("public_transport")
    hw = stop.get("highway")
 
    if hw == "bus_stop":
        return 0
    if pt == "platform":
        return 1
    if pt == "stop_position":
        return 2
    return 9
 
context = ET.iterparse(proc.stdout, events=("end",))
 
for event, elem in context:
    if elem.tag == "node":
        node_id = elem.attrib["id"]
        lon = float(elem.attrib["lon"])
        lat = float(elem.attrib["lat"])
        nodes[node_id] = (lon, lat)
 
        tags = tags_from_elem(elem)
        if is_stop_node(tags):
            stop_nodes[node_id] = make_stop_candidate(node_id, lon, lat, tags)
 
        elem.clear()
 
    elif elem.tag == "way":
        way_id = elem.attrib["id"]
        tags = tags_from_elem(elem)
        node_refs = [nd.attrib["ref"] for nd in elem.findall("nd")]
        route_ways[way_id] = {"tags": tags, "node_refs": node_refs}
        elem.clear()
 
    elif elem.tag == "relation":
        tags = tags_from_elem(elem)
 
        if tags.get("route") == "bus":
            rel_id = int(elem.attrib["id"])
            members = elem.findall("member")
 
            route_way_ids = []
            for member in members:
                mtype = member.attrib.get("type")
                mref = member.attrib.get("ref")
                role = member.attrib.get("role", "")
 
                if mtype == "node" and role in {"stop", "platform", "stop_entry_only", "stop_exit_only", ""}:
                    route_memberships_by_stop[mref].append({
                        "route_id": rel_id,
                        "line_ref": tags.get("ref"),
                        "route_name": tags.get("name"),
                        "role": role,
                        "network": tags.get("network"),
                        "operator": tags.get("operator"),
                        "from": tags.get("from"),
                        "to": tags.get("to"),
                    })
 
                elif mtype == "way" and role in {"", "forward", "backward", "route"}:
                    route_way_ids.append(mref)
 
            route_relations.append({
                "relation_id": rel_id,
                "tags": tags,
                "way_ids": route_way_ids,
            })
 
        elem.clear()
 
stderr = proc.stderr.read()
ret = proc.wait()
if ret != 0:
    print(stderr, file=sys.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["route_id"], r["line_ref"], r["route_name"])
        if key in seen:
            continue
        seen.add(key)
        bus_routes.append(r)
 
        if r["line_ref"] and r["line_ref"] not in bus_lines:
            bus_lines.append(r["line_ref"])
 
        if r["route_name"] and r["route_name"] not in bus_route_names:
            bus_route_names.append(r["route_name"])
 
    stop["bus_routes"] = bus_routes
    stop["bus_lines"] = bus_lines
    stop["bus_route_names"] = bus_route_names
    enriched_stops.append(stop)
 
# -------------------------------------------------
# Haltestellen zusammenfassen
# -------------------------------------------------
 
grouped = defaultdict(list)
 
for stop in enriched_stops:
    name_key = normalize_name(stop.get("name"))
    local_ref_key = (stop.get("local_ref") or "").strip().lower()
    coord_key = (rounded_coord(stop["lon"], 5), rounded_coord(stop["lat"], 5))
 
    # 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("name") else 1,
            s["osm_id"]
        )
    )
    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("bus_routes", []):
            rid = r.get("route_id")
            if rid in seen_route_ids:
                continue
            seen_route_ids.add(rid)
            merged_routes.append(r)
 
            line_ref = r.get("line_ref")
            if line_ref and line_ref not in merged_lines:
                merged_lines.append(line_ref)
 
            route_name = r.get("route_name")
            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 = {
        "type": "Feature",
        "geometry": {
            "type": "Point",
            "coordinates": [rep["lon"], rep["lat"]],
        },
        "properties": {
            "feature_kind": "stop",
            "osm_type": "node",
            "osm_id": rep["osm_id"],
            "name": rep.get("name"),
            "highway": rep.get("highway"),
            "public_transport": rep.get("public_transport"),
            "ref": rep.get("ref"),
            "local_ref": rep.get("local_ref"),
            "network": rep.get("network"),
            "operator": rep.get("operator"),
            "shelter": rep.get("shelter"),
            "bench": rep.get("bench"),
            "bin": rep.get("bin"),
            "tactile_paving": rep.get("tactile_paving"),
            "wheelchair": rep.get("wheelchair"),
            "bus_lines": merged_lines,
            "bus_route_names": merged_route_names,
            "bus_routes": merged_routes,
            "group_size": len(group_stops),
            "merged_osm_ids": [s["osm_id"] for s in group_stops],
            "raw_tags": rep.get("raw_tags", {}),
        },
    }
 
    features.append(feature)
 
# -------------------------------------------------
# Bus-Routen als GeoJSON drinlassen
# -------------------------------------------------
 
for rel in route_relations:
    segments = []
 
    for way_id in rel["way_ids"]:
        way = route_ways.get(way_id)
        if not way:
            continue
 
        coords = [nodes[nid] for nid in way["node_refs"] if nid in nodes]
        if len(coords) >= 2:
            segments.append([list(c) for c in coords])
 
    if not segments:
        continue
 
    if len(segments) == 1:
        geometry = {
            "type": "LineString",
            "coordinates": segments[0],
        }
    else:
        geometry = {
            "type": "MultiLineString",
            "coordinates": segments,
        }
 
    tags = rel["tags"]
    features.append({
        "type": "Feature",
        "geometry": geometry,
        "properties": {
            "feature_kind": "route",
            "osm_type": "relation",
            "osm_id": rel["relation_id"],
            "route": tags.get("route"),
            "ref": tags.get("ref"),
            "name": tags.get("name"),
            "network": tags.get("network"),
            "operator": tags.get("operator"),
            "from": tags.get("from"),
            "to": tags.get("to"),
            "colour": tags.get("colour"),
            "raw_tags": tags,
        },
    })
 
geojson = {
    "type": "FeatureCollection",
    "features": features,
}
 
with open(output_geojson, "w", encoding="utf-8") as f:
    json.dump(geojson, f, ensure_ascii=False)
 
n_stops = sum(1 for f in features if f["properties"].get("feature_kind") == "stop")
n_routes = sum(1 for f in features if f["properties"].get("feature_kind") == "route")
 
print(f"Geschrieben: {output_geojson}")
print(f"Stops: {n_stops}")
print(f"Routen: {n_routes}")

HTML

let busGeojson = await fetch("bus-stops.geojson").then(r => {
  if (!r.ok) throw new Error(`bus-stops.geojson konnte nicht geladen werden (${r.status})`);
  return r.json();
});

console.log("busGeojson:", busGeojson);

map.addSource("bus", {
  type: "geojson",
  data: busGeojson
});

// Quelle für die ausgewählte Route
map.addSource("selected-bus-route", {
  type: "geojson",
  data: {
    type: "FeatureCollection",
    features: []
  }
});

// Nur die ausgewählte Linie zeichnen
map.addLayer({
  id: "selected-bus-route-outline",
  type: "line",
  source: "selected-bus-route",
  layout: {
    "line-cap": "round",
    "line-join": "round"
  },
  paint: {
    "line-color": "#ffffff",
    "line-width": 8,
    "line-opacity": 0.95
  }
});

map.addLayer({
  id: "selected-bus-route-line",
  type: "line",
  source: "selected-bus-route",
  layout: {
    "line-cap": "round",
    "line-join": "round"
  },
  paint: {
    "line-color": "#d08a00",
    "line-width": 5,
    "line-opacity": 0.95
  }
});

// NUR Stops anzeigen
map.addLayer({
  id: "bus-stops",
  type: "circle",
  source: "bus",
  minzoom: 12,
  filter: [
    "all",
    ["==", ["geometry-type"], "Point"],
    ["==", ["get", "feature_kind"], "stop"],
	["==", ["get", "public_transport"], "stop_position"]
  ],
  paint: {
    "circle-radius": 4,
    "circle-color": "#15803d",
    "circle-stroke-color": "#ffffff",
    "circle-stroke-width": 1
  }
});

function parseJsonArray(value) {
  if (Array.isArray(value)) return value;
  if (typeof value !== "string" || !value.trim()) return [];

  try {
    const parsed = JSON.parse(value);
    return Array.isArray(parsed) ? parsed : [];
  } catch (err) {
    console.warn("parseJsonArray failed:", value, err);
    return [];
  }
}

function parseJsonObjectArray(value) {
  if (Array.isArray(value)) return value;
  if (typeof value !== "string" || !value.trim()) return [];

  try {
    const parsed = JSON.parse(value);
    return Array.isArray(parsed) ? parsed : [];
  } catch (err) {
    console.warn("parseJsonObjectArray failed:", value, err);
    return [];
  }
}

function getBusRouteFeatureByRelationId(routeId) {
  if (!busGeojson?.features) return null;

  return busGeojson.features.find((f) => {
    const p = f.properties || {};
    return p.feature_kind === "route" && String(p.osm_id) === String(routeId);
  }) || null;
}

function clearSelectedBusRoute() {
  const src = map.getSource("selected-bus-route");
  if (!src) return;

  src.setData({
    type: "FeatureCollection",
    features: []
  });
}

function showSelectedBusRoute(routeId) {
  const feature = getBusRouteFeatureByRelationId(routeId);
  if (!feature) {
    console.warn("Keine Route gefunden für relation id:", routeId);
    return;
  }

  const src = map.getSource("selected-bus-route");
  if (!src) return;

  src.setData({
    type: "FeatureCollection",
    features: [feature]
  });

  const bounds = new maplibregl.LngLatBounds();

  if (feature.geometry?.type === "LineString") {
    feature.geometry.coordinates.forEach((coord) => bounds.extend(coord));
  } else if (feature.geometry?.type === "MultiLineString") {
    feature.geometry.coordinates.forEach((line) => {
      line.forEach((coord) => bounds.extend(coord));
    });
  }

  if (!bounds.isEmpty()) {
    map.fitBounds(bounds, { padding: 50, maxZoom: 16 });
  }
}

function buildBusStopPopupHtml(props, lng, lat) {
  const name = props.name || "Haltestelle";
  const network = props.network || "-";
  const osmId = props.osm_id || "-";

  const busRoutes = parseJsonObjectArray(props.bus_routes);
  const busLines = parseJsonArray(props.bus_lines);

  let connectionsHtml = "<div style='color:#64748b;'>Keine Linien gefunden</div>";

  if (busRoutes.length) {
    connectionsHtml = `
      <div
        style="
          max-height:220px;
          overflow-y:auto;
          border:1px solid #e2e8f0;
          border-radius:8px;
          background:#f8fafc;
          padding:6px;
          margin-top:6px;
        "
      >
        <div style="display:flex;flex-direction:column;gap:6px;">
          ${busRoutes.map((route) => `
            <button
              type="button"
              class="bus-route-btn"
              data-route-id="${escapeHtml(String(route.route_id || ""))}"
              style="
                display:block;
                width:100%;
                text-align:left;
                border:1px solid #cbd5e1;
                background:#ffffff;
                border-radius:8px;
                padding:8px 10px;
                cursor:pointer;
                overflow:hidden;
              "
            >
              <div style="font-weight:700; color:#0f172a; margin-bottom:2px;">
                ${escapeHtml(String(route.line_ref || "-"))}
              </div>
              <div
                style="
                  font-size:12px;
                  line-height:1.3;
                  color:#475569;
                  word-break:break-word;
                "
              >
                ${escapeHtml(String(route.route_name || "-"))}
              </div>
            </button>
          `).join("")}
        </div>
      </div>
    `;
  } else if (busLines.length) {
    connectionsHtml = `
      <div
        style="
          max-height:160px;
          overflow-y:auto;
          border:1px solid #e2e8f0;
          border-radius:8px;
          background:#f8fafc;
          padding:8px;
          margin-top:6px;
          font-size:13px;
          line-height:1.4;
        "
      >
        ${busLines.map(line => `
          <div style="padding:4px 0; border-bottom:1px solid #e2e8f0;">
            ${escapeHtml(String(line))}
          </div>
        `).join("")}
      </div>
    `;
  }

  return `
    <div style="min-width:280px; max-width:360px; font-size:13px; line-height:1.4;">
      <div style="margin-bottom:8px;">
        <div style="font-size:16px; font-weight:700; color:#0f172a;">
          ${escapeHtml(name)}
        </div>
        <div style="font-size:12px; color:#64748b; margin-top:2px;">
          Netzwerk: ${escapeHtml(network)}
        </div>
      </div>

      <div style="font-size:12px; color:#475569;">
        OSMID: ${escapeHtml(String(osmId))}<br>
        Koordinaten: ${lat}, ${lng}
      </div>

      <div style="margin-top:12px;">
        <div style="font-weight:600; color:#0f172a; margin-bottom:4px;">Linien</div>
        ${connectionsHtml}
      </div>

      <div style="margin-top:12px;">
        <button
          type="button"
          id="clear-bus-route-btn"
          style="
            border:1px solid #cbd5e1;
            background:#fff;
            border-radius:8px;
            padding:6px 10px;
            cursor:pointer;
            width:100%;
          "
        >
          Route ausblenden
        </button>
      </div>
    </div>
  `;
}

map.on("click", "bus-stops", (e) => {
  const f = e.features?.[0];
  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, lng, lat))
    .addTo(map);

  const popupEl = popup.getElement();
  if (!popupEl) return;

  popupEl.querySelectorAll(".bus-route-btn").forEach((btn) => {
    btn.addEventListener("click", () => {
      const routeId = btn.dataset.routeId;
      if (!routeId) return;
      showSelectedBusRoute(routeId);
    });
  });

  popupEl.querySelector("#clear-bus-route-btn")?.addEventListener("click", () => {
    clearSelectedBusRoute();
  });
});

map.on("mouseenter", "bus-stops", () => {
  map.getCanvas().style.cursor = "pointer";
});

map.on("mouseleave", "bus-stops", () => {
  map.getCanvas().style.cursor = "";
});

Linux

sudo apt update
sudo apt install osmium-tool
 
osmium extract --bbox 16.20,48.10,16.50,48.35 --strategy smart -o wien.osm.pbf europe-latest.osm.pbf
 
osmium tags-filter wien.osm.pbf n/highway=bus_stop -o bus_stops.osm.pbf
 
osmium sort edited.osm.pbf -o edited-sorted.osm.pbf
 
osmium renumber edited-sorted.osm.pbf -o edited-renumbered.osm.pbf
 
osmium fileinfo -e edited-renumbered.osm.pbf
 
osmium extract -b 16.36,48.20,16.38,48.21 input.osm.pbf -o output.osm.pbf --output-header=generator="MeinTool/1.0"

Siehe osmium-extract

# bbox
at - 9.54063,46.37853,17.17397,49.01266
vienna - 16.20,48.10,16.50,48.35

Python

import osmium
import sys
 
MIN_LON = 16.20
MIN_LAT = 48.10
MAX_LON = 16.50
MAX_LAT = 48.35
 
INPUT_FILE = "europe-latest.osm.pbf"
OUTPUT_FILE = "wien.osm.pbf"
 
 
def in_bbox(lon: float, lat: float) -> bool:
    return MIN_LON <= lon <= MAX_LON and MIN_LAT <= lat <= MAX_LAT
 
 
class BBoxWriter(osmium.SimpleHandler):
    def __init__(self, writer):
        super().__init__()
        self.writer = writer
 
    def node(self, n):
        if n.location.valid() and in_bbox(n.location.lon, n.location.lat):
            self.writer.add_node(n)
 
    def way(self, w):
        for nr in w.nodes:
            if nr.location.valid() and in_bbox(nr.location.lon, nr.location.lat):
                self.writer.add_way(w)
                return
 
    def relation(self, r):
        # Sehr einfache Variante:
        # Relations werden hier nicht sauber räumlich geprüft.
        # Für vollständige OSM-Extrakte ist osmium-tool besser.
        pass
 
 
def main():
    writer = osmium.SimpleWriter(OUTPUT_FILE)
    handler = BBoxWriter(writer)
 
    try:
        # mit_locations() ist nötig, damit Ways Node-Koordinaten bekommen
        handler.apply_file(INPUT_FILE, locations=True)
    finally:
        writer.close()
 
    print(f"Fertig: {OUTPUT_FILE}")
 
 
if __name__ == "__main__":
    main()
python.exe -m venv .venv
.venv\Scripts\activate
python -m pip install --upgrade pip
pip install osmium
python script.py

Search-Source extrahieren

wget https://download.geofabrik.de/europe/austria-latest.osm.pbf

osmium tags-filter austria-latest.osm.pbf \
  nwr/place \
  nwr/amenity \
  nwr/shop \
  nwr/tourism \
  nwr/leisure \
  nwr/railway \
  nwr/public_transport \
  nwr/highway \
  nwr/addr:* \
  -o search-source.osm.pbf

osmium export search-source.osm.pbf -o search-source.geojson

search.json bauen

import json
 
INPUT = "search-source.geojson"
OUTPUT = "search.json"
 
def center_of_geometry(geom):
    t = geom.get("type")
    coords = geom.get("coordinates")
 
    if t == "Point":
        return coords[0], coords[1]
 
    if t == "LineString" and coords:
        mid = coords[len(coords) // 2]
        return mid[0], mid[1]
 
    if t == "Polygon" and coords and coords[0]:
        ring = coords[0]
        mid = ring[len(ring) // 2]
        return mid[0], mid[1]
 
    if t == "MultiPolygon" and coords and coords[0] and coords[0][0]:
        ring = coords[0][0]
        mid = ring[len(ring) // 2]
        return mid[0], mid[1]
 
    return None, None
 
def norm(props, *keys):
    for k in keys:
        v = props.get(k)
        if v:
            return str(v).strip()
    return ""
 
with open(INPUT, "r", encoding="utf-8") as f:
    geo = json.load(f)
 
out = []
seen = set()
 
for feat in geo.get("features", []):
    props = feat.get("properties", {})
    geom = feat.get("geometry", {})
 
    lng, lat = center_of_geometry(geom)
    if lng is None or lat is None:
        continue
 
    name = norm(props, "name:de", "name_de", "name")
    place = norm(props, "place")
    housenumber = norm(props, "addr:housenumber")
    street = norm(props, "addr:street")
    city = norm(props, "addr:city", "addr:place")
    postcode = norm(props, "addr:postcode")
    amenity = norm(props, "amenity")
    shop = norm(props, "shop")
    tourism = norm(props, "tourism")
    leisure = norm(props, "leisure")
    railway = norm(props, "railway")
    public_transport = norm(props, "public_transport")
    highway = norm(props, "highway")
 
    item = None
 
    if housenumber and street:
        item = {
            "t": "house",
            "name": f"{street} {housenumber}",
            "street": street,
            "housenumber": housenumber,
            "city": city,
            "postcode": postcode,
            "lat": round(lat, 6),
            "lng": round(lng, 6),
        }
    elif highway and name:
        item = {
            "t": "street",
            "name": name,
            "city": city,
            "postcode": postcode,
            "lat": round(lat, 6),
            "lng": round(lng, 6),
        }
    elif place and name:
        item = {
            "t": "place",
            "name": name,
            "place": place,
            "postcode": postcode,
            "lat": round(lat, 6),
            "lng": round(lng, 6),
        }
    elif name and (amenity or shop or tourism or leisure or railway or public_transport):
        item = {
            "t": "poi",
            "name": name,
            "class": amenity or shop or tourism or leisure or railway or public_transport,
            "lat": round(lat, 6),
            "lng": round(lng, 6),
        }
 
    if not item:
        continue
 
    key = (item["t"], item["name"], item["lat"], item["lng"])
    if key in seen:
        continue
    seen.add(key)
    out.append(item)
 
with open(OUTPUT, "w", encoding="utf-8") as f:
    json.dump(out, f, ensure_ascii=False, separators=(",", ":"))
 
print(f"{len(out)} Einträge nach {OUTPUT} geschrieben")

Im Browser suchen

let SEARCH_INDEX = [];
 
async function loadSearchIndex() {
  const res = await fetch("search.json");
  SEARCH_INDEX = await res.json();
}
 
function searchLocal(query) {
  const q = query.trim().toLowerCase();
  if (!q) return [];
 
  return SEARCH_INDEX
    .filter(item => {
      const hay = [
        item.name || "",
        item.street || "",
        item.city || "",
        item.postcode || ""
      ].join(" ").toLowerCase();
 
      return hay.includes(q);
    })
    .slice(0, 20);
}
osmium.txt · Zuletzt geändert: 2026/04/05 13:50 von jango