Migrate Kiel, Lübeck & Schleswig-Holstein to use XML station board for querying departures.

This commit is contained in:
Andreas Schildbach 2015-02-01 23:49:20 +01:00
parent b031cfd2a8
commit 2ab38329c5
3 changed files with 12 additions and 176 deletions

View file

@ -2302,7 +2302,7 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider
if (location.hasLocation())
return nearbyLocationsByCoordinate(types, location.lat, location.lon, maxDistance, maxLocations);
else if (location.type == LocationType.STATION && location.hasId())
return nearbyStationsById(location.id);
return nearbyStationsById(location.id, maxDistance);
else
throw new IllegalArgumentException("cannot handle: " + location);
}
@ -2330,7 +2330,7 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider
}
}
protected final NearbyLocationsResult nearbyStationsById(final String id) throws IOException
protected NearbyLocationsResult nearbyStationsById(final String id, final int maxDistance) throws IOException
{
final StringBuilder uri = new StringBuilder(stationBoardEndpoint);
appendXmlNearbyStationsParameters(uri, id);

View file

@ -18,30 +18,15 @@
package de.schildbach.pte;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.common.base.Charsets;
import de.schildbach.pte.dto.Departure;
import de.schildbach.pte.dto.Line;
import de.schildbach.pte.dto.Location;
import de.schildbach.pte.dto.LocationType;
import de.schildbach.pte.dto.NearbyLocationsResult;
import de.schildbach.pte.dto.Position;
import de.schildbach.pte.dto.Product;
import de.schildbach.pte.dto.QueryDeparturesResult;
import de.schildbach.pte.dto.QueryDeparturesResult.Status;
import de.schildbach.pte.dto.ResultHeader;
import de.schildbach.pte.dto.StationDepartures;
import de.schildbach.pte.util.ParserUtils;
/**
* @author Andreas Schildbach
@ -51,8 +36,6 @@ public class ShProvider extends AbstractHafasProvider
public static final NetworkId NETWORK_ID = NetworkId.SH;
private static final String API_BASE = "http://nah.sh.hafas.de/bin/";
private static final long PARSER_DAY_ROLLOVER_THRESHOLD_MS = 12 * 60 * 60 * 1000;
public ShProvider()
{
super(API_BASE + "stboard.exe/dn", API_BASE + "ajax-getstop.exe/dn", API_BASE + "query.exe/dn", 10, Charsets.UTF_8);
@ -175,169 +158,18 @@ public class ShProvider extends AbstractHafasProvider
final int maxLocations) throws IOException
{
if (location.type == LocationType.STATION && location.hasId())
{
final StringBuilder uri = new StringBuilder(stationBoardEndpoint);
uri.append("?near=Anzeigen");
uri.append("&distance=").append(maxDistance != 0 ? maxDistance / 1000 : 50);
uri.append("&input=").append(normalizeStationId(location.id));
return htmlNearbyStations(uri.toString());
}
return nearbyStationsById(location.id, maxDistance);
else
{
throw new IllegalArgumentException("cannot handle: " + location);
}
}
private static final Pattern P_DEPARTURES_HEAD_COARSE = Pattern.compile(".*?" //
+ "(?:" //
+ "Bhf\\./Haltest\\.:</span>\\n<span class=\"output\">([^<]*)<.*?" // location
+ "Fahrplan:</span>\\n<span class=\"output\">\n" //
+ "(\\d{2}\\.\\d{2}\\.\\d{2})[^\n]*,\n" // date
+ "Abfahrt (\\d{1,2}:\\d{2}).*?" // time
+ "<table class=\"resultTable\"[^>]*>(.+?)</table>" // content
+ "|(verkehren an dieser Haltestelle keine)" //
+ "|(Eingabe kann nicht interpretiert)" //
+ "|(Verbindung zum Server konnte leider nicht hergestellt werden|kann vom Server derzeit leider nicht bearbeitet werden))" //
+ ".*?" //
, Pattern.DOTALL);
private static final Pattern P_DEPARTURES_COARSE = Pattern.compile("<tr class=\"(depboard-\\w*)\">(.*?)</tr>", Pattern.DOTALL);
private static final Pattern P_DEPARTURES_FINE = Pattern.compile("\n" //
+ "<td class=\"time\">(\\d{1,2}:\\d{2}).*?" // plannedTime
+ "<img class=\"product\" src=\"/hafas-res/[^\"]*?(\\w+)_pic\\.png\"[^>]*>\\s*([^<]*)<.*?" // type,line
+ "<a href=\"http://nah\\.sh\\.hafas\\.de/bin/stboard\\.exe/dn\\?input=(\\d+)&[^>]*>\n" // destinationId
+ "([^\n]*)\n.*?" // destination
+ "(?:<td class=\"center sepline top\">\n(" + ParserUtils.P_PLATFORM + ").*?)?" // position
, Pattern.DOTALL);
@Override
public QueryDeparturesResult queryDepartures(final String stationId, final Date time, final int maxDepartures, final boolean equivs)
throws IOException
protected NearbyLocationsResult nearbyStationsById(final String id, final int maxDistance) throws IOException
{
final ResultHeader header = new ResultHeader(SERVER_PRODUCT);
final QueryDeparturesResult result = new QueryDeparturesResult(header);
// scrape page
final StringBuilder uri = new StringBuilder(stationBoardEndpoint);
appendXmlStationBoardParameters(uri, time, stationId, maxDepartures, false, null);
final CharSequence page = ParserUtils.scrape(uri.toString());
// System.out.println(uri);
// System.out.println(page);
// parse page
final Matcher mHeadCoarse = P_DEPARTURES_HEAD_COARSE.matcher(page);
if (mHeadCoarse.matches())
{
// messages
if (mHeadCoarse.group(5) != null)
{
result.stationDepartures.add(new StationDepartures(new Location(LocationType.STATION, stationId),
Collections.<Departure> emptyList(), null));
return result;
}
else if (mHeadCoarse.group(6) != null)
return new QueryDeparturesResult(header, Status.INVALID_STATION);
else if (mHeadCoarse.group(7) != null)
return new QueryDeparturesResult(header, Status.SERVICE_DOWN);
final String[] placeAndName = splitStationName(ParserUtils.resolveEntities(mHeadCoarse.group(1)));
final Calendar currentTime = new GregorianCalendar(timeZone);
currentTime.clear();
ParserUtils.parseGermanDate(currentTime, mHeadCoarse.group(2));
ParserUtils.parseEuropeanTime(currentTime, mHeadCoarse.group(3));
final List<Departure> departures = new ArrayList<Departure>(8);
String oldZebra = null;
final Matcher mDepCoarse = P_DEPARTURES_COARSE.matcher(mHeadCoarse.group(4));
while (mDepCoarse.find())
{
final String zebra = mDepCoarse.group(1);
if (oldZebra != null && zebra.equals(oldZebra))
throw new IllegalArgumentException("missed row? last:" + zebra);
else
oldZebra = zebra;
final Matcher mDepFine = P_DEPARTURES_FINE.matcher(mDepCoarse.group(2));
if (mDepFine.matches())
{
final Calendar plannedTime = new GregorianCalendar(timeZone);
plannedTime.setTimeInMillis(currentTime.getTimeInMillis());
ParserUtils.parseEuropeanTime(plannedTime, mDepFine.group(1));
if (plannedTime.getTimeInMillis() - currentTime.getTimeInMillis() < -PARSER_DAY_ROLLOVER_THRESHOLD_MS)
plannedTime.add(Calendar.DAY_OF_MONTH, 1);
final String lineType = mDepFine.group(2);
final char product = intToProduct(Integer.parseInt(lineType));
final String lineStr = Character.toString(product) + normalizeLineName(mDepFine.group(3).trim());
final Line line = new Line(null, lineStr, lineStyle(null, lineStr));
final String destinationId = mDepFine.group(4);
final String destinationName = ParserUtils.resolveEntities(mDepFine.group(5));
final Location destination;
if (destinationId != null)
{
final String[] destinationPlaceAndName = splitStationName(destinationName);
destination = new Location(LocationType.STATION, destinationId, destinationPlaceAndName[0], destinationPlaceAndName[1]);
}
else
{
destination = new Location(LocationType.ANY, null, null, destinationName);
}
final Position position = parsePosition(ParserUtils.resolveEntities(mDepFine.group(6)));
final Departure dep = new Departure(plannedTime.getTime(), null, line, position, destination, null, null);
if (!departures.contains(dep))
departures.add(dep);
}
else
{
throw new IllegalArgumentException("cannot parse '" + mDepCoarse.group(2) + "' on " + stationId);
}
}
result.stationDepartures.add(new StationDepartures(new Location(LocationType.STATION, stationId, placeAndName[0], placeAndName[1]),
departures, null));
return result;
}
else
{
throw new IllegalArgumentException("cannot parse '" + page + "' on " + stationId);
}
}
@Override
protected char normalizeType(final String type)
{
final String ucType = type.toUpperCase();
if ("IXB".equals(ucType)) // ICE
return 'I';
if ("ECW".equals(ucType)) // EC
return 'I';
if ("DPF".equals(ucType)) // Hamburg-Koeln-Express
return 'I';
if ("RRT".equals(ucType)) // TGV
return 'I';
if ("ZRB".equals(ucType)) // Zahnradbahn
return 'R';
if ("KBS".equals(ucType))
return 'B';
if ("KB1".equals(ucType))
return 'B';
if ("KLB".equals(ucType))
return 'B';
final char t = super.normalizeType(type);
if (t != 0)
return t;
return 0;
uri.append("?near=Anzeigen");
uri.append("&distance=").append(maxDistance != 0 ? maxDistance / 1000 : 50);
uri.append("&input=").append(normalizeStationId(id));
return htmlNearbyStations(uri.toString());
}
}

View file

@ -92,6 +92,10 @@ public class ShProviderLiveTest extends AbstractProviderLiveTest
final QueryTripsResult result = queryTrips(new Location(LocationType.STATION, "8002547", null, "Flughafen Hamburg"), null, new Location(
LocationType.STATION, "8003781", null, "Lübeck Airport"), new Date(), true, Product.ALL, WalkSpeed.NORMAL, Accessibility.NEUTRAL);
print(result);
if (!result.context.canQueryLater())
return;
final QueryTripsResult laterResult = queryMoreTrips(result.context, true);
print(laterResult);
}