Skip to content

Commit 1611122

Browse files
committed
new approach: define units in a separate file
1 parent 1371599 commit 1611122

File tree

4 files changed

+120
-68
lines changed

4 files changed

+120
-68
lines changed

README.md

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ data/
148148
defaults.json
149149
deprecated.json
150150
discarded.json
151+
units.json
151152
```
152153

153154
The format for each file is defined in the [`schemas`](schemas) directory.
@@ -748,30 +749,27 @@ Used when `type = measurement`. Defines the unit of measurements that are suppor
748749
"key": "diameter",
749750
"type": "measurement",
750751
"measurement": {
751-
// The dimension being measured. This constrains the permitted units.
752-
// The ID id defined by CLDR.
752+
// The dimension being measured. This constrains the permitted units.
753+
// The ID id defined by CLDR.
753754
"dimension": "length",
754755

755-
"units": {
756-
// The key defines the ID of the of the unit, as defined by CLDR.
757-
// The values define the suffix used in the OSM tag value.
758-
// If there are multiple values in the array (such as "kW", "KW"),
759-
// then the first one is the preferred value, but iD will still
760-
// recognise the alternative/s.
761-
"meter": ["m"],
762-
"centimeter": ["cm"],
763-
"yard": ["yd"],
764-
765-
// mm is the default unit in OSM, so the tag value should have no suffix.
766-
// Therefore, the first array item is blank.
767-
// The second value exists so that iD will recognise tag values with an
768-
// explicit 'mm' suffix.
769-
"millimeter": ["", "mm"]
770-
}
756+
// The corresponding 'usage' from CLDR.
757+
"usage": "default",
758+
759+
// If the field only allows some units, you can list them here
760+
// using CLDR's unit names. If not specified, then, all units from
761+
// this dimension are allowed.
762+
"units": ["meter", "centimeter", "foot-and-inch"],
763+
764+
// Some OSM tags have a default unit, which does not need to be explicitly included in the tag value.
765+
// This field defines how to interpret a unit-less value:
766+
"impliedUnit": "meter"
771767
}
772768
}
773769
```
774770

771+
To convert the unit IDs into the values used by OSM, see [§Units](#Units).
772+
775773
Translations for the [`narrow` and `long` form](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat) of each unit are bundled into iD-tagging-schema's locale files.
776774

777775
### Deprecations
@@ -808,6 +806,26 @@ To update a specific tag to a specific new tag
808806
},
809807
```
810808

809+
## Units
810+
811+
The [`units.json` file](https://github.com/openstreetmap/id-tagging-schema/blob/main/data/units.json) defines the suffix which is used in the OSM tag value for every unit of measurement.
812+
813+
```jsonc
814+
{
815+
"power": {
816+
// The key defines the ID of the of the unit, as defined by CLDR.
817+
// The values define the suffix used in the OSM tag value.
818+
"megawatt": ["MW"],
819+
820+
// If there are multiple values in the array, then the first one
821+
// is the preferred value, but iD will still recognise the alternative/s.
822+
"kilowatt": ["kW", "KW"],
823+
824+
// `horsepower` is not included, therefore it can't be used by any fields.
825+
},
826+
}
827+
```
828+
811829
## Contributing
812830

813831
iD's [code of conduct](https://github.com/openstreetmap/iD/blob/release/CODE_OF_CONDUCT.md) and

lib/build.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const categorySchema = require('../schemas/preset_category.json');
1919
const defaultsSchema = require('../schemas/preset_defaults.json');
2020
const deprecatedSchema = require('../schemas/deprecated.json');
2121
const discardedSchema = require('../schemas/discarded.json');
22+
const unitsSchema = require('../schemas/generated/units.json');
2223

2324
/** @import { TranslationOptions } from "./translations.js" */
2425

@@ -142,6 +143,11 @@ function processData(options, type) {
142143
validateSchema(dataDir + '/discarded.json', discarded, discardedSchema);
143144
}
144145

146+
const units = read(dataDir + '/units.json');
147+
if (units) {
148+
validateSchema(dataDir + '/units.json', units, unitsSchema);
149+
}
150+
145151
let categories = generateCategories(dataDir, tstrings);
146152
if (options.processCategories) options.processCategories(categories);
147153

@@ -204,6 +210,7 @@ function processData(options, type) {
204210
if (defaults) fs.writeFileSync(distDir + '/preset_defaults.json', JSON.stringify(defaults, null, 4));
205211
if (deprecated) fs.writeFileSync(distDir + '/deprecated.json', JSON.stringify(deprecated, null, 4));
206212
if (discarded) fs.writeFileSync(distDir + '/discarded.json', JSON.stringify(discarded, null, 4));
213+
if (units) fs.writeFileSync(distDir + '/units.json', JSON.stringify(units, null, 4));
207214

208215
expandTStrings(tstrings);
209216
let translationsForJson = {};
@@ -221,6 +228,7 @@ function processData(options, type) {
221228
minifyJSON(distDir + '/preset_defaults.json', distDir + '/preset_defaults.min.json'),
222229
minifyJSON(distDir + '/deprecated.json', distDir + '/deprecated.min.json'),
223230
minifyJSON(distDir + '/discarded.json', distDir + '/discarded.min.json'),
231+
minifyJSON(distDir + '/units.json', distDir + '/units.min.json'),
224232
minifyJSON(distDir + '/translations/' + sourceLocale + '.json', distDir + '/translations/' + sourceLocale + '.min.json'),
225233
generateTypeDefs(distDir),
226234
];

schemas/field.json

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -378,24 +378,25 @@
378378
"description": "The corresponding 'usage' from CLDR"
379379
},
380380
"units": {
381-
"type": "object",
382-
"description": "Defines the permitted units. The key is the ID used by CLDR (see https://cdn.jsdelivr.net/npm/cldr-core/supplemental/unitPreferenceData.json). The value is the value used in the OSM tag. If there are multiple values, the first one will be preferred. Use an empty string if the unit is not included in the OSM tag.",
383-
"additionalProperties": {
384-
"type": "array",
385-
"items": {
386-
"type": ["string", "null"]
387-
},
388-
"minItems": 1
389-
},
390-
"minProperties": 1
381+
"type": "array",
382+
"minItems": 1,
383+
"uniqueItems": true,
384+
"description": "Optional, if only some units are allowed for this tag, then list the permitted units here, using the unit IDs from CLDR (see https://cdn.jsdelivr.net/npm/cldr-core/supplemental/unitPreferenceData.json).",
385+
"items": {
386+
"type": "string"
387+
}
388+
},
389+
"impliedUnit": {
390+
"type": "string",
391+
"description": "Some OSM tags have a default unit, which does not need to be explicitly included in the tag value. This field defines how to interpret a unit-less value."
391392
}
392393
},
393394
"allOf": [
394395
{ "$ref": "./generated/usage.json" },
395-
{ "$ref": "./generated/units.json" }
396+
{ "$ref": "./generated/unit-types.json" }
396397
],
397-
"additionalItems": false,
398-
"required": ["dimension", "usage", "units"]
398+
"additionalProperties": false,
399+
"required": ["dimension", "usage"]
399400
}
400401
},
401402
"additionalProperties": false,

scripts/build-schema.js

Lines changed: 62 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -37,61 +37,86 @@ const usage = {
3737
}),
3838
};
3939

40-
const units = {
40+
const unitTypes = {
4141
$schema: 'http://json-schema.org/draft-07/schema#',
4242
$id: 'https://cdn.jsdelivr.net/npm/@ideditor/schema-builder/schemas/generated/units.json',
4343

44-
allOf: dimension.enum.map((dimension) => {
45-
const defaults =
46-
unitPreference.supplemental.unitPreferenceData[dimension] || {};
44+
$defs: Object.fromEntries(
45+
dimension.enum.map((dimension) => {
46+
const defaults =
47+
unitPreference.supplemental.unitPreferenceData[dimension] || {};
4748

48-
/** @type {import('json-schema').JSONSchema4['properties']} */
49-
const properties = {};
49+
/** @type {Set<string>} */
50+
const units = new Set(
51+
// units.json does not include 'Mixed Units', so we need to add some of
52+
// the 'Mixed Units' (only the ones which are used):
53+
Object.values(defaults)
54+
.flatMap(Object.values)
55+
.flat()
56+
.map((item) => item.unit),
57+
);
5058

51-
for (const key in unitTranslations.main.en.units.long) {
52-
if (key.startsWith(`${dimension}-`)) {
53-
const unit = key.split('-').slice(1).join('-');
54-
properties[unit] = {
55-
type: 'array',
56-
items: { type: 'string' },
57-
minItems: 1,
58-
};
59+
// also add all standard units:
60+
for (const key in unitTranslations.main.en.units.long) {
61+
if (key.startsWith(`${dimension}-`)) {
62+
const unit = key.split('-').slice(1).join('-');
63+
units.add(unit);
64+
}
5965
}
60-
}
61-
62-
// units.json does not include 'Mixed Units', so we need to add some of
63-
// the 'Mixed Units' (only the ones which are used).
64-
const mixedUnits = new Set(
65-
Object.values(defaults)
66-
.flatMap(Object.values)
67-
.flat()
68-
.map((item) => item.unit),
69-
);
7066

71-
for (const unit of mixedUnits) {
72-
properties[unit] ||= {
73-
type: 'array',
74-
items: { type: 'null' },
75-
minItems: 1,
76-
maxItems: 1,
77-
};
78-
}
67+
return [dimension, { enum: [...units] }];
68+
}),
69+
),
7970

71+
allOf: dimension.enum.map((dimension) => {
8072
return {
8173
if: { properties: { dimension: { const: dimension } } },
8274
then: {
8375
properties: {
84-
units: {
85-
additionalProperties: false,
86-
properties,
87-
},
76+
units: { items: { $ref: `#/$defs/${dimension}` } },
77+
impliedUnit: { $ref: `#/$defs/${dimension}` },
8878
},
8979
},
9080
};
9181
}),
9282
};
9383

94-
const files = { dimension, usage, units };
84+
const units = {
85+
$schema: 'http://json-schema.org/draft-07/schema#',
86+
$id: 'https://cdn.jsdelivr.net/npm/@ideditor/schema-builder/schemas/generated/units.json',
87+
title: 'Units of measurement',
88+
description:
89+
'Defines the suffix used in the OSM tag value for every unit of measurement. If there are multiple values, the first one will be preferred.',
90+
type: 'object',
91+
properties: Object.fromEntries(
92+
Object.entries(unitTypes.$defs).map(([dimension, value]) => {
93+
return [
94+
dimension,
95+
{
96+
type: 'object',
97+
properties: Object.fromEntries(
98+
value.enum.map((unit) => {
99+
return [
100+
unit,
101+
{
102+
type: 'array',
103+
minItems: 1,
104+
uniqueItems: true,
105+
items: { type: 'string' },
106+
},
107+
];
108+
}),
109+
),
110+
additionalProperties: false,
111+
minProperties: 1,
112+
},
113+
];
114+
}),
115+
),
116+
additionalProperties: false,
117+
};
118+
119+
const files = { dimension, usage, 'unit-types': unitTypes, units };
95120

96121
const generatedFolder = join(import.meta.dirname, '../schemas/generated');
97122
await fs.mkdir(generatedFolder, { recursive: true });

0 commit comments

Comments
 (0)