🎯 What You'll Learn

📋 Before You Begin

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