jsonfg 0.1.0

Types for OGC Features and Geometries JSON (JSON-FG), a GeoJSON superset with support for arbitrary coordinate reference systems, solids, and curved geometries.
Documentation
//! The JSON-FG `Feature` object and its supporting members.

use serde::{Deserialize, Serialize};

use crate::JsonObject;
use crate::crs::CoordRefSys;
use crate::geometry::Geometry;
use crate::measures::Measures;
use crate::time::Time;

/// A JSON-FG feature.
///
/// A JSON-FG feature is also a valid GeoJSON feature: the `geometry` member holds a
/// WGS 84 GeoJSON geometry (or `null`), while the JSON-FG additions — most importantly
/// [`place`](Feature::place) for geometry in another coordinate reference system, and
/// [`time`](Feature::time) — are foreign members that GeoJSON readers ignore.
///
/// The `geometry` and `properties` members are always serialized (as `null` when unset),
/// as required by the JSON-FG schema. `place`, `time` and the other optional members are
/// omitted when unset.
///
/// The [`conforms_to`](Feature::conforms_to) member is only set on the top-level object
/// of a document; leave it `None` on features nested inside a
/// [`FeatureCollection`](crate::FeatureCollection).
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Feature {
    #[serde(rename = "type", default)]
    pub kind: FeatureKind,

    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub id: Option<Id>,

    /// Declared conformance classes. Set only on the top-level object of a document.
    #[serde(
        rename = "conformsTo",
        skip_serializing_if = "Option::is_none",
        default
    )]
    pub conforms_to: Option<Vec<String>>,

    #[serde(
        rename = "featureType",
        skip_serializing_if = "Option::is_none",
        default
    )]
    pub feature_type: Option<FeatureType>,

    #[serde(
        rename = "featureSchema",
        skip_serializing_if = "Option::is_none",
        default
    )]
    pub feature_schema: Option<FeatureSchema>,

    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub time: Option<Time>,

    /// The coordinate reference system of [`place`](Feature::place) (and of any geometry-
    /// valued properties). Absent means the in-scope default (the collection's CRS, or
    /// `CRS84`).
    #[serde(
        rename = "coordRefSys",
        skip_serializing_if = "Option::is_none",
        default
    )]
    pub coord_ref_sys: Option<CoordRefSys>,

    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub measures: Option<Measures>,

    /// The geometry in the feature's own coordinate reference system, allowing geometry
    /// types beyond GeoJSON (solids, curves). Omitted when the geometry is fully
    /// represented by [`geometry`](Feature::geometry).
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub place: Option<Geometry>,

    /// The WGS 84 GeoJSON geometry, or `null`. Always serialized. Must be one of the
    /// seven GeoJSON geometry types; use [`place`](Feature::place) for anything else.
    pub geometry: Option<Geometry>,

    /// The feature's properties, or `null`. Always serialized.
    pub properties: Option<JsonObject>,

    /// Any additional members (e.g. OGC API `links`), preserved verbatim.
    #[serde(flatten)]
    pub foreign_members: JsonObject,
}

impl Default for Feature {
    fn default() -> Self {
        Feature {
            kind: FeatureKind::Feature,
            id: None,
            conforms_to: None,
            feature_type: None,
            feature_schema: None,
            time: None,
            coord_ref_sys: None,
            measures: None,
            place: None,
            geometry: None,
            properties: None,
            foreign_members: JsonObject::new(),
        }
    }
}

impl Feature {
    /// A new, empty feature (`geometry` and `properties` both `null`).
    pub fn new() -> Self {
        Feature::default()
    }
}

/// Marker for the `type` member of a [`Feature`]; always serializes as `"Feature"`.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum FeatureKind {
    #[default]
    Feature,
}

/// A feature identifier: a string or a number.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Id {
    String(String),
    Number(serde_json::Number),
}

impl From<String> for Id {
    fn from(s: String) -> Self {
        Id::String(s)
    }
}

impl From<&str> for Id {
    fn from(s: &str) -> Self {
        Id::String(s.to_owned())
    }
}

impl From<i64> for Id {
    fn from(n: i64) -> Self {
        Id::Number(n.into())
    }
}

impl From<u64> for Id {
    fn from(n: u64) -> Self {
        Id::Number(n.into())
    }
}

/// The value of the `featureType` member: a single classifier, or several.
///
/// The JSON-FG 1.0 core schema defines `featureType` as a single string; the array form
/// is accepted for interoperability with producers that classify a feature under several
/// types.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum FeatureType {
    Single(String),
    Multiple(Vec<String>),
}

impl From<String> for FeatureType {
    fn from(s: String) -> Self {
        FeatureType::Single(s)
    }
}

impl From<&str> for FeatureType {
    fn from(s: &str) -> Self {
        FeatureType::Single(s.to_owned())
    }
}

/// The value of the `featureSchema` member: a single schema URI, or a map from feature
/// type name to schema URI (for multi-typed features).
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum FeatureSchema {
    Ref(String),
    PerType(std::collections::BTreeMap<String, String>),
}