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:
2026-04-09 23:44:29 +02:00
parent 9c9f5bd899
commit 7cf546af7a
17 changed files with 529 additions and 46 deletions

View File

@@ -1,7 +1,7 @@
"""
Photo model definition
"""
from sqlalchemy import Column, String, Integer, Boolean, DateTime, ForeignKey, Text, Index
from sqlalchemy import Column, String, Integer, Float, Boolean, DateTime, ForeignKey, Text, Index
from sqlalchemy.sql import func
from datetime import datetime
import uuid
@@ -49,6 +49,13 @@ class Photo(Base):
# Metadata
exif_json = Column(Text) # full EXIF/XMP blob as JSON
# GPS coordinates extracted from EXIF, in signed decimal degrees
# (S latitude / W longitude are negative). Stored as first-class columns
# so the Map view and any future location filters can query/index them
# without parsing exif_json on every request.
latitude = Column(Float)
longitude = Column(Float)
# User-editable fields
user_title = Column(String)
@@ -91,4 +98,5 @@ class Photo(Base):
Index('ix_photos_color_label', 'color_label'),
Index('ix_photos_media_type', 'media_type'),
Index('ix_photos_processing_status', 'processing_status'),
Index('ix_photos_lat_lon', 'latitude', 'longitude'),
)