Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
package org.opentripplanner.api.resource;
import com.google.common.collect.Maps;
import org.geotools.feature.FeatureCollection;
import org.geotools.geojson.feature.FeatureJSON;
import org.geotools.geometry.Envelope2D;
import org.opentripplanner.analyst.PointSet;
import org.opentripplanner.analyst.ResultSet;
import org.opentripplanner.analyst.SampleSet;
import org.opentripplanner.analyst.TimeSurface;
import org.opentripplanner.analyst.core.IsochroneData;
import org.opentripplanner.analyst.core.SlippyTile;
import org.opentripplanner.analyst.request.RenderRequest;
import org.opentripplanner.analyst.request.SampleGridRenderer.WTWD;
import org.opentripplanner.analyst.request.TileRequest;
import org.opentripplanner.api.common.ParameterException;
import org.opentripplanner.api.common.RoutingResource;
import org.opentripplanner.api.model.TimeSurfaceShort;
import org.opentripplanner.api.parameter.CRSParameter;
import org.opentripplanner.api.parameter.IsoTimeParameter;
import org.opentripplanner.api.parameter.Layer;
import org.opentripplanner.api.parameter.MIMEImageFormat;
import org.opentripplanner.api.parameter.Style;
import org.opentripplanner.common.geometry.DelaunayIsolineBuilder;
import org.opentripplanner.routing.algorithm.EarliestArrivalSearch;
import org.opentripplanner.routing.core.RoutingRequest;
import org.opentripplanner.routing.spt.ShortestPathTree;
import org.opentripplanner.standalone.Router;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
import javax.ws.rs.core.UriInfo;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Surfaces cannot be isolated per-router because sometimes you want to compare two surfaces from different router IDs.
* Though one could question whether that really makes sense (perhaps alternative scenarios should be "within" the same router)
*/
@Path("/surfaces")
@Produces({ MediaType.APPLICATION_JSON })
public class SurfaceResource extends RoutingResource {
private static final Logger LOG = LoggerFactory.getLogger(TimeSurface.class);
@Context
UriInfo uriInfo;
@POST
public Response createSurface(@QueryParam("cutoffMinutes")
@DefaultValue("90") int cutoffMinutes,
@QueryParam("routerId") String routerId) {
// Build the request
try {
RoutingRequest req = buildRequest(); // batch must be true
// routerId is optional -- select default graph if not set
Router router = otpServer.getRouter(routerId);
req.setRoutingContext(router.graph);
EarliestArrivalSearch sptService = new EarliestArrivalSearch();
sptService.maxDuration = (60 * cutoffMinutes);
ShortestPathTree spt = sptService.getShortestPathTree(req);
req.cleanup();
if (spt != null) {
TimeSurface surface = new TimeSurface(spt);
surface.params = Maps.newHashMap();
for (Map.Entry> e : uriInfo.getQueryParameters().entrySet()) {
// include only the first instance of each query parameter
surface.params.put(e.getKey(), e.getValue().get(0));
}
surface.cutoffMinutes = cutoffMinutes;
otpServer.surfaceCache.add(surface);
return Response.ok().entity(new TimeSurfaceShort(surface)).build(); // .created(URI)
} else {
return Response.noContent().entity("NO SPT").build();
}
} catch (ParameterException pex) {
return Response.status(Response.Status.BAD_REQUEST).entity("BAD USER").build();
}
}
/** List all the available surfaces. */
@GET
public Response getTimeSurfaceList () {
return Response.ok().entity(TimeSurfaceShort.list(otpServer.surfaceCache.cache.asMap().values())).build();
}
/** Describe a specific surface. */
@GET @Path("/{surfaceId}")
public Response getTimeSurfaceList (@PathParam("surfaceId") Integer surfaceId) {
TimeSurface surface = otpServer.surfaceCache.get(surfaceId);
if (surface == null) return Response.status(Response.Status.NOT_FOUND).entity("Invalid surface ID.").build();
return Response.ok().entity(new TimeSurfaceShort(surface)).build();
// DEBUG return Response.ok().entity(surface).build();
}
/**
* Evaluate a surface at all the points in a PointSet.
* This sends back a ResultSet serialized as JSON.
* Normally we return historgrams with the number of points reached (in field 'counts') and the number of
* opportunities reached (i.e. the sum of the magnitudes of all points reached) in each one-minute bin of travel
* time.
* @param detail if true, include the travel time to every point in the pointset (which is in fact an ordered list)
*/
@GET @Path("/{surfaceId}/indicator")
public Response getIndicator (@PathParam("surfaceId") Integer surfaceId,
@QueryParam("targets") String targetPointSetId,
@QueryParam("origins") String originPointSetId,
@QueryParam("detail") boolean detail) {
final TimeSurface surf = otpServer.surfaceCache.get(surfaceId);
if (surf == null) return badRequest("Invalid TimeSurface ID.");
final PointSet pset = otpServer.pointSetCache.get(targetPointSetId);
if (pset == null) return badRequest("Missing or invalid target PointSet ID.");
Router router = otpServer.getRouter(surf.routerId);
// TODO cache this sampleset
SampleSet samples = pset.getSampleSet(router.graph);
final ResultSet indicator = new ResultSet(samples, surf, detail, detail);
if (indicator == null) return badServer("Could not compute indicator as requested.");
return Response.ok().entity(new StreamingOutput() {
@Override
public void write(OutputStream output) throws IOException, WebApplicationException {
indicator.writeJson(output);
}
}).build();
}
/** Create vector isochrones for a surface. */
@GET @Path("/{surfaceId}/isochrone")
public Response getIsochrone (
@PathParam("surfaceId") Integer surfaceId,
@QueryParam("spacing") int spacing,
@QueryParam("nMax") @DefaultValue("1") int nMax) {
final TimeSurface surf = otpServer.surfaceCache.get(surfaceId);
if (surf == null) return badRequest("Invalid TimeSurface ID.");
if (spacing < 1) spacing = 30;
List isochrones = getIsochronesAccumulative(surf, spacing, nMax);
// NOTE that cutoffMinutes in the surface must be properly set for the following call to work
final FeatureCollection fc = LIsochrone.makeContourFeatures(isochrones);
return Response.ok().entity(new StreamingOutput() {
@Override
public void write(OutputStream output) throws IOException {
FeatureJSON fj = new FeatureJSON();
fj.writeFeatureCollection(fc, output);
}
}).build();
}
@Path("/{surfaceId}/isotiles/{z}/{x}/{y}.png")
@GET @Produces("image/png")
public Response tileGet(@PathParam("surfaceId") Integer surfaceId,
@PathParam("x") int x,
@PathParam("y") int y,
@PathParam("z") int z) throws Exception {
Envelope2D env = SlippyTile.tile2Envelope(x, y, z);
TimeSurface surfA = otpServer.surfaceCache.get(surfaceId);
if (surfA == null) return badRequest("Unrecognized surface ID.");
TileRequest tileRequest = new TileRequest(env, 256, 256);
MIMEImageFormat imageFormat = new MIMEImageFormat("image/png");
RenderRequest renderRequest =
new RenderRequest(imageFormat, Layer.TRAVELTIME, Style.COLOR30, true, false);
// TODO why can't the renderer be static?
Router router = otpServer.getRouter(surfA.routerId);
return router.renderer.getResponse(tileRequest, surfA, null, renderRequest);
}
/**
* Renders a raster tile for showing the difference between two TimeSurfaces.
* This service is included as a way to provide difference tiles using existing mechanisms in OTP.
* TODO However, there is some room for debate around how differences are expressed in URLs.
* We may want a more general purpose mechanism for combining time surfaces.
* For example you could make a web service request to create a time surface A-B or A+B, and the server would give
* you an ID for that surface, and then you could use that ID anywhere a surface ID is required. Perhaps internally
* there would be some sort of DifferenceTimeSurface subclass that could just drop in anywhere TimeSurface is used.
* This approach would be more stateful but more flexible.
*
* @author hannesj
*
* @param surfaceId The id of the first surface
* @param compareToSurfaceId The id of of the surface, which is compared to the first surface
*/
@Path("/{surfaceId}/differencetiles/{compareToSurfaceId}/{z}/{x}/{y}.png")
@GET @Produces("image/png")
public Response differenceTileGet(@PathParam("surfaceId") Integer surfaceId,
@PathParam("compareToSurfaceId") Integer compareToSurfaceId,
@PathParam("x") int x,
@PathParam("y") int y,
@PathParam("z") int z) throws Exception {
Envelope2D env = SlippyTile.tile2Envelope(x, y, z);
TimeSurface surfA = otpServer.surfaceCache.get(surfaceId);
if (surfA == null) return badRequest("Unrecognized surface ID.");
TimeSurface surfB = otpServer.surfaceCache.get(compareToSurfaceId);
if (surfB == null) return badRequest("Unrecognized surface ID.");
if ( ! surfA.routerId.equals(surfB.routerId)) {
return badRequest("Both surfaces must be from the same router to perform subtraction.");
}
TileRequest tileRequest = new TileRequest(env, 256, 256);
MIMEImageFormat imageFormat = new MIMEImageFormat("image/png");
RenderRequest renderRequest = new RenderRequest(imageFormat, Layer.DIFFERENCE, Style.DIFFERENCE, true, false);
// TODO why can't the renderer be static?
Router router = otpServer.getRouter(surfA.routerId);
return router.renderer.getResponse(tileRequest, surfA, surfB, renderRequest);
}
private Response badRequest(String message) {
return Response.status(Response.Status.BAD_REQUEST).entity("Bad request: " + message).build();
}
private Response badServer(String message) {
return Response.status(Response.Status.BAD_REQUEST).entity("Server fail: " + message).build();
}
/**
* Use Laurent's accumulative grid sampler. Cutoffs in minutes.
* 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 static List getIsochronesAccumulative(TimeSurface surf, int spacing, int nMax) {
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 isolineBuilder = new DelaunayIsolineBuilder(
surf.sampleGrid.delaunayTriangulate(), new WTWD.IsolineMetric());
List isochrones = new ArrayList();
for (int minutes = spacing, n = 0; minutes <= surf.cutoffMinutes && n < nMax; minutes += spacing, n++) {
int seconds = minutes * 60;
WTWD z0 = new WTWD();
z0.w = 1.0;
z0.wTime = seconds;
z0.d = 300; // meters. TODO set dynamically / properly, make sure it matches grid cell size?
IsochroneData isochrone = new IsochroneData(seconds, isolineBuilder.computeIsoline(z0));
isochrones.add(isochrone);
}
long t1 = System.currentTimeMillis();
LOG.debug("Computed {} isochrones in {} msec", isochrones.size(), (int) (t1 - t0));
return isochrones;
}
/**
* Produce a single grayscale raster of travel time, like travel time tiles but not broken into tiles.
*/
@Path("/{surfaceId}/raster")
@GET @Produces("image/*")
public Response getRaster(
@PathParam("surfaceId") Integer surfaceId,
@QueryParam("width") @DefaultValue("1024") Integer width,
@QueryParam("height") @DefaultValue("768") Integer height,
@QueryParam("resolution") Double resolution,
@QueryParam("time") IsoTimeParameter time,
@QueryParam("format") @DefaultValue("image/geotiff") MIMEImageFormat format,
@QueryParam("crs") @DefaultValue("EPSG:4326") CRSParameter crs) throws Exception {
TimeSurface surface = otpServer.surfaceCache.get(surfaceId);
Router router = otpServer.getRouter(surface.routerId);
// BoundingBox is a subclass of Envelope, an Envelope2D constructor parameter
Envelope2D bbox = new Envelope2D(router.graph.getGeomIndex().getBoundingBox(crs.crs));
if (resolution != null) {
width = (int) Math.ceil(bbox.width / resolution);
height = (int) Math.ceil(bbox.height / resolution);
}
TileRequest tileRequest = new TileRequest(bbox, width, height);
RenderRequest renderRequest = new RenderRequest(format, Layer.TRAVELTIME, Style.GRAY, false, false);
return router.renderer.getResponse(tileRequest, surface, null, renderRequest);
}
}