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