mirror of
https://github.com/ciphervance/supercell-wx.git
synced 2025-10-29 22:30:04 +00:00
268 lines
6.7 KiB
C++
268 lines
6.7 KiB
C++
// Prevent redefinition of __cpp_lib_format
|
|
#if defined(_MSC_VER)
|
|
# include <yvals_core.h>
|
|
#endif
|
|
|
|
// Enable chrono formatters
|
|
#ifndef __cpp_lib_format
|
|
# define __cpp_lib_format 202110L
|
|
#endif
|
|
|
|
#include <scwx/awips/coded_time_motion_location.hpp>
|
|
#include <scwx/util/logger.hpp>
|
|
|
|
#include <sstream>
|
|
|
|
#if (__cpp_lib_chrono < 201907L)
|
|
# include <date/date.h>
|
|
#endif
|
|
|
|
namespace scwx
|
|
{
|
|
namespace awips
|
|
{
|
|
|
|
static const std::string logPrefix_ = "scwx::awips::coded_time_motion_location";
|
|
static const auto logger_ = util::Logger::Create(logPrefix_);
|
|
|
|
class CodedTimeMotionLocationImpl
|
|
{
|
|
public:
|
|
explicit CodedTimeMotionLocationImpl() :
|
|
time_ {}, direction_ {0}, speed_ {0}, coordinates_ {}
|
|
{
|
|
}
|
|
|
|
~CodedTimeMotionLocationImpl() {}
|
|
|
|
std::chrono::hh_mm_ss<std::chrono::minutes> time_;
|
|
uint16_t direction_;
|
|
uint8_t speed_;
|
|
std::vector<common::Coordinate> coordinates_;
|
|
};
|
|
|
|
CodedTimeMotionLocation::CodedTimeMotionLocation() :
|
|
p(std::make_unique<CodedTimeMotionLocationImpl>())
|
|
{
|
|
}
|
|
CodedTimeMotionLocation::~CodedTimeMotionLocation() = default;
|
|
|
|
CodedTimeMotionLocation::CodedTimeMotionLocation(
|
|
CodedTimeMotionLocation&&) noexcept = default;
|
|
CodedTimeMotionLocation& CodedTimeMotionLocation::operator=(
|
|
CodedTimeMotionLocation&&) noexcept = default;
|
|
|
|
std::chrono::hh_mm_ss<std::chrono::minutes>
|
|
CodedTimeMotionLocation::time() const
|
|
{
|
|
return p->time_;
|
|
}
|
|
|
|
uint16_t CodedTimeMotionLocation::direction() const
|
|
{
|
|
return p->direction_;
|
|
}
|
|
|
|
uint8_t CodedTimeMotionLocation::speed() const
|
|
{
|
|
return p->speed_;
|
|
}
|
|
|
|
std::vector<common::Coordinate> CodedTimeMotionLocation::coordinates() const
|
|
{
|
|
return p->coordinates_;
|
|
}
|
|
|
|
bool CodedTimeMotionLocation::Parse(const StringRange& lines,
|
|
const std::string& wfo)
|
|
{
|
|
bool dataValid = true;
|
|
|
|
std::vector<std::string> tokenList;
|
|
|
|
for (std::string line : lines)
|
|
{
|
|
std::string token;
|
|
std::istringstream tokenStream {line};
|
|
|
|
while (tokenStream >> token)
|
|
{
|
|
tokenList.push_back(token);
|
|
}
|
|
}
|
|
|
|
// First token is "TIME...MOT...LOC"
|
|
// At a minimum, one point (latitude/longitude pair) will be included
|
|
dataValid = (tokenList.size() >= 6 && tokenList.size() % 2 == 0 &&
|
|
tokenList.at(0) == "TIME...MOT...LOC");
|
|
|
|
if (dataValid)
|
|
{
|
|
const bool wfoIsWest = (wfo != "PGUM");
|
|
double westLongitude = (wfoIsWest) ? -1.0 : 1.0;
|
|
bool straddlesDateLine = false;
|
|
|
|
// Time: hhmmZ
|
|
std::string time = tokenList.at(1);
|
|
{
|
|
using namespace std::chrono;
|
|
|
|
#if (__cpp_lib_chrono < 201907L)
|
|
using namespace date;
|
|
#endif
|
|
|
|
static const std::string timeFormat {"%H%MZ"};
|
|
|
|
std::istringstream in {time};
|
|
minutes tp;
|
|
in >> parse(timeFormat, tp);
|
|
|
|
if (time.size() == 5 && !in.fail())
|
|
{
|
|
p->time_ = std::chrono::hh_mm_ss {tp};
|
|
}
|
|
else
|
|
{
|
|
logger_->warn("Invalid time: \"{}\"", time);
|
|
p->time_ = std::chrono::hh_mm_ss<minutes> {};
|
|
dataValid = false;
|
|
}
|
|
}
|
|
|
|
// Direction: dirDEG
|
|
std::string direction = tokenList.at(2);
|
|
if (direction.size() == 6 && direction.ends_with("DEG"))
|
|
{
|
|
try
|
|
{
|
|
p->direction_ =
|
|
static_cast<uint16_t>(std::stoul(direction.substr(0, 3)));
|
|
}
|
|
catch (const std::exception& ex)
|
|
{
|
|
logger_->warn(
|
|
"Invalid direction: \"{}\" ({})", direction, ex.what());
|
|
dataValid = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logger_->warn("Invalid direction: \"{}\"", direction);
|
|
dataValid = false;
|
|
}
|
|
|
|
// Speed: <sp>KT
|
|
std::string speed = tokenList.at(3);
|
|
if (speed.size() >= 3 && speed.size() <= 5 && speed.ends_with("KT"))
|
|
{
|
|
try
|
|
{
|
|
// NWSI 10-1701 specifies a valid speed range of 0-99 knots.
|
|
// However, sometimes text products are published with a larger
|
|
// value. Instead, allow a value up to 255 knots.
|
|
auto parsedSpeed = std::stoul(speed.substr(0, speed.size() - 2));
|
|
if (parsedSpeed <= 255u)
|
|
{
|
|
p->speed_ = static_cast<uint8_t>(parsedSpeed);
|
|
}
|
|
else
|
|
{
|
|
logger_->warn("Invalid speed: \"{}\"", speed);
|
|
dataValid = false;
|
|
}
|
|
}
|
|
catch (const std::exception& ex)
|
|
{
|
|
logger_->warn("Invalid speed: \"{}\" ({})", speed, ex.what());
|
|
dataValid = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logger_->warn("Invalid speed: \"{}\"", speed);
|
|
dataValid = false;
|
|
}
|
|
|
|
// Location
|
|
for (auto token = tokenList.cbegin() + 4; token != tokenList.cend();
|
|
++token)
|
|
{
|
|
double latitude = 0.0;
|
|
double longitude = 0.0;
|
|
|
|
try
|
|
{
|
|
latitude = std::stod(*token) * 0.01;
|
|
++token;
|
|
longitude = std::stod(*token) * 0.01;
|
|
}
|
|
catch (const std::exception& ex)
|
|
{
|
|
logger_->warn(
|
|
"Invalid location token: \"{}\" ({})", *token, ex.what());
|
|
dataValid = false;
|
|
break;
|
|
}
|
|
|
|
// If a given product straddles 180 degrees longitude, those points
|
|
// west of 180 degrees will be given as if they were west longitude
|
|
if (longitude > 180.0)
|
|
{
|
|
longitude -= 360.0;
|
|
straddlesDateLine = true;
|
|
}
|
|
|
|
longitude *= westLongitude;
|
|
|
|
p->coordinates_.push_back({latitude, longitude});
|
|
}
|
|
|
|
if (!wfoIsWest && straddlesDateLine)
|
|
{
|
|
for (auto& coordinate : p->coordinates_)
|
|
{
|
|
coordinate.longitude_ *= -1.0;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (tokenList.empty())
|
|
{
|
|
logger_->warn("TIME...MOT...LOC not found");
|
|
}
|
|
else
|
|
{
|
|
logger_->warn("Malformed TIME...MOT...LOC tokens: (0: {}, size: {})",
|
|
tokenList.at(0),
|
|
tokenList.size());
|
|
|
|
for (const auto& token : tokenList)
|
|
{
|
|
logger_->debug("{}", token);
|
|
}
|
|
}
|
|
|
|
dataValid = false;
|
|
}
|
|
|
|
return dataValid;
|
|
}
|
|
|
|
std::optional<CodedTimeMotionLocation>
|
|
CodedTimeMotionLocation::Create(const StringRange& lines,
|
|
const std::string& wfo)
|
|
{
|
|
std::optional<CodedTimeMotionLocation> motion =
|
|
std::make_optional<CodedTimeMotionLocation>();
|
|
|
|
if (!motion->Parse(lines, wfo))
|
|
{
|
|
motion.reset();
|
|
}
|
|
|
|
return motion;
|
|
}
|
|
|
|
} // namespace awips
|
|
} // namespace scwx
|