get berlin/brandenburg connections from classic site rather than mobile site

git-svn-id: https://public-transport-enabler.googlecode.com/svn/trunk@553 0924bc21-9374-b0fa-ee44-9ff1593b38f0
This commit is contained in:
andreas.schildbach@gmail.com 2011-04-13 14:59:52 +00:00
parent 1b6bd8ebfd
commit 9e9cee2111
3 changed files with 273 additions and 361 deletions

View file

@ -19,6 +19,7 @@ package de.schildbach.pte;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
@ -54,9 +55,9 @@ public final class BvgProvider extends AbstractHafasProvider
public static final String OLD_NETWORK_ID = "mobil.bvg.de"; public static final String OLD_NETWORK_ID = "mobil.bvg.de";
private static final long PARSER_DAY_ROLLOVER_THRESHOLD_MS = 12 * 60 * 60 * 1000; private static final long PARSER_DAY_ROLLOVER_THRESHOLD_MS = 12 * 60 * 60 * 1000;
private static final long PARSER_DAY_ROLLDOWN_THRESHOLD_MS = 6 * 60 * 60 * 1000;
private static final String BVG_BASE_URL = "http://mobil.bvg.de"; private static final String BVG_MOBIL_BASE_URL = "http://mobil.bvg.de";
private static final String BVG_BASE_URL = "http://www.fahrinfo-berlin.de";
private static final String API_BASE = "http://mobil.bvg.de/Fahrinfo/bin/"; private static final String API_BASE = "http://mobil.bvg.de/Fahrinfo/bin/";
public BvgProvider() public BvgProvider()
@ -78,7 +79,7 @@ public final class BvgProvider extends AbstractHafasProvider
return true; return true;
} }
private static final String AUTOCOMPLETE_NAME_URL = "http://mobil.bvg.de/Fahrinfo/bin/stboard.bin/dox/dox?input=%s"; private static final String AUTOCOMPLETE_NAME_URL = API_BASE + "stboard.bin/dox/dox?input=%s";
private static final Pattern P_SINGLE_NAME = Pattern.compile(".*?Haltestelleninfo.*?<strong>(.*?)</strong>.*?input=(\\d+)&.*?", Pattern.DOTALL); private static final Pattern P_SINGLE_NAME = Pattern.compile(".*?Haltestelleninfo.*?<strong>(.*?)</strong>.*?input=(\\d+)&.*?", Pattern.DOTALL);
private static final Pattern P_MULTI_NAME = Pattern.compile("<a href=\\\"/Fahrinfo/bin/stboard\\.bin/dox.*?input=(\\d+)&.*?\">\\s*(.*?)\\s*</a>", private static final Pattern P_MULTI_NAME = Pattern.compile("<a href=\\\"/Fahrinfo/bin/stboard\\.bin/dox.*?input=(\\d+)&.*?\">\\s*(.*?)\\s*</a>",
Pattern.DOTALL); Pattern.DOTALL);
@ -201,8 +202,6 @@ public final class BvgProvider extends AbstractHafasProvider
return super.splitNameAndPlace(name); return super.splitNameAndPlace(name);
} }
public static final String STATION_URL_CONNECTION = "http://mobil.bvg.de/Fahrinfo/bin/query.bin/dox";
private String connectionsQueryUri(final Location from, final Location via, final Location to, final Date date, final boolean dep, private String connectionsQueryUri(final Location from, final Location via, final Location to, final Date date, final boolean dep,
final String products) final String products)
{ {
@ -211,7 +210,7 @@ public final class BvgProvider extends AbstractHafasProvider
final StringBuilder uri = new StringBuilder(); final StringBuilder uri = new StringBuilder();
uri.append("http://mobil.bvg.de/Fahrinfo/bin/query.bin/dox"); uri.append(API_BASE).append("query.bin/dn");
uri.append("?REQ0HafasInitialSelection=0"); uri.append("?REQ0HafasInitialSelection=0");
appendLocationBvg(uri, from, "S0", "SID"); appendLocationBvg(uri, from, "S0", "SID");
@ -227,24 +226,53 @@ public final class BvgProvider extends AbstractHafasProvider
for (final char p : products.toCharArray()) for (final char p : products.toCharArray())
{ {
if (p == 'I') if (p == 'I')
uri.append("&vw=5"); {
uri.append("&REQ0JourneyProduct_prod_section_0_5=1");
if (via != null)
uri.append("&REQ0JourneyProduct_prod_section_1_5=1");
}
if (p == 'R') if (p == 'R')
uri.append("&vw=6"); {
uri.append("&REQ0JourneyProduct_prod_section_0_6=1");
if (via != null)
uri.append("&REQ0JourneyProduct_prod_section_1_6=1");
}
if (p == 'S') if (p == 'S')
uri.append("&vw=0"); {
uri.append("&REQ0JourneyProduct_prod_section_0_0=1");
if (via != null)
uri.append("&REQ0JourneyProduct_prod_section_1_0=1");
}
if (p == 'U') if (p == 'U')
uri.append("&vw=1"); {
uri.append("&REQ0JourneyProduct_prod_section_0_1=1");
if (via != null)
uri.append("&REQ0JourneyProduct_prod_section_1_1=1");
}
if (p == 'T') if (p == 'T')
uri.append("&vw=2"); {
uri.append("&REQ0JourneyProduct_prod_section_0_2=1");
if (via != null)
uri.append("&REQ0JourneyProduct_prod_section_1_2=1");
}
if (p == 'B') if (p == 'B')
uri.append("&vw=3"); {
uri.append("&REQ0JourneyProduct_prod_section_0_3=1");
if (via != null)
uri.append("&REQ0JourneyProduct_prod_section_1_3=1");
}
if (p == 'F') if (p == 'F')
uri.append("&vw=4"); {
uri.append("&REQ0JourneyProduct_prod_section_0_4=1");
if (via != null)
uri.append("&REQ0JourneyProduct_prod_section_1_4=1");
}
// FIXME if (p == 'C') // FIXME if (p == 'C')
// TODO Ruftaxi wäre wohl &vw=7 // TODO Ruftaxi wäre wohl &REQ0JourneyProduct_prod_section_0_7=1
} }
uri.append("&start=Suchen"); uri.append("&start=Suchen");
return uri.toString(); return uri.toString();
} }
@ -252,7 +280,9 @@ public final class BvgProvider extends AbstractHafasProvider
{ {
if (location.type == LocationType.ADDRESS && location.hasLocation() && paramWgs != null) if (location.type == LocationType.ADDRESS && location.hasLocation() && paramWgs != null)
{ {
uri.append("&").append(paramWgs).append("=").append(ParserUtils.urlEncode("A=16@X=" + location.lon + "@Y=" + location.lat)); final String v = ParserUtils.urlEncode("A=16@X=" + location.lon + "@Y=" + location.lat
+ (location.name != null ? "@O=" + location.name : ""));
uri.append("&").append(paramWgs).append("=").append(v);
} }
else else
{ {
@ -304,7 +334,7 @@ public final class BvgProvider extends AbstractHafasProvider
if (addresses.isEmpty()) if (addresses.isEmpty())
{ {
return queryConnections(uri, page, from, to); return queryConnections(uri, page);
} }
else if (P_CHECK_FROM.matcher(page).find()) else if (P_CHECK_FROM.matcher(page).find())
{ {
@ -323,46 +353,84 @@ public final class BvgProvider extends AbstractHafasProvider
public QueryConnectionsResult queryMoreConnections(final String uri) throws IOException public QueryConnectionsResult queryMoreConnections(final String uri) throws IOException
{ {
final CharSequence page = ParserUtils.scrape(uri); final CharSequence page = ParserUtils.scrape(uri);
return queryConnections(uri, page, null, null); return queryConnections(uri, page);
} }
private static final Pattern P_LOCATION_ADDRESS = Pattern.compile("\\d{5}.*?,.*"); private Location location(final String typeStr, final String idStr, final String latStr, final String lonStr, final String nameStr)
private Location location(final String str, final Location originalLocation)
{ {
if (P_LOCATION_ADDRESS.matcher(str).matches()) final int id = idStr != null ? Integer.parseInt(idStr) : 0;
return new Location(LocationType.ADDRESS, 0, null, str); final int lat = latStr != null ? (int) (Float.parseFloat(latStr) * 1E6) : 0;
else if (originalLocation != null && str.equals(originalLocation.name)) final int lon = lonStr != null ? (int) (Float.parseFloat(lonStr) * 1E6) : 0;
return originalLocation; final String[] placeAndName = splitNameAndPlace(nameStr);
else if (originalLocation != null && originalLocation.type == LocationType.ADDRESS && str.length() == 0)
return originalLocation; final LocationType type;
else if (str.length() > 0) if (typeStr == null)
return new Location(LocationType.ANY, 0, null, str); type = LocationType.ANY;
else if ("HST".equals(typeStr))
type = LocationType.STATION;
else if ("ADR".equals(typeStr))
type = LocationType.ADDRESS;
else else
return null; throw new IllegalArgumentException("cannot handle: " + typeStr);
return new Location(type, id, lat, lon, placeAndName[0], placeAndName[1]);
} }
private Location location(final String[] track)
{
final int id = track[4].length() > 0 ? Integer.parseInt(track[4]) : 0;
final int lat = Integer.parseInt(track[6]);
final int lon = Integer.parseInt(track[5]);
final String[] placeAndName = splitNameAndPlace(ParserUtils.resolveEntities(track[9]));
final String typeStr = track[1];
final LocationType type;
if ("STATION".equals(typeStr))
type = LocationType.STATION;
else if ("ADDRESS".equals(typeStr))
type = LocationType.ADDRESS;
else
throw new IllegalArgumentException("cannot handle: " + Arrays.toString(track));
return new Location(type, id, lat, lon, placeAndName[0], placeAndName[1]);
}
private static final Pattern P_CONNECTIONS_ALL_DETAILS = Pattern.compile("<a href=\"([^\"]*)\"[^>]*>Details f&uuml;r alle</a>");
private static final Pattern P_CONNECTIONS_HEAD = Pattern.compile(".*?" // private static final Pattern P_CONNECTIONS_HEAD = Pattern.compile(".*?" //
+ "Von: <strong>(.*?)</strong>.*?" // from + "<td headers=\"ivuAnfFrom\"[^>]*>\n" //
+ "Nach: <strong>(.*?)</strong>.*?" // to + "([^\n]*)\n" // from name
+ "Datum: .., (.*?)<br />.*?" // currentDate + "<a href=\"[^\"]*location=(?:(\\d+)|),(?:(\\w+)|),WGS84,(\\d+\\.\\d+),(\\d+\\.\\d+)&.*?" // from id, lat,
+ "(?:<a href=\"(/Fahrinfo/bin/query\\.bin/dox[^\"]*?ScrollDir=2)\">.*?)?" // linkEarlier // lon
+ "(?:<a href=\"(/Fahrinfo/bin/query\\.bin/dox[^\"]*?ScrollDir=1)\">.*?)?" // linkLater + "(?:<td headers=\"ivuAnfVia1\"[^>]*>\n" //
+ "([^\n]*)<.*?)?" // via name
+ "<td headers=\"ivuAnfTo\"[^>]*>\n" //
+ "([^\n]*)\n" // to name
+ "<a href=\"[^\"]*location=(?:(\\d+)|),(?:(\\w+)|),WGS84,(\\d+\\.\\d+),(\\d+\\.\\d+)&.*?" // to id, lat,
// lon
+ "<td headers=\"ivuAnfTime\"[^>]*>.., (\\d{2}\\.\\d{2}\\.\\d{2}) \\d{1,2}:\\d{2}</td>.*?" // date
+ "(?:<a href=\"([^\"]*)\" title=\"fr&uuml;here Verbindungen\"[^>]*?>.*?)?" // linkEarlier
+ "(?:<a href=\"([^\"]*)\" title=\"sp&auml;tere Verbindungen\"[^>]*?>.*?)?" // linkLater
, Pattern.DOTALL); , Pattern.DOTALL);
private static final Pattern P_CONNECTIONS_COARSE = Pattern.compile("<p class=\"con(?:L|D)\">(.+?)</p>", Pattern.DOTALL); private static final Pattern P_CONNECTIONS_COARSE = Pattern
.compile("<form [^>]*name=\"ivuTrackListForm(\\d)\"[^>]*>(.+?)</form>", Pattern.DOTALL);
private static final Pattern P_CONNECTIONS_FINE = Pattern.compile(".*?" // private static final Pattern P_CONNECTIONS_FINE = Pattern.compile(".*?" //
+ "<a href=\"(/Fahrinfo/bin/query\\.bin/dox[^\"]*?)\">" // link + "Verbindungen - Detailansicht - Abfahrt: am (\\d{2}\\.\\d{2}\\.\\d{2}) um \\d{1,2}:\\d{2}.*?" // date
+ "(\\d\\d:\\d\\d)-(\\d\\d:\\d\\d)</a>&nbsp;&nbsp;" // departureTime, arrivalTime + "guiVCtrl_connection_detailsOut_setStatus_([^_]+)_allHalts=yes.*?" // id
+ "(?:\\d+ Umst\\.|([\\w\\d ]+)).*?" // line + "<input type=\"hidden\" name=\"fitrack\" value=\"\\*([^\"]*)\" />" // track
+ ".*?", Pattern.DOTALL);
private static final Pattern P_CONNECTION_DETAILS = Pattern.compile("" //
+ "<td[^>]*headers=\"hafasDTL\\d+_Platform\"[^>]*>\n\\s*([^\\s\n]*?)\\s*\n</td>.*?" // departure platform
+ "(?:\nRichtung: ([^\n]*)\n.*?)?" // destination
+ "<td[^>]*headers=\"hafasDTL\\d+_Platform\"[^>]*>\n\\s*([^\\s\n]*?)\\s*\n</td>" // arrival platform
, Pattern.DOTALL); , Pattern.DOTALL);
private static final Pattern P_CHECK_CONNECTIONS_ERROR = Pattern.compile("(zu dicht beieinander|mehrfach vorhanden oder identisch)|" private static final Pattern P_CHECK_CONNECTIONS_ERROR = Pattern.compile("(zu dicht beieinander|mehrfach vorhanden oder identisch)|"
+ "(keine geeigneten Haltestellen)|(keine Verbindung gefunden)|" + "(keine geeigneten Haltestellen)|(keine Verbindung gefunden)|"
+ "(derzeit nur Ausk&#252;nfte vom)|(zwischenzeitlich nicht mehr gespeichert)|(http-equiv=\"refresh\")", Pattern.CASE_INSENSITIVE); + "(derzeit nur Ausk&#252;nfte vom)|(zwischenzeitlich nicht mehr gespeichert)|(http-equiv=\"refresh\")", Pattern.CASE_INSENSITIVE);
private QueryConnectionsResult queryConnections(final String uri, final CharSequence page, final Location originalFrom, final Location originalTo) private QueryConnectionsResult queryConnections(final String firstUri, CharSequence firstPage) throws IOException
throws IOException
{ {
final Matcher mError = P_CHECK_CONNECTIONS_ERROR.matcher(page); final Matcher mError = P_CHECK_CONNECTIONS_ERROR.matcher(firstPage);
if (mError.find()) if (mError.find())
{ {
if (mError.group(1) != null) if (mError.group(1) != null)
@ -379,257 +447,154 @@ public final class BvgProvider extends AbstractHafasProvider
throw new IOException("connected to private wlan"); throw new IOException("connected to private wlan");
} }
final Matcher mAllDetailsAction = P_CONNECTIONS_ALL_DETAILS.matcher(firstPage);
if (!mAllDetailsAction.find())
throw new IOException("cannot find all details link in '" + firstPage + "' on " + firstUri);
final String allDetailsUri = BVG_BASE_URL + ParserUtils.resolveEntities(mAllDetailsAction.group(1));
final CharSequence page = ParserUtils.scrape(allDetailsUri);
final Matcher mHead = P_CONNECTIONS_HEAD.matcher(page); final Matcher mHead = P_CONNECTIONS_HEAD.matcher(page);
if (mHead.matches()) if (mHead.matches())
{ {
final Location from = location(ParserUtils.resolveEntities(mHead.group(1)), originalFrom); final Location from = location(mHead.group(3), mHead.group(2), mHead.group(5), mHead.group(4),
final Location to = location(ParserUtils.resolveEntities(mHead.group(2)), originalTo); ParserUtils.resolveEntities(mHead.group(1)));
final Location via = mHead.group(6) != null ? location(null, null, null, null, ParserUtils.resolveEntities(mHead.group(6))) : null;
final Location to = location(mHead.group(9), mHead.group(8), mHead.group(11), mHead.group(10),
ParserUtils.resolveEntities(mHead.group(7)));
final Calendar currentDate = new GregorianCalendar(timeZone()); final Calendar currentDate = new GregorianCalendar(timeZone());
currentDate.clear(); currentDate.clear();
ParserUtils.parseGermanDate(currentDate, mHead.group(3)); ParserUtils.parseGermanDate(currentDate, mHead.group(12));
// final String linkEarlier = mHead.group(4) != null ? BVG_BASE_URL + // final String linkEarlier = mHead.group(13) != null ? BVG_BASE_URL +
// ParserUtils.resolveEntities(mHead.group(4)) : null; // ParserUtils.resolveEntities(mHead.group(13)) : null;
final String linkLater = mHead.group(5) != null ? BVG_BASE_URL + ParserUtils.resolveEntities(mHead.group(5)) : null; final String linkLater = mHead.group(14) != null ? BVG_BASE_URL + ParserUtils.resolveEntities(mHead.group(14)) : null;
final List<Connection> connections = new ArrayList<Connection>(); final List<Connection> connections = new ArrayList<Connection>();
final Matcher mConCoarse = P_CONNECTIONS_COARSE.matcher(page); final Matcher mConCoarse = P_CONNECTIONS_COARSE.matcher(page);
int iCon = 0;
while (mConCoarse.find()) while (mConCoarse.find())
{ {
final Matcher mConFine = P_CONNECTIONS_FINE.matcher(mConCoarse.group(1)); if (++iCon != Integer.parseInt(mConCoarse.group(1)))
throw new IllegalStateException("missing connection: " + iCon);
final Matcher mConFine = P_CONNECTIONS_FINE.matcher(mConCoarse.group(2));
if (mConFine.matches()) if (mConFine.matches())
{ {
final String link = BVG_BASE_URL + ParserUtils.resolveEntities(mConFine.group(1)); final Calendar time = new GregorianCalendar(timeZone());
final Calendar departureTime = new GregorianCalendar(timeZone()); time.clear();
departureTime.setTimeInMillis(currentDate.getTimeInMillis()); ParserUtils.parseGermanDate(time, mConFine.group(1));
ParserUtils.parseEuropeanTime(departureTime, mConFine.group(2)); Date lastTime = null;
if (!connections.isEmpty())
{
final long diff = departureTime.getTimeInMillis() - connections.get(connections.size() - 1).departureTime.getTime();
if (diff > PARSER_DAY_ROLLOVER_THRESHOLD_MS)
departureTime.add(Calendar.DAY_OF_YEAR, -1);
else if (diff < -PARSER_DAY_ROLLDOWN_THRESHOLD_MS)
departureTime.add(Calendar.DAY_OF_YEAR, 1);
}
final Calendar arrivalTime = new GregorianCalendar(timeZone());
arrivalTime.setTimeInMillis(currentDate.getTimeInMillis());
ParserUtils.parseEuropeanTime(arrivalTime, mConFine.group(3));
if (departureTime.after(arrivalTime))
arrivalTime.add(Calendar.DAY_OF_YEAR, 1);
final Connection connection = new Connection(AbstractHafasProvider.extractConnectionId(link), link, departureTime.getTime(),
arrivalTime.getTime(), from, to, null, null);
connections.add(connection);
}
else
{
throw new IllegalArgumentException("cannot parse '" + mConCoarse.group(1) + "' on " + uri);
}
}
return new QueryConnectionsResult(uri, from, null, to, linkLater, connections);
}
else
{
throw new IOException(page.toString());
}
}
private static final Pattern P_CONNECTION_DETAILS_HEAD = Pattern.compile(".*(?:Datum|Abfahrt): (\\d\\d\\.\\d\\d\\.\\d\\d).*", Pattern.DOTALL);
private static final Pattern P_CONNECTION_DETAILS_COARSE = Pattern.compile("<p class=\"con\\w\">\n(.+?)</p>", Pattern.DOTALL);
static final Pattern P_CONNECTION_DETAILS_FINE = Pattern.compile("" //
+ "(?:" //
+ "<a href=\"/Fahrinfo[^\"]*?input=(\\d+)\">(?:\n<strong>)?" // departureId
+ "(.+?)(?:</strong>\n)?</a>" // departureName
+ "|" //
+ "<a href=\"/Stadtplan.*?WGS84,(\\d+),(\\d+)&.*?\">([^<]*)</a>" // departureLat,departureLon,departureName
+ ")?.*?" //
+ "(?:" //
+ "ab (\\d{1,2}:\\d{2})\n" // departureTime
+ "(?:Gl\\. (.+?))?.*?" // departurePosition
+ "<strong>\\s*(.*?)\\s*</strong>.*?" // line
+ "Ri\\. (.*?)[\n\\.]*<.*?\n" // destination
+ "(.*?)\n" // intermediates
+ "an (\\d{1,2}:\\d{2})\n" // arrivalTime
+ "(?:Gl\\. (.+?))?.*?" // arrivalPosition
+ "<a href=\"/Fahrinfo[^\"]*?input=(\\d+)\">\n" // arrivalId
+ "<strong>([^<]*)</strong>" // arrivalName
+ "|" //
+ "(\\d+) Min\\.\n" // footway
+ "(?:Fussweg|&#220;bergang)\n" //
+ "<br />\n" //
+ "(?:<a href=\"/Fahrinfo[^\"]*?input=(\\d+)\">\n" // arrivalId
+ "<strong>([^<]*)</strong>|<a href=\"/Stadtplan.*?WGS84,(\\d+),(\\d+)&.*?\">([^<]*)</a>|<strong>([^<]*)</strong>).*?" // arrivalName,arrivalLat,arrivalLon,arrivalName,arrivalName
+ ").*?", Pattern.DOTALL);
private static final Pattern P_CONNECTION_DETAILS_INTERMEDIATES_ACTION = Pattern.compile("<a href=\"([^\"]*)\">Zwischenhalte anzeigen</a>",
Pattern.DOTALL);
private static final Pattern P_CONNECTION_DETAILS_INTERMEDIATES = Pattern.compile("-\n" //
+ "<a href=\"[^\"]*?input=(\\d+)\">\n" //
+ "([^\n]*)\n" //
+ "</a>\n" //
+ "&nbsp;(\\d{1,2}:\\d{2})&nbsp;\n", Pattern.DOTALL);
private static final Pattern P_CONNECTION_DETAILS_ERROR = Pattern.compile("(zwischenzeitlich nicht mehr gespeichert)", Pattern.CASE_INSENSITIVE);
@Override
public GetConnectionDetailsResult getConnectionDetails(final String uri) throws IOException
{
// fetch page without intermediate stops
CharSequence page = ParserUtils.scrape(uri);
// fetch page with intermediate stops
final Matcher mIntermediatesAction = P_CONNECTION_DETAILS_INTERMEDIATES_ACTION.matcher(page);
if (mIntermediatesAction.find())
{
try
{
page = ParserUtils.scrape(BVG_BASE_URL + ParserUtils.resolveEntities(mIntermediatesAction.group(1)));
}
catch (final IOException x)
{
// swallow
}
}
final Matcher mError = P_CONNECTION_DETAILS_ERROR.matcher(page);
if (mError.find())
throw new SessionExpiredException();
final Matcher mHead = P_CONNECTION_DETAILS_HEAD.matcher(page);
if (mHead.matches())
{
final Calendar currentDate = new GregorianCalendar(timeZone());
currentDate.clear();
ParserUtils.parseGermanDate(currentDate, mHead.group(1));
final List<Connection.Part> parts = new ArrayList<Connection.Part>(4);
Date firstDepartureTime = null; Date firstDepartureTime = null;
Location firstDeparture = null;
Date lastArrivalTime = null; Date lastArrivalTime = null;
Location lastArrival = null;
Date lastTime = null;
final Calendar time = new GregorianCalendar(timeZone());
time.setTimeInMillis(currentDate.getTimeInMillis());
final Matcher mDetCoarse = P_CONNECTION_DETAILS_COARSE.matcher(page); final String id = mConFine.group(2);
while (mDetCoarse.find())
{
final Matcher mDetFine = P_CONNECTION_DETAILS_FINE.matcher(mDetCoarse.group(1));
if (mDetFine.matches())
{
final String departureName = ParserUtils.resolveEntities(ParserUtils.selectNotNull(mDetFine.group(2), mDetFine.group(5)));
final int departureId = mDetFine.group(1) != null ? Integer.parseInt(mDetFine.group(1)) : 0; final String[] trackParts = mConFine.group(3).split("\\*");
final int departureLon = mDetFine.group(3) != null ? Integer.parseInt(mDetFine.group(3)) : 0;
final int departureLat = mDetFine.group(4) != null ? Integer.parseInt(mDetFine.group(4)) : 0;
final Location departure; final List<List<String[]>> tracks = new ArrayList<List<String[]>>();
if (departureName != null) for (final String trackPart : trackParts)
{ {
final String[] placeAndName = splitNameAndPlace(departureName); final String[] partElements = trackPart.split("\\|");
departure = new Location(departureId != 0 ? LocationType.STATION : LocationType.ANY, departureId, departureLat, departureLon, if (partElements.length != 10)
placeAndName[0], placeAndName[1]); throw new IllegalStateException("cannot parse: '" + trackPart + "'");
} final int i = Integer.parseInt(partElements[0]);
else if (i >= tracks.size())
{ tracks.add(new ArrayList<String[]>());
departure = lastArrival; tracks.get(i).add(partElements);
} }
if (departure != null && firstDeparture == null) final Matcher mDetails = P_CONNECTION_DETAILS.matcher(mConCoarse.group(2));
firstDeparture = departure;
final String min = mDetFine.group(15); final List<Connection.Part> parts = new ArrayList<Connection.Part>(tracks.size());
if (min == null) for (int iTrack = 0; iTrack < tracks.size(); iTrack++)
{ {
ParserUtils.parseEuropeanTime(time, mDetFine.group(6)); mDetails.find();
final List<String[]> track = tracks.get(iTrack);
final String[] tDep = track.get(0);
final String[] tArr = track.get(track.size() - 1);
final Location departure = location(tDep);
ParserUtils.parseEuropeanTime(time, tDep[8]);
if (lastTime != null && time.getTime().before(lastTime)) if (lastTime != null && time.getTime().before(lastTime))
time.add(Calendar.DAY_OF_YEAR, 1); time.add(Calendar.DAY_OF_YEAR, 1);
lastTime = time.getTime(); lastTime = time.getTime();
final Date departureTime = time.getTime(); final Date departureTime = time.getTime();
final String departurePosition = mDetFine.group(7);
final String lineStr = normalizeLine(ParserUtils.resolveEntities(mDetFine.group(8)));
final Line line = new Line(lineStr, lineColors(lineStr));
final String[] destinationPlaceAndName = splitNameAndPlace(ParserUtils.resolveEntities(mDetFine.group(9)));
final Location destination = new Location(LocationType.ANY, 0, destinationPlaceAndName[0], destinationPlaceAndName[1]);
final List<Stop> intermediateStops = new LinkedList<Stop>();
final Matcher mIntermediates = P_CONNECTION_DETAILS_INTERMEDIATES.matcher(mDetFine.group(10));
while (mIntermediates.find())
{
final int id = Integer.parseInt(mIntermediates.group(1));
final String[] placeAndName = splitNameAndPlace(ParserUtils.resolveEntities(mIntermediates.group(2)));
ParserUtils.parseEuropeanTime(time, mIntermediates.group(3));
if (lastTime != null && time.getTime().before(lastTime))
time.add(Calendar.DAY_OF_YEAR, 1);
lastTime = time.getTime();
final Stop stop = new Stop(new Location(LocationType.STATION, id, placeAndName[0], placeAndName[1]), null, time.getTime());
intermediateStops.add(stop);
}
ParserUtils.parseEuropeanTime(time, mDetFine.group(11));
if (lastTime.after(time.getTime()))
time.add(Calendar.DAY_OF_YEAR, 1);
lastTime = time.getTime();
final Date arrivalTime = time.getTime();
final String arrivalPosition = mDetFine.group(12);
final int arrivalId = Integer.parseInt(mDetFine.group(13));
final String[] arrivalPlaceAndName = splitNameAndPlace(ParserUtils.resolveEntities(mDetFine.group(14)));
final Location arrival = new Location(LocationType.STATION, arrivalId, arrivalPlaceAndName[0], arrivalPlaceAndName[1]);
parts.add(new Connection.Trip(line, destination, departureTime, departurePosition, departure, arrivalTime, arrivalPosition,
arrival, intermediateStops, null));
if (firstDepartureTime == null) if (firstDepartureTime == null)
firstDepartureTime = departureTime; firstDepartureTime = departureTime;
lastArrival = arrival; final String departurePosition = !mDetails.group(1).equals("&nbsp;") ? ParserUtils.resolveEntities(mDetails.group(1)) : null;
if (tArr[2].equals("walk"))
{
final String[] tArr2 = track.size() > 1 ? tArr : tracks.get(iTrack + 1).get(0);
final Location arrival = location(tArr2);
ParserUtils.parseEuropeanTime(time, tArr2[7]);
if (lastTime != null && time.getTime().before(lastTime))
time.add(Calendar.DAY_OF_YEAR, 1);
lastTime = time.getTime();
final Date arrivalTime = time.getTime();
lastArrivalTime = arrivalTime; lastArrivalTime = arrivalTime;
final int mins = (int) ((arrivalTime.getTime() - departureTime.getTime()) / 1000 / 60);
parts.add(new Connection.Footway(mins, departure, arrival, null));
} }
else else
{ {
final int arrivalId = mDetFine.group(16) != null ? Integer.parseInt(mDetFine.group(16)) : 0; final List<Stop> intermediateStops = new LinkedList<Stop>();
for (final String[] tStop : track.subList(1, track.size() - 1))
final int arrivalLon = mDetFine.group(18) != null ? Integer.parseInt(mDetFine.group(18)) : 0;
final int arrivalLat = mDetFine.group(19) != null ? Integer.parseInt(mDetFine.group(19)) : 0;
final String[] arrivalPlaceAndName = splitNameAndPlace(ParserUtils.resolveEntities(ParserUtils.selectNotNull(
mDetFine.group(17), mDetFine.group(20), mDetFine.group(21))));
final Location arrival = new Location(arrivalId != 0 ? LocationType.STATION : LocationType.ANY, arrivalId, arrivalLat,
arrivalLon, arrivalPlaceAndName[0], arrivalPlaceAndName[1]);
if (parts.size() > 0 && parts.get(parts.size() - 1) instanceof Connection.Footway)
{ {
final Connection.Footway lastFootway = (Connection.Footway) parts.remove(parts.size() - 1); ParserUtils.parseEuropeanTime(time, tStop[8]);
parts.add(new Connection.Footway(lastFootway.min + Integer.parseInt(min), lastFootway.departure, arrival, null)); if (lastTime != null && time.getTime().before(lastTime))
time.add(Calendar.DAY_OF_YEAR, 1);
lastTime = time.getTime();
intermediateStops.add(new Stop(location(tStop), null, time.getTime()));
}
final Location arrival = location(tArr);
ParserUtils.parseEuropeanTime(time, tArr[7]);
if (lastTime != null && time.getTime().before(lastTime))
time.add(Calendar.DAY_OF_YEAR, 1);
lastTime = time.getTime();
final Date arrivalTime = time.getTime();
lastArrivalTime = arrivalTime;
final String arrivalPosition = !mDetails.group(3).equals("&nbsp;") ? ParserUtils.resolveEntities(mDetails.group(3))
: null;
final String lineStr = normalizeLine(ParserUtils.resolveEntities(tDep[3]));
final Line line = new Line(lineStr, lineColors(lineStr));
final Location destination;
if (mDetails.group(2) != null)
{
final String[] destinationPlaceAndName = splitNameAndPlace(ParserUtils.resolveEntities(mDetails.group(2)));
destination = new Location(LocationType.ANY, 0, destinationPlaceAndName[0], destinationPlaceAndName[1]);
} }
else else
{ {
parts.add(new Connection.Footway(Integer.parseInt(min), departure, arrival, null)); destination = null;
} }
lastArrival = arrival; parts.add(new Connection.Trip(line, destination, departureTime, departurePosition, departure, arrivalTime,
arrivalPosition, arrival, intermediateStops, null));
} }
} }
connections.add(new Connection(id, firstUri, firstDepartureTime, lastArrivalTime, from, to, parts, null));
}
else else
{ {
throw new IllegalArgumentException("cannot parse '" + mDetCoarse.group(1) + "' on " + uri); throw new IllegalArgumentException("cannot parse '" + mConCoarse.group(2) + "' on " + allDetailsUri);
} }
} }
if (firstDepartureTime != null && lastArrivalTime != null) return new QueryConnectionsResult(firstUri, from, via, to, linkLater, connections);
return new GetConnectionDetailsResult(currentDate.getTime(), new Connection(AbstractHafasProvider.extractConnectionId(uri), uri,
firstDepartureTime, lastArrivalTime, firstDeparture, lastArrival, parts, null));
else
return new GetConnectionDetailsResult(currentDate.getTime(), null);
} }
else else
{ {
@ -637,7 +602,13 @@ public final class BvgProvider extends AbstractHafasProvider
} }
} }
private static final String DEPARTURE_URL_LIVE = "http://mobil.bvg.de/IstAbfahrtzeiten/index/mobil?"; @Override
public GetConnectionDetailsResult getConnectionDetails(final String uri) throws IOException
{
throw new UnsupportedOperationException();
}
private static final String DEPARTURE_URL_LIVE = BVG_MOBIL_BASE_URL + "/IstAbfahrtzeiten/index/mobil?";
private String departuresQueryLiveUri(final String stationId) private String departuresQueryLiveUri(final String stationId)
{ {
@ -647,7 +618,7 @@ public final class BvgProvider extends AbstractHafasProvider
return uri.toString(); return uri.toString();
} }
private static final String DEPARTURE_URL_PLAN = "http://mobil.bvg.de/Fahrinfo/bin/stboard.bin/dox/dox?boardType=dep&disableEquivs=yes&start=yes&"; private static final String DEPARTURE_URL_PLAN = API_BASE + "stboard.bin/dox/dox?boardType=dep&disableEquivs=yes&start=yes&";
private String departuresQueryPlanUri(final String stationId, final int maxDepartures) private String departuresQueryPlanUri(final String stationId, final int maxDepartures)
{ {

View file

@ -1,97 +0,0 @@
/*
* Copyright 2010, 2011 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.schildbach.pte;
import static junit.framework.Assert.assertTrue;
import java.util.regex.Matcher;
import org.junit.Test;
/**
* @author Andreas Schildbach
*/
public class BvgProviderTest
{
@Test
public void footwayWithLink()
{
assertFineConnectionDetails("" //
+ "<a href=\"/Stadtplan/index/mobil?language=d&amp;location=,ADR,WGS84,13426558,52536061&amp;label=10405 Bln Pankow, Christburger Str. 123\">10405 Bln Pankow, Christburger Str. 123</a>\n"
+ "<br />\n" //
+ "10 Min.\n" //
+ "Fussweg\n" //
+ "<br />\n" //
+ "<a href=\"/Fahrinfo/bin/stboard.bin/dox?ld=0.1&amp;n=2&amp;i=c6.0318411.1278336553&amp;rt=0&amp;input=9110017\">\n" //
+ "<strong>Prenzlauer Allee/Danziger Str. (Berlin)</strong>\n" //
+ "</a>\n" //
+ "<br />\n");
}
@Test
public void footwayWithoutLink()
{
assertFineConnectionDetails("" //
+ "18 Min.\n" //
+ "Fussweg\n" //
+ "<br />\n" //
+ "<strong>Berlin, Deutschlandhalle</strong>\n" //
+ "<br/>Messedamm 26; 14055 Berlin\n" //
+ "<br />\n");
}
@Test
public void footwayUebergang()
{
assertFineConnectionDetails("" //
+ "17 Min.\n" //
+ "&#220;bergang\n" //
+ "<br />\n" //
+ "<a href=\"/Fahrinfo/bin/stboard.bin/dox?ld=0.1&amp;n=2&amp;i=fx.0101111.1283848693&amp;rt=0&amp;input=9025321\">\n" //
+ "<strong>S Olympiastadion (Berlin)</strong>\n" //
+ "</a>\n" //
+ "<br />\n");
}
@Test
public void trip()
{
assertFineConnectionDetails("" //
+ "<a href=\"/Fahrinfo/bin/stboard.bin/dox?ld=0.1&amp;n=2&amp;i=er.042611.1278315324&amp;rt=0&amp;input=9275402\">\n" //
+ "<strong>Brandenburg, Frhr.-v.-Th&#252;ngen-Str.</strong>\n" //
+ "</a>\n" //
+ "<br />\n" //
+ "ab 09:35\n" //
+ "<br/><strong>BusH/528</strong>\n" //
+ "Ri. Brandenburg, Potsdamer Str.\n" //
+ "<br />\n" //
+ "an 09:41\n" //
+ "<br />\n" //
+ "<a href=\"/Fahrinfo/bin/stboard.bin/dox?ld=0.1&amp;n=2&amp;i=er.042611.1278315324&amp;rt=0&amp;input=9275104\">\n" //
+ "<strong>Brandenburg, Plauer Str.</strong>\n" //
+ "</a>\n" //
+ "<br />\n");
}
private void assertFineConnectionDetails(String s)
{
Matcher m = BvgProvider.P_CONNECTION_DETAILS_FINE.matcher(s);
assertTrue(m.matches());
// ParserUtils.printGroups(m);
}
}

View file

@ -24,7 +24,6 @@ import org.junit.Test;
import de.schildbach.pte.BvgProvider; import de.schildbach.pte.BvgProvider;
import de.schildbach.pte.NetworkProvider.WalkSpeed; import de.schildbach.pte.NetworkProvider.WalkSpeed;
import de.schildbach.pte.dto.Connection;
import de.schildbach.pte.dto.Location; import de.schildbach.pte.dto.Location;
import de.schildbach.pte.dto.LocationType; import de.schildbach.pte.dto.LocationType;
import de.schildbach.pte.dto.NearbyStationsResult; import de.schildbach.pte.dto.NearbyStationsResult;
@ -77,20 +76,59 @@ public class BvgProviderLiveTest
null, new Location(LocationType.STATION, 9013103, "Berlin", "Prinzenstraße"), new Date(), true, ALL_PRODUCTS, WalkSpeed.NORMAL); null, new Location(LocationType.STATION, 9013103, "Berlin", "Prinzenstraße"), new Date(), true, ALL_PRODUCTS, WalkSpeed.NORMAL);
System.out.println(result); System.out.println(result);
final QueryConnectionsResult moreResult = provider.queryMoreConnections(result.context); final QueryConnectionsResult moreResult = provider.queryMoreConnections(result.context);
for (final Connection connection : result.connections) System.out.println(moreResult);
provider.getConnectionDetails(connection.link); }
@Test
public void shortViaConnection() throws Exception
{
final QueryConnectionsResult result = provider.queryConnections(new Location(LocationType.STATION, 9056102, "Berlin", "Nollendorfplatz"),
new Location(LocationType.STATION, 9044202, "Berlin", "Bundesplatz"), new Location(LocationType.STATION, 9013103, "Berlin",
"Prinzenstraße"), new Date(), true, ALL_PRODUCTS, WalkSpeed.NORMAL);
System.out.println(result);
final QueryConnectionsResult moreResult = provider.queryMoreConnections(result.context);
System.out.println(moreResult); System.out.println(moreResult);
} }
@Test @Test
public void connectionBetweenCoordinates() throws Exception public void connectionBetweenCoordinates() throws Exception
{ {
final QueryConnectionsResult result = provider.queryConnections(new Location(LocationType.ADDRESS, 0, 52501507, 13357026), null, final QueryConnectionsResult result = provider.queryConnections(new Location(LocationType.ADDRESS, 0, 52501507, 13357026, null, "from"),
new Location(LocationType.ADDRESS, 0, 52513639, 13568648), new Date(), true, ALL_PRODUCTS, WalkSpeed.NORMAL); null, new Location(LocationType.ADDRESS, 0, 52513639, 13568648, null, "to"), new Date(), true, ALL_PRODUCTS, WalkSpeed.NORMAL);
System.out.println(result);
final QueryConnectionsResult moreResult = provider.queryMoreConnections(result.context);
System.out.println(moreResult);
}
@Test
public void viaConnectionBetweenCoordinates() throws Exception
{
final QueryConnectionsResult result = provider.queryConnections(new Location(LocationType.ADDRESS, 0, 52501507, 13357026, null, "from"),
new Location(LocationType.ADDRESS, 0, 52479868, 13324247, null, "via"), new Location(LocationType.ADDRESS, 0, 52513639, 13568648,
null, "to"), new Date(), true, ALL_PRODUCTS, WalkSpeed.NORMAL);
System.out.println(result);
final QueryConnectionsResult moreResult = provider.queryMoreConnections(result.context);
System.out.println(moreResult);
}
@Test
public void connectionBetweenAddresses() throws Exception
{
final QueryConnectionsResult result = provider.queryConnections(new Location(LocationType.ADDRESS, 0, null, "10715 Bln Charlb.-Wilm., Weimarische Str. 7"),
null, new Location(LocationType.ADDRESS, 0, null, "10178 Bln Mitte, Sophienstr. 24"), new Date(), true, ALL_PRODUCTS, WalkSpeed.NORMAL);
System.out.println(result);
final QueryConnectionsResult moreResult = provider.queryMoreConnections(result.context);
System.out.println(moreResult);
}
@Test
public void viaConnectionBetweenAddresses() throws Exception
{
final QueryConnectionsResult result = provider.queryConnections(new Location(LocationType.ADDRESS, 0, null, "10715 Bln Charlb.-Wilm., Weimarische Str. 7"),
new Location(LocationType.ADDRESS, 0, null, "10115 Bln Mitte, Hannoversche Str. 20"), new Location(LocationType.ADDRESS, 0, null,
"10178 Bln Mitte, Sophienstr. 24"), new Date(), true, ALL_PRODUCTS, WalkSpeed.NORMAL);
System.out.println(result); System.out.println(result);
final QueryConnectionsResult moreResult = provider.queryMoreConnections(result.context); final QueryConnectionsResult moreResult = provider.queryMoreConnections(result.context);
for (final Connection connection : result.connections)
provider.getConnectionDetails(connection.link);
System.out.println(moreResult); System.out.println(moreResult);
} }
} }