Building a Map

Why (and how) I built my own map for storing video footage.

In the beginning of the year, I decided to buy a GoPro to record my bike rides. I had multiple goals in mind:

  • Use it as some sort of adventure log book, recording long-distance trips across Belgium (and beyond). Slow TV.
  • Use it as some kind of dashcam1. While I am not planning to have an incident on the road, statistically it will happen. Video proof to document the road situation at the time of the incident is not only useful to car drivers, it can also be useful for cyclists.
  • Use it to document road conditions, near-misses, incorrect traffic signs, and badly-designed road layouts.

Before I bought the GoPro, I had already built a map of these situations, stored in my Obsidian vault. It looked something like this:

A Leaflet map of Brussels, generated from my Obsidian vault, with a handful of blue, orange and red markers.

Each marker was generated from a note, whose metadata (frontmatter) decided its category, and thus the colour on this map: red for near-misses, blue for weird traffic sign combinations, orange for potentially dangerous situations (usually bad road layout). As my GoPro rides were not easy to integrate into my Obsidian vault, maintaining this list there was no longer worth the friction. I decided to upgrade my workflow for documenting these conditions.

A New Map

The GoPro I bought has a built-in GPS receiver. That means each video file has the GPS coordinates encoded at every point during the recording. With a little help from Claude Code, I implemented a Python backend and a Vite frontend to not only visualise all recordings on a map, but also use it to store notes on these recordings, similar to how it worked in Obsidian.

A Leaflet map of Brussels, generated from my new system, with a handful of blue, orange and red markers.

Each line on the map corresponds to a GoPro trip, and each marker corresponds to a note on one of these trips. Clicking on one of these lines, or notes, allows playing back the video, showcasing the incident. An example from a recent trip in Brussels:

Video playing on the left, map with notes on the right.

So now I can not only look back at the accumulated footage that I have, but jump immediately to specific situations and document them.

Why?

The first question that I expect will be on a lot of people’s minds is: why bother doing this? Besides the joy I take in tinkering with technology, there are also a few practical cases for it.

First and foremost, it lets me monitor my own riding behaviour. While I consider myself a careful cyclist — obeying traffic rules, being aware of my surroundings, and so on — there are occasional lapses. Yesterday, in Antwerp, I failed to spot a moped coming at a busy intersection. Nothing happened; I was already braking before joining the mixed-use path. Still, it rattled me more than I expected. Watching the footage back let me see what I’d missed, and why. And having it pinned on a map means I’ll spot it if that intersection keeps coming up.

This brings me to my second use case: analysing lapses of others. All road users make mistakes, but many times these mistakes can be attributed to bad road design: a short turning lane, a badly-positioned traffic light, lack of dedicated lanes, and so on. As with my first use case (self-review), documenting this turns one-off annoyances into something I can point at. Some days I struggle to see anything but malice or plain carelessness2. Documenting it is still useful, either way.

A third reason is more general: documenting road-layout issues even when nothing happened — no incident, not even a near miss. Odd or outright incorrect traffic signs are, to my mind, part of a larger problem: communes, cities and their subcontractors don’t always bother getting the signage right. Signage may be downright hostile (“fietsers op de rijbaan” on a 70km/h road), or ambiguous. Documenting it helps me gauge how widespread the issue actually is.

Obviously a map like this has a huge bias. It only considers situations where I ride myself, and only situations where I ride with my GoPro turned on. While I earlier mentioned that I partially use the footage as some kind of dashcam, keeping the GoPro charged to go grocery shopping is more commitment than I can muster.

Technical Problems Encountered Along the Way

While building this app, I encountered a few issues. A short list:

  • Long GoPro recordings are split into multiple files. The GoPro prefixes each file with a number to encode which part it is (e.g., GX010064.MP4 is the first part of a recording, GX020064.MP4 is the second part of the same recording). Besides being unhelpful to sort lexicographically, split recordings need to be merged before being shown. Showing each recording separately would be a nuisance. The technical solution that had to be implemented was not to merge these files into a new file (which would be wasteful), but to dynamically combine these parts in a streaming fashion, so only content watched needs to be transcoded. On a technical level, this transcoding had to be implemented twice as my laptop has an AMD GPU but my home server, on which the app runs, uses an NVIDIA GPU. In short: VAAPI for development, and NVENC for deployment.
  • I mount my GoPro upside down, so it sits below my handlebar (where it does not compete for space with my GPS mount). Instead of flipping the footage itself, the GoPro stores this as metadata. Most video players read this information and do the necessary transformations during playback. However, the first implemented solution did not take this into account, resulting in all videos being played back upside down.
  • Some pauses are intentional, and do not break up the trip. The solution to the first problem was to use date and filename for matching split recordings, but this had to be generalised for larger trips where I took a small break (e.g., to eat something, do some shopping…). During file ingestion, the app checks for trips on the same day and merges those that took place within 30 minutes before/after an existing trip. This can be configured.
  • Some past trips of my GoPro did not have GPS enabled (I turned on this feature only recently). Where my bike GPS had recorded an activity path for the same ride, I matched them up manually.

A Retrospective on Generative AI

Claude Code is an amazing tool to design-and-implement a quick prototype of a simple application in a relatively short time. Without generative AI doing most of the work in the implementation of this tool, the construction of it would have taken me considerably longer, and probably been of worse quality. I have experience with frontends, with Python backends, and with (lightweight) database systems. However, setting all of this up in a coherent fashion, that is something I did not have a lot of experience with. And certainly, I never had to work with video files before in a manner that required me to do transcoding. While building this app (and some others) using generative AI, the long-term implications of these tools for the way software gets built were never far from my mind. Again, the positive is that the implementation of this app took barely any time (especially given the quality for such a small application). While I could have built a lot of what this app does myself in a similar time frame, it would have been a much simpler prototype: say a web app for note-taking with a video file being played, and then a separate map page with just the annotations. On a more negative note, I must repeat a critique that I have heard elsewhere: the fact that with Claude Code, I still do not have any experience doing video transcoding, despite it being an essential part of the application. Generative AI coding tools have their advantages, but I do wonder what the long-term effects of never building the skills needed to develop these applications will be.

Towards the Future

Now that I have implemented this application, there are two potential paths to extend its reach in the future.

First, I am contemplating releasing its source code. Licensing-wise, I feel the app’s source code should be made available, one way or another. It was built with a tool trained on a large quantity of open source code, and it seems only right that it flows back in the same direction. The legal position is murkier than I’d like, but morally it feels clear enough. Picking the right licence is something I would preferably let someone else decide.

Second, the app is only accessible on my home network. I could deploy it to a server and put it online. The recordings and notes, however, are meant for personal use. Publishing them would open a whole other can of worms. Belgian privacy law allows these recordings under the household exemption. Publishing them would take me straight out of that exemption and into the territory of blurring faces and number plates, which is a project of its own. So this is not something I am likely to pursue.

Footnotes

  1. A bike does not have a “dash” or “dashboard” though, so technically it should be referred to as a handlebar cam, which it kind of is.

  2. What’s the difference?