1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//! Unit *labels* and the scales they resolve to.
//!
//! Two readers need the same answer to "what does this unit label mean in
//! metres": [`crate::units`], reading an `IfcSIUnit`/`IfcConversionBasedUnit`
//! declaration, and [`crate::georef`], reading the free-text
//! `ePSet_ProjectedCRS.MapUnit` of the IFC2x3 georeferencing convention. Both
//! tables and the MapUnit label rules live here so the two cannot drift; the
//! `units::` paths are preserved as re-exports.
/// SI Prefix multiplier for a member of the `IfcSIPrefix` EXPRESS
/// enumeration, or `None` when the string is not one of its sixteen members.
///
/// The checked form exists because callers that need to tell "no prefix" from
/// "a prefix I do not recognise" cannot use [`get_si_prefix_multiplier`],
/// which collapses both onto 1.0. Resolving an `ePSet_ProjectedCRS.MapUnit`
/// label is one such caller: answering 1.0 for an unrecognised spelling is a
/// silent wrong answer, where declining to answer defers to the project
/// length unit, which is the documented convention.
/// SI Prefix multipliers as defined in IFC specification.
/// Maps IfcSIPrefix enum values to their numeric multipliers; an absent or
/// unrecognised prefix means the base unit (metres), i.e. 1.0. Callers that
/// must distinguish those two cases want [`try_si_prefix_multiplier`].
/// Known conversion factors for imperial/conversion-based units to meters
/// These are the standard conversions defined in IFC specification
/// 1200/3937 m — the US survey foot, distinct from the international 0.3048.
const US_SURVEY_FOOT_SCALE: f64 = 0.3048006096;
/// The foot/feet spellings a US-survey qualifier may be attached to.
const FOOT_TOKENS: = ;
/// Order-insensitive recogniser for the separated US-survey foot spellings —
/// `foot (US survey)`, `SURVEY FEET (US)`, `US survey foot`. The glued
/// spellings (`USSURVEYFOOT`, `FTUS`, ...) are matched exactly in
/// [`resolve_exact_unit_label`]; once an exporter puts separators in, the
/// word order varies and gluing alone cannot see it.
///
/// Accepts exactly one foot token plus either `US` (or the glued `USSURVEY`)
/// or both `US` and `SURVEY`. A bare `SURVEY FOOT` without `US` is REFUSED:
/// other national survey feet exist (the Indian and Clarke feet are different
/// ratios), so the qualifier alone does not identify the value.
///
/// Twin of `isUsSurveyFootTokens` in
/// packages/parser/src/georef-extractor.ts.
/// Exact lookup of an already-folded (alphanumeric, upper-cased) unit label.
///
/// The accepted set is DERIVED, not hand-written: the conversion-based length
/// units of the shared table, the glued US-survey foot spellings, and the
/// METRE/METER base spellings carrying any member of the `IfcSIPrefix`
/// EXPRESS enumeration.
/// Resolve an `ePSet_ProjectedCRS.MapUnit` free-text label to its metre scale.
///
/// `MapUnit` on the ePSet path is exporter free text, so the label is
/// NORMALISED and then matched EXACTLY — never substring-matched.
///
/// What the normalisation accepts:
/// - any case and any separators: `metres`, `Meters`, `US survey foot`,
/// `MILLI-METRE` (letters and digits are kept, everything else dropped);
/// - the two base spellings `METRE` and `METER`, each prefixed by any of the
/// sixteen `IfcSIPrefix` members (`MILLIMETRE` ... `GIGAMETER`);
/// - the conversion-based length units of the shared table (`FOOT`, `FEET`,
/// `INCH`, `YARD`, `MILE`);
/// - one English plural suffix on any of the above, stripped once and then
/// re-matched exactly: `METRES`, `KILOMETERS`, `MILLIMETRES`, `INCHES`,
/// `MILES`;
/// - the US survey foot, glued (`USSURVEYFOOT`, `USFT`, `FTUS`, ...) or
/// separated in any word order (see [`is_us_survey_foot_tokens`]).
///
/// What it still REFUSES (returns `None`, so the ePSet convention defers to
/// the project length unit downstream):
/// - an absent or blank label;
/// - a label that merely CONTAINS a known unit: `SQUARE METRE` is an area,
/// `BANANAMETRE` is not a unit, and neither may borrow the metre's scale;
/// - abbreviations that are not in the table (`M`, `MM`, `MTR`);
/// - a survey foot with no nationality (`SURVEY FOOT`);
/// - anything else, including a plural whose singular is still unknown.
///
/// The exactness is the point. A `contains("METRE")` test is satisfied by
/// MILLIMETRE, CENTIMETRE, KILOMETRE, DECAMETRE, HECTOMETRE and every other
/// prefixed spelling, so a decametre map unit answered 1.0 — a silent 10x
/// error in the CRS scale; the same shape scaled a projected CRS by 1000x in
/// #3274. Normalising a recognisable spelling onto a table entry is not that:
/// the answer is still one exact table hit, and `DECAMETRES` resolves to 10,
/// not to 1. Where no exact answer exists after normalisation, decline rather
/// than approximate: an absent MapUnit is honest and has a defined meaning, a
/// wrong one relocates the model.
///
/// Twin of `inferMapUnitScaleFromLabel` in
/// packages/parser/src/georef-extractor.ts; both are pinned to the shared
/// vectors in rust/core/tests/fixtures/georef_vectors.json.
pub