diff --git a/qc_opendrive/base/models.py b/qc_opendrive/base/models.py index 1666375..9355cd2 100644 --- a/qc_opendrive/base/models.py +++ b/qc_opendrive/base/models.py @@ -7,7 +7,7 @@ from dataclasses import dataclass from enum import Enum from lxml import etree -from typing import Union, Optional +from typing import Optional from qc_baselib import Configuration, Result @@ -84,6 +84,7 @@ class LaneSectionWithLength: class OffsetPoly3: poly3: Poly3 s_offset: float + xml_element: Optional[etree._ElementTree] = None class LaneDirection(str, Enum): diff --git a/qc_opendrive/base/utils.py b/qc_opendrive/base/utils.py index e4f63c3..e580e16 100644 --- a/qc_opendrive/base/utils.py +++ b/qc_opendrive/base/utils.py @@ -623,6 +623,7 @@ def get_poly3_from_width( d=to_float(width.get("d")), ), s_offset=to_float(width.get("sOffset")), + xml_element=width, ) if is_valid_offset_poly3(offset_poly3): @@ -801,6 +802,7 @@ def get_borders_from_lane(lane: etree._ElementTree) -> List[models.OffsetPoly3]: d=to_float(border.get("d")), ), s_offset=to_float(border.get("sOffset")), + xml_element=border, ) if is_valid_offset_poly3(offset_poly3): border_list.append(offset_poly3) @@ -874,6 +876,7 @@ def get_road_elevations(road: etree._ElementTree) -> List[models.OffsetPoly3]: d=to_float(elevation.get("d")), ), s_offset=to_float(elevation.get("s")), + xml_element=elevation, ) if is_valid_offset_poly3(offset_poly3): @@ -898,6 +901,7 @@ def get_road_superelevations(road: etree._ElementTree) -> List[models.OffsetPoly d=to_float(superelevation.get("d")), ), s_offset=to_float(superelevation.get("s")), + xml_element=superelevation, ) if is_valid_offset_poly3(offset_poly3): @@ -922,6 +926,7 @@ def get_lane_offsets_from_road(road: etree._ElementTree) -> List[models.OffsetPo d=to_float(lane_offset.get("d")), ), s_offset=to_float(lane_offset.get("s")), + xml_element=lane_offset, ) if is_valid_offset_poly3(offset_poly3): diff --git a/qc_opendrive/checks/geometry/road_geometry_parampoly3_arclength_range.py b/qc_opendrive/checks/geometry/road_geometry_parampoly3_arclength_range.py index 45d1241..926da18 100644 --- a/qc_opendrive/checks/geometry/road_geometry_parampoly3_arclength_range.py +++ b/qc_opendrive/checks/geometry/road_geometry_parampoly3_arclength_range.py @@ -63,7 +63,7 @@ def _check_all_roads(checker_data: models.CheckerData) -> None: checker_id=CHECKER_ID, issue_id=issue_id, xpath=checker_data.input_file_xml_root.getpath(geometry), - description=f"", + description=f"Length does not match the actual curve length. The estimated absolute error from numerical integration is {estimated_error}", ) s_coordinate = utils.get_s_from_geometry(geometry) diff --git a/qc_opendrive/checks/geometry/road_geometry_parampoly3_length_match.py b/qc_opendrive/checks/geometry/road_geometry_parampoly3_length_match.py index 96a0c05..3473694 100644 --- a/qc_opendrive/checks/geometry/road_geometry_parampoly3_length_match.py +++ b/qc_opendrive/checks/geometry/road_geometry_parampoly3_length_match.py @@ -62,7 +62,7 @@ def _check_all_roads(checker_data: models.CheckerData) -> None: checker_id=CHECKER_ID, issue_id=issue_id, xpath=checker_data.input_file_xml_root.getpath(geometry), - description=f"", + description=f"Length does not match the actual curve length. The estimated absolute error from numerical integration is {estimated_error}", ) s_coordinate = utils.get_s_from_geometry(geometry) diff --git a/qc_opendrive/checks/geometry/road_geometry_parampoly3_normalized_range.py b/qc_opendrive/checks/geometry/road_geometry_parampoly3_normalized_range.py index 35299a5..82e3356 100644 --- a/qc_opendrive/checks/geometry/road_geometry_parampoly3_normalized_range.py +++ b/qc_opendrive/checks/geometry/road_geometry_parampoly3_normalized_range.py @@ -62,7 +62,7 @@ def _check_all_roads(checker_data: models.CheckerData) -> None: checker_id=CHECKER_ID, issue_id=issue_id, xpath=checker_data.input_file_xml_root.getpath(geometry), - description=f"", + description=f"Length does not match the actual curve length. The estimated absolute error from numerical integration is {estimated_error}", ) s_coordinate = utils.get_s_from_geometry(geometry) diff --git a/qc_opendrive/checks/geometry/road_lane_border_overlap_with_inner_lanes.py b/qc_opendrive/checks/geometry/road_lane_border_overlap_with_inner_lanes.py index 687ea82..724fdcf 100644 --- a/qc_opendrive/checks/geometry/road_lane_border_overlap_with_inner_lanes.py +++ b/qc_opendrive/checks/geometry/road_lane_border_overlap_with_inner_lanes.py @@ -129,7 +129,7 @@ def _raise_issue( checker_id=CHECKER_ID, issue_id=issue_id, xpath=checker_data.input_file_xml_root.getpath(left_lane), - description=f"", + description=f"Outer lane border intersects or stays within inner lane border.", ) checker_data.result.add_xml_location( @@ -137,7 +137,7 @@ def _raise_issue( checker_id=CHECKER_ID, issue_id=issue_id, xpath=checker_data.input_file_xml_root.getpath(right_lane), - description=f"", + description=f"Outer lane border intersects or stays within inner lane border.", ) s_section = utils.get_s_from_lane_section(lane_section_with_length.lane_section) diff --git a/qc_opendrive/checks/performance/performance_avoid_redundant_info.py b/qc_opendrive/checks/performance/performance_avoid_redundant_info.py index 776df34..04c18f2 100644 --- a/qc_opendrive/checks/performance/performance_avoid_redundant_info.py +++ b/qc_opendrive/checks/performance/performance_avoid_redundant_info.py @@ -33,7 +33,7 @@ def _check_road_superelevations( issue_id = checker_data.result.register_issue( checker_bundle_name=constants.BUNDLE_NAME, checker_id=CHECKER_ID, - description=f"Redudant superelevation declaration.", + description=f"Redundant superelevation declaration.", level=IssueSeverity.WARNING, rule_uid=RULE_UID, ) @@ -42,8 +42,20 @@ def _check_road_superelevations( checker_bundle_name=constants.BUNDLE_NAME, checker_id=CHECKER_ID, issue_id=issue_id, - xpath=checker_data.input_file_xml_root.getpath(road), - description=f"", + xpath=checker_data.input_file_xml_root.getpath( + current_superelevation.xml_element + ), + description=f"Redundant superelevation declaration.", + ) + + checker_data.result.add_xml_location( + checker_bundle_name=constants.BUNDLE_NAME, + checker_id=CHECKER_ID, + issue_id=issue_id, + xpath=checker_data.input_file_xml_root.getpath( + next_superelevation.xml_element + ), + description=f"Redundant superelevation declaration.", ) inertial_point = utils.get_point_xyz_from_road_reference_line( @@ -57,7 +69,7 @@ def _check_road_superelevations( x=inertial_point.x, y=inertial_point.y, z=inertial_point.z, - description="Redudant superelevation declaration.", + description="Redundant superelevation declaration.", ) @@ -72,7 +84,7 @@ def _check_road_elevations( issue_id = checker_data.result.register_issue( checker_bundle_name=constants.BUNDLE_NAME, checker_id=CHECKER_ID, - description=f"Redudant elevation declaration.", + description=f"Redundant elevation declaration.", level=IssueSeverity.WARNING, rule_uid=RULE_UID, ) @@ -81,8 +93,20 @@ def _check_road_elevations( checker_bundle_name=constants.BUNDLE_NAME, checker_id=CHECKER_ID, issue_id=issue_id, - xpath=checker_data.input_file_xml_root.getpath(road), - description=f"", + xpath=checker_data.input_file_xml_root.getpath( + current_elevation.xml_element + ), + description=f"Redundant elevation declaration.", + ) + + checker_data.result.add_xml_location( + checker_bundle_name=constants.BUNDLE_NAME, + checker_id=CHECKER_ID, + issue_id=issue_id, + xpath=checker_data.input_file_xml_root.getpath( + next_elevation.xml_element + ), + description=f"Redundant elevation declaration.", ) inertial_point = utils.get_point_xyz_from_road_reference_line( @@ -96,7 +120,7 @@ def _check_road_elevations( x=inertial_point.x, y=inertial_point.y, z=inertial_point.z, - description="Redudant elevation declaration.", + description="Redundant elevation declaration.", ) @@ -111,7 +135,7 @@ def _check_lane_offsets( issue_id = checker_data.result.register_issue( checker_bundle_name=constants.BUNDLE_NAME, checker_id=CHECKER_ID, - description=f"Redudant lane offset declaration.", + description=f"Redundant lane offset declaration.", level=IssueSeverity.WARNING, rule_uid=RULE_UID, ) @@ -120,8 +144,20 @@ def _check_lane_offsets( checker_bundle_name=constants.BUNDLE_NAME, checker_id=CHECKER_ID, issue_id=issue_id, - xpath=checker_data.input_file_xml_root.getpath(road), - description=f"", + xpath=checker_data.input_file_xml_root.getpath( + current_lane_offset.xml_element + ), + description=f"Redundant lane offset declaration.", + ) + + checker_data.result.add_xml_location( + checker_bundle_name=constants.BUNDLE_NAME, + checker_id=CHECKER_ID, + issue_id=issue_id, + xpath=checker_data.input_file_xml_root.getpath( + next_lane_offset.xml_element + ), + description=f"Redundant lane offset declaration.", ) s = next_lane_offset.s_offset @@ -139,7 +175,7 @@ def _check_lane_offsets( x=inertial_point.x, y=inertial_point.y, z=inertial_point.z, - description="Redudant lane offset declaration.", + description="Redundant lane offset declaration.", ) @@ -165,7 +201,7 @@ def _check_road_plan_view( issue_id = checker_data.result.register_issue( checker_bundle_name=constants.BUNDLE_NAME, checker_id=CHECKER_ID, - description=f"Redudant line geometry declaration.", + description=f"Redundant line geometry declaration.", level=IssueSeverity.WARNING, rule_uid=RULE_UID, ) @@ -174,8 +210,16 @@ def _check_road_plan_view( checker_bundle_name=constants.BUNDLE_NAME, checker_id=CHECKER_ID, issue_id=issue_id, - xpath=checker_data.input_file_xml_root.getpath(road), - description=f"", + xpath=checker_data.input_file_xml_root.getpath(current_geometry), + description=f"Redundant line geometry declaration.", + ) + + checker_data.result.add_xml_location( + checker_bundle_name=constants.BUNDLE_NAME, + checker_id=CHECKER_ID, + issue_id=issue_id, + xpath=checker_data.input_file_xml_root.getpath(next_geometry), + description=f"Redundant line geometry declaration.", ) s_offset = utils.get_s_from_geometry(next_geometry) @@ -209,7 +253,7 @@ def _check_lane_widths( issue_id = checker_data.result.register_issue( checker_bundle_name=constants.BUNDLE_NAME, checker_id=CHECKER_ID, - description=f"Redudant lane width declaration.", + description=f"Redundant lane width declaration.", level=IssueSeverity.WARNING, rule_uid=RULE_UID, ) @@ -218,8 +262,18 @@ def _check_lane_widths( checker_bundle_name=constants.BUNDLE_NAME, checker_id=CHECKER_ID, issue_id=issue_id, - xpath=checker_data.input_file_xml_root.getpath(lane), - description=f"", + xpath=checker_data.input_file_xml_root.getpath( + current_width.xml_element + ), + description=f"Redundant lane width declaration.", + ) + + checker_data.result.add_xml_location( + checker_bundle_name=constants.BUNDLE_NAME, + checker_id=CHECKER_ID, + issue_id=issue_id, + xpath=checker_data.input_file_xml_root.getpath(next_width.xml_element), + description=f"Redundant lane width declaration.", ) s_section = utils.get_s_from_lane_section(lane_section) @@ -241,7 +295,7 @@ def _check_lane_widths( x=inertial_point.x, y=inertial_point.y, z=inertial_point.z, - description="Redudant lane width declaration.", + description="Redundant lane width declaration.", ) @@ -259,7 +313,7 @@ def _check_lane_borders( issue_id = checker_data.result.register_issue( checker_bundle_name=constants.BUNDLE_NAME, checker_id=CHECKER_ID, - description=f"Redudant lane border declaration.", + description=f"Redundant lane border declaration.", level=IssueSeverity.WARNING, rule_uid=RULE_UID, ) @@ -268,8 +322,18 @@ def _check_lane_borders( checker_bundle_name=constants.BUNDLE_NAME, checker_id=CHECKER_ID, issue_id=issue_id, - xpath=checker_data.input_file_xml_root.getpath(lane), - description=f"", + xpath=checker_data.input_file_xml_root.getpath( + current_border.xml_element + ), + description=f"Redundant lane border declaration.", + ) + + checker_data.result.add_xml_location( + checker_bundle_name=constants.BUNDLE_NAME, + checker_id=CHECKER_ID, + issue_id=issue_id, + xpath=checker_data.input_file_xml_root.getpath(next_border.xml_element), + description=f"Redundant lane border declaration.", ) s_section = utils.get_s_from_lane_section(lane_section) @@ -291,7 +355,7 @@ def _check_lane_borders( x=inertial_point.x, y=inertial_point.y, z=inertial_point.z, - description="Redudant lane border declaration.", + description="Redundant lane border declaration.", ) 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 e73a31c..2ed329c 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 @@ -165,7 +165,7 @@ def _check_level_change_between_lane_sections( checker_id=CHECKER_ID, issue_id=issue_id, xpath=warning, - description="", + description="Lane levels are not the same in two consecutive lane sections", ) @@ -229,7 +229,15 @@ def _check_level_change_linkage_roads( checker_id=CHECKER_ID, issue_id=issue_id, xpath=root.getpath(lane), - description="", + description="Lane levels are not the same between two connected roads.", + ) + + result.add_xml_location( + checker_bundle_name=constants.BUNDLE_NAME, + checker_id=CHECKER_ID, + issue_id=issue_id, + xpath=root.getpath(other_lane), + description="Lane levels are not the same between two connected roads.", ) s = None @@ -398,15 +406,7 @@ def _check_level_among_junctions( checker_id=CHECKER_ID, issue_id=issue_id, xpath=checker_data.input_file_xml_root.getpath(incoming_lane), - description="", - ) - - issue_id = checker_data.result.register_issue( - checker_bundle_name=constants.BUNDLE_NAME, - checker_id=CHECKER_ID, - description="Lane levels are not the same between junction and incoming road.", - level=IssueSeverity.WARNING, - rule_uid=RULE_UID, + description="Lane levels are not the same between incoming road and junction.", ) checker_data.result.add_xml_location( @@ -414,7 +414,7 @@ def _check_level_among_junctions( checker_id=CHECKER_ID, issue_id=issue_id, xpath=checker_data.input_file_xml_root.getpath(connection_lane), - description="", + description="Lane levels are not the same between incoming road and junction.", ) diff --git a/qc_opendrive/checks/semantic/road_lane_link_lanes_across_lane_sections.py b/qc_opendrive/checks/semantic/road_lane_link_lanes_across_lane_sections.py index 561571b..6c99663 100644 --- a/qc_opendrive/checks/semantic/road_lane_link_lanes_across_lane_sections.py +++ b/qc_opendrive/checks/semantic/road_lane_link_lanes_across_lane_sections.py @@ -63,7 +63,7 @@ def _check_two_lane_sections_one_direction( checker_id=CHECKER_ID, issue_id=issue_id, xpath=checker_data.input_file_xml_root.getpath(connecting_lane), - description="", + description="Missing lane link.", ) diff --git a/qc_opendrive/checks/semantic/road_linkage_is_junction_needed.py b/qc_opendrive/checks/semantic/road_linkage_is_junction_needed.py index 08bb3b1..50100e6 100644 --- a/qc_opendrive/checks/semantic/road_linkage_is_junction_needed.py +++ b/qc_opendrive/checks/semantic/road_linkage_is_junction_needed.py @@ -42,7 +42,7 @@ def _raise_road_linkage_is_junction_needed_issue( checker_id=CHECKER_ID, issue_id=issue_id, xpath=checker_data.input_file_xml_root.getpath(element), - description="", + description=f"Road cannot have ambiguous {linkage_tag.value}, a junction is needed.", ) if problematic_road is not None: diff --git a/tests/test_performance_checks.py b/tests/test_performance_checks.py index e731cdf..05ac301 100644 --- a/tests/test_performance_checks.py +++ b/tests/test_performance_checks.py @@ -27,14 +27,16 @@ "elevation_invalid_1", 1, [ - "/OpenDRIVE/road", + "/OpenDRIVE/road/elevationProfile/elevation[1]", + "/OpenDRIVE/road/elevationProfile/elevation[2]", ], ), ( "elevation_invalid_2", 1, [ - "/OpenDRIVE/road", + "/OpenDRIVE/road/elevationProfile/elevation[1]", + "/OpenDRIVE/road/elevationProfile/elevation[2]", ], ), ( @@ -46,7 +48,8 @@ "superelevation_invalid", 1, [ - "/OpenDRIVE/road", + "/OpenDRIVE/road/lateralProfile/superelevation[2]", + "/OpenDRIVE/road/lateralProfile/superelevation[3]", ], ), ( @@ -58,14 +61,16 @@ "lane_offset_invalid_1", 1, [ - "/OpenDRIVE/road", + "/OpenDRIVE/road/lanes/laneOffset[2]", + "/OpenDRIVE/road/lanes/laneOffset[3]", ], ), ( "lane_offset_invalid_2", 1, [ - "/OpenDRIVE/road", + "/OpenDRIVE/road/lanes/laneOffset[2]", + "/OpenDRIVE/road/lanes/laneOffset[3]", ], ), ( @@ -77,7 +82,8 @@ "lane_width_invalid", 1, [ - "/OpenDRIVE/road/lanes/laneSection[1]/left/lane[7]", + "/OpenDRIVE/road/lanes/laneSection[1]/left/lane[7]/width[1]", + "/OpenDRIVE/road/lanes/laneSection[1]/left/lane[7]/width[2]", ], ), ( @@ -89,7 +95,8 @@ "lane_border_invalid", 1, [ - "/OpenDRIVE/road/lanes/laneSection[2]/right/lane[2]", + "/OpenDRIVE/road/lanes/laneSection[2]/right/lane[2]/border[1]", + "/OpenDRIVE/road/lanes/laneSection[2]/right/lane[2]/border[2]", ], ), ( @@ -101,7 +108,8 @@ "line_geometry_invalid", 1, [ - "/OpenDRIVE/road", + "/OpenDRIVE/road/planView/geometry[2]", + "/OpenDRIVE/road/planView/geometry[3]", ], ), ], diff --git a/tests/test_semantic_checks.py b/tests/test_semantic_checks.py index 5cd6328..b7f0e5b 100644 --- a/tests/test_semantic_checks.py +++ b/tests/test_semantic_checks.py @@ -280,7 +280,7 @@ def test_road_lane_true_level_one_side_road( ("valid", 0, []), ( "invalid_incoming", - 3, # Two issues raised in junction, one issue raised in road + 2, # One issue raised in junction, one issue raised in road [ "/OpenDRIVE/road[1]/lanes/laneSection/right/lane", "/OpenDRIVE/road[2]/lanes/laneSection/right/lane",