spa-l2-lagos-hotspot-overlaps
Map (reference ← swipe → agent)
0:00
Hot-spot map for the Lagos housing brief. `lagos_landuse` carries land-use polygons with a population density attribute; `lagos_hex_grid` is a hex grid. I need the top 10 % of hex cells ranked by area-weighted mean population density across overlapping land-use polygons. Tiny polygons under 100 m² are noise and should be excluded.
I need two output files that share exactly the same set of `hex_id` values, one row per cell. `hotspots.geoparquet` is a GeoParquet (Polygon or MultiPolygon geometry) in Nigeria's national grid with columns `hex_id`, `rank`, `area_weighted_density`, and `geometry`. `hotspot_ranking.parquet` is a plain Parquet with no geometry, carrying `hex_id`, `rank`, `area_weighted_density`, `n_overlap_polygons` (integer), and `n_slivers_filtered` (integer). In both files `rank` must be a unique integer starting at 1 where ascending rank corresponds to descending `area_weighted_density`.
0:00
System info
{
"adapter": "openrouter",
"model": "google/gemma-4-26b-a4b-it",
"base_url": "https://openrouter.ai/api/v1"
}
0:02
Bash
ls -F
lagos_hex_grid.geojson lagos_landuse.geojson
0:05
Write
/work/inspect_data.py
content · 20 lines · py
import geopandas as gpd
# Check the first few rows and CRS of both files
try:
landuse = gpd.read_file('lagos_landuse.geojson')
print("Landuse columns:", landuse.columns)
print("Landuse CRS:", landuse.crs)
print("Landuse first 5 rows:\n", landuse.head())
print("Landuse area check:", landuse.geometry.area.describe())
except Exception as e:
print(f"Error reading landuse: {e}")
try:
hex_grid = gpd.read_file('lagos_hex_grid.geojson')
print("\nHex grid columns:", hex_grid.columns)
print("Hex grid CRS:", hex_grid.crs)
print("Hex grid first 5 rows:\n", hex_grid.head())
except Exception as e:
print(f"Error reading hex_grid: {e}")
Wrote /work/inspect_data.py (675 chars).
0:06
Bash
python /work/inspect_data.py
Landuse columns: Index(['id', 'class', 'pop_density', 'geometry'], dtype='str')
Landuse CRS: EPSG:4326
Landuse first 5 rows:
id ... geometry
0 004ca249-d2cf-3617-906e-dea1138e762b ... POLYGON ((3.46992 6.44615, 3.46993 6.44566, 3....
1 006a7dc6-2b5e-34fa-84a2-8a679620c9de ... POLYGON ((3.42136 6.43091, 3.42118 6.43051, 3....
2 00a334d1-7e8e-3e4c-8565-3501d4ed74df ... POLYGON ((3.45824 6.43361, 3.45872 6.43306, 3....
3 00af9ff7-3138-37a7-bbb8-0a2fbe2bb8e2 ... POLYGON ((3.34834 6.61603, 3.34842 6.61593, 3....
4 00d4b7d6-09b5-3d43-97c5-009fd8c97362 ... POLYGON ((3.31147 6.48073, 3.31041 6.47971, 3....
[5 rows x 4 columns]
Landuse area check: count 5.542000e+03
mean 7.606990e-06
std 7.631376e-05
min 8.184751e-11
25% 3.835068e-09
50% 7.451139e-09
75% 4.147784e-07
max 2.854502e-03
dtype: float64
Hex grid columns: Index(['hex_id', 'geometry'], dtype='str')
Hex grid CRS: EPSG:4326
Hex grid first 5 rows:
hex_id geometry
0 H000-000 POLYGON ((3.25491 6.40082, 3.2523 6.40535, 3.2...
1 H000-001 POLYGON ((3.25491 6.40987, 3.2523 6.41439, 3.2...
2 H000-002 POLYGON ((3.25492 6.41891, 3.25231 6.42344, 3....
3 H000-003 POLYGON ((3.25492 6.42796, 3.25231 6.43248, 3....
4 H000-004 POLYGON ((3.25493 6.43701, 3.25232 6.44153, 3....
/work/inspect_data.py:9: UserWarning: Geometry is in a geographic CRS. Results from 'area' are likely incorrect. Use 'GeoSeries.to_crs()' to re-project geometries to a projected CRS before this operation.
print("Landuse area check:", landuse.geometry.area.describe())
0:27
Write
/work/solve.py
content · 110 lines · py
import geopandas as gpd
import pandas as pd
def solve():
# 1. Load data
landuse = gpd.read_file('lagos_landuse.geojson')
hex_grid = gpd.read_file('lagos_hex_grid.geojson')
# Nigeria National Grid is EPSG:26332 (UTM zone 32N) or similar.
# Let's use EPSG:3857 or a local UTM to ensure accurate area calculations.
# Since the prompt says "Nigeria's national grid", I'll check for a likely candidate
# or just use a standard UTM zone for Nigeria if not specified.
# Actually, many GIS tasks assume EPSG:3857 for general work,
# but let's use EPSG:26332 (UTM 32N) which covers Lagos.
target_crs = 'EPSG:26332'
# 2. Filter slivers in landuse
# We must project to metric CRS to filter by area in m^2
landuse_proj = landuse.to_crs(target_crs)
# Count slivers before filtering
landuse['is_sliver'] = landuse_proj.geometry.area < 100
n_slivers_filtered = landuse['is_sliver'].sum()
landuse_clean = landuse[~landuse['is_sliver']].copy()
landuse_clean_proj = landuse_clean.to_crs(target_crs)
# 3. Spatial Join to calculate area-weighted density
# We need the intersection of each hex with each landuse polygon
# To do this efficiently, we intersect the hexes with landuse
hex_grid_proj = hex_grid.to_crs(target_crs)
# Perform intersection
# This returns geometries that are the intersection of the two
intersections = gpd.overlay(hex_grid_proj, landuse_clean_proj, how='intersection')
# The intersection geometries now have the attributes of both
# We need to calculate the area of these intersection pieces
intersections['inter_area'] = intersections.geometry.area
# We also need the original hex area to weight it?
# The prompt says "area-weighted mean population density across overlapping land-use polygons"
# Formula for weighted mean: sum(density_i * area_i) / sum(area_i)
# where area_i is the area of the landuse polygon within the hex.
# Calculate numerator: sum(pop_density * inter_area)
intersections['weighted_pop'] = intersections['pop_density'] * intersections['inter_area']
# Group by hex_id
grouped = intersections.groupby('hex_id').agg(
sum_weighted_pop=('weighted_pop', 'sum'),
sum_inter_area=('inter_area', 'sum'),
n_overlap_polygons=('id', 'count') # 'id' is from landuse
).reset_index()
# Calculate the area-weighted mean population density
# We divide by the sum of the intersection areas (which is the area of the hex covered by landuse)
# Note: if sum_inter_area is 0, density should be 0.
grouped['area_weighted_density'] = grouped['sum_weighted_pop'] / grouped['sum_inter_area']
# 4. Filter top 10%
# "top 10% of hex cells ranked by area-weighted mean population density"
# This implies we take the 10% of cells with the highest density.
threshold = grouped['area_weighted_density'].quantile(0.9)
top_hexes = grouped[grouped['area_weighted_density'] >= threshold].copy()
# 5. Ranking
# "rank must be a unique integer starting at 1 where ascending rank corresponds to descending area_weighted_density"
top_hexes = top_hexes.sort_values(by='area_weighted_density', ascending=False)
top_hexes['rank'] = range(1, len(top_hexes) + 1)
# Merge back n_slivers_filtered info. Since n_slivers_filtered is a single number
# for the whole dataset, we'll just add it as a constant column or handle it per row?
# "carrying hex_id, rank, area_weighted_density, n_overlap_polygons (integer), and n_slivers_filtered (integer)"
# This implies n_slivers_filtered is a column in the parquet.
top_hexes['n_slivers_filtered'] = n_slivers_filtered
# 6. Prepare outputs
# Output 1: hotspots.geoparquet
# columns: hex_id, rank, area_weighted_density, geometry
# Must be in Nigeria's national grid (EPSG:26332)
# We need to join the geometry back from the original hex_grid
hotspots_geo = hex_grid[hex_grid['hex_id'].isin(top_hexes['hex_id'])].copy()
hotspots_geo = hotspots_geo.merge(
top_hexes[['hex_id', 'rank', 'area_weighted_density']],
on='hex_id',
how='inner'
)
# Project to target CRS
hotspots_geo = hotspots_geo.to_crs(target_crs)
# Reorder columns
hotspots_geo = hotspots_geo[['hex_id', 'rank', 'area_weighted_density', 'geometry']]
hotspots_geo.to_parquet('hotspots.geoparquet')
# Output 2: hotspot_ranking.parquet
# columns: hex_id, rank, area_weighted_density, n_overlap_polygons, n_slivers_filtered
# No geometry
ranking_df = top_hexes[['hex_id', 'rank', 'area_weighted_density', 'n_overlap_polygons', 'n_slivers_filtered']]
ranking_df.to_parquet('hotspot_ranking.parquet')
print("Files created successfully.")
print(f"Total top hexes: {len(top_hexes)}")
print(f"Slivers filtered: {n_slivers_filtered}")
if __name__ == "__main__":
solve()
Wrote /work/solve.py (4996 chars).
0:28
Bash
python /work/solve.py
Files created successfully. Total top hexes: 104 Slivers filtered: 3022
0:35
Bash
# Check columns of the outputs
# Check columns of the outputs
python -c "import pandas as pd; print('hotspot_ranking.parquet columns:', pd.read_parquet('hotspot_ranking.parquet').columns.tolist())"
python -c "import geopandas as gpd; print('hotspots.geoparquet columns:', gpd.read_parquet('hotspots.geoparquet').columns.tolist()); print('hotspots.geoparquet CRS:', gpd.read_parquet('hotspots.geoparquet').crs)"
# Check if hex_id match
python -c "import pandas as pd; h1 = pd.read_parquet('hotspots.geoparquet')['hex_id'].sort_values(); h2 = pd.read_parquet('hotspot_ranking.parquet')['hex_id'].sort_values(); print('Match:', h1.equals(h2))"
# Check rank uniqueness and range
python -c "import pandas as pd; df = pd.read_parquet('hotspot_ranking.parquet'); print('Unique ranks:', df['rank'].nunique() == len(df)); print('Min rank:', df['rank'].min()); print('Max rank:', df['rank'].max())"
# Check density sorting
python -c "import pandas as pd; df = pd.read_parquet('hotspot_ranking.parquet'); print('Sorted density:', df['area_weighted_density'].is_monotonic_decreasing)"
hotspot_ranking.parquet columns: ['hex_id', 'rank', 'area_weighted_density', 'n_overlap_polygons', 'n_slivers_filtered']
hotspots.geoparquet columns: ['hex_id', 'rank', 'area_weighted_density', 'geometry']
hotspots.geoparquet CRS: {"$schema": "https://proj.org/schemas/v0.7/projjson.schema.json", "type": "ProjectedCRS", "name": "Minna / UTM zone 32N", "base_crs": {"name": "Minna", "datum": {"type": "GeodeticReferenceFrame", "name": "Minna", "ellipsoid": {"name": "Clarke 1880 (RGS)", "semi_major_axis": 6378249.145, "inverse_flattening": 293.465}}, "coordinate_system": {"subtype": "ellipsoidal", "axis": [{"name": "Geodetic latitude", "abbreviation": "Lat", "direction": "north", "unit": "degree"}, {"name": "Geodetic longitude", "abbreviation": "Lon", "direction": "east", "unit": "degree"}]}, "id": {"authority": "EPSG", "code": 4263}}, "conversion": {"name": "UTM zone 32N", "method": {"name": "Transverse Mercator", "id": {"authority": "EPSG", "code": 9807}}, "parameters": [{"name": "Latitude of natural origin", "value": 0, "unit": "degree", "id": {"authority": "EPSG", "code": 8801}}, {"name": "Longitude of natural origin", "value": 9, "unit": "degree", "id": {"authority": "EPSG", "code": 8802}}, {"name": "Scale factor at natural origin", "value": 0.9996, "unit": "unity", "id": {"authority": "EPSG", "code": 8805}}, {"name": "False easting", "value": 500000, "unit": "metre", "id": {"authority": "EPSG", "code": 8806}}, {"name": "False northing", "value": 0, "unit": "metre", "id": {"authority": "EPSG", "code": 8807}}]}, "coordinate_system": {"subtype": "Cartesian", "axis": [{"name": "Easting", "abbreviation": "E", "direction": "east", "unit": "metre"}, {"name": "Northing", "abbreviation": "N", "direction": "north", "unit": "metre"}]}, "scope": "Engineering survey, topographic mapping.", "area": "Nigeria - offshore deep water - east of 6\u00b0E.", "bbox": {"south_latitude": 2.61, "west_longitude": 6, "north_latitude": 3.68, "east_longitude": 7.82}, "id": {"authority": "EPSG", "code": 26332}}
Match: False
Unique ranks: True
Min rank: 1
Max rank: 104
Sorted density: True
0:51
Write
/work/solve.py
content · 92 lines · py
import geopandas as gpd
import pandas as pd
def solve():
# 1. Load data
landuse = gpd.read_file('lagos_landuse.geojson')
hex_grid = gpd.read_file('lagos_hex_grid.geojson')
# Nigeria National Grid is EPSG:26332 (UTM zone 32N)
target_crs = 'EPSG:26332'
# 2. Filter slivers in landuse
landuse_proj = landuse.to_crs(target_crs)
# Count slivers before filtering
landuse['is_sliver'] = landuse_proj.geometry.area < 100
n_slivers_filtered = int(landuse['is_sliver'].sum())
landuse_clean = landuse[~landuse['is_sliver']].copy()
landuse_clean_proj = landuse_clean.to_crs(target_crs)
# 3. Spatial Join / Intersection
hex_grid_proj = hex_grid.to_crs(target_crs)
# Intersection: this yields polygons that are parts of both hex and landuse
intersections = gpd.overlay(hex_grid_proj, landuse_clean_proj, how='intersection')
# Calculate intersection area
intersections['inter_area'] = intersections.geometry.area
# Calculate weighted population component
intersections['weighted_pop'] = intersections['pop_density'] * intersections['inter_area']
# Group by hex_id
# n_overlap_polygons: count of original landuse polygons inside the hex
# Note: 'id' is the landuse identifier
grouped = intersections.groupby('hex_id').agg(
sum_weighted_pop=('weighted_pop', 'sum'),
sum_inter_area=('inter_area', 'sum'),
n_overlap_polygons=('id', 'count')
).reset_index()
# Area-weighted mean: sum(density_i * area_i) / sum(area_i)
# Where sum(area_i) is the total area within the hex that is covered by landuse polygons
grouped['area_weighted_density'] = grouped['sum_weighted_pop'] / grouped['sum_inter_area']
# 4. Filter top 10%
# We need the top 10% of hexes by density.
# To get exactly 10%, we use quantile
threshold = grouped['area_weighted_density'].quantile(0.9)
top_hexes_df = grouped[grouped['area_weighted_density'] >= threshold].copy()
# 5. Ranking
# Sort descending by density
top_hexes_df = top_hexes_df.sort_values(by='area_weighted_density', ascending=False)
# Assign unique rank starting at 1
top_hexes_df['rank'] = range(1, len(top_hexes_df) + 1)
# Add n_slivers_filtered
top_hexes_df['n_slivers_filtered'] = n_slivers_filtered
# 6. Prepare outputs
# Output 1: hotspots.geoparquet
# columns: hex_id, rank, area_weighted_density, geometry
# Must match exactly the hex_ids in top_hexes_df
# Join geometry back from the original hex_grid (which is in EPSG:4326)
# But the prompt asks for the file in Nigeria's national grid
hotspots_geo = hex_grid.merge(
top_hexes_df[['hex_id', 'rank', 'area_weighted_density']],
on='hex_id',
how='inner'
)
hotspots_geo = hotspots_geo.to_crs(target_crs)
hotspots_geo = hotspots_geo[['hex_id', 'rank', 'area_weighted_density', 'geometry']]
hotspots_geo.to_parquet('hotspots.geoparquet')
# Output 2: hotspot_ranking.parquet
# columns: hex_id, rank, area_weighted_density, n_overlap_polygons, n_slivers_filtered
# No geometry.
# IMPORTANT: Must share EXACTLY the same hex_id values as hotspots.geoparquet
# Since we merged on hex_id using inner join, it should be correct.
ranking_df = top_hexes_df[['hex_id', 'rank', 'area_weighted_density', 'n_overlap_polygons', 'n_slivers_filtered']]
ranking_df.to_parquet('hotspot_ranking.parquet')
print("Files created successfully.")
print(f"Total top hexes: {len(top_hexes_df)}")
print(f"Slivers filtered: {n_slivers_filtered}")
if __name__ == "__main__":
solve()
Wrote /work/solve.py (3688 chars).
0:52
Bash
python /work/solve.py
Files created successfully. Total top hexes: 104 Slivers filtered: 3022
0:59
Bash
# Final verification
# Final verification
python -c "import pandas as pd; print('hotspot_ranking.parquet columns:', pd.read_parquet('hotspot_ranking.parquet').columns.tolist())"
python -c "import geopandas as gpd; print('hotspots.geoparquet columns:', gpd.read_parquet('hotspots.geoparquet').columns.tolist()); print('hotspots.geoparquet CRS:', gpd.read_parquet('hotspots.geoparquet').crs)"
# Check if hex_id match
python -c "import pandas as pd; h1 = pd.read_parquet('hotspots.geoparquet')['hex_id'].sort_values(); h2 = pd.read_parquet('hotspot_ranking.parquet')['hex_id'].sort_values(); print('Match:', h1.equals(h2))"
# Check rank uniqueness and range
python -c "import pandas as pd; df = pd.read_parquet('hotspot_ranking.parquet'); print('Unique ranks:', df['rank'].nunique() == len(df)); print('Min rank:', df['rank'].min()); print('Max rank:', df['rank'].max())"
# Check density sorting
python -c "import pandas as pd; df = pd.read_parquet('hotspot_ranking.parquet'); print('Sorted density:', df['area_weighted_density'].is_monotonic_decreasing)"
hotspot_ranking.parquet columns: ['hex_id', 'rank', 'area_weighted_density', 'n_overlap_polygons', 'n_slivers_filtered']
hotspots.geoparquet columns: ['hex_id', 'rank', 'area_weighted_density', 'geometry']
hotspots.geoparquet CRS: {"$schema": "https://proj.org/schemas/v0.7/projjson.schema.json", "type": "ProjectedCRS", "name": "Minna / UTM zone 32N", "base_crs": {"name": "Minna", "datum": {"type": "GeodeticReferenceFrame", "name": "Minna", "ellipsoid": {"name": "Clarke 1880 (RGS)", "semi_major_axis": 6378249.145, "inverse_flattening": 293.465}}, "coordinate_system": {"subtype": "ellipsoidal", "axis": [{"name": "Geodetic latitude", "abbreviation": "Lat", "direction": "north", "unit": "degree"}, {"name": "Geodetic longitude", "abbreviation": "Lon", "direction": "east", "unit": "degree"}]}, "id": {"authority": "EPSG", "code": 4263}}, "conversion": {"name": "UTM zone 32N", "method": {"name": "Transverse Mercator", "id": {"authority": "EPSG", "code": 9807}}, "parameters": [{"name": "Latitude of natural origin", "value": 0, "unit": "degree", "id": {"authority": "EPSG", "code": 8801}}, {"name": "Longitude of natural origin", "value": 9, "unit": "degree", "id": {"authority": "EPSG", "code": 8802}}, {"name": "Scale factor at natural origin", "value": 0.9996, "unit": "unity", "id": {"authority": "EPSG", "code": 8805}}, {"name": "False easting", "value": 500000, "unit": "metre", "id": {"authority": "EPSG", "code": 8806}}, {"name": "False northing", "value": 0, "unit": "metre", "id": {"authority": "EPSG", "code": 8807}}]}, "coordinate_system": {"subtype": "Cartesian", "axis": [{"name": "Easting", "abbreviation": "E", "direction": "east", "unit": "metre"}, {"name": "Northing", "abbreviation": "N", "direction": "north", "unit": "metre"}]}, "scope": "Engineering survey, topographic mapping.", "area": "Nigeria - offshore deep water - east of 6\u00b0E.", "bbox": {"south_latitude": 2.61, "west_longitude": 6, "north_latitude": 3.68, "east_longitude": 7.82}, "id": {"authority": "EPSG", "code": 26332}}
Match: False
Unique ranks: True
Min rank: 1
Max rank: 104
Sorted density: True
1:26
Write
/work/solve.py
content · 102 lines · py
import geopandas as gpd
import pandas as pd
def solve():
# 1. Load data
landuse = gpd.read_file('lagos_landuse.geojson')
hex_grid = gpd.read_file('lagos_hex_grid.geojson')
# Nigeria National Grid is EPSG:26332 (UTM zone 32N)
target_crs = 'EPSG:26332'
# 2. Filter slivers in landuse
landuse_proj = landuse.to_crs(target_crs)
# Count slivers before filtering
landuse['is_sliver'] = landuse_proj.geometry.area < 100
n_slivers_filtered = int(landuse['is_sliver'].sum())
landuse_clean = landuse[~landuse['is_sliver']].copy()
landuse_clean_proj = landuse_clean.to_crs(target_crs)
# 3. Spatial Join / Intersection
hex_grid_proj = hex_grid.to_crs(target_crs)
# Intersection: this yields polygons that are parts of both hex and landuse
intersections = gpd.overlay(hex_grid_proj, landuse_clean_proj, how='intersection')
# Calculate intersection area
intersections['inter_area'] = intersections.geometry.area
# Calculate weighted population component
intersections['weighted_pop'] = intersections['pop_density'] * intersections['inter_area']
# Group by hex_id
# n_overlap_polygons: count of original landuse polygons inside the hex
# Note: 'id' is the landuse identifier
grouped = intersections.groupby('hex_id').agg(
sum_weighted_pop=('weighted_pop', 'sum'),
sum_inter_area=('inter_area', 'sum'),
n_overlap_polygons=('id', 'count')
).reset_index()
# Area-weighted mean: sum(density_i * area_i) / sum(area_i)
# Where sum(area_i) is the total area within the hex that is covered by landuse polygons
grouped['area_weighted_density'] = grouped['sum_weighted_pop'] / grouped['sum_inter_area']
# 4. Filter top 10%
# We need the top 10% of hexes by density.
# To get exactly 10%, we use quantile
threshold = grouped['area_weighted_density'].quantile(0.9)
top_hexes_df = grouped[grouped['area_weighted_density'] >= threshold].copy()
# 5. Ranking
# Sort descending by density
top_hexes_df = top_hexes_df.sort_values(by='area_weighted_density', ascending=False)
# Assign unique rank starting at 1
top_hexes_df['rank'] = range(1, len(top_hexes_df) + 1)
# Add n_slivers_filtered
top_hexes_df['n_slivers_filtered'] = n_slivers_filtered
# 6. Prepare outputs
# Output 1: hotspots.geoparquet
# columns: hex_id, rank, area_weighted_density, geometry
# Must match exactly the hex_ids in top_hexes_df
# Join geometry back from the original hex_grid (which is in EPSG:4326)
# We'll merge the top_hexes_df with hex_grid to get geometry.
# hex_grid contains the original geometries in EPSG:4326.
hotspots_geo = hex_grid.merge(
top_hexes_df[['hex_id', 'rank', 'area_weighted_density']],
on='hex_id',
how='inner'
)
hotspots_geo = hotspots_geo.to_crs(target_crs)
hotspots_geo = hotspots_geo[['hex_id', 'rank', 'area_weighted_density', 'geometry']]
hotspots_geo.to_parquet('hotspots.geoparquet')
# Output 2: hotspot_ranking.parquet
# columns: hex_id, rank, area_weighted_density, n_overlap_polygons, n_slivers_filtered
# No geometry.
# IMPORTANT: Must share EXACTLY the same hex_id values as hotspots.geoparquet
# To ensure they match exactly, let's define the final set of hex_ids
final_hex_ids = hotspots_geo['hex_id'].unique()
# Filter top_hexes_df to include only these hex_ids and re-order to match hotspots_geo
# (Though order doesn't strictly matter for "sharing the same set", let's be safe)
ranking_df = top_hexes_df[top_hexes_df['hex_id'].isin(final_hex_ids)].copy()
# Ensure column order matches prompt exactly
ranking_df = ranking_df[['hex_id', 'rank', 'area_weighted_density', 'n_overlap_polygons', 'n_slivers_filtered']]
ranking_df.to_parquet('hotspot_ranking.parquet')
print("Files created successfully.")
print(f"Total top hexes: {len(top_hexes_df)}")
print(f"Slivers filtered: {n_slivers_filtered}")
if __name__ == "__main__":
solve()
Wrote /work/solve.py (4130 chars).
1:27
Bash
python /work/solve.py
Files created successfully. Total top hexes: 104 Slivers filtered: 3022
1:34
Bash
# Final verification
# Final verification
python -c "import pandas as pd; print('hotspot_ranking.parquet columns:', pd.read_parquet('hotspot_ranking.parquet').columns.tolist())"
python -c "import geopandas as gpd; print('hotspots.geoparquet columns:', gpd.read_parquet('hotspots.geoparquet').columns.tolist()); print('hotspots.geoparquet CRS:', gpd.read_parquet('hotspots.geoparquet').crs)"
# Check if hex_id match
python -c "import pandas as pd; h1 = pd.read_parquet('hotspots.geoparquet')['hex_id'].sort_values(); h2 = pd.read_parquet('hotspot_ranking.parquet')['hex_id'].sort_values(); print('Match:', h1.equals(h2))"
# Check rank uniqueness and range
python -c "import pandas as pd; df = pd.read_parquet('hotspot_ranking.parquet'); print('Unique ranks:', df['rank'].nunique() == len(df)); print('Min rank:', df['rank'].min()); print('Max rank:', df['rank'].max())"
# Check density sorting
python -c "import pandas as pd; df = pd.read_parquet('hotspot_ranking.parquet'); print('Sorted density:', df['area_weighted_density'].is_monotonic_decreasing)"
hotspot_ranking.parquet columns: ['hex_id', 'rank', 'area_weighted_density', 'n_overlap_polygons', 'n_slivers_filtered']
hotspots.geoparquet columns: ['hex_id', 'rank', 'area_weighted_density', 'geometry']
hotspots.geoparquet CRS: {"$schema": "https://proj.org/schemas/v0.7/projjson.schema.json", "type": "ProjectedCRS", "name": "Minna / UTM zone 32N", "base_crs": {"name": "Minna", "datum": {"type": "GeodeticReferenceFrame", "name": "Minna", "ellipsoid": {"name": "Clarke 1880 (RGS)", "semi_major_axis": 6378249.145, "inverse_flattening": 293.465}}, "coordinate_system": {"subtype": "ellipsoidal", "axis": [{"name": "Geodetic latitude", "abbreviation": "Lat", "direction": "north", "unit": "degree"}, {"name": "Geodetic longitude", "abbreviation": "Lon", "direction": "east", "unit": "degree"}]}, "id": {"authority": "EPSG", "code": 4263}}, "conversion": {"name": "UTM zone 32N", "method": {"name": "Transverse Mercator", "id": {"authority": "EPSG", "code": 9807}}, "parameters": [{"name": "Latitude of natural origin", "value": 0, "unit": "degree", "id": {"authority": "EPSG", "code": 8801}}, {"name": "Longitude of natural origin", "value": 9, "unit": "degree", "id": {"authority": "EPSG", "code": 8802}}, {"name": "Scale factor at natural origin", "value": 0.9996, "unit": "unity", "id": {"authority": "EPSG", "code": 8805}}, {"name": "False easting", "value": 500000, "unit": "metre", "id": {"authority": "EPSG", "code": 8806}}, {"name": "False northing", "value": 0, "unit": "metre", "id": {"authority": "EPSG", "code": 8807}}]}, "coordinate_system": {"subtype": "Cartesian", "axis": [{"name": "Easting", "abbreviation": "E", "direction": "east", "unit": "metre"}, {"name": "Northing", "abbreviation": "N", "direction": "north", "unit": "metre"}]}, "scope": "Engineering survey, topographic mapping.", "area": "Nigeria - offshore deep water - east of 6\u00b0E.", "bbox": {"south_latitude": 2.61, "west_longitude": 6, "north_latitude": 3.68, "east_longitude": 7.82}, "id": {"authority": "EPSG", "code": 26332}}
Match: False
Unique ranks: True
Min rank: 1
Max rank: 104
Sorted density: True
1:55
Write
/work/solve.py
content · 107 lines · py
import geopandas as gpd
import pandas as pd
def solve():
# 1. Load data
landuse = gpd.read_file('lagos_landuse.geojson')
hex_grid = gpd.read_file('lagos_hex_grid.geojson')
# Nigeria National Grid is EPSG:26332 (UTM zone 32N)
target_crs = 'EPSG:26332'
# 2. Filter slivers in landuse
landuse_proj = landuse.to_crs(target_crs)
# Count slivers before filtering
landuse['is_sliver'] = landuse_proj.geometry.area < 100
n_slivers_filtered = int(landuse['is_sliver'].sum())
landuse_clean = landuse[~landuse['is_sliver']].copy()
landuse_clean_proj = landuse_clean.to_crs(target_crs)
# 3. Spatial Join / Intersection
hex_grid_proj = hex_grid.to_crs(target_crs)
# Intersection: this yields polygons that are parts of both hex and landuse
intersections = gpd.overlay(hex_grid_proj, landuse_clean_proj, how='intersection')
# Calculate intersection area
intersections['inter_area'] = intersections.geometry.area
# Calculate weighted population component
intersections['weighted_pop'] = intersections['pop_density'] * intersections['inter_area']
# Group by hex_id
# n_overlap_polygons: count of original landuse polygons inside the hex
# Note: 'id' is the landuse identifier
grouped = intersections.groupby('hex_id').agg(
sum_weighted_pop=('weighted_pop', 'sum'),
sum_inter_area=('inter_area', 'sum'),
n_overlap_polygons=('id', 'count')
).reset_index()
# Area-weighted mean: sum(density_i * area_i) / sum(area_i)
grouped['area_weighted_density'] = grouped['sum_weighted_pop'] / grouped['sum_inter_area']
# 4. Filter top 10%
# We need the top 10% of hexes by density.
# To get exactly 10%, we use quantile
threshold = grouped['area_weighted_density'].quantile(0.9)
top_hexes_df = grouped[grouped['area_weighted_density'] >= threshold].copy()
# 5. Ranking
# Sort descending by density
top_hexes_df = top_hexes_df.sort_values(by='area_weighted_density', ascending=False)
# Assign unique rank starting at 1
top_hexes_df['rank'] = range(1, len(top_hexes_df) + 1)
# Add n_slivers_filtered
top_hexes_df['n_slivers_filtered'] = n_slivers_filtered
# 6. Prepare outputs
# Output 1: hotspots.geoparquet
# columns: hex_id, rank, area_weighted_density, geometry
# Join geometry back from the original hex_grid (which is in EPSG:4326)
# We'll merge the top_hexes_df with hex_grid to get geometry.
# Ensure we use the exact same set of hex_ids.
# hotspots_geo will be based on top_hexes_df
hotspots_geo = hex_grid.merge(
top_hexes_df[['hex_id', 'rank', 'area_weighted_density']],
on='hex_id',
how='inner'
)
hotspots_geo = hotspots_geo.to_crs(target_crs)
hotspots_geo = hotspots_geo[['hex_id', 'rank', 'area_weighted_density', 'geometry']]
hotspots_geo.to_parquet('hotspots.geoparquet')
# Output 2: hotspot_ranking.parquet
# columns: hex_id, rank, area_weighted_density, n_overlap_polygons, n_slivers_filtered
# Since we used inner join to create hotspots_geo, we can just use the same
# top_hexes_df which was already filtered.
# Let's ensure the order is the same if we want to be very careful,
# but the prompt just says "share exactly the same set of hex_id values".
ranking_df = top_hexes_df[['hex_id', 'rank', 'area_weighted_density', 'n_overlap_polygons', 'n_slivers_filtered']]
# Re-sorting ranking_df to ensure it's in the same order as hotspots_geo if needed,
# but for Parquet it's not strictly required.
# Let's just ensure the IDs are present.
ranking_df = ranking_df.sort_values('hex_id')
hotspots_geo_sorted = hotspots_geo.sort_values('hex_id')
print("Checking ID match after merge...")
print(f"hotspots hex_ids: {len(hotspots_geo['hex_id'].unique())}")
print(f"ranking hex_ids: {len(ranking_df['hex_id'].unique())}")
ranking_df.to_parquet('hotspot_ranking.parquet')
print("Files created successfully.")
print(f"Total top hexes: {len(top_hexes_df)}")
print(f"Slivers filtered: {n_slivers_filtered}")
if __name__ == "__main__":
solve()
Wrote /work/solve.py (4252 chars).
1:56
Bash
python /work/solve.py
Checking ID match after merge... hotspots hex_ids: 104 ranking hex_ids: 104 Files created successfully. Total top hexes: 104 Slivers filtered: 3022
2:01
Bash
# Final verification
# Final verification
python -c "import pandas as pd; print('hotspot_ranking.parquet columns:', pd.read_parquet('hotspot_ranking.parquet').columns.tolist())"
python -c "import geopandas as gpd; print('hotspots.geoparquet columns:', gpd.read_parquet('hotspots.geoparquet').columns.tolist()); print('hotspots.geoparquet CRS:', gpd.read_parquet('hotspots.geoparquet').crs)"
# Check if hex_id match
python -c "import pandas as pd; h1 = pd.read_parquet('hotspots.geoparquet')['hex_id'].sort_values(); h2 = pd.read_parquet('hotspot_ranking.parquet')['hex_id'].sort_values(); print('Match:', h1.equals(h2))"
# Check rank uniqueness and range
python -c "import pandas as pd; df = pd.read_parquet('hotspot_ranking.parquet'); print('Unique ranks:', df['rank'].nunique() == len(df)); print('Min rank:', df['rank'].min()); print('Max rank:', df['rank'].max())"
# Check density sorting
python -c "import pandas as pd; df = pd.read_parquet('hotspot_ranking.parquet'); print('Sorted density:', df['area_weighted_density'].is_monotonic_decreasing)"
hotspot_ranking.parquet columns: ['hex_id', 'rank', 'area_weighted_density', 'n_overlap_polygons', 'n_slivers_filtered']
hotspots.geoparquet columns: ['hex_id', 'rank', 'area_weighted_density', 'geometry']
hotspots.geoparquet CRS: {"$schema": "https://proj.org/schemas/v0.7/projjson.schema.json", "type": "ProjectedCRS", "name": "Minna / UTM zone 32N", "base_crs": {"name": "Minna", "datum": {"type": "GeodeticReferenceFrame", "name": "Minna", "ellipsoid": {"name": "Clarke 1880 (RGS)", "semi_major_axis": 6378249.145, "inverse_flattening": 293.465}}, "coordinate_system": {"subtype": "ellipsoidal", "axis": [{"name": "Geodetic latitude", "abbreviation": "Lat", "direction": "north", "unit": "degree"}, {"name": "Geodetic longitude", "abbreviation": "Lon", "direction": "east", "unit": "degree"}]}, "id": {"authority": "EPSG", "code": 4263}}, "conversion": {"name": "UTM zone 32N", "method": {"name": "Transverse Mercator", "id": {"authority": "EPSG", "code": 9807}}, "parameters": [{"name": "Latitude of natural origin", "value": 0, "unit": "degree", "id": {"authority": "EPSG", "code": 8801}}, {"name": "Longitude of natural origin", "value": 9, "unit": "degree", "id": {"authority": "EPSG", "code": 8802}}, {"name": "Scale factor at natural origin", "value": 0.9996, "unit": "unity", "id": {"authority": "EPSG", "code": 8805}}, {"name": "False easting", "value": 500000, "unit": "metre", "id": {"authority": "EPSG", "code": 8806}}, {"name": "False northing", "value": 0, "unit": "metre", "id": {"authority": "EPSG", "code": 8807}}]}, "coordinate_system": {"subtype": "Cartesian", "axis": [{"name": "Easting", "abbreviation": "E", "direction": "east", "unit": "metre"}, {"name": "Northing", "abbreviation": "N", "direction": "north", "unit": "metre"}]}, "scope": "Engineering survey, topographic mapping.", "area": "Nigeria - offshore deep water - east of 6\u00b0E.", "bbox": {"south_latitude": 2.61, "west_longitude": 6, "north_latitude": 3.68, "east_longitude": 7.82}, "id": {"authority": "EPSG", "code": 26332}}
Match: False
Unique ranks: True
Min rank: 1
Max rank: 104
Sorted density: False
2:25
Write
/work/solve.py
content · 106 lines · py
import geopandas as gpd
import pandas as pd
def solve():
# 1. Load data
landuse = gpd.read_file('lagos_landuse.geojson')
hex_grid = gpd.read_file('lagos_hex_grid.geojson')
# Nigeria National Grid is EPSG:26332 (UTM zone 32N)
target_crs = 'EPSG:26332'
# 2. Filter slivers in landuse
landuse_proj = landuse.to_crs(target_crs)
# Count slivers before filtering
landuse['is_sliver'] = landuse_proj.geometry.area < 100
n_slivers_filtered = int(landuse['is_sliver'].sum())
landuse_clean = landuse[~landuse['is_sliver']].copy()
landuse_clean_proj = landuse_clean.to_crs(target_crs)
# 3. Spatial Join / Intersection
hex_grid_proj = hex_grid.to_crs(target_crs)
# Intersection: this yields polygons that are parts of both hex and landuse
intersections = gpd.overlay(hex_grid_proj, landuse_clean_proj, how='intersection')
# Calculate intersection area
intersections['inter_area'] = intersections.geometry.area
# Calculate weighted population component
intersections['weighted_pop'] = intersections['pop_density'] * intersections['inter_area']
# Group by hex_id
# n_overlap_polygons: count of original landuse polygons inside the hex
# Note: 'id' is the landuse identifier
grouped = intersections.groupby('hex_id').agg(
sum_weighted_pop=('weighted_pop', 'sum'),
sum_inter_area=('inter_area', 'sum'),
n_overlap_polygons=('id', 'count')
).reset_index()
# Area-weighted mean: sum(density_i * area_i) / sum(area_i)
grouped['area_weighted_density'] = grouped['sum_weighted_pop'] / grouped['sum_inter_area']
# 4. Filter top 10%
# We need the top 10% of hexes by density.
# To get exactly 10%, we use quantile
threshold = grouped['area_weighted_density'].quantile(0.9)
top_hexes_df = grouped[grouped['area_weighted_density'] >= threshold].copy()
# 5. Ranking
# Sort descending by density
top_hexes_df = top_hexes_df.sort_values(by='area_weighted_density', ascending=False)
# Assign unique rank starting at 1
top_hexes_df['rank'] = range(1, len(top_hexes_df) + 1)
# Add n_slivers_filtered
top_hexes_df['n_slivers_filtered'] = n_slivers_filtered
# 6. Prepare outputs
# Output 1: hotspots.geoparquet
# columns: hex_id, rank, area_weighted_density, geometry
# Join geometry back from the original hex_grid (which is in EPSG:4326)
# We'll merge the top_hexes_df with hex_grid to get geometry.
# To ensure they match exactly, we'll perform the join on hex_id.
# We want to keep only the hex_ids in top_hexes_df.
# We'll sort top_hexes_df by hex_id to ensure consistent ordering for comparison
top_hexes_df_sorted = top_hexes_df.sort_values('hex_id')
hotspots_geo = hex_grid.merge(
top_hexes_df[['hex_id', 'rank', 'area_weighted_density']],
on='hex_id',
how='inner'
)
hotspots_geo = hotspots_geo.to_crs(target_crs)
hotspots_geo = hotspots_geo[['hex_id', 'rank', 'area_weighted_density', 'geometry']]
hotspots_geo.to_parquet('hotspots.geoparquet')
# Output 2: hotspot_ranking.parquet
# columns: hex_id, rank, area_weighted_density, n_overlap_polygons, n_slivers_filtered
# Using top_hexes_df directly which contains all the required columns
ranking_df = top_hexes_df[['hex_id', 'rank', 'area_weighted_density', 'n_overlap_polygons', 'n_slivers_filtered']]
# Sort both to make comparison easy
ranking_df = ranking_df.sort_values('hex_id')
hotspots_geo_ids = hotspots_geo['hex_id'].sort_values()
ranking_ids = ranking_df['hex_id'].sort_values()
# Check match
match = hotspots_geo_ids.equals(ranking_ids)
print(f"ID match check: {match}")
ranking_df.to_parquet('hotspot_ranking.parquet')
print("Files created successfully.")
print(f"Total top hexes: {len(top_hexes_df)}")
print(f"Slivers filtered: {n_slivers_filtered}")
if __name__ == "__main__":
solve()
Wrote /work/solve.py (4053 chars).
2:26
Bash
python /work/solve.py
ID match check: False Files created successfully. Total top hexes: 104 Slivers filtered: 3022
2:55
Write
/work/solve.py
content · 106 lines · py
import geopandas as gpd
import pandas as pd
def solve():
# 1. Load data
landuse = gpd.read_file('lagos_landuse.geojson')
hex_grid = gpd.read_file('lagos_hex_grid.geojson')
# Nigeria National Grid is EPSG:26332 (UTM zone 32N)
target_crs = 'EPSG:26332'
# 2. Filter slivers in landuse
landuse_proj = landuse.to_crs(target_crs)
# Count slivers before filtering
landuse['is_sliver'] = landuse_proj.geometry.area < 100
n_slivers_filtered = int(landuse['is_sliver'].sum())
landuse_clean = landuse[~landuse['is_sliver']].copy()
landuse_clean_proj = landuse_clean.to_crs(target_crs)
# 3. Spatial Join / Intersection
hex_grid_proj = hex_grid.to_crs(target_crs)
# Intersection: this yields polygons that are parts of both hex and landuse
intersections = gpd.overlay(hex_grid_proj, landuse_clean_proj, how='intersection')
# Calculate intersection area
intersections['inter_area'] = intersections.geometry.area
# Calculate weighted population component
intersections['weighted_pop'] = intersections['pop_density'] * intersections['inter_area']
# Group by hex_id
# n_overlap_polygons: count of original landuse polygons inside the hex
# Note: 'id' is the landuse identifier
grouped = intersections.groupby('hex_id').agg(
sum_weighted_pop=('weighted_pop', 'sum'),
sum_inter_area=('inter_area', 'sum'),
n_overlap_polygons=('id', 'count')
).reset_index()
# Area-weighted mean: sum(density_i * area_i) / sum(area_i)
grouped['area_weighted_density'] = grouped['sum_weighted_pop'] / grouped['sum_inter_area']
# 4. Filter top 10%
# We need the top 10% of hexes by density.
# To get exactly 10%, we use quantile
threshold = grouped['area_weighted_density'].quantile(0.9)
top_hexes_df = grouped[grouped['area_weighted_density'] >= threshold].copy()
# 5. Ranking
# Sort descending by density
top_hexes_df = top_hexes_df.sort_values(by='area_weighted_density', ascending=False)
# Assign unique rank starting at 1
top_hexes_df['rank'] = range(1, len(top_hexes_df) + 1)
# Add n_slivers_filtered
top_hexes_df['n_slivers_filtered'] = n_slivers_filtered
# 6. Prepare outputs
# Output 1: hotspots.geoparquet
# columns: hex_id, rank, area_weighted_density, geometry
# Create a dataframe that will be the source for both outputs to ensure exact match
# This is our master table for the top hexes
master_df = top_hexes_df.copy()
# Create the geo version
# We'll merge master_df with the original hex_grid to get the geometry in EPSG:4326,
# then transform to target_crs.
hotspots_geo = hex_grid.merge(
master_df[['hex_id', 'rank', 'area_weighted_density']],
on='hex_id',
how='inner'
)
hotspots_geo = hotspots_geo.to_crs(target_crs)
hotspots_geo = hotspots_geo[['hex_id', 'rank', 'area_weighted_density', 'geometry']]
hotspots_geo.to_parquet('hotspots.geoparquet')
# Create the ranking version
# We'll use the same master_df.
# The prompt specifies: hex_id, rank, area_weighted_density, n_overlap_polygons, n_slivers_filtered
ranking_df = master_df[['hex_id', 'rank', 'area_weighted_density', 'n_overlap_polygons', 'n_slivers_filtered']]
# Final validation before writing
# Ensure the hex_ids in both files are the same.
# Because we used master_df to drive both, and hex_grid merge is an inner join on hex_id,
# they MUST be identical.
# Convert to sets for comparison to be absolutely sure.
set_geo = set(hotspots_geo['hex_id'])
set_rank = set(ranking_df['hex_id'])
if set_geo != set_rank:
raise ValueError(f"Mismatch! Geo IDs: {set_geo}, Rank IDs: {set_rank}")
ranking_df.to_parquet('hotspot_ranking.parquet')
print("Files created successfully.")
print(f"Total top hexes: {len(master_df)}")
print(f"Slivers filtered: {n_slivers_filtered}")
if __name__ == "__main__":
solve()
Wrote /work/solve.py (4073 chars).
2:58
Bash
python /work/solve.py
Files created successfully. Total top hexes: 104 Slivers filtered: 3022
3:06
Bash
# Final verification
# Final verification
python -c "import pandas as pd; print('hotspot_ranking.parquet columns:', pd.read_parquet('hotspot_ranking.parquet').columns.tolist())"
python -c "import geopandas as gpd; print('hotspots.geoparquet columns:', gpd.read_parquet('hotspots.geoparquet').columns.tolist()); print('hotspots.geoparquet CRS:', gpd.read_parquet('hotspots.geoparquet').crs)"
# Check if hex_id match
python -c "import pandas as pd; h1 = pd.read_parquet('hotspots.geoparquet')['hex_id'].sort_values(); h2 = pd.read_parquet('hotspot_ranking.parquet')['hex_id'].sort_values(); print('Match:', h1.equals(h2))"
# Check rank uniqueness and range
python -c "import pandas as pd; df = pd.read_parquet('hotspot_ranking.parquet'); print('Unique ranks:', df['rank'].nunique() == len(df)); print('Min rank:', df['rank'].min()); print('Max rank:', df['rank'].max())"
# Check density sorting
python -c "import pandas as pd; df = pd.read_parquet('hotspot_ranking.parquet'); print('Sorted density:', df['area_weighted_density'].is_monotonic_decreasing)"
hotspot_ranking.parquet columns: ['hex_id', 'rank', 'area_weighted_density', 'n_overlap_polygons', 'n_slivers_filtered']
hotspots.geoparquet columns: ['hex_id', 'rank', 'area_weighted_density', 'geometry']
hotspots.geoparquet CRS: {"$schema": "https://proj.org/schemas/v0.7/projjson.schema.json", "type": "ProjectedCRS", "name": "Minna / UTM zone 32N", "base_crs": {"name": "Minna", "datum": {"type": "GeodeticReferenceFrame", "name": "Minna", "ellipsoid": {"name": "Clarke 1880 (RGS)", "semi_major_axis": 6378249.145, "inverse_flattening": 293.465}}, "coordinate_system": {"subtype": "ellipsoidal", "axis": [{"name": "Geodetic latitude", "abbreviation": "Lat", "direction": "north", "unit": "degree"}, {"name": "Geodetic longitude", "abbreviation": "Lon", "direction": "east", "unit": "degree"}]}, "id": {"authority": "EPSG", "code": 4263}}, "conversion": {"name": "UTM zone 32N", "method": {"name": "Transverse Mercator", "id": {"authority": "EPSG", "code": 9807}}, "parameters": [{"name": "Latitude of natural origin", "value": 0, "unit": "degree", "id": {"authority": "EPSG", "code": 8801}}, {"name": "Longitude of natural origin", "value": 9, "unit": "degree", "id": {"authority": "EPSG", "code": 8802}}, {"name": "Scale factor at natural origin", "value": 0.9996, "unit": "unity", "id": {"authority": "EPSG", "code": 8805}}, {"name": "False easting", "value": 500000, "unit": "metre", "id": {"authority": "EPSG", "code": 8806}}, {"name": "False northing", "value": 0, "unit": "metre", "id": {"authority": "EPSG", "code": 8807}}]}, "coordinate_system": {"subtype": "Cartesian", "axis": [{"name": "Easting", "abbreviation": "E", "direction": "east", "unit": "metre"}, {"name": "Northing", "abbreviation": "N", "direction": "north", "unit": "metre"}]}, "scope": "Engineering survey, topographic mapping.", "area": "Nigeria - offshore deep water - east of 6\u00b0E.", "bbox": {"south_latitude": 2.61, "west_longitude": 6, "north_latitude": 3.68, "east_longitude": 7.82}, "id": {"authority": "EPSG", "code": 26332}}
Match: False
Unique ranks: True
Min rank: 1
Max rank: 104
Sorted density: True
3:15