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

1199
7
11-27-2012 06:24 AM
JorgeKappa
Occasional Contributor
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
Tags (2)
0 Kudos
7 Replies
JorgeKappa
Occasional Contributor
I fixed it by deselecting right after reading the ancillary layers like this:
arcpy.SelectLayerByAttribute_management(DISLyr, "REMOVE_FROM_SELECTION", "")

Smells like victory!
0 Kudos
AndrewMaracini
New Contributor
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
0 Kudos
AndrewMaracini
New Contributor
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?
0 Kudos
JorgeKappa
Occasional Contributor
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.
0 Kudos
JorgeKappa
Occasional Contributor
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.
0 Kudos
AndrewMaracini
New Contributor
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")
0 Kudos
JorgeKappa
Occasional Contributor
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

0 Kudos