1use crate::decoder::EntityDecoder;
11use crate::error::Result;
12use crate::schema::IfcType;
13use crate::schema_gen::DecodedEntity;
14
15#[derive(Debug, Clone)]
17pub struct GeoReference {
18 pub crs_name: Option<String>,
20 pub geodetic_datum: Option<String>,
22 pub vertical_datum: Option<String>,
24 pub map_projection: Option<String>,
26 pub eastings: f64,
28 pub northings: f64,
30 pub orthogonal_height: f64,
32 pub x_axis_abscissa: f64,
34 pub x_axis_ordinate: f64,
36 pub scale: f64,
38}
39
40impl Default for GeoReference {
41 fn default() -> Self {
42 Self {
43 crs_name: None,
44 geodetic_datum: None,
45 vertical_datum: None,
46 map_projection: None,
47 eastings: 0.0,
48 northings: 0.0,
49 orthogonal_height: 0.0,
50 x_axis_abscissa: 1.0, x_axis_ordinate: 0.0, scale: 1.0,
53 }
54 }
55}
56
57impl GeoReference {
58 pub fn new() -> Self {
60 Self::default()
61 }
62
63 #[inline]
65 pub fn has_georef(&self) -> bool {
66 self.crs_name.is_some()
67 || self.eastings != 0.0
68 || self.northings != 0.0
69 || self.orthogonal_height != 0.0
70 }
71
72 #[inline]
74 pub fn rotation(&self) -> f64 {
75 self.x_axis_ordinate.atan2(self.x_axis_abscissa)
76 }
77
78 #[inline]
80 pub fn local_to_map(&self, x: f64, y: f64, z: f64) -> (f64, f64, f64) {
81 let cos_r = self.x_axis_abscissa;
82 let sin_r = self.x_axis_ordinate;
83 let s = self.scale;
84
85 let e = s * (cos_r * x - sin_r * y) + self.eastings;
86 let n = s * (sin_r * x + cos_r * y) + self.northings;
87 let h = z + self.orthogonal_height;
88
89 (e, n, h)
90 }
91
92 #[inline]
94 pub fn map_to_local(&self, e: f64, n: f64, h: f64) -> (f64, f64, f64) {
95 let cos_r = self.x_axis_abscissa;
96 let sin_r = self.x_axis_ordinate;
97 let inv_scale = if self.scale.abs() < f64::EPSILON { 1.0 } else { 1.0 / self.scale };
99
100 let dx = e - self.eastings;
101 let dy = n - self.northings;
102
103 let x = inv_scale * (cos_r * dx + sin_r * dy);
105 let y = inv_scale * (-sin_r * dx + cos_r * dy);
106 let z = h - self.orthogonal_height;
107
108 (x, y, z)
109 }
110
111 pub fn to_matrix(&self) -> [f64; 16] {
113 let cos_r = self.x_axis_abscissa;
114 let sin_r = self.x_axis_ordinate;
115 let s = self.scale;
116
117 [
119 s * cos_r, s * sin_r, 0.0, 0.0,
120 -s * sin_r, s * cos_r, 0.0, 0.0,
121 0.0, 0.0, 1.0, 0.0,
122 self.eastings, self.northings, self.orthogonal_height, 1.0,
123 ]
124 }
125}
126
127pub struct GeoRefExtractor;
129
130impl GeoRefExtractor {
131 pub fn extract(decoder: &mut EntityDecoder, entity_types: &[(u32, IfcType)]) -> Result<Option<GeoReference>> {
133 let mut map_conversion_id: Option<u32> = None;
135 let mut projected_crs_id: Option<u32> = None;
136
137 for (id, ifc_type) in entity_types {
138 match ifc_type {
139 IfcType::IfcMapConversion => {
140 map_conversion_id = Some(*id);
141 }
142 IfcType::IfcProjectedCRS => {
143 projected_crs_id = Some(*id);
144 }
145 _ => {}
146 }
147 }
148
149 if map_conversion_id.is_none() {
151 return Self::extract_from_pset(decoder, entity_types);
152 }
153
154 let mut georef = GeoReference::new();
155
156 if let Some(id) = map_conversion_id {
160 let entity = decoder.decode_by_id(id)?;
161 Self::parse_map_conversion(&entity, &mut georef);
162 }
163
164 if let Some(id) = projected_crs_id {
168 let entity = decoder.decode_by_id(id)?;
169 Self::parse_projected_crs(&entity, &mut georef);
170 }
171
172 if georef.has_georef() {
173 Ok(Some(georef))
174 } else {
175 Ok(None)
176 }
177 }
178
179 fn parse_map_conversion(entity: &DecodedEntity, georef: &mut GeoReference) {
181 if let Some(e) = entity.get_float(2) {
183 georef.eastings = e;
184 }
185 if let Some(n) = entity.get_float(3) {
187 georef.northings = n;
188 }
189 if let Some(h) = entity.get_float(4) {
191 georef.orthogonal_height = h;
192 }
193 if let Some(xa) = entity.get_float(5) {
195 georef.x_axis_abscissa = xa;
196 }
197 if let Some(xo) = entity.get_float(6) {
199 georef.x_axis_ordinate = xo;
200 }
201 if let Some(s) = entity.get_float(7) {
203 georef.scale = s;
204 }
205 }
206
207 fn parse_projected_crs(entity: &DecodedEntity, georef: &mut GeoReference) {
209 if let Some(name) = entity.get_string(0) {
211 georef.crs_name = Some(name.to_string());
212 }
213 if let Some(datum) = entity.get_string(2) {
215 georef.geodetic_datum = Some(datum.to_string());
216 }
217 if let Some(vdatum) = entity.get_string(3) {
219 georef.vertical_datum = Some(vdatum.to_string());
220 }
221 if let Some(proj) = entity.get_string(4) {
223 georef.map_projection = Some(proj.to_string());
224 }
225 }
226
227 fn extract_from_pset(
229 decoder: &mut EntityDecoder,
230 entity_types: &[(u32, IfcType)],
231 ) -> Result<Option<GeoReference>> {
232 for (id, ifc_type) in entity_types {
234 if *ifc_type == IfcType::IfcPropertySet {
235 let entity = decoder.decode_by_id(*id)?;
236 if let Some(name) = entity.get_string(0) {
237 if name == "ePSet_MapConversion" || name == "EPset_MapConversion" {
238 return Self::parse_pset_map_conversion(decoder, &entity);
239 }
240 }
241 }
242 }
243 Ok(None)
244 }
245
246 fn parse_pset_map_conversion(
248 decoder: &mut EntityDecoder,
249 pset: &DecodedEntity,
250 ) -> Result<Option<GeoReference>> {
251 let mut georef = GeoReference::new();
252
253 if let Some(props_list) = pset.get_list(4) {
255 for prop_attr in props_list {
256 if let Some(prop_id) = prop_attr.as_entity_ref() {
257 let prop = decoder.decode_by_id(prop_id)?;
258 if let Some(name) = prop.get_string(0) {
260 let value = prop.get_float(2);
261 match name {
262 "Eastings" => {
263 if let Some(v) = value {
264 georef.eastings = v;
265 }
266 }
267 "Northings" => {
268 if let Some(v) = value {
269 georef.northings = v;
270 }
271 }
272 "OrthogonalHeight" => {
273 if let Some(v) = value {
274 georef.orthogonal_height = v;
275 }
276 }
277 "XAxisAbscissa" => {
278 if let Some(v) = value {
279 georef.x_axis_abscissa = v;
280 }
281 }
282 "XAxisOrdinate" => {
283 if let Some(v) = value {
284 georef.x_axis_ordinate = v;
285 }
286 }
287 "Scale" => {
288 if let Some(v) = value {
289 georef.scale = v;
290 }
291 }
292 _ => {}
293 }
294 }
295 }
296 }
297 }
298
299 if georef.has_georef() {
300 Ok(Some(georef))
301 } else {
302 Ok(None)
303 }
304 }
305}
306
307#[derive(Debug, Clone, Default)]
309pub struct RtcOffset {
310 pub x: f64,
312 pub y: f64,
313 pub z: f64,
314}
315
316impl RtcOffset {
317 #[inline]
319 pub fn from_positions(positions: &[f32]) -> Self {
320 if positions.is_empty() {
321 return Self::default();
322 }
323
324 let count = positions.len() / 3;
325 let mut sum = (0.0f64, 0.0f64, 0.0f64);
326
327 for chunk in positions.chunks_exact(3) {
328 sum.0 += chunk[0] as f64;
329 sum.1 += chunk[1] as f64;
330 sum.2 += chunk[2] as f64;
331 }
332
333 Self {
334 x: sum.0 / count as f64,
335 y: sum.1 / count as f64,
336 z: sum.2 / count as f64,
337 }
338 }
339
340 #[inline]
342 pub fn is_significant(&self) -> bool {
343 const THRESHOLD: f64 = 10000.0; self.x.abs() > THRESHOLD || self.y.abs() > THRESHOLD || self.z.abs() > THRESHOLD
345 }
346
347 #[inline]
349 pub fn apply(&self, positions: &mut [f32]) {
350 for chunk in positions.chunks_exact_mut(3) {
351 chunk[0] = (chunk[0] as f64 - self.x) as f32;
352 chunk[1] = (chunk[1] as f64 - self.y) as f32;
353 chunk[2] = (chunk[2] as f64 - self.z) as f32;
354 }
355 }
356}
357
358#[cfg(test)]
359mod tests {
360 use super::*;
361
362 #[test]
363 fn test_georef_local_to_map() {
364 let mut georef = GeoReference::new();
365 georef.eastings = 500000.0;
366 georef.northings = 5000000.0;
367 georef.orthogonal_height = 100.0;
368
369 let (e, n, h) = georef.local_to_map(10.0, 20.0, 5.0);
370 assert!((e - 500010.0).abs() < 1e-10);
371 assert!((n - 5000020.0).abs() < 1e-10);
372 assert!((h - 105.0).abs() < 1e-10);
373 }
374
375 #[test]
376 fn test_georef_map_to_local() {
377 let mut georef = GeoReference::new();
378 georef.eastings = 500000.0;
379 georef.northings = 5000000.0;
380 georef.orthogonal_height = 100.0;
381
382 let (x, y, z) = georef.map_to_local(500010.0, 5000020.0, 105.0);
383 assert!((x - 10.0).abs() < 1e-10);
384 assert!((y - 20.0).abs() < 1e-10);
385 assert!((z - 5.0).abs() < 1e-10);
386 }
387
388 #[test]
389 fn test_georef_with_rotation() {
390 let mut georef = GeoReference::new();
391 georef.eastings = 0.0;
392 georef.northings = 0.0;
393 georef.x_axis_abscissa = 0.0;
395 georef.x_axis_ordinate = 1.0;
396
397 let (e, n, _) = georef.local_to_map(10.0, 0.0, 0.0);
398 assert!(e.abs() < 1e-10);
400 assert!((n - 10.0).abs() < 1e-10);
401 }
402
403 #[test]
404 fn test_rtc_offset() {
405 let positions = vec![
406 500000.0f32, 5000000.0, 0.0,
407 500010.0, 5000010.0, 10.0,
408 500020.0, 5000020.0, 20.0,
409 ];
410
411 let offset = RtcOffset::from_positions(&positions);
412 assert!(offset.is_significant());
413 assert!((offset.x - 500010.0).abs() < 1.0);
414 assert!((offset.y - 5000010.0).abs() < 1.0);
415 }
416
417 #[test]
418 fn test_rtc_apply() {
419 let mut positions = vec![
420 500000.0f32, 5000000.0, 0.0,
421 500010.0, 5000010.0, 10.0,
422 ];
423
424 let offset = RtcOffset {
425 x: 500000.0,
426 y: 5000000.0,
427 z: 0.0,
428 };
429
430 offset.apply(&mut positions);
431
432 assert!((positions[0] - 0.0).abs() < 1e-5);
433 assert!((positions[1] - 0.0).abs() < 1e-5);
434 assert!((positions[3] - 10.0).abs() < 1e-5);
435 assert!((positions[4] - 10.0).abs() < 1e-5);
436 }
437}