afscgap.convert

Logic to convert types when interacting with the AFSC GAP REST service.

(c) 2024 Regents of University of California / The Eric and Wendy Schmidt Center for Data Science and the Environment at UC Berkeley.

This file is part of afscgap released under the BSD 3-Clause License. See LICENSE.md.

  1"""
  2Logic to convert types when interacting with the AFSC GAP REST service.
  3
  4(c) 2024 Regents of University of California / The Eric and Wendy Schmidt Center
  5for Data Science and the Environment at UC Berkeley.
  6
  7This file is part of afscgap released under the BSD 3-Clause License. See
  8LICENSE.md.
  9"""
 10import re
 11
 12from afscgap.typesdef import OPT_FLOAT
 13
 14ISO_8601_REGEX = re.compile('(?P<year>\\d{4})\\-(?P<month>\\d{2})\\-' + \
 15    '(?P<day>\\d{2})T(?P<hours>\\d{2})\\:(?P<minutes>\\d{2})\\:' + \
 16    '(?P<seconds>\\d{2})')
 17
 18CONVERTERS = {
 19    'area': {
 20        'ha': lambda x: x,
 21        'm2': lambda x: x * 10000,
 22        'km2': lambda x: x * 0.01
 23    },
 24    'distance': {
 25        'm': lambda x: x,
 26        'km': lambda x: x / 1000
 27    },
 28    'temperature': {
 29        'c': lambda x: x,
 30        'f': lambda x: x * 9 / 5 + 32
 31    },
 32    'time': {
 33        'day': lambda x: x / 24,
 34        'hr': lambda x: x,
 35        'min': lambda x: x * 60
 36    },
 37    'weight': {
 38        'g': lambda x: x * 1000,
 39        'kg': lambda x: x
 40    },
 41    'degrees': {
 42        'dd': lambda x: x
 43    },
 44    'effortWeight': {
 45        'kg/ha': lambda x: x / 100,
 46        'kg1000/km2': lambda x: x / 0.1,
 47        'kg/km2': lambda x: x
 48    },
 49    'effortCount': {
 50        'no/ha': lambda x: x / 100,
 51        'no1000/km2': lambda x: x / 0.1,
 52        'no/km2': lambda x: x,
 53        'count/ha': lambda x: x / 100,
 54        'count1000/km2': lambda x: x / 0.1,
 55        'count/km2': lambda x: x
 56    }
 57}
 58
 59UNCONVERTERS = {
 60    'area': {
 61        'ha': lambda x: x,
 62        'm2': lambda x: x / 10000,
 63        'km2': lambda x: x / 0.01
 64    },
 65    'distance': {
 66        'm': lambda x: x,
 67        'km': lambda x: x * 1000
 68    },
 69    'temperature': {
 70        'c': lambda x: x,
 71        'f': lambda x: (x - 32) * 5 / 9
 72    },
 73    'time': {
 74        'day': lambda x: x * 24,
 75        'hr': lambda x: x,
 76        'min': lambda x: x / 60
 77    },
 78    'weight': {
 79        'g': lambda x: x / 1000,
 80        'kg': lambda x: x
 81    },
 82    'degrees': {
 83        'dd': lambda x: x
 84    },
 85    'effortWeight': {
 86        'kg/ha': lambda x: x * 100,
 87        'kg1000/km2': lambda x: x * 0.1,
 88        'kg/km2': lambda x: x
 89    },
 90    'effortCount': {
 91        'no/ha': lambda x: x * 100,
 92        'no1000/km2': lambda x: x * 0.1,
 93        'no/km2': lambda x: x,
 94        'count/ha': lambda x: x * 100,
 95        'count1000/km2': lambda x: x * 0.1,
 96        'count/km2': lambda x: x
 97    }
 98}
 99
100UNIT_TYPES = {
101    'ha': 'area',
102    'm2': 'area',
103    'km2': 'area',
104    'm': 'distance',
105    'km': 'distance',
106    'c': 'temperature',
107    'f': 'temperature',
108    'day': 'time',
109    'hr': 'time',
110    'min': 'time',
111    'g': 'weight',
112    'kg': 'weight',
113    'dd': 'degrees',
114    'kg/ha': 'effortWeight',
115    'kg1000/km2': 'effortWeight',
116    'kg/km2': 'effortWeight',
117    'no/ha': 'effortCount',
118    'no1000/km2': 'effortCount',
119    'no/km2': 'effortCount',
120    'count/ha': 'effortCount',
121    'count1000/km2': 'effortCount',
122    'count/km2': 'effortCount'
123}
124
125
126def is_iso8601(target: str) -> bool:
127    """Determine if a string matches an expected ISO 8601 format.
128
129    Args:
130        target: The string to test.
131
132    Returns:
133        True if it matches the expected format and false otherwise.
134    """
135    return ISO_8601_REGEX.match(target) is not None
136
137
138def convert(target: OPT_FLOAT, source: str, destination: str) -> OPT_FLOAT:
139    """Convert a value.
140
141    Args:
142        target: The value to convert.
143        source: Original units.
144        destination: Target units.
145
146    Returns:
147        The converted value. Note that, if target is None, will return None.
148    """
149    if target is None:
150        return None
151
152    if source not in UNIT_TYPES:
153        raise RuntimeError('Unknown units: %s' % source)
154
155    if destination not in UNIT_TYPES:
156        raise RuntimeError('Unknown units: %s' % destination)
157
158    source_type = UNIT_TYPES[source]
159    destination_type = UNIT_TYPES[destination]
160
161    if source_type != destination_type:
162        raise RuntimeError('Cannot convert from %s to %s' % (source, destination))
163
164    source_converter = UNCONVERTERS[source_type][source]
165    destination_converter = CONVERTERS[destination_type][destination]
166
167    unconverted = source_converter(target)
168    converted = destination_converter(unconverted)
169
170    return converted
ISO_8601_REGEX = re.compile('(?P<year>\\d{4})\\-(?P<month>\\d{2})\\-(?P<day>\\d{2})T(?P<hours>\\d{2})\\:(?P<minutes>\\d{2})\\:(?P<seconds>\\d{2})')
CONVERTERS = {'area': {'ha': <function <lambda>>, 'm2': <function <lambda>>, 'km2': <function <lambda>>}, 'distance': {'m': <function <lambda>>, 'km': <function <lambda>>}, 'temperature': {'c': <function <lambda>>, 'f': <function <lambda>>}, 'time': {'day': <function <lambda>>, 'hr': <function <lambda>>, 'min': <function <lambda>>}, 'weight': {'g': <function <lambda>>, 'kg': <function <lambda>>}, 'degrees': {'dd': <function <lambda>>}, 'effortWeight': {'kg/ha': <function <lambda>>, 'kg1000/km2': <function <lambda>>, 'kg/km2': <function <lambda>>}, 'effortCount': {'no/ha': <function <lambda>>, 'no1000/km2': <function <lambda>>, 'no/km2': <function <lambda>>, 'count/ha': <function <lambda>>, 'count1000/km2': <function <lambda>>, 'count/km2': <function <lambda>>}}
UNCONVERTERS = {'area': {'ha': <function <lambda>>, 'm2': <function <lambda>>, 'km2': <function <lambda>>}, 'distance': {'m': <function <lambda>>, 'km': <function <lambda>>}, 'temperature': {'c': <function <lambda>>, 'f': <function <lambda>>}, 'time': {'day': <function <lambda>>, 'hr': <function <lambda>>, 'min': <function <lambda>>}, 'weight': {'g': <function <lambda>>, 'kg': <function <lambda>>}, 'degrees': {'dd': <function <lambda>>}, 'effortWeight': {'kg/ha': <function <lambda>>, 'kg1000/km2': <function <lambda>>, 'kg/km2': <function <lambda>>}, 'effortCount': {'no/ha': <function <lambda>>, 'no1000/km2': <function <lambda>>, 'no/km2': <function <lambda>>, 'count/ha': <function <lambda>>, 'count1000/km2': <function <lambda>>, 'count/km2': <function <lambda>>}}
UNIT_TYPES = {'ha': 'area', 'm2': 'area', 'km2': 'area', 'm': 'distance', 'km': 'distance', 'c': 'temperature', 'f': 'temperature', 'day': 'time', 'hr': 'time', 'min': 'time', 'g': 'weight', 'kg': 'weight', 'dd': 'degrees', 'kg/ha': 'effortWeight', 'kg1000/km2': 'effortWeight', 'kg/km2': 'effortWeight', 'no/ha': 'effortCount', 'no1000/km2': 'effortCount', 'no/km2': 'effortCount', 'count/ha': 'effortCount', 'count1000/km2': 'effortCount', 'count/km2': 'effortCount'}
def is_iso8601(target: str) -> bool:
127def is_iso8601(target: str) -> bool:
128    """Determine if a string matches an expected ISO 8601 format.
129
130    Args:
131        target: The string to test.
132
133    Returns:
134        True if it matches the expected format and false otherwise.
135    """
136    return ISO_8601_REGEX.match(target) is not None

Determine if a string matches an expected ISO 8601 format.

Arguments:
  • target: The string to test.
Returns:

True if it matches the expected format and false otherwise.

def convert( target: Optional[float], source: str, destination: str) -> Optional[float]:
139def convert(target: OPT_FLOAT, source: str, destination: str) -> OPT_FLOAT:
140    """Convert a value.
141
142    Args:
143        target: The value to convert.
144        source: Original units.
145        destination: Target units.
146
147    Returns:
148        The converted value. Note that, if target is None, will return None.
149    """
150    if target is None:
151        return None
152
153    if source not in UNIT_TYPES:
154        raise RuntimeError('Unknown units: %s' % source)
155
156    if destination not in UNIT_TYPES:
157        raise RuntimeError('Unknown units: %s' % destination)
158
159    source_type = UNIT_TYPES[source]
160    destination_type = UNIT_TYPES[destination]
161
162    if source_type != destination_type:
163        raise RuntimeError('Cannot convert from %s to %s' % (source, destination))
164
165    source_converter = UNCONVERTERS[source_type][source]
166    destination_converter = CONVERTERS[destination_type][destination]
167
168    unconverted = source_converter(target)
169    converted = destination_converter(unconverted)
170
171    return converted

Convert a value.

Arguments:
  • target: The value to convert.
  • source: Original units.
  • destination: Target units.
Returns:

The converted value. Note that, if target is None, will return None.