As you were testing, you probably saw some aspects of the app that could be improved. We already noted that the HMZ_ID of a new Species_HMZ record defaulted to 0 and in this section we'll correct this shortcoming by configuring a calculated expression written in Esri's Arcade scripting language.
Also, returning to the project background, you may recall that the Impact rating is based on the HMZ’s habitat type. Similarly, the Restoration rating is based on the species and its extent. So after the relatively simple HMZ_ID expression, we'll up the ante a bit to create more complex expressions that populate the Impact_Value and Restoration_Value fields by querying the Impact and Restoration lookup tables. We’ll also then be able to carry out the Priority score calculation using the various ratings entered by the user and derived from the lookup tables.
Recall that maintaining the relationship between the HMZ polygon feature and the Species_HMZ records associated with it depends on the GlobalID of the HMZ being entered into the Species_HMZ table’s HMZ_GUID field. That was a result of the relationship class we created. While this does maintain the record associations, the HMZ_GUID value (randomly generated by ArcGIS) will have no meaning to someone who views the Species_HMZ records in isolation. That's what drove the inclusion of HMZ_ID in the Species_HMZ table; it’s an ID assigned by the DCNR staff. However, as we saw, this field won’t be automatically populated for us. So let’s see how we can use Arcade scripting to populate this field.
In AGO, re-open your ISMP Web Map in the Field Maps Designer.
Return to the Form designer interface and re-open the Species HMZ table’s form.
Click on the HMZ_ID control to select it. You should see its Properties appear in the panel to the right.
Scrolling down that panel, you should see an area labeled Logic that has a Calculated expression property. Click the gear icon to access the calculated expressions stored with the web map. (There won't be any at this stage.)
Click New expression.
A dialog will open for building an Arcade expression, with a large box on the left for the expression and a collapsed panel on the right providing helpful shortcuts. The shortcuts are in the form of Profile variables and Functions.
The profile variables provide a number of potential starting points for expression building. These include:
$feature – the feature whose attributes the user is currently viewing (or as in this case, a record from a standalone table rather than a feature from a feature class)
$map – the web map the user is currently viewing
$layer – the layer/table that the current feature/record is coming from
$featureSet – the complete set of features/records associated with the current layer; similar in concept to an array, common in programming languages; can be iterated over using a loop
$datastore – the complete set of layers/tables that are built into the web map
The Functions tab gives access to many general purpose functions found in many languages (e.g., Left, Right, Length, Count, etc.) along with spatial functions (Area, Centroid, Distance, Extent, etc.).
In order to retrieve the HMZ_ID value, we need to get at the HMZ feature associated with the currently open Species_HMZ record. We can do that by a) getting the HMZ_GUID, b) querying the HMZ layer for the HMZ feature with that value in its GlobalID field, and c) asking for the value in that feature’s HMZ_ID field.
Start with step a) by entering the following:
var hmz_guid = $feature["HMZ_GUID"]
Note that:
- The var keyword is used in Arcade to define variables.
- Field values are accessed from features/records by quoting the field name and putting it in brackets.
- You can expand the $feature profile variable to the right to see a list of the fields available and to insert the field reference automatically (by clicking the appropriate link).
Let’s confirm that we’re on the right track before moving on to the next step.
Add the following statement to the expression:
Console(hmz_guid)
Click the Run button in the upper left of the Expression box.
Field Maps may churn for a few seconds, but eventually you should see a new section appear in the interface beneath the Expression box. The Output tab will show the result returned by the expression – which we haven’t gotten to yet. What we want to look at now is the Console tab, which is where the Console function sends its output. Assuming all is well, you should see a GUID. (Recalling that $feature refers to the record currently being viewed, you may be wondering which record it’s referring to when testing here in the expression builder. I presume Field Maps is grabbing the first record it finds in the applicable table, Species_HMZ in this case.)
Returning to the expression, we can now work on step b) of the steps outlined above. Arcade has a FeatureSetByName function that can be used to get a reference to a FeatureSet (layer or table) through the global $map variable.
First, remove the Console statement.
Next, get a reference to the HMZ layer, as follows:
var hmz_lyr = FeatureSetByName($map, "HMZ")
Please note that your layer might not be called just "HMZ" it's likely called "ISMP Web Layer aaa123 - HMZ" so you'll need to amend your code accordingly. To find out what your layer is called open it in Map Viewer and use the name in the Layers list.
Now we want to query the HMZ layer for all features where the GlobalID is equal to the value in our hmz_guid variable.
Carry out this operation using the Filter function:
var qry = "GlobalID = '" + hmz_guid + "'" var feat_set = Filter(hmz_lyr, qry)
Here we’re passing the Filter function a reference to the HMZ layer and a query expression. Note that GUID values must be quoted, so in creating the qry variable, the concatenation operator (+) is used to enclose the GUID in single quotes. When querying a layer in this way, the Filter function will return a FeatureSet object, which we’re storing here in the feat_set variable.
In other contexts, we might use a for loop to iterate over the Features in a FeatureSet. However, given the nature of GUIDs, we know that this FeatureSet will hold exactly 1 Feature.
Use the First function to get at the 1 returned Feature:
var feat = First(feat_set)
With a reference to the HMZ feature associated with the current Species_HMZ record, we’re ready to specify our Arcade expression’s return value (i.e., the value that should go into the HMZ_ID field). This is done in Arcade using the return keyword followed by the desired value.
Define the expression's return value, as follows:
return feat["HMZ_ID"]
Your complete Arcade expression should look like this:
var hmz_guid = $feature["HMZ_GUID"] // remember that your layer might not be called "HMZ" so you // might need to amend your layer name to be something like : // ISMP Web Layer aaa123 - HMZ var hmz_lyr = FeatureSetByName($map, "HMZ") var qry = "GlobalID = '" + hmz_guid + "'" var feat_set = Filter(hmz_lyr, qry) var feat = First(feat_set) return feat["HMZ_ID"]
With that, you can again click Run.
As the expression contains no Console statements, you should expect to see nothing under the Console tab. However, with the addition of a return statement, you should now expect to see something under the Output tab. Specifically, if you added a single Species_HMZ record associated with the Campground Management Area in your testing of the app at the end of the last section, you should see that the output returned by the expression is 12.
Before leaving the expression builder, set the Title of the expression to getHMZ_ID.
Click Done to close the expression builder and return to the form designer.
Back in the form designer, you should now see a "Calculated" label beneath the HMZ_ID field and your getHMZ_ID expression listed under the Calculated value area of the Properties panel.
Click the Save button in the upper right of the form designer to commit your changes.
At this point, you can return to Field Maps on your mobile device to test the change you just made. If you’re looking at the list of Maps available, you’ll probably see your ISMP Web Map listed under the Current heading. It’s probably a good idea to tap the … button next to it and then Reload Map to ensure the changes have made their way to your device.
Open the map and open the popup for the Campground Management Area again.
Access the related Species_HMZ form and click Add. The form associated with that table should appear and instead of defaulting to 0, the HMZ_ID should be set to 12. (It may take a second or two for the calculation to complete.) There’s no need to submit another species at this point, so you can tap Cancel and then Discard to back your way out of adding a new species.
That wasn’t a mission-critical calculation, but it did give a good introduction to writing calculated expressions with Arcade. You can learn more about the topic from the Field Maps documentation [1] (https://doc.arcgis.com/en/field-maps/android/help/configure-the-form.htm... [1]). In the next section, we'll build the expressions that are needed to compute the priority score for each Species_HMZ record.
Recall from the scenario that one of the ratings that contributes to a Species_HMZ record’s priority score is the Impact_Value ("regular bad" or "super bad"), which is dependent on which species is present and what type of habitat it’s invading (Mature Forest, Wetland, etc.). The application could be built with the expectation that the staffer would look up the impact value and enter it manually, but that lookup can be handled automatically with another calculated expression since we have the data needed in the Impact table. For example, looking in that table, we can see that the garlic mustard species in the Wetlands habitat type has an impact value of 1 (super bad).
Hopefully you’re already thinking through the steps that are required for this calculation. As in the previous one, we’ll want to obtain a reference to the associated HMZ feature. We can then get the value from the Type field. Another data point we’ll need is which species we’re dealing with. That we can access through the $feature variable. Having those two data points, we can query the Impact table to get the impact value associated with that species+habitat type combination. We’ll get to the Impact table through the $datastore variable.
One wrinkle involved in this query is that the Impact lookup table has been populated such that it contains only those species+habitat type combinations that have an impact value of 1 (the "super bad" ones). Thus, if a species+habitat type combination cannot be found in the table, it can be assumed that the impact value is 0 (i.e., that it’s a "regular bad" situation).
var species = $feature["Common_Name"] var hmz_guid = $feature["HMZ_GUID"] // remember your layer might not be called "HMZ" but // "ISMP Web Layer aaa123 - HMZ" so adjust it accordingly var hmz_lyr = FeatureSetByName($map, "HMZ") var hmz_qry = "GlobalID = '" + hmz_guid + "'" var feat_set = Filter(hmz_lyr, hmz_qry) var feat = First(feat_set) var habitat_type = feat["Type"] var impact_tbl = FeatureSetByName($datastore, "Impact") var impact_qry = "Habitat_Type = '" + habitat_type + "' and Common_Name = '" + species + "'" var impact_rows = Filter(impact_tbl, impact_qry) if (Count(impact_rows) == 0) { //no match on this species+habitat, so impact is 0 (regular bad) return 0 } else { //there is a match on this species+habitat, let's get the impact value var impact_row = First(impact_rows) var impact_val = impact_row["Impact_Value"] return impact_val }
Hopefully you’re able to follow everything going on in this expression. Here are a couple of points of clarification:
- Based on the design decision to omit records where the impact value is 0, note the use of the Count function to determine whether the query on the Impact table found any rows. The expression’s return value is set to 0 if a match is not found; otherwise, the return value is set to whatever is held in the Impact_Value field. (This field only holds values of 1, so alternatively the expression could just be set to return 1 when the query finds a match, but explicitly retrieving the value has the advantage of offering flexibility in the event that the impact rating scale were to change from something other than just 0-1.)
- The expression contains a couple of comments intended to clarify the logic involved. These are the lines that begin with double slashes (//).
Assign a name to the expression of getImpact, then close out of the expression editor and Save your changes to the web map.
Return to the Field Maps app on your mobile device to test. (Remember to reload the map first.)
Again, select the Campground Management Area, and add a new species.
When the Species_HMZ form first opens, you should see the Impact_Value field automatically take on a 0 - regular bad value because the species name has not been specified yet.
Choose a species found in the Impact table associated with the MF habitat type (wavyleaf basketgrass is one) and confirm that the Impact_Value field updates itself to 1 - super bad.
Cancel out of the edit once you’ve confirmed your expression is working as intended.
Recall from the scenario that the restoration effort rating is another field on the form that is derived from other user-supplied values. Specifically, it depends on the species in question and the extent of its infestation. Unlike the calculation of the impact value, which required obtaining one value from the Species_HMZ form and another from the HMZ form, the values needed for the restoration calculation are both found in the Species_HMZ form. So this expression will be a bit less complicated.
Return to Field Maps in AGO and re-open the Species_HMZ form for editing.
Select the Restoration_Value field and open up the expression builder.
Take a crack at writing the expression yourself.
As noted above, you’ll need the user-entered species name and extent rating. You can then query the Restoration lookup table to get the restoration value associated with the species+extent combination. Like the Impact lookup table, combinations that yield a 0 value have been omitted from the table. Unlike it, however, the possible restoration values range from 0-3 rather than 0-1, so you would not have the option of assuming a value of 1 if a match for the species+extent combination is found.
Code that completes the task can be found here:
var species = $feature["Common_Name"] var ext = $feature["Extent_Value"] var rest_tbl = FeatureSetByName($datastore, "Restoration") var rest_qry = "Common_Name = '" + species + "' and Extent_Value = " + ext var rest_rows = Filter(rest_tbl, rest_qry) if (Count(rest_rows) == 0) { return 0 } else { var rest_row = First(rest_rows) var rest_val = rest_row["Restoration_Value"] return rest_val }
We’ve now written expressions to automatically populate two of the important ratings fields, eliminating the need for a staffer to look them up manually. Recall that the ultimate purpose of populating those fields, and of the map itself, is to derive an overall remediation priority score. We’re now ready to build one last expression to carry out that calculation.
In the Field Maps form designer, select the Priority field and open up the expression builder.
Recall from the discussion earlier that the priority score is computed as the sum of the following five values:
Stewardship and Outreach (from the HMZ table)
Extent, Impact, and Restoration (from the Species_HMZ table).
Using your expression building experience, see if you can come up with the code needed to populate the Priority field with the correct score.
Code that completes the task can be found here:
//Get the extent, impact, and restoration vals from the current Species_HMZ record var extent_val = $feature["Extent_Value"] var impact_val = $feature["Impact_Value"] var rest_val = $feature["Restoration_Value"] //Get the stewardship and outreach vals from the related HMZ record var hmz_guid = $feature["HMZ_GUID"] var hmz_lyr = FeatureSetByName($map, "HMZ") var feat_set = Filter(hmz_lyr, "GlobalID = '" + hmz_guid + "'") var feat = First(feat_set) var steward_val = feat["Stewardship"] var outreach_val = feat["Outreach"] //Sum and return the result var priority_val = steward_val + outreach_val + extent_val + impact_val + rest_val return priority_val
Save the expression with a name of calcPriority.
Save your changes to the Species_HMZ form.
Return to the Field Maps app on your mobile device and go through the addition of a Species_HMZ row (woody vines, extent of 2, and butterfly bush, extent of 1, are other species found in the Campground Area). Confirm that both the Restoration_Value and Priority fields are populated automatically.