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

org.opentripplanner.ext.vectortiles.VectorTilesResource Maven / Gradle / Ivy

package org.opentripplanner.ext.vectortiles;

import com.wdtinc.mapbox_vector_tile.VectorTile;
import java.io.Serializable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import org.geotools.geometry.Envelope2D;
import org.glassfish.grizzly.http.server.Request;
import org.locationtech.jts.geom.Envelope;
import org.opentripplanner.common.geometry.WebMercatorTile;
import org.opentripplanner.ext.vectortiles.layers.stations.StationsLayerBuilder;
import org.opentripplanner.ext.vectortiles.layers.stops.StopsLayerBuilder;
import org.opentripplanner.ext.vectortiles.layers.vehicleparkings.VehicleParkingGroupsLayerBuilder;
import org.opentripplanner.ext.vectortiles.layers.vehicleparkings.VehicleParkingsLayerBuilder;
import org.opentripplanner.ext.vectortiles.layers.vehiclerental.VehicleRentalPlacesLayerBuilder;
import org.opentripplanner.ext.vectortiles.layers.vehiclerental.VehicleRentalStationsLayerBuilder;
import org.opentripplanner.ext.vectortiles.layers.vehiclerental.VehicleRentalVehiclesLayerBuilder;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.standalone.api.OtpServerRequestContext;
import org.opentripplanner.transit.service.TransitService;
import org.opentripplanner.util.WorldEnvelope;

@Path("/routers/{ignoreRouterId}/vectorTiles")
public class VectorTilesResource {

  public static final String APPLICATION_X_PROTOBUF = "application/x-protobuf";
  private static final Map layers = new HashMap<>();
  private final OtpServerRequestContext serverContext;
  private final String ignoreRouterId;
  private final Locale locale;

  static {
    layers.put(
      LayerType.Stop,
      (graph, transitService, layerParameters, locale) ->
        new StopsLayerBuilder(transitService, layerParameters, locale)
    );
    layers.put(
      LayerType.Station,
      (graph, transitService, layerParameters, locale) ->
        new StationsLayerBuilder(transitService, layerParameters, locale)
    );
    layers.put(
      LayerType.VehicleRental,
      (graph, transitService, layerParameters, locale) ->
        new VehicleRentalPlacesLayerBuilder(
          graph.getVehicleRentalStationService(),
          layerParameters,
          locale
        )
    );
    layers.put(
      LayerType.VehicleRentalStation,
      (graph, transitService, layerParameters, locale) ->
        new VehicleRentalStationsLayerBuilder(
          graph.getVehicleRentalStationService(),
          layerParameters,
          locale
        )
    );
    layers.put(
      LayerType.VehicleRentalVehicle,
      (graph, transitService, layerParameters, locale) ->
        new VehicleRentalVehiclesLayerBuilder(
          graph.getVehicleRentalStationService(),
          layerParameters
        )
    );
    layers.put(
      LayerType.VehicleParking,
      (graph, transitService, layerParameters, locale) ->
        new VehicleParkingsLayerBuilder(graph, layerParameters, locale)
    );
    layers.put(
      LayerType.VehicleParkingGroup,
      (graph, transitService, layerParameters, locale) ->
        new VehicleParkingGroupsLayerBuilder(graph, layerParameters, locale)
    );
  }

  public VectorTilesResource(
    @Context OtpServerRequestContext serverContext,
    @Context Request grizzlyRequest,
    /**
     * @deprecated The support for multiple routers are removed from OTP2.
     * See https://github.com/opentripplanner/OpenTripPlanner/issues/2760
     */
    @Deprecated @PathParam("ignoreRouterId") String ignoreRouterId
  ) {
    this.locale = grizzlyRequest.getLocale();
    this.serverContext = serverContext;
    this.ignoreRouterId = ignoreRouterId;
  }

  @GET
  @Path("/{layers}/{z}/{x}/{y}.pbf")
  @Produces(APPLICATION_X_PROTOBUF)
  public Response tileGet(
    @PathParam("x") int x,
    @PathParam("y") int y,
    @PathParam("z") int z,
    @PathParam("layers") String requestedLayers
  ) {
    VectorTile.Tile.Builder mvtBuilder = VectorTile.Tile.newBuilder();

    if (z < LayerParameters.MIN_ZOOM) {
      return Response.status(Response.Status.OK).entity(mvtBuilder.build().toByteArray()).build();
    }

    Envelope2D env = WebMercatorTile.tile2Envelope(x, y, z);
    Envelope envelope = new Envelope(env.getMaxX(), env.getMinX(), env.getMaxY(), env.getMinY());

    List layers = Arrays.asList(requestedLayers.split(","));

    int cacheMaxSeconds = Integer.MAX_VALUE;

    for (LayerParameters layerParameters : serverContext
      .routerConfig()
      .vectorTileLayers()
      .layers()) {
      if (
        layers.contains(layerParameters.name()) &&
        layerParameters.minZoom() <= z &&
        z <= layerParameters.maxZoom()
      ) {
        cacheMaxSeconds = Math.min(cacheMaxSeconds, layerParameters.cacheMaxSeconds());
        mvtBuilder.addLayers(
          VectorTilesResource.layers
            .get(layerParameters.type())
            .create(serverContext.graph(), serverContext.transitService(), layerParameters, locale)
            .build(envelope, layerParameters)
        );
      }
    }

    CacheControl cacheControl = new CacheControl();
    if (cacheMaxSeconds != Integer.MAX_VALUE) {
      cacheControl.setMaxAge(cacheMaxSeconds);
    }
    byte[] bytes = mvtBuilder.build().toByteArray();
    return Response.status(Response.Status.OK).cacheControl(cacheControl).entity(bytes).build();
  }

  @GET
  @Path("/{layers}/tilejson.json")
  @Produces(MediaType.APPLICATION_JSON)
  public TileJson getTileJson(
    @Context UriInfo uri,
    @Context HttpHeaders headers,
    @PathParam("layers") String requestedLayers
  ) {
    return new TileJson(
      serverContext.graph(),
      serverContext.transitService(),
      uri,
      headers,
      requestedLayers
    );
  }

  private String getBaseAddress(UriInfo uri, HttpHeaders headers) {
    String protocol;
    if (headers.getRequestHeader("X-Forwarded-Proto") != null) {
      protocol = headers.getRequestHeader("X-Forwarded-Proto").get(0);
    } else {
      protocol = uri.getRequestUri().getScheme();
    }

    String host;
    if (headers.getRequestHeader("X-Forwarded-Host") != null) {
      host = headers.getRequestHeader("X-Forwarded-Host").get(0);
    } else if (headers.getRequestHeader("Host") != null) {
      host = headers.getRequestHeader("Host").get(0);
    } else {
      host = uri.getBaseUri().getHost() + ":" + uri.getBaseUri().getPort();
    }

    return protocol + "://" + host;
  }

  public enum LayerType {
    Stop,
    Station,
    VehicleRental,
    VehicleRentalVehicle,
    VehicleRentalStation,
    VehicleParking,
    VehicleParkingGroup,
  }

  public interface LayersParameters {
    List layers();
  }

  public interface LayerParameters {
    int MIN_ZOOM = 9;
    int MAX_ZOOM = 20;
    int CACHE_MAX_SECONDS = -1;
    double EXPANSION_FACTOR = 0.25d;

    String name();

    LayerType type();

    String mapper();

    int maxZoom();

    int minZoom();

    int cacheMaxSeconds();

    double expansionFactor();
  }

  private class TileJson implements Serializable {

    // Some fields(all @SuppressWarnings("unused")) below are required to support the TileJSON
    // format. See https://github.com/mapbox/tilejson-spec

    @SuppressWarnings("unused")
    public final String tilejson = "2.2.0";

    @SuppressWarnings("unused")
    public final String scheme = "xyz";

    @SuppressWarnings("unused")
    public final int minzoom = LayerParameters.MIN_ZOOM;

    @SuppressWarnings("unused")
    public final int maxzoom = LayerParameters.MAX_ZOOM;

    public final String name = "OpenTripPlanner";
    public final String attribution;
    public final String[] tiles;
    public final double[] bounds;
    public final double[] center;

    private TileJson(
      Graph graph,
      TransitService transitService,
      UriInfo uri,
      HttpHeaders headers,
      String layers
    ) {
      attribution =
        transitService
          .getFeedIds()
          .stream()
          .map(transitService::getFeedInfo)
          .filter(Predicate.not(Objects::isNull))
          .map(feedInfo ->
            "" + feedInfo.getPublisherName() + ""
          )
          .collect(Collectors.joining(", "));

      tiles =
        new String[] {
          getBaseAddress(uri, headers) +
          "/otp/routers/" +
          ignoreRouterId +
          "/vectorTiles/" +
          layers +
          "/{z}/{x}/{y}.pbf",
        };

      WorldEnvelope envelope = graph.getEnvelope();

      bounds =
        new double[] {
          envelope.getLowerLeftLongitude(),
          envelope.getLowerLeftLatitude(),
          envelope.getUpperRightLongitude(),
          envelope.getUpperRightLatitude(),
        };

      center =
        transitService
          .getCenter()
          .map(coordinate -> new double[] { coordinate.x, coordinate.y, 9 })
          .orElse(null);
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy