Generating Map Tiles with Rust: A Performance-Driven Approach

Learn how to generate map tiles with Rust, a programming language that offers memory safety and C-like performance. This article explores how to create a custom map tile generator using Rust and data from the Vehicle Energy Dataset.
Generating Map Tiles with Rust: A Performance-Driven Approach

Generating Map Tiles with Rust

Sometimes, you must display vast amounts of data on an interactive map while keeping it usable and responsive. Interactive online maps are implemented in HTML, and adding many visual elements to the map display generally degrades performance and usability. A possible alternative is to draw all of the elements offline and display them over the map as a transparent layer using tiles. Each square tile neatly overlaps the map’s tiles, and the interactive map control handles far fewer visual elements.

I addressed this issue a few years ago

I addressed this issue a few years ago by writing a custom map tile generator using Python and data from the Vehicle Energy Dataset. This project illustrated how to display massive amounts of information on an interactive online map by using custom tile layers over the map. The process involves using a web application that generates, caches and serves the tiles.

As you know, Python is not fast

As you know, Python is not fast, so there is a significant performance hit while the web application generates each tile. When a tile is cached, the serving process is quick and is not noticeable while interacting with the map.

Still, I was unhappy with the performance

Still, I was unhappy with the performance, so I wanted to solve the problem again by dramatically improving the code execution speed. At first, I thought about converting the code base to Cython, but then my attention was diverted to another candidate.

Enter Rust

The Rust programming language has been on my radar for quite some time. With a background in C, C++, and C#, I was intrigued by the language’s promise of memory safety and C-like performance. I finally decided to have a go at it, and this problem looked like a perfect starting point to learn and exercise the language.

Rocket

To answer the web application question, I turned to Rocket. The Getting Started page from Rocket’s online documentation shows how easy it is to set up a basic web application. We will surely need more complexity to build our tile server, but the boilerplate seems minimal and straightforward. And, as it turned out to be, Rocket is very easy to use and adapt. It’s a keeper to me.

sqlx

After a few minutes online, I quickly realized that the most popular answer to accessing SQLite databases was through the sqlx package. It presents a different paradigm from the one I used in Python but much closer to the one I used in my former life when I developed in C#. Instead of generic data structures or Pandas DataFrames, you must use strongly typed data structures here. Although they are a bit more laborious to work with, they will bring an extra layer of sanity to your life.

Figure 1

PNG

Creating, drawing, and saving PNG files using the image crate is easy. The code to create a transparent tile is quite simple:

I also used the colorgrad package to handle the color gradient for the tiles.

It’s Tiles All The Way Down

Map tiles usually consist of square 256x256 bitmaps. We may address each tile by combining x and y coordinates, a “zoom” level, or a quadkey code. To each zoom level corresponds a square patchwork of tiles of different dimensions. The whole Earth is depicted on a single tile at the topmost level. By zooming in, the original tile is split up into four tiles. The following Figures 2 and 3 illustrate the process of zooming in.

Figure 2

Figure 3

Serving Tiles With Rust

We can now discuss the Rust server code to generate, cache, and serve tiles. The present solution closely follows the previous tile server design.

Figure 5

As you can see, the server replies to zoom levels ranging from one to eighteen only. This limitation was baked into the data generation process for the density database.

The web application draws each tile using the function listed in Figure 6 below.

Figure 6

As you can see from the listing above, the tile painting process has three steps. First, on line 12, we collect the tile’s per-pixel density information. Next, we retrieve the tile’s level range, i.e., the minimum and maximum density levels for the tile’s “zoom” level. Finally, on line 14, we paint the tile’s bitmap. The function finalizes by saving the tile bitmap to the file cache.

Figure 7

Using the Code

After correctly configuring the database file path, you start the tile server by opening a terminal window, changing to the Rust project directory, and running the following command:

cargo run --release

Next, you can open the map client and configure the density tile layer URI.

Figure 8

And that’s it!

Figure 9

Conclusion

My first foray into Rust was not nearly as difficult as I expected. I started by immersing myself in the available literature and YouTube videos before giving it a go. Next, I ensured I was using a helping hand with a great IDE from JetBrains: RustRover. Although still in preview mode, I found this IDE helpful and instructive when using Rust. Still, you will also be perfectly fine if you prefer Visual Studio Code. Just make sure you get the sanctioned plugins.