Ejemplo n.º 1
0
    def __init__(
        self,
        api_url: str,
        start_location: Union[Tuple[float, float, Optional[float]], Point],
        start_time: datetime,
        ascent_rate: float,
        burst_altitude: float,
        sea_level_descent_rate: float,
        float_altitude: float = None,
        float_duration: timedelta = None,
        name: str = None,
        descent_only: bool = False,
    ):
        """
        :param api_url: URL of API
        :param start_location: location of balloon launch
        :param start_time: date and time of balloon launch
        :param ascent_rate: average ascent rate (m/s)
        :param burst_altitude: altitude at which balloon will burst
        :param sea_level_descent_rate: descent rate at sea level (m/s)
        :param float_altitude: altitude of float (m)
        :param float_duration: date and time of float end
        :param name: name of prediction track
        :param descent_only: whether to query for descent only
        """

        if not isinstance(start_location, Point):
            start_location = typepigeon.convert_value(start_location, Point)

        if name is None:
            name = "prediction"

        if start_time is not None:
            if not isinstance(start_time, datetime):
                start_time = typepigeon.convert_value(start_time, datetime)
            if (start_time.tzinfo is None
                    or start_time.tzinfo.utcoffset(start_time) is None):
                start_time = UTC_TIMEZONE.localize(start_time)

        if float_duration is not None:
            if not isinstance(float_duration, datetime):
                float_duration = typepigeon.convert_value(
                    float_duration, timedelta)

        self.api_url = api_url
        self.start_location = start_location
        self.start_time = start_time
        self.ascent_rate = ascent_rate
        self.burst_altitude = burst_altitude
        self.sea_level_descent_rate = abs(sea_level_descent_rate)
        self.float_altitude = float_altitude
        self.float_duration = float_duration
        self.name = name
        self.descent_only = descent_only
Ejemplo n.º 2
0
 def __setitem__(self, key: str, value: Any):
     if key in self.fields:
         field_type = self.fields[key]
         if not isinstance(field_type, type):
             field_type = type(field_type)
         if not isinstance(value, field_type):
             value = typepigeon.convert_value(value, self.fields[key])
     else:
         self.fields[key] = type(value)
     self.__configuration[key] = value
Ejemplo n.º 3
0
def convert_key_pairs(value_mapping: Mapping,
                      type_mapping: Mapping[str, type]) -> Mapping:
    value_mapping = dict(**value_mapping)
    keys = list(value_mapping)
    for key in keys:
        if key in type_mapping:
            value = value_mapping[key]
            value_type = type_mapping[key]
            if not isinstance(value_type, (Collection)) and issubclass(
                    value_type, Configuration):
                value = value_type(**value)
            elif isinstance(value, dict):
                value = convert_key_pairs(value, value_type)
            else:
                value = typepigeon.convert_value(value, value_type)
        else:
            value = typepigeon.convert_value(value_mapping, type_mapping)
        value_mapping[key] = value
    return value_mapping
Ejemplo n.º 4
0
    def packets(self) -> List[LocationPacket]:
        if self.__last_access_time is not None and self.interval is not None:
            interval = datetime.now() - self.__last_access_time
            if interval < self.interval:
                raise TimeIntervalError(
                    f"interval {interval} less than minimum interval {self.interval}"
                )

        if Path(self.location).exists():
            with open(Path(
                    self.location).expanduser().resolve()) as file_connection:
                features = geojson.load(file_connection)
        else:
            response = requests.get(self.location, stream=True)
            features = geojson.loads(response.text)

        packets = []
        for feature in features["features"]:
            if feature["geometry"]["type"] == "Point":
                properties = feature["properties"]
                time = typepigeon.convert_value(properties["time"], datetime)
                del properties["time"]

                if "from" in properties:
                    from_callsign = properties["from"]
                    to_callsign = properties["to"]
                    del properties["from"], properties["to"]

                    packet = APRSPacket(
                        from_callsign,
                        to_callsign,
                        time,
                        *feature["geometry"]["coordinates"],
                        source=self.location,
                        **properties,
                    )
                else:
                    packet = LocationPacket(
                        time,
                        *feature["geometry"]["coordinates"],
                        source=self.location,
                        **properties,
                    )

                packets.append(packet)

        self.__last_access_time = datetime.now()

        return packets
Ejemplo n.º 5
0
def parse_record_values(record: Dict[str, Any],
                        field_types: Dict[str, type]) -> Dict[str, Any]:
    """
    parse the values in the given record into their respective field types

    :param record: dictionary mapping fields to values
    :param field_types: dictionary mapping fields to types
    :return: record with values parsed into their respective types
    """

    for field, value in record.items():
        if field in field_types:
            field_type = field_types[field]
            record[field] = convert_value(value, field_type)
    return record
Ejemplo n.º 6
0
    def packets(self) -> List[APRSPacket]:
        if self.__last_access_time is not None and self.interval is not None:
            interval = datetime.now() - self.__last_access_time
            if interval < self.interval:
                raise TimeIntervalError(
                    f"interval {interval} less than minimum interval {self.interval}"
                )

        if Path(self.location).exists():
            file_connection = open(Path(self.location).expanduser().resolve())
            lines = file_connection.readlines()
        else:
            file_connection = requests.get(self.location, stream=True)
            lines = file_connection.iter_lines()

        packets = []
        for line in lines:
            if len(line) > 0:
                if isinstance(line, bytes):
                    line = line.decode()
                if line not in self.__parsed_lines:
                    self.__parsed_lines.append(line)
                    try:
                        packet_time, raw_aprs = line.split(": ", 1)
                        packet_time = typepigeon.convert_value(
                            packet_time, datetime)
                    except:
                        raw_aprs = line
                        packet_time = datetime.now()
                    raw_aprs = raw_aprs.strip()
                    try:
                        packets.append(
                            APRSPacket.from_frame(raw_aprs,
                                                  packet_time,
                                                  source=self.location))
                    except Exception as error:
                        logging.debug(f"{error.__class__.__name__} - {error}")

        file_connection.close()

        if self.callsigns is not None:
            packets = [
                packet for packet in packets
                if packet.from_callsign in self.callsigns
            ]
        self.__last_access_time = datetime.now()

        return packets
Ejemplo n.º 7
0
    def __init__(
        self,
        time: datetime,
        x: float,
        y: float,
        z: float = None,
        crs: CRS = None,
        source: str = None,
        **kwargs,
    ):
        if not isinstance(time, datetime):
            time = typepigeon.convert_value(time, datetime)

        self.time = time
        self.coordinates = numpy.array((x, y, z if z is not None else 0))
        self.crs = crs if crs is not None else DEFAULT_CRS
        self.source = source
        self.attributes = kwargs
Ejemplo n.º 8
0
    def __init__(
        self,
        default: PredictionConfiguration,
        perturbations: List[PredictionConfiguration],
        **configuration,
    ):
        if not isinstance(perturbations, Mapping):
            perturbations = {
                f"profile_{index + 1}": perturbation
                for index, perturbation in enumerate(
                    typepigeon.convert_value(perturbations,
                                             [PredictionConfiguration]))
            }

        configuration["default"] = default
        configuration["perturbations"] = perturbations

        super().__init__(**configuration)
Ejemplo n.º 9
0
 def update(self, other: Mapping):
     for key, value in other.items():
         if key in self:
             if isinstance(self[key], Configuration):
                 self[key].update(value)
             else:
                 field_type = self.fields[key]
                 if (isinstance(field_type, type)
                         and issubclass(field_type, Configuration)
                         and value is not None):
                     converted_value = field_type(**value)
                 elif isinstance(field_type, Mapping) and value is not None:
                     converted_value = convert_key_pairs(value, field_type)
                 else:
                     converted_value = typepigeon.convert_value(
                         value, field_type)
                 if self[key] is None or self[key] != converted_value:
                     value = converted_value
         self[key] = value
Ejemplo n.º 10
0
    def get(self) -> Dict[str, Any]:
        response = requests.get(self.api_url, params=self.query)

        if response.status_code == 200:
            response = response.json()
            if "error" not in response:
                # TODO tawhiri currently does not include descent when querying a float profile
                if self.profile == FlightProfile.float:
                    # this code runs another prediction query with a standard profile and extracts the descent stage to append to the response from the original query
                    for stage in response["prediction"]:
                        # if a descent stage exists, we don't need to do anything
                        if stage["stage"] == "descent":
                            break
                    else:
                        for stage in response["prediction"]:
                            if stage["stage"] == "float":
                                float_end = stage["trajectory"][-1]
                                break
                        else:
                            raise PredictionError(
                                "API did not return a float trajectory")

                        standard_profile_query = self.__class__(
                            start_location=[
                                float_end["longitude"],
                                float_end["latitude"],
                                float_end["altitude"],
                            ],
                            start_time=typepigeon.convert_value(
                                float_end["datetime"], datetime),
                            ascent_rate=10,
                            burst_altitude=float_end["altitude"] + 0.1,
                            sea_level_descent_rate=self.sea_level_descent_rate,
                            profile=FlightProfile.standard,
                            version=self.version,
                            dataset_time=self.dataset_time,
                            float_altitude=None,
                            float_duration=None,
                            api_url=self.api_url,
                            name=self.name,
                            descent_only=True,
                        )

                        for stage in standard_profile_query.get(
                        )["prediction"]:
                            if stage["stage"] == "descent":
                                response["prediction"].append(stage)
                                break

                if self.descent_only:
                    indices_to_remove = []
                    for index, stage in enumerate(response["prediction"]):
                        # if a descent stage exists, we don't need to do anything
                        if stage["stage"] != "descent":
                            indices_to_remove.append(index)
                            break
                    for index in indices_to_remove:
                        response["prediction"].pop(index)

                return response
            else:
                raise PredictionError(response["error"]["description"])
        else:
            try:
                error = response.json()["error"]["description"]
            except:
                error = "no message"
            raise ConnectionError(
                f"connection raised error {response.status_code} for {response.url} - {error}"
            )
Ejemplo n.º 11
0
    def __init__(
        self,
        start_location: Union[Tuple[float, float, Optional[float]], Point],
        start_time: datetime,
        ascent_rate: float,
        burst_altitude: float,
        sea_level_descent_rate: float,
        profile: FlightProfile = None,
        version: float = None,
        dataset_time: datetime = None,
        float_altitude: float = None,
        float_duration: timedelta = None,
        api_url: PredictionAPIURL = None,
        name: str = None,
        descent_only: bool = False,
    ):
        if profile is None:
            if not descent_only and (float_altitude is not None
                                     or float_duration is not None):
                profile = FlightProfile.float
            else:
                profile = FlightProfile.standard

        if dataset_time is not None:
            if not isinstance(dataset_time, datetime):
                dataset_time = typepigeon.convert_value(dataset_time, datetime)
            if (dataset_time.tzinfo is None
                    or dataset_time.tzinfo.utcoffset(dataset_time) is None):
                dataset_time = UTC_TIMEZONE.localize(dataset_time)

        if api_url is None:
            api_url = PredictionAPIURL.cusf

        if isinstance(api_url, PredictionAPIURL):
            api_url = api_url.value

        if name is None:
            name = "cusf_prediction"

        super().__init__(
            api_url,
            start_location,
            start_time,
            ascent_rate,
            burst_altitude,
            sea_level_descent_rate,
            float_altitude,
            float_duration,
            name,
            descent_only,
        )

        # CUSF API requires longitude in 0-360 format
        if self.start_location.x < 0:
            x = self.start_location.x + 360
        else:
            x = self.start_location.x
        launch_coordinates = [x, self.start_location.y]
        if self.start_location.has_z:
            launch_coordinates.append(self.start_location.z)
        self.launch_site = Point(launch_coordinates)

        self.profile = profile if not self.descent_only else FlightProfile.standard
        self.version = version
        self.dataset_time = dataset_time
Ejemplo n.º 12
0
 def end_time(self, end_time: datetime):
     end_time = typepigeon.convert_value(end_time, datetime)
     self.__end_time = end_time
     if self.end_time < self.start_time:
         self.end_time = self.start_time
         self.start_time = end_time
Ejemplo n.º 13
0
 def start_time(self, start_time: datetime):
     start_time = typepigeon.convert_value(start_time, datetime)
     self.__start_time = start_time
     if self.start_time > self.end_time:
         self.start_time = self.end_time
         self.end_time = start_time