#!/usr/bin/env python3

# usage: uv run python gpx-track.py <img-directory>
# requires Python modules: pillow pillow-heif folium

import os, sys
import folium
from PIL import Image
from pillow_heif import register_heif_opener
register_heif_opener()

# convert EXIF GPS (degrees, minutes, seconds) tuple to decimal degrees
def gps_to_decimal(coords, ref: str) -> float:
    d, m, s = (float(x) for x in coords)
    decimal = d + m / 60 + s / 3600
    if ref in ('S', 'W'):
        decimal = -decimal
    return decimal

# extract GPS (lat, lon) from image EXIF; return None if unavailable
def get_gps_data(path: str):
    with Image.open(path) as img:
        exif = img.getexif()
    if not exif:
        return None

    gps_ifd = exif.get_ifd(0x8825)
    if not gps_ifd:
        return None

    lat  = gps_ifd.get(0x0002)  # GPSLatitude
    latR = gps_ifd.get(0x0001)  # GPSLatitudeRef
    lon  = gps_ifd.get(0x0004)  # GPSLongitude
    lonR = gps_ifd.get(0x0003)  # GPSLongitudeRef

    if lat and latR and lon and lonR:
        return (gps_to_decimal(lat, latR), gps_to_decimal(lon, lonR))
    return None

# write a list of (lat, lon) coordinates as a GPX track file
def create_gpx(coordinates, output_file='track.gpx'):
    lines = [
        '<?xml version="1.0" encoding="UTF-8"?>',
        '<gpx version="1.1" creator="EXIF-to-GPX Script">',
        '<trk><name>Photo Track</name><trkseg>',
        
        *[f'<trkpt lat="{lat}" lon="{lon}"></trkpt>' \
          for lat, lon in coordinates],

        '</trkseg></trk>',
        '</gpx>',
    ]
    with open(output_file, 'w') as f:
        f.write('\n'.join(lines))

# open image, resize to thumbnail, return as RGB PIL Image
def create_thumbnail(path, size=(100, 100)):
    with Image.open(path) as img:
        img.thumbnail(size)
        return img.convert('RGB')

# build an interactive folium map with a track line and photo markers
def create_map(photo_data, output_file='track_map_photos.html', output_dir='photo_thumbs'):
    if not photo_data:
        print("No photo data to plot")
        return

    os.makedirs(output_dir, exist_ok=True)
    coordinates = [coords for _, coords in photo_data]
    center = (sum(lat for lat, _ in coordinates) / len(coordinates),
              sum(lon for _, lon in coordinates) / len(coordinates))

    m = folium.Map(location=center, zoom_start=12)
    folium.PolyLine(coordinates, weight=3, color='red', opacity=0.8).add_to(m)

    for i, (filename, coords) in enumerate(photo_data):
        thumb_path = os.path.join(output_dir, f'thumb_{i}.jpg')
        create_thumbnail(filename).save(thumb_path)

        popup_html = f'''<div style="text-align:center">
            <img src="{thumb_path}" style="max-width:100px;margin-bottom:5px"><br>
            <a href="{filename}" target="_blank">{os.path.basename(filename)}</a>
        </div>'''

        folium.Marker(
            coords,
            popup=folium.Popup(popup_html, max_width=150),
            icon=folium.Icon(color='blue', icon='camera', prefix='fa')
        ).add_to(m)

    folium.Marker(coordinates[0],  popup='Start', icon=folium.Icon(color='green')).add_to(m)
    folium.Marker(coordinates[-1], popup='End',   icon=folium.Icon(color='red')).add_to(m)
    m.save(output_file)

# process all files in local directory or in the directory
# passed as parameter
def main():
    image_dir = sys.argv[1] if len(sys.argv) > 1 else '.'
    photo_data = []

    for filename in sorted(os.listdir(image_dir)):
        if filename.lower().endswith(('.jpg', '.jpeg', '.heic')):
            filepath = os.path.join(image_dir, filename)
            coords = get_gps_data(filepath)
            if coords:
                photo_data.append((filepath, coords))
                print(f"Extracted coordinates from {filename}: {coords}")
            else:
                print(f"No GPS data in {filename}")

    if photo_data:
        create_gpx([coords for _, coords in photo_data])
        print("Created track.gpx")
        create_map(photo_data)
        print("Created track_map_photos.html")
        print("Thumbnails saved in photo_thumbs/")
    else:
        print("No GPS coordinates found in any images")

if __name__ == "__main__":
    main()
