diff --git a/CHANGELOG.md b/CHANGELOG.md index dfe3c4407..cd72327e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,7 +36,7 @@ Attention: The newest changes should be on top --> ### Changed -- +- DEP: deprecate NOAA's RuC sounding [#706](https://github.com/RocketPy-Team/RocketPy/pull/706) ### Fixed diff --git a/README.md b/README.md index c549840f7..04181badd 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ RocketPy is the next-generation trajectory simulation solution for High-Power Ro 2. **Accurate Weather Modeling** - Supports International Standard Atmosphere (1976) - - Custom atmospheric profiles and Soundings (Wyoming, NOAARuc) + - Custom atmospheric profiles and Soundings (Wyoming) - Weather forecasts, reanalysis, and ensembles for realistic scenarios 3. **Aerodynamic Models** diff --git a/docs/user/environment/1-atm-models/soundings.rst b/docs/user/environment/1-atm-models/soundings.rst index c84243ca5..1b0ae95b5 100644 --- a/docs/user/environment/1-atm-models/soundings.rst +++ b/docs/user/environment/1-atm-models/soundings.rst @@ -41,6 +41,17 @@ Initialize a new Environment instance: NOAA's Ruc Soundings -------------------- +.. important:: + + From September 30th, 2024, this model is no longer available since NOAA has \ + discontinued the Ruc Soundings public service. The following message is \ + displayed on the website: \ + "On Monday, September 30, a number of legacy websites were permanently removed. \ + These sites were no longer being maintained and did not meet security and \ + design requirements mandated by NOAA. They were intended for research \ + purposes and are not designed for operational use, such as for commercial \ + purposes or the safety of life and property." + Another option for upper air soundings is `NOAA's Ruc Soundings `_. This service allows users to download virtual soundings from numerical weather prediction models such as GFS, RAP, and NAM, and also real soundings from the diff --git a/rocketpy/environment/environment.py b/rocketpy/environment/environment.py index 39ce18532..4c2c61263 100644 --- a/rocketpy/environment/environment.py +++ b/rocketpy/environment/environment.py @@ -16,7 +16,6 @@ fetch_gfs_file_return_dataset, fetch_hiresw_file_return_dataset, fetch_nam_file_return_dataset, - fetch_noaaruc_sounding, fetch_open_elevation, fetch_rap_file_return_dataset, fetch_wyoming_sounding, @@ -142,11 +141,11 @@ class Environment: Environment.atmospheric_model_type : string Describes the atmospheric model which is being used. Can only assume the following values: ``standard_atmosphere``, ``custom_atmosphere``, - ``wyoming_sounding``, ``NOAARucSounding``, ``Forecast``, ``Reanalysis``, + ``wyoming_sounding``, ``Forecast``, ``Reanalysis``, ``Ensemble``. Environment.atmospheric_model_file : string Address of the file used for the atmospheric model being used. Only - defined for ``wyoming_sounding``, ``NOAARucSounding``, ``Forecast``, + defined for ``wyoming_sounding``, ``Forecast``, ``Reanalysis``, ``Ensemble`` Environment.atmospheric_model_dict : dictionary Dictionary used to properly interpret ``netCDF`` and ``OPeNDAP`` files. @@ -1053,24 +1052,6 @@ def set_atmospheric_model( # pylint: disable=too-many-statements .. _weather.uwyo: http://weather.uwyo.edu/upperair/sounding.html - - ``NOAARucSounding``: sets pressure, temperature, wind-u - and wind-v profiles and surface elevation obtained from - an upper air sounding given by the file parameter through - an URL. This URL should point to a data webpage obtained - through NOAA's Ruc Sounding servers, which can be accessed - in `rucsoundings`_. Selecting ROABs as the - initial data source, specifying the station through it's - WMO-ID and opting for the ASCII (GSD format) button, the - following example URL opens up: - - https://rucsoundings.noaa.gov/get_raobs.cgi?data_source=RAOB&latest=latest&start_year=2019&start_month_name=Feb&start_mday=5&start_hour=12&start_min=0&n_hrs=1.0&fcst_len=shortest&airport=83779&text=Ascii%20text%20%28GSD%20format%29&hydrometeors=false&start=latest - - Any ASCII GSD format page from this server can be read, - so information from virtual soundings such as GFS and NAM - can also be imported. - - .. _rucsoundings: https://rucsoundings.noaa.gov/ - - ``windy_atmosphere``: sets pressure, temperature, wind-u and wind-v profiles and surface elevation obtained from the Windy API. See file argument to specify the model as either ``ECMWF``, @@ -1279,8 +1260,6 @@ def set_atmospheric_model( # pylint: disable=too-many-statements self.process_standard_atmosphere() elif type == "wyoming_sounding": self.process_wyoming_sounding(file) - elif type == "noaarucsounding": - self.process_noaaruc_sounding(file) elif type == "custom_atmosphere": self.process_custom_atmosphere(pressure, temperature, wind_u, wind_v) elif type == "windy": @@ -1689,107 +1668,18 @@ def process_noaaruc_sounding(self, file): # pylint: disable=too-many-statements See also -------- - More details can be found at: https://rucsoundings.noaa.gov/. + This method is deprecated and will be fully deleted in version 1.8.0. Returns ------- None """ - # Request NOAA Ruc Sounding from file url - response = fetch_noaaruc_sounding(file) - - # Split response into lines - lines = response.text.split("\n") - - # Process GSD format (https://rucsoundings.noaa.gov/raob_format.html) - - # Extract elevation data - for line in lines: - # Split line into columns - columns = re.split(" +", line)[1:] - if len(columns) > 0: - if columns[0] == "1" and columns[5] != "99999": - # Save elevation - self.elevation = float(columns[5]) - else: - # No elevation data available - pass - - pressure_array = [] - barometric_height_array = [] - temperature_array = [] - wind_speed_array = [] - wind_direction_array = [] - - for line in lines: - # Split line into columns - columns = re.split(" +", line)[1:] - if len(columns) < 6: - # skip lines with less than 6 columns - continue - if columns[0] in ["4", "5", "6", "7", "8", "9"]: - # Convert columns to floats - columns = np.array(columns, dtype=float) - # Select relevant columns - altitude, pressure, temperature, wind_direction, wind_speed = columns[ - [2, 1, 3, 5, 6] - ] - # Check for missing values - if altitude == 99999: - continue - # Save values only if they are not missing - if pressure != 99999: - pressure_array.append([altitude, pressure]) - barometric_height_array.append([pressure, altitude]) - if temperature != 99999: - temperature_array.append([altitude, temperature]) - if wind_direction != 99999: - wind_direction_array.append([altitude, wind_direction]) - if wind_speed != 99999: - wind_speed_array.append([altitude, wind_speed]) - - # Convert lists to arrays - pressure_array = np.array(pressure_array) - barometric_height_array = np.array(barometric_height_array) - temperature_array = np.array(temperature_array) - wind_speed_array = np.array(wind_speed_array) - wind_direction_array = np.array(wind_direction_array) - - # Converts 10*hPa to Pa and save values - pressure_array[:, 1] = 10 * pressure_array[:, 1] - self.__set_pressure_function(pressure_array) - # Converts 10*hPa to Pa and save values - barometric_height_array[:, 0] = 10 * barometric_height_array[:, 0] - self.__set_barometric_height_function(barometric_height_array) - - # Convert C to K and save values - temperature_array[:, 1] = temperature_array[:, 1] / 10 + 273.15 - self.__set_temperature_function(temperature_array) - - # Process wind-u and wind-v - # Converts Knots to m/s - wind_speed_array[:, 1] = wind_speed_array[:, 1] * 1.852 / 3.6 - wind_heading_array = wind_direction_array[:, :] * 1 - # Convert wind direction to wind heading - wind_heading_array[:, 1] = (wind_direction_array[:, 1] + 180) % 360 - wind_u = wind_speed_array[:, :] * 1 - wind_v = wind_speed_array[:, :] * 1 - wind_u[:, 1] = wind_speed_array[:, 1] * np.sin( - np.deg2rad(wind_heading_array[:, 1]) - ) - wind_v[:, 1] = wind_speed_array[:, 1] * np.cos( - np.deg2rad(wind_heading_array[:, 1]) + warnings.warn( + "NOAA RUC models are no longer available. " + "This method is deprecated and will be fully deleted in version 1.8.0.", + DeprecationWarning, ) - - # Save wind data - self.__set_wind_direction_function(wind_direction_array) - self.__set_wind_heading_function(wind_heading_array) - self.__set_wind_speed_function(wind_speed_array) - self.__set_wind_velocity_x_function(wind_u) - self.__set_wind_velocity_y_function(wind_v) - - # Save maximum expected height - self.max_expected_height = pressure_array[-1, 0] + return file def process_forecast_reanalysis( self, file, dictionary diff --git a/rocketpy/environment/fetchers.py b/rocketpy/environment/fetchers.py index 6ba566145..58537025b 100644 --- a/rocketpy/environment/fetchers.py +++ b/rocketpy/environment/fetchers.py @@ -5,6 +5,7 @@ import re import time +import warnings from datetime import datetime, timedelta, timezone import netCDF4 @@ -347,10 +348,12 @@ def fetch_noaaruc_sounding(file): ImportError If unable to load the specified file or the file content is too short. """ - response = requests.get(file) - if response.status_code != 200 or len(response.text) < 10: - raise ImportError("Unable to load " + file + ".") - return response + warnings.warn( + "The NOAA RUC soundings are deprecated since September 30th, 2024. " + "This method will be removed in version 1.8.0.", + DeprecationWarning, + ) + return file @exponential_backoff(max_attempts=5, base_delay=2, max_delay=60) diff --git a/tests/integration/test_environment.py b/tests/integration/test_environment.py index 5495d2e03..17d81a76e 100644 --- a/tests/integration/test_environment.py +++ b/tests/integration/test_environment.py @@ -98,20 +98,6 @@ def test_standard_atmosphere( assert example_plain_env.prints.print_earth_details() is None -@patch("matplotlib.pyplot.show") -def test_noaaruc_atmosphere( - mock_show, example_spaceport_env -): # pylint: disable=unused-argument - url = ( - r"https://rucsoundings.noaa.gov/get_raobs.cgi?data_source=RAOB&latest=" - r"latest&start_year=2019&start_month_name=Feb&start_mday=5&start_hour=12" - r"&start_min=0&n_hrs=1.0&fcst_len=shortest&airport=83779&text=Ascii" - r"%20text%20%28GSD%20format%29&hydrometeors=false&start=latest" - ) - example_spaceport_env.set_atmospheric_model(type="NOAARucSounding", file=url) - assert example_spaceport_env.all_info() is None - - @pytest.mark.parametrize( "model_name", [