Coverage for py_tools_ds/numeric/vector.py: 100%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
3# py_tools_ds - A collection of geospatial data analysis tools that simplify standard
4# operations when handling geospatial raster and vector data as well as projections.
5#
6# Copyright (C) 2016-2021
7# - Daniel Scheffler (GFZ Potsdam, daniel.scheffler@gfz-potsdam.de)
8# - Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences Potsdam,
9# Germany (https://www.gfz-potsdam.de/)
10#
11# This software was developed within the context of the GeoMultiSens project funded
12# by the German Federal Ministry of Education and Research
13# (project grant code: 01 IS 14 010 A-C).
14#
15# Licensed under the Apache License, Version 2.0 (the "License");
16# you may not use this file except in compliance with the License.
17# You may obtain a copy of the License at
18#
19# http://www.apache.org/licenses/LICENSE-2.0
20#
21# Unless required by applicable law or agreed to in writing, software
22# distributed under the License is distributed on an "AS IS" BASIS,
23# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
24# See the License for the specific language governing permissions and
25# limitations under the License.
27import collections
28import numpy as np
29import bisect
30from typing import Iterable # noqa F401 # flake8 issue
32__author__ = "Daniel Scheffler"
35def find_nearest(array, value, roundAlg='auto', extrapolate=False, exclude_val=False, tolerance=0):
36 # type: (Iterable, float, str, bool, bool, float) -> float
37 """Find the value of an array nearest to a another single value.
39 NOTE: In case of extrapolation an EQUALLY INCREMENTED array (like a coordinate grid) is assumed!
41 :param array: array or list of numbers
42 :param value: a number
43 :param roundAlg: <str> 'auto', 'on', 'off'
44 :param extrapolate: extrapolate the given array if the given value is outside the array
45 :param exclude_val: exclude the given value from possible return values
46 :param tolerance: tolerance (with array = [10, 20, 30] and value=19.9 and roundAlg='off' and tolerance=0.1, 20
47 is returned)
48 """
49 assert roundAlg in ['auto', 'on', 'off']
50 assert isinstance(array, list) or (isinstance(array, np.ndarray) and len(array.shape) == 1)
51 array = sorted(list(array))
53 if exclude_val and value in array:
54 array.remove(value)
56 if extrapolate:
57 increment = abs(array[1] - array[0])
58 if value > max(array): # expand array until value
59 array = np.arange(min(array), value + 2 * increment, increment) # 2 * inc to make array_sub work below
60 if value < min(array): # negatively expand array until value
61 array = (np.arange(max(array), value - 2 * increment, -increment))[::-1]
62 elif value < min(array) or value > max(array):
63 raise ValueError('Value %s is outside of the given array.' % value)
65 if roundAlg == 'auto':
66 diffs = np.abs(np.array(array) - value)
67 minDiff = diffs.min()
68 minIdx = diffs.argmin()
69 isMiddleVal = collections.Counter(diffs)[minDiff] > 1 # value exactly between its both neighbours
70 idx = minIdx if not isMiddleVal else bisect.bisect_left(array, value)
71 out = array[idx]
72 elif roundAlg == 'off':
73 idx = bisect.bisect_left(array, value)
74 if array[idx] == value:
75 out = value # exact hit
76 else:
77 idx -= 1
78 out = array[idx] # round off
79 else: # roundAlg == 'on'
80 idx = bisect.bisect_left(array, value)
81 out = array[idx]
83 if tolerance:
84 array_sub = array[idx - 1: idx + 2]
85 diffs = np.abs(np.array(array_sub) - value)
86 inTol = diffs <= tolerance
88 if True in inTol:
89 out = array_sub[np.argwhere(inTol.astype(int) == 1)[0][0]]
91 return out