Mapping 15,000 Historical Photos of Lausanne
In 2021, I took the Digital Urban History course at EPFL as part of the Lausanne Time Machine project. The idea was to use digital tools to explore Lausanne’s history. My group’s project focused on the Ceinture Pichard, a series of roads built between 1838 and 1863 that let people bypass the old city center’s narrow, steep streets. We built an interactive map that placed historical photos along that route, with descriptions and historical analysis.
The map was small, maybe a few dozen curated images with hand-written commentary. But the data came from the Musee Historique Lausanne (MHL), and their collection was much bigger than what we used.
The Museum Had No Map
The MHL has thousands of geolocalized historical photos in their Museris database. Photos of Lausanne streets, buildings, events, people, spanning from 1808 to 2009. Each one has coordinates, a year, and a description. But there was no way to browse them on a map. You could search the database by keyword, but you couldn’t just look at a neighborhood and see what it looked like in 1920.
After the course ended, I kept thinking about it. The data was right there. So I built an interactive map for the entire collection: not just the Pichard belt, but every geolocalized image in the database.
1,099 Locations, 15,766 Images
The dataset covers two centuries of Lausanne. Each entry is a location (latitude, longitude) with images grouped by year. Some locations have photos from a single year; others span decades, showing the same street corner evolving over time.
The data lives in a single JSON file served statically. No API, no database queries at runtime. The entire dataset loads when you open the map. It’s about 1.5 MB, which is fine for a one-time fetch. This keeps the architecture dead simple: no backend, no auth, no server to maintain.
From Vanilla JS to SvelteKit
The first version was plain HTML, CSS, and JavaScript. Leaflet for the map, Leaflet.markercluster for grouping nearby points, noUiSlider for the year range filter. No build step; just open index.html in a browser.
It worked, but it felt clunky. The marker popups were cramped, the image viewer was basic, and the whole thing didn’t feel like a proper app. So I rewrote it.
The current stack:
- SvelteKit with static adapter: builds to plain HTML/CSS/JS, deployed as a static subfolder
- MapLibre GL instead of Leaflet: vector tiles, smooth zooming, built-in clustering
- Tailwind CSS for styling: a dark terminal aesthetic with CRT scanlines, cyan and gold accents on a CARTO Dark Matter basemap
The rewrite was mostly about the UI. The map logic is straightforward: load the JSON, build a GeoJSON FeatureCollection filtered by the year range, feed it to MapLibre’s clustering. When the year slider moves, rebuild the GeoJSON and update the source. MapLibre handles the rest: clustering, zooming, transitions.