use geo_types::{
coord, CoordNum, Geometry, GeometryCollection, LineString, MultiLineString, Point, Polygon,
};
use geos::{CoordSeq, Geom};
use kurbo::PathEl;
use num_traits::real::Real;
use std::error::Error;
pub mod nannou;
pub mod hatch;
pub mod svg;
pub mod clip;
pub mod buffer;
pub mod flatten;
pub mod boolean;
pub mod matrix;
pub mod shapes;
pub trait PointDistance<T: CoordNum> {
fn distance(&self, other: &Point<T>) -> T;
fn length(&self) -> T;
}
pub trait ToGTGeometry {
fn to_gt_geometry(&self, accuracy: f64) -> Result<Geometry<f64>, Box<dyn Error>>;
}
impl ToGTGeometry for kurbo::BezPath {
fn to_gt_geometry(&self, accuracy: f64) -> Result<Geometry<f64>, Box<dyn Error>> {
let mut segments: MultiLineString<f64> = MultiLineString::new(vec![]);
let mut lastpoint = kurbo::Point::new(0.0, 0.0);
let add_segment = |el: PathEl| match el {
PathEl::MoveTo(pos) => {
segments
.0
.push(LineString::new(vec![coord! {x: pos.x, y: pos.y}]));
lastpoint = pos.clone();
}
PathEl::LineTo(pos) => {
if let Some(line) = segments.0.last_mut() {
line.0.push(coord! {x: pos.x, y: pos.y});
}
}
PathEl::ClosePath => {
if let Some(line) = segments.0.last_mut() {
line.0.push(coord! {x: lastpoint.x, y: lastpoint.y});
}
}
_ => panic!("Unexpected/Impossible segment type interpolating a bezier path!"),
};
self.flatten(accuracy.into(), add_segment);
let tmp_gtgeo = Geometry::MultiLineString(segments);
let tmp_geos = tmp_gtgeo.to_geos();
Ok(match tmp_geos {
Ok(geos_geom) => {
if let Ok((poly_geo, _cuts_geo, dangles_geo, invalid_geo)) =
geos_geom.polygonize_full()
{
let out_gtgeo =
Geometry::GeometryCollection(GeometryCollection::new_from(vec![
Geometry::try_from(&poly_geo).unwrap_or(Geometry::GeometryCollection(
GeometryCollection::new_from(vec![]),
)),
match invalid_geo {
Some(invalid) => Geometry::try_from(&invalid).unwrap_or(
Geometry::GeometryCollection(GeometryCollection::new_from(
vec![],
)),
),
None => Geometry::GeometryCollection(GeometryCollection::new_from(
vec![],
)),
},
match dangles_geo {
Some(dangles) => Geometry::try_from(&dangles).unwrap_or(
Geometry::GeometryCollection(GeometryCollection::new_from(
vec![],
)),
),
None => Geometry::GeometryCollection(GeometryCollection::new_from(
vec![],
)),
},
]));
out_gtgeo
} else {
tmp_gtgeo.clone()
}
}
Err(_err) => {
tmp_gtgeo.clone()
}
})
}
}
impl<T> PointDistance<T> for Point<T>
where
T: CoordNum,
T: Real,
{
fn distance(&self, other: &Point<T>) -> T {
let p = *self - *other;
p.length()
}
fn length(&self) -> T {
(self.x().powi(2) + self.y().powi(2)).sqrt()
}
}
pub trait ToGeos {
fn to_geos(&self) -> Result<geos::Geometry<'_>, Box<dyn Error>>;
}
impl ToGeos for geo_types::Geometry<f64> {
fn to_geos(&self) -> Result<geos::Geometry<'_>, Box<dyn Error>> {
if let Geometry::GeometryCollection(collection) = self {
let geomap: Vec<geos::Geometry> = collection
.iter()
.map(|item| {
item.to_geos()
.or(geos::Geometry::create_empty_collection(
geos::GeometryTypes::GeometryCollection,
))
.unwrap()
})
.collect();
if let Ok(geosmap) = geos::Geometry::create_geometry_collection(geomap) {
return Ok(geosmap);
} else {
return Err(Box::new(geos::Error::InvalidGeometry(
"Wrong type of geometry".into(),
)));
}
}
Ok(match self {
Geometry::Point(p) => geos::Geometry::try_from(p),
Geometry::Line(line) => geos::Geometry::create_line_string(
CoordSeq::new_from_vec(&vec![
vec![line.start.x, line.start.y],
vec![line.end.x, line.end.y],
])
.expect("Unexpected failure of create_line_string"),
),
Geometry::Rect(rect) => geos::Geometry::try_from(Polygon::new(
LineString::from(vec![
rect.min(),
coord! {x: rect.max().x, y: rect.min().y},
rect.max(),
coord! {x: rect.min().x, y: rect.max().y},
rect.min(),
]),
vec![],
)),
Geometry::LineString(line) => geos::Geometry::try_from(line),
Geometry::Polygon(poly) => geos::Geometry::try_from(poly),
Geometry::MultiPolygon(polys) => geos::Geometry::try_from(polys),
Geometry::MultiLineString(mls) => geos::Geometry::create_multiline_string(
mls.0
.clone()
.iter()
.map(|line| {
geos::Geometry::try_from(line)
.unwrap_or(geos::Geometry::create_empty_line_string().unwrap())
})
.collect(),
),
_ => Err(geos::Error::InvalidGeometry(
"Wrong type of geometry".into(),
)),
}?)
}
}
#[cfg(test)]
mod tests {
use super::PointDistance;
use geo_types::Point;
use num_traits::abs;
#[test]
fn test_length() {
let p = Point::new(10.0f64, 0.0f64);
assert!(abs(p.length() - 10.0) < 0.0001)
}
#[test]
fn test_distance() {
let d = Point::new(10.0, 0.0).distance(&Point::new(0.0, 10.0));
assert!(abs(d - (10.0f64.powi(2) + 10.0f64.powi(2)).sqrt()) < 0.0001)
}
}