feat: map view with GPS extraction fix
Adds a new Map sidebar entry that plots photos by their EXIF GPS coordinates on a clustered Leaflet map. While wiring this up, the metadata extractor was reading unprefixed GPS keys that never exist in `exiftool -G -j` output AND assumed coordinates were already floats — every photo silently lost its GPS. The new extract_gps helper handles Composite/EXIF group prefixes and parses DMS strings, and lat/lon are stored as first-class indexed columns so the map can query them without parsing exif_json on every request. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -214,6 +214,36 @@ async def list_photos(
|
||||
"pages": (total + per_page - 1) // per_page if total else 0,
|
||||
}
|
||||
|
||||
@router.get("/map")
|
||||
async def list_photos_with_gps(db: AsyncSession = Depends(get_db)):
|
||||
"""Lightweight listing of every non-discarded photo that has GPS
|
||||
coordinates, used by the Map view. Intentionally returns a flat list
|
||||
(no pagination) with only the fields the map renderer needs, so even
|
||||
large libraries serialize to a few MB at most. Declared *before*
|
||||
``/{photo_id}`` so the literal path wins the FastAPI route match."""
|
||||
result = await db.execute(
|
||||
select(
|
||||
Photo.id,
|
||||
Photo.latitude,
|
||||
Photo.longitude,
|
||||
Photo.taken_at,
|
||||
).where(
|
||||
Photo.is_discarded.is_(False),
|
||||
Photo.latitude.is_not(None),
|
||||
Photo.longitude.is_not(None),
|
||||
)
|
||||
)
|
||||
return [
|
||||
{
|
||||
"id": row.id,
|
||||
"latitude": row.latitude,
|
||||
"longitude": row.longitude,
|
||||
"taken_at": row.taken_at.isoformat() if row.taken_at else None,
|
||||
}
|
||||
for row in result.all()
|
||||
]
|
||||
|
||||
|
||||
@router.get("/{photo_id}")
|
||||
async def get_photo(
|
||||
photo_id: str,
|
||||
|
||||
Reference in New Issue
Block a user