org.opentripplanner.api.resource.TimeGridWs.java Source code

Java tutorial

Introduction

Here is the source code for org.opentripplanner.api.resource.TimeGridWs.java

Source

/* 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.api.resource;

import java.io.ByteArrayOutputStream;
import java.util.Date;
import java.util.Locale;

import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;

import org.apache.commons.codec.binary.Base64;
import org.opentripplanner.analyst.request.SampleGridRenderer;
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.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;

/**
 * Return a grid with time for a set of points.
 * 
 * Example of request:
 * 
 * <code>
 * http://localhost:8080/otp-rest-servlet/ws/timegrid?routerId=bordeaux&fromPlace=47.059,-0.880&date=2013/10/01&time=12:00:00&maxWalkDistance=1000&mode=WALK,TRANSIT
 * </code>
 * 
 * @author laurent
 */
@Path("/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);

    @Context // FIXME use Application injection
    private SampleGridRenderer sampleGridRenderer;

    @QueryParam("maxTimeSec")
    private Integer maxTimeSec;

    @QueryParam("precisionMeters")
    @DefaultValue("200")
    private Integer precisionMeters;

    @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 {

        if (precisionMeters < 10)
            throw new IllegalArgumentException("Too small precisionMeters: " + precisionMeters);

        // Build the request
        RoutingRequest sptRequest = buildRequest(0);
        SampleGridRequest tgRequest = new SampleGridRequest();
        tgRequest.maxTimeSec = maxTimeSec;
        tgRequest.precisionMeters = precisionMeters;
        if (coordinateOrigin != null)
            tgRequest.coordinateOrigin = new GenericLocation(null, coordinateOrigin).getCoordinate();

        // Get a sample grid
        ZSampleGrid<WTWD> sampleGrid = 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, "%f",
                sampleGridRenderer.getOffRoadDistanceMeters(precisionMeters));

        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<WTWD> 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();
    }
}