(function (angular) {
'use strict';
/**
* @memberof spApp
* @ngdoc service
* @name ToolsService
* @description
* List of spatial-hub tools. Includes client side tools with names matching Tool*Service. Includes
* tools from spatial-service/capabilities.
*/
angular.module('tools-service', [])
.factory('ToolsService',
['$injector', '$q', '$http', '$timeout', 'MapService', 'LayersService',
'LoggerService', 'LayoutService',
function ($injector, $q, $http, $timeout, MapService, LayersService,
LoggerService, LayoutService) {
var cap = $SH.layersServiceCapabilities;
var viewConfig = $SH.viewConfig;
var localToolServices = {};
var promises = [];
initLocalTools();
var _httpDescription = function (method, httpconfig) {
if (httpconfig === undefined) {
httpconfig = {};
}
httpconfig.service = 'ToolsService';
httpconfig.method = method;
return httpconfig;
};
/*
uiScope is ToolCtrl
*/
function executeRemote(uiScope, inputs) {
var m = {};
m['input'] = inputs;
m['name'] = uiScope.toolName;
var url = $SH.baseUrl + '/portal/postTask?sessionId='
+ $SH.sessionId;
$http.post(url, m, _httpDescription('executeRemote')).then(
function (response) {
uiScope.externalTaskId = response.data.id;
uiScope.statusUrl = LayersService.url() + '/tasks/status/'
+ response.data.id;
// Create log entry for an external tool when the task is created with the taskId
LoggerService.log('Tool', uiScope.toolName,{"taskId": uiScope.externalTaskId })
$timeout(function () {
_checkStatus(uiScope);
}, 5000)
},
function (error) {
if (!error.handled) {
var message = '';
if (error.headers()['content-type'].indexOf('json') > 0) {
message = JSON.stringify((error.data))
} else {
message = error.data;
}
bootbox.alert('Failed: ' + error.status);
}
uiScope.status = 'Failed';
uiScope.finished = true
});
return $q.when(false)
}
/*
uiScope is ToolCtrl
*/
function _checkStatus(uiScope) {
if (uiScope.cancelled) {
return;
}
return $http.get(uiScope.statusUrl + "?last=" + uiScope.last,
_httpDescription('checkStatus')).then(function (response) {
uiScope.status = response.data.message;
var keys = [];
var k;
for (k in response.data.history) {
if (response.data.history.hasOwnProperty(k)) {
keys.push(k)
}
}
keys.sort();
for (k in keys) {
uiScope.log[keys[k]] = response.data.history[keys[k]];
uiScope.logText = response.data.history[keys[k]] + '\r\n'
+ uiScope.logText;
uiScope.last = keys[k]
}
if (response.data.status < 2) {
uiScope.checkStatusTimeout = $timeout(function () {
_checkStatus(uiScope);
}, 5000)
} else if (response.data.status === 2) {
uiScope.status = 'cancelled';
uiScope.finished = true
} else if (response.data.status === 3) {
uiScope.status = 'error';
uiScope.finished = true
} else if (response.data.status === 4) {
if (response.data.output === undefined) {
uiScope.status = 'error';
uiScope.finished = true
} else {
uiScope.status = 'successful';
uiScope.finishedData = response.data;
return _executeResult(uiScope)
}
}
})
}
/**
* Operate on tool output uiScope.finishedData.output.
*
* Match output file:
* *.zip - Automatically initiate download when uiScope.spec.download==true
* *.html - Assigned to metadataUrl. LayersServiceUrl metadataUrls are opened in an iframe, others in a new tab
* *.csv - Opened with CsvCtrl only when there is no *.html
* *.tif - Add to the map as a new environmental layer
*
* Match output name:
* "area" - Add a new area to the map
* "species" - Add a new species layer to the map
* "nextprocess" - Initiate a new tool
*
* @param uiScope
* @returns {Promise<any>}
* @private
*/
function _executeResult(uiScope) {
var layers = [];
var nextprocess;
for (k in uiScope.finishedData.output) {
if (uiScope.finishedData.output.hasOwnProperty(k)) {
var d = uiScope.finishedData.output[k];
if (d.file && d.file.match(/\.zip$/g) != null) {
var filename = uiScope.toolName + " (" + (uiScope.taskId
|| uiScope.externalTaskId) + ").zip";
uiScope.downloadUrl = LayersService.url() + '/tasks/output/'
+ uiScope.finishedData.id + '/' + encodeURI(filename)
+ '?filename=' + d.file;
if (uiScope.downloadImmediately && uiScope.spec.download
!== false) {
Util.download(uiScope.downloadUrl, d.file);
}
} else if (d.downloadUrl) {
uiScope.downloadUrl = d.downloadUrl;
// look for "filename=" at the end of the url
var filename = 'file'
var match = d.downloadUrl.match('filename=.*$')
if (match && match.length > 0) {
filename = match[0].replace('filename=', '')
} else {
// remove params and use end of url
match = d.downloadUrl.match('([^/\\?]*)(\\?.*)?$')
if (match && match.length > 1) {
filename = match[1]
}
}
if (uiScope.downloadImmediately && uiScope.spec.download
!== false) {
Util.download(uiScope.downloadUrl, filename);
}
}
}
}
var csvFile = null;
var csvUrl = null;
for (k in uiScope.finishedData.output) {
if (uiScope.finishedData.output.hasOwnProperty(k)) {
var d = uiScope.finishedData.output[k];
if (d.openUrl) {
uiScope.metadataUrl = d.openUrl
} else if (d.file && d.file.match(/\.zip$/g) != null) {
//processed earlier
} else if (d.file && d.file.match(/\.html$/g) != null) {
uiScope.metadataUrl = LayersService.url() + '/tasks/output/'
+ uiScope.finishedData.id + '/' + d.file
} else if (d.file && d.file.match(/\.csv/g) != null) {
//can only display one csv file
csvUrl = LayersService.url() + '/tasks/output/'
+ uiScope.finishedData.id + '/' + d.file;
csvFile = d.file;
} else if (d.file && d.file.match(/\.tif$/g) != null) {
var name = d.file.replace('/layer/', '').replace('.tif', '');
layers.push({
id: name,
displaypath: $SH.geoserverUrl + '/wms?layers=ALA:' + name,
type: 'e',
name: name,
displayname: name,
layer: {
id: name,
displaypath: $SH.geoserverUrl + '/wms?layers=ALA:' + name,
type: 'e',
name: name,
displayname: name
}
});
} else if (d.name === 'area') {
if (d.file.indexOf("{") === 0) {
// parse JSON response
// ENVELOPE is the only output of this type
var json = JSON.parse(d.file);
layers.push({
id: json.id,
displaypath: json.wmsurl,
type: 'envelope',
layertype: 'area',
q: json.q,
name: json.name,
area_km: json.area_km,
bbox: json.bbox,
wkt: json.bbox,
pid: 'ENVELOPE' + json.id
});
} else {
//might be an area pid
promises.push(
LayersService.getObject(d.file).then(function (data) {
data.data.layertype = 'area';
data.data.log = false // The task is logged, no need to log adding the layer
return MapService.add(data.data)
}))
}
} else if (d.name === 'species') {
var q = jQuery.parseJSON(d.file);
if (!q.qid) {
q.qid = q.q;
}
q.opacity = 60;
q.scatterplotDataUrl = uiScope.downloadUrl;
q.log = false // The task is logged, no need to log adding the layer
promises.push(MapService.add(q))
} else if (d.name === 'nextprocess') {
var nextinput = jQuery.parseJSON(d.file);
// format 'nextprocess' output for LayoutService.openModal
nextprocess = {
processName: nextinput.process,
overrideValues: {}
};
nextprocess.overrideValues[nextinput.process] = {input: nextinput.input};
}
}
}
if (layers.length > 0) {
$.each(layers, function () {
var layer = this;
if (uiScope.metadataUrl
!== null) {
layer.metadataUrl = uiScope.metadataUrl;
}
layer.name = uiScope.toolName + " (" + layer.name + ")";
layer.log = false // The task is logged, no need to log adding the layer
promises.push(MapService.add(layer));
})
}
if (uiScope.metadataUrl !== null) {
uiScope.openUrl(uiScope.metadataUrl);
} else if (csvUrl !== null) {
$http.get(csvUrl, _httpDescription('getCsv')).then(
function (data) {
var columnOrder = uiScope.spec.output.columnOrder;
if (!columnOrder) {
columnOrder = [];
}
LayoutService.openModal('csv', {
title: uiScope.toolName + " (" + csvFile + ")",
csv: data.data,
columnOrder: columnOrder,
info: '',
filename: csvFile,
display: {size: 'full'}
}, false)
});
}
return $q.all(promises).then(function () {
uiScope.finished = true;
uiScope.$close();
if (nextprocess) {
$timeout(function () {
LayoutService.openModal('tool', nextprocess, false);
}, 0)
}
})
}
function registerService(toolName, service) {
localToolServices[toolName] = service;
cap[toolName] = service.spec
}
function executeLocal(uiScope, toolName, inputs) {
// Create log entry for a local tool when the task is finished with the input data
LoggerService.log('Tool', uiScope.toolName, inputs)
var result = localToolServices[toolName].execute(inputs)
if (result && result.then) {
result.then(function (response) {
uiScope.finishedData = response;
_executeResult(uiScope);
uiScope.$close();
})
} else {
uiScope.$close();
}
}
function refreshLocal(uiScope, toolName, inputs) {
if (typeof localToolServices[toolName].refresh === "function") {
localToolServices[toolName].refresh(inputs, uiScope.spec)
}
}
function initLocalTools() {
//inject all Tools into ToolsService
$.each(spApp.requires, function (x) {
var v = spApp.requires[x];
if (v.match(/-service$/g) != null && v.match(/^tool-/g) != null) {
var name = v.replace(/-.|^./g, function (match) {
return match.toUpperCase().replace('-', '')
});
var tool = $injector.get(name);
//is this a valid tool service?
if (tool && tool.spec && tool.execute) {
registerService(name, tool);
}
}
});
}
return {
/**
* Initialize client side tool
* @memberof ToolsService
* @param {string} name of client side tool
* @return {Promise}
*/
init: function (toolName) {
if (localToolServices[toolName]
&& localToolServices[toolName].init) {
localToolServices[toolName].init();
}
return $q.when($SH.layersServiceCapabilities);
},
/**
* Test if a tool name is a client side tool
* @memberof ToolsService
* @param {string} name of tool
* @return {boolean} true when it is a client side tool
*/
isLocalTask: function (toolName) {
return localToolServices[toolName] !== undefined;
},
/**
* Test if a tool name is a client side or remote tool
* @memberof ToolsService
* @param {string} name of tool
* @return {boolean} true when it is a client side or remote tool
*/
isTool: function (toolName) {
return localToolServices[toolName] !== undefined || cap[toolName]
!== undefined;
},
/**
* Run a tool
* @memberof ToolsService
* @param {Scope} interface scope
* @param {string} tool name
* @param {Map} input parameters
* @return {Promise(Boolean)} true the tool is successful
*/
execute: function (uiScope, toolName, inputs) {
if (localToolServices[toolName]) {
return executeLocal(uiScope, toolName, inputs)
} else {
return executeRemote(uiScope, inputs)
}
},
/**
* Run a tool
* @memberof ToolsService
* @param {Scope} interface scope
* @param {string} tool name
* @param {Map} input parameters
* @return {Promise(Boolean)} true the tool is successful
*/
refresh: function (uiScope, toolName, inputs) {
if (localToolServices[toolName]) {
refreshLocal(uiScope, toolName, inputs)
}
},
/**
* Get tool capabilities (info including inputs/outputs)
* @memberof ToolsService
* @param {string} tool name
* @return {Map} capabilities information
*/
getCap: function (toolName) {
return cap[toolName]
},
/**
* Get spatial-hub view config for a tool. View config overrides capabilities to control display
* and defaults.
* @memberof ToolsService
* @param {string} tool name
* @return {Map} view config including capabilities override information
*/
getViewConfig: function (toolName) {
return viewConfig[toolName]
},
/**
* Begin tool status monitoring.
*
* Monitoring will repeat until execution is finished.
*
* When successful #executeResult is called.
*
* @memberof ToolsService
* @param {Scope} interface scope
*/
checkStatus: function (uiScope) {
_checkStatus(uiScope)
},
/**
* Process tool output. Does download, display and/or add to the map as defined by
* capabilities and view config.
*
* @memberof ToolsService
* @param {Scope} interface scope
*/
executeResult: function (uiScope) {
_executeResult(uiScope)
}
}
}])
}(angular));