Esri/solutions-geoprocessing-toolbox

View on GitHub
clearing_operations/scripts/NumberFeaturesTool.py

Summary

Maintainability
D
2 days
Test Coverage
# coding: utf-8
'''
------------------------------------------------------------------------------
 Copyright 2017 Esri
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
   http://www.apache.org/licenses/LICENSE-2.0
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
------------------------------------------------------------------------------
 ==================================================
 NumberFeatures_new.py
 --------------------------------------------------
 requirements: ArcGIS X.X, Python 2.7 or Python 3.x
 author: ArcGIS Solutions
 contact: support@esri.com
 company: Esri
 ==================================================
 description: 
 Number Features Tool logic module. 
 Supports ClearingOperationsTools.pyt
 ==================================================
 history:
 9/6/2017 - mf - original coding/transfer from NumberFeatures.py
 ==================================================
'''


import os
import sys
import traceback
import arcpy
from . import Utilities

class NumberFeatures(object):
    '''
    Number input features within a specified area.
    '''
    def __init__(self):
        '''
        Number Features constructor
        '''
        self.label = "Number Features"
        self.description = "Number input point features within a selected area."

    def getParameterInfo(self):
        '''
        Define parameter definitions
        '''
        input_area_features = arcpy.Parameter(name='input_area_features',
                                              displayName='Input Area to Number',
                                              direction='Input',
                                              datatype='GPFeatureRecordSetLayer',
                                              parameterType='Required',
                                              enabled=True,
                                              multiValue=False)
        input_layer_file_path = os.path.join(os.path.dirname(os.path.dirname(__file__)),
                                             "layers",
                                             "RelativeNumberFeaturesAreaInput.lyr")
        input_area_features.value = input_layer_file_path

        input_number_features = arcpy.Parameter(name='input_point_features',
                                               displayName='Features to Number',
                                               direction='Input',
                                               datatype='GPFeatureLayer',
                                               parameterType='Required',
                                               enabled=True,
                                               multiValue=False)

        field_to_number = arcpy.Parameter(name='field_to_number',
                                          displayName='Field to Number',
                                          direction='Input',
                                          datatype='Field',
                                          parameterType='Optional',
                                          enabled=True,
                                          multiValue=False)
        field_to_number.filter.list = ['Short', 'Long', 'Double', 'Single']
        field_to_number.parameterDependencies = [input_number_features.name]

        output_features= arcpy.Parameter(name='output_features',
                                         displayName='Output Numbered Features',
                                         direction='Output',
                                         datatype='DEFeatureClass',
                                         parameterType='Optional',
                                         enabled=True,
                                         multiValue=False)
        #output_features.value = r"%scratchGDB%/numbered_features"
        output_features.symbology = os.path.join(os.path.dirname(os.path.dirname(__file__)),
                                                 "layers", "NumberedStructures.lyr")

        return [input_area_features,
                input_number_features,
                field_to_number,
                output_features]

    def updateParameters(self, parameters):
        '''
        Modify the values and properties of parameters before internal
        validation is performed.  This method is called whenever a parameter
        has been changed.
        '''
        return

    def updateMessages(self, parameters):
        '''
        '''
        return

    def execute(self, parameters, messages):
        '''
        '''

        output_fc = self.NumberFeatures(parameters[0].value,
                                        parameters[1].value,
                                        parameters[2].value,
                                        parameters[3].value)

        return output_fc


    def NumberFeatures(self,
                       areaToNumber,
                       pointFeatures,
                       numberingField,
                       outputFeatureClass):

        descPointFeatures = arcpy.Describe(pointFeatures)
        arcpy.AddMessage("pointFeatures: {0}".format(descPointFeatures.catalogPath))
            
        # If no output FC is specified, then set it a temporary one, as this will be copied to the input and then deleted.
        overwriteFC = False
        if not outputFeatureClass:
            DEFAULT_OUTPUT_LOCATION = r'%scratchGDB%\tempSortedPoints'
            outputFeatureClass = DEFAULT_OUTPUT_LOCATION
            overwriteFC = True
        else:
            descOutputFeatureClass = arcpy.Describe(outputFeatureClass)
            arcpy.AddMessage("outputFeatureClass: {0}".format(descOutputFeatureClass.catalogPath))
      
        # Sort layer by upper right across and then down spatially               
        arcpy.CopyFeatures_management(areaToNumber, os.path.join("in_memory","areaToNumber"))
        areaToNumber = os.path.join("in_memory","areaToNumber")

        DEBUG = True
        appEnvironment = None
        mxd, df, aprx, mp, mapList = None, None, None, None, None
        pointFeatureName = os.path.basename(str(pointFeatures))
        layerExists = False
        try:
            # Check that area to number is a polygon
            descArea = arcpy.Describe(areaToNumber)
            areaGeom = descArea.shapeType
            arcpy.AddMessage("Shape type: " + str(areaGeom))
            if (descArea.shapeType != "Polygon"):
                raise Exception("ERROR: The area to number must be a polygon.")

            #Checking the version of the Desktop Application
            appEnvironment = Utilities.GetApplication()
            if DEBUG == True: arcpy.AddMessage("App environment: " + appEnvironment)

            #Getting the layer name from the Table of Contents
            if appEnvironment == "ARCGIS_PRO":
                from arcpy import mp
                aprx = arcpy.mp.ArcGISProject("CURRENT")
                mapList = aprx.listMaps()[0]
                for lyr in mapList.listLayers():
                    if lyr.name == pointFeatureName:
                        layerExists = True
            #else:
            if appEnvironment == "ARCMAP":
                from arcpy import mapping
                mxd = arcpy.mapping.MapDocument('CURRENT')
                df = arcpy.mapping.ListDataFrames(mxd)[0]
                for lyr in arcpy.mapping.ListLayers(mxd):
                    if lyr.name == pointFeatureName:
                        layerExists = True

            if layerExists == False:
                arcpy.MakeFeatureLayer_management(pointFeatures, pointFeatureName)
            else:
                pointFeatureName = pointFeatures

            # Select all the points that are inside of area
            if areaToNumber:
                arcpy.AddMessage("Selecting points from {0} inside of the area {1}".format(pointFeatureName, areaToNumber))
            else:
                arcpy.AddMessage("Selecting points from {0} inside of the area {1}".format(pointFeatureName, areaToNumber.name))

            selectionLayer = arcpy.SelectLayerByLocation_management(pointFeatureName, "INTERSECT",
                                                                    areaToNumber, "#", "NEW_SELECTION")
            if DEBUG == True:
                arcpy.AddMessage("Selected " + str(arcpy.GetCount_management(pointFeatureName).getOutput(0)) + " points")

            arcpy.AddMessage("Sorting the selected points geographically, left to right, top to bottom")
            arcpy.Sort_management(pointFeatureName, outputFeatureClass, [["Shape", "ASCENDING"]])

            #global numberingField
            if numberingField is None or numberingField == "":
                fnames = [field.name for field in arcpy.ListFields(outputFeatureClass)]
                addfield = "Number"
                if addfield in fnames:
                    arcpy.AddMessage("Number field is already used")
                    numberingField = "Number"
                else:
                    arcpy.AddMessage("Add One")
                    arcpy.AddMessage("Adding Number field because no input field was given")
                    arcpy.AddField_management(outputFeatureClass,"Number","SHORT")
                    numberingField = "Number"
            else:
                pass

            # Number the fields
            arcpy.AddMessage("Numbering the fields")
            i = 1
            cursor = arcpy.UpdateCursor(outputFeatureClass) # Object: Error in parsing arguments for UpdateCursor
            for row in cursor:
                row.setValue(numberingField, i)
                cursor.updateRow(row)
                i += 1
            # Clear the selection
            arcpy.AddMessage("Clearing the selection")
            arcpy.SelectLayerByAttribute_management(pointFeatureName, "CLEAR_SELECTION")

            # Overwrite the Input Point Features, and then delete the temporary output feature class
            targetLayerName = ""
            if (overwriteFC):
                arcpy.AddMessage("Copying the features to the input, and then deleting the temporary feature class")
                desc = arcpy.Describe(pointFeatures)
                if hasattr(desc, "layer"):
                    overwriteFC = desc.layer.catalogPath
                else:
                    overwriteFC = desc.catalogPath

                arcpy.AddMessage("what is the numberingField: " + str(numberingField))
                addfield = "Number"
                fnames1 = [field.name for field in arcpy.ListFields(overwriteFC)]
                if addfield in fnames1:
                    arcpy.AddMessage("Number field is already used")
                else:
                    arcpy.AddMessage("Adding Number field to overwriteFC due to no input field")
                    arcpy.AddField_management(overwriteFC,"Number")
                    arcpy.AddMessage("Added Number field to overwriteFC")

                fields = (str(numberingField), "SHAPE@")

                overwriteCursor = arcpy.da.UpdateCursor(overwriteFC, fields)
                for overwriteRow in overwriteCursor:
                    sortedPointsCursor = arcpy.da.SearchCursor(outputFeatureClass, fields)
                    for sortedRow in sortedPointsCursor:
                        if sortedRow[1].equals(overwriteRow[1]):
                            overwriteRow[0] = sortedRow[0]
                    overwriteCursor.updateRow(overwriteRow)
                arcpy.Delete_management(outputFeatureClass)
                targetLayerName = pointFeatureName
            else:
                targetLayerName = os.path.basename(str(outputFeatureClass))

            # Workaround: don't set the outputFeatureClass if none was supplied to the tool
            if overwriteFC:
                outputFeatureClass = ''

            #Setting the correct output for the feature class
            arcpy.SetParameter(3, outputFeatureClass)

        except arcpy.ExecuteError:
            # Get the tool error messages
            msgs = arcpy.GetMessages()
            arcpy.AddError(msgs)
            print(msgs)

        except:
            # Get the traceback object
            tb = sys.exc_info()[2]
            tbinfo = traceback.format_tb(tb)[0]

            # Concatenate information together concerning the error into a message string
            pymsg = "PYTHON ERRORS:\nTraceback info:\n" + tbinfo + "\nError Info:\n" + str(sys.exc_info()[1])
            msgs = "ArcPy ERRORS:\n" + arcpy.GetMessages() + "\n"

            # Return python error messages for use in script tool or Python Window
            arcpy.AddError(pymsg)
            arcpy.AddError(msgs)

            # Print Python error messages for use in Python / Python Window
            print(pymsg + "\n")
            print(msgs)

        return outputFeatureClass