Skip to content

Style

A style describes what a map shows: its sources supply data, and its layers draw that data in order. This guide shows how to load a style and change its sources, layers, and images. Sources and layers use the JSON of the MapLibre style spec.

Set the style by URL with mln_map_set_style_urlMapHandle.setStyleUrlMapHandle.SetStyleUrlAsyncMapHandle.SetStyleURLmaplibre_native_ffi_map_set_style_url_asyncMapHandle.setStyleUrlMapHandle.setStyleUrlMapHandle.set_style_urlMapHandle::set_style_urlMapHandle.setStyleUrlMapHandle.setStyleUrlMap.setStyleUrl, or by JSON with mln_map_set_style_jsonMapHandle.setStyleJsonMapHandle.SetStyleJsonAsyncMapHandle.SetStyleJSONmaplibre_native_ffi_map_set_style_json_asyncMapHandle.setStyleJsonMapHandle.setStyleJsonMapHandle.set_style_jsonMapHandle::set_style_jsonMapHandle.setStyleJsonMapHandle.setStyleJsonMap.setStyleJson. Either one completes once the map has parsed the style, or fails when the style doesn’t load.

main.c
static void on_style_loaded(
void*, const mln_completion_result* result
) {
if (result->status == MLN_STATUS_OK) {
add_sources_and_layers();
}
}
const mln_completion loaded = {
.size = sizeof(mln_completion), .callback = on_style_loaded
};
mln_map_set_style_url(
map,
mln_buffer_view_from_cstr(
"https://demotiles.maplibre.org/style.json"
),
&loaded, NULL
);
main.dart
await map.setStyleUrl('https://demotiles.maplibre.org/style.json');
Program.cs
await map.SetStyleUrlAsync(
"https://demotiles.maplibre.org/style.json"
);
main.go
loaded, err := m.SetStyleURL(
"https://demotiles.maplibre.org/style.json",
)
if err != nil {
return err
}
if _, err := loaded.Await(ctx); err != nil {
return err
}
main.vala
yield map.set_style_url_async (
"https://demotiles.maplibre.org/style.json"
);
Main.java
map.setStyleUrl("https://demotiles.maplibre.org/style.json")
.join();
Main.kt
map.setStyleUrl("https://demotiles.maplibre.org/style.json").await()
main.py
map_handle.set_style_url(
"https://demotiles.maplibre.org/style.json"
).result()
main.rs
map.set_style_url("https://demotiles.maplibre.org/style.json")?
.await?;
main.swift
try await map
.setStyleUrl("https://demotiles.maplibre.org/style.json")
main.ts
await map.setStyleUrl("https://demotiles.maplibre.org/style.json");
main.zig
const loaded = try map.setStyleUrl(
"https://demotiles.maplibre.org/style.json",
null,
);
_ = try loaded.await(null);

Add a source by its ID and its JSON with mln_map_add_style_source_jsonMapHandle.addStyleSourceJsonMapHandle.AddStyleSourceJsonAsyncMapHandle.AddStyleSourceJSONmaplibre_native_ffi_map_add_style_source_json_asyncMapHandle.addStyleSourceJsonMapHandle.addStyleSourceJsonMapHandle.add_style_source_jsonMapHandle::add_style_source_jsonMapHandle.addStyleSourceJsonMapHandle.addStyleSourceJsonMap.addStyleSourceJson. The JSON is the same object that a style puts under sources. See Sources in the style spec.

Add a layer by its JSON with mln_map_add_style_layer_jsonMapHandle.addStyleLayerJsonMapHandle.AddStyleLayerJsonAsyncMapHandle.AddStyleLayerJSONmaplibre_native_ffi_map_add_style_layer_json_asyncMapHandle.addStyleLayerJsonMapHandle.addStyleLayerJsonMapHandle.add_style_layer_jsonMapHandle::add_style_layer_jsonMapHandle.addStyleLayerJsonMapHandle.addStyleLayerJsonMap.addStyleLayerJson. It draws on top, unless you give the ID of a layer to draw it under. See Layers in the style spec.

main.c
mln_map_add_style_source_json(
map, mln_buffer_view_from_cstr("satellite"),
mln_buffer_view_from_cstr(
"{\"type\":\"raster\",\"tileSize\":256,"
"\"tiles\":[\"https://tiles.example.com/{z}/{x}/{y}.png\"]}"
),
NULL, NULL
);
// Draw the layer under the "countries-label" layer.
mln_map_add_style_layer_json(
map,
mln_buffer_view_from_cstr(
"{\"id\":\"satellite\",\"type\":\"raster\",\"source\":"
"\"satellite\"}"
),
mln_buffer_view_from_cstr("countries-label"), NULL, NULL
);
main.dart
map.discarding.addStyleSourceJson('satellite', {
'type': 'raster',
'tileSize': 256,
'tiles': ['https://tiles.example.com/{z}/{x}/{y}.png'],
});
// Draw the layer under the "countries-label" layer.
map.discarding.addStyleLayerJson({
'id': 'satellite',
'type': 'raster',
'source': 'satellite',
}, beforeLayerId: 'countries-label');
Program.cs
map.Discarding.AddStyleSourceJson(
"satellite",
"""
{
"type": "raster",
"tileSize": 256,
"tiles": ["https://tiles.example.com/{z}/{x}/{y}.png"]
}
"""
);
// Draw the layer under the "countries-label" layer.
map.Discarding.AddStyleLayerJson(
"""
{"id":"satellite","type":"raster","source":"satellite"}
""",
"countries-label"
);
main.go
err = m.Discarding().AddStyleSourceJSON("satellite", `{
"type": "raster",
"tileSize": 256,
"tiles": ["https://tiles.example.com/{z}/{x}/{y}.png"]
}`)
if err != nil {
return err
}
// Draw the layer under the "countries-label" layer.
err = m.Discarding().AddStyleLayerJSON(
`{"id": "satellite", "type": "raster", "source": "satellite"}`,
"countries-label",
)
if err != nil {
return err
}
main.vala
map.add_style_source_json_async.begin (
"satellite",
"""{"type": "raster", "tileSize": 256,
"tiles": ["https://tiles.example.com/{z}/{x}/{y}.png"]}"""
);
// Draw the layer under the "countries-label" layer.
map.add_style_layer_json_async.begin (
"""{"id": "satellite", "type": "raster",
"source": "satellite"}""",
"countries-label"
);
Main.java
map.getDiscarding()
.addStyleSourceJson(
"satellite",
"""
{
"type": "raster",
"tileSize": 256,
"tiles": ["https://tiles.example.com/{z}/{x}/{y}.png"]
}
""");
// Draw the layer under the "countries-label" layer.
map.getDiscarding()
.addStyleLayerJson(
"""
{"id": "satellite", "type": "raster", "source": "satellite"}
""",
"countries-label");
Main.kt
map.discarding.addStyleSourceJson(
"satellite",
"""
{
"type": "raster",
"tileSize": 256,
"tiles": ["https://tiles.example.com/{z}/{x}/{y}.png"]
}
""",
)
// Draw the layer under the "countries-label" layer.
map.discarding.addStyleLayerJson(
"""
{"id": "satellite", "type": "raster", "source": "satellite"}
""",
"countries-label",
)
main.py
map_handle.discarding.add_style_source_json(
"satellite",
{
"type": "raster",
"tileSize": 256,
"tiles": ["https://tiles.example.com/{z}/{x}/{y}.png"],
},
)
# Draw the layer under the "countries-label" layer.
map_handle.discarding.add_style_layer_json(
{"id": "satellite", "type": "raster", "source": "satellite"},
"countries-label",
)
main.rs
map.discarding().add_style_source_json(
"satellite",
r#"{
"type": "raster",
"tileSize": 256,
"tiles": ["https://tiles.example.com/{z}/{x}/{y}.png"]
}"#,
)?;
// Draw the layer under the "countries-label" layer.
map.discarding().add_style_layer_json(
r#"{"id":"satellite","type":"raster","source":"satellite"}"#,
Some("countries-label"),
)?;
main.swift
try map.discarding.addStyleSourceJson("satellite", sourceJson: """
{
"type": "raster",
"tileSize": 256,
"tiles": ["https://tiles.example.com/{z}/{x}/{y}.png"]
}
""")
// Draw the layer under the "countries-label" layer.
try map.discarding.addStyleLayerJson(
#"{"id": "satellite", "type": "raster", "source": "satellite"}"#,
beforeLayerId: "countries-label"
)
main.ts
map.discarding.addStyleSourceJson("satellite", {
type: "raster",
tileSize: 256,
tiles: ["https://tiles.example.com/{z}/{x}/{y}.png"],
});
// Draw the layer under the "countries-label" layer.
map.discarding.addStyleLayerJson(
{ id: "satellite", type: "raster", source: "satellite" },
"countries-label",
);
main.zig
try map.discarding().addStyleSourceJson(
"satellite",
\\{
\\ "type": "raster",
\\ "tileSize": 256,
\\ "tiles": ["https://tiles.example.com/{z}/{x}/{y}.png"]
\\}
,
null,
);
// Draw the layer under the "countries-label" layer.
try map.discarding().addStyleLayerJson(
\\{
\\ "id": "satellite",
\\ "type": "raster",
\\ "source": "satellite"
\\}
,
"countries-label",
null,
);

For GeoJSON data in memory, add a GeoJSON source with mln_map_add_geo_json_sourceMapHandle.addGeoJsonSourceMapHandle.AddGeoJsonSourceAsyncMapHandle.AddGeoJSONSourcemaplibre_native_ffi_map_add_geo_json_source_asyncMapHandle.addGeoJsonSourceMapHandle.addGeoJsonSourceMapHandle.add_geo_json_sourceMapHandle::add_geo_json_sourceMapHandle.addGeoJsonSourceMapHandle.addGeoJsonSourceMap.addGeoJsonSource, then set its data with mln_map_set_geo_json_source_dataMapHandle.setGeoJsonSourceDataMapHandle.SetGeoJsonSourceDataAsyncMapHandle.SetGeoJSONSourceDatamaplibre_native_ffi_map_set_geo_json_source_data_asyncMapHandle.setGeoJsonSourceDataMapHandle.setGeoJsonSourceDataMapHandle.set_geo_json_source_dataMapHandle::set_geo_json_source_dataMapHandle.setGeoJsonSourceDataMapHandle.setGeoJsonSourceDataMap.setGeoJsonSourceData. The library processes the data in the background, so a large document doesn’t hold up your thread or the map. Set new data the same way to update the source. When updates come faster than the source can take them, it skips to the newest.

main.c
mln_map_add_geo_json_source(
map, mln_buffer_view_from_cstr("points"), NULL,
(mln_buffer_view){0}, NULL, NULL
);
mln_map_set_geo_json_source_data(
map, mln_buffer_view_from_cstr("points"),
mln_buffer_view_from_cstr(
"{\"type\":\"Point\",\"coordinates\":[-122.4,37.8]}"
),
NULL, NULL
);
main.dart
map.discarding.addGeoJsonSource('points');
map.discarding.setGeoJsonSourceData('points', {
'type': 'Point',
'coordinates': [-122.4, 37.8],
});
Program.cs
map.Discarding.AddGeoJsonSource("points");
map.Discarding.SetGeoJsonSourceData(
"points",
"""{"type":"Point","coordinates":[-122.4,37.8]}"""
);
main.go
err = m.Discarding().AddGeoJSONSource("points", nil, "")
if err != nil {
return err
}
err = m.Discarding().SetGeoJSONSourceData(
"points", `{"type": "Point", "coordinates": [-122.4, 37.8]}`,
)
if err != nil {
return err
}
main.vala
map.add_geo_json_source_async.begin ("points", null, null);
map.set_geo_json_source_data_async.begin (
"points", """{"type": "Point", "coordinates": [-122.4, 37.8]}"""
);
Main.java
map.getDiscarding().addGeoJsonSource("points", null, null);
map.getDiscarding()
.setGeoJsonSourceData(
"points",
"""
{"type": "Point", "coordinates": [-122.4, 37.8]}
""");
Main.kt
map.discarding.addGeoJsonSource("points")
map.discarding.setGeoJsonSourceData(
"points",
"""{"type": "Point", "coordinates": [-122.4, 37.8]}""",
)
main.py
map_handle.discarding.add_geo_json_source("points")
map_handle.discarding.set_geo_json_source_data(
"points", {"type": "Point", "coordinates": [-122.4, 37.8]}
)
main.rs
map.discarding().add_geo_json_source("points", None, None)?;
map.discarding().set_geo_json_source_data(
"points",
r#"{"type":"Point","coordinates":[-122.4,37.8]}"#,
)?;
main.swift
try map.discarding.addGeoJsonSource("points")
try map.discarding.setGeoJsonSourceData(
"points",
data: #"{"type": "Point", "coordinates": [-122.4, 37.8]}"#
)
main.ts
map.discarding.addGeoJsonSource("points");
map.discarding.setGeoJsonSourceData("points", {
type: "Point",
coordinates: [-122.4, 37.8],
});
main.zig
try map.discarding().addGeoJsonSource("points", null, null, null);
try map.discarding().setGeoJsonSourceData(
"points",
"{\"type\":\"Point\",\"coordinates\":[-122.4,37.8]}",
null,
);

To remove a source, first remove the layers that use it with mln_map_remove_style_layerMapHandle.removeStyleLayerMapHandle.RemoveStyleLayerAsyncMapHandle.RemoveStyleLayermaplibre_native_ffi_map_remove_style_layer_asyncMapHandle.removeStyleLayerMapHandle.removeStyleLayerMapHandle.remove_style_layerMapHandle::remove_style_layerMapHandle.removeStyleLayerMapHandle.removeStyleLayerMap.removeStyleLayer, then the source with mln_map_remove_style_sourceMapHandle.removeStyleSourceMapHandle.RemoveStyleSourceAsyncMapHandle.RemoveStyleSourcemaplibre_native_ffi_map_remove_style_source_asyncMapHandle.removeStyleSourceMapHandle.removeStyleSourceMapHandle.remove_style_sourceMapHandle::remove_style_sourceMapHandle.removeStyleSourceMapHandle.removeStyleSourceMap.removeStyleSource.

Set a layer property by its style-spec name and a JSON value with mln_map_set_style_layer_propertyMapHandle.setStyleLayerPropertyMapHandle.SetStyleLayerPropertyAsyncMapHandle.SetStyleLayerPropertymaplibre_native_ffi_map_set_style_layer_property_asyncMapHandle.setStyleLayerPropertyMapHandle.setStyleLayerPropertyMapHandle.set_style_layer_propertyMapHandle::set_style_layer_propertyMapHandle.setStyleLayerPropertyMapHandle.setStyleLayerPropertyMap.setStyleLayerProperty. This also covers visibility and filter. A value can be an expression.

Each change can show up in its own frame. To show several changes together, send them between mln_map_begin_command_groupMapHandle.beginCommandGroupMapHandle.BeginCommandGroupAsyncMapHandle.BeginCommandGroupmaplibre_native_ffi_map_begin_command_group_asyncMapHandle.beginCommandGroupMapHandle.beginCommandGroupMapHandle.begin_command_groupMapHandle::begin_command_groupMapHandle.beginCommandGroupMapHandle.beginCommandGroupMap.beginCommandGroup and mln_map_end_command_groupMapHandle.endCommandGroupMapHandle.EndCommandGroupAsyncMapHandle.EndCommandGroupmaplibre_native_ffi_map_end_command_group_asyncMapHandle.endCommandGroupMapHandle.endCommandGroupMapHandle.end_command_groupMapHandle::end_command_groupMapHandle.endCommandGroupMapHandle.endCommandGroupMap.endCommandGroup.

main.c
const mln_buffer_view satellite =
mln_buffer_view_from_cstr("satellite");
const mln_buffer_view boundaries =
mln_buffer_view_from_cstr("countries-boundary");
// Show both changes in the same frame.
mln_map_begin_command_group(map, NULL, NULL);
mln_map_set_style_layer_property(
map, satellite, mln_buffer_view_from_cstr("visibility"),
mln_buffer_view_from_cstr("\"none\""), NULL, NULL
);
mln_map_set_style_layer_property(
map, boundaries, mln_buffer_view_from_cstr("line-color"),
mln_buffer_view_from_cstr(
"[\"interpolate\",[\"linear\"],[\"zoom\"],5,\"#9cf\",12,\"#"
"06c\"]"
),
NULL, NULL
);
mln_map_end_command_group(map, NULL, NULL);
main.dart
// Show both changes in the same frame.
map.discarding.beginCommandGroup();
map.discarding.setStyleLayerProperty(
'satellite',
'visibility',
'none',
);
map.discarding.setStyleLayerProperty(
'countries-boundary',
'line-color',
[
'interpolate',
['linear'],
['zoom'],
5,
'#9cf',
12,
'#06c',
],
);
map.discarding.endCommandGroup();
Program.cs
// Show both changes in the same frame.
map.Discarding.BeginCommandGroup();
map.Discarding.SetStyleLayerProperty(
"satellite",
"visibility",
"\"none\""
);
map.Discarding.SetStyleLayerProperty(
"countries-boundary",
"line-color",
"""
["interpolate",["linear"],["zoom"],5,"#9cf",12,"#06c"]
"""
);
map.Discarding.EndCommandGroup();
main.go
// Show both changes in the same frame.
if err := m.Discarding().BeginCommandGroup(); err != nil {
return err
}
err = m.Discarding().SetStyleLayerProperty(
"satellite", "visibility", `"none"`,
)
if err != nil {
return err
}
err = m.Discarding().SetStyleLayerProperty(
"countries-boundary",
"line-color",
`["interpolate", ["linear"], ["zoom"], 5, "#9cf", 12, "#06c"]`,
)
if err != nil {
return err
}
if err := m.Discarding().EndCommandGroup(); err != nil {
return err
}
main.vala
// Show both changes in the same frame.
map.begin_command_group_async.begin ();
map.set_style_layer_property_async.begin (
"satellite", "visibility", "\"none\""
);
map.set_style_layer_property_async.begin (
"countries-boundary",
"line-color",
"""["interpolate", ["linear"], ["zoom"],
5, "#9cf", 12, "#06c"]"""
);
map.end_command_group_async.begin ();
Main.java
// Show both changes in the same frame.
map.getDiscarding().beginCommandGroup();
map.getDiscarding()
.setStyleLayerProperty("satellite", "visibility", "\"none\"");
map.getDiscarding()
.setStyleLayerProperty(
"countries-boundary",
"line-color",
"""
["interpolate", ["linear"], ["zoom"], 5, "#9cf", 12, "#06c"]
""");
map.getDiscarding().endCommandGroup();
Main.kt
// Show both changes in the same frame.
map.discarding.beginCommandGroup()
map.discarding.setStyleLayerProperty(
"satellite",
"visibility",
"\"none\"",
)
map.discarding.setStyleLayerProperty(
"countries-boundary",
"line-color",
"""
["interpolate", ["linear"], ["zoom"], 5, "#9cf", 12, "#06c"]
""",
)
map.discarding.endCommandGroup()
main.py
# Show both changes in the same frame.
map_handle.discarding.begin_command_group()
map_handle.discarding.set_style_layer_property(
"satellite", "visibility", "none"
)
map_handle.discarding.set_style_layer_property(
"countries-boundary",
"line-color",
["interpolate", ["linear"], ["zoom"], 5, "#9cf", 12, "#06c"],
)
map_handle.discarding.end_command_group()
main.rs
// Show both changes in the same frame.
map.discarding().begin_command_group()?;
map.discarding().set_style_layer_property(
"satellite",
"visibility",
r#""none""#,
)?;
map.discarding().set_style_layer_property(
"countries-boundary",
"line-color",
r##"["interpolate",["linear"],["zoom"],5,"#9cf",12,"#06c"]"##,
)?;
map.discarding().end_command_group()?;
main.swift
// Show both changes in the same frame.
try map.discarding.beginCommandGroup()
try map.discarding.setStyleLayerProperty(
"satellite",
propertyName: "visibility",
value: #""none""#
)
try map.discarding.setStyleLayerProperty(
"countries-boundary",
propertyName: "line-color",
value: """
["interpolate", ["linear"], ["zoom"], 5, "#9cf", 12, "#06c"]
"""
)
try map.discarding.endCommandGroup()
main.ts
// Show both changes in the same frame.
map.discarding.beginCommandGroup();
map.discarding.setStyleLayerProperty(
"satellite",
"visibility",
"none",
);
map.discarding.setStyleLayerProperty(
"countries-boundary",
"line-color",
["interpolate", ["linear"], ["zoom"], 5, "#9cf", 12, "#06c"],
);
map.discarding.endCommandGroup();
main.zig
// Show both changes in the same frame.
try map.discarding().beginCommandGroup(null);
try map.discarding().setStyleLayerProperty(
"satellite",
"visibility",
"\"none\"",
null,
);
try map.discarding().setStyleLayerProperty(
"countries-boundary",
"line-color",
\\["interpolate", ["linear"], ["zoom"], 5, "#9cf", 12, "#06c"]
,
null,
);
try map.discarding().endCommandGroup(null);

Add an image to the style by its ID and premultiplied RGBA pixels with mln_map_set_style_imageMapHandle.setStyleImageMapHandle.SetStyleImageAsyncMapHandle.SetStyleImagemaplibre_native_ffi_map_set_style_image_asyncMapHandle.setStyleImageMapHandle.setStyleImageMapHandle.set_style_imageMapHandle::set_style_imageMapHandle.setStyleImageMapHandle.setStyleImageMap.setStyleImage. Symbol layers then draw it by that ID in icon-image.

main.c
const mln_premultiplied_rgba8_image image = {
.size = sizeof(mln_premultiplied_rgba8_image),
.width = 16,
.height = 16,
.stride = 16 * 4,
.pixels = pixels,
.byte_length = 16 * 16 * 4,
};
mln_map_set_style_image(
map, mln_buffer_view_from_cstr("marker"), &image, NULL, NULL, NULL
);
mln_map_add_style_layer_json(
map,
mln_buffer_view_from_cstr(
"{\"id\":\"markers\",\"type\":\"symbol\",\"source\":\"points\","
"\"layout\":{\"icon-image\":\"marker\"}}"
),
(mln_buffer_view){0}, NULL, NULL
);
main.dart
map.discarding.setStyleImage(
'marker',
PremultipliedRgba8Image(
width: 16,
height: 16,
stride: 16 * 4,
pixels: pixels,
),
);
// An empty layer ID draws the layer on top.
map.discarding.addStyleLayerJson({
'id': 'markers',
'type': 'symbol',
'source': 'points',
'layout': {'icon-image': 'marker'},
});
Program.cs
map.Discarding.SetStyleImage(
"marker",
new PremultipliedRgba8Image(16, 16, 16 * 4, pixels)
);
// An empty layer ID draws the layer on top.
map.Discarding.AddStyleLayerJson(
"""
{
"id": "markers",
"type": "symbol",
"source": "points",
"layout": {"icon-image": "marker"}
}
"""
);
main.go
image := maplibre.PremultipliedRgba8Image{
Width: 16, Height: 16, Stride: 16 * 4, Pixels: pixels,
}
err = m.Discarding().SetStyleImage("marker", image, nil)
if err != nil {
return err
}
// An empty layer ID draws the layer on top.
err = m.Discarding().AddStyleLayerJSON(`{
"id": "markers",
"type": "symbol",
"source": "points",
"layout": {"icon-image": "marker"}
}`, "")
if err != nil {
return err
}
main.vala
var marker = new PremultipliedRgba8Image () {
width = 16,
height = 16,
stride = 16 * 4,
pixels = pixels,
};
map.set_style_image_async.begin ("marker", marker, null);
// An empty layer ID draws the layer on top.
map.add_style_layer_json_async.begin (
"""{"id": "markers", "type": "symbol", "source": "points",
"layout": {"icon-image": "marker"}}""",
null
);
Main.java
map.getDiscarding()
.setStyleImage(
"marker",
PremultipliedRgba8Image.of(16, 16, 16 * 4, pixels),
null);
// A null layer ID draws the layer on top.
map.getDiscarding()
.addStyleLayerJson(
"""
{
"id": "markers",
"type": "symbol",
"source": "points",
"layout": {"icon-image": "marker"}
}
""",
null);
Main.kt
map.discarding.setStyleImage(
"marker",
PremultipliedRgba8Image(
width = 16u,
height = 16u,
stride = 16u * 4u,
pixels = pixels,
),
)
// An empty layer ID draws the layer on top.
map.discarding.addStyleLayerJson(
"""
{
"id": "markers",
"type": "symbol",
"source": "points",
"layout": {"icon-image": "marker"}
}
""",
)
main.py
map_handle.discarding.set_style_image(
"marker",
mln.PremultipliedRgba8Image(
width=16, height=16, stride=16 * 4, pixels=pixels
),
)
# An empty layer ID draws the layer on top.
map_handle.discarding.add_style_layer_json(
{
"id": "markers",
"type": "symbol",
"source": "points",
"layout": {"icon-image": "marker"},
}
)
main.rs
let image = PremultipliedRgba8Image::new(16, 16, 16 * 4, pixels);
map.discarding().set_style_image("marker", &image, None)?;
// An empty layer ID draws the layer on top.
map.discarding().add_style_layer_json(
r#"{
"id": "markers",
"type": "symbol",
"source": "points",
"layout": {"icon-image": "marker"}
}"#,
None,
)?;
main.swift
try map.discarding.setStyleImage(
"marker",
image: PremultipliedRgba8Image(
width: 16,
height: 16,
stride: 16 * 4,
pixels: pixels
)
)
// An empty layer ID draws the layer on top.
try map.discarding.addStyleLayerJson("""
{
"id": "markers",
"type": "symbol",
"source": "points",
"layout": {"icon-image": "marker"}
}
""")
main.ts
map.discarding.setStyleImage("marker", {
width: 16,
height: 16,
stride: 16 * 4,
pixels,
});
// An empty layer ID draws the layer on top.
map.discarding.addStyleLayerJson({
id: "markers",
type: "symbol",
source: "points",
layout: { "icon-image": "marker" },
});
main.zig
try map.discarding().setStyleImage(allocator, "marker", .{
.width = 16,
.height = 16,
.stride = 16 * 4,
.pixels = pixels,
}, null, null);
// An empty layer ID draws the layer on top.
try map.discarding().addStyleLayerJson(
\\{
\\ "id": "markers",
\\ "type": "symbol",
\\ "source": "points",
\\ "layout": {"icon-image": "marker"}
\\}
,
null,
null,
);

When a layer asks for an image the style doesn’t have, the map sends a missing-image event, whose mln_runtime_event_style_image.image_idRuntimeEventStyleImage.imageIdRuntimeEventStyleImage.ImageIdRuntimeEventStyleImage.ImageIDmaplibre_native_ffi_runtime_event_style_image_get_image_idRuntimeEventStyleImage.imageIdRuntimeEventStyleImage.imageIdRuntimeEventStyleImage.image_idRuntimeEventStyleImage::image_idRuntimeEventStyleImage.imageIdRuntimeEventStyleImage.imageIdRuntimeEventStyleImage.image_id names the image. Set the image when the event arrives.

main.c
static void on_events(
void* user_data, const mln_event_batch_view* batch
) {
(void)user_data;
for (size_t i = 0; i < batch->event_count; i++) {
const mln_runtime_event* event = MLN_STRIDED_AT(
mln_runtime_event, batch->events, batch->event_size, i
);
if (
event->source != map ||
event->type != MLN_RUNTIME_EVENT_MAP_STYLE_IMAGE_MISSING
) {
continue;
}
const mln_buffer_view id = event->payload.style_image.image_id;
const mln_premultiplied_rgba8_image image = draw_image(id);
mln_map_set_style_image(map, id, &image, NULL, NULL, NULL);
}
}
main.dart
// The handler runs in this isolate. The binding queues each call.
runtime.discarding.setEventHandler(
RuntimeEventHandler(
callback: (events) {
for (final event in events) {
if (event.source != map.id) continue;
if (event.payload case RuntimeEventPayloadStyleImage(
:final value,
)) {
map.discarding.setStyleImage(
value.imageId,
drawImage(value.imageId),
);
}
}
},
),
);
Program.cs
// Runs on a library thread.
runtime.Discarding.SetEventHandler(
new RuntimeEventHandler(events =>
{
foreach (var e in events)
{
if (
e.Source != map.Id
|| e.Payload
is not RuntimeEvent.PayloadValue.StyleImage missing
)
continue;
var id = missing.Value.ImageId;
map.Discarding.SetStyleImage(id, DrawImage(id));
}
})
);
main.go
// Runs on a library thread.
handleEvents := func(events []maplibre.RuntimeEvent) {
for _, event := range events {
if event.Source != m.ID() {
continue
}
missing, ok := event.Payload.(maplibre.RuntimeEventPayloadStyleImageVariant)
if !ok {
continue
}
id := missing.Value.ImageID
err := m.Discarding().SetStyleImage(id, drawImage(id), nil)
if err != nil {
log.Print(err)
}
}
}
err := runtime.Discarding().SetEventHandler(
maplibre.RuntimeEventHandler{Callback: handleEvents},
)
if err != nil {
return err
}
main.vala
var handler = new RuntimeEventHandler ();
// Runs on the main context of the thread that set it.
handler.set_callback ((events) => {
foreach (var event in events) {
var payload = event.get_payload ();
if (!map.is_event_source (event)
|| !payload.is_style_image ()) {
continue;
}
var image_id = payload.get_style_image ().get_image_id ();
map.set_style_image_async.begin (
image_id, draw_image (image_id), null
);
}
});
runtime.set_event_handler_async.begin (handler);
Main.java
// Runs on a library thread.
runtime
.getDiscarding()
.setEventHandler(
new RuntimeEventHandler(
events -> {
for (RuntimeEvent event : events) {
if (event.getSource() == map.getId()
&& event.getPayload()
instanceof
RuntimeEventPayload.StyleImage missing) {
String id = missing.getValue().getImageId();
map.getDiscarding()
.setStyleImage(id, drawImage(id), null);
}
}
}));
Main.kt
runtime.discarding.setEventHandler(
RuntimeEventHandler { events ->
for (event in events) {
if (event.source != map.id) continue
val missing =
event.payload as? RuntimeEventPayload.StyleImage ?: continue
val id = missing.value.imageId
map.discarding.setStyleImage(id, drawImage(id))
}
},
)
main.py
# Runs on the binding's dispatcher thread.
def on_events(events: tuple[mln.RuntimeEvent, ...]) -> None:
for event in events:
if event.source != map_handle.id:
continue
match event.payload:
case mln.RuntimeEventStyleImageVariant(value=missing):
map_handle.discarding.set_style_image(
missing.image_id, draw_image(missing.image_id)
)
runtime.discarding.set_event_handler(
mln.RuntimeEventHandler(on_events)
)
main.rs
// A strong reference would keep the map from dropping.
let map = Arc::downgrade(map);
// The handler runs on a library thread.
runtime.discarding().set_event_handler(
RuntimeEventHandler::new(move |events| {
let Some(map) = map.upgrade() else {
return;
};
for event in events {
if event.source != map.id() {
continue;
}
let RuntimeEventPayload::StyleImage(missing) =
&event.payload
else {
continue;
};
let image = draw_image(&missing.image_id);
let _ = map.discarding().set_style_image(
&missing.image_id,
&image,
None,
);
}
}),
)?;
main.swift
// Runs on a library thread.
try runtime.discarding
.setEventHandler(RuntimeEventHandler { events in
for event in events where event.source == map.id {
guard case let .styleImage(missing) = event.payload
else { continue }
try? map.discarding.setStyleImage(
missing.imageId,
image: drawImage(missing.imageId)
)
}
})
main.ts
// The handler runs on this thread. The binding queues each call.
runtime.discarding.setEventHandler({
callback: (events) => {
for (const { source, payload } of events) {
if (source !== map.id || payload.kind !== "styleImage")
continue;
const { imageId } = payload.value;
map.discarding.setStyleImage(imageId, drawImage(imageId));
}
},
});
main.zig
// Runs on a library thread.
fn onEvents(
_: ?*anyopaque,
events: []const maplibre.RuntimeEvent,
) anyerror!void {
for (events) |event| {
if (event.source != map.id()) continue;
const missing = switch (event.payload) {
.style_image => |value| value,
else => continue,
};
try map.discarding().setStyleImage(
allocator,
missing.image_id,
drawImage(missing.image_id),
null,
null,
);
}
}