diff --git a/meteoinfo-lab/milconfig.xml b/meteoinfo-lab/milconfig.xml
index 203e089b..61e3c364 100644
--- a/meteoinfo-lab/milconfig.xml
+++ b/meteoinfo-lab/milconfig.xml
@@ -1,6 +1,6 @@
-
+
@@ -12,21 +12,23 @@
-
-
+
+
+
+
@@ -34,5 +36,5 @@
-
+
diff --git a/meteoinfo-lab/pylib/mipylib/meteolib/calc/tools.py b/meteoinfo-lab/pylib/mipylib/meteolib/calc/tools.py
index eae2014d..c874c960 100644
--- a/meteoinfo-lab/pylib/mipylib/meteolib/calc/tools.py
+++ b/meteoinfo-lab/pylib/mipylib/meteolib/calc/tools.py
@@ -1,5 +1,48 @@
import mipylib.numeric as np
+def resample_nn_1d(a, centers):
+ """Return one-dimensional nearest-neighbor indexes based on user-specified centers.
+ Parameters
+ ----------
+ a : array-like
+ 1-dimensional array of numeric values from which to extract indexes of
+ nearest-neighbors
+ centers : array-like
+ 1-dimensional array of numeric values representing a subset of values to approximate
+ Returns
+ -------
+ A list of indexes (in type given by `array.argmin()`) representing values closest to
+ given array values.
+ """
+ ix = []
+ for center in centers:
+ index = (np.abs(a - center)).argmin()
+ if index not in ix:
+ ix.append(index)
+ return ix
+
+def nearest_intersection_idx(a, b):
+ """Determine the index of the point just before two lines with common x values.
+ Parameters
+ ----------
+ a : array-like
+ 1-dimensional array of y-values for line 1
+ b : array-like
+ 1-dimensional array of y-values for line 2
+ Returns
+ -------
+ An array of indexes representing the index of the values
+ just before the intersection(s) of the two lines.
+ """
+ # Difference in the two y-value sets
+ difference = a - b
+
+ # Determine the point just before the intersection of the lines
+ # Will return multiple points for multiple intersections
+ sign_change_idx, = np.nonzero(np.diff(np.sign(difference)))
+
+ return sign_change_idx
+
def _remove_nans(*variables):
"""Remove NaNs from arrays that cause issues with calculations.
Takes a variable number of arguments and returns masked arrays in the same
diff --git a/meteoinfo-lab/pylib/mipylib/numeric/__init__$py.class b/meteoinfo-lab/pylib/mipylib/numeric/__init__$py.class
index 05c75087..d41ec291 100644
Binary files a/meteoinfo-lab/pylib/mipylib/numeric/__init__$py.class and b/meteoinfo-lab/pylib/mipylib/numeric/__init__$py.class differ
diff --git a/meteoinfo-lab/pylib/mipylib/numeric/__init__.py b/meteoinfo-lab/pylib/mipylib/numeric/__init__.py
index 2268c9c4..40ab03d5 100644
--- a/meteoinfo-lab/pylib/mipylib/numeric/__init__.py
+++ b/meteoinfo-lab/pylib/mipylib/numeric/__init__.py
@@ -4,6 +4,7 @@ from . import lib
from .lib import *
from . import linalg
from . import random
+from . import ma
from . import fitting
from . import stats
from . import interpolate
@@ -17,6 +18,6 @@ __all__ = []
__all__.extend(['__version__'])
__all__.extend(core.__all__)
__all__.extend(lib.__all__)
-__all__.extend(['linalg', 'fitting', 'random', 'stats', 'interpolate', 'optimize', 'signal', 'spatial',
+__all__.extend(['linalg', 'fitting', 'random', 'ma', 'stats', 'interpolate', 'optimize', 'signal', 'spatial',
'special'])
__all__.extend(['griddata'])
\ No newline at end of file
diff --git a/meteoinfo-lab/pylib/mipylib/numeric/ma/__init__.py b/meteoinfo-lab/pylib/mipylib/numeric/ma/__init__.py
new file mode 100644
index 00000000..692f3c69
--- /dev/null
+++ b/meteoinfo-lab/pylib/mipylib/numeric/ma/__init__.py
@@ -0,0 +1 @@
+from .core import masked_array, MaskedArray
\ No newline at end of file
diff --git a/meteoinfo-lab/pylib/mipylib/numeric/ma/core.py b/meteoinfo-lab/pylib/mipylib/numeric/ma/core.py
new file mode 100644
index 00000000..5fd0a281
--- /dev/null
+++ b/meteoinfo-lab/pylib/mipylib/numeric/ma/core.py
@@ -0,0 +1,165 @@
+from mipylib import numeric as np
+from ..core._ndarray import NDArray
+from org.meteoinfo.ndarray.math import ArrayUtil
+
+nomask = False
+
+class MaskedArray(NDArray):
+ """
+ An array class with possibly masked values.
+ Masked values of True exclude the corresponding element from any
+ computation.
+ Construction::
+ x = MaskedArray(data, mask=nomask, dtype=None, copy=False, subok=True,
+ ndmin=0, fill_value=None, keep_mask=True, hard_mask=None,
+ shrink=True, order=None)
+ Parameters
+ ----------
+ data : array_like
+ Input data.
+ mask : sequence, optional
+ Mask. Must be convertible to an array of booleans with the same
+ shape as `data`. True indicates a masked (i.e. invalid) data.
+ dtype : dtype, optional
+ Data type of the output.
+ If `dtype` is None, the type of the data argument (``data.dtype``)
+ is used. If `dtype` is not None and different from ``data.dtype``,
+ a copy is performed.
+ copy : bool, optional
+ Whether to copy the input data (True), or to use a reference instead.
+ Default is False.
+ subok : bool, optional
+ Whether to return a subclass of `MaskedArray` if possible (True) or a
+ plain `MaskedArray`. Default is True.
+ ndmin : int, optional
+ Minimum number of dimensions. Default is 0.
+ fill_value : scalar, optional
+ Value used to fill in the masked values when necessary.
+ If None, a default based on the data-type is used.
+ """
+
+ def __init__(self, data, mask=nomask, dtype=None, copy=False,
+ subok=True, ndmin=0, fill_value=None):
+ if isinstance(data, NDArray):
+ data = data._array
+ super(MaskedArray, self).__init__(data)
+ self._data = NDArray(self._array)
+ self._baseclass = getattr(data, '_baseclass', type(self._data))
+ if mask is nomask:
+ self._mask = mask
+ else:
+ self._mask = np.array(mask)
+ if self._mask.shape != self._data.shape:
+ self._mask = self._mask.reshape(self._data.shape)
+ if self._mask.dtype != np.dtype.bool:
+ self._mask = self._mask.astype(np.dtype.bool)
+ self._fill_value = fill_value
+
+ def __str__(self):
+ r = 'masked_array(data=' + ArrayUtil.convertToString(self._data._array) + ',\n\tmask='
+ if self._mask is nomask:
+ r = r + 'False,'
+ else:
+ r = r + ArrayUtil.convertToString(self._mask._array) + ','
+ r = r + '\n\tfill_value=' + str(self._fill_value) + ')'
+ return r
+
+ def __repr__(self):
+ return self.__str__()
+
+ def filled(self, fill_value=None):
+ """
+ Return a copy of self, with masked values filled with a given value.
+ **However**, if there are no masked values to fill, self will be
+ returned instead as an ndarray.
+ Parameters
+ ----------
+ fill_value : array_like, optional
+ The value to use for invalid entries. Can be scalar or non-scalar.
+ If non-scalar, the resulting ndarray must be broadcastable over
+ input array. Default is None, in which case, the `fill_value`
+ attribute of the array is used instead.
+ Returns
+ -------
+ filled_array : ndarray
+ A copy of ``self`` with invalid entries replaced by *fill_value*
+ (be it the function argument or the attribute of ``self``), or
+ ``self`` itself as an ndarray if there are no invalid entries to
+ be replaced.
+ Notes
+ -----
+ The result is **not** a MaskedArray!
+ Examples
+ --------
+ >>> x = np.ma.array([1,2,3,4,5], mask=[0,0,1,0,1], fill_value=-999)
+ >>> x.filled()
+ array([ 1, 2, -999, 4, -999])
+ >>> x.filled(fill_value=1000)
+ array([ 1, 2, 1000, 4, 1000])
+ >>> type(x.filled())
+
+ Subclassing is preserved. This means that if, e.g., the data part of
+ the masked array is a recarray, `filled` returns a recarray:
+ >>> x = np.array([(-1, 2), (-3, 4)], dtype='i8,i8').view(np.recarray)
+ >>> m = np.ma.array(x, mask=[(True, False), (False, True)])
+ >>> m.filled()
+ rec.array([(999999, 2), ( -3, 999999)],
+ dtype=[('f0', '