2

I'm trying to run the following code in the Python window of ArcGIS Pro 3.2, just to quickly see how all my layers are projected, and I ran into a strange error which I can't solve.

aprx = arcpy.mp.ArcGISProject("CURRENT")
map = aprx.listMaps('Processed*')[0]
lyr = map.listLayers()[0]
arcpy.Describe(lyr).spatialReference.PCSCode

for l in map.listLayers():
    arcpy.Describe(l).spatialReference.PCSCode

for i in range(0,len(map.listLayers(0))):
    l = map.listLayers()[i]
    arcpy.Describe(l).spatialReference.PCSCode

The first four lines run without issue. I get the four-digit PCS Code output. The loops do not work and I get the error:

AttributeError: DescribeData: Method spatialReference does not exist

Even stranger, when I run this code as a standalone script (by importing arcpy and using the path to my aprx, of course) I get that error for the fourth line too, none of the methods work.

What is going on here?

2
  • Double check that you're listlayers isn't returning a broken layer, table or other aspatial type, for example check arcpy.Describe(lyr).dataType == 'FeatureLayer' before trying to grab the spatialReference. It is pythonic to try: except: where these sorts of failures potentially occur, you could put except: arcpy.AddWarning('No SR {} of type {}'.format(lyr.name, arcpy.Describe(lyr).dataType)) to try to figure out which layers are failing. Commented Aug 9 at 4:24
  • 1
    @MichaelStimson solved it! Turns out the layer group headers were being read as layers too, and throwing the error as obviously they have no data. To top it off, the test lyr variable in the python window was actually referencing a Feature Layer in a different map (i'd defined it earlier before changing the map variable to a different Map), hence seemingly "working" inside ArcGIS Pro, but not in a standalone script. Also had to add print() around the PCSCode line or nothing would be output. If you add this as an answer I'd be happy to accept it.
    – panta_ray
    Commented Aug 9 at 5:08

2 Answers 2

3

The map object method listlayers() returns all sorts of layers, not all of them are spatial so the describe doesn't have a spatialReference property when used on them. Just to be sure you could check the type of the layer before trying to obtain a spatialReference. Alternately there's the more pythonic (so I've been told) method of try: and handle any error that occurs with an except: with some meaningful method that is extremely unlikely to throw an error.

for example:

aprx = arcpy.mp.ArcGISProject("CURRENT")
map  = aprx.listMaps('Processed*')[0]
lyr  = map.listLayers()[0]
SRID = -1 # fail value

# test the dataType and see if it's a type that we would expect
# to have a spatial reference associated with it
if arcpy.Describe(lyr).dataType in ['RasterLayer','FeatureLayer']:
    SRID = arcpy.Describe(lyr).spatialReference.PCSCode

for l in map.listLayers():
  # the 'python' way: have a go and handle it if it fails
  try:
    SRID = arcpy.Describe(l).spatialReference.PCSCode
  except:
    arcpy.AddWarning('No SR {} of type {}'.format(lyr.name, arcpy.Describe(lyr).dataType))
1

Assuming most layers in most maps will have data sources, and hence spatial references, a try/except block will perform the best. That has been the case historically but especially since Python 3.11 with the implementation of "zero-cost" exceptions.

From What’s New In Python 3.11 - Python 3.11.8 documentation:

“Zero-cost” exceptions are implemented, eliminating the cost of try statements when no exception is raised. (Contributed by Mark Shannon in bpo-40222.)

For me personally, I don't like the aesthetics of try/except blocks so I tend to avoid them unless necessary. A common sylistic argument for using try/except blocks is that they are explicit, and PEP 20 – The Zen of Python | peps.python.org states, "Explicit is better than implicit." I actually agree that explicit is better than implicit and that try/except blocks are explicit, but it doesn't mean one has to use a try/except block to be explicit.

The Look Before You Leap (LBYL) code in another answer

if arcpy.Describe(lyr).dataType in ['RasterLayer','FeatureLayer']:
    SRID = arcpy.Describe(lyr).spatialReference.PCSCode

is implicit because it uses the data type of the layer to infer whether the describe object has a spatial reference before calling a spatial reference property. A more explicit LBYL approach is to use hasattr - Built-in Functions - Python 3.x documentation to check whether the describe object has a spatial reference.

desc = arcpy.Describe(lyr)
SRID = desc.spatialReference.factoryCode if hasattr(desc, 'spatialReference') else "N/A"

A couple other comments on the original code:

  1. arcpy.Describe is an expensive function call. It is much better for performance and maintainability to call it once and store it in a variable, e.g., desc = arcpy.Describe(lyr), than to call it over and over again.

  2. spatialReference.factoryCode applies to both geographic and projected data sets, so code will be more portable using it than PCSCode or GCSCode. Also, WKID is typically what people are after then calling PCSCode or GCSCode.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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