Skip to content

Commit

Permalink
Merge pull request #853 from ahuang11/master
Browse files Browse the repository at this point in the history
Add angle_to_dir to convert angles to nearest direction
  • Loading branch information
zbruick committed Sep 27, 2019
2 parents d2650cd + 6116cec commit f377a48
Show file tree
Hide file tree
Showing 4 changed files with 306 additions and 16 deletions.
1 change: 1 addition & 0 deletions docs/_templates/overrides/metpy.calc.rst
Expand Up @@ -173,6 +173,7 @@ Other
.. autosummary::
:toctree: ./

angle_to_direction
find_bounding_indices
find_intersections
get_layer
Expand Down
38 changes: 38 additions & 0 deletions examples/calculations/Angle_to_Direction.py
@@ -0,0 +1,38 @@
# Copyright (c) 2019 MetPy Developers.
# Distributed under the terms of the BSD 3-Clause License.
# SPDX-License-Identifier: BSD-3-Clause
"""
Angle to Direction
==================
Demonstrate how to convert angles to direction strings.
The code below shows how to convert angles into directional text.
It also demonstrates the function's flexibility.
"""
import metpy.calc as mpcalc
from metpy.units import units

###########################################
# Create a test value of an angle
angle_deg = 70 * units('degree')
print(angle_deg)

###########################################
# Now throw that angle into the function to
# get the corresponding direction
dir_str = mpcalc.angle_to_direction(angle_deg)
print(dir_str)

###########################################
# The function can also handle array of angles,
# rounding to the nearest direction, handling angles > 360,
# and defaulting to degrees if no units are specified.
angle_deg_list = [0, 361, 719]
dir_str_list = mpcalc.angle_to_direction(angle_deg_list)
print(dir_str_list)

###########################################
# If you want the unabbrieviated version, input full=True
full_dir_str_list = mpcalc.angle_to_direction(angle_deg_list, full=True)
print(full_dir_str_list)
141 changes: 133 additions & 8 deletions metpy/calc/tests/test_calc_tools.py
Expand Up @@ -11,21 +11,24 @@
import pytest
import xarray as xr


from metpy.calc import (find_bounding_indices, find_intersections, first_derivative, get_layer,
get_layer_heights, gradient, grid_deltas_from_dataarray, interp,
interpolate_nans, laplacian, lat_lon_grid_deltas, log_interp,
from metpy.calc import (angle_to_direction, find_bounding_indices, find_intersections,
first_derivative, get_layer, get_layer_heights, gradient,
grid_deltas_from_dataarray, interp, interpolate_nans,
laplacian, lat_lon_grid_deltas, log_interp,
nearest_intersection_idx, parse_angle, pressure_to_height_std,
reduce_point_density, resample_nn_1d, second_derivative)
from metpy.calc.tools import (_delete_masked_points, _get_bound_pressure_height,
_greater_or_close, _less_or_close, _next_non_masked_element,
DIR_STRS)
BASE_DEGREE_MULTIPLIER, DIR_STRS, UND)
from metpy.deprecation import MetpyDeprecationWarning
from metpy.testing import (assert_almost_equal, assert_array_almost_equal, assert_array_equal,
check_and_silence_deprecation)
from metpy.units import units


FULL_CIRCLE_DEGREES = np.arange(0, 360, BASE_DEGREE_MULTIPLIER.m) * units.degree


def test_resample_nn():
"""Test 1d nearest neighbor functionality."""
a = np.arange(5.)
Expand Down Expand Up @@ -649,8 +652,8 @@ def test_laplacian_x_deprecation(deriv_2d_data):

def test_parse_angle_abbrieviated():
"""Test abbrieviated directional text in degrees."""
expected_angles_degrees = np.arange(0, 360, 22.5) * units.degree
output_angles_degrees = parse_angle(DIR_STRS)
expected_angles_degrees = FULL_CIRCLE_DEGREES
output_angles_degrees = parse_angle(DIR_STRS[:-1])
assert_array_almost_equal(output_angles_degrees, expected_angles_degrees)


Expand All @@ -671,7 +674,42 @@ def test_parse_angle_mix_multiple():
'easT', 'east se', 'south east', ' south southeast',
'SOUTH', 'SOUTH SOUTH WEST', 'sw', 'WEST south_WEST',
'w', 'wnw', 'North West', 'nnw']
expected_angles_degrees = np.arange(0, 360, 22.5) * units.degree
expected_angles_degrees = FULL_CIRCLE_DEGREES
output_angles_degrees = parse_angle(test_dir_strs)
assert_array_almost_equal(output_angles_degrees, expected_angles_degrees)


def test_parse_angle_none():
"""Test list of extended (unabbrieviated) directional text in degrees in one go."""
test_dir_strs = None
expected_angles_degrees = np.nan
output_angles_degrees = parse_angle(test_dir_strs)
assert_array_almost_equal(output_angles_degrees, expected_angles_degrees)


def test_parse_angle_invalid_number():
"""Test list of extended (unabbrieviated) directional text in degrees in one go."""
test_dir_strs = 365.
expected_angles_degrees = np.nan
output_angles_degrees = parse_angle(test_dir_strs)
assert_array_almost_equal(output_angles_degrees, expected_angles_degrees)


def test_parse_angle_invalid_arr():
"""Test list of extended (unabbrieviated) directional text in degrees in one go."""
test_dir_strs = ['nan', None, np.nan, 35, 35.5, 'north', 'andrewiscool']
expected_angles_degrees = [np.nan, np.nan, np.nan, np.nan, np.nan, 0, np.nan]
output_angles_degrees = parse_angle(test_dir_strs)
assert_array_almost_equal(output_angles_degrees, expected_angles_degrees)


def test_parse_angle_mix_multiple_arr():
"""Test list of extended (unabbrieviated) directional text in degrees in one go."""
test_dir_strs = np.array(['NORTH', 'nne', 'ne', 'east north east',
'easT', 'east se', 'south east', ' south southeast',
'SOUTH', 'SOUTH SOUTH WEST', 'sw', 'WEST south_WEST',
'w', 'wnw', 'North West', 'nnw'])
expected_angles_degrees = FULL_CIRCLE_DEGREES
output_angles_degrees = parse_angle(test_dir_strs)
assert_array_almost_equal(output_angles_degrees, expected_angles_degrees)

Expand Down Expand Up @@ -770,6 +808,93 @@ def test_bounding_indices_above():
assert_array_equal(good, np.array([[True, False], [False, True]]))


def test_angle_to_direction():
"""Test single angle in degree."""
expected_dirs = DIR_STRS[:-1] # UND at -1
output_dirs = [angle_to_direction(angle) for angle in FULL_CIRCLE_DEGREES]
assert_array_equal(output_dirs, expected_dirs)


def test_angle_to_direction_edge():
"""Test single angle edge case (360 and no units) in degree."""
expected_dirs = 'N'
output_dirs = angle_to_direction(360)
assert_array_equal(output_dirs, expected_dirs)


def test_angle_to_direction_list():
"""Test list of angles in degree."""
expected_dirs = DIR_STRS[:-1]
output_dirs = list(angle_to_direction(FULL_CIRCLE_DEGREES))
assert_array_equal(output_dirs, expected_dirs)


def test_angle_to_direction_arr():
"""Test array of angles in degree."""
expected_dirs = DIR_STRS[:-1]
output_dirs = angle_to_direction(FULL_CIRCLE_DEGREES)
assert_array_equal(output_dirs, expected_dirs)


def test_angle_to_direction_full():
"""Test the `full` keyword argument, expecting unabbrieviated output."""
expected_dirs = [
'North', 'North North East', 'North East', 'East North East',
'East', 'East South East', 'South East', 'South South East',
'South', 'South South West', 'South West', 'West South West',
'West', 'West North West', 'North West', 'North North West'
]
output_dirs = angle_to_direction(FULL_CIRCLE_DEGREES, full=True)
assert_array_equal(output_dirs, expected_dirs)


def test_angle_to_direction_invalid_scalar():
"""Test invalid angle."""
expected_dirs = UND
output_dirs = angle_to_direction(None)
assert_array_equal(output_dirs, expected_dirs)


def test_angle_to_direction_invalid_arr():
"""Test array of invalid angles."""
expected_dirs = ['NE', UND, UND, UND, 'N']
output_dirs = angle_to_direction(['46', None, np.nan, None, '362.'])
assert_array_equal(output_dirs, expected_dirs)


def test_angle_to_direction_level_4():
"""Test non-existent level of complexity."""
with pytest.raises(ValueError) as exc:
angle_to_direction(FULL_CIRCLE_DEGREES, level=4)
assert 'cannot be less than 1 or greater than 3' in str(exc.value)


def test_angle_to_direction_level_3():
"""Test array of angles in degree."""
expected_dirs = DIR_STRS[:-1] # UND at -1
output_dirs = angle_to_direction(FULL_CIRCLE_DEGREES, level=3)
assert_array_equal(output_dirs, expected_dirs)


def test_angle_to_direction_level_2():
"""Test array of angles in degree."""
expected_dirs = [
'N', 'N', 'NE', 'NE', 'E', 'E', 'SE', 'SE',
'S', 'S', 'SW', 'SW', 'W', 'W', 'NW', 'NW'
]
output_dirs = angle_to_direction(FULL_CIRCLE_DEGREES, level=2)
assert_array_equal(output_dirs, expected_dirs)


def test_angle_to_direction_level_1():
"""Test array of angles in degree."""
expected_dirs = [
'N', 'N', 'N', 'E', 'E', 'E', 'E', 'S', 'S', 'S', 'S',
'W', 'W', 'W', 'W', 'N']
output_dirs = angle_to_direction(FULL_CIRCLE_DEGREES, level=1)
assert_array_equal(output_dirs, expected_dirs)


def test_3d_gradient_3d_data_no_axes(deriv_4d_data):
"""Test 3D gradient with 3D data and no axes parameter."""
test = deriv_4d_data[0]
Expand Down

0 comments on commit f377a48

Please sign in to comment.