-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.js
538 lines (447 loc) · 18.8 KB
/
main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
// ** GLOBALS ** //
const apiURL = 'http://api.emissionsintrade.com/v1/'
// add basemap layer
var map = L.map('map')
.setView({lat: 51.5, lon: 11}, 3)
.setMinZoom(2); //Set center coordinates and zoom level (2)
var tileURL = 'https://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}'
L.tileLayer(tileURL, {
attribution: 'Tiles © Esri — Esri, DeLorme, NAVTEQ',
maxZoom: 16,
interactive: true,
dragging: !L.Browser.mobile,
tap: !L.Browser.mobile,
touchZoom: true
}).addTo(map);
// add layer group
var lyrGroup = L.featureGroup().addTo(map);
// Match a sector to a color (used to color polylines)
var colorDict = {"accommodation_and_food_services": "blue",
"agriculture": "red",
"construction": "green",
"electricity": "yellow",
"finance_and_real_estate": "black",
"manufacturing": "white",
"mining": "purple",
"public_services": "teal",
"retail":"brown",
"transport": "pink",
"water_and_waste":"orange"};
var maxVals = {
"co2": 4901975988251,
"economy": 15968584,
"vl": 424087
};
document.getElementById("info-table").style.visibility="hidden";
// Initialise all info-icon tooltips (code snippet from Bootstrap)
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl)
})
// ** FUNCTIONS ** //
// Load stressors
async function loadStressors() {
const response = await fetch(apiURL+'stressors');
const data = await response.json();
var options = ""; //<option> -- Select stressor --</option>
for (let stressor of data.result) {
options += `<option value="${stressor.path_var}">${stressor.name}</option>`;
}
document.getElementById("stressor").innerHTML=options;
}
// Load regions
async function loadRegions() {
const response = await fetch(apiURL+'regions');
const data = await response.json();
var options = "";//'<option value=""> -- Select country --</option>'
//data.sort() // cannot sort because its a dictionart from the API (this is an OBJECT) needs to be aray
data.result.sort(function(a,b){
if (a.region_name < b.region_name) { //needed to call region name because a and b are objects
return -1;
} //need the ifs becasue its strings (numbers would be a-b)
if (a.region_name > b.region_name) {
return 1;
}
// names must be equal
return 0;
});
for (let country of data.result) { //data.result is an array
options += `<option value="${country.region_id}">${country.region_name}</option>`;
}
document.getElementById("region-from").innerHTML=options;
document.getElementById("region-to").innerHTML=options;
}
// Load sectors
async function loadSectors() {
const response = await fetch(apiURL+'sectors');
const data = await response.json();
var options = ""; //<option> -- Select sector --</option>
for (let sector of data.result) {
options += `<option value="${sector.sector}">${sector.sector}</option>`;
}
document.getElementById("sector-from").innerHTML=options;
document.getElementById("sector-to").innerHTML=options;
}
// Check how many Leaflet layers at start
function layerStart(){
let qq = 0;
map.eachLayer(function(){ qq += 1; });
console.log('Map has', qq, 'layers at start.');
}
// Count layers
function countLayers() {
let ll = 0;
map.eachLayer(function(){ ll += 1; });
console.log('Map has', ll, 'layers.');
}
// Delete old layers
function deleteLayers(){
// Remove old polylines/markers on each submit
if(map.hasLayer(lyrGroup)){
lyrGroup.eachLayer(
function(i){
lyrGroup.removeLayer(i);
});
}
}
function readData() {
var stressor = document.getElementById('stressor').value;
var regionFrom = Array.from(document.querySelectorAll('#region-from > option:checked'),
({value}) => value
);
var regionTo = Array.from(document.querySelectorAll('#region-to > option:checked'),
({value}) => value
);
var sectorFrom = Array.from(document.querySelectorAll('#sector-from > option:checked'),
({value}) => value
);
var sectorTo = Array.from(document.querySelectorAll('#sector-to > option:checked'),
({value}) => value
);
return {
"stressor": stressor,
"regionTo": regionTo,
"regionFrom": regionFrom,
"sectorFrom": sectorFrom,
"sectorTo": sectorTo
}
}
async function getResults() {
formData = readData();
var endpoint = apiURL+`stressors/${formData.stressor}?region_to=${formData.regionTo}®ion_from=${formData.regionFrom}§or_from=${formData.sectorFrom}§or_to=${formData.sectorTo}`;
//console.log(endpoint);
try {
const response = await fetch(endpoint);
const data = await response.json();
return data;
} catch {
console.log("Error in the API request");
}
}
async function regData() {
// Read the data that the user has chosen from the form
formData = readData();
var results = await getResults();
// Get all lat/lon data from the regions API
var regionURL = apiURL+"regions";
try {
// Retrieve lat/lon data from the regions API. This will return the data for all countries
const response = await fetch(regionURL);
const latlondata = await response.json();
// arrayLL will return an array nested with dicts of all the region ids, region names & their lats/lons
// example: arrayLL[0] = {region_id:"at", region_lat: 47.xx, region_lon: 14.xx, region_name:"austria"}
var arrayLL = latlondata.result;
// userRequest will return a list of results of the data that the user is requesting in the form
var userRequest = results.result;
// for each row in userRequest
for (var p=0; p < userRequest.length; p++ ){
// and for each row in the list of country coordinate data
for (var t=0; t < arrayLL.length; t++ ){
// if the country id at row t in the array matches the region_from (also the id) data requested by the user
if (arrayLL[t].region_id === userRequest[p].region_from) {
//create a dictionary that houses the region from coordinates
userRequest[p]["coords_from"] = {"lat" : arrayLL[t].region_lat, "lon" : arrayLL[t].region_lon}
}
// and do the same when region_id matches the region_to id
if (arrayLL[t].region_id === userRequest[p].region_to) {
userRequest[p]["coords_to"] = {"lat" : arrayLL[t].region_lat, "lon" : arrayLL[t].region_lon}
}
}
}
return userRequest;
} catch(err) {
console.log(err.message);
}
}
function createPopUpTable(userData) {
var popUpTable = document.getElementById('results-pop-up-body');
popUpTable.innerHTML = `<tr>`;
for (var i=0; i<userData.length; i++){
if (userData[i].region_from === userData[i].region_to) {
var row = ` <td>${userData[i].region_from}</td>
<td>${userData[i].sector_from}</td>
<td>${userData[i].sector_to}</td>
<td>${Math.round(userData[i].val)}</td>
<td>${userData[i].unit}</td>`;
popUpTable.innerHTML += row;
}
}
popUpTable.innerHTML += `</tr>`;
var popup = L.popup({offset: [-2, -24], maxWidth: "auto"})
.setContent(document.getElementById("pop-up-table"))
return popup;
}
function createTable(userData){
/* createTable() accepts the results from the user input (comes from the getResults())
and makes a table from these results. */
document.getElementById("info-table").style.visibility="visible";
var table = document.getElementById('results-table-body');
table.innerHTML = "";
for (var i=0; i<userData.length; i++){
//console.log(data.length);
var row = ` <td>${userData[i].region_from}</td>
<td>${userData[i].region_to}</td>
<td>${userData[i].sector_from}</td>
<td>${userData[i].sector_to}</td>
<td>${Math.round(userData[i].val)}</td>
<td>${userData[i].unit}</td>`;
table.innerHTML += row;
}
}
function bboxLatLng(userData) {
bnds = []
// Get latlon data for each region selected
for (var i=0; i < userData.length; i++ ){
var reg1 = Object.values(userData[i].coords_from);
var reg2 = Object.values(userData[i].coords_to);
bnds.push(reg1, reg2)
}
// Remove duplicates (from SO: https://stackoverflow.com/questions/44014799/javascript-how-to-remove-duplicate-arrays-inside-array-of-arrays)
// Comments my own
// Create an object to hold the lats and lons
var removeDuplicates = {}
// For each array in the bnds element, join it to remove duplicates as a key
// and as value {45.1, 15.2: [45.1, 15.2]. Since we get the key as the lat/lon
// we can get these later (since a dict will not have a duplicate key)
// so this allows us to only get once instance each of the lat/lon as an array
bnds.forEach(function(arr){
removeDuplicates[arr.join(",")] = arr;
});
//console.log(removeDuplicates);
var bndsOnce = Object.keys(removeDuplicates).map(function(i) {
return removeDuplicates[i]
});
//console.log(bndsOnce)
return bndsOnce
}
/* function colorArrow(userData){
var setColors = new Set()
for (var c=0; c < userData.length; c++ ){
var sectorFrom = userData[c].sector_from;
var lineColor = colorDict[sectorFrom];
setColors.add(lineColor);
}
var arrayColors = Array.from(setColors);
return arrayColors
}
*/
function getRanFloat(){
counter = []
// Assign min/max values
min = 0.1
max = 0.8
// num will yield a number between 0.1 and 0.8*random float
num = min + Math.random() * (max);
// Check that the same number doesn't get used more than once
if (counter.includes(num)) {
getRanFloat();
} else {
counter.push(num);
}
return num;
}
function swoops(userData, pop, bounds) {
var formData = readData();
//console.log(userData)
for (var i=0; i < userData.length; i++ ){
var sectorFrom = userData[i].sector_from;
var lineColor = colorDict[sectorFrom];
var value = userData[i].val;
var latlng1 = Object.values(userData[i].coords_from);
var latlng2 = Object.values(userData[i].coords_to);
// Get weight of line
var stressor = formData.stressor;
var stressorMax = maxVals[stressor];
var stressorRatio = Math.log(value)/Math.log(stressorMax);
var lineSize = 15
var lineWeight = lineSize*stressorRatio
// Get values for tooltip
var ud_rf = userData[i].region_from;
//console.log("rf " + ud_rf)
var ud_rt = userData[i].region_to;
//console.log("rt " + ud_rt)
var ud_sf = userData[i].sector_from;
var ud_st = userData[i].sector_to;
var ud_val = Math.round(userData[i].val);
//console.log(ud_val)
var ud_unit = userData[i].unit;
// Insert a marker (pin) if rf = rt
if (userData[i].region_from === userData[i].region_to) {
region = userData[i].coords_from;
markerIcon(region, pop);
// When region_from = ROW, print "rest of the world" at the end of the polyline
// so it is clear to the user where this arrow is "coming from"
} else if (userData[i].region_from === "row") {
lyrGroup.addLayer(L.swoopyArrow(latlng1, latlng2, {
iconAnchor: [35, -15], // Adjust html test positioning
color: lineColor, // Color polyline and arrowhead
arrowFilled: true,
factor: getRanFloat(), // Control the bend of the arrow
weight: lineWeight, // Change polyline weight
html: 'Rest of the world' // Include as explanation for the ROW region_from
})).addTo(map);
addTextTooltip(ud_rf, ud_rt, ud_sf, ud_st, ud_val, ud_unit);
} else {
lyrGroup.addLayer(L.swoopyArrow(latlng1, latlng2, {
color: lineColor,
arrowFilled: true,
factor: getRanFloat(),
weight: lineWeight,
arrowFilled: true,
})
).addTo(map);
addTextTooltip(ud_rf, ud_rt, ud_sf, ud_st, ud_val, ud_unit);
}
// Accepts the bounds from the bboxLatLng function and bounds around all polyines
map.fitBounds(L.latLngBounds(bounds));
}
}
function markerIcon (region, pop) {
/* When rTo and rFrom are same, a marker is added. The original blue marker is swapped
with a nicer marker (made in ppt). The iconAnchor puts the top left corner of the icon at the
designated latlong. The iconAnchor is moved up by the length of the icon (30) and to the left by 15 pixels
since the midpoint is assumed to be in the middle of the bottom edge of the icon. */
//var newMarker = L.icon({iconUrl: 'images/marker.svg',
// iconSize: [24, 30],
// iconAnchor: [15, 30]});
lyrGroup.addLayer(L.marker(region)
.bindPopup(pop).addTo(map));
var newmap = map.setView(region, zoom=6);
return newmap
}
/* Hide the map if the button "View Table" at the top of the page is selected */
function viewTable() {
var countClicks = 0;
var tableBtn = document.getElementById('table-btn');
tableBtn.onclick = function() {
countClicks++;
if (countClicks % 2 == 0) {
var showMap = document.getElementById('map');
showMap.style.display = 'block';
var infoTable = document.getElementById('info-table');
infoTable.style.display= 'none';
} else {
var showMap = document.getElementById('map');
showMap.style.display = 'none';
var infoTable = document.getElementById('info-table');
infoTable.style.display= 'block';
}
}
}
/* Hide the table */
function viewMap() {
var countClicks = 0;
var mapBtn = document.getElementById('map-btn');
mapBtn.onclick = function() {
countClicks++;
if (countClicks % 2 == 0) {
var infoTable = document.getElementById('info-table');
infoTable.style.display = 'block';
var showMap = document.getElementById('map');
showMap.style.display= 'none';
} else {
var infoTable = document.getElementById('info-table');
infoTable.style.display = 'none';
var showMap = document.getElementById('map');
showMap.style.display= 'block';
}
console.log(countClicks)
}
}
function maxZIndex() {
return Array.from(document.querySelectorAll('body *'))
.map(a => parseFloat(window.getComputedStyle(a).zIndex))
.filter(a => !isNaN(a))
.sort()
.pop();
}
function addTextTooltip(tt_regionFrom, tt_regionTo, tt_sectorFrom, tt_sectorTo, tt_Val, tt_Unit) { // CAN innerHTML ONMOUSEOVER event instead to get tooltip.
var arrowPath = document.getElementsByTagName('path');
// getElementsBy (class, tag name, ...) will always return a node list, so we need to iterate through this
// Since the addTextTooltip() will be called for each iteration in swoops(), we need to
// match the iterations to the item in the getElementsBy(...) node list.
for (var i = 0; i < arrowPath.length; i++) {
var iteration = arrowPath.length-1
}
arrowPath[iteration].addEventListener('mouseenter', function(e) {
document.getElementById("ArrowTable").style.display="block";
// client may be relev to viewport and page to document
var tooltipTest = document.getElementById('arrow-table');
tooltipTest.innerHTML = `<th scope="col">Region From</th>
<th scope="col">Region To</th>
<th scope="col">Sector From</th>
<th scope="col">Sector to</th>
<th scope="col">Value</th>
<th scope="col">Unit</th> `;
var row = ` <td>${tt_regionFrom}</td>
<td>${tt_regionTo}</td>
<td>${tt_sectorFrom}</td>
<td>${tt_sectorTo}</td>
<td>${tt_Val}</td>
<td>${tt_Unit}</td>`;
tooltipTest.innerHTML += row;
tooltipTest.innerHTML += `</tbody>`;
//tooltipTest.style.position = 'absolute'; // move to css
// get mouse coords (relative or absol)
tooltipTest.style.top = (e.pageY - tooltipTest.offsetHeight/2)+'px';
tooltipTest.style.left = (e.pageX - tooltipTest.offsetWidth/2)+'px';
tooltipTest.style.zIndex = 400;
console.log(maxZIndex());
tooltipTest.style.visibility= 'visible';
tooltipTest.addEventListener('mouseleave',function(){
tooltipTest.innerHTML = '';
tooltipTest.style.display = 'hidden'; //display auto or display hidden
})
// to center, subtract dimension
// need to computer highest value of z index because this is what is getting hte value
// to show up above the map (the map is hiding the tooltip information at the moment)
//console.log(tableHeader)
})
// can change cursor style to pointer in css (cursor: pointer;) in CSS: .tooltip { cursor: pointer }
}
function getFormData(){
document.getElementById('api-form').addEventListener('submit', async function (e){
e.preventDefault();
deleteLayers();
var userData = await regData();
var pop = createPopUpTable(userData);
var bounds = bboxLatLng(userData);
swoops(userData, pop, bounds);
createTable(userData);
})
}
async function main(){
// Load the API data into the form selection (list all str, reg, and sec in form)
await loadStressors();
await loadRegions();
await loadSectors();
// Implement the business logic
layerStart();
getFormData();
map.on('zoomend',function(e) {
//console.log('Actual zoom: ' + e.target.getZoom());
})
}
viewMap();
viewTable();
main();