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

org.opentripplanner.client.OtpApiClient Maven / Gradle / Ivy

There is a newer version: 0.1.7
Show newest version
package org.opentripplanner.client;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import java.io.IOException;
import java.net.URI;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.opentripplanner.client.model.Agency;
import org.opentripplanner.client.model.Pattern;
import org.opentripplanner.client.model.Route;
import org.opentripplanner.client.model.Stop;
import org.opentripplanner.client.model.TripPlan;
import org.opentripplanner.client.model.VehicleRentalStation;
import org.opentripplanner.client.parameters.TripPlanParameters;
import org.opentripplanner.client.query.GraphQLQueries;
import org.opentripplanner.client.serialization.ObjectMappers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class OtpApiClient {

  private static final Logger LOG = LoggerFactory.getLogger(OtpApiClient.class);
  private static final String DEFAULT_GRAPHQL_PATH = "/otp/gtfs/v1";

  private final CloseableHttpClient httpClient = HttpClientBuilder.create().build();

  private final URI graphQlUri;
  private final ObjectMapper mapper;

  public OtpApiClient(ZoneId timezone, String baseUrl) {
    this.mapper = ObjectMappers.withTimezone(timezone);
    this.graphQlUri = URI.create(baseUrl + DEFAULT_GRAPHQL_PATH);
  }

  /**
   * Returns a TripPlan, also known as a routing result.
   *
   * @link OTP API
   *     docs
   */
  public TripPlan plan(TripPlanParameters req) throws IOException {

    var planQuery = GraphQLQueries.plan();
    var formattedModes =
        req.modes().stream()
            .map(m -> "{mode: %s, qualifier: %s}".formatted(m.mode, m.qualifier))
            .collect(Collectors.joining(", "));
    var formattedQuery =
        planQuery.formatted(
            req.fromPlace().toPlaceString(),
            req.toPlace().toPlaceString(),
            formattedModes,
            req.numItineraries(),
            req.time().toLocalDate().toString(),
            req.time().toLocalTime().truncatedTo(ChronoUnit.SECONDS).toString(),
            req.searchDirection().isArriveBy(),
            req.searchWindow().map(sw -> "searchWindow : %d".formatted(sw.toSeconds())).orElse(""),
            req.walkReluctance().map(Object::toString).orElse("null"),
            req.carReluctance().map(Object::toString).orElse("null"),
            req.bikeReluctance().map(Object::toString).orElse("null"),
            req.bikeWalkingReluctance().map(Object::toString).orElse("null"),
            req.wheelchair(),
            req.banned().map(banned -> "banned : %s".formatted(banned)).orElse(""),
            req.optimize(),
            req.triangle().map(triangle -> "triangle : %s".formatted(triangle)).orElse(""));

    final var jsonNode = sendRequest(formattedQuery);
    return deserialize(jsonNode, "/data/plan", TripPlan.class);
  }

  /**
   * Return the list of routes.
   *
   * @link OTP
   *     API docs
   */
  public List routes() throws IOException {
    var json = sendRequest(GraphQLQueries.routes());
    var type = listType(Route.class);
    return deserializeList(json, type, "/data/routes");
  }

  /**
   * Return the list of vehicle rental stations.
   *
   * @link OTP
   *     API docs
   */
  public List vehicleRentalStations() throws IOException {
    var json = sendRequest(GraphQLQueries.vehicleRentalStations());
    var type = listType(VehicleRentalStation.class);
    return deserializeList(json, type, "/data/vehicleRentalStations");
  }

  /**
   * Return the list of trip patterns.
   *
   * @link OTP
   *     API docs
   */
  public List patterns() throws IOException {
    var json = sendRequest(GraphQLQueries.patterns());
    var type = listType(Pattern.class);
    return deserializeList(json, type, "/data/patterns");
  }

  /**
   * Return the list of agencies.
   *
   * @link OTP
   *     API docs
   */
  public List agencies() throws IOException {
    var json = sendRequest(GraphQLQueries.agencies());
    var type = listType(Agency.class);
    return deserializeList(json, type, "/data/agencies");
  }

  /**
   * Returns a Stop with limited information.
   *
   * @link OTP API
   *     docs
   */
  public Stop stop(String gtfsId) throws IOException {

    var stopQuery = GraphQLQueries.stop();
    var formattedQuery = stopQuery.formatted(gtfsId);

    final var jsonNode = sendRequest(formattedQuery);
    return deserialize(jsonNode, "/data/stop", Stop.class);
  }

  /** Use full text search to query for stops. */
  public List stopSearch(String nameMask) throws IOException {
    var stopQuery = GraphQLQueries.stops();
    var formattedQuery = stopQuery.formatted(nameMask);

    final var jsonNode = sendRequest(formattedQuery);
    return deserializeList(jsonNode, listType(Stop.class), "/data/stops");
  }

  private  T deserialize(JsonNode jsonNode, String path, Class clazz) throws IOException {
    try {
      var plan = jsonNode.at(path);
      return mapper.treeToValue(plan, clazz);
    } catch (IOException e) {
      LOG.error("Could not deserialize response: {}", jsonNode.toPrettyString());
      throw e;
    }
  }

  private  List deserializeList(JsonNode json, CollectionType type, String path)
      throws JsonProcessingException {
    return mapper.treeToValue(json.at(path), type);
  }

  private static CollectionType listType(Class clazz) {
    return TypeFactory.defaultInstance().constructCollectionType(List.class, clazz);
  }

  private JsonNode sendRequest(String formattedQuery) throws IOException {
    LOG.debug("Sending GraphQL query to {}: {}", graphQlUri, formattedQuery);

    var body = mapper.createObjectNode();
    body.put("query", formattedQuery);

    var bodyString = mapper.writeValueAsString(body);

    HttpPost httpPost = new HttpPost(graphQlUri);
    var stringEntity = new StringEntity(bodyString, ContentType.APPLICATION_JSON);
    httpPost.setEntity(stringEntity);
    var response = httpClient.execute(httpPost);
    if (response.getCode() != 200) {
      throw new IOException(
          "HTTP request to '%s' returned status code %s".formatted(graphQlUri, response.getCode()));
    }
    var jsonNode = mapper.readTree(response.getEntity().getContent());

    LOG.trace("Received the following JSON: {}", jsonNode.toPrettyString());
    return jsonNode;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy