I have an Arcade calculation attribute rule that sets polyline M-values to the cumulative length of the line.
The script works as expected. But I'm still a novice, so I thought I'd ask: Can the script be improved?
Notes about the script:
- It works for both singlepart and multipart features.
- It densifies true curves (unavoidable, since Arcade pre-densifies true curves).
- The code below is out-of-date. I've posted an updated version in a reply...further down in this post.
//var paths = [[[0, 5, null], [10, 10, null], [30, 0, null], [50, 10, null], [60, 10, null]]]; //JS //Only do the calculation if the geometry was changed/edited. if (!Equals(Geometry($feature), Geometry($originalfeature))) { var geom = Dictionary(Text(Geometry($feature))); var paths = geom['paths']; //Alternatively, this could be done with the Arcade distance() function. I chose to do the math manually so that the script can be used elsewhere, such as VSCode (for debugging). function pythagoras(x1, y1, x2, y2) { return sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2)); } for (var path_idx in paths) { var oldX = paths[0][0][0], oldY = paths[0][0][1], length = 0; for (var point_idx in paths[path_idx]) { var newX = paths[path_idx][point_idx][0], newY = paths[path_idx][point_idx][1]; length += pythagoras(oldX, oldY, newX, newY); paths[path_idx][point_idx][2] = length; oldX = newX; oldY = newY; } } console(paths); return { "result": { "geometry": Polyline(geom) } }; //Output: [[[0,5,0],[10,10,11.180339887498949],[30,0,33.54101966249685],[50,10,55.90169943749475],[60,10,65.90169943749476]]] }
(The script was adapted from this GitHub sample: Set Ms to Index.)
Thanks!
Looks pretty good, minor tweaks and comments
//Only do the calculation if the geometry was changed/edited.
// I like to exit early when condition is not met, makes the code a little more readable
if (Equals(Geometry($feature), Geometry($originalfeature))) {
return
}
var geom = Dictionary(Text(Geometry($feature)));
var paths = geom['paths'];
//Alternatively, this could be done with the Arcade distance() function. I chose to do the math manually so that the script can be used elsewhere, such as VSCode (for debugging).
function pythagoras(x1, y1, x2, y2) {
return sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2));
}
// Easier to read when on multi lines and moved out of the loop, it was being reset on each part, not sure that is what you wanted
var oldX = paths[0][0][0];
var oldY = paths[0][0][1];
// dont use length, that is a reserved function name
var line_length = 0;
for (var path_idx in paths) {
for (var point_idx in paths[path_idx]) {
var newX = paths[path_idx][point_idx][0];
var newY = paths[path_idx][point_idx][1];
line_length += pythagoras(oldX, oldY, newX, newY);
// can use a negative slice, will work when there is no Z
paths[path_idx][point_idx][-1] = line_length;
oldX = newX;
oldY = newY;
}
}
console(paths);
return { "result": { "geometry": Polyline(geom) } };
//Output: [[[0,5,0],[10,10,11.180339887498949],[30,0,33.54101966249685],[50,10,55.90169943749475],[60,10,65.90169943749476]]]
}
//Generic JS version: https://jsfiddle.net/5m1c69g3/
Thanks very much Mike. That really helps.
Hi Mike, here's my latest version, in case you're interested. I used your suggestions.
//Script title: Set polyline M-values to cumulative length of line
var geom = Dictionary(Text(Geometry($feature)));
var paths = geom['paths'];
//Using the Equals() function doesn't work as expected. See: Arcade: Editing M-values not treated as change to geometry (https://community.esri.com/t5/arcgis-pro-questions/arcade-editing-m-values-not-treated-as-change-to/m-p/1153917)
//if (Equals(Geometry($feature), Geometry($originalfeature)) ) {
if (Text(Geometry($feature)) == Text(Geometry($originalfeature))) {
console("The geometry has not changed. We don't need to redefine the M-Values.")
return
} else {
//Alternatively, this could be done with the Arcade distance() function. I chose to do the math manually so that the script can be used elsewhere, such as VSCode (for debugging).
function pythagoras(x1, y1, x2, y2) {
return sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2));
}
var oldX = paths[0][0][0];
var oldY = paths[0][0][1];
var line_length = 0;
for (var path_idx in paths) {
for (var point_idx in paths[path_idx]) {
var newX = paths[path_idx][point_idx][0];
var newY = paths[path_idx][point_idx][1];
//For multi-part features, we want the M-value for additional parts to start at the last M-value from the last part. We don't want to measure the distance *between* parts.
//So, for the first vertex in a given part, we will skip calculating the length between vertices.
if (point_idx != 0) {
line_length += pythagoras(oldX, oldY, newX, newY);
}
//We can use a negative slice: it will work when there is no Z.
paths[path_idx][point_idx][-1] = line_length;
oldX = newX;
oldY = newY;
}
}
console("The geometry has changed. So we *will* redefine the M-Values.")
return { "result": { "geometry": Polyline(geom) } };
}
@JohannesLindner might find this interesting.
Cheers!
The else is redundant, easier to read without the indent
//Script title: Set polyline M-values to cumulative length of line
var geom = Dictionary(Text(Geometry($feature)));
var paths = geom['paths'];
//Using the Equals() function doesn't work as expected. See: Arcade: Editing M-values not treated as change to geometry (https://community.esri.com/t5/arcgis-pro-questions/arcade-editing-m-values-not-treated-as-change-to/m-p/1153917)
//if (Equals(Geometry($feature), Geometry($originalfeature)) ) {
if (Text(Geometry($feature)) == Text(Geometry($originalfeature))) {
console("The geometry has not changed. We don't need to redefine the M-Values.")
return
}
//Alternatively, this could be done with the Arcade distance() function. I chose to do the math manually so that the script can be used elsewhere, such as VSCode (for debugging).
function pythagoras(x1, y1, x2, y2) {
return sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2));
}
var oldX = paths[0][0][0];
var oldY = paths[0][0][1];
var line_length = 0;
for (var path_idx in paths) {
for (var point_idx in paths[path_idx]) {
var newX = paths[path_idx][point_idx][0];
var newY = paths[path_idx][point_idx][1];
//For multi-part features, we want the M-value for additional parts to start at the last M-value from the last part. We don't want to measure the distance *between* parts.
//So, for the first vertex in a given part, we will skip calculating the length between vertices.
if (point_idx != 0) {
line_length += pythagoras(oldX, oldY, newX, newY);
}
//We can use a negative slice: it will work when there is no Z.
paths[path_idx][point_idx][-1] = line_length;
oldX = newX;
oldY = newY;
}
}
console("The geometry has changed. So we *will* redefine the M-Values.")
return { "result": { "geometry": Polyline(geom) } };
Looks good!
As Mike said, the else is unnecessary and makes it a little harder to read. Personally, I would declare geom and paths right before oldX, so that they are closer to where you actually use them.
Thanks. Good suggestion about the variables. I tried to clean them up:
var geom_text = Text(Geometry($feature));
if (geom_text == Text(Geometry($originalfeature))) {
return
}
function pythagoras(x1, y1, x2, y2) {
return sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2));
}
var geom = Dictionary(geom_text);
var paths = geom['paths'];
var oldX = paths[0][0][0];
var oldY = paths[0][0][1];
var line_length = 0;
for (var path_idx in paths) {
for (var point_idx in paths[path_idx]) {
var newX = paths[path_idx][point_idx][0];
var newY = paths[path_idx][point_idx][1];
if (point_idx != 0) {
line_length += pythagoras(oldX, oldY, newX, newY);
}
paths[path_idx][point_idx][-1] = line_length;
oldX = newX;
oldY = newY;
}
}
return { "result": { "geometry": Polyline(geom) } };
You could use the https://developers.arcgis.com/arcade/function-reference/geometry_functions/#equals function to compare Geo instead of a text comparison.
Yeah, although Equals() doesn’t work the way I would have expected…it doesn’t consider changes to M-values: Arcade: Editing M-values not treated as change to geometry
So that’s why I went with the text comparison.
Ahh good point, forgot about that.