---
title: From Photo Metadata to 3D Map: Building an Interactive Travel Diary
published: true
date: 2026-02-05 12:50:16 UTC
description: How I turned GPS-tagged vacation photos into an interactive 3D journey through Spain's terrain
tags: javascript, webdev, maps, visualization
cover_image: https://exploring-data.com/img/preview/elevation-diary-nerja.png
canonical_url: https://geeksta.net/geeklog/building-a-3d-elevation-photo-diary/
---
During a recent two-week vacation in Nerja, Spain, I took hundreds of photos with my phone. Many of them captured GPS coordinates and altitude data. Instead of letting that metadata sit unused, I decided to build an interactive 3D visualization that plots each photo on the actual terrain where it was taken.
{% embed https://www.youtube.com/watch?v=6Ul_n-zxAv4 %}
Quick demo of [the interactive viewer](https://exploring-data.com/map/3d/elevation-diary-nerja/)
## The Concept
The idea was simple: create a chronological journey through my vacation photos, where each photo appears on a 3D terrain map at its location. As you navigate through the photos, the camera flies to each spot, showing the landscape in 3D.
## Tech Stack
### deck.gl for 3D Visualization
I chose deck.gl for rendering the 3D terrain and photo markers. It's a WebGL-powered framework that handles complex 3D visualizations with impressive performance. Two key layers made this possible:
**TerrainLayer** - Renders the 3D elevation map using Terrarium-format tiles from AWS:
```
const terrain = new TerrainLayer({
id: 'terrain',
elevationData: 'https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png',
texture: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
...
});
```
**ScatterplotLayer** - Displays photo locations as 3D points on the terrain. I used two layers: one for visited photos (gray, smaller) and one for the current photo (orange, larger).
### Data Processing
The photos' EXIF data provided latitude, longitude, altitude, and timestamps. I extracted this into a CSV file with entries sorted chronologically:
```
filestem,latitude,longitude,altitude,timestamp
IMG_20260106_132810881,36.78705,-3.883663888888889,197.6,2026:01:06 13:28:12
...
```
## Key Features
### Smooth Camera Transitions
When navigating between photos, the camera smoothly flies to the new location using deck.gl's `FlyToInterpolator`:
```
deckInstance.setProps({
initialViewState: {
longitude: currentPhoto.longitude,
latitude: currentPhoto.latitude,
zoom: 15,
bearing: 180,
pitch: 40,
transitionDuration: 2000,
transitionInterpolator: new deck.FlyToInterpolator()
}
});
```
### Progressive Trail Visualization
As you move through the photos, previously visited locations remain visible in gray, creating a visual trail of your journey. The current photo is highlighted in orange.
### Responsive Layout
The interface adapts to different screen sizes:
- **Landscape:** Map takes 60-70% of width, photo/controls on the right
- **Portrait:** Map takes 60% of height, photo/controls below
```
@media (max-aspect-ratio: 1/1) {
#app-container {
flex-direction: column;
}
#map-container {
flex: 0 0 60%;
}
...
}
```
### Keyboard Navigation
Keyboard shortcuts for quick navigation:
- Arrow keys: Previous/Next photo
- Home/End: First/Last photo
- Spacebar: Play/Pause auto-advance
### Auto-Play Journey
A play button advances through photos automatically (3 seconds each), creating a cinematic journey through the vacation.
## Challenges & Solutions
### GPS Altitude Accuracy
Phone GPS altitude data can be unreliable, especially near sea level. I added a note in the info modal about this limitation. In future versions, I might cross-reference with the terrain elevation data to improve accuracy.
### Photo Loading Performance
I selected 300 of the photos that had GPS data and created smaller web-optimized versions using [wim](https://github.com/yaph/wim) to ensure quick loading without sacrificing too much quality.
### Mobile Layout
Getting the controls to fit on small portrait screens required careful tweaking of photo max-height and padding values. The final solution uses a compact info display (icons instead of labels) and reduced button spacing.
## What I Learned
1. **deck.gl is powerful but has a learning curve** - The layer system is elegant once you understand it, but proper coordinate handling and view state management took some experimentation.
2. **GPS metadata opens creative possibilities** - This project barely scratches the surface of what's possible with GPS metadata from photos.
3. **Responsive 3D is tricky** - Balancing the 3D visualization with UI controls across different screen sizes required more iteration than expected.
4. **Small touches matter** - The smooth camera transitions, progressive trail effect, and keyboard shortcuts help make the experience more engaging.
## Try It Yourself
**[View the live project →](https://exploring-data.com/map/3d/elevation-diary-nerja/)**
The project is built with vanilla JavaScript and deck.gl - no frameworks needed. If you have GPS-tagged photos from a trip, you could [adapt this code](https://github.com/exploringdata/website/blob/main/src/js/map/elevation-diary-nerja.js) to create your own elevation diary.
Have you built something similar or have ideas for improvements? Drop a comment below!
* * *
**Tech Used:**
- [deck.gl](https://deck.gl/) - 3D visualization
- [wim](https://github.com/yaph/wim) - Image optimization
- [AWS Terrain Tiles](https://registry.opendata.aws/terrain-tiles/) - Elevation data
- [Esri World Imagery](https://www.arcgis.com/home/item.html?id=10df2279f9684e4a9f6a7f08febac2a9) - Satellite imagery
* * *
Thank you for reading!
This article was written by Ramiro Gómez using open source software and the assistance of AI tools. While I strive to ensure accurate information, please verify any details independently before taking action. For more articles, visit the [Geeklog on geeksta.net](https://geeksta.net/geeklog/).