Visualizing Hurricane Ian Using hvPlot#

Within this post, we explore how we can use hvPlot to plot data from Hurricane Ian, which will soon make landfall in Florida.

Motivation#

By the end of this post, you will be able to recreate the following plot: Hurricane Ian Plot

Imports#

import cartopy.crs as ccrs
import geopandas as gpd
import fiona

from siphon.simplewebservice.ndbc import NDBC
import holoviews as hv
import hvplot.pandas
fiona.drvsupport.supported_drivers['libkml'] = 'rw' # enable KML support which is disabled by default
fiona.drvsupport.supported_drivers['LIBKML'] = 'rw' # enable KML support which is disabled by default

hv.extension("bokeh")

Access the Data#

We are interested in looking at data related to Hurricane Ian, which is currently off the Florida Coast. We want to plot the expected hurricane track, and the some surface observations from buoys, or floating platforms on water!

National Hurricane Center Data#

We are using a few different datasets here. Let’s start with the hurricane forecast from the National Hurricane Center, accessible from their Geographic Information Systems (GIS) webpage:

We need to select the kmz files (a Google GIS file standard for the Hurricane Ian cone and track.

The Cone#

A forecast cone is defines as the following, from the National Hurricane Center

The cone represents the probable track of the center of a tropical cyclone, and is formed by enclosing the area swept out by a set of circles (not shown) along the forecast track (at 12, 24, 36 hours, etc). The size of each circle is set so that two-thirds of historical official forecast errors over a 5-year sample fall within the circle. The circle radii defining the cones in 2022 for the Atlantic, Eastern North Pacific, and Central North Pacific basins are given in the table below.

One can also examine historical tracks to determine how often the entire 5-day path of a cyclone remains completely within the area of the cone. This is a different perspective that ignores most timing errors. For example, a storm moving very slowly but in the expected direction would still be within the area of the cone, even though the track forecast error could be very large. Based on forecasts over the previous 5 years, the entire track of the tropical cyclone can be expected to remain within the cone roughly 60-70% of the time.

The Best Track#

The Best Track Dataset is the best estimated track from the variety of possible scenarios.

Read the Data Using Geopandas#

hurricane_cone = gpd.read_file("../data/AL092022_CONE_latest.kmz")
hurricane_track = gpd.read_file("../data/AL092022_TRACK_latest.kmz")
hurricane_track.head()
Name description timestamp begin end altitudeMode tessellate extrude visibility drawOrder ... pubAdvTime TCInitLocation maxWindKnots maxWindMPH maxGustKnots maxGustMPH stormMovement minimumPressure snippet geometry
0 None \n\t \t <table> \n ... NaT NaT NaT None -1 0 -1 None ... 800 PM EDT Tue Sep 27 2022 24.4N, 83.0W 105 120 130 150 NNE 947 empty LINESTRING Z (-83.00000 24.40000 0.00000, -82....
1 None \n\t \t <table> \n ... NaT NaT NaT None -1 0 -1 None ... None None None None None None None None empty LINESTRING Z (-83.00000 24.40000 0.00000, -82....
2 None \n\t \t <table> \n ... NaT NaT NaT None -1 0 -1 None ... None None None None None None None None empty POINT Z (-83.00000 24.40000 0.00000)
3 None \n\t \t <table> \n ... NaT NaT NaT None -1 0 -1 None ... None None None None None None None None empty POINT Z (-82.90000 25.30000 0.00000)
4 None \n\t \t <table> \n ... NaT NaT NaT None -1 0 -1 None ... None None None None None None None None empty POINT Z (-82.50000 26.60000 0.00000)

5 rows × 30 columns

Our hurricane track file includes additional point data - we just need the line (the first row, so let’s filter based on the TCInitLocation

hurricane_track = hurricane_track.dropna(subset=["TCInitLocation"])
hurricane_track
Name description timestamp begin end altitudeMode tessellate extrude visibility drawOrder ... pubAdvTime TCInitLocation maxWindKnots maxWindMPH maxGustKnots maxGustMPH stormMovement minimumPressure snippet geometry
0 None \n\t \t <table> \n ... NaT NaT NaT None -1 0 -1 None ... 800 PM EDT Tue Sep 27 2022 24.4N, 83.0W 105 120 130 150 NNE 947 empty LINESTRING Z (-83.00000 24.40000 0.00000, -82....

1 rows × 30 columns

Investigate the GeoDataframes#

Let’s check out the geodataframe. Notice how it looks like a typical dataframe, with additional geometry information.

hurricane_cone
Name description timestamp begin end altitudeMode tessellate extrude visibility drawOrder ... stormType advisoryDate basin fcstpd storm atcfid advisoryNum stormNum stormName geometry
0 None None NaT NaT NaT None -1 0 -1 None ... HU 800 PM EDT Tue Sep 27 2022 AL 120 Hurricane Ian AL092022 19A 9 Ian POLYGON Z ((-83.04900 24.14015 0.00000, -83.02...

1 rows × 22 columns

We can create static matplotlib plots using the .plot() method.

hurricane_cone.plot()
hurricane_track.plot();
../../../_images/9bbaf8e2c7c7e41ff5051f5055ed4a635142c3e14c08dc51cbd13f497c1ca26e.png ../../../_images/b1fc4805f60879d2258b5d4f9537c51dc0128fc267fc64f32a84e87c61595987.png

Access NOAA Buoy Data#

The National Oceanic and Atmospheric Administation (NOAA) has a buoy dataset (from the National Data Buoy Center), which includes observations from around the world. We can access this data using siphon, a tool developed by the Unidata Program Center which makes accessing this dataset much easier.

We can use the .lastest_observations() method from the NDBC module to access the latest data.

buoy_df = NDBC.latest_observations()
buoy_df
station latitude longitude wind_direction wind_speed wind_gust wave_height dominant_wave_period average_wave_period dominant_wave_direction pressure 3hr_pressure_tendency air_temperature water_temperature dewpoint visibility water_level_above_mean time
0 13001 12.000 -23.000 336.0 4.1 5.0 NaN NaN NaN NaN 1013.3 NaN 27.6 NaN NaN NaN NaN 2022-09-28 01:00:00+00:00
1 13002 21.000 -23.000 37.0 3.5 4.8 NaN NaN NaN NaN NaN NaN 26.1 26.8 NaN NaN NaN 2022-09-28 01:00:00+00:00
2 13008 15.000 -38.000 14.0 5.7 7.0 NaN NaN NaN NaN 1012.8 NaN 27.3 28.0 NaN NaN NaN 2022-09-28 01:00:00+00:00
3 13009 8.000 -38.000 173.0 4.3 NaN NaN NaN NaN NaN NaN NaN 28.8 NaN NaN NaN NaN 2022-09-28 00:00:00+00:00
4 14043 -12.000 67.000 101.0 4.8 6.0 NaN NaN NaN NaN 1012.7 NaN 24.2 25.0 NaN NaN NaN 2022-09-28 01:00:00+00:00
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
894 WYCM6 30.326 -89.326 10.0 5.7 7.2 NaN NaN NaN NaN 1017.7 1.3 25.9 26.0 NaN NaN NaN 2022-09-28 01:00:00+00:00
895 YATA2 59.548 -139.733 NaN NaN NaN NaN NaN NaN NaN 1013.2 0.6 NaN 12.0 NaN NaN NaN 2022-09-28 01:00:00+00:00
896 YKRV2 37.251 -76.342 30.0 4.1 4.6 NaN NaN NaN NaN 1016.3 2.6 21.3 NaN NaN NaN NaN 2022-09-28 01:00:00+00:00
897 YKTV2 37.227 -76.479 360.0 2.6 3.6 NaN NaN NaN NaN 1016.2 3.1 20.0 23.5 NaN NaN NaN 2022-09-28 01:00:00+00:00
898 YRSV2 37.414 -76.712 230.0 1.0 NaN NaN NaN NaN NaN 1016.0 NaN 15.8 NaN 10.0 NaN NaN 2022-09-28 00:30:00+00:00

899 rows × 18 columns

Filter the Dataset#

We are interested in locations that have water_temperature values, so we filter using .dropna()

buoy_df = buoy_df.dropna(subset=["water_temperature"])
buoy_df.plot.scatter(x='longitude', y='latitude', c='water_temperature')
<AxesSubplot: xlabel='longitude', ylabel='latitude'>
../../../_images/0b0315752121928aa7b364afe0544a4c2f3455eb813bce9bfc62772fdd815d90.png

Plot our Data using hvPlot#

Let’s move to interactive visualization!

Plot the Hurricane Cone and Track Using hvPlot#

We can do better than just static plots - let’s create an interactive one using hvPlot!

hurricane_cone.hvplot(color='None',
                      line_width=3,
                      coastline=True,
                      xlim=(-95, -65),
                      ylim=(20, 40),
                      label='NHC Forecast Cone for Hurricane Ian',
                      projection=ccrs.PlateCarree(),
                      features=["land", "ocean"])

And the same for the best estimated track

hurricane_track_plot = hurricane_track.hvplot(line_color='Red',
                                              color="None",
                                              line_width=3,
                                              coastline=True,
                                              xlim=(-95, -65),
                                              ylim=(20, 40),
                                              label='NHC Forecast Track for Hurricane Ian',
                                              projection=ccrs.PlateCarree(),
                                              features=["land", "ocean"])
hurricane_track_plot

We still need a title though, since this does not tell us what time this is valid… this information is in the dataframe!

hurricane_cone.advisoryDate
0    800 PM EDT Tue Sep 27 2022
Name: advisoryDate, dtype: object

Combine our Forecast Cone and Track Plots#

Let’s add the titles, and combine our plots.

hurricane_track_plot = hurricane_track.hvplot(line_color='Red',
                                              color="None",
                                              line_width=3,
                                              coastline=True,
                                              xlim=(-95, -65),
                                              ylim=(20, 40),
                                              title=f'NHC Forecast Valid: {hurricane_track.pubAdvTime.values[0]}',
                                              label='NHC Forecast Track for Hurricane Ian',
                                              projection=ccrs.PlateCarree()
                                             )

hurricane_cone_plot = hurricane_cone.hvplot(color='None',
                                            line_width=3,
                                            line_color='Black',
                                            coastline=True,
                                            xlim=(-95, -65),
                                            ylim=(20, 40),
                                            title=f"NHC Forecast Valid {hurricane_cone.advisoryDate.values[0]}",
                                            label='NHC Forecast Cone for Hurricane Ian',
                                            projection=ccrs.PlateCarree(),
                                            features=["land", "ocean"])

hurricane_plot = (hurricane_cone_plot * hurricane_track_plot)
hurricane_plot

Plot the Buoy Data using hvPlot#

buoy_plot = buoy_df.hvplot.points(x='longitude',
                                  y='latitude',
                                  c='water_temperature',
                                  cmap='inferno',
                                  label='NOAA Buoy Locations',
                                  title='NOAA Buoy Water Temperature',
                                  clabel='Temperature (degC)',
                                  geo=True,
                                  coastline=True,
                                  projection=ccrs.PlateCarree(),
                                  xlim=(-95, -65),
                                  ylim=(20, 40),
                                  clim=(20, 35))
buoy_plot

Final Visualization#

Let’s put it all together! We combine our plots using the * operator.

hurricane_plot * buoy_plot

Conclusions#

Within this example, we explored visualizing data from the National Hurricane Center, and from the National Data Buoy Center. We encourage you to try this out on your own.

In the future, it would be nice to add other datasets, such as weather radar data, onto these plots as well.