Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add angle_to_dir to convert angles to nearest direction #853

Merged
merged 8 commits into from Sep 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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)
zbruick marked this conversation as resolved.
Show resolved Hide resolved


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