dopecarpet/api.py

106 lines
4.2 KiB
Python
Raw Normal View History

2025-03-05 19:22:56 +00:00
from fastapi import FastAPI, HTTPException, Query
from fastapi.responses import Response, FileResponse, JSONResponse
2025-02-27 10:33:19 +00:00
from fastapi.middleware.gzip import GZipMiddleware
import geopandas as gpd
import cbor2
from pathlib import Path
2025-03-05 19:22:56 +00:00
import pygeohash as pgh
from shapely.geometry import Point, LineString, MultiLineString
from pyproj import Geod
import json
2025-02-27 10:33:19 +00:00
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"})
2025-03-05 19:22:56 +00:00
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)