/** * Copyright (C) 2011 Brian Ferris <bdferris@onebusaway.org> * Copyright (C) 2011 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.onebusaway.transit_data_federation.impl.beans.itineraries; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang.ObjectUtils; import org.onebusaway.collections.CollectionsLibrary; import org.onebusaway.exceptions.NoSuchTripServiceException; import org.onebusaway.exceptions.ServiceException; import org.onebusaway.geospatial.model.CoordinatePoint; import org.onebusaway.geospatial.model.EncodedPolylineBean; import org.onebusaway.geospatial.services.PolylineEncoder; import org.onebusaway.geospatial.services.SphericalGeometryLibrary; import org.onebusaway.gtfs.model.AgencyAndId; import org.onebusaway.transit_data.model.ListBean; import org.onebusaway.transit_data.model.StopBean; import org.onebusaway.transit_data.model.oba.LocalSearchResult; import org.onebusaway.transit_data.model.oba.MinTravelTimeToStopsBean; import org.onebusaway.transit_data.model.oba.TimedPlaceBean; import org.onebusaway.transit_data.model.schedule.FrequencyBean; import org.onebusaway.transit_data.model.tripplanning.ConstraintsBean; import org.onebusaway.transit_data.model.tripplanning.EdgeNarrativeBean; import org.onebusaway.transit_data.model.tripplanning.ItinerariesBean; import org.onebusaway.transit_data.model.tripplanning.ItineraryBean; import org.onebusaway.transit_data.model.tripplanning.LegBean; import org.onebusaway.transit_data.model.tripplanning.LocationBean; import org.onebusaway.transit_data.model.tripplanning.Modes; import org.onebusaway.transit_data.model.tripplanning.StreetLegBean; import org.onebusaway.transit_data.model.tripplanning.TransitLegBean; import org.onebusaway.transit_data.model.tripplanning.TransitLocationBean; import org.onebusaway.transit_data.model.tripplanning.TransitShedConstraintsBean; import org.onebusaway.transit_data.model.tripplanning.VertexBean; import org.onebusaway.transit_data.model.trips.TripBean; import org.onebusaway.transit_data_federation.impl.beans.ApplicationBeanLibrary; import org.onebusaway.transit_data_federation.impl.beans.FrequencyBeanLibrary; import org.onebusaway.transit_data_federation.impl.otp.OBAState; import org.onebusaway.transit_data_federation.impl.otp.OBATraverseOptions; import org.onebusaway.transit_data_federation.impl.otp.graph.AbstractBlockVertex; import org.onebusaway.transit_data_federation.impl.otp.graph.AbstractStopVertex; import org.onebusaway.transit_data_federation.impl.otp.graph.ArrivalVertex; import org.onebusaway.transit_data_federation.impl.otp.graph.BlockArrivalVertex; import org.onebusaway.transit_data_federation.impl.otp.graph.BlockDepartureVertex; import org.onebusaway.transit_data_federation.impl.otp.graph.DepartureVertex; import org.onebusaway.transit_data_federation.impl.otp.graph.tp.TPBlockArrivalVertex; import org.onebusaway.transit_data_federation.impl.otp.graph.tp.TPBlockDepartureVertex; import org.onebusaway.transit_data_federation.impl.otp.graph.tp.TPTransferEdge; import org.onebusaway.transit_data_federation.model.ShapePoints; import org.onebusaway.transit_data_federation.model.narrative.StopTimeNarrative; import org.onebusaway.transit_data_federation.services.AgencyAndIdLibrary; import org.onebusaway.transit_data_federation.services.ArrivalAndDepartureQuery; import org.onebusaway.transit_data_federation.services.ArrivalAndDepartureService; import org.onebusaway.transit_data_federation.services.beans.ItinerariesBeanService; import org.onebusaway.transit_data_federation.services.beans.StopBeanService; import org.onebusaway.transit_data_federation.services.beans.TripBeanService; import org.onebusaway.transit_data_federation.services.blocks.BlockCalendarService; import org.onebusaway.transit_data_federation.services.blocks.BlockInstance; import org.onebusaway.transit_data_federation.services.blocks.BlockTripInstance; import org.onebusaway.transit_data_federation.services.blocks.BlockTripInstanceLibrary; import org.onebusaway.transit_data_federation.services.narrative.NarrativeService; import org.onebusaway.transit_data_federation.services.otp.OTPConfigurationService; import org.onebusaway.transit_data_federation.services.otp.TransitShedPathService; import org.onebusaway.transit_data_federation.services.realtime.ArrivalAndDepartureInstance; import org.onebusaway.transit_data_federation.services.realtime.BlockLocation; import org.onebusaway.transit_data_federation.services.shapes.ShapePointService; import org.onebusaway.transit_data_federation.services.transit_graph.BlockEntry; import org.onebusaway.transit_data_federation.services.transit_graph.BlockStopTimeEntry; import org.onebusaway.transit_data_federation.services.transit_graph.BlockTripEntry; import org.onebusaway.transit_data_federation.services.transit_graph.FrequencyEntry; import org.onebusaway.transit_data_federation.services.transit_graph.StopEntry; import org.onebusaway.transit_data_federation.services.transit_graph.StopTimeEntry; import org.onebusaway.transit_data_federation.services.transit_graph.TransitGraphDao; import org.onebusaway.transit_data_federation.services.transit_graph.TripEntry; import org.onebusaway.transit_data_federation.services.tripplanner.ItinerariesService; import org.opentripplanner.routing.core.Edge; import org.opentripplanner.routing.core.EdgeNarrative; import org.opentripplanner.routing.core.Graph; import org.opentripplanner.routing.core.GraphVertex; import org.opentripplanner.routing.core.HasEdges; import org.opentripplanner.routing.core.State; import org.opentripplanner.routing.core.TraverseMode; import org.opentripplanner.routing.core.Vertex; import org.opentripplanner.routing.edgetype.StreetEdge; import org.opentripplanner.routing.edgetype.StreetTraversalPermission; import org.opentripplanner.routing.edgetype.StreetVertex; import org.opentripplanner.routing.services.GraphService; import org.opentripplanner.routing.services.StreetVertexIndexService; import org.opentripplanner.routing.spt.BasicShortestPathTree; import org.opentripplanner.routing.spt.GraphPath; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.LineString; @Component public class ItinerariesBeanServiceImpl implements ItinerariesBeanService { private static final String MODE_WALK = "walk"; private static final String MODE_BICYCLE = "bicycle"; private static final String MODE_TRANSIT = "transit"; private ItinerariesService _itinerariesService; private TransitShedPathService _transitShedPathService; private StreetVertexIndexService _streetVertexIndexService; private GraphService _graphService; private OTPConfigurationService _otpConfigurationService; private TripBeanService _tripBeanService; private NarrativeService _narrativeService; private StopBeanService _stopBeanService; private ShapePointService _shapePointService; private TransitGraphDao _transitGraphDao; private ArrivalAndDepartureService _arrivalAndDepartureService; private BlockCalendarService _blockCalendarService; private boolean _enabled = true; @Autowired public void setItinerariesService(ItinerariesService itinerariesService) { _itinerariesService = itinerariesService; } @Autowired public void setTransitShedPathService( TransitShedPathService transitShedPathService) { _transitShedPathService = transitShedPathService; } @Autowired public void setStreetVertexIndexService( StreetVertexIndexService streetVertexIndexService) { _streetVertexIndexService = streetVertexIndexService; } @Autowired public void setGraphService(GraphService graphService) { _graphService = graphService; } @Autowired public void setOtpConfigurationService( OTPConfigurationService otpConfigurationService) { _otpConfigurationService = otpConfigurationService; } @Autowired public void setTripBeanService(TripBeanService tripBeanService) { _tripBeanService = tripBeanService; } @Autowired public void setStopBeanService(StopBeanService stopBeanService) { _stopBeanService = stopBeanService; } @Autowired public void setNarrativeService(NarrativeService narrativeService) { _narrativeService = narrativeService; } @Autowired public void setShapePointService(ShapePointService shapePointService) { _shapePointService = shapePointService; } @Autowired public void setTransitGraphDao(TransitGraphDao transitGraphDao) { _transitGraphDao = transitGraphDao; } @Autowired public void setArrivalAndDepartureService( ArrivalAndDepartureService arrivalAndDepartureService) { _arrivalAndDepartureService = arrivalAndDepartureService; } @Autowired public void setBlockCalendarService(BlockCalendarService blockCalendarService) { _blockCalendarService = blockCalendarService; } public void setEnabled(boolean enabled) { _enabled = enabled; } /**** * {@link ItinerariesBeanService} Interface ****/ @Override public ItinerariesBean getItinerariesBetween(TransitLocationBean from, TransitLocationBean to, long targetTime, ConstraintsBean constraints) throws ServiceException { if (!_enabled) throw new ServiceException("service disabled"); OBATraverseOptions options = createTraverseOptions(); applyConstraintsToOptions(constraints, options); List<GraphPath> paths = _itinerariesService.getItinerariesBetween(from, to, targetTime, options); LocationBean fromBean = getPointAsLocation(from); LocationBean toBean = getPointAsLocation(to); ItinerariesBean itineraries = getPathsAsItineraries(paths, fromBean, toBean, options); ensureSelectedItineraryIsIncluded(from, to, targetTime, itineraries, constraints.getSelectedItinerary(), options); if (options.isArriveBy()) Collections.sort(itineraries.getItineraries(), new SortByArrival()); else Collections.sort(itineraries.getItineraries(), new SortByDeparture()); return itineraries; } @Override public ListBean<VertexBean> getStreetGraphForRegion(double latFrom, double lonFrom, double latTo, double lonTo) { if (!_enabled) throw new ServiceException("service disabled"); double x1 = Math.min(lonFrom, lonTo); double x2 = Math.max(lonFrom, lonTo); double y1 = Math.min(latFrom, latTo); double y2 = Math.max(latFrom, latTo); Envelope env = new Envelope(x1, x2, y1, y2); Collection<Vertex> vertices = _streetVertexIndexService.getVerticesForEnvelope(env); Map<Vertex, VertexBean> beansByVertex = new HashMap<Vertex, VertexBean>(); for (Vertex vertex : vertices) getVertexAsBean(beansByVertex, vertex); for (Vertex vertex : vertices) { Collection<Edge> edges = null; if (vertex instanceof HasEdges) { HasEdges hasEdges = (HasEdges) vertex; edges = hasEdges.getOutgoing(); } else { Graph graph = _graphService.getGraph(); GraphVertex gv = graph.getGraphVertex(vertex.getLabel()); if (gv != null) edges = gv.getOutgoing(); } if (edges != null) { VertexBean from = getVertexAsBean(beansByVertex, vertex); List<EdgeNarrativeBean> edgeNarratives = new ArrayList<EdgeNarrativeBean>(); for (Edge edge : edges) { if (edge instanceof EdgeNarrative) { EdgeNarrative narrative = (EdgeNarrative) edge; EdgeNarrativeBean narrativeBean = new EdgeNarrativeBean(); narrativeBean.setName(narrative.getName()); Geometry geom = narrative.getGeometry(); if (geom != null) { List<CoordinatePoint> path = new ArrayList<CoordinatePoint>(); appendGeometryToPath(geom, path, true); EncodedPolylineBean polyline = PolylineEncoder.createEncodings(path); narrativeBean.setPath(polyline.getPoints()); } narrativeBean.setFrom(from); narrativeBean.setTo(getVertexAsBean(beansByVertex, narrative.getToVertex())); Map<String, Object> tags = new HashMap<String, Object>(); if (edge instanceof StreetEdge) { StreetEdge streetEdge = (StreetEdge) edge; StreetTraversalPermission permission = streetEdge.getPermission(); if (permission != null) tags.put("access", permission.toString().toLowerCase()); } if (!tags.isEmpty()) narrativeBean.setTags(tags); edgeNarratives.add(narrativeBean); } } if (!edgeNarratives.isEmpty()) from.setOutgoing(edgeNarratives); } } List<VertexBean> beans = new ArrayList<VertexBean>(beansByVertex.values()); return new ListBean<VertexBean>(beans, false); } @Override public MinTravelTimeToStopsBean getMinTravelTimeToStopsFrom( CoordinatePoint location, long time, TransitShedConstraintsBean constraints) { if (!_enabled) throw new ServiceException("service disabled"); OBATraverseOptions options = createTraverseOptions(); applyConstraintsToOptions(constraints.getConstraints(), options); Coordinate c = new Coordinate(location.getLon(), location.getLat()); Vertex origin = _streetVertexIndexService.getClosestVertex(c, options); State originState = new OBAState(time, origin, options); BasicShortestPathTree tree = _transitShedPathService.getTransitShed(origin, originState, options); Map<StopEntry, Long> results = new HashMap<StopEntry, Long>(); for (State state : tree.getAllStates()) { OBAState obaState = (OBAState) state; Vertex v = state.getVertex(); if (v instanceof AbstractStopVertex) { AbstractStopVertex stopVertex = (AbstractStopVertex) v; StopEntry stop = stopVertex.getStop(); long initialWaitTime = obaState.getInitialWaitTime(); long duration = Math.abs(state.getTime() - time) - initialWaitTime; if (!results.containsKey(stop) || results.get(stop) > duration) results.put(stop, duration); } else if (v instanceof AbstractBlockVertex) { AbstractBlockVertex blockVertex = (AbstractBlockVertex) v; ArrivalAndDepartureInstance instance = blockVertex.getInstance(); StopEntry stop = instance.getStop(); long initialWaitTime = obaState.getInitialWaitTime(); long duration = Math.abs(state.getTime() - time) - initialWaitTime; if (!results.containsKey(stop) || results.get(stop) > duration) results.put(stop, duration); } } return getStopTravelTimesAsResultsBean(results, options.speed); } @Override public List<TimedPlaceBean> getLocalPaths(ConstraintsBean constraints, MinTravelTimeToStopsBean travelTimes, List<LocalSearchResult> localResults) throws ServiceException { if (!_enabled) throw new ServiceException("service disabled"); long maxTripLength = constraints.getMaxTripDuration() * 1000; List<TimedPlaceBean> beans = new ArrayList<TimedPlaceBean>(); double walkingVelocity = travelTimes.getWalkingVelocity() / 1000; ConstraintsBean walkConstraints = new ConstraintsBean(constraints); walkConstraints.setModes(CollectionsLibrary.set(Modes.WALK)); for (LocalSearchResult result : localResults) { double placeLat = result.getLat(); double placeLon = result.getLon(); List<TripToStop> closestStops = new ArrayList<TripToStop>(); for (int index = 0; index < travelTimes.getSize(); index++) { String stopIdAsString = travelTimes.getStopId(index); AgencyAndId stopId = AgencyAndIdLibrary.convertFromString(stopIdAsString); long currentTripDuration = travelTimes.getTravelTime(index); double stopLat = travelTimes.getStopLat(index); double stopLon = travelTimes.getStopLon(index); double d = SphericalGeometryLibrary.distance(stopLat, stopLon, placeLat, placeLon); double t = currentTripDuration + d / walkingVelocity; if (d <= constraints.getMaxWalkingDistance() && t < maxTripLength) { closestStops.add(new TripToStop(stopId, currentTripDuration, t, index)); } } if (closestStops.isEmpty()) continue; Collections.sort(closestStops); double minTime = 0; TripToStop minStop = null; TransitLocationBean place = new TransitLocationBean(); place.setLat(result.getLat()); place.setLon(result.getLon()); for (TripToStop o : closestStops) { long currentTripDuration = o.getTransitTimeToStop(); double minTimeToPlace = o.getMinTansitTimeToPlace(); // Short circuit if there is no way any of the remaining trips is going // to be better than our current winner if (minStop != null && minTimeToPlace > minTime) break; int remainingTime = (int) ((maxTripLength - currentTripDuration) / 1000); walkConstraints.setMaxTripDuration(remainingTime); int index = o.getIndex(); TransitLocationBean stopLocation = new TransitLocationBean(); stopLocation.setLat(travelTimes.getStopLat(index)); stopLocation.setLon(travelTimes.getStopLon(index)); ItinerariesBean itineraries = getItinerariesBetween(stopLocation, place, System.currentTimeMillis(), walkConstraints); for (ItineraryBean plan : itineraries.getItineraries()) { double t = currentTripDuration + (plan.getEndTime() - plan.getStartTime()); if (minStop == null || t < minTime) { minTime = t; minStop = o; } } } if (minStop != null && minTime <= maxTripLength) { TimedPlaceBean bean = new TimedPlaceBean(); bean.setPlaceId(result.getId()); bean.setStopId(ApplicationBeanLibrary.getId(minStop.getStopId())); bean.setTime((int) (minTime / 1000)); beans.add(bean); } } return beans; } /**** * Private Methods ****/ private OBATraverseOptions createTraverseOptions() { OBATraverseOptions options = _otpConfigurationService.createTraverseOptions(); return options; } private void applyConstraintsToOptions(ConstraintsBean constraints, OBATraverseOptions options) { _otpConfigurationService.applyConstraintsToTraverseOptions(constraints, options); } private LocationBean getPointAsLocation(TransitLocationBean p) { LocationBean bean = new LocationBean(); bean.setLocation(new CoordinatePoint(p.getLat(), p.getLon())); return bean; } private ItinerariesBean getPathsAsItineraries(List<GraphPath> paths, LocationBean from, LocationBean to, OBATraverseOptions options) { ItinerariesBean bean = new ItinerariesBean(); bean.setFrom(from); bean.setTo(to); List<ItineraryBean> beans = new ArrayList<ItineraryBean>(); bean.setItineraries(beans); boolean computationTimeLimitReached = false; if (!CollectionsLibrary.isEmpty(paths)) { for (GraphPath path : paths) { ItineraryBean itinerary = getPathAsItinerary(path, options); beans.add(itinerary); } } bean.setComputationTimeLimitReached(computationTimeLimitReached); return bean; } private ItineraryBean getPathAsItinerary(GraphPath path, OBATraverseOptions options) { ItineraryBean itinerary = new ItineraryBean(); State startState = path.states.getFirst(); State endState = path.states.getLast(); itinerary.setStartTime(startState.getTime()); itinerary.setEndTime(endState.getTime()); List<LegBean> legs = new ArrayList<LegBean>(); itinerary.setLegs(legs); /** * We set the current state index to 1, skipping the first state, since it * has no back edge */ List<State> states = new ArrayList<State>(path.states); int currentIndex = 1; while (currentIndex < states.size()) { State state = states.get(currentIndex); EdgeNarrative edgeNarrative = state.getBackEdgeNarrative(); TraverseMode mode = edgeNarrative.getMode(); if (mode.isTransit()) { currentIndex = extendTransitLeg(states, currentIndex, options, legs); } else { currentIndex = extendStreetLeg(states, currentIndex, mode, legs); } } return itinerary; } private int extendTransitLeg(List<State> states, int currentIndex, OBATraverseOptions options, List<LegBean> legs) { TransitLegBuilder builder = new TransitLegBuilder(); while (currentIndex < states.size()) { State state = states.get(currentIndex); Edge edge = state.getBackEdge(); EdgeNarrative narrative = state.getBackEdgeNarrative(); TraverseMode mode = narrative.getMode(); if (!mode.isTransit()) break; Vertex vFrom = narrative.getFromVertex(); Vertex vTo = narrative.getToVertex(); if (vFrom instanceof BlockDepartureVertex) { builder = extendTransitLegWithDepartureAndArrival(legs, builder, (BlockDepartureVertex) vFrom, (BlockArrivalVertex) vTo); } else if (vFrom instanceof TPBlockDepartureVertex) { builder = extendTransitLegWithTPDepartureAndArrival(legs, builder, (TPBlockDepartureVertex) vFrom, (TPBlockArrivalVertex) vTo); } else if (vFrom instanceof BlockArrivalVertex) { builder = extendTransitLegWithArrival(legs, builder, (BlockArrivalVertex) vFrom, vTo, state, options); } else if (vFrom instanceof ArrivalVertex) { if (vTo instanceof BlockDepartureVertex) { /** * This vertex combination occurs when we are doing an "arrive by" * trip and we need to do a transfer between two stops. */ ArrivalVertex fromStopVertex = (ArrivalVertex) vFrom; StopEntry fromStop = fromStopVertex.getStop(); BlockDepartureVertex toStopVertex = (BlockDepartureVertex) vTo; ArrivalAndDepartureInstance departureInstance = toStopVertex.getInstance(); StopEntry toStop = departureInstance.getStop(); addTransferLegIfNeeded(state, fromStop, toStop, options, legs); } } else if (edge instanceof TPTransferEdge) { TPTransferEdge transferEdge = (TPTransferEdge) edge; StopEntry fromStop = transferEdge.getFromStop(); StopEntry toStop = transferEdge.getToStop(); addTransferLegIfNeeded(state, fromStop, toStop, options, legs); } currentIndex++; } return currentIndex; } private void addTransferLegIfNeeded(State state, StopEntry fromStop, StopEntry toStop, OBATraverseOptions options, List<LegBean> legs) { if (!fromStop.equals(toStop)) { long timeFrom = state.getBackState().getTime(); long timeTo = state.getTime(); GraphPath path = _itinerariesService.getWalkingItineraryBetweenStops( fromStop, toStop, new Date(timeFrom), options); if (path != null) { ItineraryBean walk = getPathAsItinerary(path, options); scaleItinerary(walk, timeFrom, timeTo); legs.addAll(walk.getLegs()); } } } private TransitLegBuilder extendTransitLegWithDepartureAndArrival( List<LegBean> legs, TransitLegBuilder builder, BlockDepartureVertex vFrom, BlockArrivalVertex vTo) { ArrivalAndDepartureInstance from = vFrom.getInstance(); ArrivalAndDepartureInstance to = vTo.getInstance(); return extendTransitLegWithDepartureAndArrival(legs, builder, from, to); } private TransitLegBuilder extendTransitLegWithTPDepartureAndArrival( List<LegBean> legs, TransitLegBuilder builder, TPBlockDepartureVertex vFrom, TPBlockArrivalVertex vTo) { ArrivalAndDepartureInstance from = vFrom.getDeparture(); ArrivalAndDepartureInstance to = vFrom.getArrival(); builder = extendTransitLegWithDepartureAndArrival(legs, builder, from, to); return getTransitLegBuilderAsLeg(builder, legs); } private TransitLegBuilder extendTransitLegWithDepartureAndArrival( List<LegBean> legs, TransitLegBuilder builder, ArrivalAndDepartureInstance from, ArrivalAndDepartureInstance to) { BlockTripEntry tripFrom = from.getBlockTrip(); BlockTripEntry tripTo = to.getBlockTrip(); if (builder.getBlockTripInstanceFrom() == null) { builder.setScheduledDepartureTime(from.getScheduledDepartureTime()); builder.setPredictedDepartureTime(from.getPredictedDepartureTime()); builder.setBlockTripInstanceFrom(from.getBlockTripInstance()); builder.setFromStop(from); } if (!tripFrom.equals(tripTo)) { /** * We switch trips during the course of the block, so we clean up the * current leg and introduce a new one */ /** * We just split the difference for now */ long scheduledTransitionTime = (from.getScheduledDepartureTime() + to.getScheduledArrivalTime()) / 2; long predictedTransitionTime = 0; if (from.isPredictedDepartureTimeSet() && to.isPredictedArrivalTimeSet()) predictedTransitionTime = (from.getPredictedDepartureTime() + to.getPredictedArrivalTime()) / 2; builder.setScheduledArrivalTime(scheduledTransitionTime); builder.setPredictedArrivalTime(predictedTransitionTime); builder.setToStop(null); builder.setNextTrip(tripTo); getTransitLegBuilderAsLeg(builder, legs); builder = new TransitLegBuilder(); builder.setScheduledDepartureTime(scheduledTransitionTime); builder.setPredictedDepartureTime(predictedTransitionTime); builder.setBlockTripInstanceTo(to.getBlockTripInstance()); } builder.setToStop(to); builder.setScheduledArrivalTime(to.getScheduledArrivalTime()); builder.setPredictedArrivalTime(to.getPredictedArrivalTime()); return builder; } private TransitLegBuilder extendTransitLegWithArrival(List<LegBean> legs, TransitLegBuilder builder, BlockArrivalVertex arrival, Vertex vTo, State state, OBATraverseOptions options) { /** * Did we finish up a transit leg? */ if (vTo instanceof ArrivalVertex) { /** * We've finished up our transit leg, so publish the leg */ builder = getTransitLegBuilderAsLeg(builder, legs); } /** * Did we have a transfer to another stop? */ else if (vTo instanceof DepartureVertex) { /** * We've finished up our transit leg either way, so publish the leg */ builder = getTransitLegBuilderAsLeg(builder, legs); /** * We've possibly transfered to another stop, so we need to insert the * walk leg */ ArrivalAndDepartureInstance fromStopTimeInstance = arrival.getInstance(); StopEntry fromStop = fromStopTimeInstance.getStop(); DepartureVertex toStopVertex = (DepartureVertex) vTo; StopEntry toStop = toStopVertex.getStop(); addTransferLegIfNeeded(state, fromStop, toStop, options, legs); } return builder; } private TransitLegBuilder getTransitLegBuilderAsLeg( TransitLegBuilder builder, List<LegBean> legs) { BlockTripInstance blockTripInstanceFrom = builder.getBlockTripInstanceFrom(); if (blockTripInstanceFrom == null) return new TransitLegBuilder(); LegBean leg = createTransitLegFromBuilder(builder); legs.add(leg); return new TransitLegBuilder(); } private LegBean createTransitLegFromBuilder(TransitLegBuilder builder) { BlockTripInstance blockTripInstanceFrom = builder.getBlockTripInstanceFrom(); LegBean leg = new LegBean(); leg.setStartTime(builder.getBestDepartureTime()); leg.setEndTime(builder.getBestArrivalTime()); double distance = getTransitLegBuilderAsDistance(builder); leg.setDistance(distance); leg.setMode(MODE_TRANSIT); TripEntry trip = blockTripInstanceFrom.getBlockTrip().getTrip(); TransitLegBean transitLeg = new TransitLegBean(); leg.setTransitLeg(transitLeg); transitLeg.setServiceDate(blockTripInstanceFrom.getServiceDate()); FrequencyEntry frequencyLabel = blockTripInstanceFrom.getFrequencyLabel(); if (frequencyLabel != null) { FrequencyBean frequency = FrequencyBeanLibrary.getBeanForFrequency( blockTripInstanceFrom.getServiceDate(), frequencyLabel); transitLeg.setFrequency(frequency); } TripBean tripBean = _tripBeanService.getTripForId(trip.getId()); transitLeg.setTrip(tripBean); transitLeg.setScheduledDepartureTime(builder.getScheduledDepartureTime()); transitLeg.setScheduledArrivalTime(builder.getScheduledArrivalTime()); transitLeg.setPredictedDepartureTime(builder.getPredictedDepartureTime()); transitLeg.setPredictedArrivalTime(builder.getPredictedArrivalTime()); String path = getTransitLegBuilderAsPath(builder); transitLeg.setPath(path); applyFromStopDetailsForTransitLeg(builder, transitLeg, leg); applyToStopDetailsForTransitLeg(builder, transitLeg, leg); if (leg.getFrom() == null || leg.getTo() == null && path != null) { List<CoordinatePoint> points = PolylineEncoder.decode(path); if (leg.getFrom() == null) leg.setFrom(points.get(0)); if (leg.getTo() == null) leg.setTo(points.get(points.size() - 1)); } return leg; } private double getTransitLegBuilderAsDistance(TransitLegBuilder builder) { BlockTripInstance blockTripInstanceFrom = builder.getBlockTripInstanceFrom(); BlockTripEntry trip = blockTripInstanceFrom.getBlockTrip(); BlockStopTimeEntry fromStop = null; BlockStopTimeEntry toStop = null; if (builder.getFromStop() != null) fromStop = builder.getFromStop().getBlockStopTime(); if (builder.getToStop() != null) toStop = builder.getToStop().getBlockStopTime(); if (fromStop == null && toStop == null) return trip.getTrip().getTotalTripDistance(); if (fromStop == null && toStop != null) return toStop.getDistanceAlongBlock() - trip.getDistanceAlongBlock(); if (fromStop != null && toStop == null) return trip.getDistanceAlongBlock() + trip.getTrip().getTotalTripDistance() - fromStop.getDistanceAlongBlock(); return toStop.getDistanceAlongBlock() - fromStop.getDistanceAlongBlock(); } private String getTransitLegBuilderAsPath(TransitLegBuilder builder) { BlockTripInstance blockTripInstanceFrom = builder.getBlockTripInstanceFrom(); BlockTripEntry blockTrip = blockTripInstanceFrom.getBlockTrip(); TripEntry trip = blockTrip.getTrip(); AgencyAndId shapeId = trip.getShapeId(); if (shapeId == null) return null; ShapePoints shapePoints = _shapePointService.getShapePointsForShapeId(shapeId); BlockStopTimeEntry fromStop = null; BlockStopTimeEntry toStop = null; if (builder.getFromStop() != null) fromStop = builder.getFromStop().getBlockStopTime(); if (builder.getToStop() != null) toStop = builder.getToStop().getBlockStopTime(); CoordinatePoint nextPoint = null; BlockTripEntry nextBlockTrip = builder.getNextTrip(); if (nextBlockTrip != null) { TripEntry nextTrip = nextBlockTrip.getTrip(); AgencyAndId nextShapeId = nextTrip.getShapeId(); if (nextShapeId != null) { ShapePoints nextShapePoints = _shapePointService.getShapePointsForShapeId(nextShapeId); nextPoint = nextShapePoints.getPointForIndex(0); } } if (fromStop == null && toStop == null) { return ShapeSupport.getFullPath(shapePoints, nextPoint); } if (fromStop == null && toStop != null) { return ShapeSupport.getPartialPathToStop(shapePoints, toStop.getStopTime()); } if (fromStop != null && toStop == null) { return ShapeSupport.getPartialPathFromStop(shapePoints, fromStop.getStopTime(), nextPoint); } return ShapeSupport.getPartialPathBetweenStops(shapePoints, fromStop.getStopTime(), toStop.getStopTime()); } private void applyFromStopDetailsForTransitLeg(TransitLegBuilder builder, TransitLegBean transitLeg, LegBean leg) { ArrivalAndDepartureInstance fromStopTimeInstance = builder.getFromStop(); if (fromStopTimeInstance == null) return; BlockStopTimeEntry bstFrom = fromStopTimeInstance.getBlockStopTime(); StopTimeEntry fromStopTime = bstFrom.getStopTime(); StopTimeNarrative stopTimeNarrative = _narrativeService.getStopTimeForEntry(fromStopTime); transitLeg.setRouteShortName(stopTimeNarrative.getRouteShortName()); transitLeg.setTripHeadsign(stopTimeNarrative.getStopHeadsign()); StopEntry fromStop = fromStopTimeInstance.getStop(); StopBean fromStopBean = _stopBeanService.getStopForId(fromStop.getId()); transitLeg.setFromStop(fromStopBean); transitLeg.setFromStopSequence(fromStopTime.getSequence()); leg.setFrom(fromStop.getStopLocation()); BlockLocation blockLocation = fromStopTimeInstance.getBlockLocation(); if (blockLocation != null) { AgencyAndId vehicleId = blockLocation.getVehicleId(); transitLeg.setVehicleId(AgencyAndIdLibrary.convertToString(vehicleId)); } } private void applyToStopDetailsForTransitLeg(TransitLegBuilder builder, TransitLegBean transitLeg, LegBean leg) { ArrivalAndDepartureInstance toStopTimeInstance = builder.getToStop(); if (toStopTimeInstance == null) return; StopEntry toStop = toStopTimeInstance.getStop(); StopBean toStopBean = _stopBeanService.getStopForId(toStop.getId()); transitLeg.setToStop(toStopBean); BlockStopTimeEntry blockStopTime = toStopTimeInstance.getBlockStopTime(); StopTimeEntry stopTime = blockStopTime.getStopTime(); transitLeg.setToStopSequence(stopTime.getSequence()); leg.setTo(toStop.getStopLocation()); BlockLocation blockLocation = toStopTimeInstance.getBlockLocation(); if (blockLocation != null) { AgencyAndId vehicleId = blockLocation.getVehicleId(); transitLeg.setVehicleId(AgencyAndIdLibrary.convertToString(vehicleId)); } } private int extendStreetLeg(List<State> states, int currentIndex, TraverseMode mode, List<LegBean> legs) { List<State> streetStates = new ArrayList<State>(); while (currentIndex < states.size()) { State state = states.get(currentIndex); EdgeNarrative narrative = state.getBackEdgeNarrative(); TraverseMode edgeMode = narrative.getMode(); if (mode != edgeMode) break; streetStates.add(state); currentIndex++; } if (!streetStates.isEmpty()) { getStreetLegBuilderAsLeg(streetStates, mode, legs); } return currentIndex; } private void getStreetLegBuilderAsLeg(List<State> streetStates, TraverseMode mode, List<LegBean> legs) { List<StreetLegBean> streetLegs = new ArrayList<StreetLegBean>(); StreetLegBean streetLeg = null; List<CoordinatePoint> path = new ArrayList<CoordinatePoint>(); double distance = 0.0; double totalDistance = 0.0; long startTime = 0; long endTime = 0; CoordinatePoint from = null; CoordinatePoint to = null; for (State state : streetStates) { EdgeNarrative edgeResult = state.getBackEdgeNarrative(); Geometry geom = edgeResult.getGeometry(); if (geom == null) { continue; } String streetName = edgeResult.getName(); if (streetLeg == null || !ObjectUtils.equals(streetLeg.getStreetName(), streetName)) { addPathToStreetLegIfApplicable(streetLeg, path, distance); streetLeg = createStreetLeg(state); streetLegs.add(streetLeg); path = new ArrayList<CoordinatePoint>(); appendGeometryToPath(geom, path, true); distance = edgeResult.getDistance(); } else { appendGeometryToPath(geom, path, false); distance += edgeResult.getDistance(); } totalDistance += edgeResult.getDistance(); if (startTime == 0) startTime = state.getBackState().getTime(); endTime = state.getTime(); if (!path.isEmpty()) { if (from == null) from = path.get(0); to = path.get(path.size() - 1); } } addPathToStreetLegIfApplicable(streetLeg, path, distance); LegBean leg = new LegBean(); legs.add(leg); leg.setStartTime(startTime); leg.setEndTime(endTime); leg.setMode(getStreetModeAsString(mode)); leg.setFrom(from); leg.setTo(to); leg.setDistance(totalDistance); leg.setStreetLegs(streetLegs); } private void addPathToStreetLegIfApplicable(StreetLegBean streetLeg, List<CoordinatePoint> path, double distance) { if (streetLeg != null) { EncodedPolylineBean polyline = PolylineEncoder.createEncodings(path); streetLeg.setPath(polyline.getPoints()); streetLeg.setDistance(distance); } } private void appendGeometryToPath(Geometry geom, List<CoordinatePoint> path, boolean includeFirstPoint) { if (geom instanceof LineString) { LineString ls = (LineString) geom; for (int i = 0; i < ls.getNumPoints(); i++) { if (i == 0 && !includeFirstPoint) continue; Coordinate c = ls.getCoordinateN(i); CoordinatePoint p = new CoordinatePoint(c.y, c.x); path.add(p); } } else { throw new IllegalStateException("unknown geometry: " + geom); } } private StreetLegBean createStreetLeg(State state) { StreetLegBean bean = new StreetLegBean(); bean.setStreetName(state.getBackEdgeNarrative().getName()); return bean; } private String getStreetModeAsString(TraverseMode mode) { switch (mode) { case BICYCLE: return MODE_BICYCLE; case WALK: return MODE_WALK; } throw new IllegalStateException("unknown street mode: " + mode); } private void scaleItinerary(ItineraryBean bean, long timeFrom, long timeTo) { long tStart = bean.getStartTime(); long tEnd = bean.getEndTime(); double ratio = (timeTo - timeFrom) / (tEnd - tStart); bean.setStartTime(scaleTime(tStart, timeFrom, ratio, tStart)); bean.setEndTime(scaleTime(tStart, timeFrom, ratio, tEnd)); for (LegBean leg : bean.getLegs()) { leg.setStartTime(scaleTime(tStart, timeFrom, ratio, leg.getStartTime())); leg.setEndTime(scaleTime(tStart, timeFrom, ratio, leg.getEndTime())); } } private long scaleTime(long tStartOrig, long tStartNew, double ratio, long t) { return (long) ((t - tStartOrig) * ratio + tStartNew); } private void ensureSelectedItineraryIsIncluded(TransitLocationBean from, TransitLocationBean to, long targetTime, ItinerariesBean itineraries, ItineraryBean selected, OBATraverseOptions options) { if (selected == null) return; if (!isItinerarySufficientlySpecified(selected)) return; for (ItineraryBean itinerary : itineraries.getItineraries()) { if (isItineraryMatch(itinerary, selected)) { itinerary.setSelected(true); return; } } updateItinerary(selected, from, to, targetTime, options); selected.setSelected(true); itineraries.getItineraries().add(selected); } private boolean isItinerarySufficientlySpecified(ItineraryBean itinerary) { for (LegBean leg : itinerary.getLegs()) { if (!isLegSufficientlySpecified(leg)) return false; } return true; } private boolean isLegSufficientlySpecified(LegBean leg) { if (leg == null) return false; if (leg.getFrom() == null || leg.getTo() == null) return false; String mode = leg.getMode(); if (MODE_TRANSIT.equals(mode)) { if (!isTransitLegSufficientlySpecified(leg.getTransitLeg())) return false; } else if (MODE_WALK.equals(mode) || MODE_BICYCLE.equals(mode)) { } else { return false; } return true; } private boolean isTransitLegSufficientlySpecified(TransitLegBean leg) { if (leg == null) return false; if (leg.getTrip() == null || leg.getTrip().getId() == null) return false; if (leg.getServiceDate() <= 0) return false; return true; } private boolean isItineraryMatch(ItineraryBean a, ItineraryBean b) { List<String> instancesA = getTransitInstancesForItinerary(a); List<String> instancesB = getTransitInstancesForItinerary(b); return instancesA.equals(instancesB); } private List<String> getTransitInstancesForItinerary(ItineraryBean itinerary) { List<String> instances = new ArrayList<String>(); for (LegBean leg : itinerary.getLegs()) { TransitLegBean transitLeg = leg.getTransitLeg(); if (transitLeg != null) { String instance = transitLeg.getTrip().getId() + " " + transitLeg.getServiceDate(); instances.add(instance); } } return instances; } private void updateItinerary(ItineraryBean itinerary, TransitLocationBean from, TransitLocationBean to, long targetTime, OBATraverseOptions options) { List<LegBean> legs = itinerary.getLegs(); int firstTransitLegIndex = -1; /** * Update the legs */ for (int i = 0; i < legs.size(); i++) { LegBean leg = legs.get(i); TransitLegBean transitLeg = leg.getTransitLeg(); if (transitLeg != null) { LegBean updatedLeg = updateTransitLeg(transitLeg, options); legs.set(i, updatedLeg); if (firstTransitLegIndex == -1) firstTransitLegIndex = i; } else if (isStreetLeg(leg)) { Date time = new Date(targetTime); CoordinatePoint walkFrom = leg.getFrom(); CoordinatePoint walkTo = leg.getTo(); /** * Adjust the start and end locations for walk-legs to match the query * points. This is a hack, since it allows the client to pass in the * original unmodified itinerary from a previous to call, but also * slightly change their start or end point (aka user walks some * distance) without having to understand how to update the itinerary * object itself. */ if (i == 0) walkFrom = from.getLocation(); if (i + 1 == legs.size()) walkTo = to.getLocation(); GraphPath path = _itinerariesService.getWalkingItineraryBetweenPoints( walkFrom, walkTo, time, options); if (path == null) { throw new IllegalStateException("expected walking path to exist"); } ItineraryBean walkItinerary = getPathAsItinerary(path, options); legs.set(i, walkItinerary.getLegs().get(0)); } } /** * Update the times */ if (firstTransitLegIndex == -1) { } else { long nextTime = legs.get(firstTransitLegIndex).getStartTime(); for (int i = firstTransitLegIndex - 1; i >= 0; i--) { LegBean leg = legs.get(i); if (isStreetLeg(leg)) { long duration = leg.getEndTime() - leg.getStartTime(); leg.setEndTime(nextTime); leg.setStartTime(nextTime - duration); } nextTime = leg.getStartTime(); } long prevTime = legs.get(firstTransitLegIndex).getEndTime(); for (int i = firstTransitLegIndex + 1; i < legs.size(); i++) { LegBean leg = legs.get(i); if (isStreetLeg(leg)) { long duration = leg.getEndTime() - leg.getStartTime(); leg.setStartTime(prevTime); leg.setEndTime(prevTime + duration); } prevTime = leg.getEndTime(); } itinerary.setStartTime(nextTime); itinerary.setEndTime(prevTime); } } private boolean isStreetLeg(LegBean leg) { return MODE_WALK.equals(leg.getMode()) || MODE_BICYCLE.equals(leg.getMode()); } private LegBean updateTransitLeg(TransitLegBean transitLeg, OBATraverseOptions options) { TransitLegBuilder b = new TransitLegBuilder(); AgencyAndId tripId = AgencyAndIdLibrary.convertFromString(transitLeg.getTrip().getId()); TripEntry trip = _transitGraphDao.getTripEntryForId(tripId); if (trip == null) throw new NoSuchTripServiceException(transitLeg.getTrip().getId()); long serviceDate = transitLeg.getServiceDate(); AgencyAndId vehicleId = null; if (transitLeg.getVehicleId() != null) vehicleId = AgencyAndIdLibrary.convertFromString(transitLeg.getVehicleId()); if (transitLeg.getFromStop() != null && transitLeg.getFromStop().getId() != null) { AgencyAndId fromStopId = AgencyAndIdLibrary.convertFromString(transitLeg.getFromStop().getId()); StopEntry fromStop = _transitGraphDao.getStopEntryForId(fromStopId, true); int fromStopSequence = transitLeg.getFromStopSequence(); ArrivalAndDepartureQuery query = new ArrivalAndDepartureQuery(); query.setStop(fromStop); query.setStopSequence(fromStopSequence); query.setTrip(trip); query.setServiceDate(serviceDate); query.setVehicleId(vehicleId); query.setTime(options.currentTime); ArrivalAndDepartureInstance instance = _arrivalAndDepartureService.getArrivalAndDepartureForStop(query); b.setFromStop(instance); b.setBlockTripInstanceFrom(instance.getBlockTripInstance()); b.setScheduledDepartureTime(instance.getScheduledDepartureTime()); b.setPredictedDepartureTime(instance.getPredictedDepartureTime()); } if (transitLeg.getToStop() != null && transitLeg.getToStop().getId() != null) { AgencyAndId toStopId = AgencyAndIdLibrary.convertFromString(transitLeg.getToStop().getId()); StopEntry toStop = _transitGraphDao.getStopEntryForId(toStopId, true); int toStopSequence = transitLeg.getToStopSequence(); ArrivalAndDepartureQuery query = new ArrivalAndDepartureQuery(); query.setStop(toStop); query.setStopSequence(toStopSequence); query.setTrip(trip); query.setServiceDate(serviceDate); query.setVehicleId(vehicleId); query.setTime(options.currentTime); ArrivalAndDepartureInstance instance = _arrivalAndDepartureService.getArrivalAndDepartureForStop(query); b.setToStop(instance); b.setBlockTripInstanceTo(instance.getBlockTripInstance()); b.setScheduledArrivalTime(instance.getScheduledArrivalTime()); b.setPredictedArrivalTime(instance.getPredictedArrivalTime()); } if (b.getBlockTripInstanceFrom() == null) { BlockEntry block = trip.getBlock(); BlockInstance blockInstance = _blockCalendarService.getBlockInstance( block.getId(), serviceDate); BlockTripInstance tripInstance = BlockTripInstanceLibrary.getBlockTripInstance( blockInstance, trip.getId()); b.setBlockTripInstanceFrom(tripInstance); } return createTransitLegFromBuilder(b); } private VertexBean getVertexAsBean(Map<Vertex, VertexBean> beansByVertex, Vertex vertex) { VertexBean bean = beansByVertex.get(vertex); if (bean == null) { bean = new VertexBean(); bean.setId(vertex.getLabel()); bean.setLocation(new CoordinatePoint(vertex.getY(), vertex.getX())); Map<String, Object> tags = new HashMap<String, Object>(); tags.put("class", vertex.getClass().getName()); if (vertex instanceof StreetVertex) { StreetVertex sv = (StreetVertex) vertex; StreetTraversalPermission perms = sv.getPermission(); if (perms != null) tags.put("access", perms.toString().toLowerCase()); } else if (vertex instanceof AbstractStopVertex) { AbstractStopVertex stopVertex = (AbstractStopVertex) vertex; StopEntry stop = stopVertex.getStop(); StopBean stopBean = _stopBeanService.getStopForId(stop.getId()); tags.put("stop", stopBean); } bean.setTags(tags); beansByVertex.put(vertex, bean); } return bean; } private MinTravelTimeToStopsBean getStopTravelTimesAsResultsBean( Map<StopEntry, Long> results, double walkingVelocity) { int n = results.size(); String[] stopIds = new String[n]; double[] lats = new double[n]; double[] lons = new double[n]; long[] times = new long[n]; int index = 0; String agencyId = null; for (Map.Entry<StopEntry, Long> entry : results.entrySet()) { StopEntry stop = entry.getKey(); agencyId = stop.getId().getAgencyId(); Long time = entry.getValue(); stopIds[index] = ApplicationBeanLibrary.getId(stop.getId()); lats[index] = stop.getStopLat(); lons[index] = stop.getStopLon(); times[index] = time; index++; } return new MinTravelTimeToStopsBean(agencyId, stopIds, lats, lons, times, walkingVelocity); } private static class SortByDeparture implements Comparator<ItineraryBean> { @Override public int compare(ItineraryBean o1, ItineraryBean o2) { long t1 = o1.getStartTime(); long t2 = o2.getStartTime(); return t1 == t2 ? 0 : (t1 < t2 ? -1 : 1); } } private static class SortByArrival implements Comparator<ItineraryBean> { @Override public int compare(ItineraryBean o1, ItineraryBean o2) { long t1 = o1.getEndTime(); long t2 = o2.getEndTime(); return t1 == t2 ? 0 : (t1 > t2 ? -1 : 1); } } private static class TripToStop implements Comparable<TripToStop> { private AgencyAndId _stopId; private long _transitTimeToStop; private double _minTransitTimeToPlace; private int _index; public TripToStop(AgencyAndId stopId, long transitTimeToStop, double minTransitTimeToPlace, int index) { _stopId = stopId; _transitTimeToStop = transitTimeToStop; _minTransitTimeToPlace = minTransitTimeToPlace; _index = index; } public AgencyAndId getStopId() { return _stopId; } public long getTransitTimeToStop() { return _transitTimeToStop; } public double getMinTansitTimeToPlace() { return _minTransitTimeToPlace; } public int getIndex() { return _index; } public int compareTo(TripToStop o) { return _minTransitTimeToPlace == o._minTransitTimeToPlace ? 0 : (_minTransitTimeToPlace < o._minTransitTimeToPlace ? -1 : 1); } } }