index.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. const express = require('express');
  2. const helpers = require('./helpers.js');
  3. const nutsData = require('./nuts-data.js');
  4. const cors = require('cors');
  5. const R = require('r-script');
  6. var where = require('lodash.where');
  7. const app = express();
  8. const _datasetsFilePath = 'data/datasets.csv';
  9. const _datasetsCzFilePath = 'data/datasets-cz.csv';
  10. const _dataFilePath = 'data/data.csv';
  11. const _dataCzFilePath = 'data/cz/data-input-CZ_LAU2_37_cols.csv';
  12. //const _clusteringInputFilePath = 'data/clustering/input_all.csv';
  13. const _clusteringCzInputFilePath = 'data/cz/clusters-input-CZ_LAU2_37_cols.csv';
  14. const _clusteringModifiedFilePath = 'data/clustering/input_modified.csv';
  15. const _clusteringCzModifiedFilePath = 'data/cz/input_modified.csv';
  16. const _clustersFilePath = 'data/clustering/out_file.csv';
  17. const _clustersCzFilePath = 'data/cz/out_file_cz.csv';
  18. var _datasets = undefined;
  19. var _datasetsCZ = undefined;
  20. var _ruralData = undefined;
  21. var _ruralDataCZ = undefined;
  22. // parse incoming POST requests body to JSON
  23. app.use(express.json());
  24. // handle CORS
  25. app.use(cors())
  26. // serve static files from the /static directory
  27. app.use(express.static('static'))
  28. // set PUG as default rendering engine
  29. app.set('view engine', 'pug')
  30. /* Dummy web service call without the method specified */
  31. app.get('/', (req, res) => {
  32. res.send('Rural attractivness web service');
  33. });
  34. /* Makes refresh of the data loaded to the server objects.
  35. Must be called after the CSV data has changed */
  36. app.get('/refresh', (req, res, next) => {
  37. nutsData.loadDatasets(_datasetsFilePath, function (ds) {
  38. //console.log('Datasets loaded succesfully');
  39. _datasets = ds;
  40. nutsData.loadRuralData(_dataFilePath, _datasets, function (rd) {
  41. //console.log('Rural data loaded succesfully');
  42. _ruralData = rd;
  43. res.send('Data refreshed');
  44. });
  45. });
  46. });
  47. /* Returns JSON array with the list of all the datasets */
  48. app.get('/datasets', (req, res, next) => {
  49. if (_datasets) {
  50. helpers.formatResponse(_datasets, req, res);
  51. }
  52. else {
  53. nutsData.loadDatasets(_datasetsFilePath, function (ds) {
  54. //console.log('Datasets loaded callback');
  55. _datasets = ds;
  56. helpers.formatResponse(_datasets, req, res);
  57. });
  58. }
  59. });
  60. /* Returns JSON array with the list of all the datasets */
  61. app.get('/datasets/cz', (req, res, next) => {
  62. console.log('received datasets/cz GET request')
  63. if (_datasetsCZ) {
  64. helpers.formatResponse(_datasetsCZ, req, res);
  65. }
  66. else {
  67. nutsData.loadDatasets(_datasetsCzFilePath, function (ds) {
  68. //console.log('Datasets loaded callback');
  69. _datasetsCZ = ds;
  70. helpers.formatResponse(_datasetsCZ, req, res);
  71. });
  72. }
  73. });
  74. /* Returns attractivity data for the region with ID equal to the 'nuts' parameter */
  75. app.get('/scores/:nuts', (req, res, next) => {
  76. if (_ruralData) {
  77. returnRegionScores(req.params.nuts, req, res);
  78. }
  79. else {
  80. if (_datasets) { // datasets must be loaded prior to data loading
  81. nutsData.loadRuralData(_dataFilePath, _datasets, function (rd) {
  82. _ruralData = rd;
  83. returnRegionScores(req.params.nuts, req, res);
  84. });
  85. }
  86. else {
  87. nutsData.loadDatasets(_datasetsFilePath, function (ds) {
  88. _datasets = ds;
  89. nutsData.loadRuralData(_dataFilePath, _datasets, function (rd) {
  90. _ruralData = rd;
  91. returnRegionScores(req.params.nuts, req, res);
  92. });
  93. });
  94. }
  95. }
  96. });
  97. /* Returns attractivity data for all the regions in source CSV data. */
  98. app.get('/scores', (req, res, next) => {
  99. if (_ruralData)
  100. helpers.formatResponse(_ruralData, req, res);
  101. else {
  102. if (_datasets) { // datasets must be loaded prior to data loading
  103. nutsData.loadRuralData(_dataFilePath, _datasets, function (rd) {
  104. _ruralData = rd;
  105. helpers.formatResponse(_ruralData, req, res);
  106. });
  107. }
  108. else {
  109. nutsData.loadDatasets(_datasetsFilePath, function (ds) {
  110. _datasets = ds;
  111. nutsData.loadRuralData(_dataFilePath, _datasets, function (rd) {
  112. _ruralData = rd;
  113. helpers.formatResponse(_ruralData, req, res);
  114. });
  115. });
  116. }
  117. }
  118. });
  119. /* Computes and returns attractivity data for all the NUTS regions based on the
  120. incomming datasets and factor weights. */
  121. app.post('/scores', (req, res, next) => {
  122. //console.log("query: " + JSON.stringify(req.body.factors, null, 4));
  123. if (_ruralData) {
  124. returnAllScores(req, res);
  125. }
  126. else {
  127. if (_datasets) { // datasets must be loaded prior to data loading
  128. nutsData.loadRuralData(_dataFilePath, _datasets, function (rd) {
  129. _ruralData = rd;
  130. returnAllScores(req, res);
  131. });
  132. }
  133. else {
  134. nutsData.loadDatasets(_datasetsFilePath, function (ds) {
  135. _datasets = ds;
  136. nutsData.loadRuralData(_dataFilePath, _datasets, function (rd) {
  137. _ruralData = rd;
  138. returnAllScores(req, res);
  139. });
  140. });
  141. }
  142. }
  143. });
  144. /* Returns attractivity data for all the regions in source CSV data. */
  145. app.get('/scores/cz', (req, res, next) => {
  146. console.log('received scores/cz GET request')
  147. if (_ruralDataCZ)
  148. helpers.formatResponse(_ruralDataCZ, req, res);
  149. else {
  150. if (_datasetsCZ) { // datasets must be loaded prior to data loading
  151. nutsData.loadRuralData(_dataCzFilePath, _datasetsCZ, function (rd) {
  152. _ruralDataCZ = rd;
  153. helpers.formatResponse(_ruralDataCZ, req, res);
  154. });
  155. }
  156. else {
  157. nutsData.loadDatasets(_datasetsCzFilePath, function (ds) {
  158. _datasetsCZ = ds;
  159. nutsData.loadRuralData(_dataCzFilePath, _datasetsCZ, function (rd) {
  160. _ruralDataCZ = rd;
  161. helpers.formatResponse(_ruralDataCZ, req, res);
  162. });
  163. });
  164. }
  165. }
  166. });
  167. /* Computes and returns attractivity data for all the NUTS regions based on the
  168. incomming datasets and factor weights. */
  169. app.post('/scores/cz', (req, res, next) => {
  170. console.log('received scores/cz POST request')
  171. //console.log("query: " + JSON.stringify(req.body.factors, null, 4));
  172. if (_ruralDataCZ) {
  173. returnAllScoresCz(req, res);
  174. }
  175. else {
  176. if (_datasetsCZ) { // datasets must be loaded prior to data loading
  177. nutsData.loadRuralData(_dataCzFilePath, _datasetsCZ, function (rd) {
  178. _ruralDataCZ = rd;
  179. returnAllScoresCz(req, res);
  180. });
  181. }
  182. else {
  183. nutsData.loadDatasets(_datasetsCzFilePath, function (ds) {
  184. _datasetsCZ = ds;
  185. nutsData.loadRuralData(_dataCzFilePath, _datasetsCZ, function (rd) {
  186. _ruralDataCZ = rd;
  187. returnAllScoresCz(req, res);
  188. });
  189. });
  190. }
  191. }
  192. });
  193. /*
  194. Only testing purposes
  195. */
  196. app.get('/runR/:version?', (req, res, next) => {
  197. //console.log(console);
  198. let v = req.params.version;
  199. const filename = v == 'cz' ? './r/CZ_clusters.r' :'./r/selected_data.r';
  200. console.log('calling R... for ' + v);
  201. //console.log(req)
  202. R(filename).data(9).call(
  203. function (err, data) {
  204. console.log('R done');
  205. if (err) {
  206. console.log(err.toString('utf8'));
  207. data = { result: err.toString('utf8') };
  208. }
  209. else {
  210. console.log(data);
  211. data = { result: 'R call succesful' };
  212. }
  213. helpers.formatResponse({ response: data }, req, res);
  214. }
  215. );
  216. });
  217. /*
  218. Just informative response. POST with JSON data is required.
  219. */
  220. app.get(['/clusters', '/clusters/cz'], (req, res, next) => {
  221. const data = { response: '/clusters methods are only available under POST' }
  222. helpers.formatResponse(data, req, res);
  223. });
  224. /*
  225. Modifies input CSV file, calls R script, loads the resulting CSV file and returns it
  226. */
  227. app.post('/clusters', async (req, res, next) => {
  228. try {
  229. if (!_datasets) {
  230. //TODO: promisify all functions to avoid callback hell and make this work properly
  231. await nutsData.loadDatasets(_datasetsFilePath, function (ds) {
  232. //console.log('Datasets loaded succesfully');
  233. _datasets = ds;
  234. })
  235. }
  236. //console.log(req.body);
  237. const clusteringData = await nutsData.loadClusteringInput(
  238. _dataFilePath
  239. );
  240. await nutsData.modifyClusteringData({
  241. datasets: _datasets,
  242. data: clusteringData,
  243. params: req.body,
  244. idString: 'NUTS_ID',
  245. outputFileName: _clusteringModifiedFilePath
  246. });
  247. handleRCall(req, res, 'eu');
  248. } catch (error) { // Catch errors in async functions
  249. next(error.toString());
  250. }
  251. });
  252. /*
  253. Modifies input CSV file, calls R script, loads the resulting CSV file and returns it
  254. */
  255. app.post('/clusters/cz', async (req, res, next) => {
  256. try {
  257. if (!_datasetsCZ) {
  258. //TODO: promisify all functions to avoid callback hell and make this work properly
  259. await nutsData.loadDatasets(_datasetsCzFilePath, function (ds) {
  260. //console.log('Datasets loaded succesfully');
  261. _datasetsCZ = ds;
  262. })
  263. }
  264. //console.log(req.body);
  265. const clusteringData = await nutsData.loadClusteringInput(
  266. _clusteringCzInputFilePath
  267. );
  268. await nutsData.modifyClusteringData({
  269. region: 'cz',
  270. datasets: _datasetsCZ,
  271. data: clusteringData,
  272. params: req.body,
  273. idString: 'LAU2',
  274. outputFileName: _clusteringCzModifiedFilePath
  275. });
  276. handleRCall(req, res, 'cz');
  277. } catch (error) { // Catch errors in async functions
  278. next(error.toString());
  279. }
  280. });
  281. app.get('/georeport/:nuts', (req, res, next) => {
  282. if (_ruralData) {
  283. renderPugReport(req.params.nuts, req, res);
  284. }
  285. else {
  286. if (_datasets) { // datasets must be loaded prior to data loading
  287. nutsData.loadRuralData(_dataFilePath, _datasets, function (rd) {
  288. _ruralData = rd;
  289. renderPugReport(req.params.nuts, req, res);
  290. });
  291. }
  292. else {
  293. nutsData.loadDatasets(_datasetsFilePath, function (ds) {
  294. _datasets = ds;
  295. nutsData.loadRuralData(_dataFilePath, _datasets, function (rd) {
  296. _ruralData = rd;
  297. renderPugReport(req.params.nuts, req, res);
  298. });
  299. });
  300. }
  301. }
  302. });
  303. // start the service on the port xxxx
  304. app.listen(3000, () => console.log('Rural attractivity WS listening on port 3000...'));
  305. function renderPugReport(nuts, req, res) {
  306. var found = false;
  307. _ruralData.forEach(region => {
  308. if (region.nuts == nuts) {
  309. let nutsGeoJson = helpers.loadJSON('static/NUTS3_4326.json');
  310. let filterednutsGeoJson = where(nutsGeoJson.features, { "properties": { "NUTS_ID": nuts } });
  311. let pilots = helpers.loadJSON('static/pilots.json');
  312. region.metadata = {
  313. name: filterednutsGeoJson[0].properties.NUTS_NAME,
  314. code: nuts,
  315. centroid: helpers.getCentroid(filterednutsGeoJson[0].geometry),
  316. pilot: helpers.getPilotRegion(nuts, pilots)
  317. };
  318. res.render('nuts', { region: region, datasets: _datasets });
  319. found = true;
  320. }
  321. });
  322. if (!found)
  323. res.render('err', {});
  324. }
  325. function returnAllScores(req, res) {
  326. var resData = [];
  327. _ruralData.forEach(region => {
  328. //var region = _ruralData[0];
  329. var sumWeight = 0;
  330. var sumValue = 0;
  331. let regionIndexes = { code: region.nuts };
  332. req.body.factors.forEach(f => {
  333. let fi = nutsData.getFactorIndex(region, f);
  334. //console.log("f: " + JSON.stringify(f));
  335. //console.log("fi: " + JSON.stringify(fi));
  336. regionIndexes[f.factor] = fi.index;
  337. sumValue += fi.sumValue * f.weight;
  338. sumWeight += fi.sumWeight;
  339. });
  340. regionIndexes.aggregate = sumValue / sumWeight;
  341. resData.push(regionIndexes);
  342. });
  343. helpers.formatResponse(resData, req, res);
  344. }
  345. function returnAllScoresCz(req, res) {
  346. var resData = [];
  347. _ruralDataCZ.forEach(region => {
  348. //var region = _ruralData[0];
  349. var sumWeight = 0;
  350. var sumValue = 0;
  351. let regionIndexes = { code: region.lau2 };
  352. req.body.factors.forEach(f => {
  353. let fi = nutsData.getFactorIndex(region, f);
  354. //console.log("f: " + JSON.stringify(f));
  355. //console.log("fi: " + JSON.stringify(fi));
  356. regionIndexes[f.factor] = fi.index;
  357. sumValue += fi.sumValue * f.weight;
  358. sumWeight += fi.sumWeight;
  359. });
  360. regionIndexes.aggregate = sumValue / sumWeight;
  361. resData.push(regionIndexes);
  362. });
  363. helpers.formatResponse(resData, req, res);
  364. }
  365. function returnRegionScores(nuts, req, res) {
  366. var found = false;
  367. res.header("Content-Type", 'application/json');
  368. _ruralData.forEach(region => {
  369. if (region.nuts == nuts) {
  370. helpers.formatResponse(region, req, res);
  371. found = true;
  372. }
  373. });
  374. if (!found)
  375. // NUTS region not found
  376. res.status(404).send('NUTS region not found.');
  377. }
  378. function handleRCall(req, res, region = 'eu') {
  379. const numberOfClusters = req.body.numberOfClusters || 12;
  380. //console.log('calling R...', numberOfClusters)
  381. const rScriptInputFileName = region == 'cz' ? './r/CZ_clusters.r' :'./r/selected_data.r';
  382. const rScriptResultsFileName = region == 'cz' ? _clustersCzFilePath : _clustersFilePath;
  383. const idString = region == 'cz' ? 'lau2' : 'nuts_id';
  384. R(rScriptInputFileName).data(numberOfClusters).call(
  385. function (err, data) {
  386. //console.log('R done');
  387. if (err) {
  388. console.log(err.toString('utf8'));
  389. data = { result: err.toString('utf8') };
  390. res.status(204).send({ response: data });
  391. }
  392. else {
  393. console.log(data);
  394. nutsData.loadClusters(rScriptResultsFileName, idString, function (clusterData) {
  395. data = clusterData;
  396. helpers.formatResponse({ response: data }, req, res);
  397. });
  398. }
  399. }
  400. );
  401. }