import logging import re from twisted.python import log import json import datetime import time from math import sin, cos, sqrt, atan2, radians, floor, ceil from trackdirect.common.Model import Model from trackdirect.repositories.StationRepository import StationRepository from trackdirect.repositories.SenderRepository import SenderRepository from trackdirect.objects.Station import Station from trackdirect.exceptions.TrackDirectMissingSenderError import TrackDirectMissingSenderError from trackdirect.exceptions.TrackDirectMissingStationError import TrackDirectMissingStationError class Packet(Model): """Packet represents a APRS packet, AIS packet or any other supported packet Note: Packet corresponds to a row in the packetYYYYMMDD table """ def __init__(self, db): """The __init__ method. Args: db (psycopg2.Connection): Database connection """ Model.__init__(self, db) self.logger = logging.getLogger('trackdirect') self.id = None self.stationId = None self.senderId = None self.packetTypeId = None self.timestamp = None self.reportedTimestamp = None self.positionTimestamp = None # Inherited from prev packet if position was equal self.latitude = None self.longitude = None self.symbol = None self.symbolTable = None self.markerId = None self.markerCounter = None self.markerPrevPacketTimestamp = None self.mapId = None self.sourceId = None self.mapSector = None self.relatedMapSectors = [] self.speed = None self.course = None self.altitude = None self.rng = None self.phg = None self.latestRngTimestamp = None self.latestPhgTimestamp = None self.comment = None self.rawPath = None self.raw = None # packet tail timestamp indicates how long time ago we had a tail self.packetTailTimestamp = None # If packet reports a new position for a moving symbol is_moving will be 1 otherwise 0 # Some times is_moving will be 0 for a moving symbol, but as fast we realize it is moving related packets will have is_moving set to 1 self.isMoving = 1 self.posambiguity = None # Following attributes will not allways be loaded from database (comes from related tables) self.stationIdPath = [] self.stationNamePath = [] self.stationLocationPath = [] # Will only be used when packet is not inserted to database yet self.replacePacketId = None self.replacePacketTimestamp = None self.abnormalPacketId = None self.abnormalPacketTimestamp = None self.confirmPacketId = None self.confirmPacketTimestamp = None # Will only be used when packet is not inserted to database yet self.ogn = None self.weather = None self.telemetry = None self.stationTelemetryBits = None self.stationTelemetryEqns = None self.stationTelemetryParam = None self.stationTelemetryUnit = None self.senderName = None self.stationName = None def validate(self): """Returns true on success (when object content is valid), otherwise false Returns: True on success otherwise False """ return True def insert(self): """Method to call when we want to save a new object to database Since packet will be inserted in batch we never use this method. Returns: True on success otherwise False """ return False def update(self): """Method to call when we want to save changes to database Since packet will be updated in batch we never use this method. Returns: True on success otherwise False """ return False def getDistance(self, p2Lat, p2Lng): """Get distance in meters between current position and specified position Args: p2Lat (float): Position 2 latitude p2Lng (float): Position 2 longitude Returns: Distance in meters between the two specified positions (as float) """ if (self.latitude is not None and self.longitude is not None): p1Lat = self.latitude p1Lng = self.longitude R = 6378137 # Earths mean radius in meter dLat = radians(p2Lat - p1Lat) dLong = radians(p2Lng - p1Lng) a = sin(dLat / 2) * sin(dLat / 2) + cos(radians(p1Lat)) * \ cos(radians(p2Lat)) * sin(dLong / 2) * sin(dLong / 2) c = 2 * atan2(sqrt(a), sqrt(1 - a)) d = R * c return d # returns the distance in meter else: return None def getCalculatedSpeed(self, prevPacket): """Get speed compared to previous packet position and timestamp Args: prevPacket (Packet): Previous related packet for the same station Returns: Speed in kmh compared to previous packet position and timestamp (as float) """ if (self.latitude is not None and self.longitude is not None): distance = self.getDistance( prevPacket.latitude, prevPacket.longitude) time = abs(prevPacket.timestamp - self.timestamp) if (self.reportedTimestamp is not None and prevPacket.reportedTimestamp is not None and self.reportedTimestamp != 0 and prevPacket.reportedTimestamp != 0 and (self.reportedTimestamp % 60 != 0 or prevPacket.reportedTimestamp % 60 != 0) and prevPacket.reportedTimestamp != self.reportedTimestamp): time = abs(prevPacket.reportedTimestamp - self.reportedTimestamp) if (time == 0): return 0 return distance / time # meters per second else: return None def isSymbolEqual(self, comparePacket): """Returns true if current symbol is equal to symbol in specified packet Args: comparePacket (Packet): Packet to compare current symbol with Returns: True if current symbol is equal to symbol in specified packet """ if (self.symbol is not None and self.symbolTable is not None and comparePacket.symbol is not None and comparePacket.symbolTable is not None and self.symbol == comparePacket.symbol and self.symbolTable == comparePacket.symbolTable): return True else: return False def isPostitionEqual(self, comparePacket): """Returns true if current position is equal to position in specified packet Args: comparePacket (Packet): Packet to compare current position with Returns: True if current position is equal to position in specified packet """ if (comparePacket.latitude is not None and comparePacket.longitude is not None and self.longitude is not None and self.latitude is not None and round(self.latitude, 5) == round(comparePacket.latitude, 5) and round(self.longitude, 5) == round(comparePacket.longitude, 5)): return True else: return False def getTransmitDistance(self): """Calculate the transmit distance Notes: require that stationLocationPath is set Args: None Returns: Distance in meters for this transmission """ if (self.stationLocationPath is None or len(self.stationLocationPath) < 1): return None location = self.stationLocationPath[0] if (location[0] is None or location[1] is None): return None if (self.latitude is not None and self.longitude is not None): # Current packet contains position, use that return self.getDistance(location[0], location[1]) else: # Current packet is missing position, use latest station position stationRepository = StationRepository(self.db) station = stationRepository.getObjectById(self.stationId) if (not station.isExistingObject()): return None if (station.latestConfirmedLatitude is not None and station.latestConfirmedLongitude is not None): curStationLatestLocationPacket = Packet(self.db) curStationLatestLocationPacket.latitude = station.latestConfirmedLatitude curStationLatestLocationPacket.longitude = station.latestConfirmedLongitude return curStationLatestLocationPacket.getDistance(location[0], location[1]) else: return None def getDict(self, includeStationName=False): """Returns a dict representation of the object Args: includeStationName (Boolean): Include station name and sender name in dict Returns: Dict representation of the object """ data = {} data['id'] = self.id if (self.stationId is not None): data['station_id'] = int(self.stationId) else: data['station_id'] = None if (self.senderId is not None): data['sender_id'] = int(self.senderId) else: data['sender_id'] = None data['packet_type_id'] = self.packetTypeId data['timestamp'] = self.timestamp data['reported_timestamp'] = self.reportedTimestamp data['position_timestamp'] = self.positionTimestamp if (self.latitude is not None and self.longitude is not None): data['latitude'] = float(self.latitude) data['longitude'] = float(self.longitude) else: data['latitude'] = None data['longitude'] = None data['symbol'] = self.symbol data['symbol_table'] = self.symbolTable data['marker_id'] = self.markerId data['marker_counter'] = self.markerCounter data['map_id'] = self.mapId data['source_id'] = self.sourceId data['map_sector'] = self.mapSector data['related_map_sectors'] = self.relatedMapSectors data['speed'] = self.speed data['course'] = self.course data['altitude'] = self.altitude data['rng'] = self.rng data['phg'] = self.phg data['latest_phg_timestamp'] = self.latestPhgTimestamp data['latest_rng_timestamp'] = self.latestRngTimestamp data['comment'] = self.comment data['raw_path'] = self.rawPath data['raw'] = self.raw data['packet_tail_timestamp'] = self.packetTailTimestamp data['is_moving'] = self.isMoving data['posambiguity'] = self.posambiguity data['db'] = 1 if (includeStationName): try: stationRepository = StationRepository(self.db) station = stationRepository.getCachedObjectById( data['station_id']) data['station_name'] = station.name except TrackDirectMissingStationError as e: data['station_name'] = '' try: senderRepository = SenderRepository(self.db) sender = senderRepository.getCachedObjectById( data['sender_id']) data['sender_name'] = sender.name except TrackDirectMissingSenderError as e: data['sender_name'] = '' data['station_id_path'] = self.stationIdPath data['station_name_path'] = self.stationNamePath data['station_location_path'] = self.stationLocationPath data['telemetry'] = None if (self.telemetry is not None): data['telemetry'] = self.telemetry.getDict() data['weather'] = None if (self.weather is not None): data['weather'] = self.weather.getDict() data['ogn'] = None if (self.ogn is not None): data['ogn'] = self.ogn.getDict() return data def getJson(self): """Returns a json representation of the object Returns: Json representation of the object (returnes None on failure) """ data = self.getDict() try: return json.dumps(data, ensure_ascii=False).encode('utf8') except (ValueError) as exp: self.logger.error(e, exc_info=1) return None