// 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 routes_m = L.layerGroup([]); // polyline routes
var homemarker, homebase, clues, clue_rels, bikes, routes, 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});
routes_m.addLayer(route);
}
function drawRoutes() {
routes_m.clearLayers();
for (var i = 0; i < routes['clusters'].length; i++){
if(routes['clusters'][i].length > 0){
var color;
var r = Math.floor(Math.random() * 155+100);
var g = 0; // no greens -- avoid yellow
var b = Math.floor(Math.random() * 155+100);
color= "rgb("+r+","+g+","+ b+")";
console.log(color);
drawRoute(routes['clusters'][i], color);
}
}
for (var i = 0; i < routes['individual_routes'].length; i++){
if(routes['individual_routes'][i].length > 0){
drawRoute(routes['individual_routes'][i], 'yellow');
}
}
}
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 = ""+name+"";
var statuscell = document.createElement("td"); statuscell.innerHTML = "" + bike['team_status'] + " ("+parseInt(bike['time_since_last_contact']).toString()+"s)";
var targetcell = document.createElement("td"); targetcell.innerHTML = ""+bike['target_clue']+"";
var etacell = document.createElement("td"); etacell.innerHTML = "ETA: "+routes['cluster_times'][key];
row.appendChild(namecell);
row.appendChild(statuscell);
row.appendChild(targetcell);
row.appendChild(etacell);
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 routes
if(json['routes_changed']){
routes = json['routes'];
console.log(routes);
drawRoutes();
}
// process bikes
if(true || json['bikes_changed']){ // always true since we need constant updates for bikes
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);
}
}
// remove no longer existing bikes
for (const [key, value] of Object.entries(bikes)) {
var deleteBike = true;
for(var i = 0; i < bikes_t.length; i++){
if(bikes_t[i]['team_name'] == key) {deleteBike = false; }
}
if(deleteBike) {
console.log("deleting bike");
bikes_m.removeLayer(value['marker']);
delete bikes[key];
}
}
updateBikeStatus();
}
// process clues
if(json['clues_changed']){
clues = json['clues'];
clue_rels = {};
drawClues();
updateClueStats();
}
document.getElementById("routeinfo").innerText = json['calculating_routes'] ? "ROUTE INFO | (Calculating...)" : "ROUTE INFO";
}
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();
}, 3000);
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 = {}; routes = {};
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}?api_key=24746ff5-179d-482f-97a8-0267c5b276a1', {
minZoom: 0,
maxZoom: 20,
attribution: '© Stadia Maps © OpenMapTiles © OpenStreetMap 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}?api_key=24746ff5-179d-482f-97a8-0267c5b276a1', {
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);
map.addLayer(routes_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();
}