From c21c80f6ac6b73a2e11f9afaec16e844c336e207 Mon Sep 17 00:00:00 2001 From: Zachary Burnett Date: Mon, 11 Apr 2022 11:47:58 -0400 Subject: [PATCH] fill missing values in MRD and MSLP (#31) * fill missing values in MRD and MSLP * use column lengths off of ATCF columns * update reference files * generalize `.forecasts` to `.tracks` instead, and allow explicit filtering by multiple advisors * test distances * restructure isotachs and wind swaths * restructure isotachs and wind swaths * restore column order to correct ATCF order * restore column order to correct ATCF order * update documentation * catch case where data has length of 1 * skip test that runs forever Co-authored-by: zacharyburnett --- README.md | 198 +- stormevents/nhc/atcf.py | 30 +- stormevents/nhc/storms.py | 66 +- stormevents/nhc/track.py | 715 +- stormevents/stormevent.py | 53 +- stormevents/usgs/events.py | 77 +- .../test_coops_product_within_region/data.nc | Bin 20042 -> 20042 bytes .../stations.csv | 4 +- .../data/reference/test_nhc_storms/storms.csv | 233 +- .../florence2018_water_levels.nc | Bin 653562 -> 204426 bytes .../florence2018.fort.22 | 340 +- .../test_storm_event_track/ida2021.fort.22 | 340 +- .../test_vortex_track/florence2018.fort.22 | 237 +- .../test_vortex_track/harvey2017.fort.22 | 416 +- .../test_vortex_track/ike2008.fort.22 | 285 +- .../test_vortex_track/irene2011.fort.22 | 275 +- .../test_vortex_track/irma2017.fort.22 | 540 +- .../test_vortex_track/isabel2003.fort.22 | 132 +- .../test_vortex_track/maria2017.fort.22 | 475 +- .../test_vortex_track/michael2018.fort.22 | 92 +- .../test_vortex_track/sandy2012.fort.22 | 237 +- .../a-deck_CARQ.22 | 410 +- .../a-deck_HMON.22 | 3679 ++- .../a-deck_HWRF.22 | 2028 +- .../a-deck_OFCL.22 | 1001 +- .../b-deck_BEST.22 | 132 +- .../florence2018_fort.22 | 340 +- .../irma2017_fort.22 | 164 +- .../test_vortex_track_no_internet/vortex_1.22 | 140 +- .../test_vortex_track_no_internet/vortex_2.22 | 140 +- .../test_vortex_track_no_internet/vortex_3.22 | 140 +- .../irma2017_fort.22 | 346 +- .../florence2018_OFCL.dat | 2616 +- .../florence2018_OFCL.fort.22 | 2624 +- .../florence2018_all.dat | 20194 ++++++++------- .../florence2018_all.fort.22 | 20324 ++++++++-------- .../florence2018_best.fort.22 | 340 +- tests/test_atcf.py | 10 +- tests/test_nhc.py | 47 +- tests/test_stormevent.py | 29 +- 40 files changed, 30321 insertions(+), 29128 deletions(-) diff --git a/README.md b/README.md index 4ef8b24..259ee14 100644 --- a/README.md +++ b/README.md @@ -44,20 +44,20 @@ nhc_storms() ``` name class year basin number source start_date end_date -nhc_code +nhc_code AL021851 UNNAMED HU 1851 AL 2 ARCHIVE 1851-07-05 12:00:00 1851-07-05 12:00:00 AL031851 UNNAMED TS 1851 AL 3 ARCHIVE 1851-07-10 12:00:00 1851-07-10 12:00:00 AL041851 UNNAMED HU 1851 AL 4 ARCHIVE 1851-08-16 00:00:00 1851-08-27 18:00:00 AL051851 UNNAMED TS 1851 AL 5 ARCHIVE 1851-09-13 00:00:00 1851-09-16 18:00:00 AL061851 UNNAMED TS 1851 AL 6 ARCHIVE 1851-10-16 00:00:00 1851-10-19 18:00:00 ... ... ... ... ... ... ... ... ... +CP902021 INVEST LO 2021 CP 90 METWATCH 2021-07-24 12:00:00 NaT +CP912021 INVEST DB 2021 CP 91 METWATCH 2021-08-07 18:00:00 NaT EP922021 INVEST DB 2021 EP 92 METWATCH 2021-06-05 06:00:00 NaT -AL952021 INVEST DB 2021 AL 95 METWATCH 2021-10-28 12:00:00 NaT -AL962021 INVEST EX 2021 AL 96 METWATCH 2021-11-07 12:00:00 NaT EP712022 GENESIS001 DB 2022 EP 71 GENESIS 2022-01-20 12:00:00 NaT EP902022 INVEST LO 2022 EP 90 METWATCH 2022-01-20 12:00:00 NaT -[2729 rows x 8 columns] +[2714 rows x 8 columns] ``` ##### retrieve storm track by NHC code @@ -70,20 +70,20 @@ track.data ``` ``` - basin storm_number record_type datetime ... direction speed name geometry -0 AL 11 BEST 2017-08-30 00:00:00 ... 0.000000 0.000000 INVEST POINT (-26.90000 16.10000) -1 AL 11 BEST 2017-08-30 06:00:00 ... 274.421188 6.951105 INVEST POINT (-28.30000 16.20000) -2 AL 11 BEST 2017-08-30 12:00:00 ... 274.424523 6.947623 IRMA POINT (-29.70000 16.30000) -3 AL 11 BEST 2017-08-30 18:00:00 ... 270.154371 5.442611 IRMA POINT (-30.80000 16.30000) -4 AL 11 BEST 2017-08-30 18:00:00 ... 270.154371 5.442611 IRMA POINT (-30.80000 16.30000) -.. ... ... ... ... ... ... ... ... ... -168 AL 11 BEST 2017-09-12 12:00:00 ... 309.875306 7.262151 IRMA POINT (-86.90000 33.80000) -169 AL 11 BEST 2017-09-12 18:00:00 ... 315.455084 7.247674 IRMA POINT (-88.10000 34.80000) -170 AL 11 BEST 2017-09-13 00:00:00 ... 320.849994 5.315966 IRMA POINT (-88.90000 35.60000) -171 AL 11 BEST 2017-09-13 06:00:00 ... 321.042910 3.973414 IRMA POINT (-89.50000 36.20000) -172 AL 11 BEST 2017-09-13 12:00:00 ... 321.262133 3.961652 IRMA POINT (-90.10000 36.80000) + basin storm_number datetime advisory_number ... isowave_radius_for_SWQ extra_values geometry track_start_time +0 AL 11 2017-08-30 00:00:00 ... NaN POINT (-26.90000 16.10000) 2017-08-30 +1 AL 11 2017-08-30 06:00:00 ... NaN POINT (-28.30000 16.20000) 2017-08-30 +2 AL 11 2017-08-30 12:00:00 ... NaN POINT (-29.70000 16.30000) 2017-08-30 +3 AL 11 2017-08-30 18:00:00 ... NaN POINT (-30.80000 16.30000) 2017-08-30 +4 AL 11 2017-08-30 18:00:00 ... NaN POINT (-30.80000 16.30000) 2017-08-30 +.. ... ... ... ... ... ... ... ... ... +168 AL 11 2017-09-12 12:00:00 ... NaN POINT (-86.90000 33.80000) 2017-08-30 +169 AL 11 2017-09-12 18:00:00 ... NaN POINT (-88.10000 34.80000) 2017-08-30 +170 AL 11 2017-09-13 00:00:00 ... NaN POINT (-88.90000 35.60000) 2017-08-30 +171 AL 11 2017-09-13 06:00:00 ... NaN POINT (-89.50000 36.20000) 2017-08-30 +172 AL 11 2017-09-13 12:00:00 ... NaN POINT (-90.10000 36.80000) 2017-08-30 -[173 rows x 22 columns] +[173 rows x 38 columns] ``` ##### retrieve storm track by name and year @@ -97,7 +97,7 @@ VortexTrack.from_storm_name('irma', 2017) ``` ``` -VortexTrack('AL112017', Timestamp('2017-08-30 00:00:00'), Timestamp('2017-09-13 12:00:00'), , , 'BEST', None) +VortexTrack('AL112017', Timestamp('2017-08-30 00:00:00'), Timestamp('2017-09-13 12:00:00'), , [], None) ``` ##### specify storm track file deck @@ -113,20 +113,20 @@ track.data ``` ``` - basin storm_number record_type datetime ... direction speed name geometry -0 AL 11 CARQ 2017-08-27 06:00:00 ... 0.000000 0.000000 INVEST POINT (-17.40000 11.70000) -1 AL 11 CARQ 2017-08-27 12:00:00 ... 281.524268 2.574642 INVEST POINT (-17.90000 11.80000) -2 AL 11 CARQ 2017-08-27 12:00:00 ... 281.524268 2.574642 INVEST POINT (-13.30000 11.50000) -3 AL 11 CARQ 2017-08-27 18:00:00 ... 281.528821 2.573747 INVEST POINT (-18.40000 11.90000) -4 AL 11 CARQ 2017-08-27 18:00:00 ... 281.528821 2.573747 INVEST POINT (-16.00000 11.50000) -... ... ... ... ... ... ... ... ... ... -10739 AL 11 HMON 2017-09-16 09:00:00 ... 52.414833 11.903071 POINT (-84.30000 43.00000) -10740 AL 11 HMON 2017-09-16 12:00:00 ... 7.196515 6.218772 POINT (-84.30000 41.00000) -10741 AL 11 HMON 2017-09-16 12:00:00 ... 7.196515 6.218772 POINT (-82.00000 39.50000) -10742 AL 11 HMON 2017-09-16 12:00:00 ... 7.196515 6.218772 POINT (-84.30000 44.00000) -10743 AL 11 HMON 2017-09-16 15:00:00 ... 122.402907 22.540200 POINT (-81.90000 39.80000) + basin storm_number datetime advisory_number ... isowave_radius_for_SWQ extra_values geometry track_start_time +0 AL 11 2017-08-27 06:00:00 01 ... NaN POINT (-17.40000 11.70000) 2017-08-28 06:00:00 +1 AL 11 2017-08-27 12:00:00 01 ... NaN POINT (-17.90000 11.80000) 2017-08-28 06:00:00 +2 AL 11 2017-08-27 18:00:00 01 ... NaN POINT (-18.40000 11.90000) 2017-08-28 06:00:00 +3 AL 11 2017-08-28 00:00:00 01 ... NaN POINT (-19.00000 12.00000) 2017-08-28 06:00:00 +4 AL 11 2017-08-28 06:00:00 01 ... NaN POINT (-19.50000 12.00000) 2017-08-28 06:00:00 +... ... ... ... ... ... ... ... ... ... +10739 AL 11 2017-09-12 00:00:00 03 ... NaN POINT (-84.40000 31.90000) 2017-09-12 00:00:00 +10740 AL 11 2017-09-12 03:00:00 03 ... NaN POINT (-84.90000 32.40000) 2017-09-12 00:00:00 +10741 AL 11 2017-09-12 12:00:00 03 ... NaN POINT (-86.40000 33.80000) 2017-09-12 00:00:00 +10742 AL 11 2017-09-13 00:00:00 03 ... NaN POINT (-88.20000 35.20000) 2017-09-12 00:00:00 +10743 AL 11 2017-09-13 12:00:00 03 ... NaN POINT (-88.60000 36.40000) 2017-09-12 00:00:00 -[10744 rows x 22 columns] +[10434 rows x 38 columns] ``` ##### read storm track from file @@ -136,21 +136,21 @@ If you have an ATCF or `fort.22` file, use the corresponding methods: ```python from stormevents.nhc import VortexTrack -VortexTrack.from_file('tests/data/input/test_from_atcf/atcf.trk') +VortexTrack.from_file('tests/data/input/test_vortex_track_from_file/AL062018.dat') ``` ``` -VortexTrack('BT02008', Timestamp('2008-10-16 17:06:00'), Timestamp('2008-10-20 20:06:00'), , , 'BEST', 'tests/data/input/test_from_atcf/atcf.trk') +VortexTrack('AL062018', Timestamp('2018-08-30 06:00:00'), Timestamp('2018-09-18 12:00:00'), None, , ['BEST', 'OFCL', 'OFCP', 'HMON', 'CARQ', 'HWRF'], PosixPath('/home/zrb/Projects/StormEvents/tests/data/input/test_vortex_track_from_file/AL062018.dat')) ``` ```python from stormevents.nhc import VortexTrack -VortexTrack.from_fort22('tests/data/input/test_from_fort22/irma2017_fort.22') +VortexTrack.from_file('tests/data/input/test_vortex_track_from_file/irma2017_fort.22') ``` ``` -VortexTrack('AL112017', Timestamp('2017-09-05 00:00:00'), Timestamp('2017-09-19 00:00:00'), , , 'BEST', 'tests/data/input/test_from_fort22/irma2017_fort.22') +VortexTrack('AL112017', Timestamp('2017-09-05 00:00:00'), Timestamp('2017-09-12 00:00:00'), None, , ['BEST', 'OFCL', 'OFCP', 'HMON', 'CARQ', 'HWRF'], PosixPath('/home/zrb/Projects/StormEvents/tests/data/input/test_vortex_track_from_file/irma2017_fort.22')) ``` ##### write storm track to `fort.22` file @@ -177,21 +177,21 @@ usgs_flood_events() ``` ``` - name year ... start_date end_date -usgs_id ... -7 FEMA 2013 exercise 2013 ... 2013-05-15 04:00:00 2013-05-23 04:00:00 -8 Wilma 2005 ... 2005-10-20 00:00:00 2005-10-31 00:00:00 -9 Midwest Floods 2011 2011 ... 2011-02-01 06:00:00 2011-08-30 05:00:00 -10 2013 - June PA Flood 2013 ... 2013-06-23 00:00:00 2013-07-01 00:00:00 -11 Colorado 2013 Front Range Flood 2013 ... 2013-09-12 05:00:00 2013-09-24 05:00:00 -... ... ... ... ... ... -311 2021 August Flash Flood TN 2021 ... 2021-08-21 05:00:00 2021-08-22 05:00:00 -312 2021 Tropical Cyclone Ida 2021 ... 2021-08-27 05:00:00 2021-09-03 05:00:00 -313 Chesapeake Bay - October 2021 2021 ... 2021-10-28 04:00:00 NaT -314 2021 November Flooding Washington State 2021 ... 2021-11-08 06:00:00 2021-11-19 06:00:00 -315 Washington Coastal Winter 2021-2022 2021 ... 2021-11-01 05:00:00 2022-06-30 05:00:00 + name year description ... last_updated_by start_date end_date +usgs_id ... +7 FEMA 2013 exercise 2013 Ardent/Sentry 2013 FEMA Exercise ... NaN 2013-05-15 04:00:00 2013-05-23 04:00:00 +8 Wilma 2005 Category 3 in west FL. \nHurricane Wilma was t... ... NaN 2005-10-20 00:00:00 2005-10-31 00:00:00 +9 Midwest Floods 2011 2011 Spring and summer 2011 flooding of the Mississ... ... 35.0 2011-02-01 06:00:00 2011-08-30 05:00:00 +10 2013 - June PA Flood 2013 Localized summer rain, small scale event ... NaN 2013-06-23 00:00:00 2013-07-01 00:00:00 +11 Colorado 2013 Front Range Flood 2013 A large prolonged precipitation event resulted... ... 35.0 2013-09-12 05:00:00 2013-09-24 05:00:00 +... ... ... ... ... ... ... ... +312 2021 Tropical Cyclone Ida 2021 NaN ... 864.0 2021-08-27 05:00:00 2021-09-03 05:00:00 +313 Chesapeake Bay - October 2021 2021 Coastal-flooding event in the Chesapeake Bay. ... 406.0 2021-10-28 04:00:00 NaT +314 2021 November Flooding Washington State 2021 Atmospheric River Flooding ... 864.0 2021-11-08 06:00:00 2021-11-19 06:00:00 +315 Washington Coastal Winter 2021-2022 2021 NaN ... 864.0 2021-11-01 05:00:00 2022-06-30 05:00:00 +317 2022 Hunga Tonga-Hunga Haapai tsunami 2022 ... 1.0 2022-01-14 05:00:00 2022-01-18 05:00:00 -[292 rows x 11 columns] +[293 rows x 11 columns] ``` ##### retrieve HWM survey data for any flood event @@ -204,19 +204,19 @@ flood.high_water_marks() ``` ``` - latitude longitude eventName ... hwm_notes siteZone geometry -hwm_id ... -22602 31.170642 -81.428402 Irma September 2017 ... NaN NaN POINT (-81.42840 31.17064) -22605 31.453850 -81.362853 Irma September 2017 ... NaN NaN POINT (-81.36285 31.45385) -22612 30.720000 -81.549440 Irma September 2017 ... There is a secondary peak around 5.5 ft, so th... NaN POINT (-81.54944 30.72000) -22636 32.007730 -81.238270 Irma September 2017 ... Trimble R8 used to establish TBM. Levels ran f... NaN POINT (-81.23827 32.00773) -22653 31.531078 -81.358894 Irma September 2017 ... NaN NaN POINT (-81.35889 31.53108) -... ... ... ... ... ... ... ... -26171 18.470402 -66.246631 Irma September 2017 ... NaN NaN POINT (-66.24663 18.47040) -26173 18.470300 -66.449900 Irma September 2017 ... levels from GNSS BM NaN POINT (-66.44990 18.47030) -26175 18.463954 -66.140869 Irma September 2017 ... levels from GNSS BM NaN POINT (-66.14087 18.46395) -26177 18.488720 -66.392160 Irma September 2017 ... levels from GNSS BM NaN POINT (-66.39216 18.48872) -26179 18.005607 -65.871768 Irma September 2017 ... levels from GNSS BM NaN POINT (-65.87177 18.00561) + latitude longitude eventName hwmTypeName ... hwm_uncertainty hwm_notes siteZone geometry +hwm_id ... +22602 31.170642 -81.428402 Irma September 2017 Debris ... NaN NaN NaN POINT (-81.42840 31.17064) +22605 31.453850 -81.362853 Irma September 2017 Seed line ... 0.1 NaN NaN POINT (-81.36285 31.45385) +22612 30.720000 -81.549440 Irma September 2017 Seed line ... NaN There is a secondary peak around 5.5 ft, so th... NaN POINT (-81.54944 30.72000) +22636 32.007730 -81.238270 Irma September 2017 Seed line ... 0.1 Trimble R8 used to establish TBM. Levels ran f... NaN POINT (-81.23827 32.00773) +22653 31.531078 -81.358894 Irma September 2017 Seed line ... NaN NaN NaN POINT (-81.35889 31.53108) +... ... ... ... ... ... ... ... ... ... +26171 18.470402 -66.246631 Irma September 2017 Debris ... 0.5 NaN NaN POINT (-66.24663 18.47040) +26173 18.470300 -66.449900 Irma September 2017 Debris ... 0.5 levels from GNSS BM NaN POINT (-66.44990 18.47030) +26175 18.463954 -66.140869 Irma September 2017 Debris ... 0.5 levels from GNSS BM NaN POINT (-66.14087 18.46395) +26177 18.488720 -66.392160 Irma September 2017 Debris ... 0.5 levels from GNSS BM NaN POINT (-66.39216 18.48872) +26179 18.005607 -65.871768 Irma September 2017 Debris ... 0.5 levels from GNSS BM NaN POINT (-65.87177 18.00561) [506 rows x 53 columns] ``` @@ -229,21 +229,21 @@ flood.high_water_marks(quality=['EXCELLENT', 'GOOD']) ``` ``` - latitude longitude eventName ... hwm_notes siteZone geometry -hwm_id ... -22602 31.170642 -81.428402 Irma September 2017 ... NaN NaN POINT (-81.42840 31.17064) -22605 31.453850 -81.362853 Irma September 2017 ... NaN NaN POINT (-81.36285 31.45385) -22612 30.720000 -81.549440 Irma September 2017 ... There is a secondary peak around 5.5 ft, so th... NaN POINT (-81.54944 30.72000) -22636 32.007730 -81.238270 Irma September 2017 ... Trimble R8 used to establish TBM. Levels ran f... NaN POINT (-81.23827 32.00773) -22653 31.531078 -81.358894 Irma September 2017 ... NaN NaN POINT (-81.35889 31.53108) -... ... ... ... ... ... ... ... -26171 18.470402 -66.246631 Irma September 2017 ... NaN NaN POINT (-66.24663 18.47040) -26173 18.470300 -66.449900 Irma September 2017 ... levels from GNSS BM NaN POINT (-66.44990 18.47030) -26175 18.463954 -66.140869 Irma September 2017 ... levels from GNSS BM NaN POINT (-66.14087 18.46395) -26177 18.488720 -66.392160 Irma September 2017 ... levels from GNSS BM NaN POINT (-66.39216 18.48872) -26179 18.005607 -65.871768 Irma September 2017 ... levels from GNSS BM NaN POINT (-65.87177 18.00561) + latitude longitude eventName hwmTypeName ... hwm_notes peak_summary_id siteZone geometry +hwm_id ... +22605 31.453850 -81.362853 Irma September 2017 Seed line ... NaN NaN NaN POINT (-81.36285 31.45385) +22612 30.720000 -81.549440 Irma September 2017 Seed line ... There is a secondary peak around 5.5 ft, so th... NaN NaN POINT (-81.54944 30.72000) +22636 32.007730 -81.238270 Irma September 2017 Seed line ... Trimble R8 used to establish TBM. Levels ran f... NaN NaN POINT (-81.23827 32.00773) +22674 32.030907 -80.900605 Irma September 2017 Seed line ... NaN 5042.0 NaN POINT (-80.90061 32.03091) +22849 30.741940 -81.687780 Irma September 2017 Debris ... NaN 4834.0 NaN POINT (-81.68778 30.74194) +... ... ... ... ... ... ... ... ... ... +25150 30.038222 -81.880928 Irma September 2017 Seed line ... GNSS Level II survey. NaN NaN POINT (-81.88093 30.03822) +25151 30.118110 -81.760220 Irma September 2017 Seed line ... GNSS Level III survey. NaN NaN POINT (-81.76022 30.11811) +25158 29.720560 -81.506110 Irma September 2017 Seed line ... GNSS Level II survey. NaN NaN POINT (-81.50611 29.72056) +25159 30.097514 -81.794375 Irma September 2017 Seed line ... GNSS Level III survey. NaN NaN POINT (-81.79438 30.09751) +25205 29.783890 -81.263060 Irma September 2017 Seed line ... GNSS Level II survey. NaN NaN POINT (-81.26306 29.78389) -[506 rows x 53 columns] +[277 rows x 53 columns] ``` #### data products from the Center for Operational Oceanographic Products and Services (CO-OPS) @@ -262,21 +262,21 @@ coops_stations() ``` ``` - nws_id name state status removed geometry + nws_id name state status removed geometry nos_id -1600012 46125 QREB buoy active POINT (122.62500 37.75000) -8735180 DILA1 Dauphin Island AL active 2019-07-18 10:00:00,2018-07-30 16:40:00,2017-0... POINT (-88.06250 30.25000) -8557380 LWSD1 Lewes DE active 2019-08-01 00:00:00,2018-06-18 00:00:00,2017-0... POINT (-75.12500 38.78125) -8465705 NWHC3 New Haven CT active 2019-08-18 14:55:00,2019-08-18 14:54:00,2018-0... POINT (-72.93750 41.28125) -9439099 WAUO3 Wauna OR active 2019-08-19 22:59:00,2014-06-20 21:30:00,2013-0... POINT (-123.43750 46.15625) -... ... ... ... ... ... ... -8448725 MSHM3 Menemsha Harbor, MA MA discontinued 2013-09-26 23:59:00,2013-09-26 00:00:00,2012-0... POINT (-70.75000 41.34375) -8538886 TPBN4 Tacony-Palmyra Bridge NJ discontinued 2013-11-11 00:01:00,2013-11-11 00:00:00,2012-0... POINT (-75.06250 40.00000) -9439011 HMDO3 Hammond OR discontinued 2014-08-13 00:00:00,2011-04-12 23:59:00,2011-0... POINT (-123.93750 46.18750) -8762372 LABL1 East Bank 1, Norco, B. LaBranche LA discontinued 2012-11-05 10:38:00,2012-11-05 10:37:00,2012-1... POINT (-90.37500 30.04688) -8530528 CARN4 CARLSTADT, HACKENSACK RIVER NJ discontinued 1994-11-12 23:59:00,1994-11-12 00:00:00 POINT (-74.06250 40.81250) +1600012 46125 QREB buoy active POINT (122.62500 37.75000) +1619910 SNDP5 Sand Island, Midway Islands active POINT (-177.37500 28.21875) +1630000 APRP7 Apra Harbor, Guam active POINT (144.62500 13.44531) +1631428 PGBP7 Pago Bay, Guam active POINT (144.75000 13.42969) +1770000 NSTP6 Pago Pago, American Samoa active POINT (-170.75000 -14.27344) +... ... ... ... ... ... ... +8423898 FTPN3 Fort Point NH discontinued 2020-04-13 00:00:00,2014-08-05 00:00:00,2012-0... POINT (-70.68750 43.06250) +8726667 MCYF1 Mckay Bay Entrance FL discontinued 2020-05-20 00:00:00,2019-03-08 00:00:00,2017-0... POINT (-82.43750 27.90625) +8772447 FCGT2 Freeport TX discontinued 2020-05-24 18:45:00,2018-10-10 21:50:00,2018-1... POINT (-95.31250 28.93750) +9087079 GBWW3 Green Bay WI discontinued 2020-10-28 13:00:00,2007-08-06 23:59:00,2007-0... POINT (-88.00000 44.53125) +8770570 SBPT2 Sabine Pass North TX discontinued 2021-01-18 00:00:00,2020-09-30 15:45:00,2020-0... POINT (-93.87500 29.73438) -[433 rows x 6 columns] +[435 rows x 6 columns] ``` Additionally, you can use a Shapely `Polygon` or `MultiPolygon` to constrain the stations query to a specific region: @@ -403,7 +403,7 @@ StormEvent('FLORENCE', 2018) ``` ``` -StormEvent('FLORENCE', 2018) +StormEvent(name='FLORENCE', year=2018, start_date=Timestamp('2018-08-30 06:00:00'), end_date=Timestamp('2018-09-18 12:00:00')) ``` or from a storm NHC code, @@ -415,7 +415,7 @@ StormEvent.from_nhc_code('EP172016') ``` ``` -StormEvent('PAINE', 2016) +StormEvent(name='PAINE', year=2016, start_date=Timestamp('2016-09-18 00:00:00'), end_date=Timestamp('2016-09-21 12:00:00')) ``` or from a USGS flood event ID. @@ -427,7 +427,7 @@ StormEvent.from_usgs_id(310) ``` ``` -StormEvent('HENRI', 2021, end_date='2021-08-24 12:00:00') +StormEvent(name='HENRI', year=2021, start_date=Timestamp('2021-08-20 18:00:00'), end_date=Timestamp('2021-08-24 12:00:00')) ``` To constrain the time interval, you can provide an absolute time range, @@ -440,7 +440,7 @@ StormEvent('paine', 2016, start_date='2016-09-19', end_date=datetime(2016, 9, 19 ``` ``` -StormEvent('PAINE', 2016, start_date='2016-09-19 00:00:00', end_date='2016-09-19 12:00:00') +StormEvent(name='PAINE', year=2016, start_date=datetime.datetime(2016, 9, 19, 0, 0), end_date=datetime.datetime(2016, 9, 19, 12, 0)) ``` ```python @@ -451,7 +451,7 @@ StormEvent('paine', 2016, end_date=datetime(2016, 9, 19, 12)) ``` ``` -StormEvent('PAINE', 2016, end_date='2016-09-19 12:00:00') +StormEvent(name='PAINE', year=2016, start_date=Timestamp('2016-09-18 00:00:00'), end_date=datetime.datetime(2016, 9, 19, 12, 0)) ``` or, alternatively, you can provide relative time deltas, which will be interpreted compared to the absolute time interval @@ -465,7 +465,7 @@ StormEvent('florence', 2018, start_date=timedelta(days=2)) # <- start 2 days af ``` ``` -StormEvent('FLORENCE', 2018, start_date='2018-09-01 06:00:00') +StormEvent(name='FLORENCE', year=2018, start_date=Timestamp('2018-09-01 06:00:00'), end_date=Timestamp('2018-09-18 12:00:00')) ``` ```python @@ -481,7 +481,7 @@ StormEvent( ``` ``` -StormEvent('HENRI', 2021, start_date='2021-08-21 12:00:00', end_date='2021-08-22 12:00:00') +StormEvent(name='HENRI', year=2021, start_date=Timestamp('2021-08-21 12:00:00'), end_date=Timestamp('2021-08-22 12:00:00')) ``` ```python @@ -492,7 +492,7 @@ StormEvent('ida', 2021, end_date=timedelta(days=2)) # <- end 2 days after NHC s ``` ``` -StormEvent('IDA', 2021, end_date='2021-08-29 18:00:00') +StormEvent(name='IDA', year=2021, start_date=Timestamp('2021-08-27 18:00:00'), end_date=Timestamp('2021-08-29 18:00:00')) ``` #### retrieve data for a storm @@ -511,7 +511,7 @@ storm.track() ``` ``` -VortexTrack('AL062018', Timestamp('2018-08-30 06:00:00'), Timestamp('2018-09-18 12:00:00'), , , 'BEST', None) +VortexTrack('AL062018', Timestamp('2018-08-30 06:00:00'), Timestamp('2018-09-18 12:00:00'), , , [], None) ``` ```python @@ -522,7 +522,7 @@ storm.track(file_deck='a') ``` ``` -VortexTrack('AL062018', Timestamp('2018-08-30 06:00:00'), Timestamp('2018-09-18 12:00:00'), , , None, None) +VortexTrack('AL062018', Timestamp('2018-08-30 06:00:00'), Timestamp('2018-09-18 12:00:00'), , , ['OFCL', 'OFCP', 'HMON', 'CARQ', 'HWRF'], None) ``` ##### high-water mark (HWM) surveys provided by the United States Geological Survey (USGS) diff --git a/stormevents/nhc/atcf.py b/stormevents/nhc/atcf.py index 48d5680..2a4e52c 100644 --- a/stormevents/nhc/atcf.py +++ b/stormevents/nhc/atcf.py @@ -5,7 +5,7 @@ import itertools from os import PathLike from pathlib import Path -from typing import Any, Iterable, List, TextIO, Union +from typing import Iterable, List, TextIO, Union import geopandas from geopandas import GeoDataFrame @@ -298,18 +298,6 @@ def atcf_url( return url -def normalize_atcf_value(value: Any, to_type: type, round_digits: int = None,) -> Any: - if type(value).__name__ == 'Quantity': - value = value.magnitude - if not (value is None or pandas.isna(value) or value == ''): - if round_digits is not None and issubclass(to_type, (int, float)): - if isinstance(value, str): - value = float(value) - value = round(value, round_digits) - value = typepigeon.convert_value(value, to_type) - return value - - def read_atcf( atcf: Union[PathLike, io.BytesIO, TextIO], advisories: List[ATCF_Advisory] = None, @@ -340,11 +328,19 @@ def read_atcf( for line in lines ) - data = DataFrame.from_records(lines, columns=list(ATCF_FIELDS),).astype( - {field: 'string' for field in ATCF_FIELDS} + data = DataFrame.from_records(lines) + data.rename( + columns={index: list(ATCF_FIELDS)[index] for index in range(len(data.columns))}, + inplace=True, + ) + for column in ATCF_FIELDS: + if column not in data.columns: + data[column] = pandas.NA + data.astype( + {field: 'string' for field in data.columns}, copy=False, ) - if data['USERDEFINED'].str.contains(',').any(): + if 'USERDEFINED' in data and data['USERDEFINED'].str.contains(',').any(): if fort_22: extra_fields = FORT_22_FIELDS else: @@ -366,7 +362,7 @@ def read_atcf( except ValueError: pass - if advisories is not None: + if advisories is not None and len(advisories) > 0: data = data[data['TECH'].isin(advisories)] if len(data) == 0: raise ValueError(f'no ATCF records found matching "{advisories}"') diff --git a/stormevents/nhc/storms.py b/stormevents/nhc/storms.py index a262cde..99c62f3 100644 --- a/stormevents/nhc/storms.py +++ b/stormevents/nhc/storms.py @@ -1,7 +1,7 @@ from datetime import datetime from functools import lru_cache import re -from typing import Iterable, List +from typing import Iterable from bs4 import BeautifulSoup import numpy @@ -28,12 +28,12 @@ def nhc_storms(year: int = None) -> pandas.DataFrame: AL051851 UNNAMED TS 1851 AL 5 ARCHIVE 1851-09-13 00:00:00 1851-09-16 18:00:00 AL061851 UNNAMED TS 1851 AL 6 ARCHIVE 1851-10-16 00:00:00 1851-10-19 18:00:00 ... ... ... ... ... ... ... ... ... + CP902021 INVEST LO 2021 CP 90 METWATCH 2021-07-24 12:00:00 NaT + CP912021 INVEST DB 2021 CP 91 METWATCH 2021-08-07 18:00:00 NaT EP922021 INVEST DB 2021 EP 92 METWATCH 2021-06-05 06:00:00 NaT - AL952021 INVEST DB 2021 AL 95 METWATCH 2021-10-28 12:00:00 NaT - AL962021 INVEST EX 2021 AL 96 METWATCH 2021-11-07 12:00:00 NaT EP712022 GENESIS001 DB 2022 EP 71 GENESIS 2022-01-20 12:00:00 NaT EP902022 INVEST LO 2022 EP 90 METWATCH 2022-01-20 12:00:00 NaT - [2729 rows x 8 columns] + [2714 rows x 8 columns] """ url = 'https://ftp.nhc.noaa.gov/atcf/index/storm_list.txt' @@ -95,19 +95,21 @@ def nhc_storms(year: int = None) -> pandas.DataFrame: else: storms = storms[storms['year'] == int(year)] - for string_column in ['nhc_code', 'name', 'class', 'source']: - storms[string_column] = storms[string_column].str.strip() - + storms['nhc_code'] = storms['nhc_code'].str.strip() storms.set_index('nhc_code', inplace=True) - gis_storms = nhc_storms_gis_archive(year=year) - gis_storms = gis_storms.drop(gis_storms[gis_storms.index.isin(storms.index)].index) - if len(gis_storms) > 0: - gis_storms[['start_date', 'end_date']] = pandas.to_datetime(numpy.nan) - storms = pandas.concat([storms, gis_storms[storms.columns]]) + gis_archive_storms = nhc_storms_gis_archive(year=year) + gis_archive_storms = gis_archive_storms.drop( + gis_archive_storms[gis_archive_storms.index.isin(storms.index)].index + ) + if len(gis_archive_storms) > 0: + gis_archive_storms[['start_date', 'end_date']] = pandas.to_datetime(numpy.nan) + storms = pandas.concat([storms, gis_archive_storms[storms.columns]]) for string_column in ['name', 'class', 'source']: - storms.loc[storms[string_column].str.len() == 0, string_column] = None + storms.loc[storms[string_column].str.len() == 0, string_column] = pandas.NA + storms[string_column] = storms[string_column].str.strip() + storms[string_column] = storms[string_column].astype('string') storms.sort_values(['year', 'number', 'basin'], inplace=True) @@ -115,7 +117,7 @@ def nhc_storms(year: int = None) -> pandas.DataFrame: @lru_cache(maxsize=None) -def nhc_storms_archive(year: int = None) -> List[str]: +def nhc_storms_archive(year: int = None) -> pandas.DataFrame: url = 'https://ftp.nhc.noaa.gov/atcf/archive/storm.table' columns = [ @@ -144,10 +146,37 @@ def nhc_storms_archive(year: int = None) -> List[str]: storms = pandas.read_csv(url, header=0, names=columns) + storms = storms[ + [ + 'nhc_code', + 'name', + 'class', + 'year', + 'basin', + 'number', + 'source', + 'start_date', + 'end_date', + ] + ] + if year is not None: - storms = storms[storms['year'] == year] + if isinstance(year, Iterable) and not isinstance(year, str): + storms = storms[storms['year'].isin(year)] + else: + storms = storms[storms['year'] == int(year)] + + storms['nhc_code'] = storms['nhc_code'].str.strip() + storms.set_index('nhc_code', inplace=True) + + storms.sort_values(['year', 'number', 'basin'], inplace=True) + + for string_column in ['name', 'class', 'source']: + storms.loc[storms[string_column].str.len() == 0, string_column] = pandas.NA + storms[string_column] = storms[string_column].str.strip() + storms[string_column] = storms[string_column].astype('string') - return storms['nhc_code'].str.strip().to_list() + return storms @lru_cache(maxsize=None) @@ -228,4 +257,9 @@ def nhc_storms_gis_archive(year: int = None) -> pandas.DataFrame: storms.sort_values(['year', 'basin', 'number'], inplace=True) + for string_column in ['name', 'class', 'source']: + storms.loc[storms[string_column].str.len() == 0, string_column] = pandas.NA + storms[string_column] = storms[string_column].str.strip() + storms[string_column] = storms[string_column].astype('string') + return storms[['name', 'class', 'year', 'basin', 'number', 'source']] diff --git a/stormevents/nhc/track.py b/stormevents/nhc/track.py index f30905d..7e25b74 100644 --- a/stormevents/nhc/track.py +++ b/stormevents/nhc/track.py @@ -7,6 +7,7 @@ import pathlib import re from typing import Any, Dict, List, Union +from urllib.error import URLError from urllib.request import urlopen import numpy @@ -14,13 +15,7 @@ from pandas import DataFrame from pyproj import Geod from shapely import ops -from shapely.geometry import ( - GeometryCollection, - LineString, - MultiLineString, - MultiPolygon, - Polygon, -) +from shapely.geometry import LineString, MultiPolygon, Polygon import typepigeon from stormevents.nhc.atcf import ( @@ -32,7 +27,7 @@ get_atcf_entry, read_atcf, ) -from stormevents.nhc.storms import nhc_storms, nhc_storms_archive +from stormevents.nhc.storms import nhc_storms from stormevents.utilities import subset_time_interval @@ -47,29 +42,27 @@ def __init__( start_date: datetime = None, end_date: datetime = None, file_deck: ATCF_FileDeck = None, - mode: ATCF_Mode = None, - advisory: ATCF_Advisory = None, + advisories: List[ATCF_Advisory] = None, ): """ :param storm: storm ID, or storm name and year :param start_date: start date of track :param end_date: end date of track :param file_deck: ATCF file deck; one of `a`, `b`, `f` - :param mode: ATCF mode; either `historical` or `realtime` - :param advisory: ATCF advisory type; one of `BEST`, `OFCL`, `OFCP`, `HMON`, `CARQ`, `HWRF` + :param advisories: ATCF advisory types; one of `BEST`, `OFCL`, `OFCP`, `HMON`, `CARQ`, `HWRF` >>> VortexTrack('AL112017') - VortexTrack('AL112017', Timestamp('2017-08-30 00:00:00'), Timestamp('2017-09-13 12:00:00'), , , 'BEST', None) + VortexTrack('AL112017', Timestamp('2017-08-30 00:00:00'), Timestamp('2017-09-13 12:00:00'), , , [], None) >>> VortexTrack('AL112017', start_date='2017-09-04') - VortexTrack('AL112017', datetime.datetime(2017, 9, 4, 0, 0), Timestamp('2017-09-13 12:00:00'), , , 'BEST', None) + VortexTrack('AL112017', Timestamp('2017-09-04 00:00:00'), Timestamp('2017-09-13 12:00:00'), , , [], None) >>> from datetime import timedelta >>> VortexTrack('AL112017', start_date=timedelta(days=2), end_date=timedelta(days=-1)) - VortexTrack('AL112017', Timestamp('2017-09-01 00:00:00'), Timestamp('2017-09-12 12:00:00'), , , 'BEST', None) + VortexTrack('AL112017', Timestamp('2017-09-01 00:00:00'), Timestamp('2017-09-12 12:00:00'), , , [], None) >>> VortexTrack('AL112017', file_deck='a') - VortexTrack('AL112017', Timestamp('2017-08-27 06:00:00'), Timestamp('2017-09-16 15:00:00'), , , None, None) + VortexTrack('AL112017', Timestamp('2017-08-27 06:00:00'), Timestamp('2017-09-13 12:00:00'), , , ['OFCL', 'OFCP', 'HMON', 'CARQ', 'HWRF'], None) """ self.__unfiltered_data = None @@ -81,11 +74,13 @@ def __init__( self.__start_date = None self.__end_date = None self.__file_deck = None - self.__mode = None - self.__advisory = None + self.__advisories = None + self.__advisories_to_remove = [] self.__invalid_storm_name = False self.__location_hash = None + self.__linestrings = None + self.__distances = None if isinstance(storm, DataFrame): self.__unfiltered_data = storm @@ -99,9 +94,8 @@ def __init__( else: raise FileNotFoundError(f'file not found "{storm}"') + self.advisories = advisories self.file_deck = file_deck - self.mode = mode - self.advisory = advisory self.__previous_configuration = self.__configuration @@ -117,8 +111,7 @@ def from_storm_name( start_date: datetime = None, end_date: datetime = None, file_deck: ATCF_FileDeck = None, - mode: ATCF_Mode = None, - advisory: str = None, + advisories: [ATCF_Advisory] = None, ) -> 'VortexTrack': """ :param name: storm name @@ -126,11 +119,10 @@ def from_storm_name( :param start_date: start date of track :param end_date: end date of track :param file_deck: ATCF file deck; one of ``a``, ``b``, ``f`` - :param mode: ATCF mode; either ``historical`` or ``realtime`` - :param advisory: ATCF advisory type; one of ``BEST``, ``OFCL``, ``OFCP``, ``HMON``, ``CARQ``, ``HWRF`` + :param advisories: ATCF advisory type; one of ``BEST``, ``OFCL``, ``OFCP``, ``HMON``, ``CARQ``, ``HWRF`` >>> VortexTrack.from_storm_name('irma', 2017) - VortexTrack('AL112017', Timestamp('2017-08-30 00:00:00'), Timestamp('2017-09-13 12:00:00'), , , 'BEST', None) + VortexTrack('AL112017', Timestamp('2017-08-30 00:00:00'), Timestamp('2017-09-13 12:00:00'), , [], None) """ year = int(year) @@ -141,8 +133,7 @@ def from_storm_name( start_date=start_date, end_date=end_date, file_deck=file_deck, - mode=mode, - advisory=advisory, + advisories=advisories, ) @classmethod @@ -154,10 +145,10 @@ def from_file( :param start_date: start date of track :param end_date: end date of track + >>> VortexTrack.from_file('tests/data/input/test_vortex_track_from_file/AL062018.dat') + VortexTrack('AL062018', Timestamp('2018-08-30 06:00:00'), Timestamp('2018-09-18 12:00:00'), None, , ['BEST', 'OFCL', 'OFCP', 'HMON', 'CARQ', 'HWRF'], PosixPath('/home/zrb/Projects/StormEvents/tests/data/input/test_vortex_track_from_file/AL062018.dat')) >>> VortexTrack.from_file('tests/data/input/test_vortex_track_from_file/irma2017_fort.22') - VortexTrack('AL112017', Timestamp('2017-09-05 00:00:00'), Timestamp('2017-09-19 00:00:00'), , , 'BEST', PosixPath('tests/data/input/test_vortex_track_from_file/irma2017_fort.22')) - >>> VortexTrack.from_file('tests/data/input/test_vortex_track_from_file/BT02008.dat') - VortexTrack('BT02008', Timestamp('2008-10-16 17:06:00'), Timestamp('2008-10-20 20:06:00'), , , 'BEST', PosixPath('tests/data/input/test_vortex_track_from_file/BT02008.dat')) + VortexTrack('AL112017', Timestamp('2017-09-05 00:00:00'), Timestamp('2017-09-12 00:00:00'), None, , ['BEST', 'OFCL', 'OFCP', 'HMON', 'CARQ', 'HWRF'], PosixPath('/home/zrb/Projects/StormEvents/tests/data/input/test_vortex_track_from_file/irma2017_fort.22')) """ try: @@ -178,7 +169,12 @@ def name(self) -> str: """ if self.__name is None: - name = self.data['name'].value_counts()[:].index.tolist()[0] + # get the most frequently-used storm name in the data + names = self.data['name'].value_counts() + if len(names) > 0: + name = names.index[0] + else: + name = '' if name.strip() == '': storms = nhc_storms(year=self.year) @@ -360,62 +356,39 @@ def file_deck(self) -> ATCF_FileDeck: @file_deck.setter def file_deck(self, file_deck: ATCF_FileDeck): if file_deck is None and self.filename is None: - file_deck = ATCF_FileDeck.BEST + if self.advisories is not None or len(self.advisories) > 0: + if ATCF_Advisory.BEST in typepigeon.convert_value( + self.advisories, [ATCF_Advisory] + ): + file_deck = ATCF_FileDeck.BEST + else: + file_deck = ATCF_FileDeck.ADVISORY + else: + file_deck = ATCF_FileDeck.BEST elif not isinstance(file_deck, ATCF_FileDeck): file_deck = typepigeon.convert_value(file_deck, ATCF_FileDeck) self.__file_deck = file_deck @property - def mode(self) -> ATCF_Mode: + def advisories(self) -> List[ATCF_Advisory]: """ - :return: ATCF mode; either ``historical`` or ``realtime`` - """ - - if self.__mode is None: - if self.filename is None: - mode = ATCF_Mode.REALTIME - if self.nhc_code is not None: - try: - archive_storms = nhc_storms_archive() - if self.nhc_code.upper() in archive_storms: - mode = ATCF_Mode.HISTORICAL - except: - pass - else: - mode = ATCF_Mode.HISTORICAL - self.__mode = mode - - return self.__mode - - @mode.setter - def mode(self, mode: ATCF_Mode): - if mode is not None and not isinstance(mode, ATCF_Mode): - mode = typepigeon.convert_value(mode, ATCF_Mode) - self.__mode = mode - - @property - def advisory(self) -> str: - """ - :return: ATCF advisory type; one of ``BEST``, ``OFCL``, ``OFCP``, ``HMON``, ``CARQ``, ``HWRF`` + :return: ATCF advisory types; one of ``BEST``, ``OFCL``, ``OFCP``, ``HMON``, ``CARQ``, ``HWRF`` """ if self.file_deck == ATCF_FileDeck.BEST: - self.__advisory = ATCF_Advisory.BEST.value + self.__advisories = [ATCF_Advisory.BEST] - return self.__advisory + return self.__advisories - @advisory.setter - def advisory(self, advisory: ATCF_Advisory): + @advisories.setter + def advisories(self, advisories: List[ATCF_Advisory]): # e.g. `BEST`, `OFCL`, `HWRF`, etc. - if advisory is not None: - if not isinstance(advisory, str): - advisory = typepigeon.convert_value(advisory, str) - advisory = advisory.upper() - if advisory not in self.__valid_advisories: - raise ValueError( - f'invalid advisory "{advisory}"; not one of {self.__valid_advisories}' - ) - self.__advisory = advisory + if advisories is None: + advisories = self.__valid_advisories + else: + advisories = typepigeon.convert_value(advisories, [str]) + advisories = [advisory.upper() for advisory in advisories] + self.__advisories = advisories @property def __valid_advisories(self) -> List[ATCF_Advisory]: @@ -457,35 +430,35 @@ def data(self) -> DataFrame: >>> track = VortexTrack('AL112017') >>> track.data - basin storm_number advisory datetime ... direction speed name geometry - 0 AL 11 BEST 2017-08-30 00:00:00 ... 0.000000 0.000000 INVEST POINT (-26.90000 16.10000) - 1 AL 11 BEST 2017-08-30 06:00:00 ... 274.421188 6.951105 INVEST POINT (-28.30000 16.20000) - 2 AL 11 BEST 2017-08-30 12:00:00 ... 274.424523 6.947623 IRMA POINT (-29.70000 16.30000) - 3 AL 11 BEST 2017-08-30 18:00:00 ... 270.154371 5.442611 IRMA POINT (-30.80000 16.30000) - 4 AL 11 BEST 2017-08-30 18:00:00 ... 270.154371 5.442611 IRMA POINT (-30.80000 16.30000) - .. ... ... ... ... ... ... ... ... ... - 168 AL 11 BEST 2017-09-12 12:00:00 ... 309.875306 7.262151 IRMA POINT (-86.90000 33.80000) - 169 AL 11 BEST 2017-09-12 18:00:00 ... 315.455084 7.247674 IRMA POINT (-88.10000 34.80000) - 170 AL 11 BEST 2017-09-13 00:00:00 ... 320.849994 5.315966 IRMA POINT (-88.90000 35.60000) - 171 AL 11 BEST 2017-09-13 06:00:00 ... 321.042910 3.973414 IRMA POINT (-89.50000 36.20000) - 172 AL 11 BEST 2017-09-13 12:00:00 ... 321.262133 3.961652 IRMA POINT (-90.10000 36.80000) - [173 rows x 22 columns] + basin storm_number datetime advisory_number ... isowave_radius_for_SWQ extra_values geometry track_start_time + 0 AL 11 2017-08-30 00:00:00 ... NaN POINT (-26.90000 16.10000) 2017-08-30 + 1 AL 11 2017-08-30 06:00:00 ... NaN POINT (-28.30000 16.20000) 2017-08-30 + 2 AL 11 2017-08-30 12:00:00 ... NaN POINT (-29.70000 16.30000) 2017-08-30 + 3 AL 11 2017-08-30 18:00:00 ... NaN POINT (-30.80000 16.30000) 2017-08-30 + 4 AL 11 2017-08-30 18:00:00 ... NaN POINT (-30.80000 16.30000) 2017-08-30 + .. ... ... ... ... ... ... ... ... ... + 168 AL 11 2017-09-12 12:00:00 ... NaN POINT (-86.90000 33.80000) 2017-08-30 + 169 AL 11 2017-09-12 18:00:00 ... NaN POINT (-88.10000 34.80000) 2017-08-30 + 170 AL 11 2017-09-13 00:00:00 ... NaN POINT (-88.90000 35.60000) 2017-08-30 + 171 AL 11 2017-09-13 06:00:00 ... NaN POINT (-89.50000 36.20000) 2017-08-30 + 172 AL 11 2017-09-13 12:00:00 ... NaN POINT (-90.10000 36.80000) 2017-08-30 + [173 rows x 38 columns] >>> track = VortexTrack('AL112017', file_deck='a') >>> track.data - basin storm_number advisory datetime ... direction speed name geometry - 0 AL 11 CARQ 2017-08-27 06:00:00 ... 0.000000 0.000000 INVEST POINT (-17.40000 11.70000) - 1 AL 11 CARQ 2017-08-27 12:00:00 ... 281.524268 2.574642 INVEST POINT (-17.90000 11.80000) - 2 AL 11 CARQ 2017-08-27 12:00:00 ... 281.524268 2.574642 INVEST POINT (-13.30000 11.50000) - 3 AL 11 CARQ 2017-08-27 18:00:00 ... 281.528821 2.573747 INVEST POINT (-18.40000 11.90000) - 4 AL 11 CARQ 2017-08-27 18:00:00 ... 281.528821 2.573747 INVEST POINT (-16.00000 11.50000) - ... ... ... ... ... ... ... ... ... ... - 10739 AL 11 HMON 2017-09-16 09:00:00 ... 52.414833 11.903071 POINT (-84.30000 43.00000) - 10740 AL 11 HMON 2017-09-16 12:00:00 ... 7.196515 6.218772 POINT (-84.30000 41.00000) - 10741 AL 11 HMON 2017-09-16 12:00:00 ... 7.196515 6.218772 POINT (-82.00000 39.50000) - 10742 AL 11 HMON 2017-09-16 12:00:00 ... 7.196515 6.218772 POINT (-84.30000 44.00000) - 10743 AL 11 HMON 2017-09-16 15:00:00 ... 122.402907 22.540200 POINT (-81.90000 39.80000) - [10744 rows x 22 columns + basin storm_number datetime advisory_number ... isowave_radius_for_SWQ extra_values geometry track_start_time + 0 AL 11 2017-08-27 06:00:00 01 ... NaN POINT (-17.40000 11.70000) 2017-08-28 06:00:00 + 1 AL 11 2017-08-27 12:00:00 01 ... NaN POINT (-17.90000 11.80000) 2017-08-28 06:00:00 + 2 AL 11 2017-08-27 18:00:00 01 ... NaN POINT (-18.40000 11.90000) 2017-08-28 06:00:00 + 3 AL 11 2017-08-28 00:00:00 01 ... NaN POINT (-19.00000 12.00000) 2017-08-28 06:00:00 + 4 AL 11 2017-08-28 06:00:00 01 ... NaN POINT (-19.50000 12.00000) 2017-08-28 06:00:00 + ... ... ... ... ... ... ... ... ... ... + 10739 AL 11 2017-09-12 00:00:00 03 ... NaN POINT (-84.40000 31.90000) 2017-09-12 00:00:00 + 10740 AL 11 2017-09-12 03:00:00 03 ... NaN POINT (-84.90000 32.40000) 2017-09-12 00:00:00 + 10741 AL 11 2017-09-12 12:00:00 03 ... NaN POINT (-86.40000 33.80000) 2017-09-12 00:00:00 + 10742 AL 11 2017-09-13 00:00:00 03 ... NaN POINT (-88.20000 35.20000) 2017-09-12 00:00:00 + 10743 AL 11 2017-09-13 12:00:00 03 ... NaN POINT (-88.60000 36.40000) 2017-09-12 00:00:00 + [10434 rows x 38 columns] """ return self.unfiltered_data.loc[ @@ -514,7 +487,6 @@ def to_file(self, path: PathLike, advisory: ATCF_Advisory = None, overwrite: boo data.to_csv(path, index=False, header=False) else: raise NotImplementedError(f'writing to `*{path.suffix}` not supported') - else: logging.warning(f'skipping existing file "{path}"') @@ -528,7 +500,11 @@ def atcf(self, advisory: ATCF_Advisory = None) -> DataFrame: :return: dataframe of CSV lines in ATCF format """ - atcf = DataFrame(self.data.drop(columns='geometry'), copy=True) + atcf = self.data.copy(deep=True) + atcf.loc[atcf['advisory'] != 'BEST', 'datetime'] = atcf.loc[ + atcf['advisory'] != 'BEST', 'track_start_time' + ] + atcf.drop(columns=['geometry', 'track_start_time'], inplace=True) if advisory is not None: if isinstance(advisory, ATCF_Advisory): @@ -653,169 +629,104 @@ def fort_22(self, advisory: ATCF_Advisory = None) -> DataFrame: :return: `fort.22` representation of the current track """ - fort22 = DataFrame(self.data.drop(columns='geometry'), copy=True) - - if advisory is not None: - if isinstance(advisory, ATCF_Advisory): - advisory = advisory.value - fort22 = fort22[fort22['advisory'] == advisory] + fort22 = self.atcf(advisory=advisory) fort22.drop( columns=[field for field in EXTRA_ATCF_FIELDS.values() if field in fort22.columns], inplace=True, ) - fort22.loc[:, ['longitude', 'latitude']] = ( - fort22.loc[:, ['longitude', 'latitude']] * 10 - ) - - float_columns = fort22.select_dtypes(include=['float']).columns - integer_na_value = -99999 - for column in float_columns: - fort22.loc[pandas.isna(fort22[column]), column] = integer_na_value - fort22.loc[:, column] = fort22.loc[:, column].round(0).astype(int) - - fort22['basin'] = fort22['basin'].str.pad(2) - fort22['storm_number'] = fort22['storm_number'].astype('string').str.pad(3) - fort22['datetime'] = fort22['datetime'].dt.strftime('%Y%m%d%H').str.pad(11) - fort22['advisory_number'] = fort22['advisory_number'].str.pad(3) - fort22['advisory'] = fort22['advisory'].str.pad(5) - fort22['forecast_hours'] = fort22['forecast_hours'].astype('string').str.pad(4) - - fort22['latitude'] = fort22['latitude'].astype('string') - fort22.loc[~fort22['latitude'].str.contains('-'), 'latitude'] = ( - fort22.loc[~fort22['latitude'].str.contains('-'), 'latitude'] + 'N' - ) - fort22.loc[fort22['latitude'].str.contains('-'), 'latitude'] = ( - fort22.loc[fort22['latitude'].str.contains('-'), 'latitude'] + 'S' - ) - fort22['latitude'] = fort22['latitude'].str.strip('-').str.pad(4) - - fort22['longitude'] = fort22['longitude'].astype('string') - fort22.loc[~fort22['longitude'].str.contains('-'), 'longitude'] = ( - fort22.loc[~fort22['longitude'].str.contains('-'), 'longitude'] + 'E' - ) - fort22.loc[fort22['longitude'].str.contains('-'), 'longitude'] = ( - fort22.loc[fort22['longitude'].str.contains('-'), 'longitude'] + 'W' - ) - fort22['longitude'] = fort22['longitude'].str.strip('-').str.pad(5) - - fort22['max_sustained_wind_speed'] = ( - fort22['max_sustained_wind_speed'].astype('string').str.pad(5) - ) - fort22['central_pressure'] = fort22['central_pressure'].astype('string').str.pad(5) - fort22['development_level'] = fort22['development_level'].str.pad(3) - fort22['isotach_radius'] = fort22['isotach_radius'].astype('string').str.pad(4) - fort22['isotach_quadrant_code'] = fort22['isotach_quadrant_code'].str.pad(4) - fort22['isotach_radius_for_NEQ'] = ( - fort22['isotach_radius_for_NEQ'].astype('string').str.pad(5) - ) - fort22['isotach_radius_for_SEQ'] = ( - fort22['isotach_radius_for_SEQ'].astype('string').str.pad(5) - ) - fort22['isotach_radius_for_NWQ'] = ( - fort22['isotach_radius_for_NWQ'].astype('string').str.pad(5) - ) - fort22['isotach_radius_for_SWQ'] = ( - fort22['isotach_radius_for_SWQ'].astype('string').str.pad(5) - ) - - fort22['background_pressure'].fillna(method='ffill', inplace=True) - fort22.loc[ - ~pandas.isna(self.data['central_pressure']) - & (self.data['background_pressure'] <= self.data['central_pressure']) - & (self.data['central_pressure'] < 1013), - 'background_pressure', - ] = '1013' - fort22.loc[ - ~pandas.isna(self.data['central_pressure']) - & (self.data['background_pressure'] <= self.data['central_pressure']) - & (self.data['central_pressure'] < 1013), - 'background_pressure', - ] = (self.data['central_pressure'] + 1) - fort22['background_pressure'] = ( - fort22['background_pressure'].astype(int).astype('string').str.pad(5) - ) - - fort22['radius_of_last_closed_isobar'] = ( - fort22['radius_of_last_closed_isobar'].astype('string').str.pad(5) - ) - fort22['radius_of_maximum_winds'] = ( - fort22['radius_of_maximum_winds'].astype('string').str.pad(4) - ) - fort22['gust_speed'] = fort22['gust_speed'].astype('string').str.pad(5) - fort22['eye_diameter'] = fort22['eye_diameter'].astype('string').str.pad(4) - fort22['subregion_code'] = fort22['subregion_code'].str.pad(4) - fort22['maximum_wave_height'] = ( - fort22['maximum_wave_height'].astype('string').str.pad(4) - ) - fort22['forecaster_initials'] = fort22['forecaster_initials'].str.pad(4) - - fort22['direction'] = fort22['direction'].astype('string').str.pad(3) - fort22['speed'] = fort22['speed'].astype('string').str.pad(4) - fort22['name'] = fort22['name'].astype('string').str.pad(12) + fort22['longitude'] = fort22['longitude'].str.strip().str.pad(4) + fort22['latitude'] = fort22['latitude'].str.strip().str.pad(5) + fort22['gust_speed'] = fort22['gust_speed'].str.strip().str.pad(5) + fort22['direction'] = fort22['direction'].str.strip().str.pad(3) + fort22['name'] = fort22['name'].str.strip().str.pad(12) + fort22.loc[fort22['name'] == '', 'name'] = self.name fort22['record_number'] = ( (self.data.groupby(['datetime']).ngroup() + 1).astype('string').str.pad(4) ) - for column in fort22.select_dtypes(include=['string']).columns: - fort22[column] = fort22[column].str.replace(re.compile(str(integer_na_value)), '') - return fort22 @property - def linestring(self) -> MultiLineString: + def linestrings(self) -> Dict[str, Dict[str, LineString]]: """ - :return: spatial linestring of current track + :return: spatial linestrings for every advisory and track """ - linestrings = [ - self.data[self.data['advisory'] == advisory] - .sort_values('datetime')['geometry'] - .drop_duplicates() - for advisory in pandas.unique(self.data['advisory']) - ] + configuration = self.__configuration - linestrings = [ - LineString(linestring.tolist()) - for linestring in linestrings - if len(linestring) > 1 - ] + # only proceed if the configuration has changed + if ( + self.__linestrings is None + or len(self.__linestrings) == 0 + or configuration != self.__previous_configuration + ): + tracks = self.tracks + + linestrings = {} + for advisory, advisory_tracks in tracks.items(): + linestrings[advisory] = {} + for track_start_time, track in advisory_tracks.items(): + geometries = track['geometry'] + if len(geometries) > 1: + geometries = geometries.drop_duplicates() + if len(geometries) > 1: + linestrings[advisory][track_start_time] = LineString( + geometries.to_list() + ) - if len(linestrings) > 0: - geometry = MultiLineString(linestrings) - else: - geometry = GeometryCollection(linestrings) + self.__linestrings = linestrings - return geometry + return self.__linestrings @property - def distance(self) -> float: + def distances(self) -> Dict[str, Dict[str, float]]: """ - :return: length, in meters, of the track over the default WGS84 that comes with pyPROJ + :return: length, in meters, of the track over WGS84 (``EPSG:4326``) """ - geodetic = Geod(ellps='WGS84') - _, _, distances = geodetic.inv( - self.data['longitude'].iloc[:-1], - self.data['latitude'].iloc[:-1], - self.data['longitude'].iloc[1:], - self.data['latitude'].iloc[1:], - ) - return numpy.sum(distances) + configuration = self.__configuration + + # only proceed if the configuration has changed + if ( + self.__distances is None + or len(self.__distances) == 0 + or configuration != self.__previous_configuration + ): + geodetic = Geod(ellps='WGS84') + + linestrings = self.linestrings + + distances = {} + for advisory, advisory_tracks in linestrings.items(): + distances[advisory] = {} + for track_start_time, linestring in advisory_tracks.items(): + x, y = linestring.xy + _, _, track_distances = geodetic.inv(x[:-1], y[:-1], x[1:], y[1:],) + distances[advisory][track_start_time] = numpy.sum(track_distances) + + self.__distances = distances + + return self.__distances def isotachs( self, wind_speed: float, segments: int = 91 - ) -> Dict[str, Dict[datetime, Polygon]]: + ) -> Dict[str, Dict[str, Dict[str, Polygon]]]: """ - calculate the isotach at the given speed at every time in the dataset + isotach at the given wind speed at every time in the dataset :param wind_speed: wind speed to extract (in knots) :param segments: number of discretization points per quadrant - :return: list of isotachs as polygons for each advisory type + :return: list of isotachs as polygons for each advisory type and individual track """ + valid_isotach_values = [34, 50, 64] + assert ( + wind_speed in valid_isotach_values + ), f'isotach must be one of {valid_isotach_values}' + # collect the attributes needed from the forcing to generate swath data = self.data[self.data['isotach_radius'] == wind_speed] @@ -832,118 +743,111 @@ def isotachs( geodetic = Geod(ellps='WGS84') + tracks = separate_tracks(data) + # generate overall swath based on the desired isotach isotachs = {} - for advisory in pandas.unique(data['advisory']): - advisory_data = data[data['advisory'] == advisory] - + for advisory, advisory_tracks in tracks.items(): advisory_isotachs = {} - for index, row in advisory_data.iterrows(): - # get the starting angle range for NEQ based on storm direction - rotation_angle = 360 - row['direction'] - start_angle = 0 + rotation_angle - end_angle = 90 + rotation_angle - - # append quadrants in counter-clockwise direction from NEQ - quadrants = [] - for quadrant_name in quadrant_names: - # skip if quadrant radius is zero - if row[quadrant_name] > 1: - # enter the angle range for this quadrant - theta = numpy.linspace(start_angle, end_angle, segments) - - # move angle to next quadrant - start_angle = start_angle + 90 - end_angle = end_angle + 90 - - # make the coordinate list for this quadrant using forward geodetic (origin,angle,dist) - vectorized_forward_geodetic = numpy.vectorize( - partial( - geodetic.fwd, - lons=row['longitude'], - lats=row['latitude'], - dist=row[quadrant_name], + for track_start_time, track_data in advisory_tracks.items(): + track_isotachs = {} + for index, row in track_data.iterrows(): + # get the starting angle range for NEQ based on storm direction + rotation_angle = 360 - row['direction'] + start_angle = 0 + rotation_angle + end_angle = 90 + rotation_angle + + # append quadrants in counter-clockwise direction from NEQ + quadrants = [] + for quadrant_name in quadrant_names: + # skip if quadrant radius is zero + if row[quadrant_name] > 1: + # enter the angle range for this quadrant + theta = numpy.linspace(start_angle, end_angle, segments) + + # move angle to next quadrant + start_angle = start_angle + 90 + end_angle = end_angle + 90 + + # make the coordinate list for this quadrant using forward geodetic (origin,angle,dist) + vectorized_forward_geodetic = numpy.vectorize( + partial( + geodetic.fwd, + lons=row['longitude'], + lats=row['latitude'], + dist=row[quadrant_name], + ) + ) + x, y, reverse_azimuth = vectorized_forward_geodetic(az=theta) + vertices = numpy.stack([x, y], axis=1) + + # insert center point at beginning and end of list + vertices = numpy.concatenate( + [ + row[['longitude', 'latitude']].values[None, :], + vertices, + row[['longitude', 'latitude']].values[None, :], + ], + axis=0, ) - ) - x, y, reverse_azimuth = vectorized_forward_geodetic(az=theta) - vertices = numpy.stack([x, y], axis=1) - - # insert center point at beginning and end of list - vertices = numpy.concatenate( - [ - row[['longitude', 'latitude']].values[None, :], - vertices, - row[['longitude', 'latitude']].values[None, :], - ], - axis=0, - ) - - quadrants.append(Polygon(vertices)) - if len(quadrants) > 0: - isotach = ops.unary_union(quadrants) + quadrants.append(Polygon(vertices)) - if isinstance(isotach, MultiPolygon): - isotach = isotach.buffer(1e-10) + if len(quadrants) > 0: + isotach = ops.unary_union(quadrants) - advisory_isotachs[row['datetime']] = isotach + if isinstance(isotach, MultiPolygon): + isotach = isotach.buffer(1e-10) + track_isotachs[f'{row["datetime"]}:%Y%m%dT%H%M%S'] = isotach + if len(track_isotachs) > 0: + advisory_isotachs[track_start_time] = track_isotachs if len(advisory_isotachs) > 0: isotachs[advisory] = advisory_isotachs - return isotachs - def wind_swaths(self, wind_speed: int, segments: int = 91) -> Dict[str, Polygon]: + def wind_swaths( + self, wind_speed: int, segments: int = 91 + ) -> Dict[str, Dict[str, Polygon]]: """ - extract wind swaths (per advisory type) of the track as polygons + wind swaths (per advisory type) for each advisory and track, as polygons :param wind_speed: wind speed in knots (one of ``34``, ``50``, or ``64``) :param segments: number of discretization points per quadrant (default = ``91``) """ - valid_isotach_values = [34, 50, 64] - assert ( - wind_speed in valid_isotach_values - ), f'isotach must be one of {valid_isotach_values}' - - advisory_isotachs = self.isotachs(wind_speed=wind_speed, segments=segments) + isotachs = self.isotachs(wind_speed=wind_speed, segments=segments) wind_swaths = {} - for advisory, isotachs in advisory_isotachs.items(): - isotachs = list(isotachs.values()) - convex_hulls = [] - for index in range(len(isotachs) - 1): - convex_hulls.append( - ops.unary_union([isotachs[index], isotachs[index + 1]]).convex_hull - ) - - # get the union of polygons - wind_swaths[advisory] = ops.unary_union(convex_hulls) + for advisory, advisory_isotachs in isotachs.items(): + advisory_wind_swaths = {} + for track_start_time, track_isotachs in advisory_isotachs.items(): + convex_hulls = [] + isotach_times = list(track_isotachs) + for index in range(len(isotach_times) - 1): + convex_hulls.append( + ops.unary_union( + [ + track_isotachs[isotach_times[index]], + track_isotachs[isotach_times[index + 1]], + ] + ).convex_hull + ) + + if len(convex_hulls) > 0: + # get the union of polygons + advisory_wind_swaths[track_start_time] = ops.unary_union(convex_hulls) + if len(advisory_isotachs) > 0: + wind_swaths[advisory] = advisory_wind_swaths return wind_swaths @property - def forecasts(self) -> Dict[str, Dict[str, DataFrame]]: - data = self.data - - forecast_advisories = ( - advisory for advisory in pandas.unique(data['advisory']) if advisory != 'BEST' - ) - - forecasts = {} - for advisory in forecast_advisories: - advisory_data = data[data['advisory'] == advisory] - - initial_times = pandas.unique(advisory_data['datetime']) - - forecasts[advisory] = {} - for initial_time in initial_times: - forecast_data = advisory_data[advisory_data['datetime'] == initial_time] - forecasts[advisory][ - f'{pandas.to_datetime(initial_time):%Y%m%dT%H%M%S}' - ] = forecast_data.sort_values('forecast_hours') - - return forecasts + def tracks(self) -> Dict[str, Dict[str, DataFrame]]: + """ + :return: individual tracks sorted into advisories and initial times + """ + return separate_tracks(self.data) @property def duration(self) -> pandas.Timedelta: @@ -956,36 +860,59 @@ def duration(self) -> pandas.Timedelta: @property def unfiltered_data(self) -> DataFrame: """ - :return: data frame containing all track data for the specified storm and file deck + :return: data frame containing all track data for the specified storm and file deck; NOTE: datetimes for forecasts represent the initial datetime of the forecast, not the datetime of the record """ configuration = self.__configuration - # only download new file if the configuration has changed since the last download + # only proceed if the configuration has changed if ( self.__unfiltered_data is None or len(self.__unfiltered_data) == 0 or configuration != self.__previous_configuration ): + advisories = self.advisories if configuration['filename'] is not None: - advisories = None if self.advisory is None else [self.advisory] atcf_file = configuration['filename'] else: - advisories = ( - self.__valid_advisories if self.advisory is None else [self.advisory] - ) - url = atcf_url(self.nhc_code, self.file_deck, self.mode) + url = atcf_url(self.nhc_code, self.file_deck) + try: + response = urlopen(url) + except URLError: + url = atcf_url(self.nhc_code, self.file_deck, mode=ATCF_Mode.HISTORICAL) + try: + response = urlopen(url) + except URLError: + raise ConnectionError(f'could not connect to {url}') atcf_file = io.BytesIO() - atcf_file.write(urlopen(url).read()) + atcf_file.write(response.read()) atcf_file.seek(0) if url.endswith('.gz'): atcf_file = gzip.GzipFile(fileobj=atcf_file, mode='rb') - dataframe = read_atcf(atcf_file, advisories=advisories) + if 'OFCL' in advisories and 'CARQ' not in advisories: + self.__advisories_to_remove.append(ATCF_Advisory.CARQ) + + dataframe = read_atcf( + atcf_file, advisories=advisories + self.__advisories_to_remove + ) dataframe.sort_values(['datetime', 'advisory'], inplace=True) dataframe.reset_index(inplace=True, drop=True) - self.__unfiltered_data = dataframe + dataframe['track_start_time'] = dataframe['datetime'].copy() + if ATCF_Advisory.BEST in self.advisories: + dataframe.loc[dataframe['advisory'] == 'BEST', 'track_start_time'] = ( + dataframe.loc[dataframe['advisory'] == 'BEST', 'datetime'] + .sort_values() + .iloc[0] + ) + + dataframe.loc[dataframe['advisory'] != 'BEST', 'datetime'] += pandas.to_timedelta( + dataframe.loc[dataframe['advisory'] != 'BEST', 'forecast_hours'].astype(int), + unit='hours', + ) + + self.unfiltered_data = dataframe self.__previous_configuration = configuration # if location values have changed, recompute velocity @@ -1007,6 +934,61 @@ def unfiltered_data(self) -> DataFrame: @unfiltered_data.setter def unfiltered_data(self, dataframe: DataFrame): + # fill missing values of MRD and MSLP in the OFCL advisory + if 'OFCL' in self.advisories: + tracks = separate_tracks(dataframe) + + if 'OFCL' in tracks: + ofcl_tracks = tracks['OFCL'] + carq_tracks = tracks['CARQ'] + + for initial_time, forecast in ofcl_tracks.items(): + if initial_time in carq_tracks: + carq_forecast = carq_tracks[initial_time] + else: + carq_forecast = carq_tracks[list(carq_tracks)[0]] + + relation = HollandBRelation() + holland_b = relation.holland_b( + max_sustained_wind_speed=carq_forecast['max_sustained_wind_speed'], + background_pressure=carq_forecast['background_pressure'], + central_pressure=carq_forecast['central_pressure'], + ) + + holland_b[holland_b == numpy.inf] = numpy.nan + holland_b = numpy.nanmean(holland_b) + + mrd_missing = pandas.isna(forecast['radius_of_maximum_winds']) + mslp_missing = pandas.isna(forecast['central_pressure']) + radp_missing = pandas.isna(forecast['background_pressure']) + + # fill OFCL maximum wind radius with the first entry from the CARQ advisory + forecast.loc[mrd_missing, 'radius_of_maximum_winds'] = carq_forecast[ + 'radius_of_maximum_winds' + ].iloc[0] + + # fill OFCL background pressure with the first entry from the CARQ advisory central pressure (at sea level) + forecast.loc[radp_missing, 'background_pressure'] = carq_forecast[ + 'central_pressure' + ].iloc[0] + + # fill OFCL central pressure (at sea level) with the 3rd hour entry, preserving Holland B + forecast.loc[mslp_missing, 'central_pressure'] = relation.central_pressure( + max_sustained_wind_speed=forecast.loc[ + mslp_missing, 'max_sustained_wind_speed' + ], + background_pressure=forecast.loc[mslp_missing, 'background_pressure'], + holland_b=holland_b, + ) + + if len(self.__advisories_to_remove) > 0: + dataframe = dataframe[ + ~dataframe['advisory'].isin( + [value.value for value in self.__advisories_to_remove] + ) + ] + self.__advisories_to_remove = [] + self.__unfiltered_data = dataframe @property @@ -1014,8 +996,7 @@ def __configuration(self) -> Dict[str, Any]: return { 'id': self.nhc_code, 'file_deck': self.file_deck, - 'mode': self.mode, - 'advisory': self.advisory, + 'advisories': self.advisories, 'filename': self.filename, } @@ -1078,7 +1059,7 @@ def __copy__(self) -> 'VortexTrack': start_date=self.start_date, end_date=self.end_date, file_deck=self.file_deck, - advisory=self.advisory, + advisories=self.advisories, ) if self.filename is not None: instance.filename = self.filename @@ -1088,7 +1069,71 @@ def __eq__(self, other: 'VortexTrack') -> bool: return self.data.equals(other.data) def __str__(self) -> str: - return f'{self.nhc_code} ({" + ".join(pandas.unique(self.data["advisory"]).tolist())}) track with {len(self)} entries, spanning {self.distance:.2f} meters over {self.duration}' + return f'{self.nhc_code} ({" + ".join(pandas.unique(self.data["advisory"]).tolist())}) track with {len(self)} entries, spanning {self.distances:.2f} meters over {self.duration}' def __repr__(self) -> str: - return f'{self.__class__.__name__}({", ".join(repr(value) for value in [self.nhc_code, self.start_date, self.end_date, self.file_deck, self.mode, self.advisory, self.filename])})' + return f'{self.__class__.__name__}({", ".join(repr(value) for value in [self.nhc_code, self.start_date, self.end_date, self.file_deck, self.advisories, self.filename])})' + + +class HollandBRelation: + def __init__(self, rho: float = None): + if rho is None: + rho = 1.15 + self.rho = rho + + def holland_b( + self, + max_sustained_wind_speed: float, + background_pressure: float, + central_pressure: float, + ) -> float: + return ((max_sustained_wind_speed ** 2) * self.rho * numpy.exp(1)) / ( + background_pressure - central_pressure + ) + + def central_pressure( + self, max_sustained_wind_speed: float, background_pressure: float, holland_b: float, + ) -> float: + return ( + -(max_sustained_wind_speed ** 2) * self.rho * numpy.exp(1) + ) / holland_b + background_pressure + + def max_sustained_wind_speed( + self, holland_b: float, background_pressure: float, central_pressure: float, + ) -> float: + return numpy.sqrt( + holland_b * (background_pressure - central_pressure) / (self.rho * numpy.exp(1)) + ) + + +def separate_tracks(data: DataFrame) -> Dict[str, Dict[str, DataFrame]]: + """ + separate the given track data frame into advisories and tracks (forecasts / hindcasts) + + :param data: data frame of track + :return: dictionary of forecasts for each advisory (aside from best track ``BEST``, which only has one hindcast) + """ + + tracks = {} + for advisory in pandas.unique(data['advisory']): + advisory_data = data[data['advisory'] == advisory] + + if advisory == 'BEST': + advisory_data = advisory_data.sort_values('datetime') + + track_start_times = advisory_data['track_start_time'] + + tracks[advisory] = {} + for track_start_time in track_start_times: + if advisory == 'BEST': + track_data = advisory_data + else: + track_data = advisory_data[ + advisory_data['datetime'] == pandas.to_datetime(track_start_time) + ].sort_values('forecast_hours') + + tracks[advisory][ + f'{pandas.to_datetime(track_start_time):%Y%m%dT%H%M%S}' + ] = track_data + + return tracks diff --git a/stormevents/stormevent.py b/stormevents/stormevent.py index 2686498..a0d9c99 100644 --- a/stormevents/stormevent.py +++ b/stormevents/stormevent.py @@ -1,12 +1,13 @@ from datetime import datetime from functools import lru_cache from os import PathLike +from typing import List import pandas from shapely import ops from shapely.geometry import MultiPolygon, Polygon from shapely.geometry.base import BaseGeometry -import typepigeon +from shapely.ops import shape as shapely_shape import xarray from xarray import Dataset @@ -21,7 +22,7 @@ COOPS_Units, ) from stormevents.nhc import nhc_storms, VortexTrack -from stormevents.nhc.atcf import ATCF_FileDeck, ATCF_Mode +from stormevents.nhc.atcf import ATCF_Advisory, ATCF_FileDeck, ATCF_Mode from stormevents.usgs import usgs_flood_storms, USGS_StormEvent from stormevents.utilities import relative_to_time_interval, subset_time_interval @@ -42,19 +43,19 @@ def __init__( :param end_date: ending time >>> StormEvent('florence', 2018) - StormEvent('FLORENCE', 2018) + StormEvent(name='FLORENCE', year=2018, start_date=Timestamp('2018-08-30 06:00:00'), end_date=Timestamp('2018-09-18 12:00:00')) >>> StormEvent('paine', 2016, start_date='2016-09-18', end_date=datetime(2016, 9, 19, 12)) - StormEvent('PAINE', 2016, start_date='2016-09-19 00:00:00', end_date='2016-09-19 12:00:00') + StormEvent(name='PAINE', year=2016, start_date=Timestamp('2016-09-18 00:00:00'), end_date=datetime.datetime(2016, 9, 19, 12, 0)) >>> StormEvent('florence', 2018, start_date=timedelta(days=2)) - StormEvent('FLORENCE', 2018, start_date='2018-09-01 06:00:00') + StormEvent(name='FLORENCE', year=2018, start_date=Timestamp('2018-09-01 06:00:00'), end_date=Timestamp('2018-09-18 12:00:00')) >>> StormEvent('henri', 2021, start_date=timedelta(days=-3), end_date=timedelta(days=-2)) - StormEvent('HENRI', 2021, start_date='2021-08-21 12:00:00', end_date='2021-08-22 12:00:00') + StormEvent(name='HENRI', year=2021, start_date=Timestamp('2021-08-21 12:00:00'), end_date=Timestamp('2021-08-22 12:00:00')) >>> StormEvent('ida', 2021, end_date=timedelta(days=2)) - StormEvent('IDA', 2021, end_date='2021-08-29 18:00:00') + StormEvent(name='IDA', year=2021, start_date=Timestamp('2021-08-27 18:00:00'), end_date=Timestamp('2021-08-29 18:00:00')) """ storms = nhc_storms(year=year) @@ -84,7 +85,7 @@ def from_nhc_code( :return: storm object >>> StormEvent.from_nhc_code('EP172016') - StormEvent('PAINE', 2016) + StormEvent(name='PAINE', year=2016, start_date=Timestamp('2016-09-18 00:00:00'), end_date=Timestamp('2016-09-21 12:00:00')) """ try: @@ -118,7 +119,7 @@ def from_usgs_id( :return: storm object >>> StormEvent.from_usgs_id(310) - StormEvent('HENRI', 2021, end_date='2021-08-24 12:00:00') + StormEvent(name='HENRI', year=2021, start_date=Timestamp('2021-08-20 18:00:00'), end_date=Timestamp('2021-08-24 12:00:00')) """ flood_events = usgs_flood_storms(year=year) @@ -236,14 +237,12 @@ def __data_end(self) -> datetime: data_end = VortexTrack.from_storm_name(self.name, self.year).end_date return data_end - @lru_cache(maxsize=None) def track( self, start_date: datetime = None, end_date: datetime = None, file_deck: ATCF_FileDeck = None, - mode: ATCF_Mode = None, - advisory: str = None, + advisories: List[ATCF_Advisory] = None, filename: PathLike = None, ) -> VortexTrack: """ @@ -252,14 +251,13 @@ def track( :param start_date: start date :param end_date: end date :param file_deck: ATCF file deck - :param mode: ATCF mode - :param advisory: ATCF record type + :param advisories: ATCF advisory types :param filename: file path to ``fort.22`` :return: vortex track >>> storm = StormEvent('florence', 2018) - >>> storm.track(file_deck='b') - VortexTrack('AL062018', Timestamp('2018-08-30 06:00:00'), Timestamp('2018-09-18 12:00:00'), , , 'BEST', None) + >>> storm.track() + VortexTrack('AL062018', Timestamp('2018-08-30 06:00:00'), Timestamp('2018-09-18 12:00:00'), , , [], None) """ if start_date is None: @@ -276,8 +274,7 @@ def track( start_date=start_date, end_date=end_date, file_deck=file_deck, - mode=mode, - advisory=advisory, + advisories=advisories, ) return track @@ -314,6 +311,7 @@ def coops_product_within_isotach( self, product: COOPS_Product, wind_speed: int, + advisories: List[ATCF_Advisory] = None, station_type: COOPS_StationStatus = None, start_date: datetime = None, end_date: datetime = None, @@ -328,6 +326,7 @@ def coops_product_within_isotach( :param product: CO-OPS product :param wind_speed: wind speed in knots (one of ``34``, ``50``, or ``64``) + :param advisories: ATCF advisory types :param start_date: start date :param end_date: end date :param station_type: either ``current`` or ``historical`` @@ -358,10 +357,17 @@ def coops_product_within_isotach( if isinstance(track, VortexTrack): track.start_date = start_date track.end_date = end_date + track.advisories = advisories else: - track = self.track(start_date=start_date, end_date=end_date, filename=track) + track = self.track( + start_date=start_date, end_date=end_date, filename=track, advisories=advisories + ) - region = ops.unary_union(list(track.wind_swaths(wind_speed).values())) + polygons = [] + wind_swaths = track.wind_swaths(wind_speed) + for advisory, advisory_wind_swaths in wind_swaths.items(): + polygons.append(ops.unary_union(list(advisory_wind_swaths.values()))) + region = ops.unary_union(polygons) return self.coops_product_within_region( region=region, @@ -403,7 +409,7 @@ def coops_product_within_region( >>> import shapely >>> storm = StormEvent('florence', 2018) - >>> region = shapely.geometry.box(*storm.track().linestring.bounds) + >>> region = shapely.geometry.box(*storm.track().linestrings.bounds) >>> storm.coops_product_within_region('water_level', region=region, start_date='2018-09-12 14:03:00', end_date='2018-09-14') Dimensions: (nos_id: 89, t: 340) @@ -421,10 +427,7 @@ def coops_product_within_region( """ if not isinstance(region, BaseGeometry): - try: - region = typepigeon.convert_value(region, MultiPolygon) - except ValueError: - region = typepigeon.convert_value(region, Polygon) + region = shapely_shape(region) if start_date is None: start_date = self.start_date diff --git a/stormevents/usgs/events.py b/stormevents/usgs/events.py index 55330d0..3d0422a 100644 --- a/stormevents/usgs/events.py +++ b/stormevents/usgs/events.py @@ -42,20 +42,21 @@ def usgs_flood_events( >>> usgs_flood_events() - name year ... start_date end_date - usgs_id ... - 7 FEMA 2013 exercise 2013 ... 2013-05-15 04:00:00 2013-05-23 04:00:00 - 8 Wilma 2005 ... 2005-10-20 00:00:00 2005-10-31 00:00:00 - 9 Midwest Floods 2011 2011 ... 2011-02-01 06:00:00 2011-08-30 05:00:00 - 10 2013 - June PA Flood 2013 ... 2013-06-23 00:00:00 2013-07-01 00:00:00 - 11 Colorado 2013 Front Range Flood 2013 ... 2013-09-12 05:00:00 2013-09-24 05:00:00 - ... ... ... ... ... ... - 311 2021 August Flash Flood TN 2021 ... 2021-08-21 05:00:00 2021-08-22 05:00:00 - 312 2021 Tropical Cyclone Ida 2021 ... 2021-08-27 05:00:00 2021-09-03 05:00:00 - 313 Chesapeake Bay - October 2021 2021 ... 2021-10-28 04:00:00 NaT - 314 2021 November Flooding Washington State 2021 ... 2021-11-08 06:00:00 2021-11-19 06:00:00 - 315 Washington Coastal Winter 2021-2022 2021 ... 2021-11-01 05:00:00 2022-06-30 05:00:00 - [292 rows x 11 columns] + name year description ... last_updated_by start_date end_date + usgs_id ... + 7 FEMA 2013 exercise 2013 Ardent/Sentry 2013 FEMA Exercise ... NaN 2013-05-15 04:00:00 2013-05-23 04:00:00 + 8 Wilma 2005 Category 3 in west FL. \nHurricane Wilma was t... ... NaN 2005-10-20 00:00:00 2005-10-31 00:00:00 + 9 Midwest Floods 2011 2011 Spring and summer 2011 flooding of the Mississ... ... 35.0 2011-02-01 06:00:00 2011-08-30 05:00:00 + 10 2013 - June PA Flood 2013 Localized summer rain, small scale event ... NaN 2013-06-23 00:00:00 2013-07-01 00:00:00 + 11 Colorado 2013 Front Range Flood 2013 A large prolonged precipitation event resulted... ... 35.0 2013-09-12 05:00:00 2013-09-24 05:00:00 + ... ... ... ... ... ... ... ... + 312 2021 Tropical Cyclone Ida 2021 NaN ... 864.0 2021-08-27 05:00:00 2021-09-03 05:00:00 + 313 Chesapeake Bay - October 2021 2021 Coastal-flooding event in the Chesapeake Bay. ... 406.0 2021-10-28 04:00:00 NaT + 314 2021 November Flooding Washington State 2021 Atmospheric River Flooding ... 864.0 2021-11-08 06:00:00 2021-11-19 06:00:00 + 315 Washington Coastal Winter 2021-2022 2021 NaN ... 864.0 2021-11-01 05:00:00 2022-06-30 05:00:00 + 317 2022 Hunga Tonga-Hunga Haapai tsunami 2022 ... 1.0 2022-01-14 05:00:00 2022-01-18 05:00:00 + + [293 rows x 11 columns] """ events = pandas.read_json('https://stn.wim.usgs.gov/STNServices/Events.json') @@ -145,8 +146,7 @@ def usgs_flood_storms(year: int = None) -> DataFrame: storms = nhc_storms(tuple(pandas.unique(events['year']))) - storm_names = pandas.unique(storms['name'].str.strip()) - storm_names.sort() + storm_names = sorted(pandas.unique(storms['name'].str.strip())) for storm_name in storm_names: event_storms = events[ events['usgs_name'].str.contains(storm_name, flags=re.IGNORECASE) @@ -380,22 +380,37 @@ def high_water_marks( """ :returns: data frame of data for the current parameters - >>> flood = USGS_Event(23) + >>> flood = USGS_Event(182) >>> flood.high_water_marks() - latitude longitude eventName hwmTypeName ... approval_id hwm_uncertainty uncertainty geometry - hwm_id ... - 14699 38.917360 -75.947890 Irene Debris ... NaN NaN NaN POINT (-75.94789 38.91736) - 14700 38.917360 -75.947890 Irene Mud ... NaN NaN NaN POINT (-75.94789 38.91736) - 14701 38.917580 -75.948470 Irene Mud ... NaN NaN NaN POINT (-75.94847 38.91758) - 14702 38.917360 -75.946060 Irene Stain line ... NaN NaN NaN POINT (-75.94606 38.91736) - 14703 38.917580 -75.945970 Irene Mud ... NaN NaN NaN POINT (-75.94597 38.91758) - ... ... ... ... ... ... ... ... ... ... - 41666 44.184900 -72.823970 Irene Other (Note in Description box) ... 24707.0 NaN NaN POINT (-72.82397 44.18490) - 41667 43.616332 -72.658893 Irene Clear water ... 24706.0 NaN NaN POINT (-72.65889 43.61633) - 41668 43.617370 -72.667530 Irene Seed line ... 24705.0 NaN NaN POINT (-72.66753 43.61737) - 41670 43.524600 -72.677540 Irene Seed line ... NaN NaN NaN POINT (-72.67754 43.52460) - 41671 43.534470 -72.672750 Irene Seed line ... NaN NaN NaN POINT (-72.67275 43.53447) - [1300 rows x 51 columns] + latitude longitude eventName hwmTypeName ... hwm_uncertainty hwm_notes siteZone geometry + hwm_id ... + 22602 31.170642 -81.428402 Irma September 2017 Debris ... NaN NaN NaN POINT (-81.42840 31.17064) + 22605 31.453850 -81.362853 Irma September 2017 Seed line ... 0.1 NaN NaN POINT (-81.36285 31.45385) + 22612 30.720000 -81.549440 Irma September 2017 Seed line ... NaN There is a secondary peak around 5.5 ft, so th... NaN POINT (-81.54944 30.72000) + 22636 32.007730 -81.238270 Irma September 2017 Seed line ... 0.1 Trimble R8 used to establish TBM. Levels ran f... NaN POINT (-81.23827 32.00773) + 22653 31.531078 -81.358894 Irma September 2017 Seed line ... NaN NaN NaN POINT (-81.35889 31.53108) + ... ... ... ... ... ... ... ... ... ... + 26171 18.470402 -66.246631 Irma September 2017 Debris ... 0.5 NaN NaN POINT (-66.24663 18.47040) + 26173 18.470300 -66.449900 Irma September 2017 Debris ... 0.5 levels from GNSS BM NaN POINT (-66.44990 18.47030) + 26175 18.463954 -66.140869 Irma September 2017 Debris ... 0.5 levels from GNSS BM NaN POINT (-66.14087 18.46395) + 26177 18.488720 -66.392160 Irma September 2017 Debris ... 0.5 levels from GNSS BM NaN POINT (-66.39216 18.48872) + 26179 18.005607 -65.871768 Irma September 2017 Debris ... 0.5 levels from GNSS BM NaN POINT (-65.87177 18.00561) + [506 rows x 53 columns] + >>> flood.high_water_marks(quality=['EXCELLENT', 'GOOD']) + latitude longitude eventName hwmTypeName ... hwm_notes peak_summary_id siteZone geometry + hwm_id ... + 22605 31.453850 -81.362853 Irma September 2017 Seed line ... NaN NaN NaN POINT (-81.36285 31.45385) + 22612 30.720000 -81.549440 Irma September 2017 Seed line ... There is a secondary peak around 5.5 ft, so th... NaN NaN POINT (-81.54944 30.72000) + 22636 32.007730 -81.238270 Irma September 2017 Seed line ... Trimble R8 used to establish TBM. Levels ran f... NaN NaN POINT (-81.23827 32.00773) + 22674 32.030907 -80.900605 Irma September 2017 Seed line ... NaN 5042.0 NaN POINT (-80.90061 32.03091) + 22849 30.741940 -81.687780 Irma September 2017 Debris ... NaN 4834.0 NaN POINT (-81.68778 30.74194) + ... ... ... ... ... ... ... ... ... ... + 25150 30.038222 -81.880928 Irma September 2017 Seed line ... GNSS Level II survey. NaN NaN POINT (-81.88093 30.03822) + 25151 30.118110 -81.760220 Irma September 2017 Seed line ... GNSS Level III survey. NaN NaN POINT (-81.76022 30.11811) + 25158 29.720560 -81.506110 Irma September 2017 Seed line ... GNSS Level II survey. NaN NaN POINT (-81.50611 29.72056) + 25159 30.097514 -81.794375 Irma September 2017 Seed line ... GNSS Level III survey. NaN NaN POINT (-81.79438 30.09751) + 25205 29.783890 -81.263060 Irma September 2017 Seed line ... GNSS Level II survey. NaN NaN POINT (-81.26306 29.78389) + [277 rows x 53 columns] """ if self.__query is None: diff --git a/tests/data/reference/test_coops_product_within_region/data.nc b/tests/data/reference/test_coops_product_within_region/data.nc index 2af4ba743041b74377ab495372ad48c964e5ad35..731d057573db5d5057702dae6a71bc86cd6d8234 100644 GIT binary patch delta 475 zcmX>#hw;=L#tj}UlOtFhq>hNRGJwE42?(9U525*_Cof_to235OrS zvW`m%WF42yZrSJYunLHi9JsD0J4~)%O_=-wsE~o-Kz)RziB0)FdApz;a&}zT7h8j{ zQ$niEm%kx4n=RO$vP{u8Ah@8|(xY*8+?4B77X?=)Kw2V3X|R Y|Go~=IW`Vp`8ie)I=~J>@3C|M0I*O}ZvX%Q delta 452 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 j;PyWPEN=F%JWE&m9|IOQ`-gd1!N5=1utfBIw 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 0 + assert len(abf_realtime) > 0 + assert len(a_2014_2015) > 0 + assert len(abf_2014_2015) > 0 def test_atcf_entry(): diff --git a/tests/test_nhc.py b/tests/test_nhc.py index 31c61b7..db9537e 100644 --- a/tests/test_nhc.py +++ b/tests/test_nhc.py @@ -61,9 +61,7 @@ def test_vortex_track(): ] for storm in storms: - track = VortexTrack.from_storm_name( - *storm, start_date=timedelta(days=-1), file_deck='a' - ) + track = VortexTrack.from_storm_name(*storm, file_deck='b') track.to_file( output_directory / f'{track.name.lower()}{track.year}.fort.22', overwrite=True ) @@ -71,40 +69,48 @@ def test_vortex_track(): check_reference_directory(output_directory, reference_directory) +def test_vortex_track_isotachs(): + track_1 = VortexTrack('florence2018') + track_2 = VortexTrack('florence2018', file_deck='a') + + track_1.isotachs(34) + track_2.isotachs(34) + + def test_vortex_track_properties(): track = VortexTrack('florence2018', file_deck='a') - assert len(track) == 10234 + assert len(track) == 10090 track.start_date = timedelta(days=1) - assert len(track) == 9877 + assert len(track) == 10080 track.end_date = timedelta(days=-1) - assert len(track) == 9826 + assert len(track) == 9894 - track.advisory = 'OFCL' + track.advisories = 'OFCL' - assert len(track) == 1273 + assert len(track) == 1249 track.end_date = None - assert len(track) == 1296 + assert len(track) == 1289 track.nhc_code = 'AL072018' assert len(track) == 175 -def test_vortex_track_forecasts(): +def test_vortex_track_tracks(): track = VortexTrack.from_storm_name('florence', 2018, file_deck='a') - forecasts = track.forecasts + tracks = track.tracks - assert len(forecasts) == 4 - assert len(forecasts['OFCL']) == 77 - assert len(forecasts['OFCL']['20180830T120000']) == 13 + assert len(tracks) == 4 + assert len(tracks['OFCL']) == 77 + assert len(tracks['OFCL']['20180831T000000']) == 2 @pytest.mark.disable_socket @@ -154,6 +160,15 @@ def test_vortex_track_to_file(): check_reference_directory(output_directory, reference_directory) +@pytest.mark.skip +def test_vortex_track_distances(): + track_1 = VortexTrack.from_storm_name('florence', 2018) + track_2 = VortexTrack.from_storm_name('florence', 2018, file_deck='a', advisories=['OFCL']) + + assert track_1.distances['BEST']['20180830T060000'] == 8725961.838567913 + assert track_2.distances['OFCL']['20180831T000000'] == 15490.033837939689 + + def test_vortex_track_recompute_velocity(): output_directory = OUTPUT_DIRECTORY / 'test_vortex_track_recompute_velocity' reference_directory = REFERENCE_DIRECTORY / 'test_vortex_track_recompute_velocity' @@ -198,7 +213,7 @@ def test_vortex_track_file_decks(): start_date=values['start_date'], end_date=values['end_date'], file_deck=file_deck, - advisory=advisory, + advisories=advisory, ) track.to_file(output_directory / f'{file_deck}-deck_{advisory}.22', overwrite=True) @@ -231,6 +246,6 @@ def test_vortex_track_no_internet(): track_3.to_file(output_directory / 'vortex_3.22', overwrite=True) assert track_1 == track_2 - assert track_1 == track_3 + assert track_1 != track_3 # these are not the same because of the velocity recalculation check_reference_directory(output_directory, reference_directory) diff --git a/tests/test_stormevent.py b/tests/test_stormevent.py index a81f9a5..732ecbe 100644 --- a/tests/test_stormevent.py +++ b/tests/test_stormevent.py @@ -1,6 +1,5 @@ from datetime import datetime, timedelta import os -import sys import pytest from shapely.geometry import box @@ -66,14 +65,14 @@ def test_storm_event_lookup(): assert henri2021.nhc_code == 'AL082021' assert henri2021.usgs_id == 310 assert henri2021.start_date == datetime(2021, 8, 20, 18) - assert henri2021.end_date == datetime(2021, 8, 24, 12) + assert henri2021.end_date == datetime(2021, 8, 24, 18) assert ida2021.name == 'IDA' assert ida2021.year == 2021 assert ida2021.nhc_code == 'AL092021' assert ida2021.usgs_id == 312 assert ida2021.start_date == datetime(2021, 8, 27, 18) - assert ida2021.end_date == datetime(2021, 9, 2, 6) + assert ida2021.end_date == datetime(2021, 9, 4, 18) def test_storm_event_time_interval(): @@ -105,10 +104,10 @@ def test_storm_event_time_interval(): ) assert henri2021.start_date == datetime(2021, 8, 20, 18) - assert henri2021.end_date == datetime(2021, 8, 22, 12) + assert henri2021.end_date == datetime(2021, 8, 22, 18) assert ( repr(henri2021) - == "StormEvent(name='HENRI', year=2021, start_date=Timestamp('2021-08-20 18:00:00'), end_date=Timestamp('2021-08-22 12:00:00'))" + == "StormEvent(name='HENRI', year=2021, start_date=Timestamp('2021-08-20 18:00:00'), end_date=Timestamp('2021-08-22 18:00:00'))" ) assert ida2021.start_date == datetime(2021, 8, 30) @@ -160,7 +159,7 @@ def test_storm_event_coops_product_within_isotach(florence2018): os.remove(path) null_data = florence2018.coops_product_within_isotach( - 'water_level', wind_speed=34, end_date=florence2018.start_date + timedelta(minutes=1), + 'water_level', wind_speed=34, end_date=florence2018.start_date + timedelta(hours=1), ) tidal_data = florence2018.coops_product_within_isotach( @@ -168,14 +167,13 @@ def test_storm_event_coops_product_within_isotach(florence2018): wind_speed=34, start_date=datetime(2018, 9, 13), end_date=datetime(2018, 9, 14), - track=florence2018.track(file_deck='a'), ) assert len(null_data.data_vars) == 0 assert list(tidal_data.data_vars) == ['v', 's', 'f', 'q'] assert null_data['t'].sizes == {} - assert tidal_data.sizes == {'nos_id': 22, 't': 241} + assert tidal_data.sizes == {'nos_id': 6, 't': 241} tidal_data.to_netcdf(output_directory / 'florence2018_water_levels.nc') @@ -185,24 +183,27 @@ def test_storm_event_coops_product_within_isotach(florence2018): def test_storm_event_coops_product_within_region(florence2018): null_track = florence2018.track(end_date=florence2018.start_date + timedelta(hours=12)) null_data = florence2018.coops_product_within_region( - 'water_level', region=box(*null_track.linestring.bounds), end_date=null_track.end_date, + 'water_level', + region=box(*null_track.linestrings['BEST']['20180830T060000'].bounds), + end_date=null_track.end_date, ) track = florence2018.track( start_date=datetime(2018, 9, 13, 23, 59), end_date=datetime(2018, 9, 14), file_deck='a', - advisory='OFCL', + advisories='OFCL', ) - tidal_data = florence2018.coops_product_within_region( + + tidal_data_1 = florence2018.coops_product_within_region( 'water_level', - region=box(*track.linestring.bounds), + region=box(*track.linestrings['OFCL']['20180914T000000'].bounds), start_date=track.start_date, end_date=track.end_date, ) assert len(null_data.data_vars) == 0 - assert list(tidal_data.data_vars) == ['v', 's', 'f', 'q'] + assert list(tidal_data_1.data_vars) == ['v', 's', 'f', 'q'] assert null_data['t'].sizes == {} - assert tidal_data.sizes == {'nos_id': 64, 't': 1} + assert tidal_data_1.sizes == {'nos_id': 8, 't': 1}