From e68db3ba38884e052ca48c81d8aa033d428a296a Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Thu, 29 Feb 2024 11:06:30 +0000 Subject: [PATCH] Start an example with 2021 England commutes. It doesn't quite run yet. --- examples/edinburgh/config.json | 4 +- .../england_2011_home_to_work/config.json | 4 +- examples/england_2021_home_to_work/README.md | 11 +++ .../england_2021_home_to_work/config.json | 18 ++++ examples/england_2021_home_to_work/setup.py | 82 +++++++++++++++++++ examples/england_2021_home_to_work/utils.py | 1 + examples/lisbon/config.json | 4 +- examples/run_all.sh | 1 + od2net/src/config.rs | 4 + od2net/src/od.rs | 13 ++- 10 files changed, 136 insertions(+), 6 deletions(-) create mode 100644 examples/england_2021_home_to_work/README.md create mode 100644 examples/england_2021_home_to_work/config.json create mode 100644 examples/england_2021_home_to_work/setup.py create mode 120000 examples/england_2021_home_to_work/utils.py diff --git a/examples/edinburgh/config.json b/examples/edinburgh/config.json index ca2ba6e..502ec4a 100644 --- a/examples/edinburgh/config.json +++ b/examples/edinburgh/config.json @@ -4,7 +4,9 @@ "pattern": { "BetweenZones": { "zones_path": "zones.geojson", - "csv_path": "od.csv" + "csv_path": "od.csv", + "origin_zone_centroid_fallback": false, + "destination_zone_centroid_fallback": false } }, "origins_path": "buildings.geojson", diff --git a/examples/england_2011_home_to_work/config.json b/examples/england_2011_home_to_work/config.json index a92e646..8a3a65b 100644 --- a/examples/england_2011_home_to_work/config.json +++ b/examples/england_2011_home_to_work/config.json @@ -4,7 +4,9 @@ "pattern": { "BetweenZones": { "zones_path": "zones.geojson", - "csv_path": "od.csv" + "csv_path": "od.csv", + "origin_zone_centroid_fallback": false, + "destination_zone_centroid_fallback": false } }, "origins_path": "buildings.geojson", diff --git a/examples/england_2021_home_to_work/README.md b/examples/england_2021_home_to_work/README.md new file mode 100644 index 0000000..57dcb41 --- /dev/null +++ b/examples/england_2021_home_to_work/README.md @@ -0,0 +1,11 @@ +# 2021 England home-to-work + +- Origins: all buildings +- Destinations: all buildings (for now) +- Flows include all modes (including work-from-home) + +Data sources: + +- [ODWP01EW](https://www.nomisweb.co.uk/sources/census_2021_od): UK OA to OA, about 9 million rows + - There are [major caveats](https://www.ons.gov.uk/peoplepopulationandcommunity/populationandmigration/populationestimates/bulletins/origindestinationdataenglandandwales/census2021#origin-destination-workplace-data) with OD data from 2021 due to COVID. +- OA zone geojson from ONS Geography diff --git a/examples/england_2021_home_to_work/config.json b/examples/england_2021_home_to_work/config.json new file mode 100644 index 0000000..0e08022 --- /dev/null +++ b/examples/england_2021_home_to_work/config.json @@ -0,0 +1,18 @@ +{ + "requests": { + "description": "2021 home to work trips in England, for any mode of commuting. From OA-level data from ODWP01EW. Origins and destinations are all buildings.", + "pattern": { + "BetweenZones": { + "zones_path": "zones.geojson", + "csv_path": "od.csv", + "origin_zone_centroid_fallback": true, + "destination_zone_centroid_fallback": true + } + }, + "origins_path": "buildings.geojson", + "destinations_path": "buildings.geojson" + }, + "cost": "Distance", + "uptake": "Identity", + "lts": "BikeOttawa" +} diff --git a/examples/england_2021_home_to_work/setup.py b/examples/england_2021_home_to_work/setup.py new file mode 100644 index 0000000..851e634 --- /dev/null +++ b/examples/england_2021_home_to_work/setup.py @@ -0,0 +1,82 @@ +import csv +import json +import os.path +import sys + +from utils import * + + +def makeOSM(): + download( + url="https://download.geofabrik.de/europe/great-britain/england-latest.osm.pbf", + outputFilename="input/input.osm.pbf", + ) + + +def makeOrigins(): + extractCentroids( + osmInput="input/input.osm.pbf", geojsonOutput="input/buildings.geojson" + ) + + +def makeDestinations(): + # Same as origins + pass + + +def makeZones(): + download( + url="https://github.com/dabreegster/uk-boundaries/raw/main/2021_output_areas.geojson.gz", + outputFilename="input/zones.geojson.gz", + ) + run(["gunzip", "input/zones.geojson.gz"]) + + with open("input/zones.geojson") as f1: + gj = json.load(f1) + gj["features"] = list( + filter(lambda f: f["properties"]["OA21CD"][0] == "E", gj["features"]) + ) + for f in gj["features"]: + props = {"name": f["properties"]["OA21CD"]} + f["properties"] = props + + with open("input/zones.geojson", "w") as f2: + f2.write(json.dumps(gj)) + + +def makeOD(): + download( + url="https://www.nomisweb.co.uk/output/census/2021/odwp01ew.zip", + outputFilename="input/odwp01ew.zip", + ) + run(["unzip", "input/odwp01ew.zip", "-d", "input"]) + with open("input/ODWP01EW_OA.csv") as f1: + with open("input/od.csv", "w") as f2: + writer = csv.DictWriter(f2, fieldnames=["from", "to", "count"]) + writer.writeheader() + + for row in csv.DictReader(f1): + zone1 = row["Output Areas code"] + zone2 = row["OA of workplace code"] + if zone1[0] == "E" and zone2[0] == "E": + # Ideally there'd be a split by commute mode. This dataset + # includes work-from-home, but just include all trips. + count = int(row["Count"]) + if count > 0: + writer.writerow( + { + "from": zone1, + "to": zone2, + "count": count, + } + ) + + +if __name__ == "__main__": + checkDependencies() + run(["mkdir", "-p", "input"]) + makeOSM() + makeOrigins() + makeDestinations() + makeZones() + makeOD() diff --git a/examples/england_2021_home_to_work/utils.py b/examples/england_2021_home_to_work/utils.py new file mode 120000 index 0000000..50fbc6d --- /dev/null +++ b/examples/england_2021_home_to_work/utils.py @@ -0,0 +1 @@ +../utils.py \ No newline at end of file diff --git a/examples/lisbon/config.json b/examples/lisbon/config.json index 766c87a..b031136 100644 --- a/examples/lisbon/config.json +++ b/examples/lisbon/config.json @@ -4,7 +4,9 @@ "pattern": { "BetweenZones": { "zones_path": "zones.geojson", - "csv_path": "od.csv" + "csv_path": "od.csv", + "origin_zone_centroid_fallback": false, + "destination_zone_centroid_fallback": false } }, "origins_path": "buildings.geojson", diff --git a/examples/run_all.sh b/examples/run_all.sh index 26752aa..e7e0442 100755 --- a/examples/run_all.sh +++ b/examples/run_all.sh @@ -59,6 +59,7 @@ run_example lisbon # Huge run_example england_2011_home_to_work +run_example england_2021_home_to_work run_example seattle python3 summarize_results.py */output/metadata.json diff --git a/od2net/src/config.rs b/od2net/src/config.rs index a495f0e..8cceb65 100644 --- a/od2net/src/config.rs +++ b/od2net/src/config.rs @@ -45,6 +45,10 @@ pub enum ODPattern { /// Path to a CSV file that must have 3 columns "from", "to", and "count". The first /// two must match zone names. "count" must be an integer. csv_path: String, + /// If an origin zone doesn't have any matching origin points, use the zone's centroid instead. + origin_zone_centroid_fallback: bool, + /// If a destination zone doesn't have any matching origin points, use the zone's centroid instead. + destination_zone_centroid_fallback: bool, }, ZoneToPoint { /// Path to a GeoJSON file containing Polygons and MultiPolygons with a "name" property diff --git a/od2net/src/od.rs b/od2net/src/od.rs index 907ddbf..d21afd6 100644 --- a/od2net/src/od.rs +++ b/od2net/src/od.rs @@ -83,6 +83,8 @@ pub fn generate_requests( ODPattern::BetweenZones { zones_path, csv_path, + origin_zone_centroid_fallback, + destination_zone_centroid_fallback, } => { let zones_path = format!("{input_directory}/{zones_path}"); let csv_path = format!("{input_directory}/{csv_path}"); @@ -91,9 +93,14 @@ pub fn generate_requests( let zones = load_zones(&zones_path)?; timer.stop(); timer.start("Matching points to zones"); - let origins_per_zone = points_per_polygon("origin", origins, &zones, false)?; - let destinations_per_zone = - points_per_polygon("destination", destinations, &zones, false)?; + let origins_per_zone = + points_per_polygon("origin", origins, &zones, *origin_zone_centroid_fallback)?; + let destinations_per_zone = points_per_polygon( + "destination", + destinations, + &zones, + *destination_zone_centroid_fallback, + )?; timer.stop(); timer.start(format!("Generating requests from {csv_path}"));