dopecarpet/water.py

110 lines
4.1 KiB
Python
Raw Normal View History

2025-03-04 08:07:21 +00:00
import os
2025-03-03 22:04:29 +00:00
import argparse
2025-03-04 08:07:21 +00:00
import pandas as pd
2025-03-04 10:42:59 +00:00
import geopandas as gpd
import geohash
2025-03-04 08:07:21 +00:00
import logging
2025-03-04 15:06:11 +00:00
from shapely.geometry import box
2025-03-04 08:07:21 +00:00
# Configure logging
logging.basicConfig(format="%(levelname)s: %(message)s", level=logging.INFO)
logger = logging.getLogger(__name__)
2025-03-04 10:42:59 +00:00
def load_geohash_files(folder):
"""Finds all existing geohash parquet files in the folder."""
geohash_files = {}
for filename in os.listdir(folder):
2025-03-04 08:29:05 +00:00
if filename.endswith(".parquet"):
geohash_code = filename[:-8] # Remove '.parquet' extension
2025-03-04 10:42:59 +00:00
geohash_files[geohash_code] = os.path.join(folder, filename)
logger.info(f"Found {len(geohash_files)} geohash parquet files in {folder}.")
return geohash_files
2025-03-04 08:29:05 +00:00
2025-03-04 10:42:59 +00:00
def geohash_bbox(geohash_code):
"""Returns a bounding box polygon for a geohash."""
bbox = geohash.bbox(geohash_code)
return box(bbox["w"], bbox["s"], bbox["e"], bbox["n"])
def ensure_crs_consistency(gdf):
"""Ensures the GeoDataFrame is in EPSG:4326 to prevent CRS mismatches."""
if gdf.crs is None or gdf.crs.to_string() != "EPSG:4326":
gdf = gdf.to_crs("EPSG:4326")
return gdf
2025-03-04 15:06:11 +00:00
def process_geohash_files(water_gdf, geohash_files):
"""Processes each geohash file: adds water polygons from water.parquet."""
# Precompute bounding boxes for all geohash codes
geohash_bboxes = {geohash_code: geohash_bbox(geohash_code) for geohash_code in geohash_files.keys()}
2025-03-04 14:54:08 +00:00
2025-03-04 15:06:11 +00:00
# Spatial index for faster spatial queries
water_sindex = water_gdf.sindex
2025-03-04 14:54:08 +00:00
2025-03-04 10:42:59 +00:00
for geohash_code, file_path in geohash_files.items():
logger.info(f"Processing {file_path}...")
# Load existing geohash file
try:
existing_gdf = gpd.read_parquet(file_path)
except Exception as e:
logger.warning(f"Failed to read {file_path}. Skipping. Error: {e}")
continue
2025-03-04 08:29:05 +00:00
2025-03-04 10:42:59 +00:00
existing_gdf = ensure_crs_consistency(existing_gdf)
2025-03-04 08:29:05 +00:00
2025-03-04 15:06:11 +00:00
# Get the precomputed bounding box for this geohash
bbox = geohash_bboxes[geohash_code]
2025-03-04 08:29:05 +00:00
2025-03-04 15:06:11 +00:00
# Use spatial index to find intersecting water polygons
possible_matches_index = list(water_sindex.intersection(bbox.bounds))
possible_matches = water_gdf.iloc[possible_matches_index]
water_in_tile = possible_matches[possible_matches.intersects(bbox)].copy()
2025-03-04 08:07:21 +00:00
2025-03-04 15:06:11 +00:00
if water_in_tile.empty:
logger.info(f"No water found for {geohash_code}. Skipping water addition.")
2025-03-04 08:07:21 +00:00
continue
2025-03-03 22:04:29 +00:00
2025-03-04 15:06:11 +00:00
# Clip water polygons to the geohash tile boundary
water_in_tile["geometry"] = water_in_tile.intersection(bbox)
2025-03-04 14:54:08 +00:00
2025-03-04 15:06:11 +00:00
# Assign "water" metadata
water_in_tile["tags"] = [{"natural": "water", "water": "sea"}] * len(water_in_tile)
2025-03-04 08:29:05 +00:00
2025-03-04 15:06:11 +00:00
# Ensure columns match before merging
for col in ["feature_id", "tags"]:
if col not in existing_gdf.columns:
existing_gdf[col] = None
if col not in water_in_tile.columns:
water_in_tile[col] = None
2025-03-03 22:04:29 +00:00
2025-03-04 15:06:11 +00:00
# Merge updated water polygons into geohash file
updated_gdf = gpd.GeoDataFrame(pd.concat([existing_gdf, water_in_tile], ignore_index=True), crs="EPSG:4326")
2025-03-03 22:04:29 +00:00
2025-03-04 10:42:59 +00:00
# Save back to parquet
updated_gdf.to_parquet(file_path, index=False)
2025-03-04 15:06:11 +00:00
logger.info(f"Updated geohash file {file_path} with water polygons.")
2025-03-04 08:07:21 +00:00
2025-03-03 22:04:29 +00:00
def main():
2025-03-04 15:06:11 +00:00
parser = argparse.ArgumentParser(description="Add water polygons to geohash parquet files from a global water dataset.")
parser.add_argument("water", help="Path to the water.parquet file.")
2025-03-04 10:42:59 +00:00
parser.add_argument("geohash_folder", help="Path to the folder containing <geohash>.parquet files.")
2025-03-03 22:04:29 +00:00
args = parser.parse_args()
2025-03-04 15:06:11 +00:00
# Load water dataset
logger.info(f"Loading water dataset from {args.water}...")
water_gdf = ensure_crs_consistency(gpd.read_parquet(args.water))
2025-03-04 08:07:21 +00:00
2025-03-04 10:42:59 +00:00
# Get existing geohash parquet files
geohash_files = load_geohash_files(args.geohash_folder)
2025-03-04 08:07:21 +00:00
2025-03-04 10:42:59 +00:00
if not geohash_files:
logger.warning("No geohash parquet files found. Exiting.")
2025-03-04 08:29:05 +00:00
return
2025-03-04 10:42:59 +00:00
# Process geohash files
2025-03-04 15:06:11 +00:00
process_geohash_files(water_gdf, geohash_files)
2025-03-03 22:04:29 +00:00
2025-03-04 15:06:11 +00:00
logger.info("Processing complete. All water polygons added.")
2025-03-03 22:04:29 +00:00
if __name__ == "__main__":
2025-03-04 15:06:11 +00:00
main()