Powering maps across all platforms with Mapbox GL

Thiago Santos
Fri, 09 Sep 2016 00:00:00 -0400

Mapbox GL Native is at the core of our Android and iOS SDKs. It’s written with portability in mind, allowing us to quickly roll out Qt support for automotive environments and most recently Unity 3d support for games.

Mapbox GL Native is a combination of low-level, performance-oriented code and cross-platform portability: properties that often don’t come together. Let’s dive into the strategies that make it possible to bring Mapbox GL Native to so many new platforms.

Build and runtime dependencies

Before getting Mapbox GL Native running on a new framework, it’s important to understand the build dependencies to see if we can actually build it for the target platform.

OpenGL

Mapbox GL Native uses OpenGL for accelerated rendering. We need OpenGL 2.0 or OpenGL ES 2.0 to render maps, which is widely available these days on desktops, phones, and embedded devices.

Windows computers typically support DirectX instead of OpenGL, so the existing codebase can’t run natively on them. The ANGLE project translates from DirectX to OpenGL, so we’ll be able to support Windows eventually.

Dependencies

To make configuring and building Mapbox GL Native robust, we’ve made many of its dependencies self-contained, optional, or replaceable. Most dependencies are linked statically, to avoid version clashes and reduce our reliance on platform-specific dependency management.

These dependencies in particular can be omitted or replaced:

libuv for the event loop (main loop)

libcurl as an HTTP/HTTPS network backend

SQLite for storing cached tiles and offline maps in a database

libjpeg-turbo for JPEG image format support

libpng for PNG image format support

libwebp for WebP image format support

Nunicode for Unicode 8.0 support

On the Qt platform, Mapbox GL Native uses Qt-specific libraries instead of each of these libraries, saving valuable bytes on embedded platforms.

Modern C++

The core of Mapbox GL Native is written in modern C++, and we’re excited to take advantage of C++ 11 and 14 features. With new versions of C++, much of the functionality that was previously relegated to libraries is now available in the core language, making cross-platform builds easier and more reliable.

For instance, we use the <thread> module for background tasks that could potentially block the render thread, such as vector tile decoding, network I/O, disk I/O (if caching is enabled), and image decompression.

Integration

Mapbox GL Native has abstractions that allow a deep level of integration with frameworks. Most of the platform specific code lives inside the platform/ directory of our repository.

The directory is organized by platform subdirectories: android/, linux/, macos/, qt/, etc. The default/ subdirectory contains generic, platform-independent components.

We typically don’t use default/ components: on Android, for instance, we rely on the excellent OkHttp client for network requests rather than using libcurl. But keeping these components around makes it possible to incrementally add support for a new platform, initially using the default/ components but switching to platform-provided functionality as we build compatibility.

That’s also how we’re building Unity support: there’s a new directory called unity/ with platform-specific integration, and a build step that mostly uses default/ code as we evaluate Unity’s built-in functionality.

Event loop

Mapbox GL Native is interactive: it’s focused on animations, user input, and speedy rendering. The event loop orchestrates all of this activity by scheduling work and responding to events, like tap gestures and network activity. We use libuv by default, but it can be replaced by any other event loop implementing similar functionality. The Android SDK uses Looper.

Image decoding

We love vector maps, but raster images are still the best way to transfer satellite imagery, icons, and labels. By default, Mapbox GL Native decodes images using libpng, libjpeg-turbo, and libwebp. Some frameworks, such as Qt, include their own image decoder; we can use it by writing a lightweight adapter function.

For new platforms, we can get away with supporting only JPEG to display almost all raster maps. By adding WebP support, however, we can tap into much more compact maps to save bandwidth and disk space.

Networking and caching

The entire default network stack can be replaced. On the iOS platform, we replaced libcurl with NSURLSession. We did this at the “network request” level, so logic for retry, refresh, and caching can be reused, saving a lot of work.

Disk caching is optional and comes with the dependency on SQLite. A device with a read-only file system but RAM memory to spare can make use of the :memory: backend of the SQLite cache.

Running Mapbox GL Native on a new platform?

If you’re running Mapbox GL on a new platform and have questions, reach out on Twitter!