fix Bavaria by implementing mobile efa protocol

This commit is contained in:
Andreas Schildbach 2013-06-05 13:01:38 +02:00
parent 01bc6b6219
commit d1a23d7fdb
6 changed files with 978 additions and 29 deletions

View file

@ -22,12 +22,14 @@ 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;
@ -333,7 +335,7 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider
throw new JSONException("unknown type: " + type);
}
protected List<Location> xmlStopfinderRequest(final Location constraint) throws IOException
private StringBuilder stopfinderRequestParameters(final Location constraint)
{
final StringBuilder parameters = new StringBuilder();
appendCommonRequestParams(parameters, "XML");
@ -351,6 +353,13 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider
parameters.append("&useHouseNumberList=true");
}
return parameters;
}
protected List<Location> xmlStopfinderRequest(final Location constraint) throws IOException
{
final StringBuilder parameters = stopfinderRequestParameters(constraint);
final StringBuilder uri = new StringBuilder(stopFinderEndpoint);
if (!httpPost)
uri.append(parameters);
@ -419,7 +428,147 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider
}
}
protected NearbyStationsResult xmlCoordRequest(final int lat, final int lon, final int maxDistance, final int maxStations) throws IOException
private class LocationAndQuality implements Serializable, Comparable<LocationAndQuality>
{
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<Location> 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<LocationAndQuality> locations = new ArrayList<LocationAndQuality>();
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<Location> results = new ArrayList<Location>(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");
@ -429,6 +578,13 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider
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);
@ -493,6 +649,78 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider
}
}
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<Location> stations = new ArrayList<Location>();
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<Location> autocompleteStations(final CharSequence constraint) throws IOException
{
return jsonStopfinderRequest(new Location(LocationType.ANY, 0, null, constraint.toString()));
@ -1127,7 +1355,7 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider
+ "' trainType='" + trainType + "' trainNum='" + trainNum + "' trainName='" + trainName + "'");
}
public QueryDeparturesResult queryDepartures(final int stationId, final int maxDepartures, final boolean equivs) throws IOException
protected StringBuilder queryDeparturesParameters(final int stationId, final int maxDepartures, final boolean equivs)
{
final StringBuilder parameters = new StringBuilder();
appendCommonRequestParams(parameters, "XML");
@ -1141,6 +1369,13 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider
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);
@ -1352,6 +1587,191 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider
}
}
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<Departure>(
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> stationDepartures, final int id)
{
for (final StationDepartures stationDeparture : stationDepartures)
@ -1653,6 +2073,41 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider
}
}
protected QueryTripsResult queryTripsMobile(final Location from, final Location via, final Location to, final Date date, final boolean dep,
final int numTrips, final Collection<Product> products, final WalkSpeed walkSpeed, final Accessibility accessibility,
final Set<Option> options) throws IOException
{
final String parameters = xsltTripRequestParameters(from, via, to, date, dep, numTrips, products, walkSpeed, accessibility, options);
final StringBuilder uri = new StringBuilder(tripEndpoint);
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, httpRefererTrip, "NSC_", 3);
return queryTripsMobile(uri.toString(), from, via, to, is);
}
catch (final XmlPullParserException x)
{
throw new ParserException(x);
}
catch (final RuntimeException x)
{
throw new RuntimeException("uncategorized problem while processing " + uri, x);
}
finally
{
if (is != null)
is.close();
}
}
private static final Pattern P_SESSION_EXPIRED = Pattern.compile("Your session has expired");
public QueryTripsResult queryMoreTrips(final QueryTripsContext contextObj, final boolean later, final int numTrips) throws IOException
@ -1701,6 +2156,49 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider
}
}
protected QueryTripsResult queryMoreTripsMobile(final QueryTripsContext contextObj, final boolean later, final int numConnections)
throws IOException
{
final Context context = (Context) contextObj;
final String commandUri = context.context;
final StringBuilder uri = new StringBuilder(commandUri);
uri.append("&command=").append(later ? "tripNext" : "tripPrev");
InputStream is = null;
try
{
is = new BufferedInputStream(ParserUtils.scrapeInputStream(uri.toString(), null, null, httpRefererTrip, "NSC_", 3));
is.mark(512);
return queryTripsMobile(uri.toString(), null, null, null, is);
}
catch (final XmlPullParserException x)
{
throw new ParserException(x);
}
catch (final ProtocolException x) // must be html content
{
is.reset();
final BufferedReader reader = new BufferedReader(new InputStreamReader(is));
String line;
while ((line = reader.readLine()) != null)
if (P_SESSION_EXPIRED.matcher(line).find())
throw new SessionExpiredException();
throw x;
}
catch (final RuntimeException x)
{
throw new RuntimeException("uncategorized problem while processing " + uri, x);
}
finally
{
if (is != null)
is.close();
}
}
private QueryTripsResult queryTrips(final String uri, final InputStream is) throws XmlPullParserException, IOException
{
// System.out.println(uri);
@ -2252,10 +2750,257 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider
}
}
private QueryTripsResult queryTripsMobile(final String uri, final Location from, final Location via, final Location to, final InputStream is)
throws XmlPullParserException, IOException
{
// System.out.println(uri);
final XmlPullParser pp = parserFactory.newPullParser();
pp.setInput(is, null);
final ResultHeader header = enterEfa(pp);
final Calendar plannedTime = new GregorianCalendar(timeZone());
final Calendar predictedTime = new GregorianCalendar(timeZone());
final List<Trip> trips = new ArrayList<Trip>();
if (XmlPullUtil.test(pp, "ts"))
{
XmlPullUtil.enter(pp, "ts");
while (XmlPullUtil.test(pp, "tp"))
{
XmlPullUtil.enter(pp, "tp");
XmlPullUtil.optSkip(pp, "attrs");
requireValueTag(pp, "d"); // duration
final int numChanges = Integer.parseInt(requireValueTag(pp, "ic"));
final String tripId = requireValueTag(pp, "de");
XmlPullUtil.enter(pp, "ls");
final List<Trip.Leg> legs = new LinkedList<Trip.Leg>();
Location firstDepartureLocation = null;
Location lastArrivalLocation = null;
while (XmlPullUtil.test(pp, "l"))
{
XmlPullUtil.enter(pp, "l");
XmlPullUtil.enter(pp, "ps");
Stop departure = null;
Stop arrival = null;
while (XmlPullUtil.test(pp, "p"))
{
XmlPullUtil.enter(pp, "p");
final String name = requireValueTag(pp, "n");
final String usage = requireValueTag(pp, "u");
optValueTag(pp, "de");
XmlPullUtil.requireSkip(pp, "dt");
parseMobileSt(pp, plannedTime, predictedTime);
XmlPullUtil.requireSkip(pp, "lis");
XmlPullUtil.enter(pp, "r");
final int id = Integer.parseInt(requireValueTag(pp, "id"));
optValueTag(pp, "a");
final String position = optValueTag(pp, "pl");
final String place = normalizeLocationName(optValueTag(pp, "pc"));
final Point coord = coordStrToPoint(requireValueTag(pp, "c"));
XmlPullUtil.exit(pp, "r");
final Location location;
if (id == 99999997 || id == 99999998)
location = new Location(LocationType.ADDRESS, 0, coord.lat, coord.lon, place, name);
else
location = new Location(LocationType.STATION, id, coord.lat, coord.lon, place, name);
XmlPullUtil.exit(pp, "p");
if ("departure".equals(usage))
{
departure = new Stop(location, true, plannedTime.isSet(Calendar.HOUR_OF_DAY) ? plannedTime.getTime()
: predictedTime.getTime(), predictedTime.isSet(Calendar.HOUR_OF_DAY) ? predictedTime.getTime() : null, position,
null);
if (firstDepartureLocation == null)
firstDepartureLocation = location;
}
else if ("arrival".equals(usage))
{
arrival = new Stop(location, false, plannedTime.isSet(Calendar.HOUR_OF_DAY) ? plannedTime.getTime()
: predictedTime.getTime(), predictedTime.isSet(Calendar.HOUR_OF_DAY) ? predictedTime.getTime() : null, position,
null);
lastArrivalLocation = location;
}
else
{
throw new IllegalStateException("unknown usage: " + usage);
}
}
XmlPullUtil.exit(pp, "ps");
final boolean isRealtime = requireValueTag(pp, "realtime").equals("1");
final LineDestination lineDestination = parseMobileM(pp, false);
final List<Point> path;
if (XmlPullUtil.test(pp, "pt"))
path = processCoordinateStrings(pp, "pt");
else
path = null;
XmlPullUtil.require(pp, "pss");
final List<Stop> intermediateStops;
if (!pp.isEmptyElementTag())
{
XmlPullUtil.enter(pp, "pss");
intermediateStops = new LinkedList<Stop>();
while (XmlPullUtil.test(pp, "s"))
{
plannedTime.clear();
predictedTime.clear();
final String s = requireValueTag(pp, "s");
final String[] intermediateParts = s.split(";");
final int id = Integer.parseInt(intermediateParts[0]);
if (id != departure.location.id && id != arrival.location.id)
{
final String name = normalizeLocationName(intermediateParts[1]);
if (!("0000-1".equals(intermediateParts[2]) && "000-1".equals(intermediateParts[3])))
{
ParserUtils.parseIsoDate(plannedTime, intermediateParts[2]);
ParserUtils.parseIsoTime(plannedTime, intermediateParts[3]);
if (isRealtime)
{
ParserUtils.parseIsoDate(predictedTime, intermediateParts[2]);
ParserUtils.parseIsoTime(predictedTime, intermediateParts[3]);
if (intermediateParts.length > 5)
{
final int delay = Integer.parseInt(intermediateParts[5]);
predictedTime.add(Calendar.MINUTE, delay);
}
}
}
final String coord = intermediateParts[4];
final Location location;
if (!"::".equals(coord))
{
final String[] coordParts = coord.split(":");
if (!"WGS84".equals(coordParts[2]))
throw new IllegalStateException("unknown map name: " + coordParts[2]);
final int lat = Math.round(Float.parseFloat(coordParts[1]));
final int lon = Math.round(Float.parseFloat(coordParts[0]));
location = new Location(LocationType.STATION, id, lat, lon, null, name);
}
else
{
location = new Location(LocationType.STATION, id, null, name);
}
final Stop stop = new Stop(location, false, plannedTime.isSet(Calendar.HOUR_OF_DAY) ? plannedTime.getTime()
: predictedTime.getTime(), predictedTime.isSet(Calendar.HOUR_OF_DAY) ? predictedTime.getTime() : null, null,
null);
intermediateStops.add(stop);
}
}
XmlPullUtil.exit(pp, "pss");
}
else
{
intermediateStops = null;
XmlPullUtil.next(pp);
}
XmlPullUtil.optSkip(pp, "interchange");
XmlPullUtil.requireSkip(pp, "ns");
// TODO messages
XmlPullUtil.exit(pp, "l");
if (lineDestination.line == Line.FOOTWAY)
{
legs.add(new Trip.Individual(Trip.Individual.Type.WALK, departure.location, departure.getDepartureTime(), arrival.location,
arrival.getArrivalTime(), path, 0));
}
else if (lineDestination.line == Line.SECURE_CONNECTION || lineDestination.line == Line.DO_NOT_CHANGE)
{
// ignore
}
else
{
legs.add(new Trip.Public(lineDestination.line, lineDestination.destination, departure, arrival, intermediateStops, path, null));
}
}
XmlPullUtil.exit(pp, "ls");
XmlPullUtil.require(pp, "tcs");
final List<Fare> fares;
if (!pp.isEmptyElementTag())
{
XmlPullUtil.enter(pp, "tcs");
fares = new ArrayList<Fare>(2);
while (XmlPullUtil.test(pp, "tc"))
{
XmlPullUtil.enter(pp, "tc");
// TODO fares
XmlPullUtil.exit(pp, "tc");
}
XmlPullUtil.exit(pp, "tcs");
}
else
{
fares = null;
XmlPullUtil.next(pp);
}
final Trip trip = new Trip(tripId, firstDepartureLocation, lastArrivalLocation, legs, fares, null, numChanges);
trips.add(trip);
XmlPullUtil.exit(pp, "tp");
}
XmlPullUtil.exit(pp, "ts");
}
if (trips.size() > 0)
{
final String[] context = (String[]) header.context;
return new QueryTripsResult(header, uri, from, via, to, new Context(commandLink(context[0], context[1])), trips);
}
else
{
return new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS);
}
}
private List<Point> processItdPathCoordinates(final XmlPullParser pp) throws XmlPullParserException, IOException
{
final List<Point> path = new LinkedList<Point>();
XmlPullUtil.enter(pp, "itdPathCoordinates");
XmlPullUtil.enter(pp, "coordEllipsoid");
@ -2272,19 +3017,33 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider
if (!"GEO_DECIMAL".equals(type))
throw new IllegalStateException("unknown type: " + type);
XmlPullUtil.enter(pp, "itdCoordinateString");
for (final String coordStr : pp.getText().split(" +"))
{
final String[] coordsStr = coordStr.split(",");
path.add(new Point(Math.round(Float.parseFloat(coordsStr[1])), Math.round(Float.parseFloat(coordsStr[0]))));
}
XmlPullUtil.exit(pp, "itdCoordinateString");
final List<Point> path = processCoordinateStrings(pp, "itdCoordinateString");
XmlPullUtil.exit(pp, "itdPathCoordinates");
return path;
}
private List<Point> processCoordinateStrings(final XmlPullParser pp, final String tag) throws XmlPullParserException, IOException
{
final List<Point> path = new LinkedList<Point>();
final String value = requireValueTag(pp, tag);
for (final String coordStr : value.split(" +"))
path.add(coordStrToPoint(coordStr));
return path;
}
private Point coordStrToPoint(final String coordStr)
{
if (coordStr == null)
return null;
final String[] parts = coordStr.split(",");
return new Point(Math.round(Float.parseFloat(parts[1])), Math.round(Float.parseFloat(parts[0])));
}
private Fare processItdGenericTicketGroup(final XmlPullParser pp, final String net, final Currency currency) throws XmlPullParserException,
IOException
{
@ -2491,4 +3250,78 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider
return header;
}
private ResultHeader enterEfa(final XmlPullParser pp) throws XmlPullParserException, IOException
{
if (pp.getEventType() != XmlPullParser.START_DOCUMENT)
throw new IllegalStateException("start of document expected");
pp.next();
XmlPullUtil.enter(pp, "efa");
XmlPullUtil.enter(pp, "now");
final String now = pp.getText();
final Calendar serverTime = new GregorianCalendar(timeZone());
ParserUtils.parseIsoDate(serverTime, now.substring(0, 10));
ParserUtils.parseEuropeanTime(serverTime, now.substring(11));
XmlPullUtil.exit(pp, "now");
final Map<String, String> params = processPas(pp);
final String sessionId = params.get("sessionID");
final String requestId = params.get("requestID");
final ResultHeader header = new ResultHeader(SERVER_PRODUCT, null, serverTime.getTimeInMillis(), new String[] { sessionId, requestId });
return header;
}
private Map<String, String> processPas(final XmlPullParser pp) throws XmlPullParserException, IOException
{
final Map<String, String> params = new HashMap<String, String>();
XmlPullUtil.enter(pp, "pas");
while (XmlPullUtil.test(pp, "pa"))
{
XmlPullUtil.enter(pp, "pa");
final String name = requireValueTag(pp, "n");
final String value = requireValueTag(pp, "v");
params.put(name, value);
XmlPullUtil.exit(pp, "pa");
}
XmlPullUtil.exit(pp, "pas");
return params;
}
private String optValueTag(final XmlPullParser pp, final String name) throws XmlPullParserException, IOException
{
if (XmlPullUtil.test(pp, name))
{
if (!pp.isEmptyElementTag())
{
return requireValueTag(pp, name);
}
else
{
pp.next();
return null;
}
}
else
{
return null;
}
}
private String requireValueTag(final XmlPullParser pp, final String name) throws XmlPullParserException, IOException
{
XmlPullUtil.enter(pp, name);
final String value = pp.getText();
XmlPullUtil.exit(pp, name);
return value != null ? value.trim() : null;
}
}

View file

@ -17,17 +17,37 @@
package de.schildbach.pte;
import java.io.IOException;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Set;
import de.schildbach.pte.dto.Location;
import de.schildbach.pte.dto.LocationType;
import de.schildbach.pte.dto.NearbyStationsResult;
import de.schildbach.pte.dto.Product;
import de.schildbach.pte.dto.QueryDeparturesResult;
import de.schildbach.pte.dto.QueryTripsContext;
import de.schildbach.pte.dto.QueryTripsResult;
/**
* @author Andreas Schildbach
*/
public class BayernProvider extends AbstractEfaProvider
{
public static final NetworkId NETWORK_ID = NetworkId.BAYERN;
private final static String API_BASE = "http://vm-bayern-fahrplan03.defas-fgi.de:81/standard/";
private final static String API_BASE = "http://mobile.defas-fgi.de/beg/";
private static final String DEPARTURE_MONITOR_ENDPOINT = "XML_DM_REQUEST";
private static final String TRIP_ENDPOINT = "XML_TRIP_REQUEST2";
private static final String STOP_FINDER_ENDPOINT = "XML_STOPFINDER_REQUEST";
public BayernProvider()
{
super(API_BASE);
super(API_BASE, DEPARTURE_MONITOR_ENDPOINT, TRIP_ENDPOINT, STOP_FINDER_ENDPOINT, null);
setRequestUrlEncoding(UTF_8);
setIncludeRegionId(false);
}
public NetworkId id()
@ -43,4 +63,42 @@ public class BayernProvider extends AbstractEfaProvider
return false;
}
@Override
public NearbyStationsResult queryNearbyStations(final Location location, final int maxDistance, final int maxStations) throws IOException
{
if (location.hasLocation())
return mobileCoordRequest(location.lat, location.lon, maxDistance, maxStations);
if (location.type != LocationType.STATION)
throw new IllegalArgumentException("cannot handle: " + location.type);
throw new IllegalArgumentException("station"); // TODO
}
@Override
public QueryDeparturesResult queryDepartures(final int stationId, final int maxDepartures, final boolean equivs) throws IOException
{
return queryDeparturesMobile(stationId, maxDepartures, equivs);
}
@Override
public List<Location> autocompleteStations(final CharSequence constraint) throws IOException
{
return mobileStopfinderRequest(new Location(LocationType.ANY, 0, null, constraint.toString()));
}
@Override
public QueryTripsResult queryTrips(final Location from, final Location via, final Location to, final Date date, final boolean dep,
final int numTrips, final Collection<Product> products, final WalkSpeed walkSpeed, final Accessibility accessibility,
final Set<Option> options) throws IOException
{
return queryTripsMobile(from, via, to, date, dep, numTrips, products, walkSpeed, accessibility, options);
}
@Override
public QueryTripsResult queryMoreTrips(final QueryTripsContext contextObj, final boolean later, final int numTrips) throws IOException
{
return queryMoreTripsMobile(contextObj, later, numTrips);
}
}

View file

@ -41,6 +41,10 @@ public final class Line implements Serializable, Comparable<Line>
private static final String PRODUCT_ORDER = "IRSUTBPFC?";
public static final Line FOOTWAY = new Line(null, null, null);
public static final Line SECURE_CONNECTION = new Line(null, null, null);
public static final Line DO_NOT_CHANGE = new Line(null, null, null);
public Line(final String id, final String label, final Style style)
{
this(id, label, style, null, null);

View file

@ -413,7 +413,7 @@ public final class ParserUtils
return builder.toString();
}
private static final Pattern P_ISO_DATE = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})");
private static final Pattern P_ISO_DATE = Pattern.compile("(\\d{4})-?(\\d{2})-?(\\d{2})");
private static final Pattern P_ISO_DATE_REVERSE = Pattern.compile("(\\d{2})-(\\d{2})-(\\d{4})");
public static final void parseIsoDate(final Calendar calendar, final CharSequence str)
@ -437,7 +437,21 @@ public final class ParserUtils
}
throw new RuntimeException("cannot parse: '" + str + "'");
}
private static final Pattern P_ISO_TIME = Pattern.compile("(\\d{2})-?(\\d{2})");
public static final void parseIsoTime(final Calendar calendar, final CharSequence str)
{
final Matcher mIso = P_ISO_TIME.matcher(str);
if (mIso.matches())
{
calendar.set(Calendar.HOUR_OF_DAY, Integer.parseInt(mIso.group(1)));
calendar.set(Calendar.MINUTE, Integer.parseInt(mIso.group(2)));
return;
}
throw new RuntimeException("cannot parse: '" + str + "'");
}
private static final Pattern P_GERMAN_DATE = Pattern.compile("(\\d{2})[\\./-](\\d{2})[\\./-](\\d{2,4})");

View file

@ -21,15 +21,6 @@ public final class XmlPullUtil
{
public static final String XSI_NS = "http://www.w3.org/2001/XMLSchema-instance";
/**
* directly jumps forward to start tag, ignoring any structure
*/
public static void jump(final XmlPullParser pp, final String tagName) throws XmlPullParserException, IOException
{
if (!jumpToStartTag(pp, null, tagName))
throw new IllegalStateException("cannot find <" + tagName + " />");
}
public static void require(final XmlPullParser pp, final String tagName) throws XmlPullParserException, IOException
{
pp.require(XmlPullParser.START_TAG, null, tagName);
@ -91,6 +82,27 @@ public final class XmlPullUtil
return pp.getEventType() == XmlPullParser.START_TAG && pp.getName().equals(tagName);
}
public static void requireSkip(final XmlPullParser pp, final String tagName) throws XmlPullParserException, IOException
{
require(pp, tagName);
if (!pp.isEmptyElementTag())
{
enter(pp);
exit(pp);
}
else
{
next(pp);
}
}
public static void optSkip(final XmlPullParser pp, final String tagName) throws XmlPullParserException, IOException
{
if (test(pp, tagName))
requireSkip(pp, tagName);
}
public static void next(final XmlPullParser pp) throws XmlPullParserException, IOException
{
skipSubTree(pp);

View file

@ -17,6 +17,8 @@
package de.schildbach.pte.live;
import static org.junit.Assert.assertEquals;
import java.util.Date;
import java.util.List;
@ -61,9 +63,14 @@ public class BayernProviderLiveTest extends AbstractProviderLiveTest
@Test
public void queryDepartures() throws Exception
{
final QueryDeparturesResult result = provider.queryDepartures(80001083, 0, false);
final QueryDeparturesResult munichOstbahnhof = provider.queryDepartures(80000793, 0, false);
print(munichOstbahnhof);
print(result);
final QueryDeparturesResult munichHauptbahnhof = provider.queryDepartures(80000689, 0, false);
print(munichHauptbahnhof);
final QueryDeparturesResult nurembergHauptbahnhof = provider.queryDepartures(80001020, 0, false);
print(nurembergHauptbahnhof);
}
@Test
@ -82,6 +89,27 @@ public class BayernProviderLiveTest extends AbstractProviderLiveTest
print(autocompletes);
}
@Test
public void autocompleteAddress() throws Exception
{
final List<Location> autocompletes = provider.autocompleteStations("München, Friedenstraße 2");
print(autocompletes);
}
@Test
public void autocompleteLocal() throws Exception
{
final List<Location> autocompleteRegensburg = provider.autocompleteStations("Regensburg");
assertEquals(80001083, autocompleteRegensburg.iterator().next().id);
final List<Location> autocompleteMunich = provider.autocompleteStations("München");
assertEquals(80000689, autocompleteMunich.iterator().next().id);
final List<Location> autocompleteNuremberg = provider.autocompleteStations("Nürnberg");
assertEquals(80001020, autocompleteNuremberg.iterator().next().id);
}
@Test
public void shortTrip() throws Exception
{
@ -114,8 +142,8 @@ public class BayernProviderLiveTest extends AbstractProviderLiveTest
@Test
public void tripBetweenCoordinateAndStation() throws Exception
{
final QueryTripsResult result = queryTrips(new Location(LocationType.ADDRESS, 0, 48238341, 11478230), null, new Location(LocationType.ANY, 0,
null, "Ostbahnhof"), new Date(), true, Product.ALL, WalkSpeed.NORMAL, Accessibility.NEUTRAL);
final QueryTripsResult result = queryTrips(new Location(LocationType.ADDRESS, 0, 48238341, 11478230), null, new Location(
LocationType.STATION, 80000793, "München", "Ostbahnhof"), new Date(), true, Product.ALL, WalkSpeed.NORMAL, Accessibility.NEUTRAL);
System.out.println(result);
final QueryTripsResult laterResult = queryMoreTrips(result.context, true);
System.out.println(laterResult);
@ -134,7 +162,7 @@ public class BayernProviderLiveTest extends AbstractProviderLiveTest
@Test
public void tripBetweenStationAndAddress() throws Exception
{
final QueryTripsResult result = queryTrips(new Location(LocationType.STATION, 1220, null, "Josephsburg"), null, new Location(
final QueryTripsResult result = queryTrips(new Location(LocationType.STATION, 1001220, null, "Josephsburg"), null, new Location(
LocationType.ADDRESS, 0, 48188018, 11574239, null, "München Frankfurter Ring 35"), new Date(), true, Product.ALL, WalkSpeed.NORMAL,
Accessibility.NEUTRAL);
System.out.println(result);