Repository: Unidata/MetPy

Issue:
Error in absolute_momentum (bug in normal_component?)
I want to get absolute momentum with xarray.DataArray like:

```
ds = xr.DataArray(...)  # set data, including 'u' and 'v'
cross = cross_section(data, ...)  # create cross section
m = absolute_momentum(cross['u'], cross['v'])
```
But DimensionalityError occurs:

```pytb
> DimensionalityError                       Traceback (most recent call last)
> <ipython-input-269-605840055d4d> in <module>
> ----> 1 m = mp_calc.absolute_momentum(cross['u'], cross['v'])
> 
> ~/myproject-env/lib/python3.8/site-packages/metpy/xarray.py in wrapper(*args, **kwargs)
>    1300                 if not first.metpy.coordinates_identical(other):
>    1301                     raise ValueError('Input DataArray arguments must be on same coordinates.')
> -> 1302         return func(*args, **kwargs)
>    1303     return wrapper
>    1304 
> 
> ~/myproject-env/lib/python3.8/site-packages/metpy/calc/cross_sections.py in absolute_momentum(u, v, index)
>     293     """
>     294     # Get the normal component of the wind
> --> 295     norm_wind = normal_component(u, v, index=index).metpy.convert_units('m/s')
>     296 
>     297     # Get other pieces of calculation (all as ndarrays matching shape of norm_wind)
> 
> ~/myproject-env/lib/python3.8/site-packages/metpy/xarray.py in convert_units(self, units)
>     170         convert_coordinate_units
>     171         """
> --> 172         return self.quantify().copy(data=self.unit_array.to(units))
>     173 
>     174     def convert_coordinate_units(self, coord, units):
> 
> ~/myproject-env/lib/python3.8/site-packages/pint/quantity.py in to(self, other, *contexts, **ctx_kwargs)
>     658         other = to_units_container(other, self._REGISTRY)
>     659 
> --> 660         magnitude = self._convert_magnitude_not_inplace(other, *contexts, **ctx_kwargs)
>     661 
>     662         return self.__class__(magnitude, other)
> 
> ~/myproject-env/lib/python3.8/site-packages/pint/quantity.py in _convert_magnitude_not_inplace(self, other, *contexts, **ctx_kwargs)
>     607                 return self._REGISTRY.convert(self._magnitude, self._units, other)
>     608 
> --> 609         return self._REGISTRY.convert(self._magnitude, self._units, other)
>     610 
>     611     def _convert_magnitude(self, other, *contexts, **ctx_kwargs):
> 
> ~/myproject-env/lib/python3.8/site-packages/pint/registry.py in convert(self, value, src, dst, inplace)
>     945             return value
>     946 
> --> 947         return self._convert(value, src, dst, inplace)
>     948 
>     949     def _convert(self, value, src, dst, inplace=False, check_dimensionality=True):
> 
> ~/myproject-env/lib/python3.8/site-packages/pint/registry.py in _convert(self, value, src, dst, inplace)
>    1828                 value, src = src._magnitude, src._units
>    1829 
> -> 1830         return super()._convert(value, src, dst, inplace)
>    1831 
>    1832     def _get_compatible_units(self, input_units, group_or_system):
> 
> ~/myproject-env/lib/python3.8/site-packages/pint/registry.py in _convert(self, value, src, dst, inplace)
>    1431 
>    1432         if not (src_offset_unit or dst_offset_unit):
> -> 1433             return super()._convert(value, src, dst, inplace)
>    1434 
>    1435         src_dim = self._get_dimensionality(src)
> 
> ~/myproject-env/lib/python3.8/site-packages/pint/registry.py in _convert(self, value, src, dst, inplace, check_dimensionality)
>     978             # then the conversion cannot be performed.
>     979             if src_dim != dst_dim:
> --> 980                 raise DimensionalityError(src, dst, src_dim, dst_dim)
>     981 
>     982         # Here src and dst have only multiplicative units left. Thus we can
> 
> DimensionalityError: Cannot convert from 'dimensionless' (dimensionless) to 'meter / second' ([length] / [time])
```
So I try:
`nc = normal_component(cross['u'], cross['v'])`

but result (`nc`) has no attribute. 

How can I use absolute_momentum ?

My environment is follows:
- Platform: Ubuntu Linux
- python version: 3.8.10
- metpy version: 1.0.1

Discussion:

Patch:
diff --git a/ci/doc_requirements.txt b/ci/doc_requirements.txt
index 0e9bdc51bd..9c1a752596 100644
--- a/ci/doc_requirements.txt
+++ b/ci/doc_requirements.txt
@@ -1,4 +1,4 @@
-sphinx==4.0.3
+sphinx==4.0.2
 # Jinja 3 breaks until we bump pydata-sphinx-theme (I think)
 jinja2<3
 pydata-sphinx-theme==0.5.2
diff --git a/ci/requirements.txt b/ci/requirements.txt
index b1e264f604..963086f84f 100644
--- a/ci/requirements.txt
+++ b/ci/requirements.txt
@@ -1,4 +1,4 @@
-importlib_metadata==4.6.1
+importlib_metadata==4.6.0
 importlib_resources==5.2.0
 matplotlib==3.4.2
 numpy==1.21.0
diff --git a/src/metpy/calc/cross_sections.py b/src/metpy/calc/cross_sections.py
index 28a2c91baf..507ab0c25f 100644
--- a/src/metpy/calc/cross_sections.py
+++ b/src/metpy/calc/cross_sections.py
@@ -292,16 +292,14 @@ def absolute_momentum(u, v, index='index'):
 
     """
     # Get the normal component of the wind
-    norm_wind = normal_component(u, v, index=index).metpy.convert_units('m/s')
+    norm_wind = normal_component(u.metpy.quantify(), v.metpy.quantify(), index=index)
 
     # Get other pieces of calculation (all as ndarrays matching shape of norm_wind)
     latitude = latitude_from_cross_section(norm_wind)
     _, latitude = xr.broadcast(norm_wind, latitude)
-    f = coriolis_parameter(np.deg2rad(latitude.values))
+    f = coriolis_parameter(latitude)
     x, y = distances_from_cross_section(norm_wind)
-    x = x.metpy.convert_units('meters')
-    y = y.metpy.convert_units('meters')
     _, x, y = xr.broadcast(norm_wind, x, y)
     distance = np.hypot(x.metpy.quantify(), y.metpy.quantify())
 
-    return norm_wind + f * distance
+    return (norm_wind + f * distance).metpy.convert_units('m/s')


Test patch:
diff --git a/ci/test_requirements.txt b/ci/test_requirements.txt
index e8a0706227..6f6cc6064a 100644
--- a/ci/test_requirements.txt
+++ b/ci/test_requirements.txt
@@ -1,4 +1,4 @@
 pytest==6.2.4
-pytest-mpl==0.13
+pytest-mpl==0.12
 netCDF4==1.5.7
 coverage==5.5
diff --git a/tests/calc/test_cross_sections.py b/tests/calc/test_cross_sections.py
index ba5698d89f..70193ab49d 100644
--- a/tests/calc/test_cross_sections.py
+++ b/tests/calc/test_cross_sections.py
@@ -11,6 +11,7 @@ from metpy.calc import (absolute_momentum, cross_section_components, normal_comp
                         tangential_component, unit_vectors_from_cross_section)
 from metpy.calc.cross_sections import (distances_from_cross_section,
                                        latitude_from_cross_section)
+from metpy.cbook import get_test_data
 from metpy.interpolate import cross_section
 from metpy.testing import assert_array_almost_equal, assert_xarray_allclose, needs_cartopy
 from metpy.units import units
@@ -321,3 +322,23 @@ def test_absolute_momentum_given_xy(test_cross_xy):
                                  coords=test_cross_xy['u_wind'].coords,
                                  dims=test_cross_xy['u_wind'].dims)
     assert_xarray_allclose(momentum, true_momentum)
+
+
+def test_absolute_momentum_xarray_units_attr():
+    """Test absolute momentum when `u` and `v` are DataArrays with a `units` attribute."""
+    data = xr.open_dataset(get_test_data('narr_example.nc', False))
+    data = data.metpy.parse_cf().squeeze()
+
+    start = (37.0, -105.0)
+    end = (35.5, -65.0)
+    cross = cross_section(data, start, end)
+
+    u = cross['u_wind'][0].sel(index=slice(0, 2))
+    v = cross['v_wind'][0].sel(index=slice(0, 2))
+
+    momentum = absolute_momentum(u, v)
+    true_momentum_values = np.array([137.46164031, 134.11450232, 133.85196023])
+    true_momentum = xr.DataArray(units.Quantity(true_momentum_values, 'm/s'),
+                                 coords=u.coords)
+
+    assert_xarray_allclose(momentum, true_momentum)