Skip to content

v0.2.49..v0.2.50 changeset PoiPolygonCache.cpp

Garret Voltz edited this page Nov 6, 2019 · 1 revision
diff --git a/hoot-core/src/main/cpp/hoot/core/conflate/poi-polygon/PoiPolygonCache.cpp b/hoot-core/src/main/cpp/hoot/core/conflate/poi-polygon/PoiPolygonCache.cpp
new file mode 100644
index 0000000..d85e61a
--- /dev/null
+++ b/hoot-core/src/main/cpp/hoot/core/conflate/poi-polygon/PoiPolygonCache.cpp
@@ -0,0 +1,465 @@
+/*
+ * 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) 2019 DigitalGlobe (http://www.digitalglobe.com/)
+ */
+#include "PoiPolygonCache.h"
+
+// geos
+#include <geos/util/TopologyException.h>
+
+// hoot
+#include <hoot/core/algorithms/extractors/poi-polygon/PoiPolygonTypeScoreExtractor.h>
+#include <hoot/core/elements/ElementConverter.h>
+#include <hoot/core/util/Log.h>
+#include <hoot/core/util/StringUtils.h>
+#include <hoot/core/util/Factory.h>
+
+namespace hoot
+{
+
+PoiPolygonCache::PoiPolygonCache(const ConstOsmMapPtr& map) :
+_map(map)
+{
+}
+
+PoiPolygonCache::~PoiPolygonCache()
+{
+}
+
+void PoiPolygonCache::clear()
+{
+  _numCacheHitsByCacheType.clear();
+  _numCacheEntriesByCacheType.clear();
+
+  _elementContainsCache.clear();
+  _isTypeCache.clear();
+  _hasCriterionCache.clear();
+  _poiNeighborCloserCache.clear();
+  _criterionCache.clear();
+  _geometryCache.clear();
+  _lineStringCache.clear();
+}
+
+void PoiPolygonCache::printCacheInfo()
+{
+  LOG_DEBUG(
+    "POI/Polygon caches used: " << StringUtils::formatLargeNumber(_numCacheHitsByCacheType.size()));
+  for (QMap<QString, int>::const_iterator numCacheHitsByCacheTypeItr = _numCacheHitsByCacheType.begin();
+       numCacheHitsByCacheTypeItr != _numCacheHitsByCacheType.end(); ++numCacheHitsByCacheTypeItr)
+  {
+    const QString line =
+      QString("%1:\t%2 hits     entries: %3")
+        .arg(numCacheHitsByCacheTypeItr.key())
+        .arg(StringUtils::formatLargeNumber(numCacheHitsByCacheTypeItr.value()))
+        .arg(
+          StringUtils::formatLargeNumber(
+            _numCacheEntriesByCacheType[numCacheHitsByCacheTypeItr.key()]));
+    LOG_DEBUG(line);
+  }
+}
+
+void PoiPolygonCache::_incrementCacheHitCount(const QString& cacheTypeKey)
+{
+  if (!_numCacheHitsByCacheType.contains(cacheTypeKey))
+  {
+    _numCacheHitsByCacheType[cacheTypeKey] = 1;
+  }
+  else
+  {
+    _numCacheHitsByCacheType[cacheTypeKey] = _numCacheHitsByCacheType[cacheTypeKey] + 1;
+  }
+}
+
+void PoiPolygonCache::_incrementCacheSizeCount(const QString& cacheTypeKey)
+{
+  if (!_numCacheEntriesByCacheType.contains(cacheTypeKey))
+  {
+    _numCacheEntriesByCacheType[cacheTypeKey] = 1;
+  }
+  else
+  {
+    _numCacheEntriesByCacheType[cacheTypeKey] = _numCacheEntriesByCacheType[cacheTypeKey] + 1;
+  }
+}
+
+std::shared_ptr<geos::geom::Geometry> PoiPolygonCache::_getGeometry(ConstElementPtr element)
+{
+  if (!element)
+  {
+    return std::shared_ptr<geos::geom::Geometry>();
+  }
+  LOG_VART(element->getElementType());
+
+  QHash<ElementId, std::shared_ptr<geos::geom::Geometry>>::const_iterator itr =
+    _geometryCache.find(element->getElementId());
+  if (itr != _geometryCache.end())
+  {
+    LOG_VART(itr.value());
+    _incrementCacheHitCount("geometry");
+    return itr.value();
+  }
+  else
+  {
+    std::shared_ptr<geos::geom::Geometry> newGeom;
+    QString errorMsg =
+      "Feature passed to PoiPolygonReviewReducer caused topology exception on conversion to a geometry: ";
+    try
+    {
+      newGeom = ElementConverter(_map).convertToGeometry(element);
+    }
+    catch (const geos::util::TopologyException& e)
+    {
+      if (_badGeomCount <= Log::getWarnMessageLimit())
+      {
+        LOG_TRACE(errorMsg << element->toString() << "\n" << e.what());
+        _badGeomCount++;
+      }
+    }
+    catch (const HootException& e)
+    {
+      if (_badGeomCount <= Log::getWarnMessageLimit())
+      {
+        LOG_TRACE(errorMsg << element->toString() << "\n" << e.what());
+        _badGeomCount++;
+      }
+    }
+    LOG_VART(newGeom.get());
+    if (newGeom.get() &&
+        QString::fromStdString(newGeom->toString()).toUpper().contains("EMPTY"))
+    {
+      if (_badGeomCount <= Log::getWarnMessageLimit())
+      {
+        LOG_TRACE("Invalid element passed to PoiPolygonReviewReducer: " << newGeom->toString());
+        _badGeomCount++;
+      }
+      newGeom.reset();
+    }
+    LOG_VART(newGeom.get());
+    _geometryCache[element->getElementId()] = newGeom;
+    _incrementCacheSizeCount("geometry");
+    return newGeom;
+  }
+}
+
+std::shared_ptr<geos::geom::LineString> PoiPolygonCache::_getLineString(ConstWayPtr poly)
+{
+  LOG_VART(poly.get());
+  if (!poly)
+  {
+    return std::shared_ptr<geos::geom::LineString>();
+  }
+
+  QHash<ElementId, std::shared_ptr<geos::geom::LineString>>::const_iterator itr =
+    _lineStringCache.find(poly->getElementId());
+  if (itr != _lineStringCache.end())
+  {
+    _incrementCacheHitCount("linestring");
+    return itr.value();
+  }
+  else
+  {
+    std::shared_ptr<geos::geom::LineString> newGeom;
+    try
+    {
+      newGeom = ElementConverter(_map).convertToLineString(poly);
+    }
+    catch (const geos::util::TopologyException& e)
+    {
+      if (_badGeomCount <= Log::getWarnMessageLimit())
+      {
+        LOG_TRACE(
+          "Feature passed to PoiPolygonReviewReducer caused topology exception on conversion to a " <<
+          "geometry: " << poly->toString() << "\n" << e.what());
+        _badGeomCount++;
+      }
+    }
+    LOG_VART(newGeom.get());
+    if (newGeom.get() &&
+        QString::fromStdString(newGeom->toString()).toUpper().contains("EMPTY"))
+    {
+      if (_badGeomCount <= Log::getWarnMessageLimit())
+      {
+        LOG_TRACE("Invalid element passed to PoiPolygonReviewReducer: " << newGeom->toString());
+        _badGeomCount++;
+      }
+      newGeom.reset();
+    }
+    LOG_VART(newGeom.get());
+    _lineStringCache[poly->getElementId()] = newGeom;
+    _incrementCacheSizeCount("linestring");
+    return newGeom;
+  }
+}
+
+bool PoiPolygonCache::polyContainsPoi(ConstWayPtr poly, ConstNodePtr point)
+{
+  if (!poly || !point)
+  {
+    return false;
+  }
+
+  const QString key =
+    poly->getElementId().toString() + ";" + point->getElementId().toString();
+  QHash<QString, bool>::const_iterator itr = _elementContainsCache.find(key);
+  if (itr != _elementContainsCache.end())
+  {
+    _incrementCacheHitCount("contains");
+    return itr.value();
+  }
+  else
+  {
+    std::shared_ptr<geos::geom::LineString> polyGeom = _getLineString(poly);
+    std::shared_ptr<geos::geom::Geometry> pointGeom = _getGeometry(point);
+    bool contains = false;
+    if (polyGeom && pointGeom)
+    {
+      contains = polyGeom->contains(pointGeom.get());
+    }
+    _elementContainsCache[key] = contains;
+    _incrementCacheSizeCount("contains");
+    return contains;
+  }
+}
+
+bool PoiPolygonCache::isType(ConstElementPtr element, const QString& type)
+{
+  if (!element || type.trimmed().isEmpty())
+  {
+    return false;
+  }
+
+  const QString key = element->getElementId().toString() + ";" + type;
+  QHash<QString, bool>::const_iterator itr = _isTypeCache.find(key);
+  if (itr != _isTypeCache.end())
+  {
+    _incrementCacheHitCount("isType");
+    return itr.value();
+  }
+  else
+  {
+    // A redesign is needed to make this less maintenance prone.
+    bool isType = false;
+    if (type == "park")
+    {
+      isType = PoiPolygonTypeScoreExtractor::isPark(element);
+    }
+    else if (type == "sport")
+    {
+      isType = PoiPolygonTypeScoreExtractor::isSport(element);
+    }
+    else if (type == "restaurant")
+    {
+      isType = PoiPolygonTypeScoreExtractor::isRestaurant(element);
+    }
+    else if (type == "specificSchool")
+    {
+      isType = PoiPolygonTypeScoreExtractor::isSpecificSchool(element);
+    }
+    else if (type == "natural")
+    {
+      isType = PoiPolygonTypeScoreExtractor::isNatural(element);
+    }
+    else if (type == "restroom")
+    {
+      isType = PoiPolygonTypeScoreExtractor::isRestroom(element);
+    }
+    else if (type == "parking")
+    {
+      isType = PoiPolygonTypeScoreExtractor::isParking(element);
+    }
+    else if (type == "school")
+    {
+      isType = PoiPolygonTypeScoreExtractor::isSchool(element);
+    }
+    else if (type == "playground")
+    {
+      isType = PoiPolygonTypeScoreExtractor::isPlayground(element);
+    }
+    else if (type == "parkish")
+    {
+      isType = PoiPolygonTypeScoreExtractor::isParkish(element);
+    }
+    else
+    {
+      throw IllegalArgumentException("Invalid type passed to PoiPolygonCache::isType: " + type);
+    }
+    _isTypeCache[key] = isType;
+    _incrementCacheSizeCount("isType");
+    return isType;
+  }
+}
+
+bool PoiPolygonCache::hasCriterion(ConstElementPtr element, const QString& criterionClassName)
+{
+  if (!element || criterionClassName.trimmed().isEmpty())
+  {
+    return false;
+  }
+
+  const QString key = element->getElementId().toString() + ";" + criterionClassName;
+  QHash<QString, bool>::const_iterator itr = _hasCriterionCache.find(key);
+  if (itr != _hasCriterionCache.end())
+  {
+    _incrementCacheHitCount("hasCrit");
+    return itr.value();
+  }
+  else
+  {
+    ElementCriterionPtr crit = _getCrit(criterionClassName);
+    const bool hasCrit = crit->isSatisfied(element);
+    _hasCriterionCache[key] = hasCrit;
+    _incrementCacheSizeCount("hasCrit");
+    return hasCrit;
+  }
+}
+
+ElementCriterionPtr PoiPolygonCache::_getCrit(const QString& criterionClassName)
+{
+  if (criterionClassName.trimmed().isEmpty())
+  {
+    return ElementCriterionPtr();
+  }
+
+  QHash<QString, ElementCriterionPtr>::const_iterator itr =
+    _criterionCache.find(criterionClassName);
+  if (itr != _criterionCache.end())
+  {
+    return itr.value();
+  }
+  else
+  {
+    ElementCriterionPtr crit =
+      ElementCriterionPtr(
+        Factory::getInstance().constructObject<ElementCriterion>(criterionClassName));
+    if (!crit)
+    {
+      throw IllegalArgumentException(
+        "Invalid criterion passed to PoiPolygonCache::hasCriterion: " + criterionClassName);
+    }
+    _criterionCache[criterionClassName] = crit;
+    return crit;
+  }
+}
+
+bool PoiPolygonCache::polyHasPoiNeighborCloserThanPoi(ConstWayPtr poly, ConstNodePtr poi,
+                                                      const std::set<ElementId>& poiNeighborIds,
+                                                      const double poiPolyDistance)
+{
+  if (!poly || !poi || poiNeighborIds.size() == 0)
+  {
+    return false;
+  }
+
+  const QString key = poly->getElementId().toString() + ";" + poi->getElementId().toString();
+  QHash<QString, bool>::const_iterator itr = _poiNeighborCloserCache.find(key);
+  if (itr != _poiNeighborCloserCache.end())
+  {
+    _incrementCacheHitCount("hasCloserPoiNeighbor");
+    return itr.value();
+  }
+  else
+  {
+    bool hasCloserPoiNeighbor = false;
+    LOG_VART(poiNeighborIds.size());
+    //LOG_VART(_poiNeighborIds);
+    for (std::set<ElementId>::const_iterator poiNeighborItr = poiNeighborIds.begin();
+         poiNeighborItr != poiNeighborIds.end(); ++poiNeighborItr)
+    {
+      ConstElementPtr poiNeighbor = _map->getElement(*poiNeighborItr);
+      LOG_VART(poiNeighbor.get());
+      if (poiNeighbor->getElementId() != poi->getElementId())
+      {
+        long neighborPoiToPolyDist = -1.0;
+        neighborPoiToPolyDist =
+          getPolyToPointDistance(poly, std::dynamic_pointer_cast<const Node>(poiNeighbor));
+        LOG_VART(neighborPoiToPolyDist);
+        if (neighborPoiToPolyDist != -1.0 && poiPolyDistance > neighborPoiToPolyDist)
+        {
+          hasCloserPoiNeighbor = true;
+          break;
+        }
+      }
+    }
+
+    _poiNeighborCloserCache[key] = hasCloserPoiNeighbor;
+    _incrementCacheSizeCount("hasCloserPoiNeighbor");
+    return hasCloserPoiNeighbor;
+  }
+}
+
+bool PoiPolygonCache::elementIntersectsElement(ConstElementPtr element1, ConstElementPtr element2)
+{
+  if (!element1 || !element2)
+  {
+    return false;
+  }
+
+  std::shared_ptr<geos::geom::Geometry> geom1 = _getGeometry(element1);
+  std::shared_ptr<geos::geom::Geometry> geom2 = _getGeometry(element2);
+  bool intersects = false;
+  if (geom1 && geom2)
+  {
+    intersects = geom1->intersects(geom2.get());
+  }
+  return intersects;
+}
+
+double PoiPolygonCache::getPolyToPointDistance(ConstWayPtr poly, ConstNodePtr point)
+{
+  if (!poly || !point)
+  {
+    return -1.0;
+  }
+
+  std::shared_ptr<geos::geom::LineString> polyGeom = _getLineString(poly);
+  std::shared_ptr<geos::geom::Geometry> pointGeom = _getGeometry(point);
+  double distance = -1.0;
+  if (polyGeom && pointGeom)
+  {
+    distance = polyGeom->distance(pointGeom.get());
+  }
+  return distance;
+}
+
+double PoiPolygonCache::getArea(ConstElementPtr element)
+{
+  LOG_VART(element.get());
+  if (!element)
+  {
+    return -1.0;
+  }
+
+  std::shared_ptr<geos::geom::Geometry> geom = _getGeometry(element);
+  LOG_VART(geom.get());
+  double area = -1.0;
+  if (geom)
+  {
+    area = geom->getArea();
+  }
+  LOG_VART(area);
+  return area;
+}
+
+}
Clone this wiki locally