from typing import Dict, Literal, List, Any, Optional, Union
import arcpy
import os
import json
from composition_configs import io_types
[docs]
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
[docs]
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.")
[docs]
def create_feature_class(template_feature: str, new_feature: str) -> None:
"""Creates a new feature from a templete feature class"""
out_path, out_name = os.path.split(new_feature)
delete_feature(input_feature=new_feature)
arcpy.management.CreateFeatureclass(
out_path=out_path, out_name=out_name, template=template_feature
)
[docs]
def delete_feature(input_feature):
"""Deletes a feature class if it exists."""
if arcpy.Exists(input_feature):
arcpy.management.Delete(input_feature)
[docs]
def compare_feature_classes(feature_class_1, feature_class_2):
# Get count of features in the first feature class
count_fc1 = int(arcpy.GetCount_management(feature_class_1)[0])
# Get count of features in the second feature class
count_fc2 = int(arcpy.GetCount_management(feature_class_2)[0])
# Calculate the difference
difference = count_fc2 - count_fc1
# Determine the appropriate message
if difference > 0:
print(f"There are {difference} more features in the second feature class.")
elif difference < 0:
print(f"There are {-difference} fewer features in the second feature class.")
else:
print("Both feature classes have the same number of features.")
[docs]
def reclassify_value(
input_table: str,
target_field: str,
target_value: str,
replace_value: str,
reference_field: str = None,
logic_format: str = "PYTHON3",
) -> None:
update_missing_block = f"""def Reclass(value):
if value == {target_value}:
return {replace_value}
else:
return value
"""
value_field = reference_field if reference_field else target_field
arcpy.management.CalculateField(
in_table=input_table,
field=target_field,
expression=f"Reclass(!{value_field}!)",
expression_type=logic_format,
code_block=update_missing_block,
)
[docs]
def deleting_added_field_from_feature_to_x(
input_file_feature: str = None,
field_name_feature: str = None,
) -> None:
directory_path, split_file_name = os.path.split(field_name_feature)
print("Directory path:", directory_path)
print("Filename:", split_file_name)
esri_added_prefix = "FID_"
generated_field_name = f"{esri_added_prefix}{split_file_name}"[:64]
generated_field_name_with_underscore = (
f"{generated_field_name[:-1]}_"
if len(generated_field_name) == 64
else generated_field_name
)
# List fields in the feature
feature_fields = arcpy.ListFields(input_file_feature)
list_of_fields = [field_object.name for field_object in feature_fields]
if generated_field_name in list_of_fields:
field_to_delete = generated_field_name
elif generated_field_name_with_underscore in list_of_fields:
field_to_delete = generated_field_name_with_underscore
else:
raise ValueError(
f"""
The generated field name by Esri Geoprocessing tool did not match the predicted naming convention for the file:\n
{input_file_feature}\n
Expected name (without underscore): {generated_field_name}\n
Expected name (with underscore): {generated_field_name_with_underscore}\n
Available fields:\n{list_of_fields}
"""
)
try:
arcpy.management.DeleteField(
in_table=input_file_feature,
drop_field=field_to_delete,
)
print(f"Deleted field: {field_to_delete}")
except Exception as e:
print(f"An error occurred: {e}")
[docs]
def get_all_fields(input_fields, *added_field_sets):
"""
Combines an input fields list with any number of additional field sets.
Assumes each added field set is a list of [field_name, field_type] pairs.
"""
combined = list(input_fields)
for fields in added_field_sets:
combined.extend([item[0] for item in fields])
return combined
[docs]
def count_objects(input_layer):
count = int(arcpy.management.GetCount(input_layer).getOutput(0))
return count
[docs]
def delete_fields_if_exist(
feature_class_path: str, fields_to_delete: list[str]
) -> None:
"""
Deletes specified fields from a feature class if they exist.
Args:
feature_class_path (str): Path to the feature class.
fields_to_delete (list[str]): Fields to delete if present.
"""
if not arcpy.Exists(feature_class_path):
print(f"Skipped field deletion: file does not exist -> {feature_class_path}")
return
for field_name in fields_to_delete:
try:
if arcpy.ListFields(feature_class_path, field_name):
arcpy.management.DeleteField(feature_class_path, field_name)
print(f"Deleted field '{field_name}' from {feature_class_path}")
except arcpy.ExecuteError as e:
print(f"Failed to delete '{field_name}' from {feature_class_path}: {e}")
[docs]
def write_dict_to_json(path: str, dict_data: dict) -> None:
"""Writes a dictionary to a specified JSON file path."""
with open(path, "w", encoding="utf-8") as f:
json.dump(dict_data, f, indent=4)
[docs]
def feature_has_rows(feature: Union[str, io_types.GdbIOArg]) -> bool:
if not feature:
return False
if not arcpy.Exists(feature):
return False
return count_objects(input_layer=feature) > 0
[docs]
def print_feature_info(file_path: io_types.GdbIOArg, description: str) -> None:
"""Helper for consistent print formatting of feature diagnostics."""
has_rows = feature_has_rows(file_path)
count_or_status = count_objects(file_path) if has_rows else has_rows
print(f"\n{description}: {count_or_status}")
print(f"For file path:\n {file_path}\n")