diff --git a/.clang-tidy b/.clang-tidy
index 1eed15a2..602e3d0c 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -6,6 +6,7 @@ Checks:
   - 'misc-*'
   - 'modernize-*'
   - 'performance-*'
+  - '-cppcoreguidelines-pro-type-reinterpret-cast'
   - '-misc-include-cleaner'
   - '-misc-non-private-member-variables-in-classes'
   - '-modernize-use-trailing-return-type'
diff --git a/scwx-qt/gl/radar.frag b/scwx-qt/gl/radar.frag
index 0c605b8a..b6491e8b 100644
--- a/scwx-qt/gl/radar.frag
+++ b/scwx-qt/gl/radar.frag
@@ -9,14 +9,14 @@ uniform float uDataMomentScale;
 
 uniform bool uCFPEnabled;
 
-flat in uint dataMoment;
-flat in uint cfpMoment;
+in float dataMoment;
+in float cfpMoment;
 
 layout (location = 0) out vec4 fragColor;
 
 void main()
 {
-   float texCoord = float(dataMoment - uDataMomentOffset) / uDataMomentScale;
+   float texCoord = (dataMoment - float(uDataMomentOffset)) / uDataMomentScale;
 
    if (uCFPEnabled && cfpMoment > 8u)
    {
diff --git a/scwx-qt/gl/radar.vert b/scwx-qt/gl/radar.vert
index b4da9f17..97754b73 100644
--- a/scwx-qt/gl/radar.vert
+++ b/scwx-qt/gl/radar.vert
@@ -13,8 +13,8 @@ layout (location = 2) in uint aCfpMoment;
 uniform mat4 uMVPMatrix;
 uniform vec2 uMapScreenCoord;
 
-flat out uint dataMoment;
-flat out uint cfpMoment;
+out float dataMoment;
+out float cfpMoment;
 
 vec2 latLngToScreenCoordinate(in vec2 latLng)
 {
diff --git a/scwx-qt/source/scwx/qt/main/main_window.cpp b/scwx-qt/source/scwx/qt/main/main_window.cpp
index 37b8b268..ad504cc0 100644
--- a/scwx-qt/source/scwx/qt/main/main_window.cpp
+++ b/scwx-qt/source/scwx/qt/main/main_window.cpp
@@ -322,6 +322,8 @@ MainWindow::MainWindow(QWidget* parent) :
    p->mapSettingsGroup_ = new ui::CollapsibleGroup(tr("Map Settings"), this);
    p->mapSettingsGroup_->GetContentsLayout()->addWidget(ui->mapStyleLabel);
    p->mapSettingsGroup_->GetContentsLayout()->addWidget(ui->mapStyleComboBox);
+   p->mapSettingsGroup_->GetContentsLayout()->addWidget(
+      ui->smoothRadarDataCheckBox);
    p->mapSettingsGroup_->GetContentsLayout()->addWidget(
       ui->trackLocationCheckBox);
    ui->radarToolboxScrollAreaContents->layout()->replaceWidget(
@@ -642,6 +644,11 @@ void MainWindow::on_actionDumpRadarProductRecords_triggered()
    manager::RadarProductManager::DumpRecords();
 }
 
+void MainWindow::on_actionRadarWireframe_triggered(bool checked)
+{
+   p->activeMap_->SetRadarWireframeEnabled(checked);
+}
+
 void MainWindow::on_actionUserManual_triggered()
 {
    QDesktopServices::openUrl(QUrl {"https://supercell-wx.readthedocs.io/"});
@@ -1085,6 +1092,25 @@ void MainWindowImpl::ConnectOtherSignals()
                  }
               }
            });
+   connect(
+      mainWindow_->ui->smoothRadarDataCheckBox,
+      &QCheckBox::checkStateChanged,
+      mainWindow_,
+      [this](Qt::CheckState state)
+      {
+         const bool smoothingEnabled = (state == Qt::CheckState::Checked);
+
+         auto it = std::find(maps_.cbegin(), maps_.cend(), activeMap_);
+         if (it != maps_.cend())
+         {
+            const std::size_t i = std::distance(maps_.cbegin(), it);
+            settings::MapSettings::Instance().smoothing_enabled(i).StageValue(
+               smoothingEnabled);
+         }
+
+         // Turn on smoothing
+         activeMap_->SetSmoothingEnabled(smoothingEnabled);
+      });
    connect(mainWindow_->ui->trackLocationCheckBox,
            &QCheckBox::checkStateChanged,
            mainWindow_,
@@ -1471,6 +1497,13 @@ void MainWindowImpl::UpdateRadarProductSettings()
    {
       level2SettingsGroup_->setVisible(false);
    }
+
+   mainWindow_->ui->smoothRadarDataCheckBox->setCheckState(
+      activeMap_->GetSmoothingEnabled() ? Qt::CheckState::Checked :
+                                          Qt::CheckState::Unchecked);
+
+   mainWindow_->ui->actionRadarWireframe->setChecked(
+      activeMap_->GetRadarWireframeEnabled());
 }
 
 void MainWindowImpl::UpdateRadarSite()
diff --git a/scwx-qt/source/scwx/qt/main/main_window.hpp b/scwx-qt/source/scwx/qt/main/main_window.hpp
index 6a4fb5b4..6eb7fee2 100644
--- a/scwx-qt/source/scwx/qt/main/main_window.hpp
+++ b/scwx-qt/source/scwx/qt/main/main_window.hpp
@@ -29,7 +29,7 @@ public:
    void keyPressEvent(QKeyEvent* ev) override final;
    void keyReleaseEvent(QKeyEvent* ev) override final;
    void showEvent(QShowEvent* event) override;
-   void closeEvent(QCloseEvent *event) override;
+   void closeEvent(QCloseEvent* event) override;
 
 signals:
    void ActiveMapMoved(double latitude, double longitude);
@@ -49,6 +49,7 @@ private slots:
    void on_actionImGuiDebug_triggered();
    void on_actionDumpLayerList_triggered();
    void on_actionDumpRadarProductRecords_triggered();
+   void on_actionRadarWireframe_triggered(bool checked);
    void on_actionUserManual_triggered();
    void on_actionDiscord_triggered();
    void on_actionGitHubRepository_triggered();
diff --git a/scwx-qt/source/scwx/qt/main/main_window.ui b/scwx-qt/source/scwx/qt/main/main_window.ui
index c5e877c9..5d856663 100644
--- a/scwx-qt/source/scwx/qt/main/main_window.ui
+++ b/scwx-qt/source/scwx/qt/main/main_window.ui
@@ -97,6 +97,8 @@
     
     
     
+    
+    
    
    
                
+               - 
+                
+                 
+                  false
+                 
+                 
+                  
+                 
+                 
+                  Multi-Pane Cursor Marker Always On
+                 
+                
+               
- 
                 
                  
@@ -584,22 +597,16 @@
                 
                
- 
-                
+                
                  
-                  Update Notifications Enabled
+                  Show Range Folding when Smoothing Radar Data
                  
                 
                
- 
-                
-                 
-                  false
-                 
-                 
-                  
-                 
+                
                  
-                  Multi-Pane Cursor Marker Always On
+                  Update Notifications Enabled
                  
                 
                diff --git a/scwx-qt/source/scwx/qt/view/level2_product_view.cpp b/scwx-qt/source/scwx/qt/view/level2_product_view.cpp
index a5a26157..8dc44ab2 100644
--- a/scwx-qt/source/scwx/qt/view/level2_product_view.cpp
+++ b/scwx-qt/source/scwx/qt/view/level2_product_view.cpp
@@ -25,6 +25,11 @@ static constexpr std::uint32_t kMaxRadialGates_ =
    common::MAX_0_5_DEGREE_RADIALS * common::MAX_DATA_MOMENT_GATES;
 static constexpr std::uint32_t kMaxCoordinates_ = kMaxRadialGates_ * 2u;
 
+static constexpr std::uint8_t kDataWordSize8_ = 8u;
+
+static constexpr std::size_t kVerticesPerGate_       = 6u;
+static constexpr std::size_t kVerticesPerOriginGate_ = 3u;
+
 static constexpr uint16_t RANGE_FOLDED      = 1u;
 static constexpr uint32_t VERTICES_PER_BIN  = 6u;
 static constexpr uint32_t VALUES_PER_VERTEX = 2u;
@@ -53,11 +58,10 @@ static const std::unordered_map
                   {common::Level2Product::CorrelationCoefficient, "%"},
                   {common::Level2Product::ClutterFilterPowerRemoved, "dB"}};
 
-class Level2ProductViewImpl
+class Level2ProductView::Impl
 {
 public:
-   explicit Level2ProductViewImpl(Level2ProductView*    self,
-                                  common::Level2Product product) :
+   explicit Impl(Level2ProductView* self, common::Level2Product product) :
        self_ {self},
        product_ {product},
        selectedElevation_ {0.0f},
@@ -94,7 +98,7 @@ public:
       UpdateOtherUnits(unitSettings.other_units().GetValue());
       UpdateSpeedUnits(unitSettings.speed_units().GetValue());
    }
-   ~Level2ProductViewImpl()
+   ~Impl()
    {
       auto& unitSettings = settings::UnitSettings::Instance();
 
@@ -106,23 +110,36 @@ public:
       threadPool_.join();
    };
 
+   Impl(const Impl&)            = delete;
+   Impl& operator=(const Impl&) = delete;
+
+   Impl(Impl&&) noexcept            = delete;
+   Impl& operator=(Impl&&) noexcept = delete;
+
    void ComputeCoordinates(
-      const std::shared_ptr& radarData);
+      const std::shared_ptr& radarData,
+      bool                                               smoothingEnabled);
 
    void SetProduct(const std::string& productName);
    void SetProduct(common::Level2Product product);
    void UpdateOtherUnits(const std::string& name);
    void UpdateSpeedUnits(const std::string& name);
 
+   void ComputeEdgeValue();
+   template
+   [[nodiscard]] inline T RemapDataMoment(T dataMoment) const;
+
    static bool IsRadarDataIncomplete(
       const std::shared_ptr& radarData);
+   static units::degrees NormalizeAngle(units::degrees angle);
 
    Level2ProductView* self_;
 
    boost::asio::thread_pool threadPool_ {1u};
 
    common::Level2Product      product_;
-   wsr88d::rda::DataBlockType dataBlockType_;
+   wsr88d::rda::DataBlockType dataBlockType_ {
+      wsr88d::rda::DataBlockType::Unknown};
 
    float selectedElevation_;
 
@@ -130,11 +147,17 @@ public:
    std::shared_ptr
       momentDataBlock0_;
 
+   bool lastShowSmoothedRangeFolding_ {false};
+   bool lastSmoothingEnabled_ {false};
+
    std::vector    coordinates_ {};
    std::vector    vertices_ {};
    std::vector  dataMoments8_ {};
    std::vector dataMoments16_ {};
    std::vector  cfpMoments_ {};
+   std::uint16_t         edgeValue_ {};
+
+   bool showSmoothedRangeFolding_ {false};
 
    float                    latitude_;
    float                    longitude_;
@@ -164,7 +187,7 @@ Level2ProductView::Level2ProductView(
    common::Level2Product                         product,
    std::shared_ptr radarProductManager) :
     RadarProductView(radarProductManager),
-    p(std::make_unique(this, product))
+    p(std::make_unique(this, product))
 {
    ConnectRadarProductManager();
 }
@@ -379,12 +402,12 @@ void Level2ProductView::SelectProduct(const std::string& productName)
    p->SetProduct(productName);
 }
 
-void Level2ProductViewImpl::SetProduct(const std::string& productName)
+void Level2ProductView::Impl::SetProduct(const std::string& productName)
 {
    SetProduct(common::GetLevel2Product(productName));
 }
 
-void Level2ProductViewImpl::SetProduct(common::Level2Product product)
+void Level2ProductView::Impl::SetProduct(common::Level2Product product)
 {
    product_ = product;
 
@@ -401,12 +424,12 @@ void Level2ProductViewImpl::SetProduct(common::Level2Product product)
    }
 }
 
-void Level2ProductViewImpl::UpdateOtherUnits(const std::string& name)
+void Level2ProductView::Impl::UpdateOtherUnits(const std::string& name)
 {
    otherUnits_ = types::GetOtherUnitsFromName(name);
 }
 
-void Level2ProductViewImpl::UpdateSpeedUnits(const std::string& name)
+void Level2ProductView::Impl::UpdateSpeedUnits(const std::string& name)
 {
    speedUnits_ = types::GetSpeedUnitsFromName(name);
 }
@@ -511,6 +534,9 @@ void Level2ProductView::ComputeSweep()
 
    std::shared_ptr radarProductManager =
       radar_product_manager();
+   const bool smoothingEnabled          = smoothing_enabled();
+   p->showSmoothedRangeFolding_         = show_smoothed_range_folding();
+   const bool& showSmoothedRangeFolding = p->showSmoothedRangeFolding_;
 
    std::shared_ptr radarData;
    std::chrono::system_clock::time_point       requestedTime {selected_time()};
@@ -523,12 +549,18 @@ void Level2ProductView::ComputeSweep()
       Q_EMIT SweepNotComputed(types::NoUpdateReason::NotLoaded);
       return;
    }
-   if (radarData == p->elevationScan_)
+   if (radarData == p->elevationScan_ &&
+       smoothingEnabled == p->lastSmoothingEnabled_ &&
+       (showSmoothedRangeFolding == p->lastShowSmoothedRangeFolding_ ||
+        !smoothingEnabled))
    {
       Q_EMIT SweepNotComputed(types::NoUpdateReason::NoChange);
       return;
    }
 
+   p->lastShowSmoothedRangeFolding_ = showSmoothedRangeFolding;
+   p->lastSmoothingEnabled_         = smoothingEnabled;
+
    logger_->debug("Computing Sweep");
 
    std::size_t radials       = radarData->crbegin()->first + 1;
@@ -536,8 +568,7 @@ void Level2ProductView::ComputeSweep()
 
    // When there is missing data, insert another empty vertex radial at the end
    // to avoid stretching
-   const bool isRadarDataIncomplete =
-      Level2ProductViewImpl::IsRadarDataIncomplete(radarData);
+   const bool isRadarDataIncomplete = Impl::IsRadarDataIncomplete(radarData);
    if (isRadarDataIncomplete)
    {
       ++vertexRadials;
@@ -548,7 +579,7 @@ void Level2ProductView::ComputeSweep()
    vertexRadials =
       std::min(vertexRadials, common::MAX_0_5_DEGREE_RADIALS);
 
-   p->ComputeCoordinates(radarData);
+   p->ComputeCoordinates(radarData, smoothingEnabled);
 
    const std::vector& coordinates = p->coordinates_;
 
@@ -627,11 +658,20 @@ void Level2ProductView::ComputeSweep()
    // Start radial is always 0, as coordinates are calculated for each sweep
    constexpr std::uint16_t startRadial = 0u;
 
-   for (auto& radialPair : *radarData)
+   // For most products other than reflectivity, the edge should not go to the
+   // bottom of the color table
+   if (smoothingEnabled)
    {
+      p->ComputeEdgeValue();
+   }
+
+   for (auto it = radarData->cbegin(); it != radarData->cend(); ++it)
+   {
+      const auto&   radialPair = *it;
       std::uint16_t radial     = radialPair.first;
-      auto&         radialData = radialPair.second;
-      auto momentData = radialData->moment_data_block(p->dataBlockType_);
+      const auto&   radialData = radialPair.second;
+      const std::shared_ptr
+         momentData = radialData->moment_data_block(p->dataBlockType_);
 
       if (momentData0->data_word_size() != momentData->data_word_size())
       {
@@ -653,7 +693,7 @@ void Level2ProductView::ComputeSweep()
          std::max(1, dataMomentInterval / gateSizeMeters);
 
       // Compute gate range [startGate, endGate)
-      const std::int32_t startGate =
+      std::int32_t startGate =
          (dataMomentRange - dataMomentIntervalH) / gateSizeMeters;
       const std::int32_t numberOfDataMomentGates =
          std::min(momentData->number_of_data_moment_gates(),
@@ -662,9 +702,19 @@ void Level2ProductView::ComputeSweep()
          startGate + numberOfDataMomentGates * gateSize,
          static_cast(common::MAX_DATA_MOMENT_GATES));
 
-      const std::uint8_t*  dataMomentsArray8  = nullptr;
-      const std::uint16_t* dataMomentsArray16 = nullptr;
-      const std::uint8_t*  cfpMomentsArray    = nullptr;
+      if (smoothingEnabled)
+      {
+         // If smoothing is enabled, the start gate is incremented by one, as we
+         // are skipping the radar site origin. The end gate is unaffected, as
+         // we need to draw one less data point.
+         ++startGate;
+      }
+
+      const std::uint8_t*  dataMomentsArray8      = nullptr;
+      const std::uint16_t* dataMomentsArray16     = nullptr;
+      const std::uint8_t*  nextDataMomentsArray8  = nullptr;
+      const std::uint16_t* nextDataMomentsArray16 = nullptr;
+      const std::uint8_t*  cfpMomentsArray        = nullptr;
 
       if (momentData->data_word_size() == 8)
       {
@@ -684,6 +734,45 @@ void Level2ProductView::ComputeSweep()
                ->data_moments());
       }
 
+      std::shared_ptr
+                   nextMomentData              = nullptr;
+      std::int32_t numberOfNextDataMomentGates = 0;
+      if (smoothingEnabled)
+      {
+         // Smoothing requires the next radial pair as well
+         auto nextIt = std::next(it);
+         if (nextIt == radarData->cend())
+         {
+            nextIt = radarData->cbegin();
+         }
+
+         const auto& nextRadialPair = *(nextIt);
+         const auto& nextRadialData = nextRadialPair.second;
+         nextMomentData = nextRadialData->moment_data_block(p->dataBlockType_);
+
+         if (momentData->data_word_size() != nextMomentData->data_word_size())
+         {
+            // Data should be consistent between radials
+            logger_->warn("Invalid data moment size");
+            continue;
+         }
+
+         if (nextMomentData->data_word_size() == kDataWordSize8_)
+         {
+            nextDataMomentsArray8 = reinterpret_cast(
+               nextMomentData->data_moments());
+         }
+         else
+         {
+            nextDataMomentsArray16 = reinterpret_cast(
+               nextMomentData->data_moments());
+         }
+
+         numberOfNextDataMomentGates = std::min(
+            nextMomentData->number_of_data_moment_gates(),
+            static_cast(gates));
+      }
+
       for (std::int32_t gate = startGate, i = 0; gate + gateSize <= endGate;
            gate += gateSize, ++i)
       {
@@ -692,57 +781,172 @@ void Level2ProductView::ComputeSweep()
             continue;
          }
 
-         std::size_t vertexCount = (gate > 0) ? 6 : 3;
+         const std::size_t vertexCount =
+            (gate > 0) ? kVerticesPerGate_ : kVerticesPerOriginGate_;
+
+         // Allow pointer arithmetic here, as bounds have already been checked
+         // NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic)
 
          // Store data moment value
          if (dataMomentsArray8 != nullptr)
          {
-            std::uint8_t dataValue = dataMomentsArray8[i];
-            if (dataValue < snrThreshold && dataValue != RANGE_FOLDED)
+            if (!smoothingEnabled)
             {
-               continue;
-            }
-
-            for (std::size_t m = 0; m < vertexCount; m++)
-            {
-               dataMoments8[mIndex++] = dataMomentsArray8[i];
-
-               if (cfpMomentsArray != nullptr)
+               const std::uint8_t& dataValue = dataMomentsArray8[i];
+               if (dataValue < snrThreshold && dataValue != RANGE_FOLDED)
                {
-                  cfpMoments[mIndex - 1] = cfpMomentsArray[i];
+                  continue;
                }
+
+               for (std::size_t m = 0; m < vertexCount; m++)
+               {
+                  dataMoments8[mIndex++] = dataValue;
+
+                  if (cfpMomentsArray != nullptr)
+                  {
+                     cfpMoments[mIndex - 1] = cfpMomentsArray[i];
+                  }
+               }
+            }
+            else if (gate > 0)
+            {
+               // Validate indices are all in range
+               if (i + 1 >= numberOfDataMomentGates ||
+                   i + 1 >= numberOfNextDataMomentGates)
+               {
+                  continue;
+               }
+
+               const std::uint8_t& dm1 = dataMomentsArray8[i];
+               const std::uint8_t& dm2 = dataMomentsArray8[i + 1];
+               const std::uint8_t& dm3 = nextDataMomentsArray8[i];
+               const std::uint8_t& dm4 = nextDataMomentsArray8[i + 1];
+
+               if ((!showSmoothedRangeFolding && //
+                    (dm1 < snrThreshold || dm1 == RANGE_FOLDED) &&
+                    (dm2 < snrThreshold || dm2 == RANGE_FOLDED) &&
+                    (dm3 < snrThreshold || dm3 == RANGE_FOLDED) &&
+                    (dm4 < snrThreshold || dm4 == RANGE_FOLDED)) ||
+                   (showSmoothedRangeFolding && //
+                    dm1 < snrThreshold && dm1 != RANGE_FOLDED &&
+                    dm2 < snrThreshold && dm2 != RANGE_FOLDED &&
+                    dm3 < snrThreshold && dm3 != RANGE_FOLDED &&
+                    dm4 < snrThreshold && dm4 != RANGE_FOLDED))
+               {
+                  // Skip only if all data moments are hidden
+                  continue;
+               }
+
+               // The order must match the store vertices section below
+               dataMoments8[mIndex++] = p->RemapDataMoment(dm1);
+               dataMoments8[mIndex++] = p->RemapDataMoment(dm2);
+               dataMoments8[mIndex++] = p->RemapDataMoment(dm4);
+               dataMoments8[mIndex++] = p->RemapDataMoment(dm1);
+               dataMoments8[mIndex++] = p->RemapDataMoment(dm3);
+               dataMoments8[mIndex++] = p->RemapDataMoment(dm4);
+
+               // cfpMoments is unused, so not populated here
+            }
+            else
+            {
+               // If smoothing is enabled, gate should never start at zero
+               // (radar site origin)
+               logger_->error(
+                  "Smoothing enabled, gate should not start at zero");
+               continue;
             }
          }
          else
          {
-            std::uint16_t dataValue = dataMomentsArray16[i];
-            if (dataValue < snrThreshold && dataValue != RANGE_FOLDED)
+            if (!smoothingEnabled)
             {
+               const std::uint16_t& dataValue = dataMomentsArray16[i];
+               if (dataValue < snrThreshold && dataValue != RANGE_FOLDED)
+               {
+                  continue;
+               }
+
+               for (std::size_t m = 0; m < vertexCount; m++)
+               {
+                  dataMoments16[mIndex++] = dataValue;
+               }
+            }
+            else if (gate > 0)
+            {
+               // Validate indices are all in range
+               if (i + 1 >= numberOfDataMomentGates ||
+                   i + 1 >= numberOfNextDataMomentGates)
+               {
+                  continue;
+               }
+
+               const std::uint16_t& dm1 = dataMomentsArray16[i];
+               const std::uint16_t& dm2 = dataMomentsArray16[i + 1];
+               const std::uint16_t& dm3 = nextDataMomentsArray16[i];
+               const std::uint16_t& dm4 = nextDataMomentsArray16[i + 1];
+
+               if ((!showSmoothedRangeFolding && //
+                    (dm1 < snrThreshold || dm1 == RANGE_FOLDED) &&
+                    (dm2 < snrThreshold || dm2 == RANGE_FOLDED) &&
+                    (dm3 < snrThreshold || dm3 == RANGE_FOLDED) &&
+                    (dm4 < snrThreshold || dm4 == RANGE_FOLDED)) ||
+                   (showSmoothedRangeFolding && //
+                    dm1 < snrThreshold && dm1 != RANGE_FOLDED &&
+                    dm2 < snrThreshold && dm2 != RANGE_FOLDED &&
+                    dm3 < snrThreshold && dm3 != RANGE_FOLDED &&
+                    dm4 < snrThreshold && dm4 != RANGE_FOLDED))
+               {
+                  // Skip only if all data moments are hidden
+                  continue;
+               }
+
+               // The order must match the store vertices section below
+               dataMoments16[mIndex++] = p->RemapDataMoment(dm1);
+               dataMoments16[mIndex++] = p->RemapDataMoment(dm2);
+               dataMoments16[mIndex++] = p->RemapDataMoment(dm4);
+               dataMoments16[mIndex++] = p->RemapDataMoment(dm1);
+               dataMoments16[mIndex++] = p->RemapDataMoment(dm3);
+               dataMoments16[mIndex++] = p->RemapDataMoment(dm4);
+
+               // cfpMoments is unused, so not populated here
+            }
+            else
+            {
+               // If smoothing is enabled, gate should never start at zero
+               // (radar site origin)
                continue;
             }
-
-            for (std::size_t m = 0; m < vertexCount; m++)
-            {
-               dataMoments16[mIndex++] = dataMomentsArray16[i];
-            }
          }
 
+         // NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+
          // Store vertices
          if (gate > 0)
          {
+            // Draw two triangles per gate
+            //
+            // 2 +---+ 4
+            //   |  /|
+            //   | / |
+            //   |/  |
+            // 1 +---+ 3
+
             const std::uint16_t baseCoord = gate - 1;
 
-            std::size_t offset1 = ((startRadial + radial) % vertexRadials *
-                                      common::MAX_DATA_MOMENT_GATES +
-                                   baseCoord) *
-                                  2;
-            std::size_t offset2 = offset1 + gateSize * 2;
-            std::size_t offset3 =
+            const std::size_t offset1 =
+               ((startRadial + radial) % vertexRadials *
+                   common::MAX_DATA_MOMENT_GATES +
+                baseCoord) *
+               2;
+            const std::size_t offset2 =
+               offset1 + static_cast(gateSize) * 2;
+            const std::size_t offset3 =
                (((startRadial + radial + 1) % vertexRadials) *
                    common::MAX_DATA_MOMENT_GATES +
                 baseCoord) *
                2;
-            std::size_t offset4 = offset3 + gateSize * 2;
+            const std::size_t offset4 =
+               offset3 + static_cast(gateSize) * 2;
 
             vertices[vIndex++] = coordinates[offset1];
             vertices[vIndex++] = coordinates[offset1 + 1];
@@ -750,19 +954,17 @@ void Level2ProductView::ComputeSweep()
             vertices[vIndex++] = coordinates[offset2];
             vertices[vIndex++] = coordinates[offset2 + 1];
 
-            vertices[vIndex++] = coordinates[offset3];
-            vertices[vIndex++] = coordinates[offset3 + 1];
+            vertices[vIndex++] = coordinates[offset4];
+            vertices[vIndex++] = coordinates[offset4 + 1];
+
+            vertices[vIndex++] = coordinates[offset1];
+            vertices[vIndex++] = coordinates[offset1 + 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
          {
@@ -786,8 +988,6 @@ void Level2ProductView::ComputeSweep()
 
             vertices[vIndex++] = coordinates[offset2];
             vertices[vIndex++] = coordinates[offset2 + 1];
-
-            vertexCount = 3;
          }
       }
    }
@@ -819,8 +1019,50 @@ void Level2ProductView::ComputeSweep()
    Q_EMIT SweepComputed();
 }
 
-void Level2ProductViewImpl::ComputeCoordinates(
-   const std::shared_ptr& radarData)
+void Level2ProductView::Impl::ComputeEdgeValue()
+{
+   const float offset = momentDataBlock0_->offset();
+
+   switch (dataBlockType_)
+   {
+   case wsr88d::rda::DataBlockType::MomentVel:
+   case wsr88d::rda::DataBlockType::MomentZdr:
+      edgeValue_ = static_cast(offset);
+      break;
+
+   case wsr88d::rda::DataBlockType::MomentSw:
+   case wsr88d::rda::DataBlockType::MomentPhi:
+      edgeValue_ = 2;
+      break;
+
+   case wsr88d::rda::DataBlockType::MomentRho:
+      edgeValue_ = std::numeric_limits::max();
+      break;
+
+   case wsr88d::rda::DataBlockType::MomentRef:
+   default:
+      edgeValue_ = 0;
+      break;
+   }
+}
+
+template
+T Level2ProductView::Impl::RemapDataMoment(T dataMoment) const
+{
+   if (dataMoment != 0 &&
+       (dataMoment != RANGE_FOLDED || showSmoothedRangeFolding_))
+   {
+      return dataMoment;
+   }
+   else
+   {
+      return edgeValue_;
+   }
+}
+
+void Level2ProductView::Impl::ComputeCoordinates(
+   const std::shared_ptr& radarData,
+   bool                                               smoothingEnabled)
 {
    logger_->debug("ComputeCoordinates()");
 
@@ -860,6 +1102,14 @@ void Level2ProductViewImpl::ComputeCoordinates(
    auto radials = boost::irange(0u, numRadials);
    auto gates   = boost::irange(0u, numRangeBins);
 
+   const float gateRangeOffset = (smoothingEnabled) ?
+                                    // Center of the first gate is half the gate
+                                    // size distance from the radar site
+                                    0.5f :
+                                    // Far end of the first gate is the gate
+                                    // size distance from the radar site
+                                    1.0f;
+
    std::for_each(
       std::execution::par_unseq,
       radials.begin(),
@@ -869,7 +1119,7 @@ void Level2ProductViewImpl::ComputeCoordinates(
          units::degrees angle {};
 
          auto radialData = radarData->find(radial);
-         if (radialData != radarData->cend())
+         if (radialData != radarData->cend() && !smoothingEnabled)
          {
             angle = radialData->second->azimuth_angle();
          }
@@ -880,19 +1130,60 @@ void Level2ProductViewImpl::ComputeCoordinates(
             auto prevRadial2 = radarData->find(
                (radial >= 2) ? radial - 2 : numRadials - (2 - radial));
 
-            if (prevRadial1 != radarData->cend() &&
-                prevRadial2 != radarData->cend())
+            if (radialData != radarData->cend() &&
+                prevRadial1 != radarData->cend() && smoothingEnabled)
+            {
+               const units::degrees currentAngle =
+                  radialData->second->azimuth_angle();
+               const units::degrees prevAngle =
+                  prevRadial1->second->azimuth_angle();
+
+               // Calculate delta angle
+               const units::degrees deltaAngle =
+                  NormalizeAngle(currentAngle - prevAngle);
+
+               // Delta scale is half the delta angle to reach the center of the
+               // bin, because smoothing is enabled
+               constexpr float deltaScale = 0.5f;
+
+               angle = currentAngle + deltaAngle * deltaScale;
+            }
+            else if (radialData != radarData->cend() && smoothingEnabled)
+            {
+               const units::degrees currentAngle =
+                  radialData->second->azimuth_angle();
+
+               // Assume a half degree delta if there aren't enough angles
+               // to determine a delta angle
+               constexpr units::degrees deltaAngle {0.5f};
+
+               // Delta scale is half the delta angle to reach the center of the
+               // bin, because smoothing is enabled
+               constexpr float deltaScale = 0.5f;
+
+               angle = currentAngle + deltaAngle * deltaScale;
+            }
+            else if (prevRadial1 != radarData->cend() &&
+                     prevRadial2 != radarData->cend())
             {
                const units::degrees prevAngle1 =
                   prevRadial1->second->azimuth_angle();
                const units::degrees prevAngle2 =
                   prevRadial2->second->azimuth_angle();
 
-               // No wrapping required since angle is only used for geodesic
-               // calculation
-               const units::degrees deltaAngle = prevAngle1 - prevAngle2;
+               // Calculate delta angle
+               const units::degrees deltaAngle =
+                  NormalizeAngle(prevAngle1 - prevAngle2);
 
-               angle = prevAngle1 + deltaAngle;
+               const float deltaScale =
+                  (smoothingEnabled) ?
+                     // Delta scale is 1.5x the delta angle to reach the center
+                     // of the next bin, because smoothing is enabled
+                     1.5f :
+                     // Delta scale is 1.0x the delta angle
+                     1.0f;
+
+               angle = prevAngle1 + deltaAngle * deltaScale;
             }
             else if (prevRadial1 != radarData->cend())
             {
@@ -903,7 +1194,15 @@ void Level2ProductViewImpl::ComputeCoordinates(
                // to determine a delta angle
                constexpr units::degrees deltaAngle {0.5f};
 
-               angle = prevAngle1 + deltaAngle;
+               const float deltaScale =
+                  (smoothingEnabled) ?
+                     // Delta scale is 1.5x the delta angle to reach the center
+                     // of the next bin, because smoothing is enabled
+                     1.5f :
+                     // Delta scale is 1.0x the delta angle
+                     1.0f;
+
+               angle = prevAngle1 + deltaAngle * deltaScale;
             }
             else
             {
@@ -912,35 +1211,38 @@ void Level2ProductViewImpl::ComputeCoordinates(
             }
          }
 
-         std::for_each(std::execution::par_unseq,
-                       gates.begin(),
-                       gates.end(),
-                       [&](std::uint32_t gate)
-                       {
-                          const std::uint32_t radialGate =
-                             radial * common::MAX_DATA_MOMENT_GATES + gate;
-                          const float       range  = (gate + 1) * gateSize;
-                          const std::size_t offset = radialGate * 2;
+         std::for_each(
+            std::execution::par_unseq,
+            gates.begin(),
+            gates.end(),
+            [&](std::uint32_t gate)
+            {
+               const std::uint32_t radialGate =
+                  radial * common::MAX_DATA_MOMENT_GATES + gate;
+               const float range =
+                  (static_cast(gate) + gateRangeOffset) * gateSize;
+               const std::size_t offset =
+                  static_cast(radialGate) * 2;
 
-                          double latitude;
-                          double longitude;
+               double latitude  = 0.0;
+               double longitude = 0.0;
 
-                          geodesic.Direct(radarLatitude,
-                                          radarLongitude,
-                                          angle.value(),
-                                          range,
-                                          latitude,
-                                          longitude);
+               geodesic.Direct(radarLatitude,
+                               radarLongitude,
+                               angle.value(),
+                               range,
+                               latitude,
+                               longitude);
 
-                          coordinates_[offset]     = latitude;
-                          coordinates_[offset + 1] = longitude;
-                       });
+               coordinates_[offset]     = static_cast(latitude);
+               coordinates_[offset + 1] = static_cast(longitude);
+            });
       });
    timer.stop();
    logger_->debug("Coordinates calculated in {}", timer.format(6, "%ws"));
 }
 
-bool Level2ProductViewImpl::IsRadarDataIncomplete(
+bool Level2ProductView::Impl::IsRadarDataIncomplete(
    const std::shared_ptr& radarData)
 {
    // Assume the data is incomplete when the delta between the first and last
@@ -957,6 +1259,25 @@ bool Level2ProductViewImpl::IsRadarDataIncomplete(
    return angleDelta > kIncompleteDataAngleThreshold_;
 }
 
+units::degrees
+Level2ProductView::Impl::NormalizeAngle(units::degrees angle)
+{
+   constexpr auto angleLimit = units::degrees {180.0f};
+   constexpr auto fullAngle  = units::degrees {360.0f};
+
+   // Normalize angle to [-180, 180)
+   while (angle < -angleLimit)
+   {
+      angle += fullAngle;
+   }
+   while (angle >= angleLimit)
+   {
+      angle -= fullAngle;
+   }
+
+   return angle;
+}
+
 std::optional
 Level2ProductView::GetBinLevel(const common::Coordinate& coordinate) const
 {
@@ -1003,7 +1324,7 @@ Level2ProductView::GetBinLevel(const common::Coordinate& coordinate) const
       static_cast(radarData->crbegin()->first + 1);
 
    // Add an extra radial when incomplete data exists
-   if (Level2ProductViewImpl::IsRadarDataIncomplete(radarData))
+   if (Impl::IsRadarDataIncomplete(radarData))
    {
       ++numRadials;
    }
diff --git a/scwx-qt/source/scwx/qt/view/level2_product_view.hpp b/scwx-qt/source/scwx/qt/view/level2_product_view.hpp
index 9e25a254..db8fc45c 100644
--- a/scwx-qt/source/scwx/qt/view/level2_product_view.hpp
+++ b/scwx-qt/source/scwx/qt/view/level2_product_view.hpp
@@ -15,8 +15,6 @@ namespace qt
 namespace view
 {
 
-class Level2ProductViewImpl;
-
 class Level2ProductView : public RadarProductView
 {
    Q_OBJECT
@@ -73,7 +71,8 @@ protected slots:
    void ComputeSweep() override;
 
 private:
-   std::unique_ptr p;
+   class Impl;
+   std::unique_ptr p;
 };
 
 } // namespace view
diff --git a/scwx-qt/source/scwx/qt/view/level3_product_view.cpp b/scwx-qt/source/scwx/qt/view/level3_product_view.cpp
index d0bb0d47..3858ac11 100644
--- a/scwx-qt/source/scwx/qt/view/level3_product_view.cpp
+++ b/scwx-qt/source/scwx/qt/view/level3_product_view.cpp
@@ -485,6 +485,52 @@ void Level3ProductView::UpdateColorTableLut()
    Q_EMIT ColorTableLutUpdated();
 }
 
+std::uint8_t Level3ProductView::ComputeEdgeValue() const
+{
+   std::uint8_t edgeValue = 0;
+
+   const std::shared_ptr
+      descriptionBlock = p->graphicMessage_->description_block();
+
+   const float offset = descriptionBlock->offset();
+   const float scale  = descriptionBlock->scale();
+
+   switch (p->category_)
+   {
+   case common::Level3ProductCategory::Velocity:
+      edgeValue = static_cast((scale > 0.0f) ? (-offset / scale) :
+                                                             -offset);
+      break;
+
+   case common::Level3ProductCategory::DifferentialReflectivity:
+      edgeValue = static_cast(-offset);
+      break;
+
+   case common::Level3ProductCategory::SpectrumWidth:
+   case common::Level3ProductCategory::SpecificDifferentialPhase:
+      edgeValue = 2;
+      break;
+
+   case common::Level3ProductCategory::CorrelationCoefficient:
+      edgeValue = static_cast(
+         std::max(std::numeric_limits::max(),
+                                 descriptionBlock->number_of_levels()));
+      break;
+
+   case common::Level3ProductCategory::Reflectivity:
+   case common::Level3ProductCategory::StormRelativeVelocity:
+   case common::Level3ProductCategory::VerticallyIntegratedLiquid:
+   case common::Level3ProductCategory::EchoTops:
+   case common::Level3ProductCategory::HydrometeorClassification:
+   case common::Level3ProductCategory::PrecipitationAccumulation:
+   default:
+      edgeValue = 0;
+      break;
+   }
+
+   return edgeValue;
+}
+
 std::optional
 Level3ProductView::GetDataLevelCode(std::uint16_t level) const
 {
diff --git a/scwx-qt/source/scwx/qt/view/level3_product_view.hpp b/scwx-qt/source/scwx/qt/view/level3_product_view.hpp
index e836c6e0..b5e043b3 100644
--- a/scwx-qt/source/scwx/qt/view/level3_product_view.hpp
+++ b/scwx-qt/source/scwx/qt/view/level3_product_view.hpp
@@ -58,6 +58,8 @@ protected:
    void DisconnectRadarProductManager() override;
    void UpdateColorTableLut() override;
 
+   [[nodiscard]] std::uint8_t ComputeEdgeValue() const;
+
 private:
    class Impl;
    std::unique_ptr p;
diff --git a/scwx-qt/source/scwx/qt/view/level3_radial_view.cpp b/scwx-qt/source/scwx/qt/view/level3_radial_view.cpp
index 5fa3531f..135f5e65 100644
--- a/scwx-qt/source/scwx/qt/view/level3_radial_view.cpp
+++ b/scwx-qt/source/scwx/qt/view/level3_radial_view.cpp
@@ -44,7 +44,11 @@ public:
    ~Impl() { threadPool_.join(); };
 
    void ComputeCoordinates(
-      const std::shared_ptr& radialData);
+      const std::shared_ptr& radialData,
+      bool smoothingEnabled);
+
+   [[nodiscard]] inline std::uint8_t
+   RemapDataMoment(std::uint8_t dataMoment) const;
 
    Level3RadialView* self_;
 
@@ -53,8 +57,13 @@ public:
    std::vector        coordinates_ {};
    std::vector        vertices_ {};
    std::vector dataMoments8_ {};
+   std::uint8_t              edgeValue_ {};
+
+   bool showSmoothedRangeFolding_ {false};
 
    std::shared_ptr lastRadialData_ {};
+   bool lastShowSmoothedRangeFolding_ {false};
+   bool lastSmoothingEnabled_ {false};
 
    float         latitude_;
    float         longitude_;
@@ -125,6 +134,9 @@ void Level3RadialView::ComputeSweep()
 
    std::shared_ptr radarProductManager =
       radar_product_manager();
+   const bool smoothingEnabled          = smoothing_enabled();
+   p->showSmoothedRangeFolding_         = show_smoothed_range_folding();
+   const bool& showSmoothedRangeFolding = p->showSmoothedRangeFolding_;
 
    // Retrieve message from Radar Product Manager
    std::shared_ptr message;
@@ -155,7 +167,10 @@ void Level3RadialView::ComputeSweep()
       Q_EMIT SweepNotComputed(types::NoUpdateReason::InvalidData);
       return;
    }
-   else if (gpm == graphic_product_message())
+   else if (gpm == graphic_product_message() &&
+            smoothingEnabled == p->lastSmoothingEnabled_ &&
+            (showSmoothedRangeFolding == p->lastShowSmoothedRangeFolding_ ||
+             !smoothingEnabled))
    {
       // Skip if this is the message we previously processed
       Q_EMIT SweepNotComputed(types::NoUpdateReason::NoChange);
@@ -163,6 +178,9 @@ void Level3RadialView::ComputeSweep()
    }
    set_graphic_product_message(gpm);
 
+   p->lastShowSmoothedRangeFolding_ = showSmoothedRangeFolding;
+   p->lastSmoothingEnabled_         = smoothingEnabled;
+
    // A message with radial data should have a Product Description Block and
    // Product Symbology Block
    std::shared_ptr descriptionBlock =
@@ -267,11 +285,11 @@ void Level3RadialView::ComputeSweep()
    const std::vector& coordinates =
       (radialSize == common::RadialSize::NonStandard) ?
          p->coordinates_ :
-         radarProductManager->coordinates(radialSize);
+         radarProductManager->coordinates(radialSize, smoothingEnabled);
 
    // There should be a positive number of range bins in radial data
-   const uint16_t gates = radialData->number_of_range_bins();
-   if (gates < 1)
+   const uint16_t numberOfDataMomentGates = radialData->number_of_range_bins();
+   if (numberOfDataMomentGates < 1)
    {
       logger_->warn("No range bins in radial data");
       Q_EMIT SweepNotComputed(types::NoUpdateReason::InvalidData);
@@ -293,13 +311,14 @@ void Level3RadialView::ComputeSweep()
    std::vector& vertices = p->vertices_;
    size_t              vIndex   = 0;
    vertices.clear();
-   vertices.resize(radials * gates * VERTICES_PER_BIN * VALUES_PER_VERTEX);
+   vertices.resize(radials * numberOfDataMomentGates * VERTICES_PER_BIN *
+                   VALUES_PER_VERTEX);
 
    // Setup data moment vector
    std::vector& dataMoments8 = p->dataMoments8_;
    size_t                mIndex       = 0;
 
-   dataMoments8.resize(radials * gates * VERTICES_PER_BIN);
+   dataMoments8.resize(radials * numberOfDataMomentGates * VERTICES_PER_BIN);
 
    // Compute threshold at which to display an individual bin
    const uint16_t snrThreshold = descriptionBlock->threshold();
@@ -308,7 +327,7 @@ void Level3RadialView::ComputeSweep()
    std::uint16_t startRadial;
    if (radialSize == common::RadialSize::NonStandard)
    {
-      p->ComputeCoordinates(radialData);
+      p->ComputeCoordinates(radialData, smoothingEnabled);
       startRadial = 0;
    }
    else
@@ -318,40 +337,105 @@ void Level3RadialView::ComputeSweep()
       startRadial = std::lroundf(startAngle * radialMultiplier);
    }
 
-   for (uint16_t radial = 0; radial < radialData->number_of_radials(); radial++)
+   // Compute gate interval
+   const std::uint16_t dataMomentInterval =
+      descriptionBlock->x_resolution_raw();
+
+   // Compute gate size (number of base gates per bin)
+   const std::uint16_t gateSize = std::max(
+      1,
+      dataMomentInterval /
+         static_cast(radarProductManager->gate_size()));
+
+   // Compute gate range [startGate, endGate)
+   std::uint16_t       startGate = 0;
+   const std::uint16_t endGate =
+      std::min(startGate + numberOfDataMomentGates * gateSize,
+                              common::MAX_DATA_MOMENT_GATES);
+
+   if (smoothingEnabled)
    {
-      const auto dataMomentsArray8 = radialData->level(radial);
+      // If smoothing is enabled, the start gate is incremented by one, as we
+      // are skipping the radar site origin. The end gate is unaffected, as
+      // we need to draw one less data point.
+      ++startGate;
 
-      // Compute gate interval
-      const uint16_t dataMomentInterval = descriptionBlock->x_resolution_raw();
+      // For most products other than reflectivity, the edge should not go to
+      // the bottom of the color table
+      p->edgeValue_ = ComputeEdgeValue();
+   }
 
-      // Compute gate size (number of base gates per bin)
-      const uint16_t gateSize = std::max(
-         1,
-         dataMomentInterval /
-            static_cast(radarProductManager->gate_size()));
+   for (std::uint16_t radial = 0; radial < radialData->number_of_radials();
+        ++radial)
+   {
+      const auto& dataMomentsArray8 = radialData->level(radial);
 
-      // Compute gate range [startGate, endGate)
-      const uint16_t startGate = 0;
-      const uint16_t endGate   = std::min(
-         startGate + gates * gateSize, common::MAX_DATA_MOMENT_GATES);
+      const std::uint16_t nextRadial =
+         (radial == radialData->number_of_radials() - 1) ? 0 : radial + 1;
+      const auto& nextDataMomentsArray8 = radialData->level(nextRadial);
 
-      for (uint16_t gate = startGate, i = 0; gate + gateSize <= endGate;
+      for (std::uint16_t gate = startGate, i = 0; gate + gateSize <= endGate;
            gate += gateSize, ++i)
       {
          size_t vertexCount = (gate > 0) ? 6 : 3;
 
-         // Store data moment value
-         uint8_t dataValue =
-            (i < dataMomentsArray8.size()) ? dataMomentsArray8[i] : 0;
-         if (dataValue < snrThreshold && dataValue != RANGE_FOLDED)
+         if (!smoothingEnabled)
          {
-            continue;
-         }
+            // Store data moment value
+            const uint8_t dataValue =
+               (i < dataMomentsArray8.size()) ? dataMomentsArray8[i] : 0;
+            if (dataValue < snrThreshold && dataValue != RANGE_FOLDED)
+            {
+               continue;
+            }
 
-         for (size_t m = 0; m < vertexCount; m++)
+            for (size_t m = 0; m < vertexCount; m++)
+            {
+               dataMoments8[mIndex++] = dataValue;
+            }
+         }
+         else if (gate > 0)
          {
-            dataMoments8[mIndex++] = dataValue;
+            // Validate indices are all in range
+            if (i + 1 >= numberOfDataMomentGates)
+            {
+               continue;
+            }
+
+            const std::uint8_t& dm1 = dataMomentsArray8[i];
+            const std::uint8_t& dm2 = dataMomentsArray8[i + 1];
+            const std::uint8_t& dm3 = nextDataMomentsArray8[i];
+            const std::uint8_t& dm4 = nextDataMomentsArray8[i + 1];
+
+            if ((!showSmoothedRangeFolding && //
+                 (dm1 < snrThreshold || dm1 == RANGE_FOLDED) &&
+                 (dm2 < snrThreshold || dm2 == RANGE_FOLDED) &&
+                 (dm3 < snrThreshold || dm3 == RANGE_FOLDED) &&
+                 (dm4 < snrThreshold || dm4 == RANGE_FOLDED)) ||
+                (showSmoothedRangeFolding && //
+                 dm1 < snrThreshold && dm1 != RANGE_FOLDED &&
+                 dm2 < snrThreshold && dm2 != RANGE_FOLDED &&
+                 dm3 < snrThreshold && dm3 != RANGE_FOLDED &&
+                 dm4 < snrThreshold && dm4 != RANGE_FOLDED))
+            {
+               // Skip only if all data moments are hidden
+               continue;
+            }
+
+            // The order must match the store vertices section below
+            dataMoments8[mIndex++] = p->RemapDataMoment(dm1);
+            dataMoments8[mIndex++] = p->RemapDataMoment(dm2);
+            dataMoments8[mIndex++] = p->RemapDataMoment(dm4);
+            dataMoments8[mIndex++] = p->RemapDataMoment(dm1);
+            dataMoments8[mIndex++] = p->RemapDataMoment(dm3);
+            dataMoments8[mIndex++] = p->RemapDataMoment(dm4);
+         }
+         else
+         {
+            // If smoothing is enabled, gate should never start at zero
+            // (radar site origin)
+            logger_->error("Smoothing enabled, gate should not start at zero");
+            continue;
          }
 
          // Store vertices
@@ -376,19 +460,17 @@ void Level3RadialView::ComputeSweep()
             vertices[vIndex++] = coordinates[offset2];
             vertices[vIndex++] = coordinates[offset2 + 1];
 
-            vertices[vIndex++] = coordinates[offset3];
-            vertices[vIndex++] = coordinates[offset3 + 1];
+            vertices[vIndex++] = coordinates[offset4];
+            vertices[vIndex++] = coordinates[offset4 + 1];
+
+            vertices[vIndex++] = coordinates[offset1];
+            vertices[vIndex++] = coordinates[offset1 + 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
          {
@@ -411,8 +493,6 @@ void Level3RadialView::ComputeSweep()
 
             vertices[vIndex++] = coordinates[offset2];
             vertices[vIndex++] = coordinates[offset2 + 1];
-
-            vertexCount = 3;
          }
       }
    }
@@ -430,8 +510,23 @@ void Level3RadialView::ComputeSweep()
    Q_EMIT SweepComputed();
 }
 
+std::uint8_t
+Level3RadialView::Impl::RemapDataMoment(std::uint8_t dataMoment) const
+{
+   if (dataMoment != 0 &&
+       (dataMoment != RANGE_FOLDED || showSmoothedRangeFolding_))
+   {
+      return dataMoment;
+   }
+   else
+   {
+      return edgeValue_;
+   }
+}
+
 void Level3RadialView::Impl::ComputeCoordinates(
-   const std::shared_ptr& radialData)
+   const std::shared_ptr& radialData,
+   bool smoothingEnabled)
 {
    logger_->debug("ComputeCoordinates()");
 
@@ -455,38 +550,54 @@ void Level3RadialView::Impl::ComputeCoordinates(
    auto radials = boost::irange(0u, numRadials);
    auto gates   = boost::irange(0u, numRangeBins);
 
-   std::for_each(std::execution::par_unseq,
-                 radials.begin(),
-                 radials.end(),
-                 [&](std::uint32_t radial)
-                 {
-                    const float angle = radialData->start_angle(radial);
+   const float gateRangeOffset = (smoothingEnabled) ?
+                                    // Center of the first gate is half the gate
+                                    // size distance from the radar site
+                                    0.5f :
+                                    // Far end of the first gate is the gate
+                                    // size distance from the radar site
+                                    1.0f;
 
-                    std::for_each(std::execution::par_unseq,
-                                  gates.begin(),
-                                  gates.end(),
-                                  [&](std::uint32_t gate)
-                                  {
-                                     const std::uint32_t radialGate =
-                                        radial * common::MAX_DATA_MOMENT_GATES +
-                                        gate;
-                                     const float range = (gate + 1) * gateSize;
-                                     const std::size_t offset = radialGate * 2;
+   std::for_each(
+      std::execution::par_unseq,
+      radials.begin(),
+      radials.end(),
+      [&](std::uint32_t radial)
+      {
+         float angle = radialData->start_angle(radial);
 
-                                     double latitude;
-                                     double longitude;
+         if (smoothingEnabled)
+         {
+            static constexpr float kDeltaAngleFactor = 0.5f;
+            angle += radialData->delta_angle(radial) * kDeltaAngleFactor;
+         }
 
-                                     geodesic.Direct(radarLatitude,
-                                                     radarLongitude,
-                                                     angle,
-                                                     range,
-                                                     latitude,
-                                                     longitude);
+         std::for_each(
+            std::execution::par_unseq,
+            gates.begin(),
+            gates.end(),
+            [&](std::uint32_t gate)
+            {
+               const std::uint32_t radialGate =
+                  radial * common::MAX_DATA_MOMENT_GATES + gate;
+               const float range =
+                  (static_cast(gate) + gateRangeOffset) * gateSize;
+               const std::size_t offset = static_cast(radialGate) * 2;
 
-                                     coordinates_[offset]     = latitude;
-                                     coordinates_[offset + 1] = longitude;
-                                  });
-                 });
+               double latitude  = 0.0;
+               double longitude = 0.0;
+
+               geodesic.Direct(radarLatitude,
+                               radarLongitude,
+                               angle,
+                               range,
+                               latitude,
+                               longitude);
+
+               coordinates_[offset]     = static_cast(latitude);
+               coordinates_[offset + 1] = static_cast(longitude);
+            });
+      });
    timer.stop();
    logger_->debug("Coordinates calculated in {}", timer.format(6, "%ws"));
 }
diff --git a/scwx-qt/source/scwx/qt/view/level3_raster_view.cpp b/scwx-qt/source/scwx/qt/view/level3_raster_view.cpp
index b51c2cd0..3056cc03 100644
--- a/scwx-qt/source/scwx/qt/view/level3_raster_view.cpp
+++ b/scwx-qt/source/scwx/qt/view/level3_raster_view.cpp
@@ -33,12 +33,20 @@ public:
    }
    ~Level3RasterViewImpl() { threadPool_.join(); };
 
+   [[nodiscard]] inline std::uint8_t
+   RemapDataMoment(std::uint8_t dataMoment) const;
+
    boost::asio::thread_pool threadPool_ {1u};
 
-   std::vector   vertices_;
-   std::vector dataMoments8_;
+   std::vector        vertices_ {};
+   std::vector dataMoments8_ {};
+   std::uint8_t              edgeValue_ {};
+
+   bool showSmoothedRangeFolding_ {false};
 
    std::shared_ptr lastRasterData_ {};
+   bool lastShowSmoothedRangeFolding_ {false};
+   bool lastSmoothingEnabled_ {false};
 
    float    latitude_;
    float    longitude_;
@@ -109,6 +117,9 @@ void Level3RasterView::ComputeSweep()
 
    std::shared_ptr radarProductManager =
       radar_product_manager();
+   const bool smoothingEnabled          = smoothing_enabled();
+   p->showSmoothedRangeFolding_         = show_smoothed_range_folding();
+   const bool& showSmoothedRangeFolding = p->showSmoothedRangeFolding_;
 
    // Retrieve message from Radar Product Manager
    std::shared_ptr message;
@@ -139,7 +150,10 @@ void Level3RasterView::ComputeSweep()
       Q_EMIT SweepNotComputed(types::NoUpdateReason::InvalidData);
       return;
    }
-   else if (gpm == graphic_product_message())
+   else if (gpm == graphic_product_message() &&
+            smoothingEnabled == p->lastSmoothingEnabled_ &&
+            (showSmoothedRangeFolding == p->lastShowSmoothedRangeFolding_ ||
+             !smoothingEnabled))
    {
       // Skip if this is the message we previously processed
       Q_EMIT SweepNotComputed(types::NoUpdateReason::NoChange);
@@ -147,6 +161,9 @@ void Level3RasterView::ComputeSweep()
    }
    set_graphic_product_message(gpm);
 
+   p->lastShowSmoothedRangeFolding_ = showSmoothedRangeFolding;
+   p->lastSmoothingEnabled_         = smoothingEnabled;
+
    // A message with radial data should have a Product Description Block and
    // Product Symbology Block
    std::shared_ptr descriptionBlock =
@@ -231,16 +248,18 @@ void Level3RasterView::ComputeSweep()
    const GeographicLib::Geodesic& geodesic =
       util::GeographicLib::DefaultGeodesic();
 
-   const uint16_t xResolution = descriptionBlock->x_resolution_raw();
-   const uint16_t yResolution = descriptionBlock->y_resolution_raw();
-   double         iCoordinate =
+   const std::uint16_t xResolution = descriptionBlock->x_resolution_raw();
+   const std::uint16_t yResolution = descriptionBlock->y_resolution_raw();
+   const double        iCoordinate =
       (-rasterData->i_coordinate_start() - 1.0 - p->range_) * 1000.0;
-   double jCoordinate =
+   const double jCoordinate =
       (rasterData->j_coordinate_start() + 1.0 + p->range_) * 1000.0;
+   const double xOffset = (smoothingEnabled) ? xResolution * 0.5 : 0.0;
+   const double yOffset = (smoothingEnabled) ? yResolution * 0.5 : 0.0;
 
-   size_t numCoordinates =
+   const std::size_t numCoordinates =
       static_cast(rows + 1) * static_cast(maxColumns + 1);
-   auto coordinateRange =
+   const auto coordinateRange =
       boost::irange(0, static_cast(numCoordinates));
 
    std::vector coordinates;
@@ -260,8 +279,8 @@ void Level3RasterView::ComputeSweep()
          const uint32_t col = index % (rows + 1);
          const uint32_t row = index / (rows + 1);
 
-         const double i = iCoordinate + xResolution * col;
-         const double j = jCoordinate - yResolution * row;
+         const double i = iCoordinate + xResolution * col + xOffset;
+         const double j = jCoordinate - yResolution * row - yOffset;
 
          // Calculate polar coordinates based on i and j
          const double angle  = std::atan2(i, j) * 180.0 / M_PI;
@@ -299,25 +318,83 @@ void Level3RasterView::ComputeSweep()
    // Compute threshold at which to display an individual bin
    const uint16_t snrThreshold = descriptionBlock->threshold();
 
-   for (size_t row = 0; row < rasterData->number_of_rows(); ++row)
+   const std::size_t rowCount = (smoothingEnabled) ?
+                                   rasterData->number_of_rows() - 1 :
+                                   rasterData->number_of_rows();
+
+   if (smoothingEnabled)
    {
-      const auto dataMomentsArray8 =
+      // For most products other than reflectivity, the edge should not go to
+      // the bottom of the color table
+      p->edgeValue_ = ComputeEdgeValue();
+   }
+
+   for (std::size_t row = 0; row < rowCount; ++row)
+   {
+      const std::size_t nextRow =
+         (row == static_cast(rasterData->number_of_rows() - 1)) ?
+            0 :
+            row + 1;
+
+      const auto& dataMomentsArray8 =
          rasterData->level(static_cast(row));
+      const auto& nextDataMomentsArray8 =
+         rasterData->level(static_cast(nextRow));
 
       for (size_t bin = 0; bin < dataMomentsArray8.size(); ++bin)
       {
-         constexpr size_t vertexCount = 6;
-
-         // Store data moment value
-         uint8_t dataValue = dataMomentsArray8[bin];
-         if (dataValue < snrThreshold && dataValue != RANGE_FOLDED)
+         if (!smoothingEnabled)
          {
-            continue;
+            static constexpr std::size_t vertexCount = 6;
+
+            // Store data moment value
+            const std::uint8_t& dataValue = dataMomentsArray8[bin];
+            if (dataValue < snrThreshold && dataValue != RANGE_FOLDED)
+            {
+               continue;
+            }
+
+            for (size_t m = 0; m < vertexCount; m++)
+            {
+               dataMoments8[mIndex++] = dataValue;
+            }
          }
-
-         for (size_t m = 0; m < vertexCount; m++)
+         else
          {
-            dataMoments8[mIndex++] = dataValue;
+            // Validate indices are all in range
+            if (bin + 1 >= dataMomentsArray8.size() ||
+                bin + 1 >= nextDataMomentsArray8.size())
+            {
+               continue;
+            }
+
+            const std::uint8_t& dm1 = dataMomentsArray8[bin];
+            const std::uint8_t& dm2 = dataMomentsArray8[bin + 1];
+            const std::uint8_t& dm3 = nextDataMomentsArray8[bin];
+            const std::uint8_t& dm4 = nextDataMomentsArray8[bin + 1];
+
+            if ((!showSmoothedRangeFolding && //
+                 (dm1 < snrThreshold || dm1 == RANGE_FOLDED) &&
+                 (dm2 < snrThreshold || dm2 == RANGE_FOLDED) &&
+                 (dm3 < snrThreshold || dm3 == RANGE_FOLDED) &&
+                 (dm4 < snrThreshold || dm4 == RANGE_FOLDED)) ||
+                (showSmoothedRangeFolding && //
+                 dm1 < snrThreshold && dm1 != RANGE_FOLDED &&
+                 dm2 < snrThreshold && dm2 != RANGE_FOLDED &&
+                 dm3 < snrThreshold && dm3 != RANGE_FOLDED &&
+                 dm4 < snrThreshold && dm4 != RANGE_FOLDED))
+            {
+               // Skip only if all data moments are hidden
+               continue;
+            }
+
+            // The order must match the store vertices section below
+            dataMoments8[mIndex++] = p->RemapDataMoment(dm1);
+            dataMoments8[mIndex++] = p->RemapDataMoment(dm2);
+            dataMoments8[mIndex++] = p->RemapDataMoment(dm4);
+            dataMoments8[mIndex++] = p->RemapDataMoment(dm1);
+            dataMoments8[mIndex++] = p->RemapDataMoment(dm3);
+            dataMoments8[mIndex++] = p->RemapDataMoment(dm4);
          }
 
          // Store vertices
@@ -332,17 +409,17 @@ void Level3RasterView::ComputeSweep()
          vertices[vIndex++] = coordinates[offset2];
          vertices[vIndex++] = coordinates[offset2 + 1];
 
-         vertices[vIndex++] = coordinates[offset3];
-         vertices[vIndex++] = coordinates[offset3 + 1];
+         vertices[vIndex++] = coordinates[offset4];
+         vertices[vIndex++] = coordinates[offset4 + 1];
+
+         vertices[vIndex++] = coordinates[offset1];
+         vertices[vIndex++] = coordinates[offset1 + 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];
       }
    }
    vertices.resize(vIndex);
@@ -359,6 +436,20 @@ void Level3RasterView::ComputeSweep()
    Q_EMIT SweepComputed();
 }
 
+std::uint8_t
+Level3RasterViewImpl::RemapDataMoment(std::uint8_t dataMoment) const
+{
+   if (dataMoment != 0 &&
+       (dataMoment != RANGE_FOLDED || showSmoothedRangeFolding_))
+   {
+      return dataMoment;
+   }
+   else
+   {
+      return edgeValue_;
+   }
+}
+
 std::optional
 Level3RasterView::GetBinLevel(const common::Coordinate& coordinate) const
 {
diff --git a/scwx-qt/source/scwx/qt/view/radar_product_view.cpp b/scwx-qt/source/scwx/qt/view/radar_product_view.cpp
index e2ca6c21..9c5a84de 100644
--- a/scwx-qt/source/scwx/qt/view/radar_product_view.cpp
+++ b/scwx-qt/source/scwx/qt/view/radar_product_view.cpp
@@ -1,4 +1,5 @@
 #include 
+#include 
 #include 
 #include 
 
@@ -28,26 +29,44 @@ class RadarProductViewImpl
 {
 public:
    explicit RadarProductViewImpl(
+      RadarProductView*                             self,
       std::shared_ptr radarProductManager) :
+       self_ {self},
        initialized_ {false},
        sweepMutex_ {},
        selectedTime_ {},
        radarProductManager_ {radarProductManager}
    {
+      auto& productSettings = settings::ProductSettings::Instance();
+      connection_           = productSettings.changed_signal().connect(
+         [this]()
+         {
+            showSmoothedRangeFolding_ = settings::ProductSettings::Instance()
+                                           .show_smoothed_range_folding()
+                                           .GetValue();
+            self_->Update();
+         });
+      ;
    }
    ~RadarProductViewImpl() {}
 
+   RadarProductView* self_;
+
    bool       initialized_;
    std::mutex sweepMutex_;
 
    std::chrono::system_clock::time_point selectedTime_;
+   bool                                  showSmoothedRangeFolding_ {false};
+   bool                                  smoothingEnabled_ {false};
 
    std::shared_ptr radarProductManager_;
+
+   boost::signals2::scoped_connection connection_;
 };
 
 RadarProductView::RadarProductView(
    std::shared_ptr radarProductManager) :
-    p(std::make_unique(radarProductManager)) {};
+    p(std::make_unique(this, radarProductManager)) {};
 RadarProductView::~RadarProductView() = default;
 
 const std::vector&
@@ -87,6 +106,16 @@ std::chrono::system_clock::time_point RadarProductView::selected_time() const
    return p->selectedTime_;
 }
 
+bool RadarProductView::show_smoothed_range_folding() const
+{
+   return p->showSmoothedRangeFolding_;
+}
+
+bool RadarProductView::smoothing_enabled() const
+{
+   return p->smoothingEnabled_;
+}
+
 std::chrono::system_clock::time_point RadarProductView::sweep_time() const
 {
    return {};
@@ -105,6 +134,11 @@ void RadarProductView::set_radar_product_manager(
    ConnectRadarProductManager();
 }
 
+void RadarProductView::set_smoothing_enabled(bool smoothingEnabled)
+{
+   p->smoothingEnabled_ = smoothingEnabled;
+}
+
 void RadarProductView::Initialize()
 {
    ComputeSweep();
diff --git a/scwx-qt/source/scwx/qt/view/radar_product_view.hpp b/scwx-qt/source/scwx/qt/view/radar_product_view.hpp
index c695a9e5..31d47840 100644
--- a/scwx-qt/source/scwx/qt/view/radar_product_view.hpp
+++ b/scwx-qt/source/scwx/qt/view/radar_product_view.hpp
@@ -47,12 +47,16 @@ public:
    virtual std::uint16_t                         vcp() const        = 0;
    virtual const std::vector&             vertices() const   = 0;
 
-   std::shared_ptr radar_product_manager() const;
-   std::chrono::system_clock::time_point         selected_time() const;
-   std::mutex&                                   sweep_mutex();
+   [[nodiscard]] std::shared_ptr
+   radar_product_manager() const;
+   [[nodiscard]] std::chrono::system_clock::time_point selected_time() const;
+   [[nodiscard]] bool        show_smoothed_range_folding() const;
+   [[nodiscard]] bool        smoothing_enabled() const;
+   [[nodiscard]] std::mutex& sweep_mutex();
 
    void set_radar_product_manager(
       std::shared_ptr radarProductManager);
+   void set_smoothing_enabled(bool smoothingEnabled);
 
    void Initialize();
    virtual void
diff --git a/test/data b/test/data
index eaf8f185..0eb47590 160000
--- a/test/data
+++ b/test/data
@@ -1 +1 @@
-Subproject commit eaf8f185ce2b3a3248da1a4d6c8e2e9265638f15
+Subproject commit 0eb475909f9e64ce81e7b8b39420d980b81b3baa