# WMDR2 development model v0.3.2 This repository contains the current development version of the simplified WMDR2 JSON representation, converter utilities, JSON Schemas, generated examples, and tests. The format is intended to represent WIGOS station metadata in an OGC Records / GeoJSON-oriented structure while preserving the information that can be recovered from WMDR 1.0 source records without inventing missing metadata. The current model version described here is **WMDR2 v0.3.2**. ## Design principles The v0.3.2 model follows these principles. 1. A WMDR2 station record is a GeoJSON `Feature` whose root `id` is the primary WIGOS Station Identifier. 2. Facility names and identifiers are normalized to one primary value plus explicit additional values. 3. Time-varying properties use a `time` object with an interval; older source-specific temporal field names are not part of the public model. 4. The useful content of source equipment and configuration history is represented directly in `observingConfigurations[]`; no nested location wrapper is emitted. 5. Reusable contacts, instruments, and schedules are registries in the facility record and are referenced from the places where they are used. 6. The converter must preserve recorded information and must not fabricate validity dates, phone country codes, instrument serial numbers, observing methods, or programme affiliations. 7. Source examples that cannot validate without inventing information are explicitly commented in the end-to-end tests rather than being silently “fixed”. ## Record shape A WMDR2 facility record has this top-level shape: ```json { "id": "0-20008-0-THE", "conformsTo": ["http://wigos.wmo.int/spec/wmdr/2/conf/core"], "type": "Feature", "geometry": { "type": "Point", "coordinates": [22.957, 40.631, 60.0] }, "temporalGeometry": { "type": "MovingPoint", "coordinates": [[22.957, 40.631, 60.0]], "dates": ["1982-03-13"], "methods": [[]] }, "time": { "interval": ["1982-03-13", ".."], "resolution": "P1D" }, "properties": { "type": "facility", "title": "Thessaloniki", "observationSeries": [] }, "links": [] } ``` ### Root members | Member | Meaning | | --- | --- | | `id` | Primary WIGOS Station Identifier. It is a bare WSI, for example `0-20008-0-THE`, not a prefixed identifier. | | `conformsTo` | Conformance classes. A core record contains `http://wigos.wmo.int/spec/wmdr/2/conf/core`. | | `type` | Always `Feature`. | | `geometry` | Latest or representative GeoJSON point geometry. Coordinates are GeoJSON order: longitude, latitude, optional elevation. | | `temporalGeometry` | Optional movement or location-history extension. It uses aligned `coordinates`, `dates`, and optional `methods` arrays. | | `time` | Overall validity or temporal extent of the record. | | `properties` | Facility metadata and related WMDR metadata blocks. | | `links` | OGC-style links about the record. | ## Facility properties `properties.type` is always `facility`. The facility object carries the primary description of the station and registries for reusable objects. ```json { "type": "facility", "title": "Flüela permafrost", "additionalTitles": ["Flüelapass"], "additionalIds": ["0-756-1-387493"], "facilityType": "landFixed", "wmoRegion": "southWestPacific", "description": "Example station description.", "keywords": ["GCW", "permafrost"], "contacts": [], "contactAssignments": [], "instruments": [], "observationSeries": [], "schedules": [] } ``` ### Facility names and identifiers The converter applies deterministic primary/additional rules. | Source concept | WMDR2 output | | --- | --- | | First recorded facility identifier | root `id` | | Further recorded WSI values | `properties.additionalIds[]` | | First recorded facility name | `properties.title` | | Further recorded facility names | `properties.additionalTitles[]` | `additionalIds[]` contains only values that match the WSI pattern: ```text ^(0|1|2|3)-([1-9]\d*)-([0-9]+)-([A-Za-z0-9._-]+)$ ``` This rule avoids hiding alternate official station identifiers while keeping the root feature identifier single-valued. ## Time model Temporal metadata is represented with OGC-style `time` objects. ```json { "time": { "interval": ["2020-01-01", ".."], "resolution": "P1D" } } ``` The interval is a two-element array. Each endpoint is either a date-like value (`YYYY`, `YYYY-MM`, `YYYY-MM-DD`) or `..` for open or unknown. `time.resolution`, where present, is an ISO 8601 duration such as `P1D`, `PT1H`, or `PT10M`. The same structure is used for facility histories, territories, programme affiliations, observing configurations, observing procedures, and official status entries. When a source record does not provide a required time anchor for a time-varying object, the converter should not invent one. Such source-derived examples are treated as intentionally non-validating in the end-to-end test policy until the source metadata is corrected. ## Spatial model The root `geometry` is the current or representative facility position. The optional root `temporalGeometry` records location history: ```json { "type": "MovingPoint", "coordinates": [ [7.0, 46.0, 100.0], [7.1, 46.1, 101.0] ], "dates": ["2000-01-01", "2020-01-01"], "methods": [[], ["gps"]] } ``` `coordinates`, `dates`, and `methods` are aligned by array index. Empty method arrays are allowed when the source does not record the position method. ## Contacts Reusable contacts are stored in `properties.contacts[]` using the OGC Records Contact model. Contact roles in WMDR are contextual, so they are represented separately through `contactAssignments[]` at the facility or observation-series level. ```json { "contacts": [ { "identifier": "contact:met-service-example", "organization": "Example Meteorological Service", "emails": [{"value": "ops@example.org"}], "phones": [{"value": "+41123456789"}], "links": [ { "rel": "about", "href": "https://example.org", "type": "text/html" } ] } ], "contactAssignments": [ { "contact": "contact:met-service-example", "roles": ["owner"] } ] } ``` Phone values follow the strict OGC Contact schema used in this repository and must be E.164-style values when emitted. The converter may normalize clearly international numbers, for example `00...` to `+...`, but it must not infer a country code for local-only source values. ## Environment, territory, and programme affiliations Facility-level environmental and administrative histories are arrays of time-bound objects. ```json { "environment": [ { "time": {"interval": ["2020-01-01", ".."]}, "climateZone": "temperate", "surfaceCover": "grass", "surfaceRoughness": "low", "population": [10000, 50000], "perimeter_km": [10, 50] } ], "territory": [ { "time": {"interval": ["2020-01-01", ".."]}, "territory": "CHE" } ], "programAffiliations": [ { "time": {"interval": ["2020-01-01", ".."]}, "program": "GAWregional", "programSpecificFacilityId": "GAW-TEST", "reportingStatus": "operational" } ] } ``` Programme affiliations at facility level are temporal objects because the station relationship with a programme may change. Observation-series programme memberships may be emitted as plain code values where the source only records membership without a temporal association. ## Instruments `properties.instruments[]` is a reusable instrument-type registry, not a list of individual physical instances. It may contain manufacturer, model, observing-method metadata, and vertical range where these are known. ```json { "instruments": [ { "id": "instrument:thermo--49i", "manufacturer": "Thermo", "model": "49i", "observingMethods": ["266"], "verticalRange": { "min": 0.0, "max": 30.0 } } ] } ``` Serial numbers are not part of the instrument catalogue because they identify individual items, not catalogue entries. They are optional instance metadata on `observingConfigurations[]`. An instrument can therefore be documented through the catalogue reference even when the serial number is unknown. In that case, omit `serialNumber`; do not create a catalogue-specific instrument instance. ## Observation series An observation series describes observations of one property or closely related property/feature/geometry combination at the facility. ```json { "id": "observationSeries:0-20008-0-THE--12006", "title": "Air temperature", "observedProperty": "12006", "observedFeature": { "domain": "atmosphere", "domainFeature": "nearSurface", "featureName": "air" }, "observedGeometry": "point", "applicationAreas": ["weather"], "representativeness": "local", "programAffiliations": ["GBON"], "observingConfigurations": [], "observingProcedures": [], "reportingProcedures": [], "officialStatus": [], "contactAssignments": [] } ``` `observedFeature.domain` is required when `observedFeature` is present. `domainFeature` and `featureName` allow more specific description where the source contains it. Code-list values are emitted as JSON strings, even when the source code is numeric-looking. For example, an observed-property code such as `12006` is represented as `"12006"`, not as the JSON number `12006`. `applicationAreas[]` is always an array in WMDR2. The XML-derived WMDR1 source may contain singular `applicationArea` values; the WMDR2 converter collects them into the plural list. ## Observing configurations `observingConfigurations[]` is the time-bound history of how and where an observation series is made. It is the place for observing method, optional operating status, source of observation, instrument reference, optional serial number, exposure, local geometry, reference surface, and vertical distance. ```json { "time": {"interval": ["2020-01-01", ".."]}, "observingMethod": "266", "operatingStatus": "operational", "sourceOfObservation": "automaticReading", "instrument": "instrument:thermo--49i", "serialNumber": "SN-001", "exposure": "good", "geometry": { "type": "Point", "coordinates": [7.0, 46.0, 2.0] }, "referenceSurface": "localGround", "verticalDistanceFromReferenceSurface": { "value": 2.0, "uom": "m" } } ``` An observing configuration requires `observingMethod` and a `time` interval. `operatingStatus` has cardinality 0..1 and is emitted only when recorded. `serialNumber` also has cardinality 0..1 and is emitted only when the instrument instance serial number is known; a missing serial number does not prevent documenting the instrument via `instrument`. Use `{"nilReason": "unknown"}` for an explicitly unknown method. Do not emit discovery keywords or a nested location wrapper here; the relevant location fields are represented directly on the configuration. If the source carries a temporal operating-status history, the converter creates separate observing-configuration entries, each with its own `time.interval` and scalar `operatingStatus`. It must not emit an array-valued `operatingStatus` inside one configuration. ## Observing procedures `observingProcedures[]` contains time-bound procedure history for the observation series. It references reusable schedules from `properties.schedules[]` through `observingSchedules[]`. ```json { "time": {"interval": ["2020-01-01", ".."]}, "strategy": "continuous", "observingSchedules": ["schedule_001"] } ``` The actual validity of the procedure is carried by `time.interval`. The schedule object itself describes a reusable temporal pattern. ## Reporting procedures `reportingProcedures[]` contains the reporting procedure metadata for an observation series. Reporting procedures are not time-bound objects in this version. They reference reusable schedules through `reportingSchedules[]`. ```json { "dataFormat": ["BUFR"], "dataPolicy": "open", "internationalExchange": true, "levelOfData": "level1", "numberOfObservationsInReportingInterval": 6, "referenceDatum": "meanSeaLevel", "referenceTimeSource": ["utc"], "spatialReportingInterval": "point", "strategy": "automatic", "timeliness": "PT30M", "timeStampMeaning": "endOfPeriod", "uom": "K", "reportingSchedules": ["schedule_002"], "contactAssignments": [] } ``` The reporting interval and aggregation interval belong in the associated schedule as `wmo.int:aggregationInterval`, not as temporal properties on the reporting procedure. ## Official status `officialStatus[]` is a time-bound observation-series history. ```json { "time": {"interval": ["2020-01-01", ".."]}, "officialStatus": "primary" } ``` When converting from a boolean official-status source value, the intended mapping is: | Source value | WMDR2 value | | --- | --- | | `true` | `primary` | | `false` | `additional` | | absent | no `officialStatus` entry | ## Schedules Reusable schedules are stored once in `properties.schedules[]` and referenced from observing and reporting procedures. ```json { "uid": "schedule_001", "@type": "Event", "start": "0001-01-01T06:00:00", "duration": "PT12H", "recurrenceRules": [], "recurrenceOverrides": {}, "timeZone": "UTC", "wmo.int:samplingFrequency": "PT10M", "wmo.int:aggregationInterval": "PT1H", "wmo.int:diurnalBaseTime": "06:00:00" } ``` The schedule fields are intentionally JSCalendar-like, with WMO extension members for sampling, aggregation, and diurnal base time. `duration` is reserved for a within-day coverage window. When a source gives a daily window, the converter anchors `start` to the dummy date `0001-01-01T