Select by many attributes and then zoom to selected

2966
14
04-27-2018 02:38 AM
MichaelMichaelides
New Contributor II

simple model builderMore complex modelGUI

Hello. I need the help of the community and if possible the help of an ESRI’s staff.

After days and days of searching and reading different posts in the net and many dead ends (broken links) in ESRI’s site, I couldn’t find a solution to my problem.

My problem is that I’m trying to give a tool to some colleagues (ArcMap 10.5), that don’t have any experience with GIS software, which would help them to zoom to a selected parcel by selecting a value from various drop-down lists and not writing the SQL query themselves.

The tool which I’m trying to build in Model Builder (coding is not my strong background), will take the following variables: District, Town/Village, Quarter, Block, Sheet, Plan, and Parcel Number. Combining all those variables make the parcel selection unique.

Those Variables are different columns within a single Feature Class called “Parcels”.

In my model I used a tool that I had from an old search (now is a broken link) called “ChooseFieldValueToolbox10.tbx”. This tool let me expose as a list, the values from a field. Then I found the code (that actually works) from another post in the community (https://community.esri.com/thread/8396 - thanks Paul Lang), to zoom to selected feature.

Through my many failed attempts I decided to make the model (for testing purposes) with only one variable and not use all the aforementioned variables, just to see if it works, which it does (screenshot 1). For example the user to select a District or to select a Town as different models each time.

The problem is when I want to make combination of all those parameters. I can’t force the model to select from current selection and then zoom to final selection.

I attached a screenshot (screenshot 2) of my model, with the use of the first two variables (District and Town/Village).

When I run the tool, at the interface (screenshot 3), the second selection (Value (2)) does not populate the values based on the first selection (Value), instead it shows all the values of the field (Field(2)), and then nothing runs as it supposed to do. For example if in the second “Select by attributes” tool (screenshot 2), I have as a “Selection Type = SUBSET_SELECTION”, nothing gets selected so the zoom doesn’t work. If in the second “Select by attributes” tool (screenshot 2), I have as a “Selection Type = ADD_TO_SELECTION”, it selects all the rows that have the same “Dist_code” and doesn’t take account the Town/Village.

Sorry for the long post but I tried to make my problem as clear as possible.

Any ideas?

0 Kudos
14 Replies
RandyBurton
MVP Alum

I've been studying the information you attached.  I haven't done a lot with model builder; the other post I mentioned was working with a script tool.  The script tool could be used in a model, but would also perform a specific function on its own.

You mention the code posted on 21 April.  That was an example of debugging the ToolValidator section of a script tool.  Since this section is inside a script tool, it is difficult to see what is happening in the module.  The first 16 lines set up the ToolValidator class and provide it with some test values.  The last few lines set up a validator and run the updateParameters and updateMessages blocks.  Once the code runs as expected, then just the middle section is pasted into the script tools validation section.

You ask if the tool will work with an SDE feature class that will have a joined table.  I haven't worked with an SDE feature class, but I would think it probably would.  Perhaps Jake Skinner‌ would have a better idea.

Regarding your remaining question, in the other post we were modifying the script tool to do multiple fields, in this case 3.  We just need to add an additional 4 fields for your project.

0 Kudos
RandyBurton
MVP Alum

I've worked with the sample data that you have provided and have a script tool that might give you an idea for your project.  It is based on code in this thread: Conditional Drop Down Lists - Tool Validator.  If a feature is selected that has the required fields, it will allow the user to drill down to a parcel.  Currently the script just selects the parcel, but code can be added to zoom to it.

Most of the  work is done in the updateParameters module of the ToolValidator class:

class ToolValidator(object):

    def __init__(self):
        import arcpy
        self.params = arcpy.GetParameterInfo()

    def initializeParameters(self):
        # (initializeParameters code here)
        return

    def updateParameters(self):

        if self.params[0].value: # feature has been selected
            useFields = [u'DIST_CODE', u'VIL_CODE', u'QRTR_CODE', u'BLCK_CODE', u'SHEET', u'PLAN', u'PARCEL_NBR'] # name of fields used by tool - in order
            desc = arcpy.Describe(self.params[0].value)  # information about the input feature
            fieldNames = { f.name: f.type for f in desc.fields } # dictionary used to keep field names and types together

            if set(useFields).issubset(fieldNames.keys()): # all fields were found by tool

                fc, c_1 = str(self.params[0].value), useFields[0] # DIST_CODE
                self.params[1].filter.list = [str(val) for val in sorted(set(row.getValue(c_1) for row in arcpy.SearchCursor(fc,fields=c_1)))]
                if self.params[1].value not in self.params[1].filter.list:
                    self.params[1].value = self.params[1].filter.list[0]

                fc, c_2 = str(self.params[0].value), useFields[1] # VIL_CODE
                if fieldNames[useFields[0]] == u'String': 
                    wc2 = "{} = '{}'".format(useFields[0], str(self.params[1].value)) # field is string
                else: 
                    wc2 = "{} = {}".format(useFields[0], str(self.params[1].value)) # field assumed to be a number field
                self.params[2].filter.list = [str(val) for val in sorted(set(row.getValue(c_2) for row in arcpy.SearchCursor(fc,fields=c_2,where_clause=wc2)))]
                if self.params[2].value not in self.params[2].filter.list:
                    self.params[2].value = self.params[2].filter.list[0]

                fc, c_3 = str(self.params[0].value), useFields[2] # QRTR_CODE
                if fieldNames[useFields[1]] == u'String':
                    wc3 = "{} AND {} = '{}'".format(wc2, useFields[1], str(self.params[2].value)) # field is string
                else:
                    wc3 = "{} AND {} = {}".format(wc2, useFields[1], str(self.params[2].value)) # field assumed to be a number field
                self.params[3].filter.list = [str(val) for val in sorted(set(row.getValue(c_3) for row in arcpy.SearchCursor(fc,fields=c_3,where_clause=wc3)))]
                if self.params[3].value not in self.params[3].filter.list:
                    self.params[3].value = self.params[3].filter.list[0]

                fc, c_4 = str(self.params[0].value), useFields[3] # BLCK_CODE
                if fieldNames[useFields[2]] == u'String':
                    wc4 = "{} AND {} = '{}'".format(wc3, useFields[2], str(self.params[3].value)) # field is string
                else:
                    wc4 = "{} AND {} = {}".format(wc3, useFields[2], str(self.params[3].value)) # field assumed to be a number field
                self.params[4].filter.list = [str(val) for val in sorted(set(row.getValue(c_4) for row in arcpy.SearchCursor(fc,fields=c_4,where_clause=wc4)))]
                if self.params[4].value not in self.params[4].filter.list:
                    self.params[4].value = self.params[4].filter.list[0]

                fc, c_5 = str(self.params[0].value), useFields[4] # SHEET
                if fieldNames[useFields[3]] == u'String':
                    wc5 = "{} AND {} = '{}'".format(wc4, useFields[3], str(self.params[4].value)) # field is string
                else:
                    wc5 = "{} AND {} = {}".format(wc4, useFields[3], str(self.params[4].value)) # field assumed to be a number field
                self.params[5].filter.list = [str(val) for val in sorted(set(row.getValue(c_5) for row in arcpy.SearchCursor(fc,fields=c_5,where_clause=wc5)))]
                if self.params[5].value not in self.params[5].filter.list:
                    self.params[5].value = self.params[5].filter.list[0]

                fc, c_6 = str(self.params[0].value), useFields[5] # PLAN
                if fieldNames[useFields[4]] == u'String':
                    wc6 = "{} AND {} = '{}'".format(wc5, useFields[4], str(self.params[5].value)) # field is string
                else:
                    wc6 = "{} AND {} = {}".format(wc5, useFields[4], str(self.params[5].value)) # field assumed to be a number field
                self.params[6].filter.list = [str(val) for val in sorted(set(row.getValue(c_6) for row in arcpy.SearchCursor(fc,fields=c_6,where_clause=wc6)))]
                if self.params[6].value not in self.params[6].filter.list:
                    self.params[6].value = self.params[6].filter.list[0]

                fc, c_7 = str(self.params[0].value), useFields[6] # PARCEL_NBR
                if fieldNames[useFields[5]] == u'String':
                    wc7 = "{} AND {} = '{}'".format(wc6, useFields[5], str(self.params[6].value)) # field is string
                else:
                    wc7 = "{} AND {} = {}".format(wc6, useFields[5], str(self.params[6].value)) # field assumed to be a number field
                print wc7 # # Debug # #
                self.params[7].filter.list = [str(val) for val in sorted(set(row.getValue(c_7) for row in arcpy.SearchCursor(fc,fields=c_7,where_clause=wc7)))]
                if self.params[7].value not in self.params[7].filter.list:
                    self.params[7].value = self.params[7].filter.list[0]

                # output completed whereClause
                if fieldNames[useFields[6]] == u'String': 
                    self.params[8].value = "{} AND {} = '{}'".format(wc7, useFields[6], str(self.params[7].value)) # where normally assumes string values for fields
                else:
                    self.params[8].value = "{} AND {} = {}".format(wc7, useFields[6], str(self.params[7].value)) # Setting assumed to be a number field

            else: # at least one of the field names does not exist in feature; using the parameter to hold an error message

                if  useFields[0] not in fieldNames.keys(): # DIST_CODE
                    self.params[1].value = "ERROR: Field '{}' not in selected feature class".format(useFields[0])
                if  useFields[1] not in fieldNames.keys(): # VIL_CODE
                    self.params[2].value = "ERROR: Field '{}' not in selected feature class".format(useFields[1])
                if  useFields[2] not in fieldNames.keys(): # QRTR_CODE
                    self.params[3].value = "ERROR: Field '{}' not in selected feature class".format(useFields[2])
                if  useFields[3] not in fieldNames.keys(): # BLCK_CODE
                    self.params[4].value = "ERROR: Field '{}' not in selected feature class".format(useFields[3])
                if  useFields[4] not in fieldNames.keys(): # SHEET
                    self.params[5].value = "ERROR: Field '{}' not in selected feature class".format(useFields[4])
                if  useFields[5] not in fieldNames.keys(): # PLAN
                    self.params[6].value = "ERROR: Field '{}' not in selected feature class".format(useFields[5])
                if  useFields[6] not in fieldNames.keys(): # PARCEL_NBR
                    self.params[7].value = "ERROR: Field '{}' not in selected feature class".format(useFields[6])

        return

    def updateMessages(self):
        
        self.params[0].clearMessage() # clear all error messages
        self.params[1].clearMessage()
        self.params[2].clearMessage()
        self.params[3].clearMessage()
        self.params[4].clearMessage()
        self.params[5].clearMessage()
        self.params[6].clearMessage()
        self.params[7].clearMessage()
        
        if self.params[0].value is not None: # set error message if field not in feature so user can correct problem
            useFields = [u'DIST_CODE', u'VIL_CODE', u'QRTR_CODE', u'BLCK_CODE', u'SHEET', u'PLAN', u'PARCEL_NBR'] # name of fields used by tool - in order

            if self.params[7].value is not None:
                if self.params[7].value.startswith("ERROR:"):
                    # self.params[7].value = None # clear error message in parameter value, if desired
                    self.params[0].setErrorMessage("Input FC '{}' does not contain field '{}'".format(self.params[0].value, useFields[6]))
                    self.params[7].setErrorMessage("Field '{}' is not in Input FC '{}'".format(useFields[6], self.params[0].value))

            if self.params[6].value is not None:
                if self.params[6].value.startswith("ERROR:"):
                    # self.params[6].value = None # clear error message in parameter value, if desired
                    self.params[0].setErrorMessage("Input FC '{}' does not contain field '{}'".format(self.params[0].value, useFields[5]))
                    self.params[6].setErrorMessage("Field '{}' is not in Input FC '{}'".format(useFields[5], self.params[0].value))

            if self.params[5].value is not None:
                if self.params[5].value.startswith("ERROR:"):
                    # self.params[5].value = None # clear error message in parameter value, if desired
                    self.params[0].setErrorMessage("Input FC '{}' does not contain field '{}'".format(self.params[0].value, useFields[4]))
                    self.params[5].setErrorMessage("Field '{}' is not in Input FC '{}'".format(useFields[4], self.params[0].value))

            if self.params[4].value is not None:
                if self.params[4].value.startswith("ERROR:"):
                    # self.params[4].value = None # clear error message in parameter value, if desired
                    self.params[0].setErrorMessage("Input FC '{}' does not contain field '{}'".format(self.params[0].value, useFields[3]))
                    self.params[4].setErrorMessage("Field '{}' is not in Input FC '{}'".format(useFields[3], self.params[0].value))

            if self.params[3].value is not None:
                if self.params[3].value.startswith("ERROR:"):
                    # self.params[3].value = None # clear error message in parameter value, if desired
                    self.params[0].setErrorMessage("Input FC '{}' does not contain field '{}'".format(self.params[0].value, useFields[2]))
                    self.params[3].setErrorMessage("Field '{}' is not in Input FC '{}'".format(useFields[2], self.params[0].value))

            if self.params[2].value is not None:
                if self.params[2].value.startswith("ERROR:"):
                    # self.params[2].value = None # clear error message in parameter value, if desired
                    self.params[0].setErrorMessage("Input FC '{}' does not contain field '{}'".format(self.params[0].value, useFields[1]))
                    self.params[2].setErrorMessage("Field '{}' is not in Input FC '{}'".format(useFields[1], self.params[0].value))

            if self.params[1].value is not None:
                if self.params[1].value.startswith("ERROR:"):
                    # self.params[1].value = None # clear error message in parameter value, if desired
                    self.params[0].setErrorMessage("Input FC '{}' does not contain field '{}'".format(self.params[0].value, useFields[0]))
                    self.params[1].setErrorMessage("Field '{}' is not in Input FC '{}'".format(useFields[0], self.params[0].value))

        return
‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

The script I used for testing:

import arcpy

# to drill down to parcel number, use fields: DIST_CODE, VIL_CODE, QRTR_CODE, BLCK_CODE, SHEET, PLAN, PARCEL_NBR
# District, Town/Village, Quarter, Block, Sheet, Plan, and Parcel Number
inFC = arcpy.GetParameterAsText(0) # input feature class (Input, Data Type 'Feature Layer')
dist_code = arcpy.GetParameterAsText(1) # selected value of DIST_CODE field (Input, Data Type 'String')
vil_code = arcpy.GetParameterAsText(2) # selected value of VIL_CODE field (Input, Data Type 'String')
qrtr_code = arcpy.GetParameterAsText(3) # selected value of QRTR_CODE field (Input, Data Type 'String')
blck_code = arcpy.GetParameterAsText(4) # selected value of BLCK_CODE field (Input, Data Type 'String')
sheet_val = arcpy.GetParameterAsText(5) # selected value of SHEET field (Input, Data Type 'String')
plan_val = arcpy.GetParameterAsText(6) # selected value of PLAN field (Input, Data Type 'String')
parcel_nbr = arcpy.GetParameterAsText(7) # selected value of PARCEL_NBR field (Input, Data Type 'String')
whereClause = arcpy.GetParameterAsText(8) # set by ToolValidator updateParameters (Output, Derived, Data Type 'String')

arcpy.AddMessage("Where clause used: {}".format(whereClause)) 
arcpy.SelectLayerByAttribute_management(inFC, "NEW_SELECTION", where_clause=whereClause)

n = arcpy.GetCount_management(inFC)
arcpy.AddMessage("Number of records selected: {}".format(n))‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

And a couple of screen shots.

Tool Photo

Screen shot

I've attached the code and toolbox.  You may need to edit the location of the script in the toolbox.  I've also included a debug version of the ToolValidator code should you wish examine it.

I'm not sure how a join would affect the tool as the field names sometimes get appended with the table name.

MichaelMichaelides
New Contributor II

Mr. Burton hello. THANK YOU so much for your help and effort.

I gave it a first try and works perfect.

I will make some more testings and i will let you know.

Have a nice weekend.

Regards 

Mike

0 Kudos
MichaelMichaelides
New Contributor II

Mr Burton hello again. I tried your script and i incorporated teo models of mine. 

Though it does work very nice when my data are a small amount of the original, when i use the SDE feature class where theere are around 1,5 million parcels then things get very difficult. I mean it takes a lot of time to show the values per field thus making it inconvinient to  use.

Any ideas how to improve the speed of the script?

Thank you for you time and efforts

Regards 

Mike

0 Kudos
RandyBurton
MVP Alum

I must admit that I have not worked with SDE feature classes.  I'm not sure if building indexes for the columns used by the tool will improve the speed.

The tool works to build a parcel UID by starting with a district code, adding a village code, quarter code, block code, etc. It might be possible to build a table with only the unique combinations of the first four parts of the parcel code that contains much fewer rows than the feature class.  Perhaps this would allow your user to start building a correct query that could then be used to make the final query.

I notice that the parcel UID might point to the sheet and plan numbers, and that the plan number would point to the  parcels that are involved.  It might be possible to reorder the fields queried and perhaps eliminate one.

These are just some initial thoughts.  Hope it helps.

0 Kudos