#![cfg(all(feature = "georef", feature = "properties"))]
use std::fmt;
use ifc_georef::{resolve_project_to_map_in, GeorefError, GeorefView, ProjectToMap};
use ifc_model::{EntityId, Model};
use ifc_properties::{exact_unit, ExactUnitError};
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum GeoreferencingError {
Georef(GeorefError),
ProjectLengthUnit(ExactUnitError),
OffsetLengthUnit {
unit: Option<EntityId>,
},
}
impl fmt::Display for GeoreferencingError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Georef(error) => write!(f, "{error}"),
Self::ProjectLengthUnit(error) => write!(f, "project length unit: {error}"),
Self::OffsetLengthUnit { unit } => {
write!(f, "project length unit {unit:?} has an offset")
}
}
}
}
impl std::error::Error for GeoreferencingError {}
pub fn georeferencing(model: &Model) -> Result<Vec<ProjectToMap>, GeoreferencingError> {
let view = GeorefView::for_model(model).map_err(GeoreferencingError::Georef)?;
let schema = view.schema();
let types: Vec<&str> = model
.type_histogram()
.into_iter()
.filter(|(name, _)| schema.is_a(name, "IFCCOORDINATEOPERATION"))
.map(|(name, _)| name)
.collect();
let mut operations: Vec<EntityId> = types
.iter()
.flat_map(|name| model.ids_of_type(name).iter().copied())
.collect();
if operations.is_empty() {
return Ok(Vec::new());
}
let position: std::collections::HashMap<EntityId, usize> =
model.ids().enumerate().map(|(i, id)| (id, i)).collect();
operations.sort_by_key(|id| position.get(id).copied().unwrap_or(usize::MAX));
let unit = exact_unit(model, "IFCLENGTHMEASURE", None)
.map_err(GeoreferencingError::ProjectLengthUnit)?;
if unit.offset != 0.0 {
return Err(GeoreferencingError::OffsetLengthUnit { unit: unit.unit });
}
operations
.into_iter()
.map(|id| resolve_project_to_map_in(&view, id, unit.scale))
.collect::<Result<_, _>>()
.map_err(GeoreferencingError::Georef)
}