diff --git a/DEVELOPER_README.md b/DEVELOPER_README.md new file mode 100644 index 0000000..3272308 --- /dev/null +++ b/DEVELOPER_README.md @@ -0,0 +1,9 @@ +# Getting started + +To get a copy of the code and run a test web server: + +1. [Fork the repository](https://github.com/projecthorus/sondehub-tracker/fork) by visiting [https://github.com/projecthorus/sondehub-tracker/fork](https://github.com/projecthorus/sondehub-tracker/fork). +2. Clone the repository with your git tool of choice. +3. Run `build.sh` to compile the javascript files. (This requires Java to be installed and in your path.) +4. Run `python serve.py` to run a simple web server to (This requires python 3.x) +5. Visit [http://localhost:8000](http://localhost:8000) to view the local version of the server! diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..fbb1bdb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM alpine:latest + +RUN apk add --no-cache python3 openjdk11 sed git + +WORKDIR /app +ADD . . + +RUN sh build.sh + +ENTRYPOINT ["python3", "serve.py"] +EXPOSE 8000 diff --git a/README.md b/README.md index 9844b4e..ce5a68a 100644 --- a/README.md +++ b/README.md @@ -40,11 +40,10 @@ Requirements: Java $ git clone https://github.com/projecthorus/sondehub-tracker.git $ ./build.sh + $ python serve.py + +Visit [http://localhost:8000](http://localhost:8000) to view the local version of the tracker! ## Original design Author: Daniel Saul [@danielsaul](https://github.com/danielsaul) - - - - diff --git a/img/splash/splash-narrow.png b/img/splash/splash-narrow.png new file mode 100644 index 0000000..8738ab2 Binary files /dev/null and b/img/splash/splash-narrow.png differ diff --git a/img/splash/splash-wide.png b/img/splash/splash-wide.png new file mode 100644 index 0000000..d7eff35 Binary files /dev/null and b/img/splash/splash-wide.png differ diff --git a/index.html b/index.html index 874429f..cf46f36 100644 --- a/index.html +++ b/index.html @@ -27,6 +27,7 @@
+
@@ -210,6 +211,13 @@

Visibility

+
+ Only show visible on sidebar +
+ + +
+

Other


diff --git a/js/app.js b/js/app.js index 62a976d..de44b5e 100644 --- a/js/app.js +++ b/js/app.js @@ -722,6 +722,7 @@ $(window).ready(function() { '#sw_hide_horizon', '#sw_hide_titles', '#sw_layers_launches', + '#sw_selective_sidebar', "#sw_nowelcome", "#sw_interpolate", ]; @@ -820,6 +821,9 @@ $(window).ready(function() { if(on) map.overlayMapTypes.setAt("1", overlayAPRS); else map.overlayMapTypes.setAt("1", null); break; + case "opt_selective_sidebar": + sidebar_update(); + break; case "opt_layers_launches": if(on) { map.removeLayer(launches); diff --git a/js/tracker.js b/js/tracker.js index 5dba81c..4b34c19 100644 --- a/js/tracker.js +++ b/js/tracker.js @@ -45,7 +45,7 @@ var focusID = 0; var receiverCanvas = null; -var sondePrefix = ["RS92", "RS92-SGP", "RS92-NGP", "RS41", "RS41-SG", "RS41-SGP", "RS41-SGM", "DFM", "DFM06", "DFM09", "DFM17", "M10", "M20", "iMet-4", "iMet-54", "LMS6", "LMS6-400", "LMS6-1680", "iMS-100", "MRZ", "chase"]; +var sondePrefix = ["RS92", "RS92-SGP", "RS92-NGP", "RS41", "RS41-SG", "RS41-SGP", "RS41-SGM", "DFM", "DFM06", "DFM09", "DFM17", "M10", "M20", "iMet-1", "iMet-4", "iMet-54", "LMS6", "LMS6-400", "LMS6-1680", "iMS-100", "MRZ", "chase"]; var sondeCodes = { "07":"iMet-1", "11":"LMS6-403", "13":"RS92", "14":"RS92", "17":"DFM-09", "18":"DFM-06", "19":"MRZ-N1", "22":"RS-11G", "23":"RS41", "24":"RS41", "34":"iMet-4", "35":"iMS-100", "41":"RS41", "42":"RS41", "52":"RS92-NGP", "54":"DFM-17", "62":"MRZ-3MK", "63":"M20", "77":"M10", "82":"LMS6-1680", "84":"iMet-54" @@ -878,6 +878,7 @@ function load() { map.on('moveend', function (e) { lhash_update(); + sidebar_update(); }); map.on('baselayerchange', function (e) { @@ -926,6 +927,7 @@ function load() { map.on('moveend', function() { lhash_update(); + sidebar_update(); }); map.on('baselayerchange', function() { lhash_update(); @@ -1053,6 +1055,24 @@ function panToRecovery(rcallsign) { } } +function sidebar_update() { + if (offline.get('opt_selective_sidebar')) { + for (let serial in vehicles) { + if (map.getBounds().contains(vehicles[serial].marker.getLatLng())) { + $("#main .vehicle"+vehicles[serial].uuid).show(); + } else { + if (!($("#main .vehicle"+vehicles[serial].uuid).hasClass("follow"))) { + $("#main .vehicle"+vehicles[serial].uuid).hide(); + } + } + } + } else { + for (let serial in vehicles) { + $("#main .vehicle"+vehicles[serial].uuid).show(); + } + } +} + function title_case(s) { return s.replace(/\w\S*/g, function(txt) { return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); @@ -1528,6 +1548,14 @@ function updateVehicleInfo(vcallsign, newPosition) { $('.landscape').append('
'); } + if (offline.get('opt_selective_sidebar')) { + if (map.getBounds().contains(vehicles[vcallsign].marker.getLatLng())) { + $("#main .vehicle"+vehicle.uuid).show(); + } else { + $("#main .vehicle"+vehicle.uuid).hide(); + } + } + } else if(elm.attr('data-vcallsign') === undefined) { elm.attr('data-vcallsign', vcallsign); } @@ -1554,7 +1582,9 @@ function updateVehicleInfo(vcallsign, newPosition) { roundNumber(newPosition.gps_lat, 5) + ', ' + roundNumber(newPosition.gps_lon, 5) +'' + ' '; } else { - coords_text = roundNumber(newPosition.gps_lat, 5) + ', ' + roundNumber(newPosition.gps_lon, 5); + coords_text = '' + + roundNumber(newPosition.gps_lat, 5) + ', ' + roundNumber(newPosition.gps_lon, 5) +'' + + ' '; } // format altitude strings @@ -2563,8 +2593,16 @@ var marker_rotate_setup = function(marker, image_src) { marker.rotated = false; if(image_src in icon_cache) { marker.iconImg = icon_cache[image_src]; - marker.setCourse(90); - marker.setLatLng(marker.getLatLng()); + + if (marker.iconImg.complete){ + marker.setCourse(90); + marker.setLatLng(marker.getLatLng()); + }else{ + marker.iconImg.addEventListener("load", function() { + marker.setCourse(90); + marker.setLatLng(marker.getLatLng()); + }) + } } else { marker.iconImg = new Image(); @@ -3455,7 +3493,7 @@ function refresh() { refreshSingle(wvar.query); } else { response = formatData(data, false); - update(response); + update(response, true); $("#stTimer").attr("data-timestamp", response.fetch_timestamp); } $("#stText").text(""); @@ -3583,7 +3621,7 @@ function refreshSingle(serial) { dataType: "json", success: function(data, textStatus) { response = formatData(data, false); - update(response); + update(response, true); singleRecovery(serial); $("#stText").text(""); }, @@ -3625,7 +3663,7 @@ function refreshSingleNew(serial) { dataType: "json", success: function(data, textStatus) { response = formatData(data, false); - update(response); + update(response, true); }, error: function() { ajax_inprogress_single_new = false; @@ -4340,14 +4378,16 @@ var ssdv = {}; var status = ""; var bs_idx = 0; -function update(response) { +function update(response, none) { if (response === null || !response.positions || !response.positions.position || !response.positions.position.length) { // if no vehicles are found, this will remove the spinner and put a friendly message - $("#main .empty").html("No vehicles :("); + if (none) { + $("#main .empty").html("No vehicles :("); + } return; } diff --git a/js/xdata.js b/js/xdata.js index a000467..104e470 100644 --- a/js/xdata.js +++ b/js/xdata.js @@ -14,7 +14,6 @@ function lerp(x, y, a){ return x * (1 - a) + y * a } - function get_oif411_Cef(pressure){ // Get the Pump efficiency correction value for a given pressure. @@ -40,6 +39,64 @@ function get_oif411_Cef(pressure){ return 1.0; } +function parseOzonesonde(xdata, pressure) { + // Attempt to parse an XDATA string from an ECC Ozonesonde + // Returns an object with parameters to be added to the sondes telemetry. + // + // References: + // https://gml.noaa.gov/aftp/user/jordan/iMet%20Radiosonde%20Protocol.pdf + // + // Sample data: 01010349FDC54296 (length = 16 characters) + + // Run some checks over the input + if(xdata.length != 16){ + // Invalid Ozonesonde dataset + return {}; + } + + if(xdata.substr(0,2) !== '01'){ + // Not an Ozonesonde (shouldn't get here) + return {}; + } + + var _output = {}; + + // Instrument number is common to all XDATA types. + _output['ozonesonde_instrument_number'] = parseInt(xdata.substr(2,2),16); + + // Cell Current + _cell_current = parseInt(xdata.substr(4,4),16)*0.001; // uA + _output['ozonesonde_cell_current'] = Math.round(_cell_current * 1000) / 1000; // 3 DP + + // Pump Temperature + _pump_temperature = parseInt(xdata.substr(8,4),16); + if ((_pump_temperature & 0x8000) > 0) { + _pump_temperature = _pump_temperature - 0x10000; + } + _pump_temperature = _pump_temperature*0.01; // Degrees C + _output['ozonesonde_pump_temperature'] = Math.round(_pump_temperature * 100) / 100; // 2 DP + + // Pump Current + _pump_current = parseInt(xdata.substr(12,2),16); // mA + _output['ozondesonde_pump_current'] = Math.round(_pump_current * 10) / 10; // 1 DP + + // Battery Voltage + _battery_voltage = parseInt(xdata.substr(14,2),16)*0.1; // Volts + _output['ozondesonde_battery_voltage'] = Math.round(_battery_voltage * 10) / 10; // 1 DP + + // Now attempt to calculate the O3 partial pressure (copy OIF411 calculations) + + // Calibration values + Ibg = 0.0; // The BOM appear to use a Ozone background current value of 0 uA + Cef = get_oif411_Cef(pressure); // Calculate the pump efficiency correction. + FlowRate = 28.5; // Use a 'nominal' value for Flow Rate (seconds per 100mL). + + _O3_partial_pressure = (4.30851e-4)*(_output['ozonesonde_cell_current'] - Ibg)*(_output['ozonesonde_pump_temperature']+273.15)*FlowRate*Cef; // mPa + _output['ozondesonde_O3_partial_pressure'] = Math.round(_O3_partial_pressure * 1000) / 1000; // 3 DP + + return _output +} + function parseOIF411(xdata, pressure){ // Attempt to parse an XDATA string from an OIF411 Ozone Sounder // Returns an object with parameters to be added to the sondes telemetry. @@ -159,7 +216,7 @@ function parseCOBALD(xdata) { // Internal temperature _internal_temperature = parseInt(xdata.substr(7,3),16); if ((_internal_temperature & 0x800) > 0) { - _internal_temperature = _internal_temperature - 0x1000; + _internal_temperature = _internal_temperature - 0x1000; } _internal_temperature = _internal_temperature/8; // Degrees C (-40 - 50) _output['cobald_internal_temperature'] = Math.round(_internal_temperature * 100) / 100; // 2 DP @@ -167,28 +224,28 @@ function parseCOBALD(xdata) { // Blue backscatter _blue_backscatter = parseInt(xdata.substr(10,6),16); if ((_blue_backscatter & 0x800000) > 0) { - _blue_backscatter = _blue_backscatter - 0x1000000; + _blue_backscatter = _blue_backscatter - 0x1000000; } _output['cobald_blue_backscatter'] = _blue_backscatter; // (0 - 1000000) // Red backckatter _red_backscatter = parseInt(xdata.substr(16,6),16); if ((_red_backscatter & 0x800000) > 0) { - _red_backscatter = _red_backscatter - 0x1000000; + _red_backscatter = _red_backscatter - 0x1000000; } _output['cobald_red_backscatter'] = _red_backscatter; // (0 - 1000000) // Blue monitor _blue_monitor = parseInt(xdata.substr(22,4),16); if ((_blue_monitor & 0x8000) > 0) { - _blue_monitor = _blue_monitor - 0x10000; + _blue_monitor = _blue_monitor - 0x10000; } _output['cobald_blue_monitor'] = _blue_monitor; // (-32768 - 32767) // Red monitor _red_monitor = parseInt(xdata.substr(26,4),16); if ((_red_monitor & 0x8000) > 0) { - _red_monitor = _red_monitor - 0x10000; + _red_monitor = _red_monitor - 0x10000; } _output['cobald_red_monitor'] = _red_monitor; // (-32768 - 32767) @@ -565,10 +622,12 @@ function parseXDATA(data, pressure, temperature){ _instrument = _current_xdata.substr(0,2); if (_instrument === '01') { - // V7 - // 0102 time=1001 cnt=0 rpm=0 - // 0102 time=1001 cnt=7 rpm=419 - if (!_instruments.includes("V7")) _instruments.push('V7'); + if (_current_xdata.length = 16) { + // Ozonesonde + _xdata_temp = parseOzonesonde(_current_xdata, pressure); + _output = Object.assign(_output,_xdata_temp); + if (!_instruments.includes("Ozonesonde")) _instruments.push('Ozonesonde'); + } } else if (_instrument === '05'){ // OIF411 _xdata_temp = parseOIF411(_current_xdata, pressure);