use super::Extension;
use geojson::Geometry;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Clone)]
pub struct Projection {
#[serde(skip_serializing_if = "Option::is_none")]
pub code: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub wkt2: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub projjson: Option<Map<String, Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub geometry: Option<Geometry>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bbox: Option<Vec<f64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub centroid: Option<Centroid>,
#[serde(skip_serializing_if = "Option::is_none")]
pub shape: Option<Vec<usize>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub transform: Option<Vec<f64>>,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct Centroid {
pub lat: f64,
pub lon: f64,
}
impl Projection {
#[cfg(feature = "gdal")]
pub fn wgs84_bounds(&self) -> crate::Result<Option<crate::Bbox>> {
use gdal::spatial_ref::{AxisMappingStrategy, CoordTransform, SpatialRef};
if let Some(bbox) = self.bbox.as_ref() {
if bbox.len() != 4 {
return Ok(None);
}
if let Some(spatial_ref) = self.spatial_ref()? {
let mut wgs84 = SpatialRef::from_epsg(4326)?;
wgs84.set_axis_mapping_strategy(AxisMappingStrategy::TraditionalGisOrder);
let coord_transform = CoordTransform::new(&spatial_ref, &wgs84)?;
let bounds =
coord_transform.transform_bounds(&[bbox[0], bbox[1], bbox[2], bbox[3]], 21)?;
let round = |n: f64| (n * 10_000_000.).round() / 10_000_000.;
Ok(Some(crate::Bbox::new(
round(bounds[0]),
round(bounds[1]),
round(bounds[2]),
round(bounds[3]),
)))
} else {
Ok(None)
}
} else {
Ok(None)
}
}
#[cfg(feature = "gdal")]
fn spatial_ref(&self) -> crate::Result<Option<gdal::spatial_ref::SpatialRef>> {
use crate::Error;
use gdal::spatial_ref::SpatialRef;
if let Some(code) = self.code.as_deref() {
SpatialRef::from_definition(code)
.map(Some)
.map_err(Error::from)
} else if let Some(wkt) = self.wkt2.as_ref() {
SpatialRef::from_wkt(wkt).map(Some).map_err(Error::from)
} else if let Some(projjson) = self.projjson.clone() {
SpatialRef::from_definition(&serde_json::to_string(&Value::Object(projjson))?)
.map(Some)
.map_err(Error::from)
} else {
Ok(None)
}
}
pub fn is_empty(&self) -> bool {
serde_json::to_value(self)
.map(|v| v == Value::Object(Default::default()))
.unwrap_or(true)
}
}
impl Extension for Projection {
const IDENTIFIER: &'static str =
"https://stac-extensions.github.io/projection/v2.0.0/schema.json";
const PREFIX: &'static str = "proj";
}
#[cfg(test)]
mod tests {
use super::Projection;
use crate::{Fields, Item};
#[cfg(feature = "gdal")]
#[test]
fn axis_order() {
let projection = Projection {
code: Some("EPSG:32621".to_string()),
bbox: Some(vec![
373185.0,
8019284.949381611,
639014.9492102272,
8286015.0,
]),
..Default::default()
};
let bounds = projection.wgs84_bounds().unwrap().unwrap();
assert!(
(bounds.xmin() - -61.2876244).abs() < 0.1,
"{}",
bounds.xmin()
);
assert!((bounds.ymin() - 72.229798).abs() < 0.1);
}
#[test]
fn example() {
let item: Item =
crate::read("examples/extensions-collection/proj-example/proj-example.json").unwrap();
let projection = item.extension::<Projection>().unwrap();
assert_eq!(projection.code.unwrap(), "EPSG:32614");
}
}