Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

This example replicates the traditional DIFAX images for upper-level observations.

By: Kevin Goebbert

Observation data comes from Iowa State Archive, accessed through the Siphon package. Contour data comes from the GFS 0.5 degree analysis. Classic upper-level data of Geopotential Height and Temperature are plotted.

Observation Data

Set a date and time for upper-air observations (should only be 00 or 12 UTC for the hour).

Request all data from Iowa State using the Siphon package. The result is a pandas DataFrame containing all of the sounding data from all available stations.

Subset Observational Data

From the request above will give all levels from all radisonde sites available through the service. For plotting a pressure surface map there is only need to have the data from that level. Below the data is subset and a few parameters set based on the level chosen. Additionally, the station information is obtained and latitude and longitude data is added to the DataFrame.

Downloading file 'sfstns.tbl' from 'https://github.com/Unidata/MetPy/raw/v1.7.1/staticdata/sfstns.tbl' to '/home/runner/.cache/metpy/v1.7.1'.
Downloading file 'master.txt' from 'https://github.com/Unidata/MetPy/raw/v1.7.1/staticdata/master.txt' to '/home/runner/.cache/metpy/v1.7.1'.
Downloading file 'stations.txt' from 'https://github.com/Unidata/MetPy/raw/v1.7.1/staticdata/stations.txt' to '/home/runner/.cache/metpy/v1.7.1'.
Downloading file 'airport-codes.csv' from 'https://github.com/Unidata/MetPy/raw/v1.7.1/staticdata/airport-codes.csv' to '/home/runner/.cache/metpy/v1.7.1'.

Gridded Data

Obtain GFS gridded output for contour plotting. Specifically, geopotential height and temperature data for the given level and subset for over North America. Data are smoothed for aesthetic reasons.

New in MetPy v1.7, we can use metpy.calc.find_peaks to find local maxima and minima from our data. We can specify a higher IQR Ratio to see fewer, stronger peaks. Then we can smooth our data for seeing larger-scale patterns in the final contours.

Create DIFAX Replication

Plot the observational data and contours on a Lambert Conformal map and add features that resemble the historic DIFAX maps.

/home/runner/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/cartopy/io/__init__.py:263: DownloadWarning: Downloading: https://naturalearth.s3.amazonaws.com/50m_physical/ne_50m_land.zip
  warnings.warn(f'Downloading: {url}', DownloadWarning)
Error in callback <function _draw_all_if_interactive at 0x7f1a842c35e0> (for post_execute), with arguments args (),kwargs {}:
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/matplotlib/pyplot.py:300, in _draw_all_if_interactive()
    298 def _draw_all_if_interactive() -> None:
    299     if matplotlib.is_interactive():
--> 300         draw_all()

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/matplotlib/_pylab_helpers.py:133, in Gcf.draw_all(cls, force)
    131 for manager in cls.get_all_fig_managers():
    132     if force or manager.canvas.figure.stale:
--> 133         manager.canvas.draw_idle()

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/matplotlib/backend_bases.py:1989, in FigureCanvasBase.draw_idle(self, *args, **kwargs)
   1987 if not self._is_idle_drawing:
   1988     with self._idle_draw_cntx():
-> 1989         self.draw(*args, **kwargs)

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:438, in FigureCanvasAgg.draw(self)
    435 # Acquire a lock on the shared font cache.
    436 with (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar
    437       else nullcontext()):
--> 438     self.figure.draw(self.renderer)
    439     # A GUI class may be need to update a window using this draw, so
    440     # don't forget to call the superclass.
    441     super().draw()

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/matplotlib/artist.py:94, in _finalize_rasterization.<locals>.draw_wrapper(artist, renderer, *args, **kwargs)
     92 @wraps(draw)
     93 def draw_wrapper(artist, renderer, *args, **kwargs):
---> 94     result = draw(artist, renderer, *args, **kwargs)
     95     if renderer._rasterizing:
     96         renderer.stop_rasterizing()

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/matplotlib/figure.py:3282, in Figure.draw(self, renderer)
   3279             # ValueError can occur when resizing a window.
   3281     self.patch.draw(renderer)
-> 3282     mimage._draw_list_compositing_images(
   3283         renderer, self, artists, self.suppressComposite)
   3285     renderer.close_group('figure')
   3286 finally:

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/matplotlib/image.py:133, in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
    131 if not_composite or not has_images:
    132     for a in artists:
--> 133         a.draw(renderer)
    134 else:
    135     # Composite any adjacent images together
    136     image_group = []

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/cartopy/mpl/geoaxes.py:520, in GeoAxes.draw(self, renderer, **kwargs)
    515         self.imshow(img, extent=extent, origin=origin,
    516                     transform=factory.crs, *factory_args[1:],
    517                     **factory_kwargs)
    518 self._done_img_factory = True
--> 520 return super().draw(renderer=renderer, **kwargs)

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/matplotlib/axes/_base.py:3372, in _AxesBase.draw(self, renderer)
   3369 if artists_rasterized:
   3370     _draw_rasterized(self.get_figure(root=True), artists_rasterized, renderer)
-> 3372 mimage._draw_list_compositing_images(
   3373     renderer, self, artists, self.get_figure(root=True).suppressComposite)
   3375 renderer.close_group('axes')
   3376 self.stale = False

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/matplotlib/image.py:133, in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
    131 if not_composite or not has_images:
    132     for a in artists:
--> 133         a.draw(renderer)
    134 else:
    135     # Composite any adjacent images together
    136     image_group = []

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/metpy/plots/text.py:233, in TextCollection.draw(self, renderer)
    230 _, info, _ = self._get_layout(renderer)
    231 self._text = ''
--> 233 for line, _, x, y in info:
    235     mtext = self if len(info) == 1 else None
    236     x = x + posx

ValueError: not enough values to unpack (expected 4, got 3)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/matplotlib/backend_bases.py:2257, in FigureCanvasBase.print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
   2254     # we do this instead of `self.figure.draw_without_rendering`
   2255     # so that we can inject the orientation
   2256     with getattr(renderer, "_draw_disabled", nullcontext)():
-> 2257         self.figure.draw(renderer)
   2258 else:
   2259     renderer = None

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/matplotlib/artist.py:94, in _finalize_rasterization.<locals>.draw_wrapper(artist, renderer, *args, **kwargs)
     92 @wraps(draw)
     93 def draw_wrapper(artist, renderer, *args, **kwargs):
---> 94     result = draw(artist, renderer, *args, **kwargs)
     95     if renderer._rasterizing:
     96         renderer.stop_rasterizing()

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/matplotlib/figure.py:3282, in Figure.draw(self, renderer)
   3279             # ValueError can occur when resizing a window.
   3281     self.patch.draw(renderer)
-> 3282     mimage._draw_list_compositing_images(
   3283         renderer, self, artists, self.suppressComposite)
   3285     renderer.close_group('figure')
   3286 finally:

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/matplotlib/image.py:133, in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
    131 if not_composite or not has_images:
    132     for a in artists:
--> 133         a.draw(renderer)
    134 else:
    135     # Composite any adjacent images together
    136     image_group = []

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/cartopy/mpl/geoaxes.py:520, in GeoAxes.draw(self, renderer, **kwargs)
    515         self.imshow(img, extent=extent, origin=origin,
    516                     transform=factory.crs, *factory_args[1:],
    517                     **factory_kwargs)
    518 self._done_img_factory = True
--> 520 return super().draw(renderer=renderer, **kwargs)

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/matplotlib/axes/_base.py:3372, in _AxesBase.draw(self, renderer)
   3369 if artists_rasterized:
   3370     _draw_rasterized(self.get_figure(root=True), artists_rasterized, renderer)
-> 3372 mimage._draw_list_compositing_images(
   3373     renderer, self, artists, self.get_figure(root=True).suppressComposite)
   3375 renderer.close_group('axes')
   3376 self.stale = False

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/matplotlib/image.py:133, in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
    131 if not_composite or not has_images:
    132     for a in artists:
--> 133         a.draw(renderer)
    134 else:
    135     # Composite any adjacent images together
    136     image_group = []

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/metpy/plots/text.py:233, in TextCollection.draw(self, renderer)
    230 _, info, _ = self._get_layout(renderer)
    231 self._text = ''
--> 233 for line, _, x, y in info:
    235     mtext = self if len(info) == 1 else None
    236     x = x + posx

ValueError: not enough values to unpack (expected 4, got 3)
<Figure size 1700x1500 with 1 Axes>