tracman-server/static/js/map.js

469 lines
14 KiB
JavaScript
Executable File

'use strict'
/* global alert io google $ mapuser userid disp noHeader mapKey navigator token */
// Variables
let map, marker, elevator, newLoc
const mapElem = document.getElementById('map')
const socket = io('//' + window.location.hostname)
const IDLE_TIMEOUT = 300 // 5 minutes in seconds
let _idleSecondsCounter = 0
// Idle timeout listeners
function resetIdleSecondsCounter () {
_idleSecondsCounter = 0
}
document.onclick = resetIdleSecondsCounter
document.onmousemove = resetIdleSecondsCounter
document.onkeypress = resetIdleSecondsCounter
// Disconnect socket.io if user is idle for longer than IDLE_TIMEOUT seconds
window.setInterval( function CheckIdleTime () {
_idleSecondsCounter++
// Disconnect idle user if still connected
if (_idleSecondsCounter >= IDLE_TIMEOUT) {
if (socket.connected) {
console.log('Disconnecting because idle for more than',IDLE_TIMEOUT,'seconds.')
$('#inactive-mask').show()
$('#inactive-message').show()
socket.disconnect()
}
// Connect user if disconnected
} else {
if (!socket.connected) {
console.log('Reconnecting the user because they are no longer idle.')
$('#inactive-mask').hide()
$('#inactive-message').hide()
socket.connect()
}
}
}, 1000)
// Convert to feet if needed
function metersToFeet (meters) {
//console.log('metersToFeet('+meters+')')
return (mapuser.settings.units === 'standard') ? (meters * 3.28084).toFixed() : meters.toFixed()
}
// socket.io stuff
socket
.on('connect', function () {
console.log('Connected!')
// Can get location
socket.emit('can-get', mapuser._id)
// Can set location too
if (mapuser._id === userid) socket.emit('can-set', userid)
}).on('disconnect', function () {
console.log('Disconnected!')
}).on('error', function (err) {
console.error(err.stack)
})
// Show/hide map if location is set/unset
function toggleMaps (loc) {
if (loc.lat === 0 && loc.lon === 0) {
$('#map').hide()
$('#view').hide()
$('#notset').show()
} else {
$('#map').show()
$('#view').show()
$('#notset').hide()
}
}
// On page load
$(function () {
toggleMaps(mapuser.last)
// Controls
let wpid, newloc
// Set location
$('#set-loc').click(function () {
// Check if logged in and enabled
if (!userid === mapuser._id) alert('You are not logged in! '); else {
if (!navigator.geolocation) alert('Geolocation not enabled. '); else {
navigator.geolocation.getCurrentPosition(
// Success callback
function (pos) {
let newloc = {
ts: Date.now(),
tok: token,
usr: userid,
alt: pos.coords.altitude,
lat: pos.coords.latitude,
lon: pos.coords.longitude,
spd: (pos.coords.speed || 0)
}
socket.emit('set', newloc)
toggleMaps(newloc)
console.log('Set location:', newloc.lat + ', ' + newloc.lon)
},
// Error callback
function (err) {
alert('Unable to set location.')
console.error(err.stack)
},
// Options
{ enableHighAccuracy: true }
)
}
}
})
// Track location
$('#track-loc').click(function () {
// Check for login
if (!userid === mapuser._id) alert('You are not logged in! '); else {
// Start tracking
if (!wpid) {
if (!navigator.geolocation) alert('Unable to track location. '); else {
$('#track-loc').html(
'<i class="fa fa-crosshairs fa-spin"></i>Stop'
).prop('title',
'Click here to stop tracking your location. '
)
wpid = navigator.geolocation.watchPosition(
// Success callback
function (pos) {
newloc = {
ts: Date.now(),
tok: token,
usr: userid,
lat: pos.coords.latitude,
lon: pos.coords.longitude,
alt: pos.coords.altitude,
spd: (pos.coords.speed || 0)
}
socket.emit('set', newloc)
toggleMaps(newloc)
console.log('Set location:', newloc.lat + ', ' + newloc.lon)
},
// Error callback
function (err) {
alert('Unable to track location.')
console.error(err.stack)
},
// Options
{ enableHighAccuracy: true }
)
}
// Stop tracking
} else {
$('#track-loc').html('<i class="fa fa-crosshairs"></i>Track').prop('title', 'Click here to track your location. ')
navigator.geolocation.clearWatch(wpid)
wpid = undefined
}
}
})
// Clear location
$('#clear-loc').click(function () {
if (!userid === mapuser._id) alert('You are not logged in! '); else {
// Stop tracking
if (wpid) {
$('#track-loc').html('<i class="fa fa-crosshairs"></i>Track')
navigator.geolocation.clearWatch(wpid)
wpid = undefined
}
// Clear location
newloc = {
ts: Date.now(),
tok: token,
usr: userid,
lat: 0,
lon: 0,
spd: 0
}; socket.emit('set', newloc)
// Turn off map
toggleMaps(newloc)
console.log('Cleared location')
}
})
})
// Load google maps API
function initMap() {
// Create map
if (disp !== '1') {
// Create map and marker elements
map = new google.maps.Map(mapElem, {
center: {
lat: mapuser.last.lat,
lng: mapuser.last.lon
},
gestureHandling: 'auto', // Allows use of scroll wheel
panControl: false,
scrollwheel: true,
scaleControl: !!(mapuser.settings.showScale),
draggable: false,
zoom: mapuser.settings.defaultZoom,
streetViewControl: false,
zoomControlOptions: {position: google.maps.ControlPosition.LEFT_TOP},
mapTypeId: (mapuser.settings.defaultMap === 'road') ? google.maps.MapTypeId.ROADMAP : google.maps.MapTypeId.HYBRID
})
marker = new google.maps.Marker({
position: { lat: mapuser.last.lat, lng: mapuser.last.lon },
title: mapuser.name,
icon: (mapuser.settings.marker) ? '/static/img/marker/' + mapuser.settings.marker + '.png' : '/static/img/marker/red.png',
map: map,
draggable: false
})
map.addListener('zoom_changed', function () {
map.setCenter(marker.getPosition())
})
// Create iFrame logo
if (noHeader !== '0' && mapuser._id !== 'demo') {
const logoDiv = document.createElement('div')
logoDiv.id = 'map-logo'
logoDiv.innerHTML = '<a href="https://www.tracman.org/">' +
'<img src="https://www.tracman.org/static/img/style/logo-28.png" alt="[]">' +
"<span class='text'>Tracman</span></a>"
map.controls[google.maps.ControlPosition.BOTTOM_LEFT].push(logoDiv)
}
// Create update time block
const timeDiv = document.createElement('div')
timeDiv.id = 'timestamp'
if (mapuser.last.time) {
timeDiv.innerHTML = 'location updated ' + new Date(mapuser.last.time).toLocaleString()
}
map.controls[google.maps.ControlPosition.RIGHT_BOTTOM].push(timeDiv)
// Create speed block
if (mapuser.settings.showSpeed) {
const speedSign = document.createElement('div')
const speedLabel = document.createElement('div')
const speedText = document.createElement('div')
const speedUnit = document.createElement('div')
speedLabel.id = 'spd-label'
speedLabel.innerHTML = 'SPEED'
speedText.id = 'spd'
speedText.innerHTML = (mapuser.settings.units === 'standard') ? (parseFloat(mapuser.last.spd) * 2.23694).toFixed() : mapuser.last.spd.toFixed()
speedUnit.id = 'spd-unit'
speedUnit.innerHTML = (mapuser.settings.units === 'standard') ? 'm.p.h.' : 'k.p.h.'
speedSign.id = 'spd-sign'
speedSign.appendChild(speedLabel)
speedSign.appendChild(speedText)
speedSign.appendChild(speedUnit)
map.controls[google.maps.ControlPosition.TOP_RIGHT].push(speedSign)
}
// Create altitude block
if (mapuser.settings.showAlt) {
elevator = new google.maps.ElevationService()
const altitudeSign = document.createElement('div')
const altitudeLabel = document.createElement('div')
const altitudeText = document.createElement('div')
const altitudeUnit = document.createElement('div')
altitudeLabel.id = 'alt-label'
altitudeText.id = 'alt'
altitudeUnit.id = 'alt-unit'
altitudeSign.id = 'alt-sign'
altitudeText.innerHTML = ''
altitudeLabel.innerHTML = 'ALTITUDE'
parseAlt(mapuser.last).then(function (alt) {
altitudeText.innerHTML = metersToFeet(alt)
}).catch(function (err) {
console.error('Could not load altitude from last known location: ', err)
})
altitudeUnit.innerHTML = (mapuser.settings.units === 'standard') ? 'feet' : 'meters'
altitudeSign.appendChild(altitudeLabel)
altitudeSign.appendChild(altitudeText)
altitudeSign.appendChild(altitudeUnit)
map.controls[google.maps.ControlPosition.TOP_RIGHT].push(altitudeSign)
}
}
// Create streetview
if (disp !== '0' && mapuser.settings.showStreetview) {
updateStreetView(parseLoc(mapuser.last), 10)
}
// Get altitude from Google API
function getAlt (loc) {
return new Promise(function (resolve, reject) {
// Get elevator service
elevator = elevator || new google.maps.ElevationService()
// Query API
return elevator.getElevationForLocations({
'locations': [{ lat: loc.lat, lng: loc.lon }]
}, function (results, status, errorMessage) {
// Success; return altitude
if (status === google.maps.ElevationStatus.OK && results[0]) {
console.log('Altitude was retrieved from Google Elevations API as', results[0].elevation, 'm')
resolve(results[0].elevation)
// Unable to get any altitude
} else reject(Error(errorMessage))
})
})
}
// Parse altitude
function parseAlt (loc) {
// console.log('parseAlt('+loc+'})')
return new Promise(function (resolve, reject) {
// Check if altitude was provided
if (typeof loc.alt === 'number') {
console.log('Altitude was provided in loc as ', loc.alt, 'm')
resolve(loc.alt)
// No altitude provided
} else {
console.log('No altitude was provided in loc')
// Query google altitude API
getAlt(loc).then(function (alt) {
resolve(alt)
}).catch(function (err) { reject(err) })
}
})
}
// Parse location
function parseLoc (loc) {
loc.spd = (mapuser.settings.units === 'standard') ? parseFloat(loc.spd) * 2.23694 : parseFloat(loc.spd)
loc.dir = parseFloat(loc.dir)
loc.lat = parseFloat(loc.lat)
loc.lon = parseFloat(loc.lon)
// loc.alt = parseAlt(loc);
loc.tim = new Date(loc.tim).toLocaleString()
return loc
}
// Got location
socket.on('get', function (loc) {
console.log('Got location:', loc.lat + ', ' + loc.lon)
// Parse location
newLoc = parseLoc(loc)
// Update map
if (disp !== '1') {
// console.log('Updating map...')
// Update time
$('#timestamp').text('location updated ' + newLoc.tim)
// Update marker and map center
google.maps.event.trigger(map, 'resize')
map.setCenter({ lat: newLoc.lat, lng: newLoc.lon })
marker.setPosition({ lat: newLoc.lat, lng: newLoc.lon })
// Update speed
if (mapuser.settings.showSpeed) $('#spd').text(newLoc.spd.toFixed())
// Update altitude
if (mapuser.settings.showAlt) {
// console.log('updating altitude...');
parseAlt(loc).then(function (alt) {
$('#alt').text(metersToFeet(alt))
}).catch(function (err) {
$('#alt').text('????')
console.error(err.stack)
})
}
}
// Update street view
if (disp !== '0' && mapuser.settings.showStreetview) updateStreetView(newLoc, 10)
})
// Get street view imagery
function getStreetViewData (loc, rad, cb) {
// Ensure that the location hasn't changed (or this is the initial setting)
if (newLoc == null || loc.tim === newLoc.tim) {
if (!sv) var sv = new google.maps.StreetViewService()
sv.getPanorama({
location: {
lat: loc.lat,
lng: loc.lon
},
radius: rad
}, function (data, status) {
switch (status) {
// Success
case google.maps.StreetViewStatus.OK: {
cb(data)
break
// No results in that radius
} case google.maps.StreetViewStatus.ZERO_RESULTS: {
// Try again with a bigger radius
getStreetViewData(loc, rad * 2, cb)
break
// Error
} default:
console.error(new Error('Street view not available: ' + status).message)
}
})
}
}
// Update streetview
function updateStreetView (loc) {
// Calculate bearing between user and position of streetview image
// https://stackoverflow.com/a/26609687/3006854
function getBearing (userLoc, imageLoc) {
return 90 - (
Math.atan2(userLoc.lat - imageLoc.latLng.lat(), userLoc.lon - imageLoc.latLng.lng()) *
(180 / Math.PI)) % 360
}
// Get dimensions for sv request (images proportional to element up to 640x640)
function getDimensions (element) {
// Window is smaller than max
if (element.width() < 640 && element.height() < 640) {
return element.width().toFixed() + 'x' + element.height().toFixed()
// Width must be made proportional to 640
} else if (element.width() > element.height()) {
return '640x' + (element.height() * 640 / element.width()).toFixed()
// Height must be made proportional to 640
} else return (element.width() * 640 / element.height()).toFixed() + 'x640'
}
// Set image
getStreetViewData(loc, 2, function (data) {
$('#viewImg').attr('src', 'https://maps.googleapis.com/maps/api/streetview?' +
'size=' + getDimensions($('#view')) +
'&location=' + data.location.latLng.lat() + ',' + data.location.latLng.lng() +
'&fov=90' + // Inclination
// Show direction if moving, point to user if stationary
'&heading=' + ((loc.spd > 2) ? loc.dir : String(getBearing(loc, data.location))) +
'&key=' + mapKey
)
})
}
// Error loading gmaps API
}