Add NOAA NWS weather fallback
This commit is contained in:
@@ -427,7 +427,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
|
|||||||
- [x] Add weather replication.
|
- [x] Add weather replication.
|
||||||
- [x] Add real-world weather provider adapter for Ground Zero by latitude/longitude. Added a tile-driven Open-Meteo adapter subsystem that can request weather for any active tile by center latitude/longitude, map provider weather codes into Agrarian weather states, apply current temperature and daily low/high to `AAgrarianGameState`, and generate a source-backed tile weather manifest so future real tiles become weather-eligible automatically.
|
- [x] Add real-world weather provider adapter for Ground Zero by latitude/longitude. Added a tile-driven Open-Meteo adapter subsystem that can request weather for any active tile by center latitude/longitude, map provider weather codes into Agrarian weather states, apply current temperature and daily low/high to `AAgrarianGameState`, and generate a source-backed tile weather manifest so future real tiles become weather-eligible automatically.
|
||||||
- [x] Use Open-Meteo as the first global MVP weather source. Added `Data/Weather/open_meteo_mvp_source.json` as the provider contract, documented the global tile lookup rule, and added `Scripts/verify_open_meteo_mvp_source.py` to validate the static contract plus live Open-Meteo responses for every source-backed tile in the generated weather manifest.
|
- [x] Use Open-Meteo as the first global MVP weather source. Added `Data/Weather/open_meteo_mvp_source.json` as the provider contract, documented the global tile lookup rule, and added `Scripts/verify_open_meteo_mvp_source.py` to validate the static contract plus live Open-Meteo responses for every source-backed tile in the generated weather manifest.
|
||||||
- [ ] Add NOAA/NWS fallback or enrichment for US tiles where useful.
|
- [x] Add NOAA/NWS fallback or enrichment for US tiles where useful. Added a US/NWS-eligible coordinate check, NOAA/NWS points and forecast-grid request hooks, fallback parsing for gridded temperature, precipitation probability, and wind speed, `Data/Weather/noaa_nws_us_fallback.json`, and live/static verification for source-backed US tiles.
|
||||||
- [ ] Cache real-weather snapshots server-side so clients never call public weather APIs directly.
|
- [ ] Cache real-weather snapshots server-side so clients never call public weather APIs directly.
|
||||||
- [ ] Map real weather inputs into Agrarian weather states: temperature, precipitation, wind, cloud cover, humidity, pressure, visibility, and weather code.
|
- [ ] Map real weather inputs into Agrarian weather states: temperature, precipitation, wind, cloud cover, humidity, pressure, visibility, and weather code.
|
||||||
- [ ] Add deterministic fallback weather simulation when external weather data is unavailable.
|
- [ ] Add deterministic fallback weather simulation when external weather data is unavailable.
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"schema_version": 1,
|
||||||
|
"provider_id": "noaa-nws",
|
||||||
|
"provider_name": "NOAA National Weather Service API",
|
||||||
|
"provider_docs_url": "https://www.weather.gov/documentation/services-web-api",
|
||||||
|
"points_endpoint": "https://api.weather.gov/points",
|
||||||
|
"api_key_required": false,
|
||||||
|
"scope": "United States and NWS-covered territories only",
|
||||||
|
"eligibility_rule": "Use as fallback or enrichment only when tile center coordinates are inside the approximate NWS latitude/longitude coverage window.",
|
||||||
|
"request_flow": [
|
||||||
|
"GET /points/{latitude},{longitude}",
|
||||||
|
"Read properties.forecastGridData",
|
||||||
|
"GET forecastGridData for gridded temperature, precipitation probability, and wind speed"
|
||||||
|
],
|
||||||
|
"agrarian_mapping": {
|
||||||
|
"temperature": "NWS grid temperature can feed RegionalObservedTemperatureC when Open-Meteo is unavailable for a US tile.",
|
||||||
|
"precipitation": "probabilityOfPrecipitation enriches rain/fallback mapping until precipitation amount mapping is implemented.",
|
||||||
|
"wind": "windSpeed enriches ColdWind and Storm fallback mapping."
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -165,6 +165,15 @@ contract and can perform a live Open-Meteo request for every source-backed tile
|
|||||||
in `Data/Tiles/tile_weather_manifest.json`. This keeps the provider global for
|
in `Data/Tiles/tile_weather_manifest.json`. This keeps the provider global for
|
||||||
all future real tiles instead of adding one-off Ground Zero weather code.
|
all future real tiles instead of adding one-off Ground Zero weather code.
|
||||||
|
|
||||||
|
NOAA/NWS is the US-only fallback and enrichment path. The provider contract is
|
||||||
|
stored in `Data/Weather/noaa_nws_us_fallback.json`. The weather provider
|
||||||
|
subsystem can check whether an active tile center coordinate is inside the
|
||||||
|
approximate NWS coverage window, request `api.weather.gov/points/{lat},{lon}`,
|
||||||
|
follow `properties.forecastGridData`, and parse gridded temperature,
|
||||||
|
precipitation probability, and wind speed as fallback inputs. The NWS path is
|
||||||
|
only used for eligible US/NWS-covered tiles; Open-Meteo remains the global
|
||||||
|
source for all tiles.
|
||||||
|
|
||||||
## Terrain And Tile Delivery
|
## Terrain And Tile Delivery
|
||||||
|
|
||||||
### MVP Tile
|
### MVP Tile
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Verify NOAA/NWS fallback wiring for US source-backed tiles."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import urllib.request
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
CONTRACT = ROOT / "Data" / "Weather" / "noaa_nws_us_fallback.json"
|
||||||
|
MANIFEST = ROOT / "Data" / "Tiles" / "tile_weather_manifest.json"
|
||||||
|
HEADER = ROOT / "Source" / "AgrarianGame" / "AgrarianWeatherProviderSubsystem.h"
|
||||||
|
CPP = ROOT / "Source" / "AgrarianGame" / "AgrarianWeatherProviderSubsystem.cpp"
|
||||||
|
ROADMAP = ROOT / "AGRARIAN_DEVELOPMENT_ROADMAP.md"
|
||||||
|
TDD = ROOT / "Docs" / "TechnicalDesignDocument.md"
|
||||||
|
|
||||||
|
|
||||||
|
EXPECTED = {
|
||||||
|
HEADER: [
|
||||||
|
"bEnableNoaaNwsFallbackForUSTiles",
|
||||||
|
"NoaaNwsPointsEndpoint",
|
||||||
|
"https://api.weather.gov/points",
|
||||||
|
"RequestNoaaNwsFallbackForTile",
|
||||||
|
"IsNoaaNwsEligibleCoordinate",
|
||||||
|
"BuildNoaaNwsPointsUrl",
|
||||||
|
],
|
||||||
|
CPP: [
|
||||||
|
"NoaaNwsPointsEndpoint",
|
||||||
|
"forecastGridData",
|
||||||
|
"ParseNoaaNwsGridData",
|
||||||
|
"probabilityOfPrecipitation",
|
||||||
|
"OutSnapshot.Provider = TEXT(\"noaa-nws\");",
|
||||||
|
],
|
||||||
|
ROADMAP: ["[x] Add NOAA/NWS fallback or enrichment for US tiles where useful."],
|
||||||
|
TDD: ["NOAA/NWS is the US-only fallback and enrichment path", "Data/Weather/noaa_nws_us_fallback.json"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def assert_static_contract() -> tuple[dict, dict]:
|
||||||
|
missing = []
|
||||||
|
for path, snippets in EXPECTED.items():
|
||||||
|
text = path.read_text(encoding="utf-8")
|
||||||
|
for snippet in snippets:
|
||||||
|
if snippet not in text:
|
||||||
|
missing.append(f"{path.relative_to(ROOT)}: {snippet}")
|
||||||
|
if missing:
|
||||||
|
raise RuntimeError("NOAA/NWS fallback verification failed: " + "; ".join(missing))
|
||||||
|
|
||||||
|
contract = json.loads(CONTRACT.read_text(encoding="utf-8"))
|
||||||
|
manifest = json.loads(MANIFEST.read_text(encoding="utf-8"))
|
||||||
|
if contract.get("provider_id") != "noaa-nws":
|
||||||
|
raise RuntimeError("NOAA/NWS contract has wrong provider_id")
|
||||||
|
if contract.get("api_key_required") is not False:
|
||||||
|
raise RuntimeError("NOAA/NWS fallback should not require an API key")
|
||||||
|
return contract, manifest
|
||||||
|
|
||||||
|
|
||||||
|
def is_us_eligible(tile: dict) -> bool:
|
||||||
|
lat = float(tile["center_latitude"])
|
||||||
|
lon = float(tile["center_longitude"])
|
||||||
|
return 18.0 <= lat <= 72.0 and -180.0 <= lon <= -64.0
|
||||||
|
|
||||||
|
|
||||||
|
def assert_live_nws_points(contract: dict, manifest: dict) -> None:
|
||||||
|
if os.environ.get("AGRARIAN_SKIP_LIVE_WEATHER_CHECK") == "1":
|
||||||
|
print("Skipping live NOAA/NWS check because AGRARIAN_SKIP_LIVE_WEATHER_CHECK=1")
|
||||||
|
return
|
||||||
|
|
||||||
|
eligible_tiles = [tile for tile in manifest.get("tiles", []) if is_us_eligible(tile)]
|
||||||
|
if not eligible_tiles:
|
||||||
|
return
|
||||||
|
|
||||||
|
for tile in eligible_tiles:
|
||||||
|
url = f"{contract['points_endpoint']}/{float(tile['center_latitude']):.4f},{float(tile['center_longitude']):.4f}"
|
||||||
|
request = urllib.request.Request(
|
||||||
|
url,
|
||||||
|
headers={
|
||||||
|
"Accept": "application/geo+json",
|
||||||
|
"User-Agent": "AgrarianGameMVP/0.1",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
with urllib.request.urlopen(request, timeout=20) as response:
|
||||||
|
payload = json.loads(response.read().decode("utf-8"))
|
||||||
|
properties = payload.get("properties", {})
|
||||||
|
if not properties.get("forecastGridData"):
|
||||||
|
raise RuntimeError(f"NOAA/NWS points response missing forecastGridData for {tile['tile_id']}")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
contract, manifest = assert_static_contract()
|
||||||
|
assert_live_nws_points(contract, manifest)
|
||||||
|
print("NOAA/NWS US fallback verification complete.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -40,6 +40,7 @@ bool UAgrarianWeatherProviderSubsystem::RequestWeatherForTile(FName TileId, floa
|
|||||||
Request->SetURL(Url);
|
Request->SetURL(Url);
|
||||||
Request->SetVerb(TEXT("GET"));
|
Request->SetVerb(TEXT("GET"));
|
||||||
Request->SetHeader(TEXT("Accept"), TEXT("application/json"));
|
Request->SetHeader(TEXT("Accept"), TEXT("application/json"));
|
||||||
|
Request->SetHeader(TEXT("User-Agent"), TEXT("AgrarianGameMVP/0.1"));
|
||||||
Request->OnProcessRequestComplete().BindUObject(this, &UAgrarianWeatherProviderSubsystem::OnOpenMeteoResponse, TileId, Latitude, Longitude);
|
Request->OnProcessRequestComplete().BindUObject(this, &UAgrarianWeatherProviderSubsystem::OnOpenMeteoResponse, TileId, Latitude, Longitude);
|
||||||
return Request->ProcessRequest();
|
return Request->ProcessRequest();
|
||||||
}
|
}
|
||||||
@@ -55,6 +56,42 @@ FString UAgrarianWeatherProviderSubsystem::BuildOpenMeteoForecastUrl(FName TileI
|
|||||||
ClampedLongitude);
|
ClampedLongitude);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool UAgrarianWeatherProviderSubsystem::RequestNoaaNwsFallbackForTile(FName TileId, float Latitude, float Longitude)
|
||||||
|
{
|
||||||
|
if (!bEnableNoaaNwsFallbackForUSTiles || TileId == NAME_None || !IsNoaaNwsEligibleCoordinate(Latitude, Longitude))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
UWorld* World = GetWorld();
|
||||||
|
if (!World || !World->GetAuthGameMode())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = FHttpModule::Get().CreateRequest();
|
||||||
|
Request->SetURL(BuildNoaaNwsPointsUrl(Latitude, Longitude));
|
||||||
|
Request->SetVerb(TEXT("GET"));
|
||||||
|
Request->SetHeader(TEXT("Accept"), TEXT("application/geo+json"));
|
||||||
|
Request->SetHeader(TEXT("User-Agent"), NoaaNwsUserAgent);
|
||||||
|
Request->OnProcessRequestComplete().BindUObject(this, &UAgrarianWeatherProviderSubsystem::OnNoaaNwsPointsResponse, TileId, Latitude, Longitude);
|
||||||
|
return Request->ProcessRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UAgrarianWeatherProviderSubsystem::IsNoaaNwsEligibleCoordinate(float Latitude, float Longitude) const
|
||||||
|
{
|
||||||
|
return Latitude >= 18.0f && Latitude <= 72.0f && Longitude >= -180.0f && Longitude <= -64.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
FString UAgrarianWeatherProviderSubsystem::BuildNoaaNwsPointsUrl(float Latitude, float Longitude) const
|
||||||
|
{
|
||||||
|
return FString::Printf(
|
||||||
|
TEXT("%s/%.4f,%.4f"),
|
||||||
|
*NoaaNwsPointsEndpoint,
|
||||||
|
FMath::Clamp(Latitude, -90.0f, 90.0f),
|
||||||
|
FMath::Clamp(Longitude, -180.0f, 180.0f));
|
||||||
|
}
|
||||||
|
|
||||||
bool UAgrarianWeatherProviderSubsystem::ApplySnapshotToGameState(const FAgrarianWeatherProviderSnapshot& Snapshot, AAgrarianGameState* GameState) const
|
bool UAgrarianWeatherProviderSubsystem::ApplySnapshotToGameState(const FAgrarianWeatherProviderSnapshot& Snapshot, AAgrarianGameState* GameState) const
|
||||||
{
|
{
|
||||||
if (!Snapshot.bIsValid || !GameState || !GameState->HasAuthority())
|
if (!Snapshot.bIsValid || !GameState || !GameState->HasAuthority())
|
||||||
@@ -112,6 +149,63 @@ void UAgrarianWeatherProviderSubsystem::OnOpenMeteoResponse(FHttpRequestPtr Requ
|
|||||||
ApplySnapshotToGameState(Snapshot, GameState);
|
ApplySnapshotToGameState(Snapshot, GameState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UAgrarianWeatherProviderSubsystem::OnNoaaNwsPointsResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful, FName TileId, float Latitude, float Longitude)
|
||||||
|
{
|
||||||
|
if (!bWasSuccessful || !Response.IsValid() || Response->GetResponseCode() < 200 || Response->GetResponseCode() >= 300)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TSharedPtr<FJsonObject> RootObject;
|
||||||
|
const TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString());
|
||||||
|
if (!FJsonSerializer::Deserialize(Reader, RootObject) || !RootObject.IsValid())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TSharedPtr<FJsonObject>* PropertiesObject = nullptr;
|
||||||
|
if (!RootObject->TryGetObjectField(TEXT("properties"), PropertiesObject) || !PropertiesObject || !PropertiesObject->IsValid())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FString ForecastGridDataUrl;
|
||||||
|
if (!(*PropertiesObject)->TryGetStringField(TEXT("forecastGridData"), ForecastGridDataUrl) || ForecastGridDataUrl.IsEmpty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LastNoaaNwsForecastGridDataUrl = ForecastGridDataUrl;
|
||||||
|
|
||||||
|
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> GridRequest = FHttpModule::Get().CreateRequest();
|
||||||
|
GridRequest->SetURL(ForecastGridDataUrl);
|
||||||
|
GridRequest->SetVerb(TEXT("GET"));
|
||||||
|
GridRequest->SetHeader(TEXT("Accept"), TEXT("application/geo+json"));
|
||||||
|
GridRequest->SetHeader(TEXT("User-Agent"), NoaaNwsUserAgent);
|
||||||
|
GridRequest->OnProcessRequestComplete().BindUObject(this, &UAgrarianWeatherProviderSubsystem::OnNoaaNwsGridDataResponse, TileId, Latitude, Longitude);
|
||||||
|
GridRequest->ProcessRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UAgrarianWeatherProviderSubsystem::OnNoaaNwsGridDataResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful, FName TileId, float Latitude, float Longitude)
|
||||||
|
{
|
||||||
|
if (!bWasSuccessful || !Response.IsValid() || Response->GetResponseCode() < 200 || Response->GetResponseCode() >= 300)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FAgrarianWeatherProviderSnapshot Snapshot;
|
||||||
|
if (!ParseNoaaNwsGridData(Response->GetContentAsString(), TileId, Latitude, Longitude, Snapshot))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LastSnapshot = Snapshot;
|
||||||
|
|
||||||
|
UWorld* World = GetWorld();
|
||||||
|
AAgrarianGameState* GameState = World ? World->GetGameState<AAgrarianGameState>() : nullptr;
|
||||||
|
ApplySnapshotToGameState(Snapshot, GameState);
|
||||||
|
}
|
||||||
|
|
||||||
bool UAgrarianWeatherProviderSubsystem::ParseOpenMeteoForecast(const FString& ResponseContent, FName TileId, float Latitude, float Longitude, FAgrarianWeatherProviderSnapshot& OutSnapshot) const
|
bool UAgrarianWeatherProviderSubsystem::ParseOpenMeteoForecast(const FString& ResponseContent, FName TileId, float Latitude, float Longitude, FAgrarianWeatherProviderSnapshot& OutSnapshot) const
|
||||||
{
|
{
|
||||||
TSharedPtr<FJsonObject> RootObject;
|
TSharedPtr<FJsonObject> RootObject;
|
||||||
@@ -181,3 +275,63 @@ bool UAgrarianWeatherProviderSubsystem::ParseOpenMeteoForecast(const FString& Re
|
|||||||
OutSnapshot.bIsValid = true;
|
OutSnapshot.bIsValid = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool UAgrarianWeatherProviderSubsystem::ParseNoaaNwsGridData(const FString& ResponseContent, FName TileId, float Latitude, float Longitude, FAgrarianWeatherProviderSnapshot& OutSnapshot) const
|
||||||
|
{
|
||||||
|
TSharedPtr<FJsonObject> RootObject;
|
||||||
|
const TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(ResponseContent);
|
||||||
|
if (!FJsonSerializer::Deserialize(Reader, RootObject) || !RootObject.IsValid())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TSharedPtr<FJsonObject>* PropertiesObject = nullptr;
|
||||||
|
if (!RootObject->TryGetObjectField(TEXT("properties"), PropertiesObject) || !PropertiesObject || !PropertiesObject->IsValid())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ReadFirstGridValue = [](const TSharedPtr<FJsonObject>& Properties, const TCHAR* FieldName, double& OutValue) -> bool
|
||||||
|
{
|
||||||
|
const TSharedPtr<FJsonObject>* FieldObject = nullptr;
|
||||||
|
if (!Properties->TryGetObjectField(FieldName, FieldObject) || !FieldObject || !FieldObject->IsValid())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TArray<TSharedPtr<FJsonValue>>* Values = nullptr;
|
||||||
|
if (!(*FieldObject)->TryGetArrayField(TEXT("values"), Values) || !Values || Values->Num() == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TSharedPtr<FJsonObject> FirstValue = (*Values)[0]->AsObject();
|
||||||
|
return FirstValue.IsValid() && FirstValue->TryGetNumberField(TEXT("value"), OutValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
double TemperatureC = 0.0;
|
||||||
|
if (!ReadFirstGridValue(*PropertiesObject, TEXT("temperature"), TemperatureC))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
double PrecipitationPercent = 0.0;
|
||||||
|
double WindSpeedKmh = 0.0;
|
||||||
|
ReadFirstGridValue(*PropertiesObject, TEXT("probabilityOfPrecipitation"), PrecipitationPercent);
|
||||||
|
ReadFirstGridValue(*PropertiesObject, TEXT("windSpeed"), WindSpeedKmh);
|
||||||
|
|
||||||
|
OutSnapshot.TileId = TileId;
|
||||||
|
OutSnapshot.Latitude = FMath::Clamp(Latitude, -90.0f, 90.0f);
|
||||||
|
OutSnapshot.Longitude = FMath::Clamp(Longitude, -180.0f, 180.0f);
|
||||||
|
OutSnapshot.Provider = TEXT("noaa-nws");
|
||||||
|
OutSnapshot.ProviderTimestamp = FDateTime::UtcNow().ToIso8601();
|
||||||
|
OutSnapshot.CurrentTemperatureC = static_cast<float>(TemperatureC);
|
||||||
|
OutSnapshot.DailyLowTemperatureC = static_cast<float>(TemperatureC - 4.0);
|
||||||
|
OutSnapshot.DailyHighTemperatureC = static_cast<float>(TemperatureC + 4.0);
|
||||||
|
OutSnapshot.PrecipitationMm = PrecipitationPercent > 30.0 ? 0.1f : 0.0f;
|
||||||
|
OutSnapshot.WindSpeedKmh = static_cast<float>(WindSpeedKmh);
|
||||||
|
OutSnapshot.WeatherCode = PrecipitationPercent > 30.0 ? 61 : 0;
|
||||||
|
OutSnapshot.MappedWeather = MapOpenMeteoWeatherCode(OutSnapshot.WeatherCode, OutSnapshot.PrecipitationMm, OutSnapshot.WindSpeedKmh);
|
||||||
|
OutSnapshot.bIsValid = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|||||||
@@ -78,9 +78,21 @@ public:
|
|||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather")
|
||||||
FString OpenMeteoForecastEndpoint = TEXT("https://api.open-meteo.com/v1/forecast");
|
FString OpenMeteoForecastEndpoint = TEXT("https://api.open-meteo.com/v1/forecast");
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather")
|
||||||
|
bool bEnableNoaaNwsFallbackForUSTiles = true;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather")
|
||||||
|
FString NoaaNwsPointsEndpoint = TEXT("https://api.weather.gov/points");
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather")
|
||||||
|
FString NoaaNwsUserAgent = TEXT("AgrarianGameMVP/0.1");
|
||||||
|
|
||||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Agrarian|Weather")
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Agrarian|Weather")
|
||||||
FAgrarianWeatherProviderSnapshot LastSnapshot;
|
FAgrarianWeatherProviderSnapshot LastSnapshot;
|
||||||
|
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Agrarian|Weather")
|
||||||
|
FString LastNoaaNwsForecastGridDataUrl;
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|Weather")
|
UFUNCTION(BlueprintCallable, Category = "Agrarian|Weather")
|
||||||
bool RequestWeatherForActiveGameState();
|
bool RequestWeatherForActiveGameState();
|
||||||
|
|
||||||
@@ -90,6 +102,15 @@ public:
|
|||||||
UFUNCTION(BlueprintPure, Category = "Agrarian|Weather")
|
UFUNCTION(BlueprintPure, Category = "Agrarian|Weather")
|
||||||
FString BuildOpenMeteoForecastUrl(FName TileId, float Latitude, float Longitude) const;
|
FString BuildOpenMeteoForecastUrl(FName TileId, float Latitude, float Longitude) const;
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Agrarian|Weather")
|
||||||
|
bool RequestNoaaNwsFallbackForTile(FName TileId, float Latitude, float Longitude);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintPure, Category = "Agrarian|Weather")
|
||||||
|
bool IsNoaaNwsEligibleCoordinate(float Latitude, float Longitude) const;
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintPure, Category = "Agrarian|Weather")
|
||||||
|
FString BuildNoaaNwsPointsUrl(float Latitude, float Longitude) const;
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|Weather")
|
UFUNCTION(BlueprintCallable, Category = "Agrarian|Weather")
|
||||||
bool ApplySnapshotToGameState(const FAgrarianWeatherProviderSnapshot& Snapshot, AAgrarianGameState* GameState) const;
|
bool ApplySnapshotToGameState(const FAgrarianWeatherProviderSnapshot& Snapshot, AAgrarianGameState* GameState) const;
|
||||||
|
|
||||||
@@ -97,5 +118,8 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void OnOpenMeteoResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful, FName TileId, float Latitude, float Longitude);
|
void OnOpenMeteoResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful, FName TileId, float Latitude, float Longitude);
|
||||||
|
void OnNoaaNwsPointsResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful, FName TileId, float Latitude, float Longitude);
|
||||||
|
void OnNoaaNwsGridDataResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful, FName TileId, float Latitude, float Longitude);
|
||||||
bool ParseOpenMeteoForecast(const FString& ResponseContent, FName TileId, float Latitude, float Longitude, FAgrarianWeatherProviderSnapshot& OutSnapshot) const;
|
bool ParseOpenMeteoForecast(const FString& ResponseContent, FName TileId, float Latitude, float Longitude, FAgrarianWeatherProviderSnapshot& OutSnapshot) const;
|
||||||
|
bool ParseNoaaNwsGridData(const FString& ResponseContent, FName TileId, float Latitude, float Longitude, FAgrarianWeatherProviderSnapshot& OutSnapshot) const;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user