Denmark departures

git-svn-id: https://public-transport-enabler.googlecode.com/svn/trunk@588 0924bc21-9374-b0fa-ee44-9ff1593b38f0
This commit is contained in:
andreas.schildbach@gmail.com 2011-05-01 08:16:01 +00:00
parent 2cd4ebb926
commit 8833e42bb6
12 changed files with 719 additions and 513 deletions

View file

@ -21,6 +21,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
@ -30,11 +31,15 @@ import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import de.schildbach.pte.dto.Connection;
import de.schildbach.pte.dto.Departure;
import de.schildbach.pte.dto.GetConnectionDetailsResult;
import de.schildbach.pte.dto.Line;
import de.schildbach.pte.dto.Location;
@ -42,6 +47,8 @@ import de.schildbach.pte.dto.LocationType;
import de.schildbach.pte.dto.NearbyStationsResult;
import de.schildbach.pte.dto.QueryConnectionsResult;
import de.schildbach.pte.dto.QueryConnectionsResult.Status;
import de.schildbach.pte.dto.QueryDeparturesResult;
import de.schildbach.pte.dto.StationDepartures;
import de.schildbach.pte.util.Color;
import de.schildbach.pte.util.ParserUtils;
import de.schildbach.pte.util.XmlPullUtil;
@ -193,6 +200,178 @@ public abstract class AbstractHafasProvider implements NetworkProvider
}
}
private static final Pattern P_AJAX_GET_STOPS_JSON = Pattern.compile("SLs\\.sls=(.*?);SLs\\.showSuggestion\\(\\);", Pattern.DOTALL);
private static final Pattern P_AJAX_GET_STOPS_ID = Pattern.compile(".*?@L=(\\d+)@.*?");
protected final List<Location> ajaxGetStops(final String uri) throws IOException
{
final CharSequence page = ParserUtils.scrape(uri);
final Matcher mJson = P_AJAX_GET_STOPS_JSON.matcher(page);
if (mJson.matches())
{
final String json = mJson.group(1);
final List<Location> results = new ArrayList<Location>();
try
{
final JSONObject head = new JSONObject(json);
final JSONArray aSuggestions = head.getJSONArray("suggestions");
for (int i = 0; i < aSuggestions.length(); i++)
{
final JSONObject suggestion = aSuggestions.optJSONObject(i);
if (suggestion != null)
{
final int type = suggestion.getInt("type");
final String value = suggestion.getString("value");
final int lat = suggestion.getInt("ycoord");
final int lon = suggestion.getInt("xcoord");
int localId = 0;
final Matcher m = P_AJAX_GET_STOPS_ID.matcher(suggestion.getString("id"));
if (m.matches())
localId = Integer.parseInt(m.group(1));
if (type == 1) // station
{
results.add(new Location(LocationType.STATION, localId, lat, lon, null, value));
}
else if (type == 2) // address
{
results.add(new Location(LocationType.ADDRESS, 0, lat, lon, null, value));
}
else if (type == 4) // poi
{
results.add(new Location(LocationType.POI, localId, lat, lon, null, value));
}
else
{
throw new IllegalStateException("unknown type " + type + " on " + uri);
}
}
}
return results;
}
catch (final JSONException x)
{
x.printStackTrace();
throw new RuntimeException("cannot parse: '" + json + "' on " + uri, x);
}
}
else
{
throw new RuntimeException("cannot parse: '" + page + "' on " + uri);
}
}
private static final Pattern P_XML_QUERY_DEPARTURES_COARSE = Pattern.compile("\\G<Journey (.*?)(?:/>|><HIMMessage (.*?)/></Journey>)(?:\n|\\z)",
Pattern.DOTALL);
private static final Pattern P_XML_QUERY_DEPARTURES_FINE = Pattern.compile("" //
+ "fpTime\\s*=\"(\\d{1,2}:\\d{2})\"\\s*" // time
+ "fpDate\\s*=\"(\\d{2}\\.\\d{2}\\.\\d{2})\"\\s*" // date
+ "delay\\s*=\"(?:-|k\\.A\\.?|cancel|\\+?\\s*(\\d+))\"\\s*" // delay
+ "(?:e_delay\\s*=\"\\d+\"\\s*)?" // (???)
+ "(?:newpl\\s*=\"([^\"]*)\"\\s*)?" //
+ "(?:platform\\s*=\"([^\"]*)\"\\s*)?" // position
+ "targetLoc\\s*=\"([^\"]*)\"\\s*" // destination
+ "(?:hafasname\\s*=\"[^\"]*\"\\s*)?" // (???)
+ "prod\\s*=\"([^\"]*)\"\\s*" // line
+ "(?:class\\s*=\"[^\"]*\"\\s*)?" // (???)
+ "(?:dir\\s*=\"[^\"]*\"\\s*)?" // (destination)
+ "(?:depStation\\s*=\"(.*?)\"\\s*)?" //
+ "(?:delayReason\\s*=\"([^\"]*)\"\\s*)?" // message
+ "(?:is_reachable\\s*=\"([^\"]*)\"\\s*)?" // (???)
);
private static final Pattern P_XML_QUERY_DEPARTURES_MESSAGES = Pattern.compile("<Err code=\"([^\"]*)\" text=\"([^\"]*)\"");
protected QueryDeparturesResult xmlQueryDepartures(final String uri, final int stationId) throws IOException
{
// scrape page
final CharSequence page = ParserUtils.scrape(uri);
final QueryDeparturesResult result = new QueryDeparturesResult();
// parse page
final Matcher mMessage = P_XML_QUERY_DEPARTURES_MESSAGES.matcher(page);
if (mMessage.find())
{
final String code = mMessage.group(1);
final String text = mMessage.group(2);
if (code.equals("H730")) // Your input is not valid
return new QueryDeparturesResult(QueryDeparturesResult.Status.INVALID_STATION);
if (code.equals("H890"))
{
result.stationDepartures.add(new StationDepartures(new Location(LocationType.STATION, stationId),
Collections.<Departure> emptyList(), null));
return result;
}
throw new IllegalArgumentException("unknown error " + code + ", " + text);
}
final List<Departure> departures = new ArrayList<Departure>(8);
final Matcher mCoarse = P_XML_QUERY_DEPARTURES_COARSE.matcher(page);
while (mCoarse.find())
{
// TODO parse HIMMessage
final Matcher mFine = P_XML_QUERY_DEPARTURES_FINE.matcher(mCoarse.group(1));
if (mFine.matches())
{
if (mFine.group(8) == null)
{
final Calendar plannedTime = new GregorianCalendar(timeZone());
plannedTime.clear();
ParserUtils.parseEuropeanTime(plannedTime, mFine.group(1));
ParserUtils.parseGermanDate(plannedTime, mFine.group(2));
final Calendar predictedTime;
if (mFine.group(3) != null)
{
predictedTime = new GregorianCalendar(timeZone());
predictedTime.setTimeInMillis(plannedTime.getTimeInMillis());
predictedTime.add(Calendar.MINUTE, Integer.parseInt(mFine.group(3)));
}
else
{
predictedTime = null;
}
// TODO parse newpl if present
final String position = mFine.group(5) != null ? "Gl. " + ParserUtils.resolveEntities(mFine.group(5)) : null;
final String destination = ParserUtils.resolveEntities(mFine.group(6)).trim();
final String line = normalizeLine(ParserUtils.resolveEntities(mFine.group(7)));
final String message;
if (mFine.group(9) != null)
{
final String m = ParserUtils.resolveEntities(mFine.group(9)).trim();
message = m.length() > 0 ? m : null;
}
else
{
message = null;
}
departures.add(new Departure(plannedTime.getTime(), predictedTime != null ? predictedTime.getTime() : null, line,
line != null ? lineColors(line) : null, null, position, 0, destination, message));
}
}
else
{
throw new IllegalArgumentException("cannot parse '" + mCoarse.group(1) + "' on " + uri);
}
}
result.stationDepartures.add(new StationDepartures(new Location(LocationType.STATION, stationId), departures, null));
return result;
}
public QueryConnectionsResult queryConnections(Location from, Location via, Location to, final Date date, final boolean dep,
final String products, final WalkSpeed walkSpeed) throws IOException
{
@ -620,6 +799,290 @@ public abstract class AbstractHafasProvider implements NetworkProvider
throw new IllegalArgumentException(location.type.toString());
}
private final static Pattern P_WHITESPACE = Pattern.compile("\\s+");
private final String normalizeWhitespace(final String str)
{
return P_WHITESPACE.matcher(str).replaceAll("");
}
public GetConnectionDetailsResult getConnectionDetails(String connectionUri) throws IOException
{
throw new UnsupportedOperationException();
}
private static final Pattern P_XML_NEARBY_STATIONS_COARSE = Pattern.compile("\\G<St\\s*(.*?)/?>(?:\n|\\z)", Pattern.DOTALL);
private static final Pattern P_XML_NEARBY_STATIONS_FINE = Pattern.compile("" //
+ "evaId=\"(\\d+)\"\\s*" // id
+ "name=\"([^\"]+)\".*?" // name
+ "x=\"(\\d+)\"\\s*" // x
+ "y=\"(\\d+)\"\\s*" // y
);
private static final Pattern P_XML_NEARBY_STATIONS_MESSAGES = Pattern.compile("<Err code=\"([^\"]*)\" text=\"([^\"]*)\"");
protected final NearbyStationsResult xmlNearbyStations(final String uri) throws IOException
{
// scrape page
final CharSequence page = ParserUtils.scrape(uri);
final List<Location> stations = new ArrayList<Location>();
// parse page
final Matcher mMessage = P_XML_NEARBY_STATIONS_MESSAGES.matcher(page);
if (mMessage.find())
{
final String code = mMessage.group(1);
final String text = mMessage.group(2);
if (code.equals("H730")) // Your input is not valid
return new NearbyStationsResult(NearbyStationsResult.Status.INVALID_STATION);
if (code.equals("H890")) // No trains in result
return new NearbyStationsResult(stations);
throw new IllegalArgumentException("unknown error " + code + ", " + text);
}
final Matcher mCoarse = P_XML_NEARBY_STATIONS_COARSE.matcher(page);
while (mCoarse.find())
{
final Matcher mFine = P_XML_NEARBY_STATIONS_FINE.matcher(mCoarse.group(1));
if (mFine.matches())
{
final int parsedId = Integer.parseInt(mFine.group(1));
final String parsedName = ParserUtils.resolveEntities(mFine.group(2)).trim();
final int parsedLon = Integer.parseInt(mFine.group(3));
final int parsedLat = Integer.parseInt(mFine.group(4));
stations.add(new Location(LocationType.STATION, parsedId, parsedLat, parsedLon, null, parsedName));
}
else
{
throw new IllegalArgumentException("cannot parse '" + mCoarse.group(1) + "' on " + uri);
}
}
return new NearbyStationsResult(stations);
}
private final static Pattern P_NEARBY_COARSE = Pattern.compile("<tr class=\"(zebra[^\"]*)\">(.*?)</tr>", Pattern.DOTALL);
private final static Pattern P_NEARBY_FINE_COORDS = Pattern
.compile("REQMapRoute0\\.Location0\\.X=(-?\\d+)&(?:amp;)?REQMapRoute0\\.Location0\\.Y=(-?\\d+)&");
private final static Pattern P_NEARBY_FINE_LOCATION = Pattern.compile("[\\?&]input=(\\d+)&[^\"]*\">([^<]*)<");
protected abstract String nearbyStationUri(String stationId);
public NearbyStationsResult nearbyStations(final String stationId, final int lat, final int lon, final int maxDistance, final int maxStations)
throws IOException
{
if (stationId == null)
throw new IllegalArgumentException("stationId must be given");
final List<Location> stations = new ArrayList<Location>();
final String uri = nearbyStationUri(stationId);
final CharSequence page = ParserUtils.scrape(uri);
String oldZebra = null;
final Matcher mCoarse = P_NEARBY_COARSE.matcher(page);
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 Matcher mFineLocation = P_NEARBY_FINE_LOCATION.matcher(mCoarse.group(2));
if (mFineLocation.find())
{
int parsedLon = 0;
int parsedLat = 0;
final int parsedId = Integer.parseInt(mFineLocation.group(1));
final String parsedName = ParserUtils.resolveEntities(mFineLocation.group(2));
final Matcher mFineCoords = P_NEARBY_FINE_COORDS.matcher(mCoarse.group(2));
if (mFineCoords.find())
{
parsedLon = Integer.parseInt(mFineCoords.group(1));
parsedLat = Integer.parseInt(mFineCoords.group(2));
}
final String[] nameAndPlace = splitNameAndPlace(parsedName);
stations.add(new Location(LocationType.STATION, parsedId, parsedLat, parsedLon, nameAndPlace[0], nameAndPlace[1]));
}
else
{
throw new IllegalArgumentException("cannot parse '" + mCoarse.group(2) + "' on " + uri);
}
}
if (maxStations == 0 || maxStations >= stations.size())
return new NearbyStationsResult(stations);
else
return new NearbyStationsResult(stations.subList(0, maxStations));
}
protected static final Pattern P_NORMALIZE_LINE = Pattern.compile("([A-Za-zÄÖÜäöüßáàâéèêíìîóòôúùûØ/-]+)[\\s-]*(.*)");
protected String normalizeLine(final String type, final String line)
{
final char normalizedType = normalizeType(type);
if (normalizedType != 0)
{
if (line != null)
{
final Matcher m = P_NORMALIZE_LINE.matcher(line);
final String strippedLine = m.matches() ? m.group(1) + m.group(2) : line;
return normalizedType + strippedLine;
}
else
{
return Character.toString(normalizedType);
}
}
throw new IllegalStateException("cannot normalize type '" + type + "' line '" + line + "'");
}
protected abstract char normalizeType(String type);
protected final char normalizeCommonTypes(final String ucType)
{
// Intercity
if (ucType.equals("EC")) // EuroCity
return 'I';
if (ucType.equals("EN")) // EuroNight
return 'I';
if (ucType.equals("EIC")) // Ekspres InterCity, Polen
return 'I';
if (ucType.equals("ICE")) // InterCityExpress
return 'I';
if (ucType.equals("IC")) // InterCity
return 'I';
if (ucType.equals("ICT")) // InterCity
return 'I';
if (ucType.equals("CNL")) // CityNightLine
return 'I';
if (ucType.equals("OEC")) // ÖBB-EuroCity
return 'I';
if (ucType.equals("OIC")) // ÖBB-InterCity
return 'I';
if (ucType.equals("RJ")) // RailJet, Österreichische Bundesbahnen
return 'I';
if (ucType.equals("THA")) // Thalys
return 'I';
if (ucType.equals("TGV")) // Train à Grande Vitesse
return 'I';
if (ucType.equals("DNZ")) // Berlin-Saratov, Berlin-Moskva, Connections only?
return 'I';
if (ucType.equals("AIR")) // Generic Flight
return 'I';
if (ucType.equals("ECB")) // EC, Verona-München
return 'I';
if (ucType.equals("INZ")) // Nacht
return 'I';
if (ucType.equals("RHI")) // ICE
return 'I';
if (ucType.equals("RHT")) // TGV
return 'I';
if (ucType.equals("TGD")) // TGV
return 'I';
if (ucType.equals("IRX")) // IC
return 'I';
// Regional
if (ucType.equals("ZUG")) // Generic Train
return 'R';
if (ucType.equals("R")) // Generic Regional Train
return 'R';
if (ucType.equals("DPN")) // Dritter Personen Nahverkehr
return 'R';
if (ucType.equals("RB")) // RegionalBahn
return 'R';
if (ucType.equals("RE")) // RegionalExpress
return 'R';
if (ucType.equals("IR")) // Interregio
return 'R';
if (ucType.equals("IRE")) // Interregio Express
return 'R';
if (ucType.equals("HEX")) // Harz-Berlin-Express, Veolia
return 'R';
if (ucType.equals("WFB")) // Westfalenbahn
return 'R';
if (ucType.equals("RT")) // RegioTram
return 'R';
if (ucType.equals("REX")) // RegionalExpress, Österreich
return 'R';
if (ucType.equals("OS")) // Osobný vlak, Slovakia oder Osobní vlak, Czech Republic
return 'R';
if (ucType.equals("SP")) // Spěšný vlak, Czech Republic
return 'R';
// Suburban Trains
if (ucType.equals("S")) // Generic S-Bahn
return 'S';
// Subway
if (ucType.equals("U")) // Generic U-Bahn
return 'U';
// Tram
if (ucType.equals("STR")) // Generic Tram
return 'T';
// Bus
if (ucType.equals("BUS")) // Generic Bus
return 'B';
if (ucType.equals("AST")) // Anruf-Sammel-Taxi
return 'B';
if (ucType.equals("RUF")) // Rufbus
return 'B';
if (ucType.equals("SEV")) // Schienen-Ersatz-Verkehr
return 'B';
if (ucType.equals("BUSSEV")) // Schienen-Ersatz-Verkehr
return 'B';
if (ucType.equals("BSV")) // Bus SEV
return 'B';
if (ucType.equals("FB")) // Luxemburg-Saarbrücken
return 'B';
// Ferry
if (ucType.equals("SCH")) // Schiff
return 'F';
if (ucType.equals("AS")) // SyltShuttle, eigentlich Autoreisezug
return 'F';
return 0;
}
protected String normalizeLine(final String line)
{
if (line == null || line.length() == 0)
return null;
final Matcher m = P_NORMALIZE_LINE.matcher(line);
if (m.matches())
{
final String type = m.group(1);
final String number = m.group(2);
final char normalizedType = normalizeType(type);
if (normalizedType != 0)
return normalizedType + type + number;
throw new IllegalStateException("cannot normalize type " + type + " number " + number + " line " + line);
}
throw new IllegalStateException("cannot normalize line " + line);
}
private static final Pattern P_LINE_S = Pattern.compile("S\\d+");
private static final Pattern P_LINE_SN = Pattern.compile("SN\\d*");
@ -755,214 +1218,6 @@ public abstract class AbstractHafasProvider implements NetworkProvider
+ longCategory + "'");
}
private final static Pattern P_WHITESPACE = Pattern.compile("\\s+");
private final String normalizeWhitespace(final String str)
{
return P_WHITESPACE.matcher(str).replaceAll("");
}
public GetConnectionDetailsResult getConnectionDetails(String connectionUri) throws IOException
{
throw new UnsupportedOperationException();
}
private final static Pattern P_NEARBY_COARSE = Pattern.compile("<tr class=\"(zebra[^\"]*)\">(.*?)</tr>", Pattern.DOTALL);
private final static Pattern P_NEARBY_FINE_COORDS = Pattern
.compile("REQMapRoute0\\.Location0\\.X=(-?\\d+)&(?:amp;)?REQMapRoute0\\.Location0\\.Y=(-?\\d+)&");
private final static Pattern P_NEARBY_FINE_LOCATION = Pattern.compile("[\\?&]input=(\\d+)&[^\"]*\">([^<]*)<");
protected abstract String nearbyStationUri(String stationId);
public NearbyStationsResult nearbyStations(final String stationId, final int lat, final int lon, final int maxDistance, final int maxStations)
throws IOException
{
if (stationId == null)
throw new IllegalArgumentException("stationId must be given");
final List<Location> stations = new ArrayList<Location>();
final String uri = nearbyStationUri(stationId);
final CharSequence page = ParserUtils.scrape(uri);
String oldZebra = null;
final Matcher mCoarse = P_NEARBY_COARSE.matcher(page);
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 Matcher mFineLocation = P_NEARBY_FINE_LOCATION.matcher(mCoarse.group(2));
if (mFineLocation.find())
{
int parsedLon = 0;
int parsedLat = 0;
final int parsedId = Integer.parseInt(mFineLocation.group(1));
final String parsedName = ParserUtils.resolveEntities(mFineLocation.group(2));
final Matcher mFineCoords = P_NEARBY_FINE_COORDS.matcher(mCoarse.group(2));
if (mFineCoords.find())
{
parsedLon = Integer.parseInt(mFineCoords.group(1));
parsedLat = Integer.parseInt(mFineCoords.group(2));
}
final String[] nameAndPlace = splitNameAndPlace(parsedName);
stations.add(new Location(LocationType.STATION, parsedId, parsedLat, parsedLon, nameAndPlace[0], nameAndPlace[1]));
}
else
{
throw new IllegalArgumentException("cannot parse '" + mCoarse.group(2) + "' on " + uri);
}
}
if (maxStations == 0 || maxStations >= stations.size())
return new NearbyStationsResult(stations);
else
return new NearbyStationsResult(stations.subList(0, maxStations));
}
protected static final Pattern P_NORMALIZE_LINE = Pattern.compile("([A-Za-zÄÖÜäöüßáàâéèêíìîóòôúùû/-]+)[\\s-]*(.*)");
protected String normalizeLine(final String type, final String line)
{
final char normalizedType = normalizeType(type);
if (normalizedType != 0)
{
if (line != null)
{
final Matcher m = P_NORMALIZE_LINE.matcher(line);
final String strippedLine = m.matches() ? m.group(1) + m.group(2) : line;
return normalizedType + strippedLine;
}
else
{
return Character.toString(normalizedType);
}
}
throw new IllegalStateException("cannot normalize type '" + type + "' line '" + line + "'");
}
protected abstract char normalizeType(String type);
protected final char normalizeCommonTypes(final String ucType)
{
// Intercity
if (ucType.equals("EC")) // EuroCity
return 'I';
if (ucType.equals("EN")) // EuroNight
return 'I';
if (ucType.equals("EIC")) // Ekspres InterCity, Polen
return 'I';
if (ucType.equals("ICE")) // InterCityExpress
return 'I';
if (ucType.equals("IC")) // InterCity
return 'I';
if (ucType.equals("ICT")) // InterCity
return 'I';
if (ucType.equals("CNL")) // CityNightLine
return 'I';
if (ucType.equals("OEC")) // ÖBB-EuroCity
return 'I';
if (ucType.equals("OIC")) // ÖBB-InterCity
return 'I';
if (ucType.equals("RJ")) // RailJet, Österreichische Bundesbahnen
return 'I';
if (ucType.equals("THA")) // Thalys
return 'I';
if (ucType.equals("TGV")) // Train à Grande Vitesse
return 'I';
if (ucType.equals("DNZ")) // Berlin-Saratov, Berlin-Moskva, Connections only?
return 'I';
if (ucType.equals("AIR")) // Generic Flight
return 'I';
if (ucType.equals("ECB")) // EC, Verona-München
return 'I';
if (ucType.equals("INZ")) // Nacht
return 'I';
if (ucType.equals("RHI")) // ICE
return 'I';
if (ucType.equals("RHT")) // TGV
return 'I';
if (ucType.equals("TGD")) // TGV
return 'I';
if (ucType.equals("IRX")) // IC
return 'I';
// Regional
if (ucType.equals("ZUG")) // Generic Train
return 'R';
if (ucType.equals("R")) // Generic Regional Train
return 'R';
if (ucType.equals("DPN")) // Dritter Personen Nahverkehr
return 'R';
if (ucType.equals("RB")) // RegionalBahn
return 'R';
if (ucType.equals("RE")) // RegionalExpress
return 'R';
if (ucType.equals("IR")) // Interregio
return 'R';
if (ucType.equals("IRE")) // Interregio Express
return 'R';
if (ucType.equals("HEX")) // Harz-Berlin-Express, Veolia
return 'R';
if (ucType.equals("WFB")) // Westfalenbahn
return 'R';
if (ucType.equals("RT")) // RegioTram
return 'R';
if (ucType.equals("REX")) // RegionalExpress, Österreich
return 'R';
if (ucType.equals("OS")) // Osobný vlak, Slovakia oder Osobní vlak, Czech Republic
return 'R';
if (ucType.equals("SP")) // Spěšný vlak, Czech Republic
return 'R';
// Suburban Trains
if (ucType.equals("S")) // Generic S-Bahn
return 'S';
// Subway
if (ucType.equals("U")) // Generic U-Bahn
return 'U';
// Tram
if (ucType.equals("STR")) // Generic Tram
return 'T';
// Bus
if (ucType.equals("BUS")) // Generic Bus
return 'B';
if (ucType.equals("AST")) // Anruf-Sammel-Taxi
return 'B';
if (ucType.equals("RUF")) // Rufbus
return 'B';
if (ucType.equals("SEV")) // Schienen-Ersatz-Verkehr
return 'B';
if (ucType.equals("BUSSEV")) // Schienen-Ersatz-Verkehr
return 'B';
if (ucType.equals("BSV")) // Bus SEV
return 'B';
if (ucType.equals("FB")) // Luxemburg-Saarbrücken
return 'B';
// Ferry
if (ucType.equals("SCH")) // Schiff
return 'F';
if (ucType.equals("AS")) // SyltShuttle, eigentlich Autoreisezug
return 'F';
return 0;
}
private static final Pattern P_CONNECTION_ID = Pattern.compile("co=(C\\d+-\\d+)&");
protected static String extractConnectionId(final String link)

View file

@ -20,7 +20,6 @@ package de.schildbach.pte;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
@ -28,7 +27,6 @@ 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.Line;
import de.schildbach.pte.dto.Location;
@ -36,7 +34,6 @@ import de.schildbach.pte.dto.LocationType;
import de.schildbach.pte.dto.NearbyStationsResult;
import de.schildbach.pte.dto.QueryConnectionsResult;
import de.schildbach.pte.dto.QueryDeparturesResult;
import de.schildbach.pte.dto.StationDepartures;
import de.schildbach.pte.exception.SessionExpiredException;
import de.schildbach.pte.util.ParserUtils;
@ -485,107 +482,18 @@ public final class BahnProvider extends AbstractHafasProvider
}
}
private String departuresQueryUri(final String stationId, final int maxDepartures)
public QueryDeparturesResult queryDepartures(final String stationId, final int maxDepartures, final boolean equivs) throws IOException
{
final StringBuilder uri = new StringBuilder();
uri.append(API_BASE).append("bhftafel.exe/dn");
uri.append("?productsFilter=11111111111111");
uri.append("&boardType=dep");
// taken from railnavigator; ignore maxDepartures because result contains other stations
uri.append("&maxJourneys=50");
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);
return uri.toString();
}
static final Pattern P_DEPARTURES_COARSE = Pattern.compile("\\G<Journey (.*?)/?>(?:\n|\\z)", Pattern.DOTALL);
static final Pattern P_DEPARTURES_FINE = Pattern.compile("" //
+ "fpTime=\"(\\d{1,2}:\\d{2})\" fpDate=\"(\\d{2}\\.\\d{2}\\.\\d{2})\" \n" // time, date
+ "delay=\"(?:-|k\\.A\\.?|cancel|\\+?\\s*(\\d+))\" \n" // delay
+ "(?:platform =\"([^\"]*)\" \n)?" // position
+ "(?:newpl =\"([^\"]*)\" \n)?" //
+ "targetLoc=\"(.*?)\" \n" // destination
+ "prod=\"([^\"]*)\" \n" // line
+ "(?:dir=[^\n]*\n)?" // (destination)
+ "(?:depStation=\"(.*?)\"\n)?" //
+ "delayReason=\"([^\"]*)\"\n" // message
);
private static final Pattern P_DEPARTURES_MESSAGES = Pattern.compile("<Err code=\"([^\"]*)\" text=\"([^\"]*)\"");
public QueryDeparturesResult queryDepartures(final String stationId, final int maxDepartures, final boolean equivs) throws IOException
{
final QueryDeparturesResult result = new QueryDeparturesResult();
// scrape page
final String uri = departuresQueryUri(stationId, maxDepartures);
final CharSequence page = ParserUtils.scrape(uri);
// parse page
final Matcher mMessage = P_DEPARTURES_MESSAGES.matcher(page);
if (mMessage.find())
{
final String code = mMessage.group(1);
final String text = mMessage.group(2);
if (code.equals("H730")) // Your input is not valid
return new QueryDeparturesResult(QueryDeparturesResult.Status.INVALID_STATION);
if (code.equals("H890"))
{
result.stationDepartures.add(new StationDepartures(new Location(LocationType.STATION, Integer.parseInt(stationId)), Collections
.<Departure> emptyList(), null));
return result;
}
throw new IllegalArgumentException("unknown error " + code + ", " + text);
}
final List<Departure> departures = new ArrayList<Departure>(8);
final Matcher mDepCoarse = P_DEPARTURES_COARSE.matcher(page);
while (mDepCoarse.find())
{
final Matcher mDepFine = P_DEPARTURES_FINE.matcher(mDepCoarse.group(1));
if (mDepFine.matches())
{
if (mDepFine.group(8) == null)
{
final Calendar plannedTime = new GregorianCalendar(timeZone());
plannedTime.clear();
ParserUtils.parseEuropeanTime(plannedTime, mDepFine.group(1));
ParserUtils.parseGermanDate(plannedTime, mDepFine.group(2));
final Calendar predictedTime;
if (mDepFine.group(3) != null)
{
predictedTime = new GregorianCalendar(timeZone());
predictedTime.setTimeInMillis(plannedTime.getTimeInMillis());
predictedTime.add(Calendar.MINUTE, Integer.parseInt(mDepFine.group(3)));
}
else
{
predictedTime = null;
}
final String position = mDepFine.group(4) != null ? "Gl. " + ParserUtils.resolveEntities(mDepFine.group(4)) : null;
final String destination = ParserUtils.resolveEntities(mDepFine.group(6)).trim();
final String line = normalizeLine(ParserUtils.resolveEntities(mDepFine.group(7)));
final String message = ParserUtils.resolveEntities(mDepFine.group(9)).trim();
departures.add(new Departure(plannedTime.getTime(), predictedTime != null ? predictedTime.getTime() : null, line,
line != null ? lineColors(line) : null, null, position, 0, destination, message.length() > 0 ? message : null));
}
}
else
{
throw new IllegalArgumentException("cannot parse '" + mDepCoarse.group(1) + "' on " + stationId);
}
}
result.stationDepartures.add(new StationDepartures(new Location(LocationType.STATION, Integer.parseInt(stationId)), departures, null));
return result;
return xmlQueryDepartures(uri.toString(), Integer.parseInt(stationId));
}
@Override
@ -599,7 +507,8 @@ public final class BahnProvider extends AbstractHafasProvider
private static final Pattern P_NORMALIZE_LINE_RUSSIA = Pattern.compile("(?:D\\s*)?(\\d{1,3}(?:[A-Z]{2}|Y))");
private static final Pattern P_NORMALIZE_LINE_SBAHN = Pattern.compile("S\\w*\\d+");
private static String normalizeLine(final String line)
@Override
protected final String normalizeLine(final String line)
{
// TODO ARZ Simplon Tunnel: Brig - Iselle di Trasquera
// ARZ29171

View file

@ -906,7 +906,8 @@ public final class BvgProvider extends AbstractHafasProvider
private static final Pattern P_NORMALIZE_LINE_SPECIAL_NUMBER = Pattern.compile("\\d{4,}");
private static final Pattern P_NORMALIZE_LINE_SPECIAL_BUS = Pattern.compile("Bus[A-Z]");
private static String normalizeLine(final String line)
@Override
protected String normalizeLine(final String line)
{
if (line == null || line.length() == 0)
return null;

View file

@ -0,0 +1,173 @@
/*
* 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 java.io.IOException;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import de.schildbach.pte.dto.Location;
import de.schildbach.pte.dto.NearbyStationsResult;
import de.schildbach.pte.dto.QueryDeparturesResult;
import de.schildbach.pte.util.ParserUtils;
/**
* @author Andreas Schildbach
*/
public class DsbProvider extends AbstractHafasProvider
{
public static final NetworkId NETWORK_ID = NetworkId.DSB;
private static final String API_BASE = "http://mobil.rejseplanen.dk/mobil-bin/";
public DsbProvider()
{
super(null, null);
}
public NetworkId id()
{
return NETWORK_ID;
}
public boolean hasCapabilities(final Capability... capabilities)
{
for (final Capability capability : capabilities)
if (capability == Capability.DEPARTURES)
return true;
return false;
}
private static final String AUTOCOMPLETE_URI = API_BASE + "ajax-getstop.exe/dn?getstop=1&REQ0JourneyStopsS0A=255&S=%s?&js=true&";
private static final String ENCODING = "ISO-8859-1";
@Override
public List<Location> autocompleteStations(final CharSequence constraint) throws IOException
{
final String uri = String.format(AUTOCOMPLETE_URI, ParserUtils.urlEncode(constraint.toString(), ENCODING));
return ajaxGetStops(uri);
}
@Override
protected String nearbyStationUri(String stationId)
{
throw new UnsupportedOperationException();
}
@Override
public NearbyStationsResult nearbyStations(final String stationId, final int lat, final int lon, final int maxDistance, final int maxStations)
throws IOException
{
final StringBuilder uri = new StringBuilder(API_BASE);
uri.append("stboard.exe/dn");
uri.append("?productsFilter=11111111111");
uri.append("&boardType=dep");
uri.append("&input=").append(ParserUtils.urlEncode(stationId));
uri.append("&sTI=1&start=yes&hcount=0");
uri.append("&L=vs_java3");
// &inputTripelId=A%3d1%40O%3dCopenhagen%20Airport%40X%3d12646941%40Y%3d55629753%40U%3d86%40L%3d900000011%40B%3d1
return xmlNearbyStations(uri.toString());
}
private static final Pattern P_NORMALIZE_LINE_AND_TYPE = Pattern.compile("([^#]*)#(.*)");
@Override
protected String normalizeLine(final String line)
{
final Matcher m = P_NORMALIZE_LINE_AND_TYPE.matcher(line);
if (m.matches())
{
final String number = m.group(1).replaceAll("\\s+", " ");
final String type = m.group(2);
final char normalizedType = normalizeType(type);
if (normalizedType != 0)
return normalizedType + number;
throw new IllegalStateException("cannot normalize type " + type + " number " + number + " line " + line);
}
throw new IllegalStateException("cannot normalize line " + line);
}
@Override
protected char normalizeType(final String type)
{
final String ucType = type.toUpperCase();
if ("ICL".equals(ucType))
return 'I';
if ("ØR".equals(ucType))
return 'R';
if ("RA".equals(ucType))
return 'R';
if ("RX".equals(ucType))
return 'R';
if ("PP".equals(ucType))
return 'R';
if ("S-TOG".equals(ucType))
return 'S';
if ("MET".equals(ucType))
return 'U';
if ("BYBUS".equals(ucType))
return 'B';
if ("X-BUS".equals(ucType))
return 'B';
if ("HV-BUS".equals(ucType)) // Havnebus
return 'B';
if ("T-BUS".equals(ucType)) // Togbus
return 'B';
if ("TELEBUS".equals(ucType))
return 'P';
if ("TELETAXI".equals(ucType))
return 'P';
if ("FÆRGE".equals(ucType))
return 'F';
final char t = normalizeCommonTypes(ucType);
if (t != 0)
return t;
return 0;
}
public QueryDeparturesResult queryDepartures(final String stationId, final int maxDepartures, final boolean equivs) throws IOException
{
final StringBuilder uri = new StringBuilder();
uri.append(API_BASE).append("stboard.exe/dn");
uri.append("?productsFilter=11111111111");
uri.append("&boardType=dep");
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);
return xmlQueryDepartures(uri.toString(), Integer.parseInt(stationId));
}
}

View file

@ -37,6 +37,9 @@ public enum NetworkId
// Netherlands
NS,
// Denmark
DSB,
// United Kingdom
TFL, TLEM, TLEA, TLSE, TLSW,

View file

@ -40,7 +40,7 @@ public class NsProvider extends AbstractHafasProvider
{
public static final NetworkId NETWORK_ID = NetworkId.NS;
public static final String OLD_NETWORK_ID = "hafas.bene-system.com";
private static final String API_URI = "http://hafas.bene-system.com/bin/extxml.exe";
private static final String API_URI = "http://hafas.bene-system.com/bin/extxml.exe"; // http://plannerint.b-rail.be/bin/extxml.exe
private static final long PARSER_DAY_ROLLOVER_THRESHOLD_MS = 12 * 60 * 60 * 1000;
@ -179,27 +179,6 @@ public class NsProvider extends AbstractHafasProvider
}
}
private String normalizeLine(final String line)
{
if (line == null || line.length() == 0)
return null;
final Matcher m = P_NORMALIZE_LINE.matcher(line);
if (m.matches())
{
final String type = m.group(1);
final String number = m.group(2);
final char normalizedType = normalizeType(type);
if (normalizedType != 0)
return normalizedType + type + number;
throw new IllegalStateException("cannot normalize type " + type + " number " + number + " line " + line);
}
throw new IllegalStateException("cannot normalize line " + line);
}
@Override
protected char normalizeType(final String type)
{

View file

@ -76,71 +76,13 @@ public class OebbProvider extends AbstractHafasProvider
private static final String AUTOCOMPLETE_URI = API_BASE
+ "ajax-getstop.exe/dny?start=1&tpl=suggest2json&REQ0JourneyStopsS0A=255&REQ0JourneyStopsB=12&S=%s?&js=true&";
private static final String ENCODING = "ISO-8859-1";
private static final Pattern P_AUTOCOMPLETE_JSON = Pattern.compile("SLs\\.sls=(.*?);SLs\\.showSuggestion\\(\\);", Pattern.DOTALL);
private static final Pattern P_AUTOCOMPLETE_ID = Pattern.compile(".*?@L=(\\d+)@.*?");
@Override
public List<Location> autocompleteStations(final CharSequence constraint) throws IOException
{
final String uri = String.format(AUTOCOMPLETE_URI, ParserUtils.urlEncode(constraint.toString(), ENCODING));
final CharSequence page = ParserUtils.scrape(uri);
final Matcher mJson = P_AUTOCOMPLETE_JSON.matcher(page);
if (mJson.matches())
{
final String json = mJson.group(1);
final List<Location> results = new ArrayList<Location>();
try
{
final JSONObject head = new JSONObject(json);
final JSONArray aSuggestions = head.getJSONArray("suggestions");
for (int i = 0; i < aSuggestions.length(); i++)
{
final JSONObject suggestion = aSuggestions.optJSONObject(i);
if (suggestion != null)
{
final int type = suggestion.getInt("type");
final String value = suggestion.getString("value");
final int lat = suggestion.getInt("ycoord");
final int lon = suggestion.getInt("xcoord");
int localId = 0;
final Matcher m = P_AUTOCOMPLETE_ID.matcher(suggestion.getString("id"));
if (m.matches())
localId = Integer.parseInt(m.group(1));
if (type == 1) // station
{
results.add(new Location(LocationType.STATION, localId, lat, lon, null, value));
}
else if (type == 2) // address
{
results.add(new Location(LocationType.ADDRESS, 0, lat, lon, null, value));
}
else if (type == 4) // poi
{
results.add(new Location(LocationType.POI, localId, lat, lon, null, value));
}
else
{
throw new IllegalStateException("unknown type " + type + " on " + uri);
}
}
}
return results;
}
catch (final JSONException x)
{
x.printStackTrace();
throw new RuntimeException("cannot parse: '" + json + "' on " + uri, x);
}
}
else
{
throw new RuntimeException("cannot parse: '" + page + "' on " + uri);
}
return ajaxGetStops(uri);
}
private final String NEARBY_URI = API_BASE + "stboard.exe/dn?distance=50&near=Suchen&input=%s";
@ -604,7 +546,8 @@ public class OebbProvider extends AbstractHafasProvider
private static final Pattern P_NORMALIZE_LINE_RUSSIA = Pattern.compile("\\d{1,3}[A-Z]{2}");
private static final Pattern P_NORMALIZE_LINE_RUSSIA_INT = Pattern.compile("\\d{3}Y");
private String normalizeLine(final String line)
@Override
protected String normalizeLine(final String line)
{
final Matcher m = P_NORMALIZE_LINE.matcher(line);
if (m.matches())

View file

@ -603,7 +603,8 @@ public class RmvProvider extends AbstractHafasProvider
}
}
private static String normalizeLine(final String line)
@Override
protected String normalizeLine(final String line)
{
if (line == null || line.length() == 0)
return null;

View file

@ -202,27 +202,6 @@ public class SbbProvider extends AbstractHafasProvider
}
}
private String normalizeLine(final String line)
{
if (line == null || line.length() == 0)
return null;
final Matcher m = P_NORMALIZE_LINE.matcher(line);
if (m.matches())
{
final String type = m.group(1);
final String number = m.group(2);
final char normalizedType = normalizeType(type);
if (normalizedType != 0)
return normalizedType + type + number;
throw new IllegalStateException("cannot normalize type " + type + " number " + number + " line " + line);
}
throw new IllegalStateException("cannot normalize line " + line);
}
private static final Pattern P_NORMALIZE_TYPE_SBAHN = Pattern.compile("SN?\\d*");
private static final Pattern P_NORMALIZE_TYPE_BUS = Pattern.compile("BUS\\w*");

View file

@ -179,27 +179,6 @@ public class SncbProvider extends AbstractHafasProvider
}
}
private String normalizeLine(final String line)
{
if (line == null || line.length() == 0)
return null;
final Matcher m = P_NORMALIZE_LINE.matcher(line);
if (m.matches())
{
final String type = m.group(1);
final String number = m.group(2);
final char normalizedType = normalizeType(type);
if (normalizedType != 0)
return normalizedType + type + number;
throw new IllegalStateException("cannot normalize type " + type + " number " + number + " line " + line);
}
throw new IllegalStateException("cannot normalize line " + line);
}
@Override
protected char normalizeType(final String type)
{

View file

@ -17,7 +17,6 @@
package de.schildbach.pte;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
import java.util.regex.Matcher;
@ -29,49 +28,6 @@ import org.junit.Test;
*/
public class BahnProviderTest
{
@Test
public void coarseDeparture()
{
assertCoarseDepartures("" //
+ "<Journey fpTime=\"17:45\" fpDate=\"02.10.10\" \n" //
+ "delay=\"k.A\" \n" //
+ "platform =\"1\" \n" //
+ "targetLoc=\"Berlin Südkreuz (S)\" \n" //
+ "prod=\"S 41\" \n" //
+ "depStation=\"Berlin Sonnenallee\"\n" //
+ "delayReason=\" \"\n" //
+ "/>\n");
}
@Test
public void coarseDepartureWithoutTrailingNewLine()
{
assertCoarseDepartures("" //
+ "<Journey fpTime=\"17:45\" fpDate=\"02.10.10\" \n" //
+ "delay=\"k.A\" \n" //
+ "platform =\"1\" \n" //
+ "targetLoc=\"Berlin Südkreuz (S)\" \n" //
+ "prod=\"S 41\" \n" //
+ "depStation=\"Berlin Sonnenallee\"\n" //
+ "delayReason=\" \"\n" //
+ "/>");
}
@Test
public void coarseDepartureWithInvalidClosingBracket()
{
assertCoarseDepartures("" //
+ "<Journey fpTime=\"17:45\" fpDate=\"02.10.10\" \n" //
+ "delay=\"k.A\" \n" //
+ "platform =\"1\" \n" //
+ "targetLoc=\"Berlin Südkreuz (S)\" \n" //
+ "prod=\"S 41\" \n" //
+ "dir=\"Ringbahn ->\"\n" //
+ "depStation=\"Berlin Sonnenallee\"\n" //
+ "delayReason=\" \"\n" //
+ "/>\n");
}
@Test
public void connectionUebergang()
{
@ -82,21 +38,6 @@ public class BahnProviderTest
+ "<span class=\"bold\">Berlin-Lichtenberg</span><br />\n");
}
@Test
public void newDepartureWithMessage()
{
final Matcher m = assertFineDepartures("" //
+ "fpTime=\"21:10\" fpDate=\"01.10.10\" \n" //
+ "delay=\"cancel\" \n" //
+ "platform =\"1\" \n" //
+ "targetLoc=\"Magdeburg Hbf\" \n" //
+ "prod=\"RE 38090\" \n" //
+ "delayReason=\" Notarzteinsatz am Gleis\"\n");
assertNotNull(m.group(4)); // position
assertNotNull(m.group(9)); // message
}
private void assertFineConnectionDetails(final String s)
{
Matcher m = BahnProvider.P_CONNECTION_DETAILS_FINE.matcher(s);
@ -104,28 +45,4 @@ public class BahnProviderTest
// ParserUtils.printGroups(m);
}
private Matcher assertCoarseDepartures(final String s)
{
Matcher m = BahnProvider.P_DEPARTURES_COARSE.matcher(s);
assertTrue(m.find());
assertFineDepartures(m.group(1));
return m;
}
private Matcher assertFineDepartures(final String s)
{
Matcher m = BahnProvider.P_DEPARTURES_FINE.matcher(s);
assertTrue(m.matches());
// ParserUtils.printGroups(m);
assertNotNull(m.group(1)); // time
assertNotNull(m.group(2)); // date
assertNotNull(m.group(6)); // destination
assertNotNull(m.group(7)); // line
return m;
}
}

View file

@ -0,0 +1,67 @@
/*
* 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.live;
import java.util.List;
import org.junit.Test;
import de.schildbach.pte.DsbProvider;
import de.schildbach.pte.dto.Location;
import de.schildbach.pte.dto.NearbyStationsResult;
import de.schildbach.pte.dto.QueryDeparturesResult;
/**
* @author Andreas Schildbach
*/
public class DsbProviderLiveTest
{
private final DsbProvider provider = new DsbProvider();
@Test
public void autocomplete() throws Exception
{
final List<Location> autocompletes = provider.autocompleteStations("Airport");
list(autocompletes);
}
private void list(final List<Location> autocompletes)
{
System.out.print(autocompletes.size() + " ");
for (final Location autocomplete : autocompletes)
System.out.print(autocomplete.toDebugString() + " ");
System.out.println();
}
@Test
public void nearbyStation() throws Exception
{
final NearbyStationsResult result = provider.nearbyStations("8600858", 0, 0, 0, 0);
System.out.println(result.stations.size() + " " + result.stations);
}
@Test
public void queryDepartures() throws Exception
{
final QueryDeparturesResult result = provider.queryDepartures("8600858", 0, false);
System.out.println(result.stationDepartures);
}
}