mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 01:50:06 +00:00 
			
		
		
		
	Deriving level 2 product view
This commit is contained in:
		
							parent
							
								
									2f1f15938a
								
							
						
					
					
						commit
						9ee52e7a6b
					
				
					 12 changed files with 602 additions and 354 deletions
				
			
		
							
								
								
									
										404
									
								
								scwx-qt/source/scwx/qt/view/level2_product_view.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										404
									
								
								scwx-qt/source/scwx/qt/view/level2_product_view.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,404 @@ | |||
| #include <scwx/qt/view/level2_product_view.hpp> | ||||
| #include <scwx/common/constants.hpp> | ||||
| 
 | ||||
| #include <boost/log/trivial.hpp> | ||||
| #include <boost/range/irange.hpp> | ||||
| #include <boost/timer/timer.hpp> | ||||
| 
 | ||||
| namespace scwx | ||||
| { | ||||
| namespace qt | ||||
| { | ||||
| namespace view | ||||
| { | ||||
| 
 | ||||
| static const std::string logPrefix_ = "[scwx::qt::view::level2_product_view] "; | ||||
| 
 | ||||
| static constexpr uint32_t VERTICES_PER_BIN  = 6; | ||||
| static constexpr uint32_t VALUES_PER_VERTEX = 2; | ||||
| 
 | ||||
| static const std::unordered_map<std::string, wsr88d::rda::DataBlockType> | ||||
|    blockTypes_ {{PRODUCT_L2_REF, wsr88d::rda::DataBlockType::MomentRef}, | ||||
|                 {PRODUCT_L2_VEL, wsr88d::rda::DataBlockType::MomentVel}, | ||||
|                 {PRODUCT_L2_SW, wsr88d::rda::DataBlockType::MomentSw}, | ||||
|                 {PRODUCT_L2_ZDR, wsr88d::rda::DataBlockType::MomentZdr}, | ||||
|                 {PRODUCT_L2_PHI, wsr88d::rda::DataBlockType::MomentPhi}, | ||||
|                 {PRODUCT_L2_RHO, wsr88d::rda::DataBlockType::MomentRho}, | ||||
|                 {PRODUCT_L2_CFP, wsr88d::rda::DataBlockType::MomentCfp}}; | ||||
| 
 | ||||
| static std::chrono::system_clock::time_point | ||||
| TimePoint(uint16_t modifiedJulianDate, uint32_t milliseconds); | ||||
| 
 | ||||
| class Level2ProductViewImpl | ||||
| { | ||||
| public: | ||||
|    explicit Level2ProductViewImpl( | ||||
|       const std::string&                            productName, | ||||
|       std::shared_ptr<manager::RadarProductManager> radarProductManager) : | ||||
|        radarProductManager_ {radarProductManager}, sweepTime_ {}, colorTable_ {} | ||||
|    { | ||||
|       auto it = blockTypes_.find(productName); | ||||
| 
 | ||||
|       if (it != blockTypes_.end()) | ||||
|       { | ||||
|          dataBlockType_ = it->second; | ||||
|       } | ||||
|       else | ||||
|       { | ||||
|          BOOST_LOG_TRIVIAL(warning) | ||||
|             << logPrefix_ << "Unknown product: \"" << productName << "\""; | ||||
|          dataBlockType_ = wsr88d::rda::DataBlockType::Unknown; | ||||
|       } | ||||
|    } | ||||
|    ~Level2ProductViewImpl() = default; | ||||
| 
 | ||||
|    wsr88d::rda::DataBlockType                    dataBlockType_; | ||||
|    std::shared_ptr<manager::RadarProductManager> radarProductManager_; | ||||
| 
 | ||||
|    std::vector<float>    vertices_; | ||||
|    std::vector<uint8_t>  dataMoments8_; | ||||
|    std::vector<uint16_t> dataMoments16_; | ||||
| 
 | ||||
|    std::chrono::system_clock::time_point sweepTime_; | ||||
| 
 | ||||
|    std::vector<boost::gil::rgba8_pixel_t> colorTable_; | ||||
| }; | ||||
| 
 | ||||
| Level2ProductView::Level2ProductView( | ||||
|    const std::string&                            productName, | ||||
|    std::shared_ptr<manager::RadarProductManager> radarProductManager) : | ||||
|     p(std::make_unique<Level2ProductViewImpl>(productName, radarProductManager)) | ||||
| { | ||||
|    connect(radarProductManager.get(), | ||||
|            &manager::RadarProductManager::Level2DataLoaded, | ||||
|            this, | ||||
|            &Level2ProductView::ComputeSweep); | ||||
| } | ||||
| Level2ProductView::~Level2ProductView() = default; | ||||
| 
 | ||||
| const std::vector<boost::gil::rgba8_pixel_t>& | ||||
| Level2ProductView::color_table() const | ||||
| { | ||||
|    if (p->colorTable_.size() == 0) | ||||
|    { | ||||
|       return RadarProductView::color_table(); | ||||
|    } | ||||
|    else | ||||
|    { | ||||
|       return p->colorTable_; | ||||
|    } | ||||
| } | ||||
| 
 | ||||
| std::chrono::system_clock::time_point Level2ProductView::sweep_time() const | ||||
| { | ||||
|    return p->sweepTime_; | ||||
| } | ||||
| 
 | ||||
| const std::vector<float>& Level2ProductView::vertices() const | ||||
| { | ||||
|    return p->vertices_; | ||||
| } | ||||
| 
 | ||||
| std::tuple<const void*, size_t, size_t> Level2ProductView::GetMomentData() const | ||||
| { | ||||
|    const void* data; | ||||
|    size_t      dataSize; | ||||
|    size_t      componentSize; | ||||
| 
 | ||||
|    if (p->dataMoments8_.size() > 0) | ||||
|    { | ||||
|       data          = p->dataMoments8_.data(); | ||||
|       dataSize      = p->dataMoments8_.size() * sizeof(uint8_t); | ||||
|       componentSize = 1; | ||||
|    } | ||||
|    else | ||||
|    { | ||||
|       data          = p->dataMoments16_.data(); | ||||
|       dataSize      = p->dataMoments16_.size() * sizeof(uint16_t); | ||||
|       componentSize = 2; | ||||
|    } | ||||
| 
 | ||||
|    return std::tie(data, dataSize, componentSize); | ||||
| } | ||||
| 
 | ||||
| void Level2ProductView::LoadColorTable( | ||||
|    std::shared_ptr<common::ColorTable> colorTable) | ||||
| { | ||||
|    // TODO: Make size, offset and scale dynamic
 | ||||
|    const float offset = 66.0f; | ||||
|    const float scale  = 2.0f; | ||||
| 
 | ||||
|    std::vector<boost::gil::rgba8_pixel_t>& lut = p->colorTable_; | ||||
|    lut.resize(254); | ||||
| 
 | ||||
|    auto dataRange = boost::irange<uint16_t>(2, 255); | ||||
| 
 | ||||
|    std::for_each(std::execution::par_unseq, | ||||
|                  dataRange.begin(), | ||||
|                  dataRange.end(), | ||||
|                  [&](uint16_t i) { | ||||
|                     float f                     = (i - offset) / scale; | ||||
|                     lut[i - *dataRange.begin()] = colorTable->Color(f); | ||||
|                  }); | ||||
| 
 | ||||
|    emit ColorTableLoaded(); | ||||
| } | ||||
| 
 | ||||
| void Level2ProductView::ComputeSweep() | ||||
| { | ||||
|    BOOST_LOG_TRIVIAL(debug) << logPrefix_ << "ComputeSweep()"; | ||||
| 
 | ||||
|    boost::timer::cpu_timer timer; | ||||
| 
 | ||||
|    if (p->dataBlockType_ == wsr88d::rda::DataBlockType::Unknown) | ||||
|    { | ||||
|       return; | ||||
|    } | ||||
| 
 | ||||
|    std::shared_ptr<const wsr88d::Ar2vFile> level2Data = | ||||
|       p->radarProductManager_->level2_data(); | ||||
|    if (level2Data == nullptr) | ||||
|    { | ||||
|       return; | ||||
|    } | ||||
| 
 | ||||
|    // TODO: Pick this based on radar data
 | ||||
|    const std::vector<float>& coordinates = | ||||
|       p->radarProductManager_->coordinates(common::RadialSize::_0_5Degree); | ||||
| 
 | ||||
|    // TODO: Pick this based on view settings
 | ||||
|    auto radarData = level2Data->radar_data()[0]; | ||||
| 
 | ||||
|    p->sweepTime_ = TimePoint(radarData[0]->modified_julian_date(), | ||||
|                              radarData[0]->collection_time()); | ||||
| 
 | ||||
|    // Calculate vertices
 | ||||
|    timer.start(); | ||||
| 
 | ||||
|    auto momentData0 = radarData[0]->moment_data_block(p->dataBlockType_); | ||||
| 
 | ||||
|    // Setup vertex vector
 | ||||
|    std::vector<float>& vertices = p->vertices_; | ||||
|    const size_t        radials  = radarData.size(); | ||||
|    const uint32_t      gates    = momentData0->number_of_data_moment_gates(); | ||||
|    size_t              vIndex   = 0; | ||||
|    vertices.clear(); | ||||
|    vertices.resize(radials * gates * VERTICES_PER_BIN * VALUES_PER_VERTEX); | ||||
| 
 | ||||
|    // Setup data moment vector
 | ||||
|    std::vector<uint8_t>&  dataMoments8  = p->dataMoments8_; | ||||
|    std::vector<uint16_t>& dataMoments16 = p->dataMoments16_; | ||||
|    size_t                 mIndex        = 0; | ||||
| 
 | ||||
|    if (momentData0->data_word_size() == 8) | ||||
|    { | ||||
|       dataMoments16.resize(0); | ||||
|       dataMoments16.shrink_to_fit(); | ||||
| 
 | ||||
|       dataMoments8.resize(radials * gates * VERTICES_PER_BIN); | ||||
|    } | ||||
|    else | ||||
|    { | ||||
|       dataMoments8.resize(0); | ||||
|       dataMoments8.shrink_to_fit(); | ||||
| 
 | ||||
|       dataMoments16.resize(radials * gates * VERTICES_PER_BIN); | ||||
|    } | ||||
| 
 | ||||
|    // Compute threshold at which to display an individual bin
 | ||||
|    const float    scale  = momentData0->scale(); | ||||
|    const float    offset = momentData0->offset(); | ||||
|    const uint16_t snrThreshold = | ||||
|       std::lroundf(momentData0->snr_threshold_raw() * scale / 10 + offset); | ||||
| 
 | ||||
|    // Azimuth resolution spacing:
 | ||||
|    //   1 = 0.5 degrees
 | ||||
|    //   2 = 1.0 degrees
 | ||||
|    const float radialMultiplier = | ||||
|       2.0f / | ||||
|       std::clamp<int8_t>(radarData[0]->azimuth_resolution_spacing(), 1, 2); | ||||
| 
 | ||||
|    const float    startAngle  = radarData[0]->azimuth_angle(); | ||||
|    const uint16_t startRadial = std::lroundf(startAngle * radialMultiplier); | ||||
| 
 | ||||
|    for (uint16_t radial = 0; radial < radials; ++radial) | ||||
|    { | ||||
|       auto radialData = radarData[radial]; | ||||
|       auto momentData = radarData[radial]->moment_data_block(p->dataBlockType_); | ||||
| 
 | ||||
|       if (momentData0->data_word_size() != momentData->data_word_size()) | ||||
|       { | ||||
|          BOOST_LOG_TRIVIAL(warning) | ||||
|             << logPrefix_ << "Radial " << radial << " has different word size"; | ||||
|          continue; | ||||
|       } | ||||
| 
 | ||||
|       // Compute gate interval
 | ||||
|       const uint16_t dataMomentRange = momentData->data_moment_range_raw(); | ||||
|       const uint16_t dataMomentInterval = | ||||
|          momentData->data_moment_range_sample_interval_raw(); | ||||
|       const uint16_t dataMomentIntervalH = dataMomentInterval / 2; | ||||
| 
 | ||||
|       // Compute gate size (number of base 250m gates per bin)
 | ||||
|       const uint16_t gateSize = std::max<uint16_t>(1, dataMomentInterval / 250); | ||||
| 
 | ||||
|       // Compute gate range [startGate, endGate)
 | ||||
|       const uint16_t startGate = (dataMomentRange - dataMomentIntervalH) / 250; | ||||
|       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 uint8_t*  dataMomentsArray8  = nullptr; | ||||
|       const uint16_t* dataMomentsArray16 = nullptr; | ||||
| 
 | ||||
|       if (momentData->data_word_size() == 8) | ||||
|       { | ||||
|          dataMomentsArray8 = | ||||
|             reinterpret_cast<const uint8_t*>(momentData->data_moments()); | ||||
|       } | ||||
|       else | ||||
|       { | ||||
|          dataMomentsArray16 = | ||||
|             reinterpret_cast<const uint16_t*>(momentData->data_moments()); | ||||
|       } | ||||
| 
 | ||||
|       for (uint16_t gate = startGate, i = 0; gate + gateSize <= endGate; | ||||
|            gate += gateSize, ++i) | ||||
|       { | ||||
|          size_t vertexCount = (gate > 0) ? 6 : 3; | ||||
| 
 | ||||
|          // Store data moment value
 | ||||
|          if (dataMomentsArray8 != nullptr) | ||||
|          { | ||||
|             uint8_t dataValue = dataMomentsArray8[i]; | ||||
|             if (dataValue < snrThreshold) | ||||
|             { | ||||
|                continue; | ||||
|             } | ||||
| 
 | ||||
|             for (size_t m = 0; m < vertexCount; m++) | ||||
|             { | ||||
|                dataMoments8[mIndex++] = dataMomentsArray8[i]; | ||||
|             } | ||||
|          } | ||||
|          else | ||||
|          { | ||||
|             uint16_t dataValue = dataMomentsArray16[i]; | ||||
|             if (dataValue < snrThreshold) | ||||
|             { | ||||
|                continue; | ||||
|             } | ||||
| 
 | ||||
|             for (size_t m = 0; m < vertexCount; m++) | ||||
|             { | ||||
|                dataMoments16[mIndex++] = dataMomentsArray16[i]; | ||||
|             } | ||||
|          } | ||||
| 
 | ||||
|          // Store vertices
 | ||||
|          if (gate > 0) | ||||
|          { | ||||
|             const uint16_t baseCoord = gate - 1; | ||||
| 
 | ||||
|             size_t offset1 = ((startRadial + radial) % common::MAX_RADIALS * | ||||
|                                  common::MAX_DATA_MOMENT_GATES + | ||||
|                               baseCoord) * | ||||
|                              2; | ||||
|             size_t offset2 = offset1 + gateSize * 2; | ||||
|             size_t offset3 = | ||||
|                (((startRadial + radial + 1) % common::MAX_RADIALS) * | ||||
|                    common::MAX_DATA_MOMENT_GATES + | ||||
|                 baseCoord) * | ||||
|                2; | ||||
|             size_t offset4 = offset3 + gateSize * 2; | ||||
| 
 | ||||
|             vertices[vIndex++] = coordinates[offset1]; | ||||
|             vertices[vIndex++] = coordinates[offset1 + 1]; | ||||
| 
 | ||||
|             vertices[vIndex++] = coordinates[offset2]; | ||||
|             vertices[vIndex++] = coordinates[offset2 + 1]; | ||||
| 
 | ||||
|             vertices[vIndex++] = coordinates[offset3]; | ||||
|             vertices[vIndex++] = coordinates[offset3 + 1]; | ||||
| 
 | ||||
|             vertices[vIndex++] = coordinates[offset3]; | ||||
|             vertices[vIndex++] = coordinates[offset3 + 1]; | ||||
| 
 | ||||
|             vertices[vIndex++] = coordinates[offset4]; | ||||
|             vertices[vIndex++] = coordinates[offset4 + 1]; | ||||
| 
 | ||||
|             vertices[vIndex++] = coordinates[offset2]; | ||||
|             vertices[vIndex++] = coordinates[offset2 + 1]; | ||||
| 
 | ||||
|             vertexCount = 6; | ||||
|          } | ||||
|          else | ||||
|          { | ||||
|             const uint16_t baseCoord = gate; | ||||
| 
 | ||||
|             size_t offset1 = ((startRadial + radial) % common::MAX_RADIALS * | ||||
|                                  common::MAX_DATA_MOMENT_GATES + | ||||
|                               baseCoord) * | ||||
|                              2; | ||||
|             size_t offset2 = | ||||
|                (((startRadial + radial + 1) % common::MAX_RADIALS) * | ||||
|                    common::MAX_DATA_MOMENT_GATES + | ||||
|                 baseCoord) * | ||||
|                2; | ||||
| 
 | ||||
|             // TODO: Radar location
 | ||||
|             vertices[vIndex++] = 38.6986f; | ||||
|             vertices[vIndex++] = -90.6828f; | ||||
| 
 | ||||
|             vertices[vIndex++] = coordinates[offset1]; | ||||
|             vertices[vIndex++] = coordinates[offset1 + 1]; | ||||
| 
 | ||||
|             vertices[vIndex++] = coordinates[offset2]; | ||||
|             vertices[vIndex++] = coordinates[offset2 + 1]; | ||||
| 
 | ||||
|             vertexCount = 3; | ||||
|          } | ||||
|       } | ||||
|    } | ||||
|    vertices.resize(vIndex); | ||||
| 
 | ||||
|    if (momentData0->data_word_size() == 8) | ||||
|    { | ||||
|       dataMoments8.resize(mIndex); | ||||
|    } | ||||
|    else | ||||
|    { | ||||
|       dataMoments16.resize(mIndex); | ||||
|    } | ||||
| 
 | ||||
|    timer.stop(); | ||||
|    BOOST_LOG_TRIVIAL(debug) | ||||
|       << logPrefix_ << "Vertices calculated in " << timer.format(6, "%ws"); | ||||
| 
 | ||||
|    emit SweepComputed(); | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<Level2ProductView> Level2ProductView::Create( | ||||
|    const std::string&                            productName, | ||||
|    std::shared_ptr<manager::RadarProductManager> radarProductManager) | ||||
| { | ||||
|    return std::make_shared<Level2ProductView>(productName, radarProductManager); | ||||
| } | ||||
| 
 | ||||
| static std::chrono::system_clock::time_point | ||||
| TimePoint(uint16_t modifiedJulianDate, uint32_t milliseconds) | ||||
| { | ||||
|    using namespace std::chrono; | ||||
|    using sys_days       = time_point<system_clock, days>; | ||||
|    constexpr auto epoch = sys_days {1969y / December / 31d}; | ||||
| 
 | ||||
|    return epoch + (modifiedJulianDate * 24h) + | ||||
|           std::chrono::milliseconds {milliseconds}; | ||||
| } | ||||
| 
 | ||||
| } // namespace view
 | ||||
| } // namespace qt
 | ||||
| } // namespace scwx
 | ||||
							
								
								
									
										59
									
								
								scwx-qt/source/scwx/qt/view/level2_product_view.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								scwx-qt/source/scwx/qt/view/level2_product_view.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,59 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <scwx/common/color_table.hpp> | ||||
| #include <scwx/qt/manager/radar_product_manager.hpp> | ||||
| #include <scwx/qt/view/radar_product_view.hpp> | ||||
| 
 | ||||
| #include <chrono> | ||||
| #include <memory> | ||||
| #include <vector> | ||||
| 
 | ||||
| namespace scwx | ||||
| { | ||||
| namespace qt | ||||
| { | ||||
| namespace view | ||||
| { | ||||
| 
 | ||||
| const std::string PRODUCT_L2_REF = "L2REF"; | ||||
| const std::string PRODUCT_L2_VEL = "L2VEL"; | ||||
| const std::string PRODUCT_L2_SW  = "L2SW"; | ||||
| const std::string PRODUCT_L2_ZDR = "L2ZDR"; | ||||
| const std::string PRODUCT_L2_PHI = "L2PHI"; | ||||
| const std::string PRODUCT_L2_RHO = "L2RHO"; | ||||
| const std::string PRODUCT_L2_CFP = "L2CFP"; | ||||
| 
 | ||||
| class Level2ProductViewImpl; | ||||
| 
 | ||||
| class Level2ProductView : public RadarProductView | ||||
| { | ||||
|    Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|    explicit Level2ProductView( | ||||
|       const std::string&                            productName, | ||||
|       std::shared_ptr<manager::RadarProductManager> radarProductManager); | ||||
|    ~Level2ProductView(); | ||||
| 
 | ||||
|    const std::vector<boost::gil::rgba8_pixel_t>& color_table() const; | ||||
|    std::chrono::system_clock::time_point         sweep_time() const; | ||||
|    const std::vector<float>&                     vertices() const; | ||||
| 
 | ||||
|    void LoadColorTable(std::shared_ptr<common::ColorTable> colorTable); | ||||
| 
 | ||||
|    std::tuple<const void*, size_t, size_t> GetMomentData() const; | ||||
| 
 | ||||
|    static std::shared_ptr<Level2ProductView> | ||||
|    Create(const std::string&                            productName, | ||||
|           std::shared_ptr<manager::RadarProductManager> radarProductManager); | ||||
| 
 | ||||
| protected slots: | ||||
|    void ComputeSweep(); | ||||
| 
 | ||||
| private: | ||||
|    std::unique_ptr<Level2ProductViewImpl> p; | ||||
| }; | ||||
| 
 | ||||
| } // namespace view
 | ||||
| } // namespace qt
 | ||||
| } // namespace scwx
 | ||||
|  | @ -14,57 +14,31 @@ namespace view | |||
| 
 | ||||
| static const std::string logPrefix_ = "[scwx::qt::view::radar_product_view] "; | ||||
| 
 | ||||
| static constexpr uint32_t VERTICES_PER_BIN  = 6; | ||||
| static constexpr uint32_t VALUES_PER_VERTEX = 2; | ||||
| 
 | ||||
| static std::chrono::system_clock::time_point | ||||
| TimePoint(uint16_t modifiedJulianDate, uint32_t milliseconds); | ||||
| static const std::vector<boost::gil::rgba8_pixel_t> DEFAULT_COLOR_TABLE = { | ||||
|    boost::gil::rgba8_pixel_t(0, 128, 0, 255), | ||||
|    boost::gil::rgba8_pixel_t(255, 192, 0, 255), | ||||
|    boost::gil::rgba8_pixel_t(255, 0, 0, 255)}; | ||||
| 
 | ||||
| class RadarProductViewImpl | ||||
| { | ||||
| public: | ||||
|    explicit RadarProductViewImpl( | ||||
|       std::shared_ptr<manager::RadarProductManager> radarProductManager) : | ||||
|        radarProductManager_(radarProductManager), | ||||
|        sweepTime_(), | ||||
|        colorTable_ {boost::gil::rgba8_pixel_t(0, 128, 0, 255), | ||||
|                     boost::gil::rgba8_pixel_t(255, 192, 0, 255), | ||||
|                     boost::gil::rgba8_pixel_t(255, 0, 0, 255)} | ||||
|    { | ||||
|    } | ||||
|    ~RadarProductViewImpl() = default; | ||||
| 
 | ||||
|    std::shared_ptr<manager::RadarProductManager> radarProductManager_; | ||||
| 
 | ||||
|    std::vector<float>    vertices_; | ||||
|    std::vector<uint8_t>  dataMoments8_; | ||||
|    std::vector<uint16_t> dataMoments16_; | ||||
| 
 | ||||
|    std::chrono::system_clock::time_point sweepTime_; | ||||
| 
 | ||||
|    std::vector<boost::gil::rgba8_pixel_t> colorTable_; | ||||
|    explicit RadarProductViewImpl() = default; | ||||
|    ~RadarProductViewImpl()         = default; | ||||
| }; | ||||
| 
 | ||||
| RadarProductView::RadarProductView( | ||||
|    std::shared_ptr<manager::RadarProductManager> radarProductManager) : | ||||
|     p(std::make_unique<RadarProductViewImpl>(radarProductManager)) | ||||
| { | ||||
|    connect(radarProductManager.get(), | ||||
|            &manager::RadarProductManager::Level2DataLoaded, | ||||
|            this, | ||||
|            &RadarProductView::ComputeSweep); | ||||
| } | ||||
| RadarProductView::RadarProductView() : | ||||
|     p(std::make_unique<RadarProductViewImpl>()) {}; | ||||
| RadarProductView::~RadarProductView() = default; | ||||
| 
 | ||||
| const std::vector<float>& RadarProductView::vertices() const | ||||
| { | ||||
|    return p->vertices_; | ||||
| } | ||||
| 
 | ||||
| const std::vector<boost::gil::rgba8_pixel_t>& | ||||
| RadarProductView::color_table() const | ||||
| { | ||||
|    return p->colorTable_; | ||||
|    return DEFAULT_COLOR_TABLE; | ||||
| } | ||||
| 
 | ||||
| std::chrono::system_clock::time_point RadarProductView::sweep_time() const | ||||
| { | ||||
|    return {}; | ||||
| } | ||||
| 
 | ||||
| void RadarProductView::Initialize() | ||||
|  | @ -72,300 +46,13 @@ void RadarProductView::Initialize() | |||
|    ComputeSweep(); | ||||
| } | ||||
| 
 | ||||
| std::tuple<const void*, size_t, size_t> RadarProductView::GetMomentData() | ||||
| { | ||||
|    const void* data; | ||||
|    size_t      dataSize; | ||||
|    size_t      componentSize; | ||||
| 
 | ||||
|    if (p->dataMoments8_.size() > 0) | ||||
|    { | ||||
|       data          = p->dataMoments8_.data(); | ||||
|       dataSize      = p->dataMoments8_.size() * sizeof(uint8_t); | ||||
|       componentSize = 1; | ||||
|    } | ||||
|    else | ||||
|    { | ||||
|       data          = p->dataMoments16_.data(); | ||||
|       dataSize      = p->dataMoments16_.size() * sizeof(uint16_t); | ||||
|       componentSize = 2; | ||||
|    } | ||||
| 
 | ||||
|    return std::tie(data, dataSize, componentSize); | ||||
| } | ||||
| 
 | ||||
| void RadarProductView::LoadColorTable( | ||||
|    std::shared_ptr<common::ColorTable> colorTable) | ||||
| { | ||||
|    // TODO: Make size, offset and scale dynamic
 | ||||
|    const float offset = 66.0f; | ||||
|    const float scale  = 2.0f; | ||||
| 
 | ||||
|    std::vector<boost::gil::rgba8_pixel_t>& lut = p->colorTable_; | ||||
|    lut.resize(254); | ||||
| 
 | ||||
|    auto dataRange = boost::irange<uint16_t>(2, 255); | ||||
| 
 | ||||
|    std::for_each(std::execution::par_unseq, | ||||
|                  dataRange.begin(), | ||||
|                  dataRange.end(), | ||||
|                  [&](uint16_t i) { | ||||
|                     float f                     = (i - offset) / scale; | ||||
|                     lut[i - *dataRange.begin()] = colorTable->Color(f); | ||||
|                  }); | ||||
| 
 | ||||
|    emit ColorTableLoaded(); | ||||
| } | ||||
| 
 | ||||
| std::chrono::system_clock::time_point RadarProductView::SweepTime() | ||||
| { | ||||
|    return p->sweepTime_; | ||||
| } | ||||
| 
 | ||||
| void RadarProductView::ComputeSweep() | ||||
| { | ||||
|    BOOST_LOG_TRIVIAL(debug) << logPrefix_ << "ComputeSweep()"; | ||||
| 
 | ||||
|    boost::timer::cpu_timer timer; | ||||
| 
 | ||||
|    // TODO: Pick this based on radar data
 | ||||
|    const std::vector<float>& coordinates = | ||||
|       p->radarProductManager_->coordinates(common::RadialSize::_0_5Degree); | ||||
| 
 | ||||
|    std::shared_ptr<const wsr88d::Ar2vFile> level2Data = | ||||
|       p->radarProductManager_->level2_data(); | ||||
|    if (level2Data == nullptr) | ||||
|    { | ||||
|       return; | ||||
|    } | ||||
| 
 | ||||
|    // TODO: Pick these based on view settings
 | ||||
|    auto                       radarData = level2Data->radar_data()[0]; | ||||
|    wsr88d::rda::DataBlockType blockType = wsr88d::rda::DataBlockType::MomentRef; | ||||
| 
 | ||||
|    p->sweepTime_ = TimePoint(radarData[0]->modified_julian_date(), | ||||
|                              radarData[0]->collection_time()); | ||||
| 
 | ||||
|    // Calculate vertices
 | ||||
|    timer.start(); | ||||
| 
 | ||||
|    auto momentData0 = radarData[0]->moment_data_block(blockType); | ||||
| 
 | ||||
|    // Setup vertex vector
 | ||||
|    std::vector<float>& vertices = p->vertices_; | ||||
|    const size_t        radials  = radarData.size(); | ||||
|    const uint32_t      gates    = momentData0->number_of_data_moment_gates(); | ||||
|    size_t              vIndex   = 0; | ||||
|    vertices.clear(); | ||||
|    vertices.resize(radials * gates * VERTICES_PER_BIN * VALUES_PER_VERTEX); | ||||
| 
 | ||||
|    // Setup data moment vector
 | ||||
|    std::vector<uint8_t>&  dataMoments8  = p->dataMoments8_; | ||||
|    std::vector<uint16_t>& dataMoments16 = p->dataMoments16_; | ||||
|    size_t                 mIndex        = 0; | ||||
| 
 | ||||
|    if (momentData0->data_word_size() == 8) | ||||
|    { | ||||
|       dataMoments16.resize(0); | ||||
|       dataMoments16.shrink_to_fit(); | ||||
| 
 | ||||
|       dataMoments8.resize(radials * gates * VERTICES_PER_BIN); | ||||
|    } | ||||
|    else | ||||
|    { | ||||
|       dataMoments8.resize(0); | ||||
|       dataMoments8.shrink_to_fit(); | ||||
| 
 | ||||
|       dataMoments16.resize(radials * gates * VERTICES_PER_BIN); | ||||
|    } | ||||
| 
 | ||||
|    // Compute threshold at which to display an individual bin
 | ||||
|    const float    scale  = momentData0->scale(); | ||||
|    const float    offset = momentData0->offset(); | ||||
|    const uint16_t snrThreshold = | ||||
|       std::lroundf(momentData0->snr_threshold_raw() * scale / 10 + offset); | ||||
| 
 | ||||
|    // Azimuth resolution spacing:
 | ||||
|    //   1 = 0.5 degrees
 | ||||
|    //   2 = 1.0 degrees
 | ||||
|    const float radialMultiplier = | ||||
|       2.0f / | ||||
|       std::clamp<int8_t>(radarData[0]->azimuth_resolution_spacing(), 1, 2); | ||||
| 
 | ||||
|    const float    startAngle  = radarData[0]->azimuth_angle(); | ||||
|    const uint16_t startRadial = std::lroundf(startAngle * radialMultiplier); | ||||
| 
 | ||||
|    for (uint16_t radial = 0; radial < radials; ++radial) | ||||
|    { | ||||
|       auto radialData = radarData[radial]; | ||||
|       auto momentData = radarData[radial]->moment_data_block(blockType); | ||||
| 
 | ||||
|       if (momentData0->data_word_size() != momentData->data_word_size()) | ||||
|       { | ||||
|          BOOST_LOG_TRIVIAL(warning) | ||||
|             << logPrefix_ << "Radial " << radial << " has different word size"; | ||||
|          continue; | ||||
|       } | ||||
| 
 | ||||
|       // Compute gate interval
 | ||||
|       const uint16_t dataMomentRange = momentData->data_moment_range_raw(); | ||||
|       const uint16_t dataMomentInterval = | ||||
|          momentData->data_moment_range_sample_interval_raw(); | ||||
|       const uint16_t dataMomentIntervalH = dataMomentInterval / 2; | ||||
| 
 | ||||
|       // Compute gate size (number of base 250m gates per bin)
 | ||||
|       const uint16_t gateSize = std::max<uint16_t>(1, dataMomentInterval / 250); | ||||
| 
 | ||||
|       // Compute gate range [startGate, endGate)
 | ||||
|       const uint16_t startGate = (dataMomentRange - dataMomentIntervalH) / 250; | ||||
|       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 uint8_t*  dataMomentsArray8  = nullptr; | ||||
|       const uint16_t* dataMomentsArray16 = nullptr; | ||||
| 
 | ||||
|       if (momentData->data_word_size() == 8) | ||||
|       { | ||||
|          dataMomentsArray8 = | ||||
|             reinterpret_cast<const uint8_t*>(momentData->data_moments()); | ||||
|       } | ||||
|       else | ||||
|       { | ||||
|          dataMomentsArray16 = | ||||
|             reinterpret_cast<const uint16_t*>(momentData->data_moments()); | ||||
|       } | ||||
| 
 | ||||
|       for (uint16_t gate = startGate, i = 0; gate + gateSize <= endGate; | ||||
|            gate += gateSize, ++i) | ||||
|       { | ||||
|          size_t vertexCount = (gate > 0) ? 6 : 3; | ||||
| 
 | ||||
|          // Store data moment value
 | ||||
|          if (dataMomentsArray8 != nullptr) | ||||
|          { | ||||
|             uint8_t dataValue = dataMomentsArray8[i]; | ||||
|             if (dataValue < snrThreshold) | ||||
|             { | ||||
|                continue; | ||||
|             } | ||||
| 
 | ||||
|             for (size_t m = 0; m < vertexCount; m++) | ||||
|             { | ||||
|                dataMoments8[mIndex++] = dataMomentsArray8[i]; | ||||
|             } | ||||
|          } | ||||
|          else | ||||
|          { | ||||
|             uint16_t dataValue = dataMomentsArray16[i]; | ||||
|             if (dataValue < snrThreshold) | ||||
|             { | ||||
|                continue; | ||||
|             } | ||||
| 
 | ||||
|             for (size_t m = 0; m < vertexCount; m++) | ||||
|             { | ||||
|                dataMoments16[mIndex++] = dataMomentsArray16[i]; | ||||
|             } | ||||
|          } | ||||
| 
 | ||||
|          // Store vertices
 | ||||
|          if (gate > 0) | ||||
|          { | ||||
|             const uint16_t baseCoord = gate - 1; | ||||
| 
 | ||||
|             size_t offset1 = ((startRadial + radial) % common::MAX_RADIALS * | ||||
|                                  common::MAX_DATA_MOMENT_GATES + | ||||
|                               baseCoord) * | ||||
|                              2; | ||||
|             size_t offset2 = offset1 + gateSize * 2; | ||||
|             size_t offset3 = | ||||
|                (((startRadial + radial + 1) % common::MAX_RADIALS) * | ||||
|                    common::MAX_DATA_MOMENT_GATES + | ||||
|                 baseCoord) * | ||||
|                2; | ||||
|             size_t offset4 = offset3 + gateSize * 2; | ||||
| 
 | ||||
|             vertices[vIndex++] = coordinates[offset1]; | ||||
|             vertices[vIndex++] = coordinates[offset1 + 1]; | ||||
| 
 | ||||
|             vertices[vIndex++] = coordinates[offset2]; | ||||
|             vertices[vIndex++] = coordinates[offset2 + 1]; | ||||
| 
 | ||||
|             vertices[vIndex++] = coordinates[offset3]; | ||||
|             vertices[vIndex++] = coordinates[offset3 + 1]; | ||||
| 
 | ||||
|             vertices[vIndex++] = coordinates[offset3]; | ||||
|             vertices[vIndex++] = coordinates[offset3 + 1]; | ||||
| 
 | ||||
|             vertices[vIndex++] = coordinates[offset4]; | ||||
|             vertices[vIndex++] = coordinates[offset4 + 1]; | ||||
| 
 | ||||
|             vertices[vIndex++] = coordinates[offset2]; | ||||
|             vertices[vIndex++] = coordinates[offset2 + 1]; | ||||
| 
 | ||||
|             vertexCount = 6; | ||||
|          } | ||||
|          else | ||||
|          { | ||||
|             const uint16_t baseCoord = gate; | ||||
| 
 | ||||
|             size_t offset1 = ((startRadial + radial) % common::MAX_RADIALS * | ||||
|                                  common::MAX_DATA_MOMENT_GATES + | ||||
|                               baseCoord) * | ||||
|                              2; | ||||
|             size_t offset2 = | ||||
|                (((startRadial + radial + 1) % common::MAX_RADIALS) * | ||||
|                    common::MAX_DATA_MOMENT_GATES + | ||||
|                 baseCoord) * | ||||
|                2; | ||||
| 
 | ||||
|             // TODO: Radar location
 | ||||
|             vertices[vIndex++] = 38.6986f; | ||||
|             vertices[vIndex++] = -90.6828f; | ||||
| 
 | ||||
|             vertices[vIndex++] = coordinates[offset1]; | ||||
|             vertices[vIndex++] = coordinates[offset1 + 1]; | ||||
| 
 | ||||
|             vertices[vIndex++] = coordinates[offset2]; | ||||
|             vertices[vIndex++] = coordinates[offset2 + 1]; | ||||
| 
 | ||||
|             vertexCount = 3; | ||||
|          } | ||||
|       } | ||||
|    } | ||||
|    vertices.resize(vIndex); | ||||
| 
 | ||||
|    if (momentData0->data_word_size() == 8) | ||||
|    { | ||||
|       dataMoments8.resize(mIndex); | ||||
|    } | ||||
|    else | ||||
|    { | ||||
|       dataMoments16.resize(mIndex); | ||||
|    } | ||||
| 
 | ||||
|    timer.stop(); | ||||
|    BOOST_LOG_TRIVIAL(debug) | ||||
|       << logPrefix_ << "Vertices calculated in " << timer.format(6, "%ws"); | ||||
| 
 | ||||
|    emit SweepComputed(); | ||||
| } | ||||
| 
 | ||||
| static std::chrono::system_clock::time_point | ||||
| TimePoint(uint16_t modifiedJulianDate, uint32_t milliseconds) | ||||
| { | ||||
|    using namespace std::chrono; | ||||
|    using sys_days       = time_point<system_clock, days>; | ||||
|    constexpr auto epoch = sys_days {1969y / December / 31d}; | ||||
| 
 | ||||
|    return epoch + (modifiedJulianDate * 24h) + | ||||
|           std::chrono::milliseconds {milliseconds}; | ||||
| } | ||||
| 
 | ||||
| } // namespace view
 | ||||
| } // namespace qt
 | ||||
| } // namespace scwx
 | ||||
|  |  | |||
|  | @ -1,12 +1,13 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <scwx/common/color_table.hpp> | ||||
| #include <scwx/qt/manager/radar_product_manager.hpp> | ||||
| 
 | ||||
| #include <chrono> | ||||
| #include <memory> | ||||
| #include <vector> | ||||
| 
 | ||||
| #include <QObject> | ||||
| 
 | ||||
| namespace scwx | ||||
| { | ||||
| namespace qt | ||||
|  | @ -21,22 +22,21 @@ class RadarProductView : public QObject | |||
|    Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|    explicit RadarProductView( | ||||
|       std::shared_ptr<manager::RadarProductManager> radarProductManager); | ||||
|    explicit RadarProductView(); | ||||
|    ~RadarProductView(); | ||||
| 
 | ||||
|    const std::vector<float>&                     vertices() const; | ||||
|    const std::vector<boost::gil::rgba8_pixel_t>& color_table() const; | ||||
|    virtual const std::vector<boost::gil::rgba8_pixel_t>& color_table() const; | ||||
|    virtual std::chrono::system_clock::time_point         sweep_time() const; | ||||
|    virtual const std::vector<float>&                     vertices() const = 0; | ||||
| 
 | ||||
|    void Initialize(); | ||||
|    virtual void | ||||
|    LoadColorTable(std::shared_ptr<common::ColorTable> colorTable) = 0; | ||||
| 
 | ||||
|    std::tuple<const void*, size_t, size_t> GetMomentData(); | ||||
|    void LoadColorTable(std::shared_ptr<common::ColorTable> colorTable); | ||||
| 
 | ||||
|    std::chrono::system_clock::time_point SweepTime(); | ||||
|    virtual std::tuple<const void*, size_t, size_t> GetMomentData() const = 0; | ||||
| 
 | ||||
| protected slots: | ||||
|    void ComputeSweep(); | ||||
|    virtual void ComputeSweep(); | ||||
| 
 | ||||
| signals: | ||||
|    void ColorTableLoaded(); | ||||
|  |  | |||
							
								
								
									
										51
									
								
								scwx-qt/source/scwx/qt/view/radar_product_view_factory.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								scwx-qt/source/scwx/qt/view/radar_product_view_factory.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,51 @@ | |||
| #include <scwx/qt/view/radar_product_view_factory.hpp> | ||||
| #include <scwx/qt/view/level2_product_view.hpp> | ||||
| 
 | ||||
| #include <boost/log/trivial.hpp> | ||||
| 
 | ||||
| namespace scwx | ||||
| { | ||||
| namespace qt | ||||
| { | ||||
| namespace view | ||||
| { | ||||
| 
 | ||||
| static const std::string logPrefix_ = | ||||
|    "[scwx::qt::view::radar_product_view_factory] "; | ||||
| 
 | ||||
| typedef std::function<std::shared_ptr<RadarProductView>( | ||||
|    const std::string&                            productName, | ||||
|    std::shared_ptr<manager::RadarProductManager> radarProductManager)> | ||||
|    CreateRadarProductFunction; | ||||
| 
 | ||||
| static const std::unordered_map<std::string, CreateRadarProductFunction> | ||||
|    create_ {{PRODUCT_L2_REF, Level2ProductView::Create}, | ||||
|             {PRODUCT_L2_VEL, Level2ProductView::Create}, | ||||
|             {PRODUCT_L2_SW, Level2ProductView::Create}, | ||||
|             {PRODUCT_L2_ZDR, Level2ProductView::Create}, | ||||
|             {PRODUCT_L2_PHI, Level2ProductView::Create}, | ||||
|             {PRODUCT_L2_RHO, Level2ProductView::Create}, | ||||
|             {PRODUCT_L2_CFP, Level2ProductView::Create}}; | ||||
| 
 | ||||
| std::shared_ptr<RadarProductView> RadarProductViewFactory::Create( | ||||
|    const std::string&                            productName, | ||||
|    std::shared_ptr<manager::RadarProductManager> radarProductManager) | ||||
| { | ||||
|    std::shared_ptr<RadarProductView> view = nullptr; | ||||
| 
 | ||||
|    if (create_.find(productName) == create_.end()) | ||||
|    { | ||||
|       BOOST_LOG_TRIVIAL(warning) | ||||
|          << logPrefix_ << "Unknown radar product: " << productName; | ||||
|    } | ||||
|    else | ||||
|    { | ||||
|       view = create_.at(productName)(productName, radarProductManager); | ||||
|    } | ||||
| 
 | ||||
|    return view; | ||||
| } | ||||
| 
 | ||||
| } // namespace view
 | ||||
| } // namespace qt
 | ||||
| } // namespace scwx
 | ||||
							
								
								
									
										36
									
								
								scwx-qt/source/scwx/qt/view/radar_product_view_factory.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								scwx-qt/source/scwx/qt/view/radar_product_view_factory.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <scwx/qt/manager/radar_product_manager.hpp> | ||||
| #include <scwx/qt/view/radar_product_view.hpp> | ||||
| 
 | ||||
| #include <string> | ||||
| 
 | ||||
| namespace scwx | ||||
| { | ||||
| namespace qt | ||||
| { | ||||
| namespace view | ||||
| { | ||||
| 
 | ||||
| class RadarProductViewFactory | ||||
| { | ||||
| private: | ||||
|    explicit RadarProductViewFactory() = delete; | ||||
|    ~RadarProductViewFactory()         = delete; | ||||
| 
 | ||||
|    RadarProductViewFactory(const RadarProductViewFactory&) = delete; | ||||
|    RadarProductViewFactory& operator=(const RadarProductViewFactory&) = delete; | ||||
| 
 | ||||
|    RadarProductViewFactory(RadarProductViewFactory&&) noexcept = delete; | ||||
|    RadarProductViewFactory& | ||||
|    operator=(RadarProductViewFactory&&) noexcept = delete; | ||||
| 
 | ||||
| public: | ||||
|    static std::shared_ptr<RadarProductView> | ||||
|    Create(const std::string&                            productName, | ||||
|           std::shared_ptr<manager::RadarProductManager> radarProductManager); | ||||
| }; | ||||
| 
 | ||||
| } // namespace view
 | ||||
| } // namespace qt
 | ||||
| } // namespace scwx
 | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Dan Paulat
						Dan Paulat