7 Replies Latest reply on Dec 5, 2012 12:08 PM by jkappa

    Zoom or Pan to Selected Features, But not All of Them!

    jkappa
      I get lots of these really simple maps, which take 10 minutes to make with a template, but the boss wants them in one minute! So I was told to write a tool to do this. I chose a Python Toolbox tool so I get to know this new 10.1 feature, and practice my Python a bit, at which I am a beginner--at best.
      My code, upon some manual input of some parameters, selects a parcel, reads the address, then intersects this parcel with a couple of layers, reads some attributes from them, and writes everything to the map's title block, but I ran into a brick wall now.
      Everything is fine, except that my code zooms not only to the subject parcel, but also to the selected features in the intersecting layers from which I need to read the attributes that relate to my parcel. I've tried (and tried) a couple of few methods to avoid zooming to the ancillary layers, but I cannot get it to work.
      In any case, here's the code, and I have entered some of the error messages as comments. So this is my desperate call for help to the Arcpy Brotherhood, because I'm drowning in Python.

      
      import arcpy, sys, os
      
      class Toolbox(object):
          def __init__(self):
              """Define the toolbox (the name of the toolbox is the name of the
              .pyt file)."""
              self.label = "Location Map Toolbox"
              self.alias = "Location Map"
      
              # List of tool classes associated with this toolbox
              self.tools = [FillTitleBlock]
      
              
      class FillTitleBlock(object):
          def __init__(self):
              """Define the tool (tool name is the name of the class)."""
              self.label = "Fill Title Block"
              self.description = "Fill Title Block"
              self.canRunInBackground = False
      
      
          # user project data input
          def getParameterInfo(self):
              """Define parameter definitions"""
              #params = None
              #return params
      
              ProjectNumber= arcpy.Parameter(
              displayName= "Project Number",
              name= "ProjectNumber",
              datatype= "GPString",
              parameterType= "Required",
              direction= "Input")
      
              ProjectTitle= arcpy.Parameter(
              displayName= "Project Title",
              name= "ProjectTitle",
              datatype= "GPString",
              parameterType= "Required",
              direction= "Input")
      
              ParcelID= arcpy.Parameter(
              displayName= "Parcel ID",
              name= "ParcelID",
              datatype= "GPString",
              parameterType= "Required",
              direction= "Input")
      
              parameters= [ProjectNumber, ProjectTitle, ParcelID]
              
              return parameters
      
      
          def isLicensed(self):
              """Set whether tool is licensed to execute."""
              return True
      
          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):
              """Modify the messages created by internal validation for each tool
              parameter.  This method is called after internal validation."""
              return
      
          def execute(self, parameters, messages):
              """The source code of the tool."""
      
              #Layout and Table of Contents variables
              mxd= arcpy.mapping.MapDocument(r"G:\PCM_Jorge\LOCATION_MAPS\0-MAPS\LocationMap.mxd")
              df = arcpy.mapping.ListDataFrames(mxd, "Base Map")[0]
              lyr= "GIS.GISADMIN.parcels"
              TAZ= "GIS.GISADMIN.TAZ_LRTP2009_2035_371"
              DIS= "CommissionDistrict"
      
              #Parameters
              ProjNum= parameters[0].valueAsText
              ProjTit= parameters[1].valueAsText
              ParcelID= parameters[2].valueAsText
      
              #Input title block data
              for txtelem in arcpy.mapping.ListLayoutElements(mxd, "TEXT_ELEMENT"):
                  if txtelem.name == "PRJ_NUM":
                      txtelem.text = ProjNum
                  elif txtelem.name == "PRJ_TIT":
                      txtelem.text = ProjTit
                  elif txtelem.name == "PID":
                      txtelem.text = ParcelID
      
              #Select parcel and zoom to it
              selPID= "\"VPARCEL\" = '" + ParcelID + "'"
              arcpy.SelectLayerByAttribute_management(lyr, "NEW_SELECTION", selPID)
      
              #Here comes the problem
              #df.zoomToSelectedFeatures(lyr) #option 1
              #without lyr, it zooms to all selected layers (below). With this argument, I get TypeError: zoomToSelectedFeatures() takes exactly 1 argument (2 given)
      
              df.panToExtent(lyr.getSelectedExtent()) #option 2
              # I get AttributeError: 'str' object has no attribute 'getSelectedExtent'
              df.scale = df.scale * 1.25
      
              arcpy.RefreshActiveView() #This used to be at the end of the script, but I moved it here hoping the layer selections below would not get zoomed to: FAIL
      
              
              #Input address, TAZ, and commission district
              arcpy.MakeFeatureLayer_management(lyr, "ParSel", selPID)
              parLyr= arcpy.SelectLayerByLocation_management(lyr,"ARE_IDENTICAL_TO","ParSel")
              rows= arcpy.SearchCursor(parLyr)
              row= rows.next()
              lblAddress= arcpy.mapping.ListLayoutElements(mxd, "TEXT_ELEMENT", "PRJ_ADD")[0]
      
              if row.isNull("PHYSADDR") == True:
                  lblAddress.text= "None found."
              else:
                  lblAddress.text= row.getValue("PHYSADDR")
             
              #The ancillary layers 
              TAZLyr= arcpy.SelectLayerByLocation_management(TAZ,"INTERSECT","ParSel")
              rows= arcpy.SearchCursor(TAZLyr)
              row= rows.next()
              lblTAZ= arcpy.mapping.ListLayoutElements(mxd, "TEXT_ELEMENT", "TAZ")[0]
              lblTAZ.text= row.getValue("ctyTAZ")
      
              DISLyr= arcpy.SelectLayerByLocation_management(DIS,"INTERSECT","ParSel")
              rows= arcpy.SearchCursor(DISLyr)
              row= rows.next()
              lblDIS= arcpy.mapping.ListLayoutElements(mxd, "TEXT_ELEMENT", "DIS")[0]
              lblDIS.text= row.getValue("COMMISSION")
                      
              
                      
      ##      mxd.saveACopy(r"C:\TEMP\\" + ProjNum + ".mxd") #directory must exist
      ##      arcpy.mapping.ExportToPDF(mxd, r"C:\TEMP\\" + ProjNum + ".pdf") 
      
      ##      mxd.save() ##this will overwrite the template
              
              del mxd, row, rows, lblAddress, lblTAZ, lblDIS,
             
              return
      
        • Re: Zoom or Pan to Selected Features, But not All of Them!
          jkappa
          I fixed it by deselecting right after reading the ancillary layers like this:
          arcpy.SelectLayerByAttribute_management(DISLyr, "REMOVE_FROM_SELECTION", "")

          Smells like victory!
          • Re: Zoom or Pan to Selected Features, But not All of Them!
            andywinngis
            I fixed it by deselecting right after reading the ancillary layers like this:
            arcpy.SelectLayerByAttribute_management(DISLyr, "REMOVE_FROM_SELECTION", "")

            Smells like victory!


            Jorge, thanks for posting this is exactly what I need to do, (well almost). If you consider yourself a beginner, then I guess I'm a complete neophyte/idiot, but I'm hoping I can get somewhere with your starting point. What I would like to do is to have the user input a parcel ID have the map pan to that parcel at a set scale and pull the owner name and quarter section from those two tables and put that into a title box.

            Are you creating a toolbox with the code or did you just create Toolbox called Location Map Toolbox and then add a new script with that same name?

            In the Class FillTitleBlock does the user fill out the parameters ProjectNumber, ProjectTitle, ParcelID? How does that work?

            I'm also having a hard time figuring out where I need to make changes to variables other than the obivous ones such as the .mxd. Where you specify your lyr = "GIS.GISADMIN.parcels" and your other two layers, are those just your layer names in the table of contents?

            And is the title block named "TEXT_ELEMENT" in you layout?

            I know that is a ton of questions but I hope you could help me out.

            Andy
            • Re: Zoom or Pan to Selected Features, But not All of Them!
              andywinngis
              Jorge, thanks for posting this is exactly what I need to do, (well almost). If you consider yourself a beginner, then I guess I'm a complete neophyte/idiot, but I'm hoping I can get somewhere with your starting point. What I would like to do is to have the user input a parcel ID have the map pan to that parcel at a set scale and pull the owner name and quarter section from those two tables and put that into a title box.

              Are you creating a toolbox with the code or did you just create Toolbox called Location Map Toolbox and then add a new script with that same name?

              In the Class FillTitleBlock does the user fill out the parameters ProjectNumber, ProjectTitle, ParcelID? How does that work?

              I'm also having a hard time figuring out where I need to make changes to variables other than the obivous ones such as the .mxd. Where you specify your lyr = "GIS.GISADMIN.parcels" and your other two layers, are those just your layer names in the table of contents?

              And is the title block named "TEXT_ELEMENT" in you layout?

              I know that is a ton of questions but I hope you could help me out.

              Andy


              Also how does the select parcel line work? Where do you get the "\" VPARCEL\" from what does that mean/do? I'm assuming that ParcelID is your field for parcel IDs in your parcel attribute table?
              • Re: Zoom or Pan to Selected Features, But not All of Them!
                jkappa
                Jorge, thanks for posting this is exactly what I need to do, (well almost). If you consider yourself a beginner, then I guess I'm a complete neophyte/idiot, but I'm hoping I can get somewhere with your starting point. What I would like to do is to have the user input a parcel ID have the map pan to that parcel at a set scale and pull the owner name and quarter section from those two tables and put that into a title box.


                Well, I have been a beginner for quite some time. Yes, it sounds like you can take this toolbox, change the paths to your data, rework the variables, adjust the arguments in the functions, and you will be all set!

                Are you creating a toolbox with the code or did you just create Toolbox called Location Map Toolbox and then add a new script with that same name?


                Yes, but not just a Toolbox, this is a Python Toolbox, new for 10.1, which provides you with an empty code framework to which you add your code for the tools and parameters. It's just a text file with a .pyt extension.

                In the Class FillTitleBlock does the user fill out the parameters ProjectNumber, ProjectTitle, ParcelID? How does that work?


                Yes, the work requests come via email, and we have trained the requesters to provide those three pieces of data which get copied and pasted into the tool parameters form. The first two go directly to the title block, and the parcel ID goes into the title block, but also into a query which will allow you to select the subject parcel--look for the variable selPID.

                I'm also having a hard time figuring out where I need to make changes to variables other than the obivous ones such as the .mxd. Where you specify your lyr = "GIS.GISADMIN.parcels" and your other two layers, are those just your layer names in the table of contents?


                Yes. A good way to double check these names, is to run a dummy function in the Python console in ArcGIS, and enter for example arcpy.selectlayerbyattribute_management( and at this point the Python version of Intellisense will offer you a dropdown from where you can pick a layer, once you picked, the correct sintax will be written for you.

                And is the title block named "TEXT_ELEMENT" in you layout?


                No, TEXT_ELEMENT are each one of the labels whose text property is going to be changed to the attribut value you will be reading from the layer table with your search cursor. Hit the Help menu and paste ListLayoutElements, this will bring up a table which will make abundantly clear which arguments this function takes.


                I know that is a ton of questions but I hope you could help me out.


                Look, with this code here as a start up, you're almost done, just figure out how to insert your paths and adjust the variables.
                It took me like three days to make it work, but the map making time went from 10 minutes to under 1.
                • Re: Zoom or Pan to Selected Features, But not All of Them!
                  jkappa
                  Also how does the select parcel line work? Where do you get the "\" VPARCEL\" from what does that mean/do? I'm assuming that ParcelID is your field for parcel IDs in your parcel attribute table?


                  VPARCEL is just the field name whose attribute values are the parcel ID's. That's how you query the subject parcel ID into the variable selPID.
                  See if you can make it work. If you need to, I can paste here a more updated version of the code. It works great.
                  • Re: Zoom or Pan to Selected Features, But not All of Them!
                    andywinngis
                    Jorge,

                    Thank you for your reply. I've created a PythonToolbox and modified the script but I'm not sure where that "fix" that you added is supposed to go after #Here comes the problem

                    My syntax checks out so far in ArcCatalog, but the tool still has a red X thru it.

                    I'm attaching a screen grab of ArcCatalog as well as my script. I'd really appreciate any further help if you have time.

                    Thanks in advance!

                    import arcpy


                    class Toolbox(object):
                        def __init__(self):
                            """Define the toolbox (the name of the toolbox is the name of the
                            .pyt file)."""
                            self.label = "Location Map Toolbox"
                            self.alias = "Location Map"

                            # List of tool classes associated with this toolbox
                            self.tools = [FillTitleBlock]


                    class Tool(object):
                        def __init__(self):
                            """Define the tool (tool name is the name of the class)."""
                            self.label = "Fill Title Block"
                            self.description = "Fill Title Block"
                            self.canRunInBackground = False

                    # user project data input
                    def getParameterInfo(self):
                            """Define parameter definitions"""
                            #params = None
                            #return params

                            OwnerName= arcpy.Parameter(
                            displayName= "Owner Name",
                            name= "OwnerName",
                            datatype= "GPString",
                            parameterType= "Required",
                            direction= "Input")

                            ParcelID= arcpy.Parameter(
                            displayName= "Parcel ID",
                            name= "ParcelID",
                            datatype= "GPString",
                            parameterType= "Required",
                            direction= "Input")

                            parameters= [OwnerName, ParcelID]
                           
                            return parameters


                    def isLicensed(self):
                        """Set whether tool is licensed to execute."""
                        return True
                    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):
                        """Modify the messages created by internal validation for each tool
                        parameter.  This method is called after internal validation."""
                        return

                        def execute(self, parameters, messages):
                            """The source code of the tool."""
                           
                    #Layout and Table of Contents variables
                           
                    mxd= arcpy.mapping.MapDocument(r"R:\lwcd\_land\GIS\Maps\Templates\LocationMap.mxd")
                            df = arcpy.mapping.ListDataFrames(mxd, "Base Map")[0]
                            lyr= "Winnebago Co Parcels"
                            USGS= "USGS 24k Topo Map Boundaries"
                            QSE= "Quarter Section"

                            #Parameters
                            OwnerName= parameters[1].valueAsText
                            ParcelID= parameters[2].valueAsText

                            #Input title block data
                            for txtelem in arcpy.mapping.ListLayoutElements(mxd, "TEXT_ELEMENT"):
                                if txtelem.name == "Owner_Name":
                                    txtelem.text = OwnerName
                                elif txtelem.name == "PID":
                                    txtelem.text = ParcelID

                            #Select parcel and zoom to it
                            selPID= "\"parno\" = '" + ParcelID + "'"
                            arcpy.SelectLayerByAttribute_management(lyr, "NEW_SELECTION", selPID)

                    #Input ParcelID, USGS Quad, and Quarter Section
                            arcpy.MakeFeatureLayer_management(lyr, "ParSel", selPID)
                            parLyr= arcpy.SelectLayerByLocation_management(lyr,"ARE_IDENTICAL_TO","ParSel")
                            rows= arcpy.SearchCursor(parLyr)
                            row= rows.next()
                            lblOwner= arcpy.mapping.ListLayoutElements(mxd, "TEXT_ELEMENT", "Owner")[0]

                            if row.isNull("owner1") == True:
                                lblOwner.text= "None found."
                            else:
                                lblOwner.text= row.getValue("owner1")



                    #The layers that the information is pulled from for the title block
                            USGSLyr= arcpy.SelectLayerByLocation_management(USGS,"INTERSECT","ParSel")
                            rows= arcpy.SearchCursor(USGSLyr)
                            row= rows.next()
                            lblUSGS= arcpy.mapping.ListLayoutElements(mxd, "TEXT_ELEMENT", "USGS")[0]
                            lblUSGS.text= row.getValue("QUAD NAME")

                            QSELyr= arcpy.SelectLayerByLocation_management(QSE,"INTERSECT","ParSel")
                            rows= arcpy.SearchCursor(QSELyr)
                            row= rows.next()
                            lblQSE= arcpy.mapping.ListLayoutElements(mxd, "TEXT_ELEMENT", "QSE")[0]
                            lblQSE.text= row.getValue("TAG")
                    • Re: Zoom or Pan to Selected Features, But not All of Them!
                      jkappa
                      I do it directly in ArcGIS, and have the toolbox visible in the catalog window. If you don't see your tool, that means there's something wrong with the syntax. you can right click on the toolbox and select Check Syntax, and get some clues that way, and the error descriptions are pretty good, with line number of the offending piece of code. For this, I paste my code in a session of Notepad++ since I don't think IDLE has that, or I haven't figured out. Another thing I've notice about IDLE is that if you customize your colors for keywords, stings, comments, etc. it works great on .py files, but it doesn't in .pyt.
                      You have to keep executing your code for debugging, and make corrections based on the error messages. Also, every time you make changes to the code, you must right click on the tool and click on Refresh. I found that deselecting after reading is better than trying to filter your selections. Following is my finished working code, where you will see where the deselection lines go.
                      OK, is 7 minutes after 5, and I'm so out of here it's not even funny--YABADABADOO!!!
                      import arcpy, sys, os
                      
                      class Toolbox(object):
                          def __init__(self):
                              """Define the toolbox (the name of the toolbox is the name of the
                              .pyt file)."""
                              self.label = "Location Map Toolbox"
                              self.alias = "Location Map"
                      
                              # List of tool classes associated with this toolbox
                              self.tools = [FillTitleBlock]
                      
                              
                      class FillTitleBlock(object):
                          def __init__(self):
                              """Define the tool (tool name is the name of the class)."""
                              self.label = "Fill Title Block"
                              self.description = "Fill Title Block"
                              self.canRunInBackground = False
                      
                      
                          # user project data input
                          
                          def getParameterInfo(self):
                              """Define parameter definitions"""
                              #params = None
                              #return params
                      
                              ProjectNumber= arcpy.Parameter(
                              displayName= "Project Number",
                              name= "ProjectNumber",
                              datatype= "GPString",
                              parameterType= "Required",
                              direction= "Input")
                      
                              ProjectTitle= arcpy.Parameter(
                              displayName= "Project Title",
                              name= "ProjectTitle",
                              datatype= "GPString",
                              parameterType= "Required",
                              direction= "Input")
                      
                              ParcelID= arcpy.Parameter(
                              displayName= "Parcel ID",
                              name= "ParcelID",
                              datatype= "GPString",
                              parameterType= "Required",
                              direction= "Input")
                      
                              parameters= [ProjectNumber, ProjectTitle, ParcelID]
                              
                              return parameters
                      
                          def isLicensed(self):
                              """Set whether tool is licensed to execute."""
                              return True
                      
                          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):
                              """Modify the messages created by internal validation for each tool
                              parameter.  This method is called after internal validation."""
                              return
                      
                          def execute(self, parameters, messages):
                              """The source code of the tool."""
                      
                              #Layout and Table of Contents variables
                              mxd= arcpy.mapping.MapDocument("CURRENT")
                              df = arcpy.mapping.ListDataFrames(mxd, "Base Map")[0]
                              lyr= "GIS.GISADMIN.parcels"
                              ZIP= "GIS.GISADMIN.Zipcodes"
                              TAZ= "GIS.GISADMIN.TAZ_LRTP2009_2035_371"
                              DIS= "CommissionDistrict"
                              
                      
                              #Parameters
                              ProjNum= parameters[0].valueAsText
                              ProjTit= parameters[1].valueAsText
                              ParcelID= parameters[2].valueAsText
                      
                              #Input title block data
                              for txtelem in arcpy.mapping.ListLayoutElements(mxd, "TEXT_ELEMENT"):
                                  if txtelem.name == "PRJ_NUM":
                                      txtelem.text = ProjNum
                                  elif txtelem.name == "PRJ_TIT":
                                      txtelem.text = ProjTit
                                  elif txtelem.name == "PID":
                                      txtelem.text = ParcelID
                      
                              #Select parcel and zoom to it
                              selPID= "\"VPARCEL\" = '" + ParcelID + "'"
                              arcpy.SelectLayerByAttribute_management(lyr, "NEW_SELECTION", selPID)
                      
                              df.zoomToSelectedFeatures() #option 1
                              # Without lyr, it zooms to all selected layers (below). With this argument, I get TypeError: zoomToSelectedFeatures() takes exactly 1 argument (2 given)
                      
                              #df.panToExtent(lyr.getSelectedExtent()) #option 2
                              # I get AttributeError: 'str' object has no attribute 'getSelectedExtent'
                              
                              df.scale = df.scale * 1.25
                      
                              #arcpy.RefreshActiveView() #This used to be at the end of the script, but I moved it here hoping the layer selections below would not get zoomed too: FAIL
                      
                              #Input address, TAZ, and commission district
                              arcpy.MakeFeatureLayer_management(lyr, "ParSel", selPID)
                              parLyr= arcpy.SelectLayerByLocation_management(lyr,"ARE_IDENTICAL_TO","ParSel")
                              rows= arcpy.SearchCursor(parLyr)
                              row= rows.next()
                              lblAddress= arcpy.mapping.ListLayoutElements(mxd, "TEXT_ELEMENT", "PRJ_ADD")[0]
                      
                              if row.isNull("PHYSADDR") == True:
                                  lblAddress.text= "None found."
                              else:
                                  lblAddress.text= row.getValue("PHYSADDR")
                      
                                #Add zip code and city--this does not work well with some properties if adjacent or accross zip bounds
                      ##        CITLyr= arcpy.SelectLayerByLocation_management(ZIP,"INTERSECT","ParSel")
                      ##        rows= arcpy.SearchCursor(CITLyr)
                      ##        row= rows.next()
                      ##        lblCIT= arcpy.mapping.ListLayoutElements(mxd, "TEXT_ELEMENT", "PRJ_CIT")[0]
                      ##        lblCIT.text= row.getValue("CITY")
                      ##        arcpy.SelectLayerByAttribute_management(CITLyr, "REMOVE_FROM_SELECTION", "")
                      ##
                      ##        ZIPLyr= arcpy.SelectLayerByLocation_management(ZIP,"INTERSECT","ParSel")
                      ##        rows= arcpy.SearchCursor(ZIPLyr)
                      ##        row= rows.next()
                      ##        lblZIP= arcpy.mapping.ListLayoutElements(mxd, "TEXT_ELEMENT", "PRJ_ZIP")[0]
                      ##        lblZIP.text= row.getValue("ZIPCODE")
                      ##        arcpy.SelectLayerByAttribute_management(ZIPLyr, "REMOVE_FROM_SELECTION", "")
                             
                              TAZLyr= arcpy.SelectLayerByLocation_management(TAZ,"INTERSECT","ParSel")
                              list= []
                              lblTAZ= arcpy.mapping.ListLayoutElements(mxd, "TEXT_ELEMENT", "TAZ")[0]
                              rows= arcpy.SearchCursor(TAZLyr)
                              row= rows.next()
                              while row:
                                  ParTAZ= row.getValue("ctyTAZ")
                                  list.append(ParTAZ)
                                  row= rows.next()
                              lblTAZ.text= str(list).translate(None, '[]')
                              arcpy.SelectLayerByAttribute_management(TAZLyr, "REMOVE_FROM_SELECTION", "")
                      
                      
                              DISLyr= arcpy.SelectLayerByLocation_management(DIS,"INTERSECT","ParSel")
                              rows= arcpy.SearchCursor(DISLyr)
                              row= rows.next()
                              lblDIS= arcpy.mapping.ListLayoutElements(mxd, "TEXT_ELEMENT", "COM")[0]
                              lblDIS.text= row.getValue("DISTRICT")
                              arcpy.SelectLayerByAttribute_management(DISLyr, "REMOVE_FROM_SELECTION", "")
                      
                              arcpy.RefreshActiveView()
                                                      
                              mxd.saveACopy(r"G:\PCM_Jorge\LOCATION_MAPS\0-MAPS\\" + ProjNum + ".mxd") #directory must exist
                              arcpy.mapping.ExportToPDF(mxd, r"G:\PCM_Jorge\LOCATION_MAPS\0-MAPS\PDF\\" + ProjNum + ".pdf") 
                      
                      ##      mxd.save() ##this will overwrite the template
                              
                              del mxd, row, rows, lblAddress, lblTAZ, lblDIS,
                             
                              return