import argparse
import math
import os
import sys
from collections import defaultdict
try:
import osmium
except ImportError:
sys.exit(
"prepare.py needs pyosmium: pip install osmium\n"
"(a virtualenv is fine; nothing else here imports it)"
)
ROUTABLE_HIGHWAY = {
"motorway",
"motorway_link",
"trunk",
"trunk_link",
"primary",
"primary_link",
"secondary",
"secondary_link",
"tertiary",
"tertiary_link",
"unclassified",
"residential",
"living_street",
"road",
}
OPTIONAL_HIGHWAY = {"service"}
EARTH_RADIUS_M = 6371008.8
def haversine(lon1, lat1, lon2, lat2):
p1, p2 = math.radians(lat1), math.radians(lat2)
dp = p2 - p1
dl = math.radians(lon2 - lon1)
a = math.sin(dp / 2) ** 2 + math.cos(p1) * math.cos(p2) * math.sin(dl / 2) ** 2
return 2 * EARTH_RADIUS_M * math.asin(math.sqrt(a))
def oneway_kind(tags):
ow = tags.get("oneway", "").strip().lower()
if ow in ("yes", "1", "true"):
return "fwd"
if ow == "-1" or ow == "reverse":
return "rev"
if ow in ("no", "0", "false"):
return "both"
if tags.get("junction", "").lower() in ("roundabout", "circular"):
return "fwd"
return "both"
def keep(tags, routable):
hw = tags.get("highway")
return hw is not None and hw in routable
def main():
ap = argparse.ArgumentParser()
ap.add_argument("pbf")
ap.add_argument("--out-dir", default=None, help="default: the .pbf's directory")
ap.add_argument("--prefix", default=None, help="default: the .pbf's basename")
ap.add_argument(
"--no-service",
action="store_true",
help="drop highway=service (parking aisles, driveways)",
)
args = ap.parse_args()
routable = set(ROUTABLE_HIGHWAY)
if not args.no_service:
routable |= OPTIONAL_HIGHWAY
out_dir = args.out_dir or os.path.dirname(os.path.abspath(args.pbf))
prefix = args.prefix or os.path.basename(args.pbf).split(".")[0].replace(
"-latest", ""
)
os.makedirs(out_dir, exist_ok=True)
edges_path = os.path.join(out_dir, prefix + ".edges.csv")
nodes_path = os.path.join(out_dir, prefix + ".nodes.csv")
print("pass 1: counting node usage...", file=sys.stderr)
uses = defaultdict(int)
ways_kept = 0
for w in osmium.FileProcessor(args.pbf, osmium.osm.WAY):
if not keep(w.tags, routable):
continue
ways_kept += 1
refs = [n.ref for n in w.nodes]
if len(refs) < 2:
continue
for r in refs:
uses[r] += 1
print(f"pass 1: {ways_kept} ways kept, {len(uses)} distinct nodes", file=sys.stderr)
print("pass 2: splitting ways into edges...", file=sys.stderr)
vid = {} vcoord = []
def vertex(osm_id, lon, lat):
v = vid.get(osm_id)
if v is None:
v = len(vcoord) + 1
vid[osm_id] = v
vcoord.append((lon, lat))
return v
edge_id = 0
skipped_ways = 0
with open(edges_path, "w") as f:
f.write("id,source,target,cost,reverse_cost,x1,y1,x2,y2\n")
fp = osmium.FileProcessor(args.pbf).with_locations()
for w in fp:
if w.type_str() != "w" or not keep(w.tags, routable):
continue
try:
pts = [(n.ref, n.lon, n.lat) for n in w.nodes if n.location.valid()]
except (osmium.InvalidLocationError, RuntimeError):
skipped_ways += 1
continue
if len(pts) < 2:
skipped_ways += 1
continue
kind = oneway_kind(w.tags)
last = len(pts) - 1
seg_start = 0
for i in range(1, len(pts)):
is_vertex = i == last or uses[pts[i][0]] >= 2
if not is_vertex:
continue
chunk = pts[seg_start : i + 1]
cost = 0.0
xs, ys = [], []
for (_, lo, la), (_, lo2, la2) in zip(chunk, chunk[1:]):
cost += haversine(lo, la, lo2, la2)
for _, lo, la in chunk:
xs.append(lo)
ys.append(la)
seg_start = i
if cost <= 0.0:
continue
s = vertex(chunk[0][0], chunk[0][1], chunk[0][2])
t = vertex(chunk[-1][0], chunk[-1][1], chunk[-1][2])
if s == t:
continue if kind == "rev":
s, t = t, s
rev = -1.0 if kind in ("fwd", "rev") else cost
edge_id += 1
f.write(
"%d,%d,%d,%.3f,%.3f,%.7f,%.7f,%.7f,%.7f\n"
% (
edge_id,
s,
t,
cost,
rev,
min(xs),
min(ys),
max(xs),
max(ys),
)
)
with open(nodes_path, "w") as f:
f.write("node,lon,lat\n")
for i, (lo, la) in enumerate(vcoord, start=1):
f.write("%d,%.7f,%.7f\n" % (i, lo, la))
print(
f"ways kept: {ways_kept}\nedges: {edge_id}\nvertices: {len(vcoord)}"
+ (f"\nways skipped (missing locations): {skipped_ways}" if skipped_ways else ""),
file=sys.stderr,
)
print(f"wrote {edges_path}\nwrote {nodes_path}", file=sys.stderr)
if __name__ == "__main__":
main()