diff --git a/hoot-core/src/main/cpp/hoot/core/conflate/poi-polygon/PoiPolygonInvalidReviewNodeRemover.cpp b/hoot-core/src/main/cpp/hoot/core/conflate/poi-polygon/PoiPolygonInvalidReviewNodeRemover.cpp
new file mode 100644
index 0000000..2efd3ed
--- /dev/null
+++ b/hoot-core/src/main/cpp/hoot/core/conflate/poi-polygon/PoiPolygonInvalidReviewNodeRemover.cpp
@@ -0,0 +1,173 @@
+/*
+ * 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) 2020 DigitalGlobe (http://www.digitalglobe.com/)
+ */
+
+#include "PoiPolygonInvalidReviewNodeRemover.h"
+
+// Hoot
+#include <hoot/core/elements/OsmMap.h>
+#include <hoot/core/util/Factory.h>
+#include <hoot/core/util/Log.h>
+#include <hoot/core/util/StringUtils.h>
+#include <hoot/core/ops/RemoveNodeByEid.h>
+#include <hoot/core/ops/RemoveRelationByEid.h>
+//#include <hoot/core/ops/RecursiveElementRemover.h>
+#include <hoot/core/conflate/poi-polygon/PoiPolygonMatch.h>
+
+namespace hoot
+{
+
+HOOT_FACTORY_REGISTER(OsmMapOperation, PoiPolygonInvalidReviewNodeRemover)
+
+PoiPolygonInvalidReviewNodeRemover::PoiPolygonInvalidReviewNodeRemover() :
+_numRelationsRemoved(0),
+_taskStatusUpdateInterval(ConfigOptions().getTaskStatusUpdateInterval())
+{
+}
+
+void PoiPolygonInvalidReviewNodeRemover::apply(const std::shared_ptr<OsmMap>& map)
+{
+ _numAffected = 0; // _numAffected reflects the actual number of nodes removed
+ _numProcessed = 0; // _numProcessed reflects total elements processed
+ _numRelationsRemoved = 0;
+ _nodesToRemove.clear();
+ _reviewRelationsToRemove.clear();
+
+ // make a copy here, since we may be removing some relations later
+ const RelationMap relations = map->getRelations();
+ for (RelationMap::const_iterator it = relations.begin(); it != relations.end(); ++it)
+ {
+ ConstRelationPtr relation = it->second;
+ LOG_VART(relation->getType());
+ LOG_VART(relation->getTags()[MetadataTags::HootReviewType()]);
+ if (relation->getType() == MetadataTags::RelationReview() &&
+ relation->getTags()[MetadataTags::HootReviewType()] == PoiPolygonMatch::MATCH_NAME)
+ {
+ const std::vector<RelationData::Entry>& members = relation->getMembers();
+ for (size_t i = 0; i < members.size(); i++)
+ {
+ const RelationData::Entry member = members[i];
+ LOG_VART(member.getElementId());
+ if (member.getElementId().getType() == ElementType::Node)
+ {
+ ConstElementPtr element = map->getElement(member.getElementId());
+ if (element)
+ {
+ LOG_VART(map->getElement(member.getElementId())->getTags().size());
+ if (map->getElement(member.getElementId())->getTags().size() == 0)
+ {
+ LOG_TRACE("Marking " << member.getElementId() << " for removal...");
+ _nodesToRemove.insert(member.getElementId().getId());
+ if (members.size() <= 2)
+ {
+ LOG_TRACE("Marking " << relation->getElementId() << " for removal...");
+ _reviewRelationsToRemove.insert(relation->getElementId().getId());
+ }
+ }
+ }
+ }
+ }
+ }
+
+ _numProcessed++;
+ if (_numProcessed % _taskStatusUpdateInterval == 0)
+ {
+ PROGRESS_INFO(
+ "Marked " << StringUtils::formatLargeNumber(_nodesToRemove.size()) <<
+ " nodes for removal after processing " << StringUtils::formatLargeNumber(_numProcessed) <<
+ " total elements.");
+ }
+ }
+ LOG_VART(_nodesToRemove.size());
+
+ if (_nodesToRemove.size() == 0)
+ {
+ return;
+ }
+
+ _numProcessed = 0;
+ for (RelationMap::const_iterator it = relations.begin(); it != relations.end(); ++it)
+ {
+ const Relation* relation = it->second.get();
+ LOG_VART(relation->getElementId());
+ const long relationId = relation->getId();
+ if (_reviewRelationsToRemove.find(relationId) != _reviewRelationsToRemove.end())
+ {
+ LOG_TRACE("Removing relation: " << relation->getElementId() << "...");
+ RemoveRelationByEid::removeRelation(map, relationId);
+ _numRelationsRemoved++;
+ }
+ _numProcessed++;
+
+ if (_numRelationsRemoved % _taskStatusUpdateInterval == 0)
+ {
+ PROGRESS_INFO(
+ "Removed " << StringUtils::formatLargeNumber(_numRelationsRemoved) <<
+ " relation / " << StringUtils::formatLargeNumber(_reviewRelationsToRemove.size()) <<
+ " total relations to remove.");
+ }
+ else if (_numProcessed % _taskStatusUpdateInterval == 0)
+ {
+ PROGRESS_INFO(
+ "Processed " << StringUtils::formatLargeNumber(_numProcessed) <<
+ " relations / " << StringUtils::formatLargeNumber(relations.size()) << " total relations.");
+ }
+ }
+
+ // make a copy here, since we may be removing some of these nodes
+ const NodeMap nodes = map->getNodes();
+ _numProcessed = 0;
+ for (NodeMap::const_iterator it = nodes.begin(); it != nodes.end(); ++it)
+ {
+ const Node* n = it->second.get();
+ LOG_VART(n->getElementId());
+ const long nodeId = n->getId();
+ if (_nodesToRemove.find(nodeId) != _nodesToRemove.end())
+ {
+ LOG_TRACE("Removing node: " << n->getElementId() << "...");
+ RemoveNodeByEid::removeNodeNoCheck(map, nodeId);
+ //RecursiveElementRemover(n->getElementId()).apply(map);
+ _numAffected++;
+ }
+ _numProcessed++;
+
+ if (_numAffected % _taskStatusUpdateInterval == 0)
+ {
+ PROGRESS_INFO(
+ "Removed " << StringUtils::formatLargeNumber(_numAffected) <<
+ " nodes / " << StringUtils::formatLargeNumber(_nodesToRemove.size()) <<
+ " total nodes to remove.");
+ }
+ else if (_numProcessed % _taskStatusUpdateInterval == 0)
+ {
+ PROGRESS_INFO(
+ "Processed " << StringUtils::formatLargeNumber(_numProcessed) <<
+ " nodes / " << StringUtils::formatLargeNumber(nodes.size()) << " total nodes.");
+ }
+ }
+}
+
+}