diff --git a/docs/onebusaway-gtfs-transformer-cli-sample1.png b/docs/onebusaway-gtfs-transformer-cli-sample1.png new file mode 100644 index 00000000..eec78075 Binary files /dev/null and b/docs/onebusaway-gtfs-transformer-cli-sample1.png differ diff --git a/docs/onebusaway-gtfs-transformer-cli.md b/docs/onebusaway-gtfs-transformer-cli.md index efe4c6cc..7b357e7b 100644 --- a/docs/onebusaway-gtfs-transformer-cli.md +++ b/docs/onebusaway-gtfs-transformer-cli.md @@ -21,6 +21,7 @@ * [Retain an Entity](#retain-an-entity) * [Remove an Entity](#remove-an-entity) * [Retain Up From Polygon](#retain-up-from-polygon) + * [Trim Trip From Polygon](#trim-trip-from-polygon) * [Trim a Trip](#trim-a-trip) * [Generate Stop Times](#generate-stop-times) * [Extend Service Calendars](#extend-service-calendars) @@ -31,6 +32,7 @@ * [Shift Negative Stop Times](#shift-negative-stop-times) * [Arbitrary Transform](#arbitrary-transform) * [How to Reduce your GTFS](#how-to-reduce-your-gtfs) + * [Clip National GTFS for Regional Integration and Consistency](#clip-national-gtfs-for-regional-integration-and-consistency) ## Introduction @@ -39,7 +41,7 @@ The `onebusaway-gtfs-transformer-cli` command-line application is a simple comma [GTFS](https://developers.google.com/transit/gtfs) feeds. ### Requirements - + * Java 17 or greater ### Getting the Application @@ -47,7 +49,7 @@ The `onebusaway-gtfs-transformer-cli` command-line application is a simple comma You can download the application from Maven Central: https://repo1.maven.org/maven2/org/onebusaway/onebusaway-gtfs-transformer-cli/ Select the largest jar file from the version you would like to use, for example https://repo1.maven.org/maven2/org/onebusaway/onebusaway-gtfs-transformer-cli/2.0.0/onebusaway-gtfs-transformer-cli-2.0.0.jar - + ### Using the Application To run the application: @@ -59,20 +61,20 @@ java -jar onebusaway-gtfs-transformer-cli.jar [-args] input_gtfs_path ... output `input_gtfs_path` and `output_gtfs_path` can be either a directory containing a GTFS feed or a .zip file. _Note_: Transforming large GTFS feeds is processor and memory intensive. You'll likely need to increase the -max amount of memory allocated to Java with an option like `-Xmx1G` or greater. Adding the `-server` argument -if you are running the Oracle or OpenJDK can also increase performance. +max amount of memory allocated to Java with an option like `-Xmx1G` or greater. Adding the `-server` argument +if you are running the Oracle or OpenJDK can also increase performance. ### Arguments * `--transform=...` : specify a transformation to apply to the input GTFS feed (see syntax below) * `--agencyId=id` : specify a default agency id for the input GTFS feed * `--overwriteDuplicates` : specify that duplicate GTFS entities should overwrite each other when read - - + + ### Transform Syntax Transforms are specified as snippets of example. A simple example to remove a stop might look like: - + ``` {"op":"remove","match":{"file":"stops.txt","stop_name":"Stop NameP"}} ``` @@ -87,17 +89,17 @@ You can have multiple `--transform` arguments to specify multiple transformation transformations that you wish to apply, it can be easier to put them in a file, with a JSON snippet per line. Then specify the file on the command-line: -``` +``` --transform=path/to/local-file ``` You can even specify a URL where the transformations will be read: - -``` + +``` --transform=http://server/path ``` -### Matching +### Matching We provide a number of configurable transformations out-of-the-box that can do simple operations like adding, updating, retaining, and removing GTFS entities. Many of the transforms accept a "`match`" term that controls how the @@ -181,7 +183,7 @@ collection. You can use the calendar collection matches, for example, to retain a calendar, including all `calendar.txt`, `calendar_dates.txt`, and `trip.txt` entries that reference the specified `service_id` value. This convenient -short-hand is easier than writing the equivalent expression using references to the three file types separately. +short-hand is easier than writing the equivalent expression using references to the three file types separately. ### Types of Transforms @@ -204,7 +206,7 @@ You can update arbitrary fields of a GTFS entity. Normally, update values are used as-is. However, we support a number of special update operations: - + #### Find/Replace ``` @@ -218,16 +220,16 @@ following example: ``` {"op":"update", "match":{"file":"trips.txt"}, "update":{"trip_short_name":"s/North/N/"}} ``` - + Here, a trip with a headsign of `North Seattle` will be updated to `N Seattle`. - -#### Path Expressions + +#### Path Expressions By using `path(...)` syntax in the update value, the expression will be treated as a compound Java bean properties path expression. This path expression will be evaluated against the target entity to produce the update value. Consider the following example: - + ``` {"op":"update", "match":{"file":"trips.txt"}, "update":{"trip_short_name":"path(route.longName)"}} ``` @@ -238,10 +240,10 @@ associated route. #### Retain an Entity -We also provide a powerful mechanism for selecting just a sub-set of a feed. -You can apply retain operations to entities you wish to keep and all the supporting entities referenced +We also provide a powerful mechanism for selecting just a sub-set of a feed. +You can apply retain operations to entities you wish to keep and all the supporting entities referenced by the retained entity will be retained as well. Unreferenced entities will be pruned. - + In the following example, only route B15 will be retained, along with all the stops, trips, stop times, shapes, and agencies linked to directly by that route. ``` @@ -279,7 +281,7 @@ Retain Up From Polygon is an operation that filters GTFS input data based on a s This strategy applies two main functions: * **Retain Function**: retains **up** all stops, trips, and routes that are located inside the defined polygon. - + The algorithm starts by applying retain up to each entity, traversing the entity dependency tree. Starting from the stop, retain up is applied to the stop_times referencing this stop, then to the trips, and so on. Once the base of the entity tree is reached, it automatically applies retain **down** to all the traversed entities. Therefore, all the trips of the route and then all the stop_times of each trip will be tagged as **retain**. @@ -295,11 +297,27 @@ This strategy ensures that the GTFS output retains only the entities directly or ``` {"op":"transform","class":"org.onebusaway.gtfs_transformer.impl.RetainUpFromPolygon","polygon":"POLYGON ((-123.0 37.0, -123.0 38.0, -122.0 38.0, -122.0 37.0, -123.0 37.0))"} ``` - + +#### Trim Trip From Polygon + +The Trim Trip From Polygon strategy refines GTFS data by removing all stop_times associated with stops located outside a specified geographical area. The area is defined using a configurable WKT Polygon or Multipolygon in the JSON transformer snippet. + +This removal of stop_times is achieved by invoking the **TrimTrip operation**, ensuring that only stops within the defined polygon are retained. + +Only valid stop_times within the polygon are retained, maintaining the integrity of the trips. + +**Parameters**: + + * **polygon**: a required argument, which accepts the polygon in WKT format using the WGS84 coordinate system (SRID: 4326). This polygon defines the area of interest for filtering. + +``` +{"op":"transform","class":"org.onebusaway.gtfs_transformer.impl.TrimTripFromPolygon","polygon":"POLYGON ((-123.0 37.0, -123.0 38.0, -122.0 38.0, -122.0 37.0, -123.0 37.0))"} +``` + #### Trim a Trip You can remove stop times from the beginning or end of a trip using the "trim_trip" operation. Example: - + ``` {"op":"trim_trip", "match":{"file":"trips.txt", "route_id":"R10"}, "from_stop_id":"138S"} ``` @@ -320,7 +338,7 @@ Or both: #### Generate Stop Times You can generate stop time entries for a trip. Example: - + ``` {"op":"stop_times_factory", "trip_id":"TRIP01", "start_time":"06:00:00", "end_time":"06:20:00", "stop_ids":["S01", "S02", "S03"]} ``` @@ -375,18 +393,18 @@ By default, it deletes entries from both the calendar.txt and calendar_dates.txt With the remove_today attribute added to the JSON transformer snippet, users can control whether entries in calendar or calendar_dates that are valid for today are included or excluded in the GTFS output. * If remove_today is set to true, the transformer will remove entries for the current date. - + ``` {"op":"transform", "class":"org.onebusaway.gtfs_transformer.impl.RemoveOldCalendarStatements", "remove_today":true} ``` - + * If remove_today is set to false or not specified, the transformer will retain the calendar entries for the current date. - + ``` {"op":"transform", "class":"org.onebusaway.gtfs_transformer.impl.RemoveOldCalendarStatements", "remove_today":false} ``` -Additionally, after truncating the calendar entries, it is recommended to use a **retain operation** to ensure that only trips with valid calendar dates are retained. +Additionally, after truncating the calendar entries, it is recommended to use a **retain operation** to ensure that only trips with valid calendar dates are retained. Without this retain operation, the `trips.txt` file will contain trips with non-existent calendar dates, leading to invalid data. @@ -413,10 +431,10 @@ This operation truncates calendar and calendar date entries based on the configu - `Calendar.MONTH` = 2 (default) - `Calendar.DAY_OF_MONTH` = 5 - `Calendar.DAY_OF_YEAR` = 6 - + * calendar_amount: Specifies the number of units to truncate entries. The value is an integer representing the amount (default = 1). - + Both `calendar_field` and `calendar_amount` must be provided as integers in the JSON transformer. If these parameters are not specified, the default behavior is truncation by 1 month. @@ -424,7 +442,7 @@ If these parameters are not specified, the default behavior is truncation by 1 m Example : Truncate calendar and calendar dates to the next 21 days: - + ``` {"op":"transform", "class":"org.onebusaway.gtfs_transformer.impl.TruncateNewCalendarStatements","calendar_field":6,"calendar_amount":21} ``` @@ -435,7 +453,7 @@ Truncate entries to the next 3 months: {"op":"transform", "class":"org.onebusaway.gtfs_transformer.impl.TruncateNewCalendarStatements","calendar_field":2,"calendar_amount":3} ``` -Additionally, after truncating the calendar entries, it is recommended to use a **retain operation** to ensure that only trips with valid calendar dates are retained. +Additionally, after truncating the calendar entries, it is recommended to use a **retain operation** to ensure that only trips with valid calendar dates are retained. Without this retain operation, the `trips.txt` file will contain trips with non-existent calendar dates, leading to invalid data. @@ -443,7 +461,7 @@ Without this retain operation, the `trips.txt` file will contain trips with non- {"op":"transform", "class":"org.onebusaway.gtfs_transformer.impl.TruncateNewCalendarStatements","calendar_field":6,"calendar_amount":21} {"op":"retain", "match":{"file":"calendar_dates.txt"}, "retainBlocks":false} ``` - + #### Merge Trips and Simplify Calendar Entries Some agencies model their transit schedule favoring multiple entries in calendar_dates.txt as opposed to a more concise @@ -459,7 +477,7 @@ calendar entries to match. To run it, apply the following transform: ``` The transform takes additional optional arguments to control its behavior: - + * min_number_of_weeks_for_calendar_entry - how many weeks does a service id need to span before it gets its own entry in calendar.txt (default=3) @@ -473,14 +491,14 @@ The transform takes additional optional arguments to control its behavior: Frequency is defined as how often the target day of the week occurs vs the count for day of the week appearing MOST frequently for the service id (default=0.5) - + * undo_google_transit_data_feed_merge_tool - set to true to indicate that merged trip ids, as produced by the [GoogleTransitDataFeedMergeTool](http://code.google.com/p/googletransitdatafeed/wiki/Merge), should be un-mangled where possible. Merged trip ids will often have the form `OriginalTripId_merged_1234567`. We attempt to set the trip id back to `OrginalTripId` where appropriate. - - + + #### Shift Negative Stop Times Some agencies have trips that they model as starting BEFORE midnight on a given service date. For these agencies, it @@ -496,21 +514,21 @@ To run it, apply the following transform: ``` {"op":"shift_negative_stop_times"} ``` - + _A note on negative stop times:_ When writing negative stop times, the negative value ONLY applies to the hour portion of the time. Here are a few examples: - + * "-01:45:00" => "23:45:00" on the previous day - - * "-05:13:32" => "19:13:32" on the previous day + + * "-05:13:32" => "19:13:32" on the previous day * Remove non-revenue stops - Stop_times which do not allow pick up or drop off are also known as non-revenue stops. Some GTFS consumers display - these stops as if they were stops that passengers can use, at which point it is helpful to remove them. - + Stop_times which do not allow pick up or drop off are also known as non-revenue stops. Some GTFS consumers display + these stops as if they were stops that passengers can use, at which point it is helpful to remove them. + To remove them, apply the following transform: - + ``` {"op":"remove_non_revenue_stops"} ``` @@ -579,7 +597,7 @@ to support those routes. Consider an existing feed with a number of routes and stops. We can add an entirely new route, with trips and stop-times and frequency-based service, using the transform. This can be handy to add temporary service to an existing feed. - + ``` {"op":"add", "obj":{"file":"routes.txt", "route_id":"r0", "route_long_name":"Temporary Shuttle", "route_type":3}} @@ -595,4 +613,27 @@ and frequency-based service, using the transform. This can be handy to add temp {"op":"stop_times_factory", "trip_id":"t1", "start_time":"06:00:00", "end_time":"06:20:00", "stop_ids":["s3", "s2", "s1", "s0"]} ``` - +### Clip National GTFS for Regional Integration and Consistency + +This section of the document describes how to reduce a large GTFS to a smaller area. Several transformations can be applied to a national GTFS to clean it up and adjust the data to a regional area in order to get ready for the integration with another regional GTFS. Below is an overview of the operations carried out: + + * Removing Inactive Calendars and Dates. + * Truncating Calendars and Dates to 21 days. + * Retaining Data Within a Specific Geographic Area: a small geographic area is used for retaining only the entities within our area of interest. All routes and trips that do not pass through this area will therefore be eliminated. + * Trimming Stop Times Outside a Specific Geographic Area: a larger polygon is used to ensure that only the relevant stops_times within a wider region are retained. That means that all trips that go outside the area are truncated. + * Clean up entities that are no longer referenced by any trips. + +RetainUpFromPolygon and TrimTripFromPolygon together will clip the GTFS data to a small area and allow some Origin/Destination transit to nearby cities. + +``` +{"op":"transform", "class":"org.onebusaway.gtfs_transformer.impl.RemoveOldCalendarStatements"} +{"op":"transform", "class":"org.onebusaway.gtfs_transformer.impl.TruncateNewCalendarStatements","calendar_field":6,"calendar_amount":21} +{"op":"retain", "match":{"file":"calendar_dates.txt"}, "retainBlocks":false} + +{"op":"transform","class":"org.onebusaway.gtfs_transformer.impl.RetainUpFromPolygon","polygon":"MULTIPOLYGON (((1.2 43.7, 1.55 43.7, 1.55 43.4, 1.2 43.4, 1.2 43.7)))"} + +{"op":"transform","class":"org.onebusaway.gtfs_transformer.impl.TrimTripFromPolygon","polygon":"MULTIPOLYGON (((1.0 44.2, 2.2 44.2, 2.2 43.3, 1.0 43.3, 1.0 44.2)))"} +{"op":"retain", "match":{"file":"trips.txt"}, "retainBlocks":false} +``` + +![RetainUpFromPolygon and TrimTripFromPolygon](onebusaway-gtfs-transformer-cli-sample1.png "RetainUpFromPolygon and TrimTripFromPolygon") \ No newline at end of file diff --git a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/TrimTripFromPolygon.java b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/TrimTripFromPolygon.java new file mode 100644 index 00000000..98ba9e6b --- /dev/null +++ b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/TrimTripFromPolygon.java @@ -0,0 +1,138 @@ +package org.onebusaway.gtfs_transformer.impl; + +import java.util.ArrayList; +import java.util.List; + +import org.locationtech.jts.io.ParseException; +import org.locationtech.jts.io.WKTReader; +import org.locationtech.jts.geom.*; + +import org.onebusaway.collections.beans.PropertyPathExpression; +import org.onebusaway.csv_entities.schema.annotations.CsvField; +import org.onebusaway.gtfs.model.Stop; +import org.onebusaway.gtfs.model.StopTime; +import org.onebusaway.gtfs.model.Trip; +import org.onebusaway.gtfs.services.GtfsMutableRelationalDao; +import org.onebusaway.gtfs_transformer.services.GtfsTransformStrategy; +import org.onebusaway.gtfs_transformer.services.TransformContext; +import org.onebusaway.gtfs_transformer.updates.TrimTripTransformStrategy; +import org.onebusaway.gtfs_transformer.updates.TrimTripTransformStrategy.TrimOperation; +import org.onebusaway.gtfs_transformer.match.PropertyValueEntityMatch; +import org.onebusaway.gtfs_transformer.match.SimpleValueMatcher; +import org.onebusaway.gtfs_transformer.match.TypedEntityMatch; + +public class TrimTripFromPolygon implements GtfsTransformStrategy { + private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory(); + private final WKTReader wktReader = new WKTReader(GEOMETRY_FACTORY); + + private final TrimTripTransformStrategy strategy = new TrimTripTransformStrategy(); + private final TransformContext context = new TransformContext(); + + @CsvField(optional = false) + private String polygon; + + @CsvField(ignore = true) + private Geometry polygonGeometry; + + public void setPolygon(String polygon) { + this.polygon = polygon; + this.polygonGeometry = buildPolygon(polygon); + + if (this.polygonGeometry == null || !this.polygonGeometry.isValid() || this.polygonGeometry.isEmpty()) { + throw new IllegalArgumentException("The provided polygon is invalid or empty."); + } + } + + @Override + public String getName() { + return this.getClass().getSimpleName(); + } + + @Override + public void run(TransformContext transformContext, GtfsMutableRelationalDao gtfsMutableRelationalDao) { + + for (Trip trip : gtfsMutableRelationalDao.getAllTrips()){ + // retrieve the list of StopTimes for the current trip + List stopTimes = gtfsMutableRelationalDao.getStopTimesForTrip(trip); + List stopTimesInPolygon = new ArrayList<>(); + + for (StopTime stopTime : stopTimes) { + Stop stop = gtfsMutableRelationalDao.getStopForId(stopTime.getStop().getId()); + + // add StopTime to the list if its Stop is within the polygon boundaries + if (insidePolygon(polygonGeometry, stop.getLon(), stop.getLat())) { + stopTimesInPolygon.add(stopTime); + } + } + + // if some stops are inside the polygon but not all, apply the TrimTrip operation + if (!stopTimesInPolygon.isEmpty() && stopTimesInPolygon.size() < stopTimes.size()) { + applyTrimOperation(gtfsMutableRelationalDao,trip, stopTimesInPolygon); + } + } + // execute the TrimTrip transformation strategy + strategy.run(context, gtfsMutableRelationalDao); + } + + private void applyTrimOperation(GtfsMutableRelationalDao gtfs, Trip trip, List stopTimes) { + // initialize a new TrimOperation object to define parameters + TrimOperation operation = new TrimOperation(); + + StopTime firstStopTime = stopTimes.get(0); + StopTime lastStopTime = stopTimes.get(stopTimes.size() - 1); + + // set 'ToStopId' of the trim operation if the first StopTime is not the first stopTime in the trip + if (firstStopTime.getStopSequence() > 0) { + StopTime previousStop = gtfs.getStopTimesForTrip(trip).get(firstStopTime.getStopSequence() - 1); + operation.setToStopId(previousStop.getStop().getId().getId()); + } + + // set 'FromStopId' of the trim operation if the last StopTime is not the last stopTime in the trip + if (lastStopTime.getStopSequence() < (gtfs.getStopTimesForTrip(trip).size()-1)) { + StopTime nextStop = gtfs.getStopTimesForTrip(trip).get(lastStopTime.getStopSequence() + 1); + operation.setFromStopId(nextStop.getStop().getId().getId()); + } + + // define the matching criteria + operation.setMatch(new TypedEntityMatch( + Trip.class, + new PropertyValueEntityMatch( + new PropertyPathExpression("id"), + new SimpleValueMatcher(trip.getId()) + ) + )); + + // add the TrimOperation to the strategy for later execution + strategy.addOperation(operation); + } + + /* + * Creates a Geometry object (polygon or multi-polygon) from the provided WKT string. + * + * @param polygonWKT The WKT representation of the polygon. + * @return The Geometry object. + * @throws IllegalArgumentException if the WKT string is invalid or cannot be parsed. + */ + private Geometry buildPolygon(String polygonWKT) { + try{ + return wktReader.read(polygonWKT); + } catch (ParseException e){ + throw new IllegalArgumentException( + String.format("Error parsing WKT string: %s", e.getMessage()), e + ); + } + } + /* + * insidePolygon Checks whether a given point (specified by its longitude and latitude) is inside a given polygon or multipolygon. + * + * @param geometry The Geometry object representing the polygon or multipolygon. + * @param lon the longitude of the point to check. + * @param lat the latitude of the point to check. + * @return true if the point is within the boundaries of the geometry; false otherwise. + */ + private boolean insidePolygon(Geometry geometry, double lon, double lat) { + Point point = GEOMETRY_FACTORY.createPoint(new Coordinate(lon, lat)); + return geometry.contains(point); + } + +} \ No newline at end of file diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/impl/TrimTripFromPolygonTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/impl/TrimTripFromPolygonTest.java new file mode 100644 index 00000000..586ad7f3 --- /dev/null +++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/impl/TrimTripFromPolygonTest.java @@ -0,0 +1,81 @@ +package org.onebusaway.gtfs_transformer.impl; +import static org.junit.jupiter.api.Assertions.assertEquals; +import java.io.IOException; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.onebusaway.gtfs.services.GtfsMutableRelationalDao; +import org.onebusaway.gtfs.services.MockGtfs; +import org.onebusaway.gtfs_transformer.services.TransformContext; + +public class TrimTripFromPolygonTest { + + private TrimTripFromPolygon trimTripFromPolygon = new TrimTripFromPolygon(); + private RetainUpFromPolygon retainUpFromPolygon = new RetainUpFromPolygon(); + private TransformContext _context = new TransformContext(); + private MockGtfs _gtfs; + + @BeforeEach + public void setup() throws IOException{ + + _gtfs = MockGtfs.create(); + // Insert mock data into the GTFS for testing: + // 1 agency + _gtfs.putAgencies(1); + // 4 routes + _gtfs.putRoutes(4); + // 4 trips + _gtfs.putTrips(4, "r$0","sid$0"); + // 8 stops + _gtfs.putStops(10); + // 13 stop times + _gtfs.putLines("stop_times.txt", + "trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled", + // Trip t0: sequence of stops s0,s1,s2,s3 + "t0,08:00:00,08:25:00,s0,0,,,,", + "t0,08:30:00,08:55:00,s1,1,,,,", + "t0,09:00:00,09:55:00,s2,2,,,,", + "t0,10:00:00,10:30:00,s3,3,,,,", + // Trip t1: reverse sequence of stops s3,s2,s1,s0 + "t1,08:00:00,08:25:00,s3,0,,,,", + "t1,08:30:00,08:55:00,s2,1,,,,", + "t1,09:00:00,09:55:00,s1,2,,,,", + "t1,10:00:00,10:00:00,s0,3,,,,", + // Trip t2: sequence of stops s2,s3,s4,s5 + "t2,09:00:00,09:55:00,s2,0,,,,", + "t2,10:00:00,10:55:00,s3,1,,,,", + "t2,11:00:00,11:25:00,s4,2,,,,", + "t2,11:30:00,11:55:00,s5,3,,,,", + // Trip t3: Additional stops + "t3,12:00:00,12:25:00,s6,0,,,,", + "t3,12:30:00,12:55:00,s7,1,,,,"); + } + + @Test + public void testTrimTripFromPolygon() throws IOException { + GtfsMutableRelationalDao dao = _gtfs.read(); + + // Define a polygon in WKT (Well-Known Text) format + // This polygon is designed to include only the first 4 stops (S0 to S4) + String polygonWKT = "POLYGON ((-122.308 47.653, -122.308 47.666, -122.307 47.666, -122.307 47.665, -122.307 47.661, -122.307 47.657, -122.307 47.653, -122.308 47.653))"; + trimTripFromPolygon.setPolygon(polygonWKT); + retainUpFromPolygon.setPolygon(polygonWKT); + + // Execute the retainUpFromPolygon strategy based on the polygon + retainUpFromPolygon.run(_context, dao); + // Execute the trimTripFromPolygon strategy based on the polygon + trimTripFromPolygon.run(_context, dao); + + // Verify that the number of routes is reduced to 3 + assertEquals(3,dao.getAllRoutes().size()); + + // Verify that the number of trips is reduced to 3 + assertEquals(3,dao.getAllTrips().size()); + + // Verify that the number of stops is reduced to 6 + assertEquals(6,dao.getAllStops().size()); + + // Verify that the number of stop times is reduced to 10 + assertEquals(10,dao.getAllStopTimes().size()); + } +} \ No newline at end of file