Skip to main content

maplibre_native_ffi/map/
style.rs

1use std::ptr;
2
3pub(crate) use maplibre_core::style::{
4    GeoJsonSourceOptionsNativeExt, NativeGeoJsonSourceOptions, NativeStyleImageOptions,
5    NativeTileSourceOptions, NativeTileUrls, StyleImageOptionsNativeExt,
6    TileSourceOptionsNativeExt,
7};
8pub use maplibre_core::{
9    GeoJsonSourceOptions, ImageContent, ImageStretch, LocationIndicatorImageKind,
10    RasterDemEncoding, SourceInfo, SourceType, StyleImage, StyleImageInfo, StyleImageOptions,
11    StyleImageTextFit, StyleLayerInfo, StyleLayerVisibility, StyleTransitionOptions, TileJsonInfo,
12    TileScheme, TileSourceOptions, VectorTileEncoding,
13};
14use maplibre_native_ffi_core as maplibre_core;
15use maplibre_native_ffi_core::ptr::const_ptr_or_null;
16use maplibre_native_ffi_core::query::FeatureStateSelectorNativeExt;
17use maplibre_native_ffi_core::values::lat_lngs_to_native;
18use maplibre_native_ffi_sys as sys;
19
20use crate::custom_geometry::{CanonicalTileId, CustomGeometrySourceState};
21use crate::custom_mvt_vector::CustomMvtVectorSourceState;
22use crate::render::PremultipliedRgba8Image;
23use crate::values::NativeValue;
24use crate::{
25    CustomGeometrySourceOptions, CustomMvtVectorSourceOptions, Error, ErrorKind,
26    FeatureStateSelector, LatLng, LatLngBounds, Result,
27};
28
29impl super::MapHandle {
30    /// Loads a style URL through MapLibre Native style APIs.
31    ///
32    /// Loading is asynchronous: a style that fails to fetch or parse still
33    /// returns `Ok` here and reports through a later loading-failed runtime
34    /// event. Watch the event stream for the load outcome.
35    pub fn set_style_url(&self, url: &str) -> Result<()> {
36        let map = self.inner.native()?;
37        let url = maplibre_core::string::c_string(url)?;
38        // SAFETY: map is live and url is a NUL-terminated UTF-8 string the C
39        // API consumes before returning.
40        maplibre_core::check(unsafe { sys::mln_map_set_style_url(map, url.as_ptr()) })?;
41        Ok(())
42    }
43
44    /// Loads inline style JSON through MapLibre Native style APIs.
45    ///
46    /// A parse failure is reported twice: this call returns the error, and the
47    /// same message arrives as a loading-failed runtime event.
48    pub fn set_style_json(&self, json: &[u8]) -> Result<()> {
49        let map = self.inner.native()?;
50        let json = maplibre_core::string::buffer_view(json);
51        // SAFETY: map is live and json is valid for the call. Style replacement
52        // completes before a successful return, so the C API has already
53        // released the callback state of the sources this load dropped.
54        maplibre_core::check(unsafe { sys::mln_map_set_style_json(map, json) })
55    }
56
57    /// Sets a global-state JSON value. JSON null restores its style default.
58    pub fn set_global_state_property(&self, property_name: &str, value: &[u8]) -> Result<()> {
59        let map = self.inner.native()?;
60        let property_name = maplibre_core::string::string_view(property_name);
61        let value = maplibre_core::string::buffer_view(value);
62        // SAFETY: map is live, and property_name and value remain valid for this call.
63        maplibre_core::check(unsafe {
64            sys::mln_map_set_global_state_property(map, property_name.raw(), value)
65        })
66    }
67
68    /// Copies the current global-state JSON object, including defaults.
69    pub fn get_global_state(&self) -> Result<Vec<u8>> {
70        let map = self.inner.native()?;
71        let mut out = maplibre_core::ptr::OutHandle::<sys::mln_buffer>::new();
72        // SAFETY: map is live and out is a null-initialized writable handle.
73        maplibre_core::check(unsafe { sys::mln_map_get_global_state(map, out.as_mut_ptr()) })?;
74        // SAFETY: Success transfers the owned buffer to this call.
75        unsafe { maplibre_core::string::copy_owned_buffer(out.get()) }
76    }
77
78    /// Sets per-feature state on this map.
79    pub fn set_feature_state(&self, selector: &FeatureStateSelector, state: &[u8]) -> Result<()> {
80        let map = self.inner.native()?;
81        let selector = selector.to_native();
82        let state = maplibre_core::string::buffer_view(state);
83        // SAFETY: map is live and all borrowed storage remains valid for the call.
84        maplibre_core::check(unsafe {
85            sys::mln_map_set_feature_state(map, selector.as_ptr(), state)
86        })
87    }
88
89    /// Copies per-feature state from this map.
90    pub fn get_feature_state(&self, selector: &FeatureStateSelector) -> Result<Vec<u8>> {
91        let map = self.inner.native()?;
92        let selector = selector.to_native();
93        let mut out = maplibre_core::ptr::OutHandle::<sys::mln_buffer>::new();
94        // SAFETY: map is live, selector storage remains valid, and out is writable.
95        maplibre_core::check(unsafe {
96            sys::mln_map_get_feature_state(map, selector.as_ptr(), out.as_mut_ptr())
97        })?;
98        // SAFETY: Success transfers the owned buffer to this call.
99        unsafe { maplibre_core::string::copy_owned_buffer(out.get()) }
100    }
101
102    /// Removes per-feature state selected on this map.
103    pub fn remove_feature_state(&self, selector: &FeatureStateSelector) -> Result<()> {
104        let map = self.inner.native()?;
105        let selector = selector.to_native();
106        // SAFETY: map is live and selector storage remains valid for the call.
107        maplibre_core::check(unsafe { sys::mln_map_remove_feature_state(map, selector.as_ptr()) })
108    }
109
110    /// Copies the style document this map's style was last parsed from: the
111    /// string given to [`Self::set_style_json`] or the body fetched for
112    /// [`Self::set_style_url`], byte for byte. Runtime mutations do not change
113    /// it. An empty buffer means no document has been parsed.
114    pub fn loaded_style_json(&self) -> Result<Vec<u8>> {
115        let map = self.inner.native()?;
116        // SAFETY: map is live, and each call writes only through the pointers
117        // it is given.
118        unsafe {
119            copy_bytes(|text, capacity, out_size| {
120                sys::mln_map_copy_loaded_style_json(map, text, capacity, out_size)
121            })
122        }
123    }
124
125    /// Copies the URL this map's style was last requested from.
126    ///
127    /// [`Self::set_style_url`] records the URL when the request is made, before
128    /// the response arrives, and [`Self::set_style_json`] clears it, so this can
129    /// disagree with [`Self::loaded_style_json`] while a load is in flight. An
130    /// empty string means no URL bytes are available.
131    pub fn style_url(&self) -> Result<String> {
132        let map = self.inner.native()?;
133        // SAFETY: map is live, and each call writes only through the pointers
134        // it is given.
135        unsafe {
136            copy_text(|url, capacity, out_size| {
137                sys::mln_map_copy_style_url(map, url, capacity, out_size)
138            })
139        }
140    }
141
142    /// Adds a custom geometry source to the current style.
143    ///
144    /// The callback state is scoped to this map's current style. The C API
145    /// frees it once it stops referencing it, whether the source is removed,
146    /// dropped by a style load, or retired with the map. Native may invoke
147    /// callbacks from worker threads, so queue owner-thread work before calling
148    /// map APIs.
149    pub fn add_custom_geometry_source(
150        &self,
151        source_id: &str,
152        options: CustomGeometrySourceOptions,
153    ) -> Result<()> {
154        let map = self.inner.native()?;
155        let source_id_view = maplibre_core::string::string_view(source_id);
156        let state = CustomGeometrySourceState::new(options);
157        let descriptor = state.descriptor();
158        // The descriptor's release callback frees this box, so the C API owns
159        // the callback state from a successful add onwards.
160        let state = Box::into_raw(state);
161        // SAFETY: map is live, source_id_view is valid for this call, and
162        // descriptor names callback state that lives until the release callback.
163        let status = unsafe {
164            sys::mln_map_add_custom_geometry_source(map, source_id_view.raw(), &descriptor)
165        };
166        if let Err(error) = maplibre_core::check(status) {
167            // SAFETY: A rejected add releases nothing, so this box is still
168            // this call's to free.
169            drop(unsafe { Box::from_raw(state) });
170            return Err(error);
171        }
172        Ok(())
173    }
174
175    /// Sets custom geometry source data for one canonical tile.
176    pub fn set_custom_geometry_source_tile_data(
177        &self,
178        source_id: &str,
179        tile_id: CanonicalTileId,
180        data: &[u8],
181    ) -> Result<()> {
182        let map = self.inner.native()?;
183        let source_id = maplibre_core::string::string_view(source_id);
184        let data = maplibre_core::string::buffer_view(data);
185        // SAFETY: map is live, source_id is valid for this call, tile_id is
186        // passed by value, and data remains valid for this call.
187        maplibre_core::check(unsafe {
188            sys::mln_map_set_custom_geometry_source_tile_data(
189                map,
190                source_id.raw(),
191                tile_id.to_native(),
192                data,
193            )
194        })
195    }
196
197    /// Invalidates custom geometry source data for one canonical tile.
198    pub fn invalidate_custom_geometry_source_tile(
199        &self,
200        source_id: &str,
201        tile_id: CanonicalTileId,
202    ) -> Result<()> {
203        let map = self.inner.native()?;
204        let source_id = maplibre_core::string::string_view(source_id);
205        // SAFETY: map is live, source_id is valid for this call, and tile_id is
206        // passed by value.
207        maplibre_core::check(unsafe {
208            sys::mln_map_invalidate_custom_geometry_source_tile(
209                map,
210                source_id.raw(),
211                tile_id.to_native(),
212            )
213        })
214    }
215
216    /// Invalidates custom geometry source data inside a geographic region.
217    pub fn invalidate_custom_geometry_source_region(
218        &self,
219        source_id: &str,
220        bounds: LatLngBounds,
221    ) -> Result<()> {
222        let map = self.inner.native()?;
223        let source_id = maplibre_core::string::string_view(source_id);
224        // SAFETY: map is live, source_id is valid for this call, and bounds is
225        // passed by value.
226        maplibre_core::check(unsafe {
227            sys::mln_map_invalidate_custom_geometry_source_region(
228                map,
229                source_id.raw(),
230                bounds.to_native(),
231            )
232        })
233    }
234
235    /// Adds a custom MVT vector source to the current style.
236    ///
237    /// The callback state is scoped to this map's current style. The C API
238    /// frees it once it stops referencing it, whether the source is removed,
239    /// dropped by a style load, or retired with the map. Native may invoke
240    /// callbacks from worker threads, so queue owner-thread work before calling
241    /// map APIs.
242    pub fn add_custom_mvt_vector_source(
243        &self,
244        source_id: &str,
245        options: CustomMvtVectorSourceOptions,
246    ) -> Result<()> {
247        let map = self.inner.native()?;
248        let source_id_view = maplibre_core::string::string_view(source_id);
249        let state = CustomMvtVectorSourceState::new(options);
250        let descriptor = state.descriptor();
251        // The descriptor's release callback frees this box, so the C API owns
252        // the callback state from a successful add onwards.
253        let state = Box::into_raw(state);
254        // SAFETY: map is live, source_id_view is valid for this call, and
255        // descriptor names callback state that lives until the release callback.
256        let status = unsafe {
257            sys::mln_map_add_custom_mvt_vector_source(map, source_id_view.raw(), &descriptor)
258        };
259        if let Err(error) = maplibre_core::check(status) {
260            // SAFETY: A rejected add releases nothing, so this box is still
261            // this call's to free.
262            drop(unsafe { Box::from_raw(state) });
263            return Err(error);
264        }
265        Ok(())
266    }
267
268    /// Sets custom MVT vector source data for one canonical tile.
269    ///
270    /// Pass an empty slice for an empty tile. Native ignores the bytes when
271    /// that tile is not awaiting a response after fetch.
272    pub fn set_custom_mvt_vector_source_tile_data(
273        &self,
274        source_id: &str,
275        tile_id: CanonicalTileId,
276        data: &[u8],
277    ) -> Result<()> {
278        let map = self.inner.native()?;
279        let source_id = maplibre_core::string::string_view(source_id);
280        let data = maplibre_core::string::buffer_view(data);
281        // SAFETY: map is live, source_id is valid for this call, tile_id is
282        // passed by value, and data remains valid for this call.
283        maplibre_core::check(unsafe {
284            sys::mln_map_set_custom_mvt_vector_source_tile_data(
285                map,
286                source_id.raw(),
287                tile_id.to_native(),
288                data,
289            )
290        })
291    }
292
293    /// Reports a custom MVT vector source error for one canonical tile.
294    pub fn set_custom_mvt_vector_source_tile_error(
295        &self,
296        source_id: &str,
297        tile_id: CanonicalTileId,
298        message: &str,
299    ) -> Result<()> {
300        let map = self.inner.native()?;
301        let source_id = maplibre_core::string::string_view(source_id);
302        let message = maplibre_core::string::string_view(message);
303        // SAFETY: map is live, source_id and message are valid for this call,
304        // and tile_id is passed by value.
305        maplibre_core::check(unsafe {
306            sys::mln_map_set_custom_mvt_vector_source_tile_error(
307                map,
308                source_id.raw(),
309                tile_id.to_native(),
310                message.raw(),
311            )
312        })
313    }
314
315    /// Invalidates custom MVT vector source data for one canonical tile.
316    pub fn invalidate_custom_mvt_vector_source_tile(
317        &self,
318        source_id: &str,
319        tile_id: CanonicalTileId,
320    ) -> Result<()> {
321        let map = self.inner.native()?;
322        let source_id = maplibre_core::string::string_view(source_id);
323        // SAFETY: map is live, source_id is valid for this call, and tile_id is
324        // passed by value.
325        maplibre_core::check(unsafe {
326            sys::mln_map_invalidate_custom_mvt_vector_source_tile(
327                map,
328                source_id.raw(),
329                tile_id.to_native(),
330            )
331        })
332    }
333
334    /// Adds one style source from a style-spec source JSON object.
335    pub fn add_style_source_json(&self, source_id: &str, source_json: &[u8]) -> Result<()> {
336        let map = self.inner.native()?;
337        let source_id = maplibre_core::string::string_view(source_id);
338        let source_json = maplibre_core::string::buffer_view(source_json);
339        // SAFETY: map is live, source_id and source_json are explicit-length
340        // views valid for this call.
341        maplibre_core::check(unsafe {
342            sys::mln_map_add_style_source_json(map, source_id.raw(), source_json)
343        })
344    }
345
346    /// Adds a vector source with a TileJSON URL.
347    pub fn add_vector_source_url(
348        &self,
349        source_id: &str,
350        url: &str,
351        options: Option<&TileSourceOptions>,
352    ) -> Result<()> {
353        let map = self.inner.native()?;
354        let source_id = maplibre_core::string::string_view(source_id);
355        let url = maplibre_core::string::string_view(url);
356        let options = options.map(TileSourceOptions::to_native);
357        let options_ptr = options
358            .as_ref()
359            .map_or(ptr::null(), NativeTileSourceOptions::as_ptr);
360        // SAFETY: map is live, source_id and url are valid for this call, and
361        // options_ptr is null or points to call-scoped native options.
362        maplibre_core::check(unsafe {
363            sys::mln_map_add_vector_source_url(map, source_id.raw(), url.raw(), options_ptr)
364        })
365    }
366
367    /// Adds a vector source with inline tile URLs.
368    pub fn add_vector_source_tiles<S: AsRef<str>>(
369        &self,
370        source_id: &str,
371        tiles: &[S],
372        options: Option<&TileSourceOptions>,
373    ) -> Result<()> {
374        let map = self.inner.native()?;
375        let source_id = maplibre_core::string::string_view(source_id);
376        let raw_tiles = NativeTileUrls::new(tiles);
377        let options = options.map(TileSourceOptions::to_native);
378        let options_ptr = options
379            .as_ref()
380            .map_or(ptr::null(), NativeTileSourceOptions::as_ptr);
381        // SAFETY: map is live, source_id is valid for this call, raw_tiles
382        // points to call-scoped string views, and options_ptr is null or points
383        // to call-scoped native options.
384        maplibre_core::check(unsafe {
385            sys::mln_map_add_vector_source_tiles(
386                map,
387                source_id.raw(),
388                raw_tiles.as_ptr(),
389                raw_tiles.len(),
390                options_ptr,
391            )
392        })
393    }
394
395    /// Adds a raster source with a TileJSON URL.
396    pub fn add_raster_source_url(
397        &self,
398        source_id: &str,
399        url: &str,
400        options: Option<&TileSourceOptions>,
401    ) -> Result<()> {
402        let map = self.inner.native()?;
403        let source_id = maplibre_core::string::string_view(source_id);
404        let url = maplibre_core::string::string_view(url);
405        let options = options.map(TileSourceOptions::to_native);
406        let options_ptr = options
407            .as_ref()
408            .map_or(ptr::null(), NativeTileSourceOptions::as_ptr);
409        // SAFETY: map is live, source_id and url are valid for this call, and
410        // options_ptr is null or points to call-scoped native options.
411        maplibre_core::check(unsafe {
412            sys::mln_map_add_raster_source_url(map, source_id.raw(), url.raw(), options_ptr)
413        })
414    }
415
416    /// Adds a raster source with inline tile URLs.
417    pub fn add_raster_source_tiles<S: AsRef<str>>(
418        &self,
419        source_id: &str,
420        tiles: &[S],
421        options: Option<&TileSourceOptions>,
422    ) -> Result<()> {
423        let map = self.inner.native()?;
424        let source_id = maplibre_core::string::string_view(source_id);
425        let raw_tiles = NativeTileUrls::new(tiles);
426        let options = options.map(TileSourceOptions::to_native);
427        let options_ptr = options
428            .as_ref()
429            .map_or(ptr::null(), NativeTileSourceOptions::as_ptr);
430        // SAFETY: map is live, source_id is valid for this call, raw_tiles
431        // points to call-scoped string views, and options_ptr is null or points
432        // to call-scoped native options.
433        maplibre_core::check(unsafe {
434            sys::mln_map_add_raster_source_tiles(
435                map,
436                source_id.raw(),
437                raw_tiles.as_ptr(),
438                raw_tiles.len(),
439                options_ptr,
440            )
441        })
442    }
443
444    /// Adds a raster DEM source with a TileJSON URL.
445    pub fn add_raster_dem_source_url(
446        &self,
447        source_id: &str,
448        url: &str,
449        options: Option<&TileSourceOptions>,
450    ) -> Result<()> {
451        let map = self.inner.native()?;
452        let source_id = maplibre_core::string::string_view(source_id);
453        let url = maplibre_core::string::string_view(url);
454        let options = options.map(TileSourceOptions::to_native);
455        let options_ptr = options
456            .as_ref()
457            .map_or(ptr::null(), NativeTileSourceOptions::as_ptr);
458        // SAFETY: map is live, source_id and url are valid for this call, and
459        // options_ptr is null or points to call-scoped native options.
460        maplibre_core::check(unsafe {
461            sys::mln_map_add_raster_dem_source_url(map, source_id.raw(), url.raw(), options_ptr)
462        })
463    }
464
465    /// Adds a raster DEM source with inline tile URLs.
466    pub fn add_raster_dem_source_tiles<S: AsRef<str>>(
467        &self,
468        source_id: &str,
469        tiles: &[S],
470        options: Option<&TileSourceOptions>,
471    ) -> Result<()> {
472        let map = self.inner.native()?;
473        let source_id = maplibre_core::string::string_view(source_id);
474        let raw_tiles = NativeTileUrls::new(tiles);
475        let options = options.map(TileSourceOptions::to_native);
476        let options_ptr = options
477            .as_ref()
478            .map_or(ptr::null(), NativeTileSourceOptions::as_ptr);
479        // SAFETY: map is live, source_id is valid for this call, raw_tiles
480        // points to call-scoped string views, and options_ptr is null or points
481        // to call-scoped native options.
482        maplibre_core::check(unsafe {
483            sys::mln_map_add_raster_dem_source_tiles(
484                map,
485                source_id.raw(),
486                raw_tiles.as_ptr(),
487                raw_tiles.len(),
488                options_ptr,
489            )
490        })
491    }
492
493    /// Adds an image source that loads its image from a URL.
494    ///
495    /// Coordinates are borrowed for the call and copied by native on success.
496    /// The array entries are in top-left, top-right, bottom-right, bottom-left
497    /// order.
498    pub fn add_image_source_url(
499        &self,
500        source_id: &str,
501        coordinates: &[LatLng; 4],
502        url: &str,
503    ) -> Result<()> {
504        let map = self.inner.native()?;
505        let source_id = maplibre_core::string::string_view(source_id);
506        let coordinates = lat_lngs_to_native(coordinates);
507        let url = maplibre_core::string::string_view(url);
508        // SAFETY: map is live, source_id and url are explicit-length views
509        // valid for this call, and coordinates points to call-scoped native
510        // coordinate storage. Native validates coordinate contents.
511        maplibre_core::check(unsafe {
512            sys::mln_map_add_image_source_url(
513                map,
514                source_id.raw(),
515                const_ptr_or_null(&coordinates),
516                coordinates.len(),
517                url.raw(),
518            )
519        })
520    }
521
522    /// Adds an image source with inline premultiplied RGBA8 pixels.
523    ///
524    /// Coordinates and image pixels are borrowed for the call and copied by
525    /// native on success. Coordinate entries are in top-left, top-right,
526    /// bottom-right, bottom-left order.
527    pub fn add_image_source_image(
528        &self,
529        source_id: &str,
530        coordinates: &[LatLng; 4],
531        image: &PremultipliedRgba8Image,
532    ) -> Result<()> {
533        let map = self.inner.native()?;
534        let source_id = maplibre_core::string::string_view(source_id);
535        let coordinates = lat_lngs_to_native(coordinates);
536        let image = maplibre_core::values::premultiplied_rgba8_image_to_native(image);
537        // SAFETY: map is live, source_id is an explicit-length view valid for
538        // this call, coordinates points to call-scoped native coordinate
539        // storage, and image points into the borrowed Rust image for this call.
540        maplibre_core::check(unsafe {
541            sys::mln_map_add_image_source_image(
542                map,
543                source_id.raw(),
544                const_ptr_or_null(&coordinates),
545                coordinates.len(),
546                &image,
547            )
548        })
549    }
550
551    /// Updates an image source to load its image from a URL.
552    pub fn set_image_source_url(&self, source_id: &str, url: &str) -> Result<()> {
553        let map = self.inner.native()?;
554        let source_id = maplibre_core::string::string_view(source_id);
555        let url = maplibre_core::string::string_view(url);
556        // SAFETY: map is live, and source_id and url are explicit-length views
557        // valid for this call.
558        maplibre_core::check(unsafe {
559            sys::mln_map_set_image_source_url(map, source_id.raw(), url.raw())
560        })
561    }
562
563    /// Updates an image source with inline premultiplied RGBA8 pixels.
564    pub fn set_image_source_image(
565        &self,
566        source_id: &str,
567        image: &PremultipliedRgba8Image,
568    ) -> Result<()> {
569        let map = self.inner.native()?;
570        let source_id = maplibre_core::string::string_view(source_id);
571        let image = maplibre_core::values::premultiplied_rgba8_image_to_native(image);
572        // SAFETY: map is live, source_id is an explicit-length view valid for
573        // this call, and image points into the borrowed Rust image for this call.
574        maplibre_core::check(unsafe {
575            sys::mln_map_set_image_source_image(map, source_id.raw(), &image)
576        })
577    }
578
579    /// Updates image source coordinates.
580    ///
581    /// Coordinates are borrowed for the call and copied by native on success.
582    /// The array entries are in top-left, top-right, bottom-right, bottom-left
583    /// order.
584    pub fn set_image_source_coordinates(
585        &self,
586        source_id: &str,
587        coordinates: &[LatLng; 4],
588    ) -> Result<()> {
589        let map = self.inner.native()?;
590        let source_id = maplibre_core::string::string_view(source_id);
591        let coordinates = lat_lngs_to_native(coordinates);
592        // SAFETY: map is live, source_id is an explicit-length view valid for
593        // this call, and coordinates points to call-scoped native coordinate
594        // storage. Native validates coordinate contents.
595        maplibre_core::check(unsafe {
596            sys::mln_map_set_image_source_coordinates(
597                map,
598                source_id.raw(),
599                const_ptr_or_null(&coordinates),
600                coordinates.len(),
601            )
602        })
603    }
604
605    /// Copies image source coordinates into owned Rust values.
606    pub fn image_source_coordinates(&self, source_id: &str) -> Result<Option<[LatLng; 4]>> {
607        let map = self.inner.native()?;
608        let source_id = maplibre_core::string::string_view(source_id);
609        let mut coordinates = [sys::mln_lat_lng {
610            latitude: 0.0,
611            longitude: 0.0,
612        }; 4];
613        let mut coordinate_count = 0;
614        let mut found = false;
615        // SAFETY: map is live, source_id is an explicit-length view valid for
616        // this call, coordinates has capacity for four native coordinates, and
617        // output pointers refer to writable storage.
618        maplibre_core::check(unsafe {
619            sys::mln_map_get_image_source_coordinates(
620                map,
621                source_id.raw(),
622                coordinates.as_mut_ptr(),
623                coordinates.len(),
624                &mut coordinate_count,
625                &mut found,
626            )
627        })?;
628        if !found {
629            return Ok(None);
630        }
631        if coordinate_count != coordinates.len() {
632            return Err(Error::new(
633                ErrorKind::NativeError,
634                None,
635                "native image source coordinate count did not match Rust image source invariant",
636            ));
637        }
638        Ok(Some(coordinates.map(LatLng::from_native)))
639    }
640
641    /// Removes one style source by ID.
642    ///
643    /// Returns whether a source existed and was removed. Native returns an
644    /// error when a layer still uses the source.
645    pub fn remove_style_source(&self, source_id: &str) -> Result<bool> {
646        let map = self.inner.native()?;
647        let source_id = maplibre_core::string::string_view(source_id);
648        let mut removed = false;
649        // SAFETY: map is live, source_id is an explicit-length view valid for
650        // this call, and removed points to writable storage.
651        maplibre_core::check(unsafe {
652            sys::mln_map_remove_style_source(map, source_id.raw(), &mut removed)
653        })?;
654        Ok(removed)
655    }
656
657    /// Reports whether a style source ID exists.
658    pub fn style_source_exists(&self, source_id: &str) -> Result<bool> {
659        let map = self.inner.native()?;
660        let source_id = maplibre_core::string::string_view(source_id);
661        let mut exists = false;
662        // SAFETY: map is live, source_id is an explicit-length view valid for
663        // this call, and exists points to writable storage.
664        maplibre_core::check(unsafe {
665            sys::mln_map_style_source_exists(map, source_id.raw(), &mut exists)
666        })?;
667        Ok(exists)
668    }
669
670    /// Adds or replaces one runtime style image.
671    pub fn set_style_image(
672        &self,
673        image_id: &str,
674        image: &PremultipliedRgba8Image,
675        options: Option<&StyleImageOptions>,
676    ) -> Result<()> {
677        let map = self.inner.native()?;
678        let image_id = maplibre_core::string::string_view(image_id);
679        let image = maplibre_core::values::premultiplied_rgba8_image_to_native(image);
680        let options = options.map(StyleImageOptions::to_native);
681        let options_ptr = options
682            .as_ref()
683            .map_or(ptr::null(), NativeStyleImageOptions::as_ptr);
684        // SAFETY: map is live, image_id is an explicit-length view valid for
685        // this call, image points into the borrowed Rust image for this call,
686        // and options_ptr is either null or points to call-scoped options.
687        maplibre_core::check(unsafe {
688            sys::mln_map_set_style_image(map, image_id.raw(), &image, options_ptr)
689        })
690    }
691
692    /// Removes one runtime style image by ID.
693    ///
694    /// Returns whether an image existed and was removed.
695    pub fn remove_style_image(&self, image_id: &str) -> Result<bool> {
696        let map = self.inner.native()?;
697        let image_id = maplibre_core::string::string_view(image_id);
698        let mut removed = false;
699        // SAFETY: map is live, image_id is an explicit-length view valid for
700        // this call, and removed points to writable storage.
701        maplibre_core::check(unsafe {
702            sys::mln_map_remove_style_image(map, image_id.raw(), &mut removed)
703        })?;
704        Ok(removed)
705    }
706
707    /// Reports whether a runtime style image ID exists.
708    pub fn style_image_exists(&self, image_id: &str) -> Result<bool> {
709        let map = self.inner.native()?;
710        let image_id = maplibre_core::string::string_view(image_id);
711        let mut exists = false;
712        // SAFETY: map is live, image_id is an explicit-length view valid for
713        // this call, and exists points to writable storage.
714        maplibre_core::check(unsafe {
715            sys::mln_map_style_image_exists(map, image_id.raw(), &mut exists)
716        })?;
717        Ok(exists)
718    }
719
720    /// Copies fixed metadata for one runtime style image.
721    pub fn style_image_info(&self, image_id: &str) -> Result<Option<StyleImageInfo>> {
722        let map = self.inner.native()?;
723        let image_id = maplibre_core::string::string_view(image_id);
724        let mut info = maplibre_core::style::empty_style_image_info();
725        let mut found = false;
726        // SAFETY: map is live, image_id is an explicit-length view valid for
727        // this call, info has its ABI size initialized, and found points to
728        // writable storage.
729        maplibre_core::check(unsafe {
730            sys::mln_map_get_style_image_info(map, image_id.raw(), &mut info, &mut found)
731        })?;
732        Ok(found.then(|| maplibre_core::values::style_image_info_from_native(&info)))
733    }
734
735    /// Copies one runtime style image into owned tightly packed premultiplied RGBA8 pixels.
736    pub fn copy_style_image_premultiplied_rgba8(
737        &self,
738        image_id: &str,
739    ) -> Result<Option<StyleImage>> {
740        let map = self.inner.native()?;
741        let image_id = maplibre_core::string::string_view(image_id);
742        let mut raw_info = maplibre_core::style::empty_style_image_info();
743        let mut info_found = false;
744        // SAFETY: map is live, image_id is an explicit-length view valid for
745        // this call, raw_info has its ABI size initialized, and info_found
746        // points to writable storage.
747        maplibre_core::check(unsafe {
748            sys::mln_map_get_style_image_info(map, image_id.raw(), &mut raw_info, &mut info_found)
749        })?;
750        if !info_found {
751            return Ok(None);
752        }
753        let info = maplibre_core::values::style_image_info_from_native(&raw_info);
754
755        let mut data = vec![0u8; info.byte_length];
756        let mut copied_size = 0;
757        let mut found = false;
758        let pixels = if data.is_empty() {
759            ptr::null_mut()
760        } else {
761            data.as_mut_ptr()
762        };
763        // SAFETY: map is live, image_id remains valid for this call, data is
764        // writable for info.byte_length bytes (or null with zero capacity), and
765        // output pointers refer to writable storage.
766        maplibre_core::check(unsafe {
767            sys::mln_map_copy_style_image_premultiplied_rgba8(
768                map,
769                image_id.raw(),
770                pixels,
771                data.len(),
772                &mut copied_size,
773                &mut found,
774            )
775        })?;
776        if !found {
777            return Ok(None);
778        }
779        maplibre_core::style::style_image_from_copied_premultiplied_rgba8(info, data, copied_size)
780            .map(Some)
781    }
782
783    /// Gets one style source type.
784    pub fn style_source_type(&self, source_id: &str) -> Result<Option<SourceType>> {
785        let map = self.inner.native()?;
786        let source_id = maplibre_core::string::string_view(source_id);
787        let mut raw_source_type = sys::MLN_STYLE_SOURCE_TYPE_UNKNOWN;
788        let mut found = false;
789        // SAFETY: map is live, source_id is an explicit-length view valid for
790        // this call, and output pointers refer to writable storage.
791        maplibre_core::check(unsafe {
792            sys::mln_map_get_style_source_type(
793                map,
794                source_id.raw(),
795                &mut raw_source_type,
796                &mut found,
797            )
798        })?;
799        Ok(found.then(|| SourceType::from_raw(raw_source_type)))
800    }
801
802    /// Copies retained metadata for one style source.
803    pub fn style_source_info(&self, source_id: &str) -> Result<Option<SourceInfo>> {
804        let map = self.inner.native()?;
805        let source_id = maplibre_core::string::string_view(source_id);
806        let mut info = maplibre_core::style::empty_style_source_info();
807        let mut found = false;
808        // SAFETY: map is live, source_id is an explicit-length view valid for
809        // this call, info has its ABI size initialized, and found points to
810        // writable storage.
811        maplibre_core::check(unsafe {
812            sys::mln_map_get_style_source_info(map, source_id.raw(), &mut info, &mut found)
813        })?;
814        if !found {
815            return Ok(None);
816        }
817
818        let attribution = if info.has_attribution {
819            match self.copy_style_source_attribution(map, source_id.raw(), info.attribution_size)? {
820                Some(attribution) => Some(attribution),
821                None => return Ok(None),
822            }
823        } else {
824            None
825        };
826
827        let url = if info.fields & sys::MLN_STYLE_SOURCE_INFO_URL != 0 {
828            match self.copy_style_source_url(map, source_id.raw(), info.url_size)? {
829                Some(url) => Some(url),
830                None => return Ok(None),
831            }
832        } else {
833            None
834        };
835
836        let tiles = if info.fields & sys::MLN_STYLE_SOURCE_INFO_TILEJSON != 0 {
837            match self.copy_style_source_tile_urls(map, source_id.raw())? {
838                Some(tiles) => tiles,
839                None => return Ok(None),
840            }
841        } else {
842            Vec::new()
843        };
844
845        Ok(Some(maplibre_core::style::style_source_info_from_native(
846            &info,
847            attribution,
848            url,
849            tiles,
850        )))
851    }
852
853    /// Sets whether a style source stores fetched tiles in persistent storage.
854    ///
855    /// When `is_volatile` is true, source implementations that fetch tiles do
856    /// not store fetched tiles in persistent storage. Other source types retain
857    /// the value for inspection without changing their loading behavior.
858    pub fn set_style_source_volatile(&self, source_id: &str, is_volatile: bool) -> Result<()> {
859        let map = self.inner.native()?;
860        let source_id = maplibre_core::string::string_view(source_id);
861        // SAFETY: map is live and source_id is an explicit-length view valid
862        // for this call.
863        maplibre_core::check(unsafe {
864            sys::mln_map_set_style_source_volatile(map, source_id.raw(), is_volatile)
865        })
866    }
867
868    fn copy_style_source_attribution(
869        &self,
870        map: sys::mln_map,
871        source_id: sys::mln_buffer_view,
872        attribution_size: usize,
873    ) -> Result<Option<String>> {
874        if attribution_size == 0 {
875            let mut copied_size = 0;
876            let mut found = false;
877            // SAFETY: map is live, source_id remains valid for this call,
878            // capacity is zero so the output buffer may be null, and output
879            // pointers refer to writable storage.
880            maplibre_core::check(unsafe {
881                sys::mln_map_copy_style_source_attribution(
882                    map,
883                    source_id,
884                    ptr::null_mut(),
885                    0,
886                    &mut copied_size,
887                    &mut found,
888                )
889            })?;
890            return Ok(found.then(String::new));
891        }
892
893        let mut buffer = vec![0u8; attribution_size];
894        let mut copied_size = 0;
895        let mut found = false;
896        // SAFETY: map is live, source_id remains valid for this call, buffer is
897        // writable for attribution_size bytes, and output pointers refer to
898        // writable storage.
899        maplibre_core::check(unsafe {
900            sys::mln_map_copy_style_source_attribution(
901                map,
902                source_id,
903                buffer.as_mut_ptr().cast(),
904                buffer.len(),
905                &mut copied_size,
906                &mut found,
907            )
908        })?;
909        if !found {
910            return Ok(None);
911        }
912        if copied_size > buffer.len() {
913            return Err(Error::new(
914                ErrorKind::NativeError,
915                None,
916                "native style source attribution size exceeded caller buffer",
917            ));
918        }
919        buffer.truncate(copied_size);
920        String::from_utf8(buffer).map(Some).map_err(|error| {
921            Error::invalid_argument(format!(
922                "native style source attribution was not valid UTF-8: {error}"
923            ))
924        })
925    }
926
927    fn copy_style_source_url(
928        &self,
929        map: sys::mln_map,
930        source_id: sys::mln_buffer_view,
931        url_size: usize,
932    ) -> Result<Option<String>> {
933        let mut buffer = vec![0u8; url_size];
934        let mut copied_size = 0;
935        let mut found = false;
936        // SAFETY: map and source_id remain live for this call, the buffer is
937        // writable for url_size bytes or null-equivalent when empty, and the
938        // output pointers refer to writable storage.
939        maplibre_core::check(unsafe {
940            sys::mln_map_copy_style_source_url(
941                map,
942                source_id,
943                if buffer.is_empty() {
944                    ptr::null_mut()
945                } else {
946                    buffer.as_mut_ptr().cast()
947                },
948                buffer.len(),
949                &mut copied_size,
950                &mut found,
951            )
952        })?;
953        if !found {
954            return Ok(None);
955        }
956        if copied_size > buffer.len() {
957            return Err(Error::new(
958                ErrorKind::NativeError,
959                None,
960                "native style source URL size exceeded caller buffer",
961            ));
962        }
963        buffer.truncate(copied_size);
964        String::from_utf8(buffer).map(Some).map_err(|error| {
965            Error::invalid_argument(format!(
966                "native style source URL was not valid UTF-8: {error}"
967            ))
968        })
969    }
970
971    fn copy_style_source_tile_urls(
972        &self,
973        map: sys::mln_map,
974        source_id: sys::mln_buffer_view,
975    ) -> Result<Option<Vec<String>>> {
976        let mut out = maplibre_core::ptr::OutHandle::<sys::mln_style_string_list>::new();
977        let mut found = false;
978        // SAFETY: map and source_id remain live for this call, out is a
979        // null-initialized output handle, and found points to writable storage.
980        maplibre_core::check(unsafe {
981            sys::mln_map_get_style_source_tile_urls(map, source_id, out.as_mut_ptr(), &mut found)
982        })?;
983        if !found {
984            return Ok(None);
985        }
986        // SAFETY: A found source returns an owned style string list; core
987        // copies every borrowed view and releases the list on all paths.
988        unsafe {
989            maplibre_core::style::copy_style_string_list(out.into_live("mln_style_string_list")?)
990                .map(Some)
991        }
992    }
993
994    /// Adds a GeoJSON source that loads data from a URL.
995    /// `options` are fixed at creation; later data or URL updates keep them.
996    pub fn add_geojson_source_url(
997        &self,
998        source_id: &str,
999        url: &str,
1000        options: Option<&GeoJsonSourceOptions>,
1001    ) -> Result<()> {
1002        let map = self.inner.native()?;
1003        let source_id = maplibre_core::string::string_view(source_id);
1004        let url = maplibre_core::string::string_view(url);
1005        let options = options
1006            .map(GeoJsonSourceOptions::try_to_native)
1007            .transpose()?;
1008        let options_ptr = options
1009            .as_ref()
1010            .map_or(ptr::null(), NativeGeoJsonSourceOptions::as_ptr);
1011        // SAFETY: map is live, source_id and url are valid for this call, and
1012        // options_ptr is null or points to call-scoped native options that keep
1013        // the cluster-properties buffer alive.
1014        maplibre_core::check(unsafe {
1015            sys::mln_map_add_geojson_source_url(map, source_id.raw(), url.raw(), options_ptr)
1016        })
1017    }
1018
1019    /// Adds a GeoJSON source with prepared inline data.
1020    ///
1021    /// The call borrows `data`, and the source adopts the options the data
1022    /// was prepared with, fixed for the lifetime of the source.
1023    pub fn add_geojson_source_data(
1024        &self,
1025        source_id: &str,
1026        data: &crate::GeoJsonSourceDataHandle,
1027    ) -> Result<()> {
1028        let map = self.inner.native()?;
1029        let source_id = maplibre_core::string::string_view(source_id);
1030        // SAFETY: map is live, source_id is valid for this call, and data is a
1031        // live prepared-data handle the call only borrows.
1032        maplibre_core::check(unsafe {
1033            sys::mln_map_add_geojson_source_data(map, source_id.raw(), data.native())
1034        })
1035    }
1036
1037    /// Updates one GeoJSON source to load data from a URL.
1038    ///
1039    /// The source keeps the options it was added with.
1040    pub fn set_geojson_source_url(&self, source_id: &str, url: &str) -> Result<()> {
1041        let map = self.inner.native()?;
1042        let source_id = maplibre_core::string::string_view(source_id);
1043        let url = maplibre_core::string::string_view(url);
1044        // SAFETY: map is live and source_id and url are valid for this call.
1045        maplibre_core::check(unsafe {
1046            sys::mln_map_set_geojson_source_url(map, source_id.raw(), url.raw())
1047        })
1048    }
1049
1050    /// Updates one GeoJSON source with prepared inline data.
1051    ///
1052    /// The call borrows `data`, and the expensive parse and tiling already
1053    /// happened when the data was prepared, so the install is cheap. The data
1054    /// must have been prepared with options equal to the options the source
1055    /// was added with, `cluster_properties` excepted; a mismatch is rejected.
1056    pub fn set_geojson_source_data(
1057        &self,
1058        source_id: &str,
1059        data: &crate::GeoJsonSourceDataHandle,
1060    ) -> Result<()> {
1061        let map = self.inner.native()?;
1062        let source_id = maplibre_core::string::string_view(source_id);
1063        // SAFETY: map is live, source_id is valid for this call, and data is a
1064        // live prepared-data handle the call only borrows.
1065        maplibre_core::check(unsafe {
1066            sys::mln_map_set_geojson_source_data(map, source_id.raw(), data.native())
1067        })
1068    }
1069
1070    /// Overrides one GeoJSON source's synchronous tiling at runtime.
1071    ///
1072    /// While enabled, the source slices requested tiles inline during the
1073    /// update pass, as if its options had set `synchronous_tiling`; disabling
1074    /// restores the option the source was added with. The override applies to
1075    /// update passes after this call returns.
1076    pub fn set_geojson_source_synchronous_tiling(
1077        &self,
1078        source_id: &str,
1079        enabled: bool,
1080    ) -> Result<()> {
1081        let map = self.inner.native()?;
1082        let source_id = maplibre_core::string::string_view(source_id);
1083        // SAFETY: map is live and source_id is valid for this call.
1084        maplibre_core::check(unsafe {
1085            sys::mln_map_set_geojson_source_synchronous_tiling(map, source_id.raw(), enabled)
1086        })
1087    }
1088
1089    /// Adds one style layer from a full style-spec layer JSON object.
1090    pub fn add_style_layer_json(
1091        &self,
1092        layer_json: &[u8],
1093        before_layer_id: Option<&str>,
1094    ) -> Result<()> {
1095        let map = self.inner.native()?;
1096        let layer_json = maplibre_core::string::buffer_view(layer_json);
1097        let before_layer_id = maplibre_core::string::string_view(before_layer_id.unwrap_or(""));
1098        // SAFETY: map is live, and layer_json and before_layer_id are
1099        // explicit-length views valid for this call.
1100        maplibre_core::check(unsafe {
1101            sys::mln_map_add_style_layer_json(map, layer_json, before_layer_id.raw())
1102        })
1103    }
1104
1105    /// Adds a hillshade layer for a raster DEM source.
1106    pub fn add_hillshade_layer(
1107        &self,
1108        layer_id: &str,
1109        source_id: &str,
1110        before_layer_id: Option<&str>,
1111    ) -> Result<()> {
1112        let map = self.inner.native()?;
1113        let layer_id = maplibre_core::string::string_view(layer_id);
1114        let source_id = maplibre_core::string::string_view(source_id);
1115        let before_layer_id = maplibre_core::string::string_view(before_layer_id.unwrap_or(""));
1116        // SAFETY: map is live, and all string views are valid for this call.
1117        maplibre_core::check(unsafe {
1118            sys::mln_map_add_hillshade_layer(
1119                map,
1120                layer_id.raw(),
1121                source_id.raw(),
1122                before_layer_id.raw(),
1123            )
1124        })
1125    }
1126
1127    /// Adds a color-relief layer for a raster DEM source.
1128    pub fn add_color_relief_layer(
1129        &self,
1130        layer_id: &str,
1131        source_id: &str,
1132        before_layer_id: Option<&str>,
1133    ) -> Result<()> {
1134        let map = self.inner.native()?;
1135        let layer_id = maplibre_core::string::string_view(layer_id);
1136        let source_id = maplibre_core::string::string_view(source_id);
1137        let before_layer_id = maplibre_core::string::string_view(before_layer_id.unwrap_or(""));
1138        // SAFETY: map is live, and all string views are valid for this call.
1139        maplibre_core::check(unsafe {
1140            sys::mln_map_add_color_relief_layer(
1141                map,
1142                layer_id.raw(),
1143                source_id.raw(),
1144                before_layer_id.raw(),
1145            )
1146        })
1147    }
1148
1149    /// Adds a source-free location indicator layer.
1150    pub fn add_location_indicator_layer(
1151        &self,
1152        layer_id: &str,
1153        before_layer_id: Option<&str>,
1154    ) -> Result<()> {
1155        let map = self.inner.native()?;
1156        let layer_id = maplibre_core::string::string_view(layer_id);
1157        let before_layer_id = maplibre_core::string::string_view(before_layer_id.unwrap_or(""));
1158        // SAFETY: map is live, and string views are valid for this call.
1159        maplibre_core::check(unsafe {
1160            sys::mln_map_add_location_indicator_layer(map, layer_id.raw(), before_layer_id.raw())
1161        })
1162    }
1163
1164    /// Sets a location indicator layer location.
1165    pub fn set_location_indicator_location(
1166        &self,
1167        layer_id: &str,
1168        coordinate: LatLng,
1169        altitude: f64,
1170    ) -> Result<()> {
1171        let map = self.inner.native()?;
1172        let layer_id = maplibre_core::string::string_view(layer_id);
1173        // SAFETY: map is live, layer_id is valid for this call, and coordinate
1174        // is passed by value.
1175        maplibre_core::check(unsafe {
1176            sys::mln_map_set_location_indicator_location(
1177                map,
1178                layer_id.raw(),
1179                coordinate.to_native(),
1180                altitude,
1181            )
1182        })
1183    }
1184
1185    /// Sets a location indicator layer bearing in degrees.
1186    pub fn set_location_indicator_bearing(&self, layer_id: &str, bearing: f64) -> Result<()> {
1187        let map = self.inner.native()?;
1188        let layer_id = maplibre_core::string::string_view(layer_id);
1189        // SAFETY: map is live and layer_id is valid for this call.
1190        maplibre_core::check(unsafe {
1191            sys::mln_map_set_location_indicator_bearing(map, layer_id.raw(), bearing)
1192        })
1193    }
1194
1195    /// Sets a location indicator layer accuracy radius in meters.
1196    pub fn set_location_indicator_accuracy_radius(
1197        &self,
1198        layer_id: &str,
1199        radius: f64,
1200    ) -> Result<()> {
1201        let map = self.inner.native()?;
1202        let layer_id = maplibre_core::string::string_view(layer_id);
1203        // SAFETY: map is live and layer_id is valid for this call.
1204        maplibre_core::check(unsafe {
1205            sys::mln_map_set_location_indicator_accuracy_radius(map, layer_id.raw(), radius)
1206        })
1207    }
1208
1209    /// Sets one location indicator image-name property.
1210    pub fn set_location_indicator_image_name(
1211        &self,
1212        layer_id: &str,
1213        image_kind: LocationIndicatorImageKind,
1214        image_id: &str,
1215    ) -> Result<()> {
1216        let map = self.inner.native()?;
1217        let layer_id = maplibre_core::string::string_view(layer_id);
1218        let image_id = maplibre_core::string::string_view(image_id);
1219        // SAFETY: map is live, string views are valid for this call, and
1220        // image_kind is a valid C enum value.
1221        maplibre_core::check(unsafe {
1222            sys::mln_map_set_location_indicator_image_name(
1223                map,
1224                layer_id.raw(),
1225                image_kind.raw_value(),
1226                image_id.raw(),
1227            )
1228        })
1229    }
1230
1231    /// Copies one style layer as a full style-spec JSON object.
1232    pub fn style_layer_json(&self, layer_id: &str) -> Result<Option<Vec<u8>>> {
1233        let map = self.inner.native()?;
1234        let layer_id = maplibre_core::string::string_view(layer_id);
1235        let mut out = maplibre_core::ptr::OutHandle::<sys::mln_buffer>::new();
1236        let mut found = false;
1237        // SAFETY: map is live, layer_id is valid for this call, out is a
1238        // null-initialized out-pointer, and found points to writable storage.
1239        maplibre_core::check(unsafe {
1240            sys::mln_map_get_style_layer_json(map, layer_id.raw(), out.as_mut_ptr(), &mut found)
1241        })?;
1242        if !found {
1243            return Ok(None);
1244        }
1245        // SAFETY: Success transfers the owned buffer to this call.
1246        unsafe { maplibre_core::string::copy_owned_buffer(out.get()) }.map(Some)
1247    }
1248
1249    /// Sets the style light from a style-spec light JSON object.
1250    pub fn set_style_light_json(&self, light_json: &[u8]) -> Result<()> {
1251        let map = self.inner.native()?;
1252        let light_json = maplibre_core::string::buffer_view(light_json);
1253        // SAFETY: map is live and light_json remains valid for this call.
1254        maplibre_core::check(unsafe { sys::mln_map_set_style_light_json(map, light_json) })
1255    }
1256
1257    /// Sets one style light property.
1258    pub fn set_style_light_property(&self, property_name: &str, value: &[u8]) -> Result<()> {
1259        let map = self.inner.native()?;
1260        let property_name = maplibre_core::string::string_view(property_name);
1261        let value = maplibre_core::string::buffer_view(value);
1262        // SAFETY: map is live, and property_name and value remain valid for this call.
1263        maplibre_core::check(unsafe {
1264            sys::mln_map_set_style_light_property(map, property_name.raw(), value)
1265        })
1266    }
1267
1268    /// Copies one style light property as a style-spec JSON value.
1269    pub fn style_light_property(&self, property_name: &str) -> Result<Option<Vec<u8>>> {
1270        let map = self.inner.native()?;
1271        let property_name = maplibre_core::string::string_view(property_name);
1272        let mut out = maplibre_core::ptr::OutHandle::<sys::mln_buffer>::new();
1273        // SAFETY: map is live, property_name is valid for this call, and out is
1274        // a null-initialized out-pointer.
1275        maplibre_core::check(unsafe {
1276            sys::mln_map_get_style_light_property(map, property_name.raw(), out.as_mut_ptr())
1277        })?;
1278        let Some(buffer) = out.into_option() else {
1279            return Ok(None);
1280        };
1281        // SAFETY: Success transfers the owned buffer to this call.
1282        unsafe { maplibre_core::string::copy_owned_buffer(buffer) }.map(Some)
1283    }
1284
1285    /// Sets the style's global transition options. This replaces the whole
1286    /// configuration rather than merging, and loading a style replaces it
1287    /// again, so apply an override after the style loads.
1288    pub fn set_style_transition_options(&self, options: &StyleTransitionOptions) -> Result<()> {
1289        let map = self.inner.native()?;
1290        let raw = maplibre_core::style::style_transition_options_to_native(options);
1291        // SAFETY: map is live and raw is a fully initialized options struct
1292        // borrowed for this call.
1293        maplibre_core::check(unsafe { sys::mln_map_set_style_transition_options(map, &raw) })
1294    }
1295
1296    /// Reads the style's global transition options.
1297    pub fn style_transition_options(&self) -> Result<StyleTransitionOptions> {
1298        let map = self.inner.native()?;
1299        let mut raw = maplibre_core::style::empty_style_transition_options();
1300        // SAFETY: map is live and raw has its ABI size initialized.
1301        maplibre_core::check(unsafe { sys::mln_map_get_style_transition_options(map, &mut raw) })?;
1302        Ok(maplibre_core::style::style_transition_options_from_native(
1303            &raw,
1304        ))
1305    }
1306
1307    /// Sets one layer style property.
1308    pub fn set_layer_property(
1309        &self,
1310        layer_id: &str,
1311        property_name: &str,
1312        value: &[u8],
1313    ) -> Result<()> {
1314        let map = self.inner.native()?;
1315        let layer_id = maplibre_core::string::string_view(layer_id);
1316        let property_name = maplibre_core::string::string_view(property_name);
1317        let value = maplibre_core::string::buffer_view(value);
1318        // SAFETY: map is live, and all string and buffer views remain valid for
1319        // this call.
1320        maplibre_core::check(unsafe {
1321            sys::mln_map_set_layer_property(map, layer_id.raw(), property_name.raw(), value)
1322        })
1323    }
1324
1325    /// Copies one layer style property as a style-spec JSON value.
1326    pub fn layer_property(&self, layer_id: &str, property_name: &str) -> Result<Option<Vec<u8>>> {
1327        let map = self.inner.native()?;
1328        let layer_id = maplibre_core::string::string_view(layer_id);
1329        let property_name = maplibre_core::string::string_view(property_name);
1330        let mut out = maplibre_core::ptr::OutHandle::<sys::mln_buffer>::new();
1331        // SAFETY: map is live, string views are valid for this call, and out is
1332        // a null-initialized out-pointer.
1333        maplibre_core::check(unsafe {
1334            sys::mln_map_get_layer_property(
1335                map,
1336                layer_id.raw(),
1337                property_name.raw(),
1338                out.as_mut_ptr(),
1339            )
1340        })?;
1341        let Some(buffer) = out.into_option() else {
1342            return Ok(None);
1343        };
1344        // SAFETY: Success transfers the owned buffer to this call.
1345        unsafe { maplibre_core::string::copy_owned_buffer(buffer) }.map(Some)
1346    }
1347
1348    /// Sets or clears one layer filter.
1349    pub fn set_layer_filter(&self, layer_id: &str, filter: Option<&[u8]>) -> Result<()> {
1350        let map = self.inner.native()?;
1351        let layer_id = maplibre_core::string::string_view(layer_id);
1352        let native_filter = filter.map(maplibre_core::string::buffer_view);
1353        // SAFETY: map is live, layer_id is valid for this call, and the
1354        // optional filter descriptor is either null or valid for this call.
1355        maplibre_core::check(unsafe {
1356            sys::mln_map_set_layer_filter(
1357                map,
1358                layer_id.raw(),
1359                native_filter.as_ref().map_or(ptr::null(), ptr::from_ref),
1360            )
1361        })
1362    }
1363
1364    /// Copies one layer filter as a style-spec JSON value.
1365    pub fn layer_filter(&self, layer_id: &str) -> Result<Option<Vec<u8>>> {
1366        let map = self.inner.native()?;
1367        let layer_id = maplibre_core::string::string_view(layer_id);
1368        let mut out = maplibre_core::ptr::OutHandle::<sys::mln_buffer>::new();
1369        // SAFETY: map is live, layer_id is valid for this call, and out is a
1370        // null-initialized out-pointer.
1371        maplibre_core::check(unsafe {
1372            sys::mln_map_get_layer_filter(map, layer_id.raw(), out.as_mut_ptr())
1373        })?;
1374        let Some(buffer) = out.into_option() else {
1375            return Ok(None);
1376        };
1377        // SAFETY: Success transfers the owned buffer to this call.
1378        unsafe { maplibre_core::string::copy_owned_buffer(buffer) }.map(Some)
1379    }
1380
1381    /// Copies one runtime style image's stretchable intervals.
1382    ///
1383    /// Returns `None` when no image carries `image_id`.
1384    pub fn style_image_stretches(
1385        &self,
1386        image_id: &str,
1387    ) -> Result<Option<(Vec<ImageStretch>, Vec<ImageStretch>)>> {
1388        let map = self.inner.native()?;
1389        let image_id = maplibre_core::string::string_view(image_id);
1390        let mut x_count = 0;
1391        let mut y_count = 0;
1392        let mut found = false;
1393        // SAFETY: map is live, image_id stays valid for this call, both arrays
1394        // are null with zero capacity so this is a size probe, and the output
1395        // pointers refer to writable storage.
1396        maplibre_core::check(unsafe {
1397            sys::mln_map_copy_style_image_stretches(
1398                map,
1399                image_id.raw(),
1400                ptr::null_mut(),
1401                0,
1402                &mut x_count,
1403                ptr::null_mut(),
1404                0,
1405                &mut y_count,
1406                &mut found,
1407            )
1408        })?;
1409        if !found {
1410            return Ok(None);
1411        }
1412
1413        let mut stretch_x = vec![sys::mln_image_stretch { from: 0.0, to: 0.0 }; x_count];
1414        let mut stretch_y = vec![sys::mln_image_stretch { from: 0.0, to: 0.0 }; y_count];
1415        // SAFETY: each buffer is writable for its reported count, and the output
1416        // pointers refer to writable storage.
1417        maplibre_core::check(unsafe {
1418            sys::mln_map_copy_style_image_stretches(
1419                map,
1420                image_id.raw(),
1421                stretch_x.as_mut_ptr(),
1422                stretch_x.len(),
1423                &mut x_count,
1424                stretch_y.as_mut_ptr(),
1425                stretch_y.len(),
1426                &mut y_count,
1427                &mut found,
1428            )
1429        })?;
1430        let to_public = |stretches: &[sys::mln_image_stretch]| -> Vec<ImageStretch> {
1431            stretches
1432                .iter()
1433                .map(|stretch| ImageStretch::new(stretch.from, stretch.to))
1434                .collect()
1435        };
1436        Ok(Some((to_public(&stretch_x), to_public(&stretch_y))))
1437    }
1438
1439    /// Sets one layer's source-layer ID.
1440    ///
1441    /// Layer types that take no source, such as background, are rejected.
1442    pub fn set_layer_source_layer(&self, layer_id: &str, source_layer: &str) -> Result<()> {
1443        let map = self.inner.native()?;
1444        let layer_id = maplibre_core::string::string_view(layer_id);
1445        let source_layer = maplibre_core::string::string_view(source_layer);
1446        // SAFETY: map is live and both string views stay valid for this call.
1447        maplibre_core::check(unsafe {
1448            sys::mln_map_set_layer_source_layer(map, layer_id.raw(), source_layer.raw())
1449        })
1450    }
1451
1452    /// Copies one layer's source-layer ID, empty when the layer carries none.
1453    pub fn layer_source_layer(&self, layer_id: &str) -> Result<String> {
1454        let map = self.inner.native()?;
1455        let layer_id = maplibre_core::string::string_view(layer_id);
1456        // SAFETY: map is live, layer_id stays valid for both calls, and each
1457        // call writes only through the pointers it is given.
1458        unsafe {
1459            copy_text(|text, capacity, out_size| {
1460                sys::mln_map_copy_layer_source_layer(map, layer_id.raw(), text, capacity, out_size)
1461            })
1462        }
1463    }
1464
1465    /// Sets one layer's source ID.
1466    ///
1467    /// Layer types that take no source, such as background, are rejected. The
1468    /// named source need not exist yet.
1469    pub fn set_layer_source_id(&self, layer_id: &str, source_id: &str) -> Result<()> {
1470        let map = self.inner.native()?;
1471        let layer_id = maplibre_core::string::string_view(layer_id);
1472        let source_id = maplibre_core::string::string_view(source_id);
1473        // SAFETY: map is live and both string views stay valid for this call.
1474        maplibre_core::check(unsafe {
1475            sys::mln_map_set_layer_source_id(map, layer_id.raw(), source_id.raw())
1476        })
1477    }
1478
1479    /// Copies one layer's source ID, empty when the layer carries none.
1480    pub fn layer_source_id(&self, layer_id: &str) -> Result<String> {
1481        let map = self.inner.native()?;
1482        let layer_id = maplibre_core::string::string_view(layer_id);
1483        // SAFETY: map is live, layer_id stays valid for both calls, and each
1484        // call writes only through the pointers it is given.
1485        unsafe {
1486            copy_text(|text, capacity, out_size| {
1487                sys::mln_map_copy_layer_source_id(map, layer_id.raw(), text, capacity, out_size)
1488            })
1489        }
1490    }
1491
1492    /// Sets the lowest zoom at which one layer draws.
1493    ///
1494    /// Pass `f64::NEG_INFINITY` for no lower bound.
1495    pub fn set_layer_min_zoom(&self, layer_id: &str, min_zoom: f64) -> Result<()> {
1496        let map = self.inner.native()?;
1497        let layer_id = maplibre_core::string::string_view(layer_id);
1498        // SAFETY: map is live and layer_id stays valid for this call.
1499        maplibre_core::check(unsafe {
1500            sys::mln_map_set_layer_min_zoom(map, layer_id.raw(), min_zoom)
1501        })
1502    }
1503
1504    /// Reads the lowest zoom at which one layer draws.
1505    ///
1506    /// A layer with no lower bound reports `f64::NEG_INFINITY`.
1507    pub fn layer_min_zoom(&self, layer_id: &str) -> Result<f64> {
1508        let map = self.inner.native()?;
1509        let layer_id = maplibre_core::string::string_view(layer_id);
1510        let mut min_zoom = 0.0;
1511        // SAFETY: map is live, layer_id stays valid for this call, and min_zoom
1512        // is writable storage.
1513        maplibre_core::check(unsafe {
1514            sys::mln_map_get_layer_min_zoom(map, layer_id.raw(), &mut min_zoom)
1515        })?;
1516        Ok(min_zoom)
1517    }
1518
1519    /// Sets the highest zoom at which one layer draws.
1520    ///
1521    /// Pass `f64::INFINITY` for no upper bound.
1522    pub fn set_layer_max_zoom(&self, layer_id: &str, max_zoom: f64) -> Result<()> {
1523        let map = self.inner.native()?;
1524        let layer_id = maplibre_core::string::string_view(layer_id);
1525        // SAFETY: map is live and layer_id stays valid for this call.
1526        maplibre_core::check(unsafe {
1527            sys::mln_map_set_layer_max_zoom(map, layer_id.raw(), max_zoom)
1528        })
1529    }
1530
1531    /// Reads the highest zoom at which one layer draws.
1532    ///
1533    /// A layer with no upper bound reports `f64::INFINITY`.
1534    pub fn layer_max_zoom(&self, layer_id: &str) -> Result<f64> {
1535        let map = self.inner.native()?;
1536        let layer_id = maplibre_core::string::string_view(layer_id);
1537        let mut max_zoom = 0.0;
1538        // SAFETY: map is live, layer_id stays valid for this call, and max_zoom
1539        // is writable storage.
1540        maplibre_core::check(unsafe {
1541            sys::mln_map_get_layer_max_zoom(map, layer_id.raw(), &mut max_zoom)
1542        })?;
1543        Ok(max_zoom)
1544    }
1545
1546    /// Sets whether one layer draws.
1547    pub fn set_layer_visibility(
1548        &self,
1549        layer_id: &str,
1550        visibility: StyleLayerVisibility,
1551    ) -> Result<()> {
1552        let map = self.inner.native()?;
1553        let layer_id = maplibre_core::string::string_view(layer_id);
1554        // SAFETY: map is live and layer_id stays valid for this call.
1555        maplibre_core::check(unsafe {
1556            sys::mln_map_set_layer_visibility(map, layer_id.raw(), visibility.raw_value())
1557        })
1558    }
1559
1560    /// Reads whether one layer draws.
1561    pub fn layer_visibility(&self, layer_id: &str) -> Result<StyleLayerVisibility> {
1562        let map = self.inner.native()?;
1563        let layer_id = maplibre_core::string::string_view(layer_id);
1564        let mut visibility = 0;
1565        // SAFETY: map is live, layer_id stays valid for this call, and
1566        // visibility is writable storage.
1567        maplibre_core::check(unsafe {
1568            sys::mln_map_get_layer_visibility(map, layer_id.raw(), &mut visibility)
1569        })?;
1570        Ok(StyleLayerVisibility::from_raw(visibility))
1571    }
1572
1573    /// Copies current style source IDs into owned Rust strings.
1574    pub fn style_source_ids(&self) -> Result<Vec<String>> {
1575        let map = self.inner.native()?;
1576        let mut out = maplibre_core::ptr::OutHandle::<sys::mln_style_id_list>::new();
1577        // SAFETY: map is live and out is a null-initialized out-pointer owned by
1578        // this call. On success the returned handle is wrapped and destroyed by
1579        // the copying helper below.
1580        maplibre_core::check(unsafe { sys::mln_map_list_style_source_ids(map, out.as_mut_ptr()) })?;
1581        // SAFETY: On success, the C API returns an owned style ID list handle;
1582        // core copies and releases it.
1583        unsafe { maplibre_core::style::copy_style_id_list(out.into_live("mln_style_id_list")?) }
1584    }
1585
1586    /// Copies current style layer IDs into owned Rust strings.
1587    pub fn style_layer_ids(&self) -> Result<Vec<String>> {
1588        let map = self.inner.native()?;
1589        let mut out = maplibre_core::ptr::OutHandle::<sys::mln_style_id_list>::new();
1590        // SAFETY: map is live and out is a null-initialized out-pointer owned by
1591        // this call. On success the returned handle is wrapped and destroyed by
1592        // the copying helper below.
1593        maplibre_core::check(unsafe { sys::mln_map_list_style_layer_ids(map, out.as_mut_ptr()) })?;
1594        // SAFETY: On success, the C API returns an owned style ID list handle;
1595        // core copies and releases it.
1596        unsafe { maplibre_core::style::copy_style_id_list(out.into_live("mln_style_id_list")?) }
1597    }
1598
1599    /// Copies the ID, type, source ID, and source-layer of every style layer in
1600    /// style order.
1601    pub fn style_layers(&self) -> Result<Vec<StyleLayerInfo>> {
1602        let map = self.inner.native()?;
1603        let mut out = maplibre_core::ptr::OutHandle::<sys::mln_style_layer_list>::new();
1604        // SAFETY: map is live and out is a null-initialized out-pointer owned by
1605        // this call. On success the returned handle is wrapped and destroyed by
1606        // the copying helper below.
1607        maplibre_core::check(unsafe { sys::mln_map_list_style_layers(map, out.as_mut_ptr()) })?;
1608        // SAFETY: On success, the C API returns an owned style layer list handle;
1609        // core copies and releases it.
1610        unsafe {
1611            maplibre_core::style::copy_style_layer_list(out.into_live("mln_style_layer_list")?)
1612        }
1613    }
1614}
1615
1616/// Probes the required byte length, then copies the text into an owned `String`.
1617///
1618/// # Safety
1619///
1620/// `copy` must forward its arguments to a C entry point that writes at most
1621/// `capacity` bytes through the text pointer and the required length through the
1622/// size pointer.
1623unsafe fn copy_text(
1624    copy: impl Fn(*mut std::os::raw::c_char, usize, *mut usize) -> sys::mln_status,
1625) -> Result<String> {
1626    let mut required = 0;
1627    maplibre_core::check(copy(ptr::null_mut(), 0, &mut required))?;
1628    if required == 0 {
1629        return Ok(String::new());
1630    }
1631
1632    let mut buffer = vec![0u8; required];
1633    let mut copied = 0;
1634    maplibre_core::check(copy(buffer.as_mut_ptr().cast(), buffer.len(), &mut copied))?;
1635    if copied > buffer.len() {
1636        return Err(Error::new(
1637            ErrorKind::NativeError,
1638            None,
1639            "native text size exceeded caller buffer",
1640        ));
1641    }
1642    buffer.truncate(copied);
1643    String::from_utf8(buffer).map_err(|_| {
1644        Error::new(
1645            ErrorKind::NativeError,
1646            None,
1647            "native text was not valid UTF-8",
1648        )
1649    })
1650}
1651
1652/// Probes the required byte length, then copies the bytes into owned storage.
1653///
1654/// # Safety
1655///
1656/// `copy` must write at most `capacity` bytes and report the required length
1657/// through the size pointer.
1658unsafe fn copy_bytes(
1659    copy: impl Fn(*mut u8, usize, *mut usize) -> sys::mln_status,
1660) -> Result<Vec<u8>> {
1661    let mut required = 0;
1662    maplibre_core::check(copy(ptr::null_mut(), 0, &mut required))?;
1663    if required == 0 {
1664        return Ok(Vec::new());
1665    }
1666
1667    let mut buffer = vec![0u8; required];
1668    let mut copied = 0;
1669    maplibre_core::check(copy(buffer.as_mut_ptr(), buffer.len(), &mut copied))?;
1670    if copied > buffer.len() {
1671        return Err(Error::new(
1672            ErrorKind::NativeError,
1673            None,
1674            "native byte size exceeded caller buffer",
1675        ));
1676    }
1677    buffer.truncate(copied);
1678    Ok(buffer)
1679}