diff --git a/.travis.yml b/.travis.yml
index bf618a27af..7b95844c38 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -83,7 +83,6 @@ install:
fi
# prepare iris build directory
- - python setup.py --with-unpack build_ext --include-dirs=${PREFIX}/include --library-dirs=${PREFIX}/lib
- if [[ $TEST_TARGET -ne 'coding' ]]; then
IRIS=$(ls -d1 build/lib*/iris);
mkdir $IRIS/etc;
@@ -107,8 +106,8 @@ install:
fi
# iris
- - python setup.py --quiet --with-unpack build
- - python setup.py --quiet --with-unpack install
+ - python setup.py --quiet build
+ - python setup.py --quiet install
script:
- if [[ $TEST_TARGET == 'default' ]]; then
diff --git a/lib/iris/fileformats/pp.py b/lib/iris/fileformats/pp.py
index 5f79a8966e..b998ad981d 100644
--- a/lib/iris/fileformats/pp.py
+++ b/lib/iris/fileformats/pp.py
@@ -39,8 +39,7 @@
import netcdftime
from iris._deprecation import warn_deprecated
-from iris._lazy_data import (array_masked_to_nans, as_concrete_data,
- as_lazy_data, is_lazy_data)
+from iris._lazy_data import as_concrete_data, as_lazy_data, is_lazy_data
import iris.config
import iris.fileformats.rules
import iris.fileformats.pp_rules
@@ -52,11 +51,6 @@
except ImportError:
mo_pack = None
-try:
- from iris.fileformats import _old_pp_packing as pp_packing
-except ImportError:
- pp_packing = None
-
__all__ = ['load', 'save', 'load_cubes', 'PPField',
'reset_load_rules', 'add_save_rules',
@@ -934,13 +928,6 @@ def _data_bytes_to_shaped_array(data_bytes, lbpack, boundary_packing,
decompress_wgdos = mo_pack.decompress_wgdos
except AttributeError:
decompress_wgdos = mo_pack.unpack_wgdos
- elif pp_packing is not None:
- msg = 'iris.fileformats.pp_packing has been ' \
- 'deprecated and will be removed in a future release. ' \
- 'Install mo_pack to make use of the new unpacking ' \
- 'functionality.'
- warn_deprecated(msg)
- decompress_wgdos = pp_packing.wgdos_unpack
else:
msg = 'Unpacking PP fields with LBPACK of {} ' \
'requires mo_pack to be installed'.format(lbpack.n1)
@@ -949,13 +936,6 @@ def _data_bytes_to_shaped_array(data_bytes, lbpack, boundary_packing,
elif lbpack.n1 == 4:
if mo_pack is not None and hasattr(mo_pack, 'decompress_rle'):
decompress_rle = mo_pack.decompress_rle
- elif pp_packing is not None:
- msg = 'iris.fileformats.pp_packing has been ' \
- 'deprecated and will be removed in a future release. ' \
- 'Install/upgrade mo_pack to make use of the new unpacking ' \
- 'functionality.'
- warn_deprecated(msg)
- decompress_rle = pp_packing.rle_decode
else:
msg = 'Unpacking PP fields with LBPACK of {} ' \
'requires mo_pack to be installed'.format(lbpack.n1)
diff --git a/lib/iris/fileformats/pp_packing.py b/lib/iris/fileformats/pp_packing.py
deleted file mode 100644
index 2dd2d86e2f..0000000000
--- a/lib/iris/fileformats/pp_packing.py
+++ /dev/null
@@ -1,80 +0,0 @@
-# (C) British Crown Copyright 2016, Met Office
-#
-# This file is part of Iris.
-#
-# Iris is free software: you can redistribute it and/or modify it under
-# the terms of the GNU Lesser General Public License as published by the
-# Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# Iris is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with Iris. If not, see .
-"""
-This extension module provides access to the underlying libmo_unpack library
-functionality.
-
-.. deprecated:: 1.10
- :mod:`iris.fileformats.pp_packing` is deprecated.
- Please install mo_pack (https://github.com/SciTools/mo_pack) instead.
- This provides additional pack/unpacking functionality.
-
-"""
-from __future__ import (absolute_import, division, print_function)
-from six.moves import (filter, input, map, range, zip) # noqa
-import six
-
-from iris._deprecation import warn_deprecated
-from iris.fileformats import _old_pp_packing as old_pp_packing
-
-
-_DEPRECATION_DOCSTRING_SUFFIX = """
-.. deprecated:: 1.10
- :mod:`iris.fileformats.pp_packing` is deprecated.
- Please install mo_pack (https://github.com/SciTools/mo_pack) instead.
- This provides additional pack/unpacking functionality.
-
-"""
-
-_DEPRECATION_WARNING = (
- 'Module "iris.fileformats.pp_packing" is deprecated. '
- 'Please install mo_pack (https://github.com/SciTools/mo_pack) instead. '
- 'This provides additional pack/unpacking functionality.')
-
-
-# Emit a deprecation warning when anyone tries to import this.
-# For quiet, can still use _old_pp_packing instead, as fileformats.pp does.
-warn_deprecated(_DEPRECATION_WARNING)
-
-
-# Define simple wrappers for functions in pp_packing.
-# N.B. signatures must match the originals !
-def wgdos_unpack(data, lbrow, lbnpt, bmdi):
- warn_deprecated(_DEPRECATION_WARNING)
- return old_pp_packing.wgdos_unpack(data, lbrow, lbnpt, bmdi)
-
-
-def rle_decode(data, lbrow, lbnpt, bmdi):
- warn_deprecated(_DEPRECATION_WARNING)
- return old_pp_packing.rle_decode(data, lbrow, lbnpt, bmdi)
-
-
-def _add_fixed_up_docstring(new_fn, original_fn):
- # Add docstring to a wrapper function, based on the original function.
- # This would be simpler if Sphinx were less fussy about formatting.
- docstring = original_fn.__doc__
- lines = [line for line in docstring.split('\n')]
- # Strip off last blank lines, and add deprecation notice.
- while len(lines[-1].strip()) == 0:
- lines = lines[:-1]
- docstring = '\n'.join(lines)
- docstring += _DEPRECATION_DOCSTRING_SUFFIX
- new_fn.__doc__ = docstring
-
-
-_add_fixed_up_docstring(wgdos_unpack, old_pp_packing.wgdos_unpack)
-_add_fixed_up_docstring(rle_decode, old_pp_packing.rle_decode)
diff --git a/lib/iris/proxy.py b/lib/iris/proxy.py
deleted file mode 100644
index 506995369c..0000000000
--- a/lib/iris/proxy.py
+++ /dev/null
@@ -1,69 +0,0 @@
-# (C) British Crown Copyright 2010 - 2016, Met Office
-#
-# This file is part of Iris.
-#
-# Iris is free software: you can redistribute it and/or modify it under
-# the terms of the GNU Lesser General Public License as published by the
-# Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# Iris is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with Iris. If not, see .
-"""
-.. deprecated:: 1.9
- This module has been deprecated. Please use lazy imports instead.
-
-Provision of a service to handle missing packages at runtime.
-Current just a very thin layer but gives the option to extend
-handling as much as needed
-
-"""
-
-from __future__ import (absolute_import, division, print_function)
-from six.moves import (filter, input, map, range, zip) # noqa
-
-import sys
-
-from iris._deprecation import warn_deprecated
-
-
-warn_deprecated('iris.proxy is deprecated in Iris v1.9. Please use lazy '
- 'imports instead.')
-
-
-class FakeModule(object):
- __slots__ = ('_name',)
-
- def __init__(self, name):
- self._name = name
-
- def __setattr__(self, name, value):
- object.__setattr__(self, name, value)
-
- def __getattr__(self, name):
- raise AttributeError(
- 'Module "{}" not available or not installed'.format(self._name))
-
-
-def apply_proxy(module_name, dic):
- """
- Attempt the import else use the proxy module.
- It is important to note that '__import__()' must be used
- instead of the higher-level 'import' as we need to
- ensure the scope of the import can be propagated out of this package.
- Also, note the splitting of name - this is because '__import__()'
- requires full package path, unlike 'import' (this issue is
- explicitly seen in lib/iris/fileformats/pp.py importing pp_packing)
-
- """
- name = module_name.split('.')[-1]
- try:
- __import__(module_name)
- dic[name] = sys.modules[module_name]
- except ImportError:
- dic[name] = sys.modules[name] = FakeModule(name)
diff --git a/lib/iris/tests/experimental/regrid/test_regrid_conservative_via_esmpy.py b/lib/iris/tests/experimental/regrid/test_regrid_conservative_via_esmpy.py
index 4f4d8e5b6e..1e1de2ba9f 100644
--- a/lib/iris/tests/experimental/regrid/test_regrid_conservative_via_esmpy.py
+++ b/lib/iris/tests/experimental/regrid/test_regrid_conservative_via_esmpy.py
@@ -36,8 +36,6 @@
# Import ESMF if installed, else fail quietly + disable all the tests.
try:
import ESMF
- # Check it *is* the real module, and not an iris.proxy FakeModule.
- ESMF.Manager
except ImportError as AttributeError:
ESMF = None
skip_esmf = unittest.skipIf(
diff --git a/lib/iris/tests/experimental/ugrid/test_ugrid.py b/lib/iris/tests/experimental/ugrid/test_ugrid.py
index 30356a501d..617f826bd2 100644
--- a/lib/iris/tests/experimental/ugrid/test_ugrid.py
+++ b/lib/iris/tests/experimental/ugrid/test_ugrid.py
@@ -1,4 +1,4 @@
-# (C) British Crown Copyright 2014 - 2015, Met Office
+# (C) British Crown Copyright 2014 - 2017, Met Office
#
# This file is part of Iris.
#
@@ -29,8 +29,6 @@
# Import pyugrid if installed, else fail quietly + disable all the tests.
try:
import pyugrid
- # Check it *is* the real module, and not an iris.proxy FakeModule.
- pyugrid.ugrid
except (ImportError, AttributeError):
pyugrid = None
skip_pyugrid = unittest.skipIf(
diff --git a/lib/iris/tests/unit/unit/__init__.py b/lib/iris/tests/unit/unit/__init__.py
deleted file mode 100644
index 8e119a957f..0000000000
--- a/lib/iris/tests/unit/unit/__init__.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# (C) British Crown Copyright 2014 - 2015, Met Office
-#
-# This file is part of Iris.
-#
-# Iris is free software: you can redistribute it and/or modify it under
-# the terms of the GNU Lesser General Public License as published by the
-# Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# Iris is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with Iris. If not, see .
-"""Unit tests for the :mod:`cf_units` module."""
-
-from __future__ import (absolute_import, division, print_function)
-from six.moves import (filter, input, map, range, zip) # noqa
diff --git a/lib/iris/tests/unit/unit/test_Unit.py b/lib/iris/tests/unit/unit/test_Unit.py
deleted file mode 100644
index dfc7b6ffda..0000000000
--- a/lib/iris/tests/unit/unit/test_Unit.py
+++ /dev/null
@@ -1,223 +0,0 @@
-# (C) British Crown Copyright 2014 - 2015, Met Office
-#
-# This file is part of Iris.
-#
-# Iris is free software: you can redistribute it and/or modify it under
-# the terms of the GNU Lesser General Public License as published by the
-# Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# Iris is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with Iris. If not, see .
-"""Unit tests for the `cf_units.Unit` class."""
-
-from __future__ import (absolute_import, division, print_function)
-from six.moves import (filter, input, map, range, zip) # noqa
-
-# import iris tests first so that some things can be initialised before
-# importing anything else
-import iris.tests as tests
-
-import numpy as np
-
-import cf_units
-from cf_units import Unit
-
-
-class Test___init__(tests.IrisTest):
-
- def test_capitalised_calendar(self):
- calendar = 'GrEgoRian'
- expected = cf_units.CALENDAR_GREGORIAN
- u = Unit('hours since 1970-01-01 00:00:00', calendar=calendar)
- self.assertEqual(u.calendar, expected)
-
- def test_not_basestring_calendar(self):
- with self.assertRaises(TypeError):
- u = Unit('hours since 1970-01-01 00:00:00', calendar=5)
-
-
-class Test_convert__calendar(tests.IrisTest):
-
- class MyStr(str):
- pass
-
- def test_gregorian_calendar_conversion_setup(self):
- # Reproduces a situation where a unit's gregorian calendar would not
- # match (using the `is` operator) to the literal string 'gregorian',
- # causing an `is not` test to return a false negative.
- cal_str = cf_units.CALENDAR_GREGORIAN
- calendar = self.MyStr(cal_str)
- self.assertIsNot(calendar, cal_str)
- u1 = Unit('hours since 1970-01-01 00:00:00', calendar=calendar)
- u2 = Unit('hours since 1969-11-30 00:00:00', calendar=calendar)
- u1point = np.array([8.], dtype=np.float32)
- expected = np.array([776.], dtype=np.float32)
- result = u1.convert(u1point, u2)
- return expected, result
-
- def test_gregorian_calendar_conversion_array(self):
- expected, result = self.test_gregorian_calendar_conversion_setup()
- self.assertArrayEqual(expected, result)
-
- def test_gregorian_calendar_conversion_dtype(self):
- expected, result = self.test_gregorian_calendar_conversion_setup()
- self.assertEqual(expected.dtype, result.dtype)
-
- def test_gregorian_calendar_conversion_shape(self):
- expected, result = self.test_gregorian_calendar_conversion_setup()
- self.assertEqual(expected.shape, result.shape)
-
- def test_non_gregorian_calendar_conversion_dtype(self):
- data = np.arange(4, dtype=np.float32)
- u1 = Unit('hours since 2000-01-01 00:00:00', calendar='360_day')
- u2 = Unit('hours since 2000-01-02 00:00:00', calendar='360_day')
- result = u1.convert(data, u2)
- self.assertEqual(result.dtype, np.float32)
-
-
-class Test_convert__endianness_time(tests.IrisTest):
- # Test the behaviour of converting time units of differing
- # dtype endianness.
-
- def setUp(self):
- self.time1_array = np.array([31.5, 32.5, 33.5])
- self.time2_array = np.array([0.5, 1.5, 2.5])
- self.time1_unit = cf_units.Unit('days since 1970-01-01 00:00:00',
- calendar=cf_units.CALENDAR_STANDARD)
- self.time2_unit = cf_units.Unit('days since 1970-02-01 00:00:00',
- calendar=cf_units.CALENDAR_STANDARD)
-
- def test_no_endian(self):
- dtype = 'f8'
- result = self.time1_unit.convert(self.time1_array.astype(dtype),
- self.time2_unit)
- self.assertArrayAlmostEqual(result, self.time2_array)
-
- def test_little_endian(self):
- dtype = '.
-"""
-.. deprecated:: 1.9
- This module has been deprecated. Please use `cf_units
- `_ instead.
-
-Units of measure.
-
-Provision of a wrapper class to support Unidata/UCAR UDUNITS-2, and the
-netcdftime calendar functionality.
-
-See also: `UDUNITS-2
-`_.
-
-"""
-
-from __future__ import (absolute_import, division, print_function)
-from six.moves import (filter, input, map, range, zip) # noqa
-import six
-
-from contextlib import contextmanager
-import copy
-import ctypes
-import ctypes.util
-import os.path
-import sys
-import warnings
-
-import netcdftime
-import numpy as np
-
-from iris._deprecation import warn_deprecated
-import iris.config
-import iris.util
-
-
-warn_deprecated('iris.unit is deprecated in Iris v1.9. Please use cf_units '
- '(https://github.com/SciTools/cf_units) instead.')
-
-
-__all__ = ['Unit', 'date2num', 'decode_time', 'encode_clock', 'encode_date',
- 'encode_time', 'num2date']
-
-
-########################################################################
-#
-# module level constants
-#
-########################################################################
-
-#
-# default constants
-#
-IRIS_EPOCH = '1970-01-01 00:00:00'
-_STRING_BUFFER_DEPTH = 128
-_UNKNOWN_UNIT_STRING = 'unknown'
-_UNKNOWN_UNIT_SYMBOL = '?'
-_UNKNOWN_UNIT = [_UNKNOWN_UNIT_STRING, _UNKNOWN_UNIT_SYMBOL, '???', '']
-_NO_UNIT_STRING = 'no_unit'
-_NO_UNIT_SYMBOL = '-'
-_NO_UNIT = [_NO_UNIT_STRING, _NO_UNIT_SYMBOL, 'no unit', 'no-unit', 'nounit']
-_UNIT_DIMENSIONLESS = '1'
-_OP_SINCE = ' since '
-_CATEGORY_UNKNOWN, _CATEGORY_NO_UNIT, _CATEGORY_UDUNIT = range(3)
-
-
-#
-# libudunits2 constants
-#
-# ut_status enumerations
-_UT_STATUS = ['UT_SUCCESS', 'UT_BAD_ARG', 'UT_EXISTS', 'UT_NO_UNIT',
- 'UT_OS', 'UT_NOT_SAME_NAME', 'UT_MEANINGLESS', 'UT_NO_SECOND',
- 'UT_VISIT_ERROR', 'UT_CANT_FORMAT', 'UT_SYNTAX', 'UT_UNKNOWN',
- 'UT_OPEN_ARG', 'UT_OPEN_ENV', 'UT_OPEN_DEFAULT', 'UT_PARSE']
-
-# explicit function names
-_UT_HANDLER = 'ut_set_error_message_handler'
-_UT_IGNORE = 'ut_ignore'
-
-# ut_encoding enumerations
-UT_ASCII = 0
-UT_ISO_8859_1 = 1
-UT_LATIN1 = 1
-UT_UTF8 = 2
-UT_NAMES = 4
-UT_DEFINITION = 8
-
-UT_FORMATS = [UT_ASCII, UT_ISO_8859_1, UT_LATIN1, UT_UTF8, UT_NAMES,
- UT_DEFINITION]
-
-#
-# netcdftime constants
-#
-CALENDAR_STANDARD = 'standard'
-CALENDAR_GREGORIAN = 'gregorian'
-CALENDAR_PROLEPTIC_GREGORIAN = 'proleptic_gregorian'
-CALENDAR_NO_LEAP = 'noleap'
-CALENDAR_JULIAN = 'julian'
-CALENDAR_ALL_LEAP = 'all_leap'
-CALENDAR_365_DAY = '365_day'
-CALENDAR_366_DAY = '366_day'
-CALENDAR_360_DAY = '360_day'
-
-CALENDARS = [CALENDAR_STANDARD, CALENDAR_GREGORIAN,
- CALENDAR_PROLEPTIC_GREGORIAN, CALENDAR_NO_LEAP, CALENDAR_JULIAN,
- CALENDAR_ALL_LEAP, CALENDAR_365_DAY, CALENDAR_366_DAY,
- CALENDAR_360_DAY]
-
-#
-# ctypes types
-#
-FLOAT32 = ctypes.c_float
-FLOAT64 = ctypes.c_double
-
-########################################################################
-#
-# module level variables
-#
-########################################################################
-
-# cache for ctypes foreign shared library handles
-_lib_c = None
-_lib_ud = None
-_ud_system = None
-
-# cache for libc shared library functions
-_strerror = None
-
-# class cache for libudunits2 shared library functions
-_cv_convert_float = None
-_cv_convert_floats = None
-_cv_convert_double = None
-_cv_convert_doubles = None
-_cv_free = None
-_ut_are_convertible = None
-_ut_clone = None
-_ut_compare = None
-_ut_decode_time = None
-_ut_divide = None
-_ut_encode_clock = None
-_ut_encode_date = None
-_ut_encode_time = None
-_ut_format = None
-_ut_free = None
-_ut_get_converter = None
-_ut_get_status = None
-_ut_get_unit_by_name = None
-_ut_ignore = None
-_ut_invert = None
-_ut_is_dimensionless = None
-_ut_log = None
-_ut_multiply = None
-_ut_offset = None
-_ut_offset_by_time = None
-_ut_parse = None
-_ut_raise = None
-_ut_read_xml = None
-_ut_root = None
-_ut_scale = None
-_ut_set_error_message_handler = None
-
-########################################################################
-#
-# module level statements
-#
-########################################################################
-
-#
-# load the libc shared library
-#
-if _lib_c is None:
- if sys.platform == 'win32':
- _lib_c = ctypes.cdll.msvcrt
- else:
- _lib_c = ctypes.CDLL(ctypes.util.find_library('libc'))
-
- #
- # cache common shared library functions
- #
- _strerror = _lib_c.strerror
- _strerror.restype = ctypes.c_char_p
-
-#
-# load the libudunits2 shared library
-#
-if _lib_ud is None:
- _lib_ud = iris.config.get_option(
- 'System', 'udunits2_path',
- default=ctypes.util.find_library('udunits2'))
- _lib_ud = ctypes.CDLL(_lib_ud, use_errno=True)
-
- #
- # cache common shared library functions
- #
- _cv_convert_float = _lib_ud.cv_convert_float
- _cv_convert_float.argtypes = [ctypes.c_void_p, ctypes.c_float]
- _cv_convert_float.restype = ctypes.c_float
-
- _cv_convert_floats = _lib_ud.cv_convert_floats
- _cv_convert_floats.argtypes = [ctypes.c_void_p, ctypes.c_void_p,
- ctypes.c_ulong, ctypes.c_void_p]
- _cv_convert_floats.restype = ctypes.c_void_p
-
- _cv_convert_double = _lib_ud.cv_convert_double
- _cv_convert_double.argtypes = [ctypes.c_void_p, ctypes.c_double]
- _cv_convert_double.restype = ctypes.c_double
-
- _cv_convert_doubles = _lib_ud.cv_convert_doubles
- _cv_convert_doubles.argtypes = [ctypes.c_void_p, ctypes.c_void_p,
- ctypes.c_ulong, ctypes.c_void_p]
- _cv_convert_doubles.restype = ctypes.c_void_p
-
- _cv_free = _lib_ud.cv_free
- _cv_free.argtypes = [ctypes.c_void_p]
-
- _ut_are_convertible = _lib_ud.ut_are_convertible
- _ut_are_convertible.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
-
- _ut_clone = _lib_ud.ut_clone
- _ut_clone.argtypes = [ctypes.c_void_p]
- _ut_clone.restype = ctypes.c_void_p
-
- _ut_compare = _lib_ud.ut_compare
- _ut_compare.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
- _ut_compare.restype = ctypes.c_int
-
- _ut_decode_time = _lib_ud.ut_decode_time
- _ut_decode_time.restype = None
-
- _ut_divide = _lib_ud.ut_divide
- _ut_divide.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
- _ut_divide.restype = ctypes.c_void_p
-
- _ut_encode_clock = _lib_ud.ut_encode_clock
- _ut_encode_clock.restype = ctypes.c_double
-
- _ut_encode_date = _lib_ud.ut_encode_date
- _ut_encode_date.restype = ctypes.c_double
-
- _ut_encode_time = _lib_ud.ut_encode_time
- _ut_encode_time.restype = ctypes.c_double
-
- _ut_format = _lib_ud.ut_format
- _ut_format.argtypes = [ctypes.c_void_p, ctypes.c_char_p,
- ctypes.c_ulong, ctypes.c_uint]
-
- _ut_free = _lib_ud.ut_free
- _ut_free.argtypes = [ctypes.c_void_p]
- _ut_free.restype = None
-
- _ut_get_converter = _lib_ud.ut_get_converter
- _ut_get_converter.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
- _ut_get_converter.restype = ctypes.c_void_p
-
- _ut_get_status = _lib_ud.ut_get_status
-
- _ut_get_unit_by_name = _lib_ud.ut_get_unit_by_name
- _ut_get_unit_by_name.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
- _ut_get_unit_by_name.restype = ctypes.c_void_p
-
- _ut_invert = _lib_ud.ut_invert
- _ut_invert.argtypes = [ctypes.c_void_p]
- _ut_invert.restype = ctypes.c_void_p
-
- _ut_is_dimensionless = _lib_ud.ut_is_dimensionless
- _ut_is_dimensionless.argtypes = [ctypes.c_void_p]
-
- _ut_log = _lib_ud.ut_log
- _ut_log.argtypes = [ctypes.c_double, ctypes.c_void_p]
- _ut_log.restype = ctypes.c_void_p
-
- _ut_multiply = _lib_ud.ut_multiply
- _ut_multiply.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
- _ut_multiply.restype = ctypes.c_void_p
-
- _ut_offset = _lib_ud.ut_offset
- _ut_offset.argtypes = [ctypes.c_void_p, ctypes.c_double]
- _ut_offset.restype = ctypes.c_void_p
-
- _ut_offset_by_time = _lib_ud.ut_offset_by_time
- _ut_offset_by_time.argtypes = [ctypes.c_void_p, ctypes.c_double]
- _ut_offset_by_time.restype = ctypes.c_void_p
-
- _ut_parse = _lib_ud.ut_parse
- _ut_parse.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int]
- _ut_parse.restype = ctypes.c_void_p
-
- _ut_raise = _lib_ud.ut_raise
- _ut_raise.argtypes = [ctypes.c_void_p, ctypes.c_int]
- _ut_raise.restype = ctypes.c_void_p
-
- _ut_read_xml = _lib_ud.ut_read_xml
- _ut_read_xml.argtypes = [ctypes.c_char_p]
- _ut_read_xml.restype = ctypes.c_void_p
-
- _ut_root = _lib_ud.ut_root
- _ut_root.argtypes = [ctypes.c_void_p, ctypes.c_int]
- _ut_root.restype = ctypes.c_void_p
-
- _ut_scale = _lib_ud.ut_scale
- _ut_scale.argtypes = [ctypes.c_double, ctypes.c_void_p]
- _ut_scale.restype = ctypes.c_void_p
-
- # convenience dictionary for the Unit convert method
- _cv_convert_scalar = {FLOAT32: _cv_convert_float,
- FLOAT64: _cv_convert_double}
- _cv_convert_array = {FLOAT32: _cv_convert_floats,
- FLOAT64: _cv_convert_doubles}
- _numpy2ctypes = {np.float32: FLOAT32, np.float64: FLOAT64}
- _ctypes2numpy = {v: k for k, v in _numpy2ctypes.items()}
-#
-# load the UDUNITS-2 xml-formatted unit-database
-#
-if not _ud_system:
- _func_type = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_char_p,
- use_errno=True)
- _set_handler_type = ctypes.CFUNCTYPE(_func_type, _func_type)
- _ut_set_error_message_handler = _set_handler_type((_UT_HANDLER, _lib_ud))
- _ut_ignore = _func_type((_UT_IGNORE, _lib_ud))
- # ignore standard UDUNITS-2 start-up preamble redirected to stderr stream
- _default_handler = _ut_set_error_message_handler(_ut_ignore)
- # Load the unit-database from the default location (modified via
- # the UDUNITS2_XML_PATH environment variable) and if that fails look
- # relative to sys.prefix to support environments such as conda.
- _ud_system = _ut_read_xml(None)
- if _ud_system is None:
- _alt_xml_path = os.path.join(sys.prefix, 'share',
- 'udunits', 'udunits2.xml')
- _ud_system = _ut_read_xml(_alt_xml_path.encode())
- # reinstate old error handler
- _ut_set_error_message_handler(_default_handler)
- del _func_type
- if not _ud_system:
- _status_msg = 'UNKNOWN'
- _error_msg = ''
- _status = _ut_get_status()
- try:
- _status_msg = _UT_STATUS[_status]
- except IndexError:
- pass
- _errno = ctypes.get_errno()
- if _errno != 0:
- _error_msg = ': "%s"' % _strerror(_errno)
- ctypes.set_errno(0)
- raise OSError('[%s] Failed to open UDUNITS-2 XML unit database %s' % (
- _status_msg, _error_msg))
-
-
-########################################################################
-#
-# module level function definitions
-#
-########################################################################
-
-def encode_time(year, month, day, hour, minute, second):
- """
- Return date/clock time encoded as a double precision value.
-
- Encoding performed using UDUNITS-2 hybrid Gregorian/Julian calendar.
- Dates on or after 1582-10-15 are assumed to be Gregorian dates;
- dates before that are assumed to be Julian dates. In particular, the
- year 1 BCE is immediately followed by the year 1 CE.
-
- Args:
-
- * year (int):
- Year value to be encoded.
- * month (int):
- Month value to be encoded.
- * day (int):
- Day value to be encoded.
- * hour (int):
- Hour value to be encoded.
- * minute (int):
- Minute value to be encoded.
- * second (int):
- Second value to be encoded.
-
- Returns:
- float.
-
- For example:
-
- >>> import cf_units as unit
- >>> unit.encode_time(1970, 1, 1, 0, 0, 0)
- -978307200.0
-
- """
-
- return _ut_encode_time(ctypes.c_int(year), ctypes.c_int(month),
- ctypes.c_int(day), ctypes.c_int(hour),
- ctypes.c_int(minute), ctypes.c_double(second))
-
-
-def encode_date(year, month, day):
- """
- Return date encoded as a double precision value.
-
- Encoding performed using UDUNITS-2 hybrid Gergorian/Julian calendar.
- Dates on or after 1582-10-15 are assumed to be Gregorian dates;
- dates before that are assumed to be Julian dates. In particular, the
- year 1 BCE is immediately followed by the year 1 CE.
-
- Args:
-
- * year (int):
- Year value to be encoded.
- * month (int):
- Month value to be encoded.
- * day (int):
- Day value to be encoded.
-
- Returns:
- float.
-
- For example:
-
- >>> import cf_units as unit
- >>> unit.encode_date(1970, 1, 1)
- -978307200.0
-
- """
-
- return _ut_encode_date(ctypes.c_int(year), ctypes.c_int(month),
- ctypes.c_int(day))
-
-
-def encode_clock(hour, minute, second):
- """
- Return clock time encoded as a double precision value.
-
- Args:
-
- * hour (int):
- Hour value to be encoded.
- * minute (int):
- Minute value to be encoded.
- * second (int):
- Second value to be encoded.
-
- Returns:
- float.
-
- For example:
-
- >>> import cf_units as unit
- >>> unit.encode_clock(0, 0, 0)
- 0.0
-
- """
-
- return _ut_encode_clock(ctypes.c_int(hour), ctypes.c_int(minute),
- ctypes.c_double(second))
-
-
-def decode_time(time):
- """
- Decode a double precision date/clock time value into its component
- parts and return as tuple.
-
- Decode time into it's year, month, day, hour, minute, second, and
- resolution component parts. Where resolution is the uncertainty of
- the time in seconds.
-
- Args:
-
- * time (float): Date/clock time encoded as a double precision value.
-
- Returns:
- tuple of (year, month, day, hour, minute, second, resolution).
-
- For example:
-
- >>> import cf_units as unit
- >>> unit.decode_time(unit.encode_time(1970, 1, 1, 0, 0, 0))
- (1970, 1, 1, 0, 0, 0.0, 1.086139178596568e-07)
-
- """
-
- year = ctypes.c_int()
- month = ctypes.c_int()
- day = ctypes.c_int()
- hour = ctypes.c_int()
- minute = ctypes.c_int()
- second = ctypes.c_double()
- resolution = ctypes.c_double()
- _ut_decode_time(ctypes.c_double(time), ctypes.pointer(year),
- ctypes.pointer(month), ctypes.pointer(day),
- ctypes.pointer(hour), ctypes.pointer(minute),
- ctypes.pointer(second), ctypes.pointer(resolution))
- return (year.value, month.value, day.value, hour.value, minute.value,
- second.value, resolution.value)
-
-
-def julian_day2date(julian_day, calendar):
- """
- Return a netcdftime datetime-like object representing the Julian day.
-
- If calendar is 'standard' or 'gregorian', Julian day follows
- Julian calendar on and before 1582-10-5, Gregorian calendar after
- 1582-10-15.
- If calendar is 'proleptic_gregorian', Julian Day follows Gregorian
- calendar.
- If calendar is 'julian', Julian Day follows Julian calendar.
-
- The datetime object is a 'real' datetime object if the date falls in
- the Gregorian calendar (i.e. calendar is 'proleptic_gregorian', or
- calendar is 'standard'/'gregorian' and the date is after 1582-10-15).
- Otherwise, it's a 'phony' datetime object which is actually an instance
- of netcdftime.datetime.
-
- Algorithm:
- Meeus, Jean (1998) Astronomical Algorithms (2nd Edition).
- Willmann-Bell, Virginia. p. 63.
-
- Args:
-
- * julian_day (float):
- Julian day with a resolution of 1 second.
- * calendar (string):
- Name of the calendar, see cf_units.CALENDARS.
-
- Returns:
- datetime or netcdftime.datetime.
-
- For example:
-
- >>> import cf_units as unit
- >>> import datetime
- >>> unit.julian_day2date(
- ... unit.date2julian_day(datetime.datetime(1970, 1, 1, 0, 0, 0),
- ... unit.CALENDAR_STANDARD),
- ... unit.CALENDAR_STANDARD)
- datetime.datetime(1970, 1, 1, 0, 0)
-
- """
-
- return netcdftime.DateFromJulianDay(julian_day, calendar)
-
-
-def date2julian_day(date, calendar):
- """
- Return the Julian day (resolution of 1 second) from a netcdftime
- datetime-like object.
-
- If calendar is 'standard' or 'gregorian', Julian day follows Julian
- calendar on and before 1582-10-5, Gregorian calendar after 1582-10-15.
- If calendar is 'proleptic_gregorian', Julian day follows Gregorian
- calendar.
- If calendar is 'julian', Julian day follows Julian calendar.
-
- Algorithm:
- Meeus, Jean (1998) Astronomical Algorithms (2nd Edition).
- Willmann-Bell, Virginia. p. 63.
-
- Args:
-
- * date (netcdftime.date):
- Date and time representation.
- * calendar (string):
- Name of the calendar, see cf_units.CALENDARS.
-
- Returns:
- float.
-
- For example:
-
- >>> import cf_units as unit
- >>> import datetime
- >>> unit.date2julian_day(datetime.datetime(1970, 1, 1, 0, 0, 0),
- ... unit.CALENDAR_STANDARD)
- 2440587.5
-
- """
-
- return netcdftime.JulianDayFromDate(date, calendar)
-
-
-def date2num(date, unit, calendar):
- """
- Return numeric time value (resolution of 1 second) encoding of
- datetime object.
-
- The units of the numeric time values are described by the unit and
- calendar arguments. The datetime objects must be in UTC with no
- time-zone offset. If there is a time-zone offset in unit, it will be
- applied to the returned numeric values.
-
- Like the :func:`matplotlib.dates.date2num` function, except that it allows
- for different units and calendars. Behaves the same as if
- unit = 'days since 0001-01-01 00:00:00' and
- calendar = 'proleptic_gregorian'.
-
- Args:
-
- * date (datetime):
- A datetime object or a sequence of datetime objects.
- The datetime objects should not include a time-zone offset.
- * unit (string):
- A string of the form ' since ' describing
- the time units. The can be days, hours, minutes or seconds.
- The is a date/time reference point. A valid choice
- would be unit='hours since 1800-01-01 00:00:00 -6:00'.
- * calendar (string):
- Name of the calendar, see cf_units.CALENDARS.
-
- Returns:
- float, or numpy.ndarray of float.
-
- For example:
-
- >>> import cf_units as unit
- >>> import datetime
- >>> dt1 = datetime.datetime(1970, 1, 1, 6, 0, 0)
- >>> dt2 = datetime.datetime(1970, 1, 1, 7, 0, 0)
- >>> unit.date2num(dt1, 'hours since 1970-01-01 00:00:00',
- ... unit.CALENDAR_STANDARD)
- 6.0
- >>> unit.date2num([dt1, dt2], 'hours since 1970-01-01 00:00:00',
- ... unit.CALENDAR_STANDARD)
- array([ 6., 7.])
-
- """
-
- #
- # ensure to strip out any 'UTC' postfix which is generated by
- # UDUNITS-2 formatted output and causes the netcdftime parser
- # to choke
- #
- unit_string = unit.rstrip(" UTC")
- if unit_string.endswith(" since epoch"):
- unit_string = unit_string.replace("epoch", IRIS_EPOCH)
- cdftime = netcdftime.utime(unit_string, calendar=calendar)
- return cdftime.date2num(date)
-
-
-def num2date(time_value, unit, calendar):
- """
- Return datetime encoding of numeric time value (resolution of 1 second).
-
- The units of the numeric time value are described by the unit and
- calendar arguments. The returned datetime object represent UTC with
- no time-zone offset, even if the specified unit contain a time-zone
- offset.
-
- Like the :func:`matplotlib.dates.num2date` function, except that it allows
- for different units and calendars. Behaves the same if
- unit = 'days since 001-01-01 00:00:00'}
- calendar = 'proleptic_gregorian'.
-
- The datetime instances returned are 'real' python datetime
- objects if the date falls in the Gregorian calendar (i.e.
- calendar='proleptic_gregorian', or calendar = 'standard' or 'gregorian'
- and the date is after 1582-10-15). Otherwise, they are 'phony' datetime
- objects which support some but not all the methods of 'real' python
- datetime objects. This is because the python datetime module cannot
- use the 'proleptic_gregorian' calendar, even before the switch
- occured from the Julian calendar in 1582. The datetime instances
- do not contain a time-zone offset, even if the specified unit
- contains one.
-
- Args:
-
- * time_value (float):
- Numeric time value/s. Maximum resolution is 1 second.
- * unit (sting):
- A string of the form ' since '
- describing the time units. The can be days, hours,
- minutes or seconds. The is the date/time reference
- point. A valid choice would be
- unit='hours since 1800-01-01 00:00:00 -6:00'.
- * calendar (string):
- Name of the calendar, see cf_units.CALENDARS.
-
- Returns:
- datetime, or numpy.ndarray of datetime object.
-
- For example:
-
- >>> import cf_units as unit
- >>> import datetime
- >>> unit.num2date(6, 'hours since 1970-01-01 00:00:00',
- ... unit.CALENDAR_STANDARD)
- datetime.datetime(1970, 1, 1, 6, 0)
- >>> unit.num2date([6, 7], 'hours since 1970-01-01 00:00:00',
- ... unit.CALENDAR_STANDARD)
- array([datetime.datetime(1970, 1, 1, 6, 0),
- datetime.datetime(1970, 1, 1, 7, 0)], dtype=object)
-
- """
-
- #
- # ensure to strip out any 'UTC' postfix which is generated by
- # UDUNITS-2 formatted output and causes the netcdftime parser
- # to choke
- #
- unit_string = unit.rstrip(" UTC")
- if unit_string.endswith(" since epoch"):
- unit_string = unit_string.replace("epoch", IRIS_EPOCH)
- cdftime = netcdftime.utime(unit_string, calendar=calendar)
- return cdftime.num2date(time_value)
-
-
-def _handler(func):
- """Set the error message handler."""
-
- _ut_set_error_message_handler(func)
-
-
-@contextmanager
-def suppress_unit_warnings():
- """
- Suppresses all warnings raised because of invalid units in loaded data.
-
- """
- # Suppress any warning messages raised by UDUNITS2.
- _func_type = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_char_p,
- use_errno=True)
- _set_handler_type = ctypes.CFUNCTYPE(_func_type, _func_type)
- _ut_set_error_message_handler = _set_handler_type((_UT_HANDLER, _lib_ud))
- _ut_ignore = _func_type((_UT_IGNORE, _lib_ud))
- _default_handler = _ut_set_error_message_handler(_ut_ignore)
- with warnings.catch_warnings():
- # Also suppress invalid units warnings from the Iris loader code.
- warnings.filterwarnings("ignore", message=".*invalid units")
- yield
- _ut_set_error_message_handler(_default_handler)
-
-
-########################################################################
-#
-# unit wrapper class for unidata/ucar UDUNITS-2
-#
-########################################################################
-
-def _Unit(category, ut_unit, calendar=None, origin=None):
- unit = iris.util._OrderedHashable.__new__(Unit)
- unit._init(category, ut_unit, calendar, origin)
- return unit
-
-
-_CACHE = {}
-
-
-def as_unit(unit):
- """
- Returns a Unit corresponding to the given unit.
-
- .. note::
-
- If the given unit is already a Unit it will be returned unchanged.
-
- """
- if isinstance(unit, Unit):
- result = unit
- else:
- result = None
- use_cache = isinstance(unit, six.string_types) or unit is None
- if use_cache:
- result = _CACHE.get(unit)
- if result is None:
- result = Unit(unit)
- if use_cache:
- _CACHE[unit] = result
- return result
-
-
-def is_time(unit):
- """
- Determine whether the unit is a related SI Unit of time.
-
- Args:
-
- * unit (string/Unit): Unit to be compared.
-
- Returns:
- Boolean.
-
- For example:
-
- >>> import cf_units as unit
- >>> unit.is_time('hours')
- True
- >>> unit.is_time('meters')
- False
-
- """
- return as_unit(unit).is_time()
-
-
-def is_vertical(unit):
- """
- Determine whether the unit is a related SI Unit of pressure or distance.
-
- Args:
-
- * unit (string/Unit): Unit to be compared.
-
- Returns:
- Boolean.
-
- For example:
-
- >>> import cf_units as unit
- >>> unit.is_vertical('millibar')
- True
- >>> unit.is_vertical('km')
- True
-
- """
- return as_unit(unit).is_vertical()
-
-
-class Unit(iris.util._OrderedHashable):
- """
- A class to represent S.I. units and support common operations to
- manipulate such units in a consistent manner as per UDUNITS-2.
-
- These operations include scaling the unit, offsetting the unit by a
- constant or time, inverting the unit, raising the unit by a power,
- taking a root of the unit, taking a log of the unit, multiplying the
- unit by a constant or another unit, dividing the unit by a constant
- or another unit, comparing units, copying units and converting unit
- data to single precision or double precision floating point numbers.
-
- This class also supports time and calendar defintion and manipulation.
-
- """
- # Declare the attribute names relevant to the _OrderedHashable behaviour.
- _names = ('category', 'ut_unit', 'calendar', 'origin')
-
- category = None
- 'Is this an unknown unit, a no-unit, or a UDUNITS-2 unit.'
-
- ut_unit = None
- 'Reference to the ctypes quantity defining the UDUNITS-2 unit.'
-
- calendar = None
- 'Represents the unit calendar name, see cf_units.CALENDARS'
-
- origin = None
- 'The original string used to create this unit.'
-
- __slots__ = ()
-
- def __init__(self, unit, calendar=None):
- """
- Create a wrapper instance for UDUNITS-2.
-
- An optional calendar may be provided for a unit which defines a
- time reference of the form ' since '
- i.e. unit='days since 1970-01-01 00:00:00'. For a unit that is a
- time reference, the default calendar is 'standard'.
-
- Accepted calendars are as follows,
-
- * 'standard' or 'gregorian' - Mixed Gregorian/Julian calendar as
- defined by udunits.
- * 'proleptic_gregorian' - A Gregorian calendar extended to dates
- before 1582-10-15. A year is a leap year if either,
-
- 1. It is divisible by 4 but not by 100, or
- 2. It is divisible by 400.
-
- * 'noleap' or '365_day' - A Gregorian calendar without leap
- years i.e. all years are 365 days long.
- * 'all_leap' or '366_day' - A Gregorian calendar with every year
- being a leap year i.e. all years are 366 days long.
- * '360_day' - All years are 360 days divided into 30 day months.
- * 'julian' - Proleptic Julian calendar, extended to dates after
- 1582-10-5. A year is a leap year if it is divisible by 4.
-
- Args:
-
- * unit:
- Specify the unit as defined by UDUNITS-2.
- * calendar (string):
- Describes the calendar used in time calculations. The
- default is 'standard' or 'gregorian' for a time reference
- unit.
-
- Returns:
-
- Unit object.
-
- Units should be set to "no_unit" for values which are strings.
- Units can also be set to "unknown" (or None).
- For example:
-
- >>> from cf_units import Unit
- >>> volts = Unit('volts')
- >>> no_unit = Unit('no_unit')
- >>> unknown = Unit('unknown')
- >>> unknown = Unit(None)
-
- """
- ut_unit = None
- calendar_ = None
-
- if unit is None:
- unit = ''
- else:
- unit = str(unit).strip()
-
- if unit.lower().endswith(' utc'):
- unit = unit[:unit.lower().rfind(' utc')]
-
- if unit.endswith(" since epoch"):
- unit = unit.replace("epoch", IRIS_EPOCH)
-
- if unit.lower() in _UNKNOWN_UNIT:
- # TODO - removing the option of an unknown unit. Currently
- # the auto generated MOSIG rules are missing units on a
- # number of phenomena which would lead to errors.
- # Will be addressed by work on metadata translation.
- category = _CATEGORY_UNKNOWN
- unit = _UNKNOWN_UNIT_STRING
- elif unit.lower() in _NO_UNIT:
- category = _CATEGORY_NO_UNIT
- unit = _NO_UNIT_STRING
- else:
- category = _CATEGORY_UDUNIT
- ut_unit = _ut_parse(_ud_system, unit.encode('ascii'), UT_ASCII)
- # _ut_parse returns 0 on failure
- if ut_unit is None:
- self._raise_error('Failed to parse unit "%s"' % unit)
- if _OP_SINCE in unit.lower():
- if calendar is None:
- calendar_ = CALENDAR_GREGORIAN
- elif isinstance(calendar, six.string_types):
- if calendar.lower() in CALENDARS:
- calendar_ = calendar.lower()
- else:
- msg = '{!r} is an unsupported calendar.'
- raise ValueError(msg.format(calendar))
- else:
- msg = 'Expected string-like calendar argument, got {!r}.'
- raise TypeError(msg.format(type(calendar)))
-
- self._init(category, ut_unit, calendar_, unit)
-
- def _raise_error(self, msg):
- """
- Retrieve the UDUNITS-2 ut_status, the implementation-defined string
- corresponding to UDUNITS-2 errno and raise generic exception.
-
- """
- status_msg = 'UNKNOWN'
- error_msg = ''
- if _lib_ud:
- status = _ut_get_status()
- try:
- status_msg = _UT_STATUS[status]
- except IndexError:
- pass
- errno = ctypes.get_errno()
- if errno != 0:
- error_msg = ': "%s"' % _strerror(errno)
- ctypes.set_errno(0)
-
- raise ValueError('[%s] %s %s' % (status_msg, msg, error_msg))
-
- # NOTE:
- # "__getstate__" and "__setstate__" functions are defined here to
- # provide a custom interface for Pickle
- # : Pickle "normal" behaviour is just to save/reinstate the object
- # dictionary
- # : that won't work here, because the "ut_unit" attribute is an
- # object handle
- # - the corresponding udunits object only exists in the original
- # invocation
- def __getstate__(self):
- # state capture method for Pickle.dump()
- # - return the instance data needed to reconstruct a Unit value
- return {'unit_text': self.origin, 'calendar': self.calendar}
-
- def __setstate__(self, state):
- # object reconstruction method for Pickle.load()
- # intercept the Pickle.load() operation and call own __init__ again
- # - this is to ensure a valid ut_unit attribute (as these
- # handles aren't persistent)
- self.__init__(state['unit_text'], calendar=state['calendar'])
-
- def __del__(self):
- # NB. If Python is terminating then the module global "_ut_free"
- # may have already been deleted ... so we check before using it.
- if _ut_free:
- _ut_free(self.ut_unit)
-
- def __copy__(self):
- return self
-
- def __deepcopy__(self, memo):
- return self
-
- def is_time(self):
- """
- Determine whether this unit is a related SI Unit of time.
-
- Returns:
- Boolean.
-
- For example:
-
- >>> import cf_units as unit
- >>> u = unit.Unit('hours')
- >>> u.is_time()
- True
- >>> v = unit.Unit('meter')
- >>> v.is_time()
- False
-
- """
- if self.is_unknown() or self.is_no_unit():
- result = False
- else:
- day = _ut_get_unit_by_name(_ud_system, b'day')
- result = _ut_are_convertible(self.ut_unit, day) != 0
- return result
-
- def is_vertical(self):
- """
- Determine whether the unit is a related SI Unit of pressure or
- distance.
-
- Returns:
- Boolean.
-
- For example:
-
- >>> import cf_units as unit
- >>> u = unit.Unit('millibar')
- >>> u.is_vertical()
- True
- >>> v = unit.Unit('km')
- >>> v.is_vertical()
- True
-
- """
- if self.is_unknown() or self.is_no_unit():
- result = False
- else:
- bar = _ut_get_unit_by_name(_ud_system, b'bar')
- result = _ut_are_convertible(self.ut_unit, bar) != 0
- if not result:
- meter = _ut_get_unit_by_name(_ud_system, b'meter')
- result = _ut_are_convertible(self.ut_unit, meter) != 0
- return result
-
- def is_udunits(self):
- """Return whether the unit is a vaild unit of UDUNITS."""
- return self.ut_unit is not None
-
- def is_time_reference(self):
- """
- Return whether the unit is a time reference unit of the form
- ' since '
- i.e. unit='days since 1970-01-01 00:00:00'
-
- Returns:
- Boolean.
-
- For example:
-
- >>> import cf_units as unit
- >>> u = unit.Unit('days since epoch')
- >>> u.is_time_reference()
- True
-
- """
- return self.calendar is not None
-
- def title(self, value):
- """
- Return the unit value as a title string.
-
- Args:
-
- * value (float): Unit value to be incorporated into title string.
-
- Returns:
- string.
-
- For example:
-
- >>> import cf_units as unit
- >>> u = unit.Unit('hours since epoch',
- ... calendar=unit.CALENDAR_STANDARD)
- >>> u.title(10)
- '1970-01-01 10:00:00'
-
- """
- if self.is_time_reference():
- dt = self.num2date(value)
- result = dt.strftime('%Y-%m-%d %H:%M:%S')
- else:
- result = '%s %s' % (str(value), self)
- return result
-
- @property
- def modulus(self):
- """
- *(read-only)* Return the modulus value of the unit.
-
- Convenience method that returns the unit modulus value as follows,
- * 'radians' - pi*2
- * 'degrees' - 360.0
- * Otherwise None.
-
- Returns:
- float.
-
- For example:
-
- >>> import cf_units as unit
- >>> u = unit.Unit('degrees')
- >>> u.modulus
- 360.0
-
- """
-
- if self == 'radians':
- result = np.pi * 2
- elif self == 'degrees':
- result = 360.0
- else:
- result = None
- return result
-
- def is_convertible(self, other):
- """
- Return whether two units are convertible.
-
- Args:
-
- * other (Unit): Unit to be compared.
-
- Returns:
- Boolean.
-
- For example:
-
- >>> import cf_units as unit
- >>> u = unit.Unit('meters')
- >>> v = unit.Unit('kilometers')
- >>> u.is_convertible(v)
- True
-
- """
- other = as_unit(other)
- if self.is_unknown() or self.is_no_unit() or other.is_unknown() or \
- other.is_no_unit():
- result = False
- else:
- result = (self.calendar == other.calendar and
- _ut_are_convertible(self.ut_unit, other.ut_unit) != 0)
- return result
-
- def is_dimensionless(self):
- """
- Return whether the unit is dimensionless.
-
- Returns:
- Boolean.
-
- For example:
-
- >>> import cf_units as unit
- >>> u = unit.Unit('meters')
- >>> u.is_dimensionless()
- False
- >>> u = unit.Unit('1')
- >>> u.is_dimensionless()
- True
-
- """
- return (self.category == _CATEGORY_UDUNIT and
- bool(_ut_is_dimensionless(self.ut_unit)))
-
- def is_unknown(self):
- """
- Return whether the unit is defined to be an *unknown* unit.
-
- Returns:
- Boolean.
-
- For example:
-
- >>> import cf_units as unit
- >>> u = unit.Unit('unknown')
- >>> u.is_unknown()
- True
- >>> u = unit.Unit('meters')
- >>> u.is_unknown()
- False
-
- """
- return self.category == _CATEGORY_UNKNOWN
-
- def is_no_unit(self):
- """
- Return whether the unit is defined to be a *no_unit* unit.
-
- Typically, a quantity such as a string, will have no associated
- unit to describe it. Such a class of quantity may be defined
- using the *no_unit* unit.
-
- Returns:
- Boolean.
-
- For example:
-
- >>> import cf_units as unit
- >>> u = unit.Unit('no unit')
- >>> u.is_no_unit()
- True
- >>> u = unit.Unit('meters')
- >>> u.is_no_unit()
- False
-
- """
- return self.category == _CATEGORY_NO_UNIT
-
- def format(self, option=None):
- """
- Return a formatted string representation of the binary unit.
-
- Args:
-
- * option (cf_units.UT_FORMATS):
- Set the encoding option of the formatted string representation.
- Valid encoding options may be one of the following enumerations:
-
- * Unit.UT_ASCII
- * Unit.UT_ISO_8859_1
- * Unit.UT_LATIN1
- * Unit.UT_UTF8
- * Unit.UT_NAMES
- * Unit.UT_DEFINITION
-
- Multiple options may be combined within a list. The default
- option is cf_units.UT_ASCII.
-
- Returns:
- string.
-
- For example:
-
- >>> import cf_units as unit
- >>> u = unit.Unit('meters')
- >>> u.format()
- 'm'
- >>> u.format(unit.UT_NAMES)
- 'meter'
- >>> u.format(unit.UT_DEFINITION)
- 'm'
-
- """
- if self.is_unknown():
- return _UNKNOWN_UNIT_STRING
- elif self.is_no_unit():
- return _NO_UNIT_STRING
- else:
- bitmask = UT_ASCII
- if option is not None:
- if not isinstance(option, list):
- option = [option]
- for i in option:
- bitmask |= i
- string_buffer = ctypes.create_string_buffer(_STRING_BUFFER_DEPTH)
- depth = _ut_format(self.ut_unit, string_buffer,
- ctypes.sizeof(string_buffer), bitmask)
- if depth < 0:
- self._raise_error('Failed to format %r' % self)
- return str(string_buffer.value.decode('ascii'))
-
- @property
- def name(self):
- """
- *(read-only)* The full name of the unit.
-
- Formats the binary unit into a string representation using
- method :func:`cf_units.Unit.format` with keyword argument
- option=cf_units.UT_NAMES.
-
- Returns:
- string.
-
- For example:
-
- >>> import cf_units as unit
- >>> u = unit.Unit('watts')
- >>> u.name
- 'watt'
-
- """
- return self.format(UT_NAMES)
-
- @property
- def symbol(self):
- """
- *(read-only)* The symbolic representation of the unit.
-
- Formats the binary unit into a string representation using
- method :func:`cf_units.Unit.format`.
-
- Returns:
- string.
-
- For example:
-
- >>> import cf_units as unit
- >>> u = unit.Unit('watts')
- >>> u.symbol
- 'W'
-
- """
- if self.is_unknown():
- result = _UNKNOWN_UNIT_SYMBOL
- elif self.is_no_unit():
- result = _NO_UNIT_SYMBOL
- else:
- result = self.format()
- return result
-
- @property
- def definition(self):
- """
- *(read-only)* The symbolic decomposition of the unit.
-
- Formats the binary unit into a string representation using
- method :func:`cf_units.Unit.format` with keyword argument
- option=cf_units.UT_DEFINITION.
-
- Returns:
- string.
-
- For example:
-
- >>> import cf_units as unit
- >>> u = unit.Unit('watts')
- >>> u.definition
- 'm2.kg.s-3'
-
- """
- if self.is_unknown():
- result = _UNKNOWN_UNIT_SYMBOL
- elif self.is_no_unit():
- result = _NO_UNIT_SYMBOL
- else:
- result = self.format(UT_DEFINITION)
- return result
-
- def offset_by_time(self, origin):
- """
- Returns the time unit offset with respect to the time origin.
-
- Args:
-
- * origin (float): Time origin as returned by the
- :func:`cf_units.encode_time` method.
-
- Returns:
- None.
-
- For example:
-
- >>> import cf_units as unit
- >>> u = unit.Unit('hours')
- >>> u.offset_by_time(unit.encode_time(1970, 1, 1, 0, 0, 0))
- Unit('hour since 1970-01-01 00:00:00.0000000 UTC')
-
- """
-
- if not isinstance(origin, (float, six.integer_types)):
- raise TypeError('a numeric type for the origin argument is'
- ' required')
- ut_unit = _ut_offset_by_time(self.ut_unit, ctypes.c_double(origin))
- if not ut_unit:
- self._raise_error('Failed to offset %r' % self)
- calendar = None
- return _Unit(_CATEGORY_UDUNIT, ut_unit, calendar)
-
- def invert(self):
- """
- Invert the unit i.e. find the reciprocal of the unit, and return
- the Unit result.
-
- Returns:
- Unit.
-
- For example:
-
- >>> import cf_units as unit
- >>> u = unit.Unit('meters')
- >>> u.invert()
- Unit('meter^-1')
-
- """
- if self.is_unknown():
- result = self
- elif self.is_no_unit():
- raise ValueError("Cannot invert a 'no-unit'.")
- else:
- ut_unit = _ut_invert(self.ut_unit)
- if not ut_unit:
- self._raise_error('Failed to invert %r' % self)
- calendar = None
- result = _Unit(_CATEGORY_UDUNIT, ut_unit, calendar)
- return result
-
- def root(self, root):
- """
- Returns the given root of the unit.
-
- Args:
-
- * root (int): Value by which the unit root is taken.
-
- Returns:
- None.
-
- For example:
-
- >>> import cf_units as unit
- >>> u = unit.Unit('meters^2')
- >>> u.root(2)
- Unit('meter')
-
- .. note::
-
- Taking a fractional root of a unit is not supported.
-
- """
- try:
- root = ctypes.c_int(root)
- except TypeError:
- raise TypeError('An int type for the root argument'
- ' is required')
-
- if self.is_unknown():
- result = self
- elif self.is_no_unit():
- raise ValueError("Cannot take the logarithm of a 'no-unit'.")
- else:
- # only update the unit if it is not scalar
- if self == Unit('1'):
- result = self
- else:
- ut_unit = _ut_root(self.ut_unit, root)
- if not ut_unit:
- self._raise_error('Failed to take the root of %r' % self)
- calendar = None
- result = _Unit(_CATEGORY_UDUNIT, ut_unit, calendar)
- return result
-
- def log(self, base):
- """
- Returns the logorithmic unit corresponding to the given
- logorithmic base.
-
- Args:
-
- * base (int/float): Value of the logorithmic base.
-
- Returns:
- None.
-
- For example:
-
- >>> import cf_units as unit
- >>> u = unit.Unit('meters')
- >>> u.log(2)
- Unit('lb(re 1 meter)')
-
- """
- try:
- base = ctypes.c_double(base)
- except TypeError:
- raise TypeError('A numeric type for the base argument is required')
-
- if self.is_unknown():
- result = self
- elif self.is_no_unit():
- raise ValueError("Cannot take the logarithm of a 'no-unit'.")
- else:
- ut_unit = _ut_log(base, self.ut_unit)
- if not ut_unit:
- msg = 'Failed to calculate logorithmic base of %r' % self
- self._raise_error(msg)
- calendar = None
- result = _Unit(_CATEGORY_UDUNIT, ut_unit, calendar)
- return result
-
- def __str__(self):
- """
- Returns a simple string representation of the unit.
-
- Returns:
- string.
-
- For example:
-
- >>> import cf_units as unit
- >>> u = unit.Unit('meters')
- >>> str(u)
- 'meters'
-
- """
- return self.origin or self.name
-
- def __repr__(self):
- """
- Returns a string representation of the unit object.
-
- Returns:
- string.
-
- For example:
-
- >>> import cf_units as unit
- >>> u = unit.Unit('meters')
- >>> repr(u)
- "Unit('meters')"
-
- """
-
- if self.calendar is None:
- result = "%s('%s')" % (self.__class__.__name__, self)
- else:
- result = "%s('%s', calendar='%s')" % (self.__class__.__name__,
- self, self.calendar)
- return result
-
- def _offset_common(self, offset):
- try:
- offset = ctypes.c_double(offset)
- except TypeError:
- result = NotImplemented
- else:
- if self.is_unknown():
- result = self
- elif self.is_no_unit():
- raise ValueError("Cannot offset a 'no-unit'.")
- else:
- ut_unit = _ut_offset(self.ut_unit, offset)
- if not ut_unit:
- self._raise_error('Failed to offset %r' % self)
- calendar = None
- result = _Unit(_CATEGORY_UDUNIT, ut_unit, calendar)
- return result
-
- def __add__(self, other):
- return self._offset_common(other)
-
- def __sub__(self, other):
- try:
- other = -other
- except TypeError:
- result = NotImplemented
- else:
- result = self._offset_common(-other)
- return result
-
- def _op_common(self, other, op_func):
- # Convienience method to create a new unit from an operation between
- # the units 'self' and 'other'.
-
- op_label = op_func.__name__.split('_')[1]
-
- other = as_unit(other)
-
- if self.is_no_unit() or other.is_no_unit():
- raise ValueError("Cannot %s a 'no-unit'." % op_label)
-
- if self.is_unknown() or other.is_unknown():
- result = _Unit(_CATEGORY_UNKNOWN, None)
- else:
- ut_unit = op_func(self.ut_unit, other.ut_unit)
- if not ut_unit:
- msg = 'Failed to %s %r by %r' % (op_label, self, other)
- self._raise_error(msg)
- calendar = None
- result = _Unit(_CATEGORY_UDUNIT, ut_unit, calendar)
- return result
-
- def __rmul__(self, other):
- # NB. Because we've subclassed a tuple, we need to define this to
- # prevent the default tuple-repetition behaviour.
- # ie. 2 * ('a', 'b') -> ('a', 'b', 'a', 'b')
- return self * other
-
- def __mul__(self, other):
- """
- Multiply the self unit by the other scale factor or unit and
- return the Unit result.
-
- Note that, multiplication involving an 'unknown' unit will always
- result in an 'unknown' unit.
-
- Args:
-
- * other (int/float/string/Unit): Multiplication scale
- factor or unit.
-
- Returns:
- Unit.
-
- For example:
-
- >>> import cf_units as unit
- >>> u = unit.Unit('meters')
- >>> v = unit.Unit('hertz')
- >>> u*v
- Unit('meter-second^-1')
-
- """
- return self._op_common(other, _ut_multiply)
-
- def __div__(self, other):
- """
- Divide the self unit by the other scale factor or unit and
- return the Unit result.
-
- Note that, division involving an 'unknown' unit will always
- result in an 'unknown' unit.
-
- Args:
-
- * other (int/float/string/Unit): Division scale factor or unit.
-
- Returns:
- Unit.
-
- For example:
-
- >>> import cf_units as unit
- >>> u = unit.Unit('m.s-1')
- >>> v = unit.Unit('hertz')
- >>> u/v
- Unit('meter')
-
- """
- return self._op_common(other, _ut_divide)
-
- def __truediv__(self, other):
- """
- Divide the self unit by the other scale factor or unit and
- return the Unit result.
-
- Note that, division involving an 'unknown' unit will always
- result in an 'unknown' unit.
-
- Args:
-
- * other (int/float/string/Unit): Division scale factor or unit.
-
- Returns:
- Unit.
-
- For example:
-
- >>> import cf_units as unit
- >>> u = unit.Unit('m.s-1')
- >>> v = unit.Unit('hertz')
- >>> u/v
- Unit('meter')
-
- """
- return self.__div__(other)
-
- def __pow__(self, power):
- """
- Raise the unit by the given power and return the Unit result.
-
- Note that, UDUNITS-2 does not support raising a
- non-dimensionless unit by a fractional power.
- Approximate floating point power behaviour has been implemented
- specifically for Iris.
-
- Args:
-
- * power (int/float): Value by which the unit power is raised.
-
- Returns:
- Unit.
-
- For example:
-
- >>> import cf_units as unit
- >>> u = unit.Unit('meters')
- >>> u**2
- Unit('meter^2')
-
- """
- try:
- power = float(power)
- except ValueError:
- raise TypeError('A numeric value is required for the power'
- ' argument.')
-
- if self.is_unknown():
- result = self
- elif self.is_no_unit():
- raise ValueError("Cannot raise the power of a 'no-unit'.")
- elif self == Unit('1'):
- # 1 ** N -> 1
- result = self
- else:
- # UDUNITS-2 does not support floating point raise/root.
- # But if the power is of the form 1/N, where N is an integer
- # (within a certain acceptable accuracy) then we can find the Nth
- # root.
- if not iris.util.approx_equal(power, 0.0) and abs(power) < 1:
- if not iris.util.approx_equal(1 / power, round(1 / power)):
- raise ValueError('Cannot raise a unit by a decimal.')
- root = int(round(1 / power))
- result = self.root(root)
- else:
- # Failing that, check for powers which are (very nearly) simple
- # integer values.
- if not iris.util.approx_equal(power, round(power)):
- msg = 'Cannot raise a unit by a decimal (got %s).' % power
- raise ValueError(msg)
- power = int(round(power))
-
- ut_unit = _ut_raise(self.ut_unit, ctypes.c_int(power))
- if not ut_unit:
- self._raise_error('Failed to raise the power of %r' % self)
- result = _Unit(_CATEGORY_UDUNIT, ut_unit)
- return result
-
- def _identity(self):
- # Redefine the comparison/hash/ordering identity as used by
- # iris.util._OrderedHashable.
- return (self.name, self.calendar)
-
- __hash__ = iris.util._OrderedHashable.__hash__
-
- def __eq__(self, other):
- """
- Compare the two units for equality and return the boolean result.
-
- Args:
-
- * other (string/Unit): Unit to be compared.
-
- Returns:
- Boolean.
-
- For example:
-
- >>> from cf_units import Unit
- >>> Unit('meters') == Unit('millimeters')
- False
- >>> Unit('meters') == 'm'
- True
-
- """
- other = as_unit(other)
-
- # Compare category (i.e. unknown, no_unit, etc.).
- if self.category != other.category:
- return False
-
- # Compare calendar as UDUNITS cannot handle calendars.
- if self.calendar != other.calendar:
- return False
-
- # Compare UDUNITS.
- res = _ut_compare(self.ut_unit, other.ut_unit)
- return res == 0
-
- def __ne__(self, other):
- """
- Compare the two units for inequality and return the boolean result.
-
- Args:
-
- * other (string/Unit): Unit to be compared.
-
- Returns:
- Boolean.
-
- For example:
-
- >>> from cf_units import Unit
- >>> Unit('meters') != Unit('millimeters')
- True
- >>> Unit('meters') != 'm'
- False
-
- """
- return not self == other
-
- def convert(self, value, other, ctype=FLOAT64):
- """
- Converts a single value or numpy array of values from the current unit
- to the other target unit.
-
- If the units are not convertible, then no conversion will take place.
-
- Args:
-
- * value (int/float/numpy.ndarray):
- Value/s to be converted.
- * other (string/Unit):
- Target unit to convert to.
- * ctype (ctypes.c_float/ctypes.c_double):
- Floating point 32-bit single-precision (cf_units.FLOAT32) or
- 64-bit double-precision (cf_units.FLOAT64) used for conversion
- when `value` is not a NumPy array or is a NumPy array composed of
- NumPy integers. The default is 64-bit double-precision conversion.
-
- Returns:
- float or numpy.ndarray of appropriate float type.
-
- For example:
-
- >>> import cf_units as unit
- >>> import numpy as np
- >>> c = unit.Unit('deg_c')
- >>> f = unit.Unit('deg_f')
- >>> c.convert(0, f)
- 31.999999999999886
- >>> c.convert(0, f, unit.FLOAT32)
- 32.0
- >>> a64 = np.arange(10, dtype=np.float64)
- >>> c.convert(a64, f)
- array([ 32. , 33.8, 35.6, 37.4, 39.2, 41. , 42.8, 44.6, \
- 46.4, 48.2])
- >>> a32 = np.arange(10, dtype=np.float32)
- >>> c.convert(a32, f)
- array([ 32. , 33.79999924, 35.59999847, 37.40000153,
- 39.20000076, 41. , 42.79999924, 44.59999847,
- 46.40000153, 48.20000076], dtype=float32)
-
- .. note::
-
- Conversion between unit calendars is not permitted.
-
- """
- result = None
- other = as_unit(other)
- value_copy = copy.deepcopy(value)
-
- if self == other:
- return value
-
- if self.is_convertible(other):
- # Use utime for converting reference times that are not using a
- # gregorian calendar as it handles these and udunits does not.
- if self.is_time_reference() \
- and self.calendar != CALENDAR_GREGORIAN:
- ut1 = self.utime()
- ut2 = other.utime()
- result = ut2.date2num(ut1.num2date(value_copy))
- # Preserve the datatype of the input array if it was float32.
- if isinstance(value, np.ndarray) and value.dtype == np.float32:
- result = result.astype(np.float32)
- else:
- ut_converter = _ut_get_converter(self.ut_unit, other.ut_unit)
- if ut_converter:
- if isinstance(value_copy, np.ndarray):
- # Can only handle array of np.float32 or np.float64 so
- # cast array of ints to array of floats of requested
- # precision.
- if issubclass(value_copy.dtype.type, np.integer):
- value_copy = value_copy.astype(
- _ctypes2numpy[ctype])
- # Convert arrays with explicit endianness to native
- # endianness: udunits seems to be tripped up by arrays
- # with endianness other than native.
- if value_copy.dtype.byteorder != '=':
- value_copy = value_copy.astype(
- value_copy.dtype.type)
- # strict type check of numpy array
- if value_copy.dtype.type not in _numpy2ctypes:
- raise TypeError(
- "Expect a numpy array of '%s' or '%s'" %
- tuple(sorted(_numpy2ctypes.keys())))
- ctype = _numpy2ctypes[value_copy.dtype.type]
- pointer = value_copy.ctypes.data_as(
- ctypes.POINTER(ctype))
- # Utilise global convenience dictionary
- # _cv_convert_array
- _cv_convert_array[ctype](ut_converter, pointer,
- value_copy.size, pointer)
- result = value_copy
- else:
- if ctype not in _cv_convert_scalar:
- raise ValueError('Invalid target type. Can only '
- 'convert to float or double.')
- # Utilise global convenience dictionary
- # _cv_convert_scalar
- result = _cv_convert_scalar[ctype](ut_converter,
- ctype(value_copy))
- _cv_free(ut_converter)
- else:
- self._raise_error('Failed to convert %r to %r' %
- (self, other))
- else:
- raise ValueError("Unable to convert from '%r' to '%r'." %
- (self, other))
- return result
-
- def utime(self):
- """
- Returns a netcdftime.utime object which performs conversions of
- numeric time values to/from datetime objects given the current
- calendar and unit time reference.
-
- The current unit time reference must be of the form:
- ' since '
- i.e. 'hours since 1970-01-01 00:00:00'
-
- Returns:
- netcdftime.utime.
- """
-
- #
- # ensure to strip out non-parsable 'UTC' postfix which
- # is generated by UDUNITS-2 formatted output
- #
- if self.calendar is None:
- raise ValueError('Unit has undefined calendar')
- return netcdftime.utime(str(self).rstrip(" UTC"), self.calendar)
-
- def date2num(self, date):
- """
- Returns the numeric time value calculated from the datetime
- object using the current calendar and unit time reference.
-
- The current unit time reference must be of the form:
- ' since '
- i.e. 'hours since 1970-01-01 00:00:00'
-
- Works for scalars, sequences and numpy arrays. Returns a scalar
- if input is a scalar, else returns a numpy array.
-
- Args:
-
- * date (datetime):
- A datetime object or a sequence of datetime objects.
- The datetime objects should not include a time-zone offset.
-
- Returns:
- float or numpy.ndarray of float.
- """
-
- cdf_utime = self.utime()
- return cdf_utime.date2num(date)
-
- def num2date(self, time_value):
- """
- Returns a datetime-like object calculated from the numeric time
- value using the current calendar and the unit time reference.
-
- The current unit time reference must be of the form:
- ' since '
- i.e. 'hours since 1970-01-01 00:00:00'
-
- The datetime objects returned are 'real' Python datetime objects
- if the date falls in the Gregorian calendar (i.e. the calendar
- is 'standard', 'gregorian', or 'proleptic_gregorian' and the
- date is after 1582-10-15). Otherwise a 'phoney' datetime-like
- object (netcdftime.datetime) is returned which can handle dates
- that don't exist in the Proleptic Gregorian calendar.
-
- Works for scalars, sequences and numpy arrays. Returns a scalar
- if input is a scalar, else returns a numpy array.
-
- Args:
-
- * time_value (float): Numeric time value/s. Maximum resolution
- is 1 second.
-
- Returns:
- datetime, or numpy.ndarray of datetime object.
- """
- cdf_utime = self.utime()
- return cdf_utime.num2date(time_value)
diff --git a/setup.py b/setup.py
index d69ff83517..6cd19e5cb1 100644
--- a/setup.py
+++ b/setup.py
@@ -210,20 +210,6 @@ def extract_version():
},
data_files=[('iris', ['CHANGES', 'COPYING', 'COPYING.LESSER'])],
tests_require=['nose'],
- features={
- 'unpack': setuptools.Feature(
- "use of UKMO unpack library",
- standard=False,
- ext_modules=[
- setuptools.Extension(
- 'iris.fileformats._old_pp_packing',
- ['src/iris/fileformats/pp_packing/pp_packing.c'],
- libraries=['mo_unpack'],
- include_dirs=[np.get_include()]
- )
- ]
- )
- },
cmdclass={'test': SetupTestRunner, 'build_py': BuildPyWithExtras,
'std_names': MakeStdNames, 'pyke_rules': MakePykeRules,
'clean_source': CleanSource},
diff --git a/src/iris/fileformats/pp_packing/pp_packing.c b/src/iris/fileformats/pp_packing/pp_packing.c
deleted file mode 100644
index 044ce1295d..0000000000
--- a/src/iris/fileformats/pp_packing/pp_packing.c
+++ /dev/null
@@ -1,254 +0,0 @@
-// (C) British Crown Copyright 2010 - 2015, Met Office
-//
-// This file is part of Iris.
-//
-// Iris is free software: you can redistribute it and/or modify it under
-// the terms of the GNU Lesser General Public License as published by the
-// Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Iris is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with Iris. If not, see .
-#include
-
-#include
-
-#include
-#include
-
-static PyObject *wgdos_unpack_py(PyObject *self, PyObject *args);
-static PyObject *rle_decode_py(PyObject *self, PyObject *args);
-
-#define BYTES_PER_INT_UNPACK_PPFIELD 4
-#define LBPACK_WGDOS_PACKED 1
-#define LBPACK_RLE_PACKED 4
-
-
-
-#if PY_MAJOR_VERSION >= 3
-PyMODINIT_FUNC PyInit_old_pp_packing(void)
-#else
-PyMODINIT_FUNC init_old_pp_packing(void)
-#endif
-{
-
- /* The module doc string */
- PyDoc_STRVAR(pp_packing__doc__,
- "This extension module provides access to the underlying libmo_unpack library functionality.\n"
- ""
- );
-
- PyDoc_STRVAR(wgdos_unpack__doc__,
- "Unpack PP field data that has been packed using WGDOS archive method.\n"
- "\n"
- "Provides access to the libmo_unpack library function Wgdos_Unpack.\n"
- "\n"
- "Args:\n\n"
- "* data (numpy.ndarray):\n"
- " The raw field byte array to be unpacked.\n"
- "* lbrow (int):\n"
- " The number of rows in the grid.\n"
- "* lbnpt (int):\n"
- " The number of points (columns) per row in the grid.\n"
- "* bmdi (float):\n"
- " The value used in the field to indicate missing data points.\n"
- "\n"
- "Returns:\n"
- " numpy.ndarray, 2d array containing normal unpacked field data.\n"
- ""
- );
-
-
- PyDoc_STRVAR(rle_decode__doc__,
- "Uncompress PP field data that has been compressed using Run Length Encoding.\n"
- "\n"
- "Provides access to the libmo_unpack library function runlenDecode.\n"
- "Decodes the field by expanding out the missing data points represented\n"
- "by a single missing data value followed by a value indicating the length\n"
- "of the run of missing data values.\n"
- "\n"
- "Args:\n\n"
- "* data (numpy.ndarray):\n"
- " The raw field byte array to be uncompressed.\n"
- "* lbrow (int):\n"
- " The number of rows in the grid.\n"
- "* lbnpt (int):\n"
- " The number of points (columns) per row in the grid.\n"
- "* bmdi (float):\n"
- " The value used in the field to indicate missing data points.\n"
- "\n"
- "Returns:\n"
- " numpy.ndarray, 2d array containing normal uncompressed field data.\n"
- ""
- );
-
- /* ==== Set up the module's methods table ====================== */
- static PyMethodDef pp_packingMethods[] = {
- {"wgdos_unpack", wgdos_unpack_py, METH_VARARGS, wgdos_unpack__doc__},
- {"rle_decode", rle_decode_py, METH_VARARGS, rle_decode__doc__},
- {NULL, NULL, 0, NULL} /* marks the end of this structure */
- };
-
-#if PY_MAJOR_VERSION >= 3
- static struct PyModuleDef moduledef = {
- PyModuleDef_HEAD_INIT,
- "_old_pp_packing",
- pp_packing__doc__,
- -1,
- pp_packingMethods,
- NULL,
- NULL,
- NULL,
- NULL,
- };
-
- PyObject *m = PyModule_Create(&moduledef);
- import_array(); // Must be present for NumPy.
-
- return m;
-#else
- Py_InitModule3("_old_pp_packing", pp_packingMethods, pp_packing__doc__);
- import_array(); // Must be present for NumPy.
-#endif
-}
-
-
-/* wgdos_unpack(byte_array, lbrow, lbnpt, mdi) */
-static PyObject *wgdos_unpack_py(PyObject *self, PyObject *args)
-{
- char *bytes_in=NULL;
- PyArrayObject *npy_array_out=NULL;
- int bytes_in_len;
- npy_intp dims[2];
- int lbrow, lbnpt, npts;
- float mdi;
-
- if (!PyArg_ParseTuple(args, "s#iif", &bytes_in, &bytes_in_len, &lbrow, &lbnpt, &mdi)) return NULL;
-
- // Unpacking algorithm accepts an int - so assert that lbrow*lbnpt does not overflow
- if (lbrow > 0 && lbnpt >= INT_MAX / (lbrow+1)) {
- PyErr_SetString(PyExc_ValueError, "Resulting unpacked PP field is larger than PP supports.");
- return NULL;
- } else{
- npts = lbnpt*lbrow;
- }
-
- // We can't use the macros Py_BEGIN_ALLOW_THREADS / Py_END_ALLOW_THREADS
- // because they declare a new scope block, but we want multiple exits.
- PyThreadState *_save;
- _save = PyEval_SaveThread();
-
- /* Do the unpack of the given byte array */
- float *dataout = (float*)calloc(npts, sizeof(float));
-
- if (dataout == NULL) {
- PyEval_RestoreThread(_save);
- PyErr_SetString(PyExc_ValueError, "Unable to allocate memory for wgdos_unpacking.");
- return NULL;
- }
-
- function func; // function is defined by wgdosstuff.
- set_function_name(__func__, &func, 0);
- int status = unpack_ppfield(mdi, 0, bytes_in, LBPACK_WGDOS_PACKED, npts, dataout, &func);
-
- /* Raise an exception if there was a problem with the WGDOS algorithm */
- if (status != 0) {
- free(dataout);
- PyEval_RestoreThread(_save);
- PyErr_SetString(PyExc_ValueError, "WGDOS unpack encountered an error.");
- return NULL;
- }
- else {
- /* The data came back fine, so make a Numpy array and return it */
- dims[0]=lbrow;
- dims[1]=lbnpt;
- PyEval_RestoreThread(_save);
- npy_array_out=(PyArrayObject *) PyArray_SimpleNewFromData(2, dims, NPY_FLOAT, dataout);
-
- if (npy_array_out == NULL) {
- PyErr_SetString(PyExc_ValueError, "Failed to make the numpy array for the packed data.");
- return NULL;
- }
-
- // give ownership of dataout to the Numpy array - Numpy will then deal with memory cleanup.
- npy_array_out->flags = npy_array_out->flags | NPY_OWNDATA;
-
- return (PyObject *)npy_array_out;
- }
-}
-
-
-/* A null function required by the wgdos unpack library */
-void MO_syslog(int value, char* message, const function* const caller)
-{
- /* printf("MESSAGE %d %s: %s\n", value, caller, message); */
- return;
-}
-
-
-/* rle_decode(byte_array, lbrow, lbnpt, mdi) */
-static PyObject *rle_decode_py(PyObject *self, PyObject *args)
-{
- char *bytes_in=NULL;
- PyArrayObject *npy_array_out=NULL;
- int bytes_in_len;
- npy_intp dims[2];
- int lbrow, lbnpt, npts;
- float mdi;
-
- if (!PyArg_ParseTuple(args, "s#iif", &bytes_in, &bytes_in_len, &lbrow, &lbnpt, &mdi)) return NULL;
-
- // Unpacking algorithm accepts an int - so assert that lbrow*lbnpt does not overflow
- if (lbrow > 0 && lbnpt >= INT_MAX / (lbrow+1)) {
- PyErr_SetString(PyExc_ValueError, "Resulting unpacked PP field is larger than PP supports.");
- return NULL;
- } else{
- npts = lbnpt*lbrow;
- }
-
- // We can't use the macros Py_BEGIN_ALLOW_THREADS / Py_END_ALLOW_THREADS
- // because they declare a new scope block, but we want multiple exits.
- PyThreadState *_save;
- _save = PyEval_SaveThread();
-
- float *dataout = (float*)calloc(npts, sizeof(float));
-
- if (dataout == NULL) {
- PyEval_RestoreThread(_save);
- PyErr_SetString(PyExc_ValueError, "Unable to allocate memory for wgdos_unpacking.");
- return NULL;
- }
-
- function func; // function is defined by wgdosstuff.
- set_function_name(__func__, &func, 0);
- int status = unpack_ppfield(mdi, (bytes_in_len/BYTES_PER_INT_UNPACK_PPFIELD), bytes_in, LBPACK_RLE_PACKED, npts, dataout, &func);
-
- /* Raise an exception if there was a problem with the REL algorithm */
- if (status != 0) {
- free(dataout);
- PyEval_RestoreThread(_save);
- PyErr_SetString(PyExc_ValueError, "RLE decode encountered an error.");
- return NULL;
- }
- else {
- /* The data came back fine, so make a Numpy array and return it */
- dims[0]=lbrow;
- dims[1]=lbnpt;
- PyEval_RestoreThread(_save);
- npy_array_out=(PyArrayObject *) PyArray_SimpleNewFromData(2, dims, NPY_FLOAT, dataout);
-
- if (npy_array_out == NULL) {
- PyErr_SetString(PyExc_ValueError, "Failed to make the numpy array for the packed data.");
- return NULL;
- }
-
- // give ownership of dataout to the Numpy array - Numpy will then deal with memory cleanup.
- npy_array_out->flags = npy_array_out->flags | NPY_OWNDATA;
- return (PyObject *)npy_array_out;
- }
-}