1use crate::decoder::EntityDecoder;
11use crate::error::Result;
12use crate::generated::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 {
99 1.0
100 } else {
101 1.0 / self.scale
102 };
103
104 let dx = e - self.eastings;
105 let dy = n - self.northings;
106
107 let x = inv_scale * (cos_r * dx + sin_r * dy);
109 let y = inv_scale * (-sin_r * dx + cos_r * dy);
110 let z = h - self.orthogonal_height;
111
112 (x, y, z)
113 }
114
115 pub fn to_matrix(&self) -> [f64; 16] {
117 let cos_r = self.x_axis_abscissa;
118 let sin_r = self.x_axis_ordinate;
119 let s = self.scale;
120
121 [
123 s * cos_r,
124 s * sin_r,
125 0.0,
126 0.0,
127 -s * sin_r,
128 s * cos_r,
129 0.0,
130 0.0,
131 0.0,
132 0.0,
133 1.0,
134 0.0,
135 self.eastings,
136 self.northings,
137 self.orthogonal_height,
138 1.0,
139 ]
140 }
141}
142
143pub struct GeoRefExtractor;
145
146impl GeoRefExtractor {
147 pub fn extract(
149 decoder: &mut EntityDecoder,
150 entity_types: &[(u32, IfcType)],
151 ) -> Result<Option<GeoReference>> {
152 let mut map_conversion_id: Option<u32> = None;
154 let mut projected_crs_id: Option<u32> = None;
155
156 for (id, ifc_type) in entity_types {
157 match ifc_type {
158 IfcType::IfcMapConversion => {
159 map_conversion_id = Some(*id);
160 }
161 IfcType::IfcProjectedCRS => {
162 projected_crs_id = Some(*id);
163 }
164 _ => {}
165 }
166 }
167
168 if map_conversion_id.is_none() {
170 return Self::extract_from_pset(decoder, entity_types);
171 }
172
173 let mut georef = GeoReference::new();
174
175 if let Some(id) = map_conversion_id {
179 let entity = decoder.decode_by_id(id)?;
180 Self::parse_map_conversion(&entity, &mut georef);
181 }
182
183 if let Some(id) = projected_crs_id {
187 let entity = decoder.decode_by_id(id)?;
188 Self::parse_projected_crs(&entity, &mut georef);
189 }
190
191 if georef.has_georef() {
192 Ok(Some(georef))
193 } else {
194 Ok(None)
195 }
196 }
197
198 fn parse_map_conversion(entity: &DecodedEntity, georef: &mut GeoReference) {
200 if let Some(e) = entity.get_float(2) {
202 georef.eastings = e;
203 }
204 if let Some(n) = entity.get_float(3) {
206 georef.northings = n;
207 }
208 if let Some(h) = entity.get_float(4) {
210 georef.orthogonal_height = h;
211 }
212 if let Some(xa) = entity.get_float(5) {
214 georef.x_axis_abscissa = xa;
215 }
216 if let Some(xo) = entity.get_float(6) {
218 georef.x_axis_ordinate = xo;
219 }
220 if let Some(s) = entity.get_float(7) {
222 georef.scale = s;
223 }
224 }
225
226 fn parse_projected_crs(entity: &DecodedEntity, georef: &mut GeoReference) {
228 if let Some(name) = entity.get_string(0) {
230 georef.crs_name = Some(name.to_string());
231 }
232 if let Some(datum) = entity.get_string(2) {
234 georef.geodetic_datum = Some(datum.to_string());
235 }
236 if let Some(vdatum) = entity.get_string(3) {
238 georef.vertical_datum = Some(vdatum.to_string());
239 }
240 if let Some(proj) = entity.get_string(4) {
242 georef.map_projection = Some(proj.to_string());
243 }
244 }
245
246 fn extract_from_pset(
248 decoder: &mut EntityDecoder,
249 entity_types: &[(u32, IfcType)],
250 ) -> Result<Option<GeoReference>> {
251 for (id, ifc_type) in entity_types {
253 if *ifc_type == IfcType::IfcPropertySet {
254 let entity = decoder.decode_by_id(*id)?;
255 if let Some(name) = entity.get_string(0) {
256 if name == "ePSet_MapConversion" || name == "EPset_MapConversion" {
257 return Self::parse_pset_map_conversion(decoder, &entity);
258 }
259 }
260 }
261 }
262 Ok(None)
263 }
264
265 fn parse_pset_map_conversion(
267 decoder: &mut EntityDecoder,
268 pset: &DecodedEntity,
269 ) -> Result<Option<GeoReference>> {
270 let mut georef = GeoReference::new();
271
272 if let Some(props_list) = pset.get_list(4) {
274 for prop_attr in props_list {
275 if let Some(prop_id) = prop_attr.as_entity_ref() {
276 let prop = decoder.decode_by_id(prop_id)?;
277 if let Some(name) = prop.get_string(0) {
279 let value = prop.get_float(2);
280 match name {
281 "Eastings" => {
282 if let Some(v) = value {
283 georef.eastings = v;
284 }
285 }
286 "Northings" => {
287 if let Some(v) = value {
288 georef.northings = v;
289 }
290 }
291 "OrthogonalHeight" => {
292 if let Some(v) = value {
293 georef.orthogonal_height = v;
294 }
295 }
296 "XAxisAbscissa" => {
297 if let Some(v) = value {
298 georef.x_axis_abscissa = v;
299 }
300 }
301 "XAxisOrdinate" => {
302 if let Some(v) = value {
303 georef.x_axis_ordinate = v;
304 }
305 }
306 "Scale" => {
307 if let Some(v) = value {
308 georef.scale = v;
309 }
310 }
311 _ => {}
312 }
313 }
314 }
315 }
316 }
317
318 if georef.has_georef() {
319 Ok(Some(georef))
320 } else {
321 Ok(None)
322 }
323 }
324}
325
326#[derive(Debug, Clone, Default)]
328pub struct RtcOffset {
329 pub x: f64,
331 pub y: f64,
332 pub z: f64,
333}
334
335impl RtcOffset {
336 #[inline]
338 pub fn from_positions(positions: &[f32]) -> Self {
339 if positions.is_empty() {
340 return Self::default();
341 }
342
343 let count = positions.len() / 3;
344 let mut sum = (0.0f64, 0.0f64, 0.0f64);
345
346 for chunk in positions.chunks_exact(3) {
347 sum.0 += chunk[0] as f64;
348 sum.1 += chunk[1] as f64;
349 sum.2 += chunk[2] as f64;
350 }
351
352 Self {
353 x: sum.0 / count as f64,
354 y: sum.1 / count as f64,
355 z: sum.2 / count as f64,
356 }
357 }
358
359 #[inline]
361 pub fn is_significant(&self) -> bool {
362 const THRESHOLD: f64 = 10000.0; self.x.abs() > THRESHOLD || self.y.abs() > THRESHOLD || self.z.abs() > THRESHOLD
364 }
365
366 #[inline]
368 pub fn apply(&self, positions: &mut [f32]) {
369 for chunk in positions.chunks_exact_mut(3) {
370 chunk[0] = (chunk[0] as f64 - self.x) as f32;
371 chunk[1] = (chunk[1] as f64 - self.y) as f32;
372 chunk[2] = (chunk[2] as f64 - self.z) as f32;
373 }
374 }
375}
376
377#[cfg(test)]
378mod tests {
379 use super::*;
380
381 #[test]
382 fn test_georef_local_to_map() {
383 let mut georef = GeoReference::new();
384 georef.eastings = 500000.0;
385 georef.northings = 5000000.0;
386 georef.orthogonal_height = 100.0;
387
388 let (e, n, h) = georef.local_to_map(10.0, 20.0, 5.0);
389 assert!((e - 500010.0).abs() < 1e-10);
390 assert!((n - 5000020.0).abs() < 1e-10);
391 assert!((h - 105.0).abs() < 1e-10);
392 }
393
394 #[test]
395 fn test_georef_map_to_local() {
396 let mut georef = GeoReference::new();
397 georef.eastings = 500000.0;
398 georef.northings = 5000000.0;
399 georef.orthogonal_height = 100.0;
400
401 let (x, y, z) = georef.map_to_local(500010.0, 5000020.0, 105.0);
402 assert!((x - 10.0).abs() < 1e-10);
403 assert!((y - 20.0).abs() < 1e-10);
404 assert!((z - 5.0).abs() < 1e-10);
405 }
406
407 #[test]
408 fn test_georef_with_rotation() {
409 let mut georef = GeoReference::new();
410 georef.eastings = 0.0;
411 georef.northings = 0.0;
412 georef.x_axis_abscissa = 0.0;
414 georef.x_axis_ordinate = 1.0;
415
416 let (e, n, _) = georef.local_to_map(10.0, 0.0, 0.0);
417 assert!(e.abs() < 1e-10);
419 assert!((n - 10.0).abs() < 1e-10);
420 }
421
422 #[test]
423 fn test_rtc_offset() {
424 let positions = vec![
425 500000.0f32,
426 5000000.0,
427 0.0,
428 500010.0,
429 5000010.0,
430 10.0,
431 500020.0,
432 5000020.0,
433 20.0,
434 ];
435
436 let offset = RtcOffset::from_positions(&positions);
437 assert!(offset.is_significant());
438 assert!((offset.x - 500010.0).abs() < 1.0);
439 assert!((offset.y - 5000010.0).abs() < 1.0);
440 }
441
442 #[test]
443 fn test_rtc_apply() {
444 let mut positions = vec![500000.0f32, 5000000.0, 0.0, 500010.0, 5000010.0, 10.0];
445
446 let offset = RtcOffset {
447 x: 500000.0,
448 y: 5000000.0,
449 z: 0.0,
450 };
451
452 offset.apply(&mut positions);
453
454 assert!((positions[0] - 0.0).abs() < 1e-5);
455 assert!((positions[1] - 0.0).abs() < 1e-5);
456 assert!((positions[3] - 10.0).abs() < 1e-5);
457 assert!((positions[4] - 10.0).abs() < 1e-5);
458 }
459}