diff --git a/wxdata/include/scwx/wsr88d/rpg/level3_message_header.hpp b/wxdata/include/scwx/wsr88d/rpg/level3_message_header.hpp new file mode 100644 index 00000000..0cc870a7 --- /dev/null +++ b/wxdata/include/scwx/wsr88d/rpg/level3_message_header.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include +#include + +namespace scwx +{ +namespace wsr88d +{ +namespace rpg +{ + +class Level3MessageHeaderImpl; + +class Level3MessageHeader +{ +public: + explicit Level3MessageHeader(); + ~Level3MessageHeader(); + + Level3MessageHeader(const Level3MessageHeader&) = delete; + Level3MessageHeader& operator=(const Level3MessageHeader&) = delete; + + Level3MessageHeader(Level3MessageHeader&&) noexcept; + Level3MessageHeader& operator=(Level3MessageHeader&&) noexcept; + + int16_t message_code() const; + uint16_t date_of_message() const; + uint32_t time_of_message() const; + uint32_t length_of_message() const; + uint16_t source_id() const; + uint16_t destination_id() const; + uint16_t number_blocks() const; + + bool Parse(std::istream& is); + + static const size_t SIZE = 18u; + +private: + std::unique_ptr p; +}; + +} // namespace rpg +} // namespace wsr88d +} // namespace scwx diff --git a/wxdata/include/scwx/wsr88d/rpg/product_description_block.hpp b/wxdata/include/scwx/wsr88d/rpg/product_description_block.hpp new file mode 100644 index 00000000..2b9c57d8 --- /dev/null +++ b/wxdata/include/scwx/wsr88d/rpg/product_description_block.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include + +#include +#include + +namespace scwx +{ +namespace wsr88d +{ +namespace rpg +{ + +class ProductDescriptionBlockImpl; + +class ProductDescriptionBlock : public Message +{ +public: + explicit ProductDescriptionBlock(); + ~ProductDescriptionBlock(); + + ProductDescriptionBlock(const ProductDescriptionBlock&) = delete; + ProductDescriptionBlock& operator=(const ProductDescriptionBlock&) = delete; + + ProductDescriptionBlock(ProductDescriptionBlock&&) noexcept; + ProductDescriptionBlock& operator=(ProductDescriptionBlock&&) noexcept; + + int16_t block_divider() const; + int32_t latitude_of_radar() const; + int32_t longitude_of_radar() const; + int16_t height_of_radar() const; + int16_t product_code() const; + uint16_t operational_mode() const; + uint16_t volume_coverage_pattern() const; + int16_t sequence_number() const; + uint16_t volume_scan_number() const; + uint16_t volume_scan_date() const; + uint32_t volume_scan_start_time() const; + uint16_t generation_date_of_product() const; + uint32_t generation_time_of_product() const; + uint16_t elevation_number() const; + uint8_t version() const; + uint8_t spot_blank() const; + uint32_t offset_to_symbology() const; + uint32_t offset_to_graphic() const; + uint32_t offset_to_tabular() const; + + bool IsCompressionEnabled() const; + + bool Parse(std::istream& is); + + static const size_t SIZE = 102u; + +private: + std::unique_ptr p; +}; + +} // namespace rpg +} // namespace wsr88d +} // namespace scwx diff --git a/wxdata/include/scwx/wsr88d/rpg/wmo_header.hpp b/wxdata/include/scwx/wsr88d/rpg/wmo_header.hpp new file mode 100644 index 00000000..33ba428b --- /dev/null +++ b/wxdata/include/scwx/wsr88d/rpg/wmo_header.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include +#include + +namespace scwx +{ +namespace wsr88d +{ +namespace rpg +{ + +class WmoHeaderImpl; + +/** + * @brief The WMO Header is defined in WMO Manual No. 386, with additional codes + * defined in WMO Codes Manual 306. The NWS summarizes the relevant + * information. + * + * + * + * + * + * + */ +class WmoHeader +{ +public: + explicit WmoHeader(); + ~WmoHeader(); + + WmoHeader(const WmoHeader&) = delete; + WmoHeader& operator=(const WmoHeader&) = delete; + + WmoHeader(WmoHeader&&) noexcept; + WmoHeader& operator=(WmoHeader&&) noexcept; + + const std::string& data_type() const; + const std::string& geographic_designator() const; + const std::string& bulletin_id() const; + const std::string& icao() const; + const std::string& date_time() const; + const std::string& bbb_indicator() const; + const std::string& product_category() const; + const std::string& product_designator() const; + + bool Parse(std::istream& is); + +private: + std::unique_ptr p; +}; + +} // namespace rpg +} // namespace wsr88d +} // namespace scwx diff --git a/wxdata/source/scwx/wsr88d/rpg/level3_message_header.cpp b/wxdata/source/scwx/wsr88d/rpg/level3_message_header.cpp new file mode 100644 index 00000000..0c0c0f74 --- /dev/null +++ b/wxdata/source/scwx/wsr88d/rpg/level3_message_header.cpp @@ -0,0 +1,176 @@ +#include + +#include +#include + +#include + +#ifdef WIN32 +# include +#else +# include +#endif + +namespace scwx +{ +namespace wsr88d +{ +namespace rpg +{ + +static const std::string logPrefix_ = + "[scwx::wsr88d::rpg::level3_message_header] "; + +class Level3MessageHeaderImpl +{ +public: + explicit Level3MessageHeaderImpl() : + messageCode_ {}, + dateOfMessage_ {}, + timeOfMessage_ {}, + lengthOfMessage_ {}, + sourceId_ {}, + destinationId_ {}, + numberBlocks_ {} {}; + ~Level3MessageHeaderImpl() = default; + + int16_t messageCode_; + uint16_t dateOfMessage_; + uint32_t timeOfMessage_; + uint32_t lengthOfMessage_; + uint16_t sourceId_; + uint16_t destinationId_; + uint16_t numberBlocks_; +}; + +Level3MessageHeader::Level3MessageHeader() : + p(std::make_unique()) +{ +} +Level3MessageHeader::~Level3MessageHeader() = default; + +Level3MessageHeader::Level3MessageHeader(Level3MessageHeader&&) noexcept = + default; +Level3MessageHeader& +Level3MessageHeader::operator=(Level3MessageHeader&&) noexcept = default; + +int16_t Level3MessageHeader::message_code() const +{ + return p->messageCode_; +} + +uint16_t Level3MessageHeader::date_of_message() const +{ + return p->dateOfMessage_; +} + +uint32_t Level3MessageHeader::time_of_message() const +{ + return p->timeOfMessage_; +} + +uint32_t Level3MessageHeader::length_of_message() const +{ + return p->lengthOfMessage_; +} + +uint16_t Level3MessageHeader::source_id() const +{ + return p->sourceId_; +} + +uint16_t Level3MessageHeader::destination_id() const +{ + return p->destinationId_; +} + +uint16_t Level3MessageHeader::number_blocks() const +{ + return p->numberBlocks_; +} + +bool Level3MessageHeader::Parse(std::istream& is) +{ + bool headerValid = true; + + is.read(reinterpret_cast(&p->messageCode_), 2); + is.read(reinterpret_cast(&p->dateOfMessage_), 2); + is.read(reinterpret_cast(&p->timeOfMessage_), 4); + is.read(reinterpret_cast(&p->lengthOfMessage_), 4); + is.read(reinterpret_cast(&p->sourceId_), 2); + is.read(reinterpret_cast(&p->destinationId_), 2); + is.read(reinterpret_cast(&p->numberBlocks_), 2); + + p->messageCode_ = ntohs(p->messageCode_); + p->dateOfMessage_ = ntohs(p->dateOfMessage_); + p->timeOfMessage_ = ntohl(p->timeOfMessage_); + p->lengthOfMessage_ = ntohl(p->lengthOfMessage_); + p->sourceId_ = ntohs(p->sourceId_); + p->destinationId_ = ntohs(p->destinationId_); + p->numberBlocks_ = ntohs(p->numberBlocks_); + + if (is.eof()) + { + BOOST_LOG_TRIVIAL(debug) << logPrefix_ << "Reached end of file"; + headerValid = false; + } + else + { + if (p->messageCode_ < -131 || + (p->messageCode_ > -16 && p->messageCode_ < 0) || + p->messageCode_ > 211) + { + BOOST_LOG_TRIVIAL(warning) + << logPrefix_ << "Invalid message code: " << p->messageCode_; + headerValid = false; + } + if (p->dateOfMessage_ < 1u || p->dateOfMessage_ > 32'767u) + { + BOOST_LOG_TRIVIAL(warning) + << logPrefix_ << "Invalid date: " << p->dateOfMessage_; + headerValid = false; + } + if (p->timeOfMessage_ > 86'399u) + { + BOOST_LOG_TRIVIAL(warning) + << logPrefix_ << "Invalid time: " << p->timeOfMessage_; + headerValid = false; + } + if (p->lengthOfMessage_ < 18 || p->lengthOfMessage_ > 1'329'270u) + { + BOOST_LOG_TRIVIAL(warning) + << logPrefix_ << "Invalid length: " << p->lengthOfMessage_; + headerValid = false; + } + if (p->sourceId_ > 999u) + { + BOOST_LOG_TRIVIAL(warning) + << logPrefix_ << "Invalid source ID: " << p->sourceId_; + headerValid = false; + } + if (p->destinationId_ > 999u) + { + BOOST_LOG_TRIVIAL(warning) + << logPrefix_ << "Invalid destination ID: " << p->destinationId_; + headerValid = false; + } + if (p->numberBlocks_ < 1u || p->numberBlocks_ > 51u) + { + BOOST_LOG_TRIVIAL(warning) + << logPrefix_ << "Invalid block count: " << p->numberBlocks_; + headerValid = false; + } + } + + if (headerValid) + { + BOOST_LOG_TRIVIAL(trace) + << logPrefix_ << "Message code: " << p->messageCode_; + } + + return headerValid; +} + +} // namespace rpg +} // namespace wsr88d +} // namespace scwx diff --git a/wxdata/source/scwx/wsr88d/rpg/product_description_block.cpp b/wxdata/source/scwx/wsr88d/rpg/product_description_block.cpp new file mode 100644 index 00000000..7c1e8e6a --- /dev/null +++ b/wxdata/source/scwx/wsr88d/rpg/product_description_block.cpp @@ -0,0 +1,306 @@ +#include + +#include +#include +#include +#include + +#include + +#ifdef WIN32 +# include +#else +# include +#endif + +namespace scwx +{ +namespace wsr88d +{ +namespace rpg +{ + +static const std::string logPrefix_ = + "[scwx::wsr88d::rpg::product_description_block] "; + +static const std::set compressedProducts_ = { + 32, 94, 99, 134, 135, 138, 149, 152, 153, 154, 155, 159, 161, 163, 165, + 167, 168, 170, 172, 173, 174, 175, 176, 177, 178, 179, 193, 195, 202}; + +class ProductDescriptionBlockImpl +{ +public: + explicit ProductDescriptionBlockImpl() : + blockDivider_ {}, + latitudeOfRadar_ {}, + longitudeOfRadar_ {}, + heightOfRadar_ {}, + productCode_ {}, + operationalMode_ {}, + volumeCoveragePattern_ {}, + sequenceNumber_ {}, + volumeScanNumber_ {}, + volumeScanDate_ {}, + volumeScanStartTime_ {}, + generationDateOfProduct_ {}, + generationTimeOfProduct_ {}, + elevationNumber_ {}, + halfword31_ {}, + halfword32_ {}, + halfword33_ {}, + halfword34_ {}, + halfword35_ {}, + version_ {}, + spotBlank_ {}, + offsetToSymbology_ {}, + offsetToGraphic_ {}, + offsetToTabular_ {}, + parameters_ {} {}; + ~ProductDescriptionBlockImpl() = default; + + int16_t blockDivider_; + int32_t latitudeOfRadar_; + int32_t longitudeOfRadar_; + int16_t heightOfRadar_; + int16_t productCode_; + uint16_t operationalMode_; + uint16_t volumeCoveragePattern_; + int16_t sequenceNumber_; + uint16_t volumeScanNumber_; + uint16_t volumeScanDate_; + uint32_t volumeScanStartTime_; + uint16_t generationDateOfProduct_; + uint32_t generationTimeOfProduct_; + // 27-28: Product dependent parameters 1 and 2 (Table V) + uint16_t elevationNumber_; + // 30: Product dependent parameter 3 (Table V) + // 31-46: Product dependent (Note 1) + uint16_t halfword31_; + uint16_t halfword32_; + uint16_t halfword33_; + uint16_t halfword34_; + uint16_t halfword35_; + // Halfwords 36-46 are unused + // 47-53: Product dependent parameters 4-10 (Table V, Note 3) + uint8_t version_; + uint8_t spotBlank_; + uint32_t offsetToSymbology_; + uint32_t offsetToGraphic_; + uint32_t offsetToTabular_; + + std::array parameters_; +}; + +ProductDescriptionBlock::ProductDescriptionBlock() : + p(std::make_unique()) +{ +} +ProductDescriptionBlock::~ProductDescriptionBlock() = default; + +ProductDescriptionBlock::ProductDescriptionBlock( + ProductDescriptionBlock&&) noexcept = default; +ProductDescriptionBlock& ProductDescriptionBlock::operator=( + ProductDescriptionBlock&&) noexcept = default; + +int16_t ProductDescriptionBlock::block_divider() const +{ + return p->blockDivider_; +} + +int32_t ProductDescriptionBlock::latitude_of_radar() const +{ + return p->latitudeOfRadar_; +} + +int32_t ProductDescriptionBlock::longitude_of_radar() const +{ + return p->longitudeOfRadar_; +} + +int16_t ProductDescriptionBlock::height_of_radar() const +{ + return p->heightOfRadar_; +} + +int16_t ProductDescriptionBlock::product_code() const +{ + return p->productCode_; +} + +uint16_t ProductDescriptionBlock::operational_mode() const +{ + return p->operationalMode_; +} + +uint16_t ProductDescriptionBlock::volume_coverage_pattern() const +{ + return p->volumeCoveragePattern_; +} + +int16_t ProductDescriptionBlock::sequence_number() const +{ + return p->sequenceNumber_; +} + +uint16_t ProductDescriptionBlock::volume_scan_number() const +{ + return p->volumeScanNumber_; +} + +uint16_t ProductDescriptionBlock::volume_scan_date() const +{ + return p->volumeScanDate_; +} + +uint32_t ProductDescriptionBlock::volume_scan_start_time() const +{ + return p->volumeScanStartTime_; +} + +uint16_t ProductDescriptionBlock::generation_date_of_product() const +{ + return p->generationDateOfProduct_; +} + +uint32_t ProductDescriptionBlock::generation_time_of_product() const +{ + return p->generationTimeOfProduct_; +} + +uint16_t ProductDescriptionBlock::elevation_number() const +{ + return p->elevationNumber_; +} + +uint8_t ProductDescriptionBlock::version() const +{ + return p->version_; +} + +uint8_t ProductDescriptionBlock::spot_blank() const +{ + return p->spotBlank_; +} + +uint32_t ProductDescriptionBlock::offset_to_symbology() const +{ + return p->offsetToSymbology_; +} + +uint32_t ProductDescriptionBlock::offset_to_graphic() const +{ + return p->offsetToGraphic_; +} + +uint32_t ProductDescriptionBlock::offset_to_tabular() const +{ + return p->offsetToTabular_; +} + +bool ProductDescriptionBlock::IsCompressionEnabled() const +{ + bool isCompressed = false; + + if (compressedProducts_.contains(p->productCode_)) + { + isCompressed = (p->parameters_[7] == 1u); + } + + return isCompressed; +} + +bool ProductDescriptionBlock::Parse(std::istream& is) +{ + bool blockValid = true; + + is.read(reinterpret_cast(&p->blockDivider_), 2); // 10 + is.read(reinterpret_cast(&p->latitudeOfRadar_), 4); // 11-12 + is.read(reinterpret_cast(&p->longitudeOfRadar_), 4); // 13-14 + is.read(reinterpret_cast(&p->heightOfRadar_), 2); // 15 + is.read(reinterpret_cast(&p->productCode_), 2); // 16 + is.read(reinterpret_cast(&p->operationalMode_), 2); // 17 + is.read(reinterpret_cast(&p->volumeCoveragePattern_), 2); // 18 + is.read(reinterpret_cast(&p->sequenceNumber_), 2); // 19 + is.read(reinterpret_cast(&p->volumeScanNumber_), 2); // 20 + is.read(reinterpret_cast(&p->volumeScanDate_), 2); // 21 + is.read(reinterpret_cast(&p->volumeScanStartTime_), 4); // 22-23 + is.read(reinterpret_cast(&p->generationDateOfProduct_), 2); // 24 + is.read(reinterpret_cast(&p->generationTimeOfProduct_), 4); // 25-26 + is.read(reinterpret_cast(&p->parameters_[0]), 2 * 2); // 27-28 + is.read(reinterpret_cast(&p->elevationNumber_), 2); // 29 + is.read(reinterpret_cast(&p->parameters_[2]), 2); // 30 + is.read(reinterpret_cast(&p->halfword31_), 2); // 31 + is.read(reinterpret_cast(&p->halfword32_), 2); // 32 + is.read(reinterpret_cast(&p->halfword33_), 2); // 33 + is.read(reinterpret_cast(&p->halfword34_), 2); // 34 + is.read(reinterpret_cast(&p->halfword35_), 2); // 35 + + is.seekg(11 * 2, std::ios_base::cur); // 36-46 + + is.read(reinterpret_cast(&p->parameters_[3]), 7 * 2); // 47-53 + is.read(reinterpret_cast(&p->version_), 1); // 54 + is.read(reinterpret_cast(&p->spotBlank_), 1); // 54 + is.read(reinterpret_cast(&p->offsetToSymbology_), 4); // 55-56 + is.read(reinterpret_cast(&p->offsetToGraphic_), 4); // 57-58 + is.read(reinterpret_cast(&p->offsetToTabular_), 4); // 59-60 + + p->blockDivider_ = ntohs(p->blockDivider_); + p->latitudeOfRadar_ = ntohl(p->latitudeOfRadar_); + p->longitudeOfRadar_ = ntohl(p->longitudeOfRadar_); + p->heightOfRadar_ = ntohs(p->heightOfRadar_); + p->productCode_ = ntohs(p->productCode_); + p->operationalMode_ = ntohs(p->operationalMode_); + p->volumeCoveragePattern_ = ntohs(p->volumeCoveragePattern_); + p->sequenceNumber_ = ntohs(p->sequenceNumber_); + p->volumeScanNumber_ = ntohs(p->volumeScanNumber_); + p->volumeScanDate_ = ntohs(p->volumeScanDate_); + p->volumeScanStartTime_ = ntohl(p->volumeScanStartTime_); + p->generationDateOfProduct_ = ntohs(p->generationDateOfProduct_); + p->generationTimeOfProduct_ = ntohl(p->generationTimeOfProduct_); + p->elevationNumber_ = ntohs(p->elevationNumber_); + p->halfword31_ = ntohs(p->halfword31_); + p->halfword32_ = ntohs(p->halfword32_); + p->halfword33_ = ntohs(p->halfword33_); + p->halfword34_ = ntohs(p->halfword34_); + p->halfword35_ = ntohs(p->halfword35_); + p->offsetToSymbology_ = ntohl(p->offsetToSymbology_); + p->offsetToGraphic_ = ntohl(p->offsetToGraphic_); + p->offsetToTabular_ = ntohl(p->offsetToTabular_); + + SwapArray(p->parameters_); + + if (is.eof()) + { + BOOST_LOG_TRIVIAL(debug) << logPrefix_ << "Reached end of file"; + blockValid = false; + } + else + { + if (p->blockDivider_ != -1) + { + BOOST_LOG_TRIVIAL(warning) + << logPrefix_ << "Invalid block divider: " << p->blockDivider_; + blockValid = false; + } + if (p->productCode_ < -299 || + (p->productCode_ > -16 && p->productCode_ < 16) || + p->productCode_ > 299) + { + BOOST_LOG_TRIVIAL(warning) + << logPrefix_ << "Invalid product code: " << p->productCode_; + blockValid = false; + } + } + + if (blockValid) + { + BOOST_LOG_TRIVIAL(trace) + << logPrefix_ << "Product code: " << p->productCode_; + } + + return blockValid; +} + +} // namespace rpg +} // namespace wsr88d +} // namespace scwx diff --git a/wxdata/source/scwx/wsr88d/rpg/wmo_header.cpp b/wxdata/source/scwx/wsr88d/rpg/wmo_header.cpp new file mode 100644 index 00000000..c4c86418 --- /dev/null +++ b/wxdata/source/scwx/wsr88d/rpg/wmo_header.cpp @@ -0,0 +1,212 @@ +#include +#include + +#include +#include +#include + +#include + +#ifdef WIN32 +# include +#else +# include +#endif + +namespace scwx +{ +namespace wsr88d +{ +namespace rpg +{ + +static const std::string logPrefix_ = "[scwx::wsr88d::rpg::wmo_header] "; + +class WmoHeaderImpl +{ +public: + explicit WmoHeaderImpl() : + dataType_ {}, + geographicDesignator_ {}, + bulletinId_ {}, + icao_ {}, + dateTime_ {}, + bbbIndicator_ {}, + productCategory_ {}, + productDesignator_ {} {}; + ~WmoHeaderImpl() = default; + + std::string dataType_; + std::string geographicDesignator_; + std::string bulletinId_; + std::string icao_; + std::string dateTime_; + std::string bbbIndicator_; + std::string productCategory_; + std::string productDesignator_; +}; + +WmoHeader::WmoHeader() : p(std::make_unique()) {} +WmoHeader::~WmoHeader() = default; + +WmoHeader::WmoHeader(WmoHeader&&) noexcept = default; +WmoHeader& WmoHeader::operator=(WmoHeader&&) noexcept = default; + +const std::string& WmoHeader::data_type() const +{ + return p->dataType_; +} + +const std::string& WmoHeader::geographic_designator() const +{ + return p->geographicDesignator_; +} + +const std::string& WmoHeader::bulletin_id() const +{ + return p->bulletinId_; +} + +const std::string& WmoHeader::icao() const +{ + return p->icao_; +} + +const std::string& WmoHeader::date_time() const +{ + return p->dateTime_; +} + +const std::string& WmoHeader::bbb_indicator() const +{ + return p->bbbIndicator_; +} + +const std::string& WmoHeader::product_category() const +{ + return p->productCategory_; +} + +const std::string& WmoHeader::product_designator() const +{ + return p->productDesignator_; +} + +bool WmoHeader::Parse(std::istream& is) +{ + bool headerValid = true; + + std::string wmoLine; + std::string awipsLine; + + std::getline(is, wmoLine); + std::getline(is, awipsLine); + + if (is.eof()) + { + BOOST_LOG_TRIVIAL(debug) << logPrefix_ << "Reached end of file"; + headerValid = false; + } + else if (!wmoLine.ends_with("\r\r")) + { + BOOST_LOG_TRIVIAL(debug) + << logPrefix_ << "WMO Abbreviated Heading Line is malformed"; + headerValid = false; + } + else if (!awipsLine.ends_with("\r\r")) + { + BOOST_LOG_TRIVIAL(debug) + << logPrefix_ << "AWIPS Identifier Line is malformed"; + headerValid = false; + } + else + { + // Remove delimiters from the end of the line + wmoLine.erase(wmoLine.end() - 2); + awipsLine.erase(awipsLine.end() - 2); + } + + // WMO Abbreviated Heading Line: + // T1T2A1A2ii CCCC YYGGgg (BBB) + + if (headerValid) + { + std::string token; + std::istringstream wmoTokens(wmoLine); + std::vector wmoTokenList; + + while (wmoTokens >> token) + { + wmoTokenList.push_back(std::move(token)); + } + + if (wmoTokenList.size() < 3 || wmoTokenList.size() > 4) + { + BOOST_LOG_TRIVIAL(debug) + << logPrefix_ << "Invalid number of WMO tokens"; + headerValid = false; + } + else if (wmoTokenList[0].size() != 6) + { + BOOST_LOG_TRIVIAL(debug) << logPrefix_ << "WMO identifier malformed"; + headerValid = false; + } + else if (wmoTokenList[1].size() != 4) + { + BOOST_LOG_TRIVIAL(debug) << logPrefix_ << "ICAO malformed"; + headerValid = false; + } + else if (wmoTokenList[2].size() != 6) + { + BOOST_LOG_TRIVIAL(debug) << logPrefix_ << "Date/time malformed"; + headerValid = false; + } + else if (wmoTokenList.size() == 4 && wmoTokenList[3].size() != 3) + { + // BBB indicator is optional + BOOST_LOG_TRIVIAL(debug) << logPrefix_ << "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, 2); + p->icao_ = wmoTokenList[1]; + p->dateTime_ = wmoTokenList[2]; + + if (wmoTokenList.size() == 4) + { + p->bbbIndicator_ = wmoTokenList[3]; + } + else + { + p->bbbIndicator_ = ""; + } + } + } + + // AWIPS Identifer Line: + // NNNxxx + + if (headerValid) + { + if (awipsLine.size() != 6) + { + BOOST_LOG_TRIVIAL(debug) + << logPrefix_ << "AWIPS Identifier Line bad size"; + headerValid = false; + } + else + { + p->productCategory_ = awipsLine.substr(0, 3); + p->productDesignator_ = awipsLine.substr(3, 3); + } + } + + return headerValid; +} + +} // namespace rpg +} // namespace wsr88d +} // namespace scwx