|
POST
|
This tag should probably also be escaped correctly in the Community feed... @JesseCloutier
... View more
08-08-2023
11:28 AM
|
0
|
0
|
1505
|
|
POST
|
You haven't closed your for loop. Add a closing curly brace in line 16. Also, in line 15 your field name has a space. Use the actual field name, not the alias.
... View more
08-06-2023
10:50 PM
|
1
|
0
|
1676
|
|
POST
|
An Arcade expression can't interpret HTML. What you want is an Arcade element. An Arcade element returns a dictionary (which your code does) and can interpret that in different ways, depending on the "type" keyword (see here for different possible types). We just have to change your code a little bit so that it also returns a dictionary when the url is empty: var attachment = $feature.Attachment_URL
var result = "No photo available"
if (!isEmpty(attachment)){
result = `<p>Click the photo to open full size in new tab</p>
<p style="text-align:center;"><a href="${attachment}?size=full">
<img class="image_resized" style="width:100%;" src="${attachment}?size=lg" alt="Photo of ${$feature.Common_Name}"></a></p>`
}
return {
type : "text",
text : result
}
... View more
08-06-2023
10:37 PM
|
5
|
5
|
9818
|
|
POST
|
You can use Floor() for that. The opposite (always round up) is Ceil().
... View more
08-04-2023
02:01 AM
|
2
|
0
|
2103
|
|
POST
|
check the filter expression in line 6 (probably a copy/paste error) check that both tables actually have a field called "shape_area" just calculate the Area(): 'shape_area': Area(poly),
... View more
08-03-2023
01:26 PM
|
0
|
1
|
2285
|
|
POST
|
To post formatted code: In your loops over fs1 and fs2, you use feature as iteration variable. This overwrites the native Arcade function Feature. That's not a problem here, but in general, it's not recommended to do that, because you can't use those native functions anymore. You try to input arrays into the admin_type field. 'admin_type': ['Municipality'] should be 'admin_type': 'Municipality' You say you want to output the geometry, but you don't set it in your output fs nor in your out features. Upon further inspection, you seem to have points (municipalities), lines (districts) and polygons (weather events). You can't combine these in the same featureset. You can get the features intersecting a target feature with Intersects(). You would need a third loop over your merged features that intersects those features with the weather events. But actually, it should be faster to do it the other way around: loop over the weather events intersects with fs1 and copy each intersected feature do the same for fs2 Something like this: var municipalities =
var districts =
var weather_events =
out_fs = {
fields: [
{name: "admin_type", type: "esriFieldTypeString"},
{name: "name", type: "esriFieldTypeString"},
],
geometryType: "",
features: []
}
for(var weather_event in weather_events) {
var i_municipalities = Intersects(weather_event, municipalities)
for(var m in i_municipalities) {
var f = {attributes: {admin_type: "Municipality", name: m.Municipality}}
Push(out_fc.features, f)
}
var i_districts = Intersects(weather_event, districts)
for(var d in i_districts) {
var f = {attributes: {admin_type: "Regional District", name: d.admin_area_name}}
Push(out_fc.features, f)
}
}
return Featureset(out_fs)
... View more
08-03-2023
01:20 PM
|
0
|
0
|
1011
|
|
POST
|
You have created an Attribute Rule on the fc that writes to the table. As you said, that triggers only when you edit the fc. To get the same effect when you edit the table, you have to create another Attribute Rule there that reads from the fc: // Calculation Attribute Rule on the table
// Field: empty
// Triggers: Insert, Update
// load the polygon fc
var polygons = FeaturesetByName($datastore, "Polygons")
// find the correct polygon
var id = $feature.PolygonID
var poly = First(Filter(polygons, "PolygonID = @ID"))
if(poly == null) { return }
// and return its values:
return {
result: {
attributes: {
Field1: poly.Field1,
Field2: poly.Field2,
Field3: poly.Field3,
}
}
}
... View more
08-03-2023
12:07 AM
|
0
|
3
|
2296
|
|
POST
|
OK, I may have found a solution that works. You can enable symbol property connections in the symbology, which lets you choose your color programmatically (or with a field). I didn't suggest that originally, because this isn't reflected in the legend. But luckily, you have three treatment types which you can let correspond to the three color channels of an RGB value (red, green, blue). You can then vary the lightness of the color by using the rgb channel of the missing treatment(s). The varying lightness won't show up in the legend, so you have a manageable number of items there, and your viewers can get a sense of your point system. But they won't be able to directly get the points by looking at the color, because the color doesn't show up in the legend, so they still have to rely on labels or popups for that. Overlay corresponds to the red channel, Seal corresponds to the green channel, Crack Fill corresponds to the blue channel. Use the symbology expression from my previous answer and set the colors accordingly (you have to do that manually). This is just for the legend, the actual symbol color won't be affected by this. I chose gray instead of white for all 3 treatments: Allow symbol property connections For each of the treatment combinations (except None), map the symbol color to the expression below. The first three lines of the expression control how the color will be varied. rgb_channel_min: the minimum value for the missing channels (RGB channels go from 0 to 255). This should be 0 except for all 3 treatments, 100 works nicely there. rgb_channel_max: the maximum value for the missing channels. Don't set it too high, else your points are just white. Values I found to work: 1 treatment: 200 2 treatments: 230 3 treatments: 250 low_values_are_lighter: Should low point values be lighter than high point values? var rgb_channel_min = 0
var rgb_channel_max = 200
var low_values_are_lighter = false
var pt = [
$feature.Points_1,
$feature.Points_2,
$feature.Points_3,
]
var zeroes = 0
for(var p in pt) { zeroes += Includes([null, 0], pt[p]) }
var value = Sum(pt)
var max_value = (3 - zeroes) * 10.5
var normalized_value = value / max_value
var rgb_channel = IIf(low_values_are_lighter,
rgb_channel_max - normalized_value * (rgb_channel_max - rgb_channel_min),
rgb_channel_min + normalized_value * (rgb_channel_max - rgb_channel_min)
)
var rgb = [0, 0, 0]
for(var p in pt) {
rgb[p] = IIf(pt[p] > 0 && zeroes > 0, 255, Round(rgb_channel))
}
return `rgb (${Concatenate(rgb, ", ")})` So it's still a little manual work, but only for 8 items, not for 65+. Here is the result:
... View more
08-01-2023
01:43 AM
|
0
|
0
|
3597
|
|
POST
|
I tested that expression in a File GDB, but it doesn't seem to work in a shape file. If you're working in an Enterprise GDB, your DBMS also might not be able to handle the expression. Instead of going back and forth trying to find the right expression for your DBMS, it might be easier to just calculate a new field: # Calculate Field
# Field type: Short or Long
# Language: Python 3
# Field =
re.search(".*[A-Za-z].*", !STREETNUM!) is not None
# Code Block
import re This new field will be 1 if there is a letter in STREETNUM, so you can select by that field:
... View more
07-28-2023
04:37 PM
|
1
|
1
|
6242
|
|
POST
|
Your expression for "Overlay - 8" is wrong! You labeling expression can be massively simplified: var pt = [
$feature.Points_1,
$feature.Points_2,
$feature.Points_3,
]
// count the zeroes
var zeroes = 0
for(var p in pt) { zeroes += pt[p] == 0 }
// all values are zero -> None
if(zeroes == 3) { return "None" }
// two values are zero -> return the non-zero value
if(zeroes == 2) {
if(pt[0] > 0) { return "Overlay - " + pt[0] }
if(pt[1] > 0) { return "Seal - " + pt[1] }
if(pt[2] > 0) { return "Crack Fill - " + pt[2] }
}
// one or no value(s) are zero -> return all non-zero Trtmt values and the sum of the Points values
var trtmt = [
$feature.Trtmt_1,
$feature.Trtmt_2,
$feature.Trtmt_3,
]
var trt = []
for(var t in trtmt) {
if(pt[t] > 0) { Push(trt, trtmt[t]) }
}
return Concatenate(trt, ", ") + " - " + Sum(pt) You can input that expression into the symbology as Unique Value "field": This will give you a legend like this: But that's not really practical. There are 65 conditions in your original expression, and there are actually a lot more possible values, because you take the sum of all non-zero Point values. You would have to format all the colors, and it wouldn't provide any useful information to your viewers, because the color would be too similar. In my opinion, a better way would be to only symbolize the combination of treatments and only display the actual values in the label. This is the last part of the label expression, slightly adapted: var pt = [
$feature.Points_1,
$feature.Points_2,
$feature.Points_3,
]
var trtmt = [
$feature.Trtmt_1,
$feature.Trtmt_2,
$feature.Trtmt_3,
]
var trt = []
for(var t in trtmt) {
if(pt[t] > 0) { Push(trt, trtmt[t]) }
}
if(Count(trt) == 0) { return "None" }
return Concatenate(trt, ", ") And this gives you a much more manageable legend (only 8 items) that actually provides information to your viewers: Together with the labels:
... View more
07-28-2023
04:15 PM
|
1
|
1
|
3619
|
|
POST
|
The SQL that is allowed in Select Layer By Attributes is quite limited. For example, it can't handle the regex-like queries for "[A-Za-z]" that would search for values containing any letter in "normal" SQL. What you can do: CAST(YourField AS INTEGER) >= 0 and then invert the selection The SQL expression will only select values it can convert from string to integer (so all values that don't contain letters), and when you invert that, you get all values containing letters:
... View more
07-27-2023
04:24 PM
|
2
|
3
|
6259
|
|
POST
|
If your company has its own Portal, you need to supply its URL in line 2. If you're working in AGOL, the URL should be correct. Replace lines 4-6 with the layers you want to merge (and add more if applicable). The function we use to load layers in dashboards is FeaturesetByPortalItem. Its arguments are the portal where the layer is stored (we create that variable in line 2) the item id of the feature layer or service, you can find that in the service's URL: the layer id, which you can find in the layer's URL: the fields you want to load, the asterisk is a placeholder for "load all fields" if you want to load geometries (slower, not necessary here)
... View more
07-27-2023
03:58 PM
|
1
|
0
|
7405
|
|
POST
|
So, this was an adventure... You can do it, but it's complicated and there are several pitfalls along the way. You can't add an empty group layer file to an existing group layer (as you found out) You can only add layers to a group layer, so you have to convert your input feature classes first You could do it with MakeFeatureLayer, but there was some problem ( I forgot what it was exactly) So the best way I found is to use map.addDataFromPath, add the resulting layer to a group with map.addLayerToGroup and then delete the original layer with map.removeLayer You need to traverse your layer list recursively to be able to add group layers to group layers Bonus: You can't use the same lyrx when you are in recursive mode. When you add an empty group lyrx to the map and then try to add the same lyrx to it, it will raise a RuntimeError and complain about missing credentials. So you actually need to create new empty layer files as you go, so you need to find a way to do that (fortunately, it is quite easy) And then you realize that it always adds layers to the top of the toc, so you need to find a way to combat that. addDataToPath doesn't have a position argument, so you would have to do it with map.moveLayer, but I did not want to go down that rabbit hole, so I just used reversed() in 2 places to traverse the layer structure from end to start. This works for feature classes with the same geometry type, but arcpy changes position according to geometry type (points above lines above polygons), so it might not work for different geometry types. With all that said, here is the very ugly result: from pathlib import Path
from uuid import uuid4
def create_group_layer_file(folder, name):
"""Creates an empty group layer file.
folder: str or pathlib.Path, the folder where the file will be saved
name: str, the name of the layer (without extension)
Returns a str of the layer file's path
"""
uRI = f"CIMPATH={uuid4()}.json" # unique id for the layer
cim = {
"type" : "CIMLayerDocument", # this is a layer document
"layers" : [ # these are the included layers
uRI
],
"layerDefinitions" : [ # and these are the layers' definitions
{
"type" : "CIMGroupLayer",
"name" : name,
"uRI" : uRI
}
]
}
# create the file
lyrx = Path(folder) / f"{name}.lyrx"
with lyrx.open("w") as f:
f.write(str(cim))
# return the path as str
return str(lyrx)
def add_layer_recursive(target_map, layer, folder, group=None):
"""Adds a layer to a map. If the specified layer is a group layer, it adds
all its children recursively.
target_map: arcpy.mp.Map, the map to which the layer will be added
layer: str or dict, the layer that will be added
str: full path to a feature class, this will add a feature layer
dict: dictionary with the keys "name" and "layers", this will add
a group layer with the specified name and then it will add all
specified layers to this group
folder: str or pathlib.Path, a folder where temporary layer files will be created
you need write access to that folder!
group: arcpy.mp.Layer, the group layer to which teh layer will be added
only important for recursion, call the function with group=None
"""
# layer is path to feature class
if isinstance(layer, (str, Path)):
print(f"Adding {layer} to the map")
lyr = target_map.addDataFromPath(str(layer))
if group is not None:
print(f"\tMoving {layer} to group layer {group.name}")
target_map.addLayerToGroup(group, lyr)
target_map.removeLayer(lyr)
# layer is group layer definition dictionary
elif isinstance(layer, dict):
print(f"Adding group layer {layer['name']} to the map")
new_group_lyrx = create_group_layer_file(folder, layer["name"]) # create lyrx
new_group = target_map.addDataFromPath(new_group_lyrx)
new_group.name = layer["name"]
Path(new_group_lyrx).unlink() # delete lyrx
# call this function recursively
for sub_layer in reversed(layer["layers"]):
add_layer_recursive(target_map, sub_layer, temp_dir, new_group)
# and move the subgroup
if group is not None:
print(f"\tMoving group layer {new_group.name} to group layer {group.name}")
target_map.addLayerToGroup(group, new_group)
target_map.removeLayer(new_group)
# the map to which the layers will be added
target_map = arcpy.mp.ArcGISProject("current").activeMap
# a folder where you have write access
temp_dir = r"G:\ArcGIS\data\temp"
# the layer structure
# feature layers as path to their source (str or pathlib.Path)
# group layers as dict with the keys "name" and "layers"
gdb = Path(arcpy.env.workspace)
layers = [
gdb/"FC0",
{"name": "GroupA", "layers":[
gdb/"FC1",
gdb/"FC2",
]},
gdb/"FC3",
{"name": "GroupB", "layers":[
{"name": "GroupBA", "layers": [
gdb/"FC4",
gdb/"FC5",
]},
{"name": "GroupBB", "layers": [
gdb/"FC6",
{"name": "GroupBBA", "layers": [
gdb/"FC7",
gdb/"FC8",
]},
]},
]},
gdb/"FC9",
]
# create the temp dir
Path(temp_dir).mkdir(exist_ok=True)
# add the layers
for layer in reversed(layers):
add_layer_recursive(target_map, layer, temp_dir, None) Because the function works recursively, you can do as many levels as you like. Maybe ArcGIS has some limit, but the function doesn't. For example, the layer structure in lines 86 and onwards nests 3 group layers:
... View more
07-27-2023
03:43 PM
|
0
|
1
|
3751
|
|
POST
|
Assuming all your layers have the same fields, you should be able to merge them with a data expression: // load your layers
var p = Portal("https://www.arcgis.com")
var featuresets = [
FeaturesetByPortalItem(p, "b0d335151aad48a5883326b9aed69cdd", 0, ["*"], false),
FeaturesetByPortalItem(p, "b0d335151aad48a5883326b9aed69cdd", 1, ["*"], false),
FeaturesetByPortalItem(p, "b0d335151aad48a5883326b9aed69cdd", 2, ["*"], false),
]
// create the output featureset (this assumes that all layers have the same fields)
var merged_fs = {
geometryType: "",
fields: Schema(featuresets[0]).fields,
features: []
}
// append every feature of every input layer to the output featureset
for(var fs in featuresets) {
for(var f in featuresets[fs]) {
var att = Dictionary(f).attributes
Push(merged_fs.features, {attributes: att})
}
}
// and return that featureset
return Featureset(merged_fs) For the indicators, it's best to make that a separate question.
... View more
07-27-2023
03:00 PM
|
1
|
0
|
7410
|
|
POST
|
What isn't possible: You can't show the featureset you create in data expressions in the map. What you can do: if your featureset has geometry, you can flash that geometry on the map and zoom to it if your featureset has a field that corresponds to your map layer, you should be able to filter it
... View more
07-27-2023
02:43 PM
|
0
|
1
|
2467
|
| Title | Kudos | Posted |
|---|---|---|
| 1 | 01-30-2023 09:57 AM | |
| 1 | 05-18-2023 12:51 AM | |
| 1 | 03-05-2023 12:46 PM | |
| 1 | 12-07-2022 07:01 AM | |
| 1 | 06-21-2022 08:27 AM |
| Online Status |
Offline
|
| Date Last Visited |
02-03-2024
06:14 PM
|