crs-l3-tokyo-jgd-crossings
Model error
Retrospective
assessed 2026-06-09T18:05:00Z
Map (reference ← swipe → agent)
0:00
I'm rebuilding the road-safety dashboard for Tokyo's 23 special wards. For each ward I need to know how often the drivable road network crosses the ward boundary (please skip service roads), plus a 50 m buffer around every crossing for jurisdictional reporting. Pull the data from current OSM via Overpass. Run the engineering layers in the regional metric coordinate system so the distances and areas come out in real metres.
Save everything to a single `tokyo_crossings.gpkg` with five layers:
- `wards_jgd` (Polygon): the 23 ward boundaries.
- `crossing_points` (Point): one point at each place a road crosses a ward boundary.
- `crossing_buffers_50m` (Polygon): a 50 m buffer around each crossing point.
- `buffer_ward_intersection` (Polygon): each buffer clipped to the ward whose boundary it crossed.
- `ward_crossing_density_wgs84` (Polygon): one row per ward with `ward_id`, `crossing_count`, `ward_area_km2`, and `crossings_per_km2`. This is what the public dashboard reads, so it goes in WGS84 while the other four stay in the metric grid.
Use each ward's OSM relation id as `ward_id`, and carry the English and native ward names as `ward_name_en` and `ward_name` on every layer that's tied to a specific ward.
0:00
System info
{
"adapter": "openrouter",
"model": "deepseek/deepseek-v4-flash",
"base_url": "https://openrouter.ai/api/v1"
}
0:11
Let me break this down: 1. Get Tokyo's 23 special wards from OSM via Overpass 2. Get the drivable road network (excluding service roads) for the Tokyo area 3. Find where roads cross ward boundaries 4. Create 50m buffers around crossings 5. Clip buffers to respective wards 6. Calculate crossing density per ward 7. Save everything to a GeoPackage with 5 layers Let me start by writing a Python script to do all of this. First, let me understand the coordinate systems: - Metric coordinate system for Japan: JGD2011 / Japan Plane Rectangular CS IX (zone 9 for Tokyo) - EPSG:6675? Actually, for Tokyo it's zone 9 (EPSG:6675). Let me check - JGD2011 / Japan Plane Rectangular CS IX is EPSG:6675. Or JGD2000 / Japan Plane Rectangular CS IX is EPSG:2449. Let me use EPSG:6675 (JGD2011 / Japan Plane Rectangular CS IX) which covers Tokyo. Actually, let me think about this more carefully. The Tokyo 23 special wards are in zone 9 of Japan Plane Rectangular coordinate system. For JGD2011, EPSG:6675 is zone IX. For JGD2000, EPSG:2449 is zone IX. Let me use EPSG:6675 (JGD2011 / Japan Plane Rectangular CS IX). For WGS84, EPSG:4326. Let me write the script. The 23 special wards of Tokyo have OSM relations. Let me query Overpass for them. Actually, let me think about the approach: 1. Get the 23 special wards boundaries via Overpass 2. Get drivable roads via Overpass (highway=motorway, trunk, primary, secondary, tertiary, unclassified, residential, and their links - but not service) 3. Find intersections of road lines with ward boundaries 4. Create buffers 5. Clip buffers to wards 6. Calculate densities Let me write this script step by step. For Overpass, I'll use overpass-api.de or overpass.kumi.systems. Let me start coding. First, let me check what's available in terms of libraries.
0:11
Bash
python3 -c "import geopandas; print(geopandas.__version__)"
1.1.3
0:14
Bash
python3 -c "import shapely; print(shapely.__version__)"
2.1.2
0:15
Bash
python3 -c "import pyproj; print(pyproj.__version__)"
3.7.2
1:04
Let me plan this out: 1. Fetch Tokyo 23 special wards from OSM via Overpass API using their relation IDs 2. Fetch road network (excluding service roads) within the Tokyo area 3. Compute crossing points where roads cross ward boundaries 4. Create 50m buffers around crossings 5. Clip buffers to the intersected ward 6. Compute crossing density per ward 7. Save everything to GeoPackage with proper CRS The Tokyo 23 special wards are: - Chiyoda, Chuo, Minato, Shinjuku, Bunkyo, Taito, Sumida, Koto, Shinagawa, Meguro, Ota, Setagaya, Shibuya, Nakano, Suginami, Toshima, Kita, Arakawa, Itabashi, Nerima, Adachi, Katsushika, Edogawa Let me look up the OSM relation IDs for these wards. The coordinate system for metric distances in Japan: JGD2011 / Japan Plane Rectangular CS - Zone 9 (EPSG:6677) for Tokyo area. Actually, Tokyo is mostly in Zone 9 (EPSG:6677) and Zone 16 for some parts? Let me check... Tokyo 23 wards are in Zone 9 (EPSG:6677 or EPSG:2451 for JGD2000 / Japan Plane Rectangular CS IX). For JGD2011, it's EPSG:6677. Actually, for Japan Plane Rectangular CS IX (Tokyo), EPSG:6677 (JGD2011) or EPSG:2451 (JGD2000). Let me use EPSG:6677. Let me start by writing a Python script. First, let me get the OSM relation IDs for the 23 wards. Actually, let me write a comprehensive script.
1:04
Write
/work/solve.py
content · 484 lines · py
#!/usr/bin/env python3
"""
Tokyo 23 special wards — road boundary crossing analysis.
Fetches data from OSM via Overpass, computes crossings, buffers, densities.
Output: /work/tokyo_crossings.gpkg (5 layers)
"""
import json
import math
import requests
import geopandas as gpd
import pandas as pd
import numpy as np
from shapely.geometry import Point, LineString, MultiLineString, box
from shapely.ops import linemerge, unary_union, polygonize
from shapely import wkt
from functools import lru_cache
import os
import sys
# ── Config ──────────────────────────────────────────────────────────────
OUT_GPKG = "/work/tokyo_crossings.gpkg"
OVERPAST_URL = "https://overpass-api.de/api/interpreter"
# JGD2011 / Japan Plane Rectangular CS IX (Tokyo region)
METRIC_CRS = "EPSG:6677"
WGS84_CRS = "EPSG:4326"
# ── 23 special ward names (English) and their expected OSM relation ids ──
# We'll discover them via Overpass query
WARD_RELATIONS = {
350031: "Chiyoda",
350032: "Chuo",
350033: "Minato",
350034: "Shinjuku",
350035: "Bunkyo",
350036: "Taito",
350037: "Sumida",
350038: "Koto",
350039: "Shinagawa",
350040: "Meguro",
350041: "Ota",
350042: "Setagaya",
350043: "Shibuya",
350044: "Nakano",
350045: "Suginami",
350046: "Toshima",
350047: "Kita",
350048: "Arakawa",
350049: "Itabashi",
350050: "Nerima",
350051: "Adachi",
350052: "Katsushika",
350053: "Edogawa",
}
def overpass_query(query):
"""Run an Overpass QL query and return parsed JSON."""
r = requests.post(OVERPAST_URL, data={"data": query}, timeout=180)
r.raise_for_status()
return r.json()
def get_elements_from_overpass(query):
"""Return list of element dicts from Overpass."""
data = overpass_query(query)
return data.get("elements", [])
def geom_from_element(elem, id_map=None):
"""Build a shapely geometry from an Overpass element."""
if elem["type"] == "node":
return Point(elem["lon"], elem["lat"])
elif elem["type"] == "way":
if id_map is None:
return None
coords = []
for nid in elem.get("nodes", []):
node = id_map.get(nid)
if node is not None:
coords.append((node["lon"], node["lat"]))
if len(coords) < 2:
return None
return LineString(coords)
elif elem["type"] == "relation":
# We'll handle relations differently
return None
return None
def fetch_ward_boundaries():
"""Fetch all 23 special ward boundary relations from OSM.
Returns a GeoDataFrame with columns: ward_id (int), ward_name_en, ward_name (ja),
and geometry (Polygon, WGS84)."""
# First, get all relation ids and their details
rel_ids = list(WARD_RELATIONS.keys())
# Query to get relation details and member ways + their nodes
# We'll use a recursive query: get relation → get member ways → get way nodes
query = f"""
[out:json][timeout:180];
(
rel({','.join(str(i) for i in rel_ids)});
);
out body;
>;
out skel qt;
"""
elements = get_elements_from_overpass(query)
# Build node index
nodes = {}
for elem in elements:
if elem["type"] == "node":
nodes[elem["id"]] = elem
# Build way index
ways = {}
for elem in elements:
if elem["type"] == "way":
ways[elem["id"]] = elem
# Build relation index
rels = {}
for elem in elements:
if elem["type"] == "relation":
rels[elem["id"]] = elem
# For each relation, build the boundary polygon
ward_data = []
for rid, en_name in WARD_RELATIONS.items():
rel = rels.get(rid)
if rel is None:
print(f" WARNING: relation {rid} ({en_name}) not found")
continue
# Get tags
tags = rel.get("tags", {})
ja_name = tags.get("name:ja", tags.get("name", en_name))
# Collect all way geometries that form the boundary (role=outer)
outer_lines = []
for member in rel.get("members", []):
if member["type"] == "way" and member.get("role") in ("outer", ""):
wid = member["ref"]
way = ways.get(wid)
if way is None:
continue
coords = []
for nid in way.get("nodes", []):
node = nodes.get(nid)
if node is not None:
coords.append((node["lon"], node["lat"]))
if len(coords) >= 2:
outer_lines.append(LineString(coords))
if not outer_lines:
print(f" WARNING: no outer boundary for {rid} ({en_name})")
continue
# Merge lines and polygonize
merged = unary_union(outer_lines)
if merged.geom_type == "MultiLineString":
# Try to polygonize
from shapely.ops import polygonize as poly_ops
polygons = list(poly_ops(merged))
if polygons:
# Pick the largest polygon
polygons.sort(key=lambda p: p.area, reverse=True)
geom = polygons[0]
else:
print(f" WARNING: could not polygonize {rid} ({en_name})")
continue
elif merged.geom_type == "LineString":
from shapely.ops import polygonize as poly_ops
polygons = list(poly_ops(merged))
if polygons:
polygons.sort(key=lambda p: p.area, reverse=True)
geom = polygons[0]
else:
print(f" WARNING: could not polygonize {rid} ({en_name})")
continue
else:
print(f" Unexpected geom type {merged.geom_type} for {rid}")
continue
ward_data.append({
"ward_id": rid,
"ward_name_en": en_name,
"ward_name": ja_name,
"geometry": geom,
})
print(f" {en_name} ({ja_name}) — OK")
return gpd.GeoDataFrame(ward_data, crs=WGS84_CRS, geometry="geometry")
def fetch_roads(bbox):
"""Fetch drivable roads (excluding service roads) within the bounding box.
bbox: (min_lat, min_lon, max_lat, max_lon)
Returns a GeoDataFrame with LineString geometries in WGS84.
"""
min_lat, min_lon, max_lat, max_lon = bbox
# OSM highway values considered "drivable" (excluding service, track, etc.)
# We include: motorway, trunk, primary, secondary, tertiary, unclassified, residential,
# motorway_link, trunk_link, primary_link, secondary_link, tertiary_link
# Exclude: service, track, footway, cycleway, path, pedestrian, steps, bridleway, etc.
highway_values = [
"motorway", "trunk", "primary", "secondary", "tertiary",
"unclassified", "residential",
"motorway_link", "trunk_link", "primary_link", "secondary_link", "tertiary_link"
]
# Build a query with individual highway=value filters
highway_filters = "\n ".join(f'way["highway"="{v}"]' for v in highway_values)
query = f"""
[out:json][timeout:180];
(
{highway_filters}({min_lat},{min_lon},{max_lat},{max_lon});
);
out body;
>;
out skel qt;
"""
print(" Fetching road data from Overpass...")
elements = get_elements_from_overpass(query)
print(f" Got {len(elements)} elements")
# Build node index
nodes = {}
for elem in elements:
if elem["type"] == "node":
nodes[elem["id"]] = elem
# Build ways
road_rows = []
for elem in elements:
if elem["type"] != "way":
continue
tags = elem.get("tags", {})
highway = tags.get("highway", "")
coords = []
for nid in elem.get("nodes", []):
node = nodes.get(nid)
if node is not None:
coords.append((node["lon"], node["lat"]))
if len(coords) < 2:
continue
road_rows.append({
"osm_id": elem["id"],
"highway": highway,
"geometry": LineString(coords),
})
print(f" Built {len(road_rows)} road linestrings")
return gpd.GeoDataFrame(road_rows, crs=WGS84_CRS, geometry="geometry")
def explode_multigeoms(gdf):
"""Explode Multi* geometries into individual simple geometries."""
rows = []
for idx, row in gdf.iterrows():
geom = row.geometry
if geom is None:
continue
if geom.geom_type.startswith("Multi"):
for part in geom.geoms:
r = row.copy()
r.geometry = part
rows.append(r)
else:
rows.append(row)
if rows:
return gpd.GeoDataFrame(rows, crs=gdf.crs, geometry="geometry")
else:
return gdf.iloc[0:0]
def compute_crossings(wards_metric, roads_metric):
"""
For each ward, find all points where a road crosses the ward boundary.
wards_metric: GeoDataFrame (Polygon) in metric CRS
roads_metric: GeoDataFrame (LineString) in metric CRS
Returns: GeoDataFrame of crossing points (Point, metric CRS) with ward_id, ward_name_en, ward_name
"""
crossing_rows = []
for widx, ward in wards_metric.iterrows():
ward_geom = ward.geometry
boundary = ward_geom.boundary
for ridx, road in roads_metric.iterrows():
road_geom = road.geometry
if not road_geom.intersects(boundary):
continue
intersection = road_geom.intersection(boundary)
if intersection.is_empty:
continue
# intersection could be Point or MultiPoint
if intersection.geom_type == "Point":
crossing_rows.append({
"ward_id": ward["ward_id"],
"ward_name_en": ward["ward_name_en"],
"ward_name": ward["ward_name"],
"geometry": intersection,
})
elif intersection.geom_type == "MultiPoint":
for pt in intersection.geoms:
crossing_rows.append({
"ward_id": ward["ward_id"],
"ward_name_en": ward["ward_name_en"],
"ward_name": ward["ward_name"],
"geometry": pt,
})
if crossing_rows:
return gpd.GeoDataFrame(crossing_rows, crs=METRIC_CRS, geometry="geometry")
else:
return gpd.GeoDataFrame(
{"ward_id": pd.Series(dtype=int), "ward_name_en": pd.Series(dtype=str),
"ward_name": pd.Series(dtype=str), "geometry": pd.Series(dtype=object)},
crs=METRIC_CRS, geometry="geometry"
)
def main():
# ── Step 1: Fetch ward boundaries ──
print("=== Step 1: Fetching ward boundaries from OSM ===")
wards = fetch_ward_boundaries()
print(f" Total wards loaded: {len(wards)}")
print(wards[["ward_id", "ward_name_en", "ward_name"]].to_string())
# Compute overall bounding box for road fetching (with some padding)
total_bounds = wards.total_bounds # (minx, miny, maxx, maxy) in WGS84
pad = 0.02 # ~2 km padding
bbox = (total_bounds[1] - pad, total_bounds[0] - pad,
total_bounds[3] + pad, total_bounds[2] + pad)
# ── Step 2: Fetch roads ──
print("\n=== Step 2: Fetching road network from OSM ===")
roads = fetch_roads(bbox)
roads = explode_multigeoms(roads)
print(f" Total road segments: {len(roads)}")
# ── Step 3: Reproject to metric CRS ──
print("\n=== Step 3: Reprojecting to metric CRS ===")
wards_metric = wards.to_crs(METRIC_CRS)
roads_metric = roads.to_crs(METRIC_CRS)
# Clip roads to a slightly expanded bounding box of all wards (for efficiency)
wards_bounds_metric = wards_metric.total_bounds
expand_m = 500 # 500m buffer
clip_box = box(wards_bounds_metric[0] - expand_m, wards_bounds_metric[1] - expand_m,
wards_bounds_metric[2] + expand_m, wards_bounds_metric[3] + expand_m)
roads_metric = roads_metric[roads_metric.intersects(clip_box)].copy()
print(f" Roads intersecting ward area: {len(roads_metric)}")
# ── Step 4: Compute crossing points ──
print("\n=== Step 4: Computing crossing points ===")
crossings = compute_crossings(wards_metric, roads_metric)
print(f" Total crossing points: {len(crossings)}")
if len(crossings) == 0:
print(" ERROR: No crossing points found. Something is wrong.")
sys.exit(1)
# ── Step 5: Create 50m buffers ──
print("\n=== Step 5: Creating 50m buffers ===")
buffers = crossings.copy()
buffers.geometry = buffers.geometry.buffer(50.0)
print(f" Buffer count: {len(buffers)}")
# ── Step 6: Clip buffers to the ward whose boundary was crossed ──
print("\n=== Step 6: Clipping buffers to ward boundaries ===")
# For each crossing, find which ward it belongs to
# The crossing point is ON the boundary; the buffer extends into both sides.
# We clip to the ward whose boundary was crossed.
# Build a spatial index for wards
ward_sindex = wards_metric.sindex
clipped_rows = []
for bidx, buf in buffers.iterrows():
ward_id = buf["ward_id"]
ward_row = wards_metric[wards_metric["ward_id"] == ward_id].iloc[0]
ward_geom = ward_row.geometry
clipped = buf.geometry.intersection(ward_geom)
if clipped.is_empty or clipped.area == 0:
continue
if clipped.geom_type == "MultiPolygon":
# Take the largest part
parts = list(clipped.geoms)
parts.sort(key=lambda p: p.area, reverse=True)
clipped = parts[0]
clipped_rows.append({
"ward_id": ward_id,
"ward_name_en": buf["ward_name_en"],
"ward_name": buf["ward_name"],
"geometry": clipped,
})
buffer_ward_intersection = gpd.GeoDataFrame(clipped_rows, crs=METRIC_CRS, geometry="geometry")
print(f" Clipped buffer count: {len(buffer_ward_intersection)}")
# ── Step 7: Compute crossing density per ward ──
print("\n=== Step 7: Computing crossing density ===")
# Count crossings per ward
crossing_counts = crossings.groupby("ward_id").size().reset_index(name="crossing_count")
# Compute ward area in km² (from metric CRS)
wards_metric["ward_area_km2"] = wards_metric.geometry.area / 1_000_000
# Merge
density = wards_metric[["ward_id", "ward_name_en", "ward_name", "ward_area_km2", "geometry"]].copy()
density = density.merge(crossing_counts, on="ward_id", how="left")
density["crossing_count"] = density["crossing_count"].fillna(0).astype(int)
density["crossings_per_km2"] = (density["crossing_count"] / density["ward_area_km2"]).round(4)
# Reproject density to WGS84
density_wgs84 = density.to_crs(WGS84_CRS)
density_wgs84 = density_wgs84[["ward_id", "ward_name_en", "ward_name",
"crossing_count", "ward_area_km2",
"crossings_per_km2", "geometry"]]
print(" Density table:")
print(density_wgs84[["ward_id", "ward_name_en", "crossing_count", "ward_area_km2", "crossings_per_km2"]].to_string())
# ── Step 8: Save to GeoPackage ──
print("\n=== Step 8: Saving to GeoPackage ===")
# Remove existing file if present
if os.path.exists(OUT_GPKG):
os.remove(OUT_GPKG)
# Layer 1: wards_jgd (metric CRS)
wards_out = wards_metric[["ward_id", "ward_name_en", "ward_name", "geometry"]].copy()
wards_out.to_file(OUT_GPKG, layer="wards_jgd", driver="GPKG")
print(f" Saved 'wards_jgd' — {len(wards_out)} polygons")
# Layer 2: crossing_points (metric CRS)
crossings_out = crossings[["ward_id", "ward_name_en", "ward_name", "geometry"]].copy()
crossings_out.to_file(OUT_GPKG, layer="crossing_points", driver="GPKG")
print(f" Saved 'crossing_points' — {len(crossings_out)} points")
# Layer 3: crossing_buffers_50m (metric CRS)
buffers_out = buffers[["ward_id", "ward_name_en", "ward_name", "geometry"]].copy()
buffers_out.to_file(OUT_GPKG, layer="crossing_buffers_50m", driver="GPKG")
print(f" Saved 'crossing_buffers_50m' — {len(buffers_out)} polygons")
# Layer 4: buffer_ward_intersection (metric CRS)
buffer_ward_intersection_out = buffer_ward_intersection[["ward_id", "ward_name_en", "ward_name", "geometry"]].copy()
buffer_ward_intersection_out.to_file(OUT_GPKG, layer="buffer_ward_intersection", driver="GPKG", mode="a")
print(f" Saved 'buffer_ward_intersection' — {len(buffer_ward_intersection_out)} polygons")
# Layer 5: ward_crossing_density_wgs84 (WGS84)
density_wgs84.to_file(OUT_GPKG, layer="ward_crossing_density_wgs84", driver="GPKG", mode="a")
print(f" Saved 'ward_crossing_density_wgs84' — {len(density_wgs84)} polygons")
print(f"\n✓ All layers saved to {OUT_GPKG}")
# Verify
print("\n=== Verification ===")
for layer_name in ["wards_jgd", "crossing_points", "crossing_buffers_50m",
"buffer_ward_intersection", "ward_crossing_density_wgs84"]:
try:
gdf = gpd.read_file(OUT_GPKG, layer=layer_name)
print(f" {layer_name}: {len(gdf)} features, CRS={gdf.crs}")
except Exception as e:
print(f" {layer_name}: ERROR — {e}")
if __name__ == "__main__":
main()
Wrote /work/solve.py (17723 chars).
1:27
Let me run the script and see what happens.
1:27
Bash
cd /work && python solve.py 2>&1
=== Step 1: Fetching ward boundaries from OSM ===
Traceback (most recent call last):
File "/work/solve.py", line 484, in <module>
main()
~~~~^^
File "/work/solve.py", line 337, in main
wards = fetch_ward_boundaries()
File "/work/solve.py", line 108, in fetch_ward_boundaries
elements = get_elements_from_overpass(query)
File "/work/solve.py", line 65, in get_elements_from_overpass
data = overpass_query(query)
File "/work/solve.py", line 60, in overpass_query
r.raise_for_status()
~~~~~~~~~~~~~~~~~~^^
File "/usr/local/lib/python3.14/site-packages/requests/models.py", line 1167, in raise_for_status
raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 406 Client Error: Not Acceptable for url: https://overpass-api.de/api/interpreter
1:40
The Overpass query is returning a 406 error. This is likely because we're sending the POST request without proper content-type headers. Let me adjust the query to use GET instead, or set the correct headers. Actually, the Overpass API usually works with POST when using `data` parameter. The 406 "Not Acceptable" might be because we need to set the Accept header. Let me fix this.
1:40
Edit
/work/solve.py
Edited /work/solve.py.
1:45
Let me also fix the query syntax - Overpass QL might have issues with certain syntax. Let me check the query more carefully. The issue might be that the Overpass server is expecting a different content type or the query format is wrong. Let me try also fixing the query to use proper Overpass QL syntax.
1:45
Bash
cd /work && python solve.py 2>&1
=== Step 1: Fetching ward boundaries from OSM ===
Traceback (most recent call last):
File "/work/solve.py", line 485, in <module>
main()
~~~~^^
File "/work/solve.py", line 338, in main
wards = fetch_ward_boundaries()
File "/work/solve.py", line 109, in fetch_ward_boundaries
elements = get_elements_from_overpass(query)
File "/work/solve.py", line 66, in get_elements_from_overpass
data = overpass_query(query)
File "/work/solve.py", line 61, in overpass_query
r.raise_for_status()
~~~~~~~~~~~~~~~~~~^^
File "/usr/local/lib/python3.14/site-packages/requests/models.py", line 1167, in raise_for_status
raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 406 Client Error: Not Acceptable for url: https://overpass-api.de/api/interpreter
1:52
The 406 error is likely due to the Overpass server rejecting our request format. Let me check what the response body says.
1:52
Bash
cd /work && python3 -c "
cd /work && python3 -c "
import requests
url = 'https://overpass-api.de/api/interpreter'
query = '[out:json];rel(350031);out body;'
r = requests.post(url, data={'data': query}, timeout=30)
print('Status:', r.status_code)
print('Headers:', dict(r.headers))
print('Text:', r.text[:500])
" 2>&1
Status: 406
Headers: {'Date': 'Mon, 15 Jun 2026 13:57:14 GMT', 'Server': 'Apache/2.4.67 (Debian)', 'Content-Length': '371', 'Connection': 'close', 'Content-Type': 'text/html; charset=iso-8859-1'}
Text: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html><head>
<title>406 Not Acceptable</title>
</head><body>
<h1>Not Acceptable</h1>
<p>An appropriate representation of the requested resource could not be found on this server.</p>
<hr>
<address>Apache/2.4.67 (Debian) Server at overpass-api.de Port 443</address>
</body></html>
2:00
The 406 error is because Overpass API expects a specific Accept header. Let's try with a proper Content-Type header or try using the GET method instead.
2:00
Bash
cd /work && python3 -c "
cd /work && python3 -c "
import requests
url = 'https://overpass-api.de/api/interpreter'
query = '[out:json];rel(350031);out body;'
# Try GET
r = requests.get(url, params={'data': query}, timeout=30)
print('GET Status:', r.status_code)
print('GET Text:', r.text[:300])
# Try POST with Accept header
headers = {'Accept': '*/*'}
r2 = requests.post(url, data={'data': query}, headers=headers, timeout=30)
print('POST Status:', r2.status_code)
print('POST Text:', r2.text[:300])
" 2>&1
GET Status: 406 GET Text: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html><head> <title>406 Not Acceptable</title> </head><body> <h1>Not Acceptable</h1> <p>An appropriate representation of the requested resource could not be found on this server.</p> <hr> <address>Apache/2.4.6 POST Status: 406 POST Text: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html><head> <title>406 Not Acceptable</title> </head><body> <h1>Not Acceptable</h1> <p>An appropriate representation of the requested resource could not be found on this server.</p> <hr> <address>Apache/2.4.6
2:09
The Overpass API is rejecting our requests with 406. Let me try a slightly different approach - maybe the issue is the User-Agent or we need to use a different endpoint. Let me try with curl to see what's happening.
2:09
Bash
curl -s -o /dev/null -w "%{http_code}" -X POST -d "data=[out:json];rel(350031);o...
bash: line 1: curl: command not found
2:15
The Overpass API is returning 406. Let me try a different approach - maybe the user agent or content-type needs to be set differently.
2:15
Bash
cd /work && python3 -c "
cd /work && python3 -c "
import requests
url = 'https://overpass-api.de/api/interpreter'
query = '[out:json];rel(350031);out body;'
# Try with content-type
r = requests.post(url, data={'data': query}, headers={'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json'}, timeout=30)
print('Status:', r.status_code)
print('Text:', r.text[:500])
" 2>&1
Status: 406 Text: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html><head> <title>406 Not Acceptable</title> </head><body> <h1>Not Acceptable</h1> <p>An appropriate representation of the requested resource could not be found on this server.</p> <hr> <address>Apache/2.4.67 (Debian) Server at overpass-api.de Port 443</address> </body></html>
2:29
The 406 error seems persistent. Let me try a different Overpass endpoint or check if there's an alternative. Let me try the `overpass-api.de/api/interpreter` with a different user agent or something. Actually, wait - let me check if maybe the server is blocking due to the Python requests library's default headers. Let me try setting a more browser-like user agent.
2:29
Bash
cd /work && python3 -c "
cd /work && python3 -c "
import requests
url = 'https://overpass-api.de/api/interpreter'
query = '[out:json];rel(350031);out body;'
headers = {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36',
'Accept': 'application/json, text/plain, */*'
}
r = requests.post(url, data={'data': query}, headers=headers, timeout=30)
print('Status:', r.status_code)
print('Content-Type:', r.headers.get('Content-Type'))
print('Text:', r.text[:500])
" 2>&1
Status: 406 Content-Type: text/html; charset=iso-8859-1 Text: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html><head> <title>406 Not Acceptable</title> </head><body> <h1>Not Acceptable</h1> <p>An appropriate representation of the requested resource could not be found on this server.</p> <hr> <address>Apache/2.4.67 (Debian) Server at overpass-api.de Port 443</address> </body></html>
2:39
The Overpass API is rejecting requests from the default Python User-Agent. Let me check if there's a different URL or if I need to set a proper User-Agent.
2:39
Bash
cd /work && python3 -c "
cd /work && python3 -c "
import requests
url = 'https://overpass-api.de/api/interpreter'
query = '[out:json];rel(350031);out body;'
headers = {
'User-Agent': 'Mozilla/5.0 (compatible; GIS-bot/1.0)',
'Accept': '*/*'
}
r = requests.post(url, data={'data': query}, headers=headers, timeout=30)
print('Status:', r.status_code)
print('Content-Type:', r.headers.get('Content-Type'))
print('Text:', r.text[:500])
" 2>&1
Status: 406 Content-Type: text/html; charset=iso-8859-1 Text: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html><head> <title>406 Not Acceptable</title> </head><body> <h1>Not Acceptable</h1> <p>An appropriate representation of the requested resource could not be found on this server.</p> <hr> <address>Apache/2.4.67 (Debian) Server at overpass-api.de Port 443</address> </body></html>
2:46
The 406 error is coming from the server's Apache layer, not the Overpass application. This might be an issue with the requests library being blocked by the server. Let me try a different approach - perhaps I need to use the `requests` library with a proper User-Agent and also try the query via the Overpass Turbo style or use a different API endpoint. Let me check if the server is accessible at all, and try some other Overpass instances.
2:46
Bash
cd /work && python3 -c "
cd /work && python3 -c "
import requests
# Try a different Overpass instance
url = 'https://overpass.kumi.systems/api/interpreter'
query = '[out:json];rel(350031);out body;'
r = requests.post(url, data={'data': query}, timeout=30)
print('Status:', r.status_code)
print('Text:', r.text[:500])
" 2>&1
Traceback (most recent call last):
File "/usr/local/lib/python3.14/site-packages/urllib3/connectionpool.py", line 534, in _make_request
response = conn.getresponse()
File "/usr/local/lib/python3.14/site-packages/urllib3/connection.py", line 571, in getresponse
httplib_response = super().getresponse()
File "/usr/local/lib/python3.14/http/client.py", line 1459, in getresponse
response.begin()
~~~~~~~~~~~~~~^^
File "/usr/local/lib/python3.14/http/client.py", line 336, in begin
version, status, reason = self._read_status()
~~~~~~~~~~~~~~~~~^^
File "/usr/local/lib/python3.14/http/client.py", line 297, in _read_status
line = str(self.fp.readline(_MAXLINE + 1), "iso-8859-1")
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^
File "/usr/local/lib/python3.14/socket.py", line 729, in readinto
return self._sock.recv_into(b)
~~~~~~~~~~~~~~~~~~~~^^^
File "/usr/local/lib/python3.14/ssl.py", line 1304, in recv_into
return self.read(nbytes, buffer)
~~~~~~~~~^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.14/ssl.py", line 1138, in read
return self._sslobj.read(len, buffer)
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^
TimeoutError: The read operation timed out
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/usr/local/lib/python3.14/site-packages/requests/adapters.py", line 696, in send
resp = conn.urlopen(
method=request.method,
...<9 lines>...
chunked=chunked,
)
File "/usr/local/lib/python3.14/site-packages/urllib3/connectionpool.py", line 842, in urlopen
retries = retries.increment(
method, url, error=new_e, _pool=self, _stacktrace=sys.exc_info()[2]
)
File "/usr/local/lib/python3.14/site-packages/urllib3/util/retry.py", line 498, in increment
raise reraise(type(error), error, _stacktrace)
~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.14/site-packages/urllib3/util/util.py", line 39, in reraise
raise value
File "/usr/local/lib/python3.14/site-packages/urllib3/connectionpool.py", line 788, in urlopen
response = self._make_request(
conn,
...<10 lines>...
**response_kw,
)
File "/usr/local/lib/python3.14/site-packages/urllib3/connectionpool.py", line 536, in _make_request
self._raise_timeout(err=e, url=url, timeout_value=read_timeout)
~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.14/site-packages/urllib3/connectionpool.py", line 367, in _raise_timeout
raise ReadTimeoutError(
self, url, f"Read timed out. (read timeout={timeout_value})"
) from err
urllib3.exceptions.ReadTimeoutError: HTTPSConnectionPool(host='overpass.kumi.systems', port=443): Read timed out. (read timeout=30)
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<string>", line 6, in <module>
r = requests.post(url, data={'data': query}, timeout=30)
File "/usr/local/lib/python3.14/site-packages/requests/api.py", line 134, in post
return request("post", url, data=data, json=json, **kwargs)
File "/usr/local/lib/python3.14/site-packages/requests/api.py", line 71, in request
return session.request(method=method, url=url, **kwargs)
~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.14/site-packages/requests/sessions.py", line 651, in request
resp = self.send(prep, **send_kwargs)
File "/usr/local/lib/python3.14/site-packages/requests/sessions.py", line 784, in send
r = adapter.send(request, **kwargs)
File "/usr/local/lib/python3.14/site-packages/requests/adapters.py", line 742, in send
raise ReadTimeout(e, request=request)
requests.exceptions.ReadTimeout: HTTPSConnectionPool(host='overpass.kumi.systems', port=443): Read timed out. (read timeout=30)
3:39
The overpass-api.de site is returning 406 Not Acceptable. This seems to be an issue with the request headers. Let me check what the issue is - maybe it's the User-Agent or some other header that the Apache server is configured to block. Let me try with curl or wget.
3:39
Bash
which wget 2>&1 && wget --version 2>&1 | head -2
(no output)
3:49
Let me try a different approach. I'll try to use the `Accept: application/json` or maybe the issue is related to the Overpass server expecting a different content type. Let me check the raw request headers being sent.
3:49
Bash
pip install --user httpx 2>&1 | tail -3
Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location. Successfully installed anyio-4.13.0 h11-0.16.0 httpcore-1.0.9 httpx-0.28.1
3:58
Bash
cd /work && python3 -c "
cd /work && python3 -c "
import httpx
url = 'https://overpass-api.de/api/interpreter'
query = '[out:json];rel(350031);out body;'
client = httpx.Client()
r = client.post(url, data={'data': query}, timeout=60)
print('Status:', r.status_code)
print('Content-Type:', r.headers.get('content-type'))
print('Text:', r.text[:500])
" 2>&1
Status: 406 Content-Type: text/html; charset=iso-8859-1 Text: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html><head> <title>406 Not Acceptable</title> </head><body> <h1>Not Acceptable</h1> <p>An appropriate representation of the requested resource could not be found on this server.</p> <hr> <address>Apache/2.4.67 (Debian) Server at overpass-api.de Port 443</address> </body></html>
4:04
The 406 error persists. This is a server-side issue. The Overpass API seems to be rejecting our requests based on Accept headers. Let me check if there's an alternative way to access Overpass data. Maybe I should try a different Overpass endpoint, or use the `overpass` library which might handle the headers correctly. Let me check if there's an `overpy` library installed.
4:04
Bash
pip install --user overpy 2>&1 | tail -3
Downloading overpy-0.7-py3-none-any.whl (14 kB) Installing collected packages: overpy Successfully installed overpy-0.7
4:21
Let me try using overpy (a Python library for Overpass API) to see if it works.
4:21
Bash
cd /work && python3 -c "
cd /work && python3 -c "
import overpy
api = overpy.Overpass()
result = api.query('[out:json];rel(350031);out body;')
print('Result:', result)
for rel in result.relations:
print('Relation id:', rel.id)
print('Tags:', rel.tags)
" 2>&1
Traceback (most recent call last):
File "<string>", line 4, in <module>
result = api.query('[out:json];rel(350031);out body;')
File "/home/agent/.local/lib/python3.14/site-packages/overpy/__init__.py", line 195, in query
raise current_exception
overpy.exception.OverpassUnknownHTTPStatusCode: Unknown/Unhandled status code: 406
4:37
Overpass API is returning 406. Let me check what the actual issue is - maybe the overpass-api.de instance is rejecting requests from this environment. Let me try using `http://overpass-api.de` instead of `https://`, or try a different approach. Let me check if there's a network connectivity issue or if it's an actual HTTP 406 error (which typically means server doesn't support the requested format).
4:37
Bash
cd /work && python3 -c "
cd /work && python3 -c "
import requests
# Check what URL methods work
url = 'https://overpass-api.de/api/interpreter'
# Try with just query string parameter via GET
import urllib.parse
q = '[out:json];rel(350031);out body;'
r = requests.get(url, params={'data': q}, timeout=30,
headers={'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'})
print('GET Status:', r.status_code)
print('Text preview:', r.text[:300])
" 2>&1
GET Status: 406 Text preview: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html><head> <title>406 Not Acceptable</title> </head><body> <h1>Not Acceptable</h1> <p>An appropriate representation of the requested resource could not be found on this server.</p> <hr> <address>Apache/2.4.6
4:46
The Overpass API is returning 406 for all requests. This might be because the server is configured to require a specific Accept header that includes `text/html` or something else. Let me try a different approach - maybe they've changed their API or there's an IP block. Let me try the main API endpoint with different approaches. Actually, let me check if maybe there's a rate limiting issue or if the server expects a specific format.
4:46
Bash
cd /work && python3 -c "
cd /work && python3 -c "
import requests
# Check what URL methods work
url = 'https://overpass-api.de/api/interpreter'
q = '[out:json];rel(350031);out body;'
# Try with form-encoded data properly
r = requests.post(url, data=q, timeout=30,
headers={'Content-Type': 'application/x-www-form-urlencoded',
'Accept': '*/*'})
print('POST (raw data) Status:', r.status_code)
print('Text preview:', r.text[:300])
print()
# Try with the data parameter as bytes
r2 = requests.post(url, data=q.encode(), timeout=30,
headers={'Content-Type': 'application/x-www-form-urlencoded',
'Accept': '*/*'})
print('POST (bytes data) Status:', r2.status_code)
print('Text preview:', r2.text[:300])
" 2>&1
POST (raw data) Status: 406 Text preview: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html><head> <title>406 Not Acceptable</title> </head><body> <h1>Not Acceptable</h1> <p>An appropriate representation of the requested resource could not be found on this server.</p> <hr> <address>Apache/2.4.6 POST (bytes data) Status: 406 Text preview: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html><head> <title>406 Not Acceptable</title> </head><body> <h1>Not Acceptable</h1> <p>An appropriate representation of the requested resource could not be found on this server.</p> <hr> <address>Apache/2.4.6
4:59
Something is off. The 406 error exists even when we send Accept: */*. This is strange. Let me check if maybe the overpass-api is actually working but my test query is too specific. Let me try a different Overpass endpoint. Actually, the 406 might be because the Apache server has some configuration that's rejecting. Let me try with a different user agent or try `https://overpass-api.de/api/status` first.
4:59
Bash
cd /work && python3 -c "
cd /work && python3 -c "
import requests
# Check status endpoint
r = requests.get('https://overpass-api.de/api/status', timeout=30)
print('Status code:', r.status_code)
print('Text:', r.text[:500])
" 2>&1
Status code: 406 Text: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html><head> <title>406 Not Acceptable</title> </head><body> <h1>Not Acceptable</h1> <p>An appropriate representation of the requested resource could not be found on this server.</p> <hr> <address>Apache/2.4.67 (Debian) Server at overpass-api.de Port 443</address> </body></html>
5:15
The Overpass API is returning 406 for everything, which is unusual. It might be related to the IP or some network configuration. Let me try using a different approach - maybe using a mirror or trying different instances. Wait, let me check if there's a network issue. The 406 "Not Acceptable" is typically about content negotiation. Maybe the server has a strict requirement for Accept headers or the User-Agent needs to be something specific. Let me try setting a more specific Accept header and User-Agent.
5:15
Bash
cd /work && python3 -c "
cd /work && python3 -c "
import requests
# Try with very specific Accept header for XML or JSON
# The default Overpass response without [out:json] would be XML
r = requests.get('https://overpass-api.de/api/interpreter?data=rel(350031);out%20body;',
timeout=30,
headers={'Accept': 'application/xml,text/xml,*/*',
'User-Agent': 'Mozilla/5.0'})
print('GET status:', r.status_code)
print('Text:', r.text[:300])
" 2>&1
GET status: 406 Text: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html><head> <title>406 Not Acceptable</title> </head><body> <h1>Not Acceptable</h1> <p>An appropriate representation of the requested resource could not be found on this server.</p> <hr> <address>Apache/2.4.6
5:24
The Overpass API seems to be returning 406 for all requests - this is unusual. Let me check if there's a network issue or if the service is down. Let me try another approach - maybe using a different endpoint or checking if there's a local proxy.
5:24
Bash
cd /work && python3 -c "
cd /work && python3 -c "
import requests
# Try the Overpass Turbo endpoint
urls = [
'https://overpass-api.de/api/interpreter',
'http://overpass-api.de/api/interpreter',
'https://overpass.openstreetmap.fr/api/interpreter',
]
for url in urls:
try:
r = requests.get(url, timeout=10)
print(f'{url}: status={r.status_code}')
except Exception as e:
print(f'{url}: error={e}')
" 2>&1
https://overpass-api.de/api/interpreter: status=406 http://overpass-api.de/api/interpreter: status=406 https://overpass.openstreetmap.fr/api/interpreter: status=403
5:35
The main overpass-api.de endpoint is returning 406 regardless of the Accept header. This is very unusual. The 406 status means "Not Acceptable" which is typically about content negotiation. Let me check if there's something about the request format that's different. Let me try using the `[out:json]` without the brackets being URL-encoded differently, or try a different approach. Actually, this might be a server misconfiguration or they might be filtering by some other criteria. Let me try a minimal query to see if anything works.
5:35
Bash
cd /work && python3 -c "
cd /work && python3 -c "
import requests
# Try minimal query
r = requests.get('https://overpass-api.de/api/interpreter?data=%5Bout%3Ajson%5D%3Bnode(1)%3Bout%3B', timeout=30)
print('Status:', r.status_code)
print('Text:', r.text[:500])
" 2>&1
Status: 406 Text: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html><head> <title>406 Not Acceptable</title> </head><body> <h1>Not Acceptable</h1> <p>An appropriate representation of the requested resource could not be found on this server.</p> <hr> <address>Apache/2.4.67 (Debian) Server at overpass-api.de Port 443</address> </body></html>
5:41
The Overpass API is returning 406 for everything. Let me check if maybe the server is blocking based on some other criteria like IP, or if there's a proxy issue. Let me try overpass turbo's own API or another mirror. Actually, 406 Not Acceptable means the server cannot produce a response matching the Accept headers sent. But we're getting it even with Accept: */*. That's weird - it might be that the Apache server is configured to only allow certain User-Agents or has some other restriction. Let me try with curl.
5:41
Bash
pip install --user curl_cffi 2>&1 | tail -3
Installing collected packages: pygments, pycparser, mdurl, markdown-it-py, cffi, rich, curl_cffi Successfully installed cffi-2.0.0 curl_cffi-0.15.0 markdown-it-py-4.2.0 mdurl-0.1.2 pycparser-3.0 pygments-2.20.0 rich-15.0.0
6:36
Let me try a different approach. The 406 error from overpass-api.de suggests the server is rejecting based on some header. Let me check if maybe there's a proxy or different endpoint we should use. Let me also try using a raw socket or check if there's a network path issue. Actually, let me try a different approach - maybe use `osmnx` which handles Overpass internally.
6:36
Bash
pip install --user osmnx 2>&1 | tail -5
Requirement already satisfied: certifi in /usr/local/lib/python3.14/site-packages (from pyogrio>=0.7.2->geopandas>=1.0.1->osmnx) (2026.5.20) Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.14/site-packages (from python-dateutil>=2.8.2->pandas>=1.5->osmnx) (1.17.0) Requirement already satisfied: charset_normalizer<4,>=2 in /usr/local/lib/python3.14/site-packages (from requests>=2.30->osmnx) (3.4.7) Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.14/site-packages (from requests>=2.30->osmnx) (3.18) Requirement already satisfied: urllib3<3,>=1.26 in /usr/local/lib/python3.14/site-packages (from requests>=2.30->osmnx) (2.7.0)
7:27
Let me try osmnx to see if it can fetch data from OSM.
7:27
Bash
cd /work && python3 -c "
cd /work && python3 -c "
import osmnx as ox
ox.settings.timeout = 180
ox.settings.max_query_area_size = 10000000000
# Try fetching a single ward boundary
gdf = ox.features_from_place('Chiyoda, Tokyo, Japan', tags={'boundary': 'administrative', 'admin_level': '9'})
print(gdf.columns)
print(gdf.head())
" 2>&1
Index(['geometry', 'admin_level', 'alt_name:be', 'alt_name:hr', 'alt_name:la',
'name', 'name:ab', 'name:af', 'name:am', 'name:an',
...
'landuse', 'leisure', 'contact:instagram', 'name:etymology',
'name:etymology:wikidata', 'alt_name', 'alt_name:en', 'alt_name:fr',
'alt_name:ja-Hira', 'alt_name:ja-Latn'],
dtype='str', length=324)
geometry ... alt_name:ja-Latn
element id ...
relation 382313 MULTIPOLYGON (((136.18312 20.61393, 136.26002 ... ... NaN
1543125 MULTIPOLYGON (((136.18312 20.61393, 136.26002 ... ... NaN
1758858 POLYGON ((139.67342 35.71879, 139.67378 35.719... ... NaN
1758878 POLYGON ((139.71781 35.71701, 139.71786 35.717... ... NaN
1758888 POLYGON ((139.76227 35.7316, 139.76228 35.7316... ... NaN
[5 rows x 324 columns]
8:13
osmnx works! Let me use osmnx to fetch the data. But I need to be more precise - I need the 23 special wards of Tokyo. Let me check what relations they have. Actually, I see that `ox.features_from_place` might return various features. Let me approach this differently. The OSM relation IDs for the 23 special wards of Tokyo are: - 350031 (Chiyoda) through 350053 (Edogawa) Let me use osmnx to fetch these relation boundaries.
8:13
Bash
cd /work && python3 -c "
cd /work && python3 -c "
import osmnx as ox
ox.settings.timeout = 180
# Check the relationship for Chiyoda
gdf = ox.features_from_place('Chiyoda, Tokyo, Japan', tags={'boundary': 'administrative'})
# Filter to admin_level=9 for special wards
wards = gdf[gdf['admin_level'].astype(str) == '9']
print('Number of admin_level=9 features:', len(wards))
print(wards[['name', 'admin_level', 'name:ja']].head(10).to_string())
print()
# Check what admin_level values exist
print('admin_level values:', gdf['admin_level'].unique())
" 2>&1
Number of admin_level=9 features: 337
name admin_level name:ja
element id
relation 3406698 神田駿河台 9 神田駿河台
3407418 神田三崎町 9 神田三崎町
3518461 一ツ橋 9 一ツ橋
3544394 丸の内 9 NaN
3545196 大手町 9 NaN
3545325 神田神保町 9 神田神保町
3545343 西神田 9 西神田
3545396 神田猿楽町 9 神田猿楽町
3546176 神田小川町 9 神田小川町
3546241 神田錦町 9 神田錦町
admin_level values: <ArrowStringArray>
['2', '4', '7', '9', '10', nan, '9', '7', '12']
Length: 9, dtype: str
8:45
So OSM uses admin_level=7 for the special wards (Tokyo's 23 wards are at level 7, not 9). In Japan, special wards are admin_level=7. Let me check.
8:45
Bash
cd /work && python3 -c "
cd /work && python3 -c "
import osmnx as ox
ox.settings.timeout = 180
# Get Tokyo 23 wards with admin_level=7
gdf = ox.features_from_place('Tokyo, Japan', tags={'boundary': 'administrative', 'admin_level': '7'})
print('Number of admin_level=7 features:', len(gdf))
print(gdf[['name', 'name:ja', 'osm_id']].to_string())
print()
print('Columns:', list(gdf.columns))
" 2>&1
<exit_code>-1</exit_code> <error>Command timed out after 120000ms</error>
10:54
The Overpass API seems to be rejecting requests with 406. Let me try a different approach - maybe using osmnx which handles the data fetching differently. But the previous query timed out because it was too large. Let me be more specific - fetch just the 23 special wards by their known relation IDs using osmnx.
10:54
Bash
cd /work && python3 -c "
cd /work && python3 -c "
import osmnx as ox
ox.settings.timeout = 180
ox.settings.max_query_area_size = 10000000000
ox.settings.log_console = False
# Get Tokyo-to (Tokyo prefecture) boundary
tokyo = ox.geocode_to_gdf('Tokyo, Japan')
print(tokyo.total_bounds)
" 2>&1
[135.8536855 20.2145811 154.205541 35.8984245]
11:26
Hmm, the OSM admin levels are: level 2 = country, level 4 = prefecture, level 7 = special wards (Tokyo's 23 special wards), level 9 = neighborhoods/cho-chome. So I should look for admin_level=7 in Tokyo for the 23 special wards. Let me try a more specific query.
11:26
Bash
cd /work && python3 -c "
cd /work && python3 -c "
import osmnx as ox
ox.settings.timeout = 300
ox.settings.max_query_area_size = 10000000000
ox.settings.log_console = False
# Get Tokyo prefecture first
tokyo = ox.geocode_to_gdf('Tokyo, Japan')
print('Tokyo bounds:', tokyo.total_bounds)
# Now get admin_level=7 within Tokyo's bounding box
bbox = tokyo.total_bounds
# osmnx uses (left, bottom, right, top) = (minx, miny, maxx, maxy)
gdf = ox.features_from_bbox(
bbox[3], bbox[1], bbox[0], bbox[2],
tags={'boundary': 'administrative'}
)
print('Total features:', len(gdf))
print('admin_level values:', gdf['admin_level'].unique())
# Filter to admin_level=7
wards = gdf[gdf['admin_level'].astype(str).str.strip() == '7']
print()
print('Number of admin_level=7:', len(wards))
print(wards[['name', 'name:ja', 'osm_id']].to_string())
" 2>&1
Tokyo bounds: [135.8536855 20.2145811 154.205541 35.8984245]
Traceback (most recent call last):
File "<string>", line 14, in <module>
gdf = ox.features_from_bbox(
bbox[3], bbox[1], bbox[0], bbox[2],
tags={'boundary': 'administrative'}
)
TypeError: features_from_bbox() got multiple values for argument 'tags'
11:36
The overpass-api.de server is rejecting all requests with 406 Not Acceptable. Let me try using osmnx which works through its own Overpass queries.
11:36
Bash
cd /work && python3 -c "
cd /work && python3 -c "
import osmnx as ox
ox.settings.timeout = 300
ox.settings.max_query_area_size = 10000000000
ox.settings.log_console = False
# Get Tokyo prefecture first
tokyo = ox.geocode_to_gdf('Tokyo, Japan')
print('Tokyo bounds:', tokyo.total_bounds)
# Now get admin_level=7 within Tokyo's bounding box
bbox = tokyo.total_bounds
# features_from_bbox: north, south, east, west
gdf = ox.features_from_bbox(
north=bbox[3], south=bbox[1], east=bbox[2], west=bbox[0],
tags={'boundary': 'administrative', 'admin_level': '7'}
)
print('Total features:', len(gdf))
print(gdf[['name', 'name:ja', 'osm_id']].head(30).to_string())
" 2>&1
Tokyo bounds: [135.8536855 20.2145811 154.205541 35.8984245]
Traceback (most recent call last):
File "<string>", line 14, in <module>
gdf = ox.features_from_bbox(
north=bbox[3], south=bbox[1], east=bbox[2], west=bbox[0],
tags={'boundary': 'administrative', 'admin_level': '7'}
)
TypeError: features_from_bbox() got an unexpected keyword argument 'north'
11:48
It seems the osmnx version doesn't support those kwargs. Let me check the API.
11:48
Bash
cd /work && python3 -c "
cd /work && python3 -c " import osmnx as ox help(ox.features_from_bbox) " 2>&1 | head -30
Help on function features_from_bbox in module osmnx.features:
features_from_bbox(
bbox: tuple[float, float, float, float],
tags: dict[str, bool | str | list[str]]
) -> gpd.GeoDataFrame
Download OSM features within a lat-lon bounding box.
You can use the `settings` module to retrieve a snapshot of historical OSM
data as of a certain date, or to configure the Overpass server timeout,
memory allocation, and other custom settings. This function searches for
features using tags. For more details, see:
https://wiki.openstreetmap.org/wiki/Map_features
Parameters
----------
bbox
Bounding box as `(left, bottom, right, top)`. Coordinates should be in
unprojected latitude-longitude degrees (EPSG:4326).
tags
Tags for finding elements in the selected area. Results are the union,
not intersection of the tags and each result matches at least one tag.
The keys are OSM tags (e.g. `building`, `landuse`, `highway`, etc) and
the values can be either `True` to retrieve all elements matching the
tag, or a string to retrieve a single `tag:value` combination, or a
list of strings to retrieve multiple values for the tag. For example,
`tags = {'building': True}` would return all buildings in the area.
Or, `tags = {'amenity':True, 'landuse':['retail','commercial'],
'highway':'bus_stop'}` would return all amenities, any landuse=retail,
any landuse=commercial, and any highway=bus_stop.
12:06
So the function signature is `features_from_bbox(bbox, tags)`. Let me try again with the correct signature and focus on just the Tokyo 23 special wards.
12:06
Bash
cd /work && python3 -c "
cd /work && python3 -c "
import osmnx as ox
ox.settings.timeout = 300
ox.settings.max_query_area_size = 10000000000
ox.settings.log_console = False
# Get Tokyo prefecture to narrow bounding box
tokyo = ox.geocode_to_gdf('Tokyo, Japan')
bbox = tokyo.total_bounds # (minx, miny, maxx, maxy) i.e. (left, bottom, right, top)
print('bbox:', bbox)
# We want the main land area of the 23 wards which is around 139.5-140 E, 35.5-35.8 N
# Let's use a tighter bbox for the 23 wards
bbox_23 = (139.4, 35.5, 139.95, 35.85)
gdf = ox.features_from_bbox(bbox_23, tags={'boundary': 'administrative', 'admin_level': '7'})
print('Total features:', len(gdf))
# Filter for only ones whose name is in our ward list
wards = gdf[gdf['admin_level'].astype(str).str.strip() == '7']
print('admin_level=7 count:', len(wards))
print(wards[['name', 'name:ja', 'osm_id']].to_string())
" 2>&1
bbox: [135.8536855 20.2145811 154.205541 35.8984245]
Total features: 19944
admin_level=7 count: 1657
Traceback (most recent call last):
File "<string>", line 19, in <module>
print(wards[['name', 'name:ja', 'osm_id']].to_string())
~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.14/site-packages/geopandas/geodataframe.py", line 1891, in __getitem__
result = super().__getitem__(key)
File "/usr/local/lib/python3.14/site-packages/pandas/core/frame.py", line 4384, in __getitem__
indexer = self.columns._get_indexer_strict(key, "columns")[1]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.14/site-packages/pandas/core/indexes/base.py", line 6302, in _get_indexer_strict
self._raise_if_missing(keyarr, indexer, axis_name)
~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.14/site-packages/pandas/core/indexes/base.py", line 6355, in _raise_if_missing
raise KeyError(f"{not_found} not in index")
KeyError: "['osm_id'] not in index"
13:20
Let me check what columns the features have.
13:20
Bash
cd /work && python3 -c "
cd /work && python3 -c "
import osmnx as ox
ox.settings.timeout = 300
ox.settings.max_query_area_size = 10000000000
ox.settings.log_console = False
bbox_23 = (139.4, 35.5, 139.95, 35.85)
gdf = ox.features_from_bbox(bbox_23, tags={'boundary': 'administrative', 'admin_level': '7'})
wards = gdf[gdf['admin_level'].astype(str).str.strip() == '7']
print('Columns:', list(wards.columns))
print()
# Check first few rows of relevant columns
cols = [c for c in wards.columns if 'name' in c.lower() or 'admin' in c.lower() or 'osm' in c.lower()]
print('Relevant cols:', cols)
print(wards[cols].head(20).to_string())
" 2>&1
Columns: ['geometry', 'admin_level', 'alt_name:be', 'alt_name:hr', 'alt_name:la', 'name', 'name:ab', 'name:af', 'name:am', 'name:an', 'name:ar', 'name:ast', 'name:ay', 'name:az', 'name:ba', 'name:be', 'name:bg', 'name:bn', 'name:bo', 'name:br', 'name:bs', 'name:ca', 'name:ce', 'name:ch', 'name:ckb', 'name:cnr', 'name:cs', 'name:cu', 'name:cv', 'name:cy', 'name:da', 'name:de', 'name:dv', 'name:dz', 'name:ee', 'name:el', 'name:en', 'name:eo', 'name:es', 'name:et', 'name:eu', 'name:fa', 'name:fi', 'name:fo', 'name:fr', 'name:fy', 'name:ga', 'name:gd', 'name:gl', 'name:gn', 'name:ha', 'name:he', 'name:hi', 'name:hr', 'name:ht', 'name:hu', 'name:hy', 'name:ia', 'name:id', 'name:ie', 'name:io', 'name:is', 'name:it', 'name:ja', 'name:ja-Hira', 'name:ja-Latn', 'name:jbo', 'name:jv', 'name:ka', 'name:kab', 'name:ki', 'name:kk', 'name:km', 'name:kn', 'name:ko', 'name:ku', 'name:kw', 'name:ky', 'name:la', 'name:lb', 'name:li', 'name:ln', 'name:lo', 'name:lt', 'name:lv', 'name:mg', 'name:mi', 'name:mk', 'name:ml', 'name:mn', 'name:mr', 'name:ms', 'name:mt', 'name:my', 'name:nan', 'name:ne', 'name:nl', 'name:nn', 'name:no', 'name:nv', 'name:oc', 'name:or', 'name:os', 'name:pa', 'name:pap', 'name:pl', 'name:ps', 'name:pt', 'name:qu', 'name:rm', 'name:ro', 'name:ru', 'name:rw', 'name:sa', 'name:sc', 'name:sd', 'name:se', 'name:si', 'name:sk', 'name:sl', 'name:sm', 'name:so', 'name:sq', 'name:sr', 'name:ss', 'name:su', 'name:sv', 'name:sw', 'name:szl', 'name:ta', 'name:te', 'name:tg', 'name:th', 'name:tk', 'name:tl', 'name:tr', 'name:tt', 'name:tw', 'name:ty', 'name:ug', 'name:uk', 'name:ur', 'name:uz', 'name:vi', 'name:vo', 'name:wo', 'name:wuu', 'name:yi', 'name:yo', 'name:yue', 'name:za', 'name:zh', 'name:zh-Hans', 'name:zh-Hant', 'name:zu', 'old_name:vi', 'place', 'population', 'website', 'wikidata', 'wikimedia_commons', 'wikipedia', 'source:population', 'start_date', 'layer', 'note', 'note:ja', 'ref', 'sorting_name:cs', 'source', 'source_ref', 'population:date', 'name:ja_rm', 'government', 'office', 'fax', 'level', 'phone', 'postal_code', 'operator', 'boundary', 'direction', 'highway', 'stop', 'oneway', 'lanes', 'maxspeed', 'surface', 'bicycle', 'foot', 'motorcar', 'motorcycle', 'smoothness', 'toll', 'tunnel', 'tunnel:name', 'tunnel:name:en', 'lane_markings', 'cycleway:left', 'bridge', 'noname', 'bridge:name', 'oneway:bicycle', 'lit', 'sidewalk:left', 'access', 'sidewalk', 'maritime', 'service', 'parking:lane:both', 'waterway', 'cycleway:both', 'source:ja', 'width', 'incline', 'est_width', 'parking:both', 'parking:both:restriction', 'yh:WIDTH', 'natural', 'footway', 'mapillary', 'survey:date', 'cycleway', 'cycleway:lane', 'hgv:conditional', 'handrail', 'ramp:bicycle', 'step_count', 'colour', 'electrified', 'frequency', 'gauge', 'railway', 'voltage', 'turn:lanes', 'sidewalk:left:surface', 'passing', 'lanes:backward', 'lanes:forward', 'boat', 'bus', 'hgv', 'vehicle:conditional', 'hazmat', 'official_name', 'segregated', 'sidewalk:right', 'name:etymology:wikidata', 'motor_vehicle:conditional', 'type', 'covered', 'barrier', 'left:quarter', 'right:quarter', 'check_date', 'building', 'addr:block_number', 'addr:city_block', 'addr:city_block:ref', 'addr:housenumber', 'addr:neighbourhood', 'addr:neighbourhood:ref', 'addr:place', 'addr:place:ref', 'addr:quarter', 'amenity', 'landuse', 'operation', 'operator:ownership', 'ownership', 'social_facility', 'demolished:electrified', 'demolished:gauge', 'demolished:name', 'demolished:railway', 'demolished:service', 'crossing', 'crossing:markings', 'source:alt', 'access:conditional', 'ISO3166-1', 'ISO3166-1:alpha2', 'ISO3166-1:alpha3', 'ISO3166-1:numeric', 'alt_name:eo', 'alt_name:vi', 'default_language', 'driving_side', 'flag', 'name:ace', 'name:als', 'name:ang', 'name:arc', 'name:arz', 'name:bar', 'name:bat-smg', 'name:bcl', 'name:be-tarask', 'name:bjn', 'name:bpy', 'name:bug', 'name:bxr', 'name:cbk-zam', 'name:cdo', 'name:ceb', 'name:chr', 'name:crh', 'name:csb', 'name:din', 'name:diq', 'name:dsb', 'name:ext', 'name:frp', 'name:frr', 'name:gag', 'name:gan', 'name:gu', 'name:gv', 'name:hak', 'name:haw', 'name:hif', 'name:hsb', 'name:ilo', 'name:iu', 'name:kbd', 'name:kl', 'name:koi', 'name:krc', 'name:kv', 'name:lez', 'name:lij', 'name:lmo', 'name:ltg', 'name:lzh', 'name:mdf', 'name:mhr', 'name:min', 'name:mo', 'name:mrj', 'name:mwl', 'name:mzn', 'name:na', 'name:nah', 'name:nap', 'name:nds', 'name:nds-nl', 'name:new', 'name:nov', 'name:nrm', 'name:nso', 'name:om', 'name:pam', 'name:pcd', 'name:pdc', 'name:pih', 'name:pms', 'name:pnb', 'name:roa-rup', 'name:roa-tara', 'name:rue', 'name:sah', 'name:scn', 'name:sco', 'name:sh', 'name:smn', 'name:sms', 'name:srn', 'name:stq', 'name:ti', 'name:tok', 'name:tpi', 'name:tzl', 'name:udm', 'name:vec', 'name:vro', 'name:war', 'name:xal', 'name:xmf', 'name:zea', 'official_name:ar', 'official_name:en', 'official_name:es', 'official_name:fr', 'official_name:hu', 'official_name:ja', 'official_name:ko', 'official_name:zh-Hans', 'official_name:zh-Hant', 'timezone', 'land_area', 'ISO3166-2', 'is_in:country', 'is_in:country_code', 'official_name:it', 'contact:facebook', 'contact:tripadvisor', 'contact:twitter', 'contact:website', 'contact:youtube', 'is_in:prefecture', 'name:azb', 'name:cr', 'sorting_name', 'alt_name:fr', 'leisure', 'contact:instagram', 'name:etymology', 'alt_name', 'alt_name:en', 'alt_name:ja-Hira', 'alt_name:ja-Latn', 'reclaimed', 'is_in']
Relevant cols: ['admin_level', 'alt_name:be', 'alt_name:hr', 'alt_name:la', 'name', 'name:ab', 'name:af', 'name:am', 'name:an', 'name:ar', 'name:ast', 'name:ay', 'name:az', 'name:ba', 'name:be', 'name:bg', 'name:bn', 'name:bo', 'name:br', 'name:bs', 'name:ca', 'name:ce', 'name:ch', 'name:ckb', 'name:cnr', 'name:cs', 'name:cu', 'name:cv', 'name:cy', 'name:da', 'name:de', 'name:dv', 'name:dz', 'name:ee', 'name:el', 'name:en', 'name:eo', 'name:es', 'name:et', 'name:eu', 'name:fa', 'name:fi', 'name:fo', 'name:fr', 'name:fy', 'name:ga', 'name:gd', 'name:gl', 'name:gn', 'name:ha', 'name:he', 'name:hi', 'name:hr', 'name:ht', 'name:hu', 'name:hy', 'name:ia', 'name:id', 'name:ie', 'name:io', 'name:is', 'name:it', 'name:ja', 'name:ja-Hira', 'name:ja-Latn', 'name:jbo', 'name:jv', 'name:ka', 'name:kab', 'name:ki', 'name:kk', 'name:km', 'name:kn', 'name:ko', 'name:ku', 'name:kw', 'name:ky', 'name:la', 'name:lb', 'name:li', 'name:ln', 'name:lo', 'name:lt', 'name:lv', 'name:mg', 'name:mi', 'name:mk', 'name:ml', 'name:mn', 'name:mr', 'name:ms', 'name:mt', 'name:my', 'name:nan', 'name:ne', 'name:nl', 'name:nn', 'name:no', 'name:nv', 'name:oc', 'name:or', 'name:os', 'name:pa', 'name:pap', 'name:pl', 'name:ps', 'name:pt', 'name:qu', 'name:rm', 'name:ro', 'name:ru', 'name:rw', 'name:sa', 'name:sc', 'name:sd', 'name:se', 'name:si', 'name:sk', 'name:sl', 'name:sm', 'name:so', 'name:sq', 'name:sr', 'name:ss', 'name:su', 'name:sv', 'name:sw', 'name:szl', 'name:ta', 'name:te', 'name:tg', 'name:th', 'name:tk', 'name:tl', 'name:tr', 'name:tt', 'name:tw', 'name:ty', 'name:ug', 'name:uk', 'name:ur', 'name:uz', 'name:vi', 'name:vo', 'name:wo', 'name:wuu', 'name:yi', 'name:yo', 'name:yue', 'name:za', 'name:zh', 'name:zh-Hans', 'name:zh-Hant', 'name:zu', 'old_name:vi', 'sorting_name:cs', 'name:ja_rm', 'tunnel:name', 'tunnel:name:en', 'noname', 'bridge:name', 'official_name', 'name:etymology:wikidata', 'demolished:name', 'alt_name:eo', 'alt_name:vi', 'name:ace', 'name:als', 'name:ang', 'name:arc', 'name:arz', 'name:bar', 'name:bat-smg', 'name:bcl', 'name:be-tarask', 'name:bjn', 'name:bpy', 'name:bug', 'name:bxr', 'name:cbk-zam', 'name:cdo', 'name:ceb', 'name:chr', 'name:crh', 'name:csb', 'name:din', 'name:diq', 'name:dsb', 'name:ext', 'name:frp', 'name:frr', 'name:gag', 'name:gan', 'name:gu', 'name:gv', 'name:hak', 'name:haw', 'name:hif', 'name:hsb', 'name:ilo', 'name:iu', 'name:kbd', 'name:kl', 'name:koi', 'name:krc', 'name:kv', 'name:lez', 'name:lij', 'name:lmo', 'name:ltg', 'name:lzh', 'name:mdf', 'name:mhr', 'name:min', 'name:mo', 'name:mrj', 'name:mwl', 'name:mzn', 'name:na', 'name:nah', 'name:nap', 'name:nds', 'name:nds-nl', 'name:new', 'name:nov', 'name:nrm', 'name:nso', 'name:om', 'name:pam', 'name:pcd', 'name:pdc', 'name:pih', 'name:pms', 'name:pnb', 'name:roa-rup', 'name:roa-tara', 'name:rue', 'name:sah', 'name:scn', 'name:sco', 'name:sh', 'name:smn', 'name:sms', 'name:srn', 'name:stq', 'name:ti', 'name:tok', 'name:tpi', 'name:tzl', 'name:udm', 'name:vec', 'name:vro', 'name:war', 'name:xal', 'name:xmf', 'name:zea', 'official_name:ar', 'official_name:en', 'official_name:es', 'official_name:fr', 'official_name:hu', 'official_name:ja', 'official_name:ko', 'official_name:zh-Hans', 'official_name:zh-Hant', 'official_name:it', 'name:azb', 'name:cr', 'sorting_name', 'alt_name:fr', 'name:etymology', 'alt_name', 'alt_name:en', 'alt_name:ja-Hira', 'alt_name:ja-Latn']
admin_level alt_name:be alt_name:hr alt_name:la name name:ab name:af name:am name:an name:ar name:ast name:ay name:az name:ba name:be name:bg name:bn name:bo name:br name:bs name:ca name:ce name:ch name:ckb name:cnr name:cs name:cu name:cv name:cy name:da name:de name:dv name:dz name:ee name:el name:en name:eo name:es name:et name:eu name:fa name:fi name:fo name:fr name:fy name:ga name:gd name:gl name:gn name:ha name:he name:hi name:hr name:ht name:hu name:hy name:ia name:id name:ie name:io name:is name:it name:ja name:ja-Hira name:ja-Latn name:jbo name:jv name:ka name:kab name:ki name:kk name:km name:kn name:ko name:ku name:kw name:ky name:la name:lb name:li name:ln name:lo name:lt name:lv name:mg name:mi name:mk name:ml name:mn name:mr name:ms name:mt name:my name:nan name:ne name:nl name:nn name:no name:nv name:oc name:or name:os name:pa name:pap name:pl name:ps name:pt name:qu name:rm name:ro name:ru name:rw name:sa name:sc name:sd name:se name:si name:sk name:sl name:sm name:so name:sq name:sr name:ss name:su name:sv name:sw name:szl name:ta name:te name:tg name:th name:tk name:tl name:tr name:tt name:tw name:ty name:ug name:uk name:ur name:uz name:vi name:vo name:wo name:wuu name:yi name:yo name:yue name:za name:zh name:zh-Hans name:zh-Hant name:zu old_name:vi sorting_name:cs name:ja_rm tunnel:name tunnel:name:en noname bridge:name official_name name:etymology:wikidata demolished:name alt_name:eo alt_name:vi name:ace name:als name:ang name:arc name:arz name:bar name:bat-smg name:bcl name:be-tarask name:bjn name:bpy name:bug name:bxr name:cbk-zam name:cdo name:ceb name:chr name:crh name:csb name:din name:diq name:dsb name:ext name:frp name:frr name:gag name:gan name:gu name:gv name:hak name:haw name:hif name:hsb name:ilo name:iu name:kbd name:kl name:koi name:krc name:kv name:lez name:lij name:lmo name:ltg name:lzh name:mdf name:mhr name:min name:mo name:mrj name:mwl name:mzn name:na name:nah name:nap name:nds name:nds-nl name:new name:nov name:nrm name:nso name:om name:pam name:pcd name:pdc name:pih name:pms name:pnb name:roa-rup name:roa-tara name:rue name:sah name:scn name:sco name:sh name:smn name:sms name:srn name:stq name:ti name:tok name:tpi name:tzl name:udm name:vec name:vro name:war name:xal name:xmf name:zea official_name:ar official_name:en official_name:es official_name:fr official_name:hu official_name:ja official_name:ko official_name:zh-Hans official_name:zh-Hant official_name:it name:azb name:cr sorting_name alt_name:fr name:etymology alt_name alt_name:en alt_name:ja-Hira alt_name:ja-Latn
element id
node 721916368 7 NaN NaN NaN 江東区 NaN NaN NaN NaN كوتو، طوكيو NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN Kōtō NaN NaN NaN NaN Koto NaN Kōtō NaN Koto NaN NaN NaN Kōtō NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN Koto NaN NaN NaN Koto 江東区 こうとうく Kōtō-ku NaN NaN NaN NaN NaN NaN NaN NaN 고토구 NaN NaN NaN NaN NaN NaN NaN NaN Kotas NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
[... truncated ...] NaN Shinjuku NaN Shinjuku NaN NaN NaN Синдзюку NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN Шінджюку NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 新宿区 新宿区 新宿區 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
1758878 7 NaN NaN NaN 文京区 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN Bunkyō NaN NaN NaN NaN NaN NaN NaN Arrondissement de Bunkyō NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 文京区 ぶんきょうく Bunkyō-ku NaN NaN NaN NaN NaN NaN NaN NaN 분쿄구 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN Бункё NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN Бункьо NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 文京區 文京区 文京區 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
1758888 7 NaN NaN NaN 台東区 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN Taito NaN Taito NaN NaN NaN NaN NaN Arrondissement de Taitō NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 台東区 たいとうく Taitō-ku NaN NaN NaN NaN NaN NaN NaN NaN 다이토구 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN Тайто NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 臺東區 台东区 臺東區 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
1758891 7 NaN NaN NaN 墨田区 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN Sumida NaN Sumida NaN NaN NaN NaN NaN Arrondissement de Sumida NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 墨田区 すみだく Sumida-ku NaN NaN NaN NaN NaN NaN NaN NaN 스미다구 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN Сумида NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 墨田區 墨田区 墨田區 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
1758897 7 NaN NaN NaN 中央区 NaN NaN NaN NaN تشوأو، طوكيو NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN Chūō NaN NaN NaN NaN Chuo NaN Chūō NaN NaN NaN NaN NaN Arrondissement de Chūō NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN Chūō 中央区 ちゅうおうく Chūō-ku NaN NaN NaN NaN NaN NaN NaN NaN 주오구 NaN NaN NaN NaN NaN NaN NaN NaN Čiūo NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN Chūō NaN NaN NaN NaN NaN Тюо NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN เขตชูโอ NaN NaN NaN NaN NaN NaN NaN Тюо NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 中央区 中央区 中央區 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
1758936 7 NaN NaN NaN 目黒区 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN Meguro NaN Meguro NaN NaN NaN NaN NaN Arrondissement de Meguro NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 目黒区 めぐろく Meguro-ku NaN NaN NaN NaN NaN NaN NaN NaN 메구로구 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN Мегуро NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 目黑區 目黑区 目黑區 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
13:54
I can see that OSM admin_level=7 in Tokyo includes the special wards (区 - ku). The features have `name`, `name:en`, `name:ja`, etc. I notice the element index is a MultiIndex with (element_type, id). Let me check what element types we have. Looking at the output, I can see: - `node 721916368` - Koto ward - Various relations like `relation 1758878` - Bunkyo ward Let me figure out the relation IDs for the 23 special wards. The ones I listed earlier (350031-350053) might be wrong. Let me check the actual relation IDs from osmnx.
13:54
Bash
cd /work && python3 -c "
cd /work && python3 -c "
import osmnx as ox
ox.settings.timeout = 300
ox.settings.max_query_area_size = 10000000000
ox.settings.log_console = False
bbox_23 = (139.4, 35.5, 139.95, 35.85)
gdf = ox.features_from_bbox(bbox_23, tags={'boundary': 'administrative', 'admin_level': '7'})
wards = gdf[gdf['admin_level'].astype(str).str.strip() == '7']
# Check indices
print('Index type:', type(wards.index))
print('Index names:', wards.index.names)
print()
# The index is a MultiIndex with (element_type, id)
# Let's get relation-type features only (those are the actual ward boundaries)
# Filter to relation type
rel_mask = wards.index.get_level_values(0) == 'relation'
rel_wards = wards[rel_mask]
print(f'Number of relation features: {len(rel_wards)}')
print()
# Show relevant info
print('name, name:en, name:ja, element_id:')
for idx, row in rel_wards.iterrows():
etype, eid = idx
print(f' relation {eid}: name={row.get(\"name\",\"\")}, name:en={row.get(\"name:en\",\"\")}, name:ja={row.get(\"name:ja\",\"\")}')
" 2>&1
Index type: <class 'pandas.MultiIndex'> Index names: ['element', 'id'] Number of relation features: 76 name, name:en, name:ja, element_id: relation 1543055: name=杉並区, name:en=Suginami, name:ja=杉並区 relation 1543056: name=中野区, name:en=Nakano, name:ja=中野区 relation 1758858: name=新宿区, name:en=Shinjuku, name:ja=新宿区 relation 1758878: name=文京区, name:en=Bunkyō, name:ja=文京区 relation 1758888: name=台東区, name:en=Taito, name:ja=台東区 relation 1758891: name=墨田区, name:en=Sumida, name:ja=墨田区 relation 1758897: name=中央区, name:en=Chuo, name:ja=中央区 relation 1758936: name=目黒区, name:en=Meguro, name:ja=目黒区 relation 1758947: name=大田区, name:en=Ota, name:ja=大田区 relation 1759474: name=世田谷区, name:en=Setagaya, name:ja=世田谷区 relation 1759475: name=狛江市, name:en=Komae, name:ja=狛江市 relation 1759477: name=渋谷区, name:en=Shibuya, name:ja=渋谷区 relation 1759506: name=豊島区, name:en=Toshima, name:ja=豊島区 relation 1760038: name=北区, name:en=Kita, name:ja=北区 relation 1760040: name=荒川区, name:en=Arakawa, name:ja=荒川区 relation 1760078: name=板橋区, name:en=Itabashi, name:ja=板橋区 relation 1760119: name=練馬区, name:en=Nerima, name:ja=練馬区 relation 1760124: name=足立区, name:en=Adachi, name:ja=足立区 relation 1761717: name=港区, name:en=Minato, name:ja=港区 relation 1761718: name=葛飾区, name:en=Katsushika, name:ja=葛飾区 relation 1761742: name=千代田区, name:en=Chiyoda, name:ja=千代田区 relation 1761743: name=江戸川区, name:en=Edogawa, name:ja=江戸川区 relation 1761771: name=立川市, name:en=Tachikawa, name:ja=立川市 relation 1761804: name=武蔵野市, name:en=Musashino, name:ja=武蔵野市 relation 1761820: name=三鷹市, name:en=Mitaka, name:ja=三鷹市 relation 1767047: name=さいたま市, name:en=Saitama, name:ja=さいたま市 relation 1768272: name=入間市, name:en=Iruma, name:ja=入間市 relation 1768273: name=所沢市, name:en=Tokorozawa, name:ja=所沢市 relation 1768274: name=新座市, name:en=Niiza, name:ja=新座市 relation 1768277: name=狭山市, name:en=Sayama, name:ja=狭山市 relation 1768312: name=和光市, name:en=Wako, name:ja=和光市 relation 1768313: name=戸田市, name:en=Toda, name:ja=戸田市 relation 1768314: name=朝霞市, name:en=Asaka, name:ja=朝霞市 relation 1768315: name=蕨市, name:en=Warabi, name:ja=蕨市 relation 1768335: name=三郷市, name:en=Misato, name:ja=三郷市 relation 1768336: name=八潮市, name:en=Yashio, name:ja=八潮市 relation 1768337: name=川口市, name:en=Kawaguchi, name:ja=川口市 relation 1768338: name=草加市, name:en=Soka, name:ja=草加市 relation 1768500: name=三芳町, name:en=Miyoshi, name:ja=三芳町 relation 1768983: name=ふじみ野市, name:en=Fujimino, name:ja=ふじみ野市 relation 1768984: name=富士見市, name:en=Fujimi, name:ja=富士見市 relation 1768985: name=川越市, name:en=Kawagoe, name:ja=川越市 relation 1768986: name=志木市, name:en=Shiki, name:ja=志木市 relation 2679914: name=市原市, name:en=Ichihara, name:ja=市原市 relation 2679915: name=市川市, name:en=Ichikawa, name:ja=市川市 relation 2679921: name=木更津市, name:en=Kisarazu, name:ja=木更津市 relation 2679924: name=松戸市, name:en=Matsudo, name:ja=松戸市 relation 2679925: name=柏市, name:en=Kashiwa, name:ja=柏市 relation 2679928: name=流山市, name:en=Nagareyama, name:ja=流山市 relation 2679929: name=浦安市, name:en=Urayasu, name:ja=浦安市 relation 2679935: name=船橋市, name:en=Funabashi, name:ja=船橋市 relation 2679938: name=袖ケ浦市, name:en=Sodegaura, name:ja=袖ケ浦市 relation 2689424: name=大和市, name:en=Yamato, name:ja=大和市 relation 2689430: name=座間市, name:en=Zama, name:ja=座間市 relation 2689476: name=川崎市, name:en=Kawasaki, name:ja=川崎市 relation 2689481: name=相模原市, name:en=Sagamihara, name:ja=相模原市 relation 2689482: name=横浜市, name:en=Yokohama, name:ja=横浜市 relation 3554015: name=江東区, name:en=Koto, name:ja=江東区 relation 3554304: name=品川区, name:en=Shinagawa, name:ja=品川区 relation 3807613: name=稲城市, name:en=Inagi, name:ja=稲城市 relation 5100276: name=町田市, name:en=Machida, name:ja=町田市 relation 5301639: name=八王子市, name:en=Hachioji, name:ja=八王子市 relation 5301641: name=武蔵村山市, name:en=Musashimurayama, name:ja=武蔵村山市 relation 5301789: name=多摩市, name:en=Tama, name:ja=多摩市 relation 5301790: name=日野市, name:en=Hino, name:ja=日野市 relation 5302157: name=国分寺市, name:en=Kokubunji, name:ja=国分寺市 relation 5302158: name=国立市, name:en=Kunitachi, name:ja=国立市 relation 5302159: name=小平市, name:en=Kodaira, name:ja=小平市 relation 5302160: name=東大和市, name:en=Higashiyamato, name:ja=東大和市 relation 5302161: name=東村山市, name:en=Higashimurayama, name:ja=東村山市 relation 5302326: name=小金井市, name:en=Koganei, name:ja=小金井市 relation 5302327: name=府中市, name:en=Fuchu, name:ja=府中市 relation 5302328: name=東久留米市, name:en=Higashikurume, name:ja=東久留米市 relation 5302329: name=清瀬市, name:en=Kiyose, name:ja=清瀬市 relation 5302330: name=西東京市, name:en=Nishitokyo, name:ja=西東京市 relation 5303898: name=調布市, name:en=Chofu, name:ja=調布市
14:52
Now I can see the actual OSM relation IDs for the 23 special wards. Let me map them: 1. Chiyoda - 1761742 2. Chuo - 1758897 3. Minato - 1761717 4. Shinjuku - 1758858 5. Bunkyo - 1758878 6. Taito - 1758888 7. Sumida - 1758891 8. Koto - 3554015 9. Shinagawa - 3554304 10. Meguro - 1758936 11. Ota - 1758947 12. Setagaya - 1759474 13. Shibuya - 1759477 14. Nakano - 1543056 15. Suginami - 1543055 16. Toshima - 1759506 17. Kita - 1760038 18. Arakawa - 1760040 19. Itabashi - 1760078 20. Nerima - 1760119 21. Adachi - 1760124 22. Katsushika - 1761718 23. Edogawa - 1761743 Great! So the actual OSM relation IDs are different from what I initially had. Let me rewrite the script to use the correct IDs and use osmnx for fetching the ward boundaries directly, since that works. Let me rewrite the whole script using osmnx for both wards and roads.
14:52
14:52
Write
/work/solve.py
content · 394 lines · py
#!/usr/bin/env python3
"""
Tokyo 23 special wards — road boundary crossing analysis.
Uses osmnx to fetch ward boundaries and roads from OSM.
Output: /work/tokyo_crossings.gpkg (5 layers)
"""
import os
import sys
import geopandas as gpd
import pandas as pd
import numpy as np
import osmnx as ox
from shapely.geometry import Point, LineString, MultiLineString, box, MultiPoint
from shapely.ops import polygonize, unary_union
import warnings
warnings.filterwarnings("ignore")
# ── Config ──────────────────────────────────────────────────────────────
OUT_GPKG = "/work/tokyo_crossings.gpkg"
METRIC_CRS = "EPSG:6677" # JGD2011 / Japan Plane Rectangular CS IX (Tokyo)
WGS84_CRS = "EPSG:4326"
ox.settings.timeout = 300
ox.settings.max_query_area_size = 10000000000
ox.settings.log_console = False
# Correct OSM relation IDs for the 23 special wards of Tokyo
WARD_RELATIONS = {
1543055: ("Suginami", "杉並区"),
1543056: ("Nakano", "中野区"),
1758858: ("Shinjuku", "新宿区"),
1758878: ("Bunkyo", "文京区"),
1758888: ("Taito", "台東区"),
1758891: ("Sumida", "墨田区"),
1758897: ("Chuo", "中央区"),
1758936: ("Meguro", "目黒区"),
1758947: ("Ota", "大田区"),
1759474: ("Setagaya", "世田谷区"),
1759477: ("Shibuya", "渋谷区"),
1759506: ("Toshima", "豊島区"),
1760038: ("Kita", "北区"),
1760040: ("Arakawa", "荒川区"),
1760078: ("Itabashi", "板橋区"),
1760119: ("Nerima", "練馬区"),
1760124: ("Adachi", "足立区"),
1761717: ("Minato", "港区"),
1761718: ("Katsushika", "葛飾区"),
1761742: ("Chiyoda", "千代田区"),
1761743: ("Edogawa", "江戸川区"),
3554015: ("Koto", "江東区"),
3554304: ("Shinagawa", "品川区"),
}
def fetch_ward_boundaries():
"""Fetch 23 special ward boundaries using osmnx by querying with their relation IDs.
Returns a GeoDataFrame in WGS84 with columns: ward_id, ward_name_en, ward_name, geometry.
"""
rel_ids = list(WARD_RELATIONS.keys())
print(f" Fetching {len(rel_ids)} ward boundaries from OSM ...")
# Use osmnx to get features around Tokyo with admin_level=7, then filter to our wards
# Bounding box covering the 23 wards (roughly)
bbox_23 = (139.4, 35.5, 139.95, 35.85) # (left, bottom, right, top)
gdf = ox.features_from_bbox(bbox_23, tags={"boundary": "administrative", "admin_level": "7"})
# Filter to relation-type features (these have the full polygons)
mask = gdf.index.get_level_values("element") == "relation"
rel_gdf = gdf[mask].copy()
# Now filter to our 23 wards by relation ID
rel_ids_set = set(rel_ids)
mask2 = rel_gdf.index.get_level_values("id").isin(rel_ids_set)
wards = rel_gdf[mask2].copy()
print(f" Found {len(wards)} matching ward relations")
rows = []
for idx, row in wards.iterrows():
etype, eid = idx
en_name, ja_name = WARD_RELATIONS[eid]
geom = row.geometry
# The geometry might be a MultiPolygon; simplify to single polygon if needed
if geom.geom_type == "MultiPolygon":
# Take the largest part
parts = list(geom.geoms)
parts.sort(key=lambda p: p.area, reverse=True)
geom = parts[0]
elif geom.geom_type != "Polygon":
print(f" WARNING: {en_name} has unexpected geom type {geom.geom_type}")
continue
rows.append({
"ward_id": eid,
"ward_name_en": en_name,
"ward_name": ja_name,
"geometry": geom,
})
print(f" {en_name} ({ja_name}) — OK")
result = gpd.GeoDataFrame(rows, crs=WGS84_CRS, geometry="geometry")
return result
def fetch_roads_in_bbox(bbox):
"""Fetch drivable roads (excluding service roads) within bounding box.
bbox: (left, bottom, right, top) in WGS84
Returns GeoDataFrame in WGS84.
"""
highway_types = [
"motorway", "trunk", "primary", "secondary", "tertiary",
"unclassified", "residential",
"motorway_link", "trunk_link", "primary_link", "secondary_link", "tertiary_link",
]
tags = {"highway": highway_types}
print(f" Fetching roads from OSM within bbox {bbox} ...")
gdf = ox.features_from_bbox(bbox, tags=tags)
print(f" Received {len(gdf)} road features")
# Filter to only LineString geometries (ways, not nodes)
line_mask = gdf.geometry.geom_type.isin(["LineString", "MultiLineString"])
gdf = gdf[line_mask].copy()
# Explode MultiLineStrings
rows = []
for idx, row in gdf.iterrows():
geom = row.geometry
if geom.geom_type == "MultiLineString":
for part in geom.geoms:
rows.append({"geometry": part})
else:
rows.append({"geometry": geom})
result = gpd.GeoDataFrame(rows, crs=WGS84_CRS, geometry="geometry")
print(f" After exploding: {len(result)} road segments")
return result
def compute_crossings(wards_metric, roads_metric):
"""
For each ward, find all points where a road crosses the ward boundary.
wards_metric: GeoDataFrame (Polygon) in metric CRS
roads_metric: GeoDataFrame (LineString) in metric CRS
Returns: GeoDataFrame of crossing points (Point, metric CRS)
"""
crossing_rows = []
for widx, ward in wards_metric.iterrows():
ward_geom = ward.geometry
boundary = ward_geom.boundary
if boundary.is_empty:
continue
# Spatial join to find roads that might intersect the boundary
for ridx, road in roads_metric.iterrows():
road_geom = road.geometry
if not road_geom.intersects(boundary):
continue
intersection = road_geom.intersection(boundary)
if intersection.is_empty:
continue
if intersection.geom_type == "Point":
crossing_rows.append({
"ward_id": ward["ward_id"],
"ward_name_en": ward["ward_name_en"],
"ward_name": ward["ward_name"],
"geometry": intersection,
})
elif intersection.geom_type == "MultiPoint":
for pt in intersection.geoms:
crossing_rows.append({
"ward_id": ward["ward_id"],
"ward_name_en": ward["ward_name_en"],
"ward_name": ward["ward_name"],
"geometry": pt,
})
elif "LineString" in intersection.geom_type:
# Road runs along the boundary — tricky.
# Take the midpoint of the intersecting line segment(s)
if intersection.geom_type == "MultiLineString":
for line in intersection.geoms:
pt = line.interpolate(0.5, normalized=True)
crossing_rows.append({
"ward_id": ward["ward_id"],
"ward_name_en": ward["ward_name_en"],
"ward_name": ward["ward_name"],
"geometry": pt,
})
else: # LineString
pt = intersection.interpolate(0.5, normalized=True)
crossing_rows.append({
"ward_id": ward["ward_id"],
"ward_name_en": ward["ward_name_en"],
"ward_name": ward["ward_name"],
"geometry": pt,
})
elif intersection.geom_type == "GeometryCollection":
for geom in intersection.geoms:
if geom.geom_type == "Point":
crossing_rows.append({
"ward_id": ward["ward_id"],
"ward_name_en": ward["ward_name_en"],
"ward_name": ward["ward_name"],
"geometry": geom,
})
elif "LineString" in geom.geom_type:
pt = geom.interpolate(0.5, normalized=True)
crossing_rows.append({
"ward_id": ward["ward_id"],
"ward_name_en": ward["ward_name_en"],
"ward_name": ward["ward_name"],
"geometry": pt,
})
if crossing_rows:
return gpd.GeoDataFrame(crossing_rows, crs=METRIC_CRS, geometry="geometry")
else:
return gpd.GeoDataFrame(
{"ward_id": pd.Series(dtype=int), "ward_name_en": pd.Series(dtype=str),
"ward_name": pd.Series(dtype=str), "geometry": pd.Series(dtype=object)},
crs=METRIC_CRS, geometry="geometry"
)
def main():
# ── Step 1: Fetch ward boundaries ──
print("=" * 60)
print("Step 1: Fetching ward boundaries from OSM")
print("=" * 60)
wards = fetch_ward_boundaries()
print(f"\nTotal wards loaded: {len(wards)}")
# ── Step 2: Fetch roads ──
print("\n" + "=" * 60)
print("Step 2: Fetching road network from OSM")
print("=" * 60)
# Expand bbox slightly beyond ward bounds
bounds = wards.total_bounds # (minx, miny, maxx, maxy)
pad = 0.03 # ~3 km padding in degrees
bbox = (bounds[0] - pad, bounds[1] - pad, bounds[2] + pad, bounds[3] + pad)
roads = fetch_roads_in_bbox(bbox)
print(f"Total road segments: {len(roads)}")
# ── Step 3: Reproject to metric CRS ──
print("\n" + "=" * 60)
print("Step 3: Reprojecting to metric CRS")
print("=" * 60)
wards_metric = wards.to_crs(METRIC_CRS)
roads_metric = roads.to_crs(METRIC_CRS)
print(f" Wards CRS: {wards_metric.crs}")
print(f" Roads CRS: {roads_metric.crs}")
# Clip roads to a slightly expanded bounding box of all wards
wards_bounds = wards_metric.total_bounds
expand_m = 500
clip_box = box(wards_bounds[0] - expand_m, wards_bounds[1] - expand_m,
wards_bounds[2] + expand_m, wards_bounds[3] + expand_m)
roads_metric = roads_metric[roads_metric.intersects(clip_box)].copy()
print(f" Roads intersecting ward area: {len(roads_metric)}")
# ── Step 4: Compute crossing points ──
print("\n" + "=" * 60)
print("Step 4: Computing crossing points")
print("=" * 60)
crossings = compute_crossings(wards_metric, roads_metric)
print(f" Total crossing points found: {len(crossings)}")
if len(crossings) == 0:
print("ERROR: No crossing points found! Something went wrong.")
sys.exit(1)
# Count per ward
ward_counts = crossings.groupby("ward_id").size()
print("\n Crossings per ward:")
for wid, cnt in ward_counts.items():
en = wards[wards["ward_id"] == wid]["ward_name_en"].values[0]
print(f" {en}: {cnt}")
# ── Step 5: Create 50m buffers ──
print("\n" + "=" * 60)
print("Step 5: Creating 50m buffers around crossing points")
print("=" * 60)
buffers = crossings.copy()
buffers.geometry = buffers.geometry.buffer(50.0)
print(f" Buffers created: {len(buffers)}")
# ── Step 6: Clip buffers to the ward whose boundary was crossed ──
print("\n" + "=" * 60)
print("Step 6: Clipping buffers to ward boundaries")
print("=" * 60)
clipped_rows = []
for bidx, buf in buffers.iterrows():
ward_id = buf["ward_id"]
ward_row = wards_metric[wards_metric["ward_id"] == ward_id].iloc[0]
ward_geom = ward_row.geometry
clipped = buf.geometry.intersection(ward_geom)
if clipped.is_empty or clipped.area == 0:
continue
if clipped.geom_type == "MultiPolygon":
parts = list(clipped.geoms)
parts.sort(key=lambda p: p.area, reverse=True)
clipped = parts[0]
clipped_rows.append({
"ward_id": ward_id,
"ward_name_en": buf["ward_name_en"],
"ward_name": buf["ward_name"],
"geometry": clipped,
})
buffer_intersect = gpd.GeoDataFrame(clipped_rows, crs=METRIC_CRS, geometry="geometry")
print(f" Clipped buffers: {len(buffer_intersect)}")
# ── Step 7: Compute crossing density per ward ──
print("\n" + "=" * 60)
print("Step 7: Computing crossing density per ward")
print("=" * 60)
crossing_counts = crossings.groupby("ward_id").size().reset_index(name="crossing_count")
wards_metric["ward_area_km2"] = wards_metric.geometry.area / 1_000_000
density = wards_metric[["ward_id", "ward_name_en", "ward_name", "ward_area_km2", "geometry"]].copy()
density = density.merge(crossing_counts, on="ward_id", how="left")
density["crossing_count"] = density["crossing_count"].fillna(0).astype(int)
density["crossings_per_km2"] = (density["crossing_count"] / density["ward_area_km2"]).round(4)
# Reproject density to WGS84 for the public dashboard
density_wgs84 = density.to_crs(WGS84_CRS)
density_wgs84 = density_wgs84[["ward_id", "ward_name_en", "ward_name",
"crossing_count", "ward_area_km2",
"crossings_per_km2", "geometry"]]
print(" Ward crossing density summary:")
print(density_wgs84[["ward_name_en", "crossing_count", "ward_area_km2", "crossings_per_km2"]].to_string())
# ── Step 8: Save to GeoPackage ──
print("\n" + "=" * 60)
print("Step 8: Saving to GeoPackage")
print("=" * 60)
if os.path.exists(OUT_GPKG):
os.remove(OUT_GPKG)
# Layer 1: wards_jgd (metric CRS)
layer1 = wards_metric[["ward_id", "ward_name_en", "ward_name", "geometry"]].copy()
layer1.to_file(OUT_GPKG, layer="wards_jgd", driver="GPKG")
print(f" ✓ wards_jgd: {len(layer1)} polygons")
# Layer 2: crossing_points (metric CRS)
layer2 = crossings[["ward_id", "ward_name_en", "ward_name", "geometry"]].copy()
layer2.to_file(OUT_GPKG, layer="crossing_points", driver="GPKG")
print(f" ✓ crossing_points: {len(layer2)} points")
# Layer 3: crossing_buffers_50m (metric CRS)
layer3 = buffers[["ward_id", "ward_name_en", "ward_name", "geometry"]].copy()
layer3.to_file(OUT_GPKG, layer="crossing_buffers_50m", driver="GPKG")
print(f" ✓ crossing_buffers_50m: {len(layer3)} polygons")
# Layer 4: buffer_ward_intersection (metric CRS)
layer4 = buffer_intersect[["ward_id", "ward_name_en", "ward_name", "geometry"]].copy()
layer4.to_file(OUT_GPKG, layer="buffer_ward_intersection", driver="GPKG", mode="a")
print(f" ✓ buffer_ward_intersection: {len(layer4)} polygons")
# Layer 5: ward_crossing_density_wgs84 (WGS84)
density_wgs84.to_file(OUT_GPKG, layer="ward_crossing_density_wgs84", driver="GPKG", mode="a")
print(f" ✓ ward_crossing_density_wgs84: {len(density_wgs84)} polygons")
# ── Verification ──
print("\n" + "=" * 60)
print("Verification")
print("=" * 60)
for layer_name in ["wards_jgd", "crossing_points", "crossing_buffers_50m",
"buffer_ward_intersection", "ward_crossing_density_wgs84"]:
try:
gdf = gpd.read_file(OUT_GPKG, layer=layer_name)
cols = list(gdf.columns)
print(f" {layer_name}: {len(gdf)} features, CRS={gdf.crs}, cols={cols}")
if "ward_name_en" in cols:
print(f" wards: {gdf['ward_name_en'].unique().tolist()}")
except Exception as e:
print(f" {layer_name}: ERROR — {e}")
print(f"\n✓ All layers saved to {OUT_GPKG}")
if __name__ == "__main__":
main()
Wrote /work/solve.py (15491 chars).
15:00
Bash
cd /work && python solve.py 2>&1
============================================================
Step 1: Fetching ward boundaries from OSM
============================================================
Fetching 23 ward boundaries from OSM ...
Found 23 matching ward relations
Suginami (杉並区) — OK
Nakano (中野区) — OK
Shinjuku (新宿区) — OK
Bunkyo (文京区) — OK
Taito (台東区) — OK
Sumida (墨田区) — OK
Chuo (中央区) — OK
Meguro (目黒区) — OK
Ota (大田区) — OK
Setagaya (世田谷区) — OK
Shibuya (渋谷区) — OK
Toshima (豊島区) — OK
Kita (北区) — OK
Arakawa (荒川区) — OK
Itabashi (板橋区) — OK
Nerima (練馬区) — OK
Adachi (足立区) — OK
Minato (港区) — OK
Katsushika (葛飾区) — OK
Chiyoda (千代田区) — OK
Edogawa (江戸川区) — OK
Koto (江東区) — OK
Shinagawa (品川区) — OK
Total wards loaded: 23
============================================================
Step 2: Fetching road network from OSM
============================================================
Fetching roads from OSM within bbox (np.float64(139.5328986), np.float64(35.4516556), np.float64(139.9489004), np.float64(35.8474937)) ...
Received 168308 road features
After exploding: 168305 road segments
Total road segments: 168305
============================================================
Step 3: Reprojecting to metric CRS
============================================================
Wards CRS: EPSG:6677
Roads CRS: EPSG:6677
Roads intersecting ward area: 133966
============================================================
Step 4: Computing crossing points
============================================================
Total crossing points found: 5463
Crossings per ward:
Suginami: 318
Nakano: 304
Shinjuku: 276
Bunkyo: 243
Taito: 153
Sumida: 87
Chuo: 100
Meguro: 262
Ota: 211
Setagaya: 446
Shibuya: 229
Toshima: 318
Kita: 218
Arakawa: 155
Itabashi: 292
Nerima: 453
Adachi: 212
Minato: 248
Katsushika: 224
Chiyoda: 185
Edogawa: 147
Koto: 126
Shinagawa: 256
============================================================
Step 5: Creating 50m buffers around crossing points
============================================================
Buffers created: 5463
============================================================
Step 6: Clipping buffers to ward boundaries
============================================================
Clipped buffers: 5463
============================================================
Step 7: Computing crossing density per ward
============================================================
Ward crossing density summary:
ward_name_en crossing_count ward_area_km2 crossings_per_km2
0 Suginami 318 33.927774 9.3729
1 Nakano 304 15.582962 19.5085
2 Shinjuku 276 18.271967 15.1051
3 Bunkyo 243 11.367203 21.3773
4 Taito 153 10.093873 15.1577
5 Sumida 87 13.770894 6.3177
6 Chuo 100 11.273083 8.8707
7 Meguro 262 14.848302 17.6451
8 Ota 211 101.400994 2.0808
9 Setagaya 446 58.097629 7.6767
10 Shibuya 229 15.116938 15.1486
11 Toshima 318 12.982196 24.4951
12 Kita 218 20.501458 10.6334
13 Arakawa 155 10.206810 15.1859
14 Itabashi 292 32.109091 9.0940
15 Nerima 453 48.122121 9.4136
16 Adachi 212 53.275846 3.9793
17 Minato 248 23.884431 10.3833
18 Katsushika 224 34.813982 6.4342
19 Chiyoda 185 11.361513 16.2830
20 Edogawa 147 81.418518 1.8055
21 Koto 126 65.422936 1.9259
22 Shinagawa 256 26.065159 9.8215
============================================================
Step 8: Saving to GeoPackage
============================================================
✓ wards_jgd: 23 polygons
✓ crossing_points: 5463 points
✓ crossing_buffers_50m: 5463 polygons
✓ buffer_ward_intersection: 5463 polygons
✓ ward_crossing_density_wgs84: 23 polygons
============================================================
Verification
============================================================
wards_jgd: 23 features, CRS=EPSG:6677, cols=['ward_id', 'ward_name_en', 'ward_name', 'geometry']
wards: ['Suginami', 'Nakano', 'Shinjuku', 'Bunkyo', 'Taito', 'Sumida', 'Chuo', 'Meguro', 'Ota', 'Setagaya', 'Shibuya', 'Toshima', 'Kita', 'Arakawa', 'Itabashi', 'Nerima', 'Adachi', 'Minato', 'Katsushika', 'Chiyoda', 'Edogawa', 'Koto', 'Shinagawa']
crossing_points: 5463 features, CRS=EPSG:6677, cols=['ward_id', 'ward_name_en', 'ward_name', 'geometry']
wards: ['Suginami', 'Nakano', 'Shinjuku', 'Bunkyo', 'Taito', 'Sumida', 'Chuo', 'Meguro', 'Ota', 'Setagaya', 'Shibuya', 'Toshima', 'Kita', 'Arakawa', 'Itabashi', 'Nerima', 'Adachi', 'Minato', 'Katsushika', 'Chiyoda', 'Edogawa', 'Koto', 'Shinagawa']
crossing_buffers_50m: 5463 features, CRS=EPSG:6677, cols=['ward_id', 'ward_name_en', 'ward_name', 'geometry']
wards: ['Suginami', 'Nakano', 'Shinjuku', 'Bunkyo', 'Taito', 'Sumida', 'Chuo', 'Meguro', 'Ota', 'Setagaya', 'Shibuya', 'Toshima', 'Kita', 'Arakawa', 'Itabashi', 'Nerima', 'Adachi', 'Minato', 'Katsushika', 'Chiyoda', 'Edogawa', 'Koto', 'Shinagawa']
buffer_ward_intersection: 5463 features, CRS=EPSG:6677, cols=['ward_id', 'ward_name_en', 'ward_name', 'geometry']
wards: ['Suginami', 'Nakano', 'Shinjuku', 'Bunkyo', 'Taito', 'Sumida', 'Chuo', 'Meguro', 'Ota', 'Setagaya', 'Shibuya', 'Toshima', 'Kita', 'Arakawa', 'Itabashi', 'Nerima', 'Adachi', 'Minato', 'Katsushika', 'Chiyoda', 'Edogawa', 'Koto', 'Shinagawa']
ward_crossing_density_wgs84: 23 features, CRS=EPSG:4326, cols=['ward_id', 'ward_name_en', 'ward_name', 'crossing_count', 'ward_area_km2', 'crossings_per_km2', 'geometry']
wards: ['Suginami', 'Nakano', 'Shinjuku', 'Bunkyo', 'Taito', 'Sumida', 'Chuo', 'Meguro', 'Ota', 'Setagaya', 'Shibuya', 'Toshima', 'Kita', 'Arakawa', 'Itabashi', 'Nerima', 'Adachi', 'Minato', 'Katsushika', 'Chiyoda', 'Edogawa', 'Koto', 'Shinagawa']
✓ All layers saved to /work/tokyo_crossings.gpkg
23:07
The script ran successfully. Let me verify the output GeoPackage to make sure everything is correct.
23:07
Bash
cd /work && python3 -c "
cd /work && python3 -c "
import geopandas as gpd
import sqlite3
# Check all layers
gpk = '/work/tokyo_crossings.gpkg'
conn = sqlite3.connect(gpk)
cur = conn.execute('SELECT layer_name FROM gpkg_contents ORDER BY layer_name')
layers = [r[0] for r in cur.fetchall()]
print('Layers in geopackage:', layers)
conn.close()
print()
for layer in layers:
gdf = gpd.read_file(gpk, layer=layer)
print(f'--- {layer} ---')
print(f' Features: {len(gdf)}')
print(f' CRS: {gdf.crs}')
print(f' Columns: {list(gdf.columns)}')
if 'ward_name_en' in gdf.columns:
names = gdf['ward_name_en'].unique()
print(f' Wards ({len(names)}): {sorted(names)}')
if 'crossings_per_km2' in gdf.columns:
print(f' Density range: {gdf[\"crossings_per_km2\"].min():.4f} - {gdf[\"crossings_per_km2\"].max():.4f}')
if 'ward_area_km2' in gdf.columns:
print(f' Area range: {gdf[\"ward_area_km2\"].min():.4f} - {gdf[\"ward_area_km2\"].max():.4f} km²')
print()
" 2>&1
Traceback (most recent call last):
File "<string>", line 8, in <module>
cur = conn.execute('SELECT layer_name FROM gpkg_contents ORDER BY layer_name')
sqlite3.OperationalError: no such column: layer_name
23:23
Let me check the GeoPackage structure properly.
23:23
Bash
cd /work && python3 -c "
cd /work && python3 -c "
import geopandas as gpd
gpk = '/work/tokyo_crossings.gpkg'
# List all layers
layers = gpd.list_layers(gpk)
print('Layers in geopackage:')
print(layers)
print()
for layer in layers['name']:
gdf = gpd.read_file(gpk, layer=layer)
print(f'--- {layer} ---')
print(f' Features: {len(gdf)}')
print(f' CRS: {gdf.crs}')
print(f' Columns: {list(gdf.columns)}')
if 'ward_name_en' in gdf.columns:
names = sorted(gdf['ward_name_en'].unique())
print(f' Wards ({len(names)}): {names}')
if 'crossings_per_km2' in gdf.columns:
print(f' Density range: {gdf[\"crossings_per_km2\"].min():.4f} - {gdf[\"crossings_per_km2\"].max():.4f}')
if 'ward_area_km2' in gdf.columns:
print(f' Area range: {gdf[\"ward_area_km2\"].min():.4f} - {gdf[\"ward_area_km2\"].max():.4f} km²')
print()
" 2>&1
Layers in geopackage:
name geometry_type
0 wards_jgd Polygon
1 crossing_points Point
2 crossing_buffers_50m Polygon
3 buffer_ward_intersection Polygon
4 ward_crossing_density_wgs84 Polygon
--- wards_jgd ---
Features: 23
CRS: EPSG:6677
Columns: ['ward_id', 'ward_name_en', 'ward_name', 'geometry']
Wards (23): ['Adachi', 'Arakawa', 'Bunkyo', 'Chiyoda', 'Chuo', 'Edogawa', 'Itabashi', 'Katsushika', 'Kita', 'Koto', 'Meguro', 'Minato', 'Nakano', 'Nerima', 'Ota', 'Setagaya', 'Shibuya', 'Shinagawa', 'Shinjuku', 'Suginami', 'Sumida', 'Taito', 'Toshima']
--- crossing_points ---
Features: 5463
CRS: EPSG:6677
Columns: ['ward_id', 'ward_name_en', 'ward_name', 'geometry']
Wards (23): ['Adachi', 'Arakawa', 'Bunkyo', 'Chiyoda', 'Chuo', 'Edogawa', 'Itabashi', 'Katsushika', 'Kita', 'Koto', 'Meguro', 'Minato', 'Nakano', 'Nerima', 'Ota', 'Setagaya', 'Shibuya', 'Shinagawa', 'Shinjuku', 'Suginami', 'Sumida', 'Taito', 'Toshima']
--- crossing_buffers_50m ---
Features: 5463
CRS: EPSG:6677
Columns: ['ward_id', 'ward_name_en', 'ward_name', 'geometry']
Wards (23): ['Adachi', 'Arakawa', 'Bunkyo', 'Chiyoda', 'Chuo', 'Edogawa', 'Itabashi', 'Katsushika', 'Kita', 'Koto', 'Meguro', 'Minato', 'Nakano', 'Nerima', 'Ota', 'Setagaya', 'Shibuya', 'Shinagawa', 'Shinjuku', 'Suginami', 'Sumida', 'Taito', 'Toshima']
--- buffer_ward_intersection ---
Features: 5463
CRS: EPSG:6677
Columns: ['ward_id', 'ward_name_en', 'ward_name', 'geometry']
Wards (23): ['Adachi', 'Arakawa', 'Bunkyo', 'Chiyoda', 'Chuo', 'Edogawa', 'Itabashi', 'Katsushika', 'Kita', 'Koto', 'Meguro', 'Minato', 'Nakano', 'Nerima', 'Ota', 'Setagaya', 'Shibuya', 'Shinagawa', 'Shinjuku', 'Suginami', 'Sumida', 'Taito', 'Toshima']
--- ward_crossing_density_wgs84 ---
Features: 23
CRS: EPSG:4326
Columns: ['ward_id', 'ward_name_en', 'ward_name', 'crossing_count', 'ward_area_km2', 'crossings_per_km2', 'geometry']
Wards (23): ['Adachi', 'Arakawa', 'Bunkyo', 'Chiyoda', 'Chuo', 'Edogawa', 'Itabashi', 'Katsushika', 'Kita', 'Koto', 'Meguro', 'Minato', 'Nakano', 'Nerima', 'Ota', 'Setagaya', 'Shibuya', 'Shinagawa', 'Shinjuku', 'Suginami', 'Sumida', 'Taito', 'Toshima']
Density range: 1.8055 - 24.4951
Area range: 10.0939 - 101.4010 km²
23:34
Everything looks good. Let me verify the output matches all requirements: 1. ✅ `wards_jgd` (Polygon): the 23 ward boundaries — in EPSG:6677 (metric CRS) 2. ✅ `crossing_points` (Point): one point at each place a road crosses a ward boundary — 5,463 points 3. ✅ `crossing_buffers_50m` (Polygon): a 50 m buffer around each crossing point — 5,463 buffers 4. ✅ `buffer_ward_intersection` (Polygon): each buffer clipped to the ward whose boundary it crossed — 5,463 polygons 5. ✅ `ward_crossing_density_wgs84` (Polygon): one row per ward with `ward_id`, `crossing_count`, `ward_area_km2`, and `crossings_per_km2` — in WGS84 Every layer has: - `ward_id` = OSM relation id - `ward_name_en` = English ward name - `ward_name` = Japanese ward name - Service roads were skipped (we excluded highway=service from the road query) Let me provide a summary to the user.
23:34