#!/usr/bin/env python3 """Generate crash-junction events from real OSM intersections. Usage: osm_junction.py "Name|Street A|Street B" ... For each spec: junction = shared node of the two streets. Every same-named way passing within RADIUS of it becomes a convoy stream (clipped, plus a reversed copy lane-offset the other way). Spawn point sits ~120m up Street A aimed at the junction. Coordinates are level-local Godot metres (x east, z south). """ import json import math import sys RADIUS = 170.0 LANE = 2.7 TARGETS = [15000, 35000, 60000] src, out = sys.argv[1], sys.argv[2] LAT0, LON0 = float(sys.argv[3]), float(sys.argv[4]) M_LAT = 110574.0 M_LON = 111320.0 * math.cos(math.radians(LAT0)) def xz(lat, lon): return ((lon - LON0) * M_LON, -(lat - LAT0) * M_LAT) data = json.load(open(src)) ways = {} for e in data["elements"]: t = e.get("tags", {}) if e["type"] == "way" and t.get("highway") and t.get("name") and e.get("geometry"): ways.setdefault(t["name"], []).append(e) def offset(pts, d): outp = [] for i, p in enumerate(pts): a = pts[max(i - 1, 0)] b = pts[min(i + 1, len(pts) - 1)] dx, dz = b[0] - a[0], b[1] - a[1] L = math.hypot(dx, dz) or 1.0 outp.append([p[0] + dz / L * d, p[1] - dx / L * d]) return outp events = [] for spec in sys.argv[5:]: name, a, b = spec.split("|")[:3] na = {n for w in ways.get(a, []) for n in w["nodes"]} nb = {n for w in ways.get(b, []) for n in w["nodes"]} shared = na & nb if not shared: print("SKIP %s: %s never meets %s" % (name, a, b)) continue jn = sorted(shared)[0] jpt = None for w in ways[a]: if jn in w["nodes"]: g = w["geometry"][w["nodes"].index(jn)] jpt = xz(g["lat"], g["lon"]) streams = [] spawn = None for street in (a, b): for w in ways[street]: pts = [xz(g["lat"], g["lon"]) for g in w["geometry"]] near = [p for p in pts if math.hypot(p[0] - jpt[0], p[1] - jpt[1]) < RADIUS] if len(near) < 2: continue length = sum(math.hypot(near[i + 1][0] - near[i][0], near[i + 1][1] - near[i][1]) for i in range(len(near) - 1)) if length < 60: continue streams.append({"pts": offset(near, LANE), "speed": 12.0, "interval": 2.2}) streams.append({"pts": offset(list(reversed(near)), LANE), "speed": 11.0, "interval": 2.6}) if street == a and spawn is None: best = max(near, key=lambda p: math.hypot(p[0] - jpt[0], p[1] - jpt[1])) dx, dz = jpt[0] - best[0], jpt[1] - best[1] L = math.hypot(dx, dz) or 1.0 spawn = {"x": best[0], "z": best[1], "dx": dx / L, "dz": dz / L} if not streams or spawn is None: print("SKIP %s: no usable streams" % name) continue events.append({"name": name, "spawn": spawn, "streams": streams[:6], "targets": TARGETS}) print("OK %s: %d streams, spawn %.0fm out" % (name, min(len(streams), 6), math.hypot(spawn["x"] - jpt[0], spawn["z"] - jpt[1]))) json.dump(events, open(out, "w"), indent=1) print("wrote %s (%d events)" % (out, len(events)))