diff --git a/enabler/src/de/schildbach/pte/AbstractEfaProvider.java b/enabler/src/de/schildbach/pte/AbstractEfaProvider.java index 2b95a6aa..107fcb5d 100644 --- a/enabler/src/de/schildbach/pte/AbstractEfaProvider.java +++ b/enabler/src/de/schildbach/pte/AbstractEfaProvider.java @@ -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 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 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 + { + public final Location location; + public final int quality; + + public LocationAndQuality(final Location location, final int quality) + { + this.location = location; + this.quality = quality; + } + + public int compareTo(final LocationAndQuality other) + { + // prefer quality + if (this.quality > other.quality) + return -1; + else if (this.quality < other.quality) + return 1; + + // prefer stations + final int compareLocationType = this.location.type.compareTo(other.location.type); + if (compareLocationType != 0) + return compareLocationType; + + return 0; + } + + @Override + public boolean equals(final Object o) + { + if (o == this) + return true; + if (!(o instanceof LocationAndQuality)) + return false; + final LocationAndQuality other = (LocationAndQuality) o; + return location.equals(other.location); + } + + @Override + public int hashCode() + { + return location.hashCode(); + } + + @Override + public String toString() + { + return quality + ":" + location; + } + } + + protected List mobileStopfinderRequest(final Location constraint) throws IOException + { + final StringBuilder parameters = stopfinderRequestParameters(constraint); + + final StringBuilder uri = new StringBuilder(stopFinderEndpoint); + if (!httpPost) + uri.append(parameters); + + // System.out.println(uri); + // System.out.println(parameters); + + InputStream is = null; + try + { + is = ParserUtils.scrapeInputStream(uri.toString(), httpPost ? parameters.substring(1) : null, null, httpReferer, null, 3); + + final XmlPullParser pp = parserFactory.newPullParser(); + pp.setInput(is, null); + enterEfa(pp); + + final List locations = new ArrayList(); + + XmlPullUtil.enter(pp, "sf"); + + while (XmlPullUtil.test(pp, "p")) + { + XmlPullUtil.enter(pp, "p"); + + final String name = normalizeLocationName(requireValueTag(pp, "n")); + final String u = requireValueTag(pp, "u"); + if (!"sf".equals(u)) + throw new RuntimeException("unknown usage: " + u); + final String ty = requireValueTag(pp, "ty"); + final LocationType type; + if ("stop".equals(ty)) + type = LocationType.STATION; + else if ("poi".equals(ty)) + type = LocationType.POI; + else if ("loc".equals(ty)) + type = LocationType.ADDRESS; + else if ("street".equals(ty)) + type = LocationType.ADDRESS; + else if ("singlehouse".equals(ty)) + type = LocationType.ADDRESS; + else + throw new RuntimeException("unknown type: " + ty); + + XmlPullUtil.enter(pp, "r"); + + final int id = Integer.parseInt(requireValueTag(pp, "id")); + requireValueTag(pp, "omc"); + final String place = normalizeLocationName(optValueTag(pp, "pc")); + requireValueTag(pp, "pid"); + final Point coord = coordStrToPoint(optValueTag(pp, "c")); + + XmlPullUtil.exit(pp, "r"); + + final String qal = optValueTag(pp, "qal"); + final int quality = qal != null ? Integer.parseInt(qal) : 0; + + XmlPullUtil.exit(pp, "p"); + + final Location location = new Location(type, type == LocationType.STATION ? id : 0, coord != null ? coord.lat : 0, + coord != null ? coord.lon : 0, place, name); + final LocationAndQuality locationAndQuality = new LocationAndQuality(location, quality); + locations.add(locationAndQuality); + } + + XmlPullUtil.exit(pp, "sf"); + + Collections.sort(locations); + + final List results = new ArrayList(locations.size()); + for (final LocationAndQuality location : locations) + results.add(location.location); + + return results; + } + catch (final XmlPullParserException x) + { + throw new ParserException(x); + } + finally + { + if (is != null) + is.close(); + } + } + + private StringBuilder xmlCoordRequestParameters(final int lat, final int lon, final int maxDistance, final int maxStations) { final StringBuilder parameters = new StringBuilder(); appendCommonRequestParams(parameters, "XML"); @@ -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 stations = new ArrayList(); + + if (XmlPullUtil.test(pp, "pis")) + { + XmlPullUtil.enter(pp, "pis"); + + while (XmlPullUtil.test(pp, "pi")) + { + XmlPullUtil.enter(pp, "pi"); + + final String name = normalizeLocationName(requireValueTag(pp, "de")); + final String type = requireValueTag(pp, "ty"); + if (!"STOP".equals(type)) + throw new RuntimeException("unknown type"); + + final int id = Integer.parseInt(requireValueTag(pp, "id")); + requireValueTag(pp, "omc"); + requireValueTag(pp, "pid"); + final String place = normalizeLocationName(requireValueTag(pp, "locality")); + requireValueTag(pp, "layer"); + requireValueTag(pp, "gisID"); + requireValueTag(pp, "ds"); + final Point coord = coordStrToPoint(requireValueTag(pp, "c")); + + stations.add(new Location(LocationType.STATION, id, coord.lat, coord.lon, place, name)); + + XmlPullUtil.exit(pp, "pi"); + } + + XmlPullUtil.exit(pp, "pis"); + } + + XmlPullUtil.exit(pp, "ci"); + + return new NearbyStationsResult(header, stations); + } + catch (final XmlPullParserException x) + { + throw new ParserException(x); + } + finally + { + if (is != null) + is.close(); + } + } + public List autocompleteStations(final CharSequence constraint) throws IOException { return jsonStopfinderRequest(new Location(LocationType.ANY, 0, null, constraint.toString())); @@ -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( + maxDepartures), null); + result.stationDepartures.add(stationDepartures); + } + + stationDepartures.departures.add(new Departure(plannedDepartureTime.getTime(), + predictedDepartureTime.isSet(Calendar.HOUR_OF_DAY) ? predictedDepartureTime.getTime() : null, lineDestination.line, + position, lineDestination.destination, null, null)); + + XmlPullUtil.exit(pp, "dp"); + } + + XmlPullUtil.exit(pp, "dps"); + + return result; + } + else + { + return new QueryDeparturesResult(header, QueryDeparturesResult.Status.INVALID_STATION); + } + } + catch (final XmlPullParserException x) + { + throw new ParserException(x); + } + finally + { + if (is != null) + is.close(); + } + } + + private static final Pattern P_MOBILE_M_SYMBOL = Pattern.compile("([^\\s]*)\\s+([^\\s]*)"); + + private LineDestination parseMobileM(final XmlPullParser pp, final boolean tyOrCo) throws XmlPullParserException, IOException + { + XmlPullUtil.enter(pp, "m"); + + final String n = optValueTag(pp, "n"); + final String productNu = requireValueTag(pp, "nu"); + final String ty = requireValueTag(pp, "ty"); + + final Line line; + final Location destination; + if ("100".equals(ty) || "99".equals(ty)) + { + destination = null; + line = Line.FOOTWAY; + } + else if ("98".equals(ty)) + { + destination = null; + line = Line.SECURE_CONNECTION; + } + else if ("97".equals(ty)) + { + destination = null; + line = Line.DO_NOT_CHANGE; + } + else + { + final String co = requireValueTag(pp, "co"); + final String productType = tyOrCo ? ty : co; + final String destinationName = normalizeLocationName(requireValueTag(pp, "des")); + destination = new Location(LocationType.ANY, 0, null, destinationName); + optValueTag(pp, "dy"); + final String de = optValueTag(pp, "de"); + final String productName = n != null ? n : de; + final String lineId = parseMobileDv(pp); + + final String symbol = productNu.endsWith(" " + productName) ? productNu.substring(0, productNu.length() - productName.length() - 1) + : productNu; + final String trainType; + final String trainNum; + final Matcher mSymbol = P_MOBILE_M_SYMBOL.matcher(symbol); + if (mSymbol.matches()) + { + trainType = mSymbol.group(1); + trainNum = mSymbol.group(2); + } + else + { + trainType = null; + trainNum = null; + } + + final String lineLabel = parseLine(productType, symbol, symbol, null, trainType, trainNum, productName); + line = new Line(lineId, lineLabel, lineStyle(lineLabel)); + } + + XmlPullUtil.exit(pp, "m"); + + return new LineDestination(line, destination); + } + + private String parseMobileDv(final XmlPullParser pp) throws XmlPullParserException, IOException + { + XmlPullUtil.enter(pp, "dv"); + optValueTag(pp, "branch"); + final String lineIdLi = requireValueTag(pp, "li"); + final String lineIdSu = requireValueTag(pp, "su"); + final String lineIdPr = requireValueTag(pp, "pr"); + final String lineIdDct = requireValueTag(pp, "dct"); + final String lineIdNe = requireValueTag(pp, "ne"); + XmlPullUtil.exit(pp, "dv"); + + return lineIdNe + ":" + lineIdLi + ":" + lineIdSu + ":" + lineIdDct + ":" + lineIdPr; + } + + private void parseMobileSt(final XmlPullParser pp, final Calendar plannedDepartureTime, final Calendar predictedDepartureTime) + throws XmlPullParserException, IOException + { + XmlPullUtil.enter(pp, "st"); + + plannedDepartureTime.clear(); + ParserUtils.parseIsoDate(plannedDepartureTime, requireValueTag(pp, "da")); + ParserUtils.parseIsoTime(plannedDepartureTime, requireValueTag(pp, "t")); + + predictedDepartureTime.clear(); + if (XmlPullUtil.test(pp, "rda")) + { + ParserUtils.parseIsoDate(predictedDepartureTime, requireValueTag(pp, "rda")); + ParserUtils.parseIsoTime(predictedDepartureTime, requireValueTag(pp, "rt")); + } + + XmlPullUtil.exit(pp, "st"); + } + private StationDepartures findStationDepartures(final List stationDepartures, final int id) { for (final StationDepartures stationDeparture : stationDepartures) @@ -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 products, final WalkSpeed walkSpeed, final Accessibility accessibility, + final Set