ogcapi_types/common/
crs.rs

1use std::{fmt, str};
2
3use serde::{Deserialize, Serialize};
4
5/// Default CRS for coordinates without height
6pub const OGC_CRS84: &str = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
7
8/// Default CRS for coordinates with height
9pub const OGC_CRS84H: &str = "http://www.opengis.net/def/crs/OGC/0/CRS84h";
10
11/// Coordinate Reference System (CRS)
12#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
13pub struct Crs {
14    pub authority: Authority,
15    pub version: String,
16    pub code: String,
17}
18
19impl Crs {
20    pub fn new(authority: Authority, version: impl ToString, code: impl ToString) -> Crs {
21        Crs {
22            authority,
23            version: version.to_string(),
24            code: code.to_string(),
25        }
26    }
27
28    pub fn from_epsg(code: i32) -> Self {
29        Crs::new(Authority::EPSG, "0", code)
30    }
31
32    pub fn from_srid(code: i32) -> Self {
33        if code == 4326 {
34            Crs::default()
35        } else {
36            Crs::new(Authority::EPSG, "0", code)
37        }
38    }
39
40    pub fn to_urn(&self) -> String {
41        format!(
42            "urn:ogc:def:crs:{authority}:{version}:{code}",
43            authority = self.authority,
44            version = self.version,
45            code = self.code
46        )
47    }
48
49    pub fn to_epsg(&self) -> Option<Crs> {
50        match self.authority {
51            Authority::OGC => match self.code.as_str() {
52                "CRS84h" => Some(Crs::new(Authority::EPSG, "0", "4979")),
53                _ => None,
54            },
55            Authority::EPSG => Some(self.to_owned()),
56        }
57    }
58
59    pub fn as_epsg(&self) -> Option<i32> {
60        match self.authority {
61            Authority::OGC => match self.code.as_str() {
62                "CRS84h" => Some(4979),
63                _ => panic!("Unable to extract epsg code from `{}`", self),
64            },
65            Authority::EPSG => self.code.parse().ok(),
66        }
67    }
68
69    pub fn as_srid(&self) -> i32 {
70        match self.authority {
71            Authority::OGC => match self.code.as_str() {
72                "CRS84" => 4326,
73                "CRS84h" => 4979,
74                _ => panic!("Unable to extract epsg code from `{}`", self),
75            },
76            Authority::EPSG => self.code.parse().unwrap(),
77        }
78    }
79
80    /// "AUTHORITY:CODE", like "EPSG:25832"
81    pub fn as_known_crs(&self) -> String {
82        format!("{}:{}", self.authority, self.code)
83    }
84}
85
86impl fmt::Display for Crs {
87    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
88        write!(
89            f,
90            "http://www.opengis.net/def/crs/{authority}/{version}/{code}",
91            authority = self.authority,
92            version = self.version,
93            code = self.code
94        )
95    }
96}
97
98impl str::FromStr for Crs {
99    type Err = String;
100
101    fn from_str(s: &str) -> Result<Self, Self::Err> {
102        let parts: Vec<&str> = if s.starts_with("urn") {
103            s.trim_start_matches("urn:ogc:def:crs:")
104                .split(':')
105                .collect()
106        } else {
107            s.trim_start_matches("http://www.opengis.net/def/crs/")
108                .split('/')
109                .collect()
110        };
111        match parts.len() {
112            3 => Ok(Crs::new(Authority::from_str(parts[0])?, parts[1], parts[2])),
113            _ => Err(format!("Unable to parse CRS from `{s}`!")),
114        }
115    }
116}
117
118impl Default for Crs {
119    fn default() -> Crs {
120        Crs {
121            authority: Authority::OGC,
122            version: "1.3".to_string(),
123            code: "CRS84".to_string(),
124        }
125    }
126}
127
128/// CRS Authorities
129#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Hash)]
130pub enum Authority {
131    OGC,
132    EPSG,
133}
134
135impl fmt::Display for Authority {
136    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137        match self {
138            Authority::OGC => write!(f, "OGC"),
139            Authority::EPSG => write!(f, "EPSG"),
140        }
141    }
142}
143
144impl str::FromStr for Authority {
145    type Err = &'static str;
146
147    fn from_str(s: &str) -> Result<Self, Self::Err> {
148        match s {
149            "OGC" => Ok(Authority::OGC),
150            "EPSG" => Ok(Authority::EPSG),
151            _ => Err("Unknown crs authority!"),
152        }
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use std::str::FromStr;
159
160    use crate::common::{Crs, OGC_CRS84};
161
162    #[test]
163    fn parse_crs() {
164        let crs = Crs::from_str(OGC_CRS84).unwrap();
165        assert_eq!(format!("{:#}", crs), OGC_CRS84)
166    }
167
168    #[test]
169    fn from_epsg() {
170        let code = 4979;
171        let crs = Crs::from_epsg(code);
172        assert_eq!(
173            crs.to_string(),
174            "http://www.opengis.net/def/crs/EPSG/0/4979".to_string()
175        );
176
177        let epsg = crs.as_epsg();
178        assert_eq!(epsg, Some(code));
179    }
180
181    #[test]
182    fn into_epsg() {
183        let crs = Crs::from_epsg(4979);
184        assert_eq!(
185            crs.to_string(),
186            "http://www.opengis.net/def/crs/EPSG/0/4979".to_string()
187        )
188    }
189
190    #[test]
191    fn to_epsg() {
192        let crs = Crs::from_str("http://www.opengis.net/def/crs/EPSG/0/4979").unwrap();
193        assert_eq!(crs.to_epsg(), Some(Crs::from_epsg(4979)))
194    }
195}