public-transport-enabler/src/de/schildbach/pte/AbstractEfaProvider.java
2011-04-29 11:23:27 +00:00

1953 lines
64 KiB
Java

/*
* 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.io.InputStream;
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.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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.GetConnectionDetailsResult;
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.QueryConnectionsResult;
import de.schildbach.pte.dto.QueryConnectionsResult.Status;
import de.schildbach.pte.dto.QueryDeparturesResult;
import de.schildbach.pte.dto.StationDepartures;
import de.schildbach.pte.dto.Stop;
import de.schildbach.pte.exception.ParserException;
import de.schildbach.pte.util.Color;
import de.schildbach.pte.util.ParserUtils;
import de.schildbach.pte.util.XmlPullUtil;
/**
* @author Andreas Schildbach
*/
public abstract class AbstractEfaProvider implements NetworkProvider
{
private final String apiBase;
private final String additionalQueryParameter;
private final XmlPullParserFactory parserFactory;
public AbstractEfaProvider()
{
this(null, null);
}
public AbstractEfaProvider(final String apiBase, final String additionalQueryParameter)
{
try
{
parserFactory = XmlPullParserFactory.newInstance(System.getProperty(XmlPullParserFactory.PROPERTY_NAME), null);
}
catch (final XmlPullParserException x)
{
throw new RuntimeException(x);
}
this.apiBase = apiBase;
this.additionalQueryParameter = additionalQueryParameter;
}
protected TimeZone timeZone()
{
return TimeZone.getTimeZone("Europe/Berlin");
}
private String wrapUri(final String uri)
{
if (additionalQueryParameter == null)
return uri;
else
return uri + "&" + additionalQueryParameter;
}
protected List<Location> xmlStopfinderRequest(final Location constraint) throws IOException
{
final StringBuilder uri = new StringBuilder(apiBase);
uri.append("XML_STOPFINDER_REQUEST?coordOutputFormat=WGS84&locationServerActive=1");
appendLocation(uri, constraint, "sf");
if (constraint.type == LocationType.ANY)
{
uri.append("&SpEncId=0");
uri.append("&anyObjFilter_sf=126"); // 1=place 2=stop 4=street 8=address 16=crossing 32=poi 64=postcode
uri.append("&reducedAnyPostcodeObjFilter_sf=64&reducedAnyTooManyObjFilter_sf=2");
uri.append("&useHouseNumberList=true&regionID_sf=1");
}
if (additionalQueryParameter != null)
uri.append('&').append(additionalQueryParameter);
InputStream is = null;
try
{
is = ParserUtils.scrapeInputStream(uri.toString());
final XmlPullParser pp = parserFactory.newPullParser();
pp.setInput(is, null);
assertItdRequest(pp);
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);
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 List<Location> xmlCoordRequest(final int lat, final int lon, final int maxDistance, final int maxStations) throws IOException
{
final StringBuilder uri = new StringBuilder(apiBase);
uri.append("XML_COORD_REQUEST?coord=");
uri.append(String.format("%2.6f:%2.6f:WGS84", latLonToDouble(lon), latLonToDouble(lat)));
uri.append("&coordOutputFormat=WGS84&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");
if (additionalQueryParameter != null)
uri.append('&').append(additionalQueryParameter);
InputStream is = null;
try
{
is = ParserUtils.scrapeInputStream(uri.toString());
final XmlPullParser pp = parserFactory.newPullParser();
pp.setInput(is, null);
assertItdRequest(pp);
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);
XmlPullUtil.enter(pp, "itdCoordInfoRequest");
XmlPullUtil.enter(pp, "itdCoordInfo");
XmlPullUtil.enter(pp, "coordInfoRequest");
XmlPullUtil.exit(pp, "coordInfoRequest");
final List<Location> results = 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");
results.add(new Location(LocationType.STATION, id, coord.lat, coord.lon, place, name));
}
XmlPullUtil.exit(pp, "coordInfoItemList");
}
return results;
}
catch (final XmlPullParserException x)
{
throw new ParserException(x);
}
finally
{
if (is != null)
is.close();
}
}
private String autocompleteUri(final CharSequence constraint)
{
final String AUTOCOMPLETE_URI = apiBase + "XSLT_TRIP_REQUEST2?outputFormat=XML&coordOutputFormat=WGS84&type_origin=any&name_origin=%s";
return String.format(AUTOCOMPLETE_URI, ParserUtils.urlEncode(constraint.toString(), "ISO-8859-1"));
}
public List<Location> autocompleteStations(final CharSequence constraint) throws IOException
{
final String uri = wrapUri(autocompleteUri(constraint));
InputStream is = null;
try
{
is = ParserUtils.scrapeInputStream(uri);
final XmlPullParser pp = parserFactory.newPullParser();
pp.setInput(is, null);
assertItdRequest(pp);
final List<Location> results = new ArrayList<Location>();
// parse odv name elements
if (!XmlPullUtil.jumpToStartTag(pp, null, "itdOdv") || !"origin".equals(pp.getAttributeValue(null, "usage")))
throw new IllegalStateException("cannot find <itdOdv usage=\"origin\" />");
XmlPullUtil.enter(pp, "itdOdv");
final String place = processItdOdvPlace(pp);
if (!XmlPullUtil.test(pp, "itdOdvName"))
throw new IllegalStateException("cannot find <itdOdvName />");
final String nameState = XmlPullUtil.attr(pp, "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, place));
// parse assigned stops
if (XmlPullUtil.jumpToStartTag(pp, null, "itdOdvAssignedStops"))
{
XmlPullUtil.enter(pp, "itdOdvAssignedStops");
while (XmlPullUtil.test(pp, "itdOdvAssignedStop"))
{
final Location location = processItdOdvAssignedStop(pp);
if (!results.contains(location))
results.add(location);
}
}
return results;
}
catch (final XmlPullParserException x)
{
throw new ParserException(x);
}
finally
{
if (is != null)
is.close();
}
}
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 = Integer.parseInt(pp.getAttributeValue(null, "y"));
lon = Integer.parseInt(pp.getAttributeValue(null, "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 (stopIdStr == null && idStr == null && (lat != 0 || lon != 0))
{
type = LocationType.ADDRESS;
id = 0;
}
else if (poiIdStr != null)
{
type = LocationType.POI;
id = Integer.parseInt(poiIdStr);
}
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 = Integer.parseInt(pp.getAttributeValue(null, "y"));
lon = Integer.parseInt(pp.getAttributeValue(null, "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);
}
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 (lat != 0 || lon != 0)
return new NearbyStationsResult(xmlCoordRequest(lat, lon, maxDistance, maxStations));
String uri = null;
if (uri == null && stationId != null)
uri = wrapUri(nearbyStationUri(stationId));
if (uri == null)
throw new IllegalArgumentException("at least one of stationId or lat/lon must be given");
InputStream is = null;
try
{
is = ParserUtils.scrapeInputStream(uri);
final XmlPullParser pp = parserFactory.newPullParser();
pp.setInput(is, null);
assertItdRequest(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 = XmlPullUtil.intAttr(pp, "x");
final int parsedLat = XmlPullUtil.intAttr(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(stations);
else
return new NearbyStationsResult(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(stations);
}
else if ("notidentified".equals(nameState))
{
return new NearbyStationsResult(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");
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 (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 (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("IR")) // Interregio
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 (type.equals("REX")) // RegionalExpress, Österreich
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("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")) // EGP - die Städtebahn GmbH
return 'R' + str;
if (type.equals("SES")) // EGP - die Städtebahn GmbH
return 'R' + str;
if (type.equals("agi")) // agilis
return 'R' + str;
if (type.equals("ag")) // agilis
return 'R' + str;
if (type.equals("TLX")) // Trilex (Vogtlandbahn)
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 (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 (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("Regionalexpress")) // VRR
return 'R' + name;
if ("CAPITOL".equals(name)) // San Francisco
return 'R' + name;
if ("Train".equals(noTrainName)) // San Francisco
return "R" + name;
if ("Regional Train :".equals(longName))
return "R";
if ("Regional Train".equals(noTrainName)) // Melbourne
return "R" + name;
if (type.equals("ATB")) // Autoschleuse Tauernbahn
return 'R' + name;
if (type.equals("BSB")) // Breisgau-S-Bahn
return 'S' + str;
if (type.equals("RER")) // Réseau Express Régional, Frankreich
return 'S' + str;
if (type.equals("LO")) // London Overground, GB
return 'S' + str;
if ("A".equals(name) || "B".equals(name) || "C".equals(name)) // SES
return 'S' + str;
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 (type.equals("BUS"))
return 'B' + str;
if ("SEV-Bus".equals(type))
return 'B' + str;
if (type.length() == 0)
return "?";
if (P_LINE_NUMBER.matcher(type).matches())
return "?";
if (P_LINE_Y.matcher(name).matches())
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 String stationId, final int maxDepartures, final boolean equivs) throws IOException
{
final StringBuilder uri = new StringBuilder();
uri.append(apiBase).append("XSLT_DM_REQUEST");
uri.append("?outputFormat=XML&coordOutputFormat=WGS84&type_dm=stop&useRealtime=1&mode=direct");
uri.append("&name_dm=").append(ParserUtils.urlEncode(stationId));
uri.append("&deleteAssignedStops_dm=").append(equivs ? '0' : '1');
if (maxDepartures > 0)
uri.append("&limit=").append(maxDepartures);
InputStream is = null;
try
{
is = ParserUtils.scrapeInputStream(wrapUri(uri.toString()));
final XmlPullParser pp = parserFactory.newPullParser();
pp.setInput(is, null);
assertItdRequest(pp);
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);
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();
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, "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 destination = normalizeLocationName(pp.getAttributeValue(null, "direction"));
final String destinationIdStr = pp.getAttributeValue(null, "destID");
final int destinationId = destinationIdStr.length() > 0 ? Integer.parseInt(destinationIdStr) : 0;
final String lineStr = processItdServingLine(pp);
final LineDestination line = new LineDestination(lineStr, lineColors(lineStr), destinationId, 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 = XmlPullUtil.intAttr(pp, "x");
final int lat = XmlPullUtil.intAttr(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 = normalizePlatform(pp.getAttributeValue(null, "platform"), pp.getAttributeValue(null, "platformName"));
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 destination = normalizeLocationName(pp.getAttributeValue(null, "direction"));
final int destinationId = Integer.parseInt(pp.getAttributeValue(null, "destID"));
final String line = processItdServingLine(pp);
if (isRealtime && !predictedDepartureTime.isSet(Calendar.HOUR_OF_DAY))
predictedDepartureTime.setTimeInMillis(plannedDepartureTime.getTimeInMillis());
final Departure departure = new Departure(plannedDepartureTime.getTime(),
predictedDepartureTime.isSet(Calendar.HOUR_OF_DAY) ? predictedDepartureTime.getTime() : null, line, lineColors(line),
null, position, destinationId, destination, null);
assignedStationDepartures.departures.add(departure);
XmlPullUtil.exit(pp, "itdDeparture");
}
XmlPullUtil.exit(pp, "itdDepartureList");
}
return result;
}
else if ("notidentified".equals(nameState))
{
return new QueryDeparturesResult(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 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;
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 String 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");
XmlPullUtil.enter(pp, "itdServingLine");
String noTrainName = null;
if (XmlPullUtil.test(pp, "itdNoTrain"))
noTrainName = pp.getAttributeValue(null, "name");
XmlPullUtil.exit(pp, "itdServingLine");
return parseLine(motType, number, number, noTrainName);
}
private static final Pattern P_STATION_NAME_WHITESPACE = Pattern.compile("\\s+");
protected static 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;
}
public QueryConnectionsResult queryConnections(final Location from, final Location via, final Location to, final Date date, final boolean dep,
final String products, final WalkSpeed walkSpeed) throws IOException
{
final String uri = wrapUri(connectionsQueryUri(from, via, to, date, dep, products, walkSpeed) + "&sessionID=0&requestID=0");
InputStream is = null;
try
{
is = ParserUtils.scrapeInputStream(uri);
return queryConnections(uri, is);
}
finally
{
if (is != null)
is.close();
}
}
public QueryConnectionsResult queryMoreConnections(final String uri) throws IOException
{
InputStream is = null;
try
{
is = ParserUtils.scrapeInputStream(uri);
return queryConnections(uri, is);
}
finally
{
if (is != null)
is.close();
}
}
private QueryConnectionsResult queryConnections(final String uri, final InputStream is) throws IOException
{
try
{
final XmlPullParser pp = parserFactory.newPullParser();
pp.setInput(is, null);
assertItdRequest(pp);
final String sessionId = XmlPullUtil.attr(pp, "sessionID");
if (!XmlPullUtil.jumpToStartTag(pp, null, "itdTripRequest"))
throw new IllegalStateException("cannot find <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(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);
}
XmlPullUtil.exit(pp, "itdOdvName");
XmlPullUtil.exit(pp, "itdOdv");
}
if (ambiguousFrom != null || ambiguousTo != null || ambiguousVia != null)
return new QueryConnectionsResult(ambiguousFrom, ambiguousVia, ambiguousTo);
XmlPullUtil.enter(pp, "itdTripDateTime");
XmlPullUtil.enter(pp, "itdDateTime");
if (!XmlPullUtil.test(pp, "itdDate"))
throw new IllegalStateException("cannot find <itdDate />");
if (!pp.isEmptyElementTag())
{
XmlPullUtil.enter(pp, "itdDate");
if (!XmlPullUtil.test(pp, "itdMessage"))
throw new IllegalStateException("cannot find <itdMessage />");
final String message = pp.nextText();
if (message.equals("invalid date"))
return new QueryConnectionsResult(Status.INVALID_DATE);
XmlPullUtil.exit(pp, "itdDate");
}
XmlPullUtil.exit(pp, "itdDateTime");
final Calendar departureTime = new GregorianCalendar(timeZone());
final Calendar arrivalTime = new GregorianCalendar(timeZone());
final Calendar stopTime = 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 = pp.getAttributeValue(null, "routeIndex") + "-" + pp.getAttributeValue(null, "routeTripIndex");
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;
Date firstDepartureTime = null;
Location lastArrival = null;
Date lastArrivalTime = null;
while (XmlPullUtil.test(pp, "itdPartialRoute"))
{
XmlPullUtil.enter(pp, "itdPartialRoute");
XmlPullUtil.test(pp, "itdPoint");
if (!"departure".equals(pp.getAttributeValue(null, "usage")))
throw new IllegalStateException();
final int departureId = Integer.parseInt(pp.getAttributeValue(null, "stopID"));
final String departureName = normalizeLocationName(pp.getAttributeValue(null, "name"));
final Location departure = new Location(LocationType.STATION, departureId, null, departureName);
if (firstDeparture == null)
firstDeparture = departure;
final String departurePosition = normalizePlatform(pp.getAttributeValue(null, "platform"),
pp.getAttributeValue(null, "platformName"));
XmlPullUtil.enter(pp, "itdPoint");
if (XmlPullUtil.test(pp, "itdMapItemList"))
XmlPullUtil.next(pp);
XmlPullUtil.require(pp, "itdDateTime");
processItdDateTime(pp, departureTime);
if (firstDepartureTime == null)
firstDepartureTime = departureTime.getTime();
XmlPullUtil.exit(pp, "itdPoint");
XmlPullUtil.test(pp, "itdPoint");
if (!"arrival".equals(pp.getAttributeValue(null, "usage")))
throw new IllegalStateException();
final int arrivalId = Integer.parseInt(pp.getAttributeValue(null, "stopID"));
final String arrivalName = normalizeLocationName(pp.getAttributeValue(null, "name"));
final Location arrival = new Location(LocationType.STATION, arrivalId, null, arrivalName);
lastArrival = arrival;
final String arrivalPosition = normalizePlatform(pp.getAttributeValue(null, "platform"),
pp.getAttributeValue(null, "platformName"));
XmlPullUtil.enter(pp, "itdPoint");
if (XmlPullUtil.test(pp, "itdMapItemList"))
XmlPullUtil.next(pp);
XmlPullUtil.require(pp, "itdDateTime");
processItdDateTime(pp, arrivalTime);
lastArrivalTime = arrivalTime.getTime();
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.getTimeInMillis() - departureTime.getTimeInMillis()) / 1000 / 60;
XmlPullUtil.enter(pp, "itdMeansOfTransport");
XmlPullUtil.exit(pp, "itdMeansOfTransport");
if (XmlPullUtil.test(pp, "itdStopSeq"))
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, lastFootway.departure, arrival, path));
}
else
{
parts.add(new Connection.Footway(min, 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 destinationIdStr = pp.getAttributeValue(null, "destID");
final String destinationName = normalizeLocationName(pp.getAttributeValue(null, "destination"));
final Location destination = destinationIdStr.length() > 0 ? new Location(LocationType.STATION,
Integer.parseInt(destinationIdStr), null, destinationName) : new Location(LocationType.ANY, 0, null,
destinationName);
final String lineStr;
if ("AST".equals(pp.getAttributeValue(null, "symbol")))
lineStr = "BAST";
else
lineStr = parseLine(pp.getAttributeValue(null, "motType"), pp.getAttributeValue(null, "shortname"),
pp.getAttributeValue(null, "name"), null);
final Line line = new Line(lineStr, lineColors(lineStr));
XmlPullUtil.enter(pp, "itdMeansOfTransport");
XmlPullUtil.exit(pp, "itdMeansOfTransport");
if (XmlPullUtil.test(pp, "itdRBLControlled"))
XmlPullUtil.next(pp);
if (XmlPullUtil.test(pp, "itdInfoTextList"))
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 int stopId = Integer.parseInt(pp.getAttributeValue(null, "stopID"));
final String stopName = normalizeLocationName(pp.getAttributeValue(null, "name"));
final String stopPosition = normalizePlatform(pp.getAttributeValue(null, "platform"),
pp.getAttributeValue(null, "platformName"));
XmlPullUtil.enter(pp, "itdPoint");
XmlPullUtil.require(pp, "itdDateTime");
final boolean success1 = processItdDateTime(pp, stopTime);
final boolean success2 = XmlPullUtil.test(pp, "itdDateTime") ? processItdDateTime(pp, stopTime) : false;
XmlPullUtil.exit(pp, "itdPoint");
if (success1 || success2)
intermediateStops.add(new Stop(new Location(LocationType.STATION, stopId, null, stopName), stopPosition,
stopTime.getTime()));
}
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 != arrivalId)
throw new IllegalStateException();
intermediateStops.remove(size - 1);
if (intermediateStops.get(0).location.id != departureId)
throw new IllegalStateException();
intermediateStops.remove(0);
}
}
List<Point> path = null;
if (XmlPullUtil.test(pp, "itdPathCoordinates"))
path = processItdPathCoordinates(pp);
parts.add(new Connection.Trip(line, destination, departureTime.getTime(), departurePosition, departure, arrivalTime
.getTime(), arrivalPosition, arrival, intermediateStops, path));
}
XmlPullUtil.exit(pp, "itdPartialRoute");
}
XmlPullUtil.exit(pp, "itdPartialRouteList");
final List<Fare> fares = new ArrayList<Fare>(2);
if (XmlPullUtil.test(pp, "itdFare") && !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");
if (fareAdult != null && fareAdult.length() > 0)
fares.add(new Fare(net, Type.ADULT, currency, Float.parseFloat(fareAdult), unitName, unitsAdult));
if (fareChild != null && fareChild.length() > 0)
fares.add(new Fare(net, Type.CHILD, currency, Float.parseFloat(fareChild), unitName, 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");
}
}
XmlPullUtil.exit(pp, "itdFare");
}
connections.add(new Connection(id, uri, firstDepartureTime, lastArrivalTime, firstDeparture, lastArrival, parts,
fares.isEmpty() ? null : fares));
XmlPullUtil.exit(pp, "itdRoute");
}
XmlPullUtil.exit(pp, "itdRouteList");
return new QueryConnectionsResult(uri, from, via, to, commandLink(sessionId, requestId, "tripNext"), connections);
}
else
{
return new QueryConnectionsResult(Status.NO_CONNECTIONS);
}
}
catch (final XmlPullParserException x)
{
throw new ParserException(x);
}
}
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(Integer.parseInt(coordsStr[1]), Integer.parseInt(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");
}
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("US$") ? 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;
}
public GetConnectionDetailsResult getConnectionDetails(final String connectionUri) throws IOException
{
throw new UnsupportedOperationException();
}
protected abstract String connectionsQueryUri(Location from, Location via, Location to, Date date, boolean dep, String products,
WalkSpeed walkSpeed);
protected abstract String commandLink(String sessionId, String requestId, String command);
protected static final void appendCommonConnectionParams(final StringBuilder uri)
{
uri.append("&outputFormat=XML");
uri.append("&coordListOutputFormat=STRING");
uri.append("&coordOutputFormat=WGS84");
uri.append("&calcNumberOfTrips=4");
}
protected void appendLocation(final StringBuilder uri, final Location location, final String paramSuffix)
{
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), "ISO-8859-1"));
}
}
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 String productParams(final String products)
{
if (products == null)
return "";
final StringBuilder params = new StringBuilder("&includedMeans=checkbox");
for (final char p : products.toCharArray())
{
if (p == 'I' || p == 'R')
params.append("&inclMOT_0=on");
if (p == 'S')
params.append("&inclMOT_1=on");
if (p == 'U')
params.append("&inclMOT_2=on");
if (p == 'T')
params.append("&inclMOT_3=on&inclMOT_4=on");
if (p == 'B')
params.append("&inclMOT_5=on&inclMOT_6=on&inclMOT_7=on");
if (p == 'P')
params.append("&inclMOT_10=on");
if (p == 'F')
params.append("&inclMOT_9=on");
if (p == 'C')
params.append("&inclMOT_8=on");
params.append("&inclMOT_11=on"); // TODO always show 'others', for now
}
return params.toString();
}
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 static final Map<Character, int[]> LINES = new HashMap<Character, int[]>();
static
{
LINES.put('I', new int[] { Color.WHITE, Color.RED, Color.RED });
LINES.put('R', new int[] { Color.GRAY, Color.WHITE });
LINES.put('S', new int[] { Color.parseColor("#006e34"), Color.WHITE });
LINES.put('U', new int[] { Color.parseColor("#003090"), Color.WHITE });
LINES.put('T', new int[] { Color.parseColor("#cc0000"), Color.WHITE });
LINES.put('B', new int[] { Color.parseColor("#993399"), Color.WHITE });
LINES.put('F', new int[] { Color.BLUE, Color.WHITE });
LINES.put('?', new int[] { Color.DKGRAY, Color.WHITE });
}
public int[] lineColors(final String line)
{
if (line.length() == 0)
return null;
return LINES.get(line.charAt(0));
}
private void assertItdRequest(final XmlPullParser pp) throws XmlPullParserException, IOException
{
if (!XmlPullUtil.jumpToStartTag(pp, null, "itdRequest"))
throw new IOException("cannot find <itdRequest />");
}
}