migrated Saarbrücken to binary connection queries, xml departures and more

This commit is contained in:
Andreas Schildbach 2013-03-14 15:46:34 +01:00
parent 96fe89286c
commit 9653a0420f
2 changed files with 50 additions and 186 deletions

View file

@ -18,24 +18,17 @@
package de.schildbach.pte; package de.schildbach.pte;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.Date;
import java.util.Calendar;
import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.regex.Matcher; import java.util.Set;
import java.util.regex.Pattern;
import de.schildbach.pte.dto.Departure;
import de.schildbach.pte.dto.Line;
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;
import de.schildbach.pte.dto.QueryConnectionsContext;
import de.schildbach.pte.dto.QueryConnectionsResult;
import de.schildbach.pte.dto.QueryDeparturesResult; import de.schildbach.pte.dto.QueryDeparturesResult;
import de.schildbach.pte.dto.QueryDeparturesResult.Status;
import de.schildbach.pte.dto.ResultHeader;
import de.schildbach.pte.dto.StationDepartures;
import de.schildbach.pte.util.ParserUtils; import de.schildbach.pte.util.ParserUtils;
/** /**
@ -43,9 +36,7 @@ import de.schildbach.pte.util.ParserUtils;
*/ */
public class VgsProvider extends AbstractHafasProvider public class VgsProvider extends AbstractHafasProvider
{ {
private static final String API_BASE = "http://www.vgs-online.de/cgi-bin/"; // "http://www.saarfahrplan.de/cgi-bin/"; private static final String API_BASE = "http://www.saarfahrplan.de/cgi-bin/"; // http://www.vgs-online.de/cgi-bin/
private static final long PARSER_DAY_ROLLOVER_THRESHOLD_MS = 12 * 60 * 60 * 1000;
public VgsProvider() public VgsProvider()
{ {
@ -113,6 +104,22 @@ public class VgsProvider extends AbstractHafasProvider
} }
} }
private static final String[] PLACES = { "Saarbrücken" };
@Override
protected String[] splitPlaceAndName(final String name)
{
for (final String place : PLACES)
{
if (name.endsWith(", " + place))
return new String[] { place, name.substring(0, name.length() - place.length() - 2) };
else if (name.startsWith(place + " ") || name.startsWith(place + "-"))
return new String[] { place, name.substring(place.length() + 1) };
}
return super.splitPlaceAndName(name);
}
public NearbyStationsResult queryNearbyStations(final Location location, final int maxDistance, final int maxStations) throws IOException public NearbyStationsResult queryNearbyStations(final Location location, final int maxDistance, final int maxStations) throws IOException
{ {
final StringBuilder uri = new StringBuilder(API_BASE); final StringBuilder uri = new StringBuilder(API_BASE);
@ -143,164 +150,22 @@ public class VgsProvider extends AbstractHafasProvider
} }
} }
private String departuresQueryUri(final int stationId, final int maxDepartures)
{
final StringBuilder uri = new StringBuilder();
uri.append(API_BASE).append("stboard.exe/dn");
uri.append("?input=").append(stationId);
uri.append("&boardType=dep");
uri.append("&productsFilter=").append(allProductsString());
if (maxDepartures != 0)
uri.append("&maxJourneys=").append(maxDepartures);
uri.append("&disableEquivs=yes"); // don't use nearby stations
uri.append("&start=yes");
return uri.toString();
}
private static final Pattern P_DEPARTURES_HEAD_COARSE = Pattern
.compile(
".*?" //
+ "(?:" //
+ "<table class=\"hafasResult\"[^>]*>(.+?)</table>.*?" //
+ "(?:<table cellspacing=\"0\" class=\"hafasResult\"[^>]*>(.+?)</table>|(verkehren an dieser Haltestelle keine))"//
+ "|(Eingabe kann nicht interpretiert)|(Verbindung zum Server konnte leider nicht hergestellt werden|kann vom Server derzeit leider nicht bearbeitet werden))" //
+ ".*?" //
, Pattern.DOTALL);
private static final Pattern P_DEPARTURES_HEAD_FINE = Pattern.compile(".*?" //
+ "<td class=\"querysummary screennowrap\">\\s*(.*?)\\s*<.*?" // location
+ "(\\d{2}\\.\\d{2}\\.\\d{2}).*?" // date
+ "Abfahrt (\\d{1,2}:\\d{2}).*?" // time
, Pattern.DOTALL);
private static final Pattern P_DEPARTURES_COARSE = Pattern.compile("<tr class=\"(depboard-\\w*)\">(.*?)</tr>", Pattern.DOTALL);
private static final Pattern P_DEPARTURES_FINE = Pattern.compile(".*?" //
+ "<td class=\"[\\w ]*\">(\\d{1,2}:\\d{2})</td>\n" // plannedTime
+ "(?:<td class=\"[\\w ]*prognosis[\\w ]*\">\n" //
+ "(?:&nbsp;|<span class=\"rtLimit\\d\">(p&#252;nktlich|\\d{1,2}:\\d{2})</span>)\n</td>\n" // predictedTime
+ ")?.*?" //
+ "<img src=\"/hafas-res/img/(\\w+)_pic\\.gif\"[^>]*>\\s*(.*?)\\s*<.*?" // type, line
+ "<span class=\"bold\">\n" //
+ "<a href=\"http://www\\.saarfahrplan\\.de/cgi-bin/stboard\\.exe/dn\\?input=(\\d+)&[^>]*>" // destinationId
+ "\\s*(.*?)\\s*</a>\n" // destination
+ "</span>.*?" //
+ "(?:<td class=\"center sepline top\">\n(" + ParserUtils.P_PLATFORM + ").*?)?" // position
, Pattern.DOTALL);
public QueryDeparturesResult queryDepartures(final int stationId, final int maxDepartures, final boolean equivs) throws IOException public QueryDeparturesResult queryDepartures(final int stationId, final int maxDepartures, final boolean equivs) throws IOException
{ {
final ResultHeader header = new ResultHeader(SERVER_PRODUCT); final StringBuilder uri = new StringBuilder();
final QueryDeparturesResult result = new QueryDeparturesResult(header); uri.append(API_BASE).append("stboard.exe/dn");
uri.append("?productsFilter=").append(allProductsString());
uri.append("&boardType=dep");
uri.append("&disableEquivs=yes"); // don't use nearby stations
uri.append("&maxJourneys=50"); // ignore maxDepartures because result contains other stations
uri.append("&start=yes");
uri.append("&L=vs_java3");
uri.append("&input=").append(stationId);
// scrape page return xmlQueryDepartures(uri.toString(), stationId);
final String uri = departuresQueryUri(stationId, maxDepartures);
final CharSequence page = ParserUtils.scrape(uri);
// parse page
final Matcher mHeadCoarse = P_DEPARTURES_HEAD_COARSE.matcher(page);
if (mHeadCoarse.matches())
{
// messages
if (mHeadCoarse.group(3) != null)
{
result.stationDepartures.add(new StationDepartures(new Location(LocationType.STATION, stationId),
Collections.<Departure> emptyList(), null));
return result;
}
else if (mHeadCoarse.group(4) != null)
return new QueryDeparturesResult(header, Status.INVALID_STATION);
else if (mHeadCoarse.group(5) != null)
return new QueryDeparturesResult(header, Status.SERVICE_DOWN);
final Matcher mHeadFine = P_DEPARTURES_HEAD_FINE.matcher(mHeadCoarse.group(1));
if (mHeadFine.matches())
{
final String location = ParserUtils.resolveEntities(mHeadFine.group(1));
final Calendar currentTime = new GregorianCalendar(timeZone());
currentTime.clear();
ParserUtils.parseGermanDate(currentTime, mHeadFine.group(2));
ParserUtils.parseEuropeanTime(currentTime, mHeadFine.group(3));
final List<Departure> departures = new ArrayList<Departure>(8);
String oldZebra = null;
final Matcher mDepCoarse = P_DEPARTURES_COARSE.matcher(mHeadCoarse.group(2));
while (mDepCoarse.find())
{
final String zebra = mDepCoarse.group(1);
if (oldZebra != null && zebra.equals(oldZebra))
throw new IllegalArgumentException("missed row? last:" + zebra);
else
oldZebra = zebra;
final Matcher mDepFine = P_DEPARTURES_FINE.matcher(mDepCoarse.group(2));
if (mDepFine.matches())
{
final Calendar plannedTime = new GregorianCalendar(timeZone());
plannedTime.setTimeInMillis(currentTime.getTimeInMillis());
ParserUtils.parseEuropeanTime(plannedTime, mDepFine.group(1));
if (plannedTime.getTimeInMillis() - currentTime.getTimeInMillis() < -PARSER_DAY_ROLLOVER_THRESHOLD_MS)
plannedTime.add(Calendar.DAY_OF_MONTH, 1);
final Calendar predictedTime;
final String prognosis = ParserUtils.resolveEntities(mDepFine.group(2));
if (prognosis != null)
{
predictedTime = new GregorianCalendar(timeZone());
if (!prognosis.equals("pünktlich"))
{
predictedTime.setTimeInMillis(plannedTime.getTimeInMillis());
}
else
{
predictedTime.setTimeInMillis(currentTime.getTimeInMillis());
ParserUtils.parseEuropeanTime(predictedTime, prognosis);
}
}
else
{
predictedTime = null;
}
final String lineType = mDepFine.group(3);
final Line line = parseLine(lineType, ParserUtils.resolveEntities(mDepFine.group(4)), false);
final int destinationId = mDepFine.group(5) != null ? Integer.parseInt(mDepFine.group(5)) : 0;
final String destinationName = ParserUtils.resolveEntities(mDepFine.group(6));
final Location destination = new Location(destinationId > 0 ? LocationType.STATION : LocationType.ANY, destinationId, null,
destinationName);
final String position = mDepFine.group(7) != null ? "Gl. " + ParserUtils.resolveEntities(mDepFine.group(7)) : null;
final Departure dep = new Departure(plannedTime.getTime(), predictedTime != null ? predictedTime.getTime() : null, line,
position, destination, null, null);
if (!departures.contains(dep))
departures.add(dep);
}
else
{
throw new IllegalArgumentException("cannot parse '" + mDepCoarse.group(2) + "' on " + stationId);
}
}
result.stationDepartures.add(new StationDepartures(new Location(LocationType.STATION, stationId, null, location), departures, null));
return result;
}
else
{
throw new IllegalArgumentException("cannot parse '" + mHeadCoarse.group(1) + "' on " + stationId);
}
}
else
{
throw new IllegalArgumentException("cannot parse '" + page + "' on " + stationId);
}
} }
private static final String AUTOCOMPLETE_URI = API_BASE private static final String AUTOCOMPLETE_URI = API_BASE + "ajax-getstop.exe/dn?getstop=1&REQ0JourneyStopsS0A=255&S=%s?&js=true&";
+ "ajax-getstop.exe/eny?start=1&tpl=suggest2json&REQ0JourneyStopsS0A=1&getstop=1&noSession=yes&REQ0JourneyStopsB=12&REQ0JourneyStopsS0G=%s?&js=true&";
public List<Location> autocompleteStations(final CharSequence constraint) throws IOException public List<Location> autocompleteStations(final CharSequence constraint) throws IOException
{ {
@ -310,25 +175,23 @@ public class VgsProvider extends AbstractHafasProvider
} }
@Override @Override
protected char normalizeType(final String type) protected void appendCustomConnectionsQueryBinaryUri(final StringBuilder uri)
{ {
final String ucType = type.toUpperCase(); uri.append("&h2g-direct=11");
}
if (ucType.equals("E")) // Stadtbahn Karlsruhe: S4/S31/xxxxx @Override
return 'S'; public QueryConnectionsResult queryConnections(final Location from, final Location via, final Location to, final Date date, final boolean dep,
final int maxNumConnections, final String products, final WalkSpeed walkSpeed, final Accessibility accessibility,
final Set<Option> options) throws IOException
{
return queryConnectionsBinary(from, via, to, date, dep, maxNumConnections, products, walkSpeed, accessibility, options);
}
if (ucType.equals("BSS")) @Override
return 'B'; public QueryConnectionsResult queryMoreConnections(final QueryConnectionsContext contextObj, final boolean later, final int numConnections)
if (ucType.equals("BOV")) throws IOException
return 'B'; {
return queryMoreConnectionsBinary(contextObj, later, numConnections);
final char t = super.normalizeType(type);
if (t != 0)
return t;
if (ucType.equals("T84")) // U.K.
return '?';
return 0;
} }
} }

View file

@ -76,8 +76,9 @@ public class VgsProviderLiveTest extends AbstractProviderLiveTest
@Test @Test
public void shortConnection() throws Exception public void shortConnection() throws Exception
{ {
final QueryConnectionsResult result = queryConnections(new Location(LocationType.STATION, 0, null, "Hauptwache"), null, new Location( final QueryConnectionsResult result = queryConnections(new Location(LocationType.STATION, 10640, "Saarbrücken", "Hauptbahnhof"), null,
LocationType.STATION, 0, null, "Südbahnhof"), new Date(), true, ALL_PRODUCTS, WalkSpeed.NORMAL, Accessibility.NEUTRAL); new Location(LocationType.STATION, 10700, "Saarbrücken", "Ostbahnhof"), new Date(), true, ALL_PRODUCTS, WalkSpeed.NORMAL,
Accessibility.NEUTRAL);
System.out.println(result); System.out.println(result);
final QueryConnectionsResult laterResult = queryMoreConnections(result.context, true); final QueryConnectionsResult laterResult = queryMoreConnections(result.context, true);
System.out.println(laterResult); System.out.println(laterResult);