dd-l3-lagos-overture-buildings

Status: done
Score: 1.000
Duration: 12:11
Cost: 5.06¢
Model: deepseek/deepseek-v4-flash

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:03
Let me break this down:

1. Get Overture Maps building footprints for Lagos State, Nigeria
2. Filter to buildings with area > 1000 m²
3. Get LGA boundaries for Lagos State
4. Spatial join to assign LGA to each building
5. Compute per-LGA summary stats
6. Output two files

Let me start by exploring what's available in the working directory and then write the solution.

For Overture Maps, I'll use the overturemaps Python library. I need to query buildings in the Lagos State bounding box.

Let me figure out the bounding box for Lagos State, Nigeria.

Lagos State is roughly bounded by:
- Latitude: 6.2°N to 6.7°N
- Longitude: 2.7°E to 3.9°E (or so)

Let me write the script step by step.

For Overture, the overturemaps library can be used. Let me check if it's installed.

The key challenge is:
1. Download Overture buildings for Lagos
2. Load LGA boundaries for Lagos State
3. Spatial join
4. Compute area in a metric CRS, filter > 1000 m², then convert back to 4326
5. Output

Let me start writing the script.
0:03
Bash pip list 2>/dev/null | grep -iE 'overture|pyogrio|geopandas|shapely|pyproj|panda...
duckdb             1.5.3
geopandas          1.1.3
overturemaps       1.0.0
pandas             3.0.3
pyarrow            24.0.0
pyogrio            0.12.1
pyproj             3.7.2
shapely            2.1.2
0:04
Bash ls /work/
(no output)
0:22
Let me plan this:

1. Lagos State, Nigeria — get the administrative boundary (LGAs) and building footprints from Overture
2. Overture has buildings and administrative divisions data
3. I need to:
   - Get Lagos State boundary (or LGA boundaries directly)
   - Get all building footprints within Lagos State
   - Filter footprints > 1000 m²
   - Compute per-LGA summary stats
   - Write two output files

Let me use overturemaps to get the data. Overture has a `boundary` type for administrative divisions and `building` type for buildings.

Let me write a Python script to do this.

First, let me check what's available in overturemaps:
- `overturemaps` Python library lets you download Overture data by type and bounding box
- For buildings: type="building"
- For administrative boundaries: type="administrative" or use the `boundary` division data

Let me think about approach:

1. Get Lagos State boundary - I can use overturemaps to get the administrative division for Lagos State, Nigeria
2. Then use that boundary to clip buildings
3. Or, I can get the bounding box for Lagos State and download buildings within it, then clip

Actually, a simpler approach:
1. Use overturemaps to get buildings in the Lagos State area (by specifying a bbox around Lagos)
2. Get Lagos LGA boundaries from Overture's administrative division data
3. Spatial join

Let me write the script step by step.

Lagos State approximate bounds: ~2.5°E to 4.5°E, ~6.2°N to 6.8°N

Let me write the script:
0:22
Write /work/solve.py
content · 179 lines · py
#!/usr/bin/env python3
"""
Extract Lagos State (Nigeria) building footprints > 1000 m² from Overture Maps.
Compute per-LGA summary statistics.
"""

import geopandas as gpd
import pandas as pd
import numpy as np
from overturemaps import download
from shapely.geometry import box
import logging
import time
import sys

logging.basicConfig(level=logging.INFO, stream=sys.stdout)
logger = logging.getLogger(__name__)


def retry_download(record_type, geometry, max_retries=3, **kwargs):
    """Download Overture data with retries and backoff."""
    last_err = None
    for attempt in range(1, max_retries + 1):
        try:
            logger.info(f"Downloading {record_type} (attempt {attempt}/{max_retries}) ...")
            gdf = download(record_type, geometry=geometry, **kwargs)
            logger.info(f"Downloaded {len(gdf)} {record_type} records.")
            return gdf
        except Exception as e:
            last_err = e
            logger.warning(f"Attempt {attempt} failed: {e}")
            if attempt < max_retries:
                time.sleep(2 ** attempt)
    raise RuntimeError(f"All {max_retries} attempts failed for {record_type}. Last error: {last_err}")


def main():
    # ------------------------------------------------------------------
    # 1. Define a bounding box generously covering Lagos State, Nigeria
    # ------------------------------------------------------------------
    # Lagos State roughly spans (lon, lat): ~2.5 to 4.5 E, ~6.2 to 6.8 N
    # Use a slightly generous box to capture everything
    bbox = box(2.5, 6.2, 4.5, 6.8)   # (minx, miny, maxx, maxy)  — EPSG:4326

    # ------------------------------------------------------------------
    # 2. Download Lagos LGA boundaries from Overture administrative divisions
    # ------------------------------------------------------------------
    logger.info("Downloading administrative divisions (LGAs) for Lagos area...")
    divisions = retry_download("boundary", geometry=bbox)

    if divisions.empty:
        raise RuntimeError("No administrative boundary data returned for Lagos area.")

    logger.info(f"Columns: {list(divisions.columns)}")
    logger.info(f"Division records: {len(divisions)}")

    # Keep only administrative divisions at locality / local_admin level
    # Overture boundary schema has: id, name, class, subtype, geometry, etc.
    # For Nigeria LGAs, look for subtype='administrative_area_level_3' or class='locality'
    # Filter to Nigeria / Lagos region
    # Let's see what we got first

    # Keep only polygon geometries
    divisions = divisions[divisions.geometry.notna() & (divisions.geometry.type.isin(["Polygon", "MultiPolygon"]))].copy()

    if len(divisions) == 0:
        raise RuntimeError("No polygon administrative boundaries found.")

    # Print sample to understand the data
    print("\n--- Sample division records ---")
    sample_cols = [c for c in divisions.columns if c in ("id", "name", "class", "subtype", "country", "region")]
    print(divisions[sample_cols].head(20).to_string())

    # Try to find Lagos State and filter LGAs
    # Overture boundaries have properties like: subtype, class, name, country_code, etc.
    lgas = divisions.copy()

    # Strategy: look for locality-level divisions within Lagos State
    # The divisions dataframe may include countries, states, LGAs
    # Filter to keep only level-3 administrative areas (LGAs) in Lagos

    # Let's try filtering by subtype first
    if "subtype" in lgas.columns:
        subtypes = lgas["subtype"].value_counts()
        print(f"\nSubtypes found: {dict(subtypes)}")

    if "class" in lgas.columns:
        classes = lgas["class"].value_counts()
        print(f"\nClasses found: {dict(classes)}")

    # For Nigeria, country name might be "Nigeria" or country_code "NGA"
    # Let's check
    country_cols = [c for c in lgas.columns if "country" in c.lower()]
    print(f"\nCountry-related columns: {country_cols}")

    for cc in country_cols:
        vals = lgas[cc].dropna().unique()
        print(f"  {cc}: {vals}")

    # Try to identify Lagos State records
    name_cols = [c for c in lgas.columns if "name" in c.lower()]
    print(f"\nName columns: {name_cols}")
    for nc in name_cols:
        names = lgas[nc].dropna().unique()
        # Show names containing "lagos" case-insensitive
        lagos_names = [n for n in names if "lagos" in str(n).lower()]
        print(f"  Lagos-related names in '{nc}': {lagos_names[:20]}")

    # ------------------------------------------------------------------
    # 3. Filter to Lagos State LGAs
    # ------------------------------------------------------------------
    # Based on the exploration above, let's filter
    # We'll look for records where the name contains "Lagos" at the state level,
    # or where the division level is LGA-like

    # Let's see if there's a "Lagos" state polygon
    state_mask = lgas["name"].str.contains("lagos", case=False, na=False) if "name" in lgas.columns else pd.Series(False, index=lgas.index)

    # Also check other name columns
    for nc in name_cols:
        if nc != "name":
            m = lgas[nc].astype(str).str.contains("lagos", case=False, na=False)
            state_mask = state_mask | m

    lagos_records = lgas[state_mask].copy()
    print(f"\n--- Records containing 'Lagos' ({len(lagos_records)}) ---")
    print(lagos_records[sample_cols].to_string())

    # ------------------------------------------------------------------
    # We might need a different approach. Let me try the 'division' dataset
    # ------------------------------------------------------------------
    # Actually, overturemaps has a 'division' type as well. Let's try that.
    logger.info("\nTrying 'division' record type instead...")
    divisions2 = retry_download("division", geometry=bbox)

    if not divisions2.empty:
        print(f"\nDivision2 columns: {list(divisions2.columns)}")
        print(divisions2.head(10).to_string())

        # Filter for Nigeria / Lagos
        country_cols2 = [c for c in divisions2.columns if "country" in c.lower()]
        for cc in country_cols2:
            vals = divisions2[cc].dropna().unique()
            print(f"  {cc}: {vals[:20]}")

        name_cols2 = [c for c in divisions2.columns if "name" in c.lower()]
        for nc in name_cols2:
            names = divisions2[nc].dropna().unique()
            lagos_n = [n for n in names if "lagos" in str(n).lower()]
            print(f"  Lagos names in '{nc}': {lagos_n[:20]}")

        # Check divisions by subtype / level
        for col in ["subtype", "class", "division_level", "level"]:
            if col in divisions2.columns:
                print(f"  {col} values: {dict(divisions2[col].value_counts().head(20))}")

    # ------------------------------------------------------------------
    # 4. Download buildings over the same bbox
    # ------------------------------------------------------------------
    logger.info("Downloading building footprints...")
    buildings = retry_download("building", geometry=bbox)
    logger.info(f"Total buildings downloaded: {len(buildings)}")

    if buildings.empty:
        raise RuntimeError("No building data returned.")

    print(f"\nBuildings columns: {list(buildings.columns)}")
    print(buildings.head(5).to_string())

    # Show height info
    height_cols = [c for c in buildings.columns if "height" in c.lower()]
    print(f"Height columns: {height_cols}")
    for hc in height_cols:
        non_null = buildings[hc].notna().sum()
        print(f"  {hc}: {non_null} non-null out of {len(buildings)}")


if __name__ == "__main__":
    main()
Wrote /work/solve.py (7617 chars).
0:24
Let me run this exploration script first to understand the Overture data structure before building the final solution.
0:24
Bash cd /work && python solve.py 2>&1
Traceback (most recent call last):
  File "/work/solve.py", line 10, in <module>
    from overturemaps import download
ImportError: cannot import name 'download' from 'overturemaps' (/usr/local/lib/python3.14/site-packages/overturemaps/__init__.py)
0:26
The overturemaps library API has changed. Let me check what's available.
0:26
Bash python -c "import overturemaps; print(dir(overturemaps))"
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'core', 'geodataframe', 'get_all_overture_types', 'models', 'record_batch_reader']
0:27
Bash python -m overturemaps --help 2>&1 || true
Usage: python -m overturemaps [OPTIONS] COMMAND [ARGS]...

Options:
  --version  Show the version and exit.
  --help     Show this message and exit.

Commands:
  changelog  Query the GERS changelog for feature changes.
  download
  gers       Query the GERS registry for a feature by its GERS ID.
  releases   Manage and query Overture Maps releases.
0:30
So the API is `overturemaps.core.download` or via CLI `python -m overturemaps download`. Let me check how to use it programmatically.
0:30
Bash python -c "
python -c "
from overturemaps import core
help(core.download)
" 2>&1 | head -40
Traceback (most recent call last):
  File "<string>", line 3, in <module>
    help(core.download)
         ^^^^^^^^^^^^^
AttributeError: module 'overturemaps.core' has no attribute 'download'
0:31
Bash python -m overturemaps download --help 2>&1
Usage: python -m overturemaps download [OPTIONS]

Options:
  --bbox BBOX
  -f [geojson|geojsonseq|geoparquet]
                                  [required]
  -o, --output PATH
  -t, --type [address|bathymetry|building|building_part|division|division_area|division_boundary|place|segment|connector|infrastructure|land|land_cover|land_use|water]
                                  [required]
  -r, --release TEXT              Release version (defaults to latest)
  --stac / --no-stac              By default, uses the STAC catalog to limit
                                  which Parquet files are downloaded. Pass
                                  --no-stac to skip the catalog and read the
                                  full S3 dataset directly.
  --connect_timeout INTEGER
  --request_timeout INTEGER
  --help                          Show this message and exit.
0:34
So the `overturemaps` library provides a CLI tool. The Python API seems to use `overturemaps.geodataframe` or `overturemaps.core`. Let me explore how to use it programmatically.
0:34
Bash python -c "
python -c "
import overturemaps
# Look at geodataframe module
from overturemaps import geodataframe
print(dir(geodataframe))
" 2>&1
['__annotate__', '__annotations__', '__builtins__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__getstate__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__type_params__']
0:35
Bash python -c "
python -c "
# Try using the CLI via subprocess to download first, then explore
from overturemaps import core
print(dir(core))
" 2>&1
['ALL_RELEASES', 'BBox', 'GeoDataFrame', 'HAS_GEOPANDAS', 'List', 'Optional', 'STAC_CATALOG_URL', 'Tuple', '_ReleasesProxy', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '_binary_search_manifest', '_cached_stac_catalog', '_coerce_bbox', '_dataset_path', '_get_all_releases', '_get_files_from_stac', '_get_stac_catalog', '_prepare_query', '_record_batch_reader_from_dataset', 'count_rows', 'ds', 'fs', 'geoarrow_schema_adapter', 'geodataframe', 'get_all_overture_types', 'get_available_releases', 'get_latest_release', 'io', 'json', 'pa', 'pc', 'pq', 'query_gers_registry', 'record_batch_reader', 'record_batch_reader_from_gers', 'sys', 'type_theme_map', 'urlopen']
0:38
Let me look at the `record_batch_reader` function in `core` - that seems to be how you programmatically download data.
0:38
Bash python -c "
python -c "
from overturemaps.core import record_batch_reader
import inspect
print(inspect.signature(record_batch_reader))
print(inspect.getsource(record_batch_reader))
" 2>&1 | head -80
(overture_type, bbox=None, release=None, connect_timeout=None, request_timeout=None, stac=False) -> pyarrow.lib.RecordBatchReader | None
def record_batch_reader(
    overture_type,
    bbox=None,
    release=None,
    connect_timeout=None,
    request_timeout=None,
    stac=False,
) -> Optional[pa.RecordBatchReader]:
    """Return a pyarrow RecordBatchReader for the desired bounding box and s3 path, or None on error."""
    result = _prepare_query(
        overture_type, bbox, release, connect_timeout, request_timeout, stac
    )
    if result is None:
        return None
    dataset, filter_expr = result
    return _record_batch_reader_from_dataset(dataset, filter_expr=filter_expr)
0:42
So the `overturemaps` library has a function `record_batch_reader` in `overturemaps.core` that returns a PyArrow RecordBatchReader. I can use that to read the data as PyArrow tables, then convert to GeoPandas.

Let me also check the `geodataframe` module since the import worked there.
0:42
Bash python -c "
python -c "
from overturemaps.core import record_batch_reader, GeoDataFrame
print(type(GeoDataFrame))
import inspect
# Check what GeoDataFrame is
if isinstance(GeoDataFrame, type):
    print('GeoDataFrame is a class')
    import inspect as i
    src = i.getsource(GeoDataFrame)
    print(src[:500])
else:
    print('GeoDataFrame:', GeoDataFrame)
" 2>&1
<class 'type'>
GeoDataFrame is a class
class GeoDataFrame(GeoPandasBase, DataFrame):
    """A GeoDataFrame object is a pandas.DataFrame that has one or more columns
    containing geometry.

    In addition to the standard DataFrame constructor arguments,
    GeoDataFrame also accepts the following keyword arguments:

    Parameters
    ----------
    crs : value (optional)
        Coordinate Reference System of the geometry objects. Can be anything accepted by
        :meth:`pyproj.CRS.from_user_input() <pyproj.crs.CRS.from_user_inp
0:47
So `overturemaps` provides a `record_batch_reader` function to get a PyArrow RecordBatchReader. But there's also a CLI tool. Let me use the CLI to download specific types.

The approach:
1. Use `python -m overturemaps download` with `--type building`, `-f geojsonseq` to get buildings for Lagos bbox
2. Use `python -m overturemaps download` with `--type division` or `--type division_area` or `--type division_boundary` to get administrative boundaries for Lagos LGAs
3. Process everything in Python

Actually, let me think about this more carefully. The CLI downloads to a file. I could download via subprocess.

Or I can use the `record_batch_reader` function directly in Python to get pyarrow RecordBatchReader, then convert to GeoPandas.

Let me use the Python API since it's cleaner.

Let me also check what overture types are available for divisions.
0:47
Bash python -c "
python -c "
from overturemaps import get_all_overture_types
types = get_all_overture_types()
print(types)
" 2>&1
['address', 'bathymetry', 'building', 'building_part', 'division', 'division_area', 'division_boundary', 'place', 'segment', 'connector', 'infrastructure', 'land', 'land_cover', 'land_use', 'water']
0:51
Now I understand the API. I should use `record_batch_reader` to get a PyArrow RecordBatchReader, then convert it to a GeoDataFrame. Let me also check how to convert the RecordBatchReader to a GeoDataFrame properly.
0:51
Bash python -c "
python -c "
from overturemaps.core import record_batch_reader, GeoDataFrame
import pyarrow as pa

# Test with a small bbox for Lagos
bbox = (3.2, 6.3, 3.6, 6.7)  # (west, south, east, north)
reader = record_batch_reader('building', bbox=bbox)
if reader is not None:
    # Read a few rows to see schema
    batch = reader.read_next_batch()
    print('Schema:')
    print(batch.schema)
    print()
    print('First batch num_rows:', batch.num_rows)
    print()
    print('Column names:', batch.schema.names)
else:
    print('No reader returned')

# Also test division
print('\\n--- Division ---')
reader2 = record_batch_reader('division', bbox=bbox)
if reader2 is not None:
    batch2 = reader2.read_next_batch()
    print('Division schema:')
    print(batch2.schema)
    print()
    print('Column names:', batch2.schema.names)
else:
    print('No reader returned')
" 2>&1
Schema:
id: string
names: struct<primary: string, common: map<string, string ('common')>, rules: list<element: struct<variant: (... 159 chars omitted)
  child 0, primary: string
  child 1, common: map<string, string ('common')>
      child 0, common: struct<key: string not null, value: string> not null
          child 0, key: string not null
          child 1, value: string
  child 2, rules: list<element: struct<variant: string, language: string, perspectives: struct<mode: string, countries (... 87 chars omitted)
      child 0, element: struct<variant: string, language: string, perspectives: struct<mode: string, countries: list<element (... 72 chars omitted)
          child 0, variant: string
          child 1, language: string
          child 2, perspectives: struct<mode: string, countries: list<element: string>>
              child 0, mode: string
              child 1, countries: list<element: string>
                  child 0, element: string
          child 3, value: string
          child 4, between: list<element: double>
              child 0, element: double
          child 5, side: string
sources: list<element: struct<property: string, dataset: string, license: string, record_id: string, update_t (... 65 chars omitted)
  child 0, element: struct<property: string, dataset: string, license: string, record_id: string, update_time: string, c (... 50 chars omitted)
      child 0, property: string
      child 1, dataset: string
      child 2, license: string
      child 3, record_id: string
      child 4, update_time: string
      child 5, confidence: double
      child 6, between: list<element: double>
          child 0, element: double
level: int32
height: double
min_height: double
is_underground: bool
num_floors: int32
num_floors_underground: int32
min_floor: int32
subtype: string
class: string
facade_color: string
facade_material: string
roof_material: string
roof_shape: string
roof_direction: double
roof_orientation: string
roof_color: string
roof_height: double
geometry: binary
has_parts: bool
version: int32
bbox: struct<xmin: double, xmax: double, ymin: double, ymax: double>
  child 0, xmin: double
  child 1, xmax: double
  child 2, ymin: double
  child 3, ymax: double
-- schema metadata --
geo: '{"version": "1.1.0", "primary_column": "geometry", "columns": {"geo' + 282

First batch num_rows: 21664

Column names: ['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']

--- Division ---
Division schema:
id: string
geometry: binary
country: string
sources: list<element: struct<property: string, dataset: string, license: string, record_id: string, update_t (... 65 chars omitted)
  child 0, element: struct<property: string, dataset: string, license: string, record_id: string, update_time: string, c (... 50 chars omitted)
      child 0, property: string
      child 1, dataset: string
      child 2, license: string
      child 3, record_id: string
      child 4, update_time: string
      child 5, confidence: double
      child 6, between: list<element: double>
          child 0, element: double
subtype: string
admin_level: int32
class: string
names: struct<primary: string, common: map<string, string ('common')>, rules: list<element: struct<variant: (... 159 chars omitted)
  child 0, primary: string
  child 1, common: map<string, string ('common')>
      child 0, common: struct<key: string not null, value: string> not null
          child 0, key: string not null
          child 1, value: string
  child 2, rules: list<element: struct<variant: string, language: string, perspectives: struct<mode: string, countries (... 87 chars omitted)
      child 0, element: struct<variant: string, language: string, perspectives: struct<mode: string, countries: list<element (... 72 chars omitted)
          child 0, variant: string
          child 1, language: string
          child 2, perspectives: struct<mode: string, countries: list<element: string>>
              child 0, mode: string
              child 1, countries: list<element: string>
                  child 0, element: string
          child 3, value: string
          child 4, between: list<element: double>
              child 0, element: double
          child 5, side: string
wikidata: string
perspectives: struct<mode: string, countries: list<element: string>>
  child 0, mode: string
  child 1, countries: list<element: string>
      child 0, element: string
local_type: map<string, string ('local_type')>
  child 0, local_type: struct<key: string not null, value: string> not null
      child 0, key: string not null
      child 1, value: string
region: string
hierarchies: list<element: list<element: struct<division_id: string, subtype: string, name: string>>>
  child 0, element: list<element: struct<division_id: string, subtype: string, name: string>>
      child 0, element: struct<division_id: string, subtype: string, name: string>
          child 0, division_id: string
          child 1, subtype: string
          child 2, name: string
parent_division_id: string
norms: struct<driving_side: string>
  child 0, driving_side: string
population: int32
capital_division_ids: list<element: string>
  child 0, element: string
capital_of_divisions: list<element: struct<division_id: string, subtype: string>>
  child 0, element: struct<division_id: string, subtype: string>
      child 0, division_id: string
      child 1, subtype: string
cartography: struct<prominence: int32, min_zoom: int32, max_zoom: int32, sort_key: int32>
  child 0, prominence: int32
  child 1, min_zoom: int32
  child 2, max_zoom: int32
  child 3, sort_key: int32
version: int32
bbox: struct<xmin: double, xmax: double, ymin: double, ymax: double>
  child 0, xmin: double
  child 1, xmax: double
  child 2, ymin: double
  child 3, ymax: double
-- schema metadata --
geo: '{"version": "1.1.0", "primary_column": "geometry", "columns": {"geo' + 275

Column names: ['id', 'geometry', 'country', 'sources', 'subtype', 'admin_level', 'class', 'names', 'wikidata', 'perspectives', 'local_type', 'region', 'hierarchies', 'parent_division_id', 'norms', 'population', 'capital_division_ids', 'capital_of_divisions', 'cartography', 'version', 'bbox']
1:52
Great, I can see the schemas now. Let me understand the data:

**Building schema** has: `id`, `height`, `geometry`, `bbox`, `names`, `subtype`, `class`, etc.
**Division schema** has: `id`, `geometry`, `country`, `names`, `subtype`, `admin_level`, `class`, `region`, `hierarchies`, `parent_division_id`, etc.

For the buildings, I need to download all buildings in Lagos State, compute footprint area from geometry, filter to > 1000 m², and assign each building to an LGA.

For divisions, I need to find Lagos State LGAs. The division data has `country` field, and `names` field with `primary` name. Also has `subtype` (likely 'country', 'region', 'state', 'locality' etc.) and `admin_level` and `region`.

Let me explore the division data more to find the LGAs.
1:52
Write /work/explore.py
content · 83 lines · py
#!/usr/bin/env python3
"""Explore Overture divisions for Lagos, Nigeria to understand LGA structure."""

from overturemaps.core import record_batch_reader
import pyarrow as pa
import pyarrow.compute as pc

bbox = (2.5, 6.0, 4.5, 7.0)  # generous Lagos area box

reader = record_batch_reader("division", bbox=bbox)
if reader is None:
    print("No division reader returned")
    exit(1)

batches = []
while True:
    try:
        b = reader.read_next_batch()
        batches.append(b)
    except StopAsyncIteration:
        break
    except Exception:
        break

table = pa.Table.from_batches(batches)
print(f"Total division records: {len(table)}")

# Show country values
country_col = table.column("country")
print(f"\nCountry unique values: {pc.unique(country_col).to_pylist()}")

# Show subtype values
subtype_col = table.column("subtype")
print(f"\nSubtype unique values: {pc.unique(subtype_col).to_pylist()}")

# Show admin_level values
admin_col = table.column("admin_level")
print(f"\nAdmin level unique values: {pc.unique(admin_col).to_pylist()}")

# Show region values
region_col = table.column("region")
regions = pc.unique(region_col).to_pylist()
print(f"\nRegion unique values: {regions}")

# Find records with country='NG' or country='Nigeria' etc
# First filter to Nigeria
mask_ng = pc.equal(country_col, "NG")
ng_table = table.filter(mask_ng)
print(f"\n--- Nigeria records: {len(ng_table)} ---")

# Get names primary for Nigeria records
names_col = ng_table.column("names")
# names is struct<primary: string, ...>
prim_names = pc.struct_field(names_col, "primary")
print(f"Names: {prim_names.to_pylist()}")

# Subtypes for NG
ng_subtypes = ng_table.column("subtype")
print(f"Subtypes for NG: {pc.unique(ng_subtypes).to_pylist()}")

# Admin levels for NG
ng_admin = ng_table.column("admin_level")
print(f"Admin levels for NG: {pc.unique(ng_admin).to_pylist()}")

# Show all Nigeria records
import pandas as pd
ng_df = ng_table.to_pandas()
for idx, row in ng_df.iterrows():
    name = row["names"]["primary"] if row.get("names") and hasattr(row["names"], "__getitem__") else str(row.get("names"))
    subtype = row.get("subtype")
    admin = row.get("admin_level")
    region = row.get("region")
    print(f"  ID={row['id']}, name={name}, subtype={subtype}, admin_level={admin}, region={region}")

# Also check for Lagos directly in all records
all_prim_names = pc.struct_field(table.column("names"), "primary")
all_names_list = all_prim_names.to_pylist()
lagos_mask = [n is not None and "lagos" in str(n).lower() for n in all_names_list]
lagos_indices = [i for i, m in enumerate(lagos_mask) if m]
print(f"\n--- Records with 'Lagos' in name ({len(lagos_indices)}) ---")
for idx in lagos_indices:
    row = table.slice(idx, 1).to_pydict()
    print(f"  Index {idx}: { {k: v[0] for k, v in row.items() if k in ('id', 'subtype', 'admin_level', 'class', 'region', 'country')} }, name={all_names_list[idx]}")
Wrote /work/explore.py (2919 chars).
1:53
Bash cd /work && python explore.py 2>&1
Total division records: 1406

Country unique values: ['NG', 'BJ']

Subtype unique values: ['locality', 'neighborhood', 'macrohood', 'county', 'microhood', 'region']

Admin level unique values: [None, 2, 1]

Region unique values: ['NG-OG', 'NG-LA', 'BJ-OU', 'BJ-PL', 'NG-ON']

--- Nigeria records: 670 ---
Names: ['Oluke', 'Ebute Lekki', 'Akawkaw', 'Ijaiye', 'Ilera', 'Aba-Onigbagbo', 'Ologode', 'Ijede', 'Erukan', 'Akete', 'Ashipa', 'Abegede', 'Obada', 'Ikoga', 'Ladeba', 'Lagos', 'Ladeda Ebute', 'Agaja', 'Igbede', 'Tinubu', 'Amuwo Odofin', 'Sangotedo', 'Mawun Isale', 'Ibeju', 'Morekete', 'Badore', 'Ilashe', 'Ijegun', 'Ijofin', 'Odo-Mola', 'Ebute Ikorodu', 'Owode Onirin', 'Ilushin', 'Araromi-Obu', 'Victoria Island', 'Ago Palace', 'Ilupeju', 'Onigbongbo', 'Ilagbo', 'Ikeja', 'Ilumawfin', 'Lagos Mainland', 'Poka', 'Tarkwa', 'Alausa', 'Aqani', 'Eleko', 'Gbaj', 'Saga', 'Dongo', 'Oko Ito', 'Ago Sasa', 'Apapa', 'Arapagi', 'Ijagba', 'Ere-Ise', 'Akere', 'Akarakumoh', 'Iganmu', 'Epe', 'Agbonyedo', 'Ajah', 'Okun Isoku', 'Igbe', 'Okun-Ajah', 'Igbogbele', 'Ajibo', 'Elachi', 'Animashawun', 'Igbopa', 'Ogogoro', 'Igbessa', 'Ewu Ebi', 'Ejirin', 'Orimedu', 'Ikosi', 'Idolosa', 'Itirin', 'Fonji', 'Appa', 'Ado Odo', 'Araromi Seaside', 'Somolu', 'Jakande', 'Ikeja', 'Mosere Ikoga', 'Abagbo', 'Otolu', 'Iworo', 'Lagos', 'Ikorodu', 'Akoka', 'Ason', 'Ketu Adieowe', 'Toto-Owu', 'Ganyingbo', 'Irede', 'Banimgbe', 'Isan Town', 'Ladugbo', 'Idimu', 'Maroko', 'Ado', 'Singiokolo Creek', 'Aiyetoto-Asogun', 'Abigi', 'Ise', 'Ibese', 'Igando Oloja', 'Maguta', 'Sunmoge', 'Anagoji', 'Oregun', 'Igbosa', 'Ilado', 'Ilubirin', 'Egan', 'Agura', 'Idaso', 'Iranla', 'Ijotan', 'Ilepa', 'Ijanikin', 'Mafoluku', 'Oruba', 'Ibon', 'Ijomu', 'Badagry', 'Ere', 'Surulere', 'Alimosho', 'Igbogbo', 'Isunba', 'Ojo', 'Agiliti', 'Ilado Ogunu', 'Igando', 'Gberigbe', 'Ikotun', 'Owo', 'Mushin', 'Ojagemo', 'Imeri', 'Oko-Abe', 'Lagos Island', 'Angorin', 'Lekki Phase II', 'Iyafin', 'Oshodi/Isolo', 'Tawpo', 'Akowonjo', 'Shangisha', 'Ojomu', 'Ologogoro', 'Ganme', 'Moba', 'Ikota', 'Ipatira', 'Alimosho', 'Tapa', 'Pasi', 'Emina', 'Ikoga', 'Oke Agba', 'Igbe Ewoliwo', 'Oshodi', 'Agaw Awusa', 'Osibadan', 'Ginti', 'Banana Island', 'Mowo', 'Imobi', 'Erekiti', 'Festac', 'Apakin', 'Alaguntan', 'Oko Orisa', 'Araromi-Tope', 'Alaiyabiagba', 'Alo', 'Wesere', 'Abule-Eko', 'Maryland', 'Ikate', 'Idole', 'Shomolu', 'Imoba', 'Eko Atlantic', 'Oke Oloku', 'Iddo', 'Henume', 'Badia East', 'Okunegun', 'Akodo', 'Mushin', 'Magodo', 'Satellite Town', 'Igando', 'Lekki Free Trade Zone', 'Igbalu', 'Ikoga', 'Alasia', 'Kosofe', 'Enuto', 'Oshosun', 'Tigara', 'Ijesha', 'Agbara', 'Mosafejaw', 'Gbodo', 'Oko Agbo', 'Oke-Lisa', 'Omu', 'Makoko', 'Isasi', 'Moyopa', 'Bode Ase', 'Akodo', 'Isekun', 'Kweme', 'Ibeju Lekki', 'Modo', 'Alaro City', 'Bariga', 'Oworonṣoki', 'Ijagemo', 'Ijeododo', 'Idoluwo', 'Iwuku', 'Oke Eletu', 'Epe', 'Idosemo', 'Isola', 'Lomiro', 'Origele', 'Tofa', 'Holy Centre Salem', 'Ipota', 'Cele', 'Alagba', 'Adougbé Gbago', 'Elesin', 'Orita-Bodun', 'Ebute-Metta', 'Ajegunle', 'Okori Egun', 'Budo', 'Apapa', 'Owoye', 'Animshaun', "Gan'ingbo", 'Kirikiri', 'Orisigun', 'Agani', 'Mosafejo', 'Iwesolu', 'Idosa', 'Iwaye', 'Bariga', 'Ito-Omu', 'Iwopin', 'Anagunji', 'Alagbede', 'Ilemere', 'Gbagada', 'Mafoluku Oshodi', 'Lakuwe', 'Isolo', 'Baiyeku', 'Okoran', 'Ago Hausa', 'Ado', 'Ìkòròdú', 'Igbin', 'Tewon', 'Victoria Garden City', 'Idi Ofe', 'Igbokin', 'Badagry', 'Awdan Iyasi', 'Agodo', 'Odogun', 'Awoyaya', 'Ogun Waterside', 'Ajao Estate', 'Surulere', 'Moba', 'Idimu', 'Mopo-Akinlade', 'Pashi', 'Iyesi', 'Seidu', 'Dosemo', 'Ibiade', 'Eti Osa', 'Fawosedi', 'Agboyi', 'Madigan', 'Ropoji', 'Atapa', 'Itere', 'Ogombo', 'Ijako', 'Ogudu', 'Igbafo', 'Lekki Phase I', 'Langbasa', 'Siriwon', 'Oriba', 'Igude', 'Soke', 'Ilado', 'Aba-Oriyanrin', 'Iwerekun', 'Ijora', 'Itoga', 'Ajebo', 'Whekan', 'Akoko', 'Ibeshe', 'Folu', 'Imare', 'Ajara', 'Ojo', 'Mopo Onijebu', 'Igboefon', 'Ori-Ita', 'Afowo Doforo', 'Sabi', 'Ide', 'Falomo', 'Ijomi', 'Ebute Oni', 'Ajeromi/Ifelodun', 'Majidun', 'Igbowore', 'Igbologun', 'Tafi', 'Old Ikawga', 'Ibasa', 'Oreta', 'Ikawga-Zebe', 'Otogun', 'Amiju', 'Olambe', 'Odogbolu', 'Isoku', 'Agbowa', 'Komi Oba', 'Abule Egba', 'Anga', 'Iropo Elefa', 'Irawo', 'Ibogun Sowunmi', 'Asa', 'Afuye', 'Afojupa', 'Layemi', 'Aganga', 'Iju', 'Igbosuru', 'Jobore, Omu Ijebu', 'Oke Ogun', 'Okun-Owa', 'Refurefu', 'Onosenlu Alako', 'Ibido, omu ijebu', 'Olokuta', 'Egbe', 'Igode', 'Ikotun', 'Asipa', 'Odo', 'Ajura', 'Ohumbe', 'Salabo', 'Remo North', 'Mosan', 'Debasco', 'Asafo', 'Ita Egbe', 'Isiwu', 'Abule Alari', 'Ododeyo', 'Iro', 'Abule Jiga', 'Sagotumo', 'Iyalade', 'Awbele', 'Idomogu', 'Otta', 'Osho', 'Ilate', 'Okere', 'Atiba', 'Araromi', 'Alagbede', 'Irete, omu ijebu', 'Ojundo', 'Orudu-Olokan', 'Omu', 'Iperu', 'Ìjẹ̀bú-Ìgbò', 'Moro Bale', 'Shagamu', 'Omole', 'Alaparu', 'Araromi Jiga', 'Ala', 'Oko-Eko', 'Isaga Abiodun', 'Orogbo-Olamodi', 'Gbokote', 'Adata', 'Obelle', 'Ebute Adewale', 'Konigbagbe', 'Awado', 'Akerun Fade', 'Oluwo', 'Isamuro', 'Ihumbaw', 'Igbokoto', 'Ilewo', 'Erinle', 'Wasimi Alafia', 'Ijebu North East', 'Isagbo', 'Olorunda Agana', 'Iganmi', 'Obafemi Owode', 'Oniorosu', 'Agosa', 'Igbo Otun', 'Olowoira', 'Ita Balogun', 'Awdaw Refurefu', 'Fowoseje', 'Ita Epini', 'Ojete', 'Imope', 'Ipaja', 'Afojupa', 'Mosan', 'Abule Alfa', 'Imule Olaore', 'Sare', 'Ososun', 'Itoikin', 'Odo', 'Kajola', 'Ile Igboro', 'Ilishan', 'Ososa', 'Oke Odan', 'Irogun Akere', 'Akerun Ajibode', 'Igborodo', 'Imule Amos', 'Agboju', 'Igaun', 'Oju Ota', 'Abule Awba', 'Odan Egun', 'Ìjẹ̀bú Òde', 'Ikenne', 'Ifo', 'Ishawo', 'Alapa', 'Igbaga', 'Imodi-Ijasi', 'Eredo', 'Osese', 'Aguda', 'Apoje-Orile', 'Olopa', 'Logbara', 'Modogan', 'Gboga', 'Asipa', 'Itamaga', 'Ashipa', 'Iledu', 'Gbamu Gbamu', 'Owode', 'Araromi Owode', 'Aiyepe', 'Ijebu-Ode', 'Refurefu Egbebi', 'Ebute Igbawraw', 'Otungbade', 'Ijebu East', 'Àgọ́-Ìwòyè', 'Moba', 'Oba', 'Agege', 'Magado', 'Erimi Oguntade', 'Faleke', 'Abule Idi', 'Isiwo', 'Itapanpa', 'Yewa Mata', 'Imuwen', 'Someke', 'Adefisan', 'Yema Mata', 'Ijaiye', 'Orugbo', 'Papa Alanto', 'Iyedi Balogun', 'Sekungba', 'Pakuro', 'Abule Egba', 'Oke-Odo', 'Isaga Latunji', 'Olute', 'Ilashe', 'Orisada', 'Ajegunle', 'Ilaro', 'Awa', 'Idiroko', 'Alakuko', 'Adeyokun', 'Ogun', 'Owode', 'Agbede', 'Etebu', 'Ita Okere', 'Oloki', 'Ipokia', 'Mowe', 'Ariwo', 'Oloruko Alfa', 'Idologun', 'Sagamu', 'Oke Iwo', 'Oko-Makun', 'Ibido', 'Awdan Ibola', 'Agbile', 'Ibayun', 'Oke Imale', 'Esuru', 'Kori', 'Idoforo', 'Ijere-Agelette', 'Tidenuren', 'Oloparun', 'Mokoloki', 'Agege', 'Isheri', 'Sepeti', 'Ikolaja', 'Idi-Iroko', 'Eri Oburo', 'Itoro', 'Asa-Oke-Oko', 'Jonah', 'Ogunsolu', 'Iropo', 'Imede', 'Alapoti', 'Iiashe', 'Kakanfo', 'Ita Ege', 'Isola', 'Pokoto', 'Ajebo', 'Odogbolu', 'Dologu', 'Ogbere', 'Imagbon', 'Orobiyi', 'Ebute Igboro', 'Asese', 'Ifako/Ijaye', 'Ilogbo', 'Odo Siwaren', 'Elemuren', 'Aiyetoro', 'Eriwe', 'Arogun', 'Ajibabi', 'Ibefon', 'Tidowo', 'Agbaku', 'Sojuolu', 'Oniyangi', 'Olorunpodo', 'Onibudo', 'Imule', 'Ode-Remo', 'Imomo', 'Ajibode', 'Agunfoye', 'Onigbongbo', 'Sefu', 'Ologiri', 'Eleworo', 'Mawshaw', 'Igbobe', 'Fade', 'Abeku', 'Idawgo', 'Maweje', 'Parafa', 'Ewekoro', 'Yewa South', 'Igbeme', 'Eriki-Odofin', 'Imota', 'Iledun', 'Ajillete', 'Matogbun', 'Ado Odo/Ota', 'Idi', 'Ijado', 'Ipokia', 'Oja Odan', 'Ijado Agbo', 'Ibafo', 'Gbokoto', 'Odo Lagaye', 'Ijesha-Ijebu', 'Ita Egge', 'Isale', 'Akopin', 'Koko', 'Ibeshe', 'Jibowu', 'Fagbohun', 'Maya', 'Iweke', 'Otere', 'Imepe', 'Agbole', 'Ishara', 'Ogba', 'Lemomu', 'Mede', 'Bale', 'Kajola', 'Odo Ragun', 'Ikene', 'Eiyekose', 'Igbo Ale', 'Otta', 'Oloji', 'Akila', 'Aketu-Oja', 'Ihunsa', 'Ofada', 'Magboro', 'Ileayo', 'Obanla', 'Ketu', 'Ewekoro', 'Gberedu', 'Imule Agunbiade', 'Idode', 'Idolubo', 'Ifo', 'Oke', 'Ile Igbawraw', 'Kobejo', 'Ajebo', 'Alaran', 'Ogunrin Eletu']
Subtypes for NG: ['locality', 'macrohood', 'county', 'neighborhood', 'region', 'microhood']
Admin levels for NG: [None, 2, 1]
  ID=402559bc-af1e-4100-8847-68bba035ff1e, name=Oluke, subtype=locality, admin_level=nan, region=NG-OG
  ID=8b13e0db-c4bc-4b11-bd5c-18eb3bc93154, name=Ebute Lekki, subtype=locality, admin_level=nan, region=NG-LA
  ID=9b8618f2-3b98-4b58-82fb-58763f9978a4, name=Akawkaw, subtype=locality, admin_level=nan, region=NG-LA
  ID=7c96a300-682a-4fd1-9d7b-2b6e30c76453, name=Ijaiye, subtype=locality, admin_level=nan, region=NG-OG
  ID=767566f6-f366-47c4-b775-36394e7603af, name=Ilera, subtype=locality, admin_level=nan, region=NG-OG
  ID=d23eb8fe-e30a-48c9-85ca-a3beae30686d, name=Aba-Onigbagbo, subtype=locality, admin_level=nan, region=NG-LA
  ID=43457571-904c-4c84-b5a5-04d8a48838fd, name=Ologode, subtype=locality, admin_level=nan, region=NG-LA
  ID=e86e95ce-ca20-45f8-91dd-5f1902a4a1f9, name=Ijede, subtype=locality, admin_level=nan, region=NG-LA
  ID=399ef48d-f75c-4ef4-8a25-7b5f15062bdf, name=Erukan, subtype=macrohood, admin_level=nan, region=NG-LA
  ID=8f611dbb-f498-4b5d-948a-c0f7124cd823, name=Akete, subtype=locality, admin_level=nan, region=NG-OG
  ID=12d2975b-fcf4-4eaa-ab6b-4bc7235028cf, name=Ashipa, subtype=locality, admin_level=nan, region=NG-LA
  ID=7dfb1c74-5722-4373-bf3e-45e3e85a1b1a, name=Abegede, subtype=locality, admin_level=nan, region=NG-LA
  ID=98d9916a-e544-4e2a-b451-310f0874c1f1, name=Obada, subtype=locality, admin_level=nan, region=NG-LA
  ID=4737e20d-3318-469d-860b-02d20626b731, name=Ikoga, subtype=locality, admin_level=nan, region=NG-OG
  ID=eb35e476-502e-4224-937b-7040dd25ddd7, name=Ladeba, subtype=locality, admin_level=nan, region=NG-LA
  ID=902166cd-3918-4b7d-b342-eac736aa984e, name=Lagos, subtype=locality, admin_level=nan, region=NG-LA
  ID=16d790c7-3367-4a7f-9a89-7b15ae3cab96, name=Ladeda Ebute, subtype=locality, admin_level=nan, region=NG-LA
  ID=470508a5-c037-40f0-bcc7-aac8c7d0590c, name=Agaja, subtype=locality, admin_level=nan, region=NG-LA
  ID=442fc556-cb61-402c-a59c-fbd8f5548505, name=Igbede, subtype=locality, admin_level=nan, region=NG-LA
  ID=d7d73751-79a3-491e-9c6b-b3a4d4cad03e, name=Tinubu, subtype=macrohood, admin_level=nan, region=NG-LA
  ID=7583d723-71b9-4f18-9fff-bff98df8322d, name=Amuwo Odofin, subtype=county, admin_level=2.0, region=NG-LA
  ID=73b567d8-1f96-44f4-b4e2-b82622d82b15, name=Sangotedo, subtype=macrohood, admin_level=nan, region=NG-LA
  ID=e20e8e53-4a5f-489f-a1a1-2e9ae52a8319, name=Mawun Isale, subtype=locality, admin_level=nan, region=NG-OG
  ID=feb0815a-f3ac-48d1-8abe-32bdf34d0620, name=Ibeju, subtype=macrohood, admin_level=nan, region=NG-LA
  ID=792ee2b7-1dee-48a4-982e-8bc43b9eefc5, name=Morekete, subtype=locality, admin_level=nan, region=NG-LA
  ID=2936f2c7-65ad-4dbd-887a-7cf0a2c3c7fd, name=Badore, subtype=macrohood, admin_level=nan, region=NG-LA
  ID=2f45f6ca-8df7-456b-92cc-9033b2cc7205, name=Ilashe, subtype=locality, admin_level=nan, region=NG-LA
  ID=0da79cea-8e85-401f-bb82-3ca93173091b, name=Ijegun, subtype=locality, admin_level=nan, region=NG-LA
  ID=b242bfb6-6493-4361-ad76-773b33f000a8, name=Ijofin, subtype=locality, admin_level=nan, region=NG-OG
  ID=e5de1f6c-5d25-4ea6-b4d3-8b7ea6698922, name=Odo-Mola, subtype=locality, admin_level=nan, region=NG-LA
  ID=e49d4398-8b5a-4222-b38d-74632f4be234, name=Ebute Ikorodu, subtype=locality, admin_level=nan, region=NG-LA
  ID=443d05f4-37ec-4d34-b571-9b727b2cf39e, name=Owode Onirin, subtype=neighborhood, admin_level=nan, region=NG-LA
  ID=4c8fdba6-595b-4c9b-a46e-05fde3155efa, name=Ilushin, subtype=locality, admin_level=nan, region=NG-OG
  ID=55576faf-d11e-4448-866f-d3dd4dc3e84d, name=Araromi-Obu, subtype=locality, admin_level=nan, region=NG-ON
  ID=7f67a43a-5daf-44a6-af25-9fbebd07ee1e, name=Victoria Island, subtype=macrohood, admin_level=nan, region=NG-LA
  ID=87f6a76e-51ac-4a6b-b2bc-e6e2a285bb1b, name=Ago Palace, subtype=macrohood, admin_level=nan, region=NG-LA
  ID=ab09eec2-9313-40bd-b05f-09ae436a0f7a, name=Ilupeju, subtype=macrohood, admin_level=nan, region=NG-LA
  ID=8c391986-583e-4f3a-b540-dfd244dbc183, name=Onigbongbo, subtype=locality, admin_level=nan, region=NG-LA
  ID=76955f8e-9803-41c8-bc65-dd6f864a5873, name=Ilagbo, subtype=locality, admin_level=nan, region=NG-LA
  ID=af88a8c6-f84f-4629-a25b-1e79a9ccbc3d, name=Ikeja, subtype=county, admin_level=2.0, region=NG-LA
  ID=b0754b52-6bc4-4a22-a1d7-8ecacc3d0aab, name=Ilumawfin, subtype=locality, admin_level=nan, region=NG-LA
  ID=4f1aabae-5a08-4179-bdfb-f0e9d71d2ca5, name=Lagos Mainland, subtype=county, admin_level=2.0, region=NG-LA
  ID=77250d96-c731-4645-b992-22278b3e6425, name=Poka, subtype=locality, admin_level=nan, region=NG-LA
  ID=d91bb622-40b8-401f-a8bb-4514b463adbf, name=Tarkwa, subtype=macrohood, admin_level=nan, region=NG-LA
  ID=131b8a30-a646-45f6-b4be-57eeb7ea0f83, name=Alausa, subtype=locality, admin_level=nan, region=NG-LA
  ID=aca546d2-cf04-48d2-96f4-6abd344463a9, name=Aqani, subtype=locality, admin_level=nan, region=NG-LA
  ID=2ba8fb33-908b-42b6-8a51-1b80a7c6ca52, name=Eleko, subtype=macrohood, admin_level=nan, region=NG-LA
  ID=90703861-5059-4f91-9e29-d4336c31398b, name=Gbaj, subtype=locality, admin_level=nan, region=NG-LA
  ID=7a98beb5-504f-44ec-a86f-747e1d4b4e33, name=Saga, subtype=locality, admin_level=nan, region=NG-LA
  ID=604f4270-56b2-4df7-a8f1-5022ac7c2913, name=Dongo, subtype=locality, admin_level=nan, region=NG-LA
  ID=ca183980-db7c-4d3f-9078-acd7cc7fd012, name=Oko Ito, subtype=locality, admin_level=nan, region=NG-LA
  ID=0534e9ca-7e7f-4ddc-8305-7522a6694e9a, name=Ago Sasa, subtype=locality, admin_level=nan, region=NG-OG
  ID=dc5547ff-cb10-4818-8e9b-363c14daa13b, name=Apapa, subtype=county, admin_level=2.0, region=NG-LA
  ID=04a06d5d-dfb1-424d-ac67-7cdce0d65ad7, name=Arapagi, subtype=locality, admin_level=nan, region=NG-LA
  ID=e68e4e33-8d18-4d2c-bb4e-0a671b60f486, name=Ijagba, subtype=locality, admin_level=nan, region=NG-OG
  ID=b6d384bd-1b06-4eac-bf6c-474010a33b73, name=Ere-Ise, subtype=locality, admin_level=nan, region=NG-LA
  ID=9156d2b5-7905-4f94-a1d1-6ff7e92b3c6d, name=Akere, subtype=locality, admin_level=nan, region=NG-OG
  ID=3c9c2b79-8b26-4745-a385-ab2ca1e4da26, name=Akarakumoh, subtype=locality, admin_level=nan, region=NG-LA
  ID=6f5eb05e-86b5-4020-ad1e-2e68ee880492, name=Iganmu, subtype=locality, admin_level=nan, region=NG-LA
  ID=fb818c09-f201-4a72-9c3e-aeee73f4e530, name=Epe, subtype=locality, admin_level=nan, region=NG-LA
  ID=c4966ce6-13b8-4d7a-86ac-50adab1d82ed, name=Agbonyedo, subtype=locality, admin_level=nan, region=NG-OG
  ID=9234ded0-b6bc-4793-9e7a-57d3db77a13b, name=Ajah, subtype=macrohood, admin_level=nan, region=NG-LA
  ID=45aa4ba9-9405-47d2-ac1f-cba96bc1b96b, name=Okun Isoku, subtype=locality, admin_level=nan, region=NG-OG
  ID=0a7ca99f-391d-4a8f-9d50-724a1df62751, name=Igbe, subtype=locality, admin_level=nan, region=NG-LA
  ID=62b4941c-ac52-44e5-9cce-7326b2a2841f, name=Okun-Ajah, subtype=locality, admin_level=nan, region=NG-LA
  ID=579a00f6-255c-46f4-95e8-5a952d43523f, name=Igbogbele, subtype=locality, admin_level=nan, region=NG-LA
  ID=9e15f466-f5a1-4436-849f-89c461e812d1, name=Ajibo, subtype=locality, admin_level=nan, region=NG-LA
  ID=1a2c0489-4076-434e-a2d2-0ca1843d8
[... truncated ...]=locality, admin_level=nan, region=NG-OG
  ID=b3e7cd4c-aca6-4a12-a842-900cd17ebfe9, name=Oloruko Alfa, subtype=locality, admin_level=nan, region=NG-OG
  ID=b3d2b5a1-ac8c-4eb6-a493-0d0eea99c9aa, name=Idologun, subtype=locality, admin_level=nan, region=NG-OG
  ID=61051cd2-88e6-4f78-aae6-17952b3d4916, name=Sagamu, subtype=locality, admin_level=nan, region=NG-OG
  ID=09d021e2-b63c-4cb9-9061-df2612443e05, name=Oke Iwo, subtype=locality, admin_level=nan, region=NG-OG
  ID=05868f23-0bb1-4b1e-9f68-22033a5611b9, name=Oko-Makun, subtype=locality, admin_level=nan, region=NG-OG
  ID=99a3ae92-92e0-4cb7-9985-85c53f0aa89c, name=Ibido, subtype=locality, admin_level=nan, region=NG-OG
  ID=e176f5bf-c55f-4da1-af6a-5d6a7f8c1bb8, name=Awdan Ibola, subtype=locality, admin_level=nan, region=NG-OG
  ID=0076af0e-ce5b-4d82-8545-5c42416fdd32, name=Agbile, subtype=locality, admin_level=nan, region=NG-OG
  ID=a020162c-1518-484e-8d63-d9e4ddc08cd3, name=Ibayun, subtype=locality, admin_level=nan, region=NG-OG
  ID=5d00d5ff-e2b4-4135-b547-15cd6e6e0b7a, name=Oke Imale, subtype=locality, admin_level=nan, region=NG-OG
  ID=6a44a269-e447-47f2-9afe-8592eba5e362, name=Esuru, subtype=locality, admin_level=nan, region=NG-OG
  ID=de0cbef4-23f0-4124-9b11-2158315e0878, name=Kori, subtype=locality, admin_level=nan, region=NG-OG
  ID=411203a1-0ffb-4b55-97e3-d73b2159e732, name=Idoforo, subtype=locality, admin_level=nan, region=NG-OG
  ID=a49482d8-d490-42b3-a211-c2d3db6f3425, name=Ijere-Agelette, subtype=locality, admin_level=nan, region=NG-OG
  ID=15cf1cf7-1774-4c43-a39a-96898d045175, name=Tidenuren, subtype=locality, admin_level=nan, region=NG-OG
  ID=c1cf87c5-9cef-49ce-aa6b-84224ba49e88, name=Oloparun, subtype=locality, admin_level=nan, region=NG-OG
  ID=7c83a067-40bb-43b7-850a-b2ccb6dd165e, name=Mokoloki, subtype=locality, admin_level=nan, region=NG-OG
  ID=24e04d44-9e6e-47ba-a71c-737793381785, name=Agege, subtype=county, admin_level=2.0, region=NG-LA
  ID=740b4bb2-c6bf-48b6-a009-2b21ba9b8855, name=Isheri, subtype=locality, admin_level=nan, region=NG-OG
  ID=8bba7eed-d682-402b-8360-973b451466c1, name=Sepeti, subtype=locality, admin_level=nan, region=NG-OG
  ID=685944c4-bfc2-42d8-a25d-50c1babacd9d, name=Ikolaja, subtype=locality, admin_level=nan, region=NG-OG
  ID=6dd28fc0-d1ea-4aa7-894e-303e8d7ff7c3, name=Idi-Iroko, subtype=locality, admin_level=nan, region=NG-OG
  ID=9c233223-5a9d-47b6-afe1-ecc1225a4a67, name=Eri Oburo, subtype=locality, admin_level=nan, region=NG-OG
  ID=a76654b0-7eee-460c-9140-91651c8b808d, name=Itoro, subtype=locality, admin_level=nan, region=NG-OG
  ID=a1b1cbd9-7740-4fbb-8989-9935b209ab95, name=Asa-Oke-Oko, subtype=locality, admin_level=nan, region=NG-OG
  ID=2bbd5c23-4398-453d-917e-2598870bad26, name=Jonah, subtype=locality, admin_level=nan, region=NG-OG
  ID=2e4e546e-95b6-4e41-9c70-f54cec04bbf2, name=Ogunsolu, subtype=locality, admin_level=nan, region=NG-OG
  ID=c75f57b2-fec9-40e9-9bd9-3ca6bf18ef86, name=Iropo, subtype=locality, admin_level=nan, region=NG-OG
  ID=b416b50b-bde7-48b0-a4c3-c6a59b3d175f, name=Imede, subtype=locality, admin_level=nan, region=NG-OG
  ID=53b6508b-c105-42cb-a478-0a4c5abbfc91, name=Alapoti, subtype=locality, admin_level=nan, region=NG-OG
  ID=7ddead3b-df22-45f6-a52e-3b60603eb63f, name=Iiashe, subtype=locality, admin_level=nan, region=NG-OG
  ID=80996cd6-86be-44f7-8ab1-ad72662883b8, name=Kakanfo, subtype=locality, admin_level=nan, region=NG-OG
  ID=67a2414f-0799-4d75-93d1-585d5426a197, name=Ita Ege, subtype=locality, admin_level=nan, region=NG-OG
  ID=baad88cd-8af0-47f4-abf2-715955d50655, name=Isola, subtype=locality, admin_level=nan, region=NG-OG
  ID=ff2cadfa-ea92-4a0a-a6b7-31ae05bf9363, name=Pokoto, subtype=locality, admin_level=nan, region=NG-OG
  ID=ac76ddd8-f790-491e-84de-338a7f98aa71, name=Ajebo, subtype=locality, admin_level=nan, region=NG-LA
  ID=84af59ec-885e-41b8-81a3-1212ac452974, name=Odogbolu, subtype=locality, admin_level=nan, region=NG-OG
  ID=7c2b7e10-0f93-41fd-aebb-e1b66e2d573f, name=Dologu, subtype=locality, admin_level=nan, region=NG-OG
  ID=534269a7-f071-4153-9d02-32399fb02954, name=Ogbere, subtype=locality, admin_level=nan, region=NG-OG
  ID=134bfe8a-d68a-46d7-bbe6-6aa30379a049, name=Imagbon, subtype=locality, admin_level=nan, region=NG-OG
  ID=495e86b6-4ea2-44a0-80fa-d54d46c183de, name=Orobiyi, subtype=locality, admin_level=nan, region=NG-OG
  ID=8d41d9d0-38d3-4934-869d-96320ad53024, name=Ebute Igboro, subtype=locality, admin_level=nan, region=NG-OG
  ID=6a41e2b1-532e-4649-87b3-4051a3e8d926, name=Asese, subtype=locality, admin_level=nan, region=NG-OG
  ID=a9df031a-eaa0-4b74-b42c-5bf303feb0e1, name=Ifako/Ijaye, subtype=county, admin_level=2.0, region=NG-LA
  ID=16d41d58-a156-4eac-ad1c-a48929afdd2c, name=Ilogbo, subtype=locality, admin_level=nan, region=NG-OG
  ID=eb2f40c1-fce9-4877-8be5-5beab9680daa, name=Odo Siwaren, subtype=locality, admin_level=nan, region=NG-OG
  ID=cdc2c57d-b592-4c41-a167-f67a44f498d0, name=Elemuren, subtype=locality, admin_level=nan, region=NG-OG
  ID=78e954f2-5e46-462e-99b6-7ee1c451c1a0, name=Aiyetoro, subtype=locality, admin_level=nan, region=NG-OG
  ID=c44c1f89-87d9-426e-9bc6-0c6e8879a18c, name=Eriwe, subtype=locality, admin_level=nan, region=NG-OG
  ID=13009a56-703b-476a-a83b-453e1f7d3610, name=Arogun, subtype=locality, admin_level=nan, region=NG-OG
  ID=a96dfd29-72d7-47e7-b9e2-4457ccbe88f9, name=Ajibabi, subtype=locality, admin_level=nan, region=NG-OG
  ID=78f6e560-cfd9-4c88-a7ee-58249101b015, name=Ibefon, subtype=locality, admin_level=nan, region=NG-OG
  ID=a76520bd-9e24-4975-a106-b86bf3127a39, name=Tidowo, subtype=locality, admin_level=nan, region=NG-OG
  ID=6ae77845-3231-468e-a2a3-cba8a0fb6152, name=Agbaku, subtype=locality, admin_level=nan, region=NG-LA
  ID=6859191f-a19e-4d59-807d-a3948f3e6d38, name=Sojuolu, subtype=locality, admin_level=nan, region=NG-OG
  ID=3b5b86f1-e254-41f0-b0eb-dbe72e326960, name=Oniyangi, subtype=locality, admin_level=nan, region=NG-OG
  ID=2e066c1a-7373-4b51-9b6d-fb3ca00e22a3, name=Olorunpodo, subtype=locality, admin_level=nan, region=NG-OG
  ID=62954ebe-cd42-4c1a-8d21-17c1643d2b26, name=Onibudo, subtype=locality, admin_level=nan, region=NG-OG
  ID=f122f853-ad27-44bd-8ec6-b79ffa5d706b, name=Imule, subtype=locality, admin_level=nan, region=NG-OG
  ID=41b4f46c-45bd-441e-aff4-e2b1bfdc8abf, name=Ode-Remo, subtype=locality, admin_level=nan, region=NG-OG
  ID=72cc76ff-1b43-479c-8b66-45afec70721c, name=Imomo, subtype=locality, admin_level=nan, region=NG-OG
  ID=531217c8-16ad-4bb2-b163-021057b73cef, name=Ajibode, subtype=locality, admin_level=nan, region=NG-OG
  ID=5f3864b1-90a0-4048-9c89-bff48973b2b3, name=Agunfoye, subtype=locality, admin_level=nan, region=NG-LA
  ID=aa4ada0b-45e0-40c6-9c77-bdf474bf97f8, name=Onigbongbo, subtype=locality, admin_level=nan, region=NG-OG
  ID=ea50c791-9b23-4be9-8336-58b2726efa08, name=Sefu, subtype=locality, admin_level=nan, region=NG-OG
  ID=1310f606-be2d-4761-ac3f-b5c515d6c3f7, name=Ologiri, subtype=locality, admin_level=nan, region=NG-OG
  ID=36e2ab06-759c-46bb-924d-5912d1d3a752, name=Eleworo, subtype=locality, admin_level=nan, region=NG-OG
  ID=87b1994a-b0a1-4fe9-aa86-4be6af2600c7, name=Mawshaw, subtype=locality, admin_level=nan, region=NG-OG
  ID=6059ef2f-5dc7-4792-a4fd-8feca9c799d3, name=Igbobe, subtype=locality, admin_level=nan, region=NG-OG
  ID=ac554df6-58e3-4dc1-9761-7d5179b1e01e, name=Fade, subtype=locality, admin_level=nan, region=NG-OG
  ID=d74eed68-1902-4dbf-9964-f5d4862231d6, name=Abeku, subtype=locality, admin_level=nan, region=NG-OG
  ID=8d11ebb5-89a2-418f-8402-8055c4a578b0, name=Idawgo, subtype=locality, admin_level=nan, region=NG-OG
  ID=7135ed8b-d1fc-4766-b4ea-93f6cc2b61ba, name=Maweje, subtype=locality, admin_level=nan, region=NG-OG
  ID=bdfd256b-7869-4f19-9f43-1c671b9f34cc, name=Parafa, subtype=locality, admin_level=nan, region=NG-LA
  ID=921da412-7134-4d63-bbf6-679b4862a778, name=Ewekoro, subtype=county, admin_level=2.0, region=NG-OG
  ID=d6357489-86e9-4e24-ba64-2d3bda2d31a3, name=Yewa South, subtype=county, admin_level=2.0, region=NG-OG
  ID=e351d582-2c7f-4ee7-8418-bde0be11a181, name=Igbeme, subtype=locality, admin_level=nan, region=NG-OG
  ID=8f381b24-9cf6-4921-8b34-a7aaa5b937c7, name=Eriki-Odofin, subtype=locality, admin_level=nan, region=NG-OG
  ID=1c10f8b9-a1b5-4ea2-8b64-65ff8b1eb244, name=Imota, subtype=locality, admin_level=nan, region=NG-LA
  ID=e6636317-d687-465e-a5f7-2555a6de077c, name=Iledun, subtype=locality, admin_level=nan, region=NG-OG
  ID=e22904b9-d153-4c41-a224-dd5203a3264f, name=Ajillete, subtype=locality, admin_level=nan, region=NG-OG
  ID=98255967-1aba-4516-b926-7df6d1f3d98a, name=Matogbun, subtype=locality, admin_level=nan, region=NG-OG
  ID=14cdd032-7c30-44aa-beee-476b4d62184e, name=Ado Odo/Ota, subtype=county, admin_level=2.0, region=NG-OG
  ID=ddb648be-86cf-46c8-bee7-0a64e5ab60a4, name=Idi, subtype=locality, admin_level=nan, region=NG-OG
  ID=32c8e1af-77e3-4a7a-b3f1-81913f6787d7, name=Ijado, subtype=locality, admin_level=nan, region=NG-OG
  ID=199f6f87-94c7-40e8-ba34-662f345e5794, name=Ipokia, subtype=county, admin_level=2.0, region=NG-OG
  ID=0b3cea79-50ae-4f6b-bf30-7304e4414ef1, name=Oja Odan, subtype=locality, admin_level=nan, region=NG-OG
  ID=71286847-59bb-4b74-9e7c-9b94a639b5ad, name=Ijado Agbo, subtype=locality, admin_level=nan, region=NG-OG
  ID=9585bf1a-7f5d-4718-a812-303aa9409f02, name=Ibafo, subtype=locality, admin_level=nan, region=NG-OG
  ID=1ecd0a16-56f4-4ac9-b6bf-25e583e012c1, name=Gbokoto, subtype=locality, admin_level=nan, region=NG-OG
  ID=e74f9b06-d927-4f34-8ed6-240e2e86d15d, name=Odo Lagaye, subtype=locality, admin_level=nan, region=NG-OG
  ID=542106b9-7568-49f5-81df-a8d17b0379ca, name=Ijesha-Ijebu, subtype=locality, admin_level=nan, region=NG-OG
  ID=570542ec-50ec-482a-8397-e42b945008f7, name=Ita Egge, subtype=locality, admin_level=nan, region=NG-OG
  ID=ec311b38-d208-498d-ae16-516ffa4b1b00, name=Isale, subtype=locality, admin_level=nan, region=NG-OG
  ID=e4d4de8d-19b8-4626-8c9c-7b1f57a0fb84, name=Akopin, subtype=locality, admin_level=nan, region=NG-OG
  ID=cdde4147-0257-4bc3-93c9-921f96c0a66f, name=Koko, subtype=locality, admin_level=nan, region=NG-OG
  ID=839074b3-2aec-439a-994e-a503b727ca28, name=Ibeshe, subtype=locality, admin_level=nan, region=NG-OG
  ID=0447038a-2af3-4835-a4eb-b5681bfd77c8, name=Jibowu, subtype=locality, admin_level=nan, region=NG-LA
  ID=76a4d683-9605-4898-b85e-c9be01394d26, name=Fagbohun, subtype=locality, admin_level=nan, region=NG-OG
  ID=5acd93fa-4d91-4d9f-9b11-b51edd07bb6a, name=Maya, subtype=locality, admin_level=nan, region=NG-LA
  ID=491ff64d-4518-4f82-82b7-d489c7bfc863, name=Iweke, subtype=locality, admin_level=nan, region=NG-OG
  ID=bd8213d9-d8b6-4835-abe8-6074f5c934cc, name=Otere, subtype=locality, admin_level=nan, region=NG-OG
  ID=9d322323-ec56-41d7-a550-63557e051a76, name=Imepe, subtype=locality, admin_level=nan, region=NG-OG
  ID=053884ba-c60c-4a1d-a1c3-19212983b080, name=Agbole, subtype=locality, admin_level=nan, region=NG-LA
  ID=9e9fc65c-8401-4171-aa47-9f7241e81cd5, name=Ishara, subtype=locality, admin_level=nan, region=NG-OG
  ID=7ba10f0b-4425-49d9-ab57-922c7da0ad77, name=Ogba, subtype=macrohood, admin_level=nan, region=NG-LA
  ID=7e686364-3af1-4750-9812-7267391489ee, name=Lemomu, subtype=locality, admin_level=nan, region=NG-OG
  ID=b60e2755-c0d5-4872-b849-a28863a45a5e, name=Mede, subtype=locality, admin_level=nan, region=NG-OG
  ID=9a8609ee-f864-4fd1-9ec0-1f984e5fdba3, name=Bale, subtype=locality, admin_level=nan, region=NG-OG
  ID=45ba2e35-7852-4da4-9ded-7402bd73959c, name=Kajola, subtype=locality, admin_level=nan, region=NG-OG
  ID=003b5980-91c1-49ea-978a-e0c96d4e9917, name=Odo Ragun, subtype=locality, admin_level=nan, region=NG-LA
  ID=3df5da58-e160-4116-ac2a-7d03cc4b1b41, name=Ikene, subtype=locality, admin_level=nan, region=NG-OG
  ID=eb3fbd73-5920-485f-9b5c-f9684351a686, name=Eiyekose, subtype=locality, admin_level=nan, region=NG-OG
  ID=92d2f766-db34-4c5f-bd3d-7da24d4fdc48, name=Igbo Ale, subtype=locality, admin_level=nan, region=NG-OG
  ID=3b344b35-fc19-487a-8884-21cbef315256, name=Otta, subtype=locality, admin_level=nan, region=NG-OG
  ID=51ab2858-1a9d-43d5-905e-4f1319d27787, name=Oloji, subtype=locality, admin_level=nan, region=NG-OG
  ID=ebebf7f5-7062-4f74-a1c7-0f803afc7ae4, name=Akila, subtype=locality, admin_level=nan, region=NG-OG
  ID=891c1efa-5047-447f-b70c-7a89a5a90690, name=Aketu-Oja, subtype=locality, admin_level=nan, region=NG-OG
  ID=9f6d5264-d086-4320-8e07-94a13c2cc585, name=Ihunsa, subtype=locality, admin_level=nan, region=NG-OG
  ID=e073423f-e42e-4c07-8e25-c2d26205e487, name=Ofada, subtype=locality, admin_level=nan, region=NG-OG
  ID=dd85fe4c-dc21-40ae-9c43-391d74a46840, name=Magboro, subtype=locality, admin_level=nan, region=NG-OG
  ID=9391e91e-0348-4192-896a-cea6e9d563b8, name=Ileayo, subtype=locality, admin_level=nan, region=NG-OG
  ID=1289b2fb-4e54-434e-bbe0-07712f920fc4, name=Obanla, subtype=locality, admin_level=nan, region=NG-OG
  ID=ce36fc78-73c0-4871-8210-42f9170252f6, name=Ketu, subtype=locality, admin_level=nan, region=NG-LA
  ID=0d2169e3-5818-4c68-8048-fa863ceb0d76, name=Ewekoro, subtype=locality, admin_level=nan, region=NG-OG
  ID=49991c40-1def-49e5-af1a-9b3f60a77018, name=Gberedu, subtype=locality, admin_level=nan, region=NG-OG
  ID=46b4cb68-e46a-4d86-a4c5-1b74d7436c83, name=Imule Agunbiade, subtype=locality, admin_level=nan, region=NG-OG
  ID=cb9f8148-acb3-4dd5-940f-fee02f679991, name=Idode, subtype=locality, admin_level=nan, region=NG-OG
  ID=7f62899a-aaec-47f0-a5f7-71fcdbbe4c4b, name=Idolubo, subtype=locality, admin_level=nan, region=NG-OG
  ID=bf72a742-460e-40d8-b67e-5cdcc75eeec9, name=Ifo, subtype=county, admin_level=2.0, region=NG-OG
  ID=6fa0740c-ff41-4cf9-8a43-2349a76ffbe9, name=Oke, subtype=locality, admin_level=nan, region=NG-LA
  ID=b7b7070e-511f-438d-8b57-ba836f7437cd, name=Ile Igbawraw, subtype=locality, admin_level=nan, region=NG-OG
  ID=baeca2e0-e7a8-45d2-a433-37c5bf984124, name=Kobejo, subtype=locality, admin_level=nan, region=NG-OG
  ID=00f31f5d-c00b-48ef-a8fe-a5baa67bf2d9, name=Ajebo, subtype=locality, admin_level=nan, region=NG-LA
  ID=f596faea-e32a-44b6-bf58-13d76e02e75e, name=Alaran, subtype=locality, admin_level=nan, region=NG-OG
  ID=33205b90-4ef9-4116-a8af-b719b7bddf9c, name=Ogunrin Eletu, subtype=locality, admin_level=nan, region=NG-OG

--- Records with 'Lagos' in name (4) ---
  Index 35: {'id': '902166cd-3918-4b7d-b342-eac736aa984e', 'country': 'NG', 'subtype': 'locality', 'admin_level': None, 'class': None, 'region': 'NG-LA'}, name=Lagos
  Index 94: {'id': '4f1aabae-5a08-4179-bdfb-f0e9d71d2ca5', 'country': 'NG', 'subtype': 'county', 'admin_level': 2, 'class': None, 'region': 'NG-LA'}, name=Lagos Mainland
  Index 217: {'id': '246ecf78-8622-4d7d-8d12-102e73a67cda', 'country': 'NG', 'subtype': 'region', 'admin_level': 1, 'class': None, 'region': 'NG-LA'}, name=Lagos
  Index 311: {'id': '65a83885-eae9-4a94-8663-f6917cc9a04e', 'country': 'NG', 'subtype': 'county', 'admin_level': 2, 'class': None, 'region': 'NG-LA'}, name=Lagos Island
2:05
Now I understand the data structure. Let me summarize what I've found:

1. **Region** `NG-LA` = Lagos State (admin_level=1, subtype='region')
2. **Counties** (admin_level=2, subtype='county') in `NG-LA` = Lagos LGAs! Let me list them:
   - Amuwo Odofin
   - Ikeja
   - Lagos Mainland
   - Apapa
   - Agege
   - Ifako/Ijaye
   - Lagos Island
   - And there should be more...

Let me get the full list of counties (LGAs) in Lagos region.
2:05
2:05
Write /work/lgas.py
content · 46 lines · py
#!/usr/bin/env python3
"""Get all Lagos LGAs (counties) from Overture divisions."""

from overturemaps.core import record_batch_reader
import pyarrow as pa
import pyarrow.compute as pc

bbox = (2.5, 6.0, 4.5, 7.0)

reader = record_batch_reader("division", bbox=bbox)
if reader is None:
    print("No division reader returned")
    exit(1)

batches = []
while True:
    try:
        b = reader.read_next_batch()
        batches.append(b)
    except StopAsyncIteration:
        break
    except Exception:
        break

table = pa.Table.from_batches(batches)
print(f"Total division records: {len(table)}")

# Filter to Lagos region (NG-LA), subtype county (admin_level 2)
mask_lagos = pc.equal(table.column("region"), "NG-LA")
lagos_table = table.filter(mask_lagos)
print(f"Lagos region records: {len(lagos_table)}")

mask_county = pc.equal(lagos_table.column("subtype"), "county")
counties = lagos_table.filter(mask_county)
print(f"Lagos LGAs (counties): {len(counties)}")

# Get names
names_col = counties.column("names")
prim_names = pc.struct_field(names_col, "primary")
names_list = prim_names.to_pylist()
print(f"\nLGA names: {sorted(names_list)}")

# Also get IDs for spatial join
ids = counties.column("id").to_pylist()
for n, i in sorted(zip(names_list, ids)):
    print(f"  {n}: {i}")
Wrote /work/lgas.py (1293 chars).
2:07
Bash cd /work && python lgas.py 2>&1
Total division records: 1406
Lagos region records: 299
Lagos LGAs (counties): 20

LGA names: ['Agege', 'Ajeromi/Ifelodun', 'Alimosho', 'Amuwo Odofin', 'Apapa', 'Badagry', 'Epe', 'Eti Osa', 'Ibeju Lekki', 'Ifako/Ijaye', 'Ikeja', 'Kosofe', 'Lagos Island', 'Lagos Mainland', 'Mushin', 'Ojo', 'Oshodi/Isolo', 'Shomolu', 'Surulere', 'Ìkòròdú']
  Agege: 24e04d44-9e6e-47ba-a71c-737793381785
  Ajeromi/Ifelodun: b9ecb255-fc97-4e0a-8bc7-9dd0588f7a21
  Alimosho: b4bd3fef-918f-4a60-bfd3-3569ed991562
  Amuwo Odofin: 7583d723-71b9-4f18-9fff-bff98df8322d
  Apapa: dc5547ff-cb10-4818-8e9b-363c14daa13b
  Badagry: 202749c9-2473-4ecb-8528-72dc7a1278ee
  Epe: 053c8979-6fdf-4078-a9d1-3b545a06d72d
  Eti Osa: 4d40b68b-a144-47cc-9345-4604adc7fb60
  Ibeju Lekki: 343b4b84-9150-4f3b-937f-c58ca20db27f
  Ifako/Ijaye: a9df031a-eaa0-4b74-b42c-5bf303feb0e1
  Ikeja: af88a8c6-f84f-4629-a25b-1e79a9ccbc3d
  Kosofe: 9713c2c3-7416-41b8-9912-6e1450d3347d
  Lagos Island: 65a83885-eae9-4a94-8663-f6917cc9a04e
  Lagos Mainland: 4f1aabae-5a08-4179-bdfb-f0e9d71d2ca5
  Mushin: fe1ca044-b547-49be-9883-2c8ffa3f04d5
  Ojo: 2c66b8ba-4745-4334-b145-ae00b1a6f604
  Oshodi/Isolo: e199f023-49ae-46ff-92a4-580c4fe1346d
  Shomolu: 1b64c604-707e-4a65-a114-75f0f1abb3c5
  Surulere: 22ef6348-2be5-4543-8f1d-6e9faf24e126
  Ìkòròdú: 634b4f3c-a5c4-42d2-b18b-1432b67373b8
2:18
Now I have:

1. The 20 Lagos LGAs with their names and Overture IDs
2. The building schema from Overture

My approach:
1. Download all buildings in the Lagos bbox
2. Download the LGA division boundaries (counties in NG-LA region)
3. Compute footprint area from geometry
4. Spatial join buildings to LGAs
5. Filter to buildings > 1000 m²
6. Output the GeoParquet file
7. Compute per-LGA summary and output the Parquet file

Let me write the full solution. I need to use `record_batch_reader` from `overturemaps.core` and then convert to GeoDataFrame.

Let me also explore how to get the division boundaries as actual geometries.
2:18
Write /work/check_geo.py
content · 76 lines · py
#!/usr/bin/env python3
"""Check division geometry availability."""

from overturemaps.core import record_batch_reader
import pyarrow as pa
import pyarrow.compute as pc

bbox = (2.5, 6.0, 4.5, 7.0)

reader = record_batch_reader("division", bbox=bbox)
if reader is None:
    print("No division reader returned")
    exit(1)

batches = []
while True:
    try:
        b = reader.read_next_batch()
        batches.append(b)
    except StopAsyncIteration:
        break
    except Exception:
        break

table = pa.Table.from_batches(batches)
print(f"Total division records: {len(table)}")

# Check geometry column type
geo_col = table.column("geometry")
print(f"Geometry type: {geo_col.type}")

# Check a sample geometry for a county
mask_lagos = pc.equal(table.column("region"), "NG-LA")
lagos_table = table.filter(mask_lagos)
mask_county = pc.equal(lagos_table.column("subtype"), "county")
counties = lagos_table.filter(mask_county)

geo_vals = counties.column("geometry").to_pylist()
print(f"\nSample geometry values (first 3):")
for i, g in enumerate(geo_vals[:3]):
    print(f"  {i}: type={type(g).__name__}, len={len(g) if hasattr(g, '__len__') else 'N/A'}, val={str(g)[:100]}")

# Check if there's a division_boundary type
reader2 = record_batch_reader("division_boundary", bbox=bbox)
if reader2:
    batches2 = []
    while True:
        try:
            b = reader2.read_next_batch()
            batches2.append(b)
        except:
            break
    table2 = pa.Table.from_batches(batches2)
    print(f"\nDivision boundary records: {len(table2)}")
    print(f"Division boundary columns: {table2.column_names}")
else:
    print("\nNo division_boundary records")

# Check division_area type
reader3 = record_batch_reader("division_area", bbox=bbox)
if reader3:
    batches3 = []
    while True:
        try:
            b = reader3.read_next_batch()
            batches3.append(b)
        except:
            break
    table3 = pa.Table.from_batches(batches3)
    print(f"\nDivision area records: {len(table3)}")
    print(f"Division area columns: {table3.column_names}")
    if len(table3) > 0:
        geo3 = table3.column("geometry").to_pylist()
        print(f"  Sample geometry: {str(geo3[0])[:100] if geo3[0] else 'None'}")
else:
    print("\nNo division_area records")
Wrote /work/check_geo.py (2284 chars).
2:20
Bash cd /work && python check_geo.py 2>&1
Total division records: 1406
Geometry type: binary

Sample geometry values (first 3):
  0: type=bytes, len=21, val=b'\x01\x01\x00\x00\x00\x9b\x1b\r\x05j!\n@^\xdb\xdb-\xc9\xc9\x19@'
  1: type=bytes, len=21, val=b'\x01\x01\x00\x00\x00PjX\xa8\\\xd3\n@\x82\xc0\x14/`k\x1a@'
  2: type=bytes, len=21, val=b'\x01\x01\x00\x00\x00)\x8c\xbb\xa1Y\x1b\x0b@b]&\xd4\x1d\xf9\x19@'

Division boundary records: 105
Division boundary columns: ['id', 'geometry', 'division_ids', 'subtype', 'admin_level', 'class', 'sources', 'perspectives', 'is_disputed', 'is_land', 'is_territorial', 'country', 'region', 'version', 'bbox']

Division area records: 75
Division area columns: ['id', 'geometry', 'country', 'sources', 'subtype', 'admin_level', 'class', 'names', 'is_land', 'is_territorial', 'region', 'division_id', 'version', 'bbox']
  Sample geometry: b'\x01\x06\x00\x00\x00\t\x00\x00\x00\x01\x03\x00\x00\x00\x01\x00\x00\x00\xda\x01\x00\x00{h\x1f+\xb8\
2:45
The division geometries in Overture are WKB (Well-Known Binary). The division_boundary has polygon geometries, and division_area also has polygon geometries with a `division_id` field linking to the division table. Let me see if division_area has the LGA boundaries.

Let me look at the structure more carefully and use division_area with a join to division IDs for the Lagos LGAs.
2:45
Write /work/check_division_area.py
content · 69 lines · py
#!/usr/bin/env python3
"""Check division_area for Lagos LGAs."""

from overturemaps.core import record_batch_reader
import pyarrow as pa
import pyarrow.compute as pc

bbox = (2.5, 6.0, 4.5, 7.0)

# Get Lagos county IDs first
reader = record_batch_reader("division", bbox=bbox)
batches = []
while True:
    try:
        batches.append(reader.read_next_batch())
    except:
        break
div_table = pa.Table.from_batches(batches)

mask_lagos = pc.equal(div_table.column("region"), "NG-LA")
lagos_divs = div_table.filter(mask_lagos)
mask_county = pc.equal(lagos_divs.column("subtype"), "county")
counties = lagos_divs.filter(mask_county)

county_ids = set(counties.column("id").to_pylist())
county_names = pc.struct_field(counties.column("names"), "primary").to_pylist()
print(f"LGA count: {len(county_ids)}")
print(f"LGA IDs: {county_ids}")

# Now get division_area records
reader2 = record_batch_reader("division_area", bbox=bbox)
batches2 = []
while True:
    try:
        batches2.append(reader2.read_next_batch())
    except:
        break
area_table = pa.Table.from_batches(batches2)
print(f"\nDivision area total: {len(area_table)}")
print(f"Columns: {area_table.column_names}")

# Check division_id column
div_ids_col = area_table.column("division_id")
print(f"\ndivision_id unique count: {pc.count_distinct(div_ids_col)}")

# Filter to Lagos LGAs
area_mask = pc.is_in(div_ids_col, pa.array(list(county_ids)))
lagos_areas = area_table.filter(area_mask)
print(f"Lagos LGA areas: {len(lagos_areas)}")

# For each area, check geometry
geo_col = lagos_areas.column("geometry")
geo_vals = geo_col.to_pylist()
print(f"Number of geometries: {len([g for g in geo_vals if g is not None])}")
for i, g in enumerate(geo_vals[:5]):
    print(f"  Area {i}: type=bytes, len={len(g) if g else 0}")

# Check names
names_col = lagos_areas.column("names")
prim_names = pc.struct_field(names_col, "primary") if "names" in area_table.column_names else None
if prim_names:
    print(f"\nArea names: {prim_names.to_pylist()}")

# Map division_id to name
area_div_ids = lagos_areas.column("division_id").to_pylist()
for did in area_div_ids:
    idx = list(county_ids).index(did) if did in county_ids else -1
    if idx >= 0:
        print(f"  ID {did} -> {list(zip(county_ids, county_names))}")
Wrote /work/check_division_area.py (2276 chars).
2:47
Bash cd /work && python check_division_area.py 2>&1
LGA count: 20
LGA IDs: {'22ef6348-2be5-4543-8f1d-6e9faf24e126', '4d40b68b-a144-47cc-9345-4604adc7fb60', '4f1aabae-5a08-4179-bdfb-f0e9d71d2ca5', '7583d723-71b9-4f18-9fff-bff98df8322d', '634b4f3c-a5c4-42d2-b18b-1432b67373b8', 'b9ecb255-fc97-4e0a-8bc7-9dd0588f7a21', '343b4b84-9150-4f3b-937f-c58ca20db27f', 'fe1ca044-b547-49be-9883-2c8ffa3f04d5', 'af88a8c6-f84f-4629-a25b-1e79a9ccbc3d', '65a83885-eae9-4a94-8663-f6917cc9a04e', 'a9df031a-eaa0-4b74-b42c-5bf303feb0e1', 'b4bd3fef-918f-4a60-bfd3-3569ed991562', 'dc5547ff-cb10-4818-8e9b-363c14daa13b', '202749c9-2473-4ecb-8528-72dc7a1278ee', '1b64c604-707e-4a65-a114-75f0f1abb3c5', '053c8979-6fdf-4078-a9d1-3b545a06d72d', '9713c2c3-7416-41b8-9912-6e1450d3347d', '2c66b8ba-4745-4334-b145-ae00b1a6f604', '24e04d44-9e6e-47ba-a71c-737793381785', 'e199f023-49ae-46ff-92a4-580c4fe1346d'}

Division area total: 75
Columns: ['id', 'geometry', 'country', 'sources', 'subtype', 'admin_level', 'class', 'names', 'is_land', 'is_territorial', 'region', 'division_id', 'version', 'bbox']

division_id unique count: 72
Lagos LGA areas: 20
Number of geometries: 20
  Area 0: type=bytes, len=2493
  Area 1: type=bytes, len=1085
  Area 2: type=bytes, len=1853
  Area 3: type=bytes, len=2061
  Area 4: type=bytes, len=2941

Area names: ['Amuwo Odofin', 'Agege', 'Ifako/Ijaye', 'Oshodi/Isolo', 'Badagry', 'Alimosho', 'Ojo', 'Ìkòròdú', 'Epe', 'Ajeromi/Ifelodun', 'Kosofe', 'Lagos Island', 'Ikeja', 'Shomolu', 'Lagos Mainland', 'Ibeju Lekki', 'Apapa', 'Surulere', 'Eti Osa', 'Mushin']
  ID 7583d723-71b9-4f18-9fff-bff98df8322d -> [('22ef6348-2be5-4543-8f1d-6e9faf24e126', 'Amuwo Odofin'), ('4d40b68b-a144-47cc-9345-4604adc7fb60', 'Ikeja'), ('4f1aabae-5a08-4179-bdfb-f0e9d71d2ca5', 'Lagos Mainland'), ('7583d723-71b9-4f18-9fff-bff98df8322d', 'Apapa'), ('634b4f3c-a5c4-42d2-b18b-1432b67373b8', 'Ojo'), ('b9ecb255-fc97-4e0a-8bc7-9dd0588f7a21', 'Lagos Island'), ('343b4b84-9150-4f3b-937f-c58ca20db27f', 'Oshodi/Isolo'), ('fe1ca044-b547-49be-9883-2c8ffa3f04d5', 'Alimosho'), ('af88a8c6-f84f-4629-a25b-1e79a9ccbc3d', 'Shomolu'), ('65a83885-eae9-4a94-8663-f6917cc9a04e', 'Mushin'), ('a9df031a-eaa0-4b74-b42c-5bf303feb0e1', 'Kosofe'), ('b4bd3fef-918f-4a60-bfd3-3569ed991562', 'Ibeju Lekki'), ('dc5547ff-cb10-4818-8e9b-363c14daa13b', 'Epe'), ('202749c9-2473-4ecb-8528-72dc7a1278ee', 'Ìkòròdú'), ('1b64c604-707e-4a65-a114-75f0f1abb3c5', 'Badagry'), ('053c8979-6fdf-4078-a9d1-3b545a06d72d', 'Surulere'), ('9713c2c3-7416-41b8-9912-6e1450d3347d', 'Eti Osa'), ('2c66b8ba-4745-4334-b145-ae00b1a6f604', 'Ajeromi/Ifelodun'), ('24e04d44-9e6e-47ba-a71c-737793381785', 'Agege'), ('e199f023-49ae-46ff-92a4-580c4fe1346d', 'Ifako/Ijaye')]
  ID 24e04d44-9e6e-47ba-a71c-737793381785 -> [('22ef6348-2be5-4543-8f1d-6e9faf24e126', 'Amuwo Odofin'), ('4d40b68b-a144-47cc-9345-4604adc7fb60', 'Ikeja'), ('4f1aabae-5a08-4179-bdfb-f0e9d71d2ca5', 'Lagos Mainland'), ('7583d723-71b9-4f18-9fff-bff98df8322d', 'Apapa'), ('634b4f3c-a5c4-42d2-b18b-1432b67373b8', 'Ojo'), ('b9ecb255-fc97-4e0a-8bc7-9dd0588f7a21', 'Lagos Island'), ('343b4b84-9150-4f3b-937f-c58ca20db27f', 'Oshodi/Isolo'), ('fe1ca044-b547-49be-9883-2c8ffa3f04d5', 'Alimosho'), ('af88a8c6-f84f-4629-a25b-1e79a9ccbc3d', 'Shomolu'), ('65a83885-eae9-4a94-8663-f6917cc9a04e', 'Mushin'), ('a9df031a-eaa0-4b74-b42c-5bf303feb0e1', 'Kosofe'), ('b4bd3fef-918f-4a60-bfd3-3569ed991562', 'Ibeju Lekki'), ('dc5547ff-cb10-4818-8e9b-363c14daa13b', 'Epe'), ('202749c9-2473-4ecb-8528-72dc7a1278ee', 'Ìkòròdú'), ('1b64c604-707e-4a65-a114-75f0f1abb3c5', 'Badagry'), ('053c8979-6fdf-4078-a9d1-3b545a06d72d', 'Surulere'), ('9713c2c3-7416-41b8-9912-6e1450d3347d', 'Eti Osa'), ('2c66b8ba-4745-4334-b145-ae00b1a6f604', 'Ajeromi/Ifelodun'), ('24e04d44-9e6e-47ba-a71c-737793381785', 'Agege'), ('e199f023-49ae-46ff-92a4-580c4fe1346d', 'Ifako/Ijaye')]
  ID a9df031a-eaa0-4b74-b42c-5bf303feb0e1 -> [('22ef6348-2be5-4543-8f1d-6e9faf24e126', 'Amuwo Odofin'), ('4d40b68b-a144-47cc-9345-4604adc7fb60', 'Ikeja'), ('4f1aabae-5a08-4179-bdfb-f0e9d71d2ca5', 'Lagos Mainland'), ('7583d723-71b9-4f18-9fff-bff98df8322d', 'Apapa'), ('634b4f3c-a5c4-42d2-b18b-1432b67373b8', 'Ojo'), ('b9ecb255-fc97-4e0a-8bc7-9dd0588f7a21', 'Lagos Island'), ('343b4b84-9150-4f3b-937f-c58ca20db27f', 'Oshodi/Isolo'), ('fe1ca044-b547-49be-9883-2c8ffa3f04d5', 'Alimosho'), ('af88a8c6-f84f-4629-a25b-1e79a9ccbc3d', 'Shomolu'), ('65a83885-eae9-4a94-8663-f6917cc9a04e', 'Mushin'), ('a9df031a-eaa0-4b74-b42c-5bf303feb0e1', 'Kosofe'), ('b4bd3fef-918f-4a60-bfd3-3569ed991562', 'Ibeju Lekki'), ('dc5547ff-cb10-4818-8e9b-363c14daa13b', 'Epe'), ('202749c9-2473-4ecb-8528-72dc7a1278ee', 'Ìkòròdú'), ('1b64c604-707e-4a65-a114-75f0f1abb3c5', 'Badagry'), ('053c8979-6fdf-4078-a9d1-3b545a06d72d', 'Surulere'), ('9713c2c3-7416-41b8-9912-6e1450d3347d', 'Eti Osa'), ('2c66b8ba-4745-4334-b145-ae00b1a6f604', 'Ajeromi/Ifelodun'), ('24e04d44-9e6e-47ba-a71c-737793381785', 'Agege'), ('e199f023-49ae-46ff-92a4-580c4fe1346d', 'Ifako/Ijaye')]
  ID e199f023-49ae-46ff-92a4-580c4fe1346d -> [('22ef6348-2be5-4543-8f1d-6e9faf24e126', 'Amuwo Odofin'), ('4d40b68b-a144-47cc-9345-4604adc7fb60', 'Ikeja'), ('4f1aabae-5a08-4179-bdfb-f0e9d71d2ca5', 'Lagos Mainland'), ('7583d723-71b9-4f18-9fff-bff98df8322d', 'Apapa'), ('634b4f3c-a5c4-42d2-b18b-1432b67373b8', 'Ojo'), ('b9ecb255-fc97-4e0a-8bc7-9dd0588f7a21', 'Lagos Island'), ('343b4b84-9150-4f3b-937f-c58ca20db27f', 'Oshodi/Isolo'), ('fe1ca044-b547-49be-9883-2c8ffa3f04d5', 'Alimosho'), ('af88a8c6-f84f-4629-a25b-1e79a9ccbc3d', 'Shomolu'), ('65a83885-eae9-4a94-8663-f6917cc9a04e', 'Mushin'), ('a9df031a-eaa0-4b74-b42c-5bf303feb0e1', 'Kosofe'), ('b4bd3fef-918f-4a60-bfd3-3569ed991562', 'Ibeju Lekki'), ('dc5547ff-cb10-4818-8e9b-363c14daa13b', 'Epe'), ('202749c9-2473-4ecb-8528-72dc7a1278ee', 'Ìkòròdú'), ('1b64c604-707e-4a65-a114-75f0f1abb3c5', 'Badagry'), ('053c8979-6fdf-4078-a9d1-3b545a06d72d', 'Surulere'), ('9713c2c3-7416-41b8-9912-6e1450d3347d', 'Eti Osa'), ('2c66b8ba-4745-4334-b145-ae00b1a6f604', 'Ajeromi/Ifelodun'), ('24e04d44-9e6e-47ba-a71c-737793381785', 'Agege'), ('e199f023-49ae-46ff-92a4-580c4fe1346d', 'Ifako/Ijaye')]
  ID 202749c9-2473-4ecb-8528-72dc7a1278ee -> [('22ef6348-2be5-4543-8f1d-6e9faf24e126', 'Amuwo Odofin'), ('4d40b68b-a144-47cc-9345-4604adc7fb60', 'Ikeja'), ('4f1aabae-5a08-4179-bdfb-f0e9d71d2ca5', 'Lagos Mainland'), ('7583d723-71b9-4f18-9fff-bff98df8322d', 'Apapa'), ('634b4f3c-a5c4-42d2-b18b-1432b67373b8', 'Ojo'), ('b9ecb255-fc97-4e0a-8bc7-9dd0588f7a21', 'Lagos Island'), ('343b4b84-9150-4f3b-937f-c58ca20db27f', 'Oshodi/Isolo'), ('fe1ca044-b547-49be-9883-2c8ffa3f04d5', 'Alimosho'), ('af88a8c6-f84f-4629-a25b-1e79a9ccbc3d', 'Shomolu'), ('65a83885-eae9-4a94-8663-f6917cc9a04e', 'Mushin'), ('a9df031a-eaa0-4b74-b42c-5bf303feb0e1', 'Kosofe'), ('b4bd3fef-918f-4a60-bfd3-3569ed991562', 'Ibeju Lekki'), ('dc5547ff-cb10-4818-8e9b-363c14daa13b', 'Epe'), ('202749c9-2473-4ecb-8528-72dc7a1278ee', 'Ìkòròdú'), ('1b64c604-707e-4a65-a114-75f0f1abb3c5', 'Badagry'), ('053c8979-6fdf-4078-a9d1-3b545a06d72d', 'Surulere'), ('9713c2c3-7416-41b8-9912-6e1450d3347d', 'Eti Osa'), ('2c66b8ba-4745-4334-b145-ae00b1a6f604', 'Ajeromi/Ifelodun'), ('24e04d44-9e6e-47ba-a71c-737793381785', 'Agege'), ('e199f023-49ae-46ff-92a4-580c4fe1346d', 'Ifako/Ijaye')]
  ID b4bd3fef-918f-4a60-bfd3-3569ed991562 -> [('22ef6348-2be5-4543-8f1d-6e9faf24e126', 'Amuwo Odofin'), ('4d40b68b-a144-47cc-9345-4604adc7fb60', 'Ikeja'), ('4f1aabae-5a08-4179-bdfb-f0e9d71d2ca5', 'Lagos Mainland'), ('7583d723-71b9-4f18-9fff-bff98df8322d', 'Apapa'), ('634b4f3c-a5c4-42d2-b18b-1432b67373b8', 'Ojo'), ('b9ecb255-fc97-4e0a-8bc7-9dd0588f7a21', 'Lagos Island'), ('343b4b84-9150-4f3b-937f-c58ca20db27f', 'Oshodi/Isolo'), ('fe1ca044-b547-49be-9883-2c8ffa3f04d5', 'Alimosho'), ('af88a8c6-f84f-4629-a25b-1e79a9ccbc3d', 'Shomolu'), ('65a83885-eae9-4a94-8663-f6917cc9a04e', 'Mushin'), ('a9df031a-eaa0-4b74-b42c-5bf303feb0e1', 'Kosofe'), ('b4bd3fef-918f-4a60-bfd3-3569ed991562', 'Ibeju Lekki'), ('dc5547ff-cb10-4818-8e9b-363c14daa13b', 'Epe'), ('202749c9-2473-4ecb-8528-72dc7a1278ee', 'Ìkòròdú'), ('1b64c604-707e-4a65-a114-75f0f1abb3c5', 'Badagry'), ('053c8979-6fdf-4078-a9d1-3b545a06d72d', 'Surulere'), ('9713c2c3-7416-41b8-9912-6e1450d3347d', 'Eti Osa'), ('2c66b8ba-4745-4334-b145-ae00b1a6f604', 'Ajeromi/Ifelodun'), ('24e04d44-9e6e-47ba-a71c-737793381785', 'Agege'), ('e199f023-49ae-46ff-92a4-580c4fe1346d', 'Ifako/Ijaye')]
  ID 2c66b8ba-4745-4334-b145-ae00b1a6f604 -> [('22ef6348-2be5-4543-8f1d-6e9faf24e126', 'Amuwo Odofin'), ('4d40b68b-a144-47cc-9345-4604adc7fb60', 'Ikeja'), ('4f1aabae-5a08-4179-bdfb-f0e9d71d2ca5', 'Lagos Mainland'), ('7583d723-71b9-4f18-9fff-bff98df8322d', 'Apapa'), ('634b4f3c-a5c4-42d2-b18b-1432b67373b8', 'Ojo'), ('b9ecb255-fc97-4e0a-8bc7-9dd0588f7a21', 'Lagos Island'), ('343b4b84-9150-4f3b-937f-c58ca20db27f', 'Oshodi/Isolo'), ('fe1ca044-b547-49be-9883-2c8ffa3f04d5', 'Alimosho'), ('af88a8c6-f84f-4629-a25b-1e79a9ccbc3d', 'Shomolu'), ('65a83885-eae9-4a94-8663-f6917cc9a04e', 'Mushin'), ('a9df031a-eaa0-4b74-b42c-5bf303feb0e1', 'Kosofe'), ('b4bd3fef-918f-4a60-bfd3-3569ed991562', 'Ibeju Lekki'), ('dc5547ff-cb10-4818-8e9b-363c14daa13b', 'Epe'), ('202749c9-2473-4ecb-8528-72dc7a1278ee', 'Ìkòròdú'), ('1b64c604-707e-4a65-a114-75f0f1abb3c5', 'Badagry'), ('053c8979-6fdf-4078-a9d1-3b545a06d72d', 'Surulere'), ('9713c2c3-7416-41b8-9912-6e1450d3347d', 'Eti Osa'), ('2c66b8ba-4745-4334-b145-ae00b1a6f604', 'Ajeromi/Ifelodun'), ('24e04d44-9e6e-47ba-a71c-737793381785', 'Agege'), ('e199f023-49ae-46ff-92a4-580c4fe1346d', 'Ifako/Ijaye')]
  ID 634b4f3c-a5c4-42d2-b18b-1432b67373b8 -> [('22ef6348-2be5-4543-8f1d-6e9faf24e126', 'Amuwo Odofin'), ('4d40b68b-a144-47cc-9345-4604adc7fb60', 'Ikeja'), ('4f1aabae-5a08-4179-bdfb-f0e9d71d2ca5', 'Lagos Mainland'), ('7583d723-71b9-4f18-9fff-bff98df8322d', 'Apapa'), ('634b4f3c-a5c4-42d2-b18b-1432b67373b8', 'Ojo'), ('b9ecb255-fc97-4e0a-8bc7-9dd0588f7a21', 'Lagos Island'), ('343b4b84-9150-4f3b-937f-c58ca20db27f', 'Oshodi/Isolo'), ('fe1ca044-b547-49be-9883-2c8ffa3f04d5', 'Alimosho'), ('af88a8c6-f84f-4629-a25b-1e79a9ccbc3d', 'Shomolu'), ('65a83885-eae9-4a94-8663-f6917cc9a04e', 'Mushin'), ('a9df031a-eaa0-4b74-b42c-5bf303feb0e1', 'Kosofe'), ('b4bd3fef-918f-4a60-bfd3-3569ed991562', 'Ibeju Lekki'), ('dc5547ff-cb10-4818-8e9b-363c14daa13b', 'Epe'), ('202749c9-2473-4ecb-8528-72dc7a1278ee', 'Ìkòròdú'), ('1b64c604-707e-4a65-a114-75f0f1abb3c5', 'Badagry'), ('053c8979-6fdf-4078-a9d1-3b545a06d72d', 'Surulere'), ('9713c2c3-7416-41b8-9912-6e1450d3347d', 'Eti Osa'), ('2c66b8ba-4745-4334-b145-ae00b1a6f604', 'Ajeromi/Ifelodun'), ('24e04d44-9e6e-47ba-a71c-737793381785', 'Agege'), ('e199f023-49ae-46ff-92a4-580c4fe1346d', 'Ifako/Ijaye')]
  ID 053c8979-6fdf-4078-a9d1-3b545a06d72d -> [('22ef6348-2be5-4543-8f1d-6e9faf24e126', 'Amuwo Odofin'), ('4d40b68b-a144-47cc-9345-4604adc7fb60', 'Ikeja'), ('4f1aabae-5a08-4179-bdfb-f0e9d71d2ca5', 'Lagos Mainland'), ('7583d723-71b9-4f18-9fff-bff98df8322d', 'Apapa'), ('634b4f3c-a5c4-42d2-b18b-1432b67373b8', 'Ojo'), ('b9ecb255-fc97-4e0a-8bc7-9dd0588f7a21', 'Lagos Island'), ('343b4b84-9150-4f3b-937f-c58ca20db27f', 'Oshodi/Isolo'), ('fe1ca044-b547-49be-9883-2c8ffa3f04d5', 'Alimosho'), ('af88a8c6-f84f-4629-a25b-1e79a9ccbc3d', 'Shomolu'), ('65a83885-eae9-4a94-8663-f6917cc9a04e', 'Mushin'), ('a9df031a-eaa0-4b74-b42c-5bf303feb0e1', 'Kosofe'), ('b4bd3fef-918f-4a60-bfd3-3569ed991562', 'Ibeju Lekki'), ('dc5547ff-cb10-4818-8e9b-363c14daa13b', 'Epe'), ('202749c9-2473-4ecb-8528-72dc7a1278ee', 'Ìkòròdú'), ('1b64c604-707e-4a65-a114-75f0f1abb3c5', 'Badagry'), ('053c8979-6fdf-4078-a9d1-3b545a06d72d', 'Surulere'), ('9713c2c3-7416-41b8-9912-6e1450d3347d', 'Eti Osa'), ('2c66b8ba-4745-4334-b145-ae00b1a6f604', 'Ajeromi/Ifelodun'), ('24e04d44-9e6e-47ba-a71c-737793381785', 'Agege'), ('e199f023-49ae-46ff-92a4-580c4fe1346d', 'Ifako/Ijaye')]
  ID b9ecb255-fc97-4e0a-8bc7-9dd0588f7a21 -> [('22ef6348-2be5-4543-8f1d-6e9faf24e126', 'Amuwo Odofin'), ('4d40b68b-a144-47cc-9345-4604adc7fb60', 'Ikeja'), ('4f1aabae-5a08-4179-bdfb-f0e9d71d2ca5', 'Lagos Mainland'), ('7583d723-71b9-4f18-9fff-bff98df8322d', 'Apapa'), ('634b4f3c-a5c4-42d2-b18b-1432b67373b8', 'Ojo'), ('b9ecb255-fc97-4e0a-8bc7-9dd0588f7a21', 'Lagos Island'), ('343b4b84-9150-4f3b-937f-c58ca20db27f', 'Oshodi/Isolo'), ('fe1ca044-b547-49be-9883-2c8ffa3f04d5', 'Alimosho'), ('af88a8c6-f84f-4629-a25b-1e79a9ccbc3d', 'Shomolu'), ('65a83885-eae9-4a94-8663-f6917cc9a04e', 'Mushin'), ('a9df031a-eaa0-4b74-b42c-5bf303feb0e1', 'Kosofe'), ('b4bd3fef-918f-4a60-bfd3-3569ed991562', 'Ibeju Lekki'), ('dc5547ff-cb10-4818-8e9b-363c14daa13b', 'Epe'), ('202749c9-2473-4ecb-8528-72dc7a1278ee', 'Ìkòròdú'), ('1b64c604-707e-4a65-a114-75f0f1abb3c5', 'Badagry'), ('053c8979-6fdf-4078-a9d1-3b545a06d72d', 'Surulere'), ('9713c2c3-7416-41b8-9912-6e1450d3347d', 'Eti Osa'), ('2c66b8ba-4745-4334-b145-ae00b1a6f604', 'Ajeromi/Ifelodun'), ('24e04d44-9e6e-47ba-a71c-737793381785', 'Agege'), ('e199f023-49ae-46ff-92a4-580c4fe1346d', 'Ifako/Ijaye')]
  ID 9713c2c3-7416-41b8-9912-6e1450d3347d -> [('22ef6348-2be5-4543-8f1d-6e9faf24e126', 'Amuwo Odofin'), ('4d40b68b-a144-47cc-9345-4604adc7fb60', 'Ikeja'), ('4f1aabae-5a08-4179-bdfb-f0e9d71d2ca5', 'Lagos Mainland'), ('7583d723-71b9-4f18-9fff-bff98df8322d', 'Apapa'), ('634b4f3c-a5c4-42d2-b18b-1432b67373b8', 'Ojo'), ('b9ecb255-fc97-4e0a-8bc7-9dd0588f7a21', 'Lagos Island'), ('343b4b84-9150-4f3b-937f-c58ca20db27f', 'Oshodi/Isolo'), ('fe1ca044-b547-49be-9883-2c8ffa3f04d5', 'Alimosho'), ('af88a8c6-f84f-4629-a25b-1e79a9ccbc3d', 'Shomolu'), ('65a83885-eae9-4a94-8663-f6917cc9a04e', 'Mushin'), ('a9df031a-eaa0-4b74-b42c-5bf303feb0e1', 'Kosofe'), ('b4bd3fef-918f-4a60-bfd3-3569ed991562', 'Ibeju Lekki'), ('dc5547ff-cb10-4818-8e9b-363c14daa13b', 'Epe'), ('202749c9-2473-4ecb-8528-72dc7a1278ee', 'Ìkòròdú'), ('1b64c604-707e-4a65-a114-75f0f1abb3c5', 'Badagry'), ('053c8979-6fdf-4078-a9d1-3b545a06d72d', 'Surulere'), ('9713c2c3-7416-41b8-9912-6e1450d3347d', 'Eti Osa'), ('2c66b8ba-4745-4334-b145-ae00b1a6f604', 'Ajeromi/Ifelodun'), ('24e04d44-9e6e-47ba-a71c-737793381785', 'Agege'), ('e199f023-49ae-46ff-92a4-580c4fe1346d', 'Ifako/Ijaye')]
  ID 65a83885-eae9-4a94-8663-f6917cc9a04e -> [('22ef6348-2be5-4543-8f1d-6e9faf24e126', 'Amuwo Odofin'), ('4d40b68b-a144-47cc-9345-4604adc7fb60', 'Ikeja'), ('4f1aabae-5a08-4179-bdfb-f0e9d71d2ca5', 'Lagos Mainland'), ('7583d723-71b9-4f18-9fff-bff98df8322d', 'Apapa'), ('634b4f3c-a5c4-42d2-b18b-1432b67373b8', 'Ojo'), ('b9ecb255-fc97-4e0a-8bc7-9dd0588f7a21', 'Lagos Island'), ('343b4b84-9150-4f3b-937f-c58ca20db27f', 'Oshodi/Isolo'), ('fe1ca044-b547-49be-9883-2c8ffa3f04d5', 'Alimosho'), ('af88a8c6-f84f-4629-a25b-1e79a9ccbc3d', 'Shomolu'), ('65a83885-eae9-4a94-8663-f6917cc9a04e', 'Mushin'), ('a9df031a-eaa0-4b74-b42c-5bf303feb0e1', 'Kosofe'), ('b4bd3fef-918f-4a60-bfd3-3569ed991562', 'Ibeju Lekki'), ('dc5547ff-cb10-4818-8e9b-363c14daa13b', 'Epe'), ('202749c9-2473-4ecb-8528-72dc7a1278ee', 'Ìkòròdú'), ('1b64c604-707e-4a65-a114-75f0f1abb3c5', 'Badagry'), ('053c8979-6fdf-4078-a9d1-3b545a06d72d', 'Surulere'), ('9713c2c3-7416-41b8-9912-6e1450d3347d', 'Eti Osa'), ('2c66b8ba-4745-4334-b145-ae00b1a6f604', 'Ajeromi/Ifelodun'), ('24e04d44-9e6e-47ba-a71c-737793381785', 'Agege'), ('e199f023-49ae-46ff-92a4-580c4fe1346d', 'Ifako/Ijaye')]
  ID af88a8c6-f84f-4629-a25b-1e79a9ccbc3d -> [('22ef6348-2be5-4543-8f1d-6e9faf24e126', 'Amuwo Odofin'), ('4d40b68b-a144-47cc-9345-4604adc7fb60', 'Ikeja'), ('4f1aabae-5a08-4179-bdfb-f0e9d71d2ca5', 'Lagos Mainland'), ('7583d723-71b9-4f18-9fff-bff98df8322d', 'Apapa'), ('634b4f3c-a5c4-42d2-b18b-1432b67373b8', 'Ojo'), ('b9ecb255-fc97-4e0a-8bc7-9dd0588f7a21', 'Lagos Island'), ('343b4b84-9150-4f3b-937f-c58ca20db27f', 'Oshodi/Isolo'), ('fe1ca044-b547-49be-9883-2c8ffa3f04d5', 'Alimosho'), ('af88a8c6-f84f-4629-a25b-1e79a9ccbc3d', 'Shomolu'), ('65a83885-eae9-4a94-8663-f6917cc9a04e', 'Mushin'), ('a9df031a-eaa0-4b74-b42c-5bf303feb0e1', 'Kosofe'), ('b4bd3fef-918f-4a60-bfd3-3569ed991562', 'Ibeju Lekki'), ('dc5547ff-cb10-4818-8e9b-363c14daa13b', 'Epe'), ('202749c9-2473-4ecb-8528-72dc7a1278ee', 'Ìkòròdú'), ('1b64c604-707e-4a65-a114-75f0f1abb3c5', 'Badagry'), ('053c8979-6fdf-4078-a9d1-3b545a06d72d', 'Surulere'), ('9713c2c3-7416-41b8-9912-6e1450d3347d', 'Eti Osa'), ('2c66b8ba-4745-4334-b145-ae00b1a6f604', 'Ajeromi/Ifelodun'), ('24e04d44-9e6e-47ba-a71c-737793381785', 'Agege'), ('e199f023-49ae-46ff-92a4-580c4fe1346d', 'Ifako/Ijaye')]
  ID 1b64c604-707e-4a65-a114-75f0f1abb3c5 -> [('22ef6348-2be5-4543-8f1d-6e9faf24e126', 'Amuwo Odofin'), ('4d40b68b-a144-47cc-9345-4604adc7fb60', 'Ikeja'), ('4f1aabae-5a08-4179-bdfb-f0e9d71d2ca5', 'Lagos Mainland'), ('7583d723-71b9-4f18-9fff-bff98df8322d', 'Apapa'), ('634b4f3c-a5c4-42d2-b18b-1432b67373b8', 'Ojo'), ('b9ecb255-fc97-4e0a-8bc7-9dd0588f7a21', 'Lagos Island'), ('343b4b84-9150-4f3b-937f-c58ca20db27f', 'Oshodi/Isolo'), ('fe1ca044-b547-49be-9883-2c8ffa3f04d5', 'Alimosho'), ('af88a8c6-f84f-4629-a25b-1e79a9ccbc3d', 'Shomolu'), ('65a83885-eae9-4a94-8663-f6917cc9a04e', 'Mushin'), ('a9df031a-eaa0-4b74-b42c-5bf303feb0e1', 'Kosofe'), ('b4bd3fef-918f-4a60-bfd3-3569ed991562', 'Ibeju Lekki'), ('dc5547ff-cb10-4818-8e9b-363c14daa13b', 'Epe'), ('202749c9-2473-4ecb-8528-72dc7a1278ee', 'Ìkòròdú'), ('1b64c604-707e-4a65-a114-75f0f1abb3c5', 'Badagry'), ('053c8979-6fdf-4078-a9d1-3b545a06d72d', 'Surulere'), ('9713c2c3-7416-41b8-9912-6e1450d3347d', 'Eti Osa'), ('2c66b8ba-4745-4334-b145-ae00b1a6f604', 'Ajeromi/Ifelodun'), ('24e04d44-9e6e-47ba-a71c-737793381785', 'Agege'), ('e199f023-49ae-46ff-92a4-580c4fe1346d', 'Ifako/Ijaye')]
  ID 4f1aabae-5a08-4179-bdfb-f0e9d71d2ca5 -> [('22ef6348-2be5-4543-8f1d-6e9faf24e126', 'Amuwo Odofin'), ('4d40b68b-a144-47cc-9345-4604adc7fb60', 'Ikeja'), ('4f1aabae-5a08-4179-bdfb-f0e9d71d2ca5', 'Lagos Mainland'), ('7583d723-71b9-4f18-9fff-bff98df8322d', 'Apapa'), ('634b4f3c-a5c4-42d2-b18b-1432b67373b8', 'Ojo'), ('b9ecb255-fc97-4e0a-8bc7-9dd0588f7a21', 'Lagos Island'), ('343b4b84-9150-4f3b-937f-c58ca20db27f', 'Oshodi/Isolo'), ('fe1ca044-b547-49be-9883-2c8ffa3f04d5', 'Alimosho'), ('af88a8c6-f84f-4629-a25b-1e79a9ccbc3d', 'Shomolu'), ('65a83885-eae9-4a94-8663-f6917cc9a04e', 'Mushin'), ('a9df031a-eaa0-4b74-b42c-5bf303feb0e1', 'Kosofe'), ('b4bd3fef-918f-4a60-bfd3-3569ed991562', 'Ibeju Lekki'), ('dc5547ff-cb10-4818-8e9b-363c14daa13b', 'Epe'), ('202749c9-2473-4ecb-8528-72dc7a1278ee', 'Ìkòròdú'), ('1b64c604-707e-4a65-a114-75f0f1abb3c5', 'Badagry'), ('053c8979-6fdf-4078-a9d1-3b545a06d72d', 'Surulere'), ('9713c2c3-7416-41b8-9912-6e1450d3347d', 'Eti Osa'), ('2c66b8ba-4745-4334-b145-ae00b1a6f604', 'Ajeromi/Ifelodun'), ('24e04d44-9e6e-47ba-a71c-737793381785', 'Agege'), ('e199f023-49ae-46ff-92a4-580c4fe1346d', 'Ifako/Ijaye')]
  ID 343b4b84-9150-4f3b-937f-c58ca20db27f -> [('22ef6348-2be5-4543-8f1d-6e9faf24e126', 'Amuwo Odofin'), ('4d40b68b-a144-47cc-9345-4604adc7fb60', 'Ikeja'), ('4f1aabae-5a08-4179-bdfb-f0e9d71d2ca5', 'Lagos Mainland'), ('7583d723-71b9-4f18-9fff-bff98df8322d', 'Apapa'), ('634b4f3c-a5c4-42d2-b18b-1432b67373b8', 'Ojo'), ('b9ecb255-fc97-4e0a-8bc7-9dd0588f7a21', 'Lagos Island'), ('343b4b84-9150-4f3b-937f-c58ca20db27f', 'Oshodi/Isolo'), ('fe1ca044-b547-49be-9883-2c8ffa3f04d5', 'Alimosho'), ('af88a8c6-f84f-4629-a25b-1e79a9ccbc3d', 'Shomolu'), ('65a83885-eae9-4a94-8663-f6917cc9a04e', 'Mushin'), ('a9df031a-eaa0-4b74-b42c-5bf303feb0e1', 'Kosofe'), ('b4bd3fef-918f-4a60-bfd3-3569ed991562', 'Ibeju Lekki'), ('dc5547ff-cb10-4818-8e9b-363c14daa13b', 'Epe'), ('202749c9-2473-4ecb-8528-72dc7a1278ee', 'Ìkòròdú'), ('1b64c604-707e-4a65-a114-75f0f1abb3c5', 'Badagry'), ('053c8979-6fdf-4078-a9d1-3b545a06d72d', 'Surulere'), ('9713c2c3-7416-41b8-9912-6e1450d3347d', 'Eti Osa'), ('2c66b8ba-4745-4334-b145-ae00b1a6f604', 'Ajeromi/Ifelodun'), ('24e04d44-9e6e-47ba-a71c-737793381785', 'Agege'), ('e199f023-49ae-46ff-92a4-580c4fe1346d', 'Ifako/Ijaye')]
  ID dc5547ff-cb10-4818-8e9b-363c14daa13b -> [('22ef6348-2be5-4543-8f1d-6e9faf24e126', 'Amuwo Odofin'), ('4d40b68b-a144-47cc-9345-4604adc7fb60', 'Ikeja'), ('4f1aabae-5a08-4179-bdfb-f0e9d71d2ca5', 'Lagos Mainland'), ('7583d723-71b9-4f18-9fff-bff98df8322d', 'Apapa'), ('634b4f3c-a5c4-42d2-b18b-1432b67373b8', 'Ojo'), ('b9ecb255-fc97-4e0a-8bc7-9dd0588f7a21', 'Lagos Island'), ('343b4b84-9150-4f3b-937f-c58ca20db27f', 'Oshodi/Isolo'), ('fe1ca044-b547-49be-9883-2c8ffa3f04d5', 'Alimosho'), ('af88a8c6-f84f-4629-a25b-1e79a9ccbc3d', 'Shomolu'), ('65a83885-eae9-4a94-8663-f6917cc9a04e', 'Mushin'), ('a9df031a-eaa0-4b74-b42c-5bf303feb0e1', 'Kosofe'), ('b4bd3fef-918f-4a60-bfd3-3569ed991562', 'Ibeju Lekki'), ('dc5547ff-cb10-4818-8e9b-363c14daa13b', 'Epe'), ('202749c9-2473-4ecb-8528-72dc7a1278ee', 'Ìkòròdú'), ('1b64c604-707e-4a65-a114-75f0f1abb3c5', 'Badagry'), ('053c8979-6fdf-4078-a9d1-3b545a06d72d', 'Surulere'), ('9713c2c3-7416-41b8-9912-6e1450d3347d', 'Eti Osa'), ('2c66b8ba-4745-4334-b145-ae00b1a6f604', 'Ajeromi/Ifelodun'), ('24e04d44-9e6e-47ba-a71c-737793381785', 'Agege'), ('e199f023-49ae-46ff-92a4-580c4fe1346d', 'Ifako/Ijaye')]
  ID 22ef6348-2be5-4543-8f1d-6e9faf24e126 -> [('22ef6348-2be5-4543-8f1d-6e9faf24e126', 'Amuwo Odofin'), ('4d40b68b-a144-47cc-9345-4604adc7fb60', 'Ikeja'), ('4f1aabae-5a08-4179-bdfb-f0e9d71d2ca5', 'Lagos Mainland'), ('7583d723-71b9-4f18-9fff-bff98df8322d', 'Apapa'), ('634b4f3c-a5c4-42d2-b18b-1432b67373b8', 'Ojo'), ('b9ecb255-fc97-4e0a-8bc7-9dd0588f7a21', 'Lagos Island'), ('343b4b84-9150-4f3b-937f-c58ca20db27f', 'Oshodi/Isolo'), ('fe1ca044-b547-49be-9883-2c8ffa3f04d5', 'Alimosho'), ('af88a8c6-f84f-4629-a25b-1e79a9ccbc3d', 'Shomolu'), ('65a83885-eae9-4a94-8663-f6917cc9a04e', 'Mushin'), ('a9df031a-eaa0-4b74-b42c-5bf303feb0e1', 'Kosofe'), ('b4bd3fef-918f-4a60-bfd3-3569ed991562', 'Ibeju Lekki'), ('dc5547ff-cb10-4818-8e9b-363c14daa13b', 'Epe'), ('202749c9-2473-4ecb-8528-72dc7a1278ee', 'Ìkòròdú'), ('1b64c604-707e-4a65-a114-75f0f1abb3c5', 'Badagry'), ('053c8979-6fdf-4078-a9d1-3b545a06d72d', 'Surulere'), ('9713c2c3-7416-41b8-9912-6e1450d3347d', 'Eti Osa'), ('2c66b8ba-4745-4334-b145-ae00b1a6f604', 'Ajeromi/Ifelodun'), ('24e04d44-9e6e-47ba-a71c-737793381785', 'Agege'), ('e199f023-49ae-46ff-92a4-580c4fe1346d', 'Ifako/Ijaye')]
  ID 4d40b68b-a144-47cc-9345-4604adc7fb60 -> [('22ef6348-2be5-4543-8f1d-6e9faf24e126', 'Amuwo Odofin'), ('4d40b68b-a144-47cc-9345-4604adc7fb60', 'Ikeja'), ('4f1aabae-5a08-4179-bdfb-f0e9d71d2ca5', 'Lagos Mainland'), ('7583d723-71b9-4f18-9fff-bff98df8322d', 'Apapa'), ('634b4f3c-a5c4-42d2-b18b-1432b67373b8', 'Ojo'), ('b9ecb255-fc97-4e0a-8bc7-9dd0588f7a21', 'Lagos Island'), ('343b4b84-9150-4f3b-937f-c58ca20db27f', 'Oshodi/Isolo'), ('fe1ca044-b547-49be-9883-2c8ffa3f04d5', 'Alimosho'), ('af88a8c6-f84f-4629-a25b-1e79a9ccbc3d', 'Shomolu'), ('65a83885-eae9-4a94-8663-f6917cc9a04e', 'Mushin'), ('a9df031a-eaa0-4b74-b42c-5bf303feb0e1', 'Kosofe'), ('b4bd3fef-918f-4a60-bfd3-3569ed991562', 'Ibeju Lekki'), ('dc5547ff-cb10-4818-8e9b-363c14daa13b', 'Epe'), ('202749c9-2473-4ecb-8528-72dc7a1278ee', 'Ìkòròdú'), ('1b64c604-707e-4a65-a114-75f0f1abb3c5', 'Badagry'), ('053c8979-6fdf-4078-a9d1-3b545a06d72d', 'Surulere'), ('9713c2c3-7416-41b8-9912-6e1450d3347d', 'Eti Osa'), ('2c66b8ba-4745-4334-b145-ae00b1a6f604', 'Ajeromi/Ifelodun'), ('24e04d44-9e6e-47ba-a71c-737793381785', 'Agege'), ('e199f023-49ae-46ff-92a4-580c4fe1346d', 'Ifako/Ijaye')]
  ID fe1ca044-b547-49be-9883-2c8ffa3f04d5 -> [('22ef6348-2be5-4543-8f1d-6e9faf24e126', 'Amuwo Odofin'), ('4d40b68b-a144-47cc-9345-4604adc7fb60', 'Ikeja'), ('4f1aabae-5a08-4179-bdfb-f0e9d71d2ca5', 'Lagos Mainland'), ('7583d723-71b9-4f18-9fff-bff98df8322d', 'Apapa'), ('634b4f3c-a5c4-42d2-b18b-1432b67373b8', 'Ojo'), ('b9ecb255-fc97-4e0a-8bc7-9dd0588f7a21', 'Lagos Island'), ('343b4b84-9150-4f3b-937f-c58ca20db27f', 'Oshodi/Isolo'), ('fe1ca044-b547-49be-9883-2c8ffa3f04d5', 'Alimosho'), ('af88a8c6-f84f-4629-a25b-1e79a9ccbc3d', 'Shomolu'), ('65a83885-eae9-4a94-8663-f6917cc9a04e', 'Mushin'), ('a9df031a-eaa0-4b74-b42c-5bf303feb0e1', 'Kosofe'), ('b4bd3fef-918f-4a60-bfd3-3569ed991562', 'Ibeju Lekki'), ('dc5547ff-cb10-4818-8e9b-363c14daa13b', 'Epe'), ('202749c9-2473-4ecb-8528-72dc7a1278ee', 'Ìkòròdú'), ('1b64c604-707e-4a65-a114-75f0f1abb3c5', 'Badagry'), ('053c8979-6fdf-4078-a9d1-3b545a06d72d', 'Surulere'), ('9713c2c3-7416-41b8-9912-6e1450d3347d', 'Eti Osa'), ('2c66b8ba-4745-4334-b145-ae00b1a6f604', 'Ajeromi/Ifelodun'), ('24e04d44-9e6e-47ba-a71c-737793381785', 'Agege'), ('e199f023-49ae-46ff-92a4-580c4fe1346d', 'Ifako/Ijaye')]
3:19
Now I have a clear picture. The division_area table has 20 records (one per LGA) that match the Lagos county IDs. The geometries are binary WKB-encoded. Let me build the main solution now.

Plan:
1. Download buildings from Overture using a generous Lagos bbox
2. Download division_area geometries for Lagos LGAs
3. Convert both to GeoDataFrames (decode WKB geometries)
4. Spatial join: assign each building to an LGA
5. Filter buildings with footprint area > 1000 m²
6. Compute per-LGA summary
7. Write outputs

Let me write the complete script.
3:19
3:19
Write /work/solve.py
content · 235 lines · py
#!/usr/bin/env python3
"""
Extract Lagos State (Nigeria) building footprints > 1000 m² from Overture Maps.
Compute per-LGA summary statistics.

Outputs:
  - lagos_buildings.geoparquet: EPSG:4326 GeoParquet with id, height, footprint_area_m2, lga, geometry
  - lagos_building_summary.parquet: plain Parquet with lga, n_buildings, total_footprint_m2, n_with_height, p50_height_m
"""

import logging
import sys
import time
import pyarrow as pa
import pyarrow.compute as pc
from overturemaps.core import record_batch_reader
import geopandas as gpd
import pandas as pd
import numpy as np
from shapely import wkb
from shapely.geometry import box

logging.basicConfig(level=logging.INFO, stream=sys.stdout, format="%(asctime)s %(levelname)s %(message)s")
logger = logging.getLogger(__name__)

MAX_RETRIES = 3


def read_all_batches(reader, desc="data"):
    """Read all batches from a RecordBatchReader into a single Table."""
    batches = []
    while True:
        try:
            batches.append(reader.read_next_batch())
        except StopAsyncIteration:
            break
        except Exception as e:
            logger.warning(f"Batch read ended: {e}")
            break
    if not batches:
        raise RuntimeError(f"No data returned for {desc}")
    table = pa.Table.from_batches(batches)
    logger.info(f"Read {len(table)} {desc} records.")
    return table


def retry_reader(overture_type, bbox, **kwargs):
    """Download Overture data with retries and backoff."""
    for attempt in range(1, MAX_RETRIES + 1):
        try:
            logger.info(f"Downloading {overture_type} (attempt {attempt}/{MAX_RETRIES}) ...")
            reader = record_batch_reader(overture_type, bbox=bbox, **kwargs)
            if reader is None:
                raise RuntimeError(f"record_batch_reader returned None for {overture_type}")
            table = read_all_batches(reader, desc=overture_type)
            return table
        except Exception as e:
            logger.warning(f"Attempt {attempt} failed: {e}")
            if attempt < MAX_RETRIES:
                time.sleep(2 ** attempt)
    raise RuntimeError(f"All {MAX_RETRIES} attempts failed for {overture_type}.")


def decode_geometry_wkb(geom_col):
    """Decode a PyArrow column of WKB bytes into a list of Shapely geometries."""
    raw = geom_col.to_pylist()
    geoms = []
    for g in raw:
        if g is not None and len(g) > 0:
            try:
                geoms.append(wkb.loads(g))
            except Exception:
                geoms.append(None)
        else:
            geoms.append(None)
    return geoms


def main():
    # ------------------------------------------------------------------
    # 1. Bounding box covering Lagos State, Nigeria generously
    # ------------------------------------------------------------------
    bbox = (2.5, 6.0, 4.5, 7.0)  # (west, south, east, north)

    # ------------------------------------------------------------------
    # 2. Download LGA boundaries (division_area)
    # ------------------------------------------------------------------
    logger.info("Step 1: Downloading Lagos LGA boundaries...")
    div_area_table = retry_reader("division_area", bbox=bbox)

    # Get Lagos county IDs (subtype=county, region=NG-LA)
    div_table = retry_reader("division", bbox=bbox)
    mask_la = pc.equal(div_table.column("region"), "NG-LA")
    lagos_divs = div_table.filter(mask_la)
    mask_county = pc.equal(lagos_divs.column("subtype"), "county")
    county_ids_set = set(lagos_divs.filter(mask_county).column("id").to_pylist())
    logger.info(f"Found {len(county_ids_set)} Lagos LGAs (counties).")

    # Build name lookup: division_id -> primary name
    county_names_map = {}
    for row in lagos_divs.filter(mask_county).to_pylist():
        cid = row["id"]
        primary = None
        if row.get("names") and isinstance(row["names"], dict):
            primary = row["names"].get("primary", row["names"].get("common", {}).get("en"))
        county_names_map[cid] = primary or cid

    # Filter division_area to only Lagos LGAs
    area_div_ids = div_area_table.column("division_id")
    area_mask = pc.is_in(area_div_ids, pa.array(list(county_ids_set)))
    lga_area_table = div_area_table.filter(area_mask)
    logger.info(f"LGA area records: {len(lga_area_table)}")

    # Decode geometries for LGA areas
    lga_geoms = decode_geometry_wkb(lga_area_table.column("geometry"))
    lga_div_ids = lga_area_table.column("division_id").to_pylist()
    lga_names_list = [county_names_map.get(did, str(did)) for did in lga_div_ids]

    # Build a GeoDataFrame of LGAs
    lga_gdf = gpd.GeoDataFrame(
        {"lga": lga_names_list, "division_id": lga_div_ids},
        geometry=lga_geoms,
        crs="EPSG:4326",
    )
    logger.info(f"LGA boundaries: {len(lga_gdf)} records.")
    logger.info(f"LGAs: {sorted(lga_gdf['lga'].tolist())}")

    # ------------------------------------------------------------------
    # 3. Download all buildings in the Lagos bbox
    # ------------------------------------------------------------------
    logger.info("Step 2: Downloading building footprints...")
    bldg_table = retry_reader("building", bbox=bbox)
    logger.info(f"Total building records: {len(bldg_table)}")

    # Decode building geometries
    logger.info("Decoding building geometries...")
    bldg_geoms = decode_geometry_wkb(bldg_table.column("geometry"))

    # Extract height (metres) from the 'height' column (float)
    height_col = bldg_table.column("height").to_pylist()
    heights = [h if h is not None and not np.isnan(h) else None for h in height_col]

    # Get IDs
    ids = bldg_table.column("id").to_pylist()

    # Build a GeoDataFrame
    bldg_gdf = gpd.GeoDataFrame(
        {"id": ids, "height": heights},
        geometry=bldg_geoms,
        crs="EPSG:4326",
    )
    # Drop rows with null geometry
    bldg_gdf = bldg_gdf[bldg_gdf.geometry.notna()].copy()
    logger.info(f"Buildings with valid geometry: {len(bldg_gdf)}")

    # ------------------------------------------------------------------
    # 4. Spatial join: assign each building to an LGA
    # ------------------------------------------------------------------
    logger.info("Step 3: Spatial join — assigning buildings to LGAs...")
    bldg_with_lga = gpd.sjoin(
        bldg_gdf, lga_gdf[["lga", "geometry"]],
        how="left", predicate="intersects"
    )
    # Drop buildings that don't intersect any LGA (shouldn't happen with our bbox)
    bldg_with_lga = bldg_with_lga[bldg_with_lga["lga"].notna()].copy()
    logger.info(f"Buildings after spatial join: {len(bldg_with_lga)}")

    # ------------------------------------------------------------------
    # 5. Compute footprint area in m² and filter > 1000 m²
    # ------------------------------------------------------------------
    logger.info("Step 4: Computing footprint areas...")
    # Use a geographic approach: reproject each geometry to an appropriate
    # UTM zone for accurate area calculation. Lagos is around UTM zone 31N / 32N.
    # We'll use a simple approach: compute area in a metric CRS.
    # Lagos spans approx lon 2.7° to 4.5°, so UTM zone 31N (EPSG:32631)
    # or 32N (EPSG:32632). Zone 31N covers 0° to 6°E, so it's appropriate.

    # Quick area: project the GeoDataFrame to EPSG:32631 for area calculation
    bldg_with_lga_sqm = bldg_with_lga.to_crs("EPSG:32631")
    bldg_with_lga["footprint_area_m2"] = bldg_with_lga_sqm.geometry.area

    # Filter to > 1000 m²
    large_bldgs = bldg_with_lga[bldg_with_lga["footprint_area_m2"] > 1000.0].copy()
    logger.info(f"Buildings > 1000 m²: {len(large_bldgs)}")

    # Keep only requested columns
    result_gdf = large_bldgs[["id", "height", "footprint_area_m2", "lga", "geometry"]].copy()
    # Ensure EPSG:4326 for output
    if result_gdf.crs is None or result_gdf.crs.to_string() != "EPSG:4326":
        result_gdf = result_gdf.to_crs("EPSG:4326")

    # ------------------------------------------------------------------
    # 6. Write buildings GeoParquet
    # ------------------------------------------------------------------
    logger.info("Step 5: Writing lagos_buildings.geoparquet...")
    result_gdf.to_parquet(
        "/work/lagos_buildings.geoparquet",
        engine="pyogrio",
        compression="zstd",
    )
    logger.info(f"Written {len(result_gdf)} building footprints.")

    # ------------------------------------------------------------------
    # 7. Compute per-LGA summary
    # ------------------------------------------------------------------
    logger.info("Step 6: Computing per-LGA summary...")
    summary_rows = []
    for lga_name, grp in result_gdf.groupby("lga"):
        n_buildings = len(grp)
        total_footprint_m2 = grp["footprint_area_m2"].sum()
        has_height = grp["height"].notna()
        n_with_height = has_height.sum()
        if n_with_height > 0:
            p50_height_m = float(grp.loc[has_height, "height"].median())
        else:
            p50_height_m = None
        summary_rows.append({
            "lga": lga_name,
            "n_buildings": n_buildings,
            "total_footprint_m2": total_footprint_m2,
            "n_with_height": n_with_height,
            "p50_height_m": p50_height_m,
        })

    summary_df = pd.DataFrame(summary_rows)
    # Sort alphabetically by LGA name for readability
    summary_df = summary_df.sort_values("lga").reset_index(drop=True)
    summary_df.to_parquet("/work/lagos_building_summary.parquet", index=False, compression="zstd")
    logger.info(f"Written summary for {len(summary_df)} LGAs.")
    print("\n--- Summary ---")
    print(summary_df.to_string(index=False))


if __name__ == "__main__":
    main()
Wrote /work/solve.py (9692 chars).
3:33
Bash cd /work && python solve.py 2>&1
2026-06-08 10:28:18,752 INFO Step 1: Downloading Lagos LGA boundaries...
2026-06-08 10:28:18,752 INFO Downloading division_area (attempt 1/3) ...
2026-06-08 10:28:26,657 WARNING Batch read ended: 
2026-06-08 10:28:26,657 INFO Read 75 division_area records.
2026-06-08 10:28:26,658 INFO Downloading division (attempt 1/3) ...
2026-06-08 10:28:30,184 WARNING Batch read ended: 
2026-06-08 10:28:30,184 INFO Read 1406 division records.
2026-06-08 10:28:30,195 INFO Found 20 Lagos LGAs (counties).
Traceback (most recent call last):
  File "/work/solve.py", line 235, in <module>
    main()
    ~~~~^^
  File "/work/solve.py", line 105, in main
    primary = row["names"].get("primary", row["names"].get("common", {}).get("en"))
                                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'get'
3:49
The code fails because `row["names"]` is a dict but `row["names"].get("common")` returns `None` and then I try to call `.get("en")` on it. Let me fix this.
3:49
Read /work/solve.py
    95	    mask_county = pc.equal(lagos_divs.column("subtype"), "county")
    96	    county_ids_set = set(lagos_divs.filter(mask_county).column("id").to_pylist())
    97	    logger.info(f"Found {len(county_ids_set)} Lagos LGAs (counties).")
    98	
    99	    # Build name lookup: division_id -> primary name
   100	    county_names_map = {}
   101	    for row in lagos_divs.filter(mask_county).to_pylist():
   102	        cid = row["id"]
   103	        primary = None
   104	        if row.get("names") and isinstance(row["names"], dict):
   105	            primary = row["names"].get("primary", row["names"].get("common", {}).get("en"))
   106	        county_names_map[cid] = primary or cid
   107	
   108	    # Filter division_area to only Lagos LGAs
   109	    area_div_ids = div_area_table.column("division_id")
   110	    area_mask = pc.is_in(area_div_ids, pa.array(list(county_ids_set)))
   111	    lga_area_table = div_area_table.filter(area_mask)
   112	    logger.info(f"LGA area records: {len(lga_area_table)}")
   113	
   114	    # Decode geometries for LGA areas
<system-reminder>Showed lines 95-114 of 235. Re-call Read with offset=115 to continue, or raise limit.</system-reminder>
3:54
The issue is on line 104-105. The `row["names"]` from PyArrow's `to_pylist()` for division records returns a dict-like structure. Let me check what it actually looks like. It's a struct type (not a dict), so `row.get("names")` will return a dict-like struct object. Let me inspect the actual structure.

Actually the error says `'NoneType' object has no attribute 'get'` at `row["names"].get("common", {}).get("en")`. So `row["names"]` is okay (it's a dict-like struct), but `row["names"].get("common")` returns None, and None doesn't have `.get()`. I should just try to extract the primary name safely.
3:54
Edit /work/solve.py
Edited /work/solve.py.
3:57
Let me also check what the "names" column structure looks like from the PyArrow schema. In the Overture schema, `names` is a struct with `primary`, `common` (map), and `rules`. When reading with record_batch_reader, the struct fields should be accessible as dict items. Let me check what the actual values look like.
3:57
Bash python -c "
python -c "
from overturemaps.core import record_batch_reader
import pyarrow as pa

bbox = (2.5, 6.0, 4.5, 7.0)
reader = record_batch_reader('division', bbox=bbox)
batches = []
while True:
    try:
        batches.append(reader.read_next_batch())
    except:
        break
table = pa.Table.from_batches(batches)

# Filter to Lagos counties
import pyarrow.compute as pc
mask_la = pc.equal(table.column('region'), 'NG-LA')
lagos = table.filter(mask_la)
mask_c = pc.equal(lagos.column('subtype'), 'county')
counties = lagos.filter(mask_c)

# Check names column type
print('Names column type:', counties.column('names').type)
print()

# Get a sample value
names_vals = counties.column('names').to_pylist()
for i, n in enumerate(names_vals[:3]):
    print(f'Record {i}: type={type(n).__name__}, value={n}')
" 2>&1
Names column type: struct<primary: string, common: map<string, string ('common')>, rules: list<element: struct<variant: string, language: string, perspectives: struct<mode: string, countries: list<element: string>>, value: string, between: list<element: double>, side: string>>>

Record 0: type=dict, value={'primary': 'Amuwo Odofin', 'common': None, 'rules': None}
Record 1: type=dict, value={'primary': 'Ikeja', 'common': [('ru', 'Икеджа')], 'rules': None}
Record 2: type=dict, value={'primary': 'Lagos Mainland', 'common': None, 'rules': None}
4:06
So the names are dicts with 'primary' key. The issue is that there could be a case where `names` is `None` or the 'common' key has `None` value (not a dict). Let me fix the code to handle this properly.
4:06
4:06
Edit /work/solve.py
Edited /work/solve.py.
4:08
Bash cd /work && python solve.py 2>&1
2026-06-08 10:28:53,404 INFO Step 1: Downloading Lagos LGA boundaries...
2026-06-08 10:28:53,404 INFO Downloading division_area (attempt 1/3) ...
2026-06-08 10:29:13,908 WARNING Batch read ended: 
2026-06-08 10:29:13,908 INFO Read 75 division_area records.
2026-06-08 10:29:13,908 INFO Downloading division (attempt 1/3) ...
2026-06-08 10:29:19,178 WARNING Batch read ended: 
2026-06-08 10:29:19,179 INFO Read 1406 division records.
2026-06-08 10:29:19,188 INFO Found 20 Lagos LGAs (counties).
2026-06-08 10:29:19,198 INFO LGA area records: 20
2026-06-08 10:29:19,229 INFO LGA boundaries: 20 records.
2026-06-08 10:29:19,229 INFO LGAs: ['Agege', 'Ajeromi/Ifelodun', 'Alimosho', 'Amuwo Odofin', 'Apapa', 'Badagry', 'Epe', 'Eti Osa', 'Ibeju Lekki', 'Ifako/Ijaye', 'Ikeja', 'Kosofe', 'Lagos Island', 'Lagos Mainland', 'Mushin', 'Ojo', 'Oshodi/Isolo', 'Shomolu', 'Surulere', 'Ìkòròdú']
2026-06-08 10:29:19,229 INFO Step 2: Downloading building footprints...
2026-06-08 10:29:19,229 INFO Downloading building (attempt 1/3) ...
2026-06-08 10:31:27,234 WARNING Batch read ended: 
2026-06-08 10:31:27,254 INFO Read 5233143 building records.
2026-06-08 10:31:27,255 INFO Total building records: 5233143
2026-06-08 10:31:27,255 INFO Decoding building geometries...
2026-06-08 10:32:06,906 INFO Buildings with valid geometry: 5233143
2026-06-08 10:32:06,906 INFO Step 3: Spatial join — assigning buildings to LGAs...
2026-06-08 10:32:22,202 INFO Buildings after spatial join: 2246328
2026-06-08 10:32:22,203 INFO Step 4: Computing footprint areas...
2026-06-08 10:32:29,676 INFO Buildings > 1000 m²: 7272
2026-06-08 10:32:29,685 INFO Step 5: Writing lagos_buildings.geoparquet...
Traceback (most recent call last):
  File "/work/solve.py", line 236, in <module>
    main()
    ~~~~^^
  File "/work/solve.py", line 197, in main
    result_gdf.to_parquet(
    ~~~~~~~~~~~~~~~~~~~~~^
        "/work/lagos_buildings.geoparquet",
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        engine="pyogrio",
        ^^^^^^^^^^^^^^^^^
        compression="zstd",
        ^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/usr/local/lib/python3.14/site-packages/geopandas/geodataframe.py", line 1457, in to_parquet
    raise ValueError(
    ...<2 lines>...
    )
ValueError: GeoPandas only supports using pyarrow as the engine for to_parquet: 'pyogrio' passed instead.
7:53
The GeoParquet write needs `engine="pyarrow"` not `pyogrio`. Let me fix that.
7:53
Edit /work/solve.py
Edited /work/solve.py.
7:55
Let me also check that `Ìkòròdú` is handled properly - the `ì` characters might cause issues. Let me also check the index column handling.

Actually, the earlier run showed "Buildings after spatial join: 2246328" - that's a lot of buildings. And then "Buildings > 1000 m²: 7272". Let me see if the output was successful now.
7:55
Bash cd /work && python solve.py 2>&1
2026-06-08 10:32:41,647 INFO Step 1: Downloading Lagos LGA boundaries...
2026-06-08 10:32:41,647 INFO Downloading division_area (attempt 1/3) ...
2026-06-08 10:32:51,311 WARNING Batch read ended: 
2026-06-08 10:32:51,313 INFO Read 75 division_area records.
2026-06-08 10:32:51,313 INFO Downloading division (attempt 1/3) ...
2026-06-08 10:33:07,285 WARNING Batch read ended: 
2026-06-08 10:33:07,286 INFO Read 1406 division records.
2026-06-08 10:33:07,291 INFO Found 20 Lagos LGAs (counties).
2026-06-08 10:33:07,306 INFO LGA area records: 20
2026-06-08 10:33:07,379 INFO LGA boundaries: 20 records.
2026-06-08 10:33:07,380 INFO LGAs: ['Agege', 'Ajeromi/Ifelodun', 'Alimosho', 'Amuwo Odofin', 'Apapa', 'Badagry', 'Epe', 'Eti Osa', 'Ibeju Lekki', 'Ifako/Ijaye', 'Ikeja', 'Kosofe', 'Lagos Island', 'Lagos Mainland', 'Mushin', 'Ojo', 'Oshodi/Isolo', 'Shomolu', 'Surulere', 'Ìkòròdú']
2026-06-08 10:33:07,380 INFO Step 2: Downloading building footprints...
2026-06-08 10:33:07,380 INFO Downloading building (attempt 1/3) ...
2026-06-08 10:35:08,342 WARNING Batch read ended: 
2026-06-08 10:35:08,364 INFO Read 5233143 building records.
2026-06-08 10:35:08,365 INFO Total building records: 5233143
2026-06-08 10:35:08,365 INFO Decoding building geometries...
2026-06-08 10:35:46,107 INFO Buildings with valid geometry: 5233143
2026-06-08 10:35:46,107 INFO Step 3: Spatial join — assigning buildings to LGAs...
2026-06-08 10:36:02,354 INFO Buildings after spatial join: 2246328
2026-06-08 10:36:02,355 INFO Step 4: Computing footprint areas...
2026-06-08 10:36:08,991 INFO Buildings > 1000 m²: 7272
2026-06-08 10:36:08,995 INFO Step 5: Writing lagos_buildings.geoparquet...
2026-06-08 10:36:09,121 INFO Written 7272 building footprints.
2026-06-08 10:36:09,121 INFO Step 6: Computing per-LGA summary...
2026-06-08 10:36:09,152 INFO Written summary for 20 LGAs.

--- Summary ---
             lga  n_buildings  total_footprint_m2  n_with_height  p50_height_m
           Agege           55        1.178889e+05              0           NaN
Ajeromi/Ifelodun           60        1.078803e+05              0           NaN
        Alimosho          353        6.476227e+05              0           NaN
    Amuwo Odofin          835        1.747930e+06              0           NaN
           Apapa          286        6.083739e+05              0           NaN
         Badagry          124        2.259610e+05              0           NaN
             Epe          345        8.156054e+05              0           NaN
         Eti Osa         1055        1.828513e+06            237          20.0
     Ibeju Lekki          596        1.793963e+06              0           NaN
     Ifako/Ijaye          120        2.485358e+05              0           NaN
           Ikeja          928        3.199985e+06             46          15.0
          Kosofe          166        3.493556e+05              0           NaN
    Lagos Island          117        2.261127e+05              9          20.0
  Lagos Mainland          123        2.295373e+05              0           NaN
          Mushin          463        1.063793e+06              1          35.0
             Ojo          232        4.085015e+05              0           NaN
    Oshodi/Isolo          418        7.928819e+05              3          27.0
         Shomolu          124        2.006563e+05              3          17.0
        Surulere          340        7.777677e+05              0           NaN
         Ìkòròdú          532        1.852702e+06              2           8.0
11:43
The script ran successfully and produced the two output files. Let me verify them.

A few things to check:
1. The p50_height_m shows NaN for LGAs without heights - the spec says "null otherwise". NaN in pandas gets written to Parquet as null by default, which should be fine. But let me verify.
2. The `lga` column name is correct.
3. Let me check the output files.
11:43
11:43
Bash cd /work && python -c "
cd /work && python -c "
import geopandas as gpd
import pandas as pd

# Check buildings
bldg = gpd.read_parquet('lagos_buildings.geoparquet')
print('=== lagos_buildings.geoparquet ===')
print(f'Rows: {len(bldg)}')
print(f'CRS: {bldg.crs}')
print(f'Columns: {list(bldg.columns)}')
print(bldg.dtypes)
print()
print(bldg.head(3).to_string())
print()

# Check summary
smry = pd.read_parquet('lagos_building_summary.parquet')
print('=== lagos_building_summary.parquet ===')
print(f'Rows: {len(smry)}')
print(f'Columns: {list(smry.columns)}')
print(smry.dtypes)
print()
print(smry.to_string(index=False))
" 2>&1
=== lagos_buildings.geoparquet ===
Rows: 7272
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}}
Columns: ['id', 'height', 'footprint_area_m2', 'lga', 'geometry']
id                        str
height                float64
footprint_area_m2     float64
lga                       str
geometry             geometry
dtype: object

                                         id  height  footprint_area_m2      lga                                                                                                                                                                                                                                                                                                  geometry
61112  e24f1bf5-e957-4867-859b-a6a578fd9b06     NaN        1072.458049  Badagry                                                                                                                                                                                                           POLYGON ((2.70836 6.37804, 2.70816 6.37804, 2.70817 6.37759, 2.70837 6.37759, 2.70836 6.37804))
62195  5df57f70-f431-495f-98cb-eff96e1ca151     NaN        1052.426977  Badagry                                                                    POLYGON ((2.72675 6.37881, 2.72672 6.37907, 2.72646 6.37904, 2.72645 6.37908, 2.7263 6.37906, 2.72632 6.37888, 2.72657 6.37891, 2.72657 6.37896, 2.72664 6.37897, 2.72665 6.37889, 2.72652 6.37888, 2.72654 6.37878, 2.72675 6.37881))
63188  cfb03953-1851-4ec2-a087-3283cd510753     NaN        1077.286127  Badagry  POLYGON ((2.7137 6.37984, 2.7136 6.37984, 2.71361 6.37962, 2.71356 6.37962, 2.71356 6.37955, 2.71398 6.37957, 2.71398 6.37964, 2.71393 6.37964, 2.71392 6.37983, 2.71382 6.37983, 2.71383 6.37966, 2.71382 6.37966, 2.71381 6.37983, 2.71372 6.37983, 2.71373 6.37963, 2.71371 6.37963, 2.7137 6.37984))

=== lagos_building_summary.parquet ===
Rows: 20
Columns: ['lga', 'n_buildings', 'total_footprint_m2', 'n_with_height', 'p50_height_m']
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
           Agege           55        1.178889e+05              0           NaN
Ajeromi/Ifelodun           60        1.078803e+05              0           NaN
        Alimosho          353        6.476227e+05              0           NaN
    Amuwo Odofin          835        1.747930e+06              0           NaN
           Apapa          286        6.083739e+05              0           NaN
         Badagry          124        2.259610e+05              0           NaN
             Epe          345        8.156054e+05              0           NaN
         Eti Osa         1055        1.828513e+06            237          20.0
     Ibeju Lekki          596        1.793963e+06              0           NaN
     Ifako/Ijaye          120        2.485358e+05              0           NaN
           Ikeja          928        3.199985e+06             46          15.0
          Kosofe          166        3.493556e+05              0           NaN
    Lagos Island          117        2.261127e+05              9          20.0
  Lagos Mainland          123        2.295373e+05              0           NaN
          Mushin          463        1.063793e+06              1          35.0
             Ojo          232        4.085015e+05              0           NaN
    Oshodi/Isolo          418        7.928819e+05              3          27.0
         Shomolu          124        2.006563e+05              3          17.0
        Surulere          340        7.777677e+05              0           NaN
         Ìkòròdú          532        1.852702e+06              2           8.0
11:51
Let me check a few things:

1. The CRS is EPSG:4326 (WGS 84) - correct.
2. Columns: `id`, `height`, `footprint_area_m2`, `lga`, `geometry` - correct.
3. The summary has `lga`, `n_buildings`, `total_footprint_m2`, `n_with_height`, `p50_height_m` - correct.

However, the spec says `p50_height_m` should be null when no heights are available - but in the Parquet it shows `NaN`. Let me check if NaN is fine or if it should be None/null.

Actually, looking at the summary more carefully, the `p50_height_m` is `NaN` (float NaN) which is fine for Parquet. The user said "null otherwise" - NaN in float columns is the standard representation of null in Parquet. That's fine.

Wait, but for the `p50_height_m` column, the user wants `null` when median height is not available (n_with_height=0). Currently it shows `NaN`. Let me check if the parquet stores these as actual nulls or just NaN values.

Let me look at how pandas handles this. When we do `p50_height_m = None` in the summary_rows list, and then create a DataFrame, the column dtype will be `float64` and the None will become NaN. When writing to parquet, NaN is preserved. This is fine for the use case.

Actually, I should verify that the NaN values are correctly stored as null in Parquet. Pandas may write NaN as actual NaN float values rather than null. Let me check.

Actually, in the Parquet format, NaN in float columns is preserved as NaN. For the user's purpose, "null" when the median isn't available, NaN in a float column is semantically equivalent.

Let me also verify the footprint_area_m2 values are reasonable. 1000 m² is about a medium-sized building footprint. Lagos buildings >1000 m²... we got 7272 such buildings. That seems reasonable.

Let me check the coordinate magnitudes to ensure they're in degrees. Looking at the geometry output:
- `POLYGON ((2.70836 6.37804, ...))` - these are longitude/latitude in degrees, correct for EPSG:4326.

Everything looks good! Let me verify the files exist.
11:51
11:51
Bash ls -lh /work/lagos_buildings.geoparquet /work/lagos_building_summary.parquet
-rw-r--r-- 1 agent agent 4.1K Jun  8 10:36 /work/lagos_building_summary.parquet
-rw-r--r-- 1 agent agent 1.2M Jun  8 10:36 /work/lagos_buildings.geoparquet
11:51
Bash cd /work && python -c "
cd /work && python -c "
import pandas as pd
smry = pd.read_parquet('lagos_building_summary.parquet')
# Check null values in p50_height_m
null_mask = smry['p50_height_m'].isna()
print('LGAs with null p50_height_m:')
print(smry.loc[null_mask, ['lga', 'n_with_height', 'p50_height_m']].to_string())
print()
print('LGAs with non-null p50_height_m:')
print(smry.loc[~null_mask, ['lga', 'n_with_height', 'p50_height_m']].to_string())
"
LGAs with null p50_height_m:
                 lga  n_with_height  p50_height_m
0              Agege              0           NaN
1   Ajeromi/Ifelodun              0           NaN
2           Alimosho              0           NaN
3       Amuwo Odofin              0           NaN
4              Apapa              0           NaN
5            Badagry              0           NaN
6                Epe              0           NaN
8        Ibeju Lekki              0           NaN
9        Ifako/Ijaye              0           NaN
11            Kosofe              0           NaN
13    Lagos Mainland              0           NaN
15               Ojo              0           NaN
18          Surulere              0           NaN

LGAs with non-null p50_height_m:
             lga  n_with_height  p50_height_m
7        Eti Osa            237          20.0
10         Ikeja             46          15.0
12  Lagos Island              9          20.0
14        Mushin              1          35.0
16  Oshodi/Isolo              3          27.0
17       Shomolu              3          17.0
19       Ìkòròdú              2           8.0
11:55
Everything looks good. Let me verify the outputs match the specification:

1. `lagos_buildings.geoparquet` - EPSG:4326 GeoParquet with columns `id`, `height`, `footprint_area_m2`, `lga`, and `geometry` ✓
2. `lagos_building_summary.parquet` - plain Parquet (no geometry) with columns `lga`, `n_buildings`, `total_footprint_m2`, `n_with_height`, `p50_height_m` ✓

- Buildings are filtered to > 1000 m² ✓ (7272 buildings)
- Per-LGA rollup includes count, total area, count with non-null height, median height (null when not available) ✓
- p50_height_m is NaN (null) for LGAs with no height data ✓

Let me do a final check on coordinate magnitudes to ensure CRS sanity.
11:55
Bash cd /work && python -c "
cd /work && python -c "
import geopandas as gpd
bldg = gpd.read_parquet('lagos_buildings.geoparquet')
# Check coordinate bounds
bounds = bldg.geometry.bounds
print(f'X range: {bounds.minx.min():.4f} to {bounds.maxx.max():.4f}')
print(f'Y range: {bounds.miny.min():.4f} to {bounds.maxy.max():.4f}')
print(f'Expected: ~2.5 to ~4.5 lon, ~6.2 to ~6.8 lat for Lagos')
"
X range: 2.7082 to 4.3332
Y range: 6.3776 to 6.6962
Expected: ~2.5 to ~4.5 lon, ~6.2 to ~6.8 lat for Lagos
12:02
Everything looks correct:

1. **lagos_buildings.geoparquet** - EPSG:4326 GeoParquet with 7272 rows, columns: `id`, `height`, `footprint_area_m2`, `lga`, `geometry` ✓
   - CRS is EPSG:4326 ✓
   - Coordinates are in degrees (lon ~2.7-4.3, lat ~6.4-6.7) ✓
   - Buildings filtered to > 1000 m² ✓

2. **lagos_building_summary.parquet** - Plain Parquet with 20 rows, columns: `lga`, `n_buildings`, `total_footprint_m2`, `n_with_height`, `p50_height_m` ✓
   - No geometry column ✓
   - Null p50_height_m where n_with_height=0 ✓
   - Non-null values where heights exist ✓

Let me do one more check - the specification says p50_height_m should be null when not available. In the output, it's NaN, which is the Parquet representation of null for float columns. That's correct - nulls in Parquet float columns show as NaN in pandas.

Let me also verify the file sizes are reasonable.
12:02
12:02
Bash cd /work && python -c "
cd /work && python -c "
import geopandas as gpd
import pandas as pd

# 1. Check buildings geoparquet spec compliance
b = gpd.read_parquet('lagos_buildings.geoparquet')
print('=== lagos_buildings.geoparquet ===')
assert set(b.columns) == {'id', 'height', 'footprint_area_m2', 'lga', 'geometry'}, f'Wrong columns: {b.columns}'
assert str(b.crs).startswith('EPSG:4326') or '4326' in str(b.crs), f'Wrong CRS: {b.crs}'
assert len(b) == 7272
assert (b['footprint_area_m2'] > 1000).all()
print('All assertions PASS')

# 2. Check summary parquet
s = pd.read_parquet('lagos_building_summary.parquet')
print()
print('=== lagos_building_summary.parquet ===')
assert set(s.columns) == {'lga', 'n_buildings', 'total_footprint_m2', 'n_with_height', 'p50_height_m'}, f'Wrong columns: {s.columns}'
assert s['n_with_height'].sum() == b['height'].notna().sum(), f'n_with_height sum {s[\"n_with_height\"].sum()} != {b[\"height\"].notna().sum()}'
# Verify p50_height_m is null when n_with_height=0
null_lgas = s.loc[s['n_with_height'] == 0, 'p50_height_m']
assert null_lgas.isna().all(), f'LGAs with n_with_height=0 should have null p50: {null_lgas}'
print('All assertions PASS')
" 2>&1
=== lagos_buildings.geoparquet ===
All assertions PASS

=== lagos_building_summary.parquet ===
All assertions PASS
12:09
All checks pass. Let me summarize what was done.
12:09