Source code for uwsift.model.area_definitions_manager

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
uwsift.model.area_definitions_manager
-------------------------------------

Manage area definitions from Satpy/Pyresample.

:author: A.Rettig <alexander.rettig@askvisual.de>
"""
from __future__ import annotations

import logging

from pyresample.area_config import AreaNotFound, load_area_from_string
from pyresample.geometry import AreaDefinition
from satpy.resample import get_area_def

from uwsift import config

LOG = logging.getLogger(__name__)

DEFAULT_AREAS = {
    # A plate carree Projection (aka geographic projection, lat/lon projection,
    # plane chart) should be available always. It will be only added though,
    # if there is no other area definition found in configuration which uses the
    # 'latlong' projection (or its PROJ.4 aliases)
    "Plate Carree": {"plate_carree": "plate_carree"}
}

DEFAULT_AREA_DEFINITIONS_YAML = """
plate_carree:
  description:
    Plate Carree with 0.1 degree resolution
  projection:
    proj: longlat
    ellps: WGS84
  shape:
    height: 1800
    width: 3600
  area_extent:
    lower_left_xy: [-180.0, -90.0]
    upper_right_xy: [180.0, 90.0]
"""


[docs] class AreaDefinitionsManager: """ Manage Pyresample AreaDefinitions, i.e., check their availability in accessible configuration and provide them based on their *display name*. The *display name* is the one to be used in the GUI, it should be less abstract than the *area_id* and shorter than the *description* provided in the AreaDefinition object. """ _available_area_defs_by_id: None | dict[str, AreaDefinition] = None _available_area_defs_group_by_group_name: None | dict[str, dict[str, str]] = ( None # changed | group_name : { name : id name : id} )
[docs] @classmethod def init_available_area_defs(cls) -> None: cls._available_area_defs_by_id = {} cls._available_area_defs_group_by_group_name = {} desired_area_defs = config.get("area_definitions", {}) for area_def_group_name, area_def_group in desired_area_defs.items(): for area_resolution, area_id in area_def_group.items(): try: area_def = get_area_def(area_id) except AreaNotFound as e: LOG.warning( f"Area definition configured for display name" f" '{area_id}' unknown: {e}. Skipping..." ) continue # adding ar LOG.info(f"Adding area definition: {area_def_group_name} , {area_resolution} -> {area_id}") cls._available_area_defs_by_id[area_id] = area_def cls._available_area_defs_group_by_group_name[area_def_group_name] = area_def_group # cls._available_area_defs_group_by_group_name and cls._available_area_defs_by_id are initialised # Check for existence of at least one 'latlong' projection # (https://proj.org/operations/conversions/latlon.html) for area_def in cls._available_area_defs_by_id.values(): if area_def.crs.is_geographic: return # Add default area definition(s)? for area_def_group_name, area_def_group in DEFAULT_AREAS.items(): for area_resolution, area_id in area_def_group.items(): area_def = load_area_from_string(DEFAULT_AREA_DEFINITIONS_YAML, area_id) LOG.info(f"Adding area definition: {area_def_group_name} , {area_resolution} -> {area_id}") cls._available_area_defs_by_id[area_id] = area_def cls._available_area_defs_group_by_group_name[area_def_group_name] = area_def_group
# # returns all area projections
[docs] @classmethod def available_area_def_names(cls): return cls._available_area_defs_group_by_group_name.keys()
[docs] @classmethod def area_def_by_id(cls, id): return cls._available_area_defs_by_id.get(id)
# returns the first area definition from the group
[docs] @classmethod def area_def_by_name(cls, name): area_group = cls._available_area_defs_group_by_group_name.get(name) first_key = next(iter(area_group)) area_id = area_group[first_key] return cls.area_def_by_id(area_id)
[docs] @classmethod def default_area_def_name(cls): # TODO: take from configuration, make robust # Since nothing has been configured, take the first key (i.e. the # display_name) of the known area definitions return next(iter(cls._available_area_defs_group_by_group_name))
# returns all possible resolutions for one area projection
[docs] @classmethod def available_area_def_group_resolutions(cls, group_name): return cls._available_area_defs_group_by_group_name.get(group_name).keys()
# returns area group by its name --- for example if a group_name = MSG SEVIRI FES, a return value will be # {3 km: msg_seviri_fes_3km, 1 km: msg_seviri_fes_1km}
[docs] @classmethod def area_group_by_group_name(cls, group_name): return cls._available_area_defs_group_by_group_name.get(group_name)
# returns area definition by its name and resolution --- for example if a group_name = MSG SEVIRI FES and # resolution = 3km , a return value will be area definition for id = msg_seviri_fes_3km
[docs] @classmethod def area_def_by_group_name_and_resolution(cls, group_name, resolution): return cls.area_def_by_id(cls._available_area_defs_group_by_group_name.get(group_name).get(resolution))
# prepares area def for resampling
[docs] @classmethod def prepare_area_def_for_resampling(cls, area_def, width, height): # when the shape is changed, it also affects other parameters in the area definition area_def.width = width area_def.height = height area_def.pixel_size_x = (area_def.area_extent[2] - area_def.area_extent[0]) / float(area_def.width) area_def.pixel_size_y = (area_def.area_extent[3] - area_def.area_extent[1]) / float(area_def.height) area_def.pixel_upper_left = ( float(area_def.area_extent[0]) + float(area_def.pixel_size_x) / 2, float(area_def.area_extent[3]) - float(area_def.pixel_size_y) / 2, ) area_def.pixel_offset_x = -area_def.area_extent[0] / area_def.pixel_size_x area_def.pixel_offset_y = area_def.area_extent[3] / area_def.pixel_size_y
# calculates pixel_size_x and pixel_size_y for an area definition with custom resolution values
[docs] @classmethod def area_def_custom_resolution_values(cls, area_def, width, height): if width and height: pixel_size_x = (area_def.area_extent[2] - area_def.area_extent[0]) / float(width) pixel_size_y = (area_def.area_extent[3] - area_def.area_extent[1]) / float(height) return pixel_size_x, pixel_size_y
# TODO: Why does this need to be a class being updated instead of an instance of a class? AreaDefinitionsManager.init_available_area_defs()