hurdat2py 0.3.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hurdat2py-0.3.5/LICENSE +21 -0
- hurdat2py-0.3.5/PKG-INFO +176 -0
- hurdat2py-0.3.5/README.md +162 -0
- hurdat2py-0.3.5/pyproject.toml +18 -0
- hurdat2py-0.3.5/setup.cfg +4 -0
- hurdat2py-0.3.5/setup.py +4 -0
- hurdat2py-0.3.5/src/hurdat2py/__init__.py +25 -0
- hurdat2py-0.3.5/src/hurdat2py/analytics.py +100 -0
- hurdat2py-0.3.5/src/hurdat2py/core.py +207 -0
- hurdat2py-0.3.5/src/hurdat2py/errors.py +20 -0
- hurdat2py-0.3.5/src/hurdat2py/objects.py +338 -0
- hurdat2py-0.3.5/src/hurdat2py/plot.py +370 -0
- hurdat2py-0.3.5/src/hurdat2py.egg-info/PKG-INFO +176 -0
- hurdat2py-0.3.5/src/hurdat2py.egg-info/SOURCES.txt +15 -0
- hurdat2py-0.3.5/src/hurdat2py.egg-info/dependency_links.txt +1 -0
- hurdat2py-0.3.5/src/hurdat2py.egg-info/requires.txt +4 -0
- hurdat2py-0.3.5/src/hurdat2py.egg-info/top_level.txt +1 -0
hurdat2py-0.3.5/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Andy McKeen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
hurdat2py-0.3.5/PKG-INFO
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hurdat2py
|
|
3
|
+
Version: 0.3.5
|
|
4
|
+
Summary: A custom HURDAT2 data parser.
|
|
5
|
+
Author-email: Andy McKeen <andymckeen648@gmail.com>
|
|
6
|
+
Requires-Python: >=3.7
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: matplotlib
|
|
10
|
+
Requires-Dist: cartopy
|
|
11
|
+
Requires-Dist: numpy
|
|
12
|
+
Requires-Dist: pandas
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
<div align="center">
|
|
16
|
+
<img src="https://github.com/andy-theia/hurdat2py/blob/main/hurdat2py_logo_v1.png?raw=true" width="600" alt="hurdat2py logo">
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
[](https://pypi.org/project/hurdat2py/)
|
|
20
|
+
[](https://opensource.org/licenses/MIT)
|
|
21
|
+
[](https://www.python.org/downloads/)
|
|
22
|
+
|
|
23
|
+
**hurdat2py** is a research-focused Python interface for the NOAA HURDAT2 Dataset. It automates data retrieval and parsing, giving you immediate access to clean, analysis-ready data.
|
|
24
|
+
* Note: Currently only the North Atlantic Basin is supported. See [Roadmap](#roadmap) for more info.
|
|
25
|
+
|
|
26
|
+
## Table of Contents
|
|
27
|
+
1. [Installation](#installation)
|
|
28
|
+
2. [Quick Start](#quick-start)
|
|
29
|
+
3. [API Reference](#api-reference)
|
|
30
|
+
- [The Database (Hurdat2)](#1-the-database-hurdat2)
|
|
31
|
+
- [The Storm Object](#2-the-storm-object)
|
|
32
|
+
- [The Season Object](#3-the-season-object)
|
|
33
|
+
4. [Roadmap](#roadmap)
|
|
34
|
+
5. [Changelog](#changelog)
|
|
35
|
+
6. [Attribution](#attribution--data-sources)
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install hurdat2py
|
|
43
|
+
```
|
|
44
|
+
## Quick Start
|
|
45
|
+
|
|
46
|
+
### 1. Initialize the Database
|
|
47
|
+
* You can specify a path to a local download of the Hurdat2 dataset.
|
|
48
|
+
```python
|
|
49
|
+
import hurdat2py
|
|
50
|
+
|
|
51
|
+
hd2 = hurdat2py.Hurdat2("path_to_file.txt")
|
|
52
|
+
```
|
|
53
|
+
* If no path is specified, it will automatically download the latest data from [https://www.nhc.noaa.gov/data/hurdat/](https://www.nhc.noaa.gov/data/hurdat/) (~7MB) and save it locally.
|
|
54
|
+
* Note: The cached file will be overwritten after 30 days to ensure the latest file is available.
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
import hurdat2py
|
|
58
|
+
|
|
59
|
+
hd2 = hurdat2py.Hurdat2()
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 2. Create `Storm` or `Season` objects
|
|
63
|
+
* Allows for easy access to storm or season data.
|
|
64
|
+
```python
|
|
65
|
+
# Storm obect:
|
|
66
|
+
storm = hd2['bob', 1991]
|
|
67
|
+
# or
|
|
68
|
+
storm = hd2['al031991']
|
|
69
|
+
|
|
70
|
+
# Season object:
|
|
71
|
+
season = hd2[1991]
|
|
72
|
+
```
|
|
73
|
+
* See [The Storm Object](#2-the-storm-object) or [The Season Object](#3-the-season-object) for more info.
|
|
74
|
+
|
|
75
|
+
## API Reference
|
|
76
|
+
### 1. The Database (Hurdat2)
|
|
77
|
+
The main entry point. Handles downloading, caching, and parsing the raw text data.
|
|
78
|
+
|
|
79
|
+
Method | Description | Example
|
|
80
|
+
:--- | :--- | :---
|
|
81
|
+
`hurdat2py.Hurdat2()` | Initializes the database. Downloads latest data if local cache is missing/expired. | `hd2 = hurdat2py.Hurdat2()`
|
|
82
|
+
`db['name', year]`| **Get Storm (by Name)***. Returns a `Storm` object. Case-insensitive. <br> <br> *This method will not work for storms named UNNAMED, especially before 1950. | `storm = hd2['bob', 1991]`
|
|
83
|
+
`db[atcfid]` | **Get Storm (by ID)**. Returns a `Storm` object using ATCFID. | `storm = hd2['al031991']`
|
|
84
|
+
`db[year]` | **Get Season**. Returns a `Season` object for the specified year. | `season = hd2[1991]`
|
|
85
|
+
`rank_seasons_by_ace()` | Returns a sorted list of seasons by Accumulated Cyclone Energy (`float`). | `top5 = hd2.rank_seasons_by_ace()[:5]`
|
|
86
|
+
|
|
87
|
+
### 2. The `Storm` Object
|
|
88
|
+
Represents a single tropical cyclone.
|
|
89
|
+
|
|
90
|
+
Method/Attribute | Description | Example
|
|
91
|
+
:--- | :--- | :---
|
|
92
|
+
`name` | Operational name (e.g., "Bob") (`str`). | `print(storm.name)`
|
|
93
|
+
`atcfid` | ATCF ID (e.g., "AL031991") (`str`). | `print(storm.atcfid)`
|
|
94
|
+
`year` | The year the storm formed (`int`). | `print(storm.year)`
|
|
95
|
+
`ace` | Accumulated Cyclone Energy (10^-4 kn^2) (`float`). | `print(storm.ace)`
|
|
96
|
+
`peak_wind` | Maximum sustained wind speed (knots) (`int`). | `print(storm.peak_wind)`
|
|
97
|
+
`peak_status` | Highest tropical classification the storm achieved (MH, HU, TS, SS, TD, SD) (`str`). | `print(storm.peak_status)`
|
|
98
|
+
`min_pressure` | Minimum central pressure (mb) (`int`). | `print(storm.min_pressure)`
|
|
99
|
+
`landfalls` | Number of landfalls recorded* (`int`). <br> <br>*Note: not every landfall is recorded in the Hurdat2 dataset. | `print(storm.landfalls)`
|
|
100
|
+
`lats`/`lons` | Raw list of latitude/longitude points. | `ax.plot(storm.lons, storm.lats)`
|
|
101
|
+
`duration_total` <br> `duration_tc` <br> `duration_ts` <br> `duration_hurricane` <br> `duration_major` | Duration of the system in hours, while at specified status (`float`) | `print(storm.duration_tc)`
|
|
102
|
+
`distance_total` <br> `distance_tc` <br> `distance_ts` <br> `distance_hurricane` <br> `distance_major` | Distance* the system travelled in Nautical Miles, while at specified status (`float`) <br> <br> *Note: Distances calculated using the [Haversine Formula](https://en.wikipedia.org/wiki/Haversine_formula). | `print(storm.distance_major)`
|
|
103
|
+
`info()` | Prints quick overview of the storm. | `storm.info()`
|
|
104
|
+
`stats()` | Prints statistics and detailed information about the storm. | `storm.stats()`
|
|
105
|
+
`plot()` | Plots storm track, colored by Saffir-Simpson intensity. Plot aesthetic is inspired by the tropycal package. | `storm.plot()`
|
|
106
|
+
`plot_intensity()` | Plots the storm's windspeed over time, colored by Saffir-Simpson intensity. <br> Kwargs: <br> zoom (`bool`) Crops into the intensity curve. <br> landfalls (`bool`) Plots landfall times on intensity curve. | storm.plot_intensity(zoom=True, landfalls=False)
|
|
107
|
+
`to_dataframe()` | Exports track data (date, time, lat, lon, wind, pressure) to a Pandas DataFrame. | `df = storm.to_dataframe()`
|
|
108
|
+
|
|
109
|
+
### 3. The `Season` Object
|
|
110
|
+
Represents a full year of activity.
|
|
111
|
+
|
|
112
|
+
Method/Attribute | Description | Example
|
|
113
|
+
:--- | :--- | :---
|
|
114
|
+
`year` | Returns the year of the season (`int`) | `print(season.year)`
|
|
115
|
+
`storms` | List of `Storm` objects within the season. | `print(season.storms)`
|
|
116
|
+
`total_storms` | Number of storms in the season (`int`). Effectively `len(season.storms)` | `print(season_total_storms)`
|
|
117
|
+
`tropical_storms` | Number of tropical storms in the season (`int`). | `print(season.tropical_storms)`
|
|
118
|
+
`hurricanes` | Number of hurricanes in the season (`int`). | `print(season.hurricanes)`
|
|
119
|
+
`major_hurricanes` | Number of major hurricanes in the season (`int`). | `print(season.major_hurricanes)`
|
|
120
|
+
`ace` | Total Accumulated Cyclone Energy for the season (`float`). | `print(season.ace)`
|
|
121
|
+
`stats()` | Prints statistics and detailed information about the season. | `season.stats()`
|
|
122
|
+
`plot()` | Plots all storm tracks for the season, colored by Saffir-Simpson intensity. Plot aesthetic is inspired by the tropycal package. <br> Kwargs: <br> labels (`bool`) Adds labels for each storm track. | `season.plot(labels=True)`
|
|
123
|
+
`to_dataframe()` | Exports track data (date, time, lat, lon, wind, pressure) for all storms in the season to a Pandas DataFrame. | `df = season.to_dataframe()`
|
|
124
|
+
|
|
125
|
+
## Roadmap
|
|
126
|
+
### Future planned updates:
|
|
127
|
+
* **Northeast Pacific Support**: Add support for the NEPAC Hurdat2 dataset.
|
|
128
|
+
* Expected: Spring 2026
|
|
129
|
+
* **Statistics Improvements**: Implement new statistics functionality for `Storm` and `Season` objects.
|
|
130
|
+
* Expected: Summer 2026
|
|
131
|
+
* **Improved Plotting Functionality**: Add/improve plotting functions.
|
|
132
|
+
* Expected: Summer 2026
|
|
133
|
+
* **Wind Radius Implementation**: Add support for and methods utilizing wind radius data included with modern records in the Hurdat2 dataset.
|
|
134
|
+
* Expected: Summer 2027
|
|
135
|
+
|
|
136
|
+
## Changelog
|
|
137
|
+
### v0.3.5 (2026-01-22)
|
|
138
|
+
* Documentation: Add logo to README.
|
|
139
|
+
|
|
140
|
+
### v0.3.4 (2026-01-15)
|
|
141
|
+
* Documentation: Minor updates to README.
|
|
142
|
+
* Launch: Initial release on [GitHub](https://github.com/andy-theia/hurdat2py).
|
|
143
|
+
|
|
144
|
+
### v0.3.3 (2026-01-15)
|
|
145
|
+
* Documentation: Major overhaul of README to include API reference tables and additional information.
|
|
146
|
+
|
|
147
|
+
### v0.3.2 (2026-01-12)
|
|
148
|
+
* Documentation: Updates to README.
|
|
149
|
+
|
|
150
|
+
### v0.3.1 (2026-01-12)
|
|
151
|
+
* Launch: Initial release on [PyPI](https://pypi.org/project/hurdat2py/).
|
|
152
|
+
* Structure: Package structure overhaul for organization.
|
|
153
|
+
|
|
154
|
+
## Attribution & Data Sources
|
|
155
|
+
### **Data**
|
|
156
|
+
* This package processes data from the **National Hurricane Center (NHC) [HURDAT2 Database](https://www.nhc.noaa.gov/data/hurdat/)**.
|
|
157
|
+
* Landsea, C. W. and J. L. Franklin, 2013: Atlantic Hurricane Database Uncertainty and Presentation of a New Database Format. Mon. Wea. Rev., 141, 3576-3592.
|
|
158
|
+
|
|
159
|
+
### Acknowledgements
|
|
160
|
+
* **Inspiration**: This work was inspired by the great [hurdat2parser](https://pypi.org/project/hurdat2parser/) package. There are many technical capabilities of hurdat2parser that we do not seek to replicate. We recommend you choose whichever package best suites your needs.
|
|
161
|
+
|
|
162
|
+
* **Plotting Style**: The map visualization aesthetic in this package was inspired by the excellent [tropycal](https://tropycal.github.io/tropycal/index.html) package. While `hurdat2py` is a standalone implementation, we aimed to match their clear, publication-ready visual style.
|
|
163
|
+
|
|
164
|
+
## License
|
|
165
|
+
MIT License. See LICENSE file for details.
|
|
166
|
+
|
|
167
|
+
## Disclaimer
|
|
168
|
+
`hurdat2py` is maintained for personal research and is not an official NOAA product.
|
|
169
|
+
|
|
170
|
+
Python® and the Python logo are registered trademarks of the Python Software Foundation. `hurdat2py` is an independent open-source project and is not affiliated with or endorsed by the Python Software Foundation.
|
|
171
|
+
|
|
172
|
+
## Copyright
|
|
173
|
+
**hurdat2py** <br>
|
|
174
|
+
Copyright © 2026 Andy McKeen <br>
|
|
175
|
+
License: MIT <br>
|
|
176
|
+
GitHub: [https://github.com/andy-theia/hurdat2py](https://github.com/andy-theia/hurdat2py)
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="https://github.com/andy-theia/hurdat2py/blob/main/hurdat2py_logo_v1.png?raw=true" width="600" alt="hurdat2py logo">
|
|
3
|
+
</div>
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/hurdat2py/)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://www.python.org/downloads/)
|
|
8
|
+
|
|
9
|
+
**hurdat2py** is a research-focused Python interface for the NOAA HURDAT2 Dataset. It automates data retrieval and parsing, giving you immediate access to clean, analysis-ready data.
|
|
10
|
+
* Note: Currently only the North Atlantic Basin is supported. See [Roadmap](#roadmap) for more info.
|
|
11
|
+
|
|
12
|
+
## Table of Contents
|
|
13
|
+
1. [Installation](#installation)
|
|
14
|
+
2. [Quick Start](#quick-start)
|
|
15
|
+
3. [API Reference](#api-reference)
|
|
16
|
+
- [The Database (Hurdat2)](#1-the-database-hurdat2)
|
|
17
|
+
- [The Storm Object](#2-the-storm-object)
|
|
18
|
+
- [The Season Object](#3-the-season-object)
|
|
19
|
+
4. [Roadmap](#roadmap)
|
|
20
|
+
5. [Changelog](#changelog)
|
|
21
|
+
6. [Attribution](#attribution--data-sources)
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install hurdat2py
|
|
29
|
+
```
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
### 1. Initialize the Database
|
|
33
|
+
* You can specify a path to a local download of the Hurdat2 dataset.
|
|
34
|
+
```python
|
|
35
|
+
import hurdat2py
|
|
36
|
+
|
|
37
|
+
hd2 = hurdat2py.Hurdat2("path_to_file.txt")
|
|
38
|
+
```
|
|
39
|
+
* If no path is specified, it will automatically download the latest data from [https://www.nhc.noaa.gov/data/hurdat/](https://www.nhc.noaa.gov/data/hurdat/) (~7MB) and save it locally.
|
|
40
|
+
* Note: The cached file will be overwritten after 30 days to ensure the latest file is available.
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
import hurdat2py
|
|
44
|
+
|
|
45
|
+
hd2 = hurdat2py.Hurdat2()
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 2. Create `Storm` or `Season` objects
|
|
49
|
+
* Allows for easy access to storm or season data.
|
|
50
|
+
```python
|
|
51
|
+
# Storm obect:
|
|
52
|
+
storm = hd2['bob', 1991]
|
|
53
|
+
# or
|
|
54
|
+
storm = hd2['al031991']
|
|
55
|
+
|
|
56
|
+
# Season object:
|
|
57
|
+
season = hd2[1991]
|
|
58
|
+
```
|
|
59
|
+
* See [The Storm Object](#2-the-storm-object) or [The Season Object](#3-the-season-object) for more info.
|
|
60
|
+
|
|
61
|
+
## API Reference
|
|
62
|
+
### 1. The Database (Hurdat2)
|
|
63
|
+
The main entry point. Handles downloading, caching, and parsing the raw text data.
|
|
64
|
+
|
|
65
|
+
Method | Description | Example
|
|
66
|
+
:--- | :--- | :---
|
|
67
|
+
`hurdat2py.Hurdat2()` | Initializes the database. Downloads latest data if local cache is missing/expired. | `hd2 = hurdat2py.Hurdat2()`
|
|
68
|
+
`db['name', year]`| **Get Storm (by Name)***. Returns a `Storm` object. Case-insensitive. <br> <br> *This method will not work for storms named UNNAMED, especially before 1950. | `storm = hd2['bob', 1991]`
|
|
69
|
+
`db[atcfid]` | **Get Storm (by ID)**. Returns a `Storm` object using ATCFID. | `storm = hd2['al031991']`
|
|
70
|
+
`db[year]` | **Get Season**. Returns a `Season` object for the specified year. | `season = hd2[1991]`
|
|
71
|
+
`rank_seasons_by_ace()` | Returns a sorted list of seasons by Accumulated Cyclone Energy (`float`). | `top5 = hd2.rank_seasons_by_ace()[:5]`
|
|
72
|
+
|
|
73
|
+
### 2. The `Storm` Object
|
|
74
|
+
Represents a single tropical cyclone.
|
|
75
|
+
|
|
76
|
+
Method/Attribute | Description | Example
|
|
77
|
+
:--- | :--- | :---
|
|
78
|
+
`name` | Operational name (e.g., "Bob") (`str`). | `print(storm.name)`
|
|
79
|
+
`atcfid` | ATCF ID (e.g., "AL031991") (`str`). | `print(storm.atcfid)`
|
|
80
|
+
`year` | The year the storm formed (`int`). | `print(storm.year)`
|
|
81
|
+
`ace` | Accumulated Cyclone Energy (10^-4 kn^2) (`float`). | `print(storm.ace)`
|
|
82
|
+
`peak_wind` | Maximum sustained wind speed (knots) (`int`). | `print(storm.peak_wind)`
|
|
83
|
+
`peak_status` | Highest tropical classification the storm achieved (MH, HU, TS, SS, TD, SD) (`str`). | `print(storm.peak_status)`
|
|
84
|
+
`min_pressure` | Minimum central pressure (mb) (`int`). | `print(storm.min_pressure)`
|
|
85
|
+
`landfalls` | Number of landfalls recorded* (`int`). <br> <br>*Note: not every landfall is recorded in the Hurdat2 dataset. | `print(storm.landfalls)`
|
|
86
|
+
`lats`/`lons` | Raw list of latitude/longitude points. | `ax.plot(storm.lons, storm.lats)`
|
|
87
|
+
`duration_total` <br> `duration_tc` <br> `duration_ts` <br> `duration_hurricane` <br> `duration_major` | Duration of the system in hours, while at specified status (`float`) | `print(storm.duration_tc)`
|
|
88
|
+
`distance_total` <br> `distance_tc` <br> `distance_ts` <br> `distance_hurricane` <br> `distance_major` | Distance* the system travelled in Nautical Miles, while at specified status (`float`) <br> <br> *Note: Distances calculated using the [Haversine Formula](https://en.wikipedia.org/wiki/Haversine_formula). | `print(storm.distance_major)`
|
|
89
|
+
`info()` | Prints quick overview of the storm. | `storm.info()`
|
|
90
|
+
`stats()` | Prints statistics and detailed information about the storm. | `storm.stats()`
|
|
91
|
+
`plot()` | Plots storm track, colored by Saffir-Simpson intensity. Plot aesthetic is inspired by the tropycal package. | `storm.plot()`
|
|
92
|
+
`plot_intensity()` | Plots the storm's windspeed over time, colored by Saffir-Simpson intensity. <br> Kwargs: <br> zoom (`bool`) Crops into the intensity curve. <br> landfalls (`bool`) Plots landfall times on intensity curve. | storm.plot_intensity(zoom=True, landfalls=False)
|
|
93
|
+
`to_dataframe()` | Exports track data (date, time, lat, lon, wind, pressure) to a Pandas DataFrame. | `df = storm.to_dataframe()`
|
|
94
|
+
|
|
95
|
+
### 3. The `Season` Object
|
|
96
|
+
Represents a full year of activity.
|
|
97
|
+
|
|
98
|
+
Method/Attribute | Description | Example
|
|
99
|
+
:--- | :--- | :---
|
|
100
|
+
`year` | Returns the year of the season (`int`) | `print(season.year)`
|
|
101
|
+
`storms` | List of `Storm` objects within the season. | `print(season.storms)`
|
|
102
|
+
`total_storms` | Number of storms in the season (`int`). Effectively `len(season.storms)` | `print(season_total_storms)`
|
|
103
|
+
`tropical_storms` | Number of tropical storms in the season (`int`). | `print(season.tropical_storms)`
|
|
104
|
+
`hurricanes` | Number of hurricanes in the season (`int`). | `print(season.hurricanes)`
|
|
105
|
+
`major_hurricanes` | Number of major hurricanes in the season (`int`). | `print(season.major_hurricanes)`
|
|
106
|
+
`ace` | Total Accumulated Cyclone Energy for the season (`float`). | `print(season.ace)`
|
|
107
|
+
`stats()` | Prints statistics and detailed information about the season. | `season.stats()`
|
|
108
|
+
`plot()` | Plots all storm tracks for the season, colored by Saffir-Simpson intensity. Plot aesthetic is inspired by the tropycal package. <br> Kwargs: <br> labels (`bool`) Adds labels for each storm track. | `season.plot(labels=True)`
|
|
109
|
+
`to_dataframe()` | Exports track data (date, time, lat, lon, wind, pressure) for all storms in the season to a Pandas DataFrame. | `df = season.to_dataframe()`
|
|
110
|
+
|
|
111
|
+
## Roadmap
|
|
112
|
+
### Future planned updates:
|
|
113
|
+
* **Northeast Pacific Support**: Add support for the NEPAC Hurdat2 dataset.
|
|
114
|
+
* Expected: Spring 2026
|
|
115
|
+
* **Statistics Improvements**: Implement new statistics functionality for `Storm` and `Season` objects.
|
|
116
|
+
* Expected: Summer 2026
|
|
117
|
+
* **Improved Plotting Functionality**: Add/improve plotting functions.
|
|
118
|
+
* Expected: Summer 2026
|
|
119
|
+
* **Wind Radius Implementation**: Add support for and methods utilizing wind radius data included with modern records in the Hurdat2 dataset.
|
|
120
|
+
* Expected: Summer 2027
|
|
121
|
+
|
|
122
|
+
## Changelog
|
|
123
|
+
### v0.3.5 (2026-01-22)
|
|
124
|
+
* Documentation: Add logo to README.
|
|
125
|
+
|
|
126
|
+
### v0.3.4 (2026-01-15)
|
|
127
|
+
* Documentation: Minor updates to README.
|
|
128
|
+
* Launch: Initial release on [GitHub](https://github.com/andy-theia/hurdat2py).
|
|
129
|
+
|
|
130
|
+
### v0.3.3 (2026-01-15)
|
|
131
|
+
* Documentation: Major overhaul of README to include API reference tables and additional information.
|
|
132
|
+
|
|
133
|
+
### v0.3.2 (2026-01-12)
|
|
134
|
+
* Documentation: Updates to README.
|
|
135
|
+
|
|
136
|
+
### v0.3.1 (2026-01-12)
|
|
137
|
+
* Launch: Initial release on [PyPI](https://pypi.org/project/hurdat2py/).
|
|
138
|
+
* Structure: Package structure overhaul for organization.
|
|
139
|
+
|
|
140
|
+
## Attribution & Data Sources
|
|
141
|
+
### **Data**
|
|
142
|
+
* This package processes data from the **National Hurricane Center (NHC) [HURDAT2 Database](https://www.nhc.noaa.gov/data/hurdat/)**.
|
|
143
|
+
* Landsea, C. W. and J. L. Franklin, 2013: Atlantic Hurricane Database Uncertainty and Presentation of a New Database Format. Mon. Wea. Rev., 141, 3576-3592.
|
|
144
|
+
|
|
145
|
+
### Acknowledgements
|
|
146
|
+
* **Inspiration**: This work was inspired by the great [hurdat2parser](https://pypi.org/project/hurdat2parser/) package. There are many technical capabilities of hurdat2parser that we do not seek to replicate. We recommend you choose whichever package best suites your needs.
|
|
147
|
+
|
|
148
|
+
* **Plotting Style**: The map visualization aesthetic in this package was inspired by the excellent [tropycal](https://tropycal.github.io/tropycal/index.html) package. While `hurdat2py` is a standalone implementation, we aimed to match their clear, publication-ready visual style.
|
|
149
|
+
|
|
150
|
+
## License
|
|
151
|
+
MIT License. See LICENSE file for details.
|
|
152
|
+
|
|
153
|
+
## Disclaimer
|
|
154
|
+
`hurdat2py` is maintained for personal research and is not an official NOAA product.
|
|
155
|
+
|
|
156
|
+
Python® and the Python logo are registered trademarks of the Python Software Foundation. `hurdat2py` is an independent open-source project and is not affiliated with or endorsed by the Python Software Foundation.
|
|
157
|
+
|
|
158
|
+
## Copyright
|
|
159
|
+
**hurdat2py** <br>
|
|
160
|
+
Copyright © 2026 Andy McKeen <br>
|
|
161
|
+
License: MIT <br>
|
|
162
|
+
GitHub: [https://github.com/andy-theia/hurdat2py](https://github.com/andy-theia/hurdat2py)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "hurdat2py"
|
|
7
|
+
version = "0.3.5"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Andy McKeen", email="andymckeen648@gmail.com" },
|
|
10
|
+
]
|
|
11
|
+
description = "A custom HURDAT2 data parser."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.7"
|
|
14
|
+
dependencies = [
|
|
15
|
+
"matplotlib",
|
|
16
|
+
"cartopy",
|
|
17
|
+
"numpy",
|
|
18
|
+
"pandas"]
|
hurdat2py-0.3.5/setup.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# hurdat2py/__init__.py
|
|
2
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
3
|
+
|
|
4
|
+
# Expose the main entry point
|
|
5
|
+
from .core import Hurdat2
|
|
6
|
+
|
|
7
|
+
# Expose the data objects
|
|
8
|
+
from .objects import TropicalCyclone, Season, Hurdat2Entry
|
|
9
|
+
|
|
10
|
+
# Expose errors
|
|
11
|
+
from .errors import (
|
|
12
|
+
Hurdat2Error,
|
|
13
|
+
StormNotFoundError,
|
|
14
|
+
DataDownloadError,
|
|
15
|
+
DataParseError
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
# Fetch version dynamically from pyproject.toml metadata
|
|
19
|
+
try:
|
|
20
|
+
__version__ = version("hurdat2py")
|
|
21
|
+
except PackageNotFoundError:
|
|
22
|
+
# If the package is not installed (e.g. just running local script), avoid crashing
|
|
23
|
+
__version__ = "unknown"
|
|
24
|
+
|
|
25
|
+
__author__ = "Andy McKeen"
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import datetime
|
|
3
|
+
|
|
4
|
+
# -----------------------------------------------------------------------------
|
|
5
|
+
# ACE Calculation
|
|
6
|
+
# -----------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
def calculate_ace(entries):
|
|
9
|
+
"""
|
|
10
|
+
Calculates Accumulated Cyclone Energy (ACE).
|
|
11
|
+
Formula: Sum of (Wind_Speed / 10000)^2 for every 6 hours (00, 06, 12, 18Z)
|
|
12
|
+
where storm status is Tropical/Subtropical and wind >= 34kts.
|
|
13
|
+
"""
|
|
14
|
+
ace_sum = 0
|
|
15
|
+
for entry in entries:
|
|
16
|
+
# Check for 6-hour synoptic times
|
|
17
|
+
if entry.entrytime.hour in [0, 6, 12, 18]:
|
|
18
|
+
# Check for Tropical/Subtropical status
|
|
19
|
+
if entry.status in ['SS', 'TS', 'HU', 'SD']:
|
|
20
|
+
# Check for TS intensity
|
|
21
|
+
if entry.wind >= 34:
|
|
22
|
+
ace_sum += (entry.wind ** 2)
|
|
23
|
+
|
|
24
|
+
return ace_sum / 10000
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# -----------------------------------------------------------------------------
|
|
28
|
+
# Duration Calculation
|
|
29
|
+
# -----------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
def calculate_duration_days(entries, min_wind=0):
|
|
32
|
+
"""
|
|
33
|
+
Calculates the duration (in days) where the storm maintained
|
|
34
|
+
winds >= min_wind.
|
|
35
|
+
"""
|
|
36
|
+
duration = datetime.timedelta(0)
|
|
37
|
+
|
|
38
|
+
# Iterate through segments
|
|
39
|
+
for i in range(1, len(entries)):
|
|
40
|
+
prev_entry = entries[i - 1]
|
|
41
|
+
curr_entry = entries[i]
|
|
42
|
+
|
|
43
|
+
# We attribute the duration of the segment to the status of the START point
|
|
44
|
+
# We check is_TC() to ensure we aren't counting Extratropical duration
|
|
45
|
+
if prev_entry.is_TC() and prev_entry.wind >= min_wind:
|
|
46
|
+
duration += curr_entry.entrytime - prev_entry.entrytime
|
|
47
|
+
|
|
48
|
+
return duration.total_seconds() / (24 * 3600)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# -----------------------------------------------------------------------------
|
|
52
|
+
# Distance Calculation (Haversine)
|
|
53
|
+
# -----------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
def calculate_track_distance(entries, min_wind=0):
|
|
56
|
+
"""
|
|
57
|
+
Calculates the track distance (in nautical miles) where the storm
|
|
58
|
+
maintained winds >= min_wind.
|
|
59
|
+
"""
|
|
60
|
+
total_distance = 0
|
|
61
|
+
if len(entries) < 2:
|
|
62
|
+
return 0
|
|
63
|
+
|
|
64
|
+
for i in range(1, len(entries)):
|
|
65
|
+
prev_entry = entries[i - 1]
|
|
66
|
+
curr_entry = entries[i]
|
|
67
|
+
|
|
68
|
+
include_segment = False
|
|
69
|
+
|
|
70
|
+
# Logic: If looking for specific intensity, check the previous entry
|
|
71
|
+
if min_wind > 0:
|
|
72
|
+
if prev_entry.is_TC() and prev_entry.wind >= min_wind:
|
|
73
|
+
include_segment = True
|
|
74
|
+
else:
|
|
75
|
+
# If no min_wind (total distance), include everything
|
|
76
|
+
include_segment = True
|
|
77
|
+
|
|
78
|
+
if include_segment:
|
|
79
|
+
dist = haversine(prev_entry.latitude, prev_entry.longitude,
|
|
80
|
+
curr_entry.latitude, curr_entry.longitude)
|
|
81
|
+
total_distance += dist
|
|
82
|
+
|
|
83
|
+
return total_distance
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def haversine(lat1, lon1, lat2, lon2):
|
|
87
|
+
"""
|
|
88
|
+
Calculates the distance between two geographical points (nautical miles).
|
|
89
|
+
"""
|
|
90
|
+
R = 3440.1 # Radius of Earth in Nautical Miles
|
|
91
|
+
|
|
92
|
+
lat1, lon1, lat2, lon2 = map(math.radians, [lat1, lon1, lat2, lon2])
|
|
93
|
+
|
|
94
|
+
dlat = lat2 - lat1
|
|
95
|
+
dlon = lon2 - lon1
|
|
96
|
+
|
|
97
|
+
a = math.sin(dlat/2)**2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon/2)**2
|
|
98
|
+
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))
|
|
99
|
+
|
|
100
|
+
return R * c
|