All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opentripplanner.analyst.request.SampleFactory Maven / Gradle / Ivy

There is a newer version: 2.6.0
Show newest version
/* 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