Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add flex stop to TripTimes, return geometries in GraphQL API #3757

Merged
merged 39 commits into from
Dec 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
2a70869
Extract geometries for flex stops
leonardehrenfried Oct 18, 2021
caebd03
Return StopLocation in LegacyGraphQLApi
leonardehrenfried Nov 18, 2021
b3a30b2
Add test for polyline generation
leonardehrenfried Nov 18, 2021
b3b1671
Implement geometries in the legacy GraphQL Api
leonardehrenfried Nov 18, 2021
acf334d
Replace Stop with StopLocation in internal logic
leonardehrenfried Nov 19, 2021
c35a6f1
Disallow getting on or off at flex stops during transit search
leonardehrenfried Nov 22, 2021
5c86045
Remove unused classes
leonardehrenfried Nov 23, 2021
da3400b
Improve serialisation of GeoJSON
leonardehrenfried Nov 23, 2021
985c90d
Simplify conversion to GeoJSON
leonardehrenfried Nov 23, 2021
6048568
Use Geometry as return type
leonardehrenfried Nov 23, 2021
de82c39
Add test for public transport routing of flex trips
leonardehrenfried Nov 25, 2021
6c3af20
Add test for skipping interpolation
leonardehrenfried Nov 25, 2021
57b2f7c
Make sure that the original shape is calculated for flex trips
leonardehrenfried Nov 25, 2021
1c527db
Map missing values to null
leonardehrenfried Nov 25, 2021
3ad7cc1
Raptor pickup/dropoff permissions during instanciation
leonardehrenfried Nov 25, 2021
84207bb
Improve documentation
leonardehrenfried Nov 25, 2021
faa1cfb
Add for combination of flex trip with continuous pick up
leonardehrenfried Nov 26, 2021
40e0532
Change interpolation of flex stops
leonardehrenfried Nov 30, 2021
67da4f9
Use correct order for lat/lon
leonardehrenfried Nov 30, 2021
d4c47f3
Add test for coordinate order
leonardehrenfried Dec 1, 2021
51e53e2
Compute centroid at graph build
leonardehrenfried Dec 1, 2021
bb03b28
Use StopLocations in newly added alert handling code
leonardehrenfried Dec 1, 2021
d41bc02
Regenerate GraphQL types after rebase
leonardehrenfried Dec 1, 2021
5828aff
Cast to StopLocation in TransmodelAPI
leonardehrenfried Dec 3, 2021
ee499a7
Remove redundant filter
leonardehrenfried Dec 3, 2021
8675f81
Compute geometry for Station, too
leonardehrenfried Dec 3, 2021
fdb1eae
Initialize stations with coordinates
leonardehrenfried Dec 3, 2021
0d8a7a0
Update snapshot expectations
leonardehrenfried Dec 3, 2021
2427a6c
Rename polylines to googleEncoded
leonardehrenfried Dec 3, 2021
1ac23ea
Add issue when building encountering flex feeds with continuous picku…
leonardehrenfried Dec 3, 2021
f66c43a
Add complete flex integration test
leonardehrenfried Dec 2, 2021
caa0ef6
Add test with two transfers
leonardehrenfried Dec 7, 2021
8594e77
Clean up tests
leonardehrenfried Dec 7, 2021
b75c031
Actually recompute geometries when adding child stations
leonardehrenfried Dec 8, 2021
8f37599
Update snapshot test
leonardehrenfried Dec 8, 2021
223d49c
Update documentation
leonardehrenfried Dec 8, 2021
ec233ff
Return ID if flex stop doesn't have name
leonardehrenfried Dec 8, 2021
a9580aa
Remove flex stops when flex is disabled
leonardehrenfried Dec 9, 2021
69a219c
Add default implementation of getPriority()
leonardehrenfried Dec 10, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package org.opentripplanner.ext.flex;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.opentripplanner.graph_builder.module.FakeGraph.getFileForResource;
import static org.opentripplanner.routing.api.request.StreetMode.FLEXIBLE;
import static org.opentripplanner.routing.core.TraverseMode.BUS;
import static org.opentripplanner.routing.core.TraverseMode.WALK;

import java.io.File;
import java.net.URISyntaxException;
import java.time.Duration;
import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.opentripplanner.ConstantsForTests;
import org.opentripplanner.graph_builder.model.GtfsBundle;
import org.opentripplanner.graph_builder.module.DirectTransferGenerator;
import org.opentripplanner.graph_builder.module.GtfsModule;
import org.opentripplanner.graph_builder.module.StreetLinkerModule;
import org.opentripplanner.model.GenericLocation;
import org.opentripplanner.model.calendar.ServiceDateInterval;
import org.opentripplanner.model.plan.Itinerary;
import org.opentripplanner.routing.RoutingService;
import org.opentripplanner.routing.api.request.RoutingRequest;
import org.opentripplanner.routing.core.TraverseMode;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.standalone.config.RouterConfig;
import org.opentripplanner.standalone.server.Router;
import org.opentripplanner.util.OTPFeature;

/**
* This test checks the combination of transit and flex works.
*/
public class FlexIntegrationTest {

static long dateTime = ZonedDateTime.parse("2021-12-02T12:00:00-05:00[America/New_York]")
.toInstant()
.getEpochSecond();

static Graph graph;
static RoutingService service;
static Router router;

@BeforeAll
static void setup() {
OTPFeature.enableFeatures(Map.of(OTPFeature.FlexRouting, true));
var osmPath = getAbsolutePath(FlexTest.COBB_OSM);
var cobblincGtfsPath = getAbsolutePath(FlexTest.COBB_BUS_30_GTFS);
var martaGtfsPath = getAbsolutePath(FlexTest.MARTA_BUS_856_GTFS);
var flexGtfsPath = getAbsolutePath(FlexTest.COBB_FLEX_GTFS);

graph = ConstantsForTests.buildOsmGraph(osmPath);
addGtfsToGraph(graph, List.of(cobblincGtfsPath, martaGtfsPath, flexGtfsPath));
router = new Router(graph, RouterConfig.DEFAULT);
router.startup();

service = new RoutingService(graph);
}

private static String getAbsolutePath(String cobbOsm) {
try {
return getFileForResource(cobbOsm).getAbsolutePath();
}
catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}

@Test
public void shouldReturnARouteTransferringFromBusToFlex() {
var from = new GenericLocation(33.84329482265106, -84.583740234375);
var to = new GenericLocation(33.86701256815635, -84.61787939071655);

var itin = getItinerary(from, to, 2);

assertEquals(4, itin.legs.size());

var walkToBus = itin.legs.get(0);
assertEquals(TraverseMode.WALK, walkToBus.mode);

var bus = itin.legs.get(1);
assertEquals(BUS, bus.mode);
assertEquals("30", bus.getRoute().getShortName());

var transfer = itin.legs.get(2);
assertEquals(TraverseMode.WALK, transfer.mode);

var flex = itin.legs.get(3);
assertEquals(BUS, flex.mode);
assertEquals("Zone 2", flex.getRoute().getShortName());
assertTrue(flex.flexibleTrip);
}

@Test
public void shouldReturnARouteWithTwoTransfers() {
var from = GenericLocation.fromStopId("ALEX DR@ALEX WAY", "MARTA", "97266");
var to = new GenericLocation(33.86701256815635, -84.61787939071655);

var itin = getItinerary(from, to, 1);

assertEquals(5, itin.legs.size());

var firstBus = itin.legs.get(0);
assertEquals(BUS, firstBus.mode);
assertEquals("856", firstBus.getRoute().getShortName());

var transferToSecondBus = itin.legs.get(1);
assertEquals(WALK, transferToSecondBus.mode);

var secondBus = itin.legs.get(2);
assertEquals(BUS, secondBus.mode);
assertEquals("30", secondBus.getRoute().getShortName());

var transferToFlex = itin.legs.get(3);
assertEquals(WALK, transferToFlex.mode);

var finalFlex = itin.legs.get(4);
assertEquals(BUS, finalFlex.mode);
assertEquals("Zone 2", finalFlex.getRoute().getShortName());
assertTrue(finalFlex.flexibleTrip);
}

private Itinerary getItinerary(GenericLocation from, GenericLocation to, int index) {
RoutingRequest request = new RoutingRequest();
request.dateTime = dateTime;
request.from = from;
request.to = to;
request.numItineraries = 10;
request.searchWindow = Duration.ofHours(2);
request.modes.egressMode = FLEXIBLE;

var result = service.route(request, router);
var itineraries = result.getTripPlan().itineraries;

assertFalse(itineraries.isEmpty());

return itineraries.get(index);
}

private static void addGtfsToGraph(
Graph graph,
List<String> gtfsFiles
) {
var extra = new HashMap<Class<?>, Object>();

// GTFS
var gtfsBundles = gtfsFiles.stream()
.map(f -> new GtfsBundle(new File(f)))
.collect(Collectors.toList());
GtfsModule gtfsModule = new GtfsModule(gtfsBundles, ServiceDateInterval.unbounded());
gtfsModule.buildGraph(graph, extra);

// link stations to streets
StreetLinkerModule streetLinkerModule = new StreetLinkerModule();
streetLinkerModule.buildGraph(graph, extra);

// link flex locations to streets
var flexMapper = new FlexLocationsToStreetEdgesMapper();
flexMapper.buildGraph(graph, new HashMap<>());

// generate direct transfers
var req = new RoutingRequest();

// we don't have a complete coverage of the entire area so use straight lines for transfers
var transfers = new DirectTransferGenerator(600, List.of(req));
transfers.buildGraph(graph, extra);

graph.index();
}


@AfterAll
static void teardown() {
OTPFeature.enableFeatures(Map.of(OTPFeature.FlexRouting, false));
}
}
18 changes: 16 additions & 2 deletions src/ext-test/java/org/opentripplanner/ext/flex/FlexTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static graphql.Assert.assertFalse;

import gnu.trove.set.hash.TIntHashSet;
import java.io.File;
import java.net.URISyntaxException;
import java.time.LocalTime;
import java.util.HashMap;
Expand All @@ -19,6 +20,13 @@

abstract public class FlexTest {

static final String ASPEN_GTFS = "/flex/aspen-flex-on-demand.gtfs.zip";
static final String COBB_FLEX_GTFS = "/flex/cobblinc-scheduled-deviated-flex.gtfs.zip";
static final String COBB_BUS_30_GTFS = "/flex/cobblinc-bus-30-only.gtfs.zip";
static final String MARTA_BUS_856_GTFS = "/flex/marta-bus-856-only.gtfs.zip";
static final String LINCOLN_COUNTY_GBFS = "/flex/lincoln-county-flex.gtfs.zip";
static final String COBB_OSM = "/flex/cobb-county.filtered.osm.pbf";

static final DirectFlexPathCalculator calculator = new DirectFlexPathCalculator(null);
static final ServiceDate serviceDate = new ServiceDate(2021, 4, 11);
static final int secondsSinceMidnight = LocalTime.of(10, 0).toSecondOfDay();
Expand All @@ -27,8 +35,14 @@ abstract public class FlexTest {
static final FlexParameters params = new FlexParameters(300);


static Graph buildFlexGraph(String fileName) throws URISyntaxException {
var file = FakeGraph.getFileForResource(fileName);
static Graph buildFlexGraph(String fileName) {
File file = null;
try {
file = FakeGraph.getFileForResource(fileName);
}
catch (URISyntaxException e) {
throw new RuntimeException(e);
}

var graph = new Graph();
GtfsBundle gtfsBundle = new GtfsBundle(file);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.opentripplanner.PolylineAssert.assertThatPolylinesAreEqual;

import java.net.URISyntaxException;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Map;
Expand All @@ -16,15 +17,23 @@
import org.opentripplanner.ext.flex.trip.ScheduledDeviatedTrip;
import org.opentripplanner.model.FeedScopedId;
import org.opentripplanner.model.FlexStopLocation;
import org.opentripplanner.model.GenericLocation;
import org.opentripplanner.model.StopTime;
import org.opentripplanner.model.plan.Itinerary;
import org.opentripplanner.routing.algorithm.raptor.router.TransitRouter;
import org.opentripplanner.routing.api.request.RoutingRequest;
import org.opentripplanner.routing.core.Fare.FareType;
import org.opentripplanner.routing.core.Money;
import org.opentripplanner.routing.core.State;
import org.opentripplanner.routing.core.WrappedCurrency;
import org.opentripplanner.routing.framework.DebugTimingAggregator;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.graphfinder.NearbyStop;
import org.opentripplanner.routing.location.StreetLocation;
import org.opentripplanner.standalone.config.RouterConfig;
import org.opentripplanner.standalone.server.Router;
import org.opentripplanner.util.OTPFeature;
import org.opentripplanner.util.TestUtils;

/**
* This tests that the feed for the Cobb County Flex service is processed correctly. This service
Expand All @@ -35,10 +44,10 @@
*/
public class ScheduledDeviatedTripTest extends FlexTest {

static final String COBB_COUNTY_GTFS = "/flex/cobblinc-scheduled-deviated-flex.gtfs.zip";

static Graph graph;

float delta = 0.01f;

@Test
public void parseCobbCountyAsScheduledDeviatedTrip() {
var flexTrips = graph.flexTripsById.values();
Expand All @@ -50,6 +59,16 @@ public void parseCobbCountyAsScheduledDeviatedTrip() {
flexTrips.stream().map(FlexTrip::getClass).collect(
Collectors.toSet())
);

var trip = getFlexTrip();
System.out.println(trip.getStops().stream().map(s -> s.getId().getId()).collect(Collectors.toList()));
var stop = trip.getStops().stream().filter(s -> s.getId().getId().equals("cujv")).findFirst().get();
assertEquals(33.85465, stop.getLat(), delta);
assertEquals(-84.60039, stop.getLon(), delta);

var flexZone = trip.getStops().stream().filter(s -> s.getId().getId().equals("zone_3")).findFirst().get();
assertEquals(33.825846635310214, flexZone.getLat(), delta);
assertEquals(-84.63430143459385, flexZone.getLon(), delta);
}

@Test
Expand Down Expand Up @@ -114,14 +133,108 @@ public void calculateDirectFare() {
var itinerary = itineraries.iterator().next();
assertFalse(itinerary.fare.fare.isEmpty());

assertEquals(new Money(new WrappedCurrency("USD"), 250), itinerary.fare.getFare(FareType.regular));
assertEquals(
new Money(new WrappedCurrency("USD"), 250),
itinerary.fare.getFare(FareType.regular)
);

OTPFeature.enableFeatures(Map.of(OTPFeature.FlexRouting, false));
}


/**
* Trips which consist of flex and fixed-schedule stops should work in transit mode.
* <p>
* The flex stops will show up as intermediate stops (without a departure/arrival time) but you
* cannot board or alight.
*/
@Test
public void flexTripInTransitMode() {
var feedId = graph.getFeedIds().iterator().next();

var router = new Router(graph, RouterConfig.DEFAULT);
router.startup();

// from zone 3 to zone 2
var from = GenericLocation.fromStopId(
"Transfer Point for Route 30",
feedId,
"cujv"
);
var to = GenericLocation.fromStopId(
"Zone 1 - PUBLIX Super Market,Zone 1 Collection Point",
feedId,
"yz85"
);

var itineraries = getItineraries(from, to, router);

assertEquals(1, itineraries.size());

var itin = itineraries.get(0);
var leg = itin.legs.get(0);

assertEquals("cujv", leg.from.stop.getId().getId());
assertEquals("yz85", leg.to.stop.getId().getId());

var intermediateStops = leg.intermediateStops;
assertEquals(1, intermediateStops.size());
assertEquals("zone_1", intermediateStops.get(0).place.stop.getId().getId());

assertThatPolylinesAreEqual(
leg.legGeometry.getPoints(),
"kfsmEjojcOa@eBRKfBfHR|ALjBBhVArMG|OCrEGx@OhAKj@a@tAe@hA]l@MPgAnAgw@nr@cDxCm@t@c@t@c@x@_@~@]pAyAdIoAhG}@lE{AzHWhAtt@t~Aj@tAb@~AXdBHn@FlBC`CKnA_@nC{CjOa@dCOlAEz@E|BRtUCbCQ~CWjD??qBvXBl@kBvWOzAc@dDOx@sHv]aIG?q@@c@ZaB\\mA"
);

}

/**
* We add flex trips, that can potentially not have a departure and arrival time, to the trip.
* <p>
* Normally these trip times are interpolated/repaired during the graph build but for flex this
* is exactly what we don't want. Here we check that the interpolation process is skipped.
*
* @see org.opentripplanner.gtfs.RepairStopTimesForEachTripOperation#interpolateStopTimes(List)
*/
@Test
public void shouldNotInterpolateFlexTimes() {
var feedId = graph.getFeedIds().iterator().next();
var pattern = graph.tripPatternForId.get(new FeedScopedId(feedId, "090z:0:01"));

assertEquals(3, pattern.getStops().size());

var tripTimes = pattern.getScheduledTimetable().getTripTimes(0);
var arrivalTime = tripTimes.getArrivalTime(1);

assertEquals(StopTime.MISSING_VALUE, arrivalTime);
}

/**
* Checks that trips which have continuous pick up/drop off set are parsed correctly.
*/
@Test
public void parseContinuousPickup() {
var lincolnGraph = FlexTest.buildFlexGraph(LINCOLN_COUNTY_GBFS);
assertNotNull(lincolnGraph);
}

private static List<Itinerary> getItineraries(
GenericLocation from,
GenericLocation to,
Router router
) {
RoutingRequest request = new RoutingRequest();
request.dateTime = TestUtils.dateInSeconds("America/Atlanta", 2021, 11, 25, 12, 0, 0);
request.from = from;
request.to = to;

var result = TransitRouter.route(request, router, new DebugTimingAggregator());
return result.getItineraries();
}

@BeforeAll
static void setup() throws URISyntaxException {
graph = FlexTest.buildFlexGraph(COBB_COUNTY_GTFS);
static void setup() {
graph = FlexTest.buildFlexGraph(COBB_FLEX_GTFS);
}

private static NearbyStop getNearbyStop(FlexTrip trip) {
Expand Down
Loading