Skip to content

Commit 0c383e1

Browse files
Merge pull request #44 from UMDBPP/develop
add predicts to plots
2 parents 1ab1cdc + 5816ece commit 0c383e1

File tree

9 files changed

+193
-87
lines changed

9 files changed

+193
-87
lines changed

client/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
DEFAULT_INTERVAL_SECONDS = 5
1+
DEFAULT_INTERVAL_SECONDS = 20

client/cli.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99

1010
from client import DEFAULT_INTERVAL_SECONDS
1111
from client.gui import PacketRavenGUI
12-
from client.retrieve import retrieve_packets, write_predictions
12+
from client.retrieve import retrieve_packets
1313
from packetraven.connections import APRSDatabaseTable, APRSfi, APRSis, SerialTNC, TextFileTNC
14-
from packetraven.predicts import PredictionAPIURL, PredictionError
14+
from packetraven.predicts import PredictionAPIURL, PredictionError, get_predictions
1515
from packetraven.utilities import get_logger, read_configuration, repository_root
16+
from packetraven.writer import write_packet_tracks
1617

1718
LOGGER = get_logger('packetraven')
1819

@@ -169,7 +170,7 @@ def main():
169170
kwargs['prediction_burst_altitude'] = float(args.prediction_burst_altitude)
170171

171172
if args.prediction_descent_rate is not None:
172-
kwargs['prediction_descent_rate'] = float(args.prediction_descent_rate)
173+
kwargs['prediction_sea_level_descent_rate'] = float(args.prediction_descent_rate)
173174

174175
if args.prediction_api_url is not None:
175176
kwargs['prediction_api_url'] = args.prediction_api_url
@@ -344,7 +345,15 @@ def main():
344345

345346
if prediction_filename is not None:
346347
try:
347-
write_predictions(packet_tracks.values(), prediction_filename)
348+
predictions = get_predictions(
349+
packet_tracks,
350+
**{
351+
key.replace('prediction_', ''): value
352+
for key, value in kwargs
353+
if 'prediction_' in key
354+
},
355+
)
356+
write_packet_tracks(predictions.values(), prediction_filename)
348357
except PredictionError as error:
349358
LOGGER.warning(f'{error.__class__.__name__} - {error}')
350359
except Exception as error:

client/gui.py

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,15 @@
1313
import numpy
1414

1515
from client import DEFAULT_INTERVAL_SECONDS
16-
from client.retrieve import retrieve_packets, write_predictions
16+
from client.retrieve import retrieve_packets
1717
from packetraven.base import available_serial_ports, next_open_serial_port
1818
from packetraven.connections import APRSDatabaseTable, APRSfi, APRSis, SerialTNC, TextFileTNC
1919
from packetraven.packets import APRSPacket
2020
from packetraven.plotting import LivePlot
21-
from packetraven.predicts import PredictionError
22-
from packetraven.tracks import APRSTrack
21+
from packetraven.predicts import PredictionError, get_predictions
22+
from packetraven.tracks import LocationPacketTrack, PredictedTrajectory
2323
from packetraven.utilities import get_logger
24+
from packetraven.writer import write_packet_tracks
2425

2526
LOGGER = get_logger('packetraven')
2627

@@ -56,11 +57,11 @@ def __init__(
5657
'database_username': None,
5758
'database_password': None,
5859
},
59-
'ssh_tunnel': {'ssh_hostname': None, 'ssh_username': None, 'ssh_password': None, },
60+
'ssh_tunnel': {'ssh_hostname': None, 'ssh_username': None, 'ssh_password': None},
6061
'prediction': {
6162
'prediction_ascent_rate': None,
6263
'prediction_burst_altitude': None,
63-
'prediction_descent_rate': None,
64+
'prediction_sea_level_descent_rate': None,
6465
'prediction_api_url': None,
6566
},
6667
}
@@ -293,6 +294,7 @@ def __init__(
293294
if self.prediction_filename is None:
294295
self.prediction_filename = Path('~') / 'Desktop'
295296
set_child_states(self.__elements['prediction_file_box'], state=tkinter.DISABLED)
297+
self.__predictions = {}
296298

297299
self.__windows['main'].protocol('WM_DELETE_WINDOW', self.close)
298300

@@ -441,7 +443,7 @@ def output_filename(self, filename: PathLike):
441443

442444
@property
443445
def prediction_filename(self) -> Path:
444-
if self.toggles['output_file']:
446+
if self.toggles['prediction_file']:
445447
filename = self.__elements['prediction_file'].get()
446448
if len(filename) > 0:
447449
filename = Path(filename)
@@ -481,9 +483,13 @@ def running(self, running: bool):
481483
self.toggle()
482484

483485
@property
484-
def packet_tracks(self) -> {str: APRSTrack}:
486+
def packet_tracks(self) -> {str: LocationPacketTrack}:
485487
return self.__packet_tracks
486488

489+
@property
490+
def predictions(self) -> {str: PredictedTrajectory}:
491+
return self.__predictions if self.toggles['prediction_file'] else None
492+
487493
def __select_tnc(self, event):
488494
if event.widget.get() == self.__file_selection_option:
489495
self.tncs = filedialog.askopenfilename(
@@ -830,7 +836,9 @@ def toggle(self):
830836
for variable, enabled in self.__plot_toggles.items():
831837
enabled = enabled.get()
832838
if enabled and variable not in self.__plots:
833-
self.__plots[variable] = LivePlot(self.__packet_tracks, variable)
839+
self.__plots[variable] = LivePlot(
840+
self.packet_tracks, variable, self.predictions
841+
)
834842
elif not enabled and variable in self.__plots:
835843
self.__plots[variable].close()
836844
del self.__plots[variable]
@@ -896,11 +904,20 @@ def retrieve_packets(self):
896904
logger=LOGGER,
897905
)
898906

899-
if self.toggles['prediction_file'] and self.prediction_filename is not None:
907+
if self.toggles['prediction_file']:
900908
try:
901-
write_predictions(
902-
self.packet_tracks.values(), self.prediction_filename
909+
self.__predictions = get_predictions(
910+
self.packet_tracks,
911+
**{
912+
key.replace('prediction_', ''): value
913+
for key, value in self.__configuration['prediction'].items()
914+
if 'prediction_' in key
915+
},
903916
)
917+
if self.prediction_filename is not None:
918+
write_packet_tracks(
919+
self.__predictions.values(), self.prediction_filename
920+
)
904921
except PredictionError as error:
905922
LOGGER.warning(f'{error.__class__.__name__} - {error}')
906923
except Exception as error:
@@ -910,7 +927,7 @@ def retrieve_packets(self):
910927

911928
if len(new_packets) > 0:
912929
for variable, plot in self.__plots.items():
913-
plot.update(self.packet_tracks)
930+
plot.update(self.packet_tracks, self.predictions)
914931

915932
if self.aprs_is is not None:
916933
for packets in new_packets.values():

client/retrieve.py

Lines changed: 1 addition & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,7 @@
88
from packetraven import APRSDatabaseTable
99
from packetraven.base import APRSPacketSource
1010
from packetraven.connections import TimeIntervalError
11-
from packetraven.predicts import (
12-
CUSFBalloonPredictionQuery,
13-
DEFAULT_ASCENT_RATE,
14-
DEFAULT_BURST_ALTITUDE,
15-
DEFAULT_SEA_LEVEL_DESCENT_RATE,
16-
PredictionAPIURL,
17-
)
18-
from packetraven.tracks import APRSTrack, LocationPacketTrack
11+
from packetraven.tracks import APRSTrack
1912
from packetraven.utilities import get_logger
2013
from packetraven.writer import write_packet_tracks
2114

@@ -129,44 +122,3 @@ def retrieve_packets(
129122
)
130123

131124
return new_packets
132-
133-
134-
def write_predictions(
135-
packet_tracks: [LocationPacketTrack],
136-
output_filename: PathLike,
137-
ascent_rate: float = None,
138-
burst_altitude: float = None,
139-
sea_level_descent_rate: float = None,
140-
api_url: str = None,
141-
):
142-
if api_url is None:
143-
api_url = PredictionAPIURL.lukerenegar
144-
145-
if output_filename is not None:
146-
prediction_tracks = []
147-
for packet_track in packet_tracks:
148-
ascent_rates = packet_track.ascent_rates
149-
if ascent_rate is None:
150-
average_ascent_rate = ascent_rates[packet_track.ascent_rates > 0]
151-
if average_ascent_rate > 0:
152-
ascent_rate = average_ascent_rate
153-
else:
154-
ascent_rate = DEFAULT_ASCENT_RATE
155-
if burst_altitude is None:
156-
burst_altitude = DEFAULT_BURST_ALTITUDE
157-
if sea_level_descent_rate is None:
158-
sea_level_descent_rate = DEFAULT_SEA_LEVEL_DESCENT_RATE
159-
160-
if len(ascent_rates) > 2 and all(ascent_rates[-2:] < 0):
161-
burst_altitude = packet_track.altitudes[-1] + 1
162-
163-
prediction_query = CUSFBalloonPredictionQuery(
164-
packet_track[-1].coordinates,
165-
packet_track[-1].time,
166-
ascent_rate=ascent_rate,
167-
burst_altitude=burst_altitude,
168-
sea_level_descent_rate=sea_level_descent_rate,
169-
api_url=api_url,
170-
)
171-
prediction_tracks.append(prediction_query.predict)
172-
write_packet_tracks(prediction_tracks, output_filename)

packetraven/plotting.py

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from matplotlib.axes import Axes
55
from matplotlib.figure import Figure
66

7-
from packetraven.tracks import LocationPacketTrack
7+
from packetraven.tracks import LocationPacketTrack, PredictedTrajectory
88

99
VARIABLES = {
1010
'altitude': {'x': 'times', 'y': 'altitudes', 'xlabel': 'time', 'ylabel': 'altitude (m)'},
@@ -24,20 +24,32 @@
2424

2525

2626
class LivePlot:
27-
def __init__(self, packet_tracks: {str: LocationPacketTrack}, variable: str):
27+
def __init__(
28+
self,
29+
packet_tracks: {str: LocationPacketTrack},
30+
variable: str,
31+
predictions: {str: PredictedTrajectory} = None,
32+
):
2833
if variable not in VARIABLES:
2934
raise NotImplementedError(f'unsupported plotting variable "{variable}"')
3035

3136
self.packet_tracks = packet_tracks
37+
self.predictions = predictions
3238
self.variable = variable
3339

3440
self.window.protocol('WM_DELETE_WINDOW', self.window.iconify)
3541

3642
self.update()
3743

38-
def update(self, packet_tracks: {str: LocationPacketTrack} = None):
44+
def update(
45+
self,
46+
packet_tracks: {str: LocationPacketTrack} = None,
47+
predictions: {str: PredictedTrajectory} = None,
48+
):
3949
if packet_tracks is not None:
4050
self.packet_tracks.update(packet_tracks)
51+
if packet_tracks is not None:
52+
self.predictions.update(predictions)
4153

4254
if len(self.packet_tracks) > 0:
4355
if self.window.state() == 'iconic':
@@ -48,12 +60,32 @@ def update(self, packet_tracks: {str: LocationPacketTrack} = None):
4860
while len(self.axis.lines) > 0:
4961
self.axis.lines.pop(-1)
5062

63+
packet_track_lines = {}
5164
for name, packet_track in self.packet_tracks.items():
52-
self.axis.scatter(
65+
lines = self.axis.plot(
5366
getattr(packet_track, VARIABLES[self.variable]['x']),
5467
getattr(packet_track, VARIABLES[self.variable]['y']),
68+
linewidth=3,
69+
marker='o',
5570
label=packet_track.name,
56-
s=2,
71+
)
72+
73+
packet_track_lines[name] = lines[0]
74+
75+
for name, packet_track in self.predictions.items():
76+
color = (
77+
packet_track_lines[name].get_color()
78+
if name in packet_track_lines
79+
else None
80+
)
81+
82+
self.axis.plot(
83+
getattr(packet_track, VARIABLES[self.variable]['x']),
84+
getattr(packet_track, VARIABLES[self.variable]['y']),
85+
'--',
86+
linewidth=0.5,
87+
color=color,
88+
label=f'{packet_track.name} prediction',
5789
)
5890

5991
self.axis.legend()

0 commit comments

Comments
 (0)