supercell-wx/wxdata/source/scwx/awips/wmo_header.cpp
2025-05-04 23:10:21 -05:00

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