#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
.py
~~~
PURPOSE
Support calculations, namedtuples and constants used throughout the library and application.
REFERENCES
REQUIRES
numpy
numba
:author: R.K.Garcia <rayg@ssec.wisc.edu>
:copyright: 2015 by University of Wisconsin Regents, see AUTHORS for more details
:license: GPLv3, see LICENSE for more details
"""
import logging
from dataclasses import dataclass
from enum import Enum
from typing import List, NamedTuple, Optional, Union
from uuid import UUID
from pyproj import Proj
LOG = logging.getLogger(__name__)
# separator for family::category::serial representation of product identity
FCS_SEP = "::"
# standard N/A string used in FCS
NOT_AVAILABLE = FCS_NA = "N/A"
PREFERRED_SCREEN_TO_TEXTURE_RATIO = 1.0 # screenpx:texturepx that we want to keep, ideally, by striding
# http://home.online.no/~sigurdhu/WGS84_Eng.html
DEFAULT_TILE_HEIGHT = 512
DEFAULT_TILE_WIDTH = 512
DEFAULT_TEXTURE_HEIGHT = 2
DEFAULT_TEXTURE_WIDTH = 16
DEFAULT_ANIMATION_DELAY = 500.0 # milliseconds
# The values below are taken from the test geotiffs that are projected to the `DEFAULT_PROJECTION` below.
# These units are in meters in mercator projection space
DEFAULT_X_PIXEL_SIZE = 4891.969810251281160
DEFAULT_Y_PIXEL_SIZE = -7566.684931505724307
DEFAULT_ORIGIN_X = -20037508.342789247632027
DEFAULT_ORIGIN_Y = 15496570.739723727107048
DEFAULT_PROJECTION = "+proj=merc +datum=WGS84 +ellps=WGS84 +over"
DEFAULT_PROJ_OBJ = p = Proj(DEFAULT_PROJECTION)
C_EQ = p(180, 0)[0] - p(-180, 0)[0]
C_POL = p(0, 89.9)[1] - p(0, -89.9)[1]
MAX_EXCURSION_Y = C_POL / 2.0
MAX_EXCURSION_X = C_EQ / 2.0
# how many 'tessellation' tiles in one texture tile? 2 = 2 rows x 2 cols
TESS_LEVEL = 200
IMAGE_MESH_SIZE = 100
# smallest difference between two image extents (in canvas units)
# before the image is considered "out of view"
CANVAS_EXTENTS_EPSILON = 1e-4
# For simple geolocated image
# Size of a image quad subdivision grid cell in meters
DEFAULT_GRID_CELL_WIDTH = 96000
DEFAULT_GRID_CELL_HEIGHT = 96000
DEFAULT_TIME_FORMAT = "%Y-%m-%d %H:%M:%S"
# R_EQ = 6378.1370 # km
# R_POL = 6356.7523142 # km
# C_EQ = 40075.0170 # linear km
# C_POL = 40007.8630 # linear km
# MAX_EXCURSION_Y = C_POL/4.0
# MAX_EXCURSION_X = C_EQ/2.0
N_A = "n/a"
NAN = str(float("nan"))
# LayerModel column display names
VISIBILITY = ""
SOURCE = "Satellite & Instrument"
NAME = "Name"
WAVELENGTH = "λ"
PROBE_VALUE = "Probe"
PROBE_UNIT = "Unit"
LAYER_TREE_VIEW_HEADER = [VISIBILITY, SOURCE, NAME, WAVELENGTH, PROBE_VALUE, PROBE_UNIT]
INVALID_COLOR_LIMITS = (float("inf"), float("-inf")) # Yes, (+inf, -inf), this is the smallest imaginable range
FALLBACK_RANGE = (0.0, 255.0)
[docs]
class ImageDisplayMode(str, Enum):
SIMPLE_GEOLOCATED = "simple_geolocated"
TILED_GEOLOCATED = "tiled_geolocated"
PIXEL_MATRIX = "pixel_matrix"
# Calculate and provide LayerModel column indices from LAYER_TREE_VIEW_HEADER
[docs]
class LayerModelColumns:
VISIBILITY = LAYER_TREE_VIEW_HEADER.index(VISIBILITY) # noqa
SOURCE = LAYER_TREE_VIEW_HEADER.index(SOURCE) # noqa
NAME = LAYER_TREE_VIEW_HEADER.index(NAME) # noqa
WAVELENGTH = LAYER_TREE_VIEW_HEADER.index(WAVELENGTH) # noqa
PROBE_VALUE = LAYER_TREE_VIEW_HEADER.index(PROBE_VALUE) # noqa
PROBE_UNIT = LAYER_TREE_VIEW_HEADER.index(PROBE_UNIT) # noqa
LATLON_GRID_DATASET_NAME = "Geo-Grid" # noqa
BORDERS_DATASET_NAME = "Borders"
DEFAULT_GAMMA_VALUE = 1.0
[docs]
class LayerVisibility(NamedTuple):
"""Combine the two parameters controlling the visibility of layers."""
visible: bool
opacity: float
[docs]
class Box(NamedTuple):
bottom: float
left: float
top: float
right: float
[docs]
class IndexBox(NamedTuple):
"""Box using integer index values as coordinates.
This has a very similar purpose to :class:`Box`, but restricts coordinates
to integer values.
"""
bottom: int
left: int
top: int
right: int
[docs]
class Resolution(NamedTuple):
"""Pixel resolution (km per pixel)."""
dy: float
dx: float
[docs]
class Point(NamedTuple):
y: float
x: float
[docs]
class ViewBox(NamedTuple):
"""Combination of Box + Resolution."""
bottom: float
left: float
top: float
right: float
dy: float
dx: float
[docs]
class Flags(set):
"""A set of enumerated Flags which may ultimately be represented as a bitfield, but observing set interface"""
pass
[docs]
class State(Enum):
"""State for products in document."""
UNKNOWN = 0
POTENTIAL = 1 # product is available as a resource and could be imported or calculated
ARRIVING = 2 # import or calculation in progress
CACHED = 3 # stable in workspace cache
ATTACHED = 5 # attached and can page in memory, as well as participating in document
ONSCREEN = 6 # actually being displayed at this moment
DANGLING = -1 # cached in workspace but no resource behind it
# PURGE = -2 # scheduled for purging -- eventually
[docs]
class Kind(Enum):
"""Kind of entities we're working with."""
UNKNOWN = 0
IMAGE = 1
OUTLINE = 2
SHAPE = 3
RGB = 4
COMPOSITE = 1 # deprecated: use Kind.IMAGE instead
LINES = 7
VECTORS = 8
POINTS = 9
MC_IMAGE = 10
[docs]
class Instrument(Enum):
UNKNOWN = "???"
GENERATED = "Generated" # for auxiliary data loaded/generated by SIFT
MIXED = "Mixed" # for data computed from data from different instruments
AHI = "AHI"
ABI = "ABI"
AMI = "AMI"
GFS = "GFS"
NAM = "NAM"
SEVIRI = "SEVIRI"
LI = "LI"
GLM = "GLM"
FCI = "FCI"
AVHRR3 = "AVHRR-3"
INSTRUMENT_MAP = {v.value.lower().replace("-", ""): v for v in Instrument}
PLATFORM_MAP = {v.value.lower().replace("-", ""): v for v in Platform}
PLATFORM_MAP["h8"] = Platform.HIMAWARI_8
PLATFORM_MAP["h9"] = Platform.HIMAWARI_9
PLATFORM_MAP["goes16"] = Platform.GOES_16
PLATFORM_MAP["goes17"] = Platform.GOES_17
PLATFORM_MAP["goes18"] = Platform.GOES_18
PLATFORM_MAP["goes19"] = Platform.GOES_19
PLATFORM_MAP["met8"] = Platform.MSG1
PLATFORM_MAP["met9"] = Platform.MSG2
PLATFORM_MAP["met10"] = Platform.MSG3
PLATFORM_MAP["met11"] = Platform.MSG4
[docs]
class Info(Enum):
"""
Standard keys for info dictionaries
Note: some fields correspond to database fields in workspace.metadatabase !
"""
UNKNOWN = "???"
# full path to the resource that the file came from
# DEPRECATED since datasets may not have one-to-one pathname mapping
PATHNAME = "path"
# CF content
SHORT_NAME = "short_name" # CF short_name
LONG_NAME = "long_name" # CF long_name
STANDARD_NAME = "standard_name" # CF compliant standard_name (when possible)
UNITS = "units" # CF compliant (udunits compliant) units string, original data units
# SIFT bookkeeping
DATASET_NAME = "dataset_name" # logical name of the file (possibly human assigned)
KIND = "kind" # Kind enumeration on what kind of layer/dataset this makes
UUID = "uuid" # UUID assigned on import, which follows the layer/dataset around the system
# track determiner is family::category; presentation is determined by family
# family::category::serial is a unique identifier equivalent to conventional make-model-serialnumber
# string representing data family, typically instrument:measurement:wavelength but may vary by data content
FAMILY = "family"
CATEGORY = "category" # string with platform:instrument:target typically but may vary by data content
SERIAL = "serial" # serial number
# projection information
ORIGIN_X = "origin_x"
ORIGIN_Y = "origin_y"
CELL_WIDTH = "cell_width"
CELL_HEIGHT = "cell_height"
PROJ = "proj4"
# original data grid layout
GRID_ORIGIN = "grid_layout_origin"
GRID_FIRST_INDEX_X = "grid_first_index_x"
GRID_FIRST_INDEX_Y = "grid_first_index_y"
# colormap amd data range
CLIM = "clim" # (min,max) color map limits
VALID_RANGE = "valid_range"
SHAPE = "shape" # (rows, columns) or (rows, columns, levels) data shape
COLORMAP = "colormap" # name or UUID of a color map
# glyph (marker) or line styling
STYLE = "style" # name (or FUTURE: UUID?) of a SVG/HTML-like style
SCHED_TIME = "timeline" # scheduled time for observation
OBS_TIME = "obstime" # actual time for observation
OBS_DURATION = "obsduration" # time from start of observation to end of observation
# instrument and scene information
PLATFORM = "platform" # full standard name of spacecraft
SCENE = "scene" # standard scene identifier string for instrument, e.g. FLDK
INSTRUMENT = "instrument" # Instrument enumeration, or string with full standard name
# human-friendly conventions
DISPLAY_NAME = "display_name" # preferred name for displaying
DISPLAY_TIME = "display_time" # typically from guidebook, used for labeling animation frame
UNIT_CONVERSION = "unit_conversion" # (preferred CF units, convert_func, format_func)
# unit numeric conversion func: lambda x, inverse=False: convert-to-units
# unit string format func: lambda x, numeric=True, units=True: formatted string
CENTRAL_WAVELENGTH = "nominal_wavelength"
# only in family info dictionaries:
DISPLAY_FAMILY = "display_family"
# only used by algebraic datasets to save the operation kind
ALGEBRAIC = "algebraic"
def __lt__(self, other):
"""
when using pickletype in sqlalchemy tables, a comparator is needed for enumerations
:param other:
:return:
"""
if isinstance(other, str):
return self.value < other
elif isinstance(other, type(self)):
return self.value < other.value
raise ValueError("cannot compare {} < {}".format(repr(self), repr(other)))
def __gt__(self, other):
"""
when using pickletype in sqlalchemy tables, a comparator is needed for enumerations
:param other:
:return:
"""
if isinstance(other, str):
return self.value > other
elif isinstance(other, type(self)):
return self.value > other.value
raise ValueError("cannot compare {} > {}".format(repr(self), repr(other)))
def __hash__(self):
return hash(self.value)
def __eq__(self, other):
"""
when using pickletype in sqlalchemy tables, a comparator is needed for enumerations
:param other:
:return:
"""
if isinstance(other, str):
return self.value == other
elif isinstance(other, type(self)):
return self.value == other.value
raise ValueError("cannot compare {} == {}".format(repr(self), repr(other)))
[docs]
@dataclass
class Presentation:
"""Presentation information for a layer and dataset."""
uuid: Optional[UUID] # dataset in the layermodel/document/workspace, None if referring to a system layer
kind: Kind # what kind of layer/dataset it is
visible: bool = True # whether it's visible or not
colormap: object = None # name or uuid: color map to use; name for default, uuid for user-specified
style: object = None # name or uuid: SVG/HTML style to use; name for default, (FUTURE?) uuid for user-specified
climits: Union[tuple, list] = INVALID_COLOR_LIMITS # valid min and valid max used for color mapping normalization
gamma: Union[float, List[float]] = 1.0 # valid (0 to 5) for gamma correction
opacity: float = 1.0