Source code for metadata_tools.geometry_support.formatting

################################################################################
# geometry_support/formatting.py - Column value formatting helpers.
#
# Config-free: depends only on oops/polymath/julian/numpy plus util, so the
# number-formatting logic can be unit-tested without the host plugin.
################################################################################
import warnings
from typing import Any, cast

import julian
import numpy as np
import oops
import polymath

import metadata_tools.util as util
from metadata_tools.geometry_support.formats import FormatTuple


#===============================================================================
[docs] def circle_coverage(angles: Any, null_value: float | str, sampling: int, flag: str | None = None) -> list[Any]: """Return inferred angular coverage, accounting for the mask. Parameters: angles: Angles in deg, as a list, np.array, or Scalar. null_value: Value to return when fully masked. sampling: Pixel sampling density. flag: "-180" to return values in the range (-180,180) rather than (0,360). Returns: Minimum and maximum values in the cyclic array. """ # Apply mask if isinstance(angles, polymath.Scalar): # Return null if fully masked if angles.mask is True: return [null_value, null_value] # Use full array if not masked if angles.mask is False: angles = angles.values # Apply mask if full mask present else: angles = angles.values[angles.antimask] return util._get_range_mod360(angles, width=sampling+1, diffmin=1, alt_format=flag)
#===============================================================================
[docs] def formatted_column(values: Any, fmt: FormatTuple, sampling: int) -> str: """Return one formatted column (or a pair of columns) as a string. Parameters: values: A Scalar of values with its applied mask (or a string). fmt: Format tuple from FORMAT_DICT/ALT_FORMAT_DICT. sampling: Pixel sampling density. Returns: Formatted column. """ # Interpret the format (flag, number_of_values, column_width, standard_format, overflow_format, null_value, valid_minimum, valid_maximum, _, _) = fmt # Convert from radians to degrees if necessary if flag in ("DEG", "360", "-180"): values = values * oops.DPR # Create a list of the numeric values for this column results: list[Any] if not isinstance(values, str): if number_of_values == 1: meanval = values.mean().as_builtin() if isinstance(meanval, oops.Scalar) and meanval.mask: results = [null_value] else: results = [meanval] elif np.all(values.mask): results = [null_value, null_value] elif flag == "360": results = circle_coverage(values, null_value, sampling) elif flag == "-180": results = circle_coverage(values, null_value, sampling, flag=flag) else: results = [values.min().as_builtin(), values.max().as_builtin()] else: results = [values] # Convert results to ISO if flag in ("ISO", "iso"): if not isinstance(results[0], str): s = julian.iso_from_tai(results, digits=3) results = [str(s[0]), str(s[1])] # Write the formatted value(s) strings: list[str] = [] for entry in results: number: Any = entry error_message = "" string: str # numeric values: flag common exceptions and use standard format if not isinstance(number, str): if np.isnan(number): warnings.warn("NaN encountered", stacklevel=2) number = null_value if np.isinf(number): warnings.warn("infinity encountered", stacklevel=2) number = null_value if valid_minimum != valid_maximum: if (number < valid_minimum) | (number > valid_maximum): number = null_value string = standard_format % number # string values: left justify and enclose in double quotes else: string = '"' + number.strip('"').ljust(column_width-2) + '"' # handle formatting overflow if len(string) > column_width: # An overflow format is always defined for columns that can overflow. overflow = cast(str, overflow_format) string = overflow % number if len(string) > column_width: number = min(max(-9.99e99, number), 9.99e99) string99 = overflow % number if len(string99) > column_width: error_message = "column overflow: " + string else: warnings.warn("column overflow: " + string + " clipped to " + string99) string = string99 string = string[:column_width] # add the formatted value strings.append(string) if error_message != "": raise RuntimeError(error_message) return ",".join(strings)