radiosonde_auto_rx/auto_rx/autorx/templates/index.html

1333 wiersze
66 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<title>Radiosonde Auto-RX Status</title>
<!-- Configure to work on mobile -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Import style sheets (font and icons are remote, should fix) -->
<link href="{{ url_for('static', filename='css/main.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/roboto.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/c3.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/leaflet.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/leaflet.fullscreen.css') }}" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" >
<link id='tabulatorsheet' href='{{ url_for('static', filename='css/tabulator_midnight.min.css') }}' rel='stylesheet'>
<!-- Import local libraries -->
<script src="{{ url_for('static', filename='js/jquery-3.6.0.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/jquery-ui.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/leaflet.js') }}"></script>
<script src="{{ url_for('static', filename='js/leaflet-providers.js') }}"></script>
<script src="{{ url_for('static', filename='js/Leaflet.fullscreen.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/leaflet.edgebuffer.js') }}"></script>
<script src="{{ url_for('static', filename='js/socket.io.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/scan_chart.js') }}"></script>
<script src="{{ url_for('static', filename='js/c3.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/d3.min.js') }}" charset="utf-8"></script>
<script src="{{ url_for('static', filename='js/utils.js') }}"></script>
<script src="{{ url_for('static', filename='js/tabulator.min.js') }}"></script>
<script>
var autorx_config = {
lat: 0.0,
lon: 0.0
};
var sonde_positions = {};
var sonde_currently_following = "none";
$( document ).ready(function() {
namespace = '/update_status';
var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + namespace);
$.ajax({
// Get station.cfg file.
url: "/get_config",
dataType: 'json',
async: false,
success: function(data) {
autorx_config = data;
}
});
$.ajax({
// Get local version number.
url: "/get_version",
dataType: 'text',
async: true,
success: function(data) {
$('#currentversion').text(data);
}
});
socket.on('log_event', function(msg) {
// New log entry received.
var log_time = new Date(msg.timestamp);
// Check if time is UTC mode.
if (getCookie('UTC') == 'false') {
// Check if entry is important.
if (msg.level == "INFO") {
var log_entry = "<tr><td><em>" + msg.level + "</em><br><b>" + msg.msg + "</b><br>" + log_time.toLocaleString("en-AU") + "</td></tr>";
} else {
// If entry is important colour text red.
var log_entry = "<tr><td><em>" + msg.level + "</em><br><b style='color:red'>" + msg.msg + "</b><br>" + log_time.toLocaleString("en-AU") + "</td></tr>";
}
} else {
if (msg.level == "INFO") {
var log_entry = "<tr><td><em>" + msg.level + "</em><br><b>" + msg.msg + "</b><br>" + msg.timestamp + "</td></tr>";
} else {
// If entry is important colour text red.
var log_entry = "<tr><td><em>" + msg.level + "</em><br><b style='color:red'>" + msg.msg + "</b><br>" + msg.timestamp + "</td></tr>";
}
}
// Append entry to log table.
$('#log_data > tbody').prepend(log_entry);
});
setup_scan_chart();
socket.on('scan_event', function(msg) {
// There is Scan data ready for us!
// Grab the latest set of data.
$.getJSON("/get_scan_data", function(data){
scan_chart_spectra.columns[0] = ['x_spectra'].concat(data.freq);
scan_chart_spectra.columns[1] = ['Spectra'].concat(data.power);
scan_chart_peaks.columns[0] = ['x_peaks'].concat(data.peak_freq);
scan_chart_peaks.columns[1] = ['Peaks'].concat(data.peak_lvl);
scan_chart_threshold.columns[1] = ['Threshold'].concat([data.threshold+autorx_config.snr_threshold,data.threshold+autorx_config.snr_threshold]);
// Plot the updated data.
scan_chart_obj.load(scan_chart_spectra);
scan_chart_obj.load(scan_chart_peaks);
scan_chart_obj.load(scan_chart_threshold);
// Run dark mode check again to solve render issues.
var z = getCookie('dark');
if (z == 'true') {
changeTheme(true);
} else if (z == 'false') {
changeTheme(false);
} else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
changeTheme(true);
} else {
changeTheme(false);
}
// Show the latest scan time.
if (getCookie('UTC') == 'false') {
temp_date = data.timestamp;
temp_date = temp_date.slice(0, -3);
temp_date += "Z";
var date = new Date(temp_date);
$('#scan_results').html('<b>Latest Scan:</b> ' + date.toLocaleString("en-AU"));
} else {
$('#scan_results').html('<b>Latest Scan:</b> ' + data.timestamp.slice(0, -3) + 'Z');
}
}
);
});
socket.on('task_event', function(msg){
// Grab the latest task list.
$.getJSON("/get_task_list", function(data){
var task_info = "";
for (_task in data){
task_info += "SDR #" + _task + ": " + data[_task] + " ";
}
// Update page with latest task.
$('#task_status').text(task_info);
});
});
// List of available map layers.
var Mapnik = L.tileLayer.provider("OpenStreetMap.Mapnik", {edgeBufferTiles: 2});
var DarkMatter = L.tileLayer.provider("CartoDB.DarkMatter", {edgeBufferTiles: 2});
var Terrain = L.tileLayer.provider("Stamen.Terrain", {edgeBufferTiles: 2});
var WorldImagery = L.tileLayer.provider("Esri.WorldImagery", {edgeBufferTiles: 2});
var Voyager = L.tileLayer.provider("CartoDB.Voyager", {edgeBufferTiles: 2});
var OpenTopoMap = L.tileLayer.provider("OpenTopoMap", {edgeBufferTiles: 2});
// Add maps to baseMaps.
var baseMaps = {
"Mapnik": Mapnik,
"DarkMatter": DarkMatter,
"WorldImagery": WorldImagery,
"Terrain": Terrain,
"Voyager": Voyager,
"OpenTopoMap": OpenTopoMap
};
// Check if user has preffered map theme.
var x = getCookie('theme');
if (x) {
mapTheme = x;
} else {
if (getCookie('dark') == "false") {
mapTheme = "Mapnik"
} else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
mapTheme = "DarkMatter"
} else {
mapTheme = "Mapnik"
}
}
// Home Icon.
homeIcon = L.icon({
iconUrl: '{{ url_for('static', filename='img/antenna-green.png') }}',
iconSize: [26, 34],
iconAnchor: [13, 34]
});
// Home Icon for dark mode.
homeIconDark = L.icon({
iconUrl: '{{ url_for('static', filename='img/antenna-green-dark.png') }}',
iconSize: [26, 34],
iconAnchor: [13, 34]
});
// Create map object.
mymap = L.map('mapid').setView([autorx_config.station_lat, autorx_config.station_lon], 8);
mymap.addControl(new L.Control.Fullscreen());
if (mapTheme != 'DarkMatter' && mapTheme != 'WorldImagery') {
home_marker = L.marker([autorx_config.station_lat, autorx_config.station_lon, autorx_config.alt],
{title: 'Receiver Location', icon: homeIcon}
).addTo(mymap);
} else {
home_marker = L.marker([autorx_config.station_lat, autorx_config.station_lon, autorx_config.alt],
{title: 'Receiver Location', icon: homeIconDark}
).addTo(mymap);
}
L.control.layers(baseMaps).addTo(mymap);
baseMaps[mapTheme].addTo(mymap);
// Update preffered them cookie on layer change.
mymap.on('baselayerchange', function(e) {
setCookie("theme", e['name'], 365);
if(e['name'] == "DarkMatter" || e['name'] == "WorldImagery"){
home_marker.setIcon(homeIconDark);
}else{
home_marker.setIcon(homeIcon);
}
});
// Check if user has preffered map visiblity.
if (getCookie('map') == 'false') {
document.getElementById("showmapbutton").checked = false;
document.getElementById("mapid").style.display = "none";
} else {
document.getElementById("showmapbutton").checked = true;
}
// Check if user has preffered table visiblity.
if (getCookie('table') == 'false') {
document.getElementById("showtablebutton").checked = false;
document.getElementById("tableid").style.display = "none";
} else {
document.getElementById("showtablebutton").checked = true;
}
// Check if user has preffered follow latest sonde selection.
if (getCookie('follow') == 'false') {
document.getElementById("sondeAutoFollow").checked = false;
} else {
document.getElementById("sondeAutoFollow").checked = true;
}
// Check if user has UTC time selection.
if (getCookie('UTC') == 'false') {
document.getElementById("showUTCbutton").checked = false;
} else {
document.getElementById("showUTCbutton").checked = true;
}
// Check if user has preffered scan chart visiblity.
if (getCookie('scan') == 'true') {
document.getElementById("showscanbutton").checked = true;
document.getElementById("scanid").style.display = "block";
} else {
document.getElementById("showscanbutton").checked = false;
document.getElementById("scanid").style.display = "none";
}
// Check if user has dark mode set.
if (getCookie('dark') == 'true') {
document.getElementById("showdarkbutton").checked = true;
changeTheme(true);
} else if (getCookie('dark') == 'false') {
document.getElementById("showdarkbutton").checked = false;
changeTheme(false);
} else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.getElementById("showdarkbutton").checked = true;
changeTheme(true);
} else {
document.getElementById("showdarkbutton").checked = false;
changeTheme(false);
}
// Function to change CSS options when changing dark mode.
function changeTheme(dark) {
if (dark == false) {
document.body.style.background = 'white';
$('#main span').css('color', 'black')
$('#main p').css('color', 'black')
$('#myBtn').css('color', 'black')
$('#scanid').css('color', 'black')
$('.c3-axis-y').css('fill', 'black')
$('.c3-axis-x').css('fill', 'black')
$('.c3-legend-item text').css('fill', 'black')
$('.domain').css('stroke', 'black')
$('.tick line').css('stroke', 'black')
$('#mapid span').css('color', 'black')
$('.sidenav').css('background-color', '#111')
$('.settings').css('background-color', '#111')
$('#tabulatorsheet').attr('href', '{{ url_for("static", filename="css/tabulator_simple.min.css") }}');
} else {
document.body.style.background = '#121212';
$('#main span').css('color', 'white')
$('#main p').css('color', 'white')
$('#myBtn').css('color', 'white')
$('#scanid').css('color', 'white')
$('.c3-axis-y').css('fill', 'white')
$('.c3-axis-x').css('fill', 'white')
$('.c3-legend-item text').css('fill', 'white')
$('.domain').css('stroke', 'white')
$('.tick line').css('stroke', 'white')
$('#mapid span').css('color', 'black')
$('.sidenav').css('background-color', '#414141')
$('.settings').css('background-color', '#414141')
$('#tabulatorsheet').attr('href', '{{ url_for("static", filename="css/tabulator_midnight.min.css") }}');
}
}
// Check if dark mode button has been ticked.
$('#showdarkbutton').change(function() {
if ($(this).is(":checked")) {
setCookie("dark", 'true', 365);
changeTheme(true);
} else {
setCookie("dark", 'false', 365);
changeTheme(false);
}
});
// Check if UTC button has been ticked.
$('#showUTCbutton').change(function() {
if ($(this).is(":checked")) {
setCookie("UTC", 'true', 365);
updateTelemetryTable();
} else {
setCookie("UTC", 'false', 365);
updateTelemetryTable();
}
});
// Check if UTC button has been ticked.
$('#paginationSelector').change(function() {
setCookie("pagination", this.value, 365);
table.setPageSize(this.value);
});
// Check if cookie exists for entries to display per page in table
if (getCookie('pagination') != null) {
pagination_size = parseInt(getCookie('pagination'));
$('#paginationSelector option[value="'+ getCookie('pagination') +'"]').attr("selected",true);
} else {
if (($( window ).width()/$( window ).height()) > 1) {
pagination_size = 6;
$('#paginationSelector option[value="6"]').attr("selected",true);
} else {
pagination_size = 3;
$('#paginationSelector option[value="3"]').attr("selected",true);
}
}
// Create Tabulator table.
table = new Tabulator("#telem_table", {
index:"realid",
placeholder:"No Sonde Data Available",
// Split into pages for over 6 entries.
pagination:"local",
paginationSize:pagination_size,
layout:"fitDataFill",
resizableColumns:"header",
layoutColumnsOnNewData:true,
columns:[ //Define Table Columns
{title:"SDR", field:"sdr_device_idx", headerSort:true},
{title:"Age", field:"age", headerSort:true},
{title:"Type", field:"type", headerSort:true},
{title:'Freq (MHz)', field:"freq", headerSort:true},
{title:"ID", field:"id", formatter:'html', width:80, headerSort:true},
{title:"Time", field:"datetime", width:180, headerSort:true, formatter:function(cell, formatterParams, onRendered){
if (getCookie('UTC') == 'false') {
var temp_time = new Date(cell.getValue());
if (temp_time.toLocaleString("en-AU") == "Invalid Date") {
return;
} else {
return temp_time.toLocaleString("en-AU");
}
} else {
return cell.getValue();
}
}
},
{title:"Frame", field:"frame", headerSort:true},
{title:"Latitude", field:"lat", width:80, formatter:'html', headerSort:false},
{title:"Longitude", field:"lon", width:80, formatter:'html', headerSort:false},
{title:"Alt (m)", field:"alt", headerSort:true},
{title:"Vel (kph)", field:"vel_h", headerSort:false},
{title:"Asc (m/s)", field:"vel_v", headerSort:false},
{title:"Temp (°C)", field:"temp", headerSort:false},
{title:"RH (%)", field:"humidity", headerSort:false},
{title:"Az (°)", field:"azimuth", headerSort:false},
{title:"El (°)", field:"elevation", headerSort:false},
{title:"Range (km)", field:"range", headerSort:true},
{title:"SNR (dB)", field:"snr", headerSort:true},
{title:"Other", field:"other", width:140, headerSort:false},
{title:"Real ID", field:"realid", visible:false}
],
rowContext:function(e, row){
e.preventDefault();
//Highlight Sonde on map when row selected
for (var i = 0; i < Object.keys(sonde_positions).length; i++) {
console.log(Object.keys(sonde_positions)[i]);
if (Object.keys(sonde_positions)[i] != row['_row']['data']['id'].replace(/(<([^>]+)>)/gi, "")) {
sonde_positions[Object.keys(sonde_positions)[i]]['path'].setStyle({
color: sonde_positions[Object.keys(sonde_positions)[i]]['colour']
});
if (sonde_positions[Object.keys(sonde_positions)[i]]['latest_data']['vel_v'] < 0){
sonde_positions[Object.keys(sonde_positions)[i]].marker.setIcon(sondeDescentIcons[sonde_positions[Object.keys(sonde_positions)[i]]['colour']]);
}else{
sonde_positions[Object.keys(sonde_positions)[i]].marker.setIcon(sondeAscentIcons[sonde_positions[Object.keys(sonde_positions)[i]]['colour']]);
}
}
}
if (sonde_positions[row['_row']['data']['id'].replace(/(<([^>]+)>)/gi, "")]['path']['options']['color'] != 'white') {
selected_sonde = row['_row']['data']['id'].replace(/(<([^>]+)>)/gi, "");
sonde_positions[row['_row']['data']['id'].replace(/(<([^>]+)>)/gi, "")]['path'].setStyle({
color: 'white'
});
if (sonde_positions[row['_row']['data']['id'].replace(/(<([^>]+)>)/gi, "")]['latest_data']['vel_v'] < 0){
sonde_positions[row['_row']['data']['id'].replace(/(<([^>]+)>)/gi, "")].marker.setIcon(sondeDescentIcons['white']);
}else{
sonde_positions[row['_row']['data']['id'].replace(/(<([^>]+)>)/gi, "")].marker.setIcon(sondeAscentIcons['white']);
}
} else {
selected_sonde = "";
sonde_positions[row['_row']['data']['id'].replace(/(<([^>]+)>)/gi, "")]['path'].setStyle({
color: sonde_positions[row['_row']['data']['id'].replace(/(<([^>]+)>)/gi, "")]['colour']
});
if (sonde_positions[row['_row']['data']['id'].replace(/(<([^>]+)>)/gi, "")]['latest_data']['vel_v'] < 0){
sonde_positions[row['_row']['data']['id'].replace(/(<([^>]+)>)/gi, "")].marker.setIcon(sondeDescentIcons[sonde_positions[row['_row']['data']['id'].replace(/(<([^>]+)>)/gi, "")]['colour']]);
}else{
sonde_positions[row['_row']['data']['id'].replace(/(<([^>]+)>)/gi, "")].marker.setIcon(sondeAscentIcons[sonde_positions[row['_row']['data']['id'].replace(/(<([^>]+)>)/gi, "")]['colour']]);
}
}
}
});
// Update Tabulator table.
function updateTelemetryTable(){
var telem_data = [];
if (jQuery.isEmptyObject(sonde_positions)){
telem_data = [];
}else{
var sonde_id_list = Object.getOwnPropertyNames(sonde_positions).reverse();
//for (sonde_id in sonde_id_list){
sonde_id_list.forEach( function(sonde_id){
var sonde_id_data = Object.assign({},sonde_positions[sonde_id].latest_data);
var sonde_id_age = Date.now() - sonde_positions[sonde_id].age;
if (sonde_id_age>(1000*autorx_config.rx_timeout)){
sonde_id_data.sdr_device_idx = "";
sonde_id_data.age = "old";
}else{
sonde_id_data.age = (sonde_id_age/1000.0).toFixed(0) + " s";
}
// If we have a station lat/lon/alt set, calculate az/el/range.
if (autorx_config.station_lat != 0.0){
// There is a station lat/lon set.
var _bal = {lat:sonde_id_data.lat, lon:sonde_id_data.lon, alt:sonde_id_data.alt};
var _station = {lat:autorx_config.station_lat, lon:autorx_config.station_lon, alt:autorx_config.station_alt};
var _look_angles = calculate_lookangles(_station, _bal);
sonde_id_data.azimuth = _look_angles.azimuth.toFixed(1);
sonde_id_data.elevation = _look_angles.elevation.toFixed(1);
sonde_id_data.range = (_look_angles.range/1000).toFixed(1);
} else{
// Insert blank data.
sonde_id_data.azimuth = "";
sonde_id_data.elevation = "";
sonde_id_data.range = "";
}
// Modify some of the fields to fixed point values.
// Add Geo ref links to lat/lon fields.
temp_lat = "<a href='geo:" + sonde_id_data.lat.toFixed(5) + "," + sonde_id_data.lon.toFixed(5) + "'>" + sonde_id_data.lat.toFixed(5) + "</a>";
temp_lon = "<a href='geo:" + sonde_id_data.lat.toFixed(5) + "," + sonde_id_data.lon.toFixed(5) + "'>" + sonde_id_data.lon.toFixed(5) + "</a>";
sonde_id_data.lat = temp_lat;
sonde_id_data.lon = temp_lon;
sonde_id_data.alt = sonde_id_data.alt.toFixed(1);
sonde_id_data.vel_v = sonde_id_data.vel_v.toFixed(1);
sonde_id_data.vel_h = (sonde_id_data.vel_h*3.6).toFixed(1);
// Add a link to HabHub if we have habitat enabled.
if (autorx_config.habitat_enabled == true) {
// Add a link to HabHub card if sonde is old.
if (sonde_id_data.age != "old") {
sonde_id_data.id = "<a href='http://sondehub.org/" + sonde_id + "' target='_blank'>" + sonde_id + "</a>";
} else {
sonde_id_data.id = "<a href='http://sondehub.org/card/" + sonde_id + "' target='_blank'>" + sonde_id + "</a>";
}
} else if (autorx_config.aprs_enabled == true && autorx_config.aprs_server == "radiosondy.info") {
sonde_id_data.id = "<a href='https://radiosondy.info/sonde_archive.php?sondenumber=" + sonde_id + "' target='_blank'>" + sonde_id + "</a>";
} else if (autorx_config.aprs_enabled == true) {
sonde_id_data.id = "<a href='https://aprs.fi/#!call=" + sonde_id + "&timerange=3600&tail=3600' target='_blank'>" + sonde_id + "</a>";
}
sonde_id_data.realid = sonde_id;
// Add SNR data, if it exists.
if (sonde_id_data.hasOwnProperty('snr')){
sonde_id_data.snr = sonde_id_data.snr.toFixed(1);
}
// Add data into the 'other' field.
sonde_id_data.other = "";
// Burst timer for RS41s
if (sonde_id_data.hasOwnProperty('bt')){
if ((sonde_id_data.bt >= 0) && (sonde_id_data.bt < 65535)) {
sonde_id_data.other += "BT " + new Date(sonde_id_data.bt*1000).toISOString().substr(11, 8) + " ";
}
}
if (sonde_id_data.hasOwnProperty('batt')){
sonde_id_data.other += sonde_id_data.batt.toFixed(1) + " V";
}
telem_data.push(sonde_id_data);
});
}
table.updateOrAddData(telem_data);
// Hide table page navigation if only one page.
if(table.getPageMax() == 1){
$(".tabulator-footer").hide();
}else{
$(".tabulator-footer").show();
}
}
// Invalidate map size to fix problems with elements resizing.
mymap.invalidateSize();
var initial_load_complete = false;
selected_sonde = "";
$.ajax({ // Get archived data.
url: "/get_telemetry_archive",
dataType: 'json',
async: true,
success: function(data) {
for (sonde_id in data){
var telem = data[sonde_id].latest_telem;
sonde_positions[sonde_id] = {
latest_data: telem,
age: 0,
colour: colour_values[colour_idx]
};
// Create markers
sonde_positions[sonde_id].path = L.polyline(data[sonde_id].path,{title:telem.id + " Path", color:sonde_positions[sonde_id].colour}).addTo(mymap);
sonde_positions[sonde_id].marker = L.marker([telem.lat, telem.lon, telem.alt],{title:telem.id, icon: sondeAscentIcons[sonde_positions[sonde_id].colour]}).bindTooltip(sonde_id,{permanent:false,direction:'right'}).addTo(mymap);
if(autorx_config.station_lat != 0.0){
sonde_positions[sonde_id].los_path = L.polyline([],
{
color:los_color,
opacity:los_opacity
}
).addTo(mymap);
}
if (telem.vel_v < 0){
sonde_positions[sonde_id].marker.setIcon(sondeDescentIcons[sonde_positions[sonde_id].colour]);
}
colour_idx = (colour_idx+1)%colour_values.length;
}
updateTelemetryTable();
initial_load_complete = true;
}
});
socket.on('station_update', function(msg) {
// Station update messages indicate a move of the station location, as updated
// by a GPS receiver.
if(initial_load_complete == false){
// If we have not completed our initial load of telemetry data, discard this data.
return
}
// Update the marker position.
home_marker.setLatLng([msg.lat, msg.lon, msg.alt]).update();
// Update the autorx_config object, which is used to calculate relative look angles for the telemetry table.
autorx_config.station_lat = msg.lat;
autorx_config.station_lon = msg.lon;
autorx_config.station_alt = msg.alt;
});
socket.on('telemetry_event', function(msg) {
// Telemetry Event messages contain the entire telemetry dictionary, as produced by the SondeDecoder class.
// This includes the fields: ['frame', 'id', 'datetime', 'lat', 'lon', 'alt', 'temp', 'type', 'freq', 'freq_float']
if(initial_load_complete == false){
// If we have not completed our initial load of telemetry data, discard this data.
return
}
// Have we seen this sonde before?
if (sonde_positions.hasOwnProperty(msg.id) == false){
// Nope, add a property to the sonde_positions object, and setup markers for the sonde.
sonde_positions[msg.id] = {
latest_data : msg,
age : Date.now(),
colour : colour_values[colour_idx]
};
// Create markers
sonde_positions[msg.id].path = L.polyline([[msg.lat, msg.lon, msg.alt]],{title:msg.id + " Path", color:sonde_positions[msg.id].colour}).addTo(mymap);
sonde_positions[msg.id].marker = L.marker([msg.lat, msg.lon, msg.alt],{title:msg.id, icon: sondeAscentIcons[sonde_positions[msg.id].colour]})
.bindTooltip(msg.id,{permanent:false,direction:'right'})
.addTo(mymap);
// If there is a station location defined, show the path from the station to the sonde.
if(autorx_config.station_lat != 0.0){
sonde_positions[msg.id].los_path = L.polyline([[autorx_config.station_lat, autorx_config.station_lon],[msg.lat, msg.lon]],
{
color:los_color,
opacity:los_opacity
}
).addTo(mymap);
}
colour_idx = (colour_idx+1)%colour_values.length;
// If this is our first sonde since the browser has been opened, follow it.
if (Object.keys(sonde_positions).length == 1){
sonde_positions[msg.id].following = true;
}
} else {
// Yep - update the sonde_positions entry.
sonde_positions[msg.id].latest_data = msg;
sonde_positions[msg.id].age = Date.now();
sonde_positions[msg.id].path.addLatLng([msg.lat, msg.lon, msg.alt]);
sonde_positions[msg.id].marker.setLatLng([msg.lat, msg.lon, msg.alt]).update();
if (msg.vel_v < 0){
if (selected_sonde == msg.id) {
sonde_positions[msg.id].marker.setIcon(sondeDescentIcons['white']);
} else {
sonde_positions[msg.id].marker.setIcon(sondeDescentIcons[sonde_positions[msg.id].colour]);
}
}else{
if (selected_sonde == msg.id) {
sonde_positions[msg.id].marker.setIcon(sondeAscentIcons['white']);
} else {
sonde_positions[msg.id].marker.setIcon(sondeAscentIcons[sonde_positions[msg.id].colour]);
}
}
if(autorx_config.station_lat != 0.0){
sonde_positions[msg.id].los_path.setLatLngs([[autorx_config.station_lat, autorx_config.station_lon],[msg.lat, msg.lon]]);
}
}
// Update the telemetry table display
//updateTelemetryText();
updateTelemetryTable();
// Are we currently following any other sondes?
if (sonde_currently_following == "none"){
// If not, follow this one!
sonde_currently_following = msg.id;
}
// Is sonde following enabled?
if (document.getElementById("sondeAutoFollow").checked == true){
// If we are currently following this sonde, snap the map to it.
if (msg.id == sonde_currently_following){
mymap.panTo([msg.lat,msg.lon]);
}
}
});
// Sonde-Following Logic. May need to adjust timeouts.
var sonde_follow_timeout = 30000; // 30 Seconds - reasonable timeout.
// Every X seconds, check if the currently followed sonde is still getting regular data.
// If not, clear the currently_following flag to allow another sonde to be auto tracked.
window.setInterval(function () {
if (sonde_currently_following == "none"){
return;
}
var now_time = Date.now();
if ( (now_time-sonde_positions[sonde_currently_following].age) > sonde_follow_timeout){
sonde_currently_following = "none";
}
}, sonde_follow_timeout);
// Automatically gets the latest version number and GitHub URL
$.ajax({
url:"https://api.github.com/repos/projecthorus/radiosonde_auto_rx/releases",
dataType: "jsonp",
success : function( returndata )
{
$("#footertext").html("<a href='" + returndata['data'][0].html_url + "' target='_blank' style='text-decoration:none;color:white;'>" + returndata['data'][0].tag_name.substring(1) + "</a>");
}
});
// Update telemetry table every second (this is mainly to update the age field)
window.setInterval(function(){
updateTelemetryTable();
}, 1000);
// Tell program we are connected and ready for data.
socket.on('connect', function() {
socket.emit('client_connected', {data: 'I\'m connected!'});
});
// Function to change table columns visible.
$(document).on('change', 'form input', function() {
var checked = $(this).is(":checked");
if (checked == false) {
var cookiesend = 'false';
} else {
var cookiesend = 'true';
}
// Set cookie for columns to show in future.
setCookie("col" + index, cookiesend, 365);
var index = $(this).attr("class");
// Update Tabulator table with selected columns visible.
if(checked) {
switch(index) {
case "0":
table.showColumn("sdr_device_idx");
break;
case "1":
table.showColumn("age");
break;
case "2":
table.showColumn("type");
break;
case "3":
table.showColumn("freq");
break;
case "4":
table.showColumn("id");
break;
case "5":
table.showColumn("datetime");
break;
case "6":
table.showColumn("frame");
break;
case "7":
table.showColumn("lat");
break;
case "8":
table.showColumn("lon");
break;
case "9":
table.showColumn("alt");
break;
case "10":
table.showColumn("vel_h");
break;
case "11":
table.showColumn("vel_v");
break;
case "12":
table.showColumn("temp");
break;
case "13":
table.showColumn("humidity");
break;
case "14":
table.showColumn("azimuth");
break;
case "15":
table.showColumn("elevation");
break;
case "16":
table.showColumn("range");
break;
case "17":
table.showColumn("snr");
break;
case "18":
table.showColumn("other");
break;
}
table.redraw();
} else {
switch(index) {
case "0":
table.hideColumn("sdr_device_idx");
break;
case "1":
table.hideColumn("age");
break;
case "2":
table.hideColumn("type");
break;
case "3":
table.hideColumn("freq");
break;
case "4":
table.hideColumn("id");
break;
case "5":
table.hideColumn("datetime");
break;
case "6":
table.hideColumn("frame");
break;
case "7":
table.hideColumn("lat");
break;
case "8":
table.hideColumn("lon");
break;
case "9":
table.hideColumn("alt");
break;
case "10":
table.hideColumn("vel_h");
break;
case "11":
table.hideColumn("vel_v");
break;
case "12":
table.hideColumn("temp");
break;
case "13":
table.hideColumn("humidity");
break;
case "14":
table.hideColumn("azimuth");
break;
case "15":
table.hideColumn("elevation");
break;
case "16":
table.hideColumn("range");
break;
case "17":
table.hideColumn("snr");
break;
case "18":
table.hideColumn("other");
break;
}
table.redraw();
}
});
// Runs once at page load to set which Tabulator columns to show/hide per set cookies
for (i = 0; i < 19; i++) {
var show = getCookie("col"+i);
if (show == 'false') {
document.getElementById("checkbox" + i).checked = false;
switch(i) {
case 0:
table.hideColumn("sdr_device_idx");
break;
case 1:
table.hideColumn("age");
break;
case 2:
table.hideColumn("type");
break;
case 3:
table.hideColumn("freq");
break;
case 4:
table.hideColumn("id");
break;
case 5:
table.hideColumn("datetime");
break;
case 6:
table.hideColumn("frame");
break;
case 7:
table.hideColumn("lat");
break;
case 8:
table.hideColumn("lon");
break;
case 9:
table.hideColumn("alt");
break;
case 10:
table.hideColumn("vel_h");
break;
case 11:
table.hideColumn("vel_v");
break;
case 12:
table.hideColumn("temp");
break;
case 13:
table.hideColumn("humidity");
break;
case 14:
table.hideColumn("azimuth");
break;
case 15:
table.hideColumn("elevation");
break;
case 16:
table.hideColumn("range");
break;
case 17:
table.hideColumn("snr");
break;
case 18:
table.hideColumn("other");
break;
}
} else if (show == 'true') {
document.getElementById("checkbox" + i).checked = true;
} else {
if (($( window ).width()/$( window ).height()) > 1) {
document.getElementById("checkbox" + i).checked = true;
} else { // If no cookies are set on mobile device show limited number for better experience.
if ([1,4,9,16].includes(i)) {
setCookie("col" + i, 'true', 365);
document.getElementById("checkbox" + i).checked = true;
}
if ([0,2,3,5,6,7,8,10,11,12,13,14,15,17,18].includes(i)) {
setCookie("col" + i, 'false', 365);
document.getElementById("checkbox" + i).checked = false;
switch(i) {
case 0:
table.hideColumn("sdr_device_idx");
break;
case 1:
table.hideColumn("age");
break;
case 2:
table.hideColumn("type");
break;
case 3:
table.hideColumn("freq");
break;
case 4:
table.hideColumn("id");
break;
case 5:
table.hideColumn("datetime");
break;
case 6:
table.hideColumn("frame");
break;
case 7:
table.hideColumn("lat");
break;
case 8:
table.hideColumn("lon");
break;
case 9:
table.hideColumn("alt");
break;
case 10:
table.hideColumn("vel_h");
break;
case 11:
table.hideColumn("vel_v");
break;
case 12:
table.hideColumn("temp");
break;
case 13:
table.hideColumn("humidity");
break;
case 14:
table.hideColumn("azimuth");
break;
case 15:
table.hideColumn("elevation");
break;
case 16:
table.hideColumn("range");
break;
case 17:
table.hideColumn("snr");
break;
case 18:
table.hideColumn("other");
break;
}
}
}
}
}
table.redraw();
});
// Function to open/close left log menu along with adjusting other elements so they render correctly.
function changeNav() {
var x = document.getElementById("closebtn");
var y = document.getElementById('mapid');
if (document.getElementById("mySidenav").style.width == "0px" || document.getElementById("mySidenav").style.width == 0) {
var myDiv = document.getElementById('sidenavtable');
myDiv.scrollTop = 0;
if ((window.innerWidth/window.innerHeight) > 1) { // 350px wide on desktop.
x.style.display = "none";
if (getCookie('map') == true || document.getElementById("showmapbutton").checked == true) {
y.style.display = "block";
}
document.getElementById("mySidenav").style.width = "350px";
document.getElementById("main").style.marginLeft = "350px";
document.getElementById("mySidenav").style.borderRadius = "0px 25px 25px 0px";
mymap.invalidateSize();
setTimeout(scan_chart_obj.resize,500);
} else { // Fullsize on mobile.
x.style.display = "block";
y.style.display = "none";
document.getElementById("mySidenav").style.width = "100%";
document.getElementById("main").style.marginLeft = "0";
document.getElementById("mySidenav").style.borderRadius = "0px";
}
} else {
x.style.display = "none";
if (getCookie('map') == true || document.getElementById("showmapbutton").checked == true) {
y.style.display = "block";
}
document.getElementById("mySidenav").style.width = 0;
document.getElementById("main").style.marginLeft = 0;
mymap.invalidateSize();
setTimeout(scan_chart_obj.resize,500);
}
}
// Function to open/close right settings menu along with adjusting other elements so they render correctly.
function changeSettings() {
var y = document.getElementById('mapid');
if (document.getElementById("mySettings").style.width == "0px" || document.getElementById("mySettings").style.width == 0) {
if ((window.innerWidth/window.innerHeight) > 1) { // 350px wide on desktop.
if (getCookie('map') == true || document.getElementById("showmapbutton").checked == true) {
y.style.display = "block";
}
document.getElementById("mySettings").style.width = "350px";
document.getElementById("main").style.marginRight = "350px";
document.getElementById("mySettings").style.borderRadius = "25px 0px 0px 25px";
mymap.invalidateSize();
setTimeout(scan_chart_obj.resize,500);
} else { // Fullsize on mobile.
y.style.display = "none";
document.getElementById("mySettings").style.width = "100%";
document.getElementById("main").style.marginRight = "0";
document.getElementById("mySettings").style.borderRadius = "0px";
}
} else {
if (getCookie('map') == true || document.getElementById("showmapbutton").checked == true) {
y.style.display = "block";
}
document.getElementById("mySettings").style.width = 0;
document.getElementById("main").style.marginRight = 0;
mymap.invalidateSize();
setTimeout(scan_chart_obj.resize,500);
}
}
// Show/hide map on button press and update cookies.
function showMap(element) {
if (element.checked == false) {
document.getElementById("mapid").style.display = "none";
setCookie("map", 'false', 365);
} else {
document.getElementById("mapid").style.display = "block";
setCookie("map", 'true', 365);
mymap.invalidateSize();
}
}
// Show/hide scan chart on button press and update cookies.
function showScan(element) {
if (element.checked == false) {
document.getElementById("scanid").style.display = "none";
setCookie("scan", 'false', 365);
mymap.invalidateSize();
} else {
document.getElementById("scanid").style.display = "block";
setCookie("scan", 'true', 365);
mymap.invalidateSize();
setTimeout(scan_chart_obj.resize,500);
}
}
// Enable/disable auto follow on button press and update cookies.
function autoFollow(element) {
if (element.checked == false) {
setCookie("follow", 'false', 365);
} else {
setCookie("follow", 'true', 365);
}
}
// Show/hide table on button press and update cookies.
function showTable(element) {
if (element.checked == false) {
document.getElementById("tableid").style.display = "none";
setCookie("table", 'false', 365);
} else {
document.getElementById("tableid").style.display = "block";
setCookie("table", 'true', 365);
}
}
// Set given cookie name and value.
function setCookie(name,value,days) {
var expires = "";
if (days) {
var date = new Date();
date.setTime(date.getTime() + (days*24*60*60*1000));
expires = "; expires=" + date.toUTCString();
}
document.cookie = name + "=" + (value || "") + expires + "; path=/";
}
// Return cookie value given name.
function getCookie(name) {
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for(var i=0;i < ca.length;i++) {
var c = ca[i];
while (c.charAt(0)==' ') c = c.substring(1,c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
}
return null;
}
// Reset specific cookie.
function eraseCookie(name) {
document.cookie = name +'=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
}
// Reset all cookies.
function deleteAllCookies() {
var cookies = document.cookie.split(";");
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i];
var eqPos = cookie.indexOf("=");
var name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie;
document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT";
}
location.reload();
}
</script>
</head>
<body>
<!-- Wrapper for entire body to ensure flex works -->
<div class="wrapper">
<!-- Wrapper for log sidebar -->
<div id="mySidenav" class="sidenav">
<div class="headerdiv">
<a href="javascript:void(0)" class="closebtn" id="closebtn" onclick="changeNav()">&#10006;</a>
<img src="{{ url_for('static', filename='img/autorx_logo.png') }}" alt="Radiosonde Auto-RX Button">
<h2>Log</h2>
</div>
<div class="sidenavtable" id="sidenavtable">
<table style="width:100%" id="log_data">
<tbody>
</tbody>
</table>
</div>
<div class="footerdiv">
<p class="footer">Version: <span id="currentversion">???</span> (latest: <span id="footertext">???</span>)</p>
<br>
</div>
</div>
<!-- Wrapper for settings sidebar -->
<div id="mySettings" class="settings">
<a href="javascript:void(0)" class="closebtn2" id="closebtn2" onclick="changeSettings()">&#10006;</a>
<br><h2>Settings</h2>
<div class="scrollsettings">
<h2>Table Options</h2>
<form>
<input type="checkbox" class="0" id="checkbox0" checked>
<label> SDR</label><br>
<input type="checkbox" class="1" id="checkbox1" checked>
<label> Age</label><br>
<input type="checkbox" class="2" id="checkbox2" checked>
<label> Type</label><br>
<input type="checkbox" class="3" id="checkbox3" checked>
<label> Frequency</label><br>
<input type="checkbox" class="4" id="checkbox4" checked>
<label> ID</label><br>
<input type="checkbox" class="5" id="checkbox5" checked>
<label> Time</label><br>
<input type="checkbox" class="6" id="checkbox6" checked>
<label> Frame</label><br>
<input type="checkbox" class="7" id="checkbox7" checked>
<label> Latitude</label><br>
<input type="checkbox" class="8" id="checkbox8" checked>
<label> Longitude</label><br>
<input type="checkbox" class="9" id="checkbox9" checked>
<label> Altitude</label><br>
<input type="checkbox" class="10" id="checkbox10" checked>
<label> Velocity</label><br>
<input type="checkbox" class="11" id="checkbox11" checked>
<label> Ascension</label><br>
<input type="checkbox" class="12" id="checkbox12" checked>
<label> Temperature</label><br>
<input type="checkbox" class="13" id="checkbox13" checked>
<label> Humidity</label><br>
<input type="checkbox" class="14" id="checkbox14" checked>
<label> Azimuth</label><br>
<input type="checkbox" class="15" id="checkbox15" checked>
<label> EI</label><br>
<input type="checkbox" class="16" id="checkbox16" checked>
<label> Range</label><br>
<input type="checkbox" class="17" id="checkbox17" checked>
<label> SNR</label><br>
<input type="checkbox" class="18" id="checkbox18" checked>
<label> Other</label><br><br>
</form>
<div style="margin-left:60px;">
<h2 style="display:inline;vertical-align:middle;">Show Table</h2>
&nbsp;
<div style="display:inline;vertical-align:middle;">
<label class="switch">
<input type="checkbox" onchange="showTable(this)" id="showtablebutton">
<span class="slider round"></span>
</label>
</div>
<br>
<br>
<h2 style="display:inline;vertical-align:middle;">Show Scan Plot</h2>
&nbsp;
<div style="display:inline;vertical-align:middle;">
<label class="switch">
<input type="checkbox" onchange="showScan(this)" id="showscanbutton">
<span class="slider round"></span>
</label>
</div>
<br>
<br>
<h2 style="display:inline;vertical-align:middle;">Show Map</h2>
&nbsp;
<div style="display:inline;vertical-align:middle;">
<label class="switch">
<input type="checkbox" onchange="showMap(this)" id="showmapbutton">
<span class="slider round"></span>
</label>
</div>
<br>
<br>
<h2 style="display:inline;vertical-align:middle;">Dark Mode</h2>
&nbsp;
<div style="display:inline;vertical-align:middle;">
<label class="switch">
<input type="checkbox" id="showdarkbutton">
<span class="slider round"></span>
</label>
</div>
<br>
<br>
<h2 style="display:inline;vertical-align:middle;">Show UTC Time</h2>
&nbsp;
<div style="display:inline;vertical-align:middle;">
<label class="switch">
<input type="checkbox" id="showUTCbutton">
<span class="slider round"></span>
</label>
</div>
<br>
<br>
<h2 style="display:inline;vertical-align:middle;">Set Pagination Size</h2>
&nbsp;
<div style="display:inline;vertical-align:middle;">
<select id="paginationSelector">
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
<option value="4">Four</option>
<option value="5">Five</option>
<option value="6">Six</option>
<option value="7">Seven</option>
<option value="8">Eight</option>
<option value="9">Nine</option>
<option value="10">Ten</option>
</select>
</div>
<br>
<br>
<h2 style="display:inline;vertical-align:middle;">Follow Sonde</h2>
&nbsp;
<div style="display:inline;vertical-align:middle;">
<label class="switch">
<input type="checkbox" onchange="autoFollow(this)" id="sondeAutoFollow">
<span class="slider round"></span>
</label>
</div>
<br>
<br>
<h2 style="display:inline;vertical-align:middle;">Live KML</h2>
&nbsp;
<div style="display:inline;vertical-align:middle;">
<button onclick="window.location.href='/rs.kml'">SHOW</button>
</div>
<br>
<br>
<h2 style="display:inline;vertical-align:middle;">Reset Page</h2>
&nbsp;
<div style="display:inline;vertical-align:middle;">
<button onclick="deleteAllCookies()">RESET</button>
</div>
</div>
</div>
</div>
<!-- Wrapper for main screen -->
<div id="main" onload="loadMap();">
<span style="font-size:3vh;cursor:pointer;" onclick="changeNav()">&#9776; Radiosonde Auto-RX</span>
<p style="font-size:2vh;">Current Task: <span id="task_status">???</span></p>
<div id="tableid">
<div id="telem_table"></div>
</div>
<div id="scanid">
<h2>Scan Results:</h2>
<div id='scan_results'>No scan data yet...</div>
<div id="scan_chart" style="width:100%;"></div>
</div>
<br>
<div id="mapid"></div>
<i id="myBtn" onclick="changeSettings()" class="fa fa-gear" style="font-size:4vh;"></i>
</div>
</div>
</body>
</html>