Files
cariflex/tools/flexmeasures-weather/flexmeasures_weather/utils/locating.py
Eric F d4974e3241 Add FlexMeasures plugins, USEF protocol, and Cariflex simulator
- flexmeasures-entsoe: ENTSO-E data plugin
- flexmeasures-weather: Weather data plugin
- USEF Flex Trading Protocol PDF (2.4MB)
- Cariflex simulator (publishes to Redis)
- Dashboard Grafana updated with correct InfluxDB queries
- All tools extracted in /tools/
2026-06-08 07:38:57 -04:00

128 lines
4.9 KiB
Python

from __future__ import annotations
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 Sensor
from flexmeasures.data.models.generic_assets import GenericAsset
from flexmeasures.utils import flexmeasures_inflection
from .. import WEATHER_STATION_TYPE_NAME
def get_locations(
location: str,
num_cells: int,
method: str,
) -> List[Tuple[float, float]]:
"""
Get locations for getting forecasts for, by parsing the location string, which possibly opens a latitude/longitude grid with several neatly ordered locations.
"""
if (
location.count(",") == 0
or location.count(",") != location.count(":") + 1
or location.count(":") == 1
and (
location.find(",") > location.find(":")
or location.find(",", location.find(",") + 1) < location.find(":")
)
):
raise Exception(
'[FLEXMEASURES-WEATHER] location parameter "%s" seems malformed. Please use "latitude,longitude" or '
' "top-left-latitude,top-left-longitude:bottom-right-latitude,bottom-right-longitude"'
% location
)
location_identifiers = tuple(location.split(":"))
if len(location_identifiers) == 1:
ll = location_identifiers[0].split(",")
locations = [(float(ll[0]), float(ll[1]))]
click.echo("[FLEXMEASURES-WEATHER] Only one location: %s,%s." % locations[0])
elif len(location_identifiers) == 2:
click.echo(
"[FLEXMEASURES-WEATHER] Making a grid of locations between top/left %s and bottom/right %s ..."
% location_identifiers
)
top_left = tuple(float(s) for s in location_identifiers[0].split(","))
if len(top_left) != 2:
raise Exception(
"[FLEXMEASURES-WEATHER] top-left parameter '%s' is invalid."
% location_identifiers[0]
)
bottom_right = tuple(float(s) for s in location_identifiers[1].split(","))
if len(bottom_right) != 2:
raise Exception(
"[FLEXMEASURES-WEATHER] bottom-right parameter '%s' is invalid."
% location_identifiers[1]
)
num_lat, num_lng = get_cell_nums(top_left, bottom_right, num_cells)
locations = LatLngGrid(
top_left=top_left,
bottom_right=bottom_right,
num_cells_lat=num_lat,
num_cells_lng=num_lng,
).get_locations(method)
else:
raise Exception(
"[FLEXMEASURES-WEATHER] location parameter '%s' has too many locations."
% location
)
return locations
def find_weather_sensor_by_location(
location: Tuple[float, float],
max_degree_difference_for_nearest_weather_sensor: int,
sensor_name: str,
) -> Sensor | None:
"""
Try to find a weather sensor of fitting type close by.
Return None if the nearest weather sensor is further away than some minimum degrees or if no sensor was found at all.
"""
weather_sensor: Optional[Sensor] = Sensor.find_closest(
generic_asset_type_name=WEATHER_STATION_TYPE_NAME,
sensor_name=sensor_name,
lat=location[0],
lng=location[1],
n=1,
)
if weather_sensor is None:
current_app.logger.warning(
"[FLEXMEASURES-WEATHER] No weather sensor set up yet for measuring %s. Try the register-weather-sensor CLI task."
% sensor_name
)
return None
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(
f"[FLEXMEASURES-WEATHER] We found a weather station, but 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})"
)
return None
return weather_sensor
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.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-WEATHER] No asset found for the given asset id %s."
% asset_id
)
return (asset.latitude, asset.longitude)