Troubleshooting

Using recolorable images in Mapbox maps

There are many ways to display and style images on Mapbox maps. This guide walks through how to add recolorable images — images that can be assigned a color at runtime.

What are Signed Distance Fields (SDF)?

One of the challenges of displaying images on a map is ensuring those images still look sharp regardless of their relative size. To address this challenge, the Mapbox GL map renderer can recognize some optimized image formats and treat them as something called a Signed Distance Field (SDF). SDFs are a way of rendering images specially designed to preserve sharp outlines from a pixel image even when the image is resized. The Mapbox Style Specification includes several options that allow you to manipulate icons as SDFs at runtime.

SDF encodes the distance from each pixel to the nearest edge of the image. As a result, raster images (like a PNG) that use a single color and have a transparent background are suitable for SDF.

In Mapbox maps, Mapbox GL generates an SDF from a raster image and renders the icon you see on the map from the SDF. Once the SDF is generated, the initial image is no longer needed.

Vector formats, like SVGs, can also be good candidates for generating an SDF. Although SVGs are a vector image format, the Mapbox GL renderer cannot treat them as vector data in the same way it handles vector tiles or GeoJSON. Converting them to SDFs, however, makes it possible to add a scalable, recolorable SVG to your map.

Benefits and limitations of SDF-enabled images

In the Mapbox Style Specification, it is possible to use SDFs for symbol layer icons. Mapbox-designed maps, like Mapbox Streets, do not commonly use SDFs for icons for things like points of interests (POIs) and highway shields because SDFs only support a single color. But SDF-enabled images can be appropriate in other situations. Below are some of the benefits and limitations related to SDF-enabled images.

Benefits

  • Optimized for displaying resized, recolored, and rotated raster images.
  • Icon color can be assigned at runtime.

Limitations

  • Designed to look good in only one arbitrary color.

Here are some examples of when SDF-enabled images may or may not be appropriate:

  • Appropriate for icons that you want to color based on a data property.
  • Appropriate for icons that change color when a user hovers or clicks on them.
  • Not appropriate for more detailed images like highway shields.

Using SDF-enabled images

Four style specification properties can only be used with SDF-enabled images: icon-color, icon-halo-color, icon-halo-width, and icon-halo-blur. This means that regardless of the platform you're developing on, if you want to, for example, change the color of icons in a symbol layer based on the value of a data property, you will need to use an SDF-enabled image as an icon and then use property expressions to specify the icon-color based on a data property.

The examples below all use the following image:

Download the sample PNG
Alternatives

There are a couple alternatives you could consider to create a similar effect without using SDF-enabled images as icons, including:

  • Add multiple icons to your sprite — one in each color you want to be able to display. Then, use property expressions to determine which icon-image to use based on a data property.
  • Use HTML markers in Mapbox GL JS or annotations on iOS and Android.
  • Use a circle layer and property expressions to specify the circle-color based on a data property.

Mapbox GL JS

For web applications, you can add SDF-enabled images to your map style on the client using addImage() with the sdf: true option.

map.loadImage('./your-file-name.png', function(error, image) {
  if (error) throw error;
  // add image to the active style and make it SDF-enabled
  map.addImage('your-image-id', image, { sdf: true });
});

Below is an example using a PNG image stored locally in the application, adding it to the map using style.addImage(), and setting the color of each icon according to the STORE_TYPE data property.

mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN';

var map = new mapboxgl.Map({
  'container': 'map',
  'style':'mapbox://styles/mapbox/light-v10',
  'center': [-105.0178157, 39.737925],
  'zoom': 12
});

map.on("load", function(){
  map.loadImage('shop-15.png', function(error, image) {
    if (error) throw error;
    map.addImage('store-icon', image, { 'sdf': true });
    map.addSource("food-stores", {
      'type': 'vector',
      'url': 'mapbox://examples.dl46ljcs'
    });
    map.addLayer({
      'id': 'stores',
      'source': 'food-stores',
      'source-layer': 'food_stores-8sw1vy',
      'type': 'symbol',
      'layout': {
        'icon-image': 'store-icon',
        'icon-size': 0.5
      },
      'paint': {
        'icon-color': [
          'match', // Use the 'match' expression: https://docs.mapbox.com/mapbox-gl-js/style-spec/#expressions-match
          ['get', 'STORE_TYPE'], // Use the result 'STORE_TYPE' property
          'Convenience Store', '#FF8C00',
          'Convenience Store With Gas', '#FF8C00',
          'Pharmacy', '#FF8C00',
          'Specialty Food Store', '#9ACD32',
          'Small Grocery Store', '#008000',
          'Supercenter', '#008000',
          'Superette', '#008000',
          'Supermarket', '#008000',
          'Warehouse Club Store', '#008000',
          '#FF0000' // any other store type
        ]
      }
    });
  });
});

Maps SDK for iOS

For iOS applications, the Maps SDK for iOS supports template images. To make a recolorable image with an optional halo, use UIImage.withRenderingMode(_:) or select the “Always Template” rendering mode option in Xcode’s asset catalog.

If you place the image in your application’s asset catalog, set Render As to Template Image:

Then add the image to the style using style.setImage():

let image = UIImage(named: "image-name")
style.setImage(image, forName: "icon-name")
Note

If you obtained the image in some other way, such as from the application target’s resource bundle, an online download, or a CGContext, create a template image before adding it to the style:

style.setImage(image.withRenderingMode(.alwaysTemplate), forName: "icon-name")

Below is an example using a PNG image stored in the application's asset catalog, adding it to the map using style.setImage(), and setting the color of each icon according to a data property, STORE_TYPE.

show hidden lines
func mapView(_ mapView: MGLMapView, didFinishLoading style: MGLStyle) {
// Add an image to the style
let image = UIImage(named: "store-icon")
style.setImage(image!, forName: "store-icon")
// Add a source to the style
let source = MGLVectorTileSource(identifier: "stores", configurationURL: URL(string: "mapbox://examples.dl46ljcs")!)
style.addSource(source)
// Add a layer to the style
let layer = MGLSymbolStyleLayer(identifier: "store-style", source: source)
layer.sourceLayerIdentifier = "food_stores-8sw1vy"
layer.iconImageName = NSExpression(forConstantValue: "store-icon")
layer.iconColor = NSExpression(format: "MGL_MATCH(STORE_TYPE, 'Convenience Store', '#FF8C00', 'Convenience Store With Gas', '#FF8C00', 'Pharmacy', '#FF8C00', 'Specialty Food Store', '#9ACD32', 'Small Grocery Store', '#008000', 'Supercenter', '#008000', 'Superette', '#008000', 'Supermarket', '#008000', 'Warehouse Club Store', '#008000', '#FF0000')")
style.addLayer(layer)
}
show hidden lines

Maps SDK for Android

For Android applications, you can add SDF-enabled images to your map style on the client using addImage() with the sdf: true option.

style.addImage("icon-name", BitmapFactory.decodeResource(MainActivity.this.getResources(), R.drawable.ICON_NAME), true);

Below is an example using a PNG image stored locally in the application, adding it to the map using style.addImage(), and setting the color of each icon according to a data property, STORE_TYPE.

MainActivity.java.java
show hidden lines
private void addShopLayer(@NonNull Style style){
style.addImage("shop-icon", BitmapFactory.decodeResource(MainActivity.this.getResources(), R.drawable.shop_15), true);
style.addSource(new VectorSource("shop-data", "mapbox://examples.dl46ljcs"));
SymbolLayer shopLayer = new SymbolLayer("shops", "shop-data");
shopLayer.setSourceLayer("food_stores-8sw1vy");
shopLayer.setProperties(
iconImage("shop-icon"),
iconColor(
match(
get("STORE_TYPE"), color(Color.parseColor("#FF0000")),
stop("Convenience Store", color(Color.parseColor("#FF8C00"))),
stop("Convenience Store With Gas", color(Color.parseColor("#FF8C00"))),
stop("Pharmacy", color(Color.parseColor("#FF8C00"))),
stop("Specialty Food Store", color(Color.parseColor("#9ACD32"))),
stop("Small Grocery Store", color(Color.parseColor("#008000"))),
stop("Supercenter", color(Color.parseColor("#008000"))),
stop("Superette", color(Color.parseColor("#008000"))),
stop("Supermarket", color(Color.parseColor("#008000"))),
stop("Warehouse Club Store", color(Color.parseColor("#008000")))
)
)
);
style.addLayer(shopLayer);
}
show hidden lines

Mapbox Studio

It is not possible to add, style, or preview images as SDFs in Mapbox Studio.

Was this page helpful?