Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* global $feature, $datastore, $layer, $map, FeatureSetByName, IIf, Buffer */
// FIELD: NAME
// EVENTS: INSERT, UPDATE
// Remove apostrophes from NAME field on CREATE and UPDATE events
// Test case: NAME="O'CONNELL" -> "OCONNELL"
// Remove apostrophes and normalize NAME to uppercase on CREATE and UPDATE events
// Test case: NAME="O'Connell" -> "OCONNELL"
var name = IIf(IsEmpty($feature.NAME), "", Text($feature.NAME));
return Replace(name, "'", "");
return Upper(Replace(name, "'", ""));
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ function normalizePart(value) {
while (Find(" ", part) > -1) {
part = Replace(part, " ", " ");
}
return part;
return Upper(part);
}

var name = normalizePart(Replace(IIf(IsEmpty($feature.NAME), "", $feature.NAME), "'", ""));
Expand All @@ -24,5 +24,5 @@ if (!IsEmpty(posttype)) {
Push(parts, posttype);
}

// Test case: NAME=" MAIN ", POSTDIR=null, POSTTYPE=" RD " -> "MAIN RD"
// Test case: NAME=" Main ", POSTDIR=null, POSTTYPE=" Rd " -> "MAIN RD"
return Concatenate(parts, " ");
41 changes: 31 additions & 10 deletions attribute-rules/calculation/Roads_Edit/unique_id.arcade
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* global $feature, $datastore, $layer, $map, FeatureSetByName, Intersects, First, Centroid, IIf, Buffer, Expects, Geometry, IsEmpty, Trim, Text, Count, Floor, Mid, Replace */
/* global $feature, $datastore, $layer, $map, FeatureSetByName, Intersects, First, Centroid, IIf, Buffer, Expects, Geometry, IsEmpty, Trim, Text, Count, Floor, Mid, Replace, Find, Upper */
// Ports the legacy UtransEditor getNewUniqueID logic, reading GRID1MIL/GRID100K from the USNG.
// Grid-cell zone mismatches at the UTM 11/12 boundary are ignored since this ID is only eyeballed, not parsed.
// FIELD: UNIQUE_ID
Expand All @@ -12,23 +12,44 @@ function trimCoordinate(value, leading) {
return Mid(digits, leading, Count(digits) - leading - 1);
}

function isLettersOnly(value) {
var letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
var normalized = Replace(value, ' ', '');
for (var index = 0; index < Count(normalized); index++) {
if (Find(Mid(normalized, index, 1), letters) == -1) {
return false;
}
}

return true;
}
Comment thread
Copilot marked this conversation as resolved.

// NAME/POSTDIR/POSTTYPE are computed directly because attribute rule execution order is not guaranteed,
// so FULLNAME may still hold the pre-edit value.
function nameSuffix() {
var name = Trim(IIf(IsEmpty($feature.NAME), '', Text($feature.NAME)));
var name = Upper(Trim(Replace(IIf(IsEmpty($feature.NAME), '', Text($feature.NAME)), "'", '')));
if (name == '') {
return '';
}

var postdir = Trim(IIf(IsEmpty($feature.POSTDIR), '', Text($feature.POSTDIR)));
var posttype = Trim(IIf(IsEmpty($feature.POSTTYPE), '', Text($feature.POSTTYPE)));
var postdir = Upper(Trim(IIf(IsEmpty($feature.POSTDIR), '', Text($feature.POSTDIR))));
var posttype = Upper(Trim(IIf(IsEmpty($feature.POSTTYPE), '', Text($feature.POSTTYPE))));

var value = '_' + name;
if (posttype != '') {
value += '_' + posttype;
}
if (postdir != '') {
value += '_' + postdir;
if (isLettersOnly(name)) {
if (posttype != '') {
value += '_' + posttype;
}
if (postdir != '') {
value += '_' + postdir;
}
} else {
if (postdir != '') {
value += '_' + postdir;
}
if (posttype != '') {
value += '_' + posttype;
}
}

return Replace(value, ' ', '_');
Expand All @@ -54,4 +75,4 @@ var grid1mil = gridMatch.GRID1MIL;
var grid100k = gridMatch.GRID100K;

// Test case: midpoint 12T VK 3756 1234 on "GOLD KEY LN" -> "12TVK37561234_GOLD_KEY_LN"
return Trim(grid1mil + grid100k + trimCoordinate(midpointGeom.x, 1) + trimCoordinate(midpointGeom.y, 2) + nameSuffix());
return Upper(Trim(grid1mil + grid100k + trimCoordinate(midpointGeom.x, 1) + trimCoordinate(midpointGeom.y, 2) + nameSuffix()));
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ for (var i in fieldsToCheck) {
var fieldName = fieldsToCheck[i];
var val = $feature[fieldName];

// Skip empty/null values (let mandatory field settings handle null checks)
if (IsEmpty(val)) { continue; }
// Skip null and empty-string values
if (IsEmpty(val) || val == '') { continue; }

// Retrieve the domain assigned to this specific field
var dom = Domain($feature, fieldName);
Expand Down
4 changes: 4 additions & 0 deletions attribute-rules/test/constrain_to_attribute_domain.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ test("constrain_to_attribute_domain accepts a coded domain value", () => {
expect(executor.execute({ "$feature": roadFeature({ SPEED_LMT: 35 }) })).toBe(true);
});

test("constrain_to_attribute_domain accepts an empty string", () => {
expect(executor.execute({ "$feature": roadFeature({ SPEED_LMT: "" }) })).toBe(true);
});

test("constrain_to_attribute_domain rejects an invalid coded domain value", () => {
expect(executor.execute({ "$feature": roadFeature({ SPEED_LMT: 30 }) })).toEqual({
errorMessage: "Invalid value [30] entered for field: SPEED_LMT"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { expect, test } from "vitest";
import { feature, loadExecutor } from "./_helpers.js";

const executor = await loadExecutor(
"calculation/Roads_Edit/remove_apostrophes_from_name.arcade",
"calculation/Roads_Edit/format_name.arcade",
[{ name: "$feature", type: "feature" }]
);

Expand All @@ -12,4 +12,8 @@ test("removes apostrophes from NAME", () => {

test("leaves names without apostrophes unchanged", () => {
expect(executor.execute({ "$feature": feature({ NAME: "WASHINGTON" }) })).toBe("WASHINGTON");
});

test("uppercases mixed-case names", () => {
expect(executor.execute({ "$feature": feature({ NAME: "O'Connell" }) })).toBe("OCONNELL");
});
41 changes: 41 additions & 0 deletions attribute-rules/test/spatial_calculations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,31 @@ function emptyDatastore() {
] });
}

const uniqueIdLineGeometry = {
type: "polyline",
paths: [[[424100, 4505000], [432500, 4505000]]],
spatialReference: { wkid: 102100 }
};

function nationalGridDatastore() {
return new ArcGISMap({ layers: [
new FeatureLayer({
title: "NationalGrid", geometryType: "polygon", spatialReference: { wkid: 102100 },
objectIdField: "OBJECTID",
fields: [
{ name: "OBJECTID", type: "oid" },
{ name: "GRID1MIL", type: "string" },
{ name: "GRID100K", type: "string" }
],
source: [feature({ OBJECTID: 1, GRID1MIL: "12T", GRID100K: "VK" }, {
type: "polygon",
rings: [[[420000, 4500000], [435000, 4500000], [435000, 4510000], [420000, 4510000], [420000, 4500000]]],
spatialReference: { wkid: 102100 }
})]
})
] });
}

const spatialExecutor = await loadExecutor("calculation/Roads_Edit/spatial_assignments.arcade", [
{ name: "$feature", type: "feature" }, { name: "$datastore", type: "featureSetCollection" }
]);
Expand All @@ -42,4 +67,20 @@ test("unique_id reports missing geometry", async () => {
"$feature": feature({ NAME: "MAIN", POSTDIR: null, POSTTYPE: null }), "$datastore": emptyDatastore()
});
expect(result).toBe("ERR:MISSING_GEOMETRY");
});

test("unique_id appends post direction before post type for non-letter names", async () => {
const result = await uniqueIdExecutor.executeAsync({
"$feature": feature({ NAME: "4500", POSTDIR: "W", POSTTYPE: "TER" }, uniqueIdLineGeometry),
"$datastore": nationalGridDatastore()
});
expect(result).toBe("12TVK28300500_4500_W_TER");
});

test("unique_id appends post type before post direction for letters-only names", async () => {
const result = await uniqueIdExecutor.executeAsync({
"$feature": feature({ NAME: "Main", POSTDIR: "W", POSTTYPE: "TER" }, uniqueIdLineGeometry),
"$datastore": nationalGridDatastore()
});
expect(result).toBe("12TVK28300500_MAIN_TER_W");
});
38 changes: 21 additions & 17 deletions attribute-rules/test/text_calculations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,30 +24,34 @@ test("remove_extra_whitespace returns an empty string for a missing NAME", () =>
).toBe("");
});

test("fullname_calculation combines normalized name parts", () => {
expect(
fullnameExecutor.execute({
$feature: feature({
NAME: " O'MAIN ",
POSTDIR: null,
POSTTYPE: " RD ",
}),
}),
).toBe("OMAIN RD");
test("fullname_calculation combines normalized uppercase name parts", () => {
expect(fullnameExecutor.execute({
"$feature": feature({
NAME: " O'Main ",
POSTDIR: null,
POSTTYPE: " Rd "
})
})).toBe("OMAIN RD");
});
Comment thread
acneville marked this conversation as resolved.

test("fullname_calculation includes both suffixes for numeric names", () => {
expect(
fullnameExecutor.execute({
$feature: feature({ NAME: "200", POSTDIR: "W", POSTTYPE: "St" }),
$feature: feature({
NAME: "200",
POSTDIR: "W",
POSTTYPE: "St"
}),
}),
).toBe("200 W St");
).toBe("200 W ST");
});

test("fullname_calculation omits missing name parts", () => {
expect(
fullnameExecutor.execute({
$feature: feature({ NAME: null, POSTDIR: " N ", POSTTYPE: null }),
}),
).toBe("N");
expect(fullnameExecutor.execute({
"$feature": feature({
NAME: null,
POSTDIR: " n ",
POSTTYPE: null
})
})).toBe("N");
});