diff --git a/conf/core/RoadCrossingBUARules.json b/conf/core/RoadCrossingBUARules.json
new file mode 100644
index 0000000000..b4a8cabd54
--- /dev/null
+++ b/conf/core/RoadCrossingBUARules.json
@@ -0,0 +1,9 @@
+{
+ "rules":
+ [
+ {
+ "name": "landuse",
+ "polyCriteriaFilter": "LanduseCriterion"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/hoot-core/src/main/cpp/hoot/core/criterion/LanduseCriterion.cpp b/hoot-core/src/main/cpp/hoot/core/criterion/LanduseCriterion.cpp
new file mode 100644
index 0000000000..14250c2912
--- /dev/null
+++ b/hoot-core/src/main/cpp/hoot/core/criterion/LanduseCriterion.cpp
@@ -0,0 +1,95 @@
+/*
+ * 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 .
+ *
+ * --------------------------------------------------------------------
+ *
+ * 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. Maxar
+ * copyrights will be updated automatically.
+ *
+ * @copyright Copyright (C) 2019-2023 Maxar (http://www.maxar.com/)
+ */
+
+#include "LanduseCriterion.h"
+
+// hoot
+#include
+#include
+#include
+
+namespace hoot
+{
+
+HOOT_FACTORY_REGISTER(ElementCriterion, LanduseCriterion)
+
+LanduseCriterion::LanduseCriterion(ConstOsmMapPtr map)
+ : ConstOsmMapConsumerBase(map)
+{
+ // Set this to allow any on poly child member to satisfy the crit.
+ _relationCrit.setAllowMixedChildren(true);
+ _relationCrit.setOsmMap(map.get());
+}
+
+void LanduseCriterion::setOsmMap(const OsmMap* map)
+{
+ ConstOsmMapConsumerBase::setOsmMap(map);
+ _relationCrit.setOsmMap(map);
+}
+
+bool LanduseCriterion::isSatisfied(const ConstElementPtr& e) const
+{
+ LOG_VART(e->getElementId());
+
+ // element has landuse tag
+ const Tags& tags = e->getTags();
+ auto it = tags.find("landuse");
+ // Specifically looking for BUAs
+ std::vector landuseTypes = {"residential", "commercial", "industrial"};
+ const bool containsLanduseTag = it != tags.end() && (std::find(landuseTypes.begin(), landuseTypes.end(), it.value()) != landuseTypes.end());
+ bool result = false;
+
+ switch(e->getElementType().getEnum())
+ {
+ default:
+ case ElementType::Node:
+ return false;
+ case ElementType::Way:
+ {
+ ConstWayPtr way = std::dynamic_pointer_cast(e);
+ LOG_VART(way->isValidPolygon());
+ LOG_VART(way->isClosedArea());
+ if (way->isValidPolygon() && way->isClosedArea() && containsLanduseTag)
+ {
+ LOG_TRACE("Way is valid closed landuse area; crit satisfied.");
+ result = true;
+ LOG_TRACE(e->getElementId() << " result: " << result);
+ return true;
+ }
+ break;
+ }
+ }
+ LOG_TRACE(e->getElementId() << " result: " << result);
+ return false;
+}
+
+QStringList LanduseCriterion::getChildCriteria() const
+{
+ return QStringList(PolygonWayNodeCriterion::className());
+}
+
+}
\ No newline at end of file
diff --git a/hoot-core/src/main/cpp/hoot/core/criterion/LanduseCriterion.h b/hoot-core/src/main/cpp/hoot/core/criterion/LanduseCriterion.h
new file mode 100644
index 0000000000..bf35a94030
--- /dev/null
+++ b/hoot-core/src/main/cpp/hoot/core/criterion/LanduseCriterion.h
@@ -0,0 +1,78 @@
+/*
+ * 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 .
+ *
+ * --------------------------------------------------------------------
+ *
+ * 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. Maxar
+ * copyrights will be updated automatically.
+ *
+ * @copyright Copyright (C) 2019-2023 Maxar (http://www.maxar.com/)
+ */
+
+#ifndef LANDUSE_CRITERION_H
+#define LANDUSE_CRITERION_H
+
+// Hoot
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+namespace hoot
+{
+
+/**
+ * Identifies Landuse polygon features
+ */
+class LanduseCriterion : public ConflatableElementCriterion, public ConstOsmMapConsumerBase
+{
+public:
+
+ static QString className() { return "LanduseCriterion"; }
+
+ LanduseCriterion() = default;
+ LanduseCriterion(ConstOsmMapPtr map);
+ ~LanduseCriterion() override = default;
+
+ bool isSatisfied(const ConstElementPtr& e) const override;
+ ElementCriterionPtr clone() override { return std::make_shared(_map); }
+
+ void setOsmMap(const OsmMap* map) override;
+
+ bool supportsSpecificConflation() const override { return false; }
+ GeometryType getGeometryType() const override { return GeometryType::Polygon; }
+ QStringList getChildCriteria() const override;
+
+ QString getName() const override { return className(); }
+ QString getClassName() const override { return className(); }
+ QString toString() const override { return className(); }
+ QString getDescription() const override { return "Identifies landuse polygon features"; }
+
+ void setAllowMixedChildren(bool allow) { _relationCrit.setAllowMixedChildren(allow); }
+
+private:
+
+ RelationWithPolygonMembersCriterion _relationCrit;
+};
+
+}
+#endif // LANDUSE_CRITERION_H
\ No newline at end of file
diff --git a/hoot-core/src/main/cpp/hoot/core/ops/FindIntersectionsOp.cpp b/hoot-core/src/main/cpp/hoot/core/ops/FindIntersectionsOp.cpp
index e3bc63cfe5..ff1be1d594 100644
--- a/hoot-core/src/main/cpp/hoot/core/ops/FindIntersectionsOp.cpp
+++ b/hoot-core/src/main/cpp/hoot/core/ops/FindIntersectionsOp.cpp
@@ -53,6 +53,7 @@ namespace hoot
HOOT_FACTORY_REGISTER(OsmMapOperation, FindHighwayIntersectionsOp)
HOOT_FACTORY_REGISTER(OsmMapOperation, FindRailwayIntersectionsOp)
+HOOT_FACTORY_REGISTER(OsmMapOperation, FindHighwayLanduseIntersectionsOp)
void FindIntersectionsOp::apply(std::shared_ptr& map)
{
@@ -115,4 +116,9 @@ std::shared_ptr FindRailwayIntersectionsOp::createVisi
return std::make_shared();
}
+std::shared_ptr FindHighwayLanduseIntersectionsOp::createVisitor()
+{
+ return std::make_shared();
+}
+
}
diff --git a/hoot-core/src/main/cpp/hoot/core/ops/FindIntersectionsOp.h b/hoot-core/src/main/cpp/hoot/core/ops/FindIntersectionsOp.h
index 07663492c6..7bb7840be1 100644
--- a/hoot-core/src/main/cpp/hoot/core/ops/FindIntersectionsOp.h
+++ b/hoot-core/src/main/cpp/hoot/core/ops/FindIntersectionsOp.h
@@ -100,6 +100,24 @@ class FindRailwayIntersectionsOp : public FindIntersectionsOp
QString getDescription() const override { return "Identifies railway intersections"; }
};
+/**
+ * Op that finds all highway/landuse intersections
+ */
+class FindHighwayLanduseIntersectionsOp : public FindIntersectionsOp
+{
+public:
+ FindHighwayLanduseIntersectionsOp() = default;
+ ~FindHighwayLanduseIntersectionsOp() override = default;
+
+ static QString className() { return "FindHighwayLanduseIntersectionsOp"; }
+
+ std::shared_ptr createVisitor() override;
+
+ QString getName() const override { return className(); }
+ QString getClassName() const override { return className(); }
+ QString getDescription() const override { return "Identifies highway/landuse intersections"; }
+};
+
}
#endif // FINDHIGHWAYINTERSECTIONSOP_H
diff --git a/hoot-core/src/main/cpp/hoot/core/ops/RoadCrossingLandusePolySplit.cpp b/hoot-core/src/main/cpp/hoot/core/ops/RoadCrossingLandusePolySplit.cpp
new file mode 100644
index 0000000000..90e3bc68ff
--- /dev/null
+++ b/hoot-core/src/main/cpp/hoot/core/ops/RoadCrossingLandusePolySplit.cpp
@@ -0,0 +1,188 @@
+/*
+ * 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 .
+ *
+ * --------------------------------------------------------------------
+ *
+ * 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. Maxar
+ * copyrights will be updated automatically.
+ *
+ * @copyright Copyright (C) 2020, 2021, 2022 Maxar (http://www.maxar.com/)
+ */
+
+#include "RoadCrossingLandusePolySplit.h"
+
+// Hoot
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace hoot
+{
+
+HOOT_FACTORY_REGISTER(OsmMapOperation, RoadCrossingLandusePolySplit)
+
+RoadCrossingLandusePolySplit::RoadCrossingLandusePolySplit()
+ : _numRoads(0),
+ _taskStatusUpdateInterval(ConfigOptions().getTaskStatusUpdateInterval())
+{
+ setConfiguration(conf());
+}
+
+void RoadCrossingLandusePolySplit::setConfiguration(const Settings& conf)
+{
+ _crossingRulesFile = ConfigOptions(conf).getHighwayCrossingPolyRules().trimmed();
+}
+
+void RoadCrossingLandusePolySplit::apply(OsmMapPtr& map)
+{
+ _numAffected = 0; // roads we marked for split
+ _numProcessed = 0; // all ways
+ _numRoads = 0; // all roads
+ _numBUAs = 0; // BUAs we marked for split
+ _map = map;
+ _nodeIds.clear();
+ _markedRoads.clear();
+ _markedBUAs.clear();
+
+ // If there are no polygons in the input, then skip initialization of the road crossing BUA
+ // rules, since the road index creation can be expensive when calling conflation in a loop.
+ ElementCriterionPtr landuseCrit = std::make_shared(_map);
+ ElementCriterionPtr tagCrit = std::make_shared(std::make_shared("highway"));
+ ElementCriterionPtr crit = std::make_shared(landuseCrit, tagCrit);
+ if (FilteredVisitor::getStat(crit, std::make_shared(), _map) == 0)
+ {
+ LOG_DEBUG("No polygons found in input map. Skipping marking roads crossing polygons.");
+ return;
+ }
+
+ _crossingRules = RoadCrossingPolyRule::readRules(_crossingRulesFile, _map);
+ OsmMapPtr toSplitMap = std::make_shared();
+
+ const WayMap& ways = _map->getWays();
+ LOG_VARD(ways.size());
+ HighwayCriterion highwayCrit(_map);
+ for (auto waysItr = ways.begin(); waysItr != ways.end(); ++waysItr)
+ {
+ WayPtr way = waysItr->second;
+ LOG_VART(way->getElementId());
+
+ // for each road
+ if (highwayCrit.isSatisfied(way))
+ {
+ // for each road crossing poly rule
+ for (const auto& rule : qAsConst(_crossingRules))
+ {
+ // If we haven't already marked this road for split, and it
+ // isn't allowed to cross the BUA specified by the current rule...
+ if (!_markedRoads.contains(way->getElementId()) &&
+ (!rule.getAllowedRoadTagFilter() || !rule.getAllowedRoadTagFilter()->isSatisfied(way)))
+ {
+ std::shared_ptr env(way->getEnvelope(_map));
+ LOG_VART(env);
+
+ // Get all nearby polys to the road that pass our poly filter
+ const std::set neighborIdsSet =
+ SpatialIndexer::findNeighbors(*env, rule.getIndex(), rule.getIndexToEid(), _map, ElementType::Way, false);
+ LOG_VART(neighborIdsSet.size());
+
+ // for each nearby poly
+ for (const auto& neighborId : neighborIdsSet)
+ {
+ LOG_VART(neighborId);
+ ElementPtr neighbor = _map->getElement(neighborId);
+
+ // If the road intersects the poly, mark it.
+ if (neighbor && rule.getPolyFilter()->isSatisfied(neighbor) &&
+ ElementGeometryUtils::haveGeometricRelationship(
+ way, neighbor, GeometricRelationship::Crosses, _map))
+ {
+ LOG_DEBUG("Marking " << way->getElementId() << " for split crossing BUA " << neighborId);
+ LOG_VART(way);
+
+ _markedRoads.insert(way->getElementId());
+ _numAffected++;
+ toSplitMap->addElement(way);
+
+ for (const auto& nodeId : way->getNodeIds())
+ {
+ if (!_nodeIds.contains(nodeId))
+ {
+ _nodeIds.insert(nodeId);
+ toSplitMap->addElement(_map->getNode(nodeId));
+ }
+ }
+
+ if (!_markedBUAs.contains(neighborId))
+ {
+ _markedBUAs.insert(neighborId);
+ _numBUAs++;
+ toSplitMap->addElement(neighbor);
+
+ for (const auto& nodeId : std::dynamic_pointer_cast(neighbor)->getNodeIds())
+ {
+ if (!_nodeIds.contains(nodeId))
+ {
+ _nodeIds.insert(nodeId);
+ toSplitMap->addElement(_map->getNode(nodeId));
+ }
+ }
+ }
+
+ }
+ }
+ }
+ }
+ _numRoads++;
+ }
+
+ _numProcessed++;
+ if (_numProcessed % (_taskStatusUpdateInterval * 10) == 0)
+ {
+ PROGRESS_INFO(
+ "\tMarked " << StringUtils::formatLargeNumber(_numAffected) << " crossing roads out of " <<
+ StringUtils::formatLargeNumber(_numRoads) << " total roads and " <<
+ StringUtils::formatLargeNumber(ways.size()) << " total ways.");
+ }
+ }
+ LOG_VARD(_numAffected);
+ LOG_VARD(_numBUAs);
+ LOG_VARD(_numProcessed);
+
+ map = toSplitMap;
+
+}
+
+QStringList RoadCrossingLandusePolySplit::getCriteria() const
+{
+ QStringList criteria;
+ criteria.append(HighwayCriterion::className());
+ return criteria;
+}
+
+}
\ No newline at end of file
diff --git a/hoot-core/src/main/cpp/hoot/core/ops/RoadCrossingLandusePolySplit.h b/hoot-core/src/main/cpp/hoot/core/ops/RoadCrossingLandusePolySplit.h
new file mode 100644
index 0000000000..85b70dfa7d
--- /dev/null
+++ b/hoot-core/src/main/cpp/hoot/core/ops/RoadCrossingLandusePolySplit.h
@@ -0,0 +1,92 @@
+/*
+ * 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 .
+ *
+ * --------------------------------------------------------------------
+ *
+ * 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. Maxar
+ * copyrights will be updated automatically.
+ *
+ * @copyright Copyright (C) 2020, 2021, 2022 Maxar (http://www.maxar.com/)
+ */
+
+#ifndef ROAD_CROSSING_LANDUSE_POLY_SPLIT_H
+#define ROAD_CROSSING_LANDUSE_POLY_SPLIT_H
+
+// Hoot
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace hoot
+{
+/**
+ * Splits roads in instances where they cross over BUAs and is governed by a set of rules (see
+ * RoadCrossingPolyRule section of the User documentation).
+ */
+class RoadCrossingLandusePolySplit : public OsmMapOperation, public Configurable
+{
+public:
+
+ static QString className() { return "RoadCrossingLandusePolySplit"; }
+
+ RoadCrossingLandusePolySplit();
+ ~RoadCrossingLandusePolySplit() override = default;
+
+ /**
+ * @see OsmMapOperation
+ */
+ void apply(OsmMapPtr& map) override;
+
+ /**
+ * @see Configurable
+ */
+ void setConfiguration(const Settings& conf) override;
+
+ QString getDescription() const override { return "Splitting roads at BUA boundaries"; }
+ QString getName() const override { return className(); }
+ QString getClassName() const override { return className(); }
+
+ /**
+ * @see FilteredByGeometryTypeCriteria
+ */
+ QStringList getCriteria() const override;
+
+private:
+
+ OsmMapPtr _map;
+
+ QString _crossingRulesFile;
+ QList _crossingRules;
+
+ QSet _nodeIds;
+
+ QSet _markedRoads;
+ QSet _markedBUAs;
+ int _numRoads;
+ int _numBUAs;
+
+ int _taskStatusUpdateInterval;
+};
+
+}
+
+#endif // ROAD_CROSSING_LANDUSE_POLY_SPLIT_H
\ No newline at end of file
diff --git a/hoot-core/src/main/cpp/hoot/core/visitors/FindIntersectionsVisitor.cpp b/hoot-core/src/main/cpp/hoot/core/visitors/FindIntersectionsVisitor.cpp
index 105577e1f2..54851b6334 100644
--- a/hoot-core/src/main/cpp/hoot/core/visitors/FindIntersectionsVisitor.cpp
+++ b/hoot-core/src/main/cpp/hoot/core/visitors/FindIntersectionsVisitor.cpp
@@ -29,6 +29,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -41,6 +42,7 @@ namespace hoot
HOOT_FACTORY_REGISTER(ElementVisitor, FindHighwayIntersectionsVisitor)
HOOT_FACTORY_REGISTER(ElementVisitor, FindRailwayIntersectionsVisitor)
+HOOT_FACTORY_REGISTER(ElementVisitor, FindHighwayLanduseIntersectionsVisitor)
void FindIntersectionsVisitor::visit(const ConstElementPtr& e)
{
@@ -63,6 +65,7 @@ void FindIntersectionsVisitor::visit(const ConstElementPtr& e)
if (hwids.size() >= 3) // two or more ways intersecting
{
// keep it
+ LOG_DEBUG("found an intersection");
_ids.push_back(id);
// TODO: This and the commented out minAngle/maxAngle sections below are puzzling...changing the
@@ -103,6 +106,11 @@ ElementCriterionPtr FindRailwayIntersectionsVisitor::createCriterion(ConstOsmMap
return std::make_shared();
}
+ElementCriterionPtr FindHighwayLanduseIntersectionsVisitor::createCriterion(ConstOsmMapPtr map)
+{
+ return std::make_shared(map);
+}
+
}
diff --git a/hoot-core/src/main/cpp/hoot/core/visitors/FindIntersectionsVisitor.h b/hoot-core/src/main/cpp/hoot/core/visitors/FindIntersectionsVisitor.h
index c5fde55177..dceb97e89a 100644
--- a/hoot-core/src/main/cpp/hoot/core/visitors/FindIntersectionsVisitor.h
+++ b/hoot-core/src/main/cpp/hoot/core/visitors/FindIntersectionsVisitor.h
@@ -115,6 +115,26 @@ class FindRailwayIntersectionsVisitor : public FindIntersectionsVisitor
ElementCriterionPtr createCriterion(ConstOsmMapPtr map) override;
};
+/**
+ * Finds all highway/landuse intersection nodes, adds some parameters to them and records their node ids
+ */
+class FindHighwayLanduseIntersectionsVisitor : public FindIntersectionsVisitor
+{
+public:
+
+ static QString className() { return "FindHighwayLanduseIntersectionsVisitor"; }
+
+ FindHighwayLanduseIntersectionsVisitor() = default;
+ ~FindHighwayLanduseIntersectionsVisitor() override = default;
+
+ QString getDescription() const override { return "Identifies highway/landuse intersections"; }
+
+ QString getInitStatusMessage() const override { return "Finding highway/landuse intersections..."; }
+ QString getCompletedStatusMessage() const override { return "Found " + QString::number(_numAffected) + " highway/landuse intersections"; }
+
+ ElementCriterionPtr createCriterion(ConstOsmMapPtr map) override;
+};
+
}
#endif // FINDINTERSECTIONSVISITOR_H
diff --git a/translations/tds71.js b/translations/tds71.js
index 55c0519882..ae8b082278 100644
--- a/translations/tds71.js
+++ b/translations/tds71.js
@@ -2908,7 +2908,10 @@ tds71 = {
case 'engine_shed':
case 'workshop':
notUsedTags.railway = tags.railway; // Preserving thisjavascript ~ operator
+ break;
+ case 'razed':
+ attrs.PCF = '5'; // Dismantled
break;
}