Skip to main content

maplibre_native/map/
style.rs

1use std::ptr;
2
3pub(crate) use maplibre_core::style::{
4    NativeTileSourceOptions, NativeTileUrls, StyleImageOptionsNativeExt, TileSourceOptionsNativeExt,
5};
6pub use maplibre_core::{
7    LocationIndicatorImageKind, RasterDemEncoding, SourceInfo, SourceType, StyleImage,
8    StyleImageInfo, StyleImageOptions, TileScheme, TileSourceOptions, VectorTileEncoding,
9};
10use maplibre_native_core as maplibre_core;
11use maplibre_native_core::ptr::const_ptr_or_null;
12use maplibre_native_core::values::lat_lngs_to_native;
13use maplibre_native_sys as sys;
14
15use crate::custom_geometry::{CanonicalTileId, CustomGeometrySourceState};
16use crate::geojson::GeoJsonNativeExt;
17use crate::json::JsonValueNativeExt;
18use crate::render::PremultipliedRgba8Image;
19use crate::values::NativeValue;
20use crate::{
21    CustomGeometrySourceOptions, Error, ErrorKind, GeoJson, JsonValue, LatLng, LatLngBounds, Result,
22};
23
24impl super::MapHandle {
25    /// Loads a style URL through MapLibre Native style APIs.
26    ///
27    pub fn set_style_url(&self, url: &str) -> Result<()> {
28        let map = self.inner.as_ptr()?;
29        let url = maplibre_core::string::c_string(url)?;
30        // SAFETY: map is live and url is a NUL-terminated UTF-8 string valid
31        // for the duration of this command. The C API copies/consumes it before
32        // returning.
33        maplibre_core::check(unsafe { sys::mln_map_set_style_url(map, url.as_ptr()) })?;
34        Ok(())
35    }
36
37    /// Loads inline style JSON through MapLibre Native style APIs.
38    pub fn set_style_json(&self, json: &str) -> Result<()> {
39        let map = self.inner.as_ptr()?;
40        let json = maplibre_core::string::c_string(json)?;
41        // SAFETY: map is live and json is a NUL-terminated UTF-8 string valid
42        // for the duration of this command. The C API copies/consumes it before
43        // returning. Inline JSON style replacement completes before a successful
44        // return, so old custom geometry callback state can be released after.
45        maplibre_core::check(unsafe { sys::mln_map_set_style_json(map, json.as_ptr()) })?;
46        self.inner.clear_custom_geometry_sources();
47        Ok(())
48    }
49
50    /// Adds a custom geometry source to the current style.
51    ///
52    /// The callback state is scoped to this map's current style. It is released
53    /// on source removal, map close/drop, successful inline JSON style
54    /// replacement, or after runtime event polling observes that the loaded
55    /// style no longer contains the source. Native may invoke callbacks from
56    /// worker threads; callbacks should queue owner-thread work before calling
57    /// map APIs.
58    pub fn add_custom_geometry_source(
59        &self,
60        source_id: &str,
61        options: CustomGeometrySourceOptions,
62    ) -> Result<()> {
63        let map = self.inner.as_ptr()?;
64        let source_id_view = maplibre_core::string::string_view(source_id);
65        let state = CustomGeometrySourceState::new(options);
66        let descriptor = state.descriptor();
67        // SAFETY: map is live, source_id_view is valid for this call, and
68        // descriptor points to callback state retained by this map on success.
69        maplibre_core::check(unsafe {
70            sys::mln_map_add_custom_geometry_source(map, source_id_view.raw(), &descriptor)
71        })?;
72        self.inner
73            .custom_geometry_sources
74            .borrow_mut()
75            .insert(source_id.to_owned(), state);
76        Ok(())
77    }
78
79    /// Sets custom geometry source data for one canonical tile.
80    pub fn set_custom_geometry_source_tile_data(
81        &self,
82        source_id: &str,
83        tile_id: CanonicalTileId,
84        data: &GeoJson,
85    ) -> Result<()> {
86        let map = self.inner.as_ptr()?;
87        let source_id = maplibre_core::string::string_view(source_id);
88        let data = data.try_to_native()?;
89        // SAFETY: map is live, source_id is valid for this call, tile_id is
90        // passed by value, and data owns the descriptor graph for this call.
91        maplibre_core::check(unsafe {
92            sys::mln_map_set_custom_geometry_source_tile_data(
93                map,
94                source_id.raw(),
95                tile_id.to_native(),
96                data.as_ptr(),
97            )
98        })
99    }
100
101    /// Invalidates custom geometry source data for one canonical tile.
102    pub fn invalidate_custom_geometry_source_tile(
103        &self,
104        source_id: &str,
105        tile_id: CanonicalTileId,
106    ) -> Result<()> {
107        let map = self.inner.as_ptr()?;
108        let source_id = maplibre_core::string::string_view(source_id);
109        // SAFETY: map is live, source_id is valid for this call, and tile_id is
110        // passed by value.
111        maplibre_core::check(unsafe {
112            sys::mln_map_invalidate_custom_geometry_source_tile(
113                map,
114                source_id.raw(),
115                tile_id.to_native(),
116            )
117        })
118    }
119
120    /// Invalidates custom geometry source data inside a geographic region.
121    pub fn invalidate_custom_geometry_source_region(
122        &self,
123        source_id: &str,
124        bounds: LatLngBounds,
125    ) -> Result<()> {
126        let map = self.inner.as_ptr()?;
127        let source_id = maplibre_core::string::string_view(source_id);
128        // SAFETY: map is live, source_id is valid for this call, and bounds is
129        // passed by value.
130        maplibre_core::check(unsafe {
131            sys::mln_map_invalidate_custom_geometry_source_region(
132                map,
133                source_id.raw(),
134                bounds.to_native(),
135            )
136        })
137    }
138
139    /// Adds one style source from a style-spec source JSON object.
140    pub fn add_style_source_json(&self, source_id: &str, source_json: &JsonValue) -> Result<()> {
141        let map = self.inner.as_ptr()?;
142        let source_id = maplibre_core::string::string_view(source_id);
143        let source_json = source_json.try_to_native()?;
144        // SAFETY: map is live, source_id is an explicit-length view valid for
145        // this call, and source_json owns the descriptor graph for this call.
146        maplibre_core::check(unsafe {
147            sys::mln_map_add_style_source_json(map, source_id.raw(), source_json.as_ptr())
148        })
149    }
150
151    /// Adds a vector source with a TileJSON URL.
152    pub fn add_vector_source_url(
153        &self,
154        source_id: &str,
155        url: &str,
156        options: Option<&TileSourceOptions>,
157    ) -> Result<()> {
158        let map = self.inner.as_ptr()?;
159        let source_id = maplibre_core::string::string_view(source_id);
160        let url = maplibre_core::string::string_view(url);
161        let options = options.map(TileSourceOptions::to_native);
162        let options_ptr = options
163            .as_ref()
164            .map_or(ptr::null(), NativeTileSourceOptions::as_ptr);
165        // SAFETY: map is live, source_id and url are valid for this call, and
166        // options_ptr is null or points to call-scoped native options.
167        maplibre_core::check(unsafe {
168            sys::mln_map_add_vector_source_url(map, source_id.raw(), url.raw(), options_ptr)
169        })
170    }
171
172    /// Adds a vector source with inline tile URLs.
173    pub fn add_vector_source_tiles<S: AsRef<str>>(
174        &self,
175        source_id: &str,
176        tiles: &[S],
177        options: Option<&TileSourceOptions>,
178    ) -> Result<()> {
179        let map = self.inner.as_ptr()?;
180        let source_id = maplibre_core::string::string_view(source_id);
181        let raw_tiles = NativeTileUrls::new(tiles);
182        let options = options.map(TileSourceOptions::to_native);
183        let options_ptr = options
184            .as_ref()
185            .map_or(ptr::null(), NativeTileSourceOptions::as_ptr);
186        // SAFETY: map is live, source_id is valid for this call, raw_tiles
187        // points to call-scoped string views, and options_ptr is null or points
188        // to call-scoped native options.
189        maplibre_core::check(unsafe {
190            sys::mln_map_add_vector_source_tiles(
191                map,
192                source_id.raw(),
193                raw_tiles.as_ptr(),
194                raw_tiles.len(),
195                options_ptr,
196            )
197        })
198    }
199
200    /// Adds a raster source with a TileJSON URL.
201    pub fn add_raster_source_url(
202        &self,
203        source_id: &str,
204        url: &str,
205        options: Option<&TileSourceOptions>,
206    ) -> Result<()> {
207        let map = self.inner.as_ptr()?;
208        let source_id = maplibre_core::string::string_view(source_id);
209        let url = maplibre_core::string::string_view(url);
210        let options = options.map(TileSourceOptions::to_native);
211        let options_ptr = options
212            .as_ref()
213            .map_or(ptr::null(), NativeTileSourceOptions::as_ptr);
214        // SAFETY: map is live, source_id and url are valid for this call, and
215        // options_ptr is null or points to call-scoped native options.
216        maplibre_core::check(unsafe {
217            sys::mln_map_add_raster_source_url(map, source_id.raw(), url.raw(), options_ptr)
218        })
219    }
220
221    /// Adds a raster source with inline tile URLs.
222    pub fn add_raster_source_tiles<S: AsRef<str>>(
223        &self,
224        source_id: &str,
225        tiles: &[S],
226        options: Option<&TileSourceOptions>,
227    ) -> Result<()> {
228        let map = self.inner.as_ptr()?;
229        let source_id = maplibre_core::string::string_view(source_id);
230        let raw_tiles = NativeTileUrls::new(tiles);
231        let options = options.map(TileSourceOptions::to_native);
232        let options_ptr = options
233            .as_ref()
234            .map_or(ptr::null(), NativeTileSourceOptions::as_ptr);
235        // SAFETY: map is live, source_id is valid for this call, raw_tiles
236        // points to call-scoped string views, and options_ptr is null or points
237        // to call-scoped native options.
238        maplibre_core::check(unsafe {
239            sys::mln_map_add_raster_source_tiles(
240                map,
241                source_id.raw(),
242                raw_tiles.as_ptr(),
243                raw_tiles.len(),
244                options_ptr,
245            )
246        })
247    }
248
249    /// Adds a raster DEM source with a TileJSON URL.
250    pub fn add_raster_dem_source_url(
251        &self,
252        source_id: &str,
253        url: &str,
254        options: Option<&TileSourceOptions>,
255    ) -> Result<()> {
256        let map = self.inner.as_ptr()?;
257        let source_id = maplibre_core::string::string_view(source_id);
258        let url = maplibre_core::string::string_view(url);
259        let options = options.map(TileSourceOptions::to_native);
260        let options_ptr = options
261            .as_ref()
262            .map_or(ptr::null(), NativeTileSourceOptions::as_ptr);
263        // SAFETY: map is live, source_id and url are valid for this call, and
264        // options_ptr is null or points to call-scoped native options.
265        maplibre_core::check(unsafe {
266            sys::mln_map_add_raster_dem_source_url(map, source_id.raw(), url.raw(), options_ptr)
267        })
268    }
269
270    /// Adds a raster DEM source with inline tile URLs.
271    pub fn add_raster_dem_source_tiles<S: AsRef<str>>(
272        &self,
273        source_id: &str,
274        tiles: &[S],
275        options: Option<&TileSourceOptions>,
276    ) -> Result<()> {
277        let map = self.inner.as_ptr()?;
278        let source_id = maplibre_core::string::string_view(source_id);
279        let raw_tiles = NativeTileUrls::new(tiles);
280        let options = options.map(TileSourceOptions::to_native);
281        let options_ptr = options
282            .as_ref()
283            .map_or(ptr::null(), NativeTileSourceOptions::as_ptr);
284        // SAFETY: map is live, source_id is valid for this call, raw_tiles
285        // points to call-scoped string views, and options_ptr is null or points
286        // to call-scoped native options.
287        maplibre_core::check(unsafe {
288            sys::mln_map_add_raster_dem_source_tiles(
289                map,
290                source_id.raw(),
291                raw_tiles.as_ptr(),
292                raw_tiles.len(),
293                options_ptr,
294            )
295        })
296    }
297
298    /// Adds an image source that loads its image from a URL.
299    ///
300    /// Coordinates are borrowed for the call and copied by native on success.
301    /// The array entries are in top-left, top-right, bottom-right, bottom-left
302    /// order.
303    pub fn add_image_source_url(
304        &self,
305        source_id: &str,
306        coordinates: &[LatLng; 4],
307        url: &str,
308    ) -> Result<()> {
309        let map = self.inner.as_ptr()?;
310        let source_id = maplibre_core::string::string_view(source_id);
311        let coordinates = lat_lngs_to_native(coordinates);
312        let url = maplibre_core::string::string_view(url);
313        // SAFETY: map is live, source_id and url are explicit-length views
314        // valid for this call, and coordinates points to call-scoped native
315        // coordinate storage. Native validates coordinate contents.
316        maplibre_core::check(unsafe {
317            sys::mln_map_add_image_source_url(
318                map,
319                source_id.raw(),
320                const_ptr_or_null(&coordinates),
321                coordinates.len(),
322                url.raw(),
323            )
324        })
325    }
326
327    /// Adds an image source with inline premultiplied RGBA8 pixels.
328    ///
329    /// Coordinates and image pixels are borrowed for the call and copied by
330    /// native on success. Coordinate entries are in top-left, top-right,
331    /// bottom-right, bottom-left order.
332    pub fn add_image_source_image(
333        &self,
334        source_id: &str,
335        coordinates: &[LatLng; 4],
336        image: &PremultipliedRgba8Image,
337    ) -> Result<()> {
338        let map = self.inner.as_ptr()?;
339        let source_id = maplibre_core::string::string_view(source_id);
340        let coordinates = lat_lngs_to_native(coordinates);
341        let image = maplibre_core::values::premultiplied_rgba8_image_to_native(image);
342        // SAFETY: map is live, source_id is an explicit-length view valid for
343        // this call, coordinates points to call-scoped native coordinate
344        // storage, and image points into the borrowed Rust image for this call.
345        maplibre_core::check(unsafe {
346            sys::mln_map_add_image_source_image(
347                map,
348                source_id.raw(),
349                const_ptr_or_null(&coordinates),
350                coordinates.len(),
351                &image,
352            )
353        })
354    }
355
356    /// Updates an image source to load its image from a URL.
357    pub fn set_image_source_url(&self, source_id: &str, url: &str) -> Result<()> {
358        let map = self.inner.as_ptr()?;
359        let source_id = maplibre_core::string::string_view(source_id);
360        let url = maplibre_core::string::string_view(url);
361        // SAFETY: map is live, and source_id and url are explicit-length views
362        // valid for this call.
363        maplibre_core::check(unsafe {
364            sys::mln_map_set_image_source_url(map, source_id.raw(), url.raw())
365        })
366    }
367
368    /// Updates an image source with inline premultiplied RGBA8 pixels.
369    pub fn set_image_source_image(
370        &self,
371        source_id: &str,
372        image: &PremultipliedRgba8Image,
373    ) -> Result<()> {
374        let map = self.inner.as_ptr()?;
375        let source_id = maplibre_core::string::string_view(source_id);
376        let image = maplibre_core::values::premultiplied_rgba8_image_to_native(image);
377        // SAFETY: map is live, source_id is an explicit-length view valid for
378        // this call, and image points into the borrowed Rust image for this call.
379        maplibre_core::check(unsafe {
380            sys::mln_map_set_image_source_image(map, source_id.raw(), &image)
381        })
382    }
383
384    /// Updates image source coordinates.
385    ///
386    /// Coordinates are borrowed for the call and copied by native on success.
387    /// The array entries are in top-left, top-right, bottom-right, bottom-left
388    /// order.
389    pub fn set_image_source_coordinates(
390        &self,
391        source_id: &str,
392        coordinates: &[LatLng; 4],
393    ) -> Result<()> {
394        let map = self.inner.as_ptr()?;
395        let source_id = maplibre_core::string::string_view(source_id);
396        let coordinates = lat_lngs_to_native(coordinates);
397        // SAFETY: map is live, source_id is an explicit-length view valid for
398        // this call, and coordinates points to call-scoped native coordinate
399        // storage. Native validates coordinate contents.
400        maplibre_core::check(unsafe {
401            sys::mln_map_set_image_source_coordinates(
402                map,
403                source_id.raw(),
404                const_ptr_or_null(&coordinates),
405                coordinates.len(),
406            )
407        })
408    }
409
410    /// Copies image source coordinates into owned Rust values.
411    pub fn image_source_coordinates(&self, source_id: &str) -> Result<Option<[LatLng; 4]>> {
412        let map = self.inner.as_ptr()?;
413        let source_id = maplibre_core::string::string_view(source_id);
414        let mut coordinates = [sys::mln_lat_lng {
415            latitude: 0.0,
416            longitude: 0.0,
417        }; 4];
418        let mut coordinate_count = 0;
419        let mut found = false;
420        // SAFETY: map is live, source_id is an explicit-length view valid for
421        // this call, coordinates has capacity for four native coordinates, and
422        // output pointers refer to writable storage.
423        maplibre_core::check(unsafe {
424            sys::mln_map_get_image_source_coordinates(
425                map,
426                source_id.raw(),
427                coordinates.as_mut_ptr(),
428                coordinates.len(),
429                &mut coordinate_count,
430                &mut found,
431            )
432        })?;
433        if !found {
434            return Ok(None);
435        }
436        if coordinate_count != coordinates.len() {
437            return Err(Error::new(
438                ErrorKind::NativeError,
439                None,
440                "native image source coordinate count did not match Rust image source invariant",
441            ));
442        }
443        Ok(Some(coordinates.map(LatLng::from_native)))
444    }
445
446    /// Removes one style source by ID.
447    ///
448    /// Returns whether a source existed and was removed. Native returns an
449    /// error when a layer still uses the source.
450    pub fn remove_style_source(&self, source_id: &str) -> Result<bool> {
451        let map = self.inner.as_ptr()?;
452        let source_id_key = source_id.to_owned();
453        let source_id = maplibre_core::string::string_view(source_id);
454        let mut removed = false;
455        // SAFETY: map is live, source_id is an explicit-length view valid for
456        // this call, and removed points to writable storage.
457        maplibre_core::check(unsafe {
458            sys::mln_map_remove_style_source(map, source_id.raw(), &mut removed)
459        })?;
460        if removed {
461            self.inner
462                .custom_geometry_sources
463                .borrow_mut()
464                .remove(&source_id_key);
465        }
466        Ok(removed)
467    }
468
469    /// Reports whether a style source ID exists.
470    pub fn style_source_exists(&self, source_id: &str) -> Result<bool> {
471        let map = self.inner.as_ptr()?;
472        let source_id = maplibre_core::string::string_view(source_id);
473        let mut exists = false;
474        // SAFETY: map is live, source_id is an explicit-length view valid for
475        // this call, and exists points to writable storage.
476        maplibre_core::check(unsafe {
477            sys::mln_map_style_source_exists(map, source_id.raw(), &mut exists)
478        })?;
479        Ok(exists)
480    }
481
482    /// Adds or replaces one runtime style image.
483    pub fn set_style_image(
484        &self,
485        image_id: &str,
486        image: &PremultipliedRgba8Image,
487        options: Option<&StyleImageOptions>,
488    ) -> Result<()> {
489        let map = self.inner.as_ptr()?;
490        let image_id = maplibre_core::string::string_view(image_id);
491        let image = maplibre_core::values::premultiplied_rgba8_image_to_native(image);
492        let options = options.map(StyleImageOptions::to_native);
493        let options_ptr = options.as_ref().map_or(ptr::null(), ptr::from_ref);
494        // SAFETY: map is live, image_id is an explicit-length view valid for
495        // this call, image points into the borrowed Rust image for this call,
496        // and options_ptr is either null or points to call-scoped options.
497        maplibre_core::check(unsafe {
498            sys::mln_map_set_style_image(map, image_id.raw(), &image, options_ptr)
499        })
500    }
501
502    /// Removes one runtime style image by ID.
503    ///
504    /// Returns whether an image existed and was removed.
505    pub fn remove_style_image(&self, image_id: &str) -> Result<bool> {
506        let map = self.inner.as_ptr()?;
507        let image_id = maplibre_core::string::string_view(image_id);
508        let mut removed = false;
509        // SAFETY: map is live, image_id is an explicit-length view valid for
510        // this call, and removed points to writable storage.
511        maplibre_core::check(unsafe {
512            sys::mln_map_remove_style_image(map, image_id.raw(), &mut removed)
513        })?;
514        Ok(removed)
515    }
516
517    /// Reports whether a runtime style image ID exists.
518    pub fn style_image_exists(&self, image_id: &str) -> Result<bool> {
519        let map = self.inner.as_ptr()?;
520        let image_id = maplibre_core::string::string_view(image_id);
521        let mut exists = false;
522        // SAFETY: map is live, image_id is an explicit-length view valid for
523        // this call, and exists points to writable storage.
524        maplibre_core::check(unsafe {
525            sys::mln_map_style_image_exists(map, image_id.raw(), &mut exists)
526        })?;
527        Ok(exists)
528    }
529
530    /// Copies fixed metadata for one runtime style image.
531    pub fn style_image_info(&self, image_id: &str) -> Result<Option<StyleImageInfo>> {
532        let map = self.inner.as_ptr()?;
533        let image_id = maplibre_core::string::string_view(image_id);
534        let mut info = maplibre_core::style::empty_style_image_info();
535        let mut found = false;
536        // SAFETY: map is live, image_id is an explicit-length view valid for
537        // this call, info has its ABI size initialized, and found points to
538        // writable storage.
539        maplibre_core::check(unsafe {
540            sys::mln_map_get_style_image_info(map, image_id.raw(), &mut info, &mut found)
541        })?;
542        Ok(found.then(|| maplibre_core::values::style_image_info_from_native(&info)))
543    }
544
545    /// Copies one runtime style image into owned tightly packed premultiplied RGBA8 pixels.
546    pub fn copy_style_image_premultiplied_rgba8(
547        &self,
548        image_id: &str,
549    ) -> Result<Option<StyleImage>> {
550        let map = self.inner.as_ptr()?;
551        let image_id = maplibre_core::string::string_view(image_id);
552        let mut raw_info = maplibre_core::style::empty_style_image_info();
553        let mut info_found = false;
554        // SAFETY: map is live, image_id is an explicit-length view valid for
555        // this call, raw_info has its ABI size initialized, and info_found
556        // points to writable storage.
557        maplibre_core::check(unsafe {
558            sys::mln_map_get_style_image_info(map, image_id.raw(), &mut raw_info, &mut info_found)
559        })?;
560        if !info_found {
561            return Ok(None);
562        }
563        let info = maplibre_core::values::style_image_info_from_native(&raw_info);
564
565        let mut data = vec![0u8; info.byte_length];
566        let mut copied_size = 0;
567        let mut found = false;
568        let pixels = if data.is_empty() {
569            ptr::null_mut()
570        } else {
571            data.as_mut_ptr()
572        };
573        // SAFETY: map is live, image_id remains valid for this call, data is
574        // writable for info.byte_length bytes (or null with zero capacity), and
575        // output pointers refer to writable storage.
576        maplibre_core::check(unsafe {
577            sys::mln_map_copy_style_image_premultiplied_rgba8(
578                map,
579                image_id.raw(),
580                pixels,
581                data.len(),
582                &mut copied_size,
583                &mut found,
584            )
585        })?;
586        if !found {
587            return Ok(None);
588        }
589        maplibre_core::style::style_image_from_copied_premultiplied_rgba8(info, data, copied_size)
590            .map(Some)
591    }
592
593    /// Gets one style source type.
594    pub fn style_source_type(&self, source_id: &str) -> Result<Option<SourceType>> {
595        let map = self.inner.as_ptr()?;
596        let source_id = maplibre_core::string::string_view(source_id);
597        let mut raw_source_type = sys::MLN_STYLE_SOURCE_TYPE_UNKNOWN;
598        let mut found = false;
599        // SAFETY: map is live, source_id is an explicit-length view valid for
600        // this call, and output pointers refer to writable storage.
601        maplibre_core::check(unsafe {
602            sys::mln_map_get_style_source_type(
603                map,
604                source_id.raw(),
605                &mut raw_source_type,
606                &mut found,
607            )
608        })?;
609        Ok(found.then(|| SourceType::from_raw(raw_source_type)))
610    }
611
612    /// Copies fixed metadata and attribution for one style source.
613    pub fn style_source_info(&self, source_id: &str) -> Result<Option<SourceInfo>> {
614        let map = self.inner.as_ptr()?;
615        let source_id = maplibre_core::string::string_view(source_id);
616        let mut info = maplibre_core::style::empty_style_source_info();
617        let mut found = false;
618        // SAFETY: map is live, source_id is an explicit-length view valid for
619        // this call, info has its ABI size initialized, and found points to
620        // writable storage.
621        maplibre_core::check(unsafe {
622            sys::mln_map_get_style_source_info(map, source_id.raw(), &mut info, &mut found)
623        })?;
624        if !found {
625            return Ok(None);
626        }
627
628        let attribution = if info.has_attribution {
629            match self.copy_style_source_attribution(map, source_id.raw(), info.attribution_size)? {
630                Some(attribution) => Some(attribution),
631                None => return Ok(None),
632            }
633        } else {
634            None
635        };
636
637        Ok(Some(maplibre_core::style::style_source_info_from_native(
638            &info,
639            attribution,
640        )))
641    }
642
643    fn copy_style_source_attribution(
644        &self,
645        map: *mut sys::mln_map,
646        source_id: sys::mln_string_view,
647        attribution_size: usize,
648    ) -> Result<Option<String>> {
649        if attribution_size == 0 {
650            let mut copied_size = 0;
651            let mut found = false;
652            // SAFETY: map is live, source_id remains valid for this call,
653            // capacity is zero so the output buffer may be null, and output
654            // pointers refer to writable storage.
655            maplibre_core::check(unsafe {
656                sys::mln_map_copy_style_source_attribution(
657                    map,
658                    source_id,
659                    ptr::null_mut(),
660                    0,
661                    &mut copied_size,
662                    &mut found,
663                )
664            })?;
665            return Ok(found.then(String::new));
666        }
667
668        let mut buffer = vec![0u8; attribution_size];
669        let mut copied_size = 0;
670        let mut found = false;
671        // SAFETY: map is live, source_id remains valid for this call, buffer is
672        // writable for attribution_size bytes, and output pointers refer to
673        // writable storage.
674        maplibre_core::check(unsafe {
675            sys::mln_map_copy_style_source_attribution(
676                map,
677                source_id,
678                buffer.as_mut_ptr().cast(),
679                buffer.len(),
680                &mut copied_size,
681                &mut found,
682            )
683        })?;
684        if !found {
685            return Ok(None);
686        }
687        if copied_size > buffer.len() {
688            return Err(Error::new(
689                ErrorKind::NativeError,
690                None,
691                "native style source attribution size exceeded caller buffer",
692            ));
693        }
694        buffer.truncate(copied_size);
695        String::from_utf8(buffer).map(Some).map_err(|error| {
696            Error::invalid_argument(format!(
697                "native style source attribution was not valid UTF-8: {error}"
698            ))
699        })
700    }
701
702    /// Adds a GeoJSON source with inline data.
703    pub fn add_geojson_source_data(&self, source_id: &str, data: &GeoJson) -> Result<()> {
704        let map = self.inner.as_ptr()?;
705        let source_id = maplibre_core::string::string_view(source_id);
706        let data = data.try_to_native()?;
707        // SAFETY: map is live, source_id is valid for this call, and data owns
708        // the descriptor graph for this call.
709        maplibre_core::check(unsafe {
710            sys::mln_map_add_geojson_source_data(map, source_id.raw(), data.as_ptr())
711        })
712    }
713
714    /// Updates one GeoJSON source with inline data.
715    pub fn set_geojson_source_data(&self, source_id: &str, data: &GeoJson) -> Result<()> {
716        let map = self.inner.as_ptr()?;
717        let source_id = maplibre_core::string::string_view(source_id);
718        let data = data.try_to_native()?;
719        // SAFETY: map is live, source_id is valid for this call, and data owns
720        // the descriptor graph for this call.
721        maplibre_core::check(unsafe {
722            sys::mln_map_set_geojson_source_data(map, source_id.raw(), data.as_ptr())
723        })
724    }
725
726    /// Adds one style layer from a full style-spec layer JSON object.
727    pub fn add_style_layer_json(
728        &self,
729        layer_json: &JsonValue,
730        before_layer_id: Option<&str>,
731    ) -> Result<()> {
732        let map = self.inner.as_ptr()?;
733        let layer_json = layer_json.try_to_native()?;
734        let before_layer_id = maplibre_core::string::string_view(before_layer_id.unwrap_or(""));
735        // SAFETY: map is live, layer_json owns the descriptor graph, and
736        // before_layer_id is an explicit-length view valid for this call.
737        maplibre_core::check(unsafe {
738            sys::mln_map_add_style_layer_json(map, layer_json.as_ptr(), before_layer_id.raw())
739        })
740    }
741
742    /// Adds a hillshade layer for a raster DEM source.
743    pub fn add_hillshade_layer(
744        &self,
745        layer_id: &str,
746        source_id: &str,
747        before_layer_id: Option<&str>,
748    ) -> Result<()> {
749        let map = self.inner.as_ptr()?;
750        let layer_id = maplibre_core::string::string_view(layer_id);
751        let source_id = maplibre_core::string::string_view(source_id);
752        let before_layer_id = maplibre_core::string::string_view(before_layer_id.unwrap_or(""));
753        // SAFETY: map is live, and all string views are valid for this call.
754        maplibre_core::check(unsafe {
755            sys::mln_map_add_hillshade_layer(
756                map,
757                layer_id.raw(),
758                source_id.raw(),
759                before_layer_id.raw(),
760            )
761        })
762    }
763
764    /// Adds a color-relief layer for a raster DEM source.
765    pub fn add_color_relief_layer(
766        &self,
767        layer_id: &str,
768        source_id: &str,
769        before_layer_id: Option<&str>,
770    ) -> Result<()> {
771        let map = self.inner.as_ptr()?;
772        let layer_id = maplibre_core::string::string_view(layer_id);
773        let source_id = maplibre_core::string::string_view(source_id);
774        let before_layer_id = maplibre_core::string::string_view(before_layer_id.unwrap_or(""));
775        // SAFETY: map is live, and all string views are valid for this call.
776        maplibre_core::check(unsafe {
777            sys::mln_map_add_color_relief_layer(
778                map,
779                layer_id.raw(),
780                source_id.raw(),
781                before_layer_id.raw(),
782            )
783        })
784    }
785
786    /// Adds a source-free location indicator layer.
787    pub fn add_location_indicator_layer(
788        &self,
789        layer_id: &str,
790        before_layer_id: Option<&str>,
791    ) -> Result<()> {
792        let map = self.inner.as_ptr()?;
793        let layer_id = maplibre_core::string::string_view(layer_id);
794        let before_layer_id = maplibre_core::string::string_view(before_layer_id.unwrap_or(""));
795        // SAFETY: map is live, and string views are valid for this call.
796        maplibre_core::check(unsafe {
797            sys::mln_map_add_location_indicator_layer(map, layer_id.raw(), before_layer_id.raw())
798        })
799    }
800
801    /// Sets a location indicator layer location.
802    pub fn set_location_indicator_location(
803        &self,
804        layer_id: &str,
805        coordinate: LatLng,
806        altitude: f64,
807    ) -> Result<()> {
808        let map = self.inner.as_ptr()?;
809        let layer_id = maplibre_core::string::string_view(layer_id);
810        // SAFETY: map is live, layer_id is valid for this call, and coordinate
811        // is passed by value.
812        maplibre_core::check(unsafe {
813            sys::mln_map_set_location_indicator_location(
814                map,
815                layer_id.raw(),
816                coordinate.to_native(),
817                altitude,
818            )
819        })
820    }
821
822    /// Sets a location indicator layer bearing in degrees.
823    pub fn set_location_indicator_bearing(&self, layer_id: &str, bearing: f64) -> Result<()> {
824        let map = self.inner.as_ptr()?;
825        let layer_id = maplibre_core::string::string_view(layer_id);
826        // SAFETY: map is live and layer_id is valid for this call.
827        maplibre_core::check(unsafe {
828            sys::mln_map_set_location_indicator_bearing(map, layer_id.raw(), bearing)
829        })
830    }
831
832    /// Sets a location indicator layer accuracy radius in logical pixels.
833    pub fn set_location_indicator_accuracy_radius(
834        &self,
835        layer_id: &str,
836        radius: f64,
837    ) -> Result<()> {
838        let map = self.inner.as_ptr()?;
839        let layer_id = maplibre_core::string::string_view(layer_id);
840        // SAFETY: map is live and layer_id is valid for this call.
841        maplibre_core::check(unsafe {
842            sys::mln_map_set_location_indicator_accuracy_radius(map, layer_id.raw(), radius)
843        })
844    }
845
846    /// Sets one location indicator image-name property.
847    pub fn set_location_indicator_image_name(
848        &self,
849        layer_id: &str,
850        image_kind: LocationIndicatorImageKind,
851        image_id: &str,
852    ) -> Result<()> {
853        let map = self.inner.as_ptr()?;
854        let layer_id = maplibre_core::string::string_view(layer_id);
855        let image_id = maplibre_core::string::string_view(image_id);
856        // SAFETY: map is live, string views are valid for this call, and
857        // image_kind is a valid C enum value.
858        maplibre_core::check(unsafe {
859            sys::mln_map_set_location_indicator_image_name(
860                map,
861                layer_id.raw(),
862                image_kind.raw_value(),
863                image_id.raw(),
864            )
865        })
866    }
867
868    /// Copies one style layer as a full style-spec JSON object.
869    pub fn style_layer_json(&self, layer_id: &str) -> Result<Option<JsonValue>> {
870        let map = self.inner.as_ptr()?;
871        let layer_id = maplibre_core::string::string_view(layer_id);
872        let mut out = maplibre_core::ptr::OutPtr::<sys::mln_json_snapshot>::new();
873        let mut found = false;
874        // SAFETY: map is live, layer_id is valid for this call, out is a
875        // null-initialized out-pointer, and found points to writable storage.
876        maplibre_core::check(unsafe {
877            sys::mln_map_get_style_layer_json(map, layer_id.raw(), out.as_mut_ptr(), &mut found)
878        })?;
879        // SAFETY: On success, the C API returns either null or an owned JSON
880        // snapshot handle for this call; core copies and releases it.
881        let snapshot = unsafe { maplibre_core::json::copy_json_snapshot(out.into_option()) }?;
882        if found { Ok(snapshot) } else { Ok(None) }
883    }
884
885    /// Sets the style light from a style-spec light JSON object.
886    pub fn set_style_light_json(&self, light_json: &JsonValue) -> Result<()> {
887        let map = self.inner.as_ptr()?;
888        let light_json = light_json.try_to_native()?;
889        // SAFETY: map is live and light_json owns the descriptor graph for this call.
890        maplibre_core::check(unsafe { sys::mln_map_set_style_light_json(map, light_json.as_ptr()) })
891    }
892
893    /// Sets one style light property.
894    pub fn set_style_light_property(&self, property_name: &str, value: &JsonValue) -> Result<()> {
895        let map = self.inner.as_ptr()?;
896        let property_name = maplibre_core::string::string_view(property_name);
897        let value = value.try_to_native()?;
898        // SAFETY: map is live, property_name is valid for this call, and value
899        // owns the descriptor graph for this call.
900        maplibre_core::check(unsafe {
901            sys::mln_map_set_style_light_property(map, property_name.raw(), value.as_ptr())
902        })
903    }
904
905    /// Copies one style light property as a style-spec JSON value.
906    pub fn style_light_property(&self, property_name: &str) -> Result<Option<JsonValue>> {
907        let map = self.inner.as_ptr()?;
908        let property_name = maplibre_core::string::string_view(property_name);
909        let mut out = maplibre_core::ptr::OutPtr::<sys::mln_json_snapshot>::new();
910        // SAFETY: map is live, property_name is valid for this call, and out is
911        // a null-initialized out-pointer.
912        maplibre_core::check(unsafe {
913            sys::mln_map_get_style_light_property(map, property_name.raw(), out.as_mut_ptr())
914        })?;
915        // SAFETY: On success, the C API returns either null or an owned JSON
916        // snapshot handle for this call; core copies and releases it.
917        unsafe { maplibre_core::json::copy_json_snapshot(out.into_option()) }
918    }
919
920    /// Sets one layer style property.
921    pub fn set_layer_property(
922        &self,
923        layer_id: &str,
924        property_name: &str,
925        value: &JsonValue,
926    ) -> Result<()> {
927        let map = self.inner.as_ptr()?;
928        let layer_id = maplibre_core::string::string_view(layer_id);
929        let property_name = maplibre_core::string::string_view(property_name);
930        let value = value.try_to_native()?;
931        // SAFETY: map is live, string views are valid for this call, and value
932        // owns the descriptor graph for this call.
933        maplibre_core::check(unsafe {
934            sys::mln_map_set_layer_property(
935                map,
936                layer_id.raw(),
937                property_name.raw(),
938                value.as_ptr(),
939            )
940        })
941    }
942
943    /// Copies one layer style property as a style-spec JSON value.
944    pub fn layer_property(&self, layer_id: &str, property_name: &str) -> Result<Option<JsonValue>> {
945        let map = self.inner.as_ptr()?;
946        let layer_id = maplibre_core::string::string_view(layer_id);
947        let property_name = maplibre_core::string::string_view(property_name);
948        let mut out = maplibre_core::ptr::OutPtr::<sys::mln_json_snapshot>::new();
949        // SAFETY: map is live, string views are valid for this call, and out is
950        // a null-initialized out-pointer.
951        maplibre_core::check(unsafe {
952            sys::mln_map_get_layer_property(
953                map,
954                layer_id.raw(),
955                property_name.raw(),
956                out.as_mut_ptr(),
957            )
958        })?;
959        // SAFETY: On success, the C API returns either null or an owned JSON
960        // snapshot handle for this call; core copies and releases it.
961        unsafe { maplibre_core::json::copy_json_snapshot(out.into_option()) }
962    }
963
964    /// Sets or clears one layer filter.
965    pub fn set_layer_filter(&self, layer_id: &str, filter: Option<&JsonValue>) -> Result<()> {
966        let map = self.inner.as_ptr()?;
967        let layer_id = maplibre_core::string::string_view(layer_id);
968        let native_filter = filter.map(JsonValue::try_to_native).transpose()?;
969        // SAFETY: map is live, layer_id is valid for this call, and the
970        // optional filter descriptor is either null or valid for this call.
971        maplibre_core::check(unsafe {
972            sys::mln_map_set_layer_filter(
973                map,
974                layer_id.raw(),
975                native_filter
976                    .as_ref()
977                    .map_or(ptr::null(), |filter| filter.as_ptr()),
978            )
979        })
980    }
981
982    /// Copies one layer filter as a style-spec JSON value.
983    pub fn layer_filter(&self, layer_id: &str) -> Result<Option<JsonValue>> {
984        let map = self.inner.as_ptr()?;
985        let layer_id = maplibre_core::string::string_view(layer_id);
986        let mut out = maplibre_core::ptr::OutPtr::<sys::mln_json_snapshot>::new();
987        // SAFETY: map is live, layer_id is valid for this call, and out is a
988        // null-initialized out-pointer.
989        maplibre_core::check(unsafe {
990            sys::mln_map_get_layer_filter(map, layer_id.raw(), out.as_mut_ptr())
991        })?;
992        // SAFETY: On success, the C API returns either null or an owned JSON
993        // snapshot handle for this call; core copies and releases it.
994        unsafe { maplibre_core::json::copy_json_snapshot(out.into_option()) }
995    }
996
997    /// Copies current style source IDs into owned Rust strings.
998    pub fn style_source_ids(&self) -> Result<Vec<String>> {
999        let map = self.inner.as_ptr()?;
1000        let mut out = maplibre_core::ptr::OutPtr::<sys::mln_style_id_list>::new();
1001        // SAFETY: map is live and out is a null-initialized out-pointer owned by
1002        // this call. On success the returned handle is wrapped and destroyed by
1003        // the copying helper below.
1004        maplibre_core::check(unsafe { sys::mln_map_list_style_source_ids(map, out.as_mut_ptr()) })?;
1005        // SAFETY: On success, the C API returns an owned style ID list handle;
1006        // core copies and releases it.
1007        unsafe { maplibre_core::style::copy_style_id_list(out.into_non_null("mln_style_id_list")?) }
1008    }
1009
1010    /// Copies current style layer IDs into owned Rust strings.
1011    pub fn style_layer_ids(&self) -> Result<Vec<String>> {
1012        let map = self.inner.as_ptr()?;
1013        let mut out = maplibre_core::ptr::OutPtr::<sys::mln_style_id_list>::new();
1014        // SAFETY: map is live and out is a null-initialized out-pointer owned by
1015        // this call. On success the returned handle is wrapped and destroyed by
1016        // the copying helper below.
1017        maplibre_core::check(unsafe { sys::mln_map_list_style_layer_ids(map, out.as_mut_ptr()) })?;
1018        // SAFETY: On success, the C API returns an owned style ID list handle;
1019        // core copies and releases it.
1020        unsafe { maplibre_core::style::copy_style_id_list(out.into_non_null("mln_style_id_list")?) }
1021    }
1022}