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 @@
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);