diff --git a/PYDANTIC_GUIDE.md b/PYDANTIC_GUIDE.md index 4571b2afe..a7748315c 100644 --- a/PYDANTIC_GUIDE.md +++ b/PYDANTIC_GUIDE.md @@ -836,7 +836,7 @@ class RoadSegment(TransportationSegment): speed_limits: SpeedLimits | None = None def get_speed_limit(self) -> float: - return self.speed_limits.max_speed if self.speed_limits else 50.0 + return self.speed_limits.speed if self.speed_limits else 50.0 # Now only concrete classes can be instantiated # base_segment = TransportationSegment(...) # TypeError: Can't instantiate abstract class diff --git a/counterexamples/transportation/segment/road/restrictions/speed_limits/bad-speed-limits-bad-type.yaml b/counterexamples/transportation/segment/road/restrictions/speed_limits/bad-speed-limits-bad-type.yaml new file mode 100644 index 000000000..38c1a773f --- /dev/null +++ b/counterexamples/transportation/segment/road/restrictions/speed_limits/bad-speed-limits-bad-type.yaml @@ -0,0 +1,19 @@ +--- +id: road segment where road.restrictions.speed_limits contains a rule with an invalid type +type: Feature +geometry: + type: LineString + coordinates: [[0, 0], [1, 1]] +properties: + ext_expected_errors: + - "[S#/$defs/propertyDefinitions/speedLimitType/enum]: value must be one of" + theme: transportation + type: segment + version: 1 + subtype: road + class: secondary + speed_limits: + - type: recommended + speed: + value: 110 + unit: mph diff --git a/counterexamples/transportation/segment/road/restrictions/speed_limits/bad-speed-limits-empty-rule.json b/counterexamples/transportation/segment/road/restrictions/speed_limits/bad-speed-limits-empty-rule.json index 833e57006..de732a0e8 100644 --- a/counterexamples/transportation/segment/road/restrictions/speed_limits/bad-speed-limits-empty-rule.json +++ b/counterexamples/transportation/segment/road/restrictions/speed_limits/bad-speed-limits-empty-rule.json @@ -15,8 +15,7 @@ {} ], "ext_expected_errors": [ - "anyOf/0/required]: missing property 'min_speed'", - "anyOf/1/required]: missing property 'max_speed'" + "required]: missing properties 'type', 'speed'" ] } } diff --git a/counterexamples/transportation/segment/road/restrictions/speed_limits/bad-speed-limits-mode.yaml b/counterexamples/transportation/segment/road/restrictions/speed_limits/bad-speed-limits-mode.yaml index 4358421ee..cb50610bb 100644 --- a/counterexamples/transportation/segment/road/restrictions/speed_limits/bad-speed-limits-mode.yaml +++ b/counterexamples/transportation/segment/road/restrictions/speed_limits/bad-speed-limits-mode.yaml @@ -21,7 +21,8 @@ properties: - connector_id: barConnector at: 1 speed_limits: - - max_speed: + - type: maximum + speed: value: 110 unit: mph when: {mode: [foo]} diff --git a/docs/schema/concepts/by-theme/transportation/roads.mdx b/docs/schema/concepts/by-theme/transportation/roads.mdx index 5b4cb234e..c6ea596b0 100644 --- a/docs/schema/concepts/by-theme/transportation/roads.mdx +++ b/docs/schema/concepts/by-theme/transportation/roads.mdx @@ -14,6 +14,7 @@ import ExampleAccessRestrictionAxleLimit from '!!raw-loader!@site/docs/_examples import ExampleSpeedLimitsSimple from '!!raw-loader!@site/docs/_examples/transportation/docusaurus/speed-limits-01-simple.yaml'; import ExampleSpeedLimitsDirectional from '!!raw-loader!@site/docs/_examples/transportation/docusaurus/speed-limits-02-directional.yaml'; import ExampleSpeedLimitsVariableMax from '!!raw-loader!@site/docs/_examples/transportation/docusaurus/speed-limits-03-variable-max.yaml'; +import ExampleSpeedLimitsAdvisory from '!!raw-loader!@site/docs/_examples/transportation/docusaurus/speed-limits-04-advisory.yaml'; import ExampleTurnRestriction1Source from '!!raw-loader!@site/docs/_examples/transportation/docusaurus/turn-restriction-01-source.yaml'; import ExampleTurnRestriction1Target from '!!raw-loader!@site/docs/_examples/transportation/docusaurus/turn-restriction-01-target.yaml'; import ExampleTurnRestriction1Exit from '!!raw-loader!@site/docs/_examples/transportation/docusaurus/turn-restriction-01-exit.yaml'; @@ -272,9 +273,13 @@ real-world context to the example.* ### Speed limits Speed limits restrict the speed at which travel is permitted on a road. -Typically speed limits specify maximum allowed speeds, but the -Overture also allows minimum speed limits to be set and variable speed -corridors to be indicated. +Each speed limit rule has a `type` indicating the kind of speed rule, +and a `speed` holding the speed value. Typically speed limits specify +`maximum` allowed speeds, but Overture also supports `minimum` speed +limits and `advisory` speeds, which are recommended but not regulatory. +Variable speed corridors and roads without a regulatory maximum speed +are indicated with the `road_flags` property, using the +`dynamic_speed` and `unlimited_speed` flags. Every road segment has an implied speed limit or set of speed limits defined by its [road class](#road-class) and local rules, norms, and @@ -306,4 +311,10 @@ As with access restrictions and turn restrictions, speed limits can be specified { ExampleSpeedLimitsVariableMax } + + + +{ ExampleSpeedLimitsAdvisory } + + \ No newline at end of file diff --git a/docs/schema/concepts/by-theme/transportation/segments.mdx b/docs/schema/concepts/by-theme/transportation/segments.mdx index 03ebe8231..0fcc34521 100644 --- a/docs/schema/concepts/by-theme/transportation/segments.mdx +++ b/docs/schema/concepts/by-theme/transportation/segments.mdx @@ -86,7 +86,7 @@ case, it is similar to, but not the same as, OSM's `highway=*` and `railway=*` t ## Flags -A segment's flags (`road_flags` for roads, `rail_flags` for rails) are a set of named flag values indicating the presence or absence of simple physical characteristics. +A segment's flags (`road_flags` for roads, `rail_flags` for rails) are a set of named flag values indicating the presence or absence of simple physical characteristics and other properties of the segment. For example, a road segment with `road_flags = [is_link, is_under_construction]` is a link segment that is physically under construction. diff --git a/examples/transportation/docusaurus/geometric-scoping.yaml b/examples/transportation/docusaurus/geometric-scoping.yaml index 86f5f58d7..ea1678f25 100644 --- a/examples/transportation/docusaurus/geometric-scoping.yaml +++ b/examples/transportation/docusaurus/geometric-scoping.yaml @@ -12,10 +12,12 @@ properties: class: primary speed_limits: - between: [0, 0.15] - max_speed: + type: maximum + speed: value: 100 unit: km/h - between: [0.15, 1] - max_speed: + type: maximum + speed: value: 60 unit: km/h diff --git a/examples/transportation/docusaurus/speed-limits-01-simple.yaml b/examples/transportation/docusaurus/speed-limits-01-simple.yaml index 30bea71ce..923ed5727 100644 --- a/examples/transportation/docusaurus/speed-limits-01-simple.yaml +++ b/examples/transportation/docusaurus/speed-limits-01-simple.yaml @@ -13,6 +13,7 @@ properties: subtype: road class: residential speed_limits: - - max_speed: + - type: maximum + speed: value: 30 unit: km/h diff --git a/examples/transportation/docusaurus/speed-limits-02-directional.yaml b/examples/transportation/docusaurus/speed-limits-02-directional.yaml index 8f30bb69a..822b6b819 100644 --- a/examples/transportation/docusaurus/speed-limits-02-directional.yaml +++ b/examples/transportation/docusaurus/speed-limits-02-directional.yaml @@ -1,5 +1,5 @@ --- -id: speed-limits-variable-max +id: speed-limits-directional type: Feature geometry: type: LineString @@ -13,8 +13,10 @@ properties: subtype: road class: secondary speed_limits: - - max_speed: {value: 70, unit: "mph"} + - type: maximum + speed: {value: 70, unit: "mph"} - when: mode: [hgv] heading: forward - max_speed: {value: 65, unit: "mph"} + type: maximum + speed: {value: 65, unit: "mph"} diff --git a/examples/transportation/docusaurus/speed-limits-03-variable-max.yaml b/examples/transportation/docusaurus/speed-limits-03-variable-max.yaml index 0aeca3887..5f8251056 100644 --- a/examples/transportation/docusaurus/speed-limits-03-variable-max.yaml +++ b/examples/transportation/docusaurus/speed-limits-03-variable-max.yaml @@ -14,8 +14,10 @@ properties: version: 1 subtype: road class: motorway + road_flags: + - values: [dynamic_speed] speed_limits: - - max_speed: + - type: maximum + speed: value: 100 unit: km/h - is_max_speed_variable: true diff --git a/examples/transportation/docusaurus/speed-limits-04-advisory.yaml b/examples/transportation/docusaurus/speed-limits-04-advisory.yaml new file mode 100644 index 000000000..c0d90ee02 --- /dev/null +++ b/examples/transportation/docusaurus/speed-limits-04-advisory.yaml @@ -0,0 +1,25 @@ +--- +id: speed-limits-advisory +type: Feature +geometry: + type: LineString + coordinates: + - [-123.36051977110497, 48.42945540586221] + - [-123.36053780784551, 48.42906755412086] + - [-123.36034842206483, 48.42868269843091] +properties: + theme: transportation + type: segment + version: 1 + subtype: road + class: secondary + speed_limits: + - type: maximum + speed: + value: 60 + unit: km/h + - between: [0.4, 1] + type: advisory + speed: + value: 40 + unit: km/h diff --git a/examples/transportation/segment/road/restrictions/road-restrictions-speed-limits.yaml b/examples/transportation/segment/road/restrictions/road-restrictions-speed-limits.yaml index ac229da48..c89741435 100644 --- a/examples/transportation/segment/road/restrictions/road-restrictions-speed-limits.yaml +++ b/examples/transportation/segment/road/restrictions/road-restrictions-speed-limits.yaml @@ -10,29 +10,46 @@ properties: subtype: road class: tertiary version: 3 + road_flags: + - values: [dynamic_speed] + when: {heading: forward} + - values: [dynamic_speed] + between: [0.25, 0.5] + when: {heading: forward} speed_limits: - - max_speed: {value: 20, unit: 'km/h'} + - type: maximum + speed: {value: 20, unit: 'km/h'} between: [ 0, 0.5 ] - - min_speed: {value: 25, unit: 'mph'} + - type: minimum + speed: {value: 25, unit: 'mph'} when: during: PH - - max_speed: {value: 100, unit: 'km/h'} - min_speed: {value: 75, unit: 'km/h'} + - type: maximum + speed: {value: 100, unit: 'km/h'} + when: {heading: forward} + - type: minimum + speed: {value: 75, unit: 'km/h'} when: {heading: forward} - - min_speed: {value: 25, unit: 'mph'} + - type: minimum + speed: {value: 25, unit: 'mph'} when: heading: forward mode: [car, hgv] - - max_speed: {value: 60, unit: 'mph'} - is_max_speed_variable: true + - type: maximum + speed: {value: 60, unit: 'mph'} when: heading: forward using: [at_destination] - - min_speed: {value: 25, unit: 'mph'} + - type: advisory + speed: {value: 45, unit: 'mph'} + between: [ 0.5, 1 ] + - type: minimum + speed: {value: 25, unit: 'mph'} when: heading: forward recognized: [as_employee] - - min_speed: {value: 40, unit: 'mph'} + - type: minimum + speed: {value: 40, unit: 'mph'} when: heading: forward vehicle: @@ -47,9 +64,8 @@ properties: comparison: less_than value: 12 unit: 'ft' - - max_speed: {value: 30, unit: 'km/h'} - min_speed: {value: 20, unit: 'mph'} - is_max_speed_variable: true + - type: maximum + speed: {value: 30, unit: 'km/h'} between: [0.25, 0.5] when: heading: forward diff --git a/examples/transportation/segment/road/road.yaml b/examples/transportation/segment/road/road.yaml index eac92f24e..d2b1dd80e 100644 --- a/examples/transportation/segment/road/road.yaml +++ b/examples/transportation/segment/road/road.yaml @@ -27,24 +27,27 @@ properties: road_surface: - value: gravel road_flags: - - values: [is_link, is_tunnel] # Note: `is_link` is deprecated and will be removed in a future release in favor of the link subclass + - values: [is_link, is_tunnel, dynamic_speed] # Note: `is_link` is deprecated and will be removed in a future release in favor of the link subclass level_rules: - value: -1 width_rules: - value: 10 speed_limits: - - min_speed: + - type: minimum + speed: value: 90 unit: km/h - max_speed: + - type: maximum + speed: value: 110 unit: mph - is_max_speed_variable: true - - max_speed: + - type: maximum + speed: value: 55 unit: mph when: {mode: [ "truck" ]} - - max_speed: + - type: maximum + speed: value: 30 unit: km/h between: [0.25, 0.50] diff --git a/packages/overture-schema-codegen/tests/test_pyspark_check_builder.py b/packages/overture-schema-codegen/tests/test_pyspark_check_builder.py index c4c9f84ef..965d73857 100644 --- a/packages/overture-schema-codegen/tests/test_pyspark_check_builder.py +++ b/packages/overture-schema-codegen/tests/test_pyspark_check_builder.py @@ -1605,16 +1605,6 @@ def test_shared_fields_have_no_variant_values( for node in subtype_nodes: assert node.guards == () - def test_speed_limits_require_any_of_in_model_nodes( - self, model_nodes: list[ModelCheck] - ) -> None: - speed_limit_nodes = [ - n - for n in _filter_nodes(model_nodes, "check_require_any_of") - if n.target == _path("speed_limits[]") - ] - assert len(speed_limit_nodes) >= 1 - def test_destinations_require_any_of_in_model_nodes( self, model_nodes: list[ModelCheck] ) -> None: diff --git a/packages/overture-schema-common/README.md b/packages/overture-schema-common/README.md index fccbe9ee4..458af87c0 100644 --- a/packages/overture-schema-common/README.md +++ b/packages/overture-schema-common/README.md @@ -33,7 +33,7 @@ from overture.schema.system.numeric import float32 @scoped(Scope.GEOMETRIC_RANGE, Scope.TEMPORAL) class SpeedLimit(BaseModel): - max_speed: float32 + speed: float32 ``` This produces a model with `between` (geometric range) and `when.during` (temporal) fields, both optional. The full set of scopes and the fields they inject: diff --git a/packages/overture-schema-theme-transportation/pyproject.toml b/packages/overture-schema-theme-transportation/pyproject.toml index 596197bf4..a5b4b5884 100644 --- a/packages/overture-schema-theme-transportation/pyproject.toml +++ b/packages/overture-schema-theme-transportation/pyproject.toml @@ -135,8 +135,9 @@ unit = "m" value = 5.2 [[examples.Segment.speed_limits]] +type = "maximum" -[examples.Segment.speed_limits.max_speed] +[examples.Segment.speed_limits.speed] unit = "km/h" value = 60 diff --git a/packages/overture-schema-theme-transportation/src/overture/schema/transportation/__init__.py b/packages/overture-schema-theme-transportation/src/overture/schema/transportation/__init__.py index 4300f8a12..7e1a22c73 100644 --- a/packages/overture-schema-theme-transportation/src/overture/schema/transportation/__init__.py +++ b/packages/overture-schema-theme-transportation/src/overture/schema/transportation/__init__.py @@ -35,6 +35,7 @@ SegmentSubtype, Speed, SpeedLimitRule, + SpeedLimitType, TransportationSegment, WaterSegment, WidthRule, @@ -69,6 +70,7 @@ "SegmentSubtype", "Speed", "SpeedLimitRule", + "SpeedLimitType", "TransportationSegment", "WaterSegment", "WidthRule", diff --git a/packages/overture-schema-theme-transportation/src/overture/schema/transportation/segment/__init__.py b/packages/overture-schema-theme-transportation/src/overture/schema/transportation/segment/__init__.py index 3bd356872..6fcc64773 100644 --- a/packages/overture-schema-theme-transportation/src/overture/schema/transportation/segment/__init__.py +++ b/packages/overture-schema-theme-transportation/src/overture/schema/transportation/segment/__init__.py @@ -18,6 +18,7 @@ SegmentSubtype, Speed, SpeedLimitRule, + SpeedLimitType, TransportationSegment, WidthRule, ) @@ -91,6 +92,7 @@ "SegmentSubtype", "Speed", "SpeedLimitRule", + "SpeedLimitType", "TransportationSegment", "WaterSegment", "WidthRule", diff --git a/packages/overture-schema-theme-transportation/src/overture/schema/transportation/segment/_common.py b/packages/overture-schema-theme-transportation/src/overture/schema/transportation/segment/_common.py index 8c21c8317..794fc4028 100644 --- a/packages/overture-schema-theme-transportation/src/overture/schema/transportation/segment/_common.py +++ b/packages/overture-schema-theme-transportation/src/overture/schema/transportation/segment/_common.py @@ -28,7 +28,7 @@ GeometryType, GeometryTypeConstraint, ) -from overture.schema.system.model_constraint import no_extra_fields, require_any_of +from overture.schema.system.model_constraint import no_extra_fields from overture.schema.system.numeric import float64, int32 from overture.schema.system.ref import Id, Reference, Relationship @@ -297,8 +297,18 @@ class Speed(BaseModel): unit: SpeedUnit +class SpeedLimitType(str, DocumentedEnum): + """The kind of speed limit.""" + + ADVISORY = ( + "advisory", + "A recommended safe speed (e.g. before sharp curves or on ramps)", + ) + MAXIMUM = "maximum" + MINIMUM = "minimum" + + @no_extra_fields -@require_any_of("max_speed", "min_speed") @scoped( Scope.GEOMETRIC_RANGE, Scope.HEADING, @@ -311,17 +321,10 @@ class Speed(BaseModel): class SpeedLimitRule(BaseModel): """An individual speed limit rule.""" - # Optional + # Required - max_speed: Speed | None = None - min_speed: Speed | None = None - is_max_speed_variable: Annotated[ - bool | None, - Field( - description="Indicates a variable speed corridor", - strict=True, - ), - ] = False + type: SpeedLimitType + speed: Speed SpeedLimits = NewType( diff --git a/packages/overture-schema-theme-transportation/src/overture/schema/transportation/segment/road.py b/packages/overture-schema-theme-transportation/src/overture/schema/transportation/segment/road.py index d29f3d9a7..2b92ec546 100644 --- a/packages/overture-schema-theme-transportation/src/overture/schema/transportation/segment/road.py +++ b/packages/overture-schema-theme-transportation/src/overture/schema/transportation/segment/road.py @@ -86,7 +86,7 @@ class RoadSubclassRule(BaseModel): class RoadFlag(str, DocumentedEnum): """Simple flags that can be on or off for a road segment. - Specifies physical characteristics and can overlap. + Specifies physical characteristics and other properties of the road and can overlap. """ IS_BRIDGE = "is_bridge" @@ -99,12 +99,28 @@ class RoadFlag(str, DocumentedEnum): IS_ABANDONED = "is_abandoned" IS_COVERED = "is_covered" IS_INDOOR = "is_indoor" + DYNAMIC_SPEED = ( + "dynamic_speed", + "Speed limits on the road are dynamic (e.g. displayed on variable signs in a dynamic speed corridor)", + ) + UNLIMITED_SPEED = ( + "unlimited_speed", + "No regulatory maximum speed applies (e.g. parts of the German Autobahn)", + ) @no_extra_fields -@scoped(Scope.GEOMETRIC_RANGE) +@scoped( + Scope.GEOMETRIC_RANGE, + Scope.HEADING, + Scope.PURPOSE_OF_USE, + Scope.RECOGNIZED_STATUS, + Scope.TEMPORAL, + Scope.TRAVEL_MODE, + Scope.VEHICLE, +) class RoadFlagRule(BaseModel): - """Road-specific flag rule with geometric scoping only.""" + """Road-specific flag rule.""" values: Annotated[list[RoadFlag], Field(min_length=1), UniqueItemsConstraint()] diff --git a/packages/overture-schema-theme-transportation/tests/segment_baseline_schema.json b/packages/overture-schema-theme-transportation/tests/segment_baseline_schema.json index 0bf9d1962..a350c9e08 100644 --- a/packages/overture-schema-theme-transportation/tests/segment_baseline_schema.json +++ b/packages/overture-schema-theme-transportation/tests/segment_baseline_schema.json @@ -755,7 +755,7 @@ "type": "string" }, "RoadFlag": { - "description": "Simple flags that can be on or off for a road segment.\n\nSpecifies physical characteristics and can overlap.", + "description": "Simple flags that can be on or off for a road segment.\n\nSpecifies physical characteristics and other properties of the road and can overlap.", "enum": [ "is_bridge", "is_link", @@ -763,14 +763,16 @@ "is_under_construction", "is_abandoned", "is_covered", - "is_indoor" + "is_indoor", + "dynamic_speed", + "unlimited_speed" ], "title": "RoadFlag", "type": "string" }, "RoadFlagRule": { "additionalProperties": false, - "description": "Road-specific flag rule with geometric scoping only.", + "description": "Road-specific flag rule.", "properties": { "between": { "description": "The linearly-referenced sub-segment of the geometry, specified as a range (pair) of percentage displacements from the start of the geometry, that the containing RoadFlagRule applies to.", @@ -792,6 +794,9 @@ "title": "Values", "type": "array", "uniqueItems": true + }, + "when": { + "$ref": "#/$defs/overture__schema__transportation__segment__road__RoadFlagRule__When" } }, "required": [ @@ -1283,32 +1288,6 @@ }, "SpeedLimitRule": { "additionalProperties": false, - "anyOf": [ - { - "properties": { - "max_speed": { - "not": { - "type": "null" - } - } - }, - "required": [ - "max_speed" - ] - }, - { - "properties": { - "min_speed": { - "not": { - "type": "null" - } - } - }, - "required": [ - "min_speed" - ] - } - ], "description": "An individual speed limit rule.", "properties": { "between": { @@ -1323,25 +1302,33 @@ "title": "Between", "type": "array" }, - "is_max_speed_variable": { - "default": false, - "description": "Indicates a variable speed corridor", - "title": "Is Max Speed Variable", - "type": "boolean" - }, - "max_speed": { + "speed": { "$ref": "#/$defs/Speed" }, - "min_speed": { - "$ref": "#/$defs/Speed" + "type": { + "$ref": "#/$defs/SpeedLimitType" }, "when": { "$ref": "#/$defs/overture__schema__transportation__segment___common__SpeedLimitRule__When" } }, + "required": [ + "type", + "speed" + ], "title": "SpeedLimitRule", "type": "object" }, + "SpeedLimitType": { + "description": "The kind of speed limit.", + "enum": [ + "advisory", + "maximum", + "minimum" + ], + "title": "SpeedLimitType", + "type": "string" + }, "SpeedUnit": { "description": "Unit of speed.", "enum": [ @@ -2227,6 +2214,164 @@ ], "title": "DestinationRule.When", "type": "object" + }, + "overture__schema__transportation__segment__road__RoadFlagRule__When": { + "additionalProperties": false, + "anyOf": [ + { + "properties": { + "heading": { + "not": { + "type": "null" + } + } + }, + "required": [ + "heading" + ] + }, + { + "properties": { + "during": { + "not": { + "type": "null" + } + } + }, + "required": [ + "during" + ] + }, + { + "properties": { + "mode": { + "not": { + "type": "null" + } + } + }, + "required": [ + "mode" + ] + }, + { + "properties": { + "using": { + "not": { + "type": "null" + } + } + }, + "required": [ + "using" + ] + }, + { + "properties": { + "recognized": { + "not": { + "type": "null" + } + } + }, + "required": [ + "recognized" + ] + }, + { + "properties": { + "vehicle": { + "not": { + "type": "null" + } + } + }, + "required": [ + "vehicle" + ] + } + ], + "description": "Scopes for RoadFlagRule: Scope.HEADING, Scope.TEMPORAL, Scope.TRAVEL MODE, Scope.PURPOSE OF USE, Scope.RECOGNIZED STATUS and Scope.VEHICLE", + "properties": { + "during": { + "description": "The recurring time span, in the OpenStreetMap opening hours format, that the containing RoadFlagRule applies to. For the OSM opening hours specification, see https://wiki.openstreetmap.org/wiki/Key:opening_hours/specification.", + "title": "During", + "type": "string" + }, + "heading": { + "$ref": "#/$defs/Heading", + "description": "The heading, either forward or backward, that the containing RoadFlagRule applies to." + }, + "mode": { + "description": "A list of one or more travel modes, such as car, truck, or foot, that the containing RoadFlagRule applies to.", + "items": { + "$ref": "#/$defs/TravelMode" + }, + "minItems": 1, + "title": "Mode", + "type": "array", + "uniqueItems": true + }, + "recognized": { + "description": "A list of one or more recognized status values, such as employee or student, that the containing RoadFlagRule applies to.", + "items": { + "$ref": "#/$defs/RecognizedStatus" + }, + "minItems": 1, + "title": "Recognized", + "type": "array", + "uniqueItems": true + }, + "using": { + "description": "A list of one or more usage purposes, such as delivery or arrival at final destination, that the containing RoadFlagRule applies to.", + "items": { + "$ref": "#/$defs/PurposeOfUse" + }, + "minItems": 1, + "title": "Using", + "type": "array", + "uniqueItems": true + }, + "vehicle": { + "description": "A list of one or more vehicle parameters that limit the vehicles the containing RoadFlagRule applies to.", + "items": { + "description": "Selects vehicles that a scope applies to based on criteria such as height, weight, or axle count.", + "discriminator": { + "mapping": { + "axle_count": "#/$defs/VehicleAxleCountSelector", + "height": "#/$defs/VehicleHeightSelector", + "length": "#/$defs/VehicleLengthSelector", + "weight": "#/$defs/VehicleWeightSelector", + "width": "#/$defs/VehicleWidthSelector" + }, + "propertyName": "dimension" + }, + "oneOf": [ + { + "$ref": "#/$defs/VehicleAxleCountSelector" + }, + { + "$ref": "#/$defs/VehicleHeightSelector" + }, + { + "$ref": "#/$defs/VehicleLengthSelector" + }, + { + "$ref": "#/$defs/VehicleWeightSelector" + }, + { + "$ref": "#/$defs/VehicleWidthSelector" + } + ] + }, + "minItems": 1, + "title": "Vehicle", + "type": "array", + "uniqueItems": true + } + }, + "title": "RoadFlagRule.When", + "type": "object" } }, "oneOf": [ diff --git a/reference/counterexamples/transportation/segment/road/restrictions/speed_limits/bad-speed-limits-bad-type.yaml b/reference/counterexamples/transportation/segment/road/restrictions/speed_limits/bad-speed-limits-bad-type.yaml new file mode 100644 index 000000000..38c1a773f --- /dev/null +++ b/reference/counterexamples/transportation/segment/road/restrictions/speed_limits/bad-speed-limits-bad-type.yaml @@ -0,0 +1,19 @@ +--- +id: road segment where road.restrictions.speed_limits contains a rule with an invalid type +type: Feature +geometry: + type: LineString + coordinates: [[0, 0], [1, 1]] +properties: + ext_expected_errors: + - "[S#/$defs/propertyDefinitions/speedLimitType/enum]: value must be one of" + theme: transportation + type: segment + version: 1 + subtype: road + class: secondary + speed_limits: + - type: recommended + speed: + value: 110 + unit: mph diff --git a/reference/counterexamples/transportation/segment/road/restrictions/speed_limits/bad-speed-limits-empty-rule.json b/reference/counterexamples/transportation/segment/road/restrictions/speed_limits/bad-speed-limits-empty-rule.json index eaf9f1a5b..f7dd2e241 100644 --- a/reference/counterexamples/transportation/segment/road/restrictions/speed_limits/bad-speed-limits-empty-rule.json +++ b/reference/counterexamples/transportation/segment/road/restrictions/speed_limits/bad-speed-limits-empty-rule.json @@ -17,8 +17,7 @@ "speed_limits": [{}], "ext_description": "road segment where road.restrictions.speed_limits contains an empty rule", "ext_expected_errors": [ - "anyOf/0/required]: missing property 'min_speed'", - "anyOf/1/required]: missing property 'max_speed'" + "required]: missing properties 'type', 'speed'" ] } } diff --git a/reference/counterexamples/transportation/segment/road/restrictions/speed_limits/bad-speed-limits-mode.yaml b/reference/counterexamples/transportation/segment/road/restrictions/speed_limits/bad-speed-limits-mode.yaml index 4358421ee..cb50610bb 100644 --- a/reference/counterexamples/transportation/segment/road/restrictions/speed_limits/bad-speed-limits-mode.yaml +++ b/reference/counterexamples/transportation/segment/road/restrictions/speed_limits/bad-speed-limits-mode.yaml @@ -21,7 +21,8 @@ properties: - connector_id: barConnector at: 1 speed_limits: - - max_speed: + - type: maximum + speed: value: 110 unit: mph when: {mode: [foo]} diff --git a/reference/examples/transportation/docusaurus/geometric-scoping.yaml b/reference/examples/transportation/docusaurus/geometric-scoping.yaml index 86f5f58d7..ea1678f25 100644 --- a/reference/examples/transportation/docusaurus/geometric-scoping.yaml +++ b/reference/examples/transportation/docusaurus/geometric-scoping.yaml @@ -12,10 +12,12 @@ properties: class: primary speed_limits: - between: [0, 0.15] - max_speed: + type: maximum + speed: value: 100 unit: km/h - between: [0.15, 1] - max_speed: + type: maximum + speed: value: 60 unit: km/h diff --git a/reference/examples/transportation/docusaurus/speed-limits-01-simple.yaml b/reference/examples/transportation/docusaurus/speed-limits-01-simple.yaml index 30bea71ce..923ed5727 100644 --- a/reference/examples/transportation/docusaurus/speed-limits-01-simple.yaml +++ b/reference/examples/transportation/docusaurus/speed-limits-01-simple.yaml @@ -13,6 +13,7 @@ properties: subtype: road class: residential speed_limits: - - max_speed: + - type: maximum + speed: value: 30 unit: km/h diff --git a/reference/examples/transportation/docusaurus/speed-limits-02-directional.yaml b/reference/examples/transportation/docusaurus/speed-limits-02-directional.yaml index 8f30bb69a..822b6b819 100644 --- a/reference/examples/transportation/docusaurus/speed-limits-02-directional.yaml +++ b/reference/examples/transportation/docusaurus/speed-limits-02-directional.yaml @@ -1,5 +1,5 @@ --- -id: speed-limits-variable-max +id: speed-limits-directional type: Feature geometry: type: LineString @@ -13,8 +13,10 @@ properties: subtype: road class: secondary speed_limits: - - max_speed: {value: 70, unit: "mph"} + - type: maximum + speed: {value: 70, unit: "mph"} - when: mode: [hgv] heading: forward - max_speed: {value: 65, unit: "mph"} + type: maximum + speed: {value: 65, unit: "mph"} diff --git a/reference/examples/transportation/docusaurus/speed-limits-03-variable-max.yaml b/reference/examples/transportation/docusaurus/speed-limits-03-variable-max.yaml index 0aeca3887..5f8251056 100644 --- a/reference/examples/transportation/docusaurus/speed-limits-03-variable-max.yaml +++ b/reference/examples/transportation/docusaurus/speed-limits-03-variable-max.yaml @@ -14,8 +14,10 @@ properties: version: 1 subtype: road class: motorway + road_flags: + - values: [dynamic_speed] speed_limits: - - max_speed: + - type: maximum + speed: value: 100 unit: km/h - is_max_speed_variable: true diff --git a/reference/examples/transportation/docusaurus/speed-limits-04-advisory.yaml b/reference/examples/transportation/docusaurus/speed-limits-04-advisory.yaml new file mode 100644 index 000000000..c0d90ee02 --- /dev/null +++ b/reference/examples/transportation/docusaurus/speed-limits-04-advisory.yaml @@ -0,0 +1,25 @@ +--- +id: speed-limits-advisory +type: Feature +geometry: + type: LineString + coordinates: + - [-123.36051977110497, 48.42945540586221] + - [-123.36053780784551, 48.42906755412086] + - [-123.36034842206483, 48.42868269843091] +properties: + theme: transportation + type: segment + version: 1 + subtype: road + class: secondary + speed_limits: + - type: maximum + speed: + value: 60 + unit: km/h + - between: [0.4, 1] + type: advisory + speed: + value: 40 + unit: km/h diff --git a/reference/examples/transportation/segment/road/restrictions/road-restrictions-speed-limits.yaml b/reference/examples/transportation/segment/road/restrictions/road-restrictions-speed-limits.yaml index ac229da48..c89741435 100644 --- a/reference/examples/transportation/segment/road/restrictions/road-restrictions-speed-limits.yaml +++ b/reference/examples/transportation/segment/road/restrictions/road-restrictions-speed-limits.yaml @@ -10,29 +10,46 @@ properties: subtype: road class: tertiary version: 3 + road_flags: + - values: [dynamic_speed] + when: {heading: forward} + - values: [dynamic_speed] + between: [0.25, 0.5] + when: {heading: forward} speed_limits: - - max_speed: {value: 20, unit: 'km/h'} + - type: maximum + speed: {value: 20, unit: 'km/h'} between: [ 0, 0.5 ] - - min_speed: {value: 25, unit: 'mph'} + - type: minimum + speed: {value: 25, unit: 'mph'} when: during: PH - - max_speed: {value: 100, unit: 'km/h'} - min_speed: {value: 75, unit: 'km/h'} + - type: maximum + speed: {value: 100, unit: 'km/h'} + when: {heading: forward} + - type: minimum + speed: {value: 75, unit: 'km/h'} when: {heading: forward} - - min_speed: {value: 25, unit: 'mph'} + - type: minimum + speed: {value: 25, unit: 'mph'} when: heading: forward mode: [car, hgv] - - max_speed: {value: 60, unit: 'mph'} - is_max_speed_variable: true + - type: maximum + speed: {value: 60, unit: 'mph'} when: heading: forward using: [at_destination] - - min_speed: {value: 25, unit: 'mph'} + - type: advisory + speed: {value: 45, unit: 'mph'} + between: [ 0.5, 1 ] + - type: minimum + speed: {value: 25, unit: 'mph'} when: heading: forward recognized: [as_employee] - - min_speed: {value: 40, unit: 'mph'} + - type: minimum + speed: {value: 40, unit: 'mph'} when: heading: forward vehicle: @@ -47,9 +64,8 @@ properties: comparison: less_than value: 12 unit: 'ft' - - max_speed: {value: 30, unit: 'km/h'} - min_speed: {value: 20, unit: 'mph'} - is_max_speed_variable: true + - type: maximum + speed: {value: 30, unit: 'km/h'} between: [0.25, 0.5] when: heading: forward diff --git a/reference/examples/transportation/segment/road/road.yaml b/reference/examples/transportation/segment/road/road.yaml index eac92f24e..d2b1dd80e 100644 --- a/reference/examples/transportation/segment/road/road.yaml +++ b/reference/examples/transportation/segment/road/road.yaml @@ -27,24 +27,27 @@ properties: road_surface: - value: gravel road_flags: - - values: [is_link, is_tunnel] # Note: `is_link` is deprecated and will be removed in a future release in favor of the link subclass + - values: [is_link, is_tunnel, dynamic_speed] # Note: `is_link` is deprecated and will be removed in a future release in favor of the link subclass level_rules: - value: -1 width_rules: - value: 10 speed_limits: - - min_speed: + - type: minimum + speed: value: 90 unit: km/h - max_speed: + - type: maximum + speed: value: 110 unit: mph - is_max_speed_variable: true - - max_speed: + - type: maximum + speed: value: 55 unit: mph when: {mode: [ "truck" ]} - - max_speed: + - type: maximum + speed: value: 30 unit: km/h between: [0.25, 0.50] diff --git a/schema/transportation/segment.yaml b/schema/transportation/segment.yaml index 4a959a1a6..85a28182f 100644 --- a/schema/transportation/segment.yaml +++ b/schema/transportation/segment.yaml @@ -229,7 +229,7 @@ properties: - restrooms # 'toilets' in OSM roadFlag: description: >- - Simple flags that can be on or off for a road segment. Specifies physical characteristics and can overlap. + Simple flags that can be on or off for a road segment. Specifies physical characteristics and other properties of the road and can overlap. type: string enum: - is_bridge @@ -239,6 +239,8 @@ properties: - is_abandoned - is_covered - is_indoor + - dynamic_speed + - unlimited_speed railFlag: description: >- Simple flags that can be on or off for a railway segment. Specifies physical characteristics and can overlap. @@ -326,6 +328,15 @@ properties: - value - unit unevaluatedProperties: false + speedLimitType: + description: >- + The kind of speed rule. minimum and maximum are regulatory + limits, and advisory is a recommended, non-regulatory speed. + type: string + enum: + - maximum + - minimum + - advisory purposeOfUse: description: >- Reason why a person or entity travelling on the transportation @@ -573,19 +584,13 @@ properties: purpose and use it to introduce an optional direction property in each rule. type: object - anyOf: - - required: [ min_speed ] - - required: [ max_speed ] + required: [ type, speed ] allOf: - "$ref": "../defs.yaml#/$defs/propertyContainers/geometricRangeScopeContainer" unevaluatedProperties: false properties: - min_speed: { "$ref": "#/$defs/propertyDefinitions/speed" } - max_speed: { "$ref": "#/$defs/propertyDefinitions/speed" } - is_max_speed_variable: - description: Indicates a variable speed corridor - type: boolean - default: false + type: { "$ref": "#/$defs/propertyDefinitions/speedLimitType" } + speed: { "$ref": "#/$defs/propertyDefinitions/speed" } when: allOf: - "$ref": "#/$defs/propertyContainers/temporalScopeContainer" @@ -677,6 +682,16 @@ properties: items: { "$ref": "#/$defs/propertyDefinitions/roadFlag" } uniqueItems: true minLength: 1 + when: + allOf: + - "$ref": "#/$defs/propertyContainers/temporalScopeContainer" + - "$ref": "#/$defs/propertyContainers/headingScopeContainer" + - "$ref": "#/$defs/propertyContainers/purposeOfUseScopeContainer" + - "$ref": "#/$defs/propertyContainers/recognizedStatusScopeContainer" + - "$ref": "#/$defs/propertyContainers/travelModeScopeContainer" + - "$ref": "#/$defs/propertyContainers/vehicleScopeContainer" + minProperties: 1 + unevaluatedProperties: false uniqueItems: true minLength: 1 railFlagsContainer: