Skip to content

Offline

The library caches resources, so maps load faster and keep working without a network. The ambient cache holds what maps loaded recently, and offline regions hold whole areas that you download on purpose.

To keep the cache between runs, set a cache file path in mln_runtime_optionsRuntimeOptionsRuntimeOptionsRuntimeOptionsMaplibreNativeFfiRuntimeOptionsRuntimeOptionsRuntimeOptionsRuntimeOptionsRuntimeOptionsRuntimeOptionsRuntimeOptionsRuntimeOptions when you create the runtime. Without one, the cache lives in memory, and offline regions are lost when the runtime is released.

main.c
mln_runtime_options runtime_options = {
.size = sizeof(mln_runtime_options),
.cache_path = mln_buffer_view_from_cstr("maplibre-cache.db"),
};
mln_runtime runtime = MLN_HANDLE_NULL;
mln_runtime_create(&runtime_options, &runtime, NULL);
main.dart
final runtime = RuntimeHandle.create(
options: const RuntimeOptions(cachePath: 'maplibre-cache.db'),
);
Program.cs
var runtime = RuntimeHandle.Create(
new RuntimeOptions { CachePath = "maplibre-cache.db" }
);
main.go
runtime, err := maplibre.NewRuntime(&maplibre.RuntimeOptions{
CachePath: "maplibre-cache.db",
})
if err != nil {
return err
}
main.vala
var options = new RuntimeOptions () {
cache_path = "maplibre-cache.db",
};
var runtime = new Runtime.create (options);
Main.java
RuntimeHandle runtime =
RuntimeHandle.create(
RuntimeOptions.of()
.withCachePath(
cacheDirectory
.resolve("maplibre-cache.db")
.toString()))
.join();
Main.kt
val runtime =
RuntimeHandle.create(
RuntimeOptions(cachePath = "maplibre-cache.db"),
)
.await()
main.py
runtime = mln.RuntimeHandle.create(
mln.RuntimeOptions(cache_path="maplibre-cache.db")
)
main.rs
let runtime = RuntimeHandle::create(Some(&RuntimeOptions {
cache_path: "maplibre-cache.db",
..Default::default()
}))?;
main.swift
let runtime = try RuntimeHandle.create(
options: RuntimeOptions(cachePath: "maplibre-cache.db")
)
main.ts
const runtime = await RuntimeHandle.create({
cachePath: "maplibre-cache.db",
});
main.zig
var runtime = try maplibre.Runtime.create(
.{ .cache_path = "maplibre-cache.db" },
null,
);

To bound the ambient cache’s disk use, set its maximum size with mln_runtime_set_maximum_ambient_cache_sizeRuntimeHandle.setMaximumAmbientCacheSizeRuntimeHandle.SetMaximumAmbientCacheSizeAsyncRuntimeHandle.SetMaximumAmbientCacheSizemaplibre_native_ffi_runtime_set_maximum_ambient_cache_size_asyncRuntimeHandle.setMaximumAmbientCacheSizeRuntimeHandle.setMaximumAmbientCacheSizeRuntimeHandle.set_maximum_ambient_cache_sizeRuntimeHandle::set_maximum_ambient_cache_sizeRuntimeHandle.setMaximumAmbientCacheSizeRuntimeHandle.setMaximumAmbientCacheSizeRuntime.setMaximumAmbientCacheSize. Offline regions don’t count toward it.

To free its space, clear it with mln_runtime_run_cache_database_operationRuntimeHandle.runCacheDatabaseOperationRuntimeHandle.RunCacheDatabaseOperationAsyncRuntimeHandle.RunCacheDatabaseOperationmaplibre_native_ffi_runtime_run_cache_database_operation_asyncRuntimeHandle.runCacheDatabaseOperationRuntimeHandle.runCacheDatabaseOperationRuntimeHandle.run_cache_database_operationRuntimeHandle::run_cache_database_operationRuntimeHandle.runCacheDatabaseOperationRuntimeHandle.runCacheDatabaseOperationRuntime.runCacheDatabaseOperation. When your tiles change, invalidate it instead. It keeps the data, but maps check each resource with the server before they use it.

main.c
// 50 MiB
mln_runtime_set_maximum_ambient_cache_size(
runtime, 50ULL * 1024 * 1024, &done, NULL
);
mln_runtime_run_cache_database_operation(
runtime, MLN_CACHE_DATABASE_OPERATION_CLEAR, &done, NULL
);
main.dart
// 50 MiB
await runtime.setMaximumAmbientCacheSize(50 * 1024 * 1024);
await runtime.runCacheDatabaseOperation(
CacheDatabaseOperation.clear,
);
Program.cs
// 50 MiB
await runtime.SetMaximumAmbientCacheSizeAsync(
50 * 1024 * 1024
);
await runtime.RunCacheDatabaseOperationAsync(
CacheDatabaseOperation.Clear
);
main.go
// 50 MiB
resized, err := runtime.SetMaximumAmbientCacheSize(50 * 1024 * 1024)
if err != nil {
return err
}
if _, err := resized.Await(ctx); err != nil {
return err
}
cleared, err := runtime.RunCacheDatabaseOperation(
maplibre.CacheDatabaseOperationClear,
)
if err != nil {
return err
}
if _, err := cleared.Await(ctx); err != nil {
return err
}
main.vala
// 50 MiB
yield runtime.set_maximum_ambient_cache_size_async (
50 * 1024 * 1024
);
yield runtime.run_cache_database_operation_async (
CacheDatabaseOperation.CLEAR
);
Main.java
// 50 MiB
runtime.setMaximumAmbientCacheSize(50L * 1024 * 1024).join();
runtime
.runCacheDatabaseOperation(CacheDatabaseOperation.CLEAR)
.join();
Main.kt
// 50 MiB
runtime.setMaximumAmbientCacheSize(50uL * 1024uL * 1024uL).await()
runtime
.runCacheDatabaseOperation(CacheDatabaseOperation.CLEAR)
.await()
main.py
# 50 MiB
runtime.set_maximum_ambient_cache_size(50 * 1024 * 1024).result()
runtime.run_cache_database_operation(
mln.CacheDatabaseOperation.CLEAR
).result()
main.rs
// 50 MiB
runtime
.set_maximum_ambient_cache_size(50 * 1024 * 1024)?
.await?;
runtime
.run_cache_database_operation(CacheDatabaseOperation::Clear)?
.await?;
main.swift
// 50 MiB
try await runtime.setMaximumAmbientCacheSize(50 * 1024 * 1024)
try await runtime.runCacheDatabaseOperation(.clear)
main.ts
// 50 MiB
await runtime.setMaximumAmbientCacheSize(50n * 1024n * 1024n);
await runtime.runCacheDatabaseOperation(
CacheDatabaseOperation.clear,
);
main.zig
// 50 MiB
const resized = try runtime.setMaximumAmbientCacheSize(
50 * 1024 * 1024,
null,
);
try resized.await(null);
try (try runtime.runCacheDatabaseOperation(.clear, null)).await(null);

Define the region with mln_offline_tile_pyramid_region_definitionOfflineTilePyramidRegionDefinitionOfflineTilePyramidRegionDefinitionOfflineTilePyramidRegionDefinitionMaplibreNativeFfiOfflineTilePyramidRegionDefinitionOfflineTilePyramidRegionDefinitionOfflineTilePyramidRegionDefinitionOfflineTilePyramidRegionDefinitionOfflineTilePyramidRegionDefinitionOfflineTilePyramidRegionDefinitionOfflineTilePyramidRegionDefinitionOfflineTilePyramidRegionDefinition: the style URL, the bounds, the zoom range, and the pixel ratio. To follow a shape instead of a box, use mln_offline_geometry_region_definitionOfflineGeometryRegionDefinitionOfflineGeometryRegionDefinitionOfflineGeometryRegionDefinitionMaplibreNativeFfiOfflineGeometryRegionDefinitionOfflineGeometryRegionDefinitionOfflineGeometryRegionDefinitionOfflineGeometryRegionDefinitionOfflineGeometryRegionDefinitionOfflineGeometryRegionDefinitionOfflineGeometryRegionDefinitionOfflineGeometryRegionDefinition with a GeoJSON geometry. Keep the zoom range tight, since each zoom level has four times the tiles of the one before.

You can store any bytes with the region as metadata, such as its name.

Create the region with mln_runtime_create_offline_regionRuntimeHandle.createOfflineRegionRuntimeHandle.CreateOfflineRegionAsyncRuntimeHandle.CreateOfflineRegionmaplibre_native_ffi_runtime_create_offline_region_asyncRuntimeHandle.createOfflineRegionRuntimeHandle.createOfflineRegionRuntimeHandle.create_offline_regionRuntimeHandle::create_offline_regionRuntimeHandle.createOfflineRegionRuntimeHandle.createOfflineRegionRuntime.createOfflineRegion, then start its download with mln_runtime_set_offline_region_download_stateRuntimeHandle.setOfflineRegionDownloadStateRuntimeHandle.SetOfflineRegionDownloadStateAsyncRuntimeHandle.SetOfflineRegionDownloadStatemaplibre_native_ffi_runtime_set_offline_region_download_state_asyncRuntimeHandle.setOfflineRegionDownloadStateRuntimeHandle.setOfflineRegionDownloadStateRuntimeHandle.set_offline_region_download_stateRuntimeHandle::set_offline_region_download_stateRuntimeHandle.setOfflineRegionDownloadStateRuntimeHandle.setOfflineRegionDownloadStateRuntime.setOfflineRegionDownloadState. Maps on the runtime use the region’s resources whenever they load the same style URL.

main.c
static void on_region_created(
void* user_data, const mln_completion_result* result
) {
if (result->status == MLN_STATUS_OK) {
const mln_offline_region_info* region = result->value;
*(mln_offline_region_id*)user_data = region->id;
}
on_done(NULL, result);
}
const mln_offline_region_definition definition = {
.size = sizeof(mln_offline_region_definition),
.type = MLN_OFFLINE_REGION_DEFINITION_TILE_PYRAMID,
.data.tile_pyramid = {
.style_url = mln_buffer_view_from_cstr(
"https://demotiles.maplibre.org/style.json"
),
.bounds =
{
.southwest = {.latitude = 47.5, .longitude = -122.5},
.northeast = {.latitude = 47.8, .longitude = -122.2},
},
.min_zoom = 0.0,
.max_zoom = 6.0,
.pixel_ratio = 1.0F,
},
};
static const uint8_t metadata[] = "Seattle";
mln_offline_region_id region_id = 0;
const mln_completion created = {
.size = sizeof(mln_completion),
.callback = on_region_created,
.user_data = &region_id,
};
mln_runtime_create_offline_region(
runtime, &definition, metadata, sizeof(metadata) - 1, &created,
NULL
);
mln_runtime_set_offline_region_download_state(
runtime, region_id, MLN_OFFLINE_REGION_DOWNLOAD_ACTIVE, &done,
NULL
);
main.dart
final region = await runtime.createOfflineRegion(
const OfflineRegionDefinition.tilePyramid(
OfflineTilePyramidRegionDefinition(
styleUrl: 'https://demotiles.maplibre.org/style.json',
bounds: LatLngBounds(
southwest: LatLng(latitude: 47.5, longitude: -122.5),
northeast: LatLng(latitude: 47.8, longitude: -122.2),
),
minZoom: 0,
maxZoom: 6,
pixelRatio: 1,
),
),
utf8.encode('Seattle'),
);
await runtime.setOfflineRegionDownloadState(
region.id,
OfflineRegionDownloadState.active,
);
Program.cs
var definition = new OfflineRegionDefinition.TilePyramid(
new OfflineTilePyramidRegionDefinition
{
StyleUrl =
"https://demotiles.maplibre.org/style.json",
Bounds = new LatLngBounds(
new LatLng(47.5, -122.5),
new LatLng(47.8, -122.2)
),
MinZoom = 0,
MaxZoom = 6,
PixelRatio = 1,
}
);
var region = await runtime.CreateOfflineRegionAsync(
definition,
"Seattle"u8
);
await runtime.SetOfflineRegionDownloadStateAsync(
region.Id,
OfflineRegionDownloadState.Active
);
main.go
definition := maplibre.OfflineRegionDefinitionTilePyramidVariant{
Value: maplibre.OfflineTilePyramidRegionDefinition{
StyleURL: "https://demotiles.maplibre.org/style.json",
Bounds: maplibre.LatLngBounds{
Southwest: maplibre.LatLng{Latitude: 47.5, Longitude: -122.5},
Northeast: maplibre.LatLng{Latitude: 47.8, Longitude: -122.2},
},
MinZoom: 0,
MaxZoom: 6,
PixelRatio: 1,
},
}
created, err := runtime.CreateOfflineRegion(
definition, []byte("Seattle"),
)
if err != nil {
return err
}
region, err := created.Await(ctx)
if err != nil {
return err
}
activated, err := runtime.SetOfflineRegionDownloadState(
region.ID, maplibre.OfflineRegionDownloadStateActive,
)
if err != nil {
return err
}
if _, err := activated.Await(ctx); err != nil {
return err
}
main.vala
var pyramid = new OfflineTilePyramidRegionDefinition () {
style_url = "https://demotiles.maplibre.org/style.json",
bounds = {
{ latitude: 47.5, longitude: -122.5 },
{ latitude: 47.8, longitude: -122.2 },
},
min_zoom = 0,
max_zoom = 6,
pixel_ratio = 1,
};
var region = yield runtime.create_offline_region_async (
new OfflineRegionDefinition.tile_pyramid (pyramid),
new GLib.Bytes ("Seattle".data)
);
yield runtime.set_offline_region_download_state_async (
region.get_id (), OfflineRegionDownloadState.ACTIVE
);
Main.java
OfflineRegionInfo region =
runtime
.createOfflineRegion(
new OfflineRegionDefinition.TilePyramid(
OfflineTilePyramidRegionDefinition.of()
.withStyleUrl(
"https://demotiles.maplibre.org/style.json")
.withBounds(
new LatLngBounds(
new LatLng(47.5, -122.5),
new LatLng(47.8, -122.2)))
.withMinZoom(0.0)
.withMaxZoom(6.0)
.withPixelRatio(1f)),
"Seattle".getBytes(StandardCharsets.UTF_8))
.join();
runtime
.setOfflineRegionDownloadState(
region.getId(), OfflineRegionDownloadState.ACTIVE)
.join();
Main.kt
val region =
runtime
.createOfflineRegion(
OfflineRegionDefinition.TilePyramid(
OfflineTilePyramidRegionDefinition(
styleUrl = "https://demotiles.maplibre.org/style.json",
bounds =
LatLngBounds(
southwest =
LatLng(latitude = 47.5, longitude = -122.5),
northeast =
LatLng(latitude = 47.8, longitude = -122.2),
),
minZoom = 0.0,
maxZoom = 6.0,
pixelRatio = 1f,
),
),
"Seattle".encodeToByteArray(),
)
.await()
runtime
.setOfflineRegionDownloadState(
region.id,
OfflineRegionDownloadState.ACTIVE,
)
.await()
main.py
region = runtime.create_offline_region(
mln.OfflineRegionDefinitionTilePyramidVariant(
mln.OfflineTilePyramidRegionDefinition(
style_url="https://demotiles.maplibre.org/style.json",
bounds=mln.LatLngBounds(
southwest=mln.LatLng(
latitude=47.5, longitude=-122.5
),
northeast=mln.LatLng(
latitude=47.8, longitude=-122.2
),
),
min_zoom=0,
max_zoom=6,
pixel_ratio=1,
)
),
b"Seattle",
).result()
runtime.set_offline_region_download_state(
region.id, mln.OfflineRegionDownloadState.ACTIVE
).result()
main.rs
let definition = OfflineRegionDefinition::TilePyramid(
OfflineTilePyramidRegionDefinition {
style_url: "https://demotiles.maplibre.org/style.json"
.to_owned(),
bounds: LatLngBounds {
southwest: LatLng::new(47.5, -122.5),
northeast: LatLng::new(47.8, -122.2),
},
min_zoom: 0.0,
max_zoom: 6.0,
pixel_ratio: 1.0,
..Default::default()
},
);
let region = runtime
.create_offline_region(&definition, b"Seattle")?
.await?;
runtime
.set_offline_region_download_state(
region.id,
OfflineRegionDownloadState::Active,
)?
.await?;
main.swift
let region = try await runtime.createOfflineRegion(
definition: .tilePyramid(OfflineTilePyramidRegionDefinition(
styleUrl: "https://demotiles.maplibre.org/style.json",
bounds: LatLngBounds(
southwest: LatLng(latitude: 47.5, longitude: -122.5),
northeast: LatLng(latitude: 47.8, longitude: -122.2)
),
minZoom: 0,
maxZoom: 6,
pixelRatio: 1
)),
metadata: Data("Seattle".utf8)
)
try await runtime.setOfflineRegionDownloadState(
region.id,
state: .active
)
main.ts
const region = await runtime.createOfflineRegion(
{
kind: "tilePyramid",
value: {
styleUrl: "https://demotiles.maplibre.org/style.json",
bounds: {
southwest: { latitude: 47.5, longitude: -122.5 },
northeast: { latitude: 47.8, longitude: -122.2 },
},
minZoom: 0,
maxZoom: 6,
pixelRatio: 1,
includeIdeographs: false,
},
},
new TextEncoder().encode("Seattle"),
);
await runtime.setOfflineRegionDownloadState(
region.id,
OfflineRegionDownloadState.active,
);
main.zig
const bounds = maplibre.LatLngBounds{
.southwest = .{ .latitude = 47.5, .longitude = -122.5 },
.northeast = .{ .latitude = 47.8, .longitude = -122.2 },
};
const created = try runtime.createOfflineRegion(allocator, .{
.tile_pyramid = .{
.style_url = "https://demotiles.maplibre.org/style.json",
.bounds = bounds,
.min_zoom = 0,
.max_zoom = 6,
.pixel_ratio = 1,
},
}, "Seattle", null);
var region = try created.await(null);
defer region.deinit();
const activated = try runtime.setOfflineRegionDownloadState(
region.value.id,
.active,
null,
);
try activated.await(null);

To track a download, observe its region with mln_runtime_set_offline_region_observedRuntimeHandle.setOfflineRegionObservedRuntimeHandle.SetOfflineRegionObservedAsyncRuntimeHandle.SetOfflineRegionObservedmaplibre_native_ffi_runtime_set_offline_region_observed_asyncRuntimeHandle.setOfflineRegionObservedRuntimeHandle.setOfflineRegionObservedRuntimeHandle.set_offline_region_observedRuntimeHandle::set_offline_region_observedRuntimeHandle.setOfflineRegionObservedRuntimeHandle.setOfflineRegionObservedRuntime.setOfflineRegionObserved, and its progress arrives as events in your event handler.

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 += 1) {
const mln_runtime_event* event = MLN_STRIDED_AT(
mln_runtime_event, batch->events, batch->event_size, i
);
if (
event->type == MLN_RUNTIME_EVENT_OFFLINE_REGION_STATUS_CHANGED
) {
const mln_runtime_event_offline_region_status* payload =
&event->payload.offline_region_status;
record_progress(
payload->region_id, payload->status.completed_resource_count,
payload->status.required_resource_count,
payload->status.complete
);
} else if (
event->type == MLN_RUNTIME_EVENT_OFFLINE_REGION_RESPONSE_ERROR
) {
record_error(
event->payload.offline_region_response_error.region_id,
event->message
);
}
}
}
const mln_runtime_event_handler handler = {
.size = sizeof(mln_runtime_event_handler), .callback = on_events
};
mln_runtime_set_event_handler(runtime, &handler, NULL, NULL);
mln_runtime_set_offline_region_observed(
runtime, region_id, MLN_RUNTIME_EVENT_FLAG_ALL_RUNTIME_EVENTS,
&done, NULL
);
main.dart
runtime.discarding.setEventHandler(
RuntimeEventHandler(
callback: (events) {
for (final RuntimeEvent(:payload, :message) in events) {
switch (payload) {
case RuntimeEventPayloadOfflineRegionStatus(:final value):
final RuntimeEventOfflineRegionStatus(
:regionId,
:status,
) = value;
recordProgress(
regionId,
status.completedResourceCount,
status.requiredResourceCount,
status.complete,
);
case RuntimeEventPayloadOfflineRegionResponseError(
:final value,
):
recordError(value.regionId, message);
default:
}
}
},
),
);
await runtime.setOfflineRegionObserved(
region.id,
RuntimeEventFlag.allRuntimeEvents,
);
Program.cs
// Runs on a library thread.
private static void OnEvents(RuntimeEvent[] events)
{
foreach (var e in events)
{
switch (e.Payload)
{
case RuntimeEvent.PayloadValue.OfflineRegionStatus
{
Value: var progress
}:
RecordProgress(
progress.RegionId,
progress.Status.CompletedResourceCount,
progress.Status.RequiredResourceCount,
progress.Status.Complete
);
break;
case RuntimeEvent.PayloadValue.OfflineRegionResponseError
{
Value: var error
}:
RecordError(error.RegionId, e.Message);
break;
}
}
}
runtime.Discarding.SetEventHandler(
new RuntimeEventHandler(OnEvents)
);
await runtime.SetOfflineRegionObservedAsync(
region.Id,
RuntimeEventFlag.AllRuntimeEvents
);
main.go
handleEvents := func(events []maplibre.RuntimeEvent) {
for _, event := range events {
switch payload := event.Payload.(type) {
case maplibre.RuntimeEventPayloadOfflineRegionStatusVariant:
status := payload.Value.Status
recordProgress(
payload.Value.RegionID,
status.CompletedResourceCount,
status.RequiredResourceCount,
status.Complete,
)
case maplibre.RuntimeEventPayloadOfflineRegionResponseErrorVariant:
recordError(payload.Value.RegionID, event.Message)
}
}
}
err = runtime.Discarding().SetEventHandler(
maplibre.RuntimeEventHandler{Callback: handleEvents},
)
if err != nil {
return err
}
observed, err := runtime.SetOfflineRegionObserved(region.ID, maplibre.RuntimeEventFlagAllRuntimeEvents)
if err != nil {
return err
}
if _, err := observed.Await(ctx); err != nil {
return err
}
main.vala
var handler = new RuntimeEventHandler ();
handler.set_callback ((events) => {
foreach (var event in events) {
var payload = event.get_payload ();
if (payload.is_offline_region_status ()) {
var update = payload.get_offline_region_status ();
record_progress (
update.region_id,
update.status.completed_resource_count,
update.status.required_resource_count,
update.status.complete
);
} else if (payload.is_offline_region_response_error ()) {
var error =
payload.get_offline_region_response_error ();
record_error (error.region_id, event.get_message ());
}
}
});
runtime.set_event_handler_async.begin (handler);
yield runtime.set_offline_region_observed_async (
region.get_id (), RuntimeEventFlag.ALL_RUNTIME_EVENTS
);
Main.java
private void handleEvents(List<RuntimeEvent> events) {
for (RuntimeEvent event : events) {
RuntimeEventPayload payload = event.getPayload();
if (payload
instanceof
RuntimeEventPayload.OfflineRegionStatus changed) {
OfflineRegionStatus status = changed.getValue().getStatus();
recordProgress(
changed.getValue().getRegionId(),
status.getCompletedResourceCount(),
status.getRequiredResourceCount(),
status.getComplete());
} else if (payload
instanceof
RuntimeEventPayload.OfflineRegionResponseError error) {
recordError(
error.getValue().getRegionId(), event.getMessage());
}
}
}
// Runs on a library thread.
runtime
.getDiscarding()
.setEventHandler(
new RuntimeEventHandler(this::handleEvents));
runtime
.setOfflineRegionObserved(
region.getId(), RuntimeEventFlag.ALL_RUNTIME_EVENTS)
.join();
Main.kt
runtime.discarding.setEventHandler(
RuntimeEventHandler { events ->
for (event in events) {
when (val payload = event.payload) {
is RuntimeEventPayload.OfflineRegionStatus -> {
val (regionId, status) = payload.value
recordProgress(
regionId,
status.completedResourceCount,
status.requiredResourceCount,
status.complete,
)
}
is RuntimeEventPayload.OfflineRegionResponseError ->
recordError(payload.value.regionId, event.message)
else -> {}
}
}
},
)
runtime
.setOfflineRegionObserved(
region.id,
RuntimeEventFlag.ALL_RUNTIME_EVENTS,
)
.await()
main.py
def on_events(events: tuple[mln.RuntimeEvent, ...]) -> None:
for event in events:
match event.payload:
case mln.RuntimeEventOfflineRegionStatusVariant(
value=update
):
record_progress(
update.region_id,
update.status.completed_resource_count,
update.status.required_resource_count,
update.status.complete,
)
case mln.RuntimeEventOfflineRegionResponseErrorVariant(
value=error
):
record_error(error.region_id, event.message)
runtime.discarding.set_event_handler(
mln.RuntimeEventHandler(on_events)
)
runtime.set_offline_region_observed(
region.id, mln.RuntimeEventFlag.ALL_RUNTIME_EVENTS
).result()
main.rs
let handler = RuntimeEventHandler::new(|events| {
for event in events {
match event.payload {
RuntimeEventPayload::OfflineRegionStatus(payload) => {
record_progress(
payload.region_id,
payload.status.completed_resource_count,
payload.status.required_resource_count,
payload.status.complete,
);
}
RuntimeEventPayload::OfflineRegionResponseError(
payload,
) => record_error(payload.region_id, &event.message),
_ => {}
}
}
});
runtime.discarding().set_event_handler(handler)?;
runtime
.set_offline_region_observed(
region.id,
RuntimeEventFlag::ALL_RUNTIME_EVENTS,
)?
.await?;
main.swift
try runtime.discarding
.setEventHandler(RuntimeEventHandler { events in
for event in events {
switch event.payload {
case let .offlineRegionStatus(payload):
recordProgress(
payload.regionId,
completed: payload.status.completedResourceCount,
required: payload.status.requiredResourceCount,
complete: payload.status.complete
)
case let .offlineRegionResponseError(payload):
recordError(payload.regionId, event.message)
default:
break
}
}
})
try await runtime.setOfflineRegionObserved(
region.id,
events: .allRuntimeEvents
)
main.ts
runtime.discarding.setEventHandler({
callback: (events) => {
for (const { payload, message } of events) {
if (payload.kind === "offlineRegionStatus") {
const { regionId, status } = payload.value;
recordProgress(
regionId,
status.completedResourceCount,
status.requiredResourceCount,
status.complete,
);
} else if (payload.kind === "offlineRegionResponseError") {
recordError(payload.value.regionId, message);
}
}
},
});
await runtime.setOfflineRegionObserved(
region.id,
RuntimeEventFlag.allRuntimeEvents,
);
main.zig
fn onEvents(
_: ?*anyopaque,
events: []const maplibre.RuntimeEvent,
) anyerror!void {
for (events) |event| {
switch (event.payload) {
.offline_region_status => |payload| recordProgress(
payload.region_id,
payload.status.completed_resource_count,
payload.status.required_resource_count,
payload.status.complete,
),
.offline_region_response_error => |payload| recordError(
payload.region_id,
event.message,
),
else => {},
}
}
}
try runtime.discarding().setEventHandler(
.{ .callback = onEvents },
null,
);
const observed = try runtime.setOfflineRegionObserved(
region.value.id,
maplibre.RuntimeEventFlag.all_runtime_events,
null,
);
try observed.await(null);

List the regions in the cache file with mln_runtime_list_offline_regionsRuntimeHandle.listOfflineRegionsRuntimeHandle.ListOfflineRegionsAsyncRuntimeHandle.ListOfflineRegionsmaplibre_native_ffi_runtime_list_offline_regions_asyncRuntimeHandle.listOfflineRegionsRuntimeHandle.listOfflineRegionsRuntimeHandle.list_offline_regionsRuntimeHandle::list_offline_regionsRuntimeHandle.listOfflineRegionsRuntimeHandle.listOfflineRegionsRuntime.listOfflineRegions. A download stops when the runtime is released, so to resume one, set its download active again.

When the server’s data changes, invalidate the region with mln_runtime_invalidate_offline_regionRuntimeHandle.invalidateOfflineRegionRuntimeHandle.InvalidateOfflineRegionAsyncRuntimeHandle.InvalidateOfflineRegionmaplibre_native_ffi_runtime_invalidate_offline_region_asyncRuntimeHandle.invalidateOfflineRegionRuntimeHandle.invalidateOfflineRegionRuntimeHandle.invalidate_offline_regionRuntimeHandle::invalidate_offline_regionRuntimeHandle.invalidateOfflineRegionRuntimeHandle.invalidateOfflineRegionRuntime.invalidateOfflineRegion, and maps check each of its resources with the server before they use it. To refresh the region for offline use, start its download again.

Delete a region you no longer need with mln_runtime_delete_offline_regionRuntimeHandle.deleteOfflineRegionRuntimeHandle.DeleteOfflineRegionAsyncRuntimeHandle.DeleteOfflineRegionmaplibre_native_ffi_runtime_delete_offline_region_asyncRuntimeHandle.deleteOfflineRegionRuntimeHandle.deleteOfflineRegionRuntimeHandle.delete_offline_regionRuntimeHandle::delete_offline_regionRuntimeHandle.deleteOfflineRegionRuntimeHandle.deleteOfflineRegionRuntime.deleteOfflineRegion.

main.c
static void on_regions(
void* user_data, const mln_completion_result* result
) {
if (result->status == MLN_STATUS_OK) {
for (size_t i = 0; i < result->value_count; i += 1) {
const mln_offline_region_info* region = MLN_STRIDED_AT(
mln_offline_region_info, result->value, result->value_size, i
);
record_region(
region->id, region->metadata, region->metadata_size
);
}
}
on_done(user_data, result);
}
const mln_completion listed = {
.size = sizeof(mln_completion), .callback = on_regions
};
mln_runtime_list_offline_regions(runtime, &listed, NULL);
// Resume a download that stopped when the runtime was released.
mln_runtime_set_offline_region_download_state(
runtime, region_id, MLN_OFFLINE_REGION_DOWNLOAD_ACTIVE, &done,
NULL
);
mln_runtime_invalidate_offline_region(
runtime, region_id, &done, NULL
);
mln_runtime_delete_offline_region(runtime, region_id, &done, NULL);
main.dart
for (final stored in await runtime.listOfflineRegions()) {
recordRegion(stored.id, stored.metadata);
}
// Resume a download that stopped when the runtime was released.
await runtime.setOfflineRegionDownloadState(
region.id,
OfflineRegionDownloadState.active,
);
await runtime.invalidateOfflineRegion(region.id);
await runtime.deleteOfflineRegion(region.id);
Program.cs
foreach (
var stored in await runtime.ListOfflineRegionsAsync()
)
{
RecordRegion(stored.Id, stored.Metadata);
}
// Resume a download that stopped when the runtime was released.
await runtime.SetOfflineRegionDownloadStateAsync(
region.Id,
OfflineRegionDownloadState.Active
);
await runtime.InvalidateOfflineRegionAsync(region.Id);
await runtime.DeleteOfflineRegionAsync(region.Id);
main.go
listed, err := runtime.ListOfflineRegions()
if err != nil {
return err
}
regions, err := listed.Await(ctx)
if err != nil {
return err
}
for _, stored := range regions {
recordRegion(stored.ID, stored.Metadata)
}
// Resume a download that stopped when the runtime was released.
resumed, err := runtime.SetOfflineRegionDownloadState(
region.ID, maplibre.OfflineRegionDownloadStateActive,
)
if err != nil {
return err
}
if _, err := resumed.Await(ctx); err != nil {
return err
}
invalidated, err := runtime.InvalidateOfflineRegion(region.ID)
if err != nil {
return err
}
if _, err := invalidated.Await(ctx); err != nil {
return err
}
deleted, err := runtime.DeleteOfflineRegion(region.ID)
if err != nil {
return err
}
if _, err := deleted.Await(ctx); err != nil {
return err
}
main.vala
var stored = yield runtime.list_offline_regions_async ();
foreach (var info in stored) {
record_region (info.get_id (), info.get_metadata ());
}
// Resume a download that stopped when the runtime was
// released.
yield runtime.set_offline_region_download_state_async (
region.get_id (), OfflineRegionDownloadState.ACTIVE
);
yield runtime.invalidate_offline_region_async (region.get_id ());
yield runtime.delete_offline_region_async (region.get_id ());
Main.java
for (OfflineRegionInfo stored :
runtime.listOfflineRegions().join()) {
recordRegion(stored.getId(), stored.getMetadata());
}
// Resume a download that stopped when the runtime was released.
runtime
.setOfflineRegionDownloadState(
region.getId(), OfflineRegionDownloadState.ACTIVE)
.join();
runtime.invalidateOfflineRegion(region.getId()).join();
runtime.deleteOfflineRegion(region.getId()).join();
Main.kt
for (stored in runtime.listOfflineRegions().await()) {
recordRegion(stored.id, stored.metadata)
}
// Resume a download that stopped when the runtime was released.
runtime
.setOfflineRegionDownloadState(
region.id,
OfflineRegionDownloadState.ACTIVE,
)
.await()
runtime.invalidateOfflineRegion(region.id).await()
runtime.deleteOfflineRegion(region.id).await()
main.py
for stored in runtime.list_offline_regions().result():
record_region(stored.id, stored.metadata)
# Resume a download that stopped when the runtime was released.
runtime.set_offline_region_download_state(
region.id, mln.OfflineRegionDownloadState.ACTIVE
).result()
runtime.invalidate_offline_region(region.id).result()
runtime.delete_offline_region(region.id).result()
main.rs
for stored in runtime.list_offline_regions()?.await? {
record_region(stored.id, &stored.metadata);
}
// Resume a download that stopped when the runtime was released.
runtime
.set_offline_region_download_state(
region.id,
OfflineRegionDownloadState::Active,
)?
.await?;
runtime.invalidate_offline_region(region.id)?.await?;
runtime.delete_offline_region(region.id)?.await?;
main.swift
for stored in try await runtime.listOfflineRegions() {
recordRegion(stored.id, stored.metadata)
}
// Resume a download that stopped when the runtime was released.
try await runtime.setOfflineRegionDownloadState(
region.id,
state: .active
)
try await runtime.invalidateOfflineRegion(region.id)
try await runtime.deleteOfflineRegion(region.id)
main.ts
for (const stored of await runtime.listOfflineRegions()) {
recordRegion(stored.id, stored.metadata);
}
// Resume a download that stopped when the runtime was released.
await runtime.setOfflineRegionDownloadState(
region.id,
OfflineRegionDownloadState.active,
);
await runtime.invalidateOfflineRegion(region.id);
await runtime.deleteOfflineRegion(region.id);
main.zig
var regions = try (try runtime.listOfflineRegions(allocator, null)).await(null);
defer regions.deinit();
for (regions.value) |stored| {
recordRegion(stored.id, stored.metadata);
}
// Resume a download that stopped when the runtime was released.
const resumed = try runtime.setOfflineRegionDownloadState(
region.value.id,
.active,
null,
);
try resumed.await(null);
const invalidated =
try runtime.invalidateOfflineRegion(region.value.id, null);
try invalidated.await(null);
const deleted =
try runtime.deleteOfflineRegion(region.value.id, null);
try deleted.await(null);