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