use_spatial_reference/
lib.rs1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7fn non_empty_text(value: impl AsRef<str>) -> Result<String, SpatialReferenceTextError> {
8 let trimmed = value.as_ref().trim();
9
10 if trimmed.is_empty() {
11 Err(SpatialReferenceTextError::Empty)
12 } else {
13 Ok(trimmed.to_string())
14 }
15}
16
17#[derive(Clone, Copy, Debug, Eq, PartialEq)]
18pub enum SpatialReferenceTextError {
19 Empty,
20}
21
22impl fmt::Display for SpatialReferenceTextError {
23 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
24 match self {
25 Self::Empty => formatter.write_str("spatial reference text cannot be empty"),
26 }
27 }
28}
29
30impl Error for SpatialReferenceTextError {}
31
32#[derive(Clone, Copy, Debug, Eq, PartialEq)]
33pub enum SpatialReferenceValueError {
34 ZeroEpsgCode,
35}
36
37impl fmt::Display for SpatialReferenceValueError {
38 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
39 match self {
40 Self::ZeroEpsgCode => formatter.write_str("EPSG code must be greater than zero"),
41 }
42 }
43}
44
45impl Error for SpatialReferenceValueError {}
46
47#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
48pub struct SpatialReferenceId(String);
49
50impl SpatialReferenceId {
51 pub fn new(value: impl AsRef<str>) -> Result<Self, SpatialReferenceTextError> {
57 non_empty_text(value).map(Self)
58 }
59
60 #[must_use]
61 pub fn from_epsg(code: EpsgCode) -> Self {
62 Self(code.to_string())
63 }
64
65 #[must_use]
66 pub fn as_str(&self) -> &str {
67 &self.0
68 }
69
70 #[must_use]
71 pub fn into_string(self) -> String {
72 self.0
73 }
74}
75
76impl AsRef<str> for SpatialReferenceId {
77 fn as_ref(&self) -> &str {
78 self.as_str()
79 }
80}
81
82impl fmt::Display for SpatialReferenceId {
83 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
84 formatter.write_str(self.as_str())
85 }
86}
87
88impl FromStr for SpatialReferenceId {
89 type Err = SpatialReferenceTextError;
90
91 fn from_str(value: &str) -> Result<Self, Self::Err> {
92 Self::new(value)
93 }
94}
95
96#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
97pub struct EpsgCode(u32);
98
99impl EpsgCode {
100 pub const fn new(value: u32) -> Result<Self, SpatialReferenceValueError> {
106 if value == 0 {
107 return Err(SpatialReferenceValueError::ZeroEpsgCode);
108 }
109
110 Ok(Self(value))
111 }
112
113 #[must_use]
114 pub const fn get(self) -> u32 {
115 self.0
116 }
117}
118
119impl fmt::Display for EpsgCode {
120 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
121 write!(formatter, "EPSG:{}", self.get())
122 }
123}
124
125#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
126pub struct SpatialReferenceSystem {
127 identifier: SpatialReferenceId,
128}
129
130impl SpatialReferenceSystem {
131 #[must_use]
132 pub const fn new(identifier: SpatialReferenceId) -> Self {
133 Self { identifier }
134 }
135
136 #[must_use]
137 pub const fn identifier(&self) -> &SpatialReferenceId {
138 &self.identifier
139 }
140}
141
142impl fmt::Display for SpatialReferenceSystem {
143 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
144 self.identifier.fmt(formatter)
145 }
146}
147
148#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
149pub struct CoordinateReferenceSystem {
150 identifier: SpatialReferenceId,
151}
152
153impl CoordinateReferenceSystem {
154 #[must_use]
155 pub const fn new(identifier: SpatialReferenceId) -> Self {
156 Self { identifier }
157 }
158
159 #[must_use]
160 pub fn from_epsg(code: EpsgCode) -> Self {
161 Self::new(SpatialReferenceId::from_epsg(code))
162 }
163
164 #[must_use]
165 pub fn from_system(system: SpatialReferenceSystem) -> Self {
166 Self::new(system.identifier)
167 }
168
169 #[must_use]
170 pub const fn identifier(&self) -> &SpatialReferenceId {
171 &self.identifier
172 }
173}
174
175impl fmt::Display for CoordinateReferenceSystem {
176 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
177 self.identifier.fmt(formatter)
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::{
184 CoordinateReferenceSystem, EpsgCode, SpatialReferenceId, SpatialReferenceTextError,
185 SpatialReferenceValueError,
186 };
187
188 #[test]
189 fn valid_epsg_code() -> Result<(), SpatialReferenceValueError> {
190 let epsg = EpsgCode::new(3857)?;
191
192 assert_eq!(epsg.get(), 3857);
193 Ok(())
194 }
195
196 #[test]
197 fn zero_epsg_code_rejected() {
198 assert_eq!(
199 EpsgCode::new(0),
200 Err(SpatialReferenceValueError::ZeroEpsgCode)
201 );
202 }
203
204 #[test]
205 fn spatial_reference_id_construction() -> Result<(), SpatialReferenceTextError> {
206 let identifier = SpatialReferenceId::new("WGS84")?;
207
208 assert_eq!(identifier.as_str(), "WGS84");
209 Ok(())
210 }
211
212 #[test]
213 fn empty_spatial_reference_id_rejected() {
214 assert_eq!(
215 SpatialReferenceId::new(" "),
216 Err(SpatialReferenceTextError::Empty)
217 );
218 }
219
220 #[test]
221 fn crs_identifier_display() -> Result<(), SpatialReferenceValueError> {
222 let crs = CoordinateReferenceSystem::from_epsg(EpsgCode::new(4326)?);
223
224 assert_eq!(crs.to_string(), "EPSG:4326");
225 Ok(())
226 }
227}