Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Connect tower nodes of highways tagged as area=yes #2805

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 13 additions & 2 deletions core/src/main/java/com/graphhopper/reader/osm/OSMNodeData.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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() {
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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<ReaderWay> wayFilter, Predicate<ReaderNode> splitNodeFilter, WayPreprocessor wayPreprocessor,
Consumer<ReaderRelation> relationPreprocessor, RelationProcessor relationProcessor,
Expand Down Expand Up @@ -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<SegmentNode> fullSegment, ReaderWay way) {
Expand Down Expand Up @@ -350,6 +358,58 @@ private void splitSegmentAtSplitNodes(List<SegmentNode> parentSegment, ReaderWay
handleSegment(segment, way);
}

void handleAreaWay(List<SegmentNode> 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++) {
// 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) || nodeData.isConnectionTowerNode(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<Map<String, Object>> 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<SegmentNode> segment, ReaderWay way) {
final PointList pointList = new PointList(segment.size(), nodeData.is3D());
final List<Map<String, Object>> nodeTags = new ArrayList<>(segment.size());
Expand Down