Skip to content

v0.2.48..v0.2.49 changeset RandomWaySplitter.cpp

Garret Voltz edited this page Oct 2, 2019 · 1 revision
diff --git a/hoot-core/src/main/cpp/hoot/core/visitors/RandomWaySplitter.cpp b/hoot-core/src/main/cpp/hoot/core/visitors/RandomWaySplitter.cpp
new file mode 100644
index 0000000..a958df7
--- /dev/null
+++ b/hoot-core/src/main/cpp/hoot/core/visitors/RandomWaySplitter.cpp
@@ -0,0 +1,323 @@
+/*
+ * 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) 2015, 2017, 2018, 2019 DigitalGlobe (http://www.digitalglobe.com/)
+ */
+#include "RandomWaySplitter.h"
+
+// boost
+#include <boost/random/uniform_real.hpp>
+#include <boost/random/uniform_int.hpp>
+
+// hoot
+#include <hoot/core/util/Factory.h>
+#include <hoot/core/elements/OsmMap.h>
+#include <hoot/core/util/MapProjector.h>
+#include <hoot/core/util/ConfigOptions.h>
+#include <hoot/core/algorithms/splitter/WaySplitter.h>
+#include <hoot/core/algorithms/splitter/MultiLineStringSplitter.h>
+#include <hoot/core/util/RandomNumberUtils.h>
+#include <hoot/core/criterion/HighwayCriterion.h>
+
+// Tgs
+#include <tgs/System/Time.h>
+
+using namespace std;
+
+namespace hoot
+{
+
+HOOT_FACTORY_REGISTER(ElementVisitor, RandomWaySplitter)
+
+RandomWaySplitter::RandomWaySplitter() :
+_splitRecursionLevel(0)
+{
+  _localRng.reset(new boost::minstd_rand());
+  _rng = _localRng.get();
+}
+
+void RandomWaySplitter::setOsmMap(OsmMap* map)
+{
+  _map = map;
+  MapProjector::projectToPlanar(_map->shared_from_this());
+}
+
+void RandomWaySplitter::setConfiguration(const Settings& conf)
+{
+  ConfigOptions configOptions(conf);
+  setWaySplitProbability(configOptions.getRandomWaySplitterProbability());
+  setMinNodeSpacing(configOptions.getRandomWaySplitterMinNodeSpacing());
+  const int seed = configOptions.getRandomSeed();
+  LOG_VARD(seed);
+  if (seed == -1)
+  {
+    _rng->seed(RandomNumberUtils::generateSeed());
+  }
+  else
+  {
+    _rng->seed(seed);
+  }
+}
+
+void RandomWaySplitter::visit(const std::shared_ptr<Element>& e)
+{
+  if (!_map)
+  {
+    throw IllegalArgumentException("No map passed to way splitter.");
+  }
+  else if (_map->getProjection()->IsGeographic())
+  {
+    throw IllegalArgumentException("Input map must be projected to planar.");
+  }
+
+  LOG_TRACE(e->getElementType());
+  if (HighwayCriterion().isSatisfied(e))
+  {
+    _split(e);
+  }
+}
+
+vector<ElementPtr> RandomWaySplitter::_split(ElementPtr element)
+{
+  //randomly select elements and split them into two parts
+  boost::uniform_real<> randomSplitDistribution(0.0, 1.0);
+  const double randomSplitNum = randomSplitDistribution(*_rng);
+  if (randomSplitNum <= _waySplitProbability)
+  {
+    LOG_TRACE("element " << element->getElementId() << " *will* be split based on a split " <<
+      "probability of: " <<  _waySplitProbability << " and a randomly generated number: " <<
+      randomSplitNum << "\n");
+
+    _splitRecursionLevel++;
+    LOG_VART(_splitRecursionLevel);
+
+    const int numNodesBeforeSplit = _map->getNodes().size();
+    LOG_VART(numNodesBeforeSplit);
+    const int numWaysBeforeSplit = _map->getWays().size();
+    LOG_VART(numWaysBeforeSplit);
+
+    WayLocation waySplitPoint;
+    MultiLineStringLocation multiLineSplitPoint;
+    QList<long> nodeIdsBeforeSplit;
+    int segmentIndex = -1;
+    ElementId wayId;
+    //determine where to split the element
+    if (element->getElementType() == ElementType::Way)
+    {
+      WayPtr way = std::dynamic_pointer_cast<Way>(element);
+      LOG_VART(way->getNodeCount());
+      nodeIdsBeforeSplit = QVector<long>::fromStdVector(way->getNodeIds()).toList();
+      LOG_VART(nodeIdsBeforeSplit);
+      waySplitPoint = _calcSplitPoint(way);
+    }
+    else
+    {
+      multiLineSplitPoint = _calcSplitPoint(std::dynamic_pointer_cast<Relation>(element), wayId);
+      waySplitPoint = multiLineSplitPoint.getWayLocation();
+    }
+
+    const QString distanceMsgStrEnd =
+      QString("a minimum node spacing of ")
+        .append(QString::number(_minNodeSpacing))
+        .append(" meters");
+    if (!waySplitPoint.isValid())
+    {
+      _splitRecursionLevel--;
+      LOG_VART(_splitRecursionLevel);
+
+      LOG_TRACE("split point *will not* be used because *it violates* " << distanceMsgStrEnd);
+      //if it violates the min node spacing, return an empty element collection, which will end the
+      //recursive splitting on the current way
+      return vector<ElementPtr>();
+    }
+    else
+    {
+      LOG_TRACE("split point *will* be used because it *does not* violate " << distanceMsgStrEnd);
+      segmentIndex = waySplitPoint.getSegmentIndex();
+      LOG_VART(segmentIndex);
+    }
+
+    //split the element
+    vector<ElementPtr> newElementsAfterSplit;
+    if (element->getElementType() == ElementType::Way)
+    {
+      vector<WayPtr> newWaysAfterSplit =
+        WaySplitter::split(_map->shared_from_this(),
+          std::dynamic_pointer_cast<Way>(element), waySplitPoint);
+      for (size_t i = 0; i < newWaysAfterSplit.size(); i++)
+      {
+        newElementsAfterSplit.push_back(newWaysAfterSplit.at(i));
+      }
+    }
+    else
+    {
+      ElementPtr match;
+      MultiLineStringSplitter().split(_map->shared_from_this(), multiLineSplitPoint, match);
+      newElementsAfterSplit.push_back(match);
+    }
+
+    const int numNodesAfterSplit = _map->getNodes().size();
+    LOG_VART(numNodesAfterSplit);
+    const int numNewNodesCreatedBySplit = numNodesAfterSplit - numNodesBeforeSplit;
+    LOG_VART(numNewNodesCreatedBySplit);
+    LOG_VART(_map->getWays().size());
+
+    if (numNewNodesCreatedBySplit > 0)
+    {
+      WayPtr way = std::dynamic_pointer_cast<Way>(element);
+      //Its possible that the splitting of a relation could generate a new node.  In that case,
+      //_updateNewNodeProperties does not need to be called b/c the MultiLineStringSplitter has
+      //already properly updated the new node's properties.  when a way is split, however, the
+      //new node's properties must be updated by the call to _updateNewNodeProperties.
+      if (way != 0)
+      {
+        assert(nodeIdsBeforeSplit.size() > 0);
+        //update properties on any nodes added as a result of the way splitting (new ways created as a
+        //result of the splitting will already have had their parent's tags added by WaySplitter)
+        _updateNewNodeProperties(
+          _getNodeAddedBySplit(nodeIdsBeforeSplit, newElementsAfterSplit),
+          _map->getNode(way->getNodeId(segmentIndex)),
+          _map->getNode(way->getNodeId(segmentIndex + 1)));
+      }
+    }
+
+    //recursive call
+    for (vector<ElementPtr>::const_iterator it = newElementsAfterSplit.begin();
+         it != newElementsAfterSplit.end(); ++it)
+    {
+      _split(*it);
+    }
+
+    return newElementsAfterSplit;
+  }
+  else
+  {
+    LOG_TRACE("element " << element->getElementId() << " *will not* be split based on a split " <<
+      "probability of: " << _waySplitProbability << " and a randomly generated number: " <<
+      randomSplitNum << "\n");
+  }
+
+  //end the recursive splitting on the current way
+  return vector<ElementPtr>();
+}
+
+WayLocation RandomWaySplitter::_calcSplitPoint(ConstWayPtr way) const
+{
+  //create a way location that is the minimum node spacing distance from the beginning of the way
+  WayLocation splitWayStart(_map->shared_from_this(), way, _minNodeSpacing);
+  //create a way location that is the minimum node spacing from the end of the way
+  WayLocation splitWayEnd =
+    WayLocation::createAtEndOfWay(_map->shared_from_this(), way).move(-1 * _minNodeSpacing);
+  //if the length between the way locations is greater than zero, then a way location can be
+  //selected that doesn't violate the min node spacing
+  const double splitWayLength =
+    splitWayEnd.calculateDistanceOnWay() - splitWayStart.calculateDistanceOnWay();
+  LOG_VART(splitWayLength);
+  if (splitWayLength > 0)
+  {
+    boost::uniform_real<> randomSplitPointDistribution(0.0, splitWayLength);
+    const double splitPoint = randomSplitPointDistribution(*_rng);
+    LOG_VART(splitPoint);
+    return splitWayStart.move(splitPoint);
+  }
+  //otherwise, return an empty location
+  else
+  {
+    return WayLocation();
+  }
+}
+
+MultiLineStringLocation RandomWaySplitter::_calcSplitPoint(ConstRelationPtr relation,
+                                                           ElementId& wayId) const
+{
+  const vector<RelationData::Entry>& members = relation->getMembers();
+  LOG_VART(members.size());
+
+  //find the way to split on
+  boost::uniform_int<> randomWayIndexDistribution(0, members.size() - 1);
+  int wayIndex = randomWayIndexDistribution(*_rng);
+  wayId = members.at(wayIndex).getElementId();
+  LOG_VART(wayIndex);
+  LOG_VART(wayId);
+  ElementPtr element = _map->getElement(wayId);
+  if (element->getElementType() != ElementType::Way)
+  {
+    throw HootException(
+      "PERTY feature splitting for multi-line string relations may only occur on relations which contain only ways.");
+  }
+  WayPtr way = std::dynamic_pointer_cast<Way>(element);
+  LOG_VART(way->getNodeCount());
+
+  //calculate the split point
+  WayLocation wayLocation = _calcSplitPoint(way);
+  //return it, if its valid
+  if (wayLocation.isValid())
+  {
+    return
+      MultiLineStringLocation(
+        _map->shared_from_this(),
+        relation,
+        wayIndex,
+        wayLocation);
+  }
+  //otherwise, return an empty location
+  else
+  {
+    return MultiLineStringLocation();
+  }
+}
+
+NodePtr RandomWaySplitter::_getNodeAddedBySplit(const QList<long>& nodeIdsBeforeSplit,
+  const vector<ElementPtr>& newElementsAfterSplit) const
+{
+  //newElementsAfterSplit is assumed to only contain ways; find the new node created by the way
+  //split; it will be the last node in the first way, which is the same as the first node in the
+  //last way
+  ConstWayPtr firstWay = std::dynamic_pointer_cast<Way>(newElementsAfterSplit.at(0));
+  const long lastNodeIdInFirstWay = firstWay->getNodeIds().at(firstWay->getNodeCount() - 1);
+  LOG_VART(lastNodeIdInFirstWay);
+  ConstWayPtr lastWay = std::dynamic_pointer_cast<Way>(newElementsAfterSplit.at(1));
+  const long firstNodeIdInLastWay = lastWay->getNodeIds().at(0);
+  LOG_VART(firstNodeIdInLastWay);
+  assert(lastNodeIdInFirstWay == firstNodeIdInLastWay);
+  assert(!nodeIdsBeforeSplit.contains(lastNodeIdInFirstWay));
+  LOG_VART(nodeIdsBeforeSplit);
+  return _map->getNode(firstNodeIdInLastWay);
+}
+
+void RandomWaySplitter::_updateNewNodeProperties(NodePtr newNode,
+                                                 ConstNodePtr firstSplitBetweenNode,
+                                                 ConstNodePtr lastSplitBetweenNode)
+{
+  //arbitrarily copy the status from one split between node to the new node
+  newNode->setStatus(firstSplitBetweenNode->getStatus());
+  //add a circular error to the new node that's a weighted average of the circular error on the
+  //two split between nodes
+  newNode->setCircularError(
+    (firstSplitBetweenNode->getCircularError() + lastSplitBetweenNode->getCircularError()) / 2);
+  LOG_TRACE(
+    "Updated the properties of a node created as a result of a way split: " << newNode->toString());
+}
+
+}
Clone this wiki locally