Create a map
You primarily work with three stateful objects:
- A
mln_runtimeRuntimeHandleRuntimeHandleRuntimeHandleMaplibreNativeFfiRuntimeRuntimeHandleRuntimeHandleRuntimeHandleRuntimeHandleRuntimeHandleRuntimeHandleRuntimeis the main configuration context for how the map operates, including network and cache. - A
mln_mapMapHandleMapHandleMapHandleMaplibreNativeFfiMapMapHandleMapHandleMapHandleMapHandleMapHandleMapHandleMapbelongs to a runtime, and holds a single map’s state, like its style and camera. - A
mln_render_sessionRenderSessionHandleRenderSessionHandleRenderSessionHandleMaplibreNativeFfiRenderSessionRenderSessionHandleRenderSessionHandleRenderSessionHandleRenderSessionHandleRenderSessionHandleRenderSessionHandleRenderSessionbelongs to a map, and holds the renderer that draws it into a target, such as a texture or a window.
To avoid a leak, release each of these when you’re done with it.
sequenceDiagram accTitle: The lifecycle of a runtime, a map, and a render session participant App as Your code participant Lib as Library App->>Lib: Create a runtime App->>Lib: Create a map Lib-->>App: Map created App->>Lib: Set a style App->>Lib: Attach a render target Lib-->>App: Render session attached Note over App,Lib: Render frames and change the map App->>Lib: Release the render session App->>Lib: Release the map App->>Lib: Release the runtime Lib-->>App: Runtime released
This guide creates a runtime and a map, releases them, and covers how calls complete and fail. Choose a render target covers the target.
Create a runtime
Section titled “Create a runtime”Create a runtime. Without mln_runtime_optionsRuntimeOptionsRuntimeOptionsRuntimeOptionsMaplibreNativeFfiRuntimeOptionsRuntimeOptionsRuntimeOptionsRuntimeOptionsRuntimeOptionsRuntimeOptionsRuntimeOptionsRuntimeOptions, it uses the defaults.
Usually, one runtime is all you need for all your maps.
mln_runtime runtime = MLN_HANDLE_NULL;mln_runtime_create(NULL, &runtime, NULL);final runtime = RuntimeHandle.create();await using var runtime = RuntimeHandle.Create();runtime, err := maplibre.NewRuntime(nil)if err != nil { return err}var runtime = new Runtime.create (null);RuntimeHandle runtime = RuntimeHandle.create(null).join();val runtime = RuntimeHandle.create().await()runtime = mln.RuntimeHandle.create()let runtime = RuntimeHandle::create(None)?;let runtime = try RuntimeHandle.create()await using runtime = await RuntimeHandle.create();var runtime = try maplibre.Runtime.create(null, null);Create a map
Section titled “Create a map”Create a map on the runtime with mln_map_optionsMapOptionsMapOptionsMapOptionsMaplibreNativeFfiMapOptionsMapOptionsMapOptionsMapOptionsMapOptionsMapOptionsMapOptionsMapOptions, and give it a size in
logical pixels, a scale factor, and a map mode. The scale factor and the mode
can’t change later. For the mode, see
Choose a render target.
Wait for the map to be ready before you attach a render target.
mln_map_options map_options = { .size = sizeof(mln_map_options), .initial_extent = {.width = 800, .height = 600, .scale_factor = 2.0}, .map_mode = MLN_MAP_MODE_CONTINUOUS,};
mln_map map = MLN_HANDLE_NULL;mln_runtime_create_map(runtime, &map_options, &map, &done, NULL);final map = runtime.createMap( const MapOptions( initialExtent: LogicalExtent( width: 800, height: 600, scaleFactor: 2, ), mapMode: MapMode.continuous, ),);await map.ready;await using var map = runtime.CreateMap( new MapOptions { InitialExtent = new LogicalExtent(800, 600, 2), MapMode = MapMode.Continuous, });await map.Ready;m, created, err := runtime.CreateMap(maplibre.MapOptions{ InitialExtent: maplibre.LogicalExtent{ Width: 800, Height: 600, ScaleFactor: 2, }, MapMode: maplibre.MapModeContinuous,})if err != nil { return err}if _, err := created.Await(ctx); err != nil { return err}var options = new MapOptions () { initial_extent = { width: 800, height: 600, scale_factor: 2.0 }, map_mode = MapMode.CONTINUOUS,};
var map = runtime.create_map (options);yield map.ready_async ();MapHandle map = runtime.createMap( MapOptions.of(LogicalExtent.of(800, 600, 2.0)) .withMapMode(MapMode.CONTINUOUS));map.getReady().join();val map = runtime.createMap( MapOptions( initialExtent = LogicalExtent( width = 800u, height = 600u, scaleFactor = 2.0, ), mapMode = MapMode.CONTINUOUS, ), )map.ready.await()map_handle = runtime.create_map( mln.MapOptions( initial_extent=mln.LogicalExtent( width=800, height=600, scale_factor=2.0 ), map_mode=mln.MapMode.CONTINUOUS, ))map_handle.ready.result()let map_options = MapOptions { map_mode: MapMode::Continuous, ..MapOptions::new(LogicalExtent::new(800, 600, 2.0))};
let (map, created) = runtime.create_map(&map_options)?;created.await?;let map = try runtime.createMap(options: MapOptions( initialExtent: LogicalExtent( width: 800, height: 600, scaleFactor: 2 ), mapMode: .continuous))try await map.readyawait using map = runtime.createMap({ initialExtent: { width: 800, height: 600, scaleFactor: 2 }, mapMode: MapMode.continuous,});await map.ready;const map_options = maplibre.MapOptions{ .initial_extent = .{ .width = 800, .height = 600, .scale_factor = 2.0, }, .map_mode = .continuous,};
const created = try runtime.createMap(map_options, null);var map = created.map;try created.ready.await(null);Set a style
Section titled “Set a style”Set the map’s 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. To work with the map style in detail, see
Style.
mln_map_set_style_url( map, mln_buffer_view_from_cstr( "https://demotiles.maplibre.org/style.json" ), NULL, NULL);map.discarding.setStyleUrl( 'https://demotiles.maplibre.org/style.json',);map.Discarding.SetStyleUrl( "https://demotiles.maplibre.org/style.json");err = m.Discarding().SetStyleURL( "https://demotiles.maplibre.org/style.json",)if err != nil { return err}map.set_style_url_async.begin ( "https://demotiles.maplibre.org/style.json");map.getDiscarding() .setStyleUrl("https://demotiles.maplibre.org/style.json");map.discarding.setStyleUrl( "https://demotiles.maplibre.org/style.json",)map_handle.discarding.set_style_url( "https://demotiles.maplibre.org/style.json")map.discarding() .set_style_url("https://demotiles.maplibre.org/style.json")?;try map.discarding .setStyleUrl("https://demotiles.maplibre.org/style.json")map.discarding.setStyleUrl( "https://demotiles.maplibre.org/style.json",);try map.discarding().setStyleUrl( "https://demotiles.maplibre.org/style.json", null,);Wait for results
Section titled “Wait for results”Most work is coordinated by the map runtime on its own thread, so most calls are asynchronous. The library queues your work and then reports the result, either with a callback or with your language’s idiom for asynchronous operations.
Operations happen in the order you send them, so you generally only need to wait for the result you need, not every call on the way.
static void on_camera_moved(void*, const mln_completion_result*) { // The camera has moved.}
const mln_completion moved = { .size = sizeof(mln_completion), .callback = on_camera_moved};mln_map_update_camera(map, &update, &moved, NULL);
// Pass no completion to ignore the result.mln_map_update_camera(map, &update, NULL, NULL);await map.updateCamera(update);
// Use the discarding variant to ignore the result.map.discarding.updateCamera(update);await map.UpdateCameraAsync(update);
// Use the discarding variant to ignore the result.map.Discarding.UpdateCamera(update);moved, err := m.UpdateCamera(update)if err != nil { return err}if _, err := moved.Await(ctx); err != nil { return err}
// Use the discarding variant to ignore the result.if err := m.Discarding().UpdateCamera(update); err != nil { return err}yield map.update_camera_async (update);
// Start it without a callback to ignore the result.map.update_camera_async.begin (update);// join() blocks until the result arrives. To continue without// blocking, chain a stage such as thenRun() instead.map.updateCamera(update).join();
// Use the discarding variant to ignore the result.map.getDiscarding().updateCamera(update);map.updateCamera(update).await()
// Use the discarding variant to ignore the result.map.discarding.updateCamera(update)map_handle.update_camera(update).result()
# Use the discarding variant to ignore the result.map_handle.discarding.update_camera(update)map.update_camera(&update)?.await?;
// Use the discarding variant to ignore the result.map.discarding().update_camera(&update)?;try await map.updateCamera(update)
// Use the discarding variant to ignore the result.try map.discarding.updateCamera(update)await map.updateCamera(update);
// Use the discarding variant to ignore the result.map.discarding.updateCamera(update);_ = try (try map.updateCamera(update, null)).await(null);
// Use the discarding variant to ignore the result.try map.discarding().updateCamera(update, null);Some information is reported to your integration as events. See Events and logs to listen for events.
Some calls fail immediately, and others report a failure through their
asynchronous result. See your language’s API reference for how each call can
fail. Every failure carries a mln_statusStatusStatusStatusMaplibreNativeFfiErrorStatusStatusStatusStatusStatusStatusStatus and a message.
static void on_camera_result( void*, const mln_completion_result* result) { if (result->status != MLN_STATUS_OK) { report_failure(result->diagnostic); }}
const mln_completion result = { .size = sizeof(mln_completion), .callback = on_camera_result};mln_diagnostic diagnostic = {.size = sizeof(mln_diagnostic)};const mln_status status = mln_map_update_camera(map, &update, &result, &diagnostic);if (status != MLN_STATUS_OK) { report_failure(mln_buffer_view_from_cstr(diagnostic.message));}try { await map.updateCamera(update);} on MaplibreException catch (error) { // Its message describes the failure. reportFailure(error);}try{ await map.UpdateCameraAsync(update);}catch (MaplibreException error){ // Its message describes the failure. ReportFailure(error);}jumped, err := m.UpdateCamera(update)if err != nil { // The call failed right away. reportFailure(err)} else if _, err := jumped.Await(ctx); err != nil { // The result reports a failure. reportFailure(err)}try { yield map.update_camera_async (update);} catch (GLib.Error error) { // Its message describes the failure. report_failure (error);}try { map.updateCamera(update).join();} catch (MaplibreException error) { // The call failed right away. Its message describes the // failure. reportFailure(error);} catch (CompletionException error) { // The result failed, with a MaplibreException as the cause. reportFailure(error.getCause());}try { map.updateCamera(update).await()} catch (error: MaplibreException) { // Its message describes the failure. reportFailure(error)}try: map_handle.update_camera(update).result()except mln.MaplibreError as error: # Its message describes the failure. report_failure(error)match map.update_camera(&update) { // The call failed right away. Err(error) => report_failure(&error), Ok(completion) => { // The result reports a failure. if let Err(error) = completion.await { report_failure(&error); } }}do { try await map.updateCamera(update)} catch { // Its message describes the failure. reportFailure(error)}try { await map.updateCamera(update);} catch (error) { // Its message describes the failure. reportFailure(error);}var diagnostic: maplibre.Diagnostic = .{};const jumped = map.updateCamera(update, &diagnostic) catch |err| { // The call failed right away. reportFailure(diagnostic.message()); return err;};_ = jumped.await(&diagnostic) catch |err| { // The result reports a failure. reportFailure(diagnostic.message()); return err;};Release
Section titled “Release”Release the render session, the map, and the runtime, in that order. Where your
language has scoped cleanup, such as await using or with, leaving the scope
cleans them up in that order.
mln_render_session_dispose(session, NULL);mln_map_dispose(map, NULL);mln_runtime_release(runtime, &done, NULL);session.dispose();map.dispose();await runtime.release();await using var session = await AttachTarget(map);// Leaving the scope releases the session, the map, and the runtime, in// that order, and waits for each.if err := session.Dispose(); err != nil { return err}if err := m.Dispose(); err != nil { return err}runtimeReleased, err := runtime.Release()if err != nil { return err}if _, err := runtimeReleased.Await(ctx); err != nil { return err}// Releasing the last reference disposes each handle without// waiting for it to retire. The session goes first, since it// keeps the map, and the map keeps the runtime.session = null;map = null;runtime = null;try { // Render frames and change the map.} finally { // Release each one, and wait for it, in this order. session.release().join(); map.release().join(); runtime.release().join();}// Each block releases its handle when it ends, and waits for it.runtime.useReleasing { map.useReleasing { session.useReleasing { // Render frames and change the map. } }}# Leaving the block disposes the session, the map, and the runtime, in# that order, without waiting for them to retire.with runtime, map_handle, session: pass # Render frames and change the map.drop(session);drop(map);runtime.release()?.await?;try session.dispose()try map.dispose()try await runtime.release()await using session = await attachTarget(map);// Leaving the scope releases the session, the map, and the runtime, in// that order, and waits for each.try session.dispose(null);try map.dispose(null);try (try runtime.release(null)).await(null);