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.
Set a cache file
Section titled “Set a cache file”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.
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);final runtime = RuntimeHandle.create( options: const RuntimeOptions(cachePath: 'maplibre-cache.db'),);var runtime = RuntimeHandle.Create( new RuntimeOptions { CachePath = "maplibre-cache.db" });runtime, err := maplibre.NewRuntime(&maplibre.RuntimeOptions{ CachePath: "maplibre-cache.db",})if err != nil { return err}var options = new RuntimeOptions () { cache_path = "maplibre-cache.db",};var runtime = new Runtime.create (options);RuntimeHandle runtime = RuntimeHandle.create( RuntimeOptions.of() .withCachePath( cacheDirectory .resolve("maplibre-cache.db") .toString())) .join();val runtime = RuntimeHandle.create( RuntimeOptions(cachePath = "maplibre-cache.db"), ) .await()runtime = mln.RuntimeHandle.create( mln.RuntimeOptions(cache_path="maplibre-cache.db"))let runtime = RuntimeHandle::create(Some(&RuntimeOptions { cache_path: "maplibre-cache.db", ..Default::default()}))?;let runtime = try RuntimeHandle.create( options: RuntimeOptions(cachePath: "maplibre-cache.db"))const runtime = await RuntimeHandle.create({ cachePath: "maplibre-cache.db",});var runtime = try maplibre.Runtime.create( .{ .cache_path = "maplibre-cache.db" }, null,);Manage the ambient cache
Section titled “Manage the ambient cache”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.
// 50 MiBmln_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);// 50 MiBawait runtime.setMaximumAmbientCacheSize(50 * 1024 * 1024);await runtime.runCacheDatabaseOperation( CacheDatabaseOperation.clear,);// 50 MiBawait runtime.SetMaximumAmbientCacheSizeAsync( 50 * 1024 * 1024);await runtime.RunCacheDatabaseOperationAsync( CacheDatabaseOperation.Clear);// 50 MiBresized, 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}// 50 MiByield runtime.set_maximum_ambient_cache_size_async ( 50 * 1024 * 1024);yield runtime.run_cache_database_operation_async ( CacheDatabaseOperation.CLEAR);// 50 MiBruntime.setMaximumAmbientCacheSize(50L * 1024 * 1024).join();runtime .runCacheDatabaseOperation(CacheDatabaseOperation.CLEAR) .join();// 50 MiBruntime.setMaximumAmbientCacheSize(50uL * 1024uL * 1024uL).await()runtime .runCacheDatabaseOperation(CacheDatabaseOperation.CLEAR) .await()# 50 MiBruntime.set_maximum_ambient_cache_size(50 * 1024 * 1024).result()runtime.run_cache_database_operation( mln.CacheDatabaseOperation.CLEAR).result()// 50 MiBruntime .set_maximum_ambient_cache_size(50 * 1024 * 1024)? .await?;runtime .run_cache_database_operation(CacheDatabaseOperation::Clear)? .await?;// 50 MiBtry await runtime.setMaximumAmbientCacheSize(50 * 1024 * 1024)try await runtime.runCacheDatabaseOperation(.clear)// 50 MiBawait runtime.setMaximumAmbientCacheSize(50n * 1024n * 1024n);await runtime.runCacheDatabaseOperation( CacheDatabaseOperation.clear,);// 50 MiBconst resized = try runtime.setMaximumAmbientCacheSize( 50 * 1024 * 1024, null,);try resized.await(null);
try (try runtime.runCacheDatabaseOperation(.clear, null)).await(null);Download a region
Section titled “Download a region”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.
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 = ®ion_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);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,);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);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}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);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();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()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()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?;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)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,);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);Track download progress
Section titled “Track download progress”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.
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);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,);// 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);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}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);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();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()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()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?;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)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,);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);Manage regions
Section titled “Manage regions”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.
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);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);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);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}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 ());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();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()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()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?;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)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);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);