Merge pull request #138 from dpaulat/feature/legacy-level2-data

Support older Level 2 data formats
This commit is contained in:
Dan Paulat 2024-01-28 01:35:45 -06:00 committed by GitHub
commit 74e4564882
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 2636 additions and 1184 deletions

View file

@ -430,8 +430,12 @@ void MainWindow::on_actionOpenNexrad_triggered()
{ {
logger_->info("Selected: {}", file.toStdString()); logger_->info("Selected: {}", file.toStdString());
auto radarSite = p->activeMap_->GetRadarSite();
std::string currentRadarSite =
(radarSite != nullptr) ? radarSite->id() : std::string {};
std::shared_ptr<request::NexradFileRequest> request = std::shared_ptr<request::NexradFileRequest> request =
std::make_shared<request::NexradFileRequest>(); std::make_shared<request::NexradFileRequest>(currentRadarSite);
connect( // connect( //
request.get(), request.get(),
@ -882,9 +886,9 @@ void MainWindowImpl::ConnectAnimationSignals()
&manager::TimelineManager::VolumeTimeUpdated, &manager::TimelineManager::VolumeTimeUpdated,
[this](std::chrono::system_clock::time_point dateTime) [this](std::chrono::system_clock::time_point dateTime)
{ {
volumeTime_ = dateTime;
for (auto map : maps_) for (auto map : maps_)
{ {
volumeTime_ = dateTime;
map->SelectTime(dateTime); map->SelectTime(dateTime);
} }
}); });

View file

@ -207,16 +207,18 @@ public:
void UpdateRecentRecords(RadarProductRecordList& recentList, void UpdateRecentRecords(RadarProductRecordList& recentList,
std::shared_ptr<types::RadarProductRecord> record); std::shared_ptr<types::RadarProductRecord> record);
void LoadNexradFileAsync(CreateNexradFileFunction load, void LoadNexradFileAsync(
std::shared_ptr<request::NexradFileRequest> request, CreateNexradFileFunction load,
std::mutex& mutex, const std::shared_ptr<request::NexradFileRequest>& request,
std::chrono::system_clock::time_point time); std::mutex& mutex,
void LoadProviderData(std::chrono::system_clock::time_point time, std::chrono::system_clock::time_point time);
void
LoadProviderData(std::chrono::system_clock::time_point time,
std::shared_ptr<ProviderManager> providerManager, std::shared_ptr<ProviderManager> providerManager,
RadarProductRecordMap& recordMap, RadarProductRecordMap& recordMap,
std::shared_mutex& recordMutex, std::shared_mutex& recordMutex,
std::mutex& loadDataMutex, std::mutex& loadDataMutex,
std::shared_ptr<request::NexradFileRequest> request); const std::shared_ptr<request::NexradFileRequest>& request);
void PopulateLevel2ProductTimes(std::chrono::system_clock::time_point time); void PopulateLevel2ProductTimes(std::chrono::system_clock::time_point time);
void PopulateLevel3ProductTimes(const std::string& product, void PopulateLevel3ProductTimes(const std::string& product,
std::chrono::system_clock::time_point time); std::chrono::system_clock::time_point time);
@ -228,10 +230,10 @@ public:
std::chrono::system_clock::time_point time); std::chrono::system_clock::time_point time);
static void static void
LoadNexradFile(CreateNexradFileFunction load, LoadNexradFile(CreateNexradFileFunction load,
std::shared_ptr<request::NexradFileRequest> request, const std::shared_ptr<request::NexradFileRequest>& request,
std::mutex& mutex, 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_; bool initialized_;
@ -394,6 +396,11 @@ float RadarProductManager::gate_size() const
return (p->radarSite_->type() == "tdwr") ? 150.0f : 250.0f; return (p->radarSite_->type() == "tdwr") ? 150.0f : 250.0f;
} }
std::string RadarProductManager::radar_id() const
{
return p->radarId_;
}
std::shared_ptr<config::RadarSite> RadarProductManager::radar_site() const std::shared_ptr<config::RadarSite> RadarProductManager::radar_site() const
{ {
return p->radarSite_; return p->radarSite_;
@ -777,12 +784,12 @@ RadarProductManager::GetActiveVolumeTimes(
} }
void RadarProductManagerImpl::LoadProviderData( void RadarProductManagerImpl::LoadProviderData(
std::chrono::system_clock::time_point time, std::chrono::system_clock::time_point time,
std::shared_ptr<ProviderManager> providerManager, std::shared_ptr<ProviderManager> providerManager,
RadarProductRecordMap& recordMap, RadarProductRecordMap& recordMap,
std::shared_mutex& recordMutex, std::shared_mutex& recordMutex,
std::mutex& loadDataMutex, std::mutex& loadDataMutex,
std::shared_ptr<request::NexradFileRequest> request) const std::shared_ptr<request::NexradFileRequest>& request)
{ {
logger_->debug("LoadProviderData: {}, {}", logger_->debug("LoadProviderData: {}, {}",
providerManager->name(), providerManager->name(),
@ -837,8 +844,8 @@ void RadarProductManagerImpl::LoadProviderData(
} }
void RadarProductManager::LoadLevel2Data( void RadarProductManager::LoadLevel2Data(
std::chrono::system_clock::time_point time, std::chrono::system_clock::time_point time,
std::shared_ptr<request::NexradFileRequest> request) const std::shared_ptr<request::NexradFileRequest>& request)
{ {
logger_->debug("LoadLevel2Data: {}", scwx::util::TimeString(time)); logger_->debug("LoadLevel2Data: {}", scwx::util::TimeString(time));
@ -851,9 +858,9 @@ void RadarProductManager::LoadLevel2Data(
} }
void RadarProductManager::LoadLevel3Data( void RadarProductManager::LoadLevel3Data(
const std::string& product, const std::string& product,
std::chrono::system_clock::time_point time, std::chrono::system_clock::time_point time,
std::shared_ptr<request::NexradFileRequest> request) const std::shared_ptr<request::NexradFileRequest>& request)
{ {
logger_->debug("LoadLevel3Data: {}", scwx::util::TimeString(time)); logger_->debug("LoadLevel3Data: {}", scwx::util::TimeString(time));
@ -883,7 +890,7 @@ void RadarProductManager::LoadLevel3Data(
} }
void RadarProductManager::LoadData( void RadarProductManager::LoadData(
std::istream& is, std::shared_ptr<request::NexradFileRequest> request) std::istream& is, const std::shared_ptr<request::NexradFileRequest>& request)
{ {
logger_->debug("LoadData()"); logger_->debug("LoadData()");
@ -899,8 +906,8 @@ void RadarProductManager::LoadData(
} }
void RadarProductManager::LoadFile( void RadarProductManager::LoadFile(
const std::string& filename, const std::string& filename,
std::shared_ptr<request::NexradFileRequest> request) const std::shared_ptr<request::NexradFileRequest>& request)
{ {
logger_->debug("LoadFile: {}", filename); logger_->debug("LoadFile: {}", filename);
@ -950,10 +957,10 @@ void RadarProductManager::LoadFile(
} }
void RadarProductManagerImpl::LoadNexradFileAsync( void RadarProductManagerImpl::LoadNexradFileAsync(
CreateNexradFileFunction load, CreateNexradFileFunction load,
std::shared_ptr<request::NexradFileRequest> request, const std::shared_ptr<request::NexradFileRequest>& request,
std::mutex& mutex, std::mutex& mutex,
std::chrono::system_clock::time_point time) std::chrono::system_clock::time_point time)
{ {
boost::asio::post(threadPool_, boost::asio::post(threadPool_,
[=, &mutex]() [=, &mutex]()
@ -961,10 +968,10 @@ void RadarProductManagerImpl::LoadNexradFileAsync(
} }
void RadarProductManagerImpl::LoadNexradFile( void RadarProductManagerImpl::LoadNexradFile(
CreateNexradFileFunction load, CreateNexradFileFunction load,
std::shared_ptr<request::NexradFileRequest> request, const std::shared_ptr<request::NexradFileRequest>& request,
std::mutex& mutex, std::mutex& mutex,
std::chrono::system_clock::time_point time) std::chrono::system_clock::time_point time)
{ {
std::unique_lock lock {mutex}; std::unique_lock lock {mutex};
@ -987,8 +994,14 @@ void RadarProductManagerImpl::LoadNexradFile(
record->set_time(time); record->set_time(time);
} }
std::string recordRadarId = (record->radar_id());
if (recordRadarId.empty())
{
recordRadarId = request->current_radar_site();
}
std::shared_ptr<RadarProductManager> manager = std::shared_ptr<RadarProductManager> manager =
RadarProductManager::Instance(record->radar_id()); RadarProductManager::Instance(recordRadarId);
manager->Initialize(); manager->Initialize();
record = manager->p->StoreRadarProductRecord(record); record = manager->p->StoreRadarProductRecord(record);
@ -1035,7 +1048,14 @@ void RadarProductManagerImpl::PopulateProductTimes(
std::shared_mutex& productRecordMutex, std::shared_mutex& productRecordMutex,
std::chrono::system_clock::time_point time) std::chrono::system_clock::time_point time)
{ {
const auto today = std::chrono::floor<std::chrono::days>(time); const auto today = std::chrono::floor<std::chrono::days>(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 yesterday = today - std::chrono::days {1};
const auto tomorrow = today + std::chrono::days {1}; const auto tomorrow = today + std::chrono::days {1};
const auto dates = {yesterday, today, tomorrow}; const auto dates = {yesterday, today, tomorrow};
@ -1119,7 +1139,7 @@ RadarProductManagerImpl::GetLevel2ProductRecord(
{ {
// Product is expired, reload it // Product is expired, reload it
std::shared_ptr<request::NexradFileRequest> request = std::shared_ptr<request::NexradFileRequest> request =
std::make_shared<request::NexradFileRequest>(); std::make_shared<request::NexradFileRequest>(radarId_);
QObject::connect( QObject::connect(
request.get(), request.get(),
@ -1184,7 +1204,7 @@ RadarProductManagerImpl::GetLevel3ProductRecord(
{ {
// Product is expired, reload it // Product is expired, reload it
std::shared_ptr<request::NexradFileRequest> request = std::shared_ptr<request::NexradFileRequest> request =
std::make_shared<request::NexradFileRequest>(); std::make_shared<request::NexradFileRequest>(radarId_);
QObject::connect( QObject::connect(
request.get(), request.get(),

View file

@ -42,6 +42,7 @@ public:
const std::vector<float>& coordinates(common::RadialSize radialSize) const; const std::vector<float>& coordinates(common::RadialSize radialSize) const;
float gate_size() const; float gate_size() const;
std::string radar_id() const;
std::shared_ptr<config::RadarSite> radar_site() const; std::shared_ptr<config::RadarSite> radar_site() const;
void Initialize(); void Initialize();
@ -110,19 +111,19 @@ public:
Instance(const std::string& radarSite); Instance(const std::string& radarSite);
void LoadLevel2Data( void LoadLevel2Data(
std::chrono::system_clock::time_point time, std::chrono::system_clock::time_point time,
std::shared_ptr<request::NexradFileRequest> request = nullptr); const std::shared_ptr<request::NexradFileRequest>& request = nullptr);
void LoadLevel3Data( void LoadLevel3Data(
const std::string& product, const std::string& product,
std::chrono::system_clock::time_point time, std::chrono::system_clock::time_point time,
std::shared_ptr<request::NexradFileRequest> request = nullptr); const std::shared_ptr<request::NexradFileRequest>& request = nullptr);
static void static void LoadData(
LoadData(std::istream& is, std::istream& is,
std::shared_ptr<request::NexradFileRequest> request = nullptr); const std::shared_ptr<request::NexradFileRequest>& request = nullptr);
static void static void LoadFile(
LoadFile(const std::string& filename, const std::string& filename,
std::shared_ptr<request::NexradFileRequest> request = nullptr); const std::shared_ptr<request::NexradFileRequest>& request = nullptr);
common::Level3ProductCategoryMap GetAvailableLevel3Categories(); common::Level3ProductCategoryMap GetAvailableLevel3Categories();
std::vector<std::string> GetLevel3Products(); std::vector<std::string> GetLevel3Products();

View file

@ -1332,7 +1332,8 @@ void MapWidgetImpl::RadarProductManagerConnect()
{ {
// Create file request // Create file request
std::shared_ptr<request::NexradFileRequest> request = std::shared_ptr<request::NexradFileRequest> request =
std::make_shared<request::NexradFileRequest>(); std::make_shared<request::NexradFileRequest>(
radarProductManager_->radar_id());
// File request callback // File request callback
if (autoUpdateEnabled_) if (autoUpdateEnabled_)

View file

@ -9,22 +9,31 @@ namespace request
static const std::string logPrefix_ = "scwx::qt::request::nexrad_file_request"; static const std::string logPrefix_ = "scwx::qt::request::nexrad_file_request";
class NexradFileRequestImpl class NexradFileRequest::Impl
{ {
public: public:
explicit NexradFileRequestImpl() : radarProductRecord_ {nullptr} {} explicit Impl(const std::string& currentRadarSite) :
currentRadarSite_ {currentRadarSite}
{
}
~Impl() = default;
~NexradFileRequestImpl() {} const std::string currentRadarSite_;
std::shared_ptr<types::RadarProductRecord> radarProductRecord_; std::shared_ptr<types::RadarProductRecord> radarProductRecord_ {nullptr};
}; };
NexradFileRequest::NexradFileRequest() : NexradFileRequest::NexradFileRequest(const std::string& currentRadarSite) :
p(std::make_unique<NexradFileRequestImpl>()) p(std::make_unique<Impl>(currentRadarSite))
{ {
} }
NexradFileRequest::~NexradFileRequest() = default; NexradFileRequest::~NexradFileRequest() = default;
std::string NexradFileRequest::current_radar_site() const
{
return p->currentRadarSite_;
}
std::shared_ptr<types::RadarProductRecord> std::shared_ptr<types::RadarProductRecord>
NexradFileRequest::radar_product_record() const NexradFileRequest::radar_product_record() const
{ {
@ -32,7 +41,7 @@ NexradFileRequest::radar_product_record() const
} }
void NexradFileRequest::set_radar_product_record( void NexradFileRequest::set_radar_product_record(
std::shared_ptr<types::RadarProductRecord> record) const std::shared_ptr<types::RadarProductRecord>& record)
{ {
p->radarProductRecord_ = record; p->radarProductRecord_ = record;
} }

View file

@ -13,23 +13,23 @@ namespace qt
namespace request namespace request
{ {
class NexradFileRequestImpl;
class NexradFileRequest : public QObject class NexradFileRequest : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit NexradFileRequest(); explicit NexradFileRequest(const std::string& currentRadarSite = {});
~NexradFileRequest(); ~NexradFileRequest();
std::string current_radar_site() const;
std::shared_ptr<types::RadarProductRecord> radar_product_record() const; std::shared_ptr<types::RadarProductRecord> radar_product_record() const;
void void set_radar_product_record(
set_radar_product_record(std::shared_ptr<types::RadarProductRecord> record); const std::shared_ptr<types::RadarProductRecord>& record);
private: private:
std::unique_ptr<NexradFileRequestImpl> p; class Impl;
std::unique_ptr<Impl> p;
signals: signals:
void RequestComplete(std::shared_ptr<NexradFileRequest> request); void RequestComplete(std::shared_ptr<NexradFileRequest> request);

View file

@ -89,8 +89,9 @@ public:
float selectedElevation_; float selectedElevation_;
std::shared_ptr<wsr88d::rda::ElevationScan> elevationScan_; std::shared_ptr<wsr88d::rda::ElevationScan> elevationScan_;
std::shared_ptr<wsr88d::rda::MomentDataBlock> momentDataBlock0_; std::shared_ptr<wsr88d::rda::GenericRadarData::MomentDataBlock>
momentDataBlock0_;
std::vector<float> coordinates_ {}; std::vector<float> coordinates_ {};
std::vector<float> vertices_ {}; std::vector<float> vertices_ {};
@ -98,12 +99,12 @@ public:
std::vector<uint16_t> dataMoments16_ {}; std::vector<uint16_t> dataMoments16_ {};
std::vector<uint8_t> cfpMoments_ {}; std::vector<uint8_t> cfpMoments_ {};
float latitude_; float latitude_;
float longitude_; float longitude_;
float elevationCut_; float elevationCut_;
std::vector<float> elevationCuts_; std::vector<float> elevationCuts_;
float range_; units::kilometers<float> range_;
uint16_t vcp_; uint16_t vcp_;
std::chrono::system_clock::time_point sweepTime_; std::chrono::system_clock::time_point sweepTime_;
@ -212,7 +213,7 @@ float Level2ProductView::elevation() const
float Level2ProductView::range() const float Level2ProductView::range() const
{ {
return p->range_; return p->range_.value();
} }
std::chrono::system_clock::time_point Level2ProductView::sweep_time() const std::chrono::system_clock::time_point Level2ProductView::sweep_time() const
@ -468,15 +469,15 @@ void Level2ProductView::ComputeSweep()
const uint32_t gates = momentData0->number_of_data_moment_gates(); const uint32_t gates = momentData0->number_of_data_moment_gates();
auto volumeData0 = radarData0->volume_data_block(); auto radarSite = radarProductManager->radar_site();
p->latitude_ = volumeData0->latitude(); p->latitude_ = radarSite->latitude();
p->longitude_ = volumeData0->longitude(); p->longitude_ = radarSite->longitude();
p->range_ = p->range_ =
momentData0->data_moment_range() + momentData0->data_moment_range() +
momentData0->data_moment_range_sample_interval() * (gates - 0.5f); momentData0->data_moment_range_sample_interval() * (gates - 0.5f);
p->sweepTime_ = scwx::util::TimePoint(radarData0->modified_julian_date(), p->sweepTime_ = scwx::util::TimePoint(radarData0->modified_julian_date(),
radarData0->collection_time()); radarData0->collection_time());
p->vcp_ = volumeData0->volume_coverage_pattern_number(); p->vcp_ = radarData0->volume_coverage_pattern_number();
// Calculate vertices // Calculate vertices
timer.start(); timer.start();
@ -521,17 +522,17 @@ void Level2ProductView::ComputeSweep()
} }
// Compute threshold at which to display an individual bin (minimum of 2) // Compute threshold at which to display an individual bin (minimum of 2)
const uint16_t snrThreshold = const std::uint16_t snrThreshold =
std::max<int16_t>(2, momentData0->snr_threshold_raw()); std::max<std::int16_t>(2, momentData0->snr_threshold_raw());
// Start radial is always 0, as coordinates are calculated for each sweep // Start radial is always 0, as coordinates are calculated for each sweep
constexpr std::uint16_t startRadial = 0u; constexpr std::uint16_t startRadial = 0u;
for (auto& radialPair : *radarData) for (auto& radialPair : *radarData)
{ {
uint16_t radial = radialPair.first; std::uint16_t radial = radialPair.first;
auto radialData = radialPair.second; auto& radialData = radialPair.second;
auto momentData = radialData->moment_data_block(p->dataBlockType_); auto momentData = radialData->moment_data_block(p->dataBlockType_);
if (momentData0->data_word_size() != momentData->data_word_size()) if (momentData0->data_word_size() != momentData->data_word_size())
{ {
@ -540,64 +541,70 @@ void Level2ProductView::ComputeSweep()
} }
// Compute gate interval // Compute gate interval
const uint16_t dataMomentRange = momentData->data_moment_range_raw(); const std::int32_t dataMomentInterval =
const uint16_t dataMomentInterval =
momentData->data_moment_range_sample_interval_raw(); momentData->data_moment_range_sample_interval_raw();
const uint16_t dataMomentIntervalH = dataMomentInterval / 2; const std::int32_t dataMomentIntervalH = dataMomentInterval / 2;
const std::int32_t dataMomentRange = std::max<std::int32_t>(
momentData->data_moment_range_raw(), dataMomentIntervalH);
// Compute gate size (number of base 250m gates per bin) // Compute gate size (number of base 250m gates per bin)
const uint16_t gateSizeMeters = const std::int32_t gateSizeMeters =
static_cast<uint16_t>(radarProductManager->gate_size()); static_cast<std::int32_t>(radarProductManager->gate_size());
const uint16_t gateSize = const std::int32_t gateSize =
std::max<uint16_t>(1, dataMomentInterval / gateSizeMeters); std::max<std::int32_t>(1, dataMomentInterval / gateSizeMeters);
// Compute gate range [startGate, endGate) // Compute gate range [startGate, endGate)
const uint16_t startGate = const std::int32_t startGate =
(dataMomentRange - dataMomentIntervalH) / gateSizeMeters; (dataMomentRange - dataMomentIntervalH) / gateSizeMeters;
const uint16_t numberOfDataMomentGates = const std::int32_t numberOfDataMomentGates =
std::min<uint16_t>(momentData->number_of_data_moment_gates(), std::min<std::int32_t>(momentData->number_of_data_moment_gates(),
static_cast<uint16_t>(gates)); static_cast<std::int32_t>(gates));
const uint16_t endGate = const std::int32_t endGate = std::min<std::int32_t>(
std::min<uint16_t>(startGate + numberOfDataMomentGates * gateSize, startGate + numberOfDataMomentGates * gateSize,
common::MAX_DATA_MOMENT_GATES); static_cast<std::int32_t>(common::MAX_DATA_MOMENT_GATES));
const uint8_t* dataMomentsArray8 = nullptr; const std::uint8_t* dataMomentsArray8 = nullptr;
const uint16_t* dataMomentsArray16 = nullptr; const std::uint16_t* dataMomentsArray16 = nullptr;
const uint8_t* cfpMomentsArray = nullptr; const std::uint8_t* cfpMomentsArray = nullptr;
if (momentData->data_word_size() == 8) if (momentData->data_word_size() == 8)
{ {
dataMomentsArray8 = dataMomentsArray8 =
reinterpret_cast<const uint8_t*>(momentData->data_moments()); reinterpret_cast<const std::uint8_t*>(momentData->data_moments());
} }
else else
{ {
dataMomentsArray16 = dataMomentsArray16 =
reinterpret_cast<const uint16_t*>(momentData->data_moments()); reinterpret_cast<const std::uint16_t*>(momentData->data_moments());
} }
if (cfpMoments.size() > 0) if (cfpMoments.size() > 0)
{ {
cfpMomentsArray = reinterpret_cast<const uint8_t*>( cfpMomentsArray = reinterpret_cast<const std::uint8_t*>(
radialData->moment_data_block(wsr88d::rda::DataBlockType::MomentCfp) radialData->moment_data_block(wsr88d::rda::DataBlockType::MomentCfp)
->data_moments()); ->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) 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 // Store data moment value
if (dataMomentsArray8 != nullptr) if (dataMomentsArray8 != nullptr)
{ {
uint8_t dataValue = dataMomentsArray8[i]; std::uint8_t dataValue = dataMomentsArray8[i];
if (dataValue < snrThreshold && dataValue != RANGE_FOLDED) if (dataValue < snrThreshold && dataValue != RANGE_FOLDED)
{ {
continue; continue;
} }
for (size_t m = 0; m < vertexCount; m++) for (std::size_t m = 0; m < vertexCount; m++)
{ {
dataMoments8[mIndex++] = dataMomentsArray8[i]; dataMoments8[mIndex++] = dataMomentsArray8[i];
@ -609,13 +616,13 @@ void Level2ProductView::ComputeSweep()
} }
else else
{ {
uint16_t dataValue = dataMomentsArray16[i]; std::uint16_t dataValue = dataMomentsArray16[i];
if (dataValue < snrThreshold && dataValue != RANGE_FOLDED) if (dataValue < snrThreshold && dataValue != RANGE_FOLDED)
{ {
continue; continue;
} }
for (size_t m = 0; m < vertexCount; m++) for (std::size_t m = 0; m < vertexCount; m++)
{ {
dataMoments16[mIndex++] = dataMomentsArray16[i]; dataMoments16[mIndex++] = dataMomentsArray16[i];
} }
@ -624,18 +631,18 @@ void Level2ProductView::ComputeSweep()
// Store vertices // Store vertices
if (gate > 0) if (gate > 0)
{ {
const uint16_t baseCoord = gate - 1; const std::uint16_t baseCoord = gate - 1;
size_t offset1 = ((startRadial + radial) % radials * std::size_t offset1 = ((startRadial + radial) % radials *
common::MAX_DATA_MOMENT_GATES + common::MAX_DATA_MOMENT_GATES +
baseCoord) * baseCoord) *
2; 2;
size_t offset2 = offset1 + gateSize * 2; std::size_t offset2 = offset1 + gateSize * 2;
size_t offset3 = (((startRadial + radial + 1) % radials) * std::size_t offset3 = (((startRadial + radial + 1) % radials) *
common::MAX_DATA_MOMENT_GATES + common::MAX_DATA_MOMENT_GATES +
baseCoord) * baseCoord) *
2; 2;
size_t offset4 = offset3 + gateSize * 2; std::size_t offset4 = offset3 + gateSize * 2;
vertices[vIndex++] = coordinates[offset1]; vertices[vIndex++] = coordinates[offset1];
vertices[vIndex++] = coordinates[offset1 + 1]; vertices[vIndex++] = coordinates[offset1 + 1];
@ -659,16 +666,16 @@ void Level2ProductView::ComputeSweep()
} }
else else
{ {
const uint16_t baseCoord = gate; const std::uint16_t baseCoord = gate;
size_t offset1 = ((startRadial + radial) % radials * std::size_t offset1 = ((startRadial + radial) % radials *
common::MAX_DATA_MOMENT_GATES + common::MAX_DATA_MOMENT_GATES +
baseCoord) * baseCoord) *
2; 2;
size_t offset2 = (((startRadial + radial + 1) % radials) * std::size_t offset2 = (((startRadial + radial + 1) % radials) *
common::MAX_DATA_MOMENT_GATES + common::MAX_DATA_MOMENT_GATES +
baseCoord) * baseCoord) *
2; 2;
vertices[vIndex++] = p->latitude_; vertices[vIndex++] = p->latitude_;
vertices[vIndex++] = p->longitude_; vertices[vIndex++] = p->longitude_;
@ -747,7 +754,8 @@ void Level2ProductViewImpl::ComputeCoordinates(
radials.end(), radials.end(),
[&](std::uint32_t radial) [&](std::uint32_t radial)
{ {
const float angle = (*radarData)[radial]->azimuth_angle(); const units::degrees<float> angle =
(*radarData)[radial]->azimuth_angle();
std::for_each(std::execution::par_unseq, std::for_each(std::execution::par_unseq,
gates.begin(), gates.begin(),
@ -765,7 +773,7 @@ void Level2ProductViewImpl::ComputeCoordinates(
geodesic.Direct(radarLatitude, geodesic.Direct(radarLatitude,
radarLongitude, radarLongitude,
angle, angle.value(),
range, range,
latitude, latitude,
longitude); longitude);
@ -830,14 +838,15 @@ Level2ProductView::GetBinLevel(const common::Coordinate& coordinate) const
radials.end(), radials.end(),
[&](std::uint32_t i) [&](std::uint32_t i)
{ {
bool found = false; bool found = false;
const float startAngle = (*radarData)[i]->azimuth_angle(); const units::degrees<float> startAngle =
const float nextAngle = (*radarData)[i]->azimuth_angle();
const units::degrees<float> nextAngle =
(*radarData)[(i + 1) % numRadials]->azimuth_angle(); (*radarData)[(i + 1) % numRadials]->azimuth_angle();
if (startAngle < nextAngle) if (startAngle < nextAngle)
{ {
if (startAngle <= azi1 && azi1 < nextAngle) if (startAngle.value() <= azi1 && azi1 < nextAngle.value())
{ {
found = true; found = true;
} }
@ -845,7 +854,7 @@ Level2ProductView::GetBinLevel(const common::Coordinate& coordinate) const
else else
{ {
// If the bin crosses 0/360 degrees, special handling is needed // 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; found = true;
} }
@ -862,24 +871,26 @@ Level2ProductView::GetBinLevel(const common::Coordinate& coordinate) const
// Compute gate interval // Compute gate interval
auto momentData = (*radarData)[*radial]->moment_data_block(dataBlockType); auto momentData = (*radarData)[*radial]->moment_data_block(dataBlockType);
const std::uint16_t dataMomentRange = momentData->data_moment_range_raw(); const std::int32_t dataMomentInterval =
const std::uint16_t dataMomentInterval =
momentData->data_moment_range_sample_interval_raw(); momentData->data_moment_range_sample_interval_raw();
const std::uint16_t dataMomentIntervalH = dataMomentInterval / 2; const std::int32_t dataMomentIntervalH = dataMomentInterval / 2;
const std::int32_t dataMomentRange = std::max<std::int32_t>(
momentData->data_moment_range_raw(), dataMomentIntervalH);
// Compute gate size (number of base 250m gates per bin) // Compute gate size (number of base 250m gates per bin)
const std::uint16_t gateSizeMeters = const std::int32_t gateSizeMeters =
static_cast<std::uint16_t>(radarProductManager->gate_size()); static_cast<std::int32_t>(radarProductManager->gate_size());
// Compute gate range [startGate, endGate) // Compute gate range [startGate, endGate)
const std::uint16_t startGate = const std::int32_t startGate =
(dataMomentRange - dataMomentIntervalH) / gateSizeMeters; (dataMomentRange - dataMomentIntervalH) / gateSizeMeters;
const std::uint16_t numberOfDataMomentGates = const std::int32_t numberOfDataMomentGates =
momentData->number_of_data_moment_gates(); 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 > static_cast<std::int32_t>(common::MAX_DATA_MOMENT_GATES))
{ {
// Coordinate is beyond radar range // Coordinate is beyond radar range
return std::nullopt; return std::nullopt;

@ -1 +1 @@
Subproject commit 6632ffd6ba35b799dd803e9711281d54a3858a29 Subproject commit e3e743a5cc9c065d05f00151380fea892fb2156c

View file

@ -1,7 +1,5 @@
#include <scwx/wsr88d/ar2v_file.hpp> #include <scwx/wsr88d/ar2v_file.hpp>
#include <fstream>
#include <gtest/gtest.h> #include <gtest/gtest.h>
namespace scwx namespace scwx
@ -9,25 +7,32 @@ namespace scwx
namespace wsr88d namespace wsr88d
{ {
TEST(ar2v_file, klsx) class Ar2vValidFileTest :
public testing::TestWithParam<std::pair<std::string, std::size_t>>
{ {
};
TEST_P(Ar2vValidFileTest, ValidFile)
{
auto& param = GetParam();
Ar2vFile file; Ar2vFile file;
bool fileValid = bool fileValid =
file.LoadFile(std::string(SCWX_TEST_DATA_DIR) + file.LoadFile(std::string(SCWX_TEST_DATA_DIR) + param.first);
"/nexrad/level2/Level2_KLSX_20210527_1757.ar2v");
EXPECT_EQ(fileValid, true); EXPECT_EQ(fileValid, true);
EXPECT_EQ(file.message_count(), param.second);
} }
TEST(ar2v_file, tstl) INSTANTIATE_TEST_SUITE_P(
{ Ar2vFile,
Ar2vFile file; Ar2vValidFileTest,
bool fileValid = testing::Values(std::pair<std::string, std::size_t> //
file.LoadFile(std::string(SCWX_TEST_DATA_DIR) + {"/nexrad/level2/KCLE20021110_221234", 4031},
"/nexrad/level2/Level2_TSTL_20220213_2357.ar2v"); std::pair<std::string, std::size_t> //
{"/nexrad/level2/Level2_KLSX_20210527_1757.ar2v", 11167},
EXPECT_EQ(fileValid, true); std::pair<std::string, std::size_t> //
} {"/nexrad/level2/Level2_TSTL_20220213_2357.ar2v", 5763}));
} // namespace wsr88d } // namespace wsr88d
} // namespace scwx } // namespace scwx

509
wxdata/archive2_format.txt Normal file
View file

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

View file

@ -1,7 +1,7 @@
#pragma once #pragma once
#include <scwx/wsr88d/nexrad_file.hpp> #include <scwx/wsr88d/nexrad_file.hpp>
#include <scwx/wsr88d/rda/digital_radar_data.hpp> #include <scwx/wsr88d/rda/generic_radar_data.hpp>
#include <scwx/wsr88d/rda/volume_coverage_pattern_data.hpp> #include <scwx/wsr88d/rda/volume_coverage_pattern_data.hpp>
#include <chrono> #include <chrono>
@ -26,21 +26,24 @@ public:
explicit Ar2vFile(); explicit Ar2vFile();
~Ar2vFile(); ~Ar2vFile();
Ar2vFile(const Ar2vFile&) = delete; Ar2vFile(const Ar2vFile&) = delete;
Ar2vFile& operator=(const Ar2vFile&) = delete; Ar2vFile& operator=(const Ar2vFile&) = delete;
Ar2vFile(Ar2vFile&&) noexcept; Ar2vFile(Ar2vFile&&) noexcept;
Ar2vFile& operator=(Ar2vFile&&) noexcept; Ar2vFile& operator=(Ar2vFile&&) noexcept;
uint32_t julian_date() const; std::uint32_t julian_date() const;
uint32_t milliseconds() const; std::uint32_t milliseconds() const;
std::string icao() 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 start_time() const;
std::chrono::system_clock::time_point end_time() const; std::chrono::system_clock::time_point end_time() const;
std::map<uint16_t, std::shared_ptr<rda::ElevationScan>> radar_data() const; std::map<std::uint16_t, std::shared_ptr<rda::ElevationScan>>
std::shared_ptr<const rda::VolumeCoveragePatternData> vcp_data() const; radar_data() const;
std::shared_ptr<const rda::VolumeCoveragePatternData> vcp_data() const;
std::tuple<std::shared_ptr<rda::ElevationScan>, float, std::vector<float>> std::tuple<std::shared_ptr<rda::ElevationScan>, float, std::vector<float>>
GetElevationScan(rda::DataBlockType dataBlockType, GetElevationScan(rda::DataBlockType dataBlockType,

View file

@ -1,7 +1,6 @@
#pragma once #pragma once
#include <scwx/util/iterator.hpp> #include <scwx/wsr88d/rda/generic_radar_data.hpp>
#include <scwx/wsr88d/rda/level2_message.hpp>
namespace scwx namespace scwx
{ {
@ -10,198 +9,52 @@ namespace wsr88d
namespace rda namespace rda
{ {
enum class DataBlockType class DigitalRadarData : public GenericRadarData
{
Volume,
Elevation,
Radial,
MomentRef,
MomentVel,
MomentSw,
MomentZdr,
MomentPhi,
MomentRho,
MomentCfp,
Unknown
};
typedef util::
Iterator<DataBlockType, DataBlockType::MomentRef, DataBlockType::MomentCfp>
MomentDataBlockTypeIterator;
class DataBlockImpl;
class ElevationDataBlockImpl;
class MomentDataBlockImpl;
class RadialDataBlockImpl;
class VolumeDataBlockImpl;
class DigitalRadarData;
class DigitalRadarDataImpl;
typedef std::map<uint16_t, std::shared_ptr<DigitalRadarData>> ElevationScan;
class DataBlock
{
protected:
explicit DataBlock(const std::string& dataBlockType,
const std::string& dataName);
~DataBlock();
DataBlock(const DataBlock&) = delete;
DataBlock& operator=(const DataBlock&) = delete;
DataBlock(DataBlock&&) noexcept;
DataBlock& operator=(DataBlock&&) noexcept;
private:
std::unique_ptr<DataBlockImpl> p;
};
class ElevationDataBlock : public DataBlock
{
public:
explicit ElevationDataBlock(const std::string& dataBlockType,
const std::string& dataName);
~ElevationDataBlock();
ElevationDataBlock(const ElevationDataBlock&) = delete;
ElevationDataBlock& operator=(const ElevationDataBlock&) = delete;
ElevationDataBlock(ElevationDataBlock&&) noexcept;
ElevationDataBlock& operator=(ElevationDataBlock&&) noexcept;
static std::shared_ptr<ElevationDataBlock>
Create(const std::string& dataBlockType,
const std::string& dataName,
std::istream& is);
private:
std::unique_ptr<ElevationDataBlockImpl> p;
bool Parse(std::istream& is);
};
class MomentDataBlock : public DataBlock
{
public:
explicit MomentDataBlock(const std::string& dataBlockType,
const std::string& dataName);
~MomentDataBlock();
MomentDataBlock(const MomentDataBlock&) = delete;
MomentDataBlock& operator=(const MomentDataBlock&) = delete;
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;
static std::shared_ptr<MomentDataBlock>
Create(const std::string& dataBlockType,
const std::string& dataName,
std::istream& is);
private:
std::unique_ptr<MomentDataBlockImpl> p;
bool Parse(std::istream& is);
};
class RadialDataBlock : public DataBlock
{
public:
explicit RadialDataBlock(const std::string& dataBlockType,
const std::string& dataName);
~RadialDataBlock();
RadialDataBlock(const RadialDataBlock&) = delete;
RadialDataBlock& operator=(const RadialDataBlock&) = delete;
RadialDataBlock(RadialDataBlock&&) noexcept;
RadialDataBlock& operator=(RadialDataBlock&&) noexcept;
float unambiguous_range() const;
static std::shared_ptr<RadialDataBlock>
Create(const std::string& dataBlockType,
const std::string& dataName,
std::istream& is);
private:
std::unique_ptr<RadialDataBlockImpl> p;
bool Parse(std::istream& is);
};
class VolumeDataBlock : public DataBlock
{
public:
explicit VolumeDataBlock(const std::string& dataBlockType,
const std::string& dataName);
~VolumeDataBlock();
VolumeDataBlock(const VolumeDataBlock&) = delete;
VolumeDataBlock& operator=(const VolumeDataBlock&) = delete;
VolumeDataBlock(VolumeDataBlock&&) noexcept;
VolumeDataBlock& operator=(VolumeDataBlock&&) noexcept;
float latitude() const;
float longitude() const;
uint16_t volume_coverage_pattern_number() const;
static std::shared_ptr<VolumeDataBlock>
Create(const std::string& dataBlockType,
const std::string& dataName,
std::istream& is);
private:
std::unique_ptr<VolumeDataBlockImpl> p;
bool Parse(std::istream& is);
};
class DigitalRadarData : public Level2Message
{ {
public: public:
explicit DigitalRadarData(); explicit DigitalRadarData();
~DigitalRadarData(); ~DigitalRadarData();
DigitalRadarData(const DigitalRadarData&) = delete; DigitalRadarData(const DigitalRadarData&) = delete;
DigitalRadarData& operator=(const DigitalRadarData&) = delete; DigitalRadarData& operator=(const DigitalRadarData&) = delete;
DigitalRadarData(DigitalRadarData&&) noexcept; DigitalRadarData(DigitalRadarData&&) noexcept;
DigitalRadarData& operator=(DigitalRadarData&&) noexcept; DigitalRadarData& operator=(DigitalRadarData&&) noexcept;
std::string radar_identifier() const; std::uint32_t collection_time() const;
uint32_t collection_time() const; std::uint16_t modified_julian_date() const;
uint16_t modified_julian_date() const; std::uint16_t unambiguous_range() const;
uint16_t azimuth_number() const; std::uint16_t azimuth_angle_raw() const;
float azimuth_angle() const; units::degrees<float> azimuth_angle() const;
uint8_t compression_indicator() const; std::uint16_t azimuth_number() const;
uint16_t radial_length() const; std::uint16_t radial_status() const;
uint8_t azimuth_resolution_spacing() const; std::uint16_t elevation_angle_raw() const;
uint8_t radial_status() const; units::degrees<float> elevation_angle() const;
uint8_t elevation_number() const; std::uint16_t elevation_number() const;
uint8_t cut_sector_number() const; std::int16_t surveillance_range_raw() const;
float elevation_angle() const; units::kilometers<float> surveillance_range() const;
uint8_t radial_spot_blanking_status() const; std::int16_t doppler_range_raw() const;
uint8_t azimuth_indexing_mode() const; units::kilometers<float> doppler_range() const;
uint16_t data_block_count() const; std::uint16_t surveillance_range_sample_interval_raw() const;
units::kilometers<float> surveillance_range_sample_interval() const;
std::uint16_t doppler_range_sample_interval_raw() const;
units::kilometers<float> 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::shared_ptr<ElevationDataBlock> elevation_data_block() const; std::shared_ptr<GenericRadarData::MomentDataBlock>
std::shared_ptr<RadialDataBlock> radial_data_block() const; moment_data_block(DataBlockType type) const;
std::shared_ptr<VolumeDataBlock> volume_data_block() const;
std::shared_ptr<MomentDataBlock> moment_data_block(DataBlockType type) const;
bool Parse(std::istream& is); bool Parse(std::istream& is);
@ -209,7 +62,8 @@ public:
std::istream& is); std::istream& is);
private: private:
std::unique_ptr<DigitalRadarDataImpl> p; class Impl;
std::unique_ptr<Impl> p;
}; };
} // namespace rda } // namespace rda

View file

@ -0,0 +1,203 @@
#pragma once
#include <scwx/wsr88d/rda/generic_radar_data.hpp>
namespace scwx
{
namespace wsr88d
{
namespace rda
{
class DigitalRadarDataGeneric : public GenericRadarData
{
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<float> 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::uint16_t elevation_number() const;
std::uint8_t cut_sector_number() const;
units::degrees<float> 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::uint16_t volume_coverage_pattern_number() const;
std::shared_ptr<ElevationDataBlock> elevation_data_block() const;
std::shared_ptr<RadialDataBlock> radial_data_block() const;
std::shared_ptr<VolumeDataBlock> volume_data_block() const;
std::shared_ptr<GenericRadarData::MomentDataBlock>
moment_data_block(DataBlockType type) const;
bool Parse(std::istream& is);
static std::shared_ptr<DigitalRadarDataGeneric>
Create(Level2MessageHeader&& header, std::istream& is);
private:
class Impl;
std::unique_ptr<Impl> p;
};
class DigitalRadarDataGeneric::DataBlock
{
protected:
explicit DataBlock(const std::string& dataBlockType,
const std::string& dataName);
virtual ~DataBlock();
DataBlock(const DataBlock&) = delete;
DataBlock& operator=(const DataBlock&) = delete;
DataBlock(DataBlock&&) noexcept;
DataBlock& operator=(DataBlock&&) noexcept;
private:
class Impl;
std::unique_ptr<Impl> p;
};
class DigitalRadarDataGeneric::ElevationDataBlock : public DataBlock
{
public:
explicit ElevationDataBlock(const std::string& dataBlockType,
const std::string& dataName);
~ElevationDataBlock();
ElevationDataBlock(const ElevationDataBlock&) = delete;
ElevationDataBlock& operator=(const ElevationDataBlock&) = delete;
ElevationDataBlock(ElevationDataBlock&&) noexcept;
ElevationDataBlock& operator=(ElevationDataBlock&&) noexcept;
static std::shared_ptr<ElevationDataBlock>
Create(const std::string& dataBlockType,
const std::string& dataName,
std::istream& is);
private:
class Impl;
std::unique_ptr<Impl> p;
bool Parse(std::istream& is);
};
class DigitalRadarDataGeneric::MomentDataBlock :
public DataBlock,
public GenericRadarData::MomentDataBlock
{
public:
explicit MomentDataBlock(const std::string& dataBlockType,
const std::string& dataName);
~MomentDataBlock();
MomentDataBlock(const MomentDataBlock&) = delete;
MomentDataBlock& operator=(const MomentDataBlock&) = delete;
MomentDataBlock(MomentDataBlock&&) noexcept;
MomentDataBlock& operator=(MomentDataBlock&&) noexcept;
std::uint16_t number_of_data_moment_gates() const;
units::kilometers<float> data_moment_range() const;
std::int16_t data_moment_range_raw() const;
units::kilometers<float> data_moment_range_sample_interval() const;
std::uint16_t data_moment_range_sample_interval_raw() const;
float snr_threshold() 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;
static std::shared_ptr<MomentDataBlock>
Create(const std::string& dataBlockType,
const std::string& dataName,
std::istream& is);
private:
class Impl;
std::unique_ptr<Impl> p;
bool Parse(std::istream& is);
};
class DigitalRadarDataGeneric::RadialDataBlock : public DataBlock
{
public:
explicit RadialDataBlock(const std::string& dataBlockType,
const std::string& dataName);
~RadialDataBlock();
RadialDataBlock(const RadialDataBlock&) = delete;
RadialDataBlock& operator=(const RadialDataBlock&) = delete;
RadialDataBlock(RadialDataBlock&&) noexcept;
RadialDataBlock& operator=(RadialDataBlock&&) noexcept;
float unambiguous_range() const;
static std::shared_ptr<RadialDataBlock>
Create(const std::string& dataBlockType,
const std::string& dataName,
std::istream& is);
private:
class Impl;
std::unique_ptr<Impl> p;
bool Parse(std::istream& is);
};
class DigitalRadarDataGeneric::VolumeDataBlock : public DataBlock
{
public:
explicit VolumeDataBlock(const std::string& dataBlockType,
const std::string& dataName);
~VolumeDataBlock();
VolumeDataBlock(const VolumeDataBlock&) = delete;
VolumeDataBlock& operator=(const VolumeDataBlock&) = delete;
VolumeDataBlock(VolumeDataBlock&&) noexcept;
VolumeDataBlock& operator=(VolumeDataBlock&&) noexcept;
float latitude() const;
float longitude() const;
std::uint16_t volume_coverage_pattern_number() const;
static std::shared_ptr<VolumeDataBlock>
Create(const std::string& dataBlockType,
const std::string& dataName,
std::istream& is);
private:
class Impl;
std::unique_ptr<Impl> p;
bool Parse(std::istream& is);
};
} // namespace rda
} // namespace wsr88d
} // namespace scwx

View file

@ -0,0 +1,99 @@
#pragma once
#include <scwx/util/iterator.hpp>
#include <scwx/wsr88d/rda/level2_message.hpp>
#include <units/angle.h>
#include <units/length.h>
namespace scwx
{
namespace wsr88d
{
namespace rda
{
enum class DataBlockType
{
Volume,
Elevation,
Radial,
MomentRef,
MomentVel,
MomentSw,
MomentZdr,
MomentPhi,
MomentRho,
MomentCfp,
Unknown
};
typedef util::
Iterator<DataBlockType, DataBlockType::MomentRef, DataBlockType::MomentCfp>
MomentDataBlockTypeIterator;
class GenericRadarData;
typedef std::map<std::uint16_t, std::shared_ptr<GenericRadarData>>
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<float> 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<MomentDataBlock>
moment_data_block(DataBlockType type) const = 0;
private:
class Impl;
std::unique_ptr<Impl> 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<float> data_moment_range() const = 0;
virtual std::int16_t data_moment_range_raw() const = 0;
virtual units::kilometers<float>
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<Impl> p;
};
} // namespace rda
} // namespace wsr88d
} // namespace scwx

View file

@ -27,18 +27,18 @@ private:
explicit Level2MessageFactory() = delete; explicit Level2MessageFactory() = delete;
~Level2MessageFactory() = delete; ~Level2MessageFactory() = delete;
Level2MessageFactory(const Level2MessageFactory&) = delete; Level2MessageFactory(const Level2MessageFactory&) = delete;
Level2MessageFactory& operator=(const Level2MessageFactory&) = delete; Level2MessageFactory& operator=(const Level2MessageFactory&) = delete;
Level2MessageFactory(Level2MessageFactory&&) noexcept = delete; Level2MessageFactory(Level2MessageFactory&&) noexcept = delete;
Level2MessageFactory& operator=(Level2MessageFactory&&) noexcept = delete; Level2MessageFactory& operator=(Level2MessageFactory&&) noexcept = delete;
public: public:
struct Context; struct Context;
static std::shared_ptr<Context> CreateContext(); static std::shared_ptr<Context> CreateContext();
static Level2MessageInfo Create(std::istream& is, static Level2MessageInfo Create(std::istream& is,
std::shared_ptr<Context> ctx); std::shared_ptr<Context>& ctx);
}; };
} // namespace rda } // namespace rda

View file

@ -7,14 +7,15 @@ namespace wsr88d
namespace rda namespace rda
{ {
enum class MessageId : uint8_t enum class MessageId : std::uint8_t
{ {
DigitalRadarData = 1,
RdaStatusData = 2, RdaStatusData = 2,
PerformanceMaintenanceData = 3, PerformanceMaintenanceData = 3,
VolumeCoveragePatternData = 5, VolumeCoveragePatternData = 5,
ClutterFilterMap = 15, ClutterFilterMap = 15,
RdaAdaptationData = 18, RdaAdaptationData = 18,
DigitalRadarData = 31 DigitalRadarDataGeneric = 31
}; };
} // namespace rda } // namespace rda

View file

@ -17,16 +17,23 @@ std::string GetVcpDescription(uint16_t vcp)
case 31: case 31:
case 32: case 32:
case 35: case 35:
case 90: return CLEAR_AIR_MODE; case 90:
return CLEAR_AIR_MODE;
case 11:
case 12: case 12:
case 21:
case 80: case 80:
case 112: case 112:
case 121: case 121:
case 211:
case 212: case 212:
case 215: return PRECIPITATION_MODE; case 215:
case 221:
return PRECIPITATION_MODE;
default: return "?"; default:
return "?";
} }
} }

View file

@ -246,7 +246,8 @@ AwsNexradDataProvider::ListObjects(std::chrono::system_clock::time_point date)
{ {
std::string key = object.GetKey(); 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); auto time = GetTimePointByKey(key);

View file

@ -1,6 +1,7 @@
#include <scwx/wsr88d/ar2v_file.hpp> #include <scwx/wsr88d/ar2v_file.hpp>
#include <scwx/wsr88d/rda/digital_radar_data.hpp>
#include <scwx/wsr88d/rda/level2_message_factory.hpp> #include <scwx/wsr88d/rda/level2_message_factory.hpp>
#include <scwx/wsr88d/rda/types.hpp> #include <scwx/wsr88d/rda/rda_types.hpp>
#include <scwx/util/logger.hpp> #include <scwx/util/logger.hpp>
#include <scwx/util/rangebuf.hpp> #include <scwx/util/rangebuf.hpp>
#include <scwx/util/time.hpp> #include <scwx/util/time.hpp>
@ -18,6 +19,7 @@
# pragma GCC diagnostic ignored "-Wdeprecated-copy" # pragma GCC diagnostic ignored "-Wdeprecated-copy"
#endif #endif
#include <boost/algorithm/string/trim.hpp>
#include <boost/iostreams/copy.hpp> #include <boost/iostreams/copy.hpp>
#include <boost/iostreams/filtering_streambuf.hpp> #include <boost/iostreams/filtering_streambuf.hpp>
#include <boost/iostreams/filter/bzip2.hpp> #include <boost/iostreams/filter/bzip2.hpp>
@ -41,16 +43,7 @@ static const auto logger_ = util::Logger::Create(logPrefix_);
class Ar2vFileImpl class Ar2vFileImpl
{ {
public: public:
explicit Ar2vFileImpl() : explicit Ar2vFileImpl() {};
tapeFilename_ {},
extensionNumber_ {},
julianDate_ {0},
milliseconds_ {0},
icao_ {},
vcpData_ {nullptr},
radarData_ {},
index_ {},
rawRecords_ {} {};
~Ar2vFileImpl() = default; ~Ar2vFileImpl() = default;
std::size_t DecompressLDMRecords(std::istream& is); std::size_t DecompressLDMRecords(std::istream& is);
@ -58,22 +51,24 @@ public:
void IndexFile(); void IndexFile();
void ParseLDMRecords(); void ParseLDMRecords();
void ParseLDMRecord(std::istream& is); void ParseLDMRecord(std::istream& is);
void ProcessRadarData(std::shared_ptr<rda::DigitalRadarData> message); void ProcessRadarData(const std::shared_ptr<rda::GenericRadarData>& message);
std::string tapeFilename_; std::string tapeFilename_ {};
std::string extensionNumber_; std::string extensionNumber_ {};
std::uint32_t julianDate_; std::uint32_t julianDate_ {0};
std::uint32_t milliseconds_; std::uint32_t milliseconds_ {0};
std::string icao_; std::string icao_ {};
std::shared_ptr<rda::VolumeCoveragePatternData> vcpData_; std::size_t messageCount_ {0};
std::map<std::uint16_t, std::shared_ptr<rda::ElevationScan>> radarData_;
std::shared_ptr<rda::VolumeCoveragePatternData> vcpData_ {};
std::map<std::uint16_t, std::shared_ptr<rda::ElevationScan>> radarData_ {};
std::map<rda::DataBlockType, std::map<rda::DataBlockType,
std::map<std::uint16_t, std::shared_ptr<rda::ElevationScan>>> std::map<std::uint16_t, std::shared_ptr<rda::ElevationScan>>>
index_; index_ {};
std::list<std::stringstream> rawRecords_; std::list<std::stringstream> rawRecords_ {};
}; };
Ar2vFile::Ar2vFile() : p(std::make_unique<Ar2vFileImpl>()) {} Ar2vFile::Ar2vFile() : p(std::make_unique<Ar2vFileImpl>()) {}
@ -82,12 +77,12 @@ Ar2vFile::~Ar2vFile() = default;
Ar2vFile::Ar2vFile(Ar2vFile&&) noexcept = default; Ar2vFile::Ar2vFile(Ar2vFile&&) noexcept = default;
Ar2vFile& Ar2vFile::operator=(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_; return p->julianDate_;
} }
uint32_t Ar2vFile::milliseconds() const std::uint32_t Ar2vFile::milliseconds() const
{ {
return p->milliseconds_; return p->milliseconds_;
} }
@ -97,6 +92,11 @@ std::string Ar2vFile::icao() const
return p->icao_; return p->icao_;
} }
std::size_t Ar2vFile::message_count() const
{
return p->messageCount_;
}
std::chrono::system_clock::time_point Ar2vFile::start_time() const std::chrono::system_clock::time_point Ar2vFile::start_time() const
{ {
return util::TimePoint(p->julianDate_, p->milliseconds_); return util::TimePoint(p->julianDate_, p->milliseconds_);
@ -108,7 +108,7 @@ std::chrono::system_clock::time_point Ar2vFile::end_time() const
if (p->radarData_.size() > 0) if (p->radarData_.size() > 0)
{ {
std::shared_ptr<rda::DigitalRadarData> lastRadial = std::shared_ptr<rda::GenericRadarData> lastRadial =
p->radarData_.crbegin()->second->crbegin()->second; p->radarData_.crbegin()->second->crbegin()->second;
endTime = util::TimePoint(lastRadial->modified_julian_date(), endTime = util::TimePoint(lastRadial->modified_julian_date(),
@ -118,7 +118,7 @@ std::chrono::system_clock::time_point Ar2vFile::end_time() const
return endTime; return endTime;
} }
std::map<uint16_t, std::shared_ptr<rda::ElevationScan>> std::map<std::uint16_t, std::shared_ptr<rda::ElevationScan>>
Ar2vFile::radar_data() const Ar2vFile::radar_data() const
{ {
return p->radarData_; return p->radarData_;
@ -142,17 +142,17 @@ Ar2vFile::GetElevationScan(rda::DataBlockType dataBlockType,
float elevationCut = 0.0f; float elevationCut = 0.0f;
std::vector<float> elevationCuts; std::vector<float> elevationCuts;
uint16_t codedElevation = std::uint16_t codedElevation =
static_cast<uint16_t>(std::lroundf(elevation * scaleFactor)); static_cast<std::uint16_t>(std::lroundf(elevation * scaleFactor));
if (p->index_.contains(dataBlockType)) if (p->index_.contains(dataBlockType))
{ {
auto scans = p->index_.at(dataBlockType); auto& scans = p->index_.at(dataBlockType);
uint16_t lowerBound = scans.cbegin()->first; std::uint16_t lowerBound = scans.cbegin()->first;
uint16_t upperBound = scans.crbegin()->first; std::uint16_t upperBound = scans.crbegin()->first;
for (auto scan : scans) for (auto& scan : scans)
{ {
if (scan.first > lowerBound && scan.first <= codedElevation) if (scan.first > lowerBound && scan.first <= codedElevation)
{ {
@ -166,10 +166,12 @@ Ar2vFile::GetElevationScan(rda::DataBlockType dataBlockType,
elevationCuts.push_back(scan.first / scaleFactor); elevationCuts.push_back(scan.first / scaleFactor);
} }
int32_t lowerDelta = std::abs(static_cast<int32_t>(codedElevation) - std::int32_t lowerDelta =
static_cast<int32_t>(lowerBound)); std::abs(static_cast<std::int32_t>(codedElevation) -
int32_t upperDelta = std::abs(static_cast<int32_t>(codedElevation) - static_cast<std::int32_t>(lowerBound));
static_cast<int32_t>(upperBound)); std::int32_t upperDelta =
std::abs(static_cast<std::int32_t>(codedElevation) -
static_cast<std::int32_t>(upperBound));
if (lowerDelta < upperDelta) if (lowerDelta < upperDelta)
{ {
@ -232,6 +234,10 @@ bool Ar2vFile::LoadData(std::istream& is)
dataValid = false; 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) if (dataValid)
{ {
logger_->debug("Filename: {}", p->tapeFilename_); logger_->debug("Filename: {}", p->tapeFilename_);
@ -256,17 +262,17 @@ bool Ar2vFile::LoadData(std::istream& is)
return dataValid; return dataValid;
} }
size_t Ar2vFileImpl::DecompressLDMRecords(std::istream& is) std::size_t Ar2vFileImpl::DecompressLDMRecords(std::istream& is)
{ {
logger_->debug("Decompressing LDM Records"); logger_->debug("Decompressing LDM Records");
size_t numRecords = 0; std::size_t numRecords = 0;
while (is.peek() != EOF) while (is.peek() != EOF)
{ {
std::streampos startPosition = is.tellg(); std::streampos startPosition = is.tellg();
int32_t controlWord = 0; std::int32_t controlWord = 0;
size_t recordSize; std::size_t recordSize;
is.read(reinterpret_cast<char*>(&controlWord), 4); is.read(reinterpret_cast<char*>(&controlWord), 4);
@ -315,7 +321,7 @@ void Ar2vFileImpl::ParseLDMRecords()
{ {
logger_->debug("Parsing LDM Records"); logger_->debug("Parsing LDM Records");
size_t count = 0; std::size_t count = 0;
for (auto it = rawRecords_.begin(); it != rawRecords_.end(); it++) for (auto it = rawRecords_.begin(); it != rawRecords_.end(); it++)
{ {
@ -331,65 +337,82 @@ void Ar2vFileImpl::ParseLDMRecords()
void Ar2vFileImpl::ParseLDMRecord(std::istream& is) 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(); auto ctx = rda::Level2MessageFactory::CreateContext();
// The communications manager inserts an extra 12 bytes at the beginning while (!is.eof() && !is.fail())
// of each record
is.seekg(12, std::ios_base::cur);
while (!is.eof())
{ {
off_t offset = 0; // The communications manager inserts an extra 12 bytes at the beginning
uint16_t nextSize = 0u; // of each record
do 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<char*>(&nextSize), 2); std::uint8_t messageType = messageHeader.message_type();
if (nextSize == 0)
// 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<std::size_t>(
messageHeader.number_of_message_segments())
<< 16) +
messageHeader.message_segment_number();
}
else
{
messageSize =
static_cast<std::size_t>(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() && nextSize == 0u);
if (!is.eof() && offset != 0)
{
logger_->trace("Next record offset by {} bytes", offset);
}
else if (is.eof())
{
break;
} }
rda::Level2MessageInfo msgInfo = // Skip to next message
rda::Level2MessageFactory::Create(is, ctx); is.seekg(messageStart + static_cast<std::streampos>(messageSize),
if (!msgInfo.headerValid) std::ios_base::beg);
{
// Invalid message
break;
}
if (msgInfo.messageValid)
{
HandleMessage(msgInfo.message);
}
} }
} }
void Ar2vFileImpl::HandleMessage(std::shared_ptr<rda::Level2Message>& message) void Ar2vFileImpl::HandleMessage(std::shared_ptr<rda::Level2Message>& message)
{ {
++messageCount_;
switch (message->header().message_type()) switch (message->header().message_type())
{ {
case static_cast<uint8_t>(rda::MessageId::VolumeCoveragePatternData): case static_cast<std::uint8_t>(rda::MessageId::VolumeCoveragePatternData):
vcpData_ = vcpData_ =
std::static_pointer_cast<rda::VolumeCoveragePatternData>(message); std::static_pointer_cast<rda::VolumeCoveragePatternData>(message);
break; break;
case static_cast<uint8_t>(rda::MessageId::DigitalRadarData): case static_cast<std::uint8_t>(rda::MessageId::DigitalRadarData):
case static_cast<std::uint8_t>(rda::MessageId::DigitalRadarDataGeneric):
ProcessRadarData( ProcessRadarData(
std::static_pointer_cast<rda::DigitalRadarData>(message)); std::static_pointer_cast<rda::GenericRadarData>(message));
break; break;
default: default:
@ -398,10 +421,10 @@ void Ar2vFileImpl::HandleMessage(std::shared_ptr<rda::Level2Message>& message)
} }
void Ar2vFileImpl::ProcessRadarData( void Ar2vFileImpl::ProcessRadarData(
std::shared_ptr<rda::DigitalRadarData> message) const std::shared_ptr<rda::GenericRadarData>& message)
{ {
uint16_t azimuthIndex = message->azimuth_number() - 1; std::uint16_t azimuthIndex = message->azimuth_number() - 1;
uint16_t elevationIndex = message->elevation_number() - 1; std::uint16_t elevationIndex = message->elevation_number() - 1;
if (radarData_[elevationIndex] == nullptr) if (radarData_[elevationIndex] == nullptr)
{ {
@ -415,20 +438,12 @@ void Ar2vFileImpl::IndexFile()
{ {
logger_->debug("Indexing file"); logger_->debug("Indexing file");
if (vcpData_ == nullptr) for (auto& elevationCut : radarData_)
{ {
logger_->warn("Cannot index file without VCP data"); std::uint16_t elevationAngle {};
return; rda::WaveformType waveformType = rda::WaveformType::Unknown;
}
for (auto elevationCut : radarData_) std::shared_ptr<rda::GenericRadarData>& radial0 =
{
uint16_t elevationAngle =
vcpData_->elevation_angle_raw(elevationCut.first);
rda::WaveformType waveformType =
vcpData_->waveform_type(elevationCut.first);
std::shared_ptr<rda::DigitalRadarData> radial0 =
(*elevationCut.second)[0]; (*elevationCut.second)[0];
if (radial0 == nullptr) if (radial0 == nullptr)
@ -437,6 +452,26 @@ void Ar2vFileImpl::IndexFile()
continue; continue;
} }
std::shared_ptr<rda::DigitalRadarData> 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<rda::DigitalRadarData>(
(*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 : for (rda::DataBlockType dataBlockType :
rda::MomentDataBlockTypeIterator()) rda::MomentDataBlockTypeIterator())
{ {

View file

@ -70,9 +70,9 @@ std::shared_ptr<NexradFile> NexradFileFactory::Create(std::istream& is)
std::string buffer; std::string buffer;
bool dataValid; bool dataValid;
buffer.resize(4); buffer.resize(8);
is.read(buffer.data(), 4); is.read(buffer.data(), 8);
dataValid = is.good(); dataValid = is.good();
is.seekg(pisBegin, std::ios_base::beg); is.seekg(pisBegin, std::ios_base::beg);
@ -89,7 +89,7 @@ std::shared_ptr<NexradFile> NexradFileFactory::Create(std::istream& is)
pis = &ss; pis = &ss;
pisBegin = ss.tellg(); pisBegin = ss.tellg();
ss.read(buffer.data(), 4); ss.read(buffer.data(), 8);
dataValid = ss.good(); dataValid = ss.good();
ss.seekg(pisBegin, std::ios_base::beg); ss.seekg(pisBegin, std::ios_base::beg);
@ -114,7 +114,7 @@ std::shared_ptr<NexradFile> NexradFileFactory::Create(std::istream& is)
if (dataValid) if (dataValid)
{ {
if (buffer.starts_with("AR2V")) if (buffer.starts_with("AR2V") || buffer.starts_with("ARCHIVE2"))
{ {
message = std::make_shared<Ar2vFile>(); message = std::make_shared<Ar2vFile>();
} }

View file

@ -78,19 +78,22 @@ bool ClutterFilterBypassMap::Parse(std::istream& is)
if (p->mapGenerationDate_ < 1) if (p->mapGenerationDate_ < 1)
{ {
logger_->warn("Invalid date: {}", p->mapGenerationDate_); logger_->trace("Ignoring empty message");
messageValid = false; messageValid = false;
} }
if (p->mapGenerationTime_ > 1440) else
{ {
logger_->warn("Invalid time: {}", p->mapGenerationTime_); if (p->mapGenerationTime_ > 1440)
messageValid = false; {
} logger_->warn("Invalid time: {}", p->mapGenerationTime_);
if (numElevationSegments < 1 || numElevationSegments > 5) messageValid = false;
{ }
logger_->warn("Invalid number of elevation segments: {}", if (numElevationSegments < 1 || numElevationSegments > 5)
numElevationSegments); {
messageValid = false; logger_->warn("Invalid number of elevation segments: {}",
numElevationSegments);
messageValid = false;
}
} }
if (!messageValid) if (!messageValid)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,776 @@
#include <scwx/wsr88d/rda/digital_radar_data_generic.hpp>
#include <scwx/util/logger.hpp>
namespace scwx
{
namespace wsr88d
{
namespace rda
{
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<std::string, DataBlockType> 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 DigitalRadarDataGeneric::DataBlock::Impl
{
public:
explicit Impl(const std::string& dataBlockType,
const std::string& dataName) :
dataBlockType_ {dataBlockType}, dataName_ {dataName}
{
}
std::string dataBlockType_;
std::string dataName_;
};
DigitalRadarDataGeneric::DataBlock::DataBlock(const std::string& dataBlockType,
const std::string& dataName) :
p(std::make_unique<Impl>(dataBlockType, dataName))
{
}
DigitalRadarDataGeneric::DataBlock::~DataBlock() = default;
DigitalRadarDataGeneric::DataBlock::DataBlock(DataBlock&&) noexcept = default;
DigitalRadarDataGeneric::DataBlock&
DigitalRadarDataGeneric::DataBlock::operator=(DataBlock&&) noexcept = default;
class DigitalRadarDataGeneric::MomentDataBlock::Impl
{
public:
explicit Impl() {}
std::uint16_t numberOfDataMomentGates_ {0};
std::int16_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<std::uint8_t> momentGates8_ {};
std::vector<std::uint16_t> momentGates16_ {};
};
DigitalRadarDataGeneric::MomentDataBlock::MomentDataBlock(
const std::string& dataBlockType, const std::string& dataName) :
DataBlock(dataBlockType, dataName), p(std::make_unique<Impl>())
{
}
DigitalRadarDataGeneric::MomentDataBlock::~MomentDataBlock() = default;
DigitalRadarDataGeneric::MomentDataBlock::MomentDataBlock(
MomentDataBlock&&) noexcept = default;
DigitalRadarDataGeneric::MomentDataBlock&
DigitalRadarDataGeneric::MomentDataBlock::operator=(
MomentDataBlock&&) noexcept = default;
std::uint16_t
DigitalRadarDataGeneric::MomentDataBlock::number_of_data_moment_gates() const
{
return p->numberOfDataMomentGates_;
}
units::kilometers<float>
DigitalRadarDataGeneric::MomentDataBlock::data_moment_range() const
{
return units::kilometers<float> {p->dataMomentRange_ * 0.001f};
}
std::int16_t
DigitalRadarDataGeneric::MomentDataBlock::data_moment_range_raw() const
{
return p->dataMomentRange_;
}
units::kilometers<float>
DigitalRadarDataGeneric::MomentDataBlock::data_moment_range_sample_interval()
const
{
return units::kilometers<float> {p->dataMomentRangeSampleInterval_ * 0.001f};
}
std::uint16_t DigitalRadarDataGeneric::MomentDataBlock::
data_moment_range_sample_interval_raw() const
{
return p->dataMomentRangeSampleInterval_;
}
float DigitalRadarDataGeneric::MomentDataBlock::snr_threshold() const
{
return p->snrThreshold_ * 0.1f;
}
std::int16_t DigitalRadarDataGeneric::MomentDataBlock::snr_threshold_raw() const
{
return p->snrThreshold_;
}
std::uint8_t DigitalRadarDataGeneric::MomentDataBlock::data_word_size() const
{
return p->dataWordSize_;
}
float DigitalRadarDataGeneric::MomentDataBlock::scale() const
{
return p->scale_;
}
float DigitalRadarDataGeneric::MomentDataBlock::offset() const
{
return p->offset_;
}
const void* DigitalRadarDataGeneric::MomentDataBlock::data_moments() const
{
const void* dataMoments;
switch (p->dataWordSize_)
{
case 8:
dataMoments = p->momentGates8_.data();
break;
case 16:
dataMoments = p->momentGates16_.data();
break;
default:
dataMoments = nullptr;
break;
}
return dataMoments;
}
std::shared_ptr<DigitalRadarDataGeneric::MomentDataBlock>
DigitalRadarDataGeneric::MomentDataBlock::Create(
const std::string& dataBlockType,
const std::string& dataName,
std::istream& is)
{
std::shared_ptr<MomentDataBlock> p =
std::make_shared<MomentDataBlock>(dataBlockType, dataName);
if (!p->Parse(is))
{
p.reset();
}
return p;
}
bool DigitalRadarDataGeneric::MomentDataBlock::Parse(std::istream& is)
{
bool dataBlockValid = true;
is.seekg(4, std::ios_base::cur); // 4-7
is.read(reinterpret_cast<char*>(&p->numberOfDataMomentGates_), 2); // 8-9
is.read(reinterpret_cast<char*>(&p->dataMomentRange_), 2); // 10-11
is.read(reinterpret_cast<char*>(&p->dataMomentRangeSampleInterval_),
2); // 12-13
is.read(reinterpret_cast<char*>(&p->tover_), 2); // 14-15
is.read(reinterpret_cast<char*>(&p->snrThreshold_), 2); // 16-17
is.read(reinterpret_cast<char*>(&p->controlFlags_), 1); // 18
is.read(reinterpret_cast<char*>(&p->dataWordSize_), 1); // 19
is.read(reinterpret_cast<char*>(&p->scale_), 4); // 20-23
is.read(reinterpret_cast<char*>(&p->offset_), 4); // 24-27
p->numberOfDataMomentGates_ = ntohs(p->numberOfDataMomentGates_);
p->dataMomentRange_ = ntohs(p->dataMomentRange_);
p->dataMomentRangeSampleInterval_ = ntohs(p->dataMomentRangeSampleInterval_);
p->tover_ = ntohs(p->tover_);
p->snrThreshold_ = ntohs(p->snrThreshold_);
p->scale_ = awips::Message::SwapFloat(p->scale_);
p->offset_ = awips::Message::SwapFloat(p->offset_);
if (p->numberOfDataMomentGates_ <= 1840)
{
if (p->dataWordSize_ == 8)
{
p->momentGates8_.resize(p->numberOfDataMomentGates_);
is.read(reinterpret_cast<char*>(p->momentGates8_.data()),
p->numberOfDataMomentGates_);
}
else if (p->dataWordSize_ == 16)
{
p->momentGates16_.resize(p->numberOfDataMomentGates_);
is.read(reinterpret_cast<char*>(p->momentGates16_.data()),
p->numberOfDataMomentGates_ * 2);
awips::Message::SwapVector(p->momentGates16_);
}
else
{
logger_->warn("Invalid data word size: {}", p->dataWordSize_);
dataBlockValid = false;
}
}
else
{
logger_->warn("Invalid number of data moment gates: {}",
p->numberOfDataMomentGates_);
dataBlockValid = false;
}
return dataBlockValid;
}
class DigitalRadarDataGeneric::VolumeDataBlock::Impl
{
public:
explicit Impl() {}
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};
};
DigitalRadarDataGeneric::VolumeDataBlock::VolumeDataBlock(
const std::string& dataBlockType, const std::string& dataName) :
DataBlock(dataBlockType, dataName), p(std::make_unique<Impl>())
{
}
DigitalRadarDataGeneric::VolumeDataBlock::~VolumeDataBlock() = default;
DigitalRadarDataGeneric::VolumeDataBlock::VolumeDataBlock(
VolumeDataBlock&&) noexcept = default;
DigitalRadarDataGeneric::VolumeDataBlock&
DigitalRadarDataGeneric::VolumeDataBlock::operator=(
VolumeDataBlock&&) noexcept = default;
float DigitalRadarDataGeneric::VolumeDataBlock::latitude() const
{
return p->latitude_;
}
float DigitalRadarDataGeneric::VolumeDataBlock::longitude() const
{
return p->longitude_;
}
std::uint16_t
DigitalRadarDataGeneric::VolumeDataBlock::volume_coverage_pattern_number() const
{
return p->volumeCoveragePatternNumber_;
}
std::shared_ptr<DigitalRadarDataGeneric::VolumeDataBlock>
DigitalRadarDataGeneric::VolumeDataBlock::Create(
const std::string& dataBlockType,
const std::string& dataName,
std::istream& is)
{
std::shared_ptr<VolumeDataBlock> p =
std::make_shared<VolumeDataBlock>(dataBlockType, dataName);
if (!p->Parse(is))
{
p.reset();
}
return p;
}
bool DigitalRadarDataGeneric::VolumeDataBlock::Parse(std::istream& is)
{
bool dataBlockValid = true;
is.read(reinterpret_cast<char*>(&p->lrtup_), 2); // 4-5
is.read(reinterpret_cast<char*>(&p->versionNumberMajor_), 1); // 6
is.read(reinterpret_cast<char*>(&p->versionNumberMinor_), 1); // 7
is.read(reinterpret_cast<char*>(&p->latitude_), 4); // 8-11
is.read(reinterpret_cast<char*>(&p->longitude_), 4); // 12-15
is.read(reinterpret_cast<char*>(&p->siteHeight_), 2); // 16-17
is.read(reinterpret_cast<char*>(&p->feedhornHeight_), 2); // 18-19
is.read(reinterpret_cast<char*>(&p->calibrationConstant_), 4); // 20-23
is.read(reinterpret_cast<char*>(&p->horizontaShvTxPower_), 4); // 24-27
is.read(reinterpret_cast<char*>(&p->verticalShvTxPower_), 4); // 28-31
is.read(reinterpret_cast<char*>(&p->systemDifferentialReflectivity_),
4); // 32-35
is.read(reinterpret_cast<char*>(&p->initialSystemDifferentialPhase_),
4); // 36-39
is.read(reinterpret_cast<char*>(&p->volumeCoveragePatternNumber_),
2); // 40-41
is.read(reinterpret_cast<char*>(&p->processingStatus_), 2); // 42-43
p->lrtup_ = ntohs(p->lrtup_);
p->latitude_ = awips::Message::SwapFloat(p->latitude_);
p->longitude_ = awips::Message::SwapFloat(p->longitude_);
p->siteHeight_ = ntohs(p->siteHeight_);
p->feedhornHeight_ = ntohs(p->feedhornHeight_);
p->calibrationConstant_ = awips::Message::SwapFloat(p->calibrationConstant_);
p->horizontaShvTxPower_ = awips::Message::SwapFloat(p->horizontaShvTxPower_);
p->verticalShvTxPower_ = awips::Message::SwapFloat(p->verticalShvTxPower_);
p->systemDifferentialReflectivity_ =
awips::Message::SwapFloat(p->systemDifferentialReflectivity_);
p->initialSystemDifferentialPhase_ =
awips::Message::SwapFloat(p->initialSystemDifferentialPhase_);
p->volumeCoveragePatternNumber_ = ntohs(p->volumeCoveragePatternNumber_);
p->processingStatus_ = ntohs(p->processingStatus_);
return dataBlockValid;
}
class DigitalRadarDataGeneric::ElevationDataBlock::Impl
{
public:
explicit Impl() {}
std::uint16_t lrtup_ {0};
std::int16_t atmos_ {0};
float calibrationConstant_ {0.0f};
};
DigitalRadarDataGeneric::ElevationDataBlock::ElevationDataBlock(
const std::string& dataBlockType, const std::string& dataName) :
DataBlock(dataBlockType, dataName), p(std::make_unique<Impl>())
{
}
DigitalRadarDataGeneric::ElevationDataBlock::~ElevationDataBlock() = default;
DigitalRadarDataGeneric::ElevationDataBlock::ElevationDataBlock(
ElevationDataBlock&&) noexcept = default;
DigitalRadarDataGeneric::ElevationDataBlock&
DigitalRadarDataGeneric::ElevationDataBlock::operator=(
ElevationDataBlock&&) noexcept = default;
std::shared_ptr<DigitalRadarDataGeneric::ElevationDataBlock>
DigitalRadarDataGeneric::ElevationDataBlock::Create(
const std::string& dataBlockType,
const std::string& dataName,
std::istream& is)
{
std::shared_ptr<ElevationDataBlock> p =
std::make_shared<ElevationDataBlock>(dataBlockType, dataName);
if (!p->Parse(is))
{
p.reset();
}
return p;
}
bool DigitalRadarDataGeneric::ElevationDataBlock::Parse(std::istream& is)
{
bool dataBlockValid = true;
is.read(reinterpret_cast<char*>(&p->lrtup_), 2); // 4-5
is.read(reinterpret_cast<char*>(&p->atmos_), 2); // 6-7
is.read(reinterpret_cast<char*>(&p->calibrationConstant_), 4); // 8-11
p->lrtup_ = ntohs(p->lrtup_);
p->atmos_ = ntohs(p->atmos_);
p->calibrationConstant_ = awips::Message::SwapFloat(p->calibrationConstant_);
return dataBlockValid;
}
class DigitalRadarDataGeneric::RadialDataBlock::Impl
{
public:
explicit Impl() {}
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};
};
DigitalRadarDataGeneric::RadialDataBlock::RadialDataBlock(
const std::string& dataBlockType, const std::string& dataName) :
DataBlock(dataBlockType, dataName), p(std::make_unique<Impl>())
{
}
DigitalRadarDataGeneric::RadialDataBlock::~RadialDataBlock() = default;
DigitalRadarDataGeneric::RadialDataBlock::RadialDataBlock(
RadialDataBlock&&) noexcept = default;
DigitalRadarDataGeneric::RadialDataBlock&
DigitalRadarDataGeneric::RadialDataBlock::operator=(
RadialDataBlock&&) noexcept = default;
float DigitalRadarDataGeneric::RadialDataBlock::unambiguous_range() const
{
return p->unambigiousRange_ / 10.0f;
}
std::shared_ptr<DigitalRadarDataGeneric::RadialDataBlock>
DigitalRadarDataGeneric::RadialDataBlock::Create(
const std::string& dataBlockType,
const std::string& dataName,
std::istream& is)
{
std::shared_ptr<RadialDataBlock> p =
std::make_shared<RadialDataBlock>(dataBlockType, dataName);
if (!p->Parse(is))
{
p.reset();
}
return p;
}
bool DigitalRadarDataGeneric::RadialDataBlock::Parse(std::istream& is)
{
bool dataBlockValid = true;
is.read(reinterpret_cast<char*>(&p->lrtup_), 2); // 4-5
is.read(reinterpret_cast<char*>(&p->unambigiousRange_), 2); // 6-7
is.read(reinterpret_cast<char*>(&p->noiseLevelHorizontal_), 4); // 8-11
is.read(reinterpret_cast<char*>(&p->noiseLevelVertical_), 4); // 12-15
is.read(reinterpret_cast<char*>(&p->nyquistVelocity_), 2); // 16-17
is.read(reinterpret_cast<char*>(&p->radialFlags_), 2); // 18-19
is.read(reinterpret_cast<char*>(&p->calibrationConstantHorizontal_),
4); // 20-23
is.read(reinterpret_cast<char*>(&p->calibrationConstantVertical_),
4); // 24-27
p->lrtup_ = ntohs(p->lrtup_);
p->unambigiousRange_ = ntohs(p->unambigiousRange_);
p->noiseLevelHorizontal_ =
awips::Message::SwapFloat(p->noiseLevelHorizontal_);
p->noiseLevelVertical_ = awips::Message::SwapFloat(p->noiseLevelVertical_);
p->nyquistVelocity_ = ntohs(p->nyquistVelocity_);
p->radialFlags_ = ntohs(p->radialFlags_);
p->calibrationConstantHorizontal_ =
awips::Message::SwapFloat(p->calibrationConstantHorizontal_);
p->calibrationConstantVertical_ =
awips::Message::SwapFloat(p->calibrationConstantVertical_);
return dataBlockValid;
}
class DigitalRadarDataGeneric::Impl
{
public:
explicit Impl() {};
~Impl() = default;
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<std::uint32_t, 10> dataBlockPointer_ {0};
std::shared_ptr<VolumeDataBlock> volumeDataBlock_ {nullptr};
std::shared_ptr<ElevationDataBlock> elevationDataBlock_ {nullptr};
std::shared_ptr<RadialDataBlock> radialDataBlock_ {nullptr};
std::unordered_map<DataBlockType, std::shared_ptr<MomentDataBlock>>
momentDataBlock_ {};
};
DigitalRadarDataGeneric::DigitalRadarDataGeneric() :
GenericRadarData(), p(std::make_unique<Impl>())
{
}
DigitalRadarDataGeneric::~DigitalRadarDataGeneric() = default;
DigitalRadarDataGeneric::DigitalRadarDataGeneric(
DigitalRadarDataGeneric&&) noexcept = default;
DigitalRadarDataGeneric& DigitalRadarDataGeneric::operator=(
DigitalRadarDataGeneric&&) noexcept = default;
std::string DigitalRadarDataGeneric::radar_identifier() const
{
return p->radarIdentifier_;
}
std::uint32_t DigitalRadarDataGeneric::collection_time() const
{
return p->collectionTime_;
}
std::uint16_t DigitalRadarDataGeneric::modified_julian_date() const
{
return p->modifiedJulianDate_;
}
std::uint16_t DigitalRadarDataGeneric::azimuth_number() const
{
return p->azimuthNumber_;
}
units::degrees<float> DigitalRadarDataGeneric::azimuth_angle() const
{
return units::degrees<float> {p->azimuthAngle_};
}
std::uint8_t DigitalRadarDataGeneric::compression_indicator() const
{
return p->compressionIndicator_;
}
std::uint16_t DigitalRadarDataGeneric::radial_length() const
{
return p->radialLength_;
}
std::uint8_t DigitalRadarDataGeneric::azimuth_resolution_spacing() const
{
return p->azimuthResolutionSpacing_;
}
std::uint8_t DigitalRadarDataGeneric::radial_status() const
{
return p->radialStatus_;
}
std::uint16_t DigitalRadarDataGeneric::elevation_number() const
{
return p->elevationNumber_;
}
std::uint8_t DigitalRadarDataGeneric::cut_sector_number() const
{
return p->cutSectorNumber_;
}
units::degrees<float> DigitalRadarDataGeneric::elevation_angle() const
{
return units::degrees<float> {p->elevationAngle_};
}
std::uint8_t DigitalRadarDataGeneric::radial_spot_blanking_status() const
{
return p->radialSpotBlankingStatus_;
}
std::uint8_t DigitalRadarDataGeneric::azimuth_indexing_mode() const
{
return p->azimuthIndexingMode_;
}
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::ElevationDataBlock>
DigitalRadarDataGeneric::elevation_data_block() const
{
return p->elevationDataBlock_;
}
std::shared_ptr<DigitalRadarDataGeneric::RadialDataBlock>
DigitalRadarDataGeneric::radial_data_block() const
{
return p->radialDataBlock_;
}
std::shared_ptr<DigitalRadarDataGeneric::VolumeDataBlock>
DigitalRadarDataGeneric::volume_data_block() const
{
return p->volumeDataBlock_;
}
std::shared_ptr<GenericRadarData::MomentDataBlock>
DigitalRadarDataGeneric::moment_data_block(DataBlockType type) const
{
std::shared_ptr<MomentDataBlock> momentDataBlock = nullptr;
auto it = p->momentDataBlock_.find(type);
if (it != p->momentDataBlock_.end())
{
momentDataBlock = it->second;
}
return momentDataBlock;
}
bool DigitalRadarDataGeneric::Parse(std::istream& is)
{
logger_->trace("Parsing Digital Radar Data (Message Type 31)");
bool messageValid = true;
std::size_t bytesRead = 0;
std::streampos isBegin = is.tellg();
p->radarIdentifier_.resize(4);
is.read(&p->radarIdentifier_[0], 4); // 0-3
is.read(reinterpret_cast<char*>(&p->collectionTime_), 4); // 4-7
is.read(reinterpret_cast<char*>(&p->modifiedJulianDate_), 2); // 8-9
is.read(reinterpret_cast<char*>(&p->azimuthNumber_), 2); // 10-11
is.read(reinterpret_cast<char*>(&p->azimuthAngle_), 4); // 12-15
is.read(reinterpret_cast<char*>(&p->compressionIndicator_), 1); // 16
is.seekg(1, std::ios_base::cur); // 17
is.read(reinterpret_cast<char*>(&p->radialLength_), 2); // 18-19
is.read(reinterpret_cast<char*>(&p->azimuthResolutionSpacing_), 1); // 20
is.read(reinterpret_cast<char*>(&p->radialStatus_), 1); // 21
is.read(reinterpret_cast<char*>(&p->elevationNumber_), 1); // 22
is.read(reinterpret_cast<char*>(&p->cutSectorNumber_), 1); // 23
is.read(reinterpret_cast<char*>(&p->elevationAngle_), 4); // 24-27
is.read(reinterpret_cast<char*>(&p->radialSpotBlankingStatus_), 1); // 28
is.read(reinterpret_cast<char*>(&p->azimuthIndexingMode_), 1); // 29
is.read(reinterpret_cast<char*>(&p->dataBlockCount_), 2); // 30-31
p->collectionTime_ = ntohl(p->collectionTime_);
p->modifiedJulianDate_ = ntohs(p->modifiedJulianDate_);
p->azimuthNumber_ = ntohs(p->azimuthNumber_);
p->azimuthAngle_ = SwapFloat(p->azimuthAngle_);
p->radialLength_ = ntohs(p->radialLength_);
p->elevationAngle_ = SwapFloat(p->elevationAngle_);
p->dataBlockCount_ = ntohs(p->dataBlockCount_);
if (p->azimuthNumber_ < 1 || p->azimuthNumber_ > 720)
{
logger_->warn("Invalid azimuth number: {}", p->azimuthNumber_);
messageValid = false;
}
if (p->elevationNumber_ < 1 || p->elevationNumber_ > 32)
{
logger_->warn("Invalid elevation number: {}", p->elevationNumber_);
messageValid = false;
}
if (p->dataBlockCount_ < 4 || p->dataBlockCount_ > 10)
{
logger_->warn("Invalid number of data blocks: {}", p->dataBlockCount_);
messageValid = false;
}
if (p->compressionIndicator_ != 0)
{
logger_->warn("Compression not supported");
messageValid = false;
}
if (!messageValid)
{
p->dataBlockCount_ = 0;
}
is.read(reinterpret_cast<char*>(&p->dataBlockPointer_),
p->dataBlockCount_ * 4);
SwapArray(p->dataBlockPointer_, p->dataBlockCount_);
for (uint16_t b = 0; b < p->dataBlockCount_; ++b)
{
is.seekg(isBegin + std::streamoff(p->dataBlockPointer_[b]),
std::ios_base::beg);
std::string dataBlockType(1, 0);
std::string dataName(3, 0);
is.read(&dataBlockType[0], 1);
is.read(&dataName[0], 3);
DataBlockType dataBlock = DataBlockType::Unknown;
try
{
dataBlock = strToDataBlock_.at(dataName);
}
catch (const std::exception&)
{
}
switch (dataBlock)
{
case DataBlockType::Volume:
p->volumeDataBlock_ =
std::move(VolumeDataBlock::Create(dataBlockType, dataName, is));
break;
case DataBlockType::Elevation:
p->elevationDataBlock_ =
std::move(ElevationDataBlock::Create(dataBlockType, dataName, is));
break;
case DataBlockType::Radial:
p->radialDataBlock_ =
std::move(RadialDataBlock::Create(dataBlockType, dataName, is));
break;
case DataBlockType::MomentRef:
case DataBlockType::MomentVel:
case DataBlockType::MomentSw:
case DataBlockType::MomentZdr:
case DataBlockType::MomentPhi:
case DataBlockType::MomentRho:
case DataBlockType::MomentCfp:
p->momentDataBlock_[dataBlock] =
std::move(MomentDataBlock::Create(dataBlockType, dataName, is));
break;
default:
logger_->warn("Unknown data name: {}", dataName);
break;
}
}
is.seekg(isBegin, std::ios_base::beg);
if (!ValidateMessage(is, bytesRead))
{
messageValid = false;
}
return messageValid;
}
std::shared_ptr<DigitalRadarDataGeneric>
DigitalRadarDataGeneric::Create(Level2MessageHeader&& header, std::istream& is)
{
std::shared_ptr<DigitalRadarDataGeneric> message =
std::make_shared<DigitalRadarDataGeneric>();
message->set_header(std::move(header));
if (!message->Parse(is))
{
message.reset();
}
return message;
}
} // namespace rda
} // namespace wsr88d
} // namespace scwx

View file

@ -0,0 +1,61 @@
#include <scwx/wsr88d/rda/generic_radar_data.hpp>
#include <scwx/util/logger.hpp>
namespace scwx
{
namespace wsr88d
{
namespace rda
{
static const std::string logPrefix_ = "scwx::wsr88d::rda::generic_radar_data";
static const std::unordered_map<std::string, DataBlockType> 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<Impl>())
{
}
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<Impl>())
{
}
GenericRadarData::~GenericRadarData() = default;
GenericRadarData::GenericRadarData(GenericRadarData&&) noexcept = default;
GenericRadarData&
GenericRadarData::operator=(GenericRadarData&&) noexcept = default;
} // namespace rda
} // namespace wsr88d
} // namespace scwx

View file

@ -5,6 +5,7 @@
#include <scwx/wsr88d/rda/clutter_filter_bypass_map.hpp> #include <scwx/wsr88d/rda/clutter_filter_bypass_map.hpp>
#include <scwx/wsr88d/rda/clutter_filter_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.hpp>
#include <scwx/wsr88d/rda/digital_radar_data_generic.hpp>
#include <scwx/wsr88d/rda/performance_maintenance_data.hpp> #include <scwx/wsr88d/rda/performance_maintenance_data.hpp>
#include <scwx/wsr88d/rda/rda_adaptation_data.hpp> #include <scwx/wsr88d/rda/rda_adaptation_data.hpp>
#include <scwx/wsr88d/rda/rda_status_data.hpp> #include <scwx/wsr88d/rda/rda_status_data.hpp>
@ -28,14 +29,15 @@ typedef std::function<std::shared_ptr<Level2Message>(Level2MessageHeader&&,
std::istream&)> std::istream&)>
CreateLevel2MessageFunction; CreateLevel2MessageFunction;
static const std::unordered_map<unsigned int, CreateLevel2MessageFunction> create_ { static const std::unordered_map<unsigned int, CreateLevel2MessageFunction>
{2, RdaStatusData::Create}, create_ {{1, DigitalRadarData::Create},
{3, PerformanceMaintenanceData::Create}, {2, RdaStatusData::Create},
{5, VolumeCoveragePatternData::Create}, {3, PerformanceMaintenanceData::Create},
{13, ClutterFilterBypassMap::Create}, {5, VolumeCoveragePatternData::Create},
{15, ClutterFilterMap::Create}, {13, ClutterFilterBypassMap::Create},
{18, RdaAdaptationData::Create}, {15, ClutterFilterMap::Create},
{31, DigitalRadarData::Create}}; {18, RdaAdaptationData::Create},
{31, DigitalRadarDataGeneric::Create}};
struct Level2MessageFactory::Context struct Level2MessageFactory::Context
{ {
@ -51,6 +53,7 @@ struct Level2MessageFactory::Context
size_t bufferedSize_; size_t bufferedSize_;
util::vectorbuf messageBuffer_; util::vectorbuf messageBuffer_;
std::istream messageBufferStream_; std::istream messageBufferStream_;
bool bufferingData_ {false};
}; };
std::shared_ptr<Level2MessageFactory::Context> std::shared_ptr<Level2MessageFactory::Context>
@ -59,14 +62,38 @@ Level2MessageFactory::CreateContext()
return std::make_shared<Context>(); return std::make_shared<Context>();
} }
Level2MessageInfo Level2MessageFactory::Create(std::istream& is, Level2MessageInfo Level2MessageFactory::Create(std::istream& is,
std::shared_ptr<Context> ctx) std::shared_ptr<Context>& ctx)
{ {
Level2MessageInfo info; Level2MessageInfo info;
Level2MessageHeader header; Level2MessageHeader header;
info.headerValid = header.Parse(is); info.headerValid = header.Parse(is);
info.messageValid = info.headerValid; 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<std::size_t>(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<std::size_t>(header.message_size()) * 2 -
Level2MessageHeader::SIZE;
}
}
if (info.headerValid && create_.find(header.message_type()) == create_.end()) if (info.headerValid && create_.find(header.message_type()) == create_.end())
{ {
logger_->warn("Unknown message type: {}", logger_->warn("Unknown message type: {}",
@ -76,10 +103,7 @@ Level2MessageInfo Level2MessageFactory::Create(std::istream& is,
if (info.messageValid) if (info.messageValid)
{ {
uint16_t segment = header.message_segment_number(); std::uint8_t messageType = header.message_type();
uint16_t totalSegments = header.number_of_message_segments();
uint8_t messageType = header.message_type();
size_t dataSize = header.message_size() * 2 - Level2MessageHeader::SIZE;
std::istream* messageStream = nullptr; std::istream* messageStream = nullptr;
@ -100,38 +124,51 @@ Level2MessageInfo Level2MessageFactory::Create(std::istream& is,
// Estimate total message size // Estimate total message size
ctx->messageData_.resize(dataSize * totalSegments); ctx->messageData_.resize(dataSize * totalSegments);
ctx->messageBufferStream_.clear(); ctx->messageBufferStream_.clear();
ctx->bufferedSize_ = 0; ctx->bufferedSize_ = 0;
ctx->bufferingData_ = true;
} }
else if (!ctx->bufferingData_)
if (ctx->messageData_.capacity() < ctx->bufferedSize_ + dataSize)
{ {
logger_->debug("Bad size estimate, increasing size"); // Segment number did not start at 1
logger_->trace("Ignoring Segment {}/{}, did not start at 1",
// Estimate remaining size segment,
uint16_t remainingSegments = totalSegments);
std::max<uint16_t>(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; 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<uint16_t>(
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<uint16_t>(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<uint16_t>(
ctx->bufferedSize_ / 2 + Level2MessageHeader::SIZE));
messageStream = &ctx->messageBufferStream_;
}
} }
} }
@ -142,14 +179,14 @@ Level2MessageInfo Level2MessageFactory::Create(std::istream& is,
ctx->messageData_.resize(0); ctx->messageData_.resize(0);
ctx->messageData_.shrink_to_fit(); ctx->messageData_.shrink_to_fit();
ctx->messageBufferStream_.clear(); ctx->messageBufferStream_.clear();
ctx->bufferedSize_ = 0; ctx->bufferedSize_ = 0;
ctx->bufferingData_ = false;
} }
} }
else if (info.headerValid) else if (info.headerValid)
{ {
// Seek to the end of the current message // Seek to the end of the current message
is.seekg(header.message_size() * 2 - rda::Level2MessageHeader::SIZE, is.seekg(dataSize, std::ios_base::cur);
std::ios_base::cur);
} }
if (info.message == nullptr) if (info.message == nullptr)

View file

@ -130,12 +130,10 @@ bool Level2MessageHeader::Parse(std::istream& is)
{ {
if (p->messageSize_ < 9) if (p->messageSize_ < 9)
{ {
logger_->warn("Invalid message size: {}", p->messageSize_); if (p->messageSize_ != 0)
headerValid = false; {
} logger_->warn("Invalid message size: {}", p->messageSize_);
if (p->julianDate_ < 1) }
{
logger_->warn("Invalid date: {}", p->julianDate_);
headerValid = false; headerValid = false;
} }
if (p->millisecondsOfDay_ > 86'399'999u) if (p->millisecondsOfDay_ > 86'399'999u)

View file

@ -102,7 +102,7 @@ VolumeCoveragePatternData::VolumeCoveragePatternData() :
VolumeCoveragePatternData::~VolumeCoveragePatternData() = default; VolumeCoveragePatternData::~VolumeCoveragePatternData() = default;
VolumeCoveragePatternData::VolumeCoveragePatternData( VolumeCoveragePatternData::VolumeCoveragePatternData(
VolumeCoveragePatternData&&) noexcept = default; VolumeCoveragePatternData&&) noexcept = default;
VolumeCoveragePatternData& VolumeCoveragePatternData::operator=( VolumeCoveragePatternData& VolumeCoveragePatternData::operator=(
VolumeCoveragePatternData&&) noexcept = default; VolumeCoveragePatternData&&) noexcept = default;
@ -419,16 +419,24 @@ bool VolumeCoveragePatternData::Parse(std::istream& is)
p->vcpSequencing_ = ntohs(p->vcpSequencing_); p->vcpSequencing_ = ntohs(p->vcpSequencing_);
p->vcpSupplementalData_ = ntohs(p->vcpSupplementalData_); 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; messageValid = false;
} }
if (numberOfElevationCuts < 1 || numberOfElevationCuts > 32) else
{ {
logger_->warn("Invalid number of elevation cuts: {}", if (messageSize < 34 || messageSize > 747)
numberOfElevationCuts); {
messageValid = false; 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) if (!messageValid)

View file

@ -103,17 +103,21 @@ set(SRC_WSR88D source/scwx/wsr88d/ar2v_file.cpp
set(HDR_WSR88D_RDA include/scwx/wsr88d/rda/clutter_filter_bypass_map.hpp 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/clutter_filter_map.hpp
include/scwx/wsr88d/rda/digital_radar_data.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.hpp
include/scwx/wsr88d/rda/level2_message_factory.hpp include/scwx/wsr88d/rda/level2_message_factory.hpp
include/scwx/wsr88d/rda/level2_message_header.hpp include/scwx/wsr88d/rda/level2_message_header.hpp
include/scwx/wsr88d/rda/performance_maintenance_data.hpp include/scwx/wsr88d/rda/performance_maintenance_data.hpp
include/scwx/wsr88d/rda/rda_adaptation_data.hpp include/scwx/wsr88d/rda/rda_adaptation_data.hpp
include/scwx/wsr88d/rda/rda_status_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) include/scwx/wsr88d/rda/volume_coverage_pattern_data.hpp)
set(SRC_WSR88D_RDA source/scwx/wsr88d/rda/clutter_filter_bypass_map.cpp 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/clutter_filter_map.cpp
source/scwx/wsr88d/rda/digital_radar_data.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.cpp
source/scwx/wsr88d/rda/level2_message_factory.cpp source/scwx/wsr88d/rda/level2_message_factory.cpp
source/scwx/wsr88d/rda/level2_message_header.cpp source/scwx/wsr88d/rda/level2_message_header.cpp