dhis2-gis
Version:
GIS Web App for DHIS 2
801 lines (667 loc) • 25.7 kB
JavaScript
import isArray from 'd2-utilizr/lib/isArray';
import isObject from 'd2-utilizr/lib/isObject';
import arrayClean from 'd2-utilizr/lib/arrayClean';
import arrayDifference from 'd2-utilizr/lib/arrayDifference';
import arrayFrom from 'd2-utilizr/lib/arrayFrom';
import arraySort from 'd2-utilizr/lib/arraySort';
export default function LayerHandlerThematic(gis, layer) {
var compareView,
loadOrganisationUnits,
loadLegend,
applyClassification,
rgbToHex,
hexToRgb,
getClass,
getColorsByRgbInterpolation,
updateMap,
updateLegend,
loadData,
afterLoad,
contextMenu,
onFeatureClick,
onFeatureRightClick,
loader;
compareView = function (view, doExecute) {
var src = layer.view,
viewIds,
viewDim,
srcIds,
srcDim;
loader.zoomToVisibleExtent = true;
if (!src) {
if (doExecute) {
loadOrganisationUnits(view);
}
return gis.conf.finals.widget.loadtype_organisationunit;
}
// organisation units
viewIds = [];
viewDim = view.rows[0];
srcIds = [];
srcDim = src.rows[0];
if (viewDim.items.length === srcDim.items.length) {
for (var i = 0; i < viewDim.items.length; i++) {
viewIds.push(viewDim.items[i].id);
}
for (var i = 0; i < srcDim.items.length; i++) {
srcIds.push(srcDim.items[i].id);
}
if (arrayDifference(viewIds, srcIds).length !== 0) {
if (doExecute) {
loadOrganisationUnits(view);
}
return gis.conf.finals.widget.loadtype_organisationunit;
}
}
else {
if (doExecute) {
loadOrganisationUnits(view);
}
return gis.conf.finals.widget.loadtype_organisationunit;
}
// data
loader.zoomToVisibleExtent = false;
viewIds = [];
viewDim = view.columns[0];
srcIds = [];
srcDim = src.columns[0];
if (viewDim.items.length === srcDim.items.length) {
for (var i = 0; i < viewDim.items.length; i++) {
viewIds.push(viewDim.items[i].id);
}
for (var i = 0; i < srcDim.items.length; i++) {
srcIds.push(srcDim.items[i].id);
}
if (arrayDifference(viewIds, srcIds).length !== 0) {
if (doExecute) {
loadData(view);
}
return gis.conf.finals.widget.loadtype_organisationunit;
}
}
else {
if (doExecute) {
loadData(view);
}
return gis.conf.finals.widget.loadtype_organisationunit;
}
// period
viewIds = [];
viewDim = view.filters[0];
srcIds = [];
srcDim = src.filters[0];
if (viewDim.items.length === srcDim.items.length) {
for (var i = 0; i < viewDim.items.length; i++) {
viewIds.push(viewDim.items[i].id);
}
for (var i = 0; i < srcDim.items.length; i++) {
srcIds.push(srcDim.items[i].id);
}
if (arrayDifference(viewIds, srcIds).length !== 0) {
if (doExecute) {
loadData(view);
}
return gis.conf.finals.widget.loadtype_organisationunit;
}
}
else {
if (doExecute) {
loadData(view);
}
return gis.conf.finals.widget.loadtype_organisationunit;
}
// if no changes - reload legend but do not zoom
if (doExecute) {
loader.zoomToVisibleExtent = false;
loadLegend(view);
return gis.conf.finals.widget.loadtype_legend;
}
if (gis.mask) {
gis.mask.hide();
}
};
loadOrganisationUnits = function (view) {
var items = view.rows[0].items,
propertyMap = {
'name': 'name',
'displayName': 'name',
'shortName': 'shortName',
'displayShortName': 'shortName'
},
keyAnalysisDisplayProperty = gis.init.userAccount.settings.keyAnalysisDisplayProperty,
displayProperty = propertyMap[keyAnalysisDisplayProperty] || propertyMap[view.displayProperty] || 'name',
url = function () {
var params = '?ou=ou:';
for (var i = 0; i < items.length; i++) {
params += items[i].id;
params += i !== items.length - 1 ? ';' : '';
}
params += '&displayProperty=' + displayProperty.toUpperCase();
if (isArray(view.userOrgUnit) && view.userOrgUnit.length) {
params += '&userOrgUnit=';
for (var i = 0; i < view.userOrgUnit.length; i++) {
params += view.userOrgUnit[i] + (i < view.userOrgUnit.length - 1 ? ';' : '');
}
}
return gis.init.contextPath + '/api/geoFeatures.json' + params;
}(),
success,
failure;
success = function (r) {
var features = gis.util.geojson.decode(r, 'ASC');
if (!features.length) {
if (gis.mask) {
gis.mask.hide();
}
gis.alert(GIS.i18n.no_valid_coordinates_found);
return;
}
layer.featureStore.loadFeatures(features.slice(0));
layer.features = features;
loadData(view, features);
};
failure = function () {
if (gis.mask) {
gis.mask.hide();
}
gis.alert(GIS.i18n.coordinates_could_not_be_loaded);
};
Ext.Ajax.request({
url: encodeURI(url),
disableCaching: false,
success: function (r) {
success(JSON.parse(r.responseText));
},
failure: function () {
failure();
}
});
};
loadData = function (view, features) {
var success;
view = view || layer.view;
features = features || layer.featureStore.features;
var dimConf = gis.conf.finals.dimension,
paramString = '?',
dxItems = view.columns[0].items,
isOperand = view.columns[0].dimension === dimConf.operand.objectName,
peItems = view.filters[0].items,
ouItems = view.rows[0].items,
propertyMap = {
'name': 'name',
'displayName': 'name',
'shortName': 'shortName',
'displayShortName': 'shortName'
},
keyAnalysisDisplayProperty = gis.init.userAccount.settings.keyAnalysisDisplayProperty,
displayProperty = propertyMap[keyAnalysisDisplayProperty] || propertyMap[view.displayProperty] || 'name';
// ou
paramString += 'dimension=ou:';
for (var i = 0; i < ouItems.length; i++) {
paramString += ouItems[i].id;
paramString += i < ouItems.length - 1 ? ';' : '';
}
// dx
paramString += '&dimension=dx:';
for (var i = 0; i < dxItems.length; i++) {
paramString += isOperand ? dxItems[i].id.split('.')[0] : dxItems[i].id;
paramString += i < dxItems.length - 1 ? ';' : '';
}
if (view.valueType === 'ds') {
paramString += '.REPORTING_RATE';
}
paramString += isOperand ? '&dimension=co' : '';
// pe
paramString += '&filter=pe:';
for (var i = 0; i < peItems.length; i++) {
paramString += peItems[i].id;
paramString += i < peItems.length - 1 ? ';' : '';
}
// display property
paramString += '&displayProperty=' + displayProperty.toUpperCase();
if (isArray(view.userOrgUnit) && view.userOrgUnit.length) {
paramString += '&userOrgUnit=';
for (var i = 0; i < view.userOrgUnit.length; i++) {
paramString += view.userOrgUnit[i] + (i < view.userOrgUnit.length - 1 ? ';' : '');
}
}
// relative period date
if (view.relativePeriodDate) {
paramString += '&relativePeriodDate=' + view.relativePeriodDate;
}
success = function (json) {
var response = gis.api.response.Response(json), // validate
featureMap = {},
valueMap = {},
ouIndex,
valueIndex,
valueFeatures = [], // only include features with values
values = []; // to find min and max values
if (!response) {
if (gis.mask) {
gis.mask.hide();
}
return;
}
// ou index, value index
for (var i = 0; i < response.headers.length; i++) {
if (response.headers[i].name === dimConf.organisationUnit.dimensionName) {
ouIndex = i;
}
else if (response.headers[i].name === dimConf.value.dimensionName) {
valueIndex = i;
}
}
// Feature map
for (var i = 0, id; i < features.length; i++) {
var id = features[i].id;
featureMap[id] = true;
}
// Value map
for (var i = 0; i < response.rows.length; i++) {
var id = response.rows[i][ouIndex],
value = parseFloat(response.rows[i][valueIndex]);
valueMap[id] = value;
}
for (var i = 0; i < features.length; i++) {
var feature = features[i],
id = feature.id;
if (featureMap.hasOwnProperty(id) && valueMap.hasOwnProperty(id)) {
feature.properties.value = valueMap[id];
valueFeatures.push(feature);
values.push(valueMap[id]);
}
}
// Sort values in ascending order
values.sort(function (a, b) {
return a - b;
});
// TODO: Temporarily
gis.data = {
metaData: response.metaData,
features: valueFeatures,
values: values
};
gis.response = response;
loadLegend(view);
};
Ext.Ajax.request({
url: encodeURI(gis.init.contextPath + '/api/analytics.json' + paramString),
disableCaching: false,
failure: function (r) {
gis.alert(r);
},
success: function (r) {
success(JSON.parse(r.responseText));
}
});
};
loadLegend = function (view, metaData, features, values) {
var dimConf = gis.conf.finals.dimension,
metaData = metaData || gis.data.metaData,
features = features || gis.data.features,
values = values || gis.data.values,
bounds = [],
colors = [],
names = [],
legends = [],
count = {}, // number in each class
addNames,
fn,
loadLegendSet;
view = view || layer.view;
addNames = function(response) {
// All dimensions
var dimensions = arrayClean([].concat(view.columns || [], view.rows || [], view.filters || [])),
metaData = response.metaData,
peIds = metaData[dimConf.period.objectName];
for (var i = 0, dimension; i < dimensions.length; i++) {
dimension = dimensions[i];
for (var j = 0, item; j < dimension.items.length; j++) {
item = dimension.items[j];
item.name = metaData.names[item.id];
}
}
// Period name without changing the id
view.filters[0].items[0].name = metaData.names[peIds[peIds.length - 1]];
};
fn = function () {
addNames(gis.response);
var options = { // Classification options
indicator: gis.conf.finals.widget.value,
method: view.method,
numClasses: view.classes,
bounds: bounds,
colors: colors,
count: count,
minSize: view.radiusLow,
maxSize: view.radiusHigh,
minValue: values[0],
maxValue: values[values.length - 1],
colorLow: view.colorLow,
colorHigh: view.colorHigh
};
layer.view = view;
layer.minValue = options.minValue;
layer.maxValue = options.maxValue;
applyClassification(options, features, values);
updateLegend(view, metaData, options);
updateMap(view, features);
afterLoad(view);
};
loadLegendSet = function (view) {
Ext.Ajax.request({
url: encodeURI(gis.init.contextPath + '/api/legendSets/' + view.legendSet.id + '.json?fields=' + gis.conf.url.legendSetFields.join(',')),
scope: this,
disableCaching: false,
success: function (r) {
legends = JSON.parse(r.responseText).legends;
arraySort(legends, 'ASC', 'startValue');
for (var i = 0; i < legends.length; i++) {
if (bounds[bounds.length - 1] !== legends[i].startValue) {
if (bounds.length !== 0) {
colors.push('#F0F0F0');
names.push('');
}
bounds.push(legends[i].startValue);
}
colors.push(legends[i].color);
names.push(legends[i].name);
bounds.push(legends[i].endValue);
}
view.legendSet.names = names;
view.legendSet.bounds = bounds;
view.legendSet.colors = colors;
view.legendSet.count = count;
},
callback: function () {
fn();
}
});
};
if (view.legendSet) {
view.method = 1; // Predefined legend
loadLegendSet(view);
}
else {
var elementMap = {
'in': 'indicators',
'de': 'dataElements',
'ds': 'dataSets'
},
elementUrl = elementMap[view.columns[0].objectName],
id = view.columns[0].items[0].id;
if (!elementUrl) {
fn();
return;
}
Ext.Ajax.request({
url: encodeURI(gis.init.contextPath + '/api/' + elementUrl + '.json?fields=legendSet[id,displayName|rename(name)]&paging=false&filter=id:eq:' + id),
success: function (r) {
var elements = JSON.parse(r.responseText)[elementUrl],
set;
if (arrayFrom(elements).length) {
set = isObject(elements[0]) ? elements[0].legendSet || null : null;
}
if (set) {
view.legendSet = set;
loadLegendSet(view);
}
else {
fn();
}
},
failure: function () {
fn();
}
});
}
};
// Calculate bounds and assign color to features based on value
applyClassification = function (options, features, values) {
var method = options.method,
bounds = [],
colors = [];
if (method === 1) { // predefined bounds
bounds = options.bounds;
colors = options.colors;
} else if (method === 2) { // equal intervals
for (var i = 0; i <= options.numClasses; i++) {
bounds[i] = options.minValue + i * (options.maxValue - options.minValue) / options.numClasses;
}
options.bounds = bounds;
colors = options.colors = getColorsByRgbInterpolation(options.colorLow, options.colorHigh, options.numClasses);
} else if (method === 3) { // quantiles
var binSize = Math.round(values.length / options.numClasses),
binLastValPos = (binSize === 0) ? 0 : binSize;
if (values.length > 0) {
bounds[0] = values[0];
for (i = 1; i < options.numClasses; i++) {
bounds[i] = values[binLastValPos];
binLastValPos += binSize;
if (binLastValPos > values.length - 1) {
binLastValPos = values.length - 1;
}
}
bounds.push(values[values.length - 1]);
}
for (var j = 0; j < bounds.length; j++) {
bounds[j] = parseFloat(bounds[j]);
}
options.bounds = bounds;
colors = options.colors = getColorsByRgbInterpolation(options.colorLow, options.colorHigh, options.numClasses);
}
if (bounds.length) {
for (var i = 0, prop, value, classNumber; i < features.length; i++) {
prop = features[i].properties;
value = prop[options.indicator];
classNumber = getClass(value, bounds);
prop.color = colors[classNumber - 1];
prop.radius = (value - options.minValue) / (options.maxValue - options.minValue) * (options.maxSize - options.minSize) + options.minSize;
// Count features in each class
if (!options.count[classNumber]) {
options.count[classNumber] = 1;
} else {
options.count[classNumber]++;
}
}
}
};
// Returns class number
getClass = function (value, bounds) {
if (value >= bounds[0]) {
for (var i = 1; i < bounds.length; i++) {
if (value < bounds[i]) {
return i;
}
}
if (value === bounds[bounds.length - 1]) {
return bounds.length - 1;
}
}
return null;
};
getColorsByRgbInterpolation = function (firstColor, lastColor, nbColors) {
var colors = [],
colorA = hexToRgb('#' + firstColor),
colorB = hexToRgb('#' + lastColor);
if (nbColors == 1) {
return ['#' + firstColor];
}
for (var i = 0; i < nbColors; i++) {
colors.push(rgbToHex({
r: parseInt(colorA.r + i * (colorB.r - colorA.r) / (nbColors - 1)),
g: parseInt(colorA.g + i * (colorB.g - colorA.g) / (nbColors - 1)),
b: parseInt(colorA.b + i * (colorB.b - colorA.b) / (nbColors - 1)),
}));
}
return colors;
};
// Convert hex color to RGB
hexToRgb = function (hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
};
// Convert RGB color to hex
rgbToHex = function (rgb) {
return "#" + ((1 << 24) + (rgb.r << 16) + (rgb.g << 8) + rgb.b).toString(16).slice(1);
};
// Add layer to map
updateMap = function (view, features) {
var layerConfig = Ext.applyIf({
data: features,
hoverLabel: '{name} ({value})'
}, layer.config);
if (view.labels) {
Ext.apply(layerConfig, {
label: '{name}',
labelStyle: {
fontSize: view.labelFontSize,
fontStyle: view.labelFontStyle
}
});
}
// Remove layer instance if already exist
if (layer.instance && gis.instance.hasLayer(layer.instance)) {
gis.instance.removeLayer(layer.instance);
}
layer.instance = gis.instance.addLayer(layerConfig);
// Put map layers in correct order: https://github.com/dhis2/dhis2-gis/issues/9
gis.util.map.orderLayers();
layer.instance.on('click', onFeatureClick);
layer.instance.on('contextmenu', onFeatureRightClick);
layer.view = view;
};
onFeatureClick = function (evt) {
GIS.core.FeaturePopup(gis, evt.layer);
};
onFeatureRightClick = function (evt) {
contextMenu = GIS.core.FeatureContextMenu(gis, layer, evt.layer);
contextMenu.showAt([evt.originalEvent.x, evt.originalEvent.y]);
};
updateLegend = function (view, metaData, options) {
var bounds = options.bounds,
colors = options.colors,
legendNames = view.legendSet ? view.legendSet.names || {} : {},
//imageLegendConfig = [],
html,
id,
name;
// title
id = view.columns[0].items[0].id;
// event data items
if (view.valueType === 'di') {
id = view.program.id + '.' + id;
}
name = view.columns[0].items[0].name;
html = '<div class="dhis2-legend"><h2>' + (metaData.names[id] || name || id);
// period
id = view.filters[0].items[0].id;
name = view.filters[0].items[0].name;
html += ' <span>' + (metaData.names[id] || name || id) + '</span></h2>';
// color legend
if (view.method === 1 && view.legendSet) {
html += '<dl class="dhis2-legend-predefined">';
for (var i = 0, name, label; i < bounds.length - 1; i++) {
name = legendNames[i];
label = bounds[i] + ' - ' + bounds[i + 1] + ' (' + (options.count[i + 1] || 0) + ')';
html += '<dt style="background-color:' + colors[i] + ';"></dt>';
html += '<dd><strong>' + (name || '') + '</strong>' + label + '</dd>';
}
}
else {
html += '<dl class="dhis2-legend-automatic">';
for (var i = 0, label; i < bounds.length - 1; i++) {
label = bounds[i].toFixed(1) + ' - ' + bounds[i + 1].toFixed(1) + ' (' + (options.count[i + 1] || 0) + ')';
html += '<dt style="background-color:' + colors[i] + ';"></dt>';
html += '<dd>' + label + '</dd>';
}
}
html += '</dl></div>';
if (layer.legendPanel) {
layer.legendPanel.update(html);
} else { // Dashboard map
if (!gis.legend) {
gis.legend = gis.instance.addControl({
type: 'legend',
offset: [0, -64],
content: html
});
} else {
gis.legend.setContent(gis.legend.getContent() + html);
}
}
},
afterLoad = function(view) {
// Legend
if (gis.viewport) {
gis.viewport.eastRegion.doLayout();
}
if (layer.legendPanel) {
layer.legendPanel.expand();
}
// Layer
layer.instance.setOpacity(view.opacity);
if (layer.item) {
layer.item.setValue(true);
}
// Filter
if (layer.filterWindow && layer.filterWindow.isVisible()) {
layer.filterWindow.filter();
}
// Gui
if (loader.updateGui && isObject(layer.widget)) {
layer.widget.setGui(view, loader.isDrillDown);
}
// Zoom
if (loader.zoomToVisibleExtent) {
gis.instance.fitBounds(layer.instance.getBounds());
}
// Mask
if (loader.hideMask) {
if (gis.mask) {
gis.mask.hide();
}
}
// Map callback
if (loader.callBack) {
loader.callBack(layer);
}
else {
gis.map = null;
if (gis.viewport && gis.viewport.shareButton) {
gis.viewport.shareButton.enable();
}
}
// session storage
if (GIS.isSessionStorage) {
gis.util.layout.setSessionStorage('map', gis.util.layout.getAnalytical());
}
};
loader = {
compare: false,
updateGui: false,
zoomToVisibleExtent: false,
hideMask: false,
callBack: null,
isDrillDown: false,
load: function(view) {
if (gis.mask && !gis.skipMask) {
gis.mask.show();
}
if (this.compare) {
compareView(view, true);
}
else {
loadOrganisationUnits(view);
}
},
loadData: loadData,
loadLegend: loadLegend
};
return loader;
};