Source code for awherepy.weather

"""
awherepy.weather
================
A module to access and retrieve aWhere weather data.
"""

import requests as rq
import pandas as pd
from pandas.io.json import json_normalize
import geopandas as gpd
import awherepy as aw

# Define variables for data cleaning
# Weather norms
NORMS_COORD_COLS = ["location.longitude", "location.latitude"]

NORMS_DROP_COLS = [
    "meanTemp.units",
    "maxTemp.units",
    "minTemp.units",
    "precipitation.units",
    "solar.units",
    "dailyMaxWind.units",
    "averageWind.units",
    "_links.curies",
    "_links.awhere:field.href",
]

NORMS_RENAME_MAP = {
    "meanTemp.average": "mean_temp_avg_cels",
    "meanTemp.stdDev": "mean_temp_std_dev_cels",
    "maxTemp.average": "max_temp_avg_cels",
    "maxTemp.stdDev": "max_temp_std_dev_cels",
    "minTemp.average": "min_temp_avg_cels",
    "minTemp.stdDev": "min_temp_std_dev_cels",
    "precipitation.average": "precip_avg_mm",
    "precipitation.stdDev": "precip_std_dev_mm",
    "solar.average": "solar_avg_w_h_per_m2",
    "solar.stdDev": "solar_avg_std_dev_w_h_per_m2",
    "minHumidity.average": "min_humiduty_avg_%",
    "minHumidity.stdDev": "min_humidity_std_dev_%",
    "maxHumidity.average": "max_humiduty_avg_%",
    "maxHumidity.stdDev": "max_humidity_std_dev_%",
    "dailyMaxWind.average": "daily_max_wind_avg_m_per_sec",
    "dailyMaxWind.stdDev": "daily_max_wind_std_dev_m_per_sec",
    "averageWind.average": "average_wind_m_per_sec",
    "averageWind.stdDev": "average_wind_std_dev_m_per_sec",
    "location.fieldId": "field_id",
}

# Observed weather
OBSERVED_COORD_COLS = ["location.longitude", "location.latitude"]

OBSERVED_DROP_COLS = [
    "temperatures.units",
    "precipitation.units",
    "solar.units",
    "wind.units",
    "_links.curies",
    "_links.awhere:field.href",
]

OBSERVED_RENAME_MAP = {
    "temperatures.max": "temp_max_cels",
    "temperatures.min": "temp_min_cels",
    "precipitation.amount": "precip_amount_mm",
    "solar.amount": "solar_energy_w_h_per_m2",
    "relativeHumidity.average": "rel_humidity_avg_%",
    "relativeHumidity.max": "rel_humidity_max_%",
    "relativeHumidity.min": "rel_humidity_min_%",
    "wind.morningMax": "wind_morning_max_m_per_sec",
    "wind.dayMax": "wind_day_max_m_per_sec",
    "wind.average": "wind_avg_m_per_sec",
    "location.fieldId": "field_id",
}

# Weather forecast
FORECAST_COORD_COLS = ["longitude", "latitude"]

FORECAST_DROP_COLS = [
    "temperatures.units",
    "precipitation.units",
    "solar.units",
    "wind.units",
    "dewPoint.units",
    "units",
]

FORECAST_RENAME_MAP = {
    "startTime": "start_time",
    "endTime": "end_time",
    "conditionsCode": "conditions_code",
    "conditionsText": "conditions_text",
    "temperatures.value": "temp_val_cels",
    "temperatures.max": "temp_max_cels",
    "temperatures.min": "temp_min_cels",
    "precipitation.chance": "precip_chance_%",
    "precipitation.amount": "precip_amount_mm",
    "sky.cloudCover": "sky_cloud_cover_%",
    "sky.sunshine": "sky_sunshine_%",
    "solar.amount": "solar_energy_w_h_per_m2",
    "relativeHumidity.average": "rel_humidity_avg_%",
    "relativeHumidity.max": "rel_humidity_max_%",
    "relativeHumidity.min": "rel_humidity_min_%",
    "wind.average": "wind_avg_m_per_sec",
    "wind.max": "wind_max_m_per_sec",
    "wind.min": "wind_min_m_per_sec",
    "wind.bearing": "wind_bearing_deg",
    "wind.direction": "wind_direction_compass",
    "dewPoint.amount": "dew_point_cels",
    "average_temp": "soil_temp_avg_cels",
    "max_temp": "soil_temp_max_cels",
    "min_temp": "soil_temp_min_cels",
    "average_moisture": "soil_moisture_avg_%",
    "max_moisture": "soil_moisture_max_%",
    "min_moisture": "soil_moisture_min_%",
}


def _call_weather_norms(
    key,
    secret,
    input_type="location",
    location=(-105.648222, 40.313250),
    field_id=None,
    start_date="01-01",
    end_date=None,
    limit=10,
    offset=0,
):
    """Helper function that calls the aWhere API for weather norms.
    """
    # Check input data type
    # Location-based
    if input_type == "location":

        # Raise error if location is not defined
        if location is None:
            raise ValueError(
                ("Must specify a location, with longitude " "and latitude.")
            )

        # Set URL to location
        api_url = (
            "https://api.awhere.com/v2/weather/locations/"
            f"{location[1]},{location[0]}"
        )

    # Field-based
    elif input_type == "field":

        # Raise error if field name is not defined
        if field_id is None:
            raise ValueError("Must specify a field name.")

        # Set URL to fields
        api_url = f"https://api.awhere.com/v2/weather/fields/{field_id}"

    # Invalid input
    else:
        raise ValueError("Invalid input type. Must be 'location' or 'field'.")

    # Get OAuth token
    auth_token = aw.get_oauth_token(key, secret)

    # Set up the HTTP request headers
    auth_headers = {"Authorization": f"Bearer {auth_token}"}

    # Define URL variants - location
    url_single_day = (
        f"{api_url}/norms/{start_date}?limit={limit}&offset={offset}"
    )
    url_multiple_days = (
        f"{api_url}/norms/{start_date},{end_date}"
        f"?limit={limit}&offset={offset}"
    )

    # Get single-day or multi-day norms
    response = (
        rq.get(url_multiple_days, headers=auth_headers)
        if end_date
        else rq.get(url_single_day, headers=auth_headers)
    )

    # Convert response to json format
    weather_norms = response.json()

    # Return norms
    return weather_norms


def _extract_weather_norms(historic_norms):
    """Helper function that extracts weather norms into a dataframe.
    """
    # Raise error if input is not of type dictionary
    if not isinstance(historic_norms, dict):
        raise TypeError("Input data must be a dictionary.")

    # Check if multiple entries (days) are in norms
    if historic_norms.get("norms"):
        # Flatten to dataframe
        historic_norms_df = json_normalize(historic_norms.get("norms"))

    # Single-day norm
    else:
        # Flatten to dataframe
        historic_norms_df = json_normalize(historic_norms)

    # Set day as index
    historic_norms_df.set_index("day", inplace=True)

    # Drop unnecessary columns
    historic_norms_df.drop(columns=["_links.self.href"], axis=1, inplace=True)

    # Return dataframe
    return historic_norms_df


def _clean_weather_norms(df):
    """Helper function that cleans and georeferences weather norms.
    """
    # Define global variables
    global NORMS_COORD_COLS, NORMS_DROP_COLS, NORMS_RENAME_MAP

    # Define CRS (EPSG 4326)
    crs = "epsg:4326"

    # Create copy of input dataframe; prevents altering the original
    df_copy = df.copy()

    # Convert to geodataframe
    gdf = gpd.GeoDataFrame(
        df_copy,
        crs=crs,
        geometry=gpd.points_from_xy(
            df[NORMS_COORD_COLS[0]], df[NORMS_COORD_COLS[1]]
        ),
    )

    # Add lat/lon columns to drop columns list
    NORMS_DROP_COLS += NORMS_COORD_COLS

    # Drop columns
    gdf.drop(columns=NORMS_DROP_COLS, axis=1, inplace=True, errors="ignore")

    # Rename columns
    gdf.rename(columns=NORMS_RENAME_MAP, inplace=True, errors="ignore")

    # Return cleaned up geodataframe
    return gdf


[docs]def get_weather_norms(key, secret, kwargs=None): """Gets historical weather norms data from the aWhere API. API reference: https://docs.awhere.com/knowledge-base-docs/weather/ Parameters ---------- key : str API key for a valid aWhere API application. secret : str API secret for a valid aWhere API application. kwargs : dict, optional Keyword arguments for different query parameters. Running the function without kwargs will use the default values. Arguments include: input_type: str Type of data, weather API by geolocation or API by field. Valid options include 'location' and 'field'. Default value is 'location'. location: tuple of float Tuple containing the location (longitude, latitude) that the API gets data from. For use with the 'location' input_type. Default value is (-105.648222, 40.313250), for Bear Lake, CO. field_id: str Field ID for a valid aWhere field associated with the input API key/secret. For use with the 'field' input_type. Default value is None. start_date: str Start date for the weather norms data to be retrieved. Formatted 'MM-DD'. All days of the year are valid, from '01-01' to '12-31'. Default value is '01-01'. end_date: str End date for the weather norms data to be retrieved. Formatted 'MM-DD'. All days of the year are valid, from '01-01' to '12-31'. Default value is None. limit: int Number of results in the API response per page. Used with offset kwarg to sort through pages of results (cases where the number of results exceeds the limit per page). Applicable when the number of results exceeds 1. Maximum value is 10. Default value is 10. offset: int Number of results in the API response to skip. Used with limit kwarg to sort through pages of results (cases where the number of results exceeds the limit per page). Applicable when the number of results exceeds 1. Default value is 0 (start with first result). Returns ------- norms_gdf : geopandas geodataframe Geotdataframe containing the historical weather norms data for the specified location or aWhere field, for the specified date(s). Example ------- >>> # Imports >>> import os >>> import awherepy as aw >>> import awherepy.weather as aww >>> # Get aWhere API key and secret >>> awhere_api_key = os.environ.get('AWHERE_API_KEY') >>> awhere_api_secret = os.environ.get('AWHERE_API_SECRET') >>> # Get historical norms with default kwargs (Bear Lake, CO) >>> bear_lake_norms = aww.get_weather_norms( ... key=awhere_api_key, secret=awhere_api_secret) >>> # Check number of entries in geodataframe >>> len(bear_lake_norms) 1 >>> # Define non-default kwargs (Manchester Center, Vermont) >>> # Define kwargs for Manchester, Vermont >>> vt_kwargs = { ... 'location': (-73.0723269, 43.1636875), ... 'start_date': '05-10', ... 'end_date': '05-19' ... } >>> # Get historical norms for Manchester, Vermont >>> manchester_vt_norms = aww.get_weather_norms( ... key=awhere_api_key, ... secret=awhere_api_secret, ... kwargs=vt_kwargs ... ) >>> # Check number entries in geodataframe >>> len(manchester_vt_norms) 10 """ # Check if credentials are valid if aw.valid_credentials(key, secret): # Call data norms_json = ( _call_weather_norms(key, secret, **kwargs) if kwargs else _call_weather_norms(key, secret) ) # Extract data norms_df = _extract_weather_norms(norms_json) # Clean data norms_gdf = _clean_weather_norms(norms_df) else: # Raise error raise ValueError("Invalid aWhere API credentials.") # Return cleaned data return norms_gdf
def _call_weather_observed( key, secret, input_type="location", location=(-105.648222, 40.313250), field_id=None, start_date=None, end_date=None, limit=10, offset=0, ): """Helper function that calls the aWhere API for observed weather. """ # Check input data type # Location-based if input_type == "location": # Raise error if location is not defined if location is None: raise ValueError( ("Must specify a location, with longitude " "and latitude.") ) # Set URL to location api_url = ( "https://api.awhere.com/v2/weather/locations/" f"{location[1]},{location[0]}" ) # Field-based elif input_type == "field": # Raise error if field name is not defined if field_id is None: raise ValueError("Must specify a field name.") # Set URL to fields api_url = f"https://api.awhere.com/v2/weather/fields/{field_id}" # Invalid input else: raise ValueError("Invalid input type. Must be 'location' or 'field'.") # Get OAuth token auth_token = aw.get_oauth_token(key, secret) # Set up the HTTP request headers auth_headers = {"Authorization": f"Bearer {auth_token}"} # Define URL variants url_no_date = f"{api_url}/observations?limit={limit}&offset={offset}" url_start_date = f"{api_url}/observations/{start_date}" url_end_date = f"{api_url}/observations/{end_date}" url_both_dates = ( f"{api_url}/observations/{start_date},{end_date}" f"?limit={limit}&offset={offset}" ) # Perform the HTTP request to obtain the norms for the Field # Default - 7-day if not (start_date or end_date): response = rq.get(url_no_date, headers=auth_headers) # Single date - specify start date elif start_date and not end_date: response = rq.get(url_start_date, headers=auth_headers) # Single date - specify end date elif end_date and not start_date: response = rq.get(url_end_date, headers=auth_headers) # Date range elif start_date and end_date: response = rq.get(url_both_dates, headers=auth_headers) # Convert response to json format weather_observed = response.json() # Return the observed return weather_observed def _extract_weather_observed(observed_weather): """Helper function that extracts observed weather into a dataframe. """ # Raise error if input is not of type dictionary if not isinstance(observed_weather, dict): raise TypeError("Input data must be a dictionary.") # Check if multiple entries (days) are in observed if observed_weather.get("observations"): # Flatten to dataframe observed_weather_df = json_normalize( observed_weather.get("observations") ) # Single-day observed else: # Flatten to dataframe observed_weather_df = json_normalize(observed_weather) # Set date as index observed_weather_df.set_index("date", inplace=True) # Drop unnecessary columns observed_weather_df.drop( columns=["_links.self.href"], axis=1, inplace=True ) # Return dataframe return observed_weather_df def _clean_weather_observed(df): """Helper function that cleans and georeferences observed weather. """ # Define global variables global OBSERVED_COORD_COLS, OBSERVED_DROP_COLS, OBSERVED_RENAME_MAP # Define CRS (EPSG 4326) crs = "epsg:4326" # Create copy of input dataframe; prevents altering the original df_copy = df.copy() # Convert to geodataframe gdf = gpd.GeoDataFrame( df_copy, crs=crs, geometry=gpd.points_from_xy( df[OBSERVED_COORD_COLS[0]], df[OBSERVED_COORD_COLS[1]] ), ) # Add lat/lon columns to drop columns list OBSERVED_DROP_COLS += OBSERVED_COORD_COLS # Drop columns gdf.drop(columns=OBSERVED_DROP_COLS, axis=1, inplace=True, errors="ignore") # Rename columns gdf.rename(columns=OBSERVED_RENAME_MAP, inplace=True, errors="ignore") # Return cleaned up geodataframe return gdf
[docs]def get_weather_observed(key, secret, kwargs=None): """Gets observed weather data from the aWhere API. API reference: https://docs.awhere.com/knowledge-base-docs/weather/ Parameters ---------- key : str API key for a valid aWhere API application. secret : str API secret for a valid aWhere API application. kwargs : dict, optional Keyword arguments for different query parameters. Running the function without kwargs will use the default values. Arguments include: input_type: str Type of data, weather API by geolocation or API by field. Valid options include 'location' and 'field'. Default value is 'location'. location: tuple of float Tuple containing the location (longitude, latitude) that the API gets data from. For use with the 'location' input_type. Default value is (-105.648222, 40.313250), for Bear Lake, CO. field_id: str Field ID for a valid aWhere field associated with the input API key/secret. For use with the 'field' input_type. Default value is None. start_date: str Start date for the observed weather data to be retrieved. Formatted 'YYYY-MM-DD'. All days of the year are valid, from 'YYYY-01-01' to 'YYYY-12-31'. Default value is None, which returns the previous 7 days of observed weather. end_date: str End date for the observed weather data to be retrieved. Formatted 'YYYY-MM-DD'. All days of the year are valid, from 'YYYY-01-01' to 'YYYY-12-31'. Default value is None, which returns the previous 7 days of observed weather. limit: int Number of results in the API response per page. Used with offset kwarg to sort through pages of results (cases where the number of results exceeds the limit per page). Applicable when the number of results exceeds 1. Maximum value is 10. Default value is 10. offset: int Number of results in the API response to skip. Used with limit kwarg to sort through pages of results (cases where the number of results exceeds the limit per page). Applicable when the number of results exceeds 1. Default value is 0 (start with first result). Returns ------- observed_gdf : geopandas geodataframe Geotdataframe containing the observed weather data for the specified location or aWhere field, for the specified date(s). Example ------- >>> # Imports >>> import os >>> import awherepy as aw >>> import awherepy.weather as aww >>> # Get aWhere API key and secret >>> awhere_api_key = os.environ.get('AWHERE_API_KEY') >>> awhere_api_secret = os.environ.get('AWHERE_API_SECRET') >>> # Get observed weather with default kwargs (Bear Lake, CO) >>> bear_lake_observed = aww.get_weather_observed( ... key=awhere_api_key, secret=awhere_api_secret) >>> # Check number of entries in geodataframe >>> len(bear_lake_observed) 7 >>> # Define non-default kwargs (Manchester Center, Vermont) >>> # Define kwargs for Manchester, Vermont >>> vt_kwargs = { ... 'location': (-73.0723269, 43.1636875), ... 'start_date': '2020-05-10', ... 'end_date': '2020-05-20' ... } >>> # Get observed weather for Manchester, Vermont >>> manchester_vt_observed = aww.get_weather_observed( ... key=awhere_api_key, ... secret=awhere_api_secret, ... kwargs=vt_kwargs ... ) >>> # Check number entries in geodataframe >>> len(manchester_vt_observed) 10 """ # Check if credentials are valid if aw.valid_credentials(key, secret): # Call data observed_json = ( _call_weather_observed(key, secret, **kwargs) if kwargs else _call_weather_observed(key, secret) ) # Extract data observed_df = _extract_weather_observed(observed_json) # Clean data observed_gdf = _clean_weather_observed(observed_df) else: # Raise error raise ValueError("Invalid aWhere API credentials.") return observed_gdf
def _call_weather_forecast( key, secret, input_type="location", location=(-105.648222, 40.313250), field_id=None, start_date=None, end_date=None, limit=10, offset=0, block_size=24, ): """Helper function that calls the aWhere API for weather forecast data. """ # Check input data type # Location-based if input_type == "location": # Raise error if location is not defined if location is None: raise ValueError( ("Must specify a location, with longitude " "and latitude.") ) # Set URL to location api_url = ( f"https://api.awhere.com/v2/weather/locations/" f"{location[1]},{location[0]}" ) # Field-based elif input_type == "field": # Raise error if field name is not defined if field_id is None: raise ValueError("Must specify a field name.") # Set URL to fields api_url = f"https://api.awhere.com/v2/weather/fields/{field_id}" # Invalid input else: raise ValueError("Invalid input type. Must be 'location' or 'field'.") # Get OAuth token auth_token = aw.get_oauth_token(key, secret) # Setup the HTTP request headers auth_headers = {"Authorization": f"Bearer {auth_token}"} # Define URL variants url_no_date = ( f"{api_url}/forecasts?limit={limit}&offset={offset}" f"&blockSize={block_size}" ) url_start_date = ( f"{api_url}/forecasts/{start_date}?limit={limit}" f"&offset={offset}&blockSize={block_size}" ) url_end_date = ( f"{api_url}/forecasts/{end_date}?limit={limit}" f"&offset={offset}&blockSize={block_size}" ) url_both_dates = ( f"{api_url}/forecasts/{start_date},{end_date}" f"?limit={limit}&offset={offset}&blockSize={block_size}" ) # Perform the HTTP request to obtain the Forecast for the Field # Default - 7-day if not (start_date or end_date): response = rq.get(url_no_date, headers=auth_headers) # Single date - specify start day elif start_date and not end_date: response = rq.get(url_start_date, headers=auth_headers) # Single date - specify end day elif end_date and not start_date: response = rq.get(url_end_date, headers=auth_headers) # Date range elif start_date and end_date: response = rq.get(url_both_dates, headers=auth_headers) # Convert response to json format weather_forecast = response.json() # Return forecast return weather_forecast def _extract_weather_forecast(forecast, forecast_type="main"): """Helper function that extracts weather foreceast data into a dataframe. """ # Initialize lists to store forecast forecast_list = [] # Check if more than one day if forecast.get("forecasts"): forecast_iterator = json_normalize(forecast.get("forecasts")) # Single day else: forecast_iterator = json_normalize(forecast) # Loop through each row in the top-level flattened dataframe for index, row in forecast_iterator.iterrows(): # Extract date, lat, lon for insertion into lower-level # dataframe outputs date = row["date"] lat = row["location.latitude"] lon = row["location.longitude"] # Check forecast type # Main forecast if forecast_type == "main": # Extract the main forecast from the top-level dataframe forecast = row["forecast"] # Faltten data into dataframe forecast_norm = json_normalize(forecast) # Drop soil moisture and soil temperature columns # (will be extracted as indivdiual dataframes) forecast_norm.drop( columns=["soilTemperatures", "soilMoisture"], axis=1, inplace=True, ) # Assign date, lat/lon to dataframe forecast_norm["date"] = date forecast_norm["latitude"] = lat forecast_norm["longitude"] = lon # Set date as index forecast_norm.set_index(["date"], inplace=True) # Add the dataframe to a list of dataframes forecast_list.append(forecast_norm) # Soil forecast elif forecast_type == "soil": # Get soil temperature data forecast_soil_temp = row["forecast"][0].get("soilTemperatures") forecast_soil_moisture = row["forecast"][0].get("soilMoisture") # Flatten data into dataframe forecast_soil_temp_df = json_normalize(forecast_soil_temp) forecast_soil_moisture_df = json_normalize(forecast_soil_moisture) # Combine temperature and moisture forecast_soil_df = forecast_soil_temp_df.merge( forecast_soil_moisture_df, on="depth", suffixes=("_temp", "_moisture"), ) # Assign date, lat/lon to dataframe forecast_soil_df["date"] = date forecast_soil_df["latitude"] = lat forecast_soil_df["longitude"] = lon # Shorten depth values to numerics (will be used in MultiIndex) forecast_soil_df["depth"] = forecast_soil_df["depth"].apply( lambda x: x[0:-15] ) # Rename depth prior to indexing forecast_soil_df.rename( columns={"depth": "ground_depth_m"}, inplace=True ) # Create multi-index dataframe for date and soil # depth (rename depth columns? rather long) soil_multi_index = forecast_soil_df.set_index( ["date", "ground_depth_m"] ) # Add dataframe to list of dataframes forecast_list.append(soil_multi_index) # Invalid forecast type else: raise ValueError( "Invalid forecast type. Must be 'main' or 'soil'." ) # Merge list of dataframes into a single dataframe merged = pd.concat(forecast_list) # Return merged dataframe return merged def _clean_weather_forecast(df): """Helper function that cleans and georeferences weather forecast data. """ # Define global variables global FORECAST_COORD_COLS, FORECAST_DROP_COLS, FORECAST_RENAME_MAP # Define CRS (EPSG 4326) crs = "epsg:4326" # Create copy of input dataframe; prevents altering the original df_copy = df.copy() # Convert to geodataframe gdf = gpd.GeoDataFrame( df_copy, crs=crs, geometry=gpd.points_from_xy( df[FORECAST_COORD_COLS[0]], df[FORECAST_COORD_COLS[1]] ), ) # Add lat/lon columns to drop columns list FORECAST_DROP_COLS += FORECAST_COORD_COLS # Drop columns gdf.drop(columns=FORECAST_DROP_COLS, axis=1, inplace=True, errors="ignore") # Rename columns gdf.rename(columns=FORECAST_RENAME_MAP, inplace=True, errors="ignore") # Return cleaned up geodataframe return gdf
[docs]def get_weather_forecast(key, secret, forecast_type="main", kwargs=None): """Gets weather forecast data from the aWhere API. API reference: https://docs.awhere.com/knowledge-base-docs/weather/ Parameters ---------- key : str API key for a valid aWhere API application. secret : str API secret for a valid aWhere API application. forecast_type : str Type of weather forecast. Valid options include 'main' and 'soil'. Default value is 'main'. kwargs : dict, optional Keyword arguments for different query parameters. Running the function without kwargs will use the default values. Arguments include: input_type: str Type of data, weather API by geolocation or API by field. Valid options include 'location' and 'field'. Default value is 'location'. location: tuple of float Tuple containing the location (longitude, latitude) that the API gets data from. For use with the 'location' input_type. Default value is (-105.648222, 40.313250), for Bear Lake, CO. field_id: str Field ID for a valid aWhere field associated with the input API key/secret. For use with the 'field' input_type. Default value is None. start_date: str Start date for the weather forecast data to be retrieved. Formatted 'YYYY-MM-DD'. Valid dates inlclude up to 15 days from the current day. Default value is None, which returns the next 10 days of forecasted weather from the current day. end_date: str End date for the weather forecast data to be retrieved. Formatted 'YYYY-MM-DD'. Valid dates inlclude up to 15 days from the current day. Default value is None, which returns the next 10 days of forecasted weather from the current day. limit: int Number of results in the API response per page. Used with offset kwarg to sort through pages of results (cases where the number of results exceeds the limit per page). Applicable when the number of results exceeds 1. Maximum value is 10. Default value is 10. offset: int Number of results in the API response to skip. Used with limit kwarg to sort through pages of results (cases where the number of results exceeds the limit per page). Applicable when the number of results exceeds 1. Default value is 0 (start with first result). block_size: int Number of hourly forecasts per result. Valid options include 1, 2, 3, 4, 6, 8, 12, 24. Value of 1 indicates that the API returns each daily forecast in 24 1-hour blocks. Value of 6 indicates that API returns each daily forecast in 4 6-hour blocks. Value of 24 indicates that the API returns each daily forecast in 1 24-hour block. Default value is 24. Returns ------- forecast_gdf : geopandas geodataframe Geotdataframe containing the weather forecast data for the specified location or aWhere field, for the specified date(s). Example ------- >>> # Imports >>> import os >>> import awherepy as aw >>> import awherepy.weather as aww >>> # Get aWhere API key and secret >>> awhere_api_key = os.environ.get('AWHERE_API_KEY') >>> awhere_api_secret = os.environ.get('AWHERE_API_SECRET') >>> # Get the weather forecast with default kwargs (Bear Lake, CO) >>> bear_lake_forecast = aww.get_weather_forecast( ... key=awhere_api_key, secret=awhere_api_secret) >>> # Check number of entries in geodataframe >>> len(bear_lake_forecast) 10 >>> # Define non-default kwargs (Manchester Center, Vermont) >>> # Define kwargs for Manchester, Vermont >>> vt_kwargs = { ... 'location': (-73.0723269, 43.1636875) ... } >>> # Get weather forecast for Manchester, Vermont >>> manchester_vt_forecast = aww.get_weather_forecast( ... key=awhere_api_key, ... secret=awhere_api_secret, ... kwargs=vt_kwargs ... ) >>> # Check number entries in geodataframe >>> len(manchester_vt_forecast) 10 >>> # Get soil forecast for default values (Bear Lake, CO) >>> bear_lake_forecast_soil = aww.get_weather_forecast( ... key=awhere_api_key, ... secret=awhere_api_secret, ... forecast_type='soil' ... ) >>> # Check number of entries in geodataframe >>> len(bear_lake_forecast_soil) 40 """ # Check if credentials are valid if aw.valid_credentials(key, secret): # Call data forecast_json = ( _call_weather_forecast(key, secret, **kwargs) if kwargs else _call_weather_forecast(key, secret) ) # Extract data forecast_df = _extract_weather_forecast( forecast_json, forecast_type=forecast_type ) # Clean data forecast_gdf = _clean_weather_forecast(forecast_df) else: # Raise error raise ValueError("Invalid aWhere API credentials.") return forecast_gdf