use super::*;
const GEOREF_IFC: &str = r#"ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('georef fixture'),'2;1');
FILE_NAME('georef.ifc','2026-06-01T00:00:00',(''),(''),'','','');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROJECT('0$ScRe4drECQ4DMSqUjd6d',$,'P',$,$,$,$,(#2),#3);
#2=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.0E-5,#5,$);
#3=IFCUNITASSIGNMENT((#6));
#4=IFCCARTESIANPOINT((0.,0.,0.));
#5=IFCAXIS2PLACEMENT3D(#4,$,$);
#6=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
#10=IFCPROJECTEDCRS('EPSG:32632','WGS84 / UTM zone 32N','WGS84',$,'UTM','32N',$);
#11=IFCMAPCONVERSION(#2,#10,1000.5,2000.25,42.0,0.866025,0.5,1.0);
ENDSEC;
END-ISO-10303-21;
"#;
#[test]
fn extracts_map_conversion_and_crs() {
let geo = extract_georeferencing(GEOREF_IFC).expect("expected georeferencing");
assert_eq!(geo.crs_name.as_deref(), Some("EPSG:32632"));
assert_eq!(geo.geodetic_datum.as_deref(), Some("WGS84"));
assert_eq!(geo.map_projection.as_deref(), Some("UTM"));
assert!((geo.eastings - 1000.5).abs() < 1e-6);
assert!((geo.northings - 2000.25).abs() < 1e-6);
assert!((geo.orthogonal_height - 42.0).abs() < 1e-6);
assert!(
(geo.rotation_degrees - 30.0).abs() < 1e-3,
"rotation should be ~30°, got {}",
geo.rotation_degrees
);
assert!((geo.transform_matrix[12] - 1000.5).abs() < 1e-6);
assert!((geo.transform_matrix[13] - 2000.25).abs() < 1e-6);
assert_eq!(geo.crs_description.as_deref(), Some("WGS84 / UTM zone 32N"));
assert_eq!(geo.map_zone.as_deref(), Some("32N"));
assert_eq!(geo.map_unit, None);
assert_eq!(geo.map_unit_scale, None);
assert_eq!(geo.source.as_deref(), Some("mapConversion"));
}
const IFC2X3_PSET_IFC: &str = r#"ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('ifc2x3 georef pset fixture'),'2;1');
FILE_NAME('georef2x3.ifc','2026-06-01T00:00:00',(''),(''),'','','');
FILE_SCHEMA(('IFC2X3'));
ENDSEC;
DATA;
#1=IFCPROPERTYSINGLEVALUE('Eastings',$,IFCLENGTHMEASURE(1000.5),$);
#2=IFCPROPERTYSINGLEVALUE('Northings',$,IFCLENGTHMEASURE(2000.25),$);
#3=IFCPROPERTYSINGLEVALUE('OrthogonalHeight',$,IFCLENGTHMEASURE(42.),$);
#4=IFCPROPERTYSET('0PSet00000000000000001',$,'ePSet_MapConversion',$,(#1,#2,#3));
ENDSEC;
END-ISO-10303-21;
"#;
#[test]
fn extracts_ifc2x3_epset_map_conversion_fallback() {
let geo = extract_georeferencing(IFC2X3_PSET_IFC)
.expect("expected georeferencing from ePSet_MapConversion");
assert!((geo.eastings - 1000.5).abs() < 1e-6);
assert!((geo.northings - 2000.25).abs() < 1e-6);
assert!((geo.orthogonal_height - 42.0).abs() < 1e-6);
}
const IFC2X3_EPSET_LOWERCASE_IFC: &str = r#"ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('ifc-georeferencer pset fixture'),'2;1');
FILE_NAME('georef-lc.ifc','2026-06-26T00:00:00',(''),(''),'','','');
FILE_SCHEMA(('IFC2X3'));
ENDSEC;
DATA;
#1=IFCPROPERTYSINGLEVALUE('TargetCRS',$,IFCLABEL('EPSG:7415'),$);
#2=IFCPROPERTYSINGLEVALUE('Eastings',$,IFCLENGTHMEASURE(160073528.13858587),$);
#3=IFCPROPERTYSINGLEVALUE('Northings',$,IFCLENGTHMEASURE(384153306.2191765),$);
#4=IFCPROPERTYSINGLEVALUE('OrthogonalHeight',$,IFCLENGTHMEASURE(0.),$);
#5=IFCPROPERTYSET('2If4Y3Lpv6dgTDkC5x_dnr',$,'ePset_MapConversion',$,(#1,#2,#3,#4));
#6=IFCPROPERTYSINGLEVALUE('Name',$,IFCLABEL('EPSG:7415'),$);
#7=IFCPROPERTYSET('27AKTMp8j58fBEhvkJkcNJ',$,'ePset_ProjectedCRS',$,(#6));
ENDSEC;
END-ISO-10303-21;
"#;
#[test]
fn extracts_ifc2x3_lowercase_epset_with_projected_crs_name() {
let geo = extract_georeferencing(IFC2X3_EPSET_LOWERCASE_IFC)
.expect("expected georeferencing from lowercase ePset_ sets");
assert_eq!(geo.source.as_deref(), Some("ePSetMapConversion"));
assert_eq!(geo.crs_name.as_deref(), Some("EPSG:7415"));
assert!((geo.eastings - 160073528.13858587).abs() < 1e-3);
}
const IFC2X3_EPSET_TARGETCRS_ONLY_IFC: &str = r#"ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('ifc-georeferencer targetcrs-only fixture'),'2;1');
FILE_NAME('georef-tc.ifc','2026-06-26T00:00:00',(''),(''),'','','');
FILE_SCHEMA(('IFC2X3'));
ENDSEC;
DATA;
#1=IFCPROPERTYSINGLEVALUE('TargetCRS',$,IFCLABEL('EPSG:28992'),$);
#2=IFCPROPERTYSINGLEVALUE('Eastings',$,IFCLENGTHMEASURE(1000.5),$);
#3=IFCPROPERTYSINGLEVALUE('Northings',$,IFCLENGTHMEASURE(2000.25),$);
#4=IFCPROPERTYSINGLEVALUE('OrthogonalHeight',$,IFCLENGTHMEASURE(0.),$);
#5=IFCPROPERTYSET('2If4Y3Lpv6dgTDkC5x_dnr',$,'ePset_MapConversion',$,(#1,#2,#3,#4));
ENDSEC;
END-ISO-10303-21;
"#;
#[test]
fn falls_back_to_target_crs_when_no_projected_crs_pset() {
let geo = extract_georeferencing(IFC2X3_EPSET_TARGETCRS_ONLY_IFC)
.expect("expected georeferencing from ePset_MapConversion");
assert_eq!(geo.crs_name.as_deref(), Some("EPSG:28992"));
}
const MM_MAPUNIT_IFC: &str = r#"ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('georef mm fixture'),'2;1');
FILE_NAME('georef-mm.ifc','2026-06-12T00:00:00',(''),(''),'','','');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#2=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.0E-5,#5,$);
#4=IFCCARTESIANPOINT((0.,0.,0.));
#5=IFCAXIS2PLACEMENT3D(#4,$,$);
#7=IFCSIUNIT(*,.LENGTHUNIT.,.MILLI.,.METRE.);
#10=IFCPROJECTEDCRS('EPSG:25832',$,'ETRS89',$,'UTM','32N',#7);
#11=IFCMAPCONVERSION(#2,#10,512000000.,5400000000.,0.,1.,0.,1.0);
ENDSEC;
END-ISO-10303-21;
"#;
#[test]
fn resolves_millimetre_map_unit_scale() {
let geo = extract_georeferencing(MM_MAPUNIT_IFC).expect("georef");
assert_eq!(geo.map_unit.as_deref(), Some("MILLIMETRE"));
assert_eq!(geo.map_unit_scale, Some(0.001));
assert_eq!(geo.map_zone.as_deref(), Some("32N"));
}
const IFC2X3_EPSET_EMPTY_CRS_NAME_IFC: &str = r#"ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('ifc-georeferencer empty-crs-name fixture'),'2;1');
FILE_NAME('georef-empty.ifc','2026-06-26T00:00:00',(''),(''),'','','');
FILE_SCHEMA(('IFC2X3'));
ENDSEC;
DATA;
#1=IFCPROPERTYSINGLEVALUE('TargetCRS',$,IFCLABEL('EPSG:28992'),$);
#2=IFCPROPERTYSINGLEVALUE('Eastings',$,IFCLENGTHMEASURE(1000.5),$);
#3=IFCPROPERTYSINGLEVALUE('Northings',$,IFCLENGTHMEASURE(2000.25),$);
#4=IFCPROPERTYSINGLEVALUE('OrthogonalHeight',$,IFCLENGTHMEASURE(0.),$);
#5=IFCPROPERTYSET('2If4Y3Lpv6dgTDkC5x_dnr',$,'ePset_MapConversion',$,(#1,#2,#3,#4));
#6=IFCPROPERTYSINGLEVALUE('Name',$,IFCLABEL(''),$);
#7=IFCPROPERTYSET('27AKTMp8j58fBEhvkJkcNJ',$,'ePset_ProjectedCRS',$,(#6));
ENDSEC;
END-ISO-10303-21;
"#;
#[test]
fn empty_projected_crs_name_falls_back_to_target_crs() {
let geo = extract_georeferencing(IFC2X3_EPSET_EMPTY_CRS_NAME_IFC)
.expect("expected georeferencing from ePset_MapConversion");
assert_eq!(geo.crs_name.as_deref(), Some("EPSG:28992"));
}
const IFC2X3_EPSET_FOOT_MAPUNIT_IFC: &str = r#"ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('ifc-georeferencer foot-mapunit fixture'),'2;1');
FILE_NAME('georef-foot.ifc','2026-06-26T00:00:00',(''),(''),'','','');
FILE_SCHEMA(('IFC2X3'));
ENDSEC;
DATA;
#2=IFCPROPERTYSINGLEVALUE('Eastings',$,IFCLENGTHMEASURE(1000.5),$);
#3=IFCPROPERTYSINGLEVALUE('Northings',$,IFCLENGTHMEASURE(2000.25),$);
#4=IFCPROPERTYSINGLEVALUE('OrthogonalHeight',$,IFCLENGTHMEASURE(0.),$);
#5=IFCPROPERTYSET('2If4Y3Lpv6dgTDkC5x_dnr',$,'ePset_MapConversion',$,(#2,#3,#4));
#6=IFCPROPERTYSINGLEVALUE('Name',$,IFCLABEL('EPSG:2225'),$);
#8=IFCPROPERTYSINGLEVALUE('MapUnit',$,IFCLABEL('FOOT'),$);
#7=IFCPROPERTYSET('27AKTMp8j58fBEhvkJkcNJ',$,'ePset_ProjectedCRS',$,(#6,#8));
ENDSEC;
END-ISO-10303-21;
"#;
#[test]
fn epset_map_unit_label_resolves_scale() {
let geo = extract_georeferencing(IFC2X3_EPSET_FOOT_MAPUNIT_IFC).expect("georef");
assert_eq!(geo.crs_name.as_deref(), Some("EPSG:2225"));
assert_eq!(geo.map_unit.as_deref(), Some("FOOT"));
assert_eq!(geo.map_unit_scale, Some(0.3048));
}
const TWO_CONVERSIONS_IFC: &str = r#"ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('georef two-conversions fixture'),'2;1');
FILE_NAME('georef-two.ifc','2026-06-12T00:00:00',(''),(''),'','','');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#2=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.0E-5,#5,$);
#4=IFCCARTESIANPOINT((0.,0.,0.));
#5=IFCAXIS2PLACEMENT3D(#4,$,$);
#10=IFCPROJECTEDCRS('EPSG:32632',$,'WGS84',$,'UTM','32N',$);
#11=IFCMAPCONVERSION(#2,#10,111.0,222.0,0.,1.,0.,1.0);
#12=IFCMAPCONVERSION(#2,#10,999.0,888.0,0.,1.,0.,1.0);
ENDSEC;
END-ISO-10303-21;
"#;
#[test]
fn first_map_conversion_wins() {
let geo = extract_georeferencing(TWO_CONVERSIONS_IFC).expect("georef");
assert!((geo.eastings - 111.0).abs() < 1e-9);
assert!((geo.northings - 222.0).abs() < 1e-9);
}
const NON_UNIT_AXIS_IFC: &str = r#"ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('georef non-unit-axis fixture'),'2;1');
FILE_NAME('georef-axis.ifc','2026-06-12T00:00:00',(''),(''),'','','');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#2=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.0E-5,#5,$);
#4=IFCCARTESIANPOINT((0.,0.,0.));
#5=IFCAXIS2PLACEMENT3D(#4,$,$);
#10=IFCPROJECTEDCRS('EPSG:32632',$,'WGS84',$,'UTM','32N',$);
#11=IFCMAPCONVERSION(#2,#10,1000.,2000.,0.,3.0,4.0,1.0);
ENDSEC;
END-ISO-10303-21;
"#;
#[test]
fn non_unit_axis_is_normalised() {
let geo = extract_georeferencing(NON_UNIT_AXIS_IFC).expect("georef");
assert!((geo.x_axis_abscissa - 0.6).abs() < 1e-9);
assert!((geo.x_axis_ordinate - 0.8).abs() < 1e-9);
assert!((geo.rotation_degrees - 53.13010235415598).abs() < 1e-9);
assert!((geo.transform_matrix[0] - 0.6).abs() < 1e-9);
assert!((geo.transform_matrix[1] - 0.8).abs() < 1e-9);
}
const SITE_ONLY_IFC: &str = r#"ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('georef site-only fixture'),'2;1');
FILE_NAME('georef-site.ifc','2026-06-12T00:00:00',(''),(''),'','','');
FILE_SCHEMA(('IFC2X3'));
ENDSEC;
DATA;
#4=IFCCARTESIANPOINT((0.,0.,0.));
#5=IFCAXIS2PLACEMENT3D(#4,$,$);
#11=IFCLOCALPLACEMENT($,#5);
#10=IFCSITE('0Site0000000000000001',$,'Site',$,$,#11,$,$,.ELEMENT.,(47,22,30,0),(8,32,15,0),420.5,$,$);
ENDSEC;
END-ISO-10303-21;
"#;
#[test]
fn site_lat_long_fallback_matches_ts_parser() {
let geo = extract_georeferencing(SITE_ONLY_IFC).expect("site georef");
assert_eq!(geo.source.as_deref(), Some("siteLocation"));
assert_eq!(geo.crs_name.as_deref(), Some("EPSG:4326"));
assert_eq!(geo.geodetic_datum.as_deref(), Some("WGS84"));
assert_eq!(geo.map_unit.as_deref(), Some("DEGREE"));
assert!((geo.northings - 47.375).abs() < 1e-9, "lat {}", geo.northings);
assert!((geo.eastings - 8.5375).abs() < 1e-9, "long {}", geo.eastings);
assert!((geo.orthogonal_height - 420.5).abs() < 1e-9);
}
#[test]
fn returns_none_without_georeferencing() {
let plain = r#"ISO-10303-21;
HEADER;
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROJECT('0$ScRe4drECQ4DMSqUjd6d',$,'P',$,$,$,$,$,$);
ENDSEC;
END-ISO-10303-21;
"#;
assert!(extract_georeferencing(plain).is_none());
}
#[test]
fn the_index_taking_variant_agrees_with_the_wrapper() {
for content in [
GEOREF_IFC.as_bytes(),
SITE_ONLY_IFC.as_bytes(),
MM_MAPUNIT_IFC.as_bytes(),
] {
let index = Arc::new(ifc_lite_core::build_entity_index(content));
assert_eq!(
extract_georeferencing(content),
extract_georeferencing_with_index(content, &index),
"the wrapper and the index-taking variant disagreed",
);
}
}
#[test]
fn the_passed_index_is_the_one_consulted() {
let content = GEOREF_IFC.as_bytes();
assert!(
extract_georeferencing(content).is_some(),
"the fixture has to georeference, or the assertion below proves nothing",
);
let empty = Arc::new(EntityIndex::default());
assert!(
extract_georeferencing_with_index(content, &empty).is_none(),
"an empty index was ignored, so the function built one of its own",
);
}