From 897eeb1d7a7f22c1d50add4b6694256266637748 Mon Sep 17 00:00:00 2001 From: hoangtungdinh <11166240+hoangtungdinh@users.noreply.github.com> Date: Thu, 12 Sep 2024 16:49:45 +0200 Subject: [PATCH] Fix missing precondition in some checks Signed-off-by: hoangtungdinh <11166240+hoangtungdinh@users.noreply.github.com> --- poetry.lock | 14 +- qc_opendrive/base/models.py | 1 - qc_opendrive/base/utils.py | 354 +++++++++--------- .../checks/basic/version_is_defined.py | 10 +- qc_opendrive/checks/schema/valid_schema.py | 7 +- ...onnection_connect_road_no_incoming_road.py | 7 +- ...oad_lane_access_no_mix_of_deny_or_allow.py | 110 +++--- .../semantic/road_lane_level_true_one_side.py | 94 ++--- 8 files changed, 302 insertions(+), 295 deletions(-) diff --git a/poetry.lock b/poetry.lock index 04c17ff..5d5e12a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -29,7 +29,7 @@ pydantic-xml = "^2.11.0" type = "git" url = "https://github.com/asam-ev/qc-baselib-py.git" reference = "develop" -resolved_reference = "a893b7b14c7b6251ba17af619a0022d8ea9c8ea9" +resolved_reference = "f7ea664805bcce456ebd0ede93c852dd389c1944" [[package]] name = "black" @@ -630,13 +630,13 @@ lxml = ["lxml (>=4.9.0)"] [[package]] name = "pytest" -version = "8.3.2" +version = "8.3.3" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, - {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, + {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, + {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, ] [package.dependencies] @@ -738,13 +738,13 @@ files = [ [[package]] name = "xmlschema" -version = "3.3.2" +version = "3.4.1" description = "An XML Schema validator and decoder" optional = false python-versions = ">=3.8" files = [ - {file = "xmlschema-3.3.2-py3-none-any.whl", hash = "sha256:e9b9663aaa313904b8bf3d7487fc8123920e199662bc09bb32e0d62d9548f1ec"}, - {file = "xmlschema-3.3.2.tar.gz", hash = "sha256:a2f021f21d0b5ab371e9bcb5a1d5c34b9ba2c74ad3e32854474c4159bf94e158"}, + {file = "xmlschema-3.4.1-py3-none-any.whl", hash = "sha256:7113a9dca6c48625c4ea819486bce9567f41cff1b6d064f7c32da2e03d443deb"}, + {file = "xmlschema-3.4.1.tar.gz", hash = "sha256:4f69eca6f2c446b06c74acd958e7d6613487c253d959a3b75078dfcc880b80ff"}, ] [package.dependencies] diff --git a/qc_opendrive/base/models.py b/qc_opendrive/base/models.py index 5e4a65c..37703dd 100644 --- a/qc_opendrive/base/models.py +++ b/qc_opendrive/base/models.py @@ -1,6 +1,5 @@ from dataclasses import dataclass from enum import Enum -from typing import Union from lxml import etree from qc_baselib import Configuration, Result diff --git a/qc_opendrive/base/utils.py b/qc_opendrive/base/utils.py index c79d013..973d3cb 100644 --- a/qc_opendrive/base/utils.py +++ b/qc_opendrive/base/utils.py @@ -14,6 +14,20 @@ ) +def to_int(s): + try: + return int(s) + except (ValueError, TypeError): + return None + + +def to_float(s): + try: + return float(s) + except (ValueError, TypeError): + return None + + def get_root_without_default_namespace(path: str) -> etree._ElementTree: with open(path, "rb") as raw_file: xml_string = raw_file.read().decode() @@ -77,9 +91,9 @@ def get_road_id_map(root: etree._ElementTree) -> Dict[int, etree._ElementTree]: road_id_map = dict() for road in root.iter("road"): - road_id = road.get("id") + road_id = to_int(road.get("id")) if road_id is not None: - road_id_map[int(road_id)] = road + road_id_map[road_id] = road return road_id_map @@ -94,9 +108,9 @@ def get_junction_id_map(root: etree._ElementTree) -> Dict[int, etree._ElementTre junction_id_map = dict() for junction in root.iter("junction"): - junction_id = junction.get("id") + junction_id = to_int(junction.get("id")) if junction_id is not None: - junction_id_map[int(junction_id)] = junction + junction_id_map[junction_id] = junction return junction_id_map @@ -157,13 +171,13 @@ def get_road_linkage( if linkage is None: return None elif linkage.get("elementType") == "road": - road_id = linkage.get("elementId") + road_id = to_int(linkage.get("elementId")) contact_point = linkage.get("contactPoint") if road_id is None or contact_point is None: return None else: return models.RoadLinkage( - id=int(road_id), contact_point=models.ContactPoint(contact_point) + id=road_id, contact_point=models.ContactPoint(contact_point) ) else: return None @@ -181,11 +195,8 @@ def get_linked_junction_id( if linkage is None: return None elif linkage.get("elementType") == "junction": - junction_id = linkage.get("elementId") - if junction_id is None: - return None - else: - return int(junction_id) + junction_id = to_int(linkage.get("elementId")) + return junction_id else: return None @@ -213,9 +224,9 @@ def get_predecessor_lane_ids(lane: etree._ElementTree) -> List[int]: for link in links: linkages = link.findall("predecessor") for linkage in linkages: - predecessor_id = linkage.get("id") + predecessor_id = to_int(linkage.get("id")) if predecessor_id is not None: - predecessors.append(int(predecessor_id)) + predecessors.append(predecessor_id) return predecessors @@ -227,9 +238,9 @@ def get_successor_lane_ids(lane: etree._ElementTree) -> List[int]: for link in links: linkages = link.findall("successor") for linkage in linkages: - successor_id = linkage.get("id") + successor_id = to_int(linkage.get("id")) if successor_id is not None: - successors.append(int(successor_id)) + successors.append(successor_id) return successors @@ -242,8 +253,8 @@ def get_lane_link_element( for link in links: linkages = link.findall("predecessor") for linkage in linkages: - predecessor_id = linkage.get("id") - if predecessor_id is not None and link_id == int(predecessor_id): + predecessor_id = to_int(linkage.get("id")) + if predecessor_id is not None and link_id == predecessor_id: return linkage return None @@ -251,8 +262,8 @@ def get_lane_link_element( for link in links: linkages = link.findall("successor") for linkage in linkages: - successor_id = linkage.get("id") - if successor_id is not None and link_id == int(successor_id): + successor_id = to_int(linkage.get("id")) + if successor_id is not None and link_id == successor_id: return linkage return None @@ -266,8 +277,8 @@ def get_lane_from_lane_section( lanes = get_left_and_right_lanes_from_lane_section(lane_section) for lane in lanes: - current_id = lane.get("id") - if current_id is not None and int(current_id) == lane_id: + current_id = get_lane_id(lane) + if current_id is not None and current_id == lane_id: return lane return None @@ -298,19 +309,11 @@ def get_connections_from_junction( def get_lane_id(lane: etree._ElementTree) -> Union[None, int]: - lane_id = lane.get("id") - if lane_id is None: - return None - else: - return int(lane_id) + return to_int(lane.get("id")) def get_road_junction_id(road: etree._ElementTree) -> Union[None, int]: - junction_id = road.get("junction") - if junction_id is None: - return None - else: - return int(junction_id) + return to_int(road.get("junction")) def get_road_link_element( @@ -321,8 +324,8 @@ def get_road_link_element( for link in links: linkages = link.findall("predecessor") for linkage in linkages: - predecessor_id = linkage.get("elementId") - if predecessor_id is not None and link_id == int(predecessor_id): + predecessor_id = to_int(linkage.get("elementId")) + if predecessor_id is not None and link_id == predecessor_id: return linkage return None @@ -330,8 +333,8 @@ def get_road_link_element( for link in links: linkages = link.findall("successor") for linkage in linkages: - successor_id = linkage.get("elementId") - if successor_id is not None and link_id == int(successor_id): + successor_id = to_int(linkage.get("elementId")) + if successor_id is not None and link_id == successor_id: return linkage return None @@ -350,21 +353,13 @@ def road_belongs_to_junction(road: etree._Element) -> bool: def get_incoming_road_id_from_connection( connection: etree._Element, ) -> Union[None, int]: - incoming_road_id = connection.get("incomingRoad") - if incoming_road_id is None: - return None - else: - return int(incoming_road_id) + return to_int(connection.get("incomingRoad")) def get_connecting_road_id_from_connection( connection: etree._Element, ) -> Union[None, int]: - connecting_road_id = connection.get("connectingRoad") - if connecting_road_id is None: - return None - else: - return int(connecting_road_id) + return to_int(connection.get("connectingRoad")) def get_contact_point_from_connection( @@ -378,27 +373,34 @@ def get_contact_point_from_connection( def get_from_attribute_from_lane_link(lane_link: etree._Element) -> Union[int, None]: - from_attribute = lane_link.get("from") - if from_attribute is None: - return None - else: - return int(from_attribute) + return to_int(lane_link.get("from")) def get_to_attribute_from_lane_link(lane_link: etree._Element) -> Union[int, None]: - to_attribute = lane_link.get("to") - if to_attribute is None: - return None - else: - return int(to_attribute) + return to_int(lane_link.get("to")) def get_length_from_geometry(geometry: etree._ElementTree) -> Union[None, float]: - length = geometry.get("length") - if length is None: - return None + return to_float(geometry.get("length")) + + +def is_valid_param_poly3(param_poly3: models.ParamPoly3) -> bool: + if any( + value is None + for value in [ + param_poly3.u.a, + param_poly3.u.b, + param_poly3.u.c, + param_poly3.u.d, + param_poly3.v.a, + param_poly3.v.b, + param_poly3.v.c, + param_poly3.v.d, + ] + ): + return False else: - return float(length) + return True def get_normalized_param_poly3_from_geometry( @@ -415,22 +417,27 @@ def get_normalized_param_poly3_from_geometry( if param_poly3.get("pRange") != models.ParamPoly3Range.NORMALIZED: return None - return models.ParamPoly3( + parsed_result = models.ParamPoly3( u=models.Poly3( - a=float(param_poly3.get("aU")), - b=float(param_poly3.get("bU")), - c=float(param_poly3.get("cU")), - d=float(param_poly3.get("dU")), + a=to_float(param_poly3.get("aU")), + b=to_float(param_poly3.get("bU")), + c=to_float(param_poly3.get("cU")), + d=to_float(param_poly3.get("dU")), ), v=models.Poly3( - a=float(param_poly3.get("aV")), - b=float(param_poly3.get("bV")), - c=float(param_poly3.get("cV")), - d=float(param_poly3.get("dV")), + a=to_float(param_poly3.get("aV")), + b=to_float(param_poly3.get("bV")), + c=to_float(param_poly3.get("cV")), + d=to_float(param_poly3.get("dV")), ), range=models.ParamPoly3Range.NORMALIZED, ) + if is_valid_param_poly3(parsed_result): + return parsed_result + else: + return None + def poly3_to_polynomial(poly3: models.Poly3) -> np.polynomial.Polynomial: return np.polynomial.Polynomial([poly3.a, poly3.b, poly3.c, poly3.d]) @@ -450,22 +457,27 @@ def get_arclen_param_poly3_from_geometry( if param_poly3.get("pRange") != models.ParamPoly3Range.ARC_LENGTH: return None - return models.ParamPoly3( + parsed_result = models.ParamPoly3( u=models.Poly3( - a=float(param_poly3.get("aU")), - b=float(param_poly3.get("bU")), - c=float(param_poly3.get("cU")), - d=float(param_poly3.get("dU")), + a=to_float(param_poly3.get("aU")), + b=to_float(param_poly3.get("bU")), + c=to_float(param_poly3.get("cU")), + d=to_float(param_poly3.get("dU")), ), v=models.Poly3( - a=float(param_poly3.get("aV")), - b=float(param_poly3.get("bV")), - c=float(param_poly3.get("cV")), - d=float(param_poly3.get("dV")), + a=to_float(param_poly3.get("aV")), + b=to_float(param_poly3.get("bV")), + c=to_float(param_poly3.get("cV")), + d=to_float(param_poly3.get("dV")), ), range=models.ParamPoly3Range.ARC_LENGTH, ) + if is_valid_param_poly3(parsed_result): + return parsed_result + else: + return None + def arc_length_integrand( t: float, du: np.polynomial.Polynomial, dv: np.polynomial.Polynomial @@ -578,19 +590,40 @@ def get_connecting_lane_ids( return [] +def is_valid_offset_poly3(offset_poly3: models.OffsetPoly3) -> bool: + if any( + value is None + for value in [ + offset_poly3.poly3.a, + offset_poly3.poly3.b, + offset_poly3.poly3.c, + offset_poly3.poly3.d, + offset_poly3.s_offset, + ] + ): + return False + else: + return True + + def get_poly3_from_width( width: etree._ElementTree, ) -> models.OffsetPoly3: - return models.OffsetPoly3( + offset_poly3 = models.OffsetPoly3( poly3=models.Poly3( - a=float(width.get("a")), - b=float(width.get("b")), - c=float(width.get("c")), - d=float(width.get("d")), + a=to_float(width.get("a")), + b=to_float(width.get("b")), + c=to_float(width.get("c")), + d=to_float(width.get("d")), ), - s_offset=float(width.get("sOffset")), + s_offset=to_float(width.get("sOffset")), ) + if is_valid_offset_poly3(offset_poly3): + return offset_poly3 + else: + return None + def get_lane_width_poly3_list(lane: etree._Element) -> List[models.OffsetPoly3]: width_poly3 = [] @@ -742,37 +775,29 @@ def get_traffic_hand_rule_from_road(road: etree._Element) -> models.TrafficHandR def get_road_length(road: etree._ElementTree) -> Union[None, float]: - length = road.get("length") - if length is None: - return None - else: - return float(length) + return to_float(road.get("length")) def get_s_from_lane_section( lane_section: etree._ElementTree, ) -> Union[None, float]: - s_coordinate = lane_section.get("s") - if s_coordinate is None: - return None - else: - return float(s_coordinate) + return to_float(lane_section.get("s")) def get_borders_from_lane(lane: etree._ElementTree) -> List[models.OffsetPoly3]: border_list = [] for border in lane.iter("border"): - border_list.append( - models.OffsetPoly3( - models.Poly3( - a=float(border.get("a")), - b=float(border.get("b")), - c=float(border.get("c")), - d=float(border.get("d")), - ), - s_offset=float(border.get("sOffset")), - ) + offset_poly3 = models.OffsetPoly3( + models.Poly3( + a=to_float(border.get("a")), + b=to_float(border.get("b")), + c=to_float(border.get("c")), + d=to_float(border.get("d")), + ), + s_offset=to_float(border.get("sOffset")), ) + if is_valid_offset_poly3(offset_poly3): + border_list.append(offset_poly3) return border_list @@ -835,18 +860,19 @@ def get_road_elevations(road: etree._ElementTree) -> List[models.OffsetPoly3]: elevation_list = [] for elevation in elevation_profile.iter("elevation"): - elevation_list.append( - models.OffsetPoly3( - models.Poly3( - a=float(elevation.get("a")), - b=float(elevation.get("b")), - c=float(elevation.get("c")), - d=float(elevation.get("d")), - ), - s_offset=float(elevation.get("s")), - ) + offset_poly3 = models.OffsetPoly3( + models.Poly3( + a=to_float(elevation.get("a")), + b=to_float(elevation.get("b")), + c=to_float(elevation.get("c")), + d=to_float(elevation.get("d")), + ), + s_offset=to_float(elevation.get("s")), ) + if is_valid_offset_poly3(offset_poly3): + elevation_list.append(offset_poly3) + return elevation_list @@ -858,18 +884,19 @@ def get_road_superelevations(road: etree._ElementTree) -> List[models.OffsetPoly superelevation_list = [] for superelevation in lateral_profile.iter("superelevation"): - superelevation_list.append( - models.OffsetPoly3( - models.Poly3( - a=float(superelevation.get("a")), - b=float(superelevation.get("b")), - c=float(superelevation.get("c")), - d=float(superelevation.get("d")), - ), - s_offset=float(superelevation.get("s")), - ) + offset_poly3 = models.OffsetPoly3( + models.Poly3( + a=to_float(superelevation.get("a")), + b=to_float(superelevation.get("b")), + c=to_float(superelevation.get("c")), + d=to_float(superelevation.get("d")), + ), + s_offset=to_float(superelevation.get("s")), ) + if is_valid_offset_poly3(offset_poly3): + superelevation_list.append(offset_poly3) + return superelevation_list @@ -881,18 +908,19 @@ def get_lane_offsets_from_road(road: etree._ElementTree) -> List[models.OffsetPo lane_offset_list = [] for lane_offset in lanes.iter("laneOffset"): - lane_offset_list.append( - models.OffsetPoly3( - models.Poly3( - a=float(lane_offset.get("a")), - b=float(lane_offset.get("b")), - c=float(lane_offset.get("c")), - d=float(lane_offset.get("d")), - ), - s_offset=float(lane_offset.get("s")), - ) + offset_poly3 = models.OffsetPoly3( + models.Poly3( + a=to_float(lane_offset.get("a")), + b=to_float(lane_offset.get("b")), + c=to_float(lane_offset.get("c")), + d=to_float(lane_offset.get("d")), + ), + s_offset=to_float(lane_offset.get("s")), ) + if is_valid_offset_poly3(offset_poly3): + lane_offset_list.append(offset_poly3) + return lane_offset_list @@ -977,36 +1005,19 @@ def get_lane_direction(lane: etree._Element) -> Union[models.LaneDirection, None def get_heading_from_geometry(geometry: etree._ElementTree) -> Union[None, float]: - heading = geometry.get("hdg") - - if heading is None: - return None - - return float(heading) + return to_float(geometry.get("hdg")) def get_s_from_geometry(geometry: etree._ElementTree) -> Union[None, float]: - s = geometry.get("s") - if s is None: - return None - else: - return float(s) + return to_float(geometry.get("s")) def get_x_from_geometry(geometry: etree._ElementTree) -> Union[None, float]: - x = geometry.get("x") - if x is None: - return None - else: - return float(x) + return to_float(geometry.get("x")) def get_y_from_geometry(geometry: etree._ElementTree) -> Union[None, float]: - y = geometry.get("y") - if y is None: - return None - else: - return float(y) + return to_float(geometry.get("y")) def get_geometry_from_road_by_s( @@ -1051,11 +1062,7 @@ def calculate_line_point( def get_curvature_from_arc(arc: etree._Element) -> Union[None, float]: - curvature = arc.get("curvature") - if curvature is None: - return None - else: - return float(curvature) + return to_float(arc.get("curvature")) def calculate_arc_point( @@ -1078,19 +1085,11 @@ def calculate_arc_point( def get_curv_start_from_spiral(spiral: etree._Element) -> Union[None, float]: - curvStart = spiral.get("curvStart") - if curvStart is None: - return None - else: - return float(curvStart) + return to_float(spiral.get("curvStart")) def get_curv_end_from_spiral(spiral: etree._Element) -> Union[None, float]: - curvEnd = spiral.get("curvEnd") - if curvEnd is None: - return None - else: - return float(curvEnd) + return to_float(spiral.get("curvEnd")) def calculate_spiral_point( @@ -1308,20 +1307,11 @@ def get_middle_point_xyz_from_road_reference_line( def get_junction_id(junction: etree._ElementTree) -> Union[None, int]: - id = junction.get("id") - if id is None: - return None - else: - return int(id) + return to_int(junction.get("id")) def get_heading_from_geometry(geometry: etree._ElementTree) -> Union[None, float]: - heading = geometry.get("hdg") - - if heading is None: - return None - - return float(heading) + return to_float(geometry.get("hdg")) def calculate_arc_point_heading( @@ -1757,3 +1747,7 @@ def get_middle_point_xyz_at_height_zero_from_lane_by_s( else: point_xyz = get_point_xyz_from_road(road, s, t, 0.0) return point_xyz + + +def get_s_offset_from_access(access: etree._ElementTree) -> Union[None, float]: + return to_float(access.get("sOffset")) diff --git a/qc_opendrive/checks/basic/version_is_defined.py b/qc_opendrive/checks/basic/version_is_defined.py index ed632c6..63fa624 100644 --- a/qc_opendrive/checks/basic/version_is_defined.py +++ b/qc_opendrive/checks/basic/version_is_defined.py @@ -3,15 +3,17 @@ from qc_baselib import IssueSeverity, Result from qc_opendrive import constants from qc_opendrive.checks.basic import basic_constants +from qc_opendrive.base import utils def is_unsigned_short(value: int) -> bool: """Helper function to check if a value is within the xsd:unsignedShort range (0-65535).""" - try: - num = int(value) - return 0 <= num <= 65535 - except ValueError: + num = utils.to_int(value) + + if num is None: return False + else: + return 0 <= num <= 65535 def check_rule(tree: etree._ElementTree, result: Result) -> bool: diff --git a/qc_opendrive/checks/schema/valid_schema.py b/qc_opendrive/checks/schema/valid_schema.py index dc43167..15c2e2c 100644 --- a/qc_opendrive/checks/schema/valid_schema.py +++ b/qc_opendrive/checks/schema/valid_schema.py @@ -65,10 +65,13 @@ def _is_schema_compliant( """ split_result = schema_version.split(".") - major = int(split_result[0]) - minor = int(split_result[1]) + major = utils.to_int(split_result[0]) + minor = utils.to_int(split_result[1]) errors = [] + if major is None or minor is None: + return False, errors + # use LXML for XSD 1.0 with better error level -> OpenDRIVE 1.7 and lower if major <= 1 and minor <= 7: schema = etree.XMLSchema(etree.parse(schema_file)) diff --git a/qc_opendrive/checks/semantic/junctions_connection_connect_road_no_incoming_road.py b/qc_opendrive/checks/semantic/junctions_connection_connect_road_no_incoming_road.py index 806b40b..01f60ef 100644 --- a/qc_opendrive/checks/semantic/junctions_connection_connect_road_no_incoming_road.py +++ b/qc_opendrive/checks/semantic/junctions_connection_connect_road_no_incoming_road.py @@ -23,7 +23,12 @@ def _check_junctions_connection_connect_road_no_incoming_road( for connection in connections: incoming_road_id = utils.get_incoming_road_id_from_connection(connection) - incoming_road = road_id_map[incoming_road_id] + if incoming_road_id is None: + continue + + incoming_road = road_id_map.get(incoming_road_id) + if incoming_road is None: + continue if utils.road_belongs_to_junction(incoming_road): issue_id = checker_data.result.register_issue( diff --git a/qc_opendrive/checks/semantic/road_lane_access_no_mix_of_deny_or_allow.py b/qc_opendrive/checks/semantic/road_lane_access_no_mix_of_deny_or_allow.py index 49acba1..6b893d0 100644 --- a/qc_opendrive/checks/semantic/road_lane_access_no_mix_of_deny_or_allow.py +++ b/qc_opendrive/checks/semantic/road_lane_access_no_mix_of_deny_or_allow.py @@ -65,66 +65,68 @@ def check_rule(checker_data: models.CheckerData) -> None: access: etree._Element for access in lane.iter("access"): - access_attr = access.attrib - - if "rule" in access_attr: - s_offset = float(access_attr["sOffset"]) - rule = access_attr["rule"] - - for s_offset_info in access_s_offset_info: - if ( - abs(s_offset_info.s_offset - s_offset) <= 1e-6 - and rule != s_offset_info.rule - ): - issue_id = checker_data.result.register_issue( - checker_bundle_name=constants.BUNDLE_NAME, - checker_id=semantic_constants.CHECKER_ID, - description="At a given s-position, either only deny or only allow values shall be given, not mixed.", - level=IssueSeverity.ERROR, - rule_uid=rule_uid, - ) + rule = access.get("rule") + if rule is None: + continue + + s_offset = utils.get_s_offset_from_access(access) + if s_offset is None: + continue + + for s_offset_info in access_s_offset_info: + if ( + abs(s_offset_info.s_offset - s_offset) <= 1e-6 + and rule != s_offset_info.rule + ): + issue_id = checker_data.result.register_issue( + checker_bundle_name=constants.BUNDLE_NAME, + checker_id=semantic_constants.CHECKER_ID, + description="At a given s-position, either only deny or only allow values shall be given, not mixed.", + level=IssueSeverity.ERROR, + rule_uid=rule_uid, + ) - path = checker_data.input_file_xml_root.getpath(access) + path = checker_data.input_file_xml_root.getpath(access) - previous_rule = s_offset_info.rule - current_rule = access_attr["rule"] + previous_rule = s_offset_info.rule + current_rule = rule - checker_data.result.add_xml_location( - checker_bundle_name=constants.BUNDLE_NAME, - checker_id=semantic_constants.CHECKER_ID, - issue_id=issue_id, - xpath=path, - description=f"First encounter of {current_rule} having {previous_rule} before.", - ) + checker_data.result.add_xml_location( + checker_bundle_name=constants.BUNDLE_NAME, + checker_id=semantic_constants.CHECKER_ID, + issue_id=issue_id, + xpath=path, + description=f"First encounter of {current_rule} having {previous_rule} before.", + ) - if s_section is None: - continue + if s_section is None: + continue - s = s_section + s_offset + (length - s_offset) / 2.0 - t = utils.get_t_middle_point_from_lane_by_s( - road, lane_section, lane, s - ) + s = s_section + s_offset + (length - s_offset) / 2.0 + t = utils.get_t_middle_point_from_lane_by_s( + road, lane_section, lane, s + ) - if t is None: - continue + if t is None: + continue - inertial_point = utils.get_point_xyz_from_road( - road, s, t, 0.0 - ) - if inertial_point is not None: - checker_data.result.add_inertial_location( - checker_bundle_name=constants.BUNDLE_NAME, - checker_id=semantic_constants.CHECKER_ID, - issue_id=issue_id, - x=inertial_point.x, - y=inertial_point.y, - z=inertial_point.z, - description="Mixed access point.", - ) - - access_s_offset_info.append( - SOffsetInfo( - s_offset=float(access_attr["sOffset"]), - rule=access_attr["rule"], + inertial_point = utils.get_point_xyz_from_road( + road, s, t, 0.0 ) + if inertial_point is not None: + checker_data.result.add_inertial_location( + checker_bundle_name=constants.BUNDLE_NAME, + checker_id=semantic_constants.CHECKER_ID, + issue_id=issue_id, + x=inertial_point.x, + y=inertial_point.y, + z=inertial_point.z, + description="Mixed access point.", + ) + + access_s_offset_info.append( + SOffsetInfo( + s_offset=s_offset, + rule=rule, ) + ) diff --git a/qc_opendrive/checks/semantic/road_lane_level_true_one_side.py b/qc_opendrive/checks/semantic/road_lane_level_true_one_side.py index 7263aef..de82cbb 100644 --- a/qc_opendrive/checks/semantic/road_lane_level_true_one_side.py +++ b/qc_opendrive/checks/semantic/road_lane_level_true_one_side.py @@ -33,58 +33,53 @@ def _check_true_level_on_side( found_true_level = False for index, lane in enumerate(side_lanes): - lane_attrib = lane.attrib + lane_level = utils.get_lane_level_from_lane(lane) - if "level" in lane_attrib: - lane_level = utils.xml_string_to_bool(lane_attrib["level"]) + if lane_level == True: + found_true_level = True + + elif lane_level == False and found_true_level == True: + # lane_level is False when previous lane_level was True before + issue_id = result.register_issue( + checker_bundle_name=constants.BUNDLE_NAME, + checker_id=semantic_constants.CHECKER_ID, + description="Lane level False encountered on same side after True set.", + level=IssueSeverity.ERROR, + rule_uid=rule_uid, + ) - if lane_level == True: - found_true_level = True + path = root.getpath(lane) - elif lane_level == False and found_true_level == True: - # lane_level is False when previous lane_level was True before - issue_id = result.register_issue( - checker_bundle_name=constants.BUNDLE_NAME, - checker_id=semantic_constants.CHECKER_ID, - description="Lane level False encountered on same side after True set.", - level=IssueSeverity.ERROR, - rule_uid=rule_uid, - ) + result.add_xml_location( + checker_bundle_name=constants.BUNDLE_NAME, + checker_id=semantic_constants.CHECKER_ID, + issue_id=issue_id, + xpath=path, + description=f"Lane id {index} @level=False where previous lane id @level=True.", + ) - path = root.getpath(lane) + s_section = utils.get_s_from_lane_section( + lane_section_with_length.lane_section + ) + if s_section is None: + continue - result.add_xml_location( + s = s_section + lane_section_with_length.length / 2.0 + + inertial_point = utils.get_middle_point_xyz_at_height_zero_from_lane_by_s( + road, lane_section_with_length.lane_section, lane, s + ) + if inertial_point is not None: + result.add_inertial_location( checker_bundle_name=constants.BUNDLE_NAME, checker_id=semantic_constants.CHECKER_ID, issue_id=issue_id, - xpath=path, - description=f"Lane id {index} @level=False where previous lane id @level=True.", + x=inertial_point.x, + y=inertial_point.y, + z=inertial_point.z, + description="Lane level false when previous lane level is True.", ) - s_section = utils.get_s_from_lane_section( - lane_section_with_length.lane_section - ) - if s_section is None: - continue - - s = s_section + lane_section_with_length.length / 2.0 - - inertial_point = ( - utils.get_middle_point_xyz_at_height_zero_from_lane_by_s( - road, lane_section_with_length.lane_section, lane, s - ) - ) - if inertial_point is not None: - result.add_inertial_location( - checker_bundle_name=constants.BUNDLE_NAME, - checker_id=semantic_constants.CHECKER_ID, - issue_id=issue_id, - x=inertial_point.x, - y=inertial_point.y, - z=inertial_point.z, - description="Lane level false when previous lane level is True.", - ) - def _get_linkage_level_warnings( root: etree._ElementTree, @@ -99,10 +94,9 @@ def _get_linkage_level_warnings( for link in lane.findall("link"): for linkage in link.findall(linkage_tag): - linkage_id = linkage.get("id") + linkage_id = utils.to_int(linkage.get("id")) if linkage_id is None: continue - linkage_id = int(linkage_id) linkage_lane = utils.get_lane_from_lane_section( target_lane_section, linkage_id ) @@ -318,10 +312,18 @@ def _check_level_in_lane_section( left_lanes_list = utils.get_left_lanes_from_lane_section(lane_section) right_lanes_list = utils.get_right_lanes_from_lane_section(lane_section) + left_lanes_list = [ + lane for lane in left_lanes_list if utils.get_lane_id(lane) is not None + ] + + right_lanes_list = [ + lane for lane in right_lanes_list if utils.get_lane_id(lane) is not None + ] + # sort by lane id to guarantee order while checking level # left ids goes monotonic increasing from 1 sorted_left_lane = sorted( - left_lanes_list, key=lambda lane: int(lane.attrib["id"]) + left_lanes_list, key=lambda lane: int(utils.get_lane_id(lane)) ) _check_true_level_on_side( @@ -336,7 +338,7 @@ def _check_level_in_lane_section( # sort by lane abs(id) to guarantee order while checking level # right ids goes monotonic decreasing from -1 sorted_right_lane = sorted( - right_lanes_list, key=lambda lane: abs(int(lane.attrib["id"])) + right_lanes_list, key=lambda lane: abs(utils.get_lane_id(lane)) ) _check_true_level_on_side(