🎯 What You'll Learn
- Extract EXIF metadata from images using Python libraries
- Convert GPS DMS (Degrees/Minutes/Seconds) to Decimal Degrees
- Create interactive maps with Folium and multiple visualization layers
- Implement data interpolation for missing GPS coordinates
- Build a complete CLI tool for spatial image mapping
📋 Before You Begin
- Basic Python programming (functions, loops, pandas DataFrames)
- Understanding of GPS coordinate systems
- Familiarity with command-line interfaces
Table of Contents
Project Architecture
The application follows a standard ETL (Extract, Transform, Load) pattern:
Walk through directories to find
.jpg or .png files and extract EXIF headers using exifread library.Convert GPS degrees/minutes/seconds (DMS) to Decimal Degrees and handle missing data via interpolation using
pandas.Plot the points on an OpenStreetMap layer using
folium for interactive map generation.Implementation Script
First, install the required libraries:
bash
pip install pandas geopandas folium exifread
Now here's the complete implementation:
python
import os
import exifread
import pandas as pd
import geopandas as gpd
import folium
from shapely.geometry import Point
def get_decimal_from_dms(dms, ref):
"""Converts GPS DMS format to decimal degrees."""
degrees = float(dms[0].num) / float(dms[0].den)
minutes = float(dms[1].num) / float(dms[1].den)
seconds = float(dms[2].num) / float(dms[2].den)
decimal = degrees + (minutes / 60.0) + (seconds / 3600.0)
if ref in ['S', 'W']:
decimal = -decimal
return decimal
def process_images(root_folder):
data = []
for root, dirs, files in os.walk(root_folder):
for file in files:
if file.lower().endswith(('.jpg', '.jpeg', '.png')):
path = os.path.join(root, file)
with open(path, 'rb') as f:
tags = exifread.process_file(f, details=False)
try:
lat = get_decimal_from_dms(tags['GPS GPSLatitude'].values, tags['GPS GPSLatitudeRef'].printable)
lon = get_decimal_from_dms(tags['GPS GPSLongitude'].values, tags['GPS GPSLongitudeRef'].printable)
timestamp = tags.get('Image DateTime', 'Unknown')
data.append({'filename': file, 'lat': lat, 'lon': lon, 'time': timestamp})
except KeyError:
data.append({'filename': file, 'lat': None, 'lon': None, 'time': None})
return pd.DataFrame(data)
def create_map(df, output_html="map.html"):
df = df.dropna(subset=['lat', 'lon'])
start_coords = [df['lat'].mean(), df['lon'].mean()]
m = folium.Map(location=start_coords, zoom_start=12)
for _, row in df.iterrows():
folium.Marker(
location=[row['lat'], row['lon']],
popup=f"File: {row['filename']}
Time: {row['time']}"
).add_to(m)
m.save(output_html)
print(f"Map saved to {output_html}")
# Usage
if __name__ == "__main__":
folder_path = "./your_image_folder"
df_images = process_images(folder_path)
df_images[['lat', 'lon']] = df_images[['lat', 'lon']].interpolate(method='linear')
create_map(df_images)
Output
Map saved to map.html
Key Technical Considerations
Data Alignment and Interpolation
If your dataset contains images without GPS tags (common in indoor or shielded environments), linear interpolation works well assuming the images were captured at consistent intervals while moving.
python
# Use 'time' method if timestamps are converted to DatetimeIndex
df_images['time'] = pd.to_datetime(df_images['time'])
df_images.set_index('time', inplace=True)
df_images[['lat', 'lon']] = df_images[['lat', 'lon']].interpolate(method='time')
Converting to Spatial Dataset
Wrap the data in a GeoPandas GeoDataFrame for actual spatial analysis:
python
geometry = [Point(xy) for xy in zip(df_images.lon, df_images.lat)]
gdf = gpd.GeoDataFrame(df_images, geometry=geometry, crs="EPSG:4326")
What you can do
- Calculate distances between photo locations - Perform "Point in Polygon" joins with regional datasets - Analyze spatial clustering patterns
CLI Packaging
Wrap the logic using argparse for a true CLI tool:
bash
python map_images.py --source ./photos --output report.html
Expanding to Multiple Views
Create different layers for different data dimensions using Folium plugins.
python
import pandas as pd
import folium
from folium.plugins import HeatMap, MarkerCluster, AntPath
def create_multi_view_map(df, output_html="multi_view_map.html"):
df = df.dropna(subset=['lat', 'lon'])
if df.empty:
print("No valid spatial data found after cleaning.")
return
start_coords = [df['lat'].mean(), df['lon'].mean()]
m = folium.Map(location=start_coords, zoom_start=13, control_scale=True)
# 1. Standard Markers
fg_markers = folium.FeatureGroup(name="Photo Markers").add_to(m)
for _, row in df.iterrows():
folium.Marker(
location=[row['lat'], row['lon']],
popup=f"File: {row['filename']}
Time: {row['time']}"
).add_to(fg_markers)
# 2. Heatmap View
fg_heatmap = folium.FeatureGroup(name="Density Heatmap", show=False).add_to(m)
heat_data = [[row['lat'], row['lon']] for _, row in df.iterrows()]
HeatMap(heat_data).add_to(fg_heatmap)
# 3. Chronological Path
df_sorted = df.sort_values(by='time')
if len(df_sorted) > 1:
fg_path = folium.FeatureGroup(name="Chronological Path", show=False).add_to(m)
path_coords = [[row['lat'], row['lon']] for _, row in df_sorted.iterrows()]
AntPath(locations=path_coords, dash_array=[10, 20], delay=1000).add_to(fg_path)
# 4. Clustered View
fg_cluster = folium.FeatureGroup(name="Marker Clusters", show=False).add_to(m)
mc = MarkerCluster().add_to(fg_cluster)
for _, row in df.iterrows():
folium.Marker(
location=[row['lat'], row['lon']],
popup=f"File: {row['filename']}
Time: {row['time']}"
).add_to(mc)
folium.LayerControl(collapsed=False).add_to(m)
m.save(output_html)
print(f"Multi-view map saved to {output_html}")
Available Layers
- Photo Markers: Individual photo locations - Density Heatmap: Visualize photo density hotspots - Chronological Path: Connect points in capture order - Marker Clusters: Group nearby images on zoom out
Sector Applications
This utility transforms unstructured photo data into actionable spatial intelligence.
| Sector | Utility |
|---|---|
| Infrastructure Audit | Engineers walk rail lines or pipelines, snapping defect photos. The script generates a "Damage Map" for repair crews. |
| Supply Chain/Kirana | Field agents photograph storefronts across a district. The tool maps informal retail density for distribution planning. |
| Environmental Science | Researchers capture flora/fauna data. Instantly convert field photos into spatial datasets for research papers. |
| Insurance & Real Estate | Claims adjusters prove exactly where/when a photo was taken, creating an immutable spatial record. |
Project Roadmap
Bulletproof metadata extraction: Handle diverse EXIF versions, process multiple formats (.TIFF), robust error handling, export to GeoJSON/KML/CSV.
Time-based interpolation, velocity calculation, "Point-in-Polygon" checks, extract camera heading and altitude for 3D elevation views.
Interactive dashboard with togglable layers, integrated gallery (click marker to see full image), dynamic filtering by time range.
Key Takeaways
- Use
exifreadfor EXIF extraction and convert DMS to decimal degrees - Linear interpolation handles missing GPS data for sequential photos
- GeoPandas enables advanced spatial analysis beyond simple mapping
- Multi-view maps (Heatmap, Path, Clusters) provide different analytical perspectives
- This tool has applications across infrastructure, retail, environmental science, and insurance