use ifc_model::value::Value;
use ifc_model::{EntityId, Model};
use ifc_schema::SchemaVersion;
use super::angle::{read_angle, Axis, CompoundPlaneAngle};
use crate::error::{GeorefError, GeorefResult};
use crate::slot::site as slot;
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq)]
pub struct SiteReference {
pub entity: EntityId,
pub version: SchemaVersion,
pub ref_latitude: Option<CompoundPlaneAngle>,
pub ref_longitude: Option<CompoundPlaneAngle>,
pub ref_elevation: Option<f64>,
}
impl SiteReference {
#[must_use]
pub fn latitude_degrees(&self) -> Option<f64> {
self.ref_latitude.map(|a| a.decimal_degrees())
}
#[must_use]
pub fn longitude_degrees(&self) -> Option<f64> {
self.ref_longitude.map(|a| a.decimal_degrees())
}
}
pub fn site_reference(
model: &Model,
site: EntityId,
project_metres_per_unit: f64,
) -> GeorefResult<SiteReference> {
let version = site_release(model)?;
let entity = model.get(site).ok_or(GeorefError::MissingEntity {
referrer: site,
missing: site,
})?;
if !entity.is_type("IFCSITE") {
return Err(GeorefError::WrongType {
entity: site,
expected: "IFCSITE",
actual: entity.type_name.to_string(),
});
}
if !project_metres_per_unit.is_finite() || project_metres_per_unit <= 0.0 {
return Err(GeorefError::InvalidUnit {
entity: site,
detail: "project length scale must be finite and positive",
});
}
let ref_latitude = read_angle(
entity.attribute(slot::REF_LATITUDE),
site,
slot::REF_LATITUDE,
Axis::Latitude,
version,
)?;
let ref_longitude = read_angle(
entity.attribute(slot::REF_LONGITUDE),
site,
slot::REF_LONGITUDE,
Axis::Longitude,
version,
)?;
let invalid_elevation = GeorefError::InvalidAttribute {
entity: site,
index: slot::REF_ELEVATION,
name: "RefElevation",
};
let ref_elevation = match entity
.attribute(slot::REF_ELEVATION)
.map(Value::unwrap_typed)
{
None | Some(Value::Null) => None,
Some(value) => {
let metres = value.as_f64().ok_or(invalid_elevation.clone())? * project_metres_per_unit;
if !metres.is_finite() {
return Err(invalid_elevation);
}
Some(metres)
}
};
Ok(SiteReference {
entity: site,
version,
ref_latitude,
ref_longitude,
ref_elevation,
})
}
fn site_release(model: &Model) -> GeorefResult<SchemaVersion> {
let token = match model.header().schema.as_slice() {
[] => return Err(GeorefError::MissingSchema),
[token] => token,
tokens => {
return Err(GeorefError::AmbiguousSchema {
tokens: tokens.to_vec(),
})
}
};
match SchemaVersion::from_header_token(token) {
Some(version @ (SchemaVersion::Ifc2x3 | SchemaVersion::Ifc4 | SchemaVersion::Ifc4x3)) => {
Ok(version)
}
_ => Err(GeorefError::UnsupportedSchema {
token: token.clone(),
}),
}
}