From cc1af3ff2dd5fd06a11cb16f1e5418c6ea13056a Mon Sep 17 00:00:00 2001 From: EllingOftedalKV Date: Tue, 13 Feb 2024 07:44:16 +0100 Subject: [PATCH 1/5] working on iteration for MST prooning logic --- file_manager/n100/file_manager_rivers.py | 43 ++++ .../n100/river/extend_river_line.py | 45 ++-- generalization/n100/river/mst_loop.py | 196 ++++++++++++++++++ .../n100/river/unconnected_river_geometry.py | 2 +- 4 files changed, 270 insertions(+), 16 deletions(-) create mode 100644 generalization/n100/river/mst_loop.py diff --git a/file_manager/n100/file_manager_rivers.py b/file_manager/n100/file_manager_rivers.py index d36f5743..39c1c312 100644 --- a/file_manager/n100/file_manager_rivers.py +++ b/file_manager/n100/file_manager_rivers.py @@ -93,6 +93,7 @@ def generate_file_name_lyrx( extending_river_geometry = "extending_river_geometry" river_centerline = "river_centerline" centerline_pruning = "centerline_pruning" +centerline_pruning_loop = "centerline_pruning_loop" ############################################################################################################################################## @@ -285,3 +286,45 @@ class River_N100(Enum): description="pruned_centerline", scale=scale, ) + + ################################################# + ########### RIVER CENTERLINE PROONING LOOP ########### + ################################################# + + centerline_pruning_loop__centerline_start_end_vertex__n100 = generate_file_name_gdb( + function_name=centerline_pruning_loop, + description="centerline_start_end_vertex", + scale=scale, + ) + + centerline_pruning_loop__centerline_intersection_vertex__n100 = ( + generate_file_name_gdb( + function_name=centerline_pruning_loop, + description="centerline_intersection_vertex", + scale=scale, + ) + ) + + centerline_pruning_loop__river_inlet_dangles__n100 = generate_file_name_gdb( + function_name=centerline_pruning_loop, + description="river_inlet_dangles", + scale=scale, + ) + + centerline_pruning_loop__water_feature_summarized__n100 = generate_file_name_gdb( + function_name=centerline_pruning_loop, + description="water_feature_summarized", + scale=scale, + ) + + centerline_pruning_loop__simple_water_features__n100 = generate_file_name_gdb( + function_name=centerline_pruning_loop, + description="simple_water_features", + scale=scale, + ) + + centerline_pruning_loop__complex_water_features__n100 = generate_file_name_gdb( + function_name=centerline_pruning_loop, + description="complex_water_features", + scale=scale, + ) diff --git a/generalization/n100/river/extend_river_line.py b/generalization/n100/river/extend_river_line.py index 89655aa9..8e992e91 100644 --- a/generalization/n100/river/extend_river_line.py +++ b/generalization/n100/river/extend_river_line.py @@ -183,8 +183,10 @@ def create_lines_from_coordinates(line_points, all_rivers): # Create a line from the start point to the end point line = arcpy.Polyline( - arcpy.Array([start_point, end_point]), - environment_setup.project_spatial_reference, + arcpy.Array( + [start_point, end_point], + ), + # spatial_reference=environment_setup.project_spatial_reference, ) new_lines.append(line) @@ -201,19 +203,26 @@ def process_new_lines(new_lines, all_rivers): """ # Create an in-memory feature class to hold the new lines new_lines_feature_class = River_N100.extending_river_geometry__new_lines__n100.value - arcpy.CopyFeatures_management(new_lines, new_lines_feature_class) - - # if arcpy.Exists(new_lines_feature_class): - # arcpy.management.Delete(new_lines_feature_class) - # - # arcpy.CreateFeatureclass_management( - # out_path=os.path.dirname(new_lines_feature_class), - # out_name=os.path.basename(new_lines_feature_class), - # geometry_type="POLYLINE", - # spatial_reference=environment_setup.project_spatial_reference, - # xytolerance="0.02 Meters", - # xy_resolution="0.01 Meters", - # ) + # arcpy.CopyFeatures_management(new_lines, new_lines_feature_class) + + # Check if the feature class already exists, and if so, delete it + if arcpy.Exists(new_lines_feature_class): + arcpy.management.Delete(new_lines_feature_class) + + sr = arcpy.Describe( + River_N100.unconnected_river_geometry__unsplit_river_features__n100.value + ).spatialReference + # Create a new feature class with the correct spatial reference, resolution, and tolerance + arcpy.CreateFeatureclass_management( + out_path=os.path.dirname(new_lines_feature_class), + out_name=os.path.basename(new_lines_feature_class), + geometry_type="POLYLINE", + spatial_reference=sr, + ) + arcpy.management.Append( + inputs=new_lines, + target=new_lines_feature_class, + ) print("Created new lines to the river network") arcpy.UnsplitLine_management( @@ -256,3 +265,9 @@ def process_new_lines(new_lines, all_rivers): if __name__ == "__main__": main() + + +""" +Tested a lot of things regarding the Resolution and Tolerance issues. Current theory is that it is somewhere in + the creation of line in "create_lines_from_coordinates". Or perhaps an issue with the near table itself. +""" diff --git a/generalization/n100/river/mst_loop.py b/generalization/n100/river/mst_loop.py new file mode 100644 index 00000000..6ef4e77c --- /dev/null +++ b/generalization/n100/river/mst_loop.py @@ -0,0 +1,196 @@ +import arcpy +import networkx as nx +import os +from itertools import combinations +import math + +from env_setup import environment_setup +from custom_tools import custom_arcpy +from file_manager.n100.file_manager_rivers import River_N100 + + +def main(): + setup_arcpy_environment() + filter_complicated_lakes() + + +input_water_polygon = f"{River_N100.unconnected_river_geometry__water_area_features_selected__n100.value}_copy" +input_centerline = ( + f"{River_N100.river_centerline__water_feature_collapsed__n100.value}_copy" +) +input_rivers = River_N100.unconnected_river_geometry__river_area_selection__n100.value + + +def setup_arcpy_environment(): + environment_setup.general_setup() + + arcpy.management.CopyFeatures( + in_features=River_N100.unconnected_river_geometry__water_area_features_selected__n100.value, + out_feature_class=f"{River_N100.unconnected_river_geometry__water_area_features_selected__n100.value}_copy", + ) + + arcpy.management.CopyFeatures( + in_features=River_N100.river_centerline__water_feature_collapsed__n100.value, + out_feature_class=f"{River_N100.river_centerline__water_feature_collapsed__n100.value}_copy", + ) + + custom_arcpy.select_location_and_make_permanent_feature( + input_layer=River_N100.unconnected_river_geometry__river_area_selection__n100.value, + overlap_type=custom_arcpy.OverlapType.INTERSECT.value, + select_features=f"{River_N100.unconnected_river_geometry__water_area_features_selected__n100.value}_copy", + output_name=f"{River_N100.unconnected_river_geometry__river_area_selection__n100.value}_copy", + ) + + +def filter_complicated_lakes(): + arcpy.management.FeatureVerticesToPoints( + in_features=input_centerline, + out_feature_class=River_N100.centerline_pruning_loop__centerline_start_end_vertex__n100.value, + point_location="BOTH_ENDS", + ) + + arcpy.management.DeleteIdentical( + in_dataset=River_N100.centerline_pruning_loop__centerline_start_end_vertex__n100.value, + fields="Shape", + ) + + arcpy.management.FeatureVerticesToPoints( + in_features=input_centerline, + out_feature_class=f"{River_N100.centerline_pruning_loop__river_inlet_dangles__n100.value}_not_selected", + point_location="DANGLE", + ) + + custom_arcpy.select_location_and_make_permanent_feature( + input_layer=f"{River_N100.centerline_pruning_loop__river_inlet_dangles__n100.value}_not_selected", + overlap_type=custom_arcpy.OverlapType.BOUNDARY_TOUCHES.value, + select_features=input_rivers, + output_name=River_N100.centerline_pruning_loop__river_inlet_dangles__n100.value, + ) + + custom_arcpy.select_location_and_make_permanent_feature( + input_layer=River_N100.centerline_pruning_loop__centerline_start_end_vertex__n100.value, + overlap_type=custom_arcpy.OverlapType.INTERSECT.value, + select_features=River_N100.centerline_pruning_loop__river_inlet_dangles__n100.value, + output_name=River_N100.centerline_pruning_loop__centerline_intersection_vertex__n100.value, + inverted=True, + ) + + intersection_field = "intersection" + river_inlet_field = "inlets" + + arcpy.AddField_management( + in_table=River_N100.centerline_pruning_loop__centerline_intersection_vertex__n100.value, + field_name=intersection_field, + field_type="LONG", + ) + arcpy.CalculateField_management( + in_table=River_N100.centerline_pruning_loop__centerline_intersection_vertex__n100.value, + field=intersection_field, + expression=1, + ) + + arcpy.AddField_management( + in_table=River_N100.centerline_pruning_loop__river_inlet_dangles__n100.value, + field_name=river_inlet_field, + field_type="LONG", + ) + arcpy.CalculateField_management( + in_table=River_N100.centerline_pruning_loop__river_inlet_dangles__n100.value, + field=river_inlet_field, + expression=1, + ) + + target_features = input_water_polygon + join_features_1 = ( + River_N100.centerline_pruning_loop__centerline_intersection_vertex__n100.value + ) + join_features_2 = ( + River_N100.centerline_pruning_loop__river_inlet_dangles__n100.value + ) + + # Create a new FieldMappings object and add both the target and join feature classes to it + field_mappings = arcpy.FieldMappings() + field_mappings.addTable(target_features) + field_mappings.addTable(join_features_1) + + # Find the index of the intersection_field in the field mappings + intersection_field_index = field_mappings.findFieldMapIndex(intersection_field) + + # If the field exists, modify its properties + if intersection_field_index != -1: + # Get the specific FieldMap object + field_map = field_mappings.getFieldMap(intersection_field_index) + + # Get the output field's properties as a Field object + field = field_map.outputField + + # Optionally, rename the output field to reflect its aggregated nature, e.g., sum_intersection + field.name = "sum_intersection" + field.aliasName = "Sum of Intersection" + field_map.outputField = field + + # Set the merge rule to sum to aggregate the values + field_map.mergeRule = "Sum" + + # Replace the old field map in the FieldMappings object with the updated one + field_mappings.replaceFieldMap(intersection_field_index, field_map) + + # Perform the Add Spatial Join with the updated field mappings + arcpy.analysis.SpatialJoin( + target_features=target_features, + join_features=join_features_1, + out_feature_class=f"{target_features}_add_join_1", + join_operation="JOIN_ONE_TO_ONE", + join_type="KEEP_ALL", + field_mapping=field_mappings, + match_option="INTERSECT", + ) + + # Prepare for the second join operation + field_mappings_2 = arcpy.FieldMappings() + field_mappings_2.addTable( + f"{target_features}_add_join_1" + ) # The output of the first join as input + field_mappings_2.addTable(join_features_2) + + # Find the index of the river_inlet_field in the field mappings + river_inlet_field_index = field_mappings_2.findFieldMapIndex(river_inlet_field) + + # If the field exists, modify its properties + if river_inlet_field_index != -1: + field_map = field_mappings_2.getFieldMap(river_inlet_field_index) + field = field_map.outputField + field.name = "sum_inlets" + field.aliasName = "Sum of Inlets" + field_map.outputField = field + field_map.mergeRule = "Sum" + field_mappings_2.replaceFieldMap(river_inlet_field_index, field_map) + + # Perform the second Add Spatial Join with the updated field mappings for river_inlet_field + arcpy.analysis.SpatialJoin( + target_features=f"{target_features}_add_join_1", + join_features=join_features_2, + out_feature_class=River_N100.centerline_pruning_loop__water_feature_summarized__n100.value, + join_operation="JOIN_ONE_TO_ONE", + join_type="KEEP_ALL", + field_mapping=field_mappings_2, + match_option="INTERSECT", + ) + + sql_simple_water_features = "sum_intersection < sum_inlets" + custom_arcpy.select_attribute_and_make_permanent_feature( + input_layer=River_N100.centerline_pruning_loop__water_feature_summarized__n100.value, + expression=sql_simple_water_features, + output_name=River_N100.centerline_pruning_loop__simple_water_features__n100.value, + ) + + sql_complex_water_features = "sum_intersection >= sum_inlets" + custom_arcpy.select_attribute_and_make_permanent_feature( + input_layer=River_N100.centerline_pruning_loop__water_feature_summarized__n100.value, + expression=sql_simple_water_features, + output_name=River_N100.centerline_pruning_loop__complex_water_features__n100.value, + ) + + +if __name__ == "__main__": + main() diff --git a/generalization/n100/river/unconnected_river_geometry.py b/generalization/n100/river/unconnected_river_geometry.py index 9318165b..61be001f 100644 --- a/generalization/n100/river/unconnected_river_geometry.py +++ b/generalization/n100/river/unconnected_river_geometry.py @@ -13,7 +13,7 @@ def main(): - geomotry_search_tolerance = 15 + geomotry_search_tolerance = 5 id_field = "orig_ob_id" dangle_id_field = "dang_id" cpu_usage_percentage = 0.9 From 7159fd5a9f7b300c4a0278460804e5514c69aa8d Mon Sep 17 00:00:00 2001 From: EllingOftedalKV Date: Tue, 13 Feb 2024 12:35:57 +0100 Subject: [PATCH 2/5] completed detection of complex and simple water features --- generalization/n100/river/mst_loop.py | 80 ++++++++++++++------------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/generalization/n100/river/mst_loop.py b/generalization/n100/river/mst_loop.py index 6ef4e77c..1e0e12be 100644 --- a/generalization/n100/river/mst_loop.py +++ b/generalization/n100/river/mst_loop.py @@ -11,6 +11,7 @@ def main(): setup_arcpy_environment() + filter_complicated_lakes() @@ -42,6 +43,10 @@ def setup_arcpy_environment(): ) +def prepare_data(): + pass + + def filter_complicated_lakes(): arcpy.management.FeatureVerticesToPoints( in_features=input_centerline, @@ -102,40 +107,30 @@ def filter_complicated_lakes(): target_features = input_water_polygon join_features_1 = ( - River_N100.centerline_pruning_loop__centerline_intersection_vertex__n100.value + River_N100.centerline_pruning_loop__river_inlet_dangles__n100.value ) join_features_2 = ( - River_N100.centerline_pruning_loop__river_inlet_dangles__n100.value + River_N100.centerline_pruning_loop__centerline_intersection_vertex__n100.value ) - # Create a new FieldMappings object and add both the target and join feature classes to it + # First Spatial Join - Adding river inlet data field_mappings = arcpy.FieldMappings() field_mappings.addTable(target_features) field_mappings.addTable(join_features_1) - # Find the index of the intersection_field in the field mappings - intersection_field_index = field_mappings.findFieldMapIndex(intersection_field) - - # If the field exists, modify its properties - if intersection_field_index != -1: - # Get the specific FieldMap object - field_map = field_mappings.getFieldMap(intersection_field_index) - - # Get the output field's properties as a Field object + # Setup field mapping for river_inlet_field (assuming sum_inlets is the intended outcome) + # Note: Corrected to ensure we're modifying the correct field mapping + inlet_field_index = field_mappings.findFieldMapIndex(river_inlet_field) + if inlet_field_index != -1: + field_map = field_mappings.getFieldMap(inlet_field_index) field = field_map.outputField - - # Optionally, rename the output field to reflect its aggregated nature, e.g., sum_intersection - field.name = "sum_intersection" - field.aliasName = "Sum of Intersection" + field.name = "sum_inlets" + field.aliasName = "Sum of Inlets" field_map.outputField = field - - # Set the merge rule to sum to aggregate the values field_map.mergeRule = "Sum" + field_mappings.replaceFieldMap(inlet_field_index, field_map) - # Replace the old field map in the FieldMappings object with the updated one - field_mappings.replaceFieldMap(intersection_field_index, field_map) - - # Perform the Add Spatial Join with the updated field mappings + # Execute the first spatial join arcpy.analysis.SpatialJoin( target_features=target_features, join_features=join_features_1, @@ -146,27 +141,24 @@ def filter_complicated_lakes(): match_option="INTERSECT", ) - # Prepare for the second join operation + # Second Spatial Join - Adding intersection data field_mappings_2 = arcpy.FieldMappings() - field_mappings_2.addTable( - f"{target_features}_add_join_1" - ) # The output of the first join as input + field_mappings_2.addTable(f"{target_features}_add_join_1") field_mappings_2.addTable(join_features_2) - # Find the index of the river_inlet_field in the field mappings - river_inlet_field_index = field_mappings_2.findFieldMapIndex(river_inlet_field) - - # If the field exists, modify its properties - if river_inlet_field_index != -1: - field_map = field_mappings_2.getFieldMap(river_inlet_field_index) + # Setup field mapping for intersection_field (assuming sum_intersection is the intended outcome) + # Corrected to focus on intersection_field + intersection_field_index = field_mappings_2.findFieldMapIndex(intersection_field) + if intersection_field_index != -1: + field_map = field_mappings_2.getFieldMap(intersection_field_index) field = field_map.outputField - field.name = "sum_inlets" - field.aliasName = "Sum of Inlets" + field.name = "sum_intersection" + field.aliasName = "Sum of Intersection" field_map.outputField = field field_map.mergeRule = "Sum" - field_mappings_2.replaceFieldMap(river_inlet_field_index, field_map) + field_mappings_2.replaceFieldMap(intersection_field_index, field_map) - # Perform the second Add Spatial Join with the updated field mappings for river_inlet_field + # Execute the second spatial join arcpy.analysis.SpatialJoin( target_features=f"{target_features}_add_join_1", join_features=join_features_2, @@ -177,17 +169,27 @@ def filter_complicated_lakes(): match_option="INTERSECT", ) - sql_simple_water_features = "sum_intersection < sum_inlets" + # Use CalculateField to set null ("sum_intersection" = ) values to 0 + arcpy.CalculateField_management( + in_table=River_N100.centerline_pruning_loop__water_feature_summarized__n100.value, + field="sum_intersection", + expression="0 if !sum_intersection! is None else !sum_intersection!", + expression_type="PYTHON3", + ) + + sql_simple_water_features = ( + "(sum_intersection < sum_inlets) OR (sum_intersection = 1 AND sum_inlets = 1)" + ) custom_arcpy.select_attribute_and_make_permanent_feature( input_layer=River_N100.centerline_pruning_loop__water_feature_summarized__n100.value, expression=sql_simple_water_features, output_name=River_N100.centerline_pruning_loop__simple_water_features__n100.value, ) - sql_complex_water_features = "sum_intersection >= sum_inlets" + sql_complex_water_features = "(sum_intersection >= sum_inlets) AND (sum_intersection <> 1 OR sum_inlets <> 1)" custom_arcpy.select_attribute_and_make_permanent_feature( input_layer=River_N100.centerline_pruning_loop__water_feature_summarized__n100.value, - expression=sql_simple_water_features, + expression=sql_complex_water_features, output_name=River_N100.centerline_pruning_loop__complex_water_features__n100.value, ) From 2b53c1134653c1fee30cab5e6ea9316be7c0a946 Mon Sep 17 00:00:00 2001 From: EllingOftedalKV Date: Wed, 14 Feb 2024 08:14:17 +0100 Subject: [PATCH 3/5] added logic to create points at water polygon boundaries --- file_manager/n100/file_manager_rivers.py | 60 ++++++++++++++++ generalization/n100/river/mst_loop.py | 87 ++++++++++++++++++------ 2 files changed, 127 insertions(+), 20 deletions(-) diff --git a/file_manager/n100/file_manager_rivers.py b/file_manager/n100/file_manager_rivers.py index 39c1c312..ae236970 100644 --- a/file_manager/n100/file_manager_rivers.py +++ b/file_manager/n100/file_manager_rivers.py @@ -291,6 +291,66 @@ class River_N100(Enum): ########### RIVER CENTERLINE PROONING LOOP ########### ################################################# + centerline_pruning_loop__lake_features__n100 = generate_file_name_gdb( + function_name=centerline_pruning_loop, + description="lake_features", + scale=scale, + ) + + centerline_pruning_loop__study_area__n100 = generate_file_name_gdb( + function_name=centerline_pruning_loop, + description="study_area", + scale=scale, + ) + + centerline_pruning_loop__water_features_study_area__n100 = generate_file_name_gdb( + function_name=centerline_pruning_loop, + description="water_features_study_area", + scale=scale, + ) + + centerline_pruning_loop__water_features_dissolved__n100 = generate_file_name_gdb( + function_name=centerline_pruning_loop, + description="water_features_dissolved", + scale=scale, + ) + + centerline_pruning_loop__water_features_dissolved_river_intersect__n100 = ( + generate_file_name_gdb( + function_name=centerline_pruning_loop, + description="water_features_dissolved_river_intersect", + scale=scale, + ) + ) + + centerline_pruning_loop__water_features_river_final_selection__n100 = ( + generate_file_name_gdb( + function_name=centerline_pruning_loop, + description="water_features_river_final_selection", + scale=scale, + ) + ) + + centerline_pruning_loop__polygon_to_line__n100 = generate_file_name_gdb( + function_name=centerline_pruning_loop, + description="polygon_to_line", + scale=scale, + ) + + centerline_pruning_loop__water_features_shared_boundaries__n100 = ( + generate_file_name_gdb( + function_name=centerline_pruning_loop, + description="water_features_shared_boundaries", + scale=scale, + ) + ) + + centerline_pruning_loop__shared_boundaries_midpoint__n100 = generate_file_name_gdb( + function_name=centerline_pruning_loop, + description="shared_boundaries_midpoint", + scale=scale, + ) + centerline_pruning_loop__centerline_start_end_vertex__n100 = generate_file_name_gdb( function_name=centerline_pruning_loop, description="centerline_start_end_vertex", diff --git a/generalization/n100/river/mst_loop.py b/generalization/n100/river/mst_loop.py index 1e0e12be..06a700f1 100644 --- a/generalization/n100/river/mst_loop.py +++ b/generalization/n100/river/mst_loop.py @@ -7,49 +7,96 @@ from env_setup import environment_setup from custom_tools import custom_arcpy from file_manager.n100.file_manager_rivers import River_N100 +from input_data import input_n50 def main(): setup_arcpy_environment() + prepare_data() - filter_complicated_lakes() + # filter_complicated_lakes() -input_water_polygon = f"{River_N100.unconnected_river_geometry__water_area_features_selected__n100.value}_copy" -input_centerline = ( - f"{River_N100.river_centerline__water_feature_collapsed__n100.value}_copy" -) +input_water_polygon = River_N100.centerline_pruning_loop__lake_features__n100.value +input_centerline = River_N100.river_centerline__water_feature_collapsed__n100.value input_rivers = River_N100.unconnected_river_geometry__river_area_selection__n100.value +water_polygon = f"{River_N100.unconnected_river_geometry__water_area_features_selected__n100.value}_copy" +centerline = f"{River_N100.river_centerline__water_feature_collapsed__n100.value}_copy" +rivers = River_N100.unconnected_river_geometry__river_area_selection__n100.value + def setup_arcpy_environment(): environment_setup.general_setup() - arcpy.management.CopyFeatures( - in_features=River_N100.unconnected_river_geometry__water_area_features_selected__n100.value, - out_feature_class=f"{River_N100.unconnected_river_geometry__water_area_features_selected__n100.value}_copy", + +def prepare_data(): + sql_expression_water_features = "OBJTYPE = 'FerskvannTørrfall' Or OBJTYPE = 'Innsjø' Or OBJTYPE = 'InnsjøRegulert' Or OBJTYPE = 'ElvBekk'" + custom_arcpy.select_attribute_and_make_permanent_feature( + input_layer=input_n50.ArealdekkeFlate, + expression=sql_expression_water_features, + output_name=River_N100.centerline_pruning_loop__lake_features__n100.value, ) - arcpy.management.CopyFeatures( - in_features=River_N100.river_centerline__water_feature_collapsed__n100.value, - out_feature_class=f"{River_N100.river_centerline__water_feature_collapsed__n100.value}_copy", + arcpy.analysis.PairwiseBuffer( + in_features=input_rivers, + out_feature_class=River_N100.centerline_pruning_loop__study_area__n100.value, + buffer_distance_or_field="5 Kilometers", + dissolve_option="ALL", ) + print("Created study area buffer") custom_arcpy.select_location_and_make_permanent_feature( - input_layer=River_N100.unconnected_river_geometry__river_area_selection__n100.value, + input_layer=input_water_polygon, overlap_type=custom_arcpy.OverlapType.INTERSECT.value, - select_features=f"{River_N100.unconnected_river_geometry__water_area_features_selected__n100.value}_copy", - output_name=f"{River_N100.unconnected_river_geometry__river_area_selection__n100.value}_copy", + select_features=River_N100.centerline_pruning_loop__study_area__n100.value, + output_name=River_N100.centerline_pruning_loop__water_features_study_area__n100.value, ) + arcpy.gapro.DissolveBoundaries( + input_layer=River_N100.centerline_pruning_loop__water_features_study_area__n100.value, + out_feature_class=River_N100.centerline_pruning_loop__water_features_dissolved__n100.value, + ) -def prepare_data(): - pass + print("Dissolved water features boundaries") + + custom_arcpy.select_location_and_make_permanent_feature( + input_layer=River_N100.centerline_pruning_loop__water_features_dissolved__n100.value, + overlap_type=custom_arcpy.OverlapType.INTERSECT.value, + select_features=input_rivers, + output_name=River_N100.centerline_pruning_loop__water_features_dissolved_river_intersect__n100.value, + ) + + custom_arcpy.select_location_and_make_permanent_feature( + input_layer=input_water_polygon, + overlap_type=custom_arcpy.OverlapType.INTERSECT.value, + select_features=River_N100.centerline_pruning_loop__water_features_dissolved_river_intersect__n100.value, + output_name=River_N100.centerline_pruning_loop__water_features_river_final_selection__n100.value, + ) + + arcpy.PolygonToLine_management( + in_features=River_N100.centerline_pruning_loop__water_features_river_final_selection__n100.value, + out_feature_class=River_N100.centerline_pruning_loop__polygon_to_line__n100.value, + neighbor_option="IDENTIFY_NEIGHBORS", + ) + + sql_expression_boundaries = "LEFT_FID <> -1 Or RIGHT_FID = -1" + custom_arcpy.select_attribute_and_make_permanent_feature( + input_layer=River_N100.centerline_pruning_loop__polygon_to_line__n100.value, + expression=sql_expression_boundaries, + output_name=River_N100.centerline_pruning_loop__water_features_shared_boundaries__n100.value, + ) + + arcpy.management.FeatureVerticesToPoints( + in_features=River_N100.centerline_pruning_loop__water_features_shared_boundaries__n100.value, + out_feature_class=River_N100.centerline_pruning_loop__shared_boundaries_midpoint__n100.value, + point_location="MID", + ) def filter_complicated_lakes(): arcpy.management.FeatureVerticesToPoints( - in_features=input_centerline, + in_features=centerline, out_feature_class=River_N100.centerline_pruning_loop__centerline_start_end_vertex__n100.value, point_location="BOTH_ENDS", ) @@ -60,7 +107,7 @@ def filter_complicated_lakes(): ) arcpy.management.FeatureVerticesToPoints( - in_features=input_centerline, + in_features=centerline, out_feature_class=f"{River_N100.centerline_pruning_loop__river_inlet_dangles__n100.value}_not_selected", point_location="DANGLE", ) @@ -68,7 +115,7 @@ def filter_complicated_lakes(): custom_arcpy.select_location_and_make_permanent_feature( input_layer=f"{River_N100.centerline_pruning_loop__river_inlet_dangles__n100.value}_not_selected", overlap_type=custom_arcpy.OverlapType.BOUNDARY_TOUCHES.value, - select_features=input_rivers, + select_features=rivers, output_name=River_N100.centerline_pruning_loop__river_inlet_dangles__n100.value, ) @@ -105,7 +152,7 @@ def filter_complicated_lakes(): expression=1, ) - target_features = input_water_polygon + target_features = water_polygon join_features_1 = ( River_N100.centerline_pruning_loop__river_inlet_dangles__n100.value ) From dca4e554b5929de7c762b12fb4af73f131f352df Mon Sep 17 00:00:00 2001 From: EllingOftedalKV Date: Wed, 14 Feb 2024 10:12:05 +0100 Subject: [PATCH 4/5] new collapsed hydropolygon logic and working centerline complexity --- file_manager/n100/file_manager_rivers.py | 31 ++++++++++- generalization/n100/river/mst_loop.py | 65 ++++++++++++++++++++++-- 2 files changed, 90 insertions(+), 6 deletions(-) diff --git a/file_manager/n100/file_manager_rivers.py b/file_manager/n100/file_manager_rivers.py index 46677db2..a879e98c 100644 --- a/file_manager/n100/file_manager_rivers.py +++ b/file_manager/n100/file_manager_rivers.py @@ -102,7 +102,6 @@ def generate_file_name_lyrx( class River_N100(Enum): - ########################################### ########### RIVER DATA PREPARATION ########## ########################################### @@ -381,6 +380,36 @@ class River_N100(Enum): scale=scale, ) + centerline_pruning_loop__river_inlets__n100 = generate_file_name_gdb( + function_name=centerline_pruning_loop, + description="river_inlets", + scale=scale, + ) + + centerline_pruning_loop__river_inlets_erased__n100 = generate_file_name_gdb( + function_name=centerline_pruning_loop, + description="river_inlets_erased", + scale=scale, + ) + + centerline_pruning_loop__river_inlets_points_merged__n100 = generate_file_name_gdb( + function_name=centerline_pruning_loop, + description="river_inlets_points_merged", + scale=scale, + ) + + short_name__water__n100 = generate_file_name_gdb( + function_name="short_name", + description="water", + scale=scale, + ) + + centerline_pruning_loop__collapsed_hydropolygon__n100 = generate_file_name_gdb( + function_name=centerline_pruning_loop, + description="collapsed_hydropolygon", + scale=scale, + ) + centerline_pruning_loop__centerline_start_end_vertex__n100 = generate_file_name_gdb( function_name=centerline_pruning_loop, description="centerline_start_end_vertex", diff --git a/generalization/n100/river/mst_loop.py b/generalization/n100/river/mst_loop.py index 06a700f1..cfaf9a69 100644 --- a/generalization/n100/river/mst_loop.py +++ b/generalization/n100/river/mst_loop.py @@ -13,8 +13,9 @@ def main(): setup_arcpy_environment() prepare_data() + create_collapsed_centerline() - # filter_complicated_lakes() + filter_complicated_lakes() input_water_polygon = River_N100.centerline_pruning_loop__lake_features__n100.value @@ -22,7 +23,7 @@ def main(): input_rivers = River_N100.unconnected_river_geometry__river_area_selection__n100.value water_polygon = f"{River_N100.unconnected_river_geometry__water_area_features_selected__n100.value}_copy" -centerline = f"{River_N100.river_centerline__water_feature_collapsed__n100.value}_copy" +centerline = River_N100.centerline_pruning_loop__collapsed_hydropolygon__n100.value rivers = River_N100.unconnected_river_geometry__river_area_selection__n100.value @@ -94,6 +95,52 @@ def prepare_data(): ) +def create_collapsed_centerline(): + custom_arcpy.select_location_and_make_permanent_feature( + input_layer=input_rivers, + overlap_type=custom_arcpy.OverlapType.BOUNDARY_TOUCHES.value, + select_features=River_N100.centerline_pruning_loop__water_features_river_final_selection__n100.value, + output_name=River_N100.centerline_pruning_loop__river_inlets__n100.value, + ) + + arcpy.analysis.PairwiseErase( + in_features=River_N100.centerline_pruning_loop__river_inlets__n100.value, + erase_features=River_N100.centerline_pruning_loop__water_features_river_final_selection__n100.value, + out_feature_class=River_N100.centerline_pruning_loop__river_inlets_erased__n100.value, + ) + print( + f"Created {River_N100.river_centerline__rivers_near_waterfeatures_erased__n100.value}" + ) + + # Copy to rename the file to have less characters in the name since the name needs to fit inside a field in CollapseHydroPolygon + arcpy.management.CopyFeatures( + in_features=River_N100.centerline_pruning_loop__water_features_river_final_selection__n100.value, + out_feature_class=River_N100.short_name__water__n100.value, + ) + print(f"Created {River_N100.short_name__water__n100.value}") + + arcpy.cartography.CollapseHydroPolygon( + in_features=River_N100.short_name__water__n100.value, + out_line_feature_class=River_N100.centerline_pruning_loop__collapsed_hydropolygon__n100.value, + connecting_features=River_N100.centerline_pruning_loop__river_inlets_erased__n100.value, + merge_adjacent_input_polygons="MERGE_ADJACENT", + ) + print( + f"Created {River_N100.centerline_pruning_loop__collapsed_hydropolygon__n100.value}" + ) + + arcpy.edit.Snap( + in_features=River_N100.centerline_pruning_loop__shared_boundaries_midpoint__n100.value, + snap_environment=[ + [ + River_N100.centerline_pruning_loop__collapsed_hydropolygon__n100.value, + "END", + "100 Meters", + ] + ], + ) + + def filter_complicated_lakes(): arcpy.management.FeatureVerticesToPoints( in_features=centerline, @@ -119,6 +166,14 @@ def filter_complicated_lakes(): output_name=River_N100.centerline_pruning_loop__river_inlet_dangles__n100.value, ) + arcpy.management.Merge( + inputs=[ + River_N100.centerline_pruning_loop__shared_boundaries_midpoint__n100.value, + River_N100.centerline_pruning_loop__river_inlet_dangles__n100.value, + ], + output=River_N100.centerline_pruning_loop__river_inlets_points_merged__n100.value, + ) + custom_arcpy.select_location_and_make_permanent_feature( input_layer=River_N100.centerline_pruning_loop__centerline_start_end_vertex__n100.value, overlap_type=custom_arcpy.OverlapType.INTERSECT.value, @@ -142,19 +197,19 @@ def filter_complicated_lakes(): ) arcpy.AddField_management( - in_table=River_N100.centerline_pruning_loop__river_inlet_dangles__n100.value, + in_table=River_N100.centerline_pruning_loop__river_inlets_points_merged__n100.value, field_name=river_inlet_field, field_type="LONG", ) arcpy.CalculateField_management( - in_table=River_N100.centerline_pruning_loop__river_inlet_dangles__n100.value, + in_table=River_N100.centerline_pruning_loop__river_inlets_points_merged__n100.value, field=river_inlet_field, expression=1, ) target_features = water_polygon join_features_1 = ( - River_N100.centerline_pruning_loop__river_inlet_dangles__n100.value + River_N100.centerline_pruning_loop__river_inlets_points_merged__n100.value ) join_features_2 = ( River_N100.centerline_pruning_loop__centerline_intersection_vertex__n100.value From 8fbb0f61f69af5bac75808b5e332acc7af1645fb Mon Sep 17 00:00:00 2001 From: EllingOftedalKV Date: Wed, 14 Feb 2024 15:23:36 +0100 Subject: [PATCH 5/5] fixed bug where closed centerlines where not contributing towards complexity --- file_manager/n100/file_manager_rivers.py | 32 ++++++ generalization/n100/river/mst_loop.py | 126 +++++++++++++++++++++-- 2 files changed, 149 insertions(+), 9 deletions(-) diff --git a/file_manager/n100/file_manager_rivers.py b/file_manager/n100/file_manager_rivers.py index a879e98c..9d73fd54 100644 --- a/file_manager/n100/file_manager_rivers.py +++ b/file_manager/n100/file_manager_rivers.py @@ -326,6 +326,14 @@ class River_N100(Enum): scale=scale, ) + centerline_pruning_loop__rivers_erased_with_lake_features__n100 = ( + generate_file_name_gdb( + function_name=centerline_pruning_loop, + description="rivers_erased_with_lake_features", + scale=scale, + ) + ) + centerline_pruning_loop__study_area__n100 = generate_file_name_gdb( function_name=centerline_pruning_loop, description="study_area", @@ -410,6 +418,24 @@ class River_N100(Enum): scale=scale, ) + centerline_pruning_loop__closed_centerline_lines__n100 = generate_file_name_gdb( + function_name=centerline_pruning_loop, + description="closed_centerline_lines", + scale=scale, + ) + + centerline_pruning_loop__closed_centerline_point__n100 = generate_file_name_gdb( + function_name=centerline_pruning_loop, + description="closed_centerline_point", + scale=scale, + ) + + centerline_pruning_loop__intersection_points_merged__n100 = generate_file_name_gdb( + function_name=centerline_pruning_loop, + description="intersection_points_merged", + scale=scale, + ) + centerline_pruning_loop__centerline_start_end_vertex__n100 = generate_file_name_gdb( function_name=centerline_pruning_loop, description="centerline_start_end_vertex", @@ -430,6 +456,12 @@ class River_N100(Enum): scale=scale, ) + # centerline_pruning_loop__river_inlet_dangles_merged__n100 = generate_file_name_gdb( + # function_name=centerline_pruning_loop, + # description="river_inlet_dangles_merged", + # scale=scale, + # ) + centerline_pruning_loop__water_feature_summarized__n100 = generate_file_name_gdb( function_name=centerline_pruning_loop, description="water_feature_summarized", diff --git a/generalization/n100/river/mst_loop.py b/generalization/n100/river/mst_loop.py index cfaf9a69..a3d041cf 100644 --- a/generalization/n100/river/mst_loop.py +++ b/generalization/n100/river/mst_loop.py @@ -20,11 +20,13 @@ def main(): input_water_polygon = River_N100.centerline_pruning_loop__lake_features__n100.value input_centerline = River_N100.river_centerline__water_feature_collapsed__n100.value -input_rivers = River_N100.unconnected_river_geometry__river_area_selection__n100.value +input_rivers = ( + River_N100.centerline_pruning_loop__rivers_erased_with_lake_features__n100.value +) -water_polygon = f"{River_N100.unconnected_river_geometry__water_area_features_selected__n100.value}_copy" +water_polygon = River_N100.centerline_pruning_loop__lake_features__n100.value centerline = River_N100.centerline_pruning_loop__collapsed_hydropolygon__n100.value -rivers = River_N100.unconnected_river_geometry__river_area_selection__n100.value +rivers = River_N100.centerline_pruning_loop__river_inlets_erased__n100.value def setup_arcpy_environment(): @@ -39,6 +41,15 @@ def prepare_data(): output_name=River_N100.centerline_pruning_loop__lake_features__n100.value, ) + arcpy.analysis.PairwiseErase( + in_features=River_N100.unconnected_river_geometry__river_area_selection__n100.value, + erase_features=River_N100.centerline_pruning_loop__lake_features__n100.value, + out_feature_class=River_N100.centerline_pruning_loop__rivers_erased_with_lake_features__n100.value, + ) + print( + f"Created {River_N100.river_centerline__rivers_near_waterfeatures_erased__n100.value}" + ) + arcpy.analysis.PairwiseBuffer( in_features=input_rivers, out_feature_class=River_N100.centerline_pruning_loop__study_area__n100.value, @@ -95,6 +106,62 @@ def prepare_data(): ) +def extract_closed_lines(input_feature_class, output_feature_class): + """ + Extracts lines that start and end at the same point from the input feature class and + creates a new feature class containing only these closed lines. + + Parameters: + input_feature_class (str): The path to the input feature class to check. + output_feature_class (str): The path to the output feature class for storing closed lines. + """ + # Create the output feature class with the same spatial reference and fields as the input + spatial_reference = arcpy.Describe(input_feature_class).spatialReference + # Check if the output feature class already exists; if so, delete it + if arcpy.Exists(output_feature_class): + arcpy.Delete_management(output_feature_class) + arcpy.CreateFeatureclass_management( + out_path=os.path.dirname(output_feature_class), + out_name=os.path.basename(output_feature_class), + geometry_type="POLYLINE", + spatial_reference=spatial_reference, + ) + + # Define fields to include (excluding OID and Shape) and add them to the output feature class + fields_to_copy = [ + field.name + for field in arcpy.ListFields(input_feature_class) + if field.type not in ("OID", "Geometry") + ] + fields = [ + "SHAPE@XY" + ] + fields_to_copy # 'SHAPE@XY' used for simplicity, consider 'SHAPE@' for exact geometry copy + for field in fields_to_copy: + arcpy.AddField_management( + output_feature_class, + field, + arcpy.ListFields(input_feature_class, field)[0].type, + ) + + # Process each line in the input feature class + with arcpy.da.SearchCursor( + input_feature_class, ["OID@", "SHAPE@"] + fields_to_copy + ) as search_cursor, arcpy.da.InsertCursor( + output_feature_class, ["SHAPE@"] + fields_to_copy + ) as insert_cursor: + for row in search_cursor: + polyline = row[1] # Geometry of the feature + if polyline.firstPoint.equals( + polyline.lastPoint + ): # Check if the line is closed + # Prepare the row for insertion + insert_row = [polyline] + list(row[2:]) # Exclude OID@ + insert_cursor.insertRow(insert_row) + print( + f"Feature ID {row[0]} is closed and has been added to the output feature class." + ) + + def create_collapsed_centerline(): custom_arcpy.select_location_and_make_permanent_feature( input_layer=input_rivers, @@ -140,6 +207,17 @@ def create_collapsed_centerline(): ], ) + extract_closed_lines( + River_N100.centerline_pruning_loop__collapsed_hydropolygon__n100.value, + River_N100.centerline_pruning_loop__closed_centerline_lines__n100.value, + ) + + arcpy.management.FeatureVerticesToPoints( + in_features=River_N100.centerline_pruning_loop__closed_centerline_lines__n100.value, + out_feature_class=River_N100.centerline_pruning_loop__closed_centerline_point__n100.value, + point_location="END", + ) + def filter_complicated_lakes(): arcpy.management.FeatureVerticesToPoints( @@ -161,11 +239,31 @@ def filter_complicated_lakes(): custom_arcpy.select_location_and_make_permanent_feature( input_layer=f"{River_N100.centerline_pruning_loop__river_inlet_dangles__n100.value}_not_selected", - overlap_type=custom_arcpy.OverlapType.BOUNDARY_TOUCHES.value, + overlap_type=custom_arcpy.OverlapType.INTERSECT.value, select_features=rivers, output_name=River_N100.centerline_pruning_loop__river_inlet_dangles__n100.value, ) + # custom_arcpy.select_location_and_make_permanent_feature( + # input_layer=f"{River_N100.centerline_pruning_loop__river_inlet_dangles__n100.value}_not_selected", + # overlap_type=custom_arcpy.OverlapType.BOUNDARY_TOUCHES.value, + # select_features=rivers, + # output_name=f"{River_N100.centerline_pruning_loop__river_inlet_dangles__n100.value}_boundary_touches", + # ) + + # arcpy.management.Merge( + # inputs=[ + # f"{River_N100.centerline_pruning_loop__river_inlet_dangles__n100.value}_not_selected", + # f"{River_N100.centerline_pruning_loop__river_inlet_dangles__n100.value}_boundary_touches", + # ], + # output=River_N100.centerline_pruning_loop__river_inlet_dangles_merged__n100.value, + # ) + # + # arcpy.management.DeleteIdentical( + # in_dataset=River_N100.centerline_pruning_loop__river_inlet_dangles_merged__n100.value, + # fields="Shape", + # ) + arcpy.management.Merge( inputs=[ River_N100.centerline_pruning_loop__shared_boundaries_midpoint__n100.value, @@ -177,21 +275,29 @@ def filter_complicated_lakes(): custom_arcpy.select_location_and_make_permanent_feature( input_layer=River_N100.centerline_pruning_loop__centerline_start_end_vertex__n100.value, overlap_type=custom_arcpy.OverlapType.INTERSECT.value, - select_features=River_N100.centerline_pruning_loop__river_inlet_dangles__n100.value, + select_features=River_N100.centerline_pruning_loop__river_inlets_points_merged__n100.value, output_name=River_N100.centerline_pruning_loop__centerline_intersection_vertex__n100.value, inverted=True, ) + arcpy.management.Merge( + inputs=[ + River_N100.centerline_pruning_loop__centerline_intersection_vertex__n100.value, + River_N100.centerline_pruning_loop__closed_centerline_point__n100.value, + ], + output=River_N100.centerline_pruning_loop__intersection_points_merged__n100.value, + ) + intersection_field = "intersection" river_inlet_field = "inlets" arcpy.AddField_management( - in_table=River_N100.centerline_pruning_loop__centerline_intersection_vertex__n100.value, + in_table=River_N100.centerline_pruning_loop__intersection_points_merged__n100.value, field_name=intersection_field, field_type="LONG", ) arcpy.CalculateField_management( - in_table=River_N100.centerline_pruning_loop__centerline_intersection_vertex__n100.value, + in_table=River_N100.centerline_pruning_loop__intersection_points_merged__n100.value, field=intersection_field, expression=1, ) @@ -207,12 +313,14 @@ def filter_complicated_lakes(): expression=1, ) - target_features = water_polygon + target_features = ( + River_N100.centerline_pruning_loop__water_features_river_final_selection__n100.value + ) join_features_1 = ( River_N100.centerline_pruning_loop__river_inlets_points_merged__n100.value ) join_features_2 = ( - River_N100.centerline_pruning_loop__centerline_intersection_vertex__n100.value + River_N100.centerline_pruning_loop__intersection_points_merged__n100.value ) # First Spatial Join - Adding river inlet data