use crate::errors::*;
use crate::gdal_major_object::MajorObject;
use crate::utils::{_last_cpl_err, _last_null_pointer_err, _string, _string_array};
use gdal_sys::CPLErr;
use std::ffi::CString;
pub trait Metadata: MajorObject {
fn description(&self) -> Result<String> {
let c_res = unsafe { gdal_sys::GDALGetDescription(self.gdal_object_ptr()) };
_string(c_res).ok_or_else(|| _last_null_pointer_err("GDALGetDescription"))
}
fn metadata_domains(&self) -> Vec<String> {
let mut domains = Vec::new();
let c_res = unsafe { gdal_sys::GDALGetMetadataDomainList(self.gdal_object_ptr()) };
if !c_res.is_null() {
domains.append(&mut _string_array(c_res));
}
unsafe { gdal_sys::CSLDestroy(c_res) };
domains
}
fn metadata_domain(&self, domain: &str) -> Option<Vec<String>> {
let mut metadata = Vec::new();
if let Ok(c_domain) = CString::new(domain.to_owned()) {
let c_res =
unsafe { gdal_sys::GDALGetMetadata(self.gdal_object_ptr(), c_domain.as_ptr()) };
if c_res.is_null() {
return None;
} else {
metadata.append(&mut _string_array(c_res));
}
}
Some(metadata)
}
fn metadata_item(&self, key: &str, domain: &str) -> Option<String> {
if let Ok(c_key) = CString::new(key.to_owned()) {
if let Ok(c_domain) = CString::new(domain.to_owned()) {
let c_res = unsafe {
gdal_sys::GDALGetMetadataItem(
self.gdal_object_ptr(),
c_key.as_ptr(),
c_domain.as_ptr(),
)
};
return _string(c_res);
}
}
None
}
fn set_metadata_item(&mut self, key: &str, value: &str, domain: &str) -> Result<()> {
let c_key = CString::new(key)?;
let c_domain = CString::new(domain)?;
let c_value = CString::new(value)?;
let c_res = unsafe {
gdal_sys::GDALSetMetadataItem(
self.gdal_object_ptr(),
c_key.as_ptr(),
c_value.as_ptr(),
c_domain.as_ptr(),
)
};
if c_res != CPLErr::CE_None {
return Err(_last_cpl_err(c_res));
}
Ok(())
}
fn set_description(&mut self, description: &str) -> Result<()> {
let c_description = CString::new(description.to_owned())?;
unsafe { gdal_sys::GDALSetDescription(self.gdal_object_ptr(), c_description.as_ptr()) };
Ok(())
}
fn metadata(&self) -> MetadataIter<'_>
where
Self: Sized,
{
MetadataIter::new(self)
}
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub struct MetadataEntry {
pub domain: String,
pub key: String,
pub value: String,
}
impl MetadataEntry {
pub fn new<D, K, V>(domain: D, key: K, value: V) -> Self
where
D: Into<String>,
K: Into<String>,
V: Into<String>,
{
Self {
domain: domain.into(),
key: key.into(),
value: value.into(),
}
}
pub fn is_default_domain(&self) -> bool {
self.domain.is_empty()
}
}
pub struct MetadataIter<'a> {
stream: Box<dyn Iterator<Item = MetadataEntry> + 'a>,
}
impl<'a> MetadataIter<'a> {
fn new<P: Metadata>(parent: &'a P) -> Self {
let stream = parent
.metadata_domains()
.into_iter()
.flat_map(move |domain| {
let keyvals = parent.metadata_domain(&domain).unwrap_or_default();
keyvals.into_iter().filter_map(move |keyval| {
keyval
.split_once('=')
.map(|(key, value)| MetadataEntry::new(domain.clone(), key, value))
})
});
Self {
stream: Box::new(stream),
}
}
}
impl Iterator for MetadataIter<'_> {
type Item = MetadataEntry;
fn next(&mut self) -> Option<Self::Item> {
self.stream.next()
}
}
#[cfg(test)]
mod tests {
use crate::metadata::MetadataEntry;
use crate::test_utils::fixture;
use crate::*;
#[test]
fn test_get_dataset_driver() {
let dataset = Dataset::open(fixture("tinymarble.tif")).unwrap();
let driver = dataset.driver();
assert_eq!(driver.short_name(), "GTiff");
assert_eq!(driver.long_name(), "GeoTIFF");
}
#[test]
fn test_get_description() {
let driver = DriverManager::get_driver_by_name("mem").unwrap();
assert_eq!(driver.description().unwrap(), "MEM".to_string());
}
#[test]
fn test_get_metadata_domains() {
let dataset = Dataset::open(fixture("tinymarble.tif")).unwrap();
let mut domains = dataset.metadata_domains();
domains.retain(|d| !d.is_empty());
domains.sort();
domains.dedup();
assert_eq!(
domains,
vec![
"COLOR_PROFILE",
"DERIVED_SUBDATASETS",
"IMAGE_STRUCTURE",
"xml:XMP",
]
);
}
#[test]
fn test_get_metadata_domain() {
let dataset = Dataset::open(fixture("tinymarble.tif")).unwrap();
let domain = "None";
let meta = dataset.metadata_domain(domain);
assert_eq!(meta, None);
let domain = "IMAGE_STRUCTURE";
let meta = dataset.metadata_domain(domain);
assert_eq!(meta, Some(vec!(String::from("INTERLEAVE=PIXEL"))));
}
#[test]
fn test_get_metadata_item() {
let dataset = Dataset::open(fixture("tinymarble.tif")).unwrap();
let key = "None";
let domain = "None";
let meta = dataset.metadata_item(key, domain);
assert_eq!(meta, None);
let key = "INTERLEAVE";
let domain = "IMAGE_STRUCTURE";
let meta = dataset.metadata_item(key, domain);
assert_eq!(meta, Some(String::from("PIXEL")));
}
#[test]
fn test_set_metadata_item() {
let driver = DriverManager::get_driver_by_name("MEM").unwrap();
let mut dataset = driver.create("", 1, 1, 1).unwrap();
let key = "Test_Key";
let domain = "Test_Domain";
let value = "Test_Value";
let result = dataset.set_metadata_item(key, value, domain);
assert!(result.is_ok());
let result = dataset.metadata_item(key, domain);
assert_eq!(Some(value.to_owned()), result);
}
#[test]
fn test_set_description() {
let driver = DriverManager::get_driver_by_name("MEM").unwrap();
let dataset = driver.create("", 1, 1, 1).unwrap();
let mut band = dataset.rasterband(1).unwrap();
let description = "A merry and cheerful band description";
assert_eq!(band.description().unwrap(), "");
band.set_description(description).unwrap();
assert_eq!(band.description().unwrap(), description);
}
#[test]
fn test_md_iter() {
let driver = DriverManager::get_driver_by_name("GTiff").unwrap();
driver.metadata().any(|e| e.key == "LIBGEOTIFF");
let ds = Dataset::open(fixture("m_3607824_se_17_1_20160620_sub.tif")).unwrap();
assert_eq!(ds.metadata_item("AREA_OR_POINT", ""), Some("Area".into()));
assert!(ds
.metadata()
.any(|e| e == MetadataEntry::new("", "AREA_OR_POINT", "Area")));
assert!(ds.metadata().any(|e| e.domain == "DERIVED_SUBDATASETS"));
let ds = Dataset::open(fixture("labels.tif")).unwrap();
let band = ds.rasterband(1).unwrap();
assert!(band.metadata().any(|e| e.key == "CLASSES"));
}
}