From bfd7963d4c191c350514c39908a7ec4c2cc4a04b Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 18 Jan 2024 23:50:46 -0600 Subject: [PATCH 01/37] Rename DigitalRadarData to DigitalRadarDataGeneric to align with message type 31 name --- wxdata/include/scwx/wsr88d/ar2v_file.hpp | 4 +- ...ata.hpp => digital_radar_data_generic.hpp} | 37 ++++----- wxdata/source/scwx/wsr88d/ar2v_file.cpp | 13 ++-- ...ata.cpp => digital_radar_data_generic.cpp} | 76 ++++++++++--------- .../wsr88d/rda/level2_message_factory.cpp | 18 ++--- wxdata/wxdata.cmake | 4 +- 6 files changed, 79 insertions(+), 73 deletions(-) rename wxdata/include/scwx/wsr88d/rda/{digital_radar_data.hpp => digital_radar_data_generic.hpp} (82%) rename wxdata/source/scwx/wsr88d/rda/{digital_radar_data.cpp => digital_radar_data_generic.cpp} (90%) diff --git a/wxdata/include/scwx/wsr88d/ar2v_file.hpp b/wxdata/include/scwx/wsr88d/ar2v_file.hpp index 90702e8e..87628fd6 100644 --- a/wxdata/include/scwx/wsr88d/ar2v_file.hpp +++ b/wxdata/include/scwx/wsr88d/ar2v_file.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include #include @@ -26,7 +26,7 @@ public: explicit Ar2vFile(); ~Ar2vFile(); - Ar2vFile(const Ar2vFile&) = delete; + Ar2vFile(const Ar2vFile&) = delete; Ar2vFile& operator=(const Ar2vFile&) = delete; Ar2vFile(Ar2vFile&&) noexcept; diff --git a/wxdata/include/scwx/wsr88d/rda/digital_radar_data.hpp b/wxdata/include/scwx/wsr88d/rda/digital_radar_data_generic.hpp similarity index 82% rename from wxdata/include/scwx/wsr88d/rda/digital_radar_data.hpp rename to wxdata/include/scwx/wsr88d/rda/digital_radar_data_generic.hpp index 17a56b57..0e0afdc5 100644 --- a/wxdata/include/scwx/wsr88d/rda/digital_radar_data.hpp +++ b/wxdata/include/scwx/wsr88d/rda/digital_radar_data_generic.hpp @@ -34,10 +34,11 @@ class MomentDataBlockImpl; class RadialDataBlockImpl; class VolumeDataBlockImpl; -class DigitalRadarData; -class DigitalRadarDataImpl; +class DigitalRadarDataGeneric; +class DigitalRadarDataGenericImpl; -typedef std::map> ElevationScan; +typedef std::map> + ElevationScan; class DataBlock { @@ -46,7 +47,7 @@ protected: const std::string& dataName); ~DataBlock(); - DataBlock(const DataBlock&) = delete; + DataBlock(const DataBlock&) = delete; DataBlock& operator=(const DataBlock&) = delete; DataBlock(DataBlock&&) noexcept; @@ -63,7 +64,7 @@ public: const std::string& dataName); ~ElevationDataBlock(); - ElevationDataBlock(const ElevationDataBlock&) = delete; + ElevationDataBlock(const ElevationDataBlock&) = delete; ElevationDataBlock& operator=(const ElevationDataBlock&) = delete; ElevationDataBlock(ElevationDataBlock&&) noexcept; @@ -87,7 +88,7 @@ public: const std::string& dataName); ~MomentDataBlock(); - MomentDataBlock(const MomentDataBlock&) = delete; + MomentDataBlock(const MomentDataBlock&) = delete; MomentDataBlock& operator=(const MomentDataBlock&) = delete; MomentDataBlock(MomentDataBlock&&) noexcept; @@ -123,7 +124,7 @@ public: const std::string& dataName); ~RadialDataBlock(); - RadialDataBlock(const RadialDataBlock&) = delete; + RadialDataBlock(const RadialDataBlock&) = delete; RadialDataBlock& operator=(const RadialDataBlock&) = delete; RadialDataBlock(RadialDataBlock&&) noexcept; @@ -149,7 +150,7 @@ public: const std::string& dataName); ~VolumeDataBlock(); - VolumeDataBlock(const VolumeDataBlock&) = delete; + VolumeDataBlock(const VolumeDataBlock&) = delete; VolumeDataBlock& operator=(const VolumeDataBlock&) = delete; VolumeDataBlock(VolumeDataBlock&&) noexcept; @@ -170,17 +171,17 @@ private: bool Parse(std::istream& is); }; -class DigitalRadarData : public Level2Message +class DigitalRadarDataGeneric : public Level2Message { public: - explicit DigitalRadarData(); - ~DigitalRadarData(); + explicit DigitalRadarDataGeneric(); + ~DigitalRadarDataGeneric(); - DigitalRadarData(const DigitalRadarData&) = delete; - DigitalRadarData& operator=(const DigitalRadarData&) = delete; + DigitalRadarDataGeneric(const DigitalRadarDataGeneric&) = delete; + DigitalRadarDataGeneric& operator=(const DigitalRadarDataGeneric&) = delete; - DigitalRadarData(DigitalRadarData&&) noexcept; - DigitalRadarData& operator=(DigitalRadarData&&) noexcept; + DigitalRadarDataGeneric(DigitalRadarDataGeneric&&) noexcept; + DigitalRadarDataGeneric& operator=(DigitalRadarDataGeneric&&) noexcept; std::string radar_identifier() const; uint32_t collection_time() const; @@ -205,11 +206,11 @@ public: bool Parse(std::istream& is); - static std::shared_ptr Create(Level2MessageHeader&& header, - std::istream& is); + static std::shared_ptr + Create(Level2MessageHeader&& header, std::istream& is); private: - std::unique_ptr p; + std::unique_ptr p; }; } // namespace rda diff --git a/wxdata/source/scwx/wsr88d/ar2v_file.cpp b/wxdata/source/scwx/wsr88d/ar2v_file.cpp index b1790d6f..12763f83 100644 --- a/wxdata/source/scwx/wsr88d/ar2v_file.cpp +++ b/wxdata/source/scwx/wsr88d/ar2v_file.cpp @@ -58,7 +58,8 @@ public: void IndexFile(); void ParseLDMRecords(); void ParseLDMRecord(std::istream& is); - void ProcessRadarData(std::shared_ptr message); + void ProcessRadarData( + const std::shared_ptr& message); std::string tapeFilename_; std::string extensionNumber_; @@ -108,7 +109,7 @@ std::chrono::system_clock::time_point Ar2vFile::end_time() const if (p->radarData_.size() > 0) { - std::shared_ptr lastRadial = + std::shared_ptr lastRadial = p->radarData_.crbegin()->second->crbegin()->second; endTime = util::TimePoint(lastRadial->modified_julian_date(), @@ -389,7 +390,7 @@ void Ar2vFileImpl::HandleMessage(std::shared_ptr& message) case static_cast(rda::MessageId::DigitalRadarData): ProcessRadarData( - std::static_pointer_cast(message)); + std::static_pointer_cast(message)); break; default: @@ -398,7 +399,7 @@ void Ar2vFileImpl::HandleMessage(std::shared_ptr& message) } void Ar2vFileImpl::ProcessRadarData( - std::shared_ptr message) + const std::shared_ptr& message) { uint16_t azimuthIndex = message->azimuth_number() - 1; uint16_t elevationIndex = message->elevation_number() - 1; @@ -421,14 +422,14 @@ void Ar2vFileImpl::IndexFile() return; } - for (auto elevationCut : radarData_) + for (auto& elevationCut : radarData_) { uint16_t elevationAngle = vcpData_->elevation_angle_raw(elevationCut.first); rda::WaveformType waveformType = vcpData_->waveform_type(elevationCut.first); - std::shared_ptr radial0 = + std::shared_ptr& radial0 = (*elevationCut.second)[0]; if (radial0 == nullptr) diff --git a/wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp b/wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp similarity index 90% rename from wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp rename to wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp index b31955a7..bad25049 100644 --- a/wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp +++ b/wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp @@ -1,4 +1,4 @@ -#include +#include #include namespace scwx @@ -8,8 +8,9 @@ namespace wsr88d namespace rda { -static const std::string logPrefix_ = "scwx::wsr88d::rda::digital_radar_data"; -static const auto logger_ = util::Logger::Create(logPrefix_); +static const std::string logPrefix_ = + "scwx::wsr88d::rda::digital_radar_data_generic"; +static const auto logger_ = util::Logger::Create(logPrefix_); static const std::unordered_map strToDataBlock_ { {"VOL", DataBlockType::Volume}, @@ -493,10 +494,10 @@ bool RadialDataBlock::Parse(std::istream& is) return dataBlockValid; } -class DigitalRadarDataImpl +class DigitalRadarDataGenericImpl { public: - explicit DigitalRadarDataImpl() : + explicit DigitalRadarDataGenericImpl() : radarIdentifier_ {}, collectionTime_ {0}, modifiedJulianDate_ {0}, @@ -517,7 +518,7 @@ public: elevationDataBlock_ {nullptr}, radialDataBlock_ {nullptr}, momentDataBlock_ {} {}; - ~DigitalRadarDataImpl() = default; + ~DigitalRadarDataGenericImpl() = default; std::string radarIdentifier_; uint32_t collectionTime_; @@ -543,109 +544,112 @@ public: momentDataBlock_; }; -DigitalRadarData::DigitalRadarData() : - Level2Message(), p(std::make_unique()) +DigitalRadarDataGeneric::DigitalRadarDataGeneric() : + Level2Message(), p(std::make_unique()) { } -DigitalRadarData::~DigitalRadarData() = default; +DigitalRadarDataGeneric::~DigitalRadarDataGeneric() = default; -DigitalRadarData::DigitalRadarData(DigitalRadarData&&) noexcept = default; -DigitalRadarData& -DigitalRadarData::operator=(DigitalRadarData&&) noexcept = default; +DigitalRadarDataGeneric::DigitalRadarDataGeneric( + DigitalRadarDataGeneric&&) noexcept = default; +DigitalRadarDataGeneric& DigitalRadarDataGeneric::operator=( + DigitalRadarDataGeneric&&) noexcept = default; -std::string DigitalRadarData::radar_identifier() const +std::string DigitalRadarDataGeneric::radar_identifier() const { return p->radarIdentifier_; } -uint32_t DigitalRadarData::collection_time() const +uint32_t DigitalRadarDataGeneric::collection_time() const { return p->collectionTime_; } -uint16_t DigitalRadarData::modified_julian_date() const +uint16_t DigitalRadarDataGeneric::modified_julian_date() const { return p->modifiedJulianDate_; } -uint16_t DigitalRadarData::azimuth_number() const +uint16_t DigitalRadarDataGeneric::azimuth_number() const { return p->azimuthNumber_; } -float DigitalRadarData::azimuth_angle() const +float DigitalRadarDataGeneric::azimuth_angle() const { return p->azimuthAngle_; } -uint8_t DigitalRadarData::compression_indicator() const +uint8_t DigitalRadarDataGeneric::compression_indicator() const { return p->compressionIndicator_; } -uint16_t DigitalRadarData::radial_length() const +uint16_t DigitalRadarDataGeneric::radial_length() const { return p->radialLength_; } -uint8_t DigitalRadarData::azimuth_resolution_spacing() const +uint8_t DigitalRadarDataGeneric::azimuth_resolution_spacing() const { return p->azimuthResolutionSpacing_; } -uint8_t DigitalRadarData::radial_status() const +uint8_t DigitalRadarDataGeneric::radial_status() const { return p->radialStatus_; } -uint8_t DigitalRadarData::elevation_number() const +uint8_t DigitalRadarDataGeneric::elevation_number() const { return p->elevationNumber_; } -uint8_t DigitalRadarData::cut_sector_number() const +uint8_t DigitalRadarDataGeneric::cut_sector_number() const { return p->cutSectorNumber_; } -float DigitalRadarData::elevation_angle() const +float DigitalRadarDataGeneric::elevation_angle() const { return p->elevationAngle_; } -uint8_t DigitalRadarData::radial_spot_blanking_status() const +uint8_t DigitalRadarDataGeneric::radial_spot_blanking_status() const { return p->radialSpotBlankingStatus_; } -uint8_t DigitalRadarData::azimuth_indexing_mode() const +uint8_t DigitalRadarDataGeneric::azimuth_indexing_mode() const { return p->azimuthIndexingMode_; } -uint16_t DigitalRadarData::data_block_count() const +uint16_t DigitalRadarDataGeneric::data_block_count() const { return p->dataBlockCount_; } std::shared_ptr -DigitalRadarData::elevation_data_block() const +DigitalRadarDataGeneric::elevation_data_block() const { return p->elevationDataBlock_; } -std::shared_ptr DigitalRadarData::radial_data_block() const +std::shared_ptr +DigitalRadarDataGeneric::radial_data_block() const { return p->radialDataBlock_; } -std::shared_ptr DigitalRadarData::volume_data_block() const +std::shared_ptr +DigitalRadarDataGeneric::volume_data_block() const { return p->volumeDataBlock_; } std::shared_ptr -DigitalRadarData::moment_data_block(DataBlockType type) const +DigitalRadarDataGeneric::moment_data_block(DataBlockType type) const { std::shared_ptr momentDataBlock = nullptr; @@ -658,7 +662,7 @@ DigitalRadarData::moment_data_block(DataBlockType type) const return momentDataBlock; } -bool DigitalRadarData::Parse(std::istream& is) +bool DigitalRadarDataGeneric::Parse(std::istream& is) { logger_->trace("Parsing Digital Radar Data (Message Type 31)"); @@ -784,11 +788,11 @@ bool DigitalRadarData::Parse(std::istream& is) return messageValid; } -std::shared_ptr -DigitalRadarData::Create(Level2MessageHeader&& header, std::istream& is) +std::shared_ptr +DigitalRadarDataGeneric::Create(Level2MessageHeader&& header, std::istream& is) { - std::shared_ptr message = - std::make_shared(); + std::shared_ptr message = + std::make_shared(); message->set_header(std::move(header)); if (!message->Parse(is)) diff --git a/wxdata/source/scwx/wsr88d/rda/level2_message_factory.cpp b/wxdata/source/scwx/wsr88d/rda/level2_message_factory.cpp index 48d2d8d7..db76fafb 100644 --- a/wxdata/source/scwx/wsr88d/rda/level2_message_factory.cpp +++ b/wxdata/source/scwx/wsr88d/rda/level2_message_factory.cpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include #include @@ -28,14 +28,14 @@ typedef std::function(Level2MessageHeader&&, std::istream&)> CreateLevel2MessageFunction; -static const std::unordered_map create_ { - {2, RdaStatusData::Create}, - {3, PerformanceMaintenanceData::Create}, - {5, VolumeCoveragePatternData::Create}, - {13, ClutterFilterBypassMap::Create}, - {15, ClutterFilterMap::Create}, - {18, RdaAdaptationData::Create}, - {31, DigitalRadarData::Create}}; +static const std::unordered_map + create_ {{2, RdaStatusData::Create}, + {3, PerformanceMaintenanceData::Create}, + {5, VolumeCoveragePatternData::Create}, + {13, ClutterFilterBypassMap::Create}, + {15, ClutterFilterMap::Create}, + {18, RdaAdaptationData::Create}, + {31, DigitalRadarDataGeneric::Create}}; struct Level2MessageFactory::Context { diff --git a/wxdata/wxdata.cmake b/wxdata/wxdata.cmake index d0170819..e2cdec59 100644 --- a/wxdata/wxdata.cmake +++ b/wxdata/wxdata.cmake @@ -102,7 +102,7 @@ set(SRC_WSR88D source/scwx/wsr88d/ar2v_file.cpp source/scwx/wsr88d/wsr88d_types.cpp) set(HDR_WSR88D_RDA include/scwx/wsr88d/rda/clutter_filter_bypass_map.hpp include/scwx/wsr88d/rda/clutter_filter_map.hpp - include/scwx/wsr88d/rda/digital_radar_data.hpp + include/scwx/wsr88d/rda/digital_radar_data_generic.hpp include/scwx/wsr88d/rda/level2_message.hpp include/scwx/wsr88d/rda/level2_message_factory.hpp include/scwx/wsr88d/rda/level2_message_header.hpp @@ -113,7 +113,7 @@ set(HDR_WSR88D_RDA include/scwx/wsr88d/rda/clutter_filter_bypass_map.hpp include/scwx/wsr88d/rda/volume_coverage_pattern_data.hpp) set(SRC_WSR88D_RDA source/scwx/wsr88d/rda/clutter_filter_bypass_map.cpp source/scwx/wsr88d/rda/clutter_filter_map.cpp - source/scwx/wsr88d/rda/digital_radar_data.cpp + source/scwx/wsr88d/rda/digital_radar_data_generic.cpp source/scwx/wsr88d/rda/level2_message.cpp source/scwx/wsr88d/rda/level2_message_factory.cpp source/scwx/wsr88d/rda/level2_message_header.cpp From 68cca50ddf9919788af78b5b00561191af7de428 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 19 Jan 2024 23:34:59 -0600 Subject: [PATCH 02/37] Add Level 2 Message Type 1, Digital Radar Data --- .../scwx/wsr88d/rda/digital_radar_data.hpp | 62 +++ .../scwx/wsr88d/rda/digital_radar_data.cpp | 355 ++++++++++++++++++ .../wsr88d/rda/level2_message_factory.cpp | 4 +- wxdata/wxdata.cmake | 2 + 4 files changed, 422 insertions(+), 1 deletion(-) create mode 100644 wxdata/include/scwx/wsr88d/rda/digital_radar_data.hpp create mode 100644 wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp diff --git a/wxdata/include/scwx/wsr88d/rda/digital_radar_data.hpp b/wxdata/include/scwx/wsr88d/rda/digital_radar_data.hpp new file mode 100644 index 00000000..42acb84b --- /dev/null +++ b/wxdata/include/scwx/wsr88d/rda/digital_radar_data.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include + +namespace scwx +{ +namespace wsr88d +{ +namespace rda +{ + +class DigitalRadarData : public Level2Message +{ +public: + explicit DigitalRadarData(); + ~DigitalRadarData(); + + DigitalRadarData(const DigitalRadarData&) = delete; + DigitalRadarData& operator=(const DigitalRadarData&) = delete; + + DigitalRadarData(DigitalRadarData&&) noexcept; + DigitalRadarData& operator=(DigitalRadarData&&) noexcept; + + std::uint32_t collection_time() const; + std::uint16_t modified_julian_date() const; + std::uint16_t unambiguous_range() const; + std::uint16_t azimuth_angle() const; + std::uint16_t azimuth_number() const; + std::uint16_t radial_status() const; + std::uint16_t elevation_angle() const; + std::uint16_t elevation_number() const; + std::uint16_t surveillance_range() const; + std::uint16_t doppler_range() const; + std::uint16_t surveillance_range_sample_interval() const; + std::uint16_t doppler_range_sample_interval() const; + std::uint16_t number_of_surveillance_bins() const; + std::uint16_t number_of_doppler_bins() const; + std::uint16_t cut_sector_number() const; + float calibration_constant() const; + std::uint16_t surveillance_pointer() const; + std::uint16_t velocity_pointer() const; + std::uint16_t spectral_width_pointer() const; + std::uint16_t doppler_velocity_resolution() const; + std::uint16_t volume_coverage_pattern_number() const; + std::uint16_t nyquist_velocity() const; + std::uint16_t atmos() const; + std::uint16_t tover() const; + std::uint16_t radial_spot_blanking_status() const; + + bool Parse(std::istream& is); + + static std::shared_ptr Create(Level2MessageHeader&& header, + std::istream& is); + +private: + class Impl; + std::unique_ptr p; +}; + +} // namespace rda +} // namespace wsr88d +} // namespace scwx diff --git a/wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp b/wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp new file mode 100644 index 00000000..f894458e --- /dev/null +++ b/wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp @@ -0,0 +1,355 @@ +#include +#include + +namespace scwx +{ +namespace wsr88d +{ +namespace rda +{ + +static const std::string logPrefix_ = "scwx::wsr88d::rda::digital_radar_data"; +static const auto logger_ = util::Logger::Create(logPrefix_); + +class DigitalRadarData::Impl +{ +public: + explicit Impl() {}; + ~Impl() = default; + + std::uint32_t collectionTime_ {}; + std::uint16_t modifiedJulianDate_ {}; + std::uint16_t unambiguousRange_ {}; + std::uint16_t azimuthAngle_ {}; + std::uint16_t azimuthNumber_ {}; + std::uint16_t radialStatus_ {}; + std::uint16_t elevationAngle_ {}; + std::uint16_t elevationNumber_ {}; + std::uint16_t surveillanceRange_ {}; + std::uint16_t dopplerRange_ {}; + std::uint16_t surveillanceRangeSampleInterval_ {}; + std::uint16_t dopplerRangeSampleInterval_ {}; + std::uint16_t numberOfSurveillanceBins_ {}; + std::uint16_t numberOfDopplerBins_ {}; + std::uint16_t cutSectorNumber_ {}; + float calibrationConstant_ {}; + std::uint16_t surveillancePointer_ {}; + std::uint16_t velocityPointer_ {}; + std::uint16_t spectralWidthPointer_ {}; + std::uint16_t dopplerVelocityResolution_ {}; + std::uint16_t vcpNumber_ {}; + std::uint16_t nyquistVelocity_ {}; + std::uint16_t atmos_ {}; + std::uint16_t tover_ {}; + std::uint16_t radialSpotBlankingStatus_ {}; + + std::vector reflectivity_ {}; + std::vector dopplerVelocity_ {}; + std::vector dopplerSpectrumWidth_ {}; +}; + +DigitalRadarData::DigitalRadarData() : + Level2Message(), p(std::make_unique()) +{ +} +DigitalRadarData::~DigitalRadarData() = default; + +DigitalRadarData::DigitalRadarData(DigitalRadarData&&) noexcept = default; +DigitalRadarData& +DigitalRadarData::operator=(DigitalRadarData&&) noexcept = default; + +std::uint32_t DigitalRadarData::collection_time() const +{ + return p->collectionTime_; +} + +std::uint16_t DigitalRadarData::modified_julian_date() const +{ + return p->modifiedJulianDate_; +} + +std::uint16_t DigitalRadarData::unambiguous_range() const +{ + return p->unambiguousRange_; +} + +std::uint16_t DigitalRadarData::azimuth_angle() const +{ + return p->azimuthAngle_; +} + +std::uint16_t DigitalRadarData::azimuth_number() const +{ + return p->azimuthNumber_; +} + +std::uint16_t DigitalRadarData::radial_status() const +{ + return p->radialStatus_; +} + +std::uint16_t DigitalRadarData::elevation_angle() const +{ + return p->elevationAngle_; +} + +std::uint16_t DigitalRadarData::elevation_number() const +{ + return p->elevationNumber_; +} + +std::uint16_t DigitalRadarData::surveillance_range() const +{ + return p->surveillanceRange_; +} + +std::uint16_t DigitalRadarData::doppler_range() const +{ + return p->dopplerRange_; +} + +std::uint16_t DigitalRadarData::surveillance_range_sample_interval() const +{ + return p->surveillanceRangeSampleInterval_; +} + +std::uint16_t DigitalRadarData::doppler_range_sample_interval() const +{ + return p->dopplerRangeSampleInterval_; +} + +std::uint16_t DigitalRadarData::number_of_surveillance_bins() const +{ + return p->numberOfSurveillanceBins_; +} + +std::uint16_t DigitalRadarData::number_of_doppler_bins() const +{ + return p->numberOfDopplerBins_; +} + +std::uint16_t DigitalRadarData::cut_sector_number() const +{ + return p->cutSectorNumber_; +} + +float DigitalRadarData::calibration_constant() const +{ + return p->calibrationConstant_; +} + +std::uint16_t DigitalRadarData::surveillance_pointer() const +{ + return p->surveillancePointer_; +} + +std::uint16_t DigitalRadarData::velocity_pointer() const +{ + return p->velocityPointer_; +} + +std::uint16_t DigitalRadarData::spectral_width_pointer() const +{ + return p->spectralWidthPointer_; +} + +std::uint16_t DigitalRadarData::doppler_velocity_resolution() const +{ + return p->dopplerVelocityResolution_; +} + +std::uint16_t DigitalRadarData::volume_coverage_pattern_number() const +{ + return p->vcpNumber_; +} + +std::uint16_t DigitalRadarData::nyquist_velocity() const +{ + return p->nyquistVelocity_; +} + +std::uint16_t DigitalRadarData::atmos() const +{ + return p->atmos_; +} + +std::uint16_t DigitalRadarData::tover() const +{ + return p->tover_; +} + +std::uint16_t DigitalRadarData::radial_spot_blanking_status() const +{ + return p->radialSpotBlankingStatus_; +} + +bool DigitalRadarData::Parse(std::istream& is) +{ + logger_->trace("Parsing Digital Radar Data (Message Type 1)"); + + bool messageValid = true; + std::size_t bytesRead = 0; + + std::streampos isBegin = is.tellg(); + + is.read(reinterpret_cast(&p->collectionTime_), 4); // 0-3 + is.read(reinterpret_cast(&p->modifiedJulianDate_), 2); // 4-5 + is.read(reinterpret_cast(&p->unambiguousRange_), 2); // 6-7 + is.read(reinterpret_cast(&p->azimuthAngle_), 2); // 8-9 + is.read(reinterpret_cast(&p->azimuthNumber_), 2); // 10-11 + is.read(reinterpret_cast(&p->radialStatus_), 2); // 12-13 + is.read(reinterpret_cast(&p->elevationAngle_), 2); // 14-15 + is.read(reinterpret_cast(&p->elevationNumber_), 2); // 16-17 + is.read(reinterpret_cast(&p->surveillanceRange_), 2); // 18-19 + is.read(reinterpret_cast(&p->dopplerRange_), 2); // 20-21 + + is.read(reinterpret_cast(&p->surveillanceRangeSampleInterval_), + 2); // 22-23 + is.read(reinterpret_cast(&p->dopplerRangeSampleInterval_), + 2); // 24-25 + + is.read(reinterpret_cast(&p->numberOfSurveillanceBins_), 2); // 26-27 + is.read(reinterpret_cast(&p->numberOfDopplerBins_), 2); // 28-29 + is.read(reinterpret_cast(&p->cutSectorNumber_), 2); // 30-31 + is.read(reinterpret_cast(&p->calibrationConstant_), 4); // 32-35 + is.read(reinterpret_cast(&p->surveillancePointer_), 2); // 36-37 + is.read(reinterpret_cast(&p->velocityPointer_), 2); // 38-39 + is.read(reinterpret_cast(&p->spectralWidthPointer_), 2); // 40-41 + is.read(reinterpret_cast(&p->dopplerVelocityResolution_), 2); // 42-43 + is.read(reinterpret_cast(&p->vcpNumber_), 2); // 44-45 + is.seekg(14, std::ios_base::cur); // 46-59 + is.read(reinterpret_cast(&p->nyquistVelocity_), 2); // 60-61 + is.read(reinterpret_cast(&p->atmos_), 2); // 62-63 + is.read(reinterpret_cast(&p->tover_), 2); // 64-65 + is.read(reinterpret_cast(&p->radialSpotBlankingStatus_), 2); // 66-67 + is.seekg(32, std::ios_base::cur); // 68-99 + + p->collectionTime_ = ntohl(p->collectionTime_); + p->modifiedJulianDate_ = ntohs(p->modifiedJulianDate_); + p->unambiguousRange_ = ntohs(p->unambiguousRange_); + p->azimuthAngle_ = ntohs(p->azimuthAngle_); + p->azimuthNumber_ = ntohs(p->azimuthNumber_); + p->radialStatus_ = ntohs(p->radialStatus_); + p->elevationAngle_ = ntohs(p->elevationAngle_); + p->elevationNumber_ = ntohs(p->elevationNumber_); + p->surveillanceRange_ = ntohs(p->surveillanceRange_); + p->dopplerRange_ = ntohs(p->dopplerRange_); + + p->surveillanceRangeSampleInterval_ = + ntohs(p->surveillanceRangeSampleInterval_); + + p->dopplerRangeSampleInterval_ = ntohs(p->dopplerRangeSampleInterval_); + p->numberOfSurveillanceBins_ = ntohs(p->numberOfSurveillanceBins_); + p->numberOfDopplerBins_ = ntohs(p->numberOfDopplerBins_); + p->cutSectorNumber_ = ntohs(p->cutSectorNumber_); + p->calibrationConstant_ = SwapFloat(p->calibrationConstant_); + p->surveillancePointer_ = ntohs(p->surveillancePointer_); + p->velocityPointer_ = ntohs(p->velocityPointer_); + p->spectralWidthPointer_ = ntohs(p->spectralWidthPointer_); + p->dopplerVelocityResolution_ = ntohs(p->dopplerVelocityResolution_); + p->vcpNumber_ = ntohs(p->vcpNumber_); + p->nyquistVelocity_ = ntohs(p->nyquistVelocity_); + p->atmos_ = ntohs(p->atmos_); + p->tover_ = ntohs(p->tover_); + p->radialSpotBlankingStatus_ = ntohs(p->radialSpotBlankingStatus_); + + if (p->azimuthNumber_ < 1 || p->azimuthNumber_ > 400) + { + logger_->warn("Invalid azimuth number: {}", p->azimuthNumber_); + messageValid = false; + } + if (p->elevationNumber_ < 1 || p->elevationNumber_ > 25) + { + logger_->warn("Invalid elevation number: {}", p->elevationNumber_); + messageValid = false; + } + if (p->numberOfSurveillanceBins_ > 460) + { + logger_->warn("Invalid number of surveillance bins: {}", + p->numberOfSurveillanceBins_); + messageValid = false; + } + if (p->numberOfDopplerBins_ > 920) + { + logger_->warn("Invalid number of doppler bins: {}", + p->numberOfDopplerBins_); + messageValid = false; + } + if (p->surveillancePointer_ != 0 && p->surveillancePointer_ != 100) + { + logger_->warn("Invalid surveillance pointer: {}", + p->surveillancePointer_); + messageValid = false; + } + if (p->velocityPointer_ != 0 && + (p->velocityPointer_ < 100 || p->velocityPointer_ > 560)) + { + logger_->warn("Invalid velocity pointer: {}", p->velocityPointer_); + messageValid = false; + } + if (p->spectralWidthPointer_ != 0 && + (p->spectralWidthPointer_ < 100 || p->spectralWidthPointer_ > 1480 || + p->spectralWidthPointer_ > data_size())) + { + logger_->warn("Invalid spectral width pointer: {}", + p->spectralWidthPointer_); + messageValid = false; + } + + if (messageValid && p->surveillancePointer_ != 0) + { + is.seekg(isBegin + std::streamoff(p->surveillancePointer_), + std::ios_base::beg); + + p->reflectivity_.resize(p->numberOfSurveillanceBins_); + is.read(reinterpret_cast(p->reflectivity_.data()), + p->numberOfSurveillanceBins_); + } + + if (messageValid && p->velocityPointer_ != 0) + { + is.seekg(isBegin + std::streamoff(p->velocityPointer_), + std::ios_base::beg); + + p->dopplerVelocity_.resize(p->numberOfDopplerBins_); + is.read(reinterpret_cast(p->dopplerVelocity_.data()), + p->numberOfDopplerBins_); + } + + if (messageValid && p->spectralWidthPointer_ != 0) + { + is.seekg(isBegin + std::streamoff(p->spectralWidthPointer_), + std::ios_base::beg); + + p->dopplerSpectrumWidth_.resize(p->numberOfDopplerBins_); + is.read(reinterpret_cast(p->dopplerSpectrumWidth_.data()), + p->numberOfDopplerBins_); + } + + is.seekg(isBegin, std::ios_base::beg); + if (!ValidateMessage(is, bytesRead)) + { + messageValid = false; + } + + return messageValid; +} + +std::shared_ptr +DigitalRadarData::Create(Level2MessageHeader&& header, std::istream& is) +{ + std::shared_ptr message = + std::make_shared(); + message->set_header(std::move(header)); + + if (!message->Parse(is)) + { + message.reset(); + } + + return message; +} + +} // namespace rda +} // namespace wsr88d +} // namespace scwx diff --git a/wxdata/source/scwx/wsr88d/rda/level2_message_factory.cpp b/wxdata/source/scwx/wsr88d/rda/level2_message_factory.cpp index db76fafb..04c45676 100644 --- a/wxdata/source/scwx/wsr88d/rda/level2_message_factory.cpp +++ b/wxdata/source/scwx/wsr88d/rda/level2_message_factory.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -29,7 +30,8 @@ typedef std::function(Level2MessageHeader&&, CreateLevel2MessageFunction; static const std::unordered_map - create_ {{2, RdaStatusData::Create}, + create_ {{1, DigitalRadarData::Create}, + {2, RdaStatusData::Create}, {3, PerformanceMaintenanceData::Create}, {5, VolumeCoveragePatternData::Create}, {13, ClutterFilterBypassMap::Create}, diff --git a/wxdata/wxdata.cmake b/wxdata/wxdata.cmake index e2cdec59..c151b046 100644 --- a/wxdata/wxdata.cmake +++ b/wxdata/wxdata.cmake @@ -102,6 +102,7 @@ set(SRC_WSR88D source/scwx/wsr88d/ar2v_file.cpp source/scwx/wsr88d/wsr88d_types.cpp) set(HDR_WSR88D_RDA include/scwx/wsr88d/rda/clutter_filter_bypass_map.hpp include/scwx/wsr88d/rda/clutter_filter_map.hpp + include/scwx/wsr88d/rda/digital_radar_data.hpp include/scwx/wsr88d/rda/digital_radar_data_generic.hpp include/scwx/wsr88d/rda/level2_message.hpp include/scwx/wsr88d/rda/level2_message_factory.hpp @@ -113,6 +114,7 @@ set(HDR_WSR88D_RDA include/scwx/wsr88d/rda/clutter_filter_bypass_map.hpp include/scwx/wsr88d/rda/volume_coverage_pattern_data.hpp) set(SRC_WSR88D_RDA source/scwx/wsr88d/rda/clutter_filter_bypass_map.cpp source/scwx/wsr88d/rda/clutter_filter_map.cpp + source/scwx/wsr88d/rda/digital_radar_data.cpp source/scwx/wsr88d/rda/digital_radar_data_generic.cpp source/scwx/wsr88d/rda/level2_message.cpp source/scwx/wsr88d/rda/level2_message_factory.cpp From 664a8dcaee5fc42846ed8d49f69d8b4f4446d89d Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 19 Jan 2024 23:35:25 -0600 Subject: [PATCH 03/37] Display invalid elevation number for Message Type 31 --- wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp b/wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp index bad25049..6335a1e3 100644 --- a/wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp +++ b/wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp @@ -705,7 +705,7 @@ bool DigitalRadarDataGeneric::Parse(std::istream& is) } if (p->elevationNumber_ < 1 || p->elevationNumber_ > 32) { - logger_->warn("Invalid elevation number: ", p->elevationNumber_); + logger_->warn("Invalid elevation number: {}", p->elevationNumber_); messageValid = false; } if (p->dataBlockCount_ < 4 || p->dataBlockCount_ > 10) From dbda1172844df298ce3a92413c12eae1e00f2ebe Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 19 Jan 2024 23:35:44 -0600 Subject: [PATCH 04/37] Ignore invalid julian date in level 2 header --- wxdata/source/scwx/wsr88d/rda/level2_message_header.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/wxdata/source/scwx/wsr88d/rda/level2_message_header.cpp b/wxdata/source/scwx/wsr88d/rda/level2_message_header.cpp index 2d450bf4..5aabc20c 100644 --- a/wxdata/source/scwx/wsr88d/rda/level2_message_header.cpp +++ b/wxdata/source/scwx/wsr88d/rda/level2_message_header.cpp @@ -133,11 +133,6 @@ bool Level2MessageHeader::Parse(std::istream& is) logger_->warn("Invalid message size: {}", p->messageSize_); headerValid = false; } - if (p->julianDate_ < 1) - { - logger_->warn("Invalid date: {}", p->julianDate_); - headerValid = false; - } if (p->millisecondsOfDay_ > 86'399'999u) { logger_->warn("Invalid milliseconds: {}", p->millisecondsOfDay_); From 53717434a63432637f44ac0dd0569e2941ec1f04 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 19 Jan 2024 23:37:06 -0600 Subject: [PATCH 05/37] Ignore message segments when first segment doesn't start at 1 --- .../wsr88d/rda/level2_message_factory.cpp | 71 +++++++++++-------- 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/wxdata/source/scwx/wsr88d/rda/level2_message_factory.cpp b/wxdata/source/scwx/wsr88d/rda/level2_message_factory.cpp index 04c45676..3ba0ac7b 100644 --- a/wxdata/source/scwx/wsr88d/rda/level2_message_factory.cpp +++ b/wxdata/source/scwx/wsr88d/rda/level2_message_factory.cpp @@ -53,6 +53,7 @@ struct Level2MessageFactory::Context size_t bufferedSize_; util::vectorbuf messageBuffer_; std::istream messageBufferStream_; + bool bufferingData_ {false}; }; std::shared_ptr @@ -102,38 +103,51 @@ Level2MessageInfo Level2MessageFactory::Create(std::istream& is, // Estimate total message size ctx->messageData_.resize(dataSize * totalSegments); ctx->messageBufferStream_.clear(); - ctx->bufferedSize_ = 0; + ctx->bufferedSize_ = 0; + ctx->bufferingData_ = true; } - - if (ctx->messageData_.capacity() < ctx->bufferedSize_ + dataSize) + else if (!ctx->bufferingData_) { - logger_->debug("Bad size estimate, increasing size"); - - // Estimate remaining size - uint16_t remainingSegments = - std::max(totalSegments - segment + 1, 100u); - size_t remainingSize = remainingSegments * dataSize; - - ctx->messageData_.resize(ctx->bufferedSize_ + remainingSize); - } - - is.read(ctx->messageData_.data() + ctx->bufferedSize_, dataSize); - ctx->bufferedSize_ += dataSize; - - if (is.eof()) - { - logger_->warn("End of file reached trying to buffer message"); + // Segment number did not start at 1 + logger_->trace("Ignoring Segment {}/{}, did not start at 1", + segment, + totalSegments); info.messageValid = false; - ctx->messageData_.shrink_to_fit(); - ctx->bufferedSize_ = 0; } - else if (segment == totalSegments) - { - ctx->messageBuffer_.update_read_pointers(ctx->bufferedSize_); - header.set_message_size(static_cast( - ctx->bufferedSize_ / 2 + Level2MessageHeader::SIZE)); - messageStream = &ctx->messageBufferStream_; + if (ctx->bufferingData_) + { + if (ctx->messageData_.capacity() < ctx->bufferedSize_ + dataSize) + { + logger_->debug("Bad size estimate, increasing size"); + + // Estimate remaining size + uint16_t remainingSegments = + std::max(totalSegments - segment + 1, 100u); + size_t remainingSize = remainingSegments * dataSize; + + ctx->messageData_.resize(ctx->bufferedSize_ + remainingSize); + } + + is.read(ctx->messageData_.data() + ctx->bufferedSize_, dataSize); + ctx->bufferedSize_ += dataSize; + + if (is.eof()) + { + logger_->warn("End of file reached trying to buffer message"); + info.messageValid = false; + ctx->messageData_.shrink_to_fit(); + ctx->bufferedSize_ = 0; + ctx->bufferingData_ = false; + } + else if (segment == totalSegments) + { + ctx->messageBuffer_.update_read_pointers(ctx->bufferedSize_); + header.set_message_size(static_cast( + ctx->bufferedSize_ / 2 + Level2MessageHeader::SIZE)); + + messageStream = &ctx->messageBufferStream_; + } } } @@ -144,7 +158,8 @@ Level2MessageInfo Level2MessageFactory::Create(std::istream& is, ctx->messageData_.resize(0); ctx->messageData_.shrink_to_fit(); ctx->messageBufferStream_.clear(); - ctx->bufferedSize_ = 0; + ctx->bufferedSize_ = 0; + ctx->bufferingData_ = false; } } else if (info.headerValid) From 30de3a59fca2dde82ef209fba8744089c4635ded Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 22 Jan 2024 19:58:55 -0600 Subject: [PATCH 06/37] Refactor message ID enum DigitalRadarData to DigitalRadarDataGeneric --- wxdata/include/scwx/wsr88d/rda/types.hpp | 2 +- wxdata/source/scwx/wsr88d/ar2v_file.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/wxdata/include/scwx/wsr88d/rda/types.hpp b/wxdata/include/scwx/wsr88d/rda/types.hpp index 617054a2..549558e5 100644 --- a/wxdata/include/scwx/wsr88d/rda/types.hpp +++ b/wxdata/include/scwx/wsr88d/rda/types.hpp @@ -14,7 +14,7 @@ enum class MessageId : uint8_t VolumeCoveragePatternData = 5, ClutterFilterMap = 15, RdaAdaptationData = 18, - DigitalRadarData = 31 + DigitalRadarDataGeneric = 31 }; } // namespace rda diff --git a/wxdata/source/scwx/wsr88d/ar2v_file.cpp b/wxdata/source/scwx/wsr88d/ar2v_file.cpp index 12763f83..ee718c56 100644 --- a/wxdata/source/scwx/wsr88d/ar2v_file.cpp +++ b/wxdata/source/scwx/wsr88d/ar2v_file.cpp @@ -388,7 +388,7 @@ void Ar2vFileImpl::HandleMessage(std::shared_ptr& message) std::static_pointer_cast(message); break; - case static_cast(rda::MessageId::DigitalRadarData): + case static_cast(rda::MessageId::DigitalRadarDataGeneric): ProcessRadarData( std::static_pointer_cast(message)); break; From bf2569cb3e2f5cc1abbfdb59cd3f5c6b77d777e2 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 22 Jan 2024 19:59:18 -0600 Subject: [PATCH 07/37] Pass context shared pointer by reference --- wxdata/include/scwx/wsr88d/rda/level2_message_factory.hpp | 8 ++++---- wxdata/source/scwx/wsr88d/rda/level2_message_factory.cpp | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/wxdata/include/scwx/wsr88d/rda/level2_message_factory.hpp b/wxdata/include/scwx/wsr88d/rda/level2_message_factory.hpp index a6d87c54..7359e72b 100644 --- a/wxdata/include/scwx/wsr88d/rda/level2_message_factory.hpp +++ b/wxdata/include/scwx/wsr88d/rda/level2_message_factory.hpp @@ -27,18 +27,18 @@ private: explicit Level2MessageFactory() = delete; ~Level2MessageFactory() = delete; - Level2MessageFactory(const Level2MessageFactory&) = delete; + Level2MessageFactory(const Level2MessageFactory&) = delete; Level2MessageFactory& operator=(const Level2MessageFactory&) = delete; - Level2MessageFactory(Level2MessageFactory&&) noexcept = delete; + Level2MessageFactory(Level2MessageFactory&&) noexcept = delete; Level2MessageFactory& operator=(Level2MessageFactory&&) noexcept = delete; public: struct Context; static std::shared_ptr CreateContext(); - static Level2MessageInfo Create(std::istream& is, - std::shared_ptr ctx); + static Level2MessageInfo Create(std::istream& is, + std::shared_ptr& ctx); }; } // namespace rda diff --git a/wxdata/source/scwx/wsr88d/rda/level2_message_factory.cpp b/wxdata/source/scwx/wsr88d/rda/level2_message_factory.cpp index 3ba0ac7b..56141da9 100644 --- a/wxdata/source/scwx/wsr88d/rda/level2_message_factory.cpp +++ b/wxdata/source/scwx/wsr88d/rda/level2_message_factory.cpp @@ -62,8 +62,8 @@ Level2MessageFactory::CreateContext() return std::make_shared(); } -Level2MessageInfo Level2MessageFactory::Create(std::istream& is, - std::shared_ptr ctx) +Level2MessageInfo Level2MessageFactory::Create(std::istream& is, + std::shared_ptr& ctx) { Level2MessageInfo info; Level2MessageHeader header; From 233246a87e97e709eafcb805da0c909058483663 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 22 Jan 2024 20:05:02 -0600 Subject: [PATCH 08/37] Qualify names with std namespace in ar2v_file.cpp --- wxdata/source/scwx/wsr88d/ar2v_file.cpp | 48 +++++++++++++------------ 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/wxdata/source/scwx/wsr88d/ar2v_file.cpp b/wxdata/source/scwx/wsr88d/ar2v_file.cpp index ee718c56..07961562 100644 --- a/wxdata/source/scwx/wsr88d/ar2v_file.cpp +++ b/wxdata/source/scwx/wsr88d/ar2v_file.cpp @@ -83,12 +83,12 @@ Ar2vFile::~Ar2vFile() = default; Ar2vFile::Ar2vFile(Ar2vFile&&) noexcept = default; Ar2vFile& Ar2vFile::operator=(Ar2vFile&&) noexcept = default; -uint32_t Ar2vFile::julian_date() const +std::uint32_t Ar2vFile::julian_date() const { return p->julianDate_; } -uint32_t Ar2vFile::milliseconds() const +std::uint32_t Ar2vFile::milliseconds() const { return p->milliseconds_; } @@ -119,7 +119,7 @@ std::chrono::system_clock::time_point Ar2vFile::end_time() const return endTime; } -std::map> +std::map> Ar2vFile::radar_data() const { return p->radarData_; @@ -143,15 +143,15 @@ Ar2vFile::GetElevationScan(rda::DataBlockType dataBlockType, float elevationCut = 0.0f; std::vector elevationCuts; - uint16_t codedElevation = - static_cast(std::lroundf(elevation * scaleFactor)); + std::uint16_t codedElevation = + static_cast(std::lroundf(elevation * scaleFactor)); if (p->index_.contains(dataBlockType)) { auto scans = p->index_.at(dataBlockType); - uint16_t lowerBound = scans.cbegin()->first; - uint16_t upperBound = scans.crbegin()->first; + std::uint16_t lowerBound = scans.cbegin()->first; + std::uint16_t upperBound = scans.crbegin()->first; for (auto scan : scans) { @@ -167,10 +167,12 @@ Ar2vFile::GetElevationScan(rda::DataBlockType dataBlockType, elevationCuts.push_back(scan.first / scaleFactor); } - int32_t lowerDelta = std::abs(static_cast(codedElevation) - - static_cast(lowerBound)); - int32_t upperDelta = std::abs(static_cast(codedElevation) - - static_cast(upperBound)); + std::int32_t lowerDelta = + std::abs(static_cast(codedElevation) - + static_cast(lowerBound)); + std::int32_t upperDelta = + std::abs(static_cast(codedElevation) - + static_cast(upperBound)); if (lowerDelta < upperDelta) { @@ -257,17 +259,17 @@ bool Ar2vFile::LoadData(std::istream& is) return dataValid; } -size_t Ar2vFileImpl::DecompressLDMRecords(std::istream& is) +std::size_t Ar2vFileImpl::DecompressLDMRecords(std::istream& is) { logger_->debug("Decompressing LDM Records"); - size_t numRecords = 0; + std::size_t numRecords = 0; while (is.peek() != EOF) { std::streampos startPosition = is.tellg(); - int32_t controlWord = 0; - size_t recordSize; + std::int32_t controlWord = 0; + std::size_t recordSize; is.read(reinterpret_cast(&controlWord), 4); @@ -316,7 +318,7 @@ void Ar2vFileImpl::ParseLDMRecords() { logger_->debug("Parsing LDM Records"); - size_t count = 0; + std::size_t count = 0; for (auto it = rawRecords_.begin(); it != rawRecords_.end(); it++) { @@ -340,8 +342,8 @@ void Ar2vFileImpl::ParseLDMRecord(std::istream& is) while (!is.eof()) { - off_t offset = 0; - uint16_t nextSize = 0u; + off_t offset = 0; + std::uint16_t nextSize = 0u; do { is.read(reinterpret_cast(&nextSize), 2); @@ -383,12 +385,12 @@ void Ar2vFileImpl::HandleMessage(std::shared_ptr& message) { switch (message->header().message_type()) { - case static_cast(rda::MessageId::VolumeCoveragePatternData): + case static_cast(rda::MessageId::VolumeCoveragePatternData): vcpData_ = std::static_pointer_cast(message); break; - case static_cast(rda::MessageId::DigitalRadarDataGeneric): + case static_cast(rda::MessageId::DigitalRadarDataGeneric): ProcessRadarData( std::static_pointer_cast(message)); break; @@ -401,8 +403,8 @@ void Ar2vFileImpl::HandleMessage(std::shared_ptr& message) void Ar2vFileImpl::ProcessRadarData( const std::shared_ptr& message) { - uint16_t azimuthIndex = message->azimuth_number() - 1; - uint16_t elevationIndex = message->elevation_number() - 1; + std::uint16_t azimuthIndex = message->azimuth_number() - 1; + std::uint16_t elevationIndex = message->elevation_number() - 1; if (radarData_[elevationIndex] == nullptr) { @@ -424,7 +426,7 @@ void Ar2vFileImpl::IndexFile() for (auto& elevationCut : radarData_) { - uint16_t elevationAngle = + std::uint16_t elevationAngle = vcpData_->elevation_angle_raw(elevationCut.first); rda::WaveformType waveformType = vcpData_->waveform_type(elevationCut.first); From b1a4fd4381f7b3a9ed9ebd09d8e318afafeeb85b Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 22 Jan 2024 20:05:25 -0600 Subject: [PATCH 09/37] Use references in ar2v_file where appropriate --- wxdata/source/scwx/wsr88d/ar2v_file.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wxdata/source/scwx/wsr88d/ar2v_file.cpp b/wxdata/source/scwx/wsr88d/ar2v_file.cpp index 07961562..5c563eb2 100644 --- a/wxdata/source/scwx/wsr88d/ar2v_file.cpp +++ b/wxdata/source/scwx/wsr88d/ar2v_file.cpp @@ -148,12 +148,12 @@ Ar2vFile::GetElevationScan(rda::DataBlockType dataBlockType, if (p->index_.contains(dataBlockType)) { - auto scans = p->index_.at(dataBlockType); + auto& scans = p->index_.at(dataBlockType); std::uint16_t lowerBound = scans.cbegin()->first; std::uint16_t upperBound = scans.crbegin()->first; - for (auto scan : scans) + for (auto& scan : scans) { if (scan.first > lowerBound && scan.first <= codedElevation) { From a9e1998632e6f6d3feb6ac7f726c3c527853956b Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 22 Jan 2024 20:05:40 -0600 Subject: [PATCH 10/37] Add DigitalRadarData message ID 1 enumeration --- wxdata/include/scwx/wsr88d/rda/types.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wxdata/include/scwx/wsr88d/rda/types.hpp b/wxdata/include/scwx/wsr88d/rda/types.hpp index 549558e5..032cab77 100644 --- a/wxdata/include/scwx/wsr88d/rda/types.hpp +++ b/wxdata/include/scwx/wsr88d/rda/types.hpp @@ -7,8 +7,9 @@ namespace wsr88d namespace rda { -enum class MessageId : uint8_t +enum class MessageId : std::uint8_t { + DigitalRadarData = 1, RdaStatusData = 2, PerformanceMaintenanceData = 3, VolumeCoveragePatternData = 5, From 807d98d7ef9a8721a672b5037aaf4d7aa895f7c6 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 22 Jan 2024 21:36:47 -0600 Subject: [PATCH 11/37] Use strong-typed units for angles and ranges in Digital Radar Data Generic Format --- .../scwx/qt/view/level2_product_view.cpp | 30 +++++----- .../wsr88d/rda/digital_radar_data_generic.hpp | 55 ++++++++++--------- .../wsr88d/rda/digital_radar_data_generic.cpp | 17 +++--- 3 files changed, 54 insertions(+), 48 deletions(-) diff --git a/scwx-qt/source/scwx/qt/view/level2_product_view.cpp b/scwx-qt/source/scwx/qt/view/level2_product_view.cpp index 6c89ea6d..ad65d59a 100644 --- a/scwx-qt/source/scwx/qt/view/level2_product_view.cpp +++ b/scwx-qt/source/scwx/qt/view/level2_product_view.cpp @@ -98,12 +98,12 @@ public: std::vector dataMoments16_ {}; std::vector cfpMoments_ {}; - float latitude_; - float longitude_; - float elevationCut_; - std::vector elevationCuts_; - float range_; - uint16_t vcp_; + float latitude_; + float longitude_; + float elevationCut_; + std::vector elevationCuts_; + units::kilometers range_; + uint16_t vcp_; std::chrono::system_clock::time_point sweepTime_; @@ -212,7 +212,7 @@ float Level2ProductView::elevation() const float Level2ProductView::range() const { - return p->range_; + return p->range_.value(); } std::chrono::system_clock::time_point Level2ProductView::sweep_time() const @@ -747,7 +747,8 @@ void Level2ProductViewImpl::ComputeCoordinates( radials.end(), [&](std::uint32_t radial) { - const float angle = (*radarData)[radial]->azimuth_angle(); + const units::degrees angle = + (*radarData)[radial]->azimuth_angle(); std::for_each(std::execution::par_unseq, gates.begin(), @@ -765,7 +766,7 @@ void Level2ProductViewImpl::ComputeCoordinates( geodesic.Direct(radarLatitude, radarLongitude, - angle, + angle.value(), range, latitude, longitude); @@ -830,14 +831,15 @@ Level2ProductView::GetBinLevel(const common::Coordinate& coordinate) const radials.end(), [&](std::uint32_t i) { - bool found = false; - const float startAngle = (*radarData)[i]->azimuth_angle(); - const float nextAngle = + bool found = false; + const units::degrees startAngle = + (*radarData)[i]->azimuth_angle(); + const units::degrees nextAngle = (*radarData)[(i + 1) % numRadials]->azimuth_angle(); if (startAngle < nextAngle) { - if (startAngle <= azi1 && azi1 < nextAngle) + if (startAngle.value() <= azi1 && azi1 < nextAngle.value()) { found = true; } @@ -845,7 +847,7 @@ Level2ProductView::GetBinLevel(const common::Coordinate& coordinate) const else { // If the bin crosses 0/360 degrees, special handling is needed - if (startAngle <= azi1 || azi1 < nextAngle) + if (startAngle.value() <= azi1 || azi1 < nextAngle.value()) { found = true; } diff --git a/wxdata/include/scwx/wsr88d/rda/digital_radar_data_generic.hpp b/wxdata/include/scwx/wsr88d/rda/digital_radar_data_generic.hpp index 0e0afdc5..7543ec21 100644 --- a/wxdata/include/scwx/wsr88d/rda/digital_radar_data_generic.hpp +++ b/wxdata/include/scwx/wsr88d/rda/digital_radar_data_generic.hpp @@ -3,6 +3,9 @@ #include #include +#include +#include + namespace scwx { namespace wsr88d @@ -94,17 +97,17 @@ public: MomentDataBlock(MomentDataBlock&&) noexcept; MomentDataBlock& operator=(MomentDataBlock&&) noexcept; - uint16_t number_of_data_moment_gates() const; - float data_moment_range() const; - uint16_t data_moment_range_raw() const; - float data_moment_range_sample_interval() const; - uint16_t data_moment_range_sample_interval_raw() const; - float snr_threshold() const; - int16_t snr_threshold_raw() const; - uint8_t data_word_size() const; - float scale() const; - float offset() const; - const void* data_moments() const; + uint16_t number_of_data_moment_gates() const; + units::kilometers data_moment_range() const; + uint16_t data_moment_range_raw() const; + units::kilometers data_moment_range_sample_interval() const; + uint16_t data_moment_range_sample_interval_raw() const; + float snr_threshold() const; + int16_t snr_threshold_raw() const; + uint8_t data_word_size() const; + float scale() const; + float offset() const; + const void* data_moments() const; static std::shared_ptr Create(const std::string& dataBlockType, @@ -183,21 +186,21 @@ public: DigitalRadarDataGeneric(DigitalRadarDataGeneric&&) noexcept; DigitalRadarDataGeneric& operator=(DigitalRadarDataGeneric&&) noexcept; - std::string radar_identifier() const; - uint32_t collection_time() const; - uint16_t modified_julian_date() const; - uint16_t azimuth_number() const; - float azimuth_angle() const; - uint8_t compression_indicator() const; - uint16_t radial_length() const; - uint8_t azimuth_resolution_spacing() const; - uint8_t radial_status() const; - uint8_t elevation_number() const; - uint8_t cut_sector_number() const; - float elevation_angle() const; - uint8_t radial_spot_blanking_status() const; - uint8_t azimuth_indexing_mode() const; - uint16_t data_block_count() const; + std::string radar_identifier() const; + uint32_t collection_time() const; + uint16_t modified_julian_date() const; + uint16_t azimuth_number() const; + units::degrees azimuth_angle() const; + uint8_t compression_indicator() const; + uint16_t radial_length() const; + uint8_t azimuth_resolution_spacing() const; + uint8_t radial_status() const; + uint8_t elevation_number() const; + uint8_t cut_sector_number() const; + units::degrees elevation_angle() const; + uint8_t radial_spot_blanking_status() const; + uint8_t azimuth_indexing_mode() const; + uint16_t data_block_count() const; std::shared_ptr elevation_data_block() const; std::shared_ptr radial_data_block() const; diff --git a/wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp b/wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp index 6335a1e3..f3891abb 100644 --- a/wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp +++ b/wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp @@ -94,9 +94,9 @@ uint16_t MomentDataBlock::number_of_data_moment_gates() const return p->numberOfDataMomentGates_; } -float MomentDataBlock::data_moment_range() const +units::kilometers MomentDataBlock::data_moment_range() const { - return p->dataMomentRange_ * 0.001f; + return units::kilometers {p->dataMomentRange_ * 0.001f}; } uint16_t MomentDataBlock::data_moment_range_raw() const @@ -104,9 +104,10 @@ uint16_t MomentDataBlock::data_moment_range_raw() const return p->dataMomentRange_; } -float MomentDataBlock::data_moment_range_sample_interval() const +units::kilometers +MomentDataBlock::data_moment_range_sample_interval() const { - return p->dataMomentRangeSampleInterval_ * 0.001f; + return units::kilometers {p->dataMomentRangeSampleInterval_ * 0.001f}; } uint16_t MomentDataBlock::data_moment_range_sample_interval_raw() const @@ -575,9 +576,9 @@ uint16_t DigitalRadarDataGeneric::azimuth_number() const return p->azimuthNumber_; } -float DigitalRadarDataGeneric::azimuth_angle() const +units::degrees DigitalRadarDataGeneric::azimuth_angle() const { - return p->azimuthAngle_; + return units::degrees {p->azimuthAngle_}; } uint8_t DigitalRadarDataGeneric::compression_indicator() const @@ -610,9 +611,9 @@ uint8_t DigitalRadarDataGeneric::cut_sector_number() const return p->cutSectorNumber_; } -float DigitalRadarDataGeneric::elevation_angle() const +units::degrees DigitalRadarDataGeneric::elevation_angle() const { - return p->elevationAngle_; + return units::degrees {p->elevationAngle_}; } uint8_t DigitalRadarDataGeneric::radial_spot_blanking_status() const From 5058e3493c1bf1353ee294fae9e4c62d7390e1cc Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 22 Jan 2024 21:37:33 -0600 Subject: [PATCH 12/37] Add scaled values for ranges and angles in Digital Radar Data --- .../scwx/wsr88d/rda/digital_radar_data.hpp | 59 +++++++++++-------- .../scwx/wsr88d/rda/digital_radar_data.cpp | 51 ++++++++++++++-- 2 files changed, 79 insertions(+), 31 deletions(-) diff --git a/wxdata/include/scwx/wsr88d/rda/digital_radar_data.hpp b/wxdata/include/scwx/wsr88d/rda/digital_radar_data.hpp index 42acb84b..200cfbe9 100644 --- a/wxdata/include/scwx/wsr88d/rda/digital_radar_data.hpp +++ b/wxdata/include/scwx/wsr88d/rda/digital_radar_data.hpp @@ -2,6 +2,9 @@ #include +#include +#include + namespace scwx { namespace wsr88d @@ -21,31 +24,37 @@ public: DigitalRadarData(DigitalRadarData&&) noexcept; DigitalRadarData& operator=(DigitalRadarData&&) noexcept; - std::uint32_t collection_time() const; - std::uint16_t modified_julian_date() const; - std::uint16_t unambiguous_range() const; - std::uint16_t azimuth_angle() const; - std::uint16_t azimuth_number() const; - std::uint16_t radial_status() const; - std::uint16_t elevation_angle() const; - std::uint16_t elevation_number() const; - std::uint16_t surveillance_range() const; - std::uint16_t doppler_range() const; - std::uint16_t surveillance_range_sample_interval() const; - std::uint16_t doppler_range_sample_interval() const; - std::uint16_t number_of_surveillance_bins() const; - std::uint16_t number_of_doppler_bins() const; - std::uint16_t cut_sector_number() const; - float calibration_constant() const; - std::uint16_t surveillance_pointer() const; - std::uint16_t velocity_pointer() const; - std::uint16_t spectral_width_pointer() const; - std::uint16_t doppler_velocity_resolution() const; - std::uint16_t volume_coverage_pattern_number() const; - std::uint16_t nyquist_velocity() const; - std::uint16_t atmos() const; - std::uint16_t tover() const; - std::uint16_t radial_spot_blanking_status() const; + std::uint32_t collection_time() const; + std::uint16_t modified_julian_date() const; + std::uint16_t unambiguous_range() const; + std::uint16_t azimuth_angle_raw() const; + units::degrees azimuth_angle() const; + std::uint16_t azimuth_number() const; + std::uint16_t radial_status() const; + std::uint16_t elevation_angle_raw() const; + units::degrees elevation_angle() const; + std::uint16_t elevation_number() const; + std::uint16_t surveillance_range_raw() const; + units::kilometers surveillance_range() const; + std::uint16_t doppler_range_raw() const; + units::kilometers doppler_range() const; + std::uint16_t surveillance_range_sample_interval_raw() const; + units::kilometers surveillance_range_sample_interval() const; + std::uint16_t doppler_range_sample_interval_raw() const; + units::kilometers doppler_range_sample_interval() const; + std::uint16_t number_of_surveillance_bins() const; + std::uint16_t number_of_doppler_bins() const; + std::uint16_t cut_sector_number() const; + float calibration_constant() const; + std::uint16_t surveillance_pointer() const; + std::uint16_t velocity_pointer() const; + std::uint16_t spectral_width_pointer() const; + std::uint16_t doppler_velocity_resolution() const; + std::uint16_t volume_coverage_pattern_number() const; + std::uint16_t nyquist_velocity() const; + std::uint16_t atmos() const; + std::uint16_t tover() const; + std::uint16_t radial_spot_blanking_status() const; bool Parse(std::istream& is); diff --git a/wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp b/wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp index f894458e..b84c6aff 100644 --- a/wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp +++ b/wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp @@ -11,6 +11,12 @@ namespace rda static const std::string logPrefix_ = "scwx::wsr88d::rda::digital_radar_data"; static const auto logger_ = util::Logger::Create(logPrefix_); +// Table III-A Angle Data Format +constexpr float kAngleDataScale = 0.0054931640625f; + +// Table III-B Range Format +constexpr float kRangeScale = 0.001f; + class DigitalRadarData::Impl { public: @@ -73,11 +79,16 @@ std::uint16_t DigitalRadarData::unambiguous_range() const return p->unambiguousRange_; } -std::uint16_t DigitalRadarData::azimuth_angle() const +std::uint16_t DigitalRadarData::azimuth_angle_raw() const { return p->azimuthAngle_; } +units::degrees DigitalRadarData::azimuth_angle() const +{ + return units::degrees {p->azimuthAngle_ * kAngleDataScale}; +} + std::uint16_t DigitalRadarData::azimuth_number() const { return p->azimuthNumber_; @@ -88,36 +99,64 @@ std::uint16_t DigitalRadarData::radial_status() const return p->radialStatus_; } -std::uint16_t DigitalRadarData::elevation_angle() const +std::uint16_t DigitalRadarData::elevation_angle_raw() const { return p->elevationAngle_; } +units::degrees DigitalRadarData::elevation_angle() const +{ + return units::degrees {p->elevationAngle_ * kAngleDataScale}; +} + std::uint16_t DigitalRadarData::elevation_number() const { return p->elevationNumber_; } -std::uint16_t DigitalRadarData::surveillance_range() const +std::uint16_t DigitalRadarData::surveillance_range_raw() const { return p->surveillanceRange_; } -std::uint16_t DigitalRadarData::doppler_range() const +units::kilometers DigitalRadarData::surveillance_range() const +{ + return units::kilometers {p->surveillanceRange_ * kRangeScale}; +} + +std::uint16_t DigitalRadarData::doppler_range_raw() const { return p->dopplerRange_; } -std::uint16_t DigitalRadarData::surveillance_range_sample_interval() const +units::kilometers DigitalRadarData::doppler_range() const +{ + return units::kilometers {p->dopplerRange_ * kRangeScale}; +} + +std::uint16_t DigitalRadarData::surveillance_range_sample_interval_raw() const { return p->surveillanceRangeSampleInterval_; } -std::uint16_t DigitalRadarData::doppler_range_sample_interval() const +units::kilometers +DigitalRadarData::surveillance_range_sample_interval() const +{ + return units::kilometers {p->surveillanceRangeSampleInterval_ * + kRangeScale}; +} + +std::uint16_t DigitalRadarData::doppler_range_sample_interval_raw() const { return p->dopplerRangeSampleInterval_; } +units::kilometers DigitalRadarData::doppler_range_sample_interval() const +{ + return units::kilometers {p->dopplerRangeSampleInterval_ * + kRangeScale}; +} + std::uint16_t DigitalRadarData::number_of_surveillance_bins() const { return p->numberOfSurveillanceBins_; From 26aeeb18ced134c5ea597304142e7b2b57edbc20 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 22 Jan 2024 21:38:10 -0600 Subject: [PATCH 13/37] Make radialData a reference in level 2 product view --- scwx-qt/source/scwx/qt/view/level2_product_view.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scwx-qt/source/scwx/qt/view/level2_product_view.cpp b/scwx-qt/source/scwx/qt/view/level2_product_view.cpp index ad65d59a..469894d1 100644 --- a/scwx-qt/source/scwx/qt/view/level2_product_view.cpp +++ b/scwx-qt/source/scwx/qt/view/level2_product_view.cpp @@ -530,7 +530,7 @@ void Level2ProductView::ComputeSweep() for (auto& radialPair : *radarData) { uint16_t radial = radialPair.first; - auto radialData = radialPair.second; + auto& radialData = radialPair.second; auto momentData = radialData->moment_data_block(p->dataBlockType_); if (momentData0->data_word_size() != momentData->data_word_size()) From b524b6f53d00f06f82fc98f41bb5310c22472e30 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 22 Jan 2024 21:52:42 -0600 Subject: [PATCH 14/37] Refactor rda/types.hpp to rda/rda_types.hpp --- wxdata/include/scwx/wsr88d/rda/{types.hpp => rda_types.hpp} | 0 wxdata/source/scwx/wsr88d/ar2v_file.cpp | 2 +- wxdata/wxdata.cmake | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename wxdata/include/scwx/wsr88d/rda/{types.hpp => rda_types.hpp} (100%) diff --git a/wxdata/include/scwx/wsr88d/rda/types.hpp b/wxdata/include/scwx/wsr88d/rda/rda_types.hpp similarity index 100% rename from wxdata/include/scwx/wsr88d/rda/types.hpp rename to wxdata/include/scwx/wsr88d/rda/rda_types.hpp diff --git a/wxdata/source/scwx/wsr88d/ar2v_file.cpp b/wxdata/source/scwx/wsr88d/ar2v_file.cpp index 5c563eb2..23c76e03 100644 --- a/wxdata/source/scwx/wsr88d/ar2v_file.cpp +++ b/wxdata/source/scwx/wsr88d/ar2v_file.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include #include diff --git a/wxdata/wxdata.cmake b/wxdata/wxdata.cmake index c151b046..047ff40d 100644 --- a/wxdata/wxdata.cmake +++ b/wxdata/wxdata.cmake @@ -110,7 +110,7 @@ set(HDR_WSR88D_RDA include/scwx/wsr88d/rda/clutter_filter_bypass_map.hpp include/scwx/wsr88d/rda/performance_maintenance_data.hpp include/scwx/wsr88d/rda/rda_adaptation_data.hpp include/scwx/wsr88d/rda/rda_status_data.hpp - include/scwx/wsr88d/rda/types.hpp + include/scwx/wsr88d/rda/rda_types.hpp include/scwx/wsr88d/rda/volume_coverage_pattern_data.hpp) set(SRC_WSR88D_RDA source/scwx/wsr88d/rda/clutter_filter_bypass_map.cpp source/scwx/wsr88d/rda/clutter_filter_map.cpp From b8b0812ce621dbc09d1984ce11be73524fcd9259 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 22 Jan 2024 22:22:47 -0600 Subject: [PATCH 15/37] Significant refactor of Digital Radar Data Generic with nested classes --- .../scwx/qt/view/level2_product_view.cpp | 5 +- .../wsr88d/rda/digital_radar_data_generic.hpp | 143 ++++--- .../wsr88d/rda/digital_radar_data_generic.cpp | 399 ++++++++---------- 3 files changed, 254 insertions(+), 293 deletions(-) diff --git a/scwx-qt/source/scwx/qt/view/level2_product_view.cpp b/scwx-qt/source/scwx/qt/view/level2_product_view.cpp index 469894d1..51751609 100644 --- a/scwx-qt/source/scwx/qt/view/level2_product_view.cpp +++ b/scwx-qt/source/scwx/qt/view/level2_product_view.cpp @@ -89,8 +89,9 @@ public: float selectedElevation_; - std::shared_ptr elevationScan_; - std::shared_ptr momentDataBlock0_; + std::shared_ptr elevationScan_; + std::shared_ptr + momentDataBlock0_; std::vector coordinates_ {}; std::vector vertices_ {}; diff --git a/wxdata/include/scwx/wsr88d/rda/digital_radar_data_generic.hpp b/wxdata/include/scwx/wsr88d/rda/digital_radar_data_generic.hpp index 7543ec21..e86e6534 100644 --- a/wxdata/include/scwx/wsr88d/rda/digital_radar_data_generic.hpp +++ b/wxdata/include/scwx/wsr88d/rda/digital_radar_data_generic.hpp @@ -31,24 +31,66 @@ typedef util:: Iterator MomentDataBlockTypeIterator; -class DataBlockImpl; -class ElevationDataBlockImpl; -class MomentDataBlockImpl; -class RadialDataBlockImpl; -class VolumeDataBlockImpl; - class DigitalRadarDataGeneric; -class DigitalRadarDataGenericImpl; -typedef std::map> +typedef std::map> ElevationScan; -class DataBlock +class DigitalRadarDataGeneric : public Level2Message +{ +public: + class DataBlock; + class ElevationDataBlock; + class MomentDataBlock; + class RadialDataBlock; + class VolumeDataBlock; + + explicit DigitalRadarDataGeneric(); + ~DigitalRadarDataGeneric(); + + DigitalRadarDataGeneric(const DigitalRadarDataGeneric&) = delete; + DigitalRadarDataGeneric& operator=(const DigitalRadarDataGeneric&) = delete; + + DigitalRadarDataGeneric(DigitalRadarDataGeneric&&) noexcept; + DigitalRadarDataGeneric& operator=(DigitalRadarDataGeneric&&) noexcept; + + std::string radar_identifier() const; + std::uint32_t collection_time() const; + std::uint16_t modified_julian_date() const; + std::uint16_t azimuth_number() const; + units::degrees azimuth_angle() const; + std::uint8_t compression_indicator() const; + std::uint16_t radial_length() const; + std::uint8_t azimuth_resolution_spacing() const; + std::uint8_t radial_status() const; + std::uint8_t elevation_number() const; + std::uint8_t cut_sector_number() const; + units::degrees elevation_angle() const; + std::uint8_t radial_spot_blanking_status() const; + std::uint8_t azimuth_indexing_mode() const; + std::uint16_t data_block_count() const; + + std::shared_ptr elevation_data_block() const; + std::shared_ptr radial_data_block() const; + std::shared_ptr volume_data_block() const; + std::shared_ptr moment_data_block(DataBlockType type) const; + + bool Parse(std::istream& is); + + static std::shared_ptr + Create(Level2MessageHeader&& header, std::istream& is); + +private: + class Impl; + std::unique_ptr p; +}; + +class DigitalRadarDataGeneric::DataBlock { protected: explicit DataBlock(const std::string& dataBlockType, const std::string& dataName); - ~DataBlock(); + virtual ~DataBlock(); DataBlock(const DataBlock&) = delete; DataBlock& operator=(const DataBlock&) = delete; @@ -57,10 +99,11 @@ protected: DataBlock& operator=(DataBlock&&) noexcept; private: - std::unique_ptr p; + class Impl; + std::unique_ptr p; }; -class ElevationDataBlock : public DataBlock +class DigitalRadarDataGeneric::ElevationDataBlock : public DataBlock { public: explicit ElevationDataBlock(const std::string& dataBlockType, @@ -79,12 +122,13 @@ public: std::istream& is); private: - std::unique_ptr p; + class Impl; + std::unique_ptr p; bool Parse(std::istream& is); }; -class MomentDataBlock : public DataBlock +class DigitalRadarDataGeneric::MomentDataBlock : public DataBlock { public: explicit MomentDataBlock(const std::string& dataBlockType, @@ -97,14 +141,14 @@ public: MomentDataBlock(MomentDataBlock&&) noexcept; MomentDataBlock& operator=(MomentDataBlock&&) noexcept; - uint16_t number_of_data_moment_gates() const; + std::uint16_t number_of_data_moment_gates() const; units::kilometers data_moment_range() const; - uint16_t data_moment_range_raw() const; + std::uint16_t data_moment_range_raw() const; units::kilometers data_moment_range_sample_interval() const; - uint16_t data_moment_range_sample_interval_raw() const; + std::uint16_t data_moment_range_sample_interval_raw() const; float snr_threshold() const; - int16_t snr_threshold_raw() const; - uint8_t data_word_size() const; + std::int16_t snr_threshold_raw() const; + std::uint8_t data_word_size() const; float scale() const; float offset() const; const void* data_moments() const; @@ -115,12 +159,13 @@ public: std::istream& is); private: - std::unique_ptr p; + class Impl; + std::unique_ptr p; bool Parse(std::istream& is); }; -class RadialDataBlock : public DataBlock +class DigitalRadarDataGeneric::RadialDataBlock : public DataBlock { public: explicit RadialDataBlock(const std::string& dataBlockType, @@ -141,12 +186,13 @@ public: std::istream& is); private: - std::unique_ptr p; + class Impl; + std::unique_ptr p; bool Parse(std::istream& is); }; -class VolumeDataBlock : public DataBlock +class DigitalRadarDataGeneric::VolumeDataBlock : public DataBlock { public: explicit VolumeDataBlock(const std::string& dataBlockType, @@ -159,9 +205,9 @@ public: VolumeDataBlock(VolumeDataBlock&&) noexcept; VolumeDataBlock& operator=(VolumeDataBlock&&) noexcept; - float latitude() const; - float longitude() const; - uint16_t volume_coverage_pattern_number() const; + float latitude() const; + float longitude() const; + std::uint16_t volume_coverage_pattern_number() const; static std::shared_ptr Create(const std::string& dataBlockType, @@ -169,53 +215,12 @@ public: std::istream& is); private: - std::unique_ptr p; + class Impl; + std::unique_ptr p; bool Parse(std::istream& is); }; -class DigitalRadarDataGeneric : public Level2Message -{ -public: - explicit DigitalRadarDataGeneric(); - ~DigitalRadarDataGeneric(); - - DigitalRadarDataGeneric(const DigitalRadarDataGeneric&) = delete; - DigitalRadarDataGeneric& operator=(const DigitalRadarDataGeneric&) = delete; - - DigitalRadarDataGeneric(DigitalRadarDataGeneric&&) noexcept; - DigitalRadarDataGeneric& operator=(DigitalRadarDataGeneric&&) noexcept; - - std::string radar_identifier() const; - uint32_t collection_time() const; - uint16_t modified_julian_date() const; - uint16_t azimuth_number() const; - units::degrees azimuth_angle() const; - uint8_t compression_indicator() const; - uint16_t radial_length() const; - uint8_t azimuth_resolution_spacing() const; - uint8_t radial_status() const; - uint8_t elevation_number() const; - uint8_t cut_sector_number() const; - units::degrees elevation_angle() const; - uint8_t radial_spot_blanking_status() const; - uint8_t azimuth_indexing_mode() const; - uint16_t data_block_count() const; - - std::shared_ptr elevation_data_block() const; - std::shared_ptr radial_data_block() const; - std::shared_ptr volume_data_block() const; - std::shared_ptr moment_data_block(DataBlockType type) const; - - bool Parse(std::istream& is); - - static std::shared_ptr - Create(Level2MessageHeader&& header, std::istream& is); - -private: - std::unique_ptr p; -}; - } // namespace rda } // namespace wsr88d } // namespace scwx diff --git a/wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp b/wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp index f3891abb..ca27b181 100644 --- a/wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp +++ b/wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp @@ -24,11 +24,11 @@ static const std::unordered_map strToDataBlock_ { {"RHO", DataBlockType::MomentRho}, {"CFP", DataBlockType::MomentCfp}}; -class DataBlockImpl +class DigitalRadarDataGeneric::DataBlock::Impl { public: - explicit DataBlockImpl(const std::string& dataBlockType, - const std::string& dataName) : + explicit Impl(const std::string& dataBlockType, + const std::string& dataName) : dataBlockType_ {dataBlockType}, dataName_ {dataName} { } @@ -37,110 +37,106 @@ public: std::string dataName_; }; -DataBlock::DataBlock(const std::string& dataBlockType, - const std::string& dataName) : - p(std::make_unique(dataBlockType, dataName)) +DigitalRadarDataGeneric::DataBlock::DataBlock(const std::string& dataBlockType, + const std::string& dataName) : + p(std::make_unique(dataBlockType, dataName)) { } -DataBlock::~DataBlock() = default; +DigitalRadarDataGeneric::DataBlock::~DataBlock() = default; -DataBlock::DataBlock(DataBlock&&) noexcept = default; -DataBlock& DataBlock::operator=(DataBlock&&) noexcept = default; +DigitalRadarDataGeneric::DataBlock::DataBlock(DataBlock&&) noexcept = default; +DigitalRadarDataGeneric::DataBlock& +DigitalRadarDataGeneric::DataBlock::operator=(DataBlock&&) noexcept = default; -class MomentDataBlockImpl +class DigitalRadarDataGeneric::MomentDataBlock::Impl { public: - explicit MomentDataBlockImpl() : - numberOfDataMomentGates_ {0}, - dataMomentRange_ {0}, - dataMomentRangeSampleInterval_ {0}, - tover_ {0}, - snrThreshold_ {0}, - controlFlags_ {0}, - dataWordSize_ {0}, - scale_ {0.0f}, - offset_ {0.0f} - { - } + explicit Impl() {} - uint16_t numberOfDataMomentGates_; - uint16_t dataMomentRange_; - uint16_t dataMomentRangeSampleInterval_; - uint16_t tover_; - int16_t snrThreshold_; - uint8_t controlFlags_; - uint8_t dataWordSize_; - float scale_; - float offset_; + std::uint16_t numberOfDataMomentGates_ {0}; + std::uint16_t dataMomentRange_ {0}; + std::uint16_t dataMomentRangeSampleInterval_ {0}; + std::uint16_t tover_ {0}; + std::int16_t snrThreshold_ {0}; + std::uint8_t controlFlags_ {0}; + std::uint8_t dataWordSize_ {0}; + float scale_ {0.0f}; + float offset_ {0.0f}; - std::vector momentGates8_; - std::vector momentGates16_; + std::vector momentGates8_ {}; + std::vector momentGates16_ {}; }; -MomentDataBlock::MomentDataBlock(const std::string& dataBlockType, - const std::string& dataName) : - DataBlock(dataBlockType, dataName), - p(std::make_unique()) +DigitalRadarDataGeneric::MomentDataBlock::MomentDataBlock( + const std::string& dataBlockType, const std::string& dataName) : + DataBlock(dataBlockType, dataName), p(std::make_unique()) { } -MomentDataBlock::~MomentDataBlock() = default; +DigitalRadarDataGeneric::MomentDataBlock::~MomentDataBlock() = default; -MomentDataBlock::MomentDataBlock(MomentDataBlock&&) noexcept = default; -MomentDataBlock& -MomentDataBlock::operator=(MomentDataBlock&&) noexcept = default; +DigitalRadarDataGeneric::MomentDataBlock::MomentDataBlock( + MomentDataBlock&&) noexcept = default; +DigitalRadarDataGeneric::MomentDataBlock& +DigitalRadarDataGeneric::MomentDataBlock::operator=( + MomentDataBlock&&) noexcept = default; -uint16_t MomentDataBlock::number_of_data_moment_gates() const +std::uint16_t +DigitalRadarDataGeneric::MomentDataBlock::number_of_data_moment_gates() const { return p->numberOfDataMomentGates_; } -units::kilometers MomentDataBlock::data_moment_range() const +units::kilometers +DigitalRadarDataGeneric::MomentDataBlock::data_moment_range() const { return units::kilometers {p->dataMomentRange_ * 0.001f}; } -uint16_t MomentDataBlock::data_moment_range_raw() const +std::uint16_t +DigitalRadarDataGeneric::MomentDataBlock::data_moment_range_raw() const { return p->dataMomentRange_; } units::kilometers -MomentDataBlock::data_moment_range_sample_interval() const +DigitalRadarDataGeneric::MomentDataBlock::data_moment_range_sample_interval() + const { return units::kilometers {p->dataMomentRangeSampleInterval_ * 0.001f}; } -uint16_t MomentDataBlock::data_moment_range_sample_interval_raw() const +std::uint16_t DigitalRadarDataGeneric::MomentDataBlock:: + data_moment_range_sample_interval_raw() const { return p->dataMomentRangeSampleInterval_; } -float MomentDataBlock::snr_threshold() const +float DigitalRadarDataGeneric::MomentDataBlock::snr_threshold() const { return p->snrThreshold_ * 0.1f; } -int16_t MomentDataBlock::snr_threshold_raw() const +std::int16_t DigitalRadarDataGeneric::MomentDataBlock::snr_threshold_raw() const { return p->snrThreshold_; } -uint8_t MomentDataBlock::data_word_size() const +std::uint8_t DigitalRadarDataGeneric::MomentDataBlock::data_word_size() const { return p->dataWordSize_; } -float MomentDataBlock::scale() const +float DigitalRadarDataGeneric::MomentDataBlock::scale() const { return p->scale_; } -float MomentDataBlock::offset() const +float DigitalRadarDataGeneric::MomentDataBlock::offset() const { return p->offset_; } -const void* MomentDataBlock::data_moments() const +const void* DigitalRadarDataGeneric::MomentDataBlock::data_moments() const { const void* dataMoments; @@ -160,10 +156,11 @@ const void* MomentDataBlock::data_moments() const return dataMoments; } -std::shared_ptr -MomentDataBlock::Create(const std::string& dataBlockType, - const std::string& dataName, - std::istream& is) +std::shared_ptr +DigitalRadarDataGeneric::MomentDataBlock::Create( + const std::string& dataBlockType, + const std::string& dataName, + std::istream& is) { std::shared_ptr p = std::make_shared(dataBlockType, dataName); @@ -176,7 +173,7 @@ MomentDataBlock::Create(const std::string& dataBlockType, return p; } -bool MomentDataBlock::Parse(std::istream& is) +bool DigitalRadarDataGeneric::MomentDataBlock::Parse(std::istream& is) { bool dataBlockValid = true; @@ -231,74 +228,61 @@ bool MomentDataBlock::Parse(std::istream& is) return dataBlockValid; } -class VolumeDataBlockImpl +class DigitalRadarDataGeneric::VolumeDataBlock::Impl { public: - explicit VolumeDataBlockImpl() : - lrtup_ {0}, - versionNumberMajor_ {0}, - versionNumberMinor_ {0}, - latitude_ {0.0f}, - longitude_ {0.0f}, - siteHeight_ {0}, - feedhornHeight_ {0}, - calibrationConstant_ {0.0f}, - horizontaShvTxPower_ {0.0f}, - verticalShvTxPower_ {0.0f}, - systemDifferentialReflectivity_ {0.0f}, - initialSystemDifferentialPhase_ {0.0f}, - volumeCoveragePatternNumber_ {0}, - processingStatus_ {0} - { - } + explicit Impl() {} - uint16_t lrtup_; - uint8_t versionNumberMajor_; - uint8_t versionNumberMinor_; - float latitude_; - float longitude_; - int16_t siteHeight_; - uint16_t feedhornHeight_; - float calibrationConstant_; - float horizontaShvTxPower_; - float verticalShvTxPower_; - float systemDifferentialReflectivity_; - float initialSystemDifferentialPhase_; - uint16_t volumeCoveragePatternNumber_; - uint16_t processingStatus_; + std::uint16_t lrtup_ {0}; + std::uint8_t versionNumberMajor_ {0}; + std::uint8_t versionNumberMinor_ {0}; + float latitude_ {0.0f}; + float longitude_ {0.0f}; + std::int16_t siteHeight_ {0}; + std::uint16_t feedhornHeight_ {0}; + float calibrationConstant_ {0.0f}; + float horizontaShvTxPower_ {0.0f}; + float verticalShvTxPower_ {0.0f}; + float systemDifferentialReflectivity_ {0.0f}; + float initialSystemDifferentialPhase_ {0.0f}; + std::uint16_t volumeCoveragePatternNumber_ {0}; + std::uint16_t processingStatus_ {0}; }; -VolumeDataBlock::VolumeDataBlock(const std::string& dataBlockType, - const std::string& dataName) : - DataBlock(dataBlockType, dataName), - p(std::make_unique()) +DigitalRadarDataGeneric::VolumeDataBlock::VolumeDataBlock( + const std::string& dataBlockType, const std::string& dataName) : + DataBlock(dataBlockType, dataName), p(std::make_unique()) { } -VolumeDataBlock::~VolumeDataBlock() = default; +DigitalRadarDataGeneric::VolumeDataBlock::~VolumeDataBlock() = default; -VolumeDataBlock::VolumeDataBlock(VolumeDataBlock&&) noexcept = default; -VolumeDataBlock& -VolumeDataBlock::operator=(VolumeDataBlock&&) noexcept = default; +DigitalRadarDataGeneric::VolumeDataBlock::VolumeDataBlock( + VolumeDataBlock&&) noexcept = default; +DigitalRadarDataGeneric::VolumeDataBlock& +DigitalRadarDataGeneric::VolumeDataBlock::operator=( + VolumeDataBlock&&) noexcept = default; -float VolumeDataBlock::latitude() const +float DigitalRadarDataGeneric::VolumeDataBlock::latitude() const { return p->latitude_; } -float VolumeDataBlock::longitude() const +float DigitalRadarDataGeneric::VolumeDataBlock::longitude() const { return p->longitude_; } -uint16_t VolumeDataBlock::volume_coverage_pattern_number() const +std::uint16_t +DigitalRadarDataGeneric::VolumeDataBlock::volume_coverage_pattern_number() const { return p->volumeCoveragePatternNumber_; } -std::shared_ptr -VolumeDataBlock::Create(const std::string& dataBlockType, - const std::string& dataName, - std::istream& is) +std::shared_ptr +DigitalRadarDataGeneric::VolumeDataBlock::Create( + const std::string& dataBlockType, + const std::string& dataName, + std::istream& is) { std::shared_ptr p = std::make_shared(dataBlockType, dataName); @@ -311,7 +295,7 @@ VolumeDataBlock::Create(const std::string& dataBlockType, return p; } -bool VolumeDataBlock::Parse(std::istream& is) +bool DigitalRadarDataGeneric::VolumeDataBlock::Parse(std::istream& is) { bool dataBlockValid = true; @@ -351,35 +335,34 @@ bool VolumeDataBlock::Parse(std::istream& is) return dataBlockValid; } -class ElevationDataBlockImpl +class DigitalRadarDataGeneric::ElevationDataBlock::Impl { public: - explicit ElevationDataBlockImpl() : - lrtup_ {0}, atmos_ {0}, calibrationConstant_ {0.0f} - { - } + explicit Impl() {} - uint16_t lrtup_; - int16_t atmos_; - float calibrationConstant_; + std::uint16_t lrtup_ {0}; + std::int16_t atmos_ {0}; + float calibrationConstant_ {0.0f}; }; -ElevationDataBlock::ElevationDataBlock(const std::string& dataBlockType, - const std::string& dataName) : - DataBlock(dataBlockType, dataName), - p(std::make_unique()) +DigitalRadarDataGeneric::ElevationDataBlock::ElevationDataBlock( + const std::string& dataBlockType, const std::string& dataName) : + DataBlock(dataBlockType, dataName), p(std::make_unique()) { } -ElevationDataBlock::~ElevationDataBlock() = default; +DigitalRadarDataGeneric::ElevationDataBlock::~ElevationDataBlock() = default; -ElevationDataBlock::ElevationDataBlock(ElevationDataBlock&&) noexcept = default; -ElevationDataBlock& -ElevationDataBlock::operator=(ElevationDataBlock&&) noexcept = default; +DigitalRadarDataGeneric::ElevationDataBlock::ElevationDataBlock( + ElevationDataBlock&&) noexcept = default; +DigitalRadarDataGeneric::ElevationDataBlock& +DigitalRadarDataGeneric::ElevationDataBlock::operator=( + ElevationDataBlock&&) noexcept = default; -std::shared_ptr -ElevationDataBlock::Create(const std::string& dataBlockType, - const std::string& dataName, - std::istream& is) +std::shared_ptr +DigitalRadarDataGeneric::ElevationDataBlock::Create( + const std::string& dataBlockType, + const std::string& dataName, + std::istream& is) { std::shared_ptr p = std::make_shared(dataBlockType, dataName); @@ -392,7 +375,7 @@ ElevationDataBlock::Create(const std::string& dataBlockType, return p; } -bool ElevationDataBlock::Parse(std::istream& is) +bool DigitalRadarDataGeneric::ElevationDataBlock::Parse(std::istream& is) { bool dataBlockValid = true; @@ -407,52 +390,44 @@ bool ElevationDataBlock::Parse(std::istream& is) return dataBlockValid; } -class RadialDataBlockImpl +class DigitalRadarDataGeneric::RadialDataBlock::Impl { public: - explicit RadialDataBlockImpl() : - lrtup_ {0}, - unambigiousRange_ {0}, - noiseLevelHorizontal_ {0.0f}, - noiseLevelVertical_ {0.0f}, - nyquistVelocity_ {0}, - radialFlags_ {0}, - calibrationConstantHorizontal_ {0.0f}, - calibrationConstantVertical_ {0.0f} - { - } + explicit Impl() {} - uint16_t lrtup_; - uint16_t unambigiousRange_; - float noiseLevelHorizontal_; - float noiseLevelVertical_; - uint16_t nyquistVelocity_; - uint16_t radialFlags_; - float calibrationConstantHorizontal_; - float calibrationConstantVertical_; + std::uint16_t lrtup_ {0}; + std::uint16_t unambigiousRange_ {0}; + float noiseLevelHorizontal_ {0.0f}; + float noiseLevelVertical_ {0.0f}; + std::uint16_t nyquistVelocity_ {0}; + std::uint16_t radialFlags_ {0}; + float calibrationConstantHorizontal_ {0.0f}; + float calibrationConstantVertical_ {0.0f}; }; -RadialDataBlock::RadialDataBlock(const std::string& dataBlockType, - const std::string& dataName) : - DataBlock(dataBlockType, dataName), - p(std::make_unique()) +DigitalRadarDataGeneric::RadialDataBlock::RadialDataBlock( + const std::string& dataBlockType, const std::string& dataName) : + DataBlock(dataBlockType, dataName), p(std::make_unique()) { } -RadialDataBlock::~RadialDataBlock() = default; +DigitalRadarDataGeneric::RadialDataBlock::~RadialDataBlock() = default; -RadialDataBlock::RadialDataBlock(RadialDataBlock&&) noexcept = default; -RadialDataBlock& -RadialDataBlock::operator=(RadialDataBlock&&) noexcept = default; +DigitalRadarDataGeneric::RadialDataBlock::RadialDataBlock( + RadialDataBlock&&) noexcept = default; +DigitalRadarDataGeneric::RadialDataBlock& +DigitalRadarDataGeneric::RadialDataBlock::operator=( + RadialDataBlock&&) noexcept = default; -float RadialDataBlock::unambiguous_range() const +float DigitalRadarDataGeneric::RadialDataBlock::unambiguous_range() const { return p->unambigiousRange_ / 10.0f; } -std::shared_ptr -RadialDataBlock::Create(const std::string& dataBlockType, - const std::string& dataName, - std::istream& is) +std::shared_ptr +DigitalRadarDataGeneric::RadialDataBlock::Create( + const std::string& dataBlockType, + const std::string& dataName, + std::istream& is) { std::shared_ptr p = std::make_shared(dataBlockType, dataName); @@ -465,7 +440,7 @@ RadialDataBlock::Create(const std::string& dataBlockType, return p; } -bool RadialDataBlock::Parse(std::istream& is) +bool DigitalRadarDataGeneric::RadialDataBlock::Parse(std::istream& is) { bool dataBlockValid = true; @@ -495,58 +470,38 @@ bool RadialDataBlock::Parse(std::istream& is) return dataBlockValid; } -class DigitalRadarDataGenericImpl +class DigitalRadarDataGeneric::Impl { public: - explicit DigitalRadarDataGenericImpl() : - radarIdentifier_ {}, - collectionTime_ {0}, - modifiedJulianDate_ {0}, - azimuthNumber_ {0}, - azimuthAngle_ {0.0f}, - compressionIndicator_ {0}, - radialLength_ {0}, - azimuthResolutionSpacing_ {0}, - radialStatus_ {0}, - elevationNumber_ {0}, - cutSectorNumber_ {0}, - elevationAngle_ {0.0f}, - radialSpotBlankingStatus_ {0}, - azimuthIndexingMode_ {0}, - dataBlockCount_ {0}, - dataBlockPointer_ {0}, - volumeDataBlock_ {nullptr}, - elevationDataBlock_ {nullptr}, - radialDataBlock_ {nullptr}, - momentDataBlock_ {} {}; - ~DigitalRadarDataGenericImpl() = default; + explicit Impl() {}; + ~Impl() = default; - std::string radarIdentifier_; - uint32_t collectionTime_; - uint16_t modifiedJulianDate_; - uint16_t azimuthNumber_; - float azimuthAngle_; - uint8_t compressionIndicator_; - uint16_t radialLength_; - uint8_t azimuthResolutionSpacing_; - uint8_t radialStatus_; - uint8_t elevationNumber_; - uint8_t cutSectorNumber_; - float elevationAngle_; - uint8_t radialSpotBlankingStatus_; - uint8_t azimuthIndexingMode_; - uint16_t dataBlockCount_; - std::array dataBlockPointer_; + std::string radarIdentifier_ {}; + std::uint32_t collectionTime_ {0}; + std::uint16_t modifiedJulianDate_ {0}; + std::uint16_t azimuthNumber_ {0}; + float azimuthAngle_ {0.0f}; + std::uint8_t compressionIndicator_ {0}; + std::uint16_t radialLength_ {0}; + std::uint8_t azimuthResolutionSpacing_ {0}; + std::uint8_t radialStatus_ {0}; + std::uint8_t elevationNumber_ {0}; + std::uint8_t cutSectorNumber_ {0}; + float elevationAngle_ {0.0f}; + std::uint8_t radialSpotBlankingStatus_ {0}; + std::uint8_t azimuthIndexingMode_ {0}; + std::uint16_t dataBlockCount_ {0}; + std::array dataBlockPointer_ {0}; - std::shared_ptr volumeDataBlock_; - std::shared_ptr elevationDataBlock_; - std::shared_ptr radialDataBlock_; + std::shared_ptr volumeDataBlock_ {nullptr}; + std::shared_ptr elevationDataBlock_ {nullptr}; + std::shared_ptr radialDataBlock_ {nullptr}; std::unordered_map> - momentDataBlock_; + momentDataBlock_ {}; }; DigitalRadarDataGeneric::DigitalRadarDataGeneric() : - Level2Message(), p(std::make_unique()) + Level2Message(), p(std::make_unique()) { } DigitalRadarDataGeneric::~DigitalRadarDataGeneric() = default; @@ -561,17 +516,17 @@ std::string DigitalRadarDataGeneric::radar_identifier() const return p->radarIdentifier_; } -uint32_t DigitalRadarDataGeneric::collection_time() const +std::uint32_t DigitalRadarDataGeneric::collection_time() const { return p->collectionTime_; } -uint16_t DigitalRadarDataGeneric::modified_julian_date() const +std::uint16_t DigitalRadarDataGeneric::modified_julian_date() const { return p->modifiedJulianDate_; } -uint16_t DigitalRadarDataGeneric::azimuth_number() const +std::uint16_t DigitalRadarDataGeneric::azimuth_number() const { return p->azimuthNumber_; } @@ -581,32 +536,32 @@ units::degrees DigitalRadarDataGeneric::azimuth_angle() const return units::degrees {p->azimuthAngle_}; } -uint8_t DigitalRadarDataGeneric::compression_indicator() const +std::uint8_t DigitalRadarDataGeneric::compression_indicator() const { return p->compressionIndicator_; } -uint16_t DigitalRadarDataGeneric::radial_length() const +std::uint16_t DigitalRadarDataGeneric::radial_length() const { return p->radialLength_; } -uint8_t DigitalRadarDataGeneric::azimuth_resolution_spacing() const +std::uint8_t DigitalRadarDataGeneric::azimuth_resolution_spacing() const { return p->azimuthResolutionSpacing_; } -uint8_t DigitalRadarDataGeneric::radial_status() const +std::uint8_t DigitalRadarDataGeneric::radial_status() const { return p->radialStatus_; } -uint8_t DigitalRadarDataGeneric::elevation_number() const +std::uint8_t DigitalRadarDataGeneric::elevation_number() const { return p->elevationNumber_; } -uint8_t DigitalRadarDataGeneric::cut_sector_number() const +std::uint8_t DigitalRadarDataGeneric::cut_sector_number() const { return p->cutSectorNumber_; } @@ -616,40 +571,40 @@ units::degrees DigitalRadarDataGeneric::elevation_angle() const return units::degrees {p->elevationAngle_}; } -uint8_t DigitalRadarDataGeneric::radial_spot_blanking_status() const +std::uint8_t DigitalRadarDataGeneric::radial_spot_blanking_status() const { return p->radialSpotBlankingStatus_; } -uint8_t DigitalRadarDataGeneric::azimuth_indexing_mode() const +std::uint8_t DigitalRadarDataGeneric::azimuth_indexing_mode() const { return p->azimuthIndexingMode_; } -uint16_t DigitalRadarDataGeneric::data_block_count() const +std::uint16_t DigitalRadarDataGeneric::data_block_count() const { return p->dataBlockCount_; } -std::shared_ptr +std::shared_ptr DigitalRadarDataGeneric::elevation_data_block() const { return p->elevationDataBlock_; } -std::shared_ptr +std::shared_ptr DigitalRadarDataGeneric::radial_data_block() const { return p->radialDataBlock_; } -std::shared_ptr +std::shared_ptr DigitalRadarDataGeneric::volume_data_block() const { return p->volumeDataBlock_; } -std::shared_ptr +std::shared_ptr DigitalRadarDataGeneric::moment_data_block(DataBlockType type) const { std::shared_ptr momentDataBlock = nullptr; @@ -667,8 +622,8 @@ bool DigitalRadarDataGeneric::Parse(std::istream& is) { logger_->trace("Parsing Digital Radar Data (Message Type 31)"); - bool messageValid = true; - size_t bytesRead = 0; + bool messageValid = true; + std::size_t bytesRead = 0; std::streampos isBegin = is.tellg(); From 571d0b2ce989c4235135632f1f372827cf469de8 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 22 Jan 2024 23:01:29 -0600 Subject: [PATCH 16/37] Add Generic Radar Data as a common base class to message types 1 and 31 --- .../scwx/qt/view/level2_product_view.cpp | 10 +- wxdata/include/scwx/wsr88d/ar2v_file.hpp | 2 +- .../wsr88d/rda/digital_radar_data_generic.hpp | 39 ++------ .../scwx/wsr88d/rda/generic_radar_data.hpp | 97 +++++++++++++++++++ wxdata/source/scwx/wsr88d/ar2v_file.cpp | 5 +- .../wsr88d/rda/digital_radar_data_generic.cpp | 16 ++- .../scwx/wsr88d/rda/generic_radar_data.cpp | 61 ++++++++++++ wxdata/wxdata.cmake | 2 + 8 files changed, 191 insertions(+), 41 deletions(-) create mode 100644 wxdata/include/scwx/wsr88d/rda/generic_radar_data.hpp create mode 100644 wxdata/source/scwx/wsr88d/rda/generic_radar_data.cpp diff --git a/scwx-qt/source/scwx/qt/view/level2_product_view.cpp b/scwx-qt/source/scwx/qt/view/level2_product_view.cpp index 51751609..0e98111f 100644 --- a/scwx-qt/source/scwx/qt/view/level2_product_view.cpp +++ b/scwx-qt/source/scwx/qt/view/level2_product_view.cpp @@ -90,7 +90,7 @@ public: float selectedElevation_; std::shared_ptr elevationScan_; - std::shared_ptr + std::shared_ptr momentDataBlock0_; std::vector coordinates_ {}; @@ -469,15 +469,15 @@ void Level2ProductView::ComputeSweep() const uint32_t gates = momentData0->number_of_data_moment_gates(); - auto volumeData0 = radarData0->volume_data_block(); - p->latitude_ = volumeData0->latitude(); - p->longitude_ = volumeData0->longitude(); + auto radarSite = radarProductManager->radar_site(); + p->latitude_ = radarSite->latitude(); + p->longitude_ = radarSite->longitude(); p->range_ = momentData0->data_moment_range() + momentData0->data_moment_range_sample_interval() * (gates - 0.5f); p->sweepTime_ = scwx::util::TimePoint(radarData0->modified_julian_date(), radarData0->collection_time()); - p->vcp_ = volumeData0->volume_coverage_pattern_number(); + p->vcp_ = radarData0->volume_coverage_pattern_number(); // Calculate vertices timer.start(); diff --git a/wxdata/include/scwx/wsr88d/ar2v_file.hpp b/wxdata/include/scwx/wsr88d/ar2v_file.hpp index 87628fd6..0df24765 100644 --- a/wxdata/include/scwx/wsr88d/ar2v_file.hpp +++ b/wxdata/include/scwx/wsr88d/ar2v_file.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include #include diff --git a/wxdata/include/scwx/wsr88d/rda/digital_radar_data_generic.hpp b/wxdata/include/scwx/wsr88d/rda/digital_radar_data_generic.hpp index e86e6534..ba246eee 100644 --- a/wxdata/include/scwx/wsr88d/rda/digital_radar_data_generic.hpp +++ b/wxdata/include/scwx/wsr88d/rda/digital_radar_data_generic.hpp @@ -1,10 +1,6 @@ #pragma once -#include -#include - -#include -#include +#include namespace scwx { @@ -13,30 +9,7 @@ namespace wsr88d namespace rda { -enum class DataBlockType -{ - Volume, - Elevation, - Radial, - MomentRef, - MomentVel, - MomentSw, - MomentZdr, - MomentPhi, - MomentRho, - MomentCfp, - Unknown -}; -typedef util:: - Iterator - MomentDataBlockTypeIterator; - -class DigitalRadarDataGeneric; - -typedef std::map> - ElevationScan; - -class DigitalRadarDataGeneric : public Level2Message +class DigitalRadarDataGeneric : public GenericRadarData { public: class DataBlock; @@ -69,11 +42,13 @@ public: std::uint8_t radial_spot_blanking_status() const; std::uint8_t azimuth_indexing_mode() const; std::uint16_t data_block_count() const; + std::uint16_t volume_coverage_pattern_number() const; std::shared_ptr elevation_data_block() const; std::shared_ptr radial_data_block() const; std::shared_ptr volume_data_block() const; - std::shared_ptr moment_data_block(DataBlockType type) const; + std::shared_ptr + moment_data_block(DataBlockType type) const; bool Parse(std::istream& is); @@ -128,7 +103,9 @@ private: bool Parse(std::istream& is); }; -class DigitalRadarDataGeneric::MomentDataBlock : public DataBlock +class DigitalRadarDataGeneric::MomentDataBlock : + public DataBlock, + public GenericRadarData::MomentDataBlock { public: explicit MomentDataBlock(const std::string& dataBlockType, diff --git a/wxdata/include/scwx/wsr88d/rda/generic_radar_data.hpp b/wxdata/include/scwx/wsr88d/rda/generic_radar_data.hpp new file mode 100644 index 00000000..532cbe02 --- /dev/null +++ b/wxdata/include/scwx/wsr88d/rda/generic_radar_data.hpp @@ -0,0 +1,97 @@ +#pragma once + +#include +#include + +#include +#include + +namespace scwx +{ +namespace wsr88d +{ +namespace rda +{ + +enum class DataBlockType +{ + Volume, + Elevation, + Radial, + MomentRef, + MomentVel, + MomentSw, + MomentZdr, + MomentPhi, + MomentRho, + MomentCfp, + Unknown +}; +typedef util:: + Iterator + MomentDataBlockTypeIterator; + +class GenericRadarData; + +typedef std::map> + ElevationScan; + +class GenericRadarData : public Level2Message +{ +public: + class MomentDataBlock; + + explicit GenericRadarData(); + virtual ~GenericRadarData(); + + GenericRadarData(const GenericRadarData&) = delete; + GenericRadarData& operator=(const GenericRadarData&) = delete; + + GenericRadarData(GenericRadarData&&) noexcept; + GenericRadarData& operator=(GenericRadarData&&) noexcept; + + virtual std::uint32_t collection_time() const = 0; + virtual std::uint16_t modified_julian_date() const = 0; + virtual units::degrees azimuth_angle() const = 0; + virtual std::uint16_t volume_coverage_pattern_number() const = 0; + + virtual std::shared_ptr + moment_data_block(DataBlockType type) const = 0; + +private: + class Impl; + std::unique_ptr p; +}; + +class GenericRadarData::MomentDataBlock +{ +public: + explicit MomentDataBlock(); + virtual ~MomentDataBlock(); + + MomentDataBlock(const MomentDataBlock&) = delete; + MomentDataBlock& operator=(const MomentDataBlock&) = delete; + + MomentDataBlock(MomentDataBlock&&) noexcept; + MomentDataBlock& operator=(MomentDataBlock&&) noexcept; + + virtual std::uint16_t number_of_data_moment_gates() const = 0; + virtual units::kilometers data_moment_range() const = 0; + virtual std::uint16_t data_moment_range_raw() const = 0; + virtual units::kilometers + data_moment_range_sample_interval() const = 0; + virtual std::uint16_t data_moment_range_sample_interval_raw() const = 0; + virtual std::int16_t snr_threshold_raw() const = 0; + virtual std::uint8_t data_word_size() const = 0; + virtual float scale() const = 0; + virtual float offset() const = 0; + virtual const void* data_moments() const = 0; + +private: + class Impl; + std::unique_ptr p; +}; + +} // namespace rda +} // namespace wsr88d +} // namespace scwx diff --git a/wxdata/source/scwx/wsr88d/ar2v_file.cpp b/wxdata/source/scwx/wsr88d/ar2v_file.cpp index 23c76e03..c78aa2d3 100644 --- a/wxdata/source/scwx/wsr88d/ar2v_file.cpp +++ b/wxdata/source/scwx/wsr88d/ar2v_file.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -109,7 +110,7 @@ std::chrono::system_clock::time_point Ar2vFile::end_time() const if (p->radarData_.size() > 0) { - std::shared_ptr lastRadial = + std::shared_ptr lastRadial = p->radarData_.crbegin()->second->crbegin()->second; endTime = util::TimePoint(lastRadial->modified_julian_date(), @@ -431,7 +432,7 @@ void Ar2vFileImpl::IndexFile() rda::WaveformType waveformType = vcpData_->waveform_type(elevationCut.first); - std::shared_ptr& radial0 = + std::shared_ptr& radial0 = (*elevationCut.second)[0]; if (radial0 == nullptr) diff --git a/wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp b/wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp index ca27b181..a5f128f1 100644 --- a/wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp +++ b/wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp @@ -501,7 +501,7 @@ public: }; DigitalRadarDataGeneric::DigitalRadarDataGeneric() : - Level2Message(), p(std::make_unique()) + GenericRadarData(), p(std::make_unique()) { } DigitalRadarDataGeneric::~DigitalRadarDataGeneric() = default; @@ -586,6 +586,18 @@ std::uint16_t DigitalRadarDataGeneric::data_block_count() const return p->dataBlockCount_; } +std::uint16_t DigitalRadarDataGeneric::volume_coverage_pattern_number() const +{ + std::uint16_t vcpNumber = 0; + + if (p->volumeDataBlock_ != nullptr) + { + vcpNumber = p->volumeDataBlock_->volume_coverage_pattern_number(); + } + + return vcpNumber; +} + std::shared_ptr DigitalRadarDataGeneric::elevation_data_block() const { @@ -604,7 +616,7 @@ DigitalRadarDataGeneric::volume_data_block() const return p->volumeDataBlock_; } -std::shared_ptr +std::shared_ptr DigitalRadarDataGeneric::moment_data_block(DataBlockType type) const { std::shared_ptr momentDataBlock = nullptr; diff --git a/wxdata/source/scwx/wsr88d/rda/generic_radar_data.cpp b/wxdata/source/scwx/wsr88d/rda/generic_radar_data.cpp new file mode 100644 index 00000000..1ca7e237 --- /dev/null +++ b/wxdata/source/scwx/wsr88d/rda/generic_radar_data.cpp @@ -0,0 +1,61 @@ +#include +#include + +namespace scwx +{ +namespace wsr88d +{ +namespace rda +{ + +static const std::string logPrefix_ = "scwx::wsr88d::rda::generic_radar_data"; + +static const std::unordered_map strToDataBlock_ { + {"VOL", DataBlockType::Volume}, + {"ELV", DataBlockType::Elevation}, + {"RAD", DataBlockType::Radial}, + {"REF", DataBlockType::MomentRef}, + {"VEL", DataBlockType::MomentVel}, + {"SW ", DataBlockType::MomentSw}, + {"ZDR", DataBlockType::MomentZdr}, + {"PHI", DataBlockType::MomentPhi}, + {"RHO", DataBlockType::MomentRho}, + {"CFP", DataBlockType::MomentCfp}}; + +class GenericRadarData::MomentDataBlock::Impl +{ +public: + explicit Impl() {} +}; + +GenericRadarData::MomentDataBlock::MomentDataBlock() : + p(std::make_unique()) +{ +} +GenericRadarData::MomentDataBlock::~MomentDataBlock() = default; + +GenericRadarData::MomentDataBlock::MomentDataBlock(MomentDataBlock&&) noexcept = + default; +GenericRadarData::MomentDataBlock& GenericRadarData::MomentDataBlock::operator=( + MomentDataBlock&&) noexcept = default; + +class GenericRadarData::Impl +{ +public: + explicit Impl() {}; + ~Impl() = default; +}; + +GenericRadarData::GenericRadarData() : + Level2Message(), p(std::make_unique()) +{ +} +GenericRadarData::~GenericRadarData() = default; + +GenericRadarData::GenericRadarData(GenericRadarData&&) noexcept = default; +GenericRadarData& +GenericRadarData::operator=(GenericRadarData&&) noexcept = default; + +} // namespace rda +} // namespace wsr88d +} // namespace scwx diff --git a/wxdata/wxdata.cmake b/wxdata/wxdata.cmake index 047ff40d..0652d898 100644 --- a/wxdata/wxdata.cmake +++ b/wxdata/wxdata.cmake @@ -104,6 +104,7 @@ set(HDR_WSR88D_RDA include/scwx/wsr88d/rda/clutter_filter_bypass_map.hpp include/scwx/wsr88d/rda/clutter_filter_map.hpp include/scwx/wsr88d/rda/digital_radar_data.hpp include/scwx/wsr88d/rda/digital_radar_data_generic.hpp + include/scwx/wsr88d/rda/generic_radar_data.hpp include/scwx/wsr88d/rda/level2_message.hpp include/scwx/wsr88d/rda/level2_message_factory.hpp include/scwx/wsr88d/rda/level2_message_header.hpp @@ -116,6 +117,7 @@ set(SRC_WSR88D_RDA source/scwx/wsr88d/rda/clutter_filter_bypass_map.cpp source/scwx/wsr88d/rda/clutter_filter_map.cpp source/scwx/wsr88d/rda/digital_radar_data.cpp source/scwx/wsr88d/rda/digital_radar_data_generic.cpp + source/scwx/wsr88d/rda/generic_radar_data.cpp source/scwx/wsr88d/rda/level2_message.cpp source/scwx/wsr88d/rda/level2_message_factory.cpp source/scwx/wsr88d/rda/level2_message_header.cpp From 730c1a9ba7e25bffe9b6533ad48d1f9441cba3f5 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 22 Jan 2024 23:59:04 -0600 Subject: [PATCH 17/37] Inherit DigitalRadarData from GenericRadarData --- .../scwx/wsr88d/rda/digital_radar_data.hpp | 10 +- .../scwx/wsr88d/rda/digital_radar_data.cpp | 228 +++++++++++++++++- 2 files changed, 223 insertions(+), 15 deletions(-) diff --git a/wxdata/include/scwx/wsr88d/rda/digital_radar_data.hpp b/wxdata/include/scwx/wsr88d/rda/digital_radar_data.hpp index 200cfbe9..bf96024e 100644 --- a/wxdata/include/scwx/wsr88d/rda/digital_radar_data.hpp +++ b/wxdata/include/scwx/wsr88d/rda/digital_radar_data.hpp @@ -1,9 +1,6 @@ #pragma once -#include - -#include -#include +#include namespace scwx { @@ -12,7 +9,7 @@ namespace wsr88d namespace rda { -class DigitalRadarData : public Level2Message +class DigitalRadarData : public GenericRadarData { public: explicit DigitalRadarData(); @@ -56,6 +53,9 @@ public: std::uint16_t tover() const; std::uint16_t radial_spot_blanking_status() const; + std::shared_ptr + moment_data_block(DataBlockType type) const; + bool Parse(std::istream& is); static std::shared_ptr Create(Level2MessageHeader&& header, diff --git a/wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp b/wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp index b84c6aff..6d840d79 100644 --- a/wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp +++ b/wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp @@ -20,6 +20,8 @@ constexpr float kRangeScale = 0.001f; class DigitalRadarData::Impl { public: + class MomentDataBlock; + explicit Impl() {}; ~Impl() = default; @@ -49,13 +51,59 @@ public: std::uint16_t tover_ {}; std::uint16_t radialSpotBlankingStatus_ {}; - std::vector reflectivity_ {}; - std::vector dopplerVelocity_ {}; - std::vector dopplerSpectrumWidth_ {}; + std::shared_ptr reflectivityDataBlock_ {nullptr}; + std::shared_ptr dopplerVelocityDataBlock_ {nullptr}; + std::shared_ptr dopplerSpectrumWidthDataBlock_ {nullptr}; +}; + +class DigitalRadarData::Impl::MomentDataBlock : + public GenericRadarData::MomentDataBlock +{ +public: + explicit MomentDataBlock(const DigitalRadarData* self, DataBlockType type); + ~MomentDataBlock() = default; + + MomentDataBlock(const MomentDataBlock&) = delete; + MomentDataBlock& operator=(const MomentDataBlock&) = delete; + + MomentDataBlock(MomentDataBlock&&) noexcept = default; + MomentDataBlock& operator=(MomentDataBlock&&) noexcept = default; + + std::uint16_t number_of_data_moment_gates() const; + units::kilometers data_moment_range() const; + std::uint16_t data_moment_range_raw() const; + units::kilometers data_moment_range_sample_interval() const; + std::uint16_t data_moment_range_sample_interval_raw() const; + std::int16_t snr_threshold_raw() const; + std::uint8_t data_word_size() const; + float scale() const; + float offset() const; + const void* data_moments() const; + + std::vector& data_moment_vector() const; + +private: + class Impl; + std::unique_ptr p; +}; + +class DigitalRadarData::Impl::MomentDataBlock::Impl +{ +public: + explicit Impl() {}; + ~Impl() = default; + + std::uint16_t numberOfDataMomentGates_ {}; + std::uint16_t dataMomentRange_ {}; + std::uint16_t dataMomentRangeSampleInterval_ {}; + float scale_ {}; + float offset_ {}; + + std::vector dataMoments_ {}; }; DigitalRadarData::DigitalRadarData() : - Level2Message(), p(std::make_unique()) + GenericRadarData(), p(std::make_unique()) { } DigitalRadarData::~DigitalRadarData() = default; @@ -222,6 +270,151 @@ std::uint16_t DigitalRadarData::radial_spot_blanking_status() const return p->radialSpotBlankingStatus_; } +std::shared_ptr +DigitalRadarData::moment_data_block(DataBlockType type) const +{ + std::shared_ptr block = nullptr; + + switch (type) + { + case DataBlockType::MomentRef: + block = p->reflectivityDataBlock_; + break; + + case DataBlockType::MomentVel: + block = p->dopplerVelocityDataBlock_; + break; + + case DataBlockType::MomentSw: + block = p->dopplerSpectrumWidthDataBlock_; + break; + + default: + break; + } + + return block; +} + +DigitalRadarData::Impl::MomentDataBlock::MomentDataBlock( + const DigitalRadarData* self, DataBlockType type) +{ + switch (type) + { + case DataBlockType::MomentRef: + p->numberOfDataMomentGates_ = self->number_of_surveillance_bins(); + p->dataMomentRange_ = self->surveillance_range_raw(); + p->dataMomentRangeSampleInterval_ = + self->surveillance_range_sample_interval_raw(); + + // Table III-E Base Data Scaling + // Rnum = (R / 2) - 33.0 + p->scale_ = 2.0f; + p->offset_ = 66.0f; // (33.0 * 2) + break; + + case DataBlockType::MomentVel: + p->numberOfDataMomentGates_ = self->number_of_doppler_bins(); + p->dataMomentRange_ = self->doppler_range_raw(); + p->dataMomentRangeSampleInterval_ = + self->doppler_range_sample_interval_raw(); + + // Table III-E Base Data Scaling + if (self->doppler_velocity_resolution() == 2) // 2 = 0.5 m/s + { + // Vnum = (V / 2) - 64.5 + p->scale_ = 2.0f; + p->offset_ = 129.0f; // (64.5 * 2) + } + else // 4 = 1.0 m/s + { + // Vnum = V - 129.0 + p->scale_ = 1.0f; + p->offset_ = 129.0f; + } + break; + + case DataBlockType::MomentSw: + p->numberOfDataMomentGates_ = self->number_of_doppler_bins(); + p->dataMomentRange_ = self->doppler_range_raw(); + p->dataMomentRangeSampleInterval_ = + self->doppler_range_sample_interval_raw(); + + // Table III-E Base Data Scaling + // SWnum = (SW / 2) - 64.5 + p->scale_ = 2.0f; + p->offset_ = 129.0f; // (64.5 * 2) + break; + } +} +std::uint16_t +DigitalRadarData::Impl::MomentDataBlock::number_of_data_moment_gates() const +{ + return p->numberOfDataMomentGates_; +} + +units::kilometers +DigitalRadarData::Impl::MomentDataBlock::data_moment_range() const +{ + return units::kilometers {p->dataMomentRange_ * kRangeScale}; +} + +std::uint16_t +DigitalRadarData::Impl::MomentDataBlock::data_moment_range_raw() const +{ + return p->dataMomentRange_; +} + +units::kilometers +DigitalRadarData::Impl::MomentDataBlock::data_moment_range_sample_interval() + const +{ + return units::kilometers {p->dataMomentRangeSampleInterval_ * + kRangeScale}; +} + +std::uint16_t +DigitalRadarData::Impl::MomentDataBlock::data_moment_range_sample_interval_raw() + const +{ + return p->dataMomentRangeSampleInterval_; +} + +std::int16_t DigitalRadarData::Impl::MomentDataBlock::snr_threshold_raw() const +{ + // Table III Digital Radar Data (Message Type 1) Note 10: + // Value of 00 (prior to scaling) is Signal Below Threshold, value of 01 + // (prior to scaling) is Signal Overlaid + return 2; +} + +std::uint8_t DigitalRadarData::Impl::MomentDataBlock::data_word_size() const +{ + // Data moments are 8-bit for Digital Radar Data + return 8; +} + +float DigitalRadarData::Impl::MomentDataBlock::scale() const +{ + return p->scale_; +} + +float DigitalRadarData::Impl::MomentDataBlock::offset() const +{ + return p->offset_; +} + +const void* DigitalRadarData::Impl::MomentDataBlock::data_moments() const +{ + return p->dataMoments_.data(); +} + +std::vector& +DigitalRadarData::Impl::MomentDataBlock::data_moment_vector() const +{ + return p->dataMoments_; +} + bool DigitalRadarData::Parse(std::istream& is) { logger_->trace("Parsing Digital Radar Data (Message Type 1)"); @@ -337,31 +530,46 @@ bool DigitalRadarData::Parse(std::istream& is) if (messageValid && p->surveillancePointer_ != 0) { + p->reflectivityDataBlock_ = std::make_shared( + this, DataBlockType::MomentRef); + auto& reflectivity = p->reflectivityDataBlock_->data_moment_vector(); + is.seekg(isBegin + std::streamoff(p->surveillancePointer_), std::ios_base::beg); - p->reflectivity_.resize(p->numberOfSurveillanceBins_); - is.read(reinterpret_cast(p->reflectivity_.data()), + reflectivity.resize(p->numberOfSurveillanceBins_); + is.read(reinterpret_cast(reflectivity.data()), p->numberOfSurveillanceBins_); } if (messageValid && p->velocityPointer_ != 0) { + p->dopplerVelocityDataBlock_ = std::make_shared( + this, DataBlockType::MomentVel); + auto& dopplerVelocity = + p->dopplerVelocityDataBlock_->data_moment_vector(); + is.seekg(isBegin + std::streamoff(p->velocityPointer_), std::ios_base::beg); - p->dopplerVelocity_.resize(p->numberOfDopplerBins_); - is.read(reinterpret_cast(p->dopplerVelocity_.data()), + dopplerVelocity.resize(p->numberOfDopplerBins_); + is.read(reinterpret_cast(dopplerVelocity.data()), p->numberOfDopplerBins_); } if (messageValid && p->spectralWidthPointer_ != 0) { + p->dopplerSpectrumWidthDataBlock_ = + std::make_shared(this, + DataBlockType::MomentVel); + auto& dopplerSpectrumWidth = + p->dopplerSpectrumWidthDataBlock_->data_moment_vector(); + is.seekg(isBegin + std::streamoff(p->spectralWidthPointer_), std::ios_base::beg); - p->dopplerSpectrumWidth_.resize(p->numberOfDopplerBins_); - is.read(reinterpret_cast(p->dopplerSpectrumWidth_.data()), + dopplerSpectrumWidth.resize(p->numberOfDopplerBins_); + is.read(reinterpret_cast(dopplerSpectrumWidth.data()), p->numberOfDopplerBins_); } From 835e8d8f0d00687aadee5c9cdd7b05a6b360ebfb Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 23 Jan 2024 00:22:56 -0600 Subject: [PATCH 18/37] Process and index Digital Radar Data --- .../wsr88d/rda/digital_radar_data_generic.hpp | 2 +- .../scwx/wsr88d/rda/generic_radar_data.hpp | 2 + wxdata/source/scwx/wsr88d/ar2v_file.cpp | 42 ++++++++++++------- .../wsr88d/rda/digital_radar_data_generic.cpp | 2 +- 4 files changed, 31 insertions(+), 17 deletions(-) diff --git a/wxdata/include/scwx/wsr88d/rda/digital_radar_data_generic.hpp b/wxdata/include/scwx/wsr88d/rda/digital_radar_data_generic.hpp index ba246eee..42ee713a 100644 --- a/wxdata/include/scwx/wsr88d/rda/digital_radar_data_generic.hpp +++ b/wxdata/include/scwx/wsr88d/rda/digital_radar_data_generic.hpp @@ -36,7 +36,7 @@ public: std::uint16_t radial_length() const; std::uint8_t azimuth_resolution_spacing() const; std::uint8_t radial_status() const; - std::uint8_t elevation_number() const; + std::uint16_t elevation_number() const; std::uint8_t cut_sector_number() const; units::degrees elevation_angle() const; std::uint8_t radial_spot_blanking_status() const; diff --git a/wxdata/include/scwx/wsr88d/rda/generic_radar_data.hpp b/wxdata/include/scwx/wsr88d/rda/generic_radar_data.hpp index 532cbe02..085f7849 100644 --- a/wxdata/include/scwx/wsr88d/rda/generic_radar_data.hpp +++ b/wxdata/include/scwx/wsr88d/rda/generic_radar_data.hpp @@ -53,6 +53,8 @@ public: virtual std::uint32_t collection_time() const = 0; virtual std::uint16_t modified_julian_date() const = 0; virtual units::degrees azimuth_angle() const = 0; + virtual std::uint16_t azimuth_number() const = 0; + virtual std::uint16_t elevation_number() const = 0; virtual std::uint16_t volume_coverage_pattern_number() const = 0; virtual std::shared_ptr diff --git a/wxdata/source/scwx/wsr88d/ar2v_file.cpp b/wxdata/source/scwx/wsr88d/ar2v_file.cpp index c78aa2d3..2179b835 100644 --- a/wxdata/source/scwx/wsr88d/ar2v_file.cpp +++ b/wxdata/source/scwx/wsr88d/ar2v_file.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include @@ -59,8 +59,7 @@ public: void IndexFile(); void ParseLDMRecords(); void ParseLDMRecord(std::istream& is); - void ProcessRadarData( - const std::shared_ptr& message); + void ProcessRadarData(const std::shared_ptr& message); std::string tapeFilename_; std::string extensionNumber_; @@ -391,9 +390,10 @@ void Ar2vFileImpl::HandleMessage(std::shared_ptr& message) std::static_pointer_cast(message); break; + case static_cast(rda::MessageId::DigitalRadarData): case static_cast(rda::MessageId::DigitalRadarDataGeneric): ProcessRadarData( - std::static_pointer_cast(message)); + std::static_pointer_cast(message)); break; default: @@ -402,7 +402,7 @@ void Ar2vFileImpl::HandleMessage(std::shared_ptr& message) } void Ar2vFileImpl::ProcessRadarData( - const std::shared_ptr& message) + const std::shared_ptr& message) { std::uint16_t azimuthIndex = message->azimuth_number() - 1; std::uint16_t elevationIndex = message->elevation_number() - 1; @@ -419,18 +419,10 @@ void Ar2vFileImpl::IndexFile() { logger_->debug("Indexing file"); - if (vcpData_ == nullptr) - { - logger_->warn("Cannot index file without VCP data"); - return; - } - for (auto& elevationCut : radarData_) { - std::uint16_t elevationAngle = - vcpData_->elevation_angle_raw(elevationCut.first); - rda::WaveformType waveformType = - vcpData_->waveform_type(elevationCut.first); + std::uint16_t elevationAngle {}; + rda::WaveformType waveformType = rda::WaveformType::Unknown; std::shared_ptr& radial0 = (*elevationCut.second)[0]; @@ -441,6 +433,26 @@ void Ar2vFileImpl::IndexFile() continue; } + std::shared_ptr digitalRadarData0 = nullptr; + + if (vcpData_ != nullptr) + { + elevationAngle = vcpData_->elevation_angle_raw(elevationCut.first); + waveformType = vcpData_->waveform_type(elevationCut.first); + } + else if ((digitalRadarData0 = + std::dynamic_pointer_cast( + (*elevationCut.second)[0])) != nullptr) + { + elevationAngle = digitalRadarData0->elevation_angle_raw(); + } + else + { + // Return here, because we should only have a single message type + logger_->warn("Cannot index file without VCP data"); + return; + } + for (rda::DataBlockType dataBlockType : rda::MomentDataBlockTypeIterator()) { diff --git a/wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp b/wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp index a5f128f1..94492a8e 100644 --- a/wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp +++ b/wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp @@ -556,7 +556,7 @@ std::uint8_t DigitalRadarDataGeneric::radial_status() const return p->radialStatus_; } -std::uint8_t DigitalRadarDataGeneric::elevation_number() const +std::uint16_t DigitalRadarDataGeneric::elevation_number() const { return p->elevationNumber_; } From 524d5e4fac44bd53c7bd8db362e01eff0735546d Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 23 Jan 2024 00:23:19 -0600 Subject: [PATCH 19/37] Fix initialization of Digital Radar Data moment block --- wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp b/wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp index 6d840d79..37500ed6 100644 --- a/wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp +++ b/wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp @@ -297,7 +297,8 @@ DigitalRadarData::moment_data_block(DataBlockType type) const } DigitalRadarData::Impl::MomentDataBlock::MomentDataBlock( - const DigitalRadarData* self, DataBlockType type) + const DigitalRadarData* self, DataBlockType type) : + p(std::make_unique()) { switch (type) { From 527ada5b0725381e692a9b152098cbefc55b046a Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 23 Jan 2024 23:24:34 -0600 Subject: [PATCH 20/37] Specify a minimum data moment range when computing level 2 gate interval --- scwx-qt/source/scwx/qt/view/level2_product_view.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/scwx-qt/source/scwx/qt/view/level2_product_view.cpp b/scwx-qt/source/scwx/qt/view/level2_product_view.cpp index 0e98111f..24d7eacc 100644 --- a/scwx-qt/source/scwx/qt/view/level2_product_view.cpp +++ b/scwx-qt/source/scwx/qt/view/level2_product_view.cpp @@ -541,10 +541,11 @@ void Level2ProductView::ComputeSweep() } // Compute gate interval - const uint16_t dataMomentRange = momentData->data_moment_range_raw(); - const uint16_t dataMomentInterval = + const std::uint16_t dataMomentInterval = momentData->data_moment_range_sample_interval_raw(); - const uint16_t dataMomentIntervalH = dataMomentInterval / 2; + const std::uint16_t dataMomentIntervalH = dataMomentInterval / 2; + const std::uint16_t dataMomentRange = + std::max(momentData->data_moment_range_raw(), dataMomentIntervalH); // Compute gate size (number of base 250m gates per bin) const uint16_t gateSizeMeters = @@ -865,10 +866,11 @@ Level2ProductView::GetBinLevel(const common::Coordinate& coordinate) const // Compute gate interval auto momentData = (*radarData)[*radial]->moment_data_block(dataBlockType); - const std::uint16_t dataMomentRange = momentData->data_moment_range_raw(); const std::uint16_t dataMomentInterval = momentData->data_moment_range_sample_interval_raw(); const std::uint16_t dataMomentIntervalH = dataMomentInterval / 2; + const std::uint16_t dataMomentRange = + std::max(momentData->data_moment_range_raw(), dataMomentIntervalH); // Compute gate size (number of base 250m gates per bin) const std::uint16_t gateSizeMeters = From 0fa3b8524c929c65c9ce85dc2df73e6b8c9f9e1f Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 23 Jan 2024 23:24:57 -0600 Subject: [PATCH 21/37] Add precipitation mode VCPs --- wxdata/source/scwx/common/vcp.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/wxdata/source/scwx/common/vcp.cpp b/wxdata/source/scwx/common/vcp.cpp index 9708cfd0..347d09a7 100644 --- a/wxdata/source/scwx/common/vcp.cpp +++ b/wxdata/source/scwx/common/vcp.cpp @@ -17,16 +17,23 @@ std::string GetVcpDescription(uint16_t vcp) case 31: case 32: case 35: - case 90: return CLEAR_AIR_MODE; + case 90: + return CLEAR_AIR_MODE; + case 11: case 12: + case 21: case 80: case 112: case 121: + case 211: case 212: - case 215: return PRECIPITATION_MODE; + case 215: + case 221: + return PRECIPITATION_MODE; - default: return "?"; + default: + return "?"; } } From 2fd7dca8a426c9939cdcdd61ed7210154cba3b83 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 23 Jan 2024 23:25:44 -0600 Subject: [PATCH 22/37] Change invalid time key log from warning to trace, as this is not always an unexpected condition --- wxdata/source/scwx/provider/aws_level2_data_provider.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wxdata/source/scwx/provider/aws_level2_data_provider.cpp b/wxdata/source/scwx/provider/aws_level2_data_provider.cpp index 6ac939c0..319c9ad7 100644 --- a/wxdata/source/scwx/provider/aws_level2_data_provider.cpp +++ b/wxdata/source/scwx/provider/aws_level2_data_provider.cpp @@ -94,7 +94,7 @@ AwsLevel2DataProvider::GetTimePointFromKey(const std::string& key) if (in.fail()) { - logger_->warn("Invalid time: \"{}\"", timeStr); + logger_->trace("Invalid time: \"{}\"", timeStr); } } else From af1b42e7b0c017470bf54216e49368945b198be6 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 23 Jan 2024 23:26:06 -0600 Subject: [PATCH 23/37] Ignore empty messages for clutter filter bypass map and VCP data --- .../wsr88d/rda/clutter_filter_bypass_map.cpp | 23 +++++++++++-------- .../rda/volume_coverage_pattern_data.cpp | 22 ++++++++++++------ 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/wxdata/source/scwx/wsr88d/rda/clutter_filter_bypass_map.cpp b/wxdata/source/scwx/wsr88d/rda/clutter_filter_bypass_map.cpp index c0e8a7f9..5f95d3a5 100644 --- a/wxdata/source/scwx/wsr88d/rda/clutter_filter_bypass_map.cpp +++ b/wxdata/source/scwx/wsr88d/rda/clutter_filter_bypass_map.cpp @@ -78,19 +78,22 @@ bool ClutterFilterBypassMap::Parse(std::istream& is) if (p->mapGenerationDate_ < 1) { - logger_->warn("Invalid date: {}", p->mapGenerationDate_); + logger_->trace("Ignoring empty message"); messageValid = false; } - if (p->mapGenerationTime_ > 1440) + else { - logger_->warn("Invalid time: {}", p->mapGenerationTime_); - messageValid = false; - } - if (numElevationSegments < 1 || numElevationSegments > 5) - { - logger_->warn("Invalid number of elevation segments: {}", - numElevationSegments); - messageValid = false; + if (p->mapGenerationTime_ > 1440) + { + logger_->warn("Invalid time: {}", p->mapGenerationTime_); + messageValid = false; + } + if (numElevationSegments < 1 || numElevationSegments > 5) + { + logger_->warn("Invalid number of elevation segments: {}", + numElevationSegments); + messageValid = false; + } } if (!messageValid) diff --git a/wxdata/source/scwx/wsr88d/rda/volume_coverage_pattern_data.cpp b/wxdata/source/scwx/wsr88d/rda/volume_coverage_pattern_data.cpp index 72943869..d2f3dec2 100644 --- a/wxdata/source/scwx/wsr88d/rda/volume_coverage_pattern_data.cpp +++ b/wxdata/source/scwx/wsr88d/rda/volume_coverage_pattern_data.cpp @@ -102,7 +102,7 @@ VolumeCoveragePatternData::VolumeCoveragePatternData() : VolumeCoveragePatternData::~VolumeCoveragePatternData() = default; VolumeCoveragePatternData::VolumeCoveragePatternData( - VolumeCoveragePatternData&&) noexcept = default; + VolumeCoveragePatternData&&) noexcept = default; VolumeCoveragePatternData& VolumeCoveragePatternData::operator=( VolumeCoveragePatternData&&) noexcept = default; @@ -419,16 +419,24 @@ bool VolumeCoveragePatternData::Parse(std::istream& is) p->vcpSequencing_ = ntohs(p->vcpSequencing_); p->vcpSupplementalData_ = ntohs(p->vcpSupplementalData_); - if (messageSize < 34 || messageSize > 747) + if (messageSize == 0) { - logger_->warn("Invalid message size: {}", messageSize); + logger_->trace("Ignoring empty message"); messageValid = false; } - if (numberOfElevationCuts < 1 || numberOfElevationCuts > 32) + else { - logger_->warn("Invalid number of elevation cuts: {}", - numberOfElevationCuts); - messageValid = false; + if (messageSize < 34 || messageSize > 747) + { + logger_->warn("Invalid message size: {}", messageSize); + messageValid = false; + } + if (numberOfElevationCuts < 1 || numberOfElevationCuts > 32) + { + logger_->warn("Invalid number of elevation cuts: {}", + numberOfElevationCuts); + messageValid = false; + } } if (!messageValid) From db6e504aa8b589788fd25932caea59563a14b643 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 23 Jan 2024 23:40:40 -0600 Subject: [PATCH 24/37] Stub for ARCHIVE2 format --- wxdata/source/scwx/wsr88d/nexrad_file_factory.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/wxdata/source/scwx/wsr88d/nexrad_file_factory.cpp b/wxdata/source/scwx/wsr88d/nexrad_file_factory.cpp index 8ef0c49d..ff5e241f 100644 --- a/wxdata/source/scwx/wsr88d/nexrad_file_factory.cpp +++ b/wxdata/source/scwx/wsr88d/nexrad_file_factory.cpp @@ -70,9 +70,9 @@ std::shared_ptr NexradFileFactory::Create(std::istream& is) std::string buffer; bool dataValid; - buffer.resize(4); + buffer.resize(8); - is.read(buffer.data(), 4); + is.read(buffer.data(), 8); dataValid = is.good(); is.seekg(pisBegin, std::ios_base::beg); @@ -89,7 +89,7 @@ std::shared_ptr NexradFileFactory::Create(std::istream& is) pis = &ss; pisBegin = ss.tellg(); - ss.read(buffer.data(), 4); + ss.read(buffer.data(), 8); dataValid = ss.good(); ss.seekg(pisBegin, std::ios_base::beg); @@ -118,6 +118,10 @@ std::shared_ptr NexradFileFactory::Create(std::istream& is) { message = std::make_shared(); } + else if (buffer.starts_with("ARCHIVE2")) + { + logger_->warn("ARCHIVE2 format not supported"); + } else { message = std::make_shared(); From 6e7debbb3a9b4b154bdaa16e2ca667a8a47ca9eb Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 24 Jan 2024 00:06:14 -0600 Subject: [PATCH 25/37] Add ARCHIVE2 format definition, difficult to locate online Archived from NCAR: https://github.com/NCAR/lrose-cedric/blob/master/sprint/source/wsr88d.tape-documentation --- wxdata/archive2_format.txt | 509 +++++++++++++++++++++++++++++++++++++ 1 file changed, 509 insertions(+) create mode 100644 wxdata/archive2_format.txt diff --git a/wxdata/archive2_format.txt b/wxdata/archive2_format.txt new file mode 100644 index 00000000..77825083 --- /dev/null +++ b/wxdata/archive2_format.txt @@ -0,0 +1,509 @@ +[Image] NEXRAD DOCUMENTATION +---------------------------------------------------------------------------- + + LEVEL II TAPE DOCUMENTATION + + WSR-88D BASE DATA + +INTRODUCTION: + +Weather Surveillance Radar - 1988 Doppler (WSR-88D), or NEXt Generation +RADar (NEXRAD), Level II data are the base digital data produced by the +signal processor (mean radial velocity, reflectivity, and spectrum width) at +the full spatial and temporal resolution of the radar. Level II data also +contain status messages, performance/maintenance data, volume scan strategy, +clutter filter bypass map, and wideband communication console messages. +These are the same data transmitted over high-speed, wideband communications +to the WSR-88D Radar Product Generator (RPG) for processing by the +meteorological analysis algorithms. + +Initially it was thought that Level II recorders would be used at selected +sites, and only when significant weather events were taking place. As system +development progressed, it became evident that the Level II data would be of +vital importance to ensure proper calibration of the radars and for use by +researchers to investigate events in more detail than would be possible by +using the Level III products. The Level II data can also be used to test +revised algorithms that may later be applied to operational use. + +The NEXRAD agencies (Departments of Commerce, Defense, and Transportation) +recognized the value of Level II data. In June 1994, the agencies agreed to +record Level II data throughout the WSR-88D network. The Level II recording +is not essential to the operational use of the WSR-88D system. The NEXRAD +agencies agreed to certain procedures to minimize the impact of Level II +data collection on the operations of base weather stations, forecast offices +and FAA control locations. The priority of Level II recorder maintenance, +reloading of tapes and continuous recording of data will be assigned by the +local site management. + +RECORDING: + +The vast amounts of data collected at the Radar Data Acquisition (RDA) site +made it mandatory that the most economical recording devices and media +available at that time be used. It was determined that EXABYTE tape drives +and 8mm tapes provided the most viable system. Depending on operation of the +radar, the recorder model used, and station requirements, one tape may be +filled every 1.8 days for each site. Data grade tapes are used for recording +and archiving. Initially, sites were equipped with EXABYTE 8200 recorders. +These tapes can contain up to 2.3 gigabytes per tape. Later, EXABYTE 8500 +recorders were installed which record at higher density with up to 4.7 +gigabytes per tape. Also available are 8500c (capable of recording in a +standard compressed mode), and 8505 which is a half height drive fully +downward compatible. The 8505 records up 4.7 gigabytes in an uncompressed +mode. + +PROCESSING AND ARCHIVING: + +The Level II recorder system consists of an 8mm reorder, 10-tape jukebox +(automated sequential loading of new tapes), an uninterruptable power supply +ans a controller board seated in the RDA computer. Under jukebox operation +the 10-tape supply will last 11 to 27 days depending on the radar scanning +strategies used. Tapes are received at the National Climatic Data Center +(NCDC) from the individual sites in 10-tape cases. Incoming tapes are +processed on a series of 8505 EXABYTE drives, reblocked, cataloged, +inventoried, and archived. The original tapes are sent to an off-site +storage facility for security back-up to the NCDC NEXRAD files. + +SPECIAL NOTE: + +The WSR-88D is a very complex system. Program modifications and engineering +changes are rather constant features during the phase-in process. Some early +pre-production models experienced considerable difficulties in the recording +of Level II data. Even today, tapes are received that contain spurious, +erroneous, or illegal configurations. We have attempted to recover as much +data as possible from these problem tapes. The user is cautioned that these +anomalies may be encountered while reading the archive tapes. Special care +must be taken to ensure that illegal configurations do not contaminate any +summaries or statistical studies. + +NCDC will be glad to assist in solving problems encountered in reading the +tapes, but technical questions about the data themselves must be addressed +to the: + + NWS/Operational Support Facility + Operations Branch + 1200 Westheimer Dr. + Norman, OK 73069 + + Telephone: (405) 366-6530 + FAX: (405) 366-6550 + +Definitive information about all aspects of the Doppler radar is contained +in Federal Meteorological Handbook No. 11 (FMH-11), Volumes A through D. +These may be ordered from the National Climatic Data Center. + +DATA AVAILABILITY: + +As stated previously, all NCDC archives are being generated on EXABYTE 8505 +drives. Users must specify whether they require 8200 or 8500 mode tapes. If +copies are requested in the 8200 mode, two or more output tapes may be +required. A header record will appear on each output tape. + +Each 8mm tape records approximately 10 hours of Volume Coverage Pattern 11 +(VCP 11), 18 hours of VCP 21, or 40 hours of VCP 31 or 32 using the EXABYTE +8200 mode. Using the EXABYTE 8500 mode doubles both the storage capacity and +number of hours of data possible per 8mm tape. + +FORMAT: + +HEADER FILE: The first file on tape contains only one 31616 byte record. +This record is called the header record. + +HEADER RECORD: This 31616 byte "physical record" is divided into 494 +"logical records" of 64 bytes each with position 1 as the first byte. + +POSITIONS FORMAT DESCRIPTION + +1 - 8 C*8 Always ARCHIVE2 + +9 - 12 C*4 4-letter site ID. e.g. KLMB + +13 - 18 C*6 NCDC tape number. e.g. N00001 + +19 Blank + +20 - 28 C*9 Date tape written. dd-MMM-yy e.g. 19-FEB-93 + +29 Blank + +30 - 37 C*8 Time tape written. hh:mm:ss. e.g. 10:22:59 + (local time) + +38 Blank + +39 - 43 C*5 Data Center writing tape: RDASC or NCDC + (Left justified, blank filled) + +44 - 48 C*5 WBAN Number of this NEXRAD site. (This is a unique + 5-digit number assigned at NCDC. Numbers are + contained in the NCDC NEXRAD Station History file + (WSR-88D RDA LOCATIONS). The file also contains the + four letter site ID, Latitude, Longitude, Elevation, + and Standard location name.) + +49 - 53 C*5 Tape output mode. Current values are 8200, 8500, + 8500C + +54 - 58 C*5 A volume number to be used for copies and extractions + of data from tapes. The form would be VOL01, VOL02, + VOL03 ....VOLnn. + +59 - 64 Blank (Available for future use.) + +65 - 31616 May be used for internal controls or other + information at each archive center. Information of + value to users will be documented at the time of tape + shipment. + +During the process of copying archive tapes, positions 1-18 and 44-48 will +be duplicated. New values will be written in positions 19-43 and 49-58. + +DATA FILES: + +A new data file is created upon completion of a volume scan. A data file +contains a title, a complete radar volume scan (360 degree revolutions at +each specified elevation cut) of base data, digital radar data message, and +any control/response messages from the RDA to the RPG. The title is the +first record located in each data file and contains a file name, creation +date, and creation time. After the title record through the remainder of the +data file, variable length records containing base data intermixed with +control/response messages are recorded. Messages and base data are +distinguishable by a message header coded for either digital radar base data +or one of the thirteen types of messages. The message header uses a format +common to both data and messages and is included in each 2432 byte packet. +Depending on the predefined volume scan strategy (selected elevations, sweep +rate, pulse rate etc.) used during the collection period, each data file +could contain either five, six, or ten minutes of base data. +Control/response messages are used during actual operations and are of +limited use for post analyses. + +DATA TYPES SUPPORTED WITHIN DATA FILES: + +A Concurrent minicomputer serves as the host computer for generation of all +Archive Level II data. Depending on the computer used for reading the tapes, +the data types may be different from those used in the Concurrent system. +The Concurrent computer byte (8 bits) structure places bit 0 as the left +most bit and designates bit 0 as the Most Significant Bit (MSB). Bit 7 for a +byte, bit 15 for a halfword (2 bytes), bit 31 for a fullword (4 bytes) and +bit 63 for a double word (8 bytes) are all the Least Significant Bit (LSB) +for their respective data formats. + +Level II is recorded using the following data types: + + Unsigned byte (byte) - number ranging from 0-255 + + Character (C) - Standard ASCII characters + + Signed Short Integer (I*2) - Most Significant Bit (MSB) is the sign bit + (bit 0). (1-Negative, 0-Positive). + + Signed Long Integer (I*4) - MSB (bit 0) is the sign bit. + + Single Precision Real (R*4) - MSB (bit 0) is the sign bit (positive), + bit 1-7 is the exponent in excess-64 notation format, and bit 8-31 is + the fraction field. An example may be helpful: + + Starting with 4180 69E8 (hex), the sign bit = 0 (positive), the + exponent = +1 [e.g. 41 (hex) converted to 65 (dec) - 64 (excess 64 + notation) = +1], and the fraction 8069E8 (hex) shifted by exponent of + +1 gives 8.069E8 (hex). To convert 8.069E8 (hex) to decimal, start with + the whole number 8 (hex) which in this case equals 8 (dec). Next, the + precision of the fraction .069E8 must be noted. This fraction has 5 + digits of precision. Next, the fraction portion in hex (069E8) is + converted to decimal (27112) and divided by 16 raised to the power of + the precision of the fraction (5). In other words 27112/(16**5) = + .02585 plus the whole number 8, gives 8.02585 in decimal. + +DATA RECORDS: + +Within the data file, base data and control/response messages are stored +using a variable record-length structure. The convention here is to begin +with byte 0 as the first byte. Included as the first record of each data +file is a volume scan title containing the following information: + +Bytes Format Description + +0-8 C*9 Filename (root) - "ARCHIVE2." + +9-11 C*3 Filename (extension) - "1", "2", etc. + +12-15 I*4 Modified Julian Date referenced from 1/1/70 + +16-19 I*4 Time - Milliseconds from midnight (UTC) of the day + when the file was created. + +20-23 Unused + +All remaining records in the data file are composed of data and +command/response messages which are initially stored in separate 2432 byte +packets within an RDA memory buffer. During the archive process the packets +are copied from memory and grouped together to form a record. Record lengths +are variable and are always sized in multiples of the 2432 byte packets. +During the reblocking process, physical records are set to 31616 bytes (2432 +x 13). + +The following example shows a portion of one packet which includes +Concurrent computer Channel Terminal Manager (CTM) information, a message +header, and a digital radar data message containing reflectivity only. + +0000 0000 0980 0000 0002 0000 04B8 0001 +0060 1E9E 04B0 1841 0001 0001 0480 14A2 +1E9E 1234 6530 0059 0001 0058 0001 0000 +FE89 03E8 00FA 01CC 0000 0001 4180 69E8 +0064 0000 0000 0000 0015 0000 0000 0000 +0000 0064 0000 0000 0000 FFF4 0064 0000 +0000 0000 0000 0000 0000 0000 0000 0000 +0000 0000 0000 0000 0000 0000 0000 0000 +005A 5A00 0070 6D51 6455 6060 4F54 0040 +5C3F 4049 4900 4D42 4349 434E 4B3D 4430 +4340 3F3D 4644 4443 3A3D 473F 3A3A 3D3D +3C45 3A43 433C 3E43 413C 393F 3F40 4038 + (etc.) + +Using the above example, each portion of the packet is described in detail. +Remember, this packet may be one of several contained in one record within +the data file. + + Bytes 0-11 (halfwords 1-6) Channel Terminal Manager (CTM) + information: +0000 0000 0980 0000 0002 0000 04B8 0001 +0060 1E9E 04B0 1841 0001 0001 0480 14A2 Archive II (the data tape) is a +1E9E 1234 6530 0059 0001 0058 0001 0000 copy of messages or data packets +FE89 03E8 00FA 01CC 0000 0001 4180 69E8 prepared for transmission from the +0064 0000 0000 0000 0015 0000 0000 0000 RDA to the RPG. CTM information is +0000 0064 0000 0000 0000 FFF4 0064 0000 attached to a message or data +0000 0000 0000 0000 0000 0000 0000 0000 packet for checking data integrity +0000 0000 0000 0000 0000 0000 0000 0000 during the transmission process +005A 5A00 0070 6D51 6455 6060 4F54 0040 and is of no importance to the base +5C3F 4049 4900 4D42 4349 434E 4B3D 4430 data (omit or read past these +4340 3F3D 4644 4443 3A3D 473F 3A3A 3D3D bytes). +3C45 3A43 433C 3E43 413C 393F 3F40 4038 + (etc.) + + Bytes 12-27 (halfwords 7-14) Message Header: + +0000 0000 0980 0000 0002 0000 04B8 0001 This information is +0060 1E9E 04B0 1841 0001 0001 0480 14A2 used to identify +1E9E 1234 6530 0059 0001 0058 0001 0000 either base data or one of thirteen +FE89 03E8 00FA 01CC 0000 0001 4180 69E8 types of messages that may follow +0064 0000 0000 0000 0015 0000 0000 0000 in bytes 28 - 2431. This header +0000 0064 0000 0000 0000 FFF4 0064 0000 includes the information indicated +0000 0000 0000 0000 0000 0000 0000 0000 below: +0000 0000 0000 0000 0000 0000 0000 0000 +005A 5A00 0070 6D51 6455 6060 4F54 0040 +5C3F 4049 4900 4D42 4349 434E 4B3D 4430 +4340 3F3D 4644 4443 3A3D 473F 3A3A 3D3D +3C45 3A43 433C 3E43 413C 393F 3F40 4038 + (etc.) + +Halfword Format Description + +7 I*2 Message size in halfwords measured from this + halfword to the end of the record. + +8 I*1 (Left Byte) Channel ID: + 0 = Non-Redundant Site + 1 = Redundant Site Channel 1 + 2 = Redundant Site Channel 2 + +8 I*1 (Right Byte) Message type, where: + 1 = DIGITAL RADAR DATA (This message + may contain a combination of either + reflectivity, aliased velocity, or + spectrum width) + 2 = RDA STATUS DATA. + 3 = PERFORMANCE/MAINTENANCE DATA. + 4 = CONSOLE MESSAGE - RDA TO RPG. + 5 = MAINTENANCE LOG DATA. + 6 = RDA CONTROL COMMANDS. + 7 = VOLUME COVERAGE PATTERN. + 8 = CLUTTER CENSOR ZONES. + 9 = REQUEST FOR DATA. + 10 = CONSOLE MESSAGE - RPG TO RDA. + 11 = LOOP BACK TEST - RDA TO RPG. + 12 = LOOP BACK TEST - RPG TO RDA. + 13 = CLUTTER FILTER BYPASS MAP - RDA to RPG. + 14 = EDITED CLUTTER FILTER BYPASS MAP - RPG to RDA. + +9 I*2 I.D. Sequence = 0 to 7FFF, then roll over back to 0. + +10 I*2 Modified Julian date starting from 1/1/70. + +11-12 I*4 Generation time of messages in milliseconds of day past + midnight (UTC). This time may be different than time + listed in halfwords 15-16 defined below. + +13 I*2 Number of message segments. Messages larger than message + size (halfword 7 defined above) are segmented and + recorded in separate data packets. + +14 I*2 Message segment number. + + Bytes 28-127 (halfwords 15-64) Digital Radar Data Header: + +0000 0000 0980 0000 0002 0000 04B8 0001 This information describes the +0060 1E9E 04B0 1841 0001 0001 0480 14A2 date, time, azimuth, +1E9E 1234 6530 0059 0001 0058 0001 0000 elevation, and type +FE89 03E8 00FA 01CC 0000 0001 4180 69E8 of base data included +0064 0000 0000 0000 0015 0000 0000 0000 in the radial. This +0000 0064 0000 0000 0000 FFF4 0064 0000 header includes the +0000 0000 0000 0000 0000 0000 0000 0000 following +0000 0000 0000 0000 0000 0000 0000 0000 information: +005A 5A00 0070 6D51 6455 6060 4F54 0040 +5C3F 4049 4900 4D42 4349 434E 4B3D 4430 +4340 3F3D 4644 4443 3A3D 473F 3A3A 3D3D +3C45 3A43 433C 3E43 413C 393F 3F40 4038 + (etc.) + +Halfword Format Description + +15-16 I*4 Collection time for this radial in + milliseconds of the day from midnight (UTC). + +17 I*2 Modified Julian date referenced from 1/1/70. + +18 I*2 Unambiguous range (scaled: Value/10. = KM). + +19 I*2 Azimuth angle (coded: [Value/8.]*[180./4096.] = DEG). + An azimuth of "0 degrees" points to true north while "90 + degrees" points east. Rotation is always clockwise as + viewed from above the radar. + +20 I*2 Radial number within the elevation scan. + +21 I*2 Radial status where: + 0 = START OF NEW ELEVATION. + 1 = INTERMEDIATE RADIAL. + 2 = END OF ELEVATION. + 3 = BEGINNING OF VOLUME SCAN. + 4 = END OF VOLUME SCAN. + +22 I*2 Elevation angle (coded:[Value/8.]*[180./4096.] = DEG). + An elevation of "0 degree" is parallel to the pedestal + base while "90 degrees" is perpendicular to the pedestal + base. + +23 I*2 RDA elevation number within the volume scan. + +24 I*2 Range to first gate of reflectivity data (METERS). + Range may be negative to account for system delays + in transmitter and/or receiver components. + +25 I*2 Range to first gate of Doppler data. + Doppler data - velocity and spectrum width (METERS). + Range may be negative to account for system delays in + transmitter and/or receiver components. + +26 I*2 Reflectivity data gate size (METERS). + +27 I*2 Doppler data gate size (METERS). + +28 I*2 Number of reflectivity gates. + +29 I*2 Number of velocity and/or spectrum width data gates. + +30 I*2 Sector number within cut. + +31-32 R*4 System gain calibration constant (dB biased). + +33 I*2 Reflectivity data pointer (byte # from the start of + digital radar data message header). This pointer + locates the beginning of reflectivity data. + +34 I*2 Velocity data pointer (byte # from the start of digital + radar data message header). This pointer locates + beginning of velocity data. + +35 I*2 Spectrum-width pointer (byte # from the start of + digital radar data message header). This pointer + locates beginning of spectrum-width data. + +36 I*2 Doppler velocity resolution. + Value of: 2 = 0.5 m/s + 4 = 1.0 + +37 I*2 Volume coverage pattern. + Value of: 11 = 16 elev. scans/ 5 mins. + 21 = 11 elev. scans/ 6 mins. + 31 = 8 elev. scans/ 10 mins. + 32 = 7 elev. scans/ 10 mins. + +38-41 Unused. Reserved for V&V Simulator. + +42 I*2 Reflectivity data pointer for Archive II playback. + Archive II playback pointer used exclusively by RDA. + +43 I*2 Velocity data pointer for Archive II playback. + Archive II playback pointer used exclusively by RDA. + +44 I*2 Spectrum-width data pointer for Archive II playback. + Archive II playback pointer used exclusively by RDA. + +45 I*2 Nyquist velocity (scaled: Value/100. = M/S). + +46 I*2 Atmospheric attenuation factor (scaled: + [Value/1000. = dB/KM]). + +47 I*2 Threshold parameter for minimum difference in echo + power between two resolution volumes for them not + to be labeled range ambiguous (i.e.,overlaid) + [Value/10. = Watts]. + +48-64 Unused. + + Bytes 128-2431 (halfwords 65-1216) Base Data: + +0000 0000 0980 0000 0002 0000 04B8 0001 This information includes the three +0060 1E9E 04B0 1841 0001 0001 0480 14A2 base data moments; reflectivity, +1E9E 1234 6530 0059 0001 0058 0001 0000 velocity and spectrum width. +FE89 03E8 00FA 01CC 0000 0001 4180 69E8 Depending on the collection method, +0064 0000 0000 0000 0015 0000 0000 0000 up to three base data moments may +0000 0064 0000 0000 0000 FFF4 0064 0000 exist in this section of the +0000 0000 0000 0000 0000 0000 0000 0000 packet. (For this example, only +0000 0000 0000 0000 0000 0000 0000 0000 reflectivity is present.) Base data +005A 5A00 0070 6D51 6455 6060 4F54 0040 is coded and placed +5C3F 4049 4900 4D42 4349 434E 4B3D 4430 in a single byte and +4340 3F3D 4644 4443 3A3D 473F 3A3A 3D3D is archived in the +3C45 3A43 433C 3E43 413C 393F 3F40 4038 following format: + (etc.) + +Halfword Format Description + +65-294 BYTE Reflectivity data (0 - 460 gates) (coded: + [((Value-2)/2.)-32. = dBZ], for Value of 0 or + 1 see note below). + +65-754 BYTE Doppler velocity data (coded: for doppler velocity + resolution of 0.5 M/S, [((Value-2)/2.)-63.5 = M/S]; + for doppler resolution of 1.0 M/S, [(Value-2)-127.] + = M/S], for Value of 0 or 1 see note below), (0 - 92 + gates). Starting data location depends on length of + the reflectivity field, stop location depends on length + of the velocity field. Velocity data is range unambiguous + out to 230 KM. + +65-1214 BYTE Doppler spectrum width (coded: [((Value - 2)/2.)-63.5 + = M/S], for Value of 0 or 1 see note below), (0 - 920 + gates). Starting data location depends on length of + the reflectivity and velocity fields, stop location + depends on length of the spectrum width field. Spectrum + width is range unambiguous out to 230 KM. + + Four bytes of trailer characters referred to the Frame + Check Sequence (FCS) follow the data. In cases where + the three moments are not all present or the number of + gates for each moment have been reduced, the record is + padded out to a constant size of 1216 halfwords (2432 + bytes) following the trailer characters. + +Note: + +Any base data value of 0 is data below Signal to Noise Ratio(SNR) thresholds +set for that specific base data. Any base data value of 1 is data considered +range ambiguous (i.e., overlaid). +---------------------------------------------------------------------------- +[Image] NEXRAD DOCUMENTATION + +---------------------------------------------------------------------------- +http://www.ncdc.noaa.gov/pub/data/nexrad/tapeii.html +Created by Dick Cram (dcram@ncdc.noaa.gov) +Last updated 18 April 96 From 3705ca4f2e069d98480600cc367487fe515fe51a Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 24 Jan 2024 08:14:53 -0600 Subject: [PATCH 26/37] Add missing default case statement --- wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp b/wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp index 37500ed6..abd03a12 100644 --- a/wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp +++ b/wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp @@ -346,6 +346,9 @@ DigitalRadarData::Impl::MomentDataBlock::MomentDataBlock( p->scale_ = 2.0f; p->offset_ = 129.0f; // (64.5 * 2) break; + + default: + break; } } std::uint16_t From fdd09011f499c9ebe1f73dde1496db54d705f2ec Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 24 Jan 2024 22:32:15 -0600 Subject: [PATCH 27/37] If the input stream fails during level 2 parsing, avoid an infinite loop --- wxdata/source/scwx/wsr88d/ar2v_file.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wxdata/source/scwx/wsr88d/ar2v_file.cpp b/wxdata/source/scwx/wsr88d/ar2v_file.cpp index 2179b835..714e15a1 100644 --- a/wxdata/source/scwx/wsr88d/ar2v_file.cpp +++ b/wxdata/source/scwx/wsr88d/ar2v_file.cpp @@ -355,13 +355,13 @@ void Ar2vFileImpl::ParseLDMRecord(std::istream& is) { is.seekg(-2, std::ios_base::cur); } - } while (!is.eof() && nextSize == 0u); + } while (!is.eof() && !is.fail() && nextSize == 0u); - if (!is.eof() && offset != 0) + if (!is.eof() && !is.fail() && offset != 0) { logger_->trace("Next record offset by {} bytes", offset); } - else if (is.eof()) + else if (is.eof() || is.fail()) { break; } From 243a7c870cbc6a93392f1b8cc46ec4f7d5481e97 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 27 Jan 2024 01:17:34 -0600 Subject: [PATCH 28/37] Update Archive II processing to better handle older data sets --- wxdata/include/scwx/wsr88d/ar2v_file.hpp | 13 +- wxdata/source/scwx/wsr88d/ar2v_file.cpp | 125 ++++++++++-------- .../wsr88d/rda/level2_message_factory.cpp | 32 ++++- .../scwx/wsr88d/rda/level2_message_header.cpp | 5 +- 4 files changed, 110 insertions(+), 65 deletions(-) diff --git a/wxdata/include/scwx/wsr88d/ar2v_file.hpp b/wxdata/include/scwx/wsr88d/ar2v_file.hpp index 0df24765..1f3ab0cc 100644 --- a/wxdata/include/scwx/wsr88d/ar2v_file.hpp +++ b/wxdata/include/scwx/wsr88d/ar2v_file.hpp @@ -32,15 +32,18 @@ public: Ar2vFile(Ar2vFile&&) noexcept; Ar2vFile& operator=(Ar2vFile&&) noexcept; - uint32_t julian_date() const; - uint32_t milliseconds() const; - std::string icao() const; + std::uint32_t julian_date() const; + std::uint32_t milliseconds() const; + std::string icao() const; + + std::size_t message_count() const; std::chrono::system_clock::time_point start_time() const; std::chrono::system_clock::time_point end_time() const; - std::map> radar_data() const; - std::shared_ptr vcp_data() const; + std::map> + radar_data() const; + std::shared_ptr vcp_data() const; std::tuple, float, std::vector> GetElevationScan(rda::DataBlockType dataBlockType, diff --git a/wxdata/source/scwx/wsr88d/ar2v_file.cpp b/wxdata/source/scwx/wsr88d/ar2v_file.cpp index 714e15a1..6d951d6c 100644 --- a/wxdata/source/scwx/wsr88d/ar2v_file.cpp +++ b/wxdata/source/scwx/wsr88d/ar2v_file.cpp @@ -19,6 +19,7 @@ # pragma GCC diagnostic ignored "-Wdeprecated-copy" #endif +#include #include #include #include @@ -42,16 +43,7 @@ static const auto logger_ = util::Logger::Create(logPrefix_); class Ar2vFileImpl { public: - explicit Ar2vFileImpl() : - tapeFilename_ {}, - extensionNumber_ {}, - julianDate_ {0}, - milliseconds_ {0}, - icao_ {}, - vcpData_ {nullptr}, - radarData_ {}, - index_ {}, - rawRecords_ {} {}; + explicit Ar2vFileImpl() {}; ~Ar2vFileImpl() = default; std::size_t DecompressLDMRecords(std::istream& is); @@ -61,20 +53,22 @@ public: void ParseLDMRecord(std::istream& is); void ProcessRadarData(const std::shared_ptr& message); - std::string tapeFilename_; - std::string extensionNumber_; - std::uint32_t julianDate_; - std::uint32_t milliseconds_; - std::string icao_; + std::string tapeFilename_ {}; + std::string extensionNumber_ {}; + std::uint32_t julianDate_ {0}; + std::uint32_t milliseconds_ {0}; + std::string icao_ {}; - std::shared_ptr vcpData_; - std::map> radarData_; + std::size_t messageCount_ {0}; + + std::shared_ptr vcpData_ {}; + std::map> radarData_ {}; std::map>> - index_; + index_ {}; - std::list rawRecords_; + std::list rawRecords_ {}; }; Ar2vFile::Ar2vFile() : p(std::make_unique()) {} @@ -98,6 +92,11 @@ std::string Ar2vFile::icao() const return p->icao_; } +std::size_t Ar2vFile::message_count() const +{ + return p->messageCount_; +} + std::chrono::system_clock::time_point Ar2vFile::start_time() const { return util::TimePoint(p->julianDate_, p->milliseconds_); @@ -235,6 +234,10 @@ bool Ar2vFile::LoadData(std::istream& is) dataValid = false; } + // Trim spaces and null characters from the end of the ICAO + boost::trim_right_if(p->icao_, + [](char x) { return std::isspace(x) || x == '\0'; }); + if (dataValid) { logger_->debug("Filename: {}", p->tapeFilename_); @@ -334,55 +337,71 @@ void Ar2vFileImpl::ParseLDMRecords() void Ar2vFileImpl::ParseLDMRecord(std::istream& is) { + static constexpr std::size_t kDefaultSegmentSize = 2432; + static constexpr std::size_t kCtmHeaderSize = 12; + auto ctx = rda::Level2MessageFactory::CreateContext(); - // The communications manager inserts an extra 12 bytes at the beginning - // of each record - is.seekg(12, std::ios_base::cur); - - while (!is.eof()) + while (!is.eof() && !is.fail()) { - off_t offset = 0; - std::uint16_t nextSize = 0u; - do + // The communications manager inserts an extra 12 bytes at the beginning + // of each record + is.seekg(kCtmHeaderSize, std::ios_base::cur); + + // Each message requires 2432 bytes of storage, with the exception of + // Message Types 29 and 31. + std::size_t messageSize = kDefaultSegmentSize - kCtmHeaderSize; + + // Mark current position + std::streampos messageStart = is.tellg(); + + // Parse the header + rda::Level2MessageHeader messageHeader; + bool headerValid = messageHeader.Parse(is); + is.seekg(messageStart, std::ios_base::beg); + + if (headerValid) { - is.read(reinterpret_cast(&nextSize), 2); - if (nextSize == 0) + std::uint8_t messageType = messageHeader.message_type(); + + // Each message requires 2432 bytes of storage, with the exception of + // Message Types 29 and 31. + if (messageType == 29 || messageType == 31) { - offset += 2; + if (messageHeader.message_size() == 65535) + { + messageSize = (static_cast( + messageHeader.number_of_message_segments()) + << 16) + + messageHeader.message_segment_number(); + } + else + { + messageSize = + static_cast(messageHeader.message_size()) * 2; + } } - else + + // Parse the current message + rda::Level2MessageInfo msgInfo = + rda::Level2MessageFactory::Create(is, ctx); + + if (msgInfo.messageValid) { - is.seekg(-2, std::ios_base::cur); + HandleMessage(msgInfo.message); } - } while (!is.eof() && !is.fail() && nextSize == 0u); - - if (!is.eof() && !is.fail() && offset != 0) - { - logger_->trace("Next record offset by {} bytes", offset); - } - else if (is.eof() || is.fail()) - { - break; } - rda::Level2MessageInfo msgInfo = - rda::Level2MessageFactory::Create(is, ctx); - if (!msgInfo.headerValid) - { - // Invalid message - break; - } - - if (msgInfo.messageValid) - { - HandleMessage(msgInfo.message); - } + // Skip to next message + is.seekg(messageStart + static_cast(messageSize), + std::ios_base::beg); } } void Ar2vFileImpl::HandleMessage(std::shared_ptr& message) { + ++messageCount_; + switch (message->header().message_type()) { case static_cast(rda::MessageId::VolumeCoveragePatternData): diff --git a/wxdata/source/scwx/wsr88d/rda/level2_message_factory.cpp b/wxdata/source/scwx/wsr88d/rda/level2_message_factory.cpp index 56141da9..2a478a2f 100644 --- a/wxdata/source/scwx/wsr88d/rda/level2_message_factory.cpp +++ b/wxdata/source/scwx/wsr88d/rda/level2_message_factory.cpp @@ -70,6 +70,30 @@ Level2MessageInfo Level2MessageFactory::Create(std::istream& is, info.headerValid = header.Parse(is); info.messageValid = info.headerValid; + std::uint16_t segment = 0; + std::uint16_t totalSegments = 0; + std::size_t dataSize = 0; + + if (info.headerValid) + { + if (header.message_size() == 65535) + { + segment = 1; + totalSegments = 1; + dataSize = + (static_cast(header.number_of_message_segments()) + << 16) + + header.message_segment_number(); + } + else + { + segment = header.message_segment_number(); + totalSegments = header.number_of_message_segments(); + dataSize = static_cast(header.message_size()) * 2 - + Level2MessageHeader::SIZE; + } + } + if (info.headerValid && create_.find(header.message_type()) == create_.end()) { logger_->warn("Unknown message type: {}", @@ -79,10 +103,7 @@ Level2MessageInfo Level2MessageFactory::Create(std::istream& is, if (info.messageValid) { - uint16_t segment = header.message_segment_number(); - uint16_t totalSegments = header.number_of_message_segments(); - uint8_t messageType = header.message_type(); - size_t dataSize = header.message_size() * 2 - Level2MessageHeader::SIZE; + std::uint8_t messageType = header.message_type(); std::istream* messageStream = nullptr; @@ -165,8 +186,7 @@ Level2MessageInfo Level2MessageFactory::Create(std::istream& is, else if (info.headerValid) { // Seek to the end of the current message - is.seekg(header.message_size() * 2 - rda::Level2MessageHeader::SIZE, - std::ios_base::cur); + is.seekg(dataSize, std::ios_base::cur); } if (info.message == nullptr) diff --git a/wxdata/source/scwx/wsr88d/rda/level2_message_header.cpp b/wxdata/source/scwx/wsr88d/rda/level2_message_header.cpp index 5aabc20c..a8ed4125 100644 --- a/wxdata/source/scwx/wsr88d/rda/level2_message_header.cpp +++ b/wxdata/source/scwx/wsr88d/rda/level2_message_header.cpp @@ -130,7 +130,10 @@ bool Level2MessageHeader::Parse(std::istream& is) { if (p->messageSize_ < 9) { - logger_->warn("Invalid message size: {}", p->messageSize_); + if (p->messageSize_ != 0) + { + logger_->warn("Invalid message size: {}", p->messageSize_); + } headerValid = false; } if (p->millisecondsOfDay_ > 86'399'999u) From 636f444c40d547f7c315dfc3cb73e59b5b39f325 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 27 Jan 2024 01:18:25 -0600 Subject: [PATCH 29/37] Support ARCHIVE2 --- .../scwx/qt/manager/radar_product_manager.cpp | 16 ++++++++++++---- .../source/scwx/wsr88d/nexrad_file_factory.cpp | 6 +----- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp b/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp index bc5e8b98..60cb1b31 100644 --- a/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp @@ -231,7 +231,8 @@ public: LoadNexradFile(CreateNexradFileFunction load, std::shared_ptr request, std::mutex& mutex, - std::chrono::system_clock::time_point time = {}); + std::chrono::system_clock::time_point time = {}, + const std::string& radarId = {}); const std::string radarId_; bool initialized_; @@ -957,14 +958,15 @@ void RadarProductManagerImpl::LoadNexradFileAsync( { boost::asio::post(threadPool_, [=, &mutex]() - { LoadNexradFile(load, request, mutex, time); }); + { LoadNexradFile(load, request, mutex, time, radarId_); }); } void RadarProductManagerImpl::LoadNexradFile( CreateNexradFileFunction load, std::shared_ptr request, std::mutex& mutex, - std::chrono::system_clock::time_point time) + std::chrono::system_clock::time_point time, + const std::string& radarId) { std::unique_lock lock {mutex}; @@ -987,8 +989,14 @@ void RadarProductManagerImpl::LoadNexradFile( record->set_time(time); } + std::string recordRadarId = (record->radar_id()); + if (recordRadarId.empty()) + { + recordRadarId = radarId; + } + std::shared_ptr manager = - RadarProductManager::Instance(record->radar_id()); + RadarProductManager::Instance(recordRadarId); manager->Initialize(); record = manager->p->StoreRadarProductRecord(record); diff --git a/wxdata/source/scwx/wsr88d/nexrad_file_factory.cpp b/wxdata/source/scwx/wsr88d/nexrad_file_factory.cpp index ff5e241f..98d83ff2 100644 --- a/wxdata/source/scwx/wsr88d/nexrad_file_factory.cpp +++ b/wxdata/source/scwx/wsr88d/nexrad_file_factory.cpp @@ -114,14 +114,10 @@ std::shared_ptr NexradFileFactory::Create(std::istream& is) if (dataValid) { - if (buffer.starts_with("AR2V")) + if (buffer.starts_with("AR2V") || buffer.starts_with("ARCHIVE2")) { message = std::make_shared(); } - else if (buffer.starts_with("ARCHIVE2")) - { - logger_->warn("ARCHIVE2 format not supported"); - } else { message = std::make_shared(); From fb7f25e0bd1f1155d5e1663ca3f1c7ae7ecdc1ac Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 27 Jan 2024 13:06:18 -0600 Subject: [PATCH 30/37] Explicit capture of this --- scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp b/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp index 60cb1b31..c0d9370d 100644 --- a/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp @@ -957,7 +957,7 @@ void RadarProductManagerImpl::LoadNexradFileAsync( std::chrono::system_clock::time_point time) { boost::asio::post(threadPool_, - [=, &mutex]() + [=, this, &mutex]() { LoadNexradFile(load, request, mutex, time, radarId_); }); } From a0f43b5f3f1d9802ec12d1dc3d61f4af73ccb656 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 27 Jan 2024 16:39:49 -0600 Subject: [PATCH 31/37] Handle negative data moment ranges for level 2 data --- .../scwx/qt/view/level2_product_view.cpp | 130 +++++++++--------- .../scwx/wsr88d/rda/digital_radar_data.hpp | 4 +- .../wsr88d/rda/digital_radar_data_generic.hpp | 2 +- .../scwx/wsr88d/rda/generic_radar_data.hpp | 2 +- .../scwx/wsr88d/rda/digital_radar_data.cpp | 14 +- .../wsr88d/rda/digital_radar_data_generic.cpp | 4 +- 6 files changed, 81 insertions(+), 75 deletions(-) diff --git a/scwx-qt/source/scwx/qt/view/level2_product_view.cpp b/scwx-qt/source/scwx/qt/view/level2_product_view.cpp index 24d7eacc..0e95ddd3 100644 --- a/scwx-qt/source/scwx/qt/view/level2_product_view.cpp +++ b/scwx-qt/source/scwx/qt/view/level2_product_view.cpp @@ -522,17 +522,17 @@ void Level2ProductView::ComputeSweep() } // Compute threshold at which to display an individual bin (minimum of 2) - const uint16_t snrThreshold = - std::max(2, momentData0->snr_threshold_raw()); + const std::uint16_t snrThreshold = + std::max(2, momentData0->snr_threshold_raw()); // Start radial is always 0, as coordinates are calculated for each sweep constexpr std::uint16_t startRadial = 0u; for (auto& radialPair : *radarData) { - uint16_t radial = radialPair.first; - auto& radialData = radialPair.second; - auto momentData = radialData->moment_data_block(p->dataBlockType_); + std::uint16_t radial = radialPair.first; + auto& radialData = radialPair.second; + auto momentData = radialData->moment_data_block(p->dataBlockType_); if (momentData0->data_word_size() != momentData->data_word_size()) { @@ -541,65 +541,70 @@ void Level2ProductView::ComputeSweep() } // Compute gate interval - const std::uint16_t dataMomentInterval = + const std::int32_t dataMomentInterval = momentData->data_moment_range_sample_interval_raw(); - const std::uint16_t dataMomentIntervalH = dataMomentInterval / 2; - const std::uint16_t dataMomentRange = - std::max(momentData->data_moment_range_raw(), dataMomentIntervalH); + const std::int32_t dataMomentIntervalH = dataMomentInterval / 2; + const std::int32_t dataMomentRange = std::max( + momentData->data_moment_range_raw(), dataMomentIntervalH); // Compute gate size (number of base 250m gates per bin) - const uint16_t gateSizeMeters = - static_cast(radarProductManager->gate_size()); - const uint16_t gateSize = - std::max(1, dataMomentInterval / gateSizeMeters); + const std::int32_t gateSizeMeters = + static_cast(radarProductManager->gate_size()); + const std::int32_t gateSize = + std::max(1, dataMomentInterval / gateSizeMeters); // Compute gate range [startGate, endGate) - const uint16_t startGate = + const std::int32_t startGate = (dataMomentRange - dataMomentIntervalH) / gateSizeMeters; - const uint16_t numberOfDataMomentGates = - std::min(momentData->number_of_data_moment_gates(), - static_cast(gates)); - const uint16_t endGate = - std::min(startGate + numberOfDataMomentGates * gateSize, - common::MAX_DATA_MOMENT_GATES); + const std::int32_t numberOfDataMomentGates = + std::min(momentData->number_of_data_moment_gates(), + static_cast(gates)); + const std::int32_t endGate = std::min( + startGate + numberOfDataMomentGates * gateSize, + static_cast(common::MAX_DATA_MOMENT_GATES)); - const uint8_t* dataMomentsArray8 = nullptr; - const uint16_t* dataMomentsArray16 = nullptr; - const uint8_t* cfpMomentsArray = nullptr; + const std::uint8_t* dataMomentsArray8 = nullptr; + const std::uint16_t* dataMomentsArray16 = nullptr; + const std::uint8_t* cfpMomentsArray = nullptr; if (momentData->data_word_size() == 8) { dataMomentsArray8 = - reinterpret_cast(momentData->data_moments()); + reinterpret_cast(momentData->data_moments()); } else { dataMomentsArray16 = - reinterpret_cast(momentData->data_moments()); + reinterpret_cast(momentData->data_moments()); } if (cfpMoments.size() > 0) { - cfpMomentsArray = reinterpret_cast( + cfpMomentsArray = reinterpret_cast( radialData->moment_data_block(wsr88d::rda::DataBlockType::MomentCfp) ->data_moments()); } - for (uint16_t gate = startGate, i = 0; gate + gateSize <= endGate; + for (std::int32_t gate = startGate, i = 0; gate + gateSize <= endGate; gate += gateSize, ++i) { - size_t vertexCount = (gate > 0) ? 6 : 3; + if (gate < 0) + { + continue; + } + + std::size_t vertexCount = (gate > 0) ? 6 : 3; // Store data moment value if (dataMomentsArray8 != nullptr) { - uint8_t dataValue = dataMomentsArray8[i]; + std::uint8_t dataValue = dataMomentsArray8[i]; if (dataValue < snrThreshold && dataValue != RANGE_FOLDED) { continue; } - for (size_t m = 0; m < vertexCount; m++) + for (std::size_t m = 0; m < vertexCount; m++) { dataMoments8[mIndex++] = dataMomentsArray8[i]; @@ -611,13 +616,13 @@ void Level2ProductView::ComputeSweep() } else { - uint16_t dataValue = dataMomentsArray16[i]; + std::uint16_t dataValue = dataMomentsArray16[i]; if (dataValue < snrThreshold && dataValue != RANGE_FOLDED) { continue; } - for (size_t m = 0; m < vertexCount; m++) + for (std::size_t m = 0; m < vertexCount; m++) { dataMoments16[mIndex++] = dataMomentsArray16[i]; } @@ -626,18 +631,18 @@ void Level2ProductView::ComputeSweep() // Store vertices if (gate > 0) { - const uint16_t baseCoord = gate - 1; + const std::uint16_t baseCoord = gate - 1; - size_t offset1 = ((startRadial + radial) % radials * - common::MAX_DATA_MOMENT_GATES + - baseCoord) * - 2; - size_t offset2 = offset1 + gateSize * 2; - size_t offset3 = (((startRadial + radial + 1) % radials) * - common::MAX_DATA_MOMENT_GATES + - baseCoord) * - 2; - size_t offset4 = offset3 + gateSize * 2; + std::size_t offset1 = ((startRadial + radial) % radials * + common::MAX_DATA_MOMENT_GATES + + baseCoord) * + 2; + std::size_t offset2 = offset1 + gateSize * 2; + std::size_t offset3 = (((startRadial + radial + 1) % radials) * + common::MAX_DATA_MOMENT_GATES + + baseCoord) * + 2; + std::size_t offset4 = offset3 + gateSize * 2; vertices[vIndex++] = coordinates[offset1]; vertices[vIndex++] = coordinates[offset1 + 1]; @@ -661,16 +666,16 @@ void Level2ProductView::ComputeSweep() } else { - const uint16_t baseCoord = gate; + const std::uint16_t baseCoord = gate; - size_t offset1 = ((startRadial + radial) % radials * - common::MAX_DATA_MOMENT_GATES + - baseCoord) * - 2; - size_t offset2 = (((startRadial + radial + 1) % radials) * - common::MAX_DATA_MOMENT_GATES + - baseCoord) * - 2; + std::size_t offset1 = ((startRadial + radial) % radials * + common::MAX_DATA_MOMENT_GATES + + baseCoord) * + 2; + std::size_t offset2 = (((startRadial + radial + 1) % radials) * + common::MAX_DATA_MOMENT_GATES + + baseCoord) * + 2; vertices[vIndex++] = p->latitude_; vertices[vIndex++] = p->longitude_; @@ -866,25 +871,26 @@ Level2ProductView::GetBinLevel(const common::Coordinate& coordinate) const // Compute gate interval auto momentData = (*radarData)[*radial]->moment_data_block(dataBlockType); - const std::uint16_t dataMomentInterval = + const std::int32_t dataMomentInterval = momentData->data_moment_range_sample_interval_raw(); - const std::uint16_t dataMomentIntervalH = dataMomentInterval / 2; - const std::uint16_t dataMomentRange = - std::max(momentData->data_moment_range_raw(), dataMomentIntervalH); + const std::int32_t dataMomentIntervalH = dataMomentInterval / 2; + const std::int32_t dataMomentRange = std::max( + momentData->data_moment_range_raw(), dataMomentIntervalH); // Compute gate size (number of base 250m gates per bin) - const std::uint16_t gateSizeMeters = - static_cast(radarProductManager->gate_size()); + const std::int32_t gateSizeMeters = + static_cast(radarProductManager->gate_size()); // Compute gate range [startGate, endGate) - const std::uint16_t startGate = + const std::int32_t startGate = (dataMomentRange - dataMomentIntervalH) / gateSizeMeters; - const std::uint16_t numberOfDataMomentGates = + const std::int32_t numberOfDataMomentGates = momentData->number_of_data_moment_gates(); - const std::uint16_t gate = s12 / dataMomentInterval - startGate; + const std::int32_t gate = s12 / dataMomentInterval - startGate; - if (gate > numberOfDataMomentGates || gate > common::MAX_DATA_MOMENT_GATES) + if (gate < 0 || gate > numberOfDataMomentGates || + gate > common::MAX_DATA_MOMENT_GATES) { // Coordinate is beyond radar range return std::nullopt; diff --git a/wxdata/include/scwx/wsr88d/rda/digital_radar_data.hpp b/wxdata/include/scwx/wsr88d/rda/digital_radar_data.hpp index bf96024e..01c2cd41 100644 --- a/wxdata/include/scwx/wsr88d/rda/digital_radar_data.hpp +++ b/wxdata/include/scwx/wsr88d/rda/digital_radar_data.hpp @@ -31,9 +31,9 @@ public: std::uint16_t elevation_angle_raw() const; units::degrees elevation_angle() const; std::uint16_t elevation_number() const; - std::uint16_t surveillance_range_raw() const; + std::int16_t surveillance_range_raw() const; units::kilometers surveillance_range() const; - std::uint16_t doppler_range_raw() const; + std::int16_t doppler_range_raw() const; units::kilometers doppler_range() const; std::uint16_t surveillance_range_sample_interval_raw() const; units::kilometers surveillance_range_sample_interval() const; diff --git a/wxdata/include/scwx/wsr88d/rda/digital_radar_data_generic.hpp b/wxdata/include/scwx/wsr88d/rda/digital_radar_data_generic.hpp index 42ee713a..ba911898 100644 --- a/wxdata/include/scwx/wsr88d/rda/digital_radar_data_generic.hpp +++ b/wxdata/include/scwx/wsr88d/rda/digital_radar_data_generic.hpp @@ -120,7 +120,7 @@ public: std::uint16_t number_of_data_moment_gates() const; units::kilometers data_moment_range() const; - std::uint16_t data_moment_range_raw() const; + std::int16_t data_moment_range_raw() const; units::kilometers data_moment_range_sample_interval() const; std::uint16_t data_moment_range_sample_interval_raw() const; float snr_threshold() const; diff --git a/wxdata/include/scwx/wsr88d/rda/generic_radar_data.hpp b/wxdata/include/scwx/wsr88d/rda/generic_radar_data.hpp index 085f7849..2b4b49e3 100644 --- a/wxdata/include/scwx/wsr88d/rda/generic_radar_data.hpp +++ b/wxdata/include/scwx/wsr88d/rda/generic_radar_data.hpp @@ -79,7 +79,7 @@ public: virtual std::uint16_t number_of_data_moment_gates() const = 0; virtual units::kilometers data_moment_range() const = 0; - virtual std::uint16_t data_moment_range_raw() const = 0; + virtual std::int16_t data_moment_range_raw() const = 0; virtual units::kilometers data_moment_range_sample_interval() const = 0; virtual std::uint16_t data_moment_range_sample_interval_raw() const = 0; diff --git a/wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp b/wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp index abd03a12..f98dafcd 100644 --- a/wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp +++ b/wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp @@ -33,8 +33,8 @@ public: std::uint16_t radialStatus_ {}; std::uint16_t elevationAngle_ {}; std::uint16_t elevationNumber_ {}; - std::uint16_t surveillanceRange_ {}; - std::uint16_t dopplerRange_ {}; + std::int16_t surveillanceRange_ {}; + std::int16_t dopplerRange_ {}; std::uint16_t surveillanceRangeSampleInterval_ {}; std::uint16_t dopplerRangeSampleInterval_ {}; std::uint16_t numberOfSurveillanceBins_ {}; @@ -71,7 +71,7 @@ public: std::uint16_t number_of_data_moment_gates() const; units::kilometers data_moment_range() const; - std::uint16_t data_moment_range_raw() const; + std::int16_t data_moment_range_raw() const; units::kilometers data_moment_range_sample_interval() const; std::uint16_t data_moment_range_sample_interval_raw() const; std::int16_t snr_threshold_raw() const; @@ -94,7 +94,7 @@ public: ~Impl() = default; std::uint16_t numberOfDataMomentGates_ {}; - std::uint16_t dataMomentRange_ {}; + std::int16_t dataMomentRange_ {}; std::uint16_t dataMomentRangeSampleInterval_ {}; float scale_ {}; float offset_ {}; @@ -162,7 +162,7 @@ std::uint16_t DigitalRadarData::elevation_number() const return p->elevationNumber_; } -std::uint16_t DigitalRadarData::surveillance_range_raw() const +std::int16_t DigitalRadarData::surveillance_range_raw() const { return p->surveillanceRange_; } @@ -172,7 +172,7 @@ units::kilometers DigitalRadarData::surveillance_range() const return units::kilometers {p->surveillanceRange_ * kRangeScale}; } -std::uint16_t DigitalRadarData::doppler_range_raw() const +std::int16_t DigitalRadarData::doppler_range_raw() const { return p->dopplerRange_; } @@ -363,7 +363,7 @@ DigitalRadarData::Impl::MomentDataBlock::data_moment_range() const return units::kilometers {p->dataMomentRange_ * kRangeScale}; } -std::uint16_t +std::int16_t DigitalRadarData::Impl::MomentDataBlock::data_moment_range_raw() const { return p->dataMomentRange_; diff --git a/wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp b/wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp index 94492a8e..4870c1c3 100644 --- a/wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp +++ b/wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp @@ -54,7 +54,7 @@ public: explicit Impl() {} std::uint16_t numberOfDataMomentGates_ {0}; - std::uint16_t dataMomentRange_ {0}; + std::int16_t dataMomentRange_ {0}; std::uint16_t dataMomentRangeSampleInterval_ {0}; std::uint16_t tover_ {0}; std::int16_t snrThreshold_ {0}; @@ -92,7 +92,7 @@ DigitalRadarDataGeneric::MomentDataBlock::data_moment_range() const return units::kilometers {p->dataMomentRange_ * 0.001f}; } -std::uint16_t +std::int16_t DigitalRadarDataGeneric::MomentDataBlock::data_moment_range_raw() const { return p->dataMomentRange_; From 1259cba8111ca806b5329af27afcd399a87c09d7 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 27 Jan 2024 16:49:47 -0600 Subject: [PATCH 32/37] Fix signed/unsigned implicit cast --- scwx-qt/source/scwx/qt/view/level2_product_view.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scwx-qt/source/scwx/qt/view/level2_product_view.cpp b/scwx-qt/source/scwx/qt/view/level2_product_view.cpp index 0e95ddd3..f0199c4b 100644 --- a/scwx-qt/source/scwx/qt/view/level2_product_view.cpp +++ b/scwx-qt/source/scwx/qt/view/level2_product_view.cpp @@ -890,7 +890,7 @@ Level2ProductView::GetBinLevel(const common::Coordinate& coordinate) const const std::int32_t gate = s12 / dataMomentInterval - startGate; if (gate < 0 || gate > numberOfDataMomentGates || - gate > common::MAX_DATA_MOMENT_GATES) + gate > static_cast(common::MAX_DATA_MOMENT_GATES)) { // Coordinate is beyond radar range return std::nullopt; From 9071c4751e3104830233ca83554010f9e05d2359 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 27 Jan 2024 22:30:02 -0600 Subject: [PATCH 33/37] Add current radar site to NexradFileRequest in the event there is no radar site in the Archive II file --- scwx-qt/source/scwx/qt/main/main_window.cpp | 6 +- .../scwx/qt/manager/radar_product_manager.cpp | 83 ++++++++++--------- .../scwx/qt/manager/radar_product_manager.hpp | 23 ++--- scwx-qt/source/scwx/qt/map/map_widget.cpp | 3 +- .../scwx/qt/request/nexrad_file_request.cpp | 23 +++-- .../scwx/qt/request/nexrad_file_request.hpp | 12 +-- 6 files changed, 85 insertions(+), 65 deletions(-) diff --git a/scwx-qt/source/scwx/qt/main/main_window.cpp b/scwx-qt/source/scwx/qt/main/main_window.cpp index 41f6fdbe..8b95768b 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.cpp +++ b/scwx-qt/source/scwx/qt/main/main_window.cpp @@ -430,8 +430,12 @@ void MainWindow::on_actionOpenNexrad_triggered() { logger_->info("Selected: {}", file.toStdString()); + auto radarSite = p->activeMap_->GetRadarSite(); + std::string currentRadarSite = + (radarSite != nullptr) ? radarSite->id() : std::string {}; + std::shared_ptr request = - std::make_shared(); + std::make_shared(currentRadarSite); connect( // request.get(), diff --git a/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp b/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp index c0d9370d..75c471d0 100644 --- a/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp @@ -207,16 +207,18 @@ public: void UpdateRecentRecords(RadarProductRecordList& recentList, std::shared_ptr record); - void LoadNexradFileAsync(CreateNexradFileFunction load, - std::shared_ptr request, - std::mutex& mutex, - std::chrono::system_clock::time_point time); - void LoadProviderData(std::chrono::system_clock::time_point time, + void LoadNexradFileAsync( + CreateNexradFileFunction load, + const std::shared_ptr& request, + std::mutex& mutex, + std::chrono::system_clock::time_point time); + void + LoadProviderData(std::chrono::system_clock::time_point time, std::shared_ptr providerManager, RadarProductRecordMap& recordMap, std::shared_mutex& recordMutex, std::mutex& loadDataMutex, - std::shared_ptr request); + const std::shared_ptr& request); void PopulateLevel2ProductTimes(std::chrono::system_clock::time_point time); void PopulateLevel3ProductTimes(const std::string& product, std::chrono::system_clock::time_point time); @@ -228,11 +230,10 @@ public: std::chrono::system_clock::time_point time); static void - LoadNexradFile(CreateNexradFileFunction load, - std::shared_ptr request, - std::mutex& mutex, - std::chrono::system_clock::time_point time = {}, - const std::string& radarId = {}); + LoadNexradFile(CreateNexradFileFunction load, + const std::shared_ptr& request, + std::mutex& mutex, + std::chrono::system_clock::time_point time = {}); const std::string radarId_; bool initialized_; @@ -395,6 +396,11 @@ float RadarProductManager::gate_size() const return (p->radarSite_->type() == "tdwr") ? 150.0f : 250.0f; } +std::string RadarProductManager::radar_id() const +{ + return p->radarId_; +} + std::shared_ptr RadarProductManager::radar_site() const { return p->radarSite_; @@ -778,12 +784,12 @@ RadarProductManager::GetActiveVolumeTimes( } void RadarProductManagerImpl::LoadProviderData( - std::chrono::system_clock::time_point time, - std::shared_ptr providerManager, - RadarProductRecordMap& recordMap, - std::shared_mutex& recordMutex, - std::mutex& loadDataMutex, - std::shared_ptr request) + std::chrono::system_clock::time_point time, + std::shared_ptr providerManager, + RadarProductRecordMap& recordMap, + std::shared_mutex& recordMutex, + std::mutex& loadDataMutex, + const std::shared_ptr& request) { logger_->debug("LoadProviderData: {}, {}", providerManager->name(), @@ -838,8 +844,8 @@ void RadarProductManagerImpl::LoadProviderData( } void RadarProductManager::LoadLevel2Data( - std::chrono::system_clock::time_point time, - std::shared_ptr request) + std::chrono::system_clock::time_point time, + const std::shared_ptr& request) { logger_->debug("LoadLevel2Data: {}", scwx::util::TimeString(time)); @@ -852,9 +858,9 @@ void RadarProductManager::LoadLevel2Data( } void RadarProductManager::LoadLevel3Data( - const std::string& product, - std::chrono::system_clock::time_point time, - std::shared_ptr request) + const std::string& product, + std::chrono::system_clock::time_point time, + const std::shared_ptr& request) { logger_->debug("LoadLevel3Data: {}", scwx::util::TimeString(time)); @@ -884,7 +890,7 @@ void RadarProductManager::LoadLevel3Data( } void RadarProductManager::LoadData( - std::istream& is, std::shared_ptr request) + std::istream& is, const std::shared_ptr& request) { logger_->debug("LoadData()"); @@ -900,8 +906,8 @@ void RadarProductManager::LoadData( } void RadarProductManager::LoadFile( - const std::string& filename, - std::shared_ptr request) + const std::string& filename, + const std::shared_ptr& request) { logger_->debug("LoadFile: {}", filename); @@ -951,22 +957,21 @@ void RadarProductManager::LoadFile( } void RadarProductManagerImpl::LoadNexradFileAsync( - CreateNexradFileFunction load, - std::shared_ptr request, - std::mutex& mutex, - std::chrono::system_clock::time_point time) + CreateNexradFileFunction load, + const std::shared_ptr& request, + std::mutex& mutex, + std::chrono::system_clock::time_point time) { boost::asio::post(threadPool_, - [=, this, &mutex]() - { LoadNexradFile(load, request, mutex, time, radarId_); }); + [=, &mutex]() + { LoadNexradFile(load, request, mutex, time); }); } void RadarProductManagerImpl::LoadNexradFile( - CreateNexradFileFunction load, - std::shared_ptr request, - std::mutex& mutex, - std::chrono::system_clock::time_point time, - const std::string& radarId) + CreateNexradFileFunction load, + const std::shared_ptr& request, + std::mutex& mutex, + std::chrono::system_clock::time_point time) { std::unique_lock lock {mutex}; @@ -992,7 +997,7 @@ void RadarProductManagerImpl::LoadNexradFile( std::string recordRadarId = (record->radar_id()); if (recordRadarId.empty()) { - recordRadarId = radarId; + recordRadarId = request->current_radar_site(); } std::shared_ptr manager = @@ -1127,7 +1132,7 @@ RadarProductManagerImpl::GetLevel2ProductRecord( { // Product is expired, reload it std::shared_ptr request = - std::make_shared(); + std::make_shared(radarId_); QObject::connect( request.get(), @@ -1192,7 +1197,7 @@ RadarProductManagerImpl::GetLevel3ProductRecord( { // Product is expired, reload it std::shared_ptr request = - std::make_shared(); + std::make_shared(radarId_); QObject::connect( request.get(), diff --git a/scwx-qt/source/scwx/qt/manager/radar_product_manager.hpp b/scwx-qt/source/scwx/qt/manager/radar_product_manager.hpp index ed8ba97b..aaf28996 100644 --- a/scwx-qt/source/scwx/qt/manager/radar_product_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/radar_product_manager.hpp @@ -42,6 +42,7 @@ public: const std::vector& coordinates(common::RadialSize radialSize) const; float gate_size() const; + std::string radar_id() const; std::shared_ptr radar_site() const; void Initialize(); @@ -110,19 +111,19 @@ public: Instance(const std::string& radarSite); void LoadLevel2Data( - std::chrono::system_clock::time_point time, - std::shared_ptr request = nullptr); + std::chrono::system_clock::time_point time, + const std::shared_ptr& request = nullptr); void LoadLevel3Data( - const std::string& product, - std::chrono::system_clock::time_point time, - std::shared_ptr request = nullptr); + const std::string& product, + std::chrono::system_clock::time_point time, + const std::shared_ptr& request = nullptr); - static void - LoadData(std::istream& is, - std::shared_ptr request = nullptr); - static void - LoadFile(const std::string& filename, - std::shared_ptr request = nullptr); + static void LoadData( + std::istream& is, + const std::shared_ptr& request = nullptr); + static void LoadFile( + const std::string& filename, + const std::shared_ptr& request = nullptr); common::Level3ProductCategoryMap GetAvailableLevel3Categories(); std::vector GetLevel3Products(); diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index 5f16ebf3..a5100e4a 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -1332,7 +1332,8 @@ void MapWidgetImpl::RadarProductManagerConnect() { // Create file request std::shared_ptr request = - std::make_shared(); + std::make_shared( + radarProductManager_->radar_id()); // File request callback if (autoUpdateEnabled_) diff --git a/scwx-qt/source/scwx/qt/request/nexrad_file_request.cpp b/scwx-qt/source/scwx/qt/request/nexrad_file_request.cpp index 5324027f..42f0ab33 100644 --- a/scwx-qt/source/scwx/qt/request/nexrad_file_request.cpp +++ b/scwx-qt/source/scwx/qt/request/nexrad_file_request.cpp @@ -9,22 +9,31 @@ namespace request static const std::string logPrefix_ = "scwx::qt::request::nexrad_file_request"; -class NexradFileRequestImpl +class NexradFileRequest::Impl { public: - explicit NexradFileRequestImpl() : radarProductRecord_ {nullptr} {} + explicit Impl(const std::string& currentRadarSite) : + currentRadarSite_ {currentRadarSite} + { + } + ~Impl() = default; - ~NexradFileRequestImpl() {} + const std::string currentRadarSite_; - std::shared_ptr radarProductRecord_; + std::shared_ptr radarProductRecord_ {nullptr}; }; -NexradFileRequest::NexradFileRequest() : - p(std::make_unique()) +NexradFileRequest::NexradFileRequest(const std::string& currentRadarSite) : + p(std::make_unique(currentRadarSite)) { } NexradFileRequest::~NexradFileRequest() = default; +std::string NexradFileRequest::current_radar_site() const +{ + return p->currentRadarSite_; +} + std::shared_ptr NexradFileRequest::radar_product_record() const { @@ -32,7 +41,7 @@ NexradFileRequest::radar_product_record() const } void NexradFileRequest::set_radar_product_record( - std::shared_ptr record) + const std::shared_ptr& record) { p->radarProductRecord_ = record; } diff --git a/scwx-qt/source/scwx/qt/request/nexrad_file_request.hpp b/scwx-qt/source/scwx/qt/request/nexrad_file_request.hpp index 787f513b..090d7169 100644 --- a/scwx-qt/source/scwx/qt/request/nexrad_file_request.hpp +++ b/scwx-qt/source/scwx/qt/request/nexrad_file_request.hpp @@ -13,23 +13,23 @@ namespace qt namespace request { -class NexradFileRequestImpl; - class NexradFileRequest : public QObject { Q_OBJECT public: - explicit NexradFileRequest(); + explicit NexradFileRequest(const std::string& currentRadarSite = {}); ~NexradFileRequest(); + std::string current_radar_site() const; std::shared_ptr radar_product_record() const; - void - set_radar_product_record(std::shared_ptr record); + void set_radar_product_record( + const std::shared_ptr& record); private: - std::unique_ptr p; + class Impl; + std::unique_ptr p; signals: void RequestComplete(std::shared_ptr request); From 6af8e398ddd56eda8a34941c8f4531d34f2f2012 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 27 Jan 2024 22:30:26 -0600 Subject: [PATCH 34/37] Adding 2002 ARCHIVE2 file to Level 2 test set --- test/data | 2 +- test/source/scwx/wsr88d/ar2v_file.test.cpp | 33 +++++++++++++--------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/test/data b/test/data index 6632ffd6..e3e743a5 160000 --- a/test/data +++ b/test/data @@ -1 +1 @@ -Subproject commit 6632ffd6ba35b799dd803e9711281d54a3858a29 +Subproject commit e3e743a5cc9c065d05f00151380fea892fb2156c diff --git a/test/source/scwx/wsr88d/ar2v_file.test.cpp b/test/source/scwx/wsr88d/ar2v_file.test.cpp index 03b0a25b..097fb69c 100644 --- a/test/source/scwx/wsr88d/ar2v_file.test.cpp +++ b/test/source/scwx/wsr88d/ar2v_file.test.cpp @@ -1,7 +1,5 @@ #include -#include - #include namespace scwx @@ -9,25 +7,32 @@ namespace scwx namespace wsr88d { -TEST(ar2v_file, klsx) +class Ar2vValidFileTest : + public testing::TestWithParam> { +}; + +TEST_P(Ar2vValidFileTest, ValidFile) +{ + auto& param = GetParam(); + Ar2vFile file; bool fileValid = - file.LoadFile(std::string(SCWX_TEST_DATA_DIR) + - "/nexrad/level2/Level2_KLSX_20210527_1757.ar2v"); + file.LoadFile(std::string(SCWX_TEST_DATA_DIR) + param.first); EXPECT_EQ(fileValid, true); + EXPECT_EQ(file.message_count(), param.second); } -TEST(ar2v_file, tstl) -{ - Ar2vFile file; - bool fileValid = - file.LoadFile(std::string(SCWX_TEST_DATA_DIR) + - "/nexrad/level2/Level2_TSTL_20220213_2357.ar2v"); - - EXPECT_EQ(fileValid, true); -} +INSTANTIATE_TEST_SUITE_P( + Ar2vFile, + Ar2vValidFileTest, + testing::Values(std::pair // + {"/nexrad/level2/KCLE20021110_221234", 4031}, + std::pair // + {"/nexrad/level2/Level2_KLSX_20210527_1757.ar2v", 11167}, + std::pair // + {"/nexrad/level2/Level2_TSTL_20220213_2357.ar2v", 5763})); } // namespace wsr88d } // namespace scwx From 0f215a94699fe3c88c92f404cd1d0bd8a9fe58bd Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 27 Jan 2024 23:18:15 -0600 Subject: [PATCH 35/37] Reintroduce invalid time warning, filter NWS_NEXRAD_* filenames instead --- wxdata/source/scwx/provider/aws_level2_data_provider.cpp | 2 +- wxdata/source/scwx/provider/aws_nexrad_data_provider.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/wxdata/source/scwx/provider/aws_level2_data_provider.cpp b/wxdata/source/scwx/provider/aws_level2_data_provider.cpp index 319c9ad7..6ac939c0 100644 --- a/wxdata/source/scwx/provider/aws_level2_data_provider.cpp +++ b/wxdata/source/scwx/provider/aws_level2_data_provider.cpp @@ -94,7 +94,7 @@ AwsLevel2DataProvider::GetTimePointFromKey(const std::string& key) if (in.fail()) { - logger_->trace("Invalid time: \"{}\"", timeStr); + logger_->warn("Invalid time: \"{}\"", timeStr); } } else diff --git a/wxdata/source/scwx/provider/aws_nexrad_data_provider.cpp b/wxdata/source/scwx/provider/aws_nexrad_data_provider.cpp index c2d0128e..04b16731 100644 --- a/wxdata/source/scwx/provider/aws_nexrad_data_provider.cpp +++ b/wxdata/source/scwx/provider/aws_nexrad_data_provider.cpp @@ -246,7 +246,8 @@ AwsNexradDataProvider::ListObjects(std::chrono::system_clock::time_point date) { std::string key = object.GetKey(); - if (!key.ends_with("_MDM")) + if (key.find("NWS_NEXRAD_") == std::string::npos && + !key.ends_with("_MDM")) { auto time = GetTimePointByKey(key); From 8d1fcfec7fe1dd671289b04cd03b4b20b1daf634 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 27 Jan 2024 23:18:33 -0600 Subject: [PATCH 36/37] Don't query for NEXRAD data at the epoch --- scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp b/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp index 75c471d0..732529ff 100644 --- a/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp @@ -1048,7 +1048,14 @@ void RadarProductManagerImpl::PopulateProductTimes( std::shared_mutex& productRecordMutex, std::chrono::system_clock::time_point time) { - const auto today = std::chrono::floor(time); + const auto today = std::chrono::floor(time); + + // Don't query for the epoch + if (today == std::chrono::system_clock::time_point {}) + { + return; + } + const auto yesterday = today - std::chrono::days {1}; const auto tomorrow = today + std::chrono::days {1}; const auto dates = {yesterday, today, tomorrow}; From eb076307c9a646432589b201690d874cc108fcad Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 27 Jan 2024 23:28:22 -0600 Subject: [PATCH 37/37] Update volume time outside of loop --- scwx-qt/source/scwx/qt/main/main_window.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scwx-qt/source/scwx/qt/main/main_window.cpp b/scwx-qt/source/scwx/qt/main/main_window.cpp index 8b95768b..179c1020 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.cpp +++ b/scwx-qt/source/scwx/qt/main/main_window.cpp @@ -886,9 +886,9 @@ void MainWindowImpl::ConnectAnimationSignals() &manager::TimelineManager::VolumeTimeUpdated, [this](std::chrono::system_clock::time_point dateTime) { + volumeTime_ = dateTime; for (auto map : maps_) { - volumeTime_ = dateTime; map->SelectTime(dateTime); } });