Hexagon Geospatial
MENU

Shared Samples

M.App Portfolio provides a modern, cloud-based platform for creating and delivering diverse geospatial web applications.
Through the M.App Studio, our partners can design, build, and deploy their own Hexagon Smart M.Apps.
Showing results for 
Search instead for 
Do you mean 

Dynamically switching between datasets and data models in same BI application.

by Technical Evangelist ‎09-27-2016 06:08 AM - edited ‎09-27-2016 06:39 AM (1,089 Views)

Introduction

 

Following article describes code snippet for switching between two datasets and data models to show different BI contexts in the same application. It uses data: geometry and attributes from WFS service connected by code.

Link to BitBucket repository: Code snippet

 

Description

 

1. For working properly snippet, create new BI application which contains only map of Poland in customization.

 

M.App Studio » Edit M.App.png

 

 

M.App Studio » Edit M.App1.png

 

 

2. Just paste following code snippet to JS panel:

 

M.App Studio » Edit M.App2.png

 

 

 

//# sourceURL=customization.js;

/**
* Generate 2 pairs of WFS requests:
* A) Without any geometry or attribute filters that return data for whole Poland from 2000 to 2014
* B) With geometry and attribute filters narrowing data to eastern Poland from 2005 to 2010
* Note: this is completely irrevelant for stage switching implementation
*/

var baseUrl = "https://dev-test-mapp.hexagongeospatial.com/gsdemo/psz_wojewodztwa_wfs/wfservice.aspx",
    bboxFilterParts = [
        '<fes:BBOX>',
        '<Envelope srsName="EPSG:4326">',
        '<lowerCorner>48.40 20.49</lowerCorner>',
        '<upperCorner>54.82 24.79</upperCorner>',
        '</Envelope>',
        '</fes:BBOX>'
    ],
    filterBeginParts = [
        '<fes:Filter xmlns:fes="http://www.opengis.net/ogc" xmlns="http://www.opengis.net/gml">'
    ],
    filterEndParts = [
        '</fes:Filter>'
    ],
    andBeginParts = [
        '<fes:And>'
    ],
    andEndParts = [
        '</fes:And>'
    ],
    geometryRequest = "?request=getfeature&service=wfs&version=2.0.0&typenames=wojewodztwaFinal&propertyname=geometry,id_woj&outputFormat=application/vnd.geo%2Bjson&filter=",
    attributesRequest = "?request=getfeature&service=wfs&version=2.0.0&typenames=wojewodztwaFinal&outputFormat=text/csv&filter=",
    // filters
    geometryFilterParts = filterBeginParts.concat(andBeginParts).concat(bboxFilterParts).concat([
        '<fes:PropertyIsEqualTo>',
        '<fes:PropertyName>',
        'YEAR',
        '</fes:PropertyName>',
        '<fes:Literal>',
        '2010',
        '</fes:Literal>',
        '</fes:PropertyIsEqualTo>'
    ]).concat(andEndParts).concat(filterEndParts),
    geometryFilter = encodeURI(geometryFilterParts.join("")),
    attributesFilterParts = filterBeginParts.concat(andBeginParts).concat(bboxFilterParts).concat([
        '<fes:PropertyIsBetween>',
            '<fes:PropertyName>',
                'YEAR',
            '</fes:PropertyName>',
            '<fes:LowerBoundary>',
                '<fes:Literal>',
                    '2005',
                '</fes:Literal>',
            '</fes:LowerBoundary>',
            '<fes:UpperBoundary>',
                '<fes:Literal>',
                    '2010',
                '</fes:Literal>',
            '</fes:UpperBoundary>',
        '</fes:PropertyIsBetween>'
    ]).concat(andEndParts).concat(filterEndParts),
    attributesFilter = encodeURI(attributesFilterParts.join("")),
    // data URLs without filters
    geometryUrl = baseUrl + geometryRequest,
    attributesUrl = baseUrl + attributesRequest,
    // data URLs with filters 
    geometryUrlFilt = baseUrl + geometryRequest + geometryFilter,
    attributesUrlFilt = baseUrl + attributesRequest + attributesFilter;

// beign global state variables
var currentModel, currentLayer, currentId, currentWidgetIds;
// end global state variables

var s = gsp.bi.stage;

/**
* First configuration uses:
* Stage Model:
*   - Fields - fields with id's and NUTS1 names for each region
*   - Features - layer "labour_geom"
*   - Values - Count, Avarage Wage and Year for each region
* Table Source:
*   - Table with data from url defined above
* Chart Descriptors:
*   - Chart model which defines chart showing avarage gross salary by NUTS regions
* Choropleth Model:
*   - Properties of the choropleth widget
* Legend Model:
*   - Properties of geometry layer
*/
var noFilterConfig = {
    stageModel: {
        "id": "stageWithoutFilter",
        "fields": [{
            "id": "ac2016e27b3e84215bd114745d17c2ad6",
            "name": "id_woj",
            "value": "id_woj"
        }, {
            "id": "a694945b7789743798d3b27acae05de94",
            "name": "NUTS1",
            "value": "NUTS1"
        }],
        "totals": [],
        "features": [{
            "id": "labour_geom",
            "key": "id_woj",
            "name": "Adminstrative Zones"
        }],
        "values": [{
            "id": "vCount",
            "name": "Count",
            "value": "$count"
        }, {
            "id": "vAverageWage",
            "name": "Average_wage",
            "value": "$sum(WAGES_TO_1)"
        }, {
            "id": "vYear",
            "name": "year_m",
            "value": "YEAR"
        }]
    },
    tableSource: attributesUrl,
    chartDescriptors: [{
        chartM: {
            "id": "ab993d83ce0634681acb1f5984070df98",
            "name": "AVERAGE GROSS SALARY BY NUTS1 REGION IN CURRENT ZŁ",
            "title": "AVERAGE GROSS SALARY BY NUTS1 REGION IN CURRENT ZŁ",
            "placement": "left",
            "key": ["a694945b7789743798d3b27acae05de94"],
            "values": ["vAverageWage"],
            "tooltips": "{vAverageWage}",
            "colors": ["#ffbf00", "#ffbf00", "#ffbf00", "#ffbf00", "#ffbf00"],
            "axis": {
                "x": {
                    "ticks": 2,
                    "rotation": "",
                    "units": 70
                },
                "y": {
                    "ticks": 2,
                    "units": 70
                }
            },
            "chart": "bar",
            "margins": {
                "top": 50,
                "right": 30,
                "bottom": 50,
                "left": 60
            }
        }
    }],
    choroplethModel: {
        chart: "choropleth",
        name: "Average Wage",
        target: "map",
        layer: "labour_geom",
        key: "id_woj",
        values: "vCount",
        id: "choropleth_id"
    },
    legendModel: {
        definitionName: "MAppPlatformGeoJson",
        url: geometryUrl,
        name: "Average Wage",
        id: "labour_geom",
        bbox: [0, 0, 0, 0],
        bboxCrs: "EPSG:4326",
        supportedCrses: ["EPSG:4326", "EPSG:3857"],
        style: {
            display: "thematicLayer"
        }
    }
};

/**
* Second configuration uses:
* Stage Model:
*   - Fields - fields with id's and NUTS1 names for each region as stage model above
*   - Features - layer "labour_geom" as stage model above
*   - Values - Count, sum of male population employment and sum of female population employment
* Table Source:
*   - Table with data from url defined above - with filter
* Chart Descriptors:
*   - Chart model which defines chart showing employment population by sex and NUTS region
* Choropleth Model:
*   - Properties of the choropleth widget - with filter
* Legend Model:
*   - Properties of geometry layer - with filter
*/
var withFilterConfig = {
    stageModel: {
        "id": "stageModelWithFilter",
        "fields": [{
            "id": "ac2016e27b3e84215bd114745d17c2ad6",
            "name": "id_woj",
            "value": "id_woj"
        }, {
            "id": "a694945b7789743798d3b27acae05de94",
            "name": "NUTS1",
            "value": "NUTS1"
        }],
        "totals": [],
        "features": [{
            "id": "labour_geom",
            "key": "id_woj",
            "name": "Adminstrative Zones"
        }],
        "colors": {
            "paletteYellow": {
                "range": ["#fff0b3", "#ffeb99", "#ffe066", "#ffd633", "#ffcc00", "#cca300", "#997a00", "#665200", "#4d3d00", "#332900"],
                "domain": ["2500", "5000"],
                "type": "quantize"
            }
        },
        "values": [{
            "id": "vCount",
            "name": "Count",
            "value": "$count"
        }, {
            "id": "vPopulationMaleEmployed",
            "name": "POPULATION_M_EMPLOYED",
            "value": "$sum(POPULATI_3)"
        }, {
            "id": "vPopulationFemaleEmployed",
            "name": "POPULATION_F_EMPLOYED",
            "value": "$sum(POPULATI_4)"
        }]
    },
    tableSource: attributesUrlFilt,
    chartDescriptors: [{
        chartM: {
            "id": "ac303bbaf331945c98950a6219e07d883",
            "name": "EMPLOYED POPULATION BY SEX AND NUTS1 REGION",
            "title": "EMPLOYED POPULATION BY SEX AND NUTS1 REGION",
            "placement": "left",
            "key": ["a694945b7789743798d3b27acae05de94"],
            "values": ["vPopulationMaleEmployed", "vPopulationFemaleEmployed"],
            "tooltips": "{vPopulationMaleEmployed} and {vPopulationFemaleEmployed}",
            "colors": ["#2f69f6", "#ffaea1"],
            "axis": {
                "x": {
                    "ticks": 2,
                    "rotation": "",
                    "units": 70
                },
                "y": {
                    "ticks": 2,
                    "units": 70
                }
            },
            "chart": "bar",
            "margins": {
                "top": 50,
                "right": 30,
                "bottom": 50,
                "left": 60
            }
        }
    }],
    choroplethModel: {
        chart: "choropleth",
        name: "Average Wage",
        target: "map",
        layer: "labour_geom",
        key: "id_woj",
        values: "vCount",
        id: "choropleth_filter_id"
    },
    legendModel: {
        definitionName: "MAppPlatformGeoJson",
        url: geometryUrlFilt,
        name: "Average Wage",
        id: "labour_geom",
        bbox: [0, 0, 0, 0],
        bboxCrs: "EPSG:4326",
        supportedCrses: ["EPSG:4326", "EPSG:3857"],
        style: {
            display: "thematicLayer"
        }
    }
};

//Error handler
function onError(e) {
    console.log("Something went wrong");
    console.log(e);
}

//Function which registers stageModel with passed config, and displays it to map
function display (config) {
    //config definition
    var stageModel = config.stageModel,
        tableSource = config.tableSource,
        chartDescriptors = config.chartDescriptors,
        choroplethModel = config.choroplethModel,
        legendModel = config.legendModel;

    // store widget ids to remove them later by id
    currentWidgetIds = chartDescriptors.map(function(obj){
        return obj.chartM.id;
    });
    s.registerStageModel({
        stageModel: stageModel
    }, function() {
        s.registerDataTable({
            tableSource: tableSource
        }, function() {
            s.addWidgets({
                descriptors: chartDescriptors
            }, function() {
                s.registerChoropleth({
                    model: choroplethModel,
                    mapTitleEnabled: true,
                    mapLegendEnabled: true,
                    mapLegendPlacement: "map"
                }, function(layerName) {
                    // begin workaround
                    // legend.add adds properties to the 
                    // legendModel and it would not be usable later
                    var legendModelClone = JSON.parse(JSON.stringify(legendModel));
                    // end workaround
                    gsp.legend.add(legendModelClone, function() {
                        console.log("Registered layer");
                        currentModel = choroplethModel;
                        currentLayer = legendModel;
                    }, onError);
                }, onError);
            });
        }, onError);
        currentId = stageModel.id;
    }, onError);
}


// Function which removes registered choropleth and resets stage
function cleanup(callback, errback) {
    //Remove choropleth
    s.unregisterChoropleth(currentModel, function() {
        //Remove Widgets
        s.removeWidgets({
            widgetIds: currentWidgetIds
        }, gsp.legend.find({
            name: currentLayer.name
        }, function(ret) {
            if (!ret.legendItems || !ret.legendItems[0]) {
                if (typeof errback === "function")
                    errback({
                        message:"No legend item with name: " + currentLayer.name
                    });
                return;
            }
            ret.legendItems[0].remove(function () {
                if (typeof callback === "function") {
                    resetStage();
                    callback();
                }
            });
        }, function (){
            if (typeof callback === "function") {
                resetStage();
                callback();
            }
        }));
    });
}

// Begin hack
// Reseting stage using gvc library
function resetStage() {
    s.requireLibraries(function(gvc) {
        var stages = mainContext.businessIntelligence.stageManager.__stages,
            index = 0;
        stageId = stages[index].id();
        stages[index] = gvc.dataStage(stageId);
    });    
}
// end hack

//Function which switch stages by their ID's
function changeStage() {
    cleanup(function () {
        var config;
        switch (currentId) {
            case "stageModelWithFilter":
                config = noFilterConfig;
                break;
            case "stageWithoutFilter":
                config = withFilterConfig;
                break;
            default:
                alert("Unknown stage ID. Using config with filter.");
                config = withFilterConfig;
        }
        display(config);
    }, onError);
}

//Adding button to toolbar which will trigger stage change.
gsp.ui.toolbar.add({
    id: "tool-ne",
    title: "Change config"
}, function(ret) {
    ret.div.onclick = function() {
        console.log("Current stage ID: " + currentId);
        changeStage();
    };
});

//Triggering stage model which will be displayed as first, when app is opened
display(noFilterConfig);

 

 

Following code snippet contains config for 2 stages. Each config consists of:

  • Stage Model config - Object with defined fields, features and values.
  • Table source - in this case, table source is url.
  • Chart descriptors - Object containing chart model with properties for chart.
  • Choropleth Model - Object with properties of the choropleth widget.
  • Legend Model - Object containing properties of geometry layer.

And few functions:

  • function display(config){} - Function which registers stageModel with configuration passed to config parameter.
  • function cleanup(callback, errback) {} - Function which removes registered choropleth and triggers another function:
  • function reseStage(){} - Function which resets Stage using GVC library.
  • function changeStage(){} - Function which switch between current Stage ID's and triggers display(config) function.

At the end of code snippet, button is added. Following button triggers the changeStage function. 

 

3. Save and open the application.

 

Button showed on screen below, triggers change of stage. Following stage is without any filter and contain full set of attributes:

 

CleaningStageM.png

 

After click, stage is changed. Data with filters is displayed and also, chart is changed:

 

CleaningStageM1.png

 

For debugging purposes, customization can be seen in developer tools sources:

DevToolsS.png