diff --git a/hoot-core/src/main/cpp/hoot/core/conflate/poi-polygon/PoiPolygonMatchCreator.cpp b/hoot-core/src/main/cpp/hoot/core/conflate/poi-polygon/PoiPolygonMatchCreator.cpp
index 142db85..ca783cc 100644
--- a/hoot-core/src/main/cpp/hoot/core/conflate/poi-polygon/PoiPolygonMatchCreator.cpp
+++ b/hoot-core/src/main/cpp/hoot/core/conflate/poi-polygon/PoiPolygonMatchCreator.cpp
@@ -22,7 +22,7 @@
* This will properly maintain the copyright information. DigitalGlobe
* copyrights will be updated automatically.
*
- * @copyright Copyright (C) 2016, 2017, 2018, 2019 DigitalGlobe (http://www.digitalglobe.com/)
+ * @copyright Copyright (C) 2016, 2017, 2018, 2019, 2020 DigitalGlobe (http://www.digitalglobe.com/)
*/
#include "PoiPolygonMatchCreator.h"
@@ -36,6 +36,9 @@
#include <hoot/core/util/Factory.h>
#include <hoot/core/util/StringUtils.h>
+// Std
+#include <float.h>
+
namespace hoot
{
@@ -46,12 +49,12 @@ PoiPolygonMatchCreator::PoiPolygonMatchCreator()
}
MatchPtr PoiPolygonMatchCreator::createMatch(const ConstOsmMapPtr& map, ElementId eid1,
- ElementId eid2)
+ ElementId eid2)
{
if (!_infoCache)
{
- LOG_TRACE("Initializing info cache...");
- _infoCache.reset(new PoiPolygonCache(map));
+ LOG_DEBUG("Initializing info cache...");
+ _infoCache.reset(new PoiPolygonInfoCache(map));
}
std::shared_ptr<PoiPolygonMatch> result;
@@ -78,25 +81,59 @@ void PoiPolygonMatchCreator::createMatches(const ConstOsmMapPtr& map,
std::vector<ConstMatchPtr>& matches,
ConstMatchThresholdPtr threshold)
{
- LOG_INFO("Looking for matches with: " << className() << "...");
+ QElapsedTimer timer;
+ timer.start();
+
+ //poi.polygon.additional.search.distance
+ //poi.polygon.match.distance.threshold
+ QString searchRadiusStr;
+ const double additionalDistance = ConfigOptions().getPoiPolygonAdditionalSearchDistance();
+ if (additionalDistance <= 0)
+ {
+ searchRadiusStr = "within a feature dependent search radius";
+ }
+ else
+ {
+ searchRadiusStr =
+ "within a feature dependent search radius plus an additional distance of " +
+ QString::number(additionalDistance, 'g', 2) + " meters";
+ }
+ LOG_STATUS("Looking for matches with: " << className() << " " << searchRadiusStr << "...");
LOG_VARD(*threshold);
+ const int matchesSizeBefore = matches.size();
if (!_infoCache)
{
- LOG_TRACE("Initializing info cache...");
- _infoCache.reset(new PoiPolygonCache(map));
+ LOG_DEBUG("Initializing info cache...");
+ _infoCache.reset(new PoiPolygonInfoCache(map));
+ _infoCache->setConfiguration(conf());
}
PoiPolygonMatch::resetMatchDistanceInfo();
- QElapsedTimer timer;
- timer.start();
- PoiPolygonMatchVisitor v(map, matches, threshold, _getRf(), _infoCache, _filter);
- map->visitRo(v);
- LOG_INFO(
- "Found " << StringUtils::formatLargeNumber(v.getNumMatchCandidatesFound()) <<
- " POI to Polygon match candidates in: " << StringUtils::millisecondsToDhms(timer.elapsed()) <<
- ".");
+ PoiPolygonMatchVisitor matchVis(map, matches, threshold, _getRf(), _infoCache, _filter);
+ map->visitNodesRo(matchVis);
+ const int matchesSizeAfter = matches.size();
+
+ LOG_STATUS(
+ "Found " << StringUtils::formatLargeNumber(matchVis.getNumMatchCandidatesFound()) <<
+ " POI to Polygon match candidate features and " <<
+ StringUtils::formatLargeNumber(matchesSizeAfter - matchesSizeBefore) << " total matches in: " <<
+ StringUtils::millisecondsToDhms(timer.elapsed()) << ".");
+
+ // If we're only keeping matches/reviews with the closest distances between features, then let's
+ // weed out the ones that aren't as close to each other.
+ int numMatchesRemoved = 0;
+ if (ConfigOptions().getPoiPolygonKeepClosestMatchesOnly())
+ {
+ timer.restart();
+ const int startingMatchCount = matches.size();
+ numMatchesRemoved = _retainClosestDistanceMatchesOnly(matches, map);
+ LOG_INFO(
+ "Discarded " << StringUtils::formatLargeNumber(numMatchesRemoved) <<
+ " non-closest " << StringUtils::formatLargeNumber(startingMatchCount) <<
+ " / total matches in: " << StringUtils::millisecondsToDhms(timer.elapsed()) << ".");
+ }
if (conf().getBool(ConfigOptions::getPoiPolygonPrintMatchDistanceTruthKey()))
{
@@ -138,7 +175,359 @@ void PoiPolygonMatchCreator::createMatches(const ConstOsmMapPtr& map,
LOG_DEBUG(
"POI/Polygon review reductions: " <<
StringUtils::formatLargeNumber(PoiPolygonMatch::numReviewReductions));
+ if (ConfigOptions().getPoiPolygonKeepClosestMatchesOnly())
+ {
+ LOG_DEBUG(
+ "Number of matches removed due to option enabled to only keeping closest matches: " <<
+ StringUtils::formatLargeNumber(numMatchesRemoved));
+ }
_infoCache->printCacheInfo();
+ _infoCache->clear();
+}
+
+int PoiPolygonMatchCreator::_retainClosestDistanceMatchesOnly(
+ std::vector<ConstMatchPtr>& matches, const ConstOsmMapPtr& map)
+{
+ LOG_INFO("Discarding non-closest matches...");
+
+ const bool debug = false; // leave this false by default
+ const ElementId testElementId1 = ElementId(ElementType::Node, -1893344);
+ const ElementId testElementId2 = ElementId(ElementType::Way, -276936);
+
+ int numRemoved = 0;
+ // look for overlapping matches separately for POI and poly matches
+ numRemoved += _retainClosestDistanceMatchesOnlyByType(matches, map, true);
+ LOG_DEBUG("Removed " << StringUtils::formatLargeNumber(numRemoved) << " POI matches.");
+
+ if (debug)
+ {
+ const bool containsMatch = _containsMatch(testElementId1, testElementId2, matches);
+ QString containstStr = containsMatch ? "contains" : "does not contain";
+ LOG_DEBUG(
+ "Matches after POI match removal " << containstStr << " elements: " << testElementId1 <<
+ ", " << testElementId2);
+ int numMatchesContaining = _numMatchesContainingElement(testElementId1, matches);
+ LOG_DEBUG(numMatchesContaining << " contain " << testElementId1);
+ numMatchesContaining = _numMatchesContainingElement(testElementId2, matches);
+ LOG_DEBUG(numMatchesContaining << " contain " << testElementId2);
+ }
+
+ numRemoved += _retainClosestDistanceMatchesOnlyByType(matches, map, false);
+ LOG_DEBUG("Removed " << StringUtils::formatLargeNumber(numRemoved) << " poly matches.");
+
+ if (debug)
+ {
+ const bool containsMatch = _containsMatch(testElementId1, testElementId2, matches);
+ QString containstStr = containsMatch ? "contains" : "does not contain";
+ LOG_DEBUG(
+ "Matches after poly match removal " << containstStr << " elements: " << testElementId1 <<
+ ", " << testElementId2);
+ int numMatchesContaining = _numMatchesContainingElement(testElementId1, matches);
+ LOG_DEBUG(numMatchesContaining << " matches contain " << testElementId1);
+ numMatchesContaining = _numMatchesContainingElement(testElementId2, matches);
+ LOG_DEBUG(numMatchesContaining << " matches contain " << testElementId2);
+ }
+
+ return numRemoved;
+}
+
+bool PoiPolygonMatchCreator::_containsMatch(
+ const ElementId& elementId1, const ElementId& elementId2,
+ const std::vector<ConstMatchPtr>& matches) const
+{
+ // for debugging only
+ for (std::vector<ConstMatchPtr>::const_iterator matchItr = matches.begin();
+ matchItr != matches.end(); ++matchItr)
+ {
+ ConstMatchPtr match = *matchItr;
+ if (match->getType() != MatchType::Miss)
+ {
+ if (match->getMatchPairs().size() != 1)
+ {
+ LOG_VART(match->getMatchPairs().size());
+ }
+ assert(match->getMatchPairs().size() == 1);
+ std::pair<ElementId, ElementId> matchElementIds = *(match->getMatchPairs()).begin();
+ if ((matchElementIds.first == elementId1 && matchElementIds.second == elementId2) ||
+ (matchElementIds.first == elementId2 && matchElementIds.second == elementId1))
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+int PoiPolygonMatchCreator::_numMatchesContainingElement(
+ const ElementId& elementId, const std::vector<ConstMatchPtr>& matches) const
+{
+ // for debugging only
+ int numMatchesContainingId = 0;
+ for (std::vector<ConstMatchPtr>::const_iterator matchItr = matches.begin();
+ matchItr != matches.end(); ++matchItr)
+ {
+ ConstMatchPtr match = *matchItr;
+ if (match->getType() != MatchType::Miss)
+ {
+ if (match->getMatchPairs().size() != 1)
+ {
+ LOG_VART(match->getMatchPairs().size());
+ }
+ assert(match->getMatchPairs().size() == 1);
+ std::pair<ElementId, ElementId> matchElementIds = *(match->getMatchPairs()).begin();
+ if (matchElementIds.first == elementId || matchElementIds.second == elementId)
+ {
+ numMatchesContainingId++;
+ } }
+ }
+ return numMatchesContainingId;
+}
+
+QMultiMap<ElementId, ConstMatchPtr> PoiPolygonMatchCreator::_indexMatchesById(
+ const std::vector<ConstMatchPtr>& matches, const QString& matchTypeStr)
+{
+ LOG_DEBUG(
+ "Indexing " << StringUtils::formatLargeNumber(matches.size()) << " " << matchTypeStr <<
+ " matches...");
+ const bool processPois = matchTypeStr == "POI";
+ QMultiMap<ElementId, ConstMatchPtr> matchesById;
+
+ for (std::vector<ConstMatchPtr>::const_iterator matchItr = matches.begin();
+ matchItr != matches.end(); ++matchItr)
+ {
+ ConstMatchPtr match = *matchItr;
+ //LOG_VART(match);
+ if (match->getType() != MatchType::Miss)
+ {
+ if (match->getMatchPairs().size() != 1)
+ {
+ LOG_VART(match->getMatchPairs().size());
+ }
+ assert(match->getMatchPairs().size() == 1);
+ std::pair<ElementId, ElementId> matchElementIds = *(match->getMatchPairs()).begin();
+ if (processPois)
+ {
+ if (matchElementIds.first.getType() == ElementType::Node)
+ {
+ matchesById.insertMulti(matchElementIds.first, match);
+ }
+ else
+ {
+ matchesById.insertMulti(matchElementIds.second, match);
+ }
+ }
+ else
+ {
+ if (matchElementIds.first.getType() == ElementType::Way ||
+ matchElementIds.first.getType() == ElementType::Relation)
+ {
+ matchesById.insertMulti(matchElementIds.first, match);
+ }
+ else
+ {
+ matchesById.insertMulti(matchElementIds.second, match);
+ }
+ }
+ }
+ }
+
+ return matchesById;
+}
+
+QMap<ElementId, QList<ConstMatchPtr>> PoiPolygonMatchCreator::_getOverlappingMatches(
+ const QMultiMap<ElementId, ConstMatchPtr>& matchesById, const QString& matchTypeStr)
+{
+ LOG_DEBUG(
+ "Finding overlapping " << matchTypeStr << " matches out of " <<
+ StringUtils::formatLargeNumber(matchesById.size()) << " indexed matches...");
+ QMap<ElementId, QList<ConstMatchPtr>> overlappingMatches;
+
+ const QList<ElementId> ids = matchesById.keys();
+ for (QList<ElementId>::const_iterator idItr = ids.begin(); idItr != ids.end(); ++idItr)
+ {
+ //LOG_VART(*idItr);
+ const QList<ConstMatchPtr> matches = matchesById.values(*idItr);
+ //LOG_VART(matches.size());
+ if (matches.size() > 1)
+ {
+ //LOG_TRACE("Found overlapping matches: " << matches);
+ LOG_TRACE("Found overlapping matches of size: " << matches.size());
+ overlappingMatches[*idItr] = matches;
+ }
+ }
+
+ return overlappingMatches;
+}
+
+std::vector<ConstMatchPtr> PoiPolygonMatchCreator::_filterOutNonClosestMatches(
+ const QMap<ElementId, QList<ConstMatchPtr>>& overlappingMatches,
+ const std::vector<ConstMatchPtr>& allMatches, const ConstOsmMapPtr& map,
+ const QString& matchTypeStr)
+{
+ LOG_DEBUG(
+ "Filtering out non-closest " << matchTypeStr << " matches out of " <<
+ StringUtils::formatLargeNumber(overlappingMatches.size()) << " overlapping matches...");
+ QList<ConstMatchPtr> matchesToRemove;
+ for (QMap<ElementId, QList<ConstMatchPtr>>::const_iterator matchesItr = overlappingMatches.begin();
+ matchesItr != overlappingMatches.end(); ++matchesItr)
+ {
+ LOG_TRACE("****************************************");
+ ElementId sharedElementId = matchesItr.key();
+ LOG_VART(sharedElementId);
+ ConstElementPtr sharedElement = map->getElement(sharedElementId);
+
+ ConstMatchPtr closestMatch;
+ double smallestDistance = DBL_MAX;
+ QList<ConstMatchPtr> matchesWithSharedId = matchesItr.value();
+ for (QList<ConstMatchPtr>::const_iterator matchesItr2 = matchesWithSharedId.begin();
+ matchesItr2 != matchesWithSharedId.end(); ++matchesItr2)
+ {
+ ConstMatchPtr overlappingMatch = *matchesItr2;
+ LOG_VART(overlappingMatch->getMatchName());
+ LOG_VART(overlappingMatch);
+
+ if (overlappingMatch->getMatchName() == PoiPolygonMatch::getPoiPolygonMatchName())
+ {
+ std::pair<ElementId, ElementId> matchElementIds =
+ *(overlappingMatch->getMatchPairs()).begin();
+ ElementId comparisonElementId;
+ if (matchElementIds.first == sharedElementId)
+ {
+ comparisonElementId = matchElementIds.second;
+ }
+ else
+ {
+ comparisonElementId = matchElementIds.first;
+ }
+ LOG_VART(comparisonElementId);
+
+ int pointCount = 0;
+ if (sharedElementId.getType() == ElementType::Node)
+ {
+ pointCount++;
+ }
+ if (comparisonElementId.getType() == ElementType::Node)
+ {
+ pointCount++;
+ }
+ if (pointCount != 1)
+ {
+ throw IllegalArgumentException(
+ "POI/Polygon match does not contain exactly one POI: " + overlappingMatch->toString());
+ }
+
+ int polyCount = 0;
+ if (sharedElementId.getType() == ElementType::Way ||
+ sharedElementId.getType() == ElementType::Relation)
+ {
+ polyCount++;
+ }
+ if (comparisonElementId.getType() == ElementType::Way ||
+ comparisonElementId.getType() == ElementType::Relation)
+ {
+ polyCount++;
+ }
+ if (polyCount != 1)
+ {
+ throw IllegalArgumentException(
+ "POI/Polygon match does not contain exactly one Polygon: " +
+ overlappingMatch->toString());
+ }
+
+ ConstElementPtr comparisonElement = map->getElement(comparisonElementId);
+
+ ConstNodePtr point;
+ bool sharedWasPoint = false;
+ if (sharedElementId.getType() == ElementType::Node)
+ {
+ point = std::dynamic_pointer_cast<const Node>(sharedElement);
+ sharedWasPoint = true;
+ }
+ else
+ {
+ point = std::dynamic_pointer_cast<const Node>(comparisonElement);
+ }
+
+ double distance = DBL_MAX;
+ if (sharedWasPoint)
+ {
+ distance = _infoCache->getDistance(comparisonElement, point);
+ }
+ else
+ {
+ distance = _infoCache->getDistance(sharedElement, point);
+ }
+ LOG_VART(distance);
+ if (distance != -1.0 && distance < smallestDistance)
+ {
+ smallestDistance = distance;
+ LOG_TRACE(
+ "smallest distance: " << smallestDistance << ", " << sharedElementId << ";" <<
+ comparisonElementId);
+ closestMatch = overlappingMatch;
+ }
+ }
+ }
+
+ if (closestMatch)
+ {
+ LOG_TRACE("Keeping closest match: " << closestMatch << "...");
+ for (QList<ConstMatchPtr>::const_iterator matchItr = matchesWithSharedId.begin();
+ matchItr != matchesWithSharedId.end(); ++matchItr)
+ {
+ ConstMatchPtr match = *matchItr;
+ if (match != closestMatch)
+ {
+ matchesToRemove.append(match);
+ }
+ }
+ }
+
+ LOG_VART(matchesToRemove.size());
+ }
+ LOG_DEBUG("Removing " << StringUtils::formatLargeNumber(matchesToRemove.size()) << " matches.");
+
+ std::vector<ConstMatchPtr> matchesToKeep;
+ for (std::vector<ConstMatchPtr>::const_iterator matchItr = allMatches.begin();
+ matchItr != allMatches.end(); ++matchItr)
+ {
+ ConstMatchPtr match = *matchItr;
+ if (!matchesToRemove.contains(match))
+ {
+ matchesToKeep.push_back(match);
+ }
+ }
+ LOG_VART(matchesToKeep.size());
+ return matchesToKeep;
+}
+
+int PoiPolygonMatchCreator::_retainClosestDistanceMatchesOnlyByType(
+ std::vector<ConstMatchPtr>& matches, const ConstOsmMapPtr& map, const bool processPois)
+{
+ QString matchTypeStr = "Polygon";
+ if (processPois)
+ {
+ matchTypeStr = "POI";
+ }
+ const int startingMatchesSize = matches.size();
+ LOG_DEBUG(
+ "Discarding non-closest " << matchTypeStr << " matches (out of " <<
+ StringUtils::formatLargeNumber(matches.size()) << " remaining)...");
+
+ // index matches by involved element ID
+ const QMultiMap<ElementId, ConstMatchPtr> matchesById = _indexMatchesById(matches, matchTypeStr);
+
+ // find matches sharing the same element ID
+ const QMap<ElementId, QList<ConstMatchPtr>> overlappingMatches =
+ _getOverlappingMatches(matchesById, matchTypeStr);
+
+ // for each overlapping match, find the match in the group with the closest distance between
+ // features and throw out the rest
+ matches = _filterOutNonClosestMatches(overlappingMatches, matches, map, matchTypeStr);
+
+ const int diff = startingMatchesSize - matches.size();
+ LOG_VARD(diff);
+ return diff;
}
std::vector<CreatorDescription> PoiPolygonMatchCreator::getAllCreators() const