Add data level codes, refactor data value determination to product description block

This commit is contained in:
Dan Paulat 2024-01-04 01:17:14 -06:00
parent b67f546774
commit 2f06076bb5
5 changed files with 407 additions and 51 deletions

View file

@ -7,6 +7,8 @@
#include <scwx/wsr88d/rpg/graphic_product_message.hpp>
#include <scwx/wsr88d/rpg/radial_data_packet.hpp>
#include <limits>
#include <boost/range/irange.hpp>
#include <boost/timer/timer.hpp>
#include <fmt/format.h>
@ -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::uint8_t>(
std::clamp<std::uint16_t>(descriptionBlock->threshold(),
std::numeric_limits<std::uint8_t>::min(),
std::numeric_limits<std::uint8_t>::max()));
// If the threshold is 2, the range min should be set to 1 for range folding
uint16_t rangeMin = std::min<uint16_t>(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<std::uint8_t>(1, threshold);
std::uint16_t numberOfLevels = descriptionBlock->number_of_levels();
std::uint8_t rangeMax = static_cast<std::uint8_t>(
std::clamp<std::uint16_t>((numberOfLevels > 0) ? numberOfLevels - 1 : 0,
std::numeric_limits<std::uint8_t>::min(),
std::numeric_limits<std::uint8_t>::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<float> 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<float>(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
{

View file

@ -1,9 +1,11 @@
#pragma once
#include <scwx/awips/message.hpp>
#include <scwx/wsr88d/rpg/rpg_types.hpp>
#include <cstdint>
#include <memory>
#include <optional>
#include <units/angle.h>
@ -61,6 +63,9 @@ public:
float scale() const;
uint16_t number_of_levels() const;
std::optional<DataLevelCode> data_level_code(std::uint8_t level) const;
std::optional<float> data_value(std::uint8_t level) const;
float log_offset() const;
float log_scale() const;

View file

@ -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

View file

@ -705,6 +705,316 @@ bool ProductDescriptionBlock::Parse(std::istream& is)
return blockValid;
}
std::optional<DataLevelCode>
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<float>
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<float>(th & 0x00ffu) * scaleFactor;
}
else
{
// If bit 0 is one, then the LSB is coded
std::uint16_t lsb = th & 0x00ffu;
f = static_cast<float>(lsb);
}
}
return f;
}
} // namespace rpg
} // namespace wsr88d
} // namespace scwx

View file

@ -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