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
{