3  IBTrACS

Load IBTrACS (Knapp et al. (2010)) from Postgres, and plot just to check the right stuff has been loaded.

Code
%load_ext jupyter_black
%load_ext autoreload
%autoreload 2
Code
from src.datasources import ibtracs, zma
import pandas as pd
import geopandas as gpd
from shapely.geometry import LineString
from matplotlib import pyplot as plt
Code
gdf_zma = zma.load_zma()
Code
total_bounds = gdf_zma.total_bounds
Code
total_bounds
array([-86.8,  18.5, -72.3,  24. ])
Code
df_all = ibtracs.load_ibtracs_in_bounds(*total_bounds)
Code
df_all
point_id sid valid_time latitude longitude wind_speed gust_speed pressure max_wind_radius last_closed_isobar_radius last_closed_isobar_pressure basin nature provider quadrant_radius_34 quadrant_radius_50 quadrant_radius_64 created_at
0 fdc4f638-eb3d-4044-912c-a80e2921f92f 2023152N26274 2023-06-03 18:00:00.000039 23.900000 -85.000000 25.0 NaN 1001.0 40.0 140.0 1005.0 NA DS nhc_working_bt [nan, nan, nan, nan] [nan, nan, nan, nan] [nan, nan, nan, nan] 2025-05-13 21:22:52.749544
1 8cd22c3c-b67f-4c58-947e-1375b54f0d46 2023152N26274 2023-06-03 21:00:00.000039 23.677523 -84.609840 25.0 NaN 1001.0 40.0 140.0 1005.0 NA DS None [nan, nan, nan, nan] [nan, nan, nan, nan] [nan, nan, nan, nan] 2025-05-13 21:22:52.749544
2 030c5694-3836-436d-a5ef-e5b83fa46a81 2023152N26274 2023-06-04 00:00:00.000039 23.500000 -84.099998 25.0 NaN 1002.0 40.0 140.0 1005.0 NA DS nhc_working_bt [nan, nan, nan, nan] [nan, nan, nan, nan] [nan, nan, nan, nan] 2025-05-13 21:22:52.749544
3 ebb42dc3-fd00-4381-acd4-8f4f874ad1a6 2023152N26274 2023-06-04 03:00:00.000039 23.382824 -83.555038 25.0 NaN 1002.0 35.0 140.0 1005.0 NA DS None [nan, nan, nan, nan] [nan, nan, nan, nan] [nan, nan, nan, nan] 2025-05-13 21:22:52.749544
4 037fb1c8-0fb4-4455-ac15-3687be8880d1 2023152N26274 2023-06-04 06:00:00.000039 23.299999 -83.000000 25.0 NaN 1002.0 30.0 140.0 1005.0 NA DS nhc_working_bt [nan, nan, nan, nan] [nan, nan, nan, nan] [nan, nan, nan, nan] 2025-05-13 21:22:52.749544
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
3058 4ff60e09-8b82-497c-bdf7-57fdaff92070 2022266N12294 2022-09-27 00:00:00.000039 20.799999 -83.300003 85.0 105.0 965.0 20.0 130.0 1006.0 NA TS hurdat_atl [100.0, 90.0, 60.0, 90.0] [50.0, 50.0, 20.0, 30.0] [30.0, 25.0, nan, 20.0] 2025-05-13 21:22:52.749544
3059 89a90801-a7f1-492e-8c69-75ea047d8a2a 2022266N12294 2022-09-27 06:00:00.000039 21.799999 -83.599998 100.0 120.0 956.0 15.0 200.0 1008.0 NA TS hurdat_atl [100.0, 100.0, 70.0, 90.0] [50.0, 50.0, 30.0, 40.0] [30.0, 25.0, 20.0, 20.0] 2025-05-13 21:22:52.749544
3060 4bc322f1-1600-41a0-b8a9-18b2005d9843 2022266N12294 2022-09-27 08:30:00.000066 22.200001 -83.699997 110.0 NaN 947.0 15.0 200.0 1008.0 NA TS hurdat_atl [100.0, 100.0, 70.0, 90.0] [50.0, 50.0, 30.0, 40.0] [30.0, 25.0, 20.0, 20.0] 2025-05-13 21:22:52.749544
3061 44c6b365-52e7-4945-9669-4843f6b68c81 2022266N12294 2022-09-27 12:00:00.000039 22.600000 -83.599998 100.0 135.0 963.0 15.0 270.0 1009.0 NA TS hurdat_atl [120.0, 120.0, 100.0, 120.0] [60.0, 60.0, 50.0, 60.0] [30.0, 25.0, 20.0, 20.0] 2025-05-13 21:22:52.749544
3062 f856ebc9-966b-4337-8efd-a60a998bcec3 2022266N12294 2022-09-27 18:00:00.000039 23.500000 -83.300003 105.0 130.0 951.0 15.0 270.0 1009.0 NA TS hurdat_atl [120.0, 120.0, 100.0, 120.0] [60.0, 60.0, 50.0, 60.0] [35.0, 35.0, 20.0, 20.0] 2025-05-13 21:22:52.749544

3063 rows × 18 columns

Code
df_all.dtypes
point_id                               object
sid                                    object
valid_time                     datetime64[ns]
latitude                              float64
longitude                             float64
wind_speed                            float64
gust_speed                            float64
pressure                              float64
max_wind_radius                       float64
last_closed_isobar_radius             float64
last_closed_isobar_pressure           float64
basin                                  object
nature                                 object
provider                               object
quadrant_radius_34                     object
quadrant_radius_50                     object
quadrant_radius_64                     object
created_at                     datetime64[ns]
dtype: object
Code
def df_to_track_lines(df: pd.DataFrame) -> gpd.GeoDataFrame:
    """
    Convert a DataFrame of points to a GeoDataFrame of LineStrings,
    grouped by 'sid'.

    Parameters:
        df: DataFrame with 'sid', 'latitude', 'longitude' columns

    Returns:
        GeoDataFrame with one LineString per 'sid'
    """
    # Ensure sorted order if needed (e.g., by time)
    
    df = df.sort_values(["sid"])  # optionally add 'time' or similar

    # Group and build LineStrings
    lines = (
        df.groupby("sid")
        .filter(lambda group: len(group) > 1)  # Keep only groups with more than one point
        .groupby("sid")
        .apply(
            lambda group: LineString(
                zip(group["longitude"], group["latitude"])
            )
        )
        .reset_index(name="geometry")
    )

    return gpd.GeoDataFrame(lines, geometry="geometry", crs="EPSG:4326")
Code
gdf_lines = df_to_track_lines(df_all)
/var/folders/61/cp06zhcj4y76q7rfx0qlm06c0000gn/T/ipykernel_9023/1121599445.py:21: DeprecationWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
  .apply(
Code
gdf_lines
sid geometry
0 1851228N13313 LINESTRING (-79 21.2, -72.6 18.9, -74.3 19.4, ...
1 1852232N21293 LINESTRING (-74 21.9, -74.9 22.1, -76.1 22.2, ...
2 1852264N13309 LINESTRING (-73 21.4, -73.6 21.9, -74 22.5, -7...
3 1852278N14293 LINESTRING (-83.1 18.7, -86.4 21.2, -85.5 20.4...
4 1855236N12304 LINESTRING (-72.3 19.5, -73.6 19.7)
... ... ...
410 2022154N21273 LINESTRING (-86.3 22.8, -85.1 23.9)
411 2022266N12294 LINESTRING (-82.4 18.7, -83 19.7, -83.3 20.8, ...
412 2023152N26274 LINESTRING (-85 23.9, -84.60984 23.67752, -84....
413 2023239N21274 LINESTRING (-86.1 20.8, -86.14497 21.11002, -8...
414 2023321N15278 LINESTRING (-77.84354 18.57282, -77.2 19.2)

415 rows × 2 columns

Code
fig, ax = plt.subplots()
max_lines = 100
plotted_lines = 0
for sid, row in gdf_lines.iterrows():
    plotted_lines += 1
    if plotted_lines > max_lines:
        break
    x, y = row.geometry.xy
    ax.plot(x, y, label=str(row["sid"]))  # matplotlib auto-assigns color

gdf_zma.boundary.plot(ax=ax, color="k")

Looks like all the tracks are inside the ZMA, so should be good