/*
* 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.io.Serializable;
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.Collections;
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.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.QueryDeparturesResult;
import de.schildbach.pte.dto.QueryTripsContext;
import de.schildbach.pte.dto.QueryTripsResult;
import de.schildbach.pte.dto.ResultHeader;
import de.schildbach.pte.dto.StationDepartures;
import de.schildbach.pte.dto.Stop;
import de.schildbach.pte.dto.Trip;
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 useRouteIndexAsTripId = true;
private boolean useLineRestriction = true;
private float fareCorrectionFactor = 1f;
private final XmlPullParserFactory parserFactory;
private static class Context implements QueryTripsContext
{
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 setAdditionalQueryParameter(final String additionalQueryParameter)
{
this.additionalQueryParameter = additionalQueryParameter;
}
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 setUseRouteIndexAsTripId(final boolean useRouteIndexAsTripId)
{
this.useRouteIndexAsTripId = useRouteIndexAsTripId;
}
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 setFareCorrectionFactor(final float fareCorrectionFactor)
{
this.fareCorrectionFactor = fareCorrectionFactor;
}
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);
}
private StringBuilder stopfinderRequestParameters(final Location constraint)
{
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");
}
return parameters;
}
protected List xmlStopfinderRequest(final Location constraint) throws IOException
{
final StringBuilder parameters = stopfinderRequestParameters(constraint);
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();
}
}
private class LocationAndQuality implements Serializable, Comparable
{
public final Location location;
public final int quality;
public LocationAndQuality(final Location location, final int quality)
{
this.location = location;
this.quality = quality;
}
public int compareTo(final LocationAndQuality other)
{
// prefer quality
if (this.quality > other.quality)
return -1;
else if (this.quality < other.quality)
return 1;
// prefer stations
final int compareLocationType = this.location.type.compareTo(other.location.type);
if (compareLocationType != 0)
return compareLocationType;
return 0;
}
@Override
public boolean equals(final Object o)
{
if (o == this)
return true;
if (!(o instanceof LocationAndQuality))
return false;
final LocationAndQuality other = (LocationAndQuality) o;
return location.equals(other.location);
}
@Override
public int hashCode()
{
return location.hashCode();
}
@Override
public String toString()
{
return quality + ":" + location;
}
}
protected List mobileStopfinderRequest(final Location constraint) throws IOException
{
final StringBuilder parameters = stopfinderRequestParameters(constraint);
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);
enterEfa(pp);
final List locations = new ArrayList();
XmlPullUtil.enter(pp, "sf");
while (XmlPullUtil.test(pp, "p"))
{
XmlPullUtil.enter(pp, "p");
final String name = normalizeLocationName(requireValueTag(pp, "n"));
final String u = requireValueTag(pp, "u");
if (!"sf".equals(u))
throw new RuntimeException("unknown usage: " + u);
final String ty = requireValueTag(pp, "ty");
final LocationType type;
if ("stop".equals(ty))
type = LocationType.STATION;
else if ("poi".equals(ty))
type = LocationType.POI;
else if ("loc".equals(ty))
type = LocationType.ADDRESS;
else if ("street".equals(ty))
type = LocationType.ADDRESS;
else if ("singlehouse".equals(ty))
type = LocationType.ADDRESS;
else
throw new RuntimeException("unknown type: " + ty);
XmlPullUtil.enter(pp, "r");
final int id = Integer.parseInt(requireValueTag(pp, "id"));
requireValueTag(pp, "omc");
final String place = normalizeLocationName(optValueTag(pp, "pc"));
requireValueTag(pp, "pid");
final Point coord = coordStrToPoint(optValueTag(pp, "c"));
XmlPullUtil.exit(pp, "r");
final String qal = optValueTag(pp, "qal");
final int quality = qal != null ? Integer.parseInt(qal) : 0;
XmlPullUtil.exit(pp, "p");
final Location location = new Location(type, type == LocationType.STATION ? id : 0, coord != null ? coord.lat : 0,
coord != null ? coord.lon : 0, place, name);
final LocationAndQuality locationAndQuality = new LocationAndQuality(location, quality);
locations.add(locationAndQuality);
}
XmlPullUtil.exit(pp, "sf");
Collections.sort(locations);
final List results = new ArrayList(locations.size());
for (final LocationAndQuality location : locations)
results.add(location.location);
return results;
}
catch (final XmlPullParserException x)
{
throw new ParserException(x);
}
finally
{
if (is != null)
is.close();
}
}
private StringBuilder xmlCoordRequestParameters(final int lat, final int lon, final int maxDistance, final int maxStations)
{
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
return parameters;
}
protected NearbyStationsResult xmlCoordRequest(final int lat, final int lon, final int maxDistance, final int maxStations) throws IOException
{
final StringBuilder parameters = xmlCoordRequestParameters(lat, lon, maxDistance, maxStations);
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();
}
}
protected NearbyStationsResult mobileCoordRequest(final int lat, final int lon, final int maxDistance, final int maxStations) throws IOException
{
final StringBuilder parameters = xmlCoordRequestParameters(lat, lon, maxDistance, maxStations);
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 = enterEfa(pp);
XmlPullUtil.enter(pp, "ci");
XmlPullUtil.enter(pp, "request");
XmlPullUtil.exit(pp, "request");
final List stations = new ArrayList();
if (XmlPullUtil.test(pp, "pis"))
{
XmlPullUtil.enter(pp, "pis");
while (XmlPullUtil.test(pp, "pi"))
{
XmlPullUtil.enter(pp, "pi");
final String name = normalizeLocationName(requireValueTag(pp, "de"));
final String type = requireValueTag(pp, "ty");
if (!"STOP".equals(type))
throw new RuntimeException("unknown type");
final int id = Integer.parseInt(requireValueTag(pp, "id"));
requireValueTag(pp, "omc");
requireValueTag(pp, "pid");
final String place = normalizeLocationName(requireValueTag(pp, "locality"));
requireValueTag(pp, "layer");
requireValueTag(pp, "gisID");
requireValueTag(pp, "ds");
final Point coord = coordStrToPoint(requireValueTag(pp, "c"));
stations.add(new Location(LocationType.STATION, id, coord.lat, coord.lon, place, name));
XmlPullUtil.exit(pp, "pi");
}
XmlPullUtil.exit(pp, "pis");
}
XmlPullUtil.exit(pp, "ci");
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 locality = normalizeLocationName(pp.getAttributeValue(null, "locality"));
final String objectName = normalizeLocationName(pp.getAttributeValue(null, "objectName"));
final String mapName = XmlPullUtil.optAttr(pp, "mapName", null);
final float x = XmlPullUtil.optFloatAttr(pp, "x", 0);
final float y = XmlPullUtil.optFloatAttr(pp, "y", 0);
XmlPullUtil.enter(pp, "odvNameElem");
final String elemName = normalizeLocationName(pp.getText());
XmlPullUtil.exit(pp, "odvNameElem");
final int lat;
final int lon;
if (mapName == null || (x == 0 && y == 0))
{
lat = 0;
lon = 0;
}
else if ("WGS84".equals(mapName))
{
lat = Math.round(y);
lon = Math.round(x);
}
else
{
throw new IllegalStateException("unknown mapName=" + mapName + " x=" + x + " y=" + y);
}
final LocationType type;
final int id;
final String place;
final String name;
if ("stop".equals(anyType))
{
type = LocationType.STATION;
id = Integer.parseInt(idStr);
place = locality;
name = objectName;
}
else if ("poi".equals(anyType) || "poiHierarchy".equals(anyType))
{
type = LocationType.POI;
id = Integer.parseInt(idStr);
place = locality;
name = objectName;
}
else if ("loc".equals(anyType))
{
type = LocationType.ANY;
id = 0;
place = locality;
name = locality;
}
else if ("address".equals(anyType))
{
type = LocationType.ADDRESS;
id = 0;
place = locality;
name = objectName;
}
else if ("postcode".equals(anyType) || "street".equals(anyType) || "crossing".equals(anyType) || "singlehouse".equals(anyType)
|| "buildingname".equals(anyType))
{
type = LocationType.ADDRESS;
id = 0;
place = locality;
name = objectName;
}
else if (anyType == null || "unknown".equals(anyType))
{
if (stopIdStr != null)
{
type = LocationType.STATION;
id = Integer.parseInt(stopIdStr);
}
else if (poiIdStr != null)
{
type = LocationType.POI;
id = Integer.parseInt(poiIdStr);
}
else if (streetIdStr != null)
{
type = LocationType.ADDRESS;
id = Integer.parseInt(streetIdStr);
}
else if (lat != 0 || lon != 0)
{
type = LocationType.ADDRESS;
id = 0;
}
else
{
throw new IllegalArgumentException("cannot substitute type");
}
place = locality;
name = objectName;
}
else
{
throw new IllegalArgumentException("unknown type: " + anyType);
}
return new Location(type, id, lat, lon, place != null ? place : defaultPlace, name != null ? name : elemName);
}
private Location processItdOdvAssignedStop(final XmlPullParser pp) throws XmlPullParserException, IOException
{
final int id = Integer.parseInt(pp.getAttributeValue(null, "stopID"));
final String mapName = XmlPullUtil.optAttr(pp, "mapName", null);
final float x = XmlPullUtil.optFloatAttr(pp, "x", 0);
final float y = XmlPullUtil.optFloatAttr(pp, "y", 0);
final int lat;
final int lon;
if (mapName == null || (x == 0 && y == 0))
{
lat = 0;
lon = 0;
}
else if ("WGS84".equals(mapName))
{
lat = Math.round(y);
lon = Math.round(x);
}
else
{
throw new IllegalStateException("unknown mapName=" + mapName + " x=" + x + " y=" + y);
}
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 Location newStation = processItdOdvAssignedStop(pp);
if (!stations.contains(newStation))
stations.add(newStation);
}
XmlPullUtil.exit(pp, "itdOdvAssignedStops");
}
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 ("Chiemseebahn".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 ("Bus".equals(trainType))
return "B" + 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 + "'");
}
protected StringBuilder queryDeparturesParameters(final int stationId, final int maxDepartures, final boolean equivs)
{
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);
return parameters;
}
public QueryDeparturesResult queryDepartures(final int stationId, final int maxDepartures, final boolean equivs) throws IOException
{
final StringBuilder parameters = queryDeparturesParameters(stationId, maxDepartures, equivs);
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 = XmlPullUtil.optAttr(pp, "mapName", null);
final float x = XmlPullUtil.optFloatAttr(pp, "x", 0);
final float y = XmlPullUtil.optFloatAttr(pp, "y", 0);
final int lat;
final int lon;
if (mapName == null || (x == 0 && y == 0))
{
lat = 0;
lon = 0;
}
else if ("WGS84".equals(mapName))
{
lat = Math.round(y);
lon = Math.round(x);
}
else
{
throw new IllegalStateException("unknown mapName=" + mapName + " x=" + x + " y=" + 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();
}
}
protected QueryDeparturesResult queryDeparturesMobile(final int stationId, final int maxDepartures, final boolean equivs) throws IOException
{
final StringBuilder parameters = queryDeparturesParameters(stationId, maxDepartures, equivs);
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 = enterEfa(pp);
final QueryDeparturesResult result = new QueryDeparturesResult(header);
XmlPullUtil.require(pp, "dps");
if (!pp.isEmptyElementTag())
{
XmlPullUtil.enter(pp, "dps");
final Calendar plannedDepartureTime = new GregorianCalendar(timeZone());
final Calendar predictedDepartureTime = new GregorianCalendar(timeZone());
while (XmlPullUtil.test(pp, "dp"))
{
XmlPullUtil.enter(pp, "dp");
// misc
/* final String stationName = */normalizeLocationName(requireValueTag(pp, "n"));
final boolean isRealtime = requireValueTag(pp, "realtime").equals("1");
XmlPullUtil.optSkip(pp, "dt");
// time
parseMobileSt(pp, plannedDepartureTime, predictedDepartureTime);
final LineDestination lineDestination = parseMobileM(pp, true);
XmlPullUtil.enter(pp, "r");
final int assignedId = Integer.parseInt(requireValueTag(pp, "id"));
requireValueTag(pp, "a");
final String position = optValueTag(pp, "pl");
XmlPullUtil.exit(pp, "r");
/* final Point positionCoordinate = */coordStrToPoint(optValueTag(pp, "c"));
// TODO messages
StationDepartures stationDepartures = findStationDepartures(result.stationDepartures, assignedId);
if (stationDepartures == null)
{
stationDepartures = new StationDepartures(new Location(LocationType.STATION, assignedId), new ArrayList(
maxDepartures), null);
result.stationDepartures.add(stationDepartures);
}
stationDepartures.departures.add(new Departure(plannedDepartureTime.getTime(),
predictedDepartureTime.isSet(Calendar.HOUR_OF_DAY) ? predictedDepartureTime.getTime() : null, lineDestination.line,
position, lineDestination.destination, null, null));
XmlPullUtil.exit(pp, "dp");
}
XmlPullUtil.exit(pp, "dps");
return result;
}
else
{
return new QueryDeparturesResult(header, QueryDeparturesResult.Status.INVALID_STATION);
}
}
catch (final XmlPullParserException x)
{
throw new ParserException(x);
}
finally
{
if (is != null)
is.close();
}
}
private static final Pattern P_MOBILE_M_SYMBOL = Pattern.compile("([^\\s]*)\\s+([^\\s]*)");
private LineDestination parseMobileM(final XmlPullParser pp, final boolean tyOrCo) throws XmlPullParserException, IOException
{
XmlPullUtil.enter(pp, "m");
final String n = optValueTag(pp, "n");
final String productNu = requireValueTag(pp, "nu");
final String ty = requireValueTag(pp, "ty");
final Line line;
final Location destination;
if ("100".equals(ty) || "99".equals(ty))
{
destination = null;
line = Line.FOOTWAY;
}
else if ("98".equals(ty))
{
destination = null;
line = Line.SECURE_CONNECTION;
}
else if ("97".equals(ty))
{
destination = null;
line = Line.DO_NOT_CHANGE;
}
else
{
final String co = requireValueTag(pp, "co");
final String productType = tyOrCo ? ty : co;
final String destinationName = normalizeLocationName(requireValueTag(pp, "des"));
destination = new Location(LocationType.ANY, 0, null, destinationName);
optValueTag(pp, "dy");
final String de = optValueTag(pp, "de");
final String productName = n != null ? n : de;
final String lineId = parseMobileDv(pp);
final String symbol = productNu.endsWith(" " + productName) ? productNu.substring(0, productNu.length() - productName.length() - 1)
: productNu;
final String trainType;
final String trainNum;
final Matcher mSymbol = P_MOBILE_M_SYMBOL.matcher(symbol);
if (mSymbol.matches())
{
trainType = mSymbol.group(1);
trainNum = mSymbol.group(2);
}
else
{
trainType = null;
trainNum = null;
}
final String lineLabel = parseLine(productType, symbol, symbol, null, trainType, trainNum, productName);
line = new Line(lineId, lineLabel, lineStyle(lineLabel));
}
XmlPullUtil.exit(pp, "m");
return new LineDestination(line, destination);
}
private String parseMobileDv(final XmlPullParser pp) throws XmlPullParserException, IOException
{
XmlPullUtil.enter(pp, "dv");
optValueTag(pp, "branch");
final String lineIdLi = requireValueTag(pp, "li");
final String lineIdSu = requireValueTag(pp, "su");
final String lineIdPr = requireValueTag(pp, "pr");
final String lineIdDct = requireValueTag(pp, "dct");
final String lineIdNe = requireValueTag(pp, "ne");
XmlPullUtil.exit(pp, "dv");
return lineIdNe + ":" + lineIdLi + ":" + lineIdSu + ":" + lineIdDct + ":" + lineIdPr;
}
private void parseMobileSt(final XmlPullParser pp, final Calendar plannedDepartureTime, final Calendar predictedDepartureTime)
throws XmlPullParserException, IOException
{
XmlPullUtil.enter(pp, "st");
plannedDepartureTime.clear();
ParserUtils.parseIsoDate(plannedDepartureTime, requireValueTag(pp, "da"));
ParserUtils.parseIsoTime(plannedDepartureTime, requireValueTag(pp, "t"));
predictedDepartureTime.clear();
if (XmlPullUtil.test(pp, "rda"))
{
ParserUtils.parseIsoDate(predictedDepartureTime, requireValueTag(pp, "rda"));
ParserUtils.parseIsoTime(predictedDepartureTime, requireValueTag(pp, "rt"));
}
XmlPullUtil.exit(pp, "st");
}
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 = XmlPullUtil.optAttr(pp, "mapName", null);
final float x = XmlPullUtil.optFloatAttr(pp, "x", 0);
final float y = XmlPullUtil.optFloatAttr(pp, "y", 0);
final int lat;
final int lon;
if (mapName == null || (x == 0 && y == 0))
{
lat = 0;
lon = 0;
}
else if ("WGS84".equals(mapName))
{
lat = Math.round(y);
lon = Math.round(x);
}
else
{
throw new IllegalStateException("unknown mapName=" + mapName + " x=" + x + " y=" + y);
}
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 numTrips, final Collection products, final WalkSpeed walkSpeed, final Accessibility accessibility,
final Set