Azure Maps - How to use AND logic in Data Driven Style Expressions - azure-maps

When using the layer.setOptions function, I need to figure out how to combine two data driven style expressions so that both expressions are respected when using the filter property.
Why? In my project I am filtering the visbility of map objects (features likes shapes, points etc) using layer groups whereby each feature when created or updated, I set a property value with the layer group that it belongs to. So later the user can choose whether turn the visibility on or off for groups of map objects, example below:
In addition to filtering the visbility of my map objects by layer group, I also need to apply a filter so each layer type will only render certain geomtry types. The MS docs explanation on this is shown here:
Now when I set the options for any of the layers used, I need to be able to combine the two filters into one argument-->
The first filter argument would filter the layers by layer groups:
var filter = ['in', ['get', 'layerGroup'], ['literal', layerGroupsArray]]
The second filter would ensure that only certain geomtry types are rendered for that layer:
var filter = ['==', ['geometry-type'], 'LineString']; // other shapes could be 'Polygon' / 'Circle' etc
Note: Using either of the above filters in isolation both work in their own right, but I dont know how to combine both arguments together, its almost like we need a logical AND function, which i tried but the browser debug throws an error:
var combinedFilterExample = ['in', ['get', 'layerGroup'], ['literal', layerGroupsArray]] & ['==', ['geometry-type'], 'LineString'];

Eventually I found a snippet from the MS Docs that explains how to set the expression so it requires all arguments to be respected, they use the 'all' attribute to do this which i beleive achieves a simialr objective to using an AND logic operator.
So from my original question, we can combine two filter expressions into one argument as follows:
var lineLayerFilter = ['all', ['==', ['geometry-type'], 'LineString'], ['in', ['get', 'layerGroup'], ['literal', layerGroupsArray]]]
If you want to combine more expressions in the argument then we simply add more to the argument, and it looks to be we dont need to add any closing sqaure brackets at the end. Further example:
var polygonOuterLayerFilter = ['all', ['==', ['geometry-type'], 'Polygon'], ['==', ['geometry-type'], 'LineString'], ['in', ['get', 'layerGroup'], ['literal', layerGroupsArray]]]
MS Docs reference here: https://learn.microsoft.com/en-us/azure/azure-maps/data-driven-style-expressions-web-sdk#aggregate-expression

Related

Azure Maps - Unable to set layer options 'offset' from point properties without browser warnings

I'm trying to use the offset parameters for the text label in relation to the position of the point (marker) on the map.
In my app, the user sets their preference to the x & y-axis offset values, which when changed, update the map point properties. I then need to use data-driven expressions to pull the values from each map point properties when updating the point layer options.
When the point is first created, the offset property of the point is set as below:
offset: [0, 0],
When updating the point layer, I've tried using the expression formulas below, but none of them are working without getting warnings in the browser debug console.
layers.pointLayer.setOptions({
iconOptions: {
offset: [ // ******NEEDS FIXING*******
'case', // Use a conditional case expression.
['has', 'offset'], // Check to see if feature has an "offset" property
//["length", ["array", ["get", "offset"]]] // not working
['get', 'offset'], // not working without browser warning
//['get', ['literal', [0, 0]]], // not working
//['literal', [0,0]], // working but not relevant, set locally, not pulling value from properties!!!
'literal', [0, 0]] // If it doesn't, default to array [0,0] (x & y-axis).
]
}
})
If using the example ['get', 'offset'], in the expression, although I can actually modify the offset and it works on the map as shown in the screenshot, I get the following warning in the browser debug console:
I'd like to have a warning free environment as any debug warnings. I obviously need to get the formatting set correctly in the data driven expression when setting the options for the layer (2nd code sample) but none of the syntax I've tried so far are working correctly.
I also tried studying the MS example here, but it seems they don't actually pull the offset values from the map point properties, they are setting the layer options directly from the user form which is no good unless i wanted to implement a global change for all points that belong to this particular layer.

How do deal with nested Arrays/objects in BehaviorSubjects, Observables?

I generally have problems using rxjs with nested Objects or Arrays.
My current use-case is this:
{a: [
{b: 0, c:[{d:1}]},
{b: 1, e:[{f: 'someString'}]}
]
Task: Get and set the Observable or value of a,b,c,d,e,f. I also want to be able to subscribe to each property.
I had this Problem in a similar use-case with an Array of BehaviorSubjects:
Efficiently get Observable of an array BehaviorSubjects
I generally have problems to use the basic functionality of nested arrays/objects in rxjs.
The basic functionality I mean includes:
Array:
getting Element by Index
using for of/in on Arrays
setting an Element by Index
push, pop, shift, slice, splice, ...
Object:
getting Value by Property name
going into the nested tree: object.key1.key2.key3[3].key4 ...
setting Value by Property name
assign
for of/in loops
Generally:
Destructuring: e.g.: let [variable1, variable2] = someObject;
Maybe other stuff I forgot.
I dont know if and which functions are possible for which rxjs Objects and which make sense (for example you should be able to set values in an Observable directly). But coming from a background without rxjs, I have trouble to manage my rxjs Objects properly.
I think reason for this besides my lack of knowledge and understanding is, that
a. The rxjs Objects don't provide the functionality as I'm used to from normal arrays and objects. e.g.:
let variable1 = array[1].property;
//becomes this (see related stack-Question I mentioned earlier)
let variable2 = array.pipe(mergeMap(d=> d[index].pipe(map(d1 => d1[property]));
// -> what happens here? You first need to know what mergeMap,
// map is doing and you have 5 levels of nested inline functions.
b. To implement the those mentioned functionalities I need to go over the .pipe() function and use some function like mergeMap, map, pluck, ... Functions that aren't directly indicating that you can get the Observable of let's say 'e' in my example. Making something like object.a[1].e wierd to implement (at least I don't know how to do that yet)
EDIT:
I also want to note, that I still love the idea of rxjs which works well in angular. I just have problems using it to it's full extend, as I'm a bit new to angular and consequently rxjs.
I thin RX is mainly focus on dealing with async operations. Mutation of array and object we can perfectly use the methods comes natively with javascript if theres no existing operators. or you can create your own operator for mutation/iteration etc.
Will try to answer some of your question on array/objects mutation, they are actually very straight forward.
Array:
getting Element by Index
map(arr=>arr[index])
using for of/in on Arrays
map(arr=>arry.map(item=>....))
setting an Element by Index
tap(arr=>arr[index]=somevalue)
Object:
getting Value by Property name
pluck('name')
going into the nested tree: object.key1.key2.key3[3].key4 ...
pluck('key1','key2')
setting Value by Property name
map(obj=>({a:value,obj...}))
assign
lets say your really want some pick array index method as rxjs operator you can create something like, same as for..in operations.
const pluckIndex=(index)=>source=>source.pipe(map(arr=>arr[index]))
const source = of([2,3])
source.pipe(pluckIndex(1)).subscribe(x => console.log(x));

sortBy in React?

I fetch the data from a server and some items have a specific attribute others don't. I need to sort data according to this specific attribute and I am using sortBy package but of course it doesn't work properly because when it tries to sort data and doesn't find the attribute, it is broken.
myItems.sort(sortBy('specificAttr'))
Basically, what I did (think of inside of a loop):
if(!myItems.specificAttr) {myItems.speficificAttr = 0);
I know it doesn't make sense at all, but I don't know what I can do.
Do you have any advice with code examples?
Using lodash's sortBy
Lodash handles this case out of the box. If it can't find the attribute, it gets pushed to the end of the sorted array.
var users = [
  { 'age': 48 },
  { 'user': 'barney', 'age': 36 },
  { 'user': 'fred',   'age': 40 },
  { 'user': 'barney', 'age': 34 }
];
 
_.sortBy(users, [(o) => { return o.user; }]);
// => objects for barney, barney, fred, and no-name in that order
_.sortBy(users, ['user']);
// => objects for barney, barney, fred, and no-name in that order
If you're using some other library's sortBy, check the documentation to see if there's an optional second argument that would allow you to customize the attribute getter function.
Rolling your own
If you're implementing your own sorting algorithm, simply do a check like if myItem.specificAttr to check if the attribute exists while you're doing the actual sorting (rather than prior sorting, as you described in your example).

URL Type Hierarchy in Adobe DTM

I'm trying to create a page type hierarchy where I can use it both a page hierarchy as well as props and evars, using the page URL. In a nutshell my URL would look something like this:
http://www.domain.com/BrandHomePage/SuperCategory/ProductCategory/Product
The mindset is to take the URL and use a data element to split the URL, and then capture the values into separate data elements that could also be used in a page hierarchy.
var url = "http://www.domain.com/part1/part2/part3/part4"
var parts = url.split('/').splice(2);
console.log(parts);
var baseUrl = parts[0];
var part1 = parts[1];
var part2 = parts[2];
var part3 = parts[3];
var part4 = parts[4]
My question is, would it even be possible to capture each individual portion of the URL into separate data elements? Or is my approach overkill.
Create a Data Element
The following will create a Data Element that returns an array containing up to 4 elements, depending on how many dir levels there are in the URL.
Go to Rules > Data Elements > Create New Data Element
Name it "hier1" (no quotes).
Choose Type Custom Script and click Open Editor.
Add the following code to the code box:
return location.pathname.split('/').filter(Boolean).slice(0,4);
When you are done, Save Changes.
Populate the Hierarchy Variable
Here is an example of populating hier1 on page view.
Go to Overview > Adobe Analytics Tool Config > Pageviews & Content
Under Hierarchy, select Hierarchy1 from the dropdown (this is shown by default).
To the right of the dropdown, in the first field, add %hier1%
Leave the other 3 fields blank.
Leave Delimiter as default comma , (it doesn't matter what you put here).
Note: DTM stringifies the returned array (String(Array) or Array.toString()) from the Data Element, which is effectively the same as doing Array.join(','). This is why the above shows to only put the Data Element reference in the first field, and the Delimiter is ignored.
If your implementation uses a delimiter other than a comma, see additional notes below.
Additional Notes
Populating other Variables
You can also reference %hier1% to populate other variable fields in the Global Variables section. Note that the data element will be stringified with default comma delimiter.
Alternatively, you may consider using Dynamic Variable syntax (e.g. D=h1) as the value, to shorten the request URL. If you are using the latest AppMeasurement and Marketing Cloud Service libraries, this isn't a big deal (the libs will automatically use a POST request instead of GET request if the request URL is too long).
Using the Data Element in Custom Code Boxes
You can use _satellite.getVar('hier1') to return the data element. Note that this returns an array, e.g. ['foo','bar'], so you need to use .join() to concatenate to a single delimited string value.
Using a different Delimiter
If your implementation uses a delimiter other than a comma (,) and you use the same alternate delimiter for all your variables, you can update the Data Element as such:
return location.pathname.split('/').filter(Boolean).slice(0,4).join('[d]');
Where [d] is replaced by your delimiter. Note that this will now cause the Data Element to return a single concatenated String value instead of an Array. Using %hier1% syntax in DTM fields remains the same, but you will no longer need to use .join() in Custom Code boxes.
If your implementation uses different delimiters for different variables, implement the Data Element per the original instructions in the first section. You may use %hier1% syntax in DTM fields only if the delimiter is a comma. For all other delimiters, you will need to populate the variable in a custom code box and use a .join('[d]').
Capturing more than Four Directory Levels
Since you are no longer trying to put a value in four hierarchy fields, you may consider pushing more levels to hier1 or other variables.
In the Data Element, change the 4 in .slice(0,4); to whatever max level of dirs you want to capture. Or, if you want to capture all dir levels, remove .slice(0,4) completely.

AngularJS two way binding variables to array items

To view my code follow this link
I've created a directive which handles an array of items (it will always be two because it's a "from" and "to" date pair).
I want to make the array items accessible as separate values for later use so I refer to the array items like this
vm.data = ['data a', 'data b'];
vm.separateData = vm.data[0];
vm.otherData = vm.data[1];
When I implement a two way bind into the directive, the vm.data[0] and vm.data[1] references are updated but the vm.separateData and vm.otherData aren't.
Is there a way of making this work or should I just restructure the rest of my app (where needed) to accomodate for array items?
In my fiddle link (same as above) try changing the text input values and you'll see what I mean.
vm.data[0] is a string and thus it is a primitive datatype in javascript which is immutable. So you bind the immutable String 'Data a' to vm.separateData, which is not a reference to data[0].
If you want to copy the reference to the array into vm.separateData try to wrap your strings in other javascript objects, e.g.
vm.data = [{"value":"Data a"}, {"value":"Data b"}]
and then you can reference
vm.separateData = vm.data[0];
and access the value via
vm.separateData.value

Resources