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; }