#![allow(dead_code)]
use super::{GeoProperties, GeoValue};
use crate::ext::geometry_to_json;
use geo_types::{Geometry, MultiLineString, MultiPoint, MultiPolygon, Point};
use std::fmt::Debug;
use versatiles_core::json::{JsonObject, JsonValue};
#[derive(Clone, Debug)]
pub struct GeoFeature {
pub id: Option<GeoValue>,
pub geometry: Geometry<f64>,
pub properties: GeoProperties,
}
impl GeoFeature {
#[must_use]
pub fn new(geometry: Geometry<f64>) -> Self {
Self {
id: None,
geometry,
properties: GeoProperties::new(),
}
}
pub fn set_id(&mut self, id: GeoValue) {
self.id = Some(id);
}
pub fn set_properties(&mut self, properties: GeoProperties) {
self.properties = properties;
}
pub fn set_property<T>(&mut self, key: String, value: T)
where
GeoValue: From<T>,
{
self.properties.insert(key, GeoValue::from(value));
}
pub fn to_single_geometry(&mut self) {
take_and_replace(&mut self.geometry, into_single_geometry);
}
pub fn to_multi_geometry(&mut self) {
take_and_replace(&mut self.geometry, into_multi_geometry);
}
#[must_use]
pub fn to_json(&self, precision: Option<u8>) -> JsonObject {
let mut json = JsonObject::new();
json.set("type", JsonValue::from("Feature"));
if let Some(id) = &self.id {
json.set("id", id.to_json());
}
json.set("geometry", JsonValue::from(geometry_to_json(&self.geometry, precision)));
json.set("properties", self.properties.to_json());
json
}
#[cfg(any(test, feature = "test"))]
#[must_use]
pub fn new_example() -> Self {
Self {
id: Some(GeoValue::from(13)),
geometry: example_geometry(),
properties: GeoProperties::from(vec![
("name", GeoValue::from("Nice")),
("population", GeoValue::from(348085)),
("is_nice", GeoValue::from(true)),
]),
}
}
}
#[must_use]
pub fn into_multi_geometry(g: Geometry<f64>) -> Geometry<f64> {
match g {
Geometry::Point(p) => Geometry::MultiPoint(MultiPoint(vec![p])),
Geometry::LineString(ls) => Geometry::MultiLineString(MultiLineString(vec![ls])),
Geometry::Polygon(p) => Geometry::MultiPolygon(MultiPolygon(vec![p])),
other => other,
}
}
#[must_use]
pub fn into_single_geometry(g: Geometry<f64>) -> Geometry<f64> {
match g {
Geometry::MultiPoint(MultiPoint(mut v)) if v.len() == 1 => Geometry::Point(v.pop().expect("len == 1")),
Geometry::MultiLineString(MultiLineString(mut v)) if v.len() == 1 => {
Geometry::LineString(v.pop().expect("len == 1"))
}
Geometry::MultiPolygon(MultiPolygon(mut v)) if v.len() == 1 => Geometry::Polygon(v.pop().expect("len == 1")),
other => other,
}
}
fn take_and_replace<F>(slot: &mut Geometry<f64>, f: F)
where
F: FnOnce(Geometry<f64>) -> Geometry<f64>,
{
let dummy: Geometry<f64> = Geometry::Point(Point::new(0.0, 0.0));
let owned = std::mem::replace(slot, dummy);
*slot = f(owned);
}
#[cfg(any(test, feature = "test"))]
#[must_use]
pub fn example_geometry() -> Geometry<f64> {
use geo_types::{LineString, Polygon};
fn polygon_from_rings(rings: &[Vec<[f64; 2]>]) -> Polygon<f64> {
let mut iter = rings.iter().map(|ring| LineString::from(ring.clone()));
let exterior = iter.next().expect("polygon has at least an exterior");
let interiors = iter.collect();
Polygon::new(exterior, interiors)
}
let polygon_a = polygon_from_rings(&[
vec![[0.0, 0.0], [5.0, 0.0], [2.5, 4.0], [0.0, 0.0]],
vec![[2.0, 1.0], [2.5, 2.0], [3.0, 1.0], [2.0, 1.0]],
]);
let polygon_b = polygon_from_rings(&[
vec![[6.0, 0.0], [9.0, 0.0], [9.0, 4.0], [6.0, 4.0], [6.0, 0.0]],
vec![[7.0, 1.0], [7.0, 3.0], [8.0, 3.0], [8.0, 1.0], [7.0, 1.0]],
]);
Geometry::MultiPolygon(MultiPolygon(vec![polygon_a, polygon_b]))
}
impl From<Geometry<f64>> for GeoFeature {
fn from(geometry: Geometry<f64>) -> Self {
Self::new(geometry)
}
}
impl From<MultiPolygon<f64>> for GeoFeature {
fn from(geometry: MultiPolygon<f64>) -> Self {
Self::new(Geometry::MultiPolygon(geometry))
}
}
#[cfg(test)]
mod tests {
use super::*;
use geo_types::{Coord, MultiPolygon, polygon};
#[test]
fn new_sets_defaults() {
let geom = example_geometry();
let f = GeoFeature::new(geom.clone());
assert!(f.id.is_none());
assert_eq!(f.geometry, geom);
assert!(f.properties.is_empty());
}
#[test]
fn set_id_and_properties_work() {
let mut f = GeoFeature::new(example_geometry());
f.set_id(GeoValue::from(42));
assert_eq!(f.id, Some(GeoValue::from(42)));
let mut props = GeoProperties::new();
props.insert("name".into(), GeoValue::from("Nice"));
props.insert("population".into(), GeoValue::from(348_085));
f.set_properties(props.clone());
assert_eq!(f.properties, props);
}
#[test]
fn set_property_inserts_values_of_various_types() {
let mut f = GeoFeature::new(example_geometry());
f.set_property("a".into(), 1u32);
f.set_property("b".into(), "text");
f.set_property("c".into(), true);
assert_eq!(f.properties.get("a"), Some(&GeoValue::from(1u32)));
assert_eq!(f.properties.get("b"), Some(&GeoValue::from("text")));
assert_eq!(f.properties.get("c"), Some(&GeoValue::from(true)));
}
#[test]
fn to_json_contains_type_geometry_properties_and_optional_id() {
let mut f = GeoFeature::new_example();
let j = f.to_json(None);
assert_eq!(j.string("type").unwrap(), Some("Feature".into()));
assert!(j.number("id").unwrap().is_some());
assert!(j.object("geometry").unwrap().is_some());
assert!(j.object("properties").unwrap().is_some());
f.id = None;
let j2 = f.to_json(None);
assert_eq!(j2.string("type").unwrap(), Some("Feature".into()));
assert!(j2.number("id").unwrap().is_none());
}
#[test]
fn from_multipolygon_builds_feature() {
let poly = polygon![(x: 0.0, y: 0.0), (x: 1.0, y: 0.0), (x: 1.0, y: 1.0), (x: 0.0, y: 1.0), (x: 0.0, y: 0.0)];
let mp = MultiPolygon(vec![poly]);
let f: GeoFeature = mp.into();
assert!(f.id.is_none());
assert!(f.properties.is_empty());
let j = f.to_json(None);
assert_eq!(j.string("type").unwrap(), Some("Feature".into()));
assert!(j.object("geometry").unwrap().is_some());
}
#[test]
fn into_multi_lifts_singles() {
let pt: Geometry<f64> = Geometry::Point(Point::new(1.0, 2.0));
match into_multi_geometry(pt) {
Geometry::MultiPoint(_) => {}
other => panic!("expected MultiPoint, got {other:?}"),
}
}
#[test]
fn into_single_unwraps_singletons() {
let mp = Geometry::MultiPoint(MultiPoint(vec![Point(Coord { x: 1.0, y: 2.0 })]));
match into_single_geometry(mp) {
Geometry::Point(_) => {}
other => panic!("expected Point, got {other:?}"),
}
}
#[test]
fn into_single_keeps_multi_with_more_than_one() {
let mp = Geometry::MultiPoint(MultiPoint(vec![
Point(Coord { x: 1.0, y: 2.0 }),
Point(Coord { x: 3.0, y: 4.0 }),
]));
match into_single_geometry(mp) {
Geometry::MultiPoint(v) => assert_eq!(v.0.len(), 2),
other => panic!("expected MultiPoint, got {other:?}"),
}
}
}