From e49adafda9d385b47514806edcbd4098e2c05638 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 16 May 2025 23:50:42 -0500 Subject: [PATCH] Update RPG to Build 23.0 --- .../rpg/digital_raster_data_array_packet.hpp | 48 ++++ .../rpg/digital_raster_data_array_packet.cpp | 220 ++++++++++++++++++ .../wsr88d/rpg/level3_message_factory.cpp | 5 + .../source/scwx/wsr88d/rpg/packet_factory.cpp | 2 + .../wsr88d/rpg/product_description_block.cpp | 46 ++-- wxdata/wxdata.cmake | 2 + 6 files changed, 299 insertions(+), 24 deletions(-) create mode 100644 wxdata/include/scwx/wsr88d/rpg/digital_raster_data_array_packet.hpp create mode 100644 wxdata/source/scwx/wsr88d/rpg/digital_raster_data_array_packet.cpp diff --git a/wxdata/include/scwx/wsr88d/rpg/digital_raster_data_array_packet.hpp b/wxdata/include/scwx/wsr88d/rpg/digital_raster_data_array_packet.hpp new file mode 100644 index 00000000..76b0f2c3 --- /dev/null +++ b/wxdata/include/scwx/wsr88d/rpg/digital_raster_data_array_packet.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include + +#include +#include + +namespace scwx::wsr88d::rpg +{ + +class DigitalRasterDataArrayPacket : public Packet +{ +public: + explicit DigitalRasterDataArrayPacket(); + ~DigitalRasterDataArrayPacket(); + + DigitalRasterDataArrayPacket(const DigitalRasterDataArrayPacket&) = delete; + DigitalRasterDataArrayPacket& + operator=(const DigitalRasterDataArrayPacket&) = delete; + + DigitalRasterDataArrayPacket(DigitalRasterDataArrayPacket&&) noexcept; + DigitalRasterDataArrayPacket& + operator=(DigitalRasterDataArrayPacket&&) noexcept; + + [[nodiscard]] std::uint16_t packet_code() const override; + [[nodiscard]] std::uint16_t i_coordinate_start() const; + [[nodiscard]] std::uint16_t j_coordinate_start() const; + [[nodiscard]] std::uint16_t i_scale_factor() const; + [[nodiscard]] std::uint16_t j_scale_factor() const; + [[nodiscard]] std::uint16_t number_of_cells() const; + [[nodiscard]] std::uint16_t number_of_rows() const; + + [[nodiscard]] std::uint16_t number_of_bytes_in_row(std::uint16_t r) const; + [[nodiscard]] const std::vector& level(std::uint16_t r) const; + + [[nodiscard]] std::size_t data_size() const override; + + bool Parse(std::istream& is) override; + + static std::shared_ptr + Create(std::istream& is); + +private: + class Impl; + std::unique_ptr p; +}; + +} // namespace scwx::wsr88d::rpg diff --git a/wxdata/source/scwx/wsr88d/rpg/digital_raster_data_array_packet.cpp b/wxdata/source/scwx/wsr88d/rpg/digital_raster_data_array_packet.cpp new file mode 100644 index 00000000..ece33807 --- /dev/null +++ b/wxdata/source/scwx/wsr88d/rpg/digital_raster_data_array_packet.cpp @@ -0,0 +1,220 @@ +#include +#include + +#include +#include + +namespace scwx::wsr88d::rpg +{ + +static const std::string logPrefix_ = + "scwx::wsr88d::rpg::digital_raster_data_array_packet"; +static const auto logger_ = util::Logger::Create(logPrefix_); + +class DigitalRasterDataArrayPacket::Impl +{ +public: + struct Row + { + std::uint16_t numberOfBytes_ {0}; + std::vector level_ {}; + }; + + explicit Impl() = default; + ~Impl() = default; + + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; + + std::uint16_t packetCode_ {0}; + std::uint16_t iCoordinateStart_ {0}; + std::uint16_t jCoordinateStart_ {0}; + std::uint16_t iScaleFactor_ {0}; + std::uint16_t jScaleFactor_ {0}; + std::uint16_t numberOfCells_ {0}; + std::uint16_t numberOfRows_ {0}; + std::uint16_t numberOfBytesInRow_ {0}; + + // Repeat for each row + std::vector row_ {}; + + std::size_t dataSize_ {0}; +}; + +DigitalRasterDataArrayPacket::DigitalRasterDataArrayPacket() : + p(std::make_unique()) +{ +} +DigitalRasterDataArrayPacket::~DigitalRasterDataArrayPacket() = default; + +DigitalRasterDataArrayPacket::DigitalRasterDataArrayPacket( + DigitalRasterDataArrayPacket&&) noexcept = default; +DigitalRasterDataArrayPacket& DigitalRasterDataArrayPacket::operator=( + DigitalRasterDataArrayPacket&&) noexcept = default; + +std::uint16_t DigitalRasterDataArrayPacket::packet_code() const +{ + return p->packetCode_; +} + +std::uint16_t DigitalRasterDataArrayPacket::i_coordinate_start() const +{ + return p->iCoordinateStart_; +} + +std::uint16_t DigitalRasterDataArrayPacket::j_coordinate_start() const +{ + return p->jCoordinateStart_; +} + +std::uint16_t DigitalRasterDataArrayPacket::i_scale_factor() const +{ + return p->iScaleFactor_; +} + +std::uint16_t DigitalRasterDataArrayPacket::j_scale_factor() const +{ + return p->jScaleFactor_; +} + +std::uint16_t DigitalRasterDataArrayPacket::number_of_cells() const +{ + return p->numberOfCells_; +} + +std::uint16_t DigitalRasterDataArrayPacket::number_of_rows() const +{ + return p->numberOfRows_; +} + +std::uint16_t +DigitalRasterDataArrayPacket::number_of_bytes_in_row(std::uint16_t r) const +{ + return p->row_[r].numberOfBytes_; +} + +const std::vector& +DigitalRasterDataArrayPacket::level(std::uint16_t r) const +{ + return p->row_[r].level_; +} + +bool DigitalRasterDataArrayPacket::Parse(std::istream& is) +{ + bool blockValid = true; + std::size_t bytesRead = 0; + + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) + + is.read(reinterpret_cast(&p->packetCode_), 2); + is.read(reinterpret_cast(&p->iCoordinateStart_), 2); + is.read(reinterpret_cast(&p->jCoordinateStart_), 2); + is.read(reinterpret_cast(&p->iScaleFactor_), 2); + is.read(reinterpret_cast(&p->jScaleFactor_), 2); + is.read(reinterpret_cast(&p->numberOfCells_), 2); + is.read(reinterpret_cast(&p->numberOfRows_), 2); + bytesRead += 14; + + p->packetCode_ = ntohs(p->packetCode_); + p->iCoordinateStart_ = ntohs(p->iCoordinateStart_); + p->jCoordinateStart_ = ntohs(p->jCoordinateStart_); + p->iScaleFactor_ = ntohs(p->iScaleFactor_); + p->jScaleFactor_ = ntohs(p->jScaleFactor_); + p->numberOfCells_ = ntohs(p->numberOfCells_); + p->numberOfRows_ = ntohs(p->numberOfRows_); + + if (is.eof()) + { + logger_->debug("Reached end of file"); + blockValid = false; + } + else + { + if (p->packetCode_ != 33) + { + logger_->warn("Invalid packet code: {}", p->packetCode_); + blockValid = false; + } + if (p->numberOfCells_ < 1 || p->numberOfCells_ > 1840) + { + logger_->warn("Invalid number of cells: {}", p->numberOfCells_); + blockValid = false; + } + if (p->numberOfRows_ < 1 || p->numberOfRows_ > 464) + { + logger_->warn("Invalid number of rows: {}", p->numberOfRows_); + blockValid = false; + } + } + + if (blockValid) + { + p->row_.resize(p->numberOfRows_); + + for (std::uint16_t r = 0; r < p->numberOfRows_; r++) + { + auto& row = p->row_[r]; + + is.read(reinterpret_cast(&row.numberOfBytes_), 2); + bytesRead += 2; + + row.numberOfBytes_ = ntohs(row.numberOfBytes_); + + if (row.numberOfBytes_ < 1 || row.numberOfBytes_ > 1840) + { + logger_->warn( + "Invalid number of bytes: {} (Row {})", row.numberOfBytes_, r); + blockValid = false; + break; + } + else if (row.numberOfBytes_ < p->numberOfCells_) + { + logger_->warn("Number of bytes < number of cells: {} < {} (Row {})", + row.numberOfBytes_, + p->numberOfCells_, + r); + blockValid = false; + break; + } + + // Read raster bins + std::size_t dataSize = p->numberOfCells_; + row.level_.resize(dataSize); + is.read(reinterpret_cast(row.level_.data()), + static_cast(dataSize)); + + is.seekg(static_cast(row.numberOfBytes_ - dataSize), + std::ios_base::cur); + bytesRead += row.numberOfBytes_; + } + } + + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + + p->dataSize_ = bytesRead; + + if (!ValidateMessage(is, bytesRead)) + { + blockValid = false; + } + + return blockValid; +} + +std::shared_ptr +DigitalRasterDataArrayPacket::Create(std::istream& is) +{ + std::shared_ptr packet = + std::make_shared(); + + if (!packet->Parse(is)) + { + packet.reset(); + } + + return packet; +} + +} // namespace scwx::wsr88d::rpg diff --git a/wxdata/source/scwx/wsr88d/rpg/level3_message_factory.cpp b/wxdata/source/scwx/wsr88d/rpg/level3_message_factory.cpp index 25675470..77af1672 100644 --- a/wxdata/source/scwx/wsr88d/rpg/level3_message_factory.cpp +++ b/wxdata/source/scwx/wsr88d/rpg/level3_message_factory.cpp @@ -119,9 +119,14 @@ static const std::unordered_map // {182, GraphicProductMessage::Create}, {184, GraphicProductMessage::Create}, {186, GraphicProductMessage::Create}, + {189, GraphicProductMessage::Create}, + {190, GraphicProductMessage::Create}, + {191, GraphicProductMessage::Create}, + {192, GraphicProductMessage::Create}, {193, GraphicProductMessage::Create}, {195, GraphicProductMessage::Create}, {196, GraphicProductMessage::Create}, + {197, GraphicProductMessage::Create}, {202, GraphicProductMessage::Create}}; std::shared_ptr Level3MessageFactory::Create(std::istream& is) diff --git a/wxdata/source/scwx/wsr88d/rpg/packet_factory.cpp b/wxdata/source/scwx/wsr88d/rpg/packet_factory.cpp index e3cd034b..eb85ebb4 100644 --- a/wxdata/source/scwx/wsr88d/rpg/packet_factory.cpp +++ b/wxdata/source/scwx/wsr88d/rpg/packet_factory.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -69,6 +70,7 @@ static const std::unordered_map create_ { {26, PointGraphicSymbolPacket::Create}, {28, GenericDataPacket::Create}, {29, GenericDataPacket::Create}, + {33, DigitalRasterDataArrayPacket::Create}, {0x0802, SetColorLevelPacket::Create}, {0x0E03, LinkedContourVectorPacket::Create}, {0x3501, UnlinkedContourVectorPacket::Create}, diff --git a/wxdata/source/scwx/wsr88d/rpg/product_description_block.cpp b/wxdata/source/scwx/wsr88d/rpg/product_description_block.cpp index 10fdbe72..d9417d27 100644 --- a/wxdata/source/scwx/wsr88d/rpg/product_description_block.cpp +++ b/wxdata/source/scwx/wsr88d/rpg/product_description_block.cpp @@ -21,28 +21,13 @@ static const std::string logPrefix_ = static const auto logger_ = util::Logger::Create(logPrefix_); 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, 180, 182, 186, 193, 195, 202}; + 32, 94, 99, 113, 134, 135, 138, 149, 152, 153, 154, 155, 159, + 161, 163, 165, 167, 168, 170, 172, 173, 174, 175, 176, 177, 178, + 179, 180, 182, 186, 189, 190, 191, 192, 193, 195, 197, 202}; -static const std::set uncodedDataLevelProducts_ = {32, - 34, - 81, - 93, - 94, - 99, - 134, - 135, - 138, - 153, - 154, - 155, - 159, - 161, - 163, - 177, - 193, - 195}; +static const std::set uncodedDataLevelProducts_ = { + 32, 34, 81, 93, 94, 99, 134, 135, 138, 153, 154, 155, + 159, 161, 163, 177, 189, 190, 191, 192, 193, 195, 197}; static const std::unordered_map rangeMap_ { {19, 230}, {20, 460}, {27, 230}, {30, 230}, {31, 230}, {32, 230}, @@ -57,7 +42,8 @@ static const std::unordered_map rangeMap_ { {163, 300}, {165, 300}, {166, 230}, {167, 300}, {168, 300}, {169, 230}, {170, 230}, {171, 230}, {172, 230}, {173, 230}, {174, 230}, {175, 230}, {176, 230}, {177, 230}, {178, 300}, {179, 300}, {180, 89}, {181, 89}, - {182, 89}, {184, 89}, {186, 417}, {193, 460}, {195, 460}, {196, 50}}; + {182, 89}, {184, 89}, {186, 417}, {193, 460}, {195, 460}, {196, 50}, + {197, 230}}; static const std::unordered_map xResolutionMap_ { {19, 1000}, {20, 2000}, {27, 1000}, {30, 1000}, {31, 2000}, {32, 1000}, @@ -71,7 +57,7 @@ static const std::unordered_map xResolutionMap_ { {166, 250}, {167, 250}, {168, 250}, {169, 2000}, {170, 250}, {171, 2000}, {172, 250}, {173, 250}, {174, 250}, {175, 250}, {176, 250}, {177, 250}, {178, 1000}, {179, 1000}, {180, 150}, {181, 150}, {182, 150}, {184, 150}, - {186, 300}, {193, 250}, {195, 1000}}; + {186, 300}, {193, 250}, {195, 1000}, {197, 250}}; static const std::unordered_map yResolutionMap_ {{37, 1000}, {38, 4000}, @@ -86,7 +72,11 @@ static const std::unordered_map yResolutionMap_ {{37, 1000}, {90, 4000}, {97, 1000}, {98, 4000}, - {166, 250}}; + {166, 250}, + {189, 20}, + {190, 20}, + {191, 20}, + {192, 20}}; // GR uses different internal units than defined units in level 3 products static const std::unordered_map grScale_ { @@ -580,6 +570,10 @@ uint16_t ProductDescriptionBlock::number_of_levels() const break; case 134: + case 189: + case 190: + case 191: + case 192: numberOfLevels = 256; break; @@ -864,6 +858,10 @@ ProductDescriptionBlock::data_level_code(std::uint8_t level) const case 163: case 167: case 168: + case 189: + case 190: + case 191: + case 192: case 195: switch (level) { diff --git a/wxdata/wxdata.cmake b/wxdata/wxdata.cmake index 6a02a265..a30398a0 100644 --- a/wxdata/wxdata.cmake +++ b/wxdata/wxdata.cmake @@ -147,6 +147,7 @@ set(HDR_WSR88D_RPG include/scwx/wsr88d/rpg/ccb_header.hpp include/scwx/wsr88d/rpg/cell_trend_volume_scan_times.hpp include/scwx/wsr88d/rpg/digital_precipitation_data_array_packet.hpp include/scwx/wsr88d/rpg/digital_radial_data_array_packet.hpp + include/scwx/wsr88d/rpg/digital_raster_data_array_packet.hpp include/scwx/wsr88d/rpg/general_status_message.hpp include/scwx/wsr88d/rpg/generic_data_packet.hpp include/scwx/wsr88d/rpg/generic_radial_data_packet.hpp @@ -188,6 +189,7 @@ set(SRC_WSR88D_RPG source/scwx/wsr88d/rpg/ccb_header.cpp source/scwx/wsr88d/rpg/cell_trend_volume_scan_times.cpp source/scwx/wsr88d/rpg/digital_precipitation_data_array_packet.cpp source/scwx/wsr88d/rpg/digital_radial_data_array_packet.cpp + source/scwx/wsr88d/rpg/digital_raster_data_array_packet.cpp source/scwx/wsr88d/rpg/general_status_message.cpp source/scwx/wsr88d/rpg/generic_data_packet.cpp source/scwx/wsr88d/rpg/generic_radial_data_packet.cpp