use serde::de;
use crate::{
Geometry, GeometryCollection, LineString, MultiLineString, MultiPoint, MultiPolygon, Point,
Polygon, Position,
};
fn position(coord: geo_types::Coord<f64>) -> Position {
vec![coord.x, coord.y]
}
fn line_string_positions(ls: geo_types::LineString<f64>) -> Vec<Position> {
ls.0.into_iter().map(position).collect()
}
fn polygon_rings(poly: geo_types::Polygon<f64>) -> Vec<Vec<Position>> {
let (exterior, interiors) = poly.into_inner();
let exterior = line_string_positions(exterior);
if exterior.is_empty() {
return vec![];
}
let mut rings = vec![exterior];
rings.extend(interiors.into_iter().map(line_string_positions));
rings
}
impl From<geo_types::Point<f64>> for Point {
fn from(p: geo_types::Point<f64>) -> Self {
Point::new(position(p.0))
}
}
impl From<geo_types::MultiPoint<f64>> for MultiPoint {
fn from(mp: geo_types::MultiPoint<f64>) -> Self {
MultiPoint::new(mp.0.into_iter().map(|p| position(p.0)).collect())
}
}
impl From<geo_types::LineString<f64>> for LineString {
fn from(ls: geo_types::LineString<f64>) -> Self {
LineString::new(line_string_positions(ls))
}
}
impl From<geo_types::MultiLineString<f64>> for MultiLineString {
fn from(mls: geo_types::MultiLineString<f64>) -> Self {
MultiLineString::new(mls.0.into_iter().map(line_string_positions).collect())
}
}
impl From<geo_types::Polygon<f64>> for Polygon {
fn from(poly: geo_types::Polygon<f64>) -> Self {
Polygon::new(polygon_rings(poly))
}
}
impl From<geo_types::MultiPolygon<f64>> for MultiPolygon {
fn from(mp: geo_types::MultiPolygon<f64>) -> Self {
MultiPolygon::new(mp.0.into_iter().map(polygon_rings).collect())
}
}
impl From<geo_types::GeometryCollection<f64>> for GeometryCollection {
fn from(gc: geo_types::GeometryCollection<f64>) -> Self {
GeometryCollection::new(gc.0.into_iter().map(Geometry::from).collect())
}
}
impl From<geo_types::Geometry<f64>> for Geometry {
fn from(g: geo_types::Geometry<f64>) -> Self {
use geo_types::Geometry as G;
match g {
G::Point(p) => Geometry::Point(p.into()),
G::MultiPoint(mp) => Geometry::MultiPoint(mp.into()),
G::LineString(ls) => Geometry::LineString(ls.into()),
G::MultiLineString(mls) => Geometry::MultiLineString(mls.into()),
G::Polygon(p) => Geometry::Polygon(p.into()),
G::MultiPolygon(mp) => Geometry::MultiPolygon(mp.into()),
G::GeometryCollection(gc) => Geometry::GeometryCollection(gc.into()),
G::Line(l) => {
Geometry::LineString(LineString::new(vec![position(l.start), position(l.end)]))
}
G::Rect(r) => Geometry::Polygon(r.to_polygon().into()),
G::Triangle(t) => Geometry::Polygon(t.to_polygon().into()),
}
}
}
fn arity_error(len: usize) -> serde_json::Error {
<serde_json::Error as de::Error>::custom(format!(
"geo_types needs at least 2 coordinates (x, y) per position, found {len}"
))
}
fn coord(pos: &Position) -> Result<geo_types::Coord<f64>, serde_json::Error> {
match pos.as_slice() {
[x, y, ..] => Ok(geo_types::Coord { x: *x, y: *y }),
_ => Err(arity_error(pos.len())),
}
}
fn geo_line_string(ring: &[Position]) -> Result<geo_types::LineString<f64>, serde_json::Error> {
ring.iter()
.map(coord)
.collect::<Result<Vec<_>, _>>()
.map(geo_types::LineString)
}
fn geo_polygon(rings: &[Vec<Position>]) -> Result<geo_types::Polygon<f64>, serde_json::Error> {
let exterior = match rings.first() {
Some(ring) => geo_line_string(ring)?,
None => geo_types::LineString(vec![]),
};
let interiors = rings
.iter()
.skip(1)
.map(|ring| geo_line_string(ring))
.collect::<Result<Vec<_>, _>>()?;
Ok(geo_types::Polygon::new(exterior, interiors))
}
impl TryFrom<Point> for geo_types::Point<f64> {
type Error = serde_json::Error;
fn try_from(p: Point) -> Result<Self, Self::Error> {
Ok(geo_types::Point(coord(&p.coordinates)?))
}
}
impl TryFrom<MultiPoint> for geo_types::MultiPoint<f64> {
type Error = serde_json::Error;
fn try_from(mp: MultiPoint) -> Result<Self, Self::Error> {
mp.coordinates
.iter()
.map(|c| coord(c).map(geo_types::Point))
.collect::<Result<Vec<_>, _>>()
.map(geo_types::MultiPoint)
}
}
impl TryFrom<LineString> for geo_types::LineString<f64> {
type Error = serde_json::Error;
fn try_from(ls: LineString) -> Result<Self, Self::Error> {
geo_line_string(&ls.coordinates)
}
}
impl TryFrom<MultiLineString> for geo_types::MultiLineString<f64> {
type Error = serde_json::Error;
fn try_from(mls: MultiLineString) -> Result<Self, Self::Error> {
mls.coordinates
.iter()
.map(|ring| geo_line_string(ring))
.collect::<Result<Vec<_>, _>>()
.map(geo_types::MultiLineString)
}
}
impl TryFrom<Polygon> for geo_types::Polygon<f64> {
type Error = serde_json::Error;
fn try_from(p: Polygon) -> Result<Self, Self::Error> {
geo_polygon(&p.coordinates)
}
}
impl TryFrom<MultiPolygon> for geo_types::MultiPolygon<f64> {
type Error = serde_json::Error;
fn try_from(mp: MultiPolygon) -> Result<Self, Self::Error> {
mp.coordinates
.iter()
.map(|poly| geo_polygon(poly))
.collect::<Result<Vec<_>, _>>()
.map(geo_types::MultiPolygon)
}
}
impl TryFrom<GeometryCollection> for geo_types::GeometryCollection<f64> {
type Error = serde_json::Error;
fn try_from(gc: GeometryCollection) -> Result<Self, Self::Error> {
gc.geometries
.into_iter()
.map(geo_types::Geometry::try_from)
.collect::<Result<Vec<_>, _>>()
.map(geo_types::GeometryCollection)
}
}
impl TryFrom<Geometry> for geo_types::Geometry<f64> {
type Error = serde_json::Error;
fn try_from(g: Geometry) -> Result<Self, Self::Error> {
Ok(match g {
Geometry::Point(p) => geo_types::Geometry::Point(p.try_into()?),
Geometry::MultiPoint(mp) => geo_types::Geometry::MultiPoint(mp.try_into()?),
Geometry::LineString(ls) => geo_types::Geometry::LineString(ls.try_into()?),
Geometry::MultiLineString(mls) => geo_types::Geometry::MultiLineString(mls.try_into()?),
Geometry::Polygon(p) => geo_types::Geometry::Polygon(p.try_into()?),
Geometry::MultiPolygon(mp) => geo_types::Geometry::MultiPolygon(mp.try_into()?),
Geometry::GeometryCollection(gc) => {
geo_types::Geometry::GeometryCollection(gc.try_into()?)
}
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn point_round_trips_through_geo_types() {
let ours = Point::new(vec![-96.8, 32.8]);
let geo: geo_types::Point<f64> = ours.clone().try_into().unwrap();
assert_eq!(geo, geo_types::Point::new(-96.8, 32.8));
assert_eq!(Point::from(geo), ours);
}
#[test]
fn elevation_is_dropped_like_upstream() {
let p3d = Point::new(vec![1.0, 2.0, 3.0]);
let geo: geo_types::Point<f64> = p3d.try_into().unwrap();
assert_eq!(geo, geo_types::Point::new(1.0, 2.0));
}
#[test]
fn short_position_is_an_error() {
let bad = Point::new(vec![1.0]);
let err = geo_types::Point::<f64>::try_from(bad).unwrap_err();
assert!(err.to_string().contains("at least 2"));
}
#[test]
fn polygon_rings_survive_both_directions() {
let ours = Polygon::new(vec![
vec![
vec![0.0, 0.0],
vec![4.0, 0.0],
vec![4.0, 4.0],
vec![0.0, 4.0],
vec![0.0, 0.0],
],
vec![
vec![1.0, 1.0],
vec![2.0, 1.0],
vec![1.0, 2.0],
vec![1.0, 1.0],
],
]);
let geo: geo_types::Polygon<f64> = ours.clone().try_into().unwrap();
assert_eq!(geo.interiors().len(), 1);
assert_eq!(Polygon::from(geo), ours);
}
#[test]
fn geo_line_and_triangle_degrade_to_geojson_shapes() {
let line = geo_types::Line::new(
geo_types::Coord { x: 0.0, y: 0.0 },
geo_types::Coord { x: 1.0, y: 1.0 },
);
assert!(matches!(
Geometry::from(geo_types::Geometry::Line(line)),
Geometry::LineString(_)
));
let tri = geo_types::Triangle::new(
geo_types::Coord { x: 0.0, y: 0.0 },
geo_types::Coord { x: 2.0, y: 0.0 },
geo_types::Coord { x: 1.0, y: 1.0 },
);
assert!(matches!(
Geometry::from(geo_types::Geometry::Triangle(tri)),
Geometry::Polygon(_)
));
}
#[test]
fn geometry_union_round_trips() {
let ours = Geometry::MultiPoint(MultiPoint::new(vec![vec![0.0, 0.0], vec![1.0, 1.0]]));
let geo: geo_types::Geometry<f64> = ours.clone().try_into().unwrap();
assert_eq!(Geometry::from(geo), ours);
}
}