package org.opentripplanner.routing.impl;
import java.util.Comparator;
import lombok.Getter;
import org.opentripplanner.common.geometry.DirectionUtils;
import org.opentripplanner.common.geometry.GeometryUtils;
import org.opentripplanner.common.geometry.SphericalDistanceLibrary;
import org.opentripplanner.common.model.GenericLocation;
import org.opentripplanner.routing.core.TraverseModeSet;
import org.opentripplanner.routing.edgetype.StreetEdge;
import org.opentripplanner.routing.edgetype.StreetTraversalPermission;
import org.opentripplanner.routing.vertextype.StreetVertex;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.CoordinateSequence;
import com.vividsolutions.jts.geom.LineString;
public class CandidateEdge {
private static final double PLATFORM_PREFERENCE = 2.0;
private static final double SIDEWALK_PREFERENCE = 1.5;
// Massive preference for streets that allow cars, also applied to platforms for vehicles of
// the specified mode.
private static final double CAR_PREFERENCE = 100;
private static final double MAX_DIRECTION_DIFFERENCE = 180.0;
private static final double MAX_ABS_DIRECTION_DIFFERENCE = 360.0;
/** The edge being considered for linking. */
@Getter
protected final StreetEdge edge;
/** Pointer to the geometry (coordinate sequence) of the edge. */
private final CoordinateSequence edgeCoords;
/** Number of coordinates on the edge. */
private final int numEdgeCoords;
/** Whether point is located at a platform. */
private final int platform;
/** Preference value passed in. */
private final double preference;
/** Index of the closest segment along the edge. */
private int nearestSegmentIndex;
/** Fractional distance along the closest segment. */
private double nearestSegmentFraction;
/**
* Set when to the closest endpoint of the edge when the input location is really sitting on
* that endpoint (within some tolerance).
*/
@Getter
protected StreetVertex endwiseVertex;
/** The coordinate of the nearest point on the edge to the linking location. */
@Getter
protected Coordinate nearestPointOnEdge;
/** Heading if given. */
@Getter
protected Double heading;
/** Azimuth between input point and closest point on edge. */
@Getter
protected double directionToEdge;
/** Azimuth of the subsegment of the edge to which the input point is closest. */
@Getter
protected double directionOfEdge;
/** Difference in direction between heading and nearest subsegment of edge. Null if no heading given. */
@Getter
protected Double directionDifference;
/** Distance from edge to linking point. */
@Getter
protected double distance;
/** Score of the match. Lower is better. */
@Getter
protected double score;
/** Sorts CandidateEdges by best score first (lower = better). */
public static class CandidateEdgeScoreComparator implements Comparator<CandidateEdge> {
@Override
public int compare(CandidateEdge arg0, CandidateEdge arg1) {
double score1 = arg0.getScore();
double score2 = arg1.getScore();
if (score1 == score2) {
return 0;
} else if (score1 < score2) {
return -1;
}
return 1;
}
}
/**
* Construct CandidateEdge based on a GenericLocation.
* The edge's score is calculated as the final step of construction.
*/
public CandidateEdge(StreetEdge e, GenericLocation loc, double pref, TraverseModeSet mode) {
preference = pref;
edge = e;
edgeCoords = e.getGeometry().getCoordinateSequence();
numEdgeCoords = edgeCoords.size();
platform = calcPlatform(mode);
nearestPointOnEdge = new Coordinate();
// Initializes nearestPointOnEdge, nearestSegmentIndex,
// nearestSegmentFraction.
distance = calcNearestPoint(loc.getCoordinate());
// Calculates the endwise vertex as appropriate.
endwiseVertex = calcEndwiseVertex();
// Calculate the directional info.
int edgeSegmentIndex = nearestSegmentIndex;
Coordinate c0 = edgeCoords.getCoordinate(edgeSegmentIndex);
Coordinate c1 = edgeCoords.getCoordinate(edgeSegmentIndex + 1);
directionOfEdge = DirectionUtils.getAzimuth(c0, c1);
directionToEdge = DirectionUtils.getAzimuth(nearestPointOnEdge, loc.getCoordinate());
// Calculates the direction differently depending on whether a heading
// is supplied.
heading = loc.getHeading();
if (heading != null) {
double absDiff = Math.abs(heading - directionOfEdge);
directionDifference = Math.min(MAX_ABS_DIRECTION_DIFFERENCE - absDiff, absDiff);
}
// Calculate the score last so it can use all other data.
score = calcScore();
}
/** Construct CandidateEdge based on a Coordinate. */
public CandidateEdge(StreetEdge e, Coordinate p, double pref, TraverseModeSet mode) {
this(e, new GenericLocation(p), pref, mode);
}
public boolean endwise() {
return endwiseVertex != null;
}
public String toString() {
return String
.format("CandidateEdge<edge=\"%s\" score=\"%f\" heading=\"%s\" directionDifference=\"%s\" nearestPoint=\"%s\">",
edge, score, heading, directionDifference, nearestPointOnEdge);
}
/* PRIVATE METHODS */
/** Initializes this.nearestPointOnEdge and other distance-related variables. */
private double calcNearestPoint(Coordinate p) {
LineString edgeGeom = edge.getGeometry();
CoordinateSequence coordSeq = edgeGeom.getCoordinateSequence();
int bestSeg = 0;
double bestDist2 = Double.POSITIVE_INFINITY;
double bestFrac = 0;
double xscale = Math.cos(p.y * Math.PI / 180);
for (int seg = 0; seg < numEdgeCoords - 1; seg++) {
double x0 = coordSeq.getX(seg);
double y0 = coordSeq.getY(seg);
double x1 = coordSeq.getX(seg + 1);
double y1 = coordSeq.getY(seg + 1);
double frac = GeometryUtils.segmentFraction(x0, y0, x1, y1, p.x, p.y, xscale);
// project to get closest point
double x = x0 + frac * (x1 - x0);
double y = y0 + frac * (y1 - y0);
// find ersatz distance to edge (do not take root)
double dx = (x - p.x) * xscale;
double dy = y - p.y;
double dist2 = dx * dx + dy * dy;
// replace best segments
if (dist2 < bestDist2) {
nearestPointOnEdge.x = x;
nearestPointOnEdge.y = y;
bestFrac = frac;
bestSeg = seg;
bestDist2 = dist2;
}
} // end loop over segments
nearestSegmentIndex = bestSeg;
nearestSegmentFraction = bestFrac;
return Math.sqrt(bestDist2); // distanceLibrary.distance(p,
// nearestPointOnEdge);
}
/** Calculates the endwiseVertex if appropriate. */
private StreetVertex calcEndwiseVertex() {
StreetVertex retV = null;
if (nearestSegmentIndex == 0 && Math.abs(nearestSegmentFraction) < 0.000001) {
retV = (StreetVertex) edge.getFromVertex();
} else if (nearestSegmentIndex == numEdgeCoords - 2
&& Math.abs(nearestSegmentFraction - 1.0) < 0.000001) {
retV = (StreetVertex) edge.getToVertex();
}
return retV;
}
/**
* Get the platform mask for the given mode.
* This is compatible with the bit flags from StreetEdge.getStreetClass().
*/
private int calcPlatform(TraverseModeSet mode) {
int out = 0;
if (mode.getTrainish()) {
out |= StreetEdge.CLASS_TRAIN_PLATFORM;
}
if (mode.getBusish() ) {
// includes CABLE_CAR
out |= StreetEdge.CLASS_OTHER_PLATFORM;
}
return out;
}
/** Internal calculator for the score. Assumes that edge, platform and distance are initialized. */
private double calcScore() {
double myScore = 0;
// why is this being scaled by 1/360th of the radius of the earth?
myScore = distance * SphericalDistanceLibrary.RADIUS_OF_EARTH_IN_M / 360.0;
myScore /= preference;
if ((edge.getStreetClass() & platform) != 0) {
// this a hack, but there's not really a better way to do it
myScore /= PLATFORM_PREFERENCE;
}
if (edge.getName().contains("sidewalk")) {
// this is a hack, but there's not really a better way to do it
myScore /= SIDEWALK_PREFERENCE;
}
// apply strong preference to car edges and to platforms for the specified modes
if (edge.getPermission().allows(StreetTraversalPermission.CAR)
|| (edge.getStreetClass() & platform) != 0) {
// we're subtracting here because no matter how close we are to a
// good non-car non-platform edge, we really want to avoid it in
// case it's a Pedway or other weird and unlikely starting location.
myScore -= CAR_PREFERENCE;
}
// Consider the heading in the score if it is available.
if (heading != null) {
// If you are moving along the edge, score is not penalized.
// If you are moving against the edge, score is penalized by 1.
myScore += (directionDifference / MAX_DIRECTION_DIFFERENCE);
}
// break ties by choosing shorter edges; this should cause split streets
// to be preferred
myScore += edge.getLength() / 1000000;
return myScore;
}
}