use alloc::vec::Vec;
use geometry_cs::Cartesian;
use geometry_model::{
DynGeometry, Linestring, MultiLinestring, MultiPoint, MultiPolygon, Point2D, Polygon, Ring,
};
use crate::json::{GeoJsonError, JsonValue, parse_geojson};
type Pt = Point2D<f64, Cartesian>;
pub fn from_geojson<S: AsRef<str>>(input: S) -> Result<DynGeometry<f64, Cartesian>, GeoJsonError> {
let value = parse_geojson(input.as_ref())?;
parse_geometry(&value)
}
fn parse_geometry(value: &JsonValue) -> Result<DynGeometry<f64, Cartesian>, GeoJsonError> {
let type_name = value
.get("type")
.and_then(JsonValue::as_str)
.ok_or(GeoJsonError::ExpectedType)?;
match type_name {
"Point" => Ok(DynGeometry::Point(read_point_value(coordinate(value)?)?)),
"LineString" => Ok(DynGeometry::LineString(Linestring(read_positions(
coords(value)?,
)?))),
"Polygon" => Ok(DynGeometry::Polygon(read_polygon(coords(value)?)?)),
"MultiPoint" => {
let points = read_positions(coords(value)?)?;
Ok(DynGeometry::MultiPoint(MultiPoint(points)))
}
"MultiLineString" => read_multi_linestring(coords(value)?),
"MultiPolygon" => read_multi_polygon(coords(value)?),
"GeometryCollection" => read_geometry_collection(value),
"Feature" | "FeatureCollection" => Err(GeoJsonError::UnsupportedType(
alloc::string::String::from(type_name),
)),
other => Err(GeoJsonError::UnknownGeometryType(
alloc::string::String::from(other),
)),
}
}
fn coords(value: &JsonValue) -> Result<&[JsonValue], GeoJsonError> {
coordinate(value)?
.as_array()
.ok_or(GeoJsonError::MalformedCoordinates)
}
fn coordinate(value: &JsonValue) -> Result<&JsonValue, GeoJsonError> {
value
.get("coordinates")
.ok_or(GeoJsonError::MalformedCoordinates)
}
fn read_point(position: &[JsonValue]) -> Result<Pt, GeoJsonError> {
if position.len() < 2 {
return Err(GeoJsonError::MalformedCoordinates);
}
let x = position[0]
.as_f64()
.ok_or(GeoJsonError::MalformedCoordinates)?;
let y = position[1]
.as_f64()
.ok_or(GeoJsonError::MalformedCoordinates)?;
Ok(Point2D::new(x, y))
}
fn read_point_value(position: &JsonValue) -> Result<Pt, GeoJsonError> {
if let Some([x, y]) = position.as_position() {
return Ok(Point2D::new(x, y));
}
read_point(
position
.as_array()
.ok_or(GeoJsonError::MalformedCoordinates)?,
)
}
fn read_positions(positions: &[JsonValue]) -> Result<Vec<Pt>, GeoJsonError> {
positions.iter().map(read_point_value).collect()
}
fn read_polygon(rings: &[JsonValue]) -> Result<Polygon<Pt>, GeoJsonError> {
read_polygon_value(rings)
}
fn read_polygon_value(rings: &[JsonValue]) -> Result<Polygon<Pt>, GeoJsonError> {
let mut iter = rings.iter();
let outer = match iter.next() {
Some(r) => {
let pts = read_positions(r.as_array().ok_or(GeoJsonError::MalformedCoordinates)?)?;
Ring::from_vec(pts)
}
None => Ring::new(),
};
let inners = iter
.map(|r| r.as_array().ok_or(GeoJsonError::MalformedCoordinates))
.map(|r| r.and_then(read_positions).map(Ring::from_vec))
.collect::<Result<Vec<Ring<Pt>>, GeoJsonError>>()?;
Ok(Polygon::with_inners(outer, inners))
}
fn read_multi_linestring(lines: &[JsonValue]) -> Result<DynGeometry<f64, Cartesian>, GeoJsonError> {
let linestrings = lines
.iter()
.map(|l| l.as_array().ok_or(GeoJsonError::MalformedCoordinates))
.map(|l| l.and_then(read_positions).map(Linestring))
.collect::<Result<Vec<Linestring<Pt>>, GeoJsonError>>()?;
Ok(DynGeometry::MultiLineString(MultiLinestring(linestrings)))
}
fn read_multi_polygon(polys: &[JsonValue]) -> Result<DynGeometry<f64, Cartesian>, GeoJsonError> {
let polygons = polys
.iter()
.map(|p| p.as_array().ok_or(GeoJsonError::MalformedCoordinates))
.map(|p| p.and_then(read_polygon_value))
.collect::<Result<Vec<Polygon<Pt>>, GeoJsonError>>()?;
Ok(DynGeometry::MultiPolygon(MultiPolygon(polygons)))
}
fn read_geometry_collection(
value: &JsonValue,
) -> Result<DynGeometry<f64, Cartesian>, GeoJsonError> {
let geometries = value
.get("geometries")
.and_then(JsonValue::as_array)
.ok_or(GeoJsonError::MalformedCoordinates)?;
let items = geometries
.iter()
.map(parse_geometry)
.collect::<Result<Vec<_>, GeoJsonError>>()?;
Ok(DynGeometry::GeometryCollection(items))
}
#[cfg(test)]
mod tests {
#![allow(
clippy::float_cmp,
reason = "coordinate values come from exact decimal GeoJSON literals"
)]
use super::from_geojson;
use geometry_model::{DynGeometry, DynKind, Linestring, MultiPolygon, Point2D, Polygon, Ring};
#[test]
fn point_example() {
let g = from_geojson(r#"{"type":"Point","coordinates":[100.0,0.0]}"#).unwrap();
assert_eq!(g, DynGeometry::Point(Point2D::new(100.0, 0.0)));
}
#[test]
fn point_altitude_ordinate_is_ignored() {
let g = from_geojson(r#"{"type":"Point","coordinates":[100.0,0.0,500.0]}"#).unwrap();
assert_eq!(g, DynGeometry::Point(Point2D::new(100.0, 0.0)));
}
#[test]
fn linestring_example() {
let g = from_geojson(r#"{"type":"LineString","coordinates":[[100.0,0.0],[101.0,1.0]]}"#)
.unwrap();
assert_eq!(
g,
DynGeometry::LineString(Linestring::from_vec(vec![
Point2D::new(100.0, 0.0),
Point2D::new(101.0, 1.0),
]))
);
}
#[test]
fn polygon_with_hole_example() {
let g = from_geojson(
r#"{"type":"Polygon","coordinates":[
[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0],[100.0,0.0]],
[[100.8,0.8],[100.8,0.2],[100.2,0.2],[100.2,0.8],[100.8,0.8]]
]}"#,
)
.unwrap();
assert_eq!(
g,
DynGeometry::Polygon(Polygon::with_inners(
Ring::from_vec(vec![
Point2D::new(100.0, 0.0),
Point2D::new(101.0, 0.0),
Point2D::new(101.0, 1.0),
Point2D::new(100.0, 1.0),
Point2D::new(100.0, 0.0),
]),
vec![Ring::from_vec(vec![
Point2D::new(100.8, 0.8),
Point2D::new(100.8, 0.2),
Point2D::new(100.2, 0.2),
Point2D::new(100.2, 0.8),
Point2D::new(100.8, 0.8),
])],
))
);
}
#[test]
fn multipoint_example() {
let g = from_geojson(r#"{"type":"MultiPoint","coordinates":[[100.0,0.0],[101.0,1.0]]}"#)
.unwrap();
assert_eq!(g.kind(), DynKind::MultiPoint);
}
#[test]
fn multipolygon_example() {
let g = from_geojson(
r#"{"type":"MultiPolygon","coordinates":[
[[[102.0,2.0],[103.0,2.0],[103.0,3.0],[102.0,3.0],[102.0,2.0]]],
[[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0],[100.0,0.0]]]
]}"#,
)
.unwrap();
assert_eq!(
g,
DynGeometry::MultiPolygon(MultiPolygon::from_vec(vec![
Polygon::new(Ring::from_vec(vec![
Point2D::new(102.0, 2.0),
Point2D::new(103.0, 2.0),
Point2D::new(103.0, 3.0),
Point2D::new(102.0, 3.0),
Point2D::new(102.0, 2.0),
])),
Polygon::new(Ring::from_vec(vec![
Point2D::new(100.0, 0.0),
Point2D::new(101.0, 0.0),
Point2D::new(101.0, 1.0),
Point2D::new(100.0, 1.0),
Point2D::new(100.0, 0.0),
])),
]))
);
}
#[test]
fn geometry_collection_example() {
let g = from_geojson(
r#"{"type":"GeometryCollection","geometries":[
{"type":"Point","coordinates":[100.0,0.0]},
{"type":"LineString","coordinates":[[101.0,0.0],[102.0,1.0]]}
]}"#,
)
.unwrap();
assert_eq!(
g,
DynGeometry::GeometryCollection(vec![
DynGeometry::Point(Point2D::new(100.0, 0.0)),
DynGeometry::LineString(Linestring::from_vec(vec![
Point2D::new(101.0, 0.0),
Point2D::new(102.0, 1.0),
])),
])
);
}
#[test]
fn feature_wrapper_is_unsupported() {
use crate::GeoJsonError;
let err = from_geojson(
r#"{"type":"Feature","geometry":{"type":"Point","coordinates":[0,0]},"properties":{}}"#,
)
.unwrap_err();
assert_eq!(err, GeoJsonError::UnsupportedType("Feature".into()));
}
#[test]
fn unknown_type_errors() {
use crate::GeoJsonError;
let err = from_geojson(r#"{"type":"Curve","coordinates":[]}"#).unwrap_err();
assert_eq!(err, GeoJsonError::UnknownGeometryType("Curve".into()));
}
#[test]
fn missing_type_errors() {
use crate::GeoJsonError;
let err = from_geojson(r#"{"coordinates":[1,2]}"#).unwrap_err();
assert_eq!(err, GeoJsonError::ExpectedType);
}
#[test]
fn missing_coordinates_errors() {
use crate::GeoJsonError;
let err = from_geojson(r#"{"type":"Point"}"#).unwrap_err();
assert_eq!(err, GeoJsonError::MalformedCoordinates);
}
}