kopia lustrzana https://github.com/projecthorus/chasemapper
929 wiersze
40 KiB
HTML
929 wiersze
40 KiB
HTML
<!DOCTYPE HTML>
|
|
<html>
|
|
<head>
|
|
<title>Project Horus Chase Mapper</title>
|
|
|
|
<link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">
|
|
<link href="{{ url_for('static', filename='css/leaflet.css') }}" rel="stylesheet">
|
|
<link href="{{ url_for('static', filename='css/leaflet-sidebar.min.css') }}" rel="stylesheet">
|
|
<link href="{{ url_for('static', filename='css/leaflet-control-topcenter.css') }}" rel="stylesheet">
|
|
<link href="{{ url_for('static', filename='css/Leaflet.PolylineMeasure.css') }}" rel="stylesheet">
|
|
<link href="{{ url_for('static', filename='css/leaflet-routing-machine.css') }}" rel="stylesheet">
|
|
<link href="{{ url_for('static', filename='css/easy-button.css') }}" rel="stylesheet">
|
|
<link href="{{ url_for('static', filename='css/tabulator_simple.css') }}" rel="stylesheet">
|
|
<link href="{{ url_for('static', filename='css/font-awesome.min.css') }}" rel="stylesheet">
|
|
<link href="{{ url_for('static', filename='css/jquery-ui.css') }}" rel="stylesheet">
|
|
<link href="{{ url_for('static', filename='css/chasemapper.css') }}" rel="stylesheet">
|
|
|
|
|
|
<!-- I should probably feel bad for using so many libraries, but apparently this is the way thing are done :-/ -->
|
|
<script src="{{ url_for('static', filename='js/jquery-3.3.1.min.js')}}"></script>
|
|
<script src="{{ url_for('static', filename='js/jquery-ui.min.js')}}"></script>
|
|
<script src="{{ url_for('static', filename='js/socket.io.min.js') }}"></script>
|
|
<script src="{{ url_for('static', filename='js/leaflet.js') }}"></script>
|
|
<!-- Leaflet plugins... -->
|
|
<script src="{{ url_for('static', filename='js/leaflet.rotatedMarker.js') }}"></script>
|
|
<script src="{{ url_for('static', filename='js/leaflet-control-topcenter.js') }}"></script>
|
|
<script src="{{ url_for('static', filename='js/leaflet-sidebar.min.js') }}"></script>
|
|
<script src="{{ url_for('static', filename='js/Leaflet.Control.Custom.js') }}"></script>
|
|
<script src="{{ url_for('static', filename='js/Leaflet.PolylineMeasure.js') }}"></script>
|
|
<script src="{{ url_for('static', filename='js/easy-button.js') }}"></script>
|
|
|
|
<script src="{{ url_for('static', filename='js/tabulator.min.js') }}"></script>
|
|
|
|
<script src="{{ url_for('static', filename='js/d3.v3.min.js') }}"></script>
|
|
<script src="{{ url_for('static', filename='js/micropolar-v0.2.2.js') }}"></script>
|
|
|
|
<!-- Custom scripts -->
|
|
<script src="{{ url_for('static', filename='js/habitat.js') }}"></script>
|
|
<script src="{{ url_for('static', filename='js/sondehub.js') }}"></script>
|
|
<script src="{{ url_for('static', filename='js/utils.js') }}"></script>
|
|
<script src="{{ url_for('static', filename='js/tables.js') }}"></script>
|
|
<script src="{{ url_for('static', filename='js/settings.js') }}"></script>
|
|
<script src="{{ url_for('static', filename='js/balloon.js') }}"></script>
|
|
<script src="{{ url_for('static', filename='js/predictions.js') }}"></script>
|
|
<script src="{{ url_for('static', filename='js/car.js') }}"></script>
|
|
<script src="{{ url_for('static', filename='js/bearings.js') }}"></script>
|
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
|
|
|
<script type="text/javascript" charset="utf-8">
|
|
|
|
// Object which will contain balloon markers and traces.
|
|
// properties for each key in this object (key = callsign)
|
|
// latest_data - latest sonde telemetry object from SondeDecoded
|
|
// marker: Leaflet marker object.
|
|
// path: Leaflet polyline object.
|
|
// pred_marker: Leaflet marker for predicted landing position.
|
|
// pred_path: Leaflet polyline object for predicted path.
|
|
// burst_marker: Leaflet marker for burst prediction.
|
|
// abort_marker: Leaflet marker for abort landing prediction.
|
|
// abort_path: Leaflet marker for abort prediction track.
|
|
var balloon_positions = {};
|
|
var initial_load_complete = false;
|
|
|
|
// The sonde we are currently following on the map
|
|
var balloon_currently_following = "none";
|
|
|
|
// Other chase vehicles
|
|
var chase_vehicles = {};
|
|
// Only add chase cars which are (initially) within this range limit (km).
|
|
var vehicle_max_range = 200.0;
|
|
|
|
// Chase car position.
|
|
// properties will contain:
|
|
// latest_data: [lat,lon, alt] (latest car position)
|
|
// heading: Car heading (to point icon appropriately.)
|
|
// marker: Leaflet marker
|
|
var chase_car_position = {latest_data: [], heading:0, marker: 'NONE', path: 'NONE'};
|
|
|
|
// Home position marker
|
|
var home_marker = 'NONE';
|
|
|
|
// Data Age variables - these are updated regularly to indicate
|
|
// if we haven't received data in a while.
|
|
var payload_data_age = 0.0;
|
|
var car_data_age = 0.0;
|
|
var pred_data_age = 0.0;
|
|
|
|
// Other markers which may be added. (TBD, probably other chase car positions via the LoRa payload?)
|
|
var misc_markers = {};
|
|
|
|
// Habitat chase cars
|
|
var habitat_chase_cars = {};
|
|
|
|
// Functions defined later.
|
|
var setChaseCarTrack;
|
|
|
|
// Leaflet map instance.
|
|
var map;
|
|
// Routing Engine
|
|
var router;
|
|
|
|
|
|
// Socket.IO Settings
|
|
var namespace = '/chasemapper';
|
|
// Socket.IO instance.
|
|
var socket;
|
|
|
|
$(document).ready(function() {
|
|
|
|
// Connect to the Socket.IO server.
|
|
// The connection URL has the following format:
|
|
// http[s]://<domain>:<port>[/<namespace>]
|
|
socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + namespace);
|
|
|
|
|
|
// Grab the System config on startup.
|
|
// Refer to config.py for the contents of the configuration blob.
|
|
$.ajax({
|
|
url: "/get_config",
|
|
dataType: 'json',
|
|
async: false, // Yes, this is deprecated...
|
|
success: function(data) {
|
|
serverSettingsUpdate(data);
|
|
}
|
|
});
|
|
|
|
// Handler for further settings updates supplied by the server
|
|
// (i.e. when another client makes a change)
|
|
socket.on('server_settings_update', function(data){
|
|
serverSettingsUpdate(data);
|
|
});
|
|
|
|
// Add handlers for various text fields.
|
|
// Use the jquery on-changed call for text entry fields,
|
|
// so they only fire after they lose focus.
|
|
$("#burstAlt").change(function(){
|
|
clientSettingsUpdate();
|
|
});
|
|
$("#descentRate").change(function(){
|
|
clientSettingsUpdate();
|
|
});
|
|
$("#predUpdateRate").change(function(){
|
|
clientSettingsUpdate();
|
|
});
|
|
$("#habitatUpdateRate").change(function(){
|
|
clientSettingsUpdate();
|
|
});
|
|
$("#habitatCall").change(function(){
|
|
clientSettingsUpdate();
|
|
});
|
|
|
|
// Handlers for range ring settings.
|
|
$("#ringQuantity").change(function(){
|
|
updateRangeRings();
|
|
});
|
|
$("#ringSpacing").change(function(){
|
|
updateRangeRings();
|
|
});
|
|
$("#ringWeight").change(function(){
|
|
updateRangeRings();
|
|
});
|
|
$("#ringColorSelect").change(function(){
|
|
updateRangeRings();
|
|
});
|
|
$("#ringCustomColor").change(function(){
|
|
updateRangeRings();
|
|
});
|
|
|
|
// Handlers for bearing settings
|
|
$("#bearingWeight").change(function(){
|
|
updateBearingSettings();
|
|
});
|
|
$("#bearingLength").change(function(){
|
|
updateBearingSettings();
|
|
});
|
|
$("#bearingConfidenceThreshold").change(function(){
|
|
updateBearingSettings();
|
|
});
|
|
$("#bearingMaximumAge").change(function(){
|
|
updateBearingSettings();
|
|
});
|
|
$("#bearingMinOpacity").change(function(){
|
|
updateBearingSettings();
|
|
});
|
|
$("#bearingMaxOpacity").change(function(){
|
|
updateBearingSettings();
|
|
});
|
|
$("#bearingColorSelect").change(function(){
|
|
updateBearingSettings();
|
|
});
|
|
$("#bearingCustomColor").change(function(){
|
|
updateBearingSettings();
|
|
});
|
|
|
|
// Changes to the telemetry source profile selection works a bit differently, as we
|
|
// need to do a fair bit of work on the server when these occur. These have a dedicated
|
|
// socketio message.
|
|
$("#profileSelect").change(function(){
|
|
// On a profile selection change, emit a message to the client.
|
|
socket.emit('profile_change', this.value);
|
|
|
|
// Remove chase car if switching to a profile which does not have live position updates.
|
|
_profile = this.value;
|
|
reconfigureCarMarker(this.value);
|
|
});
|
|
|
|
// Handle arrival of new log data.
|
|
socket.on('log_event', function(msg) {
|
|
$('#log_data').prepend('<br>' + $('<div/>').text(msg.timestamp + " [" + msg.level + "]: " + msg.msg).html());
|
|
// Scroll to the bottom of the log table
|
|
$("#log_data").scrollTop($("#log_data")[0].scrollHeight);
|
|
});
|
|
|
|
//
|
|
// LEAFLET MAP SETUP
|
|
//
|
|
// Setup a basic Leaflet map
|
|
map = L.map('map').setView([chase_config.default_lat, chase_config.default_lon], 8);
|
|
|
|
// Add OSM Map Layer
|
|
var osm_map = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
|
}).addTo(map);
|
|
|
|
// Add OSM Topo Map Layer
|
|
var osm_topo_map = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
|
|
attribution: '© <a href="https://wiki.openstreetmap.org/wiki/OpenTopoMap">OpenTopoMap</a> contributors'
|
|
});
|
|
|
|
// Add ESRI Satellite Map layers.
|
|
var esrimapLink =
|
|
'<a href="http://www.esri.com/">Esri</a>';
|
|
var esriwholink =
|
|
'i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community';
|
|
var esri_sat_map = L.tileLayer(
|
|
'http://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
|
|
{
|
|
attribution: '© '+esrimapLink+', '+esriwholink,
|
|
maxZoom: 18,
|
|
});
|
|
// Dark Matter map layer, one of the maps suitable for 'dark mode'
|
|
var dark_matter_map = L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/dark_all/{z}/{x}/{y}.png', {
|
|
attribution: '© <a href="https://carto.com/help/building-maps/basemap-list/">Carto</a>'
|
|
});
|
|
|
|
|
|
|
|
var map_layers = {'OSM':osm_map, 'OpenTopo':osm_topo_map, 'ESRI Satellite':esri_sat_map, 'Dark Matter':dark_matter_map};
|
|
|
|
// Add ThunderForest layers, if we have a key provided.
|
|
if (chase_config.thunderforest_api_key !== 'none'){
|
|
// Thunderforest Outdoors layer.
|
|
var thunderforest_outdoors = L.tileLayer('https://tile.thunderforest.com/outdoors/{z}/{x}/{y}.png?apikey='+chase_config.thunderforest_api_key,
|
|
{
|
|
attribution: '© <a href="http://www.thunderforest.com">Thunderforest</a>, Data © <a href="http://www.openstreetmap.org/copyright">OpenStreetMap contributors</a>'
|
|
});
|
|
map_layers['Outdoors (Terrain)'] = thunderforest_outdoors;
|
|
|
|
}
|
|
|
|
if (chase_config.stadia_api_key !== 'none'){
|
|
// Stadia Alidade Smooth Dark Layer.
|
|
var alidade_smooth_dark = L.tileLayer('https://tiles.stadiamaps.com/tiles/alidade_smooth_dark/{z}/{x}/{y}{r}.png?apikey='+chase_config.stadia_api_key,
|
|
{
|
|
attribution: '© <a href="https://stadiamaps.com/">Stadia Maps</a>, © <a href="https://openmaptiles.org/">OpenMapTiles</a> © <a href="http://openstreetmap.org">OpenStreetMap</a> contributors'
|
|
});
|
|
map_layers['Alidade Smooth Dark'] = alidade_smooth_dark;
|
|
|
|
}
|
|
|
|
// Add Offline map layers, if we have any.
|
|
for (var i = 0, len = chase_config.offline_tile_layers.length; i < len; i++) {
|
|
var _layer_name = chase_config.offline_tile_layers[i];
|
|
map_layers['Offline - ' + _layer_name] = L.tileLayer(location.protocol + '//' + document.domain + ':' + location.port + '/tiles/'+_layer_name+'/{z}/{x}/{y}.png');
|
|
}
|
|
|
|
// Add measurement control.
|
|
|
|
if (chase_config['unitselection'] == "imperial") {
|
|
L.control.polylineMeasure({
|
|
position: 'topleft',
|
|
unit: 'landmiles',
|
|
showClearControl: true,
|
|
}).addTo(map);
|
|
}
|
|
|
|
|
|
if (chase_config['unitselection'] == "metric") {
|
|
L.control.polylineMeasure({
|
|
position: 'topleft',
|
|
unit: 'metres',
|
|
showClearControl: true,
|
|
}).addTo(map);
|
|
}
|
|
|
|
|
|
|
|
// Add layer selection control (top right).
|
|
map.addControl(new L.Control.Layers(map_layers));
|
|
|
|
// Add map scale.
|
|
if (chase_config['unitselection'] == "imperial") { L.control.scale({imperial: true, metric: false}).addTo(map) ;}
|
|
if (chase_config['unitselection'] == "metric") { L.control.scale({imperial: false, metric: true}).addTo(map) ;}
|
|
|
|
|
|
|
|
// Units for rings
|
|
if (chase_config['unitselection'] == "imperial") { document.getElementById("ring_spacing").innerHTML = "Ring Spacing (ft)";}
|
|
if (chase_config['unitselection'] == "metric") { document.getElementById("ring_spacing").innerHTML = "Ring Spacing (m)";}
|
|
|
|
// Add sidebar to map (where all of our controls are!)
|
|
var sidebar = L.control.sidebar('sidebar').addTo(map);
|
|
|
|
// Add custom controls, which show various sets of data.
|
|
|
|
// Telemetry table - shows all payload telemetry.
|
|
L.control.custom({
|
|
position: 'bottomright',
|
|
content : "<div class='centerWrapper'><div id='telem_table_btn' class='center_btn'><i class='fa fa-angle-right fa-4x text-center'></i></div><div id='telem_table' style='float:right;'></div></div>",
|
|
classes : 'btn-group-horizonal btn-group-sm',
|
|
id: 'telem_display',
|
|
style :
|
|
{
|
|
margin: '5px',
|
|
padding: '0px 0 0 0',
|
|
cursor: 'pointer',
|
|
}
|
|
})
|
|
.addTo(map);
|
|
|
|
// Allow hiding of the telemetry table. This function is in tables.js
|
|
$("#telem_table_btn").click(toggleTelemTableHide);
|
|
|
|
|
|
// Summary display - a 'quick-look' of where the currently tracked payload is and what it's doing.
|
|
L.control.custom({
|
|
position: 'topcenter',
|
|
content : "<div id='summary_table'></div>",
|
|
classes : 'btn-group-vertical btn-group-sm',
|
|
id: 'summary_display',
|
|
style :
|
|
{
|
|
margin: '5px',
|
|
padding: '0px 0 0 0',
|
|
cursor: 'pointer',
|
|
}
|
|
})
|
|
.addTo(map);
|
|
|
|
// Data age display - shows how old the various datasets are.
|
|
L.control.custom({
|
|
position: 'topleft',
|
|
content : "<div class='dataAgeHeader'>Data Age</div><div id='payload_age' class='dataAgeOK'></div><div id='car_age' class='dataAgeOK'></div><div id='pred_age' class='dataAgeOK'></div>",
|
|
classes : 'btn-group-vertical btn-group-sm',
|
|
id: 'age_display',
|
|
style :
|
|
{
|
|
margin: '5px',
|
|
padding: '0px 0 0 0',
|
|
cursor: 'pointer',
|
|
}
|
|
})
|
|
.addTo(map);
|
|
|
|
// Chase Car Speed Display
|
|
L.control.custom({
|
|
position: 'bottomleft',
|
|
content : "<div class='dataAgeHeader' id='chase_car_speed_header'></div><div id='chase_car_speed' class='chaseCarSpeed'></div>",
|
|
classes : 'btn-group-vertical btn-group-sm',
|
|
id: 'speed_display',
|
|
style :
|
|
{
|
|
margin: '5px',
|
|
padding: '0px 0 0 0',
|
|
cursor: 'pointer',
|
|
}
|
|
})
|
|
.addTo(map);
|
|
|
|
// Time-to-landing display - shows the time until landing for the currently tracked payload.
|
|
L.control.custom({
|
|
position: 'bottomleft',
|
|
content : "<div id='time_to_landing' class='timeToLanding'></div>",
|
|
classes : 'btn-group-vertical btn-group-sm',
|
|
id: 'ttl_display',
|
|
style :
|
|
{
|
|
margin: '5px',
|
|
padding: '0px 0 0 0',
|
|
cursor: 'pointer',
|
|
}
|
|
})
|
|
.addTo(map);
|
|
|
|
|
|
L.control.custom({
|
|
position: 'bottomright',
|
|
content : "<div id='bearing_table'></div>",
|
|
classes : 'btn-group-vertical btn-group-sm',
|
|
id: 'bearing_data',
|
|
style :
|
|
{
|
|
margin: '5px',
|
|
padding: '0px 0 0 0',
|
|
cursor: 'pointer',
|
|
}
|
|
})
|
|
.addTo(map);
|
|
|
|
L.control.custom({
|
|
position: 'bottomright',
|
|
content : "<div id='bearing_plot'></div>",
|
|
classes : 'btn-group-vertical btn-group-sm',
|
|
id: 'bearing_plot_control',
|
|
style :
|
|
{
|
|
margin: '5px',
|
|
padding: '0px 0 0 0',
|
|
cursor: 'pointer',
|
|
}
|
|
})
|
|
.addTo(map);
|
|
|
|
|
|
|
|
// Follow buttons - these just set the radio buttons on the settings pane.
|
|
// TODO: Figure out how to centre the icons under iOS's Safari browser.
|
|
// Also TODO: Find a decent balloon icon!
|
|
L.easyButton('fa-paper-plane', function(btn, map){
|
|
$('input:radio[name=autoFollow]').val(['payload']);
|
|
}, 'Follow Payload', 'followPayloadButton', {
|
|
position: 'topright'
|
|
}
|
|
).addTo(map);
|
|
|
|
L.easyButton('fa-car', function(btn, map){
|
|
$('input:radio[name=autoFollow]').val(['car']);
|
|
}, 'Follow Chase Car', 'followCarButton', {
|
|
position: 'topright'
|
|
}
|
|
).addTo(map);
|
|
|
|
map.zoomControl.setPosition('topright');
|
|
|
|
// Experimental routing control
|
|
// Currently disabled, as there is currently not enough control around how the map is re-panned
|
|
// after a route is calculated.
|
|
// router = L.Routing.control({
|
|
// waypoints: [null],
|
|
// show: false,
|
|
// showAlternatives: false
|
|
// }).addTo(map);
|
|
|
|
// Handler for when the user manually moves the map. This turns off
|
|
// the auto-follow options.
|
|
// Note we have to use 'dragend' else map movements from auto-follow would trigger this.
|
|
function mapMovedEvent(e){
|
|
// The user has panned the map, stop following things.
|
|
$('input:radio[name=autoFollow]').val(['none']);
|
|
}
|
|
map.on('dragend',mapMovedEvent);
|
|
|
|
|
|
initTables();
|
|
|
|
|
|
// Grab the recent archive of telemetry data
|
|
// This should only ever be run once - on page load.
|
|
$.ajax({
|
|
url: "/get_telemetry_archive",
|
|
dataType: 'json',
|
|
async: true,
|
|
success: function(data) {
|
|
|
|
for (callsign in data){
|
|
add_new_balloon(data[callsign]);
|
|
}
|
|
// Update telemetry table
|
|
updateTelemetryTable();
|
|
initial_load_complete = true;
|
|
}
|
|
});
|
|
|
|
// Initialise bearings
|
|
initialiseBearings();
|
|
|
|
// Initial setup of station marker, if using a profile which uses it.
|
|
reconfigureCarMarker(chase_config.selected_profile);
|
|
|
|
|
|
// Telemetry event handler.
|
|
// We will get one of these mesages with every new balloon position
|
|
socket.on('telemetry_event', function(data) {
|
|
handleTelemetry(data);
|
|
});
|
|
|
|
socket.on('modem_stats_event', function(data) {
|
|
handleModemStats(data);
|
|
});
|
|
|
|
// Predictor Functions
|
|
socket.on('predictor_model_update', function(data){
|
|
var _model_data = data.model;
|
|
$("#predictorModelValue").text(_model_data);
|
|
});
|
|
|
|
socket.on('predictor_update', function(data){
|
|
handlePrediction(data);
|
|
});
|
|
|
|
socket.on('bearing_change', function(data){
|
|
bearingUpdate(data);
|
|
});
|
|
|
|
|
|
$("#downloadModel").click(function(){
|
|
socket.emit('download_model', {data: 'plzkthx'});
|
|
});
|
|
|
|
$("#clearPayloadData").click(function(){
|
|
var _confirm = confirm("Really clear all Payload data?");
|
|
if (_confirm == true){
|
|
socket.emit('payload_data_clear', {data: 'plzkthx'});
|
|
|
|
// Clear all payload markers and tracks from the map/
|
|
for (_callsign in balloon_positions){
|
|
balloon_positions[_callsign].marker.remove();
|
|
balloon_positions[_callsign].path.remove();
|
|
balloon_positions[_callsign].pred_path.remove();
|
|
balloon_positions[_callsign].abort_path.remove();
|
|
// Clear out the markers if they exist.
|
|
if (balloon_positions[_callsign].abort_marker != null){
|
|
balloon_positions[_callsign].abort_marker.remove();
|
|
}
|
|
if (balloon_positions[_callsign].burst_marker != null){
|
|
balloon_positions[_callsign].burst_marker.remove();
|
|
}
|
|
if (balloon_positions[_callsign].pred_marker != null){
|
|
balloon_positions[_callsign].pred_marker.remove();
|
|
}
|
|
}
|
|
// Reset the balloon positions object to nothing.
|
|
balloon_positions = {};
|
|
balloon_currently_following = "none";
|
|
// Update tables.
|
|
updateTelemetryTable();
|
|
updateSummaryDisplay();
|
|
// Clear the time-to-landing display.
|
|
$('#time_to_landing').text("");
|
|
}
|
|
});
|
|
|
|
$("#clearCarData").click(function(){
|
|
var _confirm = confirm("Really clear all Chase Car data?");
|
|
if (_confirm == true){
|
|
socket.emit('car_data_clear', {data: 'plzkthx'});
|
|
if (chase_car_position.marker !== "NONE"){
|
|
chase_car_position.marker.remove();
|
|
}
|
|
if (chase_car_position.path !== "NONE"){
|
|
chase_car_position.path.remove();
|
|
}
|
|
|
|
chase_car_position = {latest_data: [], heading:0, marker: 'NONE', path: 'NONE'};
|
|
|
|
}
|
|
});
|
|
|
|
// Function to show/hide chase car track tail.
|
|
setChaseCarTrack = function(){
|
|
// Read state of checkbox
|
|
var _car_track_enabled = document.getElementById("chaseCarTrack").checked;
|
|
// Only continue if we have chase car data to show/hide.
|
|
if (chase_car_position !== "NONE"){
|
|
if (_car_track_enabled == false){
|
|
chase_car_position.path.remove();
|
|
}else{
|
|
chase_car_position.path.addTo(map);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Tell the server we are connected and ready for data.
|
|
socket.on('connect', function() {
|
|
socket.emit('client_connected', {data: 'I\'m connected!'});
|
|
// This will cause the server to emit a few messages telling us to fetch data.
|
|
});
|
|
|
|
|
|
// Data age update.
|
|
var age_update_rate = 500;
|
|
window.setInterval(function () {
|
|
// Update the data age displays.
|
|
payload_data_age += age_update_rate/1000.0;
|
|
car_data_age += age_update_rate/1000.0;
|
|
pred_data_age += age_update_rate/1000.0;
|
|
|
|
if (balloon_currently_following === 'none'){
|
|
$("#payload_age").text("Payload: No Data.");
|
|
}else{
|
|
$("#payload_age").text("Payload: " + payload_data_age.toFixed(1)+"s");
|
|
if (payload_data_age > payload_bad_age){
|
|
$("#payload_age").removeClass();
|
|
$("#payload_age").addClass('dataAgeBad');
|
|
}else{
|
|
$("#payload_age").removeClass();
|
|
$("#payload_age").addClass('dataAgeOK');
|
|
}
|
|
}
|
|
|
|
if(chase_car_position.latest_data.length == 0){
|
|
$("#car_age").text("Car GPS: No Data.");
|
|
}else{
|
|
$("#car_age").text("Car GPS: " + car_data_age.toFixed(1)+"s");
|
|
if (car_data_age > car_bad_age){
|
|
$("#car_age").removeClass();
|
|
$("#car_age").addClass('dataAgeBad');
|
|
}else{
|
|
$("#car_age").removeClass();
|
|
$("#car_age").addClass('dataAgeOK');
|
|
}
|
|
}
|
|
|
|
if (chase_config['pred_enabled']==false){
|
|
$("#pred_age").text("Predictions: No Data.");
|
|
}else{
|
|
$("#pred_age").text("Predictions: " + pred_data_age.toFixed(1)+"s");
|
|
}
|
|
}, age_update_rate);
|
|
|
|
|
|
// Habitat/Sondehub Chase Car Position Grabber
|
|
var habitat_update_rate = 20000;
|
|
window.setInterval(function(){
|
|
if(document.getElementById("showOtherCars").checked){
|
|
if (chase_config.profiles[chase_config.selected_profile].online_tracker === "habitat"){
|
|
get_habitat_vehicles();
|
|
}
|
|
else if (chase_config.profiles[chase_config.selected_profile].online_tracker === "sondehub"){
|
|
get_sondehub_vehicles();
|
|
}
|
|
else{
|
|
// Do nothing...
|
|
}
|
|
}
|
|
}, habitat_update_rate);
|
|
|
|
// Sadly we can't request location information unless chasemapper is served up via HTTPS
|
|
// which is a bit difficult to make happen.
|
|
// window.setInterval(function(){
|
|
// if(document.getElementById('useDevicePosition').checked){
|
|
// if(navigator.geolocation){
|
|
// car_bad_age = 20.0;
|
|
// navigator.geolocation.getCurrentPosition(devicePositionCallback,devicePositionError,{enableHighAccuracy:true});
|
|
// }
|
|
// }
|
|
// }, 5000);
|
|
|
|
|
|
window.setInterval(function(){
|
|
if(Object.keys(bearing_store).length > 0){
|
|
$.ajax({
|
|
url: "/server_time",
|
|
dataType: 'json',
|
|
async: true,
|
|
success: function(data) {
|
|
latest_server_timestamp = data;
|
|
restyleBearings();
|
|
}
|
|
});
|
|
}
|
|
},10000);
|
|
});
|
|
</script>
|
|
</head>
|
|
<body>
|
|
|
|
<!-- Dialog boxes. These are activated from the telemetry table - see tables.js -->
|
|
<div id="telemetry-select-dialog" title="Payload Selection" style='display:none;'>
|
|
<div class="paramRow">
|
|
<b>Current Position:</b> <div style='float:right;' id='telemDialogPosition'></div><br/>
|
|
</div>
|
|
<div class="paramRow">
|
|
<b>Predicted Landing:</b> <div style='float:right;' id='telemDialogPredPosition'></div><br/>
|
|
</div>
|
|
<div class="paramRow">
|
|
<b>Select Action:</b><br/>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="mark-recovered-dialog" title="Mark Payload Recovered" style='display:none;'>
|
|
<div class="paramRow">
|
|
<b>Last Position:</b> <div style='float:right;' id='recoveryPosition'></div><br/>
|
|
</div>
|
|
<div class="paramRow">
|
|
<b>Use Car Position:</b> <input type="checkbox" class="paramSelector" id="recoveryCarPosition" onclick='setRecoveryCarPosition();'><br/>
|
|
</div>
|
|
<div class="paramRow">
|
|
<b>Title:</b><input type="text" class="paramEntry" size="40" id="customRecoveryTitle"><br/>
|
|
</div>
|
|
<div class="paramRow">
|
|
<b>Message:</b><input type="text" class="paramEntry" size="40" id="customRecoveryMessage"><br/>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="sidebar" class="sidebar collapsed">
|
|
<!-- Nav tabs -->
|
|
<div class="sidebar-tabs">
|
|
<ul role="tablist">
|
|
<li><a href="#home" role="tab"><i class="fa fa-bars"></i></a></li>
|
|
<li><a href="#settings" role="tab"><i class="fa fa-gear"></i></a></li>
|
|
<li><a href="#car-settings" role="tab"><i class="fa fa-car"></i></a></li>
|
|
<li><a href="#df-settings" role="tab"><i class="fa fa-compass"></i></a></li>
|
|
</ul>
|
|
</div>
|
|
|
|
<!-- Tab panes -->
|
|
<div class="sidebar-content">
|
|
<div class="sidebar-pane" id="home">
|
|
<div class="paramRow">
|
|
<b>Chasemapper Version: </b> <div id="chasemapper_version" class="paramEntry">Unknown</div></br>
|
|
</div>
|
|
</br>
|
|
<h1 class="sidebar-header">
|
|
Log Messages
|
|
<span class="sidebar-close"><i class="fa fa-caret-left"></i></span>
|
|
</h1>
|
|
|
|
<div id="log_data" class="logText"></div>
|
|
|
|
</div>
|
|
|
|
<div class="sidebar-pane" id="settings">
|
|
<h1 class="sidebar-header">Settings<span class="sidebar-close"><i class="fa fa-caret-left"></i></span></h1>
|
|
</hr>
|
|
<h3>Data Source</h3>
|
|
<select id="profileSelect" name="profileSelect">
|
|
</select>
|
|
|
|
<h3>Map</h3>
|
|
<h4>Auto-Follow</h4>
|
|
<form>
|
|
<input type="radio" name="autoFollow" value="payload" checked> Payload<br>
|
|
<input type="radio" name="autoFollow" value="car"> Chase Car<br>
|
|
<input type="radio" name="autoFollow" value="none"> None
|
|
</form>
|
|
</hr>
|
|
<div class="paramRow">
|
|
<b>Show Car Track</b> <input type="checkbox" class="paramSelector" id="chaseCarTrack" onclick='setChaseCarTrack();' checked>
|
|
</div>
|
|
|
|
</hr>
|
|
<h3>Predictor</h3>
|
|
<div class="paramRow" id="predictorModel">
|
|
<b>Current Model: </b> <div class="predictorModelValue" id="predictorModelValue">Disabled</div>
|
|
</div>
|
|
<div class="paramRow">
|
|
<b>Download Model</b> <button type="button" class="paramSelector" id="downloadModel">Download</button>
|
|
</div>
|
|
<div class="paramRow">
|
|
<b>Enable Predictions</b> <input type="checkbox" class="paramSelector" id="predictorEnabled" onclick='clientSettingsUpdate();'>
|
|
</div>
|
|
<div class="paramRow">
|
|
<b>Show 'Abort' Predictions</b> <input type="checkbox" class="paramSelector" id="abortPredictionEnabled" onclick='clientSettingsUpdate();'>
|
|
</div>
|
|
<div class="paramRow">
|
|
<b>Burst Altitude</b><input type="text" class="paramEntry" id="burstAlt"><br/>
|
|
</div>
|
|
<div class="paramRow">
|
|
<b>Descent Rate</b><input type="text" class="paramEntry" id="descentRate"><br/>
|
|
</div>
|
|
<div class="paramRow">
|
|
<b>Update Rate</b><input type="text" class="paramEntry" id="predUpdateRate"><br/>
|
|
</div>
|
|
</hr>
|
|
</hr>
|
|
<h3>Other</h3>
|
|
<div class="paramRow">
|
|
<button type="button" class="paramSelector" id="clearPayloadData">Clear Payload Data</button></br>
|
|
</div>
|
|
<div class="paramRow">
|
|
<button type="button" class="paramSelector" id="clearCarData">Clear Chase-Car Track</button></br>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="sidebar-pane" id="car-settings">
|
|
<h1 class="sidebar-header">
|
|
Car Settings
|
|
<span class="sidebar-close"><i class="fa fa-caret-left"></i></span>
|
|
</h1>
|
|
|
|
<!--
|
|
<h3>Car Position Source</h3>
|
|
<div class="paramRow">
|
|
<b>Use Device Position:</b> <input type="checkbox" class="paramSelector" id="useDevicePosition">
|
|
</div>
|
|
-->
|
|
|
|
<h3>Habitat Chase Car</h3>
|
|
<div class="paramRow">
|
|
<b>Show Nearby Chase-Cars:</b> <input type="checkbox" class="paramSelector" id="showOtherCars" onclick="show_habitat_vehicles();">
|
|
</div>
|
|
<div class="paramRow">
|
|
<b>Enable Chase-Car Position Upload</b> <input type="checkbox" class="paramSelector" id="habitatUploadEnabled" onclick='clientSettingsUpdate();'>
|
|
</div>
|
|
<div class="paramRow">
|
|
<b>Habitat Call:</b><input type="text" class="paramEntry" id="habitatCall"><br/>
|
|
</div>
|
|
<div class="paramRow">
|
|
<b>Update Rate (seconds):</b><input type="text" class="paramEntry" id="habitatUpdateRate"><br/>
|
|
</div>
|
|
|
|
<h3>Range Rings</h3>
|
|
<div class="paramRow">
|
|
<b>Enable Range Rings</b> <input type="checkbox" class="paramSelector" id="rangeRingsEnabled" onclick='updateRangeRings();'>
|
|
</div>
|
|
<div class="paramRow">
|
|
<b>Ring Qty</b><input type="text" class="paramEntry" id="ringQuantity" value="5"><br/>
|
|
</div>
|
|
<div class="paramRow">
|
|
<b> <p><p id="ring_spacing">Ring Spacing (m)</p></p></b><input type="text" class="paramEntry" id="ringSpacing" value="1000"><br/>
|
|
</div>
|
|
<div class="paramRow">
|
|
<b>Ring Weight (px)</b><input type="text" class="paramEntry" id="ringWeight" value="2"><br/>
|
|
</div>
|
|
<div class="paramRow">
|
|
<b>Ring Color</b>
|
|
<select class="paramSelector" id="ringColorSelect" name="ringColorSelect">
|
|
<option value='red'>Red</option>
|
|
<option value='black'>Black</option>
|
|
<option value='green'>Green</option>
|
|
<option value='blue'>Blue</option>
|
|
<option value='custom'>Custom</option>
|
|
</select>
|
|
<br/>
|
|
</div>
|
|
<div class="paramRow">
|
|
<b>Custom Color</b><input type="text" class="paramEntry" id="ringCustomColor" value="#FF0000"><br/>
|
|
</div>
|
|
|
|
<h3>Speed Display</h3>
|
|
<div class="paramRow">
|
|
<b>Show Chase Car Speed:</b> <input type="checkbox" class="paramSelector" id="showCarSpeed">
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
<div class="sidebar-pane" id="df-settings">
|
|
<h1 class="sidebar-header">
|
|
DF Settings
|
|
<span class="sidebar-close"><i class="fa fa-caret-left"></i></span>
|
|
</h1>
|
|
|
|
<div class="paramRow">
|
|
<b>Enable Bearings</b> <input type="checkbox" class="paramSelector" id="bearingsEnabled" checked onclick='toggleBearingsEnabled();'>
|
|
</div>
|
|
|
|
<div class="paramRow">
|
|
<b>Bearing-Only Mode</b> <input type="checkbox" class="paramSelector" id="bearingsOnlyMode" onclick='toggleBearingsOnlyMode();'>
|
|
</div>
|
|
|
|
<div class="paramRow">
|
|
<b>Enable TDOA Plot</b> <input type="checkbox" class="paramSelector" id="tdoaEnabled" checked>
|
|
</div>
|
|
|
|
<div class="paramRow">
|
|
<b>Confidence Threshold</b><input type="text" class="paramEntry" id="bearingConfidenceThreshold" value="50"><br/>
|
|
</div>
|
|
|
|
<div class="paramRow">
|
|
<b>Maximum Age (min)</b><input type="text" class="paramEntry" id="bearingMaximumAge" value="20"><br/>
|
|
</div>
|
|
|
|
<div class="paramRow">
|
|
<b>Show Stationary Bearings</b> <input type="checkbox" class="paramSelector" id="showStationaryBearings">
|
|
</div>
|
|
|
|
|
|
<div class="paramRow">
|
|
<button type="button" class="paramSelector" id="clearBearingsBtn" onclick='destroyAllBearings();'>Clear Map</button></br>
|
|
</div>
|
|
<div class="paramRow">
|
|
<button type="button" class="paramSelector" id="flushBearingsBtn" onclick='flushBearings();'>Flush Bearing Store</button></br>
|
|
</div>
|
|
|
|
<h3>Bearing Style</h3>
|
|
<div class="paramRow">
|
|
<b>Bearing Length (km)</b><input type="text" class="paramEntry" id="bearingLength" value="10"><br/>
|
|
</div>
|
|
<div class="paramRow">
|
|
<b>Bearing Weight (px)</b><input type="text" class="paramEntry" id="bearingWeight" value="0.5"><br/>
|
|
</div>
|
|
<div class="paramRow">
|
|
<b>Bearing Min Opacity</b><input type="text" class="paramEntry" id="bearingMinOpacity" value="0.1"><br/>
|
|
</div>
|
|
<div class="paramRow">
|
|
<b>Bearing Max Opacity</b><input type="text" class="paramEntry" id="bearingMaxOpacity" value="0.8"><br/>
|
|
</div>
|
|
<div class="paramRow">
|
|
<b>Bearing Color</b>
|
|
<select class="paramSelector" id="bearingColorSelect" name="bearingColorSelect">
|
|
<option value='black'>Black</option>
|
|
<option value='red'>Red</option>
|
|
<option value='green'>Green</option>
|
|
<option value='blue'>Blue</option>
|
|
<option value='custom'>Custom</option>
|
|
</select>
|
|
<br/>
|
|
</div>
|
|
<div class="paramRow">
|
|
<b>Custom Color</b><input type="text" class="paramEntry" id="bearingCustomColor" value="#FF0000"><br/>
|
|
</div>
|
|
|
|
<div class="paramRow">
|
|
<button type="button" class="paramSelector" id="redrawBearingsBtn" onclick='redrawBearings();'>Redraw Bearings</button></br>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="map" class="sidebar-map"></div>
|
|
</body>
|
|
</html>
|