From d1754b0a59b0bdf5cf6193aba6b393dd1edca1ee Mon Sep 17 00:00:00 2001 From: easbar Date: Thu, 20 Apr 2023 16:14:35 +0200 Subject: [PATCH 1/2] Connect tower nodes of ways tagged as area=yes --- .../reader/osm/WaySegmentParser.java | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/core/src/main/java/com/graphhopper/reader/osm/WaySegmentParser.java b/core/src/main/java/com/graphhopper/reader/osm/WaySegmentParser.java index 52593f6401..0c0117c907 100644 --- a/core/src/main/java/com/graphhopper/reader/osm/WaySegmentParser.java +++ b/core/src/main/java/com/graphhopper/reader/osm/WaySegmentParser.java @@ -31,6 +31,11 @@ import com.graphhopper.util.PointList; import com.graphhopper.util.StopWatch; import com.graphhopper.util.shapes.GHPoint3D; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geom.LineString; +import org.locationtech.jts.geom.impl.PackedCoordinateSequence; +import org.locationtech.jts.geom.prep.PreparedPolygon; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -78,6 +83,8 @@ public class WaySegmentParser { private final OSMNodeData nodeData; private Date timestamp; + private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory(); + private WaySegmentParser(PointAccess nodeAccess, Directory directory, ElevationProvider eleProvider, Predicate wayFilter, Predicate splitNodeFilter, WayPreprocessor wayPreprocessor, Consumer relationPreprocessor, RelationProcessor relationProcessor, @@ -263,6 +270,7 @@ public void handleWay(ReaderWay way) { segment.add(new SegmentNode(node.value, nodeData.getId(node.value), nodeData.getTags(node.value))); wayPreprocessor.preprocessWay(way, osmNodeId -> nodeData.getCoordinates(nodeData.getId(osmNodeId))); splitWayAtJunctionsAndEmptySections(segment, way); + handleAreaWay(segment, way); } private void splitWayAtJunctionsAndEmptySections(List fullSegment, ReaderWay way) { @@ -350,6 +358,58 @@ private void splitSegmentAtSplitNodes(List parentSegment, ReaderWay handleSegment(segment, way); } + void handleAreaWay(List segment, ReaderWay way) { + if (segment.size() <= 3 || !way.hasTag("area", "yes") || + (!way.hasTag("highway", "pedestrian") && !way.hasTag("highway", "footway"))) + return; + if (segment.get(0).osmNodeId != segment.get(segment.size() - 1).osmNodeId) { + // not a closed ring + // todonow: we could fix these by simply connecting the ends + return; + } + + // we connect all the junction nodes appearing in the area way with each other, but only if the connecting + // path lies completely within the area + Coordinate[] polygonCoordinates = new Coordinate[segment.size()]; + for (int i = 0; i < segment.size(); i++) { + int id = segment.get(i).id; + GHPoint3D c = nodeData.getCoordinates(id); + polygonCoordinates[i] = new Coordinate(c.lon, c.lat); + } + PreparedPolygon areaPolygon = new PreparedPolygon(GEOMETRY_FACTORY.createPolygon(new PackedCoordinateSequence.Double(polygonCoordinates, 2))); + // leave out the last node, because it is the same as the first + for (int i = 0; i < segment.size() - 1; i++) { + // TODO NOW can we count the number of involved edges somehow? because areas are often mapped so that + // the start and end are only "unnecessary" tower nodes -> CONNECTION_NODE + if (!isTowerNode(segment.get(i).id)) + continue; + // we can skip the direct neighbor and the last node (because it's equal to the first) + for (int j = i + 2; j < segment.size() - 1; j++) { + if (!isTowerNode(segment.get(j).id)) + continue; + + GHPoint3D from = nodeData.getCoordinates(segment.get(i).id); + GHPoint3D to = nodeData.getCoordinates(segment.get(j).id); + Coordinate[] lineCoordinates = new Coordinate[]{ + new Coordinate(from.lon, from.lat), + new Coordinate(to.lon, to.lat) + }; + LineString lineString = GEOMETRY_FACTORY.createLineString(new PackedCoordinateSequence.Double(lineCoordinates, 2)); + if (!areaPolygon.contains(lineString)) + continue; + PointList pointList = new PointList(2, nodeData.is3D()); + pointList.add(from.lat, from.lon, from.ele); + pointList.add(to.lat, to.lon, to.ele); + List> nodeTags = new ArrayList<>(2); + nodeTags.add(segment.get(i).tags); + nodeTags.add(segment.get(j).tags); + // note that the area edges inherit the tags from the area way. in some odd (?) cases this could go wrong, + // like when area is tagged as oneway? + edgeHandler.handleEdge(nodeData.idToTowerNode(segment.get(i).id), nodeData.idToTowerNode(segment.get(j).id), pointList, way, nodeTags); + } + } + } + void handleSegment(List segment, ReaderWay way) { final PointList pointList = new PointList(segment.size(), nodeData.is3D()); final List> nodeTags = new ArrayList<>(segment.size()); From 10628546a4b383f58b76796078c1217084bd262c Mon Sep 17 00:00:00 2001 From: easbar Date: Thu, 20 Apr 2023 17:11:39 +0200 Subject: [PATCH 2/2] Skip connection tower nodes when creating area edges --- .../com/graphhopper/reader/osm/OSMNodeData.java | 15 +++++++++++++-- .../graphhopper/reader/osm/WaySegmentParser.java | 8 ++++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/com/graphhopper/reader/osm/OSMNodeData.java b/core/src/main/java/com/graphhopper/reader/osm/OSMNodeData.java index 3c442c01e8..76dbeabcba 100644 --- a/core/src/main/java/com/graphhopper/reader/osm/OSMNodeData.java +++ b/core/src/main/java/com/graphhopper/reader/osm/OSMNodeData.java @@ -18,6 +18,8 @@ package com.graphhopper.reader.osm; +import com.carrotsearch.hppc.IntScatterSet; +import com.carrotsearch.hppc.IntSet; import com.carrotsearch.hppc.LongScatterSet; import com.carrotsearch.hppc.LongSet; import com.graphhopper.coll.GHLongIntBTree; @@ -30,7 +32,6 @@ import com.graphhopper.util.shapes.GHPoint3D; import java.util.Collections; -import java.util.HashMap; import java.util.Map; import java.util.function.DoubleSupplier; import java.util.function.IntUnaryOperator; @@ -81,6 +82,9 @@ class OSMNodeData { // we use negative ids to create artificial OSM node ids private long nextArtificialOSMNodeId = -Long.MAX_VALUE; + // we use this set to store which tower nodes are only connecting two ways + private final IntSet connectionTowerNodes; + public OSMNodeData(PointAccess nodeAccess, Directory directory) { // We use GHLongIntBTree, because it is based on a tree, not an array, so it can store as many entries as there // are longs. This also makes it memory efficient, because there is no need to pre-allocate memory for empty @@ -92,6 +96,7 @@ public OSMNodeData(PointAccess nodeAccess, Directory directory) { nodeTagIndicesByOsmNodeIds = new GHLongIntBTree(200); nodesToBeSplit = new LongScatterSet(); nodeKVStorage = new KVStorage(directory, false); + connectionTowerNodes = new IntScatterSet(); } public boolean is3D() { @@ -116,6 +121,10 @@ public static boolean isPillarNode(int id) { return id > CONNECTION_NODE; } + public boolean isConnectionTowerNode(int id) { + return connectionTowerNodes.contains(id); + } + public static boolean isNodeId(int id) { return id > CONNECTION_NODE || id < JUNCTION_NODE; } @@ -168,7 +177,9 @@ else if (nodeType == INTERMEDIATE_NODE || nodeType == END_NODE) private int addTowerNode(long osmId, double lat, double lon, double ele) { towerNodes.setNode(nextTowerId, lat, lon, ele); int id = towerNodeToId(nextTowerId); - idsByOsmNodeIds.put(osmId, id); + int prevId = idsByOsmNodeIds.put(osmId, id); + if (prevId == CONNECTION_NODE) + connectionTowerNodes.add(id); nextTowerId++; return id; } diff --git a/core/src/main/java/com/graphhopper/reader/osm/WaySegmentParser.java b/core/src/main/java/com/graphhopper/reader/osm/WaySegmentParser.java index 0c0117c907..8426287d8d 100644 --- a/core/src/main/java/com/graphhopper/reader/osm/WaySegmentParser.java +++ b/core/src/main/java/com/graphhopper/reader/osm/WaySegmentParser.java @@ -379,13 +379,13 @@ void handleAreaWay(List segment, ReaderWay way) { PreparedPolygon areaPolygon = new PreparedPolygon(GEOMETRY_FACTORY.createPolygon(new PackedCoordinateSequence.Double(polygonCoordinates, 2))); // leave out the last node, because it is the same as the first for (int i = 0; i < segment.size() - 1; i++) { - // TODO NOW can we count the number of involved edges somehow? because areas are often mapped so that - // the start and end are only "unnecessary" tower nodes -> CONNECTION_NODE - if (!isTowerNode(segment.get(i).id)) + // we skip tower nodes that aren't really linked to other edges, but only were created to connect the beginning + // and end of the (closed-ring) area way + if (!isTowerNode(segment.get(i).id) || nodeData.isConnectionTowerNode(segment.get(i).id)) continue; // we can skip the direct neighbor and the last node (because it's equal to the first) for (int j = i + 2; j < segment.size() - 1; j++) { - if (!isTowerNode(segment.get(j).id)) + if (!isTowerNode(segment.get(j).id) || nodeData.isConnectionTowerNode(segment.get(j).id)) continue; GHPoint3D from = nodeData.getCoordinates(segment.get(i).id);