rdz_ttgo_sonde_dl9rdz/RX_FSK/data/livemap.js

495 lines
18 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

try {
var check = $(document);
} catch (e) {
document.addEventListener("DOMContentLoaded", function(event) {
document.getElementById('map').innerHTML = '<br /><br />In order to use this functionality, there must be an internet connection.<br /><br/><a href="livemap.html">retry</a><br /><br/><a href="index.html">go back</a>';
});
}
$(document).ready(function(){
var map = L.map('map', { attributionControl: false, zoomControl: false });
map.on('mousedown touchstart',function () { follow=false; });
L.control.scale().addTo(map);
L.control.attribution({prefix:false}).addTo(map);
var osm = L.tileLayer('https://{s}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png', {
attribution: '<div><a href="https://leafletjs.com/">Leaflet</a> &middot; Map: <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a></div>',
minZoom: 1,
maxZoom: 19
});
var esri = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
attribution: '<div><a href="https://leafletjs.com/">Leaflet</a> &middot; Map: <a href="https://www.esri.com/">Esri</a> &middot; Earthstar Geographics</div>',
minZoom: 1,
maxZoom: 20
});
var basemap = 'osm';
osm.addTo(map);
basemap_change = function () {
if (basemap == 'osm') {
map.removeLayer(osm);
map.addLayer(esri);
basemap = 'esri';
} else {
map.removeLayer(esri);
map.addLayer(osm);
basemap = 'osm';
}
};
if(mapcenter) map.setView(mapcenter, 5);
else map.setView([51.163361,10.447683], 5); // Mitte DE
var reddot = '<span class="ldot rbg"></span>';
var yellowdot = '<span class="ldot ybg"></span>';
var greendot = '<span class="ldot gbg"></span>';
$('#map .leaflet-control-container').append(L.DomUtil.create('div', 'leaflet-top leaflet-center leaflet-header'));
var header = '';
header += '<div id="sonde_main"><b>rdzTTGOSonde LiveMap</b><br />🎈 <b><span id="sonde_id"></span> - <span id="sonde_freq"></span> MHz - <span id="sonde_type"></span></b></div>';
header += '<div id="sonde_detail"><span id="sonde_alt"></span>m | <span id="sonde_climb"></span>m/s | <span id="sonde_speed"></span>km/h</div>';
header += '<div id="sonde_status"><span id="sonde_statbar"></span></div>';
header += '<div id="settings"><br /><b>Prediction-Settings</b><br />';
header += '<label for="burst">Burst at:</label><input type="text" id="burst" maxlength="5" value="..." /> m<br />';
header += '<label for="overwrite_descend">Descending:</label><input type="text" id="overwrite_descend" maxlength="2" value="..." /> m/s<br />';
header += '<label for="overwrite_descend_till">Use this descending until:</label><input type="text" id="overwrite_descend_till" maxlength="5" value="..." /> m<br />';
header += '<small>after the transmitted descend will be used</small>';
header += '<div id="submit"><input type="button" value="save" onclick="settings_save();"/>&nbsp;&nbsp;&nbsp;<input type="button" id="submit" value="reset" onclick="settings_reset();"/></div>';
header += '</div>';
$('.leaflet-header').append(header);
$('#map .leaflet-control-container').append(L.DomUtil.create('div', 'leaflet-bottom leaflet-center leaflet-footer'));
var footer = '';
footer += '<div id="gps_main"><b>Direction: </b><span class="gps_dir">...</span>°<br /><b>Distance: </b><span class="gps_dist">...</span>m</div>';
$('.leaflet-footer').append(footer);
var statbar = '';
headtxt = function(data,stat) {
console.log(data);
var staticon = (stat == '1')?greendot:yellowdot;
statbar = staticon + statbar;
if ((statbar.length) > 10*greendot.length) { statbar = statbar.substring(0,10*greendot.length); }
if (data.lat == '0.000000') { return false; }
if (data.res == 0) {
$('#sonde_id').html(data.id);
$('#sonde_alt').html(data.alt);
$('#sonde_climb').html(data.climb);
$('#sonde_speed').html( mr(data.speed * 3.6 * 10) / 10 );
$('#sonde_detail').show();
} else {
$('#sonde_id').html(data.launchsite.trim());
$('#sonde_detail').hide();
}
$('#sonde_freq').html(data.freq);
$('#sonde_type').html(data.type);
$('#sonde_statbar').html('&nbsp;'+statbar);
};
map.addControl(new L.Control.Button([ { position: 'topleft', text: '🔙', href: 'index.html' } ]));
L.control.zoom({ position:'topleft' }).addTo(map);
map.addControl(new L.Control.Button([ { position: 'topleft', text: '🗺️', href: 'javascript:basemap_change();' } ]));
map.addControl(new L.Control.Button([ { position: 'topright', id: "status", text: '', href: 'javascript:get_data();' } ]));
map.addControl(new L.Control.Button([
{ position:'topright', text: '🎈', href: 'javascript:show(marker,\'marker\');' },
{ text: '〰️', href: 'javascript:show_line();' },
{ text: '💥', href: 'javascript:show(marker_burst,\'burst\');' },
{ text: '🎯', href: 'javascript:show(marker_landing,\'landing\');' }
]));
map.addControl(new L.Control.Button([ { position:'topright', text: '⚙️', href: 'javascript:show_settings();' } ]));
show = function(e,p) {
if (p == 'landing') { get_predict(last_data); }
if (e) {
map.closePopup();
map.setView(map._layers[e._leaflet_id].getLatLng());
map._layers[e._leaflet_id].openPopup();
follow = p;
}
};
getTwoBounds = function (a,b) {
var sW = new L.LatLng((a._southWest.lat > b._southWest.lat)?b._southWest.lat:a._southWest.lat, (a._southWest.lng > b._southWest.lng)?b._southWest.lng:a._southWest.lng);
var nE = new L.LatLng((a._northEast.lat < b._northEast.lat)?b._northEast.lat:a._northEast.lat, (a._northEast.lng < b._northEast.lng)?b._northEast.lng:a._northEast.lng);
return new L.LatLngBounds(sW, nE);
};
show_line = function() {
$('.i_position, .i_landing').remove();
map.closePopup();
if (line._latlngs.length != 0 && line_predict._latlngs.length != 0) {
map.fitBounds(getTwoBounds(line.getBounds(),line_predict.getBounds()));
} else if (line._latlngs.length != 0) {
map.fitBounds(line.getBounds());
} else if (line_predict._latlngs.length != 0) {
map.fitBounds(line_predict.getBounds());
}
};
last_data = false;
follow = 'marker';
marker_landing = false;
icon_landing = L.divIcon({className: 'leaflet-landing'});
dots_predict = [];
line_predict = L.polyline(dots_predict,{color: 'yellow'}).addTo(map);
marker_burst = false;
icon_burst = L.divIcon({className: 'leaflet-burst'});
marker = false;
dots = [];
line = L.polyline(dots).addTo(map);
draw = function(data) {
var stat;
console.log(data);
if (data.id) {
// data.res: 0: ok 1: no rx (timeout), 2: crc err, >2 some other error
if ((data.lat != '0.000000' && data.lon != '0.000000') && (data.res==0)) { //JSON.stringify(data) != JSON.stringify(last_data)) ) {
var location = [data.lat,data.lon,data.alt];
if (!marker) {
map.setView(location, 14);
marker = L.marker(location).addTo(map)
.bindPopup(poptxt('position',data),{closeOnClick:false, autoPan:false}).openPopup();
get_predict(data);
} else {
marker.slideTo(location, {
duration: 500,
keepAtCenter: (follow=='marker')?true:false
})
.setPopupContent(poptxt('position',data));
if (last_data.id != data.id) {
storage_remove();
dots = [];
get_predict(data);
}
}
dots.push(location);
line.setLatLngs(dots);
storage_write(data);
$('#status').html(greendot);
stat = 1;
} else {
$('#status').html(yellowdot);
stat = 0;
}
headtxt(data,stat);
last_data = data;
} else {
$('#status').html(yellowdot);
headtxt(data,0);
}
};
marker_gps = false;
icon_gps = L.divIcon({className: 'leaflet-gps'});
circ_gps = false;
gps = function(e) {
gps_location = [e.lat/1000000,e.lon/1000000];
gps_accuracy = e.hdop*2;
if (last_data && last_data.lat != '0.000000') {
if ($('.leaflet-footer').css('display') == 'none') { $('.leaflet-footer').show(); }
var distance = Math.round(map.distance(gps_location,[last_data.lat, last_data.lon]));
distance = (distance > 1000)?(distance / 1000) + 'k':distance;
$('.leaflet-footer .gps_dist').html(distance);
$('.leaflet-footer .gps_dir').html( bearing(gps_location,[last_data.lat, last_data.lon]) );
}
if (!marker_gps) {
map.addControl(new L.Control.Button([{ position: 'topleft', text: '🛰️', href: 'javascript:show(marker_gps,\'gps\');' }]));
marker_gps = L.marker(gps_location,{icon:icon_gps}).addTo(map)
.bindPopup(poptxt('gps',e),{closeOnClick:false, autoPan:false});
circ_gps = L.circle(gps_location, gps_accuracy).addTo(map);
} else {
marker_gps.slideTo(gps_location, {
duration: 500,
keepAtCenter: (follow=='gps')?true:false
})
.setPopupContent(poptxt('gps',e));
circ_gps.slideTo(gps_location, { duration: 500 });
circ_gps.setRadius(gps_accuracy);
}
};
get_data = function() {
$('#status').html(reddot);
console.log("get_data called");
$.ajax({url: 'live.json', success: (function( data ) {
if (typeof data != "object") { data = $.parseJSON(data); }
console.log(data);
if (data.sonde) {
draw(data.sonde);
} else {
setTimeout(function() {$('#status').html(yellowdot);},100);
}
if (data.gps) {
gps(data.gps);
}
}),
timeout: 1000}
);
};
storage = (typeof(Storage) !== "undefined")?true:false;
settings_std = {
burst: 32500,
overwrite_descend: 6,
overwrite_descend_till: 12000
};
settings_read = function() {
if (storage) {
if (sessionStorage.settings) {
return JSON.parse(sessionStorage.settings);
} else {
settings_write(settings_std);
return settings_std;
}
} else {
return settings_std;
}
return false;
};
settings_write = function (data) {
if (storage) {
sessionStorage.settings = JSON.stringify(data);
settings = data;
}
};
settings = settings_read();
settings_save = function() {
settings.burst = parseInt($('#settings #burst').val());
settings.overwrite_descend = parseInt($('#settings #overwrite_descend').val());
settings.overwrite_descend_till = parseInt($('#settings #overwrite_descend_till').val());
if (Number.isInteger(settings.burst) && Number.isInteger(settings.overwrite_descend) && Number.isInteger(settings.overwrite_descend_till)) {
settings_write(settings);
$("#settings").slideUp();
get_predict(last_data);
} else {
alert('Error: only numeric values allowed!');
}
};
settings_reset = function() {
if (confirm('Reset to default?')) {
settings_write(settings_std);
show_settings();
}
};
show_settings = function() {
$('#settings #burst').val(settings.burst);
$('#settings #overwrite_descend').val(settings.overwrite_descend);
$('#settings #overwrite_descend_till').val(settings.overwrite_descend_till);
$("#settings").slideToggle();
};
predictor = false;
get_predict = function(data) {
if (!data) { return; }
var ascent = (data.climb > 0)? data.climb : 15;
var descent = (data.climb > 0)? settings.overwrite_descend : data.climb * -1;
var burst;
if (data.climb > 0) {
burst = (data.alt > settings.burst )?data.alt + 100 : settings.burst;
} else {
burst = parseInt(data.alt) + 7;
if (data.alt > settings.overwrite_descend_till ) { descent = settings.overwrite_descend; }
}
var m = new Date();
var datetime = m.getUTCFullYear() + "-" + az(m.getUTCMonth()+1) + "-" + az(m.getUTCDate()) + "T" +
az(m.getUTCHours()) + ":" + az(m.getUTCMinutes()) + ":" + az(m.getUTCSeconds()) + "Z";
var url = 'https://predict.cusf.co.uk/api/v1/';
url += '?launch_latitude='+data.lat + '&launch_longitude='+tawhiri_lon(data.lon);
url += '&launch_altitude='+data.alt + '&launch_datetime='+datetime;
url += '&ascent_rate='+ascent + '&burst_altitude=' + burst + '&descent_rate='+descent;
$.getJSON(url, function( prediction ) {
draw_predict(prediction,data);
});
};
draw_predict = function(prediction,data) {
var ascending = prediction.prediction[0].trajectory;
var highest = ascending[ascending.length-1];
var highest_location = [highest.latitude,sanitize_lon(highest.longitude)];
var descending = prediction.prediction[1].trajectory;
var landing = descending[descending.length-1];
var landing_location = [landing.latitude,sanitize_lon(landing.longitude)];
if (!marker_landing) {
marker_landing = L.marker(landing_location,{icon: icon_landing}).addTo(map)
.bindPopup(poptxt('landing',landing),{closeOnClick:false, autoPan:false});
} else {
marker_landing.slideTo(landing_location, {
duration: 500,
keepAtCenter: (follow=='landing')?true:false
})
.setPopupContent(poptxt('landing',landing));
}
dots_predict=[];
if (data.climb > 0) {
ascending.forEach(p => dots_predict.push([p.latitude,sanitize_lon(p.longitude)]));
if (!marker_burst) {
marker_burst = L.marker(highest_location,{icon:icon_burst}).addTo(map).bindPopup(poptxt('burst',highest),{closeOnClick:false, autoPan:false});
} else {
marker_burst.slideTo(highest_location, {
duration: 500,
keepAtCenter: (follow=='burst')?true:false
}).setPopupContent(poptxt('burst',highest));
}
}
descending.forEach(p => dots_predict.push([p.latitude,sanitize_lon(p.longitude)]));
line_predict.setLatLngs(dots_predict);
if (data.climb > 0) {
predictor_time = 5 * 60; // ascending, every 5 min
} else if (data.climb < 0 && data.alt > 5000) {
predictor_time = 2 * 60; // descending, above 5km, every 2 min
} else {
predictor_time = 30; // descending, below 5km, every 30 sec
}
clearTimeout(predictor);
predictor = setTimeout(function() {get_predict(last_data);}, predictor_time*1000);
};
sanitize_lon = function(lon) {
if (lon > 180) { return lon - 360; }
return lon;
}
tawhiri_lon = function(lon) {
if (lon < 0) { return lon + 360; }
return lon;
}
poptxt = function(t,i) {
var lat_input = (i.id)?i.lat:i.latitude;
var lon_input = sanitize_lon((i.id)?i.lon:i.longitude);
var lat = Math.round(lat_input * 1000000) / 1000000;
var lon = Math.round(lon_input * 1000000) / 1000000;
var add =
'<br /><b>Position:</b> '+lat+', '+lon+'<br />'+
'<b>Open:</b> <a href="https://www.google.de/maps/?q='+lat+', '+lon+'" target="_blank">GMaps</a> | <a href="https://www.openstreetmap.org/?mlat='+lat+'&mlon='+lon+'&zoom=15" target="_blank">OSM</a> | <a href="mapsme://map?ll='+lat+','+lon+'">Maps.me</a>';
if (t == 'position') { return '<div class="i_position"><b>🎈 '+i.id+'</b>'+add+'</div>'; }
if (t == 'burst') { return '<div class="i_burst"><b>💥 Predicted Burst:</b><br />'+fd(i.datetime)+' in '+mr(i.altitude)+'m'+add+'</div>'; }
if (t == 'highest') { return '<div class="i_burst"><b>💥 Burst:</b> '+mr(i.altitude)+'m'+add+'</div>';}
if (t == 'landing') { return '<div class="i_landing"><b>🎯 Predicted Landing:</b><br />'+fd(i.datetime)+' at '+mr(i.altitude)+'m'+add+'</div>'; }
if (t == 'gps') { return '<div class="i_gps">Position: '+(i.lat/1000000)+','+(i.lon/1000000)+'<br />Altitude: '+mr(i.alt/1000)+'m<br />Speed: '+mr(i.speed/1000 * 1.852 * 10)/10+'km/h '+mr(i.dir/1000)+'°<br />Sat: '+i.sat+' Hdop:'+(i.hdop/10)+'</div>'; }
};
fd = function(date) {
var d = new Date(Date.parse(date));
return az(d.getUTCHours()) +':'+ az(d.getUTCMinutes())+' UTC';
};
az = function(n) { return (n<10)?'0'+n:n; };
mr = function(n) { return Math.round(n); };
storage = (typeof(Storage) !== "undefined")?true:false;
storage_write = function (data) {
if (storage) {
if (sessionStorage.sonde) {
storage_data = JSON.parse(sessionStorage.sonde);
} else {
storage_data = [];
}
if (JSON.stringify(data) != JSON.stringify(storage_data[storage_data.length - 1])) {
storage_data.push(data);
sessionStorage.sonde = JSON.stringify(storage_data);
}
}
};
storage_read = function() {
if (storage) {
if (sessionStorage.sonde) {
storage_data = JSON.parse(sessionStorage.sonde);
return storage_data;
}
}
return false;
};
storage_remove = function() {
sessionStorage.removeItem('sonde');
};
session_storage = storage_read();
if (session_storage) {
session_storage.forEach(function(d) {
dots.push([d.lat,d.lon,d.alt]);
session_storage_last = d;
});
draw(session_storage_last);
}
setInterval(get_data,1000);
});
L.Control.Button = L.Control.extend({
onAdd: function (map) {
var container = L.DomUtil.create('div', 'leaflet-bar leaflet-control');
options = this.options;
Object.keys(options).forEach(function(key) {
this.link = L.DomUtil.create('a', '', container);
this.link.text = options[key].text;
this.link.href = options[key].href;
this.link.id = options[key].id;
});
this.options.position = this.options[0].position;
return container;
}
});
// https://github.com/makinacorpus/Leaflet.GeometryUtil/blob/master/src/leaflet.geometryutil.js#L682
// modified to fit
function bearing(latlng1, latlng2) {
var rad = Math.PI / 180,
lat1 = latlng1[0] * rad,
lat2 = latlng2[0] * rad,
lon1 = latlng1[1] * rad,
lon2 = latlng2[1] * rad,
y = Math.sin(lon2 - lon1) * Math.cos(lat2),
x = Math.cos(lat1) * Math.sin(lat2) -
Math.sin(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1);
var bearing = ((Math.atan2(y, x) * 180 / Math.PI) + 360) % 360;
bearing = bearing < 0 ? bearing-360 : bearing;
return Math.round(bearing);
}