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

org.opentripplanner.ext.transferanalyzer.DirectTransferAnalyzer Maven / Gradle / Ivy

The newest version!
package org.opentripplanner.ext.transferanalyzer;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.locationtech.jts.geom.Coordinate;
import org.opentripplanner.ext.transferanalyzer.annotations.TransferCouldNotBeRouted;
import org.opentripplanner.ext.transferanalyzer.annotations.TransferRoutingDistanceTooLong;
import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore;
import org.opentripplanner.graph_builder.model.GraphBuilderModule;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.graphfinder.DirectGraphFinder;
import org.opentripplanner.routing.graphfinder.NearbyStop;
import org.opentripplanner.routing.graphfinder.StreetGraphFinder;
import org.opentripplanner.street.model.vertex.TransitStopVertex;
import org.opentripplanner.transit.model.site.RegularStop;
import org.opentripplanner.transit.service.TimetableRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Module used for analyzing the transfers between nearby stops generated by routing via OSM data.
 * It creates data import issues both for nearby stops that cannot be routed between and instances
 * where the street routing distance is unusually long compared to the euclidean distance (sorted by
 * the ratio between the two distances). These lists can typically be used to improve the quality of
 * OSM data for transfer purposes. This can take a long time if the transfer distance is long and/or
 * there are many stops to route between.
 */
public class DirectTransferAnalyzer implements GraphBuilderModule {

  private static final int RADIUS_MULTIPLIER = 5;

  private static final int MIN_RATIO_TO_LOG = 2;

  private static final int MIN_STREET_DISTANCE_TO_LOG = 100;

  private static final Logger LOG = LoggerFactory.getLogger(DirectTransferAnalyzer.class);

  private final Graph graph;
  private final TimetableRepository timetableRepository;
  private final DataImportIssueStore issueStore;
  private final double radiusMeters;

  public DirectTransferAnalyzer(
    Graph graph,
    TimetableRepository timetableRepository,
    DataImportIssueStore issueStore,
    double radiusMeters
  ) {
    this.graph = graph;
    this.timetableRepository = timetableRepository;
    this.issueStore = issueStore;
    this.radiusMeters = radiusMeters;
  }

  @Override
  public void buildGraph() {
    /* Initialize transit index which is needed by the nearby stop finder. */
    timetableRepository.index();

    LOG.info("Analyzing transfers (this can be time consuming)...");

    List directTransfersTooLong = new ArrayList<>();
    List directTransfersNotFound = new ArrayList<>();

    DirectGraphFinder nearbyStopFinderEuclidian = new DirectGraphFinder(
      timetableRepository.getSiteRepository()::findRegularStops
    );
    StreetGraphFinder nearbyStopFinderStreets = new StreetGraphFinder(graph);

    int stopsAnalyzed = 0;

    for (TransitStopVertex originStopVertex : graph.getVerticesOfType(TransitStopVertex.class)) {
      if (++stopsAnalyzed % 1000 == 0) {
        LOG.info("{} stops analyzed", stopsAnalyzed);
      }

      /* Find nearby stops by euclidean distance */
      Coordinate c0 = originStopVertex.getCoordinate();
      Map stopsEuclidean = nearbyStopFinderEuclidian
        .findClosestStops(c0, radiusMeters)
        .stream()
        .filter(t -> t.stop instanceof RegularStop)
        .collect(Collectors.toMap(t -> (RegularStop) t.stop, t -> t));

      Map stopsStreets = new HashMap<>();
      try {
        /* Find nearby stops by street distance */
        nearbyStopFinderStreets
          .findClosestStops(c0, radiusMeters * RADIUS_MULTIPLIER)
          .stream()
          .filter(t -> t.stop instanceof RegularStop)
          .forEach(t -> stopsStreets.putIfAbsent((RegularStop) t.stop, t));
      } catch (Exception ignored) {}

      RegularStop originStop = originStopVertex.getStop();

      /* Get stops found by both street and euclidean search */
      List stopsConnected = stopsEuclidean
        .keySet()
        .stream()
        .filter(t -> stopsStreets.containsKey(t) && t != originStop)
        .toList();

      /* Get stops found by euclidean search but not street search */
      List stopsUnconnected = stopsEuclidean
        .keySet()
        .stream()
        .filter(t -> !stopsStreets.containsKey(t) && t != originStop)
        .toList();

      for (RegularStop destStop : stopsConnected) {
        NearbyStop euclideanStop = stopsEuclidean.get(destStop);
        NearbyStop streetStop = stopsStreets.get(destStop);

        TransferInfo transferInfo = new TransferInfo(
          originStop,
          destStop,
          euclideanStop.distance,
          streetStop.distance
        );

        /* Log transfer where the street distance is too long compared to the euclidean distance */
        if (
          transferInfo.ratio > MIN_RATIO_TO_LOG &&
          transferInfo.streetDistance > MIN_STREET_DISTANCE_TO_LOG
        ) {
          directTransfersTooLong.add(transferInfo);
        }
      }

      for (RegularStop destStop : stopsUnconnected) {
        NearbyStop euclideanStop = stopsEuclidean.get(destStop);

        /* Log transfers that are found by euclidean search but not by street search */
        directTransfersNotFound.add(
          new TransferInfo(originStop, destStop, euclideanStop.distance, -1)
        );
      }
    }

    /* Sort by street distance to euclidean distance ratio before adding to issues */
    directTransfersTooLong.sort(Comparator.comparingDouble(t -> t.ratio));
    Collections.reverse(directTransfersTooLong);

    for (TransferInfo transferInfo : directTransfersTooLong) {
      issueStore.add(
        new TransferRoutingDistanceTooLong(
          transferInfo.origin,
          transferInfo.destination,
          transferInfo.directDistance,
          transferInfo.streetDistance,
          transferInfo.ratio
        )
      );
    }

    /* Sort by direct distance before adding to issues */
    directTransfersNotFound.sort(Comparator.comparingDouble(t -> t.directDistance));

    for (TransferInfo transferInfo : directTransfersNotFound) {
      issueStore.add(
        new TransferCouldNotBeRouted(
          transferInfo.origin,
          transferInfo.destination,
          transferInfo.directDistance
        )
      );
    }

    LOG.info(
      "Done analyzing transfers. {} transfers could not be routed and {} transfers had a too long routing" +
      " distance.",
      directTransfersNotFound.size(),
      directTransfersTooLong.size()
    );
  }

  private static class TransferInfo {

    final RegularStop origin;
    final RegularStop destination;
    final double directDistance;
    final double streetDistance;
    final double ratio;

    TransferInfo(
      RegularStop origin,
      RegularStop destination,
      double directDistance,
      double streetDistance
    ) {
      this.origin = origin;
      this.destination = destination;
      this.directDistance = directDistance;
      this.streetDistance = streetDistance;
      this.ratio = directDistance != 0 ? streetDistance / directDistance : 0;
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy