diff --git a/auto_rx/autorx/config.py b/auto_rx/autorx/config.py index bfc45a8..21153b4 100644 --- a/auto_rx/autorx/config.py +++ b/auto_rx/autorx/config.py @@ -564,7 +564,7 @@ def read_auto_rx_config(filename, no_sdr_test=False): logging.warning( "Config - Did not find kml_refresh_rate setting, using default (10 seconds)." ) - auto_rx_config["kml_refresh_rate"] = 11 + auto_rx_config["kml_refresh_rate"] = 10 # New Sondehub db Settings try: diff --git a/auto_rx/autorx/log_files.py b/auto_rx/autorx/log_files.py index 477e3a5..d784488 100644 --- a/auto_rx/autorx/log_files.py +++ b/auto_rx/autorx/log_files.py @@ -522,17 +522,20 @@ def zip_log_files(serial_list=None): return data -def _coordinates_to_kml_placemark(lat, lon, alt, - name="Placemark Name", - absolute=False, - icon="http://maps.google.com/mapfiles/kml/shapes/placemark_circle.png", - scale=1.0): +def coordinates_to_kml_placemark(lat, lon, alt, + name="Placemark Name", + description="Placemark Description", + absolute=False, + icon="https://maps.google.com/mapfiles/kml/shapes/placemark_circle.png", + scale=1.0): """ Generate a generic placemark object """ placemark = ET.Element("Placemark") pm_name = ET.SubElement(placemark, "name") pm_name.text = name + pm_desc = ET.SubElement(placemark, "description") + pm_desc.text = description style = ET.SubElement(placemark, "Style") icon_style = ET.SubElement(style, "IconStyle") @@ -552,13 +555,13 @@ def _coordinates_to_kml_placemark(lat, lon, alt, return placemark -def _flight_path_to_kml_placemark(flight_path, - name="Flight Path Name", - track_color="aaffffff", - poly_color="20000000", - track_width=2.0, - absolute=True, - extrude=True): +def path_to_kml_placemark(flight_path, + name="Flight Path Name", + track_color="ff03bafc", + poly_color="8003bafc", + track_width=2.0, + absolute=True, + extrude=True): ''' Produce a placemark object from a flight path array ''' placemark = ET.Element("Placemark") @@ -572,13 +575,14 @@ def _flight_path_to_kml_placemark(flight_path, color.text = track_color width = ET.SubElement(line_style, "width") width.text = str(track_width) - poly_style = ET.SubElement(style, "PolyStyle") - color = ET.SubElement(poly_style, "color") - color.text = poly_color - fill = ET.SubElement(poly_style, "fill") - fill.text = "1" - outline = ET.SubElement(poly_style, "outline") - outline.text = "1" + if extrude: + poly_style = ET.SubElement(style, "PolyStyle") + color = ET.SubElement(poly_style, "color") + color.text = poly_color + fill = ET.SubElement(poly_style, "fill") + fill.text = "1" + outline = ET.SubElement(poly_style, "outline") + outline.text = "1" line_string = ET.SubElement(placemark, "LineString") if absolute: @@ -603,26 +607,19 @@ def _log_file_to_kml_folder(filename, absolute=True, extrude=True, last_only=Fal _flight_data = read_log_file(filename) _flight_serial = _flight_data["serial"] - _launch_time = parse(_flight_data["first_time"]).strftime("%Y%m%d-%H%M%SZ") - # Generate a comment line to use in the folder and placemark descriptions - _track_comment = "%s %s" % (_launch_time, _flight_serial) - _landing_comment = "%s Last Position" % (_flight_serial) - - # Grab last-seen position + _landing_time = _flight_data["last_time"] _landing_pos = _flight_data["path"][-1] _folder = ET.Element("Folder") _name = ET.SubElement(_folder, "name") - _name.text = _track_comment - _description = ET.SubElement(_folder, "description") - _description.text = "Radiosonde Flight Path" + _name.text = _flight_serial # Generate the placemark & flight track. + _folder.append(coordinates_to_kml_placemark(_landing_pos[0], _landing_pos[1], _landing_pos[2], + name=_flight_serial, description=_landing_time, absolute=absolute)) if not last_only: - _folder.append(_flight_path_to_kml_placemark(_flight_data["path"], name=_track_comment, - absolute=absolute, extrude=extrude)) - _folder.append(_coordinates_to_kml_placemark(_landing_pos[0], _landing_pos[1], _landing_pos[2], - name=_landing_comment, absolute=absolute)) + _folder.append(path_to_kml_placemark(_flight_data["path"], name="Track", + absolute=absolute, extrude=extrude)) return _folder @@ -630,7 +627,7 @@ def _log_file_to_kml_folder(filename, absolute=True, extrude=True, last_only=Fal def log_files_to_kml(file_list, kml_file, absolute=True, extrude=True, last_only=False): """ Convert a collection of log files to a KML file """ - kml_root = ET.Element("kml", {"xmlns": "http://www.opengis.net/kml/2.2"}) + kml_root = ET.Element("kml", xmlns="http://www.opengis.net/kml/2.2") kml_doc = ET.SubElement(kml_root, "Document") for file in file_list: diff --git a/auto_rx/autorx/web.py b/auto_rx/autorx/web.py index e4f96c1..b026689 100644 --- a/auto_rx/autorx/web.py +++ b/auto_rx/autorx/web.py @@ -18,12 +18,20 @@ import requests import time import traceback import sys +import xml.etree.ElementTree as ET import autorx import autorx.config import autorx.scan from autorx.geometry import GenericTrack from autorx.utils import check_autorx_versions -from autorx.log_files import list_log_files, read_log_by_serial, zip_log_files, log_files_to_kml +from autorx.log_files import ( + list_log_files, + read_log_by_serial, + zip_log_files, + log_files_to_kml, + coordinates_to_kml_placemark, + path_to_kml_placemark +) from autorx.decode import SondeDecoder from queue import Queue from threading import Thread @@ -31,15 +39,6 @@ import flask from flask import request, abort, make_response, send_file from flask_socketio import SocketIO from werkzeug.middleware.proxy_fix import ProxyFix -import re - -try: - from simplekml import Kml, AltitudeMode -except ImportError: - print( - "Could not import simplekml! Try running: sudo pip3 install -r requirements.txt" - ) - sys.exit(1) # Inhibit Flask warning message about running a development server... (we know!) @@ -149,47 +148,60 @@ def flask_get_task_list(): def flask_get_kml(): """ Return KML with autorefresh """ - _config = autorx.config.global_config - kml = Kml() - netlink = kml.newnetworklink(name="Radiosonde Auto-RX Live Telemetry") - netlink.open = 1 - netlink.link.href = flask.request.url_root + "rs_feed.kml" - try: - netlink.link.refreshinterval = _config["kml_refresh_rate"] - except KeyError: - netlink.link.refreshinterval = 10 - netlink.link.refreshmode = "onInterval" - return kml.kml(), 200, {"content-type": "application/vnd.google-earth.kml+xml"} + kml_root = ET.Element("kml", xmlns="http://www.opengis.net/kml/2.2") + kml_doc = ET.SubElement(kml_root, "Document") + + network_link = ET.SubElement(kml_doc, "NetworkLink") + + name = ET.SubElement(network_link, "name") + name.text = "Radiosonde Auto-RX Live Telemetry" + + open = ET.SubElement(network_link, "open") + open.text = "1" + + link = ET.SubElement(network_link, "Link") + + href = ET.SubElement(link, "href") + href.text = flask.request.url_root + "rs_feed.kml" + + refresh_mode = ET.SubElement(link, "refreshMode") + refresh_mode.text = "onInterval" + + refresh_interval = ET.SubElement(link, "refreshInterval") + refresh_interval.text = str(autorx.config.global_config["kml_refresh_rate"]) + + kml_string = ET.tostring(kml_root, encoding="UTF-8", xml_declaration=True) + return kml_string, 200, {"content-type": "application/vnd.google-earth.kml+xml"} @app.route("/rs_feed.kml") def flask_get_kml_feed(): """ Return KML with RS telemetry """ - kml = Kml() - kml.resetidcounter() - kml.document.name = "Track" - kml.document.open = 1 + kml_root = ET.Element("kml", xmlns="http://www.opengis.net/kml/2.2") + kml_doc = ET.SubElement(kml_root, "Document") + + name = ET.SubElement(kml_doc, "name") + name.text = "Track" + open = ET.SubElement(kml_doc, "open") + open.text = "1" + # Station Placemark - pnt = kml.newpoint( - name="Ground Station", - altitudemode=AltitudeMode.absolute, + kml_doc.append(coordinates_to_kml_placemark( + autorx.config.global_config["station_lat"], + autorx.config.global_config["station_lon"], + autorx.config.global_config["station_alt"], + name=autorx.config.global_config["habitat_uploader_callsign"], description="AutoRX Ground Station", - ) - pnt.open = 1 - pnt.iconstyle.icon.href = flask.request.url_root + "static/img/antenna-green.png" - pnt.coords = [ - ( - autorx.config.global_config["station_lon"], - autorx.config.global_config["station_lat"], - autorx.config.global_config["station_alt"], - ) - ] + absolute=True, + icon=flask.request.url_root + "static/img/antenna-green.png" + )) + for rs_id in flask_telemetry_store: try: coordinates = [] for tp in flask_telemetry_store[rs_id]["track"].track_history: - coordinates.append((tp[2], tp[1], tp[3])) + coordinates.append((tp[1], tp[2], tp[3])) rs_data = """\ {type}/{subtype} @@ -208,56 +220,59 @@ def flask_get_kml_feed(): icon = flask.request.url_root + "static/img/parachute-green.png" # Add folder - fol = kml.newfolder(name=rs_id) + folder = ET.SubElement(kml_doc, "Folder", id=f"folder_{rs_id}") + name = ET.SubElement(folder, "name") + name.text = rs_id + open = ET.SubElement(folder, "open") + open.text = "1" + # HAB Placemark - pnt = fol.newpoint( + folder.append(coordinates_to_kml_placemark( + flask_telemetry_store[rs_id]["latest_telem"]["lat"], + flask_telemetry_store[rs_id]["latest_telem"]["lon"], + flask_telemetry_store[rs_id]["latest_telem"]["alt"], name=rs_id, - altitudemode=AltitudeMode.absolute, - description=rs_data.format( - **flask_telemetry_store[rs_id]["latest_telem"] - ), - ) - pnt.iconstyle.icon.href = icon - pnt.coords = [ + description=rs_data.format(**flask_telemetry_store[rs_id]["latest_telem"]), + absolute=True, + icon=icon + )) + + # Track + folder.append(path_to_kml_placemark( + coordinates, + name="Track", + absolute=True, + extrude=True + )) + + # LOS line + coordinates = [ ( - flask_telemetry_store[rs_id]["latest_telem"]["lon"], - flask_telemetry_store[rs_id]["latest_telem"]["lat"], - flask_telemetry_store[rs_id]["latest_telem"]["alt"], - ) - ] - linestring = fol.newlinestring(name="Track") - linestring.coords = coordinates - linestring.altitudemode = AltitudeMode.absolute - linestring.extrude = 1 - linestring.stylemap.normalstyle.linestyle.color = "ff03bafc" - linestring.stylemap.highlightstyle.linestyle.color = "ff03bafc" - linestring.stylemap.normalstyle.polystyle.color = "AA03bafc" - linestring.stylemap.highlightstyle.polystyle.color = "CC03bafc" - # Add LOS line - linestring = fol.newlinestring(name="LOS") - linestring.altitudemode = AltitudeMode.absolute - linestring.coords = [ - ( - autorx.config.global_config["station_lon"], autorx.config.global_config["station_lat"], + autorx.config.global_config["station_lon"], autorx.config.global_config["station_alt"], ), ( - flask_telemetry_store[rs_id]["latest_telem"]["lon"], flask_telemetry_store[rs_id]["latest_telem"]["lat"], + flask_telemetry_store[rs_id]["latest_telem"]["lon"], flask_telemetry_store[rs_id]["latest_telem"]["alt"], ), ] + folder.append(path_to_kml_placemark( + coordinates, + name="LOS", + track_color="ffffffff", + absolute=True, + extrude=False + )) + except Exception as e: logging.error( "KML - Could not parse data from RS %s - %s" % (rs_id, str(e)) ) - return ( - re.sub("", "", kml.kml()), - 200, - {"content-type": "application/vnd.google-earth.kml+xml"}, - ) + kml_string = ET.tostring(kml_root, encoding="UTF-8", xml_declaration=True) + return kml_string, 200, {"content-type": "application/vnd.google-earth.kml+xml"} @app.route("/get_config") diff --git a/auto_rx/requirements.txt b/auto_rx/requirements.txt index 3f1bb37..347a105 100644 --- a/auto_rx/requirements.txt +++ b/auto_rx/requirements.txt @@ -5,5 +5,4 @@ flask-socketio numpy requests semver -simplekml simple-websocket