Ejemplo n.º 1
0
  /**
   * Finds all egress paths from to coordinate to end stop and adds routers to egressRouter
   *
   * @param request
   * @param egressRouter
   */
  private void findEgressPaths(ProfileRequest request, Map<LegMode, StreetRouter> egressRouter) {
    // For egress
    // TODO: this must be reverse search
    for (LegMode mode : request.egressModes) {
      StreetRouter streetRouter = new StreetRouter(transportNetwork.streetLayer);
      streetRouter.transitStopSearch = true;
      streetRouter.dominanceVariable = StreetRouter.State.RoutingVariable.DURATION_SECONDS;
      if (egressUnsupportedModes.contains(mode)) {
        continue;
      }
      // TODO: add support for bike sharing
      streetRouter.streetMode = StreetMode.valueOf(mode.toString());
      streetRouter.profileRequest = request;
      streetRouter.timeLimitSeconds = request.getTimeLimit(mode);
      if (streetRouter.setOrigin(request.toLat, request.toLon)) {
        streetRouter.route();
        TIntIntMap stops = streetRouter.getReachedStops();
        egressRouter.put(mode, streetRouter);
        LOG.info("Added {} edgres stops for mode {}", stops.size(), mode);

      } else {
        LOG.warn(
            "MODE:{}, Edge near the origin coordinate wasn't found. Routing didn't start!", mode);
      }
    }
  }
Ejemplo n.º 2
0
/**
 * Class which will make point to point or profile queries on Transport network based on
 * profileRequest Created by mabu on 23.12.2015.
 */
public class PointToPointQuery {
  private static final Logger LOG = LoggerFactory.getLogger(PointToPointQuery.class);

  /**
   * The largest number of stops to consider boarding at. If there are 1000 stops within 2km, only
   * consider boarding at the closest 200. It's not clear this has a major effect on speed, so we
   * could consider removing it.
   */
  public static final int MAX_ACCESS_STOPS = 200;

  private final TransportNetwork transportNetwork;

  // interpretation of below parameters: if biking is less than BIKE_PENALTY seconds faster than
  // walking, we prefer to walk

  /** how many seconds worse biking to transit is than walking */
  private static final int BIKE_PENALTY = 600;

  /** how many seconds worse bikeshare is than just walking */
  private static final int BIKESHARE_PENALTY = 300;

  /** How many seconds worse driving to transit is than just walking */
  private static final int CAR_PENALTY = 1200;

  private static final EnumSet<LegMode> egressUnsupportedModes = EnumSet.of(LegMode.CAR_PARK);

  /** Time to rent a bike in seconds */
  private static final int BIKE_RENTAL_PICKUP_TIME_S = 60;

  /**
   * Cost of renting a bike. The cost is a bit more than actual time to model the associated cost
   * and trouble.
   */
  private static final int BIKE_RENTAL_PICKUP_COST = 120;
  /** Time to drop-off a rented bike in seconds */
  private static final int BIKE_RENTAL_DROPOFF_TIME_S = 30;
  /** Cost of dropping-off a rented bike */
  private static final int BIKE_RENTAL_DROPOFF_COST = 30;
  /** Time to park car in P+R in seconds * */
  private static final int CAR_PARK_DROPOFF_TIME_S = 120;

  private static final int CAR_PARK_DROPOFF_COST = 120;

  public PointToPointQuery(TransportNetwork transportNetwork) {
    this.transportNetwork = transportNetwork;
  }

  public ZoneId getTimezone() {
    return this.transportNetwork.getTimeZone();
  }

  // Does point to point routing with data from request
  public ProfileResponse getPlan(ProfileRequest request) {
    long startRouting = System.currentTimeMillis();
    request.zoneId = transportNetwork.getTimeZone();
    // Do the query and return result
    ProfileResponse profileResponse = new ProfileResponse();

    ProfileOption option = new ProfileOption();

    findDirectPaths(request, option);
    option.summary = option.generateSummary();
    profileResponse.addOption(option);

    if (request.hasTransit()) {
      Map<LegMode, StreetRouter> accessRouter = new HashMap<>(request.accessModes.size());
      Map<LegMode, StreetRouter> egressRouter = new HashMap<>(request.egressModes.size());

      // This map saves which access mode was used to access specific stop in access mode
      TIntObjectMap<LegMode> stopModeAccessMap = new TIntObjectHashMap<>();
      // This map saves which egress mode was used to access specific stop in egress mode
      TIntObjectMap<LegMode> stopModeEgressMap = new TIntObjectHashMap<>();

      findAccessPaths(request, accessRouter);

      findEgressPaths(request, egressRouter);

      // fold access and egress times into single maps
      TIntIntMap accessTimes =
          combineMultimodalRoutingAccessTimes(accessRouter, stopModeAccessMap, request);
      TIntIntMap egressTimes =
          combineMultimodalRoutingAccessTimes(egressRouter, stopModeEgressMap, request);

      McRaptorSuboptimalPathProfileRouter router =
          new McRaptorSuboptimalPathProfileRouter(
              transportNetwork, request, accessTimes, egressTimes);
      List<PathWithTimes> usefullpathList = new ArrayList<>();

      // getPaths actually returns a set, which is important so that things are deduplicated.
      // However we need a list
      // so we can sort it below.
      usefullpathList.addAll(router.getPaths());

      // This sort is necessary only for text debug output so it will be disabled when it is
      // finished

      /**
       * Orders first no transfers then one transfers 2 etc - then orders according to first trip: -
       * board stop - alight stop - alight time - same for one transfer trip
       */
      usefullpathList.sort(
          (o1, o2) -> {
            int c;
            c = Integer.compare(o1.patterns.length, o2.patterns.length);
            if (c == 0) {
              c = Integer.compare(o1.boardStops[0], o2.boardStops[0]);
            }
            if (c == 0) {
              c = Integer.compare(o1.alightStops[0], o2.alightStops[0]);
            }
            if (c == 0) {
              c = Integer.compare(o1.alightTimes[0], o2.alightTimes[0]);
            }
            if (c == 0 && o1.patterns.length == 2) {
              c = Integer.compare(o1.boardStops[1], o2.boardStops[1]);
              if (c == 0) {
                c = Integer.compare(o1.alightStops[1], o2.alightStops[1]);
              }
              if (c == 0) {
                c = Integer.compare(o1.alightTimes[1], o2.alightTimes[1]);
              }
            }
            return c;
          });
      LOG.info("Usefull paths:{}", usefullpathList.size());
      int seen_paths = 0;
      int boardStop = -1, alightStop = -1;
      for (PathWithTimes path : usefullpathList) {
        profileResponse.addTransitPath(
            accessRouter,
            egressRouter,
            stopModeAccessMap,
            stopModeEgressMap,
            path,
            transportNetwork,
            request.getFromTimeDateZD());
        // LOG.info("Num patterns:{}", path.patterns.length);
        // ProfileOption transit_option = new ProfileOption();

        /*if (path.patterns.length == 1) {
            continue;
        }*/

        /*if (seen_paths > 20) {
            break;
        }*/

        if (LOG.isDebugEnabled()) {
          LOG.debug(" ");
          for (int i = 0; i < path.patterns.length; i++) {
            // TransitSegment transitSegment = new TransitSegment(transportNetwork.transitLayer,
            // path.boardStops[i], path.alightStops[i], path.patterns[i]);
            if (!(((boardStop == path.boardStops[i] && alightStop == path.alightStops[i])))) {
              LOG.debug(
                  "   BoardStop: {} pattern: {} allightStop: {}",
                  path.boardStops[i],
                  path.patterns[i],
                  path.alightStops[i]);
            }
            TripPattern pattern = transportNetwork.transitLayer.tripPatterns.get(path.patterns[i]);
            if (pattern.routeIndex >= 0) {
              RouteInfo routeInfo = transportNetwork.transitLayer.routes.get(pattern.routeIndex);
              LOG.debug(
                  "     Pattern:{} on route:{} ({}) with {} stops",
                  path.patterns[i],
                  routeInfo.route_long_name,
                  routeInfo.route_short_name,
                  pattern.stops.length);
            }
            LOG.debug(
                "     {}->{} ({}:{})",
                transportNetwork.transitLayer.stopNames.get(path.boardStops[i]),
                transportNetwork.transitLayer.stopNames.get(path.alightStops[i]),
                path.alightTimes[i] / 3600,
                path.alightTimes[i] % 3600 / 60);
            // transit_option.addTransit(transitSegment);
          }
          boardStop = path.boardStops[0];
          alightStop = path.alightStops[0];
        }
        seen_paths++;
      }
      profileResponse.generateStreetTransfers(transportNetwork, request);
    }

    LOG.info("Returned {} options", profileResponse.getOptions().size());
    LOG.info("Took {} ms", System.currentTimeMillis() - startRouting);

    return profileResponse;
  }

  /**
   * Finds all egress paths from to coordinate to end stop and adds routers to egressRouter
   *
   * @param request
   * @param egressRouter
   */
  private void findEgressPaths(ProfileRequest request, Map<LegMode, StreetRouter> egressRouter) {
    // For egress
    // TODO: this must be reverse search
    for (LegMode mode : request.egressModes) {
      StreetRouter streetRouter = new StreetRouter(transportNetwork.streetLayer);
      streetRouter.transitStopSearch = true;
      streetRouter.dominanceVariable = StreetRouter.State.RoutingVariable.DURATION_SECONDS;
      if (egressUnsupportedModes.contains(mode)) {
        continue;
      }
      // TODO: add support for bike sharing
      streetRouter.streetMode = StreetMode.valueOf(mode.toString());
      streetRouter.profileRequest = request;
      streetRouter.timeLimitSeconds = request.getTimeLimit(mode);
      if (streetRouter.setOrigin(request.toLat, request.toLon)) {
        streetRouter.route();
        TIntIntMap stops = streetRouter.getReachedStops();
        egressRouter.put(mode, streetRouter);
        LOG.info("Added {} edgres stops for mode {}", stops.size(), mode);

      } else {
        LOG.warn(
            "MODE:{}, Edge near the origin coordinate wasn't found. Routing didn't start!", mode);
      }
    }
  }

  /**
   * Finds direct paths between from and to coordinates in request and adds them to option
   *
   * @param request
   * @param option
   */
  private void findDirectPaths(ProfileRequest request, ProfileOption option) {
    // For direct modes
    for (LegMode mode : request.directModes) {
      StreetRouter streetRouter = new StreetRouter(transportNetwork.streetLayer);
      StreetPath streetPath;
      streetRouter.profileRequest = request;
      if (mode == LegMode.BICYCLE_RENT) {
        if (!transportNetwork.streetLayer.bikeSharing) {
          LOG.warn("Bike sharing trip requested but no bike sharing stations in the streetlayer");
          continue;
        }
        streetRouter = findBikeRentalPath(request, streetRouter, true);
        if (streetRouter != null) {
          StreetRouter.State lastState = streetRouter.getState(request.toLat, request.toLon);
          if (lastState != null) {
            streetPath =
                new StreetPath(lastState, streetRouter, LegMode.BICYCLE_RENT, transportNetwork);

          } else {
            LOG.warn(
                "MODE:{}, Edge near the destination coordinate wasn't found. Routing didn't start!",
                mode);
            continue;
          }
        } else {
          LOG.warn("Not found path from cycle to end");
          continue;
        }
      } else {
        streetRouter.streetMode = StreetMode.valueOf(mode.toString());
        streetRouter.timeLimitSeconds = request.streetTime * 60;
        if (streetRouter.setOrigin(request.fromLat, request.fromLon)) {
          if (!streetRouter.setDestination(request.toLat, request.toLon)) {
            LOG.warn("Direct mode {} destination wasn't found!", mode);
            continue;
          }
          streetRouter.route();
          StreetRouter.State lastState = streetRouter.getState(streetRouter.getDestinationSplit());
          if (lastState == null) {
            LOG.warn("Direct mode {} last state wasn't found", mode);
            continue;
          }
          streetPath = new StreetPath(lastState, transportNetwork);
        } else {
          LOG.warn("Direct mode {} origin wasn't found!", mode);
          continue;
        }
      }

      StreetSegment streetSegment =
          new StreetSegment(streetPath, mode, transportNetwork.streetLayer);
      option.addDirect(streetSegment, request.getFromTimeDateZD());
    }
  }

  /**
   * Finds access paths from from coordinate in request and adds all routers with paths to
   * accessRouter map
   *
   * @param request
   * @param accessRouter
   */
  private void findAccessPaths(ProfileRequest request, Map<LegMode, StreetRouter> accessRouter) {
    // Routes all access modes
    for (LegMode mode : request.accessModes) {
      StreetRouter streetRouter = new StreetRouter(transportNetwork.streetLayer);
      streetRouter.profileRequest = request;
      if (mode == LegMode.CAR_PARK) {
        streetRouter = findParkRidePath(request, streetRouter);
        if (streetRouter != null) {
          accessRouter.put(LegMode.CAR_PARK, streetRouter);
        } else {
          LOG.warn(
              "MODE:{}, Edge near the origin coordinate wasn't found. Routing didn't start!", mode);
        }
      } else if (mode == LegMode.BICYCLE_RENT) {
        if (!transportNetwork.streetLayer.bikeSharing) {
          LOG.warn("Bike sharing trip requested but no bike sharing stations in the streetlayer");
          continue;
        }
        streetRouter = findBikeRentalPath(request, streetRouter, false);
        if (streetRouter != null) {
          accessRouter.put(LegMode.BICYCLE_RENT, streetRouter);
        } else {
          LOG.warn("Not found path from cycle to end");
        }
      } else {
        streetRouter.streetMode = StreetMode.valueOf(mode.toString());

        // Gets correct maxCar/Bike/Walk time in seconds for access leg based on mode since it
        // depends on the mode
        streetRouter.timeLimitSeconds = request.getTimeLimit(mode);
        streetRouter.transitStopSearch = true;
        streetRouter.dominanceVariable = StreetRouter.State.RoutingVariable.DURATION_SECONDS;

        if (streetRouter.setOrigin(request.fromLat, request.fromLon)) {
          streetRouter.route();
          // Searching for access paths
          accessRouter.put(mode, streetRouter);
        } else {
          LOG.warn(
              "MODE:{}, Edge near the origin coordinate wasn't found. Routing didn't start!", mode);
        }
      }
    }
  }

  /**
   * Uses 2 streetSearches to get P+R path
   *
   * <p>First CAR search from fromLat/fromLon to all car parks. Then from those found places WALK
   * search.
   *
   * <p>Result is then used as access part. Since P+R in direct mode is useless.
   *
   * @param request profileRequest from which from/to destination is used
   * @param streetRouter where profileRequest was already set
   * @return null if path isn't found
   */
  private StreetRouter findParkRidePath(ProfileRequest request, StreetRouter streetRouter) {
    streetRouter.streetMode = StreetMode.CAR;
    streetRouter.timeLimitSeconds = request.maxCarTime * 60;
    streetRouter.flagSearch = VertexStore.VertexFlag.PARK_AND_RIDE;
    streetRouter.dominanceVariable = StreetRouter.State.RoutingVariable.DURATION_SECONDS;
    if (streetRouter.setOrigin(request.fromLat, request.fromLon)) {
      streetRouter.route();
      TIntObjectMap<StreetRouter.State> carParks =
          streetRouter.getReachedVertices(VertexStore.VertexFlag.PARK_AND_RIDE);
      LOG.info("CAR PARK: Found {} car parks", carParks.size());
      StreetRouter walking = new StreetRouter(transportNetwork.streetLayer);
      walking.streetMode = StreetMode.WALK;
      walking.profileRequest = request;
      walking.timeLimitSeconds = request.maxCarTime * 60;
      walking.transitStopSearch = true;
      walking.setOrigin(carParks, CAR_PARK_DROPOFF_TIME_S, CAR_PARK_DROPOFF_COST, LegMode.CAR_PARK);
      walking.dominanceVariable = StreetRouter.State.RoutingVariable.DURATION_SECONDS;
      walking.route();
      walking.previousRouter = streetRouter;
      return walking;
    } else {
      return null;
    }
  }

  /**
   * Uses 3 streetSearches to first search from fromLat/fromLon to all the bike renting places in
   * WALK mode. Then from all found bike renting places to other bike renting places with BIKE and
   * then just routing from those found bike renting places in WALK mode.
   *
   * <p>This can then be used as streetRouter for access paths or as a direct search for specific
   * destination
   *
   * <p>Last streetRouter (WALK from bike rentals) is returned
   *
   * @param request profileRequest from which from/to destination is used
   * @param streetRouter where profileRequest was already set
   * @param direct
   * @return null if path isn't found
   */
  private StreetRouter findBikeRentalPath(
      ProfileRequest request, StreetRouter streetRouter, boolean direct) {
    streetRouter.streetMode = StreetMode.WALK;
    // TODO add time and distance limits to routing, not just weight.
    streetRouter.timeLimitSeconds = request.maxWalkTime * 60;
    if (!direct) {
      streetRouter.dominanceVariable = StreetRouter.State.RoutingVariable.DURATION_SECONDS;
    }
    streetRouter.flagSearch = VertexStore.VertexFlag.BIKE_SHARING;
    if (streetRouter.setOrigin(request.fromLat, request.fromLon)) {
      // if we can't find destination we can stop search before even trying
      if (direct && !streetRouter.setDestination(request.toLat, request.toLon)) {
        return null;
      }
      Split destinationSplit = streetRouter.getDestinationSplit();

      // reset destinationSplit since we need it at the last part of routing
      streetRouter.setDestination(null);
      streetRouter.route();
      // This finds all the nearest bicycle rent stations when walking
      TIntObjectMap<StreetRouter.State> bikeStations =
          streetRouter.getReachedVertices(VertexStore.VertexFlag.BIKE_SHARING);
      LOG.info(
          "BIKE RENT: Found {} bike stations which are {} minutes away",
          bikeStations.size(),
          streetRouter.timeLimitSeconds / 60);
      /*LOG.info("Start to bike share:");
      bikeStations.forEachEntry((idx, state) -> {
          LOG.info("   {} ({}m)", idx, state.distance);
          return true;
      });*/

      // This finds best cycling path from best start bicycle station to end bicycle station
      StreetRouter bicycle = new StreetRouter(transportNetwork.streetLayer);
      bicycle.previousRouter = streetRouter;
      bicycle.streetMode = StreetMode.BICYCLE;
      bicycle.profileRequest = request;
      bicycle.flagSearch = streetRouter.flagSearch;
      bicycle.maxVertices = Integer.MAX_VALUE;
      // Longer bike part if this is direct search
      if (direct) {
        bicycle.timeLimitSeconds = request.streetTime * 60;
      } else {
        bicycle.timeLimitSeconds = request.maxBikeTime * 60;
        bicycle.dominanceVariable = StreetRouter.State.RoutingVariable.DURATION_SECONDS;
      }
      bicycle.setOrigin(
          bikeStations, BIKE_RENTAL_PICKUP_TIME_S, BIKE_RENTAL_PICKUP_COST, LegMode.BICYCLE_RENT);
      bicycle.setDestination(destinationSplit);
      bicycle.route();
      TIntObjectMap<StreetRouter.State> cycledStations =
          bicycle.getReachedVertices(VertexStore.VertexFlag.BIKE_SHARING);
      LOG.info(
          "BIKE RENT: Found {} cycled stations which are {} minutes away",
          cycledStations.size(),
          bicycle.timeLimitSeconds / 60);
      /*LOG.info("Bike share to bike share:");
      cycledStations.retainEntries((idx, state) -> {
          if (bikeStations.containsKey(idx)) {
              LOG.warn("  MM:{} ({}m)", idx, state.distance/1000);
              return false;
          } else {
              LOG.info("   {} ({}m)", idx, state.distance / 1000);
              return true;
          }

      });*/
      // This searches for walking path from end bicycle station to end point
      StreetRouter end = new StreetRouter(transportNetwork.streetLayer);
      end.streetMode = StreetMode.WALK;
      end.profileRequest = request;
      end.timeLimitSeconds = bicycle.timeLimitSeconds;
      if (!direct) {
        end.transitStopSearch = true;
        end.dominanceVariable = StreetRouter.State.RoutingVariable.DURATION_SECONDS;
      }
      end.setOrigin(
          cycledStations,
          BIKE_RENTAL_DROPOFF_TIME_S,
          BIKE_RENTAL_DROPOFF_COST,
          LegMode.BICYCLE_RENT);
      end.route();
      end.previousRouter = bicycle;
      return end;
    } else {
      return null;
    }
  }

  /**
   * Combine the results of several street searches using different modes into a single map It also
   * saves with which mode was stop reached into stopModeMap. This map is then used to create
   * itineraries in response
   */
  private TIntIntMap combineMultimodalRoutingAccessTimes(
      Map<LegMode, StreetRouter> routers,
      TIntObjectMap<LegMode> stopModeMap,
      ProfileRequest request) {
    // times at transit stops
    TIntIntMap times = new TIntIntHashMap();

    // weights at transit stops
    TIntIntMap weights = new TIntIntHashMap();

    for (Map.Entry<LegMode, StreetRouter> entry : routers.entrySet()) {
      int maxTime = 30;
      int minTime = 0;
      int penalty = 0;

      LegMode mode = entry.getKey();
      switch (mode) {
        case BICYCLE:
          maxTime = request.maxBikeTime;
          minTime = request.minBikeTime;
          penalty = BIKE_PENALTY;
          break;
        case BICYCLE_RENT:
          // TODO this is not strictly correct, bike rent is partly walking
          maxTime = request.maxBikeTime;
          minTime = request.minBikeTime;
          penalty = BIKESHARE_PENALTY;
          break;
        case WALK:
          maxTime = request.maxWalkTime;
          break;
        case CAR:
          // TODO this is not strictly correct, CAR PARK is partly walking
        case CAR_PARK:
          maxTime = request.maxCarTime;
          minTime = request.minCarTime;
          penalty = CAR_PENALTY;
          break;
      }

      maxTime *= 60; // convert to seconds
      minTime *= 60; // convert to seconds

      final int maxTimeFinal = maxTime;
      final int minTimeFinal = minTime;
      final int penaltyFinal = penalty;

      StreetRouter router = entry.getValue();
      router
          .getReachedStops()
          .forEachEntry(
              (stop, time) -> {
                if (time > maxTimeFinal || time < minTimeFinal) return true;
                // Skip stops that can't be used with wheelchairs if wheelchair routing is requested
                if (request.wheelchair
                    && !transportNetwork.transitLayer.stopsWheelchair.get(stop)) {
                  return true;
                }

                int weight = time + penaltyFinal;

                // There are penalties for using certain modes, to avoid bike/car trips that are
                // only marginally faster
                // than walking, so we use weights to decide which mode "wins" to access a
                // particular stop.
                if (!weights.containsKey(stop) || weight < weights.get(stop)) {
                  times.put(stop, time);
                  weights.put(stop, weight);
                  stopModeMap.put(stop, mode);
                }

                return true; // iteration should continue
              });
    }

    // we don't want to explore a boatload of access/egress stops. Pick only the closest several
    // hundred.
    // What this means is that in urban environments you'll get on the bus nearby, in suburban
    // environments
    // you may walk/bike/drive a very long way.
    // NB in testing it's not clear this actually does a lot for performance, maybe 1-1.5s
    int stopsFound = times.size();
    if (stopsFound > MAX_ACCESS_STOPS) {
      TIntList timeList = new TIntArrayList();
      times.forEachValue(timeList::add);

      timeList.sort();

      // This gets last time in timeList
      int cutoff =
          timeList.get(
              MAX_ACCESS_STOPS); // it needs to be same as MAX_ACCESS_STOPS since if there are
      // minimally MAX_ACCESS_STOPS + 1 stops the indexes are from
      // 0-MAX_ACCESS_STOPS

      for (TIntIntIterator it = times.iterator(); it.hasNext(); ) {
        it.advance();

        if (it.value() > cutoff) it.remove();
      }

      LOG.warn("{} stops found, using {} nearest", stopsFound, times.size());
    } else {

      LOG.info("{} stops found", stopsFound);
    }

    // return the times, not the weights
    return times;
  }
}