|
POST
|
Sure. It gets a little more complicated, that's why I didn't post it in the original answer. Setup: Point feature class TestPoints Line feature class TestLines both have a field "IntegerField" TestPoints.IntegerField has to be the same value as the closest feature of TestLines This has to work when inserting points, updating points, inserting lines, updating lines, and deleting lines // Calculation Attribute Rule on TestPoints
// Triggers: Insert, Update
// Field: IntegerField
// returns a field value of the closest TestLines feature
// This is the code from the original answer, slighlty modified to return the whole feature, not just a field value
// also, I made it into a function to keep the code below cleaner
function get_closest_feature(test_feature, compare_feature_set) {
var min_distance = 9999999
var closest_feature = null
for(var f in compare_feature_set) {
var d = Distance(test_feature, f)
if(d < min_distance) {
min_distance = d
closest_feature = f
}
}
return closest_feature
}
// only run if this is a new feature or the geometry got updated
if($editcontext.editType == "INSERT" || !Equals(Geometry($feature), Geometry($originalfeature))) {
// load TestLines
var lines = FeatureSetByName($datastore, "TestLines", ["IntegerField"], false)
// find the closest TestLines feature
var close_lines = Intersects(lines, Buffer($feature, 100, "Meters"))
var closest_line = get_closest_feature($feature, close_lines)
// return its field value
if(closest_line != null) {
return closest_line.IntegerField
}
// there is no TestLines feature within the specified buffer distance
return null
} // Calculation Attribute Rule on TestLines
// Triggers: Insert, Update, Delete
// Field: empty
// Exclude from application evaluation
// Edits the IntegerField values of nearby TestPoints features
function get_closest_feature(test_feature, compare_feature_set) {
var min_distance = 9999999
var closest_feature = null
for(var f in compare_feature_set) {
var d = Distance(test_feature, f)
if(d < min_distance) {
min_distance = d
closest_feature = f
}
}
return closest_feature
}
// load features
var points = FeatureSetByName($datastore, "TestPoints", ["GlobalID"], false) // we edit the point using their GlobalID as identifier
var lines = FeatureSetByName($datastore, "TestLines", ["IntegerField"], false)
// if we're deleting this line, we have to remove it from the lines feature set,
// because at this point, it's still there
if($editcontext.editType == "DELETE") {
var gid = $feature.GlobalID
lines = Filter(lines, "GlobalID <> @gid")
}
// create array with updates to TestPoints
var update_array = []
// create array to keep track of already visited points
// we have to recalcluate all points that were nearby the old geometry and all points that are nearby the new geometry
// some/many points will fall in both categories, but we don't need to check them twice.
// if there are many points, this will save some time.
var visited = []
// we need to check all points close to the old and the new geometries.
var geometries = [Geometry($feature), Geometry($originalfeature)]
for(var g in geometries) {
// when we insert a new line, Geometry($originalfeature) is null and fails the code below.
// in that case, just continue with the next geometry.
if(geometries[g] == null) { continue }
// find all points nearby the geometry, remove the points we already checked
var points_in_buffer = Intersects(points, Buffer(geometries[g], 100, "Meters"))
if(Count(visited) > 0) {
points_in_buffer = Filter(points_in_buffer, "GlobalID NOT IN @visited")
}
// recalculate each point
for(var p in points_in_buffer) {
var new_value = null // default is null (in case there is no nearby line feature)
var close_lines = Intersects(lines, Buffer(p, 100, "Meters"))
var closest_line = get_closest_feature(p, close_lines)
if(closest_line != null) {
new_value = closest_line.IntegerField
}
// we identify the point feature that we want to update by its GlobalID and specify its new attributes in a dictionary.
Push(update_array, {"globalID": p.GlobalID, "attributes": {"IntegerField": new_value}})
// hey, we already checked this point, no need to do it again for the next geometry!
Push(visited, p.GlobalID)
}
}
// instead of returning a singular field value, we can also return a dictionary.
// this dictionary can contain edits that we want to make to other feature classes.
// documentation for the dictionary keywords can be found here:
// https://pro.arcgis.com/en/pro-app/latest/help/data/geodatabases/overview/attribute-rule-dictionary-keywords.htm
return {
"edit": [
{
"className": "TestPoints", // we want to edit TestPoints
"updates": update_array // we want to apply all the new IntegerField values that we calculated above
}
]
} Inserting TestPoints Moving TestPoints Inserting TestLines Moving TestLines Deleting TestLines As I said in the original answer, be mindful of the buffer distance you chose. The point in the north east can't find line 1, because my buffer distance is too small.
... View more
03-18-2022
02:38 AM
|
0
|
0
|
5192
|
|
POST
|
Convert back to arcpy.Polyline and call getLength(): point_list = [[1000, 1000], [2000, 2000], [3000, 3000], [4000, 4000]]
point_array = arcpy.Array([arcpy.Point(p[0], p[1]) for p in point_list])
polyline = arcpy.Polyline(point_array, spatial_reference=arcpy.SpatialReference(25832))
polyline.getLength("PLANAR", "METERS") # uses cartesian coordinates
# 4242.64
polyline.getLength("GEODESIC", "METERS") # considers Earth's curvature
# 4231.36
... View more
03-17-2022
06:16 AM
|
3
|
1
|
2957
|
|
POST
|
The FeatureSetBy*() functions return feature sets (like the name says), which are collections of features. You have to return the attributes of a feature, not the whole set. var otherFeatureClass= FeatureSetByName($datastore,'OtherFeatureClass')
var firstFeature = First(otherFeatureClass)
return firstFeature.Name
... View more
03-17-2022
02:30 AM
|
1
|
1
|
2152
|
|
POST
|
Easiest way is probably to use Python: Open the Python window Copy and paste the script below Modify the layer and fields variables Run You may have to refresh the map to see the changes # Name of the layer (if loaded in map) or path to the feature class / shape file
layer = "test"
# fields that are copied. First one has to be SHAPE@
fields = ["SHAPE@", "TextField"]
# read the original points
original_points = [row for row in arcpy.da.SearchCursor(layer, fields)]
with arcpy.da.InsertCursor(layer, fields) as cursor:
for point in original_points:
# create the 8 new coordinate pairs
coords = point[0].firstPoint
new_coords = [
[coords.X - 25, coords.Y],
[coords.X - 50, coords.Y],
[coords.X + 25, coords.Y],
[coords.X + 50, coords.Y],
[coords.X, coords.Y - 25],
[coords.X, coords.Y - 50],
[coords.X, coords.Y + 25],
[coords.X, coords.Y + 50],
]
for nc in new_coords:
# create a copy of the original point's attributes
new_point = list(point)
# change the copy's geometry
new_point[0] = arcpy.PointGeometry(arcpy.Point(nc[0], nc[1]))
# write copied point to shape file
cursor.insertRow(new_point) Before After (note that I only specified "TextField" to be copied)
... View more
03-15-2022
05:01 AM
|
2
|
1
|
1846
|
|
POST
|
So, this is a case of me not knowing how Dashboard pie charts work... I thought you would supply a numeric and a label column, in which case the GroupBy would have sufficed. But you only supply one column and ArcGIS groups those values on its own. Reading your question again, you correctly outlined the general process, although you can do it without using an extra dictionary: // load your feature set
var dict = {
fields: [
{ name: "wholename", type: "esriFieldTypeString" }
],
geometryType: "",
features: [
{"attributes": {"wholename": "abc"}},
{"attributes": {"wholename": "abc"}},
{"attributes": {"wholename": "def"}}, // singular name
{"attributes": {"wholename": "ghi"}},
{"attributes": {"wholename": "ghi"}},
{"attributes": {"wholename": "ghi"}},
],
};
var tablefs = FeatureSet(Text(dict))
// group by name, only keep multiple occurences
var grouped_fs = GroupBy(tablefs, "wholename", {"name": "NameCount", "expression": "wholename", "statistic": "Count"})
var duplicate_fs = Filter(grouped_fs, "NameCount > 1")
// construct an SQL filter statement with which to filter the original data
// wholename IN ('Name1', 'Name2', 'Name3')
var filter_names = []
for(var f in duplicate_fs) {
Push(filter_names, f.wholename)
}
var filter_statement = `wholename IN ('${Concatenate(filter_names, "', '")}')`
// filter the original feature set with this statement and return the result
// this contains all the original rows with duplicate names
return Filter(tablefs, filter_statement)
... View more
03-14-2022
12:24 AM
|
1
|
0
|
5225
|
|
POST
|
I think you're using the wrong tool. Merge combines multiple input datasets into a single, new output dataset. You want to use Append, which appends a dataset to an already existing one. Also, make sure that you published with permission to edit the dataset, or append to the "original", unpublished feature class. The edits should show up in the published service, too.
... View more
03-11-2022
02:18 AM
|
4
|
0
|
1536
|
|
POST
|
Features in JSON (Conversion)—ArcGIS Pro | Dokumentation
... View more
03-11-2022
02:10 AM
|
1
|
3
|
2840
|
|
POST
|
Speedwise, no. Push is even slightly slower, though you will only start to realize that when appending multiple hundred thousand elements... Time in seconds to append x elements to an array Elements i++ Push
100 0.000 0.000
1000 0.005 0.008
10000 0.026 0.027
100000 0.126 0.177
1000000 1.417 1.727 For most use cases, you won't need that many elements. Then it comes down to preference in writing and reading the code. For example, I think i++ looks cleaner, so I might use that in small expressions, even if it takes up an extra line to declare the index variable. But in some of my longer expressions, I'm appending to multiple arrays throughout the code. This means that I'd have to declare an index variable for all of the arrays, and I'd have to remember which belongs to which array or make the names more descriptive ("i_array_3" vs. "k"), which makes the code harder to write and read. So for that I use Push, because then it's completely clear to which array you are appending an element. And because I don't want to switch between the two styles in my project, I stick with Push.
... View more
03-10-2022
11:21 PM
|
1
|
0
|
4746
|
|
POST
|
Re: Arcade: Increment operators (++x and x++) - Esri Community Legacy Appending var arr = []
// Previously, you had to append elements like this
var i = 0
arr[i++] = "a"
arr[i++] = "b"
// Now, you can also do it like this:
Push(arr, "a")
Push(arr, "b")
... View more
03-10-2022
04:07 AM
|
2
|
2
|
4784
|
|
POST
|
intersectLayer is a FeatureSet. It will be shown in the expression builder's test, but popups can't show it. You have to build a string: var inspection_dates = []
for(var f in intersectLayer) {
Push(inspection_dates, f.Inspection_Date)
}
return Concatenate(inspection_dates, TextFormatting.NewLine)
... View more
03-10-2022
12:55 AM
|
2
|
0
|
1896
|
|
POST
|
GoupBy() returns a FeatureSet (the same data type as your tablefs). If I understand your code correctly, you want to filter out names that only show up once? You can use Filter() for that: var alln = groupby(tablefs, 'wholename', {"name": "NameCount", "expression": "wholename", "statistic": "Count"});
var dups = Filter(alln, "NameCount > 1")
... View more
03-10-2022
12:50 AM
|
0
|
0
|
5238
|
|
POST
|
It's A. You can't read FCs with Attribute Rules in ArcMap. (It's also B, because if you can't see it, you can't write to it) Users that have to write to the FC have to use Pro. You can create a database view for ArcMap-Users that need to see the FC. They won't be able to see the FC, but they will be able to work normally with the View. You can basically just select everything from your FC to be shown in the View: SELECT * FROM DataOwnerName.FeatureClassName
... View more
03-10-2022
12:16 AM
|
2
|
0
|
1329
|
|
POST
|
Is that correct? No. What it means is: If you don't do it yourself, the Attribute Rule will take care of the conversion between the data types. To be sure about the results (which should be best practice), you should do it yourself. Take this rule: // converts the value from the text field to integer and double
var txt = $feature.TextField
if(IsEmpty(txt)) { return }
return {"result": {"attributes": {"IntegerField": txt, "DoubleField": txt}}} I'm not doing any type casting here. I return text values for numeric fields, ArcGIS automatically takes care of the conversion. Great, less work for me! Let's see how that turns out: Uh oh, something is wrong here... I'm German, and we (and also the German ArcGIS locationing) use comma as decimal separator and dot as thousands separator. If I input numbers in American format as text (stupid, but just as an example), I get unexpected results. I expected int(1.5) to be floor(1.5) = 1, but ArcGIS uses Round(1.5) = 2 So the Attribute Rule automatically casted between the data types, but to be sure about the results I get (again. that should be best practice), I should do it myself: var txt = $feature.TextField
if(IsEmpty(txt)) { return }
txt = Replace(txt, ",", "") // remove American thousands separator
txt = Replace(txt, ".", ",") // replace decimal separator
var dbl = Number(txt)
var int = Floor(dbl)
return {"result": {"attributes": {"IntegerField": int, "DoubleField": dbl}}} Same concept applies to conversion from and to date. Probably more so, seeing how many different date formats there are... As for geometries: Yes, you just return the Polyline, which is already a Geometry.
... View more
03-09-2022
03:32 AM
|
2
|
0
|
930
|
|
POST
|
You return inside the for loop, not outside. When you return from a function, you stop executing it at that point. Everything after it will not be evaulated: function test() {
var x = 2
return x // returns 2
// everything after this will never get executed.
// the new value of x will never be returned, x won't even get changed at all.
x += 5
return x
} So when you return inside a for loop, you return just the first element: var arr = [25, 30, 103]
for(var i in arr) {
return arr[i] + 1 // this will just return 26 and then stop
} What you want to do is this: Create an object before the loop that will hold all the intermediate results. In most cases, this will be an array or a string. Inside the loop, append your intermediate results to that object. After the loop, return that object. var arr = [25, 30, 103]
var output_arr = []
for(var i in arr) {
Push(output_arr, arr[i] + 1) // append the intermediate result
}
return output_arr // this will return [26, 31, 104] All in all you just have to restructure your code a bit: var myList = ["Yes", "Yes", "No", "No", "Yes"]
var field1 = [1, 1, "N/A", "N/A", 1]
var field2 = [1, 1, "N/A", "N/A", 1]
var field3 = [1, 1, "N/A", "N/A", 1]
var myHTML = "" // this string will hold all the tables
for (var v in myList){
if(myList[v] == "Yes"){
var testNum = (v + 1)
var i = v
var firstField = field1[i]
var secondField = field2[i]
var thirdField = field3[i]
// append the table html. Note that this isn't a dict anymore!
myHTML += `
<table style="width:80%">
<tbody>
<tr style="background-color:#636363;color:#FFFFFF;font-size:16px;text-align:center;">
<td colspan="2">
<span style="font-size:16px;">
<strong>Test ${testNum} Results</strong></span>
</td>
</tr>
<tr>
<td>
<span style="font-size:14px;">
<strong>Volume Min</strong></span>
</td>
<td>
<span style="font-size:14px;">${firstField}</span>
</td>
</tr>
<tr style="background-color:#d9d9d9;">
<td>
<span style="font-size:14px;">
<strong>Total Volume Flowed</strong></span>
</td>
<td>
<span style="font-size:14px;">${secondField}</span>
</td>
</tr>
<tr>
<td>
<span style="font-size:14px;">
<strong>Accuracy (%)</strong></span>
</td>
<td>
<span style="font-size:14px;">${thirdField}</span>
</td>
</tr>
</tbody>
</table>
`
} else {
myHTML += "<p>No results for Test " + (v + 1) + "</p>"
}
}
// return the complete HTML outside of the for loop
return {"type": "text", "text": myHTML}
... View more
03-09-2022
02:34 AM
|
3
|
0
|
27066
|
|
IDEA
|
I think the explanation is quite intuitive and tells you everything you need to know in a very concise way. A three-dimensional array of numbers We're storing numeric information. The numbers are stored in a hirarchical structure. We have to go 3 levels deep to get the numbers The inner-most array contains the x,y,z,m coordinates of a single point So that's what the numbers represent, cool. It makes sense to put that information first, instead of going though the structure top-down. This translates to p = [x, y, z, m] The second dimension contains additional points making up a line segment We already know the second dimension contains multiple instances of the first dimension (that' s what "dimension" means in the context of arrays: Higher dimensions hold multiple instances of lower dimensions). The new information is that these form a segment. This means segment = [p0, p1, p2] The third dimension allows the polyline to have multiple segments Alright, that's why we need the third dimension. This means paths = [segment0, segment1] So, from just 4 sentences, I know what the data represents, how it is structured, and how I can get to the information I need. The documentation is intuitive enough. The real problem is that it is somewhat wrong: It is correct for creating a Polyline (or Polygon). But if you want to read the paths/rings property of an existing geometry, it is not number[][][] (3D number array), but Point[][] (2D Point array). var p0 = [1, 1, 0, 0] // [x, y, z, m]
var p1 = [2, 2, 0, sqrt(2)]
var segment0 = [p0, p1]
var paths = [segment0]
Console("paths used to create the geometry:")
Console(paths + "\n")
var geo = Polyline({"paths": paths, "spatialReference": {"wkid": 25832}})
Console("paths read from the geometry:")
Console(geo.paths + "\n")
// I want to get the x coordinate of the start point.
//Console(geo.paths[0][0][0]) // this will fail, because geo.paths is not number[][][], but Point[][]
Console(geo.paths[0][0].x)
Console(geo.paths[0][0].type) paths used to create the geometry:
[[[1,1,0,0],[2,2,0,1.414213562373095]]]
paths read from the geometry:
[[{"x":1,"y":1,"z":0,"m":0,"spatialReference":{"wkid":25832}},{"x":2,"y":2,"z":0,"m":1.414213562373095,"spatialReference":{"wkid":25832}}]]
1
Point
... View more
03-09-2022
01:28 AM
|
0
|
0
|
1562
|
| 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
|