mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 10:30:06 +00:00 
			
		
		
		
	Merge pull request #138 from dpaulat/feature/legacy-level2-data
Support older Level 2 data formats
This commit is contained in:
		
						commit
						74e4564882
					
				
					 28 changed files with 2636 additions and 1184 deletions
				
			
		|  | @ -430,8 +430,12 @@ void MainWindow::on_actionOpenNexrad_triggered() | |||
|       { | ||||
|          logger_->info("Selected: {}", file.toStdString()); | ||||
| 
 | ||||
|          auto        radarSite = p->activeMap_->GetRadarSite(); | ||||
|          std::string currentRadarSite = | ||||
|             (radarSite != nullptr) ? radarSite->id() : std::string {}; | ||||
| 
 | ||||
|          std::shared_ptr<request::NexradFileRequest> request = | ||||
|             std::make_shared<request::NexradFileRequest>(); | ||||
|             std::make_shared<request::NexradFileRequest>(currentRadarSite); | ||||
| 
 | ||||
|          connect( //
 | ||||
|             request.get(), | ||||
|  | @ -882,9 +886,9 @@ void MainWindowImpl::ConnectAnimationSignals() | |||
|            &manager::TimelineManager::VolumeTimeUpdated, | ||||
|            [this](std::chrono::system_clock::time_point dateTime) | ||||
|            { | ||||
|               volumeTime_ = dateTime; | ||||
|               for (auto map : maps_) | ||||
|               { | ||||
|                  volumeTime_ = dateTime; | ||||
|                  map->SelectTime(dateTime); | ||||
|               } | ||||
|            }); | ||||
|  |  | |||
|  | @ -207,16 +207,18 @@ public: | |||
|    void UpdateRecentRecords(RadarProductRecordList& recentList, | ||||
|                             std::shared_ptr<types::RadarProductRecord> record); | ||||
| 
 | ||||
|    void LoadNexradFileAsync(CreateNexradFileFunction                    load, | ||||
|                             std::shared_ptr<request::NexradFileRequest> request, | ||||
|                             std::mutex&                                 mutex, | ||||
|                             std::chrono::system_clock::time_point       time); | ||||
|    void LoadProviderData(std::chrono::system_clock::time_point time, | ||||
|    void LoadNexradFileAsync( | ||||
|       CreateNexradFileFunction                           load, | ||||
|       const std::shared_ptr<request::NexradFileRequest>& request, | ||||
|       std::mutex&                                        mutex, | ||||
|       std::chrono::system_clock::time_point              time); | ||||
|    void | ||||
|         LoadProviderData(std::chrono::system_clock::time_point time, | ||||
|                          std::shared_ptr<ProviderManager>      providerManager, | ||||
|                          RadarProductRecordMap&                recordMap, | ||||
|                          std::shared_mutex&                    recordMutex, | ||||
|                          std::mutex&                           loadDataMutex, | ||||
|                          std::shared_ptr<request::NexradFileRequest> request); | ||||
|                          const std::shared_ptr<request::NexradFileRequest>& request); | ||||
|    void PopulateLevel2ProductTimes(std::chrono::system_clock::time_point time); | ||||
|    void PopulateLevel3ProductTimes(const std::string& product, | ||||
|                                    std::chrono::system_clock::time_point time); | ||||
|  | @ -228,10 +230,10 @@ public: | |||
|                         std::chrono::system_clock::time_point time); | ||||
| 
 | ||||
|    static void | ||||
|    LoadNexradFile(CreateNexradFileFunction                    load, | ||||
|                   std::shared_ptr<request::NexradFileRequest> request, | ||||
|                   std::mutex&                                 mutex, | ||||
|                   std::chrono::system_clock::time_point       time = {}); | ||||
|    LoadNexradFile(CreateNexradFileFunction                           load, | ||||
|                   const std::shared_ptr<request::NexradFileRequest>& request, | ||||
|                   std::mutex&                                        mutex, | ||||
|                   std::chrono::system_clock::time_point              time = {}); | ||||
| 
 | ||||
|    const std::string radarId_; | ||||
|    bool              initialized_; | ||||
|  | @ -394,6 +396,11 @@ float RadarProductManager::gate_size() const | |||
|    return (p->radarSite_->type() == "tdwr") ? 150.0f : 250.0f; | ||||
| } | ||||
| 
 | ||||
| std::string RadarProductManager::radar_id() const | ||||
| { | ||||
|    return p->radarId_; | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<config::RadarSite> RadarProductManager::radar_site() const | ||||
| { | ||||
|    return p->radarSite_; | ||||
|  | @ -777,12 +784,12 @@ RadarProductManager::GetActiveVolumeTimes( | |||
| } | ||||
| 
 | ||||
| void RadarProductManagerImpl::LoadProviderData( | ||||
|    std::chrono::system_clock::time_point       time, | ||||
|    std::shared_ptr<ProviderManager>            providerManager, | ||||
|    RadarProductRecordMap&                      recordMap, | ||||
|    std::shared_mutex&                          recordMutex, | ||||
|    std::mutex&                                 loadDataMutex, | ||||
|    std::shared_ptr<request::NexradFileRequest> request) | ||||
|    std::chrono::system_clock::time_point              time, | ||||
|    std::shared_ptr<ProviderManager>                   providerManager, | ||||
|    RadarProductRecordMap&                             recordMap, | ||||
|    std::shared_mutex&                                 recordMutex, | ||||
|    std::mutex&                                        loadDataMutex, | ||||
|    const std::shared_ptr<request::NexradFileRequest>& request) | ||||
| { | ||||
|    logger_->debug("LoadProviderData: {}, {}", | ||||
|                   providerManager->name(), | ||||
|  | @ -837,8 +844,8 @@ void RadarProductManagerImpl::LoadProviderData( | |||
| } | ||||
| 
 | ||||
| void RadarProductManager::LoadLevel2Data( | ||||
|    std::chrono::system_clock::time_point       time, | ||||
|    std::shared_ptr<request::NexradFileRequest> request) | ||||
|    std::chrono::system_clock::time_point              time, | ||||
|    const std::shared_ptr<request::NexradFileRequest>& request) | ||||
| { | ||||
|    logger_->debug("LoadLevel2Data: {}", scwx::util::TimeString(time)); | ||||
| 
 | ||||
|  | @ -851,9 +858,9 @@ void RadarProductManager::LoadLevel2Data( | |||
| } | ||||
| 
 | ||||
| void RadarProductManager::LoadLevel3Data( | ||||
|    const std::string&                          product, | ||||
|    std::chrono::system_clock::time_point       time, | ||||
|    std::shared_ptr<request::NexradFileRequest> request) | ||||
|    const std::string&                                 product, | ||||
|    std::chrono::system_clock::time_point              time, | ||||
|    const std::shared_ptr<request::NexradFileRequest>& request) | ||||
| { | ||||
|    logger_->debug("LoadLevel3Data: {}", scwx::util::TimeString(time)); | ||||
| 
 | ||||
|  | @ -883,7 +890,7 @@ void RadarProductManager::LoadLevel3Data( | |||
| } | ||||
| 
 | ||||
| void RadarProductManager::LoadData( | ||||
|    std::istream& is, std::shared_ptr<request::NexradFileRequest> request) | ||||
|    std::istream& is, const std::shared_ptr<request::NexradFileRequest>& request) | ||||
| { | ||||
|    logger_->debug("LoadData()"); | ||||
| 
 | ||||
|  | @ -899,8 +906,8 @@ void RadarProductManager::LoadData( | |||
| } | ||||
| 
 | ||||
| void RadarProductManager::LoadFile( | ||||
|    const std::string&                          filename, | ||||
|    std::shared_ptr<request::NexradFileRequest> request) | ||||
|    const std::string&                                 filename, | ||||
|    const std::shared_ptr<request::NexradFileRequest>& request) | ||||
| { | ||||
|    logger_->debug("LoadFile: {}", filename); | ||||
| 
 | ||||
|  | @ -950,10 +957,10 @@ void RadarProductManager::LoadFile( | |||
| } | ||||
| 
 | ||||
| void RadarProductManagerImpl::LoadNexradFileAsync( | ||||
|    CreateNexradFileFunction                    load, | ||||
|    std::shared_ptr<request::NexradFileRequest> request, | ||||
|    std::mutex&                                 mutex, | ||||
|    std::chrono::system_clock::time_point       time) | ||||
|    CreateNexradFileFunction                           load, | ||||
|    const std::shared_ptr<request::NexradFileRequest>& request, | ||||
|    std::mutex&                                        mutex, | ||||
|    std::chrono::system_clock::time_point              time) | ||||
| { | ||||
|    boost::asio::post(threadPool_, | ||||
|                      [=, &mutex]() | ||||
|  | @ -961,10 +968,10 @@ void RadarProductManagerImpl::LoadNexradFileAsync( | |||
| } | ||||
| 
 | ||||
| void RadarProductManagerImpl::LoadNexradFile( | ||||
|    CreateNexradFileFunction                    load, | ||||
|    std::shared_ptr<request::NexradFileRequest> request, | ||||
|    std::mutex&                                 mutex, | ||||
|    std::chrono::system_clock::time_point       time) | ||||
|    CreateNexradFileFunction                           load, | ||||
|    const std::shared_ptr<request::NexradFileRequest>& request, | ||||
|    std::mutex&                                        mutex, | ||||
|    std::chrono::system_clock::time_point              time) | ||||
| { | ||||
|    std::unique_lock lock {mutex}; | ||||
| 
 | ||||
|  | @ -987,8 +994,14 @@ void RadarProductManagerImpl::LoadNexradFile( | |||
|          record->set_time(time); | ||||
|       } | ||||
| 
 | ||||
|       std::string recordRadarId = (record->radar_id()); | ||||
|       if (recordRadarId.empty()) | ||||
|       { | ||||
|          recordRadarId = request->current_radar_site(); | ||||
|       } | ||||
| 
 | ||||
|       std::shared_ptr<RadarProductManager> manager = | ||||
|          RadarProductManager::Instance(record->radar_id()); | ||||
|          RadarProductManager::Instance(recordRadarId); | ||||
| 
 | ||||
|       manager->Initialize(); | ||||
|       record = manager->p->StoreRadarProductRecord(record); | ||||
|  | @ -1035,7 +1048,14 @@ void RadarProductManagerImpl::PopulateProductTimes( | |||
|    std::shared_mutex&                    productRecordMutex, | ||||
|    std::chrono::system_clock::time_point time) | ||||
| { | ||||
|    const auto today     = std::chrono::floor<std::chrono::days>(time); | ||||
|    const auto today = std::chrono::floor<std::chrono::days>(time); | ||||
| 
 | ||||
|    // Don't query for the epoch
 | ||||
|    if (today == std::chrono::system_clock::time_point {}) | ||||
|    { | ||||
|       return; | ||||
|    } | ||||
| 
 | ||||
|    const auto yesterday = today - std::chrono::days {1}; | ||||
|    const auto tomorrow  = today + std::chrono::days {1}; | ||||
|    const auto dates     = {yesterday, today, tomorrow}; | ||||
|  | @ -1119,7 +1139,7 @@ RadarProductManagerImpl::GetLevel2ProductRecord( | |||
|    { | ||||
|       // Product is expired, reload it
 | ||||
|       std::shared_ptr<request::NexradFileRequest> request = | ||||
|          std::make_shared<request::NexradFileRequest>(); | ||||
|          std::make_shared<request::NexradFileRequest>(radarId_); | ||||
| 
 | ||||
|       QObject::connect( | ||||
|          request.get(), | ||||
|  | @ -1184,7 +1204,7 @@ RadarProductManagerImpl::GetLevel3ProductRecord( | |||
|    { | ||||
|       // Product is expired, reload it
 | ||||
|       std::shared_ptr<request::NexradFileRequest> request = | ||||
|          std::make_shared<request::NexradFileRequest>(); | ||||
|          std::make_shared<request::NexradFileRequest>(radarId_); | ||||
| 
 | ||||
|       QObject::connect( | ||||
|          request.get(), | ||||
|  |  | |||
|  | @ -42,6 +42,7 @@ public: | |||
| 
 | ||||
|    const std::vector<float>& coordinates(common::RadialSize radialSize) const; | ||||
|    float                     gate_size() const; | ||||
|    std::string               radar_id() const; | ||||
|    std::shared_ptr<config::RadarSite> radar_site() const; | ||||
| 
 | ||||
|    void Initialize(); | ||||
|  | @ -110,19 +111,19 @@ public: | |||
|    Instance(const std::string& radarSite); | ||||
| 
 | ||||
|    void LoadLevel2Data( | ||||
|       std::chrono::system_clock::time_point       time, | ||||
|       std::shared_ptr<request::NexradFileRequest> request = nullptr); | ||||
|       std::chrono::system_clock::time_point              time, | ||||
|       const std::shared_ptr<request::NexradFileRequest>& request = nullptr); | ||||
|    void LoadLevel3Data( | ||||
|       const std::string&                          product, | ||||
|       std::chrono::system_clock::time_point       time, | ||||
|       std::shared_ptr<request::NexradFileRequest> request = nullptr); | ||||
|       const std::string&                                 product, | ||||
|       std::chrono::system_clock::time_point              time, | ||||
|       const std::shared_ptr<request::NexradFileRequest>& request = nullptr); | ||||
| 
 | ||||
|    static void | ||||
|    LoadData(std::istream&                               is, | ||||
|             std::shared_ptr<request::NexradFileRequest> request = nullptr); | ||||
|    static void | ||||
|    LoadFile(const std::string&                          filename, | ||||
|             std::shared_ptr<request::NexradFileRequest> request = nullptr); | ||||
|    static void LoadData( | ||||
|       std::istream&                                      is, | ||||
|       const std::shared_ptr<request::NexradFileRequest>& request = nullptr); | ||||
|    static void LoadFile( | ||||
|       const std::string&                                 filename, | ||||
|       const std::shared_ptr<request::NexradFileRequest>& request = nullptr); | ||||
| 
 | ||||
|    common::Level3ProductCategoryMap GetAvailableLevel3Categories(); | ||||
|    std::vector<std::string>         GetLevel3Products(); | ||||
|  |  | |||
|  | @ -1332,7 +1332,8 @@ void MapWidgetImpl::RadarProductManagerConnect() | |||
|             { | ||||
|                // Create file request
 | ||||
|                std::shared_ptr<request::NexradFileRequest> request = | ||||
|                   std::make_shared<request::NexradFileRequest>(); | ||||
|                   std::make_shared<request::NexradFileRequest>( | ||||
|                      radarProductManager_->radar_id()); | ||||
| 
 | ||||
|                // File request callback
 | ||||
|                if (autoUpdateEnabled_) | ||||
|  |  | |||
|  | @ -9,22 +9,31 @@ namespace request | |||
| 
 | ||||
| static const std::string logPrefix_ = "scwx::qt::request::nexrad_file_request"; | ||||
| 
 | ||||
| class NexradFileRequestImpl | ||||
| class NexradFileRequest::Impl | ||||
| { | ||||
| public: | ||||
|    explicit NexradFileRequestImpl() : radarProductRecord_ {nullptr} {} | ||||
|    explicit Impl(const std::string& currentRadarSite) : | ||||
|        currentRadarSite_ {currentRadarSite} | ||||
|    { | ||||
|    } | ||||
|    ~Impl() = default; | ||||
| 
 | ||||
|    ~NexradFileRequestImpl() {} | ||||
|    const std::string currentRadarSite_; | ||||
| 
 | ||||
|    std::shared_ptr<types::RadarProductRecord> radarProductRecord_; | ||||
|    std::shared_ptr<types::RadarProductRecord> radarProductRecord_ {nullptr}; | ||||
| }; | ||||
| 
 | ||||
| NexradFileRequest::NexradFileRequest() : | ||||
|     p(std::make_unique<NexradFileRequestImpl>()) | ||||
| NexradFileRequest::NexradFileRequest(const std::string& currentRadarSite) : | ||||
|     p(std::make_unique<Impl>(currentRadarSite)) | ||||
| { | ||||
| } | ||||
| NexradFileRequest::~NexradFileRequest() = default; | ||||
| 
 | ||||
| std::string NexradFileRequest::current_radar_site() const | ||||
| { | ||||
|    return p->currentRadarSite_; | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<types::RadarProductRecord> | ||||
| NexradFileRequest::radar_product_record() const | ||||
| { | ||||
|  | @ -32,7 +41,7 @@ NexradFileRequest::radar_product_record() const | |||
| } | ||||
| 
 | ||||
| void NexradFileRequest::set_radar_product_record( | ||||
|    std::shared_ptr<types::RadarProductRecord> record) | ||||
|    const std::shared_ptr<types::RadarProductRecord>& record) | ||||
| { | ||||
|    p->radarProductRecord_ = record; | ||||
| } | ||||
|  |  | |||
|  | @ -13,23 +13,23 @@ namespace qt | |||
| namespace request | ||||
| { | ||||
| 
 | ||||
| class NexradFileRequestImpl; | ||||
| 
 | ||||
| class NexradFileRequest : public QObject | ||||
| { | ||||
|    Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|    explicit NexradFileRequest(); | ||||
|    explicit NexradFileRequest(const std::string& currentRadarSite = {}); | ||||
|    ~NexradFileRequest(); | ||||
| 
 | ||||
|    std::string                                current_radar_site() const; | ||||
|    std::shared_ptr<types::RadarProductRecord> radar_product_record() const; | ||||
| 
 | ||||
|    void | ||||
|    set_radar_product_record(std::shared_ptr<types::RadarProductRecord> record); | ||||
|    void set_radar_product_record( | ||||
|       const std::shared_ptr<types::RadarProductRecord>& record); | ||||
| 
 | ||||
| private: | ||||
|    std::unique_ptr<NexradFileRequestImpl> p; | ||||
|    class Impl; | ||||
|    std::unique_ptr<Impl> p; | ||||
| 
 | ||||
| signals: | ||||
|    void RequestComplete(std::shared_ptr<NexradFileRequest> request); | ||||
|  |  | |||
|  | @ -89,8 +89,9 @@ public: | |||
| 
 | ||||
|    float selectedElevation_; | ||||
| 
 | ||||
|    std::shared_ptr<wsr88d::rda::ElevationScan>   elevationScan_; | ||||
|    std::shared_ptr<wsr88d::rda::MomentDataBlock> momentDataBlock0_; | ||||
|    std::shared_ptr<wsr88d::rda::ElevationScan> elevationScan_; | ||||
|    std::shared_ptr<wsr88d::rda::GenericRadarData::MomentDataBlock> | ||||
|       momentDataBlock0_; | ||||
| 
 | ||||
|    std::vector<float>    coordinates_ {}; | ||||
|    std::vector<float>    vertices_ {}; | ||||
|  | @ -98,12 +99,12 @@ public: | |||
|    std::vector<uint16_t> dataMoments16_ {}; | ||||
|    std::vector<uint8_t>  cfpMoments_ {}; | ||||
| 
 | ||||
|    float              latitude_; | ||||
|    float              longitude_; | ||||
|    float              elevationCut_; | ||||
|    std::vector<float> elevationCuts_; | ||||
|    float              range_; | ||||
|    uint16_t           vcp_; | ||||
|    float                    latitude_; | ||||
|    float                    longitude_; | ||||
|    float                    elevationCut_; | ||||
|    std::vector<float>       elevationCuts_; | ||||
|    units::kilometers<float> range_; | ||||
|    uint16_t                 vcp_; | ||||
| 
 | ||||
|    std::chrono::system_clock::time_point sweepTime_; | ||||
| 
 | ||||
|  | @ -212,7 +213,7 @@ float Level2ProductView::elevation() const | |||
| 
 | ||||
| float Level2ProductView::range() const | ||||
| { | ||||
|    return p->range_; | ||||
|    return p->range_.value(); | ||||
| } | ||||
| 
 | ||||
| std::chrono::system_clock::time_point Level2ProductView::sweep_time() const | ||||
|  | @ -468,15 +469,15 @@ void Level2ProductView::ComputeSweep() | |||
| 
 | ||||
|    const uint32_t gates = momentData0->number_of_data_moment_gates(); | ||||
| 
 | ||||
|    auto volumeData0 = radarData0->volume_data_block(); | ||||
|    p->latitude_     = volumeData0->latitude(); | ||||
|    p->longitude_    = volumeData0->longitude(); | ||||
|    auto radarSite = radarProductManager->radar_site(); | ||||
|    p->latitude_   = radarSite->latitude(); | ||||
|    p->longitude_  = radarSite->longitude(); | ||||
|    p->range_ = | ||||
|       momentData0->data_moment_range() + | ||||
|       momentData0->data_moment_range_sample_interval() * (gates - 0.5f); | ||||
|    p->sweepTime_ = scwx::util::TimePoint(radarData0->modified_julian_date(), | ||||
|                                          radarData0->collection_time()); | ||||
|    p->vcp_       = volumeData0->volume_coverage_pattern_number(); | ||||
|    p->vcp_       = radarData0->volume_coverage_pattern_number(); | ||||
| 
 | ||||
|    // Calculate vertices
 | ||||
|    timer.start(); | ||||
|  | @ -521,17 +522,17 @@ void Level2ProductView::ComputeSweep() | |||
|    } | ||||
| 
 | ||||
|    // Compute threshold at which to display an individual bin (minimum of 2)
 | ||||
|    const uint16_t snrThreshold = | ||||
|       std::max<int16_t>(2, momentData0->snr_threshold_raw()); | ||||
|    const std::uint16_t snrThreshold = | ||||
|       std::max<std::int16_t>(2, momentData0->snr_threshold_raw()); | ||||
| 
 | ||||
|    // Start radial is always 0, as coordinates are calculated for each sweep
 | ||||
|    constexpr std::uint16_t startRadial = 0u; | ||||
| 
 | ||||
|    for (auto& radialPair : *radarData) | ||||
|    { | ||||
|       uint16_t radial     = radialPair.first; | ||||
|       auto     radialData = radialPair.second; | ||||
|       auto     momentData = radialData->moment_data_block(p->dataBlockType_); | ||||
|       std::uint16_t radial     = radialPair.first; | ||||
|       auto&         radialData = radialPair.second; | ||||
|       auto momentData = radialData->moment_data_block(p->dataBlockType_); | ||||
| 
 | ||||
|       if (momentData0->data_word_size() != momentData->data_word_size()) | ||||
|       { | ||||
|  | @ -540,64 +541,70 @@ void Level2ProductView::ComputeSweep() | |||
|       } | ||||
| 
 | ||||
|       // Compute gate interval
 | ||||
|       const uint16_t dataMomentRange = momentData->data_moment_range_raw(); | ||||
|       const uint16_t dataMomentInterval = | ||||
|       const std::int32_t dataMomentInterval = | ||||
|          momentData->data_moment_range_sample_interval_raw(); | ||||
|       const uint16_t dataMomentIntervalH = dataMomentInterval / 2; | ||||
|       const std::int32_t dataMomentIntervalH = dataMomentInterval / 2; | ||||
|       const std::int32_t dataMomentRange     = std::max<std::int32_t>( | ||||
|          momentData->data_moment_range_raw(), dataMomentIntervalH); | ||||
| 
 | ||||
|       // Compute gate size (number of base 250m gates per bin)
 | ||||
|       const uint16_t gateSizeMeters = | ||||
|          static_cast<uint16_t>(radarProductManager->gate_size()); | ||||
|       const uint16_t gateSize = | ||||
|          std::max<uint16_t>(1, dataMomentInterval / gateSizeMeters); | ||||
|       const std::int32_t gateSizeMeters = | ||||
|          static_cast<std::int32_t>(radarProductManager->gate_size()); | ||||
|       const std::int32_t gateSize = | ||||
|          std::max<std::int32_t>(1, dataMomentInterval / gateSizeMeters); | ||||
| 
 | ||||
|       // Compute gate range [startGate, endGate)
 | ||||
|       const uint16_t startGate = | ||||
|       const std::int32_t startGate = | ||||
|          (dataMomentRange - dataMomentIntervalH) / gateSizeMeters; | ||||
|       const uint16_t numberOfDataMomentGates = | ||||
|          std::min<uint16_t>(momentData->number_of_data_moment_gates(), | ||||
|                             static_cast<uint16_t>(gates)); | ||||
|       const uint16_t endGate = | ||||
|          std::min<uint16_t>(startGate + numberOfDataMomentGates * gateSize, | ||||
|                             common::MAX_DATA_MOMENT_GATES); | ||||
|       const std::int32_t numberOfDataMomentGates = | ||||
|          std::min<std::int32_t>(momentData->number_of_data_moment_gates(), | ||||
|                                 static_cast<std::int32_t>(gates)); | ||||
|       const std::int32_t endGate = std::min<std::int32_t>( | ||||
|          startGate + numberOfDataMomentGates * gateSize, | ||||
|          static_cast<std::int32_t>(common::MAX_DATA_MOMENT_GATES)); | ||||
| 
 | ||||
|       const uint8_t*  dataMomentsArray8  = nullptr; | ||||
|       const uint16_t* dataMomentsArray16 = nullptr; | ||||
|       const uint8_t*  cfpMomentsArray    = nullptr; | ||||
|       const std::uint8_t*  dataMomentsArray8  = nullptr; | ||||
|       const std::uint16_t* dataMomentsArray16 = nullptr; | ||||
|       const std::uint8_t*  cfpMomentsArray    = nullptr; | ||||
| 
 | ||||
|       if (momentData->data_word_size() == 8) | ||||
|       { | ||||
|          dataMomentsArray8 = | ||||
|             reinterpret_cast<const uint8_t*>(momentData->data_moments()); | ||||
|             reinterpret_cast<const std::uint8_t*>(momentData->data_moments()); | ||||
|       } | ||||
|       else | ||||
|       { | ||||
|          dataMomentsArray16 = | ||||
|             reinterpret_cast<const uint16_t*>(momentData->data_moments()); | ||||
|             reinterpret_cast<const std::uint16_t*>(momentData->data_moments()); | ||||
|       } | ||||
| 
 | ||||
|       if (cfpMoments.size() > 0) | ||||
|       { | ||||
|          cfpMomentsArray = reinterpret_cast<const uint8_t*>( | ||||
|          cfpMomentsArray = reinterpret_cast<const std::uint8_t*>( | ||||
|             radialData->moment_data_block(wsr88d::rda::DataBlockType::MomentCfp) | ||||
|                ->data_moments()); | ||||
|       } | ||||
| 
 | ||||
|       for (uint16_t gate = startGate, i = 0; gate + gateSize <= endGate; | ||||
|       for (std::int32_t gate = startGate, i = 0; gate + gateSize <= endGate; | ||||
|            gate += gateSize, ++i) | ||||
|       { | ||||
|          size_t vertexCount = (gate > 0) ? 6 : 3; | ||||
|          if (gate < 0) | ||||
|          { | ||||
|             continue; | ||||
|          } | ||||
| 
 | ||||
|          std::size_t vertexCount = (gate > 0) ? 6 : 3; | ||||
| 
 | ||||
|          // Store data moment value
 | ||||
|          if (dataMomentsArray8 != nullptr) | ||||
|          { | ||||
|             uint8_t dataValue = dataMomentsArray8[i]; | ||||
|             std::uint8_t dataValue = dataMomentsArray8[i]; | ||||
|             if (dataValue < snrThreshold && dataValue != RANGE_FOLDED) | ||||
|             { | ||||
|                continue; | ||||
|             } | ||||
| 
 | ||||
|             for (size_t m = 0; m < vertexCount; m++) | ||||
|             for (std::size_t m = 0; m < vertexCount; m++) | ||||
|             { | ||||
|                dataMoments8[mIndex++] = dataMomentsArray8[i]; | ||||
| 
 | ||||
|  | @ -609,13 +616,13 @@ void Level2ProductView::ComputeSweep() | |||
|          } | ||||
|          else | ||||
|          { | ||||
|             uint16_t dataValue = dataMomentsArray16[i]; | ||||
|             std::uint16_t dataValue = dataMomentsArray16[i]; | ||||
|             if (dataValue < snrThreshold && dataValue != RANGE_FOLDED) | ||||
|             { | ||||
|                continue; | ||||
|             } | ||||
| 
 | ||||
|             for (size_t m = 0; m < vertexCount; m++) | ||||
|             for (std::size_t m = 0; m < vertexCount; m++) | ||||
|             { | ||||
|                dataMoments16[mIndex++] = dataMomentsArray16[i]; | ||||
|             } | ||||
|  | @ -624,18 +631,18 @@ void Level2ProductView::ComputeSweep() | |||
|          // Store vertices
 | ||||
|          if (gate > 0) | ||||
|          { | ||||
|             const uint16_t baseCoord = gate - 1; | ||||
|             const std::uint16_t baseCoord = gate - 1; | ||||
| 
 | ||||
|             size_t offset1 = ((startRadial + radial) % radials * | ||||
|                                  common::MAX_DATA_MOMENT_GATES + | ||||
|                               baseCoord) * | ||||
|                              2; | ||||
|             size_t offset2 = offset1 + gateSize * 2; | ||||
|             size_t offset3 = (((startRadial + radial + 1) % radials) * | ||||
|                                  common::MAX_DATA_MOMENT_GATES + | ||||
|                               baseCoord) * | ||||
|                              2; | ||||
|             size_t offset4 = offset3 + gateSize * 2; | ||||
|             std::size_t offset1 = ((startRadial + radial) % radials * | ||||
|                                       common::MAX_DATA_MOMENT_GATES + | ||||
|                                    baseCoord) * | ||||
|                                   2; | ||||
|             std::size_t offset2 = offset1 + gateSize * 2; | ||||
|             std::size_t offset3 = (((startRadial + radial + 1) % radials) * | ||||
|                                       common::MAX_DATA_MOMENT_GATES + | ||||
|                                    baseCoord) * | ||||
|                                   2; | ||||
|             std::size_t offset4 = offset3 + gateSize * 2; | ||||
| 
 | ||||
|             vertices[vIndex++] = coordinates[offset1]; | ||||
|             vertices[vIndex++] = coordinates[offset1 + 1]; | ||||
|  | @ -659,16 +666,16 @@ void Level2ProductView::ComputeSweep() | |||
|          } | ||||
|          else | ||||
|          { | ||||
|             const uint16_t baseCoord = gate; | ||||
|             const std::uint16_t baseCoord = gate; | ||||
| 
 | ||||
|             size_t offset1 = ((startRadial + radial) % radials * | ||||
|                                  common::MAX_DATA_MOMENT_GATES + | ||||
|                               baseCoord) * | ||||
|                              2; | ||||
|             size_t offset2 = (((startRadial + radial + 1) % radials) * | ||||
|                                  common::MAX_DATA_MOMENT_GATES + | ||||
|                               baseCoord) * | ||||
|                              2; | ||||
|             std::size_t offset1 = ((startRadial + radial) % radials * | ||||
|                                       common::MAX_DATA_MOMENT_GATES + | ||||
|                                    baseCoord) * | ||||
|                                   2; | ||||
|             std::size_t offset2 = (((startRadial + radial + 1) % radials) * | ||||
|                                       common::MAX_DATA_MOMENT_GATES + | ||||
|                                    baseCoord) * | ||||
|                                   2; | ||||
| 
 | ||||
|             vertices[vIndex++] = p->latitude_; | ||||
|             vertices[vIndex++] = p->longitude_; | ||||
|  | @ -747,7 +754,8 @@ void Level2ProductViewImpl::ComputeCoordinates( | |||
|                  radials.end(), | ||||
|                  [&](std::uint32_t radial) | ||||
|                  { | ||||
|                     const float angle = (*radarData)[radial]->azimuth_angle(); | ||||
|                     const units::degrees<float> angle = | ||||
|                        (*radarData)[radial]->azimuth_angle(); | ||||
| 
 | ||||
|                     std::for_each(std::execution::par_unseq, | ||||
|                                   gates.begin(), | ||||
|  | @ -765,7 +773,7 @@ void Level2ProductViewImpl::ComputeCoordinates( | |||
| 
 | ||||
|                                      geodesic.Direct(radarLatitude, | ||||
|                                                      radarLongitude, | ||||
|                                                      angle, | ||||
|                                                      angle.value(), | ||||
|                                                      range, | ||||
|                                                      latitude, | ||||
|                                                      longitude); | ||||
|  | @ -830,14 +838,15 @@ Level2ProductView::GetBinLevel(const common::Coordinate& coordinate) const | |||
|       radials.end(), | ||||
|       [&](std::uint32_t i) | ||||
|       { | ||||
|          bool        found      = false; | ||||
|          const float startAngle = (*radarData)[i]->azimuth_angle(); | ||||
|          const float nextAngle = | ||||
|          bool                        found = false; | ||||
|          const units::degrees<float> startAngle = | ||||
|             (*radarData)[i]->azimuth_angle(); | ||||
|          const units::degrees<float> nextAngle = | ||||
|             (*radarData)[(i + 1) % numRadials]->azimuth_angle(); | ||||
| 
 | ||||
|          if (startAngle < nextAngle) | ||||
|          { | ||||
|             if (startAngle <= azi1 && azi1 < nextAngle) | ||||
|             if (startAngle.value() <= azi1 && azi1 < nextAngle.value()) | ||||
|             { | ||||
|                found = true; | ||||
|             } | ||||
|  | @ -845,7 +854,7 @@ Level2ProductView::GetBinLevel(const common::Coordinate& coordinate) const | |||
|          else | ||||
|          { | ||||
|             // If the bin crosses 0/360 degrees, special handling is needed
 | ||||
|             if (startAngle <= azi1 || azi1 < nextAngle) | ||||
|             if (startAngle.value() <= azi1 || azi1 < nextAngle.value()) | ||||
|             { | ||||
|                found = true; | ||||
|             } | ||||
|  | @ -862,24 +871,26 @@ Level2ProductView::GetBinLevel(const common::Coordinate& coordinate) const | |||
| 
 | ||||
|    // Compute gate interval
 | ||||
|    auto momentData = (*radarData)[*radial]->moment_data_block(dataBlockType); | ||||
|    const std::uint16_t dataMomentRange = momentData->data_moment_range_raw(); | ||||
|    const std::uint16_t dataMomentInterval = | ||||
|    const std::int32_t dataMomentInterval = | ||||
|       momentData->data_moment_range_sample_interval_raw(); | ||||
|    const std::uint16_t dataMomentIntervalH = dataMomentInterval / 2; | ||||
|    const std::int32_t dataMomentIntervalH = dataMomentInterval / 2; | ||||
|    const std::int32_t dataMomentRange     = std::max<std::int32_t>( | ||||
|       momentData->data_moment_range_raw(), dataMomentIntervalH); | ||||
| 
 | ||||
|    // Compute gate size (number of base 250m gates per bin)
 | ||||
|    const std::uint16_t gateSizeMeters = | ||||
|       static_cast<std::uint16_t>(radarProductManager->gate_size()); | ||||
|    const std::int32_t gateSizeMeters = | ||||
|       static_cast<std::int32_t>(radarProductManager->gate_size()); | ||||
| 
 | ||||
|    // Compute gate range [startGate, endGate)
 | ||||
|    const std::uint16_t startGate = | ||||
|    const std::int32_t startGate = | ||||
|       (dataMomentRange - dataMomentIntervalH) / gateSizeMeters; | ||||
|    const std::uint16_t numberOfDataMomentGates = | ||||
|    const std::int32_t numberOfDataMomentGates = | ||||
|       momentData->number_of_data_moment_gates(); | ||||
| 
 | ||||
|    const std::uint16_t gate = s12 / dataMomentInterval - startGate; | ||||
|    const std::int32_t gate = s12 / dataMomentInterval - startGate; | ||||
| 
 | ||||
|    if (gate > numberOfDataMomentGates || gate > common::MAX_DATA_MOMENT_GATES) | ||||
|    if (gate < 0 || gate > numberOfDataMomentGates || | ||||
|        gate > static_cast<std::int32_t>(common::MAX_DATA_MOMENT_GATES)) | ||||
|    { | ||||
|       // Coordinate is beyond radar range
 | ||||
|       return std::nullopt; | ||||
|  |  | |||
|  | @ -1 +1 @@ | |||
| Subproject commit 6632ffd6ba35b799dd803e9711281d54a3858a29 | ||||
| Subproject commit e3e743a5cc9c065d05f00151380fea892fb2156c | ||||
|  | @ -1,7 +1,5 @@ | |||
| #include <scwx/wsr88d/ar2v_file.hpp> | ||||
| 
 | ||||
| #include <fstream> | ||||
| 
 | ||||
| #include <gtest/gtest.h> | ||||
| 
 | ||||
| namespace scwx | ||||
|  | @ -9,25 +7,32 @@ namespace scwx | |||
| namespace wsr88d | ||||
| { | ||||
| 
 | ||||
| TEST(ar2v_file, klsx) | ||||
| class Ar2vValidFileTest : | ||||
|     public testing::TestWithParam<std::pair<std::string, std::size_t>> | ||||
| { | ||||
| }; | ||||
| 
 | ||||
| TEST_P(Ar2vValidFileTest, ValidFile) | ||||
| { | ||||
|    auto& param = GetParam(); | ||||
| 
 | ||||
|    Ar2vFile file; | ||||
|    bool     fileValid = | ||||
|       file.LoadFile(std::string(SCWX_TEST_DATA_DIR) + | ||||
|                     "/nexrad/level2/Level2_KLSX_20210527_1757.ar2v"); | ||||
|       file.LoadFile(std::string(SCWX_TEST_DATA_DIR) + param.first); | ||||
| 
 | ||||
|    EXPECT_EQ(fileValid, true); | ||||
|    EXPECT_EQ(file.message_count(), param.second); | ||||
| } | ||||
| 
 | ||||
| TEST(ar2v_file, tstl) | ||||
| { | ||||
|    Ar2vFile file; | ||||
|    bool     fileValid = | ||||
|       file.LoadFile(std::string(SCWX_TEST_DATA_DIR) + | ||||
|                     "/nexrad/level2/Level2_TSTL_20220213_2357.ar2v"); | ||||
| 
 | ||||
|    EXPECT_EQ(fileValid, true); | ||||
| } | ||||
| INSTANTIATE_TEST_SUITE_P( | ||||
|    Ar2vFile, | ||||
|    Ar2vValidFileTest, | ||||
|    testing::Values(std::pair<std::string, std::size_t> //
 | ||||
|                    {"/nexrad/level2/KCLE20021110_221234", 4031}, | ||||
|                    std::pair<std::string, std::size_t> //
 | ||||
|                    {"/nexrad/level2/Level2_KLSX_20210527_1757.ar2v", 11167}, | ||||
|                    std::pair<std::string, std::size_t> //
 | ||||
|                    {"/nexrad/level2/Level2_TSTL_20220213_2357.ar2v", 5763})); | ||||
| 
 | ||||
| } // namespace wsr88d
 | ||||
| } // namespace scwx
 | ||||
|  |  | |||
							
								
								
									
										509
									
								
								wxdata/archive2_format.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										509
									
								
								wxdata/archive2_format.txt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,509 @@ | |||
| [Image] NEXRAD DOCUMENTATION | ||||
| ---------------------------------------------------------------------------- | ||||
| 
 | ||||
|                          LEVEL II TAPE DOCUMENTATION | ||||
| 
 | ||||
|                               WSR-88D BASE DATA | ||||
| 
 | ||||
| INTRODUCTION: | ||||
| 
 | ||||
| Weather Surveillance Radar - 1988 Doppler (WSR-88D), or NEXt Generation | ||||
| RADar (NEXRAD), Level II data are the base digital data produced by the | ||||
| signal processor (mean radial velocity, reflectivity, and spectrum width) at | ||||
| the full spatial and temporal resolution of the radar. Level II data also | ||||
| contain status messages, performance/maintenance data, volume scan strategy, | ||||
| clutter filter bypass map, and wideband communication console messages. | ||||
| These are the same data transmitted over high-speed, wideband communications | ||||
| to the WSR-88D Radar Product Generator (RPG) for processing by the | ||||
| meteorological analysis algorithms. | ||||
| 
 | ||||
| Initially it was thought that Level II recorders would be used at selected | ||||
| sites, and only when significant weather events were taking place. As system | ||||
| development progressed, it became evident that the Level II data would be of | ||||
| vital importance to ensure proper calibration of the radars and for use by | ||||
| researchers to investigate events in more detail than would be possible by | ||||
| using the Level III products. The Level II data can also be used to test | ||||
| revised algorithms that may later be applied to operational use. | ||||
| 
 | ||||
| The NEXRAD agencies (Departments of Commerce, Defense, and Transportation) | ||||
| recognized the value of Level II data. In June 1994, the agencies agreed to | ||||
| record Level II data throughout the WSR-88D network. The Level II recording | ||||
| is not essential to the operational use of the WSR-88D system. The NEXRAD | ||||
| agencies agreed to certain procedures to minimize the impact of Level II | ||||
| data collection on the operations of base weather stations, forecast offices | ||||
| and FAA control locations. The priority of Level II recorder maintenance, | ||||
| reloading of tapes and continuous recording of data will be assigned by the | ||||
| local site management. | ||||
| 
 | ||||
| RECORDING: | ||||
| 
 | ||||
| The vast amounts of data collected at the Radar Data Acquisition (RDA) site | ||||
| made it mandatory that the most economical recording devices and media | ||||
| available at that time be used. It was determined that EXABYTE tape drives | ||||
| and 8mm tapes provided the most viable system. Depending on operation of the | ||||
| radar, the recorder model used, and station requirements, one tape may be | ||||
| filled every 1.8 days for each site. Data grade tapes are used for recording | ||||
| and archiving. Initially, sites were equipped with EXABYTE 8200 recorders. | ||||
| These tapes can contain up to 2.3 gigabytes per tape. Later, EXABYTE 8500 | ||||
| recorders were installed which record at higher density with up to 4.7 | ||||
| gigabytes per tape. Also available are 8500c (capable of recording in a | ||||
| standard compressed mode), and 8505 which is a half height drive fully | ||||
| downward compatible. The 8505 records up 4.7 gigabytes in an uncompressed | ||||
| mode. | ||||
| 
 | ||||
| PROCESSING AND ARCHIVING: | ||||
| 
 | ||||
| The Level II recorder system consists of an 8mm reorder, 10-tape jukebox | ||||
| (automated sequential loading of new tapes), an uninterruptable power supply | ||||
| ans a controller board seated in the RDA computer. Under jukebox operation | ||||
| the 10-tape supply will last 11 to 27 days depending on the radar scanning | ||||
| strategies used. Tapes are received at the National Climatic Data Center | ||||
| (NCDC) from the individual sites in 10-tape cases. Incoming tapes are | ||||
| processed on a series of 8505 EXABYTE drives, reblocked, cataloged, | ||||
| inventoried, and archived. The original tapes are sent to an off-site | ||||
| storage facility for security back-up to the NCDC NEXRAD files. | ||||
| 
 | ||||
| SPECIAL NOTE: | ||||
| 
 | ||||
| The WSR-88D is a very complex system. Program modifications and engineering | ||||
| changes are rather constant features during the phase-in process. Some early | ||||
| pre-production models experienced considerable difficulties in the recording | ||||
| of Level II data. Even today, tapes are received that contain spurious, | ||||
| erroneous, or illegal configurations. We have attempted to recover as much | ||||
| data as possible from these problem tapes. The user is cautioned that these | ||||
| anomalies may be encountered while reading the archive tapes. Special care | ||||
| must be taken to ensure that illegal configurations do not contaminate any | ||||
| summaries or statistical studies. | ||||
| 
 | ||||
| NCDC will be glad to assist in solving problems encountered in reading the | ||||
| tapes, but technical questions about the data themselves must be addressed | ||||
| to the: | ||||
| 
 | ||||
|      NWS/Operational Support Facility | ||||
|      Operations Branch | ||||
|      1200 Westheimer Dr. | ||||
|      Norman, OK 73069 | ||||
| 
 | ||||
|      Telephone: (405) 366-6530 | ||||
|      FAX:       (405) 366-6550 | ||||
| 
 | ||||
| Definitive information about all aspects of the Doppler radar is contained | ||||
| in Federal Meteorological Handbook No. 11 (FMH-11), Volumes A through D. | ||||
| These may be ordered from the National Climatic Data Center. | ||||
| 
 | ||||
| DATA AVAILABILITY: | ||||
| 
 | ||||
| As stated previously, all NCDC archives are being generated on EXABYTE 8505 | ||||
| drives. Users must specify whether they require 8200 or 8500 mode tapes. If | ||||
| copies are requested in the 8200 mode, two or more output tapes may be | ||||
| required. A header record will appear on each output tape. | ||||
| 
 | ||||
| Each 8mm tape records approximately 10 hours of Volume Coverage Pattern 11 | ||||
| (VCP 11), 18 hours of VCP 21, or 40 hours of VCP 31 or 32 using the EXABYTE | ||||
| 8200 mode. Using the EXABYTE 8500 mode doubles both the storage capacity and | ||||
| number of hours of data possible per 8mm tape. | ||||
| 
 | ||||
| FORMAT: | ||||
| 
 | ||||
| HEADER FILE: The first file on tape contains only one 31616 byte record. | ||||
| This record is called the header record. | ||||
| 
 | ||||
| HEADER RECORD: This 31616 byte "physical record" is divided into 494 | ||||
| "logical records" of 64 bytes each with position 1 as the first byte. | ||||
| 
 | ||||
| POSITIONS      FORMAT             DESCRIPTION | ||||
| 
 | ||||
| 1 -  8         C*8       Always ARCHIVE2 | ||||
| 
 | ||||
| 9 - 12         C*4       4-letter site ID.  e.g. KLMB | ||||
| 
 | ||||
| 13 - 18        C*6       NCDC tape number.  e.g. N00001 | ||||
| 
 | ||||
| 19                       Blank | ||||
| 
 | ||||
| 20 - 28        C*9       Date tape written. dd-MMM-yy e.g. 19-FEB-93 | ||||
| 
 | ||||
| 29                       Blank | ||||
| 
 | ||||
| 30 - 37        C*8       Time tape written. hh:mm:ss. e.g. 10:22:59 | ||||
|                          (local time) | ||||
| 
 | ||||
| 38                       Blank | ||||
| 
 | ||||
| 39 - 43        C*5       Data Center writing tape:  RDASC or NCDC | ||||
|                          (Left justified, blank filled) | ||||
| 
 | ||||
| 44 - 48        C*5       WBAN Number of this NEXRAD site. (This is a unique | ||||
|                          5-digit number assigned at NCDC.  Numbers are | ||||
|                          contained in the NCDC NEXRAD Station History file | ||||
|                          (WSR-88D RDA LOCATIONS). The file also contains the | ||||
|                          four letter site ID, Latitude, Longitude, Elevation, | ||||
|                          and Standard location name.) | ||||
| 
 | ||||
| 49 - 53        C*5       Tape output mode. Current values are 8200, 8500, | ||||
|                          8500C | ||||
| 
 | ||||
| 54 - 58        C*5       A volume number to be used for copies and extractions | ||||
|                          of data from tapes. The form would be VOL01,  VOL02, | ||||
|                          VOL03 ....VOLnn. | ||||
| 
 | ||||
| 59 - 64                  Blank  (Available for future use.) | ||||
| 
 | ||||
| 65 - 31616               May be used for internal controls or other | ||||
|                          information at each archive center. Information of | ||||
|                          value to users will be documented at the time of tape | ||||
|                          shipment. | ||||
| 
 | ||||
| During the process of copying archive tapes, positions 1-18 and 44-48 will | ||||
| be duplicated. New values will be written in positions 19-43 and 49-58. | ||||
| 
 | ||||
| DATA FILES: | ||||
| 
 | ||||
| A new data file is created upon completion of a volume scan. A data file | ||||
| contains a title, a complete radar volume scan (360 degree revolutions at | ||||
| each specified elevation cut) of base data, digital radar data message, and | ||||
| any control/response messages from the RDA to the RPG. The title is the | ||||
| first record located in each data file and contains a file name, creation | ||||
| date, and creation time. After the title record through the remainder of the | ||||
| data file, variable length records containing base data intermixed with | ||||
| control/response messages are recorded. Messages and base data are | ||||
| distinguishable by a message header coded for either digital radar base data | ||||
| or one of the thirteen types of messages. The message header uses a format | ||||
| common to both data and messages and is included in each 2432 byte packet. | ||||
| Depending on the predefined volume scan strategy (selected elevations, sweep | ||||
| rate, pulse rate etc.) used during the collection period, each data file | ||||
| could contain either five, six, or ten minutes of base data. | ||||
| Control/response messages are used during actual operations and are of | ||||
| limited use for post analyses. | ||||
| 
 | ||||
| DATA TYPES SUPPORTED WITHIN DATA FILES: | ||||
| 
 | ||||
| A Concurrent minicomputer serves as the host computer for generation of all | ||||
| Archive Level II data. Depending on the computer used for reading the tapes, | ||||
| the data types may be different from those used in the Concurrent system. | ||||
| The Concurrent computer byte (8 bits) structure places bit 0 as the left | ||||
| most bit and designates bit 0 as the Most Significant Bit (MSB). Bit 7 for a | ||||
| byte, bit 15 for a halfword (2 bytes), bit 31 for a fullword (4 bytes) and | ||||
| bit 63 for a double word (8 bytes) are all the Least Significant Bit (LSB) | ||||
| for their respective data formats. | ||||
| 
 | ||||
| Level II is recorded using the following data types: | ||||
| 
 | ||||
|      Unsigned byte (byte) - number ranging from 0-255 | ||||
| 
 | ||||
|      Character (C) - Standard ASCII characters | ||||
| 
 | ||||
|      Signed Short Integer (I*2) - Most Significant Bit (MSB) is the sign bit | ||||
|      (bit 0). (1-Negative, 0-Positive). | ||||
| 
 | ||||
|      Signed Long Integer (I*4) - MSB (bit 0) is the sign bit. | ||||
| 
 | ||||
|      Single Precision Real (R*4) - MSB (bit 0) is the sign bit (positive), | ||||
|      bit 1-7 is the exponent in excess-64 notation format, and bit 8-31 is | ||||
|      the fraction field. An example may be helpful: | ||||
| 
 | ||||
|      Starting with 4180 69E8 (hex), the sign bit = 0 (positive), the | ||||
|      exponent = +1 [e.g. 41 (hex) converted to 65 (dec) - 64 (excess 64 | ||||
|      notation) = +1], and the fraction 8069E8 (hex) shifted by exponent of | ||||
|      +1 gives 8.069E8 (hex). To convert 8.069E8 (hex) to decimal, start with | ||||
|      the whole number 8 (hex) which in this case equals 8 (dec). Next, the | ||||
|      precision of the fraction .069E8 must be noted. This fraction has 5 | ||||
|      digits of precision. Next, the fraction portion in hex (069E8) is | ||||
|      converted to decimal (27112) and divided by 16 raised to the power of | ||||
|      the precision of the fraction (5). In other words 27112/(16**5) = | ||||
|      .02585 plus the whole number 8, gives 8.02585 in decimal. | ||||
| 
 | ||||
| DATA RECORDS: | ||||
| 
 | ||||
| Within the data file, base data and control/response messages are stored | ||||
| using a variable record-length structure. The convention here is to begin | ||||
| with byte 0 as the first byte. Included as the first record of each data | ||||
| file is a volume scan title containing the following information: | ||||
| 
 | ||||
| Bytes     Format    Description | ||||
| 
 | ||||
| 0-8       C*9       Filename (root) - "ARCHIVE2." | ||||
| 
 | ||||
| 9-11      C*3       Filename (extension) - "1", "2", etc. | ||||
| 
 | ||||
| 12-15     I*4       Modified Julian Date referenced from 1/1/70 | ||||
| 
 | ||||
| 16-19     I*4       Time - Milliseconds from midnight (UTC) of the day | ||||
|                     when the file was created. | ||||
| 
 | ||||
| 20-23               Unused | ||||
| 
 | ||||
| All remaining records in the data file are composed of data and | ||||
| command/response messages which are initially stored in separate 2432 byte | ||||
| packets within an RDA memory buffer. During the archive process the packets | ||||
| are copied from memory and grouped together to form a record. Record lengths | ||||
| are variable and are always sized in multiples of the 2432 byte packets. | ||||
| During the reblocking process, physical records are set to 31616 bytes (2432 | ||||
| x 13). | ||||
| 
 | ||||
| The following example shows a portion of one packet which includes | ||||
| Concurrent computer Channel Terminal Manager (CTM) information, a message | ||||
| header, and a digital radar data message containing reflectivity only. | ||||
| 
 | ||||
| 0000 0000 0980 0000 0002 0000 04B8 0001 | ||||
| 0060 1E9E 04B0 1841 0001 0001 0480 14A2 | ||||
| 1E9E 1234 6530 0059 0001 0058 0001 0000 | ||||
| FE89 03E8 00FA 01CC 0000 0001 4180 69E8 | ||||
| 0064 0000 0000 0000 0015 0000 0000 0000 | ||||
| 0000 0064 0000 0000 0000 FFF4 0064 0000 | ||||
| 0000 0000 0000 0000 0000 0000 0000 0000 | ||||
| 0000 0000 0000 0000 0000 0000 0000 0000 | ||||
| 005A 5A00 0070 6D51 6455 6060 4F54 0040 | ||||
| 5C3F 4049 4900 4D42 4349 434E 4B3D 4430 | ||||
| 4340 3F3D 4644 4443 3A3D 473F 3A3A 3D3D | ||||
| 3C45 3A43 433C 3E43 413C 393F 3F40 4038 | ||||
|      (etc.) | ||||
| 
 | ||||
| Using the above example, each portion of the packet is described in detail. | ||||
| Remember, this packet may be one of several contained in one record within | ||||
| the data file. | ||||
| 
 | ||||
|      Bytes 0-11 (halfwords 1-6)            Channel Terminal Manager (CTM) | ||||
|                                            information: | ||||
| 0000 0000 0980 0000 0002 0000 04B8 0001 | ||||
| 0060 1E9E 04B0 1841 0001 0001 0480 14A2    Archive II (the data tape) is a | ||||
| 1E9E 1234 6530 0059 0001 0058 0001 0000    copy of messages or data packets | ||||
| FE89 03E8 00FA 01CC 0000 0001 4180 69E8    prepared for transmission from the | ||||
| 0064 0000 0000 0000 0015 0000 0000 0000    RDA to the RPG.  CTM information is | ||||
| 0000 0064 0000 0000 0000 FFF4 0064 0000    attached to a message or data | ||||
| 0000 0000 0000 0000 0000 0000 0000 0000    packet for checking data integrity | ||||
| 0000 0000 0000 0000 0000 0000 0000 0000    during the transmission process | ||||
| 005A 5A00 0070 6D51 6455 6060 4F54 0040    and is of no importance to the base | ||||
| 5C3F 4049 4900 4D42 4349 434E 4B3D 4430    data (omit or read past these | ||||
| 4340 3F3D 4644 4443 3A3D 473F 3A3A 3D3D    bytes). | ||||
| 3C45 3A43 433C 3E43 413C 393F 3F40 4038 | ||||
|      (etc.) | ||||
| 
 | ||||
|      Bytes 12-27 (halfwords 7-14)          Message Header: | ||||
| 
 | ||||
| 0000 0000 0980 0000 0002 0000 04B8 0001    This information is | ||||
| 0060 1E9E 04B0 1841 0001 0001 0480 14A2    used to identify | ||||
| 1E9E 1234 6530 0059 0001 0058 0001 0000    either base data or one of thirteen | ||||
| FE89 03E8 00FA 01CC 0000 0001 4180 69E8    types of messages that may follow | ||||
| 0064 0000 0000 0000 0015 0000 0000 0000    in bytes 28 - 2431.  This header | ||||
| 0000 0064 0000 0000 0000 FFF4 0064 0000    includes the information indicated | ||||
| 0000 0000 0000 0000 0000 0000 0000 0000    below: | ||||
| 0000 0000 0000 0000 0000 0000 0000 0000 | ||||
| 005A 5A00 0070 6D51 6455 6060 4F54 0040 | ||||
| 5C3F 4049 4900 4D42 4349 434E 4B3D 4430 | ||||
| 4340 3F3D 4644 4443 3A3D 473F 3A3A 3D3D | ||||
| 3C45 3A43 433C 3E43 413C 393F 3F40 4038 | ||||
|      (etc.) | ||||
| 
 | ||||
| Halfword Format    Description | ||||
| 
 | ||||
| 7         I*2       Message size in halfwords measured from this | ||||
|                     halfword to the end of the record. | ||||
| 
 | ||||
| 8         I*1       (Left Byte) Channel ID: | ||||
|                          0 = Non-Redundant Site | ||||
|                          1 = Redundant Site Channel 1 | ||||
|                          2 = Redundant Site Channel 2 | ||||
| 
 | ||||
| 8         I*1       (Right Byte) Message type, where: | ||||
|                          1 = DIGITAL RADAR DATA (This message | ||||
|                              may contain a combination of either | ||||
|                              reflectivity, aliased velocity, or | ||||
|                              spectrum width) | ||||
|                          2 = RDA STATUS DATA. | ||||
|                          3 = PERFORMANCE/MAINTENANCE DATA. | ||||
|                          4 = CONSOLE MESSAGE - RDA TO RPG. | ||||
|                          5 = MAINTENANCE LOG DATA. | ||||
|                          6 = RDA CONTROL COMMANDS. | ||||
|                          7 = VOLUME COVERAGE PATTERN. | ||||
|                          8 = CLUTTER CENSOR ZONES. | ||||
|                          9 = REQUEST FOR DATA. | ||||
|                          10 = CONSOLE MESSAGE - RPG TO RDA. | ||||
|                          11 = LOOP BACK TEST  - RDA TO RPG. | ||||
|                          12 = LOOP BACK TEST  - RPG TO RDA. | ||||
|                          13 = CLUTTER FILTER BYPASS MAP - RDA to RPG. | ||||
|                          14 = EDITED CLUTTER FILTER BYPASS MAP - RPG to RDA. | ||||
| 
 | ||||
| 9         I*2       I.D. Sequence = 0 to 7FFF, then roll over back to 0. | ||||
| 
 | ||||
| 10        I*2       Modified Julian date starting from 1/1/70. | ||||
| 
 | ||||
| 11-12     I*4       Generation time of messages in milliseconds of day past | ||||
|                     midnight (UTC).  This time may be different than time | ||||
|                     listed in halfwords 15-16 defined below. | ||||
| 
 | ||||
| 13        I*2       Number of message segments.  Messages larger than message | ||||
|                     size (halfword 7 defined above) are segmented and | ||||
|                     recorded in separate data packets. | ||||
| 
 | ||||
| 14        I*2       Message segment number. | ||||
| 
 | ||||
|      Bytes 28-127 (halfwords 15-64)        Digital Radar Data Header: | ||||
| 
 | ||||
| 0000 0000 0980 0000 0002 0000 04B8 0001    This information describes the | ||||
| 0060 1E9E 04B0 1841 0001 0001 0480 14A2    date, time, azimuth, | ||||
| 1E9E 1234 6530 0059 0001 0058 0001 0000    elevation, and type | ||||
| FE89 03E8 00FA 01CC 0000 0001 4180 69E8    of base data included | ||||
| 0064 0000 0000 0000 0015 0000 0000 0000    in the radial.  This | ||||
| 0000 0064 0000 0000 0000 FFF4 0064 0000    header includes the | ||||
| 0000 0000 0000 0000 0000 0000 0000 0000    following | ||||
| 0000 0000 0000 0000 0000 0000 0000 0000    information: | ||||
| 005A 5A00 0070 6D51 6455 6060 4F54 0040 | ||||
| 5C3F 4049 4900 4D42 4349 434E 4B3D 4430 | ||||
| 4340 3F3D 4644 4443 3A3D 473F 3A3A 3D3D | ||||
| 3C45 3A43 433C 3E43 413C 393F 3F40 4038 | ||||
|      (etc.) | ||||
| 
 | ||||
| Halfword Format    Description | ||||
| 
 | ||||
| 15-16     I*4       Collection time for this radial in | ||||
|                     milliseconds of the day from midnight (UTC). | ||||
| 
 | ||||
| 17        I*2       Modified Julian date referenced from 1/1/70. | ||||
| 
 | ||||
| 18        I*2       Unambiguous range (scaled: Value/10. = KM). | ||||
| 
 | ||||
| 19        I*2       Azimuth angle (coded: [Value/8.]*[180./4096.] = DEG). | ||||
|                     An azimuth of "0 degrees" points to true north while "90 | ||||
|                     degrees" points east.  Rotation is always clockwise as | ||||
|                     viewed from above the radar. | ||||
| 
 | ||||
| 20        I*2       Radial number within the elevation scan. | ||||
| 
 | ||||
| 21        I*2       Radial status where: | ||||
|                          0 = START OF NEW ELEVATION. | ||||
|                          1 = INTERMEDIATE RADIAL. | ||||
|                          2 = END OF ELEVATION. | ||||
|                          3 = BEGINNING OF VOLUME SCAN. | ||||
|                          4 = END OF VOLUME SCAN. | ||||
| 
 | ||||
| 22        I*2       Elevation angle (coded:[Value/8.]*[180./4096.] = DEG). | ||||
|                     An elevation of "0 degree" is parallel to the pedestal | ||||
|                     base while "90 degrees" is perpendicular to the pedestal | ||||
|                     base. | ||||
| 
 | ||||
| 23        I*2       RDA elevation number within the volume scan. | ||||
| 
 | ||||
| 24        I*2       Range to first gate of reflectivity data (METERS). | ||||
|                     Range may be negative to account for system delays | ||||
|                     in transmitter and/or receiver components. | ||||
| 
 | ||||
| 25        I*2       Range to first gate of Doppler data. | ||||
|                     Doppler data - velocity and spectrum width (METERS). | ||||
|                     Range may be negative to account for system delays in | ||||
|                     transmitter and/or receiver components. | ||||
| 
 | ||||
| 26        I*2       Reflectivity data gate size (METERS). | ||||
| 
 | ||||
| 27        I*2       Doppler data gate size (METERS). | ||||
| 
 | ||||
| 28        I*2       Number of reflectivity gates. | ||||
| 
 | ||||
| 29        I*2       Number of velocity and/or spectrum width data gates. | ||||
| 
 | ||||
| 30        I*2       Sector number within cut. | ||||
| 
 | ||||
| 31-32     R*4       System gain calibration constant (dB biased). | ||||
| 
 | ||||
| 33        I*2       Reflectivity data pointer (byte # from the start of | ||||
|                     digital radar data message header).  This pointer | ||||
|                     locates the beginning of reflectivity data. | ||||
| 
 | ||||
| 34        I*2       Velocity data pointer (byte # from the start of digital | ||||
|                     radar data message header).  This pointer locates | ||||
|                     beginning of velocity data. | ||||
| 
 | ||||
| 35        I*2       Spectrum-width pointer (byte # from the start of | ||||
|                     digital radar data message header).  This pointer | ||||
|                     locates beginning of spectrum-width data. | ||||
| 
 | ||||
| 36        I*2       Doppler velocity resolution. | ||||
|                          Value of:      2 = 0.5 m/s | ||||
|                                         4 = 1.0 | ||||
| 
 | ||||
| 37        I*2       Volume coverage pattern. | ||||
|                          Value of:   11 = 16 elev. scans/ 5 mins. | ||||
|                                      21 = 11 elev. scans/ 6 mins. | ||||
|                                      31 = 8 elev. scans/ 10 mins. | ||||
|                                      32 = 7 elev. scans/ 10 mins. | ||||
| 
 | ||||
| 38-41               Unused.  Reserved for V&V Simulator. | ||||
| 
 | ||||
| 42        I*2       Reflectivity data pointer for Archive II playback. | ||||
|                     Archive II playback pointer used exclusively by RDA. | ||||
| 
 | ||||
| 43        I*2       Velocity data pointer for Archive II playback. | ||||
|                     Archive II playback pointer used exclusively by RDA. | ||||
| 
 | ||||
| 44        I*2       Spectrum-width data pointer for Archive II playback. | ||||
|                     Archive II playback pointer used exclusively by RDA. | ||||
| 
 | ||||
| 45        I*2       Nyquist velocity (scaled: Value/100. = M/S). | ||||
| 
 | ||||
| 46        I*2       Atmospheric attenuation factor (scaled: | ||||
|                     [Value/1000. = dB/KM]). | ||||
| 
 | ||||
| 47        I*2       Threshold parameter for minimum difference in echo | ||||
|                     power between two resolution volumes for them not | ||||
|                     to be labeled range ambiguous (i.e.,overlaid) | ||||
|                     [Value/10. = Watts]. | ||||
| 
 | ||||
| 48-64               Unused. | ||||
| 
 | ||||
|      Bytes 128-2431 (halfwords 65-1216)    Base Data: | ||||
| 
 | ||||
| 0000 0000 0980 0000 0002 0000 04B8 0001    This information includes the three | ||||
| 0060 1E9E 04B0 1841 0001 0001 0480 14A2    base data moments; reflectivity, | ||||
| 1E9E 1234 6530 0059 0001 0058 0001 0000    velocity and spectrum width. | ||||
| FE89 03E8 00FA 01CC 0000 0001 4180 69E8    Depending on the collection method, | ||||
| 0064 0000 0000 0000 0015 0000 0000 0000    up to three base data moments may | ||||
| 0000 0064 0000 0000 0000 FFF4 0064 0000    exist in this section of the | ||||
| 0000 0000 0000 0000 0000 0000 0000 0000    packet. (For this example, only | ||||
| 0000 0000 0000 0000 0000 0000 0000 0000    reflectivity is present.) Base data | ||||
| 005A 5A00 0070 6D51 6455 6060 4F54 0040    is coded and placed | ||||
| 5C3F 4049 4900 4D42 4349 434E 4B3D 4430    in a single byte and | ||||
| 4340 3F3D 4644 4443 3A3D 473F 3A3A 3D3D    is archived in the | ||||
| 3C45 3A43 433C 3E43 413C 393F 3F40 4038    following format: | ||||
|      (etc.) | ||||
| 
 | ||||
| Halfword Format    Description | ||||
| 
 | ||||
| 65-294    BYTE      Reflectivity data (0 - 460 gates) (coded: | ||||
|                     [((Value-2)/2.)-32. = dBZ], for Value of 0 or | ||||
|                     1 see note below). | ||||
| 
 | ||||
| 65-754    BYTE      Doppler velocity data (coded: for doppler velocity | ||||
|                     resolution of 0.5 M/S, [((Value-2)/2.)-63.5 = M/S]; | ||||
|                     for doppler resolution of 1.0 M/S, [(Value-2)-127.] | ||||
|                     = M/S], for Value of 0 or 1 see note below), (0 - 92 | ||||
|                     gates).  Starting data location depends on length of | ||||
|                     the reflectivity field, stop location depends on length | ||||
|                     of the velocity field.  Velocity data is range unambiguous | ||||
|                     out to 230 KM. | ||||
| 
 | ||||
| 65-1214   BYTE      Doppler spectrum width (coded: [((Value - 2)/2.)-63.5 | ||||
|                     = M/S], for Value of 0 or 1 see note below), (0 - 920 | ||||
|                     gates).  Starting data location depends on length of | ||||
|                     the reflectivity and velocity fields, stop location | ||||
|                     depends on length of the spectrum width field.  Spectrum | ||||
|                     width is range unambiguous out to 230 KM. | ||||
| 
 | ||||
|                     Four bytes of trailer characters referred to the Frame | ||||
|                     Check Sequence (FCS) follow the data.  In cases where | ||||
|                     the three moments are not all present or the number of | ||||
|                     gates for each moment have been reduced, the record is | ||||
|                     padded out to a constant size of 1216 halfwords (2432 | ||||
|                     bytes) following the trailer characters. | ||||
| 
 | ||||
| Note: | ||||
| 
 | ||||
| Any base data value of 0 is data below Signal to Noise Ratio(SNR) thresholds | ||||
| set for that specific base data. Any base data value of 1 is data considered | ||||
| range ambiguous (i.e., overlaid). | ||||
| ---------------------------------------------------------------------------- | ||||
| [Image] NEXRAD DOCUMENTATION | ||||
| 
 | ||||
| ---------------------------------------------------------------------------- | ||||
| http://www.ncdc.noaa.gov/pub/data/nexrad/tapeii.html | ||||
| Created by Dick Cram (dcram@ncdc.noaa.gov) | ||||
| Last updated 18 April 96 | ||||
|  | @ -1,7 +1,7 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <scwx/wsr88d/nexrad_file.hpp> | ||||
| #include <scwx/wsr88d/rda/digital_radar_data.hpp> | ||||
| #include <scwx/wsr88d/rda/generic_radar_data.hpp> | ||||
| #include <scwx/wsr88d/rda/volume_coverage_pattern_data.hpp> | ||||
| 
 | ||||
| #include <chrono> | ||||
|  | @ -26,21 +26,24 @@ public: | |||
|    explicit Ar2vFile(); | ||||
|    ~Ar2vFile(); | ||||
| 
 | ||||
|    Ar2vFile(const Ar2vFile&) = delete; | ||||
|    Ar2vFile(const Ar2vFile&)            = delete; | ||||
|    Ar2vFile& operator=(const Ar2vFile&) = delete; | ||||
| 
 | ||||
|    Ar2vFile(Ar2vFile&&) noexcept; | ||||
|    Ar2vFile& operator=(Ar2vFile&&) noexcept; | ||||
| 
 | ||||
|    uint32_t    julian_date() const; | ||||
|    uint32_t    milliseconds() const; | ||||
|    std::string icao() const; | ||||
|    std::uint32_t julian_date() const; | ||||
|    std::uint32_t milliseconds() const; | ||||
|    std::string   icao() const; | ||||
| 
 | ||||
|    std::size_t message_count() const; | ||||
| 
 | ||||
|    std::chrono::system_clock::time_point start_time() const; | ||||
|    std::chrono::system_clock::time_point end_time() const; | ||||
| 
 | ||||
|    std::map<uint16_t, std::shared_ptr<rda::ElevationScan>> radar_data() const; | ||||
|    std::shared_ptr<const rda::VolumeCoveragePatternData>   vcp_data() const; | ||||
|    std::map<std::uint16_t, std::shared_ptr<rda::ElevationScan>> | ||||
|                                                          radar_data() const; | ||||
|    std::shared_ptr<const rda::VolumeCoveragePatternData> vcp_data() const; | ||||
| 
 | ||||
|    std::tuple<std::shared_ptr<rda::ElevationScan>, float, std::vector<float>> | ||||
|    GetElevationScan(rda::DataBlockType                    dataBlockType, | ||||
|  |  | |||
|  | @ -1,7 +1,6 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <scwx/util/iterator.hpp> | ||||
| #include <scwx/wsr88d/rda/level2_message.hpp> | ||||
| #include <scwx/wsr88d/rda/generic_radar_data.hpp> | ||||
| 
 | ||||
| namespace scwx | ||||
| { | ||||
|  | @ -10,198 +9,52 @@ namespace wsr88d | |||
| namespace rda | ||||
| { | ||||
| 
 | ||||
| enum class DataBlockType | ||||
| { | ||||
|    Volume, | ||||
|    Elevation, | ||||
|    Radial, | ||||
|    MomentRef, | ||||
|    MomentVel, | ||||
|    MomentSw, | ||||
|    MomentZdr, | ||||
|    MomentPhi, | ||||
|    MomentRho, | ||||
|    MomentCfp, | ||||
|    Unknown | ||||
| }; | ||||
| typedef util:: | ||||
|    Iterator<DataBlockType, DataBlockType::MomentRef, DataBlockType::MomentCfp> | ||||
|       MomentDataBlockTypeIterator; | ||||
| 
 | ||||
| class DataBlockImpl; | ||||
| class ElevationDataBlockImpl; | ||||
| class MomentDataBlockImpl; | ||||
| class RadialDataBlockImpl; | ||||
| class VolumeDataBlockImpl; | ||||
| 
 | ||||
| class DigitalRadarData; | ||||
| class DigitalRadarDataImpl; | ||||
| 
 | ||||
| typedef std::map<uint16_t, std::shared_ptr<DigitalRadarData>> ElevationScan; | ||||
| 
 | ||||
| class DataBlock | ||||
| { | ||||
| protected: | ||||
|    explicit DataBlock(const std::string& dataBlockType, | ||||
|                       const std::string& dataName); | ||||
|    ~DataBlock(); | ||||
| 
 | ||||
|    DataBlock(const DataBlock&) = delete; | ||||
|    DataBlock& operator=(const DataBlock&) = delete; | ||||
| 
 | ||||
|    DataBlock(DataBlock&&) noexcept; | ||||
|    DataBlock& operator=(DataBlock&&) noexcept; | ||||
| 
 | ||||
| private: | ||||
|    std::unique_ptr<DataBlockImpl> p; | ||||
| }; | ||||
| 
 | ||||
| class ElevationDataBlock : public DataBlock | ||||
| { | ||||
| public: | ||||
|    explicit ElevationDataBlock(const std::string& dataBlockType, | ||||
|                                const std::string& dataName); | ||||
|    ~ElevationDataBlock(); | ||||
| 
 | ||||
|    ElevationDataBlock(const ElevationDataBlock&) = delete; | ||||
|    ElevationDataBlock& operator=(const ElevationDataBlock&) = delete; | ||||
| 
 | ||||
|    ElevationDataBlock(ElevationDataBlock&&) noexcept; | ||||
|    ElevationDataBlock& operator=(ElevationDataBlock&&) noexcept; | ||||
| 
 | ||||
|    static std::shared_ptr<ElevationDataBlock> | ||||
|    Create(const std::string& dataBlockType, | ||||
|           const std::string& dataName, | ||||
|           std::istream&      is); | ||||
| 
 | ||||
| private: | ||||
|    std::unique_ptr<ElevationDataBlockImpl> p; | ||||
| 
 | ||||
|    bool Parse(std::istream& is); | ||||
| }; | ||||
| 
 | ||||
| class MomentDataBlock : public DataBlock | ||||
| { | ||||
| public: | ||||
|    explicit MomentDataBlock(const std::string& dataBlockType, | ||||
|                             const std::string& dataName); | ||||
|    ~MomentDataBlock(); | ||||
| 
 | ||||
|    MomentDataBlock(const MomentDataBlock&) = delete; | ||||
|    MomentDataBlock& operator=(const MomentDataBlock&) = delete; | ||||
| 
 | ||||
|    MomentDataBlock(MomentDataBlock&&) noexcept; | ||||
|    MomentDataBlock& operator=(MomentDataBlock&&) noexcept; | ||||
| 
 | ||||
|    uint16_t    number_of_data_moment_gates() const; | ||||
|    float       data_moment_range() const; | ||||
|    uint16_t    data_moment_range_raw() const; | ||||
|    float       data_moment_range_sample_interval() const; | ||||
|    uint16_t    data_moment_range_sample_interval_raw() const; | ||||
|    float       snr_threshold() const; | ||||
|    int16_t     snr_threshold_raw() const; | ||||
|    uint8_t     data_word_size() const; | ||||
|    float       scale() const; | ||||
|    float       offset() const; | ||||
|    const void* data_moments() const; | ||||
| 
 | ||||
|    static std::shared_ptr<MomentDataBlock> | ||||
|    Create(const std::string& dataBlockType, | ||||
|           const std::string& dataName, | ||||
|           std::istream&      is); | ||||
| 
 | ||||
| private: | ||||
|    std::unique_ptr<MomentDataBlockImpl> p; | ||||
| 
 | ||||
|    bool Parse(std::istream& is); | ||||
| }; | ||||
| 
 | ||||
| class RadialDataBlock : public DataBlock | ||||
| { | ||||
| public: | ||||
|    explicit RadialDataBlock(const std::string& dataBlockType, | ||||
|                             const std::string& dataName); | ||||
|    ~RadialDataBlock(); | ||||
| 
 | ||||
|    RadialDataBlock(const RadialDataBlock&) = delete; | ||||
|    RadialDataBlock& operator=(const RadialDataBlock&) = delete; | ||||
| 
 | ||||
|    RadialDataBlock(RadialDataBlock&&) noexcept; | ||||
|    RadialDataBlock& operator=(RadialDataBlock&&) noexcept; | ||||
| 
 | ||||
|    float unambiguous_range() const; | ||||
| 
 | ||||
|    static std::shared_ptr<RadialDataBlock> | ||||
|    Create(const std::string& dataBlockType, | ||||
|           const std::string& dataName, | ||||
|           std::istream&      is); | ||||
| 
 | ||||
| private: | ||||
|    std::unique_ptr<RadialDataBlockImpl> p; | ||||
| 
 | ||||
|    bool Parse(std::istream& is); | ||||
| }; | ||||
| 
 | ||||
| class VolumeDataBlock : public DataBlock | ||||
| { | ||||
| public: | ||||
|    explicit VolumeDataBlock(const std::string& dataBlockType, | ||||
|                             const std::string& dataName); | ||||
|    ~VolumeDataBlock(); | ||||
| 
 | ||||
|    VolumeDataBlock(const VolumeDataBlock&) = delete; | ||||
|    VolumeDataBlock& operator=(const VolumeDataBlock&) = delete; | ||||
| 
 | ||||
|    VolumeDataBlock(VolumeDataBlock&&) noexcept; | ||||
|    VolumeDataBlock& operator=(VolumeDataBlock&&) noexcept; | ||||
| 
 | ||||
|    float    latitude() const; | ||||
|    float    longitude() const; | ||||
|    uint16_t volume_coverage_pattern_number() const; | ||||
| 
 | ||||
|    static std::shared_ptr<VolumeDataBlock> | ||||
|    Create(const std::string& dataBlockType, | ||||
|           const std::string& dataName, | ||||
|           std::istream&      is); | ||||
| 
 | ||||
| private: | ||||
|    std::unique_ptr<VolumeDataBlockImpl> p; | ||||
| 
 | ||||
|    bool Parse(std::istream& is); | ||||
| }; | ||||
| 
 | ||||
| class DigitalRadarData : public Level2Message | ||||
| class DigitalRadarData : public GenericRadarData | ||||
| { | ||||
| public: | ||||
|    explicit DigitalRadarData(); | ||||
|    ~DigitalRadarData(); | ||||
| 
 | ||||
|    DigitalRadarData(const DigitalRadarData&) = delete; | ||||
|    DigitalRadarData(const DigitalRadarData&)            = delete; | ||||
|    DigitalRadarData& operator=(const DigitalRadarData&) = delete; | ||||
| 
 | ||||
|    DigitalRadarData(DigitalRadarData&&) noexcept; | ||||
|    DigitalRadarData& operator=(DigitalRadarData&&) noexcept; | ||||
| 
 | ||||
|    std::string radar_identifier() const; | ||||
|    uint32_t    collection_time() const; | ||||
|    uint16_t    modified_julian_date() const; | ||||
|    uint16_t    azimuth_number() const; | ||||
|    float       azimuth_angle() const; | ||||
|    uint8_t     compression_indicator() const; | ||||
|    uint16_t    radial_length() const; | ||||
|    uint8_t     azimuth_resolution_spacing() const; | ||||
|    uint8_t     radial_status() const; | ||||
|    uint8_t     elevation_number() const; | ||||
|    uint8_t     cut_sector_number() const; | ||||
|    float       elevation_angle() const; | ||||
|    uint8_t     radial_spot_blanking_status() const; | ||||
|    uint8_t     azimuth_indexing_mode() const; | ||||
|    uint16_t    data_block_count() const; | ||||
|    std::uint32_t            collection_time() const; | ||||
|    std::uint16_t            modified_julian_date() const; | ||||
|    std::uint16_t            unambiguous_range() const; | ||||
|    std::uint16_t            azimuth_angle_raw() const; | ||||
|    units::degrees<float>    azimuth_angle() const; | ||||
|    std::uint16_t            azimuth_number() const; | ||||
|    std::uint16_t            radial_status() const; | ||||
|    std::uint16_t            elevation_angle_raw() const; | ||||
|    units::degrees<float>    elevation_angle() const; | ||||
|    std::uint16_t            elevation_number() const; | ||||
|    std::int16_t             surveillance_range_raw() const; | ||||
|    units::kilometers<float> surveillance_range() const; | ||||
|    std::int16_t             doppler_range_raw() const; | ||||
|    units::kilometers<float> doppler_range() const; | ||||
|    std::uint16_t            surveillance_range_sample_interval_raw() const; | ||||
|    units::kilometers<float> surveillance_range_sample_interval() const; | ||||
|    std::uint16_t            doppler_range_sample_interval_raw() const; | ||||
|    units::kilometers<float> doppler_range_sample_interval() const; | ||||
|    std::uint16_t            number_of_surveillance_bins() const; | ||||
|    std::uint16_t            number_of_doppler_bins() const; | ||||
|    std::uint16_t            cut_sector_number() const; | ||||
|    float                    calibration_constant() const; | ||||
|    std::uint16_t            surveillance_pointer() const; | ||||
|    std::uint16_t            velocity_pointer() const; | ||||
|    std::uint16_t            spectral_width_pointer() const; | ||||
|    std::uint16_t            doppler_velocity_resolution() const; | ||||
|    std::uint16_t            volume_coverage_pattern_number() const; | ||||
|    std::uint16_t            nyquist_velocity() const; | ||||
|    std::uint16_t            atmos() const; | ||||
|    std::uint16_t            tover() const; | ||||
|    std::uint16_t            radial_spot_blanking_status() const; | ||||
| 
 | ||||
|    std::shared_ptr<ElevationDataBlock> elevation_data_block() const; | ||||
|    std::shared_ptr<RadialDataBlock>    radial_data_block() const; | ||||
|    std::shared_ptr<VolumeDataBlock>    volume_data_block() const; | ||||
|    std::shared_ptr<MomentDataBlock> moment_data_block(DataBlockType type) const; | ||||
|    std::shared_ptr<GenericRadarData::MomentDataBlock> | ||||
|    moment_data_block(DataBlockType type) const; | ||||
| 
 | ||||
|    bool Parse(std::istream& is); | ||||
| 
 | ||||
|  | @ -209,7 +62,8 @@ public: | |||
|                                                    std::istream&         is); | ||||
| 
 | ||||
| private: | ||||
|    std::unique_ptr<DigitalRadarDataImpl> p; | ||||
|    class Impl; | ||||
|    std::unique_ptr<Impl> p; | ||||
| }; | ||||
| 
 | ||||
| } // namespace rda
 | ||||
|  |  | |||
							
								
								
									
										203
									
								
								wxdata/include/scwx/wsr88d/rda/digital_radar_data_generic.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										203
									
								
								wxdata/include/scwx/wsr88d/rda/digital_radar_data_generic.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,203 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <scwx/wsr88d/rda/generic_radar_data.hpp> | ||||
| 
 | ||||
| namespace scwx | ||||
| { | ||||
| namespace wsr88d | ||||
| { | ||||
| namespace rda | ||||
| { | ||||
| 
 | ||||
| class DigitalRadarDataGeneric : public GenericRadarData | ||||
| { | ||||
| public: | ||||
|    class DataBlock; | ||||
|    class ElevationDataBlock; | ||||
|    class MomentDataBlock; | ||||
|    class RadialDataBlock; | ||||
|    class VolumeDataBlock; | ||||
| 
 | ||||
|    explicit DigitalRadarDataGeneric(); | ||||
|    ~DigitalRadarDataGeneric(); | ||||
| 
 | ||||
|    DigitalRadarDataGeneric(const DigitalRadarDataGeneric&)            = delete; | ||||
|    DigitalRadarDataGeneric& operator=(const DigitalRadarDataGeneric&) = delete; | ||||
| 
 | ||||
|    DigitalRadarDataGeneric(DigitalRadarDataGeneric&&) noexcept; | ||||
|    DigitalRadarDataGeneric& operator=(DigitalRadarDataGeneric&&) noexcept; | ||||
| 
 | ||||
|    std::string           radar_identifier() const; | ||||
|    std::uint32_t         collection_time() const; | ||||
|    std::uint16_t         modified_julian_date() const; | ||||
|    std::uint16_t         azimuth_number() const; | ||||
|    units::degrees<float> azimuth_angle() const; | ||||
|    std::uint8_t          compression_indicator() const; | ||||
|    std::uint16_t         radial_length() const; | ||||
|    std::uint8_t          azimuth_resolution_spacing() const; | ||||
|    std::uint8_t          radial_status() const; | ||||
|    std::uint16_t         elevation_number() const; | ||||
|    std::uint8_t          cut_sector_number() const; | ||||
|    units::degrees<float> elevation_angle() const; | ||||
|    std::uint8_t          radial_spot_blanking_status() const; | ||||
|    std::uint8_t          azimuth_indexing_mode() const; | ||||
|    std::uint16_t         data_block_count() const; | ||||
|    std::uint16_t         volume_coverage_pattern_number() const; | ||||
| 
 | ||||
|    std::shared_ptr<ElevationDataBlock> elevation_data_block() const; | ||||
|    std::shared_ptr<RadialDataBlock>    radial_data_block() const; | ||||
|    std::shared_ptr<VolumeDataBlock>    volume_data_block() const; | ||||
|    std::shared_ptr<GenericRadarData::MomentDataBlock> | ||||
|    moment_data_block(DataBlockType type) const; | ||||
| 
 | ||||
|    bool Parse(std::istream& is); | ||||
| 
 | ||||
|    static std::shared_ptr<DigitalRadarDataGeneric> | ||||
|    Create(Level2MessageHeader&& header, std::istream& is); | ||||
| 
 | ||||
| private: | ||||
|    class Impl; | ||||
|    std::unique_ptr<Impl> p; | ||||
| }; | ||||
| 
 | ||||
| class DigitalRadarDataGeneric::DataBlock | ||||
| { | ||||
| protected: | ||||
|    explicit DataBlock(const std::string& dataBlockType, | ||||
|                       const std::string& dataName); | ||||
|    virtual ~DataBlock(); | ||||
| 
 | ||||
|    DataBlock(const DataBlock&)            = delete; | ||||
|    DataBlock& operator=(const DataBlock&) = delete; | ||||
| 
 | ||||
|    DataBlock(DataBlock&&) noexcept; | ||||
|    DataBlock& operator=(DataBlock&&) noexcept; | ||||
| 
 | ||||
| private: | ||||
|    class Impl; | ||||
|    std::unique_ptr<Impl> p; | ||||
| }; | ||||
| 
 | ||||
| class DigitalRadarDataGeneric::ElevationDataBlock : public DataBlock | ||||
| { | ||||
| public: | ||||
|    explicit ElevationDataBlock(const std::string& dataBlockType, | ||||
|                                const std::string& dataName); | ||||
|    ~ElevationDataBlock(); | ||||
| 
 | ||||
|    ElevationDataBlock(const ElevationDataBlock&)            = delete; | ||||
|    ElevationDataBlock& operator=(const ElevationDataBlock&) = delete; | ||||
| 
 | ||||
|    ElevationDataBlock(ElevationDataBlock&&) noexcept; | ||||
|    ElevationDataBlock& operator=(ElevationDataBlock&&) noexcept; | ||||
| 
 | ||||
|    static std::shared_ptr<ElevationDataBlock> | ||||
|    Create(const std::string& dataBlockType, | ||||
|           const std::string& dataName, | ||||
|           std::istream&      is); | ||||
| 
 | ||||
| private: | ||||
|    class Impl; | ||||
|    std::unique_ptr<Impl> p; | ||||
| 
 | ||||
|    bool Parse(std::istream& is); | ||||
| }; | ||||
| 
 | ||||
| class DigitalRadarDataGeneric::MomentDataBlock : | ||||
|     public DataBlock, | ||||
|     public GenericRadarData::MomentDataBlock | ||||
| { | ||||
| public: | ||||
|    explicit MomentDataBlock(const std::string& dataBlockType, | ||||
|                             const std::string& dataName); | ||||
|    ~MomentDataBlock(); | ||||
| 
 | ||||
|    MomentDataBlock(const MomentDataBlock&)            = delete; | ||||
|    MomentDataBlock& operator=(const MomentDataBlock&) = delete; | ||||
| 
 | ||||
|    MomentDataBlock(MomentDataBlock&&) noexcept; | ||||
|    MomentDataBlock& operator=(MomentDataBlock&&) noexcept; | ||||
| 
 | ||||
|    std::uint16_t            number_of_data_moment_gates() const; | ||||
|    units::kilometers<float> data_moment_range() const; | ||||
|    std::int16_t             data_moment_range_raw() const; | ||||
|    units::kilometers<float> data_moment_range_sample_interval() const; | ||||
|    std::uint16_t            data_moment_range_sample_interval_raw() const; | ||||
|    float                    snr_threshold() const; | ||||
|    std::int16_t             snr_threshold_raw() const; | ||||
|    std::uint8_t             data_word_size() const; | ||||
|    float                    scale() const; | ||||
|    float                    offset() const; | ||||
|    const void*              data_moments() const; | ||||
| 
 | ||||
|    static std::shared_ptr<MomentDataBlock> | ||||
|    Create(const std::string& dataBlockType, | ||||
|           const std::string& dataName, | ||||
|           std::istream&      is); | ||||
| 
 | ||||
| private: | ||||
|    class Impl; | ||||
|    std::unique_ptr<Impl> p; | ||||
| 
 | ||||
|    bool Parse(std::istream& is); | ||||
| }; | ||||
| 
 | ||||
| class DigitalRadarDataGeneric::RadialDataBlock : public DataBlock | ||||
| { | ||||
| public: | ||||
|    explicit RadialDataBlock(const std::string& dataBlockType, | ||||
|                             const std::string& dataName); | ||||
|    ~RadialDataBlock(); | ||||
| 
 | ||||
|    RadialDataBlock(const RadialDataBlock&)            = delete; | ||||
|    RadialDataBlock& operator=(const RadialDataBlock&) = delete; | ||||
| 
 | ||||
|    RadialDataBlock(RadialDataBlock&&) noexcept; | ||||
|    RadialDataBlock& operator=(RadialDataBlock&&) noexcept; | ||||
| 
 | ||||
|    float unambiguous_range() const; | ||||
| 
 | ||||
|    static std::shared_ptr<RadialDataBlock> | ||||
|    Create(const std::string& dataBlockType, | ||||
|           const std::string& dataName, | ||||
|           std::istream&      is); | ||||
| 
 | ||||
| private: | ||||
|    class Impl; | ||||
|    std::unique_ptr<Impl> p; | ||||
| 
 | ||||
|    bool Parse(std::istream& is); | ||||
| }; | ||||
| 
 | ||||
| class DigitalRadarDataGeneric::VolumeDataBlock : public DataBlock | ||||
| { | ||||
| public: | ||||
|    explicit VolumeDataBlock(const std::string& dataBlockType, | ||||
|                             const std::string& dataName); | ||||
|    ~VolumeDataBlock(); | ||||
| 
 | ||||
|    VolumeDataBlock(const VolumeDataBlock&)            = delete; | ||||
|    VolumeDataBlock& operator=(const VolumeDataBlock&) = delete; | ||||
| 
 | ||||
|    VolumeDataBlock(VolumeDataBlock&&) noexcept; | ||||
|    VolumeDataBlock& operator=(VolumeDataBlock&&) noexcept; | ||||
| 
 | ||||
|    float         latitude() const; | ||||
|    float         longitude() const; | ||||
|    std::uint16_t volume_coverage_pattern_number() const; | ||||
| 
 | ||||
|    static std::shared_ptr<VolumeDataBlock> | ||||
|    Create(const std::string& dataBlockType, | ||||
|           const std::string& dataName, | ||||
|           std::istream&      is); | ||||
| 
 | ||||
| private: | ||||
|    class Impl; | ||||
|    std::unique_ptr<Impl> p; | ||||
| 
 | ||||
|    bool Parse(std::istream& is); | ||||
| }; | ||||
| 
 | ||||
| } // namespace rda
 | ||||
| } // namespace wsr88d
 | ||||
| } // namespace scwx
 | ||||
							
								
								
									
										99
									
								
								wxdata/include/scwx/wsr88d/rda/generic_radar_data.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								wxdata/include/scwx/wsr88d/rda/generic_radar_data.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,99 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <scwx/util/iterator.hpp> | ||||
| #include <scwx/wsr88d/rda/level2_message.hpp> | ||||
| 
 | ||||
| #include <units/angle.h> | ||||
| #include <units/length.h> | ||||
| 
 | ||||
| namespace scwx | ||||
| { | ||||
| namespace wsr88d | ||||
| { | ||||
| namespace rda | ||||
| { | ||||
| 
 | ||||
| enum class DataBlockType | ||||
| { | ||||
|    Volume, | ||||
|    Elevation, | ||||
|    Radial, | ||||
|    MomentRef, | ||||
|    MomentVel, | ||||
|    MomentSw, | ||||
|    MomentZdr, | ||||
|    MomentPhi, | ||||
|    MomentRho, | ||||
|    MomentCfp, | ||||
|    Unknown | ||||
| }; | ||||
| typedef util:: | ||||
|    Iterator<DataBlockType, DataBlockType::MomentRef, DataBlockType::MomentCfp> | ||||
|       MomentDataBlockTypeIterator; | ||||
| 
 | ||||
| class GenericRadarData; | ||||
| 
 | ||||
| typedef std::map<std::uint16_t, std::shared_ptr<GenericRadarData>> | ||||
|    ElevationScan; | ||||
| 
 | ||||
| class GenericRadarData : public Level2Message | ||||
| { | ||||
| public: | ||||
|    class MomentDataBlock; | ||||
| 
 | ||||
|    explicit GenericRadarData(); | ||||
|    virtual ~GenericRadarData(); | ||||
| 
 | ||||
|    GenericRadarData(const GenericRadarData&)            = delete; | ||||
|    GenericRadarData& operator=(const GenericRadarData&) = delete; | ||||
| 
 | ||||
|    GenericRadarData(GenericRadarData&&) noexcept; | ||||
|    GenericRadarData& operator=(GenericRadarData&&) noexcept; | ||||
| 
 | ||||
|    virtual std::uint32_t         collection_time() const                = 0; | ||||
|    virtual std::uint16_t         modified_julian_date() const           = 0; | ||||
|    virtual units::degrees<float> azimuth_angle() const                  = 0; | ||||
|    virtual std::uint16_t         azimuth_number() const                 = 0; | ||||
|    virtual std::uint16_t         elevation_number() const               = 0; | ||||
|    virtual std::uint16_t         volume_coverage_pattern_number() const = 0; | ||||
| 
 | ||||
|    virtual std::shared_ptr<MomentDataBlock> | ||||
|    moment_data_block(DataBlockType type) const = 0; | ||||
| 
 | ||||
| private: | ||||
|    class Impl; | ||||
|    std::unique_ptr<Impl> p; | ||||
| }; | ||||
| 
 | ||||
| class GenericRadarData::MomentDataBlock | ||||
| { | ||||
| public: | ||||
|    explicit MomentDataBlock(); | ||||
|    virtual ~MomentDataBlock(); | ||||
| 
 | ||||
|    MomentDataBlock(const MomentDataBlock&)            = delete; | ||||
|    MomentDataBlock& operator=(const MomentDataBlock&) = delete; | ||||
| 
 | ||||
|    MomentDataBlock(MomentDataBlock&&) noexcept; | ||||
|    MomentDataBlock& operator=(MomentDataBlock&&) noexcept; | ||||
| 
 | ||||
|    virtual std::uint16_t            number_of_data_moment_gates() const = 0; | ||||
|    virtual units::kilometers<float> data_moment_range() const           = 0; | ||||
|    virtual std::int16_t             data_moment_range_raw() const       = 0; | ||||
|    virtual units::kilometers<float> | ||||
|                          data_moment_range_sample_interval() const     = 0; | ||||
|    virtual std::uint16_t data_moment_range_sample_interval_raw() const = 0; | ||||
|    virtual std::int16_t  snr_threshold_raw() const                     = 0; | ||||
|    virtual std::uint8_t  data_word_size() const                        = 0; | ||||
|    virtual float         scale() const                                 = 0; | ||||
|    virtual float         offset() const                                = 0; | ||||
|    virtual const void*   data_moments() const                          = 0; | ||||
| 
 | ||||
| private: | ||||
|    class Impl; | ||||
|    std::unique_ptr<Impl> p; | ||||
| }; | ||||
| 
 | ||||
| } // namespace rda
 | ||||
| } // namespace wsr88d
 | ||||
| } // namespace scwx
 | ||||
|  | @ -27,18 +27,18 @@ private: | |||
|    explicit Level2MessageFactory() = delete; | ||||
|    ~Level2MessageFactory()         = delete; | ||||
| 
 | ||||
|    Level2MessageFactory(const Level2MessageFactory&) = delete; | ||||
|    Level2MessageFactory(const Level2MessageFactory&)            = delete; | ||||
|    Level2MessageFactory& operator=(const Level2MessageFactory&) = delete; | ||||
| 
 | ||||
|    Level2MessageFactory(Level2MessageFactory&&) noexcept = delete; | ||||
|    Level2MessageFactory(Level2MessageFactory&&) noexcept            = delete; | ||||
|    Level2MessageFactory& operator=(Level2MessageFactory&&) noexcept = delete; | ||||
| 
 | ||||
| public: | ||||
|    struct Context; | ||||
| 
 | ||||
|    static std::shared_ptr<Context> CreateContext(); | ||||
|    static Level2MessageInfo        Create(std::istream&            is, | ||||
|                                           std::shared_ptr<Context> ctx); | ||||
|    static Level2MessageInfo        Create(std::istream&             is, | ||||
|                                           std::shared_ptr<Context>& ctx); | ||||
| }; | ||||
| 
 | ||||
| } // namespace rda
 | ||||
|  |  | |||
|  | @ -7,14 +7,15 @@ namespace wsr88d | |||
| namespace rda | ||||
| { | ||||
| 
 | ||||
| enum class MessageId : uint8_t | ||||
| enum class MessageId : std::uint8_t | ||||
| { | ||||
|    DigitalRadarData           = 1, | ||||
|    RdaStatusData              = 2, | ||||
|    PerformanceMaintenanceData = 3, | ||||
|    VolumeCoveragePatternData  = 5, | ||||
|    ClutterFilterMap           = 15, | ||||
|    RdaAdaptationData          = 18, | ||||
|    DigitalRadarData           = 31 | ||||
|    DigitalRadarDataGeneric    = 31 | ||||
| }; | ||||
| 
 | ||||
| } // namespace rda
 | ||||
|  | @ -17,16 +17,23 @@ std::string GetVcpDescription(uint16_t vcp) | |||
|    case 31: | ||||
|    case 32: | ||||
|    case 35: | ||||
|    case 90: return CLEAR_AIR_MODE; | ||||
|    case 90: | ||||
|       return CLEAR_AIR_MODE; | ||||
| 
 | ||||
|    case 11: | ||||
|    case 12: | ||||
|    case 21: | ||||
|    case 80: | ||||
|    case 112: | ||||
|    case 121: | ||||
|    case 211: | ||||
|    case 212: | ||||
|    case 215: return PRECIPITATION_MODE; | ||||
|    case 215: | ||||
|    case 221: | ||||
|       return PRECIPITATION_MODE; | ||||
| 
 | ||||
|    default: return "?"; | ||||
|    default: | ||||
|       return "?"; | ||||
|    } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -246,7 +246,8 @@ AwsNexradDataProvider::ListObjects(std::chrono::system_clock::time_point date) | |||
|          { | ||||
|             std::string key = object.GetKey(); | ||||
| 
 | ||||
|             if (!key.ends_with("_MDM")) | ||||
|             if (key.find("NWS_NEXRAD_") == std::string::npos && | ||||
|                 !key.ends_with("_MDM")) | ||||
|             { | ||||
|                auto time = GetTimePointByKey(key); | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| #include <scwx/wsr88d/ar2v_file.hpp> | ||||
| #include <scwx/wsr88d/rda/digital_radar_data.hpp> | ||||
| #include <scwx/wsr88d/rda/level2_message_factory.hpp> | ||||
| #include <scwx/wsr88d/rda/types.hpp> | ||||
| #include <scwx/wsr88d/rda/rda_types.hpp> | ||||
| #include <scwx/util/logger.hpp> | ||||
| #include <scwx/util/rangebuf.hpp> | ||||
| #include <scwx/util/time.hpp> | ||||
|  | @ -18,6 +19,7 @@ | |||
| #   pragma GCC diagnostic ignored "-Wdeprecated-copy" | ||||
| #endif | ||||
| 
 | ||||
| #include <boost/algorithm/string/trim.hpp> | ||||
| #include <boost/iostreams/copy.hpp> | ||||
| #include <boost/iostreams/filtering_streambuf.hpp> | ||||
| #include <boost/iostreams/filter/bzip2.hpp> | ||||
|  | @ -41,16 +43,7 @@ static const auto        logger_    = util::Logger::Create(logPrefix_); | |||
| class Ar2vFileImpl | ||||
| { | ||||
| public: | ||||
|    explicit Ar2vFileImpl() : | ||||
|        tapeFilename_ {}, | ||||
|        extensionNumber_ {}, | ||||
|        julianDate_ {0}, | ||||
|        milliseconds_ {0}, | ||||
|        icao_ {}, | ||||
|        vcpData_ {nullptr}, | ||||
|        radarData_ {}, | ||||
|        index_ {}, | ||||
|        rawRecords_ {} {}; | ||||
|    explicit Ar2vFileImpl() {}; | ||||
|    ~Ar2vFileImpl() = default; | ||||
| 
 | ||||
|    std::size_t DecompressLDMRecords(std::istream& is); | ||||
|  | @ -58,22 +51,24 @@ public: | |||
|    void        IndexFile(); | ||||
|    void        ParseLDMRecords(); | ||||
|    void        ParseLDMRecord(std::istream& is); | ||||
|    void        ProcessRadarData(std::shared_ptr<rda::DigitalRadarData> message); | ||||
|    void ProcessRadarData(const std::shared_ptr<rda::GenericRadarData>& message); | ||||
| 
 | ||||
|    std::string   tapeFilename_; | ||||
|    std::string   extensionNumber_; | ||||
|    std::uint32_t julianDate_; | ||||
|    std::uint32_t milliseconds_; | ||||
|    std::string   icao_; | ||||
|    std::string   tapeFilename_ {}; | ||||
|    std::string   extensionNumber_ {}; | ||||
|    std::uint32_t julianDate_ {0}; | ||||
|    std::uint32_t milliseconds_ {0}; | ||||
|    std::string   icao_ {}; | ||||
| 
 | ||||
|    std::shared_ptr<rda::VolumeCoveragePatternData>              vcpData_; | ||||
|    std::map<std::uint16_t, std::shared_ptr<rda::ElevationScan>> radarData_; | ||||
|    std::size_t messageCount_ {0}; | ||||
| 
 | ||||
|    std::shared_ptr<rda::VolumeCoveragePatternData>              vcpData_ {}; | ||||
|    std::map<std::uint16_t, std::shared_ptr<rda::ElevationScan>> radarData_ {}; | ||||
| 
 | ||||
|    std::map<rda::DataBlockType, | ||||
|             std::map<std::uint16_t, std::shared_ptr<rda::ElevationScan>>> | ||||
|       index_; | ||||
|       index_ {}; | ||||
| 
 | ||||
|    std::list<std::stringstream> rawRecords_; | ||||
|    std::list<std::stringstream> rawRecords_ {}; | ||||
| }; | ||||
| 
 | ||||
| Ar2vFile::Ar2vFile() : p(std::make_unique<Ar2vFileImpl>()) {} | ||||
|  | @ -82,12 +77,12 @@ Ar2vFile::~Ar2vFile() = default; | |||
| Ar2vFile::Ar2vFile(Ar2vFile&&) noexcept            = default; | ||||
| Ar2vFile& Ar2vFile::operator=(Ar2vFile&&) noexcept = default; | ||||
| 
 | ||||
| uint32_t Ar2vFile::julian_date() const | ||||
| std::uint32_t Ar2vFile::julian_date() const | ||||
| { | ||||
|    return p->julianDate_; | ||||
| } | ||||
| 
 | ||||
| uint32_t Ar2vFile::milliseconds() const | ||||
| std::uint32_t Ar2vFile::milliseconds() const | ||||
| { | ||||
|    return p->milliseconds_; | ||||
| } | ||||
|  | @ -97,6 +92,11 @@ std::string Ar2vFile::icao() const | |||
|    return p->icao_; | ||||
| } | ||||
| 
 | ||||
| std::size_t Ar2vFile::message_count() const | ||||
| { | ||||
|    return p->messageCount_; | ||||
| } | ||||
| 
 | ||||
| std::chrono::system_clock::time_point Ar2vFile::start_time() const | ||||
| { | ||||
|    return util::TimePoint(p->julianDate_, p->milliseconds_); | ||||
|  | @ -108,7 +108,7 @@ std::chrono::system_clock::time_point Ar2vFile::end_time() const | |||
| 
 | ||||
|    if (p->radarData_.size() > 0) | ||||
|    { | ||||
|       std::shared_ptr<rda::DigitalRadarData> lastRadial = | ||||
|       std::shared_ptr<rda::GenericRadarData> lastRadial = | ||||
|          p->radarData_.crbegin()->second->crbegin()->second; | ||||
| 
 | ||||
|       endTime = util::TimePoint(lastRadial->modified_julian_date(), | ||||
|  | @ -118,7 +118,7 @@ std::chrono::system_clock::time_point Ar2vFile::end_time() const | |||
|    return endTime; | ||||
| } | ||||
| 
 | ||||
| std::map<uint16_t, std::shared_ptr<rda::ElevationScan>> | ||||
| std::map<std::uint16_t, std::shared_ptr<rda::ElevationScan>> | ||||
| Ar2vFile::radar_data() const | ||||
| { | ||||
|    return p->radarData_; | ||||
|  | @ -142,17 +142,17 @@ Ar2vFile::GetElevationScan(rda::DataBlockType dataBlockType, | |||
|    float                               elevationCut  = 0.0f; | ||||
|    std::vector<float>                  elevationCuts; | ||||
| 
 | ||||
|    uint16_t codedElevation = | ||||
|       static_cast<uint16_t>(std::lroundf(elevation * scaleFactor)); | ||||
|    std::uint16_t codedElevation = | ||||
|       static_cast<std::uint16_t>(std::lroundf(elevation * scaleFactor)); | ||||
| 
 | ||||
|    if (p->index_.contains(dataBlockType)) | ||||
|    { | ||||
|       auto scans = p->index_.at(dataBlockType); | ||||
|       auto& scans = p->index_.at(dataBlockType); | ||||
| 
 | ||||
|       uint16_t lowerBound = scans.cbegin()->first; | ||||
|       uint16_t upperBound = scans.crbegin()->first; | ||||
|       std::uint16_t lowerBound = scans.cbegin()->first; | ||||
|       std::uint16_t upperBound = scans.crbegin()->first; | ||||
| 
 | ||||
|       for (auto scan : scans) | ||||
|       for (auto& scan : scans) | ||||
|       { | ||||
|          if (scan.first > lowerBound && scan.first <= codedElevation) | ||||
|          { | ||||
|  | @ -166,10 +166,12 @@ Ar2vFile::GetElevationScan(rda::DataBlockType dataBlockType, | |||
|          elevationCuts.push_back(scan.first / scaleFactor); | ||||
|       } | ||||
| 
 | ||||
|       int32_t lowerDelta = std::abs(static_cast<int32_t>(codedElevation) - | ||||
|                                     static_cast<int32_t>(lowerBound)); | ||||
|       int32_t upperDelta = std::abs(static_cast<int32_t>(codedElevation) - | ||||
|                                     static_cast<int32_t>(upperBound)); | ||||
|       std::int32_t lowerDelta = | ||||
|          std::abs(static_cast<std::int32_t>(codedElevation) - | ||||
|                   static_cast<std::int32_t>(lowerBound)); | ||||
|       std::int32_t upperDelta = | ||||
|          std::abs(static_cast<std::int32_t>(codedElevation) - | ||||
|                   static_cast<std::int32_t>(upperBound)); | ||||
| 
 | ||||
|       if (lowerDelta < upperDelta) | ||||
|       { | ||||
|  | @ -232,6 +234,10 @@ bool Ar2vFile::LoadData(std::istream& is) | |||
|       dataValid = false; | ||||
|    } | ||||
| 
 | ||||
|    // Trim spaces and null characters from the end of the ICAO
 | ||||
|    boost::trim_right_if(p->icao_, | ||||
|                         [](char x) { return std::isspace(x) || x == '\0'; }); | ||||
| 
 | ||||
|    if (dataValid) | ||||
|    { | ||||
|       logger_->debug("Filename:  {}", p->tapeFilename_); | ||||
|  | @ -256,17 +262,17 @@ bool Ar2vFile::LoadData(std::istream& is) | |||
|    return dataValid; | ||||
| } | ||||
| 
 | ||||
| size_t Ar2vFileImpl::DecompressLDMRecords(std::istream& is) | ||||
| std::size_t Ar2vFileImpl::DecompressLDMRecords(std::istream& is) | ||||
| { | ||||
|    logger_->debug("Decompressing LDM Records"); | ||||
| 
 | ||||
|    size_t numRecords = 0; | ||||
|    std::size_t numRecords = 0; | ||||
| 
 | ||||
|    while (is.peek() != EOF) | ||||
|    { | ||||
|       std::streampos startPosition = is.tellg(); | ||||
|       int32_t        controlWord   = 0; | ||||
|       size_t         recordSize; | ||||
|       std::int32_t   controlWord   = 0; | ||||
|       std::size_t    recordSize; | ||||
| 
 | ||||
|       is.read(reinterpret_cast<char*>(&controlWord), 4); | ||||
| 
 | ||||
|  | @ -315,7 +321,7 @@ void Ar2vFileImpl::ParseLDMRecords() | |||
| { | ||||
|    logger_->debug("Parsing LDM Records"); | ||||
| 
 | ||||
|    size_t count = 0; | ||||
|    std::size_t count = 0; | ||||
| 
 | ||||
|    for (auto it = rawRecords_.begin(); it != rawRecords_.end(); it++) | ||||
|    { | ||||
|  | @ -331,65 +337,82 @@ void Ar2vFileImpl::ParseLDMRecords() | |||
| 
 | ||||
| void Ar2vFileImpl::ParseLDMRecord(std::istream& is) | ||||
| { | ||||
|    static constexpr std::size_t kDefaultSegmentSize = 2432; | ||||
|    static constexpr std::size_t kCtmHeaderSize      = 12; | ||||
| 
 | ||||
|    auto ctx = rda::Level2MessageFactory::CreateContext(); | ||||
| 
 | ||||
|    // The communications manager inserts an extra 12 bytes at the beginning
 | ||||
|    // of each record
 | ||||
|    is.seekg(12, std::ios_base::cur); | ||||
| 
 | ||||
|    while (!is.eof()) | ||||
|    while (!is.eof() && !is.fail()) | ||||
|    { | ||||
|       off_t    offset   = 0; | ||||
|       uint16_t nextSize = 0u; | ||||
|       do | ||||
|       // The communications manager inserts an extra 12 bytes at the beginning
 | ||||
|       // of each record
 | ||||
|       is.seekg(kCtmHeaderSize, std::ios_base::cur); | ||||
| 
 | ||||
|       // Each message requires 2432 bytes of storage, with the exception of
 | ||||
|       // Message Types 29 and 31.
 | ||||
|       std::size_t messageSize = kDefaultSegmentSize - kCtmHeaderSize; | ||||
| 
 | ||||
|       // Mark current position
 | ||||
|       std::streampos messageStart = is.tellg(); | ||||
| 
 | ||||
|       // Parse the header
 | ||||
|       rda::Level2MessageHeader messageHeader; | ||||
|       bool                     headerValid = messageHeader.Parse(is); | ||||
|       is.seekg(messageStart, std::ios_base::beg); | ||||
| 
 | ||||
|       if (headerValid) | ||||
|       { | ||||
|          is.read(reinterpret_cast<char*>(&nextSize), 2); | ||||
|          if (nextSize == 0) | ||||
|          std::uint8_t messageType = messageHeader.message_type(); | ||||
| 
 | ||||
|          // Each message requires 2432 bytes of storage, with the exception of
 | ||||
|          // Message Types 29 and 31.
 | ||||
|          if (messageType == 29 || messageType == 31) | ||||
|          { | ||||
|             offset += 2; | ||||
|             if (messageHeader.message_size() == 65535) | ||||
|             { | ||||
|                messageSize = (static_cast<std::size_t>( | ||||
|                                  messageHeader.number_of_message_segments()) | ||||
|                               << 16) + | ||||
|                              messageHeader.message_segment_number(); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                messageSize = | ||||
|                   static_cast<std::size_t>(messageHeader.message_size()) * 2; | ||||
|             } | ||||
|          } | ||||
|          else | ||||
| 
 | ||||
|          // Parse the current message
 | ||||
|          rda::Level2MessageInfo msgInfo = | ||||
|             rda::Level2MessageFactory::Create(is, ctx); | ||||
| 
 | ||||
|          if (msgInfo.messageValid) | ||||
|          { | ||||
|             is.seekg(-2, std::ios_base::cur); | ||||
|             HandleMessage(msgInfo.message); | ||||
|          } | ||||
|       } while (!is.eof() && nextSize == 0u); | ||||
| 
 | ||||
|       if (!is.eof() && offset != 0) | ||||
|       { | ||||
|          logger_->trace("Next record offset by {} bytes", offset); | ||||
|       } | ||||
|       else if (is.eof()) | ||||
|       { | ||||
|          break; | ||||
|       } | ||||
| 
 | ||||
|       rda::Level2MessageInfo msgInfo = | ||||
|          rda::Level2MessageFactory::Create(is, ctx); | ||||
|       if (!msgInfo.headerValid) | ||||
|       { | ||||
|          // Invalid message
 | ||||
|          break; | ||||
|       } | ||||
| 
 | ||||
|       if (msgInfo.messageValid) | ||||
|       { | ||||
|          HandleMessage(msgInfo.message); | ||||
|       } | ||||
|       // Skip to next message
 | ||||
|       is.seekg(messageStart + static_cast<std::streampos>(messageSize), | ||||
|                std::ios_base::beg); | ||||
|    } | ||||
| } | ||||
| 
 | ||||
| void Ar2vFileImpl::HandleMessage(std::shared_ptr<rda::Level2Message>& message) | ||||
| { | ||||
|    ++messageCount_; | ||||
| 
 | ||||
|    switch (message->header().message_type()) | ||||
|    { | ||||
|    case static_cast<uint8_t>(rda::MessageId::VolumeCoveragePatternData): | ||||
|    case static_cast<std::uint8_t>(rda::MessageId::VolumeCoveragePatternData): | ||||
|       vcpData_ = | ||||
|          std::static_pointer_cast<rda::VolumeCoveragePatternData>(message); | ||||
|       break; | ||||
| 
 | ||||
|    case static_cast<uint8_t>(rda::MessageId::DigitalRadarData): | ||||
|    case static_cast<std::uint8_t>(rda::MessageId::DigitalRadarData): | ||||
|    case static_cast<std::uint8_t>(rda::MessageId::DigitalRadarDataGeneric): | ||||
|       ProcessRadarData( | ||||
|          std::static_pointer_cast<rda::DigitalRadarData>(message)); | ||||
|          std::static_pointer_cast<rda::GenericRadarData>(message)); | ||||
|       break; | ||||
| 
 | ||||
|    default: | ||||
|  | @ -398,10 +421,10 @@ void Ar2vFileImpl::HandleMessage(std::shared_ptr<rda::Level2Message>& message) | |||
| } | ||||
| 
 | ||||
| void Ar2vFileImpl::ProcessRadarData( | ||||
|    std::shared_ptr<rda::DigitalRadarData> message) | ||||
|    const std::shared_ptr<rda::GenericRadarData>& message) | ||||
| { | ||||
|    uint16_t azimuthIndex   = message->azimuth_number() - 1; | ||||
|    uint16_t elevationIndex = message->elevation_number() - 1; | ||||
|    std::uint16_t azimuthIndex   = message->azimuth_number() - 1; | ||||
|    std::uint16_t elevationIndex = message->elevation_number() - 1; | ||||
| 
 | ||||
|    if (radarData_[elevationIndex] == nullptr) | ||||
|    { | ||||
|  | @ -415,20 +438,12 @@ void Ar2vFileImpl::IndexFile() | |||
| { | ||||
|    logger_->debug("Indexing file"); | ||||
| 
 | ||||
|    if (vcpData_ == nullptr) | ||||
|    for (auto& elevationCut : radarData_) | ||||
|    { | ||||
|       logger_->warn("Cannot index file without VCP data"); | ||||
|       return; | ||||
|    } | ||||
|       std::uint16_t     elevationAngle {}; | ||||
|       rda::WaveformType waveformType = rda::WaveformType::Unknown; | ||||
| 
 | ||||
|    for (auto elevationCut : radarData_) | ||||
|    { | ||||
|       uint16_t elevationAngle = | ||||
|          vcpData_->elevation_angle_raw(elevationCut.first); | ||||
|       rda::WaveformType waveformType = | ||||
|          vcpData_->waveform_type(elevationCut.first); | ||||
| 
 | ||||
|       std::shared_ptr<rda::DigitalRadarData> radial0 = | ||||
|       std::shared_ptr<rda::GenericRadarData>& radial0 = | ||||
|          (*elevationCut.second)[0]; | ||||
| 
 | ||||
|       if (radial0 == nullptr) | ||||
|  | @ -437,6 +452,26 @@ void Ar2vFileImpl::IndexFile() | |||
|          continue; | ||||
|       } | ||||
| 
 | ||||
|       std::shared_ptr<rda::DigitalRadarData> digitalRadarData0 = nullptr; | ||||
| 
 | ||||
|       if (vcpData_ != nullptr) | ||||
|       { | ||||
|          elevationAngle = vcpData_->elevation_angle_raw(elevationCut.first); | ||||
|          waveformType   = vcpData_->waveform_type(elevationCut.first); | ||||
|       } | ||||
|       else if ((digitalRadarData0 = | ||||
|                    std::dynamic_pointer_cast<rda::DigitalRadarData>( | ||||
|                       (*elevationCut.second)[0])) != nullptr) | ||||
|       { | ||||
|          elevationAngle = digitalRadarData0->elevation_angle_raw(); | ||||
|       } | ||||
|       else | ||||
|       { | ||||
|          // Return here, because we should only have a single message type
 | ||||
|          logger_->warn("Cannot index file without VCP data"); | ||||
|          return; | ||||
|       } | ||||
| 
 | ||||
|       for (rda::DataBlockType dataBlockType : | ||||
|            rda::MomentDataBlockTypeIterator()) | ||||
|       { | ||||
|  |  | |||
|  | @ -70,9 +70,9 @@ std::shared_ptr<NexradFile> NexradFileFactory::Create(std::istream& is) | |||
|    std::string       buffer; | ||||
|    bool              dataValid; | ||||
| 
 | ||||
|    buffer.resize(4); | ||||
|    buffer.resize(8); | ||||
| 
 | ||||
|    is.read(buffer.data(), 4); | ||||
|    is.read(buffer.data(), 8); | ||||
|    dataValid = is.good(); | ||||
|    is.seekg(pisBegin, std::ios_base::beg); | ||||
| 
 | ||||
|  | @ -89,7 +89,7 @@ std::shared_ptr<NexradFile> NexradFileFactory::Create(std::istream& is) | |||
|          pis      = &ss; | ||||
|          pisBegin = ss.tellg(); | ||||
| 
 | ||||
|          ss.read(buffer.data(), 4); | ||||
|          ss.read(buffer.data(), 8); | ||||
|          dataValid = ss.good(); | ||||
|          ss.seekg(pisBegin, std::ios_base::beg); | ||||
| 
 | ||||
|  | @ -114,7 +114,7 @@ std::shared_ptr<NexradFile> NexradFileFactory::Create(std::istream& is) | |||
| 
 | ||||
|    if (dataValid) | ||||
|    { | ||||
|       if (buffer.starts_with("AR2V")) | ||||
|       if (buffer.starts_with("AR2V") || buffer.starts_with("ARCHIVE2")) | ||||
|       { | ||||
|          message = std::make_shared<Ar2vFile>(); | ||||
|       } | ||||
|  |  | |||
|  | @ -78,19 +78,22 @@ bool ClutterFilterBypassMap::Parse(std::istream& is) | |||
| 
 | ||||
|    if (p->mapGenerationDate_ < 1) | ||||
|    { | ||||
|       logger_->warn("Invalid date: {}", p->mapGenerationDate_); | ||||
|       logger_->trace("Ignoring empty message"); | ||||
|       messageValid = false; | ||||
|    } | ||||
|    if (p->mapGenerationTime_ > 1440) | ||||
|    else | ||||
|    { | ||||
|       logger_->warn("Invalid time: {}", p->mapGenerationTime_); | ||||
|       messageValid = false; | ||||
|    } | ||||
|    if (numElevationSegments < 1 || numElevationSegments > 5) | ||||
|    { | ||||
|       logger_->warn("Invalid number of elevation segments: {}", | ||||
|                     numElevationSegments); | ||||
|       messageValid = false; | ||||
|       if (p->mapGenerationTime_ > 1440) | ||||
|       { | ||||
|          logger_->warn("Invalid time: {}", p->mapGenerationTime_); | ||||
|          messageValid = false; | ||||
|       } | ||||
|       if (numElevationSegments < 1 || numElevationSegments > 5) | ||||
|       { | ||||
|          logger_->warn("Invalid number of elevation segments: {}", | ||||
|                        numElevationSegments); | ||||
|          messageValid = false; | ||||
|       } | ||||
|    } | ||||
| 
 | ||||
|    if (!messageValid) | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										776
									
								
								wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										776
									
								
								wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,776 @@ | |||
| #include <scwx/wsr88d/rda/digital_radar_data_generic.hpp> | ||||
| #include <scwx/util/logger.hpp> | ||||
| 
 | ||||
| namespace scwx | ||||
| { | ||||
| namespace wsr88d | ||||
| { | ||||
| namespace rda | ||||
| { | ||||
| 
 | ||||
| static const std::string logPrefix_ = | ||||
|    "scwx::wsr88d::rda::digital_radar_data_generic"; | ||||
| static const auto logger_ = util::Logger::Create(logPrefix_); | ||||
| 
 | ||||
| static const std::unordered_map<std::string, DataBlockType> strToDataBlock_ { | ||||
|    {"VOL", DataBlockType::Volume}, | ||||
|    {"ELV", DataBlockType::Elevation}, | ||||
|    {"RAD", DataBlockType::Radial}, | ||||
|    {"REF", DataBlockType::MomentRef}, | ||||
|    {"VEL", DataBlockType::MomentVel}, | ||||
|    {"SW ", DataBlockType::MomentSw}, | ||||
|    {"ZDR", DataBlockType::MomentZdr}, | ||||
|    {"PHI", DataBlockType::MomentPhi}, | ||||
|    {"RHO", DataBlockType::MomentRho}, | ||||
|    {"CFP", DataBlockType::MomentCfp}}; | ||||
| 
 | ||||
| class DigitalRadarDataGeneric::DataBlock::Impl | ||||
| { | ||||
| public: | ||||
|    explicit Impl(const std::string& dataBlockType, | ||||
|                  const std::string& dataName) : | ||||
|        dataBlockType_ {dataBlockType}, dataName_ {dataName} | ||||
|    { | ||||
|    } | ||||
| 
 | ||||
|    std::string dataBlockType_; | ||||
|    std::string dataName_; | ||||
| }; | ||||
| 
 | ||||
| DigitalRadarDataGeneric::DataBlock::DataBlock(const std::string& dataBlockType, | ||||
|                                               const std::string& dataName) : | ||||
|     p(std::make_unique<Impl>(dataBlockType, dataName)) | ||||
| { | ||||
| } | ||||
| DigitalRadarDataGeneric::DataBlock::~DataBlock() = default; | ||||
| 
 | ||||
| DigitalRadarDataGeneric::DataBlock::DataBlock(DataBlock&&) noexcept = default; | ||||
| DigitalRadarDataGeneric::DataBlock& | ||||
| DigitalRadarDataGeneric::DataBlock::operator=(DataBlock&&) noexcept = default; | ||||
| 
 | ||||
| class DigitalRadarDataGeneric::MomentDataBlock::Impl | ||||
| { | ||||
| public: | ||||
|    explicit Impl() {} | ||||
| 
 | ||||
|    std::uint16_t numberOfDataMomentGates_ {0}; | ||||
|    std::int16_t  dataMomentRange_ {0}; | ||||
|    std::uint16_t dataMomentRangeSampleInterval_ {0}; | ||||
|    std::uint16_t tover_ {0}; | ||||
|    std::int16_t  snrThreshold_ {0}; | ||||
|    std::uint8_t  controlFlags_ {0}; | ||||
|    std::uint8_t  dataWordSize_ {0}; | ||||
|    float         scale_ {0.0f}; | ||||
|    float         offset_ {0.0f}; | ||||
| 
 | ||||
|    std::vector<std::uint8_t>  momentGates8_ {}; | ||||
|    std::vector<std::uint16_t> momentGates16_ {}; | ||||
| }; | ||||
| 
 | ||||
| DigitalRadarDataGeneric::MomentDataBlock::MomentDataBlock( | ||||
|    const std::string& dataBlockType, const std::string& dataName) : | ||||
|     DataBlock(dataBlockType, dataName), p(std::make_unique<Impl>()) | ||||
| { | ||||
| } | ||||
| DigitalRadarDataGeneric::MomentDataBlock::~MomentDataBlock() = default; | ||||
| 
 | ||||
| DigitalRadarDataGeneric::MomentDataBlock::MomentDataBlock( | ||||
|    MomentDataBlock&&) noexcept = default; | ||||
| DigitalRadarDataGeneric::MomentDataBlock& | ||||
| DigitalRadarDataGeneric::MomentDataBlock::operator=( | ||||
|    MomentDataBlock&&) noexcept = default; | ||||
| 
 | ||||
| std::uint16_t | ||||
| DigitalRadarDataGeneric::MomentDataBlock::number_of_data_moment_gates() const | ||||
| { | ||||
|    return p->numberOfDataMomentGates_; | ||||
| } | ||||
| 
 | ||||
| units::kilometers<float> | ||||
| DigitalRadarDataGeneric::MomentDataBlock::data_moment_range() const | ||||
| { | ||||
|    return units::kilometers<float> {p->dataMomentRange_ * 0.001f}; | ||||
| } | ||||
| 
 | ||||
| std::int16_t | ||||
| DigitalRadarDataGeneric::MomentDataBlock::data_moment_range_raw() const | ||||
| { | ||||
|    return p->dataMomentRange_; | ||||
| } | ||||
| 
 | ||||
| units::kilometers<float> | ||||
| DigitalRadarDataGeneric::MomentDataBlock::data_moment_range_sample_interval() | ||||
|    const | ||||
| { | ||||
|    return units::kilometers<float> {p->dataMomentRangeSampleInterval_ * 0.001f}; | ||||
| } | ||||
| 
 | ||||
| std::uint16_t DigitalRadarDataGeneric::MomentDataBlock:: | ||||
|    data_moment_range_sample_interval_raw() const | ||||
| { | ||||
|    return p->dataMomentRangeSampleInterval_; | ||||
| } | ||||
| 
 | ||||
| float DigitalRadarDataGeneric::MomentDataBlock::snr_threshold() const | ||||
| { | ||||
|    return p->snrThreshold_ * 0.1f; | ||||
| } | ||||
| 
 | ||||
| std::int16_t DigitalRadarDataGeneric::MomentDataBlock::snr_threshold_raw() const | ||||
| { | ||||
|    return p->snrThreshold_; | ||||
| } | ||||
| 
 | ||||
| std::uint8_t DigitalRadarDataGeneric::MomentDataBlock::data_word_size() const | ||||
| { | ||||
|    return p->dataWordSize_; | ||||
| } | ||||
| 
 | ||||
| float DigitalRadarDataGeneric::MomentDataBlock::scale() const | ||||
| { | ||||
|    return p->scale_; | ||||
| } | ||||
| 
 | ||||
| float DigitalRadarDataGeneric::MomentDataBlock::offset() const | ||||
| { | ||||
|    return p->offset_; | ||||
| } | ||||
| 
 | ||||
| const void* DigitalRadarDataGeneric::MomentDataBlock::data_moments() const | ||||
| { | ||||
|    const void* dataMoments; | ||||
| 
 | ||||
|    switch (p->dataWordSize_) | ||||
|    { | ||||
|    case 8: | ||||
|       dataMoments = p->momentGates8_.data(); | ||||
|       break; | ||||
|    case 16: | ||||
|       dataMoments = p->momentGates16_.data(); | ||||
|       break; | ||||
|    default: | ||||
|       dataMoments = nullptr; | ||||
|       break; | ||||
|    } | ||||
| 
 | ||||
|    return dataMoments; | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<DigitalRadarDataGeneric::MomentDataBlock> | ||||
| DigitalRadarDataGeneric::MomentDataBlock::Create( | ||||
|    const std::string& dataBlockType, | ||||
|    const std::string& dataName, | ||||
|    std::istream&      is) | ||||
| { | ||||
|    std::shared_ptr<MomentDataBlock> p = | ||||
|       std::make_shared<MomentDataBlock>(dataBlockType, dataName); | ||||
| 
 | ||||
|    if (!p->Parse(is)) | ||||
|    { | ||||
|       p.reset(); | ||||
|    } | ||||
| 
 | ||||
|    return p; | ||||
| } | ||||
| 
 | ||||
| bool DigitalRadarDataGeneric::MomentDataBlock::Parse(std::istream& is) | ||||
| { | ||||
|    bool dataBlockValid = true; | ||||
| 
 | ||||
|    is.seekg(4, std::ios_base::cur);                                   // 4-7
 | ||||
|    is.read(reinterpret_cast<char*>(&p->numberOfDataMomentGates_), 2); // 8-9
 | ||||
|    is.read(reinterpret_cast<char*>(&p->dataMomentRange_), 2);         // 10-11
 | ||||
|    is.read(reinterpret_cast<char*>(&p->dataMomentRangeSampleInterval_), | ||||
|            2);                                             // 12-13
 | ||||
|    is.read(reinterpret_cast<char*>(&p->tover_), 2);        // 14-15
 | ||||
|    is.read(reinterpret_cast<char*>(&p->snrThreshold_), 2); // 16-17
 | ||||
|    is.read(reinterpret_cast<char*>(&p->controlFlags_), 1); // 18
 | ||||
|    is.read(reinterpret_cast<char*>(&p->dataWordSize_), 1); // 19
 | ||||
|    is.read(reinterpret_cast<char*>(&p->scale_), 4);        // 20-23
 | ||||
|    is.read(reinterpret_cast<char*>(&p->offset_), 4);       // 24-27
 | ||||
| 
 | ||||
|    p->numberOfDataMomentGates_       = ntohs(p->numberOfDataMomentGates_); | ||||
|    p->dataMomentRange_               = ntohs(p->dataMomentRange_); | ||||
|    p->dataMomentRangeSampleInterval_ = ntohs(p->dataMomentRangeSampleInterval_); | ||||
|    p->tover_                         = ntohs(p->tover_); | ||||
|    p->snrThreshold_                  = ntohs(p->snrThreshold_); | ||||
|    p->scale_                         = awips::Message::SwapFloat(p->scale_); | ||||
|    p->offset_                        = awips::Message::SwapFloat(p->offset_); | ||||
| 
 | ||||
|    if (p->numberOfDataMomentGates_ <= 1840) | ||||
|    { | ||||
|       if (p->dataWordSize_ == 8) | ||||
|       { | ||||
|          p->momentGates8_.resize(p->numberOfDataMomentGates_); | ||||
|          is.read(reinterpret_cast<char*>(p->momentGates8_.data()), | ||||
|                  p->numberOfDataMomentGates_); | ||||
|       } | ||||
|       else if (p->dataWordSize_ == 16) | ||||
|       { | ||||
|          p->momentGates16_.resize(p->numberOfDataMomentGates_); | ||||
|          is.read(reinterpret_cast<char*>(p->momentGates16_.data()), | ||||
|                  p->numberOfDataMomentGates_ * 2); | ||||
|          awips::Message::SwapVector(p->momentGates16_); | ||||
|       } | ||||
|       else | ||||
|       { | ||||
|          logger_->warn("Invalid data word size: {}", p->dataWordSize_); | ||||
|          dataBlockValid = false; | ||||
|       } | ||||
|    } | ||||
|    else | ||||
|    { | ||||
|       logger_->warn("Invalid number of data moment gates: {}", | ||||
|                     p->numberOfDataMomentGates_); | ||||
|       dataBlockValid = false; | ||||
|    } | ||||
| 
 | ||||
|    return dataBlockValid; | ||||
| } | ||||
| 
 | ||||
| class DigitalRadarDataGeneric::VolumeDataBlock::Impl | ||||
| { | ||||
| public: | ||||
|    explicit Impl() {} | ||||
| 
 | ||||
|    std::uint16_t lrtup_ {0}; | ||||
|    std::uint8_t  versionNumberMajor_ {0}; | ||||
|    std::uint8_t  versionNumberMinor_ {0}; | ||||
|    float         latitude_ {0.0f}; | ||||
|    float         longitude_ {0.0f}; | ||||
|    std::int16_t  siteHeight_ {0}; | ||||
|    std::uint16_t feedhornHeight_ {0}; | ||||
|    float         calibrationConstant_ {0.0f}; | ||||
|    float         horizontaShvTxPower_ {0.0f}; | ||||
|    float         verticalShvTxPower_ {0.0f}; | ||||
|    float         systemDifferentialReflectivity_ {0.0f}; | ||||
|    float         initialSystemDifferentialPhase_ {0.0f}; | ||||
|    std::uint16_t volumeCoveragePatternNumber_ {0}; | ||||
|    std::uint16_t processingStatus_ {0}; | ||||
| }; | ||||
| 
 | ||||
| DigitalRadarDataGeneric::VolumeDataBlock::VolumeDataBlock( | ||||
|    const std::string& dataBlockType, const std::string& dataName) : | ||||
|     DataBlock(dataBlockType, dataName), p(std::make_unique<Impl>()) | ||||
| { | ||||
| } | ||||
| DigitalRadarDataGeneric::VolumeDataBlock::~VolumeDataBlock() = default; | ||||
| 
 | ||||
| DigitalRadarDataGeneric::VolumeDataBlock::VolumeDataBlock( | ||||
|    VolumeDataBlock&&) noexcept = default; | ||||
| DigitalRadarDataGeneric::VolumeDataBlock& | ||||
| DigitalRadarDataGeneric::VolumeDataBlock::operator=( | ||||
|    VolumeDataBlock&&) noexcept = default; | ||||
| 
 | ||||
| float DigitalRadarDataGeneric::VolumeDataBlock::latitude() const | ||||
| { | ||||
|    return p->latitude_; | ||||
| } | ||||
| 
 | ||||
| float DigitalRadarDataGeneric::VolumeDataBlock::longitude() const | ||||
| { | ||||
|    return p->longitude_; | ||||
| } | ||||
| 
 | ||||
| std::uint16_t | ||||
| DigitalRadarDataGeneric::VolumeDataBlock::volume_coverage_pattern_number() const | ||||
| { | ||||
|    return p->volumeCoveragePatternNumber_; | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<DigitalRadarDataGeneric::VolumeDataBlock> | ||||
| DigitalRadarDataGeneric::VolumeDataBlock::Create( | ||||
|    const std::string& dataBlockType, | ||||
|    const std::string& dataName, | ||||
|    std::istream&      is) | ||||
| { | ||||
|    std::shared_ptr<VolumeDataBlock> p = | ||||
|       std::make_shared<VolumeDataBlock>(dataBlockType, dataName); | ||||
| 
 | ||||
|    if (!p->Parse(is)) | ||||
|    { | ||||
|       p.reset(); | ||||
|    } | ||||
| 
 | ||||
|    return p; | ||||
| } | ||||
| 
 | ||||
| bool DigitalRadarDataGeneric::VolumeDataBlock::Parse(std::istream& is) | ||||
| { | ||||
|    bool dataBlockValid = true; | ||||
| 
 | ||||
|    is.read(reinterpret_cast<char*>(&p->lrtup_), 2);               // 4-5
 | ||||
|    is.read(reinterpret_cast<char*>(&p->versionNumberMajor_), 1);  // 6
 | ||||
|    is.read(reinterpret_cast<char*>(&p->versionNumberMinor_), 1);  // 7
 | ||||
|    is.read(reinterpret_cast<char*>(&p->latitude_), 4);            // 8-11
 | ||||
|    is.read(reinterpret_cast<char*>(&p->longitude_), 4);           // 12-15
 | ||||
|    is.read(reinterpret_cast<char*>(&p->siteHeight_), 2);          // 16-17
 | ||||
|    is.read(reinterpret_cast<char*>(&p->feedhornHeight_), 2);      // 18-19
 | ||||
|    is.read(reinterpret_cast<char*>(&p->calibrationConstant_), 4); // 20-23
 | ||||
|    is.read(reinterpret_cast<char*>(&p->horizontaShvTxPower_), 4); // 24-27
 | ||||
|    is.read(reinterpret_cast<char*>(&p->verticalShvTxPower_), 4);  // 28-31
 | ||||
|    is.read(reinterpret_cast<char*>(&p->systemDifferentialReflectivity_), | ||||
|            4); // 32-35
 | ||||
|    is.read(reinterpret_cast<char*>(&p->initialSystemDifferentialPhase_), | ||||
|            4); // 36-39
 | ||||
|    is.read(reinterpret_cast<char*>(&p->volumeCoveragePatternNumber_), | ||||
|            2);                                                 // 40-41
 | ||||
|    is.read(reinterpret_cast<char*>(&p->processingStatus_), 2); // 42-43
 | ||||
| 
 | ||||
|    p->lrtup_               = ntohs(p->lrtup_); | ||||
|    p->latitude_            = awips::Message::SwapFloat(p->latitude_); | ||||
|    p->longitude_           = awips::Message::SwapFloat(p->longitude_); | ||||
|    p->siteHeight_          = ntohs(p->siteHeight_); | ||||
|    p->feedhornHeight_      = ntohs(p->feedhornHeight_); | ||||
|    p->calibrationConstant_ = awips::Message::SwapFloat(p->calibrationConstant_); | ||||
|    p->horizontaShvTxPower_ = awips::Message::SwapFloat(p->horizontaShvTxPower_); | ||||
|    p->verticalShvTxPower_  = awips::Message::SwapFloat(p->verticalShvTxPower_); | ||||
|    p->systemDifferentialReflectivity_ = | ||||
|       awips::Message::SwapFloat(p->systemDifferentialReflectivity_); | ||||
|    p->initialSystemDifferentialPhase_ = | ||||
|       awips::Message::SwapFloat(p->initialSystemDifferentialPhase_); | ||||
|    p->volumeCoveragePatternNumber_ = ntohs(p->volumeCoveragePatternNumber_); | ||||
|    p->processingStatus_            = ntohs(p->processingStatus_); | ||||
| 
 | ||||
|    return dataBlockValid; | ||||
| } | ||||
| 
 | ||||
| class DigitalRadarDataGeneric::ElevationDataBlock::Impl | ||||
| { | ||||
| public: | ||||
|    explicit Impl() {} | ||||
| 
 | ||||
|    std::uint16_t lrtup_ {0}; | ||||
|    std::int16_t  atmos_ {0}; | ||||
|    float         calibrationConstant_ {0.0f}; | ||||
| }; | ||||
| 
 | ||||
| DigitalRadarDataGeneric::ElevationDataBlock::ElevationDataBlock( | ||||
|    const std::string& dataBlockType, const std::string& dataName) : | ||||
|     DataBlock(dataBlockType, dataName), p(std::make_unique<Impl>()) | ||||
| { | ||||
| } | ||||
| DigitalRadarDataGeneric::ElevationDataBlock::~ElevationDataBlock() = default; | ||||
| 
 | ||||
| DigitalRadarDataGeneric::ElevationDataBlock::ElevationDataBlock( | ||||
|    ElevationDataBlock&&) noexcept = default; | ||||
| DigitalRadarDataGeneric::ElevationDataBlock& | ||||
| DigitalRadarDataGeneric::ElevationDataBlock::operator=( | ||||
|    ElevationDataBlock&&) noexcept = default; | ||||
| 
 | ||||
| std::shared_ptr<DigitalRadarDataGeneric::ElevationDataBlock> | ||||
| DigitalRadarDataGeneric::ElevationDataBlock::Create( | ||||
|    const std::string& dataBlockType, | ||||
|    const std::string& dataName, | ||||
|    std::istream&      is) | ||||
| { | ||||
|    std::shared_ptr<ElevationDataBlock> p = | ||||
|       std::make_shared<ElevationDataBlock>(dataBlockType, dataName); | ||||
| 
 | ||||
|    if (!p->Parse(is)) | ||||
|    { | ||||
|       p.reset(); | ||||
|    } | ||||
| 
 | ||||
|    return p; | ||||
| } | ||||
| 
 | ||||
| bool DigitalRadarDataGeneric::ElevationDataBlock::Parse(std::istream& is) | ||||
| { | ||||
|    bool dataBlockValid = true; | ||||
| 
 | ||||
|    is.read(reinterpret_cast<char*>(&p->lrtup_), 2);               // 4-5
 | ||||
|    is.read(reinterpret_cast<char*>(&p->atmos_), 2);               // 6-7
 | ||||
|    is.read(reinterpret_cast<char*>(&p->calibrationConstant_), 4); // 8-11
 | ||||
| 
 | ||||
|    p->lrtup_               = ntohs(p->lrtup_); | ||||
|    p->atmos_               = ntohs(p->atmos_); | ||||
|    p->calibrationConstant_ = awips::Message::SwapFloat(p->calibrationConstant_); | ||||
| 
 | ||||
|    return dataBlockValid; | ||||
| } | ||||
| 
 | ||||
| class DigitalRadarDataGeneric::RadialDataBlock::Impl | ||||
| { | ||||
| public: | ||||
|    explicit Impl() {} | ||||
| 
 | ||||
|    std::uint16_t lrtup_ {0}; | ||||
|    std::uint16_t unambigiousRange_ {0}; | ||||
|    float         noiseLevelHorizontal_ {0.0f}; | ||||
|    float         noiseLevelVertical_ {0.0f}; | ||||
|    std::uint16_t nyquistVelocity_ {0}; | ||||
|    std::uint16_t radialFlags_ {0}; | ||||
|    float         calibrationConstantHorizontal_ {0.0f}; | ||||
|    float         calibrationConstantVertical_ {0.0f}; | ||||
| }; | ||||
| 
 | ||||
| DigitalRadarDataGeneric::RadialDataBlock::RadialDataBlock( | ||||
|    const std::string& dataBlockType, const std::string& dataName) : | ||||
|     DataBlock(dataBlockType, dataName), p(std::make_unique<Impl>()) | ||||
| { | ||||
| } | ||||
| DigitalRadarDataGeneric::RadialDataBlock::~RadialDataBlock() = default; | ||||
| 
 | ||||
| DigitalRadarDataGeneric::RadialDataBlock::RadialDataBlock( | ||||
|    RadialDataBlock&&) noexcept = default; | ||||
| DigitalRadarDataGeneric::RadialDataBlock& | ||||
| DigitalRadarDataGeneric::RadialDataBlock::operator=( | ||||
|    RadialDataBlock&&) noexcept = default; | ||||
| 
 | ||||
| float DigitalRadarDataGeneric::RadialDataBlock::unambiguous_range() const | ||||
| { | ||||
|    return p->unambigiousRange_ / 10.0f; | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<DigitalRadarDataGeneric::RadialDataBlock> | ||||
| DigitalRadarDataGeneric::RadialDataBlock::Create( | ||||
|    const std::string& dataBlockType, | ||||
|    const std::string& dataName, | ||||
|    std::istream&      is) | ||||
| { | ||||
|    std::shared_ptr<RadialDataBlock> p = | ||||
|       std::make_shared<RadialDataBlock>(dataBlockType, dataName); | ||||
| 
 | ||||
|    if (!p->Parse(is)) | ||||
|    { | ||||
|       p.reset(); | ||||
|    } | ||||
| 
 | ||||
|    return p; | ||||
| } | ||||
| 
 | ||||
| bool DigitalRadarDataGeneric::RadialDataBlock::Parse(std::istream& is) | ||||
| { | ||||
|    bool dataBlockValid = true; | ||||
| 
 | ||||
|    is.read(reinterpret_cast<char*>(&p->lrtup_), 2);                // 4-5
 | ||||
|    is.read(reinterpret_cast<char*>(&p->unambigiousRange_), 2);     // 6-7
 | ||||
|    is.read(reinterpret_cast<char*>(&p->noiseLevelHorizontal_), 4); // 8-11
 | ||||
|    is.read(reinterpret_cast<char*>(&p->noiseLevelVertical_), 4);   // 12-15
 | ||||
|    is.read(reinterpret_cast<char*>(&p->nyquistVelocity_), 2);      // 16-17
 | ||||
|    is.read(reinterpret_cast<char*>(&p->radialFlags_), 2);          // 18-19
 | ||||
|    is.read(reinterpret_cast<char*>(&p->calibrationConstantHorizontal_), | ||||
|            4); // 20-23
 | ||||
|    is.read(reinterpret_cast<char*>(&p->calibrationConstantVertical_), | ||||
|            4); // 24-27
 | ||||
| 
 | ||||
|    p->lrtup_            = ntohs(p->lrtup_); | ||||
|    p->unambigiousRange_ = ntohs(p->unambigiousRange_); | ||||
|    p->noiseLevelHorizontal_ = | ||||
|       awips::Message::SwapFloat(p->noiseLevelHorizontal_); | ||||
|    p->noiseLevelVertical_ = awips::Message::SwapFloat(p->noiseLevelVertical_); | ||||
|    p->nyquistVelocity_    = ntohs(p->nyquistVelocity_); | ||||
|    p->radialFlags_        = ntohs(p->radialFlags_); | ||||
|    p->calibrationConstantHorizontal_ = | ||||
|       awips::Message::SwapFloat(p->calibrationConstantHorizontal_); | ||||
|    p->calibrationConstantVertical_ = | ||||
|       awips::Message::SwapFloat(p->calibrationConstantVertical_); | ||||
| 
 | ||||
|    return dataBlockValid; | ||||
| } | ||||
| 
 | ||||
| class DigitalRadarDataGeneric::Impl | ||||
| { | ||||
| public: | ||||
|    explicit Impl() {}; | ||||
|    ~Impl() = default; | ||||
| 
 | ||||
|    std::string                   radarIdentifier_ {}; | ||||
|    std::uint32_t                 collectionTime_ {0}; | ||||
|    std::uint16_t                 modifiedJulianDate_ {0}; | ||||
|    std::uint16_t                 azimuthNumber_ {0}; | ||||
|    float                         azimuthAngle_ {0.0f}; | ||||
|    std::uint8_t                  compressionIndicator_ {0}; | ||||
|    std::uint16_t                 radialLength_ {0}; | ||||
|    std::uint8_t                  azimuthResolutionSpacing_ {0}; | ||||
|    std::uint8_t                  radialStatus_ {0}; | ||||
|    std::uint8_t                  elevationNumber_ {0}; | ||||
|    std::uint8_t                  cutSectorNumber_ {0}; | ||||
|    float                         elevationAngle_ {0.0f}; | ||||
|    std::uint8_t                  radialSpotBlankingStatus_ {0}; | ||||
|    std::uint8_t                  azimuthIndexingMode_ {0}; | ||||
|    std::uint16_t                 dataBlockCount_ {0}; | ||||
|    std::array<std::uint32_t, 10> dataBlockPointer_ {0}; | ||||
| 
 | ||||
|    std::shared_ptr<VolumeDataBlock>    volumeDataBlock_ {nullptr}; | ||||
|    std::shared_ptr<ElevationDataBlock> elevationDataBlock_ {nullptr}; | ||||
|    std::shared_ptr<RadialDataBlock>    radialDataBlock_ {nullptr}; | ||||
|    std::unordered_map<DataBlockType, std::shared_ptr<MomentDataBlock>> | ||||
|       momentDataBlock_ {}; | ||||
| }; | ||||
| 
 | ||||
| DigitalRadarDataGeneric::DigitalRadarDataGeneric() : | ||||
|     GenericRadarData(), p(std::make_unique<Impl>()) | ||||
| { | ||||
| } | ||||
| DigitalRadarDataGeneric::~DigitalRadarDataGeneric() = default; | ||||
| 
 | ||||
| DigitalRadarDataGeneric::DigitalRadarDataGeneric( | ||||
|    DigitalRadarDataGeneric&&) noexcept = default; | ||||
| DigitalRadarDataGeneric& DigitalRadarDataGeneric::operator=( | ||||
|    DigitalRadarDataGeneric&&) noexcept = default; | ||||
| 
 | ||||
| std::string DigitalRadarDataGeneric::radar_identifier() const | ||||
| { | ||||
|    return p->radarIdentifier_; | ||||
| } | ||||
| 
 | ||||
| std::uint32_t DigitalRadarDataGeneric::collection_time() const | ||||
| { | ||||
|    return p->collectionTime_; | ||||
| } | ||||
| 
 | ||||
| std::uint16_t DigitalRadarDataGeneric::modified_julian_date() const | ||||
| { | ||||
|    return p->modifiedJulianDate_; | ||||
| } | ||||
| 
 | ||||
| std::uint16_t DigitalRadarDataGeneric::azimuth_number() const | ||||
| { | ||||
|    return p->azimuthNumber_; | ||||
| } | ||||
| 
 | ||||
| units::degrees<float> DigitalRadarDataGeneric::azimuth_angle() const | ||||
| { | ||||
|    return units::degrees<float> {p->azimuthAngle_}; | ||||
| } | ||||
| 
 | ||||
| std::uint8_t DigitalRadarDataGeneric::compression_indicator() const | ||||
| { | ||||
|    return p->compressionIndicator_; | ||||
| } | ||||
| 
 | ||||
| std::uint16_t DigitalRadarDataGeneric::radial_length() const | ||||
| { | ||||
|    return p->radialLength_; | ||||
| } | ||||
| 
 | ||||
| std::uint8_t DigitalRadarDataGeneric::azimuth_resolution_spacing() const | ||||
| { | ||||
|    return p->azimuthResolutionSpacing_; | ||||
| } | ||||
| 
 | ||||
| std::uint8_t DigitalRadarDataGeneric::radial_status() const | ||||
| { | ||||
|    return p->radialStatus_; | ||||
| } | ||||
| 
 | ||||
| std::uint16_t DigitalRadarDataGeneric::elevation_number() const | ||||
| { | ||||
|    return p->elevationNumber_; | ||||
| } | ||||
| 
 | ||||
| std::uint8_t DigitalRadarDataGeneric::cut_sector_number() const | ||||
| { | ||||
|    return p->cutSectorNumber_; | ||||
| } | ||||
| 
 | ||||
| units::degrees<float> DigitalRadarDataGeneric::elevation_angle() const | ||||
| { | ||||
|    return units::degrees<float> {p->elevationAngle_}; | ||||
| } | ||||
| 
 | ||||
| std::uint8_t DigitalRadarDataGeneric::radial_spot_blanking_status() const | ||||
| { | ||||
|    return p->radialSpotBlankingStatus_; | ||||
| } | ||||
| 
 | ||||
| std::uint8_t DigitalRadarDataGeneric::azimuth_indexing_mode() const | ||||
| { | ||||
|    return p->azimuthIndexingMode_; | ||||
| } | ||||
| 
 | ||||
| std::uint16_t DigitalRadarDataGeneric::data_block_count() const | ||||
| { | ||||
|    return p->dataBlockCount_; | ||||
| } | ||||
| 
 | ||||
| std::uint16_t DigitalRadarDataGeneric::volume_coverage_pattern_number() const | ||||
| { | ||||
|    std::uint16_t vcpNumber = 0; | ||||
| 
 | ||||
|    if (p->volumeDataBlock_ != nullptr) | ||||
|    { | ||||
|       vcpNumber = p->volumeDataBlock_->volume_coverage_pattern_number(); | ||||
|    } | ||||
| 
 | ||||
|    return vcpNumber; | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<DigitalRadarDataGeneric::ElevationDataBlock> | ||||
| DigitalRadarDataGeneric::elevation_data_block() const | ||||
| { | ||||
|    return p->elevationDataBlock_; | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<DigitalRadarDataGeneric::RadialDataBlock> | ||||
| DigitalRadarDataGeneric::radial_data_block() const | ||||
| { | ||||
|    return p->radialDataBlock_; | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<DigitalRadarDataGeneric::VolumeDataBlock> | ||||
| DigitalRadarDataGeneric::volume_data_block() const | ||||
| { | ||||
|    return p->volumeDataBlock_; | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<GenericRadarData::MomentDataBlock> | ||||
| DigitalRadarDataGeneric::moment_data_block(DataBlockType type) const | ||||
| { | ||||
|    std::shared_ptr<MomentDataBlock> momentDataBlock = nullptr; | ||||
| 
 | ||||
|    auto it = p->momentDataBlock_.find(type); | ||||
|    if (it != p->momentDataBlock_.end()) | ||||
|    { | ||||
|       momentDataBlock = it->second; | ||||
|    } | ||||
| 
 | ||||
|    return momentDataBlock; | ||||
| } | ||||
| 
 | ||||
| bool DigitalRadarDataGeneric::Parse(std::istream& is) | ||||
| { | ||||
|    logger_->trace("Parsing Digital Radar Data (Message Type 31)"); | ||||
| 
 | ||||
|    bool        messageValid = true; | ||||
|    std::size_t bytesRead    = 0; | ||||
| 
 | ||||
|    std::streampos isBegin = is.tellg(); | ||||
| 
 | ||||
|    p->radarIdentifier_.resize(4); | ||||
| 
 | ||||
|    is.read(&p->radarIdentifier_[0], 4);                                // 0-3
 | ||||
|    is.read(reinterpret_cast<char*>(&p->collectionTime_), 4);           // 4-7
 | ||||
|    is.read(reinterpret_cast<char*>(&p->modifiedJulianDate_), 2);       // 8-9
 | ||||
|    is.read(reinterpret_cast<char*>(&p->azimuthNumber_), 2);            // 10-11
 | ||||
|    is.read(reinterpret_cast<char*>(&p->azimuthAngle_), 4);             // 12-15
 | ||||
|    is.read(reinterpret_cast<char*>(&p->compressionIndicator_), 1);     // 16
 | ||||
|    is.seekg(1, std::ios_base::cur);                                    // 17
 | ||||
|    is.read(reinterpret_cast<char*>(&p->radialLength_), 2);             // 18-19
 | ||||
|    is.read(reinterpret_cast<char*>(&p->azimuthResolutionSpacing_), 1); // 20
 | ||||
|    is.read(reinterpret_cast<char*>(&p->radialStatus_), 1);             // 21
 | ||||
|    is.read(reinterpret_cast<char*>(&p->elevationNumber_), 1);          // 22
 | ||||
|    is.read(reinterpret_cast<char*>(&p->cutSectorNumber_), 1);          // 23
 | ||||
|    is.read(reinterpret_cast<char*>(&p->elevationAngle_), 4);           // 24-27
 | ||||
|    is.read(reinterpret_cast<char*>(&p->radialSpotBlankingStatus_), 1); // 28
 | ||||
|    is.read(reinterpret_cast<char*>(&p->azimuthIndexingMode_), 1);      // 29
 | ||||
|    is.read(reinterpret_cast<char*>(&p->dataBlockCount_), 2);           // 30-31
 | ||||
| 
 | ||||
|    p->collectionTime_     = ntohl(p->collectionTime_); | ||||
|    p->modifiedJulianDate_ = ntohs(p->modifiedJulianDate_); | ||||
|    p->azimuthNumber_      = ntohs(p->azimuthNumber_); | ||||
|    p->azimuthAngle_       = SwapFloat(p->azimuthAngle_); | ||||
|    p->radialLength_       = ntohs(p->radialLength_); | ||||
|    p->elevationAngle_     = SwapFloat(p->elevationAngle_); | ||||
|    p->dataBlockCount_     = ntohs(p->dataBlockCount_); | ||||
| 
 | ||||
|    if (p->azimuthNumber_ < 1 || p->azimuthNumber_ > 720) | ||||
|    { | ||||
|       logger_->warn("Invalid azimuth number: {}", p->azimuthNumber_); | ||||
|       messageValid = false; | ||||
|    } | ||||
|    if (p->elevationNumber_ < 1 || p->elevationNumber_ > 32) | ||||
|    { | ||||
|       logger_->warn("Invalid elevation number: {}", p->elevationNumber_); | ||||
|       messageValid = false; | ||||
|    } | ||||
|    if (p->dataBlockCount_ < 4 || p->dataBlockCount_ > 10) | ||||
|    { | ||||
|       logger_->warn("Invalid number of data blocks: {}", p->dataBlockCount_); | ||||
|       messageValid = false; | ||||
|    } | ||||
|    if (p->compressionIndicator_ != 0) | ||||
|    { | ||||
|       logger_->warn("Compression not supported"); | ||||
|       messageValid = false; | ||||
|    } | ||||
| 
 | ||||
|    if (!messageValid) | ||||
|    { | ||||
|       p->dataBlockCount_ = 0; | ||||
|    } | ||||
| 
 | ||||
|    is.read(reinterpret_cast<char*>(&p->dataBlockPointer_), | ||||
|            p->dataBlockCount_ * 4); | ||||
| 
 | ||||
|    SwapArray(p->dataBlockPointer_, p->dataBlockCount_); | ||||
| 
 | ||||
|    for (uint16_t b = 0; b < p->dataBlockCount_; ++b) | ||||
|    { | ||||
|       is.seekg(isBegin + std::streamoff(p->dataBlockPointer_[b]), | ||||
|                std::ios_base::beg); | ||||
| 
 | ||||
|       std::string dataBlockType(1, 0); | ||||
|       std::string dataName(3, 0); | ||||
| 
 | ||||
|       is.read(&dataBlockType[0], 1); | ||||
|       is.read(&dataName[0], 3); | ||||
| 
 | ||||
|       DataBlockType dataBlock = DataBlockType::Unknown; | ||||
|       try | ||||
|       { | ||||
|          dataBlock = strToDataBlock_.at(dataName); | ||||
|       } | ||||
|       catch (const std::exception&) | ||||
|       { | ||||
|       } | ||||
| 
 | ||||
|       switch (dataBlock) | ||||
|       { | ||||
|       case DataBlockType::Volume: | ||||
|          p->volumeDataBlock_ = | ||||
|             std::move(VolumeDataBlock::Create(dataBlockType, dataName, is)); | ||||
|          break; | ||||
|       case DataBlockType::Elevation: | ||||
|          p->elevationDataBlock_ = | ||||
|             std::move(ElevationDataBlock::Create(dataBlockType, dataName, is)); | ||||
|          break; | ||||
|       case DataBlockType::Radial: | ||||
|          p->radialDataBlock_ = | ||||
|             std::move(RadialDataBlock::Create(dataBlockType, dataName, is)); | ||||
|          break; | ||||
|       case DataBlockType::MomentRef: | ||||
|       case DataBlockType::MomentVel: | ||||
|       case DataBlockType::MomentSw: | ||||
|       case DataBlockType::MomentZdr: | ||||
|       case DataBlockType::MomentPhi: | ||||
|       case DataBlockType::MomentRho: | ||||
|       case DataBlockType::MomentCfp: | ||||
|          p->momentDataBlock_[dataBlock] = | ||||
|             std::move(MomentDataBlock::Create(dataBlockType, dataName, is)); | ||||
|          break; | ||||
|       default: | ||||
|          logger_->warn("Unknown data name: {}", dataName); | ||||
|          break; | ||||
|       } | ||||
|    } | ||||
| 
 | ||||
|    is.seekg(isBegin, std::ios_base::beg); | ||||
|    if (!ValidateMessage(is, bytesRead)) | ||||
|    { | ||||
|       messageValid = false; | ||||
|    } | ||||
| 
 | ||||
|    return messageValid; | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<DigitalRadarDataGeneric> | ||||
| DigitalRadarDataGeneric::Create(Level2MessageHeader&& header, std::istream& is) | ||||
| { | ||||
|    std::shared_ptr<DigitalRadarDataGeneric> message = | ||||
|       std::make_shared<DigitalRadarDataGeneric>(); | ||||
|    message->set_header(std::move(header)); | ||||
| 
 | ||||
|    if (!message->Parse(is)) | ||||
|    { | ||||
|       message.reset(); | ||||
|    } | ||||
| 
 | ||||
|    return message; | ||||
| } | ||||
| 
 | ||||
| } // namespace rda
 | ||||
| } // namespace wsr88d
 | ||||
| } // namespace scwx
 | ||||
							
								
								
									
										61
									
								
								wxdata/source/scwx/wsr88d/rda/generic_radar_data.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								wxdata/source/scwx/wsr88d/rda/generic_radar_data.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,61 @@ | |||
| #include <scwx/wsr88d/rda/generic_radar_data.hpp> | ||||
| #include <scwx/util/logger.hpp> | ||||
| 
 | ||||
| namespace scwx | ||||
| { | ||||
| namespace wsr88d | ||||
| { | ||||
| namespace rda | ||||
| { | ||||
| 
 | ||||
| static const std::string logPrefix_ = "scwx::wsr88d::rda::generic_radar_data"; | ||||
| 
 | ||||
| static const std::unordered_map<std::string, DataBlockType> strToDataBlock_ { | ||||
|    {"VOL", DataBlockType::Volume}, | ||||
|    {"ELV", DataBlockType::Elevation}, | ||||
|    {"RAD", DataBlockType::Radial}, | ||||
|    {"REF", DataBlockType::MomentRef}, | ||||
|    {"VEL", DataBlockType::MomentVel}, | ||||
|    {"SW ", DataBlockType::MomentSw}, | ||||
|    {"ZDR", DataBlockType::MomentZdr}, | ||||
|    {"PHI", DataBlockType::MomentPhi}, | ||||
|    {"RHO", DataBlockType::MomentRho}, | ||||
|    {"CFP", DataBlockType::MomentCfp}}; | ||||
| 
 | ||||
| class GenericRadarData::MomentDataBlock::Impl | ||||
| { | ||||
| public: | ||||
|    explicit Impl() {} | ||||
| }; | ||||
| 
 | ||||
| GenericRadarData::MomentDataBlock::MomentDataBlock() : | ||||
|     p(std::make_unique<Impl>()) | ||||
| { | ||||
| } | ||||
| GenericRadarData::MomentDataBlock::~MomentDataBlock() = default; | ||||
| 
 | ||||
| GenericRadarData::MomentDataBlock::MomentDataBlock(MomentDataBlock&&) noexcept = | ||||
|    default; | ||||
| GenericRadarData::MomentDataBlock& GenericRadarData::MomentDataBlock::operator=( | ||||
|    MomentDataBlock&&) noexcept = default; | ||||
| 
 | ||||
| class GenericRadarData::Impl | ||||
| { | ||||
| public: | ||||
|    explicit Impl() {}; | ||||
|    ~Impl() = default; | ||||
| }; | ||||
| 
 | ||||
| GenericRadarData::GenericRadarData() : | ||||
|     Level2Message(), p(std::make_unique<Impl>()) | ||||
| { | ||||
| } | ||||
| GenericRadarData::~GenericRadarData() = default; | ||||
| 
 | ||||
| GenericRadarData::GenericRadarData(GenericRadarData&&) noexcept = default; | ||||
| GenericRadarData& | ||||
| GenericRadarData::operator=(GenericRadarData&&) noexcept = default; | ||||
| 
 | ||||
| } // namespace rda
 | ||||
| } // namespace wsr88d
 | ||||
| } // namespace scwx
 | ||||
|  | @ -5,6 +5,7 @@ | |||
| #include <scwx/wsr88d/rda/clutter_filter_bypass_map.hpp> | ||||
| #include <scwx/wsr88d/rda/clutter_filter_map.hpp> | ||||
| #include <scwx/wsr88d/rda/digital_radar_data.hpp> | ||||
| #include <scwx/wsr88d/rda/digital_radar_data_generic.hpp> | ||||
| #include <scwx/wsr88d/rda/performance_maintenance_data.hpp> | ||||
| #include <scwx/wsr88d/rda/rda_adaptation_data.hpp> | ||||
| #include <scwx/wsr88d/rda/rda_status_data.hpp> | ||||
|  | @ -28,14 +29,15 @@ typedef std::function<std::shared_ptr<Level2Message>(Level2MessageHeader&&, | |||
|                                                      std::istream&)> | ||||
|    CreateLevel2MessageFunction; | ||||
| 
 | ||||
| static const std::unordered_map<unsigned int, CreateLevel2MessageFunction> create_ { | ||||
|    {2, RdaStatusData::Create}, | ||||
|    {3, PerformanceMaintenanceData::Create}, | ||||
|    {5, VolumeCoveragePatternData::Create}, | ||||
|    {13, ClutterFilterBypassMap::Create}, | ||||
|    {15, ClutterFilterMap::Create}, | ||||
|    {18, RdaAdaptationData::Create}, | ||||
|    {31, DigitalRadarData::Create}}; | ||||
| static const std::unordered_map<unsigned int, CreateLevel2MessageFunction> | ||||
|    create_ {{1, DigitalRadarData::Create}, | ||||
|             {2, RdaStatusData::Create}, | ||||
|             {3, PerformanceMaintenanceData::Create}, | ||||
|             {5, VolumeCoveragePatternData::Create}, | ||||
|             {13, ClutterFilterBypassMap::Create}, | ||||
|             {15, ClutterFilterMap::Create}, | ||||
|             {18, RdaAdaptationData::Create}, | ||||
|             {31, DigitalRadarDataGeneric::Create}}; | ||||
| 
 | ||||
| struct Level2MessageFactory::Context | ||||
| { | ||||
|  | @ -51,6 +53,7 @@ struct Level2MessageFactory::Context | |||
|    size_t            bufferedSize_; | ||||
|    util::vectorbuf   messageBuffer_; | ||||
|    std::istream      messageBufferStream_; | ||||
|    bool              bufferingData_ {false}; | ||||
| }; | ||||
| 
 | ||||
| std::shared_ptr<Level2MessageFactory::Context> | ||||
|  | @ -59,14 +62,38 @@ Level2MessageFactory::CreateContext() | |||
|    return std::make_shared<Context>(); | ||||
| } | ||||
| 
 | ||||
| Level2MessageInfo Level2MessageFactory::Create(std::istream&            is, | ||||
|                                                std::shared_ptr<Context> ctx) | ||||
| Level2MessageInfo Level2MessageFactory::Create(std::istream&             is, | ||||
|                                                std::shared_ptr<Context>& ctx) | ||||
| { | ||||
|    Level2MessageInfo   info; | ||||
|    Level2MessageHeader header; | ||||
|    info.headerValid  = header.Parse(is); | ||||
|    info.messageValid = info.headerValid; | ||||
| 
 | ||||
|    std::uint16_t segment       = 0; | ||||
|    std::uint16_t totalSegments = 0; | ||||
|    std::size_t   dataSize      = 0; | ||||
| 
 | ||||
|    if (info.headerValid) | ||||
|    { | ||||
|       if (header.message_size() == 65535) | ||||
|       { | ||||
|          segment       = 1; | ||||
|          totalSegments = 1; | ||||
|          dataSize = | ||||
|             (static_cast<std::size_t>(header.number_of_message_segments()) | ||||
|              << 16) + | ||||
|             header.message_segment_number(); | ||||
|       } | ||||
|       else | ||||
|       { | ||||
|          segment       = header.message_segment_number(); | ||||
|          totalSegments = header.number_of_message_segments(); | ||||
|          dataSize      = static_cast<std::size_t>(header.message_size()) * 2 - | ||||
|                     Level2MessageHeader::SIZE; | ||||
|       } | ||||
|    } | ||||
| 
 | ||||
|    if (info.headerValid && create_.find(header.message_type()) == create_.end()) | ||||
|    { | ||||
|       logger_->warn("Unknown message type: {}", | ||||
|  | @ -76,10 +103,7 @@ Level2MessageInfo Level2MessageFactory::Create(std::istream&            is, | |||
| 
 | ||||
|    if (info.messageValid) | ||||
|    { | ||||
|       uint16_t segment       = header.message_segment_number(); | ||||
|       uint16_t totalSegments = header.number_of_message_segments(); | ||||
|       uint8_t  messageType   = header.message_type(); | ||||
|       size_t   dataSize = header.message_size() * 2 - Level2MessageHeader::SIZE; | ||||
|       std::uint8_t messageType = header.message_type(); | ||||
| 
 | ||||
|       std::istream* messageStream = nullptr; | ||||
| 
 | ||||
|  | @ -100,38 +124,51 @@ Level2MessageInfo Level2MessageFactory::Create(std::istream&            is, | |||
|             // Estimate total message size
 | ||||
|             ctx->messageData_.resize(dataSize * totalSegments); | ||||
|             ctx->messageBufferStream_.clear(); | ||||
|             ctx->bufferedSize_ = 0; | ||||
|             ctx->bufferedSize_  = 0; | ||||
|             ctx->bufferingData_ = true; | ||||
|          } | ||||
| 
 | ||||
|          if (ctx->messageData_.capacity() < ctx->bufferedSize_ + dataSize) | ||||
|          else if (!ctx->bufferingData_) | ||||
|          { | ||||
|             logger_->debug("Bad size estimate, increasing size"); | ||||
| 
 | ||||
|             // Estimate remaining size
 | ||||
|             uint16_t remainingSegments = | ||||
|                std::max<uint16_t>(totalSegments - segment + 1, 100u); | ||||
|             size_t remainingSize = remainingSegments * dataSize; | ||||
| 
 | ||||
|             ctx->messageData_.resize(ctx->bufferedSize_ + remainingSize); | ||||
|          } | ||||
| 
 | ||||
|          is.read(ctx->messageData_.data() + ctx->bufferedSize_, dataSize); | ||||
|          ctx->bufferedSize_ += dataSize; | ||||
| 
 | ||||
|          if (is.eof()) | ||||
|          { | ||||
|             logger_->warn("End of file reached trying to buffer message"); | ||||
|             // Segment number did not start at 1
 | ||||
|             logger_->trace("Ignoring Segment {}/{}, did not start at 1", | ||||
|                            segment, | ||||
|                            totalSegments); | ||||
|             info.messageValid = false; | ||||
|             ctx->messageData_.shrink_to_fit(); | ||||
|             ctx->bufferedSize_ = 0; | ||||
|          } | ||||
|          else if (segment == totalSegments) | ||||
|          { | ||||
|             ctx->messageBuffer_.update_read_pointers(ctx->bufferedSize_); | ||||
|             header.set_message_size(static_cast<uint16_t>( | ||||
|                ctx->bufferedSize_ / 2 + Level2MessageHeader::SIZE)); | ||||
| 
 | ||||
|             messageStream = &ctx->messageBufferStream_; | ||||
|          if (ctx->bufferingData_) | ||||
|          { | ||||
|             if (ctx->messageData_.capacity() < ctx->bufferedSize_ + dataSize) | ||||
|             { | ||||
|                logger_->debug("Bad size estimate, increasing size"); | ||||
| 
 | ||||
|                // Estimate remaining size
 | ||||
|                uint16_t remainingSegments = | ||||
|                   std::max<uint16_t>(totalSegments - segment + 1, 100u); | ||||
|                size_t remainingSize = remainingSegments * dataSize; | ||||
| 
 | ||||
|                ctx->messageData_.resize(ctx->bufferedSize_ + remainingSize); | ||||
|             } | ||||
| 
 | ||||
|             is.read(ctx->messageData_.data() + ctx->bufferedSize_, dataSize); | ||||
|             ctx->bufferedSize_ += dataSize; | ||||
| 
 | ||||
|             if (is.eof()) | ||||
|             { | ||||
|                logger_->warn("End of file reached trying to buffer message"); | ||||
|                info.messageValid = false; | ||||
|                ctx->messageData_.shrink_to_fit(); | ||||
|                ctx->bufferedSize_  = 0; | ||||
|                ctx->bufferingData_ = false; | ||||
|             } | ||||
|             else if (segment == totalSegments) | ||||
|             { | ||||
|                ctx->messageBuffer_.update_read_pointers(ctx->bufferedSize_); | ||||
|                header.set_message_size(static_cast<uint16_t>( | ||||
|                   ctx->bufferedSize_ / 2 + Level2MessageHeader::SIZE)); | ||||
| 
 | ||||
|                messageStream = &ctx->messageBufferStream_; | ||||
|             } | ||||
|          } | ||||
|       } | ||||
| 
 | ||||
|  | @ -142,14 +179,14 @@ Level2MessageInfo Level2MessageFactory::Create(std::istream&            is, | |||
|          ctx->messageData_.resize(0); | ||||
|          ctx->messageData_.shrink_to_fit(); | ||||
|          ctx->messageBufferStream_.clear(); | ||||
|          ctx->bufferedSize_ = 0; | ||||
|          ctx->bufferedSize_  = 0; | ||||
|          ctx->bufferingData_ = false; | ||||
|       } | ||||
|    } | ||||
|    else if (info.headerValid) | ||||
|    { | ||||
|       // Seek to the end of the current message
 | ||||
|       is.seekg(header.message_size() * 2 - rda::Level2MessageHeader::SIZE, | ||||
|                std::ios_base::cur); | ||||
|       is.seekg(dataSize, std::ios_base::cur); | ||||
|    } | ||||
| 
 | ||||
|    if (info.message == nullptr) | ||||
|  |  | |||
|  | @ -130,12 +130,10 @@ bool Level2MessageHeader::Parse(std::istream& is) | |||
|    { | ||||
|       if (p->messageSize_ < 9) | ||||
|       { | ||||
|          logger_->warn("Invalid message size: {}", p->messageSize_); | ||||
|          headerValid = false; | ||||
|       } | ||||
|       if (p->julianDate_ < 1) | ||||
|       { | ||||
|          logger_->warn("Invalid date: {}", p->julianDate_); | ||||
|          if (p->messageSize_ != 0) | ||||
|          { | ||||
|             logger_->warn("Invalid message size: {}", p->messageSize_); | ||||
|          } | ||||
|          headerValid = false; | ||||
|       } | ||||
|       if (p->millisecondsOfDay_ > 86'399'999u) | ||||
|  |  | |||
|  | @ -102,7 +102,7 @@ VolumeCoveragePatternData::VolumeCoveragePatternData() : | |||
| VolumeCoveragePatternData::~VolumeCoveragePatternData() = default; | ||||
| 
 | ||||
| VolumeCoveragePatternData::VolumeCoveragePatternData( | ||||
|    VolumeCoveragePatternData&&) noexcept                      = default; | ||||
|    VolumeCoveragePatternData&&) noexcept = default; | ||||
| VolumeCoveragePatternData& VolumeCoveragePatternData::operator=( | ||||
|    VolumeCoveragePatternData&&) noexcept = default; | ||||
| 
 | ||||
|  | @ -419,16 +419,24 @@ bool VolumeCoveragePatternData::Parse(std::istream& is) | |||
|    p->vcpSequencing_       = ntohs(p->vcpSequencing_); | ||||
|    p->vcpSupplementalData_ = ntohs(p->vcpSupplementalData_); | ||||
| 
 | ||||
|    if (messageSize < 34 || messageSize > 747) | ||||
|    if (messageSize == 0) | ||||
|    { | ||||
|       logger_->warn("Invalid message size: {}", messageSize); | ||||
|       logger_->trace("Ignoring empty message"); | ||||
|       messageValid = false; | ||||
|    } | ||||
|    if (numberOfElevationCuts < 1 || numberOfElevationCuts > 32) | ||||
|    else | ||||
|    { | ||||
|       logger_->warn("Invalid number of elevation cuts: {}", | ||||
|                     numberOfElevationCuts); | ||||
|       messageValid = false; | ||||
|       if (messageSize < 34 || messageSize > 747) | ||||
|       { | ||||
|          logger_->warn("Invalid message size: {}", messageSize); | ||||
|          messageValid = false; | ||||
|       } | ||||
|       if (numberOfElevationCuts < 1 || numberOfElevationCuts > 32) | ||||
|       { | ||||
|          logger_->warn("Invalid number of elevation cuts: {}", | ||||
|                        numberOfElevationCuts); | ||||
|          messageValid = false; | ||||
|       } | ||||
|    } | ||||
| 
 | ||||
|    if (!messageValid) | ||||
|  |  | |||
|  | @ -103,17 +103,21 @@ set(SRC_WSR88D source/scwx/wsr88d/ar2v_file.cpp | |||
| set(HDR_WSR88D_RDA include/scwx/wsr88d/rda/clutter_filter_bypass_map.hpp | ||||
|                    include/scwx/wsr88d/rda/clutter_filter_map.hpp | ||||
|                    include/scwx/wsr88d/rda/digital_radar_data.hpp | ||||
|                    include/scwx/wsr88d/rda/digital_radar_data_generic.hpp | ||||
|                    include/scwx/wsr88d/rda/generic_radar_data.hpp | ||||
|                    include/scwx/wsr88d/rda/level2_message.hpp | ||||
|                    include/scwx/wsr88d/rda/level2_message_factory.hpp | ||||
|                    include/scwx/wsr88d/rda/level2_message_header.hpp | ||||
|                    include/scwx/wsr88d/rda/performance_maintenance_data.hpp | ||||
|                    include/scwx/wsr88d/rda/rda_adaptation_data.hpp | ||||
|                    include/scwx/wsr88d/rda/rda_status_data.hpp | ||||
|                    include/scwx/wsr88d/rda/types.hpp | ||||
|                    include/scwx/wsr88d/rda/rda_types.hpp | ||||
|                    include/scwx/wsr88d/rda/volume_coverage_pattern_data.hpp) | ||||
| set(SRC_WSR88D_RDA source/scwx/wsr88d/rda/clutter_filter_bypass_map.cpp | ||||
|                    source/scwx/wsr88d/rda/clutter_filter_map.cpp | ||||
|                    source/scwx/wsr88d/rda/digital_radar_data.cpp | ||||
|                    source/scwx/wsr88d/rda/digital_radar_data_generic.cpp | ||||
|                    source/scwx/wsr88d/rda/generic_radar_data.cpp | ||||
|                    source/scwx/wsr88d/rda/level2_message.cpp | ||||
|                    source/scwx/wsr88d/rda/level2_message_factory.cpp | ||||
|                    source/scwx/wsr88d/rda/level2_message_header.cpp | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Dan Paulat
						Dan Paulat