diff --git a/README.md b/README.md index 1379c29..b92a338 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ Edit this file with your preferred text editor. The configuration file is fairly * At least one telemetry 'profile', which defines where payload and (optionally) car position telemetry data is sourced from. * A default latitude and longitude for the map to centre on. -The example configuration file includes profiles suitable for receiving data from radiosonde_auto_rx, and from OziMux messages. +The example configuration file includes profiles suitable for receiving data from radiosonde_auto_rx, and from [Horus-GUI](https://github.com/projecthorus/horus-gui). Once configured, you can start-up the horusmapper server with: ``` diff --git a/chasemapper/__init__.py b/chasemapper/__init__.py index e9f5dfd..2930500 100644 --- a/chasemapper/__init__.py +++ b/chasemapper/__init__.py @@ -5,3 +5,7 @@ # Copyright (C) 2018 Mark Jessop # Released under GNU GPL v3 or later # + +# Now using Semantic Versioning (https://semver.org/) MAJOR.MINOR.PATCH + +__version__ = "1.0.1" \ No newline at end of file diff --git a/chasemapper/config.py b/chasemapper/config.py index e6bbf93..5fbb803 100644 --- a/chasemapper/config.py +++ b/chasemapper/config.py @@ -20,6 +20,7 @@ default_config = { # Start location for the map (until either a chase car position, or balloon position is available.) "default_lat": -34.9, "default_lon": 138.6, + "default_alt": 0, "payload_max_age": 180, "thunderforest_api_key": "none", # Predictor settings @@ -64,8 +65,8 @@ def parse_config_file(filename): # Map Defaults chase_config["flask_host"] = config.get("map", "flask_host") chase_config["flask_port"] = config.getint("map", "flask_port") - chase_config["default_lat"] = config.get("map", "default_lat") - chase_config["default_lon"] = config.get("map", "default_lon") + chase_config["default_lat"] = config.getfloat("map", "default_lat") + chase_config["default_lon"] = config.getfloat("map", "default_lon") chase_config["payload_max_age"] = config.getint("map", "payload_max_age") chase_config["thunderforest_api_key"] = config.get("map", "thunderforest_api_key") @@ -156,6 +157,12 @@ def parse_config_file(filename): logging.info("Missing Chase Car Speedo Setting, using default (disabled)") chase_config["chase_car_speed"] = False + try: + chase_config["default_alt"] = config.getfloat("map", "default_alt") + except: + logging.info("Missing default_alt setting, using default (0m)") + chase_config["default_alt"] = 0 + # Telemetry Source Profiles _profile_count = config.getint("profile_selection", "profile_count") diff --git a/horusmapper.cfg.example b/horusmapper.cfg.example index 8025156..c4437c0 100644 --- a/horusmapper.cfg.example +++ b/horusmapper.cfg.example @@ -32,6 +32,7 @@ telemetry_source_port = 55673 # horus_udp - Read Horus UDP Broadcast 'Car GPS' messages # serial - Read GPS positions from a serial-connected GPS receiver. # gpsd - Poll GPSD for positions. +# station - Stationary position (set in the [map] section below) car_source_type = gpsd # Car position source port (UDP) - only used if horus_udp is selected, but still needs to be provided. car_source_port = 12345 @@ -74,15 +75,18 @@ gps_port = /dev/ttyUSB0 gps_baud = 9600 -# Map Defaults +# Map Settings [map] # Host/port to host webserver on flask_host = 0.0.0.0 flask_port = 5001 -# Default map centre +# Default Map Centre & Stationary Position +# If the profile's car_source_type is set to station, the following position will be indicated on the map +# as a stationary receiver. default_lat = -34.9 default_lon = 138.6 +default_alt = 0.0 # How long to keep payload data (minutes) payload_max_age = 180 @@ -191,7 +195,8 @@ range_ring_color = red range_ring_custom_color = #FF0000 # -# Chase Car Speedometer +# Chase Car Speedometer +# If enabled display the chase car speed at the bottom left of the display. # [speedo] chase_car_speed = True diff --git a/horusmapper.py b/horusmapper.py index a21ac87..86a4112 100644 --- a/horusmapper.py +++ b/horusmapper.py @@ -18,6 +18,7 @@ from threading import Thread from datetime import datetime, timedelta from dateutil.parser import parse +from chasemapper import __version__ as CHASEMAPPER_VERSION from chasemapper.config import * from chasemapper.earthmaths import * from chasemapper.geometry import * @@ -789,7 +790,7 @@ def udp_listener_car_callback(data): # Handle when GPSD and/or other GPS data sources return a n/a for altitude. try: _alt = float(data["altitude"]) - except ValueError: + except: _alt = 0.0 _comment = "CAR" @@ -896,7 +897,7 @@ def start_listeners(profile): 'name' (str): Profile name 'telemetry_source_type' (str): Data source type (ozimux or horus_udp) 'telemetry_source_port' (int): Data source port - 'car_source_type' (str): Car Position source type (none, horus_udp or gpsd) + 'car_source_type' (str): Car Position source type (none, horus_udp, gpsd, or station) 'car_source_port' (int): Car Position source port """ global data_listeners @@ -994,6 +995,9 @@ def start_listeners(profile): ) data_listeners.append(_serial_gps) + elif profile["car_source_type"] == "station": + logging.info("Using Stationary receiver position.") + else: # No Car position. logging.info("No car position data source.") @@ -1014,6 +1018,16 @@ def profile_change(data): # Update all clients with the new profile selection flask_emit_event("server_settings_update", chasemapper_config) +@socketio.on("device_position", namespace="/chasemapper") +def device_position_update(data): + """ Accept a device position update from a client and process it as if it was a chase car position """ + try: + udp_listener_car_callback(data) + except: + pass + + + class WebHandler(logging.Handler): """ Logging Handler for sending log messages via Socket.IO to a Web Client """ @@ -1093,6 +1107,9 @@ if __name__ == "__main__": logging.critical("Could not read configuration data. Exiting") sys.exit(1) + # Add in Chasemapper version information. + chasemapper_config['version'] = CHASEMAPPER_VERSION + # Copy out the predictor settings to another dictionary. pred_settings = { "pred_binary": chasemapper_config["pred_binary"], diff --git a/static/js/balloon.js b/static/js/balloon.js index f73e5d7..0db00d5 100644 --- a/static/js/balloon.js +++ b/static/js/balloon.js @@ -124,13 +124,21 @@ function updateSummaryDisplay(){ _summary_update.vel_v = _latest_telem.vel_v.toFixed(1) + " m/s"; + // Work out if we have data to calculate look-angles from. if (chase_car_position.latest_data.length == 3){ - // We have a chase car position! Calculate relative position. - var _bal = {lat:_latest_telem.position[0], lon:_latest_telem.position[1], alt:_latest_telem.position[2]}; + // Chase car position available - use that. var _car = {lat:chase_car_position.latest_data[0], lon:chase_car_position.latest_data[1], alt:chase_car_position.latest_data[2]}; + } else if (home_marker !== "NONE") { + // Home marker is on the map - use the home marker position + var _car = {lat:chase_config.default_lat, lon:chase_config.default_lon, alt:chase_config.default_alt}; + } else { + // Otherwise, nothing we can use + var _car = null; + } + if(_car !== null){ + var _bal = {lat:_latest_telem.position[0], lon:_latest_telem.position[1], alt:_latest_telem.position[2]}; var _look_angles = calculate_lookangles(_car, _bal); - _summary_update.elevation = _look_angles.elevation.toFixed(0) + "°"; _summary_update.azimuth = _look_angles.azimuth.toFixed(0) + "°"; _summary_update.range = (_look_angles.range/1000).toFixed(1) + "km"; @@ -168,11 +176,21 @@ function updateSummaryDisplayImperial(){ _summary_update.vel_v = (_latest_telem.vel_v*3.28084*60).toFixed(0) + " ft/min"; + // Work out if we have data to calculate look-angles from. if (chase_car_position.latest_data.length == 3){ + // Chase car position available - use that. + var _car = {lat:chase_car_position.latest_data[0], lon:chase_car_position.latest_data[1], alt:chase_car_position.latest_data[2]}; + } else if (home_marker !== "NONE") { + // Home marker is on the map - use the home marker position + var _car = {lat:chase_config.default_lat, lon:chase_config.default_lon, alt:chase_config.default_alt}; + } else { + // Otherwise, nothing we can use + var _car = null; + } + + if(_car !== null){ // We have a chase car position! Calculate relative position. var _bal = {lat:_latest_telem.position[0], lon:_latest_telem.position[1], alt:_latest_telem.position[2]}; - var _car = {lat:chase_car_position.latest_data[0], lon:chase_car_position.latest_data[1], alt:chase_car_position.latest_data[2]}; - var _look_angles = calculate_lookangles(_car, _bal); _summary_update.elevation = _look_angles.elevation.toFixed(0) + "°"; diff --git a/static/js/car.js b/static/js/car.js index cf9138e..afd1d22 100644 --- a/static/js/car.js +++ b/static/js/car.js @@ -109,3 +109,39 @@ function updateRangeRings(){ } } + +function reconfigureCarMarker(profile_name){ + // Remove chase-car marker if it exists, and is not used. + if( (chase_config.profiles[profile_name].car_source_type === "none") || (chase_config.profiles[profile_name].car_source_type === "station")){ + if (chase_car_position.marker !== "NONE"){ + chase_car_position.marker.remove(); + chase_car_position.path.remove(); + } + } + + if (chase_config.profiles[profile_name].car_source_type === "station") { + // If we are using a stationary profile, add the station icon to the map. + // Add our station location marker. + home_marker = L.marker([chase_config.default_lat, chase_config.default_lon, chase_config.default_alt], + {title: 'Receiver Location', icon: homeIcon} + ).addTo(map); + } + + // If we are switching to a profile with a live car position source, remove the home station Icon + if ((chase_config.profiles[profile_name].car_source_type === "serial") || (chase_config.profiles[profile_name].car_source_type === "gpsd") || (chase_config.profiles[profile_name].car_source_type === "horus_udp")){ + if(home_marker !== "NONE"){ + home_marker.remove(); + } + } +} + + +var devicePositionCallback = function(position){ + // Pass a Device position update onto the back-end for processing and re-distribution. + var device_pos = {time:position.timestamp, latitude:position.coords.latitude, longitude:position.coords.longitude, altitude:position.coords.altitude}; + socket.emit('device_position', device_pos); +} + +var devicePositionError = function(error){ + console.log(error.message); +} \ No newline at end of file diff --git a/static/js/settings.js b/static/js/settings.js index 91bdf05..77b85c8 100644 --- a/static/js/settings.js +++ b/static/js/settings.js @@ -66,8 +66,6 @@ function serverSettingsUpdate(data){ $('#bearingCustomColor').val(chase_config.bearing_custom_color); $('#bearingMaximumAge').val((chase_config.max_bearing_age/60.0).toFixed(0)); - - // Clear and populate the profile selection. $('#profileSelect').children('option:not(:first)').remove(); @@ -78,6 +76,9 @@ function serverSettingsUpdate(data){ .text(key)); }); $("#profileSelect").val(chase_config.selected_profile); + + // Update version + $('#chasemapper_version').html(chase_config.version); } function clientSettingsUpdate(){ diff --git a/static/js/utils.js b/static/js/utils.js index 4f24938..9d61d2a 100644 --- a/static/js/utils.js +++ b/static/js/utils.js @@ -66,6 +66,14 @@ var carIconFlip = L.icon({ iconAnchor: [27,12] // Revisit this }); +// Home Icon. +var homeIcon = L.icon({ + iconUrl: '/static/img/antenna-green.png', + iconSize: [26, 34], + iconAnchor: [13, 34] +}); + + // Habitat (or APRS?) sourced chase car icons. var car_colour_values = ['red', 'green', 'yellow']; var car_colour_idx = 0; diff --git a/templates/index.html b/templates/index.html index 3e27e0c..ac91724 100644 --- a/templates/index.html +++ b/templates/index.html @@ -72,6 +72,9 @@ // marker: Leaflet marker var chase_car_position = {latest_data: [], heading:0, marker: 'NONE', path: 'NONE'}; + // Home position marker + var home_marker = 'NONE'; + // Data Age variables - these are updated regularly to indicate // if we haven't received data in a while. var payload_data_age = 0.0; @@ -191,8 +194,11 @@ $("#profileSelect").change(function(){ // On a profile selection change, emit a message to the client. socket.emit('profile_change', this.value); - }); + // Remove chase car if switching to a profile which does not have live position updates. + _profile = this.value; + reconfigureCarMarker(this.value); + }); // Handle arrival of new log data. socket.on('log_event', function(msg) { @@ -251,24 +257,20 @@ // Add measurement control. if (chase_config['unitselection'] == "imperial") { - - - L.control.polylineMeasure({ - position: 'topleft', - unit: 'landmiles', - showClearControl: true, - }).addTo(map); - + L.control.polylineMeasure({ + position: 'topleft', + unit: 'landmiles', + showClearControl: true, + }).addTo(map); } if (chase_config['unitselection'] == "metric") { - L.control.polylineMeasure({ - position: 'topleft', - unit: 'metres', - showClearControl: true, - }).addTo(map); - + L.control.polylineMeasure({ + position: 'topleft', + unit: 'metres', + showClearControl: true, + }).addTo(map); } @@ -462,6 +464,9 @@ // Initialise bearings initialiseBearings(); + // Initial setup of station marker, if using a profile which uses it. + reconfigureCarMarker(chase_config.selected_profile); + // Telemetry event handler. // We will get one of these mesages with every new balloon position @@ -613,6 +618,16 @@ }, habitat_update_rate); + // window.setInterval(function(){ + // if(document.getElementById('useDevicePosition').checked){ + // if(navigator.geolocation){ + // car_bad_age = 20.0; + // navigator.geolocation.getCurrentPosition(devicePositionCallback,devicePositionError,{enableHighAccuracy:true}); + // } + // } + // }, 5000); + + window.setInterval(function(){ if(Object.keys(bearing_store).length > 0){ $.ajax({ @@ -673,6 +688,10 @@