From Google Maps to MapBox
Tags: webdev, mapbox, maps
Published: 2020-05-26 22:17:20

Hi. Over the past two years, I have been working with maps and I want to share some knowledge writing maps from the ground up for a large real estate project.

## The Problem 😏

![Our first Google Map](https://dev-to-uploads.s3.amazonaws.com/i/g0hfqb3le4r1mftfy0h6.png)

This was our map as of two years ago. All of the data would load at page load and Google Maps would render basic pins and polygons.

The **first problem** with this implementation was that we couldn't render thousands of markers at the same time. The size of the data to load on the client was over 10MB.

The **second problem** was the lack of UI performance - rendering speed, inertia when dragging, and smoothness while zooming.

And our dream was to render 3D buildings 🏢


## Attempt #1 – Stick to Google Maps 🤔

To solve the first problem we considered clusterization. It looked less like a solution, and more like "hey, we are not able to render all the data you want to see, so here is your circle with a number, and try to zoom in more." Admission of defeat. Not an option 😎

![Clusterization example](https://dev-to-uploads.s3.amazonaws.com/i/f5miyhb6e84wevtqy28c.png)

So we moved towards [KML](https://developers.google.com/maps/documentation/javascript/kml). Despite the fact that KML is an XML format, debugging can be time-consuming: you send a KML payload to the Google server and get nothing in the response. No errors, just blank tiles. Having assembled a working prototype, we got the following:

![KML blinking](https://dev-to-uploads.s3.amazonaws.com/i/gyqs1ixgecotsoxmwrw0.gif)

We thought "cool, now we’re only rendering markers in the viewport as many as we want!" But with this came two new problems:

1. Flickering while zooming. Any new zoom level is a new request to the Google tile server. Tiles are raster images so they are reloaded, not scaled.
2. Markers were blurry on high-resolution displays. And there was no way to fix this problem.


## Attempt #2 – MapBox 😳

While searching for solutions we found MapBox. Unlike Google Maps, MapBox works with vector tiles and provides a very flexible API with [`mapbox-gl-js`](https://github.com/mapbox/mapbox-gl-js).

Instead of downloading all of the GeoJSON data to the client, we could now prepare our data to be distributed in the vector `.pbf` tiles format:

1. Convert GeoJSON file to `.mbtiles` with [tippecanoe](https://github.com/mapbox/tippecanoe)
2. Serve this file with a tile server - [MapBox Studio](https://www.mapbox.com/mapbox-studio/) or [tileserver-gl](https://github.com/maptiler/tileserver-gl), if you prefer a self-hosted solution.

The result exceeded all expectations 😱

![MapBox](https://dev-to-uploads.s3.amazonaws.com/i/v5aatp1faac040lqym12.gif)

Smoothly. Tiles are rendered without any flickering between zoom levels.


## Rendering 3D Models 🙀

Not so long ago MapBox added support for [Custom Layer](https://docs.mapbox.com/mapbox-gl-js/example/custom-style-layer/), with a low-level API to WebGL context. This makes it possible to render 3D models.

{% codesandbox mapbox-3d-model-5p080 %}

As of now, nothing like this can be done with Google Maps 🤷‍♀️

## The Magic of Extrusion 🧙‍♂️

Extrusion is another powerful feature of `mapbox-gl-js`. It helps us render [3D buildings on the fly](https://docs.mapbox.com/mapbox-gl-js/example/3d-buildings/). Unlike the 3D model example above, `fill-extrusion` works without any `.glb` or `.obj` files, rendering is based on provided GeoJSON properties. For example, the `height` property from the OpenStreetMaps database. Since we render tile by tile on the fly, this approach turned out to be very scaleable. The property `fill-extrusion` comes in handy when we want to render a single apartment or even an entire floor 😯

{% codesandbox mapbox-3d-floor-9d2t6 %}

This example is not interactive, without `hover` and `click` events, but they are super easy to add.

## Not Enough Minerals 💎

MapBox Studio and MapBox as a service are brilliant. Without any deep knowledge of what’s under the hood of the map or tile server, it helps create fast beautiful maps. But this service costs a pretty penny. So if you explore using it, it’s important to weigh out whether or not it will have a direct impact on your bottom line. In our case, it didn’t. 💸

But it is possible to rid yourself of the payments. Let’s a look at how to do that.

The first step is to move tile distribution to our end. For both map tiles and data tiles (if you do not render GeoJSON data directly).

First, you need to create a [Style JSON file](https://docs.mapbox.com/mapbox-gl-js/style-spec/). It has at least 3 important fields you need to host by yourself:

* [Sprite](https://docs.mapbox.com/mapbox-gl-js/style-spec/sprite/) - all icons that are used on the map. Take a look at the [Maki Icons](https://labs.mapbox.com/maki-icons/).
* [Glyphs](https://docs.mapbox.com/mapbox-gl-js/style-spec/glyphs/) - fonts. All fonts that are used on the map must be converted to `.*pbf`. For the most common font families, you check out [fonts.pbf](https://github.com/korywka/fonts.pbf)
* [Sources](https://docs.mapbox.com/mapbox-gl-js/style-spec/sources/) - the most challenging part. First, you need to find out OpenStreetMaps `.mbtiles`. There are two main options that we have found: buy them at [OpenMapTiles](https://openmaptiles.org/) (the latest data is not free for commercial use) or download OSM `.pbf` data at [Geofabrik](https://download.geofabrik.de/) (free and updated quite often), and convert it to tiles with [openmaptiles](https://github.com/openmaptiles/openmaptiles).

After hosting all of these style assets (I just have to mention [maputnik](https://maputnik.github.io/) - worthwhile layers editing tool) your map can be rendered by `mapbox-gl-js` without any Access Tokens. 

And then it’s time to think about how to render the vector data on top of it.

What have we tried?

1. Export MySQL data to GeoJSON file, convert it to `*.mptiles` file with [tippecanoe](https://github.com/mapbox/tippecanoe) and upload it to MapBox Studio.
2. Once we did this, we had to give up with Studio, we set up [tileserver-gl](https://github.com/maptiler/tileserver-gl) from MapTiler. And it’s great – all we have to do is set up a single config file.
3. Coming up in a minute 🙊

So we ended up with a cheap, fully self-hosted map that makes no concessions to the ones you have to pay for 💰

## Tiles on The Fly 🚀

In the end, our weakest pipeline was a dynamic data update - MySQL => PHP => GeoJSON => tippecanoe => MBTiles => upload tiles to `tileserver-gl` => reload Docker container 🤯

[MARTIN](https://github.com/urbica/martin) comes to the rescue here ✨ 

It is a tile server written in Rust and works on top of PostGIS. I don't know who came up with the idea, but it is genius.

You just have to create a database table (we were using PostgreSQL), fill in geo data, and point MARTIN to serve this table as a source. It works out of the box, without any config files. If you need to perform geospatial operations at the moment of tile creation (e.g. do not render geometries that [intersect](https://postgis.net/docs/ST_Intersects.html)) then [function sources](https://github.com/urbica/martin#function-sources) is the right place to do it. With a caching proxy (e.g. NGINX-based) in front of MARTIN, you will get a blazingly fast tile server ⚡

## The TL;DR 💡

* Switching from raster to vector tiles decreases loading time - often vector tiles are lighter with the ability to [overzoom](https://docs.mapbox.com/help/glossary/overzoom/).
* Vector maps are easy to customize with [MapBox Studio](https://www.mapbox.com/mapbox-studio/) or [maputnik](https://github.com/maputnik/editor).
* With OpenStreetMaps it is easy to add or update data like streets or POI, unlike Google Maps.
* [HERE](https://developer.here.com/documentation/map-tile/dev_guide/topics/example-satellite-map.html) have Satellite tiles with a significant free of charge limit.
* It’s not a good idea to use GeoJSON source for heavy data visualization. Serve it with tiles and let your users download only the data, that is visible in the viewport.
* Best not to hold a lot of data in `property` keys of GeoJSON Feature, it will make tiles heavier. If you need any properties after a click, you can load it later with an XHR request.
* If you can afford to pay for MapBox service, they certainly deserve every dollar. But if not, a step by step self-hosted tile server is not hard to implement.

`mapbox-gl-js` is definitely the future of geo data visualization on the web 🌏