Mapbox – Two ways to show a map

For the past few months, I have been charged with visualizing data onto maps using either Google Maps or Mapbox. I chose the latter. After days and nights of struggle, I am pretty close to the finishing line and have gained quite a bit of experience in Mapbox that I want to share. 

Long story short, to map data onto maps, you need to structure data into a specific structure called GEOJSON. It looks like this:

You can put anything in the “properties” key, but the rest essentially have to follow the above format. The coordinates will be used to locate markers or data on the map. 

Let’s say the data that I want to map has 6 different mission areas (see the screenshot above) and my job is to map them onto the map in 6 different colors. 

I have an array that contains 6 different mission areas like this and 6 different colors representing those areas

var missionarea = [area1, area2, area3, area4, area5, area6]
var colorcode = [color1, color2, color3, color4, color5, color6]

One layer approach

#create a map canvas

var map = new mapboxgl.Map({
    container: 'map',
    style: 'mapbox://styles/mapbox/light-v9',
    center: [-96.797885, 39.363438],
    // initial zoom
    zoom: 3.3
});

#load data and create the layer

map.on("load", function() {

    map.addSource('communityData', {   #name the data as communityData
        type: 'geojson',
        data: communityData, #this is the name of your GEOJSON
    });

    map.addLayer({      #add the layer
        "id": "commMap", #your single layer's name
        "type": "circle", #meaning that each item will be a dot
        "source": "communityData", #the name of your data assigned above
        'layout': {},
        'paint': {
            "circle-radius": 8,
            "circle-opacity": 1,
            "circle-color": {
                "property": "Mission Area",
                "type": 'categorical',
                "stops": [ #assign color to respective mission areas
                    [Missionarea[0], colorcode[0]],
                    [Missionarea[1], colorcode[1]],
                    [Missionarea[2], colorcode[2]],
                    [Missionarea[3], colorcode[3]],
                    [Missionarea[4], colorcode[4]],
                    [Missionarea[5], colorcode[5]],
                ]
            }
        }
    });
}

Multiple Layers

In this example, I’ll label each map layer as “show0”, “show1″…”show5”

var showlist = [] //the array of layerIDs, used to categorize layers
var base = "show"
for (var i = 0; i < Missionarea.length; i++) { //populate showlist array
    var text = base + i
    showlist.push(text)
}
var map = new mapboxgl.Map({
    container: 'map',
    style: 'mapbox://styles/mapbox/light-v9',
    center: [-95.957309, 41.276479],
    // initial zoom
    zoom: 6
});

map.on("load", function() {

    map.addSource('communityData', {
        type: 'geojson',
        data: communityData,
    });
    //*********************************** Load partners *****************************************************

    communityData.features.forEach(function(feature) {
        var primary = feature.properties["Mission Area"];
       // iterate through the mission area array
        for (var i = 0; i < missionarea.length; i++) {
            if (primary == missionarea[i]) {
      // assign a name to each layer
                layerID = showlist[i];
                if (!map.getLayer(layerID)) {
                    map.addLayer({
                        "id": layerID, #layer's name
                        "type": "circle",
                        "source": "communityData",
                        "paint": {
                            "circle-radius": 8,
                            "circle-opacity": 1,
                            "circle-color": colorcode[i], #color
                        },
                        "filter": ["all", ["==", "Mission Area", primary]]
                    })
                }
            }
        }
    });

Which approach is better?

If the intention is just to show the dots, there is no difference and it depends on personal preference. However, if your code gets more complicated and as in my case, I had to create at least 6 filters on the map, things will get messy and one approach will no longer allow you to do what you want. Unfortunately, I don’t have that much experience yet to tell you more and I personally believe it’s a case-by-case thing. 

Going above and beyond

One of my Capstone project’s requirements is to match an address input by a user with a legislative district in Nebraska on a map visualization. Unfortunately, neither Google Map API or Mapbox API, two of the most popular map APIs out there, has that feature.

I read through their documentation and decided to email them both, hoping that I might have missed something or that their specialists might have some advice. Both came back with a response. The Google customer service agent simply said that there was no such feature from Google Map API and that I might go to a designated link to make a suggestion. Meanwhile, the Mapbox agent confirmed my suspicion that Mapbox didn’t offer that feature. However, he suggested a way to accomplish it by using turf Javascript package.

Even though both don’t have the feature I am looking for, the extra effort by the Mapbox agent delighted me. I like the brand even more now. In the future, if I have to look for a map API service, Mapbox, for sure, will be up there at the top of the list. It goes to show how a going-an-extra-mile customer service can leave a lasting positive impact on users and potential customers.

Potential new features for Mapbox and Google Map

I found two small players that offered legislative district mapping and census data: Cicero and US Geocoder. Surprisingly, Mapbox and Google Map do not offer such features. It shouldn’t be difficult for them to do so, I imagine. So I hope that they will take a suggestion from me and add those features to their already awesome products.

Create a hover effect on Mapbox

I am sharing my experience in trying to create a hover effect on Mapbox. The first thing to do is to read their example and understand what is going on. Let’s unpack a little bit:

<!DOCTYPE html>
<html>
<head>
    <meta charset='utf-8' />
    <title>Create a hover effect</title>
    <meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
    <script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.49.0/mapbox-gl.js'></script>
    <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.49.0/mapbox-gl.css' rel='stylesheet' />
    <style>
        body { margin:0; padding:0; }
        #map { position:absolute; top:0; bottom:0; width:100%; }
    </style>
</head>

It’s the <head> of the HTML that has scripts from Mapbox. Just follow them and you’ll be fine. Change the text in <title> to have your own page title.

<body>


—- Your real code goes here —–

</body>

Your real work will go between and . The <div> is a container that refers to the map you are working on. The next part is Mapbox token

mapboxgl.accessToken = '<your access token here>';

To get a token, just create a free account on Mapbox. A free account is allowed up to 50,000 requests a month if I am not mistaken. It should be enough for a student or an enthusiast wishing to try it out. Once you have a token, just put it in between ” in the above line.

Let’s have a base map

var map = new mapboxgl.Map({
    container: 'map',
    style: 'mapbox://styles/mapbox/streets-v9',
    center: [-100.486052, 37.830348],
    zoom: 2
});

The “center” feature’s coordinates refer to where you want to focus on. Get your chosen destination’s coordinates and just put them there. Alternate the two figures in coordinates if you don’t get it right in the first try. “Zoom” is how close you look at the chosen destination. The greater the number, the closer the zoom.

var hoveredStateId =  null;

map.on('load', function () {
    map.addSource("states", {
        "type": "geojson",
        "data": "https://www.mapbox.com/mapbox-gl-js/assets/us_states.geojson"
    });

HoveredStateID is a placeholder variable that will be used later for hover effect. The following code block is to load the base map. Just follow the templates. Three things to note here:

  • “state” refers to the object’s name that contains the GEOJSON data. You can name whatever you want
  • “GEOJSON” refers to to the style of the file. Mapping requires GEOJSON files, whether you load it from an external source, like we do in this case, or from a hardcoded file
  • The link that goes with “data” is where the author stores the data.

One note here: if you use Github or any cloud platform to store and source your file, be careful. For instance, let’s look at a file I have on github.

Github_Link

Just copying the usual link when you access your file on Github like that won’t work. To get the link that works, click on “Raw” and here is how it shows on the screen

Github_Content_Link

Copy the link in the browser. It should work.

Back to the HTML. Add the two “map.addLayer” code sections to what you already have. It should look like the below

map.on('load', function () {
    map.addSource("states", {
        "type": "geojson",
        "data": "https://www.mapbox.com/mapbox-gl-js/assets/us_states.geojson"
    });

    map.addLayer({
        "id": "state-fills",
        "type": "fill",
        "source": "states",
        "layout": {},
        "paint": {
            "fill-color": "#627BC1",
            "fill-opacity": ["case",
                ["boolean", ["feature-state", "hover"], false],
                1,
                0.5
            ]
        }
    });

    map.addLayer({
        "id": "state-borders",
        "type": "line",
        "source": "states",
        "layout": {},
        "paint": {
            "line-color": "#627BC1",
            "line-width": 2
        }
    });

The first addLayer is for the polygon itself while the second one is for the lines between the states. “id” refers to the name of the layer for future reference. Remember to tie the “source” value back to the name of map.addSource. In this case, it’s “states”. The rest is a Mapbox standard template for hover effect. You can change the color whenever you feel like.

The next step is to work on “hover effect”. Add the following code to the end of the previous block

    map.on("mousemove", "state-fills", function(e) {
        if (e.features.length > 0) {
            if (hoveredStateId) {
                map.setFeatureState({source: 'states', id: hoveredStateId}, { hover: false});
            }
            hoveredStateId = e.features[0].id;
            map.setFeatureState({source: 'states', id: hoveredStateId}, { hover: true});
        }
    });

    // When the mouse leaves the state-fill layer, update the feature state of the
    // previously hovered feature.
    map.on("mouseleave", "state-fills", function() {
        if (hoveredStateId) {
            map.setFeatureState({source: 'states', id: hoveredStateId}, { hover: false});
        }
        hoveredStateId =  null;
    });

The first thing to notice is here:  map.on(“mousemove”, “state-fills”, function(e) {

“State-fills” is the “id” of the polygon layer mentioned previously. So whatever name is chosen for that addLayer, it should be used here.

source: ‘states’

In this case, ‘states’ refers to the source of the data in the map.addSource section above. Remember to use the same reference name for the source. The rest is just a standard template. If you have time, feel free to explore. I am under pressure to deliver features for my Capstone, so I just prefer not touching or changing any of it.

Here is an important note. If you don’t follow, the hover effect won’t work. I use the same code as Mapbox’s example, just changing the GEOJSON source. The hover effect doesn’t work as you can see below:

The key is the data source. Let’s look at the data that Mapbox uses. Here is the tree view of the first item in the polygon array, just to show its structure

GEOJSON_2

Here is the structure of the data I used that led to the unsuccessful “hover effect”

GEOJSON_3

Notice the difference? As far as I am concerned, the hover template in question needs the data to have a certain structure. Otherwise, the code won’t work. Now, there should be other ways to go around this, but if you don’t have time, I’d suggest modifying the data to mirror Mapbox’s example. Here is the structure of my modified data

GEOJSON_4

Does the code work? You bet!

GEOJSON_5

Hopefully this post will be useful to starters like I am.