Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get forecasts for an asset-id instead of passing location #16

Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
6f5a55f
add asset-id to get forecasts, used GenericAsset(type) instead of Ass…
Ahmad-Wahid Jul 22, 2023
acf1e03
refactoring
Ahmad-Wahid Jul 24, 2023
9080a0a
refactoring
Ahmad-Wahid Jul 25, 2023
f9448ce
remove extra brackets
Ahmad-Wahid Jul 25, 2023
abea5df
add both assets name in the exception message
Ahmad-Wahid Jul 25, 2023
ddfc405
add asset-id to get forecasts, used GenericAsset(type) instead of Ass…
Ahmad-Wahid Jul 22, 2023
adb193b
refactoring
Ahmad-Wahid Jul 24, 2023
caae0c1
refactoring
Ahmad-Wahid Jul 25, 2023
06d26c1
remove extra brackets
Ahmad-Wahid Jul 25, 2023
767f4c1
add both assets name in the exception message
Ahmad-Wahid Jul 25, 2023
59ef4e0
clear exception message
Ahmad-Wahid Jul 26, 2023
1fc3691
Merge remote-tracking branch 'origin/2-allow-to-pass-asset-id-andor-n…
Ahmad-Wahid Jul 26, 2023
dbc475b
Merge remote-tracking branch 'origin/main' into 2-allow-to-pass-asset…
Ahmad-Wahid Jul 26, 2023
8465c17
removed unused package
Ahmad-Wahid Jul 26, 2023
3022ffd
add asset-id cli param
Ahmad-Wahid Jul 26, 2023
96a7c53
add asset-id to register a weather sensor
Ahmad-Wahid Jul 27, 2023
6a93236
fix sensor schema validation and raise Warnings
Ahmad-Wahid Jul 27, 2023
7c4a077
refactoring
Ahmad-Wahid Jul 27, 2023
1333fc1
refactor schema and update generic asset and type
Ahmad-Wahid Jul 28, 2023
22e52b7
stop code when no close station is found
Ahmad-Wahid Jul 28, 2023
eddc198
update warning message and refactoring
Ahmad-Wahid Aug 1, 2023
b33dd7a
Merge main + update test warning message
Mar 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 36 additions & 9 deletions flexmeasures_openweathermap/cli/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
from .schemas.weather_sensor import WeatherSensorSchema
from ..utils.modeling import (
get_or_create_weather_station,
get_asset_id_weather_station,
)
from ..utils.locating import get_locations
from ..utils.locating import get_locations, get_location_by_asset_id
from ..utils.filing import make_file_path
from ..utils.owm import (
save_forecasts_in_db,
Expand All @@ -39,16 +40,21 @@
required=True,
help=f"Name of the sensor. Has to be from the supported list ({supported_sensors_list})",
)
# @click.option("--generic-asset-id", required=False, help="The asset id of the weather station (you can also give its location).")
@click.option(
"--asset-id",
required=False,
type=int,
help="The asset id of the weather station (you can also give its location).",
)
@click.option(
"--latitude",
required=True,
required=False,
type=float,
help="Latitude of where you want to measure.",
)
@click.option(
"--longitude",
required=True,
required=False,
type=float,
help="Longitude of where you want to measure.",
)
Expand All @@ -69,8 +75,16 @@ def add_weather_sensor(**args):
f"[FLEXMEASURES-OWM] Please correct the following errors:\n{errors}.\n Use the --help flag to learn more."
)
raise click.Abort

weather_station = get_or_create_weather_station(args["latitude"], args["longitude"])
if args["asset_id"] is not None:
weather_station = get_asset_id_weather_station(args["asset_id"])
elif args["latitude"] is not None and args["longitude"] is not None:
weather_station = get_or_create_weather_station(
args["latitude"], args["longitude"]
)
else:
raise Exception(
"Arguments are missing to register a weather sensor. Provide either '--asset-id' or ('--latitude' and '--longitude')."
)

fm_sensor_specs = get_supported_sensor_spec(args["name"])
fm_sensor_specs["generic_asset"] = weather_station
Expand All @@ -95,12 +109,18 @@ def add_weather_sensor(**args):
@click.option(
"--location",
type=str,
required=True,
required=False,
help='Measurement location(s). "latitude,longitude" or "top-left-latitude,top-left-longitude:'
'bottom-right-latitude,bottom-right-longitude." The first format defines one location to measure.'
" The second format defines a region of interest with several (>=4) locations"
' (see also the "method" and "num_cells" parameters for details on how to use this feature).',
)
@click.option(
"--asset-id",
type=int,
required=False,
help="ID of a weather station asset - forecasts will be gotten for its location. If present, --location will be ignored.",
)
@click.option(
"--store-in-db/--store-as-json-files",
default=True,
Expand All @@ -125,7 +145,7 @@ def add_weather_sensor(**args):
help="Name of the region (will create sub-folder if you store json files).",
)
@task_with_status_report("get-openweathermap-forecasts")
def collect_weather_data(location, store_in_db, num_cells, method, region):
def collect_weather_data(location, asset_id, store_in_db, num_cells, method, region):
"""
Collect weather forecasts from the OpenWeatherMap API.
This will be done for one or more locations, for which we first identify relevant weather stations.
Expand All @@ -139,7 +159,14 @@ def collect_weather_data(location, store_in_db, num_cells, method, region):
raise Exception(
"[FLEXMEASURES-OWM] Setting OPENWEATHERMAP_API_KEY not available."
)
locations = get_locations(location, num_cells, method)
if asset_id is not None:
locations = [get_location_by_asset_id(asset_id)]
elif location is not None:
locations = get_locations(location, num_cells, method)
else:
raise Warning(
"[FLEXMEASURES-OWM] Pass either location or asset-id to get weather forecasts."
)

# Save the results
if store_in_db:
Expand Down
33 changes: 26 additions & 7 deletions flexmeasures_openweathermap/cli/schemas/weather_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
import pytz
from flexmeasures import Sensor

from ...utils.modeling import get_or_create_weather_station
from ...utils.modeling import (
get_or_create_weather_station,
get_asset_id_weather_station,
)
from ...utils.owm import get_supported_sensor_spec, get_supported_sensors_str


Expand All @@ -22,8 +25,13 @@ class WeatherSensorSchema(Schema):

name = fields.Str(required=True)
timezone = fields.Str()
latitude = fields.Float(required=True, validate=validate.Range(min=-90, max=90))
longitude = fields.Float(required=True, validate=validate.Range(min=-180, max=180))
asset_id = fields.Int(required=False, allow_none=True)
latitude = fields.Float(
required=False, validate=validate.Range(min=-90, max=90), allow_none=True
)
longitude = fields.Float(
required=False, validate=validate.Range(min=-180, max=180), allow_none=True
)

@validates("name")
def validate_name_is_supported(self, name: str):
Expand All @@ -35,11 +43,22 @@ def validate_name_is_supported(self, name: str):

@validates_schema(skip_on_field_errors=False)
def validate_name_is_unique_in_weather_station(self, data, **kwargs):
if "name" not in data or "latitude" not in data or "longitude" not in data:
if (
"name" not in data
or "latitude" not in data
or "longitude" not in data
or "asset_id" not in data
Ahmad-Wahid marked this conversation as resolved.
Show resolved Hide resolved
):
return # That's a different validation problem
weather_station = get_or_create_weather_station(
data["latitude"], data["longitude"]
)
if data["latitude"] is not None and data["longitude"] is not None:
weather_station = get_or_create_weather_station(
data["latitude"], data["longitude"]
)
elif data["asset_id"] is not None:
weather_station = get_asset_id_weather_station(data["asset_id"])
else:
return

sensor = Sensor.query.filter(
Sensor.name == data["name"].lower(),
Sensor.generic_asset == weather_station,
Expand Down
26 changes: 21 additions & 5 deletions flexmeasures_openweathermap/utils/locating.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
from typing import Tuple, List, Optional

import click
from flask import current_app

from flexmeasures.utils.grid_cells import LatLngGrid, get_cell_nums
from flexmeasures import Asset, Sensor
from flexmeasures import Sensor
from flexmeasures.data.models.generic_assets import GenericAsset
from flexmeasures.utils import flexmeasures_inflection

from .. import WEATHER_STATION_TYPE_NAME
Expand Down Expand Up @@ -92,19 +92,35 @@ def find_weather_sensor_by_location(
n=1,
)
if weather_sensor is not None:
weather_station: Asset = weather_sensor.generic_asset
weather_station: GenericAsset = weather_sensor.generic_asset
if abs(
location[0] - weather_station.location[0]
) > max_degree_difference_for_nearest_weather_sensor or abs(
location[1] - weather_station.location[1]
> max_degree_difference_for_nearest_weather_sensor
):
current_app.logger.warning(
raise Warning(
Ahmad-Wahid marked this conversation as resolved.
Show resolved Hide resolved
f"[FLEXMEASURES-OWM] No sufficiently close weather sensor found (within {max_degree_difference_for_nearest_weather_sensor} {flexmeasures_inflection.pluralize('degree', max_degree_difference_for_nearest_weather_sensor)} distance) for measuring {sensor_name}! We're looking for: {location}, closest available: ({weather_station.location})"
nhoening marked this conversation as resolved.
Show resolved Hide resolved
)
else:
current_app.logger.warning(
raise Warning(
"[FLEXMEASURES-OWM] No weather sensor set up yet for measuring %s. Try the register-weather-sensor CLI task."
% sensor_name
)
return weather_sensor
Ahmad-Wahid marked this conversation as resolved.
Show resolved Hide resolved


def get_location_by_asset_id(asset_id: int) -> Tuple[float, float]:
"""Get location for forecasting by passing an asset id"""
asset = GenericAsset.query.filter(
GenericAsset.generic_asset_type_id == asset_id
).one_or_none()
if asset.generic_asset_type.name != WEATHER_STATION_TYPE_NAME:
raise Exception(
f"Asset {asset} does not seem to be a weather station we should use ― we expect an asset with type '{WEATHER_STATION_TYPE_NAME}'."
)
if asset is None:
raise Exception(
"[FLEXMEASURES-OWM] No asset found for the given asset id %s." % asset_id
)
Ahmad-Wahid marked this conversation as resolved.
Show resolved Hide resolved
return (asset.latitude, asset.longitude)
36 changes: 27 additions & 9 deletions flexmeasures_openweathermap/utils/modeling.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from packaging import version

from flask import current_app
from flexmeasures import Asset, AssetType, Source, __version__ as flexmeasures_version
from flexmeasures.data.models.generic_assets import GenericAsset, GenericAssetType
from flexmeasures import Source, __version__ as flexmeasures_version
from flexmeasures.data import db
from flexmeasures.data.services.data_sources import get_or_create_source

Expand Down Expand Up @@ -38,35 +39,52 @@ def get_or_create_owm_data_source_for_derived_data() -> Source:
)


def get_or_create_weather_station_type() -> AssetType:
def get_or_create_weather_station_type() -> GenericAssetType:
"""Make sure a weather station type exists"""
weather_station_type = AssetType.query.filter(
AssetType.name == WEATHER_STATION_TYPE_NAME,
weather_station_type = GenericAssetType.query.filter(
GenericAssetType.name == WEATHER_STATION_TYPE_NAME,
).one_or_none()
if weather_station_type is None:
weather_station_type = AssetType(
weather_station_type = GenericAssetType(
name=WEATHER_STATION_TYPE_NAME,
description="A weather station with various sensors.",
)
db.session.add(weather_station_type)
return weather_station_type


def get_or_create_weather_station(latitude: float, longitude: float) -> Asset:
def get_or_create_weather_station(latitude: float, longitude: float) -> GenericAsset:
"""Make sure a weather station exists at this location."""
station_name = current_app.config.get(
"WEATHER_STATION_NAME", DEFAULT_WEATHER_STATION_NAME
)
weather_station = Asset.query.filter(
Asset.latitude == latitude, Asset.longitude == longitude
weather_station = GenericAsset.query.filter(
GenericAsset.latitude == latitude, GenericAsset.longitude == longitude
).one_or_none()
if weather_station is None:
weather_station_type = get_or_create_weather_station_type()
weather_station = Asset(
weather_station = GenericAsset(
name=station_name,
generic_asset_type=weather_station_type,
latitude=latitude,
longitude=longitude,
)
db.session.add(weather_station)
return weather_station


def get_asset_id_weather_station(asset_id: int) -> GenericAsset:
Ahmad-Wahid marked this conversation as resolved.
Show resolved Hide resolved
weather_station = GenericAsset.query.filter(
GenericAsset.generic_asset_type_id == asset_id
).one_or_none()
if weather_station is None:
raise Exception(
f"[FLEXMEASURES-OWM] Weather station is not present for the given asset id '{asset_id}'."
)

if weather_station.latitude is None or weather_station.longitude is None:
raise Exception(
f"[FLEXMEASURES-OWM] Weather station {weather_station} is missing location information [Latitude, Longitude]."
)

return weather_station