use std::ffi::{CStr, CString};
use std::mem::MaybeUninit;
use std::ptr::NonNull;
use libc::c_int;
use lwgeom_sys::*;
use crate::lwgeom_parser_result::LWGeomParserResult;
use crate::lwpoly::LWPoly;
use crate::{GBox, LWGeomError, Result};
pub struct LWGeom {
inner: NonNull<LWGEOM>,
}
impl LWGeom {
pub(crate) fn new_from_raw(p_geom: *mut LWGEOM) -> Result<Self> {
Ok(Self {
inner: NonNull::new(p_geom).ok_or(LWGeomError::NullPtrError)?,
})
}
fn inner_ptr(&self) -> *mut LWGEOM {
self.inner.as_ptr()
}
pub fn from_text(wkt: &str, srid: Option<i32>) -> Result<Self> {
let c_wkt = CString::new(wkt)?;
let p_wkt = c_wkt.into_raw();
let parser_result = LWGeomParserResult::new()?;
let result = unsafe {
lwgeom_parse_wkt(
parser_result.inner_ptr(),
p_wkt,
LW_PARSER_CHECK_ALL as c_int,
)
};
let _ = unsafe { CString::from_raw(p_wkt) };
if result == LW_FAILURE as c_int {
return Err(LWGeomError::WKTParseError(parser_result.message()?));
}
let geom = parser_result.geom()?;
if geom.has_srid() {
panic!("OGC WKT expected, EWKT provided - use from_ewkt() for this")
}
if let Some(srid) = srid {
geom.set_srid(srid);
}
Ok(geom)
}
pub fn from_ewkt(wkt: &str) -> Result<Self> {
let c_wkt = CString::new(wkt)?;
let p_wkt = c_wkt.into_raw();
let parser_result = LWGeomParserResult::new()?;
let result = unsafe {
lwgeom_parse_wkt(
parser_result.inner_ptr(),
p_wkt,
LW_PARSER_CHECK_ALL as c_int,
)
};
let _ = unsafe { CString::from_raw(p_wkt) };
if result == LW_FAILURE as c_int {
return Err(LWGeomError::WKTParseError(parser_result.message()?));
}
parser_result.geom()
}
pub fn as_text(&self, precision: Option<i32>) -> Result<String> {
let precision = precision.unwrap_or(15);
let mut unint: MaybeUninit<usize> = MaybeUninit::uninit();
let p_sz = unint.as_mut_ptr();
let p_wkt = unsafe { lwgeom_to_wkt(self.inner_ptr(), WKT_ISO as u8, precision, p_sz) };
let sz = unsafe { unint.assume_init() };
let c_wkt = unsafe {
CStr::from_bytes_with_nul_unchecked(core::slice::from_raw_parts(p_wkt.cast(), sz))
};
Ok(c_wkt.to_string_lossy().to_string())
}
pub fn as_ewkt(&self, precision: Option<i32>) -> Result<String> {
let precision = precision.unwrap_or(15);
let mut unint: MaybeUninit<usize> = MaybeUninit::uninit();
let p_sz = unint.as_mut_ptr();
let p_wkt = unsafe { lwgeom_to_wkt(self.inner_ptr(), WKT_EXTENDED as u8, precision, p_sz) };
let sz = unsafe { unint.assume_init() };
let c_wkt = unsafe {
CStr::from_bytes_with_nul_unchecked(core::slice::from_raw_parts(p_wkt.cast(), sz))
};
Ok(c_wkt.to_string_lossy().to_string())
}
pub fn split(&self, blade: &LWGeom) -> Result<Self> {
let p_geom = unsafe { lwgeom_split(self.inner_ptr(), blade.inner_ptr()) };
Self::new_from_raw(p_geom)
}
pub fn has_srid(&self) -> bool {
unsafe { lwgeom_has_srid(self.inner_ptr()) != 0 }
}
pub fn get_srid(&self) -> Option<i32> {
if self.has_srid() {
Some(unsafe { lwgeom_get_srid(self.inner_ptr()) })
} else {
None
}
}
pub fn set_srid(&self, srid: i32) {
unsafe { lwgeom_set_srid(self.inner_ptr(), srid) }
}
pub fn get_bbox(&self) -> Result<GBox> {
let p_bbox = unsafe { lwgeom_get_bbox(self.inner_ptr()) };
GBox::new_from_raw(p_bbox.cast_mut())
}
pub fn tile_envelope(
zoom: i32, x: i32, y: i32, bounds: Option<&LWGeom>, margin: Option<f64>,
) -> Result<Self> {
let bounds = match bounds {
Some(bounds) => bounds,
None => &Self::from_ewkt("SRID=3857;LINESTRING(-20037508.342789 -20037508.342789,20037508.342789 20037508.342789)").unwrap(),
};
let bbox = bounds.get_bbox()?;
let srid = bounds.get_srid().unwrap_or(3857);
let margin = margin.unwrap_or(0.0);
if margin < -0.5 {
return Err(LWGeomError::InvalidParameterError(
"ST_TileEnvelope".to_owned(),
"margin".to_owned(),
));
}
let bounds_width = bbox.xmax() - bbox.xmin();
let bounds_height = bbox.ymax() - bbox.ymin();
if bounds_width <= 0.0 || bounds_height <= 0.0 {
return Err(LWGeomError::InvalidParameterError(
"ST_TileEnvelope".to_owned(),
"bounds".to_owned(),
));
}
if !(0..32).contains(&zoom) {
return Err(LWGeomError::InvalidParameterError(
"ST_TileEnvelope".to_owned(),
"zoom".to_owned(),
));
}
let world_tile_size = 1 << zoom.min(31);
if x < 0 || x >= world_tile_size {
return Err(LWGeomError::InvalidParameterError(
"ST_TileEnvelope".to_owned(),
"x".to_owned(),
));
}
if y < 0 || y >= world_tile_size {
return Err(LWGeomError::InvalidParameterError(
"ST_TileEnvelope".to_owned(),
"y".to_owned(),
));
}
let tile_geo_size_x = bounds_width / world_tile_size as f64;
let tile_geo_size_y = bounds_height / world_tile_size as f64;
let (x1, x2) = if (1.0 + margin * 2.0) > world_tile_size as f64 {
(bbox.xmin(), bbox.xmax())
} else {
(
bbox.xmin() + tile_geo_size_x * (x as f64 - margin),
bbox.xmin() + tile_geo_size_x * (x as f64 + 1.0 + margin),
)
};
let mut y1 = bbox.ymax() - tile_geo_size_y * (y as f64 + 1.0 + margin);
let mut y2 = bbox.ymax() - tile_geo_size_y * (y as f64 - margin);
if y1 < bbox.ymin() {
y1 = bbox.ymin()
}
if y2 > bbox.ymax() {
y2 = bbox.ymax()
}
LWPoly::construct_envelope(srid, x1, y1, x2, y2).and_then(|poly| poly.into_lwgeom())
}
}
unsafe impl Send for LWGeom {}
unsafe impl Sync for LWGeom {}