From 2f06076bb541acbaca7d03374773d206f2e9c9a1 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 4 Jan 2024 01:17:14 -0600 Subject: [PATCH] Add data level codes, refactor data value determination to product description block --- .../scwx/qt/view/level3_product_view.cpp | 84 ++--- .../wsr88d/rpg/product_description_block.hpp | 5 + wxdata/include/scwx/wsr88d/rpg/rpg_types.hpp | 58 ++++ .../wsr88d/rpg/product_description_block.cpp | 310 ++++++++++++++++++ wxdata/wxdata.cmake | 1 + 5 files changed, 407 insertions(+), 51 deletions(-) create mode 100644 wxdata/include/scwx/wsr88d/rpg/rpg_types.hpp diff --git a/scwx-qt/source/scwx/qt/view/level3_product_view.cpp b/scwx-qt/source/scwx/qt/view/level3_product_view.cpp index 06f291e0..7a5204c8 100644 --- a/scwx-qt/source/scwx/qt/view/level3_product_view.cpp +++ b/scwx-qt/source/scwx/qt/view/level3_product_view.cpp @@ -7,6 +7,8 @@ #include #include +#include + #include #include #include @@ -240,15 +242,22 @@ void Level3ProductView::UpdateColorTable() return; } - int16_t productCode = descriptionBlock->product_code(); - float offset = descriptionBlock->offset(); - float scale = descriptionBlock->scale(); - uint16_t threshold = descriptionBlock->threshold(); + std::int16_t productCode = descriptionBlock->product_code(); + float offset = descriptionBlock->offset(); + float scale = descriptionBlock->scale(); + std::uint8_t threshold = static_cast( + std::clamp(descriptionBlock->threshold(), + std::numeric_limits::min(), + std::numeric_limits::max())); - // If the threshold is 2, the range min should be set to 1 for range folding - uint16_t rangeMin = std::min(1, threshold); - uint16_t numberOfLevels = descriptionBlock->number_of_levels(); - uint16_t rangeMax = (numberOfLevels > 0) ? numberOfLevels - 1 : 0; + // If the threshold is 2, the range min should be set to 1 for range + // folding + std::uint8_t rangeMin = std::min(1, threshold); + std::uint16_t numberOfLevels = descriptionBlock->number_of_levels(); + std::uint8_t rangeMax = static_cast( + std::clamp((numberOfLevels > 0) ? numberOfLevels - 1 : 0, + std::numeric_limits::min(), + std::numeric_limits::max())); if (p->savedColorTable_ == p->colorTable_ && // p->savedOffset_ == offset && // @@ -274,7 +283,8 @@ void Level3ProductView::UpdateColorTable() [&](uint16_t i) { const size_t lutIndex = i - *dataRange.begin(); - float f; + + std::optional f = descriptionBlock->data_value(i); // Different products use different scale/offset formulas if (numberOfLevels > 16 || productCode == 34) @@ -285,28 +295,14 @@ void Level3ProductView::UpdateColorTable() } else { - switch (descriptionBlock->product_code()) + if (f.has_value()) { - case 159: - case 161: - case 163: - case 167: - case 168: - case 170: - case 172: - case 173: - case 174: - case 175: - case 176: - f = (i - offset) / scale; - break; - - default: - f = i * scale + offset; - break; + lut[lutIndex] = p->colorTable_->Color(f.value()); + } + else + { + lut[lutIndex] = boost::gil::rgba8_pixel_t {0, 0, 0, 0}; } - - lut[lutIndex] = p->colorTable_->Color(f); } } else @@ -314,29 +310,15 @@ void Level3ProductView::UpdateColorTable() uint16_t th = descriptionBlock->data_level_threshold(i); if ((th & 0x8000u) == 0) { - float scaleFactor = 1.0f; - - if (th & 0x4000u) - { - scaleFactor *= 0.01f; - } - if (th & 0x2000u) - { - scaleFactor *= 0.05f; - } - if (th & 0x1000u) - { - scaleFactor *= 0.1f; - } - if (th & 0x0100u) - { - scaleFactor *= -1.0f; - } - // If bit 0 is zero, then the LSB is numeric - f = static_cast(th & 0x00ffu) * scaleFactor; - - lut[lutIndex] = p->colorTable_->Color(f); + if (f.has_value()) + { + lut[lutIndex] = p->colorTable_->Color(f.value()); + } + else + { + lut[lutIndex] = boost::gil::rgba8_pixel_t {0, 0, 0, 0}; + } } else { diff --git a/wxdata/include/scwx/wsr88d/rpg/product_description_block.hpp b/wxdata/include/scwx/wsr88d/rpg/product_description_block.hpp index e3675670..7357f13a 100644 --- a/wxdata/include/scwx/wsr88d/rpg/product_description_block.hpp +++ b/wxdata/include/scwx/wsr88d/rpg/product_description_block.hpp @@ -1,9 +1,11 @@ #pragma once #include +#include #include #include +#include #include @@ -61,6 +63,9 @@ public: float scale() const; uint16_t number_of_levels() const; + std::optional data_level_code(std::uint8_t level) const; + std::optional data_value(std::uint8_t level) const; + float log_offset() const; float log_scale() const; diff --git a/wxdata/include/scwx/wsr88d/rpg/rpg_types.hpp b/wxdata/include/scwx/wsr88d/rpg/rpg_types.hpp new file mode 100644 index 00000000..bbeb9067 --- /dev/null +++ b/wxdata/include/scwx/wsr88d/rpg/rpg_types.hpp @@ -0,0 +1,58 @@ +#pragma once + +namespace scwx +{ +namespace wsr88d +{ +namespace rpg +{ + +enum class DataLevelCode +{ + BadData, + BelowThreshold, + Blank, + ChaffDetection, + EditRemove, + FlaggedData, + Missing, + NoData, + OutsideCoverageArea, + NoAccumulation, + RangeFolded, + Reserved, + + // Hydrometeor Classification + Biological, + AnomalousPropagationGroundClutter, + IceCrystals, + DrySnow, + WetSnow, + LightAndOrModerateRain, + HeavyRain, + BigDrops, + Graupel, + SmallHail, + LargeHail, + GiantHail, + UnknownClassification, + + // Rainfall Rate Classification + NoPrecipitation, + Unfilled, + Convective, + Tropical, + SpecificAttenuation, + KL, + KH, + Z1, + Z6, + Z8, + SI, + + Unknown +}; + +} // 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 index 73014bd2..5c4b82dd 100644 --- a/wxdata/source/scwx/wsr88d/rpg/product_description_block.cpp +++ b/wxdata/source/scwx/wsr88d/rpg/product_description_block.cpp @@ -705,6 +705,316 @@ bool ProductDescriptionBlock::Parse(std::istream& is) return blockValid; } +std::optional +ProductDescriptionBlock::data_level_code(std::uint8_t level) const +{ + switch (p->productCode_) + { + case 32: + case 93: + case 94: + case 99: + case 153: + case 154: + case 155: + case 159: + case 161: + case 163: + case 167: + case 168: + case 195: + switch (level) + { + case 0: + return DataLevelCode::BelowThreshold; + case 1: + return DataLevelCode::RangeFolded; + default: + break; + } + break; + + case 81: + switch (level) + { + case 0: + return DataLevelCode::NoAccumulation; + case 255: + return DataLevelCode::OutsideCoverageArea; + default: + break; + } + break; + + case 134: + switch (level) + { + case 0: + return DataLevelCode::BelowThreshold; + case 1: + return DataLevelCode::FlaggedData; + case 255: + return DataLevelCode::Reserved; + default: + break; + } + break; + + case 135: + switch (level) + { + case 0: + return DataLevelCode::BelowThreshold; + case 1: + return DataLevelCode::BadData; + default: + break; + } + break; + + case 138: + switch (level) + { + case 0: + return DataLevelCode::NoAccumulation; + default: + break; + } + break; + + case 165: + case 177: + switch (level) + { + case 0: + return DataLevelCode::BelowThreshold; + case 10: + return DataLevelCode::Biological; + case 20: + return DataLevelCode::AnomalousPropagationGroundClutter; + case 30: + return DataLevelCode::IceCrystals; + case 40: + return DataLevelCode::DrySnow; + case 50: + return DataLevelCode::WetSnow; + case 60: + return DataLevelCode::LightAndOrModerateRain; + case 70: + return DataLevelCode::HeavyRain; + case 80: + return DataLevelCode::BigDrops; + case 90: + return DataLevelCode::Graupel; + case 100: + return DataLevelCode::SmallHail; + case 110: + return DataLevelCode::LargeHail; + case 120: + return DataLevelCode::GiantHail; + case 140: + return DataLevelCode::UnknownClassification; + case 150: + return DataLevelCode::RangeFolded; + } + break; + + case 170: + case 172: + case 173: + case 174: + case 175: + switch (level) + { + case 0: + return DataLevelCode::NoData; + default: + break; + } + break; + + case 193: + switch (level) + { + case 0: + return DataLevelCode::BelowThreshold; + case 1: + return DataLevelCode::RangeFolded; + case 2: + return DataLevelCode::EditRemove; + case 254: + return DataLevelCode::ChaffDetection; + default: + break; + } + break; + + case 197: + switch (level) + { + case 0: + return DataLevelCode::NoPrecipitation; + case 10: + return DataLevelCode::Unfilled; + case 20: + return DataLevelCode::Convective; + case 30: + return DataLevelCode::Tropical; + case 40: + return DataLevelCode::SpecificAttenuation; + case 50: + return DataLevelCode::KL; + case 60: + return DataLevelCode::KH; + case 70: + return DataLevelCode::Z1; + case 80: + return DataLevelCode::Z6; + case 90: + return DataLevelCode::Z8; + case 100: + return DataLevelCode::SI; + default: + break; + } + break; + + default: + break; + } + + // Different products use different scale/offset formulas + if (number_of_levels() <= 16 && p->productCode_ != 34) + { + uint16_t th = data_level_threshold(level); + if ((th & 0x8000u)) + { + // If bit 0 is one, then the LSB is coded + uint16_t lsb = th & 0x00ffu; + + switch (lsb) + { + case 0: + return DataLevelCode::Blank; + case 1: + return DataLevelCode::BelowThreshold; + case 2: + return DataLevelCode::NoData; + case 3: + return DataLevelCode::RangeFolded; + case 4: + return DataLevelCode::Biological; + case 5: + return DataLevelCode::AnomalousPropagationGroundClutter; + case 6: + return DataLevelCode::IceCrystals; + case 7: + return DataLevelCode::Graupel; + case 8: + return DataLevelCode::WetSnow; + case 9: + return DataLevelCode::DrySnow; + case 10: + return DataLevelCode::LightAndOrModerateRain; + case 11: + return DataLevelCode::HeavyRain; + case 12: + return DataLevelCode::BigDrops; + case 13: + return DataLevelCode::SmallHail; + case 14: + return DataLevelCode::UnknownClassification; + case 15: + return DataLevelCode::LargeHail; + case 16: + return DataLevelCode::GiantHail; + default: + break; + } + } + } + + return std::nullopt; +} + +std::optional +ProductDescriptionBlock::data_value(std::uint8_t level) const +{ + float dataOffset = offset(); + float dataScale = scale(); + std::uint16_t dataThreshold = threshold(); + std::uint16_t numberOfLevels = number_of_levels(); + + if (level < dataThreshold) + { + return std::nullopt; + } + + float f; + + // Different products use different scale/offset formulas + if (numberOfLevels > 16 || p->productCode_ == 34) + { + switch (p->productCode_) + { + case 159: + case 161: + case 163: + case 167: + case 168: + case 170: + case 172: + case 173: + case 174: + case 175: + case 176: + f = (level - dataOffset) / dataScale; + break; + + default: + f = level * dataScale + dataOffset; + break; + } + } + else + { + std::uint16_t th = data_level_threshold(level); + if ((th & 0x8000u) == 0) + { + float scaleFactor = 1.0f; + + if (th & 0x4000u) + { + scaleFactor *= 0.01f; + } + if (th & 0x2000u) + { + scaleFactor *= 0.05f; + } + if (th & 0x1000u) + { + scaleFactor *= 0.1f; + } + if (th & 0x0100u) + { + scaleFactor *= -1.0f; + } + + // If bit 0 is zero, then the LSB is numeric + f = static_cast(th & 0x00ffu) * scaleFactor; + } + else + { + // If bit 0 is one, then the LSB is coded + std::uint16_t lsb = th & 0x00ffu; + + f = static_cast(lsb); + } + } + + return f; +} + } // namespace rpg } // namespace wsr88d } // namespace scwx diff --git a/wxdata/wxdata.cmake b/wxdata/wxdata.cmake index 25998a11..11a6d54b 100644 --- a/wxdata/wxdata.cmake +++ b/wxdata/wxdata.cmake @@ -146,6 +146,7 @@ set(HDR_WSR88D_RPG include/scwx/wsr88d/rpg/ccb_header.hpp include/scwx/wsr88d/rpg/radar_coded_message.hpp include/scwx/wsr88d/rpg/radial_data_packet.hpp include/scwx/wsr88d/rpg/raster_data_packet.hpp + include/scwx/wsr88d/rpg/rpg_types.hpp include/scwx/wsr88d/rpg/scit_forecast_data_packet.hpp include/scwx/wsr88d/rpg/set_color_level_packet.hpp include/scwx/wsr88d/rpg/special_graphic_symbol_packet.hpp