Skip to content

Commit

Permalink
Feat/Weather/RT: Prefer METAR in direction of flight
Browse files Browse the repository at this point in the history
User Max. METAR Dist also for standard METAR search
  • Loading branch information
TwinFan committed Sep 10, 2024
1 parent a51dc7d commit 80a42d6
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 45 deletions.
8 changes: 4 additions & 4 deletions Include/LTRealTraffic.h
Original file line number Diff line number Diff line change
Expand Up @@ -291,8 +291,8 @@ class RealTrafficConnection : public LTFlightDataChannel
/// METAR entry in the NearestMETAR response
struct NearestMETAR {
std::string ICAO = RT_METAR_UNKN; ///< ICAO code of METAR station
float dist = NAN; ///< distance to station
float brgTo = NAN; ///< bearing to station
double dist = NAN; ///< distance to station
double brgTo = NAN; ///< bearing to station

NearestMETAR() {} ///< Standard constructor, all empty
NearestMETAR(const JSON_Object* pObj) { Parse (pObj); } ///< Fill from JSON
Expand All @@ -307,7 +307,7 @@ class RealTrafficConnection : public LTFlightDataChannel
struct WxTy {
double QNH = NAN; ///< baro pressure
std::chrono::steady_clock::time_point next; ///< next time to request RealTraffic weather
positionTy pos; ///< viewer position for which we received Realtraffic weather
positionTy pos; ///< plane position for which we requested Realtraffic weather
NearestMETAR nearestMETAR; ///< info on nearest METAR
long tOff = 0; ///< time offset for which we requested weather
int nErr = 0; ///< How many errors did we have during weather requests?
Expand All @@ -316,7 +316,7 @@ class RealTrafficConnection : public LTFlightDataChannel
std::array<LTWeather::InterpolSet,13> interp; ///< interpolation settings to convert from RT's 20 layers to XP's 13

/// Set all relevant values
void set (double qnh, const CurrTy& o, bool bResetErr = true);
void set (double qnh, long _tOff, bool bResetErr = true);
} rtWx; ///< Data with which latest weather was requested
/// How many flights does RealTraffic have in total?
long lTotalFlights = -1;
Expand Down
19 changes: 3 additions & 16 deletions Src/DataRefs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1746,17 +1746,6 @@ bool DataRefs::SetCfgValue (void* p, int val)
return false;
}

// Special handling for fdSnapTaxiDist:
if (oldFdSnapTaxiDist != fdSnapTaxiDist) // snap taxi dist did change
{
// switched from on to off?
if (oldFdSnapTaxiDist > 0 && fdSnapTaxiDist == 0)
LTAptDisable();
// switched from off to on?
else if (oldFdSnapTaxiDist == 0 && fdSnapTaxiDist > 0)
LTAptEnable();
}

// If label draw distance changes we need to tell XPMP2
if (p == &labelMaxDist)
XPMPSetAircraftLabelDist(float(labelMaxDist), bLabelVisibilityCUtOff);
Expand Down Expand Up @@ -2691,9 +2680,7 @@ bool DataRefs::ToggleLabelDraw()
//

constexpr float WEATHER_TRY_PERIOD = 120.0f; ///< [s] Don't _try_ to read weather more often than this
constexpr float WEATHER_UPD_PERIOD = 600.0f; ///< [s] Weather to be updated at leas this often
constexpr double WEATHER_UPD_DIST_M = 25.0 * M_per_NM; ///< [m] Weather to be updated if moved more than this far from last weather update position
constexpr float WEATHER_SEARCH_RADIUS_NM = 25; ///< [nm] Search for latest weather reports in this radius
constexpr float WEATHER_UPD_PERIOD = 600.0f; ///< [s] Weather to be updated at least this often

// check if weather updated needed, then do
bool DataRefs::WeatherFetchMETAR ()
Expand All @@ -2710,14 +2697,14 @@ bool DataRefs::WeatherFetchMETAR ()
( // had no weather yet at all?
std::isnan(lastWeatherPos.lat()) ||
// moved far away from last weather pos?
posUser.dist(lastWeatherPos) > WEATHER_UPD_DIST_M ||
posUser.dist(lastWeatherPos) > double(GetWeatherMaxMetarDist_m())/2.0 ||
// enough time passed since last weather update?
lastWeatherUpd + WEATHER_UPD_PERIOD < GetMiscNetwTime()
))
{
// Trigger a weather update; this is an asynch operation
lastWeatherAttempt = GetMiscNetwTime();
return ::WeatherFetchUpdate(posUser, WEATHER_SEARCH_RADIUS_NM); // travel distances [m] doubles as weather search distance [nm]
return ::WeatherFetchUpdate(posUser, GetWeatherMaxMetarDist_nm());
}
return false;
}
Expand Down
60 changes: 44 additions & 16 deletions Src/LTRealTraffic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,10 @@ const std::vector<float> RT_ATMOS_LAYERS = {
//

// Set all relevant values
void RealTrafficConnection::WxTy::set(double qnh, const CurrTy& o, bool bResetErr)
void RealTrafficConnection::WxTy::set(double qnh, long _tOff, bool bResetErr)
{
QNH = qnh;
pos = o.pos;
tOff = o.tOff;
tOff = _tOff;
next = std::chrono::steady_clock::now() + RT_DRCT_WX_WAIT;
if (bResetErr)
nErr = 0;
Expand Down Expand Up @@ -311,11 +310,14 @@ std::chrono::time_point<std::chrono::steady_clock> RealTrafficConnection::SetReq
curr.eRequType = CurrTy::RT_REQU_WEATHER;
return tNextWeather;
}
const positionTy posUser = dataRefs.GetUsersPlanePos(); // Where is the user's plane just now=
if (rtWx.nErr < RT_DRCT_MAX_WX_ERR && // not yet seen too many weather request errors? _AND_
(std::isnan(rtWx.QNH) || // no Weather, or wrong time offset, or outdated, or moved too far away?
std::labs(curr.tOff - rtWx.tOff) > 120 ||
// too far? (we use half the max. METAR distance
rtWx.pos.distRoughSqr(curr.pos) > (sqr(dataRefs.GetWeatherMaxMetarDist_m()/2.0))))
rtWx.pos.distRoughSqr(posUser) > (sqr(dataRefs.GetWeatherMaxMetarDist_m()/2.0)) ||
// or turned by at least 45 degrees? (Heading into a different direction can mean to select a different METAR)
std::abs(HeadingDiff(rtWx.pos.heading(), posUser.heading())) > 45.0))
{
curr.eRequType = CurrTy::RT_REQU_NEAREST_METAR;
if (std::labs(curr.tOff - rtWx.tOff) > 120) // if changing the timeoffset (request other historic data) then we must have new weather before proceeding
Expand Down Expand Up @@ -384,15 +386,17 @@ void RealTrafficConnection::ComputeBody (const positionTy&)
curr.sGUID.c_str());
break;
case CurrTy::RT_REQU_NEAREST_METAR:
rtWx.pos = dataRefs.GetUsersPlanePos();
snprintf(s, sizeof(s), RT_NEAREST_METAR_POST,
curr.sGUID.c_str(),
curr.pos.lat(), curr.pos.lon(),
rtWx.pos.lat(), rtWx.pos.lon(),
curr.tOff);
break;
case CurrTy::RT_REQU_WEATHER:
rtWx.pos = dataRefs.GetUsersPlanePos();
snprintf(s, sizeof(s), RT_WEATHER_POST,
curr.sGUID.c_str(),
curr.pos.lat(), curr.pos.lon(), std::lround(curr.pos.alt_ft()),
rtWx.pos.lat(), rtWx.pos.lon(), std::lround(rtWx.pos.alt_ft()),
rtWx.nearestMETAR.ICAO.c_str(),
curr.tOff);
break;
Expand Down Expand Up @@ -595,7 +599,8 @@ bool RealTrafficConnection::ProcessFetchedData ()
// Too many WX errors? We give up and just use standard pressure
if (rtWx.nErr >= RT_DRCT_MAX_WX_ERR) {
SHOW_MSG(logERR, "Too many errors trying to fetch RealTraffic weather, will continue without; planes may appear at slightly wrong altitude.");
rtWx.set(HPA_STANDARD, curr, false);
rtWx.set(HPA_STANDARD, curr.tOff, false);
rtWx.pos = positionTy();
} else {
// We will request weather directly again, but need to wait 60s for it
tNextWeather = std::chrono::steady_clock::now() + std::chrono::seconds(60);
Expand Down Expand Up @@ -626,7 +631,7 @@ bool RealTrafficConnection::ProcessFetchedData ()
}

// Successfully received local pressure information
rtWx.set(std::isnan(rtWx.w.qnh_pas) ? wxQNH : double(rtWx.w.qnh_pas), curr); // Save new QNH
rtWx.set(std::isnan(rtWx.w.qnh_pas) ? wxQNH : double(rtWx.w.qnh_pas), curr.tOff); // Save new QNH
LOG_MSG(logDEBUG, "Received RealTraffic Weather with QNH = %.1f", rtWx.QNH);

// If requested to set X-Plane's weather based on detailed weather data
Expand Down Expand Up @@ -1096,8 +1101,8 @@ bool RealTrafficConnection::NearestMETAR::Parse (const JSON_Object* pObj)
}

ICAO = jog_s(pObj, "ICAO");
dist = (float)jog_n_nan(pObj, "Dist");
brgTo = (float)jog_n_nan(pObj, "BrgTo");
dist = jog_n_nan(pObj, "Dist");
brgTo = jog_n_nan(pObj, "BrgTo");

return isValid();
}
Expand All @@ -1115,25 +1120,48 @@ void RealTrafficConnection::ProcessNearestMETAR (const JSON_Array* pData)
rtWx.nearestMETAR.clear();

// Array must have at least one element
if (json_array_get_count(pData) < 1) {
const size_t cntMetars = json_array_get_count(pData);
if (cntMetars < 1) {
LOG_MSG(logWARN, "Received no nearest METARs from RealTraffic ('data' array empty)");
return;
}

// The first METAR is the closest, so a priori best cadidate to be used
rtWx.nearestMETAR.Parse(json_array_get_object(pData, 0));
if (!rtWx.nearestMETAR.isValid()) {
NearestMETAR m(json_array_get_object(pData, 0));
if (!m.isValid()) {
LOG_MSG(logWARN, "Nearest METAR ('data[0]') isn't valid");
return;
}
// even the nearest station is too far away for reliable weather?
if (rtWx.nearestMETAR.dist > dataRefs.GetWeatherMaxMetarDist_nm()) {
if (m.dist > dataRefs.GetWeatherMaxMetarDist_nm()) {
LOG_MSG(logDEBUG, "Nearest METAR location too far away, using none");
rtWx.nearestMETAR.clear();
return;
}

// Check for better matching station in direction of flight,
// but start with that one
rtWx.nearestMETAR = m;

// Factor in bearing of station in relation to current plane's flight path:
// It's worth half the distance if it is straight ahead (0 deg difference)
// and the full distance if it is in my back (180 deg difference),
// so that position ahead of me appear "nearer"
double bestDist = m.dist * (180.0 + std::abs(HeadingDiff(rtWx.pos.heading(), double(m.brgTo)))) / 360.0;

for (size_t i = 1; i < cntMetars; ++i)
{
m.Parse(json_array_get_object(pData, i)); // parse the next METAR info record
if (m.dist > dataRefs.GetWeatherMaxMetarDist_nm()) // stop processing once the stations are too far away
break;
// based on weighted distance, is this station "closer" than our current best?
const double weightedDist = m.dist * (180.0 + std::abs(HeadingDiff(rtWx.pos.heading(), double(m.brgTo)))) / 360.0;
if (weightedDist < bestDist) {
bestDist = weightedDist; // then use it!
rtWx.nearestMETAR = m;
}
}

// TODO: Check for better matching station in direction of flight
LOG_MSG(logDEBUG, "Using Nearest METAR location %s (%.1fnm, %.0fdeg)",
rtWx.nearestMETAR.ICAO.c_str(),
rtWx.nearestMETAR.dist, rtWx.nearestMETAR.brgTo);
Expand Down Expand Up @@ -1166,7 +1194,7 @@ void RealTrafficConnection::ProcessWeather(const JSON_Object* pData)
LOG_MSG(logWARN, "JSON response is missing one of the following arrays in data.locWX: DPs, TEMPs, WDIRs, WSPDs, DZDTs");
}

rtWx.w.pos = curr.pos;
rtWx.w.pos = rtWx.pos;

// METAR
rtWx.w.metar = jog_s(pData, "METAR");
Expand Down
26 changes: 17 additions & 9 deletions Src/LTWeather.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1165,9 +1165,6 @@ void LTWeather::IncorporateMETAR()
/// The request URL, parameters are in this order: radius, longitude, latitude
const char* WEATHER_URL="https://aviationweather.gov/api/data/metar?format=json&bbox=%.2f,%.2f,%.2f,%.2f";

/// METAR search radius
constexpr float METAR_SEARCH_RADIUS_NM = 250.0f;

/// Didn't find a METAR last time we tried?
bool gbFoundNoMETAR = false;

Expand Down Expand Up @@ -1221,21 +1218,27 @@ bool WeatherProcessResponse (const std::string& _r)
}

// Compare METAR field's position with best we have so far, skip if father away
const double dist = DistLatLonSqr(posUser.lat(), posUser.lon(), lat, lon);
if (dist >= bestDist)
vectorTy vec = posUser.between(positionTy(lat,lon));
if (vec.dist > dataRefs.GetWeatherMaxMetarDist_m()) // skip if too far away
break;
// Weigh in direction of flight into distance calculation:
// In direction of flight, distance only appears half as far,
// so we prefer METARs in the direction of flight
vec.dist *= (180.0 + std::abs(HeadingDiff(posUser.heading(), vec.angle))) / 360.0;
if (vec.dist >= bestDist)
continue;;

// We have a new nearest METAR
bestLat = lat;
bestLon = lon;
bestDist = dist;
bestDist = vec.dist;
bestHPa = hPa;
stationId = jog_s(pMObj, "icaoId");
METAR = jog_s(pMObj, "rawOb");
}

// If we found something
if (!std::isnan(bestLat) || std::isnan(bestLon) || std::isnan(bestHPa)) {
if (!std::isnan(bestLat) && !std::isnan(bestLon) && !std::isnan(bestHPa)) {
// If previously we had not found anything say huray
if (gbFoundNoMETAR) {
LOG_MSG(logINFO, INFO_FOUND_WEATHER_AGAIN, stationId.c_str());
Expand Down Expand Up @@ -1282,8 +1285,8 @@ bool WeatherFetch (float _lat, float _lon, float _radius_nm)
return false;
}

// put together the URL, convert nautical to statute miles
const boundingBoxTy box (positionTy(_lat, _lon), _radius_nm * M_per_NM);
// put together the URL, with a bounding box with _radius_nm in each direction
const boundingBoxTy box (positionTy(_lat, _lon), _radius_nm * M_per_NM * 2.0);
const positionTy minPos = box.sw();
const positionTy maxPos = box.ne();
snprintf(url, sizeof(url), WEATHER_URL,
Expand Down Expand Up @@ -1494,6 +1497,11 @@ void WeatherUpdate ()
WeatherInControl() ? "Switching to XP Real Weather" :
"LiveTraffic takes over controlling X-Plane's weather, activating XP's real weather",
bNoNearbyMETAR ? "no nearby METAR" : "flying high");
// Remember that we did _not_ use a METAR to define weather
setWeather.metar.clear();
setWeather.metarFieldIcao.clear();
setWeather.posMetarField = positionTy();
// Set to XP real weather
WeatherSetXPRealWeather();
bWeatherControlling = true;
}
Expand Down

0 comments on commit 80a42d6

Please sign in to comment.