Files
image-gps-filter/filter_gps_images.py
doofmars eb243c5aa7 Initial commit
Filter images with dry run and bounding box
2025-10-04 11:17:21 +02:00

122 lines
4.8 KiB
Python

# Iterate over a folder with images
# print the image gps coordinates
# filter for images within a certain gps coordinates box
# Use pillow to read image metadata
import os
import argparse
import shutil
from PIL import Image
def parse_arguments():
parser = argparse.ArgumentParser(description="Filter images within a GPS coordinates box")
parser.add_argument("--input_folder", type=str, required=True, help="Path to the input folder containing images")
parser.add_argument("--output_folder", type=str, required=True, help="Path to the output folder to save filtered images")
gps_box_help = "Bounding box defined by min and max latitude and longitude" \
" Swiss Alps region: --gps_box 46.0 8.0 48.0 9.0" \
" Zurich area: --gps_box 47.2 8.3 47.6 8.7" \
" Bernese Alps: --gps_box 46.4 7.8 46.8 8.3"
parser.add_argument("--gps_box", type=float, nargs=4, required=True, metavar=('LAT_MIN', 'LON_MIN', 'LAT_MAX', 'LON_MAX'), help=gps_box_help)
parser.add_argument("--dry_run", action="store_true", help="Enable dry run mode")
# Add help text for gps_box
parser.add_argument("--verbose", action="store_true", help="Enable verbose output")
return parser.parse_args()
def get_gps_coordinates(image):
"""Extract GPS coordinates from image EXIF data"""
exifdata = image.getexif()
# Check if GPS info is available
if 34853 not in exifdata:
return None, None
# Use get_ifd() to access the actual GPS data dictionary
try:
gps_info = exifdata.get_ifd(34853)
except (KeyError, TypeError):
return None, None
# Check if gps_info is actually a dictionary with GPS data
if not isinstance(gps_info, dict) or len(gps_info) == 0:
return None, None
def convert_to_degrees(value):
"""Convert GPS coordinates to decimal degrees"""
try:
if isinstance(value, (list, tuple)) and len(value) >= 3:
d, m, s = value[0], value[1], value[2]
# Handle rational numbers (tuples like (40, 1) meaning 40/1)
if isinstance(d, tuple) and len(d) == 2:
d = d[0] / d[1]
if isinstance(m, tuple) and len(m) == 2:
m = m[0] / m[1]
if isinstance(s, tuple) and len(s) == 2:
s = s[0] / s[1]
return float(d) + (float(m) / 60.0) + (float(s) / 3600.0)
return None
except (ValueError, TypeError, IndexError, ZeroDivisionError):
return None
lat = None
lon = None
try:
# Get latitude
if 2 in gps_info and 1 in gps_info: # GPSLatitude and GPSLatitudeRef
lat = convert_to_degrees(gps_info[2])
if lat is not None and gps_info[1] == 'S':
lat = -lat
# Get longitude
if 4 in gps_info and 3 in gps_info: # GPSLongitude and GPSLongitudeRef
lon = convert_to_degrees(gps_info[4])
if lon is not None and gps_info[3] == 'W':
lon = -lon
except (TypeError, KeyError):
# Handle cases where gps_info doesn't support 'in' operator or key access
return None, None
return lat, lon
def is_within_gps_box(image, gps_box):
# Get the image gps coordinates from the image metadata
latitude, longitude = get_gps_coordinates(image)
if latitude is None or longitude is None:
print("No GPS data found in image")
return False
print(f"Image GPS coordinates: {latitude}, {longitude}")
lat_min, lon_min, lat_max, lon_max = gps_box
return lat_min <= latitude <= lat_max and lon_min <= longitude <= lon_max
def main():
args = parse_arguments()
if not os.path.exists(args.output_folder):
os.makedirs(args.output_folder)
for filename in os.listdir(args.input_folder):
if filename.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.tiff')):
image_path = os.path.join(args.input_folder, filename)
# Load the image metadata
image = Image.open(image_path)
if image is None:
print(f"Failed to load image: {image_path}")
continue
if is_within_gps_box(image, args.gps_box):
if args.dry_run:
print(f"Dry run: Image {filename} is within the GPS box")
lat, lon = get_gps_coordinates(image)
print(f"coordinates: {lat}, {lon}")
continue
output_path = os.path.join(args.output_folder, filename)
shutil.copy2(image_path, output_path)
print(f"Saved filtered image: {output_path}")
else:
print(f"Image {filename} is outside the bounding box")
if __name__ == "__main__":
main()