Skip to content

v0.2.48..v0.2.49 changeset BuildingMerger.cpp

Garret Voltz edited this page Oct 2, 2019 · 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 c172484..546f5e8 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
@@ -29,7 +29,6 @@
 // hoot
 #include <hoot/core/conflate/review/ReviewMarker.h>
 #include <hoot/core/criterion/ElementTypeCriterion.h>
-#include <hoot/core/ops/BuildingPartMergeOp.h>
 #include <hoot/core/ops/RecursiveElementRemover.h>
 #include <hoot/core/ops/ReplaceElementOp.h>
 #include <hoot/core/schema/OverwriteTagMerger.h>
@@ -46,6 +45,9 @@
 #include <hoot/core/elements/OsmUtils.h>
 #include <hoot/core/schema/PreserveTypesTagMerger.h>
 #include <hoot/core/visitors/UniqueElementIdVisitor.h>
+#include <hoot/core/visitors/WorstCircularErrorVisitor.h>
+#include <hoot/core/elements/InMemoryElementSorter.h>
+#include <hoot/core/schema/BuildingRelationMemberTagMerger.h>
 
 using namespace std;
 
@@ -85,6 +87,8 @@ public:
   virtual ElementCriterionPtr clone()
   { return ElementCriterionPtr(new DeletableBuildingCriterion()); }
 
+  virtual QString toString() const override { return ""; }
+
 private:
 
   BuildingCriterion _buildingCrit;
@@ -127,6 +131,9 @@ void BuildingMerger::apply(const OsmMapPtr& map, vector<pair<ElementId, ElementI
   }
   _manyToManyMatch = firstPairs.size() > 1 && secondPairs.size() > 1;
 
+  LOG_VART(_manyToManyMatch);
+  LOG_VART(_mergeManyToManyMatches);
+
   ReviewMarker reviewMarker;
   if (_manyToManyMatch && !_mergeManyToManyMatches)
   {
@@ -238,6 +245,8 @@ void BuildingMerger::apply(const OsmMapPtr& map, vector<pair<ElementId, ElementI
         scrap->getElementId() << "...");
     }
 
+    // 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());
@@ -289,7 +298,7 @@ void BuildingMerger::apply(const OsmMapPtr& map, vector<pair<ElementId, ElementI
     RecursiveElementRemover(scrap->getElementId(), &crit).apply(map);
     scrap->getTags().clear();
 
-    // delete any multipoly members
+    // 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)
@@ -360,21 +369,28 @@ std::shared_ptr<Element> BuildingMerger::buildBuilding(const OsmMapPtr& map,
   {
     LOG_VART(preserveTypes);
 
-    vector<std::shared_ptr<Element>> parts;
+    vector<std::shared_ptr<Element>> constituentBuildings;
     vector<ElementId> toRemove;
-    parts.reserve(eid.size());
+    constituentBuildings.reserve(eid.size());
+    OverwriteTagMerger tagMerger;
     for (set<ElementId>::const_iterator it = eid.begin(); it != eid.end(); ++it)
     {
       std::shared_ptr<Element> e = map->getElement(*it);
+      LOG_VART(e);
       bool isBuilding = false;
       if (e && e->getElementType() == ElementType::Relation)
       {
         RelationPtr r = std::dynamic_pointer_cast<Relation>(e);
-        if (r->getType() == MetadataTags::RelationBuilding())
+
+        // If its a building relation or a building represented by a multipoly relation...
+        if (r->getType() == MetadataTags::RelationBuilding() ||
+            (r->getType() == MetadataTags::RelationMultiPolygon() &&
+             BuildingCriterion().isSatisfied(r)))
         {
           LOG_VART(r);
           isBuilding = true;
 
+          // Go through each of the members.
           // This is odd. Originally I had a const reference to the result, but that was causing
           // an obscure segfault. I changed it to a copy and everything is happy. I don't know
           // when/where the reference would be changing, but I also don't think this will be
@@ -382,39 +398,65 @@ std::shared_ptr<Element> BuildingMerger::buildBuilding(const OsmMapPtr& map,
           vector<RelationData::Entry> m = r->getMembers();
           for (size_t i = 0; i < m.size(); ++i)
           {
-            if (m[i].getRole() == MetadataTags::RolePart())
+            RelationData::Entry constituentBuildingMember = m[i];
+
+            // If its a building relation and the member is a building part,
+            if ((r->getType() == MetadataTags::RelationBuilding() &&
+                constituentBuildingMember.getRole() == MetadataTags::RolePart()) ||
+            // or its a multipoly relation and the member is an inner/outer part...
+                (r->getType() == MetadataTags::RelationMultiPolygon() &&
+                 (constituentBuildingMember.getRole() == MetadataTags::RoleOuter() ||
+                  constituentBuildingMember.getRole() == MetadataTags::RoleInner())))
             {
-              std::shared_ptr<Element> buildingPart = map->getElement(m[i].getElementId());
-              LOG_TRACE("Building part before tag update: " << buildingPart);
-              // Push any non-conflicting tags in the parent relation down into the building part.
-              buildingPart->setTags(
-                OverwriteTagMerger().mergeTags(
-                  buildingPart->getTags(), r->getTags(), buildingPart->getElementType()));
-              LOG_TRACE("Building part after tag update: " << buildingPart);
-              parts.push_back(buildingPart);
+              std::shared_ptr<Element> constituentBuilding = map->getElement(m[i].getElementId());
+
+              // Push any non-conflicting tags in the parent relation down into the constituent
+              // building.
+              LOG_TRACE("Constituent building before tag update: " << constituentBuilding);
+              constituentBuilding->setTags(
+                tagMerger.mergeTags(
+                  constituentBuilding->getTags(), r->getTags(),
+                  constituentBuilding->getElementType()));
+
+              if (r->getType() == MetadataTags::RelationMultiPolygon())
+              {
+                // Need to preserve this for later...not sure of a better way to do it. It will
+                // get removed during the creation of the relation.
+                constituentBuilding->getTags()[MetadataTags::HootMultiPolyRole()] =
+                  constituentBuildingMember.getRole();
+              }
+
+              // Add the building to the list to be merged into a relation.
+              LOG_TRACE("Constituent building after tag update: " << constituentBuilding);
+              constituentBuildings.push_back(constituentBuilding);
             }
           }
 
+          // Remove the parent relation, as we'll be creating a new one to contain all the
+          // constituent buildings being merged in the next step.
           toRemove.push_back(r->getElementId());
         }
       }
 
       if (!isBuilding)
       {
-        OsmUtils::logElementDetail(e, map, Log::Trace, "BuildingMerger: Non-building part");
-        parts.push_back(e);
+        // 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");
+        constituentBuildings.push_back(e);
       }
     }
-    LOG_VART(parts.size());
-    LOG_VART(parts);
+    LOG_VART(constituentBuildings.size());
+    LOG_VART(constituentBuildings);
     LOG_VART(toRemove.size());
     LOG_VART(toRemove);
 
+    // add the constituent buildings to a new relation
     std::shared_ptr<Element> result =
-      BuildingPartMergeOp(preserveTypes).combineBuildingParts(map, parts);
-    LOG_TRACE("Combined building parts into: " << result);
+      combineConstituentBuildingsIntoRelation(map, constituentBuildings, preserveTypes);
+    LOG_TRACE("Combined constituent buildings into: " << result);
 
-    // likely create a crit that only matches buildings and building parts and pass that
+    // remove the relation we previously marked for removal
     DeletableBuildingCriterion crit;
     for (size_t i = 0; i < toRemove.size(); i++)
     {
@@ -432,6 +474,150 @@ std::shared_ptr<Element> BuildingMerger::buildBuilding(const OsmMapPtr& map,
   }
 }
 
+RelationPtr BuildingMerger::combineConstituentBuildingsIntoRelation(
+  const OsmMapPtr& map, std::vector<ElementPtr>& constituentBuildings, const bool preserveTypes)
+{
+  if (constituentBuildings.size() == 0)
+  {
+    throw IllegalArgumentException("No constituent buildings passed to building merger.");
+  }
+
+  // This is primarily put here to support testable output.
+  InMemoryElementSorter::sort(constituentBuildings);
+
+  // Just looking for any key that denotes multi-level buildings. Not handling the situation where
+  // a non-3D building is merging with a 3D building...not exactly sure what to do there...create
+  // both a multipoly and building relation (even though it wouldn't be valid)? No point in worrying
+  // about it until its seen in the wild.
+  QStringList threeDBuildingKeys;
+  threeDBuildingKeys.append(MetadataTags::BuildingLevels());
+  threeDBuildingKeys.append(MetadataTags::BuildingHeight());
+  const bool allAreBuildingParts =
+    OsmUtils::allElementsHaveAnyTagKey(threeDBuildingKeys, constituentBuildings);
+  // skipping a building relation and doing a multipoly if only some of the buildings have height
+  // tags; this behavior is debatable
+  if (!allAreBuildingParts &&
+      OsmUtils::anyElementsHaveAnyTagKey(threeDBuildingKeys, constituentBuildings))
+  {
+    LOG_WARN(
+      "Merging building group where some buildings have 3D tags and others do not. A " <<
+      "multipolygon relation will be created instead of a building relation.")
+  }
+
+  // put the building parts into a relation
+  QString relationType = MetadataTags::RelationMultiPolygon();
+  if (allAreBuildingParts)
+  {
+    relationType = MetadataTags::RelationBuilding();
+  }
+  RelationPtr parentRelation(
+    new Relation(
+      constituentBuildings[0]->getStatus(), map->createNextRelationId(),
+      WorstCircularErrorVisitor::getWorstCircularError(constituentBuildings), relationType));
+
+  TagMergerPtr tagMerger;
+  LOG_VARD(preserveTypes);
+  std::set<QString> overwriteExcludeTags;
+  if (allAreBuildingParts)
+  {
+    // exclude building part type tags from the type tag preservation by passing them in to be
+    // skipped
+    overwriteExcludeTags = BuildingRelationMemberTagMerger::getBuildingPartTagNames();
+  }
+  if (!preserveTypes)
+  {
+    tagMerger.reset(new BuildingRelationMemberTagMerger(overwriteExcludeTags));
+  }
+  else
+  {
+    tagMerger.reset(new PreserveTypesTagMerger(overwriteExcludeTags));
+  }
+
+  Tags& relationTags = parentRelation->getTags();
+  LOG_TRACE("Parent relation starting tags:" << relationTags);
+  for (size_t i = 0; i < constituentBuildings.size(); i++)
+  {
+    ElementPtr constituentBuilding = constituentBuildings[i];
+    if (allAreBuildingParts)
+    {
+      parentRelation->addElement(MetadataTags::RolePart(), constituentBuilding);
+    }
+    else
+    {
+      // If the building was originally pulled out of a relation, remove the temp role tag.
+      if (constituentBuilding->getTags().contains(MetadataTags::HootMultiPolyRole()))
+      {
+        parentRelation->addElement(
+          constituentBuilding->getTags()[MetadataTags::HootMultiPolyRole()], constituentBuilding);
+        constituentBuilding->getTags().remove(MetadataTags::HootMultiPolyRole());
+      }
+      // Otherwise, it was a matched building to be grouped together with an outer role (think this
+      // will always be true...).
+      else
+      {
+        parentRelation->addElement(MetadataTags::RoleOuter(), constituentBuilding);
+      }
+    }
+    relationTags =
+      tagMerger->mergeTags(
+        parentRelation->getTags(), constituentBuilding->getTags(), ElementType::Relation);
+    parentRelation->setTags(relationTags);
+  }
+  if (!parentRelation->getTags().contains("building"))
+  {
+    // TODO: not totally sure yet if this should be added for multipoly relation parts...
+    parentRelation->getTags()["building"] = "yes";
+  }
+  relationTags = parentRelation->getTags();
+  LOG_VART(relationTags);
+
+  // Doing this for multipoly relations in Attribute Conflation only for the time being.
+  const bool suppressBuildingTagOnConstituents =
+    // relatively loose way to identify AC; also used in ConflateCmd
+    ConfigOptions().getHighwayMergeTagsOnly() &&
+    // allAreBuildingParts = building relation
+    !allAreBuildingParts &&
+    ConfigOptions().getAttributeConflationSuppressBuildingTagOnMultipolyRelationConstituents();
+  for (Tags::const_iterator it = relationTags.begin(); it != relationTags.end(); ++it)
+  {
+    // Remove any tags in the parent relation from each of the constituent buildings.
+    for (size_t i = 0; i < constituentBuildings.size(); i++)
+    {
+      ElementPtr constituentBuilding = constituentBuildings[i];
+      // leave building=* on the relation member; remove status here since it will be on the parent
+      // relation as conflated
+      const bool isBuildingTag = it.key() == "building";
+      if ((!isBuildingTag || (isBuildingTag && suppressBuildingTagOnConstituents)) &&
+          (constituentBuilding->getTags().contains(it.key()) ||
+           it.key() == MetadataTags::HootStatus()))
+      {
+        constituentBuilding->getTags().remove(it.key());
+      }
+    }
+  }
+
+  // If we're dealing with a building relation, replace the building tag on the constituents with a
+  // building:part tag.
+  if (allAreBuildingParts)
+  {
+    for (size_t i = 0; i < constituentBuildings.size(); i++)
+    {
+      ElementPtr constituentBuilding = constituentBuildings[i];
+      constituentBuilding->getTags().remove("building");
+      constituentBuilding->getTags()[MetadataTags::BuildingPart()] = "yes";
+    }
+  }
+
+  LOG_VART(parentRelation);
+  for (size_t i = 0; i < constituentBuildings.size(); i++)
+  {
+    LOG_VART(constituentBuildings[i]);
+  }
+
+  map->addRelation(parentRelation);
+  return parentRelation;
+}
+
 std::shared_ptr<Element> BuildingMerger::_buildBuilding(const OsmMapPtr& map,
                                                         const bool unknown1) const
 {
Clone this wiki locally