Skip to content

v0.2.52..v0.2.53 changeset PoiPolygonInfoCache.cpp

Garret Voltz edited this page Feb 12, 2020 · 1 revision
diff --git a/hoot-core/src/main/cpp/hoot/core/conflate/poi-polygon/PoiPolygonInfoCache.cpp b/hoot-core/src/main/cpp/hoot/core/conflate/poi-polygon/PoiPolygonInfoCache.cpp
new file mode 100644
index 0000000..0e79174
--- /dev/null
+++ b/hoot-core/src/main/cpp/hoot/core/conflate/poi-polygon/PoiPolygonInfoCache.cpp
@@ -0,0 +1,550 @@
+/*
+ * 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, 2020 DigitalGlobe (http://www.digitalglobe.com/)
+ */
+#include "PoiPolygonInfoCache.h"
+
+// geos
+#include <geos/util/TopologyException.h>
+
+// hoot
+#include <hoot/core/conflate/poi-polygon/PoiPolygonSchema.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>
+#include <hoot/core/algorithms/extractors/AddressScoreExtractor.h>
+
+// Std
+#include <float.h>
+
+// Qt
+#include <QStringBuilder>
+
+namespace hoot
+{
+
+PoiPolygonInfoCache::PoiPolygonInfoCache(const ConstOsmMapPtr& map) :
+_map(map)
+{
+}
+
+void PoiPolygonInfoCache::setConfiguration(const Settings& conf)
+{
+  _addressParser.setConfiguration(conf);
+}
+
+void PoiPolygonInfoCache::clear()
+{
+  _numCacheHitsByCacheType.clear();
+  _numCacheEntriesByCacheType.clear();
+
+  _isTypeCache.clear();
+  _hasCriterionCache.clear();
+  _criterionCache.clear();
+  _geometryCache.clear();
+  _hasMoreThanOneTypeCache.clear();
+  _numAddressesCache.clear();
+  _elementIntersectsCache.clear();
+  _numAddressesCache.clear();
+}
+
+void PoiPolygonInfoCache::printCacheInfo()
+{
+  // Add one for the address cache on AddressScoreExtractor.
+  LOG_VARD(_numCacheHitsByCacheType.size());
+  LOG_DEBUG("POI/Polygon caches used: " <<  (_numCacheHitsByCacheType.size() + 1));
+  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);
+  }
+  const QString line =
+    QString("%1:\t%2 hits     entries: %3")
+      .arg("address")
+      .arg(StringUtils::formatLargeNumber(AddressScoreExtractor::getNumAddressCacheHits()))
+      .arg(StringUtils::formatLargeNumber(AddressScoreExtractor::getAddressCacheSize()));
+  LOG_DEBUG(line);
+}
+
+void PoiPolygonInfoCache::_incrementCacheHitCount(const QString& cacheTypeKey)
+{
+  if (!_numCacheHitsByCacheType.contains(cacheTypeKey))
+  {
+    _numCacheHitsByCacheType[cacheTypeKey] = 1;
+  }
+  else
+  {
+    _numCacheHitsByCacheType[cacheTypeKey] = _numCacheHitsByCacheType[cacheTypeKey] + 1;
+  }
+}
+
+void PoiPolygonInfoCache::_incrementCacheSizeCount(const QString& cacheTypeKey)
+{
+  if (!_numCacheEntriesByCacheType.contains(cacheTypeKey))
+  {
+    _numCacheEntriesByCacheType[cacheTypeKey] = 1;
+  }
+  else
+  {
+    _numCacheEntriesByCacheType[cacheTypeKey] = _numCacheEntriesByCacheType[cacheTypeKey] + 1;
+  }
+}
+
+/////////////////NOT POI/POLY SPECIFIC///////////////////
+
+std::shared_ptr<geos::geom::Geometry> PoiPolygonInfoCache::_getGeometry(
+  const ConstElementPtr& element)
+{
+  if (!element)
+  {
+    throw IllegalArgumentException("The input element is null.");
+  }
+
+  QHash<ElementId, std::shared_ptr<geos::geom::Geometry>>::const_iterator itr =
+    _geometryCache.find(element->getElementId());
+  if (itr != _geometryCache.end())
+  {
+    _incrementCacheHitCount("geometry");
+    return itr.value();
+  }
+
+  std::shared_ptr<geos::geom::Geometry> newGeom;
+  QString errorMsg =
+    "Feature passed to PoiPolygonInfoCache 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++;
+    }
+  }
+  if (newGeom.get() &&
+      QString::fromStdString(newGeom->toString()).toUpper().contains("EMPTY"))
+  {
+    if (_badGeomCount <= Log::getWarnMessageLimit())
+    {
+      LOG_TRACE("Invalid element passed: " << newGeom->toString());
+      _badGeomCount++;
+    }
+    newGeom.reset();
+  }
+  _geometryCache[element->getElementId()] = newGeom;
+  _incrementCacheSizeCount("geometry");
+  return newGeom;
+}
+
+bool PoiPolygonInfoCache::elementContains(const ConstElementPtr& containingElement,
+                                          const ConstElementPtr& containedElement)
+{
+  if (!containingElement || !containedElement)
+  {
+    throw IllegalArgumentException("One of the input elements is null.");
+  }
+  if (containingElement->getElementType() != ElementType::Way &&
+      containingElement->getElementType() != ElementType::Relation)
+  {
+    throw IllegalArgumentException("One of the input elements is of the wrong type.");
+  }
+  LOG_VART(containedElement->getElementId());
+  LOG_VART(containingElement->getElementId());
+
+  std::shared_ptr<geos::geom::Geometry> containingElementGeom = _getGeometry(containingElement);
+  std::shared_ptr<geos::geom::Geometry> containedElementGeom = _getGeometry(containedElement);
+  bool contains = false;
+  if (containingElementGeom && containedElementGeom)
+  {
+    contains = containingElementGeom->contains(containedElementGeom.get());
+    LOG_TRACE(
+      "Calculated contains: " << contains << " for containing element: " <<
+      containingElement->getElementId() <<
+      " and contained element: " << containedElement->getElementId() << ".");
+  }
+  else
+  {
+    LOG_TRACE(
+      "Unable to calculate contains for containing element: " <<
+      containingElement->getElementId() << " and contained element: " <<
+      containedElement->getElementId() << ".");
+  }
+  return contains;
+}
+
+int PoiPolygonInfoCache::numAddresses(const ConstElementPtr& element)
+{
+  if (!element)
+  {
+    throw IllegalArgumentException("The input element is null.");
+  }
+
+  QHash<ElementId, int>::const_iterator itr = _numAddressesCache.find(element->getElementId());
+  if (itr != _numAddressesCache.end())
+  {
+    _incrementCacheHitCount("numAddresses");
+    return itr.value();
+  }
+
+  const int numAddresses = _addressParser.numAddressesRecursive(element, *_map);
+  _numAddressesCache[element->getElementId()] = numAddresses;
+  _incrementCacheSizeCount("numAddresses");
+  return numAddresses;
+}
+
+bool PoiPolygonInfoCache::containsMember(const ConstElementPtr& parent, const ElementId& memberId)
+{
+  if (!parent ||
+      (parent->getElementType() != ElementType::Way &&
+       parent->getElementType() != ElementType::Relation))
+  {
+    throw IllegalArgumentException("The parent element is null or of the wrong element type.");
+  }
+  if (parent->getElementType() != ElementType::Way && memberId.getType() != ElementType::Node)
+  {
+    throw IllegalArgumentException("The inputs are of the wrong element type.");
+  }
+  if (parent->getElementType() != ElementType::Relation &&
+      memberId.getType() == ElementType::Unknown)
+  {
+    throw IllegalArgumentException("The inputs are of the wrong element type.");
+  }
+
+  bool containsMember = false;
+  if (parent->getElementType() == ElementType::Way)
+  {
+    containsMember =
+      (std::dynamic_pointer_cast<const Way>(parent))->containsNodeId(memberId.getId());
+  }
+  else
+  {
+    containsMember =
+      (std::dynamic_pointer_cast<const Relation>(parent))->contains(memberId);
+  }
+  return containsMember;
+}
+
+bool PoiPolygonInfoCache::elementsIntersect(
+  const ConstElementPtr& element1, const ConstElementPtr& element2)
+{
+  if (!element1 || !element2)
+  {
+    throw IllegalArgumentException("One of the input elements is null.");
+  }
+
+  const QString key1 =
+    element1->getElementId().toString() % ";" % element2->getElementId().toString();
+  const QString key2 =
+    element2->getElementId().toString() % ";" % element1->getElementId().toString();
+  QHash<QString, bool>::const_iterator itr = _elementIntersectsCache.find(key1);
+  if (itr != _elementIntersectsCache.end())
+  {
+    _incrementCacheHitCount("intersects");
+    const bool intersects = itr.value();
+    LOG_TRACE("Found cached intersects: " << intersects << " for key: " << key1);
+    return intersects;
+  }
+  itr = _elementIntersectsCache.find(key2);
+  if (itr != _elementIntersectsCache.end())
+  {
+    _incrementCacheHitCount("intersects");
+    const bool intersects = itr.value();
+    LOG_TRACE("Found cached intersects: " << intersects << " for key: " << key2);
+    return intersects;
+  }
+
+  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());
+  }
+  else
+  {
+    LOG_TRACE(
+      "Unable to calculate intersects for: " << element1->getElementId() <<
+      " and: " << element2->getElementId() << ".");
+  }
+  _elementIntersectsCache[key1] = intersects;
+  _incrementCacheSizeCount("intersects");
+  return intersects;
+}
+
+double PoiPolygonInfoCache::getDistance(
+  const ConstElementPtr& element1, const ConstElementPtr& element2)
+{
+  if (!element1 || !element2)
+  {
+    throw IllegalArgumentException("One of the input elements is null.");
+  }
+  LOG_VART(element1->getElementId());
+  LOG_VART(element2->getElementId());
+
+  double distance = -1.0;
+
+  std::shared_ptr<geos::geom::Geometry> element1Geom = _getGeometry(element1);
+  std::shared_ptr<geos::geom::Geometry> element2Geom = _getGeometry(element2);
+  if (element1Geom && element2Geom)
+  {
+    distance = element1Geom->distance(element2Geom.get());
+    LOG_TRACE(
+      "Calculated distance: " << distance << " for: " << element1->getElementId() <<
+      " and: " << element2->getElementId() << ".");
+  }
+  else
+  {
+    LOG_TRACE(
+      "Unable to calculate distance for: " << element1->getElementId() <<
+      " and: " << element2->getElementId() << ".");
+  }
+
+  return distance;
+}
+
+double PoiPolygonInfoCache::getArea(const ConstElementPtr& element)
+{
+  if (!element)
+  {
+    throw IllegalArgumentException("The input element is null.");
+  }
+
+  std::shared_ptr<geos::geom::Geometry> geom = _getGeometry(element);
+  double area = -1.0;
+  if (geom)
+  {
+    area = geom->getArea();
+  }
+  else
+  {
+    LOG_TRACE("Unable to calculate area for: " << element->getElementId() << ".");
+  }
+  return area;
+}
+
+bool PoiPolygonInfoCache::hasCriterion(const ConstElementPtr& element,
+                                       const QString& criterionClassName)
+{
+  if (!element || criterionClassName.trimmed().isEmpty())
+  {
+    throw IllegalArgumentException(
+      "The input element is null or the criterion class name is empty.");
+  }
+
+  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();
+  }
+
+  ElementCriterionPtr crit = _getCrit(criterionClassName);
+  const bool hasCrit = crit->isSatisfied(element);
+  _hasCriterionCache[key] = hasCrit;
+  _incrementCacheSizeCount("hasCrit");
+  return hasCrit;
+}
+
+ElementCriterionPtr PoiPolygonInfoCache::_getCrit(const QString& criterionClassName)
+{
+  if (criterionClassName.trimmed().isEmpty())
+  {
+    throw IllegalArgumentException("The criterion class name is empty.");
+  }
+
+  QHash<QString, ElementCriterionPtr>::const_iterator itr =
+    _criterionCache.find(criterionClassName);
+  if (itr != _criterionCache.end())
+  {
+    return itr.value();
+  }
+
+  ElementCriterionPtr crit =
+    ElementCriterionPtr(
+      Factory::getInstance().constructObject<ElementCriterion>(criterionClassName));
+  if (!crit)
+  {
+    throw IllegalArgumentException(
+      "Invalid criterion passed to PoiPolygonInfoCache::hasCriterion: " + criterionClassName);
+  }
+  _criterionCache[criterionClassName] = crit;
+  return crit;
+}
+
+/////////////////POI/POLY SPECIFIC/////////////////
+
+bool PoiPolygonInfoCache::isType(const ConstElementPtr& element, const PoiPolygonSchemaType& type)
+{
+  if (!element)
+  {
+    throw IllegalArgumentException("The input element is null.");
+  }
+
+  const QString key = element->getElementId().toString() % ";" % type.toString().toLower();
+  QHash<QString, bool>::const_iterator itr = _isTypeCache.find(key);
+  if (itr != _isTypeCache.end())
+  {
+    _incrementCacheHitCount("isType");
+    return itr.value();
+  }
+
+  // A re-design is needed to make this a little less maintenance prone...possiby add custom
+  // schema lookup files for poi/poly?
+  bool isType = false;
+  switch (type.getEnum())
+  {
+    case PoiPolygonSchemaType::Natural:
+      isType = PoiPolygonSchema::isNatural(element);
+      break;
+    case PoiPolygonSchemaType::Park:
+      isType = PoiPolygonSchema::isPark(element);
+      break;
+    case PoiPolygonSchemaType::Parking:
+      isType = PoiPolygonSchema::isParking(element);
+      break;
+    case PoiPolygonSchemaType::Parkish:
+      isType = PoiPolygonSchema::isParkish(element);
+      break;
+    case PoiPolygonSchemaType::Playground:
+      isType = PoiPolygonSchema::isPlayground(element);
+      break;
+    case PoiPolygonSchemaType::Restaurant:
+      isType = PoiPolygonSchema::isRestaurant(element);
+      break;
+    case PoiPolygonSchemaType::Religion:
+      isType = PoiPolygonSchema::isReligion(element);
+      break;
+    case PoiPolygonSchemaType::Restroom:
+      isType = PoiPolygonSchema::isRestroom(element);
+      break;
+    case PoiPolygonSchemaType::School:
+      isType = PoiPolygonSchema::isSchool(element);
+      break;
+    case PoiPolygonSchemaType::SpecificSchool:
+      isType = PoiPolygonSchema::isSpecificSchool(element);
+      break;
+    case PoiPolygonSchemaType::Sport:
+      isType = PoiPolygonSchema::isSport(element);
+      break;
+    default:
+      throw IllegalArgumentException("Unsupported POI/Polygon schema type.");
+  }
+
+  _isTypeCache[key] = isType;
+  _incrementCacheSizeCount("isType");
+
+  return isType;
+}
+
+bool PoiPolygonInfoCache::hasMoreThanOneType(const ConstElementPtr& element)
+{
+  if (!element)
+  {
+    throw IllegalArgumentException("The input element is null.");
+  }
+
+  QHash<ElementId, bool>::const_iterator itr =
+    _hasMoreThanOneTypeCache.find(element->getElementId());
+  if (itr != _hasMoreThanOneTypeCache.end())
+  {
+    _incrementCacheHitCount("hasMoreThanOneType");
+    return itr.value();
+  }
+
+  const bool hasMoreThanOneType = PoiPolygonSchema::hasMoreThanOneType(element);
+  _hasMoreThanOneTypeCache[element->getElementId()] = hasMoreThanOneType;
+  _incrementCacheSizeCount("hasMoreThanOneType");
+  return hasMoreThanOneType;
+}
+
+bool PoiPolygonInfoCache::hasRelatedType(const ConstElementPtr& element)
+{
+  if (!element)
+  {
+    throw IllegalArgumentException("The input element is null.");
+  }
+
+  const bool hasRelatedType = PoiPolygonSchema::hasRelatedType(element);
+  return hasRelatedType;
+}
+
+double PoiPolygonInfoCache::getReviewDistance(const ConstElementPtr& element,
+                                          const Tags& polyTags,
+                                          const double reviewDistanceThresholdDefault)
+{
+  QHash<ElementId, double>::const_iterator itr = _reviewDistanceCache.find(element->getElementId());
+  if (itr != _reviewDistanceCache.end())
+  {
+    _incrementCacheHitCount("reviewDistance");
+    double distance = itr.value();
+    LOG_TRACE("Found review distance: " << distance << " for: " << element->getElementId());
+    return distance;
+  }
+  else
+  {
+    double distance;
+    const Tags& tags = element->getTags();
+    //these distances could be moved to a config
+    if (tags.get("leisure") == "park")
+    {
+      distance = 25.0;
+    }
+    else if ((tags.get("station").toLower() == "light_rail" ||
+              tags.get("railway").toLower() == "platform") &&
+             (polyTags.get("subway").toLower() == "yes" ||
+              polyTags.get("tunnel").toLower() == "yes"))
+    {
+      distance = 150.0;
+    }
+    else
+    {
+      distance = reviewDistanceThresholdDefault;
+    }
+    _reviewDistanceCache[element->getElementId()] = distance;
+    _incrementCacheSizeCount("reviewDistance");
+    return distance;
+  }
+}
+
+}
Clone this wiki locally