Skip to main content

ifc_tabular/read/
series.rs

1//! Reading `IfcRegularTimeSeries` and `IfcIrregularTimeSeries`.
2//!
3//! Neither subtype nor `IfcTimeSeries` declares a WHERE rule in IFC4 ADD2
4//! TC1 or IFC4X3 ADD2, so the defects a read can find are structural:
5//! missing required attributes, malformed slots, empty `LIST [1:?]`s, and
6//! `Values` members that dangle or name the other subtype's value record.
7//! A regular series lists `IfcTimeSeriesValue`, which has no timestamp;
8//! an irregular one lists `IfcIrregularTimeSeriesValue`, which does.
9
10use ifc_model::{Entity, EntityId, Model, Value};
11use ifc_schema::Schema;
12
13use super::decode::{Need, Slots};
14use super::issue::TabularIssue;
15
16pub(crate) const REGULAR: &str = "IFCREGULARTIMESERIES";
17pub(crate) const IRREGULAR: &str = "IFCIRREGULARTIMESERIES";
18const VALUE: &str = "IFCTIMESERIESVALUE";
19const IRREGULAR_VALUE: &str = "IFCIRREGULARTIMESERIESVALUE";
20
21/// Which `IfcTimeSeries` subtype was read.
22#[derive(Debug, Clone, Copy, PartialEq)]
23#[non_exhaustive]
24pub enum TimeSeriesKind {
25    /// `IfcRegularTimeSeries`: instants follow from `TimeStep`.
26    Regular {
27        /// `TimeStep` in seconds; `None` when missing or malformed.
28        time_step: Option<f64>,
29    },
30    /// `IfcIrregularTimeSeries`: each value carries its own timestamp.
31    Irregular,
32}
33
34/// A borrowed time series, its resolved value records, and every defect
35/// found on the way.
36#[derive(Debug, Clone)]
37pub struct TimeSeries<'m> {
38    id: EntityId,
39    kind: TimeSeriesKind,
40    name: Option<&'m str>,
41    description: Option<&'m str>,
42    start_time: Option<&'m str>,
43    end_time: Option<&'m str>,
44    data_type: Option<&'m str>,
45    data_origin: Option<&'m str>,
46    user_defined_data_origin: Option<&'m str>,
47    unit: Option<EntityId>,
48    values: Vec<SeriesValue<'m>>,
49    issues: Vec<TabularIssue>,
50}
51
52impl<'m> TimeSeries<'m> {
53    /// The series' id.
54    #[must_use]
55    pub const fn id(&self) -> EntityId {
56        self.id
57    }
58
59    /// Regular or irregular, with the regular step.
60    #[must_use]
61    pub const fn kind(&self) -> TimeSeriesKind {
62        self.kind
63    }
64
65    /// `Name`.
66    #[must_use]
67    pub const fn name(&self) -> Option<&'m str> {
68        self.name
69    }
70
71    /// `Description`.
72    #[must_use]
73    pub const fn description(&self) -> Option<&'m str> {
74        self.description
75    }
76
77    /// `StartTime`, an `IfcDateTime` as written.
78    #[must_use]
79    pub const fn start_time(&self) -> Option<&'m str> {
80        self.start_time
81    }
82
83    /// `EndTime`, an `IfcDateTime` as written.
84    #[must_use]
85    pub const fn end_time(&self) -> Option<&'m str> {
86        self.end_time
87    }
88
89    /// `TimeSeriesDataType` token.
90    #[must_use]
91    pub const fn data_type(&self) -> Option<&'m str> {
92        self.data_type
93    }
94
95    /// `DataOrigin` token.
96    #[must_use]
97    pub const fn data_origin(&self) -> Option<&'m str> {
98        self.data_origin
99    }
100
101    /// `UserDefinedDataOrigin`.
102    #[must_use]
103    pub const fn user_defined_data_origin(&self) -> Option<&'m str> {
104        self.user_defined_data_origin
105    }
106
107    /// `Unit`, an `IfcUnit` reference.
108    #[must_use]
109    pub const fn unit(&self) -> Option<EntityId> {
110        self.unit
111    }
112
113    /// Value records in `Values` order that resolved to the subtype's
114    /// own value entity.
115    #[must_use]
116    pub fn values(&self) -> &[SeriesValue<'m>] {
117        &self.values
118    }
119
120    /// Every defect found.
121    #[must_use]
122    pub fn issues(&self) -> &[TabularIssue] {
123        &self.issues
124    }
125}
126
127/// A borrowed `IfcTimeSeriesValue` or `IfcIrregularTimeSeriesValue`.
128#[derive(Debug, Clone, Copy)]
129pub struct SeriesValue<'m> {
130    id: EntityId,
131    timestamp: Option<&'m str>,
132    values: Option<&'m [Value]>,
133}
134
135impl<'m> SeriesValue<'m> {
136    /// The value record's id.
137    #[must_use]
138    pub const fn id(&self) -> EntityId {
139        self.id
140    }
141
142    /// `TimeStamp`; always `None` in a regular series, whose value
143    /// records declare none.
144    #[must_use]
145    pub const fn timestamp(&self) -> Option<&'m str> {
146        self.timestamp
147    }
148
149    /// `ListValues` as written; `None` when missing or malformed.
150    #[must_use]
151    pub const fn values(&self) -> Option<&'m [Value]> {
152        self.values
153    }
154}
155
156/// Read a time series the caller has already type-checked as `type_name`.
157pub(crate) fn read_series<'m>(
158    model: &'m Model,
159    schema: &'m Schema,
160    id: EntityId,
161    entity: &'m Entity,
162    type_name: &'static str,
163) -> TimeSeries<'m> {
164    let regular = type_name == REGULAR;
165    let mut issues = Vec::new();
166    let mut slots = Slots::open(model, schema, id, entity, type_name, &mut issues);
167    let name = slots.text("Name", Need::Required);
168    let description = slots.text("Description", Need::Optional);
169    let start_time = slots.text("StartTime", Need::Required);
170    let end_time = slots.text("EndTime", Need::Required);
171    let data_type = slots.enumeration("TimeSeriesDataType", Need::Required);
172    let data_origin = slots.enumeration("DataOrigin", Need::Required);
173    let user_defined_data_origin = slots.text("UserDefinedDataOrigin", Need::Optional);
174    let unit = slots.reference("Unit", Need::Optional);
175    let kind = if regular {
176        TimeSeriesKind::Regular {
177            time_step: slots.number("TimeStep", Need::Required),
178        }
179    } else {
180        TimeSeriesKind::Irregular
181    };
182    let value_type = if regular { VALUE } else { IRREGULAR_VALUE };
183    let members = slots.records("Values", Need::Required, value_type);
184
185    let values = members
186        .into_iter()
187        .flatten()
188        .map(|(value, record)| {
189            let mut slots = Slots::open(model, schema, value, record, value_type, &mut issues);
190            SeriesValue {
191                id: value,
192                timestamp: if regular {
193                    None
194                } else {
195                    slots.text("TimeStamp", Need::Required)
196                },
197                values: slots.values("ListValues", Need::Required),
198            }
199        })
200        .collect();
201
202    TimeSeries {
203        id,
204        kind,
205        name,
206        description,
207        start_time,
208        end_time,
209        data_type,
210        data_origin,
211        user_defined_data_origin,
212        unit,
213        values,
214        issues,
215    }
216}