diff --git a/custom_tools/file_utilities.py b/custom_tools/file_utilities.py new file mode 100644 index 00000000..524bd526 --- /dev/null +++ b/custom_tools/file_utilities.py @@ -0,0 +1,93 @@ +import arcpy +import os + + +class FeatureClassCreator: + def __init__( + self, + template_fc, + input_fc, + output_fc, + object_type="POLYGON", + delete_existing=False, + ): + """ + Initializes the FeatureClassCreator with required parameters. + + Parameters: + - template_fc (str): The path to the template feature class used to define the schema. + - input_fc (str): The path to the input feature class from which to append the geometry. + - output_fc (str): The path to the output feature class to create and append to. + - object_type (str): The type of geometry for the new feature class ("POINT", "MULTIPOINT", "LINE", "POLYLINE", "POLYGON", or "MULTIPATCH"). + - delete_existing (bool): Whether to delete the existing output feature class if it exists. If set to False the geometry will be appended to the existing output feature class. + + Example Usage: + -------------- + >>> feature_creator = FeatureClassCreator( + ... template_fc='path/to/template_feature_class', + ... input_fc='path/to/input_feature_class', + ... output_fc='path/to/output_feature_class', + ... object_type='POLYGON', + ... delete_existing=True + ... ) + >>> feature_creator.run() + + This initializes the creator with all parameters and creates a new feature class based on the provided template, + optionally deleting any existing feature class at the output path, and appends the geometry from the input feature class. + """ + self.template_fc = template_fc + self.input_fc = input_fc + self.output_fc = output_fc + self.object_type = object_type + self.delete_existing = delete_existing + + def run(self): + """ + Executes the process of creating a new feature class and appending geometry from the input feature class. + """ + if arcpy.Exists(self.output_fc): + if self.delete_existing: + # Deletes the output file if it exists and delete_existing boolean is set to True + arcpy.Delete_management(self.output_fc) + print(f"Deleted existing feature class: {self.output_fc}") + # Creates a new feature class after deletion + self._create_feature_class() + else: + # If output exists and delete_existing is set to False, just append data. + print( + f"Output feature class {self.output_fc} already exists. Appending data." + ) + else: + if self.delete_existing: + print("Output feature class does not exist, so it was not deleted.") + # If output does not exist, create a new feature class + self._create_feature_class() + + # Append geometry as the final step, occurring in all scenarios. + self._append_geometry() + + def _create_feature_class(self): + """ + Creates a new feature class using the specified template and object type. + """ + output_workspace, output_class_name = os.path.split(self.output_fc) + arcpy.CreateFeatureclass_management( + output_workspace, + output_class_name, + self.object_type, + self.template_fc, + spatial_reference=arcpy.Describe(self.template_fc).spatialReference, + ) + print(f"Created new feature class: {self.output_fc}") + + def _append_geometry(self): + """ + Appends geometry from the input feature class to the output feature class. + This method assumes the output feature class already exists or was just created. + """ + with arcpy.da.SearchCursor( + self.input_fc, ["SHAPE@"] + ) as s_cursor, arcpy.da.InsertCursor(self.output_fc, ["SHAPE@"]) as i_cursor: + for row in s_cursor: + i_cursor.insertRow(row) + print("Appended geometry to the feature class.") diff --git a/file_manager/n100/file_manager_rivers.py b/file_manager/n100/file_manager_rivers.py index a4d71ca7..c9c37d27 100644 --- a/file_manager/n100/file_manager_rivers.py +++ b/file_manager/n100/file_manager_rivers.py @@ -368,6 +368,12 @@ class River_N100(Enum): ) ) + centerline_pruning_loop__water_features_processed__n100 = generate_file_name_gdb( + function_name=centerline_pruning_loop, + description="water_features_processed", + scale=scale, + ) + centerline_pruning_loop__polygon_to_line__n100 = generate_file_name_gdb( function_name=centerline_pruning_loop, description="polygon_to_line", @@ -418,6 +424,22 @@ class River_N100(Enum): scale=scale, ) + centerline_pruning_loop__collapsed_hydropolygon_points__n100 = ( + generate_file_name_gdb( + function_name=centerline_pruning_loop, + description="collapsed_hydropolygon_points", + scale=scale, + ) + ) + + centerline_pruning_loop__collapsed_hydropolygon_points_selected__n100 = ( + generate_file_name_gdb( + function_name=centerline_pruning_loop, + description="collapsed_hydropolygon_points_selected", + scale=scale, + ) + ) + centerline_pruning_loop__closed_centerline_lines__n100 = generate_file_name_gdb( function_name=centerline_pruning_loop, description="closed_centerline_lines", @@ -473,3 +495,21 @@ class River_N100(Enum): description="complex_water_features", scale=scale, ) + + centerline_pruning_loop__simple_centerlines__n100 = generate_file_name_gdb( + function_name=centerline_pruning_loop, + description="simple_centerlines", + scale=scale, + ) + + centerline_pruning_loop__complex_centerlines__n100 = generate_file_name_gdb( + function_name=centerline_pruning_loop, + description="complex_centerlines", + scale=scale, + ) + + centerline_pruning_loop__finnished_centerlines__n100 = generate_file_name_gdb( + function_name=centerline_pruning_loop, + description="finnished_centerlines", + scale=scale, + ) diff --git a/generalization/n100/river/mst_loop.py b/generalization/n100/river/mst_loop.py index be818051..1ed69608 100644 --- a/generalization/n100/river/mst_loop.py +++ b/generalization/n100/river/mst_loop.py @@ -8,6 +8,7 @@ from custom_tools import custom_arcpy from file_manager.n100.file_manager_rivers import River_N100 from input_data import input_n50 +from custom_tools.file_utilities import FeatureClassCreator def main(): @@ -17,6 +18,8 @@ def main(): filter_complicated_lakes() + # create_feature_class() + input_water_polygon = River_N100.centerline_pruning_loop__lake_features__n100.value input_centerline = River_N100.river_centerline__water_feature_collapsed__n100.value @@ -24,10 +27,20 @@ def main(): River_N100.centerline_pruning_loop__rivers_erased_with_lake_features__n100.value ) -water_polygon = River_N100.centerline_pruning_loop__lake_features__n100.value +water_polygon = River_N100.centerline_pruning_loop__water_features_processed__n100.value centerline = River_N100.centerline_pruning_loop__collapsed_hydropolygon__n100.value rivers = River_N100.centerline_pruning_loop__river_inlets_erased__n100.value +complex_lakes = River_N100.centerline_pruning_loop__complex_water_features__n100.value +simple_lakes = River_N100.centerline_pruning_loop__simple_water_features__n100.value +river_inlet_nodes = ( + River_N100.centerline_pruning_loop__river_inlets_points_merged__n100.value +) +complex_centerlines = ( + River_N100.centerline_pruning_loop__complex_centerlines__n100.value +) +simple_centerlines = River_N100.centerline_pruning_loop__simple_centerlines__n100.value + def setup_arcpy_environment(): environment_setup.general_setup() @@ -86,13 +99,57 @@ def prepare_data(): output_name=River_N100.centerline_pruning_loop__water_features_river_final_selection__n100.value, ) + sql_expression_torrfall = "OBJTYPE = 'FerskvannTørrfall'" + + custom_arcpy.select_attribute_and_make_feature_layer( + input_layer=River_N100.centerline_pruning_loop__water_features_river_final_selection__n100.value, + expression=sql_expression_torrfall, + output_name=f"{River_N100.centerline_pruning_loop__water_features_river_final_selection__n100.value}_torrfall", + ) + + sql_expression_elvbekk = "OBJTYPE = 'ElvBekk'" + + custom_arcpy.select_attribute_and_make_feature_layer( + input_layer=River_N100.centerline_pruning_loop__water_features_river_final_selection__n100.value, + expression=sql_expression_elvbekk, + output_name=f"{River_N100.centerline_pruning_loop__water_features_river_final_selection__n100.value}_elvbekk", + ) + + arcpy.topographic.EliminatePolygon( + in_features=f"{River_N100.centerline_pruning_loop__water_features_river_final_selection__n100.value}_torrfall", + surrounding_features=f"{River_N100.centerline_pruning_loop__water_features_river_final_selection__n100.value}_elvbekk", + ) + + sql_expression_small_features = "shape_Area <= 100000" + + custom_arcpy.select_attribute_and_make_feature_layer( + input_layer=River_N100.centerline_pruning_loop__water_features_river_final_selection__n100.value, + expression=sql_expression_small_features, + output_name=f"{River_N100.centerline_pruning_loop__water_features_river_final_selection__n100.value}_small_features", + ) + + arcpy.Eliminate_management( + in_features=f"{River_N100.centerline_pruning_loop__water_features_river_final_selection__n100.value}_small_features", + out_feature_class=River_N100.centerline_pruning_loop__water_features_processed__n100.value, + selection="LENGTH", + ) + arcpy.PolygonToLine_management( - in_features=River_N100.centerline_pruning_loop__water_features_river_final_selection__n100.value, + in_features=River_N100.centerline_pruning_loop__water_features_processed__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" + arcpy.management.AddSpatialJoin( + target_features=River_N100.centerline_pruning_loop__polygon_to_line__n100.value, + join_features=River_N100.centerline_pruning_loop__water_features_processed__n100.value, + join_operation=None, + join_type="KEEP_ALL", + match_option="LARGEST_OVERLAP", + permanent_join="PERMANENT_FIELDS", + ) + + sql_expression_boundaries = "LEFT_FID <> -1 AND 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, @@ -166,13 +223,13 @@ 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, + select_features=River_N100.centerline_pruning_loop__water_features_processed__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, + erase_features=River_N100.centerline_pruning_loop__water_features_processed__n100.value, out_feature_class=River_N100.centerline_pruning_loop__river_inlets_erased__n100.value, ) print( @@ -181,7 +238,7 @@ def create_collapsed_centerline(): # 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, + in_features=River_N100.centerline_pruning_loop__water_features_processed__n100.value, out_feature_class=River_N100.short_name__water__n100.value, ) print(f"Created {River_N100.short_name__water__n100.value}") @@ -196,13 +253,26 @@ def create_collapsed_centerline(): f"Created {River_N100.centerline_pruning_loop__collapsed_hydropolygon__n100.value}" ) + arcpy.management.FeatureVerticesToPoints( + in_features=River_N100.centerline_pruning_loop__collapsed_hydropolygon__n100.value, + out_feature_class=River_N100.centerline_pruning_loop__collapsed_hydropolygon_points__n100.value, + point_location="BOTH_ENDS", + ) + + custom_arcpy.select_location_and_make_permanent_feature( + input_layer=River_N100.centerline_pruning_loop__collapsed_hydropolygon_points__n100.value, + overlap_type=custom_arcpy.OverlapType.INTERSECT.value, + select_features=River_N100.centerline_pruning_loop__water_features_shared_boundaries__n100.value, + output_name=River_N100.centerline_pruning_loop__collapsed_hydropolygon_points_selected__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, + River_N100.centerline_pruning_loop__collapsed_hydropolygon_points_selected__n100.value, "END", - "100 Meters", + "3000 Meters", ] ], ) @@ -251,6 +321,13 @@ def filter_complicated_lakes(): ], output=River_N100.centerline_pruning_loop__river_inlets_points_merged__n100.value, ) + print( + "NB! Need to create a logic to fix missing inlet nodes between interconnected waterfeature polygons" + ) + """ + Currently it only makes one node between two water features but some places this would lead to missing node connections + as more nodes are needed. + """ custom_arcpy.select_location_and_make_permanent_feature( input_layer=River_N100.centerline_pruning_loop__centerline_start_end_vertex__n100.value, @@ -294,7 +371,7 @@ def filter_complicated_lakes(): ) target_features = ( - River_N100.centerline_pruning_loop__water_features_river_final_selection__n100.value + River_N100.centerline_pruning_loop__water_features_processed__n100.value ) join_features_1 = ( River_N100.centerline_pruning_loop__river_inlets_points_merged__n100.value @@ -383,6 +460,76 @@ def filter_complicated_lakes(): output_name=River_N100.centerline_pruning_loop__complex_water_features__n100.value, ) + custom_arcpy.select_location_and_make_permanent_feature( + input_layer=centerline, + overlap_type=custom_arcpy.OverlapType.HAVE_THEIR_CENTER_IN.value, + select_features=River_N100.centerline_pruning_loop__simple_water_features__n100.value, + output_name=River_N100.centerline_pruning_loop__simple_centerlines__n100.value, + ) + + custom_arcpy.select_location_and_make_permanent_feature( + input_layer=centerline, + overlap_type=custom_arcpy.OverlapType.HAVE_THEIR_CENTER_IN.value, + select_features=River_N100.centerline_pruning_loop__complex_water_features__n100.value, + output_name=River_N100.centerline_pruning_loop__complex_centerlines__n100.value, + ) + + create_lake_centerline_feature = FeatureClassCreator( + template_fc=input_rivers, + input_fc=River_N100.centerline_pruning_loop__simple_centerlines__n100.value, + output_fc=River_N100.centerline_pruning_loop__finnished_centerlines__n100.value, + object_type="POLYLINE", + delete_existing=True, + ) + create_lake_centerline_feature.run() + + +def create_feature_class(): + # create_lake_centerline_feature = FeatureClassCreator( + # template_fc=input_rivers, + # input_fc=River_N100.centerline_pruning_loop__simple_centerlines__n100.value, + # output_fc=River_N100.centerline_pruning_loop__finnished_centerlines__n100.value, + # object_type="POLYLINE", + # delete_existing=True, + # ) + # create_lake_centerline_feature.run() + + input_feature_class = ( + River_N100.centerline_pruning_loop__water_features_processed__n100.value + ) + + dissolve_output = f"{input_feature_class}_dissolved" + arcpy.analysis.PairwiseDissolve( + in_features=input_feature_class, + out_feature_class=dissolve_output, + dissolve_field=["shape_Length", "shape_Area"], + multi_part="MULTI_PART", + ) + + eliminate_polygon_part_output = f"{input_feature_class}_eliminate_polygon_part" + + arcpy.management.EliminatePolygonPart( + in_features=dissolve_output, + out_feature_class=eliminate_polygon_part_output, + condition="AREA_OR_PERCENT", + part_area="1000000", + part_area_percent="99", + part_option="CONTAINED_ONLY", + ) + + polygon_islands = f"{input_feature_class}_polygon_islands" + custom_arcpy.select_location_and_make_permanent_feature( + input_layer=input_feature_class, + overlap_type=custom_arcpy.OverlapType.COMPLETELY_WITHIN.value, + select_features=eliminate_polygon_part_output, + output_name=polygon_islands, + ) + + arcpy.topographic.EliminatePolygon( + in_features=polygon_islands, + surrounding_features=input_feature_class, + ) + if __name__ == "__main__": main()