use crate::common::{BoundingBox, ContactInfo};
use crate::error::{MetadataError, Result};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct FgdcMetadata {
pub idinfo: IdentificationInfo,
pub dataqual: Option<DataQualityInfo>,
pub spdoinfo: Option<SpatialDataOrganizationInfo>,
pub spref: Option<SpatialReferenceInfo>,
pub eainfo: Option<EntityAttributeInfo>,
pub distinfo: Option<DistributionInfo>,
pub metainfo: MetadataReferenceInfo,
}
pub struct FgdcBuilder {
metadata: FgdcMetadata,
}
impl FgdcMetadata {
pub fn builder() -> FgdcBuilder {
FgdcBuilder {
metadata: Self::default(),
}
}
}
impl FgdcBuilder {
pub fn title(mut self, title: impl Into<String>) -> Self {
self.metadata.idinfo.citation.citeinfo.title = title.into();
self
}
pub fn abstract_text(mut self, abstract_text: impl Into<String>) -> Self {
self.metadata.idinfo.descript.abstract_text = abstract_text.into();
self
}
pub fn purpose(mut self, purpose: impl Into<String>) -> Self {
self.metadata.idinfo.descript.purpose = Some(purpose.into());
self
}
pub fn bbox(mut self, bbox: BoundingBox) -> Self {
self.metadata.idinfo.spdom.bounding = bbox;
self
}
pub fn keywords(mut self, theme: impl Into<String>, keywords: Vec<impl Into<String>>) -> Self {
let kw = Keywords {
theme: Some(theme.into()),
theme_key: keywords.into_iter().map(|k| k.into()).collect(),
place: Vec::new(),
temporal: Vec::new(),
};
self.metadata.idinfo.keywords.push(kw);
self
}
pub fn build(self) -> Result<FgdcMetadata> {
if self.metadata.idinfo.citation.citeinfo.title.is_empty() {
return Err(MetadataError::MissingField("title".to_string()));
}
Ok(self.metadata)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct IdentificationInfo {
pub citation: Citation,
pub descript: Description,
pub timeperd: TimePeriod,
pub status: Status,
pub spdom: SpatialDomain,
pub keywords: Vec<Keywords>,
pub accconst: Option<String>,
pub useconst: Option<String>,
pub ptcontac: Option<Contact>,
pub browse: Vec<BrowseGraphic>,
pub native: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Citation {
pub citeinfo: CiteInfo,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct CiteInfo {
pub origin: Vec<String>,
pub pubdate: Option<String>,
pub title: String,
pub edition: Option<String>,
pub geoform: Option<String>,
pub serinfo: Option<SeriesInfo>,
pub pubinfo: Option<PublicationInfo>,
pub onlink: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SeriesInfo {
pub sername: String,
pub issue: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PublicationInfo {
pub pubplace: String,
pub publish: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Description {
pub abstract_text: String,
pub purpose: Option<String>,
pub supplinf: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimePeriod {
pub timeinfo: TimeInfo,
pub current: String,
}
impl Default for TimePeriod {
fn default() -> Self {
Self {
timeinfo: TimeInfo::Single {
caldate: chrono::Utc::now().format("%Y%m%d").to_string(),
},
current: "ground condition".to_string(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TimeInfo {
Single {
caldate: String,
},
Multiple {
caldate: Vec<String>,
},
Range {
begdate: String,
enddate: String,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Status {
pub progress: Progress,
pub update: MaintenanceFrequency,
}
impl Default for Status {
fn default() -> Self {
Self {
progress: Progress::Complete,
update: MaintenanceFrequency::AsNeeded,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum Progress {
Complete,
InWork,
Planned,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum MaintenanceFrequency {
Continually,
Daily,
Weekly,
Monthly,
Annually,
Unknown,
AsNeeded,
Irregular,
NonePlanned,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SpatialDomain {
pub bounding: BoundingBox,
}
impl Default for SpatialDomain {
fn default() -> Self {
Self {
bounding: BoundingBox::new(-180.0, 180.0, -90.0, 90.0),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Keywords {
pub theme: Option<String>,
pub theme_key: Vec<String>,
pub place: Vec<String>,
pub temporal: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Contact {
pub cntinfo: ContactInfo,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BrowseGraphic {
pub browsen: String,
pub browsed: String,
pub browset: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DataQualityInfo {
pub attracc: Option<AttributeAccuracy>,
pub logic: Option<String>,
pub complete: Option<String>,
pub posacc: Option<PositionalAccuracy>,
pub lineage: Lineage,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AttributeAccuracy {
pub attraccr: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PositionalAccuracy {
pub horizpa: Option<HorizontalPositionalAccuracy>,
pub vertacc: Option<VerticalPositionalAccuracy>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HorizontalPositionalAccuracy {
pub horizpar: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VerticalPositionalAccuracy {
pub vertaccr: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Lineage {
pub srcinfo: Vec<SourceInfo>,
pub procstep: Vec<ProcessStep>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SourceInfo {
pub srccite: Citation,
pub srcscale: Option<i32>,
pub typesrc: Option<String>,
pub srccontr: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProcessStep {
pub procdesc: String,
pub procdate: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SpatialDataOrganizationInfo {
pub indspref: Option<String>,
pub direct: DirectSpatialReferenceMethod,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum DirectSpatialReferenceMethod {
Point,
Vector,
Raster,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SpatialReferenceInfo {
pub horizsys: Option<HorizontalCoordinateSystem>,
pub vertdef: Option<VerticalCoordinateSystem>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum HorizontalCoordinateSystem {
Geographic {
latres: f64,
longres: f64,
geodetic: GeodeticModel,
},
Planar {
mapproj: Option<String>,
gridsys: Option<String>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GeodeticModel {
pub horizdn: String,
pub ellips: String,
pub semiaxis: f64,
pub denflat: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VerticalCoordinateSystem {
pub altsys: AltitudeSystem,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AltitudeSystem {
pub altdatum: String,
pub altres: f64,
pub altunits: String,
pub altenc: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EntityAttributeInfo {
pub detailed: Vec<DetailedDescription>,
pub overview: Option<OverviewDescription>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DetailedDescription {
pub enttyp: EntityType,
pub attr: Vec<Attribute>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EntityType {
pub enttypl: String,
pub enttypd: String,
pub enttypds: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Attribute {
pub attrlabl: String,
pub attrdef: String,
pub attrdefs: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OverviewDescription {
pub eaover: String,
pub eadetcit: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DistributionInfo {
pub distrib: Distributor,
pub stdorder: Vec<StandardOrderProcess>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Distributor {
pub cntinfo: ContactInfo,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StandardOrderProcess {
pub nondig: Option<String>,
pub digform: Vec<DigitalForm>,
pub fees: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DigitalForm {
pub digtinfo: DigitalTransferInfo,
pub digtopt: DigitalTransferOption,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DigitalTransferInfo {
pub formname: String,
pub formvern: Option<String>,
pub transize: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DigitalTransferOption {
pub onlinopt: Vec<OnlineOption>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OnlineOption {
pub computer: ComputerContactInfo,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComputerContactInfo {
pub networka: NetworkAddress,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetworkAddress {
pub networkr: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetadataReferenceInfo {
pub metd: String,
pub metc: Contact,
pub metstdn: String,
pub metstdv: String,
}
impl Default for MetadataReferenceInfo {
fn default() -> Self {
Self {
metd: chrono::Utc::now().format("%Y%m%d").to_string(),
metc: Contact {
cntinfo: ContactInfo {
individual_name: None,
organization_name: None,
position_name: None,
email: None,
phone: None,
address: None,
online_resource: None,
},
},
metstdn: "FGDC Content Standard for Digital Geospatial Metadata".to_string(),
metstdv: "FGDC-STD-001-1998".to_string(),
}
}
}
#[cfg(feature = "xml")]
impl FgdcMetadata {
pub fn to_xml(&self) -> Result<String> {
use quick_xml::se::to_string;
to_string(&self).map_err(|e| MetadataError::XmlError(e.to_string()))
}
pub fn from_xml(xml: &str) -> Result<Self> {
use quick_xml::de::from_str;
from_str(xml).map_err(|e| MetadataError::XmlError(e.to_string()))
}
}
impl FgdcMetadata {
pub fn to_json(&self) -> Result<String> {
serde_json::to_string_pretty(&self).map_err(|e| MetadataError::JsonError(e.to_string()))
}
pub fn from_json(json: &str) -> Result<Self> {
serde_json::from_str(json).map_err(|e| MetadataError::JsonError(e.to_string()))
}
}