kopia lustrzana https://github.com/projecthorus/radiosonde_auto_rx
Add first pass at a log file to KML converter.
rodzic
1e48ce181b
commit
417a681cd4
|
|
@ -0,0 +1,273 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# Radiosonde Auto RX Tools
|
||||
# Log-to-KML Utilities
|
||||
#
|
||||
# 2018-02 Mark Jessop <vk5qi@rfhead.net>
|
||||
#
|
||||
# Note: This utility requires the fastkml and shapely libraries, which can be installed using:
|
||||
# sudo pip install fastkml shapely
|
||||
#
|
||||
|
||||
import sys
|
||||
import time
|
||||
import datetime
|
||||
import traceback
|
||||
import argparse
|
||||
import glob
|
||||
import os
|
||||
import fastkml
|
||||
from dateutil.parser import *
|
||||
from shapely.geometry import Point, LineString
|
||||
|
||||
def read_telemetry_csv(filename,
|
||||
datetime_field = 0,
|
||||
latitude_field = 3,
|
||||
longitude_field = 4,
|
||||
altitude_field = 5,
|
||||
delimiter=','):
|
||||
'''
|
||||
Read in a radiosonde_auto_rx generated telemetry CSV file.
|
||||
Fields to use can be set as arguments to this function.
|
||||
These have output like the following:
|
||||
2017-12-27T23:21:59.560,M2913374,982,-34.95143,138.52471,719.9,-273.0,RS92,401.520
|
||||
<datetime>,<serial>,<frame_no>,<lat>,<lon>,<alt>,<temp>,<sonde_type>,<freq>
|
||||
|
||||
Note that the datetime field must be parsable by dateutil.parsers.parse.
|
||||
|
||||
If any fields are missing, or invalid, this function will return None.
|
||||
|
||||
The output data structure is in the form:
|
||||
[
|
||||
[datetime (as a datetime object), latitude, longitude, altitude, raw_line],
|
||||
[datetime (as a datetime object), latitude, longitude, altitude, raw_line],
|
||||
...
|
||||
]
|
||||
'''
|
||||
|
||||
output = []
|
||||
|
||||
f = open(filename,'r')
|
||||
|
||||
for line in f:
|
||||
try:
|
||||
# Split line by comma delimiters.
|
||||
_fields = line.split(delimiter)
|
||||
|
||||
# Attempt to parse fields.
|
||||
_datetime = parse(_fields[datetime_field])
|
||||
_latitude = float(_fields[latitude_field])
|
||||
_longitude = float(_fields[longitude_field])
|
||||
_altitude = float(_fields[altitude_field])
|
||||
|
||||
output.append([_datetime, _latitude, _longitude, _altitude, line])
|
||||
except:
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
f.close()
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def flight_burst_position(flight_path):
|
||||
''' Search through flight data for the burst position and return it. '''
|
||||
|
||||
# Read through array and hunt for max altitude point.
|
||||
current_alt = 0.0
|
||||
current_index = 0
|
||||
for i in range(len(flight_path)):
|
||||
if flight_path[i][3] > current_alt:
|
||||
current_alt = flight_path[i][3]
|
||||
current_index = i
|
||||
|
||||
return flight_path[current_index]
|
||||
|
||||
|
||||
ns = '{http://www.opengis.net/kml/2.2}'
|
||||
|
||||
def new_placemark(lat, lon, alt,
|
||||
placemark_id="Placemark ID",
|
||||
name="Placemark Name",
|
||||
absolute = False,
|
||||
icon = "http://maps.google.com/mapfiles/kml/shapes/placemark_circle.png",
|
||||
scale = 1.0):
|
||||
""" Generate a generic placemark object """
|
||||
|
||||
if absolute:
|
||||
_alt_mode = 'absolute'
|
||||
else:
|
||||
_alt_mode = 'clampToGround'
|
||||
|
||||
flight_icon_style = fastkml.styles.IconStyle(
|
||||
ns=ns,
|
||||
icon_href=icon,
|
||||
scale=scale)
|
||||
|
||||
flight_style = fastkml.styles.Style(
|
||||
ns=ns,
|
||||
styles=[flight_icon_style])
|
||||
|
||||
flight_placemark = fastkml.kml.Placemark(
|
||||
ns=ns,
|
||||
id=placemark_id,
|
||||
name=name,
|
||||
description="",
|
||||
styles=[flight_style])
|
||||
|
||||
flight_placemark.geometry = fastkml.geometry.Geometry(
|
||||
ns=ns,
|
||||
geometry=Point(lon, lat, alt),
|
||||
altitude_mode=_alt_mode)
|
||||
|
||||
return flight_placemark
|
||||
|
||||
|
||||
|
||||
def flight_path_to_geometry(flight_path,
|
||||
placemark_id="Flight Path ID",
|
||||
name="Flight Path Name",
|
||||
track_color="aaffffff",
|
||||
poly_color="20000000",
|
||||
track_width=2.0,
|
||||
absolute = True,
|
||||
extrude = True,
|
||||
tessellate = True):
|
||||
''' Produce a fastkml geometry object from a flight path array '''
|
||||
|
||||
# Handle selection of absolute altitude mode
|
||||
if absolute:
|
||||
_alt_mode = 'absolute'
|
||||
else:
|
||||
_alt_mode = 'clampToGround'
|
||||
|
||||
# Convert the flight path array [time, lat, lon, alt, comment] into a LineString object.
|
||||
track_points = []
|
||||
for _point in flight_path:
|
||||
# Flight path array is in lat,lon,alt order, needs to be in lon,lat,alt
|
||||
track_points.append([_point[2],_point[1],_point[3]])
|
||||
|
||||
_flight_geom = LineString(track_points)
|
||||
|
||||
# Define the Line and Polygon styles, which are used for the flight path, and the extrusions (if enabled)
|
||||
flight_track_line_style = fastkml.styles.LineStyle(
|
||||
ns=ns,
|
||||
color=track_color,
|
||||
width=track_width)
|
||||
|
||||
flight_extrusion_style = fastkml.styles.PolyStyle(
|
||||
ns=ns,
|
||||
color=poly_color)
|
||||
|
||||
flight_track_style = fastkml.styles.Style(
|
||||
ns=ns,
|
||||
styles=[flight_track_line_style, flight_extrusion_style])
|
||||
|
||||
# Generate the Placemark which will contain the track data.
|
||||
flight_line = fastkml.kml.Placemark(
|
||||
ns=ns,
|
||||
id=placemark_id,
|
||||
name=name,
|
||||
styles=[flight_track_style])
|
||||
|
||||
# Add the track data to the Placemark
|
||||
flight_line.geometry = fastkml.geometry.Geometry(
|
||||
ns=ns,
|
||||
geometry=_flight_geom,
|
||||
altitude_mode=_alt_mode,
|
||||
extrude=extrude,
|
||||
tessellate=tessellate)
|
||||
|
||||
return flight_line
|
||||
|
||||
|
||||
def write_kml(geom_objects,
|
||||
filename="output.kml",
|
||||
comment=""):
|
||||
""" Write out flight path geometry objects to a kml file. """
|
||||
|
||||
kml_root = fastkml.kml.KML()
|
||||
kml_doc = fastkml.kml.Document(
|
||||
ns=ns,
|
||||
name=comment)
|
||||
|
||||
if type(geom_objects) is not list:
|
||||
geom_objects = [geom_objects]
|
||||
|
||||
for _flight in geom_objects:
|
||||
kml_doc.append(_flight)
|
||||
|
||||
with open(filename,'w') as kml_file:
|
||||
kml_file.write(kml_doc.to_string())
|
||||
kml_file.close()
|
||||
|
||||
|
||||
def convert_single_file(filename, absolute=True, tessellate=True):
|
||||
''' Convert a single sonde log file to a fastkml KML Folder object '''
|
||||
|
||||
# Read file.
|
||||
_flight_data = read_telemetry_csv(filename)
|
||||
|
||||
# Extract the flight's serial number and launch time from the first line in the file.
|
||||
_first_line = _flight_data[0][4]
|
||||
_flight_serial = _first_line.split(',')[1] # Serial number is the second field in the line.
|
||||
_launch_time = _flight_data[0][0].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 burst and last-seen positions
|
||||
_burst_pos = flight_burst_position(_flight_data)
|
||||
_landing_pos = _flight_data[-1]
|
||||
|
||||
# Generate the placemark & flight track.
|
||||
_flight_geom = flight_path_to_geometry(_flight_data, name=_track_comment, absolute=absolute, tessellate=tessellate, extrude=tessellate)
|
||||
_landing_geom = new_placemark(_landing_pos[1], _landing_pos[2], _landing_pos[3], name=_landing_comment, absolute=absolute)
|
||||
|
||||
_folder = fastkml.kml.Folder(ns, _flight_serial, _track_comment, 'Radiosonde Flight Path')
|
||||
_folder.append(_flight_geom)
|
||||
_folder.append(_landing_geom)
|
||||
|
||||
return _folder
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-i", "--input", type=str, default="../log/*_sonde.log",
|
||||
help="Path to log file. May include wildcards, though the path must be wrapped in quotes. Default=../log/*_sonde.log")
|
||||
parser.add_argument("-o", "--output", type=str, default="sondes.kml", help="KML output file name. Default=sondes.kml")
|
||||
parser.add_argument('--clamp', action="store_false", default=True, help="Clamp tracks to ground instead of showing absolute altitudes.")
|
||||
parser.add_argument('--noextrude', action="store_false", default=True, help="Disable Extrusions for absolute flight paths.")
|
||||
args = parser.parse_args()
|
||||
|
||||
_file_list = glob.glob(args.input)
|
||||
|
||||
_placemarks = []
|
||||
|
||||
for _file in _file_list:
|
||||
print("Processing: %s" % _file)
|
||||
try:
|
||||
_placemarks.append(convert_single_file(_file, absolute=args.clamp, tessellate=args.noextrude))
|
||||
except:
|
||||
print("Failed to process: %s" % _file)
|
||||
|
||||
write_kml(_placemarks, filename=args.output)
|
||||
|
||||
print("Output saved to: %s" % args.output)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Ładowanie…
Reference in New Issue