Skip to content

Commit

Permalink
Profile-analyst timesurface and samplegrid changes
Browse files Browse the repository at this point in the history
Method to make a samplegrid when you have no SPT
Add description field to timesurfaces
Return a set of three timesurfaces bundled together
  • Loading branch information
abyrd committed Jan 5, 2015
1 parent 26f4885 commit c07f208
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 34 deletions.
73 changes: 66 additions & 7 deletions src/main/java/org/opentripplanner/analyst/TimeSurface.java
@@ -1,13 +1,13 @@
package org.opentripplanner.analyst; package org.opentripplanner.analyst;


import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Coordinate;
import gnu.trove.iterator.TObjectIntIterator;
import gnu.trove.map.TObjectIntMap; import gnu.trove.map.TObjectIntMap;
import gnu.trove.map.hash.TObjectIntHashMap; import gnu.trove.map.hash.TObjectIntHashMap;
import org.apache.commons.math3.util.FastMath; import org.apache.commons.math3.util.FastMath;
import org.opentripplanner.analyst.request.SampleGridRenderer; import org.opentripplanner.analyst.request.SampleGridRenderer;
import org.opentripplanner.analyst.request.SampleGridRenderer.WTWD; import org.opentripplanner.analyst.request.SampleGridRenderer.WTWD;
import org.opentripplanner.common.geometry.SparseMatrixZSampleGrid; import org.opentripplanner.common.geometry.*;
import org.opentripplanner.common.geometry.SphericalDistanceLibrary;
import org.opentripplanner.common.model.GenericLocation; import org.opentripplanner.common.model.GenericLocation;
import org.opentripplanner.profile.AnalystProfileRouterPrototype; import org.opentripplanner.profile.AnalystProfileRouterPrototype;
import org.opentripplanner.profile.ProfileRequest; import org.opentripplanner.profile.ProfileRequest;
Expand All @@ -21,6 +21,7 @@
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;


import java.awt.font.NumericShaper;
import java.io.Serializable; import java.io.Serializable;
import java.util.Map; import java.util.Map;


Expand All @@ -47,6 +48,7 @@ public class TimeSurface implements Serializable {
public long dateTime; public long dateTime;
public Map<String, String> params; // The query params sent by the user, for reference only public Map<String, String> params; // The query params sent by the user, for reference only
public SparseMatrixZSampleGrid<WTWD> sampleGrid; // another representation on a regular grid with a triangulation public SparseMatrixZSampleGrid<WTWD> sampleGrid; // another representation on a regular grid with a triangulation
public String description;


public TimeSurface(ShortestPathTree spt) { public TimeSurface(ShortestPathTree spt) {


Expand Down Expand Up @@ -92,7 +94,7 @@ public TimeSurface (AnalystProfileRouterPrototype profileRouter) {
cutoffMinutes = profileRouter.MAX_DURATION / 60; cutoffMinutes = profileRouter.MAX_DURATION / 60;
} }


public static void makeSurfaces (AnalystProfileRouterPrototype profileRouter) { public static TimeSurface.RangeSet makeSurfaces (AnalystProfileRouterPrototype profileRouter) {
TimeSurface minSurface = new TimeSurface(profileRouter); TimeSurface minSurface = new TimeSurface(profileRouter);
TimeSurface avgSurface = new TimeSurface(profileRouter); TimeSurface avgSurface = new TimeSurface(profileRouter);
TimeSurface maxSurface = new TimeSurface(profileRouter); TimeSurface maxSurface = new TimeSurface(profileRouter);
Expand All @@ -103,9 +105,21 @@ public static void makeSurfaces (AnalystProfileRouterPrototype profileRouter) {
avgSurface.times.put(v, tr.avg); avgSurface.times.put(v, tr.avg);
maxSurface.times.put(v, tr.max); maxSurface.times.put(v, tr.max);
} }
profileRouter.minSurface = minSurface; RangeSet result = new RangeSet();
profileRouter.avgSurface = avgSurface; minSurface.description = "Travel times assuming best luck (never waiting for a transfer).";
profileRouter.maxSurface = maxSurface; avgSurface.description = "Expected travel times (average wait for every transfer).";
maxSurface.description = "Travel times assuming worst luck (maximum wait for every transfer).";
result.min = minSurface;
result.avg = avgSurface;
result.max = maxSurface;
return result;
}

/** Groups together three TimeSurfaces as a single response for profile-analyst. */
public static class RangeSet {
public TimeSurface min;
public TimeSurface avg;
public TimeSurface max;
} }


public int getTime(Vertex v) { public int getTime(Vertex v) {
Expand Down Expand Up @@ -138,4 +152,49 @@ public void makeSampleGrid (ShortestPathTree spt) {
long t1 = System.currentTimeMillis(); long t1 = System.currentTimeMillis();
LOG.info("Made SampleGrid from SPT in {} msec.", (int) (t1 - t0)); LOG.info("Made SampleGrid from SPT in {} msec.", (int) (t1 - t0));
} }
}
/**
* Create the SampleGrid from whatever values are already in the TimeSurface, rather than looking at the SPT.
* This is not really ideal since it includes only intersection nodes, and no points along the road segments.
*/
public void makeSampleGridWithoutSPT () {
long t0 = System.currentTimeMillis();
final double gridSizeMeters = 300; // Todo: set dynamically and make sure this matches isoline builder params
// Off-road max distance MUST be APPROX EQUALS to the grid precision
// TODO: Loosen this restriction (by adding more closing sample).
// Change the 0.8 magic factor here with caution.
final double D0 = 0.8 * gridSizeMeters; // offroad walk distance roughly grid size
final double V0 = 1.00; // off-road walk speed in m/sec
Coordinate coordinateOrigin = new Coordinate();
final double cosLat = FastMath.cos(toRadians(coordinateOrigin.y));
double dY = Math.toDegrees(gridSizeMeters / SphericalDistanceLibrary.RADIUS_OF_EARTH_IN_M);
double dX = dY / cosLat;
sampleGrid = new SparseMatrixZSampleGrid<WTWD>(16, this.times.size(), dX, dY, coordinateOrigin);
AccumulativeGridSampler.AccumulativeMetric<WTWD> metric = new SampleGridRenderer.WTWDAccumulativeMetric(cosLat, D0, V0, gridSizeMeters);
AccumulativeGridSampler<WTWD> sampler = new AccumulativeGridSampler<WTWD>(sampleGrid, metric);
// Iterate over every vertex in this timesurface, adding it to the ZSampleGrid
// TODO propagation along street geometries could happen at this stage, rather than when the SPT is still available.
for (TObjectIntIterator<Vertex> iter = times.iterator(); iter.hasNext(); ) {
iter.advance();
Vertex vertex = iter.key();
int time = iter.value();
WTWD z = new WTWD();
z.w = 1.0;
z.d = 0.0;
z.wTime = time;
z.wBoardings = 0; // unused
z.wWalkDist = 0; // unused
sampler.addSamplingPoint(vertex.getCoordinate(), z, V0);
}
sampler.close();
long t1 = System.currentTimeMillis();
LOG.info("Made scalar SampleGrid from TimeSurface in {} msec.", (int) (t1 - t0));
}

/**
* TODO A trivial TZ class containing only a single scalar, or better yet a scalar grid class using primitives.
* When a new instance is created, it should be "empty" until values are accumulated into it: all its fields should
* be zero except the minimum off-road distance, which should be positive infinity.
*/

}
49 changes: 30 additions & 19 deletions src/main/java/org/opentripplanner/api/resource/ProfileResource.java
@@ -1,6 +1,7 @@
package org.opentripplanner.api.resource; package org.opentripplanner.api.resource;


import java.util.List; import java.util.List;
import java.util.Map;


import javax.ws.rs.DefaultValue; import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET; import javax.ws.rs.GET;
Expand All @@ -13,7 +14,9 @@
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.Response.Status;


import com.beust.jcommander.internal.Maps;
import org.opentripplanner.analyst.SurfaceCache; import org.opentripplanner.analyst.SurfaceCache;
import org.opentripplanner.analyst.TimeSurface;
import org.opentripplanner.api.model.TimeSurfaceShort; import org.opentripplanner.api.model.TimeSurfaceShort;
import org.opentripplanner.api.param.HourMinuteSecond; import org.opentripplanner.api.param.HourMinuteSecond;
import org.opentripplanner.api.param.LatLon; import org.opentripplanner.api.param.LatLon;
Expand All @@ -28,8 +31,8 @@
import com.google.common.collect.Lists; import com.google.common.collect.Lists;


/** /**
* A Jersey resource class which exposes OTP profile routing functionality * A Jersey resource class which exposes OTP profile routing functionality as a web service.
* as a web service. *
*/ */
@Path("routers/{routerId}/profile") @Path("routers/{routerId}/profile")
public class ProfileResource { public class ProfileResource {
Expand Down Expand Up @@ -106,25 +109,33 @@ public Response profileRoute (
req.minCarTime = minCarTime; req.minCarTime = minCarTime;
req.suboptimalMinutes = suboptimalMinutes; req.suboptimalMinutes = suboptimalMinutes;


// Uncomment to use the new prototype /* Use the new prototype faster profile-analyst. Really this should be constrained to freq-only cases. */
// AnalystProfileRouterPrototype router = new AnalystProfileRouterPrototype(graph, req); if (req.analyst == true) {
ProfileRouter router = new ProfileRouter(graph, req); AnalystProfileRouterPrototype router = new AnalystProfileRouterPrototype(graph, req);
try { TimeSurface.RangeSet result = router.route();
ProfileResponse response = router.route(); Map<String, Integer> idForSurface = Maps.newHashMap();
if (req.analyst) { idForSurface.put("min", surfaceCache.add(result.min));
surfaceCache.add(router.minSurface); idForSurface.put("avg", surfaceCache.add(result.avg));
surfaceCache.add(router.maxSurface); idForSurface.put("max", surfaceCache.add(result.max));
List<TimeSurfaceShort> surfaceShorts = Lists.newArrayList(); return Response.status(Status.OK).entity(idForSurface).build();
surfaceShorts.add(new TimeSurfaceShort(router.minSurface)); } else {
surfaceShorts.add(new TimeSurfaceShort(router.maxSurface)); ProfileRouter router = new ProfileRouter(graph, req);
return Response.status(Status.OK).entity(surfaceShorts).build(); try {
} else { ProfileResponse response = router.route();
return Response.status(Status.OK).entity(response).build(); if (req.analyst) {
surfaceCache.add(router.minSurface);
surfaceCache.add(router.maxSurface);
List<TimeSurfaceShort> surfaceShorts = Lists.newArrayList();
surfaceShorts.add(new TimeSurfaceShort(router.minSurface));
surfaceShorts.add(new TimeSurfaceShort(router.maxSurface));
return Response.status(Status.OK).entity(surfaceShorts).build();
} else {
return Response.status(Status.OK).entity(response).build();
}
} finally {
router.cleanup(); // destroy routing contexts even when an exception happens
} }
} finally {
router.cleanup(); // destroy routing contexts even when an exception happens
} }

} }


} }
Expand Up @@ -261,10 +261,17 @@ private Response badServer(String message) {
/** /**
* Use Laurent's accumulative grid sampler. Cutoffs in minutes. * Use Laurent's accumulative grid sampler. Cutoffs in minutes.
* The grid and Delaunay triangulation are cached, so subsequent requests are very fast. * The grid and Delaunay triangulation are cached, so subsequent requests are very fast.
*
* @param spacing the number of minutes between isochrones
* @return a list of evenly-spaced isochrones up to the timesurface's cutoff point
*/ */
public List<IsochroneData> getIsochronesAccumulative(TimeSurface surf, int spacing) { public List<IsochroneData> getIsochronesAccumulative(TimeSurface surf, int spacing) {


long t0 = System.currentTimeMillis(); long t0 = System.currentTimeMillis();
if (surf.sampleGrid == null) {
// The sample grid was not built from the SPT; make a minimal one including only time from the vertices in this timesurface
surf.makeSampleGridWithoutSPT();
}
DelaunayIsolineBuilder<WTWD> isolineBuilder = new DelaunayIsolineBuilder<WTWD>( DelaunayIsolineBuilder<WTWD> isolineBuilder = new DelaunayIsolineBuilder<WTWD>(
surf.sampleGrid.delaunayTriangulate(), new WTWD.IsolineMetric()); surf.sampleGrid.delaunayTriangulate(), new WTWD.IsolineMetric());


Expand All @@ -280,7 +287,7 @@ public List<IsochroneData> getIsochronesAccumulative(TimeSurface surf, int spaci
} }


long t1 = System.currentTimeMillis(); long t1 = System.currentTimeMillis();
LOG.debug("Computed {} isochrones in {}msec", isochrones.size(), (int) (t1 - t0)); LOG.debug("Computed {} isochrones in {} msec", isochrones.size(), (int) (t1 - t0));


return isochrones; return isochrones;
} }
Expand Down
Expand Up @@ -82,7 +82,6 @@ public interface AccumulativeMetric<TZ> {


/** /**
* @param metric TZ data "behavior" and "metric". * @param metric TZ data "behavior" and "metric".
* @param size Estimated grid size
*/ */
public AccumulativeGridSampler(ZSampleGrid<TZ> sampleGrid, AccumulativeMetric<TZ> metric) { public AccumulativeGridSampler(ZSampleGrid<TZ> sampleGrid, AccumulativeMetric<TZ> metric) {
this.metric = metric; this.metric = metric;
Expand Down
Expand Up @@ -58,12 +58,9 @@ public AnalystProfileRouterPrototype(Graph graph, ProfileRequest request) {
Multimap<StopCluster, StopAtDistance> fromStopPaths, toStopPaths; // ways to reach each origin or dest stop cluster Multimap<StopCluster, StopAtDistance> fromStopPaths, toStopPaths; // ways to reach each origin or dest stop cluster
List<RoutingContext> routingContexts = Lists.newArrayList(); List<RoutingContext> routingContexts = Lists.newArrayList();


public TimeSurface minSurface, avgSurface, maxSurface;

TObjectIntMap<Stop> fromStops; TObjectIntMap<Stop> fromStops;
TimeWindow window; // filters trips used by time of day and service schedule TimeWindow window; // filters trips used by time of day and service schedule



/** Return a set of all patterns that pass through the stops that are present in the given Tracker. */ /** Return a set of all patterns that pass through the stops that are present in the given Tracker. */
public Set<TripPattern> uniquePatternsVisiting(Set<Stop> stops) { public Set<TripPattern> uniquePatternsVisiting(Set<Stop> stops) {
Set<TripPattern> patterns = Sets.newHashSet(); Set<TripPattern> patterns = Sets.newHashSet();
Expand All @@ -84,7 +81,7 @@ private void checkTimeout() {
} }
} }


public ProfileResponse route () { public TimeSurface.RangeSet route () {


graph.index.clusterStopsAsNeeded(); graph.index.clusterStopsAsNeeded();


Expand Down Expand Up @@ -171,6 +168,7 @@ public ProfileResponse route () {
} }
} }
LOG.info("Done with transit."); LOG.info("Done with transit.");
LOG.info("Propagating from transit stops to the street network...");
for (Stop stop : times) { for (Stop stop : times) {
TransitStop tstop = graph.index.stopVertexForStop.get(stop); TransitStop tstop = graph.index.stopVertexForStop.get(stop);
RoutingRequest rr = new RoutingRequest(TraverseMode.WALK); RoutingRequest rr = new RoutingRequest(TraverseMode.WALK);
Expand All @@ -189,9 +187,9 @@ public ProfileResponse route () {
rr.cleanup(); rr.cleanup();
} }
LOG.info("Done with propagation."); LOG.info("Done with propagation.");
TimeSurface.makeSurfaces(this); TimeSurface.RangeSet result = TimeSurface.makeSurfaces(this);
LOG.info("Done making time surfaces."); LOG.info("Done making time surfaces.");
return null; return result;
} }


/** /**
Expand Down

0 comments on commit c07f208

Please sign in to comment.