diff --git a/qc_opendrive/base/models.py b/qc_opendrive/base/models.py index 9dc81bf..28f8776 100644 --- a/qc_opendrive/base/models.py +++ b/qc_opendrive/base/models.py @@ -1,7 +1,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 @@ -78,6 +78,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 6394a39..0aa04b0 100644 --- a/qc_opendrive/base/utils.py +++ b/qc_opendrive/base/utils.py @@ -617,6 +617,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): @@ -795,6 +796,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) @@ -868,6 +870,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): @@ -892,6 +895,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): @@ -916,6 +920,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 d2403cb..e69a8b1 100644 --- a/qc_opendrive/checks/geometry/road_geometry_parampoly3_arclength_range.py +++ b/qc_opendrive/checks/geometry/road_geometry_parampoly3_arclength_range.py @@ -57,7 +57,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 fd3dfb4..015bc62 100644 --- a/qc_opendrive/checks/geometry/road_geometry_parampoly3_length_match.py +++ b/qc_opendrive/checks/geometry/road_geometry_parampoly3_length_match.py @@ -56,7 +56,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 5b9fa18..48bc882 100644 --- a/qc_opendrive/checks/geometry/road_geometry_parampoly3_normalized_range.py +++ b/qc_opendrive/checks/geometry/road_geometry_parampoly3_normalized_range.py @@ -56,7 +56,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 4fd189e..a670445 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 @@ -123,7 +123,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( @@ -131,7 +131,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 217d33f..a80ce66 100644 --- a/qc_opendrive/checks/performance/performance_avoid_redundant_info.py +++ b/qc_opendrive/checks/performance/performance_avoid_redundant_info.py @@ -27,7 +27,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, ) @@ -36,8 +36,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( @@ -51,7 +63,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.", ) @@ -66,7 +78,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, ) @@ -75,8 +87,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( @@ -90,7 +114,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.", ) @@ -105,7 +129,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, ) @@ -114,8 +138,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 @@ -133,7 +169,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.", ) @@ -159,7 +195,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, ) @@ -168,8 +204,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) @@ -203,7 +247,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, ) @@ -212,8 +256,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) @@ -235,7 +289,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.", ) @@ -253,7 +307,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, ) @@ -262,8 +316,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) @@ -285,7 +349,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 c8e56b1..754f51b 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 @@ -159,7 +159,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", ) @@ -223,7 +223,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 @@ -392,15 +400,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( @@ -408,7 +408,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 825914c..a073604 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 @@ -57,7 +57,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 56903aa..e73e0e0 100644 --- a/qc_opendrive/checks/semantic/road_linkage_is_junction_needed.py +++ b/qc_opendrive/checks/semantic/road_linkage_is_junction_needed.py @@ -36,7 +36,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 f11641f..7b8bc9f 100644 --- a/tests/test_performance_checks.py +++ b/tests/test_performance_checks.py @@ -21,14 +21,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]", ], ), ( @@ -40,7 +42,8 @@ "superelevation_invalid", 1, [ - "/OpenDRIVE/road", + "/OpenDRIVE/road/lateralProfile/superelevation[2]", + "/OpenDRIVE/road/lateralProfile/superelevation[3]", ], ), ( @@ -52,14 +55,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]", ], ), ( @@ -71,7 +76,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]", ], ), ( @@ -83,7 +89,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]", ], ), ( @@ -95,7 +102,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 2dccd09..ce67ee8 100644 --- a/tests/test_semantic_checks.py +++ b/tests/test_semantic_checks.py @@ -274,7 +274,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",