55import numpy
66from pyproj import CRS
77
8+ from .model import SECONDS_TO_GROUND
89from .packets import APRSPacket , DEFAULT_CRS , LocationPacket
910from .structures import DoublyLinkedList
1011
@@ -22,9 +23,13 @@ def __init__(self, name: str, packets: [LocationPacket] = None, crs: CRS = None)
2223 """
2324
2425 self .name = name
25- self .packets = DoublyLinkedList (packets )
26+ self .packets = DoublyLinkedList (None )
2627 self .crs = crs if crs is not None else DEFAULT_CRS
2728
29+ if packets is not None :
30+ for packet in packets :
31+ self .append (packet )
32+
2833 def append (self , packet : LocationPacket ):
2934 if packet not in self .packets :
3035 if packet .crs != self .crs :
@@ -109,16 +114,13 @@ def cumulative_overground_distances(self) -> numpy.ndarray:
109114
110115 @property
111116 def time_to_ground (self ) -> timedelta :
112- """ estimated time to reach the ground at the current ascent rate """
117+ """ estimated time to reach the ground at the current rate of descent """
113118
114119 current_ascent_rate = self .ascent_rates [- 1 ]
115-
116120 if current_ascent_rate < 0 :
117121 # TODO implement landing location as the intersection of the predicted descent track with a local DEM
118122 # TODO implement a time to impact calc based off of standard atmo
119- return timedelta (
120- seconds = self .packets [- 1 ].coordinates [2 ] / abs (current_ascent_rate )
121- )
123+ return timedelta (seconds = self .altitudes [- 1 ] / abs (current_ascent_rate ))
122124 else :
123125 return timedelta (seconds = - 1 )
124126
@@ -157,7 +159,33 @@ def __str__(self) -> str:
157159 return str (list (self ))
158160
159161
160- class APRSTrack (LocationPacketTrack ):
162+ class BalloonTrack (LocationPacketTrack ):
163+ def __init__ (self , name : str , packets : [LocationPacket ] = None , crs : CRS = None ):
164+ super ().__init__ (name , packets , crs )
165+ self .__has_burst = False
166+
167+ @property
168+ def time_to_ground (self ) -> timedelta :
169+ if self .has_burst :
170+ # TODO implement landing location as the intersection of the predicted descent track with a local DEM
171+ return timedelta (seconds = SECONDS_TO_GROUND (self .altitudes [- 1 ]))
172+ else :
173+ return timedelta (seconds = - 1 )
174+
175+ @property
176+ def has_burst (self ) -> bool :
177+ current_ascent_rate = self .ascent_rates [- 1 ]
178+ if current_ascent_rate > 0 :
179+ self .__has_burst = False
180+ elif not self .__has_burst :
181+ current_altitude = self .altitudes [- 1 ]
182+ max_altitude = numpy .max (self .altitudes )
183+ if current_ascent_rate < - 2 and max_altitude > current_altitude :
184+ self .__has_burst = True
185+ return self .__has_burst
186+
187+
188+ class APRSTrack (BalloonTrack ):
161189 """ collection of APRS location packets """
162190
163191 def __init__ (self , callsign : str , packets : [APRSPacket ] = None , crs : CRS = None ):
@@ -169,9 +197,18 @@ def __init__(self, callsign: str, packets: [APRSPacket] = None, crs: CRS = None)
169197 :param crs: coordinate reference system to use
170198 """
171199
172- self .callsign = callsign
200+ if not isinstance (callsign , str ):
201+ callsign = str (callsign )
202+ if len (callsign ) > 9 or ' ' in callsign :
203+ raise ValueError (f'unrecognized callsign format: "{ callsign } "' )
204+
205+ self .__callsign = callsign
173206 super ().__init__ (self .callsign , packets , crs )
174207
208+ @property
209+ def callsign (self ) -> str :
210+ return self .__callsign
211+
175212 def append (self , packet : APRSPacket ):
176213 packet_callsign = packet ['callsign' ]
177214
0 commit comments