If your problem involves finding or comparing a number of things in Neara, you will likely take advantage of lists and collections.
Some examples of when lists and collections are used:
Building a report of all the spans that cross other spans, retrieving the distance between the top and bottom crossing spans in various environments, as well as properties of those spans. Here the user would build a list of spans that are below other spans, then query those list items for their various properties, locations, etc.
Building a report of all cross-arms that are supporting HV lines to analyse stresses, bending moment etc, on those cross-arms. The user may create a list of all cross-arms on a pole, filtered by a minimum voltage of the spans attached to each cross-arm.
Identifying the direction of wind that causes the highest stress on a pole. The user would create a list of environments on the pole, then query each item in that list, returning the properties of the environment for which the pole has highest stress.
This article explains the composition and uses of both concepts.
Lists
Lists are one of the more powerful formulaic concepts in Neara. You can create lists of objects / items of the same "type".
The simplest form of a list can be created using the list()
function. Note that all items in this list are of type Text
:
The type will be displayed in the field settings menu above the formula definition, as well as in the schema editor, as List<something>
. The items in the following list are of type Integral
:
You will also encounter lists of more exotic types, like lists of poles, spans, strain sections, environments, etc.
Some of these are accessible as in-built fields to objects in the model schema. For example, on a poles
report / from a pole object, you have access to a field called spans
, which returns a list of all the spans attached to that pole:
Various in-built functions also return lists. For example, find_nearby_poles(<object>, <distance>)
returns a list of nearby poles:
Other formulas to explore include:
find_nearby_spans
find_spans_below
find_crossing_spans_below
Searching for the word find
in the schema editor's documentation section will reveal other similar functions:
More list-related functions can be found in the second part of this article.
Filtering lists
Given a list of objects, you can use various functions to return a reduced list of objects. A very common function for this purpose is filter()
Filter takes two inputs: a list of items, and a "match" - a list of boolean values (True
or False
) of the same length as the first list. It will then return the items in the first list that align with a True
value in the second list.
Visualising the operation like this may help with intuition:
You can create the second list by performing a logical operation over a list. For example, given a list of numbers, I want only numbers higher than 10:
1. Define the list
2. Define the match
3. Use filter to receive the expected, filtered list of numbers
You can may want to condense this operation by defining the match directly in the filter function as follows:
Accessing values of list items
After defining your list, it is possible to access the properties of individual items in the list. This is most simply demonstrated using the in-built lists that connect objects in the schema, like the field spans
on a pole report.
To access properties of the items of a list, use the []
sign. For example:
On a span report,
pole1.height
will return a single value, the height of a poleOn a pole report,
spans[].length
will return a list of values, the lengths of the spans attached to the poleOne application of this is to help filter lists to just the item or items of interest. For example, on a pole report, the following formula will return a list of spans attached to this pole that are over 40m long
Note that the []
sign can chain together to access items within items. On a pole report, writing the formula spans[].MidCableObjects[].type
will return a list of all the types of all the Mid Cable Objects of all the spans attached to a given pole.
Collections
When you make a variable of a List<>
type in Neara, you automatically also create a Collection. A Collection is an extension of the pole's schema, defined by the list, and is a powerful feature in Neara's formula language.
Simple example
On a pole report, create a new custom field called u_my_simple_list
defined as follows:
Note that there are three poles in the design, so there are three rows in the pole report.
You can access the collection that u_my_simple_list
has just defined by opening the report configuration (click the “gear” icon in top right corner) and select c_my_simple_list
from the drop-down menu:
The report is now no-longer a pole report, but a c_my_simple_list report. Notice that each row corresponds to one item in the u_my_simple_list
column of the poles report:
A way to understand this is to imagine the collection as the result of combining all the items in all the cells of the u_my_simple_list
column, then creating a row for each item in that combined list in this new report.
The following colour-coding may help visualize the operation:
Advanced example
To explore some of the more advanced concepts, we'll make a slightly more complex collection on the pole report.
Start by turning the report back into a pole report in the report configuration, as described above.
The following formula will return all the spans on each pole for which the span length is > 40m
:
If your project contains spans of over 40m
length, when you navigate into that collection (via the report configuration) you will see a number of rows:
Collections come with a number of built-in fields:
Built-in field | How it is presented in this example |
The | In this case: |
The items that are listed, which will be named with the suffix | In this case, |
| A boolean value that is
In this case, the items that are listed are spans, which are selectable in the perspective view.
This is not so for the first collection we defined above, as you can't "click on" "A" in the perspective view. |
| Similar to above but detects whether attached / parent objects are currently selected in perspective view |
Accessing parent properties from the collection object
The built-in links to the parent item (in this case pole
) and the list item (in this case spans_over_40m_item
) allow you to access the properties of those objects.
For example, we can get the ground clearance of each span as follows:
Similarly we can access the pole height of the parent pole item as follows:
Accessing a collection's properties via other connected objects
You can access the properties of the collection in the same manner as you can access any in-built schema item properties. The fields are accessible via the c_spans_over_40m
field.
For instance, back in the original pole report, we can get a list of the ground clearances for all spans over 40m as follows:
We can then find the minimum ground clearance for all spans attached to a given pole that are over 40m in length as follows:
These examples demonstrate the power of custom collections in Neara:
First we identified a list of items of interest that relate in some way to our poles i.e. “find me all long spans for each pole”
Then we went into that collection and performed some calculations and queries on a per-collection-item basis i.e. “for each long span for each pole, give me the ground clearance”
Finally, we went back to
poles
, queried the custom fields in the collection, and returned the lowest ground clearance.
Querying the item or collection
If you define a collection of type List<Span>
, you have the option to either access the collection, with any custom fields you have added onto the collection, or directly access the span schema itself.
For example, you may define a custom field called u_long_spans
on a pole report with the formula filter(spans, spans[].length > 50m)
, which will have the type List<Spans>
.
You can directly access the
spans
schema viau_long_spans[]
, e.g. to retrieve the length of the spans using the formulau_long_spans[].length
. Note the use ofu_long_spans
instead ofc_long_spans
.It is possible to return the exact same list of lengths via the collection using this formula:
c_long_spans[].long_spans_item[].length
.The collection is not the item itself. For the example above, any fields made in the
c_long_spans
report will not exist on the spans object, they will only exist in the collection.
You may decide to define fields on the collection as opposed to the span object itself for a number of reasons:
Your final report of interest may be the report of the collection, not a span report, in which case defining the formulas for the columns of your report on the collection makes more sense than referencing them via the collection's
_item
object.The fields you are defining are not universally applicable to all spans, but are instead only related to the filtered spans that exist in this collection. In this case, defining fields on the collection will prevent them from "polluting" the span object with largely unnecessary custom fields.
Functions
The following functions can be used to create or manipulate lists and collections in Neara.
range
range(n)
or
range(start, end, [step])
Generates a list of whole numbers [0, n) if a single argument is provided; or generates a list of whole numbers ["start", "end") if two arguments are provided.
Optionally, step size can be defined as a third argument.
End must be greater than start, step size must be positive.
Generates up to 1000 items.
range(8)
returns[0, 1, 2, 3, 4, 5, 6, 7, 8]
range(3, 8)
returns[3, 4, 5, 6, 7]
range(3, 8, 2)
returns[3, 5, 7]
map
map(key, value, [key, value, ...])
Builds a map object, which can then be passed to functions like map_lookup()
.
Takes an even number of arguments, in each pair there is a key and a value. The keys must all be of a compatible type with each other, and likewise the values with each other.
map("a", 5, "b", 6)
returnstwo key-value pairs {a: 5, b: 6}
map(5, "a", 6, "b")
returnstwo key-value pairs {5: a, 6: b}
map_lookup
map_lookup(map, key, [default])
Looks up a value from a map object. If the key is not in the map, returns the given default value if specified or null if not specified. The key must match the type of the keys in the map.
map_lookup(map("a", 5, "b", 6), "a")
produces5
map_lookup(map("a", 5, "b", 6), "z", 7)
produces7
map_keys
map_keys(map)
Returns all keys for the given map.
map_keys(map("a", 5, "b", 6))
returns the list of keys[a, b]
map_values
map_values(map)
Returns all values for the given map.
map_values(map("a", 5, "b", 6))
returns the list of values [5, 6]
list
list([a, b, ...])
Creates a list of values. The values must all be of the same type.
list("a", "b", "c", "d")
returns["a", "b", "c", "d"]
list(1, 2, 3, 4)
returns[1, 2, 3, 4]
tuple
tuple([a, b, ...])
Creates a tuple/record of values. The values may be of different types.
Unlike lists, tuples have a type that encodes the information of each field.
Therefore, tuples are compatible with each other if they have correspondingly typed elements.
Lists, on the other hand, are compatible regardless of their length, as long as all their elements are the same type.
len
len(str|list)
Returns the length of a string or list of values.
len("string")
returns6
len(list(1, 2, 3, 4, 5)
returns5
index
index(list, index)
Returns the nth item from the start of the list or null if indexing past the list.
index(list, 0)
returns the first item in the listindex(list, len(list) - 1)
returns the last item in the list
index_of
index_of(list, object)
Returns the index of the first matching object in the list or -1 if not found
index_of(list(4,1,3), 1)
returns1
index_of(list(4,1,3), 4)
returns0
index_of(list(1,4,4), 4)
returns1
index_of(list("a", "b", "c"), "d")
returns-1
max_by
max_by(list, comparison_value_list)
Returns the item from the list that corresponds with the maximum value in the comparison value list.
max_by(model().Poles[].label, model().Poles[].lean_angle)
returns the label of the pole with the greatest lean angle
min_by
min_by(list, comparison_value_list)
Returns the item from the list that corresponds with the minimum value in the comparison value list.
min_by(model().Spans[].label, model().Spans[].ground_clearance)
returns the label of the span with the lowest ground clearance.
flatten
flatten(list_of_lists)
Flattens a list of lists.
flatten(list(1, 2), list(3, 4))
returns[1, 2, 3, 4]
filter
filter(list, match)
Filters a list. If the list contains structured objects, the match should be a map of field->value
.
filter(model().Poles, model().Poles[].type = "wood")
returns a list of pole objects of type “wood”filter(model().Poles[].label, model().Poles[].lean_angle > unit_value(5,"degrees"))
returns a list of pole labels with a lean angle greater than 5 degreesfilter(model().Spans, model().Spans[].ground_clearance < 3m)
returns a list of all span objects that have a ground clearance of less than 3m
find
find(list, match)
Finds a single item in a list.
Equivalent to index(filter(list, match), 0)
.
find(model().Poles, model().Poles[].label = "Pole 1234")
returns the first pole object that has the label “Pole 1234”
group_by
group_by(list, group_value)
Returns a Map(group, items) with one entry per unique group.
unique
unique(list)
Removes repeated items from the list.
unique(list(1, 2, 3, 1, 3)
returns[1, 2, 3]
sort
sort(list)
Returns a list with the items sorted
sort(list(2, 1, 3, -5))
returns a numerically ascending list[-5, 1, 2, 3]
sort(list("b", "c", "a", "d")
returns an alphabetically ascending list["a", "b", "c", "d"]
sort_by
sort_by(list, comparable_values, [order: "asc" | "desc", nulls_first: true])
Returns a list with the items sorted by [comparable_values]
.
If [nulls_first]
is true
, then null values will appear at the start of the list, otherwise they will appear at the end.
If [order]
is "asc"
, then non-null values are sorted in ascending order, or "desc"
for descending order.
Note that [nulls_first]
will always be respected regardless of the value of [order]
.
sort_by(model().Poles[].label, model().Poles[].lean_angle, order: "desc")
returns a list of pole labels ordered from largest lean angle to smallest lean angle
intersect
intersect(list1, list2)
Returns a new list consisting of elements contained in both list1
and list2
.
intersect(list(1, 2, 3, 4), list(3, 4, 5, 6))
returns the list[3, 4]
exclude
exclude(list1, list2)
Returns a new list consisting of all elements in list1
that are not in list2
.
exclude(list(1, 2, 3, 4), list(3, 4, 5, 6))
returns the list[1, 2]
symmetric_exclude
symmetric_exclude(list1, list2)
Returns a new list consisting of all elements that are only available in either list1 or list2
, but not both.
Equivalent to exclude(flatten(list1, list2), intersect(list1, list2))
.
symmetric_exclude(list(1, 2, 3, 4), list(3, 4, 5, 6))
returns thelist [1, 2, 5, 6]
make_props
make_props(key, value, [key, value, ...])
Builds a key-value properties object, which can then be passed to functions like get_prop
. Unlike a map, the keys of a properties object must be text, but the values can be of different types. This makes a properties objects similar to a JSON object.
make_props("material", "wood", "phases", 3)
returns two key-value pairs{"material":"wood","phases":3}
get_prop
get_prop(properties, field, type)
Retrieves a field from the given properties as the specified type. Field must be text, or a list of text to access nested properties.
get_prop(gis_properties, "feeder", "text")
get_prop(gis_properties, "in_scope", "boolean")
get_prop(gis_properties, "num_phases", "integer")
get_prop(gis_properties, "safety_factor", "real")
get_prop(gis_properties, "length", "m")
Type may be one of text, boolean, integer, real.