Skip to content

v0.2.50..v0.2.51 changeset BuildingMerger.cpp

Garret Voltz edited this page Jan 15, 2020 · 1 revision
diff --git a/hoot-core/src/main/cpp/hoot/core/conflate/polygon/BuildingMerger.cpp b/hoot-core/src/main/cpp/hoot/core/conflate/polygon/BuildingMerger.cpp
index 9758205..75963d6 100644
--- a/hoot-core/src/main/cpp/hoot/core/conflate/polygon/BuildingMerger.cpp
+++ b/hoot-core/src/main/cpp/hoot/core/conflate/polygon/BuildingMerger.cpp
@@ -52,6 +52,7 @@
 #include <hoot/core/visitors/FilteredVisitor.h>
 #include <hoot/core/visitors/UniqueElementIdVisitor.h>
 #include <hoot/core/visitors/WorstCircularErrorVisitor.h>
+#include <hoot/core/algorithms/extractors/IntersectionOverUnionExtractor.h>
 
 using namespace std;
 
@@ -111,7 +112,9 @@ _pairs(pairs),
 _keepMoreComplexGeometryWhenAutoMerging(
   ConfigOptions().getBuildingKeepMoreComplexGeometryWhenAutoMerging()),
 _mergeManyToManyMatches(ConfigOptions().getBuildingMergeManyToManyMatches()),
-_manyToManyMatch(false)
+_manyToManyMatch(false),
+_useChangedReview(ConfigOptions().getBuildingChangedReview()),
+_changedReviewIouThreshold(ConfigOptions().getBuildingChangedReviewIouThreshold())
 {
   LOG_VART(_pairs);
 }
@@ -121,12 +124,10 @@ void BuildingMerger::apply(const OsmMapPtr& map, vector<pair<ElementId, ElementI
   set<ElementId> firstPairs;
   set<ElementId> secondPairs;
   set<ElementId> combined;
-
-  bool preserveBuildingId = false;
-
   LOG_VART(_pairs);
+  _markedReviewText = "";
 
-  //check if it is many to many
+  // check to see if it is many to many
   for (set<pair<ElementId, ElementId>>::const_iterator sit = _pairs.begin(); sit != _pairs.end();
        ++sit)
   {
@@ -141,211 +142,265 @@ void BuildingMerger::apply(const OsmMapPtr& map, vector<pair<ElementId, ElementI
   LOG_VART(_mergeManyToManyMatches);
 
   ReviewMarker reviewMarker;
+
   if (_manyToManyMatch && !_mergeManyToManyMatches)
   {
-    const QString note =
+    // If the corresponding auto merge config option is not enabled, then auto review this many to
+    // many match.
+    _markedReviewText =
       "Merging multiple buildings from each data source is error prone and requires a human eye.";
-    reviewMarker.mark(map, combined, note, "Building", 1);
+    reviewMarker.mark(map, combined, _markedReviewText, "Building", 1.0);
+    return;
   }
-  else
+
+  ElementPtr e1 = _buildBuilding(map, true);
+  if (e1.get())
+  {
+    LOG_TRACE("BuildingMerger: built building e1\n" << OsmUtils::getElementDetailString(e1, map));
+  }
+  ElementPtr e2 = _buildBuilding(map, false);
+  if (e2.get())
+  {
+    LOG_TRACE("BuildingMerger: built building e2\n" << OsmUtils::getElementDetailString(e2, map));
+  }
+
+  LOG_VART(_keepMoreComplexGeometryWhenAutoMerging);
+  LOG_VART(_useChangedReview);
+
+  double iou = 1.0;
+  if (_useChangedReview)
+  {
+    iou = IntersectionOverUnionExtractor().extract(*map, e1, e2);
+  }
+
+  ElementPtr keeper;
+  ElementPtr scrap;
+
+  if (_useChangedReview &&
+      // doubt iou would ever be <= 0 if these buildings matched in the first place but adding the
+      // check anyway
+      iou > 0.0 && iou < _changedReviewIouThreshold)
+  {
+    // If the corresponding config option is enabled, auto review this "changed" building when the
+    // IoU score falls below the specified threshold.
+    _markedReviewText =
+      "Identified as changed with an IoU score of: " + QString::number(iou) +
+      ", which is less than the specified threshold of: " +
+      QString::number(_changedReviewIouThreshold);
+    reviewMarker.mark(map, combined, _markedReviewText, "Building", 1.0);
+    return;
+  }
+
+  bool preserveBuildingId = false;
+
+  if (_keepMoreComplexGeometryWhenAutoMerging)
   {
-    ElementPtr e1 = _buildBuilding(map, true);
-    if (e1.get())
+    // keep the most complex building
+
+    const ElementId moreComplexBuildingId = _getIdOfMoreComplexBuilding(e1, e2, map);
+    if (moreComplexBuildingId.isNull())
     {
-      OsmUtils::logElementDetail(e1, map, Log::Trace, "BuildingMerger: built building e1");
+      // couldn't determine which one is more complex (one or both buildings were missing nodes)
+      return;
     }
-    ElementPtr e2 = _buildBuilding(map, false);
-    if (e2.get())
+    else if (e1->getElementId() == moreComplexBuildingId)
     {
-      OsmUtils::logElementDetail(e2, map, Log::Trace, "BuildingMerger: built building e2");
+      // ref is more complex
+      keeper = e1;
+      scrap = e2;
     }
-
-    ElementPtr keeper;
-    ElementPtr scrap;
-    LOG_VART(_keepMoreComplexGeometryWhenAutoMerging);
-    if (_keepMoreComplexGeometryWhenAutoMerging)
+    else
     {
-      // use node count as a surrogate for complexity of the geometry.
-      int nodeCount1 = 0;
-      if (e1.get())
-      {
-        LOG_VART(e1);
-        nodeCount1 =
-          (int)FilteredVisitor::getStat(
-            new NodeCriterion(), new ElementCountVisitor(), map, e1);
-      }
-      LOG_VART(nodeCount1);
+      // sec is more complex
+      keeper = e2;
+      scrap = e1;
+      //  Keep e2's geometry but keep e1's ID
+      preserveBuildingId = true;
+    }
+  }
+  else
+  {
+    // default merging strategy: keep the reference building
+    keeper = e1;
+    scrap = e2;
+    LOG_TRACE("Keeping the reference building geometry...");
+  }
 
-      int nodeCount2 = 0;
-      if (e2.get())
-      {
-        nodeCount2 =
-          (int)FilteredVisitor::getStat(
-            new NodeCriterion(), new ElementCountVisitor(), map, e2);
-      }
-      LOG_VART(nodeCount2);
+  keeper->setTags(_getMergedTags(e1, e2));
+  keeper->setStatus(Status::Conflated);
 
-      //This will happen if a way/relation building is passed in with missing nodes.
-      if (nodeCount1 == 0 || nodeCount2 == 0)
-      {
-        if (logWarnCount < Log::getWarnMessageLimit())
-        {
-          LOG_WARN("One or more of the buildings to merge are empty.  Skipping merge...");
-          if (e1.get())
-          {
-            LOG_VART(e1->getElementId());
-          }
-          else
-          {
-            LOG_TRACE("Building one null.");
-          }
-          if (e2.get())
-          {
-            LOG_VART(e2->getElementId());
-          }
-          else
-          {
-            LOG_TRACE("Building two null.");
-          }
-        }
-        else if (logWarnCount == Log::getWarnMessageLimit())
-        {
-          LOG_WARN(className() << ": " << Log::LOG_WARN_LIMIT_REACHED_MESSAGE);
-        }
-        logWarnCount++;
+  LOG_TRACE("BuildingMerger: keeper\n" << OsmUtils::getElementDetailString(keeper, map));
+  LOG_TRACE("BuildingMerger: scrap\n" << OsmUtils::getElementDetailString(scrap, map));
 
-        return;
-      }
+  // Check to see if we are removing a multipoly building relation.  If so, its multipolygon
+  // relation members, need to be removed as well.
+  const QSet<ElementId> multiPolyMemberIds = _getMultiPolyMemberIds(scrap);
 
-      if (nodeCount1 == nodeCount2)
-      {
-        keeper = e1;
-        scrap = e2;
-        LOG_TRACE(
-          "Buildings have equally complex geometries.  Keeping the first building geometry: " <<
-          keeper << "; scrap: " << scrap->getElementId() << "...");
-      }
-      else
-      {
-        if (nodeCount1 > nodeCount2)
-        {
-          keeper = e1;
-          scrap = e2;
-        }
-        else
-        {
-          keeper = e2;
-          scrap = e1;
-          //  Keep e2's geometry but keep e1's ID
-          preserveBuildingId = true;
-        }
-        LOG_TRACE(
-          "Keeping the more complex building geometry: " << keeper << "; scrap: " <<
-          scrap->getElementId() << "...");
-      }
+  // Here is where we are able to reuse node IDs between buildings
+  ReuseNodeIdsOnWayOp(scrap->getElementId(), keeper->getElementId()).apply(map);
+  // Replace the scrap with the keeper in any parents
+  ReplaceElementOp(scrap->getElementId(), keeper->getElementId()).apply(map);
+  // Swap the IDs of the two elements if keeper isn't UNKNOWN1
+  if (preserveBuildingId)
+  {
+    ElementId oldKeeperId = keeper->getElementId();
+    ElementId oldScrapId = scrap->getElementId();
+    IdSwapOp swapOp(oldScrapId, oldKeeperId);
+    swapOp.apply(map);
+    // Now swap the pointers so that the wrong one isn't deleted
+    if (swapOp.getNumAffected() > 0)
+    {
+      scrap = map->getElement(oldKeeperId);
+      keeper = map->getElement(oldScrapId);
     }
-    else
+  }
+
+  // remove the scrap element from the map
+  DeletableBuildingCriterion crit;
+  RecursiveElementRemover(scrap->getElementId(), &crit).apply(map);
+  scrap->getTags().clear();
+
+  // delete any pre-existing multipoly members
+  LOG_TRACE("Removing multi-poly members: " << multiPolyMemberIds);
+  for (QSet<ElementId>::const_iterator it = multiPolyMemberIds.begin();
+       it != multiPolyMemberIds.end(); ++it)
+  {
+    RecursiveElementRemover(*it).apply(map);
+  }
+
+  set<pair<ElementId, ElementId>> replacedSet;
+  for (set<pair<ElementId, ElementId>>::const_iterator it = _pairs.begin(); it != _pairs.end();
+       ++it)
+  {
+    // if we replaced the second group of buildings
+    if (it->second != keeper->getElementId())
     {
-      keeper = e1;
-      scrap = e2;
-      LOG_TRACE(
-        "Keeping the first building geometry: " << keeper->getElementId() << "; scrap: " <<
-        scrap->getElementId() << "...");
+      replacedSet.insert(pair<ElementId, ElementId>(it->second, keeper->getElementId()));
     }
+    if (it->first != keeper->getElementId())
+    {
+      replacedSet.insert(pair<ElementId, ElementId>(it->first, keeper->getElementId()));
+    }
+  }
+  replaced.insert(replaced.end(), replacedSet.begin(), replacedSet.end());
+}
+
+Tags BuildingMerger::_getMergedTags(const ElementPtr& e1, const ElementPtr& e2)
+{
+  // TODO: need to explain how this tag merging differs from that done in buildBuilding and
+  // combineConstituentBuildingsIntoRelation
+  Tags mergedTags;
+  LOG_TRACE("e1 tags before merging and after built building tag merge: " << e1->getTags());
+  LOG_TRACE("e2 tags before merging and after built building tag merge: " << e2->getTags());
+  if (_manyToManyMatch && _mergeManyToManyMatches)
+  {
+    // preserve type tags
+    mergedTags = PreserveTypesTagMerger().mergeTags(e1->getTags(), e2->getTags(), ElementType::Way);
+  }
+  else
+  {
+    // use the default tag merging mechanism
+    mergedTags = TagMergerFactory::mergeTags(e1->getTags(), e2->getTags(), ElementType::Way);
+  }
+  LOG_TRACE("tags after merging: " << mergedTags);
 
-    // TODO: need to explain how this tag merging differs from that done in buildBuilding and
-    // combineConstituentBuildingsIntoRelation
-    Tags newTags;
-    LOG_TRACE("e1 tags before merging and after built building tag merge: " << e1->getTags());
-    LOG_TRACE("e2 tags before merging and after built building tag merge: " << e2->getTags());
-    if (_manyToManyMatch && _mergeManyToManyMatches)
+  QStringList ref1;
+  e1->getTags().readValues(MetadataTags::Ref1(), ref1);
+  QStringList ref2;
+  e2->getTags().readValues(MetadataTags::Ref2(), ref2);
+
+  ref1.sort();
+  ref2.sort();
+
+  if (ref1.size() != 0 || ref2.size() != 0)
+  {
+    if (ref1 == ref2)
     {
-      // preserve type tags
-      newTags = PreserveTypesTagMerger().mergeTags(e1->getTags(), e2->getTags(), ElementType::Way);
+      mergedTags[MetadataTags::HootBuildingMatch()] = "true";
     }
     else
     {
-      // use the default tag merging mechanism
-      newTags = TagMergerFactory::mergeTags(e1->getTags(), e2->getTags(), ElementType::Way);
+      mergedTags[MetadataTags::HootBuildingMatch()] = "false";
     }
-    LOG_TRACE("tags after merging: " << newTags);
+  }
 
-    QStringList ref1;
-    e1->getTags().readValues(MetadataTags::Ref1(), ref1);
-    QStringList ref2;
-    e2->getTags().readValues(MetadataTags::Ref2(), ref2);
+  LOG_VART(mergedTags);
+  return mergedTags;
+}
+
+ElementId BuildingMerger::_getIdOfMoreComplexBuilding(
+  const ElementPtr& building1, const ElementPtr& building2, const OsmMapPtr& map) const
+{
+  // use node count as a surrogate for complexity of the geometry.
+  int nodeCount1 = 0;
+  if (building1.get())
+  {
+    LOG_VART(building1);
+    nodeCount1 =
+      (int)FilteredVisitor::getStat(
+        new NodeCriterion(), new ElementCountVisitor(), map, building1);
+  }
+  LOG_VART(nodeCount1);
 
-    ref1.sort();
-    ref2.sort();
+  int nodeCount2 = 0;
+  if (building2.get())
+  {
+    LOG_VART(building2);
+    nodeCount2 =
+      (int)FilteredVisitor::getStat(
+        new NodeCriterion(), new ElementCountVisitor(), map, building2);
+  }
+  LOG_VART(nodeCount2);
 
-    if (ref1.size() != 0 || ref2.size() != 0)
+  // This will happen if a way/relation building is passed in with missing nodes.
+  if (nodeCount1 == 0 || nodeCount2 == 0)
+  {
+    if (logWarnCount < Log::getWarnMessageLimit())
     {
-      if (ref1 == ref2)
+      LOG_WARN("One or more of the buildings to merge are empty.  Skipping merge...");
+      if (building1.get())
       {
-        newTags[MetadataTags::HootBuildingMatch()] = "true";
+        LOG_VART(building1->getElementId());
       }
       else
       {
-        newTags[MetadataTags::HootBuildingMatch()] = "false";
+        LOG_TRACE("Building one null.");
       }
-    }
-
-    keeper->setTags(newTags);
-    keeper->setStatus(Status::Conflated);
-
-    OsmUtils::logElementDetail(keeper, map, Log::Trace, "BuildingMerger: keeper");
-    OsmUtils::logElementDetail(scrap, map, Log::Trace, "BuildingMerger: scrap");
-
-    //Check to see if we are removing a multipoly building relation.  If so, its multipolygon
-    //relation members, need to be removed as well.
-    const QSet<ElementId> multiPolyMemberIds = _getMultiPolyMemberIds(scrap);
-
-    //  Here is where we are able to reuse node IDs between buildings
-    ReuseNodeIdsOnWayOp(scrap->getElementId(), keeper->getElementId()).apply(map);
-    //  Replace the scrap with the keeper in any parents
-    ReplaceElementOp(scrap->getElementId(), keeper->getElementId()).apply(map);
-    //  Swap the IDs of the two elements if keeper isn't UNKNOWN1
-    if (preserveBuildingId)
-    {
-      ElementId oldKeeperId = keeper->getElementId();
-      ElementId oldScrapId = scrap->getElementId();
-      IdSwapOp swapOp(oldScrapId, oldKeeperId);
-      swapOp.apply(map);
-      //  Now swap the pointers so that the wrong one isn't deleted
-      if (swapOp.getNumAffected() > 0)
+      if (building2.get())
       {
-        scrap = map->getElement(oldKeeperId);
-        keeper = map->getElement(oldScrapId);
+        LOG_VART(building2->getElementId());
+      }
+      else
+      {
+        LOG_TRACE("Building two null.");
       }
     }
-    //  Finally remove the scrap element from the map
-    DeletableBuildingCriterion crit;
-    RecursiveElementRemover(scrap->getElementId(), &crit).apply(map);
-    scrap->getTags().clear();
-
-    // delete any pre-existing multipoly members
-    LOG_TRACE("Removing multi-poly members: " << multiPolyMemberIds);
-    for (QSet<ElementId>::const_iterator it = multiPolyMemberIds.begin();
-         it != multiPolyMemberIds.end(); ++it)
+    else if (logWarnCount == Log::getWarnMessageLimit())
     {
-      RecursiveElementRemover(*it).apply(map);
+      LOG_WARN(className() << ": " << Log::LOG_WARN_LIMIT_REACHED_MESSAGE);
     }
+    logWarnCount++;
+
+    return ElementId();
+  }
 
-    set<pair<ElementId, ElementId>> replacedSet;
-    for (set<pair<ElementId, ElementId>>::const_iterator it = _pairs.begin();
-         it != _pairs.end(); ++it)
+  if (nodeCount1 == nodeCount2)
+  {
+    LOG_TRACE("Buildings have equally complex geometries.  Keeping the first building geometry...");
+    return building1->getElementId();
+  }
+  else
+  {
+    if (nodeCount1 > nodeCount2)
     {
-      // if we replaced the second group of buildings
-      if (it->second != keeper->getElementId())
-      {
-        replacedSet.insert(pair<ElementId, ElementId>(it->second, keeper->getElementId()));
-      }
-      if (it->first != keeper->getElementId())
-      {
-        replacedSet.insert(pair<ElementId, ElementId>(it->first, keeper->getElementId()));
-      }
+      LOG_TRACE("The first building is more complex.");
+      return building1->getElementId();
+    }
+    else
+    {
+      LOG_TRACE("The second building is more complex.");
+      return building2->getElementId();
     }
-    replaced.insert(replaced.end(), replacedSet.begin(), replacedSet.end());
   }
 }
 
@@ -467,7 +522,8 @@ std::shared_ptr<Element> BuildingMerger::buildBuilding(const OsmMapPtr& map,
       {
         // If the building wasn't a relation, then just add the way building on the list of
         // buildings to be merged into a relation.
-        OsmUtils::logElementDetail(e, map, Log::Trace, "BuildingMerger: Non-relation building");
+        LOG_TRACE(
+          "BuildingMerger: non-relation building\n" << OsmUtils::getElementDetailString(e, map));
         constituentBuildings.push_back(e);
       }
     }
Clone this wiki locally