You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
129 lines
3.7 KiB
129 lines
3.7 KiB
from PIL import Image
|
|
from PIL.ExifTags import TAGS, GPSTAGS
|
|
import os
|
|
|
|
"""
|
|
Copyright (c) 2025 by Robert Strutts
|
|
License: MIT
|
|
"""
|
|
|
|
def get_exif_data(image_path):
|
|
"""Get EXIF data from image file"""
|
|
try:
|
|
with Image.open(image_path) as img:
|
|
exif_data = img._getexif()
|
|
if exif_data is not None:
|
|
return {TAGS.get(tag, tag): value for tag, value in exif_data.items()}
|
|
except (AttributeError, IOError, KeyError, IndexError) as e:
|
|
print(f"Error reading EXIF data: {e}")
|
|
return None
|
|
|
|
def get_camera_info(exif):
|
|
if not exif:
|
|
return None
|
|
|
|
camera_info = {
|
|
'Make': exif.get('Make', 'Unknown'),
|
|
'Model': exif.get('Model', 'Unknown')
|
|
}
|
|
|
|
# Some cameras store model in 'Camera Model Name' instead
|
|
if camera_info['Model'] == 'Unknown':
|
|
camera_info['Model'] = exif.get('Camera Model Name', 'Unknown')
|
|
|
|
return camera_info
|
|
|
|
def get_gps_info(exif_data):
|
|
"""Extract GPS information from EXIF data"""
|
|
if not exif_data or 'GPSInfo' not in exif_data:
|
|
return None
|
|
|
|
gps_info = {}
|
|
for key in exif_data['GPSInfo'].keys():
|
|
decode = GPSTAGS.get(key, key)
|
|
gps_info[decode] = exif_data['GPSInfo'][key]
|
|
|
|
return gps_info
|
|
|
|
def convert_to_degrees(value):
|
|
"""Convert GPS coordinates stored in EXIF to degrees in decimal format"""
|
|
d, m, s = value
|
|
return d + (m / 60.0) + (s / 3600.0)
|
|
|
|
def get_lat_lon(gps_info):
|
|
"""Get latitude and longitude from GPSInfo dictionary"""
|
|
if not gps_info:
|
|
return None, None
|
|
|
|
try:
|
|
lat_ref = gps_info.get('GPSLatitudeRef', 'N')
|
|
lat = gps_info.get('GPSLatitude')
|
|
lon_ref = gps_info.get('GPSLongitudeRef', 'E')
|
|
lon = gps_info.get('GPSLongitude')
|
|
|
|
if lat and lon:
|
|
lat_degrees = convert_to_degrees(lat)
|
|
if lat_ref == 'S':
|
|
lat_degrees = -lat_degrees
|
|
|
|
lon_degrees = convert_to_degrees(lon)
|
|
if lon_ref == 'W':
|
|
lon_degrees = -lon_degrees
|
|
|
|
return lat_degrees, lon_degrees
|
|
except (TypeError, AttributeError) as e:
|
|
print(f"Error converting coordinates: {e}")
|
|
|
|
return None, None
|
|
|
|
import math
|
|
|
|
def haversine(lat1, lon1, lat2, lon2):
|
|
"""
|
|
Calculate the great circle distance between two points
|
|
on the earth (specified in decimal degrees)
|
|
Returns distance in miles
|
|
"""
|
|
# Convert decimal degrees to radians
|
|
lat1, lon1, lat2, lon2 = map(math.radians, [lat1, lon1, lat2, lon2])
|
|
|
|
# Haversine formula
|
|
dlon = lon2 - lon1
|
|
dlat = lat2 - lat1
|
|
a = math.sin(dlat/2)**2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon/2)**2
|
|
c = 2 * math.asin(math.sqrt(a))
|
|
|
|
# Radius of earth in miles (3956 for miles, 6371 for kilometers)
|
|
r = 3956
|
|
return c * r
|
|
|
|
def are_within_one_mile(lat1, lon1, lat2, lon2):
|
|
distance = haversine(lat1, lon1, lat2, lon2)
|
|
return distance <= 1
|
|
|
|
"""
|
|
Example points:
|
|
point1 = (40.7128, -74.0060) # New York City
|
|
point2 = (40.7200, -74.0100) # About 0.5 miles from NYC
|
|
"""
|
|
|
|
def get_coordinates_from_image(image_path):
|
|
"""Main function to get coordinates from image file"""
|
|
exif_data = get_exif_data(image_path)
|
|
if not exif_data:
|
|
print("No EXIF data found in image.")
|
|
return None
|
|
|
|
gps_info = get_gps_info(exif_data)
|
|
if not gps_info:
|
|
print("No GPS information found in EXIF data.")
|
|
return None
|
|
|
|
camera_info = get_camera_info(exif_data)
|
|
|
|
latitude, longitude = get_lat_lon(gps_info)
|
|
if latitude is not None and longitude is not None:
|
|
return (camera_info, latitude, longitude)
|
|
else:
|
|
print("Could not extract valid coordinates.")
|
|
return None
|
|
|