/* * Copyright 2014-2015 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 static com.google.common.base.Preconditions.checkNotNull; import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.EnumSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import javax.annotation.Nullable; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; import com.google.common.base.Strings; import de.schildbach.pte.dto.Departure; 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.NearbyLocationsResult; import de.schildbach.pte.dto.NearbyLocationsResult.Status; 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.Style; import de.schildbach.pte.dto.Style.Shape; import de.schildbach.pte.dto.SuggestLocationsResult; import de.schildbach.pte.dto.SuggestedLocation; import de.schildbach.pte.dto.Trip; import de.schildbach.pte.dto.Trip.Individual; import de.schildbach.pte.dto.Trip.Leg; import de.schildbach.pte.dto.Trip.Public; import de.schildbach.pte.exception.NotFoundException; import de.schildbach.pte.exception.ParserException; import de.schildbach.pte.util.ParserUtils; import de.schildbach.pte.util.WordUtils; /** * @author Antonio El Khoury * @author Andreas Schildbach */ public abstract class AbstractNavitiaProvider extends AbstractNetworkProvider { protected final static String SERVER_PRODUCT = "navitia"; protected final static String SERVER_VERSION = "v1"; protected String apiBase = "http://api.navitia.io/" + SERVER_VERSION + "/"; private enum PlaceType { ADDRESS, ADMINISTRATIVE_REGION, POI, STOP_POINT, STOP_AREA } private enum SectionType { CROW_FLY, PUBLIC_TRANSPORT, STREET_NETWORK, TRANSFER, WAITING, STAY_IN, ON_DEMAND_TRANSPORT, BSS_RENT, BSS_PUT_BACK, BOARDING, LANDING } private enum TransferType { BIKE, WALKING } private enum PhysicalMode { AIR, BOAT, BUS, BUSRAPIDTRANSIT, COACH, FERRY, FUNICULAR, LOCALTRAIN, LONGDISTANCETRAIN, METRO, RAPIDTRANSIT, SHUTTLE, TAXI, TRAIN, TRAMWAY } @SuppressWarnings("serial") private static class Context implements QueryTripsContext { private final Location from; private final Location to; private final String prevQueryUri; private final String nextQueryUri; private Context(final Location from, final Location to, final String prevQueryUri, final String nextQueryUri) { this.from = from; this.to = to; this.prevQueryUri = prevQueryUri; this.nextQueryUri = nextQueryUri; } public boolean canQueryLater() { return (from != null && to != null && nextQueryUri != null); } public boolean canQueryEarlier() { return (from != null && to != null && prevQueryUri != null); } @Override public String toString() { return getClass().getName() + "[" + from + "|" + to + "|" + prevQueryUri + "|" + nextQueryUri + "]"; } } public AbstractNavitiaProvider(final NetworkId network, final String apiBase, final String authorization) { this(network, authorization); this.apiBase = apiBase; } public AbstractNavitiaProvider(final NetworkId network, final String authorization) { super(network); if (authorization != null) httpClient.setHeader("Authorization", authorization); } protected abstract String region(); protected int computeForegroundColor(final String lineColor) { int bgColor = Style.parseColor(lineColor); return Style.deriveForegroundColor(bgColor); } protected Style getLineStyle(final Product product, final String code, final String color) { return new Style(Shape.RECT, Style.parseColor(color), computeForegroundColor(color)); } private String uri() { return apiBase + "coverage/" + region() + "/"; } private String tripUri() { return apiBase; } private Point parseCoord(final JSONObject coord) throws IOException { try { final double lat = coord.getDouble("lat"); final double lon = coord.getDouble("lon"); return Point.fromDouble(lat, lon); } catch (final JSONException jsonExc) { throw new ParserException(jsonExc); } } private Location parseStopPoint(final JSONObject stopPoint) throws IOException { try { final String id = stopPoint.getString("id"); final JSONObject coord = stopPoint.getJSONObject("coord"); final Point point = parseCoord(coord); final String name = WordUtils.capitalizeFully(stopPoint.getString("name")); return new Location(LocationType.STATION, id, point, null, name); } catch (final JSONException jsonExc) { throw new ParserException(jsonExc); } } private Location parseLocation(final JSONObject place) throws IOException { try { final String type = place.getString("embedded_type"); final PlaceType placeType = PlaceType.valueOf(type.toUpperCase()); switch (placeType) { case STOP_POINT: { final JSONObject stopPoint = place.getJSONObject("stop_point"); return parseStopPoint(stopPoint); } case STOP_AREA: { final JSONObject stopArea = place.getJSONObject("stop_area"); return parseStopPoint(stopArea); } case ADDRESS: { final JSONObject address = place.getJSONObject("address"); final String id = address.getString("id"); final JSONObject coord = address.getJSONObject("coord"); final Point point = parseCoord(coord); final String name = WordUtils.capitalizeFully(place.getString("name")); return new Location(LocationType.ADDRESS, id, point, null, name); } case POI: { final JSONObject poi = place.getJSONObject("poi"); final String id = poi.getString("id"); final JSONObject coord = poi.getJSONObject("coord"); final Point point = parseCoord(coord); final String name = WordUtils.capitalizeFully(poi.getString("name")); return new Location(LocationType.ADDRESS, id, point.lat, point.lon, null, name); } default: throw new IllegalArgumentException("Unhandled place type: " + type); } } catch (final JSONException jsonExc) { throw new ParserException(jsonExc); } } private String printLocation(final Location location) { if (location.hasId()) return location.id; else if (location.hasLocation()) return (double) (location.lon) / 1E6 + ";" + (double) (location.lat) / 1E6; else return ""; } private Date parseDate(final String dateString) throws ParseException { return new SimpleDateFormat("yyyyMMdd'T'HHmmss").parse(dateString); } private String printDate(final Date date) { return new SimpleDateFormat("yyyyMMdd'T'HHmmss").format(date); } private LinkedList parsePath(final JSONArray coordinates) throws IOException { LinkedList path = new LinkedList(); for (int i = 0; i < coordinates.length(); ++i) { try { final JSONArray jsonPoint = coordinates.getJSONArray(i); final double lon = jsonPoint.getDouble(0); final double lat = jsonPoint.getDouble(1); final Point point = Point.fromDouble(lat, lon); path.add(point); } catch (final JSONException jsonExc) { throw new ParserException(jsonExc); } } return path; } private class LegInfo { public final Location departure; public final Date departureTime; public final Location arrival; public final Date arrivalTime; public final List path; public final int distance; public final int min; public LegInfo(final Location departure, final Date departureTime, final Location arrival, final Date arrivalTime, final List path, final int distance, final int min) { this.departure = departure; this.departureTime = departureTime; this.arrival = arrival; this.arrivalTime = arrivalTime; this.path = path; this.distance = distance; this.min = min; } } private LegInfo parseLegInfo(final JSONObject section) throws IOException { try { final String type = section.getString("type"); if (!type.equals("waiting")) { // Build departure location. final JSONObject sectionFrom = section.getJSONObject("from"); final Location departure = parseLocation(sectionFrom); // Build departure time. final String departureDateTime = section.getString("departure_date_time"); final Date departureTime = parseDate(departureDateTime); // Build arrival location. final JSONObject sectionTo = section.getJSONObject("to"); final Location arrival = parseLocation(sectionTo); // Build arrival time. final String arrivalDateTime = section.getString("arrival_date_time"); final Date arrivalTime = parseDate(arrivalDateTime); // Build path and distance. Check first that geojson // object exists. LinkedList path = null; int distance = 0; if (section.has("geojson")) { final JSONObject jsonPath = section.getJSONObject("geojson"); final JSONArray coordinates = jsonPath.getJSONArray("coordinates"); path = parsePath(coordinates); final JSONArray properties = jsonPath.getJSONArray("properties"); for (int i = 0; i < properties.length(); ++i) { final JSONObject property = properties.getJSONObject(i); if (property.has("length")) { distance = property.getInt("length"); break; } } } // Build duration in min. final int min = section.getInt("duration") / 60; return new LegInfo(departure, departureTime, arrival, arrivalTime, path, distance, min); } else { return null; } } catch (final JSONException jsonExc) { throw new ParserException(jsonExc); } catch (final ParseException parseExc) { throw new ParserException(parseExc); } } private Line parseLineFromSection(final JSONObject section) throws IOException { try { final JSONArray links = section.getJSONArray("links"); String lineId = null; String modeId = null; for (int i = 0; i < links.length(); ++i) { final JSONObject link = links.getJSONObject(i); final String linkType = link.getString("type"); if (linkType.equals("line")) lineId = link.getString("id"); else if (linkType.equals("physical_mode")) modeId = link.getString("id"); } final Product product = parseLineProductFromMode(modeId); final JSONObject displayInfo = section.getJSONObject("display_informations"); final String code = displayInfo.getString("code"); final String colorHex = displayInfo.getString("color"); final String color = colorHex.equals("000000") ? "#FFFFFF" : "#" + colorHex; final Style lineStyle = getLineStyle(product, code, color); return new Line(lineId, null, product, code, lineStyle); } catch (final JSONException jsonExc) { throw new ParserException(jsonExc); } } private Stop parseStop(final JSONObject stopDateTime) throws IOException { try { // Build location. final JSONObject stopPoint = stopDateTime.getJSONObject("stop_point"); final Location location = parseStopPoint(stopPoint); // Build planned arrival time. final Date plannedArrivalTime = parseDate(stopDateTime.getString("arrival_date_time")); // Build planned arrival position. final Position plannedArrivalPosition = null; // Build planned departure time. final Date plannedDepartureTime = parseDate(stopDateTime.getString("departure_date_time")); // Build planned departure position. final Position plannedDeparturePosition = null; return new Stop(location, plannedArrivalTime, plannedArrivalPosition, plannedDepartureTime, plannedDeparturePosition); } catch (final JSONException jsonExc) { throw new ParserException(jsonExc); } catch (final ParseException parseExc) { throw new ParserException(parseExc); } } private Leg parseLeg(final JSONObject section) throws IOException { try { // Build common leg info. final LegInfo legInfo = parseLegInfo(section); if (legInfo == null) return null; final String type = section.getString("type"); final SectionType sectionType = SectionType.valueOf(type.toUpperCase()); switch (sectionType) { case CROW_FLY: { // Return null leg if duration is 0. if (legInfo.min == 0) return null; // Build type. final Individual.Type individualType = Individual.Type.WALK; return new Individual(individualType, legInfo.departure, legInfo.departureTime, legInfo.arrival, legInfo.arrivalTime, legInfo.path, legInfo.distance); } case PUBLIC_TRANSPORT: { // Build line. final Line line = parseLineFromSection(section); // Build destination. final JSONObject displayInfo = section.getJSONObject("display_informations"); final String direction = displayInfo.getString("direction"); final Location destination = new Location(LocationType.STATION, direction, direction, direction); final JSONArray stopDateTimes = section.getJSONArray("stop_date_times"); final int nbStopDateTime = stopDateTimes.length(); // Build departure stop. final Stop departureStop = parseStop(stopDateTimes.getJSONObject(0)); // Build arrival stop. final Stop arrivalStop = parseStop(stopDateTimes.getJSONObject(nbStopDateTime - 1)); // Build intermediate stops. final LinkedList intermediateStops = new LinkedList(); for (int i = 1; i < nbStopDateTime - 1; ++i) { final Stop intermediateStop = parseStop(stopDateTimes.getJSONObject(i)); intermediateStops.add(intermediateStop); } // Build message. final String message = null; return new Public(line, destination, departureStop, arrivalStop, intermediateStops, legInfo.path, message); } case STREET_NETWORK: { final String modeType = section.getString("mode"); final TransferType transferType = TransferType.valueOf(modeType.toUpperCase()); // Build type. final Individual.Type individualType; switch (transferType) { case BIKE: individualType = Individual.Type.BIKE; break; case WALKING: individualType = Individual.Type.WALK; break; default: throw new IllegalArgumentException("Unhandled transfer type: " + modeType); } return new Individual(individualType, legInfo.departure, legInfo.departureTime, legInfo.arrival, legInfo.arrivalTime, legInfo.path, legInfo.distance); } case TRANSFER: { // Build type. final Individual.Type individualType = Individual.Type.WALK; return new Individual(individualType, legInfo.departure, legInfo.departureTime, legInfo.arrival, legInfo.arrivalTime, legInfo.path, legInfo.distance); } case WAITING: { return null; // Do not add leg in case of waiting on the peer. } default: throw new IllegalArgumentException("Unhandled place type: " + type); } } catch (final JSONException jsonExc) { throw new ParserException(jsonExc); } } private void parseQueryTripsResult(final JSONObject head, final Location from, final Location to, final QueryTripsResult result) throws IOException { try { // Fill trips. final JSONArray journeys = head.getJSONArray("journeys"); for (int i = 0; i < journeys.length(); ++i) { final JSONObject journey = journeys.getJSONObject(i); final int changeCount = journey.getInt("nb_transfers"); // Build leg list. final List legs = new LinkedList(); final JSONArray sections = journey.getJSONArray("sections"); for (int j = 0; j < sections.length(); ++j) { final JSONObject section = sections.getJSONObject(j); final Leg leg = parseLeg(section); if (leg != null) legs.add(leg); } result.trips.add(new Trip(null, from, to, legs, null, null, changeCount)); } } catch (final JSONException jsonExc) { throw new ParserException(jsonExc); } } private Line parseLine(final JSONObject jsonLine) throws IOException { try { final String lineId = jsonLine.getString("id"); final Product product = parseLineProduct(jsonLine); final String code = jsonLine.getString("code"); final String color = "#" + jsonLine.getString("color"); final Style lineStyle = getLineStyle(product, code, color); return new Line(lineId, null, product, code, lineStyle); } catch (final JSONException jsonExc) { throw new ParserException(jsonExc); } } private Map lineProductCache = new WeakHashMap(); private Product parseLineProductFromMode(final String modeId) { final String modeType = modeId.replace("physical_mode:", ""); final PhysicalMode physicalMode = PhysicalMode.valueOf(modeType.toUpperCase()); switch (physicalMode) { case BUS: case BUSRAPIDTRANSIT: case COACH: case SHUTTLE: return Product.BUS; case RAPIDTRANSIT: case TRAIN: case LOCALTRAIN: case LONGDISTANCETRAIN: return Product.SUBURBAN_TRAIN; case TRAMWAY: return Product.TRAM; case METRO: return Product.SUBWAY; case FERRY: return Product.FERRY; case FUNICULAR: return Product.CABLECAR; case TAXI: return Product.ON_DEMAND; default: throw new IllegalArgumentException("Unhandled place type: " + modeId); } } private Product parseLineProduct(final JSONObject line) throws IOException { try { final String lineId = line.getString("id"); final Product cachedProduct = lineProductCache.get(lineId); if (cachedProduct != null) return cachedProduct; final JSONObject mode = getLinePhysicalMode(lineId); final String modeId = mode.getString("id"); final Product product = parseLineProductFromMode(modeId); lineProductCache.put(lineId, product); return product; } catch (final JSONException jsonExc) { throw new ParserException(jsonExc); } } private JSONObject getLinePhysicalMode(final String lineId) throws IOException { final String uri = uri() + "lines/" + ParserUtils.urlEncode(lineId) + "/physical_modes"; final CharSequence page = httpClient.get(uri); try { final JSONObject head = new JSONObject(page.toString()); final JSONArray physicalModes = head.getJSONArray("physical_modes"); final JSONObject physicalMode = physicalModes.getJSONObject(0); return physicalMode; } catch (final JSONException jsonExc) { throw new ParserException(jsonExc); } } private LineDestination parseLineDestination(final JSONObject route) throws IOException { try { // Build line. final JSONObject jsonLine = route.getJSONObject("line"); final Line line = parseLine(jsonLine); // Build line destination. final JSONObject direction = route.getJSONObject("direction"); Location destination = parseLocation(direction); return new LineDestination(line, destination); } catch (final JSONException jsonExc) { throw new ParserException(jsonExc); } } private List getStationLines(final String stopPointId) throws IOException { final String uri = uri() + "stop_points/" + ParserUtils.urlEncode(stopPointId) + "/routes?depth=2"; final CharSequence page = httpClient.get(uri); try { final JSONObject head = new JSONObject(page.toString()); final JSONArray routes = head.getJSONArray("routes"); final List lineDestinations = new LinkedList(); for (int i = 0; i < routes.length(); ++i) { final JSONObject route = routes.getJSONObject(i); final LineDestination lineDestination = parseLineDestination(route); lineDestinations.add(lineDestination); } return lineDestinations; } catch (final JSONException jsonExc) { throw new ParserException(jsonExc); } } private String getStopAreaId(final String stopPointId) throws IOException { final String uri = uri() + "stop_points/" + ParserUtils.urlEncode(stopPointId) + "?depth=1"; final CharSequence page = httpClient.get(uri); try { final JSONObject head = new JSONObject(page.toString()); final JSONArray stopPoints = head.getJSONArray("stop_points"); final JSONObject stopPoint = stopPoints.getJSONObject(0); final JSONObject stopArea = stopPoint.getJSONObject("stop_area"); return stopArea.getString("id"); } catch (final JSONException jsonExc) { throw new ParserException(jsonExc); } } @Override protected boolean hasCapability(final Capability capability) { if (capability == Capability.SUGGEST_LOCATIONS || capability == Capability.NEARBY_LOCATIONS || capability == Capability.DEPARTURES || capability == Capability.TRIPS) return true; else return false; } public NearbyLocationsResult queryNearbyLocations(final EnumSet types, final Location location, int maxDistance, final int maxLocations) throws IOException { final ResultHeader resultHeader = new ResultHeader(network, SERVER_PRODUCT, SERVER_VERSION, 0, null); // Build query uri depending of location type. final StringBuilder queryUri = new StringBuilder(uri()); if (location.type == LocationType.COORD || location.type == LocationType.ADDRESS || location.type == LocationType.ANY) { if (!location.hasLocation()) throw new IllegalArgumentException(); final double lon = location.lon / 1E6; final double lat = location.lat / 1E6; queryUri.append("coords/").append(lon).append(';').append(lat); } else if (location.type == LocationType.STATION) { if (!location.isIdentified()) throw new IllegalArgumentException(); queryUri.append("stop_points/").append(location.id); } else if (location.type == LocationType.POI) { if (!location.isIdentified()) throw new IllegalArgumentException(); queryUri.append("pois/").append(location.id); } else { throw new IllegalArgumentException("Unhandled location type: " + location.type); } queryUri.append('/'); if (maxDistance == 0) maxDistance = 50000; queryUri.append("places_nearby?type[]=stop_point"); queryUri.append("&distance=").append(maxDistance); if (maxLocations > 0) queryUri.append("&count=").append(maxLocations); queryUri.append("&depth=0"); final CharSequence page = httpClient.get(queryUri.toString()); try { final JSONObject head = new JSONObject(page.toString()); final JSONObject pagination = head.getJSONObject("pagination"); final int nbResults = pagination.getInt("total_result"); // If no result is available, location id must be // faulty. if (nbResults == 0) { return new NearbyLocationsResult(resultHeader, Status.INVALID_ID); } else { final List stations = new ArrayList(); final JSONArray places = head.getJSONArray("places_nearby"); // Cycle through nearby stations. for (int i = 0; i < places.length(); ++i) { final JSONObject place = places.getJSONObject(i); // Add location to station list only if // station is active, i.e. at least one // departure exists within one hour. final Location nearbyLocation = parseLocation(place); stations.add(nearbyLocation); } return new NearbyLocationsResult(resultHeader, stations); } } catch (final JSONException jsonExc) { throw new ParserException(jsonExc); } } public QueryDeparturesResult queryDepartures(final String stationId, final @Nullable Date time, final int maxDepartures, final boolean equivs) throws IOException { checkNotNull(Strings.emptyToNull(stationId)); final ResultHeader resultHeader = new ResultHeader(network, SERVER_PRODUCT, SERVER_VERSION, 0, null); try { final QueryDeparturesResult result = new QueryDeparturesResult(resultHeader, QueryDeparturesResult.Status.OK); final String dateTime = printDate(time); // If equivs is equal to true, get stop_area corresponding // to stop_point and query departures. final StringBuilder queryUri = new StringBuilder(); queryUri.append(uri()); final String header = stationId.substring(0, stationId.indexOf(":")); if (equivs && header.equals("stop_point")) { final String stopAreaId = getStopAreaId(stationId); queryUri.append("stop_areas/"); queryUri.append(stopAreaId); queryUri.append("/"); } else { if (header.equals("stop_area")) { queryUri.append("stop_areas/"); queryUri.append(stationId); queryUri.append("/"); } else { queryUri.append("stop_points/"); queryUri.append(stationId); queryUri.append("/"); } } queryUri.append("departures?from_datetime="); queryUri.append(dateTime); queryUri.append("&count="); queryUri.append(maxDepartures); queryUri.append("&duration=86400"); queryUri.append("&depth=0"); final CharSequence page = httpClient.get(queryUri.toString()); final JSONObject head = new JSONObject(page.toString()); final JSONArray departures = head.getJSONArray("departures"); // Fill departures in StationDepartures. for (int i = 0; i < departures.length(); ++i) { final JSONObject jsonDeparture = departures.getJSONObject(i); final JSONObject stopPoint = jsonDeparture.getJSONObject("stop_point"); final Location location = parseStopPoint(stopPoint); // If stop point has already been added, retrieve it // from result, otherwise add it and add station // lines. StationDepartures stationDepartures = result.findStationDepartures(location.id); if (stationDepartures == null) { stationDepartures = new StationDepartures(location, new LinkedList(), new LinkedList()); result.stationDepartures.add(stationDepartures); final List lineDestinations = getStationLines(location.id); for (LineDestination lineDestination : lineDestinations) checkNotNull(stationDepartures.lines).add(lineDestination); } // Build departure date. final JSONObject stopDateTime = jsonDeparture.getJSONObject("stop_date_time"); final String departureDateTime = stopDateTime.getString("departure_date_time"); final Date plannedTime = parseDate(departureDateTime); // Build line. final JSONObject route = jsonDeparture.getJSONObject("route"); final JSONObject jsonLine = route.getJSONObject("line"); final Line line = parseLine(jsonLine); final Location destination = findLineDestination(stationDepartures.lines, line).destination; // Add departure to list. final Departure departure = new Departure(plannedTime, null, line, null, destination, null, null); stationDepartures.departures.add(departure); } return result; } catch (final JSONException jsonExc) { throw new ParserException(jsonExc); } catch (final ParseException parseExc) { throw new ParserException(parseExc); } catch (final NotFoundException fnfExc) { try { final JSONObject head = new JSONObject(fnfExc.scrapeErrorStream().toString()); final JSONObject error = head.getJSONObject("error"); final String id = error.getString("id"); if (id.equals("unknown_object")) return new QueryDeparturesResult(resultHeader, QueryDeparturesResult.Status.INVALID_STATION); else throw new IllegalArgumentException("Unhandled error id: " + id); } catch (final JSONException jsonExc) { throw new ParserException(jsonExc); } } } private LineDestination findLineDestination(final List lineDestinations, final Line line) { for (final LineDestination lineDestination : lineDestinations) if (lineDestination.line.equals(line)) return lineDestination; return null; } public SuggestLocationsResult suggestLocations(final CharSequence constraint) throws IOException { final String nameCstr = constraint.toString(); final String queryUri = uri() + "places?q=" + ParserUtils.urlEncode(nameCstr) + "&type[]=stop_area&type[]=address" + "&depth=1"; final CharSequence page = httpClient.get(queryUri); try { final List locations = new ArrayList(); final JSONObject head = new JSONObject(page.toString()); if (head.has("places")) { final JSONArray places = head.getJSONArray("places"); for (int i = 0; i < places.length(); ++i) { final JSONObject place = places.getJSONObject(i); // Add location to station list. final Location location = parseLocation(place); locations.add(new SuggestedLocation(location)); } } final ResultHeader resultHeader = new ResultHeader(network, SERVER_PRODUCT, SERVER_VERSION, 0, null); return new SuggestLocationsResult(resultHeader, locations); } catch (final JSONException jsonExc) { throw new ParserException(jsonExc); } } public QueryTripsResult queryTrips(final Location from, final @Nullable Location via, final Location to, final Date date, final boolean dep, final @Nullable Set products, final @Nullable Optimize optimize, final @Nullable WalkSpeed walkSpeed, final @Nullable Accessibility accessibility, final @Nullable Set