/* * Copyright 2010-2013 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.Collection; 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.Product; 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.NotFoundException; 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 String additionalQueryParameter = null; private boolean canAcceptPoiId = false; private boolean needsSpEncId = false; private boolean includeRegionId = true; private Charset requestUrlEncoding = ISO_8859_1; private String httpReferer = null; private String httpRefererTrip = null; private boolean httpPost = false; private boolean suppressPositions = false; private boolean useRouteIndexAsConnectionId = true; private boolean useLineRestriction = 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(final String apiBase) { this(apiBase, null, null, null, null); } public AbstractEfaProvider(final String apiBase, final String departureMonitorEndpoint, final String tripEndpoint, final String stopFinderEndpoint, final String coordEndpoint) { 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)); } private AbstractEfaProvider(final String departureMonitorEndpoint, final String tripEndpoint, final String stopFinderEndpoint, final String coordEndpoint) { 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; } protected void setRequestUrlEncoding(final Charset requestUrlEncoding) { this.requestUrlEncoding = requestUrlEncoding; } protected void setHttpReferer(final String httpReferer) { this.httpReferer = httpReferer; this.httpRefererTrip = httpReferer; } public void setHttpRefererTrip(final String httpRefererTrip) { this.httpRefererTrip = httpRefererTrip; } protected void setHttpPost(final boolean httpPost) { this.httpPost = httpPost; } protected void setIncludeRegionId(final boolean includeRegionId) { this.includeRegionId = includeRegionId; } protected void setSuppressPositions(final boolean suppressPositions) { this.suppressPositions = suppressPositions; } protected void setUseRouteIndexAsConnectionId(final boolean useRouteIndexAsConnectionId) { this.useRouteIndexAsConnectionId = useRouteIndexAsConnectionId; } protected void setUseLineRestriction(final boolean useLineRestriction) { this.useLineRestriction = useLineRestriction; } protected void setCanAcceptPoiId(final boolean canAcceptPoiId) { this.canAcceptPoiId = canAcceptPoiId; } protected void setNeedsSpEncId(final boolean needsSpEncId) { this.needsSpEncId = needsSpEncId; } protected void setAdditionalQueryParameter(final String additionalQueryParameter) { this.additionalQueryParameter = additionalQueryParameter; } 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 parameters = new StringBuilder(); appendCommonRequestParams(parameters, "JSON"); parameters.append("&locationServerActive=1"); if (includeRegionId) parameters.append("®ionID_sf=1"); // prefer own region appendLocation(parameters, constraint, "sf"); if (constraint.type == LocationType.ANY) // 1=place 2=stop 4=street 8=address 16=crossing 32=poi 64=postcode parameters.append("&anyObjFilter_sf=").append(2 + 4 + 8 + 16 + 32 + 64); parameters.append("&anyMaxSizeHitList=500"); final StringBuilder uri = new StringBuilder(stopFinderEndpoint); if (!httpPost) uri.append(parameters); // System.out.println(uri); // System.out.println(parameters); final CharSequence page = ParserUtils.scrape(uri.toString(), httpPost ? parameters.substring(1) : 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 parameters = new StringBuilder(); appendCommonRequestParams(parameters, "XML"); parameters.append("&locationServerActive=1"); if (includeRegionId) parameters.append("®ionID_sf=1"); // prefer own region appendLocation(parameters, constraint, "sf"); if (constraint.type == LocationType.ANY) { if (needsSpEncId) parameters.append("&SpEncId=0"); // 1=place 2=stop 4=street 8=address 16=crossing 32=poi 64=postcode parameters.append("&anyObjFilter_sf=").append(2 + 4 + 8 + 16 + 32 + 64); parameters.append("&reducedAnyPostcodeObjFilter_sf=64&reducedAnyTooManyObjFilter_sf=2"); parameters.append("&useHouseNumberList=true"); } final StringBuilder uri = new StringBuilder(stopFinderEndpoint); if (!httpPost) uri.append(parameters); // System.out.println(uri); // System.out.println(parameters); InputStream is = null; try { is = ParserUtils.scrapeInputStream(uri.toString(), httpPost ? parameters.substring(1) : null, null, httpReferer, null, 3); 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 parameters = new StringBuilder(); appendCommonRequestParams(parameters, "XML"); parameters.append("&coord=").append(String.format(Locale.ENGLISH, "%2.6f:%2.6f:WGS84", latLonToDouble(lon), latLonToDouble(lat))); parameters.append("&coordListOutputFormat=STRING"); parameters.append("&max=").append(maxStations != 0 ? maxStations : 50); parameters.append("&inclFilter=1&radius_1=").append(maxDistance != 0 ? maxDistance : 1320); parameters.append("&type_1=STOP"); // ENTRANCE, BUS_POINT, POI_POINT final StringBuilder uri = new StringBuilder(coordEndpoint); if (!httpPost) uri.append(parameters); // System.out.println(uri); // System.out.println(parameters); InputStream is = null; try { is = ParserUtils.scrapeInputStream(uri.toString(), httpPost ? parameters.substring(1) : null, null, httpReferer, null, 3); 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")); final String mapName = pp.getAttributeValue(null, "mapName"); final int lat; final int lon; if (mapName == null || mapName.length() == 0) { lat = 0; lon = 0; } else if ("WGS84".equals(mapName)) { lat = Math.round(XmlPullUtil.floatAttr(pp, "y")); lon = Math.round(XmlPullUtil.floatAttr(pp, "x")); } else { throw new IllegalStateException("unknown mapName: " + mapName); } 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")); final String mapName = pp.getAttributeValue(null, "mapName"); final int lat; final int lon; if (mapName == null || mapName.length() == 0) { lat = 0; lon = 0; } else if ("WGS84".equals(mapName)) { lat = Math.round(XmlPullUtil.floatAttr(pp, "y")); lon = Math.round(XmlPullUtil.floatAttr(pp, "x")); } else { throw new IllegalStateException("unknown mapName: " + mapName); } 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 parameters = new StringBuilder(); appendCommonRequestParams(parameters, "XML"); parameters.append("&type_dm=stop&name_dm=").append(stationId); parameters.append("&itOptionsActive=1"); parameters.append("&ptOptionsActive=1"); parameters.append("&useProxFootSearch=1"); parameters.append("&mergeDep=1"); parameters.append("&useAllStops=1"); parameters.append("&mode=direct"); final StringBuilder uri = new StringBuilder(departureMonitorEndpoint); if (!httpPost) uri.append(parameters); // System.out.println(uri); // System.out.println(parameters); InputStream is = null; try { is = ParserUtils.scrapeInputStream(uri.toString(), httpPost ? parameters.substring(1) : null, null, httpReferer, "NSC_", 3); 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 && parsedMapName.length() != 0) { 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_RE = Pattern.compile("RE ?\\d+"); private static final Pattern P_LINE_RB = Pattern.compile("RB ?\\d+"); private static final Pattern P_LINE_R = Pattern.compile("R ?\\d+"); private static final Pattern P_LINE_S = Pattern.compile("^(?:%)?(S\\d+)"); private static final Pattern P_LINE_NUMBER = Pattern.compile("\\d+"); protected String parseLine(final String mot, String symbol, final String name, final String longName, final String trainType, final String trainNum, final String trainName) { if (mot == null) { if (trainName != null) { final String str = name != null ? name : ""; if (trainName.equals("S-Bahn")) return 'S' + str; if (trainName.equals("U-Bahn")) return 'U' + str; if (trainName.equals("Straßenbahn")) return 'T' + str; if (trainName.equals("Badner Bahn")) return 'T' + str; if (trainName.equals("Stadtbus")) return 'B' + str; if (trainName.equals("Citybus")) return 'B' + str; if (trainName.equals("Regionalbus")) return 'B' + str; if (trainName.equals("ÖBB-Postbus")) return 'B' + str; if (trainName.equals("Autobus")) return 'B' + str; if (trainName.equals("Discobus")) return 'B' + str; if (trainName.equals("Nachtbus")) return 'B' + str; if (trainName.equals("Anrufsammeltaxi")) return 'B' + str; if (trainName.equals("Ersatzverkehr")) return 'B' + str; if (trainName.equals("Vienna Airport Lines")) return 'B' + str; } throw new IllegalStateException("cannot normalize mot='" + mot + "' symbol='" + symbol + "' name='" + name + "' long='" + longName + "' trainType='" + trainType + "' trainNum='" + trainNum + "' trainName='" + trainName + "'"); } else if ("0".equals(mot)) { if ("EC".equals(trainType) || "EuroCity".equals(trainName) || "Eurocity".equals(trainName)) return "IEC" + trainNum; if ("EN".equals(trainType) || "EuroNight".equals(trainName)) return "IEN" + trainNum; if ("IC".equals(trainType) || "InterCity".equals(trainName)) return "IIC" + trainNum; if ("ICE".equals(trainType) || "Intercity-Express".equals(trainName)) return "IICE" + trainNum; if ("X".equals(trainType) || "InterConnex".equals(trainName)) return "IX" + trainNum; if ("CNL".equals(trainType) || "CityNightLine".equals(trainName)) // City Night Line return "ICNL" + trainNum; if ("THA".equals(trainType) || "Thalys".equals(trainName)) return "ITHA" + trainNum; if ("TGV".equals(trainType) || "TGV".equals(trainName)) return "ITGV" + trainNum; if ("RJ".equals(trainType) || "railjet".equals(trainName)) // railjet return "IRJ" + trainNum; if ("OIC".equals(trainType) || "ÖBB InterCity".equals(trainName)) return 'I' + symbol; if ("HKX".equals(trainType) || "Hamburg-Köln-Express".equals(trainName)) return "IHKX" + trainNum; if ("INT".equals(trainType)) // SVV, VAGFR return "IINT" + trainNum; if ("SC".equals(trainType) || "SC Pendolino".equals(trainName)) // SuperCity, Tschechien return "ISC" + trainNum; if ("ECB".equals(trainType)) // EC, Verona-München return "IECB" + trainNum; if ("ES".equals(trainType)) // Eurostar Italia return "IES" + trainNum; if ("EST".equals(trainType) || "EUROSTAR".equals(trainName)) return "IEST" + trainNum; if ("Zug".equals(trainName)) return 'R' + symbol; if ("Zuglinie".equals(trainName)) return 'R' + symbol; if ("IR".equals(trainType) || "Interregio".equals(trainName) || "InterRegio".equals(trainName)) return "RIR" + trainNum; if ("IRE".equals(trainType) || "Interregio-Express".equals(trainName)) return "RIRE" + trainNum; if ("RE".equals(trainType) || "Regional-Express".equals(trainName)) return "RRE" + trainNum; if (trainType == null && trainNum != null && P_LINE_RE.matcher(trainNum).matches()) return 'R' + trainNum; if ("Regionalexpress".equals(trainName)) return 'R' + symbol; if ("R-Bahn".equals(trainName)) return 'R' + symbol; if ("RB-Bahn".equals(trainName)) return 'R' + symbol; if ("RE-Bahn".equals(trainName)) return 'R' + symbol; if ("REX".equals(trainType)) // RegionalExpress, Österreich return "RREX" + trainNum; if ("RB".equals(trainType) || "Regionalbahn".equals(trainName)) return "RRB" + trainNum; if (trainType == null && trainNum != null && P_LINE_RB.matcher(trainNum).matches()) return 'R' + trainNum; if ("Abellio-Zug".equals(trainName)) return "R" + symbol; if ("Westfalenbahn".equals(trainName)) return 'R' + symbol; if ("R".equals(trainType) || "Regionalzug".equals(trainName)) return "RR" + trainNum; if (trainType == null && trainNum != null && P_LINE_R.matcher(trainNum).matches()) return 'R' + trainNum; if ("D".equals(trainType) || "Schnellzug".equals(trainName)) return "RD" + trainNum; if ("E".equals(trainType) || "Eilzug".equals(trainName)) return "RE" + trainNum; if ("WFB".equals(trainType) || "WestfalenBahn".equals(trainName)) return "RWFB" + trainNum; if ("NWB".equals(trainType) || "NordWestBahn".equals(trainName)) return "RNWB" + trainNum; if ("WB".equals(trainType) || "WESTbahn".equals(trainName)) return "RWB" + trainNum; if ("WES".equals(trainType) || "Westbahn".equals(trainName)) return "RWES" + trainNum; if ("ERB".equals(trainType) || "eurobahn".equals(trainName)) return "RERB" + trainNum; if ("CAN".equals(trainType) || "cantus Verkehrsgesellschaft".equals(trainName)) return "RCAN" + trainNum; if ("HEX".equals(trainType) || "Veolia Verkehr Sachsen-Anhalt".equals(trainName)) return "RHEX" + trainNum; if ("EB".equals(trainType) || "Erfurter Bahn".equals(trainName)) return "REB" + trainNum; if ("EBx".equals(trainType) || "Erfurter Bahn Express".equals(trainName)) return "REBx" + trainNum; if ("MRB".equals(trainType) || "Mitteldeutsche Regiobahn".equals(trainName)) return "RMRB" + trainNum; if ("ABR".equals(trainType) || "ABELLIO Rail NRW GmbH".equals(trainName)) return "RABR" + trainNum; if ("NEB".equals(trainType) || "NEB Niederbarnimer Eisenbahn".equals(trainName)) return "RNEB" + trainNum; if ("OE".equals(trainType) || "Ostdeutsche Eisenbahn GmbH".equals(trainName)) return "ROE" + trainNum; if ("ODE".equals(trainType)) return 'R' + symbol; if ("OLA".equals(trainType) || "Ostseeland Verkehr GmbH".equals(trainName)) return "ROLA" + trainNum; if ("UBB".equals(trainType) || "Usedomer Bäderbahn".equals(trainName)) return "RUBB" + trainNum; if ("EVB".equals(trainType) || "ELBE-WESER GmbH".equals(trainName)) return "REVB" + trainNum; if ("RTB".equals(trainType) || "Rurtalbahn GmbH".equals(trainName)) return "RRTB" + trainNum; if ("STB".equals(trainType) || "Süd-Thüringen-Bahn".equals(trainName)) return "RSTB" + trainNum; if ("HTB".equals(trainType) || "Hellertalbahn".equals(trainName)) return "RHTB" + trainNum; if ("VBG".equals(trainType) || "Vogtlandbahn".equals(trainName)) return "RVBG" + trainNum; if ("CB".equals(trainType) || "City-Bahn Chemnitz".equals(trainName)) return "RCB" + trainNum; if ("VEC".equals(trainType) || "vectus Verkehrsgesellschaft".equals(trainName)) return "RVEC" + trainNum; if ("HzL".equals(trainType) || "Hohenzollerische Landesbahn AG".equals(trainName)) return "RHzL" + trainNum; if ("SBB".equals(trainType) || "SBB GmbH".equals(trainName)) return "RSBB" + trainNum; if ("MBB".equals(trainType) || "Mecklenburgische Bäderbahn Molli".equals(trainName)) return "RMBB" + trainNum; if ("OS".equals(trainType)) // Osobní vlak return "ROS" + trainNum; if ("SP".equals(trainType) || "Sp".equals(trainType)) // Spěšný vlak return "RSP" + trainNum; if ("Dab".equals(trainType) || "Daadetalbahn".equals(trainName)) return "RDab" + trainNum; if ("FEG".equals(trainType) || "Freiberger Eisenbahngesellschaft".equals(trainName)) return "RFEG" + trainNum; if ("ARR".equals(trainType) || "ARRIVA".equals(trainName)) return "RARR" + trainNum; if ("HSB".equals(trainType) || "Harzer Schmalspurbahn".equals(trainName)) return "RHSB" + trainNum; if ("ALX".equals(trainType) || "alex - Länderbahn und Vogtlandbahn GmbH".equals(trainName)) return "RALX" + trainNum; if ("EX".equals(trainType) || "Fatra".equals(trainName)) return "REX" + trainNum; if ("ME".equals(trainType) || "metronom".equals(trainName)) return "RME" + trainNum; if ("MEr".equals(trainType)) return "RMEr" + trainNum; if ("AKN".equals(trainType) || "AKN Eisenbahn AG".equals(trainName)) return "RAKN" + trainNum; if ("SOE".equals(trainType) || "Sächsisch-Oberlausitzer Eisenbahngesellschaft".equals(trainName)) return "RSOE" + trainNum; if ("VIA".equals(trainType) || "VIAS GmbH".equals(trainName)) return "RVIA" + trainNum; if ("BRB".equals(trainType) || "Bayerische Regiobahn".equals(trainName)) return "RBRB" + trainNum; if ("BLB".equals(trainType) || "Berchtesgadener Land Bahn".equals(trainName)) return "RBLB" + trainNum; if ("HLB".equals(trainType) || "Hessische Landesbahn".equals(trainName)) return "RHLB" + trainNum; if ("NOB".equals(trainType) || "NordOstseeBahn".equals(trainName)) return "RNOB" + trainNum; if ("NBE".equals(trainType) || "Nordbahn Eisenbahngesellschaft".equals(trainName)) return "RNBE" + trainNum; if ("VEN".equals(trainType) || "Rhenus Veniro".equals(trainName)) return "RVEN" + trainType; if ("DPN".equals(trainType) || "Nahreisezug".equals(trainName)) return "RDPN" + trainNum; if ("RBG".equals(trainType) || "Regental Bahnbetriebs GmbH".equals(trainName)) return "RRBG" + trainNum; if ("BOB".equals(trainType) || "Bodensee-Oberschwaben-Bahn".equals(trainName)) return "RBOB" + trainNum; if ("VE".equals(trainType) || "Vetter".equals(trainName)) return "RVE" + trainNum; if ("SDG".equals(trainType) || "SDG Sächsische Dampfeisenbahngesellschaft mbH".equals(trainName)) return "RSDG" + trainNum; if ("PRE".equals(trainType) || "Pressnitztalbahn".equals(trainName)) return "RPRE" + trainNum; if ("VEB".equals(trainType) || "Vulkan-Eifel-Bahn".equals(trainName)) return "RVEB" + trainNum; if ("neg".equals(trainType) || "Norddeutsche Eisenbahn Gesellschaft".equals(trainName)) return "Rneg" + trainNum; if ("AVG".equals(trainType) || "Felsenland-Express".equals(trainName)) return "RAVG" + trainNum; if ("P".equals(trainType) || "BayernBahn Betriebs-GmbH".equals(trainName) || "Brohltalbahn".equals(trainName) || "Kasbachtalbahn".equals(trainName)) return "RP" + trainNum; if ("SBS".equals(trainType) || "Städtebahn Sachsen".equals(trainName)) return "RSBS" + trainNum; if ("SB-".equals(trainType)) // Städtebahn Sachsen return "RSB" + trainNum; if ("ag".equals(trainType)) // agilis return "Rag" + trainNum; if ("agi".equals(trainType) || "agilis".equals(trainName)) return "Ragi" + trainNum; if ("as".equals(trainType) || "agilis-Schnellzug".equals(trainName)) return "Ras" + trainNum; if ("TLX".equals(trainType) || "TRILEX".equals(trainName)) // Trilex (Vogtlandbahn) return "RTLX" + trainNum; if ("MSB".equals(trainType) || "Mainschleifenbahn".equals(trainName)) return "RMSB" + trainNum; if ("BE".equals(trainType) || "Bentheimer Eisenbahn".equals(trainName)) return "RBE" + trainNum; if ("erx".equals(trainType) || "erixx - Der Heidesprinter".equals(trainName)) return "Rerx" + trainNum; if ("SWEG-Zug".equals(trainName)) // Südwestdeutschen Verkehrs-Aktiengesellschaft return "RSWEG" + trainNum; if ("ÖBB".equals(trainType) || "ÖBB".equals(trainName)) return "RÖBB" + trainNum; if ("CAT".equals(trainType)) // City Airport Train Wien return "RCAT" + trainNum; if ("DZ".equals(trainType) || "Dampfzug".equals(trainName)) return "RDZ" + trainNum; if ("CD".equals(trainType)) // Tschechien return "RCD" + trainNum; if ("VR".equals(trainType)) // Polen return 'R' + symbol; if ("PR".equals(trainType)) // Polen return 'R' + symbol; if ("KD".equals(trainType)) // Koleje Dolnośląskie (Niederschlesische Eisenbahn) return 'R' + symbol; if ("OO".equals(trainType) || "Ordinary passenger (o.pas.)".equals(trainName)) // GB return "ROO" + trainNum; if ("XX".equals(trainType) || "Express passenger (ex.pas.)".equals(trainName)) // GB return "RXX" + trainNum; if ("XZ".equals(trainType) || "Express passenger sleeper".equals(trainName)) // GB return "RXZ" + trainNum; if ("ATB".equals(trainType)) // Autoschleuse Tauernbahn return "RATB" + trainNum; if ("ATZ".equals(trainType)) // Autozug return "RATZ" + trainNum; if ("DWE".equals(trainType) || "Dessau-Wörlitzer Eisenbahn".equals(trainName)) return "RDWE" + trainNum; if ("KTB".equals(trainType) || "Kandertalbahn".equals(trainName)) return "RKTB" + trainNum; if ("CBC".equals(trainType) || "CBC".equals(trainName)) // City-Bahn Chemnitz return "RCBC" + trainNum; if ("Bernina Express".equals(trainName)) return 'R' + trainNum; if ("STR".equals(trainType)) // Harzquerbahn, Nordhausen return "RSTR" + trainNum; if ("EXT".equals(trainType) || "Extrazug".equals(trainName)) return "REXT" + trainNum; if ("Heritage Railway".equals(trainName)) // GB return 'R' + symbol; if ("WTB".equals(trainType) || "Wutachtalbahn".equals(trainName)) return "RWTB" + trainNum; if ("DB".equals(trainType) || "DB Regio".equals(trainName)) return "RDB" + trainNum; if ("BSB-Zug".equals(trainName)) // Breisgau-S-Bahn return 'S' + trainNum; if ("RSB".equals(trainType)) // Regionalschnellbahn, Wien return "SRSB" + trainNum; if ("RER".equals(trainName) && symbol.length() == 1) // Réseau Express Régional, Frankreich return 'S' + symbol; if ("S".equals(trainType)) return "SS" + trainNum; if ("RT".equals(trainType) || "RegioTram".equals(trainName)) return "TRT" + trainNum; if ("SEV".equals(trainType) || "SEV".equals(trainNum) || "SEV".equals(symbol) || "Ersatzverkehr".equals(trainName)) return "BSEV"; if ("Bus replacement".equals(trainName)) // GB return "BBR"; if ("BR".equals(trainType) && trainName.startsWith("Bus")) // GB return "BBR" + trainNum; if ("GB".equals(trainType)) // Gondelbahn return "CGB" + trainNum; if (trainType == null && trainName == null && P_LINE_NUMBER.matcher(symbol).matches()) return '?' + symbol; if (trainType == null && trainName == null && symbol.equals(name) && symbol.equals(longName)) return '?' + symbol; if ("N".equals(trainType) && trainName == null && symbol.length() == 0) return "?N" + trainNum; if ("Train".equals(trainName)) return "?"; throw new IllegalStateException("cannot normalize mot='" + mot + "' symbol='" + symbol + "' name='" + name + "' long='" + longName + "' trainType='" + trainType + "' trainNum='" + trainNum + "' trainName='" + trainName + "'"); } else if ("1".equals(mot)) { final Matcher m = P_LINE_S.matcher(name); if (m.find()) return 'S' + m.group(1); else return 'S' + name; } else if ("2".equals(mot)) { return 'U' + name; } else if ("3".equals(mot) || "4".equals(mot)) { return 'T' + name; } else if ("5".equals(mot) || "6".equals(mot) || "7".equals(mot) || "10".equals(mot)) { if (name.equals("Schienenersatzverkehr")) return "BSEV"; else return 'B' + name; } else if ("8".equals(mot)) { return 'C' + name; } else if ("9".equals(mot)) { return 'F' + name; } else if ("11".equals(mot)) { return '?' + ParserUtils.firstNotEmpty(symbol, name); } throw new IllegalStateException("cannot normalize mot='" + mot + "' symbol='" + symbol + "' name='" + name + "' long='" + longName + "' trainType='" + trainType + "' trainNum='" + trainNum + "' trainName='" + trainName + "'"); } public QueryDeparturesResult queryDepartures(final int stationId, final int maxDepartures, final boolean equivs) throws IOException { final StringBuilder parameters = new StringBuilder(); appendCommonRequestParams(parameters, "XML"); parameters.append("&type_dm=stop"); parameters.append("&name_dm=").append(stationId); parameters.append("&useRealtime=1"); parameters.append("&mode=direct"); parameters.append("&ptOptionsActive=1"); parameters.append("&deleteAssignedStops_dm=").append(equivs ? '0' : '1'); parameters.append("&mergeDep=1"); // merge departures if (maxDepartures > 0) parameters.append("&limit=").append(maxDepartures); final StringBuilder uri = new StringBuilder(departureMonitorEndpoint); if (!httpPost) uri.append(parameters); // System.out.println(uri); // System.out.println(parameters); InputStream is = null; try { is = ParserUtils.scrapeInputStream(uri.toString(), httpPost ? parameters.substring(1) : null, null, httpReferer, 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"); final int lat; final int lon; if (mapName == null || mapName.length() == 0) { lat = 0; lon = 0; } else if ("WGS84".equals(mapName)) { lat = Math.round(XmlPullUtil.floatAttr(pp, "y")); lon = Math.round(XmlPullUtil.floatAttr(pp, "x")); } else { throw new IllegalStateException("unknown mapName: " + mapName); } // 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 String mapName = pp.getAttributeValue(null, "mapName"); final int lat; final int lon; if (mapName == null || mapName.length() == 0) { lat = 0; lon = 0; } else if ("WGS84".equals(mapName)) { lat = Math.round(XmlPullUtil.floatAttr(pp, "y")); lon = Math.round(XmlPullUtil.floatAttr(pp, "x")); } else { throw new IllegalStateException("unknown mapName: " + mapName); } 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")); final int weekday = Integer.parseInt(pp.getAttributeValue(null, "weekday")); XmlPullUtil.next(pp); if (weekday < 0) return false; 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 slMotType = pp.getAttributeValue(null, "motType"); final String slSymbol = pp.getAttributeValue(null, "symbol"); final String slNumber = pp.getAttributeValue(null, "number"); final String slStateless = pp.getAttributeValue(null, "stateless"); final String slTrainType = pp.getAttributeValue(null, "trainType"); final String slTrainName = pp.getAttributeValue(null, "trainName"); final String slTrainNum = pp.getAttributeValue(null, "trainNum"); XmlPullUtil.enter(pp, "itdServingLine"); String itdTrainName = null; String itdTrainType = null; String itdMessage = null; if (XmlPullUtil.test(pp, "itdTrain")) { itdTrainName = pp.getAttributeValue(null, "name"); itdTrainType = pp.getAttributeValue(null, "type"); if (!pp.isEmptyElementTag()) { XmlPullUtil.enter(pp, "itdTrain"); XmlPullUtil.exit(pp, "itdTrain"); } else { XmlPullUtil.next(pp); } } if (XmlPullUtil.test(pp, "itdNoTrain")) { itdTrainName = pp.getAttributeValue(null, "name"); itdTrainType = pp.getAttributeValue(null, "type"); if (!pp.isEmptyElementTag()) { XmlPullUtil.enter(pp, "itdNoTrain"); final String text = pp.getText(); if (itdTrainName.toLowerCase().contains("ruf") && text.toLowerCase().contains("ruf")) itdMessage = text; XmlPullUtil.exit(pp, "itdNoTrain"); } else { XmlPullUtil.next(pp); } } XmlPullUtil.exit(pp, "itdServingLine"); final String trainType = ParserUtils.firstNotEmpty(slTrainType, itdTrainType); final String trainName = ParserUtils.firstNotEmpty(slTrainName, itdTrainName); final String label = parseLine(slMotType, slSymbol, slNumber, slNumber, trainType, slTrainNum, trainName); return new Line(slStateless, label, lineStyle(label), itdMessage); } 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 xsltTripRequestParameters(final Location from, final Location via, final Location to, final Date date, final boolean dep, final int numConnections, final Collection products, final WalkSpeed walkSpeed, final Accessibility accessibility, final Set