use std::sync::Arc;
use ifc_lite_core::{
keyword_eq, EntityDecoder, EntityIndex, EntityScanner, GeoRefExtractor, IfcType,
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Georeferencing {
#[serde(skip_serializing_if = "Option::is_none")]
pub crs_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub geodetic_datum: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub vertical_datum: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub map_projection: Option<String>,
pub eastings: f64,
pub northings: f64,
pub orthogonal_height: f64,
pub x_axis_abscissa: f64,
pub x_axis_ordinate: f64,
pub scale: f64,
#[serde(default = "default_axis_factor")]
pub factor_x: f64,
#[serde(default = "default_axis_factor")]
pub factor_y: f64,
#[serde(default = "default_axis_factor")]
pub factor_z: f64,
pub rotation_degrees: f64,
pub transform_matrix: [f64; 16],
#[serde(skip_serializing_if = "Option::is_none", default)]
pub crs_description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub map_zone: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub map_unit: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub map_unit_scale: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub source: Option<String>,
}
const fn default_axis_factor() -> f64 {
1.0
}
impl Default for Georeferencing {
fn default() -> Self {
Self {
crs_name: None,
geodetic_datum: None,
vertical_datum: None,
map_projection: None,
eastings: 0.0,
northings: 0.0,
orthogonal_height: 0.0,
x_axis_abscissa: 0.0,
x_axis_ordinate: 0.0,
scale: 0.0,
factor_x: 1.0,
factor_y: 1.0,
factor_z: 1.0,
rotation_degrees: 0.0,
transform_matrix: [0.0; 16],
crs_description: None,
map_zone: None,
map_unit: None,
map_unit_scale: None,
source: None,
}
}
}
impl Georeferencing {
fn from_core(geo: &ifc_lite_core::GeoReference) -> Self {
Self {
crs_name: geo.crs_name.clone(),
geodetic_datum: geo.geodetic_datum.clone(),
vertical_datum: geo.vertical_datum.clone(),
map_projection: geo.map_projection.clone(),
eastings: geo.eastings,
northings: geo.northings,
orthogonal_height: geo.orthogonal_height,
x_axis_abscissa: geo.x_axis_abscissa,
x_axis_ordinate: geo.x_axis_ordinate,
scale: geo.scale,
factor_x: geo.factor_x,
factor_y: geo.factor_y,
factor_z: geo.factor_z,
rotation_degrees: geo.rotation().to_degrees(),
transform_matrix: geo.to_matrix(),
crs_description: geo.crs_description.clone(),
map_zone: geo.map_zone.clone(),
map_unit: geo.map_unit.clone(),
map_unit_scale: geo.map_unit_scale,
source: geo.source.map(|s| s.label().to_string()),
}
}
}
pub fn extract_georeferencing<T>(content: &T) -> Option<Georeferencing>
where
T: AsRef<[u8]> + ?Sized,
{
let content = content.as_ref();
let entity_index = Arc::new(crate::build_entity_index_parallel(content));
extract_georeferencing_with_index(content, &entity_index)
}
pub fn extract_georeferencing_with_index(
content: &[u8],
entity_index: &Arc<EntityIndex>,
) -> Option<Georeferencing> {
let mut entity_types = Vec::new();
let mut scanner = EntityScanner::new(content);
while let Some((id, type_name, _, _)) = scanner.next_entity() {
if let Some(ifc_type) = georeferencing_candidate_type(type_name) {
entity_types.push((id, ifc_type));
}
}
extract_georeferencing_from_candidates(
&mut EntityDecoder::with_arc_index(content, entity_index.clone()), &entity_types)
}
pub(crate) fn georeferencing_candidate_type(type_name: &str) -> Option<IfcType> {
if keyword_eq(type_name, "IFCMAPCONVERSION")
|| keyword_eq(type_name, "IFCMAPCONVERSIONSCALED")
{
return Some(IfcType::IfcMapConversion);
}
if keyword_eq(type_name, "IFCPROJECTEDCRS") {
return Some(IfcType::IfcProjectedCRS);
}
if keyword_eq(type_name, "IFCPROPERTYSET") {
return Some(IfcType::IfcPropertySet);
}
if keyword_eq(type_name, "IFCSITE") {
return Some(IfcType::IfcSite);
}
None
}
pub(crate) fn extract_georeferencing_from_candidates(
decoder: &mut EntityDecoder<'_>,
entity_types: &[(u32, IfcType)],
) -> Option<Georeferencing> {
if entity_types.is_empty() {
return None;
}
match GeoRefExtractor::extract(decoder, entity_types) {
Ok(Some(geo)) => Some(Georeferencing::from_core(&geo)),
Ok(None) => None,
Err(e) => {
tracing::debug!(error = %e, "Georeferencing extraction failed");
None
}
}
}
#[cfg(test)]
#[path = "georeferencing_tests.rs"]
mod tests;