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.
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.
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.
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.
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.
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.
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.
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.
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"
}
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!
I know MapBox designs Mapbox.js to be compatible with Lealfet.js but do you see this coming to Leaflet.js?