Skip to main content

maplibre_native/
lib.rs

1//! Safe Rust binding for the MapLibre Native C API.
2//!
3//! This crate owns Rust-specific ergonomics and safety policy: thread-affine
4//! public handles, parent retention, owner-thread `Drop`, Rust errors,
5//! callback closure APIs, and lifetime-scoped render resources. Shared C ABI
6//! adaptation lives in `maplibre-native-core`.
7
8#![deny(unsafe_op_in_unsafe_fn)]
9
10mod camera;
11mod custom_geometry;
12mod events;
13mod geojson;
14mod geometry;
15mod handle;
16mod json;
17mod logging;
18mod map;
19mod options;
20mod projection;
21mod render;
22mod resource;
23mod runtime;
24mod values;
25
26use crate::values::NativeValue;
27use maplibre_native_core as maplibre_core;
28use maplibre_native_sys as sys;
29
30pub use camera::{
31    AnimationOptions, BoundOptions, BoundsConstraint, CameraFitOptions, CameraOptions,
32    FreeCameraOptions, ProjectionMode,
33};
34pub use custom_geometry::{CanonicalTileId, CustomGeometrySourceOptions};
35pub use events::{
36    CameraTransitionFinishedEvent, MapId, OfflineOperationCompletedEvent,
37    OfflineRegionResponseErrorEvent, OfflineRegionStatus, OfflineRegionStatusEvent,
38    OfflineRegionTileCountLimitEvent, RenderFrameEvent, RenderMapEvent, RenderingStats,
39    RuntimeEvent, RuntimeEventPayload, RuntimeEventSource, StyleImageMissingEvent, TileActionEvent,
40    TileId, UnknownRuntimeEventPayload,
41};
42pub use geojson::{Feature, FeatureIdentifier, GeoJson};
43pub use geometry::Geometry;
44pub use json::{JsonMember, JsonValue};
45pub use logging::{LogRecord, clear_log_callback, set_async_log_severity_mask, set_log_callback};
46pub use map::{
47    GeoJsonSourceOptions, LocationIndicatorImageKind, MapHandle, RasterDemEncoding, SourceInfo,
48    SourceType, StyleImage, StyleImageInfo, StyleImageOptions, TileScheme, TileSourceOptions,
49    VectorTileEncoding,
50};
51pub use maplibre_core::{
52    AmbientCacheOperation, CameraChangeMode, ConstrainMode, Error, ErrorKind, LogEvent,
53    LogSeverity, LogSeverityMask, MapDebugOptions, MapMode, MapOptions, MapTileOptions,
54    MapViewportOptions, NetworkStatus, NorthOrientation, OfflineOperationKind,
55    OfflineOperationResultKind, OfflineRegionDownloadState, OpenGLContextProviderMask,
56    RenderBackendMask, RenderMode, ResourceErrorReason, ResourceKind, ResourceLoadingMethod,
57    ResourcePriority, ResourceResponseStatus, ResourceStoragePolicy, ResourceUsage, Result,
58    RuntimeEventType, TileLodMode, TileOperation, ViewportMode,
59};
60pub use projection::MapProjectionHandle;
61pub use render::{
62    DetachedRenderSessionHandle, EglContextDescriptor, FeatureExtensionResult,
63    FeatureStateSelector, FrameNativePointer, FrameOpenGLTextureName,
64    MetalBorrowedTextureDescriptor, MetalContextDescriptor, MetalOwnedTextureDescriptor,
65    MetalOwnedTextureFrame, MetalOwnedTextureFrameHandle, MetalSurfaceDescriptor, NativePointer,
66    OpenGLBorrowedTextureDescriptor, OpenGLContextDescriptor, OpenGLOwnedTextureDescriptor,
67    OpenGLOwnedTextureFrame, OpenGLOwnedTextureFrameHandle, OpenGLSurfaceDescriptor,
68    PremultipliedRgba8Image, QueriedFeature, RenderSessionHandle, RenderTargetExtent,
69    RenderedFeatureQueryOptions, RenderedQueryGeometry, SourceFeatureQueryOptions,
70    TextureImageInfo, VulkanBorrowedTextureDescriptor, VulkanContextDescriptor,
71    VulkanOwnedTextureDescriptor, VulkanOwnedTextureFrame, VulkanOwnedTextureFrameHandle,
72    VulkanSurfaceDescriptor, WglContextDescriptor,
73};
74pub use resource::{
75    ByteRange, ResourceProviderDecision, ResourceRequest, ResourceRequestHandle, ResourceResponse,
76    ResourceTransformRequest,
77};
78pub use runtime::{
79    OfflineOperationHandle, OfflineRegionDefinition, OfflineRegionInfo, RuntimeHandle,
80    RuntimeOptions, WakeSource,
81};
82pub use values::{
83    EdgeInsets, LatLng, LatLngBounds, ProjectedMeters, Quaternion, ScreenBox, ScreenPoint,
84    UnitBezier, Vec3,
85};
86
87/// Error returned by consuming one-shot handle operations when the handle
88/// remains live and the operation can be retried.
89#[derive(Debug)]
90pub struct HandleOperationError<T> {
91    error: Error,
92    handle: T,
93}
94
95impl<T> HandleOperationError<T> {
96    pub(crate) fn new(error: Error, handle: T) -> Self {
97        Self { error, handle }
98    }
99
100    /// Returns the operation error.
101    pub fn error(&self) -> &Error {
102        &self.error
103    }
104
105    /// Returns the stable category for the operation error.
106    pub fn kind(&self) -> ErrorKind {
107        self.error.kind()
108    }
109
110    /// Returns the raw C status for native operation errors, when available.
111    pub fn raw_status(&self) -> Option<i32> {
112        self.error.raw_status()
113    }
114
115    /// Returns the copied diagnostic message for the operation error.
116    pub fn diagnostic(&self) -> &str {
117        self.error.diagnostic()
118    }
119
120    /// Returns the operation error, dropping the still-live handle.
121    pub fn into_error(self) -> Error {
122        self.error
123    }
124
125    /// Returns the still-live handle so the operation can be retried.
126    pub fn into_handle(self) -> T {
127        self.handle
128    }
129
130    /// Splits this error into the operation error and still-live handle.
131    pub fn into_parts(self) -> (Error, T) {
132        (self.error, self.handle)
133    }
134}
135
136impl<T> std::fmt::Display for HandleOperationError<T> {
137    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138        self.error.fmt(f)
139    }
140}
141
142impl<T: std::fmt::Debug> std::error::Error for HandleOperationError<T> {}
143
144/// Error returned by offline operation result transfers.
145#[derive(Debug)]
146pub enum OfflineOperationTakeError<T> {
147    /// The native transfer failed before consuming the operation result.
148    Retryable(HandleOperationError<T>),
149    /// The native result was consumed, but copying it into Rust-owned data failed.
150    Consumed(Error),
151}
152
153impl<T> OfflineOperationTakeError<T> {
154    pub(crate) fn retryable(error: Error, handle: T) -> Self {
155        Self::Retryable(HandleOperationError::new(error, handle))
156    }
157
158    pub(crate) fn consumed(error: Error) -> Self {
159        Self::Consumed(error)
160    }
161
162    /// Returns the operation error.
163    pub fn error(&self) -> &Error {
164        match self {
165            Self::Retryable(error) => error.error(),
166            Self::Consumed(error) => error,
167        }
168    }
169
170    /// Returns the stable category for the operation error.
171    pub fn kind(&self) -> ErrorKind {
172        self.error().kind()
173    }
174
175    /// Returns the raw C status for native operation errors, when available.
176    pub fn raw_status(&self) -> Option<i32> {
177        self.error().raw_status()
178    }
179
180    /// Returns the copied diagnostic message for the operation error.
181    pub fn diagnostic(&self) -> &str {
182        self.error().diagnostic()
183    }
184
185    /// Returns the retryable error and still-live handle, if the operation was not consumed.
186    pub fn into_retryable(self) -> Option<HandleOperationError<T>> {
187        match self {
188            Self::Retryable(error) => Some(error),
189            Self::Consumed(_) => None,
190        }
191    }
192
193    /// Returns the operation error, dropping any retryable handle.
194    pub fn into_error(self) -> Error {
195        match self {
196            Self::Retryable(error) => error.into_error(),
197            Self::Consumed(error) => error,
198        }
199    }
200}
201
202impl<T> std::fmt::Display for OfflineOperationTakeError<T> {
203    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
204        self.error().fmt(f)
205    }
206}
207
208impl<T: std::fmt::Debug> std::error::Error for OfflineOperationTakeError<T> {}
209
210/// Returns the native C ABI contract version.
211pub fn c_version() -> u32 {
212    // SAFETY: mln_c_version takes no arguments and returns the process-global C
213    // ABI version for the linked native library.
214    unsafe { sys::mln_c_version() }
215}
216
217/// Returns the render backends compiled into the linked native library.
218pub fn supported_render_backends() -> RenderBackendMask {
219    // SAFETY: mln_supported_render_backend_mask takes no arguments and returns a
220    // value mask. Unknown future bits are preserved by from_bits_retain.
221    let mask = unsafe { sys::mln_supported_render_backend_mask() };
222    RenderBackendMask::from_bits_retain(mask)
223}
224
225/// Returns the OpenGL context providers compiled into the linked native library.
226pub fn supported_opengl_context_providers() -> OpenGLContextProviderMask {
227    // SAFETY: mln_opengl_supported_context_provider_mask takes no arguments and
228    // returns a value mask. Unknown future bits are preserved by from_bits_retain.
229    let mask = unsafe { sys::mln_opengl_supported_context_provider_mask() };
230    OpenGLContextProviderMask::from_bits_retain(mask)
231}
232
233/// Converts a geographic coordinate to Spherical Mercator projected meters.
234pub fn projected_meters_for_lat_lng(coordinate: LatLng) -> Result<ProjectedMeters> {
235    let mut raw_meters = sys::mln_projected_meters {
236        northing: 0.0,
237        easting: 0.0,
238    };
239    // SAFETY: coordinate is passed by value. out_meters points to valid
240    // writable storage for one projected-meter value.
241    maplibre_core::check(unsafe {
242        sys::mln_projected_meters_for_lat_lng(coordinate.to_native(), &mut raw_meters)
243    })?;
244    Ok(ProjectedMeters::from_native(raw_meters))
245}
246
247/// Converts Spherical Mercator projected meters to a geographic coordinate.
248pub fn lat_lng_for_projected_meters(meters: ProjectedMeters) -> Result<LatLng> {
249    let mut raw_coordinate = sys::mln_lat_lng {
250        latitude: 0.0,
251        longitude: 0.0,
252    };
253    // SAFETY: meters is passed by value. out_coordinate points to valid
254    // writable storage for one coordinate value.
255    maplibre_core::check(unsafe {
256        sys::mln_lat_lng_for_projected_meters(meters.to_native(), &mut raw_coordinate)
257    })?;
258    Ok(LatLng::from_native(raw_coordinate))
259}
260
261/// Reads MapLibre Native's process-global network status.
262pub fn network_status() -> Result<NetworkStatus> {
263    maplibre_core::network_status()
264}
265
266/// Sets MapLibre Native's process-global network status.
267pub fn set_network_status(status: NetworkStatus) -> Result<()> {
268    maplibre_core::set_network_status(status)
269}
270
271#[cfg(test)]
272fn set_network_status_raw(raw_status: u32) -> Result<()> {
273    maplibre_core::set_network_status_raw(raw_status)
274}
275
276#[cfg(test)]
277mod tests {
278    use static_assertions::assert_not_impl_any;
279
280    use super::*;
281
282    assert_not_impl_any!(RuntimeHandle: Send, Sync);
283    assert_not_impl_any!(MapHandle: Send, Sync);
284    assert_not_impl_any!(MapProjectionHandle: Send, Sync);
285    assert_not_impl_any!(NativePointer: Send, Sync);
286    assert_not_impl_any!(FrameNativePointer<'static>: Send, Sync);
287    assert_not_impl_any!(RenderSessionHandle: Send, Sync);
288
289    #[test]
290    // Spec coverage: BND-103.
291    fn projected_meter_helpers_round_trip() {
292        let coordinate = LatLng::new(45.0, -122.0);
293        let meters = projected_meters_for_lat_lng(coordinate).unwrap();
294        let round_tripped = lat_lng_for_projected_meters(meters).unwrap();
295
296        assert!((round_tripped.latitude - coordinate.latitude).abs() < 1e-9);
297        assert!((round_tripped.longitude - coordinate.longitude).abs() < 1e-9);
298    }
299
300    #[test]
301    // Spec coverage: BND-020.
302    fn invalid_network_status_reports_public_error() {
303        let error = set_network_status_raw(999_999).unwrap_err();
304
305        assert_eq!(error.kind(), ErrorKind::InvalidArgument);
306        assert_eq!(error.raw_status(), Some(sys::MLN_STATUS_INVALID_ARGUMENT));
307        assert!(error.diagnostic().contains("network status"));
308    }
309
310    #[test]
311    // Spec coverage: BND-025 and BND-068.
312    fn unknown_network_status_is_rejected_before_calling_c() {
313        let error = set_network_status(NetworkStatus::Unknown(999_999)).unwrap_err();
314
315        assert_eq!(error.kind(), ErrorKind::InvalidArgument);
316        assert_eq!(error.raw_status(), None);
317        assert!(error.diagnostic().contains("cannot be set"));
318    }
319}