mirror of
https://github.com/ciphervance/supercell-wx.git
synced 2025-10-30 16:20:06 +00:00
Merge pull request #138 from dpaulat/feature/legacy-level2-data
Support older Level 2 data formats
This commit is contained in:
commit
74e4564882
28 changed files with 2636 additions and 1184 deletions
|
|
@ -430,8 +430,12 @@ void MainWindow::on_actionOpenNexrad_triggered()
|
|||
{
|
||||
logger_->info("Selected: {}", file.toStdString());
|
||||
|
||||
auto radarSite = p->activeMap_->GetRadarSite();
|
||||
std::string currentRadarSite =
|
||||
(radarSite != nullptr) ? radarSite->id() : std::string {};
|
||||
|
||||
std::shared_ptr<request::NexradFileRequest> request =
|
||||
std::make_shared<request::NexradFileRequest>();
|
||||
std::make_shared<request::NexradFileRequest>(currentRadarSite);
|
||||
|
||||
connect( //
|
||||
request.get(),
|
||||
|
|
@ -882,9 +886,9 @@ void MainWindowImpl::ConnectAnimationSignals()
|
|||
&manager::TimelineManager::VolumeTimeUpdated,
|
||||
[this](std::chrono::system_clock::time_point dateTime)
|
||||
{
|
||||
volumeTime_ = dateTime;
|
||||
for (auto map : maps_)
|
||||
{
|
||||
volumeTime_ = dateTime;
|
||||
map->SelectTime(dateTime);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -207,16 +207,18 @@ public:
|
|||
void UpdateRecentRecords(RadarProductRecordList& recentList,
|
||||
std::shared_ptr<types::RadarProductRecord> record);
|
||||
|
||||
void LoadNexradFileAsync(CreateNexradFileFunction load,
|
||||
std::shared_ptr<request::NexradFileRequest> request,
|
||||
std::mutex& mutex,
|
||||
std::chrono::system_clock::time_point time);
|
||||
void LoadProviderData(std::chrono::system_clock::time_point time,
|
||||
void LoadNexradFileAsync(
|
||||
CreateNexradFileFunction load,
|
||||
const std::shared_ptr<request::NexradFileRequest>& request,
|
||||
std::mutex& mutex,
|
||||
std::chrono::system_clock::time_point time);
|
||||
void
|
||||
LoadProviderData(std::chrono::system_clock::time_point time,
|
||||
std::shared_ptr<ProviderManager> providerManager,
|
||||
RadarProductRecordMap& recordMap,
|
||||
std::shared_mutex& recordMutex,
|
||||
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 PopulateLevel3ProductTimes(const std::string& product,
|
||||
std::chrono::system_clock::time_point time);
|
||||
|
|
@ -228,10 +230,10 @@ public:
|
|||
std::chrono::system_clock::time_point time);
|
||||
|
||||
static void
|
||||
LoadNexradFile(CreateNexradFileFunction load,
|
||||
std::shared_ptr<request::NexradFileRequest> request,
|
||||
std::mutex& mutex,
|
||||
std::chrono::system_clock::time_point time = {});
|
||||
LoadNexradFile(CreateNexradFileFunction load,
|
||||
const std::shared_ptr<request::NexradFileRequest>& request,
|
||||
std::mutex& mutex,
|
||||
std::chrono::system_clock::time_point time = {});
|
||||
|
||||
const std::string radarId_;
|
||||
bool initialized_;
|
||||
|
|
@ -394,6 +396,11 @@ float RadarProductManager::gate_size() const
|
|||
return (p->radarSite_->type() == "tdwr") ? 150.0f : 250.0f;
|
||||
}
|
||||
|
||||
std::string RadarProductManager::radar_id() const
|
||||
{
|
||||
return p->radarId_;
|
||||
}
|
||||
|
||||
std::shared_ptr<config::RadarSite> RadarProductManager::radar_site() const
|
||||
{
|
||||
return p->radarSite_;
|
||||
|
|
@ -777,12 +784,12 @@ RadarProductManager::GetActiveVolumeTimes(
|
|||
}
|
||||
|
||||
void RadarProductManagerImpl::LoadProviderData(
|
||||
std::chrono::system_clock::time_point time,
|
||||
std::shared_ptr<ProviderManager> providerManager,
|
||||
RadarProductRecordMap& recordMap,
|
||||
std::shared_mutex& recordMutex,
|
||||
std::mutex& loadDataMutex,
|
||||
std::shared_ptr<request::NexradFileRequest> request)
|
||||
std::chrono::system_clock::time_point time,
|
||||
std::shared_ptr<ProviderManager> providerManager,
|
||||
RadarProductRecordMap& recordMap,
|
||||
std::shared_mutex& recordMutex,
|
||||
std::mutex& loadDataMutex,
|
||||
const std::shared_ptr<request::NexradFileRequest>& request)
|
||||
{
|
||||
logger_->debug("LoadProviderData: {}, {}",
|
||||
providerManager->name(),
|
||||
|
|
@ -837,8 +844,8 @@ void RadarProductManagerImpl::LoadProviderData(
|
|||
}
|
||||
|
||||
void RadarProductManager::LoadLevel2Data(
|
||||
std::chrono::system_clock::time_point time,
|
||||
std::shared_ptr<request::NexradFileRequest> request)
|
||||
std::chrono::system_clock::time_point time,
|
||||
const std::shared_ptr<request::NexradFileRequest>& request)
|
||||
{
|
||||
logger_->debug("LoadLevel2Data: {}", scwx::util::TimeString(time));
|
||||
|
||||
|
|
@ -851,9 +858,9 @@ void RadarProductManager::LoadLevel2Data(
|
|||
}
|
||||
|
||||
void RadarProductManager::LoadLevel3Data(
|
||||
const std::string& product,
|
||||
std::chrono::system_clock::time_point time,
|
||||
std::shared_ptr<request::NexradFileRequest> request)
|
||||
const std::string& product,
|
||||
std::chrono::system_clock::time_point time,
|
||||
const std::shared_ptr<request::NexradFileRequest>& request)
|
||||
{
|
||||
logger_->debug("LoadLevel3Data: {}", scwx::util::TimeString(time));
|
||||
|
||||
|
|
@ -883,7 +890,7 @@ void RadarProductManager::LoadLevel3Data(
|
|||
}
|
||||
|
||||
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()");
|
||||
|
||||
|
|
@ -899,8 +906,8 @@ void RadarProductManager::LoadData(
|
|||
}
|
||||
|
||||
void RadarProductManager::LoadFile(
|
||||
const std::string& filename,
|
||||
std::shared_ptr<request::NexradFileRequest> request)
|
||||
const std::string& filename,
|
||||
const std::shared_ptr<request::NexradFileRequest>& request)
|
||||
{
|
||||
logger_->debug("LoadFile: {}", filename);
|
||||
|
||||
|
|
@ -950,10 +957,10 @@ void RadarProductManager::LoadFile(
|
|||
}
|
||||
|
||||
void RadarProductManagerImpl::LoadNexradFileAsync(
|
||||
CreateNexradFileFunction load,
|
||||
std::shared_ptr<request::NexradFileRequest> request,
|
||||
std::mutex& mutex,
|
||||
std::chrono::system_clock::time_point time)
|
||||
CreateNexradFileFunction load,
|
||||
const std::shared_ptr<request::NexradFileRequest>& request,
|
||||
std::mutex& mutex,
|
||||
std::chrono::system_clock::time_point time)
|
||||
{
|
||||
boost::asio::post(threadPool_,
|
||||
[=, &mutex]()
|
||||
|
|
@ -961,10 +968,10 @@ void RadarProductManagerImpl::LoadNexradFileAsync(
|
|||
}
|
||||
|
||||
void RadarProductManagerImpl::LoadNexradFile(
|
||||
CreateNexradFileFunction load,
|
||||
std::shared_ptr<request::NexradFileRequest> request,
|
||||
std::mutex& mutex,
|
||||
std::chrono::system_clock::time_point time)
|
||||
CreateNexradFileFunction load,
|
||||
const std::shared_ptr<request::NexradFileRequest>& request,
|
||||
std::mutex& mutex,
|
||||
std::chrono::system_clock::time_point time)
|
||||
{
|
||||
std::unique_lock lock {mutex};
|
||||
|
||||
|
|
@ -987,8 +994,14 @@ void RadarProductManagerImpl::LoadNexradFile(
|
|||
record->set_time(time);
|
||||
}
|
||||
|
||||
std::string recordRadarId = (record->radar_id());
|
||||
if (recordRadarId.empty())
|
||||
{
|
||||
recordRadarId = request->current_radar_site();
|
||||
}
|
||||
|
||||
std::shared_ptr<RadarProductManager> manager =
|
||||
RadarProductManager::Instance(record->radar_id());
|
||||
RadarProductManager::Instance(recordRadarId);
|
||||
|
||||
manager->Initialize();
|
||||
record = manager->p->StoreRadarProductRecord(record);
|
||||
|
|
@ -1035,7 +1048,14 @@ void RadarProductManagerImpl::PopulateProductTimes(
|
|||
std::shared_mutex& productRecordMutex,
|
||||
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 tomorrow = today + std::chrono::days {1};
|
||||
const auto dates = {yesterday, today, tomorrow};
|
||||
|
|
@ -1119,7 +1139,7 @@ RadarProductManagerImpl::GetLevel2ProductRecord(
|
|||
{
|
||||
// Product is expired, reload it
|
||||
std::shared_ptr<request::NexradFileRequest> request =
|
||||
std::make_shared<request::NexradFileRequest>();
|
||||
std::make_shared<request::NexradFileRequest>(radarId_);
|
||||
|
||||
QObject::connect(
|
||||
request.get(),
|
||||
|
|
@ -1184,7 +1204,7 @@ RadarProductManagerImpl::GetLevel3ProductRecord(
|
|||
{
|
||||
// Product is expired, reload it
|
||||
std::shared_ptr<request::NexradFileRequest> request =
|
||||
std::make_shared<request::NexradFileRequest>();
|
||||
std::make_shared<request::NexradFileRequest>(radarId_);
|
||||
|
||||
QObject::connect(
|
||||
request.get(),
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ public:
|
|||
|
||||
const std::vector<float>& coordinates(common::RadialSize radialSize) const;
|
||||
float gate_size() const;
|
||||
std::string radar_id() const;
|
||||
std::shared_ptr<config::RadarSite> radar_site() const;
|
||||
|
||||
void Initialize();
|
||||
|
|
@ -110,19 +111,19 @@ public:
|
|||
Instance(const std::string& radarSite);
|
||||
|
||||
void LoadLevel2Data(
|
||||
std::chrono::system_clock::time_point time,
|
||||
std::shared_ptr<request::NexradFileRequest> request = nullptr);
|
||||
std::chrono::system_clock::time_point time,
|
||||
const std::shared_ptr<request::NexradFileRequest>& request = nullptr);
|
||||
void LoadLevel3Data(
|
||||
const std::string& product,
|
||||
std::chrono::system_clock::time_point time,
|
||||
std::shared_ptr<request::NexradFileRequest> request = nullptr);
|
||||
const std::string& product,
|
||||
std::chrono::system_clock::time_point time,
|
||||
const std::shared_ptr<request::NexradFileRequest>& request = nullptr);
|
||||
|
||||
static void
|
||||
LoadData(std::istream& is,
|
||||
std::shared_ptr<request::NexradFileRequest> request = nullptr);
|
||||
static void
|
||||
LoadFile(const std::string& filename,
|
||||
std::shared_ptr<request::NexradFileRequest> request = nullptr);
|
||||
static void LoadData(
|
||||
std::istream& is,
|
||||
const std::shared_ptr<request::NexradFileRequest>& request = nullptr);
|
||||
static void LoadFile(
|
||||
const std::string& filename,
|
||||
const std::shared_ptr<request::NexradFileRequest>& request = nullptr);
|
||||
|
||||
common::Level3ProductCategoryMap GetAvailableLevel3Categories();
|
||||
std::vector<std::string> GetLevel3Products();
|
||||
|
|
|
|||
|
|
@ -1332,7 +1332,8 @@ void MapWidgetImpl::RadarProductManagerConnect()
|
|||
{
|
||||
// Create file request
|
||||
std::shared_ptr<request::NexradFileRequest> request =
|
||||
std::make_shared<request::NexradFileRequest>();
|
||||
std::make_shared<request::NexradFileRequest>(
|
||||
radarProductManager_->radar_id());
|
||||
|
||||
// File request callback
|
||||
if (autoUpdateEnabled_)
|
||||
|
|
|
|||
|
|
@ -9,22 +9,31 @@ namespace request
|
|||
|
||||
static const std::string logPrefix_ = "scwx::qt::request::nexrad_file_request";
|
||||
|
||||
class NexradFileRequestImpl
|
||||
class NexradFileRequest::Impl
|
||||
{
|
||||
public:
|
||||
explicit NexradFileRequestImpl() : radarProductRecord_ {nullptr} {}
|
||||
explicit Impl(const std::string& currentRadarSite) :
|
||||
currentRadarSite_ {currentRadarSite}
|
||||
{
|
||||
}
|
||||
~Impl() = default;
|
||||
|
||||
~NexradFileRequestImpl() {}
|
||||
const std::string currentRadarSite_;
|
||||
|
||||
std::shared_ptr<types::RadarProductRecord> radarProductRecord_;
|
||||
std::shared_ptr<types::RadarProductRecord> radarProductRecord_ {nullptr};
|
||||
};
|
||||
|
||||
NexradFileRequest::NexradFileRequest() :
|
||||
p(std::make_unique<NexradFileRequestImpl>())
|
||||
NexradFileRequest::NexradFileRequest(const std::string& currentRadarSite) :
|
||||
p(std::make_unique<Impl>(currentRadarSite))
|
||||
{
|
||||
}
|
||||
NexradFileRequest::~NexradFileRequest() = default;
|
||||
|
||||
std::string NexradFileRequest::current_radar_site() const
|
||||
{
|
||||
return p->currentRadarSite_;
|
||||
}
|
||||
|
||||
std::shared_ptr<types::RadarProductRecord>
|
||||
NexradFileRequest::radar_product_record() const
|
||||
{
|
||||
|
|
@ -32,7 +41,7 @@ NexradFileRequest::radar_product_record() const
|
|||
}
|
||||
|
||||
void NexradFileRequest::set_radar_product_record(
|
||||
std::shared_ptr<types::RadarProductRecord> record)
|
||||
const std::shared_ptr<types::RadarProductRecord>& record)
|
||||
{
|
||||
p->radarProductRecord_ = record;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,23 +13,23 @@ namespace qt
|
|||
namespace request
|
||||
{
|
||||
|
||||
class NexradFileRequestImpl;
|
||||
|
||||
class NexradFileRequest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit NexradFileRequest();
|
||||
explicit NexradFileRequest(const std::string& currentRadarSite = {});
|
||||
~NexradFileRequest();
|
||||
|
||||
std::string current_radar_site() const;
|
||||
std::shared_ptr<types::RadarProductRecord> radar_product_record() const;
|
||||
|
||||
void
|
||||
set_radar_product_record(std::shared_ptr<types::RadarProductRecord> record);
|
||||
void set_radar_product_record(
|
||||
const std::shared_ptr<types::RadarProductRecord>& record);
|
||||
|
||||
private:
|
||||
std::unique_ptr<NexradFileRequestImpl> p;
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> p;
|
||||
|
||||
signals:
|
||||
void RequestComplete(std::shared_ptr<NexradFileRequest> request);
|
||||
|
|
|
|||
|
|
@ -89,8 +89,9 @@ public:
|
|||
|
||||
float selectedElevation_;
|
||||
|
||||
std::shared_ptr<wsr88d::rda::ElevationScan> elevationScan_;
|
||||
std::shared_ptr<wsr88d::rda::MomentDataBlock> momentDataBlock0_;
|
||||
std::shared_ptr<wsr88d::rda::ElevationScan> elevationScan_;
|
||||
std::shared_ptr<wsr88d::rda::GenericRadarData::MomentDataBlock>
|
||||
momentDataBlock0_;
|
||||
|
||||
std::vector<float> coordinates_ {};
|
||||
std::vector<float> vertices_ {};
|
||||
|
|
@ -98,12 +99,12 @@ public:
|
|||
std::vector<uint16_t> dataMoments16_ {};
|
||||
std::vector<uint8_t> cfpMoments_ {};
|
||||
|
||||
float latitude_;
|
||||
float longitude_;
|
||||
float elevationCut_;
|
||||
std::vector<float> elevationCuts_;
|
||||
float range_;
|
||||
uint16_t vcp_;
|
||||
float latitude_;
|
||||
float longitude_;
|
||||
float elevationCut_;
|
||||
std::vector<float> elevationCuts_;
|
||||
units::kilometers<float> range_;
|
||||
uint16_t vcp_;
|
||||
|
||||
std::chrono::system_clock::time_point sweepTime_;
|
||||
|
||||
|
|
@ -212,7 +213,7 @@ float Level2ProductView::elevation() const
|
|||
|
||||
float Level2ProductView::range() const
|
||||
{
|
||||
return p->range_;
|
||||
return p->range_.value();
|
||||
}
|
||||
|
||||
std::chrono::system_clock::time_point Level2ProductView::sweep_time() const
|
||||
|
|
@ -468,15 +469,15 @@ void Level2ProductView::ComputeSweep()
|
|||
|
||||
const uint32_t gates = momentData0->number_of_data_moment_gates();
|
||||
|
||||
auto volumeData0 = radarData0->volume_data_block();
|
||||
p->latitude_ = volumeData0->latitude();
|
||||
p->longitude_ = volumeData0->longitude();
|
||||
auto radarSite = radarProductManager->radar_site();
|
||||
p->latitude_ = radarSite->latitude();
|
||||
p->longitude_ = radarSite->longitude();
|
||||
p->range_ =
|
||||
momentData0->data_moment_range() +
|
||||
momentData0->data_moment_range_sample_interval() * (gates - 0.5f);
|
||||
p->sweepTime_ = scwx::util::TimePoint(radarData0->modified_julian_date(),
|
||||
radarData0->collection_time());
|
||||
p->vcp_ = volumeData0->volume_coverage_pattern_number();
|
||||
p->vcp_ = radarData0->volume_coverage_pattern_number();
|
||||
|
||||
// Calculate vertices
|
||||
timer.start();
|
||||
|
|
@ -521,17 +522,17 @@ void Level2ProductView::ComputeSweep()
|
|||
}
|
||||
|
||||
// Compute threshold at which to display an individual bin (minimum of 2)
|
||||
const uint16_t snrThreshold =
|
||||
std::max<int16_t>(2, momentData0->snr_threshold_raw());
|
||||
const std::uint16_t snrThreshold =
|
||||
std::max<std::int16_t>(2, momentData0->snr_threshold_raw());
|
||||
|
||||
// Start radial is always 0, as coordinates are calculated for each sweep
|
||||
constexpr std::uint16_t startRadial = 0u;
|
||||
|
||||
for (auto& radialPair : *radarData)
|
||||
{
|
||||
uint16_t radial = radialPair.first;
|
||||
auto radialData = radialPair.second;
|
||||
auto momentData = radialData->moment_data_block(p->dataBlockType_);
|
||||
std::uint16_t radial = radialPair.first;
|
||||
auto& radialData = radialPair.second;
|
||||
auto momentData = radialData->moment_data_block(p->dataBlockType_);
|
||||
|
||||
if (momentData0->data_word_size() != momentData->data_word_size())
|
||||
{
|
||||
|
|
@ -540,64 +541,70 @@ void Level2ProductView::ComputeSweep()
|
|||
}
|
||||
|
||||
// Compute gate interval
|
||||
const uint16_t dataMomentRange = momentData->data_moment_range_raw();
|
||||
const uint16_t dataMomentInterval =
|
||||
const std::int32_t dataMomentInterval =
|
||||
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)
|
||||
const uint16_t gateSizeMeters =
|
||||
static_cast<uint16_t>(radarProductManager->gate_size());
|
||||
const uint16_t gateSize =
|
||||
std::max<uint16_t>(1, dataMomentInterval / gateSizeMeters);
|
||||
const std::int32_t gateSizeMeters =
|
||||
static_cast<std::int32_t>(radarProductManager->gate_size());
|
||||
const std::int32_t gateSize =
|
||||
std::max<std::int32_t>(1, dataMomentInterval / gateSizeMeters);
|
||||
|
||||
// Compute gate range [startGate, endGate)
|
||||
const uint16_t startGate =
|
||||
const std::int32_t startGate =
|
||||
(dataMomentRange - dataMomentIntervalH) / gateSizeMeters;
|
||||
const uint16_t numberOfDataMomentGates =
|
||||
std::min<uint16_t>(momentData->number_of_data_moment_gates(),
|
||||
static_cast<uint16_t>(gates));
|
||||
const uint16_t endGate =
|
||||
std::min<uint16_t>(startGate + numberOfDataMomentGates * gateSize,
|
||||
common::MAX_DATA_MOMENT_GATES);
|
||||
const std::int32_t numberOfDataMomentGates =
|
||||
std::min<std::int32_t>(momentData->number_of_data_moment_gates(),
|
||||
static_cast<std::int32_t>(gates));
|
||||
const std::int32_t endGate = std::min<std::int32_t>(
|
||||
startGate + numberOfDataMomentGates * gateSize,
|
||||
static_cast<std::int32_t>(common::MAX_DATA_MOMENT_GATES));
|
||||
|
||||
const uint8_t* dataMomentsArray8 = nullptr;
|
||||
const uint16_t* dataMomentsArray16 = nullptr;
|
||||
const uint8_t* cfpMomentsArray = nullptr;
|
||||
const std::uint8_t* dataMomentsArray8 = nullptr;
|
||||
const std::uint16_t* dataMomentsArray16 = nullptr;
|
||||
const std::uint8_t* cfpMomentsArray = nullptr;
|
||||
|
||||
if (momentData->data_word_size() == 8)
|
||||
{
|
||||
dataMomentsArray8 =
|
||||
reinterpret_cast<const uint8_t*>(momentData->data_moments());
|
||||
reinterpret_cast<const std::uint8_t*>(momentData->data_moments());
|
||||
}
|
||||
else
|
||||
{
|
||||
dataMomentsArray16 =
|
||||
reinterpret_cast<const uint16_t*>(momentData->data_moments());
|
||||
reinterpret_cast<const std::uint16_t*>(momentData->data_moments());
|
||||
}
|
||||
|
||||
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)
|
||||
->data_moments());
|
||||
}
|
||||
|
||||
for (uint16_t gate = startGate, i = 0; gate + gateSize <= endGate;
|
||||
for (std::int32_t gate = startGate, i = 0; gate + gateSize <= endGate;
|
||||
gate += gateSize, ++i)
|
||||
{
|
||||
size_t vertexCount = (gate > 0) ? 6 : 3;
|
||||
if (gate < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
std::size_t vertexCount = (gate > 0) ? 6 : 3;
|
||||
|
||||
// Store data moment value
|
||||
if (dataMomentsArray8 != nullptr)
|
||||
{
|
||||
uint8_t dataValue = dataMomentsArray8[i];
|
||||
std::uint8_t dataValue = dataMomentsArray8[i];
|
||||
if (dataValue < snrThreshold && dataValue != RANGE_FOLDED)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (size_t m = 0; m < vertexCount; m++)
|
||||
for (std::size_t m = 0; m < vertexCount; m++)
|
||||
{
|
||||
dataMoments8[mIndex++] = dataMomentsArray8[i];
|
||||
|
||||
|
|
@ -609,13 +616,13 @@ void Level2ProductView::ComputeSweep()
|
|||
}
|
||||
else
|
||||
{
|
||||
uint16_t dataValue = dataMomentsArray16[i];
|
||||
std::uint16_t dataValue = dataMomentsArray16[i];
|
||||
if (dataValue < snrThreshold && dataValue != RANGE_FOLDED)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (size_t m = 0; m < vertexCount; m++)
|
||||
for (std::size_t m = 0; m < vertexCount; m++)
|
||||
{
|
||||
dataMoments16[mIndex++] = dataMomentsArray16[i];
|
||||
}
|
||||
|
|
@ -624,18 +631,18 @@ void Level2ProductView::ComputeSweep()
|
|||
// Store vertices
|
||||
if (gate > 0)
|
||||
{
|
||||
const uint16_t baseCoord = gate - 1;
|
||||
const std::uint16_t baseCoord = gate - 1;
|
||||
|
||||
size_t offset1 = ((startRadial + radial) % radials *
|
||||
common::MAX_DATA_MOMENT_GATES +
|
||||
baseCoord) *
|
||||
2;
|
||||
size_t offset2 = offset1 + gateSize * 2;
|
||||
size_t offset3 = (((startRadial + radial + 1) % radials) *
|
||||
common::MAX_DATA_MOMENT_GATES +
|
||||
baseCoord) *
|
||||
2;
|
||||
size_t offset4 = offset3 + gateSize * 2;
|
||||
std::size_t offset1 = ((startRadial + radial) % radials *
|
||||
common::MAX_DATA_MOMENT_GATES +
|
||||
baseCoord) *
|
||||
2;
|
||||
std::size_t offset2 = offset1 + gateSize * 2;
|
||||
std::size_t offset3 = (((startRadial + radial + 1) % radials) *
|
||||
common::MAX_DATA_MOMENT_GATES +
|
||||
baseCoord) *
|
||||
2;
|
||||
std::size_t offset4 = offset3 + gateSize * 2;
|
||||
|
||||
vertices[vIndex++] = coordinates[offset1];
|
||||
vertices[vIndex++] = coordinates[offset1 + 1];
|
||||
|
|
@ -659,16 +666,16 @@ void Level2ProductView::ComputeSweep()
|
|||
}
|
||||
else
|
||||
{
|
||||
const uint16_t baseCoord = gate;
|
||||
const std::uint16_t baseCoord = gate;
|
||||
|
||||
size_t offset1 = ((startRadial + radial) % radials *
|
||||
common::MAX_DATA_MOMENT_GATES +
|
||||
baseCoord) *
|
||||
2;
|
||||
size_t offset2 = (((startRadial + radial + 1) % radials) *
|
||||
common::MAX_DATA_MOMENT_GATES +
|
||||
baseCoord) *
|
||||
2;
|
||||
std::size_t offset1 = ((startRadial + radial) % radials *
|
||||
common::MAX_DATA_MOMENT_GATES +
|
||||
baseCoord) *
|
||||
2;
|
||||
std::size_t offset2 = (((startRadial + radial + 1) % radials) *
|
||||
common::MAX_DATA_MOMENT_GATES +
|
||||
baseCoord) *
|
||||
2;
|
||||
|
||||
vertices[vIndex++] = p->latitude_;
|
||||
vertices[vIndex++] = p->longitude_;
|
||||
|
|
@ -747,7 +754,8 @@ void Level2ProductViewImpl::ComputeCoordinates(
|
|||
radials.end(),
|
||||
[&](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,
|
||||
gates.begin(),
|
||||
|
|
@ -765,7 +773,7 @@ void Level2ProductViewImpl::ComputeCoordinates(
|
|||
|
||||
geodesic.Direct(radarLatitude,
|
||||
radarLongitude,
|
||||
angle,
|
||||
angle.value(),
|
||||
range,
|
||||
latitude,
|
||||
longitude);
|
||||
|
|
@ -830,14 +838,15 @@ Level2ProductView::GetBinLevel(const common::Coordinate& coordinate) const
|
|||
radials.end(),
|
||||
[&](std::uint32_t i)
|
||||
{
|
||||
bool found = false;
|
||||
const float startAngle = (*radarData)[i]->azimuth_angle();
|
||||
const float nextAngle =
|
||||
bool found = false;
|
||||
const units::degrees<float> startAngle =
|
||||
(*radarData)[i]->azimuth_angle();
|
||||
const units::degrees<float> nextAngle =
|
||||
(*radarData)[(i + 1) % numRadials]->azimuth_angle();
|
||||
|
||||
if (startAngle < nextAngle)
|
||||
{
|
||||
if (startAngle <= azi1 && azi1 < nextAngle)
|
||||
if (startAngle.value() <= azi1 && azi1 < nextAngle.value())
|
||||
{
|
||||
found = true;
|
||||
}
|
||||
|
|
@ -845,7 +854,7 @@ Level2ProductView::GetBinLevel(const common::Coordinate& coordinate) const
|
|||
else
|
||||
{
|
||||
// If the bin crosses 0/360 degrees, special handling is needed
|
||||
if (startAngle <= azi1 || azi1 < nextAngle)
|
||||
if (startAngle.value() <= azi1 || azi1 < nextAngle.value())
|
||||
{
|
||||
found = true;
|
||||
}
|
||||
|
|
@ -862,24 +871,26 @@ Level2ProductView::GetBinLevel(const common::Coordinate& coordinate) const
|
|||
|
||||
// Compute gate interval
|
||||
auto momentData = (*radarData)[*radial]->moment_data_block(dataBlockType);
|
||||
const std::uint16_t dataMomentRange = momentData->data_moment_range_raw();
|
||||
const std::uint16_t dataMomentInterval =
|
||||
const std::int32_t dataMomentInterval =
|
||||
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)
|
||||
const std::uint16_t gateSizeMeters =
|
||||
static_cast<std::uint16_t>(radarProductManager->gate_size());
|
||||
const std::int32_t gateSizeMeters =
|
||||
static_cast<std::int32_t>(radarProductManager->gate_size());
|
||||
|
||||
// Compute gate range [startGate, endGate)
|
||||
const std::uint16_t startGate =
|
||||
const std::int32_t startGate =
|
||||
(dataMomentRange - dataMomentIntervalH) / gateSizeMeters;
|
||||
const std::uint16_t numberOfDataMomentGates =
|
||||
const std::int32_t numberOfDataMomentGates =
|
||||
momentData->number_of_data_moment_gates();
|
||||
|
||||
const std::uint16_t gate = s12 / dataMomentInterval - startGate;
|
||||
const std::int32_t gate = s12 / dataMomentInterval - startGate;
|
||||
|
||||
if (gate > numberOfDataMomentGates || gate > common::MAX_DATA_MOMENT_GATES)
|
||||
if (gate < 0 || gate > numberOfDataMomentGates ||
|
||||
gate > static_cast<std::int32_t>(common::MAX_DATA_MOMENT_GATES))
|
||||
{
|
||||
// Coordinate is beyond radar range
|
||||
return std::nullopt;
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 6632ffd6ba35b799dd803e9711281d54a3858a29
|
||||
Subproject commit e3e743a5cc9c065d05f00151380fea892fb2156c
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
#include <scwx/wsr88d/ar2v_file.hpp>
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace scwx
|
||||
|
|
@ -9,25 +7,32 @@ namespace scwx
|
|||
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;
|
||||
bool fileValid =
|
||||
file.LoadFile(std::string(SCWX_TEST_DATA_DIR) +
|
||||
"/nexrad/level2/Level2_KLSX_20210527_1757.ar2v");
|
||||
file.LoadFile(std::string(SCWX_TEST_DATA_DIR) + param.first);
|
||||
|
||||
EXPECT_EQ(fileValid, true);
|
||||
EXPECT_EQ(file.message_count(), param.second);
|
||||
}
|
||||
|
||||
TEST(ar2v_file, tstl)
|
||||
{
|
||||
Ar2vFile file;
|
||||
bool fileValid =
|
||||
file.LoadFile(std::string(SCWX_TEST_DATA_DIR) +
|
||||
"/nexrad/level2/Level2_TSTL_20220213_2357.ar2v");
|
||||
|
||||
EXPECT_EQ(fileValid, true);
|
||||
}
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
Ar2vFile,
|
||||
Ar2vValidFileTest,
|
||||
testing::Values(std::pair<std::string, std::size_t> //
|
||||
{"/nexrad/level2/KCLE20021110_221234", 4031},
|
||||
std::pair<std::string, std::size_t> //
|
||||
{"/nexrad/level2/Level2_KLSX_20210527_1757.ar2v", 11167},
|
||||
std::pair<std::string, std::size_t> //
|
||||
{"/nexrad/level2/Level2_TSTL_20220213_2357.ar2v", 5763}));
|
||||
|
||||
} // namespace wsr88d
|
||||
} // namespace scwx
|
||||
|
|
|
|||
509
wxdata/archive2_format.txt
Normal file
509
wxdata/archive2_format.txt
Normal 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
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#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 <chrono>
|
||||
|
|
@ -26,21 +26,24 @@ public:
|
|||
explicit Ar2vFile();
|
||||
~Ar2vFile();
|
||||
|
||||
Ar2vFile(const Ar2vFile&) = delete;
|
||||
Ar2vFile(const Ar2vFile&) = delete;
|
||||
Ar2vFile& operator=(const Ar2vFile&) = delete;
|
||||
|
||||
Ar2vFile(Ar2vFile&&) noexcept;
|
||||
Ar2vFile& operator=(Ar2vFile&&) noexcept;
|
||||
|
||||
uint32_t julian_date() const;
|
||||
uint32_t milliseconds() const;
|
||||
std::string icao() const;
|
||||
std::uint32_t julian_date() const;
|
||||
std::uint32_t milliseconds() const;
|
||||
std::string icao() const;
|
||||
|
||||
std::size_t message_count() const;
|
||||
|
||||
std::chrono::system_clock::time_point start_time() const;
|
||||
std::chrono::system_clock::time_point end_time() const;
|
||||
|
||||
std::map<uint16_t, std::shared_ptr<rda::ElevationScan>> radar_data() const;
|
||||
std::shared_ptr<const rda::VolumeCoveragePatternData> vcp_data() const;
|
||||
std::map<std::uint16_t, std::shared_ptr<rda::ElevationScan>>
|
||||
radar_data() const;
|
||||
std::shared_ptr<const rda::VolumeCoveragePatternData> vcp_data() const;
|
||||
|
||||
std::tuple<std::shared_ptr<rda::ElevationScan>, float, std::vector<float>>
|
||||
GetElevationScan(rda::DataBlockType dataBlockType,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <scwx/util/iterator.hpp>
|
||||
#include <scwx/wsr88d/rda/level2_message.hpp>
|
||||
#include <scwx/wsr88d/rda/generic_radar_data.hpp>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
|
|
@ -10,198 +9,52 @@ 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 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
|
||||
class DigitalRadarData : public GenericRadarData
|
||||
{
|
||||
public:
|
||||
explicit DigitalRadarData();
|
||||
~DigitalRadarData();
|
||||
|
||||
DigitalRadarData(const DigitalRadarData&) = delete;
|
||||
DigitalRadarData(const DigitalRadarData&) = delete;
|
||||
DigitalRadarData& operator=(const DigitalRadarData&) = delete;
|
||||
|
||||
DigitalRadarData(DigitalRadarData&&) noexcept;
|
||||
DigitalRadarData& operator=(DigitalRadarData&&) noexcept;
|
||||
|
||||
std::string radar_identifier() const;
|
||||
uint32_t collection_time() const;
|
||||
uint16_t modified_julian_date() const;
|
||||
uint16_t azimuth_number() const;
|
||||
float azimuth_angle() const;
|
||||
uint8_t compression_indicator() const;
|
||||
uint16_t radial_length() const;
|
||||
uint8_t azimuth_resolution_spacing() const;
|
||||
uint8_t radial_status() const;
|
||||
uint8_t elevation_number() const;
|
||||
uint8_t cut_sector_number() const;
|
||||
float elevation_angle() const;
|
||||
uint8_t radial_spot_blanking_status() const;
|
||||
uint8_t azimuth_indexing_mode() const;
|
||||
uint16_t data_block_count() const;
|
||||
std::uint32_t collection_time() const;
|
||||
std::uint16_t modified_julian_date() const;
|
||||
std::uint16_t unambiguous_range() const;
|
||||
std::uint16_t azimuth_angle_raw() const;
|
||||
units::degrees<float> azimuth_angle() const;
|
||||
std::uint16_t azimuth_number() const;
|
||||
std::uint16_t radial_status() const;
|
||||
std::uint16_t elevation_angle_raw() const;
|
||||
units::degrees<float> elevation_angle() const;
|
||||
std::uint16_t elevation_number() const;
|
||||
std::int16_t surveillance_range_raw() const;
|
||||
units::kilometers<float> surveillance_range() const;
|
||||
std::int16_t doppler_range_raw() const;
|
||||
units::kilometers<float> doppler_range() 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<RadialDataBlock> radial_data_block() const;
|
||||
std::shared_ptr<VolumeDataBlock> volume_data_block() const;
|
||||
std::shared_ptr<MomentDataBlock> moment_data_block(DataBlockType type) const;
|
||||
std::shared_ptr<GenericRadarData::MomentDataBlock>
|
||||
moment_data_block(DataBlockType type) const;
|
||||
|
||||
bool Parse(std::istream& is);
|
||||
|
||||
|
|
@ -209,7 +62,8 @@ public:
|
|||
std::istream& is);
|
||||
|
||||
private:
|
||||
std::unique_ptr<DigitalRadarDataImpl> p;
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> p;
|
||||
};
|
||||
|
||||
} // namespace rda
|
||||
|
|
|
|||
203
wxdata/include/scwx/wsr88d/rda/digital_radar_data_generic.hpp
Normal file
203
wxdata/include/scwx/wsr88d/rda/digital_radar_data_generic.hpp
Normal 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
|
||||
99
wxdata/include/scwx/wsr88d/rda/generic_radar_data.hpp
Normal file
99
wxdata/include/scwx/wsr88d/rda/generic_radar_data.hpp
Normal 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
|
||||
|
|
@ -27,18 +27,18 @@ private:
|
|||
explicit Level2MessageFactory() = delete;
|
||||
~Level2MessageFactory() = delete;
|
||||
|
||||
Level2MessageFactory(const Level2MessageFactory&) = delete;
|
||||
Level2MessageFactory(const Level2MessageFactory&) = delete;
|
||||
Level2MessageFactory& operator=(const Level2MessageFactory&) = delete;
|
||||
|
||||
Level2MessageFactory(Level2MessageFactory&&) noexcept = delete;
|
||||
Level2MessageFactory(Level2MessageFactory&&) noexcept = delete;
|
||||
Level2MessageFactory& operator=(Level2MessageFactory&&) noexcept = delete;
|
||||
|
||||
public:
|
||||
struct Context;
|
||||
|
||||
static std::shared_ptr<Context> CreateContext();
|
||||
static Level2MessageInfo Create(std::istream& is,
|
||||
std::shared_ptr<Context> ctx);
|
||||
static Level2MessageInfo Create(std::istream& is,
|
||||
std::shared_ptr<Context>& ctx);
|
||||
};
|
||||
|
||||
} // namespace rda
|
||||
|
|
|
|||
|
|
@ -7,14 +7,15 @@ namespace wsr88d
|
|||
namespace rda
|
||||
{
|
||||
|
||||
enum class MessageId : uint8_t
|
||||
enum class MessageId : std::uint8_t
|
||||
{
|
||||
DigitalRadarData = 1,
|
||||
RdaStatusData = 2,
|
||||
PerformanceMaintenanceData = 3,
|
||||
VolumeCoveragePatternData = 5,
|
||||
ClutterFilterMap = 15,
|
||||
RdaAdaptationData = 18,
|
||||
DigitalRadarData = 31
|
||||
DigitalRadarDataGeneric = 31
|
||||
};
|
||||
|
||||
} // namespace rda
|
||||
|
|
@ -17,16 +17,23 @@ std::string GetVcpDescription(uint16_t vcp)
|
|||
case 31:
|
||||
case 32:
|
||||
case 35:
|
||||
case 90: return CLEAR_AIR_MODE;
|
||||
case 90:
|
||||
return CLEAR_AIR_MODE;
|
||||
|
||||
case 11:
|
||||
case 12:
|
||||
case 21:
|
||||
case 80:
|
||||
case 112:
|
||||
case 121:
|
||||
case 211:
|
||||
case 212:
|
||||
case 215: return PRECIPITATION_MODE;
|
||||
case 215:
|
||||
case 221:
|
||||
return PRECIPITATION_MODE;
|
||||
|
||||
default: return "?";
|
||||
default:
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -246,7 +246,8 @@ AwsNexradDataProvider::ListObjects(std::chrono::system_clock::time_point date)
|
|||
{
|
||||
std::string key = object.GetKey();
|
||||
|
||||
if (!key.ends_with("_MDM"))
|
||||
if (key.find("NWS_NEXRAD_") == std::string::npos &&
|
||||
!key.ends_with("_MDM"))
|
||||
{
|
||||
auto time = GetTimePointByKey(key);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#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/types.hpp>
|
||||
#include <scwx/wsr88d/rda/rda_types.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
#include <scwx/util/rangebuf.hpp>
|
||||
#include <scwx/util/time.hpp>
|
||||
|
|
@ -18,6 +19,7 @@
|
|||
# pragma GCC diagnostic ignored "-Wdeprecated-copy"
|
||||
#endif
|
||||
|
||||
#include <boost/algorithm/string/trim.hpp>
|
||||
#include <boost/iostreams/copy.hpp>
|
||||
#include <boost/iostreams/filtering_streambuf.hpp>
|
||||
#include <boost/iostreams/filter/bzip2.hpp>
|
||||
|
|
@ -41,16 +43,7 @@ static const auto logger_ = util::Logger::Create(logPrefix_);
|
|||
class Ar2vFileImpl
|
||||
{
|
||||
public:
|
||||
explicit Ar2vFileImpl() :
|
||||
tapeFilename_ {},
|
||||
extensionNumber_ {},
|
||||
julianDate_ {0},
|
||||
milliseconds_ {0},
|
||||
icao_ {},
|
||||
vcpData_ {nullptr},
|
||||
radarData_ {},
|
||||
index_ {},
|
||||
rawRecords_ {} {};
|
||||
explicit Ar2vFileImpl() {};
|
||||
~Ar2vFileImpl() = default;
|
||||
|
||||
std::size_t DecompressLDMRecords(std::istream& is);
|
||||
|
|
@ -58,22 +51,24 @@ public:
|
|||
void IndexFile();
|
||||
void ParseLDMRecords();
|
||||
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 extensionNumber_;
|
||||
std::uint32_t julianDate_;
|
||||
std::uint32_t milliseconds_;
|
||||
std::string icao_;
|
||||
std::string tapeFilename_ {};
|
||||
std::string extensionNumber_ {};
|
||||
std::uint32_t julianDate_ {0};
|
||||
std::uint32_t milliseconds_ {0};
|
||||
std::string icao_ {};
|
||||
|
||||
std::shared_ptr<rda::VolumeCoveragePatternData> vcpData_;
|
||||
std::map<std::uint16_t, std::shared_ptr<rda::ElevationScan>> radarData_;
|
||||
std::size_t messageCount_ {0};
|
||||
|
||||
std::shared_ptr<rda::VolumeCoveragePatternData> vcpData_ {};
|
||||
std::map<std::uint16_t, std::shared_ptr<rda::ElevationScan>> radarData_ {};
|
||||
|
||||
std::map<rda::DataBlockType,
|
||||
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>()) {}
|
||||
|
|
@ -82,12 +77,12 @@ Ar2vFile::~Ar2vFile() = default;
|
|||
Ar2vFile::Ar2vFile(Ar2vFile&&) noexcept = default;
|
||||
Ar2vFile& Ar2vFile::operator=(Ar2vFile&&) noexcept = default;
|
||||
|
||||
uint32_t Ar2vFile::julian_date() const
|
||||
std::uint32_t Ar2vFile::julian_date() const
|
||||
{
|
||||
return p->julianDate_;
|
||||
}
|
||||
|
||||
uint32_t Ar2vFile::milliseconds() const
|
||||
std::uint32_t Ar2vFile::milliseconds() const
|
||||
{
|
||||
return p->milliseconds_;
|
||||
}
|
||||
|
|
@ -97,6 +92,11 @@ std::string Ar2vFile::icao() const
|
|||
return p->icao_;
|
||||
}
|
||||
|
||||
std::size_t Ar2vFile::message_count() const
|
||||
{
|
||||
return p->messageCount_;
|
||||
}
|
||||
|
||||
std::chrono::system_clock::time_point Ar2vFile::start_time() const
|
||||
{
|
||||
return util::TimePoint(p->julianDate_, p->milliseconds_);
|
||||
|
|
@ -108,7 +108,7 @@ std::chrono::system_clock::time_point Ar2vFile::end_time() const
|
|||
|
||||
if (p->radarData_.size() > 0)
|
||||
{
|
||||
std::shared_ptr<rda::DigitalRadarData> lastRadial =
|
||||
std::shared_ptr<rda::GenericRadarData> lastRadial =
|
||||
p->radarData_.crbegin()->second->crbegin()->second;
|
||||
|
||||
endTime = util::TimePoint(lastRadial->modified_julian_date(),
|
||||
|
|
@ -118,7 +118,7 @@ std::chrono::system_clock::time_point Ar2vFile::end_time() const
|
|||
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
|
||||
{
|
||||
return p->radarData_;
|
||||
|
|
@ -142,17 +142,17 @@ Ar2vFile::GetElevationScan(rda::DataBlockType dataBlockType,
|
|||
float elevationCut = 0.0f;
|
||||
std::vector<float> elevationCuts;
|
||||
|
||||
uint16_t codedElevation =
|
||||
static_cast<uint16_t>(std::lroundf(elevation * scaleFactor));
|
||||
std::uint16_t codedElevation =
|
||||
static_cast<std::uint16_t>(std::lroundf(elevation * scaleFactor));
|
||||
|
||||
if (p->index_.contains(dataBlockType))
|
||||
{
|
||||
auto scans = p->index_.at(dataBlockType);
|
||||
auto& scans = p->index_.at(dataBlockType);
|
||||
|
||||
uint16_t lowerBound = scans.cbegin()->first;
|
||||
uint16_t upperBound = scans.crbegin()->first;
|
||||
std::uint16_t lowerBound = scans.cbegin()->first;
|
||||
std::uint16_t upperBound = scans.crbegin()->first;
|
||||
|
||||
for (auto scan : scans)
|
||||
for (auto& scan : scans)
|
||||
{
|
||||
if (scan.first > lowerBound && scan.first <= codedElevation)
|
||||
{
|
||||
|
|
@ -166,10 +166,12 @@ Ar2vFile::GetElevationScan(rda::DataBlockType dataBlockType,
|
|||
elevationCuts.push_back(scan.first / scaleFactor);
|
||||
}
|
||||
|
||||
int32_t lowerDelta = std::abs(static_cast<int32_t>(codedElevation) -
|
||||
static_cast<int32_t>(lowerBound));
|
||||
int32_t upperDelta = std::abs(static_cast<int32_t>(codedElevation) -
|
||||
static_cast<int32_t>(upperBound));
|
||||
std::int32_t lowerDelta =
|
||||
std::abs(static_cast<std::int32_t>(codedElevation) -
|
||||
static_cast<std::int32_t>(lowerBound));
|
||||
std::int32_t upperDelta =
|
||||
std::abs(static_cast<std::int32_t>(codedElevation) -
|
||||
static_cast<std::int32_t>(upperBound));
|
||||
|
||||
if (lowerDelta < upperDelta)
|
||||
{
|
||||
|
|
@ -232,6 +234,10 @@ bool Ar2vFile::LoadData(std::istream& is)
|
|||
dataValid = false;
|
||||
}
|
||||
|
||||
// Trim spaces and null characters from the end of the ICAO
|
||||
boost::trim_right_if(p->icao_,
|
||||
[](char x) { return std::isspace(x) || x == '\0'; });
|
||||
|
||||
if (dataValid)
|
||||
{
|
||||
logger_->debug("Filename: {}", p->tapeFilename_);
|
||||
|
|
@ -256,17 +262,17 @@ bool Ar2vFile::LoadData(std::istream& is)
|
|||
return dataValid;
|
||||
}
|
||||
|
||||
size_t Ar2vFileImpl::DecompressLDMRecords(std::istream& is)
|
||||
std::size_t Ar2vFileImpl::DecompressLDMRecords(std::istream& is)
|
||||
{
|
||||
logger_->debug("Decompressing LDM Records");
|
||||
|
||||
size_t numRecords = 0;
|
||||
std::size_t numRecords = 0;
|
||||
|
||||
while (is.peek() != EOF)
|
||||
{
|
||||
std::streampos startPosition = is.tellg();
|
||||
int32_t controlWord = 0;
|
||||
size_t recordSize;
|
||||
std::int32_t controlWord = 0;
|
||||
std::size_t recordSize;
|
||||
|
||||
is.read(reinterpret_cast<char*>(&controlWord), 4);
|
||||
|
||||
|
|
@ -315,7 +321,7 @@ void Ar2vFileImpl::ParseLDMRecords()
|
|||
{
|
||||
logger_->debug("Parsing LDM Records");
|
||||
|
||||
size_t count = 0;
|
||||
std::size_t count = 0;
|
||||
|
||||
for (auto it = rawRecords_.begin(); it != rawRecords_.end(); it++)
|
||||
{
|
||||
|
|
@ -331,65 +337,82 @@ void Ar2vFileImpl::ParseLDMRecords()
|
|||
|
||||
void Ar2vFileImpl::ParseLDMRecord(std::istream& is)
|
||||
{
|
||||
static constexpr std::size_t kDefaultSegmentSize = 2432;
|
||||
static constexpr std::size_t kCtmHeaderSize = 12;
|
||||
|
||||
auto ctx = rda::Level2MessageFactory::CreateContext();
|
||||
|
||||
// The communications manager inserts an extra 12 bytes at the beginning
|
||||
// of each record
|
||||
is.seekg(12, std::ios_base::cur);
|
||||
|
||||
while (!is.eof())
|
||||
while (!is.eof() && !is.fail())
|
||||
{
|
||||
off_t offset = 0;
|
||||
uint16_t nextSize = 0u;
|
||||
do
|
||||
// The communications manager inserts an extra 12 bytes at the beginning
|
||||
// of each record
|
||||
is.seekg(kCtmHeaderSize, std::ios_base::cur);
|
||||
|
||||
// Each message requires 2432 bytes of storage, with the exception of
|
||||
// Message Types 29 and 31.
|
||||
std::size_t messageSize = kDefaultSegmentSize - kCtmHeaderSize;
|
||||
|
||||
// Mark current position
|
||||
std::streampos messageStart = is.tellg();
|
||||
|
||||
// Parse the header
|
||||
rda::Level2MessageHeader messageHeader;
|
||||
bool headerValid = messageHeader.Parse(is);
|
||||
is.seekg(messageStart, std::ios_base::beg);
|
||||
|
||||
if (headerValid)
|
||||
{
|
||||
is.read(reinterpret_cast<char*>(&nextSize), 2);
|
||||
if (nextSize == 0)
|
||||
std::uint8_t messageType = messageHeader.message_type();
|
||||
|
||||
// Each message requires 2432 bytes of storage, with the exception of
|
||||
// Message Types 29 and 31.
|
||||
if (messageType == 29 || messageType == 31)
|
||||
{
|
||||
offset += 2;
|
||||
if (messageHeader.message_size() == 65535)
|
||||
{
|
||||
messageSize = (static_cast<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 =
|
||||
rda::Level2MessageFactory::Create(is, ctx);
|
||||
if (!msgInfo.headerValid)
|
||||
{
|
||||
// Invalid message
|
||||
break;
|
||||
}
|
||||
|
||||
if (msgInfo.messageValid)
|
||||
{
|
||||
HandleMessage(msgInfo.message);
|
||||
}
|
||||
// Skip to next message
|
||||
is.seekg(messageStart + static_cast<std::streampos>(messageSize),
|
||||
std::ios_base::beg);
|
||||
}
|
||||
}
|
||||
|
||||
void Ar2vFileImpl::HandleMessage(std::shared_ptr<rda::Level2Message>& message)
|
||||
{
|
||||
++messageCount_;
|
||||
|
||||
switch (message->header().message_type())
|
||||
{
|
||||
case static_cast<uint8_t>(rda::MessageId::VolumeCoveragePatternData):
|
||||
case static_cast<std::uint8_t>(rda::MessageId::VolumeCoveragePatternData):
|
||||
vcpData_ =
|
||||
std::static_pointer_cast<rda::VolumeCoveragePatternData>(message);
|
||||
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(
|
||||
std::static_pointer_cast<rda::DigitalRadarData>(message));
|
||||
std::static_pointer_cast<rda::GenericRadarData>(message));
|
||||
break;
|
||||
|
||||
default:
|
||||
|
|
@ -398,10 +421,10 @@ void Ar2vFileImpl::HandleMessage(std::shared_ptr<rda::Level2Message>& message)
|
|||
}
|
||||
|
||||
void Ar2vFileImpl::ProcessRadarData(
|
||||
std::shared_ptr<rda::DigitalRadarData> message)
|
||||
const std::shared_ptr<rda::GenericRadarData>& message)
|
||||
{
|
||||
uint16_t azimuthIndex = message->azimuth_number() - 1;
|
||||
uint16_t elevationIndex = message->elevation_number() - 1;
|
||||
std::uint16_t azimuthIndex = message->azimuth_number() - 1;
|
||||
std::uint16_t elevationIndex = message->elevation_number() - 1;
|
||||
|
||||
if (radarData_[elevationIndex] == nullptr)
|
||||
{
|
||||
|
|
@ -415,20 +438,12 @@ void Ar2vFileImpl::IndexFile()
|
|||
{
|
||||
logger_->debug("Indexing file");
|
||||
|
||||
if (vcpData_ == nullptr)
|
||||
for (auto& elevationCut : radarData_)
|
||||
{
|
||||
logger_->warn("Cannot index file without VCP data");
|
||||
return;
|
||||
}
|
||||
std::uint16_t elevationAngle {};
|
||||
rda::WaveformType waveformType = rda::WaveformType::Unknown;
|
||||
|
||||
for (auto elevationCut : radarData_)
|
||||
{
|
||||
uint16_t elevationAngle =
|
||||
vcpData_->elevation_angle_raw(elevationCut.first);
|
||||
rda::WaveformType waveformType =
|
||||
vcpData_->waveform_type(elevationCut.first);
|
||||
|
||||
std::shared_ptr<rda::DigitalRadarData> radial0 =
|
||||
std::shared_ptr<rda::GenericRadarData>& radial0 =
|
||||
(*elevationCut.second)[0];
|
||||
|
||||
if (radial0 == nullptr)
|
||||
|
|
@ -437,6 +452,26 @@ void Ar2vFileImpl::IndexFile()
|
|||
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 :
|
||||
rda::MomentDataBlockTypeIterator())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -70,9 +70,9 @@ std::shared_ptr<NexradFile> NexradFileFactory::Create(std::istream& is)
|
|||
std::string buffer;
|
||||
bool dataValid;
|
||||
|
||||
buffer.resize(4);
|
||||
buffer.resize(8);
|
||||
|
||||
is.read(buffer.data(), 4);
|
||||
is.read(buffer.data(), 8);
|
||||
dataValid = is.good();
|
||||
is.seekg(pisBegin, std::ios_base::beg);
|
||||
|
||||
|
|
@ -89,7 +89,7 @@ std::shared_ptr<NexradFile> NexradFileFactory::Create(std::istream& is)
|
|||
pis = &ss;
|
||||
pisBegin = ss.tellg();
|
||||
|
||||
ss.read(buffer.data(), 4);
|
||||
ss.read(buffer.data(), 8);
|
||||
dataValid = ss.good();
|
||||
ss.seekg(pisBegin, std::ios_base::beg);
|
||||
|
||||
|
|
@ -114,7 +114,7 @@ std::shared_ptr<NexradFile> NexradFileFactory::Create(std::istream& is)
|
|||
|
||||
if (dataValid)
|
||||
{
|
||||
if (buffer.starts_with("AR2V"))
|
||||
if (buffer.starts_with("AR2V") || buffer.starts_with("ARCHIVE2"))
|
||||
{
|
||||
message = std::make_shared<Ar2vFile>();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,19 +78,22 @@ bool ClutterFilterBypassMap::Parse(std::istream& is)
|
|||
|
||||
if (p->mapGenerationDate_ < 1)
|
||||
{
|
||||
logger_->warn("Invalid date: {}", p->mapGenerationDate_);
|
||||
logger_->trace("Ignoring empty message");
|
||||
messageValid = false;
|
||||
}
|
||||
if (p->mapGenerationTime_ > 1440)
|
||||
else
|
||||
{
|
||||
logger_->warn("Invalid time: {}", p->mapGenerationTime_);
|
||||
messageValid = false;
|
||||
}
|
||||
if (numElevationSegments < 1 || numElevationSegments > 5)
|
||||
{
|
||||
logger_->warn("Invalid number of elevation segments: {}",
|
||||
numElevationSegments);
|
||||
messageValid = false;
|
||||
if (p->mapGenerationTime_ > 1440)
|
||||
{
|
||||
logger_->warn("Invalid time: {}", p->mapGenerationTime_);
|
||||
messageValid = false;
|
||||
}
|
||||
if (numElevationSegments < 1 || numElevationSegments > 5)
|
||||
{
|
||||
logger_->warn("Invalid number of elevation segments: {}",
|
||||
numElevationSegments);
|
||||
messageValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!messageValid)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
776
wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp
Normal file
776
wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp
Normal 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
|
||||
61
wxdata/source/scwx/wsr88d/rda/generic_radar_data.cpp
Normal file
61
wxdata/source/scwx/wsr88d/rda/generic_radar_data.cpp
Normal 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
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
#include <scwx/wsr88d/rda/clutter_filter_bypass_map.hpp>
|
||||
#include <scwx/wsr88d/rda/clutter_filter_map.hpp>
|
||||
#include <scwx/wsr88d/rda/digital_radar_data.hpp>
|
||||
#include <scwx/wsr88d/rda/digital_radar_data_generic.hpp>
|
||||
#include <scwx/wsr88d/rda/performance_maintenance_data.hpp>
|
||||
#include <scwx/wsr88d/rda/rda_adaptation_data.hpp>
|
||||
#include <scwx/wsr88d/rda/rda_status_data.hpp>
|
||||
|
|
@ -28,14 +29,15 @@ typedef std::function<std::shared_ptr<Level2Message>(Level2MessageHeader&&,
|
|||
std::istream&)>
|
||||
CreateLevel2MessageFunction;
|
||||
|
||||
static const std::unordered_map<unsigned int, CreateLevel2MessageFunction> create_ {
|
||||
{2, RdaStatusData::Create},
|
||||
{3, PerformanceMaintenanceData::Create},
|
||||
{5, VolumeCoveragePatternData::Create},
|
||||
{13, ClutterFilterBypassMap::Create},
|
||||
{15, ClutterFilterMap::Create},
|
||||
{18, RdaAdaptationData::Create},
|
||||
{31, DigitalRadarData::Create}};
|
||||
static const std::unordered_map<unsigned int, CreateLevel2MessageFunction>
|
||||
create_ {{1, DigitalRadarData::Create},
|
||||
{2, RdaStatusData::Create},
|
||||
{3, PerformanceMaintenanceData::Create},
|
||||
{5, VolumeCoveragePatternData::Create},
|
||||
{13, ClutterFilterBypassMap::Create},
|
||||
{15, ClutterFilterMap::Create},
|
||||
{18, RdaAdaptationData::Create},
|
||||
{31, DigitalRadarDataGeneric::Create}};
|
||||
|
||||
struct Level2MessageFactory::Context
|
||||
{
|
||||
|
|
@ -51,6 +53,7 @@ struct Level2MessageFactory::Context
|
|||
size_t bufferedSize_;
|
||||
util::vectorbuf messageBuffer_;
|
||||
std::istream messageBufferStream_;
|
||||
bool bufferingData_ {false};
|
||||
};
|
||||
|
||||
std::shared_ptr<Level2MessageFactory::Context>
|
||||
|
|
@ -59,14 +62,38 @@ Level2MessageFactory::CreateContext()
|
|||
return std::make_shared<Context>();
|
||||
}
|
||||
|
||||
Level2MessageInfo Level2MessageFactory::Create(std::istream& is,
|
||||
std::shared_ptr<Context> ctx)
|
||||
Level2MessageInfo Level2MessageFactory::Create(std::istream& is,
|
||||
std::shared_ptr<Context>& ctx)
|
||||
{
|
||||
Level2MessageInfo info;
|
||||
Level2MessageHeader header;
|
||||
info.headerValid = header.Parse(is);
|
||||
info.messageValid = info.headerValid;
|
||||
|
||||
std::uint16_t segment = 0;
|
||||
std::uint16_t totalSegments = 0;
|
||||
std::size_t dataSize = 0;
|
||||
|
||||
if (info.headerValid)
|
||||
{
|
||||
if (header.message_size() == 65535)
|
||||
{
|
||||
segment = 1;
|
||||
totalSegments = 1;
|
||||
dataSize =
|
||||
(static_cast<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())
|
||||
{
|
||||
logger_->warn("Unknown message type: {}",
|
||||
|
|
@ -76,10 +103,7 @@ Level2MessageInfo Level2MessageFactory::Create(std::istream& is,
|
|||
|
||||
if (info.messageValid)
|
||||
{
|
||||
uint16_t segment = header.message_segment_number();
|
||||
uint16_t totalSegments = header.number_of_message_segments();
|
||||
uint8_t messageType = header.message_type();
|
||||
size_t dataSize = header.message_size() * 2 - Level2MessageHeader::SIZE;
|
||||
std::uint8_t messageType = header.message_type();
|
||||
|
||||
std::istream* messageStream = nullptr;
|
||||
|
||||
|
|
@ -100,38 +124,51 @@ Level2MessageInfo Level2MessageFactory::Create(std::istream& is,
|
|||
// Estimate total message size
|
||||
ctx->messageData_.resize(dataSize * totalSegments);
|
||||
ctx->messageBufferStream_.clear();
|
||||
ctx->bufferedSize_ = 0;
|
||||
ctx->bufferedSize_ = 0;
|
||||
ctx->bufferingData_ = true;
|
||||
}
|
||||
|
||||
if (ctx->messageData_.capacity() < ctx->bufferedSize_ + dataSize)
|
||||
else if (!ctx->bufferingData_)
|
||||
{
|
||||
logger_->debug("Bad size estimate, increasing size");
|
||||
|
||||
// Estimate remaining size
|
||||
uint16_t remainingSegments =
|
||||
std::max<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");
|
||||
// Segment number did not start at 1
|
||||
logger_->trace("Ignoring Segment {}/{}, did not start at 1",
|
||||
segment,
|
||||
totalSegments);
|
||||
info.messageValid = false;
|
||||
ctx->messageData_.shrink_to_fit();
|
||||
ctx->bufferedSize_ = 0;
|
||||
}
|
||||
else if (segment == totalSegments)
|
||||
{
|
||||
ctx->messageBuffer_.update_read_pointers(ctx->bufferedSize_);
|
||||
header.set_message_size(static_cast<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_.shrink_to_fit();
|
||||
ctx->messageBufferStream_.clear();
|
||||
ctx->bufferedSize_ = 0;
|
||||
ctx->bufferedSize_ = 0;
|
||||
ctx->bufferingData_ = false;
|
||||
}
|
||||
}
|
||||
else if (info.headerValid)
|
||||
{
|
||||
// Seek to the end of the current message
|
||||
is.seekg(header.message_size() * 2 - rda::Level2MessageHeader::SIZE,
|
||||
std::ios_base::cur);
|
||||
is.seekg(dataSize, std::ios_base::cur);
|
||||
}
|
||||
|
||||
if (info.message == nullptr)
|
||||
|
|
|
|||
|
|
@ -130,12 +130,10 @@ bool Level2MessageHeader::Parse(std::istream& is)
|
|||
{
|
||||
if (p->messageSize_ < 9)
|
||||
{
|
||||
logger_->warn("Invalid message size: {}", p->messageSize_);
|
||||
headerValid = false;
|
||||
}
|
||||
if (p->julianDate_ < 1)
|
||||
{
|
||||
logger_->warn("Invalid date: {}", p->julianDate_);
|
||||
if (p->messageSize_ != 0)
|
||||
{
|
||||
logger_->warn("Invalid message size: {}", p->messageSize_);
|
||||
}
|
||||
headerValid = false;
|
||||
}
|
||||
if (p->millisecondsOfDay_ > 86'399'999u)
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ VolumeCoveragePatternData::VolumeCoveragePatternData() :
|
|||
VolumeCoveragePatternData::~VolumeCoveragePatternData() = default;
|
||||
|
||||
VolumeCoveragePatternData::VolumeCoveragePatternData(
|
||||
VolumeCoveragePatternData&&) noexcept = default;
|
||||
VolumeCoveragePatternData&&) noexcept = default;
|
||||
VolumeCoveragePatternData& VolumeCoveragePatternData::operator=(
|
||||
VolumeCoveragePatternData&&) noexcept = default;
|
||||
|
||||
|
|
@ -419,16 +419,24 @@ bool VolumeCoveragePatternData::Parse(std::istream& is)
|
|||
p->vcpSequencing_ = ntohs(p->vcpSequencing_);
|
||||
p->vcpSupplementalData_ = ntohs(p->vcpSupplementalData_);
|
||||
|
||||
if (messageSize < 34 || messageSize > 747)
|
||||
if (messageSize == 0)
|
||||
{
|
||||
logger_->warn("Invalid message size: {}", messageSize);
|
||||
logger_->trace("Ignoring empty message");
|
||||
messageValid = false;
|
||||
}
|
||||
if (numberOfElevationCuts < 1 || numberOfElevationCuts > 32)
|
||||
else
|
||||
{
|
||||
logger_->warn("Invalid number of elevation cuts: {}",
|
||||
numberOfElevationCuts);
|
||||
messageValid = false;
|
||||
if (messageSize < 34 || messageSize > 747)
|
||||
{
|
||||
logger_->warn("Invalid message size: {}", messageSize);
|
||||
messageValid = false;
|
||||
}
|
||||
if (numberOfElevationCuts < 1 || numberOfElevationCuts > 32)
|
||||
{
|
||||
logger_->warn("Invalid number of elevation cuts: {}",
|
||||
numberOfElevationCuts);
|
||||
messageValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!messageValid)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
include/scwx/wsr88d/rda/clutter_filter_map.hpp
|
||||
include/scwx/wsr88d/rda/digital_radar_data.hpp
|
||||
include/scwx/wsr88d/rda/digital_radar_data_generic.hpp
|
||||
include/scwx/wsr88d/rda/generic_radar_data.hpp
|
||||
include/scwx/wsr88d/rda/level2_message.hpp
|
||||
include/scwx/wsr88d/rda/level2_message_factory.hpp
|
||||
include/scwx/wsr88d/rda/level2_message_header.hpp
|
||||
include/scwx/wsr88d/rda/performance_maintenance_data.hpp
|
||||
include/scwx/wsr88d/rda/rda_adaptation_data.hpp
|
||||
include/scwx/wsr88d/rda/rda_status_data.hpp
|
||||
include/scwx/wsr88d/rda/types.hpp
|
||||
include/scwx/wsr88d/rda/rda_types.hpp
|
||||
include/scwx/wsr88d/rda/volume_coverage_pattern_data.hpp)
|
||||
set(SRC_WSR88D_RDA source/scwx/wsr88d/rda/clutter_filter_bypass_map.cpp
|
||||
source/scwx/wsr88d/rda/clutter_filter_map.cpp
|
||||
source/scwx/wsr88d/rda/digital_radar_data.cpp
|
||||
source/scwx/wsr88d/rda/digital_radar_data_generic.cpp
|
||||
source/scwx/wsr88d/rda/generic_radar_data.cpp
|
||||
source/scwx/wsr88d/rda/level2_message.cpp
|
||||
source/scwx/wsr88d/rda/level2_message_factory.cpp
|
||||
source/scwx/wsr88d/rda/level2_message_header.cpp
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue