import json import click import pygeohash as pgh import pyarrow.parquet as pq from shapely.geometry import Point, LineString, MultiLineString from shapely import wkb from pyproj import Geod @click.command() @click.argument("latitude", type=float) @click.argument("longitude", type=float) @click.option("--distance", type=float, default=10, help="Distance threshold in meters for nearby lines.") @click.option("--parquet-dir", type=str, default="geohash", help="Directory containing geohash Parquet files.") def geohash_lookup(latitude, longitude, distance, parquet_dir): """ Finds polygons containing the given point and lines within a threshold (in meters). """ # Compute geohash (precision can be adjusted) geohash = pgh.encode(latitude, longitude, precision=4) # Adjust precision as needed parquet_file = f"{parquet_dir}/{geohash}.parquet" try: # Load the Parquet file table = pq.read_table(parquet_file) df = table.to_pandas() except FileNotFoundError: click.echo(json.dumps({"error": f"No Parquet file found for geohash {geohash}."})) return point = Point(longitude, latitude) geod = Geod(ellps="WGS84") # Accurate geodetic distance calculations found_geometries = [] for _, row in df.iterrows(): # Decode geometry from WKB try: geom = wkb.loads(bytes(row["geometry"])) # Ensure WKB is properly read except Exception as e: click.echo(json.dumps({"error": f"Failed to decode geometry: {str(e)}"})) continue # ✅ Ensure `tags` is always a dictionary metadata = {} if isinstance(row["tags"], dict): metadata = {k: v for k, v in row["tags"].items() if v is not None} # Remove empty values # ✅ Check if the point is inside a polygon if geom.contains(point): found_geometries.append({"type": "polygon", **metadata}) # ✅ Handle LineString and MultiLineString elif isinstance(geom, LineString): try: min_distance = min(geod.inv(point.x, point.y, p[0], p[1])[2] for p in geom.coords) if min_distance <= distance: found_geometries.append({"type": "line", **metadata}) except Exception as e: click.echo(json.dumps({"error": f"Failed distance calculation (LineString): {str(e)}"})) continue elif isinstance(geom, MultiLineString): try: # ✅ Iterate through all LineStrings inside the MultiLineString min_distance = min( min(geod.inv(point.x, point.y, p[0], p[1])[2] for p in line.coords) for line in geom.geoms ) if min_distance <= distance: found_geometries.append({"type": "multi_line", **metadata}) except Exception as e: click.echo(json.dumps({"error": f"Failed distance calculation (MultiLineString): {str(e)}"})) continue # Output JSON result click.echo(json.dumps(found_geometries, indent=2, ensure_ascii=False)) # Ensure proper Unicode display if __name__ == "__main__": geohash_lookup()