Render images
This guide introduces how to render still images of a map. Start with a runtime from Create a map.
Create a static map
Section titled “Create a static map”Create a map with MLN_MAP_MODE_STATICMapMode.staticMapMode.StaticMapModeStaticMAPLIBRE_NATIVE_FFI_MAP_MODE_STATICMapMode.STATICMapMode.STATICMapMode.STATICMapMode::StaticMapMode.staticMapMode.staticMapMode.static, at the image size you want in
logical pixels. For a higher-resolution image, raise the scale factor: a scale
factor of 2 doubles the image’s width and height in pixels. Then set its style,
as in Create a map.
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_STATIC,};
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.static, ),);await map.ready;var map = runtime.CreateMap( new MapOptions { InitialExtent = new LogicalExtent(800, 600, 2), MapMode = MapMode.Static, });await map.Ready;m, created, err := runtime.CreateMap(maplibre.MapOptions{ InitialExtent: maplibre.LogicalExtent{ Width: 800, Height: 600, ScaleFactor: 2, }, MapMode: maplibre.MapModeStatic,})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.STATIC,};var map = runtime.create_map (options);yield map.ready_async ();MapHandle map = runtime.createMap( MapOptions.of(LogicalExtent.of(800, 600, 2.0)) .withMapMode(MapMode.STATIC));map.getReady().join();val map = runtime.createMap( MapOptions( initialExtent = LogicalExtent( width = 800u, height = 600u, scaleFactor = 2.0, ), mapMode = MapMode.STATIC, ), )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.STATIC, ))map_handle.ready.result()let map_options = MapOptions { map_mode: MapMode::Static, ..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: .static ))try await map.readyconst map = runtime.createMap({ initialExtent: { width: 800, height: 600, scaleFactor: 2 }, mapMode: MapMode.static,});await map.ready;const map_options = maplibre.MapOptions{ .initial_extent = .{ .width = 800, .height = 600, .scale_factor = 2.0, }, .map_mode = .static,};
const created = try runtime.createMap(map_options, null);var map = created.map;try created.ready.await(null);Render offscreen
Section titled “Render offscreen”Attach an offscreen render session to the map with
mln_map_attach_offscreenMapHandle.attachOffscreenMapHandle.AttachOffscreenMapHandle.AttachOffscreenmaplibre_native_ffi_map_attach_offscreenMapHandle.attachOffscreenMapHandle.attachOffscreenMapHandle.attach_offscreenMapHandle::attach_offscreenMapHandle.attachOffscreenMapHandle.attachOffscreenMap.attachOffscreen, at the map’s size. It renders on a GPU device
that the library creates, so your app doesn’t need a graphics context. Use the
render session it returns.
To share one device among the sessions of many maps, or to pick a GPU, create an
mln_offscreen_deviceOffscreenDeviceHandleOffscreenDeviceHandleOffscreenDeviceHandleMaplibreNativeFfiOffscreenDeviceOffscreenDeviceHandleOffscreenDeviceHandleOffscreenDeviceHandleOffscreenDeviceHandleOffscreenDeviceHandleOffscreenDeviceHandleOffscreenDevice and pass it when you attach.
mln_offscreen_descriptor offscreen = { .size = sizeof(mln_offscreen_descriptor), .logical_size = {.width = 800, .height = 600},};mln_render_session_attach_options attach_options = { .size = sizeof(mln_render_session_attach_options), // The library renders on its own thread. .driver = MLN_RENDER_DRIVER_CORE_WORKER,};
// With no device, the session creates its own.mln_render_session session = MLN_HANDLE_NULL;mln_map_attach_offscreen( map, &offscreen, &attach_options, MLN_HANDLE_NULL, &session, &done, NULL);final session = map.attachOffscreen( const OffscreenDescriptor( logicalSize: LogicalSize(width: 800, height: 600), ), // The library renders on its own thread. const RenderSessionAttachOptions( driver: RenderDriverKind.coreWorker, ),);await session.ready;var session = map.AttachOffscreen( new OffscreenDescriptor(new LogicalSize(800, 600)), // The library renders on its own thread. new RenderSessionAttachOptions { Driver = RenderDriverKind.CoreWorker, });await session.Ready;session, attached, err := m.AttachOffscreen( maplibre.OffscreenDescriptor{ LogicalSize: maplibre.LogicalSize{Width: 800, Height: 600}, }, // The library renders on its own thread. maplibre.RenderSessionAttachOptions{ Driver: maplibre.RenderDriverKindCoreWorker, }, // With no device, the session creates its own. nil,)if err != nil { return err}if _, err := attached.Await(ctx); err != nil { return err}var attach = new RenderSessionAttachOptions () { // The library renders on its own thread. driver = RenderDriverKind.CORE_WORKER,};var session = map.attach_offscreen ( { logical_size: { width: 800, height: 600 } }, attach, null);yield session.ready_async ();RenderSessionHandle session = map.attachOffscreen( new OffscreenDescriptor(LogicalSize.of(800, 600)), // The library renders on its own thread. RenderSessionAttachOptions.of( RenderDriverKind.CORE_WORKER), null);session.getReady().join();val session = map.attachOffscreen( OffscreenDescriptor(LogicalSize(width = 800u, height = 600u)), // The library renders on its own thread. RenderSessionAttachOptions( driver = RenderDriverKind.CORE_WORKER, ), )session.ready.await()session = map_handle.attach_offscreen( mln.OffscreenDescriptor( logical_size=mln.LogicalSize(width=800, height=600) ), # The library renders on its own thread. mln.RenderSessionAttachOptions( driver=mln.RenderDriverKind.CORE_WORKER ),)session.ready.result()let offscreen = OffscreenDescriptor::new(LogicalSize::new(800, 600));// The library renders on its own thread.let attach_options = RenderSessionAttachOptions::new(RenderDriverKind::CoreWorker);
// With no device, the session creates its own.let (session, attached) = map.attach_offscreen(&offscreen, &attach_options, None)?;attached.await?;let session = try map.attachOffscreen( descriptor: OffscreenDescriptor( logicalSize: LogicalSize(width: 800, height: 600) ), // The library renders on its own thread. options: RenderSessionAttachOptions(driver: .coreWorker))try await session.readyconst session = map.attachOffscreen( { logicalSize: { width: 800, height: 600 } }, // The library renders on its own thread. { driver: RenderDriverKind.coreWorker },);await session.ready;const attached = try map.attachOffscreen( .{ .logical_size = .{ .width = 800, .height = 600 } }, // The library renders on its own thread. .{ .driver = .core_worker }, // With no device, the session creates its own. null, null,);var session = attached.session;try attached.ready.await(null);Render an image
Section titled “Render an image”A static map renders only when you request an image. Jump the camera to the view
you want, as in
Move the camera, then
request a still image with mln_map_request_still_imageMapHandle.requestStillImageMapHandle.RequestStillImageAsyncMapHandle.RequestStillImagemaplibre_native_ffi_map_request_still_image_asyncMapHandle.requestStillImageMapHandle.requestStillImageMapHandle.request_still_imageMapHandle::request_still_imageMapHandle.requestStillImageMapHandle.requestStillImageMap.requestStillImage.
To change the image size, resize the render session with
mln_render_session_resizeRenderSessionHandle.resizeRenderSessionHandle.ResizeAsyncRenderSessionHandle.Resizemaplibre_native_ffi_render_session_resize_asyncRenderSessionHandle.resizeRenderSessionHandle.resizeRenderSessionHandle.resizeRenderSessionHandle::resizeRenderSessionHandle.resizeRenderSessionHandle.resizeRenderSession.resize, and it resizes the map too.
mln_camera_update update = { .size = sizeof(mln_camera_update), .mode = MLN_CAMERA_UPDATE_MODE_JUMP, .camera = { .size = sizeof(mln_camera_options), .fields = MLN_CAMERA_OPTION_CENTER | MLN_CAMERA_OPTION_ZOOM, .center = {.latitude = 48.8566, .longitude = 2.3522}, .zoom = 4, },};mln_map_update_camera(map, &update, NULL, NULL);
mln_map_request_still_image(map, &done, NULL);map.discarding.updateCamera( const CameraUpdate( mode: CameraUpdateMode.jump, camera: CameraOptions( center: LatLng(latitude: 48.8566, longitude: 2.3522), zoom: 4, ), ),);
await map.requestStillImage();map.Discarding.UpdateCamera( new CameraUpdate { Mode = CameraUpdateMode.Jump, Camera = new CameraOptions { Center = new LatLng(48.8566, 2.3522), Zoom = 4, }, });
await map.RequestStillImageAsync();err = m.Discarding().UpdateCamera(maplibre.CameraUpdate{ Mode: maplibre.CameraUpdateModeJump, Camera: maplibre.CameraOptions{ Center: &maplibre.LatLng{Latitude: 48.8566, Longitude: 2.3522}, Zoom: new(4.0), },})if err != nil { return err}
rendered, err := m.RequestStillImage()if err != nil { return err}if _, err := rendered.Await(ctx); err != nil { return err}var camera = new CameraOptions () { center = { latitude: 48.8566, longitude: 2.3522 }, zoom = 4,};var update = new CameraUpdate () { mode = CameraUpdateMode.JUMP, camera = camera,};map.update_camera_async.begin (update);
yield map.request_still_image_async ();map.getDiscarding() .updateCamera( CameraUpdate.of() .withMode(CameraUpdateMode.JUMP) .withCamera( CameraOptions.of() .withCenter(new LatLng(48.8566, 2.3522)) .withZoom(4.0)));
map.requestStillImage().join();map.discarding.updateCamera( CameraUpdate( mode = CameraUpdateMode.JUMP, camera = CameraOptions( center = LatLng(latitude = 48.8566, longitude = 2.3522), zoom = 4.0, ), ),)
map.requestStillImage().await()map_handle.discarding.update_camera( mln.CameraUpdate( mode=mln.CameraUpdateMode.JUMP, camera=mln.CameraOptions( center=mln.LatLng(latitude=48.8566, longitude=2.3522), zoom=4, ), ))
map_handle.request_still_image().result()map.discarding().update_camera(&CameraUpdate { mode: CameraUpdateMode::Jump, camera: CameraOptions { center: Some(LatLng::new(48.8566, 2.3522)), zoom: Some(4.0), ..Default::default() }, ..Default::default()})?;
map.request_still_image()?.await?;try map.discarding.updateCamera(CameraUpdate( mode: .jump, camera: CameraOptions( center: LatLng(latitude: 48.8566, longitude: 2.3522), zoom: 4 )))
try await map.requestStillImage()map.discarding.updateCamera({ mode: CameraUpdateMode.jump, camera: { center: { latitude: 48.8566, longitude: 2.3522 }, zoom: 4, },});
await map.requestStillImage();try map.discarding().updateCamera(.{ .mode = .jump, .camera = .{ .center = .{ .latitude = 48.8566, .longitude = 2.3522 }, .zoom = 4, },}, null);
try (try map.requestStillImage(null)).await(null);Read the pixels
Section titled “Read the pixels”Read the image’s pixels with mln_render_session_read_textureRenderSessionHandle.readTextureRenderSessionHandle.ReadTextureAsyncRenderSessionHandle.ReadTexturemaplibre_native_ffi_render_session_read_texture_asyncRenderSessionHandle.readTextureRenderSessionHandle.readTextureRenderSessionHandle.read_textureRenderSessionHandle::read_textureRenderSessionHandle.readTextureRenderSessionHandle.readTextureRenderSession.readTexture.
To use the images on the GPU instead, see Render to a texture.
static void on_pixels( void* user_data, const mln_completion_result* result) { if (result->status == MLN_STATUS_OK) { const mln_texture_readback_result* readback = result->value; // readback->data is valid only during this callback. copy_pixels(readback->data, readback->info.stride); } on_done(user_data, result);}
const mln_completion read = { .size = sizeof(mln_completion), .callback = on_pixels};
mln_render_session_read_texture(session, &read, NULL);final image = await session.readTexture();copyPixels(image.data, image.info.stride);var image = await session.ReadTextureAsync();CopyPixels(image.Data, image.Info.Stride);read, err := session.ReadTexture()if err != nil { return err}image, err := read.Await(ctx)if err != nil { return err}copyPixels(image.Data, image.Info.Stride)var image = yield session.read_texture_async ();copy_pixels (image.get_data (), image.get_info ().stride);TextureReadbackResult image = session.readTexture().join();copyPixels(image.getData(), image.getInfo().getStride());val image = session.readTexture().await()copyPixels(image.data, image.info.stride)image = session.read_texture().result()copy_pixels(image.data, image.info.stride)let image = session.read_texture()?.await?;copy_pixels(&image.data, image.info.stride);let image = try await session.readTexture()copyPixels(image.data, stride: image.info.stride)const image = await session.readTexture();copyPixels(image.data, image.info.stride);var image = try (try session.readTexture(allocator, null)).await(null);defer image.deinit();copyPixels(image.value.data, image.value.info.stride);Render map tiles
Section titled “Render map tiles”For map tiles, such as on a tile server, create the map with
MLN_MAP_MODE_TILEMapMode.tileMapMode.TileMapModeTileMAPLIBRE_NATIVE_FFI_MAP_MODE_TILEMapMode.TILEMapMode.TILEMapMode.TILEMapMode::TileMapMode.tileMapMode.tileMapMode.tile instead. Make it square, at the tile size that your
map clients expect, such as 256 or 512. For @2x tiles, set the scale factor to
2 and keep the size.
To render tile z/x/y, compute its camera for the map’s size with
mln_camera_for_tilecameraForTileMaplibreNative.CameraForTileCameraForTilemaplibre_native_ffi_camera_for_tilecameraForTilecameraForTilecamera_for_tilecamera_for_tilecameraForTilecameraForTilecameraForTile, and jump to it.
mln_map_options map_options = { .size = sizeof(mln_map_options), .initial_extent = {.width = 512, .height = 512, .scale_factor = 1.0}, .map_mode = MLN_MAP_MODE_TILE,};mln_map map = MLN_HANDLE_NULL;mln_runtime_create_map(runtime, &map_options, &map, &done, NULL);
mln_camera_update update = { .size = sizeof(mln_camera_update), .mode = MLN_CAMERA_UPDATE_MODE_JUMP, .camera = {.size = sizeof(mln_camera_options)},};mln_camera_for_tile( (mln_canonical_tile_id){.z = z, .x = x, .y = y}, (mln_logical_size){.width = 512, .height = 512}, &update.camera, NULL);mln_map_update_camera(map, &update, NULL, NULL);final map = runtime.createMap( const MapOptions( initialExtent: LogicalExtent( width: 512, height: 512, scaleFactor: 1, ), mapMode: MapMode.tile, ),);await map.ready;
map.discarding.updateCamera( CameraUpdate( mode: CameraUpdateMode.jump, camera: cameraForTile( CanonicalTileId(z: z, x: x, y: y), const LogicalSize(width: 512, height: 512), ), ),);var map = runtime.CreateMap( new MapOptions { InitialExtent = new LogicalExtent(512, 512, 1), MapMode = MapMode.Tile, });await map.Ready;
map.Discarding.UpdateCamera( new CameraUpdate { Mode = CameraUpdateMode.Jump, Camera = MaplibreNative.CameraForTile( new CanonicalTileId(z, x, y), new LogicalSize(512, 512) ), });m, created, err := runtime.CreateMap(maplibre.MapOptions{ InitialExtent: maplibre.LogicalExtent{ Width: 512, Height: 512, ScaleFactor: 1, }, MapMode: maplibre.MapModeTile,})if err != nil { return err}if _, err := created.Await(ctx); err != nil { return err}
camera, err := maplibre.CameraForTile( maplibre.CanonicalTileID{Z: z, X: x, Y: y}, maplibre.LogicalSize{Width: 512, Height: 512},)if err != nil { return err}err = m.Discarding().UpdateCamera(maplibre.CameraUpdate{ Mode: maplibre.CameraUpdateModeJump, Camera: camera,})if err != nil { return err}var options = new MapOptions () { initial_extent = { width: 512, height: 512, scale_factor: 1.0 }, map_mode = MapMode.TILE,};var map = runtime.create_map (options);yield map.ready_async ();
var update = new CameraUpdate () { mode = CameraUpdateMode.JUMP, camera = Camera.for_tile ({ z, x, y }, { width: 512, height: 512 }),};map.update_camera_async.begin (update);MapHandle map = runtime.createMap( MapOptions.of(LogicalExtent.of(512, 512, 1.0)) .withMapMode(MapMode.TILE));map.getReady().join();
map.getDiscarding() .updateCamera( CameraUpdate.of() .withMode(CameraUpdateMode.JUMP) .withCamera( MaplibreNative.cameraForTile( CanonicalTileId.of(z, x, y), LogicalSize.of(512, 512))));val map = runtime.createMap( MapOptions( initialExtent = LogicalExtent( width = 512u, height = 512u, scaleFactor = 1.0, ), mapMode = MapMode.TILE, ), )map.ready.await()
map.discarding.updateCamera( CameraUpdate( mode = CameraUpdateMode.JUMP, camera = cameraForTile( CanonicalTileId(z = z, x = x, y = y), LogicalSize(width = 512u, height = 512u), ), ),)map_handle = runtime.create_map( mln.MapOptions( initial_extent=mln.LogicalExtent( width=512, height=512, scale_factor=1.0 ), map_mode=mln.MapMode.TILE, ))map_handle.ready.result()
map_handle.discarding.update_camera( mln.CameraUpdate( mode=mln.CameraUpdateMode.JUMP, camera=mln.camera_for_tile( mln.CanonicalTileId(z=z, x=x, y=y), mln.LogicalSize(width=512, height=512), ), ))let map_options = MapOptions { map_mode: MapMode::Tile, ..MapOptions::new(LogicalExtent::new(512, 512, 1.0))};let (map, created) = runtime.create_map(&map_options)?;created.await?;
map.discarding().update_camera(&CameraUpdate { mode: CameraUpdateMode::Jump, camera: camera_for_tile( CanonicalTileId::new(z, x, y), LogicalSize::new(512, 512), )?, ..Default::default()})?;let map = try runtime.createMap( options: MapOptions( initialExtent: LogicalExtent( width: 512, height: 512, scaleFactor: 1 ), mapMode: .tile ))try await map.ready
try map.discarding.updateCamera(CameraUpdate( mode: .jump, camera: cameraForTile( CanonicalTileId(z: z, x: x, y: y), logicalSize: LogicalSize(width: 512, height: 512) )))const map = runtime.createMap({ initialExtent: { width: 512, height: 512, scaleFactor: 1 }, mapMode: MapMode.tile,});await map.ready;
map.discarding.updateCamera({ mode: CameraUpdateMode.jump, camera: cameraForTile({ z, x, y }, { width: 512, height: 512 }),});const created = try runtime.createMap(.{ .initial_extent = .{ .width = 512, .height = 512, .scale_factor = 1.0, }, .map_mode = .tile,}, null);var map = created.map;try created.ready.await(null);
try map.discarding().updateCamera(.{ .mode = .jump, .camera = try maplibre.cameraForTile( .{ .z = z, .x = x, .y = y }, .{ .width = 512, .height = 512 }, null, ),}, null);