Skip to main content

maplibre_native/map/
style.rs

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