ifc_lite_core/project_units/
measure.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Canonical mapping from an IFC *measure* value type (e.g.
6//! `IfcVolumetricFlowRateMeasure`) to the IFC *unit* type it is expressed in
7//! (`VOLUMETRICFLOWRATEUNIT`) plus the IFC-canonical SI display symbol used when
8//! the file does not declare that unit.
9//!
10//! This association is defined by the IFC specification convention
11//! (`IfcXxxMeasure` <-> `XxxUnit`) and is **not** derivable from the EXPRESS
12//! schema files, so it is authored here as the single source of truth. The
13//! TypeScript viewer mirrors this table and both are pinned to the shared
14//! parity vectors in `rust/core/tests/fixtures/unit_symbol_vectors.json`.
15
16/// How a measure maps onto the file's declared units.
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub enum MeasureUnit {
19    /// A named or derived unit-type token (matches `IfcUnitEnum` /
20    /// `IfcDerivedUnitEnum`), with the IFC-canonical SI symbol shown when the
21    /// file declares no unit for that type.
22    Typed {
23        /// Unit-type token, e.g. `"VOLUMETRICFLOWRATEUNIT"`, `"LENGTHUNIT"`.
24        unit_type: &'static str,
25        /// SI default symbol, e.g. `"m\u{00B3}/s"`, `"mm"` never appears here
26        /// (prefixes are file-declared); base SI only.
27        default_symbol: &'static str,
28    },
29    /// The value is a monetary amount; the symbol comes from `IfcMonetaryUnit`.
30    Monetary,
31    /// Dimensionless / unit-less (ratios, counts, descriptive, ...). No symbol.
32    Dimensionless,
33}
34
35/// Resolve a measure value type name (case-insensitive, with or without the
36/// leading `IFC`) to its unit mapping. Returns `None` for value types that are
37/// not measures (labels, identifiers, booleans, ...).
38pub fn measure_unit(measure_type: &str) -> Option<MeasureUnit> {
39    // Normalise: uppercase, strip a leading "IFC" so callers can pass either
40    // "IFCAREAMEASURE" or "AreaMeasure".
41    let up = measure_type.trim().to_ascii_uppercase();
42    let key = up.strip_prefix("IFC").unwrap_or(&up);
43    Some(match key {
44        // ---- Length family (all share LENGTHUNIT) -------------------------
45        "LENGTHMEASURE" | "POSITIVELENGTHMEASURE" | "NONNEGATIVELENGTHMEASURE" => {
46            MeasureUnit::Typed { unit_type: "LENGTHUNIT", default_symbol: "m" }
47        }
48        "AREAMEASURE" => MeasureUnit::Typed { unit_type: "AREAUNIT", default_symbol: "m\u{00B2}" },
49        "VOLUMEMEASURE" => {
50            MeasureUnit::Typed { unit_type: "VOLUMEUNIT", default_symbol: "m\u{00B3}" }
51        }
52        "MASSMEASURE" => MeasureUnit::Typed { unit_type: "MASSUNIT", default_symbol: "kg" },
53        "TIMEMEASURE" => MeasureUnit::Typed { unit_type: "TIMEUNIT", default_symbol: "s" },
54        "PLANEANGLEMEASURE" | "POSITIVEPLANEANGLEMEASURE" => {
55            MeasureUnit::Typed { unit_type: "PLANEANGLEUNIT", default_symbol: "rad" }
56        }
57        "SOLIDANGLEMEASURE" => {
58            MeasureUnit::Typed { unit_type: "SOLIDANGLEUNIT", default_symbol: "sr" }
59        }
60        "THERMODYNAMICTEMPERATUREMEASURE" => MeasureUnit::Typed {
61            unit_type: "THERMODYNAMICTEMPERATUREUNIT",
62            default_symbol: "K",
63        },
64
65        // ---- Named SI derived-with-special-name units ---------------------
66        "ELECTRICCURRENTMEASURE" => {
67            MeasureUnit::Typed { unit_type: "ELECTRICCURRENTUNIT", default_symbol: "A" }
68        }
69        "ELECTRICVOLTAGEMEASURE" => {
70            MeasureUnit::Typed { unit_type: "ELECTRICVOLTAGEUNIT", default_symbol: "V" }
71        }
72        "ELECTRICRESISTANCEMEASURE" => MeasureUnit::Typed {
73            unit_type: "ELECTRICRESISTANCEUNIT",
74            default_symbol: "\u{03A9}",
75        },
76        "ELECTRICCAPACITANCEMEASURE" => {
77            MeasureUnit::Typed { unit_type: "ELECTRICCAPACITANCEUNIT", default_symbol: "F" }
78        }
79        "ELECTRICCHARGEMEASURE" => {
80            MeasureUnit::Typed { unit_type: "ELECTRICCHARGEUNIT", default_symbol: "C" }
81        }
82        "ELECTRICCONDUCTANCEMEASURE" => {
83            MeasureUnit::Typed { unit_type: "ELECTRICCONDUCTANCEUNIT", default_symbol: "S" }
84        }
85        "POWERMEASURE" => MeasureUnit::Typed { unit_type: "POWERUNIT", default_symbol: "W" },
86        "ENERGYMEASURE" => MeasureUnit::Typed { unit_type: "ENERGYUNIT", default_symbol: "J" },
87        "FORCEMEASURE" => MeasureUnit::Typed { unit_type: "FORCEUNIT", default_symbol: "N" },
88        "PRESSUREMEASURE" => {
89            MeasureUnit::Typed { unit_type: "PRESSUREUNIT", default_symbol: "Pa" }
90        }
91        "FREQUENCYMEASURE" => {
92            MeasureUnit::Typed { unit_type: "FREQUENCYUNIT", default_symbol: "Hz" }
93        }
94        "INDUCTANCEMEASURE" => {
95            MeasureUnit::Typed { unit_type: "INDUCTANCEUNIT", default_symbol: "H" }
96        }
97        "ILLUMINANCEMEASURE" => {
98            MeasureUnit::Typed { unit_type: "ILLUMINANCEUNIT", default_symbol: "lx" }
99        }
100        "LUMINOUSFLUXMEASURE" => {
101            MeasureUnit::Typed { unit_type: "LUMINOUSFLUXUNIT", default_symbol: "lm" }
102        }
103        "LUMINOUSINTENSITYMEASURE" => {
104            MeasureUnit::Typed { unit_type: "LUMINOUSINTENSITYUNIT", default_symbol: "cd" }
105        }
106        "MAGNETICFLUXMEASURE" => {
107            MeasureUnit::Typed { unit_type: "MAGNETICFLUXUNIT", default_symbol: "Wb" }
108        }
109        "MAGNETICFLUXDENSITYMEASURE" => {
110            MeasureUnit::Typed { unit_type: "MAGNETICFLUXDENSITYUNIT", default_symbol: "T" }
111        }
112        "AMOUNTOFSUBSTANCEMEASURE" => {
113            MeasureUnit::Typed { unit_type: "AMOUNTOFSUBSTANCEUNIT", default_symbol: "mol" }
114        }
115        "ABSORBEDDOSEMEASURE" => {
116            MeasureUnit::Typed { unit_type: "ABSORBEDDOSEUNIT", default_symbol: "Gy" }
117        }
118        "DOSEEQUIVALENTMEASURE" => {
119            MeasureUnit::Typed { unit_type: "DOSEEQUIVALENTUNIT", default_symbol: "Sv" }
120        }
121        "RADIOACTIVITYMEASURE" => {
122            MeasureUnit::Typed { unit_type: "RADIOACTIVITYUNIT", default_symbol: "Bq" }
123        }
124
125        // ---- Derived units (IfcDerivedUnitEnum) ---------------------------
126        "VOLUMETRICFLOWRATEMEASURE" => MeasureUnit::Typed {
127            unit_type: "VOLUMETRICFLOWRATEUNIT",
128            default_symbol: "m\u{00B3}/s",
129        },
130        "MASSFLOWRATEMEASURE" => {
131            MeasureUnit::Typed { unit_type: "MASSFLOWRATEUNIT", default_symbol: "kg/s" }
132        }
133        "MASSDENSITYMEASURE" => {
134            MeasureUnit::Typed { unit_type: "MASSDENSITYUNIT", default_symbol: "kg/m\u{00B3}" }
135        }
136        "MASSPERLENGTHMEASURE" => {
137            MeasureUnit::Typed { unit_type: "MASSPERLENGTHUNIT", default_symbol: "kg/m" }
138        }
139        "LINEARVELOCITYMEASURE" => {
140            MeasureUnit::Typed { unit_type: "LINEARVELOCITYUNIT", default_symbol: "m/s" }
141        }
142        "ACCELERATIONMEASURE" => {
143            MeasureUnit::Typed { unit_type: "ACCELERATIONUNIT", default_symbol: "m/s\u{00B2}" }
144        }
145        "ANGULARVELOCITYMEASURE" => {
146            MeasureUnit::Typed { unit_type: "ANGULARVELOCITYUNIT", default_symbol: "rad/s" }
147        }
148        "ROTATIONALFREQUENCYMEASURE" => {
149            MeasureUnit::Typed { unit_type: "ROTATIONALFREQUENCYUNIT", default_symbol: "1/s" }
150        }
151        "TORQUEMEASURE" => {
152            MeasureUnit::Typed { unit_type: "TORQUEUNIT", default_symbol: "N\u{00B7}m" }
153        }
154        "LINEARFORCEMEASURE" => {
155            MeasureUnit::Typed { unit_type: "LINEARFORCEUNIT", default_symbol: "N/m" }
156        }
157        "PLANARFORCEMEASURE" => {
158            MeasureUnit::Typed { unit_type: "PLANARFORCEUNIT", default_symbol: "N/m\u{00B2}" }
159        }
160        "LINEARSTIFFNESSMEASURE" => {
161            MeasureUnit::Typed { unit_type: "LINEARSTIFFNESSUNIT", default_symbol: "N/m" }
162        }
163        "ROTATIONALSTIFFNESSMEASURE" => MeasureUnit::Typed {
164            unit_type: "ROTATIONALSTIFFNESSUNIT",
165            default_symbol: "N\u{00B7}m/rad",
166        },
167        "MODULUSOFELASTICITYMEASURE" => {
168            MeasureUnit::Typed { unit_type: "MODULUSOFELASTICITYUNIT", default_symbol: "Pa" }
169        }
170        "SHEARMODULUSMEASURE" => {
171            MeasureUnit::Typed { unit_type: "SHEARMODULUSUNIT", default_symbol: "Pa" }
172        }
173        "THERMALTRANSMITTANCEMEASURE" => MeasureUnit::Typed {
174            unit_type: "THERMALTRANSMITTANCEUNIT",
175            default_symbol: "W/(m\u{00B2}\u{00B7}K)",
176        },
177        "THERMALCONDUCTIVITYMEASURE" => MeasureUnit::Typed {
178            unit_type: "THERMALCONDUCTANCEUNIT",
179            default_symbol: "W/(m\u{00B7}K)",
180        },
181        "THERMALRESISTANCEMEASURE" => MeasureUnit::Typed {
182            unit_type: "THERMALRESISTANCEUNIT",
183            default_symbol: "m\u{00B2}\u{00B7}K/W",
184        },
185        "THERMALADMITTANCEMEASURE" => MeasureUnit::Typed {
186            unit_type: "THERMALADMITTANCEUNIT",
187            default_symbol: "W/(m\u{00B2}\u{00B7}K)",
188        },
189        "THERMALEXPANSIONCOEFFICIENTMEASURE" => MeasureUnit::Typed {
190            unit_type: "THERMALEXPANSIONCOEFFICIENTUNIT",
191            default_symbol: "1/K",
192        },
193        "SPECIFICHEATCAPACITYMEASURE" => MeasureUnit::Typed {
194            unit_type: "SPECIFICHEATCAPACITYUNIT",
195            default_symbol: "J/(kg\u{00B7}K)",
196        },
197        "HEATFLUXDENSITYMEASURE" => {
198            MeasureUnit::Typed { unit_type: "HEATFLUXDENSITYUNIT", default_symbol: "W/m\u{00B2}" }
199        }
200        "HEATINGVALUEMEASURE" => {
201            MeasureUnit::Typed { unit_type: "HEATINGVALUEUNIT", default_symbol: "J/kg" }
202        }
203        "DYNAMICVISCOSITYMEASURE" => {
204            MeasureUnit::Typed { unit_type: "DYNAMICVISCOSITYUNIT", default_symbol: "Pa\u{00B7}s" }
205        }
206        "KINEMATICVISCOSITYMEASURE" => {
207            MeasureUnit::Typed { unit_type: "KINEMATICVISCOSITYUNIT", default_symbol: "m\u{00B2}/s" }
208        }
209        "MOMENTOFINERTIAMEASURE" => {
210            MeasureUnit::Typed { unit_type: "MOMENTOFINERTIAUNIT", default_symbol: "m\u{2074}" }
211        }
212        "SECTIONMODULUSMEASURE" => {
213            MeasureUnit::Typed { unit_type: "SECTIONMODULUSUNIT", default_symbol: "m\u{00B3}" }
214        }
215        "SECTIONALAREAINTEGRALMEASURE" => {
216            MeasureUnit::Typed { unit_type: "SECTIONAREAINTEGRALUNIT", default_symbol: "m\u{2075}" }
217        }
218        "WARPINGCONSTANTMEASURE" => {
219            MeasureUnit::Typed { unit_type: "WARPINGCONSTANTUNIT", default_symbol: "m\u{2076}" }
220        }
221        "WARPINGMOMENTMEASURE" => MeasureUnit::Typed {
222            unit_type: "WARPINGMOMENTUNIT",
223            default_symbol: "N\u{00B7}m\u{00B2}",
224        },
225        "LINEARMOMENTMEASURE" => {
226            MeasureUnit::Typed { unit_type: "LINEARMOMENTUNIT", default_symbol: "N\u{00B7}m/m" }
227        }
228        "AREADENSITYMEASURE" => {
229            MeasureUnit::Typed { unit_type: "AREADENSITYUNIT", default_symbol: "kg/m\u{00B2}" }
230        }
231        "CURVATUREMEASURE" => {
232            MeasureUnit::Typed { unit_type: "CURVATUREUNIT", default_symbol: "1/m" }
233        }
234        "MOLECULARWEIGHTMEASURE" => {
235            MeasureUnit::Typed { unit_type: "MOLECULARWEIGHTUNIT", default_symbol: "kg/mol" }
236        }
237        "IONCONCENTRATIONMEASURE" => {
238            MeasureUnit::Typed { unit_type: "IONCONCENTRATIONUNIT", default_symbol: "kg/m\u{00B3}" }
239        }
240        "MOISTUREDIFFUSIVITYMEASURE" => {
241            MeasureUnit::Typed { unit_type: "MOISTUREDIFFUSIVITYUNIT", default_symbol: "m\u{00B2}/s" }
242        }
243        "VAPORPERMEABILITYMEASURE" => MeasureUnit::Typed {
244            unit_type: "VAPORPERMEABILITYUNIT",
245            default_symbol: "kg/(s\u{00B7}m\u{00B7}Pa)",
246        },
247        "ISOTHERMALMOISTURECAPACITYMEASURE" => MeasureUnit::Typed {
248            unit_type: "ISOTHERMALMOISTURECAPACITYUNIT",
249            default_symbol: "m\u{00B3}/kg",
250        },
251        "TEMPERATUREGRADIENTMEASURE" => {
252            MeasureUnit::Typed { unit_type: "TEMPERATUREGRADIENTUNIT", default_symbol: "K/m" }
253        }
254        "TEMPERATURERATEOFCHANGEMEASURE" => MeasureUnit::Typed {
255            unit_type: "TEMPERATURERATEOFCHANGEUNIT",
256            default_symbol: "K/s",
257        },
258        "SOUNDPOWERMEASURE" => {
259            MeasureUnit::Typed { unit_type: "SOUNDPOWERUNIT", default_symbol: "W" }
260        }
261        "SOUNDPOWERLEVELMEASURE" => {
262            MeasureUnit::Typed { unit_type: "SOUNDPOWERLEVELUNIT", default_symbol: "dB" }
263        }
264        "SOUNDPRESSUREMEASURE" => {
265            MeasureUnit::Typed { unit_type: "SOUNDPRESSUREUNIT", default_symbol: "Pa" }
266        }
267        "SOUNDPRESSURELEVELMEASURE" => {
268            MeasureUnit::Typed { unit_type: "SOUNDPRESSURELEVELUNIT", default_symbol: "dB" }
269        }
270        "MODULUSOFSUBGRADEREACTIONMEASURE" => MeasureUnit::Typed {
271            unit_type: "MODULUSOFSUBGRADEREACTIONUNIT",
272            default_symbol: "N/m\u{00B3}",
273        },
274        "MODULUSOFLINEARSUBGRADEREACTIONMEASURE" => MeasureUnit::Typed {
275            unit_type: "MODULUSOFLINEARSUBGRADEREACTIONUNIT",
276            default_symbol: "N/m\u{00B2}",
277        },
278        "MODULUSOFROTATIONALSUBGRADEREACTIONMEASURE" => MeasureUnit::Typed {
279            unit_type: "MODULUSOFROTATIONALSUBGRADEREACTIONUNIT",
280            default_symbol: "N/rad",
281        },
282        "ROTATIONALMASSMEASURE" => {
283            MeasureUnit::Typed { unit_type: "ROTATIONALMASSUNIT", default_symbol: "kg\u{00B7}m\u{00B2}" }
284        }
285        "INTEGERCOUNTRATEMEASURE" => {
286            MeasureUnit::Typed { unit_type: "INTEGERCOUNTRATEUNIT", default_symbol: "1/s" }
287        }
288        "LUMINOUSINTENSITYDISTRIBUTIONMEASURE" => MeasureUnit::Typed {
289            unit_type: "LUMINOUSINTENSITYDISTRIBUTIONUNIT",
290            default_symbol: "cd/lm",
291        },
292
293        // ---- Monetary -----------------------------------------------------
294        "MONETARYMEASURE" => MeasureUnit::Monetary,
295
296        // ---- Dimensionless / unit-less ------------------------------------
297        "RATIOMEASURE" | "NORMALISEDRATIOMEASURE" | "POSITIVERATIOMEASURE" | "COUNTMEASURE"
298        | "NUMERICMEASURE" | "DESCRIPTIVEMEASURE" | "CONTEXTDEPENDENTMEASURE" | "PHMEASURE"
299        | "CURVEMEASURE" | "COMPOUNDPLANEANGLEMEASURE" | "DERIVEDMEASURE" | "MEASURE" => {
300            MeasureUnit::Dimensionless
301        }
302
303        // Not a measure value type (label, identifier, boolean, ...).
304        _ => return None,
305    })
306}
307
308#[cfg(test)]
309mod tests {
310    use super::*;
311
312    #[test]
313    fn issue_1573_flow_rate_maps_to_derived_unit() {
314        assert_eq!(
315            measure_unit("IFCVOLUMETRICFLOWRATEMEASURE"),
316            Some(MeasureUnit::Typed {
317                unit_type: "VOLUMETRICFLOWRATEUNIT",
318                default_symbol: "m\u{00B3}/s",
319            })
320        );
321    }
322
323    #[test]
324    fn accepts_prefixed_or_bare_names_case_insensitively() {
325        let a = measure_unit("IfcAreaMeasure");
326        let b = measure_unit("AREAMEASURE");
327        assert_eq!(a, b);
328        assert_eq!(
329            a,
330            Some(MeasureUnit::Typed { unit_type: "AREAUNIT", default_symbol: "m\u{00B2}" })
331        );
332    }
333
334    #[test]
335    fn length_family_shares_length_unit() {
336        for m in ["IFCLENGTHMEASURE", "IFCPOSITIVELENGTHMEASURE", "IFCNONNEGATIVELENGTHMEASURE"] {
337            assert_eq!(
338                measure_unit(m),
339                Some(MeasureUnit::Typed { unit_type: "LENGTHUNIT", default_symbol: "m" })
340            );
341        }
342    }
343
344    #[test]
345    fn ratios_and_counts_are_dimensionless() {
346        for m in ["IFCRATIOMEASURE", "IFCCOUNTMEASURE", "IFCNORMALISEDRATIOMEASURE"] {
347            assert_eq!(measure_unit(m), Some(MeasureUnit::Dimensionless));
348        }
349    }
350
351    #[test]
352    fn non_measures_return_none() {
353        assert_eq!(measure_unit("IFCLABEL"), None);
354        assert_eq!(measure_unit("IFCBOOLEAN"), None);
355        assert_eq!(measure_unit("IFCIDENTIFIER"), None);
356    }
357
358    #[test]
359    fn monetary_is_flagged() {
360        assert_eq!(measure_unit("IFCMONETARYMEASURE"), Some(MeasureUnit::Monetary));
361    }
362}