#!/usr/bin/env python3 """Convert an OSM API XML dump to overpass-style JSON for build_level_osm.py. Usage: osm_xml_to_json.py Emits ways (tags+nodes+geometry), street-furniture nodes (trees, lamps, benches, bins, bollards, bus stops, public art), and assembles water multipolygon relations (the Brisbane River is a relation, not a way) into synthetic closed ways tagged natural=water. """ import json import sys import xml.etree.ElementTree as ET # node tags the level builder turns into props -- everything else is dropped FURNITURE = { "natural": ("tree",), "highway": ("street_lamp", "bus_stop"), "amenity": ("bench", "waste_basket", "drinking_water", "bicycle_parking", "post_box"), "barrier": ("bollard",), "tourism": ("artwork",), } WAY_KEYS = ("building", "highway", "leisure", "landuse", "natural", "water", "waterway", "barrier", "man_made", "amenity", "tourism") root = ET.parse(sys.argv[1]).getroot() nodes = {} elements = [] for n in root.iter("node"): nid = int(n.get("id")) lat, lon = float(n.get("lat")), float(n.get("lon")) nodes[nid] = (lat, lon) tags = {t.get("k"): t.get("v") for t in n.findall("tag")} if any(tags.get(k) in v for k, v in FURNITURE.items()): elements.append({"type": "node", "id": nid, "tags": tags, "lat": lat, "lon": lon}) ways = {} for w in root.iter("way"): wid = int(w.get("id")) tags = {t.get("k"): t.get("v") for t in w.findall("tag")} nds = [int(nd.get("ref")) for nd in w.findall("nd")] if not all(r in nodes for r in nds) or len(nds) < 2: continue ways[wid] = nds if any(tags.get(k) for k in WAY_KEYS): elements.append({"type": "way", "id": wid, "tags": tags, "nodes": nds, "geometry": [{"lat": nodes[r][0], "lon": nodes[r][1]} for r in nds]}) # water multipolygons: join outer member ways into rings def assemble(rings_nodes): segs = [list(s) for s in rings_nodes if len(s) >= 2] rings = [] while segs: ring = segs.pop() grew = True while grew and ring[0] != ring[-1]: grew = False for i, s in enumerate(segs): if s[0] == ring[-1]: ring += s[1:] elif s[-1] == ring[-1]: ring += list(reversed(s))[1:] elif s[-1] == ring[0]: ring = s + ring[1:] elif s[0] == ring[0]: ring = list(reversed(s)) + ring[1:] else: continue segs.pop(i) grew = True break if ring[0] == ring[-1] and len(ring) > 3: rings.append(ring) return rings synth = 1 for r in root.iter("relation"): tags = {t.get("k"): t.get("v") for t in r.findall("tag")} if not (tags.get("natural") == "water" or tags.get("water") or tags.get("waterway") == "riverbank"): continue outers = [ways[int(m.get("ref"))] for m in r.findall("member") if m.get("type") == "way" and m.get("role") in ("outer", "") and int(m.get("ref")) in ways] for ring in assemble(outers): elements.append({"type": "way", "id": 10**10 + synth, "tags": {"natural": "water"}, "nodes": ring, "geometry": [{"lat": nodes[n][0], "lon": nodes[n][1]} for n in ring]}) synth += 1 json.dump({"elements": elements}, open(sys.argv[2], "w")) b = sum(1 for e in elements if e.get("tags", {}).get("building")) h = sum(1 for e in elements if e.get("tags", {}).get("highway")) w = sum(1 for e in elements if e.get("tags", {}).get("natural") == "water" or e.get("tags", {}).get("water")) t = sum(1 for e in elements if e["type"] == "node" and e["tags"].get("natural") == "tree") p = sum(1 for e in elements if e["type"] == "node") - t print("converted: %d buildings, %d highways, %d water polys, %d trees, %d props" % (b, h, w, t, p))