datex2-xml2json 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. datex2_xml2json/__init__.py +0 -0
  2. datex2_xml2json/json_validator.py +36 -0
  3. datex2_xml2json/merge.py +39 -0
  4. datex2_xml2json/schema/Baustellen-4-00-00.sch +7 -0
  5. datex2_xml2json/schema/DATEXIISchema_2_2_0.xsd +4707 -0
  6. datex2_xml2json/schema/DATEXIISchema_2_2_3.xsd +6783 -0
  7. datex2_xml2json/schema/DATEXII_3.7_Profile/.DS_Store +0 -0
  8. datex2_xml2json/schema/DATEXII_3.7_Profile/DATEXII_3_AfirEnergyInfrastructure.json +2455 -0
  9. datex2_xml2json/schema/DATEXII_3.7_Profile/DATEXII_3_AfirFacilities.json +2809 -0
  10. datex2_xml2json/schema/DATEXII_3.7_Profile/DATEXII_3_Common.json +3651 -0
  11. datex2_xml2json/schema/DATEXII_3.7_Profile/DATEXII_3_CommonExtension.json +919 -0
  12. datex2_xml2json/schema/DATEXII_3.7_Profile/DATEXII_3_ControlledZone.json +1070 -0
  13. datex2_xml2json/schema/DATEXII_3.7_Profile/DATEXII_3_D2Payload.json +11 -0
  14. datex2_xml2json/schema/DATEXII_3.7_Profile/DATEXII_3_EnergyInfrastructure.json +1924 -0
  15. datex2_xml2json/schema/DATEXII_3.7_Profile/DATEXII_3_Facilities.json +2858 -0
  16. datex2_xml2json/schema/DATEXII_3.7_Profile/DATEXII_3_FaultAndStatus.json +1415 -0
  17. datex2_xml2json/schema/DATEXII_3.7_Profile/DATEXII_3_LocationExtension.json +186 -0
  18. datex2_xml2json/schema/DATEXII_3.7_Profile/DATEXII_3_LocationReferencing.json +4847 -0
  19. datex2_xml2json/schema/DATEXII_3.7_Profile/DATEXII_3_Parking.json +5314 -0
  20. datex2_xml2json/schema/DATEXII_3.7_Profile/DATEXII_3_ReroutingManagementEnhanced.json +1160 -0
  21. datex2_xml2json/schema/DATEXII_3.7_Profile/DATEXII_3_RoadTrafficData.json +1938 -0
  22. datex2_xml2json/schema/DATEXII_3.7_Profile/DATEXII_3_Situation.json +8040 -0
  23. datex2_xml2json/schema/DATEXII_3.7_Profile/DATEXII_3_TrafficManagementPlan.json +2358 -0
  24. datex2_xml2json/schema/DATEXII_3.7_Profile/DATEXII_3_TrafficRegulation.json +3377 -0
  25. datex2_xml2json/schema/DATEXII_3.7_Profile/DATEXII_3_UrbanExtensions.json +194 -0
  26. datex2_xml2json/schema/DATEXII_3.7_Profile/DATEXII_3_Vms.json +2544 -0
  27. datex2_xml2json/schema/DATEXII_3.7_Profile/xml/.DS_Store +0 -0
  28. datex2_xml2json/schema/DATEXII_3.7_Profile/xml/DATEXII_3_AfirEnergyInfrastructure.xsd +2102 -0
  29. datex2_xml2json/schema/DATEXII_3.7_Profile/xml/DATEXII_3_AfirFacilities.xsd +2060 -0
  30. datex2_xml2json/schema/DATEXII_3.7_Profile/xml/DATEXII_3_Common.xsd +4152 -0
  31. datex2_xml2json/schema/DATEXII_3.7_Profile/xml/DATEXII_3_CommonExtension.xsd +1102 -0
  32. datex2_xml2json/schema/DATEXII_3.7_Profile/xml/DATEXII_3_ControlledZone.xsd +601 -0
  33. datex2_xml2json/schema/DATEXII_3.7_Profile/xml/DATEXII_3_D2Payload.xsd +280 -0
  34. datex2_xml2json/schema/DATEXII_3.7_Profile/xml/DATEXII_3_EnergyInfrastructure.xsd +1761 -0
  35. datex2_xml2json/schema/DATEXII_3.7_Profile/xml/DATEXII_3_Facilities.xsd +2037 -0
  36. datex2_xml2json/schema/DATEXII_3.7_Profile/xml/DATEXII_3_FaultAndStatus.xsd +1076 -0
  37. datex2_xml2json/schema/DATEXII_3.7_Profile/xml/DATEXII_3_LocationExtension.xsd +196 -0
  38. datex2_xml2json/schema/DATEXII_3.7_Profile/xml/DATEXII_3_LocationReferencing.xsd +4132 -0
  39. datex2_xml2json/schema/DATEXII_3.7_Profile/xml/DATEXII_3_Parking.xsd +4025 -0
  40. datex2_xml2json/schema/DATEXII_3.7_Profile/xml/DATEXII_3_ReroutingManagementEnhanced.xsd +997 -0
  41. datex2_xml2json/schema/DATEXII_3.7_Profile/xml/DATEXII_3_RoadTrafficData.xsd +1273 -0
  42. datex2_xml2json/schema/DATEXII_3.7_Profile/xml/DATEXII_3_Situation.xsd +5777 -0
  43. datex2_xml2json/schema/DATEXII_3.7_Profile/xml/DATEXII_3_TrafficManagementPlan.xsd +1184 -0
  44. datex2_xml2json/schema/DATEXII_3.7_Profile/xml/DATEXII_3_TrafficRegulation.xsd +2611 -0
  45. datex2_xml2json/schema/DATEXII_3.7_Profile/xml/DATEXII_3_UrbanExtensions.xsd +148 -0
  46. datex2_xml2json/schema/DATEXII_3.7_Profile/xml/DATEXII_3_Vms.xsd +2290 -0
  47. datex2_xml2json/schema/MDM_BaustellenInfoDigitalRoadworksProfile_04-03-00.xsd +2401 -0
  48. datex2_xml2json/schema/MDM_Datenmodell_Baustellen_04-00-00.xsd +3232 -0
  49. datex2_xml2json/schema/cifs_schema.json +161 -0
  50. datex2_xml2json/transform/.DS_Store +0 -0
  51. datex2_xml2json/transform/__init__.py +13 -0
  52. datex2_xml2json/transform/base.py +79 -0
  53. datex2_xml2json/transform/cifs_transformer.py +275 -0
  54. datex2_xml2json/transform/geojson_geometry_transformer.py +159 -0
  55. datex2_xml2json/transform/geoparquet_transformer.py +160 -0
  56. datex2_xml2json/transform/jsonata_transformer.py +100 -0
  57. datex2_xml2json/transform/roads_regio_transformer.py +88 -0
  58. datex2_xml2json/transform/source_setter.py +42 -0
  59. datex2_xml2json/transform/transformation_rules.py +526 -0
  60. datex2_xml2json/transformer.py +197 -0
  61. datex2_xml2json/util.py +141 -0
  62. datex2_xml2json-0.0.1.dist-info/METADATA +176 -0
  63. datex2_xml2json-0.0.1.dist-info/RECORD +65 -0
  64. datex2_xml2json-0.0.1.dist-info/WHEEL +4 -0
  65. datex2_xml2json-0.0.1.dist-info/licenses/LICENSE.md +179 -0
@@ -0,0 +1,526 @@
1
+ import logging
2
+ from dataclasses import dataclass
3
+ from typing import Callable
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+
8
+ @dataclass
9
+ class TransformationRules:
10
+ renamings: dict[str, str]
11
+ value_transforms: dict[str, Callable]
12
+ keys_to_drop: dict[str, list[str]]
13
+ type_mappings: dict[str, str]
14
+ keys_to_pull_up: dict[str, None | list[str]]
15
+
16
+
17
+ def push_down_vms_configuration(vms: dict) -> dict:
18
+ vmsConfiguration = {}
19
+ if 'numberOfPictogramDisplayAreas' in vms:
20
+ vmsConfiguration['numberOfDisplayAreas'] = vms.pop('numberOfPictogramDisplayAreas')
21
+ if 'vmsPictogramDisplayCharacteristics' in vms:
22
+ displays = vms.pop('vmsPictogramDisplayCharacteristics')
23
+ vmsConfiguration['displayArea'] = [
24
+ {
25
+ 'displayArea': {'vmsPictogramDisplayArea': displayArea.get('vmsPictogramDisplayCharacteristics')},
26
+ 'displayAreaIndex': displayArea.pop('displayAreaIndex'),
27
+ }
28
+ for displayArea in displays
29
+ if 'vmsPictogramDisplayCharacteristics' in displayArea
30
+ ]
31
+ if len(vmsConfiguration) > 0:
32
+ vms['vmsConfiguration'] = vmsConfiguration
33
+ return vms
34
+
35
+
36
+ INFRASTRUCTURE_DESCRIPTOR_VALUES = {
37
+ 'atMotorwayInterchange',
38
+ 'atRestArea',
39
+ 'atServiceArea',
40
+ 'atTollPlaza',
41
+ 'atTunnelEntryOrExit',
42
+ 'inGallery',
43
+ 'inTunnel',
44
+ 'onBridge',
45
+ 'onConnector',
46
+ 'onElevatedSection',
47
+ 'onFlyover',
48
+ 'onIceRoad',
49
+ 'onLevelCrossing',
50
+ 'onLinkRoad',
51
+ 'onRoundabout',
52
+ 'onTheRoadway',
53
+ 'onUndergroundSection',
54
+ 'onUnderpass',
55
+ 'withinJunction',
56
+ }
57
+ POSITION_ON_CARRIAGEWAY_VALUES = {
58
+ 'onTheLeft',
59
+ 'onTheRight',
60
+ 'inTheCentre',
61
+ }
62
+ GEOGRAPHIC_CHARACATERISTIC_VALUES = {
63
+ 'aroundABendInRoad',
64
+ 'onBorder',
65
+ 'onPass',
66
+ 'overCrestOfHill',
67
+ }
68
+
69
+
70
+ def transform_supplementaryPositionalDescription(v: dict):
71
+ # locationDescriptior needs to be split in multiple properties
72
+ if 'locationDescriptor' in v:
73
+ locationDescriptors = v.pop('locationDescriptor')
74
+ for ld_value in locationDescriptors:
75
+ if ld_value in INFRASTRUCTURE_DESCRIPTOR_VALUES:
76
+ if 'infrastructureDescriptor' in v:
77
+ logger.warning(
78
+ 'Conflicting values for infrastructureDescriptor (%s vs %s), keeping former.',
79
+ v['infrastructureDescriptor'],
80
+ ld_value,
81
+ )
82
+ continue
83
+ v['infrastructureDescriptor'] = {'value': ld_value}
84
+ if ld_value in POSITION_ON_CARRIAGEWAY_VALUES:
85
+ if 'positionOnCarriageway' in v:
86
+ logger.warning(
87
+ 'Conflicting values for positionOnCarriageway (%s vs %s), keeping former.',
88
+ v['positionOnCarriageway'],
89
+ ld_value,
90
+ )
91
+ continue
92
+ v['positionOnCarriageway'] = {'value': ld_value}
93
+ if ld_value in GEOGRAPHIC_CHARACATERISTIC_VALUES:
94
+ if 'geographicDescriptor' in v:
95
+ logger.warning(
96
+ 'Conflicting values for geographicDescriptor (%s vs %s), keeping former.',
97
+ v['geographicDescriptor'],
98
+ ld_value,
99
+ )
100
+ continue
101
+ v['geographicDescriptor'] = {'value': ld_value}
102
+
103
+ if 'carriageway' in v:
104
+ for c in v['carriageway']:
105
+ lengthAffected = c.pop('lengthAffected', None)
106
+ if lengthAffected is not None:
107
+ return v | {'lengthAffected': float(lengthAffected)}
108
+
109
+ return v
110
+
111
+
112
+ def wrap_lroffset(lr_dict: dict):
113
+ new_dict = {}
114
+ for k in lr_dict:
115
+ if k == 'openlrPositiveOffset':
116
+ new_dict['openlrOffsets'] = {k: lr_dict[k]}
117
+ else:
118
+ new_dict[k] = lr_dict[k]
119
+ return new_dict
120
+
121
+
122
+ def transform_alertc(v: dict, shouldWrapWithValue: bool):
123
+ """
124
+ In v2, alertCDirection did not distinguish between encoding and affected direction.
125
+ We assume, as apparently NDW mandated explictly, that for encoding matches affected direction and,
126
+ in case of both or unknown affected direction, encoding is positive.
127
+ """
128
+ currentAlertCDirectionCoded = v['alertCDirectionCoded']
129
+ if 'alertCDirectionCoded' in v:
130
+ v['alertCAffectedDirection'] = 'aligned' if currentAlertCDirectionCoded in ['positive', 'negative'] else v['alertCDirectionCoded']
131
+ v['alertCDirectionCoded'] = 'positive' if currentAlertCDirectionCoded in ['both', 'unknown'] else v['alertCDirectionCoded']
132
+ if shouldWrapWithValue:
133
+ v['alertCAffectedDirection'] = {'value': v['alertCAffectedDirection']}
134
+ v['alertCDirectionCoded'] = {'value': v['alertCDirectionCoded']}
135
+
136
+ return v
137
+
138
+
139
+ DEFAULT_LANGUAGE = 'de'
140
+
141
+
142
+ class TransformationRuleFactory:
143
+ ACCIDENT_TYPE_MAPPINGS = {
144
+ 'seriousAccident': 'seriousInjuryOrFatalAccident',
145
+ 'multivehicleAccident': 'multipleVehicleAccident',
146
+ 'earlierAccident': 'accident', # TODO: perhaps this should be another situationRecord?
147
+ }
148
+
149
+ CAUSE_TYPE_MAPPINGS = {
150
+ 'shearWeightOfTraffic': 'other',
151
+ 'earlierAccident': 'earlierIncident',
152
+ 'congestion': 'abnormalTraffic',
153
+ }
154
+
155
+ VEHICLE_OBSTRUCTION_TYPE_MAPPINGS = {
156
+ 'brokenDownBus': 'brokenDownVehicle',
157
+ 'brokenDownHeavyLorry': 'brokenDownVehicle',
158
+ 'slowMovingMaintenanceVehicle': 'slowVehicle',
159
+ 'trackLayingVehicle': 'trackedVehicle',
160
+ 'vehicleStuckUnderBridge': 'vehicleStuck',
161
+ 'saltingOrGrittingVehicleInUse': 'other',
162
+ 'snowplough': 'other',
163
+ }
164
+
165
+ # see https://docs.ndw.nu/en/dataformaten/datex2-v2.3/conversie-naar-v3/#generalobstruction-maps-to-generalobstruction
166
+ OBSTRUCTION_TYPE_MAPPINGS = {
167
+ 'recklessDriver': 'other',
168
+ 'houseFire': 'other',
169
+ 'highSpeedChase': 'other',
170
+ }
171
+
172
+ CAR_PARK_STATUS_TO_SERVICE_TYPE_MAPPINGS = {
173
+ 'allCarParksFull': 'noParkingAvailability',
174
+ 'carParkFull': 'noParkingAvailability',
175
+ 'carParkClosed': 'noParkingAvailability',
176
+ 'noMoreParkingSpacesAvailable': 'noParkingAvailability',
177
+ 'serviceAreaFull': 'serviceAreaOvercrowdedDriveToAnotherServiceArea',
178
+ }
179
+
180
+ # Type mappings convert { ..., "@xsi:type": "<key>" } to { "<value>": { ... }}
181
+ datex_xml_2_json_xsitype_mappings = {
182
+ 'AbnormalTraffic': 'sitAbnormalTraffic',
183
+ 'Accident': 'sitAccident',
184
+ 'AnimalPresenceObstruction': 'sitAnimalPresenceObstruction',
185
+ 'AuthorityOperation': 'sitAuthorityOperation',
186
+ 'Conditions': 'sitConditions',
187
+ 'ConstructionWorks': 'sitConstructionWorks',
188
+ 'DisturbanceActivity': 'sitDisturbanceActivity',
189
+ 'EnvironmentalObstruction': 'sitEnvironmentalObstruction',
190
+ 'EquipmentOrSystemFault': 'sitEquipmentOrSystemFault',
191
+ 'GeneralObstruction': 'sitGeneralObstruction',
192
+ 'GeneralInstructionOrMessageToRoadUsers': 'sitGeneralInstructionOrMessageToRoadUsers',
193
+ 'GeneralNetworkManagement': 'sitGeneralNetworkManagement',
194
+ 'GenericSituationRecord': 'sitGenericSituationRecord',
195
+ 'InfrastructureDamageObstruction': 'sitInfrastructureDamageObstruction',
196
+ 'MaintenanceWorks': 'sitMaintenanceWorks',
197
+ 'NonWeatherRelatedRoadConditions': 'sitNonWeatherRelatedRoadConditions',
198
+ 'PoorEnvironmentConditions': 'sitPoorEnvironmentConditions',
199
+ 'PublicEvent': 'sitPublicEvent',
200
+ 'RoadOperatorServiceDisruption': 'sitRoadOperatorServiceDisruption',
201
+ 'RoadOrCarriagewayOrLaneManagement': 'sitRoadOrCarriagewayOrLaneManagement',
202
+ 'RoadsideAssistance': 'sitRoadsideAssistance',
203
+ 'ReroutingManagement': 'sitReroutingManagement',
204
+ 'WeatherRelatedRoadConditions': 'sitWeatherRelatedRoadConditions',
205
+ 'ServiceDisruption': 'sitServiceDisruption',
206
+ # see for CarParks https://docs.ndw.nu/en/dataformaten/datex2-v2.3/conversie-naar-v3/#carparks-maps-to-servicedisruption
207
+ 'CarParks': 'sitServiceDisruption',
208
+ # TODO: need to set serviceDisruptionType
209
+ 'RoadsideServiceDisruption': 'sitServiceDisruption',
210
+ 'SpeedManagement': 'sitSpeedManagement',
211
+ 'TransitInformation': 'sitTransitInformation',
212
+ 'VehicleObstruction': 'sitVehicleObstruction',
213
+ 'WinterDrivingManagement': 'sitWinterDrivingManagement',
214
+ 'Linear': 'locSingleRoadLinearLocation',
215
+ 'NonOrderedLocationGroupByList': 'locLocationGroupByList',
216
+ 'SituationPublication': 'sitSituationPublication',
217
+ 'TpegNonJunctionPoint': 'locTpegNonJunctionPoint',
218
+ 'AreaDestination': 'locAreaDestination',
219
+ 'AlertCMethod4Linear': 'locAlertCMethod4Linear',
220
+ 'Point': 'locPointLocation',
221
+ 'Area': 'locAreaLocation',
222
+ 'ItineraryByIndexedLocations': 'locItineraryByIndexedLocations',
223
+ 'TpegGeometricArea': 'locTpegGeometricArea',
224
+ 'TpegNamedOnlyArea': 'locTpegNamedOnlyArea',
225
+ 'OpenlrPolygonLocationReference': 'locOpenlrPolygonLocationReference',
226
+ 'DistanceFromLinearElementReferent': 'locDistanceFromLinearElementReferent',
227
+ # VMS
228
+ 'VmsTablePublication': 'vmsVmsTablePublication',
229
+ 'TpegSimplePoint': 'locTpegSimplePoint',
230
+ 'AlertCMethod4Point': 'locAlertCMethod4Point',
231
+ # Parking
232
+ 'ParkingStatusPublication': 'prkParkingStatusPublication',
233
+ 'SpaceStatus': 'prkSpaceStatus',
234
+ 'PlaceStatus': 'prkPlaceStatus',
235
+ }
236
+
237
+ def __init__(self):
238
+ pass
239
+
240
+ def xmlv2_to_json(self):
241
+ renamings = {
242
+ # xml2json artifact
243
+ '$': 'value',
244
+ # Assumed DATEXII 2.2->DATEX3.7 naming changes
245
+ 'd2LogicalModel': 'payload',
246
+ 'affectedCarriagewayAndLanes': 'carriageway',
247
+ 'groupOfLocations': 'locationReference',
248
+ 'locationForDisplay': 'coordinatesForDisplay',
249
+ 'openlrCoordinate': 'openlrCoordinates',
250
+ 'area': 'areaLocation',
251
+ 'openlrExtendedLinear': 'openlrLinear',
252
+ 'openlrLowestFRCToNextLRPoint': 'openlrLowestFrcToNextLRPoint',
253
+ 'roadworksDuration': 'roadworksDurationClassification',
254
+ 'roadsideServiceDisruptionType': 'serviceDisruptionType',
255
+ 'carParkStatus': 'serviceDisruptionType',
256
+ # VMS
257
+ 'vmsUnitTable': 'vmsControllerTable',
258
+ 'vmsUnitRecord': 'vmsController',
259
+ 'vmsUnitIdentifier': 'externalIdentifier',
260
+ 'vmsUnitRecordExtension': 'vmsVmsControllerExtensionG',
261
+ 'vmsRecord': 'vms',
262
+ 'vmsRecordExtension': 'vmsVmsExtensionG',
263
+ 'vmsPhysicalMounting': 'physicalSupport',
264
+ 'vmsDescription': 'description',
265
+ 'pictogramDisplayAreaIndex': 'displayAreaIndex',
266
+ 'vmsUnitTableIdentification': 'vmsControllerTableIdentification',
267
+ 'vmsPictogramDisplayCharacteristicsExtension': 'vmsPictogramDisplayAreaExtensionG',
268
+ #
269
+ '@modelBaseVersion': 'modelBaseVersion',
270
+ '@id': 'id',
271
+ '@index': 'index',
272
+ '@lang': 'lang',
273
+ '@version': 'version',
274
+ '@targetClass': 'targetClass',
275
+ '@xsi:type': 'type',
276
+ '@extensionName': 'extensionNameG',
277
+ '@extensionVersion': 'extensionVersionG',
278
+ }
279
+
280
+ def add_def_lang_if_missing(v):
281
+ return v if isinstance(v, dict) else {'lang': DEFAULT_LANGUAGE, 'value': v}
282
+
283
+ # TODO move value wrapping down to datex transform, here we should use them unwrapped
284
+ value_transforms = {
285
+ # type transformations
286
+ 'offsetDistance': lambda v: int(v) if not isinstance(v, dict) else v,
287
+ # value transformations
288
+ # v3 removed some v2 enum values which need to be mapped
289
+ 'commentType': lambda v: { # FIXME move value mapping down
290
+ 'value': 'other' if v == 'locationDescriptor' else v
291
+ },
292
+ 'vehicleObstructionType': lambda v: self.VEHICLE_OBSTRUCTION_TYPE_MAPPINGS.get(v, v),
293
+ 'obstructionType': lambda v: self.OBSTRUCTION_TYPE_MAPPINGS.get(v, v),
294
+ 'causeType': lambda v: self.CAUSE_TYPE_MAPPINGS.get(v, v),
295
+ 'openlrFunctionalRoadClass': lambda v: {'value': v.lower()},
296
+ 'openlrLowestFRCToNextLRPoint': lambda v: {'value': v.lower()},
297
+ # lang: de-DE -> de as JSON schema allows only 2 chars, bemas uses uppercase
298
+ '@lang': lambda v: v[0:2].lower(),
299
+ # comment/values is expected to be a dict with value/lang. For now, we assume values only includes multi ling string
300
+ 'values': lambda v: [add_def_lang_if_missing(value) for value in v['value']],
301
+ 'alertCDirection': lambda v: transform_alertc(v, False),
302
+ 'alertCPoint': lambda v: [v] if isinstance(v, dict) else v,
303
+ 'grossWeightCharacteristic': lambda v: v | {'typeOfWeight': {'value': 'actual'}},
304
+ # v3 has no carParkStatus any longer, we map to serviceDisruption restAreaClosed
305
+ 'carParkStatus': lambda v: [{'value': self.CAR_PARK_STATUS_TO_SERVICE_TYPE_MAPPINGS.get(v, v)}],
306
+ 'recurringDayWeekMonthPeriod': lambda v: {'comDayWeekMonth': v} if v is not None else None,
307
+ 'supplementaryPositionalDescription': transform_supplementaryPositionalDescription,
308
+ 'openlrPoiWithAccessPoint': wrap_lroffset,
309
+ 'openlrPointAlongLine': wrap_lroffset,
310
+ 'managedCause': lambda v: {'objectReference': v},
311
+ # VMS
312
+ 'vmsType': lambda v: {'value': 'fullMatrixSign' if v == 'matrixSign' else v},
313
+ 'vmsRecord': push_down_vms_configuration,
314
+ # 'roadMaintenanceType': lambda v: v if isinstance(v, list) else [wrap_with_value(v)], # TODO conversion issue?
315
+ 'alertCLinear': lambda v: v if isinstance(v, list) else [v], # TODO conversion issue?
316
+ 'pointAlongLinearElement': lambda v: v if isinstance(v, list) else [v],
317
+ 'linearWithinLinearElement': lambda v: v if isinstance(v, list) else [v],
318
+ 'linearElement': lambda v: {'locLinearElement': v},
319
+ 'gmlLineString': lambda v: {
320
+ 'posList': v.get('posList', v.get('coordinates', '').replace(',', ' ')),
321
+ 'srsName': v.get('srsName'),
322
+ },
323
+ }
324
+
325
+ keys_to_drop = {
326
+ 'urgency': [], # Apparently, support for urgency has been dropped?
327
+ '@xmlns:xsi': [],
328
+ '@version': ['situation'],
329
+ 'type': ['recurringTimePeriodOfDay', 'cause'],
330
+ 'management': ['situationRecord'], # apparently support for lifecyclemanagement notifications has been dropped?
331
+ 'openlrGeoCoordinate': ['openlrPointLocationReference'],
332
+ '@xmlns': [],
333
+ 'exchange': [],
334
+ 'originalNumberOfLanes': ['impact'], # TODO, check, if we shold copy this value to carriageways
335
+ # v3 dropped CarParks Situation. We map to serviceDisruption which seems not to have an equivalent to carParkIdentity:
336
+ 'carParkIdentity': [],
337
+ }
338
+
339
+ geojson_drops = {'situationExtension': []}
340
+
341
+ # keys_to_pull_up convert { ..., '<value>': { <nested properties> } } in { ..., <nested properties>}
342
+ keys_to_pull_up = {
343
+ 'linearExtension': None,
344
+ 'linearExtended': None,
345
+ 'linearLineStringExtension': None,
346
+ 'pointExtension': None,
347
+ 'openlrExtendedPoint': None,
348
+ 'areaExtension': None,
349
+ 'openlrExtendedArea': None,
350
+ 'payloadPublication': None,
351
+ 'locGmlLineString': None,
352
+ 'impact': ['trafficConstrictionType'],
353
+ }
354
+
355
+ return TransformationRules(
356
+ renamings,
357
+ value_transforms,
358
+ keys_to_drop | geojson_drops,
359
+ {},
360
+ keys_to_pull_up,
361
+ )
362
+
363
+ def xmlv3_to_json3_7(self):
364
+ datex_json_renamings = {
365
+ # Assumed DATEXII JSON Schema Artifacts
366
+ '@modelBaseVersion': 'modelBaseVersionG',
367
+ '@id': 'idG',
368
+ '@lang': 'lang',
369
+ '@version': 'versionG',
370
+ '@targetClass': 'targetClass',
371
+ '@operatingPatternIndex': 'operatingPatternIndex',
372
+ '@extensionName': 'extensionNameG',
373
+ '@extensionVersion': 'extensionVersionG',
374
+ }
375
+
376
+ def wrap_with_value(v):
377
+ return {'value': v}
378
+
379
+ value_transforms = {
380
+ # Value wrapping for parking ...
381
+ 'status': wrap_with_value,
382
+ 'operationStatus': wrap_with_value,
383
+ 'informationStatus': wrap_with_value,
384
+ 'confidentiality': wrap_with_value,
385
+ }
386
+ keys_to_drop = {
387
+ '@xsi:schemaLocation': [],
388
+ '@xmlns': [],
389
+ '@xmlns:d2': [],
390
+ '@xmlns:loc': [],
391
+ '@xmlns:com': [],
392
+ '@xmlns:fac': [],
393
+ '@xmlns:xsd': [],
394
+ '@xmlns:xsi': [],
395
+ '@xsi:type': [],
396
+ }
397
+ keys_to_pull_up = {'modelBaseVersion': None}
398
+ return TransformationRules(
399
+ datex_json_renamings,
400
+ value_transforms,
401
+ keys_to_drop,
402
+ self.datex_xml_2_json_xsitype_mappings,
403
+ keys_to_pull_up,
404
+ )
405
+
406
+ def xmlv2_to_json3_7(self):
407
+ datex_json_renamings = {
408
+ # Assumed DATEXII JSON Schema Artifacts
409
+ '@modelBaseVersion': 'modelBaseVersionG',
410
+ '@id': 'idG',
411
+ '@lang': 'lang',
412
+ '@version': 'versionG',
413
+ '@targetClass': 'targetClass',
414
+ # XML to JSON-schema conversion artefacts which were introduced by DATEX to avoid name clashes
415
+ 'commentExtension': 'sitCommentExtensionG',
416
+ 'headerInformationExtension': 'comHeaderInformationExtensionG',
417
+ 'generalObstructionExtension': 'sitGeneralObstructionExtensionG',
418
+ 'impactExtension': 'sitImpactExtensionG',
419
+ 'operatorActionExtension': 'sitOperatorActionExtensionG',
420
+ 'roadOrCarriagewayOrLaneManagementExtension': 'sitRoadOrCarriagewayOrLaneManagementExtensionG',
421
+ 'roadworksExtension': 'sitRoadworksExtensionG',
422
+ 'situationExtension': 'sitSituationExtensionG',
423
+ 'situationRecordExtension': 'sitSituationRecordExtensionG',
424
+ 'payloadPublicationExtension': 'comPayloadPublicationExtensionG',
425
+ 'affectedCarriagewayAndLanesExtension': 'locCarriagewayExtensionG',
426
+ # Note: the following extendedLinear extension is not defined in schema, JSON might not match expectation
427
+ 'extendedLinear': 'locLinearLocationExtensionG',
428
+ # Note: the following networkLocationExtension extension is not defined in schema, JSON might not match expectation
429
+ 'networkLocationExtension': 'locNetworkLocationExtensionG',
430
+ 'openlrPointAlongLine': 'locOpenlrPointAlongLine',
431
+ 'openlrPoiWithAccessPoint': 'locOpenlrPoiWithAccessPoint',
432
+ }
433
+
434
+ # A couple of text properties are wrapped in an object, presumably for extendability
435
+ def wrap_with_value(v):
436
+ return {'value': v}
437
+
438
+ value_transforms = {
439
+ # Value wrapping...
440
+ 'abnormalTrafficType': wrap_with_value,
441
+ 'accidentType': lambda v: {'value': self.ACCIDENT_TYPE_MAPPINGS.get(v, v)},
442
+ 'informationStatus': wrap_with_value,
443
+ 'confidentiality': wrap_with_value,
444
+ 'applicableForTrafficDirection': wrap_with_value,
445
+ 'constructionWorkType': wrap_with_value,
446
+ 'delaysType': wrap_with_value,
447
+ 'probabilityOfOccurrence': wrap_with_value,
448
+ 'poorEnvironmentType': wrap_with_value,
449
+ 'roadMaintenanceType': wrap_with_value,
450
+ 'mobilityType': wrap_with_value,
451
+ 'locationDescription': wrap_with_value,
452
+ 'operatorActionStatus': wrap_with_value,
453
+ 'tpegDirection': wrap_with_value,
454
+ 'tpegLinearLocationType': wrap_with_value,
455
+ 'tpegOtherPointDescriptorType': wrap_with_value,
456
+ 'trafficManuallyDirectedBy': wrap_with_value,
457
+ 'validityStatus': wrap_with_value,
458
+ 'alertCDirection': lambda v: transform_alertc(v, True),
459
+ 'openlrFormOfWay': wrap_with_value,
460
+ 'complianceOption': wrap_with_value,
461
+ 'placesAtWhichApplicable': wrap_with_value,
462
+ 'loadType': wrap_with_value,
463
+ 'applicableForTrafficType': wrap_with_value,
464
+ 'roadOrCarriagewayOrLaneManagementType': wrap_with_value,
465
+ 'disturbanceActivityType': wrap_with_value,
466
+ 'vehicleType': wrap_with_value,
467
+ 'referentType': wrap_with_value,
468
+ 'directionRelativeOnLinearSection': wrap_with_value,
469
+ 'comparisonOperator': wrap_with_value,
470
+ 'generalInstructionToRoadUsersType': wrap_with_value,
471
+ 'environmentalObstructionType': wrap_with_value,
472
+ 'roadworksDuration': wrap_with_value,
473
+ 'drivingConditionType': wrap_with_value,
474
+ 'openlrOrientation': wrap_with_value,
475
+ 'openlrSideOfRoad': wrap_with_value,
476
+ 'tpegAreaLocationType': wrap_with_value,
477
+ 'subjectTypeOfWorks': wrap_with_value,
478
+ 'applicableDay': wrap_with_value,
479
+ 'reroutingManagementType': wrap_with_value,
480
+ 'animalPresenceType': wrap_with_value,
481
+ 'severity': wrap_with_value,
482
+ 'authorityOperationType': wrap_with_value,
483
+ 'equipmentOrSystemFaultType': wrap_with_value,
484
+ 'faultyEquipmentOrSystemType': wrap_with_value,
485
+ 'generalNetworkManagementType': wrap_with_value,
486
+ 'overallSeverity': wrap_with_value,
487
+ 'carriageway': wrap_with_value,
488
+ 'infrastructureDamageType': wrap_with_value,
489
+ 'speedManagementType': wrap_with_value,
490
+ 'publicEventType': wrap_with_value,
491
+ 'weatherRelatedRoadConditionType': wrap_with_value,
492
+ 'nonWeatherRelatedRoadConditionType': wrap_with_value,
493
+ 'maintenanceVehicleActions': wrap_with_value,
494
+ 'roadsideServiceDisruptionType': wrap_with_value,
495
+ 'trafficConstrictionType': wrap_with_value,
496
+ 'tpegAreaDescriptorType': wrap_with_value,
497
+ 'transitServiceInformation': wrap_with_value,
498
+ 'transitServiceType': wrap_with_value,
499
+ 'directionRelativeAtPoint': wrap_with_value,
500
+ 'vehicleObstructionType': lambda v: {'value': self.VEHICLE_OBSTRUCTION_TYPE_MAPPINGS.get(v, v)},
501
+ # see https://docs.ndw.nu/en/dataformaten/datex2-v2.3/conversie-naar-v3/#generalobstruction-maps-to-generalobstruction
502
+ 'obstructionType': lambda v: {'value': self.OBSTRUCTION_TYPE_MAPPINGS.get(v, v)},
503
+ 'causeType': lambda v: {'value': self.CAUSE_TYPE_MAPPINGS.get(v, v)},
504
+ # vms
505
+ 'physicalSupport': wrap_with_value,
506
+ 'vmsPhysicalMounting': wrap_with_value,
507
+ 'tpegSimplePointLocationType': wrap_with_value,
508
+ 'alertCArea': lambda v: [v], # FIXME move up to xml2_to_json?
509
+ 'gmlLineString': lambda v: {'locGmlLineString': {'posList': v.get('posList', v.get('coordinates', '').replace(',', ' '))}},
510
+ 'lane': lambda v: {'laneNumber': int(v[4:])} if v.startswith('lane') else {'laneUsage': {'value': v}},
511
+ }
512
+
513
+ keys_to_drop = {
514
+ '@xsi:schemaLocation': [],
515
+ '@xmlns:xsd': [],
516
+ '@xsi:type': [],
517
+ }
518
+
519
+ rules = self.xmlv2_to_json()
520
+ return TransformationRules(
521
+ rules.renamings | datex_json_renamings,
522
+ rules.value_transforms | value_transforms,
523
+ rules.keys_to_drop | keys_to_drop,
524
+ self.datex_xml_2_json_xsitype_mappings,
525
+ rules.keys_to_pull_up,
526
+ )