use crate::common::{BoundingBox, Keyword, TemporalExtent};
use crate::datacite::{Creator, DataCiteMetadata, IdentifierType, ResourceTypeGeneral};
use crate::dcat::Dataset as DcatDataset;
use crate::error::{MetadataError, Result};
use crate::fgdc::{FgdcMetadata, Keywords, TimeInfo};
use crate::inspire::InspireMetadata;
use crate::iso19115::{DataIdentification, Iso19115Metadata, ResponsibleParty, Role};
use chrono::Datelike;
pub fn iso19115_to_fgdc(iso: &Iso19115Metadata) -> Result<FgdcMetadata> {
let mut fgdc = FgdcMetadata::default();
if let Some(ident) = iso.identification_info.first() {
fgdc.idinfo.citation.citeinfo.title = ident.citation.title.clone();
fgdc.idinfo.descript.abstract_text = ident.abstract_text.clone();
fgdc.idinfo.descript.purpose = ident.purpose.clone();
for keyword_group in &ident.keywords {
let theme_keywords: Vec<String> =
keyword_group.iter().map(|k| k.keyword.clone()).collect();
if !theme_keywords.is_empty() {
fgdc.idinfo.keywords.push(Keywords {
theme: Some("General".to_string()),
theme_key: theme_keywords,
place: Vec::new(),
temporal: Vec::new(),
});
}
}
if let Some(bbox) = ident.extent.geographic_extent {
fgdc.idinfo.spdom.bounding = bbox;
}
if let Some(ref temporal) = ident.extent.temporal_extent {
if let (Some(start), Some(end)) = (temporal.start, temporal.end) {
fgdc.idinfo.timeperd.timeinfo = TimeInfo::Range {
begdate: start.format("%Y%m%d").to_string(),
enddate: end.format("%Y%m%d").to_string(),
};
}
}
}
fgdc.metainfo.metd = iso.date_stamp.format("%Y%m%d").to_string();
Ok(fgdc)
}
pub fn fgdc_to_iso19115(fgdc: &FgdcMetadata) -> Result<Iso19115Metadata> {
let mut iso = Iso19115Metadata::default();
let mut ident = DataIdentification::default();
ident.citation.title = fgdc.idinfo.citation.citeinfo.title.clone();
ident.abstract_text = fgdc.idinfo.descript.abstract_text.clone();
ident.purpose = fgdc.idinfo.descript.purpose.clone();
for keyword_group in &fgdc.idinfo.keywords {
let keywords: Vec<Keyword> = keyword_group
.theme_key
.iter()
.map(|k| Keyword {
keyword: k.clone(),
thesaurus: keyword_group.theme.clone(),
})
.collect();
if !keywords.is_empty() {
ident.keywords.push(keywords);
}
}
ident.extent.geographic_extent = Some(fgdc.idinfo.spdom.bounding);
if let TimeInfo::Range {
begdate: _,
enddate: _,
} = &fgdc.idinfo.timeperd.timeinfo
{
ident.extent.temporal_extent = Some(TemporalExtent {
start: None, end: None, });
}
iso.identification_info.push(ident);
Ok(iso)
}
pub fn iso19115_to_inspire(iso: &Iso19115Metadata) -> Result<InspireMetadata> {
let inspire = InspireMetadata {
base: iso.clone(),
..Default::default()
};
if inspire.resource_locator.is_empty() {
return Err(MetadataError::TransformError(
"ISO 19115 metadata lacks resource locator for INSPIRE".to_string(),
));
}
Ok(inspire)
}
pub fn inspire_to_iso19115(inspire: &InspireMetadata) -> Result<Iso19115Metadata> {
Ok(inspire.base.clone())
}
pub fn iso19115_to_datacite(iso: &Iso19115Metadata) -> Result<DataCiteMetadata> {
if iso.identification_info.is_empty() {
return Err(MetadataError::TransformError(
"ISO 19115 metadata lacks identification info".to_string(),
));
}
let ident = &iso.identification_info[0];
let mut builder = DataCiteMetadata::builder()
.identifier("10.0000/PLACEHOLDER", IdentifierType::Doi) .title(ident.citation.title.clone())
.publisher("Unknown Publisher") .publication_year(chrono::Utc::now().year() as u16) .resource_type(ResourceTypeGeneral::Dataset);
for party in &ident.point_of_contact {
if let Some(ref name) = party.individual_name {
builder = builder.creator(Creator::new(name));
} else if let Some(ref org) = party.organization_name {
builder = builder.creator(Creator::organization(org));
}
}
builder.build()
}
pub fn datacite_to_iso19115(datacite: &DataCiteMetadata) -> Result<Iso19115Metadata> {
let mut iso = Iso19115Metadata::default();
let mut ident = DataIdentification::default();
if let Some(title) = datacite.titles.first() {
ident.citation.title = title.title.clone();
}
for creator in &datacite.creators {
ident.point_of_contact.push(ResponsibleParty {
individual_name: Some(creator.name.clone()),
organization_name: None,
position_name: None,
contact_info: None,
role: Role::Originator,
});
}
if let Some(desc) = datacite.descriptions.first() {
ident.abstract_text = desc.description.clone();
}
if !datacite.subjects.is_empty() {
let keywords: Vec<Keyword> = datacite
.subjects
.iter()
.map(|s| Keyword {
keyword: s.subject.clone(),
thesaurus: s.subject_scheme.clone(),
})
.collect();
ident.keywords.push(keywords);
}
if let Some(geo_loc) = datacite.geo_locations.first() {
if let Some(bbox_dc) = &geo_loc.geo_location_box {
ident.extent.geographic_extent = Some(BoundingBox {
west: bbox_dc.west_bound_longitude,
east: bbox_dc.east_bound_longitude,
south: bbox_dc.south_bound_latitude,
north: bbox_dc.north_bound_latitude,
});
}
}
iso.identification_info.push(ident);
Ok(iso)
}
pub fn iso19115_to_dcat(iso: &Iso19115Metadata) -> Result<DcatDataset> {
if iso.identification_info.is_empty() {
return Err(MetadataError::TransformError(
"ISO 19115 metadata lacks identification info".to_string(),
));
}
let ident = &iso.identification_info[0];
let mut builder = DcatDataset::builder()
.title(ident.citation.title.clone())
.description(ident.abstract_text.clone());
for keyword_group in &ident.keywords {
for keyword in keyword_group {
builder = builder.keyword(keyword.keyword.clone());
}
}
builder.build()
}
pub fn dcat_to_iso19115(dcat: &DcatDataset) -> Result<Iso19115Metadata> {
let mut iso = Iso19115Metadata::default();
let mut ident = DataIdentification::default();
if let Some(title) = dcat.title.first() {
ident.citation.title = title.value.clone();
}
if let Some(desc) = dcat.description.first() {
ident.abstract_text = desc.value.clone();
}
if !dcat.keyword.is_empty() {
let keywords: Vec<Keyword> = dcat
.keyword
.iter()
.map(|k| Keyword {
keyword: k.clone(),
thesaurus: None,
})
.collect();
ident.keywords.push(keywords);
}
if let Some(spatial) = dcat.spatial.first() {
if let Some(bbox_dcat) = &spatial.bbox {
ident.extent.geographic_extent = Some(BoundingBox {
west: bbox_dcat.west,
east: bbox_dcat.east,
south: bbox_dcat.south,
north: bbox_dcat.north,
});
}
}
if let Some(temporal) = dcat.temporal.first() {
ident.extent.temporal_extent = Some(TemporalExtent {
start: temporal.start_date,
end: temporal.end_date,
});
}
iso.identification_info.push(ident);
Ok(iso)
}
#[derive(Debug, Clone)]
pub struct FieldMapping {
pub source_standard: String,
pub source_field: String,
pub target_standard: String,
pub target_field: String,
pub transform_function: Option<String>,
}
pub fn get_field_mappings(source: &str, target: &str) -> Vec<FieldMapping> {
let mut mappings = Vec::new();
match (source, target) {
("ISO19115", "FGDC") => {
mappings.push(FieldMapping {
source_standard: "ISO19115".to_string(),
source_field: "identification_info.citation.title".to_string(),
target_standard: "FGDC".to_string(),
target_field: "idinfo.citation.citeinfo.title".to_string(),
transform_function: None,
});
mappings.push(FieldMapping {
source_standard: "ISO19115".to_string(),
source_field: "identification_info.abstract".to_string(),
target_standard: "FGDC".to_string(),
target_field: "idinfo.descript.abstract".to_string(),
transform_function: None,
});
mappings.push(FieldMapping {
source_standard: "ISO19115".to_string(),
source_field: "identification_info.extent.geographic_extent".to_string(),
target_standard: "FGDC".to_string(),
target_field: "idinfo.spdom.bounding".to_string(),
transform_function: None,
});
}
("FGDC", "ISO19115") => {
for mapping in get_field_mappings("ISO19115", "FGDC") {
mappings.push(FieldMapping {
source_standard: mapping.target_standard,
source_field: mapping.target_field,
target_standard: mapping.source_standard,
target_field: mapping.source_field,
transform_function: mapping.transform_function,
});
}
}
_ => {}
}
mappings
}
pub struct MetadataTransformer {
pub preserve_all: bool,
pub strict_mode: bool,
}
impl Default for MetadataTransformer {
fn default() -> Self {
Self {
preserve_all: true,
strict_mode: false,
}
}
}
impl MetadataTransformer {
pub fn new() -> Self {
Self::default()
}
pub fn with_preserve_all(mut self, preserve: bool) -> Self {
self.preserve_all = preserve;
self
}
pub fn with_strict_mode(mut self, strict: bool) -> Self {
self.strict_mode = strict;
self
}
pub fn transform_iso_to_fgdc(&self, iso: &Iso19115Metadata) -> Result<FgdcMetadata> {
iso19115_to_fgdc(iso)
}
pub fn transform_fgdc_to_iso(&self, fgdc: &FgdcMetadata) -> Result<Iso19115Metadata> {
fgdc_to_iso19115(fgdc)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_transformer_builder() {
let transformer = MetadataTransformer::new()
.with_preserve_all(true)
.with_strict_mode(false);
assert!(transformer.preserve_all);
assert!(!transformer.strict_mode);
}
#[test]
fn test_field_mappings() {
let mappings = get_field_mappings("ISO19115", "FGDC");
assert!(!mappings.is_empty());
let title_mapping = mappings.iter().find(|m| m.source_field.contains("title"));
assert!(title_mapping.is_some());
}
}