/******************************************************************** * Dynamic Receiver Statistic for Radarcape ******************************************************************** * Guenter Koellner Embedded Development GmbH * * Am Rain 24 * 85256 Vierkirchen * Geschaeftsfuehrer Guenter Koellner * http://www.jetvision.de * * References: * https://www.w3schools.com/jsref/default.asp * http://www.chartjs.org/docs/latest/ * http://www.chartjs.org/samples/latest/charts/radar.html * https://github.com/arian/pngjs * https://stackoverflow.com/questions/33902299/using-jquery-ajax-to-download-a-binary-file * https://stackoverflow.com/questions/43010404/chart-js-radar-scaleshowline-for-each-scaleline * https://stackoverflow.com/questions/5422265/how-can-i-pre-populate-html-form-input-fields-from-url-parameters * ********************************************************************/ var cookie_settings_name = "statististics_range"; var PRODUCT_NAME = ""; var verbose = false; var nofBuckets = 40; var settings = { 'maxDistance' : 25, 'rangeMode' : 'Auto', 'rangeBucket' : nofBuckets -1 }; var myChart = null; var myChartMaxDistance = 25; var myChartRangeMode = "Auto"; var containers; // will store [0..2] locssources 0..39 times altitude 360 distance values var refresh_time = 5; // seconds var rangeSlider = null; var altitudeSlider = null; var labels360 = []; for( var deg=0; deg<360; deg++ ) { if ( deg%15 == 0) { labels360.push(deg); } else { labels360.push(''); } } var isContinue = true; // set when the current integration is part of the time interval function bucketToAltitude(bucket) { var alt_ft = bucket*1000 + 500; return alt_ft; } // https://stackoverflow.com/questions/7434371/image-onload-function-with-return // https://stackoverflow.com/questions/5004829/how-to-call-a-member-function-from-another-member-function-in-javascript class PngContainer { constructor(url, locsource) { //console.log('PngContainer::constructor url='+url); this.url = url; this.locsource = locsource; var self = this; var oReq = new XMLHttpRequest(); oReq.open("GET", rcd_globals.fcgipath+url, true); oReq.responseType = "arraybuffer"; oReq.onload = function() { var bytes = oReq.response; var pngReader = new PNGReader(bytes); pngReader.parse(function(err, png){ if( !err ) { self.initialize(png); updateContainers(self); } }); } oReq.send(); } initialize (png) { this.width = ( png.width==360 ) ? 360 : 0; this.height = ( png.height==40 ) ? 40 : 0; if( (this.width==0 ) || (this.height==0) ) { console.log ("invalid PNG size"); return; } // expected width = 360, expected height is 40 //console.log('file='+this.url, 'width='+png.width, 'height='+png.height, 'colorType='+png.colorType, 'bitDepth='+png.bitDepth, 'colors='+png.colors); this.data = []; for( var alt_bucket=0; alt_bucket' + ' End: ' + moment.unix(end ).toISOString() + '
' + ' First file: ' + moment.unix(T_start).utc().format("YYYYMMDDHH") + '
' + ' Last file: ' + moment.unix(T_end ).utc().format("YYYYMMDDHH") + '
' + ' Continued update: ' + ( isContinue ? 'Yes' : 'No' ); document.getElementById('infotext').innerHTML = infotext; clearContainers(); for( var T=T_start; T<=T_end; T+=3600 ) { // filename is the end time of accumulation var fn = moment.unix(T).utc().format("YYYYMMDDHH"); var arr = ['ranges/'+fn+'-v1-ADSB.png', 'ranges/'+fn+'-v1-MLAT.png', 'ranges/'+fn+'-v1-FLARM.png']; arr.forEach( function(url, locsource_index) { var png = new PngContainer(url, locsource_index); }); } } function updateRangeSlider() { var manual_range = parseInt(rangeSlider.value); //console.log ("rangeMode="+myChartRangeMode,"manualRange="+manual_range,"myChartMaxDistance="+myChartMaxDistance,"ticks.max="+myChart.options.scale.ticks.max); document.getElementById("OutputRangeSlider").innerHTML = manual_range +" km"; switch( myChartRangeMode ) { case "Auto": myChart.options.scale.ticks.max = undefined; myChart.update(); break; case "MaxHold": myChart.options.scale.ticks.max = Math.ceil(myChartMaxDistance/25)*25; myChart.update(); break; case "Manual": myChart.options.scale.ticks.max = manual_range; myChart.update(); break; default: throw "unexpected value of myChartRangeMode"; break; } } function updateAltitudeSlider() { var bucket = parseInt(altitudeSlider.value); document.getElementById("OutputAltitudeSlider").innerHTML = bucketToAltitude(bucket) +" ft"; settings.rangeBucket = nofBuckets - bucket - 1; for(var locsource=0; locsource<3; locsource++) updateChart(locsource); } function initializeDateRangePicker(){ var end = moment().utc().add('hour', 1).startOf('hour'); var start = end.clone().add('hour', -24); function cb(start, end) { var fullmonthFormat = 'MMMM D YYYY, HH:mm UTC'; $('#timerange-selector span').html(start.format(fullmonthFormat) + ' — ' + end.format(fullmonthFormat)); } $('#timerange-selector').daterangepicker({ startDate: start, endDate: end, //minDate: undefined, //maxDate: undefined, //maxSpan: undefined, timePicker: true, timePickerIncrement: 60, alwaysShowCalendars: true, timePicker24Hour: true, locale: { format: 'YYYY-MM-DD HH:mm UTC' }, ranges: { 'Today': [moment().utc().startOf("day"), moment().utc().endOf("day")] // further possible } }, cb); // function called on clicking apply $('#timerange-selector').on('apply.daterangepicker', function(ev, picker) { // daterangepicker does not have utc support: https://github.com/dangrossman/bootstrap-daterangepicker/issues/343 var startDateOffset = picker.startDate.utcOffset() var utcStartDate = moment.utc(picker.startDate).utcOffset(-startDateOffset, true).utc() var endDateOffset = picker.endDate.utcOffset() var utcEndDate = moment.utc(picker.endDate).utcOffset(-endDateOffset, true).utc() cb(utcStartDate, utcEndDate); // update label of var begin_ts = utcStartDate.unix(); var end_ts = utcEndDate.unix(); var now = moment().utc().unix(); isContinue = now < end_ts; updatePngContainers(begin_ts, end_ts); }); isContinue = true; updatePngContainers(start/1000, end/1000); cb(start, end); } $(document).ready(function() { getJSON( rcd_globals.fcgipath + "param.json?param=productname", function(params) { PRODUCT_NAME = params.productname; if( PRODUCT_NAME==='MLAT_SERVER' ) { document.title = "Network Range"; } else { document.title = "Receiver Range"; } document.getElementById("page_title").innerHTML = document.title; }); // read cookies // ------------ var tmp = []; var cookie = getCookie(cookie_settings_name); if( cookie ) { $.extend( tmp, JSON.parse(cookie) ); } for (var key in tmp) { if( key in settings ) { settings[key] = tmp[key]; } } altitudeSlider = document.getElementById("AltitudeSlider"); document.getElementById("AltitudeSliderDown").onclick = function() { altitudeSlider.stepDown(); updateAltitudeSlider(); } document.getElementById("AltitudeSliderUp").onclick = function() { altitudeSlider.stepUp(); updateAltitudeSlider(); } altitudeSlider.onchange = function() { updateAltitudeSlider(); } rangeSlider = document.getElementById("RangeSlider"); document.getElementById("RangeSliderDown").onclick = function() { rangeSlider.stepDown(); updateRangeSlider(); } document.getElementById("RangeSliderUp").onclick = function() { rangeSlider.stepUp(); updateRangeSlider(); } rangeSlider.onchange = function() { updateRangeSlider(); } var rangeModeSelectButtons = document.getElementById("RangeModeSelectButtons"); rangeModeSelectButtons.onchange = function() { myChartRangeMode = $("input[type='radio'][name='RangeModeSelectButtons']:checked").val(); updateRangeSlider(); } var color = Chart.helpers.color; clearContainers(); const options25 = { plugins: [{ afterInit: function() { console.log("Chart is ready"); } }], responsive: true, // true: scales with responsiveness, false: scales with pixel size animation: false, legend: { position: 'bottom', }, scale: { // https://www.chartjs.org/docs/latest/axes/radial/linear.html#angle-line-options // https://www.chartjs.org/docs/latest/axes/radial/linear.html#linear-radial-axis // angleLines: { display: true, color: "#00ff00ff", lineWidth: 0.1 }, // https://www.chartjs.org/docs/latest/axes/styling.html // display: true, ticks: { min:0, stepSize: 25, callback: function(value, index, values) { //console.log (value, index, values); return value; } } // this is the maximum distance to be shown } } var dataset_adsb = { label: 'ADS-B', fill: false, borderColor: "#000000ff", borderWidth: 2, pointRadius: 0 }; var dataset_mlat = { label: 'MLAT', fill: false, borderColor: "#ff0000ff", borderWidth: 2, pointRadius: 0 }; var dataset_flarm = { label: 'Flarm', fill: false, borderColor: "#ff00ffff", //window.chartColors.black, borderWidth: 2, pointRadius: 0 }; myChart = new Chart(document.getElementById('range-overall'), { type:'radar', data:{labels:labels360, datasets:[dataset_adsb, dataset_mlat, dataset_flarm]}, options:options25 } ); $("input[type='radio'][name='RangeModeSelectButtons'][value="+myChartRangeMode+"]").prop('checked', true); updateRangeSlider(); updateAltitudeSlider(); setTimeout ( initializeDateRangePicker, 1000); var initalized = false; var delay = 100; var hour_tm1 = moment().startOf("hour").unix(); function loop() { if( rcd_globals.initialized ) { if( isContinue ) { var hour = moment().startOf("hour").unix(); if( hour!= hour_tm1 ) { console.log('permanently add last hour'); hour_tm1 = hour; var fn = moment.unix(hour).utc().format("YYYYMMDDHH"); var arr = ['ranges/'+fn+'-v1-ADSB.png', 'ranges/'+fn+'-v1-MLAT.png', 'ranges/'+fn+'-v1-FLARM.png']; arr.forEach( function(url, locsource_index) { var png = new PngContainer(url, locsource_index); }); document.getElementById('infotext').innerHTML += '
added hourly: ' + moment.unix(hour).utc().format("YYYYMMDDHH"); } ['range-v0-ADSB.png', 'range-v0-MLAT.png', 'range-v0-FLARM.png'].forEach( function(url, locsource_index) { var png = new PngContainer(url, locsource_index); }); } delay = Math.max(1000, refresh_time*1000); } setTimeout( loop, delay ); } loop(); });