spa-l2-lagos-hotspot-overlaps

Status: done
Score: 79
Duration: 1:37
Cost: 0.27¢
Model: deepseek/deepseek-v4-flash

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": "deepseek/deepseek-v4-flash",
  "base_url": "https://openrouter.ai/api/v1"
}
0:08
Let me start by understanding the task:

1. I have two input files: `lagos_landuse` (land-use polygons with population density) and `lagos_hex_grid` (hex grid)
2. I need to compute area-weighted mean population density for each hex cell
3. Exclude tiny polygons under 100 m² (noise/slivers)
4. Find top 10% of hex cells ranked by this metric
5. Output two files:
   - `hotspots.geoparquet` - with geometry in Nigeria's national grid
   - `hotspot_ranking.parquet` - plain Parquet with additional stats

Let me first explore the input files to understand their structure.
0:08
Bash ls -la /work/
total 4244
drwxr-xr-x 1 agent agent      86 Jun 18 16:29 .
drwxr-xr-x 1 root  root       14 Jun 18 16:29 ..
-rw-r--r-- 1 agent agent  736774 Jun 18 16:29 lagos_hex_grid.geojson
-rw-r--r-- 1 agent agent 3607119 Jun 18 16:29 lagos_landuse.geojson
0:11
Let me first inspect both files to understand their structure.
0:11
Bash cd /work && python3 -c "
cd /work && python3 -c "
import geopandas as gpd
import pandas as pd

# Inspect landuse
lu = gpd.read_file('lagos_landuse.geojson')
print('=== LANDUSE ===')
print(f'Columns: {list(lu.columns)}')
print(f'Shape: {lu.shape}')
print(f'CRS: {lu.crs}')
print(lu.head(3))
print(f'Population density dtype: {lu[\"population_density\"].dtype}')
print(lu['population_density'].describe())
print()

# Inspect hex grid
hex = gpd.read_file('lagos_hex_grid.geojson')
print('=== HEX GRID ===')
print(f'Columns: {list(hex.columns)}')
print(f'Shape: {hex.shape}')
print(f'CRS: {hex.crs}')
print(hex.head(3))
"
=== LANDUSE ===
Columns: ['id', 'class', 'pop_density', 'geometry']
Shape: (5542, 4)
CRS: EPSG:4326
                                     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 rows x 4 columns]
Traceback (most recent call last):
  File "/usr/local/lib/python3.14/site-packages/pandas/core/indexes/base.py", line 3641, in get_loc
    return self._engine.get_loc(casted_key)
           ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^
  File "pandas/_libs/index.pyx", line 168, in pandas._libs.index.IndexEngine.get_loc
  File "pandas/_libs/index.pyx", line 197, in pandas._libs.index.IndexEngine.get_loc
  File "pandas/_libs/hashtable_class_helper.pxi", line 7668, in pandas._libs.hashtable.PyObjectHashTable.get_item
  File "pandas/_libs/hashtable_class_helper.pxi", line 7676, in pandas._libs.hashtable.PyObjectHashTable.get_item
KeyError: 'population_density'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<string>", line 12, in <module>
    print(f'Population density dtype: {lu["population_density"].dtype}')
                                       ~~^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.14/site-packages/geopandas/geodataframe.py", line 1891, in __getitem__
    result = super().__getitem__(key)
  File "/usr/local/lib/python3.14/site-packages/pandas/core/frame.py", line 4378, in __getitem__
    indexer = self.columns.get_loc(key)
  File "/usr/local/lib/python3.14/site-packages/pandas/core/indexes/base.py", line 3648, in get_loc
    raise KeyError(key) from err
KeyError: 'population_density'
0:29
The column is called `pop_density`, not `population_density`. Let me also check the hex grid CRS and columns.
0:29
Bash cd /work && python3 -c "
cd /work && python3 -c "
import geopandas as gpd
import pandas as pd

lu = gpd.read_file('lagos_landuse.geojson')
print('=== LANDUSE ===')
print(f'Columns: {list(lu.columns)}')
print(f'Shape: {lu.shape}')
print(f'CRS: {lu.crs}')
print(lu.head(3))
print(f'pop_density dtype: {lu[\"pop_density\"].dtype}')
print(lu['pop_density'].describe())
print()

hex = gpd.read_file('lagos_hex_grid.geojson')
print('=== HEX GRID ===')
print(f'Columns: {list(hex.columns)}')
print(f'Shape: {hex.shape}')
print(f'CRS: {hex.crs}')
print(hex.head(5))
print()
print('hex_id sample:', hex['hex_id'].head())
print('hex_id dtype:', hex['hex_id'].dtype)
print()

# Check if any landuse polygons are area < 100 m² (in EPSG:4326)
# Let's compute approximate area
lu_proj = lu.to_crs('EPSG:26391')  # Nigeria's national grid
lu_proj['area_m2'] = lu_proj.geometry.area
print('Landuse area stats (in EPSG:26391):')
print(lu_proj['area_m2'].describe())
print('Num polygons < 100 m²:', (lu_proj['area_m2'] < 100).sum())
"
=== LANDUSE ===
Columns: ['id', 'class', 'pop_density', 'geometry']
Shape: (5542, 4)
CRS: EPSG:4326
                                     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 rows x 4 columns]
pop_density dtype: float64
count     5542.000000
mean     25250.978979
std      14332.386707
min        507.300000
25%      12878.375000
50%      25024.250000
75%      37759.075000
max      49994.900000
Name: pop_density, dtype: float64

=== HEX GRID ===
Columns: ['hex_id', 'geometry']
Shape: (1782, 2)
CRS: EPSG:4326
     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....

hex_id sample: 0    H000-000
1    H000-001
2    H000-002
3    H000-003
4    H000-004
Name: hex_id, dtype: str
hex_id dtype: str

Landuse area stats (in EPSG:26391):
count    5.542000e+03
mean     9.302354e+04
std      9.331811e+05
min      1.000764e+00
25%      4.689652e+01
50%      9.114666e+01
75%      5.072840e+03
max      3.490980e+07
Name: area_m2, dtype: float64
Num polygons < 100 m²: 3022
0:57
Let me understand the task:

1. **Input files**: `lagos_landuse.geojson` (land-use polygons with `pop_density` attribute), `lagos_hex_grid.geojson` (hex grid with `hex_id`)
2. **Filter**: Exclude land-use polygons with area < 100 m² (these are "noise" / "slivers")
3. **Process**: For each hex cell, compute the area-weighted mean population density across overlapping land-use polygons
4. **Select**: Top 10% of hex cells ranked by descending area-weighted mean density
5. **Output**:
   - `hotspots.geoparquet`: GeoParquet with `hex_id`, `rank`, `area_weighted_density`, `geometry` in Nigeria's national grid (EPSG:26391)
   - `hotspot_ranking.parquet`: Plain Parquet with `hex_id`, `rank`, `area_weighted_density`, `n_overlap_polygons`, `n_slivers_filtered` (no geometry)

Both files share exactly the same set of `hex_id` values.

Let me also figure out:
- `n_overlap_polygons`: Number of land-use polygons that overlap with this hex cell (excluding slivers)
- `n_slivers_filtered`: Number of tiny polygons (area < 100 m²) that were filtered out within this hex cell

Wait, should `n_slivers_filtered` be per hex cell? Yes, "n_slivers_filtered" is an integer column, so it's the count of slivers that intersected this hex cell but were excluded.

Let me write the script.

The approach:
1. Read both files
2. Project landuse polygons to EPSG:26391 (Nigeria's national grid - Minna / Nigeria West Belt)
3. Compute area for each landuse polygon in projected CRS, mark slivers
4. Filter out slivers (area < 100 m²)
5. For each hex cell (also projected), perform spatial join with remaining landuse polygons
6. For each hex cell, compute area-weighted mean population density
7. Compute top 10% threshold
8. Assign ranks (1 = highest density)
9. Compute n_overlap_polygons and n_slivers_filtered for the top cells
10. Write outputs

Wait, I need to think about `n_slivers_filtered` more carefully. It says "n_slivers_filtered (integer)" - this is the number of tiny polygons that were filtered out (area < 100 m²). But should this count only slivers that intersect the hex cell? That makes sense - per hex cell, how many slivers were excluded.

Let me also think about the area-weighted mean. For each hex cell h, overlapping with land-use polygons p1, p2, ..., pk (each with area a_i and density d_i):

area_weighted_density = sum(a_i * d_i) / sum(a_i)

This is the standard area-weighted mean.

Let me write the script.

Wait, I need to check what EPSG:26391 is. Nigeria's national grid. Let me verify.

Actually, the user said "Nigeria's national grid". The common Nigerian national grid is EPSG:26391 (Minna / Nigeria West Belt) or EPSG:26392 (Minna / Nigeria Mid Belt) or EPSG:26393 (Minna / Nigeria East Belt). But there's also EPSG:26391 which is "Minna / Nigeria West Belt".

Hmm, actually the common "Nigeria national grid" used in many contexts is EPSG:26391 - Minna / Nigeria West Belt. Let me use that.

Actually, wait - let me check what the standard "Nigeria national grid" is. EPSG:26391 covers the west belt which includes Lagos, so that should work.

Let me also think about whether I should project the hex grid too, or keep it in 4326 and just project the landuse. Since I need to output in EPSG:26391, I'll project both.

Let me code it up.
0:57
Write /work/solve.py
content · 100 lines · py
import geopandas as gpd
import pandas as pd
import numpy as np

NGRID = "EPSG:26391"  # Minna / Nigeria West Belt (Lagos area)

# ── 1. Read inputs ────────────────────────────────────────────────────
lu = gpd.read_file("/work/lagos_landuse.geojson")
hex = gpd.read_file("/work/lagos_hex_grid.geojson")

print(f"Landuse: {len(lu)} polygons, CRS={lu.crs}")
print(f"Hex grid: {len(hex)} cells, CRS={hex.crs}")

# ── 2. Project to Nigeria national grid ──────────────────────────────
lu_proj = lu.to_crs(NGRID).copy()
hex_proj = hex.to_crs(NGRID).copy()

# ── 3. Compute areas and identify slivers ────────────────────────────
lu_proj["area_m2"] = lu_proj.geometry.area
lu_proj["is_sliver"] = lu_proj["area_m2"] < 100.0

print(f"Slivers (area < 100 m²): {lu_proj['is_sliver'].sum()} / {len(lu_proj)}")

# ── 4. Separate slivers from valid polygons ──────────────────────────
lu_valid = lu_proj[~lu_proj["is_sliver"]].copy()
lu_slivers = lu_proj[lu_proj["is_sliver"]].copy()

print(f"Valid landuse polygons: {len(lu_valid)}")
print(f"Filtered slivers:       {len(lu_slivers)}")

# ── 5. Spatial join: hex cells ∩ valid landuse polygons ─────────────
# For each intersecting pair, compute intersection area
# Use overlay (intersection) to get the exact intersection geometries
overlay = gpd.overlay(hex_proj, lu_valid, how="intersection", keep_geom_type=True)
overlay["intersect_area"] = overlay.geometry.area

# ── 6. Area-weighted mean density per hex cell ───────────────────────
# weighted_sum = sum(intersect_area * pop_density)
# weight_total = sum(intersect_area)
agg = overlay.groupby("hex_id").agg(
    weighted_sum=("intersect_area", lambda x: np.sum(x * overlay.loc[x.index, "pop_density"])),
    weight_total=("intersect_area", "sum"),
    n_overlap_polygons=("id", "count"),
).reset_index()

agg["area_weighted_density"] = agg["weighted_sum"] / agg["weight_total"]

# ── 7. Count slivers that overlap each hex cell ──────────────────────
if len(lu_slivers) > 0:
    sliver_overlay = gpd.overlay(hex_proj[["hex_id", "geometry"]], lu_slivers[["id", "geometry"]],
                                 how="intersection", keep_geom_type=True)
    sliver_count = sliver_overlay.groupby("hex_id").size().reset_index(name="n_slivers_filtered")
else:
    sliver_count = pd.DataFrame({"hex_id": hex_proj["hex_id"].unique(), "n_slivers_filtered": 0})

# ── 8. Merge everything ──────────────────────────────────────────────
result = hex_proj[["hex_id", "geometry"]].merge(agg[["hex_id", "area_weighted_density", "n_overlap_polygons"]],
                                                  on="hex_id", how="left")
result = result.merge(sliver_count, on="hex_id", how="left")
result["n_slivers_filtered"] = result["n_slivers_filtered"].fillna(0).astype(int)
result["n_overlap_polygons"] = result["n_overlap_polygons"].fillna(0).astype(int)

# Cells with no overlap get area_weighted_density = 0 (or NaN?) — we'll keep NaN and sort accordingly
# But the task says "top 10%", so cells with no overlap will just be at the bottom

# ── 9. Rank by descending area_weighted_density ──────────────────────
result_sorted = result.sort_values("area_weighted_density", ascending=False, na_position="last").reset_index(drop=True)

# Top 10% (at least 1 cell)
n_hex = len(result_sorted)
n_top = max(1, round(n_hex * 0.10))
top_ids = result_sorted.head(n_top)["hex_id"].tolist()

print(f"Total hex cells: {n_hex}")
print(f"Top 10% count: {n_top}")

top_df = result_sorted[result_sorted["hex_id"].isin(top_ids)].copy()
top_df["rank"] = range(1, len(top_df) + 1)

# ── 10. Build outputs ────────────────────────────────────────────────
# hotspots.geoparquet (geometry in EPSG:26391)
hotspots = top_df[["hex_id", "rank", "area_weighted_density", "geometry"]].copy()
hotspots.to_parquet("/work/hotspots.geoparquet", index=False)
print(f"Written hotspots.geoparquet: {len(hotspots)} rows")

# hotspot_ranking.parquet (no geometry)
ranking = top_df[["hex_id", "rank", "area_weighted_density", "n_overlap_polygons", "n_slivers_filtered"]].copy()
ranking.to_parquet("/work/hotspot_ranking.parquet", index=False)
print(f"Written hotspot_ranking.parquet: {len(ranking)} rows")

# ── 11. Sanity checks ────────────────────────────────────────────────
print("\n─── Sanity checks ───")
print("hotspots columns:", list(hotspots.columns))
print("ranking columns:", list(ranking.columns))
print(f"hex_id sets equal? {set(hotspots['hex_id']) == set(ranking['hex_id'])}")
print(f"hotspots CRS: {hotspots.crs}")
print(f"Rank range: {top_df['rank'].min()} – {top_df['rank'].max()}")
print(f"Ranks unique: {top_df['rank'].is_unique}")
print(f"\nTop 5:\n{top_df.head(5)[['hex_id', 'rank', 'area_weighted_density']].to_string(index=False)}")
print(f"\nBottom 5:\n{top_df.tail(5)[['hex_id', 'rank', 'area_weighted_density']].to_string(index=False)}")
Wrote /work/solve.py (4803 chars).
1:00
Let me run this script and see if it works.
1:00
Bash cd /work && python solve.py
Landuse: 5542 polygons, CRS=EPSG:4326
Hex grid: 1782 cells, CRS=EPSG:4326
Slivers (area < 100 m²): 3022 / 5542
Valid landuse polygons: 2520
Filtered slivers:       3022
Total hex cells: 1782
Top 10% count: 178
Written hotspots.geoparquet: 178 rows
Written hotspot_ranking.parquet: 178 rows

─── Sanity checks ───
hotspots columns: ['hex_id', 'rank', 'area_weighted_density', 'geometry']
ranking columns: ['hex_id', 'rank', 'area_weighted_density', 'n_overlap_polygons', 'n_slivers_filtered']
hex_id sets equal? True
hotspots CRS: EPSG:26391
Rank range: 1 – 178
Ranks unique: True

Top 5:
  hex_id  rank  area_weighted_density
H011-017     1           49816.761617
H049-008     2           49618.400000
H031-006     3           49405.200000
H035-007     4           49167.200000
H039-014     5           49163.000000

Bottom 5:
  hex_id  rank  area_weighted_density
H013-024   174           36790.625145
H043-019   175           36638.960042
H015-011   176           36619.070487
H041-026   177           36555.225523
H027-003   178           36510.621100
1:13
Let me verify the output files exist and do a quick sanity check on coordinate magnitudes.
1:13
Bash cd /work && python3 -c "
cd /work && python3 -c "
import geopandas as gpd
import pandas as pd

# Check hotspots.geoparquet
h = gpd.read_parquet('hotspots.geoparquet')
print('=== hotspots.geoparquet ===')
print(f'Rows: {len(h)}')
print(f'Columns: {list(h.columns)}')
print(f'CRS: {h.crs}')
print(f'Geometry type: {h.geometry.type.unique()}')
# Check coordinate magnitudes
bounds = h.total_bounds
print(f'Bounds: x=[{bounds[0]:.2f}, {bounds[2]:.2f}]  y=[{bounds[1]:.2f}, {bounds[3]:.2f}]')
print(f'hex_id type: {h[\"hex_id\"].dtype}')
print(f'rank dtype: {h[\"rank\"].dtype}')
print(f'rank unique: {h[\"rank\"].is_unique}')
print(f'rank range: {h[\"rank\"].min()} - {h[\"rank\"].max()}')
print()

# Check hotspot_ranking.parquet
r = pd.read_parquet('hotspot_ranking.parquet')
print('=== hotspot_ranking.parquet ===')
print(f'Rows: {len(r)}')
print(f'Columns: {list(r.columns)}')
print(f'hex_id type: {r[\"hex_id\"].dtype}')
print(f'rank dtype: {r[\"rank\"].dtype}')
print(f'n_overlap_polygons dtype: {r[\"n_overlap_polygons\"].dtype}')
print(f'n_slivers_filtered dtype: {r[\"n_slivers_filtered\"].dtype}')
print(f'rank unique: {r[\"rank\"].is_unique}')
print(f'rank range: {r[\"rank\"].min()} - {r[\"rank\"].max()}')
print()

# Verify hex_id sets match
hs = set(h['hex_id'])
rs = set(r['hex_id'])
print(f'Same hex_id set: {hs == rs}')
print(f'Difference: {hs.symmetric_difference(rs)}')

# Verify sorted descending
density_vals = h.sort_values('rank')['area_weighted_density'].values
print(f'Density monotonic decreasing: {all(density_vals[i] >= density_vals[i+1] for i in range(len(density_vals)-1))}')
"
=== hotspots.geoparquet ===
Rows: 178
Columns: ['hex_id', 'rank', 'area_weighted_density', 'geometry']
CRS: {"$schema": "https://proj.org/schemas/v0.7/projjson.schema.json", "type": "ProjectedCRS", "name": "Minna / Nigeria West Belt", "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": "Nigeria West Belt", "method": {"name": "Transverse Mercator", "id": {"authority": "EPSG", "code": 9807}}, "parameters": [{"name": "Latitude of natural origin", "value": 4, "unit": "degree", "id": {"authority": "EPSG", "code": 8801}}, {"name": "Longitude of natural origin", "value": 4.5, "unit": "degree", "id": {"authority": "EPSG", "code": 8802}}, {"name": "Scale factor at natural origin", "value": 0.99975, "unit": "unity", "id": {"authority": "EPSG", "code": 8805}}, {"name": "False easting", "value": 230738.26, "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 - onshore west of 6\u00b030'E, onshore and offshore shelf.", "bbox": {"south_latitude": 3.57, "west_longitude": 2.69, "north_latitude": 13.9, "east_longitude": 6.5}, "id": {"authority": "EPSG", "code": 26391}}
Geometry type: <ArrowStringArray>
['Polygon']
Length: 1, dtype: str
Bounds: x=[92829.53, 139039.17]  y=[264967.87, 293983.79]
hex_id type: str
rank dtype: int64
rank unique: True
rank range: 1 - 178

=== hotspot_ranking.parquet ===
Rows: 178
Columns: ['hex_id', 'rank', 'area_weighted_density', 'n_overlap_polygons', 'n_slivers_filtered']
hex_id type: str
rank dtype: int64
n_overlap_polygons dtype: int64
n_slivers_filtered dtype: int64
rank unique: True
rank range: 1 - 178

Same hex_id set: True
Difference: set()
Density monotonic decreasing: True
1:22
Everything checks out perfectly. Let me verify the file existence too.
1:22
Bash ls -lh /work/hotspots.geoparquet /work/hotspot_ranking.parquet
-rw-r--r-- 1 agent agent 7.1K Jun 18 16:30 /work/hotspot_ranking.parquet
-rw-r--r-- 1 agent agent  29K Jun 18 16:30 /work/hotspots.geoparquet
1:35
All checks passed. Let me summarize what was done:

1. Read landuse and hex grid (both EPSG:4326)
2. Projected both to Nigeria's national grid EPSG:26391 (Minna / Nigeria West Belt)
3. Filtered out "sliver" polygons (area < 100 m²) — found 3022 out of 5542
4. Performed spatial overlay (intersection) between hex grid and valid landuse polygons
5. Computed area-weighted mean population density per hex cell
6. Counted overlapping polygons per hex cell
7. Counted slivers overlapping each hex cell
8. Selected top 10% (178 cells) by descending area_weighted_density
9. Assigned unique ranks 1-178
10. Wrote both output files with matching hex_id sets

Everything checks out — both files exist, have the correct columns, matching hex_id sets, correct CRS, correct ranking order, and correct data types.
1:35