Skip to content

Commit

Permalink
initial work on #13 (turn restrictions).
Browse files Browse the repository at this point in the history
  • Loading branch information
mattwigway committed Feb 19, 2016
1 parent a818192 commit 553c925
Show file tree
Hide file tree
Showing 10 changed files with 555 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -740,11 +740,7 @@ private static void getVertexFeatures(EdgeStore.Edge cursor, VertexStore.Vertex
/**
* Creates geojson feature from specified vertex
*
<<<<<<< HEAD
* Currently it only does that if vertex have TRAFFIC_SIGNAL flag, or is a transit stop.
=======
* Currently it only does that if vertex have TRAFFIC_SIGNAL or BIKE_SHARING flag.
>>>>>>> graphQL
* Properties in GeoJSON are:
* - vertex_id
* - flags: TRAFFIC_SIGNAL or BIKE_SHARING currently not both
Expand Down
47 changes: 46 additions & 1 deletion src/main/java/com/conveyal/r5/streets/EdgeStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@
import com.conveyal.r5.common.GeometryUtils;
import com.conveyal.r5.profile.Mode;
import com.conveyal.r5.profile.ProfileRequest;
import com.conveyal.r5.util.TIntIntHashMultimap;
import com.conveyal.r5.util.TIntIntMultimap;
import com.conveyal.r5.util.TIntObjectHashMultimap;
import com.conveyal.r5.util.TIntObjectMultimap;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.LineString;
import gnu.trove.TIntCollection;
import gnu.trove.list.TIntList;
import gnu.trove.list.TLongList;
import gnu.trove.list.TShortList;
Expand Down Expand Up @@ -75,13 +80,20 @@ public class EdgeStore implements Serializable {
/** Geometries. One entry for each edge pair */
public List<int[]> geometries; // intermediate points along the edge, other than the intersection endpoints

/** Turn restrictions for turning _out of_ each edge */
public TIntIntMultimap turnRestrictions;

public StreetLayer layer;

public static final transient EnumSet<EdgeFlag> PERMISSION_FLAGS = EnumSet
.of(EdgeFlag.ALLOWS_PEDESTRIAN, EdgeFlag.ALLOWS_BIKE, EdgeFlag.ALLOWS_CAR);

private long generatedOSMID = 1;

public EdgeStore (VertexStore vertexStore, int initialSize) {
public EdgeStore (VertexStore vertexStore, StreetLayer layer, int initialSize) {
this.vertexStore = vertexStore;
this.layer = layer;

// There are separate flags and speeds entries for the forward and backward edges in each pair.
flags = new TIntArrayList(initialSize);
speeds = new TShortArrayList(initialSize);
Expand All @@ -92,6 +104,7 @@ public EdgeStore (VertexStore vertexStore, int initialSize) {
geometries = new ArrayList<>(initialEdgePairs);
lengths_mm = new TIntArrayList(initialEdgePairs);
osmids = new TLongArrayList(initialEdgePairs);
turnRestrictions = new TIntIntHashMultimap();
}

/**
Expand Down Expand Up @@ -385,6 +398,7 @@ public void copyPairFlagsAndSpeeds(Edge other) {
flags.set(backEdge, other.getEdgeStore().flags.get(otherBackEdge));
speeds.set(foreEdge, other.getEdgeStore().speeds.get(otherForeEdge));
speeds.set(backEdge, other.getEdgeStore().speeds.get(otherBackEdge));
osmids.set(pairIndex, other.pairIndex);
}

/**
Expand Down Expand Up @@ -440,6 +454,37 @@ public StreetRouter.State traverse (StreetRouter.State s0, Mode mode, ProfileReq
float time = (float) (getLengthM() / speedms);
float weight = 0;

// add turn restrictions that start on this edge
if (turnRestrictions.containsKey(getEdgeIndex())) {
s1.inTurnRestriction = true;
}

if (s0.inTurnRestriction) {
// check if we have exited any turn restrictions, and make sure the movement is not restricted
TIntCollection currentRestrictions = turnRestrictions.get(getEdgeIndex());

// array to dodge effectively final nonsense
boolean[] blockTraversal = new boolean[] { false };
turnRestrictions.get(s0.backEdge).forEach(ridx -> {
if (currentRestrictions.contains(ridx)) return true; // we have not exited this restriction, but continue iteration

// we have exited this restriction, check to see if we should block traversal
TurnRestriction restriction = layer.turnRestrictions.get(ridx);

// first check if the restriction applies to this traversal
// we don't need to check via here, as if we ever left the via edges we would have terminated the restriction then.
boolean applies = restriction.toEdge == getEdgeIndex();

if (applies && !restriction.only || !applies && restriction.only) blockTraversal[0] = true;

// only continue iteration if we're not already blocking traversal
return !blockTraversal[0];
});

if (blockTraversal[0])
return null;
}

if (s0.backEdge != -1 && getFlag(EdgeFlag.LINK) && getCursor(s0.backEdge).getFlag(EdgeFlag.LINK))
// two link edges in a row, in other words a shortcut. Disallow this.
return null;
Expand Down
153 changes: 149 additions & 4 deletions src/main/java/com/conveyal/r5/streets/StreetLayer.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package com.conveyal.r5.streets;

import com.conveyal.gtfs.model.Stop;
import com.conveyal.osmlib.Node;
import com.conveyal.osmlib.OSM;
import com.conveyal.osmlib.Way;
import com.conveyal.osmlib.*;
import com.conveyal.r5.api.util.BikeRentalStation;
import com.conveyal.r5.common.GeometryUtils;
import com.conveyal.r5.labeling.*;
Expand Down Expand Up @@ -87,7 +85,10 @@ public class StreetLayer implements Serializable {

// Initialize these when we have an estimate of the number of expected edges.
public VertexStore vertexStore = new VertexStore(100_000);
public EdgeStore edgeStore = new EdgeStore(vertexStore, 200_000);
public EdgeStore edgeStore = new EdgeStore(vertexStore, this, 200_000);

/** Turn restrictions can potentially have a large number of affected edges, so store them once and reference them */
public List<TurnRestriction> turnRestrictions = new ArrayList<>();

transient Histogram edgesPerWayHistogram = new Histogram("Number of edges per way per direction");
transient Histogram pointsPerEdgeHistogram = new Histogram("Number of geometry points per edge");
Expand Down Expand Up @@ -244,6 +245,10 @@ void loadFromOsm (OSM osm, boolean removeIslands, boolean saveVertexIndex) {
}
LOG.info("Made {} P+R vertices", numOfParkAndRides);

// create turn restrictions.
// TODO transit splitting is going to mess this up
osm.relations.entrySet().stream().filter(e -> e.getValue().hasTag("type", "restriction")).forEach(e -> this.applyTurnRestriction(e.getKey(), e.getValue()));
LOG.info("Created {} turn restrictions", turnRestrictions.size());

//edgesPerWayHistogram.display();
//pointsPerEdgeHistogram.display();
Expand Down Expand Up @@ -420,6 +425,141 @@ private void connectParkAndRide (int centerVertex, Coordinate coord, EdgeStore.E
created.setFlag(EdgeStore.EdgeFlag.LINK);
}

private void applyTurnRestriction (long id, Relation restriction) {
boolean only;

if (!restriction.hasTag("restriction")) {
LOG.error("Restriction {} has no restriction tag, skipping", id);
return;
}

if (restriction.getTag("restriction").startsWith("no_")) only = false;
else if (restriction.getTag("restriction").startsWith("only_")) only = true;
else {
LOG.error("Restriction {} has invalid restriction tag {}, skipping", id, restriction.getTag("restriction"));
return;
}

TurnRestriction out = new TurnRestriction();
out.only = only;

// sort out the members
Relation.Member from = null, to = null;
List<Relation.Member> via = new ArrayList<>();

for (Relation.Member member : restriction.members) {
if ("from".equals(member.role)) {
if (from != null) {
LOG.error("Turn restriction {} has multiple from members, skipping", id);
return;
}

from = member;
}
else if ("to".equals(member.role)) {
if (to != null) {
LOG.error("Turn restriction {} has multiple to members, skipping", id);
return;
}

to = member;
}
else if ("via".equals(member.role)) {
via.add(member);
}
}


if (from == null || to == null || via.isEmpty()) {
LOG.error("Invalid turn restriction {}, does not have from, to and via, skipping", id);
return;
}

boolean hasWays = false, hasNodes = false;

for (Relation.Member m : via) {
if (m.type == OSMEntity.Type.WAY) hasWays = true;
else if (m.type == OSMEntity.Type.NODE) hasNodes = true;
else {
LOG.error("via must be node or way, skipping restriction {}", id);
return;
}
}

if (hasWays && hasNodes || hasNodes && via.size() > 1) {
LOG.error("via must be single node or one or more ways, skipping restriction {}", id);
return;
}

EdgeStore.Edge e = edgeStore.getCursor();

if (hasNodes) {
// via node, this is a fairly simple turn restriction. First find the relevant vertex.
int vertex = vertexIndexForOsmNode.get(via.get(0).id);

if (vertex == -1) {
LOG.warn("Vertex {} not found to use as via node for restriction {}, skipping this restriction", via.get(0).id, id);
return;
}

// use array to dodge effectively final nonsense
final int[] fromEdge = new int[] { -1 };
final long fromWayId = from.id; // more effectively final nonsense
final boolean[] bad = new boolean[] { false };

// find the edges
incomingEdges.get(vertex).forEach(eidx -> {
e.seek(eidx);
if (e.getOSMID() == fromWayId) {
if (fromEdge[0] != -1) {
LOG.error("From way enters vertex {} twice, restriction {} is therefore ambiguous, skipping", vertex, id);
bad[0] = true;
return false;
}

fromEdge[0] = eidx;
}

return true; // iteration should continue
});


final int[] toEdge = new int[] { -1 };
final long toWayId = to.id; // more effectively final nonsense
outgoingEdges.get(vertex).forEach(eidx -> {
e.seek(eidx);
if (e.getOSMID() == toWayId) {
if (toEdge[0] != -1) {
LOG.error("To way exits vertex {} twice, restriction {} is therefore ambiguous, skipping", vertex, id);
bad[0] = true;
return false;
}

toEdge[0] = eidx;
}

return true; // iteration should continue
});

if (bad[0]) return; // log message already printed

if (fromEdge[0] == -1 || toEdge[0] == -1) {
LOG.error("Did not find from/to edges for restriction {}, skipping", id);
return;
}

// phew. create the restriction and apply it where needed
out.fromEdge = fromEdge[0];
out.toEdge = toEdge[0];

int index = turnRestrictions.size();
turnRestrictions.add(out);
edgeStore.turnRestrictions.put(out.fromEdge, index);
} else {
LOG.info("Restriction {} has way(s) as via members, which is not yet supported.");
}
}

/**
* Get or create mapping from a global long OSM ID to an internal street vertex ID, creating the vertex as needed.
*/
Expand Down Expand Up @@ -690,6 +830,11 @@ public int splitEdge(Split split) {
// Copy the flags and speeds for both directions, making the new edge like the existing one.
newEdge.copyPairFlagsAndSpeeds(edge);

// clean up any turn restrictions that exist
// turn restrictions on the forward edge go to the new edge's forward edge. Turn restrictions on the back edge stay
// where they are
edgeStore.turnRestrictions.removeAll(split.edge).forEach(ridx -> edgeStore.turnRestrictions.put(newEdge.edgeIndex, ridx));

return newVertexIndex;
// TODO store street-to-stop distance in a table in TransitLayer. This also allows adjusting for subway entrances etc.
}
Expand Down
Loading

0 comments on commit 553c925

Please sign in to comment.