Skip to main content

ifc_tabular/
series.rs

1//! `IfcTimeSeries` subtypes and their value records.
2
3use ifc_model::{Entity, EntityId, Transaction, Value};
4
5use crate::error::{TabularError, TabularResult};
6
7const REGULAR: &str = "IFCREGULARTIMESERIES";
8const IRREGULAR: &str = "IFCIRREGULARTIMESERIES";
9const VALUE: &str = "IFCTIMESERIESVALUE";
10const IRREGULAR_VALUE: &str = "IFCIRREGULARTIMESERIESVALUE";
11
12/// Stage an `IfcTimeSeriesValue`.
13///
14/// # Errors
15///
16/// Refuses an empty value list: `ListValues` is `LIST [1:?]`.
17pub fn add_time_series_value(tx: &mut Transaction, values: Vec<Value>) -> TabularResult<EntityId> {
18    if values.is_empty() {
19        return Err(TabularError::EmptyList {
20            entity: VALUE,
21            attribute: "ListValues",
22        });
23    }
24    Ok(tx.create(Entity::new(VALUE, vec![Value::List(values)])))
25}
26
27/// Stage an `IfcIrregularTimeSeriesValue`.
28///
29/// Carries its own `TimeStamp`: an irregular series has no step to derive
30/// the instant from, so each value states when it happened.
31///
32/// # Errors
33///
34/// Refuses a blank timestamp and an empty value list.
35pub fn add_irregular_value(
36    tx: &mut Transaction,
37    timestamp: &str,
38    values: Vec<Value>,
39) -> TabularResult<EntityId> {
40    if timestamp.trim().is_empty() {
41        return Err(TabularError::BlankRequired {
42            entity: IRREGULAR_VALUE,
43            attribute: "TimeStamp",
44        });
45    }
46    if values.is_empty() {
47        return Err(TabularError::EmptyList {
48            entity: IRREGULAR_VALUE,
49            attribute: "ListValues",
50        });
51    }
52    let attributes = vec![Value::Text(timestamp.into()), Value::List(values)];
53    Ok(tx.create(Entity::new(IRREGULAR_VALUE, attributes)))
54}
55
56/// The `IfcTimeSeries` supertype attributes, shared by both subtypes.
57#[derive(Debug, Clone, Copy, Default)]
58pub struct SeriesDraft<'a> {
59    /// `Name`. Required.
60    pub name: &'a str,
61    /// `Description`.
62    pub description: Option<&'a str>,
63    /// `StartTime`. Required.
64    pub start_time: &'a str,
65    /// `EndTime`. Required.
66    pub end_time: &'a str,
67    /// `TimeSeriesDataType`. Required enumeration token.
68    pub data_type: &'a str,
69    /// `DataOrigin`. Required enumeration token.
70    pub data_origin: &'a str,
71    /// `UserDefinedDataOrigin`.
72    pub user_defined_data_origin: Option<&'a str>,
73    /// `Unit`, an `IfcUnit` reference.
74    pub unit: Option<EntityId>,
75}
76
77fn supertype_slots(entity: &'static str, draft: SeriesDraft<'_>) -> TabularResult<Vec<Value>> {
78    for (attribute, text) in [
79        ("Name", draft.name),
80        ("StartTime", draft.start_time),
81        ("EndTime", draft.end_time),
82        ("TimeSeriesDataType", draft.data_type),
83        ("DataOrigin", draft.data_origin),
84    ] {
85        if text.trim().is_empty() {
86            return Err(TabularError::BlankRequired { entity, attribute });
87        }
88    }
89    Ok(vec![
90        Value::Text(draft.name.into()),
91        draft
92            .description
93            .map_or(Value::Null, |text| Value::Text(text.into())),
94        Value::Text(draft.start_time.into()),
95        Value::Text(draft.end_time.into()),
96        Value::Enum(draft.data_type.into()),
97        Value::Enum(draft.data_origin.into()),
98        draft
99            .user_defined_data_origin
100            .map_or(Value::Null, |text| Value::Text(text.into())),
101        draft.unit.map_or(Value::Null, Value::Ref),
102    ])
103}
104
105/// Stage an `IfcRegularTimeSeries`.
106///
107/// A regular series states one `TimeStep` and derives every instant from
108/// it, so its values carry no timestamps of their own.
109///
110/// # Errors
111///
112/// Refuses blank required text, a non-finite or non-positive step, and an
113/// empty value list.
114pub fn add_regular_time_series(
115    tx: &mut Transaction,
116    draft: SeriesDraft<'_>,
117    time_step: f64,
118    values: &[EntityId],
119) -> TabularResult<EntityId> {
120    let mut attributes = supertype_slots(REGULAR, draft)?;
121    if !time_step.is_finite() || time_step <= 0.0 {
122        return Err(TabularError::NotFinite {
123            entity: REGULAR,
124            attribute: "TimeStep",
125        });
126    }
127    if values.is_empty() {
128        return Err(TabularError::EmptyList {
129            entity: REGULAR,
130            attribute: "Values",
131        });
132    }
133    attributes.push(Value::Real(time_step));
134    attributes.push(Value::List(
135        values.iter().copied().map(Value::Ref).collect(),
136    ));
137    Ok(tx.create(Entity::new(REGULAR, attributes)))
138}
139
140/// Stage an `IfcIrregularTimeSeries`.
141///
142/// # Errors
143///
144/// Refuses blank required text and an empty value list.
145pub fn add_irregular_time_series(
146    tx: &mut Transaction,
147    draft: SeriesDraft<'_>,
148    values: &[EntityId],
149) -> TabularResult<EntityId> {
150    let mut attributes = supertype_slots(IRREGULAR, draft)?;
151    if values.is_empty() {
152        return Err(TabularError::EmptyList {
153            entity: IRREGULAR,
154            attribute: "Values",
155        });
156    }
157    attributes.push(Value::List(
158        values.iter().copied().map(Value::Ref).collect(),
159    ));
160    Ok(tx.create(Entity::new(IRREGULAR, attributes)))
161}