/* 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 <http://www.gnu.org/licenses/>. */
package org.opentripplanner.analyst.request;
import java.util.List;
import org.opentripplanner.analyst.core.GeometryIndex;
import org.opentripplanner.analyst.core.Sample;
import org.opentripplanner.analyst.core.SampleSource;
import org.opentripplanner.common.geometry.DistanceLibrary;
import org.opentripplanner.common.geometry.GeometryUtils;
import org.opentripplanner.common.geometry.SphericalDistanceLibrary;
import org.opentripplanner.routing.graph.Edge;
import org.opentripplanner.routing.graph.Vertex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.CoordinateSequence;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.LineString;
@Component
public class SampleFactory implements SampleSource {
private static DistanceLibrary distanceLibrary = SphericalDistanceLibrary.getInstance();
@Autowired
private GeometryIndex index;
private double searchRadiusM;
private double searchRadiusLat;
public SampleFactory() {
this.setSearchRadiusM(100);
}
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")
List<Edge> edges = (List<Edge>) index.queryPedestrian(env);
// look for edges and make a sample
return findClosest(edges, c, xscale);
}
/**
* 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<Edge> 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());
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
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();
double d = best.distanceTo(pt);
if (d > searchRadiusM)
return null;
double d0 = d + best.distanceAlong();
int t0 = (int) (d0 / 1.33);
double d1 = d + best.distanceToEnd();
int t1 = (int) (d1 / 1.33);
Sample s = new Sample(v0, t0, v1, t1);
//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 distanceLibrary.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 += distanceLibrary.fastDistance(y0, x0, y1, x1);
x0 = x1;
y0 = y1;
}
dist += distanceLibrary.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 = distanceLibrary.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 += distanceLibrary.fastDistance(y0, x0, y1, x1);
x0 = x1;
y0 = y1;
}
return dist;
}
}
}