Search code examples
geojsonmapbox-gl-jsoverpass-apimapbox-marker

Mapbox GL JS: How can I use JavaScript with expressions to change layer layout?


I'm using Overpass to fetch OSM data and in Mapbox show the closest point of interest of a couple of 'categories'. I convert the OSM data to a GeoJSON layer and add it to Mapbox as a symbol layer with a marker image on each point in the data. The result looks like this:

enter image description here

I want to change the text displayed beneath each marker to something other than the variables I use to categorize the data. My first idea was to use a JS object like:

translate = {
    'water': 'Waterway',
    'city': 'City Center',
    'school': 'Elementary School',
    ..etc
}

and then run each name through translate[venue_type]. This doesn't seem to work that easily though. The marker is added as a layer like this:

map.loadImage(
    './img/map_marker3.png',
    (error, image) => {
        if (error) throw error;
        if (!map.hasImage('custom-marker')) {
            map.addImage('custom-marker', image);
        }
        map.addSource('venues',
        {
            'type': 'geojson',
            'data': data
        });
        map.addLayer({
            "id": "venues",
            "type": "symbol",
            "source": "venues",
            'layout': {
                'visibility': 'none',
                'icon-image': 'custom-marker',
                'icon-size': 1,
                'icon-anchor': 'bottom',
                'icon-allow-overlap': true,
                // get the venue_type from the feature's venue_type property
                'text-field': ['get','venue_type'], 
                'text-allow-overlap': true,
                'text-font': [
                    'Open Sans Semibold',
                    'Arial Unicode MS Bold'
                ],
                'text-offset': [0, 0.5],
                'text-anchor': 'top'
            }
        });
        callback();
    });

with a GeoJson FeatureCollection of the following structure:

"type": "Feature",
"properties": {
    "dist": 0.25566043226822727,
    "index": 758,
    "location": 21.218387011937942,
    "name": "Östersjön",
    "venue_type": "coastline"
},
"geometry": {
    "type": "Point",
    "coordinates": [
        18.3434769,
        59.4563257
    ]
}

The line responsible for setting the text underneath the marker is the following, which uses a Mapbox GL JS expression to fetch the field 'venue_type' from the properties of the GeoJSON object.

'text-field': ['get','venue_type']

I figured I could just do something like

'text-field': translate[['get','venue_type']]

but it gives the error:

Error: layers.venues.layout.text-field: 'undefined' value invalid. Use null instead.
at Object.ri [as emitValidationErrors] 

text-field can take both a string, variable and a mapbox expression. But I'm not really sure if I can access the properties of the feature being added in any other way than the expression above.

Having looked into modifying the expression, it appears to be possible to use arithmetics and stuff, but I can't really find any example of mixing JavaScript into the expression.

Looking for some pointers here, should I go about it a different way? Or am I missing something?


Solution

  • First, this is a symbol layer, not a Marker.

    You can't execute arbitrary JavaScript expressions within Mapbox GL expressions.

    You can use a match expression, like this:

    'text-field': [`match', ['get', 'venue_type'],
      'water', 'Waterway',
      'city', 'City Center',
      'school', 'Elementary School',
      ''
    ]