Skip to content

v0.2.53..v0.2.54 changeset HighwayCornerSplitter.cpp

Garret Voltz edited this page Mar 31, 2020 · 1 revision
diff --git a/hoot-core/src/main/cpp/hoot/core/algorithms/splitter/HighwayCornerSplitter.cpp b/hoot-core/src/main/cpp/hoot/core/algorithms/splitter/HighwayCornerSplitter.cpp
new file mode 100644
index 0000000..50841610
--- /dev/null
+++ b/hoot-core/src/main/cpp/hoot/core/algorithms/splitter/HighwayCornerSplitter.cpp
@@ -0,0 +1,320 @@
+/*
+ * This file is part of Hootenanny.
+ *
+ * Hootenanny is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * --------------------------------------------------------------------
+ *
+ * The following copyright notices are generated automatically. If you
+ * have a new notice to add, please use the format:
+ * " * @copyright Copyright ..."
+ * This will properly maintain the copyright information. DigitalGlobe
+ * copyrights will be updated automatically.
+ *
+ * @copyright Copyright (C) 2017, 2018, 2019, 2020 DigitalGlobe (http://www.digitalglobe.com/)
+ */
+
+#include "HighwayCornerSplitter.h"
+
+// Hoot
+#include <hoot/core/algorithms/Distance.h>
+#include <hoot/core/algorithms/WayHeading.h>
+#include <hoot/core/algorithms/linearreference/WayLocation.h>
+#include <hoot/core/algorithms/splitter/WaySplitter.h>
+#include <hoot/core/elements/OsmMap.h>
+#include <hoot/core/elements/Way.h>
+#include <hoot/core/index/OsmMapIndex.h>
+#include <hoot/core/util/Factory.h>
+
+#include <geos/geom/LineString.h>
+#include <geos/geom/CoordinateArraySequence.h>
+#include <geos/geom/GeometryFactory.h>
+using namespace geos::geom;
+#include <hoot/core/elements/ElementConverter.h>
+
+// Qt
+#include <QDebug>
+#include <QTextStream>
+
+using namespace std;
+
+namespace hoot
+{
+
+HOOT_FACTORY_REGISTER(OsmMapOperation, HighwayCornerSplitter)
+
+HighwayCornerSplitter::HighwayCornerSplitter()
+  : _cornerThreshold(ConfigOptions().getHighwayCornerSplitterThresholdDefaultValue()),
+    _splitRounded(ConfigOptions().getHighwayCornerSplitterRoundedSplitDefaultValue()),
+    _roundedThreshold(ConfigOptions().getHighwayCornerSplitterRoundedThresholdDefaultValue()),
+    _roundedMaxNodeCount(ConfigOptions().getHighwayCornerSplitterRoundedMaxNodeCountDefaultValue())
+{
+}
+
+HighwayCornerSplitter::HighwayCornerSplitter(const std::shared_ptr<OsmMap>& map)
+  : _map(map),
+    _cornerThreshold(ConfigOptions().getHighwayCornerSplitterThresholdDefaultValue()),
+    _splitRounded(ConfigOptions().getHighwayCornerSplitterRoundedSplitDefaultValue()),
+    _roundedThreshold(ConfigOptions().getHighwayCornerSplitterRoundedThresholdDefaultValue()),
+    _roundedMaxNodeCount(ConfigOptions().getHighwayCornerSplitterRoundedMaxNodeCountDefaultValue())
+{
+}
+
+void HighwayCornerSplitter::splitCorners(const std::shared_ptr<OsmMap>& map)
+{
+  HighwayCornerSplitter splitter(map);
+  return splitter.splitCorners();
+}
+
+void HighwayCornerSplitter::splitCorners()
+{
+  _numAffected = 0;
+
+  // Get a list of ways (that look like roads) in the map
+  HighwayCriterion highwayCrit(_map);
+  for (WayMap::const_iterator it = _map->getWays().begin(); it != _map->getWays().end(); ++it)
+  {
+    if (highwayCrit.isSatisfied(it->second))
+    {
+      _todoWays.push_back(it->first);
+    }
+  }
+
+  double threshold = toRadians(_cornerThreshold);
+  // Traverse each way, looking for corners to split
+  // Splitting a way will usually result in adding a new way to _todoWays
+  for (size_t i = 0; i < _todoWays.size(); i++)
+  {
+    ConstWayPtr pWay = _map->getWay(_todoWays[i]);
+    size_t nodeCount = pWay->getNodeCount();
+    // If the way has just two nodes, there are no corners
+    if (nodeCount > 2)
+    {
+      // Look until we find a split, or get to the end
+      bool split = false;
+      for (size_t nodeIdx = 1; nodeIdx < nodeCount - 1 && !split; nodeIdx++)
+      {
+        WayLocation prev(_map, pWay, nodeIdx-1, 0.0);
+        WayLocation current(_map, pWay, nodeIdx, 0.0);
+        WayLocation next(_map, pWay, nodeIdx+1, 0.0);
+
+        // Calculate headings
+        const double twopi = M_PI*2.0;
+        double h1 = atan2(current.getCoordinate().y - prev.getCoordinate().y,
+                          current.getCoordinate().x - prev.getCoordinate().x);
+        double h2 = atan2(next.getCoordinate().y - current.getCoordinate().y,
+                          next.getCoordinate().x - current.getCoordinate().x);
+
+        double delta = fabs(h2-h1);
+
+        if (delta > M_PI)
+          delta = twopi - delta;
+
+        // If we make enough of a turn, split the way
+        if (delta > threshold)
+        {
+          LOG_TRACE("splitting way with delta: " << delta);
+          split = _splitWay(pWay->getId(), nodeIdx, pWay->getNodeId(nodeIdx));
+        }
+      }
+    }
+  }
+  //  Try out the rounded corner splitting algorithm if desired
+  if (_splitRounded)
+    _splitRoundedCorners();
+}
+
+void HighwayCornerSplitter::_splitRoundedCorners()
+{
+  _todoWays.clear();
+  //  Get a list of ways (that look like roads) in the map
+  HighwayCriterion highwayCriterion(_map);
+  for (WayMap::const_iterator it = _map->getWays().begin(); it != _map->getWays().end(); ++it)
+  {
+    if (highwayCriterion.isSatisfied(it->second))
+      _todoWays.push_back(it->first);
+  }
+  //  Use the threashold from the configuration for rounded corners
+  const double threshold = toRadians(_roundedThreshold);
+  //  Display trace
+  bool trace = Log::getInstance().getLevel() == Log::Trace;
+  //  Traverse each way, looking for corners to split
+  //  Splitting a way will usually result in adding a new way to _todoWays
+  for (size_t i = 0; i < _todoWays.size(); i++)
+  {
+    ConstWayPtr pWay = _map->getWay(_todoWays[i]);
+    size_t nodeCount = pWay->getNodeCount();
+
+    // If the way has less than 4 nodes it can't be rounded
+    if (nodeCount >= 4)
+    {
+      //  Precalculate the headings between points
+      QVector<double> headings;
+      QVector<double> distances;
+      for (size_t nodeIdx = 0; nodeIdx < nodeCount - 1; ++nodeIdx)
+      {
+        //  Get the current and next way locations
+        WayLocation current(_map, pWay, nodeIdx, 0.0);
+        WayLocation next(_map, pWay, nodeIdx + 1, 0.0);
+        //  Calculate and store the heading from point A to point B
+        double heading = atan2(next.getCoordinate().y - current.getCoordinate().y,
+                               next.getCoordinate().x - current.getCoordinate().x);
+        headings.push_back(heading);
+        //  Maybe we can do something with the distance, maybe not
+        double distance = Distance::euclidean(next.getCoordinate(), current.getCoordinate());
+        distances.push_back(distance);
+      }
+      int max_start_index = 0;
+      double max_total_delta = 0.0;
+      //  Iterate the headings `max_node_count` at a time to find the largest heading delta and use
+      // that section as the curve
+      for (int start_index = 0; start_index < headings.size() - 2; ++start_index)
+      {
+        double total = 0.0;
+        for (int i = 0; i < _roundedMaxNodeCount && start_index + i + 1 < headings.size(); ++i)
+        {
+          double delta = headings[start_index + i + 1] - headings[start_index + i];
+          total += delta;
+        }
+        //  Save the highest heading delta and the start index of that subline
+        if (fabs(total) > fabs(max_total_delta))
+        {
+          max_start_index = start_index;
+          max_total_delta = total;
+        }
+      }
+      //  Make the split if the heading delta exceeds the threashold
+      if (fabs(max_total_delta) > threshold)
+      {
+        //  Find the middle of the threshold to split at
+        double mid_point = max_total_delta / 2.0;
+        double total = 0.0;
+        for (int index = max_start_index + 1;
+             index < max_start_index + _roundedMaxNodeCount && index < headings.size(); ++index)
+        {
+          double delta = headings[index] - headings[index - 1];
+          total += delta;
+          //  Split the way once the cumulative heading delta exceeds the midpoint delta value
+          if (fabs(total) > fabs(mid_point))
+          {
+            _splitWay(pWay->getId(), index + 1, pWay->getNodeId(index + 1), false);
+            break;
+          }
+        }
+      }
+      //  Only build up the tables and display them in trace mode
+      if (trace)
+      {
+        QString buffer;
+        QTextStream ts(&buffer);
+        for (int i = 0; i < headings.size(); ++i)
+          ts << QString().setNum(headings[i], 'f') << "\t| " <<
+                QString().setNum(distances[i], 'f') << "\n";
+        //  Output a bunch of stuff here to help develop the algorithm
+        LOG_TRACE("\nWay: " << pWay->getTags().getName() <<
+                  "\nHeadings\t| Distances" <<
+                  "\n" << ts.readAll()
+                 );
+      }
+    }
+  }
+}
+
+bool HighwayCornerSplitter::_splitWay(long wayId, long nodeIdx, long nodeId, bool sharpCorner)
+{
+  WayPtr pWay = _map->getWay(wayId);
+  if (!pWay)
+  {
+    LOG_TRACE("way at " << wayId << " does not exist.");
+    return false;
+  }
+  //  For sharp corners, some small
+  if (sharpCorner)
+  {
+    GeometryFactory::unique_ptr factory = GeometryFactory::create();
+    //  Check the previous segment to ensure it is larger than the circular error before splitting
+    if (nodeIdx == 1)
+    {
+      std::shared_ptr<LineString> ls = ElementConverter(_map).convertToLineString(pWay);
+      CoordinateArraySequence* subline = new CoordinateArraySequence();
+      subline->add(0, ls->getCoordinateN(nodeIdx), true);
+      subline->add(1, ls->getCoordinateN(nodeIdx - 1), true);
+      std::shared_ptr<LineString> sub(factory->createLineString(subline));
+      if (sub->getLength() <= pWay->getCircularError())
+        return false;
+    }
+    //  Check the next segment to ensure it is larger than the circular error before splitting
+    if (nodeIdx == (long)(pWay->getNodeCount() - 2))
+    {
+      std::shared_ptr<LineString> ls = ElementConverter(_map).convertToLineString(pWay);
+      CoordinateArraySequence* subline = new CoordinateArraySequence();
+      subline->add(0, ls->getCoordinateN(nodeIdx), true);
+      subline->add(1, ls->getCoordinateN(nodeIdx + 1), true);
+      std::shared_ptr<LineString> sub(factory->createLineString(subline));
+      if (sub->getLength() <= pWay->getCircularError())
+        return false;
+    }
+  }
+  LOG_TRACE("Splitting way: " << pWay->getElementId() << " at node: " <<
+            ElementId(ElementType::Node, nodeId));
+
+  // split the way and remove it from the map
+  WayLocation wayLoc(_map, pWay, nodeIdx, 0.0);
+  vector<WayPtr> splits = WaySplitter::split(_map, pWay, wayLoc);
+
+  // Process splits
+  if (splits.size() > 1)
+  {
+    LOG_VART(pWay->getElementId());
+    LOG_VART(pWay->getStatus());
+    LOG_VART(splits[0]->getElementId());
+
+    const ElementId splitWayId = pWay->getElementId();
+
+    // Make sure any ways that are part of relations continue to be part of those relations after
+    // they're split.
+    QList<ElementPtr> newWays;
+    foreach (const WayPtr& w, splits)
+      newWays.append(w);
+
+    _map->replace(pWay, newWays);
+
+    // Need to process the "right-hand-side" of the split, looking for more corners
+    _todoWays.push_back(splits[1]->getId());
+
+    LOG_VART(_map->containsElement(splitWayId));
+
+    //  Split was successful
+    _numAffected++;
+    return true;
+  }
+  //  No splits were made
+  return false;
+}
+
+void HighwayCornerSplitter::apply(std::shared_ptr<OsmMap> &map)
+{
+  splitCorners(map);
+}
+
+void HighwayCornerSplitter::setConfiguration(const Settings& conf)
+{
+  ConfigOptions options(conf);
+  _cornerThreshold = options.getHighwayCornerSplitterThreshold();
+  _splitRounded = options.getHighwayCornerSplitterRoundedSplit();
+  _roundedThreshold = options.getHighwayCornerSplitterRoundedThreshold();
+  _roundedMaxNodeCount = options.getHighwayCornerSplitterRoundedMaxNodeCount();
+}
+
+}
Clone this wiki locally