diff --git a/src/de/schildbach/pte/AbstractHafasProvider.java b/src/de/schildbach/pte/AbstractHafasProvider.java index 49b0c794..cfc97e21 100644 --- a/src/de/schildbach/pte/AbstractHafasProvider.java +++ b/src/de/schildbach/pte/AbstractHafasProvider.java @@ -922,7 +922,7 @@ public abstract class AbstractHafasProvider implements NetworkProvider LINES.put('?', new int[] { Color.DKGRAY, Color.WHITE }); } - public final int[] lineColors(final String line) + public int[] lineColors(final String line) { if (line.length() == 0) return null; diff --git a/src/de/schildbach/pte/BvgProvider.java b/src/de/schildbach/pte/BvgProvider.java index c6eb2c5a..74c09ab1 100644 --- a/src/de/schildbach/pte/BvgProvider.java +++ b/src/de/schildbach/pte/BvgProvider.java @@ -42,6 +42,7 @@ import de.schildbach.pte.dto.NearbyStationsResult; import de.schildbach.pte.dto.QueryConnectionsResult; import de.schildbach.pte.dto.QueryDeparturesResult; import de.schildbach.pte.dto.QueryDeparturesResult.Status; +import de.schildbach.pte.dto.Station; import de.schildbach.pte.exception.SessionExpiredException; import de.schildbach.pte.util.Color; import de.schildbach.pte.util.ParserUtils; @@ -49,7 +50,7 @@ import de.schildbach.pte.util.ParserUtils; /** * @author Andreas Schildbach */ -public final class BvgProvider implements NetworkProvider +public final class BvgProvider extends AbstractHafasProvider { public static final String NETWORK_ID = "mobil.bvg.de"; @@ -57,6 +58,12 @@ public final class BvgProvider implements NetworkProvider private static final long PARSER_DAY_ROLLDOWN_THRESHOLD_MS = 6 * 60 * 60 * 1000; private static final String BVG_BASE_URL = "http://mobil.bvg.de"; + private static final String API_BASE = "http://mobil.bvg.de/Fahrinfo/bin/"; + + public BvgProvider() + { + super(null, null); + } public boolean hasCapabilities(final Capability... capabilities) { @@ -72,6 +79,7 @@ public final class BvgProvider implements NetworkProvider private static final Pattern P_MULTI_NAME = Pattern.compile("\\s*(.*?)\\s*", Pattern.DOTALL); + @Override public List autocompleteStations(final CharSequence constraint) throws IOException { final List results = new ArrayList(); @@ -93,10 +101,72 @@ public final class BvgProvider implements NetworkProvider return results; } + private final String NEARBY_URI = API_BASE + "stboard.bin/dn?distance=50&near&input=%s"; + + @Override + protected String nearbyStationUri(final String stationId) + { + return String.format(NEARBY_URI, ParserUtils.urlEncode(stationId)); + } + + private final static Pattern P_NEARBY_OWN = Pattern + .compile("/Stadtplan/index.*?location=(\\d+),HST,WGS84,(-?\\d+\\.\\d+),(-?\\d+\\.\\d+)&label=([^\"]*)\""); + private final static Pattern P_NEARBY_PAGE = Pattern.compile("(.*?)", Pattern.DOTALL); + private final static Pattern P_NEARBY_COARSE = Pattern.compile("(.*?)", Pattern.DOTALL); + private final static Pattern P_NEARBY_FINE_LOCATION = Pattern.compile("input=(\\d+)&[^\"]*\">([^<]*)<"); + + @Override public NearbyStationsResult nearbyStations(final String stationId, final int lat, final int lon, final int maxDistance, final int maxStations) throws IOException { - throw new UnsupportedOperationException(); + if (stationId == null) + throw new IllegalArgumentException("stationId must be given"); + + final List stations = new ArrayList(); + + final String uri = nearbyStationUri(stationId); + final CharSequence page = ParserUtils.scrape(uri); + + final Matcher mOwn = P_NEARBY_OWN.matcher(page); + if (mOwn.find()) + { + final int parsedId = Integer.parseInt(mOwn.group(1)); + final int parsedLon = (int) (Float.parseFloat(mOwn.group(2)) * 1E6); + final int parsedLat = (int) (Float.parseFloat(mOwn.group(3)) * 1E6); + final String parsedName = ParserUtils.urlDecode(mOwn.group(4), "ISO-8859-1"); + stations.add(new Station(parsedId, parsedName, parsedLat, parsedLon, 0, null, null)); + } + + final Matcher mPage = P_NEARBY_PAGE.matcher(page); + if (mPage.find()) + { + final Matcher mCoarse = P_NEARBY_COARSE.matcher(mPage.group(1)); + + while (mCoarse.find()) + { + final Matcher mFineLocation = P_NEARBY_FINE_LOCATION.matcher(mCoarse.group(1)); + + if (mFineLocation.find()) + { + final int parsedId = Integer.parseInt(mFineLocation.group(1)); + final String parsedName = ParserUtils.resolveEntities(mFineLocation.group(2)); + stations.add(new Station(parsedId, parsedName, 0, 0, 0, null, null)); + } + else + { + throw new IllegalArgumentException("cannot parse '" + mCoarse.group(1) + "' on " + uri); + } + } + + if (maxStations == 0 || maxStations >= stations.size()) + return new NearbyStationsResult(stations); + else + return new NearbyStationsResult(stations.subList(0, maxStations)); + } + else + { + throw new IllegalArgumentException("cannot parse '" + page + "' on " + uri); + } } public static final String STATION_URL_CONNECTION = "http://mobil.bvg.de/Fahrinfo/bin/query.bin/dox"; @@ -183,6 +253,7 @@ public final class BvgProvider implements NetworkProvider private static final Pattern P_CHECK_CONNECTIONS_ERROR = Pattern .compile("(zu dicht beieinander|mehrfach vorhanden oder identisch)|(keine geeigneten Haltestellen)|(keine Verbindung gefunden)|(derzeit nur Auskünfte vom)|(zwischenzeitlich nicht mehr gespeichert)"); + @Override public QueryConnectionsResult queryConnections(final Location from, final Location via, final Location to, final Date date, final boolean dep, final String products, final WalkSpeed walkSpeed) throws IOException { @@ -216,6 +287,7 @@ public final class BvgProvider implements NetworkProvider } } + @Override public QueryConnectionsResult queryMoreConnections(final String uri) throws IOException { final CharSequence page = ParserUtils.scrape(uri); @@ -321,6 +393,7 @@ public final class BvgProvider implements NetworkProvider + "(.*?)|([^<]*)|([^<]*)).*?" // arrival + ").*?", Pattern.DOTALL); + @Override public GetConnectionDetailsResult getConnectionDetails(final String uri) throws IOException { final CharSequence page = ParserUtils.scrape(uri); @@ -648,15 +721,16 @@ public final class BvgProvider implements NetworkProvider throw new IllegalStateException("cannot normalize line " + line); } + @Override + protected char normalizeType(final String type) + { + throw new UnsupportedOperationException(); + } + private static final Map LINES = new HashMap(); static { - LINES.put("I", new int[] { Color.WHITE, Color.RED, Color.RED }); // generic - LINES.put("R", new int[] { Color.WHITE, Color.RED, Color.RED }); // generic - LINES.put("S", new int[] { Color.parseColor("#006e34"), Color.WHITE }); // generic - LINES.put("U", new int[] { Color.parseColor("#003090"), Color.WHITE }); // generic - LINES.put("SS1", new int[] { Color.rgb(221, 77, 174), Color.WHITE }); LINES.put("SS2", new int[] { Color.rgb(16, 132, 73), Color.WHITE }); LINES.put("SS25", new int[] { Color.rgb(16, 132, 73), Color.WHITE }); @@ -764,10 +838,13 @@ public final class BvgProvider implements NetworkProvider LINES.put("RRB93", new int[] { Color.parseColor("#A7653F"), Color.WHITE }); } + @Override public int[] lineColors(final String line) { - if (line.length() == 0) - return null; - return LINES.get(line); + final int[] lineColors = LINES.get(line); + if (lineColors != null) + return lineColors; + else + return super.lineColors(line); } } diff --git a/src/de/schildbach/pte/util/ParserUtils.java b/src/de/schildbach/pte/util/ParserUtils.java index 5e2d2c3f..317d2bf4 100644 --- a/src/de/schildbach/pte/util/ParserUtils.java +++ b/src/de/schildbach/pte/util/ParserUtils.java @@ -27,6 +27,7 @@ import java.io.Writer; import java.net.HttpURLConnection; import java.net.SocketTimeoutException; import java.net.URL; +import java.net.URLDecoder; import java.net.URLEncoder; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -360,6 +361,18 @@ public final class ParserUtils } } + public static String urlDecode(final String str, final String enc) + { + try + { + return URLDecoder.decode(str, enc); + } + catch (final UnsupportedEncodingException x) + { + throw new RuntimeException(x); + } + } + public static T selectNotNull(final T... groups) { T selected = null; diff --git a/test/de/schildbach/pte/live/BvgProviderLiveTest.java b/test/de/schildbach/pte/live/BvgProviderLiveTest.java new file mode 100644 index 00000000..d6ae61c1 --- /dev/null +++ b/test/de/schildbach/pte/live/BvgProviderLiveTest.java @@ -0,0 +1,46 @@ +/* + * Copyright 2010 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.live; + +import org.junit.Test; + +import de.schildbach.pte.BvgProvider; +import de.schildbach.pte.dto.NearbyStationsResult; +import de.schildbach.pte.dto.QueryDeparturesResult; + +/** + * @author Andreas Schildbach + */ +public class BvgProviderLiveTest +{ + private BvgProvider provider = new BvgProvider(); + + @Test + public void nearbyStation() throws Exception + { + final NearbyStationsResult result = provider.nearbyStations("9415052", 0, 0, 0, 0); + System.out.println(result.stations.size() + " " + result.stations); + } + + @Test + public void departures() throws Exception + { + final QueryDeparturesResult queryDepartures = provider.queryDepartures("309557", 0); + System.out.println(queryDepartures.departures); + } +}