Serving map tiles to yourself using Protomaps and iOS
swifter-protomaps is a Swift-language package that exports a single function which implements code to serve Protomaps tile databases from a httpswifter/swifter web server. For many people, that sentence might sound like word salad so let’s go through each one of those items individually.
- Swift is a programming language used to write applications for the MacOS and iOS operating systems.
- swifter is a library that allows developers to write custom web servers, as standalone applications or as a part of a larger program, written in Swift.
- swifter-protomaps is a library that defines functionality for serving Protomaps database files from a
- Protomaps is a framework for bundling and dynamically styling map data using static database files, sourced from the OpenStreetMaps project.
swifter-protomaps bundles the 100+ lines of code necessary to serve Protomaps database files in a
swifter web server in to 4-6 lines of code. In short, it is what’s often referred to as a “helper” function. It is not fancy, or sophisticated, but we think it is useful.
import Swifter import SwifterProtomaps let root = URL(string: "/path/to/pmtiles") let port: in_port_t = 9000 let opts = ServeProtomapsOptions(root: root) opts.AllowOrigins = "*" opts.AllowHeaders = "*" let server = HttpServer() server["/pmtiles/:path"] = ServeProtomapsTiles(opts) server.start(port) // Error handling has been removed from the example above for the sake of brevity.
We have published this code as part of our ongoing commitment to releasing and promoting “small focused tools”:
The cultural heritage sector needs as many small, focused tools as it can produce. It needs them in the long-term to finally reach the goal of a common infrastructure that can be employed sector-wide. It needs them in the short-term to develop the skill and the practice required to make those tools successful. We need to learn how to scope the purpose of and our expectations of any single tool so that we can be generous of, and learn from, the inevitable missteps and false starts that will occur along the way.
But why was any of this necessary in the first place?
Last year, in the Interactive maps of SFO from 1930 to 2021 (and beyond) at the T2 SkyTerrace blog post I wrote:
The first versions of the [SkyTerrace interactive map] application also used a very detailed, but ultimately distracting, cartography to depict the present day airport and surrounding Bay Area. Once the decision was made to refactor the touch-based application in to something that could be controlled remotely I also took the opportunity to revisit the cartographic elements that frame the historical maps. To start, the detailed road networks of the San Francisco Bay Area were de-emphasized in favour of the Bay itself.
The coastline for the San Francisco Bay Area is derived using data from the OpenStreetMap project. That data is encoded as a GeoJSON record (700kb) which is bundled with the SkyTerrace interactive map application and rendered using the Leaflet mapping framework. This is a pretty common approach for web-based mapping applications and is orders (and orders) of magnitude simpler than rendering and storing individual map tiles for an area the size of the Bay Area from zoom levels 12 to 20.
This approach worked great until iOS 15, which introduced changes to the way that vector data (for example GeoJSON data) is rendered by the underlying operating system and suddenly a lot of things started breaking, including the SkyTerrace interactive map application. We figured this out through a process of trial and error and by trying to find reports of other people having similar problems, eventually landing on a bug report filed with Apple titled Safari crashes when GPU Process: Canvas Rendering is enabled with large paths.
The San Francisco Bay Area coastline is a “large path” and we were able to prevent crashing bugs by not including it in our application’s interface. With that knowledge I implemented some temporary workarounds and waited patiently for the bug to be fixed and rolled out to Safari (or more accurately WebKit which is the Safari web browser’s rendering engine). That happened earlier this year but those changes have not fixed our specific problem. Frustratingly, the bug has been fixed in both mobile and desktop versions of Safari but persists in instances of WebKitView which is the iOS component used to embed and render web content in iOS applications. For a longer discussion of how we’re using WebKitView to develop applications have a look at the iOS Multi-screen Starter Kit blog post.
Following a few false starts I eventually realized that if the SkyTerrace interactive map application could be updated to run its own web server as a background task then that web server could deliver tiles (to the web application running in a WebKitView component) for the San Francisco Bay Area coastline using a Protomaps database file and render them using the protomaps.js library. The Protomaps database file for the San Francisco Bay Area coastline is approximately 135MB which is over one hundred times larger than the GeoJSON file we’ve been using to draw the coastline but it’s also a fraction of the size required to store individual map tiles for the same area and only takes a couple of minutes to produce using the Protomaps large area map tool.
To help demonstrate why we think this is so exciting, we have released swift-protomaps-example which is a simple iOS (and Mac Catalyst) application to demonstrate use of the
sfomuseum/swifter-protomaps package. The application displays an interactive map of the San Francisco International Airport (SFO) and surrounding area using data from the OpenStreetMaps project. That data is stored in a Protomaps database file (2MB) which is bundled with the application which means it also works offline.
Aside from solving an immediate technical problem, we are excited about how this approach might be applied to future projects and we hope you will be too.