diff --git a/CHANGES.rst b/CHANGES.rst index 7cced2a8..cbed748b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -13,6 +13,11 @@ Changelog of threedi-modelchecker - Collect all foreign key checks and give them a uniform error or warning (0001) - Add unique check for boundary_condition_1d.connection_node_id - Add checks for dry_weather_flow_distribution.distribution format, length and sum +- Check if geometries for orifice, weir and pipe match their connection nodes +- Check if geometries for control_measure_map, dry_weather_flow_map, surface_map and pump_map match the object they connect +- Check if windshielding geometry matches with that of the linked channel +- Check if the geometry of boundary_condition_1d, control_measure_location, lateral_1d, and pump matches with that of the linked connection node +- Check if the geometry of memory_control or table_control matches to that of the linked object 2.14.1 (2024-11-25) diff --git a/threedi_modelchecker/checks/location.py b/threedi_modelchecker/checks/location.py new file mode 100644 index 00000000..65709c6d --- /dev/null +++ b/threedi_modelchecker/checks/location.py @@ -0,0 +1,166 @@ +from typing import List, NamedTuple + +from sqlalchemy import func +from sqlalchemy.orm import aliased, Session +from threedi_schema.domain import models + +from threedi_modelchecker.checks.base import BaseCheck +from threedi_modelchecker.checks.geo_query import distance + + +class PointLocationCheck(BaseCheck): + """Check if cross section locations are within {max_distance} of their channel.""" + + def __init__( + self, + ref_column, + ref_table, + max_distance, + *args, + **kwargs, + ): + self.max_distance = max_distance + self.ref_column = ref_column + self.ref_table = ref_table + super().__init__(*args, **kwargs) + + def get_invalid(self, session): + # get all channels with more than 1 cross section location + return ( + self.to_check(session) + .join( + self.ref_table, + self.ref_table.id == self.ref_column, + ) + .filter(distance(self.column, self.ref_table.geom) > self.max_distance) + .all() + ) + + def description(self): + return ( + f"{self.column_name} does not match the position of the object that " + f"{self.table.name}.{self.ref_column} refers to" + ) + + +class LinestringLocationCheck(BaseCheck): + """Check that linestring geometry starts / ends are close to their connection nodes + + This allows for reversing the geometries. threedi-gridbuilder will reverse the geometries if + that lowers the distance to the connection nodes. + """ + + def __init__( + self, + ref_column_start, + ref_column_end, + ref_table_start, + ref_table_end, + max_distance, + *args, + **kwargs, + ): + self.max_distance = max_distance + self.ref_column_start = ref_column_start + self.ref_column_end = ref_column_end + self.ref_table_start = ref_table_start + self.ref_table_end = ref_table_end + super().__init__(*args, **kwargs) + + def get_invalid(self, session: Session) -> List[NamedTuple]: + start_node = aliased(self.ref_table_start) + end_node = aliased(self.ref_table_end) + + tol = self.max_distance + start_point = func.ST_PointN(self.column, 1) + end_point = func.ST_PointN(self.column, func.ST_NPoints(self.column)) + + start_ok = distance(start_point, start_node.geom) <= tol + end_ok = distance(end_point, end_node.geom) <= tol + start_ok_if_reversed = distance(end_point, start_node.geom) <= tol + end_ok_if_reversed = distance(start_point, end_node.geom) <= tol + return ( + self.to_check(session) + .join(start_node, start_node.id == self.ref_column_start) + .join(end_node, end_node.id == self.ref_column_end) + .filter( + ~(start_ok & end_ok), + ~(start_ok_if_reversed & end_ok_if_reversed), + ) + .all() + ) + + def description(self) -> str: + ref_start_name = f"{self.table.name}.{self.ref_column_start.name}" + ref_end_name = f"{self.table.name}.{self.ref_column_end.name}" + return f"{self.column_name} does not start or end at its connection nodes: {ref_start_name} and {ref_end_name} (tolerance = {self.max_distance} m)" + + +class ConnectionNodeLinestringLocationCheck(LinestringLocationCheck): + def __init__(self, column, *args, **kwargs): + table = column.table + super().__init__( + ref_column_start=table.c.connection_node_id_start, + ref_column_end=table.c.connection_node_id_end, + ref_table_start=models.ConnectionNode, + ref_table_end=models.ConnectionNode, + column=column, + *args, + **kwargs, + ) + + def description(self) -> str: + return f"{self.column_name} does not start or end at its connection node (tolerance = {self.max_distance} m)" + + +class ControlMeasureMapLinestringMapLocationCheck(LinestringLocationCheck): + def __init__(self, control_table, filters, *args, **kwargs): + super().__init__( + ref_column_start=models.ControlMeasureMap.measure_location_id, + ref_column_end=models.ControlMeasureMap.control_id, + ref_table_start=models.ControlMeasureLocation, + ref_table_end=control_table, + column=models.ControlMeasureMap.geom, + filters=filters, + *args, + **kwargs, + ) + + +class DWFMapLinestringLocationCheck(LinestringLocationCheck): + def __init__(self, *args, **kwargs): + super().__init__( + ref_column_start=models.DryWeatherFlowMap.connection_node_id, + ref_column_end=models.DryWeatherFlowMap.dry_weather_flow_id, + ref_table_start=models.ConnectionNode, + ref_table_end=models.DryWeatherFlow, + column=models.DryWeatherFlowMap.geom, + *args, + **kwargs, + ) + + +class PumpMapLinestringLocationCheck(LinestringLocationCheck): + def __init__(self, *args, **kwargs): + super().__init__( + ref_column_start=models.PumpMap.pump_id, + ref_column_end=models.PumpMap.connection_node_id_end, + ref_table_start=models.Pump, + ref_table_end=models.ConnectionNode, + column=models.PumpMap.geom, + *args, + **kwargs, + ) + + +class SurfaceMapLinestringLocationCheck(LinestringLocationCheck): + def __init__(self, *args, **kwargs): + super().__init__( + ref_column_start=models.SurfaceMap.surface_id, + ref_column_end=models.SurfaceMap.connection_node_id, + ref_table_start=models.Surface, + ref_table_end=models.ConnectionNode, + column=models.SurfaceMap.geom, + *args, + **kwargs, + ) diff --git a/threedi_modelchecker/checks/other.py b/threedi_modelchecker/checks/other.py index e2ed6186..bd99e9b8 100644 --- a/threedi_modelchecker/checks/other.py +++ b/threedi_modelchecker/checks/other.py @@ -54,35 +54,6 @@ def description(self) -> str: ) -class CrossSectionLocationCheck(BaseCheck): - """Check if cross section locations are within {max_distance} of their channel.""" - - def __init__(self, max_distance, *args, **kwargs): - super().__init__(column=models.CrossSectionLocation.geom, *args, **kwargs) - self.max_distance = max_distance - - def get_invalid(self, session): - # get all channels with more than 1 cross section location - return ( - self.to_check(session) - .join( - models.Channel, - models.Channel.id == models.CrossSectionLocation.channel_id, - ) - .filter( - distance(models.CrossSectionLocation.geom, models.Channel.geom) - > self.max_distance - ) - .all() - ) - - def description(self): - return ( - f"cross_section_location.geom is invalid: the cross-section location " - f"should be located on the channel geometry (tolerance = {self.max_distance} m)" - ) - - class CrossSectionSameConfigurationCheck(BaseCheck): """Check the cross-sections on the object are either all open or all closed.""" @@ -494,44 +465,6 @@ def description(self) -> str: ) -class LinestringLocationCheck(BaseCheck): - """Check that linestring geometry starts / ends are close to their connection nodes - - This allows for reversing the geometries. threedi-gridbuilder will reverse the geometries if - that lowers the distance to the connection nodes. - """ - - def __init__(self, *args, **kwargs): - self.max_distance = kwargs.pop("max_distance") - super().__init__(*args, **kwargs) - - def get_invalid(self, session: Session) -> List[NamedTuple]: - start_node = aliased(models.ConnectionNode) - end_node = aliased(models.ConnectionNode) - - tol = self.max_distance - start_point = func.ST_PointN(self.column, 1) - end_point = func.ST_PointN(self.column, func.ST_NPoints(self.column)) - - start_ok = distance(start_point, start_node.geom) <= tol - end_ok = distance(end_point, end_node.geom) <= tol - start_ok_if_reversed = distance(end_point, start_node.geom) <= tol - end_ok_if_reversed = distance(start_point, end_node.geom) <= tol - return ( - self.to_check(session) - .join(start_node, start_node.id == self.table.c.connection_node_id_start) - .join(end_node, end_node.id == self.table.c.connection_node_id_end) - .filter( - ~(start_ok & end_ok), - ~(start_ok_if_reversed & end_ok_if_reversed), - ) - .all() - ) - - def description(self) -> str: - return f"{self.column_name} does not start or end at its connection node (tolerance = {self.max_distance} m)" - - class BoundaryCondition1DObjectNumberCheck(BaseCheck): """Check that the number of connected objects to 1D boundary connections is 1.""" diff --git a/threedi_modelchecker/config.py b/threedi_modelchecker/config.py index 2e3c7a29..58887a4c 100644 --- a/threedi_modelchecker/config.py +++ b/threedi_modelchecker/config.py @@ -47,7 +47,15 @@ generate_type_checks, generate_unique_checks, ) -from .checks.other import ( # Use0DFlowCheck,,;,; ,; ,; ,,; , +from .checks.location import ( + ConnectionNodeLinestringLocationCheck, + ControlMeasureMapLinestringMapLocationCheck, + DWFMapLinestringLocationCheck, + PointLocationCheck, + PumpMapLinestringLocationCheck, + SurfaceMapLinestringLocationCheck, +) +from .checks.other import ( AllPresentVegetationParameters, BetaColumnsCheck, BetaValuesCheck, @@ -57,7 +65,6 @@ ConnectionNodesLength, ControlHasSingleMeasureVariable, CorrectAggregationSettingsExist, - CrossSectionLocationCheck, CrossSectionSameConfigurationCheck, DefinedAreaCheck, DWFDistributionCSVFormatCheck, @@ -65,7 +72,6 @@ DWFDistributionSumCheck, FeatureClosedCrossSectionCheck, InflowNoFeaturesCheck, - LinestringLocationCheck, MaxOneRecordCheck, NodeSurfaceConnectionsCheck, OpenChannelsWithNestedNewton, @@ -646,9 +652,6 @@ def is_none_or_empty(col): ] CHECKS += [ - CrossSectionLocationCheck( - level=CheckLevel.WARNING, max_distance=TOLERANCE_M, error_code=52 - ), QueryCheck( error_code=54, level=CheckLevel.WARNING, @@ -1094,10 +1097,110 @@ def is_none_or_empty(col): ) for table in [models.Orifice, models.Weir] ] + + CHECKS += [ - LinestringLocationCheck(error_code=205, column=table.geom, max_distance=1) - for table in [models.Channel, models.Culvert] + ConnectionNodeLinestringLocationCheck( + error_code=205, column=table.geom, max_distance=1 + ) + for table in [ + models.Channel, + models.Culvert, + models.Orifice, + models.Pipe, + models.Weir, + ] +] + +CHECKS += [ + ControlMeasureMapLinestringMapLocationCheck( + control_table=table, + filters=models.ControlMeasureMap.control_type == control_type, + max_distance=1, + error_code=205, + ) + for table, control_type in [ + (models.ControlTable, "table"), + (models.ControlMemory, "memory"), + ] +] + +CHECKS += [ + check(max_distance=1, error_code=205) + for check in [ + DWFMapLinestringLocationCheck, + SurfaceMapLinestringLocationCheck, + PumpMapLinestringLocationCheck, + ] +] + + +CHECKS += [ + PointLocationCheck( + level=CheckLevel.WARNING, + max_distance=TOLERANCE_M, + error_code=206, + column=models.CrossSectionLocation.geom, + ref_column=models.CrossSectionLocation.channel_id, + ref_table=models.Channel, + ), + PointLocationCheck( + max_distance=TOLERANCE_M, + error_code=206, + column=models.Windshielding.geom, + ref_column=models.Windshielding.channel_id, + ref_table=models.Channel, + ), ] + +CHECKS += [ + PointLocationCheck( + level=CheckLevel.WARNING, + max_distance=TOLERANCE_M, + error_code=206, + column=table.geom, + ref_column=table.connection_node_id, + ref_table=models.ConnectionNode, + ) + for table in [ + models.BoundaryCondition1D, + models.ControlMeasureLocation, + models.Lateral1d, + models.Pump, + ] +] + +control_tables = [models.ControlMemory, models.ControlTable] +ref_tables = [ + models.Pipe, + models.Orifice, + models.Culvert, + models.Weir, + models.Pump, +] + +CHECKS += [ + PointLocationCheck( + level=CheckLevel.WARNING, + max_distance=TOLERANCE_M, + error_code=206, + column=table.geom, + ref_column=table.target_id, + ref_table=ref_table, + filters=(table.target_type == ref_table.__tablename__), + ) + for table in [models.ControlMemory, models.ControlTable] + for ref_table in [ + models.Channel, + models.Pipe, + models.Orifice, + models.Culvert, + models.Weir, + models.Pump, + ] +] + + CHECKS += [ SpatialIndexCheck( error_code=207, column=models.ConnectionNode.geom, level=CheckLevel.WARNING diff --git a/threedi_modelchecker/tests/factories.py b/threedi_modelchecker/tests/factories.py index e12936e9..5086fbc3 100644 --- a/threedi_modelchecker/tests/factories.py +++ b/threedi_modelchecker/tests/factories.py @@ -100,6 +100,14 @@ class Meta: geom = "SRID=4326;POINT(-71.064544 42.28787)" +class PumpMapFactory(factory.alchemy.SQLAlchemyModelFactory): + class Meta: + model = models.PumpMap + sqlalchemy_session = None + + geom = "SRID=4326;POINT(-71.064544 42.28787)" + + class PumpFactory(factory.alchemy.SQLAlchemyModelFactory): class Meta: model = models.Pump @@ -182,6 +190,14 @@ class Meta: infiltration_recovery_constant = 2.0 +class DryWheatherFlowMapFactory(factory.alchemy.SQLAlchemyModelFactory): + class Meta: + model = models.DryWeatherFlowMap + sqlalchemy_session = None + + geom = "SRID=4326;POINT(-71.064544 42.28787)" + + class DryWeatherFlowFactory(factory.alchemy.SQLAlchemyModelFactory): class Meta: model = models.DryWeatherFlow diff --git a/threedi_modelchecker/tests/test_checks_location.py b/threedi_modelchecker/tests/test_checks_location.py new file mode 100644 index 00000000..af3c3572 --- /dev/null +++ b/threedi_modelchecker/tests/test_checks_location.py @@ -0,0 +1,207 @@ +import pytest +from threedi_schema.domain import models + +from threedi_modelchecker.checks.location import ( + ConnectionNodeLinestringLocationCheck, + ControlMeasureMapLinestringMapLocationCheck, + DWFMapLinestringLocationCheck, + LinestringLocationCheck, + PointLocationCheck, + PumpMapLinestringLocationCheck, + SurfaceMapLinestringLocationCheck, +) +from threedi_modelchecker.tests import factories + + +def test_point_location_check(session): + factories.ChannelFactory( + id=1, + geom="SRID=4326;LINESTRING(5.387204 52.155172, 5.387204 52.155262)", + ) + factories.CrossSectionLocationFactory( + channel_id=1, geom="SRID=4326;POINT(5.387204 52.155200)" + ) + factories.CrossSectionLocationFactory( + channel_id=1, geom="SRID=4326;POINT(5.387218 52.155244)" + ) + errors = PointLocationCheck( + column=models.CrossSectionLocation.geom, + ref_column=models.CrossSectionLocation.channel_id, + ref_table=models.Channel, + max_distance=0.1, + ).get_invalid(session) + assert len(errors) == 1 + + +@pytest.mark.parametrize( + "channel_geom, nof_invalid", + [ + ("LINESTRING(5.387204 52.155172, 5.387204 52.155262)", 0), + ("LINESTRING(5.387218 52.155172, 5.387218 52.155262)", 0), # within tolerance + ("LINESTRING(5.387204 52.155262, 5.387204 52.155172)", 0), # reversed + ( + "LINESTRING(5.387218 52.155262, 5.387218 52.155172)", + 0, + ), # reversed, within tolerance + ( + "LINESTRING(5.387204 52.164151, 5.387204 52.155262)", + 1, + ), # startpoint is wrong + ("LINESTRING(5.387204 52.155172, 5.387204 52.164151)", 1), # endpoint is wrong + ], +) +def test_linestring_location_check(session, channel_geom, nof_invalid): + factories.ConnectionNodeFactory(id=1, geom="SRID=4326;POINT(5.387204 52.155172)") + factories.ConnectionNodeFactory(id=2, geom="SRID=4326;POINT(5.387204 52.155262)") + factories.ChannelFactory( + connection_node_id_start=1, + connection_node_id_end=2, + geom=f"SRID=4326;{channel_geom}", + ) + errors = LinestringLocationCheck( + column=models.Channel.geom, + ref_column_start=models.Channel.connection_node_id_start, + ref_column_end=models.Channel.connection_node_id_end, + ref_table_start=models.ConnectionNode, + ref_table_end=models.ConnectionNode, + max_distance=1.01, + ).get_invalid(session) + assert len(errors) == nof_invalid + + +@pytest.mark.parametrize( + "channel_geom, nof_invalid", + [ + ("LINESTRING(5.387204 52.155172, 5.387204 52.155262)", 0), + ( + "LINESTRING(5.387204 52.164151, 5.387204 52.155262)", + 1, + ), # startpoint is wrong + ], +) +def test_connection_node_linestring_location_check(session, channel_geom, nof_invalid): + factories.ConnectionNodeFactory(id=1, geom="SRID=4326;POINT(5.387204 52.155172)") + factories.ConnectionNodeFactory(id=2, geom="SRID=4326;POINT(5.387204 52.155262)") + factories.ChannelFactory( + connection_node_id_start=1, + connection_node_id_end=2, + geom=f"SRID=4326;{channel_geom}", + ) + errors = ConnectionNodeLinestringLocationCheck( + column=models.Channel.geom, max_distance=1.01 + ).get_invalid(session) + assert len(errors) == nof_invalid + + +@pytest.mark.parametrize( + "channel_geom, nof_invalid", + [ + ("LINESTRING(5.387204 52.155172, 5.387204 52.155262)", 0), + ( + "LINESTRING(5.387204 52.164151, 5.387204 52.155262)", + 1, + ), # startpoint is wrong + ], +) +@pytest.mark.parametrize( + "control_table, control_type", + [(models.ControlMemory, "memory"), (models.ControlTable, "table")], +) +def test_control_measure_map_linestring_map_location_check( + session, control_table, control_type, channel_geom, nof_invalid +): + factories.ControlMeasureLocationFactory( + id=1, geom="SRID=4326;POINT(5.387204 52.155172)" + ) + factories.ControlMemoryFactory(id=1, geom="SRID=4326;POINT(5.387204 52.155262)") + factories.ControlTableFactory(id=1, geom="SRID=4326;POINT(5.387204 52.155262)") + factories.ControlMeasureMapFactory( + measure_location_id=1, + control_id=1, + control_type="memory", + geom=f"SRID=4326;{channel_geom}", + ) + factories.ControlMeasureMapFactory( + measure_location_id=1, + control_id=1, + control_type="table", + geom=f"SRID=4326;{channel_geom}", + ) + errors = ControlMeasureMapLinestringMapLocationCheck( + control_table=control_table, + filters=models.ControlMeasureMap.control_type == control_type, + max_distance=1.01, + ).get_invalid(session) + assert len(errors) == nof_invalid + + +@pytest.mark.parametrize( + "channel_geom, nof_invalid", + [ + ("LINESTRING(5.387204 52.155172, 5.387204 52.155262)", 0), + ( + "LINESTRING(5.387204 52.164151, 5.387204 52.155262)", + 1, + ), # startpoint is wrong + ], +) +def test_dwf_map_linestring_map_location_check(session, channel_geom, nof_invalid): + factories.ConnectionNodeFactory(id=1, geom="SRID=4326;POINT(5.387204 52.155172)") + factories.DryWeatherFlowFactory( + id=1, + geom="SRID=4326;POLYGON((5.387204 52.155262, 5.387204 52.155172, 5.387304 52.155172, 5.387304 52.155262, 5.387204 52.155262))", + ) + factories.DryWheatherFlowMapFactory( + connection_node_id=1, + dry_weather_flow_id=1, + geom=f"SRID=4326;{channel_geom}", + ) + errors = DWFMapLinestringLocationCheck(max_distance=1.01).get_invalid(session) + assert len(errors) == nof_invalid + + +@pytest.mark.parametrize( + "channel_geom, nof_invalid", + [ + ("LINESTRING(5.387204 52.155172, 5.387204 52.155262)", 0), + ( + "LINESTRING(5.387204 52.164151, 5.387204 52.155262)", + 1, + ), # startpoint is wrong + ], +) +def test_pump_map_linestring_map_location_check(session, channel_geom, nof_invalid): + factories.ConnectionNodeFactory(id=1, geom="SRID=4326;POINT(5.387204 52.155172)") + factories.PumpFactory(id=1, geom="SRID=4326;POINT(5.387204 52.155262)") + factories.PumpMapFactory( + connection_node_id_end=1, + pump_id=1, + geom=f"SRID=4326;{channel_geom}", + ) + errors = PumpMapLinestringLocationCheck(max_distance=1.01).get_invalid(session) + assert len(errors) == nof_invalid + + +@pytest.mark.parametrize( + "channel_geom, nof_invalid", + [ + ("LINESTRING(5.387204 52.155172, 5.387204 52.155262)", 0), + ( + "LINESTRING(5.387204 52.164151, 5.387204 52.155262)", + 1, + ), # startpoint is wrong + ], +) +def test_surface_map_linestring_map_location_check(session, channel_geom, nof_invalid): + factories.ConnectionNodeFactory(id=1, geom="SRID=4326;POINT(5.387204 52.155172)") + factories.SurfaceFactory( + id=1, + geom="SRID=4326;POLYGON((5.387204 52.155262, 5.387204 52.155172, 5.387304 52.155172, 5.387304 52.155262, 5.387204 52.155262))", + ) + factories.SurfaceMapFactory( + connection_node_id=1, + surface_id=1, + geom=f"SRID=4326;{channel_geom}", + ) + errors = SurfaceMapLinestringLocationCheck(max_distance=1.01).get_invalid(session) + assert len(errors) == nof_invalid diff --git a/threedi_modelchecker/tests/test_checks_other.py b/threedi_modelchecker/tests/test_checks_other.py index c30c2510..73aec95a 100644 --- a/threedi_modelchecker/tests/test_checks_other.py +++ b/threedi_modelchecker/tests/test_checks_other.py @@ -17,7 +17,6 @@ ControlTableActionTableCheckDefault, ControlTableActionTableCheckDischargeCoefficients, CorrectAggregationSettingsExist, - CrossSectionLocationCheck, CrossSectionSameConfigurationCheck, DefinedAreaCheck, DWFDistributionCSVFormatCheck, @@ -25,7 +24,6 @@ DWFDistributionSumCheck, FeatureClosedCrossSectionCheck, InflowNoFeaturesCheck, - LinestringLocationCheck, MaxOneRecordCheck, NodeSurfaceConnectionsCheck, OpenChannelsWithNestedNewton, @@ -263,67 +261,6 @@ def test_node_distance(session): assert con2_too_close.id in invalid_ids -@pytest.mark.parametrize( - "channel_geom", - [ - "LINESTRING(5.387204 52.155172, 5.387204 52.155262)", - "LINESTRING(5.387218 52.155172, 5.387218 52.155262)", # within tolerance - "LINESTRING(5.387204 52.155262, 5.387204 52.155172)", # reversed - "LINESTRING(5.387218 52.155262, 5.387218 52.155172)", # reversed, within tolerance - ], -) -def test_channels_location_check(session, channel_geom): - factories.ConnectionNodeFactory(id=1, geom="SRID=4326;POINT(5.387204 52.155172)") - factories.ConnectionNodeFactory(id=2, geom="SRID=4326;POINT(5.387204 52.155262)") - factories.ChannelFactory( - connection_node_id_start=1, - connection_node_id_end=2, - geom=f"SRID=4326;{channel_geom}", - ) - - errors = LinestringLocationCheck( - column=models.Channel.geom, max_distance=1.01 - ).get_invalid(session) - assert len(errors) == 0 - - -@pytest.mark.parametrize( - "channel_geom", - [ - "LINESTRING(5.387204 52.164151, 5.387204 52.155262)", # startpoint is wrong - "LINESTRING(5.387204 52.155172, 5.387204 52.164151)", # endpoint is wrong - ], -) -def test_channels_location_check_invalid(session, channel_geom): - factories.ConnectionNodeFactory(id=1, geom="SRID=4326;POINT(5.387204 52.155172)") - factories.ConnectionNodeFactory(id=2, geom="SRID=4326;POINT(5.387204 52.155262)") - factories.ChannelFactory( - connection_node_id_start=1, - connection_node_id_end=2, - geom=f"SRID=4326;{channel_geom}", - ) - - errors = LinestringLocationCheck( - column=models.Channel.geom, max_distance=1.01 - ).get_invalid(session) - assert len(errors) == 1 - - -def test_cross_section_location(session): - factories.ChannelFactory( - id=1, - geom="SRID=4326;LINESTRING(5.387204 52.155172, 5.387204 52.155262)", - ) - factories.CrossSectionLocationFactory( - channel_id=1, geom="SRID=4326;POINT(5.387204 52.155200)" - ) - factories.CrossSectionLocationFactory( - channel_id=1, geom="SRID=4326;POINT(5.387218 52.155244)" - ) - errors = CrossSectionLocationCheck(0.1).get_invalid(session) - assert len(errors) == 1 - - class TestCrossSectionSameConfiguration: @pytest.mark.parametrize("sep, expected", [(",", "1"), ("\n", "1,2")]) def test_get_first_in_str(self, session, sep, expected):