org.opentripplanner.analyst.request.SampleFactory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of otp Show documentation
Show all versions of otp Show documentation
The OpenTripPlanner multimodal journey planning system
/* This program is free software: you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public License
as published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see . */
package org.opentripplanner.analyst.request;
import com.google.common.collect.Iterables;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.CoordinateSequence;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.LineString;
import gnu.trove.map.TIntDoubleMap;
import gnu.trove.map.hash.TIntDoubleHashMap;
import org.opentripplanner.analyst.core.Sample;
import org.opentripplanner.analyst.core.SampleSource;
import org.opentripplanner.common.geometry.GeometryUtils;
import org.opentripplanner.common.geometry.SphericalDistanceLibrary;
import org.opentripplanner.routing.core.TraverseMode;
import org.opentripplanner.routing.core.TraverseModeSet;
import org.opentripplanner.routing.edgetype.StreetEdge;
import org.opentripplanner.routing.graph.Edge;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.graph.Vertex;
import org.opentripplanner.routing.vertextype.OsmVertex;
import java.util.*;
public class SampleFactory implements SampleSource {
public SampleFactory(Graph graph) {
this.graph = graph;
this.setSearchRadiusM(500);
}
private Graph graph;
private double searchRadiusM;
private double searchRadiusLat;
/** When are two vertices considered equidistant and the origin should be moved slightly to avoid numerical issues? */
private final double EPSILON = 1e-10;
public void setSearchRadiusM(double radiusMeters) {
this.searchRadiusM = radiusMeters;
this.searchRadiusLat = SphericalDistanceLibrary.metersToDegrees(searchRadiusM);
}
@Override
/** implements SampleSource interface */
public Sample getSample(double lon, double lat) {
Coordinate c = new Coordinate(lon, lat);
// query always returns a (possibly empty) list, but never null
Envelope env = new Envelope(c);
// find scaling factor for equirectangular projection
double xscale = Math.cos(c.y * Math.PI / 180);
env.expandBy(searchRadiusLat / xscale, searchRadiusLat);
@SuppressWarnings("unchecked")
Collection vertices = graph.streetIndex.getVerticesForEnvelope(env);
// make sure things are in the radius
final TIntDoubleMap distances = new TIntDoubleHashMap();
for (Vertex v : vertices) {
if (!(v instanceof OsmVertex)) continue;
// figure ersatz distance
double dx = (lon - v.getLon()) * xscale;
double dy = lat - v.getLat();
distances.put(v.getIndex(), dx * dx + dy * dy);
}
List sorted = new ArrayList();
for (Vertex input : vertices) {
if (!(input instanceof OsmVertex &&
distances.get(input.getIndex()) < searchRadiusLat * searchRadiusLat))
continue;
for (StreetEdge e : Iterables.filter(input.getOutgoing(), StreetEdge.class)) {
if (e.canTraverse(new TraverseModeSet(TraverseMode.WALK))) {
sorted.add(input);
break;
}
}
}
// sort list by distance
Collections.sort(sorted, new Comparator() {
@Override
public int compare(Vertex o1, Vertex o2) {
double d1 = distances.get(o1.getIndex());
double d2 = distances.get(o2.getIndex());
if (d1 < d2)
return -1;
else if (d1 > d2)
return 1;
else return 0;
}
});
Vertex v0, v1;
if (sorted.isEmpty())
return null;
else if (sorted.size() <= 2) {
v0 = sorted.get(0);
v1 = sorted.size() > 1 ? sorted.get(1) : null;
}
else {
int vxi = 0;
// Group them by distance
Vertex[] vx = new Vertex[2];
ArrayList grouped = new ArrayList<>();
// here's the idea: accumulate vertices by distance, waiting until we find a gap
// of at least EPSILON. Once we've done that, break ties using labels (which are OSM IDs).
for (int i = 0; i < sorted.size(); i++) {
if (vxi >= 2) break;
if (grouped.isEmpty()) {
grouped.add(sorted.get(i));
continue;
}
double dlast = distances.get(sorted.get(i - 1).getIndex());
double dthis = distances.get(sorted.get(i).getIndex());
if (dthis - dlast < EPSILON) {
grouped.add(sorted.get(i));
continue;
}
else {
// we have a distinct group of vertices
// sort them by OSM IDs
// this seems like it would be slow but keep in mind that it will only do any work
// when there are multiple members of a group, which is relatively rare.
Collections.sort(grouped, (vv1, vv2) -> vv1.getLabel().compareTo(vv2.getLabel()));
// then loop over the list until it's empty or we've found two vertices
int gi = 0;
while (vxi < 2 && gi < grouped.size()) {
vx[vxi++] = grouped.get(gi++);
}
// get ready for the next group
grouped.clear();
}
}
v0 = vx[0];
v1 = vx[1];
}
double d0 = v0 != null ? SphericalDistanceLibrary.distance(v0.getLat(), v0.getLon(), lat, lon) : 0;
double d1 = v1 != null ? SphericalDistanceLibrary.distance(v1.getLat(), v1.getLon(), lat, lon) : 0;
return new Sample(v0, (int) d0, v1, (int) d1);
}
/**
* DistanceToPoint.computeDistance() uses a LineSegment, which has a closestPoint method.
* That finds the true distance every time rather than once the closest segment is known,
* and does not allow for equi-rectangular projection/scaling.
*
* Here we want to compare squared distances to all line segments until we find the best one,
* then do the precise calculations.
*
*/
public Sample findClosest(List edges, Coordinate pt, double xscale) {
Candidate c = new Candidate();
// track the best geometry
Candidate best = new Candidate();
for (Edge edge : edges) {
/* LineString.getCoordinates() uses PackedCoordinateSequence.toCoordinateArray() which
* necessarily builds new Coordinate objects.CoordinateSequence.getOrdinate() reads them
* directly. */
c.edge = edge;
LineString ls = (LineString)(edge.getGeometry());
// We used to require samples to link to OSM vertices, but that means that
// walking to a transit stop adjacent to the sample requires walking to the
// end of the street and back.
// However, linking to splitter vertices means that this sample can only be egressed in one direction,
// because the splitter vertex is only on one half of a bidirectional edge pair. Additionally, the splitter
// vertices and the connected edges for the two directions are exactly coincident, which means that which
// one of the two a sample gets linked to is effectively random.
if (!edge.getFromVertex().getLabel().startsWith("osm:node:") || (edge instanceof StreetEdge && ((StreetEdge) edge).isBack()))
continue;
CoordinateSequence coordSeq = ls.getCoordinateSequence();
int numCoords = coordSeq.size();
for (int seg = 0; seg < numCoords - 1; seg++) {
c.seg = seg;
double x0 = coordSeq.getX(seg);
double y0 = coordSeq.getY(seg);
double x1 = coordSeq.getX(seg+1);
double y1 = coordSeq.getY(seg+1);
// use bounding rectangle to find a lower bound on (squared) distance ?
// this would mean more squaring or roots.
c.frac = GeometryUtils.segmentFraction(x0, y0, x1, y1, pt.x, pt.y, xscale);
// project to get closest point
// note: no need to multiply anything by xscale; the fraction is scaleless.
c.x = x0 + c.frac * (x1 - x0);
c.y = y0 + c.frac * (y1 - y0);
// find ersatz distance to edge (do not take root)
double dx = (c.x - pt.x) * xscale;
double dy = c.y - pt.y;
c.dist2 = dx * dx + dy * dy;
// replace best segments
if (c.dist2 < best.dist2) {
best.setFrom(c);
}
} // end loop over segments
} // end loop over linestrings
// if at least one vertex was found make a sample
if (best.edge != null) {
Vertex v0 = best.edge.getFromVertex();
//Vertex v1 = best.edge.getToVertex();
Vertex v1 = v0;
double d = best.distanceTo(pt);
if (d > searchRadiusM)
return null;
double d0 = d + best.distanceAlong();
//double d1 = d + best.distanceToEnd();
double d1 = d0;
Sample s = new Sample(v0, (int) d0, v1, (int) d1);
//System.out.println(s.toString());
return s;
}
return null;
}
private static class Candidate {
double dist2 = Double.POSITIVE_INFINITY;
Edge edge = null;
int seg = 0;
double frac = 0;
double x;
double y;
public void setFrom(Candidate other) {
dist2 = other.dist2;
edge = other.edge;
seg = other.seg;
frac = other.frac;
x = other.x;
y = other.y;
}
public double distanceTo(Coordinate c) {
return SphericalDistanceLibrary.fastDistance(y, x, c.y, c.x);
}
public double distanceAlong() {
CoordinateSequence cs = ( (LineString)(edge.getGeometry()) ).getCoordinateSequence();
double dist = 0;
double x0 = cs.getX(0);
double y0 = cs.getY(0);
for (int s = 1; s < seg; s++) {
double x1 = cs.getX(s);
double y1 = cs.getY(s);
dist += SphericalDistanceLibrary.fastDistance(y0, x0, y1, x1);
x0 = x1;
y0 = y1;
}
dist += SphericalDistanceLibrary.fastDistance(y0, x0, y, x); // dist along partial segment
return dist;
}
public double distanceToEnd() {
CoordinateSequence cs = ( (LineString)(edge.getGeometry()) ).getCoordinateSequence();
int s = seg + 1;
double x0 = cs.getX(s);
double y0 = cs.getY(s);
double dist = SphericalDistanceLibrary.fastDistance(y0, x0, y, x); // dist along partial segment
int nc = cs.size();
for (; s < nc; s++) {
double x1 = cs.getX(s);
double y1 = cs.getY(s);
dist += SphericalDistanceLibrary.fastDistance(y0, x0, y1, x1);
x0 = x1;
y0 = y1;
}
return dist;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy