/*
* Copyright 2010-2014 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.Position;
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 useRealtime = true;
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 useRouteIndexAsTripId = true;
private boolean useLineRestriction = true;
private boolean useStringCoordListOutputFormat = 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));
}
public 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 setUseRealtime(final boolean useRealtime)
{
this.useRealtime = useRealtime;
}
protected void setIncludeRegionId(final boolean includeRegionId)
{
this.includeRegionId = includeRegionId;
}
protected void setUseRouteIndexAsTripId(final boolean useRouteIndexAsTripId)
{
this.useRouteIndexAsTripId = useRouteIndexAsTripId;
}
protected void setUseLineRestriction(final boolean useLineRestriction)
{
this.useLineRestriction = useLineRestriction;
}
protected void setUseStringCoordListOutputFormat(final boolean useStringCoordListOutputFormat)
{
this.useStringCoordListOutputFormat = useStringCoordListOutputFormat;
}
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 = stopfinderRequestParameters(constraint, "JSON");
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.getString("stateless"), lat, lon, place, name);
else if ("poi".equals(type))
return new Location(LocationType.POI, null, lat, lon, place, name);
else if ("crossing".equals(type))
return new Location(LocationType.ADDRESS, null, lat, lon, place, name);
else if ("street".equals(type) || "address".equals(type) || "singlehouse".equals(type) || "buildingname".equals(type))
return new Location(LocationType.ADDRESS, null, lat, lon, place, normalizeLocationName(stop.getString("name")));
else
throw new JSONException("unknown type: " + type);
}
private StringBuilder stopfinderRequestParameters(final Location constraint, final String outputFormat)
{
final StringBuilder parameters = new StringBuilder();
appendCommonRequestParams(parameters, outputFormat);
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");
parameters.append("&anyMaxSizeHitList=500");
}
return parameters;
}
protected List xmlStopfinderRequest(final Location constraint) throws IOException
{
final StringBuilder parameters = stopfinderRequestParameters(constraint, "XML");
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, "XML");
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.require(pp, "sf");
if (!pp.isEmptyElementTag())
{
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 String id = requireValueTag(pp, "id");
requireValueTag(pp, "stateless");
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 : null, 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");
}
else
{
XmlPullUtil.next(pp);
}
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)));
if (useStringCoordListOutputFormat)
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 String id = XmlPullUtil.attr(pp, "id");
final String name = normalizeLocationName(XmlPullUtil.optAttr(pp, "name", null));
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 String id = 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, null, 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 String id;
final String place;
final String name;
if ("stop".equals(anyType))
{
type = LocationType.STATION;
id = idStr;
place = locality;
name = objectName;
}
else if ("poi".equals(anyType) || "poiHierarchy".equals(anyType))
{
type = LocationType.POI;
id = idStr;
place = locality;
name = objectName;
}
else if ("loc".equals(anyType))
{
type = LocationType.ANY;
id = null;
place = locality;
name = locality;
}
else if ("address".equals(anyType))
{
type = LocationType.ADDRESS;
id = null;
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 = null;
place = locality;
name = objectName;
}
else if (anyType == null || "unknown".equals(anyType))
{
if (stopIdStr != null)
{
type = LocationType.STATION;
id = stopIdStr;
}
else if (poiIdStr != null)
{
type = LocationType.POI;
id = poiIdStr;
}
else if (streetIdStr != null)
{
type = LocationType.ADDRESS;
id = streetIdStr;
}
else if (lat != 0 || lon != 0)
{
type = LocationType.ADDRESS;
id = null;
}
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 String id = 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 String 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);
XmlPullUtil.enter(pp, "itdDepartureMonitorRequest");
if (!XmlPullUtil.test(pp, "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;
XmlPullUtil.exit(pp, "itdOdvName");
final List stations = new ArrayList();
if (XmlPullUtil.test(pp, "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");
}
XmlPullUtil.exit(pp, "itdOdv");
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))
{
final String trainNumStr = trainNum != null ? trainNum : "";
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 ("RHI".equals(trainType))
return "IRHI" + trainNum;
if ("TGV".equals(trainType) || "TGV".equals(trainName))
return "ITGV" + trainNum;
if ("INZ".equals(trainType))
return "IINZ" + trainNum;
if ("RJ".equals(trainType) || "railjet".equals(trainName)) // railjet
return "IRJ" + trainNum;
if ("OIC".equals(trainType) || "ÖBB InterCity".equals(trainName))
return 'I' + symbol;
if ("WB".equals(trainType) || "WESTbahn".equals(trainName))
return "IWB" + trainNum;
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 ("EIC".equals(trainType)) // Ekspres InterCity, Polen
return "IEIC" + trainNum;
if ("MT".equals(trainType) && "Schnee-Express".equals(trainName))
return "IMT" + 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 ("InterRegioExpress".equals(trainName))
return "RIRE" + trainNumStr;
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 ("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 ("SES".equals(trainType) || "Städteexpress Sachsen".equals(trainName))
return "RSES" + 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 ("SWEG-Zug".equals(longName))
return "RSWEG";
if ("EGP Eisenbahngesellschaft Potsdam".equals(trainName))
return "REGP" + trainNumStr;
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 ("AZ".equals(trainType) || "Auto-Zug".equals(trainName))
return "RAZ" + 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 ("M".equals(trainType) && "Meridian".equals(trainName))
return "RM" + trainNum;
if ("M".equals(trainType) && "Messezug".equals(trainName))
return "RM" + trainNum;
if ("EZ".equals(trainType)) // ÖBB Erlebniszug
return "REZ" + trainNum;
if ("DPF".equals(trainType))
return "RDPF" + trainNum;
if ("WBA".equals(trainType) || "Waldbahn".equals(trainName))
return "RWBA" + 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 ("S-Bahn".equals(trainName))
return "SS" + trainNumStr;
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(trainName) || "SEV".equals(symbol) || "BSV".equals(trainType)
|| "Ersatzverkehr".equals(trainName) || "Schienenersatzverkehr".equals(trainName))
return "BSEV" + (trainNum != null ? trainNum : "");
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 ("SB".equals(trainType)) // Seilbahn
return "CSB" + 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 String 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);
if (useRealtime)
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 String 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, "itdMessage"))
XmlPullUtil.next(pp);
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 assignedStopId = pp.getAttributeValue(null, "assignedStopID");
final String destinationName = normalizeLocationName(pp.getAttributeValue(null, "direction"));
final String destinationId = XmlPullUtil.optAttr(pp, "destID", null);
final Location destination = new Location(destinationId != null ? LocationType.STATION : LocationType.ANY, destinationId,
null, destinationName);
final LineDestination line = new LineDestination(processItdServingLine(pp), destination);
StationDepartures assignedStationDepartures;
if (assignedStopId == null)
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 String assignedStopId = XmlPullUtil.attr(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 Position position = normalizePlatformName(XmlPullUtil.optAttr(pp, "platformName", 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 = XmlPullUtil.optAttr(pp, "destID", null);
final String destinationId = !"-1".equals(destinationIdStr) ? destinationIdStr : null;
final Location destination = new Location(destinationId != null ? LocationType.STATION : LocationType.ANY, destinationId,
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 String 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 String assignedId = requireValueTag(pp, "id");
requireValueTag(pp, "a");
final Position position = new 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, null, 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 network = lineId.substring(0, lineId.indexOf(':'));
final String lineLabel = parseLine(productType, symbol, symbol, null, trainType, trainNum, productName);
line = new Line(lineId, lineLabel, lineStyle(network, 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 String id)
{
for (final StationDepartures stationDeparture : stationDepartures)
if (stationDeparture.location.id.equals(id))
return stationDeparture;
return null;
}
private Location processItdPointAttributes(final XmlPullParser pp)
{
final String id = pp.getAttributeValue(null, "stopID");
String place = normalizeLocationName(pp.getAttributeValue(null, "locality"));
if (place == null)
place = normalizeLocationName(pp.getAttributeValue(null, "place"));
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 != null && itdTrainName.toLowerCase().contains("ruf"))
itdMessage = text;
else if (text != null && text.toLowerCase().contains("ruf"))
itdMessage = text;
XmlPullUtil.exit(pp, "itdNoTrain");
}
else
{
XmlPullUtil.next(pp);
}
}
XmlPullUtil.require(pp, "motDivaParams");
final String divaNetwork = XmlPullUtil.attr(pp, "network");
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(divaNetwork, 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 Collection products, final WalkSpeed walkSpeed, final Accessibility accessibility, final Set