2020 Challenge: Visit all streets of Munich

As a software engineer, I spend a lot of time sitting in front of my computer. As compensation, I like to do sports like running or gravel cycling. I also enjoy to walk around in Munich and discover new cool places. That brought me to my crazy new year's resolution: I want to visit all streets/places of Munich. Of course, I cannot archive this in 2020 but according to my calculations, it should be possible until 2023-2025.

Software

To keep track of my progress, I had to develop an application. I decided to use Kotlin and Spring Boot.

The free app Owntracks collects the GPS position of my iPhone and transmits the data via MQTT to a self-hosted RabbitMQ instance and added to a queue. My management application periodically polls the new data from this queue. The queuing has some practical advantages:

  • I never lose data, even if my application crashes or is in maintenance If my smartphone sends many locations at once (maybe due to networking problems) my application won't be overloaded.
  • If I encounter performance problems, I can easily launch more instances of my application without any changes to the data input

After receiving the location data, my application sends a request to the Open Street Map Nominatim Service, to get the name of the street. Then the location/street will be saved in a PostgreSQL database.

If the street is new (not already existing in the database) the application will push a message with the name of the street via Telegram.

Also, it renders a map of Munich, crops it to the current area, and sends it via Telegram. All discovered streets are green, all not yet visited streets are red.

I wrote the rendering algorithm completely by myself because I didn't find an existing one that satisfied my needs. It parses Open Street Map XML files and draws the ways/nodes to a BufferedImage. I use the following approximation to convert latitude and longitude to x and y coordinates:

private fun Double.toMapProjectionLat(): Int {
    return height - (((this - mapBeginLat) / (mapEndLat - mapBeginLat)) * height).toInt()
}

private fun Double.toMapProjectionLon(): Int {
    return (((this - mapBeginLon) / (mapEndLon - mapBeginLon)) * width).toInt()
}

I know its neither accurate nor efficient, but it works very well for my use-case.

Progress

I will post the updated maps here.