use super::*;
#[test]
fn georef_discovery_does_not_retain_unrelated_property_sets() {
let mut source = String::from("DATA;\n");
let mut types = Vec::new();
for id in 1..=1000 {
source.push_str(&format!("#{id}=IFCPROPERTYSET('g',$,'Unrelated',$,(#2001));\n"));
types.push((id, IfcType::IfcPropertySet));
}
source.push_str("#1001=IFCPROPERTYSET('g',$,'ePsEt_MapConversion',$,(#2001));\n#2001=IFCPROPERTYSINGLEVALUE('Eastings',$,IFCLENGTHMEASURE(42.),$);\nENDSEC;");
types.push((1001, IfcType::IfcPropertySet));
let mut decoder = EntityDecoder::new(&source);
let geo = GeoRefExtractor::extract(&mut decoder, &types).unwrap().unwrap();
assert_eq!(geo.eastings, 42.0);
assert_eq!(geo.source, GeoRefSource::EPSetMapConversion);
assert!(decoder.cache_size() <= 2, "only the selected set and its value should be retained");
assert_eq!(decoder.decode_by_id(500).unwrap().get_string(2), Some("Unrelated"));
}
#[test]
fn test_georef_local_to_map() {
let mut georef = GeoReference::new();
georef.eastings = 500000.0;
georef.northings = 5000000.0;
georef.orthogonal_height = 100.0;
let (e, n, h) = georef.local_to_map(10.0, 20.0, 5.0);
assert!((e - 500010.0).abs() < 1e-10);
assert!((n - 5000020.0).abs() < 1e-10);
assert!((h - 105.0).abs() < 1e-10);
}
#[test]
fn test_georef_map_to_local() {
let mut georef = GeoReference::new();
georef.eastings = 500000.0;
georef.northings = 5000000.0;
georef.orthogonal_height = 100.0;
let (x, y, z) = georef.map_to_local(500010.0, 5000020.0, 105.0);
assert!((x - 10.0).abs() < 1e-10);
assert!((y - 20.0).abs() < 1e-10);
assert!((z - 5.0).abs() < 1e-10);
}
#[test]
fn test_georef_with_rotation() {
let mut georef = GeoReference::new();
georef.eastings = 0.0;
georef.northings = 0.0;
georef.x_axis_abscissa = 0.0;
georef.x_axis_ordinate = 1.0;
let (e, n, _) = georef.local_to_map(10.0, 0.0, 0.0);
assert!(e.abs() < 1e-10);
assert!((n - 10.0).abs() < 1e-10);
}
#[test]
fn test_georef_local_to_map_rotation_sign_is_a_true_rotation() {
let mut georef = GeoReference::new();
georef.eastings = 0.0;
georef.northings = 0.0;
let c = std::f64::consts::FRAC_1_SQRT_2;
georef.x_axis_abscissa = c;
georef.x_axis_ordinate = c;
let (e, n, _) = georef.local_to_map(10.0, 4.0, 0.0);
assert!((e - c * 6.0).abs() < 1e-10, "e = cos*x - sin*y, got {e}");
assert!((n - c * 14.0).abs() < 1e-10, "n = sin*x + cos*y, got {n}");
}
#[test]
fn test_georef_map_to_local_with_rotation_round_trips_local_to_map() {
let mut georef = GeoReference::new();
georef.eastings = 500000.0;
georef.northings = 4000000.0;
georef.orthogonal_height = 50.0;
georef.scale = 2.0;
let angle = std::f64::consts::FRAC_PI_6; georef.x_axis_abscissa = angle.cos();
georef.x_axis_ordinate = angle.sin();
let (lx, ly, lz) = (12.0, -7.0, 3.0);
let (e, n, h) = georef.local_to_map(lx, ly, lz);
let (x, y, z) = georef.map_to_local(e, n, h);
assert!((x - lx).abs() < 1e-9, "map_to_local must invert local_to_map (x), got {x}");
assert!((y - ly).abs() < 1e-9, "map_to_local must invert local_to_map (y), got {y}");
assert!((z - lz).abs() < 1e-9, "map_to_local must invert local_to_map (z), got {z}");
}
#[test]
fn test_rtc_offset() {
let positions = vec![
500000.0f32,
5000000.0,
0.0,
500010.0,
5000010.0,
10.0,
500020.0,
5000020.0,
20.0,
];
let offset = RtcOffset::from_positions(&positions);
assert!(offset.is_significant());
assert!((offset.x - 500010.0).abs() < 1.0);
assert!((offset.y - 5000010.0).abs() < 1.0);
}
#[test]
fn test_rtc_apply() {
let mut positions = vec![500000.0f32, 5000000.0, 0.0, 500010.0, 5000010.0, 10.0];
let offset = RtcOffset {
x: 500000.0,
y: 5000000.0,
z: 0.0,
};
offset.apply(&mut positions);
assert!((positions[0] - 0.0).abs() < 1e-5);
assert!((positions[1] - 0.0).abs() < 1e-5);
assert!((positions[3] - 10.0).abs() < 1e-5);
assert!((positions[4] - 10.0).abs() < 1e-5);
}
#[test]
fn test_rtc_apply_z_channel_uses_z_offset() {
let mut positions = vec![100.0f32, 200.0, 300.0];
let offset = RtcOffset {
x: 10.0,
y: 20.0,
z: 30.0,
};
offset.apply(&mut positions);
assert!((positions[0] - 90.0).abs() < 1e-5);
assert!((positions[1] - 180.0).abs() < 1e-5);
assert!((positions[2] - 270.0).abs() < 1e-5);
}
#[test]
fn test_extract_from_site_honours_negative_zero_degree_ref3546() {
let ifc_content = r#"ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('Test'),'2;1');
FILE_NAME('test.ifc','2024-01-01',(''),(''),'','','');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCSITE('1abc',$,'Site',$,$,$,$,$,.ELEMENT.,(-0,30,0),(-0,45,0),0.,$,$);
ENDSEC;
END-ISO-10303-21;
"#;
let mut decoder = EntityDecoder::new(ifc_content);
let entity_types = vec![(1u32, IfcType::IfcSite)];
let georef = GeoRefExtractor::extract(&mut decoder, &entity_types)
.expect("decode ok")
.expect("legacy site georeference extracted");
assert_eq!(georef.source, GeoRefSource::SiteLocation);
assert!(
(georef.northings - (-0.5)).abs() < 1e-9,
"expected northings -0.5 (0°30'S), got {}",
georef.northings
);
assert!(
(georef.eastings - (-0.75)).abs() < 1e-9,
"expected eastings -0.75 (0°45'W), got {}",
georef.eastings
);
}
#[test]
fn test_extract_from_site_honours_negative_zero_in_each_compound_angle_component_ref3546() {
let cases = [
("(0,-0,30)", -(30.0 / 3600.0)),
("(0,0,-0,30)", -(30.0 / 1_000_000.0 / 3600.0)),
("(0,0,30,-0)", -(30.0 / 3600.0)),
];
for (angle, expected_northings) in cases {
let ifc_content = format!(
"ISO-10303-21;\nHEADER;\nFILE_DESCRIPTION(('Test'),'2;1');\nFILE_NAME('test.ifc','2024-01-01',(''),(''),'','','');\nFILE_SCHEMA(('IFC4'));\nENDSEC;\nDATA;\n#1=IFCSITE('1abc',$,'Site',$,$,$,$,$,.ELEMENT.,{angle},(14,28,0),0.,$,$);\nENDSEC;\nEND-ISO-10303-21;\n"
);
let mut decoder = EntityDecoder::new(&ifc_content);
let georef = GeoRefExtractor::extract(&mut decoder, &[(1, IfcType::IfcSite)])
.expect("decode ok")
.expect("legacy site georeference extracted");
assert!(
(georef.northings - expected_northings).abs() < 1e-12,
"{angle} should produce {expected_northings}, got {}",
georef.northings
);
}
}
#[test]
fn test_extract_from_site_canonical_negative_degree_unaffected() {
let ifc_content = r#"ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('Test'),'2;1');
FILE_NAME('test.ifc','2024-01-01',(''),(''),'','','');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCSITE('1abc',$,'Site',$,$,$,$,$,.ELEMENT.,(-50,2,20),(14,28,0),0.,$,$);
ENDSEC;
END-ISO-10303-21;
"#;
let mut decoder = EntityDecoder::new(ifc_content);
let entity_types = vec![(1u32, IfcType::IfcSite)];
let georef = GeoRefExtractor::extract(&mut decoder, &entity_types)
.expect("decode ok")
.expect("legacy site georeference extracted");
let expected_lat = -(50.0 + 2.0 / 60.0 + 20.0 / 3600.0);
let expected_lon = 14.0 + 28.0 / 60.0;
assert!((georef.northings - expected_lat).abs() < 1e-9);
assert!((georef.eastings - expected_lon).abs() < 1e-9);
}
#[test]
fn test_extract_from_site_negative_zero_scan_ignores_commas_inside_quoted_strings() {
let ifc_content = r#"ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('Test'),'2;1');
FILE_NAME('test.ifc','2024-01-01',(''),(''),'','','');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCSITE('1abc',$,'Site, (annex)',$,$,$,$,$,.ELEMENT.,(-0,30,0),(-0,45,0),0.,$,$);
ENDSEC;
END-ISO-10303-21;
"#;
let mut decoder = EntityDecoder::new(ifc_content);
let entity_types = vec![(1u32, IfcType::IfcSite)];
let georef = GeoRefExtractor::extract(&mut decoder, &entity_types)
.expect("decode ok")
.expect("legacy site georeference extracted");
assert!((georef.northings - (-0.5)).abs() < 1e-9);
assert!((georef.eastings - (-0.75)).abs() < 1e-9);
}