|
POST
|
Ah, OK. For that, you need 2 rules (you should be able to do it in one, but I couldn't get it to work correctly, and it doesn't really matter). The first rule is a variation of the rule in my previous answer. Instead of only calculating the OutrunNumber for Medium Voltage conductors, it will calculate for all conductor types (but it will skip connectors, banks, etc). And instead of traversing a line, it will traverse a network. // Batch Calculation Attribute rule on ElectricLine
// field: OutrunNumber
// subtype: empty
// exclude from application evaluation
// Not a conductor? -> abort
if(!Includes([6, 9, 2], $feature.ASSETGROUP)) { return $feature.OutrunNumber }
// load all conductors and devices (split into switches and others)
// 6: Low, 9: Medium, 2: High voltage conductor
var conductors = Filter($featureset, "ASSETGROUP IN (6, 9, 2)")
// 23: Low, 37: Medium, 10: High voltage switch
var devices = FeatureSetByName($datastore, "ElectricDevice", ["OutrunNumber", "GlobalID"], false)
var switches = Filter(devices, "ASSETGROUP IN (23, 37, 10)")
var other_devices = Filter(devices, "ASSETGROUP NOT IN (23, 37, 10)")
// get the touching switch
var out_switch = First(Intersects(switches, $feature))
// no intersecting switch? -> abort
// the OutrunNumber will be supplied by the Feature that touches a mv switch
if(out_switch == null || IsEmpty(out_switch.OutrunNumber)) { return $feature.OutrunNumber }
// traverse the conductor network
var c_to_do = [$feature] // conductor features that need to be processed
var c_globalids = [] // GlobalIDs of already processed conductors
for(var i = 0; i < 9999; i++) {
var current_conductor = Pop(c_to_do)
Push(c_globalids, current_conductor.GlobalID)
for(var next_conductor in Touches(conductors, current_conductor)) {
if(!Includes(c_globalids, next_conductor.GlobalID)) {
Push(c_to_do, next_conductor)
}
}
if(Count(c_to_do) == 0) { break }
}
var conductor_updates = []
//var device_updates = []
var new_att = {OutrunNumber: out_switch.OutrunNumber}
for(var i in c_globalids) {
Push(conductor_updates, {globalID: c_globalids[i], attributes: new_att})
}
return {
result: out_switch.OutrunNumber,
edit: [{
className: "ElectricLine",
updates: conductor_updates
}]
} The second rule copies the OutrunNumber from an ElectricLine feature to all intersecting ElectricDevice features. // Batch Calculation Attribute Rule on ElectricLine
// field: OutrunNumber
// subtype: empty
// exclude from application evaluation
// Not a conductor? -> abort
if(!Includes([6, 9, 2], $feature.ASSETGROUP)) { return $feature.OutrunNumber }
// load all devices, filter out switches
var devices = FeatureSetByName($datastore, "ElectricDevice", ["GlobalID"], true)
devices = Filter(devices, "ASSETGROUP NOT IN (23, 37, 10)")
var device_updates = []
for(var device in Intersects(devices, $feature)) {
var update = {globalID: device.GlobalID, attributes: {OutrunNumber: $feature.OutrunNumber}}
Push(device_updates, update)
}
return {
result: $feature.OutrunNumber,
edit: [{
className: "ElectricDevice",
updates: device_updates
}]
} The order of these rules is important! First, the OutrunNumber has to be calculated for the Lines, then it has to be copied to the Devices. Be sure to not select a subtype.
... View more
05-22-2023
08:32 AM
|
1
|
2
|
2492
|
|
POST
|
That's probably it. To set the expression engine, you have to use the CIM: for lyr in m.listLayers(TWP+"_Sections"):
cim = lyr.getDefinition("V3")
for lblClass in cim.labelClasses:
lblClass.expressionEngine = "Arcade"
lyr.setDefinition(cim)
# rest of the code
... View more
05-20-2023
05:27 AM
|
1
|
0
|
1775
|
|
POST
|
I have no idea, that's just the official documentation I found for this.
... View more
05-20-2023
03:23 AM
|
0
|
1
|
2430
|
|
POST
|
The function will return a list of dictionaries that describe your relationship classes. You can analyse this list in the Python Window, but if you want to save it, you'll have to write extra code. Something like this: database = "G:/ArcGIS/projects/Test/Test.gdb"
out_csv = "G:/ArcGIS/projects/Test/Relationships.csv"
all_rs = listRelationshipClasses(database)
lines = ["Name", "Origin", "Destination", "Cardinality", "Composite"]
for rs in all_rs:
lines.append( [rs["name"], rs["originClassNames"][0], rs["destinationClassNames"][0], rs["cardinality"], str(rs["isComposite"])] )
with open(out_csv, "w") as f:
for line in lines:
f.write(",".join(line) + "\n")
... View more
05-20-2023
03:22 AM
|
0
|
0
|
2430
|
|
POST
|
And for SQL: https://desktop.arcgis.com/en/arcmap/latest/manage-data/using-sql-with-gdbs/returning-a-list-of-relationship-classes.htm
... View more
05-19-2023
01:02 PM
|
0
|
3
|
2473
|
|
POST
|
My code from this Idea: def listRelationshipClasses(database):
"""Returns a list of dictionaries describing the relationship classes inside the given database.
Tested with SDE and FGDB
database: str, path to the database
"""
desc = arcpy.da.Describe(database)
parents = [desc] + [c for c in desc["children"] if c["dataElementType"] == "DEFeatureDataset"]
rs = []
for p in parents:
rs += [c for c in p["children"] if c["dataElementType"] == "DERelationshipClass"]
return rs
listRelationshipClasses("Path\to\your\database.gdb")
listRelationshipClasses("Path\to\your\database.sde") import os, pprint
all_rs = listRelationshipClasses("path/to/your/database.sde") # see above
pprint.pprint(all_rs[0])
# filter by name
rs = [r for r in all_rs if r["name"] == "FilterName"]
rs = [r for r in all_rs if "PartOfTheName" in rs["name"]]
# filter by cardinality
rs = [r for r in all_rs if r["cardinality"] == "OneToOne"]
# filter by origin or destination table
rs = [r for r in all_rs if "OriginTableName" in r["originClassNames"]]
rs = [r for r in all_rs if "DestinationTableName" in r["destinationClassNames"]]
rs = [r for r in all_rs if "OriginTableName" in r["originClassNames"] and "DestinationTableName" in r["destinationClassNames"]]
# filter by dataset
rs = [r for r in all_rs if os.path.basename(r["path"]) == "DatasetName"]
# filter by flags
rs = [r for r in all_rs if r["isComposite"]]
rs = [r for r in all_rs if not r["isComposite"]]
# other flags: isAttachmentRelationship, isAttributed, isReflexive, isVersioned, canVersion, changeTracked
... View more
05-19-2023
01:01 PM
|
0
|
2
|
2473
|
|
POST
|
// function to load Featureset into RAM (for performance)
// community.esri.com/t5/arcgis-online-blog/improving-expression-performance-a-custom-function/ba-p/1288785
function Memorize(fs) {var temp_dict = {fields: Schema(fs)['fields'], geometryType: '', features: []}; for (var f in fs) {var attrs = {}; for (var attr in f) {attrs[attr] = Iif(TypeOf(f[attr]) == 'Date', Number(f[attr]), f[attr]);} Push(temp_dict['features'], {attributes: attrs});} return FeatureSet(Text(temp_dict))}
// load Featureset
var fs = Memorize(FeaturesetByPortalItem(Portal("https://www.arcgis.com"), "b0d335151aad48a5883326b9aed69cdd", 0, ["LGA", "Activity"], false))
// GroupBy county and activity fields
var local_activities = GroupBy(fs, ["LGA", "Activity"], [{name: "LocalActivityCount", expression: "1", statistic: "COUNT"}])
// GroupBy activity field
var state_activities = GroupBy(fs, ["Activity"], [{name: "StateActivityCount", expression: "1", statistic: "COUNT"}])
// create empty output featureset ( add percentage field to local_activities)
var out_fs = {
geometryType: "",
fields: Splice(
Schema(local_activities).fields,
[{name:"PercentageOfTotal", type: "esriFieldTypeDouble"}]
),
features: []
}
// iterate through the local activities
for(var f_local in local_activities) {
// find the corresponding feature in the state activities
var a = f_local.Activity
var f_state = First(Filter(state_activities, "Activity = @a"))
// copy the local activity attributes
var att = Dictionary(Text(f_local)).attributes
// calculate and append the percentage
att.PercentageOfTotal = f_local.LocalActivityCount / f_state.StateActivityCount * 100
// add the new feature to the output fs
Push(out_fs.features, {attributes: att})
}
return Featureset(Text(out_fs))
... View more
05-19-2023
10:58 AM
|
1
|
3
|
2089
|
|
POST
|
Convert Time Field indeed seems to ignore milliseconds. Instead, you can use Calculate Field with this Python expression: datetime.datetime.strptime(!TextField1!, "%d-%m-%Y %H:%M:%S.%f") (Replace "TextField1" with the name of your text field, take care that the name is surrounded by exclamation marks) Here's a quick test of the Converted vs. the Calculated dates (after I set the Date format for both fields):
... View more
05-19-2023
09:08 AM
|
1
|
0
|
8511
|
|
POST
|
Not directly, no. What you could do: Instead of referencing a layer/expression and doing the filtering and grouping in the Indicator widgets, you could create an expression that returns a featureset with two fields: an indicator id and the corresponding value. Then, each Indicator widget would only have to filter for its id and display the value. Something like this: var fs = FeaturesetByPortalItem(...)
var out_fs = {
geometryType: "",
fields: [
{name: "Indicator", type: "esriFieldTypeString"},
{name: "Value", type: "esriFieldTypeDouble"},
],
features: []
}
// Indicator 1:
var v = Count(Filter(fs, "Field = 123"))
Push(out_fs.features, {attributes: {Indicator: "count of 123", Value: v}})
// Indicator 2:
var v = Count(Filter(fs, "Field = 345"))
Push(out_fs.features, {attributes: {Indicator: "count of 345", Value: v}})
// ...
return Featureset(Text(out_fs)) And for the final Indicator, you could just use the SUM of the Value field. Of course, that would require you to edit all the other Indicator widgets, so it might be easier to just redo the filter for the final Indicator...
... View more
05-19-2023
08:29 AM
|
2
|
3
|
5869
|
|
POST
|
If that answered your question, please accept the solution, so that this thread is shown as answered.
... View more
05-19-2023
08:06 AM
|
0
|
0
|
1337
|
|
POST
|
@GIS_Solutions sent me an excerpt of their data. For context: They have an electrical Utility Network. The lines in question are transmission lines. The transmission lines are divided into multiple features, depending on asset type (eg underground/overhead). The different transmission lines don't touch each other (though they do intersect). The singular features are not oriented, meaning that it is not assured that the end point of one feature always touches the start point of the next feature They want a Batch Calculation Attribute Rule to copy the OutrunNumber (String) attribute of the outgoing switches to all features of the respective transmission lines // Batch Calculation Attribute Rule
// feature class: "ElectricalLine"
// subtype: "Medium Voltage Conductor"
// field: "OutrunNumber"
// exlude from application evaluation
// load the medium voltage switches
var devices = FeatureSetByName($datastore, "ElectricDevice", ["OutrunNumber"], false)
var mv_switches = Filter(devices, "ASSETGROUP = 37")
// get the touching switch
var mv_switch = First(Intersects(mv_switches, $feature))
// no intersecting switch? -> abort
// the OutrunNumber will be supplied by the Feature that touches a mv switch
if(mv_switch == null || IsEmpty(mv_switch.OutrunNumber)) { return $feature.OutrunNumber }
// load all medium voltage conductors
var mv_conductors = Filter($featureset, "ASSETGROUP = 9")
// traverse the line, get all GlobalIDs
var globalids = []
var current_conductor = $feature
for(var i = 0; i < 9999; i++) { // Arcade has no while loop, so we have to fake it with a loooong for loop and a break
// get the next mv conductor
// conditions: it touches the current test feature and hasn't been tested yet
Push(globalids, current_conductor.GlobalID)
var touching_conductors = Touches(mv_conductors, current_conductor)
current_conductor = null
for(var next_conductor in touching_conductors) {
if(!Includes(globalids, next_conductor.GlobalID)) {
current_conductor = next_conductor
break
}
}
if(current_conductor == null) { break }
}
// construct the update instruction
var updates = []
for(var i in globalids) {
var update = {globalID: globalids[i], attributes: {OutrunNumber: mv_switch.OutrunNumber}}
Push(updates, update)
}
return {
result: mv_switch.OutrunNumber,
edit: [{
className: "ElectricLine",
updates: updates
}]
} The data you sent me was in a FGDB, but your code suggests an EGDB. Change the table names in lines 10 & 52 to match your database schema. I have absolutely no idea why, but this rule will only work once (at least in the FGDB you sent me, maybe it's corrupted or this is expected behavior for batch rules). To run it again, follow these steps: Disable the rule, save Enable the rule, save Evaluate Rules
... View more
05-19-2023
08:02 AM
|
1
|
0
|
2518
|
|
POST
|
Option 1: Use the Attributes Panel. If you select a feature, you can show all related features in the Attributes Panel and manually select them there. Option 2: Automatically select all related features You can make it so that when you select a feature, all related features get automatically selected. This options can be found in 2 places: The layer options: The Attribute Table:
... View more
05-19-2023
05:52 AM
|
0
|
0
|
1353
|
|
POST
|
Hey Erica, that's great to hear! Please add you support to this Idea, to hopefully have this functionality in native Arcade some day.
... View more
05-19-2023
05:38 AM
|
1
|
0
|
3406
|
|
POST
|
Possible error sources: Your field name "Variant_Name" is different from what I used in my test data ("Variant"). Replace every "Variant" with "Variant_Name". I messed up and used the same variable name "f" for both the Feature and the frequency. Try this: // group by date and site, getting the max frequency
var grouped_fs = GroupBy(fs, ['Sample_Date','Site_ID'], [{name: 'Estimated_Frequency_Reads', expression: 'Estimated_Frequency_Reads', statistic: 'MAX' }] )
// create an ampty featureset for the join with variant
var join_fields = [
{name: "Variant_Name", type: "esriFieldTypeString"}
]
var out_fs = {
geometryType: "",
fields: Splice(Schema(grouped_fs).fields, join_fields),
features: []
}
// join with the original fs to get the variant
for(var f in grouped_fs) {
var d = f.Sample_Date
var s = f.Site_ID
var freq = f.Estimated_Frequency_Reads
var join = First(Filter(fs, "Sample_Date = @d AND Site_ID = @s AND Estimated_Frequency_Reads = @freq"))
Push(out_fs.features, {attributes:{Sample_Date: Number(d), Site_ID: s, Estimated_Frequency_Reads: freq, Variant_Name: join.Variant_Name}})
}
return Featureset(Text(out_fs)) If that still doesn't work, I'd need access to your actual data.
... View more
05-19-2023
03:58 AM
|
0
|
0
|
1496
|
| 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
|