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