def test_time_to_ground(): packet_1 = APRSPacket.from_frame( "W3EAX-13>APRS,N3KTX-10*,WIDE1,WIDE2-1,qAR,N3TJJ-11:!/:J..:sh'O /A=053614|!g| /W3EAX,313,0,21'C," "nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 36, 16), ) packet_2 = APRSPacket.from_frame( "W3EAX-13>APRS,WIDE1-1,WIDE2-1,qAR,W4TTU:!/:JAe:tn8O /A=046255|!i| /W3EAX,322,0,20'C,nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 38, 23), ) packet_3 = APRSPacket.from_frame( "W3EAX-13>APRS,N3KTX-10*,WIDE1,WIDE2-1,qAR,N3TJJ-11:!/:J..:sh'O /A=063614|!g| /W3EAX,313,0,21'C," "nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 39, 28), ) packet_4 = APRSPacket.from_frame( "W3EAX-13>APRS,KC3FIT-1,WIDE1*,WIDE2-1,qAR,KC3AWP-10:!/:JL2:u4wO /A=043080|!j| /W3EAX,326,0,20'C," "nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 41, 50), ) packet_5 = APRSPacket.from_frame( "W3EAX-13>APRS,N3KTX-10*,WIDE1,WIDE2-1,qAR,N3TJJ-11:!/:J..:sh'O /A=063614|!g| /W3EAX,313,0,21'C," "nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 42, 34), ) track = APRSTrack(callsign="W3EAX-13") track.append(packet_1) assert not track.falling assert track.time_to_ground == timedelta(seconds=-1) track.append(packet_2) assert track.falling assert track.time_to_ground == timedelta(seconds=1603.148748) track.append(packet_3) assert not track.falling assert track.time_to_ground == timedelta(seconds=-1) track.append(packet_4) track.append(packet_5) assert not track.falling assert track.time_to_ground == timedelta(seconds=297.913704)
def packet_track(): return APRSTrack( packets=[ APRSPacket.from_frame( "W3EAX-8>APRS,WIDE1-1,WIDE2-1,qAR,K3DO-11:!/:Gh=:j)#O /A=026909|!Q| /W3EAX,262,0,18'C,http://www.umd.edu", packet_time=pytz.timezone("America/New_York").localize( datetime(2018, 11, 11, 10, 20, 13) ), ), APRSPacket.from_frame( "W3EAX-8>APRS,N3TJJ-12,WIDE1*,WIDE2-1,qAR,N3FYI-2:!/:GiD:jcwO /A=028365|!R| /W3EAX,267,0,18'C," "http://www.umd.edu", packet_time=pytz.timezone("America/New_York").localize( datetime(2018, 11, 11, 10, 21, 24) ), ), APRSPacket.from_frame( "W3EAX-8>APRS,KC3FIT-1,WIDE1*,WIDE2-1,qAR,KC3AWP-10:!/:JL2:u4wO /A=043080|!j| /W3EAX,326,0,20'C," "nearspace.umd.edu", packet_time=pytz.timezone("America/New_York").localize( datetime(2019, 2, 3, 14, 39, 28) ), ), ], callsign="W3EAX-8", )
def test_index(): packet_1 = APRSPacket.from_frame( "W3EAX-13>APRS,N3KTX-10*,WIDE1,WIDE2-1,qAR,N3TJJ-11:!/:J..:sh'O /A=053614|!g| /W3EAX,313,0,21'C," "nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 36, 16), ) packet_2 = APRSPacket.from_frame( "W3EAX-13>APRS,WIDE1-1,WIDE2-1,qAR,W4TTU:!/:JAe:tn8O /A=046255|!i| /W3EAX,322,0,20'C,nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 38, 23), ) packet_3 = APRSPacket.from_frame( "W3EAX-13>APRS,KC3FIT-1,WIDE1*,WIDE2-1,qAR,KC3AWP-10:!/:JL2:u4wO /A=043080|!j| /W3EAX,326,0,20'C," "nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 39, 28), ) track = APRSTrack(packets=[packet_1, packet_2, packet_3], callsign="W3EAX-13") assert track[0] == packet_1 assert track[0] is packet_1 assert track[datetime(2019, 2, 3, 14, 36, 16)] == packet_1 assert track["2019-02-03 14:36:16"] == packet_1 assert track["2019-02-03 14:38:00"] == packet_2 with pytest.raises(IndexError): track["2019-02-03 14:30:00"]
def test_read_geojson(): reference_directory = REFERENCE_DIRECTORY / "test_write_geojson" tracks = LocationPacketTrack.from_file(reference_directory / "tracks.geojson") aprs_tracks = APRSTrack.from_file(reference_directory / "tracks.geojson") assert tracks[0].attributes["callsign"] == "W3EAX-8" assert aprs_tracks[0].callsign == "W3EAX-8"
def test_append(): packet_1 = APRSPacket.from_frame( "W3EAX-13>APRS,N3KTX-10*,WIDE1,WIDE2-1,qAR,N3TJJ-11:!/:J..:sh'O /A=053614|!g| /W3EAX,313,0,21'C," "nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 36, 16), ) packet_2 = APRSPacket.from_frame( "W3EAX-13>APRS,WIDE1-1,WIDE2-1,qAR,W4TTU:!/:JAe:tn8O /A=046255|!i| /W3EAX,322,0,20'C,nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 38, 23), ) packet_3 = APRSPacket.from_frame( "W3EAX-8>APRS,WIDE1-1,WIDE2-1,qAR,K3DO-11:!/:Gh=:j)#O /A=026909|!Q| /W3EAX,262,0,18'C,http://www.umd.edu" ) track = APRSTrack(callsign="W3EAX-13") track.append(packet_1) track.append(packet_2) with pytest.raises(ValueError): track.append(packet_3) assert track[0] is packet_1 assert track[1] is packet_2 assert track[-1] is packet_2 assert track[:] == track assert track[1:] == APRSTrack(packets=track.packets[1:], callsign=track.callsign, crs=track.crs)
def test_values(): packet_1 = APRSPacket.from_frame( "W3EAX-13>APRS,N3KTX-10*,WIDE1,WIDE2-1,qAR,N3TJJ-11:!/:J..:sh'O /A=053614|!g| /W3EAX,313,0,21'C," "nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 36, 16), ) track = APRSTrack(packets=[packet_1], callsign="W3EAX-13") assert numpy.all(track.coordinates[-1] == packet_1.coordinates)
def test_sorting(): packet_1 = APRSPacket.from_frame( "W3EAX-13>APRS,N3KTX-10*,WIDE1,WIDE2-1,qAR,N3TJJ-11:!/:J..:sh'O /A=053614|!g| /W3EAX,313,0,21'C," "nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 36, 16), ) packet_2 = APRSPacket.from_frame( "W3EAX-13>APRS,WIDE1-1,WIDE2-1,qAR,W4TTU:!/:JAe:tn8O /A=046255|!i| /W3EAX,322,0,20'C,nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 38, 23), ) packet_3 = APRSPacket.from_frame( "W3EAX-13>APRS,KC3FIT-1,WIDE1*,WIDE2-1,qAR,KC3AWP-10:!/:JL2:u4wO /A=043080|!j| /W3EAX,326,0,20'C," "nearspace.umd.edu", packet_time=datetime(2019, 2, 3, 14, 39, 28), ) track = APRSTrack(packets=[packet_2, packet_1, packet_3], callsign="W3EAX-13") assert sorted(track) == [packet_1, packet_2, packet_3]
def retrieve_packets( connections: List[PacketSource], packet_tracks: List[LocationPacketTrack], database: PacketDatabaseTable = None, start_date: datetime = None, end_date: datetime = None, ) -> Dict[str, APRSPacket]: logging.debug(f"receiving packets from {len(connections)} source(s)") current_time = datetime.now() parsed_packets = [] for connection in connections: try: connection_packets = connection.packets parsed_packets.extend(connection_packets) except ConnectionError as error: logging.error(f"{connection.__class__.__name__} - {error}") except TimeIntervalError: pass logging.debug(f"received {len(parsed_packets)} packets") new_packets = {} if len(parsed_packets) > 0: updated_callsigns = set() for parsed_packet in parsed_packets: callsign = parsed_packet["callsign"] start_date = ensure_datetime_timezone(start_date) end_date = ensure_datetime_timezone(end_date) parsed_packet.time = ensure_datetime_timezone(parsed_packet.time) if start_date is not None and parsed_packet.time < start_date: continue if end_date is not None and parsed_packet.time > end_date: continue if callsign not in packet_tracks: packet_tracks[callsign] = APRSTrack(packets=[parsed_packet], callsign=callsign) logging.debug(f"started tracking callsign {callsign:8}") else: packet_track = packet_tracks[callsign] if parsed_packet not in packet_track: packet_track.append(parsed_packet) else: if database is None or parsed_packet.source != database.location: logging.debug( f"skipping duplicate packet: {parsed_packet}") continue if parsed_packet.source not in new_packets: new_packets[parsed_packet.source] = [] new_packets[parsed_packet.source].append(parsed_packet) if callsign not in updated_callsigns: updated_callsigns.add(callsign) for source in new_packets: new_packets[source] = list(sorted(new_packets[source])) for source, packets in new_packets.items(): logging.info( f"received {len(packets)} new packet(s) from {source}") if database is not None: for packets in new_packets.values(): database.send(packet for packet in packets if packet.source != database.location) updated_callsigns = sorted(updated_callsigns) for callsign in updated_callsigns: packet_track = packet_tracks[callsign] packet_time = datetime.utcfromtimestamp( (packet_track.times[-1] - numpy.datetime64("1970-01-01T00:00:00")) / numpy.timedelta64(1, "s")) packet_track.sort(inplace=True) try: coordinate_string = ", ".join( f"{coordinate:.3f}°" for coordinate in packet_track.coordinates[-1, :2]) logging.info( f"{callsign:9} - packet #{len(packet_track):<3} - ({coordinate_string}, {packet_track.coordinates[-1, 2]:9.2f}m)" f'; packet time is {packet_time} ({humanize.naturaltime(current_time - packet_time)}, {packet_track.intervals[-1] / numpy.timedelta64(1, "s"):6.1f} s interval)' f"; traveled {packet_track.overground_distances[-1]:6.1f} m ({packet_track.ground_speeds[-1]:5.1f} m/s) over the ground" f", and {packet_track.ascents[-1]:6.1f} m ({packet_track.ascent_rates[-1]:5.1f} m/s) vertically, since the previous packet" ) except Exception as error: logging.exception(f"{error.__class__.__name__} - {error}") for callsign in updated_callsigns: packet_track = packet_tracks[callsign] packet_time = datetime.utcfromtimestamp( (packet_track.times[-1] - numpy.datetime64("1970-01-01T00:00:00")) / numpy.timedelta64(1, "s")) try: ascent_rates = packet_track.ascent_rates[ packet_track.ascent_rates >= 0] if len(ascent_rates) > 0: mean_ascent_rate = numpy.nanmean(ascent_rates) else: mean_ascent_rate = 0 descent_rates = packet_track.ascent_rates[ packet_track.ascent_rates < 0] if len(descent_rates) > 0: mean_descent_rate = numpy.nanmean(descent_rates) else: mean_descent_rate = 0 if len(packet_track.ground_speeds) > 0: mean_ground_speed = numpy.nanmean( packet_track.ground_speeds) else: mean_ground_speed = 0 if len(packet_track.intervals) > 0: mean_interval = numpy.nanmean(packet_track.intervals / numpy.timedelta64(1, "s")) else: mean_interval = 0 message = (f"{callsign:9} - " f"altitude: {packet_track.altitudes[-1]:6.1f} m" f"; avg. ascent rate: {mean_ascent_rate:5.1f} m/s" f"; avg. descent rate: {mean_descent_rate:5.1f} m/s" f"; avg. ground speed: {mean_ground_speed:5.1f} m/s" f"; avg. packet interval: {mean_interval:6.1f} s") if packet_track.time_to_ground >= timedelta(seconds=0): landing_time = packet_time + packet_track.time_to_ground time_to_ground = current_time - landing_time message += ( f"; estimated landing: {landing_time:%Y-%m-%d %H:%M:%S} ({humanize.naturaltime(time_to_ground)})" f"; max altitude: {packet_track.coordinates[:, 2].max():.2f} m" ) logging.info(message) except Exception as error: logging.exception(f"{error.__class__.__name__} - {error}") return new_packets