use std::iter::FromIterator;
use std::str::FromStr;
use crate::JsonObject;
use crate::errors::{Error, Result};
use crate::{Bbox, Feature};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", from = "deserialize::DeserializeFeatureCollectionHelper")]
pub struct FeatureCollection {
#[serde(skip_serializing_if = "Option::is_none")]
pub bbox: Option<Bbox>,
pub features: Vec<Feature>,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub foreign_members: Option<JsonObject>,
}
impl FeatureCollection {
pub fn new(features: impl IntoIterator<Item = Feature>) -> Self {
features.into_iter().collect()
}
}
mod deserialize {
use super::*;
use crate::util::normalize_foreign_members;
#[derive(Deserialize)]
pub(crate) struct DeserializeFeatureCollectionHelper {
#[allow(unused)]
r#type: FeatureCollectionType,
bbox: Option<Bbox>,
features: Vec<Feature>,
#[serde(flatten)]
foreign_members: Option<JsonObject>,
}
#[derive(Deserialize)]
enum FeatureCollectionType {
FeatureCollection,
}
impl From<DeserializeFeatureCollectionHelper> for FeatureCollection {
fn from(mut value: DeserializeFeatureCollectionHelper) -> Self {
normalize_foreign_members(&mut value.foreign_members);
Self {
bbox: value.bbox,
features: value.features,
foreign_members: value.foreign_members,
}
}
}
}
impl IntoIterator for FeatureCollection {
type Item = Feature;
type IntoIter = std::vec::IntoIter<Feature>;
fn into_iter(self) -> Self::IntoIter {
self.features.into_iter()
}
}
impl<'a> IntoIterator for &'a FeatureCollection {
type Item = &'a Feature;
type IntoIter = std::slice::Iter<'a, Feature>;
fn into_iter(self) -> Self::IntoIter {
IntoIterator::into_iter(&self.features)
}
}
impl FromStr for FeatureCollection {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Ok(serde_json::from_str(s)?)
}
}
impl FromIterator<Feature> for FeatureCollection {
fn from_iter<T: IntoIterator<Item = Feature>>(iter: T) -> Self {
let mut bbox = Some(vec![]);
let features = iter
.into_iter()
.inspect(|feat| {
let (curr_bbox, curr_len) = match &mut bbox {
Some(curr_bbox) => {
let curr_len = curr_bbox.len();
(curr_bbox, curr_len)
}
None => {
return;
}
};
match &feat.bbox {
None => {
bbox = None;
}
Some(fbox) if fbox.is_empty() || fbox.len() % 2 != 0 => {
bbox = None;
}
Some(fbox) if curr_len == 0 => {
curr_bbox.clone_from(fbox);
}
Some(fbox) if curr_len != fbox.len() => {
bbox = None;
}
Some(fbox) => {
curr_bbox.iter_mut().zip(fbox.iter()).enumerate().for_each(
|(idx, (bc, fc))| {
if idx < curr_len / 2 {
*bc = fc.min(*bc);
} else {
*bc = fc.max(*bc);
}
},
);
}
};
})
.collect();
FeatureCollection {
bbox,
features,
foreign_members: None,
}
}
}
#[cfg(test)]
mod tests {
use crate::{Feature, FeatureCollection, GeoJson, Geometry};
use serde_json::json;
use std::str::FromStr;
#[test]
fn test_fc_from_iterator() {
let features: Vec<Feature> = vec![
{
let mut feat: Feature = Geometry::new_point([0., 0., 0.]).into();
feat.bbox = Some(vec![-1., -1., -1., 1., 1., 1.]);
feat
},
{
let mut feat: Feature =
Geometry::new_multi_point([[10., 10., 10.], [11., 11., 11.]]).into();
feat.bbox = Some(vec![10., 10., 10., 11., 11., 11.]);
feat
},
];
let fc = FeatureCollection::new(features);
assert_eq!(fc.features.len(), 2);
assert_eq!(fc.bbox, Some(vec![-1., -1., -1., 11., 11., 11.]));
}
fn feature_collection_json() -> String {
json!({ "type": "FeatureCollection", "features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [11.1, 22.2]
},
"properties": {
"name": "Downtown"
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [33.3, 44.4]
},
"properties": {
"name": "Uptown"
}
},
]})
.to_string()
}
#[test]
fn parsing() {
let geojson_str = feature_collection_json();
let feature_collection_1: FeatureCollection = geojson_str.parse().unwrap();
let feature_collection_2: FeatureCollection = serde_json::from_str(&geojson_str).unwrap();
assert_eq!(feature_collection_1, feature_collection_2);
let GeoJson::FeatureCollection(feature_collection_3): GeoJson =
geojson_str.parse().unwrap()
else {
panic!("unexpected GeoJSON type");
};
let GeoJson::FeatureCollection(feature_collection_4): GeoJson =
serde_json::from_str(&geojson_str).unwrap()
else {
panic!("unexpected GeoJSON type");
};
assert_eq!(feature_collection_3, feature_collection_4);
assert_eq!(feature_collection_1, feature_collection_4);
}
#[test]
fn test_from_str_ok() {
let feature_collection = FeatureCollection::from_str(&feature_collection_json()).unwrap();
assert_eq!(2, feature_collection.features.len());
}
#[test]
fn wrong_type() {
let geojson_str = json!({
"type": "Point",
"coordinates": [1.1, 2.1]
})
.to_string();
Geometry::from_str(&geojson_str).unwrap();
FeatureCollection::from_str(&geojson_str).unwrap_err();
serde_json::from_str::<FeatureCollection>(&geojson_str).unwrap_err();
}
#[test]
fn iter_features() {
let feature_collection = FeatureCollection::from_str(&feature_collection_json()).unwrap();
let mut names: Vec<String> = vec![];
for feature in &feature_collection {
let name = feature
.property("name")
.unwrap()
.as_str()
.unwrap()
.to_string();
names.push(name);
}
assert_eq!(names, vec!["Downtown", "Uptown"]);
}
#[test]
fn encode_decode_feature_collection_with_foreign_members() {
let mut foreign_members = serde_json::Map::new();
foreign_members.insert("extra".to_string(), serde_json::json!("data"));
let feature_collection = FeatureCollection {
bbox: None,
features: vec![],
foreign_members: Some(foreign_members),
};
let json_string = serde_json::to_string(&feature_collection).unwrap();
let decoded: FeatureCollection = json_string.parse().unwrap();
assert_eq!(decoded, feature_collection);
}
}