summaryrefslogtreecommitdiffstats
path: root/code/osm_data.py
blob: c101ef656d0b688ec44b2cfa2528372fc204df2a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
"""OpenStreetMap layers for the study region, extracted once from the Geofabrik files in working_data/osm.

Layers in cache/osm_study_region.gpkg (EPSG:3035):
  waterways   river / stream / canal lines (same filter as flood_api, notes/08)
  water       natural=water polygons (lakes, reservoirs, river surfaces)
  roads       drivable highways with road_class and bridge flag
  facilities  fire stations and hospitals as points
"""
import argparse
import geopandas as gpd, pandas as pd, shapely

from paths import CACHE, DATA

FILE = CACHE / "osm_study_region.gpkg"
PAD_DEG = 0.05
WATERWAYS = ("river", "stream", "canal")
HIGHWAY_CLASS = {
    "motorway": "Highway", "trunk": "Highway", "motorway_link": "Highway", "trunk_link": "Highway",
    "primary": "Primary road", "primary_link": "Primary road",
    "secondary": "Secondary road", "secondary_link": "Secondary road",
    **{k: "Local road" for k in ("tertiary", "tertiary_link", "unclassified", "residential", "living_street", "service")},
}
AMENITIES = ("fire_station", "hospital")


def _sql_in(values):
    return "(" + ", ".join(f"'{v}'" for v in values) + ")"


def _read(path, layer, where, bbox):
    return gpd.read_file(path, layer=layer, where=where, bbox=bbox, engine="pyogrio")


def build():
    from region import hull
    w, s, e, n = gpd.GeoSeries([hull()], crs=3035).to_crs(4326).total_bounds + [-PAD_DEG, -PAD_DEG, PAD_DEG, PAD_DEG]
    parts = {k: [] for k in ("waterways", "water", "roads", "facilities")}
    for pbf in sorted((DATA / "osm").glob("*.osm.pbf")):
        print(f"  {pbf.name}", flush=True)
        lines = _read(pbf, "lines", f"waterway IN {_sql_in(WATERWAYS)} OR highway IN {_sql_in(HIGHWAY_CLASS)}", (w, s, e, n))
        parts["waterways"].append(lines[lines.waterway.isin(WATERWAYS)][["osm_id", "waterway", "geometry"]])
        roads = lines[lines.highway.isin(list(HIGHWAY_CLASS))].copy()
        roads["road_class"] = roads.highway.map(HIGHWAY_CLASS)
        tags = roads.other_tags.fillna("")
        roads["bridge"] = tags.str.contains('"bridge"=>"', regex=False) & ~tags.str.contains('"bridge"=>"no"', regex=False)
        parts["roads"].append(roads[["osm_id", "highway", "road_class", "bridge", "geometry"]])

        polys = _read(pbf, "multipolygons", f"natural = 'water' OR amenity IN {_sql_in(AMENITIES)}", (w, s, e, n))
        polys["osm_id"] = polys.osm_id.fillna(polys.osm_way_id)
        parts["water"].append(polys[polys.natural == "water"][["osm_id", "geometry"]])
        fac = polys[polys.amenity.isin(AMENITIES)][["osm_id", "name", "amenity", "geometry"]].copy()
        fac["geometry"] = fac.geometry.representative_point()
        pts = _read(pbf, "points", " OR ".join(f"other_tags LIKE '%\"amenity\"=>\"{a}\"%'" for a in AMENITIES), (w, s, e, n))
        pts["amenity"] = pts.other_tags.str.extract(r'"amenity"=>"([^"]+)"')[0]
        parts["facilities"] += [fac, pts[pts.amenity.isin(AMENITIES)][["osm_id", "name", "amenity", "geometry"]]]

    FILE.parent.mkdir(parents=True, exist_ok=True)
    for name, frames in parts.items():
        g = pd.concat(frames, ignore_index=True)
        g = g.drop_duplicates("osm_id")  # extracts overlap at their borders
        g = gpd.GeoDataFrame(g, geometry="geometry", crs=4326).to_crs(3035)
        g.to_file(FILE, layer=name, driver="GPKG")
        print(f"  {name}: {len(g):,}")


def layer(name, region):
    """One layer clipped to the bounding box of `region` (EPSG:3035), buffered by 2 km."""
    if not FILE.exists():
        build()
    x0, y0, x1, y1 = region.buffer(2000).bounds
    return gpd.read_file(FILE, layer=name, bbox=(x0, y0, x1, y1), engine="pyogrio")


if __name__ == "__main__":
    p = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
    p.parse_args()
    build()