use of org.opentripplanner.common.model.P2 in project OpenTripPlanner by opentripplanner.
the class GeometryUtils method splitGeometryAtPoint.
/**
* Splits the input geometry into two LineStrings at the given point.
*/
public static P2<LineString> splitGeometryAtPoint(Geometry geometry, Coordinate nearestPoint) {
// An index in JTS can actually refer to any point along the line. It is NOT an array index.
LocationIndexedLine line = new LocationIndexedLine(geometry);
LinearLocation l = line.indexOf(nearestPoint);
LineString beginning = (LineString) line.extractLine(line.getStartIndex(), l);
LineString ending = (LineString) line.extractLine(l, line.getEndIndex());
return new P2<LineString>(beginning, ending);
}
use of org.opentripplanner.common.model.P2 in project OpenTripPlanner by opentripplanner.
the class GTFSPatternHopFactory method interline.
/**
* Identify interlined trips (where a physical vehicle continues on to another logical trip)
* and update the TripPatterns accordingly. This must be called after all the pattern edges and vertices
* are already created, because it creates interline dwell edges between existing pattern arrive/depart vertices.
*/
private void interline(Collection<TripPattern> tripPatterns, Graph graph) {
/* Record which Pattern each interlined TripTimes belongs to. */
Map<TripTimes, TripPattern> patternForTripTimes = Maps.newHashMap();
/* TripTimes grouped by the block ID and service ID of their trips. Must be a ListMultimap to allow sorting. */
ListMultimap<BlockIdAndServiceId, TripTimes> tripTimesForBlock = ArrayListMultimap.create();
LOG.info("Finding interlining trips based on block IDs.");
for (TripPattern pattern : tripPatterns) {
Timetable timetable = pattern.scheduledTimetable;
/* TODO: Block semantics seem undefined for frequency trips, so skip them? */
for (TripTimes tripTimes : timetable.tripTimes) {
Trip trip = tripTimes.trip;
if (!Strings.isNullOrEmpty(trip.getBlockId())) {
tripTimesForBlock.put(new BlockIdAndServiceId(trip), tripTimes);
// For space efficiency, only record times that are part of a block.
patternForTripTimes.put(tripTimes, pattern);
}
}
}
/* Associate pairs of TripPatterns with lists of trips that continue from one pattern to the other. */
Multimap<P2<TripPattern>, P2<Trip>> interlines = ArrayListMultimap.create();
/*
Sort trips within each block by first departure time, then iterate over trips in this block and service,
linking them. Has no effect on single-trip blocks.
*/
SERVICE_BLOCK: for (BlockIdAndServiceId block : tripTimesForBlock.keySet()) {
List<TripTimes> blockTripTimes = tripTimesForBlock.get(block);
Collections.sort(blockTripTimes);
TripTimes prev = null;
for (TripTimes curr : blockTripTimes) {
if (prev != null) {
if (prev.getDepartureTime(prev.getNumStops() - 1) > curr.getArrivalTime(0)) {
LOG.error("Trip times within block {} are not increasing on service {} after trip {}.", block.blockId, block.serviceId, prev.trip.getId());
continue SERVICE_BLOCK;
}
TripPattern prevPattern = patternForTripTimes.get(prev);
TripPattern currPattern = patternForTripTimes.get(curr);
Stop fromStop = prevPattern.getStop(prevPattern.getStops().size() - 1);
Stop toStop = currPattern.getStop(0);
double teleportationDistance = SphericalDistanceLibrary.fastDistance(fromStop.getLat(), fromStop.getLon(), toStop.getLat(), toStop.getLon());
if (teleportationDistance > maxInterlineDistance) {
// FIXME Trimet data contains a lot of these -- in their data, two trips sharing a block ID just
// means that they are served by the same vehicle, not that interlining is automatically allowed.
// see #1654
// LOG.error(graph.addBuilderAnnotation(new InterliningTeleport(prev.trip, block.blockId, (int)teleportationDistance)));
// Only skip this particular interline edge; there may be other valid ones in the block.
} else {
interlines.put(new P2<TripPattern>(prevPattern, currPattern), new P2<Trip>(prev.trip, curr.trip));
}
}
prev = curr;
}
}
/*
Create the PatternInterlineDwell edges linking together TripPatterns.
All the pattern vertices and edges must already have been created.
*/
for (P2<TripPattern> patterns : interlines.keySet()) {
TripPattern prevPattern = patterns.first;
TripPattern nextPattern = patterns.second;
// This is a single (uni-directional) edge which may be traversed forward and backward.
PatternInterlineDwell edge = new PatternInterlineDwell(prevPattern, nextPattern);
for (P2<Trip> trips : interlines.get(patterns)) {
edge.add(trips.first, trips.second);
}
}
LOG.info("Done finding interlining trips and creating the corresponding edges.");
}
use of org.opentripplanner.common.model.P2 in project OpenTripPlanner by opentripplanner.
the class GraphIndex method initializeProfileTransfers.
/**
* Initialize transfer data needed for profile routing.
* Find the best transfers between each pair of patterns that pass near one another.
*/
public void initializeProfileTransfers() {
transfersFromStopCluster = HashMultimap.create();
// meters
final double TRANSFER_RADIUS = 500.0;
Map<P2<TripPattern>, ProfileTransfer.GoodTransferList> transfers = Maps.newHashMap();
LOG.info("Finding transfers between clusters...");
for (StopCluster sc0 : stopClusterForId.values()) {
Set<TripPattern> tripPatterns0 = patternsForStopCluster(sc0);
// Accounts for area-like (rather than point-like) nature of clusters
Map<StopCluster, Double> nearbyStopClusters = findNearbyStopClusters(sc0, TRANSFER_RADIUS);
for (StopCluster sc1 : nearbyStopClusters.keySet()) {
double distance = nearbyStopClusters.get(sc1);
Set<TripPattern> tripPatterns1 = patternsForStopCluster(sc1);
for (TripPattern tp0 : tripPatterns0) {
for (TripPattern tp1 : tripPatterns1) {
if (tp0 == tp1)
continue;
P2<TripPattern> pair = new P2<TripPattern>(tp0, tp1);
ProfileTransfer.GoodTransferList list = transfers.get(pair);
if (list == null) {
list = new ProfileTransfer.GoodTransferList();
transfers.put(pair, list);
}
list.add(new ProfileTransfer(tp0, tp1, sc0, sc1, (int) distance));
}
}
}
}
/* Now filter the transfers down to eliminate long series of transfers in shared trunks. */
LOG.info("Filtering out long series of transfers on trunks shared between patterns.");
for (P2<TripPattern> pair : transfers.keySet()) {
ProfileTransfer.GoodTransferList list = transfers.get(pair);
// TODO consider using second (think of express-local transfers in NYC)
TripPattern fromPattern = pair.first;
Map<StopCluster, ProfileTransfer> transfersByFromCluster = Maps.newHashMap();
for (ProfileTransfer transfer : list.good) {
transfersByFromCluster.put(transfer.sc1, transfer);
}
List<ProfileTransfer> retainedTransfers = Lists.newArrayList();
// true whenever a transfer existed for the last stop in the stop pattern
boolean inSeries = false;
for (Stop stop : fromPattern.stopPattern.stops) {
StopCluster cluster = this.stopClusterForStop.get(stop);
// LOG.info("stop {} cluster {}", stop, cluster.id);
ProfileTransfer transfer = transfersByFromCluster.get(cluster);
if (transfer == null) {
inSeries = false;
continue;
}
if (inSeries)
continue;
// Keep this transfer: it's not preceded by another stop with a transfer in this stop pattern
retainedTransfers.add(transfer);
inSeries = true;
}
// LOG.info("patterns {}, {} transfers", pair, retainedTransfers.size());
for (ProfileTransfer tr : retainedTransfers) {
transfersFromStopCluster.put(tr.sc1, tr);
// LOG.info(" {}", tr);
}
}
/*
* for (Stop stop : transfersForStop.keys()) { System.out.println("STOP " + stop); for
* (Transfer transfer : transfersForStop.get(stop)) { System.out.println(" " +
* transfer.toString()); } }
*/
LOG.info("Done finding transfers.");
}
use of org.opentripplanner.common.model.P2 in project OpenTripPlanner by opentripplanner.
the class GraphPathToTripPlanConverter method generateWalkSteps.
/**
* Converts a list of street edges to a list of turn-by-turn directions.
*
* @param previous a non-transit leg that immediately precedes this one (bike-walking, say), or null
*
* @return
*/
public static List<WalkStep> generateWalkSteps(Graph graph, State[] states, WalkStep previous, Locale requestedLocale) {
List<WalkStep> steps = new ArrayList<WalkStep>();
WalkStep step = null;
// distance used for appending elevation profiles
double lastAngle = 0, distance = 0;
// track whether we are in a roundabout, and if so the exit number
int roundaboutExit = 0;
String roundaboutPreviousStreet = null;
State onBikeRentalState = null, offBikeRentalState = null;
// Check if this leg is a SimpleTransfer; if so, rebuild state array based on stored transfer edges
if (states.length == 2 && states[1].getBackEdge() instanceof SimpleTransfer) {
SimpleTransfer transferEdge = ((SimpleTransfer) states[1].getBackEdge());
List<Edge> transferEdges = transferEdge.getEdges();
if (transferEdges != null) {
// Create a new initial state. Some parameters may have change along the way, copy them from the first state
StateEditor se = new StateEditor(states[0].getOptions(), transferEdges.get(0).getFromVertex());
se.setNonTransitOptionsFromState(states[0]);
State s = se.makeState();
ArrayList<State> transferStates = new ArrayList<>();
transferStates.add(s);
for (Edge e : transferEdges) {
s = e.traverse(s);
transferStates.add(s);
}
states = transferStates.toArray(new State[transferStates.size()]);
}
}
for (int i = 0; i < states.length - 1; i++) {
State backState = states[i];
State forwardState = states[i + 1];
Edge edge = forwardState.getBackEdge();
if (edge instanceof RentABikeOnEdge)
onBikeRentalState = forwardState;
if (edge instanceof RentABikeOffEdge)
offBikeRentalState = forwardState;
boolean createdNewStep = false, disableZagRemovalForThisStep = false;
if (edge instanceof FreeEdge) {
continue;
}
if (forwardState.getBackMode() == null || !forwardState.getBackMode().isOnStreetNonTransit()) {
// ignore STLs and the like
continue;
}
Geometry geom = edge.getGeometry();
if (geom == null) {
continue;
}
// before or will come after
if (edge instanceof ElevatorAlightEdge) {
// don't care what came before or comes after
step = createWalkStep(graph, forwardState, requestedLocale);
createdNewStep = true;
disableZagRemovalForThisStep = true;
// tell the user where to get off the elevator using the exit notation, so the
// i18n interface will say 'Elevator to <exit>'
// what happens is that the webapp sees name == null and ignores that, and it sees
// exit != null and uses to <exit>
// the floor name is the AlightEdge name
// reset to avoid confusion with 'Elevator on floor 1 to floor 1'
step.streetName = ((ElevatorAlightEdge) edge).getName(requestedLocale);
step.relativeDirection = RelativeDirection.ELEVATOR;
steps.add(step);
continue;
}
String streetName = edge.getName(requestedLocale);
int idx = streetName.indexOf('(');
String streetNameNoParens;
if (idx > 0)
streetNameNoParens = streetName.substring(0, idx - 1);
else
streetNameNoParens = streetName;
if (step == null) {
// first step
step = createWalkStep(graph, forwardState, requestedLocale);
createdNewStep = true;
steps.add(step);
double thisAngle = DirectionUtils.getFirstAngle(geom);
if (previous == null) {
step.setAbsoluteDirection(thisAngle);
step.relativeDirection = RelativeDirection.DEPART;
} else {
step.setDirections(previous.angle, thisAngle, false);
}
// new step, set distance to length of first edge
distance = edge.getDistance();
} else if (((step.streetName != null && !step.streetNameNoParens().equals(streetNameNoParens)) && (!step.bogusName || !edge.hasBogusName())) || // went on to or off of a roundabout
edge.isRoundabout() != (roundaboutExit > 0) || isLink(edge) && !isLink(backState.getBackEdge())) {
// Street name has changed, or we've gone on to or off of a roundabout.
if (roundaboutExit > 0) {
// if we were just on a roundabout,
// make note of which exit was taken in the existing step
// ordinal numbers from
step.exit = Integer.toString(roundaboutExit);
if (streetNameNoParens.equals(roundaboutPreviousStreet)) {
step.stayOn = true;
}
roundaboutExit = 0;
}
/* start a new step */
step = createWalkStep(graph, forwardState, requestedLocale);
createdNewStep = true;
steps.add(step);
if (edge.isRoundabout()) {
// indicate that we are now on a roundabout
// and use one-based exit numbering
roundaboutExit = 1;
roundaboutPreviousStreet = backState.getBackEdge().getName(requestedLocale);
idx = roundaboutPreviousStreet.indexOf('(');
if (idx > 0)
roundaboutPreviousStreet = roundaboutPreviousStreet.substring(0, idx - 1);
}
double thisAngle = DirectionUtils.getFirstAngle(geom);
step.setDirections(lastAngle, thisAngle, edge.isRoundabout());
// new step, set distance to length of first edge
distance = edge.getDistance();
} else {
/* street name has not changed */
double thisAngle = DirectionUtils.getFirstAngle(geom);
RelativeDirection direction = WalkStep.getRelativeDirection(lastAngle, thisAngle, edge.isRoundabout());
boolean optionsBefore = backState.multipleOptionsBefore();
if (edge.isRoundabout()) {
// we are on a roundabout, and have already traversed at least one edge of it.
if (optionsBefore) {
// increment exit count if we passed one.
roundaboutExit += 1;
}
}
if (edge.isRoundabout() || direction == RelativeDirection.CONTINUE) {
// we are continuing almost straight, or continuing along a roundabout.
// just append elevation info onto the existing step.
} else {
// we are not on a roundabout, and not continuing straight through.
// figure out if there were other plausible turn options at the last
// intersection
// to see if we should generate a "left to continue" instruction.
boolean shouldGenerateContinue = false;
if (edge instanceof StreetEdge) {
// the next edges will be PlainStreetEdges, we hope
double angleDiff = getAbsoluteAngleDiff(thisAngle, lastAngle);
for (Edge alternative : backState.getVertex().getOutgoingStreetEdges()) {
if (alternative.getName(requestedLocale).equals(streetName)) {
// are usually caused by street splits
continue;
}
double altAngle = DirectionUtils.getFirstAngle(alternative.getGeometry());
double altAngleDiff = getAbsoluteAngleDiff(altAngle, lastAngle);
if (angleDiff > Math.PI / 4 || altAngleDiff - angleDiff < Math.PI / 16) {
shouldGenerateContinue = true;
break;
}
}
} else {
double angleDiff = getAbsoluteAngleDiff(lastAngle, thisAngle);
// FIXME: this code might be wrong with the removal of the edge-based graph
State twoStatesBack = backState.getBackState();
Vertex backVertex = twoStatesBack.getVertex();
for (Edge alternative : backVertex.getOutgoingStreetEdges()) {
List<Edge> alternatives = alternative.getToVertex().getOutgoingStreetEdges();
if (alternatives.size() == 0) {
// this is not an alternative
continue;
}
alternative = alternatives.get(0);
if (alternative.getName(requestedLocale).equals(streetName)) {
// are usually caused by street splits
continue;
}
double altAngle = DirectionUtils.getFirstAngle(alternative.getGeometry());
double altAngleDiff = getAbsoluteAngleDiff(altAngle, lastAngle);
if (angleDiff > Math.PI / 4 || altAngleDiff - angleDiff < Math.PI / 16) {
shouldGenerateContinue = true;
break;
}
}
}
if (shouldGenerateContinue) {
// turn to stay on same-named street
step = createWalkStep(graph, forwardState, requestedLocale);
createdNewStep = true;
steps.add(step);
step.setDirections(lastAngle, thisAngle, false);
step.stayOn = true;
// new step, set distance to length of first edge
distance = edge.getDistance();
}
}
}
State exitState = backState;
Edge exitEdge = exitState.getBackEdge();
while (exitEdge instanceof FreeEdge) {
exitState = exitState.getBackState();
exitEdge = exitState.getBackEdge();
}
if (exitState.getVertex() instanceof ExitVertex) {
step.exit = ((ExitVertex) exitState.getVertex()).getExitName();
}
if (createdNewStep && !disableZagRemovalForThisStep && forwardState.getBackMode() == backState.getBackMode()) {
// check last three steps for zag
int last = steps.size() - 1;
if (last >= 2) {
WalkStep threeBack = steps.get(last - 2);
WalkStep twoBack = steps.get(last - 1);
WalkStep lastStep = steps.get(last);
if (twoBack.distance < MAX_ZAG_DISTANCE && lastStep.streetNameNoParens().equals(threeBack.streetNameNoParens())) {
if (((lastStep.relativeDirection == RelativeDirection.RIGHT || lastStep.relativeDirection == RelativeDirection.HARD_RIGHT) && (twoBack.relativeDirection == RelativeDirection.RIGHT || twoBack.relativeDirection == RelativeDirection.HARD_RIGHT)) || ((lastStep.relativeDirection == RelativeDirection.LEFT || lastStep.relativeDirection == RelativeDirection.HARD_LEFT) && (twoBack.relativeDirection == RelativeDirection.LEFT || twoBack.relativeDirection == RelativeDirection.HARD_LEFT))) {
// in this case, we have two left turns or two right turns in quick
// succession; this is probably a U-turn.
steps.remove(last - 1);
lastStep.distance += twoBack.distance;
// A U-turn to the left, typical in the US.
if (lastStep.relativeDirection == RelativeDirection.LEFT || lastStep.relativeDirection == RelativeDirection.HARD_LEFT)
lastStep.relativeDirection = RelativeDirection.UTURN_LEFT;
else
lastStep.relativeDirection = RelativeDirection.UTURN_RIGHT;
// in this case, we're definitely staying on the same street
// (since it's zag removal, the street names are the same)
lastStep.stayOn = true;
} else {
// What is a zag? TODO write meaningful documentation for this.
// It appears to mean simplifying out several rapid turns in succession
// from the description.
// total hack to remove zags.
steps.remove(last);
steps.remove(last - 1);
step = threeBack;
step.distance += twoBack.distance;
distance += step.distance;
if (twoBack.elevation != null) {
if (step.elevation == null) {
step.elevation = twoBack.elevation;
} else {
for (P2<Double> d : twoBack.elevation) {
step.elevation.add(new P2<Double>(d.first + step.distance, d.second));
}
}
}
}
}
}
} else {
if (!createdNewStep && step.elevation != null) {
List<P2<Double>> s = encodeElevationProfile(edge, distance, backState.getOptions().geoidElevation ? -graph.ellipsoidToGeoidDifference : 0);
if (step.elevation != null && step.elevation.size() > 0) {
step.elevation.addAll(s);
} else {
step.elevation = s;
}
}
distance += edge.getDistance();
}
// increment the total length for this step
step.distance += edge.getDistance();
step.addAlerts(graph.streetNotesService.getNotes(forwardState), requestedLocale);
lastAngle = DirectionUtils.getLastAngle(geom);
step.edges.add(edge);
}
// add bike rental information if applicable
if (onBikeRentalState != null && !steps.isEmpty()) {
steps.get(steps.size() - 1).bikeRentalOnStation = new BikeRentalStationInfo((BikeRentalStationVertex) onBikeRentalState.getBackEdge().getToVertex());
}
if (offBikeRentalState != null && !steps.isEmpty()) {
steps.get(0).bikeRentalOffStation = new BikeRentalStationInfo((BikeRentalStationVertex) offBikeRentalState.getBackEdge().getFromVertex());
}
return steps;
}
use of org.opentripplanner.common.model.P2 in project OpenTripPlanner by opentripplanner.
the class WalkableAreaBuilder method createEdgesForRingSegment.
private void createEdgesForRingSegment(Set<Edge> edges, AreaEdgeList edgeList, Area area, Ring ring, int i, HashSet<P2<OSMNode>> alreadyAddedEdges) {
OSMNode node = ring.nodes.get(i);
OSMNode nextNode = ring.nodes.get((i + 1) % ring.nodes.size());
P2<OSMNode> nodePair = new P2<OSMNode>(node, nextNode);
if (alreadyAddedEdges.contains(nodePair)) {
return;
}
alreadyAddedEdges.add(nodePair);
IntersectionVertex startEndpoint = __handler.getVertexForOsmNode(node, area.parent);
IntersectionVertex endEndpoint = __handler.getVertexForOsmNode(nextNode, area.parent);
createSegments(node, nextNode, startEndpoint, endEndpoint, Arrays.asList(area), edgeList, edges);
}
Aggregations