use std::f64::consts::PI;
use std::fmt::Debug;
use std::marker::PhantomData;
use std::path::Path;
use cxx::UniquePtr;
use image::{ImageBuffer, Rgba};
use crate::renderer::bridge::ffi;
use crate::renderer::MapDebugOptions;
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct Image(ImageBuffer<Rgba<u8>, Vec<u8>>);
impl Image {
pub(crate) fn from_raw(bytes: &[u8]) -> Option<Self> {
if bytes.len() < 8 {
return None;
}
let width = u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
let height = u32::from_ne_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
let data = bytes[8..].to_vec();
ImageBuffer::from_vec(width, height, data).map(Image)
}
#[must_use]
pub fn as_image(&self) -> &ImageBuffer<Rgba<u8>, Vec<u8>> {
&self.0
}
}
#[derive(Debug)]
pub struct Static;
#[derive(Debug)]
pub struct Tile;
pub struct ImageRenderer<S> {
pub(crate) instance: UniquePtr<ffi::MapRenderer>,
pub(crate) _marker: PhantomData<S>,
pub(crate) style_specified: bool,
}
impl<S> Debug for ImageRenderer<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ImageRenderer")
.field("style_specified", &self.style_specified)
.finish_non_exhaustive()
}
}
impl<S> ImageRenderer<S> {
pub fn load_style_from_url(&mut self, url: &url::Url) -> &mut Self {
self.style_specified = true;
ffi::MapRenderer_getStyle_loadURL(self.instance.pin_mut(), url.as_ref());
self
}
pub fn load_style_from_path(
&mut self,
path: impl AsRef<Path>,
) -> Result<&mut Self, std::io::Error> {
let path = path.as_ref();
if !path.is_file() {
return Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
format!("Path {} is not a file", path.display()),
));
}
let Some(path) = path.to_str() else {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Path {} is not valid UTF-8", path.display()),
));
};
self.style_specified = true;
ffi::MapRenderer_getStyle_loadURL(self.instance.pin_mut(), &format!("file://{path}"));
Ok(self)
}
pub fn set_debug_flags(&mut self, flags: MapDebugOptions) -> &mut Self {
ffi::MapRenderer_setDebugFlags(self.instance.pin_mut(), flags);
self
}
}
impl ImageRenderer<Static> {
pub fn render_static(
&mut self,
lat: f64,
lon: f64,
zoom: f64,
bearing: f64,
pitch: f64,
) -> Result<Image, RenderingError> {
if !self.style_specified {
return Err(RenderingError::StyleNotSpecified);
}
ffi::MapRenderer_setCamera(self.instance.pin_mut(), lat, lon, zoom, bearing, pitch);
let data = ffi::MapRenderer_render(self.instance.pin_mut());
let bytes = data.as_bytes();
let image = Image::from_raw(bytes).ok_or(RenderingError::InvalidImageData)?;
Ok(image)
}
}
impl ImageRenderer<Tile> {
pub fn render_tile(&mut self, zoom: u8, x: u32, y: u32) -> Result<Image, RenderingError> {
if !self.style_specified {
return Err(RenderingError::StyleNotSpecified);
}
let (lat, lon) = coords_to_lat_lon(f64::from(zoom), x, y);
ffi::MapRenderer_setCamera(self.instance.pin_mut(), lat, lon, f64::from(zoom), 0.0, 0.0);
let data = ffi::MapRenderer_render(self.instance.pin_mut());
let bytes = data.as_bytes();
let image = Image::from_raw(bytes).ok_or(RenderingError::InvalidImageData)?;
Ok(image)
}
}
#[allow(clippy::cast_precision_loss)]
fn coords_to_lat_lon(zoom: f64, x: u32, y: u32) -> (f64, f64) {
let zz = 2_f64.powf(zoom);
let lng = (f64::from(x) + 0.5) / zz * 360_f64 - 180_f64;
let lat = ((PI * (1_f64 - 2_f64 * (f64::from(y) + 0.5) / zz)).sinh())
.atan()
.to_degrees();
(lat, lng)
}
#[derive(thiserror::Error, Debug)]
pub enum RenderingError {
#[error("Style must be specified to render a tile")]
StyleNotSpecified,
#[error("Invalid image data received from renderer")]
InvalidImageData,
}