这是indexloc提供的服务,不要输入任何密码
Skip to content

switch MVV to new endpoint with realtime data #414

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

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
127 changes: 125 additions & 2 deletions src/de/schildbach/pte/MvvProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@

package de.schildbach.pte;

import java.io.IOException;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand All @@ -27,19 +32,24 @@
import com.google.common.base.Charsets;

import de.schildbach.pte.dto.Line;
import de.schildbach.pte.dto.Location;
import de.schildbach.pte.dto.Point;
import de.schildbach.pte.dto.Position;
import de.schildbach.pte.dto.Product;
import de.schildbach.pte.dto.QueryTripsContext;
import de.schildbach.pte.dto.QueryTripsResult;
import de.schildbach.pte.dto.Style;
import de.schildbach.pte.dto.Style.Shape;

import de.schildbach.pte.dto.Trip;
import de.schildbach.pte.dto.TripOptions;
import okhttp3.HttpUrl;

/**
* @author Andreas Schildbach
*/
public class MvvProvider extends AbstractEfaProvider {
private static final HttpUrl API_BASE = HttpUrl.parse("https://efa.mvv-muenchen.de/mobile/");
private static final HttpUrl API_BASE = HttpUrl.parse("https://efa.mvv-muenchen.de/ng/");

public MvvProvider() {
this(API_BASE);
Expand All @@ -50,7 +60,7 @@ public MvvProvider(final HttpUrl apiBase) {
setIncludeRegionId(false);
setRequestUrlEncoding(Charsets.UTF_8);
setStyles(STYLES);
setSessionCookieName("SIDefaalt"); // SIDefa
setSessionCookieName("SIDefa");
}

@Override
Expand Down Expand Up @@ -157,4 +167,117 @@ protected Position parsePosition(final String position) {
public Point[] getArea() {
return new Point[] { Point.fromDouble(48.140377, 11.560643) };
}

/*
MVV's new EFA uses load balancing. Therefore, stateful API functionality only works
correctly if we coincidentally hit the same server again. The session ID cookie does include
the server ID that the session was created on, but apparently the load balancer does not
respect this.

There were attempts to ask MVV to fix this issue, but they did not offer any help:
https://github.com/schildbach/public-transport-enabler/pull/414#issuecomment-954032588

Thus, we implement queryMoreTrips in a stateless manner by adjusting
the departure/arrival times ourselves. This is the same algorithm that is
also used in the Javascript code on the mobile MVV website at
https://m.mvv-muenchen.de/mvvMobile5/de/index.html#trips
*/

private static class MvvContext implements QueryTripsContext {
public Location from;
public Location via;
public Location to;
public TripOptions options;
public QueryTripsResult result;
public List<Trip> trips;

@Override
public boolean canQueryLater() {
return true;
}

@Override
public boolean canQueryEarlier() {
return true;
}
}

private boolean requestingMoreTrips;

@Override
public QueryTripsResult queryTrips(final Location from, final @Nullable Location via, final Location to,
final Date date, final boolean dep, final @Nullable TripOptions options) throws IOException {
QueryTripsResult result = super.queryTrips(from, via, to, date, dep, options);

if (result.status == QueryTripsResult.Status.OK) {
MvvContext context = new MvvContext();
context.from = from;
context.to = to;
context.via = via;
context.options = options;
context.trips = result.trips;
result = new QueryTripsResult(result.header, result.queryUri, result.from,
result.via, result.to, context, result.trips);
}
return result;
}

@Override
public QueryTripsResult queryMoreTrips(final QueryTripsContext contextObj, final boolean later) throws IOException {
if (!(contextObj instanceof MvvContext)) {
throw new IllegalArgumentException("needs an MvvContext");
}
MvvContext context = (MvvContext) contextObj;

// get departure time of last trip / arrival time of last trip as reference time
int tripIndex;
if (later) {
Trip lastTrip = context.trips.get(context.trips.size() - 1);
// if the last included trip is a walking route, use the previous one
boolean lastTripIsIndividual =
lastTrip.legs.size() == 1 && lastTrip.legs.get(0) instanceof Trip.Individual;
tripIndex = context.trips.size() - (lastTripIsIndividual ? 2 : 1);
} else {
tripIndex = 0;
}
Trip refTrip = context.trips.get(tripIndex);
Date refTime = later ? refTrip.getFirstDepartureTime() : refTrip.getLastArrivalTime();

// adjust time by one minute so that we don't get the same trip again
refTime = addMinutesToDate(refTime, later ? 1 : -1);

requestingMoreTrips = true; // set special options for more trips request
try {
QueryTripsResult result = super.queryTrips(context.from, context.via, context.to,
refTime, later, context.options);

if (result.status == QueryTripsResult.Status.OK) {
context.trips.addAll(later ? context.trips.size() - 1 : 0, result.trips);
result = new QueryTripsResult(result.header, result.queryUri, result.from,
result.via, result.to, context, result.trips);
}

return result;
} finally {
requestingMoreTrips = false; // reset options
}
}

@Override
protected void appendTripRequestParameters(HttpUrl.Builder url, Location from, @Nullable Location via, Location to, Date time, boolean dep, @Nullable TripOptions options) {
super.appendTripRequestParameters(url, from, via, to, time, dep, options);

if (requestingMoreTrips) {
// ensure that the first displayed trip is after the given departure time /
// last displayed trip is before the given arrival time
url.addEncodedQueryParameter("calcOneDirection", "1");
}
}

private Date addMinutesToDate(Date initial, int minutes) {
Calendar c = new GregorianCalendar(timeZone);
c.setTime(initial);
c.add(Calendar.MINUTE, minutes);
return c.getTime();
}
}
42 changes: 24 additions & 18 deletions test/de/schildbach/pte/live/MvvProviderLiveTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

package de.schildbach.pte.live;

import static org.hamcrest.CoreMatchers.anyOf;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
Expand Down Expand Up @@ -46,7 +47,8 @@ public MvvProviderLiveTest() {

@Test
public void nearbyStations() throws Exception {
final NearbyLocationsResult result = queryNearbyStations(new Location(LocationType.STATION, "350"));
final NearbyLocationsResult result =
queryNearbyStations(new Location(LocationType.STATION, "91000350"));
print(result);
}

Expand All @@ -68,13 +70,9 @@ public void nearbyLocationsByCoordinate() throws Exception {

@Test
public void queryDeparturesMarienplatz() throws Exception {
final QueryDeparturesResult result1 = queryDepartures("2", false);
final QueryDeparturesResult result1 = queryDepartures("91000002", false);
assertEquals(QueryDeparturesResult.Status.OK, result1.status);
print(result1);

final QueryDeparturesResult result2 = queryDepartures("1000002", false);
assertEquals(QueryDeparturesResult.Status.OK, result2.status);
print(result2);
}

@Test
Expand All @@ -99,14 +97,14 @@ public void suggestLocationsIncomplete() throws Exception {
public void suggestLocationsWithUmlaut() throws Exception {
final SuggestLocationsResult result = suggestLocations("Grüntal");
print(result);
assertThat(result.getLocations(), hasItem(new Location(LocationType.STATION, "1000619")));
assertThat(result.getLocations(), hasItem(new Location(LocationType.STATION, "91000619")));
}

@Test
public void suggestLocationsFraunhofer() throws Exception {
final SuggestLocationsResult result = suggestLocations("fraunhofer");
print(result);
assertThat(result.getLocations(), hasItem(new Location(LocationType.STATION, "1000150")));
assertThat(result.getLocations(), hasItem(new Location(LocationType.STATION, "91000150")));
}

@Test
Expand Down Expand Up @@ -134,22 +132,30 @@ public void suggestLocationsMarienplatz() throws Exception {
public void suggestAddress() throws Exception {
final SuggestLocationsResult result = suggestLocations("München, Maximilianstr. 1");
print(result);
assertThat(result.getLocations(), hasItem(new Location(LocationType.ADDRESS,
"streetID:3239:1:9162000:9162000:Maximilianstraße:München:Maximilianstraße::Maximilianstraße:80539:ANY:DIVA_ADDRESS:4468763:826437:MVTT:MVV")));
assertThat(result.getLocations(), anyOf(
hasItem(new Location(LocationType.ADDRESS,
"streetID:1500000040::9162000:-1:Maximilianstraße:München:Maximilianstraße::Maximilianstraße: 80539 80538:ANY:DIVA_STREET:1289423:5870091:MRCV:BAY")),
hasItem(new Location(LocationType.ADDRESS,
"streetID:1500000040::9162000:-1:Maximilianstraße:München:Maximilianstraße::Maximilianstraße: 80539 80538:ANY:DIVA_STREET:1289423:5870091:MRCV:bay"))
));
}

@Test
public void suggestStreet() throws Exception {
final SuggestLocationsResult result = suggestLocations("München, Maximilianstr.");
print(result);
assertThat(result.getLocations(), hasItem(new Location(LocationType.ADDRESS,
"streetID:3239::9162000:-1:Maximilianstraße:München:Maximilianstraße::Maximilianstraße: 80539 80538:ANY:DIVA_STREET:4469138:826553:MVTT:MVV")));
assertThat(result.getLocations(), anyOf(
hasItem(new Location(LocationType.ADDRESS,
"streetID:1500000040::9162000:-1:Maximilianstraße:München:Maximilianstraße::Maximilianstraße: 80539 80538:ANY:DIVA_STREET:1289423:5870091:MRCV:BAY")),
hasItem(new Location(LocationType.ADDRESS,
"streetID:1500000040::9162000:-1:Maximilianstraße:München:Maximilianstraße::Maximilianstraße: 80539 80538:ANY:DIVA_STREET:1289423:5870091:MRCV:bay"))
));
}

@Test
public void shortTrip() throws Exception {
final Location from = new Location(LocationType.STATION, "2", "München", "Marienplatz");
final Location to = new Location(LocationType.STATION, "10", "München", "Pasing");
final Location from = new Location(LocationType.STATION, "91000002", "München", "Marienplatz");
final Location to = new Location(LocationType.STATION, "91000010", "München", "Pasing");
final QueryTripsResult result = queryTrips(from, null, to, new Date(), true, null);
print(result);
final QueryTripsResult laterResult = queryMoreTrips(result.context, true);
Expand All @@ -162,7 +168,7 @@ public void shortTrip() throws Exception {
public void longTrip() throws Exception {
final Location from = new Location(LocationType.STATION, "1005530", Point.from1E6(48002924, 11340144),
"Starnberg", "Agentur für Arbeit");
final Location to = new Location(LocationType.STATION, null, null, "Ackermannstraße");
final Location to = new Location(LocationType.STATION, "91000309", null, "Ackermannstraße");
final QueryTripsResult result = queryTrips(from, null, to, new Date(), true, null);
print(result);
}
Expand All @@ -180,7 +186,7 @@ public void tripBetweenCoordinates() throws Exception {
@Test
public void tripBetweenCoordinateAndStation() throws Exception {
final Location from = new Location(LocationType.ADDRESS, null, Point.from1E6(48238341, 11478230));
final Location to = new Location(LocationType.ANY, null, null, "Ostbahnhof");
final Location to = new Location(LocationType.ANY, null, null, "München, Ostbahnhof");
final QueryTripsResult result = queryTrips(from, null, to, new Date(), true, null);
print(result);
final QueryTripsResult laterResult = queryMoreTrips(result.context, true);
Expand Down Expand Up @@ -217,7 +223,7 @@ public void tripBetweenStreets() throws Exception {

@Test
public void tripBetweenStationAndAddress() throws Exception {
final Location from = new Location(LocationType.STATION, "1220", null, "Josephsburg");
final Location from = new Location(LocationType.STATION, "91001220", null, "Josephsburg");
final Location to = new Location(LocationType.ADDRESS, null, Point.from1E6(48188018, 11574239), null,
"München Frankfurter Ring 35");
final QueryTripsResult result = queryTrips(from, null, to, new Date(), true, null);
Expand All @@ -228,7 +234,7 @@ public void tripBetweenStationAndAddress() throws Exception {

@Test
public void tripInvalidStation() throws Exception {
final Location valid = new Location(LocationType.STATION, "2", "München", "Marienplatz");
final Location valid = new Location(LocationType.STATION, "91000002", "München", "Marienplatz");
final Location invalid = new Location(LocationType.STATION, "99999", null, null);
final QueryTripsResult result1 = queryTrips(valid, null, invalid, new Date(), true, null);
assertEquals(QueryTripsResult.Status.UNKNOWN_TO, result1.status);
Expand Down