Editing a Map Filter

There is a special type of filters that are used by the Map widget to display markers according to specific criteria (e.g. connection or presence of failures and anomalies).

In the component manager, a map filter is defined like any other filter, but it must use the MAP_MARKER as output type.
In this way, when you are configuring the Map widget, the Template Editor can load the right filters in the property panel.

Here is an example of map filter coloring markers according to the operational status (e.g. standby, working) of the thing.

The filter uses the value of a thing property (e.g status) to display markers according to its value.

exports.operationalStatusMapFilter = class {
    
    constructor(config) {
        this.config = config || {};
        // sets the label displayed in the map filter selection.
        this.config.label = this.config.label || "Operational Status";
    }

    // optional method to perform asynchronous stuf (e.g. API invocation).
    onInit(){
        let _this = this;
        return new Promise(function(resolve, reject) {
            
            // registers rank descriptors 
            _this.rankDescriptors = [
                 { 'rank': 1, 'label': 'Standby', 'color': '#657895', 'clusterClass': 'cluster-normal', 'markerClass': 'marker-normal'},
                 { 'rank': 2, 'label': 'Working', 'color': '#28A745', 'clusterClass': 'cluster-online', 'markerClass': 'marker-online'},
                 { 'rank': 3, 'label': 'Failure', 'color': '#FB4E3D', 'clusterClass': 'cluster-failure', 'markerClass': 'marker-failure'}
            ]; 
             
            // registers icons 
            const MARKER_SVG = '<svg width="26" height="40" viewBox="0 0 26 40" xmlns="http://www.w3.org/2000/svg"><path d="M1 13C1.00036 19.9142 8.08249 32.7303 11.3652 38.2994C12.1115 39.5656 13.8885 39.5656 14.6348 38.2994C17.9175 32.7303 24.9995 19.9142 24.9996 13C24.9996 6.37258 19.6274 1 13 1C6.37258 1 0.999654 6.37258 1 13Z" /></svg>';
            _this.icons = {};
            _this.rankDescriptors.forEach((rd, i) => {
                
                _this.icons[rd.rank] = L.divIcon({
                        html: MARKER_SVG,
                        className: rd.markerClass,
                        iconSize: [26, 40],
                        iconAnchor: [13, 40],
                });
                    
                _this.icons[rd.rank + "-selected"] = L.divIcon({
                        html: MARKER_SVG,
                        className: rd.markerClass + "-selected",
                        iconSize: [26, 40],
                        iconAnchor: [13, 40],
                });    
               
            });
          
            resolve();
        });
    }


    // returns the current config object.
    getConfig() {
        return this.config;
    }
    
    // sets whether the map is loading THING (default) or LOCATION elements. 
    setContext(context) {
        this.context = context;
    }
    
    // computes and assign the rank of each loaded element (thing or location). 
    computeRank(elements, data) {
        elements.forEach(el => {
            switch (el.properties.status) {
                case "STANDBY":
                    el.rank = 1;
                    break;
                case "WORKING":
                    el.rank = 2;
                    break;
                default:
                    el.rank = 3
            }
        });
    }
    
    // updates the marker icon according to the selection status.
    updateMarker(element, marker) {
        if (element.selected) {
            marker.setIcon(this.icons[element.rank + "-selected"]);
        } else {
            marker.setIcon(this.icons[element.rank]);
        }
    }
    
    // returns the data required by this filter.
    getRequiredData() {
        return {
            "things": true,
            "locations": true,
            "alerts": false
        }
    }
    
    // returns the array of rank objects. 
    getRankDescriptors() {
        return this.rankDescriptors;
    }
}

The map filter constructor(config) receives the config JSON object with the parameters defined in the template and the additional filter parameters.
Wirhin the consutrctur, optionally you can define the default label to be used in the map filter selection menu.

In the onInit() method you should initilize the following variables:

  • rankDescriptors: the array of RankDescriptor objects used to configure markers on the map.

  • icons: the map containing all the possibile icons to be displayed according the defined ranks.  

This method returns a Promise, so that asynchronous operations (e.g. additional API request) can be performed.
Remember to resolve the promise when you finished your stuf.

A RankDescriptor is defined by:

  • rank: a number used to group elements. All the element with the same rank are displayed with the same marker.
    The same number must be assoicated to the loaded elements (things or locations) when the computeRank(elements, data) method is invoked.

  • label: the text displayes within the badges over the map.

  • color: the hex color code used to fill the circle in the badges over the map.

  • clusterClass: the class applied to the cluster of markers.

  • clusterSelectedClass: the class applied to the cluster of markers when they are selected.
    If missing , the map uses the clusterClass name suffixed with -selected.

  • markerClass: the class associated to marker icons.
    Used when registering icons in the onInit() method.

The map may load thousands of elements, and in order to maintain optimal performance, it is recommended to create all possible icons once and then assign them to the various markers during visualization updateMarker(element, marker). It is recommended to use SVG type icons, and for more details about icon-based marker configuration please refer to the Leafleet documentation.

The map widget uses the rank assigned to each element computeRank(elements, data) to display the right marker according what defined in the RankDescriptor.
Note that, clusters are displayed by using the highest rank present in the underlying makers.

You can leverage these predefined classes to display clusters and markers:

  • Cluster: cluster-normal, cluster-normal-selected, cluster-anomaly, cluster-anomaly-selected, cluster-failure, cluster-failure-selected, cluster-online, cluster-online-selected, cluster-offline, cluster-offline-selected.

  • Marker: marker-normal, marker-normal-selected, marker-anomaly, marker-anomaly-selected, marker-failure, marker-failure-selected, marker-online, marker-online-selected, marker-offline, marker-offline-selected.

Here is an example of CSS classes (anomaly) you can use to define your own custom style.

.cluster-anomaly {
    text {
        fill: #000000;
        font-weight: 600;
    }

    ellipse {
        fill: #FFA726;
        stroke: #F57C00;
    }
}

.cluster-anomaly-selected {
    text {
        fill: #FFFFFF;
        font-weight: 600;
    }

    ellipse {
        fill: #F57C00;
        stroke: #F57C00;
    }
}

.marker-anomaly path {
    fill: #FFA726;
    stroke: #F57C00;
}

.marker-anomaly-selected path {
    fill: #F57C00;
    stroke: #F57C00;
}