dd-l3-lagos-overture-buildings
Map (reference ← swipe → agent)
0:00
Updating the flood-risk model before rainy season. Need every building footprint exceeding 1000 m² across Lagos State (Nigeria) from Overture.
Also need a per-LGA roll-up: for each Lagos Local Government Area, the building count, total footprint area, count of buildings with a non-null Overture height, and the median height where available (null otherwise).
Write two output files. `lagos_buildings.geoparquet` should be EPSG:4326 GeoParquet with columns `id`, `height`, `footprint_area_m2`, `lga`, and `geometry`. `lagos_building_summary.parquet` should be a plain Parquet with no geometry column and columns `lga`, `n_buildings`, `total_footprint_m2`, `n_with_height`, and `p50_height_m`.
0:00
System info
{
"adapter": "openrouter",
"model": "deepseek/deepseek-v4-flash",
"base_url": "https://openrouter.ai/api/v1"
}
0:04
Let me break down the task: 1. Get building footprints from Overture for Lagos State, Nigeria, that exceed 1000 m² 2. Need to filter by Lagos State - I need to find the administrative boundaries for Lagos State 3. For each LGA (Local Government Area), compute summary stats 4. Output two files: - `lagos_buildings.geoparquet` (EPSG:4326) with columns: id, height, footprint_area_m2, lga, geometry - `lagos_building_summary.parquet` with columns: lga, n_buildings, total_footprint_m2, n_with_height, p50_height_m Let me start by exploring what Overture data looks like and how to access it. Overture Maps data is typically available as GeoParquet files from a cloud storage like S3. Let me check what's available. First, let me look for any existing data in the working directory, and then figure out the Overture data access pattern.
0:04
Bash
ls /work/
(no output)
0:09
Let me start by understanding the task: 1. Get building footprints > 1000 m² across Lagos State, Nigeria from Overture data. 2. Per-LGA summary statistics. 3. Output two files: `lagos_buildings.geoparquet` (EPSG:4326, columns: id, height, footprint_area_m2, lga, geometry) and `lagos_building_summary.parquet` (no geometry, columns: lga, n_buildings, total_footprint_m2, n_with_height, p50_height_m). First, I need to get Lagos State boundary and Overture buildings data. Let me check what's available. Overture data is available via DuckDB (using the overture extension) or via downloading Parquet files. Let me try using the Overture maps data. I'll use DuckDB with the overture extension or spatial extension to query the Overture building data for Lagos State. Let me first check what tools/libraries are available.
0:09
Bash
pip list 2>/dev/null | grep -iE "duck|geo|overture|pyogrio|fiona|shapely|pandas"
duckdb 1.5.3 fiona 1.10.1 geopandas 1.1.3 overturemaps 1.0.0 pandas 3.0.3 pyogrio 0.12.1 shapely 2.1.2
0:10
Bash
python -c "import duckdb; print(duckdb.__version__)" 2>&1
1.5.3
0:18
Great, we have overturemaps library. Let me write a script to: 1. Download Overture buildings for Lagos State, Nigeria 2. Filter those with area > 1000 m² 3. Add a column for footprint_area_m2 4. Get LGA (Local Government Area) boundaries and spatially join 5. Write the GeoParquet file 6. Compute the per-LGA summary and write as plain Parquet Let me think about the approach: 1. Use overturemaps to get building footprints for the region of Lagos State 2. Use a dataset for LGA boundaries - I can download from naturalearth or use Overture admin boundaries, or use a known dataset Let me start by exploring what's available through overturemaps. Actually, let me first check if there's a Lagos State boundary or LGA boundary shapefile available. I'll download from naturalearth or use Overture's administrative boundaries. Let me use overturemaps which has a convenient Python library.
0:18
Bash
python -c "
python -c " import overturemaps print(dir(overturemaps)) " 2>&1
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'core', 'geodataframe', 'get_all_overture_types', 'models', 'record_batch_reader']
0:19
Bash
python -c "
python -c " import overturemaps help(overturemaps.core) " 2>&1 | head -80
Help on module overturemaps.core in overturemaps:
NAME
overturemaps.core
FUNCTIONS
count_rows(
overture_type,
bbox=None,
release=None,
connect_timeout=None,
request_timeout=None,
stac=False
) -> int
Return the number of rows matching the given parameters.
geoarrow_schema_adapter(schema: pa.Schema) -> pa.Schema
Convert a geoarrow-compatible schema to a proper geoarrow schema
This assumes there is a single "geometry" column with WKB formatting
Parameters
----------
schema: pa.Schema
Returns
-------
pa.Schema
A copy of the input schema with the geometry field replaced with
a new one with the proper geoarrow ARROW:extension metadata
geodataframe(
overture_type: str,
bbox: BBox | tuple[float, float, float, float] | list[float] | None = None,
release: str = None,
connect_timeout: int = None,
request_timeout: int = None,
stac: bool = False
) -> GeoDataFrame
Loads geoparquet for specified type into a geopandas dataframe
Parameters
----------
overture_type: type to load
bbox: optional bounding box for data fetch (xmin, ymin, xmax, ymax)
connect_timeout: optional connection timeout in seconds
request_timeout: optional request timeout in seconds
Returns
-------
GeoDataFrame with the optionally filtered theme data
get_all_overture_types() -> List[str]
get_available_releases() -> Tuple[List[str], str]
Fetch available releases from the STAC catalog.
Returns
-------
Tuple of (all_releases, latest_release) where:
- all_releases is a list of release version strings
- latest_release is the latest release version string
get_latest_release() -> str
Get the latest release version.
Returns
-------
str: The latest release version
query_gers_registry(gers_id: str) -> Optional[Tuple[str, BBox | None]]
Query the GERS registry to get the filepath and bbox for a given GERS ID.
The registry always uses the latest release.
Parameters
----------
gers_id: The GERS ID to look up
Returns
0:29
Let me understand the task:
1. I need to get building footprints from Overture Maps for Lagos State, Nigeria
2. Filter to buildings with area > 1000 m²
3. For each building, compute footprint area
4. Add LGA (Local Government Area) information
5. Output two files:
- `lagos_buildings.geoparquet` - EPSG:4326 with columns: id, height, footprint_area_m2, lga, geometry
- `lagos_building_summary.parquet` - plain Parquet with columns: lga, n_buildings, total_footprint_m2, n_with_height, p50_height_m
Let me first get the Overture buildings data. I'll need a bounding box for Lagos State, Nigeria. Let me think about the approach.
I'll use `overturemaps.geodataframe("building")` with a bbox covering Lagos State. Let me figure out the bbox.
Lagos State is roughly bounded by:
- North: ~6.7°N
- South: ~6.3°N
- East: ~3.9°E
- West: ~2.7°E
Actually let me be more precise. Let me look up the Lagos State boundary first.
Actually, I think the best approach is:
1. Get Overture building data with a bbox covering Lagos
2. Load Lagos State boundary (from Natural Earth or Overture admin boundaries)
3. Intersect to get only buildings within Lagos
4. Also load LGA boundaries to assign LGA names
5. Compute area, filter > 1000 m², compute summary stats
Let me start with getting the data. I'll use a generous bbox for Lagos.
Actually, let me think about the Overture data structure. The `geodataframe` function returns a GeoDataFrame with the building theme data.
Let me check what columns are available.
0:29
Bash
python -c "
python -c "
import overturemaps
gdf = overturemaps.geodataframe('building', bbox=(2.5, 6.2, 4.0, 6.8), limit=5)
print(gdf.columns.tolist())
print(gdf.head())
print(gdf.dtypes)
" 2>&1
Traceback (most recent call last):
File "<string>", line 3, in <module>
gdf = overturemaps.geodataframe('building', bbox=(2.5, 6.2, 4.0, 6.8), limit=5)
TypeError: geodataframe() got an unexpected keyword argument 'limit'
0:33
Let me look at the overturemaps more carefully, and also think about the Lagos bounding box. Lagos State, Nigeria is roughly bounded by longitude 2.7 to 4.0 and latitude 6.2 to 6.8. Let me also check what columns are available in the buildings theme.
0:33
Bash
python -c "
python -c "
import overturemaps
gdf = overturemaps.geodataframe('building', bbox=(2.5, 6.2, 4.0, 6.8))
print(gdf.columns.tolist())
print(gdf.head())
print(gdf.dtypes)
print('Shape:', gdf.shape)
" 2>&1
['id', 'names', 'sources', 'level', 'height', 'min_height', 'is_underground', 'num_floors', 'num_floors_underground', 'min_floor', 'subtype', 'class', 'facade_color', 'facade_material', 'roof_material', 'roof_shape', 'roof_direction', 'roof_orientation', 'roof_color', 'roof_height', 'geometry', 'has_parts', 'version', 'bbox']
id ... bbox
0 0b96689d-35aa-462d-a332-0ebf7593acc4 ... {'xmin': 2.521664619445801, 'xmax': 2.52181482...
1 f4baf0d2-5037-4344-9308-3171b0e0c0f9 ... {'xmin': 2.5217909812927246, 'xmax': 2.5218367...
2 dea904c3-6bbe-46de-9c16-391dc5179f26 ... {'xmin': 2.52136492729187, 'xmax': 2.521403789...
3 8dd63d0b-fafa-49d0-8725-969edc313c6c ... {'xmin': 2.5229694843292236, 'xmax': 2.5230135...
4 ef213989-e409-47da-9c22-a21f9dcd45bc ... {'xmin': 2.5225915908813477, 'xmax': 2.5226631...
[5 rows x 24 columns]
id str
names object
sources object
level float64
height float64
min_height float64
is_underground bool
num_floors float64
num_floors_underground float64
min_floor float64
subtype str
class str
facade_color str
facade_material str
roof_material str
roof_shape str
roof_direction float64
roof_orientation str
roof_color str
roof_height float64
geometry geometry
has_parts bool
version int32
bbox object
dtype: object
Shape: (4352445, 24)
3:20
That's a lot of data. Let me understand what we need: 1. Get all building footprints in Lagos State, Nigeria that exceed 1000 m² 2. For each LGA, compute: count, total footprint area, count with non-null height, median height 3. Output: `lagos_buildings.geoparquet` (EPSG:4326, columns: id, height, footprint_area_m2, lga, geometry) 4. Output: `lagos_building_summary.parquet` (no geometry, columns: lga, n_buildings, total_footprint_m2, n_with_height, p50_height_m) First I need the boundary of Lagos State and its LGAs. Let me check what data is available. Actually, I can use Overture administrative boundaries for Lagos State and its LGAs. Or I can use a simpler approach - use the bounding box for Lagos and then filter. Let me first figure out the Lagos State boundary. I know Lagos State is in southwestern Nigeria, roughly between 2.5°E-4.0°E and 6.2°N-6.8°N. But the user asked for buildings in Lagos State, and LGA data. I need to get LGA boundaries to assign buildings to LGAs. Let me get the administrative boundaries from Overture or another source. Overture has `division` area type for administrative boundaries. Let me check what types are available.
3:20
Bash
python -c "
python -c " import overturemaps types = overturemaps.get_all_overture_types() print(sorted(types)) " 2>&1
['address', 'bathymetry', 'building', 'building_part', 'connector', 'division', 'division_area', 'division_boundary', 'infrastructure', 'land', 'land_cover', 'land_use', 'place', 'segment', 'water']
3:39
I have the overturemaps library. I can get building data for Lagos State. Lagos State bbox is approximately (2.5, 6.2, 4.0, 6.8) but I should be more precise. However, the data returned is over 4 million rows for that bbox. I need to filter to only Lagos State, get the LGA boundaries, and filter buildings > 1000 m². Let me think about the approach: 1. Get the building data for Lagos State using overturemaps 2. Get the division (administrative boundaries) for Lagos State LGAs 3. Spatially join buildings to LGAs 4. Calculate footprint area 5. Filter buildings > 1000 m² 6. Calculate per-LGA summary Let me first check what division data is available for Nigeria.
3:39
Bash
python -c "
python -c "
import overturemaps
# Check divisions for Nigeria
gdf = overturemaps.geodataframe('division_area', bbox=(2.5, 6.2, 4.0, 6.8))
print(gdf.columns.tolist())
print(gdf[['id', 'subtype', 'country', 'region', 'locality', 'geometry']].head(20))
print('Shape:', gdf.shape)
" 2>&1
['id', 'geometry', 'country', 'sources', 'subtype', 'admin_level', 'class', 'names', 'is_land', 'is_territorial', 'region', 'division_id', 'version', 'bbox']
Traceback (most recent call last):
File "<string>", line 6, in <module>
print(gdf[['id', 'subtype', 'country', 'region', 'locality', 'geometry']].head(20))
~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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: "['locality'] not in index"
3:58
Let me explore the division_area data more carefully.
3:58
Bash
python -c "
python -c "
import overturemaps
gdf = overturemaps.geodataframe('division_area', bbox=(2.5, 6.2, 4.0, 6.8))
print(gdf.columns.tolist())
print(gdf.head(10)[['id', 'subtype', 'country', 'region', 'division_id']])
print('Shape:', gdf.shape)
" 2>&1
['id', 'geometry', 'country', 'sources', 'subtype', 'admin_level', 'class', 'names', 'is_land', 'is_territorial', 'region', 'division_id', 'version', 'bbox']
id ... division_id
0 4bbf89ce-38b8-489a-9753-5db1360339d6 ... cd3c4806-7c2f-47da-9dc1-07eefac42e31
1 f5536621-a798-4c77-b1c7-39a54c8c1cdb ... cd3c4806-7c2f-47da-9dc1-07eefac42e31
2 045f935e-5ded-4798-9cc7-d7d35f09c18e ... 02a5536f-3bd6-41ca-b2a4-4c2d31666dea
3 7108ac41-68bb-4602-b583-bead9a36068f ... 78710781-930c-409a-9986-c11606ef20e0
4 e354a76e-2221-4c97-b742-fd097f7cc3ee ... 9e98fae6-3fef-49f6-add4-0c0e92cc456c
5 2a31b998-68eb-403b-883c-d0499e357cfc ... 0aeb3167-073f-42ee-a0c1-53887ca39de8
6 9b2620d6-d4fd-43c8-a88a-b1a1412f8022 ... 2ac02887-fd6f-4d60-a947-99f5d35b95c4
7 a4173f4b-fdae-4d74-8fe6-33c6420d6d46 ... f36733bd-9e3a-411d-8bf9-00091d5d6724
8 0030ea08-dd45-4051-948e-869effbdc267 ... fefe156c-41ea-4369-94a7-57357f365423
9 65cbe225-a2ff-4166-845c-da8f6b8bc620 ... dde7b6fb-f45e-4418-a9c1-f155e34e03be
[10 rows x 5 columns]
Shape: (58, 14)
4:10
Let me check what divisions are available for Nigeria (Lagos State). I need the Local Government Areas (LGAs) in Lagos State. Let me first look at the countries/regions in the division data.
4:10
Bash
python -c "
python -c "
import overturemaps
gdf = overturemaps.geodataframe('division_area', bbox=(2.5, 6.2, 4.0, 6.8))
print(gdf[['id', 'subtype', 'country', 'region', 'division_id', 'class']].to_string())
" 2>&1
id subtype country region division_id class 0 4bbf89ce-38b8-489a-9753-5db1360339d6 dependency UM NaN cd3c4806-7c2f-47da-9dc1-07eefac42e31 maritime 1 f5536621-a798-4c77-b1c7-39a54c8c1cdb dependency UM NaN cd3c4806-7c2f-47da-9dc1-07eefac42e31 land 2 045f935e-5ded-4798-9cc7-d7d35f09c18e county BJ BJ-OU 02a5536f-3bd6-41ca-b2a4-4c2d31666dea land 3 7108ac41-68bb-4602-b583-bead9a36068f region BJ BJ-OU 78710781-930c-409a-9986-c11606ef20e0 land 4 e354a76e-2221-4c97-b742-fd097f7cc3ee locality BJ BJ-OU 9e98fae6-3fef-49f6-add4-0c0e92cc456c land 5 2a31b998-68eb-403b-883c-d0499e357cfc county BJ BJ-OU 0aeb3167-073f-42ee-a0c1-53887ca39de8 land 6 9b2620d6-d4fd-43c8-a88a-b1a1412f8022 county BJ BJ-OU 2ac02887-fd6f-4d60-a947-99f5d35b95c4 land 7 a4173f4b-fdae-4d74-8fe6-33c6420d6d46 county BJ BJ-OU f36733bd-9e3a-411d-8bf9-00091d5d6724 land 8 0030ea08-dd45-4051-948e-869effbdc267 county BJ BJ-OU fefe156c-41ea-4369-94a7-57357f365423 land 9 65cbe225-a2ff-4166-845c-da8f6b8bc620 region BJ BJ-PL dde7b6fb-f45e-4418-a9c1-f155e34e03be land 10 4a7fa346-a7fd-4ed7-80df-db8e240e49c4 country BJ NaN 4b6398a5-21d4-490a-9985-ac2bbdb96286 land 11 6f1757fc-5c9c-4904-97f8-ad19e854200a country BJ NaN 4b6398a5-21d4-490a-9985-ac2bbdb96286 maritime 12 8273267f-45a9-4750-827d-8b3cf9300ab0 county BJ BJ-PL 5cfe6158-91fc-4c3e-b290-cbd8db20f980 land 13 7ddaddc4-3e25-4727-8eb7-2f34a8e762fc county BJ BJ-PL 4da8d8b1-71cb-436e-969c-832ec71d53e5 land 14 baf905e5-09bc-4441-aea0-39c5566511dd county BJ BJ-OU f9636a90-699b-4fb7-91e9-7c12335a2a89 land 15 c3aab9b7-b579-40f4-8e5d-91800265a94f county BJ BJ-OU 16ebd81c-74f4-4a86-8a36-0ca2b90c02fc land 16 3fbb5ca3-a2b6-4918-b752-a08dbd99fa88 county BJ BJ-OU 741e1dc1-eaf1-4d64-935c-52eafd212b18 land 17 4f725e6f-9c7a-4983-8b78-644eb0cdbefb county BJ BJ-OU 111df6ec-3852-4479-aa07-90ed58908ed9 land 18 00496a96-e1ca-42cb-a1c8-531f48aa7461 county NG NG-OG 199f6f87-94c7-40e8-ba34-662f345e5794 land 19 93ccd19d-3389-4c00-a8e3-920e20810d39 county NG NG-LA 202749c9-2473-4ecb-8528-72dc7a1278ee land 20 dc9f547a-48ef-4f9c-a90b-31f6b247a3b8 county NG NG-OG d6357489-86e9-4e24-ba64-2d3bda2d31a3 land 21 51955eb1-4697-4268-b8e2-8554f7a77475 county NG NG-OG 14cdd032-7c30-44aa-beee-476b4d62184e land 22 32cfa1af-5e74-41a7-9272-b9dd8da715d9 county NG NG-LA 7583d723-71b9-4f18-9fff-bff98df8322d land 23 934f5b76-4cbf-4902-b124-b33d057d87b1 county NG NG-LA a9df031a-eaa0-4b74-b42c-5bf303feb0e1 land 24 7aa34b16-ba84-45d9-94c4-083c65b8cf09 county NG NG-OG bf72a742-460e-40d8-b67e-5cdcc75eeec9 land 25 1a9532db-bcd5-4050-b62b-f27a22f2ebcf county NG NG-LA b4bd3fef-918f-4a60-bfd3-3569ed991562 land 26 7918fd74-2ce4-48af-b3c1-1a4f1e26558e county NG NG-LA 2c66b8ba-4745-4334-b145-ae00b1a6f604 land 27 d8e3535a-c797-4f52-a361-6f2c5116018b region NG NG-OG cd9acba9-bca3-4c03-a710-9f7595c9ba79 land 28 1f5072a4-71e6-409b-89c9-cd999be6a442 county NG NG-OG fd922dfa-0bc1-4bce-a445-d8627b500eca land 29 c0111aed-bd0c-4c3f-a90b-51d3ac847093 county NG NG-OG b10e97c1-fb43-472b-8605-90fb881bfd21 land 30 365d8234-4821-4229-9a4e-6a0a6325bd0b neighborhood NG NG-LA f7c4eec5-77fe-47fc-ab09-b74694f25c40 land 31 439f3b37-5d1a-42f2-9c4f-45d2aab88423 macrohood NG NG-LA ff8acdd6-cc2e-4c3f-98f6-1e247d64dc21 land 32 cb527384-149e-41c1-b3c5-0070deab27d4 county NG NG-LA 9713c2c3-7416-41b8-9912-6e1450d3347d land 33 b098198f-55d1-46a6-ac1e-71ae35fdd913 county NG NG-LA 1b64c604-707e-4a65-a114-75f0f1abb3c5 land 34 bab8ae3c-3858-4fa7-921f-9b8d71c07827 neighborhood NG NG-LA d6377e62-26b2-4562-9f11-d4d9b58648d7 land 35 4b748354-7b73-4c51-b5c3-8dc811d48714 locality NG NG-LA 45cf12c1-dd16-4a7d-97f8-8245c7d5f88c land 36 639f85d6-f581-4f31-b52b-98da63a57271 county NG NG-LA 4f1aabae-5a08-4179-bdfb-f0e9d71d2ca5 land 37 4b715cfc-a9b3-4a05-b601-353b77619939 county NG NG-LA dc5547ff-cb10-4818-8e9b-363c14daa13b land 38 e4636d6f-0327-49fd-b5fc-24076c5fd19d neighborhood NG NG-LA cec11dd6-98c2-44a3-bb87-191803a0e019 land 39 ffe6c369-91e2-4984-97d1-bd24feff3732 county NG NG-LA fe1ca044-b547-49be-9883-2c8ffa3f04d5 land 40 c540f662-44c9-498d-8f3d-2a41a039a29f county NG NG-LA af88a8c6-f84f-4629-a25b-1e79a9ccbc3d land 41 fcaab4e8-075a-4ab6-a2d2-cd614e95dfc2 county NG NG-LA 24e04d44-9e6e-47ba-a71c-737793381785 land 42 457a7986-f212-4765-b432-e1c1b77d6d61 county NG NG-LA e199f023-49ae-46ff-92a4-580c4fe1346d land 43 e2af9375-5eed-46f8-a258-515f22297084 county NG NG-LA b9ecb255-fc97-4e0a-8bc7-9dd0588f7a21 land 44 f0f8532f-4c5e-428a-981e-12a31b2a9044 county NG NG-LA 22ef6348-2be5-4543-8f1d-6e9faf24e126 land 45 5fb3f6cb-3993-4bb7-8a53-73e775feb14f region NG NG-LA 246ecf78-8622-4d7d-8d12-102e73a67cda land 46 c58fd335-e502-421c-b8e3-f263c5fb0ff7 county NG NG-LA 65a83885-eae9-4a94-8663-f6917cc9a04e land 47 0c5600b6-df25-4e75-a25e-ae9937299bad county NG NG-LA 4d40b68b-a144-47cc-9345-4604adc7fb60 land 48 030cb2c1-697b-418e-b820-49393989a03b county NG NG-LA 634b4f3c-a5c4-42d2-b18b-1432b67373b8 land 49 61530733-5992-41f4-9212-43f1b460319c county NG NG-LA 053c8979-6fdf-4078-a9d1-3b545a06d72d land 50 11d7010b-4feb-41a8-8fd6-2062a2bc075c locality NG NG-LA 6c2affed-7bf7-4a95-8df3-c55e20adb169 land 51 70bd9c0f-fcdd-4bd2-81df-34d9351c997e county NG NG-LA 343b4b84-9150-4f3b-937f-c58ca20db27f land 52 a277c8df-124c-47b7-9ab5-092e7f3bcdab county NG NG-OG 0bcee6b4-e6c2-43ff-9be3-ea45ae04d436 land 53 d8c2f7a3-d77d-4781-ba92-5359250c2516 county NG NG-OG 681a3626-3cab-4d51-94c3-78d64fa3694a land 54 fe2221c4-4b6b-4bad-b9c4-12a6f7c5c849 county NG NG-OG fb950c98-d3df-4213-b856-9db1e26a1d3e land 55 6b8e9727-a665-4667-a087-31276385e8b6 county NG NG-OG 5ce8fe65-2f8a-46d9-ad0b-178605615896 land 56 33ccad44-9d4e-4295-ba65-1e0654dee5ee country NG NaN 3264d7ab-fdb5-4956-ad97-6307200c9e0a maritime 57 e7317e99-1f37-4662-9936-d3490da0905b country NG NaN 3264d7ab-fdb5-4956-ad97-6307200c9e0a land
4:23
I can see that NG-LA is the region code for Lagos State (NG = Nigeria, LA = Lagos). Let me look at the `region` column for NG-LA entries to understand the county/subtype relationships. The column `region` has "NG-LA" for Lagos state entries. And `subtype` includes `county`, `locality`, `neighborhood`, `macrohood`, `region`. In Overture, the `region` column seems to be the ISO region code. For Lagos state, it's "NG-LA". The subtype "county" with region "NG-LA" would be the Local Government Areas (LGAs) of Lagos. But I need to find the names of these LGAs. Let me check the `names` column.
4:23
Bash
python -c "
python -c "
import overturemaps
gdf = overturemaps.geodataframe('division_area', bbox=(2.5, 6.2, 4.0, 6.8))
# Filter for NG-LA (Lagos)
lagos = gdf[gdf['region'] == 'NG-LA']
print(lagos[['id', 'subtype', 'names']].to_string())
print()
# Look at the county entries specifically
counties = lagos[lagos['subtype'] == 'county']
print('Counties (LGAs):')
print(counties[['id', 'names']].to_string())
" 2>&1
id subtype names
19 93ccd19d-3389-4c00-a8e3-920e20810d39 county {'primary': 'Badagry', 'common': None, 'rules': None}
22 32cfa1af-5e74-41a7-9272-b9dd8da715d9 county {'primary': 'Amuwo Odofin', 'common': None, 'rules': None}
23 934f5b76-4cbf-4902-b124-b33d057d87b1 county {'primary': 'Ifako/Ijaye', 'common': None, 'rules': None}
25 1a9532db-bcd5-4050-b62b-f27a22f2ebcf county {'primary': 'Alimosho', 'common': None, 'rules': None}
26 7918fd74-2ce4-48af-b3c1-1a4f1e26558e county
[... truncated ...] {'primary': 'Eti Osa', 'common': [('yo', 'Etí Ọ̀sà')], 'rules': None}
48 030cb2c1-697b-418e-b820-49393989a03b county {'primary': 'Ìkòròdú', 'common': [('en', 'Ikorodu'), ('ar', 'إكورودو'), ('yo', 'Ìkòròdú'), ('ru', 'Икороду')], 'rules': None}
49 61530733-5992-41f4-9212-43f1b460319c county {'primary': 'Epe', 'common': [('ru', 'Эпе')], 'rules': None}
50 11d7010b-4feb-41a8-8fd6-2062a2bc075c locality {'primary': 'Alaro City', 'common': None, 'rules': None}
51 70bd9c0f-fcdd-4bd2-81df-34d9351c997e county {'primary': 'Ibeju Lekki', 'common': None, 'rules': None}
Counties (LGAs):
id names
19 93ccd19d-3389-4c00-a8e3-920e20810d39 {'primary': 'Badagry', 'common': None, 'rules': None}
22 32cfa1af-5e74-41a7-9272-b9dd8da715d9 {'primary': 'Amuwo Odofin', 'common': None, 'rules': None}
23 934f5b76-4cbf-4902-b124-b33d057d87b1 {'primary': 'Ifako/Ijaye', 'common': None, 'rules': None}
25 1a9532db-bcd5-4050-b62b-f27a22f2ebcf {'primary': 'Alimosho', 'common': None, 'rules': None}
26 7918fd74-2ce4-48af-b3c1-1a4f1e26558e {'primary': 'Ojo', 'common': None, 'rules': None}
32 cb527384-149e-41c1-b3c5-0070deab27d4 {'primary': 'Kosofe', 'common': None, 'rules': None}
33 b098198f-55d1-46a6-ac1e-71ae35fdd913 {'primary': 'Shomolu', 'common': None, 'rules': None}
36 639f85d6-f581-4f31-b52b-98da63a57271 {'primary': 'Lagos Mainland', 'common': None, 'rules': None}
37 4b715cfc-a9b3-4a05-b601-353b77619939 {'primary': 'Apapa', 'common': None, 'rules': None}
39 ffe6c369-91e2-4984-97d1-bd24feff3732 {'primary': 'Mushin', 'common': [('ru', 'Мушин')], 'rules': None}
40 c540f662-44c9-498d-8f3d-2a41a039a29f {'primary': 'Ikeja', 'common': [('ru', 'Икеджа')], 'rules': None}
41 fcaab4e8-075a-4ab6-a2d2-cd614e95dfc2 {'primary': 'Agege', 'common': None, 'rules': None}
42 457a7986-f212-4765-b432-e1c1b77d6d61 {'primary': 'Oshodi/Isolo', 'common': None, 'rules': None}
43 e2af9375-5eed-46f8-a258-515f22297084 {'primary': 'Ajeromi/Ifelodun', 'common': None, 'rules': None}
44 f0f8532f-4c5e-428a-981e-12a31b2a9044 {'primary': 'Surulere', 'common': None, 'rules': None}
46 c58fd335-e502-421c-b8e3-f263c5fb0ff7 {'primary': 'Lagos Island', 'common': [('ar', 'جزيرة لاغوس')], 'rules': None}
47 0c5600b6-df25-4e75-a25e-ae9937299bad {'primary': 'Eti Osa', 'common': [('yo', 'Etí Ọ̀sà')], 'rules': None}
48 030cb2c1-697b-418e-b820-49393989a03b {'primary': 'Ìkòròdú', 'common': [('en', 'Ikorodu'), ('ar', 'إكورودو'), ('yo', 'Ìkòròdú'), ('ru', 'Икороду')], 'rules': None}
49 61530733-5992-41f4-9212-43f1b460319c {'primary': 'Epe', 'common': [('ru', 'Эпе')], 'rules': None}
51 70bd9c0f-fcdd-4bd2-81df-34d9351c997e {'primary': 'Ibeju Lekki', 'common': None, 'rules': None}
4:37
Good, I can see that for Lagos (NG-LA), the counties (subtype='county') represent the Local Government Areas. Let me now build a proper solution. The approach: 1. Get all buildings from Overture for the Lagos bounding box (roughly 2.5 to 4.0 E, 6.2 to 6.8 N) 2. Get the division boundaries (LGAs) for Lagos state 3. Spatially join buildings to LGAs 4. Calculate footprint area (using shapely area in EPSG:4326 - but better to compute area in an equal-area projection for accuracy) 5. Filter buildings with footprint > 1000 m² 6. Compute per-LGA summaries Let me think about the best approach. The data is large - the full building dataset for that bbox had 4,352,445 rows. We need to: - Download buildings (the whole Lagos region) - Download division_area for the same region - Filter division_area to NG-LA counties (LGAs) - Spatial join Let me write a comprehensive script. Actually, for area calculation: the buildings come in EPSG:4326 (WGS84). To compute area in square meters, I need to project to a suitable projected CRS. For Nigeria/Lagos, UTM zone 31N (EPSG:32631) or 32N (EPSG:32632) would work. Lagos is around 3°E to 4°E, so UTM 31N covers 0° to 6°E - EPSG:32631 is appropriate. Let me write the script.
4:37
Bash
python -c "
python -c "
import overturemaps
# Count rows in Lagos bbox
n = overturemaps.core.count_rows('building', bbox=(2.5, 6.2, 4.0, 6.8))
print(f'Building count: {n}')
" 2>&1
Building count: 4352445
9:05
So there are 4.3M buildings in the Lagos bounding box. However, the Lagos state boundary is not just a simple bbox - we need to filter by the actual boundary. Let me think about how to approach this. Approach: 1. Get the Lagos State boundary (NG-LA region) from Overture division_area 2. Get all buildings in the Lagos bbox 3. Spatial join to keep only buildings within Lagos State 4. Calculate footprint area using Shapely (area in square meters - but the CRS is 4326, so we need to project) 5. Filter to buildings > 1000 m² 6. Assign LGA names using spatial join with county-level divisions Actually, thinking more carefully: Lagos State in Overture is represented by the region `NG-LA`. The divisions within it with `subtype='county'` are the LGAs. Let me use those. But wait - the building data is huge (4.3M rows). Loading it all at once will be memory intensive. Let me use DuckDB or read with pyarrow to process. Actually, `overturemaps.geodataframe()` returns a GeoDataFrame directly. For 4.3M buildings, this could be memory-heavy. Let me check if we can use the record_batch_reader for streaming. Actually, let me try to use overturemaps with DuckDB directly. The overturemaps library uses the Overture Maps STAC/Geoparquet data. Let me see if I can access it more efficiently. Let me think about a better approach: 1. First, get the Lagos State boundary and LGA boundaries from division_area 2. Get buildings from overturemaps, filter by bbox 3. Use spatial operations to filter buildings within Lagos State 4. Then for each building, compute area, filter > 1000 m² 5. Assign LGA via spatial join But 4.3M buildings... let me see how much memory we have.
9:05
Bash
python -c "
python -c "
import overturemaps
gdf = overturemaps.geodataframe('building', bbox=(2.5, 6.2, 4.0, 6.8))
print('Memory usage:', gdf.memory_usage(deep=True).sum() / 1e9, 'GB')
print('Shape:', gdf.shape)
" 2>&1
Memory usage: 2.278339129 GB Shape: (4352445, 24)
11:56
OK, so we have 4.3 million buildings in the Lagos bounding box. That's a lot of data. The approach should be to: 1. Load the building data from Overture for Lagos state bounding box 2. Load the division_area data to get LGA boundaries 3. Do a spatial join to assign each building to an LGA 4. Filter buildings > 1000 m² 5. Compute area in square meters 6. Write the filtered building data as GeoParquet 7. Compute per-LGA summary and write as Parquet But 4.3 million buildings is a lot. Let me think about the best approach. Actually, the bounding box (2.5, 6.2, 4.0, 6.8) covers the entire Lagos state area. Let me verify the Lagos state boundary more precisely. But the bounding box approach should capture all of Lagos. Let me first figure out the right approach. Since the data is large, I could: 1. Load buildings, compute area, filter > 1000 m², spatial join with LGA areas 2. Or use DuckDB to do some operations more efficiently Let me think about this more carefully. The building data has 4.3M rows. Loading into memory works (2.3GB). Let me check what columns are needed. For the buildings output: id, height, footprint_area_m2, lga, geometry For the summary: lga, n_buildings, total_footprint_m2, n_with_height, p50_height_m Let me start by getting the LGA polygons and then doing the join. Actually, a better approach might be to: 1. Get the Lagos LGA polygons (division_area where region='NG-LA' and subtype='county') 2. Get the buildings in the same bbox 3. Compute footprint area for each building (from geometry) 4. Spatial join to assign LGA names 5. Filter > 1000 m² 6. Write output files Let me write a comprehensive script. Actually, for computing area in EPSG:4326 (WGS84 lat/lon), I need to project to a local projected CRS first to get accurate area in m². Nigeria uses EPSG:26391 (Minna / Nigeria West) or EPSG:26392 (Minna / Nigeria East) or EPSG:32631 (WGS 84 / UTM zone 31N). Lagos is around zone 31N. Let me use EPSG:32631 for area calculation. Let me write a Python script step by step.
11:56
Bash
python -c "
python -c "
import overturemaps
# Let's get all the LGA (county) divisions for Lagos
gdf = overturemaps.geodataframe('division_area', bbox=(2.5, 6.2, 4.0, 6.8))
lagos_counties = gdf[(gdf['region'] == 'NG-LA') & (gdf['subtype'] == 'county')]
print('Number of LGAs:', len(lagos_counties))
print(lagos_counties[['id', 'names']].to_string())
# Get the LGA names
lga_names = []
for idx, row in lagos_counties.iterrows():
names = row['names']
if isinstance(names, dict):
lga_names.append(names.get('primary', 'Unknown'))
else:
lga_names.append(str(names))
lagos_counties['lga_name'] = lga_names
print(lagos_counties[['lga_name']].to_string())
print(lagos_counties.crs)
" 2>&1
Number of LGAs: 20
id names
19 93ccd19d-3389-4c00-a8e3-920e20810d39 {'primary': 'Badagry', 'common': None, 'rules': None}
22 32cfa1af-5e74-41a7-9272-b9dd8da715d9 {'primary': 'Amuwo Odofin', 'common': None, 'rules': None}
23 934f5b76-4cbf-4902-b124-b33d057d87b1 {'primary': 'Ifako/Ijaye', 'common': None, 'rules': None}
25 1a9532db-bcd5-4050-b62b-f27a22f2ebcf {'primary': 'Alimosho', 'common': None, 'rules': None}
26 7918fd74-2ce4-48af-b3c1-1a4f1e26558e {'primary': 'Ojo', 'common': None, 'rules': None}
32 cb527384-149e-41c1-b3c5-0070deab27d4 {'primary': 'Kosofe', 'common': None, 'rules': None}
33 b098198f-55d1-46a6-ac1e-71ae35fdd913 {'primary': 'Shomolu', 'common': None, 'rules': None}
36 639f85d6-f581-4f31-b52b-98da63a57271 {'primary': 'Lagos Mainland', 'common': None, 'rules': None}
37 4b715cfc-a9b3-4a05-b601-353b77619939 {'primary': 'Apapa', 'common': None, 'rules': None}
39 ffe6c369-91e2-4984-97d1-bd24feff3732 {'primary': 'Mushin', 'common': [('ru', 'Мушин')], 'rules': None}
40 c540f662-44c9-498d-8f3d-2a41a039a29f {'primary': 'Ikeja', 'common': [('ru', 'Икеджа')], 'rules': None}
41 fcaab4e8-075a-4ab6-a2d2-cd614e95dfc2 {'primary': 'Agege', 'common': None, 'rules': None}
42 457a7986-f212-4765-b432-e1c1b77d6d61 {'primary': 'Oshodi/Isolo', 'common': None, 'rules': None}
43 e2af9375-5eed-46f8-a258-515f22297084 {'primary': 'Ajeromi/Ifelodun', 'common': None, 'rules': None}
44 f0f8532f-4c5e-428a-981e-12a31b2a9044 {'primary': 'Surulere', 'common': None, 'rules': None}
46 c58fd335-e502-421c-b8e3-f263c5fb0ff7 {'primary': 'Lagos Island', 'common': [('ar', 'جزيرة لاغوس')], 'rules': None}
47 0c5600b6-df25-4e75-a25e-ae9937299bad {'primary': 'Eti Osa', 'common': [('yo', 'Etí Ọ̀sà')], 'rules': None}
48 030cb2c1-697b-418e-b820-49393989a03b {'primary': 'Ìkòròdú', 'common': [('en', 'Ikorodu'), ('ar', 'إكورودو'), ('yo', 'Ìkòròdú'), ('ru', 'Икороду')], 'rules': None}
49 61530733-5992-41f4-9212-43f1b460319c {'primary': 'Epe', 'common': [('ru', 'Эпе')], 'rules': None}
51 70bd9c0f-fcdd-4bd2-81df-34d9351c997e {'primary': 'Ibeju Lekki', 'common': None, 'rules': None}
lga_name
19 Badagry
22 Amuwo Odofin
23 Ifako/Ijaye
25 Alimosho
26 Ojo
32 Kosofe
33 Shomolu
36 Lagos Mainland
37 Apapa
39 Mushin
40 Ikeja
41 Agege
42 Oshodi/Isolo
43 Ajeromi/Ifelodun
44 Surulere
46 Lagos Island
47 Eti Osa
48 Ìkòròdú
49 Epe
51 Ibeju Lekki
None
12:37
Now I have all the LGAs for Lagos. The approach will be: 1. Get all buildings in the Lagos bbox from Overture 2. Compute area from geometry (in EPSG:4326, need to project to suitable CRS for area calculation) 3. Filter to buildings > 1000 m² 4. Spatially join with LGA boundaries 5. Write the results Let me think about the best approach. The building data is 4.3M rows (~2.3GB) which is large but manageable. Let me build a comprehensive script. I'll need to: 1. Get the building data from Overture 2. Get the LGA boundaries from Overture 3. For area calculation, I'll need to use an equal-area projection for Nigeria. Let me use EPSG:26391 (Nigeria West) or EPSG:26392 (Nigeria East) - actually, Lagos is in the west, so EPSG:26391. Or I could use UTM 32N (EPSG:32632). Actually, let me use a more generic approach - calculate area in a suitable projected CRS. Let me write the script step by step. Actually, let me think about performance. 4.3M buildings is a lot. Let me use spatial join efficiently. Plan: 1. Load Overture building data for Lagos bbox 2. Calculate polygon areas in a local projected CRS 3. Filter where area > 1000 m² 4. Load Overture division_area for Lagos counties (LGAs) 5. Spatial join to assign LGA names to each building 6. Write the geoparquet file 7. Compute summary statistics per LGA and write to parquet Let me think about what CRS to use for area calculation. For Lagos, UTM zone 32N (EPSG:32632) would work well. Lagos spans roughly 2.5°E to 4°E, so UTM 31N (EPSG:32631) or 32N would both work. Actually, 32632 covers 6°E to 12°E... wait, UTM zones: Zone 31 = 0° to 6°E, Zone 32 = 6°E to 12°E. Lagos is ~2.5°E to 4°E, so UTM Zone 31N (EPSG:32631) is best. Hmm, actually I could also use EPSG:26391 which is "Minna / Nigeria West Belt" covering 2°E to 6°E. That's even more appropriate for Lagos. Let me use EPSG:26391 for area calculation. Let me write a comprehensive script.
12:37
12:37
Write
/work/solve.py
content · 148 lines · py
"""
Extract building footprints > 1000 m² across Lagos State from Overture,
assign LGA, compute per-LGA summary, and write outputs.
"""
import geopandas as gpd
import pandas as pd
import numpy as np
import overturemaps
import pyproj
from shapely import area as shapely_area
# ---------------------------------------------------------------------------
# 1. Load buildings for Lagos bbox
# ---------------------------------------------------------------------------
print("Loading Overture buildings for Lagos bbox...")
bbox_lagos = (2.5, 6.2, 4.0, 6.8) # xmin, ymin, xmax, ymax
buildings = overturemaps.geodataframe("building", bbox=bbox_lagos)
print(f" Total buildings loaded: {len(buildings):,}")
# ---------------------------------------------------------------------------
# 2. Calculate footprint area in square metres using a suitable projected CRS
# Lagos falls in the Minna / Nigeria West Belt (EPSG:26391, 2°E to 6°E)
# ---------------------------------------------------------------------------
print("Calculating footprint areas...")
# Ensure geometries are valid and compute area in projected CRS
proj_crs = "EPSG:26391" # Minna / Nigeria West Belt
# Make a copy with just what we need to save memory
buildings = buildings[["id", "height", "geometry"]].copy()
# Compute area in projected CRS
buildings_proj = buildings.to_crs(proj_crs)
buildings["footprint_area_m2"] = buildings_proj.geometry.area
del buildings_proj # free memory
print(f" Area computed. Min: {buildings['footprint_area_m2'].min():.1f}, "
f"Max: {buildings['footprint_area_m2'].max():.1f}")
# ---------------------------------------------------------------------------
# 3. Filter to buildings > 1000 m²
# ---------------------------------------------------------------------------
print("Filtering buildings > 1000 m²...")
big_buildings = buildings[buildings["footprint_area_m2"] > 1000].copy()
print(f" Buildings > 1000 m²: {len(big_buildings):,}")
del buildings # free memory
# ---------------------------------------------------------------------------
# 4. Load Lagos LGA (county) boundaries
# ---------------------------------------------------------------------------
print("Loading Lagos LGA boundaries...")
divisions = overturemaps.geodataframe("division_area", bbox=bbox_lagos)
lagos_counties = divisions[
(divisions["region"] == "NG-LA") & (divisions["subtype"] == "county")
].copy()
# Extract LGA name from the 'names' dictionary
def get_primary_name(names_dict):
if isinstance(names_dict, dict):
return names_dict.get("primary", "Unknown")
return str(names_dict)
lagos_counties["lga"] = lagos_counties["names"].apply(get_primary_name)
lagos_counties = lagos_counties[["lga", "geometry"]].copy()
lagos_counties = lagos_counties.reset_index(drop=True)
print(f" Number of LGAs: {len(lagos_counties)}")
print(f" LGA names: {lagos_counties['lga'].tolist()}")
del divisions # free memory
# ---------------------------------------------------------------------------
# 5. Spatial join: assign LGA to each building
# ---------------------------------------------------------------------------
print("Performing spatial join (building → LGA)...")
# Ensure both are in the same CRS (WGS 84 / EPSG:4326)
# big_buildings is already EPSG:4326, lagos_counties should be too
print(f" Buildings CRS: {big_buildings.crs}")
print(f" LGAs CRS: {lagos_counties.crs}")
# Use sjoin with predicate 'within' or 'intersects'
# Some buildings may straddle boundaries, use 'intersects' and then keep first match
joined = gpd.sjoin(
big_buildings,
lagos_counties,
how="left",
predicate="intersects",
)
# Some buildings might not fall into any LGA polygon (e.g., maritime)
# We'll keep them but they'll have NaN lga
# If a building intersects multiple LGAs, keep the first match
# sjoin with how='left' already does this when using the default, but let's be explicit
joined = joined.reset_index(drop=True)
# ---------------------------------------------------------------------------
# 6. Build output GeoDataFrame for lagos_buildings.geoparquet
# ---------------------------------------------------------------------------
print("Preparing output GeoDataFrame...")
out_gdf = joined[["id", "height", "footprint_area_m2", "lga", "geometry"]].copy()
# Only keep rows with a valid LGA (should be all within Lagos)
out_gdf = out_gdf.dropna(subset=["lga"]).copy()
# Ensure geometry is in EPSG:4326
if out_gdf.crs is None:
out_gdf = out_gdf.set_crs("EPSG:4326")
else:
out_gdf = out_gdf.to_crs("EPSG:4326")
print(f" Output rows: {len(out_gdf):,}")
print(f" CRS: {out_gdf.crs}")
# ---------------------------------------------------------------------------
# 7. Write lagos_buildings.geoparquet
# ---------------------------------------------------------------------------
print("Writing lagos_buildings.geoparquet...")
out_gdf.to_parquet("/work/lagos_buildings.geoparquet")
print(" Done.")
# ---------------------------------------------------------------------------
# 8. Compute per-LGA summary statistics
# ---------------------------------------------------------------------------
print("Computing per-LGA summary...")
summary = (
out_gdf
.groupby("lga")
.agg(
n_buildings=("id", "count"),
total_footprint_m2=("footprint_area_m2", "sum"),
n_with_height=("height", lambda x: x.notna().sum()),
# Median height: if all null, return NaN
p50_height_m=("height", "median"),
)
.reset_index()
)
# The median of all-NaN will be NaN already thanks to pandas
print(summary.to_string())
# ---------------------------------------------------------------------------
# 9. Write lagos_building_summary.parquet (no geometry)
# ---------------------------------------------------------------------------
print("Writing lagos_building_summary.parquet...")
summary.to_parquet("/work/lagos_building_summary.parquet", index=False)
print(" Done.")
print("\nAll outputs written successfully.")
Wrote /work/solve.py (5984 chars).
12:38
Let me run the script and see if it works. It might take a while given the amount of data (4.3M buildings).
12:38
Bash
cd /work && python solve.py 2>&1
Loading Overture buildings for Lagos bbox...
Total buildings loaded: 4,352,445
Calculating footprint areas...
Traceback (most recent call last):
File "/work/solve.py", line 33, in <module>
buildings_proj = buildings.to_crs(proj_crs)
File "/usr/local/lib/python3.14/site-packages/geopandas/geodataframe.py", line 1842, in to_crs
geom = df.geometry.to_crs(crs=crs, epsg=epsg)
File "/usr/local/lib/python3.14/site-packages/geopandas/geoseries.py", line 1251, in to_crs
self.values.to_crs(crs=crs, epsg=epsg), index=self.index, name=self.name
~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.14/site-packages/geopandas/_compat.py", line 88, in wrapper
return func(*args, **kwargs)
File "/usr/local/lib/python3.14/site-packages/geopandas/array.py", line 1114, in to_crs
raise ValueError(
...<2 lines>...
)
ValueError: Cannot transform naive geometries. Please set a crs on the object first.
15:13
The buildings GeoDataFrame from overturemaps doesn't have CRS set. Let me check what CRS the geometry is in - it's likely EPSG:4326 (WGS 84) based on the bbox coordinates.
15:13
Bash
python -c "
python -c "
import geopandas as gpd
gdf = gpd.read_parquet('/work/lagos_buildings.geoparquet')
print(gdf.crs)
print(gdf.head(2))
print(gdf.columns)
" 2>&1
Traceback (most recent call last):
File "/usr/local/lib/python3.14/site-packages/geopandas/io/arrow.py", line 654, in _read_parquet_schema_and_metadata
schema = parquet.ParquetDataset(path, filesystem=filesystem, **kwargs).schema
~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.14/site-packages/pyarrow/parquet/core.py", line 1471, in __init__
self._dataset = ds.dataset(path_or_paths, filesystem=filesystem,
~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
schema=schema, format=parquet_format,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
partitioning=partitioning,
^^^^^^^^^^^^^^^^^^^^^^^^^^
ignore_prefixes=ignore_prefixes)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.14/site-packages/pyarrow/dataset.py", line 790, in dataset
return _filesystem_dataset(source, **kwargs)
File "/usr/local/lib/python3.14/site-packages/pyarrow/dataset.py", line 472, in _filesystem_dataset
fs, paths_or_selector = _ensure_single_source(source, filesystem)
~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.14/site-packages/pyarrow/dataset.py", line 437, in _ensure_single_source
raise FileNotFoundError(path)
FileNotFoundError: /work/lagos_buildings.geoparquet
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<string>", line 3, in <module>
gdf = gpd.read_parquet('/work/lagos_buildings.geoparquet')
File "/usr/local/lib/python3.14/site-packages/geopandas/io/arrow.py", line 772, in _read_parquet
schema, metadata = _read_parquet_schema_and_metadata(path, filesystem)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.14/site-packages/geopandas/io/arrow.py", line 656, in _read_parquet_schema_and_metadata
schema = parquet.read_schema(path, filesystem=filesystem)
File "/usr/local/lib/python3.14/site-packages/pyarrow/parquet/core.py", line 2456, in read_schema
file_ctx = where = filesystem.open_input_file(where)
~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^
File "pyarrow/_fs.pyx", line 815, in pyarrow._fs.FileSystem.open_input_file
File "pyarrow/error.pxi", line 155, in pyarrow.lib.pyarrow_internal_check_status
File "pyarrow/error.pxi", line 92, in pyarrow.lib.check_status
raise convert_status(status)
FileNotFoundError: [Errno 2] Failed to open local file '/work/lagos_buildings.geoparquet'. Detail: [errno 2] No such file or directory
15:17
The file doesn't exist yet because the script failed. I need to fix it - the issue is that the CRS is not set. Let me check the CRS of the buildings GeoDataFrame from overturemaps.
15:17
Bash
python -c "
python -c "
import overturemaps
gdf = overturemaps.geodataframe('building', bbox=(2.5, 6.2, 4.0, 6.8))
print('CRS:', gdf.crs)
print('Geometry type:', gdf.geometry.dtype)
print('Columns:', gdf.columns.tolist()[:10])
" 2>&1 | head -10
CRS: None Geometry type: geometry Columns: ['id', 'names', 'sources', 'level', 'height', 'min_height', 'is_underground', 'num_floors', 'num_floors_underground', 'min_floor']
17:46
The CRS is None, which means Overture doesn't set it. But the data is in WGS84 EPSG:4326. I need to set the CRS explicitly. Let me fix the script.
17:46
Read
/work/solve.py
1 """
2 Extract building footprints > 1000 m² across Lagos State from Overture,
3 assign LGA, compute per-LGA summary, and write outputs.
4 """
5
6 import geopandas as gpd
7 import pandas as pd
8 import numpy as np
9 import overturemaps
10 import pyproj
11 from shapely import area as shapely_area
12
13 # ---------------------------------------------------------------------------
14 # 1. Load buildings for Lagos bbox
15 # ---------------------------------------------------------------------------
16 print("Loading Overture buildings for Lagos bbox...")
17 bbox_lagos = (2.5, 6.2, 4.0, 6.8) # xmin, ymin, xmax, ymax
18 buildings = overturemaps.geodataframe("building", bbox=bbox_lagos)
19 print(f" Total buildings loaded: {len(buildings):,}")
20
21 # ---------------------------------------------------------------------------
22 # 2. Calculate footprint area in square metres using a suitable projected CRS
23 # Lagos falls in the Minna / Nigeria West Belt (EPSG:26391, 2°E to 6°E)
24 # ---------------------------------------------------------------------------
25 print("Calculating footprint areas...")
26 # Ensure geometries are valid and compute area in projected CRS
27 proj_crs = "EPSG:26391" # Minna / Nigeria West Belt
28
29 # Make a copy with just what we need to save memory
30 buildings = buildings[["id", "height", "geometry"]].copy()
31
32 # Compute area in projected CRS
33 buildings_proj = buildings.to_crs(proj_crs)
34 buildings["footprint_area_m2"] = buildings_proj.geometry.area
35 del buildings_proj # free memory
36
37 print(f" Area computed. Min: {buildings['footprint_area_m2'].min():.1f}, "
38 f"Max: {buildings['footprint_area_m2'].max():.1f}")
39
40 # ---------------------------------------------------------------------------
41 # 3. Filter to buildings > 1000 m²
42 # ---------------------------------------------------------------------------
43 print("Filtering buildings > 1000 m²...")
44 big_buildings = buildings[buildings["footprint_area_m2"] > 1000].copy()
45 print(f" Buildings > 1000 m²: {len(big_buildings):,}")
46
47 del buildings # free memory
48
49 # ---------------------------------------------------------------------------
50 # 4. Load Lagos LGA (county) boundaries
<system-reminder>Showed lines 1-50 of 148. Re-call Read with offset=51 to continue, or raise limit.</system-reminder>
17:49
The issue is that the overturemaps geodataframe returns data with CRS=None but the geometry is actually in WGS84 (EPSG:4326). I need to set the CRS before projecting.
17:49
Edit
/work/solve.py
Edited /work/solve.py.
17:52
Now let me also check that the division_area data also needs CRS set.
17:52
Edit
/work/solve.py
Edited /work/solve.py.
17:54
Also need to fix the crs for the output GeoDataFrame - it was already set properly earlier.
17:54
Bash
cd /work && python solve.py 2>&1
Loading Overture buildings for Lagos bbox...
Total buildings loaded: 4,352,445
Calculating footprint areas...
Area computed. Min: 0.0, Max: 487186.5
Filtering buildings > 1000 m²...
Buildings > 1000 m²: 9,717
Loading Lagos LGA boundaries...
Number of LGAs: 20
LGA names: ['Badagry', 'Amuwo Odofin', 'Ifako/Ijaye', 'Alimosho', 'Ojo', 'Kosofe', 'Shomolu', 'Lagos Mainland', 'Apapa', 'Mushin', 'Ikeja', 'Agege', 'Oshodi/Isolo', 'Ajeromi/Ifelodun', 'Surulere', 'Lagos Island', 'Eti Osa', 'Ìkòròdú', 'Epe', 'Ibeju Lekki']
Performing spatial join (building → LGA)...
Buildings CRS: EPSG:4326
LGAs CRS: EPSG:4326
Preparing output GeoDataFrame...
Output rows: 7,042
CRS: EPSG:4326
Writing lagos_buildings.geoparquet...
Done.
Computing per-LGA summary...
lga n_buildings total_footprint_m2 n_with_height p50_height_m
0 Agege 55 1.179713e+05 0 NaN
1 Ajeromi/Ifelodun 60 1.079543e+05 0 NaN
2 Alimosho 353 6.481027e+05 0 NaN
3 Amuwo Odofin 836 1.752523e+06 0 NaN
4 Apapa 286 6.087714e+05 0 NaN
5 Badagry 125 2.271980e+05 0 NaN
6 Epe 322 7.847648e+05 0 NaN
7 Eti Osa 1066 1.857562e+06 237 20.0
8 Ibeju Lekki 370 1.145981e+06 0 NaN
9 Ifako/Ijaye 120 2.487123e+05 0 NaN
10 Ikeja 926 3.187328e+06 46 15.0
11 Kosofe 165 3.439614e+05 0 NaN
12 Lagos Island 118 2.272549e+05 9 20.0
13 Lagos Mainland 123 2.296862e+05 0 NaN
14 Mushin 465 1.066508e+06 1 35.0
15 Ojo 232 4.088346e+05 0 NaN
16 Oshodi/Isolo 420 7.953889e+05 3 27.0
17 Shomolu 125 2.017849e+05 3 17.0
18 Surulere 341 7.798400e+05 0 NaN
19 Ìkòròdú 534 1.858732e+06 4 7.5
Writing lagos_building_summary.parquet...
Done.
All outputs written successfully.
20:59
The script ran successfully. Let me verify the outputs.
20:59
20:59
Bash
python -c "
python -c "
import geopandas as gpd
import pandas as pd
# Check buildings geoparquet
gdf = gpd.read_parquet('/work/lagos_buildings.geoparquet')
print('=== lagos_buildings.geoparquet ===')
print(f'Rows: {len(gdf):,}')
print(f'Columns: {gdf.columns.tolist()}')
print(f'CRS: {gdf.crs}')
print(f'Types:\n{gdf.dtypes}')
print()
print(gdf.head())
print()
# Check summary parquet
df = pd.read_parquet('/work/lagos_building_summary.parquet')
print('=== lagos_building_summary.parquet ===')
print(f'Rows: {len(df)}')
print(f'Columns: {df.columns.tolist()}')
print(f'Types:\n{df.dtypes}')
print()
print(df.to_string())
" 2>&1
=== lagos_buildings.geoparquet ===
Rows: 7,042
Columns: ['id', 'height', 'footprint_area_m2', 'lga', 'geometry']
CRS: {"$schema": "https://proj.org/schemas/v0.7/projjson.schema.json", "type": "GeographicCRS", "name": "WGS 84", "datum_ensemble": {"name": "World Geodetic System 1984 ensemble", "members": [{"name": "World Geodetic System 1984 (Transit)"}, {"name": "World Geodetic System 1984 (G730)"}, {"name": "World Geodetic System 1984 (G873)"}, {"name": "World Geodetic System 1984 (G1150)"}, {"name": "World Geodetic System 1984 (G1674)"}, {"name": "World Geodetic System 1984 (G1762)"}, {"name": "World Geodetic System 1984 (G2139)"}, {"name": "World Geodetic System 1984 (G2296)"}], "ellipsoid": {"name": "WGS 84", "semi_major_axis": 6378137, "inverse_flattening": 298.257223563}, "accuracy": "2.0", "id": {"authority": "EPSG", "code": 6326}}, "coordinate_system": {"subtype": "ellipsoidal", "axis": [{"name": "Geodetic latitude", "abbreviation": "Lat", "direction": "north", "unit": "degree"}, {"name": "Geodetic longitude", "abbreviation": "Lon", "direction": "east", "unit": "degree"}]}, "scope": "Horizontal component of 3D system.", "area": "World.", "bbox": {"south_latitude": -90, "west_longitude": -180, "north_latitude": 90, "east_longitude": 180}, "id": {"authority": "EPSG", "code": 4326}}
Types:
id str
height float64
footprint_area_m2 float64
lga str
geometry geometry
dtype: object
id ... geometry
134 e24f1bf5-e957-4867-859b-a6a578fd9b06 ... POLYGON ((2.70836 6.37804, 2.70816 6.37804, 2....
139 5df57f70-f431-495f-98cb-eff96e1ca151 ... POLYGON ((2.72675 6.37881, 2.72672 6.37907, 2....
140 cfb03953-1851-4ec2-a087-3283cd510753 ... POLYGON ((2.7137 6.37984, 2.7136 6.37984, 2.71...
152 51f6d045-e5ae-4537-8eab-73c366a133bc ... POLYGON ((2.70833 6.38159, 2.70834 6.38147, 2....
153 a98aa38c-6427-44a7-b421-4afe08a12736 ... POLYGON ((2.70892 6.38282, 2.70897 6.38283, 2....
[5 rows x 5 columns]
=== lagos_building_summary.parquet ===
Rows: 20
Columns: ['lga', 'n_buildings', 'total_footprint_m2', 'n_with_height', 'p50_height_m']
Types:
lga str
n_buildings int64
total_footprint_m2 float64
n_with_height int64
p50_height_m float64
dtype: object
lga n_buildings total_footprint_m2 n_with_height p50_height_m
0 Agege 55 1.179713e+05 0 NaN
1 Ajeromi/Ifelodun 60 1.079543e+05 0 NaN
2 Alimosho 353 6.481027e+05 0 NaN
3 Amuwo Odofin 836 1.752523e+06 0 NaN
4 Apapa 286 6.087714e+05 0 NaN
5 Badagry 125 2.271980e+05 0 NaN
6 Epe 322 7.847648e+05 0 NaN
7 Eti Osa 1066 1.857562e+06 237 20.0
8 Ibeju Lekki 370 1.145981e+06 0 NaN
9 Ifako/Ijaye 120 2.487123e+05 0 NaN
10 Ikeja 926 3.187328e+06 46 15.0
11 Kosofe 165 3.439614e+05 0 NaN
12 Lagos Island 118 2.272549e+05 9 20.0
13 Lagos Mainland 123 2.296862e+05 0 NaN
14 Mushin 465 1.066508e+06 1 35.0
15 Ojo 232 4.088346e+05 0 NaN
16 Oshodi/Isolo 420 7.953889e+05 3 27.0
17 Shomolu 125 2.017849e+05 3 17.0
18 Surulere 341 7.798400e+05 0 NaN
19 Ìkòròdú 534 1.858732e+06 4 7.5
21:08
The outputs look correct. Both files are generated and well-formed. Let me verify the requirements: 1. **lagos_buildings.geoparquet** - EPSG:4326 GeoParquet with columns `id`, `height`, `footprint_area_m2`, `lga`, and `geometry`. ✅ 2. **lagos_building_summary.parquet** - Plain Parquet with no geometry column, columns `lga`, `n_buildings`, `total_footprint_m2`, `n_with_height`, `p50_height_m`. ✅ Let me also verify a few things: - Buildings > 1000 m² only - filtering worked (4,352,445 total → 9,717 > 1000 m² → 7,042 after spatial join) - Per-LGA rollup with correct statistics - Median height where available, NaN otherwise Everything looks good!
21:08