mirror of
https://github.com/ciphervance/supercell-wx.git
synced 2025-10-29 22:30:04 +00:00
401 lines
11 KiB
C++
401 lines
11 KiB
C++
#include <scwx/awips/wmo_header.hpp>
|
|
#include <scwx/util/logger.hpp>
|
|
#include <scwx/util/streams.hpp>
|
|
|
|
#include <istream>
|
|
#include <sstream>
|
|
#include <string>
|
|
|
|
#ifdef _WIN32
|
|
# include <WinSock2.h>
|
|
#else
|
|
# include <arpa/inet.h>
|
|
#endif
|
|
|
|
namespace scwx::awips
|
|
{
|
|
|
|
static const std::string logPrefix_ = "scwx::awips::wmo_header";
|
|
static const auto logger_ = util::Logger::Create(logPrefix_);
|
|
|
|
static constexpr std::size_t kWmoHeaderMinLineLength_ = 18;
|
|
static constexpr std::size_t kWmoIdentifierLengthMin_ = 5;
|
|
static constexpr std::size_t kWmoIdentifierLengthMax_ = 6;
|
|
static constexpr std::size_t kIcaoLength_ = 4;
|
|
static constexpr std::size_t kDateTimeLength_ = 6;
|
|
static constexpr std::size_t kAwipsIdentifierLineLength_ = 6;
|
|
|
|
class WmoHeaderImpl
|
|
{
|
|
public:
|
|
explicit WmoHeaderImpl() :
|
|
sequenceNumber_ {},
|
|
dataType_ {},
|
|
geographicDesignator_ {},
|
|
bulletinId_ {},
|
|
icao_ {},
|
|
dateTime_ {},
|
|
bbbIndicator_ {},
|
|
productCategory_ {},
|
|
productDesignator_ {}
|
|
{
|
|
}
|
|
~WmoHeaderImpl() = default;
|
|
|
|
WmoHeaderImpl(const WmoHeaderImpl&) = delete;
|
|
WmoHeaderImpl& operator=(const WmoHeaderImpl&) = delete;
|
|
WmoHeaderImpl(const WmoHeaderImpl&&) = delete;
|
|
WmoHeaderImpl& operator=(const WmoHeaderImpl&&) = delete;
|
|
|
|
void CalculateAbsoluteDateTime();
|
|
bool ParseDateTime(unsigned int& dayOfMonth,
|
|
unsigned long& hour,
|
|
unsigned long& minute);
|
|
|
|
bool operator==(const WmoHeaderImpl& o) const;
|
|
|
|
std::string sequenceNumber_ {};
|
|
std::string dataType_ {};
|
|
std::string geographicDesignator_ {};
|
|
std::string bulletinId_ {};
|
|
std::string icao_ {};
|
|
std::string dateTime_ {};
|
|
std::string bbbIndicator_ {};
|
|
std::string productCategory_ {};
|
|
std::string productDesignator_ {};
|
|
|
|
std::optional<std::chrono::year_month> dateHint_ {};
|
|
std::optional<std::chrono::sys_time<std::chrono::minutes>>
|
|
absoluteDateTime_ {};
|
|
};
|
|
|
|
WmoHeader::WmoHeader() : p(std::make_unique<WmoHeaderImpl>()) {}
|
|
WmoHeader::~WmoHeader() = default;
|
|
|
|
WmoHeader::WmoHeader(WmoHeader&&) noexcept = default;
|
|
WmoHeader& WmoHeader::operator=(WmoHeader&&) noexcept = default;
|
|
|
|
bool WmoHeader::operator==(const WmoHeader& o) const
|
|
{
|
|
return (*p.get() == *o.p.get());
|
|
}
|
|
|
|
bool WmoHeaderImpl::operator==(const WmoHeaderImpl& o) const
|
|
{
|
|
return (sequenceNumber_ == o.sequenceNumber_ && //
|
|
dataType_ == o.dataType_ && //
|
|
geographicDesignator_ == o.geographicDesignator_ && //
|
|
bulletinId_ == o.bulletinId_ && //
|
|
icao_ == o.icao_ && //
|
|
dateTime_ == o.dateTime_ && //
|
|
bbbIndicator_ == o.bbbIndicator_ && //
|
|
productCategory_ == o.productCategory_ && //
|
|
productDesignator_ == o.productDesignator_);
|
|
}
|
|
|
|
std::string WmoHeader::sequence_number() const
|
|
{
|
|
return p->sequenceNumber_;
|
|
}
|
|
|
|
std::string WmoHeader::data_type() const
|
|
{
|
|
return p->dataType_;
|
|
}
|
|
|
|
std::string WmoHeader::geographic_designator() const
|
|
{
|
|
return p->geographicDesignator_;
|
|
}
|
|
|
|
std::string WmoHeader::bulletin_id() const
|
|
{
|
|
return p->bulletinId_;
|
|
}
|
|
|
|
std::string WmoHeader::icao() const
|
|
{
|
|
return p->icao_;
|
|
}
|
|
|
|
std::string WmoHeader::date_time() const
|
|
{
|
|
return p->dateTime_;
|
|
}
|
|
|
|
std::string WmoHeader::bbb_indicator() const
|
|
{
|
|
return p->bbbIndicator_;
|
|
}
|
|
|
|
std::string WmoHeader::product_category() const
|
|
{
|
|
return p->productCategory_;
|
|
}
|
|
|
|
std::string WmoHeader::product_designator() const
|
|
{
|
|
return p->productDesignator_;
|
|
}
|
|
|
|
std::chrono::sys_time<std::chrono::minutes> WmoHeader::GetDateTime(
|
|
std::optional<std::chrono::system_clock::time_point> endTimeHint)
|
|
{
|
|
std::chrono::sys_time<std::chrono::minutes> wmoDateTime {};
|
|
|
|
const auto absoluteDateTime = p->absoluteDateTime_;
|
|
|
|
if (absoluteDateTime.has_value())
|
|
{
|
|
wmoDateTime = absoluteDateTime.value();
|
|
}
|
|
else if (endTimeHint.has_value())
|
|
{
|
|
bool dateTimeValid = false;
|
|
unsigned int dayOfMonth = 0;
|
|
unsigned long hour = 0;
|
|
unsigned long minute = 0;
|
|
|
|
dateTimeValid = p->ParseDateTime(dayOfMonth, hour, minute);
|
|
|
|
if (dateTimeValid)
|
|
{
|
|
using namespace std::chrono;
|
|
|
|
const auto endDays = floor<days>(endTimeHint.value());
|
|
const year_month_day endDate {endDays};
|
|
|
|
// Combine end date year and month with WMO date time
|
|
wmoDateTime =
|
|
sys_days {endDate.year() / endDate.month() / day {dayOfMonth}} +
|
|
hours {hour} + minutes {minute};
|
|
|
|
// If the begin date is after the end date, assume the start time
|
|
// was the previous month (give a 1 day grace period for expiring
|
|
// events in the past)
|
|
// NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers)
|
|
if (wmoDateTime > endTimeHint.value() + 24h)
|
|
{
|
|
// If the current end month is January
|
|
if (endDate.month() == January)
|
|
{
|
|
// The begin month must be December of last year
|
|
wmoDateTime =
|
|
sys_days {
|
|
year {static_cast<int>((endDate.year() - 1y).count())} /
|
|
December / day {dayOfMonth}} +
|
|
hours {hour} + minutes {minute};
|
|
}
|
|
else
|
|
{
|
|
// Back up one month
|
|
wmoDateTime =
|
|
sys_days {endDate.year() /
|
|
month {static_cast<unsigned int>(
|
|
(endDate.month() - month {1}).count())} /
|
|
day {dayOfMonth}} +
|
|
hours {hour} + minutes {minute};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return wmoDateTime;
|
|
}
|
|
|
|
bool WmoHeader::Parse(std::istream& is)
|
|
{
|
|
bool headerValid = true;
|
|
|
|
std::string sohLine;
|
|
std::string sequenceLine;
|
|
std::string wmoLine;
|
|
std::string awipsLine;
|
|
|
|
if (is.peek() == 0x01)
|
|
{
|
|
util::getline(is, sohLine);
|
|
util::getline(is, sequenceLine);
|
|
util::getline(is, wmoLine);
|
|
}
|
|
else
|
|
{
|
|
// The next line could be the WMO line or the sequence line
|
|
util::getline(is, wmoLine);
|
|
if (wmoLine.length() < kWmoHeaderMinLineLength_)
|
|
{
|
|
// This is likely the sequence line instead
|
|
sequenceLine.swap(wmoLine);
|
|
util::getline(is, wmoLine);
|
|
}
|
|
}
|
|
|
|
auto awipsLinePos = is.tellg();
|
|
util::getline(is, awipsLine);
|
|
|
|
if (is.eof())
|
|
{
|
|
logger_->trace("Reached end of file");
|
|
headerValid = false;
|
|
}
|
|
else
|
|
{
|
|
// Remove delimiters from the end of the line
|
|
while (sequenceLine.ends_with(' '))
|
|
{
|
|
sequenceLine.erase(sequenceLine.length() - 1);
|
|
}
|
|
}
|
|
|
|
// Transmission Header:
|
|
// [SOH]
|
|
// nnn
|
|
|
|
if (headerValid && !sequenceLine.empty())
|
|
{
|
|
p->sequenceNumber_ = sequenceLine;
|
|
}
|
|
|
|
// WMO Abbreviated Heading Line:
|
|
// T1T2A1A2ii CCCC YYGGgg (BBB)
|
|
|
|
if (headerValid)
|
|
{
|
|
std::string token;
|
|
std::istringstream wmoTokens(wmoLine);
|
|
std::vector<std::string> wmoTokenList;
|
|
|
|
while (wmoTokens >> token)
|
|
{
|
|
wmoTokenList.push_back(token);
|
|
}
|
|
|
|
if (wmoTokenList.size() < 3 || wmoTokenList.size() > 4)
|
|
{
|
|
logger_->warn("Invalid number of WMO tokens");
|
|
headerValid = false;
|
|
}
|
|
else if (wmoTokenList[0].size() < kWmoIdentifierLengthMin_ ||
|
|
wmoTokenList[0].size() > kWmoIdentifierLengthMax_)
|
|
{
|
|
logger_->warn("WMO identifier malformed");
|
|
headerValid = false;
|
|
}
|
|
else if (wmoTokenList[1].size() != kIcaoLength_)
|
|
{
|
|
logger_->warn("ICAO malformed");
|
|
headerValid = false;
|
|
}
|
|
else if (wmoTokenList[2].size() != kDateTimeLength_)
|
|
{
|
|
logger_->warn("Date/time malformed");
|
|
headerValid = false;
|
|
}
|
|
else if (wmoTokenList.size() == 4 && wmoTokenList[3].size() != 3)
|
|
{
|
|
// BBB indicator is optional
|
|
logger_->warn("BBB indicator malformed");
|
|
headerValid = false;
|
|
}
|
|
else
|
|
{
|
|
p->dataType_ = wmoTokenList[0].substr(0, 2);
|
|
p->geographicDesignator_ = wmoTokenList[0].substr(2, 2);
|
|
p->bulletinId_ = wmoTokenList[0].substr(4, wmoTokenList[0].size() - 4);
|
|
p->icao_ = wmoTokenList[1];
|
|
p->dateTime_ = wmoTokenList[2];
|
|
|
|
p->CalculateAbsoluteDateTime();
|
|
|
|
if (wmoTokenList.size() == 4)
|
|
{
|
|
p->bbbIndicator_ = wmoTokenList[3];
|
|
}
|
|
else
|
|
{
|
|
p->bbbIndicator_ = "";
|
|
}
|
|
}
|
|
}
|
|
|
|
// AWIPS Identifer Line:
|
|
// NNNxxx
|
|
|
|
if (headerValid)
|
|
{
|
|
if (awipsLine.size() != kAwipsIdentifierLineLength_)
|
|
{
|
|
// Older products may be missing an AWIPS Identifier Line
|
|
logger_->trace("AWIPS Identifier Line bad size");
|
|
|
|
is.seekg(awipsLinePos);
|
|
p->productCategory_ = "";
|
|
p->productDesignator_ = "";
|
|
}
|
|
else
|
|
{
|
|
p->productCategory_ = awipsLine.substr(0, 3);
|
|
p->productDesignator_ = awipsLine.substr(3, 3);
|
|
}
|
|
}
|
|
|
|
return headerValid;
|
|
}
|
|
|
|
void WmoHeader::SetDateHint(std::chrono::year_month dateHint)
|
|
{
|
|
p->dateHint_ = dateHint;
|
|
p->CalculateAbsoluteDateTime();
|
|
}
|
|
|
|
bool WmoHeaderImpl::ParseDateTime(unsigned int& dayOfMonth,
|
|
unsigned long& hour,
|
|
unsigned long& minute)
|
|
{
|
|
bool dateTimeValid = false;
|
|
|
|
try
|
|
{
|
|
// WMO date time is in the format DDHHMM
|
|
dayOfMonth =
|
|
static_cast<unsigned int>(std::stoul(dateTime_.substr(0, 2)));
|
|
hour = std::stoul(dateTime_.substr(2, 2));
|
|
minute = std::stoul(dateTime_.substr(4, 2));
|
|
dateTimeValid = true;
|
|
}
|
|
catch (const std::exception&)
|
|
{
|
|
logger_->warn("Malformed WMO date/time: {}", dateTime_);
|
|
}
|
|
|
|
return dateTimeValid;
|
|
}
|
|
|
|
void WmoHeaderImpl::CalculateAbsoluteDateTime()
|
|
{
|
|
bool dateTimeValid = false;
|
|
|
|
if (dateHint_.has_value() && !dateTime_.empty())
|
|
{
|
|
unsigned int dayOfMonth = 0;
|
|
unsigned long hour = 0;
|
|
unsigned long minute = 0;
|
|
|
|
dateTimeValid = ParseDateTime(dayOfMonth, hour, minute);
|
|
|
|
if (dateTimeValid)
|
|
{
|
|
using namespace std::chrono;
|
|
absoluteDateTime_ = sys_days {dateHint_->year() / dateHint_->month() /
|
|
day {dayOfMonth}} +
|
|
hours {hour} + minutes {minute};
|
|
}
|
|
}
|
|
|
|
if (!dateTimeValid)
|
|
{
|
|
absoluteDateTime_.reset();
|
|
}
|
|
}
|
|
|
|
} // namespace scwx::awips
|