public-transport-enabler/enabler/src/de/schildbach/pte/AbstractEfaProvider.java
2012-12-12 10:20:07 +01:00

2586 lines
85 KiB
Java

/*
* Copyright 2010-2012 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.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Currency;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
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.Fare;
import de.schildbach.pte.dto.Fare.Type;
import de.schildbach.pte.dto.Line;
import de.schildbach.pte.dto.LineDestination;
import de.schildbach.pte.dto.Location;
import de.schildbach.pte.dto.LocationType;
import de.schildbach.pte.dto.NearbyStationsResult;
import de.schildbach.pte.dto.Point;
import de.schildbach.pte.dto.QueryConnectionsContext;
import de.schildbach.pte.dto.QueryConnectionsResult;
import de.schildbach.pte.dto.QueryDeparturesResult;
import de.schildbach.pte.dto.ResultHeader;
import de.schildbach.pte.dto.StationDepartures;
import de.schildbach.pte.dto.Stop;
import de.schildbach.pte.exception.InvalidDataException;
import de.schildbach.pte.exception.ParserException;
import de.schildbach.pte.exception.ProtocolException;
import de.schildbach.pte.exception.SessionExpiredException;
import de.schildbach.pte.util.ParserUtils;
import de.schildbach.pte.util.XmlPullUtil;
/**
* @author Andreas Schildbach
*/
public abstract class AbstractEfaProvider extends AbstractNetworkProvider
{
protected static final String DEFAULT_DEPARTURE_MONITOR_ENDPOINT = "XSLT_DM_REQUEST";
protected static final String DEFAULT_TRIP_ENDPOINT = "XSLT_TRIP_REQUEST2";
protected static final String DEFAULT_STOPFINDER_ENDPOINT = "XML_STOPFINDER_REQUEST";
protected static final String DEFAULT_COORD_ENDPOINT = "XML_COORD_REQUEST";
protected static final String SERVER_PRODUCT = "efa";
private final String departureMonitorEndpoint;
private final String tripEndpoint;
private final String stopFinderEndpoint;
private final String coordEndpoint;
private final String additionalQueryParameter;
private final boolean canAcceptPoiID;
private final boolean needsSpEncId;
private boolean includeRegionId = true;
private Charset requestUrlEncoding = ISO_8859_1;
private String referer;
private boolean suppressPositions = false;
private boolean useRouteIndexAsConnectionId = true;
private final XmlPullParserFactory parserFactory;
private static class Context implements QueryConnectionsContext
{
private final String context;
private Context(final String context)
{
this.context = context;
}
public boolean canQueryLater()
{
return context != null;
}
public boolean canQueryEarlier()
{
return false; // TODO enable earlier querying
}
@Override
public String toString()
{
return getClass().getName() + "[" + context + "]";
}
}
public AbstractEfaProvider()
{
this(null, null);
}
public AbstractEfaProvider(final String apiBase, final String additionalQueryParameter)
{
this(apiBase, additionalQueryParameter, false);
}
public AbstractEfaProvider(final String apiBase, final String additionalQueryParameter, final boolean canAcceptPoiID)
{
this(apiBase, null, null, null, null, additionalQueryParameter, false, false);
}
public AbstractEfaProvider(final String apiBase, final String departureMonitorEndpoint, final String tripEndpoint,
final String stopFinderEndpoint, final String coordEndpoint, final String additionalQueryParameter, final boolean canAcceptPoiID,
final boolean needsSpEncId)
{
this(apiBase + (departureMonitorEndpoint != null ? departureMonitorEndpoint : DEFAULT_DEPARTURE_MONITOR_ENDPOINT), //
apiBase + (tripEndpoint != null ? tripEndpoint : DEFAULT_TRIP_ENDPOINT), //
apiBase + (stopFinderEndpoint != null ? stopFinderEndpoint : DEFAULT_STOPFINDER_ENDPOINT), //
apiBase + (coordEndpoint != null ? coordEndpoint : DEFAULT_COORD_ENDPOINT), //
additionalQueryParameter, canAcceptPoiID, needsSpEncId);
}
public AbstractEfaProvider(final String departureMonitorEndpoint, final String tripEndpoint, final String stopFinderEndpoint,
final String coordEndpoint, final String additionalQueryParameter, final boolean canAcceptPoiID, final boolean needsSpEncId)
{
try
{
parserFactory = XmlPullParserFactory.newInstance(System.getProperty(XmlPullParserFactory.PROPERTY_NAME), null);
}
catch (final XmlPullParserException x)
{
throw new RuntimeException(x);
}
this.departureMonitorEndpoint = departureMonitorEndpoint;
this.tripEndpoint = tripEndpoint;
this.stopFinderEndpoint = stopFinderEndpoint;
this.coordEndpoint = coordEndpoint;
this.additionalQueryParameter = additionalQueryParameter;
this.canAcceptPoiID = canAcceptPoiID;
this.needsSpEncId = needsSpEncId;
}
protected void setRequestUrlEncoding(final Charset requestUrlEncoding)
{
this.requestUrlEncoding = requestUrlEncoding;
}
protected void setReferer(final String referer)
{
this.referer = referer;
}
protected void setIncludeRegionId(final boolean includeRegionId)
{
this.includeRegionId = includeRegionId;
}
protected void setSuppressPositions(final boolean suppressPositions)
{
this.suppressPositions = suppressPositions;
}
public void setUseRouteIndexAsConnectionId(final boolean useRouteIndexAsConnectionId)
{
this.useRouteIndexAsConnectionId = useRouteIndexAsConnectionId;
}
protected TimeZone timeZone()
{
return TimeZone.getTimeZone("Europe/Berlin");
}
private final void appendCommonRequestParams(final StringBuilder uri, final String outputFormat)
{
uri.append("?outputFormat=").append(outputFormat);
uri.append("&coordOutputFormat=WGS84");
if (additionalQueryParameter != null)
uri.append('&').append(additionalQueryParameter);
}
protected List<Location> jsonStopfinderRequest(final Location constraint) throws IOException
{
final StringBuilder uri = new StringBuilder(stopFinderEndpoint);
appendCommonRequestParams(uri, "JSON");
uri.append("&locationServerActive=1");
if (includeRegionId)
uri.append("&regionID_sf=1"); // prefer own region
appendLocation(uri, constraint, "sf");
if (constraint.type == LocationType.ANY)
// 1=place 2=stop 4=street 8=address 16=crossing 32=poi 64=postcode
uri.append("&anyObjFilter_sf=").append(2 + 4 + 8 + 16 + 32 + 64);
uri.append("&anyMaxSizeHitList=500");
// System.out.println(uri.toString());
final CharSequence page = ParserUtils.scrape(uri.toString(), null, UTF_8, null);
try
{
final List<Location> results = new ArrayList<Location>();
final JSONObject head = new JSONObject(page.toString());
final JSONObject stopFinder = head.optJSONObject("stopFinder");
final JSONArray stops;
if (stopFinder == null)
{
stops = head.getJSONArray("stopFinder");
}
else
{
final JSONObject points = stopFinder.optJSONObject("points");
if (points != null)
{
final JSONObject stop = points.getJSONObject("point");
final Location location = parseJsonStop(stop);
results.add(location);
return results;
}
stops = stopFinder.getJSONArray("points");
}
final int nStops = stops.length();
for (int i = 0; i < nStops; i++)
{
final JSONObject stop = stops.optJSONObject(i);
final Location location = parseJsonStop(stop);
results.add(location);
}
return results;
}
catch (final JSONException x)
{
throw new RuntimeException("cannot parse: '" + page + "' on " + uri, x);
}
}
private Location parseJsonStop(final JSONObject stop) throws JSONException
{
String type = stop.getString("type");
if ("any".equals(type))
type = stop.getString("anyType");
final String name = normalizeLocationName(stop.getString("object"));
final JSONObject ref = stop.getJSONObject("ref");
String place = ref.getString("place");
if (place != null && place.length() == 0)
place = null;
final String coords = ref.optString("coords", null);
final int lat;
final int lon;
if (coords != null)
{
final String[] coordParts = coords.split(",");
lat = Math.round(Float.parseFloat(coordParts[1]));
lon = Math.round(Float.parseFloat(coordParts[0]));
}
else
{
lat = 0;
lon = 0;
}
if ("stop".equals(type))
return new Location(LocationType.STATION, stop.getInt("stateless"), lat, lon, place, name);
else if ("poi".equals(type))
return new Location(LocationType.POI, 0, lat, lon, place, name);
else if ("crossing".equals(type))
return new Location(LocationType.ADDRESS, 0, lat, lon, place, name);
else if ("street".equals(type) || "address".equals(type) || "singlehouse".equals(type))
return new Location(LocationType.ADDRESS, 0, lat, lon, place, normalizeLocationName(stop.getString("name")));
else
throw new JSONException("unknown type: " + type);
}
protected List<Location> xmlStopfinderRequest(final Location constraint) throws IOException
{
final StringBuilder uri = new StringBuilder(stopFinderEndpoint);
appendCommonRequestParams(uri, "XML");
uri.append("&locationServerActive=1");
if (includeRegionId)
uri.append("&regionID_sf=1"); // prefer own region
appendLocation(uri, constraint, "sf");
if (constraint.type == LocationType.ANY)
{
if (needsSpEncId)
uri.append("&SpEncId=0");
// 1=place 2=stop 4=street 8=address 16=crossing 32=poi 64=postcode
uri.append("&anyObjFilter_sf=").append(2 + 4 + 8 + 16 + 32 + 64);
uri.append("&reducedAnyPostcodeObjFilter_sf=64&reducedAnyTooManyObjFilter_sf=2");
uri.append("&useHouseNumberList=true");
}
// System.out.println(uri.toString());
InputStream is = null;
try
{
is = ParserUtils.scrapeInputStream(uri.toString());
final XmlPullParser pp = parserFactory.newPullParser();
pp.setInput(is, null);
enterItdRequest(pp);
final List<Location> results = new ArrayList<Location>();
XmlPullUtil.enter(pp, "itdStopFinderRequest");
XmlPullUtil.require(pp, "itdOdv");
if (!"sf".equals(pp.getAttributeValue(null, "usage")))
throw new IllegalStateException("cannot find <itdOdv usage=\"sf\" />");
XmlPullUtil.enter(pp, "itdOdv");
XmlPullUtil.require(pp, "itdOdvPlace");
XmlPullUtil.next(pp);
XmlPullUtil.require(pp, "itdOdvName");
final String nameState = pp.getAttributeValue(null, "state");
XmlPullUtil.enter(pp, "itdOdvName");
if (XmlPullUtil.test(pp, "itdMessage"))
XmlPullUtil.next(pp);
if ("identified".equals(nameState) || "list".equals(nameState))
{
while (XmlPullUtil.test(pp, "odvNameElem"))
results.add(processOdvNameElem(pp, null));
}
else if ("notidentified".equals(nameState))
{
// do nothing
}
else
{
throw new RuntimeException("unknown nameState '" + nameState + "' on " + uri);
}
XmlPullUtil.exit(pp, "itdOdvName");
XmlPullUtil.exit(pp, "itdOdv");
XmlPullUtil.exit(pp, "itdStopFinderRequest");
return results;
}
catch (final XmlPullParserException x)
{
throw new ParserException(x);
}
finally
{
if (is != null)
is.close();
}
}
protected NearbyStationsResult xmlCoordRequest(final int lat, final int lon, final int maxDistance, final int maxStations) throws IOException
{
final StringBuilder uri = new StringBuilder(coordEndpoint);
appendCommonRequestParams(uri, "XML");
uri.append("&coord=").append(String.format(Locale.ENGLISH, "%2.6f:%2.6f:WGS84", latLonToDouble(lon), latLonToDouble(lat)));
uri.append("&coordListOutputFormat=STRING");
uri.append("&max=").append(maxStations != 0 ? maxStations : 50);
uri.append("&inclFilter=1&radius_1=").append(maxDistance != 0 ? maxDistance : 1320);
uri.append("&type_1=STOP"); // ENTRANCE, BUS_POINT, POI_POINT
// System.out.println(uri);
InputStream is = null;
try
{
is = ParserUtils.scrapeInputStream(uri.toString());
final XmlPullParser pp = parserFactory.newPullParser();
pp.setInput(is, null);
final ResultHeader header = enterItdRequest(pp);
XmlPullUtil.enter(pp, "itdCoordInfoRequest");
XmlPullUtil.enter(pp, "itdCoordInfo");
XmlPullUtil.enter(pp, "coordInfoRequest");
XmlPullUtil.exit(pp, "coordInfoRequest");
final List<Location> stations = new ArrayList<Location>();
if (XmlPullUtil.test(pp, "coordInfoItemList"))
{
XmlPullUtil.enter(pp, "coordInfoItemList");
while (XmlPullUtil.test(pp, "coordInfoItem"))
{
if (!"STOP".equals(pp.getAttributeValue(null, "type")))
throw new RuntimeException("unknown type");
final int id = XmlPullUtil.intAttr(pp, "id");
final String name = normalizeLocationName(XmlPullUtil.attr(pp, "name"));
final String place = normalizeLocationName(XmlPullUtil.attr(pp, "locality"));
XmlPullUtil.enter(pp, "coordInfoItem");
// FIXME this is always only one coordinate
final Point coord = processItdPathCoordinates(pp).get(0);
XmlPullUtil.exit(pp, "coordInfoItem");
stations.add(new Location(LocationType.STATION, id, coord.lat, coord.lon, place, name));
}
XmlPullUtil.exit(pp, "coordInfoItemList");
}
return new NearbyStationsResult(header, stations);
}
catch (final XmlPullParserException x)
{
throw new ParserException(x);
}
finally
{
if (is != null)
is.close();
}
}
public List<Location> autocompleteStations(final CharSequence constraint) throws IOException
{
return jsonStopfinderRequest(new Location(LocationType.ANY, 0, null, constraint.toString()));
}
private String processItdOdvPlace(final XmlPullParser pp) throws XmlPullParserException, IOException
{
if (!XmlPullUtil.test(pp, "itdOdvPlace"))
throw new IllegalStateException("expecting <itdOdvPlace />");
final String placeState = XmlPullUtil.attr(pp, "state");
XmlPullUtil.enter(pp, "itdOdvPlace");
String place = null;
if ("identified".equals(placeState))
{
if (XmlPullUtil.test(pp, "odvPlaceElem"))
{
XmlPullUtil.enter(pp, "odvPlaceElem");
place = normalizeLocationName(pp.getText());
XmlPullUtil.exit(pp, "odvPlaceElem");
}
}
XmlPullUtil.exit(pp, "itdOdvPlace");
return place;
}
private Location processOdvNameElem(final XmlPullParser pp, final String defaultPlace) throws XmlPullParserException, IOException
{
if (!XmlPullUtil.test(pp, "odvNameElem"))
throw new IllegalStateException("expecting <odvNameElem />");
final String anyType = pp.getAttributeValue(null, "anyType");
final String idStr = pp.getAttributeValue(null, "id");
final String stopIdStr = pp.getAttributeValue(null, "stopID");
final String poiIdStr = pp.getAttributeValue(null, "poiID");
final String streetIdStr = pp.getAttributeValue(null, "streetID");
final String place = !"loc".equals(anyType) ? normalizeLocationName(pp.getAttributeValue(null, "locality")) : null;
final String name = normalizeLocationName(pp.getAttributeValue(null, "objectName"));
int lat = 0, lon = 0;
if ("WGS84".equals(pp.getAttributeValue(null, "mapName")))
{
lat = Math.round(XmlPullUtil.floatAttr(pp, "y"));
lon = Math.round(XmlPullUtil.floatAttr(pp, "x"));
}
LocationType type;
int id;
if ("stop".equals(anyType))
{
type = LocationType.STATION;
id = Integer.parseInt(idStr);
}
else if ("poi".equals(anyType) || "poiHierarchy".equals(anyType))
{
type = LocationType.POI;
id = Integer.parseInt(idStr);
}
else if ("loc".equals(anyType))
{
type = LocationType.ANY;
id = 0;
}
else if ("postcode".equals(anyType) || "street".equals(anyType) || "crossing".equals(anyType) || "address".equals(anyType)
|| "singlehouse".equals(anyType) || "buildingname".equals(anyType))
{
type = LocationType.ADDRESS;
id = 0;
}
else if (stopIdStr != null)
{
type = LocationType.STATION;
id = Integer.parseInt(stopIdStr);
}
else if (poiIdStr != null)
{
type = LocationType.POI;
id = Integer.parseInt(poiIdStr);
}
else if (stopIdStr == null && idStr == null && (lat != 0 || lon != 0))
{
type = LocationType.ADDRESS;
id = 0;
}
else if (streetIdStr != null)
{
type = LocationType.ADDRESS;
id = Integer.parseInt(streetIdStr);
}
else
{
throw new IllegalArgumentException("unknown type: " + anyType + " " + idStr + " " + stopIdStr);
}
XmlPullUtil.enter(pp, "odvNameElem");
final String longName = normalizeLocationName(pp.getText());
XmlPullUtil.exit(pp, "odvNameElem");
return new Location(type, id, lat, lon, place != null ? place : defaultPlace, name != null ? name : longName);
}
private Location processItdOdvAssignedStop(final XmlPullParser pp) throws XmlPullParserException, IOException
{
final int id = Integer.parseInt(pp.getAttributeValue(null, "stopID"));
int lat = 0, lon = 0;
if ("WGS84".equals(pp.getAttributeValue(null, "mapName")))
{
lat = Math.round(XmlPullUtil.floatAttr(pp, "y"));
lon = Math.round(XmlPullUtil.floatAttr(pp, "x"));
}
final String place = normalizeLocationName(XmlPullUtil.attr(pp, "place"));
XmlPullUtil.enter(pp, "itdOdvAssignedStop");
final String name = normalizeLocationName(pp.getText());
XmlPullUtil.exit(pp, "itdOdvAssignedStop");
return new Location(LocationType.STATION, id, lat, lon, place, name);
}
public NearbyStationsResult queryNearbyStations(final Location location, final int maxDistance, final int maxStations) throws IOException
{
if (location.hasLocation())
return xmlCoordRequest(location.lat, location.lon, maxDistance, maxStations);
if (location.type != LocationType.STATION)
throw new IllegalArgumentException("cannot handle: " + location.type);
if (!location.hasId())
throw new IllegalArgumentException("at least one of stationId or lat/lon must be given");
return nearbyStationsRequest(location.id, maxStations);
}
private NearbyStationsResult nearbyStationsRequest(final int stationId, final int maxStations) throws IOException
{
final StringBuilder uri = new StringBuilder(departureMonitorEndpoint);
appendCommonRequestParams(uri, "XML");
uri.append("&type_dm=stop&name_dm=").append(stationId);
uri.append("&itOptionsActive=1");
uri.append("&ptOptionsActive=1");
uri.append("&useProxFootSearch=1");
uri.append("&mergeDep=1");
uri.append("&useAllStops=1");
uri.append("&mode=direct");
// System.out.println(uri);
InputStream is = null;
try
{
is = ParserUtils.scrapeInputStream(uri.toString());
final XmlPullParser pp = parserFactory.newPullParser();
pp.setInput(is, null);
final ResultHeader header = enterItdRequest(pp);
if (!XmlPullUtil.jumpToStartTag(pp, null, "itdOdv") || !"dm".equals(pp.getAttributeValue(null, "usage")))
throw new IllegalStateException("cannot find <itdOdv usage=\"dm\" />");
XmlPullUtil.enter(pp, "itdOdv");
final String place = processItdOdvPlace(pp);
XmlPullUtil.require(pp, "itdOdvName");
final String nameState = pp.getAttributeValue(null, "state");
XmlPullUtil.enter(pp, "itdOdvName");
if ("identified".equals(nameState))
{
final Location ownLocation = processOdvNameElem(pp, place);
final Location ownStation = ownLocation.type == LocationType.STATION ? ownLocation : null;
final List<Location> stations = new ArrayList<Location>();
if (XmlPullUtil.jumpToStartTag(pp, null, "itdOdvAssignedStops"))
{
XmlPullUtil.enter(pp, "itdOdvAssignedStops");
while (XmlPullUtil.test(pp, "itdOdvAssignedStop"))
{
final String parsedMapName = pp.getAttributeValue(null, "mapName");
if (parsedMapName != null)
{
final int parsedLocationId = XmlPullUtil.intAttr(pp, "stopID");
// final String parsedLongName = normalizeLocationName(XmlPullUtil.attr(pp,
// "nameWithPlace"));
final String parsedPlace = normalizeLocationName(XmlPullUtil.attr(pp, "place"));
final int parsedLon = Math.round(XmlPullUtil.floatAttr(pp, "x"));
final int parsedLat = Math.round(XmlPullUtil.floatAttr(pp, "y"));
XmlPullUtil.enter(pp, "itdOdvAssignedStop");
final String parsedName = normalizeLocationName(pp.getText());
XmlPullUtil.exit(pp, "itdOdvAssignedStop");
if (!"WGS84".equals(parsedMapName))
throw new IllegalStateException("unknown mapName: " + parsedMapName);
final Location newStation = new Location(LocationType.STATION, parsedLocationId, parsedLat, parsedLon, parsedPlace,
parsedName);
if (!stations.contains(newStation))
stations.add(newStation);
}
else
{
if (!pp.isEmptyElementTag())
{
XmlPullUtil.enter(pp, "itdOdvAssignedStop");
XmlPullUtil.exit(pp, "itdOdvAssignedStop");
}
else
{
XmlPullUtil.next(pp);
}
}
}
}
if (ownStation != null && !stations.contains(ownStation))
stations.add(ownStation);
if (maxStations == 0 || maxStations >= stations.size())
return new NearbyStationsResult(header, stations);
else
return new NearbyStationsResult(header, stations.subList(0, maxStations));
}
else if ("list".equals(nameState))
{
final List<Location> stations = new ArrayList<Location>();
if (XmlPullUtil.test(pp, "itdMessage"))
XmlPullUtil.next(pp);
while (XmlPullUtil.test(pp, "odvNameElem"))
{
final Location newLocation = processOdvNameElem(pp, place);
if (newLocation.type == LocationType.STATION && !stations.contains(newLocation))
stations.add(newLocation);
}
return new NearbyStationsResult(header, stations);
}
else if ("notidentified".equals(nameState))
{
return new NearbyStationsResult(header, NearbyStationsResult.Status.INVALID_STATION);
}
else
{
throw new RuntimeException("unknown nameState '" + nameState + "' on " + uri);
}
// XmlPullUtil.exit(pp, "itdOdvName");
}
catch (final XmlPullParserException x)
{
throw new ParserException(x);
}
finally
{
if (is != null)
is.close();
}
}
private static final Pattern P_LINE_IRE = Pattern.compile("IRE\\d+");
private static final Pattern P_LINE_RE = Pattern.compile("RE\\d+");
private static final Pattern P_LINE_RB = Pattern.compile("RB\\d+");
private static final Pattern P_LINE_VB = Pattern.compile("VB\\d+");
private static final Pattern P_LINE_OE = Pattern.compile("OE\\d+");
private static final Pattern P_LINE_R = Pattern.compile("R\\d+(/R\\d+|\\(z\\))?");
private static final Pattern P_LINE_U = Pattern.compile("U\\d+");
private static final Pattern P_LINE_S = Pattern.compile("^(?:%)?(S\\d+)");
private static final Pattern P_LINE_NUMBER = Pattern.compile("\\d+");
private static final Pattern P_LINE_Y = Pattern.compile("\\d+Y");
private static final Pattern P_LINE_SEV = Pattern.compile("SEV.*");
protected String parseLine(final String mot, final String name, final String longName, final String noTrainName)
{
if (mot == null)
{
if (noTrainName != null)
{
final String str = name != null ? name : "";
if (noTrainName.equals("S-Bahn"))
return 'S' + str;
if (noTrainName.equals("U-Bahn"))
return 'U' + str;
if (noTrainName.equals("Straßenbahn"))
return 'T' + str;
if (noTrainName.equals("Badner Bahn"))
return 'T' + str;
if (noTrainName.equals("Stadtbus"))
return 'B' + str;
if (noTrainName.equals("Citybus"))
return 'B' + str;
if (noTrainName.equals("Regionalbus"))
return 'B' + str;
if (noTrainName.equals("ÖBB-Postbus"))
return 'B' + str;
if (noTrainName.equals("Autobus"))
return 'B' + str;
if (noTrainName.equals("Discobus"))
return 'B' + str;
if (noTrainName.equals("Nachtbus"))
return 'B' + str;
if (noTrainName.equals("Anrufsammeltaxi"))
return 'B' + str;
if (noTrainName.equals("Ersatzverkehr"))
return 'B' + str;
if (noTrainName.equals("Vienna Airport Lines"))
return 'B' + str;
}
throw new IllegalStateException("cannot normalize mot '" + mot + "' name '" + name + "' long '" + longName + "' noTrainName '"
+ noTrainName + "'");
}
final int t = Integer.parseInt(mot);
if (t == 0)
{
final String[] parts = longName.split(" ", 3);
final String type = parts[0];
final String num = parts.length >= 2 ? parts[1] : null;
final String str = type + (num != null ? num : "");
if (type.equals("EC")) // Eurocity
return 'I' + str;
if (type.equals("EN")) // Euronight
return 'I' + str;
if (type.equals("IC")) // Intercity
return 'I' + str;
if ("InterCity".equals(type))
return 'I' + str;
if (type.equals("ICE")) // Intercity Express
return 'I' + str;
if (type.equals("X")) // InterConnex
return 'I' + str;
if (type.equals("CNL")) // City Night Line
return 'I' + str;
if (type.equals("THA")) // Thalys
return 'I' + str;
if (type.equals("TGV")) // TGV
return 'I' + str;
if (type.equals("RJ")) // railjet
return 'I' + str;
if ("WB".equals(type)) // westbahn
return 'R' + str;
if (type.equals("OEC")) // ÖBB-EuroCity
return 'I' + str;
if (type.equals("OIC")) // ÖBB-InterCity
return 'I' + str;
if (type.equals("HT")) // First Hull Trains, GB
return 'I' + str;
if (type.equals("MT")) // Müller Touren, Schnee Express
return 'I' + str;
if (type.equals("HKX")) // Hamburg-Koeln-Express
return 'I' + str;
if (type.equals("DNZ")) // Nachtzug Basel-Moskau
return 'I' + str;
if ("Eurocity".equals(noTrainName)) // Liechtenstein
return 'I' + name;
if ("INT".equals(type)) // SVV
return 'I' + name;
if ("IXB".equals(type)) // ICE International
return 'I' + name;
if ("SC".equals(type)) // SuperCity, Tschechien
return 'I' + name;
if ("ECB".equals(type)) // EC, Verona-München
return 'I' + name;
if ("ES".equals(type)) // Eurostar Italia
return 'I' + name;
if (type.equals("IR")) // Interregio
return 'R' + str;
if ("InterRegio".equals(type))
return 'R' + str;
if (type.equals("IRE")) // Interregio-Express
return 'R' + str;
if (P_LINE_IRE.matcher(type).matches())
return 'R' + str;
if (type.equals("RE")) // Regional-Express
return 'R' + str;
if (type.equals("R-Bahn")) // Regional-Express, VRR
return 'R' + str;
if ("RB-Bahn".equals(type)) // Vogtland
return 'R' + str;
if (type.equals("REX")) // RegionalExpress, Österreich
return 'R' + str;
if ("EZ".equals(type)) // ÖBB ErlebnisBahn
return 'R' + str;
if (P_LINE_RE.matcher(type).matches())
return 'R' + str;
if (type.equals("RB")) // Regionalbahn
return 'R' + str;
if (P_LINE_RB.matcher(type).matches())
return 'R' + str;
if (type.equals("R")) // Regionalzug
return 'R' + str;
if (P_LINE_R.matcher(type).matches())
return 'R' + str;
if (type.equals("Bahn"))
return 'R' + str;
if (type.equals("Regionalbahn"))
return 'R' + str;
if (type.equals("D")) // Schnellzug
return 'R' + str;
if (type.equals("E")) // Eilzug
return 'R' + str;
if (type.equals("S")) // ~Innsbruck
return 'R' + str;
if (type.equals("WFB")) // Westfalenbahn
return 'R' + str;
if ("Westfalenbahn".equals(type)) // Westfalenbahn
return 'R' + name;
if (type.equals("NWB")) // NordWestBahn
return 'R' + str;
if (type.equals("NordWestBahn"))
return 'R' + str;
if (type.equals("ME")) // Metronom
return 'R' + str;
if (type.equals("ERB")) // eurobahn
return 'R' + str;
if (type.equals("CAN")) // cantus
return 'R' + str;
if (type.equals("HEX")) // Veolia Verkehr Sachsen-Anhalt
return 'R' + str;
if (type.equals("EB")) // Erfurter Bahn
return 'R' + str;
if (type.equals("EBx")) // Erfurter Bahn Express
return 'R' + str;
if (type.equals("MRB")) // Mittelrheinbahn
return 'R' + str;
if (type.equals("ABR")) // ABELLIO Rail NRW
return 'R' + str;
if (type.equals("NEB")) // Niederbarnimer Eisenbahn
return 'R' + str;
if (type.equals("OE")) // Ostdeutsche Eisenbahn
return 'R' + str;
if (P_LINE_OE.matcher(type).matches())
return 'R' + str;
if (type.equals("MR")) // Märkische Regiobahn
return 'R' + str;
if (type.equals("OLA")) // Ostseeland Verkehr
return 'R' + str;
if (type.equals("UBB")) // Usedomer Bäderbahn
return 'R' + str;
if (type.equals("EVB")) // Elbe-Weser
return 'R' + str;
if (type.equals("PEG")) // Prignitzer Eisenbahngesellschaft
return 'R' + str;
if (type.equals("RTB")) // Rurtalbahn
return 'R' + str;
if (type.equals("STB")) // Süd-Thüringen-Bahn
return 'R' + str;
if (type.equals("HTB")) // Hellertalbahn
return 'R' + str;
if (type.equals("VBG")) // Vogtlandbahn
return 'R' + str;
if (type.equals("VB")) // Vogtlandbahn
return 'R' + str;
if (P_LINE_VB.matcher(type).matches())
return 'R' + str;
if (type.equals("VX")) // Vogtland Express
return 'R' + str;
if (type.equals("CB")) // City-Bahn Chemnitz
return 'R' + str;
if (type.equals("VEC")) // VECTUS Verkehrsgesellschaft
return 'R' + str;
if (type.equals("HzL")) // Hohenzollerische Landesbahn
return 'R' + str;
if (type.equals("OSB")) // Ortenau-S-Bahn
return 'R' + str;
if (type.equals("SBB")) // SBB
return 'R' + str;
if (type.equals("MBB")) // Mecklenburgische Bäderbahn Molli
return 'R' + str;
if (type.equals("OS")) // Regionalbahn
return 'R' + str;
if (type.equals("SP"))
return 'R' + str;
if (type.equals("Dab")) // Daadetalbahn
return 'R' + str;
if (type.equals("FEG")) // Freiberger Eisenbahngesellschaft
return 'R' + str;
if (type.equals("ARR")) // ARRIVA
return 'R' + str;
if (type.equals("HSB")) // Harzer Schmalspurbahn
return 'R' + str;
if (type.equals("SBE")) // Sächsisch-Böhmische Eisenbahngesellschaft
return 'R' + str;
if (type.equals("ALX")) // Arriva-Länderbahn-Express
return 'R' + str;
if (type.equals("EX")) // ALX verwandelt sich
return 'R' + str;
if (type.equals("MEr")) // metronom regional
return 'R' + str;
if (type.equals("AKN")) // AKN Eisenbahn
return 'R' + str;
if (type.equals("ZUG")) // Regionalbahn
return 'R' + str;
if (type.equals("SOE")) // Sächsisch-Oberlausitzer Eisenbahngesellschaft
return 'R' + str;
if (type.equals("VIA")) // VIAS
return 'R' + str;
if (type.equals("BRB")) // Bayerische Regiobahn
return 'R' + str;
if (type.equals("BLB")) // Berchtesgadener Land Bahn
return 'R' + str;
if (type.equals("HLB")) // Hessische Landesbahn
return 'R' + str;
if (type.equals("NOB")) // NordOstseeBahn
return 'R' + str;
if (type.equals("WEG")) // Wieslauftalbahn
return 'R' + str;
if (type.equals("NBE")) // Nordbahn Eisenbahngesellschaft
return 'R' + str;
if (type.equals("VEN")) // Rhenus Veniro
return 'R' + str;
if (type.equals("DPN")) // Nahreisezug
return 'R' + str;
if (type.equals("SHB")) // Schleswig-Holstein-Bahn
return 'R' + str;
if (type.equals("RBG")) // Regental Bahnbetriebs GmbH
return 'R' + str;
if (type.equals("BOB")) // Bayerische Oberlandbahn
return 'R' + str;
if (type.equals("SWE")) // Südwestdeutsche Verkehrs AG
return 'R' + str;
if (type.equals("VE")) // Vetter
return 'R' + str;
if (type.equals("SDG")) // Sächsische Dampfeisenbahngesellschaft
return 'R' + str;
if (type.equals("PRE")) // Pressnitztalbahn
return 'R' + str;
if (type.equals("VEB")) // Vulkan-Eifel-Bahn
return 'R' + str;
if (type.equals("neg")) // Norddeutsche Eisenbahn Gesellschaft
return 'R' + str;
if (type.equals("AVG")) // Felsenland-Express
return 'R' + str;
if (type.equals("ABG")) // Anhaltische Bahngesellschaft
return 'R' + str;
if (type.equals("LGB")) // Lößnitzgrundbahn
return 'R' + str;
if (type.equals("LEO")) // Chiemgauer Lokalbahn
return 'R' + str;
if (type.equals("WTB")) // Weißeritztalbahn
return 'R' + str;
if (type.equals("P")) // Kasbachtalbahn, Wanderbahn im Regental, Rhön-Zügle
return 'R' + str;
if (type.equals("ÖBA")) // Eisenbahn-Betriebsgesellschaft Ochsenhausen
return 'R' + str;
if (type.equals("MBS")) // Montafonerbahn
return 'R' + str;
if (type.equals("EGP")) // EGP - die Städtebahn GmbH
return 'R' + str;
if (type.equals("SBS")) // Städtebahn Sachsen, EGP - die Städtebahn GmbH
return 'R' + str;
if (type.equals("SES")) // Städtebahn Sachsen Express, EGP - die Städtebahn GmbH
return 'R' + str;
if (type.equals("SB")) // Städtebahn Sachsen
return 'R' + str;
if (type.equals("agi")) // agilis
return 'R' + str;
if (type.equals("ag")) // agilis
return 'R' + str;
if (type.equals("as")) // agilis-Schnellzug
return 'R' + str;
if (type.equals("TLX")) // Trilex (Vogtlandbahn)
return 'R' + str;
if (type.equals("DBG")) // Döllnitzbahn
return 'R' + str;
if (type.equals("MSB")) // Mainschleifenbahn
return 'R' + str;
if (type.equals("BE")) // Grensland-Express, Niederlande
return 'R' + str;
if (type.equals("MEL")) // Museums-Eisenbahn Losheim
return 'R' + str;
if (type.equals("Abellio-Zug")) // Abellio
return 'R' + str;
if ("erx".equals(type)) // erixx
return 'R' + str;
if ("SWEG-Zug".equals(type)) // Südwestdeutschen Verkehrs-Aktiengesellschaft, evtl. S-Bahn?
return 'R' + str;
if (type.equals("KBS")) // Kursbuchstrecke
return 'R' + str;
if (type.equals("Zug"))
return 'R' + str;
if (type.equals("ÖBB"))
return 'R' + str;
if (type.equals("CAT")) // City Airport Train Wien
return 'R' + str;
if (type.equals("DZ")) // Dampfzug, STV
return 'R' + str;
if (type.equals("CD"))
return 'R' + str;
if (type.equals("PR"))
return 'R' + str;
if (type.equals("KD")) // Koleje Dolnośląskie (Niederschlesische Eisenbahn)
return 'R' + str;
if (type.equals("VIAMO"))
return 'R' + str;
if (type.equals("SE")) // Southeastern, GB
return 'R' + str;
if (type.equals("SW")) // South West Trains, GB
return 'R' + str;
if (type.equals("SN")) // Southern, GB
return 'R' + str;
if (type.equals("NT")) // Northern Rail, GB
return 'R' + str;
if (type.equals("CH")) // Chiltern Railways, GB
return 'R' + str;
if (type.equals("EA")) // National Express East Anglia, GB
return 'R' + str;
if (type.equals("FC")) // First Capital Connect, GB
return 'R' + str;
if (type.equals("GW")) // First Great Western, GB
return 'R' + str;
if (type.equals("XC")) // Cross Country, GB, evtl. auch highspeed?
return 'R' + str;
if (type.equals("HC")) // Heathrow Connect, GB
return 'R' + str;
if (type.equals("HX")) // Heathrow Express, GB
return 'R' + str;
if (type.equals("GX")) // Gatwick Express, GB
return 'R' + str;
if (type.equals("C2C")) // c2c, GB
return 'R' + str;
if (type.equals("LM")) // London Midland, GB
return 'R' + str;
if (type.equals("EM")) // East Midlands Trains, GB
return 'R' + str;
if (type.equals("VT")) // Virgin Trains, GB, evtl. auch highspeed?
return 'R' + str;
if (type.equals("SR")) // ScotRail, GB, evtl. auch long-distance?
return 'R' + str;
if (type.equals("AW")) // Arriva Trains Wales, GB
return 'R' + str;
if (type.equals("WS")) // Wrexham & Shropshire, GB
return 'R' + str;
if (type.equals("TP")) // First TransPennine Express, GB, evtl. auch long-distance?
return 'R' + str;
if (type.equals("GC")) // Grand Central, GB
return 'R' + str;
if (type.equals("IL")) // Island Line, GB
return 'R' + str;
if ("FCC".equals(type)) // First Capital Connect, GB
return 'R' + str;
if ("LE".equals(type)) // Greater Anglia, GB
return 'R' + str;
if (type.equals("BR")) // ??, GB
return 'R' + str;
if (type.equals("OO")) // ??, GB
return 'R' + str;
if (type.equals("XX")) // ??, GB
return 'R' + str;
if (type.equals("XZ")) // ??, GB
return 'R' + str;
if (type.equals("DB-Zug")) // VRR
return 'R' + name;
if (type.equals("DB"))
return 'R' + name;
if (type.equals("Regionalexpress")) // VRR
return 'R' + name;
if ("CAPITOL".equals(name)) // San Francisco
return 'R' + name;
if ("Train".equals(noTrainName) || "Train".equals(type)) // San Francisco
return "R" + name;
if ("Regional Train :".equals(longName))
return "R";
if ("Regional Train".equals(noTrainName)) // Melbourne
return "R" + name;
if ("Regional".equals(type)) // Melbourne
return "R" + name;
if (type.equals("ATB")) // Autoschleuse Tauernbahn
return 'R' + name;
if ("Chiemsee-Bahn".equals(type))
return 'R' + name;
if ("Regionalzug".equals(noTrainName)) // Liechtenstein
return 'R' + name;
if ("RegionalExpress".equals(noTrainName)) // Liechtenstein
return 'R' + name;
if ("Ostdeutsche".equals(type)) // Bayern
return 'R' + type;
if ("Südwestdeutsche".equals(type)) // Bayern
return 'R' + type;
if ("Mitteldeutsche".equals(type)) // Bayern
return 'R' + type;
if ("Norddeutsche".equals(type)) // Bayern
return 'R' + type;
if ("Hellertalbahn".equals(type)) // Bayern
return 'R' + type;
if ("Veolia".equals(type)) // Bayern
return 'R' + type;
if ("vectus".equals(type)) // Bayern
return 'R' + type;
if ("Hessische".equals(type)) // Bayern
return 'R' + type;
if ("Niederbarnimer".equals(type)) // Bayern
return 'R' + type;
if ("Rurtalbahn".equals(type)) // Bayern
return 'R' + type;
if ("Rhenus".equals(type)) // Bayern
return 'R' + type;
if ("Mittelrheinbahn".equals(type)) // Bayern
return 'R' + type;
if ("Hohenzollerische".equals(type)) // Bayern
return 'R' + type;
if ("Städtebahn".equals(type)) // Bayern
return 'R' + type;
if ("Ortenau-S-Bahn".equals(type)) // Bayern
return 'R' + type;
if ("Daadetalbahn".equals(type)) // Bayern
return 'R' + type;
if ("Mainschleifenbahn".equals(type)) // Bayern
return 'R' + type;
if ("Nordbahn".equals(type)) // Bayern
return 'R' + type;
if ("Harzer".equals(type)) // Bayern
return 'R' + type;
if ("cantus".equals(type)) // Bayern
return 'R' + type;
if ("DPF".equals(type)) // Bayern, Vogtland-Express
return 'R' + type;
if ("Freiberger".equals(type)) // Bayern
return 'R' + type;
if ("metronom".equals(type)) // Bayern
return 'R' + type;
if ("Prignitzer".equals(type)) // Bayern
return 'R' + type;
if ("Sächsisch-Oberlausitzer".equals(type)) // Bayern
return 'R' + type;
if ("Ostseeland".equals(type)) // Bayern
return 'R' + type;
if ("NordOstseeBahn".equals(type)) // Bayern
return 'R' + type;
if ("ELBE-WESER".equals(type)) // Bayern
return 'R' + type;
if ("TRILEX".equals(type)) // Bayern
return 'R' + type;
if ("Schleswig-Holstein-Bahn".equals(type)) // Bayern
return 'R' + type;
if ("Vetter".equals(type)) // Bayern
return 'R' + type;
if ("Dessau-Wörlitzer".equals(type)) // Bayern
return 'R' + type;
if ("NATURPARK-EXPRESS".equals(type)) // Bayern
return 'R' + type;
if ("Usedomer".equals(type)) // Bayern
return 'R' + type;
if ("Märkische".equals(type)) // Bayern
return 'R' + type;
if ("Vulkan-Eifel-Bahn".equals(type)) // Bayern
return 'R' + type;
if ("Kandertalbahn".equals(type)) // Bayern
return 'R' + type;
if ("RAD-WANDER-SHUTTLE".equals(type)) // Bayern, Hohenzollerische Landesbahn
return 'R' + type;
if ("RADEXPRESS".equals(type)) // Bayern, RADEXPRESS EYACHTÄLER
return 'R' + type;
if ("Dampfzug".equals(type)) // Bayern
return 'R' + type;
if ("Wutachtalbahn".equals(type)) // Bayern
return 'R' + type;
if ("Grensland-Express".equals(type)) // Bayern
return 'R' + type;
if ("Mecklenburgische".equals(type)) // Bayern
return 'R' + type;
if ("Bentheimer".equals(type)) // Bayern
return 'R' + type;
if ("Pressnitztalbahn".equals(type)) // Bayern
return 'R' + type;
if ("Regental".equals(type)) // Bayern
return 'R' + type;
if ("Döllnitzbahn".equals(type)) // Bayern
return 'R' + type;
if ("Schneeberg".equals(type)) // VOR
return 'R' + type;
if ("FLZ".equals(type)) // Stainzer Flascherlzug
return 'R' + type;
if ("FTB".equals(type)) // Feistritztalbahn
return 'R' + type;
if ("DWE".equals(type)) // Dessau-Wörlitzer Eisenbahn
return 'R' + type;
if ("KTB".equals(type)) // Kandertalbahn
return 'R' + type;
if ("UEF".equals(type)) // Ulmer Eisenbahnfreunde
return 'R' + type;
if ("CBC".equals(type)) // City-Bahn Chemnitz
return 'R' + type;
if ("Regionalzug".equals(type))
return 'R' + type;
if ("RR".equals(type)) // RR 371 Horehronec / RR 404 Vltava / RR 922 Josef Skupa
return 'R' + type;
if ("BSB".equals(type)) // Breisgau-S-Bahn
return 'S' + str;
if ("BSB-Zug".equals(type)) // Breisgau-S-Bahn
return 'S' + str;
if ("Breisgau-S-Bahn".equals(type)) // Bayern
return 'S' + type;
if ("RER".equals(type)) // Réseau Express Régional, Frankreich
return 'S' + str;
if ("LO".equals(type)) // London Overground, GB
return 'S' + str;
if ("A".equals(name) || "B".equals(name) || "C".equals(name)) // SES
return 'S' + str;
final Matcher m = P_LINE_S.matcher(name);
if (m.find())
return 'S' + m.group(1);
if (P_LINE_U.matcher(type).matches())
return 'U' + str;
if ("Underground".equals(type)) // London Underground, GB
return 'U' + str;
if ("Millbrae / Richmond".equals(name)) // San Francisco, BART
return 'U' + name;
if ("Richmond / Millbrae".equals(name)) // San Francisco, BART
return 'U' + name;
if ("Fremont / RIchmond".equals(name)) // San Francisco, BART
return 'U' + name;
if ("Richmond / Fremont".equals(name)) // San Francisco, BART
return 'U' + name;
if ("Pittsburg Bay Point / SFO".equals(name)) // San Francisco, BART
return 'U' + name;
if ("SFO / Pittsburg Bay Point".equals(name)) // San Francisco, BART
return 'U' + name;
if ("Dublin Pleasanton / Daly City".equals(name)) // San Francisco, BART
return 'U' + name;
if ("Daly City / Dublin Pleasanton".equals(name)) // San Francisco, BART
return 'U' + name;
if ("Fremont / Daly City".equals(name)) // San Francisco, BART
return 'U' + name;
if ("Daly City / Fremont".equals(name)) // San Francisco, BART
return 'U' + name;
if (type.equals("RT")) // RegioTram
return 'T' + str;
if (type.equals("STR")) // Nordhausen
return 'T' + str;
if ("California Cable Car".equals(name)) // San Francisco
return 'T' + name;
if ("Muni".equals(type)) // San Francisco
return 'T' + name;
if ("Cable".equals(type)) // San Francisco
return 'T' + name;
if ("Muni Rail".equals(noTrainName)) // San Francisco
return 'T' + name;
if ("Cable Car".equals(noTrainName)) // San Francisco
return 'T' + name;
if ("BUS".equals(type) || "Bus".equals(type))
return 'B' + str;
if (P_LINE_SEV.matcher(type).matches())
return 'B' + str;
if ("Bex".equals(type)) // Bayern Express
return 'B' + str;
if ("Ersatzverkehr".equals(type)) // Rhein-Ruhr
return 'B' + str;
if ("AST".equals(type)) // Anruf-Sammel-Taxi
return 'P' + str;
if (type.length() == 0)
return "?";
if (P_LINE_NUMBER.matcher(type).matches())
return "?";
if (P_LINE_Y.matcher(name).matches())
return "?" + name;
if ("Sonderverkehr Red Bull".equals(name))
return "?" + name;
throw new IllegalStateException("cannot normalize mot '" + mot + "' name '" + name + "' long '" + longName + "' noTrainName '"
+ noTrainName + "' type '" + type + "' str '" + str + "'");
}
if (t == 1)
{
final Matcher m = P_LINE_S.matcher(name);
if (m.find())
return 'S' + m.group(1);
else
return 'S' + name;
}
if (t == 2)
return 'U' + name;
if (t == 3 || t == 4)
return 'T' + name;
if (t == 5 || t == 6 || t == 7 || t == 10)
{
if (name.equals("Schienenersatzverkehr"))
return "BSEV";
else
return 'B' + name;
}
if (t == 8)
return 'C' + name;
if (t == 9)
return 'F' + name;
if (t == 11 || t == -1)
return '?' + name;
throw new IllegalStateException("cannot normalize mot '" + mot + "' name '" + name + "' long '" + longName + "' noTrainName '" + noTrainName
+ "'");
}
public QueryDeparturesResult queryDepartures(final int stationId, final int maxDepartures, final boolean equivs) throws IOException
{
final StringBuilder uri = new StringBuilder(departureMonitorEndpoint);
appendCommonRequestParams(uri, "XML");
uri.append("&type_dm=stop");
uri.append("&name_dm=").append(stationId);
uri.append("&useRealtime=1");
uri.append("&mode=direct");
uri.append("&ptOptionsActive=1");
uri.append("&deleteAssignedStops_dm=").append(equivs ? '0' : '1');
uri.append("&mergeDep=1"); // merge departures
if (maxDepartures > 0)
uri.append("&limit=").append(maxDepartures);
// System.out.println(uri);
InputStream is = null;
try
{
is = ParserUtils.scrapeInputStream(uri.toString(), null, null, referer, null, 3);
final XmlPullParser pp = parserFactory.newPullParser();
pp.setInput(is, null);
final ResultHeader header = enterItdRequest(pp);
XmlPullUtil.enter(pp, "itdDepartureMonitorRequest");
if (!XmlPullUtil.test(pp, "itdOdv") || !"dm".equals(XmlPullUtil.attr(pp, "usage")))
throw new IllegalStateException("cannot find <itdOdv usage=\"dm\" />");
XmlPullUtil.enter(pp, "itdOdv");
final String place = processItdOdvPlace(pp);
XmlPullUtil.require(pp, "itdOdvName");
final String nameState = pp.getAttributeValue(null, "state");
XmlPullUtil.enter(pp, "itdOdvName");
if ("identified".equals(nameState))
{
final QueryDeparturesResult result = new QueryDeparturesResult(header);
final Location location = processOdvNameElem(pp, place);
result.stationDepartures.add(new StationDepartures(location, new LinkedList<Departure>(), new LinkedList<LineDestination>()));
XmlPullUtil.exit(pp, "itdOdvName");
if (XmlPullUtil.test(pp, "itdOdvAssignedStops"))
{
XmlPullUtil.enter(pp, "itdOdvAssignedStops");
while (XmlPullUtil.test(pp, "itdOdvAssignedStop"))
{
final Location assignedLocation = processItdOdvAssignedStop(pp);
if (findStationDepartures(result.stationDepartures, assignedLocation.id) == null)
result.stationDepartures.add(new StationDepartures(assignedLocation, new LinkedList<Departure>(),
new LinkedList<LineDestination>()));
}
XmlPullUtil.exit(pp, "itdOdvAssignedStops");
}
XmlPullUtil.exit(pp, "itdOdv");
if (XmlPullUtil.test(pp, "itdDateTime"))
XmlPullUtil.next(pp);
if (XmlPullUtil.test(pp, "itdDMDateTime"))
XmlPullUtil.next(pp);
if (XmlPullUtil.test(pp, "itdDateRange"))
XmlPullUtil.next(pp);
if (XmlPullUtil.test(pp, "itdTripOptions"))
XmlPullUtil.next(pp);
if (XmlPullUtil.test(pp, "itdMessage"))
XmlPullUtil.next(pp);
final Calendar plannedDepartureTime = new GregorianCalendar(timeZone());
final Calendar predictedDepartureTime = new GregorianCalendar(timeZone());
XmlPullUtil.require(pp, "itdServingLines");
if (!pp.isEmptyElementTag())
{
XmlPullUtil.enter(pp, "itdServingLines");
while (XmlPullUtil.test(pp, "itdServingLine"))
{
final String assignedStopIdStr = pp.getAttributeValue(null, "assignedStopID");
final int assignedStopId = assignedStopIdStr != null ? Integer.parseInt(assignedStopIdStr) : 0;
final String destinationName = normalizeLocationName(pp.getAttributeValue(null, "direction"));
final String destinationIdStr = pp.getAttributeValue(null, "destID");
final int destinationId = (destinationIdStr != null && destinationIdStr.length() > 0) ? Integer.parseInt(destinationIdStr)
: 0;
final Location destination = new Location(destinationId > 0 ? LocationType.STATION : LocationType.ANY,
destinationId > 0 ? destinationId : 0, null, destinationName);
final LineDestination line = new LineDestination(processItdServingLine(pp), destination);
StationDepartures assignedStationDepartures;
if (assignedStopId == 0)
assignedStationDepartures = result.stationDepartures.get(0);
else
assignedStationDepartures = findStationDepartures(result.stationDepartures, assignedStopId);
if (assignedStationDepartures == null)
assignedStationDepartures = new StationDepartures(new Location(LocationType.STATION, assignedStopId),
new LinkedList<Departure>(), new LinkedList<LineDestination>());
if (!assignedStationDepartures.lines.contains(line))
assignedStationDepartures.lines.add(line);
}
XmlPullUtil.exit(pp, "itdServingLines");
}
else
{
XmlPullUtil.next(pp);
}
XmlPullUtil.require(pp, "itdDepartureList");
if (!pp.isEmptyElementTag())
{
XmlPullUtil.enter(pp, "itdDepartureList");
while (XmlPullUtil.test(pp, "itdDeparture"))
{
final int assignedStopId = XmlPullUtil.intAttr(pp, "stopID");
StationDepartures assignedStationDepartures = findStationDepartures(result.stationDepartures, assignedStopId);
if (assignedStationDepartures == null)
{
final String mapName = pp.getAttributeValue(null, "mapName");
if (mapName == null || !"WGS84".equals(mapName))
throw new IllegalStateException("unknown mapName: " + mapName);
final int lon = Math.round(XmlPullUtil.floatAttr(pp, "x"));
final int lat = Math.round(XmlPullUtil.floatAttr(pp, "y"));
// final String name = normalizeLocationName(XmlPullUtil.attr(pp, "nameWO"));
assignedStationDepartures = new StationDepartures(new Location(LocationType.STATION, assignedStopId, lat, lon),
new LinkedList<Departure>(), new LinkedList<LineDestination>());
}
final String position;
if (!suppressPositions)
position = normalizePlatform(pp.getAttributeValue(null, "platform"), pp.getAttributeValue(null, "platformName"));
else
position = null;
XmlPullUtil.enter(pp, "itdDeparture");
XmlPullUtil.require(pp, "itdDateTime");
plannedDepartureTime.clear();
processItdDateTime(pp, plannedDepartureTime);
predictedDepartureTime.clear();
if (XmlPullUtil.test(pp, "itdRTDateTime"))
processItdDateTime(pp, predictedDepartureTime);
if (XmlPullUtil.test(pp, "itdFrequencyInfo"))
XmlPullUtil.next(pp);
XmlPullUtil.require(pp, "itdServingLine");
final boolean isRealtime = pp.getAttributeValue(null, "realtime").equals("1");
final String destinationName = normalizeLocationName(pp.getAttributeValue(null, "direction"));
final String destinationIdStr = pp.getAttributeValue(null, "destID");
final int destinationId = destinationIdStr != null ? Integer.parseInt(destinationIdStr) : 0;
final Location destination = new Location(destinationId > 0 ? LocationType.STATION : LocationType.ANY,
destinationId > 0 ? destinationId : 0, null, destinationName);
final Line line = processItdServingLine(pp);
if (isRealtime && !predictedDepartureTime.isSet(Calendar.HOUR_OF_DAY))
predictedDepartureTime.setTimeInMillis(plannedDepartureTime.getTimeInMillis());
XmlPullUtil.exit(pp, "itdDeparture");
final Departure departure = new Departure(plannedDepartureTime.getTime(),
predictedDepartureTime.isSet(Calendar.HOUR_OF_DAY) ? predictedDepartureTime.getTime() : null, line, position,
destination, null, null);
assignedStationDepartures.departures.add(departure);
}
XmlPullUtil.exit(pp, "itdDepartureList");
}
else
{
XmlPullUtil.next(pp);
}
return result;
}
else if ("notidentified".equals(nameState) || "list".equals(nameState))
{
return new QueryDeparturesResult(header, QueryDeparturesResult.Status.INVALID_STATION);
}
else
{
throw new RuntimeException("unknown nameState '" + nameState + "' on " + uri);
}
}
catch (final XmlPullParserException x)
{
throw new ParserException(x);
}
finally
{
if (is != null)
is.close();
}
}
private StationDepartures findStationDepartures(final List<StationDepartures> stationDepartures, final int id)
{
for (final StationDepartures stationDeparture : stationDepartures)
if (stationDeparture.location.id == id)
return stationDeparture;
return null;
}
private Location processItdPointAttributes(final XmlPullParser pp)
{
final int id = Integer.parseInt(pp.getAttributeValue(null, "stopID"));
final String place = normalizeLocationName(pp.getAttributeValue(null, "locality"));
String name = normalizeLocationName(pp.getAttributeValue(null, "nameWO"));
if (name == null)
name = normalizeLocationName(pp.getAttributeValue(null, "name"));
final int lat, lon;
if ("WGS84".equals(pp.getAttributeValue(null, "mapName")))
{
lat = Math.round(XmlPullUtil.floatAttr(pp, "y"));
lon = Math.round(XmlPullUtil.floatAttr(pp, "x"));
}
else
{
lat = 0;
lon = 0;
}
return new Location(LocationType.STATION, id, lat, lon, place, name);
}
private boolean processItdDateTime(final XmlPullParser pp, final Calendar calendar) throws XmlPullParserException, IOException
{
XmlPullUtil.enter(pp);
calendar.clear();
final boolean success = processItdDate(pp, calendar);
if (success)
processItdTime(pp, calendar);
XmlPullUtil.exit(pp);
return success;
}
private boolean processItdDate(final XmlPullParser pp, final Calendar calendar) throws XmlPullParserException, IOException
{
XmlPullUtil.require(pp, "itdDate");
final int year = Integer.parseInt(pp.getAttributeValue(null, "year"));
final int month = Integer.parseInt(pp.getAttributeValue(null, "month")) - 1;
final int day = Integer.parseInt(pp.getAttributeValue(null, "day"));
XmlPullUtil.next(pp);
if (year == 0)
return false;
if (year < 1900 || year > 2100)
throw new InvalidDataException("invalid year: " + year);
if (month < 0 || month > 11)
throw new InvalidDataException("invalid month: " + month);
if (day < 1 || day > 31)
throw new InvalidDataException("invalid day: " + day);
calendar.set(Calendar.YEAR, year);
calendar.set(Calendar.MONTH, month);
calendar.set(Calendar.DAY_OF_MONTH, day);
return true;
}
private void processItdTime(final XmlPullParser pp, final Calendar calendar) throws XmlPullParserException, IOException
{
XmlPullUtil.require(pp, "itdTime");
calendar.set(Calendar.HOUR_OF_DAY, Integer.parseInt(pp.getAttributeValue(null, "hour")));
calendar.set(Calendar.MINUTE, Integer.parseInt(pp.getAttributeValue(null, "minute")));
XmlPullUtil.next(pp);
}
private Line processItdServingLine(final XmlPullParser pp) throws XmlPullParserException, IOException
{
XmlPullUtil.require(pp, "itdServingLine");
final String motType = pp.getAttributeValue(null, "motType");
final String number = pp.getAttributeValue(null, "number");
final String id = pp.getAttributeValue(null, "stateless");
XmlPullUtil.enter(pp, "itdServingLine");
String noTrainName = null;
String message = null;
if (XmlPullUtil.test(pp, "itdNoTrain"))
{
noTrainName = pp.getAttributeValue(null, "name");
if (!pp.isEmptyElementTag())
{
XmlPullUtil.enter(pp, "itdNoTrain");
final String text = pp.getText();
if (noTrainName.toLowerCase().contains("ruf") && text.toLowerCase().contains("ruf"))
message = text;
XmlPullUtil.exit(pp, "itdNoTrain");
}
}
XmlPullUtil.exit(pp, "itdServingLine");
final String label = parseLine(motType, number, number, noTrainName);
return new Line(id, label, lineStyle(label), message);
}
private static final Pattern P_STATION_NAME_WHITESPACE = Pattern.compile("\\s+");
protected String normalizeLocationName(final String name)
{
if (name == null || name.length() == 0)
return null;
return P_STATION_NAME_WHITESPACE.matcher(name).replaceAll(" ");
}
protected static double latLonToDouble(final int value)
{
return (double) value / 1000000;
}
protected String xsltTripRequest2Uri(final Location from, final Location via, final Location to, final Date date, final boolean dep,
final int numConnections, final String products, final WalkSpeed walkSpeed, final Accessibility accessibility, final Set<Option> options)
{
final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMMdd", Locale.US);
final DateFormat TIME_FORMAT = new SimpleDateFormat("HHmm", Locale.US);
final StringBuilder uri = new StringBuilder(tripEndpoint);
appendCommonRequestParams(uri, "XML");
uri.append("&sessionID=0");
uri.append("&requestID=0");
uri.append("&language=de");
appendCommonXsltTripRequest2Params(uri);
appendLocation(uri, from, "origin");
appendLocation(uri, to, "destination");
if (via != null)
appendLocation(uri, via, "via");
uri.append("&itdDate=").append(ParserUtils.urlEncode(DATE_FORMAT.format(date)));
uri.append("&itdTime=").append(ParserUtils.urlEncode(TIME_FORMAT.format(date)));
uri.append("&itdTripDateTimeDepArr=").append(dep ? "dep" : "arr");
uri.append("&calcNumberOfTrips=").append(numConnections);
uri.append("&ptOptionsActive=1"); // enable public transport options
uri.append("&itOptionsActive=1"); // enable individual transport options
uri.append("&changeSpeed=").append(WALKSPEED_MAP.get(walkSpeed));
if (accessibility == Accessibility.BARRIER_FREE)
uri.append("&imparedOptionsActive=1").append("&wheelchair=on").append("&noSolidStairs=on");
else if (accessibility == Accessibility.LIMITED)
uri.append("&imparedOptionsActive=1").append("&wheelchair=on").append("&lowPlatformVhcl=on").append("&noSolidStairs=on");
if (products != null)
{
uri.append("&includedMeans=checkbox");
boolean hasI = false;
for (final char p : products.toCharArray())
{
if (p == 'I' || p == 'R')
{
uri.append("&inclMOT_0=on");
if (p == 'I')
hasI = true;
}
if (p == 'S')
uri.append("&inclMOT_1=on");
if (p == 'U')
uri.append("&inclMOT_2=on");
if (p == 'T')
uri.append("&inclMOT_3=on&inclMOT_4=on");
if (p == 'B')
uri.append("&inclMOT_5=on&inclMOT_6=on&inclMOT_7=on");
if (p == 'P')
uri.append("&inclMOT_10=on");
if (p == 'F')
uri.append("&inclMOT_9=on");
if (p == 'C')
uri.append("&inclMOT_8=on");
}
uri.append("&inclMOT_11=on"); // TODO always show 'others', for now
// workaround for highspeed trains: fails when you want highspeed, but not regional
if (!hasI)
uri.append("&lineRestriction=403"); // means: all but ice
}
if (options != null && options.contains(Option.BIKE))
uri.append("&bikeTakeAlong=1");
uri.append("&locationServerActive=1");
uri.append("&useRealtime=1");
uri.append("&useProxFootSearch=1"); // walk if it makes journeys quicker
uri.append("&nextDepsPerLeg=1"); // next departure in case previous was missed
return uri.toString();
}
private String commandLink(final String sessionId, final String requestId)
{
final StringBuilder uri = new StringBuilder(tripEndpoint);
uri.append("?sessionID=").append(sessionId);
uri.append("&requestID=").append(requestId);
appendCommonXsltTripRequest2Params(uri);
return uri.toString();
}
private static final void appendCommonXsltTripRequest2Params(final StringBuilder uri)
{
uri.append("&coordListOutputFormat=STRING");
uri.append("&calcNumberOfTrips=4");
}
public QueryConnectionsResult queryConnections(final Location from, final Location via, final Location to, final Date date, final boolean dep,
final int numConnections, final String products, final WalkSpeed walkSpeed, final Accessibility accessibility, final Set<Option> options)
throws IOException
{
final String uri = xsltTripRequest2Uri(from, via, to, date, dep, numConnections, products, walkSpeed, accessibility, options);
InputStream is = null;
try
{
is = ParserUtils.scrapeInputStream(uri, null, null, referer, "NSC_", 3);
return queryConnections(uri, is);
}
catch (final XmlPullParserException x)
{
throw new ParserException(x);
}
catch (final RuntimeException x)
{
throw new RuntimeException("uncategorized problem while processing " + uri, x);
}
finally
{
if (is != null)
is.close();
}
}
private static final Pattern P_SESSION_EXPIRED = Pattern.compile("Your session has expired");
public QueryConnectionsResult queryMoreConnections(final QueryConnectionsContext contextObj, final boolean later, final int numConnections)
throws IOException
{
final Context context = (Context) contextObj;
final String commandUri = context.context;
final StringBuilder uri = new StringBuilder(commandUri);
uri.append("&command=").append(later ? "tripNext" : "tripPrev");
InputStream is = null;
try
{
is = new BufferedInputStream(ParserUtils.scrapeInputStream(uri.toString(), null, null, referer, "NSC_", 3));
is.mark(512);
return queryConnections(uri.toString(), is);
}
catch (final XmlPullParserException x)
{
throw new ParserException(x);
}
catch (final ProtocolException x) // must be html content
{
is.reset();
final BufferedReader reader = new BufferedReader(new InputStreamReader(is));
String line;
while ((line = reader.readLine()) != null)
if (P_SESSION_EXPIRED.matcher(line).find())
throw new SessionExpiredException();
throw x;
}
catch (final RuntimeException x)
{
throw new RuntimeException("uncategorized problem while processing " + uri, x);
}
finally
{
if (is != null)
is.close();
}
}
private QueryConnectionsResult queryConnections(final String uri, final InputStream is) throws XmlPullParserException, IOException
{
// System.out.println(uri);
final XmlPullParser pp = parserFactory.newPullParser();
pp.setInput(is, null);
final ResultHeader header = enterItdRequest(pp);
final Object context = header.context;
if (XmlPullUtil.test(pp, "itdLayoutParams"))
XmlPullUtil.next(pp);
XmlPullUtil.require(pp, "itdTripRequest");
final String requestId = XmlPullUtil.attr(pp, "requestID");
XmlPullUtil.enter(pp, "itdTripRequest");
if (XmlPullUtil.test(pp, "itdMessage"))
{
final int code = XmlPullUtil.intAttr(pp, "code");
if (code == -4000) // no connection
return new QueryConnectionsResult(header, QueryConnectionsResult.Status.NO_CONNECTIONS);
XmlPullUtil.next(pp);
}
if (XmlPullUtil.test(pp, "itdPrintConfiguration"))
XmlPullUtil.next(pp);
if (XmlPullUtil.test(pp, "itdAddress"))
XmlPullUtil.next(pp);
// parse odv name elements
List<Location> ambiguousFrom = null, ambiguousTo = null, ambiguousVia = null;
Location from = null, via = null, to = null;
while (XmlPullUtil.test(pp, "itdOdv"))
{
final String usage = XmlPullUtil.attr(pp, "usage");
XmlPullUtil.enter(pp, "itdOdv");
final String place = processItdOdvPlace(pp);
if (!XmlPullUtil.test(pp, "itdOdvName"))
throw new IllegalStateException("cannot find <itdOdvName /> inside " + usage);
final String nameState = XmlPullUtil.attr(pp, "state");
XmlPullUtil.enter(pp, "itdOdvName");
if (XmlPullUtil.test(pp, "itdMessage"))
XmlPullUtil.next(pp);
if ("list".equals(nameState))
{
if ("origin".equals(usage))
{
ambiguousFrom = new ArrayList<Location>();
while (XmlPullUtil.test(pp, "odvNameElem"))
ambiguousFrom.add(processOdvNameElem(pp, place));
}
else if ("via".equals(usage))
{
ambiguousVia = new ArrayList<Location>();
while (XmlPullUtil.test(pp, "odvNameElem"))
ambiguousVia.add(processOdvNameElem(pp, place));
}
else if ("destination".equals(usage))
{
ambiguousTo = new ArrayList<Location>();
while (XmlPullUtil.test(pp, "odvNameElem"))
ambiguousTo.add(processOdvNameElem(pp, place));
}
else
{
throw new IllegalStateException("unknown usage: " + usage);
}
}
else if ("identified".equals(nameState))
{
if (!XmlPullUtil.test(pp, "odvNameElem"))
throw new IllegalStateException("cannot find <odvNameElem /> inside " + usage);
if ("origin".equals(usage))
from = processOdvNameElem(pp, place);
else if ("via".equals(usage))
via = processOdvNameElem(pp, place);
else if ("destination".equals(usage))
to = processOdvNameElem(pp, place);
else
throw new IllegalStateException("unknown usage: " + usage);
}
else if ("notidentified".equals(nameState))
{
if ("origin".equals(usage))
return new QueryConnectionsResult(header, QueryConnectionsResult.Status.UNKNOWN_FROM);
else if ("via".equals(usage))
// return new QueryConnectionsResult(header, QueryConnectionsResult.Status.UNKNOWN_VIA);
throw new UnsupportedOperationException();
else if ("destination".equals(usage))
return new QueryConnectionsResult(header, QueryConnectionsResult.Status.UNKNOWN_TO);
else
throw new IllegalStateException("unknown usage: " + usage);
}
XmlPullUtil.exit(pp, "itdOdvName");
XmlPullUtil.exit(pp, "itdOdv");
}
if (ambiguousFrom != null || ambiguousTo != null || ambiguousVia != null)
return new QueryConnectionsResult(header, ambiguousFrom, ambiguousVia, ambiguousTo);
XmlPullUtil.enter(pp, "itdTripDateTime");
XmlPullUtil.enter(pp, "itdDateTime");
XmlPullUtil.require(pp, "itdDate");
if (!pp.isEmptyElementTag())
{
XmlPullUtil.enter(pp, "itdDate");
if (XmlPullUtil.test(pp, "itdMessage"))
{
final String message = XmlPullUtil.nextText(pp, null, "itdMessage");
if ("invalid date".equals(message))
return new QueryConnectionsResult(header, QueryConnectionsResult.Status.INVALID_DATE);
else
throw new IllegalStateException("unknown message: " + message);
}
XmlPullUtil.exit(pp, "itdDate");
}
else
{
XmlPullUtil.next(pp);
}
XmlPullUtil.exit(pp, "itdDateTime");
final Calendar time = new GregorianCalendar(timeZone());
final List<Connection> connections = new ArrayList<Connection>();
if (XmlPullUtil.jumpToStartTag(pp, null, "itdRouteList"))
{
XmlPullUtil.enter(pp, "itdRouteList");
while (XmlPullUtil.test(pp, "itdRoute"))
{
final String id = useRouteIndexAsConnectionId ? pp.getAttributeValue(null, "routeIndex") + "-"
+ pp.getAttributeValue(null, "routeTripIndex") : null;
final int numChanges = XmlPullUtil.intAttr(pp, "changes");
XmlPullUtil.enter(pp, "itdRoute");
while (XmlPullUtil.test(pp, "itdDateTime"))
XmlPullUtil.next(pp);
if (XmlPullUtil.test(pp, "itdMapItemList"))
XmlPullUtil.next(pp);
XmlPullUtil.enter(pp, "itdPartialRouteList");
final List<Connection.Part> parts = new LinkedList<Connection.Part>();
Location firstDeparture = null;
Location lastArrival = null;
while (XmlPullUtil.test(pp, "itdPartialRoute"))
{
final int distance = XmlPullUtil.optIntAttr(pp, "distance", 0);
XmlPullUtil.enter(pp, "itdPartialRoute");
XmlPullUtil.test(pp, "itdPoint");
if (!"departure".equals(pp.getAttributeValue(null, "usage")))
throw new IllegalStateException();
final Location departure = processItdPointAttributes(pp);
if (firstDeparture == null)
firstDeparture = departure;
final String departurePosition;
if (!suppressPositions)
departurePosition = normalizePlatform(pp.getAttributeValue(null, "platform"), pp.getAttributeValue(null, "platformName"));
else
departurePosition = null;
XmlPullUtil.enter(pp, "itdPoint");
if (XmlPullUtil.test(pp, "itdMapItemList"))
XmlPullUtil.next(pp);
XmlPullUtil.require(pp, "itdDateTime");
processItdDateTime(pp, time);
final Date departureTime = time.getTime();
final Date departureTargetTime;
if (XmlPullUtil.test(pp, "itdDateTimeTarget"))
{
processItdDateTime(pp, time);
departureTargetTime = time.getTime();
}
else
{
departureTargetTime = null;
}
XmlPullUtil.exit(pp, "itdPoint");
XmlPullUtil.test(pp, "itdPoint");
if (!"arrival".equals(pp.getAttributeValue(null, "usage")))
throw new IllegalStateException();
final Location arrival = processItdPointAttributes(pp);
lastArrival = arrival;
final String arrivalPosition;
if (!suppressPositions)
arrivalPosition = normalizePlatform(pp.getAttributeValue(null, "platform"), pp.getAttributeValue(null, "platformName"));
else
arrivalPosition = null;
XmlPullUtil.enter(pp, "itdPoint");
if (XmlPullUtil.test(pp, "itdMapItemList"))
XmlPullUtil.next(pp);
XmlPullUtil.require(pp, "itdDateTime");
processItdDateTime(pp, time);
final Date arrivalTime = time.getTime();
final Date arrivalTargetTime;
if (XmlPullUtil.test(pp, "itdDateTimeTarget"))
{
processItdDateTime(pp, time);
arrivalTargetTime = time.getTime();
}
else
{
arrivalTargetTime = null;
}
XmlPullUtil.exit(pp, "itdPoint");
XmlPullUtil.test(pp, "itdMeansOfTransport");
final String productName = pp.getAttributeValue(null, "productName");
if ("Fussweg".equals(productName) || "Taxi".equals(productName))
{
final int min = (int) (arrivalTime.getTime() - departureTime.getTime()) / 1000 / 60;
final boolean transfer = "Taxi".equals(productName);
XmlPullUtil.enter(pp, "itdMeansOfTransport");
XmlPullUtil.exit(pp, "itdMeansOfTransport");
if (XmlPullUtil.test(pp, "itdStopSeq"))
XmlPullUtil.next(pp);
if (XmlPullUtil.test(pp, "itdFootPathInfo"))
XmlPullUtil.next(pp);
List<Point> path = null;
if (XmlPullUtil.test(pp, "itdPathCoordinates"))
path = processItdPathCoordinates(pp);
if (parts.size() > 0 && parts.get(parts.size() - 1) instanceof Connection.Footway)
{
final Connection.Footway lastFootway = (Connection.Footway) parts.remove(parts.size() - 1);
if (path != null && lastFootway.path != null)
path.addAll(0, lastFootway.path);
parts.add(new Connection.Footway(lastFootway.min + min, distance, lastFootway.transfer || transfer,
lastFootway.departure, arrival, path));
}
else
{
parts.add(new Connection.Footway(min, distance, transfer, departure, arrival, path));
}
}
else if ("gesicherter Anschluss".equals(productName) || "nicht umsteigen".equals(productName)) // type97
{
// ignore
XmlPullUtil.enter(pp, "itdMeansOfTransport");
XmlPullUtil.exit(pp, "itdMeansOfTransport");
}
else
{
final String destinationName = normalizeLocationName(pp.getAttributeValue(null, "destination"));
final String destinationIdStr = pp.getAttributeValue(null, "destID");
final int destinationId = (destinationIdStr != null && destinationIdStr.length() > 0) ? Integer.parseInt(destinationIdStr)
: 0;
final Location destination = new Location(destinationId > 0 ? LocationType.STATION : LocationType.ANY,
destinationId > 0 ? destinationId : 0, null, destinationName);
final String lineLabel;
if ("AST".equals(pp.getAttributeValue(null, "symbol")))
lineLabel = "BAST";
else
lineLabel = parseLine(pp.getAttributeValue(null, "motType"), pp.getAttributeValue(null, "shortname"),
pp.getAttributeValue(null, "name"), null);
XmlPullUtil.enter(pp, "itdMeansOfTransport");
XmlPullUtil.require(pp, "motDivaParams");
final String lineId = XmlPullUtil.attr(pp, "network") + ':' + XmlPullUtil.attr(pp, "line") + ':'
+ XmlPullUtil.attr(pp, "supplement") + ':' + XmlPullUtil.attr(pp, "direction") + ':'
+ XmlPullUtil.attr(pp, "project");
XmlPullUtil.exit(pp, "itdMeansOfTransport");
if (XmlPullUtil.test(pp, "itdRBLControlled"))
XmlPullUtil.next(pp);
boolean lowFloorVehicle = false;
String message = null;
if (XmlPullUtil.test(pp, "itdInfoTextList"))
{
if (!pp.isEmptyElementTag())
{
XmlPullUtil.enter(pp, "itdInfoTextList");
while (XmlPullUtil.test(pp, "infoTextListElem"))
{
XmlPullUtil.enter(pp, "infoTextListElem");
final String text = pp.getText();
if ("Niederflurwagen soweit verfügbar".equals(text)) // KVV
lowFloorVehicle = true;
else if (text.toLowerCase().contains("ruf")) // RufBus, RufTaxi
message = text;
XmlPullUtil.exit(pp, "infoTextListElem");
}
XmlPullUtil.exit(pp, "itdInfoTextList");
}
else
{
XmlPullUtil.next(pp);
}
}
if (XmlPullUtil.test(pp, "itdFootPathInfo"))
XmlPullUtil.next(pp);
if (XmlPullUtil.test(pp, "infoLink"))
XmlPullUtil.next(pp);
List<Stop> intermediateStops = null;
if (XmlPullUtil.test(pp, "itdStopSeq"))
{
XmlPullUtil.enter(pp, "itdStopSeq");
intermediateStops = new LinkedList<Stop>();
while (XmlPullUtil.test(pp, "itdPoint"))
{
final Location stopLocation = processItdPointAttributes(pp);
final String stopPosition;
if (!suppressPositions)
stopPosition = normalizePlatform(pp.getAttributeValue(null, "platform"),
pp.getAttributeValue(null, "platformName"));
else
stopPosition = null;
XmlPullUtil.enter(pp, "itdPoint");
XmlPullUtil.require(pp, "itdDateTime");
final Date stopArrivalTime;
if (processItdDateTime(pp, time))
stopArrivalTime = time.getTime();
else
stopArrivalTime = null;
final Date stopDepartureTime;
if (XmlPullUtil.test(pp, "itdDateTime") && processItdDateTime(pp, time))
stopDepartureTime = time.getTime();
else
stopDepartureTime = null;
intermediateStops.add(new Stop(stopLocation, stopArrivalTime, stopPosition, stopDepartureTime, stopPosition));
// TODO planned/predicted?
XmlPullUtil.exit(pp, "itdPoint");
}
XmlPullUtil.exit(pp, "itdStopSeq");
// remove first and last, because they are not intermediate
final int size = intermediateStops.size();
if (size >= 2)
{
if (intermediateStops.get(size - 1).location.id != arrival.id)
throw new IllegalStateException();
intermediateStops.remove(size - 1);
if (intermediateStops.get(0).location.id != departure.id)
throw new IllegalStateException();
intermediateStops.remove(0);
}
}
List<Point> path = null;
if (XmlPullUtil.test(pp, "itdPathCoordinates"))
path = processItdPathCoordinates(pp);
boolean wheelChairAccess = false;
if (XmlPullUtil.test(pp, "genAttrList"))
{
XmlPullUtil.enter(pp, "genAttrList");
while (XmlPullUtil.test(pp, "genAttrElem"))
{
XmlPullUtil.enter(pp, "genAttrElem");
XmlPullUtil.enter(pp, "name");
final String name = pp.getText();
XmlPullUtil.exit(pp, "name");
XmlPullUtil.enter(pp, "value");
final String value = pp.getText();
XmlPullUtil.exit(pp, "value");
XmlPullUtil.exit(pp, "genAttrElem");
// System.out.println("genAttrElem: name='" + name + "' value='" + value + "'");
if ("PlanWheelChairAccess".equals(name) && "1".equals(value))
wheelChairAccess = true;
}
XmlPullUtil.exit(pp, "genAttrList");
}
if (XmlPullUtil.test(pp, "nextDeps"))
{
XmlPullUtil.enter(pp, "nextDeps");
while (XmlPullUtil.test(pp, "itdDateTime"))
{
processItdDateTime(pp, time);
final Date nextDepartureTime = time.getTime();
}
XmlPullUtil.exit(pp, "nextDeps");
}
final Set<Line.Attr> lineAttrs = new HashSet<Line.Attr>();
if (wheelChairAccess || lowFloorVehicle)
lineAttrs.add(Line.Attr.WHEEL_CHAIR_ACCESS);
final Line line = new Line(lineId, lineLabel, lineStyle(lineLabel), lineAttrs);
parts.add(new Connection.Trip(line, destination, departureTargetTime != null ? departureTargetTime : departureTime,
departureTargetTime, departurePosition, null, departure, arrivalTargetTime != null ? arrivalTargetTime : arrivalTime,
arrivalTargetTime, arrivalPosition, null, arrival, intermediateStops, path, message));
}
XmlPullUtil.exit(pp, "itdPartialRoute");
}
XmlPullUtil.exit(pp, "itdPartialRouteList");
final List<Fare> fares = new ArrayList<Fare>(2);
if (XmlPullUtil.test(pp, "itdFare"))
{
if (!pp.isEmptyElementTag())
{
XmlPullUtil.enter(pp, "itdFare");
if (XmlPullUtil.test(pp, "itdSingleTicket"))
{
final String net = XmlPullUtil.attr(pp, "net");
final Currency currency = parseCurrency(XmlPullUtil.attr(pp, "currency"));
final String fareAdult = XmlPullUtil.attr(pp, "fareAdult");
final String fareChild = XmlPullUtil.attr(pp, "fareChild");
final String unitName = XmlPullUtil.attr(pp, "unitName");
final String unitsAdult = XmlPullUtil.attr(pp, "unitsAdult");
final String unitsChild = XmlPullUtil.attr(pp, "unitsChild");
final String levelAdult = pp.getAttributeValue(null, "levelAdult");
final boolean hasLevelAdult = levelAdult != null && levelAdult.length() > 0;
final String levelChild = pp.getAttributeValue(null, "levelChild");
final boolean hasLevelChild = levelChild != null && levelChild.length() > 0;
if (fareAdult != null && fareAdult.length() > 0)
fares.add(new Fare(net, Type.ADULT, currency, Float.parseFloat(fareAdult), hasLevelAdult ? null : unitName,
hasLevelAdult ? levelAdult : unitsAdult));
if (fareChild != null && fareChild.length() > 0)
fares.add(new Fare(net, Type.CHILD, currency, Float.parseFloat(fareChild), hasLevelChild ? null : unitName,
hasLevelChild ? levelChild : unitsChild));
if (!pp.isEmptyElementTag())
{
XmlPullUtil.enter(pp, "itdSingleTicket");
if (XmlPullUtil.test(pp, "itdGenericTicketList"))
{
XmlPullUtil.enter(pp, "itdGenericTicketList");
while (XmlPullUtil.test(pp, "itdGenericTicketGroup"))
{
final Fare fare = processItdGenericTicketGroup(pp, net, currency);
if (fare != null)
fares.add(fare);
}
XmlPullUtil.exit(pp, "itdGenericTicketList");
}
XmlPullUtil.exit(pp, "itdSingleTicket");
}
else
{
XmlPullUtil.next(pp);
}
}
XmlPullUtil.exit(pp, "itdFare");
}
else
{
XmlPullUtil.next(pp);
}
}
connections.add(new Connection(id, firstDeparture, lastArrival, parts, fares.isEmpty() ? null : fares, null, numChanges));
XmlPullUtil.exit(pp, "itdRoute");
}
XmlPullUtil.exit(pp, "itdRouteList");
return new QueryConnectionsResult(header, uri, from, via, to, new Context(commandLink((String) context, requestId)), connections);
}
else
{
return new QueryConnectionsResult(header, QueryConnectionsResult.Status.NO_CONNECTIONS);
}
}
private List<Point> processItdPathCoordinates(final XmlPullParser pp) throws XmlPullParserException, IOException
{
final List<Point> path = new LinkedList<Point>();
XmlPullUtil.enter(pp, "itdPathCoordinates");
XmlPullUtil.enter(pp, "coordEllipsoid");
final String ellipsoid = pp.getText();
XmlPullUtil.exit(pp, "coordEllipsoid");
if (!"WGS84".equals(ellipsoid))
throw new IllegalStateException("unknown ellipsoid: " + ellipsoid);
XmlPullUtil.enter(pp, "coordType");
final String type = pp.getText();
XmlPullUtil.exit(pp, "coordType");
if (!"GEO_DECIMAL".equals(type))
throw new IllegalStateException("unknown type: " + type);
XmlPullUtil.enter(pp, "itdCoordinateString");
for (final String coordStr : pp.getText().split(" +"))
{
final String[] coordsStr = coordStr.split(",");
path.add(new Point(Math.round(Float.parseFloat(coordsStr[1])), Math.round(Float.parseFloat(coordsStr[0]))));
}
XmlPullUtil.exit(pp, "itdCoordinateString");
XmlPullUtil.exit(pp, "itdPathCoordinates");
return path;
}
private Fare processItdGenericTicketGroup(final XmlPullParser pp, final String net, final Currency currency) throws XmlPullParserException,
IOException
{
XmlPullUtil.enter(pp, "itdGenericTicketGroup");
Type type = null;
float fare = 0;
while (XmlPullUtil.test(pp, "itdGenericTicket"))
{
XmlPullUtil.enter(pp, "itdGenericTicket");
XmlPullUtil.enter(pp, "ticket");
final String key = pp.getText().trim();
XmlPullUtil.exit(pp, "ticket");
String value = null;
XmlPullUtil.require(pp, "value");
if (!pp.isEmptyElementTag())
{
XmlPullUtil.enter(pp, "value");
value = pp.getText();
if (value != null)
value = value.trim();
XmlPullUtil.exit(pp, "value");
}
else
{
XmlPullUtil.next(pp);
}
if (key.equals("FOR_RIDER"))
{
final String typeStr = value.split(" ")[0].toUpperCase();
if (typeStr.equals("REGULAR"))
type = Type.ADULT;
else
type = Type.valueOf(typeStr);
}
else if (key.equals("PRICE"))
{
fare = Float.parseFloat(value) * (currency.getCurrencyCode().equals("USD") ? 0.01f : 1);
}
XmlPullUtil.exit(pp, "itdGenericTicket");
}
XmlPullUtil.exit(pp, "itdGenericTicketGroup");
if (type != null)
return new Fare(net, type, currency, fare, null, null);
else
return null;
}
private Currency parseCurrency(final String currencyStr)
{
if (currencyStr.equals("US$"))
return Currency.getInstance("USD");
if (currencyStr.equals("Dirham"))
return Currency.getInstance("AED");
return Currency.getInstance(currencyStr);
}
private static final Pattern P_PLATFORM = Pattern.compile("#?(\\d+)", Pattern.CASE_INSENSITIVE);
private static final Pattern P_PLATFORM_NAME = Pattern.compile("(?:Gleis|Gl\\.|Bstg\\.)?\\s*" + //
"(\\d+)\\s*" + //
"(?:([A-Z])\\s*(?:-\\s*([A-Z]))?)?", Pattern.CASE_INSENSITIVE);
private static final String normalizePlatform(final String platform, final String platformName)
{
if (platform != null && platform.length() > 0)
{
final Matcher m = P_PLATFORM.matcher(platform);
if (m.matches())
{
return Integer.toString(Integer.parseInt(m.group(1)));
}
else
{
return platform;
}
}
if (platformName != null && platformName.length() > 0)
{
final Matcher m = P_PLATFORM_NAME.matcher(platformName);
if (m.matches())
{
final String simple = Integer.toString(Integer.parseInt(m.group(1)));
if (m.group(2) != null && m.group(3) != null)
return simple + m.group(2) + "-" + m.group(3);
else if (m.group(2) != null)
return simple + m.group(2);
else
return simple;
}
else
{
return platformName;
}
}
return null;
}
private void appendLocation(final StringBuilder uri, final Location location, final String paramSuffix)
{
if (canAcceptPoiID && location.type == LocationType.POI && location.hasId())
{
uri.append("&type_").append(paramSuffix).append("=poiID");
uri.append("&name_").append(paramSuffix).append("=").append(location.id);
}
else if ((location.type == LocationType.POI || location.type == LocationType.ADDRESS) && location.hasLocation())
{
uri.append("&type_").append(paramSuffix).append("=coord");
uri.append("&name_").append(paramSuffix).append("=")
.append(String.format(Locale.ENGLISH, "%.6f:%.6f", location.lon / 1E6, location.lat / 1E6)).append(":WGS84");
}
else
{
uri.append("&type_").append(paramSuffix).append("=").append(locationTypeValue(location));
uri.append("&name_").append(paramSuffix).append("=").append(ParserUtils.urlEncode(locationValue(location), requestUrlEncoding));
}
}
protected static final String locationTypeValue(final Location location)
{
final LocationType type = location.type;
if (type == LocationType.STATION)
return "stop";
if (type == LocationType.ADDRESS)
return "any"; // strange, matches with anyObjFilter
if (type == LocationType.POI)
return "poi";
if (type == LocationType.ANY)
return "any";
throw new IllegalArgumentException(type.toString());
}
protected static final String locationValue(final Location location)
{
if ((location.type == LocationType.STATION || location.type == LocationType.POI) && location.hasId())
return Integer.toString(location.id);
else
return location.name;
}
protected static final Map<WalkSpeed, String> WALKSPEED_MAP = new HashMap<WalkSpeed, String>();
static
{
WALKSPEED_MAP.put(WalkSpeed.SLOW, "slow");
WALKSPEED_MAP.put(WalkSpeed.NORMAL, "normal");
WALKSPEED_MAP.put(WalkSpeed.FAST, "fast");
}
private ResultHeader enterItdRequest(final XmlPullParser pp) throws XmlPullParserException, IOException
{
if (pp.getEventType() != XmlPullParser.START_DOCUMENT)
throw new IllegalStateException("start of document expected");
try
{
pp.next();
}
catch (final XmlPullParserException x)
{
if (x.getMessage().startsWith("Expected a quoted string"))
throw new ProtocolException("html");
}
if (pp.getEventType() == XmlPullParser.DOCDECL)
pp.next();
if (XmlPullUtil.test(pp, "html"))
throw new ProtocolException("html");
XmlPullUtil.require(pp, "itdRequest");
final String serverVersion = XmlPullUtil.attr(pp, "version");
final String now = XmlPullUtil.attr(pp, "now");
final String sessionId = XmlPullUtil.attr(pp, "sessionID");
final Calendar serverTime = new GregorianCalendar(timeZone());
ParserUtils.parseIsoDate(serverTime, now.substring(0, 10));
ParserUtils.parseEuropeanTime(serverTime, now.substring(11));
final ResultHeader header = new ResultHeader(SERVER_PRODUCT, serverVersion, serverTime.getTimeInMillis(), sessionId);
XmlPullUtil.enter(pp, "itdRequest");
if (XmlPullUtil.test(pp, "clientHeaderLines"))
XmlPullUtil.next(pp);
if (XmlPullUtil.test(pp, "itdVersionInfo"))
XmlPullUtil.next(pp);
if (XmlPullUtil.test(pp, "itdInfoLinkList"))
XmlPullUtil.next(pp);
if (XmlPullUtil.test(pp, "serverMetaInfo"))
XmlPullUtil.next(pp);
return header;
}
}