/* * 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 . */ 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 jsonStopfinderRequest(final Location constraint) throws IOException { final StringBuilder uri = new StringBuilder(stopFinderEndpoint); appendCommonRequestParams(uri, "JSON"); uri.append("&locationServerActive=1"); if (includeRegionId) uri.append("®ionID_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 results = new ArrayList(); 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 xmlStopfinderRequest(final Location constraint) throws IOException { final StringBuilder uri = new StringBuilder(stopFinderEndpoint); appendCommonRequestParams(uri, "XML"); uri.append("&locationServerActive=1"); if (includeRegionId) uri.append("®ionID_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 results = new ArrayList(); XmlPullUtil.enter(pp, "itdStopFinderRequest"); XmlPullUtil.require(pp, "itdOdv"); if (!"sf".equals(pp.getAttributeValue(null, "usage"))) throw new IllegalStateException("cannot find "); 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 stations = new ArrayList(); 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 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 "); 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 "); 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 "); 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 stations = new ArrayList(); 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 stations = new ArrayList(); 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 "); 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(), new LinkedList())); 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(), new LinkedList())); } 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(), new LinkedList()); 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(), new LinkedList()); } 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, 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