diff --git a/enabler/src/de/schildbach/pte/AustraliaProvider.java b/enabler/src/de/schildbach/pte/AustraliaProvider.java new file mode 100644 index 00000000..3d2ab003 --- /dev/null +++ b/enabler/src/de/schildbach/pte/AustraliaProvider.java @@ -0,0 +1,165 @@ +/* + * Copyright 2017 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 java.util.HashMap; +import java.util.Map; + +import de.schildbach.pte.dto.Product; +import de.schildbach.pte.dto.Style; + +import okhttp3.HttpUrl; + +public class AustraliaProvider extends AbstractNavitiaProvider { + + public static final String NETWORK_PTV = "PTV - Public Transport Victoria"; + public static final String NETWORK_TAS = "Metro Tasmania"; + public static final String NETWORK_QLD = "TransLink"; + public static final String NETWORK_SA = "Adelaide Metro"; + public static final String NETWORK_WA = "Transperth"; + + private static final Map STYLES = new HashMap<>(); + + static { + // Melbourne train networks. + // https://static.ptv.vic.gov.au/Maps/1482457134/PTV_Train-Network-Map_2017.pdf + STYLES.put(NETWORK_PTV + "|SBelgrave", new Style(Style.Shape.RECT, Style.parseColor("#094B8D"), Style.WHITE)); + STYLES.put(NETWORK_PTV + "|SLilydale", new Style(Style.Shape.RECT, Style.parseColor("#094B8D"), Style.WHITE)); + STYLES.put(NETWORK_PTV + "|SAlamein", new Style(Style.Shape.RECT, Style.parseColor("#094B8D"), Style.WHITE)); + STYLES.put(NETWORK_PTV + "|SGlen Waverly", + new Style(Style.Shape.RECT, Style.parseColor("#094B8D"), Style.WHITE)); + STYLES.put(NETWORK_PTV + "|SSunbury", new Style(Style.Shape.RECT, Style.parseColor("#FFB531"), Style.BLACK)); + STYLES.put(NETWORK_PTV + "|SCraigieburn", + new Style(Style.Shape.RECT, Style.parseColor("#FFB531"), Style.BLACK)); + STYLES.put(NETWORK_PTV + "|SUpfield", new Style(Style.Shape.RECT, Style.parseColor("#FFB531"), Style.BLACK)); + STYLES.put(NETWORK_PTV + "|SSouth Morang", + new Style(Style.Shape.RECT, Style.parseColor("#E42B23"), Style.WHITE)); + STYLES.put(NETWORK_PTV + "|SHurstbridge", + new Style(Style.Shape.RECT, Style.parseColor("#E42B23"), Style.WHITE)); + STYLES.put(NETWORK_PTV + "|SPakenham", new Style(Style.Shape.RECT, Style.parseColor("#16B4E8"), Style.WHITE)); + STYLES.put(NETWORK_PTV + "|SCranbourne", new Style(Style.Shape.RECT, Style.parseColor("#16B4E8"), Style.WHITE)); + STYLES.put(NETWORK_PTV + "|SFrankston", new Style(Style.Shape.RECT, Style.parseColor("#149943"), Style.WHITE)); + STYLES.put(NETWORK_PTV + "|SWerribee", new Style(Style.Shape.RECT, Style.parseColor("#149943"), Style.WHITE)); + STYLES.put(NETWORK_PTV + "|SWilliamstown", + new Style(Style.Shape.RECT, Style.parseColor("#149943"), Style.WHITE)); + + // Truncated version of "Sandringham". Not sure if this is a bug/limitation in either Navitia or the + // GTFS feed from PTV. + STYLES.put(NETWORK_PTV + "|SSandringha", new Style(Style.Shape.RECT, Style.parseColor("#FC7EBB"), Style.BLACK)); + + // Difficult to test this, because the line is open only for select periods of the year (e.g. for the + // Melbourne Show in September) and at time of writing, the GTFS feed did not have data up until then. + STYLES.put(NETWORK_PTV + "|SFlemington Racecourse", + new Style(Style.Shape.RECT, Style.parseColor("#9A9B9F"), Style.BLACK)); + + // Melbourne Trams. + // https://static.ptv.vic.gov.au/Maps/1493356745/PTV_Tram-Network-Map_2017.pdf + STYLES.put(NETWORK_PTV + "|T1", new Style(Style.Shape.RECT, Style.parseColor("#B8C53A"), Style.BLACK)); + + // The GTFS feed seems to combine 3 + 3a like this, but for completeness we include 3 and 3a + // separately too. + STYLES.put(NETWORK_PTV + "|T3", new Style(Style.Shape.RECT, Style.parseColor("#87D9F2"), Style.BLACK)); + STYLES.put(NETWORK_PTV + "|T3a", new Style(Style.Shape.RECT, Style.parseColor("#87D9F2"), Style.BLACK)); + STYLES.put(NETWORK_PTV + "|T3/3a", new Style(Style.Shape.RECT, Style.parseColor("#87D9F2"), Style.BLACK)); + STYLES.put(NETWORK_PTV + "|T5", new Style(Style.Shape.RECT, Style.parseColor("#F44131"), Style.WHITE)); + STYLES.put(NETWORK_PTV + "|T6", new Style(Style.Shape.RECT, Style.parseColor("#004969"), Style.WHITE)); + STYLES.put(NETWORK_PTV + "|T11", new Style(Style.Shape.RECT, Style.parseColor("#7ECBA4"), Style.BLACK)); + STYLES.put(NETWORK_PTV + "|T12", new Style(Style.Shape.RECT, Style.parseColor("#008A99"), Style.WHITE)); + STYLES.put(NETWORK_PTV + "|T16", new Style(Style.Shape.RECT, Style.parseColor("#FFD86C"), Style.BLACK)); + STYLES.put(NETWORK_PTV + "|T19", new Style(Style.Shape.RECT, Style.parseColor("#87457A"), Style.WHITE)); // Night + STYLES.put(NETWORK_PTV + "|T30", new Style(Style.Shape.RECT, Style.parseColor("#3343A3"), Style.WHITE)); + STYLES.put(NETWORK_PTV + "|T35", new Style(Style.Shape.RECT, Style.parseColor("#6E351C"), Style.WHITE)); + STYLES.put(NETWORK_PTV + "|T48", new Style(Style.Shape.RECT, Style.parseColor("#45474C"), Style.WHITE)); + STYLES.put(NETWORK_PTV + "|T57", new Style(Style.Shape.RECT, Style.parseColor("#45C6CE"), Style.WHITE)); + STYLES.put(NETWORK_PTV + "|T58", new Style(Style.Shape.RECT, Style.parseColor("#878E94"), Style.WHITE)); + STYLES.put(NETWORK_PTV + "|T59", new Style(Style.Shape.RECT, Style.parseColor("#438459"), Style.WHITE)); + STYLES.put(NETWORK_PTV + "|T64", new Style(Style.Shape.RECT, Style.parseColor("#2EB070"), Style.WHITE)); + STYLES.put(NETWORK_PTV + "|T67", new Style(Style.Shape.RECT, Style.parseColor("#B47962"), Style.WHITE)); // Night + STYLES.put(NETWORK_PTV + "|T70", new Style(Style.Shape.RECT, Style.parseColor("#FC8BC1"), Style.BLACK)); + STYLES.put(NETWORK_PTV + "|T72", new Style(Style.Shape.RECT, Style.parseColor("#97BAA6"), Style.BLACK)); + STYLES.put(NETWORK_PTV + "|T75", new Style(Style.Shape.RECT, Style.parseColor("#00A8DF"), Style.WHITE)); // Night + STYLES.put(NETWORK_PTV + "|T78", new Style(Style.Shape.RECT, Style.parseColor("#7B7EC0"), Style.WHITE)); + STYLES.put(NETWORK_PTV + "|T82", new Style(Style.Shape.RECT, Style.parseColor("#BCD649"), Style.BLACK)); + STYLES.put(NETWORK_PTV + "|T86", new Style(Style.Shape.RECT, Style.parseColor("#FFB730"), Style.BLACK)); // Night + STYLES.put(NETWORK_PTV + "|T96", new Style(Style.Shape.RECT, Style.parseColor("#F2428F"), Style.WHITE)); // Night + STYLES.put(NETWORK_PTV + "|T109", new Style(Style.Shape.RECT, Style.parseColor("#FF7B24"), Style.WHITE)); // Night + + STYLES.put(NETWORK_PTV + "|B", new Style(Style.Shape.RECT, Style.parseColor("#EA8D1E"), Style.WHITE)); + + // NOTE: This is a work around for poor GTFS data. We should instead say "All REGIONAL_TRAINs are + // purple", but the GTFS feed from Navitia instead returns rural trains as SUBURBAN_TRAINs. Given we + // have already provided colours for all suburban trains above, this more general statement about + // suburban trains results in all regional trains being coloured purple, as intented. + STYLES.put(NETWORK_PTV + "|S", new Style(Style.Shape.RECT, Style.parseColor("#782F9A"), Style.WHITE)); + + // Sydney train networks. + // http://www.sydneytrains.info/stations/pdf/suburban_map.pdf + // Navitia is not returning enough info in "display_informations" to colourise correctly. + // Specifically, they are not returning "code" which usually is the display name of the line. + // At any rate, the EFA provider for Sydney is likely going to be better than this Navitia provider + // anyway. + + // Adelaide train/tram networks. + // These are already colourised correctly by the GTFS data given to Navitia. + // But for reference, the map is available at https://www.adelaidemetro.com.au/Timetables-Maps/Maps. + + // Brisbane train/tram/bus/ferry networks. + // These are already colourised correctly by the GTFS data given to Navitia. + // But for reference, the maps are available at https://translink.com.au/plan-your-journey/maps. + + // Perth train/bus/ferry networks. + // These are already colourised correctly by the GTFS data given to Navitia. + // The styles do not include "display_informations" with a proper "code" though, so the names are not + // displayed. But for reference, the maps are available at + // http://www.transperth.wa.gov.au/Journey-Planner/Network-Maps. + + // Tasmania bus networks. + // Somewhat colourised in Navitia (e.g. Launceston has green buses), but it is incorrect (e.g. + // Launceston should have all sorts of different coloured buses). Maps are available at + // https://www.metrotas.com.au/timetables/. + } + + public AustraliaProvider(final HttpUrl apiBase, final String authorization) { + super(NetworkId.AUSTRALIA, apiBase, authorization); + + setTimeZone("Australia/Melbourne"); + setStyles(STYLES); + } + + public AustraliaProvider(final String authorization) { + super(NetworkId.AUSTRALIA, authorization); + + setTimeZone("Australia/Melbourne"); + setStyles(STYLES); + } + + @Override + public String region() { + return "au"; + } + + @Override + protected Style getLineStyle(String network, Product product, String code, String backgroundColor, + String foregroundColor) { + final Style overridenStyle = lineStyle(network, product, code); + if (overridenStyle != Standard.STYLES.get(product)) + return overridenStyle; + else + return super.getLineStyle(network, product, code, backgroundColor, foregroundColor); + } +} diff --git a/enabler/src/de/schildbach/pte/NetworkId.java b/enabler/src/de/schildbach/pte/NetworkId.java index ff844c5c..25cc286c 100644 --- a/enabler/src/de/schildbach/pte/NetworkId.java +++ b/enabler/src/de/schildbach/pte/NetworkId.java @@ -79,5 +79,5 @@ public enum NetworkId { ONTARIO, QUEBEC, // Australia - SYDNEY + SYDNEY, AUSTRALIA } diff --git a/enabler/test/de/schildbach/pte/live/AustraliaProviderLiveTest.java b/enabler/test/de/schildbach/pte/live/AustraliaProviderLiveTest.java new file mode 100644 index 00000000..dc3d4c99 --- /dev/null +++ b/enabler/test/de/schildbach/pte/live/AustraliaProviderLiveTest.java @@ -0,0 +1,305 @@ +/* + * Copyright 2017 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; + +import java.io.IOException; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +import org.junit.Ignore; +import org.junit.Test; + +import de.schildbach.pte.AustraliaProvider; +import de.schildbach.pte.dto.Location; +import de.schildbach.pte.dto.QueryTripsResult; +import de.schildbach.pte.dto.SuggestLocationsResult; +import de.schildbach.pte.dto.Trip; + +/** + * Test basic from/to directions for each mode of transport, in each state of Australia supported by Navitia. + * This is mainly to test whether or not the coverage is still in date or not. For example, at time of writing + * Cambera, Alice Springs, and Darwin were all present on Navitia, but out of date so were unable to provide + * journey planning. + * + * These tests work by taking the next Monday at 08:45 (a random peak hour time where you'd expect there to be + * a lot of public transport available). If they are unable to find a route for a specific mode of transport, + * then you should further investigate to see if the data is out of date or not in Navitia. + * + * Note that by default, only Melbourne is tested comprehensively to prevent running into API limits + * ({@see #RUN_EXPENSIVE_TESTS}). + */ +public class AustraliaProviderLiveTest extends AbstractNavitiaProviderLiveTest { + + /** + * If enabled, the entire set of tests will run, resulting in over 30 API calls to Navitia. Given this + * test may or may not be run under, e.g. continuous integration, or run frequently while working on the + * Australian API, it could end up using up your API limit unneccesarily. Thus, the default value is to + * only perform a proper test of Melbourne, and the rest of the coverage is disabled until this flag is + * true. + */ + private static final boolean RUN_EXPENSIVE_TESTS = false; + + public AustraliaProviderLiveTest() { + super(new AustraliaProvider(secretProperty("navitia.authorization"))); + } + + /** + * Ensures that each of the suburban/rural trains, trams, and buses are represented in the journey + * planning and location suggestion API. Based on travelling around the Camberwell area: + * http://www.openstreetmap.org/#map=15/-37.8195/145.0586&layers=T + */ + @Test + public void melbourne() throws IOException { + final Location suburbanTrainStation = assertAndGetLocation("Camberwell Railway Station (Camberwell)"); + final Location ruralTrainStation = assertAndGetLocation("Geelong Railway Station (Geelong)"); + assertJourneyExists(AustraliaProvider.NETWORK_PTV, new String[] { "Lilydale", "Belgrave", "Alamein" }, + suburbanTrainStation, ruralTrainStation); + + final Location tramStop = assertAndGetLocation("70-Cotham Rd/Burke Rd (Kew)"); + assertJourneyExists(AustraliaProvider.NETWORK_PTV, "72", suburbanTrainStation, tramStop); + + final Location busStop = assertAndGetLocation("Lawrence St/Burke Rd (Kew East)"); + assertJourneyExists(AustraliaProvider.NETWORK_PTV, "548", tramStop, busStop); + } + + @Test + public void adelaideRail() throws IOException { + assumeTrue(RUN_EXPENSIVE_TESTS); + + final Location railwayStation = assertAndGetLocation("Woodville Park Railway Station"); + final Location railwayStation2 = assertAndGetLocation("Unley Park Railway Station"); + assertJourneyExists(AustraliaProvider.NETWORK_SA, new String[] { "GRNG", "BEL", "OUTHA" }, railwayStation, + railwayStation2); + } + + @Test + public void adelaideBus() throws IOException { + assumeTrue(RUN_EXPENSIVE_TESTS); + + final Location busStation = assertAndGetLocation("Stop 137 Portrush Rd - East side"); + final Location busStation2 = assertAndGetLocation("Stop 144 Portrush Rd - East side"); + assertJourneyExists(AustraliaProvider.NETWORK_SA, "300", busStation, busStation2); + } + + @Test + public void adelaideTram() throws IOException { + assumeTrue(RUN_EXPENSIVE_TESTS); + + final Location tramStation = assertAndGetLocation("Stop 15 Dunbar Tce - Brighton Rd"); + final Location tramStation2 = assertAndGetLocation("Stop 5 Black Forest"); + assertJourneyExists(AustraliaProvider.NETWORK_SA, "Tram", tramStation, tramStation2); + } + + @Test + public void perthTrain() throws IOException { + assumeTrue(RUN_EXPENSIVE_TESTS); + + final Location railwayStation = assertAndGetLocation("Kenwick Stn"); + final Location railwayStation2 = assertAndGetLocation("Warwick Stn"); + assertJourneyExists(AustraliaProvider.NETWORK_WA, "", railwayStation, railwayStation2); + } + + @Test + public void perthBus() throws IOException { + assumeTrue(RUN_EXPENSIVE_TESTS); + + final Location bus = assertAndGetLocation("Curtin University"); + final Location bus2 = assertAndGetLocation("Murdoch Stn"); + assertJourneyExists(AustraliaProvider.NETWORK_WA, "", bus, bus2); + } + + @Test + public void perthFerry() throws IOException { + assumeTrue(RUN_EXPENSIVE_TESTS); + + final Location ferry = assertAndGetLocation("Elizabeth Quay Stn"); + final Location ferry2 = assertAndGetLocation("Ferry Route Mends St Jetty"); + assertJourneyExists(AustraliaProvider.NETWORK_WA, "", ferry, ferry2); + } + + @Test + public void brisbaneRail() throws IOException { + assumeTrue(RUN_EXPENSIVE_TESTS); + + final Location railwayStation = assertAndGetLocation("Beenleigh station"); + final Location railwayStation2 = assertAndGetLocation("Ipswich station"); + assertJourneyExists(AustraliaProvider.NETWORK_QLD, "BNFG", railwayStation, railwayStation2); + } + + @Test + public void brisbaneFerry() throws IOException { + assumeTrue(RUN_EXPENSIVE_TESTS); + + final Location ferry = assertAndGetLocation("Broadbeach South"); + final Location ferry2 = assertAndGetLocation("Southport"); + assertJourneyExists(AustraliaProvider.NETWORK_QLD, "GLKN", ferry, ferry2); + } + + @Test + public void brisbaneTram() throws IOException { + assumeTrue(RUN_EXPENSIVE_TESTS); + + final Location tram = assertAndGetLocation("South Bank 2 ferry terminal"); + final Location tram2 = assertAndGetLocation("Guyatt Park ferry terminal"); + assertJourneyExists(AustraliaProvider.NETWORK_QLD, "UQSL", tram, tram2); + } + + @Test + public void hobartBus() throws IOException { + assumeTrue(RUN_EXPENSIVE_TESTS); + + final Location bus = assertAndGetLocation("Stop 15, No.237 New Town Rd"); + final Location bus2 = assertAndGetLocation("Stop 2, No.131 Elizabeth St"); + assertJourneyExists(AustraliaProvider.NETWORK_TAS, "504", bus, bus2); + } + + @Test + public void launcestonBus() throws IOException { + assumeTrue(RUN_EXPENSIVE_TESTS); + + final Location bus = assertAndGetLocation("Riverside Dr / Rannoch Ave"); + final Location bus2 = assertAndGetLocation("Trevallyn Shops"); + assertJourneyExists(AustraliaProvider.NETWORK_TAS, "90", bus, bus2); + } + + @Test + public void bernieBus() throws IOException { + assumeTrue(RUN_EXPENSIVE_TESTS); + + final Location bus = assertAndGetLocation("Stop 31, 197 Mount St"); + final Location bus2 = assertAndGetLocation("Burnie Park opposite 55 West Park Gr"); + assertJourneyExists(AustraliaProvider.NETWORK_TAS, "12", bus, bus2); + } + + // Although Navitia has a GTFS feed for ACT, Darwin, and Alice Springs, they were out of date at time of + // writing. + @Test + @Ignore + public void act() { + } + + @Test + @Ignore + public void darwin() { + } + + @Test + @Ignore + public void aliceSprings() { + } + + /** + * Suggests locations similar to {@param locationName}, but then ensures that one matches exactly and then + * returns it. Try not to use an ambiguous name such as "Central Station", because it may exist in several + * datasets on Navitia. + */ + private Location assertAndGetLocation(String locationName) throws IOException { + SuggestLocationsResult locations = suggestLocations(locationName); + assertEquals(SuggestLocationsResult.Status.OK, locations.status); + assertTrue(locations.getLocations().size() > 0); + + StringBuilder nonMatching = new StringBuilder(); + for (Location location : locations.getLocations()) { + if (locationName.equals(location.name)) { + return location; + } + + nonMatching.append('[').append(location.name).append("] "); + } + + throw new AssertionError( + "suggestLocations() did not find \"" + locationName + "\". Options were: " + nonMatching); + } + + /** + * @see #assertJourneyExists(String, String[], Location, Location) + */ + private void assertJourneyExists(String network, String eligibleLine, Location from, Location to) + throws IOException { + assertJourneyExists(network, new String[] { eligibleLine }, from, to); + } + + private Date getNextMondayMorning() { + Calendar date = Calendar.getInstance(); + date.setTime(new Date()); + date.set(Calendar.HOUR_OF_DAY, 8); + date.set(Calendar.MINUTE, 45); + while (date.get(Calendar.DAY_OF_WEEK) != Calendar.MONDAY) { + date.add(Calendar.DATE, 1); + } + + return date.getTime(); + } + + private void assertJourneyExists(String network, String[] eligibleLines, Location from, Location to) + throws IOException { + QueryTripsResult trips = queryTrips(from, null, to, getNextMondayMorning(), true, null, null, null); + assertNull(trips.ambiguousFrom); + assertNull(trips.ambiguousTo); + assertEquals(QueryTripsResult.Status.OK, trips.status); + assertNotNull(trips.trips); + assertTrue(trips.trips.size() > 0); + + Set eligibleLineSet = new HashSet<>(); + Collections.addAll(eligibleLineSet, eligibleLines); + + for (Trip trip : trips.trips) { + boolean hasPublicTransport = false; + boolean matchesCode = false; + for (Trip.Leg leg : trip.legs) { + if (leg instanceof Trip.Public) { + hasPublicTransport = true; + + Trip.Public publicLeg = (Trip.Public) leg; + assertEquals(network, publicLeg.line.network); + + if (eligibleLineSet.contains(publicLeg.line.label)) { + matchesCode = true; + } + } + } + + if (hasPublicTransport && matchesCode) { + return; + } + } + + StringBuilder sb = new StringBuilder(); + for (Trip trip : trips.trips) { + sb.append("\n "); + for (Trip.Leg leg : trip.legs) { + String via = leg instanceof Trip.Public ? " (via " + ((Trip.Public) leg).line.label + ") " : " -> "; + sb.append('[').append(leg.arrival.name).append(']').append(via).append('[').append(leg.departure.name) + .append(']').append(" ... "); + } + } + + fail("No public trip found between [" + from.name + "] and [" + to.name + + "] using appropriate line. Found trips:" + sb); + } +}