
org.opentripplanner.api.resource.TimeGridWs 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.api.resource;
import java.io.ByteArrayOutputStream;
import java.util.Date;
import java.util.Locale;
import javax.ws.rs.*;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.Response;
import org.apache.commons.codec.binary.Base64;
import org.opentripplanner.analyst.request.SampleGridRenderer.WTWD;
import org.opentripplanner.analyst.request.SampleGridRequest;
import org.opentripplanner.api.common.RoutingResource;
import org.opentripplanner.common.geometry.ZSampleGrid;
import org.opentripplanner.common.geometry.ZSampleGrid.ZSamplePoint;
import org.opentripplanner.common.model.GenericLocation;
import org.opentripplanner.routing.core.RoutingRequest;
import org.opentripplanner.standalone.Router;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ar.com.hjg.pngj.ImageInfo;
import ar.com.hjg.pngj.ImageLineHelper;
import ar.com.hjg.pngj.ImageLineInt;
import ar.com.hjg.pngj.PngWriter;
import ar.com.hjg.pngj.chunks.PngChunkTEXT;
import ar.com.hjg.pngj.chunks.PngChunkTextVar;
/**
* A Jersey web service resource class that returns a grid with time for a set of points.
*
* Example of request:
*
*
* http://localhost:8080/otp/routers/bordeaux/timegrid?fromPlace=47.059,-0.880&date=2013/10/01&time=12:00:00&maxWalkDistance=1000&maxTimeSec=3600&mode=WALK,TRANSIT
*
*
* @author laurent
*/
@Path("/routers/{routerId}/timegrid")
public class TimeGridWs extends RoutingResource {
public enum DataChannel {
TIME, /* Clock time */
BOARDINGS, /* Number of boardings */
WALK_DISTANCE, /* Total walk distance */
}
@SuppressWarnings("unused")
private static final Logger LOG = LoggerFactory.getLogger(TimeGridWs.class);
@QueryParam("maxTimeSec")
private Integer maxTimeSec;
@QueryParam("precisionMeters")
@DefaultValue("200")
private Integer precisionMeters;
@QueryParam("offRoadDistanceMeters")
@DefaultValue("150")
private Integer offRoadDistanceMeters;
@QueryParam("coordinateOrigin")
private String coordinateOrigin;
@QueryParam("zDataType")
@DefaultValue("TIME")
private DataChannel zDataType;
private static final String OTPA_GRID_CORNER = "OTPA-Grid-Corner";
private static final String OTPA_GRID_CELL_SIZE = "OTPA-Grid-Cell-Size";
private static final String OTPA_OFFROAD_DIST = "OTPA-OffRoad-Dist";
@GET
@Produces({ "image/png" })
public Response getTimeGridPng(@QueryParam("base64") @DefaultValue("false") boolean base64) throws Exception {
/* Fetch the Router for this request using server and routerId fields from superclass. */
Router router = otpServer.getRouter(routerId);
if (precisionMeters < 10)
throw new IllegalArgumentException("Too small precisionMeters: " + precisionMeters);
if (offRoadDistanceMeters < 10)
throw new IllegalArgumentException("Too small offRoadDistanceMeters: " + offRoadDistanceMeters);
// Build the request
RoutingRequest sptRequest = buildRequest();
SampleGridRequest tgRequest = new SampleGridRequest();
tgRequest.maxTimeSec = maxTimeSec;
tgRequest.precisionMeters = precisionMeters;
tgRequest.offRoadDistanceMeters = offRoadDistanceMeters;
if (coordinateOrigin != null)
tgRequest.coordinateOrigin = new GenericLocation(null, coordinateOrigin).getCoordinate();
// Get a sample grid
ZSampleGrid sampleGrid = router.sampleGridRenderer.getSampleGrid(tgRequest, sptRequest);
int cols = sampleGrid.getXMax() - sampleGrid.getXMin() + 1;
int rows = sampleGrid.getYMax() - sampleGrid.getYMin() + 1;
int channels = 4; // Hard-coded to RGBA
// We force to 8 bits channel depth, some clients won't support more than 8
// (namely, HTML5 canvas...)
ImageInfo imgInfo = new ImageInfo(cols, rows, 8, true, false, false);
/**
* TODO: PNGJ allow for progressive (ie line-by-line) writing. Thus we could theorically
* prevent having to allocate a bit pixel array in the first place, but we would need a
* line-by-line iterator on the sample grid, which is currently not the case.
*/
int[][] rgba = new int[rows][cols * channels];
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PngWriter pw = new PngWriter(baos, imgInfo);
pw.getMetadata().setText(PngChunkTextVar.KEY_Software, "OTPA");
pw.getMetadata().setText(PngChunkTextVar.KEY_Creation_Time, new Date().toString());
pw.getMetadata().setText(PngChunkTextVar.KEY_Description, "Sample grid bitmap");
String gridCornerStr = String.format(Locale.US, "%.8f,%.8f", sampleGrid.getCenter().y
+ sampleGrid.getYMin() * sampleGrid.getCellSize().y, sampleGrid.getCenter().x
+ sampleGrid.getXMin() * sampleGrid.getCellSize().x);
String gridCellSzStr = String.format(Locale.US, "%.12f,%.12f", sampleGrid.getCellSize().y,
sampleGrid.getCellSize().x);
String offRoadDistStr = String.format(Locale.US, "%d", offRoadDistanceMeters);
PngChunkTEXT gridCornerChunk = new PngChunkTEXT(imgInfo);
gridCornerChunk.setKeyVal(OTPA_GRID_CORNER, gridCornerStr);
pw.getChunksList().queue(gridCornerChunk);
PngChunkTEXT gridCellSzChunk = new PngChunkTEXT(imgInfo);
gridCellSzChunk.setKeyVal(OTPA_GRID_CELL_SIZE, gridCellSzStr);
pw.getChunksList().queue(gridCellSzChunk);
PngChunkTEXT offRoadDistChunk = new PngChunkTEXT(imgInfo);
offRoadDistChunk.setKeyVal(OTPA_OFFROAD_DIST, offRoadDistStr);
pw.getChunksList().queue(offRoadDistChunk);
double unit;
switch (zDataType) {
case TIME:
unit = 1.0; // 1:1sec, max 18h
break;
case BOARDINGS:
unit = 1000.0; // 1:0.001 boarding, max 65.5
break;
case WALK_DISTANCE:
unit = 10.0; // 1:0.1m, max 6.55km
break;
default:
throw new IllegalArgumentException("Unsupported Z DataType.");
}
for (ZSamplePoint p : sampleGrid) {
WTWD z = p.getZ();
int row = p.getY() - sampleGrid.getYMin();
int col = p.getX() - sampleGrid.getXMin();
double zz;
switch (zDataType) {
case TIME:
zz = z.wTime / z.w;
break;
case BOARDINGS:
zz = z.wBoardings / z.w;
break;
case WALK_DISTANCE:
zz = z.wWalkDist / z.w;
break;
default:
throw new IllegalArgumentException("Unsupported Z DataType.");
}
int iz;
if (Double.isInfinite(zz)) {
iz = 65535;
} else {
iz = ImageLineHelper.clampTo_0_65535((int) Math.round(zz * unit));
if (iz == 65535)
iz = 65534; // Clamp
}
// d is expressed as a percentage of grid size, max 255%.
// Sometimes d will be bigger than 2.55 x grid size,
// but this should not be too much important as we are off-bounds.
int id = ImageLineHelper.clampTo_0_255((int) Math.round(z.d / precisionMeters * 100));
int offset = col * channels;
rgba[row][offset + 0] = (iz & 0xFF); // z low 8 bits
rgba[row][offset + 1] = (iz >> 8); // z high 8 bits
rgba[row][offset + 2] = id; // d
/*
* Keep the alpha channel at 255, otherwise the RGB channel will be downsampled on some
* rendering clients (namely, JS canvas).
*/
rgba[row][offset + 3] = 255;
}
for (int row = 0; row < rgba.length; row++) {
ImageLineInt iline = new ImageLineInt(imgInfo, rgba[row]);
pw.writeRow(iline, row);
}
pw.end();
// Disallow caching on client side
CacheControl cc = new CacheControl();
cc.setNoCache(true);
// Also put the meta-data in the HTML header (easier to read from JS)
byte[] data = baos.toByteArray();
if (base64) {
data = Base64.encodeBase64(data);
}
return Response.ok().cacheControl(cc).entity(data).header(OTPA_GRID_CORNER, gridCornerStr)
.header(OTPA_GRID_CELL_SIZE, gridCellSzStr)
.header(OTPA_OFFROAD_DIST, offRoadDistStr).build();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy