mirror of
https://gitlab.com/oeffi/public-transport-enabler.git
synced 2025-07-17 14:09:50 +00:00
switch connections to API
git-svn-id: https://public-transport-enabler.googlecode.com/svn/trunk@308 0924bc21-9374-b0fa-ee44-9ff1593b38f0
This commit is contained in:
parent
c90efb0c68
commit
203dbe3971
1 changed files with 4 additions and 307 deletions
|
@ -18,8 +18,6 @@
|
|||
package de.schildbach.pte;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
@ -28,12 +26,7 @@ import java.util.List;
|
|||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import de.schildbach.pte.dto.Connection;
|
||||
import de.schildbach.pte.dto.Departure;
|
||||
import de.schildbach.pte.dto.GetConnectionDetailsResult;
|
||||
import de.schildbach.pte.dto.Location;
|
||||
import de.schildbach.pte.dto.LocationType;
|
||||
import de.schildbach.pte.dto.QueryConnectionsResult;
|
||||
import de.schildbach.pte.dto.QueryDeparturesResult;
|
||||
import de.schildbach.pte.dto.QueryDeparturesResult.Status;
|
||||
import de.schildbach.pte.util.ParserUtils;
|
||||
|
@ -57,7 +50,7 @@ public class SbbProvider extends AbstractHafasProvider
|
|||
public boolean hasCapabilities(final Capability... capabilities)
|
||||
{
|
||||
for (final Capability capability : capabilities)
|
||||
if (capability == Capability.DEPARTURES)
|
||||
if (capability == Capability.DEPARTURES || capability == Capability.CONNECTIONS)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
|
@ -71,302 +64,6 @@ public class SbbProvider extends AbstractHafasProvider
|
|||
return String.format(NEARBY_URI, stationId);
|
||||
}
|
||||
|
||||
private String connectionsQueryUri(final Location from, final Location via, final Location to, final Date date, final boolean dep)
|
||||
{
|
||||
final DateFormat DATE_FORMAT = new SimpleDateFormat("dd.MM.yy");
|
||||
final DateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm");
|
||||
final StringBuilder uri = new StringBuilder();
|
||||
|
||||
uri.append(API_BASE).append("query.exe/dox");
|
||||
uri.append("?OK");
|
||||
uri.append("&REQ0HafasMaxChangeTime=120");
|
||||
uri.append("&REQ0HafasOptimize1=").append(ParserUtils.urlEncode("1:1"));
|
||||
uri.append("&REQ0HafasSearchForw=").append(dep ? "1" : "0");
|
||||
uri.append("&REQ0HafasSkipLongChanges=1");
|
||||
uri.append("&REQ0JourneyDate=").append(ParserUtils.urlEncode(DATE_FORMAT.format(date)));
|
||||
uri.append("&REQ0JourneyStopsS0G=").append(ParserUtils.urlEncode(locationValue(from)));
|
||||
uri.append("&REQ0JourneyStopsS0A=").append(locationTypeValue(from));
|
||||
uri.append("&REQ0JourneyStopsS0ID=");
|
||||
if (via != null)
|
||||
{
|
||||
uri.append("&REQ0JourneyStops1.0G=").append(ParserUtils.urlEncode(locationValue(via)));
|
||||
uri.append("&REQ0JourneyStops1.0A=").append(locationTypeValue(via));
|
||||
uri.append("&REQ0JourneyStops1.0ID=");
|
||||
}
|
||||
uri.append("&REQ0JourneyStopsZ0G=").append(ParserUtils.urlEncode(locationValue(to)));
|
||||
uri.append("&REQ0JourneyStopsZ0A=").append(locationTypeValue(to));
|
||||
uri.append("&REQ0JourneyStopsZ0ID=");
|
||||
uri.append("&REQ0JourneyTime=").append(ParserUtils.urlEncode(TIME_FORMAT.format(date)));
|
||||
uri.append("&queryPageDisplayed=yes");
|
||||
|
||||
// TODO products
|
||||
|
||||
uri.append("&start=Suchen");
|
||||
|
||||
return uri.toString();
|
||||
}
|
||||
|
||||
private static int locationTypeValue(final Location location)
|
||||
{
|
||||
final LocationType type = location.type;
|
||||
if (type == LocationType.STATION)
|
||||
return 1;
|
||||
if (type == LocationType.ADDRESS)
|
||||
return 2;
|
||||
if (type == LocationType.ANY)
|
||||
return 7;
|
||||
throw new IllegalArgumentException(type.toString());
|
||||
}
|
||||
|
||||
private static String locationValue(final Location location)
|
||||
{
|
||||
if (location.type == LocationType.STATION && location.id != 0)
|
||||
return Integer.toString(location.id);
|
||||
else
|
||||
return location.name;
|
||||
}
|
||||
|
||||
private static final Pattern P_PRE_ADDRESS = Pattern.compile(
|
||||
"<select name=\"(REQ0JourneyStopsS0K|REQ0JourneyStopsZ0K|REQ0JourneyStops1\\.0K)\"[^>]*>(.*?)</select>", Pattern.DOTALL);
|
||||
private static final Pattern P_ADDRESSES = Pattern.compile("<option[^>]*>\\s*(.*?)\\s*</option>", Pattern.DOTALL);
|
||||
private static final Pattern P_CHECK_CONNECTIONS_ERROR = Pattern
|
||||
.compile("(mehrfach vorhanden oder identisch)|(keine Verbindung gefunden werden)|(liegt nach dem Ende der Fahrplanperiode|liegt vor Beginn der Fahrplanperiode)");
|
||||
|
||||
@Override
|
||||
public QueryConnectionsResult queryConnections(final Location from, final Location via, final Location to, final Date date, final boolean dep,
|
||||
final String products, final WalkSpeed walkSpeed) throws IOException
|
||||
{
|
||||
final String uri = connectionsQueryUri(from, via, to, date, dep);
|
||||
final CharSequence page = ParserUtils.scrape(uri);
|
||||
|
||||
final Matcher mError = P_CHECK_CONNECTIONS_ERROR.matcher(page);
|
||||
if (mError.find())
|
||||
{
|
||||
if (mError.group(1) != null)
|
||||
return QueryConnectionsResult.TOO_CLOSE;
|
||||
if (mError.group(2) != null)
|
||||
return QueryConnectionsResult.NO_CONNECTIONS;
|
||||
if (mError.group(3) != null)
|
||||
return QueryConnectionsResult.INVALID_DATE;
|
||||
}
|
||||
|
||||
List<Location> fromAddresses = null;
|
||||
List<Location> viaAddresses = null;
|
||||
List<Location> toAddresses = null;
|
||||
|
||||
// FIXME cannot parse ambiguous
|
||||
|
||||
final Matcher mPreAddress = P_PRE_ADDRESS.matcher(page);
|
||||
while (mPreAddress.find())
|
||||
{
|
||||
final String type = mPreAddress.group(1);
|
||||
final String options = mPreAddress.group(2);
|
||||
|
||||
final Matcher mAddresses = P_ADDRESSES.matcher(options);
|
||||
final List<Location> addresses = new ArrayList<Location>();
|
||||
while (mAddresses.find())
|
||||
{
|
||||
final String address = ParserUtils.resolveEntities(mAddresses.group(1)).trim();
|
||||
if (!addresses.contains(address))
|
||||
addresses.add(new Location(LocationType.ANY, 0, 0, 0, address + "!"));
|
||||
}
|
||||
|
||||
if (type.equals("REQ0JourneyStopsS0K"))
|
||||
fromAddresses = addresses;
|
||||
else if (type.equals("REQ0JourneyStopsZ0K"))
|
||||
toAddresses = addresses;
|
||||
else if (type.equals("REQ0JourneyStops1.0K"))
|
||||
viaAddresses = addresses;
|
||||
else
|
||||
throw new IOException(type);
|
||||
}
|
||||
|
||||
if (fromAddresses != null || viaAddresses != null || toAddresses != null)
|
||||
return new QueryConnectionsResult(fromAddresses, viaAddresses, toAddresses);
|
||||
else
|
||||
return queryConnections(uri, page);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryConnectionsResult queryMoreConnections(final String uri) throws IOException
|
||||
{
|
||||
final CharSequence page = ParserUtils.scrape(uri);
|
||||
|
||||
return queryConnections(uri, page);
|
||||
}
|
||||
|
||||
private static final Pattern P_CONNECTIONS_HEAD = Pattern.compile(".*?" //
|
||||
+ "Von: <strong>([^<]*)<.*?" // from
|
||||
+ "Nach: <strong>([^<]*)<.*?" // to
|
||||
+ "Datum: .., (\\d{2}\\.\\d{2}\\.\\d{2}).*?" // currentDate
|
||||
+ "(<p class=\"con_.*?)<p class=\"link1\">.*?" // body
|
||||
+ "(?:<a href=\"(http://[^\"]*REQ0HafasScrollDir=2)\".*?)?" // linkEarlier
|
||||
+ "(?:<a href=\"(http://[^\"]*REQ0HafasScrollDir=1)\".*?)?" // linkLater
|
||||
, Pattern.DOTALL);
|
||||
private static final Pattern P_CONNECTIONS_COARSE = Pattern.compile("\\G" //
|
||||
+ "<p class=\"(con_\\d+)\">\n" //
|
||||
+ "(.*?)</p>\n", Pattern.DOTALL);
|
||||
private static final Pattern P_CONNECTIONS_FINE = Pattern.compile("" //
|
||||
+ "(?:<img [^>]*>)?" //
|
||||
+ "<a href=\"(http://[^\"]*)\">" // link
|
||||
+ "(\\d{1,2}:\\d{2})-(\\d{1,2}:\\d{2})</a>.*?" // departureTime, arrivalTime
|
||||
, Pattern.DOTALL);
|
||||
|
||||
private QueryConnectionsResult queryConnections(final String uri, final CharSequence page) throws IOException
|
||||
{
|
||||
final Matcher mHead = P_CONNECTIONS_HEAD.matcher(page);
|
||||
if (mHead.matches())
|
||||
{
|
||||
final Location from = new Location(LocationType.ANY, 0, 0, 0, ParserUtils.resolveEntities(mHead.group(1)));
|
||||
final Location to = new Location(LocationType.ANY, 0, 0, 0, ParserUtils.resolveEntities(mHead.group(2)));
|
||||
final Date currentDate = ParserUtils.parseDate(mHead.group(3));
|
||||
final String body = mHead.group(4);
|
||||
final String linkEarlier = mHead.group(5) != null ? ParserUtils.resolveEntities(mHead.group(5)) : null;
|
||||
final String linkLater = mHead.group(6) != null ? ParserUtils.resolveEntities(mHead.group(6)) : null;
|
||||
final List<Connection> connections = new ArrayList<Connection>();
|
||||
String oldZebra = null;
|
||||
|
||||
final Matcher mCoarse = P_CONNECTIONS_COARSE.matcher(body);
|
||||
while (mCoarse.find())
|
||||
{
|
||||
final String zebra = mCoarse.group(1);
|
||||
if (oldZebra != null && zebra.equals(oldZebra))
|
||||
throw new IllegalArgumentException("missed row? last:" + zebra);
|
||||
else
|
||||
oldZebra = zebra;
|
||||
|
||||
final String set = mCoarse.group(2);
|
||||
final Matcher mFine = P_CONNECTIONS_FINE.matcher(set);
|
||||
if (mFine.matches())
|
||||
{
|
||||
final String link = ParserUtils.resolveEntities(mFine.group(1));
|
||||
Date departureTime = ParserUtils.joinDateTime(currentDate, ParserUtils.parseTime(mFine.group(2)));
|
||||
if (!connections.isEmpty())
|
||||
{
|
||||
final long diff = ParserUtils.timeDiff(departureTime, connections.get(connections.size() - 1).departureTime);
|
||||
if (diff > PARSER_DAY_ROLLOVER_THRESHOLD_MS)
|
||||
departureTime = ParserUtils.addDays(departureTime, -1);
|
||||
else if (diff < -PARSER_DAY_ROLLOVER_THRESHOLD_MS)
|
||||
departureTime = ParserUtils.addDays(departureTime, 1);
|
||||
}
|
||||
Date arrivalTime = ParserUtils.joinDateTime(currentDate, ParserUtils.parseTime(mFine.group(3)));
|
||||
if (departureTime.after(arrivalTime))
|
||||
arrivalTime = ParserUtils.addDays(arrivalTime, 1);
|
||||
|
||||
connections.add(new Connection(extractConnectionId(link), link, departureTime, arrivalTime, null, null, 0, from.name, 0, to.name,
|
||||
null));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalArgumentException("cannot parse '" + set + "' on " + uri);
|
||||
}
|
||||
}
|
||||
|
||||
return new QueryConnectionsResult(uri, from, null, to, linkEarlier, linkLater, connections);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IOException(page.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static final Pattern P_CONNECTION_DETAILS_HEAD = Pattern.compile(".*?" //
|
||||
+ "<p class=\"conSecStart\">\n- ([^<]*) -\n</p>\n" //
|
||||
+ "(.*?)" //
|
||||
+ "<p class=\"remark\">\n" //
|
||||
+ "Abfahrt: (\\d+\\.\\d+\\.\\d+).*?", Pattern.DOTALL);
|
||||
private static final Pattern P_CONNECTION_DETAILS_COARSE = Pattern.compile("\\G" //
|
||||
+ "<p class=\"conSecJourney\">\n(.*?)</p>\n" //
|
||||
+ "<p class=\"conSecDestination\">\n(.*?)</p>\n" //
|
||||
, Pattern.DOTALL);
|
||||
private static final Pattern P_CONNECTION_DETAILS_JOURNEY = Pattern.compile("" //
|
||||
+ "(?:" //
|
||||
+ "(.+?)\n.*?" // line
|
||||
+ "ab (\\d{1,2}:\\d{2})\n" // departureTime
|
||||
+ "(?: (Gl\\. .+?)\\s*\n)?" // departurePosition
|
||||
+ ".*?" //
|
||||
+ "an (\\d{1,2}:\\d{2})\n" // arrivalTime
|
||||
+ "(?: (Gl\\. .+?)\\s*\n)?" // arrivalPosition
|
||||
+ "|" //
|
||||
+ "(?:Fussweg|Übergang)\n" //
|
||||
+ "(\\d+) Min\\.\n" // minutes
|
||||
+ ")" //
|
||||
, Pattern.DOTALL);
|
||||
private static final Pattern P_CONNECTION_DETAILS_DESTINATION = Pattern.compile("" //
|
||||
+ "- ([^<]*) -\n" // destination
|
||||
, Pattern.DOTALL);
|
||||
|
||||
@Override
|
||||
public GetConnectionDetailsResult getConnectionDetails(final String uri) throws IOException
|
||||
{
|
||||
final CharSequence page = ParserUtils.scrape(uri);
|
||||
|
||||
final Matcher mHead = P_CONNECTION_DETAILS_HEAD.matcher(page);
|
||||
if (mHead.matches())
|
||||
{
|
||||
Date firstDepartureTime = null;
|
||||
final String firstDeparture = ParserUtils.resolveEntities(mHead.group(1));
|
||||
Date lastArrivalTime = null;
|
||||
String departure = firstDeparture;
|
||||
final String body = mHead.group(2);
|
||||
final Date date = ParserUtils.parseDate(mHead.group(3));
|
||||
|
||||
final List<Connection.Part> parts = new ArrayList<Connection.Part>(4);
|
||||
|
||||
final Matcher mCoarse = P_CONNECTION_DETAILS_COARSE.matcher(body);
|
||||
while (mCoarse.find())
|
||||
{
|
||||
final Matcher mJourney = P_CONNECTION_DETAILS_JOURNEY.matcher(mCoarse.group(1));
|
||||
final Matcher mDestination = P_CONNECTION_DETAILS_DESTINATION.matcher(mCoarse.group(2));
|
||||
if (mJourney.matches() && mDestination.matches())
|
||||
{
|
||||
final String arrival = mDestination.group(1);
|
||||
|
||||
if (mJourney.group(6) == null)
|
||||
{
|
||||
final String line = normalizeLine(ParserUtils.resolveEntities(mJourney.group(1)));
|
||||
Date departureTime = ParserUtils.joinDateTime(date, ParserUtils.parseTime(mJourney.group(2)));
|
||||
if (lastArrivalTime != null && departureTime.before(lastArrivalTime))
|
||||
departureTime = ParserUtils.addDays(departureTime, 1);
|
||||
final String departurePosition = mJourney.group(3) != null ? ParserUtils.resolveEntities(mJourney.group(3)) : null;
|
||||
Date arrivalTime = ParserUtils.joinDateTime(date, ParserUtils.parseTime(mJourney.group(4)));
|
||||
if (departureTime.after(arrivalTime))
|
||||
arrivalTime = ParserUtils.addDays(arrivalTime, 1);
|
||||
final String arrivalPosition = mJourney.group(5) != null ? ParserUtils.resolveEntities(mJourney.group(5)) : null;
|
||||
|
||||
parts.add(new Connection.Trip(line, lineColors(line), 0, null, departureTime, departurePosition, 0, departure, arrivalTime,
|
||||
arrivalPosition, 0, arrival));
|
||||
|
||||
if (firstDepartureTime == null)
|
||||
firstDepartureTime = departureTime;
|
||||
lastArrivalTime = arrivalTime;
|
||||
departure = arrival;
|
||||
}
|
||||
else
|
||||
{
|
||||
final int min = Integer.parseInt(mJourney.group(6));
|
||||
|
||||
parts.add(new Connection.Footway(min, 0, departure, 0, arrival, 0, 0));
|
||||
|
||||
departure = arrival;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalArgumentException("cannot parse '" + mCoarse.group(1) + "', '" + mCoarse.group(2) + "' on " + uri);
|
||||
}
|
||||
}
|
||||
|
||||
return new GetConnectionDetailsResult(new Date(), new Connection(AbstractHafasProvider.extractConnectionId(uri), uri, firstDepartureTime,
|
||||
lastArrivalTime, null, null, 0, firstDeparture, 0, departure, parts));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalArgumentException("cannot parse '" + page + "' on " + uri);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private String departuresQueryUri(final String stationId, final int maxDepartures)
|
||||
{
|
||||
final StringBuilder uri = new StringBuilder();
|
||||
|
@ -417,11 +114,11 @@ public class SbbProvider extends AbstractHafasProvider
|
|||
{
|
||||
// messages
|
||||
if (mHeadCoarse.group(3) != null)
|
||||
return new QueryDeparturesResult( Status.NO_INFO, Integer.parseInt(stationId));
|
||||
return new QueryDeparturesResult(Status.NO_INFO, Integer.parseInt(stationId));
|
||||
else if (mHeadCoarse.group(5) != null)
|
||||
return new QueryDeparturesResult( Status.INVALID_STATION, Integer.parseInt(stationId));
|
||||
return new QueryDeparturesResult(Status.INVALID_STATION, Integer.parseInt(stationId));
|
||||
else if (mHeadCoarse.group(6) != null)
|
||||
return new QueryDeparturesResult( Status.SERVICE_DOWN, Integer.parseInt(stationId));
|
||||
return new QueryDeparturesResult(Status.SERVICE_DOWN, Integer.parseInt(stationId));
|
||||
|
||||
final String head = mHeadCoarse.group(1) + mHeadCoarse.group(4);
|
||||
final Matcher mHeadFine = P_DEPARTURES_HEAD_FINE.matcher(head);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue