summaryrefslogtreecommitdiff
path: root/dashboard_website/static/js
diff options
context:
space:
mode:
Diffstat (limited to 'dashboard_website/static/js')
-rw-r--r--dashboard_website/static/js/dashboard.js210
-rw-r--r--dashboard_website/static/js/map.js6
-rw-r--r--dashboard_website/static/js/utils.js18
3 files changed, 228 insertions, 6 deletions
diff --git a/dashboard_website/static/js/dashboard.js b/dashboard_website/static/js/dashboard.js
new file mode 100644
index 0000000..b54f65f
--- /dev/null
+++ b/dashboard_website/static/js/dashboard.js
@@ -0,0 +1,210 @@
+// uses functions in utils.js
+var host = window.location.protocol + "//" + window.location.host;
+
+// "global" map variables
+var map;
+var previewmap;
+
+var visited_clues_m = L.layerGroup([]); // all clues
+var unvisited_clues_m = L.layerGroup([]); // subset of all clues - unvisited clues
+var destination_clues_m = L.layerGroup([]); // clues bikers are currently destined for
+var bikes_m = L.layerGroup([]); // bike markers
+
+var homemarker, homebase, clues, clue_rels, bikes, previewmarker;
+
+var latest_timestamp = -1; // initially -1, otherwise set to value given by server in last successful info update
+
+var baseIcon = L.Icon.extend({
+ options: {
+ iconSize: [25, 41], // size of the icon
+ iconAnchor: [12, 41], // point of the icon which will correspond to marker's location
+ popupAnchor: [0, -41] // point from which the popup should open relative to the iconAnchor
+ }
+})
+var homeIcon = new baseIcon({iconUrl: 'static/img/marker-home.png'}),
+ activeBikeIcon = new baseIcon({iconUrl: 'static/img/marker-bike-active.png', className:"leaflet-bike-marker"}),
+ inactiveBikeIcon = new baseIcon({iconUrl: 'static/img/marker-bike-inactive.png'}),
+ visitedIcon = new baseIcon({iconUrl: 'static/img/marker-icon-grey.png'}),
+ unvisitedIcon = new baseIcon({iconUrl: 'static/img/marker-icon-blue.png'}), // generic, becomes colored when assigned to route
+ orangeIcon = new baseIcon({iconUrl: 'static/img/marker-icon-orange.png'}),
+ redIcon = new baseIcon({iconUrl: 'static/img/marker-icon-red.png'}),
+ greenIcon = new baseIcon({iconUrl: 'static/img/marker-icon-green.png'}),
+ yellowIcon = new baseIcon({iconUrl: 'static/img/marker-icon-yellow.png'});
+var bikeIcons = {'ACTIVE' : activeBikeIcon, 'INACTIVE' : inactiveBikeIcon}
+
+function zoomToBike(team_name){
+ map.panTo(bikes[team_name]['marker'].getLatLng());
+}
+function zoomToClue(clue_name){
+ map.panTo(clue_rels[clue_name].getLatLng());
+}
+function previewZoom(){
+ var long = parseFloat(document.getElementById("new_clue_longitude").value);
+ var lat = parseFloat(document.getElementById("new_clue_latitude").value);
+ console.log(long);
+ console.log(document.getElementById("new_clue_longitude").value);
+ if (!isNaN(long) && !isNaN(lat)){
+ previewmarker.setLatLng([lat, long]);
+ previewmap.panTo(previewmarker.getLatLng());
+ }
+}
+
+function drawRoute(route_coords_osrm, team_color) {
+ //osrm lat/long are swapped
+ for (var i = 0; i < route_coords_osrm.length; i++){
+ var t = route_coords_osrm[i][1];
+ route_coords_osrm[i][1] = route_coords_osrm[i][0];
+ route_coords_osrm[i][0] = t;
+ }
+ var route = new L.polyline(route_coords_osrm, {color: team_color}).addTo(map);
+}
+
+function updateBikeStatus(){
+ var table = document.getElementById("bike-teams-table");
+ table.innerHTML = '';
+ for (const [key, value] of Object.entries(bikes)) {
+ var name = key;
+ var bike = value;
+ var row = document.createElement("tr");
+ var namecell = document.createElement("td"); namecell.innerHTML = "<a href=\"#\" onclick=\"zoomToBike('"+name+"')\">"+name+"</a>";
+ var statuscell = document.createElement("td"); statuscell.innerHTML = "<span "+((bike['team_status'] == "ACTIVE")? "style=\"color:lightgreen\"" : "") +">" + bike['team_status'] + " ("+parseInt(bike['time_since_last_contact']).toString()+"s)</span>";
+ var targetcell = document.createElement("td"); targetcell.innerHTML = "<a href=\"#\" onclick=\"zoomToClue('"+bike['target_clue']+"')\">"+bike['target_clue']+"</a>";
+
+ row.appendChild(namecell);
+ row.appendChild(statuscell);
+ row.appendChild(targetcell);
+ table.appendChild(row);
+ }
+}
+
+function updateClueStats(){
+ document.getElementById("total_count").innerText = clues.length;
+ var visited_count = 0;
+ var avg_distance = 0;
+ for (var i =0; i < clues.length; i++){
+ if(clues[i]['clue_status'] == "VISITED"){
+ visited_count++;
+ avg_distance += getDistanceFromLatLon(homebase, clues[i]);
+ }
+ }
+ avg_distance /= visited_count;
+ document.getElementById("unvisited_count").innerText = clues.length-visited_count;
+ document.getElementById("visited_count").innerText = visited_count;
+ document.getElementById("percent_visited").innerText = (100*(visited_count/clues.length)).toFixed(2);
+ document.getElementById("avg_visited_distance").innerText = avg_distance.toFixed(2);
+}
+
+function drawClues(){
+ unvisited_clues_m.clearLayers();
+ visited_clues_m.clearLayers();
+ for (var i = 0; i < clues.length; i++) {
+ var tempIcon = visitedIcon;
+ if (clues[i]['clue_status'] == "UNVISITED") tempIcon = unvisitedIcon;
+ var clueMarker = L.marker([clues[i]['latitude'], clues[i]['longitude']], {icon: tempIcon}).bindPopup(clues[i]['clue_name'] + ": " + clues[i]['clue_info']);
+ clue_rels[clues[i]['clue_name']] = clueMarker;
+ if (clues[i]['clue_status'] == "UNVISITED") unvisited_clues_m.addLayer(clueMarker);
+ else visited_clues_m.addLayer(clueMarker);
+ }
+}
+
+function addClue(){
+ var long = parseFloat(document.getElementById("new_clue_longitude").value);
+ var lat = parseFloat(document.getElementById("new_clue_latitude").value);
+ if (isNaN(long) || isNaN(lat)){
+ alert("Invalid coordinates.");
+ return;
+ }
+ if(confirm("Are you sure this is the right location?")){
+ console.log("yes");
+ }
+}
+
+// run every x seconds to get latest info from server
+function requestLatestInfo(){
+ function handleLatestInfo(json){
+ if(json['status'] != "OK") return;
+ latest_timestamp = json['timestamp'];
+ // process home base
+ if (json['home_changed']){
+ homebase = json['homebase'];
+ if(homemarker)homemarker.setLatLng([homebase['latitude'], homebase['longitude']]);
+ else homemarker = L.marker([homebase['latitude'], homebase['longitude']], {icon: homeIcon}).addTo(map).bindPopup("Home is where the club is.");
+ }
+ // process bikes
+ if(true || json['bikes_changed']){ // always true since we need constant updates for bikes
+ var bikes_t = json['bikes'];
+ for (var i = 0; i < bikes_t.length; i++){
+ var name = bikes_t[i]['team_name'];
+ if(name in bikes) {
+ bikes[name]['marker'].setLatLng([bikes_t[i]['latitude'],bikes_t[i]['longitude']]);
+ if(bikes_t[i]['team_status'] != [name]['team_status'])bikes[name]['marker'].setIcon(bikeIcons[bikes_t[i]['team_status']]);
+ for (const [key, value] of Object.entries(bikes_t[i])) {
+ bikes[name][key] = value;
+ }
+ } else { // add bike
+ var bikeMarker = new L.marker([bikes_t[i]['latitude'],bikes_t[i]['longitude']]).bindPopup(bikes_t[i]['team_name']);
+ bikes[name] = bikes_t[i];
+ bikes[name]['marker'] = bikeMarker;
+ bikeMarker.setIcon(bikeIcons[bikes[name]['team_status']]);
+ bikes_m.addLayer(bikeMarker);
+ }
+ }
+ updateBikeStatus();
+ }
+ // process clues
+ if(json['clues_changed']){
+ clues = json['clues'];
+ clue_rels = {};
+ drawClues();
+ updateClueStats();
+ }
+ }
+ fetch(host+'/getLatestInfo', {
+ method: "POST",
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({ "info_age" : latest_timestamp })})
+ .then((response) => response.json())
+ .then((json) => handleLatestInfo(json));
+}
+var intervalId = window.setInterval(function(){
+ requestLatestInfo();
+ }, 5000);
+
+var clockINterval = window.setInterval(function(){
+ var d = new Date();
+ document.getElementById("titletime").innerText = d.toLocaleString();
+}, 1000);
+
+// RUN ON PAGE LOAD
+window.onload = function() {
+ clues = {}; bikes = {};
+
+ map = L.map('map').setView([42.3626081,-71.0620591], 13);
+ L.tileLayer('https://tiles.stadiamaps.com/tiles/alidade_smooth_dark/{z}/{x}/{y}{r}.{ext}', {
+ minZoom: 0,
+ maxZoom: 20,
+ attribution: '&copy; <a href="https://www.stadiamaps.com/" target="_blank">Stadia Maps</a> &copy; <a href="https://openmaptiles.org/" target="_blank">OpenMapTiles</a> &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
+ ext: 'png'
+ }).addTo(map);
+ previewmap = L.map('previewmap').setView([42.3626081,-71.0620591], 16);
+ L.tileLayer('https://tiles.stadiamaps.com/tiles/alidade_smooth_dark/{z}/{x}/{y}{r}.{ext}', {
+ minZoom: 0,
+ maxZoom: 20,
+ ext: 'png'
+ }).addTo(previewmap);
+
+ //homemarker = L.marker([homebase['latitude'], homebase['longitude']], {icon: homeIcon}).addTo(map).bindPopup("Home is where the club is.");
+ previewmarker = L.marker([0,0], {icon: greenIcon}).addTo(previewmap);
+ map.addLayer(visited_clues_m);
+ map.addLayer(unvisited_clues_m);
+ map.addLayer(bikes_m);
+ var layerControl = L.control.layers(null,null,{collapsed:false});
+ layerControl.addOverlay(unvisited_clues_m, "Unvisited Clues");
+ layerControl.addOverlay(visited_clues_m, "Visited Clues");
+ layerControl.addOverlay(bikes_m, "Bikes");
+ layerControl.addTo(map);
+ requestLatestInfo();
+} \ No newline at end of file
diff --git a/dashboard_website/static/js/map.js b/dashboard_website/static/js/map.js
deleted file mode 100644
index 35eb70a..0000000
--- a/dashboard_website/static/js/map.js
+++ /dev/null
@@ -1,6 +0,0 @@
-
-var all_clues = L.layerGroup([]); // all clues
-var unvisited_clues = L.layerGroup([]); // subset of all clues - unvisited clues
-var destination_clues = L.layerGroup([]); // clues bikers are currently destined for
-
-document.onload() \ No newline at end of file
diff --git a/dashboard_website/static/js/utils.js b/dashboard_website/static/js/utils.js
new file mode 100644
index 0000000..83e6397
--- /dev/null
+++ b/dashboard_website/static/js/utils.js
@@ -0,0 +1,18 @@
+function deg2rad(deg) {
+ return deg * (Math.PI/180)
+}
+function getDistanceFromLatLon(item1, item2) {
+ lat1 = item1['latitude']; lon1 = item1['longitude'];
+ lat2 = item2['latitude']; lon2 = item2['longitude'];
+ var R = 3958.8; // Radius of the earth
+ var dLat = deg2rad(lat2-lat1);
+ var dLon = deg2rad(lon2-lon1);
+ var a =
+ Math.sin(dLat/2) * Math.sin(dLat/2) +
+ Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) *
+ Math.sin(dLon/2) * Math.sin(dLon/2)
+ ;
+ var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
+ var d = R * c; // Distance in mi
+ return d;
+} \ No newline at end of file