mirror of
https://github.com/ciphervance/supercell-wx.git
synced 2025-10-30 22:20:04 +00:00
Adding UGC class for structured UGC
This commit is contained in:
parent
e98bca9d5d
commit
d3b3ac6be6
3 changed files with 287 additions and 0 deletions
248
wxdata/source/scwx/awips/ugc.cpp
Normal file
248
wxdata/source/scwx/awips/ugc.cpp
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
#include <scwx/awips/ugc.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
|
||||
#include <map>
|
||||
#include <regex>
|
||||
|
||||
#include <boost/assign.hpp>
|
||||
#include <boost/bimap.hpp>
|
||||
#include <boost/bimap/unordered_set_of.hpp>
|
||||
#include <boost/tokenizer.hpp>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace awips
|
||||
{
|
||||
|
||||
static const std::string logPrefix_ = "scwx::awips::ugc";
|
||||
static const auto logger_ = util::Logger::Create(logPrefix_);
|
||||
|
||||
enum class UgcFormat
|
||||
{
|
||||
Counties,
|
||||
Zones,
|
||||
Unknown
|
||||
};
|
||||
|
||||
typedef boost::bimap<boost::bimaps::unordered_set_of<UgcFormat>,
|
||||
boost::bimaps::unordered_set_of<char>>
|
||||
UgcFormatBimap;
|
||||
|
||||
static const UgcFormatBimap ugcFormatMap_ =
|
||||
boost::assign::list_of<UgcFormatBimap::relation> //
|
||||
(UgcFormat::Counties, 'C') //
|
||||
(UgcFormat::Zones, 'Z') //
|
||||
(UgcFormat::Unknown, '?');
|
||||
|
||||
class UgcImpl
|
||||
{
|
||||
public:
|
||||
explicit UgcImpl() :
|
||||
ugcString_ {},
|
||||
format_ {UgcFormat::Unknown},
|
||||
fipsIdMap_ {},
|
||||
productExpiration_ {},
|
||||
valid_ {false}
|
||||
{
|
||||
}
|
||||
|
||||
~UgcImpl() {}
|
||||
|
||||
std::vector<std::string> ugcString_;
|
||||
|
||||
UgcFormat format_;
|
||||
std::map<std::string, std::list<uint16_t>> fipsIdMap_;
|
||||
std::string productExpiration_;
|
||||
|
||||
bool valid_;
|
||||
};
|
||||
|
||||
Ugc::Ugc() : p(std::make_unique<UgcImpl>()) {}
|
||||
Ugc::~Ugc() = default;
|
||||
|
||||
Ugc::Ugc(Ugc&&) noexcept = default;
|
||||
Ugc& Ugc::operator=(Ugc&&) noexcept = default;
|
||||
|
||||
std::vector<std::string> Ugc::states() const
|
||||
{
|
||||
std::vector<std::string> states {};
|
||||
states.reserve(p->fipsIdMap_.size());
|
||||
|
||||
for (auto& entry : p->fipsIdMap_)
|
||||
{
|
||||
states.push_back(entry.first);
|
||||
}
|
||||
|
||||
return states;
|
||||
}
|
||||
|
||||
std::vector<std::string> Ugc::fips_ids() const
|
||||
{
|
||||
std::vector<std::string> fipsIds {};
|
||||
|
||||
for (auto& fipsIdList : p->fipsIdMap_)
|
||||
{
|
||||
for (auto& id : fipsIdList.second)
|
||||
{
|
||||
fipsIds.push_back(std::format("{}{}{:03}",
|
||||
fipsIdList.first,
|
||||
ugcFormatMap_.left.at(p->format_),
|
||||
id));
|
||||
}
|
||||
}
|
||||
|
||||
return fipsIds;
|
||||
}
|
||||
|
||||
std::string Ugc::product_expiration() const
|
||||
{
|
||||
return p->productExpiration_;
|
||||
}
|
||||
|
||||
bool Ugc::Parse(const std::vector<std::string>& ugcString)
|
||||
{
|
||||
bool dataValid = false;
|
||||
|
||||
// UGC takes the form SSFNNN-NNN>NNN-SSFNNN-DDHHMM- (NWSI 10-1702)
|
||||
static const std::regex reStart {"[A-Z]{2}[CZ]([0-9]{3}|ALL)"};
|
||||
static const std::regex reAnyFipsId {"([0-9]{3}|ALL)"};
|
||||
static const std::regex reSpecificFipsId {"(?!0{3})[0-9]{3}"};
|
||||
static const std::regex reProductExpiration {"[0-9]{6}"};
|
||||
|
||||
std::stringstream ugcStream;
|
||||
for (auto& line : ugcString)
|
||||
{
|
||||
ugcStream << line;
|
||||
}
|
||||
|
||||
// Concatenate UGC lines into a single string
|
||||
std::string ugc {};
|
||||
for (const std::string& line : ugcString)
|
||||
{
|
||||
ugc += line;
|
||||
}
|
||||
|
||||
boost::char_separator<char> sectionDelimiter("-");
|
||||
boost::char_separator<char> rangeDelimiter(">");
|
||||
boost::tokenizer tokens(ugc, sectionDelimiter);
|
||||
|
||||
std::string currentState {};
|
||||
|
||||
for (auto& token : tokens)
|
||||
{
|
||||
// Product Expiration is the final token
|
||||
if (std::regex_match(token, reProductExpiration))
|
||||
{
|
||||
p->productExpiration_ = token;
|
||||
dataValid = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Tokenize string again by ">" (note there will always be at least one
|
||||
// range token)
|
||||
boost::tokenizer rangeTokens(token, rangeDelimiter);
|
||||
const size_t numRangeTokens =
|
||||
std::distance(rangeTokens.begin(), rangeTokens.end());
|
||||
bool tokenValid = true;
|
||||
bool allFipsIds = false;
|
||||
auto tokenIt = rangeTokens.begin();
|
||||
std::string firstToken {tokenIt.current_token()};
|
||||
UgcFormat currentFormat {p->format_};
|
||||
std::string firstFipsId {};
|
||||
std::string secondFipsId {};
|
||||
|
||||
// Look for the start of the UGC string (may be multiple per UGC string
|
||||
// for multiple states, territories, or marine area)
|
||||
if (std::regex_match(firstToken, reStart))
|
||||
{
|
||||
currentState = firstToken.substr(0, 2);
|
||||
currentFormat = ugcFormatMap_.right.at(firstToken.at(2));
|
||||
firstFipsId = firstToken.substr(3, 3);
|
||||
|
||||
// The UGC string must contain counties or zones, but not both
|
||||
if (p->format_ != UgcFormat::Unknown && p->format_ != currentFormat)
|
||||
{
|
||||
tokenValid = false;
|
||||
}
|
||||
}
|
||||
// Look for additional FIPS IDs in the UGC string
|
||||
else if (!currentState.empty() &&
|
||||
std::regex_match(firstToken, reAnyFipsId))
|
||||
{
|
||||
firstFipsId = firstToken;
|
||||
}
|
||||
// If we see anything else, the UGC token is invalid
|
||||
else
|
||||
{
|
||||
tokenValid = false;
|
||||
}
|
||||
|
||||
// All counties or zones are specified by using "000" or "ALL"
|
||||
if (firstFipsId == "000" || firstFipsId == "ALL")
|
||||
{
|
||||
allFipsIds = true;
|
||||
}
|
||||
|
||||
// Parse the second token in a range (i.e., NNN>XXX)
|
||||
if (numRangeTokens == 2)
|
||||
{
|
||||
std::string secondToken {(++tokenIt).current_token()};
|
||||
|
||||
if (std::regex_match(secondToken, reSpecificFipsId))
|
||||
{
|
||||
secondFipsId = secondToken;
|
||||
}
|
||||
else
|
||||
{
|
||||
tokenValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check validity before using parsed data
|
||||
if (!tokenValid || numRangeTokens > 2 ||
|
||||
(allFipsIds && numRangeTokens > 1))
|
||||
{
|
||||
logger_->warn("Invalid token: {}", token);
|
||||
break;
|
||||
}
|
||||
|
||||
p->format_ = currentFormat;
|
||||
auto& fipsIds = p->fipsIdMap_[currentState];
|
||||
|
||||
if (allFipsIds)
|
||||
{
|
||||
fipsIds.push_back(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Insert the FIPS ID (NNN) from the token
|
||||
fipsIds.push_back(static_cast<uint16_t>(std::stoul(firstFipsId)));
|
||||
|
||||
if (numRangeTokens == 2)
|
||||
{
|
||||
// Insert the remainder of the FIPS IDs in the range given by the
|
||||
// token (NNN>XXX)
|
||||
const uint16_t first = fipsIds.back();
|
||||
const uint16_t last =
|
||||
static_cast<uint16_t>(std::stoul(secondFipsId));
|
||||
|
||||
for (uint16_t i = first + 1; i <= last; i++)
|
||||
{
|
||||
fipsIds.push_back(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p->valid_ = dataValid;
|
||||
|
||||
if (!dataValid)
|
||||
{
|
||||
p->fipsIdMap_.clear();
|
||||
}
|
||||
|
||||
return dataValid;
|
||||
}
|
||||
|
||||
} // namespace awips
|
||||
} // namespace scwx
|
||||
Loading…
Add table
Add a link
Reference in a new issue