"""
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