tp_lib_core/models/
gnss.rs1use crate::errors::ProjectionError;
4use chrono::{DateTime, FixedOffset};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct GnssPosition {
43 pub latitude: f64,
45
46 pub longitude: f64,
48
49 pub timestamp: DateTime<FixedOffset>,
51
52 pub crs: String,
54
55 pub metadata: HashMap<String, String>,
57}
58
59impl GnssPosition {
60 pub fn new(
62 latitude: f64,
63 longitude: f64,
64 timestamp: DateTime<FixedOffset>,
65 crs: String,
66 ) -> Result<Self, ProjectionError> {
67 let position = Self {
68 latitude,
69 longitude,
70 timestamp,
71 crs,
72 metadata: HashMap::new(),
73 };
74
75 position.validate()?;
76 Ok(position)
77 }
78
79 pub fn validate_latitude(&self) -> Result<(), ProjectionError> {
81 if self.latitude < -90.0 || self.latitude > 90.0 {
82 return Err(ProjectionError::InvalidCoordinate(format!(
83 "Latitude {} out of range [-90, 90]",
84 self.latitude
85 )));
86 }
87 Ok(())
88 }
89
90 pub fn validate_longitude(&self) -> Result<(), ProjectionError> {
92 if self.longitude < -180.0 || self.longitude > 180.0 {
93 return Err(ProjectionError::InvalidCoordinate(format!(
94 "Longitude {} out of range [-180, 180]",
95 self.longitude
96 )));
97 }
98 Ok(())
99 }
100
101 pub fn validate_timezone(&self) -> Result<(), ProjectionError> {
103 Ok(())
106 }
107
108 fn validate(&self) -> Result<(), ProjectionError> {
110 self.validate_latitude()?;
111 self.validate_longitude()?;
112 self.validate_timezone()?;
113
114 if self.crs.is_empty() {
116 return Err(ProjectionError::InvalidCrs(
117 "CRS must not be empty".to_string(),
118 ));
119 }
120
121 Ok(())
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128 use chrono::TimeZone;
129
130 #[test]
131 fn test_valid_position() {
132 let timestamp = FixedOffset::east_opt(3600)
133 .unwrap()
134 .with_ymd_and_hms(2025, 12, 9, 14, 30, 0)
135 .unwrap();
136
137 let pos = GnssPosition::new(50.8503, 4.3517, timestamp, "EPSG:4326".to_string());
138
139 assert!(pos.is_ok());
140 }
141
142 #[test]
143 fn test_invalid_latitude() {
144 let timestamp = FixedOffset::east_opt(3600)
145 .unwrap()
146 .with_ymd_and_hms(2025, 12, 9, 14, 30, 0)
147 .unwrap();
148
149 let pos = GnssPosition::new(
150 91.0, 4.3517,
152 timestamp,
153 "EPSG:4326".to_string(),
154 );
155
156 assert!(pos.is_err());
157 }
158
159 #[test]
160 fn test_invalid_longitude() {
161 let timestamp = FixedOffset::east_opt(3600)
162 .unwrap()
163 .with_ymd_and_hms(2025, 12, 9, 14, 30, 0)
164 .unwrap();
165
166 let pos = GnssPosition::new(
167 50.8503,
168 181.0, timestamp,
170 "EPSG:4326".to_string(),
171 );
172
173 assert!(pos.is_err());
174 }
175}