"""Helpers to be used with the Météo-France REST API .""" import math from datetime import datetime from pytz import timezone from pytz import utc from .const import ALERT_COLOR_LIST_EN from .const import ALERT_COLOR_LIST_FR from .const import ALERT_TYPE_DICTIONARY_EN from .const import ALERT_TYPE_DICTIONARY_FR from .const import COASTAL_DEPARTMENT_LIST from .const import VALID_DEPARTMENT_LIST from .model.place import Place from .model.warning import PhenomenonMaxColor def get_warning_text_status_from_indice_color( int_color: int, lang: str = "fr" ) -> str | None: """Convert the color code (in int) in readable text (Helper). Args: int_color: Color status in int. Value expected between 1 and 4. lang: Optional; If language is equal 'fr' (default value) results will be in French. All other value will give results in English. Returns: Color status in text. French or English according to the lang parameter. """ if lang == "fr": return ALERT_COLOR_LIST_FR[int_color] return ALERT_COLOR_LIST_EN[int_color] def get_phenomenon_name_from_indice( int_phenomenon: str, lang: str = "fr" ) -> str | None: """Convert the phenomenon code in readable text (Helper). Args: int_phenomenon: ID of the phenomenon in int. Value expected between 1 and 9. lang: Optional; If language is equal "fr" (default value) results will be in French. All other value will give results in English. Returns: phenomenon in text. French or English according to the lang parameter. """ if lang == "fr": return ALERT_TYPE_DICTIONARY_FR[int_phenomenon] return ALERT_TYPE_DICTIONARY_EN[int_phenomenon] def is_coastal_department(department_number: str) -> bool: """Identify when a second bulletin is available for coastal risks (Helper). Args: department_number: Department number on 2 characters Returns: True if the department have an additional coastal bulletin. False otherwise. """ return department_number in COASTAL_DEPARTMENT_LIST def is_valid_warning_department(department_number: str) -> bool: """Identify if there is a weather alert bulletin for this department (Helper). Weather alert bulletins are available only for metropolitan France and Andorre. Args: department_number: Department number on 2 characters. Returns: True if a department is metropolitan France or Andorre. """ return department_number in VALID_DEPARTMENT_LIST def readable_phenomenons_dict( list_phenomenons: list[PhenomenonMaxColor], language: str = "fr" ) -> dict[str | None, str | None]: """Create a dictionary with human readable keys and values (Helper). Args: list_phenomenons: Dictionary with phenomenon ID and color code of status. language: Optional; If language is equal "fr" (default value) results will be in French. All other value will give results in English. Returns: Dictionary with keys and value human readable. """ # Init empty dictionary readable_dict = {} # Translate phenomenon name and alert level for phenomenon in list_phenomenons: readable_dict[ get_phenomenon_name_from_indice(phenomenon["phenomenon_id"], language) ] = get_warning_text_status_from_indice_color( phenomenon["phenomenon_max_color_id"], language ) return readable_dict def haversine(coord1: tuple[float, float], coord2: tuple[float, float]) -> float: """Compute distance in meters between to GPS coordinates using Harvesine formula. source: https://janakiev.com/blog/gps-points-distance-python/ Args: coord1: Tuple with latitude and longitude in degrees for first point coord2: Tuple with latitude and longitude in degrees for second point Returns: Distance in meters between the two points """ radius = 6372800 # Earth radius in meters lat1, lon1 = coord1 lat2, lon2 = coord2 phi1, phi2 = math.radians(lat1), math.radians(lat2) dphi = math.radians(lat2 - lat1) dlambda = math.radians(lon2 - lon1) a = ( math.sin(dphi / 2) ** 2 + math.cos(phi1) * math.cos(phi2) * math.sin(dlambda / 2) ** 2 ) return 2 * radius * math.atan2(math.sqrt(a), math.sqrt(1 - a)) def sort_places_versus_distance_from_coordinates( list_places: list[Place], gps_coord: tuple[float, float] ) -> list[Place]: """Oder list of places according to the distance to a reference coordinates. Note: this helper is compensating the bad results of the API. Results in the API are generally sorted, but lot of cases identified where the order is inconsistent (example: Montréal) Args: list_places: List of Place instances to be ordered gps_coord: Tuple with latitude and longitude in degrees for the reference point Returns: List of Place instances ordered by distance to the reference point (nearest first) """ sorted_places = sorted( list_places, key=lambda x: haversine((float(x.latitude), float(x.longitude)), gps_coord), ) return sorted_places def timestamp_to_datetime_with_locale_tz(timestamp: int, local_tz: str) -> datetime: """Convert timestamp in datetime (Helper). Args: timestamp: Timestamp. local_tz: Name of the timezone to be used to convert the timestamp. Returns: Datetime instance corresponding to the timestamp with a timezone. """ # convert timestamp in datetime with UTC timezone dt_utc = utc.localize(datetime.utcfromtimestamp(timestamp)) # convert datetime to local timezone return dt_utc.astimezone(timezone(local_tz))