Skip to main content

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 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, monochrome color.

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

  • check Appropriate for icons that you want to color based on a data property.
  • check Appropriate for icons that change color when a user hovers or clicks on them.
  • close 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:

arrow-downDownload 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', (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.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Demo: Using recolorable images in Mapbox maps</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script src="https://api.tiles.mapbox.com/mapbox-gl-js/v3.3.0/mapbox-gl.js"></script>
<link
href="https://api.tiles.mapbox.com/mapbox-gl-js/v3.3.0/mapbox-gl.css"
rel="stylesheet"
/>
<style>
body {
margin: 0;
padding: 0;
}

#map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
<script>
mapboxgl.accessToken = '{{MAPBOX_ACCESS_TOKEN}}';

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

map.on('load', () => {
map.loadImage('https://docs.mapbox.com/demos/using-recolorable-images-in-mapbox-maps/shop-15.png', (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/style-spec/reference/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
]
}
});
});
});
</script>
</body>
</html>
SDF Value Cutoffs and Ranges

To render images with signed distance fields, we create a glyph texture that stores the distance to the next outline in every pixel. Inside of a glyph, the distance is negative; outside, it is positive. As an additional optimization, to fit into a one-byte unsigned integer, Mapbox shifts these ranges so that values between 192 and 255 represent “inside” a glyph and values from 0 to 191 represent "outside". This gives the appearance of a range of values from black (0) to white (255).

Maps SDK for iOS

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

// Add icon
mapboxMap.style.addImage(UIImage(named: "IMAGE_NAME")!,
id: "IMAGE_ID",
sdf: true)

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

// Add icon
try! mapView.mapboxMap.style.addImage(UIImage(named: "shop-15")!,
id: "shop-icon",
sdf: true,
stretchX: [],
stretchY: [])

// Access a vector tileset that contains places of interest
var source = VectorSource()
source.url = "mapbox://examples.dl46ljcs"
try! mapView.mapboxMap.style.addSource(source, id: "shop-data")

// Create a symbol layer and access the layer contained.
var layer = SymbolLayer(id: "shops")

// The source property refers to the identifier provided when the source was added.
layer.source = "shop-data"
layer.sourceLayer = "food_stores-8sw1vy"
layer.iconImage = .constant(.name("shop-icon"))


let expression = Expression(.match) {
Expression(.get) { "STORE_TYPE" }
"Convenience Store"
UIColor.red
"Convenience Store With Gas"
UIColor.red
"Pharmacy"
UIColor.red
"Specialty Food Store"
UIColor.orange
"Small Grocery Store"
UIColor.green
"Supercenter"
UIColor.blue
"Superette"
UIColor.blue
"Supermarket"
UIColor.blue
"Warehouse Club Store"
UIColor.blue
UIColor.black
}

layer.iconColor = .expression(expression)
try! mapView.mapboxMap.style.addLayer(layer, layerPosition: nil)

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.

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.

mapView?.getMapboxMap()?.loadStyle(
styleExtension = style(Style.LIGHT) {
// Add icon.
+image("shop-icon"){
bitmap(BitmapFactory.decodeResource(resources, R.drawable.shop_15))
sdf(true)
}
// Access a vector tileset that contains places of interest.
+vectorSource("shop-data") {
url("mapbox://examples.dl46ljcs")
}
// Create a symbol layer and access the layer contained.
+symbolLayer("shops", "shop-data") {
sourceLayer("food_stores-8sw1vy")
iconImage("shop-icon")
iconColor(
match {
get {
literal("STORE_TYPE")
}
stop {
literal("Convenience Store")
rgb(255.0, 140.0, 0.0)
}
stop {
literal("Convenience Store With Gas")
rgb(255.0, 140.0, 0.0)
}
stop {
literal("Pharmacy")
rgb(255.0, 140.0, 0.0)
}
stop {
literal("Specialty Food Store")
rgb(154.0, 205.0, 50.0)
}
stop {
literal("Small Grocery Store")
rgb(0.0, 128.0, 0.0)
}
stop {
literal("Supercenter")
rgb(0.0, 128.0, 0.0)
}
stop {
literal("Superette")
rgb(0.0, 128.0, 0.0)
}
stop {
literal("Supermarket")
rgb(0.0, 128.0, 0.0)
}
stop {
literal("Warehouse Club Store")
rgb(0.0, 128.0, 0.0)
}
rgb(255.0, 0.0, 0.0)
}
)
}
}
)

Mapbox Studio

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

Was this page helpful?