3

I am having some difficulty changing the data source for a group layer. Below is the code that I am using. It processes a LYR file successfully that has a single data source but if LYR file has more than one data layer, it doesn't do it.

import arcpy, datetime, os, io, sys

try:
    #Read input parameters from GP dialog

    # Prod Setting...
    folderPath = arcpy.GetParameterAsText(0)
    output = arcpy.GetParameterAsText(1)
    sourceString = r"P:\Temp" #
    replacmentString = r"Z:\Temp" #

    #Create an output file
    outFile = open(output, "w")

    #Loop through each "LYR" file
    count = 0
    for path, dires, files in os.walk(folderPath):
        #for filename in os.listdir(folderPath):
        for filename in files:
            #fullpath = os.path.join(folderPath, filename)
            fullpath = os.path.join(path, filename)

        if os.path.isfile(fullpath):
            if filename.lower().endswith(".lyr"):
                groupLayerName = ""
                #Reference LYR
                layerFile = arcpy.mapping.Layer(fullpath)

                #Reference each layer in a layer file
                count = 1
                for lyr in arcpy.mapping.ListLayers(layerFile):
                    if lyr.isGroupLayer:
                        groupLayerName = lyr.name

                    if lyr.supports("DATASOURCE"):
                        dataSourceOrig = str(lyr.dataSource)
                        if dataSourceOrig.find(sourceString) >= 0:
                            dataSourceNew = dataSourceOrig.replace(sourceString, replacmentString)
                            if groupLayerName:
                                outFile.write(fullpath + "\t" + groupLayerName + "\t" +lyr.name + "\t" + dataSourceOrig + "\t" + dataSourceNew + "\t" + "GROUP LAYER SOURCE" "\n")
                                lyr.findAndReplaceWorkspacePath(sourceString, replacmentString, True)
                                lyr.save()
                            else:
                                outFile.write(fullpath + "\t" + "Non Group Layer" + "\t" + lyr.name + "\t" + dataSourceOrig + "\t" + dataSourceNew + "\t" + "SINGLE LAYER SOURCE" "\n")
                                lyr.findAndReplaceWorkspacePath(sourceString, replacmentString, True)
                                lyr.save()
                del layerFile

    outFile.close()

    #Open the resulting text file
    os.startfile(output)

    #Delete variables that reference data on disk
    del outFile

except Exception, e:
    import traceback
    map(arcpy.AddError, traceback.format_exc().split("\n"))
    arcpy.AddError(str(e))

(the code block above transcribed from this screenshot, indentation errors may remain)

  • 1
    I applied formatting to your code, but you need to go back and put in the indents, as they never got setup properly in the first place it appears. – Chad Cooper Jun 1 '12 at 15:32
  • 1
    Jay, to retain proper formatting including indents: 1) In your editor select all and copy, 2) paste into edit pane here, 3) select all lines of the code block, and 4) press Ctrl+K. – matt wilkie Jun 1 '12 at 16:26
7

The problem you are encountering is that when a .lyr file contains more than one layer, you cannot simply set a new workspace path for that .lyr file. You have to iterate over each layer in the layer file and set the new path individually. You tried to do that using this section of code:

if groupLayerName:
  outFile.write(fullpath + "\t" + groupLayerName + "\t" +lyr.name + "\t" + dataSourceOrig + "\t" + dataSourceNew + "\t" + "GROUP LAYER SOURCE" "\n")
  lyr.findAndReplaceWorkspacePath(sourceString, replacmentString, True)
  lyr.save()
else:
  outFile.write(fullpath + "\t" + "Non Group Layer" + "\t" + lyr.name + "\t" + dataSourceOrig + "\t" + dataSourceNew + "\t" + "SINGLE LAYER SOURCE" "\n")
  lyr.findAndReplaceWorkspacePath(sourceString, replacmentString, True)
  lyr.save()

You could simply add another for loop to step through each group layer and reassign the dataset name, but this does not take into account multiply nested group layers. Instead, you could try something like the following, which calls a Recursive function to step through all nested group layers and act on each individual layer.

import arcpy, datetime, os, io, sys

def walknestedgroups(layerlist,srcString,repString,pathfull,outputFile):
  if layerlist.isGroupLayer:
    #Cycle through grouplayer
    for layer in layerlist:
      #This will continue to cycle through and test for group layers
      walknestedgroups(layer,srcString,repString,pathfull,outputFile)
  else:
    #Once a non-group layer is returned, head through the rest of original script
    singlelayer = layerlist
    if singlelayer.supports("DATASOURCE"):
      dataSourceOrig = str(singlelayer.dataSource)
      if dataSourceOrig.find(srcString) >= 0:
        dataSourceNew = dataSourceOrig.replace(srcString, repString)
        #This comparison test for layers in a group layer vs those not in.  The .longName property includes all group layers as part of path
        if Not singlelayer.name = singlelayer.longName:
          outputFile.write(pathfull + "\t" + singlelayer.longName + "\t" + singlelayer.name + "\t" + dataSourceOrig + "\t" + dataSourceNew + "\t" + "GROUP LAYER SOURCE" "\n")
          singlelayer.findAndReplaceWorkspacePath(sourceString, replacmentString, True)
          singlelayer.save()
        else:
          outputFile.write(pathfull + "\t" + "Non Group Layer" + "\t" + singlelayer.longName + "\t" + dataSourceOrig + "\t" + dataSourceNew + "\t" + "SINGLE LAYER SOURCE" "\n")
          singlelayer.findAndReplaceWorkspacePath(srcString, repString, True)
          singlelayer.save()

    return layerlist

try:

#Read input parameters from GP dialog

# Prod Setting...
folderPath = arcpy.GetParameterAsText(0)
output = arcpy.GetParameterAsText(1)
sourceString = r"P:\Temp" # 
replacmentString = r"Z:\Temp" # 

#Create an output file
outFile = open(output, "w")

#Loop through each "LYR" file
count = 0
for path, dires, files in os.walk(folderPath): 
  #for filename in os.listdir(folderPath):
  for filename in files:
    #fullpath = os.path.join(folderPath, filename)
    fullpath = os.path.join(path, filename)

    if os.path.isfile(fullpath):
      if filename.lower().endswith(".lyr"):
        groupLayerName = ""
        #Reference LYR
        layerFile = arcpy.mapping.Layer(fullpath)

        #Iterate through layer list and all group layers.  Return single layer

        for totallayer in arcpy.mapping.ListLayers(layerFile):
          #This function will take each layer in the .lyr file, cycle through any group layers then apply path changes to individual layers
          singlelayer = walknestedgroups(totallayer,sourceString,replacementString,fullpath,outFile)
        del layerFile

outFile.close()

#Open the resulting text file
os.startfile(output)

#Delete variables that reference data on disk
del outFile

except Exception, e:
import traceback
map(arcpy.AddError, traceback.format_exc().split("\n"))
arcpy.AddError(str(e)) 

A couple items here are the use of the .longName property, and a recursive function. The .longName property returns the layer name, including any group layers that it is a part of. Here is the ArcGIS help file discussing the Layer Class. The recursive function is used to iterate through nested group layers. Hat tip to @blah238 for the recursive function vocabulary lesson.

  • 1
    +1, though I think it would be remiss to not mention that a function that calls itself is a recursive function :) – blah238 Jun 2 '12 at 8:02
  • Thank you so very much for sharing this script. I am getting this error: NameError: global name 'walknestegroups' is not defined on line 6. I found that walknestegroups(layer, srcString, repString, pathfull, outputFile) takes a layer as an input whereas the function takes layerList as input. I am a Python newbie and would appreciate your feedback. Thanks, Jay – jay Jun 5 '12 at 20:07
  • It is a spelling error in my code. It should be walknestedgroups, with a "d". I'll revise in the answer as well. – Get Spatial Jun 5 '12 at 20:41
  • I have tried the script with several layer files, working really really well. I did get this message for one of teh folder that i was processing, not sure what may be causing this. Here it is: singlelayer.save() File "C:\Program Files\ArcGIS\Desktop10.0\arcpy\arcpy\utils.py", line 179, in fn_ return fn(*args, **kw) File "C:\Program Files\ArcGIS\Desktop10.0\arcpy\arcpy\arcobjects\mixins.py", line 268, in save assert hasattr(self, '_lyr'), gp.getIDMessage(89003, "No filename set.")...Any ideas? – jay Jun 5 '12 at 21:52
  • I would initially think that since it says "No filename set", and that is the variable you are using to iterate through the files in the directory, that either the directory is empty, or that it contains no .lyr files. What are the contents of this folder? – Get Spatial Jun 5 '12 at 22:02
1

One concept that might really help is to recognize that your layer list (i.e. the variable that would equal arcpy.mapping.ListLayers(layerFile)) is a list of objects, and each object in there could be a group Layer or an actual layer. Every actual layer has its own data source, so you need to be sure your code iterates a data source update (either a .findAndReplaceWorkspacePath or a .replaceDataSource for each of these objects, then does a .save to that object.

It is not enough to save the layerFile object at the end - each layer needs to be saved as you iterate through them with your function!

It looks like you probably got this but I did not and it took me a couple weeks to realize it!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.