-
Notifications
You must be signed in to change notification settings - Fork 0
/
scripts.js
260 lines (207 loc) · 8.55 KB
/
scripts.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
// For storing data locally. Principally for development.
const STORAGE_KEY = "540mongo-weather-alerts";
// Features to import from ArcGIS.
const ARCGIS_LIBS = [
"esri/Map", "esri/views/MapView", "esri/Graphic", "esri/geometry/Point",
"esri/symbols/SimpleMarkerSymbol", "esri/request",
"esri/geometry/Polygon", "esri/symbols/SimpleFillSymbol",
"esri/core/watchUtils", "esri/geometry/support/webMercatorUtils",
"esri/widgets/Search"
];
// Agency filter field.
var filter = document.getElementById(FILTER);
var filterButton = document.getElementById(FILTER_BUTTON);
var mapify = function (Map, MapView, Graphic, Point, Marker, esriRequest, Polygon, Fill, watchUtils, webMercatorUtils, Search) {
var pointsCollection = []; // Contract markers
var alertUGCs = []; // UGCs with active alerts
/* Build a point marker */
var point = (longitude, latitude) => new Graphic({
geometry: new Point({ longitude: longitude, latitude: latitude }),
symbol: new Marker(PT_SYMBOL)
});
/* Build a polygon */
var polygon = coordinates => new Graphic({
geometry: new Polygon({ rings: coordinates }),
symbol: new Fill(POLY_SYMBOL)
});
// Funct to show and then hide a notification for the user
var showNotification = notification => {
var msgfield = document.getElementById(SHADOW);
msgfield.style.display = "block";
msgfield.children[0].innerHTML = notification;
window.setTimeout(function () {
msgfield.classList.add("completed");
window.setTimeout(() => msgfield.style.display = "none", MSG_TIME);
}, MSG_TIME2);
}
// Transform a datum to make it usable on the map. TODO consider having the BE do this.
var transform = function (item) {
var poly = [];
for (var c in item.polygon) {
var x = item.polygon[c][0]["$numberDouble"];
var y = item.polygon[c][1]["$numberDouble"];
if (x && y) { poly.push([x, y]) }
}
item.polygon = poly;
item.latitude = Number(item.latitude["$numberDouble"]);
item.longitude = Number(item.longitude["$numberDouble"]);
return item
} // End of transform(1)
// This function gets run when everything else is ready.
var render = function (view, data, store, notification) {
// Store => data is raw, so transform it.
if (store) { data = data.map(item => transform(item)) }
// Add the polygon for each alert to the map.
for (var j in data) {
if (data[j].longitude && data[j].latitude) {
view.graphics.add(polygon(data[j].polygon));
for (var u in data[j].geocode) { alertUGCs.push(data[j].geocode[u]) }
}
}
// Notify the user of how far they are from any alert.
// This will be more useful once geolocation is enabled.
showNotification(notification);
// If the data came in raw and was transformed, store it.
if (store) { localStorage.setItem(STORAGE_KEY, JSON.stringify(data)) }
} // End of render(3)
// Funct to find the nearest alert (roughly).
var findNearestProblem = function (data) {
var nearest = EARTH_R, item = null;
// Distance between two [lat, long] pairs. Read to risk a headache.
function dist(p1, p2) {
if (!p2.longitude || !p2.latitude) { return EARTH_R }
var sqrt = Math["sqrt"], s = Math["sin"], c = Math["cos"], atan2 = Math["atan2"];
var dlat = p2.latitude - p1.latitude, dlon = p2.longitude - p1.longitude;
var a = (s(dlat / 2)) * (s(dlat / 2)) + c(p1.latitude) * c(p2.latitude) * (s(dlon / 2)) * (s(dlon / 2));
var c = 2 * atan2(sqrt(a), sqrt(1 - a));
return EARTH_R * c;
}
// Go through each alert to find the closest. TODO replace DEF with user's.
for (var i in data) {
if ((d = dist({ longitude: DEFAULT_LOC[0], latitude: DEFAULT_LOC[1] }, data[i])) < nearest) {
nearest = d;
item = data[i];
}
}
// Return the nearest point and its distance.
return { distance: nearest, data: item };
} // End of findNearestProblem(1)
/* Get everything running once all data is read (e.g. location is determined). */
var start = function () {
// Generate and set up the map base
var view = new MapView({
container: CONTAINER,
map: new Map(MAP_TYPE),
center: coords,
zoom: DEFAULT_ZOOM
});
view.ui.add(new Search({view:view}), {
position: "top-right",
index: 2
});
filterButton.addEventListener("click", function() {
var upperRightBound = webMercatorUtils.xyToLngLat(view.extent.xmax, view.extent.ymax);
var lowerLeftBound = webMercatorUtils.xyToLngLat(view.extent.xmin, view.extent.ymin);
var queryParams = "?minlong=" + lowerLeftBound[0] + "&minlat=" + lowerLeftBound[1] + "&maxlong=" + upperRightBound[0] + "&maxlat=" + upperRightBound[1] + (filter.value && filter.value != "" ? "&agency=" + filter.value : "");
var req = new XMLHttpRequest();
req.addEventListener("load", drawContracts);
req.open("GET", CONTRACTS_DATA_URL + queryParams);
req.send();
});
function drawContracts() {
//Removes any preexisting graphics on the board that are points;
pointsCollection.forEach(graphic => {
view.graphics.remove(graphic);
});
var pointsMap = {};
var isImpacted = ugc => alertUGCs.indexOf(ugc);
var contractData = JSON.parse(this.response);
contractData.forEach(contract => {
var contractPointKey = contract.primaryPlaceOfPerformanceLng + ', ' + contract.primaryPlaceOfPerformanceLat;
if (!pointsMap[contractPointKey]) {
pointsMap[contractPointKey] = {};
pointsMap[contractPointKey].point = new Point({
longitude: contract.primaryPlaceOfPerformanceLng,
latitude: contract.primaryPlaceOfPerformanceLat,
});
pointsMap[contractPointKey].content = []
}
pointsMap[contractPointKey].impacted = isImpacted(contract.primaryPlaceOfPerformanceUgc[0]) != -1;
pointsMap[contractPointKey].content.push({
type: 'text',
text: 'Agency: ' + contract.awardingAgencyName + '<br/>' +
'Contractor: ' + contract.recipient.name + '<br/>' +
'Total Contract Value: ' + contract.currentTotalValueOfAward
});
});
for (var contractPointKey in pointsMap) {
var markerSymbol = new Marker({
color: (pointsMap[contractPointKey].impacted ? PT_FILL_ALERT : [119, 226, 40, 0.4]),
outline: {
color: [255, 255, 255],
width: 2
},
size: view.zoom * view.zoom * (view.zoom/10)
});
var contractPointGraphic = new Graphic({
geometry: pointsMap[contractPointKey].point,
symbol: markerSymbol,
popupTemplate: {
content: pointsMap[contractPointKey].content
}
});
view.graphics.add(contractPointGraphic);
pointsCollection.push(contractPointGraphic);
}
}
watchUtils.whenTrue(view, "stationary", function () {
if (view.extent) {
var upperRightBound = webMercatorUtils.xyToLngLat(view.extent.xmax, view.extent.ymax);
var lowerLeftBound = webMercatorUtils.xyToLngLat(view.extent.xmin, view.extent.ymin);
var queryParams = "?minlong=" + lowerLeftBound[0] + "&minlat=" + lowerLeftBound[1] + "&maxlong=" + upperRightBound[0] + "&maxlat=" + upperRightBound[1] + (filter.value && filter.value != "" ? "&agency=" + filter.value : "");
var req = new XMLHttpRequest();
req.addEventListener("load", drawContracts);
req.open("GET", CONTRACTS_DATA_URL + queryParams);
req.send();
}
});
// Check if we have data on the client. TODO: check if it's new-ish.
var storedData = localStorage.getItem(STORAGE_KEY);
// If we don't have local data, load it from Stitch.
if (!storedData) {
console.log("Fetching data.");
var req = new XMLHttpRequest();
req.addEventListener("load", finish);
req.open("GET", WEATHER_DATA_URL);
req.send();
var n = findNearestProblem(storedData);
function finish() { render(view, JSON.parse(req.response), true, DISTANCE_ALERT(n)) }
}
// If we do have local data, grab it.
else {
console.log("Loading cached data.");
storedData = JSON.parse(storedData);
var n = findNearestProblem(storedData);
render(view, storedData, false, DISTANCE_ALERT(n));
}
} // End of start()
var coords = DEFAULT_LOC;
var geoStart = pos => { coords = [pos.coords.longitude, pos.coords.latitude]; start() }
var geoFailStart = err => { console.log(GEO_FAIL_ALERT(err)); start() }
// Attempt to get user location. Launch map either way.
var run = function () {
if ("geolocation" in navigator) {
navigator.geolocation.getCurrentPosition(
position => geoStart(position),
err => geoFailStart(err),
GEO_TIMEOUT
);
}
else { start() }
} // End of run()
window.addEventListener("load", run)
} // End of ArgGIS handler function.
require(ARCGIS_LIBS, mapify);
// Respond to clear button by clearing local storage.
document.getElementById("clear").addEventListener("click", clear);
function clear() { localStorage.removeItem(STORAGE_KEY) }