Skip to content

Instantly share code, notes, and snippets.

@mapmeld
Last active March 4, 2019 15:12
Show Gist options
  • Save mapmeld/8866414b7fc8940e8540 to your computer and use it in GitHub Desktop.
Save mapmeld/8866414b7fc8940e8540 to your computer and use it in GitHub Desktop.
Getting Started with MapBoxGL

Getting Started

I recently made my first map with MapBox's new WebGL+JavaScript API. There aren't many examples of how to do this yet, even on MapBox's API page, so I'll document my own experience here.

The Van Gogh Map

My map is made of several textures taken from Van Gogh paintings. The long-term goal is to allow a user to select which artworks they want to take textures from, but for now there is just one setting.

Why are we changing maps?

Web maps are usually published as image "tiles". You go over all of your data and publish images for the world at each zoom level.

The downside: this requires a lot of storage space, inflexibility (to change the shade of water I need to reprocess all my tiles), and display limitations - changing labels, highlighting and clicking features, all require data to go through side channels.

Google, MapBox, and others have been moving to use "vector tiles" which send the raw data over at different resolutions for browsers to process and display instead of images. While some in the OSM community (such as Michal Migurski) have shared work using GeoJSON tiles, JSON is kinda verbose. MapBox has been using their own slimmed-down data standard, based on Protocol Buffers. Everything MapBox does here is open-sourced on their GitHub, but you're unlikely to see a lot of forks. For now the best data source and best renderer for this format is MapBox.

As part of that process, we're also seeing Google and MapBox shift to WebGL, which is better at rendering vectors and smoothly rendering this type of data.

Load MapBox's example

There's really no better guide to this API than looking at a working example. This is also a good test that your browser handles WebGL.

Set up your basic map page

If you've done web maps before this is pretty simple

<!DOCTYPE html>
<html>
  <head>
    <style>
    html, body, #map {
      height: 100%;
      width: 100%;
      padding: 0;
      margin: 0;
    }
    </style>
  </head>
  <body>
    <div id="map"></div>
  </body>
</html>

Add MapBox CSS (https://api.tiles.mapbox.com/mapbox-gl-js/v0.2.2/mapbox-gl.css) and JS (https://api.tiles.mapbox.com/mapbox-gl-js/v0.2.1/mapbox-gl.js) to the page. If you need to do JavaScript debugging, you can either npm install and npm run build on the JS repo or grab the unminified file.

Set up your initial JavaScript map

You will need a MapBox account. Copy your public API token from https://www.mapbox.com/account/apps/ , which begins with "pk."

Start your own JavaScript file, loaded after MapBox's file, with mapboxgl.accessToken = "pk._____"; for your token

Let's load a map with MapBox's style

var map = new mapboxgl.Map({
    container: 'map',
    center: [43.218, -71.686],
    zoom: 5,
    style: "https://www.mapbox.com/mapbox-gl-styles/styles/outdoors-v4.json",
    hash: true
});

Hooray! This should work.

Start hacking

Let's say you want to style the map on your own. The style link you gave, https://www.mapbox.com/mapbox-gl-styles/styles/outdoors-v4.json, is an example of the JSON object / URL to a JSON object which is a required by the "style" parameter. The most basic style which still looks like a map is just:

{
  "version": 4,
  "constants": {},
  "sources": {
    "mapbox": {
      "type": "vector",
      "url": "mapbox://mapbox.mapbox-terrain-v1,mapbox.mapbox-streets-v6-dev",
      "maxZoom": 15
    }
  },
  "layers": [
    {
      "id": "background",
      "style": {
        "background-color": "#fff"
      },
      "type": "background"
    },
    {
      "id": "water",
      "source": "mapbox",
      "source-layer": "water",
      "style": {
        "fill-outline-color": "#a2bdc0"
      },
      "type": "fill"
    }
  ]
}

You're just setting up a white background and then a blue outline around water (which, without a fill color, sets the interior of the water to black). See the example here.

If you've written Carto or used TileMill before, this should look a little familiar. If you haven't, get started now! It's fun to play around with, works offline, and is much easier to grasp than the WebGL stuff.

When the Carto/TileMill code looks like this:

#water {
  ::outline {
    line-color: #a2bdc0;
  }
}

the JSON says:

{
  "id": "water",
  "source": "mapbox",
  "source-layer": "water",
  "style": {
    "fill-outline-color": "#a2bdc0"
  },
  "type": "fill"
}

It's confusing because you want to write Carto directly, but style.json is not just a JSON representation of Carto - it's also helping the WebGL renderer. I'm not trying to criticize the design decisions by MapBox, but to caution developers that TileMill experience is both your greatest asset and greatest enemy in understanding this format.

Textures

My Van Gogh map uses image textures. The Style Spec says that there can be a "background-image" or a "fill-image", but didn't give me hints on which to use. Also these parameters expect "string" when I didn't know if they expected "water.png", "url('water.png')" which is used by TileMill, "data/png:####" ... ultimately this was the hardest part to get right.

The style JSON can have a "sprite" property which is a relative or absolute URL

{
  "version": 4,
  "sprite": "path/to/sprite",
  "constants": {
  },
  "sources": {
  ...

In this example, MapBox's library will load path/to/sprite.png, which should contain all of the icons and textures, and path/to/sprite.json, which should describe the x,y coordinates, width, and height of each image. It also loads [email protected] and [email protected] on retina-display devices, and without them your map will be blank! So make copies or make retina versions. Here's part of my sprite.json:

{
  "urban": {
    "x": 0,
    "y": 2303,
    "width": 444,
    "height": 195,
    "pixelRatio": 1,
    "sdf": false
  },
  "water": {
    "x": 0,
    "y": 2100,
    "width": 444,
    "height":  200,
    "pixelRatio": 1,
    "sdf": false
  },

Then when you want to use a texture, use "fill-image" and the name of the sprite from your sprite.json:

{
  "id": "water",
  "source": "mapbox",
  "source-layer": "water",
  "style": {
    "fill-image": "water"
  },
  "type": "fill"
}

I don't know any good sprite builders, but it comes in handy when you're finding the right coordinates and pasting together your textures.

Adding to style.json

I wanted to add textures not just to water, but to farms, grass, parks, etc. There are some examples in https://www.mapbox.com/mapbox-gl-styles/styles/outdoors-v4.json and other style JSONs of how to filter OSM data. Here are some examples:

{
  "id": "landuse_park",
  "source": "mapbox",
  "source-layer": "landuse",
  "filter": {
    "class": "park"
  },
  "style": {
    "fill-image": "grass"
  },
  "type": "fill"
}
{
  "id": "building",
  "source": "mapbox",
  "source-layer": "building",
  "style": {
    "fill-image": "urban"
  },
  "type": "fill"
}
{
  "id": "landcover_crop",
  "source": "mapbox",
  "source-layer": "landcover",
  "filter": { "class": "crop" },
  "style": {
    "fill-image": "farm"
  },
  "type": "fill"
}

Keep hacking

That's it! Play around with some of the parameters and refer to the real examples' style.json files when making your own filters.

Thanks MapBox for pushing the envelope and giving us WebGL maps!

@bsudekum
Copy link

@stevevance - a leaflet plugin is in the works.

@bsudekum
Copy link

Awesome work Nick!

@playground
Copy link

playground commented Jun 2, 2016

I'm new to Mapbox. How does outdoors-v4.json get generated?
And how to create something like this? https://chinesenewyear.withgoogle.com/#/sky/6245176972410880

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment