MetPy icon indicating copy to clipboard operation
MetPy copied to clipboard

gdxarray() should assign units to x and y coordinates

Open sgdecker opened this issue 4 years ago • 6 comments

Is there a way to construct the proper Dataset with the current MetPy functionality so my attempt to plot wind barbs works?

Here's my attempt:

from datetime import datetime

import xarray as xr
import metpy
from metpy.units import units
from metpy.io import GempakGrid
from metpy.plots.declarative import ContourPlot, FilledContourPlot, BarbPlot, MapPanel, PanelContainer

print(metpy.__version__)

MODEL = '/ldmdata/gempak/model/'
gem_file_name = MODEL + 'nam/21092012_nam211.gem'
gem_file = GempakGrid(gem_file_name)

plot_time = datetime(2021, 9, 20, 12)

u500 = gem_file.gdxarray(parameter='UREL', date_time=plot_time, level=500)[0]
v500 = gem_file.gdxarray(parameter='VREL', date_time=plot_time, level=500)[0]
u500 = u500 * units('m/s')
v500 = v500 * units('m/s')

wind = xr.merge([u500, v500])

barbs = BarbPlot()
barbs.data = wind
barbs.time = plot_time
barbs.field = ['urel', 'vrel']
barbs.earth_relative = False   # I get a plot w/o this line, but obviously the winds aren't rotated correctly
barbs.skip = (3, 3)
barbs.plot_units = 'knot'

panel = MapPanel()
panel.area = [-120, -74, 22, 55]
panel.projection = 'lcc'
panel.layers = ['states', 'coastline', 'borders']
panel.title = f'500-mb Winds at {plot_time}'
panel.plots = [barbs]

pc = PanelContainer()
pc.size = (15, 15)
pc.panels = [panel]
pc.show()

The result:

1.1.0.post39+gd43d5eb02.d20210825
Traceback (most recent call last):
  File "/home/decker/classes/met433/new_labs/barb_issue.py", line 42, in <module>
    pc.show()
  File "/home/decker/src/git_repos/metpy/src/metpy/plots/declarative.py", line 589, in show
    self.draw()
  File "/home/decker/src/git_repos/metpy/src/metpy/plots/declarative.py", line 576, in draw
    panel.draw()
  File "/home/decker/src/git_repos/metpy/src/metpy/plots/declarative.py", line 846, in draw
    p.draw()
  File "/home/decker/src/git_repos/metpy/src/metpy/plots/declarative.py", line 1480, in draw
    self._build()
  File "/home/decker/src/git_repos/metpy/src/metpy/plots/declarative.py", line 1504, in _build
    x_like, y_like, u, v = self.plotdata
  File "/home/decker/src/git_repos/metpy/src/metpy/plots/declarative.py", line 1465, in plotdata
    if 'degree' in x.units:
  File "/home/decker/local/miniconda3/envs/devel21/lib/python3.9/site-packages/xarray/core/common.py", line 239, in __getattr__
    raise AttributeError(
AttributeError: 'DataArray' object has no attribute 'units'

sgdecker avatar Sep 20 '21 21:09 sgdecker

There should be a way of doing this either through the metpy.dequantify(), which will take units attached to a variable and make the unit an attribute within the xarray object.

Try the following for the u500 and v500...

u500 = (u500 * units('m/s')).metpy.dequantify()
v500 = (v500 * units('m/s')).metpy.dequantify()

Here is the Units portion of the xarray tutorial that might also help.

https://unidata.github.io/MetPy/latest/tutorials/xarray_tutorial.html#units

kgoebber avatar Sep 21 '21 13:09 kgoebber

Thanks. I had tried metpy.quantify(), which didn't appear to do anything (presumably because urel/vrel are not CF-kosher), and metpy.quantify('m/s'), which crashed IIRC, before settling on the unit multiplication approach. It didn't occur to me to try dequantify().

Unfortunately, that change had no effect!

Because the problem only occurs with barbs.earth_relative = False, I was thinking somehow the units for longitude were screwed up (since rotating the winds properly on this grid needs longitude info), and that's the missing attribute, but I haven't been able to confirm that yet, and if it is the problem, I haven't figured out how to fix it.

sgdecker avatar Sep 21 '21 13:09 sgdecker

Ah, yes, it could be that the x/y (or lon/lat) values don't have the units needed.

You could try attaching the units to the coordinate variables. Assuming they are 'x' and 'y' and the units are meters...

wind['x'] = wind['x'] * units('meters')
wind['y'] = wind['y'] * units('meters')

kgoebber avatar Sep 21 '21 16:09 kgoebber

@kgoebber thanks; I think we are getting somewhere! I was fixated on latitude/longitude, but the error traceback does mention x after all. Here is a revised program:

from datetime import datetime

import xarray as xr
import metpy
from metpy.units import units
from metpy.io import GempakGrid
from metpy.plots.declarative import ContourPlot, FilledContourPlot, BarbPlot, MapPanel, PanelContainer

print(metpy.__version__)

MODEL = '/ldmdata/gempak/model/'
gem_file_name = MODEL + 'nam/21092012_nam211.gem'
gem_file = GempakGrid(gem_file_name)

plot_time = datetime(2021, 9, 20, 12)

u500 = gem_file.gdxarray(parameter='UREL', date_time=plot_time, level=500)[0]
v500 = gem_file.gdxarray(parameter='VREL', date_time=plot_time, level=500)[0]
u500 = u500 * units('m/s')
v500 = v500 * units('m/s')

wind = xr.merge([u500, v500]).metpy.assign_latitude_longitude()
print(wind.longitude)
print(wind.x)
wind['x'] = wind['x'] * units('meters')
wind['y'] = wind['y'] * units('meters')
print(wind.x)

barbs = BarbPlot()
barbs.data = wind
barbs.time = plot_time
barbs.field = ['urel', 'vrel']
barbs.earth_relative = False
barbs.skip = (3, 3)
barbs.plot_units = 'knot'

panel = MapPanel()
panel.area = [-120, -74, 22, 55]
panel.projection = 'lcc'
panel.layers = ['states', 'coastline', 'borders']
panel.title = f'500-mb Winds at {plot_time}'
panel.plots = [barbs]

pc = PanelContainer()
pc.size = (15, 15)
pc.panels = [panel]
pc.show()

And the output:

1.1.0.post39+gd43d5eb02.d20210825
<xarray.DataArray 'longitude' (y: 65, x: 93)>
array([[-133.45899757, -132.75739652, -132.05371082, ...,  -66.54178831,
         -65.81553154,  -65.09097213],
       [-133.66429679, -132.95935346, -132.25229366, ...,  -66.386047  ,
         -65.65606298,  -64.92780342],
       [-133.87173891, -133.16342482, -132.4529618 , ...,  -66.22861748,
         -65.49486987,  -64.76287439],
       ...,
       [-151.97501727, -151.00076902, -150.02034207, ...,  -52.20690183,
         -51.15677614,  -50.11207458],
       [-152.41212945, -151.43223072, -150.44602669, ...,  -51.86040261,
         -50.80299078,  -49.75112582],
       [-152.85558563, -151.86999524, -150.87796976, ...,  -51.50844159,
         -50.44365796,  -49.38454751]])
Coordinates:
  * x          (x) float32 -4.226e+06 -4.145e+06 ... 3.17e+06 3.251e+06
  * y          (y) float32 2.035e+06 2.117e+06 2.198e+06 ... 7.155e+06 7.237e+06
    metpy_crs  object Projection: lambert_conformal_conic
    latitude   (y, x) float64 12.19 12.39 12.58 12.77 ... 57.68 57.49 57.29
    longitude  (y, x) float64 -133.5 -132.8 -132.1 ... -51.51 -50.44 -49.38
Attributes:
    units:          degrees_east
    standard_name:  longitude
<xarray.DataArray 'x' (x: 93)>
array([-4.226088e+06, -4.144817e+06, -4.063546e+06, -3.982275e+06,
       -3.901004e+06, -3.819733e+06, -3.738462e+06, -3.657191e+06,
       -3.575920e+06, -3.494649e+06, -3.413378e+06, -3.332107e+06,
       -3.250836e+06, -3.169565e+06, -3.088294e+06, -3.007023e+06,
       -2.925752e+06, -2.844481e+06, -2.763210e+06, -2.681939e+06,
       -2.600668e+06, -2.519397e+06, -2.438126e+06, -2.356855e+06,
       -2.275584e+06, -2.194313e+06, -2.113042e+06, -2.031771e+06,
       -1.950500e+06, -1.869229e+06, -1.787958e+06, -1.706687e+06,
       -1.625416e+06, -1.544145e+06, -1.462874e+06, -1.381603e+06,
       -1.300332e+06, -1.219061e+06, -1.137790e+06, -1.056519e+06,
       -9.752478e+05, -8.939768e+05, -8.127058e+05, -7.314348e+05,
       -6.501638e+05, -5.688928e+05, -4.876218e+05, -4.063508e+05,
       -3.250798e+05, -2.438088e+05, -1.625378e+05, -8.126677e+04,
        4.234092e+00,  8.127523e+04,  1.625462e+05,  2.438172e+05,
        3.250882e+05,  4.063592e+05,  4.876302e+05,  5.689012e+05,
        6.501722e+05,  7.314432e+05,  8.127142e+05,  8.939852e+05,
        9.752562e+05,  1.056527e+06,  1.137798e+06,  1.219069e+06,
        1.300340e+06,  1.381611e+06,  1.462882e+06,  1.544153e+06,
        1.625424e+06,  1.706695e+06,  1.787966e+06,  1.869237e+06,
        1.950508e+06,  2.031779e+06,  2.113050e+06,  2.194321e+06,
        2.275592e+06,  2.356863e+06,  2.438134e+06,  2.519405e+06,
        2.600676e+06,  2.681947e+06,  2.763218e+06,  2.844489e+06,
        2.925760e+06,  3.007031e+06,  3.088302e+06,  3.169573e+06,
        3.250844e+06], dtype=float32)
Coordinates:
  * x          (x) float32 -4.226e+06 -4.145e+06 ... 3.17e+06 3.251e+06
    metpy_crs  object Projection: lambert_conformal_conic
Attributes:
    _metpy_axis:  x
<xarray.DataArray 'x' (x: 93)>
array([-4.226088e+06, -4.144817e+06, -4.063546e+06, -3.982275e+06,
       -3.901004e+06, -3.819733e+06, -3.738462e+06, -3.657191e+06,
       -3.575920e+06, -3.494649e+06, -3.413378e+06, -3.332107e+06,
       -3.250836e+06, -3.169565e+06, -3.088294e+06, -3.007023e+06,
       -2.925752e+06, -2.844481e+06, -2.763210e+06, -2.681939e+06,
       -2.600668e+06, -2.519397e+06, -2.438126e+06, -2.356855e+06,
       -2.275584e+06, -2.194313e+06, -2.113042e+06, -2.031771e+06,
       -1.950500e+06, -1.869229e+06, -1.787958e+06, -1.706687e+06,
       -1.625416e+06, -1.544145e+06, -1.462874e+06, -1.381603e+06,
       -1.300332e+06, -1.219061e+06, -1.137790e+06, -1.056519e+06,
       -9.752478e+05, -8.939768e+05, -8.127058e+05, -7.314348e+05,
       -6.501638e+05, -5.688928e+05, -4.876218e+05, -4.063508e+05,
       -3.250798e+05, -2.438088e+05, -1.625378e+05, -8.126677e+04,
        4.234092e+00,  8.127523e+04,  1.625462e+05,  2.438172e+05,
        3.250882e+05,  4.063592e+05,  4.876302e+05,  5.689012e+05,
        6.501722e+05,  7.314432e+05,  8.127142e+05,  8.939852e+05,
        9.752562e+05,  1.056527e+06,  1.137798e+06,  1.219069e+06,
        1.300340e+06,  1.381611e+06,  1.462882e+06,  1.544153e+06,
        1.625424e+06,  1.706695e+06,  1.787966e+06,  1.869237e+06,
        1.950508e+06,  2.031779e+06,  2.113050e+06,  2.194321e+06,
        2.275592e+06,  2.356863e+06,  2.438134e+06,  2.519405e+06,
        2.600676e+06,  2.681947e+06,  2.763218e+06,  2.844489e+06,
        2.925760e+06,  3.007031e+06,  3.088302e+06,  3.169573e+06,
        3.250844e+06], dtype=float32)
Coordinates:
  * x          (x) float32 -4.226e+06 -4.145e+06 ... 3.17e+06 3.251e+06
    metpy_crs  object Projection: lambert_conformal_conic
Traceback (most recent call last):
  File "/home/decker/classes/met433/new_labs/barb_issue.py", line 47, in <module>
    pc.show()
  File "/home/decker/src/git_repos/metpy/src/metpy/plots/declarative.py", line 589, in show
    self.draw()
  File "/home/decker/src/git_repos/metpy/src/metpy/plots/declarative.py", line 576, in draw
    panel.draw()
  File "/home/decker/src/git_repos/metpy/src/metpy/plots/declarative.py", line 846, in draw
    p.draw()
  File "/home/decker/src/git_repos/metpy/src/metpy/plots/declarative.py", line 1480, in draw
    self._build()
  File "/home/decker/src/git_repos/metpy/src/metpy/plots/declarative.py", line 1504, in _build
    x_like, y_like, u, v = self.plotdata
  File "/home/decker/src/git_repos/metpy/src/metpy/plots/declarative.py", line 1465, in plotdata
    if 'degree' in x.units:
  File "/home/decker/local/miniconda3/envs/devel21/lib/python3.9/site-packages/xarray/core/common.py", line 239, in __getattr__
    raise AttributeError(
AttributeError: 'DataArray' object has no attribute 'units'

My observations:

  • For some reason, trying to attach units to x and y via multiplication doesn't seem to do anything.
  • assign_latitude_longitude() does seem to bring in proper units for latitude and longitude.

When I get a chance, I'll look at the code in assign_latitude_longitude() to see how it is attaching units. Perhaps the same approach will successfully add meters to the x and y coordinates, and I'll get a plot.

sgdecker avatar Sep 21 '21 16:09 sgdecker

My observations:

  • For some reason, trying to attach units to x and y via multiplication doesn't seem to do anything.
  • assign_latitude_longitude() does seem to bring in proper units for latitude and longitude.

When I get a chance, I'll look at the code in assign_latitude_longitude() to see how it is attaching units. Perhaps the same approach will successfully add meters to the x and y coordinates, and I'll get a plot.

Dimension coordinates (like x and y) do not support quantity arrays yet (xref https://github.com/pydata/xarray/projects/1), so multiplying by units doesn't do anything. Instead, you need to assign the units attribute:

wind['x'].attrs['units'] = 'meters'
wind['y'].attrs['units'] = 'meters'

You'll find this pattern (units-on-attribute for coordinates, units-in-quantity-array for data) often in MetPy's accessor code. Hopefully the in-progress changes upstream will make this easier/more intuitive.

jthielen avatar Sep 21 '21 16:09 jthielen

Aha, that's the incantation I was looking for! I now have a plot. Thanks, everyone!

So the actual bug/feature request is that gdxarray should do this automatically.

sgdecker avatar Sep 21 '21 16:09 sgdecker