dopecarpet/api.py
2025-03-05 19:22:56 +00:00

106 lines
No EOL
4.2 KiB
Python

from fastapi import FastAPI, HTTPException, Query
from fastapi.responses import Response, FileResponse, JSONResponse
from fastapi.middleware.gzip import GZipMiddleware
import geopandas as gpd
import cbor2
from pathlib import Path
import pygeohash as pgh
from shapely.geometry import Point, LineString, MultiLineString
from pyproj import Geod
import json
app = FastAPI()
# ✅ Add Gzip compression middleware
app.add_middleware(GZipMiddleware, minimum_size=500)
# Directory containing Parquet files
GEOHASH_FOLDER = Path("geohash")
@app.get("/get_geodata/")
async def get_geodata(geohash: str, format: str = "cbor"):
"""Fetch geospatial data by Geohash and return in the requested format with HTTP compression."""
if len(geohash) < 4:
raise HTTPException(status_code=400, detail="Geohash must be at least 4 characters long.")
base_geohash = geohash[:4]
parquet_file = GEOHASH_FOLDER / f"{base_geohash}.parquet"
if not parquet_file.exists():
raise HTTPException(status_code=404, detail=f"No data found for geohash '{base_geohash}'.")
if format == "parquet":
return FileResponse(parquet_file, media_type="application/octet-stream",
filename=f"{base_geohash}.parquet")
gdf = gpd.read_parquet(parquet_file)
if format == "geojson":
geojson_data = gdf.to_json()
return Response(content=geojson_data, media_type="application/geo+json",
headers={"Content-Disposition": f"attachment; filename={base_geohash}.geojson"})
elif format == "cbor":
geojson_dict = gdf.to_json()
cbor_data = cbor2.dumps(geojson_dict)
return Response(content=cbor_data, media_type="application/cbor",
headers={"Content-Disposition": f"attachment; filename={base_geohash}.cbor"})
raise HTTPException(status_code=400, detail="Invalid format. Use 'parquet', 'geojson', or 'cbor'.")
@app.get("/lookup/")
async def lookup(
latitude: float = Query(..., description="Latitude of the point"),
longitude: float = Query(..., description="Longitude of the point"),
distance: float = Query(10, description="Distance threshold in meters for nearby lines")
):
"""
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 = GEOHASH_FOLDER / f"{geohash}.parquet"
if not parquet_file.exists():
raise HTTPException(status_code=404, detail=f"No data found for geohash '{geohash}'.")
try:
# Load the Parquet file into a GeoDataFrame
gdf = gpd.read_parquet(parquet_file)
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to read Parquet file: {str(e)}")
point = Point(longitude, latitude)
geod = Geod(ellps="WGS84") # Accurate geodetic distance calculations
found_geometries = []
# Process each row
for _, row in gdf.iterrows():
geom = row.geometry # Directly use the Shapely geometry object
metadata = {}
if isinstance(row.get("tags"), dict):
metadata = {k: v for k, v in row["tags"].items() if v is not None} # Remove empty values
if geom.contains(point):
found_geometries.append({"type": "polygon", **metadata})
elif isinstance(geom, (LineString, MultiLineString)):
try:
if isinstance(geom, LineString):
min_distance = min(geod.inv(point.x, point.y, p[0], p[1])[2] for p in geom.coords)
elif isinstance(geom, 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": "line" if isinstance(geom, LineString) else "multi_line", **metadata})
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed distance calculation: {str(e)}")
# Return JSON result
return JSONResponse(content=found_geometries)