Can I return a list of attribute values from a geoprocessing service?

4795
19
Jump to solution
07-13-2012 06:03 AM
TracySchloss
Frequent Contributor
I have been asked to create a service that will be consumed by a non-GIS application.   The programmer wants to be able to "make a call to GIS" providing the number of a school district as input.  They want the service to return just a list of districts that surround the input number.  Their application will be written in Java, which I know nothing about.

I have considered publishing a model as a geoprocessing service and I have looked at Python.  It seems like I might have more luck in Python, which I've never used before, because at least I could make an array or list of values from the attributes?  I'm not finding much documentation on publishing a Python script as a geoprocessing service.  I assume I can do this?

I started by creating a model that had the tools select by attribute and select by location.   Then I exported this to Python.  It executes OK when I'm in ArcMap.   I'm not sure where to go from there.  I know I need to examine each polygon that is the result of the select by location query and find just the attribute of the district field.

There is no desire to view or create a map in this application.  They just want a list containing the numbers of the adjacent districts.  Even after I get the list,  I'm still not sure what I might need to do to get this to work properly as an ArcGIS Server geoprocessing service.  I am still at version 10.0.

# --------------------------------------------------------------------------- # adjacent.py # Created on: 2012-07-12 15:14:17.00000 #   (generated by ArcGIS/ModelBuilder) # Usage: adjacent <countyName>  # Description:  # ---------------------------------------------------------------------------  # Import arcpy module import arcpy  # Set Geoprocessing environments arcpy.env.scratchWorkspace = "C:\\ESRItest\\model\\process.gdb" arcpy.env.workspace = "C:\\ESRItest\\model\\process.gdb"  # Script arguments countyName = arcpy.GetParameterAsText(0) if countyName == '#' or not countyName:     countyName = "Boone" # provide a default value if unspecified  # Local variables: selected_County = countyName Adjacent_Counties = selected_County county = "county"  # Process: Select Layer By Attribute arcpy.SelectLayerByAttribute_management(county, "NEW_SELECTION", "\"NAME\" = '%countyName%'")  # Process: Select Layer By Location result = arcpy.SelectLayerByLocation_management(selected_County, "BOUNDARY_TOUCHES", "", "", "NEW_SELECTION")  ## NOW WHAT?
Tags (2)
0 Kudos
1 Solution

Accepted Solutions
TracySchloss
Frequent Contributor
This one works.  I spent an hour trying to figure out how to manage the quotes properly in my expression!

# --------------------------------------------------------------------------- # adjacent.py # Created on: 2012-07-12 15:14:17.00000 #   (generated by ArcGIS/ModelBuilder) # Usage: adjacent <countyName>  # Description:  # ---------------------------------------------------------------------------  # Import arcpy module import arcpy  # Set Geoprocessing environments arcpy.env.scratchWorkspace = "C:\\ESRItest\\model\\process.gdb" arcpy.env.workspace = "C:\\ESRItest\\model\\process.gdb"  #overwrite pre-existing files arcpy.env.overwriteOutput = True  # Script arguments #countyName = arcpy.GetParameterAsText(0) countyName = raw_input('What County do you want to select?')  # Local variables: county = "county" county_Layer = "county_Layer" countyList = [] txt_list = ""  #Output_Feature_Class = "C:\\ESRItest\\model\\process.gdb\\county_CopyFeaturesOutput"  # Process: Make Feature Layer county_Layer = arcpy.MakeFeatureLayer_management("C:\ESRItest\model\process.gdb\county", county_Layer, "", "", "OBJECTID OBJECTID VISIBLE NONE;Shape Shape VISIBLE NONE;NAME NAME VISIBLE NONE;CNTY_FIPS CNTY_FIPS VISIBLE NONE;FIPS FIPS VISIBLE NONE;POP2005 POP2005 VISIBLE NONE;POP05_SQMI POP05_SQMI VISIBLE NONE;SQMI SQMI VISIBLE NONE;NAME2 NAME2 VISIBLE NONE;Shape_Length Shape_Length VISIBLE NONE;Shape_Area Shape_Area VISIBLE NONE")  #Process: Select Layer By Attributes  #print countyName  whereClause=" \"NAME\" = " + "'"+countyName+"'" #print whereClause countySelection = arcpy.SelectLayerByAttribute_management(county_Layer, "NEW_SELECTION", whereClause)  # Process: Select Layer By Location results = arcpy.SelectLayerByLocation_management(countySelection, "BOUNDARY_TOUCHES", "", "", "NEW_SELECTION")  # Process: Copy Features #arcpy.CopyFeatures_management(county_Layer, Output_Feature_Class, "", "0", "0", "0") ## Step through the selection to get the values from the name field  rows = arcpy.SearchCursor(results,"","","NAME","NAME") for row in rows:   neighborVal = row.getValue("NAME")   if neighborVal != countyName:       countyList.append(neighborVal)   txt_list = ','.join(countyList) print txt_list


I still need to figure out how in the world to manage this as a call from Java.  At least I can give input and receive output from within the Python window.

Thanks so much Chris for your assistance!!!!!

View solution in original post

0 Kudos
19 Replies
ChristopherThompson
Occasional Contributor III
You're on the right track here, just a few more steps to go.  Once you have the select by location layer you can implement a search cursor to inspect each feature selected and then collect those values into a list.  Generically this might look something like this:
(in my fictional example the district number is held in a field named DistrictNum)

list_of_districts = [] #creates an empty list of the districts that you will fill
rows = arcpy.SearchCursor(select_by_location_layer,"","","DistrictNum") # sets up a search cursor that allows you to move row by row

for row in rows:
         dist_id = row.DistrictNum
         list_of_districts.append(dist_id)

thats it.  There are a number of ways to approach this but you'll want to start by reading up about the SearchCursor syntax. 

You'll also need to figure out how to deal with the list that is accumulated.  One option would be to write it to a text file in an operation that would look something like this:

dist_num_file = open(r'c:\districts.txt','a') #creates and opens a file on disk for appending to
for x in list_of_districts:
       dist_num_file.write(x + '\n')  #the \n places a carriage return so each district value is put on a separate line

dist_num_file.close()
The key concepts you'll want to master for this sort of thing are search cursors, iteration concepts, use the python for loop, file objects (creating, reading, writing, etc.), and text string manipulation.

I noticed on posting that the indentation that i'd put in wasn't preserved, so thats something you'll have to pay attention to within python because indentation is critical in that environment.
0 Kudos
TracySchloss
Frequent Contributor
So where is select_by_location_layer defined?   I was thinking I would need to set a variable on the Select Layer by Location, which is why I changed the line below to have results = in it.
result = arcpy.SelectLayerByLocation_management(selected_County, "BOUNDARY_TOUCHES", "", "", "NEW_SELECTION")
0 Kudos
ChristopherThompson
Occasional Contributor III
the variable you have as 'result' would be what you'd reference in place of the select_by_location_layer, so that search cursor would look more like the following:
rows = arcpy.SearchCursor(result,"","","DistrictNum")


here's an implementation of that concept that i use to select features by location, then creates a .lyr file on disk.  The search cursor is used to collect the IDs of the selected values and those are then used to created a definition query in the .lyr file.

PAZ_lyr = 'feature_layer' # feature layer created as a global variable
def make_paz_layer(keyhole):
               paz_units = []
               SelPaz = arcpy.SelectLayerByLocation_management (PAZ_lyr, 'Intersect', keyhole)
               rows = arcpy.SearchCursor(SelPaz," \"POWERPLANT\" = 'PlantName'", "", "PAZ;PAZ_NUM;PAZ_ALPHA","PAZ_NUM A;PAZ_ALPHA A")
               for row in rows:
                   unit = str(row.PAZ)
                   unum = "'" + unit + "'" #wraps each value in single quotes
                   paz_units.append(unum)
               txt_paz = ','.join(paz_units) #creates a comma separated string out of a ist
               paz_list = '(' + txt_paz + ')' #encloses the string inside parentheses to be used in the definition query
               paz_qry = ' "POWERPLANT" = '+ station_name + ' AND "PAZ" in ' + paz_list
               new_paz = arcpy.MakeFeatureLayer_management(paz, keyhole + '_paz', paz_qry)
               arcpy.SaveToLayerFile_management(new_paz, outfolder + '\\' + keyhole + '_paz.lyr')
0 Kudos
TracySchloss
Frequent Contributor
That helps, thanks.  All I really need is the list of values and I don't want it in an output file or anything, I want it to be the result of a call to a geoprocessing service. I'm still trying to wrap my head around how to get the list to be the result of my geoprocessing call.

I'm not sure where to even post this question, it's touching on script syntax and ArcGIS Server geoprocessing services.  From my searching, I'm not finding a lot of examples, but it still seems like it should be possible.
# ---------------------------------------------------------------------------
# adjacent.py
# Created on: 2012-07-12 15:14:17.00000
#   (generated by ArcGIS/ModelBuilder)
# Usage: adjacent <countyName> 
# Description: 
# ---------------------------------------------------------------------------

# Import arcpy module
import arcpy

# Set Geoprocessing environments
arcpy.env.scratchWorkspace = "C:/ESRItest/model/process.gdb"
arcpy.env.workspace = "C:/ESRItest/model/process.gdb"

# Script arguments
countyName = arcpy.GetParameterAsText(0)
if countyName == '#' or not countyName:
    countyName = "Boone" # provide a default value if unspecified

# Local variables:
selected_County = countyName
Adjacent_Counties = selected_County
county = "county"
countList = []   #  I added this
countyField = "NAME"  #I added this

# Process: Select Layer By Attribute
arcpy.SelectLayerByAttribute_management(county, "NEW_SELECTION", "\"NAME\" = '%countyName%'")

# Process: Select Layer By Location
results = arcpy.SelectLayerByLocation_management(selected_County, "BOUNDARY_TOUCHES", "", "", "NEW_SELECTION")

## I added this section 7-13-12
rows = arcpy.SearchCursor(results,"","","NAME")
for row in rows:
 neighborVal = row.NAME
 print(neighborVal)
 countyList.append(neighborVal)
 txt_list = ','.join(countyList)
print txt_list
0 Kudos
ChristopherThompson
Occasional Contributor III
Well, I guess the question you have to define an answer for is what you do with that list once you have it.  Does it get passed into another program? if your developer wants that list, then you want to put that geoprocessing service inside a python function that returns the list, probably formatted as a text string as  you've done.  That would take the form in python of doing something like this:

def GetDistricts(args): #where args are values you'd pass in if there were any
     ...code to do stuff
          txt_list = ','.join(countyList)
          return txt_list


within a python script you'd use that bit of code like this:

mylist = GetDistricts(args you pass in)

then mylist takes the values created in the txt_list variable.

How you pass that into your developer's Java application which i also know nothing about so you'll have to talk with that person about how to call a python script and have it pass out that string.  If you'd like to email me more about this please do.

chris thompson
0 Kudos
TracySchloss
Frequent Contributor
I was actually thinking of it the other way.  I have a geoprocessing service that is built from Python that would be called from another program.  I've used the other ArcGIS Server tasks like findTask, queryTask, locatorTask.  For those, you have a service that you can pass it a parameter and what it returns in in the form of JSON or HTML.  Once you start creating your own geoprocessing service, I assume whatever is returned is going to be determined more from what output or results you have from the script.

I have to take a step back for the moment because I have two different versions of Python on my machine and they seem to be conflicting with each other!
0 Kudos
TracySchloss
Frequent Contributor
I was able to get my python install problem taken care of.  Tech support recommended a repair on the install.  I probably could have just fixed it by just editing the system variable for PYTHONPATH.  Anyway, I'm back in business.

My code doesn't seem to actually return anything.  In my example, I'm just using county boundaries, because there are fewer of those and I have the data at hand.  I need to do some more reading on debugging.  I don't see that the array I created even gets any values in my searchCursor.  The selections by attribute and by location seem to be occuring.

# ---------------------------------------------------------------------------
# adjacent.py
# Created on: 2012-07-12 15:14:17.00000
#   (generated by ArcGIS/ModelBuilder)
# Usage: adjacent <countyName> 
# Description: 
# ---------------------------------------------------------------------------

# Import arcpy module
import arcpy

# Set Geoprocessing environments
arcpy.env.scratchWorkspace = "C:/ESRItest/model/process.gdb"
arcpy.env.workspace = "C:/ESRItest/model/process.gdb"

#overwrite pre-existing files
arcpy.env.overwriteOutput = True

# Script arguments
countyName = arcpy.GetParameterAsText(0)
if countyName == '#' or not countyName:
    countyName = "Boone" # provide a default value if unspecified

# Local variables:
selected_County = countyName
Adjacent_Counties = selected_County
#county = "county"
county = "C:/ESRItest/model/process.gdb/county"
Output_Layer = "county_Layer"
countList = []   #  I added this
countyField = "NAME"  #I added this
county_lyr = "C:/ESRItest/model/county.lyr"

# Process: Make Feature Layer
arcpy.MakeFeatureLayer_management(county, Output_Layer, "", "", "OBJECTID OBJECTID VISIBLE NONE;Shape Shape VISIBLE NONE;NAME NAME VISIBLE NONE;CNTY_FIPS CNTY_FIPS VISIBLE NONE;FIPS FIPS VISIBLE NONE;POP2005 POP2005 VISIBLE NONE;POP05_SQMI POP05_SQMI VISIBLE NONE;SQMI SQMI VISIBLE NONE;NAME2 NAME2 VISIBLE NONE;Shape_Length Shape_Length VISIBLE NONE;Shape_Area Shape_Area VISIBLE NONE")

# Process: Select Layer By Attribute
selection = arcpy.SelectLayerByAttribute_management(Output_Layer, "NEW_SELECTION", "\"NAME\" = '%countyName%'")

# Process: Select Layer By Location
results = arcpy.SelectLayerByLocation_management(selection, "BOUNDARY_TOUCHES", "", "", "NEW_SELECTION")

# Process: Select Layer By Attribute
#arcpy.SelectLayerByAttribute_management("C:/ESRItest/model/process.gdb/county", "NEW_SELECTION", "\"NAME\" = '%countyName%'")



## I added this section 7-13-12
rows = arcpy.SearchCursor(results,"","","NAME","NAME")
for row in rows:
    neighborVal = row.getValue("NAME")
    print(neighborVal)
    countyList.append(neighborVal)
    print countyList
#txt_list = ','.join(countyList)
0 Kudos
KevinHibma
Esri Regular Contributor
I just skimmed through this thread and the questions proposed by Chris a couple posts up "what are you doing with this list, what program will consume this list" I think have to be answered before you move forward.
The cursor is approach is fine, but is it really needed? I can't envision a cursor result as output to a GP Service that would be usable by a client (note I say I cant imagine, not it can't be done)....

From your very first post you looked to be doing select on a feature and want to pass that result.
Well regardless of it its a table, or featureclass, you get attributes back (I assume that is what you're ultimately after) from further posts.
You did mention JSON. So, if you Java developer says "sure, I can take a JSON list and get what I need from it".... you're pretty much done.
Tell him how to connect to the REST end point, execute your service, load the output into some sort of JSON object and parse it.
Of course you could do them a favor by trimming the amount you output by only returning a table/features with just the fields they need (else they might be parsing more then they want to).

As an example, I used the Copy Rows tool, published it, and the output when requested as JSON comes back like:

 {
      "attributes": {
       "OID": 1,
       "LABNO": "C-117267",
       "CATEGORY": "NURE",
       "DATASET": "NURE Coastal 98",
       "TYPEDESC": "STRM-SED-DRY",
       "COUNT_": 118,
       "PB_ICP40": 12
     }

Hopefully this helps, you're on the right track, I just think you need to confirm with your java developer what they want, or what they can use.
0 Kudos
ChristopherThompson
Occasional Contributor III
I've altered that last bit in your example to address a couple of things.  What i'm seeing is:

    you're trying to append to a list that doesn't exist (countyList) - this should be created as an empty element outside yoru loop

    assigning a value to neighborVal doesn't look right - it should use row.FIELDNAME instead of getValue('NAME') method you are using

I also added some code to write the results to a txt file that you can inspect after the fact; you just need to replace the XXX with a directory path where to write that file to.

here is the code:
## try this version by ct 7/18/12
countyList = [] # this creates an empty python list for you to append values to
# creating this outside the loop makes sure that it can be used outside the loop because of scope issues
rows = arcpy.SearchCursor(results,"","","NAME","NAME")
for row in rows:
    neighborVal = row.NAME
    print(neighborVal)
    countyList.append(neighborVal)
    print countyList
#txt_list = ','.join(countyList)
mydir = r'XXXXX' # put in a directory path here that you can write a text file to
outdoc = open(mydir + '\\results.txt','a')
outdoc.write(txt_list + '\n')
outdoc.close()


That txt file could potentially even be the object that the java script your developer grabs to use in his application, unless there is a way to pass the contents of txt_list into his application directly without having to write a file to disk (which raises file management and processing time issues).

I'll be interested to know if this will work for you.  Khimba's approach is potentially another way to do this of course, but also involves paring down the result table to just the values you need to know. Good luck!
0 Kudos