use results of FeatureSet FOR LOOP in arcade as the input for additional functions

7614
15
Jump to solution
01-29-2021 01:57 PM
LynnBerni
Occasional Contributor II

I have a hosted Feature Layer (SampleSites) with water quality sampling locations. The sample data is saved in a hosted Table (BacteriaSamples) which has the SITEID field in common. From this table I am looking at the MPN_ECOLI field to designate a "rating" for each sample collected, based on the following criteria:

MPN_ECOLI <100 'Good'; > 405 'Poor'; 101-405 'Fair'

I have a When function inside a For Loop to create the rating for each sample and have tested that it works by returning a list in my expression (see my code example saved here).  But what I really want to do, and cannot figure out, is how to use the ratings created inside the loop as input for additional functions. 

Ultimately, my goal is to designate an Ecoli Rating for each site, as defined by the lowest quality samples collected at that site. For example, if any samples are rated as 'Poor', the Ecoli Rating for the site is deemed 'Poor'. If no samples are 'Poor', but some are 'Fair', then 'Fair'. Else 'Good'.

I tried using Distinct() inside the loop and got nothing.  I tried Distinct outside the loop and it returned [Poor], but if it was working it should have returned all three.  I know this because the first site that arcade "looks at" inside my expression has 49 bacteria samples, representing  Good, Fair, and Poor samples. Interestingly, the last sample in the list is rated as 'Poor'. (Is Distinct somehow looking at my last sample rating only?)

Couple other things to note:  I've been working on this expression in "configure popup" in ArcGIS Online. The hosted data is in Portal, but when I tried this expression in a web map in Portal, it doesn't work.

I'm thinking (hoping) Xander Bakker will have an answer for me 🙂

Thanks!
Lynn

p.s. ultimately, I want this expression to update the symbology of the sites, but as I understand it, that functionality is not yet available with arcade.

 

 

 

/*
store SiteID as a variable so it can be used to query
the BacteriaSamples table for matching records
*/
var siteId = $feature.SITEID

/*
Access Bacteria Samples (related table) as a FeatureSet with three
relevant fields and filtered on QA/QC status
*/
var samplesAll = Filter(FeatureSetById($map, /* Watershed - BacteriaSamples */ "Watershed_962", ['SAMPLEDATE', 'SITEID','MPN_ECOLI']), "QAQC_COMPL = 'yes'")

/*
Use SQL filter statement to access variable with "@";
essentially this creates a relate
*/
var filterStatement = "SITEID = @siteId"

//filter the table to find related records at each site
var samplesBysite = Filter(samplesAll, filterStatement)

//Store count of resulting records as a variable
var cnt = Count(samplesBysite)
Console("Number of Samples: " + cnt)
/*
loop thru the records to calculate a rating for each 
sample based on following criteria:
MPN_ECOLI<100=Good; >405=Poor; 101-405=Fair
*/

var rating = ''
if (cnt > 0) {
    for(var f in samplesBysite){
    
//returns a list of each sample rating
    rating += When(f.MPN_ECOLI < 100, 'Good', f.MPN_ECOLI > 405, 'Poor', 'Fair') + TextFormatting.NewLine   
    }
    return (rating)
    
} else {
    rating = "No sample data"
}

/*
Next, determine final ecoliRating for the site. If any sample rating is 'Poor', the final ecoliRating for the site is 'Poor'. If no samples are 'Poor' but some are 'Fair', then 'Fair'. Else 'Good'.   Help!!!
*/

 

 

 

 

0 Kudos
1 Solution

Accepted Solutions
DavidPike
MVP Frequent Contributor

it's difficult to test it without your data, as you have some stuff going on beforehand to create the list.

However this may given an idea of a possible way about it:

//dummy list for testing
var samplesBysite = [400,1,1]
//variable to hold the highest ecoli amount encountered in the for loop
var highest_ecoli_value = ''

//iterate through the samples list, if a sample is higher than the current
//highest ecoli value, update it
for(var f in samplesBysite){

highest_ecoli_value = When(samplesBysite[f] > highest_ecoli_value, samplesBysite[f], highest_ecoli_value) 
    
}
//finally do the rating after the loop when
//you have the highest ecoli amount recorded
var rating = When(highest_ecoli_value < 100, 'Good', highest_ecoli_value > 405, 'Poor', 'Fair')

return rating

you can test it out with some dummy variables in the playground ArcGIS Arcade | ArcGIS for Developers

View solution in original post

15 Replies
DavidPike
MVP Frequent Contributor

Could you possibly have a variable ecoli_amount, which is only changed when the loop encounters f.MPN_ECOLI greater than the current value.

Then when the loop is finished, assess ecoli_amount

ecoli_amount <100=Good; >405=Poor; 101-405=Fair

LynnBerni
Occasional Contributor II

Thanks Dave. I tried using break and continue inside the for loop, and it seems to be working.  I would like to add my updated expression in a code window, but I don't have that option in a reply.  So I've pasted in how I updated the loop:

var rating = ''
if (cnt > 0) {
for(var f in samplesBysite){
//returns ratings for each sample in a list, with date
//When(f.MPN_ECOLI < 100, 'Good', f.MPN_ECOLI > 405, 'Poor', 'Fair') + " -- " + Text(f.SAMPLEDATE, 'MMMM Y') + TextFormatting.NewLine
//returns one E.coli Rating for the site
rating = When(f.MPN_ECOLI < 100, 'Good', f.MPN_ECOLI > 405, 'Poor', 'Fair')
if(rating=='Poor') break;
if(rating=='Fair') continue}

} else {
rating = "No samples"
}

return rating

0 Kudos
DavidPike
MVP Frequent Contributor

I'm not too sure on that, what if hypothetically the last value assessed in the for loop comes out as 'Good'?  I may be wrong, but the code doesn't seem to account for it.

Also btw if you click on the ellipsis (...) at the top rhs of the reply bar, you get for code window option (<>)

0 Kudos
LynnBerni
Occasional Contributor II

You're right, it doesn't work. Honestly, I'm not really sure how the break & continue work. That's the problem with copying other people's code (!).  Can you suggest a way to accomplish your original reply?

0 Kudos
JoeBorgione
MVP Emeritus

@LynnBerni -  here is how break is used in python:

The break statement in Python terminates the current loop and resumes execution at the next statement, just like the traditional break found in C.

The most common use for break is when some external condition is triggered requiring a hasty exit from a loop. The break statement can be used in both while and for loops.

I would think that a break in Arcade acts in the same way.  Continue is the Arcade equivalent to Pass in  Python which more or less says never mind, go on to the next line...

Some pythonistas avoid a break and a pass as lazy programming.  I don't think I've ever used a break, and I'll use a pass every once and a while...

 

That should just about do it....
0 Kudos
DavidPike
MVP Frequent Contributor

Hi Joe,  is continue in JS not equivalent to continue in python?  I always thought the distinction was pass is 'do nothing - I'll maybe put some code in here at some point' as opposed to continue being 'stop what you're doing and actively go to the next iteration right now!'

0 Kudos
JoeBorgione
MVP Emeritus

@DavidPike - I have never used continue in python and had to look it up:

The continue statement in Python returns the control to the beginning of the while loop. The continue statement rejects all the remaining statements in the current iteration of the loop and moves the control back to the top of the loop. The continue statement can be used in both while and for loops.

Perhaps the continue statement in Arcade does the same, although I've not used it in a loop but rather only in an if block and it works like a python pass  or as you said, do nothing...

That should just about do it....
DavidPike
MVP Frequent Contributor

it's difficult to test it without your data, as you have some stuff going on beforehand to create the list.

However this may given an idea of a possible way about it:

//dummy list for testing
var samplesBysite = [400,1,1]
//variable to hold the highest ecoli amount encountered in the for loop
var highest_ecoli_value = ''

//iterate through the samples list, if a sample is higher than the current
//highest ecoli value, update it
for(var f in samplesBysite){

highest_ecoli_value = When(samplesBysite[f] > highest_ecoli_value, samplesBysite[f], highest_ecoli_value) 
    
}
//finally do the rating after the loop when
//you have the highest ecoli amount recorded
var rating = When(highest_ecoli_value < 100, 'Good', highest_ecoli_value > 405, 'Poor', 'Fair')

return rating

you can test it out with some dummy variables in the playground ArcGIS Arcade | ArcGIS for Developers

LynnBerni
Occasional Contributor II

Wahoo, it works! So simple. So sleek. So easy, once you gave me the answer that is, haha.  And a different solution than what I originally thought I needed. Thanks so much David :^)

I named the new variable "highest_mpn" and gave it a starting value of 100, which is the highest possible MPN for the 'Good' rating. 

Now if only I could use this expression to access my related records in either a field calculation or to update symbology on the fly, because ultimately I want to symbolize my sample sites based on the rating.  I know symbology on the fly isn't available yet with arcade, but I really thought I could use this in a field calculation. There's no $map in the fc arcade window!

Here's my updated expression: 

 

//store SiteID as a variable so it can be used to query
//the BacteriaSamples table for matching records

var siteId = $feature.SITEID

//access BacteriaSamples table as a FeatureSet;
//include only three relevant fields and filter on QA/QC status
var samplesAll = Filter(FeatureSetById($map, /* Watershed - BacteriaSamples */ "Watershed_962", ['SAMPLEDATE', 'SITEID','MPN_ECOLI']), "QAQC_COMPL = 'yes'")

//use SQL filter statement to access variable with @ symbol;
//essentially this creates a relate
var filterStatement = "SITEID = @siteId"

//filter the table to find related records at each site
var samplesBysite = Filter(samplesAll, filterStatement)

//store count of related records as a variable
var cnt = Count(samplesBysite)
Console("Number of Samples: " + cnt)

//loop through samples to find the highest MPN_ECOLI
//rating criteria: <= 100 'Good'; 101-405 'Fair'; >405 'Poor'
//set starting value of variable to highest possible 'Good' rating
var highest_mpn = 100
var rating = ''
if (cnt > 0) {
    for(var f in samplesBysite){
    //if sample is higher than current highest_mpn value, update it
    highest_mpn = When(f.MPN_ECOLI > highest_MPN, f.MPN_ECOLI, highest_MPN) 
        
    }
}else {
    rating = "No samples"
}

//designate site rating based on highest_mpn value
rating = When(highest_mpn <= 100, 'Good', highest_mpn > 405, 'Poor', 'Fair') 

Console("Highest MPN: " + highest_mpn)
Console("Site Rating: " + rating)

return rating