From aac0d4a848a92d9c186585ef6d330aa333e6a922 Mon Sep 17 00:00:00 2001 From: Jym Dyer Date: Mon, 26 Feb 2024 11:43:51 -0800 Subject: [PATCH 01/48] OTP-477 Utils for schema. --- .../middleware/utils/GraphQLUtils.java | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 src/main/java/org/opentripplanner/middleware/utils/GraphQLUtils.java diff --git a/src/main/java/org/opentripplanner/middleware/utils/GraphQLUtils.java b/src/main/java/org/opentripplanner/middleware/utils/GraphQLUtils.java new file mode 100644 index 000000000..2912c4ef2 --- /dev/null +++ b/src/main/java/org/opentripplanner/middleware/utils/GraphQLUtils.java @@ -0,0 +1,50 @@ +package org.opentripplanner.middleware.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; + +public class GraphQLUtils { + private static final Logger LOG = LoggerFactory.getLogger(GraphQLUtils.class); + + // Lazily-initialized in getSchema() + private static String schema = null; + + /** + * Location of the GraphQL plan file, as Java resource. + */ + public static final String GRAPHQL_RESOURCE = "queries/planQuery.graphql"; + + /** + * Return the full GraphQL plan file schema in Java string format, with {@code "} as {@code \"} + */ + public static String getSchema() { + if (GraphQLUtils.schema == null) { + GraphQLUtils.schema = schemaAsString(GRAPHQL_RESOURCE); + } + return GraphQLUtils.schema; + } + + /** + * Return a GraphQL schema in Java string format, with {@code "} as {@code \"} + * @param resource the plan file or any GraphQL file + */ + static String schemaAsString(String resource) { + StringBuilder builder = new StringBuilder(); + try (var reader = new BufferedReader(new InputStreamReader( + GraphQLUtils.class.getClassLoader().getResourceAsStream(resource) + ))) { + int value; + // All this low-level stuff is just to put a \ in front of " in the string. + while ((value = reader.read()) != -1) { + if ((char)value == '\n') builder.append("\\n"); + else if ((char)value == '"') builder.append("\\\""); + else builder.append((char) value); + } + } catch (Exception e) { + LOG.error("Can't find \"{}\" resource.", resource, e); + } + return builder.toString(); + } +} From a26fcc1aeb4705b2ed9e2fbe5cd234d620b63b86 Mon Sep 17 00:00:00 2001 From: Jym Dyer Date: Mon, 26 Feb 2024 11:45:13 -0800 Subject: [PATCH 02/48] OTP-477 The schema. Identical to one in OTP jar. --- src/main/resources/queries/planQuery.graphql | 240 +++++++++++++++++++ 1 file changed, 240 insertions(+) create mode 100644 src/main/resources/queries/planQuery.graphql diff --git a/src/main/resources/queries/planQuery.graphql b/src/main/resources/queries/planQuery.graphql new file mode 100644 index 000000000..3aa7d4372 --- /dev/null +++ b/src/main/resources/queries/planQuery.graphql @@ -0,0 +1,240 @@ +query Plan( + $arriveBy: Boolean + $banned: InputBanned + $bikeReluctance: Float + $carReluctance: Float + $date: String + $fromPlace: String! + $modes: [TransportMode] + $numItineraries: Int + $preferred: InputPreferred + $time: String + $toPlace: String! + $unpreferred: InputUnpreferred + $walkReluctance: Float + $walkSpeed: Float + $wheelchair: Boolean +) { + plan( + arriveBy: $arriveBy + banned: $banned + bikeReluctance: $bikeReluctance + carReluctance: $carReluctance + date: $date + fromPlace: $fromPlace + # Currently only supporting EN locale, used for times and text + locale: "en" + numItineraries: $numItineraries + preferred: $preferred + time: $time + toPlace: $toPlace + transportModes: $modes + unpreferred: $unpreferred + walkReluctance: $walkReluctance + walkSpeed: $walkSpeed + wheelchair: $wheelchair + ) { + itineraries { + accessibilityScore + duration + endTime + legs { + accessibilityScore + agency { + alerts { + alertDescriptionText + alertHeaderText + alertUrl + effectiveStartDate + id + } + id + name + timezone + url + } + arrivalDelay + departureDelay + distance + dropoffType + duration + endTime + fareProducts { + id + product { + __typename + id + medium { + id + name + } + name + riderCategory { + id + name + } + ... on DefaultFareProduct { + price { + amount + currency { + code + digits + } + } + } + } + } + from { + lat + lon + name + rentalVehicle { + id + network + } + stop { + alerts { + alertDescriptionText + alertHeaderText + alertUrl + effectiveStartDate + id + } + code + gtfsId + id + } + vertexType + } + headsign + interlineWithPreviousLeg + intermediateStops { + lat + locationType + lon + name + stopCode: code + stopId: id + } + legGeometry { + length + points + } + mode + pickupBookingInfo { + earliestBookingTime { + daysPrior + } + } + pickupType + realTime + realtimeState + rentedBike + rideHailingEstimate { + arrival + maxPrice { + amount + currency { + code + } + } + minPrice { + amount + currency { + code + } + } + provider { + id + } + } + route { + alerts { + alertDescriptionText + alertHeaderText + alertUrl + effectiveStartDate + id + } + color + id + longName + shortName + textColor + type + } + startTime + steps { + absoluteDirection + alerts { + alertDescriptionText + alertHeaderText + alertUrl + effectiveStartDate + id + } + area + distance + elevationProfile { + distance + elevation + } + lat + lon + relativeDirection + stayOn + streetName + } + to { + lat + lon + name + rentalVehicle { + id + network + } + stop { + alerts { + alertDescriptionText + alertHeaderText + alertUrl + effectiveStartDate + id + } + code + gtfsId + id + } + vertexType + } + transitLeg + trip { + arrivalStoptime { + stop { + gtfsId + id + } + stopPosition + } + departureStoptime { + stop { + gtfsId + id + } + stopPosition + } + gtfsId + id + } + } + startTime + waitingTime + walkTime + } + routingErrors { + code + description + inputField + } + } +} From 4e8a0a0ff7b0002f9385f959b28312f7743acf0f Mon Sep 17 00:00:00 2001 From: Jym Dyer Date: Mon, 26 Feb 2024 11:57:25 -0800 Subject: [PATCH 03/48] OTP-477 Method to post GraphQL requests. --- .../middleware/otp/OtpDispatcher.java | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/opentripplanner/middleware/otp/OtpDispatcher.java b/src/main/java/org/opentripplanner/middleware/otp/OtpDispatcher.java index c3a13124e..17c5ed38f 100644 --- a/src/main/java/org/opentripplanner/middleware/otp/OtpDispatcher.java +++ b/src/main/java/org/opentripplanner/middleware/otp/OtpDispatcher.java @@ -2,6 +2,7 @@ import java.util.Map; import org.eclipse.jetty.http.HttpMethod; +import org.opentripplanner.middleware.utils.GraphQLUtils; import org.opentripplanner.middleware.utils.HttpResponseValues; import org.opentripplanner.middleware.utils.HttpUtils; import org.opentripplanner.middleware.utils.ItineraryUtils; @@ -22,16 +23,15 @@ public class OtpDispatcher { private static final Logger LOG = LoggerFactory.getLogger(OtpDispatcher.class); - /** - * Location of the OTP plan endpoint (e.g. /routers/default/plan). - */ - public static final String OTP_PLAN_ENDPOINT = getConfigPropertyAsText("OTP_PLAN_ENDPOINT", "/routers/default/plan"); - /** * Location of the OTP GraphQL endpoint (e.g. /routers/default/index/graphql). */ public static final String OTP_GRAPHQL_ENDPOINT = getConfigPropertyAsText("OTP_GRAPHQL_ENDPOINT", "/routers/default/index/graphql"); + /** + * Location of the OTP plan endpoint (e.g. /routers/default/plan). + */ + public static final String OTP_PLAN_ENDPOINT = getConfigPropertyAsText("OTP_PLAN_ENDPOINT", "/routers/default/plan"); private static final int OTP_SERVER_REQUEST_TIMEOUT_IN_SECONDS = 10; /** @@ -57,6 +57,22 @@ public static OtpDispatcherResponse sendOtpPostRequest( return sendOtpRequest(buildOtpUri(version, query, path), HttpMethod.POST, headers, bodyContent); } + /** + * Send GraphQL POST request. Would ideally take a JSON Object, but the {@code "query"} object is + * followed by a string of JSON-ish GraphQL and the {@code "variables"} object is a proper JSON + * object of key/value string pairs so we put these together by hand. + * @version OTP version passed along to post request + * @variables a string of a JSON object of key/value string pairs + */ + public static OtpDispatcherResponse sendGraphQLPostRequest(OtpVersion version, String variables) { + var body = new StringBuilder("{\"query\":\""); + body.append(GraphQLUtils.getSchema()); + body.append("\",\\n\"variables\":"); + body.append(variables); + body.append("}"); + return sendOtpPostRequest(version, "", OTP_GRAPHQL_ENDPOINT, HttpUtils.HEADERS_JSON, body.toString()); + } + /** * Provides a response from the OTP server target service based on the query parameters provided. */ From 8bdfd9157617f89faf9b87400b622f0e02edd94c Mon Sep 17 00:00:00 2001 From: Jym Dyer Date: Mon, 26 Feb 2024 12:02:10 -0800 Subject: [PATCH 04/48] OTP-477 Custom method to build "variables" JSON object; post to GraphQL. --- .../middleware/models/ItineraryExistence.java | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/middleware/models/ItineraryExistence.java b/src/main/java/org/opentripplanner/middleware/models/ItineraryExistence.java index b0880ce5e..4ad00f8d3 100644 --- a/src/main/java/org/opentripplanner/middleware/models/ItineraryExistence.java +++ b/src/main/java/org/opentripplanner/middleware/models/ItineraryExistence.java @@ -22,6 +22,7 @@ import java.util.Date; import java.util.List; import java.util.Locale; +import java.util.Map; import static org.opentripplanner.middleware.utils.DateTimeUtils.DEFAULT_DATE_FORMAT_PATTERN; @@ -67,6 +68,7 @@ public class ItineraryExistence extends Model { * When the itinerary existence check was run/completed. * FIXME: If a monitored trip has not been fully enabled for monitoring, we may want to check the timestamp to * verify that the existence check has not gone stale. + * FIXME: Something other than deprecated Date() class here! */ public Date timestamp = new Date(); @@ -95,6 +97,41 @@ public ItineraryExistenceResult getResultForDayOfWeek(DayOfWeek dayOfWeek) { throw new IllegalArgumentException("Invalid day of week provided!"); } + /** + * Helper function to adapt OTP request parameters to GraphQL's JSON {@code variables} object, limited to + * variables specified in the GraphQL plan. Parameter values are all simply Strings, so we can't use + * {@code ObjectMapper} to get the types right, must hardcode them specifically for ItineraryExistence so + * that the values that are actually strings have {@code \"} around them. + */ + private static String paramsToVariables(Map params) { + StringBuilder builder = new StringBuilder("{"); + params.forEach((k, v) -> { + switch (k) { + case "arriveBy": + case "numItineraries": + builder.append("\"" + k + "\":" + v + ","); + break; + + case "date": + case "time": + case "fromPlace": + case "toPlace": + builder.append("\"" + k + "\":\"" + v + "\","); + break; + + case "mode": + builder.append("\"modes\":["); + String[] modes = v.split(","); + builder.append("],"); + break; + + default: + break; + } + }); + return builder.toString(); + } + /** * Helper function to set the existence check for a particular day of the week. */ @@ -186,7 +223,8 @@ public void checkExistence() { setResultForDayOfWeek(result, dayOfWeek); } // Send off each plan query to OTP. - OtpDispatcherResponse response = OtpDispatcher.sendOtpPlanRequest(OtpVersion.OTP1, otpRequest); + String variables = ItineraryExistence.paramsToVariables(otpRequest.requestParameters); + OtpDispatcherResponse response = OtpDispatcher.sendGraphQLPostRequest(OtpVersion.OTP2, variables); TripPlan plan = null; try { plan = response.getResponse().plan; From 29439b806836ebbcb3327bc04509e5f43bbc3057 Mon Sep 17 00:00:00 2001 From: Jym Dyer Date: Wed, 28 Feb 2024 09:53:19 -0800 Subject: [PATCH 05/48] Common JSON headers. --- .../opentripplanner/middleware/utils/HttpUtils.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/middleware/utils/HttpUtils.java b/src/main/java/org/opentripplanner/middleware/utils/HttpUtils.java index 37b43f332..8f2c55d5b 100644 --- a/src/main/java/org/opentripplanner/middleware/utils/HttpUtils.java +++ b/src/main/java/org/opentripplanner/middleware/utils/HttpUtils.java @@ -40,6 +40,14 @@ public class HttpUtils { private static final Logger LOG = LoggerFactory.getLogger(HttpUtils.class); + /** + * A constant for headers used when producing or consuming application/json. + */ + public static final Map HEADERS_JSON = Map.of( + "Accept", APPLICATION_JSON, + "Content-Type", APPLICATION_JSON + ); + /** * A constant for a list of MIME types containing application/json only. */ @@ -219,8 +227,8 @@ public static Date getDate(Request request, String paramName, String paramValue, localDate = DateTimeUtils.getDateFromParam(paramName, paramValue, DEFAULT_DATE_FORMAT_PATTERN); } catch (DateTimeParseException e) { logMessageAndHalt(request, HttpStatus.BAD_REQUEST_400, - String.format("%s value: %s is not a valid date. Must be in the format: %s", paramName, paramValue, - DEFAULT_DATE_FORMAT_PATTERN + String.format("%s value: %s is not a valid date. Must be in the format: %s", + paramName, paramValue, DEFAULT_DATE_FORMAT_PATTERN )); } From 32a32124e60a1aa002fa1253b778dc0921b8ba50 Mon Sep 17 00:00:00 2001 From: Jym Dyer Date: Thu, 7 Mar 2024 13:07:44 -0800 Subject: [PATCH 06/48] Change GraphQL endpoint. --- .../org/opentripplanner/middleware/otp/OtpDispatcher.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/middleware/otp/OtpDispatcher.java b/src/main/java/org/opentripplanner/middleware/otp/OtpDispatcher.java index 17c5ed38f..f90d9786a 100644 --- a/src/main/java/org/opentripplanner/middleware/otp/OtpDispatcher.java +++ b/src/main/java/org/opentripplanner/middleware/otp/OtpDispatcher.java @@ -24,9 +24,9 @@ public class OtpDispatcher { private static final Logger LOG = LoggerFactory.getLogger(OtpDispatcher.class); /** - * Location of the OTP GraphQL endpoint (e.g. /routers/default/index/graphql). + * Location of the OTP GraphQL endpoint (e.g. /gtfs/v1). */ - public static final String OTP_GRAPHQL_ENDPOINT = getConfigPropertyAsText("OTP_GRAPHQL_ENDPOINT", "/routers/default/index/graphql"); + public static final String OTP_GRAPHQL_ENDPOINT = getConfigPropertyAsText("OTP_GRAPHQL_ENDPOINT", "/gtfs/v1"); /** * Location of the OTP plan endpoint (e.g. /routers/default/plan). From 6c626e28457c6dfbe182433aefd3b1635a608686 Mon Sep 17 00:00:00 2001 From: Jym Dyer Date: Thu, 7 Mar 2024 14:54:00 -0800 Subject: [PATCH 07/48] Cure backslashitis. --- .../java/org/opentripplanner/middleware/otp/OtpDispatcher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/middleware/otp/OtpDispatcher.java b/src/main/java/org/opentripplanner/middleware/otp/OtpDispatcher.java index f90d9786a..304580e95 100644 --- a/src/main/java/org/opentripplanner/middleware/otp/OtpDispatcher.java +++ b/src/main/java/org/opentripplanner/middleware/otp/OtpDispatcher.java @@ -67,7 +67,7 @@ public static OtpDispatcherResponse sendOtpPostRequest( public static OtpDispatcherResponse sendGraphQLPostRequest(OtpVersion version, String variables) { var body = new StringBuilder("{\"query\":\""); body.append(GraphQLUtils.getSchema()); - body.append("\",\\n\"variables\":"); + body.append("\",\n\"variables\":"); body.append(variables); body.append("}"); return sendOtpPostRequest(version, "", OTP_GRAPHQL_ENDPOINT, HttpUtils.HEADERS_JSON, body.toString()); From 38c31703fab089e8ac8ab2bfe09dc231f97c5d59 Mon Sep 17 00:00:00 2001 From: Jym Dyer Date: Fri, 8 Mar 2024 13:51:07 -0800 Subject: [PATCH 08/48] Keep in sync with gtfsId and alerts. --- src/main/resources/queries/planQuery.graphql | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/main/resources/queries/planQuery.graphql b/src/main/resources/queries/planQuery.graphql index 3aa7d4372..665596777 100644 --- a/src/main/resources/queries/planQuery.graphql +++ b/src/main/resources/queries/planQuery.graphql @@ -48,11 +48,19 @@ query Plan( effectiveStartDate id } - id + gtfsId + id: gtfsId name timezone url } + alerts { + alertDescriptionText + alertHeaderText + alertUrl + effectiveStartDate + id + } arrivalDelay departureDelay distance @@ -103,6 +111,8 @@ query Plan( code gtfsId id + lat + lon } vertexType } @@ -157,7 +167,8 @@ query Plan( id } color - id + gtfsId + id: gtfsId longName shortName textColor @@ -204,6 +215,8 @@ query Plan( code gtfsId id + lat + lon } vertexType } @@ -228,6 +241,7 @@ query Plan( } } startTime + transfers: numberOfTransfers waitingTime walkTime } From 5a56f9f8236fffab4e88108e97d57317d4a60fde Mon Sep 17 00:00:00 2001 From: Jym Dyer Date: Mon, 11 Mar 2024 09:27:31 -0700 Subject: [PATCH 09/48] Method and constant renames per PR feedback. --- .../middleware/utils/GraphQLUtils.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/opentripplanner/middleware/utils/GraphQLUtils.java b/src/main/java/org/opentripplanner/middleware/utils/GraphQLUtils.java index 2912c4ef2..544318fa4 100644 --- a/src/main/java/org/opentripplanner/middleware/utils/GraphQLUtils.java +++ b/src/main/java/org/opentripplanner/middleware/utils/GraphQLUtils.java @@ -8,29 +8,29 @@ public class GraphQLUtils { private static final Logger LOG = LoggerFactory.getLogger(GraphQLUtils.class); - // Lazily-initialized in getSchema() - private static String schema = null; + // Lazily-initialized in getPlanQueryTemplate() + private static String planQueryTemplate = null; /** - * Location of the GraphQL plan file, as Java resource. + * Location of the GraphQL plan query template file, as Java resource. */ - public static final String GRAPHQL_RESOURCE = "queries/planQuery.graphql"; + public static final String PLAN_QUERY_RESOURCE = "queries/planQuery.graphql"; /** - * Return the full GraphQL plan file schema in Java string format, with {@code "} as {@code \"} + * Return the full GraphQL plan file planQueryTemplate in Java string format, with {@code "} as {@code \"} */ - public static String getSchema() { - if (GraphQLUtils.schema == null) { - GraphQLUtils.schema = schemaAsString(GRAPHQL_RESOURCE); + public static String getPlanQueryTemplate() { + if (GraphQLUtils.planQueryTemplate == null) { + GraphQLUtils.planQueryTemplate = planQueryTemplateAsString(PLAN_QUERY_RESOURCE); } - return GraphQLUtils.schema; + return GraphQLUtils.planQueryTemplate; } /** - * Return a GraphQL schema in Java string format, with {@code "} as {@code \"} + * Return a GraphQL planQueryTemplate in Java string format, with {@code "} as {@code \"} * @param resource the plan file or any GraphQL file */ - static String schemaAsString(String resource) { + static String planQueryTemplateAsString(String resource) { StringBuilder builder = new StringBuilder(); try (var reader = new BufferedReader(new InputStreamReader( GraphQLUtils.class.getClassLoader().getResourceAsStream(resource) From 4cec6ac87d2c5d45ce472b86a76fcf568e7a36df Mon Sep 17 00:00:00 2001 From: Jym Dyer Date: Mon, 11 Mar 2024 09:29:01 -0700 Subject: [PATCH 10/48] Use common JSON headers. --- .../opentripplanner/middleware/utils/NotificationUtils.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/java/org/opentripplanner/middleware/utils/NotificationUtils.java b/src/main/java/org/opentripplanner/middleware/utils/NotificationUtils.java index 4e9e8cbcb..e4f04fd8e 100644 --- a/src/main/java/org/opentripplanner/middleware/utils/NotificationUtils.java +++ b/src/main/java/org/opentripplanner/middleware/utils/NotificationUtils.java @@ -86,15 +86,11 @@ static String sendPush(String toUser, String body) { try { NotificationInfo notifInfo = new NotificationInfo(toUser, body.substring(0, Math.min(PUSH_MESSAGE_MAX_LENGTH, body.length()))); var jsonBody = new Gson().toJson(notifInfo); - Map headers = Map.of( - "Accept", "application/json", - "Content-Type", "application/json" - ); var httpResponse = HttpUtils.httpRequestRawResponse( URI.create(PUSH_API_URL + "/notification/publish?api_key=" + PUSH_API_KEY), 1000, HttpMethod.POST, - headers, + HttpUtils.HEADERS_JSON, jsonBody ); if (httpResponse.status == 200) { From cfb9135d0cc47b189303871d94ae836be5cdb21c Mon Sep 17 00:00:00 2001 From: Jym Dyer Date: Mon, 11 Mar 2024 09:31:54 -0700 Subject: [PATCH 11/48] Better Javadoc, handle modes and commas per PR feedback. --- .../middleware/models/ItineraryExistence.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/opentripplanner/middleware/models/ItineraryExistence.java b/src/main/java/org/opentripplanner/middleware/models/ItineraryExistence.java index 4ad00f8d3..7dea7151c 100644 --- a/src/main/java/org/opentripplanner/middleware/models/ItineraryExistence.java +++ b/src/main/java/org/opentripplanner/middleware/models/ItineraryExistence.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.stream.Stream; import static org.opentripplanner.middleware.utils.DateTimeUtils.DEFAULT_DATE_FORMAT_PATTERN; @@ -98,20 +99,22 @@ public ItineraryExistenceResult getResultForDayOfWeek(DayOfWeek dayOfWeek) { } /** - * Helper function to adapt OTP request parameters to GraphQL's JSON {@code variables} object, limited to - * variables specified in the GraphQL plan. Parameter values are all simply Strings, so we can't use - * {@code ObjectMapper} to get the types right, must hardcode them specifically for ItineraryExistence so - * that the values that are actually strings have {@code \"} around them. + * Helper method to adapt OTP request parameters to GraphQL's JSON {@code variables} object, limited to + * variables specified in the GraphQL query plan. Parameter values are all simply {@code String}s, so we + * can't use JSON packages to get the types right, must hardcode them specifically for ItineraryExistence + * so that only the values that are actually {@code String}s have {@code \"} around them. */ private static String paramsToVariables(Map params) { StringBuilder builder = new StringBuilder("{"); params.forEach((k, v) -> { switch (k) { + // Don't put quotes around these values. case "arriveBy": case "numItineraries": builder.append("\"" + k + "\":" + v + ","); break; + // Put quotes around those values, they are String types. case "date": case "time": case "fromPlace": @@ -119,9 +122,11 @@ private static String paramsToVariables(Map params) { builder.append("\"" + k + "\":\"" + v + "\","); break; + // From "mode" to GraphQL "$modes". case "mode": builder.append("\"modes\":["); - String[] modes = v.split(","); + Stream.of(v.split(",")).forEach(m -> builder.append("{\"mode\":\"" + m + "\"},")); + builder.deleteCharAt(builder.length() - 1); // Remove trailing comma. builder.append("],"); break; @@ -129,6 +134,7 @@ private static String paramsToVariables(Map params) { break; } }); + builder.setCharAt(builder.length() - 1, '}'); // Replace trailing comma with closing brace. return builder.toString(); } From 27f33e6f6c7114c57868b68e9c2c51f56d16954b Mon Sep 17 00:00:00 2001 From: Jym Dyer Date: Mon, 11 Mar 2024 09:32:34 -0700 Subject: [PATCH 12/48] Update GraphQL endpoint. --- .../middleware/otp/OtpDispatcher.java | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/opentripplanner/middleware/otp/OtpDispatcher.java b/src/main/java/org/opentripplanner/middleware/otp/OtpDispatcher.java index 304580e95..bdf1cbfe1 100644 --- a/src/main/java/org/opentripplanner/middleware/otp/OtpDispatcher.java +++ b/src/main/java/org/opentripplanner/middleware/otp/OtpDispatcher.java @@ -24,14 +24,15 @@ public class OtpDispatcher { private static final Logger LOG = LoggerFactory.getLogger(OtpDispatcher.class); /** - * Location of the OTP GraphQL endpoint (e.g. /gtfs/v1). + * Location of the OTP plan endpoint (e.g. /routers/default/plan). */ - public static final String OTP_GRAPHQL_ENDPOINT = getConfigPropertyAsText("OTP_GRAPHQL_ENDPOINT", "/gtfs/v1"); + public static final String OTP_PLAN_ENDPOINT = getConfigPropertyAsText("OTP_PLAN_ENDPOINT", "/routers/default/plan"); /** - * Location of the OTP plan endpoint (e.g. /routers/default/plan). + * Location of the OTP GraphQL endpoint (e.g. /gtfs/v1). */ - public static final String OTP_PLAN_ENDPOINT = getConfigPropertyAsText("OTP_PLAN_ENDPOINT", "/routers/default/plan"); + public static final String OTP_GRAPHQL_ENDPOINT = getConfigPropertyAsText("OTP_GRAPHQL_ENDPOINT", "/gtfs/v1"); + private static final int OTP_SERVER_REQUEST_TIMEOUT_IN_SECONDS = 10; /** @@ -58,19 +59,15 @@ public static OtpDispatcherResponse sendOtpPostRequest( } /** - * Send GraphQL POST request. Would ideally take a JSON Object, but the {@code "query"} object is - * followed by a string of JSON-ish GraphQL and the {@code "variables"} object is a proper JSON - * object of key/value string pairs so we put these together by hand. - * @version OTP version passed along to post request - * @variables a string of a JSON object of key/value string pairs + * Send GraphQL POST request. Builds a JSON object with two fields. The first field is {@code "query"} + * and its value is a long String of the entire JSON-ish plan query template. The second field is + * {@code "variables"} and its value is a proper JSON object of key/value string pairs. + * @param version OTP version passed along to post request + * @param variables a string of a proper JSON object of key/value string pairs */ public static OtpDispatcherResponse sendGraphQLPostRequest(OtpVersion version, String variables) { - var body = new StringBuilder("{\"query\":\""); - body.append(GraphQLUtils.getSchema()); - body.append("\",\n\"variables\":"); - body.append(variables); - body.append("}"); - return sendOtpPostRequest(version, "", OTP_GRAPHQL_ENDPOINT, HttpUtils.HEADERS_JSON, body.toString()); + String body = "{\"query\":\"" + GraphQLUtils.getPlanQueryTemplate() + "\",\n\"variables\":" + variables + "}"; + return sendOtpPostRequest(version, "", OTP_GRAPHQL_ENDPOINT, HttpUtils.HEADERS_JSON, body); } /** From 387179159a23eff1457b32e8a7a3b5cb90076fca Mon Sep 17 00:00:00 2001 From: Jym Dyer Date: Mon, 11 Mar 2024 11:00:30 -0700 Subject: [PATCH 13/48] Cast char explictly per PR feedback. --- .../org/opentripplanner/middleware/utils/GraphQLUtils.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/opentripplanner/middleware/utils/GraphQLUtils.java b/src/main/java/org/opentripplanner/middleware/utils/GraphQLUtils.java index 544318fa4..fa6481878 100644 --- a/src/main/java/org/opentripplanner/middleware/utils/GraphQLUtils.java +++ b/src/main/java/org/opentripplanner/middleware/utils/GraphQLUtils.java @@ -38,9 +38,10 @@ static String planQueryTemplateAsString(String resource) { int value; // All this low-level stuff is just to put a \ in front of " in the string. while ((value = reader.read()) != -1) { - if ((char)value == '\n') builder.append("\\n"); - else if ((char)value == '"') builder.append("\\\""); - else builder.append((char) value); + char c = (char)value; + if (c == '\n') builder.append("\\n"); + else if (c == '"') builder.append("\\\""); + else builder.append(c); } } catch (Exception e) { LOG.error("Can't find \"{}\" resource.", resource, e); From 3f8d997205b47921051276415493ebaef4fb2298 Mon Sep 17 00:00:00 2001 From: Jym Dyer Date: Wed, 20 Mar 2024 16:38:02 -0700 Subject: [PATCH 14/48] Workaround since we've got an `overrideMode` where I think we need a `mode`. --- .../middleware/models/ItineraryExistence.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/middleware/models/ItineraryExistence.java b/src/main/java/org/opentripplanner/middleware/models/ItineraryExistence.java index 7dea7151c..4fff7f0eb 100644 --- a/src/main/java/org/opentripplanner/middleware/models/ItineraryExistence.java +++ b/src/main/java/org/opentripplanner/middleware/models/ItineraryExistence.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.stream.Collectors; import java.util.stream.Stream; import static org.opentripplanner.middleware.utils.DateTimeUtils.DEFAULT_DATE_FORMAT_PATTERN; @@ -124,8 +125,12 @@ private static String paramsToVariables(Map params) { // From "mode" to GraphQL "$modes". case "mode": + //FIXME: Mapping "LINK_LIGHT_RAIL" overrideMode to a valid mode is temporary workaround! + var modes = Stream.of(v.split(",")) + .map(m -> "LINK_LIGHT_RAIL".equals(m) ? "TRAM" : m) + .collect(Collectors.toSet()); builder.append("\"modes\":["); - Stream.of(v.split(",")).forEach(m -> builder.append("{\"mode\":\"" + m + "\"},")); + modes.forEach(m -> builder.append("{\"mode\":\"" + m + "\"},")); builder.deleteCharAt(builder.length() - 1); // Remove trailing comma. builder.append("],"); break; From 2100a5266de8cc3bd8853e04008e0141a0305cc6 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 17 Jul 2024 15:31:12 -0400 Subject: [PATCH 15/48] fix(OtpDispatcherResponse): Correctly parse OTP2 responses. --- .../middleware/OtpMiddlewareMain.java | 2 +- .../controllers/api/OtpRequestProcessor.java | 17 ++++++++--------- .../middleware/models/TripRequest.java | 6 +++--- .../middleware/otp/OtpDispatcherResponse.java | 3 ++- .../otp/response/OtpResponseGraphQLWrapper.java | 13 +++++++++++++ 5 files changed, 27 insertions(+), 14 deletions(-) create mode 100644 src/main/java/org/opentripplanner/middleware/otp/response/OtpResponseGraphQLWrapper.java diff --git a/src/main/java/org/opentripplanner/middleware/OtpMiddlewareMain.java b/src/main/java/org/opentripplanner/middleware/OtpMiddlewareMain.java index e8d09b7c5..85c263b4d 100644 --- a/src/main/java/org/opentripplanner/middleware/OtpMiddlewareMain.java +++ b/src/main/java/org/opentripplanner/middleware/OtpMiddlewareMain.java @@ -106,7 +106,7 @@ private static void initializeHttpEndpoints() throws IOException, InterruptedExc new LogController(API_PREFIX), new ErrorEventsController(API_PREFIX), new CDPFilesController(API_PREFIX), - new OtpRequestProcessor("/otp", OtpVersion.OTP1), + new OtpRequestProcessor("/otp", OtpVersion.OTP2), new OtpRequestProcessor("/otp2", OtpVersion.OTP2) // TODO Add other models. )) diff --git a/src/main/java/org/opentripplanner/middleware/controllers/api/OtpRequestProcessor.java b/src/main/java/org/opentripplanner/middleware/controllers/api/OtpRequestProcessor.java index 362bdcb7e..cb3e234f8 100644 --- a/src/main/java/org/opentripplanner/middleware/controllers/api/OtpRequestProcessor.java +++ b/src/main/java/org/opentripplanner/middleware/controllers/api/OtpRequestProcessor.java @@ -189,15 +189,13 @@ private String proxyPost(Request request, Response response) { * * Other requests will still be proxied, just not stored. */ + // TODO: Tighten up the map types HashMap graphQlVariables = (HashMap) getPOJOFromJSON(requestBody, Map.class).get("variables"); - String fromPlace = (String) graphQlVariables.get("fromPlace"); - String toPlace = (String) graphQlVariables.get("toPlace"); - // Follows the method used in otp-ui core-utils storage.js String randomBatchId = Integer.toString((int) (Math.random() * 1_000_000_000), 36); - if(!handlePlanTripResponse(randomBatchId, fromPlace, toPlace, otpDispatcherResponse, otpUser)) { + if(!handlePlanTripResponse(randomBatchId, graphQlVariables, otpDispatcherResponse, otpUser)) { logMessageAndHalt( request, HttpStatus.INTERNAL_SERVER_ERROR_500, @@ -277,8 +275,7 @@ private static boolean handlePlanTripResponse( ) { return handlePlanTripResponse( request.queryParams("batchId"), - request.queryParams("fromPlace"), - request.queryParams("toPlace"), + request.params(), otpDispatcherResponse, otpUser ); @@ -286,8 +283,7 @@ private static boolean handlePlanTripResponse( private static boolean handlePlanTripResponse( String batchId, - String fromPlace, - String toPlace, + Map graphQlVariables, OtpDispatcherResponse otpDispatcherResponse, OtpUser otpUser ) { @@ -309,12 +305,15 @@ private static boolean handlePlanTripResponse( } if (otpResponse != null) { + String fromPlace = (String) graphQlVariables.get("fromPlace"); + String toPlace = (String) graphQlVariables.get("toPlace"); + TripRequest tripRequest = new TripRequest( otpUser.id, batchId, fromPlace, toPlace, - otpResponse.requestParameters + graphQlVariables ); // only save trip summary if the trip request was saved boolean tripRequestSaved = Persistence.tripRequests.create(tripRequest); diff --git a/src/main/java/org/opentripplanner/middleware/models/TripRequest.java b/src/main/java/org/opentripplanner/middleware/models/TripRequest.java index e72866339..afdd497a4 100644 --- a/src/main/java/org/opentripplanner/middleware/models/TripRequest.java +++ b/src/main/java/org/opentripplanner/middleware/models/TripRequest.java @@ -5,7 +5,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.HashMap; +import java.util.Map; import static com.mongodb.client.model.Filters.eq; import static org.opentripplanner.middleware.persistence.TypedPersistence.filterByUserId; @@ -39,7 +39,7 @@ public class TripRequest extends Model { public String toPlace; /** A dictionary of the parameters provided in the request that triggered this response. */ - public HashMap requestParameters; + public Map requestParameters; /** * This no-arg constructor exists to make MongoDB happy. @@ -52,7 +52,7 @@ public TripRequest( String batchId, String fromPlace, String toPlace, - HashMap requestParameters + Map requestParameters ) { this.userId = userId; this.batchId = batchId; diff --git a/src/main/java/org/opentripplanner/middleware/otp/OtpDispatcherResponse.java b/src/main/java/org/opentripplanner/middleware/otp/OtpDispatcherResponse.java index 0f74c6180..6a184a822 100644 --- a/src/main/java/org/opentripplanner/middleware/otp/OtpDispatcherResponse.java +++ b/src/main/java/org/opentripplanner/middleware/otp/OtpDispatcherResponse.java @@ -6,6 +6,7 @@ import org.apache.http.Header; import org.opentripplanner.middleware.bugsnag.BugsnagReporter; import org.opentripplanner.middleware.otp.response.OtpResponse; +import org.opentripplanner.middleware.otp.response.OtpResponseGraphQLWrapper; import org.opentripplanner.middleware.utils.HttpResponseValues; import org.opentripplanner.middleware.utils.JsonUtils; import org.slf4j.Logger; @@ -67,7 +68,7 @@ public OtpDispatcherResponse(String otpResponse, URI requestUri) { */ public OtpResponse getResponse() throws JsonProcessingException { try { - return JsonUtils.getPOJOFromJSON(responseBody, OtpResponse.class); + return JsonUtils.getPOJOFromJSON(responseBody, OtpResponseGraphQLWrapper.class).data; } catch (JsonProcessingException e) { BugsnagReporter.reportErrorToBugsnag("Failed to parse OTP response!", responseBody, e); throw e; diff --git a/src/main/java/org/opentripplanner/middleware/otp/response/OtpResponseGraphQLWrapper.java b/src/main/java/org/opentripplanner/middleware/otp/response/OtpResponseGraphQLWrapper.java new file mode 100644 index 000000000..b6ea964ec --- /dev/null +++ b/src/main/java/org/opentripplanner/middleware/otp/response/OtpResponseGraphQLWrapper.java @@ -0,0 +1,13 @@ +package org.opentripplanner.middleware.otp.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * Represents a trip planner response + * + * This is a pared-down version of the org.opentripplanner.api.resource.Response class in OpenTripPlanner. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class OtpResponseGraphQLWrapper { + public OtpResponse data; +} \ No newline at end of file From 7feadadfe9cbd93fe2a629e0e1014ed3624efe27 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 17 Jul 2024 17:58:47 -0400 Subject: [PATCH 16/48] fix(OtpRequestProcessor): Store graphQL params natively in Mongo. --- .../controllers/api/OtpRequestProcessor.java | 24 ++++++++----------- .../middleware/models/TripRequest.java | 19 ++++++++++++++- .../middleware/otp/OtpGraphQLQuery.java | 8 +++++++ .../otp/OtpGraphQLRoutesAndTrips.java | 7 ++++++ .../otp/OtpGraphQLTransportMode.java | 10 ++++++++ .../middleware/otp/OtpGraphQLVariables.java | 22 +++++++++++++++++ 6 files changed, 75 insertions(+), 15 deletions(-) create mode 100644 src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLQuery.java create mode 100644 src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLRoutesAndTrips.java create mode 100644 src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLTransportMode.java create mode 100644 src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java diff --git a/src/main/java/org/opentripplanner/middleware/controllers/api/OtpRequestProcessor.java b/src/main/java/org/opentripplanner/middleware/controllers/api/OtpRequestProcessor.java index cb3e234f8..c8210ab5b 100644 --- a/src/main/java/org/opentripplanner/middleware/controllers/api/OtpRequestProcessor.java +++ b/src/main/java/org/opentripplanner/middleware/controllers/api/OtpRequestProcessor.java @@ -7,7 +7,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import java.util.Arrays; import java.util.HashMap; -import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -18,6 +17,8 @@ import org.opentripplanner.middleware.models.TripRequest; import org.opentripplanner.middleware.models.TripSummary; import org.opentripplanner.middleware.otp.OtpDispatcher; +import org.opentripplanner.middleware.otp.OtpGraphQLQuery; +import org.opentripplanner.middleware.otp.OtpGraphQLVariables; import org.opentripplanner.middleware.otp.OtpVersion; import org.opentripplanner.middleware.otp.OtpDispatcherResponse; import org.opentripplanner.middleware.otp.response.OtpResponse; @@ -121,7 +122,7 @@ public void bind(final SparkSwagger restApi) { * trip history) the response is intercepted and processed. In all cases, the response from OTP (content and HTTP * status) is passed back to the requester. */ - private String proxyGet(Request request, spark.Response response) { + private String proxyGet(Request request, spark.Response response) throws JsonProcessingException { OtpUser otpUser = checkUserPermissions(request); // Get request path intended for OTP API by removing the proxy endpoint (/otp). String otpRequestPath = request.uri().replaceFirst(basePath, ""); @@ -133,7 +134,7 @@ private String proxyGet(Request request, spark.Response response) { } // If the request path ends with the plan endpoint (e.g., '/plan' or '/default/plan'), process response. if (otpRequestPath.endsWith(OtpDispatcher.OTP_PLAN_ENDPOINT) && otpUser != null) { - if(!handlePlanTripResponse(request, otpDispatcherResponse, otpUser)) { + if (!handlePlanTripResponse(request, otpDispatcherResponse, otpUser)) { logMessageAndHalt( request, HttpStatus.INTERNAL_SERVER_ERROR_500, @@ -189,8 +190,7 @@ private String proxyPost(Request request, Response response) { * * Other requests will still be proxied, just not stored. */ - // TODO: Tighten up the map types - HashMap graphQlVariables = (HashMap) getPOJOFromJSON(requestBody, Map.class).get("variables"); + OtpGraphQLVariables graphQlVariables = getPOJOFromJSON(requestBody, OtpGraphQLQuery.class).variables; // Follows the method used in otp-ui core-utils storage.js String randomBatchId = Integer.toString((int) (Math.random() * 1_000_000_000), 36); @@ -272,10 +272,10 @@ private static boolean handlePlanTripResponse( Request request, OtpDispatcherResponse otpDispatcherResponse, OtpUser otpUser - ) { + ) throws JsonProcessingException { return handlePlanTripResponse( request.queryParams("batchId"), - request.params(), + getPOJOFromJSON(request.body(), OtpGraphQLQuery.class).variables, otpDispatcherResponse, otpUser ); @@ -283,7 +283,7 @@ private static boolean handlePlanTripResponse( private static boolean handlePlanTripResponse( String batchId, - Map graphQlVariables, + OtpGraphQLVariables graphQlVariables, OtpDispatcherResponse otpDispatcherResponse, OtpUser otpUser ) { @@ -305,14 +305,11 @@ private static boolean handlePlanTripResponse( } if (otpResponse != null) { - String fromPlace = (String) graphQlVariables.get("fromPlace"); - String toPlace = (String) graphQlVariables.get("toPlace"); - TripRequest tripRequest = new TripRequest( otpUser.id, batchId, - fromPlace, - toPlace, + graphQlVariables.fromPlace, + graphQlVariables.toPlace, graphQlVariables ); // only save trip summary if the trip request was saved @@ -329,5 +326,4 @@ private static boolean handlePlanTripResponse( LOG.debug("Trip storage added {} ms", DateTimeUtils.currentTimeMillis() - tripStorageStartTime); return result; } - } diff --git a/src/main/java/org/opentripplanner/middleware/models/TripRequest.java b/src/main/java/org/opentripplanner/middleware/models/TripRequest.java index afdd497a4..69fe6f191 100644 --- a/src/main/java/org/opentripplanner/middleware/models/TripRequest.java +++ b/src/main/java/org/opentripplanner/middleware/models/TripRequest.java @@ -1,6 +1,7 @@ package org.opentripplanner.middleware.models; import com.mongodb.client.FindIterable; +import org.opentripplanner.middleware.otp.OtpGraphQLVariables; import org.opentripplanner.middleware.persistence.Persistence; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,6 +42,8 @@ public class TripRequest extends Model { /** A dictionary of the parameters provided in the request that triggered this response. */ public Map requestParameters; + public OtpGraphQLVariables graphQLVariables; + /** * This no-arg constructor exists to make MongoDB happy. */ @@ -52,7 +55,7 @@ public TripRequest( String batchId, String fromPlace, String toPlace, - Map requestParameters + Map requestParameters ) { this.userId = userId; this.batchId = batchId; @@ -61,6 +64,20 @@ public TripRequest( this.requestParameters = requestParameters; } + public TripRequest( + String userId, + String batchId, + String fromPlace, + String toPlace, + OtpGraphQLVariables graphQLVariables + ) { + this.userId = userId; + this.batchId = batchId; + this.fromPlace = fromPlace; + this.toPlace = toPlace; + this.graphQLVariables = graphQLVariables; + } + @Override public String toString() { return "TripRequest{" + diff --git a/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLQuery.java b/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLQuery.java new file mode 100644 index 000000000..44e0f3efd --- /dev/null +++ b/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLQuery.java @@ -0,0 +1,8 @@ +package org.opentripplanner.middleware.otp; + +/** Wraps an OTP GraphQL query data */ +public class OtpGraphQLQuery { + public String query; + + public OtpGraphQLVariables variables; +} diff --git a/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLRoutesAndTrips.java b/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLRoutesAndTrips.java new file mode 100644 index 000000000..5ae00c62b --- /dev/null +++ b/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLRoutesAndTrips.java @@ -0,0 +1,7 @@ +package org.opentripplanner.middleware.otp; + +/** OTP GraphQL data structure for preferred/unpreferred/banned routes and trips */ +public class OtpGraphQLRoutesAndTrips { + public String routes; + public String trips; +} diff --git a/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLTransportMode.java b/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLTransportMode.java new file mode 100644 index 000000000..726f16340 --- /dev/null +++ b/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLTransportMode.java @@ -0,0 +1,10 @@ +package org.opentripplanner.middleware.otp; + +/** Describes a transport mode for OTP GraphQL */ +public class OtpGraphQLTransportMode { + /** A mode such as WALK, BUS, BIKE */ + public String mode; + + /** Optional qualifier such as RENT for bike or other vehicle rentals. */ + public String qualifier; +} diff --git a/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java b/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java new file mode 100644 index 000000000..f8527770e --- /dev/null +++ b/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java @@ -0,0 +1,22 @@ +package org.opentripplanner.middleware.otp; + +import java.util.List; + +/** OTP 'plan' query variables */ +public class OtpGraphQLVariables { + public boolean arriveBy; + public OtpGraphQLRoutesAndTrips banned; + public float bikeReluctance; + public float carReluctance; + public String date; + public String fromPlace; + public List modes; + public int numItineraries; + public OtpGraphQLRoutesAndTrips preferred; + public String time; + public String toPlace; + public OtpGraphQLRoutesAndTrips unpreferred; + public float walkReluctance; + public float walkSpeed; + public boolean wheelchair; +} From d049679d0eb122727945ad374136d67ababf8668 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 18 Jul 2024 17:07:42 -0400 Subject: [PATCH 17/48] refactor(ItineraryUtils): Improve extraction of modes from itinerary. --- .../otp/OtpGraphQLTransportMode.java | 16 +++++++++ .../middleware/utils/ItineraryUtils.java | 35 +++++++++++-------- .../middleware/utils/ItineraryUtilsTest.java | 31 ++++++++++++++++ 3 files changed, 67 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLTransportMode.java b/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLTransportMode.java index 726f16340..76c447e65 100644 --- a/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLTransportMode.java +++ b/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLTransportMode.java @@ -1,5 +1,7 @@ package org.opentripplanner.middleware.otp; +import org.apache.commons.lang3.StringUtils; + /** Describes a transport mode for OTP GraphQL */ public class OtpGraphQLTransportMode { /** A mode such as WALK, BUS, BIKE */ @@ -7,4 +9,18 @@ public class OtpGraphQLTransportMode { /** Optional qualifier such as RENT for bike or other vehicle rentals. */ public String qualifier; + + public static OtpGraphQLTransportMode fromModeString(String modeStr) { + String[] modeParts = modeStr.split("_"); + OtpGraphQLTransportMode graphQLMode = new OtpGraphQLTransportMode(); + graphQLMode.mode = modeParts[0]; + if (modeParts.length > 1) { + graphQLMode.qualifier = modeParts[1]; + } + return graphQLMode; + } + + public boolean sameAs(OtpGraphQLTransportMode other) { + return StringUtils.equals(mode, other.mode) && StringUtils.equals(qualifier, other.qualifier); + } } diff --git a/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java b/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java index b7d669730..43c2a1e8f 100644 --- a/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java +++ b/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java @@ -5,6 +5,7 @@ import org.apache.http.client.utils.URLEncodedUtils; import org.apache.http.message.BasicNameValuePair; import org.opentripplanner.middleware.models.MonitoredTrip; +import org.opentripplanner.middleware.otp.OtpGraphQLTransportMode; import org.opentripplanner.middleware.otp.response.Itinerary; import org.opentripplanner.middleware.otp.response.Leg; import org.opentripplanner.middleware.otp.response.Place; @@ -106,20 +107,25 @@ public static Map excludeRealtime(Map params) { * Derives the set of modes for the mode query param that is needed to recreate an OTP {@link Itinerary} using the * plan trip endpoint. */ - public static Set deriveModesFromItinerary(Itinerary itinerary) { - Set modes = itinerary.legs.stream() - .map(leg -> leg.mode) + public static Set deriveModesFromItinerary(Itinerary itinerary) { + Set modes = itinerary.legs.stream() + .map(leg -> { + OtpGraphQLTransportMode graphQLMode = new OtpGraphQLTransportMode(); + graphQLMode.mode = leg.mode; + if ("BICYCLE".equals(leg.mode) || "SCOOTER".equals(leg.mode)) { + // Field 'rentedbike' includes rented bikes and rented scooters. + if (leg.rentedBike) graphQLMode.qualifier = "RENT"; + } + return graphQLMode; + }) .collect(Collectors.toSet()); - // Remove WALK if non-car access modes are present (i.e. {BICYCLE|MICROMOBILITY}[_RENT]). + // Remove WALK if non-car access modes are present (i.e. {BICYCLE|SCOOTER}[_RENT]). // Removing WALK is necessary for OTP to return certain bicycle+transit itineraries. // Including WALK is necessary for OTP to return certain car+transit itineraries. - boolean hasAccessModes = modes.stream().anyMatch(mode -> { - String mainMode = mode.split("_")[0]; - return List.of("BICYCLE", "MICROMOBILITY").contains(mainMode); - }); + boolean hasAccessModes = modes.stream().anyMatch(mode -> List.of("BICYCLE", "SCOOTER").contains(mode.mode)); if (hasAccessModes) { - modes.remove("WALK"); + modes.removeIf(m -> "WALK".equals(m.mode)); } // Replace the "CAR" in the set of modes with the correct CAR query mode (CAR_PARK, CAR_RENT, CAR_HAIL) @@ -128,17 +134,16 @@ public static Set deriveModesFromItinerary(Itinerary itinerary) { boolean hasCarAndTransit = firstCarLeg.isPresent() && itinerary.hasTransit(); if (hasCarAndTransit) { Leg carLeg = firstCarLeg.get(); - String carQueryMode; + String carQualifier; if (Boolean.TRUE.equals(carLeg.rentedCar)) { - carQueryMode = "CAR_RENT"; + carQualifier = "RENT"; } else if (Boolean.TRUE.equals(carLeg.hailedCar)) { - carQueryMode = "CAR_HAIL"; + carQualifier = "HAIL"; } else { - carQueryMode = "CAR_PARK"; + carQualifier = "PARK"; } - modes.remove("CAR"); - modes.add(carQueryMode); + modes.stream().filter(m -> "CAR".equals(m.mode)).forEach(m -> m.qualifier = carQualifier); } return modes; } diff --git a/src/test/java/org/opentripplanner/middleware/utils/ItineraryUtilsTest.java b/src/test/java/org/opentripplanner/middleware/utils/ItineraryUtilsTest.java index df1076fea..4604ccd01 100644 --- a/src/test/java/org/opentripplanner/middleware/utils/ItineraryUtilsTest.java +++ b/src/test/java/org/opentripplanner/middleware/utils/ItineraryUtilsTest.java @@ -8,6 +8,7 @@ import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.middleware.models.ItineraryExistence; import org.opentripplanner.middleware.models.MonitoredTrip; +import org.opentripplanner.middleware.otp.OtpGraphQLTransportMode; import org.opentripplanner.middleware.otp.OtpRequest; import org.opentripplanner.middleware.otp.response.Itinerary; import org.opentripplanner.middleware.otp.response.Leg; @@ -30,6 +31,7 @@ import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -481,6 +483,35 @@ private Itinerary simpleItinerary(Long targetEpochMillis, boolean isArriveBy) { return itinerary; } + @ParameterizedTest + @MethodSource("createDeriveModesFromItineraryTestCases") + void canDeriveModesFromItinerary(List legModes, List expectedModes, String message) { + Itinerary itinerary = simpleItinerary(Instant.EPOCH.toEpochMilli(), false); + itinerary.legs = legModes.stream().map(legMode -> { + String[] modeParts = legMode.split("_"); + boolean isRent = legMode.endsWith("_RENT"); + Leg leg = new Leg(); + leg.mode = modeParts[0]; + // Field 'rentedbike' includes rented bikes and rented scooters. + leg.rentedBike = ("BICYCLE".equals(leg.mode) || "SCOOTER".equals(leg.mode)) && isRent; + leg.rentedCar = "CAR".equals(leg.mode) && isRent; + return leg; + }).collect(Collectors.toList()); + + Set derivedModes = ItineraryUtils.deriveModesFromItinerary(itinerary); + expectedModes.forEach(expMode -> { + Assertions.assertEquals(1, derivedModes.stream().filter(m -> m.sameAs(OtpGraphQLTransportMode.fromModeString(expMode))).count()); + }); + } + + private static Stream createDeriveModesFromItineraryTestCases() { + return Stream.of( + Arguments.of(List.of("WALK"), List.of("WALK"), "Walk only"), + Arguments.of(List.of("WALK", "BICYCLE_RENT"), List.of("BICYCLE_RENT"), "Bike rent only"), + Arguments.of(List.of("WALK", "BUS"), List.of("BUS"), "Walk + Bus") + ); + } + /** * Data structure for the same-day test. */ From 243160f559c619422c8ae5546fd6917f6cf7c55f Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 19 Jul 2024 11:27:15 -0400 Subject: [PATCH 18/48] refactor(OtpGraphQLVariables): Implement Cloneable. --- .../otp/OtpGraphQLRoutesAndTrips.java | 10 +++++- .../middleware/otp/OtpGraphQLVariables.java | 32 ++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLRoutesAndTrips.java b/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLRoutesAndTrips.java index 5ae00c62b..c4c406702 100644 --- a/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLRoutesAndTrips.java +++ b/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLRoutesAndTrips.java @@ -1,7 +1,15 @@ package org.opentripplanner.middleware.otp; /** OTP GraphQL data structure for preferred/unpreferred/banned routes and trips */ -public class OtpGraphQLRoutesAndTrips { +public class OtpGraphQLRoutesAndTrips implements Cloneable { public String routes; public String trips; + + @Override + public OtpGraphQLRoutesAndTrips clone() { + OtpGraphQLRoutesAndTrips clone = new OtpGraphQLRoutesAndTrips(); + clone.routes = routes; + clone.trips = trips; + return clone; + } } diff --git a/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java b/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java index f8527770e..a0828fab6 100644 --- a/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java +++ b/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java @@ -1,9 +1,14 @@ package org.opentripplanner.middleware.otp; +import com.fasterxml.jackson.core.JsonProcessingException; +import spark.Request; + import java.util.List; +import static org.opentripplanner.middleware.utils.JsonUtils.getPOJOFromJSON; + /** OTP 'plan' query variables */ -public class OtpGraphQLVariables { +public class OtpGraphQLVariables implements Cloneable { public boolean arriveBy; public OtpGraphQLRoutesAndTrips banned; public float bikeReluctance; @@ -19,4 +24,29 @@ public class OtpGraphQLVariables { public float walkReluctance; public float walkSpeed; public boolean wheelchair; + + public static OtpGraphQLVariables fromRequest(Request request) throws JsonProcessingException { + return getPOJOFromJSON(request.body(), OtpGraphQLQuery.class).variables; + } + + @Override + public OtpGraphQLVariables clone() { + OtpGraphQLVariables clone = new OtpGraphQLVariables(); + clone.arriveBy = arriveBy; + clone.banned = banned; + clone.bikeReluctance = bikeReluctance; + clone.carReluctance = carReluctance; + clone.date = date; + clone.fromPlace = fromPlace; + clone.modes = List.copyOf(modes); + clone.numItineraries = numItineraries; + clone.preferred = preferred.clone(); + clone.time = time; + clone.toPlace = toPlace; + clone.unpreferred = unpreferred.clone(); + clone.walkReluctance = walkReluctance; + clone.walkSpeed = walkSpeed; + clone.wheelchair = wheelchair; + return clone; + } } From 685218ed2772b9f3ad5de37a5497294fdd19ed7d Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 19 Jul 2024 13:06:23 -0400 Subject: [PATCH 19/48] refactor(OtpGraphQLVariables): Check for null fields before invoking clone on them. --- .../middleware/otp/OtpGraphQLTransportMode.java | 17 +++++++++++++++++ .../middleware/otp/OtpGraphQLVariables.java | 8 ++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLTransportMode.java b/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLTransportMode.java index 76c447e65..82877994f 100644 --- a/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLTransportMode.java +++ b/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLTransportMode.java @@ -20,6 +20,23 @@ public static OtpGraphQLTransportMode fromModeString(String modeStr) { return graphQLMode; } + @Override + public boolean equals(Object other) { + if (other == null) return false; + if (!(other instanceof OtpGraphQLTransportMode)) return false; + return sameAs((OtpGraphQLTransportMode) other); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @Override + public String toString() { + return mode + (qualifier != null ? "_" + qualifier : ""); + } + public boolean sameAs(OtpGraphQLTransportMode other) { return StringUtils.equals(mode, other.mode) && StringUtils.equals(qualifier, other.qualifier); } diff --git a/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java b/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java index a0828fab6..5e57ce6e9 100644 --- a/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java +++ b/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java @@ -40,10 +40,14 @@ public OtpGraphQLVariables clone() { clone.fromPlace = fromPlace; clone.modes = List.copyOf(modes); clone.numItineraries = numItineraries; - clone.preferred = preferred.clone(); + if (preferred != null) { + clone.preferred = preferred.clone(); + } clone.time = time; clone.toPlace = toPlace; - clone.unpreferred = unpreferred.clone(); + if (unpreferred != null) { + clone.unpreferred = unpreferred.clone(); + } clone.walkReluctance = walkReluctance; clone.walkSpeed = walkSpeed; clone.wheelchair = wheelchair; From 3cf85c712a521a9fe6e0feec87abdefd368e9986 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 19 Jul 2024 16:29:39 -0400 Subject: [PATCH 20/48] refactor(OtpGraphQLVariables): Skip null fields when persisting. --- .../middleware/otp/OtpGraphQLRoutesAndTrips.java | 5 +++++ .../middleware/otp/OtpGraphQLTransportMode.java | 4 ++++ .../middleware/otp/OtpGraphQLVariables.java | 8 +++++++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLRoutesAndTrips.java b/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLRoutesAndTrips.java index c4c406702..6b066a928 100644 --- a/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLRoutesAndTrips.java +++ b/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLRoutesAndTrips.java @@ -1,6 +1,11 @@ package org.opentripplanner.middleware.otp; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; + /** OTP GraphQL data structure for preferred/unpreferred/banned routes and trips */ +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) public class OtpGraphQLRoutesAndTrips implements Cloneable { public String routes; public String trips; diff --git a/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLTransportMode.java b/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLTransportMode.java index 82877994f..f4d66423e 100644 --- a/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLTransportMode.java +++ b/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLTransportMode.java @@ -1,8 +1,12 @@ package org.opentripplanner.middleware.otp; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; import org.apache.commons.lang3.StringUtils; /** Describes a transport mode for OTP GraphQL */ +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) public class OtpGraphQLTransportMode { /** A mode such as WALK, BUS, BIKE */ public String mode; diff --git a/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java b/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java index 5e57ce6e9..e414146e5 100644 --- a/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java +++ b/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java @@ -1,5 +1,7 @@ package org.opentripplanner.middleware.otp; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; import spark.Request; @@ -8,6 +10,8 @@ import static org.opentripplanner.middleware.utils.JsonUtils.getPOJOFromJSON; /** OTP 'plan' query variables */ +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) public class OtpGraphQLVariables implements Cloneable { public boolean arriveBy; public OtpGraphQLRoutesAndTrips banned; @@ -38,7 +42,9 @@ public OtpGraphQLVariables clone() { clone.carReluctance = carReluctance; clone.date = date; clone.fromPlace = fromPlace; - clone.modes = List.copyOf(modes); + if (modes != null) { + clone.modes = List.copyOf(modes); + } clone.numItineraries = numItineraries; if (preferred != null) { clone.preferred = preferred.clone(); From fe152a2d0736ba61d0f90dcfaf36c2bfb809dfbc Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 19 Jul 2024 17:44:27 -0400 Subject: [PATCH 21/48] refactor: Migrate trip checks and tests to GraphQL params. --- .../api/MonitoredTripController.java | 8 +- .../controllers/api/OtpRequestProcessor.java | 7 +- .../middleware/models/MonitoredTrip.java | 83 +- .../middleware/otp/OtpDispatcher.java | 31 +- .../middleware/otp/OtpRequest.java | 5 +- .../tripmonitor/jobs/CheckMonitoredTrip.java | 21 +- .../middleware/utils/ItineraryUtils.java | 34 +- .../controllers/api/ApiUserFlowTest.java | 3 +- .../api/GetMonitoredTripsTest.java | 2 +- .../middleware/models/MonitoredTripTest.java | 133 - .../middleware/testutils/OtpTestUtils.java | 12 + .../testutils/PersistenceTestUtils.java | 2 +- .../middleware/utils/ItineraryUtilsTest.java | 48 +- .../otp/response/planErrorResponse.json | 43 +- .../middleware/otp/response/planResponse.json | 5794 ++++++++--------- 15 files changed, 3014 insertions(+), 3212 deletions(-) delete mode 100644 src/test/java/org/opentripplanner/middleware/models/MonitoredTripTest.java diff --git a/src/main/java/org/opentripplanner/middleware/controllers/api/MonitoredTripController.java b/src/main/java/org/opentripplanner/middleware/controllers/api/MonitoredTripController.java index d6ca475ec..d36cdf357 100644 --- a/src/main/java/org/opentripplanner/middleware/controllers/api/MonitoredTripController.java +++ b/src/main/java/org/opentripplanner/middleware/controllers/api/MonitoredTripController.java @@ -85,6 +85,8 @@ MonitoredTrip preCreateHook(MonitoredTrip monitoredTrip, Request req) { "Error parsing the trip query parameters.", e ); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); } } @@ -132,7 +134,7 @@ private void preCreateOrUpdateChecks(MonitoredTrip monitoredTrip, Request req) { */ private void processTripQueryParams(MonitoredTrip monitoredTrip, Request req) { try { - monitoredTrip.initializeFromItineraryAndQueryParams(); + monitoredTrip.initializeFromItineraryAndQueryParams(req); } catch (Exception e) { logMessageAndHalt( req, @@ -198,9 +200,9 @@ private static ItineraryExistence checkItinerary(Request request, Response respo return null; } try { - trip.initializeFromItineraryAndQueryParams(); + trip.initializeFromItineraryAndQueryParams(trip.otp2QueryParams); trip.checkItineraryExistence(false); - } catch (URISyntaxException e) { // triggered by OtpQueryUtils#getQueryParams. + } catch (URISyntaxException | JsonProcessingException e) { // triggered by OtpQueryUtils#getQueryParams. logMessageAndHalt( request, HttpStatus.INTERNAL_SERVER_ERROR_500, diff --git a/src/main/java/org/opentripplanner/middleware/controllers/api/OtpRequestProcessor.java b/src/main/java/org/opentripplanner/middleware/controllers/api/OtpRequestProcessor.java index c8210ab5b..4e9958d2f 100644 --- a/src/main/java/org/opentripplanner/middleware/controllers/api/OtpRequestProcessor.java +++ b/src/main/java/org/opentripplanner/middleware/controllers/api/OtpRequestProcessor.java @@ -38,7 +38,6 @@ import static org.opentripplanner.middleware.auth.Auth0Connection.getUserFromRequest; import static org.opentripplanner.middleware.auth.Auth0Connection.isAuthHeaderPresent; import static org.opentripplanner.middleware.controllers.api.ApiController.USER_ID_PARAM; -import static org.opentripplanner.middleware.utils.JsonUtils.getPOJOFromJSON; import static org.opentripplanner.middleware.utils.JsonUtils.logMessageAndHalt; /** @@ -190,7 +189,7 @@ private String proxyPost(Request request, Response response) { * * Other requests will still be proxied, just not stored. */ - OtpGraphQLVariables graphQlVariables = getPOJOFromJSON(requestBody, OtpGraphQLQuery.class).variables; + OtpGraphQLVariables graphQlVariables = OtpGraphQLVariables.fromRequest(request); // Follows the method used in otp-ui core-utils storage.js String randomBatchId = Integer.toString((int) (Math.random() * 1_000_000_000), 36); @@ -274,8 +273,8 @@ private static boolean handlePlanTripResponse( OtpUser otpUser ) throws JsonProcessingException { return handlePlanTripResponse( - request.queryParams("batchId"), - getPOJOFromJSON(request.body(), OtpGraphQLQuery.class).variables, + request.queryParams("batchId"), // TODO: check if this still applies + OtpGraphQLVariables.fromRequest(request), otpDispatcherResponse, otpUser ); diff --git a/src/main/java/org/opentripplanner/middleware/models/MonitoredTrip.java b/src/main/java/org/opentripplanner/middleware/models/MonitoredTrip.java index 290c90c8b..1e9af9fed 100644 --- a/src/main/java/org/opentripplanner/middleware/models/MonitoredTrip.java +++ b/src/main/java/org/opentripplanner/middleware/models/MonitoredTrip.java @@ -4,13 +4,12 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.core.JsonProcessingException; import com.mongodb.client.FindIterable; -import org.apache.http.NameValuePair; -import org.apache.http.client.utils.URLEncodedUtils; import org.bson.codecs.pojo.annotations.BsonIgnore; -import org.bson.conversions.Bson; import org.opentripplanner.middleware.auth.Permission; import org.opentripplanner.middleware.auth.RequestingUser; import org.opentripplanner.middleware.otp.OtpDispatcherResponse; +import org.opentripplanner.middleware.otp.OtpGraphQLTransportMode; +import org.opentripplanner.middleware.otp.OtpGraphQLVariables; import org.opentripplanner.middleware.otp.OtpRequest; import org.opentripplanner.middleware.otp.response.Itinerary; import org.opentripplanner.middleware.otp.response.OtpResponse; @@ -21,24 +20,16 @@ import org.opentripplanner.middleware.tripmonitor.JourneyState; import org.opentripplanner.middleware.utils.DateTimeUtils; import org.opentripplanner.middleware.utils.ItineraryUtils; +import spark.Request; -import java.net.URI; import java.net.URISyntaxException; import java.time.DayOfWeek; import java.time.LocalDate; import java.time.LocalTime; import java.time.ZonedDateTime; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.function.Function; -import java.util.stream.Collectors; - -import static com.mongodb.client.model.Filters.eq; -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.opentripplanner.middleware.utils.ItineraryUtils.DATE_PARAM; -import static org.opentripplanner.middleware.utils.ItineraryUtils.MODE_PARAM; -import static org.opentripplanner.middleware.utils.ItineraryUtils.TIME_PARAM; /** * A monitored trip represents a trip a user would like to receive notification on if affected by a delay and/or route @@ -137,8 +128,14 @@ public class MonitoredTrip extends Model { /** * Query params. Query parameters influencing trip. */ + // TODO: Remove public String queryParams; + /** + * GraphQL query parameters for OTP. + */ + public OtpGraphQLVariables otp2QueryParams = new OtpGraphQLVariables(); + /** * The trip's itinerary */ @@ -185,14 +182,13 @@ public MonitoredTrip() { /** * Used only during testing */ - public MonitoredTrip(OtpDispatcherResponse otpDispatcherResponse) throws URISyntaxException, + public MonitoredTrip(OtpGraphQLVariables otp2QueryParams, OtpDispatcherResponse otpDispatcherResponse) throws URISyntaxException, JsonProcessingException { - queryParams = otpDispatcherResponse.requestUri.getQuery(); TripPlan plan = otpDispatcherResponse.getResponse().plan; itinerary = plan.itineraries.get(0); // extract trip time from parsed params and itinerary - initializeFromItineraryAndQueryParams(); + initializeFromItineraryAndQueryParams(otp2QueryParams); } /** @@ -202,7 +198,7 @@ public MonitoredTrip(OtpDispatcherResponse otpDispatcherResponse) throws URISynt public boolean checkItineraryExistence( boolean replaceItinerary, Function otpResponseProvider - ) throws URISyntaxException { + ) throws URISyntaxException, JsonProcessingException { // Get queries to execute by date. List queriesByDate = getItineraryExistenceQueries(); itineraryExistence = new ItineraryExistence(queriesByDate, itinerary, arriveBy, otpResponseProvider); @@ -217,7 +213,7 @@ public boolean checkItineraryExistence( /** * Shorthand for above method using the default otpResponseProvider. */ - public boolean checkItineraryExistence(boolean replaceItinerary) throws URISyntaxException { + public boolean checkItineraryExistence(boolean replaceItinerary) throws URISyntaxException, JsonProcessingException { return checkItineraryExistence(replaceItinerary, null); } @@ -225,9 +221,8 @@ public boolean checkItineraryExistence(boolean replaceItinerary) throws URISynta * Replace the itinerary provided with the monitored trip * with a non-real-time, verified itinerary from the responses provided. */ - private boolean updateTripWithVerifiedItinerary() throws URISyntaxException { - Map params = parseQueryParams(); - String queryDate = params.get(DATE_PARAM); + private boolean updateTripWithVerifiedItinerary() throws URISyntaxException, JsonProcessingException { + String queryDate = otp2QueryParams.date; DayOfWeek dayOfWeek = DateTimeUtils.getDateFromQueryDateString(queryDate).getDayOfWeek(); // Find the response corresponding to the day of the query. @@ -239,7 +234,7 @@ private boolean updateTripWithVerifiedItinerary() throws URISyntaxException { if (verifiedItinerary != null) { // Set itinerary for monitored trip if verified itinerary is available. this.itinerary = verifiedItinerary; - this.initializeFromItineraryAndQueryParams(); + this.initializeFromItineraryAndQueryParams(otp2QueryParams); return true; } else { // Otherwise, set itinerary existence error/message. @@ -254,10 +249,9 @@ private boolean updateTripWithVerifiedItinerary() throws URISyntaxException { */ @JsonIgnore @BsonIgnore - public List getItineraryExistenceQueries() - throws URISyntaxException { + public List getItineraryExistenceQueries() { return ItineraryUtils.getOtpRequestsForDates( - ItineraryUtils.excludeRealtime(parseQueryParams()), + otp2QueryParams, ItineraryUtils.getDatesToCheckItineraryExistence(this) ); } @@ -266,24 +260,31 @@ public List getItineraryExistenceQueries() * Initializes a MonitoredTrip by deriving some fields from the currently set itinerary. Also, the realtime info of * the itinerary is removed. */ - public void initializeFromItineraryAndQueryParams() throws IllegalArgumentException, URISyntaxException { + public void initializeFromItineraryAndQueryParams(Request req) throws IllegalArgumentException, URISyntaxException, JsonProcessingException { + initializeFromItineraryAndQueryParams(OtpGraphQLVariables.fromRequest(req)); + } + + /** + * Initializes a MonitoredTrip by deriving some fields from the currently set itinerary. Also, the realtime info of + * the itinerary is removed. + */ + public void initializeFromItineraryAndQueryParams(OtpGraphQLVariables graphQLVariables) throws IllegalArgumentException, URISyntaxException, JsonProcessingException { int lastLegIndex = itinerary.legs.size() - 1; from = itinerary.legs.get(0).from; to = itinerary.legs.get(lastLegIndex).to; - Map parsedQueryParams = parseQueryParams(); + this.otp2QueryParams = graphQLVariables; // Update modes in query params with set derived from itinerary. This ensures that, when sending requests to OTP // for trip monitoring, we are querying the modes retained by the user when they saved the itinerary. - Set modes = ItineraryUtils.deriveModesFromItinerary(itinerary); - parsedQueryParams.put(MODE_PARAM, String.join(",", modes)); - this.queryParams = ItineraryUtils.toQueryString(parsedQueryParams); - this.arriveBy = parsedQueryParams.getOrDefault("arriveBy", "false").equals("true"); + Set modes = ItineraryUtils.deriveModesFromItinerary(itinerary); + graphQLVariables.modes = List.copyOf(modes); + this.arriveBy = graphQLVariables.arriveBy; // Ensure the itinerary we store does not contain any realtime info. clearRealtimeInfo(); // set the trip time by parsing the query params - tripTime = parsedQueryParams.get(TIME_PARAM); + tripTime = graphQLVariables.time; if (tripTime == null) { throw new IllegalArgumentException("A monitored trip must have a time set in the query params!"); } @@ -371,10 +372,6 @@ public boolean canBeManagedBy(RequestingUser requestingUser) { return super.canBeManagedBy(requestingUser); } - private Bson tripIdFilter() { - return eq("monitoredTripId", this.id); - } - /** * Clear real-time info from itinerary to store. * FIXME: Do we need to clear more than the alerts? @@ -396,22 +393,6 @@ public boolean delete() { return Persistence.monitoredTrips.removeById(this.id); } - /** - * Parse the query params for this trip into a map of the variables. - */ - public Map parseQueryParams() throws URISyntaxException { - // If for some reason a leading ? is present in queryParams, skip it. - // (We already include a ? when calling URLEncodedUtils.parse below.) - String queryParamsWithoutQuestion = queryParams.startsWith("?") - ? queryParams.substring(1) - : queryParams; - - return URLEncodedUtils.parse( - new URI(String.format("http://example.com/plan?%s", queryParamsWithoutQuestion)), - UTF_8 - ).stream().collect(Collectors.toMap(NameValuePair::getName, NameValuePair::getValue)); - } - /** * Returns the target hour of the day that the trip is either departing at or arriving by */ diff --git a/src/main/java/org/opentripplanner/middleware/otp/OtpDispatcher.java b/src/main/java/org/opentripplanner/middleware/otp/OtpDispatcher.java index d60e4af41..2dee91c56 100644 --- a/src/main/java/org/opentripplanner/middleware/otp/OtpDispatcher.java +++ b/src/main/java/org/opentripplanner/middleware/otp/OtpDispatcher.java @@ -6,9 +6,10 @@ import org.eclipse.jetty.http.HttpMethod; import org.opentripplanner.middleware.bugsnag.BugsnagReporter; import org.opentripplanner.middleware.otp.response.OtpResponse; +import org.opentripplanner.middleware.utils.GraphQLUtils; import org.opentripplanner.middleware.utils.HttpResponseValues; import org.opentripplanner.middleware.utils.HttpUtils; -import org.opentripplanner.middleware.utils.ItineraryUtils; +import org.opentripplanner.middleware.utils.JsonUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -74,7 +75,23 @@ public static OtpDispatcherResponse sendOtpPlanRequest(OtpVersion version, Strin * Provides a response from the OTP server target service based on the input {@link OtpRequest}. */ public static OtpDispatcherResponse sendOtpPlanRequest(OtpVersion version, OtpRequest otpRequest) { - return sendOtpPlanRequest(version, ItineraryUtils.toQueryString(otpRequest.requestParameters)); + return sendOtpPlanRequest(version, otpRequest.requestParameters); + } + + /** + * Provides a response from the OTP server target service based on the input {@link OtpRequest}. + */ + public static OtpDispatcherResponse sendOtpPlanRequest(OtpVersion version, OtpGraphQLVariables params) { + OtpGraphQLQuery query = new OtpGraphQLQuery(); + query.query = GraphQLUtils.getPlanQueryTemplate(); + query.variables = params; + return sendOtpPostRequest( + version, + "", + OTP_GRAPHQL_ENDPOINT, + Map.of("Content-Type", "application/json"), + JsonUtils.toJson(query).replace("\\\\n", "\\n").replace("\\\\\"", "\"") + ); } /** @@ -115,7 +132,7 @@ private static OtpDispatcherResponse sendOtpRequest( Map headers, String bodyContent ) { - LOG.info("Sending request to OTP: {}", uri.toString()); + LOG.info("Sending request to OTP: {}", uri); HttpResponseValues otpResponse = HttpUtils.httpRequestRawResponse( uri, @@ -127,11 +144,15 @@ private static OtpDispatcherResponse sendOtpRequest( } public static OtpResponse sendOtpRequestWithErrorHandling(String sentParams) { - return handleOtpDispatcherResponse(() -> sendOtpPlanRequest(OtpVersion.OTP1, sentParams)); + return handleOtpDispatcherResponse(() -> sendOtpPlanRequest(OtpVersion.OTP2, sentParams)); } public static OtpResponse sendOtpRequestWithErrorHandling(OtpRequest otpRequest) { - return handleOtpDispatcherResponse(() -> sendOtpPlanRequest(OtpVersion.OTP1, otpRequest)); + return handleOtpDispatcherResponse(() -> sendOtpPlanRequest(OtpVersion.OTP2, otpRequest)); + } + + public static OtpResponse sendOtpRequestWithErrorHandling(OtpGraphQLVariables params) { + return handleOtpDispatcherResponse(() -> sendOtpPlanRequest(OtpVersion.OTP2, params)); } private static OtpResponse handleOtpDispatcherResponse(Supplier otpDispatcherResponseSupplier) { diff --git a/src/main/java/org/opentripplanner/middleware/otp/OtpRequest.java b/src/main/java/org/opentripplanner/middleware/otp/OtpRequest.java index b6ce8cc4b..5590148f1 100644 --- a/src/main/java/org/opentripplanner/middleware/otp/OtpRequest.java +++ b/src/main/java/org/opentripplanner/middleware/otp/OtpRequest.java @@ -1,16 +1,15 @@ package org.opentripplanner.middleware.otp; import java.time.ZonedDateTime; -import java.util.Map; /** * Contains information needed to build a plan request to OpenTripPlanner. */ public class OtpRequest { public final ZonedDateTime dateTime; - public final Map requestParameters; + public final OtpGraphQLVariables requestParameters; - public OtpRequest(ZonedDateTime dateTime, Map requestParameters) { + public OtpRequest(ZonedDateTime dateTime, OtpGraphQLVariables requestParameters) { this.dateTime = dateTime; this.requestParameters = requestParameters; } diff --git a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java index 989004ea0..3e0fdd1f7 100644 --- a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java +++ b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java @@ -1,12 +1,12 @@ package org.opentripplanner.middleware.tripmonitor.jobs; -import org.opentripplanner.middleware.bugsnag.BugsnagReporter; import org.opentripplanner.middleware.i18n.Message; import org.opentripplanner.middleware.models.ItineraryExistence; import org.opentripplanner.middleware.models.MonitoredTrip; import org.opentripplanner.middleware.models.OtpUser; import org.opentripplanner.middleware.models.TripMonitorAlertNotification; import org.opentripplanner.middleware.models.TripMonitorNotification; +import org.opentripplanner.middleware.otp.OtpGraphQLVariables; import org.opentripplanner.middleware.tripmonitor.TripStatus; import org.opentripplanner.middleware.otp.OtpDispatcher; import org.opentripplanner.middleware.otp.response.Itinerary; @@ -24,7 +24,6 @@ import org.slf4j.LoggerFactory; import org.slf4j.MDC; -import java.net.URISyntaxException; import java.time.Instant; import java.time.LocalDate; import java.time.ZoneId; @@ -320,26 +319,16 @@ private boolean makeOTPRequestAndUpdateMatchingItineraryInternal() { /** Default implementation for OtpResponse provider that actually invokes the OTP server. */ private OtpResponse getOtpResponse() { - Map params = getQueryParamsForTargetZonedDateTime(); - if (params == null) return null; - return OtpDispatcher.sendOtpRequestWithErrorHandling(ItineraryUtils.toQueryString(params)); + return OtpDispatcher.sendOtpRequestWithErrorHandling(getQueryParamsForTargetZonedDateTime()); } /** * Generate the appropriate OTP query params for the trip for the current check by replacing the date query * parameter with the appropriate date. */ - private Map getQueryParamsForTargetZonedDateTime() { - Map params = null; - try { - params = trip.parseQueryParams(); - params.put(ItineraryUtils.DATE_PARAM, targetZonedDateTime.format(DateTimeUtils.DEFAULT_DATE_FORMATTER)); - } catch (URISyntaxException e) { - BugsnagReporter.reportErrorToBugsnag( - "Encountered an error parsing trip query params.", - e - ); - } + private OtpGraphQLVariables getQueryParamsForTargetZonedDateTime() { + OtpGraphQLVariables params = trip.otp2QueryParams.clone(); + params.date = targetZonedDateTime.format(DateTimeUtils.DEFAULT_DATE_FORMATTER); return params; } diff --git a/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java b/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java index 43c2a1e8f..34f43821c 100644 --- a/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java +++ b/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java @@ -6,16 +6,15 @@ import org.apache.http.message.BasicNameValuePair; import org.opentripplanner.middleware.models.MonitoredTrip; import org.opentripplanner.middleware.otp.OtpGraphQLTransportMode; +import org.opentripplanner.middleware.otp.OtpGraphQLVariables; import org.opentripplanner.middleware.otp.response.Itinerary; import org.opentripplanner.middleware.otp.response.Leg; import org.opentripplanner.middleware.otp.response.Place; import org.opentripplanner.middleware.otp.OtpRequest; -import java.net.URISyntaxException; import java.time.LocalDate; import java.time.ZonedDateTime; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -32,10 +31,7 @@ */ public class ItineraryUtils { - public static final String IGNORE_REALTIME_UPDATES_PARAM = "ignoreRealtimeUpdates"; - public static final String DATE_PARAM = "date"; public static final String MODE_PARAM = "mode"; - public static final String TIME_PARAM = "time"; public static final int ITINERARY_CHECK_WINDOW = 7; public static final int SERVICE_DAY_START_HOUR = getConfigPropertyAsInt("SERVICE_DAY_START_HOUR", 3); @@ -54,16 +50,15 @@ public static String toQueryString(Map params) { * with the date changed to the desired one. * @param params a map of the base OTP query parameters. * @param dates a list of the desired dates in YYYY-MM-DD format. - * @return a map of query strings with, and indexed by the specified dates. + * @return a list of query strings with, and indexed by the specified dates. */ - public static List getOtpRequestsForDates(Map params, List dates) { + public static List getOtpRequestsForDates(OtpGraphQLVariables params, List dates) { // Create a copy of the original params in which we change the date. List requests = new ArrayList<>(); for (ZonedDateTime date : dates) { // Get updated date string and add to params copy. - Map paramsCopy = new HashMap<>(params); - String dateString = DateTimeUtils.getStringFromDate(date.toLocalDate(), DEFAULT_DATE_FORMAT_PATTERN); - paramsCopy.put(DATE_PARAM, dateString); + OtpGraphQLVariables paramsCopy = params.clone(); + paramsCopy.date = DateTimeUtils.getStringFromDate(date.toLocalDate(), DEFAULT_DATE_FORMAT_PATTERN); requests.add(new OtpRequest(date, paramsCopy)); } return requests; @@ -76,13 +71,12 @@ public static List getOtpRequestsForDates(Map params * @param trip The trip from which to extract the monitored dates to check. * @return A list of date strings in YYYY-MM-DD format corresponding to each day of the week to monitor, sorted from earliest. */ - public static List getDatesToCheckItineraryExistence(MonitoredTrip trip) - throws URISyntaxException { + public static List getDatesToCheckItineraryExistence(MonitoredTrip trip) { List datesToCheck = new ArrayList<>(); - Map params = trip.parseQueryParams(); + OtpGraphQLVariables params = trip.otp2QueryParams; // Start from the query date, if available. - String startingDateString = params.get(DATE_PARAM); + String startingDateString = params.date; // If there is no query date, start from today. LocalDate startingDate = DateTimeUtils.getDateFromQueryDateString(startingDateString); ZonedDateTime startingDateTime = trip.tripZonedDateTime(startingDate); @@ -94,15 +88,6 @@ public static List getDatesToCheckItineraryExistence(MonitoredTri return datesToCheck; } - /** - * @return a copy of the specified query parameter map, with ignoreRealtimeUpdates set to true. - */ - public static Map excludeRealtime(Map params) { - Map result = new HashMap<>(params); - result.put(IGNORE_REALTIME_UPDATES_PARAM, "true"); - return result; - } - /** * Derives the set of modes for the mode query param that is needed to recreate an OTP {@link Itinerary} using the * plan trip endpoint. @@ -123,8 +108,9 @@ public static Set deriveModesFromItinerary(Itinerary it // Remove WALK if non-car access modes are present (i.e. {BICYCLE|SCOOTER}[_RENT]). // Removing WALK is necessary for OTP to return certain bicycle+transit itineraries. // Including WALK is necessary for OTP to return certain car+transit itineraries. + // In OTP2: WALK is implied if a transit mode is also present and can be removed in those cases. boolean hasAccessModes = modes.stream().anyMatch(mode -> List.of("BICYCLE", "SCOOTER").contains(mode.mode)); - if (hasAccessModes) { + if (hasAccessModes || itinerary.hasTransit()) { modes.removeIf(m -> "WALK".equals(m.mode)); } diff --git a/src/test/java/org/opentripplanner/middleware/controllers/api/ApiUserFlowTest.java b/src/test/java/org/opentripplanner/middleware/controllers/api/ApiUserFlowTest.java index 6120bfd30..17748861b 100644 --- a/src/test/java/org/opentripplanner/middleware/controllers/api/ApiUserFlowTest.java +++ b/src/test/java/org/opentripplanner/middleware/controllers/api/ApiUserFlowTest.java @@ -195,7 +195,8 @@ public void canSimulateApiUserFlow() throws Exception { OtpUser otpUserResponse = JsonUtils.getPOJOFromJSON(createUserResponse.responseBody, OtpUser.class); // Create a monitored trip for the Otp user (API users are prevented from doing this). - MonitoredTrip monitoredTrip = new MonitoredTrip(OtpTestUtils.sendSamplePlanRequest()); + // TODO: refactor this call + MonitoredTrip monitoredTrip = new MonitoredTrip(OtpTestUtils.getSampleQueryParams(), OtpTestUtils.sendSamplePlanRequest()); monitoredTrip.updateAllDaysOfWeek(true); monitoredTrip.userId = otpUser.id; HttpResponseValues createTripResponseAsOtpUser = mockAuthenticatedRequest( diff --git a/src/test/java/org/opentripplanner/middleware/controllers/api/GetMonitoredTripsTest.java b/src/test/java/org/opentripplanner/middleware/controllers/api/GetMonitoredTripsTest.java index e84f88766..cf80f85e3 100644 --- a/src/test/java/org/opentripplanner/middleware/controllers/api/GetMonitoredTripsTest.java +++ b/src/test/java/org/opentripplanner/middleware/controllers/api/GetMonitoredTripsTest.java @@ -206,7 +206,7 @@ private ResponseList getMonitoredTripsForUser(String path, OtpUse * Creates a {@link MonitoredTrip} for the specified user. */ private static void createMonitoredTripAsUser(OtpUser otpUser) throws Exception { - MonitoredTrip monitoredTrip = new MonitoredTrip(OtpTestUtils.sendSamplePlanRequest()); + MonitoredTrip monitoredTrip = new MonitoredTrip(OtpTestUtils.getSampleQueryParams(), OtpTestUtils.sendSamplePlanRequest()); monitoredTrip.updateAllDaysOfWeek(true); monitoredTrip.userId = otpUser.id; diff --git a/src/test/java/org/opentripplanner/middleware/models/MonitoredTripTest.java b/src/test/java/org/opentripplanner/middleware/models/MonitoredTripTest.java deleted file mode 100644 index 8ec1c21eb..000000000 --- a/src/test/java/org/opentripplanner/middleware/models/MonitoredTripTest.java +++ /dev/null @@ -1,133 +0,0 @@ -package org.opentripplanner.middleware.models; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import org.opentripplanner.middleware.otp.response.Itinerary; -import org.opentripplanner.middleware.otp.response.Leg; - -import java.net.URISyntaxException; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.opentripplanner.middleware.utils.ItineraryUtils.MODE_PARAM; - -/** - * Holds tests for some methods in MonitoredTrip. - */ -class MonitoredTripTest { - /** - * Abbreviated query params with mode params you would get from UI. - */ - private static final String UI_QUERY_PARAMS - = "?fromPlace=fromplace%3A%3A28.556631%2C-81.411781&toPlace=toplace%3A%3A28.545925%2C-81.348609&date=2020-11-13&time=14%3A21&arriveBy=false&mode=WALK%2CBUS%2CRAIL&numItineraries=3"; - - /** - * Partial test for {@link MonitoredTrip#initializeFromItineraryAndQueryParams} - * that focuses on updating the mode in queryParams. - */ - @ParameterizedTest - @MethodSource("createUpdateModeInQueryParamTestCases") - void canUpdateModeInQueryParams(Leg accessLeg, Set expectedModeParams) throws URISyntaxException { - // Setup a trip with an initial queryParams argument. - MonitoredTrip trip = new MonitoredTrip(); - trip.queryParams = UI_QUERY_PARAMS; - - // Setup an itinerary returned based on the provided accessLeg and a transit and walk leg. - Leg walkLeg = new Leg(); - walkLeg.mode = "WALK"; - - Leg busLeg = new Leg(); - busLeg.mode = "BUS"; - busLeg.transitLeg = true; - - Itinerary itinerary = new Itinerary(); - itinerary.legs = List.of(accessLeg, walkLeg, busLeg); - trip.itinerary = itinerary; - - // Initialize internal trip vars. - trip.initializeFromItineraryAndQueryParams(); - - // Check that the mode was updated. - Map paramsMap = trip.parseQueryParams(); - String[] modeParams = paramsMap.get(MODE_PARAM).split(","); - Set actualModeParams = Set.of(modeParams); - - assertEquals(expectedModeParams, actualModeParams, - String.format("This set of mode params %s is incorrect for access mode %s.", actualModeParams, accessLeg.mode) - ); - } - - private static Stream createUpdateModeInQueryParamTestCases() { - // User-owned bicycle leg for bicycle+transit itineraries. - Leg bicycleLeg = new Leg(); - bicycleLeg.mode = "BICYCLE"; - - // User-owned car leg for park-and-ride itineraries. - // (hailedCar = false, rentedCar = false) - Leg ownCarLeg = new Leg(); - ownCarLeg.mode = "CAR"; - - // Rental car leg - Leg rentalCarLeg = new Leg(); - rentalCarLeg.mode = "CAR"; - rentalCarLeg.rentedCar = true; - - // Hailed car leg - Leg hailedCarLeg = new Leg(); - hailedCarLeg.mode = "CAR"; - hailedCarLeg.hailedCar = true; - - return Stream.of( - // If BICYCLE (or MICROMOBILITY...) and WALK appear together in an itinerary, - // removing WALK is necessary for OTP to return certain bicycle+transit itineraries. - Arguments.of(bicycleLeg, Set.of("BICYCLE", "BUS")), - - // For itineraries with any CAR, - // including WALK is necessary for OTP to return certain car+transit itineraries. - Arguments.of(ownCarLeg, Set.of("CAR_PARK", "WALK", "BUS")), - Arguments.of(rentalCarLeg, Set.of("CAR_RENT", "WALK", "BUS")), - Arguments.of(hailedCarLeg, Set.of("CAR_HAIL", "WALK", "BUS")) - ); - } - - /** - * Partial test for {@link MonitoredTrip#initializeFromItineraryAndQueryParams} - * that focuses on ignoring the leading question mark in the query params, if any. - */ - @Test - void canIgnoreLeadingQuestionMarkInQueryParams() throws URISyntaxException { - // Setup a trip with an initial queryParams argument. - MonitoredTrip trip = new MonitoredTrip(); - trip.queryParams = UI_QUERY_PARAMS; - - Map paramsMap = trip.parseQueryParams(); - for (String key : paramsMap.keySet()) { - assertFalse(key.startsWith("?")); - } - } - - @ParameterizedTest - @MethodSource("createOneTimeTripCases") - void canDetermineOneTimeTrips(MonitoredTrip trip, boolean isOneTime) { - assertEquals(isOneTime, trip.isOneTime()); - } - - private static Stream createOneTimeTripCases() { - return Stream.of( - Arguments.of(createRecurrentTrip(), false), - Arguments.of(new MonitoredTrip(), true) - ); - } - - private static MonitoredTrip createRecurrentTrip() { - MonitoredTrip recurrentTrip = new MonitoredTrip(); - recurrentTrip.tuesday = true; - return recurrentTrip; - } -} diff --git a/src/test/java/org/opentripplanner/middleware/testutils/OtpTestUtils.java b/src/test/java/org/opentripplanner/middleware/testutils/OtpTestUtils.java index a704ef4ff..b741a430c 100644 --- a/src/test/java/org/opentripplanner/middleware/testutils/OtpTestUtils.java +++ b/src/test/java/org/opentripplanner/middleware/testutils/OtpTestUtils.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.opentripplanner.middleware.otp.OtpDispatcher; +import org.opentripplanner.middleware.otp.OtpGraphQLVariables; import org.opentripplanner.middleware.otp.OtpVersion; import org.opentripplanner.middleware.otp.OtpDispatcherResponse; import org.opentripplanner.middleware.otp.response.Itinerary; @@ -176,6 +177,17 @@ public static OtpDispatcherResponse sendSamplePlanRequest() { ); } + /** + * Sample GraphQL params for testing. + */ + public static OtpGraphQLVariables getSampleQueryParams() { + OtpGraphQLVariables params = new OtpGraphQLVariables(); + params.fromPlace = "28.45119,-81.36818"; + params.toPlace = "28.54834,-81.37745"; + params.time = "08:35"; + return params; + } + public static List createMockOtpResponsesForTripExistence() throws Exception { // Set up monitored days and mock responses for itinerary existence check, ordered by day. LocalDate today = DateTimeUtils.nowAsLocalDate(); diff --git a/src/test/java/org/opentripplanner/middleware/testutils/PersistenceTestUtils.java b/src/test/java/org/opentripplanner/middleware/testutils/PersistenceTestUtils.java index aa43ba3be..e68a598fb 100644 --- a/src/test/java/org/opentripplanner/middleware/testutils/PersistenceTestUtils.java +++ b/src/test/java/org/opentripplanner/middleware/testutils/PersistenceTestUtils.java @@ -210,7 +210,7 @@ public static MonitoredTrip createMonitoredTrip( boolean persist, JourneyState journeyState ) throws Exception { - MonitoredTrip monitoredTrip = new MonitoredTrip(otpDispatcherResponse); + MonitoredTrip monitoredTrip = new MonitoredTrip(OtpTestUtils.getSampleQueryParams(), otpDispatcherResponse); monitoredTrip.userId = userId; monitoredTrip.tripName = "test trip"; monitoredTrip.leadTimeInMinutes = 240; diff --git a/src/test/java/org/opentripplanner/middleware/utils/ItineraryUtilsTest.java b/src/test/java/org/opentripplanner/middleware/utils/ItineraryUtilsTest.java index 4604ccd01..d8a1cc3fa 100644 --- a/src/test/java/org/opentripplanner/middleware/utils/ItineraryUtilsTest.java +++ b/src/test/java/org/opentripplanner/middleware/utils/ItineraryUtilsTest.java @@ -9,6 +9,7 @@ import org.opentripplanner.middleware.models.ItineraryExistence; import org.opentripplanner.middleware.models.MonitoredTrip; import org.opentripplanner.middleware.otp.OtpGraphQLTransportMode; +import org.opentripplanner.middleware.otp.OtpGraphQLVariables; import org.opentripplanner.middleware.otp.OtpRequest; import org.opentripplanner.middleware.otp.response.Itinerary; import org.opentripplanner.middleware.otp.response.Leg; @@ -19,7 +20,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.net.URISyntaxException; import java.time.Instant; import java.time.LocalDate; import java.time.LocalTime; @@ -30,16 +30,14 @@ import java.util.Date; import java.time.format.DateTimeFormatter; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.middleware.utils.DateTimeUtils.otpDateTimeAsEpochMillis; -import static org.opentripplanner.middleware.utils.ItineraryUtils.DATE_PARAM; -import static org.opentripplanner.middleware.utils.ItineraryUtils.IGNORE_REALTIME_UPDATES_PARAM; public class ItineraryUtilsTest extends OtpMiddlewareTestEnvironment { private static final Logger LOG = LoggerFactory.getLogger(ItineraryUtilsTest.class); @@ -206,22 +204,22 @@ public static List getMockDatedOtpResponses(List dates) thr * Check that the query date parameter is properly modified to simulate the given OTP query for different dates. */ @Test - void canGetQueriesFromDates() throws URISyntaxException { + void canGetQueriesFromDates() { MonitoredTrip trip = makeTestTrip(); // Create test dates. List testDateStrings = List.of("2020-12-30", "2020-12-31", "2021-01-01"); LOG.info(String.join(", ", testDateStrings)); List testDates = datesToZonedDateTimes(testDateStrings); // Get OTP requests modified with dates. - List requests = ItineraryUtils.getOtpRequestsForDates(trip.parseQueryParams(), testDates); + List requests = ItineraryUtils.getOtpRequestsForDates(trip.otp2QueryParams, testDates); Assertions.assertEquals(testDateStrings.size(), requests.size()); // Iterate over OTP requests and verify that query dates match the input. for (int i = 0; i < testDates.size(); i++) { ZonedDateTime testDate = testDates.get(i); - Map newParams = requests.get(i).requestParameters; + OtpGraphQLVariables newParams = requests.get(i).requestParameters; Assertions.assertEquals( testDate.format(DateTimeUtils.DEFAULT_DATE_FORMATTER), - newParams.get(DATE_PARAM) + newParams.date ); } } @@ -231,30 +229,13 @@ void canGetQueriesFromDates() throws URISyntaxException { * for which we want to check itinerary existence. */ @Test - void canGetDatesToCheckItineraryExistence() throws URISyntaxException { + void canGetDatesToCheckItineraryExistence() { MonitoredTrip trip = makeTestTrip(); List testDates = datesToZonedDateTimes(MONITORED_TRIP_DATES); List datesToCheck = ItineraryUtils.getDatesToCheckItineraryExistence(trip); Assertions.assertEquals(testDates, datesToCheck); } - /** - * Check that the ignoreRealtime query parameter is set to true - * regardless of whether it was originally missing or false. - */ - @Test - void canAddIgnoreRealtimeParam() throws URISyntaxException { - String queryWithRealtimeParam = BASE_QUERY + "&" + IGNORE_REALTIME_UPDATES_PARAM + "=false"; - List queries = List.of(BASE_QUERY, queryWithRealtimeParam); - - for (String query : queries) { - MonitoredTrip trip = new MonitoredTrip(); - trip.queryParams = query; - Map params = ItineraryUtils.excludeRealtime(trip.parseQueryParams()); - Assertions.assertEquals("true", params.get(IGNORE_REALTIME_UPDATES_PARAM)); - } - } - /** * Check whether certain itineraries match. */ @@ -340,6 +321,9 @@ private MonitoredTrip makeTestTrip() { MonitoredTrip trip = new MonitoredTrip(); trip.id = "Test trip"; trip.queryParams = BASE_QUERY; + trip.otp2QueryParams = new OtpGraphQLVariables(); + trip.otp2QueryParams.date = QUERY_DATE; + trip.otp2QueryParams.time = QUERY_TIME; trip.tripTime = QUERY_TIME; trip.from = targetPlace; @@ -495,12 +479,15 @@ void canDeriveModesFromItinerary(List legModes, List expectedMod // Field 'rentedbike' includes rented bikes and rented scooters. leg.rentedBike = ("BICYCLE".equals(leg.mode) || "SCOOTER".equals(leg.mode)) && isRent; leg.rentedCar = "CAR".equals(leg.mode) && isRent; + leg.hailedCar = "CAR".equals(leg.mode) && legMode.endsWith("_HAIL"); + leg.transitLeg = "BUS".equals(leg.mode); return leg; }).collect(Collectors.toList()); Set derivedModes = ItineraryUtils.deriveModesFromItinerary(itinerary); + assertEquals(expectedModes.size(), derivedModes.size()); expectedModes.forEach(expMode -> { - Assertions.assertEquals(1, derivedModes.stream().filter(m -> m.sameAs(OtpGraphQLTransportMode.fromModeString(expMode))).count()); + assertEquals(1, derivedModes.stream().filter(m -> m.sameAs(OtpGraphQLTransportMode.fromModeString(expMode))).count(), message); }); } @@ -508,7 +495,12 @@ private static Stream createDeriveModesFromItineraryTestCases() { return Stream.of( Arguments.of(List.of("WALK"), List.of("WALK"), "Walk only"), Arguments.of(List.of("WALK", "BICYCLE_RENT"), List.of("BICYCLE_RENT"), "Bike rent only"), - Arguments.of(List.of("WALK", "BUS"), List.of("BUS"), "Walk + Bus") + // In OTP2: WALK is implied if a transit mode is also present and can be removed in those cases. + Arguments.of(List.of("WALK", "BUS"), List.of("BUS"), "Walk + Bus"), + Arguments.of(List.of("WALK", "BICYCLE", "BUS"), List.of("BICYCLE", "BUS"), "Walk + Bicycle + Bus"), + Arguments.of(List.of("WALK", "CAR_RENT", "BUS"), List.of("CAR_RENT", "BUS"), "Rented car + Bus"), + Arguments.of(List.of("WALK", "CAR_PARK", "BUS"), List.of("CAR_PARK", "BUS"), "P+R + Bus"), + Arguments.of(List.of("WALK", "CAR_HAIL", "BUS"), List.of("CAR_HAIL", "BUS"), "Hail car + Bus") ); } diff --git a/src/test/resources/org/opentripplanner/middleware/otp/response/planErrorResponse.json b/src/test/resources/org/opentripplanner/middleware/otp/response/planErrorResponse.json index a5e6ba6ea..3dc527b22 100644 --- a/src/test/resources/org/opentripplanner/middleware/otp/response/planErrorResponse.json +++ b/src/test/resources/org/opentripplanner/middleware/otp/response/planErrorResponse.json @@ -1,33 +1,14 @@ { - "requestParameters": { - "date": "2020-06-09", - "mode": "WALK,BUS,TRAM,RAIL,GONDOLA", - "arriveBy": "false", - "walkSpeed": "1.34", - "ignoreRealtimeUpdates": "true", - "companies": "NaN", - "fromPace": "1709 NW Irving St, Portland 97209::45.527817334203,-122.68865964147231", - "showIntermediateStops": "true", - "optimize": "QUICK", - "toPlace": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204::45.51639151281627,-122.67681483620306", - "time": "08:35", - "maxWalkDistance": "1207" - }, - "error": { - "id": 400, - "msg": "Trip is not possible. You might be trying to plan a trip outside the map data boundary.", - "message": "OUTSIDE_BOUNDS", - "missing": [ - "from" - ], - "noPath": true - }, - "debugOutput": { - "precalculationTime": 0, - "pathCalculationTime": 0, - "pathTimes": [], - "renderingTime": 0, - "totalTime": 0, - "timedOut": false + "data": { + "plan": { + "itineraries": [], + "routingErrors": [ + { + "code": "NO_STOPS_IN_RANGE", + "description": "The location was found, but no stops could be found within the search radius.", + "inputField": "TO" + } + ] + } } -} \ No newline at end of file +} diff --git a/src/test/resources/org/opentripplanner/middleware/otp/response/planResponse.json b/src/test/resources/org/opentripplanner/middleware/otp/response/planResponse.json index fc68716a0..4ca0da0ca 100644 --- a/src/test/resources/org/opentripplanner/middleware/otp/response/planResponse.json +++ b/src/test/resources/org/opentripplanner/middleware/otp/response/planResponse.json @@ -1,2951 +1,2923 @@ { - "requestParameters": { - "date": "2020-06-09", - "mode": "WALK,BUS,TRAM,RAIL,GONDOLA", - "arriveBy": "false", - "walkSpeed": "1.34", - "ignoreRealtimeUpdates": "true", - "companies": "NaN", - "showIntermediateStops": "true", - "optimize": "QUICK", - "fromPlace": "1709 NW Irving St, Portland 97209::45.527817334203,-122.68865964147231", - "toPlace": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204::45.51639151281627,-122.67681483620306", - "time": "08:35", - "maxWalkDistance": "1207" - }, - "plan": { - "date": 1591716900000, - "from": { - "name": "1709 NW Irving St, Portland 97209", - "lon": -122.68865964147231, - "lat": 45.527817334203, - "orig": "1709 NW Irving St, Portland 97209", - "vertexType": "NORMAL" - }, - "to": { - "name": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", - "lon": -122.67681483620306, - "lat": 45.51639151281627, - "orig": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", - "vertexType": "NORMAL" - }, - "itineraries": [ - { - "duration": 1114, - "startTime": 1591717210000, - "endTime": 1591718324000, - "walkTime": 872, - "transitTime": 240, - "waitingTime": 2, - "walkDistance": 1256.0514029764959, - "walkLimitExceeded": false, - "elevationLost": 10.41, - "elevationGained": 15.05, - "transfers": 0, - "fare": { - "fare": { - "regular": { - "currency": { - "symbol": "$", - "currency": "USD", - "defaultFractionDigits": 2, - "currencyCode": "USD" - }, - "cents": 250 - } - }, - "details": { - "regular": [ - { - "fareId": "TriMet:R", - "price": { - "currency": { - "symbol": "$", - "currency": "USD", - "defaultFractionDigits": 2, - "currencyCode": "USD" - }, - "cents": 250 - }, - "routes": [ - "TriMet:100" - ] - } - ] - } - }, - "legs": [ - { - "startTime": 1591717210000, - "endTime": 1591717794000, - "departureDelay": 0, - "arrivalDelay": 0, - "realTime": false, - "distance": 837.5169999999998, - "pathway": false, - "mode": "WALK", - "route": "", - "agencyTimeZoneOffset": -25200000, - "interlineWithPreviousLeg": false, - "from": { - "name": "1709 NW Irving St, Portland 97209", - "lon": -122.68865964147231, - "lat": 45.527817334203, - "departure": 1591717210000, - "orig": "1709 NW Irving St, Portland 97209", - "vertexType": "NORMAL" - }, - "to": { - "name": "Providence Park MAX Station", - "stopId": "TriMet:9758", - "stopCode": "9758", - "lon": -122.689886, - "lat": 45.521321, - "arrival": 1591717794000, - "departure": 1591717795000, - "zoneId": "R", - "stopIndex": 18, - "stopSequence": 19, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - "legGeometry": { - "points": "}c{tGdsykVAu@x@AJ?v@AP?nCE\\?r@Ax@C`BADAd@?lCEp@AdAAV?JA`@?F?jACClAF@B?l@XNFpAj@f@TJDB@B@LFx@\\JDHBH@HBCPFBDBQ~@", - "length": 41 - }, - "rentedBike": false, - "rentedCar": false, - "rentedVehicle": false, - "hailedCar": false, - "flexDrtAdvanceBookMin": 0.0, - "duration": 584.0, - "transitLeg": false, - "intermediateStops": [], - "steps": [ - { - "distance": 20.794, - "relativeDirection": "DEPART", - "streetName": "NW Irving St", - "absoluteDirection": "EAST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.68866035123024, - "lat": 45.527836027296765, - "elevation": [ - { - "first": 0.0, - "second": 20.682842393406283 - }, - { - "first": 10.0, - "second": 20.452842393406282 - }, - { - "first": 20.0, - "second": 20.24284239340628 - }, - { - "first": 20.79, - "second": 20.22284239340628 - } - ] - }, - { - "distance": 547.395, - "relativeDirection": "RIGHT", - "streetName": "NW 17th Ave", - "absoluteDirection": "SOUTH", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6883935, - "lat": 45.527841, - "elevation": [ - { - "first": 0.0, - "second": 18.682842393406283 - }, - { - "first": 10.0, - "second": 18.802842393406284 - }, - { - "first": 20.0, - "second": 19.01284239340628 - }, - { - "first": 31.29, - "second": 19.24284239340628 - }, - { - "first": 31.294, - "second": 19.24284239340628 - }, - { - "first": 39.024, - "second": 19.40284239340628 - }, - { - "first": 39.023, - "second": 19.40284239340628 - }, - { - "first": 49.023, - "second": 19.60284239340628 - }, - { - "first": 59.023, - "second": 19.85284239340628 - }, - { - "first": 69.023, - "second": 19.97284239340628 - }, - { - "first": 79.273, - "second": 20.032842393406284 - }, - { - "first": 79.272, - "second": 20.032842393406284 - }, - { - "first": 89.272, - "second": 20.26284239340628 - }, - { - "first": 99.272, - "second": 20.572842393406283 - }, - { - "first": 109.272, - "second": 20.872842393406284 - }, - { - "first": 119.272, - "second": 21.112842393406282 - }, - { - "first": 129.272, - "second": 21.412842393406283 - }, - { - "first": 139.272, - "second": 21.69284239340628 - }, - { - "first": 149.272, - "second": 22.01284239340628 - }, - { - "first": 159.322, - "second": 22.002842393406283 - }, - { - "first": 159.323, - "second": 22.002842393406283 - }, - { - "first": 169.323, - "second": 22.17284239340628 - }, - { - "first": 176.933, - "second": 22.352842393406284 - }, - { - "first": 176.93, - "second": 22.352842393406284 - }, - { - "first": 186.93, - "second": 22.58284239340628 - }, - { - "first": 196.93, - "second": 22.792842393406282 - }, - { - "first": 205.41, - "second": 22.952842393406282 - }, - { - "first": 205.41, - "second": 22.952842393406282 - }, - { - "first": 215.41, - "second": 23.15284239340628 - }, - { - "first": 225.41, - "second": 23.302842393406284 - }, - { - "first": 237.51, - "second": 23.42284239340628 - }, - { - "first": 237.505, - "second": 23.42284239340628 - }, - { - "first": 247.505, - "second": 23.502842393406283 - }, - { - "first": 257.505, - "second": 23.632842393406282 - }, - { - "first": 267.505, - "second": 23.76284239340628 - }, - { - "first": 277.505, - "second": 23.90284239340628 - }, - { - "first": 292.145, - "second": 24.092842393406283 - }, - { - "first": 292.143, - "second": 24.092842393406283 - }, - { - "first": 295.53299999999996, - "second": 24.132842393406282 - }, - { - "first": 295.53499999999997, - "second": 24.132842393406282 - }, - { - "first": 305.53499999999997, - "second": 24.432842393406283 - }, - { - "first": 316.635, - "second": 24.392842393406283 - }, - { - "first": 316.63599999999997, - "second": 24.392842393406283 - }, - { - "first": 326.63599999999997, - "second": 24.522842393406282 - }, - { - "first": 336.63599999999997, - "second": 24.792842393406282 - }, - { - "first": 346.63599999999997, - "second": 25.052842393406284 - }, - { - "first": 356.63599999999997, - "second": 25.31284239340628 - }, - { - "first": 366.63599999999997, - "second": 25.602842393406284 - }, - { - "first": 376.63599999999997, - "second": 25.842842393406283 - }, - { - "first": 386.63599999999997, - "second": 25.982842393406283 - }, - { - "first": 396.02599999999995, - "second": 26.01284239340628 - }, - { - "first": 396.03, - "second": 26.01284239340628 - }, - { - "first": 406.03, - "second": 26.042842393406282 - }, - { - "first": 416.03, - "second": 26.092842393406283 - }, - { - "first": 423.69, - "second": 26.15284239340628 - }, - { - "first": 423.686, - "second": 26.15284239340628 - }, - { - "first": 433.686, - "second": 26.252842393406283 - }, - { - "first": 443.686, - "second": 26.31284239340628 - }, - { - "first": 453.686, - "second": 26.362842393406282 - }, - { - "first": 461.936, - "second": 26.40284239340628 - }, - { - "first": 461.93699999999995, - "second": 26.40284239340628 - }, - { - "first": 475.08699999999993, - "second": 26.19284239340628 - }, - { - "first": 475.08199999999994, - "second": 26.19284239340628 - }, - { - "first": 485.08199999999994, - "second": 25.732842393406283 - }, - { - "first": 495.08199999999994, - "second": 25.142842393406283 - }, - { - "first": 501.5519999999999, - "second": 24.76284239340628 - }, - { - "first": 501.54999999999995, - "second": 24.76284239340628 - }, - { - "first": 505.17999999999995, - "second": 24.602842393406284 - }, - { - "first": 505.17699999999996, - "second": 24.602842393406284 - }, - { - "first": 515.1769999999999, - "second": 24.12284239340628 - }, - { - "first": 525.1769999999999, - "second": 23.62284239340628 - }, - { - "first": 535.1769999999999, - "second": 23.292842393406282 - }, - { - "first": 547.3969999999999, - "second": 23.51284239340628 - } - ] - }, - { - "distance": 30.847, - "relativeDirection": "RIGHT", - "streetName": "W Burnside St", - "absoluteDirection": "WEST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.688213, - "lat": 45.5229198, - "elevation": [ - { - "first": 0.0, - "second": 23.51284239340628 - }, - { - "first": 10.0, - "second": 23.76284239340628 - }, - { - "first": 20.0, - "second": 24.052842393406284 - }, - { - "first": 30.85, - "second": 24.302842393406284 - } - ] - }, - { - "distance": 195.61100000000002, - "relativeDirection": "LEFT", - "streetName": "SW 17th Ave", - "absoluteDirection": "SOUTH", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6886081, - "lat": 45.522938, - "elevation": [ - { - "first": 0.0, - "second": 24.302842393406284 - }, - { - "first": 10.0, - "second": 24.532842393406284 - }, - { - "first": 20.0, - "second": 25.182842393406283 - }, - { - "first": 30.0, - "second": 25.802842393406284 - }, - { - "first": 44.36, - "second": 26.032842393406284 - }, - { - "first": 44.347, - "second": 26.032842393406284 - }, - { - "first": 54.347, - "second": 26.452842393406282 - }, - { - "first": 64.34700000000001, - "second": 27.282842393406284 - }, - { - "first": 74.34700000000001, - "second": 27.952842393406282 - }, - { - "first": 84.34700000000001, - "second": 28.132842393406282 - }, - { - "first": 92.137, - "second": 28.292842393406282 - }, - { - "first": 92.13900000000001, - "second": 28.292842393406282 - }, - { - "first": 102.13900000000001, - "second": 28.80284239340628 - }, - { - "first": 112.13900000000001, - "second": 29.30284239340628 - }, - { - "first": 123.74900000000001, - "second": 29.822842393406283 - }, - { - "first": 123.75200000000001, - "second": 29.822842393406283 - }, - { - "first": 136.752, - "second": 30.352842393406284 - } - ] - }, - { - "distance": 7.173, - "relativeDirection": "RIGHT", - "streetName": "path", - "absoluteDirection": "WEST", - "stayOn": false, - "area": false, - "bogusName": true, - "lon": -122.689436, - "lat": 45.521281, - "elevation": [ - { - "first": 0.0, - "second": 32.14284239340628 - }, - { - "first": 7.17, - "second": 32.092842393406286 - } - ] - }, - { - "distance": 8.612, - "relativeDirection": "LEFT", - "streetName": "path", - "absoluteDirection": "SOUTH", - "stayOn": true, - "area": false, - "bogusName": true, - "lon": -122.6895213, - "lat": 45.5213053, - "elevation": [ - { - "first": 0.0, - "second": 32.092842393406286 - }, - { - "first": 8.61, - "second": 32.202842393406286 - } - ] - }, - { - "distance": 27.085, - "relativeDirection": "RIGHT", - "streetName": "Providence Park (path)", - "absoluteDirection": "WEST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6895617, - "lat": 45.5212332, - "elevation": [] - } - ] - }, - { - "startTime": 1591717795000, - "endTime": 1591718035000, - "departureDelay": 0, - "arrivalDelay": 0, - "realTime": false, - "distance": 893.8869820964361, - "pathway": false, - "mode": "TRAM", - "route": "MAX Blue Line", - "agencyName": "TriMet", - "agencyUrl": "https://trimet.org/", - "agencyTimeZoneOffset": -25200000, - "routeColor": "084C8D", - "routeType": 0, - "routeId": "TriMet:100", - "routeTextColor": "FFFFFF", - "interlineWithPreviousLeg": false, - "tripBlockId": "9004", - "headsign": "Gresham", - "agencyId": "TRIMET", - "tripId": "TriMet:9942889", - "serviceDate": "20200609", - "from": { - "name": "Providence Park MAX Station", - "stopId": "TriMet:9758", - "stopCode": "9758", - "lon": -122.689886, - "lat": 45.521321, - "arrival": 1591717794000, - "departure": 1591717795000, - "zoneId": "R", - "stopIndex": 18, - "stopSequence": 19, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - "to": { - "name": "Pioneer Square South MAX Station", - "stopId": "TriMet:8334", - "stopCode": "8334", - "lon": -122.679145, - "lat": 45.518496, - "arrival": 1591718035000, - "departure": 1591718036000, - "zoneId": "R", - "stopIndex": 20, - "stopSequence": 21, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - "legGeometry": { - "points": "m{ytGtzykV@AN}@BQ@EJm@Lk@PcAF[l@mDDQBMn@uDDUj@gDDSl@mDFWDW\\oBHe@@KBI@I@EF]f@uCBQBI@KTqAP}@@IDS@GTqABSBKn@sDBQf@qC", - "length": 41 - }, - "interStopGeometry": [ - { - "points": "m{ytGtzykV@AN}@BQ@EJm@Lk@PcAF[l@mDDQBMn@uDDUj@gDDSl@mDFWDW\\oBHe@@KBI@I@EF]f@uCBQBI@KTqAP}@", - "length": 32 - }, - { - "points": "qmytGfgxkV@IDS@GTqABSBKn@sDBQf@qC", - "length": 10 - } - ], - "alerts": [ - { - "effectiveStartDate": 1591959600000, - "alertDescriptionText": "A scarf, bandanna or fabric mask works, as long as it covers your nose and mouth.", - "alertUrl": "http://trimet.org/health" - }, - { - "effectiveStartDate": 1585876166000, - "alertDescriptionText": "Due to COVID-19 service reductions, beginning Sunday, April 5th, on weekdays, trains will run every 15 minutes throughout most of the day. On weekends will run on Sunday schedule. For arrivals and trip planning, see:", - "alertUrl": "http://trimet.org/reducedservice" - }, - { - "effectiveStartDate": 1572827580000, - "alertDescriptionText": "The east elevators (zoo side) at Washington Park MAX Station are closed for improvements until early May. For access between platform and ground levels, use the west elevators (world forestry side). ", - "alertUrl": "https://news.trimet.org/2019/08/trimet-to-replace-elevators-at-the-deepest-transit-station-in-north-america/" - }, - { - "effectiveStartDate": 1591965804000, - "alertDescriptionText": "MAX Blue and Red lines disrupted due to police activity near Goose Hollow/SW Jefferson. Shuttle buses serving stations between Library/SW 9th and Galleria/SW 10th and Sunset Transit Center. Expect delays.", - "alertUrl": "http://trimet.org/alerts/" - } - ], - "routeLongName": "MAX Blue Line", - "rentedBike": false, - "rentedCar": false, - "rentedVehicle": false, - "hailedCar": false, - "flexDrtAdvanceBookMin": 0.0, - "duration": 240.0, - "transitLeg": true, - "intermediateStops": [ - { - "name": "Library/SW 9th Ave MAX Station", - "stopId": "TriMet:8333", - "stopCode": "8333", - "lon": -122.68162, - "lat": 45.51916, - "arrival": 1591717940000, - "departure": 1591717975000, - "zoneId": "R", - "stopIndex": 19, - "stopSequence": 20, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - } - ], - "steps": [] - }, - { - "startTime": 1591718036000, - "endTime": 1591718324000, - "departureDelay": 0, - "arrivalDelay": 0, - "realTime": false, - "distance": 418.19, - "pathway": false, - "mode": "WALK", - "route": "", - "agencyTimeZoneOffset": -25200000, - "interlineWithPreviousLeg": false, - "from": { - "name": "Pioneer Square South MAX Station", - "stopId": "TriMet:8334", - "stopCode": "8334", - "lon": -122.679145, - "lat": 45.518496, - "arrival": 1591718035000, - "departure": 1591718036000, - "zoneId": "R", - "stopIndex": 20, - "stopSequence": 21, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - "to": { - "name": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", - "lon": -122.67681483620306, - "lat": 45.51639151281627, - "arrival": 1591718324000, - "orig": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", - "vertexType": "NORMAL" - }, - "legGeometry": { - "points": "uiytGrwwkVDQBODBDB@G@Kn@iDBO@KLF|Ap@FBNHLF|Ar@FDNFBOh@{CD[FWb@eC", - "length": 23 - }, - "rentedBike": false, - "rentedCar": false, - "rentedVehicle": false, - "hailedCar": false, - "flexDrtAdvanceBookMin": 0.0, - "duration": 288.0, - "transitLeg": false, - "intermediateStops": [], - "steps": [ - { - "distance": 7.715, - "relativeDirection": "DEPART", - "streetName": "Pioneer Courthouse Sq (pedestrian street)", - "absoluteDirection": "EAST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.67913709381972, - "lat": 45.51851024632791, - "elevation": [] - }, - { - "distance": 6.217, - "relativeDirection": "CONTINUE", - "streetName": "path", - "absoluteDirection": "EAST", - "stayOn": false, - "area": false, - "bogusName": true, - "lon": -122.6790448, - "lat": 45.5184851, - "elevation": [ - { - "first": 0.0, - "second": 15.172842393406283 - }, - { - "first": 6.22, - "second": 15.002842393406283 - } - ] - }, - { - "distance": 7.252, - "relativeDirection": "RIGHT", - "streetName": "SW 6th Ave", - "absoluteDirection": "SOUTH", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6789697, - "lat": 45.5184662, - "elevation": [ - { - "first": 0.0, - "second": 15.002842393406283 - }, - { - "first": 7.25, - "second": 15.112842393406282 - } - ] - }, - { - "distance": 90.85300000000001, - "relativeDirection": "LEFT", - "streetName": "SW Yamhill St", - "absoluteDirection": "EAST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6790039, - "lat": 45.5184056, - "elevation": [ - { - "first": 0.0, - "second": 15.112842393406282 - }, - { - "first": 8.41, - "second": 15.092842393406283 - }, - { - "first": 8.407, - "second": 15.092842393406283 - }, - { - "first": 18.407, - "second": 14.942842393406282 - }, - { - "first": 28.407, - "second": 14.672842393406283 - }, - { - "first": 38.407, - "second": 14.392842393406283 - }, - { - "first": 48.407, - "second": 14.192842393406282 - }, - { - "first": 58.407, - "second": 13.772842393406282 - }, - { - "first": 68.407, - "second": 13.452842393406282 - }, - { - "first": 79.467, - "second": 13.182842393406283 - }, - { - "first": 79.465, - "second": 13.182842393406283 - }, - { - "first": 90.855, - "second": 12.942842393406282 - } - ] - }, - { - "distance": 156.99599999999998, - "relativeDirection": "RIGHT", - "streetName": "SW 5th Ave", - "absoluteDirection": "SOUTH", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.677916, - "lat": 45.5181117, - "elevation": [] - }, - { - "distance": 149.157, - "relativeDirection": "LEFT", - "streetName": "SW Salmon St", - "absoluteDirection": "EAST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6786441, - "lat": 45.5167953, - "elevation": [ - { - "first": 0.0, - "second": 17.552842393406284 - }, - { - "first": 10.0, - "second": 17.292842393406282 - }, - { - "first": 20.0, - "second": 16.962842393406284 - }, - { - "first": 30.0, - "second": 16.642842393406283 - }, - { - "first": 40.0, - "second": 16.162842393406283 - }, - { - "first": 50.0, - "second": 15.712842393406282 - }, - { - "first": 60.0, - "second": 15.122842393406282 - }, - { - "first": 70.0, - "second": 15.062842393406282 - }, - { - "first": 83.14, - "second": 14.982842393406283 - }, - { - "first": 83.144, - "second": 14.982842393406283 - }, - { - "first": 93.01400000000001, - "second": 14.762842393406283 - }, - { - "first": 93.01700000000001, - "second": 14.762842393406283 - }, - { - "first": 103.01700000000001, - "second": 14.502842393406283 - }, - { - "first": 113.01700000000001, - "second": 14.272842393406282 - }, - { - "first": 123.01700000000001, - "second": 13.872842393406282 - }, - { - "first": 133.017, - "second": 13.632842393406282 - }, - { - "first": 143.017, - "second": 13.412842393406283 - }, - { - "first": 149.157, - "second": 13.152842393406281 - } - ] - } - ] - } - ], - "tooSloped": false - }, - { - "duration": 1242, - "startTime": 1591717148000, - "endTime": 1591718390000, - "walkTime": 820, - "transitTime": 420, - "waitingTime": 2, - "walkDistance": 1297.4104029783646, - "walkLimitExceeded": false, - "elevationLost": 28.980000000000004, - "elevationGained": 1.1200000000000003, - "transfers": 0, - "fare": { - "fare": { - "regular": { - "currency": { - "symbol": "$", - "currency": "USD", - "defaultFractionDigits": 2, - "currencyCode": "USD" - }, - "cents": 200 - } - }, - "details": { - "regular": [ - { - "fareId": "TriMet:SC", - "price": { - "currency": { - "symbol": "$", - "currency": "USD", - "defaultFractionDigits": 2, - "currencyCode": "USD" - }, - "cents": 200 - }, - "routes": [ - "TriMet:195" - ] - } - ] - } - }, - "legs": [ - { - "startTime": 1591717148000, - "endTime": 1591717499000, - "departureDelay": 0, - "arrivalDelay": 0, - "realTime": false, - "distance": 578.6750000000001, - "pathway": false, - "mode": "WALK", - "route": "", - "agencyTimeZoneOffset": -25200000, - "interlineWithPreviousLeg": false, - "from": { - "name": "1709 NW Irving St, Portland 97209", - "lon": -122.68865964147231, - "lat": 45.527817334203, - "departure": 1591717148000, - "orig": "1709 NW Irving St, Portland 97209", - "vertexType": "NORMAL" - }, - "to": { - "name": "NW 11th & Johnson", - "stopId": "TriMet:10753", - "stopCode": "10753", - "lon": -122.682374, - "lat": 45.528742, - "arrival": 1591717499000, - "departure": 1591717500000, - "zoneId": "B", - "stopIndex": 1, - "stopSequence": 2, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - "legGeometry": { - "points": "}c{tGdsykVAu@qBB[@CiECkE???e@AoCAS?Y?kAAsA?Q?QAeD?QASAeDMA?G", - "length": 21 - }, - "rentedBike": false, - "rentedCar": false, - "rentedVehicle": false, - "hailedCar": false, - "flexDrtAdvanceBookMin": 0.0, - "duration": 351.0, - "transitLeg": false, - "intermediateStops": [], - "steps": [ - { - "distance": 20.794, - "relativeDirection": "DEPART", - "streetName": "NW Irving St", - "absoluteDirection": "EAST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.68866035123024, - "lat": 45.527836027296765, - "elevation": [ - { - "first": 0.0, - "second": 20.682842393406283 - }, - { - "first": 10.0, - "second": 20.452842393406282 - }, - { - "first": 20.0, - "second": 20.24284239340628 - }, - { - "first": 20.79, - "second": 20.22284239340628 - } - ] - }, - { - "distance": 79.476, - "relativeDirection": "LEFT", - "streetName": "NW 17th Ave", - "absoluteDirection": "NORTH", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6883935, - "lat": 45.527841, - "elevation": [ - { - "first": 0.0, - "second": 18.682842393406283 - }, - { - "first": 10.0, - "second": 18.612842393406282 - }, - { - "first": 20.0, - "second": 18.47284239340628 - }, - { - "first": 30.0, - "second": 18.272842393406282 - }, - { - "first": 40.0, - "second": 18.08284239340628 - }, - { - "first": 50.0, - "second": 17.90284239340628 - }, - { - "first": 63.93, - "second": 17.632842393406282 - }, - { - "first": 63.928, - "second": 17.632842393406282 - }, - { - "first": 73.928, - "second": 17.412842393406283 - }, - { - "first": 79.478, - "second": 17.362842393406282 - } - ] - }, - { - "distance": 467.456, - "relativeDirection": "RIGHT", - "streetName": "NW Johnson St", - "absoluteDirection": "EAST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6884207, - "lat": 45.5285555, - "elevation": [ - { - "first": 0.0, - "second": 17.362842393406282 - }, - { - "first": 10.0, - "second": 17.122842393406284 - }, - { - "first": 20.0, - "second": 17.002842393406283 - }, - { - "first": 30.0, - "second": 16.632842393406282 - }, - { - "first": 40.0, - "second": 16.372842393406284 - }, - { - "first": 50.0, - "second": 16.15284239340628 - }, - { - "first": 60.0, - "second": 15.932842393406283 - }, - { - "first": 70.0, - "second": 15.692842393406282 - }, - { - "first": 78.6, - "second": 15.642842393406283 - }, - { - "first": 78.602, - "second": 15.642842393406283 - }, - { - "first": 88.602, - "second": 15.662842393406283 - }, - { - "first": 98.602, - "second": 15.952842393406282 - }, - { - "first": 108.602, - "second": 16.112842393406282 - }, - { - "first": 118.602, - "second": 16.072842393406283 - }, - { - "first": 128.602, - "second": 15.942842393406282 - }, - { - "first": 138.602, - "second": 15.492842393406281 - }, - { - "first": 148.602, - "second": 14.812842393406282 - }, - { - "first": 157.762, - "second": 14.452842393406282 - }, - { - "first": 157.757, - "second": 14.452842393406282 - }, - { - "first": 167.757, - "second": 14.222842393406282 - }, - { - "first": 177.757, - "second": 14.022842393406282 - }, - { - "first": 187.757, - "second": 13.782842393406282 - }, - { - "first": 197.757, - "second": 13.582842393406281 - }, - { - "first": 207.757, - "second": 13.422842393406283 - }, - { - "first": 217.757, - "second": 13.102842393406283 - }, - { - "first": 227.757, - "second": 12.842842393406283 - }, - { - "first": 236.787, - "second": 12.762842393406283 - }, - { - "first": 236.78, - "second": 12.762842393406283 - }, - { - "first": 246.78, - "second": 12.462842393406284 - }, - { - "first": 256.78, - "second": 12.312842393406282 - }, - { - "first": 266.78, - "second": 12.132842393406282 - }, - { - "first": 276.35, - "second": 11.912842393406283 - }, - { - "first": 276.349, - "second": 11.912842393406283 - }, - { - "first": 286.349, - "second": 11.632842393406282 - }, - { - "first": 296.349, - "second": 11.462842393406282 - }, - { - "first": 306.349, - "second": 11.182842393406283 - }, - { - "first": 315.839, - "second": 10.942842393406282 - }, - { - "first": 315.841, - "second": 10.942842393406282 - }, - { - "first": 325.841, - "second": 10.892842393406282 - }, - { - "first": 335.841, - "second": 10.692842393406282 - }, - { - "first": 345.841, - "second": 10.582842393406283 - }, - { - "first": 355.841, - "second": 10.382842393406282 - }, - { - "first": 365.841, - "second": 10.112842393406282 - }, - { - "first": 375.841, - "second": 9.802842393406282 - }, - { - "first": 385.841, - "second": 9.522842393406282 - }, - { - "first": 395.041, - "second": 9.362842393406282 - }, - { - "first": 395.038, - "second": 9.362842393406282 - }, - { - "first": 405.038, - "second": 9.432842393406283 - }, - { - "first": 415.038, - "second": 9.632842393406282 - }, - { - "first": 425.038, - "second": 9.832842393406283 - }, - { - "first": 435.038, - "second": 9.882842393406282 - }, - { - "first": 445.038, - "second": 9.912842393406283 - }, - { - "first": 455.038, - "second": 9.872842393406282 - }, - { - "first": 467.458, - "second": 9.702842393406282 - } - ] - }, - { - "distance": 7.603, - "relativeDirection": "LEFT", - "streetName": "path", - "absoluteDirection": "NORTH", - "stayOn": false, - "area": false, - "bogusName": true, - "lon": -122.6824215, - "lat": 45.5286553, - "elevation": [ - { - "first": 0.0, - "second": 9.702842393406282 - }, - { - "first": 7.6, - "second": 9.642842393406282 - } - ] - }, - { - "distance": 3.346, - "relativeDirection": "RIGHT", - "streetName": "path", - "absoluteDirection": "EAST", - "stayOn": true, - "area": false, - "bogusName": true, - "lon": -122.6824167, - "lat": 45.5287236, - "elevation": [] - } - ] - }, - { - "startTime": 1591717500000, - "endTime": 1591717920000, - "departureDelay": 0, - "arrivalDelay": 0, - "realTime": false, - "distance": 1103.6887432320718, - "pathway": false, - "mode": "TRAM", - "route": "Portland Streetcar - B Loop", - "agencyName": "Portland Streetcar", - "agencyUrl": "https://portlandstreetcar.org/", - "agencyTimeZoneOffset": -25200000, - "routeColor": "0093B2", - "routeType": 0, - "routeId": "TriMet:195", - "routeTextColor": "FFFFFF", - "interlineWithPreviousLeg": false, - "tripBlockId": "58", - "headsign": "Lloyd via OMSI", - "agencyId": "PSC", - "tripId": "TriMet:9945177", - "serviceDate": "20200609", - "from": { - "name": "NW 11th & Johnson", - "stopId": "TriMet:10753", - "stopCode": "10753", - "lon": -122.682374, - "lat": 45.528742, - "arrival": 1591717499000, - "departure": 1591717500000, - "zoneId": "B", - "stopIndex": 1, - "stopSequence": 2, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - "to": { - "name": "SW 11th & Taylor", - "stopId": "TriMet:9633", - "stopCode": "9633", - "lon": -122.683873, - "lat": 45.519059, - "arrival": 1591717920000, - "departure": 1591717921000, - "zoneId": "B", - "stopIndex": 5, - "stopSequence": 6, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - "legGeometry": { - "points": "si{tGrkxkVP?dACtEGZ?~ACB?L?J?`CE~BCLAn@?~ACvBCFAL?J?rBCL?vBCLAF?D?D@B@lBx@LF`Bt@HBLFHDtB~@DB@?DBFB`Br@DBFB??FBlBz@", - "length": 43 - }, - "interStopGeometry": [ - { - "points": "si{tGrkxkVP?dACtEGZ?~AC", - "length": 6 - }, - { - "points": "i|ztGbkxkVB?L?J?`CE~BCLAn@?~ACvBC", - "length": 10 - }, - { - "points": "sjztGnjxkVFAL?J?rBCL?vBCLAF?D?D@B@lBx@LF`Bt@", - "length": 15 - }, - { - "points": "syytG~mxkVHBLFHDtB~@DB@?DBFB`Br@DBFB??FBlBz@", - "length": 15 - } - ], - "alerts": [ - { - "effectiveStartDate": 1591959600000, - "alertDescriptionText": "A scarf, bandanna or fabric mask works, as long as it covers your nose and mouth.", - "alertUrl": "http://trimet.org/health" - }, - { - "effectiveStartDate": 1585093573000, - "alertDescriptionText": "Streetcar has reduced regular weekday service to every 20 minutes between about 5:30 a.m. and about 11:30 p.m. For more info:", - "alertUrl": "https://portlandstreetcar.org/news/2020/03/streetcar-service-will-reduce-to-20-minute-headways-beginning-march-24" - } - ], - "routeLongName": "Portland Streetcar - B Loop", - "rentedBike": false, - "rentedCar": false, - "rentedVehicle": false, - "hailedCar": false, - "flexDrtAdvanceBookMin": 0.0, - "duration": 420.0, - "transitLeg": true, - "intermediateStops": [ - { - "name": "NW 11th & Glisan", - "stopId": "TriMet:10754", - "stopCode": "10754", - "lon": -122.682297, - "lat": 45.526605, - "arrival": 1591717560000, - "departure": 1591717560000, - "zoneId": "B", - "stopIndex": 2, - "stopSequence": 3, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - { - "name": "NW 11th & Couch", - "stopId": "TriMet:10756", - "stopCode": "10756", - "lon": -122.682223, - "lat": 45.523784, - "arrival": 1591717740000, - "departure": 1591717740000, - "zoneId": "B", - "stopIndex": 3, - "stopSequence": 4, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - { - "name": "SW 11th & Alder", - "stopId": "TriMet:9600", - "stopCode": "9600", - "lon": -122.682819, - "lat": 45.521094, - "arrival": 1591717860000, - "departure": 1591717860000, - "zoneId": "B", - "stopIndex": 4, - "stopSequence": 5, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - } - ], - "steps": [] - }, - { - "startTime": 1591717921000, - "endTime": 1591718390000, - "departureDelay": 0, - "arrivalDelay": 0, - "realTime": false, - "distance": 718.4660000000001, - "pathway": false, - "mode": "WALK", - "route": "", - "agencyTimeZoneOffset": -25200000, - "interlineWithPreviousLeg": false, - "from": { - "name": "SW 11th & Taylor", - "stopId": "TriMet:9633", - "stopCode": "9633", - "lon": -122.683873, - "lat": 45.519059, - "arrival": 1591717920000, - "departure": 1591717921000, - "zoneId": "B", - "stopIndex": 5, - "stopSequence": 6, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - "to": { - "name": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", - "lon": -122.67681483620306, - "lat": 45.51639151281627, - "arrival": 1591718390000, - "orig": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", - "vertexType": "NORMAL" - }, - "legGeometry": { - "points": "cmytGfuxkVAHD@JFBQ@GDSh@_D??FU@IBMX_BRiA@K?AVqA@KBOFY`@aCFYBSh@}CBO@CDU^wBHc@F_@@CLF|Ar@FDNFBOh@{CD[FWb@eC", - "length": 40 - }, - "rentedBike": false, - "rentedCar": false, - "rentedVehicle": false, - "hailedCar": false, - "flexDrtAdvanceBookMin": 0.0, - "duration": 469.0, - "transitLeg": false, - "intermediateStops": [], - "steps": [ - { - "distance": 14.561, - "relativeDirection": "DEPART", - "streetName": "path", - "absoluteDirection": "WEST", - "stayOn": false, - "area": false, - "bogusName": true, - "lon": -122.6838786, - "lat": 45.5190659, - "elevation": [ - { - "first": 0.0, - "second": 29.782842393406284 - }, - { - "first": 3.48, - "second": 29.83284239340628 - }, - { - "first": 7.5809999999999995, - "second": 29.83284239340628 - }, - { - "first": 11.690999999999999, - "second": 29.862842393406282 - } - ] - }, - { - "distance": 475.42999999999995, - "relativeDirection": "LEFT", - "streetName": "SW Taylor St", - "absoluteDirection": "EAST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6839744, - "lat": 45.5189848, - "elevation": [ - { - "first": 0.0, - "second": 29.87284239340628 - }, - { - "first": 10.39, - "second": 29.87284239340628 - }, - { - "first": 10.386, - "second": 29.87284239340628 - }, - { - "first": 18.735999999999997, - "second": 29.662842393406283 - }, - { - "first": 18.737000000000002, - "second": 29.662842393406283 - }, - { - "first": 28.737000000000002, - "second": 29.252842393406283 - }, - { - "first": 38.737, - "second": 28.97284239340628 - }, - { - "first": 48.737, - "second": 28.632842393406282 - }, - { - "first": 58.737, - "second": 28.05284239340628 - }, - { - "first": 68.737, - "second": 27.62284239340628 - }, - { - "first": 78.737, - "second": 27.202842393406282 - }, - { - "first": 85.84700000000001, - "second": 26.952842393406282 - }, - { - "first": 85.84299999999999, - "second": 26.952842393406282 - }, - { - "first": 95.353, - "second": 26.932842393406283 - }, - { - "first": 95.35099999999998, - "second": 26.932842393406283 - }, - { - "first": 105.13099999999999, - "second": 26.65284239340628 - }, - { - "first": 105.12999999999998, - "second": 26.65284239340628 - }, - { - "first": 115.12999999999998, - "second": 26.22284239340628 - }, - { - "first": 125.12999999999998, - "second": 25.87284239340628 - }, - { - "first": 135.13, - "second": 25.642842393406283 - }, - { - "first": 145.48999999999998, - "second": 25.15284239340628 - }, - { - "first": 145.486, - "second": 25.15284239340628 - }, - { - "first": 155.486, - "second": 24.90284239340628 - }, - { - "first": 165.486, - "second": 24.602842393406284 - }, - { - "first": 176.05599999999998, - "second": 24.342842393406283 - }, - { - "first": 176.051, - "second": 24.342842393406283 - }, - { - "first": 181.18099999999998, - "second": 24.302842393406284 - }, - { - "first": 181.18099999999998, - "second": 24.302842393406284 - }, - { - "first": 191.18099999999998, - "second": 24.08284239340628 - }, - { - "first": 201.18099999999998, - "second": 23.752842393406283 - }, - { - "first": 216.081, - "second": 23.202842393406282 - }, - { - "first": 216.077, - "second": 23.202842393406282 - }, - { - "first": 221.187, - "second": 23.08284239340628 - }, - { - "first": 221.182, - "second": 23.08284239340628 - }, - { - "first": 231.182, - "second": 22.642842393406283 - }, - { - "first": 238.742, - "second": 22.17284239340628 - }, - { - "first": 238.742, - "second": 22.17284239340628 - }, - { - "first": 248.742, - "second": 21.65284239340628 - }, - { - "first": 258.74199999999996, - "second": 21.26284239340628 - }, - { - "first": 268.74199999999996, - "second": 20.682842393406283 - }, - { - "first": 278.74199999999996, - "second": 20.272842393406282 - }, - { - "first": 288.74199999999996, - "second": 19.74284239340628 - }, - { - "first": 303.502, - "second": 19.622842393406284 - }, - { - "first": 303.505, - "second": 19.622842393406284 - }, - { - "first": 313.505, - "second": 19.342842393406283 - }, - { - "first": 323.505, - "second": 18.962842393406284 - }, - { - "first": 333.505, - "second": 18.802842393406284 - }, - { - "first": 343.505, - "second": 18.532842393406284 - }, - { - "first": 353.505, - "second": 18.252842393406283 - }, - { - "first": 363.505, - "second": 18.002842393406283 - }, - { - "first": 373.505, - "second": 17.642842393406283 - }, - { - "first": 384.315, - "second": 17.522842393406282 - }, - { - "first": 384.311, - "second": 17.522842393406282 - }, - { - "first": 394.311, - "second": 17.532842393406284 - }, - { - "first": 404.311, - "second": 17.292842393406282 - }, - { - "first": 414.311, - "second": 17.112842393406282 - }, - { - "first": 424.311, - "second": 16.90284239340628 - }, - { - "first": 434.311, - "second": 16.56284239340628 - }, - { - "first": 444.311, - "second": 16.392842393406283 - }, - { - "first": 454.311, - "second": 16.162842393406283 - }, - { - "first": 464.311, - "second": 15.902842393406281 - }, - { - "first": 475.431, - "second": 15.822842393406283 - } - ] - }, - { - "distance": 79.318, - "relativeDirection": "RIGHT", - "streetName": "SW 5th Ave", - "absoluteDirection": "SOUTH", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6782737, - "lat": 45.5174597, - "elevation": [] - }, - { - "distance": 149.157, - "relativeDirection": "LEFT", - "streetName": "SW Salmon St", - "absoluteDirection": "EAST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6786441, - "lat": 45.5167953, - "elevation": [ - { - "first": 0.0, - "second": 17.552842393406284 - }, - { - "first": 10.0, - "second": 17.292842393406282 - }, - { - "first": 20.0, - "second": 16.962842393406284 - }, - { - "first": 30.0, - "second": 16.642842393406283 - }, - { - "first": 40.0, - "second": 16.162842393406283 - }, - { - "first": 50.0, - "second": 15.712842393406282 - }, - { - "first": 60.0, - "second": 15.122842393406282 - }, - { - "first": 70.0, - "second": 15.062842393406282 - }, - { - "first": 83.14, - "second": 14.982842393406283 - }, - { - "first": 83.144, - "second": 14.982842393406283 - }, - { - "first": 93.01400000000001, - "second": 14.762842393406283 - }, - { - "first": 93.01700000000001, - "second": 14.762842393406283 - }, - { - "first": 103.01700000000001, - "second": 14.502842393406283 - }, - { - "first": 113.01700000000001, - "second": 14.272842393406282 - }, - { - "first": 123.01700000000001, - "second": 13.872842393406282 - }, - { - "first": 133.017, - "second": 13.632842393406282 - }, - { - "first": 143.017, - "second": 13.412842393406283 - }, - { - "first": 149.157, - "second": 13.152842393406281 - } - ] - } - ] - } - ], - "tooSloped": false + "data": { + "plan": { + "date": 1591716900000, + "from": { + "name": "1709 NW Irving St, Portland 97209", + "lon": -122.68865964147231, + "lat": 45.527817334203, + "orig": "1709 NW Irving St, Portland 97209", + "vertexType": "NORMAL" + }, + "to": { + "name": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", + "lon": -122.67681483620306, + "lat": 45.51639151281627, + "orig": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", + "vertexType": "NORMAL" }, - { - "duration": 1036, - "startTime": 1591717825000, - "endTime": 1591718861000, - "walkTime": 674, - "transitTime": 360, - "waitingTime": 2, - "walkDistance": 966.7338656666136, - "walkLimitExceeded": false, - "elevationLost": 8.23, - "elevationGained": 13.169999999999996, - "transfers": 0, - "fare": { + "itineraries": [ + { + "duration": 1114, + "startTime": 1591717210000, + "endTime": 1591718324000, + "walkTime": 872, + "transitTime": 240, + "waitingTime": 2, + "walkDistance": 1256.0514029764959, + "walkLimitExceeded": false, + "elevationLost": 10.41, + "elevationGained": 15.05, + "transfers": 0, "fare": { - "regular": { - "currency": { - "symbol": "$", - "currency": "USD", - "defaultFractionDigits": 2, - "currencyCode": "USD" - }, - "cents": 250 - } - }, - "details": { - "regular": [ - { - "fareId": "TriMet:B", - "price": { - "currency": { - "symbol": "$", - "currency": "USD", - "defaultFractionDigits": 2, - "currencyCode": "USD" - }, - "cents": 250 + "fare": { + "regular": { + "currency": { + "symbol": "$", + "currency": "USD", + "defaultFractionDigits": 2, + "currencyCode": "USD" }, - "routes": [ - "TriMet:15" - ] + "cents": 250 } - ] - } - }, - "legs": [ - { - "startTime": 1591717825000, - "endTime": 1591718399000, - "departureDelay": 0, - "arrivalDelay": 0, - "realTime": false, - "distance": 802.1549999999999, - "pathway": false, - "mode": "WALK", - "route": "", - "agencyTimeZoneOffset": -25200000, - "interlineWithPreviousLeg": false, - "from": { - "name": "1709 NW Irving St, Portland 97209", - "lon": -122.68865964147231, - "lat": 45.527817334203, - "departure": 1591717825000, - "orig": "1709 NW Irving St, Portland 97209", - "vertexType": "NORMAL" - }, - "to": { - "name": "SW 18th & Morrison", - "stopId": "TriMet:6911", - "stopCode": "6911", - "lon": -122.690453, - "lat": 45.52188, - "arrival": 1591718399000, - "departure": 1591718400000, - "zoneId": "B", - "stopIndex": 21, - "stopSequence": 22, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" }, - "legGeometry": { - "points": "}c{tGdsykVAu@x@AJ?v@AP?nCE\\?r@Ax@C`BADAd@?lCEp@AdAAV??NBvDl@?@?F?J?J?H?H?B@BB@DBDBFBHLZBDHLHLHHFDHFj@XHPTHJDLRCP?BB?B@B@`@N", - "length": 50 - }, - "rentedBike": false, - "rentedCar": false, - "rentedVehicle": false, - "hailedCar": false, - "flexDrtAdvanceBookMin": 0.0, - "duration": 574.0, - "transitLeg": false, - "intermediateStops": [], - "steps": [ - { - "distance": 20.794, - "relativeDirection": "DEPART", - "streetName": "NW Irving St", - "absoluteDirection": "EAST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.68866035123024, - "lat": 45.527836027296765, - "elevation": [ - { - "first": 0.0, - "second": 20.682842393406283 - }, - { - "first": 10.0, - "second": 20.452842393406282 - }, - { - "first": 20.0, - "second": 20.24284239340628 - }, - { - "first": 20.79, - "second": 20.22284239340628 - } - ] - }, - { - "distance": 475.08199999999994, - "relativeDirection": "RIGHT", - "streetName": "NW 17th Ave", - "absoluteDirection": "SOUTH", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6883935, - "lat": 45.527841, - "elevation": [ - { - "first": 0.0, - "second": 18.682842393406283 - }, - { - "first": 10.0, - "second": 18.802842393406284 - }, - { - "first": 20.0, - "second": 19.01284239340628 - }, - { - "first": 31.29, - "second": 19.24284239340628 - }, - { - "first": 31.294, - "second": 19.24284239340628 - }, - { - "first": 39.024, - "second": 19.40284239340628 - }, - { - "first": 39.023, - "second": 19.40284239340628 - }, - { - "first": 49.023, - "second": 19.60284239340628 - }, - { - "first": 59.023, - "second": 19.85284239340628 - }, - { - "first": 69.023, - "second": 19.97284239340628 - }, - { - "first": 79.273, - "second": 20.032842393406284 - }, - { - "first": 79.272, - "second": 20.032842393406284 - }, - { - "first": 89.272, - "second": 20.26284239340628 - }, - { - "first": 99.272, - "second": 20.572842393406283 - }, - { - "first": 109.272, - "second": 20.872842393406284 - }, - { - "first": 119.272, - "second": 21.112842393406282 - }, - { - "first": 129.272, - "second": 21.412842393406283 - }, - { - "first": 139.272, - "second": 21.69284239340628 - }, - { - "first": 149.272, - "second": 22.01284239340628 - }, - { - "first": 159.322, - "second": 22.002842393406283 - }, - { - "first": 159.323, - "second": 22.002842393406283 - }, - { - "first": 169.323, - "second": 22.17284239340628 - }, - { - "first": 176.933, - "second": 22.352842393406284 - }, - { - "first": 176.93, - "second": 22.352842393406284 - }, - { - "first": 186.93, - "second": 22.58284239340628 - }, - { - "first": 196.93, - "second": 22.792842393406282 - }, - { - "first": 205.41, - "second": 22.952842393406282 - }, - { - "first": 205.41, - "second": 22.952842393406282 - }, - { - "first": 215.41, - "second": 23.15284239340628 - }, - { - "first": 225.41, - "second": 23.302842393406284 - }, - { - "first": 237.51, - "second": 23.42284239340628 - }, - { - "first": 237.505, - "second": 23.42284239340628 - }, - { - "first": 247.505, - "second": 23.502842393406283 - }, - { - "first": 257.505, - "second": 23.632842393406282 - }, - { - "first": 267.505, - "second": 23.76284239340628 - }, - { - "first": 277.505, - "second": 23.90284239340628 - }, - { - "first": 292.145, - "second": 24.092842393406283 - }, - { - "first": 292.143, - "second": 24.092842393406283 - }, - { - "first": 295.53299999999996, - "second": 24.132842393406282 - }, - { - "first": 295.53499999999997, - "second": 24.132842393406282 - }, - { - "first": 305.53499999999997, - "second": 24.432842393406283 - }, - { - "first": 316.635, - "second": 24.392842393406283 - }, - { - "first": 316.63599999999997, - "second": 24.392842393406283 - }, - { - "first": 326.63599999999997, - "second": 24.522842393406282 - }, - { - "first": 336.63599999999997, - "second": 24.792842393406282 - }, - { - "first": 346.63599999999997, - "second": 25.052842393406284 - }, - { - "first": 356.63599999999997, - "second": 25.31284239340628 - }, - { - "first": 366.63599999999997, - "second": 25.602842393406284 - }, - { - "first": 376.63599999999997, - "second": 25.842842393406283 - }, - { - "first": 386.63599999999997, - "second": 25.982842393406283 - }, - { - "first": 396.02599999999995, - "second": 26.01284239340628 - }, - { - "first": 396.03, - "second": 26.01284239340628 - }, - { - "first": 406.03, - "second": 26.042842393406282 - }, - { - "first": 416.03, - "second": 26.092842393406283 - }, - { - "first": 423.69, - "second": 26.15284239340628 - }, - { - "first": 423.686, - "second": 26.15284239340628 - }, - { - "first": 433.686, - "second": 26.252842393406283 - }, - { - "first": 443.686, - "second": 26.31284239340628 - }, - { - "first": 453.686, - "second": 26.362842393406282 - }, - { - "first": 461.936, - "second": 26.40284239340628 - }, - { - "first": 461.93699999999995, - "second": 26.40284239340628 - }, - { - "first": 475.08699999999993, - "second": 26.19284239340628 - } - ] + "details": { + "regular": [ + { + "fareId": "TriMet:R", + "price": { + "currency": { + "symbol": "$", + "currency": "USD", + "defaultFractionDigits": 2, + "currencyCode": "USD" + }, + "cents": 250 + }, + "routes": [ + "TriMet:100" + ] + } + ] + } + }, + "legs": [ + { + "startTime": 1591717210000, + "endTime": 1591717794000, + "departureDelay": 0, + "arrivalDelay": 0, + "realTime": false, + "distance": 837.5169999999998, + "pathway": false, + "mode": "WALK", + "route": "", + "agencyTimeZoneOffset": -25200000, + "interlineWithPreviousLeg": false, + "from": { + "name": "1709 NW Irving St, Portland 97209", + "lon": -122.68865964147231, + "lat": 45.527817334203, + "departure": 1591717210000, + "orig": "1709 NW Irving St, Portland 97209", + "vertexType": "NORMAL" }, - { - "distance": 78.621, - "relativeDirection": "RIGHT", - "streetName": "NW Couch St", - "absoluteDirection": "WEST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6882412, - "lat": 45.5235698, - "elevation": [ - { - "first": 0.0, - "second": 26.19284239340628 - }, - { - "first": 10.0, - "second": 26.352842393406284 - }, - { - "first": 20.0, - "second": 26.572842393406283 - }, - { - "first": 30.0, - "second": 26.802842393406284 - }, - { - "first": 40.0, - "second": 27.15284239340628 - }, - { - "first": 50.0, - "second": 27.322842393406283 - }, - { - "first": 60.0, - "second": 27.55284239340628 - }, - { - "first": 70.0, - "second": 27.80284239340628 - }, - { - "first": 78.62, - "second": 28.01284239340628 - } - ] + "to": { + "name": "Providence Park MAX Station", + "stopId": "TriMet:9758", + "stopCode": "9758", + "lon": -122.689886, + "lat": 45.521321, + "arrival": 1591717794000, + "departure": 1591717795000, + "zoneId": "R", + "stopIndex": 18, + "stopSequence": 19, + "vertexType": "TRANSIT", + "boardAlightType": "DEFAULT" }, - { - "distance": 66.44500000000001, - "relativeDirection": "LEFT", - "streetName": "NW 18th Ave", - "absoluteDirection": "SOUTH", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6892498, - "lat": 45.5235462, - "elevation": [ - { - "first": 0.0, - "second": 28.01284239340628 - }, - { - "first": 10.0, - "second": 27.37284239340628 - }, - { - "first": 20.0, - "second": 26.87284239340628 - }, - { - "first": 25.41, - "second": 26.65284239340628 - }, - { - "first": 25.411, - "second": 26.65284239340628 - }, - { - "first": 27.351000000000003, - "second": 26.56284239340628 - }, - { - "first": 27.346, - "second": 26.56284239340628 - }, - { - "first": 31.706, - "second": 26.37284239340628 - }, - { - "first": 31.705, - "second": 26.37284239340628 - }, - { - "first": 38.315, - "second": 26.132842393406282 - }, - { - "first": 38.31, - "second": 26.132842393406282 - }, - { - "first": 48.31, - "second": 25.90284239340628 - }, - { - "first": 55.33, - "second": 25.822842393406283 - }, - { - "first": 55.328, - "second": 25.822842393406283 - }, - { - "first": 66.44800000000001, - "second": 25.962842393406284 - } - ] + "legGeometry": { + "points": "}c{tGdsykVAu@x@AJ?v@AP?nCE\\?r@Ax@C`BADAd@?lCEp@AdAAV?JA`@?F?jACClAF@B?l@XNFpAj@f@TJDB@B@LFx@\\JDHBH@HBCPFBDBQ~@", + "length": 41 }, - { - "distance": 161.21300000000002, - "relativeDirection": "SLIGHTLY_RIGHT", - "streetName": "SW 18th Ave", - "absoluteDirection": "SOUTHWEST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6893346, - "lat": 45.5229721, - "elevation": [ - { - "first": 0.0, - "second": 25.962842393406284 - }, - { - "first": 10.0, - "second": 26.08284239340628 - }, - { - "first": 20.0, - "second": 26.022842393406282 - }, - { - "first": 32.85, - "second": 25.642842393406283 - }, - { - "first": 32.852, - "second": 25.642842393406283 - }, - { - "first": 42.852, - "second": 25.852842393406284 - }, - { - "first": 52.852, - "second": 26.142842393406283 - }, - { - "first": 62.852, - "second": 26.572842393406283 - }, - { - "first": 72.852, - "second": 27.022842393406282 - }, - { - "first": 82.852, - "second": 27.612842393406282 - }, - { - "first": 93.812, - "second": 28.092842393406283 - }, - { - "first": 93.811, - "second": 28.092842393406283 - }, - { - "first": 103.811, - "second": 28.47284239340628 - }, - { - "first": 114.351, - "second": 28.842842393406283 - }, - { - "first": 114.346, - "second": 28.842842393406283 - }, - { - "first": 125.05600000000001, - "second": 28.962842393406284 - }, - { - "first": 134.205, - "second": 28.962842393406284 - }, - { - "first": 141.47500000000002, - "second": 28.56284239340628 - }, - { - "first": 141.47, - "second": 28.56284239340628 - }, - { - "first": 143.36, - "second": 28.482842393406283 - } - ] - } - ] - }, - { - "startTime": 1591718400000, - "endTime": 1591718760000, - "departureDelay": 0, - "arrivalDelay": 0, - "realTime": false, - "distance": 1286.9349137891197, - "pathway": false, - "mode": "BUS", - "route": "15", - "agencyName": "TriMet", - "agencyUrl": "https://trimet.org/", - "agencyTimeZoneOffset": -25200000, - "routeType": 3, - "routeId": "TriMet:15", - "interlineWithPreviousLeg": false, - "tripBlockId": "1505", - "headsign": "Gateway TC", - "agencyId": "TRIMET", - "tripId": "TriMet:9932776", - "serviceDate": "20200609", - "from": { - "name": "SW 18th & Morrison", - "stopId": "TriMet:6911", - "stopCode": "6911", - "lon": -122.690453, - "lat": 45.52188, - "arrival": 1591718399000, - "departure": 1591718400000, - "zoneId": "B", - "stopIndex": 21, - "stopSequence": 22, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - "to": { - "name": "SW Salmon & 5th", - "stopId": "TriMet:5020", - "stopCode": "5020", - "lon": -122.678854, - "lat": 45.516794, - "arrival": 1591718760000, - "departure": 1591718761000, - "zoneId": "B", - "stopIndex": 27, - "stopSequence": 28, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - "legGeometry": { - "points": "w~ytGx}ykVRHh@ZXNhBv@JFJFRJFDFFFH@@`@Xp@h@RNBMBQBMBO?I@I?O?u@?e@?A?S@WBMDY\\sBJe@Fc@@Ep@sDBOBSd@mCBKDSDWHe@XcBLs@n@mDBUFYDUFa@BKDSJm@He@@GJk@Hg@PeADUF[@IBMNy@He@RkAToAFYr@eEDSh@aDBK?ADWh@yC", - "length": 72 + "rentedBike": false, + "rentedCar": false, + "rentedVehicle": false, + "hailedCar": false, + "flexDrtAdvanceBookMin": 0.0, + "duration": 584.0, + "transitLeg": false, + "intermediateStops": [], + "steps": [ + { + "distance": 20.794, + "relativeDirection": "DEPART", + "streetName": "NW Irving St", + "absoluteDirection": "EAST", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.68866035123024, + "lat": 45.527836027296765, + "elevation": [ + { + "first": 0.0, + "second": 20.682842393406283 + }, + { + "first": 10.0, + "second": 20.452842393406282 + }, + { + "first": 20.0, + "second": 20.24284239340628 + }, + { + "first": 20.79, + "second": 20.22284239340628 + } + ] + }, + { + "distance": 547.395, + "relativeDirection": "RIGHT", + "streetName": "NW 17th Ave", + "absoluteDirection": "SOUTH", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.6883935, + "lat": 45.527841, + "elevation": [ + { + "first": 0.0, + "second": 18.682842393406283 + }, + { + "first": 10.0, + "second": 18.802842393406284 + }, + { + "first": 20.0, + "second": 19.01284239340628 + }, + { + "first": 31.29, + "second": 19.24284239340628 + }, + { + "first": 31.294, + "second": 19.24284239340628 + }, + { + "first": 39.024, + "second": 19.40284239340628 + }, + { + "first": 39.023, + "second": 19.40284239340628 + }, + { + "first": 49.023, + "second": 19.60284239340628 + }, + { + "first": 59.023, + "second": 19.85284239340628 + }, + { + "first": 69.023, + "second": 19.97284239340628 + }, + { + "first": 79.273, + "second": 20.032842393406284 + }, + { + "first": 79.272, + "second": 20.032842393406284 + }, + { + "first": 89.272, + "second": 20.26284239340628 + }, + { + "first": 99.272, + "second": 20.572842393406283 + }, + { + "first": 109.272, + "second": 20.872842393406284 + }, + { + "first": 119.272, + "second": 21.112842393406282 + }, + { + "first": 129.272, + "second": 21.412842393406283 + }, + { + "first": 139.272, + "second": 21.69284239340628 + }, + { + "first": 149.272, + "second": 22.01284239340628 + }, + { + "first": 159.322, + "second": 22.002842393406283 + }, + { + "first": 159.323, + "second": 22.002842393406283 + }, + { + "first": 169.323, + "second": 22.17284239340628 + }, + { + "first": 176.933, + "second": 22.352842393406284 + }, + { + "first": 176.93, + "second": 22.352842393406284 + }, + { + "first": 186.93, + "second": 22.58284239340628 + }, + { + "first": 196.93, + "second": 22.792842393406282 + }, + { + "first": 205.41, + "second": 22.952842393406282 + }, + { + "first": 205.41, + "second": 22.952842393406282 + }, + { + "first": 215.41, + "second": 23.15284239340628 + }, + { + "first": 225.41, + "second": 23.302842393406284 + }, + { + "first": 237.51, + "second": 23.42284239340628 + }, + { + "first": 237.505, + "second": 23.42284239340628 + }, + { + "first": 247.505, + "second": 23.502842393406283 + }, + { + "first": 257.505, + "second": 23.632842393406282 + }, + { + "first": 267.505, + "second": 23.76284239340628 + }, + { + "first": 277.505, + "second": 23.90284239340628 + }, + { + "first": 292.145, + "second": 24.092842393406283 + }, + { + "first": 292.143, + "second": 24.092842393406283 + }, + { + "first": 295.53299999999996, + "second": 24.132842393406282 + }, + { + "first": 295.53499999999997, + "second": 24.132842393406282 + }, + { + "first": 305.53499999999997, + "second": 24.432842393406283 + }, + { + "first": 316.635, + "second": 24.392842393406283 + }, + { + "first": 316.63599999999997, + "second": 24.392842393406283 + }, + { + "first": 326.63599999999997, + "second": 24.522842393406282 + }, + { + "first": 336.63599999999997, + "second": 24.792842393406282 + }, + { + "first": 346.63599999999997, + "second": 25.052842393406284 + }, + { + "first": 356.63599999999997, + "second": 25.31284239340628 + }, + { + "first": 366.63599999999997, + "second": 25.602842393406284 + }, + { + "first": 376.63599999999997, + "second": 25.842842393406283 + }, + { + "first": 386.63599999999997, + "second": 25.982842393406283 + }, + { + "first": 396.02599999999995, + "second": 26.01284239340628 + }, + { + "first": 396.03, + "second": 26.01284239340628 + }, + { + "first": 406.03, + "second": 26.042842393406282 + }, + { + "first": 416.03, + "second": 26.092842393406283 + }, + { + "first": 423.69, + "second": 26.15284239340628 + }, + { + "first": 423.686, + "second": 26.15284239340628 + }, + { + "first": 433.686, + "second": 26.252842393406283 + }, + { + "first": 443.686, + "second": 26.31284239340628 + }, + { + "first": 453.686, + "second": 26.362842393406282 + }, + { + "first": 461.936, + "second": 26.40284239340628 + }, + { + "first": 461.93699999999995, + "second": 26.40284239340628 + }, + { + "first": 475.08699999999993, + "second": 26.19284239340628 + }, + { + "first": 475.08199999999994, + "second": 26.19284239340628 + }, + { + "first": 485.08199999999994, + "second": 25.732842393406283 + }, + { + "first": 495.08199999999994, + "second": 25.142842393406283 + }, + { + "first": 501.5519999999999, + "second": 24.76284239340628 + }, + { + "first": 501.54999999999995, + "second": 24.76284239340628 + }, + { + "first": 505.17999999999995, + "second": 24.602842393406284 + }, + { + "first": 505.17699999999996, + "second": 24.602842393406284 + }, + { + "first": 515.1769999999999, + "second": 24.12284239340628 + }, + { + "first": 525.1769999999999, + "second": 23.62284239340628 + }, + { + "first": 535.1769999999999, + "second": 23.292842393406282 + }, + { + "first": 547.3969999999999, + "second": 23.51284239340628 + } + ] + }, + { + "distance": 30.847, + "relativeDirection": "RIGHT", + "streetName": "W Burnside St", + "absoluteDirection": "WEST", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.688213, + "lat": 45.5229198, + "elevation": [ + { + "first": 0.0, + "second": 23.51284239340628 + }, + { + "first": 10.0, + "second": 23.76284239340628 + }, + { + "first": 20.0, + "second": 24.052842393406284 + }, + { + "first": 30.85, + "second": 24.302842393406284 + } + ] + }, + { + "distance": 195.61100000000002, + "relativeDirection": "LEFT", + "streetName": "SW 17th Ave", + "absoluteDirection": "SOUTH", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.6886081, + "lat": 45.522938, + "elevation": [ + { + "first": 0.0, + "second": 24.302842393406284 + }, + { + "first": 10.0, + "second": 24.532842393406284 + }, + { + "first": 20.0, + "second": 25.182842393406283 + }, + { + "first": 30.0, + "second": 25.802842393406284 + }, + { + "first": 44.36, + "second": 26.032842393406284 + }, + { + "first": 44.347, + "second": 26.032842393406284 + }, + { + "first": 54.347, + "second": 26.452842393406282 + }, + { + "first": 64.34700000000001, + "second": 27.282842393406284 + }, + { + "first": 74.34700000000001, + "second": 27.952842393406282 + }, + { + "first": 84.34700000000001, + "second": 28.132842393406282 + }, + { + "first": 92.137, + "second": 28.292842393406282 + }, + { + "first": 92.13900000000001, + "second": 28.292842393406282 + }, + { + "first": 102.13900000000001, + "second": 28.80284239340628 + }, + { + "first": 112.13900000000001, + "second": 29.30284239340628 + }, + { + "first": 123.74900000000001, + "second": 29.822842393406283 + }, + { + "first": 123.75200000000001, + "second": 29.822842393406283 + }, + { + "first": 136.752, + "second": 30.352842393406284 + } + ] + }, + { + "distance": 7.173, + "relativeDirection": "RIGHT", + "streetName": "path", + "absoluteDirection": "WEST", + "stayOn": false, + "area": false, + "bogusName": true, + "lon": -122.689436, + "lat": 45.521281, + "elevation": [ + { + "first": 0.0, + "second": 32.14284239340628 + }, + { + "first": 7.17, + "second": 32.092842393406286 + } + ] + }, + { + "distance": 8.612, + "relativeDirection": "LEFT", + "streetName": "path", + "absoluteDirection": "SOUTH", + "stayOn": true, + "area": false, + "bogusName": true, + "lon": -122.6895213, + "lat": 45.5213053, + "elevation": [ + { + "first": 0.0, + "second": 32.092842393406286 + }, + { + "first": 8.61, + "second": 32.202842393406286 + } + ] + }, + { + "distance": 27.085, + "relativeDirection": "RIGHT", + "streetName": "Providence Park (path)", + "absoluteDirection": "WEST", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.6895617, + "lat": 45.5212332, + "elevation": [] + } + ] }, - "interStopGeometry": [ - { - "points": "w~ytGx}ykVRHh@ZXNhBv@JFJFRJFDFFFH@@`@Xp@h@RNBMBQBMBO?I@I?O?u@?e@?A?S@WBMDY\\sBJe@Fc@", - "length": 32 - }, - { - "points": "wpytG~vykV@Ep@sDBOBSd@mC", - "length": 6 + { + "startTime": 1591717795000, + "endTime": 1591718035000, + "departureDelay": 0, + "arrivalDelay": 0, + "realTime": false, + "distance": 893.8869820964361, + "pathway": false, + "mode": "TRAM", + "route": "MAX Blue Line", + "agencyName": "TriMet", + "agencyUrl": "https://trimet.org/", + "agencyTimeZoneOffset": -25200000, + "routeColor": "084C8D", + "routeType": 0, + "routeId": "TriMet:100", + "routeTextColor": "FFFFFF", + "interlineWithPreviousLeg": false, + "tripBlockId": "9004", + "headsign": "Gresham", + "agencyId": "TRIMET", + "tripId": "TriMet:9942889", + "serviceDate": "20200609", + "from": { + "name": "Providence Park MAX Station", + "stopId": "TriMet:9758", + "stopCode": "9758", + "lon": -122.689886, + "lat": 45.521321, + "arrival": 1591717794000, + "departure": 1591717795000, + "zoneId": "R", + "stopIndex": 18, + "stopSequence": 19, + "vertexType": "TRANSIT", + "boardAlightType": "DEFAULT" }, - { - "points": "umytGrkykVBKDSDWHe@XcBLs@n@mDBUFYDUFa@BK", - "length": 13 + "to": { + "name": "Pioneer Square South MAX Station", + "stopId": "TriMet:8334", + "stopCode": "8334", + "lon": -122.679145, + "lat": 45.518496, + "arrival": 1591718035000, + "departure": 1591718036000, + "zoneId": "R", + "stopIndex": 20, + "stopSequence": 21, + "vertexType": "TRANSIT", + "boardAlightType": "DEFAULT" }, - { - "points": "eiytGzzxkVDSJm@He@@GJk@Hg@PeA", - "length": 8 + "legGeometry": { + "points": "m{ytGtzykV@AN}@BQ@EJm@Lk@PcAF[l@mDDQBMn@uDDUj@gDDSl@mDFWDW\\oBHe@@KBI@I@EF]f@uCBQBI@KTqAP}@@IDS@GTqABSBKn@sDBQf@qC", + "length": 41 }, - { - "points": "_gytGprxkVDUF[@IBMNy@He@RkAToA", - "length": 9 + "interStopGeometry": [ + { + "points": "m{ytGtzykV@AN}@BQ@EJm@Lk@PcAF[l@mDDQBMn@uDDUj@gDDSl@mDFWDW\\oBHe@@KBI@I@EF]f@uCBQBI@KTqAP}@", + "length": 32 + }, + { + "points": "qmytGfgxkV@IDS@GTqABSBKn@sDBQf@qC", + "length": 10 + } + ], + "alerts": [ + { + "effectiveStartDate": 1591959600000, + "alertDescriptionText": "A scarf, bandanna or fabric mask works, as long as it covers your nose and mouth.", + "alertUrl": "http://trimet.org/health" + }, + { + "effectiveStartDate": 1585876166000, + "alertDescriptionText": "Due to COVID-19 service reductions, beginning Sunday, April 5th, on weekdays, trains will run every 15 minutes throughout most of the day. On weekends will run on Sunday schedule. For arrivals and trip planning, see:", + "alertUrl": "http://trimet.org/reducedservice" + }, + { + "effectiveStartDate": 1572827580000, + "alertDescriptionText": "The east elevators (zoo side) at Washington Park MAX Station are closed for improvements until early May. For access between platform and ground levels, use the west elevators (world forestry side). ", + "alertUrl": "https://news.trimet.org/2019/08/trimet-to-replace-elevators-at-the-deepest-transit-station-in-north-america/" + }, + { + "effectiveStartDate": 1591965804000, + "alertDescriptionText": "MAX Blue and Red lines disrupted due to police activity near Goose Hollow/SW Jefferson. Shuttle buses serving stations between Library/SW 9th and Galleria/SW 10th and Sunset Transit Center. Expect delays.", + "alertUrl": "http://trimet.org/alerts/" + } + ], + "routeLongName": "MAX Blue Line", + "rentedBike": false, + "rentedCar": false, + "rentedVehicle": false, + "hailedCar": false, + "flexDrtAdvanceBookMin": 0.0, + "duration": 240.0, + "transitLeg": true, + "intermediateStops": [ + { + "name": "Library/SW 9th Ave MAX Station", + "stopId": "TriMet:8333", + "stopCode": "8333", + "lon": -122.68162, + "lat": 45.51916, + "arrival": 1591717940000, + "departure": 1591717975000, + "zoneId": "R", + "stopIndex": 19, + "stopSequence": 20, + "vertexType": "TRANSIT", + "boardAlightType": "DEFAULT" + } + ], + "steps": [] + }, + { + "startTime": 1591718036000, + "endTime": 1591718324000, + "departureDelay": 0, + "arrivalDelay": 0, + "realTime": false, + "distance": 418.19, + "pathway": false, + "mode": "WALK", + "route": "", + "agencyTimeZoneOffset": -25200000, + "interlineWithPreviousLeg": false, + "from": { + "name": "Pioneer Square South MAX Station", + "stopId": "TriMet:8334", + "stopCode": "8334", + "lon": -122.679145, + "lat": 45.518496, + "arrival": 1591718035000, + "departure": 1591718036000, + "zoneId": "R", + "stopIndex": 20, + "stopSequence": 21, + "vertexType": "TRANSIT", + "boardAlightType": "DEFAULT" }, - { - "points": "gdytGjhxkVFYr@eEDSh@aDBK?ADWh@yC", - "length": 9 - } - ], - "alerts": [ - { - "effectiveStartDate": 1591959600000, - "alertDescriptionText": "A scarf, bandanna or fabric mask works, as long as it covers your nose and mouth.", - "alertUrl": "http://trimet.org/health" + "to": { + "name": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", + "lon": -122.67681483620306, + "lat": 45.51639151281627, + "arrival": 1591718324000, + "orig": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", + "vertexType": "NORMAL" }, - { - "effectiveStartDate": 1582249544000, - "alertDescriptionText": "No service to westbound NW Thurman & 28th (Stop ID 5837) due to long term stop closure. ", - "alertUrl": "http://trimet.org/alerts/" + "legGeometry": { + "points": "uiytGrwwkVDQBODBDB@G@Kn@iDBO@KLF|Ap@FBNHLF|Ar@FDNFBOh@{CD[FWb@eC", + "length": 23 }, - { - "effectiveStartDate": 1585477800000, - "alertDescriptionText": "Due to COVID-19 service reductions, beginning Sunday, April 5th, this line will be running on Saturday schedule on weekdays. For arrivals and trip planning, see trimet.org.", - "alertUrl": "https://trimet.org/alerts/reducedservice.htm" + "rentedBike": false, + "rentedCar": false, + "rentedVehicle": false, + "hailedCar": false, + "flexDrtAdvanceBookMin": 0.0, + "duration": 288.0, + "transitLeg": false, + "intermediateStops": [], + "steps": [ + { + "distance": 7.715, + "relativeDirection": "DEPART", + "streetName": "Pioneer Courthouse Sq (pedestrian street)", + "absoluteDirection": "EAST", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.67913709381972, + "lat": 45.51851024632791, + "elevation": [] + }, + { + "distance": 6.217, + "relativeDirection": "CONTINUE", + "streetName": "path", + "absoluteDirection": "EAST", + "stayOn": false, + "area": false, + "bogusName": true, + "lon": -122.6790448, + "lat": 45.5184851, + "elevation": [ + { + "first": 0.0, + "second": 15.172842393406283 + }, + { + "first": 6.22, + "second": 15.002842393406283 + } + ] + }, + { + "distance": 7.252, + "relativeDirection": "RIGHT", + "streetName": "SW 6th Ave", + "absoluteDirection": "SOUTH", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.6789697, + "lat": 45.5184662, + "elevation": [ + { + "first": 0.0, + "second": 15.002842393406283 + }, + { + "first": 7.25, + "second": 15.112842393406282 + } + ] + }, + { + "distance": 90.85300000000001, + "relativeDirection": "LEFT", + "streetName": "SW Yamhill St", + "absoluteDirection": "EAST", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.6790039, + "lat": 45.5184056, + "elevation": [ + { + "first": 0.0, + "second": 15.112842393406282 + }, + { + "first": 8.41, + "second": 15.092842393406283 + }, + { + "first": 8.407, + "second": 15.092842393406283 + }, + { + "first": 18.407, + "second": 14.942842393406282 + }, + { + "first": 28.407, + "second": 14.672842393406283 + }, + { + "first": 38.407, + "second": 14.392842393406283 + }, + { + "first": 48.407, + "second": 14.192842393406282 + }, + { + "first": 58.407, + "second": 13.772842393406282 + }, + { + "first": 68.407, + "second": 13.452842393406282 + }, + { + "first": 79.467, + "second": 13.182842393406283 + }, + { + "first": 79.465, + "second": 13.182842393406283 + }, + { + "first": 90.855, + "second": 12.942842393406282 + } + ] + }, + { + "distance": 156.99599999999998, + "relativeDirection": "RIGHT", + "streetName": "SW 5th Ave", + "absoluteDirection": "SOUTH", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.677916, + "lat": 45.5181117, + "elevation": [] + }, + { + "distance": 149.157, + "relativeDirection": "LEFT", + "streetName": "SW Salmon St", + "absoluteDirection": "EAST", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.6786441, + "lat": 45.5167953, + "elevation": [ + { + "first": 0.0, + "second": 17.552842393406284 + }, + { + "first": 10.0, + "second": 17.292842393406282 + }, + { + "first": 20.0, + "second": 16.962842393406284 + }, + { + "first": 30.0, + "second": 16.642842393406283 + }, + { + "first": 40.0, + "second": 16.162842393406283 + }, + { + "first": 50.0, + "second": 15.712842393406282 + }, + { + "first": 60.0, + "second": 15.122842393406282 + }, + { + "first": 70.0, + "second": 15.062842393406282 + }, + { + "first": 83.14, + "second": 14.982842393406283 + }, + { + "first": 83.144, + "second": 14.982842393406283 + }, + { + "first": 93.01400000000001, + "second": 14.762842393406283 + }, + { + "first": 93.01700000000001, + "second": 14.762842393406283 + }, + { + "first": 103.01700000000001, + "second": 14.502842393406283 + }, + { + "first": 113.01700000000001, + "second": 14.272842393406282 + }, + { + "first": 123.01700000000001, + "second": 13.872842393406282 + }, + { + "first": 133.017, + "second": 13.632842393406282 + }, + { + "first": 143.017, + "second": 13.412842393406283 + }, + { + "first": 149.157, + "second": 13.152842393406281 + } + ] + } + ] + } + ], + "tooSloped": false + }, + { + "duration": 1242, + "startTime": 1591717148000, + "endTime": 1591718390000, + "walkTime": 820, + "transitTime": 420, + "waitingTime": 2, + "walkDistance": 1297.4104029783646, + "walkLimitExceeded": false, + "elevationLost": 28.980000000000004, + "elevationGained": 1.1200000000000003, + "transfers": 0, + "fare": { + "fare": { + "regular": { + "currency": { + "symbol": "$", + "currency": "USD", + "defaultFractionDigits": 2, + "currencyCode": "USD" + }, + "cents": 200 } - ], - "routeShortName": "15", - "routeLongName": "Belmont/NW 23rd", - "rentedBike": false, - "rentedCar": false, - "rentedVehicle": false, - "hailedCar": false, - "flexDrtAdvanceBookMin": 0.0, - "duration": 360.0, - "transitLeg": true, - "intermediateStops": [ - { - "name": "SW Salmon & 16th", - "stopId": "TriMet:5016", - "stopCode": "5016", - "lon": -122.689312, - "lat": 45.519578, - "arrival": 1591718516000, - "departure": 1591718516000, + }, + "details": { + "regular": [ + { + "fareId": "TriMet:SC", + "price": { + "currency": { + "symbol": "$", + "currency": "USD", + "defaultFractionDigits": 2, + "currencyCode": "USD" + }, + "cents": 200 + }, + "routes": [ + "TriMet:195" + ] + } + ] + } + }, + "legs": [ + { + "startTime": 1591717148000, + "endTime": 1591717499000, + "departureDelay": 0, + "arrivalDelay": 0, + "realTime": false, + "distance": 578.6750000000001, + "pathway": false, + "mode": "WALK", + "route": "", + "agencyTimeZoneOffset": -25200000, + "interlineWithPreviousLeg": false, + "from": { + "name": "1709 NW Irving St, Portland 97209", + "lon": -122.68865964147231, + "lat": 45.527817334203, + "departure": 1591717148000, + "orig": "1709 NW Irving St, Portland 97209", + "vertexType": "NORMAL" + }, + "to": { + "name": "NW 11th & Johnson", + "stopId": "TriMet:10753", + "stopCode": "10753", + "lon": -122.682374, + "lat": 45.528742, + "arrival": 1591717499000, + "departure": 1591717500000, "zoneId": "B", - "stopIndex": 22, - "stopSequence": 23, + "stopIndex": 1, + "stopSequence": 2, "vertexType": "TRANSIT", "boardAlightType": "DEFAULT" }, - { - "name": "SW Salmon & 14th", - "stopId": "TriMet:5014", - "stopCode": "5014", - "lon": -122.687479, - "lat": 45.519106, - "arrival": 1591718559000, - "departure": 1591718559000, + "legGeometry": { + "points": "}c{tGdsykVAu@qBB[@CiECkE???e@AoCAS?Y?kAAsA?Q?QAeD?QASAeDMA?G", + "length": 21 + }, + "rentedBike": false, + "rentedCar": false, + "rentedVehicle": false, + "hailedCar": false, + "flexDrtAdvanceBookMin": 0.0, + "duration": 351.0, + "transitLeg": false, + "intermediateStops": [], + "steps": [ + { + "distance": 20.794, + "relativeDirection": "DEPART", + "streetName": "NW Irving St", + "absoluteDirection": "EAST", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.68866035123024, + "lat": 45.527836027296765, + "elevation": [ + { + "first": 0.0, + "second": 20.682842393406283 + }, + { + "first": 10.0, + "second": 20.452842393406282 + }, + { + "first": 20.0, + "second": 20.24284239340628 + }, + { + "first": 20.79, + "second": 20.22284239340628 + } + ] + }, + { + "distance": 79.476, + "relativeDirection": "LEFT", + "streetName": "NW 17th Ave", + "absoluteDirection": "NORTH", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.6883935, + "lat": 45.527841, + "elevation": [ + { + "first": 0.0, + "second": 18.682842393406283 + }, + { + "first": 10.0, + "second": 18.612842393406282 + }, + { + "first": 20.0, + "second": 18.47284239340628 + }, + { + "first": 30.0, + "second": 18.272842393406282 + }, + { + "first": 40.0, + "second": 18.08284239340628 + }, + { + "first": 50.0, + "second": 17.90284239340628 + }, + { + "first": 63.93, + "second": 17.632842393406282 + }, + { + "first": 63.928, + "second": 17.632842393406282 + }, + { + "first": 73.928, + "second": 17.412842393406283 + }, + { + "first": 79.478, + "second": 17.362842393406282 + } + ] + }, + { + "distance": 467.456, + "relativeDirection": "RIGHT", + "streetName": "NW Johnson St", + "absoluteDirection": "EAST", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.6884207, + "lat": 45.5285555, + "elevation": [ + { + "first": 0.0, + "second": 17.362842393406282 + }, + { + "first": 10.0, + "second": 17.122842393406284 + }, + { + "first": 20.0, + "second": 17.002842393406283 + }, + { + "first": 30.0, + "second": 16.632842393406282 + }, + { + "first": 40.0, + "second": 16.372842393406284 + }, + { + "first": 50.0, + "second": 16.15284239340628 + }, + { + "first": 60.0, + "second": 15.932842393406283 + }, + { + "first": 70.0, + "second": 15.692842393406282 + }, + { + "first": 78.6, + "second": 15.642842393406283 + }, + { + "first": 78.602, + "second": 15.642842393406283 + }, + { + "first": 88.602, + "second": 15.662842393406283 + }, + { + "first": 98.602, + "second": 15.952842393406282 + }, + { + "first": 108.602, + "second": 16.112842393406282 + }, + { + "first": 118.602, + "second": 16.072842393406283 + }, + { + "first": 128.602, + "second": 15.942842393406282 + }, + { + "first": 138.602, + "second": 15.492842393406281 + }, + { + "first": 148.602, + "second": 14.812842393406282 + }, + { + "first": 157.762, + "second": 14.452842393406282 + }, + { + "first": 157.757, + "second": 14.452842393406282 + }, + { + "first": 167.757, + "second": 14.222842393406282 + }, + { + "first": 177.757, + "second": 14.022842393406282 + }, + { + "first": 187.757, + "second": 13.782842393406282 + }, + { + "first": 197.757, + "second": 13.582842393406281 + }, + { + "first": 207.757, + "second": 13.422842393406283 + }, + { + "first": 217.757, + "second": 13.102842393406283 + }, + { + "first": 227.757, + "second": 12.842842393406283 + }, + { + "first": 236.787, + "second": 12.762842393406283 + }, + { + "first": 236.78, + "second": 12.762842393406283 + }, + { + "first": 246.78, + "second": 12.462842393406284 + }, + { + "first": 256.78, + "second": 12.312842393406282 + }, + { + "first": 266.78, + "second": 12.132842393406282 + }, + { + "first": 276.35, + "second": 11.912842393406283 + }, + { + "first": 276.349, + "second": 11.912842393406283 + }, + { + "first": 286.349, + "second": 11.632842393406282 + }, + { + "first": 296.349, + "second": 11.462842393406282 + }, + { + "first": 306.349, + "second": 11.182842393406283 + }, + { + "first": 315.839, + "second": 10.942842393406282 + }, + { + "first": 315.841, + "second": 10.942842393406282 + }, + { + "first": 325.841, + "second": 10.892842393406282 + }, + { + "first": 335.841, + "second": 10.692842393406282 + }, + { + "first": 345.841, + "second": 10.582842393406283 + }, + { + "first": 355.841, + "second": 10.382842393406282 + }, + { + "first": 365.841, + "second": 10.112842393406282 + }, + { + "first": 375.841, + "second": 9.802842393406282 + }, + { + "first": 385.841, + "second": 9.522842393406282 + }, + { + "first": 395.041, + "second": 9.362842393406282 + }, + { + "first": 395.038, + "second": 9.362842393406282 + }, + { + "first": 405.038, + "second": 9.432842393406283 + }, + { + "first": 415.038, + "second": 9.632842393406282 + }, + { + "first": 425.038, + "second": 9.832842393406283 + }, + { + "first": 435.038, + "second": 9.882842393406282 + }, + { + "first": 445.038, + "second": 9.912842393406283 + }, + { + "first": 455.038, + "second": 9.872842393406282 + }, + { + "first": 467.458, + "second": 9.702842393406282 + } + ] + }, + { + "distance": 7.603, + "relativeDirection": "LEFT", + "streetName": "path", + "absoluteDirection": "NORTH", + "stayOn": false, + "area": false, + "bogusName": true, + "lon": -122.6824215, + "lat": 45.5286553, + "elevation": [ + { + "first": 0.0, + "second": 9.702842393406282 + }, + { + "first": 7.6, + "second": 9.642842393406282 + } + ] + }, + { + "distance": 3.346, + "relativeDirection": "RIGHT", + "streetName": "path", + "absoluteDirection": "EAST", + "stayOn": true, + "area": false, + "bogusName": true, + "lon": -122.6824167, + "lat": 45.5287236, + "elevation": [] + } + ] + }, + { + "startTime": 1591717500000, + "endTime": 1591717920000, + "departureDelay": 0, + "arrivalDelay": 0, + "realTime": false, + "distance": 1103.6887432320718, + "pathway": false, + "mode": "TRAM", + "route": "Portland Streetcar - B Loop", + "agencyName": "Portland Streetcar", + "agencyUrl": "https://portlandstreetcar.org/", + "agencyTimeZoneOffset": -25200000, + "routeColor": "0093B2", + "routeType": 0, + "routeId": "TriMet:195", + "routeTextColor": "FFFFFF", + "interlineWithPreviousLeg": false, + "tripBlockId": "58", + "headsign": "Lloyd via OMSI", + "agencyId": "PSC", + "tripId": "TriMet:9945177", + "serviceDate": "20200609", + "from": { + "name": "NW 11th & Johnson", + "stopId": "TriMet:10753", + "stopCode": "10753", + "lon": -122.682374, + "lat": 45.528742, + "arrival": 1591717499000, + "departure": 1591717500000, "zoneId": "B", - "stopIndex": 23, - "stopSequence": 24, + "stopIndex": 1, + "stopSequence": 2, "vertexType": "TRANSIT", "boardAlightType": "DEFAULT" }, - { - "name": "SW Salmon & 12th", - "stopId": "TriMet:5012", - "stopCode": "5012", - "lon": -122.684797, - "lat": 45.518386, - "arrival": 1591718621000, - "departure": 1591718621000, + "to": { + "name": "SW 11th & Taylor", + "stopId": "TriMet:9633", + "stopCode": "9633", + "lon": -122.683873, + "lat": 45.519059, + "arrival": 1591717920000, + "departure": 1591717921000, "zoneId": "B", - "stopIndex": 24, - "stopSequence": 25, + "stopIndex": 5, + "stopSequence": 6, "vertexType": "TRANSIT", "boardAlightType": "DEFAULT" }, - { - "name": "SW Salmon & 10th", - "stopId": "TriMet:5009", - "stopCode": "5009", - "lon": -122.683475, - "lat": 45.518026, - "arrival": 1591718652000, - "departure": 1591718652000, + "legGeometry": { + "points": "si{tGrkxkVP?dACtEGZ?~ACB?L?J?`CE~BCLAn@?~ACvBCFAL?J?rBCL?vBCLAF?D?D@B@lBx@LF`Bt@HBLFHDtB~@DB@?DBFB`Br@DBFB??FBlBz@", + "length": 43 + }, + "interStopGeometry": [ + { + "points": "si{tGrkxkVP?dACtEGZ?~AC", + "length": 6 + }, + { + "points": "i|ztGbkxkVB?L?J?`CE~BCLAn@?~ACvBC", + "length": 10 + }, + { + "points": "sjztGnjxkVFAL?J?rBCL?vBCLAF?D?D@B@lBx@LF`Bt@", + "length": 15 + }, + { + "points": "syytG~mxkVHBLFHDtB~@DB@?DBFB`Br@DBFB??FBlBz@", + "length": 15 + } + ], + "alerts": [ + { + "effectiveStartDate": 1591959600000, + "alertDescriptionText": "A scarf, bandanna or fabric mask works, as long as it covers your nose and mouth.", + "alertUrl": "http://trimet.org/health" + }, + { + "effectiveStartDate": 1585093573000, + "alertDescriptionText": "Streetcar has reduced regular weekday service to every 20 minutes between about 5:30 a.m. and about 11:30 p.m. For more info:", + "alertUrl": "https://portlandstreetcar.org/news/2020/03/streetcar-service-will-reduce-to-20-minute-headways-beginning-march-24" + } + ], + "routeLongName": "Portland Streetcar - B Loop", + "rentedBike": false, + "rentedCar": false, + "rentedVehicle": false, + "hailedCar": false, + "flexDrtAdvanceBookMin": 0.0, + "duration": 420.0, + "transitLeg": true, + "intermediateStops": [ + { + "name": "NW 11th & Glisan", + "stopId": "TriMet:10754", + "stopCode": "10754", + "lon": -122.682297, + "lat": 45.526605, + "arrival": 1591717560000, + "departure": 1591717560000, + "zoneId": "B", + "stopIndex": 2, + "stopSequence": 3, + "vertexType": "TRANSIT", + "boardAlightType": "DEFAULT" + }, + { + "name": "NW 11th & Couch", + "stopId": "TriMet:10756", + "stopCode": "10756", + "lon": -122.682223, + "lat": 45.523784, + "arrival": 1591717740000, + "departure": 1591717740000, + "zoneId": "B", + "stopIndex": 3, + "stopSequence": 4, + "vertexType": "TRANSIT", + "boardAlightType": "DEFAULT" + }, + { + "name": "SW 11th & Alder", + "stopId": "TriMet:9600", + "stopCode": "9600", + "lon": -122.682819, + "lat": 45.521094, + "arrival": 1591717860000, + "departure": 1591717860000, + "zoneId": "B", + "stopIndex": 4, + "stopSequence": 5, + "vertexType": "TRANSIT", + "boardAlightType": "DEFAULT" + } + ], + "steps": [] + }, + { + "startTime": 1591717921000, + "endTime": 1591718390000, + "departureDelay": 0, + "arrivalDelay": 0, + "realTime": false, + "distance": 718.4660000000001, + "pathway": false, + "mode": "WALK", + "route": "", + "agencyTimeZoneOffset": -25200000, + "interlineWithPreviousLeg": false, + "from": { + "name": "SW 11th & Taylor", + "stopId": "TriMet:9633", + "stopCode": "9633", + "lon": -122.683873, + "lat": 45.519059, + "arrival": 1591717920000, + "departure": 1591717921000, "zoneId": "B", - "stopIndex": 25, - "stopSequence": 26, + "stopIndex": 5, + "stopSequence": 6, "vertexType": "TRANSIT", "boardAlightType": "DEFAULT" }, - { - "name": "SW Salmon & Park", - "stopId": "TriMet:5007", - "stopCode": "5007", - "lon": -122.681844, - "lat": 45.517596, - "arrival": 1591718690000, - "departure": 1591718690000, + "to": { + "name": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", + "lon": -122.67681483620306, + "lat": 45.51639151281627, + "arrival": 1591718390000, + "orig": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", + "vertexType": "NORMAL" + }, + "legGeometry": { + "points": "cmytGfuxkVAHD@JFBQ@GDSh@_D??FU@IBMX_BRiA@K?AVqA@KBOFY`@aCFYBSh@}CBO@CDU^wBHc@F_@@CLF|Ar@FDNFBOh@{CD[FWb@eC", + "length": 40 + }, + "rentedBike": false, + "rentedCar": false, + "rentedVehicle": false, + "hailedCar": false, + "flexDrtAdvanceBookMin": 0.0, + "duration": 469.0, + "transitLeg": false, + "intermediateStops": [], + "steps": [ + { + "distance": 14.561, + "relativeDirection": "DEPART", + "streetName": "path", + "absoluteDirection": "WEST", + "stayOn": false, + "area": false, + "bogusName": true, + "lon": -122.6838786, + "lat": 45.5190659, + "elevation": [ + { + "first": 0.0, + "second": 29.782842393406284 + }, + { + "first": 3.48, + "second": 29.83284239340628 + }, + { + "first": 7.5809999999999995, + "second": 29.83284239340628 + }, + { + "first": 11.690999999999999, + "second": 29.862842393406282 + } + ] + }, + { + "distance": 475.42999999999995, + "relativeDirection": "LEFT", + "streetName": "SW Taylor St", + "absoluteDirection": "EAST", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.6839744, + "lat": 45.5189848, + "elevation": [ + { + "first": 0.0, + "second": 29.87284239340628 + }, + { + "first": 10.39, + "second": 29.87284239340628 + }, + { + "first": 10.386, + "second": 29.87284239340628 + }, + { + "first": 18.735999999999997, + "second": 29.662842393406283 + }, + { + "first": 18.737000000000002, + "second": 29.662842393406283 + }, + { + "first": 28.737000000000002, + "second": 29.252842393406283 + }, + { + "first": 38.737, + "second": 28.97284239340628 + }, + { + "first": 48.737, + "second": 28.632842393406282 + }, + { + "first": 58.737, + "second": 28.05284239340628 + }, + { + "first": 68.737, + "second": 27.62284239340628 + }, + { + "first": 78.737, + "second": 27.202842393406282 + }, + { + "first": 85.84700000000001, + "second": 26.952842393406282 + }, + { + "first": 85.84299999999999, + "second": 26.952842393406282 + }, + { + "first": 95.353, + "second": 26.932842393406283 + }, + { + "first": 95.35099999999998, + "second": 26.932842393406283 + }, + { + "first": 105.13099999999999, + "second": 26.65284239340628 + }, + { + "first": 105.12999999999998, + "second": 26.65284239340628 + }, + { + "first": 115.12999999999998, + "second": 26.22284239340628 + }, + { + "first": 125.12999999999998, + "second": 25.87284239340628 + }, + { + "first": 135.13, + "second": 25.642842393406283 + }, + { + "first": 145.48999999999998, + "second": 25.15284239340628 + }, + { + "first": 145.486, + "second": 25.15284239340628 + }, + { + "first": 155.486, + "second": 24.90284239340628 + }, + { + "first": 165.486, + "second": 24.602842393406284 + }, + { + "first": 176.05599999999998, + "second": 24.342842393406283 + }, + { + "first": 176.051, + "second": 24.342842393406283 + }, + { + "first": 181.18099999999998, + "second": 24.302842393406284 + }, + { + "first": 181.18099999999998, + "second": 24.302842393406284 + }, + { + "first": 191.18099999999998, + "second": 24.08284239340628 + }, + { + "first": 201.18099999999998, + "second": 23.752842393406283 + }, + { + "first": 216.081, + "second": 23.202842393406282 + }, + { + "first": 216.077, + "second": 23.202842393406282 + }, + { + "first": 221.187, + "second": 23.08284239340628 + }, + { + "first": 221.182, + "second": 23.08284239340628 + }, + { + "first": 231.182, + "second": 22.642842393406283 + }, + { + "first": 238.742, + "second": 22.17284239340628 + }, + { + "first": 238.742, + "second": 22.17284239340628 + }, + { + "first": 248.742, + "second": 21.65284239340628 + }, + { + "first": 258.74199999999996, + "second": 21.26284239340628 + }, + { + "first": 268.74199999999996, + "second": 20.682842393406283 + }, + { + "first": 278.74199999999996, + "second": 20.272842393406282 + }, + { + "first": 288.74199999999996, + "second": 19.74284239340628 + }, + { + "first": 303.502, + "second": 19.622842393406284 + }, + { + "first": 303.505, + "second": 19.622842393406284 + }, + { + "first": 313.505, + "second": 19.342842393406283 + }, + { + "first": 323.505, + "second": 18.962842393406284 + }, + { + "first": 333.505, + "second": 18.802842393406284 + }, + { + "first": 343.505, + "second": 18.532842393406284 + }, + { + "first": 353.505, + "second": 18.252842393406283 + }, + { + "first": 363.505, + "second": 18.002842393406283 + }, + { + "first": 373.505, + "second": 17.642842393406283 + }, + { + "first": 384.315, + "second": 17.522842393406282 + }, + { + "first": 384.311, + "second": 17.522842393406282 + }, + { + "first": 394.311, + "second": 17.532842393406284 + }, + { + "first": 404.311, + "second": 17.292842393406282 + }, + { + "first": 414.311, + "second": 17.112842393406282 + }, + { + "first": 424.311, + "second": 16.90284239340628 + }, + { + "first": 434.311, + "second": 16.56284239340628 + }, + { + "first": 444.311, + "second": 16.392842393406283 + }, + { + "first": 454.311, + "second": 16.162842393406283 + }, + { + "first": 464.311, + "second": 15.902842393406281 + }, + { + "first": 475.431, + "second": 15.822842393406283 + } + ] + }, + { + "distance": 79.318, + "relativeDirection": "RIGHT", + "streetName": "SW 5th Ave", + "absoluteDirection": "SOUTH", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.6782737, + "lat": 45.5174597, + "elevation": [] + }, + { + "distance": 149.157, + "relativeDirection": "LEFT", + "streetName": "SW Salmon St", + "absoluteDirection": "EAST", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.6786441, + "lat": 45.5167953, + "elevation": [ + { + "first": 0.0, + "second": 17.552842393406284 + }, + { + "first": 10.0, + "second": 17.292842393406282 + }, + { + "first": 20.0, + "second": 16.962842393406284 + }, + { + "first": 30.0, + "second": 16.642842393406283 + }, + { + "first": 40.0, + "second": 16.162842393406283 + }, + { + "first": 50.0, + "second": 15.712842393406282 + }, + { + "first": 60.0, + "second": 15.122842393406282 + }, + { + "first": 70.0, + "second": 15.062842393406282 + }, + { + "first": 83.14, + "second": 14.982842393406283 + }, + { + "first": 83.144, + "second": 14.982842393406283 + }, + { + "first": 93.01400000000001, + "second": 14.762842393406283 + }, + { + "first": 93.01700000000001, + "second": 14.762842393406283 + }, + { + "first": 103.01700000000001, + "second": 14.502842393406283 + }, + { + "first": 113.01700000000001, + "second": 14.272842393406282 + }, + { + "first": 123.01700000000001, + "second": 13.872842393406282 + }, + { + "first": 133.017, + "second": 13.632842393406282 + }, + { + "first": 143.017, + "second": 13.412842393406283 + }, + { + "first": 149.157, + "second": 13.152842393406281 + } + ] + } + ] + } + ], + "tooSloped": false + }, + { + "duration": 1036, + "startTime": 1591717825000, + "endTime": 1591718861000, + "walkTime": 674, + "transitTime": 360, + "waitingTime": 2, + "walkDistance": 966.7338656666136, + "walkLimitExceeded": false, + "elevationLost": 8.23, + "elevationGained": 13.169999999999996, + "transfers": 0, + "fare": { + "fare": { + "regular": { + "currency": { + "symbol": "$", + "currency": "USD", + "defaultFractionDigits": 2, + "currencyCode": "USD" + }, + "cents": 250 + } + }, + "details": { + "regular": [ + { + "fareId": "TriMet:B", + "price": { + "currency": { + "symbol": "$", + "currency": "USD", + "defaultFractionDigits": 2, + "currencyCode": "USD" + }, + "cents": 250 + }, + "routes": [ + "TriMet:15" + ] + } + ] + } + }, + "legs": [ + { + "startTime": 1591717825000, + "endTime": 1591718399000, + "departureDelay": 0, + "arrivalDelay": 0, + "realTime": false, + "distance": 802.1549999999999, + "pathway": false, + "mode": "WALK", + "route": "", + "agencyTimeZoneOffset": -25200000, + "interlineWithPreviousLeg": false, + "from": { + "name": "1709 NW Irving St, Portland 97209", + "lon": -122.68865964147231, + "lat": 45.527817334203, + "departure": 1591717825000, + "orig": "1709 NW Irving St, Portland 97209", + "vertexType": "NORMAL" + }, + "to": { + "name": "SW 18th & Morrison", + "stopId": "TriMet:6911", + "stopCode": "6911", + "lon": -122.690453, + "lat": 45.52188, + "arrival": 1591718399000, + "departure": 1591718400000, "zoneId": "B", - "stopIndex": 26, - "stopSequence": 27, + "stopIndex": 21, + "stopSequence": 22, "vertexType": "TRANSIT", "boardAlightType": "DEFAULT" - } - ], - "steps": [] - }, - { - "startTime": 1591718761000, - "endTime": 1591718861000, - "departureDelay": 0, - "arrivalDelay": 0, - "realTime": false, - "distance": 164.377, - "pathway": false, - "mode": "WALK", - "route": "", - "agencyTimeZoneOffset": -25200000, - "interlineWithPreviousLeg": false, - "from": { - "name": "SW Salmon & 5th", - "stopId": "TriMet:5020", - "stopCode": "5020", - "lon": -122.678854, - "lat": 45.516794, - "arrival": 1591718760000, - "departure": 1591718761000, - "zoneId": "B", - "stopIndex": 27, - "stopSequence": 28, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - "to": { - "name": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", - "lon": -122.67681483620306, - "lat": 45.51639151281627, - "arrival": 1591718861000, - "orig": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", - "vertexType": "NORMAL" + }, + "legGeometry": { + "points": "}c{tGdsykVAu@x@AJ?v@AP?nCE\\?r@Ax@C`BADAd@?lCEp@AdAAV??NBvDl@?@?F?J?J?H?H?B@BB@DBDBFBHLZBDHLHLHHFDHFj@XHPTHJDLRCP?BB?B@B@`@N", + "length": 50 + }, + "rentedBike": false, + "rentedCar": false, + "rentedVehicle": false, + "hailedCar": false, + "flexDrtAdvanceBookMin": 0.0, + "duration": 574.0, + "transitLeg": false, + "intermediateStops": [], + "steps": [ + { + "distance": 20.794, + "relativeDirection": "DEPART", + "streetName": "NW Irving St", + "absoluteDirection": "EAST", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.68866035123024, + "lat": 45.527836027296765, + "elevation": [ + { + "first": 0.0, + "second": 20.682842393406283 + }, + { + "first": 10.0, + "second": 20.452842393406282 + }, + { + "first": 20.0, + "second": 20.24284239340628 + }, + { + "first": 20.79, + "second": 20.22284239340628 + } + ] + }, + { + "distance": 475.08199999999994, + "relativeDirection": "RIGHT", + "streetName": "NW 17th Ave", + "absoluteDirection": "SOUTH", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.6883935, + "lat": 45.527841, + "elevation": [ + { + "first": 0.0, + "second": 18.682842393406283 + }, + { + "first": 10.0, + "second": 18.802842393406284 + }, + { + "first": 20.0, + "second": 19.01284239340628 + }, + { + "first": 31.29, + "second": 19.24284239340628 + }, + { + "first": 31.294, + "second": 19.24284239340628 + }, + { + "first": 39.024, + "second": 19.40284239340628 + }, + { + "first": 39.023, + "second": 19.40284239340628 + }, + { + "first": 49.023, + "second": 19.60284239340628 + }, + { + "first": 59.023, + "second": 19.85284239340628 + }, + { + "first": 69.023, + "second": 19.97284239340628 + }, + { + "first": 79.273, + "second": 20.032842393406284 + }, + { + "first": 79.272, + "second": 20.032842393406284 + }, + { + "first": 89.272, + "second": 20.26284239340628 + }, + { + "first": 99.272, + "second": 20.572842393406283 + }, + { + "first": 109.272, + "second": 20.872842393406284 + }, + { + "first": 119.272, + "second": 21.112842393406282 + }, + { + "first": 129.272, + "second": 21.412842393406283 + }, + { + "first": 139.272, + "second": 21.69284239340628 + }, + { + "first": 149.272, + "second": 22.01284239340628 + }, + { + "first": 159.322, + "second": 22.002842393406283 + }, + { + "first": 159.323, + "second": 22.002842393406283 + }, + { + "first": 169.323, + "second": 22.17284239340628 + }, + { + "first": 176.933, + "second": 22.352842393406284 + }, + { + "first": 176.93, + "second": 22.352842393406284 + }, + { + "first": 186.93, + "second": 22.58284239340628 + }, + { + "first": 196.93, + "second": 22.792842393406282 + }, + { + "first": 205.41, + "second": 22.952842393406282 + }, + { + "first": 205.41, + "second": 22.952842393406282 + }, + { + "first": 215.41, + "second": 23.15284239340628 + }, + { + "first": 225.41, + "second": 23.302842393406284 + }, + { + "first": 237.51, + "second": 23.42284239340628 + }, + { + "first": 237.505, + "second": 23.42284239340628 + }, + { + "first": 247.505, + "second": 23.502842393406283 + }, + { + "first": 257.505, + "second": 23.632842393406282 + }, + { + "first": 267.505, + "second": 23.76284239340628 + }, + { + "first": 277.505, + "second": 23.90284239340628 + }, + { + "first": 292.145, + "second": 24.092842393406283 + }, + { + "first": 292.143, + "second": 24.092842393406283 + }, + { + "first": 295.53299999999996, + "second": 24.132842393406282 + }, + { + "first": 295.53499999999997, + "second": 24.132842393406282 + }, + { + "first": 305.53499999999997, + "second": 24.432842393406283 + }, + { + "first": 316.635, + "second": 24.392842393406283 + }, + { + "first": 316.63599999999997, + "second": 24.392842393406283 + }, + { + "first": 326.63599999999997, + "second": 24.522842393406282 + }, + { + "first": 336.63599999999997, + "second": 24.792842393406282 + }, + { + "first": 346.63599999999997, + "second": 25.052842393406284 + }, + { + "first": 356.63599999999997, + "second": 25.31284239340628 + }, + { + "first": 366.63599999999997, + "second": 25.602842393406284 + }, + { + "first": 376.63599999999997, + "second": 25.842842393406283 + }, + { + "first": 386.63599999999997, + "second": 25.982842393406283 + }, + { + "first": 396.02599999999995, + "second": 26.01284239340628 + }, + { + "first": 396.03, + "second": 26.01284239340628 + }, + { + "first": 406.03, + "second": 26.042842393406282 + }, + { + "first": 416.03, + "second": 26.092842393406283 + }, + { + "first": 423.69, + "second": 26.15284239340628 + }, + { + "first": 423.686, + "second": 26.15284239340628 + }, + { + "first": 433.686, + "second": 26.252842393406283 + }, + { + "first": 443.686, + "second": 26.31284239340628 + }, + { + "first": 453.686, + "second": 26.362842393406282 + }, + { + "first": 461.936, + "second": 26.40284239340628 + }, + { + "first": 461.93699999999995, + "second": 26.40284239340628 + }, + { + "first": 475.08699999999993, + "second": 26.19284239340628 + } + ] + }, + { + "distance": 78.621, + "relativeDirection": "RIGHT", + "streetName": "NW Couch St", + "absoluteDirection": "WEST", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.6882412, + "lat": 45.5235698, + "elevation": [ + { + "first": 0.0, + "second": 26.19284239340628 + }, + { + "first": 10.0, + "second": 26.352842393406284 + }, + { + "first": 20.0, + "second": 26.572842393406283 + }, + { + "first": 30.0, + "second": 26.802842393406284 + }, + { + "first": 40.0, + "second": 27.15284239340628 + }, + { + "first": 50.0, + "second": 27.322842393406283 + }, + { + "first": 60.0, + "second": 27.55284239340628 + }, + { + "first": 70.0, + "second": 27.80284239340628 + }, + { + "first": 78.62, + "second": 28.01284239340628 + } + ] + }, + { + "distance": 66.44500000000001, + "relativeDirection": "LEFT", + "streetName": "NW 18th Ave", + "absoluteDirection": "SOUTH", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.6892498, + "lat": 45.5235462, + "elevation": [ + { + "first": 0.0, + "second": 28.01284239340628 + }, + { + "first": 10.0, + "second": 27.37284239340628 + }, + { + "first": 20.0, + "second": 26.87284239340628 + }, + { + "first": 25.41, + "second": 26.65284239340628 + }, + { + "first": 25.411, + "second": 26.65284239340628 + }, + { + "first": 27.351000000000003, + "second": 26.56284239340628 + }, + { + "first": 27.346, + "second": 26.56284239340628 + }, + { + "first": 31.706, + "second": 26.37284239340628 + }, + { + "first": 31.705, + "second": 26.37284239340628 + }, + { + "first": 38.315, + "second": 26.132842393406282 + }, + { + "first": 38.31, + "second": 26.132842393406282 + }, + { + "first": 48.31, + "second": 25.90284239340628 + }, + { + "first": 55.33, + "second": 25.822842393406283 + }, + { + "first": 55.328, + "second": 25.822842393406283 + }, + { + "first": 66.44800000000001, + "second": 25.962842393406284 + } + ] + }, + { + "distance": 161.21300000000002, + "relativeDirection": "SLIGHTLY_RIGHT", + "streetName": "SW 18th Ave", + "absoluteDirection": "SOUTHWEST", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.6893346, + "lat": 45.5229721, + "elevation": [ + { + "first": 0.0, + "second": 25.962842393406284 + }, + { + "first": 10.0, + "second": 26.08284239340628 + }, + { + "first": 20.0, + "second": 26.022842393406282 + }, + { + "first": 32.85, + "second": 25.642842393406283 + }, + { + "first": 32.852, + "second": 25.642842393406283 + }, + { + "first": 42.852, + "second": 25.852842393406284 + }, + { + "first": 52.852, + "second": 26.142842393406283 + }, + { + "first": 62.852, + "second": 26.572842393406283 + }, + { + "first": 72.852, + "second": 27.022842393406282 + }, + { + "first": 82.852, + "second": 27.612842393406282 + }, + { + "first": 93.812, + "second": 28.092842393406283 + }, + { + "first": 93.811, + "second": 28.092842393406283 + }, + { + "first": 103.811, + "second": 28.47284239340628 + }, + { + "first": 114.351, + "second": 28.842842393406283 + }, + { + "first": 114.346, + "second": 28.842842393406283 + }, + { + "first": 125.05600000000001, + "second": 28.962842393406284 + }, + { + "first": 134.205, + "second": 28.962842393406284 + }, + { + "first": 141.47500000000002, + "second": 28.56284239340628 + }, + { + "first": 141.47, + "second": 28.56284239340628 + }, + { + "first": 143.36, + "second": 28.482842393406283 + } + ] + } + ] }, - "legGeometry": { - "points": "g_ytGtuwkV@CF]?ABOh@{CD[FWb@eC", - "length": 9 + { + "startTime": 1591718400000, + "endTime": 1591718760000, + "departureDelay": 0, + "arrivalDelay": 0, + "realTime": false, + "distance": 1286.9349137891197, + "pathway": false, + "mode": "BUS", + "route": "15", + "agencyName": "TriMet", + "agencyUrl": "https://trimet.org/", + "agencyTimeZoneOffset": -25200000, + "routeType": 3, + "routeId": "TriMet:15", + "interlineWithPreviousLeg": false, + "tripBlockId": "1505", + "headsign": "Gateway TC", + "agencyId": "TRIMET", + "tripId": "TriMet:9932776", + "serviceDate": "20200609", + "from": { + "name": "SW 18th & Morrison", + "stopId": "TriMet:6911", + "stopCode": "6911", + "lon": -122.690453, + "lat": 45.52188, + "arrival": 1591718399000, + "departure": 1591718400000, + "zoneId": "B", + "stopIndex": 21, + "stopSequence": 22, + "vertexType": "TRANSIT", + "boardAlightType": "DEFAULT" + }, + "to": { + "name": "SW Salmon & 5th", + "stopId": "TriMet:5020", + "stopCode": "5020", + "lon": -122.678854, + "lat": 45.516794, + "arrival": 1591718760000, + "departure": 1591718761000, + "zoneId": "B", + "stopIndex": 27, + "stopSequence": 28, + "vertexType": "TRANSIT", + "boardAlightType": "DEFAULT" + }, + "legGeometry": { + "points": "w~ytGx}ykVRHh@ZXNhBv@JFJFRJFDFFFH@@`@Xp@h@RNBMBQBMBO?I@I?O?u@?e@?A?S@WBMDY\\sBJe@Fc@@Ep@sDBOBSd@mCBKDSDWHe@XcBLs@n@mDBUFYDUFa@BKDSJm@He@@GJk@Hg@PeADUF[@IBMNy@He@RkAToAFYr@eEDSh@aDBK?ADWh@yC", + "length": 72 + }, + "interStopGeometry": [ + { + "points": "w~ytGx}ykVRHh@ZXNhBv@JFJFRJFDFFFH@@`@Xp@h@RNBMBQBMBO?I@I?O?u@?e@?A?S@WBMDY\\sBJe@Fc@", + "length": 32 + }, + { + "points": "wpytG~vykV@Ep@sDBOBSd@mC", + "length": 6 + }, + { + "points": "umytGrkykVBKDSDWHe@XcBLs@n@mDBUFYDUFa@BK", + "length": 13 + }, + { + "points": "eiytGzzxkVDSJm@He@@GJk@Hg@PeA", + "length": 8 + }, + { + "points": "_gytGprxkVDUF[@IBMNy@He@RkAToA", + "length": 9 + }, + { + "points": "gdytGjhxkVFYr@eEDSh@aDBK?ADWh@yC", + "length": 9 + } + ], + "alerts": [ + { + "effectiveStartDate": 1591959600000, + "alertDescriptionText": "A scarf, bandanna or fabric mask works, as long as it covers your nose and mouth.", + "alertUrl": "http://trimet.org/health" + }, + { + "effectiveStartDate": 1582249544000, + "alertDescriptionText": "No service to westbound NW Thurman & 28th (Stop ID 5837) due to long term stop closure. ", + "alertUrl": "http://trimet.org/alerts/" + }, + { + "effectiveStartDate": 1585477800000, + "alertDescriptionText": "Due to COVID-19 service reductions, beginning Sunday, April 5th, this line will be running on Saturday schedule on weekdays. For arrivals and trip planning, see trimet.org.", + "alertUrl": "https://trimet.org/alerts/reducedservice.htm" + } + ], + "routeShortName": "15", + "routeLongName": "Belmont/NW 23rd", + "rentedBike": false, + "rentedCar": false, + "rentedVehicle": false, + "hailedCar": false, + "flexDrtAdvanceBookMin": 0.0, + "duration": 360.0, + "transitLeg": true, + "intermediateStops": [ + { + "name": "SW Salmon & 16th", + "stopId": "TriMet:5016", + "stopCode": "5016", + "lon": -122.689312, + "lat": 45.519578, + "arrival": 1591718516000, + "departure": 1591718516000, + "zoneId": "B", + "stopIndex": 22, + "stopSequence": 23, + "vertexType": "TRANSIT", + "boardAlightType": "DEFAULT" + }, + { + "name": "SW Salmon & 14th", + "stopId": "TriMet:5014", + "stopCode": "5014", + "lon": -122.687479, + "lat": 45.519106, + "arrival": 1591718559000, + "departure": 1591718559000, + "zoneId": "B", + "stopIndex": 23, + "stopSequence": 24, + "vertexType": "TRANSIT", + "boardAlightType": "DEFAULT" + }, + { + "name": "SW Salmon & 12th", + "stopId": "TriMet:5012", + "stopCode": "5012", + "lon": -122.684797, + "lat": 45.518386, + "arrival": 1591718621000, + "departure": 1591718621000, + "zoneId": "B", + "stopIndex": 24, + "stopSequence": 25, + "vertexType": "TRANSIT", + "boardAlightType": "DEFAULT" + }, + { + "name": "SW Salmon & 10th", + "stopId": "TriMet:5009", + "stopCode": "5009", + "lon": -122.683475, + "lat": 45.518026, + "arrival": 1591718652000, + "departure": 1591718652000, + "zoneId": "B", + "stopIndex": 25, + "stopSequence": 26, + "vertexType": "TRANSIT", + "boardAlightType": "DEFAULT" + }, + { + "name": "SW Salmon & Park", + "stopId": "TriMet:5007", + "stopCode": "5007", + "lon": -122.681844, + "lat": 45.517596, + "arrival": 1591718690000, + "departure": 1591718690000, + "zoneId": "B", + "stopIndex": 26, + "stopSequence": 27, + "vertexType": "TRANSIT", + "boardAlightType": "DEFAULT" + } + ], + "steps": [] }, - "rentedBike": false, - "rentedCar": false, - "rentedVehicle": false, - "hailedCar": false, - "flexDrtAdvanceBookMin": 0.0, - "duration": 100.0, - "transitLeg": false, - "intermediateStops": [], - "steps": [ - { - "distance": 164.377, - "relativeDirection": "DEPART", - "streetName": "SW Salmon St", - "absoluteDirection": "EAST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.67882640142975, - "lat": 45.51684447671969, - "elevation": [ - { - "first": 15.22, - "second": 17.552842393406284 - }, - { - "first": 25.22, - "second": 17.292842393406282 - }, - { - "first": 35.22, - "second": 16.962842393406284 - }, - { - "first": 45.22, - "second": 16.642842393406283 - }, - { - "first": 55.22, - "second": 16.162842393406283 - }, - { - "first": 65.22, - "second": 15.712842393406282 - }, - { - "first": 75.22, - "second": 15.122842393406282 - }, - { - "first": 85.22, - "second": 15.062842393406282 - }, - { - "first": 98.36, - "second": 14.982842393406283 - }, - { - "first": 98.364, - "second": 14.982842393406283 - }, - { - "first": 108.23400000000001, - "second": 14.762842393406283 - }, - { - "first": 108.23700000000001, - "second": 14.762842393406283 - }, - { - "first": 118.23700000000001, - "second": 14.502842393406283 - }, - { - "first": 128.23700000000002, - "second": 14.272842393406282 - }, - { - "first": 138.23700000000002, - "second": 13.872842393406282 - }, - { - "first": 148.23700000000002, - "second": 13.632842393406282 - }, - { - "first": 158.23700000000002, - "second": 13.412842393406283 - }, - { - "first": 164.377, - "second": 13.152842393406281 - } - ] - } - ] - } - ], - "tooSloped": false - } - ] - }, - "debugOutput": { - "precalculationTime": 153, - "pathCalculationTime": 112, - "pathTimes": [ - 41, - 33, - 37 - ], - "renderingTime": 1, - "totalTime": 266, - "timedOut": false - }, - "elevationMetadata": { - "ellipsoidToGeoidDifference": -19.522842393406282, - "geoidElevation": true + { + "startTime": 1591718761000, + "endTime": 1591718861000, + "departureDelay": 0, + "arrivalDelay": 0, + "realTime": false, + "distance": 164.377, + "pathway": false, + "mode": "WALK", + "route": "", + "agencyTimeZoneOffset": -25200000, + "interlineWithPreviousLeg": false, + "from": { + "name": "SW Salmon & 5th", + "stopId": "TriMet:5020", + "stopCode": "5020", + "lon": -122.678854, + "lat": 45.516794, + "arrival": 1591718760000, + "departure": 1591718761000, + "zoneId": "B", + "stopIndex": 27, + "stopSequence": 28, + "vertexType": "TRANSIT", + "boardAlightType": "DEFAULT" + }, + "to": { + "name": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", + "lon": -122.67681483620306, + "lat": 45.51639151281627, + "arrival": 1591718861000, + "orig": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", + "vertexType": "NORMAL" + }, + "legGeometry": { + "points": "g_ytGtuwkV@CF]?ABOh@{CD[FWb@eC", + "length": 9 + }, + "rentedBike": false, + "rentedCar": false, + "rentedVehicle": false, + "hailedCar": false, + "flexDrtAdvanceBookMin": 0.0, + "duration": 100.0, + "transitLeg": false, + "intermediateStops": [], + "steps": [ + { + "distance": 164.377, + "relativeDirection": "DEPART", + "streetName": "SW Salmon St", + "absoluteDirection": "EAST", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.67882640142975, + "lat": 45.51684447671969, + "elevation": [ + { + "first": 15.22, + "second": 17.552842393406284 + }, + { + "first": 25.22, + "second": 17.292842393406282 + }, + { + "first": 35.22, + "second": 16.962842393406284 + }, + { + "first": 45.22, + "second": 16.642842393406283 + }, + { + "first": 55.22, + "second": 16.162842393406283 + }, + { + "first": 65.22, + "second": 15.712842393406282 + }, + { + "first": 75.22, + "second": 15.122842393406282 + }, + { + "first": 85.22, + "second": 15.062842393406282 + }, + { + "first": 98.36, + "second": 14.982842393406283 + }, + { + "first": 98.364, + "second": 14.982842393406283 + }, + { + "first": 108.23400000000001, + "second": 14.762842393406283 + }, + { + "first": 108.23700000000001, + "second": 14.762842393406283 + }, + { + "first": 118.23700000000001, + "second": 14.502842393406283 + }, + { + "first": 128.23700000000002, + "second": 14.272842393406282 + }, + { + "first": 138.23700000000002, + "second": 13.872842393406282 + }, + { + "first": 148.23700000000002, + "second": 13.632842393406282 + }, + { + "first": 158.23700000000002, + "second": 13.412842393406283 + }, + { + "first": 164.377, + "second": 13.152842393406281 + } + ] + } + ] + } + ], + "tooSloped": false + } + ] + } } } \ No newline at end of file From 4c179ac634bb5b4193cab0cf19504c1aced1fa86 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 22 Jul 2024 12:45:35 -0400 Subject: [PATCH 22/48] fix(OtpGraphQLVariables): Box reluctance and speed params. --- .../middleware/otp/OtpGraphQLVariables.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java b/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java index e414146e5..0b7394b9a 100644 --- a/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java +++ b/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java @@ -15,8 +15,8 @@ public class OtpGraphQLVariables implements Cloneable { public boolean arriveBy; public OtpGraphQLRoutesAndTrips banned; - public float bikeReluctance; - public float carReluctance; + public Float bikeReluctance; + public Float carReluctance; public String date; public String fromPlace; public List modes; @@ -25,8 +25,8 @@ public class OtpGraphQLVariables implements Cloneable { public String time; public String toPlace; public OtpGraphQLRoutesAndTrips unpreferred; - public float walkReluctance; - public float walkSpeed; + public Float walkReluctance; + public Float walkSpeed; public boolean wheelchair; public static OtpGraphQLVariables fromRequest(Request request) throws JsonProcessingException { From 0be9b62568786be73632b2e18efa1e8099adaa86 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 22 Jul 2024 14:44:59 -0400 Subject: [PATCH 23/48] fix(ItineraryUtils): Compare stop codes using OTP2 stop field in from and to locations. --- .../middleware/otp/response/Place.java | 2 +- .../middleware/otp/response/Stop.java | 25 +++++++++++++++++++ .../middleware/utils/ItineraryUtils.java | 8 +++++- 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/opentripplanner/middleware/otp/response/Stop.java diff --git a/src/main/java/org/opentripplanner/middleware/otp/response/Place.java b/src/main/java/org/opentripplanner/middleware/otp/response/Place.java index d6b89e314..cf3b0f101 100644 --- a/src/main/java/org/opentripplanner/middleware/otp/response/Place.java +++ b/src/main/java/org/opentripplanner/middleware/otp/response/Place.java @@ -4,7 +4,6 @@ import com.fasterxml.jackson.annotation.JsonInclude; import java.util.Date; -import java.util.Objects; import java.util.Set; /** @@ -22,6 +21,7 @@ public class Place implements Cloneable { public String vertexType; public String stopId; public Date arrival; + public Stop stop; public Integer stopIndex; public Integer stopSequence; public String stopCode; diff --git a/src/main/java/org/opentripplanner/middleware/otp/response/Stop.java b/src/main/java/org/opentripplanner/middleware/otp/response/Stop.java new file mode 100644 index 000000000..98c4e6208 --- /dev/null +++ b/src/main/java/org/opentripplanner/middleware/otp/response/Stop.java @@ -0,0 +1,25 @@ +package org.opentripplanner.middleware.otp.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; + +import java.util.ArrayList; +import java.util.List; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class Stop implements Cloneable { + public List alerts = new ArrayList<>(); + public String code; + public String gtfsId; + public String id; + public Double lon; + public Double lat; + + /** + * Clone this object. + */ + protected Place clone() throws CloneNotSupportedException { + return (Place) super.clone(); + } +} diff --git a/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java b/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java index 34f43821c..c22af196c 100644 --- a/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java +++ b/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java @@ -271,7 +271,13 @@ private static boolean stopsMatch(Place stopA, Place stopB) { } // stop code must match - if (!equalsIgnoreCaseOrReferenceWasEmpty(stopA.stopCode, stopB.stopCode)) return false; + if ( + stopA.stop != null && + stopB.stop != null && + !equalsIgnoreCaseOrReferenceWasEmpty(stopA.stop.code, stopB.stop.code) + ) { + return false; + } // stop positions must be no further than 5 meters apart double stopDistanceMeters = DistanceUtils.radians2Dist( From 0fdd6dc74d0cc7b5e2418c7c155c92fd71551244 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 22 Jul 2024 17:11:54 -0400 Subject: [PATCH 24/48] refactor(ItineraryUtils): Compare agency and routes using new structures from OTP2 response. --- .../middleware/otp/response/Agency.java | 16 ++++++ .../middleware/otp/response/Leg.java | 2 + .../middleware/otp/response/Route.java | 20 ++++++++ .../middleware/utils/ItineraryUtils.java | 49 ++++++++++++------- 4 files changed, 70 insertions(+), 17 deletions(-) create mode 100644 src/main/java/org/opentripplanner/middleware/otp/response/Agency.java create mode 100644 src/main/java/org/opentripplanner/middleware/otp/response/Route.java diff --git a/src/main/java/org/opentripplanner/middleware/otp/response/Agency.java b/src/main/java/org/opentripplanner/middleware/otp/response/Agency.java new file mode 100644 index 000000000..135211311 --- /dev/null +++ b/src/main/java/org/opentripplanner/middleware/otp/response/Agency.java @@ -0,0 +1,16 @@ +package org.opentripplanner.middleware.otp.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; + +import java.util.ArrayList; +import java.util.List; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class Agency { + public List alerts = new ArrayList<>(); + public String gtfsId; + public String id; + public String name; +} diff --git a/src/main/java/org/opentripplanner/middleware/otp/response/Leg.java b/src/main/java/org/opentripplanner/middleware/otp/response/Leg.java index b60071a0d..4b33c3184 100644 --- a/src/main/java/org/opentripplanner/middleware/otp/response/Leg.java +++ b/src/main/java/org/opentripplanner/middleware/otp/response/Leg.java @@ -54,6 +54,8 @@ public class Leg implements Cloneable { public String routeTextColor; public List alerts = null; public String headsign; + public Agency agency; + public Route route; /** * Gets the scheduled start time of this itinerary in the OTP timezone. diff --git a/src/main/java/org/opentripplanner/middleware/otp/response/Route.java b/src/main/java/org/opentripplanner/middleware/otp/response/Route.java new file mode 100644 index 000000000..61a3e2df4 --- /dev/null +++ b/src/main/java/org/opentripplanner/middleware/otp/response/Route.java @@ -0,0 +1,20 @@ +package org.opentripplanner.middleware.otp.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; + +import java.util.ArrayList; +import java.util.List; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class Route { + public List alerts = new ArrayList<>(); + public String color; + public String gtfsId; + public String id; + public String longName; + public String shortName; + public String textColor; + public Integer type; +} diff --git a/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java b/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java index c22af196c..93b4b2a8a 100644 --- a/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java +++ b/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java @@ -7,10 +7,12 @@ import org.opentripplanner.middleware.models.MonitoredTrip; import org.opentripplanner.middleware.otp.OtpGraphQLTransportMode; import org.opentripplanner.middleware.otp.OtpGraphQLVariables; +import org.opentripplanner.middleware.otp.response.Agency; import org.opentripplanner.middleware.otp.response.Itinerary; import org.opentripplanner.middleware.otp.response.Leg; import org.opentripplanner.middleware.otp.response.Place; import org.opentripplanner.middleware.otp.OtpRequest; +import org.opentripplanner.middleware.otp.response.Route; import java.time.LocalDate; import java.time.ZonedDateTime; @@ -215,23 +217,10 @@ private static boolean legsMatch(Leg referenceItineraryLeg, Leg candidateItinera // - The leg has the same interlining qualities with the previous leg if ( !equalsOrReferenceWasNull(referenceItineraryLeg.mode, candidateItineraryLeg.mode) || - !equalsIgnoreCaseOrReferenceWasEmpty( - referenceItineraryLeg.agencyName, - candidateItineraryLeg.agencyName - ) || - !equalsIgnoreCaseOrReferenceWasEmpty( - referenceItineraryLeg.routeLongName, - candidateItineraryLeg.routeLongName - ) || - !equalsIgnoreCaseOrReferenceWasEmpty( - referenceItineraryLeg.routeShortName, - candidateItineraryLeg.routeShortName - ) || - !equalsIgnoreCaseOrReferenceWasEmpty( - referenceItineraryLeg.headsign, - candidateItineraryLeg.headsign - ) || - (referenceItineraryLeg.interlineWithPreviousLeg != candidateItineraryLeg.interlineWithPreviousLeg) + !agenciesMatch(referenceItineraryLeg.agency, candidateItineraryLeg.agency) || + !routesMatch(referenceItineraryLeg.route, candidateItineraryLeg.route) || + !equalsIgnoreCaseOrReferenceWasEmpty(referenceItineraryLeg.headsign, candidateItineraryLeg.headsign) || + (referenceItineraryLeg.interlineWithPreviousLeg != candidateItineraryLeg.interlineWithPreviousLeg) ) { return false; } @@ -292,6 +281,32 @@ private static boolean stopsMatch(Place stopA, Place stopB) { return true; } + /** + * Checks whether two agencies are deemed the same for itinerary comparison purposes. + */ + private static boolean agenciesMatch(Agency agencyA, Agency agencyB) { + return ( + agencyA != null && + agencyB != null && + equalsIgnoreCaseOrReferenceWasEmpty( + agencyA.name, + agencyB.name + ) + ); + } + + /** + * Checks whether two transit routes are deemed the same for itinerary comparison purposes. + */ + private static boolean routesMatch(Route routeA, Route routeB) { + return ( + routeA != null && + routeB != null && + equalsIgnoreCaseOrReferenceWasEmpty(routeA.longName, routeB.longName) && + equalsIgnoreCaseOrReferenceWasEmpty(routeA.shortName, routeB.shortName) + ); + } + /** * Returns true if the reference value is null. Otherwise, returns Objects.equals. */ From 067aed0e37543ab74ebff20de20dfad38761f319 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 23 Jul 2024 12:21:24 -0400 Subject: [PATCH 25/48] refactor(ItineraryUtils): Remove unused code and cleanup comments. --- .../middleware/utils/ItineraryUtils.java | 33 ++++--------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java b/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java index 93b4b2a8a..ab82a274d 100644 --- a/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java +++ b/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java @@ -2,8 +2,6 @@ import com.spatial4j.core.distance.DistanceUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.http.client.utils.URLEncodedUtils; -import org.apache.http.message.BasicNameValuePair; import org.opentripplanner.middleware.models.MonitoredTrip; import org.opentripplanner.middleware.otp.OtpGraphQLTransportMode; import org.opentripplanner.middleware.otp.OtpGraphQLVariables; @@ -18,13 +16,11 @@ import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.Objects; import java.util.stream.Collectors; -import static java.nio.charset.StandardCharsets.UTF_8; import static org.opentripplanner.middleware.utils.ConfigUtils.getConfigPropertyAsInt; import static org.opentripplanner.middleware.utils.DateTimeUtils.DEFAULT_DATE_FORMAT_PATTERN; @@ -38,21 +34,10 @@ public class ItineraryUtils { public static final int SERVICE_DAY_START_HOUR = getConfigPropertyAsInt("SERVICE_DAY_START_HOUR", 3); /** - * Converts a {@link Map} to a URL query string (does not include a leading '?'). - */ - public static String toQueryString(Map params) { - List nameValuePairs = params.entrySet().stream() - .map(e -> new BasicNameValuePair(e.getKey(), e.getValue())) - .collect(Collectors.toList()); - return URLEncodedUtils.format(nameValuePairs, UTF_8); - } - - /** - * Creates a map of new query strings based on the one provided, - * with the date changed to the desired one. - * @param params a map of the base OTP query parameters. + * Generates itinerary request data for the desired dates, based on the provided query parameters. + * @param params The base OTP GraphQL query parameters. * @param dates a list of the desired dates in YYYY-MM-DD format. - * @return a list of query strings with, and indexed by the specified dates. + * @return a list of request data for the corresponding request dates. */ public static List getOtpRequestsForDates(OtpGraphQLVariables params, List dates) { // Create a copy of the original params in which we change the date. @@ -74,15 +59,14 @@ public static List getOtpRequestsForDates(OtpGraphQLVariables params * @return A list of date strings in YYYY-MM-DD format corresponding to each day of the week to monitor, sorted from earliest. */ public static List getDatesToCheckItineraryExistence(MonitoredTrip trip) { - List datesToCheck = new ArrayList<>(); - OtpGraphQLVariables params = trip.otp2QueryParams; - // Start from the query date, if available. - String startingDateString = params.date; + String startingDateString = trip.otp2QueryParams.date; // If there is no query date, start from today. LocalDate startingDate = DateTimeUtils.getDateFromQueryDateString(startingDateString); ZonedDateTime startingDateTime = trip.tripZonedDateTime(startingDate); + // Get the dates to check starting from the query date and continuing through the full date range window. + List datesToCheck = new ArrayList<>(); for (int i = 0; i < ITINERARY_CHECK_WINDOW; i++) { datesToCheck.add(startingDateTime.plusDays(i)); } @@ -288,10 +272,7 @@ private static boolean agenciesMatch(Agency agencyA, Agency agencyB) { return ( agencyA != null && agencyB != null && - equalsIgnoreCaseOrReferenceWasEmpty( - agencyA.name, - agencyB.name - ) + equalsIgnoreCaseOrReferenceWasEmpty(agencyA.name, agencyB.name) ); } From 6c0d34115f60445594a32f6397a7fc034fc7da0b Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 23 Jul 2024 15:53:32 -0400 Subject: [PATCH 26/48] refactor(MonitoredTrip): Remove unused exceptions. --- .../api/MonitoredTripController.java | 37 ++++--------------- .../middleware/models/MonitoredTrip.java | 14 +++---- 2 files changed, 14 insertions(+), 37 deletions(-) diff --git a/src/main/java/org/opentripplanner/middleware/controllers/api/MonitoredTripController.java b/src/main/java/org/opentripplanner/middleware/controllers/api/MonitoredTripController.java index d36cdf357..f22f60bbb 100644 --- a/src/main/java/org/opentripplanner/middleware/controllers/api/MonitoredTripController.java +++ b/src/main/java/org/opentripplanner/middleware/controllers/api/MonitoredTripController.java @@ -16,7 +16,6 @@ import spark.Request; import spark.Response; -import java.net.URISyntaxException; import java.util.Set; import java.util.stream.Collectors; @@ -67,26 +66,15 @@ MonitoredTrip preCreateHook(MonitoredTrip monitoredTrip, Request req) { // check itinerary existence for recurring trips only for now. // (Existence should ultimately be checked on all trips.) if (!monitoredTrip.isOneTime()) { - try { - // Check itinerary existence for all days and replace the provided trip's itinerary with a verified, - // non-realtime version of it. - boolean success = monitoredTrip.checkItineraryExistence(true); - if (!success) { - logMessageAndHalt( - req, - HttpStatus.BAD_REQUEST_400, - monitoredTrip.itineraryExistence.message - ); - } - } catch (URISyntaxException e) { // triggered by OtpQueryUtils#getQueryParams. + // Check itinerary existence for all days and replace the provided trip's itinerary with a verified, + // non-realtime version of it. + boolean success = monitoredTrip.checkItineraryExistence(true); + if (!success) { logMessageAndHalt( req, - HttpStatus.INTERNAL_SERVER_ERROR_500, - "Error parsing the trip query parameters.", - e + HttpStatus.BAD_REQUEST_400, + monitoredTrip.itineraryExistence.message ); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); } } @@ -199,17 +187,8 @@ private static ItineraryExistence checkItinerary(Request request, Response respo logMessageAndHalt(request, HttpStatus.BAD_REQUEST_400, "Error parsing JSON for MonitoredTrip", e); return null; } - try { - trip.initializeFromItineraryAndQueryParams(trip.otp2QueryParams); - trip.checkItineraryExistence(false); - } catch (URISyntaxException | JsonProcessingException e) { // triggered by OtpQueryUtils#getQueryParams. - logMessageAndHalt( - request, - HttpStatus.INTERNAL_SERVER_ERROR_500, - "Error parsing the trip query parameters.", - e - ); - } + trip.initializeFromItineraryAndQueryParams(trip.otp2QueryParams); + trip.checkItineraryExistence(false); return trip.itineraryExistence; } diff --git a/src/main/java/org/opentripplanner/middleware/models/MonitoredTrip.java b/src/main/java/org/opentripplanner/middleware/models/MonitoredTrip.java index 1e9af9fed..913c9f4cd 100644 --- a/src/main/java/org/opentripplanner/middleware/models/MonitoredTrip.java +++ b/src/main/java/org/opentripplanner/middleware/models/MonitoredTrip.java @@ -22,7 +22,6 @@ import org.opentripplanner.middleware.utils.ItineraryUtils; import spark.Request; -import java.net.URISyntaxException; import java.time.DayOfWeek; import java.time.LocalDate; import java.time.LocalTime; @@ -182,8 +181,7 @@ public MonitoredTrip() { /** * Used only during testing */ - public MonitoredTrip(OtpGraphQLVariables otp2QueryParams, OtpDispatcherResponse otpDispatcherResponse) throws URISyntaxException, - JsonProcessingException { + public MonitoredTrip(OtpGraphQLVariables otp2QueryParams, OtpDispatcherResponse otpDispatcherResponse) throws JsonProcessingException { TripPlan plan = otpDispatcherResponse.getResponse().plan; itinerary = plan.itineraries.get(0); @@ -198,7 +196,7 @@ public MonitoredTrip(OtpGraphQLVariables otp2QueryParams, OtpDispatcherResponse public boolean checkItineraryExistence( boolean replaceItinerary, Function otpResponseProvider - ) throws URISyntaxException, JsonProcessingException { + ) { // Get queries to execute by date. List queriesByDate = getItineraryExistenceQueries(); itineraryExistence = new ItineraryExistence(queriesByDate, itinerary, arriveBy, otpResponseProvider); @@ -213,7 +211,7 @@ public boolean checkItineraryExistence( /** * Shorthand for above method using the default otpResponseProvider. */ - public boolean checkItineraryExistence(boolean replaceItinerary) throws URISyntaxException, JsonProcessingException { + public boolean checkItineraryExistence(boolean replaceItinerary) { return checkItineraryExistence(replaceItinerary, null); } @@ -221,7 +219,7 @@ public boolean checkItineraryExistence(boolean replaceItinerary) throws URISynta * Replace the itinerary provided with the monitored trip * with a non-real-time, verified itinerary from the responses provided. */ - private boolean updateTripWithVerifiedItinerary() throws URISyntaxException, JsonProcessingException { + private boolean updateTripWithVerifiedItinerary() { String queryDate = otp2QueryParams.date; DayOfWeek dayOfWeek = DateTimeUtils.getDateFromQueryDateString(queryDate).getDayOfWeek(); @@ -260,7 +258,7 @@ public List getItineraryExistenceQueries() { * Initializes a MonitoredTrip by deriving some fields from the currently set itinerary. Also, the realtime info of * the itinerary is removed. */ - public void initializeFromItineraryAndQueryParams(Request req) throws IllegalArgumentException, URISyntaxException, JsonProcessingException { + public void initializeFromItineraryAndQueryParams(Request req) throws IllegalArgumentException, JsonProcessingException { initializeFromItineraryAndQueryParams(OtpGraphQLVariables.fromRequest(req)); } @@ -268,7 +266,7 @@ public void initializeFromItineraryAndQueryParams(Request req) throws IllegalArg * Initializes a MonitoredTrip by deriving some fields from the currently set itinerary. Also, the realtime info of * the itinerary is removed. */ - public void initializeFromItineraryAndQueryParams(OtpGraphQLVariables graphQLVariables) throws IllegalArgumentException, URISyntaxException, JsonProcessingException { + public void initializeFromItineraryAndQueryParams(OtpGraphQLVariables graphQLVariables) throws IllegalArgumentException { int lastLegIndex = itinerary.legs.size() - 1; from = itinerary.legs.get(0).from; to = itinerary.legs.get(lastLegIndex).to; From 21d72d4e648413b549295916e7389ac4a6f921d0 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 23 Jul 2024 16:45:16 -0400 Subject: [PATCH 27/48] chore(swagger): Update snapshot. --- .../latest-spark-swagger-output.yaml | 64 +++++++++++++++++-- 1 file changed, 60 insertions(+), 4 deletions(-) diff --git a/src/main/resources/latest-spark-swagger-output.yaml b/src/main/resources/latest-spark-swagger-output.yaml index 05bbeff36..16f2cb91c 100644 --- a/src/main/resources/latest-spark-swagger-output.yaml +++ b/src/main/resources/latest-spark-swagger-output.yaml @@ -35,7 +35,7 @@ tags: - name: "api/secure/connected-data" description: "Interface for listing and downloading CDP files from S3." - name: "otp" - description: "Proxy interface for OTP 1 endpoints. Refer to OTP's\ + description: "Proxy interface for OTP 2 endpoints. Refer to OTP's\ \ API documentation for OTP's supported API resources." - name: "otp2" description: "Proxy interface for OTP 2 endpoints. Refer to OTP's\ @@ -2109,7 +2109,7 @@ paths: get: tags: - "otp" - description: "Forwards any GET request to OTP 1. Refer to OTP's\ + description: "Forwards any GET request to OTP 2. Refer to OTP's\ \ API documentation for OTP's supported API resources." produces: - "application/json" @@ -2127,7 +2127,7 @@ paths: post: tags: - "otp" - description: "Forwards any POST request to OTP 1. Refer to OTP's\ + description: "Forwards any POST request to OTP 2. Refer to OTP's\ \ API documentation for OTP's supported API resources." produces: - "application/json" @@ -2835,7 +2835,63 @@ definitions: toPlace: type: "string" requestParameters: - $ref: "#/definitions/HashMap" + $ref: "#/definitions/Map" + graphQLVariables: + $ref: "#/definitions/OtpGraphQLVariables" + OtpGraphQLRoutesAndTrips: + type: "object" + properties: + routes: + type: "string" + trips: + type: "string" + OtpGraphQLTransportMode: + type: "object" + properties: + mode: + type: "string" + qualifier: + type: "string" + OtpGraphQLVariables: + type: "object" + properties: + arriveBy: + type: "boolean" + banned: + $ref: "#/definitions/OtpGraphQLRoutesAndTrips" + bikeReluctance: + type: "number" + format: "float" + carReluctance: + type: "number" + format: "float" + date: + type: "string" + fromPlace: + type: "string" + modes: + type: "array" + items: + $ref: "#/definitions/OtpGraphQLTransportMode" + numItineraries: + type: "integer" + format: "int32" + preferred: + $ref: "#/definitions/OtpGraphQLRoutesAndTrips" + time: + type: "string" + toPlace: + type: "string" + unpreferred: + $ref: "#/definitions/OtpGraphQLRoutesAndTrips" + walkReluctance: + type: "number" + format: "float" + walkSpeed: + type: "number" + format: "float" + wheelchair: + type: "boolean" MonitoredComponent: type: "object" properties: From 3946d61830b215180cce1affec7c3d83f7c6ad59 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 23 Jul 2024 17:03:24 -0400 Subject: [PATCH 28/48] chore(ItineraryUtilsTest): Update mock responses to OTP2. --- .../otp/response/planErrorResponse.json | 43 +- .../middleware/otp/response/planResponse.json | 5794 ++++++++--------- 2 files changed, 2895 insertions(+), 2942 deletions(-) diff --git a/src/test/resources/org/opentripplanner/middleware/otp/response/planErrorResponse.json b/src/test/resources/org/opentripplanner/middleware/otp/response/planErrorResponse.json index a5e6ba6ea..3dc527b22 100644 --- a/src/test/resources/org/opentripplanner/middleware/otp/response/planErrorResponse.json +++ b/src/test/resources/org/opentripplanner/middleware/otp/response/planErrorResponse.json @@ -1,33 +1,14 @@ { - "requestParameters": { - "date": "2020-06-09", - "mode": "WALK,BUS,TRAM,RAIL,GONDOLA", - "arriveBy": "false", - "walkSpeed": "1.34", - "ignoreRealtimeUpdates": "true", - "companies": "NaN", - "fromPace": "1709 NW Irving St, Portland 97209::45.527817334203,-122.68865964147231", - "showIntermediateStops": "true", - "optimize": "QUICK", - "toPlace": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204::45.51639151281627,-122.67681483620306", - "time": "08:35", - "maxWalkDistance": "1207" - }, - "error": { - "id": 400, - "msg": "Trip is not possible. You might be trying to plan a trip outside the map data boundary.", - "message": "OUTSIDE_BOUNDS", - "missing": [ - "from" - ], - "noPath": true - }, - "debugOutput": { - "precalculationTime": 0, - "pathCalculationTime": 0, - "pathTimes": [], - "renderingTime": 0, - "totalTime": 0, - "timedOut": false + "data": { + "plan": { + "itineraries": [], + "routingErrors": [ + { + "code": "NO_STOPS_IN_RANGE", + "description": "The location was found, but no stops could be found within the search radius.", + "inputField": "TO" + } + ] + } } -} \ No newline at end of file +} diff --git a/src/test/resources/org/opentripplanner/middleware/otp/response/planResponse.json b/src/test/resources/org/opentripplanner/middleware/otp/response/planResponse.json index fc68716a0..4ca0da0ca 100644 --- a/src/test/resources/org/opentripplanner/middleware/otp/response/planResponse.json +++ b/src/test/resources/org/opentripplanner/middleware/otp/response/planResponse.json @@ -1,2951 +1,2923 @@ { - "requestParameters": { - "date": "2020-06-09", - "mode": "WALK,BUS,TRAM,RAIL,GONDOLA", - "arriveBy": "false", - "walkSpeed": "1.34", - "ignoreRealtimeUpdates": "true", - "companies": "NaN", - "showIntermediateStops": "true", - "optimize": "QUICK", - "fromPlace": "1709 NW Irving St, Portland 97209::45.527817334203,-122.68865964147231", - "toPlace": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204::45.51639151281627,-122.67681483620306", - "time": "08:35", - "maxWalkDistance": "1207" - }, - "plan": { - "date": 1591716900000, - "from": { - "name": "1709 NW Irving St, Portland 97209", - "lon": -122.68865964147231, - "lat": 45.527817334203, - "orig": "1709 NW Irving St, Portland 97209", - "vertexType": "NORMAL" - }, - "to": { - "name": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", - "lon": -122.67681483620306, - "lat": 45.51639151281627, - "orig": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", - "vertexType": "NORMAL" - }, - "itineraries": [ - { - "duration": 1114, - "startTime": 1591717210000, - "endTime": 1591718324000, - "walkTime": 872, - "transitTime": 240, - "waitingTime": 2, - "walkDistance": 1256.0514029764959, - "walkLimitExceeded": false, - "elevationLost": 10.41, - "elevationGained": 15.05, - "transfers": 0, - "fare": { - "fare": { - "regular": { - "currency": { - "symbol": "$", - "currency": "USD", - "defaultFractionDigits": 2, - "currencyCode": "USD" - }, - "cents": 250 - } - }, - "details": { - "regular": [ - { - "fareId": "TriMet:R", - "price": { - "currency": { - "symbol": "$", - "currency": "USD", - "defaultFractionDigits": 2, - "currencyCode": "USD" - }, - "cents": 250 - }, - "routes": [ - "TriMet:100" - ] - } - ] - } - }, - "legs": [ - { - "startTime": 1591717210000, - "endTime": 1591717794000, - "departureDelay": 0, - "arrivalDelay": 0, - "realTime": false, - "distance": 837.5169999999998, - "pathway": false, - "mode": "WALK", - "route": "", - "agencyTimeZoneOffset": -25200000, - "interlineWithPreviousLeg": false, - "from": { - "name": "1709 NW Irving St, Portland 97209", - "lon": -122.68865964147231, - "lat": 45.527817334203, - "departure": 1591717210000, - "orig": "1709 NW Irving St, Portland 97209", - "vertexType": "NORMAL" - }, - "to": { - "name": "Providence Park MAX Station", - "stopId": "TriMet:9758", - "stopCode": "9758", - "lon": -122.689886, - "lat": 45.521321, - "arrival": 1591717794000, - "departure": 1591717795000, - "zoneId": "R", - "stopIndex": 18, - "stopSequence": 19, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - "legGeometry": { - "points": "}c{tGdsykVAu@x@AJ?v@AP?nCE\\?r@Ax@C`BADAd@?lCEp@AdAAV?JA`@?F?jACClAF@B?l@XNFpAj@f@TJDB@B@LFx@\\JDHBH@HBCPFBDBQ~@", - "length": 41 - }, - "rentedBike": false, - "rentedCar": false, - "rentedVehicle": false, - "hailedCar": false, - "flexDrtAdvanceBookMin": 0.0, - "duration": 584.0, - "transitLeg": false, - "intermediateStops": [], - "steps": [ - { - "distance": 20.794, - "relativeDirection": "DEPART", - "streetName": "NW Irving St", - "absoluteDirection": "EAST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.68866035123024, - "lat": 45.527836027296765, - "elevation": [ - { - "first": 0.0, - "second": 20.682842393406283 - }, - { - "first": 10.0, - "second": 20.452842393406282 - }, - { - "first": 20.0, - "second": 20.24284239340628 - }, - { - "first": 20.79, - "second": 20.22284239340628 - } - ] - }, - { - "distance": 547.395, - "relativeDirection": "RIGHT", - "streetName": "NW 17th Ave", - "absoluteDirection": "SOUTH", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6883935, - "lat": 45.527841, - "elevation": [ - { - "first": 0.0, - "second": 18.682842393406283 - }, - { - "first": 10.0, - "second": 18.802842393406284 - }, - { - "first": 20.0, - "second": 19.01284239340628 - }, - { - "first": 31.29, - "second": 19.24284239340628 - }, - { - "first": 31.294, - "second": 19.24284239340628 - }, - { - "first": 39.024, - "second": 19.40284239340628 - }, - { - "first": 39.023, - "second": 19.40284239340628 - }, - { - "first": 49.023, - "second": 19.60284239340628 - }, - { - "first": 59.023, - "second": 19.85284239340628 - }, - { - "first": 69.023, - "second": 19.97284239340628 - }, - { - "first": 79.273, - "second": 20.032842393406284 - }, - { - "first": 79.272, - "second": 20.032842393406284 - }, - { - "first": 89.272, - "second": 20.26284239340628 - }, - { - "first": 99.272, - "second": 20.572842393406283 - }, - { - "first": 109.272, - "second": 20.872842393406284 - }, - { - "first": 119.272, - "second": 21.112842393406282 - }, - { - "first": 129.272, - "second": 21.412842393406283 - }, - { - "first": 139.272, - "second": 21.69284239340628 - }, - { - "first": 149.272, - "second": 22.01284239340628 - }, - { - "first": 159.322, - "second": 22.002842393406283 - }, - { - "first": 159.323, - "second": 22.002842393406283 - }, - { - "first": 169.323, - "second": 22.17284239340628 - }, - { - "first": 176.933, - "second": 22.352842393406284 - }, - { - "first": 176.93, - "second": 22.352842393406284 - }, - { - "first": 186.93, - "second": 22.58284239340628 - }, - { - "first": 196.93, - "second": 22.792842393406282 - }, - { - "first": 205.41, - "second": 22.952842393406282 - }, - { - "first": 205.41, - "second": 22.952842393406282 - }, - { - "first": 215.41, - "second": 23.15284239340628 - }, - { - "first": 225.41, - "second": 23.302842393406284 - }, - { - "first": 237.51, - "second": 23.42284239340628 - }, - { - "first": 237.505, - "second": 23.42284239340628 - }, - { - "first": 247.505, - "second": 23.502842393406283 - }, - { - "first": 257.505, - "second": 23.632842393406282 - }, - { - "first": 267.505, - "second": 23.76284239340628 - }, - { - "first": 277.505, - "second": 23.90284239340628 - }, - { - "first": 292.145, - "second": 24.092842393406283 - }, - { - "first": 292.143, - "second": 24.092842393406283 - }, - { - "first": 295.53299999999996, - "second": 24.132842393406282 - }, - { - "first": 295.53499999999997, - "second": 24.132842393406282 - }, - { - "first": 305.53499999999997, - "second": 24.432842393406283 - }, - { - "first": 316.635, - "second": 24.392842393406283 - }, - { - "first": 316.63599999999997, - "second": 24.392842393406283 - }, - { - "first": 326.63599999999997, - "second": 24.522842393406282 - }, - { - "first": 336.63599999999997, - "second": 24.792842393406282 - }, - { - "first": 346.63599999999997, - "second": 25.052842393406284 - }, - { - "first": 356.63599999999997, - "second": 25.31284239340628 - }, - { - "first": 366.63599999999997, - "second": 25.602842393406284 - }, - { - "first": 376.63599999999997, - "second": 25.842842393406283 - }, - { - "first": 386.63599999999997, - "second": 25.982842393406283 - }, - { - "first": 396.02599999999995, - "second": 26.01284239340628 - }, - { - "first": 396.03, - "second": 26.01284239340628 - }, - { - "first": 406.03, - "second": 26.042842393406282 - }, - { - "first": 416.03, - "second": 26.092842393406283 - }, - { - "first": 423.69, - "second": 26.15284239340628 - }, - { - "first": 423.686, - "second": 26.15284239340628 - }, - { - "first": 433.686, - "second": 26.252842393406283 - }, - { - "first": 443.686, - "second": 26.31284239340628 - }, - { - "first": 453.686, - "second": 26.362842393406282 - }, - { - "first": 461.936, - "second": 26.40284239340628 - }, - { - "first": 461.93699999999995, - "second": 26.40284239340628 - }, - { - "first": 475.08699999999993, - "second": 26.19284239340628 - }, - { - "first": 475.08199999999994, - "second": 26.19284239340628 - }, - { - "first": 485.08199999999994, - "second": 25.732842393406283 - }, - { - "first": 495.08199999999994, - "second": 25.142842393406283 - }, - { - "first": 501.5519999999999, - "second": 24.76284239340628 - }, - { - "first": 501.54999999999995, - "second": 24.76284239340628 - }, - { - "first": 505.17999999999995, - "second": 24.602842393406284 - }, - { - "first": 505.17699999999996, - "second": 24.602842393406284 - }, - { - "first": 515.1769999999999, - "second": 24.12284239340628 - }, - { - "first": 525.1769999999999, - "second": 23.62284239340628 - }, - { - "first": 535.1769999999999, - "second": 23.292842393406282 - }, - { - "first": 547.3969999999999, - "second": 23.51284239340628 - } - ] - }, - { - "distance": 30.847, - "relativeDirection": "RIGHT", - "streetName": "W Burnside St", - "absoluteDirection": "WEST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.688213, - "lat": 45.5229198, - "elevation": [ - { - "first": 0.0, - "second": 23.51284239340628 - }, - { - "first": 10.0, - "second": 23.76284239340628 - }, - { - "first": 20.0, - "second": 24.052842393406284 - }, - { - "first": 30.85, - "second": 24.302842393406284 - } - ] - }, - { - "distance": 195.61100000000002, - "relativeDirection": "LEFT", - "streetName": "SW 17th Ave", - "absoluteDirection": "SOUTH", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6886081, - "lat": 45.522938, - "elevation": [ - { - "first": 0.0, - "second": 24.302842393406284 - }, - { - "first": 10.0, - "second": 24.532842393406284 - }, - { - "first": 20.0, - "second": 25.182842393406283 - }, - { - "first": 30.0, - "second": 25.802842393406284 - }, - { - "first": 44.36, - "second": 26.032842393406284 - }, - { - "first": 44.347, - "second": 26.032842393406284 - }, - { - "first": 54.347, - "second": 26.452842393406282 - }, - { - "first": 64.34700000000001, - "second": 27.282842393406284 - }, - { - "first": 74.34700000000001, - "second": 27.952842393406282 - }, - { - "first": 84.34700000000001, - "second": 28.132842393406282 - }, - { - "first": 92.137, - "second": 28.292842393406282 - }, - { - "first": 92.13900000000001, - "second": 28.292842393406282 - }, - { - "first": 102.13900000000001, - "second": 28.80284239340628 - }, - { - "first": 112.13900000000001, - "second": 29.30284239340628 - }, - { - "first": 123.74900000000001, - "second": 29.822842393406283 - }, - { - "first": 123.75200000000001, - "second": 29.822842393406283 - }, - { - "first": 136.752, - "second": 30.352842393406284 - } - ] - }, - { - "distance": 7.173, - "relativeDirection": "RIGHT", - "streetName": "path", - "absoluteDirection": "WEST", - "stayOn": false, - "area": false, - "bogusName": true, - "lon": -122.689436, - "lat": 45.521281, - "elevation": [ - { - "first": 0.0, - "second": 32.14284239340628 - }, - { - "first": 7.17, - "second": 32.092842393406286 - } - ] - }, - { - "distance": 8.612, - "relativeDirection": "LEFT", - "streetName": "path", - "absoluteDirection": "SOUTH", - "stayOn": true, - "area": false, - "bogusName": true, - "lon": -122.6895213, - "lat": 45.5213053, - "elevation": [ - { - "first": 0.0, - "second": 32.092842393406286 - }, - { - "first": 8.61, - "second": 32.202842393406286 - } - ] - }, - { - "distance": 27.085, - "relativeDirection": "RIGHT", - "streetName": "Providence Park (path)", - "absoluteDirection": "WEST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6895617, - "lat": 45.5212332, - "elevation": [] - } - ] - }, - { - "startTime": 1591717795000, - "endTime": 1591718035000, - "departureDelay": 0, - "arrivalDelay": 0, - "realTime": false, - "distance": 893.8869820964361, - "pathway": false, - "mode": "TRAM", - "route": "MAX Blue Line", - "agencyName": "TriMet", - "agencyUrl": "https://trimet.org/", - "agencyTimeZoneOffset": -25200000, - "routeColor": "084C8D", - "routeType": 0, - "routeId": "TriMet:100", - "routeTextColor": "FFFFFF", - "interlineWithPreviousLeg": false, - "tripBlockId": "9004", - "headsign": "Gresham", - "agencyId": "TRIMET", - "tripId": "TriMet:9942889", - "serviceDate": "20200609", - "from": { - "name": "Providence Park MAX Station", - "stopId": "TriMet:9758", - "stopCode": "9758", - "lon": -122.689886, - "lat": 45.521321, - "arrival": 1591717794000, - "departure": 1591717795000, - "zoneId": "R", - "stopIndex": 18, - "stopSequence": 19, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - "to": { - "name": "Pioneer Square South MAX Station", - "stopId": "TriMet:8334", - "stopCode": "8334", - "lon": -122.679145, - "lat": 45.518496, - "arrival": 1591718035000, - "departure": 1591718036000, - "zoneId": "R", - "stopIndex": 20, - "stopSequence": 21, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - "legGeometry": { - "points": "m{ytGtzykV@AN}@BQ@EJm@Lk@PcAF[l@mDDQBMn@uDDUj@gDDSl@mDFWDW\\oBHe@@KBI@I@EF]f@uCBQBI@KTqAP}@@IDS@GTqABSBKn@sDBQf@qC", - "length": 41 - }, - "interStopGeometry": [ - { - "points": "m{ytGtzykV@AN}@BQ@EJm@Lk@PcAF[l@mDDQBMn@uDDUj@gDDSl@mDFWDW\\oBHe@@KBI@I@EF]f@uCBQBI@KTqAP}@", - "length": 32 - }, - { - "points": "qmytGfgxkV@IDS@GTqABSBKn@sDBQf@qC", - "length": 10 - } - ], - "alerts": [ - { - "effectiveStartDate": 1591959600000, - "alertDescriptionText": "A scarf, bandanna or fabric mask works, as long as it covers your nose and mouth.", - "alertUrl": "http://trimet.org/health" - }, - { - "effectiveStartDate": 1585876166000, - "alertDescriptionText": "Due to COVID-19 service reductions, beginning Sunday, April 5th, on weekdays, trains will run every 15 minutes throughout most of the day. On weekends will run on Sunday schedule. For arrivals and trip planning, see:", - "alertUrl": "http://trimet.org/reducedservice" - }, - { - "effectiveStartDate": 1572827580000, - "alertDescriptionText": "The east elevators (zoo side) at Washington Park MAX Station are closed for improvements until early May. For access between platform and ground levels, use the west elevators (world forestry side). ", - "alertUrl": "https://news.trimet.org/2019/08/trimet-to-replace-elevators-at-the-deepest-transit-station-in-north-america/" - }, - { - "effectiveStartDate": 1591965804000, - "alertDescriptionText": "MAX Blue and Red lines disrupted due to police activity near Goose Hollow/SW Jefferson. Shuttle buses serving stations between Library/SW 9th and Galleria/SW 10th and Sunset Transit Center. Expect delays.", - "alertUrl": "http://trimet.org/alerts/" - } - ], - "routeLongName": "MAX Blue Line", - "rentedBike": false, - "rentedCar": false, - "rentedVehicle": false, - "hailedCar": false, - "flexDrtAdvanceBookMin": 0.0, - "duration": 240.0, - "transitLeg": true, - "intermediateStops": [ - { - "name": "Library/SW 9th Ave MAX Station", - "stopId": "TriMet:8333", - "stopCode": "8333", - "lon": -122.68162, - "lat": 45.51916, - "arrival": 1591717940000, - "departure": 1591717975000, - "zoneId": "R", - "stopIndex": 19, - "stopSequence": 20, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - } - ], - "steps": [] - }, - { - "startTime": 1591718036000, - "endTime": 1591718324000, - "departureDelay": 0, - "arrivalDelay": 0, - "realTime": false, - "distance": 418.19, - "pathway": false, - "mode": "WALK", - "route": "", - "agencyTimeZoneOffset": -25200000, - "interlineWithPreviousLeg": false, - "from": { - "name": "Pioneer Square South MAX Station", - "stopId": "TriMet:8334", - "stopCode": "8334", - "lon": -122.679145, - "lat": 45.518496, - "arrival": 1591718035000, - "departure": 1591718036000, - "zoneId": "R", - "stopIndex": 20, - "stopSequence": 21, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - "to": { - "name": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", - "lon": -122.67681483620306, - "lat": 45.51639151281627, - "arrival": 1591718324000, - "orig": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", - "vertexType": "NORMAL" - }, - "legGeometry": { - "points": "uiytGrwwkVDQBODBDB@G@Kn@iDBO@KLF|Ap@FBNHLF|Ar@FDNFBOh@{CD[FWb@eC", - "length": 23 - }, - "rentedBike": false, - "rentedCar": false, - "rentedVehicle": false, - "hailedCar": false, - "flexDrtAdvanceBookMin": 0.0, - "duration": 288.0, - "transitLeg": false, - "intermediateStops": [], - "steps": [ - { - "distance": 7.715, - "relativeDirection": "DEPART", - "streetName": "Pioneer Courthouse Sq (pedestrian street)", - "absoluteDirection": "EAST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.67913709381972, - "lat": 45.51851024632791, - "elevation": [] - }, - { - "distance": 6.217, - "relativeDirection": "CONTINUE", - "streetName": "path", - "absoluteDirection": "EAST", - "stayOn": false, - "area": false, - "bogusName": true, - "lon": -122.6790448, - "lat": 45.5184851, - "elevation": [ - { - "first": 0.0, - "second": 15.172842393406283 - }, - { - "first": 6.22, - "second": 15.002842393406283 - } - ] - }, - { - "distance": 7.252, - "relativeDirection": "RIGHT", - "streetName": "SW 6th Ave", - "absoluteDirection": "SOUTH", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6789697, - "lat": 45.5184662, - "elevation": [ - { - "first": 0.0, - "second": 15.002842393406283 - }, - { - "first": 7.25, - "second": 15.112842393406282 - } - ] - }, - { - "distance": 90.85300000000001, - "relativeDirection": "LEFT", - "streetName": "SW Yamhill St", - "absoluteDirection": "EAST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6790039, - "lat": 45.5184056, - "elevation": [ - { - "first": 0.0, - "second": 15.112842393406282 - }, - { - "first": 8.41, - "second": 15.092842393406283 - }, - { - "first": 8.407, - "second": 15.092842393406283 - }, - { - "first": 18.407, - "second": 14.942842393406282 - }, - { - "first": 28.407, - "second": 14.672842393406283 - }, - { - "first": 38.407, - "second": 14.392842393406283 - }, - { - "first": 48.407, - "second": 14.192842393406282 - }, - { - "first": 58.407, - "second": 13.772842393406282 - }, - { - "first": 68.407, - "second": 13.452842393406282 - }, - { - "first": 79.467, - "second": 13.182842393406283 - }, - { - "first": 79.465, - "second": 13.182842393406283 - }, - { - "first": 90.855, - "second": 12.942842393406282 - } - ] - }, - { - "distance": 156.99599999999998, - "relativeDirection": "RIGHT", - "streetName": "SW 5th Ave", - "absoluteDirection": "SOUTH", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.677916, - "lat": 45.5181117, - "elevation": [] - }, - { - "distance": 149.157, - "relativeDirection": "LEFT", - "streetName": "SW Salmon St", - "absoluteDirection": "EAST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6786441, - "lat": 45.5167953, - "elevation": [ - { - "first": 0.0, - "second": 17.552842393406284 - }, - { - "first": 10.0, - "second": 17.292842393406282 - }, - { - "first": 20.0, - "second": 16.962842393406284 - }, - { - "first": 30.0, - "second": 16.642842393406283 - }, - { - "first": 40.0, - "second": 16.162842393406283 - }, - { - "first": 50.0, - "second": 15.712842393406282 - }, - { - "first": 60.0, - "second": 15.122842393406282 - }, - { - "first": 70.0, - "second": 15.062842393406282 - }, - { - "first": 83.14, - "second": 14.982842393406283 - }, - { - "first": 83.144, - "second": 14.982842393406283 - }, - { - "first": 93.01400000000001, - "second": 14.762842393406283 - }, - { - "first": 93.01700000000001, - "second": 14.762842393406283 - }, - { - "first": 103.01700000000001, - "second": 14.502842393406283 - }, - { - "first": 113.01700000000001, - "second": 14.272842393406282 - }, - { - "first": 123.01700000000001, - "second": 13.872842393406282 - }, - { - "first": 133.017, - "second": 13.632842393406282 - }, - { - "first": 143.017, - "second": 13.412842393406283 - }, - { - "first": 149.157, - "second": 13.152842393406281 - } - ] - } - ] - } - ], - "tooSloped": false - }, - { - "duration": 1242, - "startTime": 1591717148000, - "endTime": 1591718390000, - "walkTime": 820, - "transitTime": 420, - "waitingTime": 2, - "walkDistance": 1297.4104029783646, - "walkLimitExceeded": false, - "elevationLost": 28.980000000000004, - "elevationGained": 1.1200000000000003, - "transfers": 0, - "fare": { - "fare": { - "regular": { - "currency": { - "symbol": "$", - "currency": "USD", - "defaultFractionDigits": 2, - "currencyCode": "USD" - }, - "cents": 200 - } - }, - "details": { - "regular": [ - { - "fareId": "TriMet:SC", - "price": { - "currency": { - "symbol": "$", - "currency": "USD", - "defaultFractionDigits": 2, - "currencyCode": "USD" - }, - "cents": 200 - }, - "routes": [ - "TriMet:195" - ] - } - ] - } - }, - "legs": [ - { - "startTime": 1591717148000, - "endTime": 1591717499000, - "departureDelay": 0, - "arrivalDelay": 0, - "realTime": false, - "distance": 578.6750000000001, - "pathway": false, - "mode": "WALK", - "route": "", - "agencyTimeZoneOffset": -25200000, - "interlineWithPreviousLeg": false, - "from": { - "name": "1709 NW Irving St, Portland 97209", - "lon": -122.68865964147231, - "lat": 45.527817334203, - "departure": 1591717148000, - "orig": "1709 NW Irving St, Portland 97209", - "vertexType": "NORMAL" - }, - "to": { - "name": "NW 11th & Johnson", - "stopId": "TriMet:10753", - "stopCode": "10753", - "lon": -122.682374, - "lat": 45.528742, - "arrival": 1591717499000, - "departure": 1591717500000, - "zoneId": "B", - "stopIndex": 1, - "stopSequence": 2, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - "legGeometry": { - "points": "}c{tGdsykVAu@qBB[@CiECkE???e@AoCAS?Y?kAAsA?Q?QAeD?QASAeDMA?G", - "length": 21 - }, - "rentedBike": false, - "rentedCar": false, - "rentedVehicle": false, - "hailedCar": false, - "flexDrtAdvanceBookMin": 0.0, - "duration": 351.0, - "transitLeg": false, - "intermediateStops": [], - "steps": [ - { - "distance": 20.794, - "relativeDirection": "DEPART", - "streetName": "NW Irving St", - "absoluteDirection": "EAST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.68866035123024, - "lat": 45.527836027296765, - "elevation": [ - { - "first": 0.0, - "second": 20.682842393406283 - }, - { - "first": 10.0, - "second": 20.452842393406282 - }, - { - "first": 20.0, - "second": 20.24284239340628 - }, - { - "first": 20.79, - "second": 20.22284239340628 - } - ] - }, - { - "distance": 79.476, - "relativeDirection": "LEFT", - "streetName": "NW 17th Ave", - "absoluteDirection": "NORTH", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6883935, - "lat": 45.527841, - "elevation": [ - { - "first": 0.0, - "second": 18.682842393406283 - }, - { - "first": 10.0, - "second": 18.612842393406282 - }, - { - "first": 20.0, - "second": 18.47284239340628 - }, - { - "first": 30.0, - "second": 18.272842393406282 - }, - { - "first": 40.0, - "second": 18.08284239340628 - }, - { - "first": 50.0, - "second": 17.90284239340628 - }, - { - "first": 63.93, - "second": 17.632842393406282 - }, - { - "first": 63.928, - "second": 17.632842393406282 - }, - { - "first": 73.928, - "second": 17.412842393406283 - }, - { - "first": 79.478, - "second": 17.362842393406282 - } - ] - }, - { - "distance": 467.456, - "relativeDirection": "RIGHT", - "streetName": "NW Johnson St", - "absoluteDirection": "EAST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6884207, - "lat": 45.5285555, - "elevation": [ - { - "first": 0.0, - "second": 17.362842393406282 - }, - { - "first": 10.0, - "second": 17.122842393406284 - }, - { - "first": 20.0, - "second": 17.002842393406283 - }, - { - "first": 30.0, - "second": 16.632842393406282 - }, - { - "first": 40.0, - "second": 16.372842393406284 - }, - { - "first": 50.0, - "second": 16.15284239340628 - }, - { - "first": 60.0, - "second": 15.932842393406283 - }, - { - "first": 70.0, - "second": 15.692842393406282 - }, - { - "first": 78.6, - "second": 15.642842393406283 - }, - { - "first": 78.602, - "second": 15.642842393406283 - }, - { - "first": 88.602, - "second": 15.662842393406283 - }, - { - "first": 98.602, - "second": 15.952842393406282 - }, - { - "first": 108.602, - "second": 16.112842393406282 - }, - { - "first": 118.602, - "second": 16.072842393406283 - }, - { - "first": 128.602, - "second": 15.942842393406282 - }, - { - "first": 138.602, - "second": 15.492842393406281 - }, - { - "first": 148.602, - "second": 14.812842393406282 - }, - { - "first": 157.762, - "second": 14.452842393406282 - }, - { - "first": 157.757, - "second": 14.452842393406282 - }, - { - "first": 167.757, - "second": 14.222842393406282 - }, - { - "first": 177.757, - "second": 14.022842393406282 - }, - { - "first": 187.757, - "second": 13.782842393406282 - }, - { - "first": 197.757, - "second": 13.582842393406281 - }, - { - "first": 207.757, - "second": 13.422842393406283 - }, - { - "first": 217.757, - "second": 13.102842393406283 - }, - { - "first": 227.757, - "second": 12.842842393406283 - }, - { - "first": 236.787, - "second": 12.762842393406283 - }, - { - "first": 236.78, - "second": 12.762842393406283 - }, - { - "first": 246.78, - "second": 12.462842393406284 - }, - { - "first": 256.78, - "second": 12.312842393406282 - }, - { - "first": 266.78, - "second": 12.132842393406282 - }, - { - "first": 276.35, - "second": 11.912842393406283 - }, - { - "first": 276.349, - "second": 11.912842393406283 - }, - { - "first": 286.349, - "second": 11.632842393406282 - }, - { - "first": 296.349, - "second": 11.462842393406282 - }, - { - "first": 306.349, - "second": 11.182842393406283 - }, - { - "first": 315.839, - "second": 10.942842393406282 - }, - { - "first": 315.841, - "second": 10.942842393406282 - }, - { - "first": 325.841, - "second": 10.892842393406282 - }, - { - "first": 335.841, - "second": 10.692842393406282 - }, - { - "first": 345.841, - "second": 10.582842393406283 - }, - { - "first": 355.841, - "second": 10.382842393406282 - }, - { - "first": 365.841, - "second": 10.112842393406282 - }, - { - "first": 375.841, - "second": 9.802842393406282 - }, - { - "first": 385.841, - "second": 9.522842393406282 - }, - { - "first": 395.041, - "second": 9.362842393406282 - }, - { - "first": 395.038, - "second": 9.362842393406282 - }, - { - "first": 405.038, - "second": 9.432842393406283 - }, - { - "first": 415.038, - "second": 9.632842393406282 - }, - { - "first": 425.038, - "second": 9.832842393406283 - }, - { - "first": 435.038, - "second": 9.882842393406282 - }, - { - "first": 445.038, - "second": 9.912842393406283 - }, - { - "first": 455.038, - "second": 9.872842393406282 - }, - { - "first": 467.458, - "second": 9.702842393406282 - } - ] - }, - { - "distance": 7.603, - "relativeDirection": "LEFT", - "streetName": "path", - "absoluteDirection": "NORTH", - "stayOn": false, - "area": false, - "bogusName": true, - "lon": -122.6824215, - "lat": 45.5286553, - "elevation": [ - { - "first": 0.0, - "second": 9.702842393406282 - }, - { - "first": 7.6, - "second": 9.642842393406282 - } - ] - }, - { - "distance": 3.346, - "relativeDirection": "RIGHT", - "streetName": "path", - "absoluteDirection": "EAST", - "stayOn": true, - "area": false, - "bogusName": true, - "lon": -122.6824167, - "lat": 45.5287236, - "elevation": [] - } - ] - }, - { - "startTime": 1591717500000, - "endTime": 1591717920000, - "departureDelay": 0, - "arrivalDelay": 0, - "realTime": false, - "distance": 1103.6887432320718, - "pathway": false, - "mode": "TRAM", - "route": "Portland Streetcar - B Loop", - "agencyName": "Portland Streetcar", - "agencyUrl": "https://portlandstreetcar.org/", - "agencyTimeZoneOffset": -25200000, - "routeColor": "0093B2", - "routeType": 0, - "routeId": "TriMet:195", - "routeTextColor": "FFFFFF", - "interlineWithPreviousLeg": false, - "tripBlockId": "58", - "headsign": "Lloyd via OMSI", - "agencyId": "PSC", - "tripId": "TriMet:9945177", - "serviceDate": "20200609", - "from": { - "name": "NW 11th & Johnson", - "stopId": "TriMet:10753", - "stopCode": "10753", - "lon": -122.682374, - "lat": 45.528742, - "arrival": 1591717499000, - "departure": 1591717500000, - "zoneId": "B", - "stopIndex": 1, - "stopSequence": 2, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - "to": { - "name": "SW 11th & Taylor", - "stopId": "TriMet:9633", - "stopCode": "9633", - "lon": -122.683873, - "lat": 45.519059, - "arrival": 1591717920000, - "departure": 1591717921000, - "zoneId": "B", - "stopIndex": 5, - "stopSequence": 6, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - "legGeometry": { - "points": "si{tGrkxkVP?dACtEGZ?~ACB?L?J?`CE~BCLAn@?~ACvBCFAL?J?rBCL?vBCLAF?D?D@B@lBx@LF`Bt@HBLFHDtB~@DB@?DBFB`Br@DBFB??FBlBz@", - "length": 43 - }, - "interStopGeometry": [ - { - "points": "si{tGrkxkVP?dACtEGZ?~AC", - "length": 6 - }, - { - "points": "i|ztGbkxkVB?L?J?`CE~BCLAn@?~ACvBC", - "length": 10 - }, - { - "points": "sjztGnjxkVFAL?J?rBCL?vBCLAF?D?D@B@lBx@LF`Bt@", - "length": 15 - }, - { - "points": "syytG~mxkVHBLFHDtB~@DB@?DBFB`Br@DBFB??FBlBz@", - "length": 15 - } - ], - "alerts": [ - { - "effectiveStartDate": 1591959600000, - "alertDescriptionText": "A scarf, bandanna or fabric mask works, as long as it covers your nose and mouth.", - "alertUrl": "http://trimet.org/health" - }, - { - "effectiveStartDate": 1585093573000, - "alertDescriptionText": "Streetcar has reduced regular weekday service to every 20 minutes between about 5:30 a.m. and about 11:30 p.m. For more info:", - "alertUrl": "https://portlandstreetcar.org/news/2020/03/streetcar-service-will-reduce-to-20-minute-headways-beginning-march-24" - } - ], - "routeLongName": "Portland Streetcar - B Loop", - "rentedBike": false, - "rentedCar": false, - "rentedVehicle": false, - "hailedCar": false, - "flexDrtAdvanceBookMin": 0.0, - "duration": 420.0, - "transitLeg": true, - "intermediateStops": [ - { - "name": "NW 11th & Glisan", - "stopId": "TriMet:10754", - "stopCode": "10754", - "lon": -122.682297, - "lat": 45.526605, - "arrival": 1591717560000, - "departure": 1591717560000, - "zoneId": "B", - "stopIndex": 2, - "stopSequence": 3, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - { - "name": "NW 11th & Couch", - "stopId": "TriMet:10756", - "stopCode": "10756", - "lon": -122.682223, - "lat": 45.523784, - "arrival": 1591717740000, - "departure": 1591717740000, - "zoneId": "B", - "stopIndex": 3, - "stopSequence": 4, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - { - "name": "SW 11th & Alder", - "stopId": "TriMet:9600", - "stopCode": "9600", - "lon": -122.682819, - "lat": 45.521094, - "arrival": 1591717860000, - "departure": 1591717860000, - "zoneId": "B", - "stopIndex": 4, - "stopSequence": 5, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - } - ], - "steps": [] - }, - { - "startTime": 1591717921000, - "endTime": 1591718390000, - "departureDelay": 0, - "arrivalDelay": 0, - "realTime": false, - "distance": 718.4660000000001, - "pathway": false, - "mode": "WALK", - "route": "", - "agencyTimeZoneOffset": -25200000, - "interlineWithPreviousLeg": false, - "from": { - "name": "SW 11th & Taylor", - "stopId": "TriMet:9633", - "stopCode": "9633", - "lon": -122.683873, - "lat": 45.519059, - "arrival": 1591717920000, - "departure": 1591717921000, - "zoneId": "B", - "stopIndex": 5, - "stopSequence": 6, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - "to": { - "name": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", - "lon": -122.67681483620306, - "lat": 45.51639151281627, - "arrival": 1591718390000, - "orig": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", - "vertexType": "NORMAL" - }, - "legGeometry": { - "points": "cmytGfuxkVAHD@JFBQ@GDSh@_D??FU@IBMX_BRiA@K?AVqA@KBOFY`@aCFYBSh@}CBO@CDU^wBHc@F_@@CLF|Ar@FDNFBOh@{CD[FWb@eC", - "length": 40 - }, - "rentedBike": false, - "rentedCar": false, - "rentedVehicle": false, - "hailedCar": false, - "flexDrtAdvanceBookMin": 0.0, - "duration": 469.0, - "transitLeg": false, - "intermediateStops": [], - "steps": [ - { - "distance": 14.561, - "relativeDirection": "DEPART", - "streetName": "path", - "absoluteDirection": "WEST", - "stayOn": false, - "area": false, - "bogusName": true, - "lon": -122.6838786, - "lat": 45.5190659, - "elevation": [ - { - "first": 0.0, - "second": 29.782842393406284 - }, - { - "first": 3.48, - "second": 29.83284239340628 - }, - { - "first": 7.5809999999999995, - "second": 29.83284239340628 - }, - { - "first": 11.690999999999999, - "second": 29.862842393406282 - } - ] - }, - { - "distance": 475.42999999999995, - "relativeDirection": "LEFT", - "streetName": "SW Taylor St", - "absoluteDirection": "EAST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6839744, - "lat": 45.5189848, - "elevation": [ - { - "first": 0.0, - "second": 29.87284239340628 - }, - { - "first": 10.39, - "second": 29.87284239340628 - }, - { - "first": 10.386, - "second": 29.87284239340628 - }, - { - "first": 18.735999999999997, - "second": 29.662842393406283 - }, - { - "first": 18.737000000000002, - "second": 29.662842393406283 - }, - { - "first": 28.737000000000002, - "second": 29.252842393406283 - }, - { - "first": 38.737, - "second": 28.97284239340628 - }, - { - "first": 48.737, - "second": 28.632842393406282 - }, - { - "first": 58.737, - "second": 28.05284239340628 - }, - { - "first": 68.737, - "second": 27.62284239340628 - }, - { - "first": 78.737, - "second": 27.202842393406282 - }, - { - "first": 85.84700000000001, - "second": 26.952842393406282 - }, - { - "first": 85.84299999999999, - "second": 26.952842393406282 - }, - { - "first": 95.353, - "second": 26.932842393406283 - }, - { - "first": 95.35099999999998, - "second": 26.932842393406283 - }, - { - "first": 105.13099999999999, - "second": 26.65284239340628 - }, - { - "first": 105.12999999999998, - "second": 26.65284239340628 - }, - { - "first": 115.12999999999998, - "second": 26.22284239340628 - }, - { - "first": 125.12999999999998, - "second": 25.87284239340628 - }, - { - "first": 135.13, - "second": 25.642842393406283 - }, - { - "first": 145.48999999999998, - "second": 25.15284239340628 - }, - { - "first": 145.486, - "second": 25.15284239340628 - }, - { - "first": 155.486, - "second": 24.90284239340628 - }, - { - "first": 165.486, - "second": 24.602842393406284 - }, - { - "first": 176.05599999999998, - "second": 24.342842393406283 - }, - { - "first": 176.051, - "second": 24.342842393406283 - }, - { - "first": 181.18099999999998, - "second": 24.302842393406284 - }, - { - "first": 181.18099999999998, - "second": 24.302842393406284 - }, - { - "first": 191.18099999999998, - "second": 24.08284239340628 - }, - { - "first": 201.18099999999998, - "second": 23.752842393406283 - }, - { - "first": 216.081, - "second": 23.202842393406282 - }, - { - "first": 216.077, - "second": 23.202842393406282 - }, - { - "first": 221.187, - "second": 23.08284239340628 - }, - { - "first": 221.182, - "second": 23.08284239340628 - }, - { - "first": 231.182, - "second": 22.642842393406283 - }, - { - "first": 238.742, - "second": 22.17284239340628 - }, - { - "first": 238.742, - "second": 22.17284239340628 - }, - { - "first": 248.742, - "second": 21.65284239340628 - }, - { - "first": 258.74199999999996, - "second": 21.26284239340628 - }, - { - "first": 268.74199999999996, - "second": 20.682842393406283 - }, - { - "first": 278.74199999999996, - "second": 20.272842393406282 - }, - { - "first": 288.74199999999996, - "second": 19.74284239340628 - }, - { - "first": 303.502, - "second": 19.622842393406284 - }, - { - "first": 303.505, - "second": 19.622842393406284 - }, - { - "first": 313.505, - "second": 19.342842393406283 - }, - { - "first": 323.505, - "second": 18.962842393406284 - }, - { - "first": 333.505, - "second": 18.802842393406284 - }, - { - "first": 343.505, - "second": 18.532842393406284 - }, - { - "first": 353.505, - "second": 18.252842393406283 - }, - { - "first": 363.505, - "second": 18.002842393406283 - }, - { - "first": 373.505, - "second": 17.642842393406283 - }, - { - "first": 384.315, - "second": 17.522842393406282 - }, - { - "first": 384.311, - "second": 17.522842393406282 - }, - { - "first": 394.311, - "second": 17.532842393406284 - }, - { - "first": 404.311, - "second": 17.292842393406282 - }, - { - "first": 414.311, - "second": 17.112842393406282 - }, - { - "first": 424.311, - "second": 16.90284239340628 - }, - { - "first": 434.311, - "second": 16.56284239340628 - }, - { - "first": 444.311, - "second": 16.392842393406283 - }, - { - "first": 454.311, - "second": 16.162842393406283 - }, - { - "first": 464.311, - "second": 15.902842393406281 - }, - { - "first": 475.431, - "second": 15.822842393406283 - } - ] - }, - { - "distance": 79.318, - "relativeDirection": "RIGHT", - "streetName": "SW 5th Ave", - "absoluteDirection": "SOUTH", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6782737, - "lat": 45.5174597, - "elevation": [] - }, - { - "distance": 149.157, - "relativeDirection": "LEFT", - "streetName": "SW Salmon St", - "absoluteDirection": "EAST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6786441, - "lat": 45.5167953, - "elevation": [ - { - "first": 0.0, - "second": 17.552842393406284 - }, - { - "first": 10.0, - "second": 17.292842393406282 - }, - { - "first": 20.0, - "second": 16.962842393406284 - }, - { - "first": 30.0, - "second": 16.642842393406283 - }, - { - "first": 40.0, - "second": 16.162842393406283 - }, - { - "first": 50.0, - "second": 15.712842393406282 - }, - { - "first": 60.0, - "second": 15.122842393406282 - }, - { - "first": 70.0, - "second": 15.062842393406282 - }, - { - "first": 83.14, - "second": 14.982842393406283 - }, - { - "first": 83.144, - "second": 14.982842393406283 - }, - { - "first": 93.01400000000001, - "second": 14.762842393406283 - }, - { - "first": 93.01700000000001, - "second": 14.762842393406283 - }, - { - "first": 103.01700000000001, - "second": 14.502842393406283 - }, - { - "first": 113.01700000000001, - "second": 14.272842393406282 - }, - { - "first": 123.01700000000001, - "second": 13.872842393406282 - }, - { - "first": 133.017, - "second": 13.632842393406282 - }, - { - "first": 143.017, - "second": 13.412842393406283 - }, - { - "first": 149.157, - "second": 13.152842393406281 - } - ] - } - ] - } - ], - "tooSloped": false + "data": { + "plan": { + "date": 1591716900000, + "from": { + "name": "1709 NW Irving St, Portland 97209", + "lon": -122.68865964147231, + "lat": 45.527817334203, + "orig": "1709 NW Irving St, Portland 97209", + "vertexType": "NORMAL" + }, + "to": { + "name": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", + "lon": -122.67681483620306, + "lat": 45.51639151281627, + "orig": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", + "vertexType": "NORMAL" }, - { - "duration": 1036, - "startTime": 1591717825000, - "endTime": 1591718861000, - "walkTime": 674, - "transitTime": 360, - "waitingTime": 2, - "walkDistance": 966.7338656666136, - "walkLimitExceeded": false, - "elevationLost": 8.23, - "elevationGained": 13.169999999999996, - "transfers": 0, - "fare": { + "itineraries": [ + { + "duration": 1114, + "startTime": 1591717210000, + "endTime": 1591718324000, + "walkTime": 872, + "transitTime": 240, + "waitingTime": 2, + "walkDistance": 1256.0514029764959, + "walkLimitExceeded": false, + "elevationLost": 10.41, + "elevationGained": 15.05, + "transfers": 0, "fare": { - "regular": { - "currency": { - "symbol": "$", - "currency": "USD", - "defaultFractionDigits": 2, - "currencyCode": "USD" - }, - "cents": 250 - } - }, - "details": { - "regular": [ - { - "fareId": "TriMet:B", - "price": { - "currency": { - "symbol": "$", - "currency": "USD", - "defaultFractionDigits": 2, - "currencyCode": "USD" - }, - "cents": 250 + "fare": { + "regular": { + "currency": { + "symbol": "$", + "currency": "USD", + "defaultFractionDigits": 2, + "currencyCode": "USD" }, - "routes": [ - "TriMet:15" - ] + "cents": 250 } - ] - } - }, - "legs": [ - { - "startTime": 1591717825000, - "endTime": 1591718399000, - "departureDelay": 0, - "arrivalDelay": 0, - "realTime": false, - "distance": 802.1549999999999, - "pathway": false, - "mode": "WALK", - "route": "", - "agencyTimeZoneOffset": -25200000, - "interlineWithPreviousLeg": false, - "from": { - "name": "1709 NW Irving St, Portland 97209", - "lon": -122.68865964147231, - "lat": 45.527817334203, - "departure": 1591717825000, - "orig": "1709 NW Irving St, Portland 97209", - "vertexType": "NORMAL" - }, - "to": { - "name": "SW 18th & Morrison", - "stopId": "TriMet:6911", - "stopCode": "6911", - "lon": -122.690453, - "lat": 45.52188, - "arrival": 1591718399000, - "departure": 1591718400000, - "zoneId": "B", - "stopIndex": 21, - "stopSequence": 22, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" }, - "legGeometry": { - "points": "}c{tGdsykVAu@x@AJ?v@AP?nCE\\?r@Ax@C`BADAd@?lCEp@AdAAV??NBvDl@?@?F?J?J?H?H?B@BB@DBDBFBHLZBDHLHLHHFDHFj@XHPTHJDLRCP?BB?B@B@`@N", - "length": 50 - }, - "rentedBike": false, - "rentedCar": false, - "rentedVehicle": false, - "hailedCar": false, - "flexDrtAdvanceBookMin": 0.0, - "duration": 574.0, - "transitLeg": false, - "intermediateStops": [], - "steps": [ - { - "distance": 20.794, - "relativeDirection": "DEPART", - "streetName": "NW Irving St", - "absoluteDirection": "EAST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.68866035123024, - "lat": 45.527836027296765, - "elevation": [ - { - "first": 0.0, - "second": 20.682842393406283 - }, - { - "first": 10.0, - "second": 20.452842393406282 - }, - { - "first": 20.0, - "second": 20.24284239340628 - }, - { - "first": 20.79, - "second": 20.22284239340628 - } - ] - }, - { - "distance": 475.08199999999994, - "relativeDirection": "RIGHT", - "streetName": "NW 17th Ave", - "absoluteDirection": "SOUTH", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6883935, - "lat": 45.527841, - "elevation": [ - { - "first": 0.0, - "second": 18.682842393406283 - }, - { - "first": 10.0, - "second": 18.802842393406284 - }, - { - "first": 20.0, - "second": 19.01284239340628 - }, - { - "first": 31.29, - "second": 19.24284239340628 - }, - { - "first": 31.294, - "second": 19.24284239340628 - }, - { - "first": 39.024, - "second": 19.40284239340628 - }, - { - "first": 39.023, - "second": 19.40284239340628 - }, - { - "first": 49.023, - "second": 19.60284239340628 - }, - { - "first": 59.023, - "second": 19.85284239340628 - }, - { - "first": 69.023, - "second": 19.97284239340628 - }, - { - "first": 79.273, - "second": 20.032842393406284 - }, - { - "first": 79.272, - "second": 20.032842393406284 - }, - { - "first": 89.272, - "second": 20.26284239340628 - }, - { - "first": 99.272, - "second": 20.572842393406283 - }, - { - "first": 109.272, - "second": 20.872842393406284 - }, - { - "first": 119.272, - "second": 21.112842393406282 - }, - { - "first": 129.272, - "second": 21.412842393406283 - }, - { - "first": 139.272, - "second": 21.69284239340628 - }, - { - "first": 149.272, - "second": 22.01284239340628 - }, - { - "first": 159.322, - "second": 22.002842393406283 - }, - { - "first": 159.323, - "second": 22.002842393406283 - }, - { - "first": 169.323, - "second": 22.17284239340628 - }, - { - "first": 176.933, - "second": 22.352842393406284 - }, - { - "first": 176.93, - "second": 22.352842393406284 - }, - { - "first": 186.93, - "second": 22.58284239340628 - }, - { - "first": 196.93, - "second": 22.792842393406282 - }, - { - "first": 205.41, - "second": 22.952842393406282 - }, - { - "first": 205.41, - "second": 22.952842393406282 - }, - { - "first": 215.41, - "second": 23.15284239340628 - }, - { - "first": 225.41, - "second": 23.302842393406284 - }, - { - "first": 237.51, - "second": 23.42284239340628 - }, - { - "first": 237.505, - "second": 23.42284239340628 - }, - { - "first": 247.505, - "second": 23.502842393406283 - }, - { - "first": 257.505, - "second": 23.632842393406282 - }, - { - "first": 267.505, - "second": 23.76284239340628 - }, - { - "first": 277.505, - "second": 23.90284239340628 - }, - { - "first": 292.145, - "second": 24.092842393406283 - }, - { - "first": 292.143, - "second": 24.092842393406283 - }, - { - "first": 295.53299999999996, - "second": 24.132842393406282 - }, - { - "first": 295.53499999999997, - "second": 24.132842393406282 - }, - { - "first": 305.53499999999997, - "second": 24.432842393406283 - }, - { - "first": 316.635, - "second": 24.392842393406283 - }, - { - "first": 316.63599999999997, - "second": 24.392842393406283 - }, - { - "first": 326.63599999999997, - "second": 24.522842393406282 - }, - { - "first": 336.63599999999997, - "second": 24.792842393406282 - }, - { - "first": 346.63599999999997, - "second": 25.052842393406284 - }, - { - "first": 356.63599999999997, - "second": 25.31284239340628 - }, - { - "first": 366.63599999999997, - "second": 25.602842393406284 - }, - { - "first": 376.63599999999997, - "second": 25.842842393406283 - }, - { - "first": 386.63599999999997, - "second": 25.982842393406283 - }, - { - "first": 396.02599999999995, - "second": 26.01284239340628 - }, - { - "first": 396.03, - "second": 26.01284239340628 - }, - { - "first": 406.03, - "second": 26.042842393406282 - }, - { - "first": 416.03, - "second": 26.092842393406283 - }, - { - "first": 423.69, - "second": 26.15284239340628 - }, - { - "first": 423.686, - "second": 26.15284239340628 - }, - { - "first": 433.686, - "second": 26.252842393406283 - }, - { - "first": 443.686, - "second": 26.31284239340628 - }, - { - "first": 453.686, - "second": 26.362842393406282 - }, - { - "first": 461.936, - "second": 26.40284239340628 - }, - { - "first": 461.93699999999995, - "second": 26.40284239340628 - }, - { - "first": 475.08699999999993, - "second": 26.19284239340628 - } - ] + "details": { + "regular": [ + { + "fareId": "TriMet:R", + "price": { + "currency": { + "symbol": "$", + "currency": "USD", + "defaultFractionDigits": 2, + "currencyCode": "USD" + }, + "cents": 250 + }, + "routes": [ + "TriMet:100" + ] + } + ] + } + }, + "legs": [ + { + "startTime": 1591717210000, + "endTime": 1591717794000, + "departureDelay": 0, + "arrivalDelay": 0, + "realTime": false, + "distance": 837.5169999999998, + "pathway": false, + "mode": "WALK", + "route": "", + "agencyTimeZoneOffset": -25200000, + "interlineWithPreviousLeg": false, + "from": { + "name": "1709 NW Irving St, Portland 97209", + "lon": -122.68865964147231, + "lat": 45.527817334203, + "departure": 1591717210000, + "orig": "1709 NW Irving St, Portland 97209", + "vertexType": "NORMAL" }, - { - "distance": 78.621, - "relativeDirection": "RIGHT", - "streetName": "NW Couch St", - "absoluteDirection": "WEST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6882412, - "lat": 45.5235698, - "elevation": [ - { - "first": 0.0, - "second": 26.19284239340628 - }, - { - "first": 10.0, - "second": 26.352842393406284 - }, - { - "first": 20.0, - "second": 26.572842393406283 - }, - { - "first": 30.0, - "second": 26.802842393406284 - }, - { - "first": 40.0, - "second": 27.15284239340628 - }, - { - "first": 50.0, - "second": 27.322842393406283 - }, - { - "first": 60.0, - "second": 27.55284239340628 - }, - { - "first": 70.0, - "second": 27.80284239340628 - }, - { - "first": 78.62, - "second": 28.01284239340628 - } - ] + "to": { + "name": "Providence Park MAX Station", + "stopId": "TriMet:9758", + "stopCode": "9758", + "lon": -122.689886, + "lat": 45.521321, + "arrival": 1591717794000, + "departure": 1591717795000, + "zoneId": "R", + "stopIndex": 18, + "stopSequence": 19, + "vertexType": "TRANSIT", + "boardAlightType": "DEFAULT" }, - { - "distance": 66.44500000000001, - "relativeDirection": "LEFT", - "streetName": "NW 18th Ave", - "absoluteDirection": "SOUTH", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6892498, - "lat": 45.5235462, - "elevation": [ - { - "first": 0.0, - "second": 28.01284239340628 - }, - { - "first": 10.0, - "second": 27.37284239340628 - }, - { - "first": 20.0, - "second": 26.87284239340628 - }, - { - "first": 25.41, - "second": 26.65284239340628 - }, - { - "first": 25.411, - "second": 26.65284239340628 - }, - { - "first": 27.351000000000003, - "second": 26.56284239340628 - }, - { - "first": 27.346, - "second": 26.56284239340628 - }, - { - "first": 31.706, - "second": 26.37284239340628 - }, - { - "first": 31.705, - "second": 26.37284239340628 - }, - { - "first": 38.315, - "second": 26.132842393406282 - }, - { - "first": 38.31, - "second": 26.132842393406282 - }, - { - "first": 48.31, - "second": 25.90284239340628 - }, - { - "first": 55.33, - "second": 25.822842393406283 - }, - { - "first": 55.328, - "second": 25.822842393406283 - }, - { - "first": 66.44800000000001, - "second": 25.962842393406284 - } - ] + "legGeometry": { + "points": "}c{tGdsykVAu@x@AJ?v@AP?nCE\\?r@Ax@C`BADAd@?lCEp@AdAAV?JA`@?F?jACClAF@B?l@XNFpAj@f@TJDB@B@LFx@\\JDHBH@HBCPFBDBQ~@", + "length": 41 }, - { - "distance": 161.21300000000002, - "relativeDirection": "SLIGHTLY_RIGHT", - "streetName": "SW 18th Ave", - "absoluteDirection": "SOUTHWEST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6893346, - "lat": 45.5229721, - "elevation": [ - { - "first": 0.0, - "second": 25.962842393406284 - }, - { - "first": 10.0, - "second": 26.08284239340628 - }, - { - "first": 20.0, - "second": 26.022842393406282 - }, - { - "first": 32.85, - "second": 25.642842393406283 - }, - { - "first": 32.852, - "second": 25.642842393406283 - }, - { - "first": 42.852, - "second": 25.852842393406284 - }, - { - "first": 52.852, - "second": 26.142842393406283 - }, - { - "first": 62.852, - "second": 26.572842393406283 - }, - { - "first": 72.852, - "second": 27.022842393406282 - }, - { - "first": 82.852, - "second": 27.612842393406282 - }, - { - "first": 93.812, - "second": 28.092842393406283 - }, - { - "first": 93.811, - "second": 28.092842393406283 - }, - { - "first": 103.811, - "second": 28.47284239340628 - }, - { - "first": 114.351, - "second": 28.842842393406283 - }, - { - "first": 114.346, - "second": 28.842842393406283 - }, - { - "first": 125.05600000000001, - "second": 28.962842393406284 - }, - { - "first": 134.205, - "second": 28.962842393406284 - }, - { - "first": 141.47500000000002, - "second": 28.56284239340628 - }, - { - "first": 141.47, - "second": 28.56284239340628 - }, - { - "first": 143.36, - "second": 28.482842393406283 - } - ] - } - ] - }, - { - "startTime": 1591718400000, - "endTime": 1591718760000, - "departureDelay": 0, - "arrivalDelay": 0, - "realTime": false, - "distance": 1286.9349137891197, - "pathway": false, - "mode": "BUS", - "route": "15", - "agencyName": "TriMet", - "agencyUrl": "https://trimet.org/", - "agencyTimeZoneOffset": -25200000, - "routeType": 3, - "routeId": "TriMet:15", - "interlineWithPreviousLeg": false, - "tripBlockId": "1505", - "headsign": "Gateway TC", - "agencyId": "TRIMET", - "tripId": "TriMet:9932776", - "serviceDate": "20200609", - "from": { - "name": "SW 18th & Morrison", - "stopId": "TriMet:6911", - "stopCode": "6911", - "lon": -122.690453, - "lat": 45.52188, - "arrival": 1591718399000, - "departure": 1591718400000, - "zoneId": "B", - "stopIndex": 21, - "stopSequence": 22, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - "to": { - "name": "SW Salmon & 5th", - "stopId": "TriMet:5020", - "stopCode": "5020", - "lon": -122.678854, - "lat": 45.516794, - "arrival": 1591718760000, - "departure": 1591718761000, - "zoneId": "B", - "stopIndex": 27, - "stopSequence": 28, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - "legGeometry": { - "points": "w~ytGx}ykVRHh@ZXNhBv@JFJFRJFDFFFH@@`@Xp@h@RNBMBQBMBO?I@I?O?u@?e@?A?S@WBMDY\\sBJe@Fc@@Ep@sDBOBSd@mCBKDSDWHe@XcBLs@n@mDBUFYDUFa@BKDSJm@He@@GJk@Hg@PeADUF[@IBMNy@He@RkAToAFYr@eEDSh@aDBK?ADWh@yC", - "length": 72 + "rentedBike": false, + "rentedCar": false, + "rentedVehicle": false, + "hailedCar": false, + "flexDrtAdvanceBookMin": 0.0, + "duration": 584.0, + "transitLeg": false, + "intermediateStops": [], + "steps": [ + { + "distance": 20.794, + "relativeDirection": "DEPART", + "streetName": "NW Irving St", + "absoluteDirection": "EAST", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.68866035123024, + "lat": 45.527836027296765, + "elevation": [ + { + "first": 0.0, + "second": 20.682842393406283 + }, + { + "first": 10.0, + "second": 20.452842393406282 + }, + { + "first": 20.0, + "second": 20.24284239340628 + }, + { + "first": 20.79, + "second": 20.22284239340628 + } + ] + }, + { + "distance": 547.395, + "relativeDirection": "RIGHT", + "streetName": "NW 17th Ave", + "absoluteDirection": "SOUTH", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.6883935, + "lat": 45.527841, + "elevation": [ + { + "first": 0.0, + "second": 18.682842393406283 + }, + { + "first": 10.0, + "second": 18.802842393406284 + }, + { + "first": 20.0, + "second": 19.01284239340628 + }, + { + "first": 31.29, + "second": 19.24284239340628 + }, + { + "first": 31.294, + "second": 19.24284239340628 + }, + { + "first": 39.024, + "second": 19.40284239340628 + }, + { + "first": 39.023, + "second": 19.40284239340628 + }, + { + "first": 49.023, + "second": 19.60284239340628 + }, + { + "first": 59.023, + "second": 19.85284239340628 + }, + { + "first": 69.023, + "second": 19.97284239340628 + }, + { + "first": 79.273, + "second": 20.032842393406284 + }, + { + "first": 79.272, + "second": 20.032842393406284 + }, + { + "first": 89.272, + "second": 20.26284239340628 + }, + { + "first": 99.272, + "second": 20.572842393406283 + }, + { + "first": 109.272, + "second": 20.872842393406284 + }, + { + "first": 119.272, + "second": 21.112842393406282 + }, + { + "first": 129.272, + "second": 21.412842393406283 + }, + { + "first": 139.272, + "second": 21.69284239340628 + }, + { + "first": 149.272, + "second": 22.01284239340628 + }, + { + "first": 159.322, + "second": 22.002842393406283 + }, + { + "first": 159.323, + "second": 22.002842393406283 + }, + { + "first": 169.323, + "second": 22.17284239340628 + }, + { + "first": 176.933, + "second": 22.352842393406284 + }, + { + "first": 176.93, + "second": 22.352842393406284 + }, + { + "first": 186.93, + "second": 22.58284239340628 + }, + { + "first": 196.93, + "second": 22.792842393406282 + }, + { + "first": 205.41, + "second": 22.952842393406282 + }, + { + "first": 205.41, + "second": 22.952842393406282 + }, + { + "first": 215.41, + "second": 23.15284239340628 + }, + { + "first": 225.41, + "second": 23.302842393406284 + }, + { + "first": 237.51, + "second": 23.42284239340628 + }, + { + "first": 237.505, + "second": 23.42284239340628 + }, + { + "first": 247.505, + "second": 23.502842393406283 + }, + { + "first": 257.505, + "second": 23.632842393406282 + }, + { + "first": 267.505, + "second": 23.76284239340628 + }, + { + "first": 277.505, + "second": 23.90284239340628 + }, + { + "first": 292.145, + "second": 24.092842393406283 + }, + { + "first": 292.143, + "second": 24.092842393406283 + }, + { + "first": 295.53299999999996, + "second": 24.132842393406282 + }, + { + "first": 295.53499999999997, + "second": 24.132842393406282 + }, + { + "first": 305.53499999999997, + "second": 24.432842393406283 + }, + { + "first": 316.635, + "second": 24.392842393406283 + }, + { + "first": 316.63599999999997, + "second": 24.392842393406283 + }, + { + "first": 326.63599999999997, + "second": 24.522842393406282 + }, + { + "first": 336.63599999999997, + "second": 24.792842393406282 + }, + { + "first": 346.63599999999997, + "second": 25.052842393406284 + }, + { + "first": 356.63599999999997, + "second": 25.31284239340628 + }, + { + "first": 366.63599999999997, + "second": 25.602842393406284 + }, + { + "first": 376.63599999999997, + "second": 25.842842393406283 + }, + { + "first": 386.63599999999997, + "second": 25.982842393406283 + }, + { + "first": 396.02599999999995, + "second": 26.01284239340628 + }, + { + "first": 396.03, + "second": 26.01284239340628 + }, + { + "first": 406.03, + "second": 26.042842393406282 + }, + { + "first": 416.03, + "second": 26.092842393406283 + }, + { + "first": 423.69, + "second": 26.15284239340628 + }, + { + "first": 423.686, + "second": 26.15284239340628 + }, + { + "first": 433.686, + "second": 26.252842393406283 + }, + { + "first": 443.686, + "second": 26.31284239340628 + }, + { + "first": 453.686, + "second": 26.362842393406282 + }, + { + "first": 461.936, + "second": 26.40284239340628 + }, + { + "first": 461.93699999999995, + "second": 26.40284239340628 + }, + { + "first": 475.08699999999993, + "second": 26.19284239340628 + }, + { + "first": 475.08199999999994, + "second": 26.19284239340628 + }, + { + "first": 485.08199999999994, + "second": 25.732842393406283 + }, + { + "first": 495.08199999999994, + "second": 25.142842393406283 + }, + { + "first": 501.5519999999999, + "second": 24.76284239340628 + }, + { + "first": 501.54999999999995, + "second": 24.76284239340628 + }, + { + "first": 505.17999999999995, + "second": 24.602842393406284 + }, + { + "first": 505.17699999999996, + "second": 24.602842393406284 + }, + { + "first": 515.1769999999999, + "second": 24.12284239340628 + }, + { + "first": 525.1769999999999, + "second": 23.62284239340628 + }, + { + "first": 535.1769999999999, + "second": 23.292842393406282 + }, + { + "first": 547.3969999999999, + "second": 23.51284239340628 + } + ] + }, + { + "distance": 30.847, + "relativeDirection": "RIGHT", + "streetName": "W Burnside St", + "absoluteDirection": "WEST", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.688213, + "lat": 45.5229198, + "elevation": [ + { + "first": 0.0, + "second": 23.51284239340628 + }, + { + "first": 10.0, + "second": 23.76284239340628 + }, + { + "first": 20.0, + "second": 24.052842393406284 + }, + { + "first": 30.85, + "second": 24.302842393406284 + } + ] + }, + { + "distance": 195.61100000000002, + "relativeDirection": "LEFT", + "streetName": "SW 17th Ave", + "absoluteDirection": "SOUTH", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.6886081, + "lat": 45.522938, + "elevation": [ + { + "first": 0.0, + "second": 24.302842393406284 + }, + { + "first": 10.0, + "second": 24.532842393406284 + }, + { + "first": 20.0, + "second": 25.182842393406283 + }, + { + "first": 30.0, + "second": 25.802842393406284 + }, + { + "first": 44.36, + "second": 26.032842393406284 + }, + { + "first": 44.347, + "second": 26.032842393406284 + }, + { + "first": 54.347, + "second": 26.452842393406282 + }, + { + "first": 64.34700000000001, + "second": 27.282842393406284 + }, + { + "first": 74.34700000000001, + "second": 27.952842393406282 + }, + { + "first": 84.34700000000001, + "second": 28.132842393406282 + }, + { + "first": 92.137, + "second": 28.292842393406282 + }, + { + "first": 92.13900000000001, + "second": 28.292842393406282 + }, + { + "first": 102.13900000000001, + "second": 28.80284239340628 + }, + { + "first": 112.13900000000001, + "second": 29.30284239340628 + }, + { + "first": 123.74900000000001, + "second": 29.822842393406283 + }, + { + "first": 123.75200000000001, + "second": 29.822842393406283 + }, + { + "first": 136.752, + "second": 30.352842393406284 + } + ] + }, + { + "distance": 7.173, + "relativeDirection": "RIGHT", + "streetName": "path", + "absoluteDirection": "WEST", + "stayOn": false, + "area": false, + "bogusName": true, + "lon": -122.689436, + "lat": 45.521281, + "elevation": [ + { + "first": 0.0, + "second": 32.14284239340628 + }, + { + "first": 7.17, + "second": 32.092842393406286 + } + ] + }, + { + "distance": 8.612, + "relativeDirection": "LEFT", + "streetName": "path", + "absoluteDirection": "SOUTH", + "stayOn": true, + "area": false, + "bogusName": true, + "lon": -122.6895213, + "lat": 45.5213053, + "elevation": [ + { + "first": 0.0, + "second": 32.092842393406286 + }, + { + "first": 8.61, + "second": 32.202842393406286 + } + ] + }, + { + "distance": 27.085, + "relativeDirection": "RIGHT", + "streetName": "Providence Park (path)", + "absoluteDirection": "WEST", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.6895617, + "lat": 45.5212332, + "elevation": [] + } + ] }, - "interStopGeometry": [ - { - "points": "w~ytGx}ykVRHh@ZXNhBv@JFJFRJFDFFFH@@`@Xp@h@RNBMBQBMBO?I@I?O?u@?e@?A?S@WBMDY\\sBJe@Fc@", - "length": 32 - }, - { - "points": "wpytG~vykV@Ep@sDBOBSd@mC", - "length": 6 + { + "startTime": 1591717795000, + "endTime": 1591718035000, + "departureDelay": 0, + "arrivalDelay": 0, + "realTime": false, + "distance": 893.8869820964361, + "pathway": false, + "mode": "TRAM", + "route": "MAX Blue Line", + "agencyName": "TriMet", + "agencyUrl": "https://trimet.org/", + "agencyTimeZoneOffset": -25200000, + "routeColor": "084C8D", + "routeType": 0, + "routeId": "TriMet:100", + "routeTextColor": "FFFFFF", + "interlineWithPreviousLeg": false, + "tripBlockId": "9004", + "headsign": "Gresham", + "agencyId": "TRIMET", + "tripId": "TriMet:9942889", + "serviceDate": "20200609", + "from": { + "name": "Providence Park MAX Station", + "stopId": "TriMet:9758", + "stopCode": "9758", + "lon": -122.689886, + "lat": 45.521321, + "arrival": 1591717794000, + "departure": 1591717795000, + "zoneId": "R", + "stopIndex": 18, + "stopSequence": 19, + "vertexType": "TRANSIT", + "boardAlightType": "DEFAULT" }, - { - "points": "umytGrkykVBKDSDWHe@XcBLs@n@mDBUFYDUFa@BK", - "length": 13 + "to": { + "name": "Pioneer Square South MAX Station", + "stopId": "TriMet:8334", + "stopCode": "8334", + "lon": -122.679145, + "lat": 45.518496, + "arrival": 1591718035000, + "departure": 1591718036000, + "zoneId": "R", + "stopIndex": 20, + "stopSequence": 21, + "vertexType": "TRANSIT", + "boardAlightType": "DEFAULT" }, - { - "points": "eiytGzzxkVDSJm@He@@GJk@Hg@PeA", - "length": 8 + "legGeometry": { + "points": "m{ytGtzykV@AN}@BQ@EJm@Lk@PcAF[l@mDDQBMn@uDDUj@gDDSl@mDFWDW\\oBHe@@KBI@I@EF]f@uCBQBI@KTqAP}@@IDS@GTqABSBKn@sDBQf@qC", + "length": 41 }, - { - "points": "_gytGprxkVDUF[@IBMNy@He@RkAToA", - "length": 9 + "interStopGeometry": [ + { + "points": "m{ytGtzykV@AN}@BQ@EJm@Lk@PcAF[l@mDDQBMn@uDDUj@gDDSl@mDFWDW\\oBHe@@KBI@I@EF]f@uCBQBI@KTqAP}@", + "length": 32 + }, + { + "points": "qmytGfgxkV@IDS@GTqABSBKn@sDBQf@qC", + "length": 10 + } + ], + "alerts": [ + { + "effectiveStartDate": 1591959600000, + "alertDescriptionText": "A scarf, bandanna or fabric mask works, as long as it covers your nose and mouth.", + "alertUrl": "http://trimet.org/health" + }, + { + "effectiveStartDate": 1585876166000, + "alertDescriptionText": "Due to COVID-19 service reductions, beginning Sunday, April 5th, on weekdays, trains will run every 15 minutes throughout most of the day. On weekends will run on Sunday schedule. For arrivals and trip planning, see:", + "alertUrl": "http://trimet.org/reducedservice" + }, + { + "effectiveStartDate": 1572827580000, + "alertDescriptionText": "The east elevators (zoo side) at Washington Park MAX Station are closed for improvements until early May. For access between platform and ground levels, use the west elevators (world forestry side). ", + "alertUrl": "https://news.trimet.org/2019/08/trimet-to-replace-elevators-at-the-deepest-transit-station-in-north-america/" + }, + { + "effectiveStartDate": 1591965804000, + "alertDescriptionText": "MAX Blue and Red lines disrupted due to police activity near Goose Hollow/SW Jefferson. Shuttle buses serving stations between Library/SW 9th and Galleria/SW 10th and Sunset Transit Center. Expect delays.", + "alertUrl": "http://trimet.org/alerts/" + } + ], + "routeLongName": "MAX Blue Line", + "rentedBike": false, + "rentedCar": false, + "rentedVehicle": false, + "hailedCar": false, + "flexDrtAdvanceBookMin": 0.0, + "duration": 240.0, + "transitLeg": true, + "intermediateStops": [ + { + "name": "Library/SW 9th Ave MAX Station", + "stopId": "TriMet:8333", + "stopCode": "8333", + "lon": -122.68162, + "lat": 45.51916, + "arrival": 1591717940000, + "departure": 1591717975000, + "zoneId": "R", + "stopIndex": 19, + "stopSequence": 20, + "vertexType": "TRANSIT", + "boardAlightType": "DEFAULT" + } + ], + "steps": [] + }, + { + "startTime": 1591718036000, + "endTime": 1591718324000, + "departureDelay": 0, + "arrivalDelay": 0, + "realTime": false, + "distance": 418.19, + "pathway": false, + "mode": "WALK", + "route": "", + "agencyTimeZoneOffset": -25200000, + "interlineWithPreviousLeg": false, + "from": { + "name": "Pioneer Square South MAX Station", + "stopId": "TriMet:8334", + "stopCode": "8334", + "lon": -122.679145, + "lat": 45.518496, + "arrival": 1591718035000, + "departure": 1591718036000, + "zoneId": "R", + "stopIndex": 20, + "stopSequence": 21, + "vertexType": "TRANSIT", + "boardAlightType": "DEFAULT" }, - { - "points": "gdytGjhxkVFYr@eEDSh@aDBK?ADWh@yC", - "length": 9 - } - ], - "alerts": [ - { - "effectiveStartDate": 1591959600000, - "alertDescriptionText": "A scarf, bandanna or fabric mask works, as long as it covers your nose and mouth.", - "alertUrl": "http://trimet.org/health" + "to": { + "name": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", + "lon": -122.67681483620306, + "lat": 45.51639151281627, + "arrival": 1591718324000, + "orig": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", + "vertexType": "NORMAL" }, - { - "effectiveStartDate": 1582249544000, - "alertDescriptionText": "No service to westbound NW Thurman & 28th (Stop ID 5837) due to long term stop closure. ", - "alertUrl": "http://trimet.org/alerts/" + "legGeometry": { + "points": "uiytGrwwkVDQBODBDB@G@Kn@iDBO@KLF|Ap@FBNHLF|Ar@FDNFBOh@{CD[FWb@eC", + "length": 23 }, - { - "effectiveStartDate": 1585477800000, - "alertDescriptionText": "Due to COVID-19 service reductions, beginning Sunday, April 5th, this line will be running on Saturday schedule on weekdays. For arrivals and trip planning, see trimet.org.", - "alertUrl": "https://trimet.org/alerts/reducedservice.htm" + "rentedBike": false, + "rentedCar": false, + "rentedVehicle": false, + "hailedCar": false, + "flexDrtAdvanceBookMin": 0.0, + "duration": 288.0, + "transitLeg": false, + "intermediateStops": [], + "steps": [ + { + "distance": 7.715, + "relativeDirection": "DEPART", + "streetName": "Pioneer Courthouse Sq (pedestrian street)", + "absoluteDirection": "EAST", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.67913709381972, + "lat": 45.51851024632791, + "elevation": [] + }, + { + "distance": 6.217, + "relativeDirection": "CONTINUE", + "streetName": "path", + "absoluteDirection": "EAST", + "stayOn": false, + "area": false, + "bogusName": true, + "lon": -122.6790448, + "lat": 45.5184851, + "elevation": [ + { + "first": 0.0, + "second": 15.172842393406283 + }, + { + "first": 6.22, + "second": 15.002842393406283 + } + ] + }, + { + "distance": 7.252, + "relativeDirection": "RIGHT", + "streetName": "SW 6th Ave", + "absoluteDirection": "SOUTH", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.6789697, + "lat": 45.5184662, + "elevation": [ + { + "first": 0.0, + "second": 15.002842393406283 + }, + { + "first": 7.25, + "second": 15.112842393406282 + } + ] + }, + { + "distance": 90.85300000000001, + "relativeDirection": "LEFT", + "streetName": "SW Yamhill St", + "absoluteDirection": "EAST", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.6790039, + "lat": 45.5184056, + "elevation": [ + { + "first": 0.0, + "second": 15.112842393406282 + }, + { + "first": 8.41, + "second": 15.092842393406283 + }, + { + "first": 8.407, + "second": 15.092842393406283 + }, + { + "first": 18.407, + "second": 14.942842393406282 + }, + { + "first": 28.407, + "second": 14.672842393406283 + }, + { + "first": 38.407, + "second": 14.392842393406283 + }, + { + "first": 48.407, + "second": 14.192842393406282 + }, + { + "first": 58.407, + "second": 13.772842393406282 + }, + { + "first": 68.407, + "second": 13.452842393406282 + }, + { + "first": 79.467, + "second": 13.182842393406283 + }, + { + "first": 79.465, + "second": 13.182842393406283 + }, + { + "first": 90.855, + "second": 12.942842393406282 + } + ] + }, + { + "distance": 156.99599999999998, + "relativeDirection": "RIGHT", + "streetName": "SW 5th Ave", + "absoluteDirection": "SOUTH", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.677916, + "lat": 45.5181117, + "elevation": [] + }, + { + "distance": 149.157, + "relativeDirection": "LEFT", + "streetName": "SW Salmon St", + "absoluteDirection": "EAST", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.6786441, + "lat": 45.5167953, + "elevation": [ + { + "first": 0.0, + "second": 17.552842393406284 + }, + { + "first": 10.0, + "second": 17.292842393406282 + }, + { + "first": 20.0, + "second": 16.962842393406284 + }, + { + "first": 30.0, + "second": 16.642842393406283 + }, + { + "first": 40.0, + "second": 16.162842393406283 + }, + { + "first": 50.0, + "second": 15.712842393406282 + }, + { + "first": 60.0, + "second": 15.122842393406282 + }, + { + "first": 70.0, + "second": 15.062842393406282 + }, + { + "first": 83.14, + "second": 14.982842393406283 + }, + { + "first": 83.144, + "second": 14.982842393406283 + }, + { + "first": 93.01400000000001, + "second": 14.762842393406283 + }, + { + "first": 93.01700000000001, + "second": 14.762842393406283 + }, + { + "first": 103.01700000000001, + "second": 14.502842393406283 + }, + { + "first": 113.01700000000001, + "second": 14.272842393406282 + }, + { + "first": 123.01700000000001, + "second": 13.872842393406282 + }, + { + "first": 133.017, + "second": 13.632842393406282 + }, + { + "first": 143.017, + "second": 13.412842393406283 + }, + { + "first": 149.157, + "second": 13.152842393406281 + } + ] + } + ] + } + ], + "tooSloped": false + }, + { + "duration": 1242, + "startTime": 1591717148000, + "endTime": 1591718390000, + "walkTime": 820, + "transitTime": 420, + "waitingTime": 2, + "walkDistance": 1297.4104029783646, + "walkLimitExceeded": false, + "elevationLost": 28.980000000000004, + "elevationGained": 1.1200000000000003, + "transfers": 0, + "fare": { + "fare": { + "regular": { + "currency": { + "symbol": "$", + "currency": "USD", + "defaultFractionDigits": 2, + "currencyCode": "USD" + }, + "cents": 200 } - ], - "routeShortName": "15", - "routeLongName": "Belmont/NW 23rd", - "rentedBike": false, - "rentedCar": false, - "rentedVehicle": false, - "hailedCar": false, - "flexDrtAdvanceBookMin": 0.0, - "duration": 360.0, - "transitLeg": true, - "intermediateStops": [ - { - "name": "SW Salmon & 16th", - "stopId": "TriMet:5016", - "stopCode": "5016", - "lon": -122.689312, - "lat": 45.519578, - "arrival": 1591718516000, - "departure": 1591718516000, + }, + "details": { + "regular": [ + { + "fareId": "TriMet:SC", + "price": { + "currency": { + "symbol": "$", + "currency": "USD", + "defaultFractionDigits": 2, + "currencyCode": "USD" + }, + "cents": 200 + }, + "routes": [ + "TriMet:195" + ] + } + ] + } + }, + "legs": [ + { + "startTime": 1591717148000, + "endTime": 1591717499000, + "departureDelay": 0, + "arrivalDelay": 0, + "realTime": false, + "distance": 578.6750000000001, + "pathway": false, + "mode": "WALK", + "route": "", + "agencyTimeZoneOffset": -25200000, + "interlineWithPreviousLeg": false, + "from": { + "name": "1709 NW Irving St, Portland 97209", + "lon": -122.68865964147231, + "lat": 45.527817334203, + "departure": 1591717148000, + "orig": "1709 NW Irving St, Portland 97209", + "vertexType": "NORMAL" + }, + "to": { + "name": "NW 11th & Johnson", + "stopId": "TriMet:10753", + "stopCode": "10753", + "lon": -122.682374, + "lat": 45.528742, + "arrival": 1591717499000, + "departure": 1591717500000, "zoneId": "B", - "stopIndex": 22, - "stopSequence": 23, + "stopIndex": 1, + "stopSequence": 2, "vertexType": "TRANSIT", "boardAlightType": "DEFAULT" }, - { - "name": "SW Salmon & 14th", - "stopId": "TriMet:5014", - "stopCode": "5014", - "lon": -122.687479, - "lat": 45.519106, - "arrival": 1591718559000, - "departure": 1591718559000, + "legGeometry": { + "points": "}c{tGdsykVAu@qBB[@CiECkE???e@AoCAS?Y?kAAsA?Q?QAeD?QASAeDMA?G", + "length": 21 + }, + "rentedBike": false, + "rentedCar": false, + "rentedVehicle": false, + "hailedCar": false, + "flexDrtAdvanceBookMin": 0.0, + "duration": 351.0, + "transitLeg": false, + "intermediateStops": [], + "steps": [ + { + "distance": 20.794, + "relativeDirection": "DEPART", + "streetName": "NW Irving St", + "absoluteDirection": "EAST", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.68866035123024, + "lat": 45.527836027296765, + "elevation": [ + { + "first": 0.0, + "second": 20.682842393406283 + }, + { + "first": 10.0, + "second": 20.452842393406282 + }, + { + "first": 20.0, + "second": 20.24284239340628 + }, + { + "first": 20.79, + "second": 20.22284239340628 + } + ] + }, + { + "distance": 79.476, + "relativeDirection": "LEFT", + "streetName": "NW 17th Ave", + "absoluteDirection": "NORTH", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.6883935, + "lat": 45.527841, + "elevation": [ + { + "first": 0.0, + "second": 18.682842393406283 + }, + { + "first": 10.0, + "second": 18.612842393406282 + }, + { + "first": 20.0, + "second": 18.47284239340628 + }, + { + "first": 30.0, + "second": 18.272842393406282 + }, + { + "first": 40.0, + "second": 18.08284239340628 + }, + { + "first": 50.0, + "second": 17.90284239340628 + }, + { + "first": 63.93, + "second": 17.632842393406282 + }, + { + "first": 63.928, + "second": 17.632842393406282 + }, + { + "first": 73.928, + "second": 17.412842393406283 + }, + { + "first": 79.478, + "second": 17.362842393406282 + } + ] + }, + { + "distance": 467.456, + "relativeDirection": "RIGHT", + "streetName": "NW Johnson St", + "absoluteDirection": "EAST", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.6884207, + "lat": 45.5285555, + "elevation": [ + { + "first": 0.0, + "second": 17.362842393406282 + }, + { + "first": 10.0, + "second": 17.122842393406284 + }, + { + "first": 20.0, + "second": 17.002842393406283 + }, + { + "first": 30.0, + "second": 16.632842393406282 + }, + { + "first": 40.0, + "second": 16.372842393406284 + }, + { + "first": 50.0, + "second": 16.15284239340628 + }, + { + "first": 60.0, + "second": 15.932842393406283 + }, + { + "first": 70.0, + "second": 15.692842393406282 + }, + { + "first": 78.6, + "second": 15.642842393406283 + }, + { + "first": 78.602, + "second": 15.642842393406283 + }, + { + "first": 88.602, + "second": 15.662842393406283 + }, + { + "first": 98.602, + "second": 15.952842393406282 + }, + { + "first": 108.602, + "second": 16.112842393406282 + }, + { + "first": 118.602, + "second": 16.072842393406283 + }, + { + "first": 128.602, + "second": 15.942842393406282 + }, + { + "first": 138.602, + "second": 15.492842393406281 + }, + { + "first": 148.602, + "second": 14.812842393406282 + }, + { + "first": 157.762, + "second": 14.452842393406282 + }, + { + "first": 157.757, + "second": 14.452842393406282 + }, + { + "first": 167.757, + "second": 14.222842393406282 + }, + { + "first": 177.757, + "second": 14.022842393406282 + }, + { + "first": 187.757, + "second": 13.782842393406282 + }, + { + "first": 197.757, + "second": 13.582842393406281 + }, + { + "first": 207.757, + "second": 13.422842393406283 + }, + { + "first": 217.757, + "second": 13.102842393406283 + }, + { + "first": 227.757, + "second": 12.842842393406283 + }, + { + "first": 236.787, + "second": 12.762842393406283 + }, + { + "first": 236.78, + "second": 12.762842393406283 + }, + { + "first": 246.78, + "second": 12.462842393406284 + }, + { + "first": 256.78, + "second": 12.312842393406282 + }, + { + "first": 266.78, + "second": 12.132842393406282 + }, + { + "first": 276.35, + "second": 11.912842393406283 + }, + { + "first": 276.349, + "second": 11.912842393406283 + }, + { + "first": 286.349, + "second": 11.632842393406282 + }, + { + "first": 296.349, + "second": 11.462842393406282 + }, + { + "first": 306.349, + "second": 11.182842393406283 + }, + { + "first": 315.839, + "second": 10.942842393406282 + }, + { + "first": 315.841, + "second": 10.942842393406282 + }, + { + "first": 325.841, + "second": 10.892842393406282 + }, + { + "first": 335.841, + "second": 10.692842393406282 + }, + { + "first": 345.841, + "second": 10.582842393406283 + }, + { + "first": 355.841, + "second": 10.382842393406282 + }, + { + "first": 365.841, + "second": 10.112842393406282 + }, + { + "first": 375.841, + "second": 9.802842393406282 + }, + { + "first": 385.841, + "second": 9.522842393406282 + }, + { + "first": 395.041, + "second": 9.362842393406282 + }, + { + "first": 395.038, + "second": 9.362842393406282 + }, + { + "first": 405.038, + "second": 9.432842393406283 + }, + { + "first": 415.038, + "second": 9.632842393406282 + }, + { + "first": 425.038, + "second": 9.832842393406283 + }, + { + "first": 435.038, + "second": 9.882842393406282 + }, + { + "first": 445.038, + "second": 9.912842393406283 + }, + { + "first": 455.038, + "second": 9.872842393406282 + }, + { + "first": 467.458, + "second": 9.702842393406282 + } + ] + }, + { + "distance": 7.603, + "relativeDirection": "LEFT", + "streetName": "path", + "absoluteDirection": "NORTH", + "stayOn": false, + "area": false, + "bogusName": true, + "lon": -122.6824215, + "lat": 45.5286553, + "elevation": [ + { + "first": 0.0, + "second": 9.702842393406282 + }, + { + "first": 7.6, + "second": 9.642842393406282 + } + ] + }, + { + "distance": 3.346, + "relativeDirection": "RIGHT", + "streetName": "path", + "absoluteDirection": "EAST", + "stayOn": true, + "area": false, + "bogusName": true, + "lon": -122.6824167, + "lat": 45.5287236, + "elevation": [] + } + ] + }, + { + "startTime": 1591717500000, + "endTime": 1591717920000, + "departureDelay": 0, + "arrivalDelay": 0, + "realTime": false, + "distance": 1103.6887432320718, + "pathway": false, + "mode": "TRAM", + "route": "Portland Streetcar - B Loop", + "agencyName": "Portland Streetcar", + "agencyUrl": "https://portlandstreetcar.org/", + "agencyTimeZoneOffset": -25200000, + "routeColor": "0093B2", + "routeType": 0, + "routeId": "TriMet:195", + "routeTextColor": "FFFFFF", + "interlineWithPreviousLeg": false, + "tripBlockId": "58", + "headsign": "Lloyd via OMSI", + "agencyId": "PSC", + "tripId": "TriMet:9945177", + "serviceDate": "20200609", + "from": { + "name": "NW 11th & Johnson", + "stopId": "TriMet:10753", + "stopCode": "10753", + "lon": -122.682374, + "lat": 45.528742, + "arrival": 1591717499000, + "departure": 1591717500000, "zoneId": "B", - "stopIndex": 23, - "stopSequence": 24, + "stopIndex": 1, + "stopSequence": 2, "vertexType": "TRANSIT", "boardAlightType": "DEFAULT" }, - { - "name": "SW Salmon & 12th", - "stopId": "TriMet:5012", - "stopCode": "5012", - "lon": -122.684797, - "lat": 45.518386, - "arrival": 1591718621000, - "departure": 1591718621000, + "to": { + "name": "SW 11th & Taylor", + "stopId": "TriMet:9633", + "stopCode": "9633", + "lon": -122.683873, + "lat": 45.519059, + "arrival": 1591717920000, + "departure": 1591717921000, "zoneId": "B", - "stopIndex": 24, - "stopSequence": 25, + "stopIndex": 5, + "stopSequence": 6, "vertexType": "TRANSIT", "boardAlightType": "DEFAULT" }, - { - "name": "SW Salmon & 10th", - "stopId": "TriMet:5009", - "stopCode": "5009", - "lon": -122.683475, - "lat": 45.518026, - "arrival": 1591718652000, - "departure": 1591718652000, + "legGeometry": { + "points": "si{tGrkxkVP?dACtEGZ?~ACB?L?J?`CE~BCLAn@?~ACvBCFAL?J?rBCL?vBCLAF?D?D@B@lBx@LF`Bt@HBLFHDtB~@DB@?DBFB`Br@DBFB??FBlBz@", + "length": 43 + }, + "interStopGeometry": [ + { + "points": "si{tGrkxkVP?dACtEGZ?~AC", + "length": 6 + }, + { + "points": "i|ztGbkxkVB?L?J?`CE~BCLAn@?~ACvBC", + "length": 10 + }, + { + "points": "sjztGnjxkVFAL?J?rBCL?vBCLAF?D?D@B@lBx@LF`Bt@", + "length": 15 + }, + { + "points": "syytG~mxkVHBLFHDtB~@DB@?DBFB`Br@DBFB??FBlBz@", + "length": 15 + } + ], + "alerts": [ + { + "effectiveStartDate": 1591959600000, + "alertDescriptionText": "A scarf, bandanna or fabric mask works, as long as it covers your nose and mouth.", + "alertUrl": "http://trimet.org/health" + }, + { + "effectiveStartDate": 1585093573000, + "alertDescriptionText": "Streetcar has reduced regular weekday service to every 20 minutes between about 5:30 a.m. and about 11:30 p.m. For more info:", + "alertUrl": "https://portlandstreetcar.org/news/2020/03/streetcar-service-will-reduce-to-20-minute-headways-beginning-march-24" + } + ], + "routeLongName": "Portland Streetcar - B Loop", + "rentedBike": false, + "rentedCar": false, + "rentedVehicle": false, + "hailedCar": false, + "flexDrtAdvanceBookMin": 0.0, + "duration": 420.0, + "transitLeg": true, + "intermediateStops": [ + { + "name": "NW 11th & Glisan", + "stopId": "TriMet:10754", + "stopCode": "10754", + "lon": -122.682297, + "lat": 45.526605, + "arrival": 1591717560000, + "departure": 1591717560000, + "zoneId": "B", + "stopIndex": 2, + "stopSequence": 3, + "vertexType": "TRANSIT", + "boardAlightType": "DEFAULT" + }, + { + "name": "NW 11th & Couch", + "stopId": "TriMet:10756", + "stopCode": "10756", + "lon": -122.682223, + "lat": 45.523784, + "arrival": 1591717740000, + "departure": 1591717740000, + "zoneId": "B", + "stopIndex": 3, + "stopSequence": 4, + "vertexType": "TRANSIT", + "boardAlightType": "DEFAULT" + }, + { + "name": "SW 11th & Alder", + "stopId": "TriMet:9600", + "stopCode": "9600", + "lon": -122.682819, + "lat": 45.521094, + "arrival": 1591717860000, + "departure": 1591717860000, + "zoneId": "B", + "stopIndex": 4, + "stopSequence": 5, + "vertexType": "TRANSIT", + "boardAlightType": "DEFAULT" + } + ], + "steps": [] + }, + { + "startTime": 1591717921000, + "endTime": 1591718390000, + "departureDelay": 0, + "arrivalDelay": 0, + "realTime": false, + "distance": 718.4660000000001, + "pathway": false, + "mode": "WALK", + "route": "", + "agencyTimeZoneOffset": -25200000, + "interlineWithPreviousLeg": false, + "from": { + "name": "SW 11th & Taylor", + "stopId": "TriMet:9633", + "stopCode": "9633", + "lon": -122.683873, + "lat": 45.519059, + "arrival": 1591717920000, + "departure": 1591717921000, "zoneId": "B", - "stopIndex": 25, - "stopSequence": 26, + "stopIndex": 5, + "stopSequence": 6, "vertexType": "TRANSIT", "boardAlightType": "DEFAULT" }, - { - "name": "SW Salmon & Park", - "stopId": "TriMet:5007", - "stopCode": "5007", - "lon": -122.681844, - "lat": 45.517596, - "arrival": 1591718690000, - "departure": 1591718690000, + "to": { + "name": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", + "lon": -122.67681483620306, + "lat": 45.51639151281627, + "arrival": 1591718390000, + "orig": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", + "vertexType": "NORMAL" + }, + "legGeometry": { + "points": "cmytGfuxkVAHD@JFBQ@GDSh@_D??FU@IBMX_BRiA@K?AVqA@KBOFY`@aCFYBSh@}CBO@CDU^wBHc@F_@@CLF|Ar@FDNFBOh@{CD[FWb@eC", + "length": 40 + }, + "rentedBike": false, + "rentedCar": false, + "rentedVehicle": false, + "hailedCar": false, + "flexDrtAdvanceBookMin": 0.0, + "duration": 469.0, + "transitLeg": false, + "intermediateStops": [], + "steps": [ + { + "distance": 14.561, + "relativeDirection": "DEPART", + "streetName": "path", + "absoluteDirection": "WEST", + "stayOn": false, + "area": false, + "bogusName": true, + "lon": -122.6838786, + "lat": 45.5190659, + "elevation": [ + { + "first": 0.0, + "second": 29.782842393406284 + }, + { + "first": 3.48, + "second": 29.83284239340628 + }, + { + "first": 7.5809999999999995, + "second": 29.83284239340628 + }, + { + "first": 11.690999999999999, + "second": 29.862842393406282 + } + ] + }, + { + "distance": 475.42999999999995, + "relativeDirection": "LEFT", + "streetName": "SW Taylor St", + "absoluteDirection": "EAST", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.6839744, + "lat": 45.5189848, + "elevation": [ + { + "first": 0.0, + "second": 29.87284239340628 + }, + { + "first": 10.39, + "second": 29.87284239340628 + }, + { + "first": 10.386, + "second": 29.87284239340628 + }, + { + "first": 18.735999999999997, + "second": 29.662842393406283 + }, + { + "first": 18.737000000000002, + "second": 29.662842393406283 + }, + { + "first": 28.737000000000002, + "second": 29.252842393406283 + }, + { + "first": 38.737, + "second": 28.97284239340628 + }, + { + "first": 48.737, + "second": 28.632842393406282 + }, + { + "first": 58.737, + "second": 28.05284239340628 + }, + { + "first": 68.737, + "second": 27.62284239340628 + }, + { + "first": 78.737, + "second": 27.202842393406282 + }, + { + "first": 85.84700000000001, + "second": 26.952842393406282 + }, + { + "first": 85.84299999999999, + "second": 26.952842393406282 + }, + { + "first": 95.353, + "second": 26.932842393406283 + }, + { + "first": 95.35099999999998, + "second": 26.932842393406283 + }, + { + "first": 105.13099999999999, + "second": 26.65284239340628 + }, + { + "first": 105.12999999999998, + "second": 26.65284239340628 + }, + { + "first": 115.12999999999998, + "second": 26.22284239340628 + }, + { + "first": 125.12999999999998, + "second": 25.87284239340628 + }, + { + "first": 135.13, + "second": 25.642842393406283 + }, + { + "first": 145.48999999999998, + "second": 25.15284239340628 + }, + { + "first": 145.486, + "second": 25.15284239340628 + }, + { + "first": 155.486, + "second": 24.90284239340628 + }, + { + "first": 165.486, + "second": 24.602842393406284 + }, + { + "first": 176.05599999999998, + "second": 24.342842393406283 + }, + { + "first": 176.051, + "second": 24.342842393406283 + }, + { + "first": 181.18099999999998, + "second": 24.302842393406284 + }, + { + "first": 181.18099999999998, + "second": 24.302842393406284 + }, + { + "first": 191.18099999999998, + "second": 24.08284239340628 + }, + { + "first": 201.18099999999998, + "second": 23.752842393406283 + }, + { + "first": 216.081, + "second": 23.202842393406282 + }, + { + "first": 216.077, + "second": 23.202842393406282 + }, + { + "first": 221.187, + "second": 23.08284239340628 + }, + { + "first": 221.182, + "second": 23.08284239340628 + }, + { + "first": 231.182, + "second": 22.642842393406283 + }, + { + "first": 238.742, + "second": 22.17284239340628 + }, + { + "first": 238.742, + "second": 22.17284239340628 + }, + { + "first": 248.742, + "second": 21.65284239340628 + }, + { + "first": 258.74199999999996, + "second": 21.26284239340628 + }, + { + "first": 268.74199999999996, + "second": 20.682842393406283 + }, + { + "first": 278.74199999999996, + "second": 20.272842393406282 + }, + { + "first": 288.74199999999996, + "second": 19.74284239340628 + }, + { + "first": 303.502, + "second": 19.622842393406284 + }, + { + "first": 303.505, + "second": 19.622842393406284 + }, + { + "first": 313.505, + "second": 19.342842393406283 + }, + { + "first": 323.505, + "second": 18.962842393406284 + }, + { + "first": 333.505, + "second": 18.802842393406284 + }, + { + "first": 343.505, + "second": 18.532842393406284 + }, + { + "first": 353.505, + "second": 18.252842393406283 + }, + { + "first": 363.505, + "second": 18.002842393406283 + }, + { + "first": 373.505, + "second": 17.642842393406283 + }, + { + "first": 384.315, + "second": 17.522842393406282 + }, + { + "first": 384.311, + "second": 17.522842393406282 + }, + { + "first": 394.311, + "second": 17.532842393406284 + }, + { + "first": 404.311, + "second": 17.292842393406282 + }, + { + "first": 414.311, + "second": 17.112842393406282 + }, + { + "first": 424.311, + "second": 16.90284239340628 + }, + { + "first": 434.311, + "second": 16.56284239340628 + }, + { + "first": 444.311, + "second": 16.392842393406283 + }, + { + "first": 454.311, + "second": 16.162842393406283 + }, + { + "first": 464.311, + "second": 15.902842393406281 + }, + { + "first": 475.431, + "second": 15.822842393406283 + } + ] + }, + { + "distance": 79.318, + "relativeDirection": "RIGHT", + "streetName": "SW 5th Ave", + "absoluteDirection": "SOUTH", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.6782737, + "lat": 45.5174597, + "elevation": [] + }, + { + "distance": 149.157, + "relativeDirection": "LEFT", + "streetName": "SW Salmon St", + "absoluteDirection": "EAST", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.6786441, + "lat": 45.5167953, + "elevation": [ + { + "first": 0.0, + "second": 17.552842393406284 + }, + { + "first": 10.0, + "second": 17.292842393406282 + }, + { + "first": 20.0, + "second": 16.962842393406284 + }, + { + "first": 30.0, + "second": 16.642842393406283 + }, + { + "first": 40.0, + "second": 16.162842393406283 + }, + { + "first": 50.0, + "second": 15.712842393406282 + }, + { + "first": 60.0, + "second": 15.122842393406282 + }, + { + "first": 70.0, + "second": 15.062842393406282 + }, + { + "first": 83.14, + "second": 14.982842393406283 + }, + { + "first": 83.144, + "second": 14.982842393406283 + }, + { + "first": 93.01400000000001, + "second": 14.762842393406283 + }, + { + "first": 93.01700000000001, + "second": 14.762842393406283 + }, + { + "first": 103.01700000000001, + "second": 14.502842393406283 + }, + { + "first": 113.01700000000001, + "second": 14.272842393406282 + }, + { + "first": 123.01700000000001, + "second": 13.872842393406282 + }, + { + "first": 133.017, + "second": 13.632842393406282 + }, + { + "first": 143.017, + "second": 13.412842393406283 + }, + { + "first": 149.157, + "second": 13.152842393406281 + } + ] + } + ] + } + ], + "tooSloped": false + }, + { + "duration": 1036, + "startTime": 1591717825000, + "endTime": 1591718861000, + "walkTime": 674, + "transitTime": 360, + "waitingTime": 2, + "walkDistance": 966.7338656666136, + "walkLimitExceeded": false, + "elevationLost": 8.23, + "elevationGained": 13.169999999999996, + "transfers": 0, + "fare": { + "fare": { + "regular": { + "currency": { + "symbol": "$", + "currency": "USD", + "defaultFractionDigits": 2, + "currencyCode": "USD" + }, + "cents": 250 + } + }, + "details": { + "regular": [ + { + "fareId": "TriMet:B", + "price": { + "currency": { + "symbol": "$", + "currency": "USD", + "defaultFractionDigits": 2, + "currencyCode": "USD" + }, + "cents": 250 + }, + "routes": [ + "TriMet:15" + ] + } + ] + } + }, + "legs": [ + { + "startTime": 1591717825000, + "endTime": 1591718399000, + "departureDelay": 0, + "arrivalDelay": 0, + "realTime": false, + "distance": 802.1549999999999, + "pathway": false, + "mode": "WALK", + "route": "", + "agencyTimeZoneOffset": -25200000, + "interlineWithPreviousLeg": false, + "from": { + "name": "1709 NW Irving St, Portland 97209", + "lon": -122.68865964147231, + "lat": 45.527817334203, + "departure": 1591717825000, + "orig": "1709 NW Irving St, Portland 97209", + "vertexType": "NORMAL" + }, + "to": { + "name": "SW 18th & Morrison", + "stopId": "TriMet:6911", + "stopCode": "6911", + "lon": -122.690453, + "lat": 45.52188, + "arrival": 1591718399000, + "departure": 1591718400000, "zoneId": "B", - "stopIndex": 26, - "stopSequence": 27, + "stopIndex": 21, + "stopSequence": 22, "vertexType": "TRANSIT", "boardAlightType": "DEFAULT" - } - ], - "steps": [] - }, - { - "startTime": 1591718761000, - "endTime": 1591718861000, - "departureDelay": 0, - "arrivalDelay": 0, - "realTime": false, - "distance": 164.377, - "pathway": false, - "mode": "WALK", - "route": "", - "agencyTimeZoneOffset": -25200000, - "interlineWithPreviousLeg": false, - "from": { - "name": "SW Salmon & 5th", - "stopId": "TriMet:5020", - "stopCode": "5020", - "lon": -122.678854, - "lat": 45.516794, - "arrival": 1591718760000, - "departure": 1591718761000, - "zoneId": "B", - "stopIndex": 27, - "stopSequence": 28, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - "to": { - "name": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", - "lon": -122.67681483620306, - "lat": 45.51639151281627, - "arrival": 1591718861000, - "orig": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", - "vertexType": "NORMAL" + }, + "legGeometry": { + "points": "}c{tGdsykVAu@x@AJ?v@AP?nCE\\?r@Ax@C`BADAd@?lCEp@AdAAV??NBvDl@?@?F?J?J?H?H?B@BB@DBDBFBHLZBDHLHLHHFDHFj@XHPTHJDLRCP?BB?B@B@`@N", + "length": 50 + }, + "rentedBike": false, + "rentedCar": false, + "rentedVehicle": false, + "hailedCar": false, + "flexDrtAdvanceBookMin": 0.0, + "duration": 574.0, + "transitLeg": false, + "intermediateStops": [], + "steps": [ + { + "distance": 20.794, + "relativeDirection": "DEPART", + "streetName": "NW Irving St", + "absoluteDirection": "EAST", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.68866035123024, + "lat": 45.527836027296765, + "elevation": [ + { + "first": 0.0, + "second": 20.682842393406283 + }, + { + "first": 10.0, + "second": 20.452842393406282 + }, + { + "first": 20.0, + "second": 20.24284239340628 + }, + { + "first": 20.79, + "second": 20.22284239340628 + } + ] + }, + { + "distance": 475.08199999999994, + "relativeDirection": "RIGHT", + "streetName": "NW 17th Ave", + "absoluteDirection": "SOUTH", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.6883935, + "lat": 45.527841, + "elevation": [ + { + "first": 0.0, + "second": 18.682842393406283 + }, + { + "first": 10.0, + "second": 18.802842393406284 + }, + { + "first": 20.0, + "second": 19.01284239340628 + }, + { + "first": 31.29, + "second": 19.24284239340628 + }, + { + "first": 31.294, + "second": 19.24284239340628 + }, + { + "first": 39.024, + "second": 19.40284239340628 + }, + { + "first": 39.023, + "second": 19.40284239340628 + }, + { + "first": 49.023, + "second": 19.60284239340628 + }, + { + "first": 59.023, + "second": 19.85284239340628 + }, + { + "first": 69.023, + "second": 19.97284239340628 + }, + { + "first": 79.273, + "second": 20.032842393406284 + }, + { + "first": 79.272, + "second": 20.032842393406284 + }, + { + "first": 89.272, + "second": 20.26284239340628 + }, + { + "first": 99.272, + "second": 20.572842393406283 + }, + { + "first": 109.272, + "second": 20.872842393406284 + }, + { + "first": 119.272, + "second": 21.112842393406282 + }, + { + "first": 129.272, + "second": 21.412842393406283 + }, + { + "first": 139.272, + "second": 21.69284239340628 + }, + { + "first": 149.272, + "second": 22.01284239340628 + }, + { + "first": 159.322, + "second": 22.002842393406283 + }, + { + "first": 159.323, + "second": 22.002842393406283 + }, + { + "first": 169.323, + "second": 22.17284239340628 + }, + { + "first": 176.933, + "second": 22.352842393406284 + }, + { + "first": 176.93, + "second": 22.352842393406284 + }, + { + "first": 186.93, + "second": 22.58284239340628 + }, + { + "first": 196.93, + "second": 22.792842393406282 + }, + { + "first": 205.41, + "second": 22.952842393406282 + }, + { + "first": 205.41, + "second": 22.952842393406282 + }, + { + "first": 215.41, + "second": 23.15284239340628 + }, + { + "first": 225.41, + "second": 23.302842393406284 + }, + { + "first": 237.51, + "second": 23.42284239340628 + }, + { + "first": 237.505, + "second": 23.42284239340628 + }, + { + "first": 247.505, + "second": 23.502842393406283 + }, + { + "first": 257.505, + "second": 23.632842393406282 + }, + { + "first": 267.505, + "second": 23.76284239340628 + }, + { + "first": 277.505, + "second": 23.90284239340628 + }, + { + "first": 292.145, + "second": 24.092842393406283 + }, + { + "first": 292.143, + "second": 24.092842393406283 + }, + { + "first": 295.53299999999996, + "second": 24.132842393406282 + }, + { + "first": 295.53499999999997, + "second": 24.132842393406282 + }, + { + "first": 305.53499999999997, + "second": 24.432842393406283 + }, + { + "first": 316.635, + "second": 24.392842393406283 + }, + { + "first": 316.63599999999997, + "second": 24.392842393406283 + }, + { + "first": 326.63599999999997, + "second": 24.522842393406282 + }, + { + "first": 336.63599999999997, + "second": 24.792842393406282 + }, + { + "first": 346.63599999999997, + "second": 25.052842393406284 + }, + { + "first": 356.63599999999997, + "second": 25.31284239340628 + }, + { + "first": 366.63599999999997, + "second": 25.602842393406284 + }, + { + "first": 376.63599999999997, + "second": 25.842842393406283 + }, + { + "first": 386.63599999999997, + "second": 25.982842393406283 + }, + { + "first": 396.02599999999995, + "second": 26.01284239340628 + }, + { + "first": 396.03, + "second": 26.01284239340628 + }, + { + "first": 406.03, + "second": 26.042842393406282 + }, + { + "first": 416.03, + "second": 26.092842393406283 + }, + { + "first": 423.69, + "second": 26.15284239340628 + }, + { + "first": 423.686, + "second": 26.15284239340628 + }, + { + "first": 433.686, + "second": 26.252842393406283 + }, + { + "first": 443.686, + "second": 26.31284239340628 + }, + { + "first": 453.686, + "second": 26.362842393406282 + }, + { + "first": 461.936, + "second": 26.40284239340628 + }, + { + "first": 461.93699999999995, + "second": 26.40284239340628 + }, + { + "first": 475.08699999999993, + "second": 26.19284239340628 + } + ] + }, + { + "distance": 78.621, + "relativeDirection": "RIGHT", + "streetName": "NW Couch St", + "absoluteDirection": "WEST", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.6882412, + "lat": 45.5235698, + "elevation": [ + { + "first": 0.0, + "second": 26.19284239340628 + }, + { + "first": 10.0, + "second": 26.352842393406284 + }, + { + "first": 20.0, + "second": 26.572842393406283 + }, + { + "first": 30.0, + "second": 26.802842393406284 + }, + { + "first": 40.0, + "second": 27.15284239340628 + }, + { + "first": 50.0, + "second": 27.322842393406283 + }, + { + "first": 60.0, + "second": 27.55284239340628 + }, + { + "first": 70.0, + "second": 27.80284239340628 + }, + { + "first": 78.62, + "second": 28.01284239340628 + } + ] + }, + { + "distance": 66.44500000000001, + "relativeDirection": "LEFT", + "streetName": "NW 18th Ave", + "absoluteDirection": "SOUTH", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.6892498, + "lat": 45.5235462, + "elevation": [ + { + "first": 0.0, + "second": 28.01284239340628 + }, + { + "first": 10.0, + "second": 27.37284239340628 + }, + { + "first": 20.0, + "second": 26.87284239340628 + }, + { + "first": 25.41, + "second": 26.65284239340628 + }, + { + "first": 25.411, + "second": 26.65284239340628 + }, + { + "first": 27.351000000000003, + "second": 26.56284239340628 + }, + { + "first": 27.346, + "second": 26.56284239340628 + }, + { + "first": 31.706, + "second": 26.37284239340628 + }, + { + "first": 31.705, + "second": 26.37284239340628 + }, + { + "first": 38.315, + "second": 26.132842393406282 + }, + { + "first": 38.31, + "second": 26.132842393406282 + }, + { + "first": 48.31, + "second": 25.90284239340628 + }, + { + "first": 55.33, + "second": 25.822842393406283 + }, + { + "first": 55.328, + "second": 25.822842393406283 + }, + { + "first": 66.44800000000001, + "second": 25.962842393406284 + } + ] + }, + { + "distance": 161.21300000000002, + "relativeDirection": "SLIGHTLY_RIGHT", + "streetName": "SW 18th Ave", + "absoluteDirection": "SOUTHWEST", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.6893346, + "lat": 45.5229721, + "elevation": [ + { + "first": 0.0, + "second": 25.962842393406284 + }, + { + "first": 10.0, + "second": 26.08284239340628 + }, + { + "first": 20.0, + "second": 26.022842393406282 + }, + { + "first": 32.85, + "second": 25.642842393406283 + }, + { + "first": 32.852, + "second": 25.642842393406283 + }, + { + "first": 42.852, + "second": 25.852842393406284 + }, + { + "first": 52.852, + "second": 26.142842393406283 + }, + { + "first": 62.852, + "second": 26.572842393406283 + }, + { + "first": 72.852, + "second": 27.022842393406282 + }, + { + "first": 82.852, + "second": 27.612842393406282 + }, + { + "first": 93.812, + "second": 28.092842393406283 + }, + { + "first": 93.811, + "second": 28.092842393406283 + }, + { + "first": 103.811, + "second": 28.47284239340628 + }, + { + "first": 114.351, + "second": 28.842842393406283 + }, + { + "first": 114.346, + "second": 28.842842393406283 + }, + { + "first": 125.05600000000001, + "second": 28.962842393406284 + }, + { + "first": 134.205, + "second": 28.962842393406284 + }, + { + "first": 141.47500000000002, + "second": 28.56284239340628 + }, + { + "first": 141.47, + "second": 28.56284239340628 + }, + { + "first": 143.36, + "second": 28.482842393406283 + } + ] + } + ] }, - "legGeometry": { - "points": "g_ytGtuwkV@CF]?ABOh@{CD[FWb@eC", - "length": 9 + { + "startTime": 1591718400000, + "endTime": 1591718760000, + "departureDelay": 0, + "arrivalDelay": 0, + "realTime": false, + "distance": 1286.9349137891197, + "pathway": false, + "mode": "BUS", + "route": "15", + "agencyName": "TriMet", + "agencyUrl": "https://trimet.org/", + "agencyTimeZoneOffset": -25200000, + "routeType": 3, + "routeId": "TriMet:15", + "interlineWithPreviousLeg": false, + "tripBlockId": "1505", + "headsign": "Gateway TC", + "agencyId": "TRIMET", + "tripId": "TriMet:9932776", + "serviceDate": "20200609", + "from": { + "name": "SW 18th & Morrison", + "stopId": "TriMet:6911", + "stopCode": "6911", + "lon": -122.690453, + "lat": 45.52188, + "arrival": 1591718399000, + "departure": 1591718400000, + "zoneId": "B", + "stopIndex": 21, + "stopSequence": 22, + "vertexType": "TRANSIT", + "boardAlightType": "DEFAULT" + }, + "to": { + "name": "SW Salmon & 5th", + "stopId": "TriMet:5020", + "stopCode": "5020", + "lon": -122.678854, + "lat": 45.516794, + "arrival": 1591718760000, + "departure": 1591718761000, + "zoneId": "B", + "stopIndex": 27, + "stopSequence": 28, + "vertexType": "TRANSIT", + "boardAlightType": "DEFAULT" + }, + "legGeometry": { + "points": "w~ytGx}ykVRHh@ZXNhBv@JFJFRJFDFFFH@@`@Xp@h@RNBMBQBMBO?I@I?O?u@?e@?A?S@WBMDY\\sBJe@Fc@@Ep@sDBOBSd@mCBKDSDWHe@XcBLs@n@mDBUFYDUFa@BKDSJm@He@@GJk@Hg@PeADUF[@IBMNy@He@RkAToAFYr@eEDSh@aDBK?ADWh@yC", + "length": 72 + }, + "interStopGeometry": [ + { + "points": "w~ytGx}ykVRHh@ZXNhBv@JFJFRJFDFFFH@@`@Xp@h@RNBMBQBMBO?I@I?O?u@?e@?A?S@WBMDY\\sBJe@Fc@", + "length": 32 + }, + { + "points": "wpytG~vykV@Ep@sDBOBSd@mC", + "length": 6 + }, + { + "points": "umytGrkykVBKDSDWHe@XcBLs@n@mDBUFYDUFa@BK", + "length": 13 + }, + { + "points": "eiytGzzxkVDSJm@He@@GJk@Hg@PeA", + "length": 8 + }, + { + "points": "_gytGprxkVDUF[@IBMNy@He@RkAToA", + "length": 9 + }, + { + "points": "gdytGjhxkVFYr@eEDSh@aDBK?ADWh@yC", + "length": 9 + } + ], + "alerts": [ + { + "effectiveStartDate": 1591959600000, + "alertDescriptionText": "A scarf, bandanna or fabric mask works, as long as it covers your nose and mouth.", + "alertUrl": "http://trimet.org/health" + }, + { + "effectiveStartDate": 1582249544000, + "alertDescriptionText": "No service to westbound NW Thurman & 28th (Stop ID 5837) due to long term stop closure. ", + "alertUrl": "http://trimet.org/alerts/" + }, + { + "effectiveStartDate": 1585477800000, + "alertDescriptionText": "Due to COVID-19 service reductions, beginning Sunday, April 5th, this line will be running on Saturday schedule on weekdays. For arrivals and trip planning, see trimet.org.", + "alertUrl": "https://trimet.org/alerts/reducedservice.htm" + } + ], + "routeShortName": "15", + "routeLongName": "Belmont/NW 23rd", + "rentedBike": false, + "rentedCar": false, + "rentedVehicle": false, + "hailedCar": false, + "flexDrtAdvanceBookMin": 0.0, + "duration": 360.0, + "transitLeg": true, + "intermediateStops": [ + { + "name": "SW Salmon & 16th", + "stopId": "TriMet:5016", + "stopCode": "5016", + "lon": -122.689312, + "lat": 45.519578, + "arrival": 1591718516000, + "departure": 1591718516000, + "zoneId": "B", + "stopIndex": 22, + "stopSequence": 23, + "vertexType": "TRANSIT", + "boardAlightType": "DEFAULT" + }, + { + "name": "SW Salmon & 14th", + "stopId": "TriMet:5014", + "stopCode": "5014", + "lon": -122.687479, + "lat": 45.519106, + "arrival": 1591718559000, + "departure": 1591718559000, + "zoneId": "B", + "stopIndex": 23, + "stopSequence": 24, + "vertexType": "TRANSIT", + "boardAlightType": "DEFAULT" + }, + { + "name": "SW Salmon & 12th", + "stopId": "TriMet:5012", + "stopCode": "5012", + "lon": -122.684797, + "lat": 45.518386, + "arrival": 1591718621000, + "departure": 1591718621000, + "zoneId": "B", + "stopIndex": 24, + "stopSequence": 25, + "vertexType": "TRANSIT", + "boardAlightType": "DEFAULT" + }, + { + "name": "SW Salmon & 10th", + "stopId": "TriMet:5009", + "stopCode": "5009", + "lon": -122.683475, + "lat": 45.518026, + "arrival": 1591718652000, + "departure": 1591718652000, + "zoneId": "B", + "stopIndex": 25, + "stopSequence": 26, + "vertexType": "TRANSIT", + "boardAlightType": "DEFAULT" + }, + { + "name": "SW Salmon & Park", + "stopId": "TriMet:5007", + "stopCode": "5007", + "lon": -122.681844, + "lat": 45.517596, + "arrival": 1591718690000, + "departure": 1591718690000, + "zoneId": "B", + "stopIndex": 26, + "stopSequence": 27, + "vertexType": "TRANSIT", + "boardAlightType": "DEFAULT" + } + ], + "steps": [] }, - "rentedBike": false, - "rentedCar": false, - "rentedVehicle": false, - "hailedCar": false, - "flexDrtAdvanceBookMin": 0.0, - "duration": 100.0, - "transitLeg": false, - "intermediateStops": [], - "steps": [ - { - "distance": 164.377, - "relativeDirection": "DEPART", - "streetName": "SW Salmon St", - "absoluteDirection": "EAST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.67882640142975, - "lat": 45.51684447671969, - "elevation": [ - { - "first": 15.22, - "second": 17.552842393406284 - }, - { - "first": 25.22, - "second": 17.292842393406282 - }, - { - "first": 35.22, - "second": 16.962842393406284 - }, - { - "first": 45.22, - "second": 16.642842393406283 - }, - { - "first": 55.22, - "second": 16.162842393406283 - }, - { - "first": 65.22, - "second": 15.712842393406282 - }, - { - "first": 75.22, - "second": 15.122842393406282 - }, - { - "first": 85.22, - "second": 15.062842393406282 - }, - { - "first": 98.36, - "second": 14.982842393406283 - }, - { - "first": 98.364, - "second": 14.982842393406283 - }, - { - "first": 108.23400000000001, - "second": 14.762842393406283 - }, - { - "first": 108.23700000000001, - "second": 14.762842393406283 - }, - { - "first": 118.23700000000001, - "second": 14.502842393406283 - }, - { - "first": 128.23700000000002, - "second": 14.272842393406282 - }, - { - "first": 138.23700000000002, - "second": 13.872842393406282 - }, - { - "first": 148.23700000000002, - "second": 13.632842393406282 - }, - { - "first": 158.23700000000002, - "second": 13.412842393406283 - }, - { - "first": 164.377, - "second": 13.152842393406281 - } - ] - } - ] - } - ], - "tooSloped": false - } - ] - }, - "debugOutput": { - "precalculationTime": 153, - "pathCalculationTime": 112, - "pathTimes": [ - 41, - 33, - 37 - ], - "renderingTime": 1, - "totalTime": 266, - "timedOut": false - }, - "elevationMetadata": { - "ellipsoidToGeoidDifference": -19.522842393406282, - "geoidElevation": true + { + "startTime": 1591718761000, + "endTime": 1591718861000, + "departureDelay": 0, + "arrivalDelay": 0, + "realTime": false, + "distance": 164.377, + "pathway": false, + "mode": "WALK", + "route": "", + "agencyTimeZoneOffset": -25200000, + "interlineWithPreviousLeg": false, + "from": { + "name": "SW Salmon & 5th", + "stopId": "TriMet:5020", + "stopCode": "5020", + "lon": -122.678854, + "lat": 45.516794, + "arrival": 1591718760000, + "departure": 1591718761000, + "zoneId": "B", + "stopIndex": 27, + "stopSequence": 28, + "vertexType": "TRANSIT", + "boardAlightType": "DEFAULT" + }, + "to": { + "name": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", + "lon": -122.67681483620306, + "lat": 45.51639151281627, + "arrival": 1591718861000, + "orig": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", + "vertexType": "NORMAL" + }, + "legGeometry": { + "points": "g_ytGtuwkV@CF]?ABOh@{CD[FWb@eC", + "length": 9 + }, + "rentedBike": false, + "rentedCar": false, + "rentedVehicle": false, + "hailedCar": false, + "flexDrtAdvanceBookMin": 0.0, + "duration": 100.0, + "transitLeg": false, + "intermediateStops": [], + "steps": [ + { + "distance": 164.377, + "relativeDirection": "DEPART", + "streetName": "SW Salmon St", + "absoluteDirection": "EAST", + "stayOn": false, + "area": false, + "bogusName": false, + "lon": -122.67882640142975, + "lat": 45.51684447671969, + "elevation": [ + { + "first": 15.22, + "second": 17.552842393406284 + }, + { + "first": 25.22, + "second": 17.292842393406282 + }, + { + "first": 35.22, + "second": 16.962842393406284 + }, + { + "first": 45.22, + "second": 16.642842393406283 + }, + { + "first": 55.22, + "second": 16.162842393406283 + }, + { + "first": 65.22, + "second": 15.712842393406282 + }, + { + "first": 75.22, + "second": 15.122842393406282 + }, + { + "first": 85.22, + "second": 15.062842393406282 + }, + { + "first": 98.36, + "second": 14.982842393406283 + }, + { + "first": 98.364, + "second": 14.982842393406283 + }, + { + "first": 108.23400000000001, + "second": 14.762842393406283 + }, + { + "first": 108.23700000000001, + "second": 14.762842393406283 + }, + { + "first": 118.23700000000001, + "second": 14.502842393406283 + }, + { + "first": 128.23700000000002, + "second": 14.272842393406282 + }, + { + "first": 138.23700000000002, + "second": 13.872842393406282 + }, + { + "first": 148.23700000000002, + "second": 13.632842393406282 + }, + { + "first": 158.23700000000002, + "second": 13.412842393406283 + }, + { + "first": 164.377, + "second": 13.152842393406281 + } + ] + } + ] + } + ], + "tooSloped": false + } + ] + } } } \ No newline at end of file From 68d32e54791478d70fe7137a93092c4212ca0ddc Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 24 Jul 2024 11:34:09 -0400 Subject: [PATCH 29/48] test(GetMonitoredTrips): Remove use of mock OTP server. --- .../api/GetMonitoredTripsTest.java | 48 +++++++++---------- .../middleware/models/MonitoredTripTest.java | 4 +- 2 files changed, 24 insertions(+), 28 deletions(-) diff --git a/src/test/java/org/opentripplanner/middleware/controllers/api/GetMonitoredTripsTest.java b/src/test/java/org/opentripplanner/middleware/controllers/api/GetMonitoredTripsTest.java index e84f88766..2eb4a91dd 100644 --- a/src/test/java/org/opentripplanner/middleware/controllers/api/GetMonitoredTripsTest.java +++ b/src/test/java/org/opentripplanner/middleware/controllers/api/GetMonitoredTripsTest.java @@ -4,7 +4,6 @@ import com.auth0.json.mgmt.users.User; import com.mongodb.BasicDBObject; import org.eclipse.jetty.http.HttpMethod; -import org.eclipse.jetty.http.HttpStatus; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; @@ -12,16 +11,21 @@ import org.opentripplanner.middleware.auth.Auth0Users; import org.opentripplanner.middleware.controllers.response.ResponseList; import org.opentripplanner.middleware.models.AdminUser; +import org.opentripplanner.middleware.models.ItineraryExistence; import org.opentripplanner.middleware.models.MonitoredTrip; +import org.opentripplanner.middleware.models.MonitoredTripTest; import org.opentripplanner.middleware.models.OtpUser; +import org.opentripplanner.middleware.otp.response.Itinerary; +import org.opentripplanner.middleware.otp.response.Place; import org.opentripplanner.middleware.persistence.Persistence; import org.opentripplanner.middleware.testutils.ApiTestUtils; import org.opentripplanner.middleware.testutils.OtpMiddlewareTestEnvironment; import org.opentripplanner.middleware.testutils.PersistenceTestUtils; -import org.opentripplanner.middleware.testutils.OtpTestUtils; import org.opentripplanner.middleware.utils.HttpResponseValues; import org.opentripplanner.middleware.utils.JsonUtils; +import java.util.Date; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -68,8 +72,6 @@ public static void setUp() { assumeTrue(IS_END_TO_END); setAuthDisabled(false); - // Mock the OTP server TODO: Run a live OTP instance? - OtpTestUtils.mockOtpServer(); String multiUserEmail = ApiTestUtils.generateEmailAddress("test-multiotpuser"); soloOtpUser = PersistenceTestUtils.createUser(ApiTestUtils.generateEmailAddress("test-solootpuser")); multiOtpUser = PersistenceTestUtils.createUser(multiUserEmail); @@ -108,7 +110,6 @@ public static void tearDown() { @AfterEach public void tearDownAfterTest() { - OtpTestUtils.resetOtpMocks(); BasicDBObject soloFilter = new BasicDBObject(); soloFilter.put("userId", soloOtpUser.id); Persistence.monitoredTrips.removeFiltered(soloFilter); @@ -124,8 +125,8 @@ public void tearDownAfterTest() { @Test void canGetOwnMonitoredTrips() throws Exception { // Create a trip for the solo and the multi OTP user. - createMonitoredTripAsUser(soloOtpUser); - createMonitoredTripAsUser(multiOtpUser); + createMonitoredTripForUser(soloOtpUser); + createMonitoredTripForUser(multiOtpUser); // Get trips for solo Otp user. ResponseList soloTrips = getMonitoredTripsForUser(MONITORED_TRIP_PATH, soloOtpUser); @@ -155,7 +156,7 @@ void canGetOwnMonitoredTrips() throws Exception { @Test void canPreserveTripFields() throws Exception { // Create a trip for the solo OTP user. - createMonitoredTripAsUser(soloOtpUser); + createMonitoredTripForUser(soloOtpUser); BasicDBObject filter = new BasicDBObject(); filter.put("userId", soloOtpUser.id); @@ -205,25 +206,20 @@ private ResponseList getMonitoredTripsForUser(String path, OtpUse /** * Creates a {@link MonitoredTrip} for the specified user. */ - private static void createMonitoredTripAsUser(OtpUser otpUser) throws Exception { - MonitoredTrip monitoredTrip = new MonitoredTrip(OtpTestUtils.sendSamplePlanRequest()); + private static void createMonitoredTripForUser(OtpUser otpUser) { + MonitoredTrip monitoredTrip = new MonitoredTrip(); monitoredTrip.updateAllDaysOfWeek(true); monitoredTrip.userId = otpUser.id; - - // Set mock OTP responses so that trip existence checks in the - // POST call below to save the monitored trip can pass. - OtpTestUtils.setupOtpMocks(OtpTestUtils.createMockOtpResponsesForTripExistence()); - - HttpResponseValues createTripResponse = mockAuthenticatedRequest(MONITORED_TRIP_PATH, - JsonUtils.toJson(monitoredTrip), - otpUser, - HttpMethod.POST - ); - - // Reset mocks after POST, because the next call to this function will need it. - // (The mocks will be also reset in the @AfterEach phase if there are any failures.) - OtpTestUtils.resetOtpMocks(); - - assertEquals(HttpStatus.OK_200, createTripResponse.status); + monitoredTrip.tripTime = "08:35"; + monitoredTrip.queryParams = MonitoredTripTest.UI_QUERY_PARAMS; + monitoredTrip.itinerary = new Itinerary(); + monitoredTrip.itinerary.startTime = new Date(); + monitoredTrip.itineraryExistence = new ItineraryExistence(); + monitoredTrip.from = new Place(); + monitoredTrip.from.name = "From Place"; + monitoredTrip.to = new Place(); + monitoredTrip.to.name = "To Place"; + + Persistence.monitoredTrips.create(monitoredTrip); } } diff --git a/src/test/java/org/opentripplanner/middleware/models/MonitoredTripTest.java b/src/test/java/org/opentripplanner/middleware/models/MonitoredTripTest.java index 8ec1c21eb..50424d3ce 100644 --- a/src/test/java/org/opentripplanner/middleware/models/MonitoredTripTest.java +++ b/src/test/java/org/opentripplanner/middleware/models/MonitoredTripTest.java @@ -20,11 +20,11 @@ /** * Holds tests for some methods in MonitoredTrip. */ -class MonitoredTripTest { +public class MonitoredTripTest { /** * Abbreviated query params with mode params you would get from UI. */ - private static final String UI_QUERY_PARAMS + public static final String UI_QUERY_PARAMS = "?fromPlace=fromplace%3A%3A28.556631%2C-81.411781&toPlace=toplace%3A%3A28.545925%2C-81.348609&date=2020-11-13&time=14%3A21&arriveBy=false&mode=WALK%2CBUS%2CRAIL&numItineraries=3"; /** From c6bf128ec3d025fda4b78b6d93a3b06ec918f10e Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 24 Jul 2024 11:40:10 -0400 Subject: [PATCH 30/48] test(GetMonitoredTrips): Refactor user db filter --- .../controllers/api/GetMonitoredTripsTest.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/test/java/org/opentripplanner/middleware/controllers/api/GetMonitoredTripsTest.java b/src/test/java/org/opentripplanner/middleware/controllers/api/GetMonitoredTripsTest.java index 2eb4a91dd..ce921c75f 100644 --- a/src/test/java/org/opentripplanner/middleware/controllers/api/GetMonitoredTripsTest.java +++ b/src/test/java/org/opentripplanner/middleware/controllers/api/GetMonitoredTripsTest.java @@ -110,12 +110,14 @@ public static void tearDown() { @AfterEach public void tearDownAfterTest() { - BasicDBObject soloFilter = new BasicDBObject(); - soloFilter.put("userId", soloOtpUser.id); - Persistence.monitoredTrips.removeFiltered(soloFilter); - BasicDBObject multiFilter = new BasicDBObject(); - multiFilter.put("userId", multiOtpUser.id); - Persistence.monitoredTrips.removeFiltered(multiFilter); + Persistence.monitoredTrips.removeFiltered(getUserFilter(soloOtpUser.id)); + Persistence.monitoredTrips.removeFiltered(getUserFilter(multiOtpUser.id)); + } + + private static BasicDBObject getUserFilter(String id) { + BasicDBObject userFilter = new BasicDBObject(); + userFilter.put("userId", id); + return userFilter; } /** @@ -158,8 +160,7 @@ void canPreserveTripFields() throws Exception { // Create a trip for the solo OTP user. createMonitoredTripForUser(soloOtpUser); - BasicDBObject filter = new BasicDBObject(); - filter.put("userId", soloOtpUser.id); + BasicDBObject filter = getUserFilter(soloOtpUser.id); // Expect only 1 trip for solo Otp user. assertEquals(1, Persistence.monitoredTrips.getCountFiltered(filter)); From bc803f4c5db366f899cbee25524275a6f799d275 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 24 Jul 2024 15:52:39 -0400 Subject: [PATCH 31/48] fix(OtpRequestProcessor): Accommodate empty request bodies. --- .../middleware/controllers/api/OtpRequestProcessor.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/middleware/controllers/api/OtpRequestProcessor.java b/src/main/java/org/opentripplanner/middleware/controllers/api/OtpRequestProcessor.java index c8210ab5b..a3b4f7859 100644 --- a/src/main/java/org/opentripplanner/middleware/controllers/api/OtpRequestProcessor.java +++ b/src/main/java/org/opentripplanner/middleware/controllers/api/OtpRequestProcessor.java @@ -273,9 +273,10 @@ private static boolean handlePlanTripResponse( OtpDispatcherResponse otpDispatcherResponse, OtpUser otpUser ) throws JsonProcessingException { + String body = request.body(); return handlePlanTripResponse( request.queryParams("batchId"), - getPOJOFromJSON(request.body(), OtpGraphQLQuery.class).variables, + body.isEmpty() ? new OtpGraphQLVariables() : getPOJOFromJSON(body, OtpGraphQLQuery.class).variables, otpDispatcherResponse, otpUser ); From 38cae82884042ea7cdd0805a8ccaeb5f008fde31 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 24 Jul 2024 15:53:16 -0400 Subject: [PATCH 32/48] test(OtpTestUtils): Wrap mocks in OTP2 response wrapper. --- .../opentripplanner/middleware/testutils/OtpTestUtils.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/opentripplanner/middleware/testutils/OtpTestUtils.java b/src/test/java/org/opentripplanner/middleware/testutils/OtpTestUtils.java index a704ef4ff..fb2dd8b02 100644 --- a/src/test/java/org/opentripplanner/middleware/testutils/OtpTestUtils.java +++ b/src/test/java/org/opentripplanner/middleware/testutils/OtpTestUtils.java @@ -6,6 +6,7 @@ import org.opentripplanner.middleware.otp.OtpDispatcherResponse; import org.opentripplanner.middleware.otp.response.Itinerary; import org.opentripplanner.middleware.otp.response.OtpResponse; +import org.opentripplanner.middleware.otp.response.OtpResponseGraphQLWrapper; import org.opentripplanner.middleware.tripmonitor.JourneyState; import org.opentripplanner.middleware.utils.DateTimeUtils; import org.opentripplanner.middleware.utils.ItineraryUtils; @@ -123,7 +124,9 @@ private static String mockOtpPlanResponse(Request request, Response response) th } LOG.info("Returning mock response at index {}", mockResponseIndex); // send back response and increment response index - String responseBody = mapper.writeValueAsString(mockResponses.get(mockResponseIndex)); + OtpResponseGraphQLWrapper wrapper = new OtpResponseGraphQLWrapper(); + wrapper.data = mockResponses.get(mockResponseIndex); + String responseBody = mapper.writeValueAsString(wrapper); mockResponseIndex++; return responseBody; } From a39431c5a1e01b5d9a78a6d451ed0c5cb5fa4c4a Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 24 Jul 2024 16:02:33 -0400 Subject: [PATCH 33/48] ci(maven): Pass OTP2_API_ROOT to process. --- .github/workflows/maven-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/maven-ci.yml b/.github/workflows/maven-ci.yml index 199b51ef7..56f045b58 100644 --- a/.github/workflows/maven-ci.yml +++ b/.github/workflows/maven-ci.yml @@ -79,6 +79,7 @@ jobs: NOTIFICATION_FROM_PHONE: ${{ secrets.NOTIFICATION_FROM_PHONE }} OTP_ADMIN_DASHBOARD_URL: ${{ secrets.OTP_ADMIN_DASHBOARD_URL }} OTP_API_ROOT: ${{ secrets.OTP_API_ROOT }} + OTP2_API_ROOT: ${{ secrets.OTP_API_ROOT }} OTP_PLAN_ENDPOINT: ${{ secrets.OTP_PLAN_ENDPOINT }} OTP_TIMEZONE: ${{ secrets.OTP_TIMEZONE }} OTP_UI_URL: ${{ secrets.OTP_UI_URL }} From 0d67cab17d0a327a932f661efffdd647652b8fa7 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 24 Jul 2024 17:44:31 -0400 Subject: [PATCH 34/48] refactor(TripRequest): Rename query params field to match MonitoredTrip. --- .../org/opentripplanner/middleware/models/TripRequest.java | 6 +++--- src/main/resources/latest-spark-swagger-output.yaml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/opentripplanner/middleware/models/TripRequest.java b/src/main/java/org/opentripplanner/middleware/models/TripRequest.java index 69fe6f191..1c8daeb38 100644 --- a/src/main/java/org/opentripplanner/middleware/models/TripRequest.java +++ b/src/main/java/org/opentripplanner/middleware/models/TripRequest.java @@ -42,7 +42,7 @@ public class TripRequest extends Model { /** A dictionary of the parameters provided in the request that triggered this response. */ public Map requestParameters; - public OtpGraphQLVariables graphQLVariables; + public OtpGraphQLVariables otp2QueryParams; /** * This no-arg constructor exists to make MongoDB happy. @@ -69,13 +69,13 @@ public TripRequest( String batchId, String fromPlace, String toPlace, - OtpGraphQLVariables graphQLVariables + OtpGraphQLVariables otp2QueryParams ) { this.userId = userId; this.batchId = batchId; this.fromPlace = fromPlace; this.toPlace = toPlace; - this.graphQLVariables = graphQLVariables; + this.otp2QueryParams = otp2QueryParams; } @Override diff --git a/src/main/resources/latest-spark-swagger-output.yaml b/src/main/resources/latest-spark-swagger-output.yaml index 16f2cb91c..ed624ed7f 100644 --- a/src/main/resources/latest-spark-swagger-output.yaml +++ b/src/main/resources/latest-spark-swagger-output.yaml @@ -2836,7 +2836,7 @@ definitions: type: "string" requestParameters: $ref: "#/definitions/Map" - graphQLVariables: + otp2QueryParams: $ref: "#/definitions/OtpGraphQLVariables" OtpGraphQLRoutesAndTrips: type: "object" From 0e1ac33af5327804c9540a82dd6aede18d4df11d Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 26 Jul 2024 16:42:30 -0400 Subject: [PATCH 35/48] refactor(TripRequest): Exclude null fields in TripRequest API response. --- .../org/opentripplanner/middleware/models/TripRequest.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/org/opentripplanner/middleware/models/TripRequest.java b/src/main/java/org/opentripplanner/middleware/models/TripRequest.java index 1c8daeb38..c25e4f4bc 100644 --- a/src/main/java/org/opentripplanner/middleware/models/TripRequest.java +++ b/src/main/java/org/opentripplanner/middleware/models/TripRequest.java @@ -1,5 +1,7 @@ package org.opentripplanner.middleware.models; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; import com.mongodb.client.FindIterable; import org.opentripplanner.middleware.otp.OtpGraphQLVariables; import org.opentripplanner.middleware.persistence.Persistence; @@ -15,6 +17,8 @@ * A trip request represents an OTP UI trip request (initiated by a user) destined for an OpenTripPlanner instance. * otp-middleware stores these trip requests for reporting purposes. */ +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) public class TripRequest extends Model { private static final long serialVersionUID = 1L; private static final Logger LOG = LoggerFactory.getLogger(TripRequest.class); From 519cd5d2836993eef739fed2be232bccecee90d2 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 31 Jul 2024 12:30:44 -0400 Subject: [PATCH 36/48] improvement(OtpGraphQLVariables): Add optional support for GMAP mobilityProfile. --- .../opentripplanner/middleware/otp/OtpGraphQLVariables.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java b/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java index f8527770e..60bf46ace 100644 --- a/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java +++ b/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java @@ -1,8 +1,13 @@ package org.opentripplanner.middleware.otp; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; + import java.util.List; /** OTP 'plan' query variables */ +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) public class OtpGraphQLVariables { public boolean arriveBy; public OtpGraphQLRoutesAndTrips banned; @@ -10,6 +15,7 @@ public class OtpGraphQLVariables { public float carReluctance; public String date; public String fromPlace; + public String mobilityProfile; public List modes; public int numItineraries; public OtpGraphQLRoutesAndTrips preferred; From f9420ead4edb8930810e002873f68a1b3cab8625 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 31 Jul 2024 12:38:46 -0400 Subject: [PATCH 37/48] refactor(OtpGraphQLVariables): Make floats optional. --- .../middleware/otp/OtpGraphQLVariables.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java b/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java index 60bf46ace..590041793 100644 --- a/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java +++ b/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java @@ -11,8 +11,8 @@ public class OtpGraphQLVariables { public boolean arriveBy; public OtpGraphQLRoutesAndTrips banned; - public float bikeReluctance; - public float carReluctance; + public Float bikeReluctance; + public Float carReluctance; public String date; public String fromPlace; public String mobilityProfile; @@ -22,7 +22,7 @@ public class OtpGraphQLVariables { public String time; public String toPlace; public OtpGraphQLRoutesAndTrips unpreferred; - public float walkReluctance; - public float walkSpeed; + public Float walkReluctance; + public Float walkSpeed; public boolean wheelchair; } From 3e1c1bb1e424360fc768f42cd6661829735764dd Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 31 Jul 2024 12:55:07 -0400 Subject: [PATCH 38/48] chore(swagger): Update snapshot. --- src/main/resources/latest-spark-swagger-output.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/resources/latest-spark-swagger-output.yaml b/src/main/resources/latest-spark-swagger-output.yaml index ed624ed7f..492d61676 100644 --- a/src/main/resources/latest-spark-swagger-output.yaml +++ b/src/main/resources/latest-spark-swagger-output.yaml @@ -2869,6 +2869,8 @@ definitions: type: "string" fromPlace: type: "string" + mobilityProfile: + type: "string" modes: type: "array" items: From b38276f6c2674c304497bcba3f401934ae005a8a Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 4 Sep 2024 17:11:28 -0400 Subject: [PATCH 39/48] fix(OtpGraphQLVariables): Extract GraphQL params from MonitoredTrip. --- .../org/opentripplanner/middleware/models/MonitoredTrip.java | 2 +- .../opentripplanner/middleware/otp/OtpGraphQLVariables.java | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/opentripplanner/middleware/models/MonitoredTrip.java b/src/main/java/org/opentripplanner/middleware/models/MonitoredTrip.java index 913c9f4cd..96876318f 100644 --- a/src/main/java/org/opentripplanner/middleware/models/MonitoredTrip.java +++ b/src/main/java/org/opentripplanner/middleware/models/MonitoredTrip.java @@ -259,7 +259,7 @@ public List getItineraryExistenceQueries() { * the itinerary is removed. */ public void initializeFromItineraryAndQueryParams(Request req) throws IllegalArgumentException, JsonProcessingException { - initializeFromItineraryAndQueryParams(OtpGraphQLVariables.fromRequest(req)); + initializeFromItineraryAndQueryParams(OtpGraphQLVariables.fromMonitoredTripRequest(req)); } /** diff --git a/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java b/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java index c23a80746..8a4c8c172 100644 --- a/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java +++ b/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; +import org.opentripplanner.middleware.models.MonitoredTrip; import spark.Request; import java.util.List; @@ -30,8 +31,8 @@ public class OtpGraphQLVariables implements Cloneable { public Float walkSpeed; public boolean wheelchair; - public static OtpGraphQLVariables fromRequest(Request request) throws JsonProcessingException { - return getPOJOFromJSON(request.body(), OtpGraphQLQuery.class).variables; + public static OtpGraphQLVariables fromMonitoredTripRequest(Request request) throws JsonProcessingException { + return getPOJOFromJSON(request.body(), MonitoredTrip.class).otp2QueryParams; } @Override From 6ae040242cfb66640b67ddcd47696d1de1191d5c Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 5 Sep 2024 17:24:58 -0400 Subject: [PATCH 40/48] fix(MonitoredTrip): Don't overwrite graphql modes when initializing trip. --- .../middleware/models/MonitoredTrip.java | 7 --- .../middleware/models/MonitoredTripTest.java | 44 +++++++++++++++++++ 2 files changed, 44 insertions(+), 7 deletions(-) create mode 100644 src/test/java/org/opentripplanner/middleware/models/MonitoredTripTest.java diff --git a/src/main/java/org/opentripplanner/middleware/models/MonitoredTrip.java b/src/main/java/org/opentripplanner/middleware/models/MonitoredTrip.java index 96876318f..382937cdb 100644 --- a/src/main/java/org/opentripplanner/middleware/models/MonitoredTrip.java +++ b/src/main/java/org/opentripplanner/middleware/models/MonitoredTrip.java @@ -8,7 +8,6 @@ import org.opentripplanner.middleware.auth.Permission; import org.opentripplanner.middleware.auth.RequestingUser; import org.opentripplanner.middleware.otp.OtpDispatcherResponse; -import org.opentripplanner.middleware.otp.OtpGraphQLTransportMode; import org.opentripplanner.middleware.otp.OtpGraphQLVariables; import org.opentripplanner.middleware.otp.OtpRequest; import org.opentripplanner.middleware.otp.response.Itinerary; @@ -27,7 +26,6 @@ import java.time.LocalTime; import java.time.ZonedDateTime; import java.util.List; -import java.util.Set; import java.util.function.Function; /** @@ -271,11 +269,6 @@ public void initializeFromItineraryAndQueryParams(OtpGraphQLVariables graphQLVar from = itinerary.legs.get(0).from; to = itinerary.legs.get(lastLegIndex).to; this.otp2QueryParams = graphQLVariables; - - // Update modes in query params with set derived from itinerary. This ensures that, when sending requests to OTP - // for trip monitoring, we are querying the modes retained by the user when they saved the itinerary. - Set modes = ItineraryUtils.deriveModesFromItinerary(itinerary); - graphQLVariables.modes = List.copyOf(modes); this.arriveBy = graphQLVariables.arriveBy; // Ensure the itinerary we store does not contain any realtime info. diff --git a/src/test/java/org/opentripplanner/middleware/models/MonitoredTripTest.java b/src/test/java/org/opentripplanner/middleware/models/MonitoredTripTest.java new file mode 100644 index 000000000..4ba076646 --- /dev/null +++ b/src/test/java/org/opentripplanner/middleware/models/MonitoredTripTest.java @@ -0,0 +1,44 @@ +package org.opentripplanner.middleware.models; + +import org.junit.jupiter.api.Test; +import org.opentripplanner.middleware.otp.OtpGraphQLTransportMode; +import org.opentripplanner.middleware.otp.OtpGraphQLVariables; +import org.opentripplanner.middleware.otp.response.Itinerary; +import org.opentripplanner.middleware.otp.response.Leg; +import org.opentripplanner.middleware.otp.response.Place; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class MonitoredTripTest { + @Test + void initializeFromItineraryAndQueryParamsShouldNotModifyModes() { + // The list of modes is provided by the UI/mobile app client, + // and can (mistakenly?) contain duplicate modes. + var originalModes = Stream + .of("BUS", "TRAM", "RAIL", "FERRY", "BUS", "TRAM") + .map(OtpGraphQLTransportMode::fromModeString) + .collect(Collectors.toList()); + + OtpGraphQLVariables variables = new OtpGraphQLVariables(); + variables.time = "14:53"; + variables.modes = List.copyOf(originalModes); + + Itinerary itinerary = new Itinerary(); + Leg leg = new Leg(); + leg.mode = "BUS"; + leg.from = new Place(); + leg.to = new Place(); + itinerary.legs = List.of(leg); + + MonitoredTrip trip = new MonitoredTrip(); + trip.otp2QueryParams = variables; + trip.itinerary = itinerary; + + trip.initializeFromItineraryAndQueryParams(variables); + assertEquals(originalModes, trip.otp2QueryParams.modes); + } +} From 39eb3a08128df61c03f96d7a7c05ce9887c3968d Mon Sep 17 00:00:00 2001 From: Jym Dyer Date: Thu, 12 Sep 2024 12:30:34 -0700 Subject: [PATCH 41/48] qbd3->qdb2 merge --- .../opentripplanner/middleware/models/MonitoredTrip.java | 9 +-------- .../middleware/otp/OtpGraphQLVariables.java | 5 +++-- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/opentripplanner/middleware/models/MonitoredTrip.java b/src/main/java/org/opentripplanner/middleware/models/MonitoredTrip.java index 913c9f4cd..382937cdb 100644 --- a/src/main/java/org/opentripplanner/middleware/models/MonitoredTrip.java +++ b/src/main/java/org/opentripplanner/middleware/models/MonitoredTrip.java @@ -8,7 +8,6 @@ import org.opentripplanner.middleware.auth.Permission; import org.opentripplanner.middleware.auth.RequestingUser; import org.opentripplanner.middleware.otp.OtpDispatcherResponse; -import org.opentripplanner.middleware.otp.OtpGraphQLTransportMode; import org.opentripplanner.middleware.otp.OtpGraphQLVariables; import org.opentripplanner.middleware.otp.OtpRequest; import org.opentripplanner.middleware.otp.response.Itinerary; @@ -27,7 +26,6 @@ import java.time.LocalTime; import java.time.ZonedDateTime; import java.util.List; -import java.util.Set; import java.util.function.Function; /** @@ -259,7 +257,7 @@ public List getItineraryExistenceQueries() { * the itinerary is removed. */ public void initializeFromItineraryAndQueryParams(Request req) throws IllegalArgumentException, JsonProcessingException { - initializeFromItineraryAndQueryParams(OtpGraphQLVariables.fromRequest(req)); + initializeFromItineraryAndQueryParams(OtpGraphQLVariables.fromMonitoredTripRequest(req)); } /** @@ -271,11 +269,6 @@ public void initializeFromItineraryAndQueryParams(OtpGraphQLVariables graphQLVar from = itinerary.legs.get(0).from; to = itinerary.legs.get(lastLegIndex).to; this.otp2QueryParams = graphQLVariables; - - // Update modes in query params with set derived from itinerary. This ensures that, when sending requests to OTP - // for trip monitoring, we are querying the modes retained by the user when they saved the itinerary. - Set modes = ItineraryUtils.deriveModesFromItinerary(itinerary); - graphQLVariables.modes = List.copyOf(modes); this.arriveBy = graphQLVariables.arriveBy; // Ensure the itinerary we store does not contain any realtime info. diff --git a/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java b/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java index c23a80746..8a4c8c172 100644 --- a/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java +++ b/src/main/java/org/opentripplanner/middleware/otp/OtpGraphQLVariables.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; +import org.opentripplanner.middleware.models.MonitoredTrip; import spark.Request; import java.util.List; @@ -30,8 +31,8 @@ public class OtpGraphQLVariables implements Cloneable { public Float walkSpeed; public boolean wheelchair; - public static OtpGraphQLVariables fromRequest(Request request) throws JsonProcessingException { - return getPOJOFromJSON(request.body(), OtpGraphQLQuery.class).variables; + public static OtpGraphQLVariables fromMonitoredTripRequest(Request request) throws JsonProcessingException { + return getPOJOFromJSON(request.body(), MonitoredTrip.class).otp2QueryParams; } @Override From 0f83cf4759c117db25599468d0c299c419f9fcc0 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 26 Sep 2024 15:11:03 -0400 Subject: [PATCH 42/48] test: Remove OTP1 mock plan response. --- .../ConnectedDataPlatformTest.java | 2 +- .../middleware/testutils/OtpTestUtils.java | 22 +- .../jobs/CheckMonitoredTripTest.java | 8 +- .../jobs/ShouldSkipTripTestCase.java | 4 +- .../middleware/utils/ItineraryUtilsTest.java | 4 +- .../otp/response/planResponse-otp2.json | 107 +- .../middleware/otp/response/planResponse.json | 2923 ----------------- 7 files changed, 84 insertions(+), 2986 deletions(-) delete mode 100644 src/test/resources/org/opentripplanner/middleware/otp/response/planResponse.json diff --git a/src/test/java/org/opentripplanner/middleware/connecteddataplatform/ConnectedDataPlatformTest.java b/src/test/java/org/opentripplanner/middleware/connecteddataplatform/ConnectedDataPlatformTest.java index c772a253a..46331d1b1 100644 --- a/src/test/java/org/opentripplanner/middleware/connecteddataplatform/ConnectedDataPlatformTest.java +++ b/src/test/java/org/opentripplanner/middleware/connecteddataplatform/ConnectedDataPlatformTest.java @@ -428,7 +428,7 @@ void canHandleMissingPlaceCoordinates() throws Exception { tripRequests.clear(); tripRequests.add(tripRequestOne); - OtpResponse planResponse = OtpTestUtils.OTP_DISPATCHER_PLAN_RESPONSE.getResponse(); + OtpResponse planResponse = OtpTestUtils.OTP2_DISPATCHER_PLAN_RESPONSE.getResponse(); for (Itinerary itinerary : planResponse.plan.itineraries) { for (Leg leg : itinerary.legs) { // Set all legs to transit so that the coordinates are extracted. diff --git a/src/test/java/org/opentripplanner/middleware/testutils/OtpTestUtils.java b/src/test/java/org/opentripplanner/middleware/testutils/OtpTestUtils.java index bb91b435b..4416cbbaa 100644 --- a/src/test/java/org/opentripplanner/middleware/testutils/OtpTestUtils.java +++ b/src/test/java/org/opentripplanner/middleware/testutils/OtpTestUtils.java @@ -46,8 +46,13 @@ public class OtpTestUtils { ) ); - /** - * The response contains an itinerary with a request with the following request parameters: + /** Contains an OTP response with no itinerary found. */ + public static final OtpDispatcherResponse OTP_DISPATCHER_PLAN_ERROR_RESPONSE = + initializeMockPlanResponse("otp/response/planErrorResponse.json"); + + + /** OTP2 plan mock response. + * Contains an itinerary with a request with the following request parameters: * - arriveBy: false * - date: 2020-06-09 (a Tuesday) * - desired start time: 08:35 @@ -56,15 +61,6 @@ public class OtpTestUtils { * - toPlace: Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204::45.51639151281627,-122.67681483620306 * - first itinerary end time: 8:58:44am */ - public static final OtpDispatcherResponse OTP_DISPATCHER_PLAN_RESPONSE = - initializeMockPlanResponse("otp/response/planResponse.json"); - - /** Contains an OTP response with no itinerary found. */ - public static final OtpDispatcherResponse OTP_DISPATCHER_PLAN_ERROR_RESPONSE = - initializeMockPlanResponse("otp/response/planErrorResponse.json"); - - - /** OTP2 plan mock response. */ public static final OtpDispatcherResponse OTP2_DISPATCHER_PLAN_RESPONSE = initializeMockPlanResponse("otp/response/planResponse-otp2.json"); @@ -139,7 +135,7 @@ private static String mockOtpPlanResponse(Request request, Response response) th // mocks not setup, simply return from a file every time LOG.info("Returning default mock response from file"); - return OTP_DISPATCHER_PLAN_RESPONSE.responseBody; + return OTP2_DISPATCHER_PLAN_RESPONSE.responseBody; } /** @@ -218,7 +214,7 @@ public static void updateBaseItineraryTime(Itinerary mockItinerary, ZonedDateTim } public static Itinerary createDefaultItinerary() throws Exception { - return OTP_DISPATCHER_PLAN_RESPONSE.clone().getResponse().plan.itineraries.get(0); + return OTP2_DISPATCHER_PLAN_RESPONSE.clone().getResponse().plan.itineraries.get(0); } public static JourneyState createDefaultJourneyState() throws Exception { diff --git a/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java b/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java index 788abc5c2..5986a74d6 100644 --- a/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java +++ b/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java @@ -87,7 +87,7 @@ public void tearDownAfterTest() { public OtpResponse mockOtpPlanResponse() { try { // Setup an OTP mock response in order to trigger some of the monitor checks. - return OtpTestUtils.OTP_DISPATCHER_PLAN_RESPONSE.getResponse(); + return OtpTestUtils.OTP2_DISPATCHER_PLAN_RESPONSE.getResponse(); } catch (JsonProcessingException e) { throw new RuntimeException(e); } @@ -101,7 +101,7 @@ public OtpResponse mockOtpPlanResponse() { void canMonitorTrip() throws Exception { MonitoredTrip monitoredTrip = PersistenceTestUtils.createMonitoredTrip( user.id, - OtpTestUtils.OTP_DISPATCHER_PLAN_RESPONSE.clone(), + OtpTestUtils.OTP2_DISPATCHER_PLAN_RESPONSE.clone(), false, OtpTestUtils.createDefaultJourneyState() ); @@ -227,7 +227,7 @@ private static CheckMonitoredTrip createCheckMonitoredTrip(Supplier private static CheckMonitoredTrip createCheckMonitoredTrip(JourneyState journeyState, Supplier otpResponseProvider) throws Exception { MonitoredTrip monitoredTrip = PersistenceTestUtils.createMonitoredTrip( user.id, - OtpTestUtils.OTP_DISPATCHER_PLAN_RESPONSE.clone(), + OtpTestUtils.OTP2_DISPATCHER_PLAN_RESPONSE.clone(), false, journeyState ); @@ -257,7 +257,7 @@ static List createSkipTripTestCases() throws Exception { // - Return true for weekend trip when current time is on a weekday. MonitoredTrip weekendTrip = PersistenceTestUtils.createMonitoredTrip( user.id, - OtpTestUtils.OTP_DISPATCHER_PLAN_RESPONSE, + OtpTestUtils.OTP2_DISPATCHER_PLAN_RESPONSE, true, OtpTestUtils.createDefaultJourneyState() ); diff --git a/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/ShouldSkipTripTestCase.java b/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/ShouldSkipTripTestCase.java index 080db1a02..3f8b03cf5 100644 --- a/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/ShouldSkipTripTestCase.java +++ b/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/ShouldSkipTripTestCase.java @@ -50,7 +50,7 @@ public String toString() { public CheckMonitoredTrip generateCheckMonitoredTrip(OtpUser user) throws Exception { // create a mock OTP response for planning a trip on a weekday target datetime - OtpResponse mockWeekdayResponse = OtpTestUtils.OTP_DISPATCHER_PLAN_RESPONSE.getResponse(); + OtpResponse mockWeekdayResponse = OtpTestUtils.OTP2_DISPATCHER_PLAN_RESPONSE.getResponse(); Itinerary mockWeekdayItinerary = mockWeekdayResponse.plan.itineraries.get(0); OtpTestUtils.updateBaseItineraryTime( mockWeekdayItinerary, @@ -62,7 +62,7 @@ public CheckMonitoredTrip generateCheckMonitoredTrip(OtpUser user) throws Except if (trip == null) { trip = PersistenceTestUtils.createMonitoredTrip( user.id, - OtpTestUtils.OTP_DISPATCHER_PLAN_RESPONSE, + OtpTestUtils.OTP2_DISPATCHER_PLAN_RESPONSE, true, OtpTestUtils.createDefaultJourneyState() ); diff --git a/src/test/java/org/opentripplanner/middleware/utils/ItineraryUtilsTest.java b/src/test/java/org/opentripplanner/middleware/utils/ItineraryUtilsTest.java index d8a1cc3fa..d69592189 100644 --- a/src/test/java/org/opentripplanner/middleware/utils/ItineraryUtilsTest.java +++ b/src/test/java/org/opentripplanner/middleware/utils/ItineraryUtilsTest.java @@ -96,7 +96,7 @@ public class ItineraryUtilsTest extends OtpMiddlewareTestEnvironment { /** Contains the verified itinerary set for a trip upon persisting. */ public static Itinerary getDefaultItinerary() throws Exception { - return OtpTestUtils.OTP_DISPATCHER_PLAN_RESPONSE.getResponse().plan.itineraries.get(0); + return OtpTestUtils.OTP2_DISPATCHER_PLAN_RESPONSE.getResponse().plan.itineraries.get(0); } /** @@ -189,7 +189,7 @@ public static List getMockDatedOtpResponses(List dates) thr // Copy the template OTP response itinerary, and change the itinerary date to the monitored date, // in order to pass the same-day itinerary requirement. - OtpResponse resp = OtpTestUtils.OTP_DISPATCHER_PLAN_RESPONSE.getResponse(); + OtpResponse resp = OtpTestUtils.OTP2_DISPATCHER_PLAN_RESPONSE.getResponse(); for (Itinerary itin : resp.plan.itineraries) { itin.startTime = getNewItineraryDate(itin.startTime, monitoredDate); itin.endTime = getNewItineraryDate(itin.endTime, monitoredDate); diff --git a/src/test/resources/org/opentripplanner/middleware/otp/response/planResponse-otp2.json b/src/test/resources/org/opentripplanner/middleware/otp/response/planResponse-otp2.json index 4ca0da0ca..7011fd571 100644 --- a/src/test/resources/org/opentripplanner/middleware/otp/response/planResponse-otp2.json +++ b/src/test/resources/org/opentripplanner/middleware/otp/response/planResponse-otp2.json @@ -71,8 +71,8 @@ "distance": 837.5169999999998, "pathway": false, "mode": "WALK", - "route": "", - "agencyTimeZoneOffset": -25200000, + "route": null, + "agency": null, "interlineWithPreviousLeg": false, "from": { "name": "1709 NW Irving St, Portland 97209", @@ -618,18 +618,27 @@ "distance": 893.8869820964361, "pathway": false, "mode": "TRAM", - "route": "MAX Blue Line", - "agencyName": "TriMet", - "agencyUrl": "https://trimet.org/", - "agencyTimeZoneOffset": -25200000, - "routeColor": "084C8D", - "routeType": 0, - "routeId": "TriMet:100", - "routeTextColor": "FFFFFF", + "route": { + "alerts": [], + "color": "084C8D", + "gtfsId": "TriMet:100", + "id": "TriMet:100", + "longName": "MAX Blue Line", + "shortName": "MAX Blue", + "textColor": "FFFFFF", + "type": 0 + }, + "agency": { + "alerts": [], + "gtfsId": "TRIMET", + "id": "TRIMET", + "name": "TriMet", + "timezone": "America/Los_Angeles", + "url": "https://trimet.org/" + }, "interlineWithPreviousLeg": false, "tripBlockId": "9004", "headsign": "Gresham", - "agencyId": "TRIMET", "tripId": "TriMet:9942889", "serviceDate": "20200609", "from": { @@ -696,7 +705,6 @@ "alertUrl": "http://trimet.org/alerts/" } ], - "routeLongName": "MAX Blue Line", "rentedBike": false, "rentedCar": false, "rentedVehicle": false, @@ -731,8 +739,8 @@ "distance": 418.19, "pathway": false, "mode": "WALK", - "route": "", - "agencyTimeZoneOffset": -25200000, + "route": null, + "agency": null, "interlineWithPreviousLeg": false, "from": { "name": "Pioneer Square South MAX Station", @@ -1040,8 +1048,8 @@ "distance": 578.6750000000001, "pathway": false, "mode": "WALK", - "route": "", - "agencyTimeZoneOffset": -25200000, + "route": null, + "agency": null, "interlineWithPreviousLeg": false, "from": { "name": "1709 NW Irving St, Portland 97209", @@ -1433,18 +1441,27 @@ "distance": 1103.6887432320718, "pathway": false, "mode": "TRAM", - "route": "Portland Streetcar - B Loop", - "agencyName": "Portland Streetcar", - "agencyUrl": "https://portlandstreetcar.org/", - "agencyTimeZoneOffset": -25200000, - "routeColor": "0093B2", - "routeType": 0, - "routeId": "TriMet:195", - "routeTextColor": "FFFFFF", + "route": { + "alerts": [], + "color": "0093B2", + "gtfsId": "TriMet:195", + "id": "TriMet:195", + "longName": "Portland Streetcar - B Loop", + "shortName": "B Loop", + "textColor": "FFFFFF", + "type": 0 + }, + "agency": { + "alerts": [], + "gtfsId": "PSC", + "id": "PSC", + "name": "Portland Streetcar", + "timezone": "America/Los_Angeles", + "url": "https://portlandstreetcar.org/" + }, "interlineWithPreviousLeg": false, "tripBlockId": "58", "headsign": "Lloyd via OMSI", - "agencyId": "PSC", "tripId": "TriMet:9945177", "serviceDate": "20200609", "from": { @@ -1509,7 +1526,6 @@ "alertUrl": "https://portlandstreetcar.org/news/2020/03/streetcar-service-will-reduce-to-20-minute-headways-beginning-march-24" } ], - "routeLongName": "Portland Streetcar - B Loop", "rentedBike": false, "rentedCar": false, "rentedVehicle": false, @@ -1572,8 +1588,8 @@ "distance": 718.4660000000001, "pathway": false, "mode": "WALK", - "route": "", - "agencyTimeZoneOffset": -25200000, + "route": null, + "agency": null, "interlineWithPreviousLeg": false, "from": { "name": "SW 11th & Taylor", @@ -2056,8 +2072,8 @@ "distance": 802.1549999999999, "pathway": false, "mode": "WALK", - "route": "", - "agencyTimeZoneOffset": -25200000, + "route": null, + "agency": null, "interlineWithPreviousLeg": false, "from": { "name": "1709 NW Irving St, Portland 97209", @@ -2610,16 +2626,27 @@ "distance": 1286.9349137891197, "pathway": false, "mode": "BUS", - "route": "15", - "agencyName": "TriMet", - "agencyUrl": "https://trimet.org/", - "agencyTimeZoneOffset": -25200000, - "routeType": 3, - "routeId": "TriMet:15", + "route": { + "alerts": [], + "color": null, + "gtfsId": "TriMet:15", + "id": "TriMet:15", + "longName": "Belmont/NW 23rd", + "shortName": "15", + "textColor": null, + "type": 3 + }, + "agency": { + "alerts": [], + "gtfsId": "TRIMET", + "id": "TRIMET", + "name": "TriMet", + "timezone": "America/Los_Angeles", + "url": "https://trimet.org/" + }, "interlineWithPreviousLeg": false, "tripBlockId": "1505", "headsign": "Gateway TC", - "agencyId": "TRIMET", "tripId": "TriMet:9932776", "serviceDate": "20200609", "from": { @@ -2697,8 +2724,6 @@ "alertUrl": "https://trimet.org/alerts/reducedservice.htm" } ], - "routeShortName": "15", - "routeLongName": "Belmont/NW 23rd", "rentedBike": false, "rentedCar": false, "rentedVehicle": false, @@ -2789,8 +2814,8 @@ "distance": 164.377, "pathway": false, "mode": "WALK", - "route": "", - "agencyTimeZoneOffset": -25200000, + "route": null, + "agency": null, "interlineWithPreviousLeg": false, "from": { "name": "SW Salmon & 5th", diff --git a/src/test/resources/org/opentripplanner/middleware/otp/response/planResponse.json b/src/test/resources/org/opentripplanner/middleware/otp/response/planResponse.json deleted file mode 100644 index 4ca0da0ca..000000000 --- a/src/test/resources/org/opentripplanner/middleware/otp/response/planResponse.json +++ /dev/null @@ -1,2923 +0,0 @@ -{ - "data": { - "plan": { - "date": 1591716900000, - "from": { - "name": "1709 NW Irving St, Portland 97209", - "lon": -122.68865964147231, - "lat": 45.527817334203, - "orig": "1709 NW Irving St, Portland 97209", - "vertexType": "NORMAL" - }, - "to": { - "name": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", - "lon": -122.67681483620306, - "lat": 45.51639151281627, - "orig": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", - "vertexType": "NORMAL" - }, - "itineraries": [ - { - "duration": 1114, - "startTime": 1591717210000, - "endTime": 1591718324000, - "walkTime": 872, - "transitTime": 240, - "waitingTime": 2, - "walkDistance": 1256.0514029764959, - "walkLimitExceeded": false, - "elevationLost": 10.41, - "elevationGained": 15.05, - "transfers": 0, - "fare": { - "fare": { - "regular": { - "currency": { - "symbol": "$", - "currency": "USD", - "defaultFractionDigits": 2, - "currencyCode": "USD" - }, - "cents": 250 - } - }, - "details": { - "regular": [ - { - "fareId": "TriMet:R", - "price": { - "currency": { - "symbol": "$", - "currency": "USD", - "defaultFractionDigits": 2, - "currencyCode": "USD" - }, - "cents": 250 - }, - "routes": [ - "TriMet:100" - ] - } - ] - } - }, - "legs": [ - { - "startTime": 1591717210000, - "endTime": 1591717794000, - "departureDelay": 0, - "arrivalDelay": 0, - "realTime": false, - "distance": 837.5169999999998, - "pathway": false, - "mode": "WALK", - "route": "", - "agencyTimeZoneOffset": -25200000, - "interlineWithPreviousLeg": false, - "from": { - "name": "1709 NW Irving St, Portland 97209", - "lon": -122.68865964147231, - "lat": 45.527817334203, - "departure": 1591717210000, - "orig": "1709 NW Irving St, Portland 97209", - "vertexType": "NORMAL" - }, - "to": { - "name": "Providence Park MAX Station", - "stopId": "TriMet:9758", - "stopCode": "9758", - "lon": -122.689886, - "lat": 45.521321, - "arrival": 1591717794000, - "departure": 1591717795000, - "zoneId": "R", - "stopIndex": 18, - "stopSequence": 19, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - "legGeometry": { - "points": "}c{tGdsykVAu@x@AJ?v@AP?nCE\\?r@Ax@C`BADAd@?lCEp@AdAAV?JA`@?F?jACClAF@B?l@XNFpAj@f@TJDB@B@LFx@\\JDHBH@HBCPFBDBQ~@", - "length": 41 - }, - "rentedBike": false, - "rentedCar": false, - "rentedVehicle": false, - "hailedCar": false, - "flexDrtAdvanceBookMin": 0.0, - "duration": 584.0, - "transitLeg": false, - "intermediateStops": [], - "steps": [ - { - "distance": 20.794, - "relativeDirection": "DEPART", - "streetName": "NW Irving St", - "absoluteDirection": "EAST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.68866035123024, - "lat": 45.527836027296765, - "elevation": [ - { - "first": 0.0, - "second": 20.682842393406283 - }, - { - "first": 10.0, - "second": 20.452842393406282 - }, - { - "first": 20.0, - "second": 20.24284239340628 - }, - { - "first": 20.79, - "second": 20.22284239340628 - } - ] - }, - { - "distance": 547.395, - "relativeDirection": "RIGHT", - "streetName": "NW 17th Ave", - "absoluteDirection": "SOUTH", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6883935, - "lat": 45.527841, - "elevation": [ - { - "first": 0.0, - "second": 18.682842393406283 - }, - { - "first": 10.0, - "second": 18.802842393406284 - }, - { - "first": 20.0, - "second": 19.01284239340628 - }, - { - "first": 31.29, - "second": 19.24284239340628 - }, - { - "first": 31.294, - "second": 19.24284239340628 - }, - { - "first": 39.024, - "second": 19.40284239340628 - }, - { - "first": 39.023, - "second": 19.40284239340628 - }, - { - "first": 49.023, - "second": 19.60284239340628 - }, - { - "first": 59.023, - "second": 19.85284239340628 - }, - { - "first": 69.023, - "second": 19.97284239340628 - }, - { - "first": 79.273, - "second": 20.032842393406284 - }, - { - "first": 79.272, - "second": 20.032842393406284 - }, - { - "first": 89.272, - "second": 20.26284239340628 - }, - { - "first": 99.272, - "second": 20.572842393406283 - }, - { - "first": 109.272, - "second": 20.872842393406284 - }, - { - "first": 119.272, - "second": 21.112842393406282 - }, - { - "first": 129.272, - "second": 21.412842393406283 - }, - { - "first": 139.272, - "second": 21.69284239340628 - }, - { - "first": 149.272, - "second": 22.01284239340628 - }, - { - "first": 159.322, - "second": 22.002842393406283 - }, - { - "first": 159.323, - "second": 22.002842393406283 - }, - { - "first": 169.323, - "second": 22.17284239340628 - }, - { - "first": 176.933, - "second": 22.352842393406284 - }, - { - "first": 176.93, - "second": 22.352842393406284 - }, - { - "first": 186.93, - "second": 22.58284239340628 - }, - { - "first": 196.93, - "second": 22.792842393406282 - }, - { - "first": 205.41, - "second": 22.952842393406282 - }, - { - "first": 205.41, - "second": 22.952842393406282 - }, - { - "first": 215.41, - "second": 23.15284239340628 - }, - { - "first": 225.41, - "second": 23.302842393406284 - }, - { - "first": 237.51, - "second": 23.42284239340628 - }, - { - "first": 237.505, - "second": 23.42284239340628 - }, - { - "first": 247.505, - "second": 23.502842393406283 - }, - { - "first": 257.505, - "second": 23.632842393406282 - }, - { - "first": 267.505, - "second": 23.76284239340628 - }, - { - "first": 277.505, - "second": 23.90284239340628 - }, - { - "first": 292.145, - "second": 24.092842393406283 - }, - { - "first": 292.143, - "second": 24.092842393406283 - }, - { - "first": 295.53299999999996, - "second": 24.132842393406282 - }, - { - "first": 295.53499999999997, - "second": 24.132842393406282 - }, - { - "first": 305.53499999999997, - "second": 24.432842393406283 - }, - { - "first": 316.635, - "second": 24.392842393406283 - }, - { - "first": 316.63599999999997, - "second": 24.392842393406283 - }, - { - "first": 326.63599999999997, - "second": 24.522842393406282 - }, - { - "first": 336.63599999999997, - "second": 24.792842393406282 - }, - { - "first": 346.63599999999997, - "second": 25.052842393406284 - }, - { - "first": 356.63599999999997, - "second": 25.31284239340628 - }, - { - "first": 366.63599999999997, - "second": 25.602842393406284 - }, - { - "first": 376.63599999999997, - "second": 25.842842393406283 - }, - { - "first": 386.63599999999997, - "second": 25.982842393406283 - }, - { - "first": 396.02599999999995, - "second": 26.01284239340628 - }, - { - "first": 396.03, - "second": 26.01284239340628 - }, - { - "first": 406.03, - "second": 26.042842393406282 - }, - { - "first": 416.03, - "second": 26.092842393406283 - }, - { - "first": 423.69, - "second": 26.15284239340628 - }, - { - "first": 423.686, - "second": 26.15284239340628 - }, - { - "first": 433.686, - "second": 26.252842393406283 - }, - { - "first": 443.686, - "second": 26.31284239340628 - }, - { - "first": 453.686, - "second": 26.362842393406282 - }, - { - "first": 461.936, - "second": 26.40284239340628 - }, - { - "first": 461.93699999999995, - "second": 26.40284239340628 - }, - { - "first": 475.08699999999993, - "second": 26.19284239340628 - }, - { - "first": 475.08199999999994, - "second": 26.19284239340628 - }, - { - "first": 485.08199999999994, - "second": 25.732842393406283 - }, - { - "first": 495.08199999999994, - "second": 25.142842393406283 - }, - { - "first": 501.5519999999999, - "second": 24.76284239340628 - }, - { - "first": 501.54999999999995, - "second": 24.76284239340628 - }, - { - "first": 505.17999999999995, - "second": 24.602842393406284 - }, - { - "first": 505.17699999999996, - "second": 24.602842393406284 - }, - { - "first": 515.1769999999999, - "second": 24.12284239340628 - }, - { - "first": 525.1769999999999, - "second": 23.62284239340628 - }, - { - "first": 535.1769999999999, - "second": 23.292842393406282 - }, - { - "first": 547.3969999999999, - "second": 23.51284239340628 - } - ] - }, - { - "distance": 30.847, - "relativeDirection": "RIGHT", - "streetName": "W Burnside St", - "absoluteDirection": "WEST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.688213, - "lat": 45.5229198, - "elevation": [ - { - "first": 0.0, - "second": 23.51284239340628 - }, - { - "first": 10.0, - "second": 23.76284239340628 - }, - { - "first": 20.0, - "second": 24.052842393406284 - }, - { - "first": 30.85, - "second": 24.302842393406284 - } - ] - }, - { - "distance": 195.61100000000002, - "relativeDirection": "LEFT", - "streetName": "SW 17th Ave", - "absoluteDirection": "SOUTH", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6886081, - "lat": 45.522938, - "elevation": [ - { - "first": 0.0, - "second": 24.302842393406284 - }, - { - "first": 10.0, - "second": 24.532842393406284 - }, - { - "first": 20.0, - "second": 25.182842393406283 - }, - { - "first": 30.0, - "second": 25.802842393406284 - }, - { - "first": 44.36, - "second": 26.032842393406284 - }, - { - "first": 44.347, - "second": 26.032842393406284 - }, - { - "first": 54.347, - "second": 26.452842393406282 - }, - { - "first": 64.34700000000001, - "second": 27.282842393406284 - }, - { - "first": 74.34700000000001, - "second": 27.952842393406282 - }, - { - "first": 84.34700000000001, - "second": 28.132842393406282 - }, - { - "first": 92.137, - "second": 28.292842393406282 - }, - { - "first": 92.13900000000001, - "second": 28.292842393406282 - }, - { - "first": 102.13900000000001, - "second": 28.80284239340628 - }, - { - "first": 112.13900000000001, - "second": 29.30284239340628 - }, - { - "first": 123.74900000000001, - "second": 29.822842393406283 - }, - { - "first": 123.75200000000001, - "second": 29.822842393406283 - }, - { - "first": 136.752, - "second": 30.352842393406284 - } - ] - }, - { - "distance": 7.173, - "relativeDirection": "RIGHT", - "streetName": "path", - "absoluteDirection": "WEST", - "stayOn": false, - "area": false, - "bogusName": true, - "lon": -122.689436, - "lat": 45.521281, - "elevation": [ - { - "first": 0.0, - "second": 32.14284239340628 - }, - { - "first": 7.17, - "second": 32.092842393406286 - } - ] - }, - { - "distance": 8.612, - "relativeDirection": "LEFT", - "streetName": "path", - "absoluteDirection": "SOUTH", - "stayOn": true, - "area": false, - "bogusName": true, - "lon": -122.6895213, - "lat": 45.5213053, - "elevation": [ - { - "first": 0.0, - "second": 32.092842393406286 - }, - { - "first": 8.61, - "second": 32.202842393406286 - } - ] - }, - { - "distance": 27.085, - "relativeDirection": "RIGHT", - "streetName": "Providence Park (path)", - "absoluteDirection": "WEST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6895617, - "lat": 45.5212332, - "elevation": [] - } - ] - }, - { - "startTime": 1591717795000, - "endTime": 1591718035000, - "departureDelay": 0, - "arrivalDelay": 0, - "realTime": false, - "distance": 893.8869820964361, - "pathway": false, - "mode": "TRAM", - "route": "MAX Blue Line", - "agencyName": "TriMet", - "agencyUrl": "https://trimet.org/", - "agencyTimeZoneOffset": -25200000, - "routeColor": "084C8D", - "routeType": 0, - "routeId": "TriMet:100", - "routeTextColor": "FFFFFF", - "interlineWithPreviousLeg": false, - "tripBlockId": "9004", - "headsign": "Gresham", - "agencyId": "TRIMET", - "tripId": "TriMet:9942889", - "serviceDate": "20200609", - "from": { - "name": "Providence Park MAX Station", - "stopId": "TriMet:9758", - "stopCode": "9758", - "lon": -122.689886, - "lat": 45.521321, - "arrival": 1591717794000, - "departure": 1591717795000, - "zoneId": "R", - "stopIndex": 18, - "stopSequence": 19, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - "to": { - "name": "Pioneer Square South MAX Station", - "stopId": "TriMet:8334", - "stopCode": "8334", - "lon": -122.679145, - "lat": 45.518496, - "arrival": 1591718035000, - "departure": 1591718036000, - "zoneId": "R", - "stopIndex": 20, - "stopSequence": 21, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - "legGeometry": { - "points": "m{ytGtzykV@AN}@BQ@EJm@Lk@PcAF[l@mDDQBMn@uDDUj@gDDSl@mDFWDW\\oBHe@@KBI@I@EF]f@uCBQBI@KTqAP}@@IDS@GTqABSBKn@sDBQf@qC", - "length": 41 - }, - "interStopGeometry": [ - { - "points": "m{ytGtzykV@AN}@BQ@EJm@Lk@PcAF[l@mDDQBMn@uDDUj@gDDSl@mDFWDW\\oBHe@@KBI@I@EF]f@uCBQBI@KTqAP}@", - "length": 32 - }, - { - "points": "qmytGfgxkV@IDS@GTqABSBKn@sDBQf@qC", - "length": 10 - } - ], - "alerts": [ - { - "effectiveStartDate": 1591959600000, - "alertDescriptionText": "A scarf, bandanna or fabric mask works, as long as it covers your nose and mouth.", - "alertUrl": "http://trimet.org/health" - }, - { - "effectiveStartDate": 1585876166000, - "alertDescriptionText": "Due to COVID-19 service reductions, beginning Sunday, April 5th, on weekdays, trains will run every 15 minutes throughout most of the day. On weekends will run on Sunday schedule. For arrivals and trip planning, see:", - "alertUrl": "http://trimet.org/reducedservice" - }, - { - "effectiveStartDate": 1572827580000, - "alertDescriptionText": "The east elevators (zoo side) at Washington Park MAX Station are closed for improvements until early May. For access between platform and ground levels, use the west elevators (world forestry side). ", - "alertUrl": "https://news.trimet.org/2019/08/trimet-to-replace-elevators-at-the-deepest-transit-station-in-north-america/" - }, - { - "effectiveStartDate": 1591965804000, - "alertDescriptionText": "MAX Blue and Red lines disrupted due to police activity near Goose Hollow/SW Jefferson. Shuttle buses serving stations between Library/SW 9th and Galleria/SW 10th and Sunset Transit Center. Expect delays.", - "alertUrl": "http://trimet.org/alerts/" - } - ], - "routeLongName": "MAX Blue Line", - "rentedBike": false, - "rentedCar": false, - "rentedVehicle": false, - "hailedCar": false, - "flexDrtAdvanceBookMin": 0.0, - "duration": 240.0, - "transitLeg": true, - "intermediateStops": [ - { - "name": "Library/SW 9th Ave MAX Station", - "stopId": "TriMet:8333", - "stopCode": "8333", - "lon": -122.68162, - "lat": 45.51916, - "arrival": 1591717940000, - "departure": 1591717975000, - "zoneId": "R", - "stopIndex": 19, - "stopSequence": 20, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - } - ], - "steps": [] - }, - { - "startTime": 1591718036000, - "endTime": 1591718324000, - "departureDelay": 0, - "arrivalDelay": 0, - "realTime": false, - "distance": 418.19, - "pathway": false, - "mode": "WALK", - "route": "", - "agencyTimeZoneOffset": -25200000, - "interlineWithPreviousLeg": false, - "from": { - "name": "Pioneer Square South MAX Station", - "stopId": "TriMet:8334", - "stopCode": "8334", - "lon": -122.679145, - "lat": 45.518496, - "arrival": 1591718035000, - "departure": 1591718036000, - "zoneId": "R", - "stopIndex": 20, - "stopSequence": 21, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - "to": { - "name": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", - "lon": -122.67681483620306, - "lat": 45.51639151281627, - "arrival": 1591718324000, - "orig": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", - "vertexType": "NORMAL" - }, - "legGeometry": { - "points": "uiytGrwwkVDQBODBDB@G@Kn@iDBO@KLF|Ap@FBNHLF|Ar@FDNFBOh@{CD[FWb@eC", - "length": 23 - }, - "rentedBike": false, - "rentedCar": false, - "rentedVehicle": false, - "hailedCar": false, - "flexDrtAdvanceBookMin": 0.0, - "duration": 288.0, - "transitLeg": false, - "intermediateStops": [], - "steps": [ - { - "distance": 7.715, - "relativeDirection": "DEPART", - "streetName": "Pioneer Courthouse Sq (pedestrian street)", - "absoluteDirection": "EAST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.67913709381972, - "lat": 45.51851024632791, - "elevation": [] - }, - { - "distance": 6.217, - "relativeDirection": "CONTINUE", - "streetName": "path", - "absoluteDirection": "EAST", - "stayOn": false, - "area": false, - "bogusName": true, - "lon": -122.6790448, - "lat": 45.5184851, - "elevation": [ - { - "first": 0.0, - "second": 15.172842393406283 - }, - { - "first": 6.22, - "second": 15.002842393406283 - } - ] - }, - { - "distance": 7.252, - "relativeDirection": "RIGHT", - "streetName": "SW 6th Ave", - "absoluteDirection": "SOUTH", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6789697, - "lat": 45.5184662, - "elevation": [ - { - "first": 0.0, - "second": 15.002842393406283 - }, - { - "first": 7.25, - "second": 15.112842393406282 - } - ] - }, - { - "distance": 90.85300000000001, - "relativeDirection": "LEFT", - "streetName": "SW Yamhill St", - "absoluteDirection": "EAST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6790039, - "lat": 45.5184056, - "elevation": [ - { - "first": 0.0, - "second": 15.112842393406282 - }, - { - "first": 8.41, - "second": 15.092842393406283 - }, - { - "first": 8.407, - "second": 15.092842393406283 - }, - { - "first": 18.407, - "second": 14.942842393406282 - }, - { - "first": 28.407, - "second": 14.672842393406283 - }, - { - "first": 38.407, - "second": 14.392842393406283 - }, - { - "first": 48.407, - "second": 14.192842393406282 - }, - { - "first": 58.407, - "second": 13.772842393406282 - }, - { - "first": 68.407, - "second": 13.452842393406282 - }, - { - "first": 79.467, - "second": 13.182842393406283 - }, - { - "first": 79.465, - "second": 13.182842393406283 - }, - { - "first": 90.855, - "second": 12.942842393406282 - } - ] - }, - { - "distance": 156.99599999999998, - "relativeDirection": "RIGHT", - "streetName": "SW 5th Ave", - "absoluteDirection": "SOUTH", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.677916, - "lat": 45.5181117, - "elevation": [] - }, - { - "distance": 149.157, - "relativeDirection": "LEFT", - "streetName": "SW Salmon St", - "absoluteDirection": "EAST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6786441, - "lat": 45.5167953, - "elevation": [ - { - "first": 0.0, - "second": 17.552842393406284 - }, - { - "first": 10.0, - "second": 17.292842393406282 - }, - { - "first": 20.0, - "second": 16.962842393406284 - }, - { - "first": 30.0, - "second": 16.642842393406283 - }, - { - "first": 40.0, - "second": 16.162842393406283 - }, - { - "first": 50.0, - "second": 15.712842393406282 - }, - { - "first": 60.0, - "second": 15.122842393406282 - }, - { - "first": 70.0, - "second": 15.062842393406282 - }, - { - "first": 83.14, - "second": 14.982842393406283 - }, - { - "first": 83.144, - "second": 14.982842393406283 - }, - { - "first": 93.01400000000001, - "second": 14.762842393406283 - }, - { - "first": 93.01700000000001, - "second": 14.762842393406283 - }, - { - "first": 103.01700000000001, - "second": 14.502842393406283 - }, - { - "first": 113.01700000000001, - "second": 14.272842393406282 - }, - { - "first": 123.01700000000001, - "second": 13.872842393406282 - }, - { - "first": 133.017, - "second": 13.632842393406282 - }, - { - "first": 143.017, - "second": 13.412842393406283 - }, - { - "first": 149.157, - "second": 13.152842393406281 - } - ] - } - ] - } - ], - "tooSloped": false - }, - { - "duration": 1242, - "startTime": 1591717148000, - "endTime": 1591718390000, - "walkTime": 820, - "transitTime": 420, - "waitingTime": 2, - "walkDistance": 1297.4104029783646, - "walkLimitExceeded": false, - "elevationLost": 28.980000000000004, - "elevationGained": 1.1200000000000003, - "transfers": 0, - "fare": { - "fare": { - "regular": { - "currency": { - "symbol": "$", - "currency": "USD", - "defaultFractionDigits": 2, - "currencyCode": "USD" - }, - "cents": 200 - } - }, - "details": { - "regular": [ - { - "fareId": "TriMet:SC", - "price": { - "currency": { - "symbol": "$", - "currency": "USD", - "defaultFractionDigits": 2, - "currencyCode": "USD" - }, - "cents": 200 - }, - "routes": [ - "TriMet:195" - ] - } - ] - } - }, - "legs": [ - { - "startTime": 1591717148000, - "endTime": 1591717499000, - "departureDelay": 0, - "arrivalDelay": 0, - "realTime": false, - "distance": 578.6750000000001, - "pathway": false, - "mode": "WALK", - "route": "", - "agencyTimeZoneOffset": -25200000, - "interlineWithPreviousLeg": false, - "from": { - "name": "1709 NW Irving St, Portland 97209", - "lon": -122.68865964147231, - "lat": 45.527817334203, - "departure": 1591717148000, - "orig": "1709 NW Irving St, Portland 97209", - "vertexType": "NORMAL" - }, - "to": { - "name": "NW 11th & Johnson", - "stopId": "TriMet:10753", - "stopCode": "10753", - "lon": -122.682374, - "lat": 45.528742, - "arrival": 1591717499000, - "departure": 1591717500000, - "zoneId": "B", - "stopIndex": 1, - "stopSequence": 2, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - "legGeometry": { - "points": "}c{tGdsykVAu@qBB[@CiECkE???e@AoCAS?Y?kAAsA?Q?QAeD?QASAeDMA?G", - "length": 21 - }, - "rentedBike": false, - "rentedCar": false, - "rentedVehicle": false, - "hailedCar": false, - "flexDrtAdvanceBookMin": 0.0, - "duration": 351.0, - "transitLeg": false, - "intermediateStops": [], - "steps": [ - { - "distance": 20.794, - "relativeDirection": "DEPART", - "streetName": "NW Irving St", - "absoluteDirection": "EAST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.68866035123024, - "lat": 45.527836027296765, - "elevation": [ - { - "first": 0.0, - "second": 20.682842393406283 - }, - { - "first": 10.0, - "second": 20.452842393406282 - }, - { - "first": 20.0, - "second": 20.24284239340628 - }, - { - "first": 20.79, - "second": 20.22284239340628 - } - ] - }, - { - "distance": 79.476, - "relativeDirection": "LEFT", - "streetName": "NW 17th Ave", - "absoluteDirection": "NORTH", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6883935, - "lat": 45.527841, - "elevation": [ - { - "first": 0.0, - "second": 18.682842393406283 - }, - { - "first": 10.0, - "second": 18.612842393406282 - }, - { - "first": 20.0, - "second": 18.47284239340628 - }, - { - "first": 30.0, - "second": 18.272842393406282 - }, - { - "first": 40.0, - "second": 18.08284239340628 - }, - { - "first": 50.0, - "second": 17.90284239340628 - }, - { - "first": 63.93, - "second": 17.632842393406282 - }, - { - "first": 63.928, - "second": 17.632842393406282 - }, - { - "first": 73.928, - "second": 17.412842393406283 - }, - { - "first": 79.478, - "second": 17.362842393406282 - } - ] - }, - { - "distance": 467.456, - "relativeDirection": "RIGHT", - "streetName": "NW Johnson St", - "absoluteDirection": "EAST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6884207, - "lat": 45.5285555, - "elevation": [ - { - "first": 0.0, - "second": 17.362842393406282 - }, - { - "first": 10.0, - "second": 17.122842393406284 - }, - { - "first": 20.0, - "second": 17.002842393406283 - }, - { - "first": 30.0, - "second": 16.632842393406282 - }, - { - "first": 40.0, - "second": 16.372842393406284 - }, - { - "first": 50.0, - "second": 16.15284239340628 - }, - { - "first": 60.0, - "second": 15.932842393406283 - }, - { - "first": 70.0, - "second": 15.692842393406282 - }, - { - "first": 78.6, - "second": 15.642842393406283 - }, - { - "first": 78.602, - "second": 15.642842393406283 - }, - { - "first": 88.602, - "second": 15.662842393406283 - }, - { - "first": 98.602, - "second": 15.952842393406282 - }, - { - "first": 108.602, - "second": 16.112842393406282 - }, - { - "first": 118.602, - "second": 16.072842393406283 - }, - { - "first": 128.602, - "second": 15.942842393406282 - }, - { - "first": 138.602, - "second": 15.492842393406281 - }, - { - "first": 148.602, - "second": 14.812842393406282 - }, - { - "first": 157.762, - "second": 14.452842393406282 - }, - { - "first": 157.757, - "second": 14.452842393406282 - }, - { - "first": 167.757, - "second": 14.222842393406282 - }, - { - "first": 177.757, - "second": 14.022842393406282 - }, - { - "first": 187.757, - "second": 13.782842393406282 - }, - { - "first": 197.757, - "second": 13.582842393406281 - }, - { - "first": 207.757, - "second": 13.422842393406283 - }, - { - "first": 217.757, - "second": 13.102842393406283 - }, - { - "first": 227.757, - "second": 12.842842393406283 - }, - { - "first": 236.787, - "second": 12.762842393406283 - }, - { - "first": 236.78, - "second": 12.762842393406283 - }, - { - "first": 246.78, - "second": 12.462842393406284 - }, - { - "first": 256.78, - "second": 12.312842393406282 - }, - { - "first": 266.78, - "second": 12.132842393406282 - }, - { - "first": 276.35, - "second": 11.912842393406283 - }, - { - "first": 276.349, - "second": 11.912842393406283 - }, - { - "first": 286.349, - "second": 11.632842393406282 - }, - { - "first": 296.349, - "second": 11.462842393406282 - }, - { - "first": 306.349, - "second": 11.182842393406283 - }, - { - "first": 315.839, - "second": 10.942842393406282 - }, - { - "first": 315.841, - "second": 10.942842393406282 - }, - { - "first": 325.841, - "second": 10.892842393406282 - }, - { - "first": 335.841, - "second": 10.692842393406282 - }, - { - "first": 345.841, - "second": 10.582842393406283 - }, - { - "first": 355.841, - "second": 10.382842393406282 - }, - { - "first": 365.841, - "second": 10.112842393406282 - }, - { - "first": 375.841, - "second": 9.802842393406282 - }, - { - "first": 385.841, - "second": 9.522842393406282 - }, - { - "first": 395.041, - "second": 9.362842393406282 - }, - { - "first": 395.038, - "second": 9.362842393406282 - }, - { - "first": 405.038, - "second": 9.432842393406283 - }, - { - "first": 415.038, - "second": 9.632842393406282 - }, - { - "first": 425.038, - "second": 9.832842393406283 - }, - { - "first": 435.038, - "second": 9.882842393406282 - }, - { - "first": 445.038, - "second": 9.912842393406283 - }, - { - "first": 455.038, - "second": 9.872842393406282 - }, - { - "first": 467.458, - "second": 9.702842393406282 - } - ] - }, - { - "distance": 7.603, - "relativeDirection": "LEFT", - "streetName": "path", - "absoluteDirection": "NORTH", - "stayOn": false, - "area": false, - "bogusName": true, - "lon": -122.6824215, - "lat": 45.5286553, - "elevation": [ - { - "first": 0.0, - "second": 9.702842393406282 - }, - { - "first": 7.6, - "second": 9.642842393406282 - } - ] - }, - { - "distance": 3.346, - "relativeDirection": "RIGHT", - "streetName": "path", - "absoluteDirection": "EAST", - "stayOn": true, - "area": false, - "bogusName": true, - "lon": -122.6824167, - "lat": 45.5287236, - "elevation": [] - } - ] - }, - { - "startTime": 1591717500000, - "endTime": 1591717920000, - "departureDelay": 0, - "arrivalDelay": 0, - "realTime": false, - "distance": 1103.6887432320718, - "pathway": false, - "mode": "TRAM", - "route": "Portland Streetcar - B Loop", - "agencyName": "Portland Streetcar", - "agencyUrl": "https://portlandstreetcar.org/", - "agencyTimeZoneOffset": -25200000, - "routeColor": "0093B2", - "routeType": 0, - "routeId": "TriMet:195", - "routeTextColor": "FFFFFF", - "interlineWithPreviousLeg": false, - "tripBlockId": "58", - "headsign": "Lloyd via OMSI", - "agencyId": "PSC", - "tripId": "TriMet:9945177", - "serviceDate": "20200609", - "from": { - "name": "NW 11th & Johnson", - "stopId": "TriMet:10753", - "stopCode": "10753", - "lon": -122.682374, - "lat": 45.528742, - "arrival": 1591717499000, - "departure": 1591717500000, - "zoneId": "B", - "stopIndex": 1, - "stopSequence": 2, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - "to": { - "name": "SW 11th & Taylor", - "stopId": "TriMet:9633", - "stopCode": "9633", - "lon": -122.683873, - "lat": 45.519059, - "arrival": 1591717920000, - "departure": 1591717921000, - "zoneId": "B", - "stopIndex": 5, - "stopSequence": 6, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - "legGeometry": { - "points": "si{tGrkxkVP?dACtEGZ?~ACB?L?J?`CE~BCLAn@?~ACvBCFAL?J?rBCL?vBCLAF?D?D@B@lBx@LF`Bt@HBLFHDtB~@DB@?DBFB`Br@DBFB??FBlBz@", - "length": 43 - }, - "interStopGeometry": [ - { - "points": "si{tGrkxkVP?dACtEGZ?~AC", - "length": 6 - }, - { - "points": "i|ztGbkxkVB?L?J?`CE~BCLAn@?~ACvBC", - "length": 10 - }, - { - "points": "sjztGnjxkVFAL?J?rBCL?vBCLAF?D?D@B@lBx@LF`Bt@", - "length": 15 - }, - { - "points": "syytG~mxkVHBLFHDtB~@DB@?DBFB`Br@DBFB??FBlBz@", - "length": 15 - } - ], - "alerts": [ - { - "effectiveStartDate": 1591959600000, - "alertDescriptionText": "A scarf, bandanna or fabric mask works, as long as it covers your nose and mouth.", - "alertUrl": "http://trimet.org/health" - }, - { - "effectiveStartDate": 1585093573000, - "alertDescriptionText": "Streetcar has reduced regular weekday service to every 20 minutes between about 5:30 a.m. and about 11:30 p.m. For more info:", - "alertUrl": "https://portlandstreetcar.org/news/2020/03/streetcar-service-will-reduce-to-20-minute-headways-beginning-march-24" - } - ], - "routeLongName": "Portland Streetcar - B Loop", - "rentedBike": false, - "rentedCar": false, - "rentedVehicle": false, - "hailedCar": false, - "flexDrtAdvanceBookMin": 0.0, - "duration": 420.0, - "transitLeg": true, - "intermediateStops": [ - { - "name": "NW 11th & Glisan", - "stopId": "TriMet:10754", - "stopCode": "10754", - "lon": -122.682297, - "lat": 45.526605, - "arrival": 1591717560000, - "departure": 1591717560000, - "zoneId": "B", - "stopIndex": 2, - "stopSequence": 3, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - { - "name": "NW 11th & Couch", - "stopId": "TriMet:10756", - "stopCode": "10756", - "lon": -122.682223, - "lat": 45.523784, - "arrival": 1591717740000, - "departure": 1591717740000, - "zoneId": "B", - "stopIndex": 3, - "stopSequence": 4, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - { - "name": "SW 11th & Alder", - "stopId": "TriMet:9600", - "stopCode": "9600", - "lon": -122.682819, - "lat": 45.521094, - "arrival": 1591717860000, - "departure": 1591717860000, - "zoneId": "B", - "stopIndex": 4, - "stopSequence": 5, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - } - ], - "steps": [] - }, - { - "startTime": 1591717921000, - "endTime": 1591718390000, - "departureDelay": 0, - "arrivalDelay": 0, - "realTime": false, - "distance": 718.4660000000001, - "pathway": false, - "mode": "WALK", - "route": "", - "agencyTimeZoneOffset": -25200000, - "interlineWithPreviousLeg": false, - "from": { - "name": "SW 11th & Taylor", - "stopId": "TriMet:9633", - "stopCode": "9633", - "lon": -122.683873, - "lat": 45.519059, - "arrival": 1591717920000, - "departure": 1591717921000, - "zoneId": "B", - "stopIndex": 5, - "stopSequence": 6, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - "to": { - "name": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", - "lon": -122.67681483620306, - "lat": 45.51639151281627, - "arrival": 1591718390000, - "orig": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", - "vertexType": "NORMAL" - }, - "legGeometry": { - "points": "cmytGfuxkVAHD@JFBQ@GDSh@_D??FU@IBMX_BRiA@K?AVqA@KBOFY`@aCFYBSh@}CBO@CDU^wBHc@F_@@CLF|Ar@FDNFBOh@{CD[FWb@eC", - "length": 40 - }, - "rentedBike": false, - "rentedCar": false, - "rentedVehicle": false, - "hailedCar": false, - "flexDrtAdvanceBookMin": 0.0, - "duration": 469.0, - "transitLeg": false, - "intermediateStops": [], - "steps": [ - { - "distance": 14.561, - "relativeDirection": "DEPART", - "streetName": "path", - "absoluteDirection": "WEST", - "stayOn": false, - "area": false, - "bogusName": true, - "lon": -122.6838786, - "lat": 45.5190659, - "elevation": [ - { - "first": 0.0, - "second": 29.782842393406284 - }, - { - "first": 3.48, - "second": 29.83284239340628 - }, - { - "first": 7.5809999999999995, - "second": 29.83284239340628 - }, - { - "first": 11.690999999999999, - "second": 29.862842393406282 - } - ] - }, - { - "distance": 475.42999999999995, - "relativeDirection": "LEFT", - "streetName": "SW Taylor St", - "absoluteDirection": "EAST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6839744, - "lat": 45.5189848, - "elevation": [ - { - "first": 0.0, - "second": 29.87284239340628 - }, - { - "first": 10.39, - "second": 29.87284239340628 - }, - { - "first": 10.386, - "second": 29.87284239340628 - }, - { - "first": 18.735999999999997, - "second": 29.662842393406283 - }, - { - "first": 18.737000000000002, - "second": 29.662842393406283 - }, - { - "first": 28.737000000000002, - "second": 29.252842393406283 - }, - { - "first": 38.737, - "second": 28.97284239340628 - }, - { - "first": 48.737, - "second": 28.632842393406282 - }, - { - "first": 58.737, - "second": 28.05284239340628 - }, - { - "first": 68.737, - "second": 27.62284239340628 - }, - { - "first": 78.737, - "second": 27.202842393406282 - }, - { - "first": 85.84700000000001, - "second": 26.952842393406282 - }, - { - "first": 85.84299999999999, - "second": 26.952842393406282 - }, - { - "first": 95.353, - "second": 26.932842393406283 - }, - { - "first": 95.35099999999998, - "second": 26.932842393406283 - }, - { - "first": 105.13099999999999, - "second": 26.65284239340628 - }, - { - "first": 105.12999999999998, - "second": 26.65284239340628 - }, - { - "first": 115.12999999999998, - "second": 26.22284239340628 - }, - { - "first": 125.12999999999998, - "second": 25.87284239340628 - }, - { - "first": 135.13, - "second": 25.642842393406283 - }, - { - "first": 145.48999999999998, - "second": 25.15284239340628 - }, - { - "first": 145.486, - "second": 25.15284239340628 - }, - { - "first": 155.486, - "second": 24.90284239340628 - }, - { - "first": 165.486, - "second": 24.602842393406284 - }, - { - "first": 176.05599999999998, - "second": 24.342842393406283 - }, - { - "first": 176.051, - "second": 24.342842393406283 - }, - { - "first": 181.18099999999998, - "second": 24.302842393406284 - }, - { - "first": 181.18099999999998, - "second": 24.302842393406284 - }, - { - "first": 191.18099999999998, - "second": 24.08284239340628 - }, - { - "first": 201.18099999999998, - "second": 23.752842393406283 - }, - { - "first": 216.081, - "second": 23.202842393406282 - }, - { - "first": 216.077, - "second": 23.202842393406282 - }, - { - "first": 221.187, - "second": 23.08284239340628 - }, - { - "first": 221.182, - "second": 23.08284239340628 - }, - { - "first": 231.182, - "second": 22.642842393406283 - }, - { - "first": 238.742, - "second": 22.17284239340628 - }, - { - "first": 238.742, - "second": 22.17284239340628 - }, - { - "first": 248.742, - "second": 21.65284239340628 - }, - { - "first": 258.74199999999996, - "second": 21.26284239340628 - }, - { - "first": 268.74199999999996, - "second": 20.682842393406283 - }, - { - "first": 278.74199999999996, - "second": 20.272842393406282 - }, - { - "first": 288.74199999999996, - "second": 19.74284239340628 - }, - { - "first": 303.502, - "second": 19.622842393406284 - }, - { - "first": 303.505, - "second": 19.622842393406284 - }, - { - "first": 313.505, - "second": 19.342842393406283 - }, - { - "first": 323.505, - "second": 18.962842393406284 - }, - { - "first": 333.505, - "second": 18.802842393406284 - }, - { - "first": 343.505, - "second": 18.532842393406284 - }, - { - "first": 353.505, - "second": 18.252842393406283 - }, - { - "first": 363.505, - "second": 18.002842393406283 - }, - { - "first": 373.505, - "second": 17.642842393406283 - }, - { - "first": 384.315, - "second": 17.522842393406282 - }, - { - "first": 384.311, - "second": 17.522842393406282 - }, - { - "first": 394.311, - "second": 17.532842393406284 - }, - { - "first": 404.311, - "second": 17.292842393406282 - }, - { - "first": 414.311, - "second": 17.112842393406282 - }, - { - "first": 424.311, - "second": 16.90284239340628 - }, - { - "first": 434.311, - "second": 16.56284239340628 - }, - { - "first": 444.311, - "second": 16.392842393406283 - }, - { - "first": 454.311, - "second": 16.162842393406283 - }, - { - "first": 464.311, - "second": 15.902842393406281 - }, - { - "first": 475.431, - "second": 15.822842393406283 - } - ] - }, - { - "distance": 79.318, - "relativeDirection": "RIGHT", - "streetName": "SW 5th Ave", - "absoluteDirection": "SOUTH", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6782737, - "lat": 45.5174597, - "elevation": [] - }, - { - "distance": 149.157, - "relativeDirection": "LEFT", - "streetName": "SW Salmon St", - "absoluteDirection": "EAST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6786441, - "lat": 45.5167953, - "elevation": [ - { - "first": 0.0, - "second": 17.552842393406284 - }, - { - "first": 10.0, - "second": 17.292842393406282 - }, - { - "first": 20.0, - "second": 16.962842393406284 - }, - { - "first": 30.0, - "second": 16.642842393406283 - }, - { - "first": 40.0, - "second": 16.162842393406283 - }, - { - "first": 50.0, - "second": 15.712842393406282 - }, - { - "first": 60.0, - "second": 15.122842393406282 - }, - { - "first": 70.0, - "second": 15.062842393406282 - }, - { - "first": 83.14, - "second": 14.982842393406283 - }, - { - "first": 83.144, - "second": 14.982842393406283 - }, - { - "first": 93.01400000000001, - "second": 14.762842393406283 - }, - { - "first": 93.01700000000001, - "second": 14.762842393406283 - }, - { - "first": 103.01700000000001, - "second": 14.502842393406283 - }, - { - "first": 113.01700000000001, - "second": 14.272842393406282 - }, - { - "first": 123.01700000000001, - "second": 13.872842393406282 - }, - { - "first": 133.017, - "second": 13.632842393406282 - }, - { - "first": 143.017, - "second": 13.412842393406283 - }, - { - "first": 149.157, - "second": 13.152842393406281 - } - ] - } - ] - } - ], - "tooSloped": false - }, - { - "duration": 1036, - "startTime": 1591717825000, - "endTime": 1591718861000, - "walkTime": 674, - "transitTime": 360, - "waitingTime": 2, - "walkDistance": 966.7338656666136, - "walkLimitExceeded": false, - "elevationLost": 8.23, - "elevationGained": 13.169999999999996, - "transfers": 0, - "fare": { - "fare": { - "regular": { - "currency": { - "symbol": "$", - "currency": "USD", - "defaultFractionDigits": 2, - "currencyCode": "USD" - }, - "cents": 250 - } - }, - "details": { - "regular": [ - { - "fareId": "TriMet:B", - "price": { - "currency": { - "symbol": "$", - "currency": "USD", - "defaultFractionDigits": 2, - "currencyCode": "USD" - }, - "cents": 250 - }, - "routes": [ - "TriMet:15" - ] - } - ] - } - }, - "legs": [ - { - "startTime": 1591717825000, - "endTime": 1591718399000, - "departureDelay": 0, - "arrivalDelay": 0, - "realTime": false, - "distance": 802.1549999999999, - "pathway": false, - "mode": "WALK", - "route": "", - "agencyTimeZoneOffset": -25200000, - "interlineWithPreviousLeg": false, - "from": { - "name": "1709 NW Irving St, Portland 97209", - "lon": -122.68865964147231, - "lat": 45.527817334203, - "departure": 1591717825000, - "orig": "1709 NW Irving St, Portland 97209", - "vertexType": "NORMAL" - }, - "to": { - "name": "SW 18th & Morrison", - "stopId": "TriMet:6911", - "stopCode": "6911", - "lon": -122.690453, - "lat": 45.52188, - "arrival": 1591718399000, - "departure": 1591718400000, - "zoneId": "B", - "stopIndex": 21, - "stopSequence": 22, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - "legGeometry": { - "points": "}c{tGdsykVAu@x@AJ?v@AP?nCE\\?r@Ax@C`BADAd@?lCEp@AdAAV??NBvDl@?@?F?J?J?H?H?B@BB@DBDBFBHLZBDHLHLHHFDHFj@XHPTHJDLRCP?BB?B@B@`@N", - "length": 50 - }, - "rentedBike": false, - "rentedCar": false, - "rentedVehicle": false, - "hailedCar": false, - "flexDrtAdvanceBookMin": 0.0, - "duration": 574.0, - "transitLeg": false, - "intermediateStops": [], - "steps": [ - { - "distance": 20.794, - "relativeDirection": "DEPART", - "streetName": "NW Irving St", - "absoluteDirection": "EAST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.68866035123024, - "lat": 45.527836027296765, - "elevation": [ - { - "first": 0.0, - "second": 20.682842393406283 - }, - { - "first": 10.0, - "second": 20.452842393406282 - }, - { - "first": 20.0, - "second": 20.24284239340628 - }, - { - "first": 20.79, - "second": 20.22284239340628 - } - ] - }, - { - "distance": 475.08199999999994, - "relativeDirection": "RIGHT", - "streetName": "NW 17th Ave", - "absoluteDirection": "SOUTH", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6883935, - "lat": 45.527841, - "elevation": [ - { - "first": 0.0, - "second": 18.682842393406283 - }, - { - "first": 10.0, - "second": 18.802842393406284 - }, - { - "first": 20.0, - "second": 19.01284239340628 - }, - { - "first": 31.29, - "second": 19.24284239340628 - }, - { - "first": 31.294, - "second": 19.24284239340628 - }, - { - "first": 39.024, - "second": 19.40284239340628 - }, - { - "first": 39.023, - "second": 19.40284239340628 - }, - { - "first": 49.023, - "second": 19.60284239340628 - }, - { - "first": 59.023, - "second": 19.85284239340628 - }, - { - "first": 69.023, - "second": 19.97284239340628 - }, - { - "first": 79.273, - "second": 20.032842393406284 - }, - { - "first": 79.272, - "second": 20.032842393406284 - }, - { - "first": 89.272, - "second": 20.26284239340628 - }, - { - "first": 99.272, - "second": 20.572842393406283 - }, - { - "first": 109.272, - "second": 20.872842393406284 - }, - { - "first": 119.272, - "second": 21.112842393406282 - }, - { - "first": 129.272, - "second": 21.412842393406283 - }, - { - "first": 139.272, - "second": 21.69284239340628 - }, - { - "first": 149.272, - "second": 22.01284239340628 - }, - { - "first": 159.322, - "second": 22.002842393406283 - }, - { - "first": 159.323, - "second": 22.002842393406283 - }, - { - "first": 169.323, - "second": 22.17284239340628 - }, - { - "first": 176.933, - "second": 22.352842393406284 - }, - { - "first": 176.93, - "second": 22.352842393406284 - }, - { - "first": 186.93, - "second": 22.58284239340628 - }, - { - "first": 196.93, - "second": 22.792842393406282 - }, - { - "first": 205.41, - "second": 22.952842393406282 - }, - { - "first": 205.41, - "second": 22.952842393406282 - }, - { - "first": 215.41, - "second": 23.15284239340628 - }, - { - "first": 225.41, - "second": 23.302842393406284 - }, - { - "first": 237.51, - "second": 23.42284239340628 - }, - { - "first": 237.505, - "second": 23.42284239340628 - }, - { - "first": 247.505, - "second": 23.502842393406283 - }, - { - "first": 257.505, - "second": 23.632842393406282 - }, - { - "first": 267.505, - "second": 23.76284239340628 - }, - { - "first": 277.505, - "second": 23.90284239340628 - }, - { - "first": 292.145, - "second": 24.092842393406283 - }, - { - "first": 292.143, - "second": 24.092842393406283 - }, - { - "first": 295.53299999999996, - "second": 24.132842393406282 - }, - { - "first": 295.53499999999997, - "second": 24.132842393406282 - }, - { - "first": 305.53499999999997, - "second": 24.432842393406283 - }, - { - "first": 316.635, - "second": 24.392842393406283 - }, - { - "first": 316.63599999999997, - "second": 24.392842393406283 - }, - { - "first": 326.63599999999997, - "second": 24.522842393406282 - }, - { - "first": 336.63599999999997, - "second": 24.792842393406282 - }, - { - "first": 346.63599999999997, - "second": 25.052842393406284 - }, - { - "first": 356.63599999999997, - "second": 25.31284239340628 - }, - { - "first": 366.63599999999997, - "second": 25.602842393406284 - }, - { - "first": 376.63599999999997, - "second": 25.842842393406283 - }, - { - "first": 386.63599999999997, - "second": 25.982842393406283 - }, - { - "first": 396.02599999999995, - "second": 26.01284239340628 - }, - { - "first": 396.03, - "second": 26.01284239340628 - }, - { - "first": 406.03, - "second": 26.042842393406282 - }, - { - "first": 416.03, - "second": 26.092842393406283 - }, - { - "first": 423.69, - "second": 26.15284239340628 - }, - { - "first": 423.686, - "second": 26.15284239340628 - }, - { - "first": 433.686, - "second": 26.252842393406283 - }, - { - "first": 443.686, - "second": 26.31284239340628 - }, - { - "first": 453.686, - "second": 26.362842393406282 - }, - { - "first": 461.936, - "second": 26.40284239340628 - }, - { - "first": 461.93699999999995, - "second": 26.40284239340628 - }, - { - "first": 475.08699999999993, - "second": 26.19284239340628 - } - ] - }, - { - "distance": 78.621, - "relativeDirection": "RIGHT", - "streetName": "NW Couch St", - "absoluteDirection": "WEST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6882412, - "lat": 45.5235698, - "elevation": [ - { - "first": 0.0, - "second": 26.19284239340628 - }, - { - "first": 10.0, - "second": 26.352842393406284 - }, - { - "first": 20.0, - "second": 26.572842393406283 - }, - { - "first": 30.0, - "second": 26.802842393406284 - }, - { - "first": 40.0, - "second": 27.15284239340628 - }, - { - "first": 50.0, - "second": 27.322842393406283 - }, - { - "first": 60.0, - "second": 27.55284239340628 - }, - { - "first": 70.0, - "second": 27.80284239340628 - }, - { - "first": 78.62, - "second": 28.01284239340628 - } - ] - }, - { - "distance": 66.44500000000001, - "relativeDirection": "LEFT", - "streetName": "NW 18th Ave", - "absoluteDirection": "SOUTH", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6892498, - "lat": 45.5235462, - "elevation": [ - { - "first": 0.0, - "second": 28.01284239340628 - }, - { - "first": 10.0, - "second": 27.37284239340628 - }, - { - "first": 20.0, - "second": 26.87284239340628 - }, - { - "first": 25.41, - "second": 26.65284239340628 - }, - { - "first": 25.411, - "second": 26.65284239340628 - }, - { - "first": 27.351000000000003, - "second": 26.56284239340628 - }, - { - "first": 27.346, - "second": 26.56284239340628 - }, - { - "first": 31.706, - "second": 26.37284239340628 - }, - { - "first": 31.705, - "second": 26.37284239340628 - }, - { - "first": 38.315, - "second": 26.132842393406282 - }, - { - "first": 38.31, - "second": 26.132842393406282 - }, - { - "first": 48.31, - "second": 25.90284239340628 - }, - { - "first": 55.33, - "second": 25.822842393406283 - }, - { - "first": 55.328, - "second": 25.822842393406283 - }, - { - "first": 66.44800000000001, - "second": 25.962842393406284 - } - ] - }, - { - "distance": 161.21300000000002, - "relativeDirection": "SLIGHTLY_RIGHT", - "streetName": "SW 18th Ave", - "absoluteDirection": "SOUTHWEST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.6893346, - "lat": 45.5229721, - "elevation": [ - { - "first": 0.0, - "second": 25.962842393406284 - }, - { - "first": 10.0, - "second": 26.08284239340628 - }, - { - "first": 20.0, - "second": 26.022842393406282 - }, - { - "first": 32.85, - "second": 25.642842393406283 - }, - { - "first": 32.852, - "second": 25.642842393406283 - }, - { - "first": 42.852, - "second": 25.852842393406284 - }, - { - "first": 52.852, - "second": 26.142842393406283 - }, - { - "first": 62.852, - "second": 26.572842393406283 - }, - { - "first": 72.852, - "second": 27.022842393406282 - }, - { - "first": 82.852, - "second": 27.612842393406282 - }, - { - "first": 93.812, - "second": 28.092842393406283 - }, - { - "first": 93.811, - "second": 28.092842393406283 - }, - { - "first": 103.811, - "second": 28.47284239340628 - }, - { - "first": 114.351, - "second": 28.842842393406283 - }, - { - "first": 114.346, - "second": 28.842842393406283 - }, - { - "first": 125.05600000000001, - "second": 28.962842393406284 - }, - { - "first": 134.205, - "second": 28.962842393406284 - }, - { - "first": 141.47500000000002, - "second": 28.56284239340628 - }, - { - "first": 141.47, - "second": 28.56284239340628 - }, - { - "first": 143.36, - "second": 28.482842393406283 - } - ] - } - ] - }, - { - "startTime": 1591718400000, - "endTime": 1591718760000, - "departureDelay": 0, - "arrivalDelay": 0, - "realTime": false, - "distance": 1286.9349137891197, - "pathway": false, - "mode": "BUS", - "route": "15", - "agencyName": "TriMet", - "agencyUrl": "https://trimet.org/", - "agencyTimeZoneOffset": -25200000, - "routeType": 3, - "routeId": "TriMet:15", - "interlineWithPreviousLeg": false, - "tripBlockId": "1505", - "headsign": "Gateway TC", - "agencyId": "TRIMET", - "tripId": "TriMet:9932776", - "serviceDate": "20200609", - "from": { - "name": "SW 18th & Morrison", - "stopId": "TriMet:6911", - "stopCode": "6911", - "lon": -122.690453, - "lat": 45.52188, - "arrival": 1591718399000, - "departure": 1591718400000, - "zoneId": "B", - "stopIndex": 21, - "stopSequence": 22, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - "to": { - "name": "SW Salmon & 5th", - "stopId": "TriMet:5020", - "stopCode": "5020", - "lon": -122.678854, - "lat": 45.516794, - "arrival": 1591718760000, - "departure": 1591718761000, - "zoneId": "B", - "stopIndex": 27, - "stopSequence": 28, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - "legGeometry": { - "points": "w~ytGx}ykVRHh@ZXNhBv@JFJFRJFDFFFH@@`@Xp@h@RNBMBQBMBO?I@I?O?u@?e@?A?S@WBMDY\\sBJe@Fc@@Ep@sDBOBSd@mCBKDSDWHe@XcBLs@n@mDBUFYDUFa@BKDSJm@He@@GJk@Hg@PeADUF[@IBMNy@He@RkAToAFYr@eEDSh@aDBK?ADWh@yC", - "length": 72 - }, - "interStopGeometry": [ - { - "points": "w~ytGx}ykVRHh@ZXNhBv@JFJFRJFDFFFH@@`@Xp@h@RNBMBQBMBO?I@I?O?u@?e@?A?S@WBMDY\\sBJe@Fc@", - "length": 32 - }, - { - "points": "wpytG~vykV@Ep@sDBOBSd@mC", - "length": 6 - }, - { - "points": "umytGrkykVBKDSDWHe@XcBLs@n@mDBUFYDUFa@BK", - "length": 13 - }, - { - "points": "eiytGzzxkVDSJm@He@@GJk@Hg@PeA", - "length": 8 - }, - { - "points": "_gytGprxkVDUF[@IBMNy@He@RkAToA", - "length": 9 - }, - { - "points": "gdytGjhxkVFYr@eEDSh@aDBK?ADWh@yC", - "length": 9 - } - ], - "alerts": [ - { - "effectiveStartDate": 1591959600000, - "alertDescriptionText": "A scarf, bandanna or fabric mask works, as long as it covers your nose and mouth.", - "alertUrl": "http://trimet.org/health" - }, - { - "effectiveStartDate": 1582249544000, - "alertDescriptionText": "No service to westbound NW Thurman & 28th (Stop ID 5837) due to long term stop closure. ", - "alertUrl": "http://trimet.org/alerts/" - }, - { - "effectiveStartDate": 1585477800000, - "alertDescriptionText": "Due to COVID-19 service reductions, beginning Sunday, April 5th, this line will be running on Saturday schedule on weekdays. For arrivals and trip planning, see trimet.org.", - "alertUrl": "https://trimet.org/alerts/reducedservice.htm" - } - ], - "routeShortName": "15", - "routeLongName": "Belmont/NW 23rd", - "rentedBike": false, - "rentedCar": false, - "rentedVehicle": false, - "hailedCar": false, - "flexDrtAdvanceBookMin": 0.0, - "duration": 360.0, - "transitLeg": true, - "intermediateStops": [ - { - "name": "SW Salmon & 16th", - "stopId": "TriMet:5016", - "stopCode": "5016", - "lon": -122.689312, - "lat": 45.519578, - "arrival": 1591718516000, - "departure": 1591718516000, - "zoneId": "B", - "stopIndex": 22, - "stopSequence": 23, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - { - "name": "SW Salmon & 14th", - "stopId": "TriMet:5014", - "stopCode": "5014", - "lon": -122.687479, - "lat": 45.519106, - "arrival": 1591718559000, - "departure": 1591718559000, - "zoneId": "B", - "stopIndex": 23, - "stopSequence": 24, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - { - "name": "SW Salmon & 12th", - "stopId": "TriMet:5012", - "stopCode": "5012", - "lon": -122.684797, - "lat": 45.518386, - "arrival": 1591718621000, - "departure": 1591718621000, - "zoneId": "B", - "stopIndex": 24, - "stopSequence": 25, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - { - "name": "SW Salmon & 10th", - "stopId": "TriMet:5009", - "stopCode": "5009", - "lon": -122.683475, - "lat": 45.518026, - "arrival": 1591718652000, - "departure": 1591718652000, - "zoneId": "B", - "stopIndex": 25, - "stopSequence": 26, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - { - "name": "SW Salmon & Park", - "stopId": "TriMet:5007", - "stopCode": "5007", - "lon": -122.681844, - "lat": 45.517596, - "arrival": 1591718690000, - "departure": 1591718690000, - "zoneId": "B", - "stopIndex": 26, - "stopSequence": 27, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - } - ], - "steps": [] - }, - { - "startTime": 1591718761000, - "endTime": 1591718861000, - "departureDelay": 0, - "arrivalDelay": 0, - "realTime": false, - "distance": 164.377, - "pathway": false, - "mode": "WALK", - "route": "", - "agencyTimeZoneOffset": -25200000, - "interlineWithPreviousLeg": false, - "from": { - "name": "SW Salmon & 5th", - "stopId": "TriMet:5020", - "stopCode": "5020", - "lon": -122.678854, - "lat": 45.516794, - "arrival": 1591718760000, - "departure": 1591718761000, - "zoneId": "B", - "stopIndex": 27, - "stopSequence": 28, - "vertexType": "TRANSIT", - "boardAlightType": "DEFAULT" - }, - "to": { - "name": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", - "lon": -122.67681483620306, - "lat": 45.51639151281627, - "arrival": 1591718861000, - "orig": "Uncharted Realities, SW 3rd Ave, Downtown - Portland 97204", - "vertexType": "NORMAL" - }, - "legGeometry": { - "points": "g_ytGtuwkV@CF]?ABOh@{CD[FWb@eC", - "length": 9 - }, - "rentedBike": false, - "rentedCar": false, - "rentedVehicle": false, - "hailedCar": false, - "flexDrtAdvanceBookMin": 0.0, - "duration": 100.0, - "transitLeg": false, - "intermediateStops": [], - "steps": [ - { - "distance": 164.377, - "relativeDirection": "DEPART", - "streetName": "SW Salmon St", - "absoluteDirection": "EAST", - "stayOn": false, - "area": false, - "bogusName": false, - "lon": -122.67882640142975, - "lat": 45.51684447671969, - "elevation": [ - { - "first": 15.22, - "second": 17.552842393406284 - }, - { - "first": 25.22, - "second": 17.292842393406282 - }, - { - "first": 35.22, - "second": 16.962842393406284 - }, - { - "first": 45.22, - "second": 16.642842393406283 - }, - { - "first": 55.22, - "second": 16.162842393406283 - }, - { - "first": 65.22, - "second": 15.712842393406282 - }, - { - "first": 75.22, - "second": 15.122842393406282 - }, - { - "first": 85.22, - "second": 15.062842393406282 - }, - { - "first": 98.36, - "second": 14.982842393406283 - }, - { - "first": 98.364, - "second": 14.982842393406283 - }, - { - "first": 108.23400000000001, - "second": 14.762842393406283 - }, - { - "first": 108.23700000000001, - "second": 14.762842393406283 - }, - { - "first": 118.23700000000001, - "second": 14.502842393406283 - }, - { - "first": 128.23700000000002, - "second": 14.272842393406282 - }, - { - "first": 138.23700000000002, - "second": 13.872842393406282 - }, - { - "first": 148.23700000000002, - "second": 13.632842393406282 - }, - { - "first": 158.23700000000002, - "second": 13.412842393406283 - }, - { - "first": 164.377, - "second": 13.152842393406281 - } - ] - } - ] - } - ], - "tooSloped": false - } - ] - } - } -} \ No newline at end of file From 340fc1f447e0d27adc41e5518d9229a8bdf0e061 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 26 Sep 2024 15:18:00 -0400 Subject: [PATCH 43/48] refactor(ItineraryUtils): Remove unused constant. --- .../org/opentripplanner/middleware/utils/ItineraryUtils.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java b/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java index ab82a274d..79a4a4a67 100644 --- a/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java +++ b/src/main/java/org/opentripplanner/middleware/utils/ItineraryUtils.java @@ -29,7 +29,6 @@ */ public class ItineraryUtils { - public static final String MODE_PARAM = "mode"; public static final int ITINERARY_CHECK_WINDOW = 7; public static final int SERVICE_DAY_START_HOUR = getConfigPropertyAsInt("SERVICE_DAY_START_HOUR", 3); From 4ff727995490aec5780d108974d228c0267371e5 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 26 Sep 2024 15:23:17 -0400 Subject: [PATCH 44/48] refactor: Apply other review feedback. --- .../java/org/opentripplanner/middleware/otp/OtpDispatcher.java | 2 +- .../org/opentripplanner/middleware/utils/GraphQLUtils.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/middleware/otp/OtpDispatcher.java b/src/main/java/org/opentripplanner/middleware/otp/OtpDispatcher.java index 4d72b94b6..503f0639b 100644 --- a/src/main/java/org/opentripplanner/middleware/otp/OtpDispatcher.java +++ b/src/main/java/org/opentripplanner/middleware/otp/OtpDispatcher.java @@ -93,7 +93,7 @@ public static OtpDispatcherResponse sendOtpPlanRequest(OtpVersion version, OtpGr version, "", OTP_GRAPHQL_ENDPOINT, - Map.of("Content-Type", "application/json"), + HttpUtils.HEADERS_JSON, JsonUtils.toJson(query).replace("\\\\n", "\\n").replace("\\\\\"", "\"") ); } diff --git a/src/main/java/org/opentripplanner/middleware/utils/GraphQLUtils.java b/src/main/java/org/opentripplanner/middleware/utils/GraphQLUtils.java index fa6481878..fce26e44d 100644 --- a/src/main/java/org/opentripplanner/middleware/utils/GraphQLUtils.java +++ b/src/main/java/org/opentripplanner/middleware/utils/GraphQLUtils.java @@ -3,7 +3,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.*; +import java.io.BufferedReader; +import java.io.InputStreamReader; public class GraphQLUtils { private static final Logger LOG = LoggerFactory.getLogger(GraphQLUtils.class); From 3c120c7ed335e78bf600559ee29ec7fdb55472b5 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 26 Sep 2024 16:42:10 -0400 Subject: [PATCH 45/48] test(ItineraryExistence): Add test override to default OTP response provider. --- .../middleware/models/ItineraryExistence.java | 11 +++++++- .../controllers/api/ApiUserFlowTest.java | 19 ++++++------- .../middleware/utils/ItineraryUtilsTest.java | 22 --------------- .../utils/MockOtpResponseProvider.java | 28 +++++++++++++++++++ 4 files changed, 47 insertions(+), 33 deletions(-) create mode 100644 src/test/java/org/opentripplanner/middleware/utils/MockOtpResponseProvider.java diff --git a/src/main/java/org/opentripplanner/middleware/models/ItineraryExistence.java b/src/main/java/org/opentripplanner/middleware/models/ItineraryExistence.java index 1e5ad1d42..48d127d9c 100644 --- a/src/main/java/org/opentripplanner/middleware/models/ItineraryExistence.java +++ b/src/main/java/org/opentripplanner/middleware/models/ItineraryExistence.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import org.bson.codecs.pojo.annotations.BsonIgnore; +import org.opentripplanner.middleware.OtpMiddlewareMain; import org.opentripplanner.middleware.otp.OtpDispatcher; import org.opentripplanner.middleware.otp.OtpRequest; import org.opentripplanner.middleware.otp.response.Itinerary; @@ -65,7 +66,9 @@ public class ItineraryExistence extends Model { */ public Date timestamp = new Date(); - private transient Function otpResponseProvider = ItineraryExistence::getOtpResponse; + private transient Function otpResponseProvider = getOtpResponseProvider(); + + public static Function otpResponseProviderOverride = null; // Required for persistence. public ItineraryExistence() {} @@ -82,6 +85,12 @@ public ItineraryExistence( if (otpResponseProvider != null) this.otpResponseProvider = otpResponseProvider; } + private Function getOtpResponseProvider() { + return OtpMiddlewareMain.inTestEnvironment && otpResponseProviderOverride != null + ? otpResponseProviderOverride + : ItineraryExistence::getOtpResponse; + } + /** * Helper function to extract the existence check for a particular day of the week. */ diff --git a/src/test/java/org/opentripplanner/middleware/controllers/api/ApiUserFlowTest.java b/src/test/java/org/opentripplanner/middleware/controllers/api/ApiUserFlowTest.java index 17748861b..175d65ba8 100644 --- a/src/test/java/org/opentripplanner/middleware/controllers/api/ApiUserFlowTest.java +++ b/src/test/java/org/opentripplanner/middleware/controllers/api/ApiUserFlowTest.java @@ -6,11 +6,11 @@ import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.opentripplanner.middleware.controllers.response.ResponseList; import org.opentripplanner.middleware.models.ApiUser; +import org.opentripplanner.middleware.models.ItineraryExistence; import org.opentripplanner.middleware.models.MonitoredTrip; import org.opentripplanner.middleware.models.OtpUser; import org.opentripplanner.middleware.models.TripRequest; @@ -22,6 +22,7 @@ import org.opentripplanner.middleware.utils.CreateApiKeyException; import org.opentripplanner.middleware.utils.HttpResponseValues; import org.opentripplanner.middleware.utils.JsonUtils; +import org.opentripplanner.middleware.utils.MockOtpResponseProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,6 +31,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.opentripplanner.middleware.auth.Auth0Connection.isAuthDisabled; import static org.opentripplanner.middleware.auth.Auth0Users.createAuth0UserForEmail; @@ -137,11 +139,6 @@ public static void tearDown() { if (otpUserStandalone != null) otpUserStandalone.delete(false); } - @AfterEach - public void tearDownAfterTest() { - OtpTestUtils.resetOtpMocks(); - } - /** * Tests to confirm that an otp user, related monitored trip and plan can be created and deleted leaving no orphaned * records. This also includes Auth0 users if auth is enabled. The basic script for this test is as follows: @@ -212,7 +209,8 @@ public void canSimulateApiUserFlow() throws Exception { // Set mock OTP responses so that trip existence checks in the // POST call below to save the monitored trip can pass. - OtpTestUtils.setupOtpMocks(OtpTestUtils.createMockOtpResponsesForTripExistence()); + MockOtpResponseProvider mockResponses = new MockOtpResponseProvider(OtpTestUtils.createMockOtpResponsesForTripExistence()); + ItineraryExistence.otpResponseProviderOverride = mockResponses::getMockResponse; HttpResponseValues createTripResponseAsApiUser = makeRequest( MONITORED_TRIP_PATH, @@ -221,9 +219,10 @@ public void canSimulateApiUserFlow() throws Exception { HttpMethod.POST ); - // After POST is complete, reset mock OTP responses for subsequent mock OTP calls below. - // (The mocks will also be reset in the @AfterEach phase if there are failures.) - OtpTestUtils.resetOtpMocks(); + // After POST is complete, reset OTP response provider to default. + ItineraryExistence.otpResponseProviderOverride = null; + // Make sure all mocks were used + assertTrue(mockResponses.areAllMocksUsed()); String responseBody = createTripResponseAsApiUser.responseBody; assertEquals(HttpStatus.OK_200, createTripResponseAsApiUser.status); diff --git a/src/test/java/org/opentripplanner/middleware/utils/ItineraryUtilsTest.java b/src/test/java/org/opentripplanner/middleware/utils/ItineraryUtilsTest.java index d69592189..6ce321555 100644 --- a/src/test/java/org/opentripplanner/middleware/utils/ItineraryUtilsTest.java +++ b/src/test/java/org/opentripplanner/middleware/utils/ItineraryUtilsTest.java @@ -536,26 +536,4 @@ public String getMessage(ZoneId zoneId) { ); } } - - /** - * Provides a set of mock OTP responses in the order they are expected to be used. - */ - static class MockOtpResponseProvider { - private int index = 0; - private final List mockResponses; - - MockOtpResponseProvider(List mockResponses) { - this.mockResponses = mockResponses; - } - - public OtpResponse getMockResponse(OtpRequest otpRequest) { - // otpRequest is ignored, and the next response is given. - // If index is out of bounds, an error will be thrown. - return mockResponses.get(index++); - } - - public boolean areAllMocksUsed() { - return index == mockResponses.size(); - } - } } diff --git a/src/test/java/org/opentripplanner/middleware/utils/MockOtpResponseProvider.java b/src/test/java/org/opentripplanner/middleware/utils/MockOtpResponseProvider.java new file mode 100644 index 000000000..c07b47e93 --- /dev/null +++ b/src/test/java/org/opentripplanner/middleware/utils/MockOtpResponseProvider.java @@ -0,0 +1,28 @@ +package org.opentripplanner.middleware.utils; + +import org.opentripplanner.middleware.otp.OtpRequest; +import org.opentripplanner.middleware.otp.response.OtpResponse; + +import java.util.List; + +/** + * Provides a set of mock OTP responses in the order they are expected to be used. + */ +public class MockOtpResponseProvider { + private int index = 0; + private final List mockResponses; + + public MockOtpResponseProvider(List mockResponses) { + this.mockResponses = mockResponses; + } + + public OtpResponse getMockResponse(OtpRequest otpRequest) { + // otpRequest is ignored, and the next response is given. + // If index is out of bounds, an error will be thrown. + return mockResponses.get(index++); + } + + public boolean areAllMocksUsed() { + return index == mockResponses.size(); + } +} From 47a54558815a15e97718359fa93817b3d87b78ae Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 27 Sep 2024 15:02:10 -0400 Subject: [PATCH 46/48] test(ApiUserFlowTest): Update plan queries. --- .../controllers/api/ApiUserFlowTest.java | 26 ++++++++++++------- .../middleware/testutils/ApiTestUtils.java | 20 +++++++++++++- .../middleware/testutils/OtpTestUtils.java | 2 ++ 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/src/test/java/org/opentripplanner/middleware/controllers/api/ApiUserFlowTest.java b/src/test/java/org/opentripplanner/middleware/controllers/api/ApiUserFlowTest.java index 175d65ba8..58753ff08 100644 --- a/src/test/java/org/opentripplanner/middleware/controllers/api/ApiUserFlowTest.java +++ b/src/test/java/org/opentripplanner/middleware/controllers/api/ApiUserFlowTest.java @@ -36,12 +36,13 @@ import static org.opentripplanner.middleware.auth.Auth0Connection.isAuthDisabled; import static org.opentripplanner.middleware.auth.Auth0Users.createAuth0UserForEmail; import static org.opentripplanner.middleware.controllers.api.ApiUserController.DEFAULT_USAGE_PLAN_ID; -import static org.opentripplanner.middleware.otp.OtpDispatcher.OTP_PLAN_ENDPOINT; +import static org.opentripplanner.middleware.otp.OtpDispatcher.OTP_GRAPHQL_ENDPOINT; import static org.opentripplanner.middleware.testutils.ApiTestUtils.TEMP_AUTH0_USER_PASSWORD; import static org.opentripplanner.middleware.testutils.ApiTestUtils.makeDeleteRequest; import static org.opentripplanner.middleware.testutils.ApiTestUtils.makeGetRequest; import static org.opentripplanner.middleware.testutils.ApiTestUtils.makeRequest; import static org.opentripplanner.middleware.testutils.ApiTestUtils.mockAuthenticatedGet; +import static org.opentripplanner.middleware.testutils.ApiTestUtils.mockAuthenticatedPlanPost; import static org.opentripplanner.middleware.testutils.ApiTestUtils.mockAuthenticatedRequest; /** @@ -261,20 +262,27 @@ public void canSimulateApiUserFlow() throws Exception { // Plan trip with OTP proxy authenticating as an OTP user. Mock plan response will be returned. This will work // as an Otp user (created by MOD UI or an Api user) because the end point has no auth. A lack of auth also means // the plan is not saved. - String otpQueryForOtpUserRequest = OTP_PROXY_ENDPOINT + - OTP_PLAN_ENDPOINT + - "?fromPlace=28.45119,-81.36818&toPlace=28.54834,-81.37745"; - HttpResponseValues planTripResponseAsOtpUser = mockAuthenticatedGet(otpQueryForOtpUserRequest, otpUserResponse); + String otpQueryForOtpUserRequest = OTP_PROXY_ENDPOINT + OTP_GRAPHQL_ENDPOINT; + HttpResponseValues planTripResponseAsOtpUser = mockAuthenticatedPlanPost( + otpQueryForOtpUserRequest, + monitoredTrip.otp2QueryParams, + ApiTestUtils.getMockHeaders(otpUserResponse), + otpUserResponse + ); LOG.info("OTP user: Plan trip response: {}\n....", planTripResponseAsOtpUser.responseBody.substring(0, 300)); assertEquals(HttpStatus.OK_200, planTripResponseAsOtpUser.status); // Plan trip with OTP proxy authenticating as an Api user. Mock plan response will be returned. This will work // as an Api user because the end point has no auth. - String otpQueryForApiUserRequest = OTP_PROXY_ENDPOINT + - OTP_PLAN_ENDPOINT + - String.format("?fromPlace=28.45119,-81.36818&toPlace=28.54834,-81.37745&userId=%s",otpUserResponse.id); - HttpResponseValues planTripResponseAsApiUser = makeGetRequest(otpQueryForApiUserRequest, apiUserHeaders); + String otpQueryForApiUserRequest = OTP_PROXY_ENDPOINT + OTP_GRAPHQL_ENDPOINT + + String.format("?userId=%s",otpUserResponse.id); + HttpResponseValues planTripResponseAsApiUser = mockAuthenticatedPlanPost( + otpQueryForApiUserRequest, + monitoredTrip.otp2QueryParams, + apiUserHeaders, + otpUserResponse + ); LOG.info("API user (on behalf of an Otp user): Plan trip response: {}\n....", planTripResponseAsApiUser.responseBody.substring(0, 300)); assertEquals(HttpStatus.OK_200, planTripResponseAsApiUser.status); diff --git a/src/test/java/org/opentripplanner/middleware/testutils/ApiTestUtils.java b/src/test/java/org/opentripplanner/middleware/testutils/ApiTestUtils.java index b83258561..163caa5ab 100644 --- a/src/test/java/org/opentripplanner/middleware/testutils/ApiTestUtils.java +++ b/src/test/java/org/opentripplanner/middleware/testutils/ApiTestUtils.java @@ -9,6 +9,8 @@ import org.opentripplanner.middleware.models.AdminUser; import org.opentripplanner.middleware.models.ApiUser; import org.opentripplanner.middleware.models.OtpUser; +import org.opentripplanner.middleware.otp.OtpGraphQLQuery; +import org.opentripplanner.middleware.otp.OtpGraphQLVariables; import org.opentripplanner.middleware.persistence.Persistence; import org.opentripplanner.middleware.utils.HttpResponseValues; import org.opentripplanner.middleware.utils.HttpUtils; @@ -18,6 +20,7 @@ import java.net.URI; import java.util.HashMap; +import java.util.Map; import java.util.UUID; import static org.opentripplanner.middleware.auth.Auth0Connection.isAuthDisabled; @@ -108,7 +111,7 @@ private static String getTestAuth0AccessToken(String username, String scope) thr * Send request to provided URL. */ public static HttpResponseValues makeRequest( - String path, String body, HashMap headers, HttpMethod requestMethod + String path, String body, Map headers, HttpMethod requestMethod ) { return HttpUtils.httpRequestRawResponse( URI.create(BASE_URL + path), @@ -162,6 +165,21 @@ public static HttpResponseValues mockAuthenticatedGet(String path, AbstractUser return makeGetRequest(path, getMockHeaders(requestingUser)); } + /** + * Construct http headers according to caller request and then make an authenticated 'POST' call. + */ + public static HttpResponseValues mockAuthenticatedPlanPost( + String path, + OtpGraphQLVariables planVariables, + Map headers, + AbstractUser requestingUser + ) throws Exception { + OtpGraphQLQuery query = new OtpGraphQLQuery(); + query.variables = planVariables; + String dummyBody = JsonUtils.toJson(query); + return makeRequest(path, dummyBody, headers, HttpMethod.POST); + } + /** * Construct http headers according to caller request and then make an authenticated call by placing the Auth0 user * id in the headers so that {@link RequestingUser} can check the database for a matching user. diff --git a/src/test/java/org/opentripplanner/middleware/testutils/OtpTestUtils.java b/src/test/java/org/opentripplanner/middleware/testutils/OtpTestUtils.java index 4416cbbaa..c8811ebb1 100644 --- a/src/test/java/org/opentripplanner/middleware/testutils/OtpTestUtils.java +++ b/src/test/java/org/opentripplanner/middleware/testutils/OtpTestUtils.java @@ -28,6 +28,7 @@ import java.util.List; import static java.nio.charset.StandardCharsets.UTF_8; +import static org.opentripplanner.middleware.otp.OtpDispatcher.OTP_GRAPHQL_ENDPOINT; import static org.opentripplanner.middleware.otp.OtpDispatcher.OTP_PLAN_ENDPOINT; import static org.opentripplanner.middleware.utils.JsonUtils.logMessageAndHalt; import static spark.Service.ignite; @@ -99,6 +100,7 @@ public static void mockOtpServer() { return; } Service http = ignite().port(8080); + http.post("/otp" + OTP_GRAPHQL_ENDPOINT, OtpTestUtils::mockOtpPlanResponse); http.get("/otp" + OTP_PLAN_ENDPOINT, OtpTestUtils::mockOtpPlanResponse); http.get("/*", (request, response) -> { logMessageAndHalt( From f9dfcd3f02e0e3812d0cc11e8896199229fab412 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 1 Oct 2024 11:25:51 -0400 Subject: [PATCH 47/48] improvement(Leg): Support new trip data. --- .../middleware/otp/response/Leg.java | 1 + .../middleware/otp/response/StopTime.java | 6 ++++++ .../middleware/otp/response/Trip.java | 13 +++++++++++++ .../middleware/otp/response/TripStop.java | 6 ++++++ 4 files changed, 26 insertions(+) create mode 100644 src/main/java/org/opentripplanner/middleware/otp/response/StopTime.java create mode 100644 src/main/java/org/opentripplanner/middleware/otp/response/Trip.java create mode 100644 src/main/java/org/opentripplanner/middleware/otp/response/TripStop.java diff --git a/src/main/java/org/opentripplanner/middleware/otp/response/Leg.java b/src/main/java/org/opentripplanner/middleware/otp/response/Leg.java index 4b33c3184..be468459c 100644 --- a/src/main/java/org/opentripplanner/middleware/otp/response/Leg.java +++ b/src/main/java/org/opentripplanner/middleware/otp/response/Leg.java @@ -56,6 +56,7 @@ public class Leg implements Cloneable { public String headsign; public Agency agency; public Route route; + public Trip trip; /** * Gets the scheduled start time of this itinerary in the OTP timezone. diff --git a/src/main/java/org/opentripplanner/middleware/otp/response/StopTime.java b/src/main/java/org/opentripplanner/middleware/otp/response/StopTime.java new file mode 100644 index 000000000..6f9e05087 --- /dev/null +++ b/src/main/java/org/opentripplanner/middleware/otp/response/StopTime.java @@ -0,0 +1,6 @@ +package org.opentripplanner.middleware.otp.response; + +public class StopTime { + public TripStop stop; + public int stopPosition; +} diff --git a/src/main/java/org/opentripplanner/middleware/otp/response/Trip.java b/src/main/java/org/opentripplanner/middleware/otp/response/Trip.java new file mode 100644 index 000000000..7e30cdddd --- /dev/null +++ b/src/main/java/org/opentripplanner/middleware/otp/response/Trip.java @@ -0,0 +1,13 @@ +package org.opentripplanner.middleware.otp.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class Trip { + public String gtfsId; + public String id; + public StopTime departureStoptime; + public StopTime arrivalStoptime; +} diff --git a/src/main/java/org/opentripplanner/middleware/otp/response/TripStop.java b/src/main/java/org/opentripplanner/middleware/otp/response/TripStop.java new file mode 100644 index 000000000..1ce76ce02 --- /dev/null +++ b/src/main/java/org/opentripplanner/middleware/otp/response/TripStop.java @@ -0,0 +1,6 @@ +package org.opentripplanner.middleware.otp.response; + +public class TripStop { + public String id; + public String gtfsId; +} From 8b64f92c4128b6ca22673c6b41bf7433e0ec0ac2 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 1 Oct 2024 11:31:34 -0400 Subject: [PATCH 48/48] docs(swagger): Update snapshot. --- .../latest-spark-swagger-output.yaml | 514 +++++++++--------- 1 file changed, 271 insertions(+), 243 deletions(-) diff --git a/src/main/resources/latest-spark-swagger-output.yaml b/src/main/resources/latest-spark-swagger-output.yaml index 0b3368104..a8dfcf3c0 100644 --- a/src/main/resources/latest-spark-swagger-output.yaml +++ b/src/main/resources/latest-spark-swagger-output.yaml @@ -2269,18 +2269,12 @@ definitions: type: "string" S3DownloadTimes: $ref: "#/definitions/Map" - Agency: + TripStop: type: "object" properties: - alerts: - type: "array" - items: - $ref: "#/definitions/LocalizedAlert" - gtfsId: - type: "string" id: type: "string" - name: + gtfsId: type: "string" Stop: type: "object" @@ -2315,13 +2309,6 @@ definitions: - "INITIAL_REMINDER" body: type: "string" - OtpGraphQLRoutesAndTrips: - type: "object" - properties: - routes: - type: "string" - trips: - type: "string" EncodedPolyline: type: "object" properties: @@ -2339,33 +2326,6 @@ definitions: type: "string" qualifier: type: "string" - FareDetails: - type: "object" - properties: - regular: - type: "array" - items: - $ref: "#/definitions/FareComponent" - student: - type: "array" - items: - $ref: "#/definitions/FareComponent" - senior: - type: "array" - items: - $ref: "#/definitions/FareComponent" - tram: - type: "array" - items: - $ref: "#/definitions/FareComponent" - special: - type: "array" - items: - $ref: "#/definitions/FareComponent" - youth: - type: "array" - items: - $ref: "#/definitions/FareComponent" Step: type: "object" properties: @@ -2405,6 +2365,273 @@ definitions: type: "array" items: $ref: "#/definitions/Itinerary" + Currency: + type: "object" + properties: + symbol: + type: "string" + currency: + type: "string" + defaultFractionDigits: + type: "integer" + format: "int32" + currencyCode: + type: "string" + Itinerary: + type: "object" + properties: + duration: + type: "integer" + format: "int64" + startTime: + type: "string" + format: "date" + endTime: + type: "string" + format: "date" + walkTime: + type: "integer" + format: "int64" + transitTime: + type: "integer" + format: "int64" + waitingTime: + type: "integer" + format: "int64" + walkDistance: + type: "number" + format: "double" + walkLimitExceeded: + type: "boolean" + elevationLost: + type: "number" + format: "double" + elevationGained: + type: "number" + format: "double" + transfers: + type: "integer" + format: "int32" + fare: + $ref: "#/definitions/FareWrapper" + legs: + type: "array" + items: + $ref: "#/definitions/Leg" + OtpGraphQLVariables: + type: "object" + properties: + arriveBy: + type: "boolean" + banned: + $ref: "#/definitions/OtpGraphQLRoutesAndTrips" + bikeReluctance: + type: "number" + format: "float" + carReluctance: + type: "number" + format: "float" + date: + type: "string" + fromPlace: + type: "string" + mobilityProfile: + type: "string" + modes: + type: "array" + items: + $ref: "#/definitions/OtpGraphQLTransportMode" + numItineraries: + type: "integer" + format: "int32" + preferred: + $ref: "#/definitions/OtpGraphQLRoutesAndTrips" + time: + type: "string" + toPlace: + type: "string" + unpreferred: + $ref: "#/definitions/OtpGraphQLRoutesAndTrips" + walkReluctance: + type: "number" + format: "float" + walkSpeed: + type: "number" + format: "float" + wheelchair: + type: "boolean" + FareWrapper: + type: "object" + properties: + fare: + $ref: "#/definitions/Fare" + details: + $ref: "#/definitions/FareDetails" + LocalizedAlert: + type: "object" + properties: + alertHeaderText: + type: "string" + alertDescriptionText: + type: "string" + alertUrl: + type: "string" + effectiveStartDate: + type: "string" + format: "date" + effectiveEndDate: + type: "string" + format: "date" + id: + type: "string" + Route: + type: "object" + properties: + alerts: + type: "array" + items: + $ref: "#/definitions/LocalizedAlert" + color: + type: "string" + gtfsId: + type: "string" + id: + type: "string" + longName: + type: "string" + shortName: + type: "string" + textColor: + type: "string" + type: + type: "integer" + format: "int32" + MonitoredTrip: + type: "object" + properties: + userId: + type: "string" + tripName: + type: "string" + tripTime: + type: "string" + from: + $ref: "#/definitions/Place" + to: + $ref: "#/definitions/Place" + arriveBy: + type: "boolean" + leadTimeInMinutes: + type: "integer" + format: "int32" + monday: + type: "boolean" + tuesday: + type: "boolean" + wednesday: + type: "boolean" + thursday: + type: "boolean" + friday: + type: "boolean" + saturday: + type: "boolean" + sunday: + type: "boolean" + excludeFederalHolidays: + type: "boolean" + isActive: + type: "boolean" + snoozed: + type: "boolean" + queryParams: + type: "string" + otp2QueryParams: + $ref: "#/definitions/OtpGraphQLVariables" + itinerary: + $ref: "#/definitions/Itinerary" + notifyOnAlert: + type: "boolean" + departureVarianceMinutesThreshold: + type: "integer" + format: "int32" + arrivalVarianceMinutesThreshold: + type: "integer" + format: "int32" + notifyOnItineraryChange: + type: "boolean" + itineraryExistence: + $ref: "#/definitions/ItineraryExistence" + journeyState: + $ref: "#/definitions/JourneyState" + notifyAtLeadingInterval: + type: "boolean" + StopTime: + type: "object" + properties: + stop: + $ref: "#/definitions/TripStop" + stopPosition: + type: "integer" + format: "int32" + Trip: + type: "object" + properties: + gtfsId: + type: "string" + id: + type: "string" + departureStoptime: + $ref: "#/definitions/StopTime" + arrivalStoptime: + $ref: "#/definitions/StopTime" + Agency: + type: "object" + properties: + alerts: + type: "array" + items: + $ref: "#/definitions/LocalizedAlert" + gtfsId: + type: "string" + id: + type: "string" + name: + type: "string" + OtpGraphQLRoutesAndTrips: + type: "object" + properties: + routes: + type: "string" + trips: + type: "string" + FareDetails: + type: "object" + properties: + regular: + type: "array" + items: + $ref: "#/definitions/FareComponent" + student: + type: "array" + items: + $ref: "#/definitions/FareComponent" + senior: + type: "array" + items: + $ref: "#/definitions/FareComponent" + tram: + type: "array" + items: + $ref: "#/definitions/FareComponent" + special: + type: "array" + items: + $ref: "#/definitions/FareComponent" + youth: + type: "array" + items: + $ref: "#/definitions/FareComponent" Fare: type: "object" properties: @@ -2512,6 +2739,8 @@ definitions: $ref: "#/definitions/Agency" route: $ref: "#/definitions/Route" + trip: + $ref: "#/definitions/Trip" OtpRequest: type: "object" properties: @@ -2561,18 +2790,6 @@ definitions: format: "date" otpResponseProvider: $ref: "#/definitions/Function" - Currency: - type: "object" - properties: - symbol: - type: "string" - currency: - type: "string" - defaultFractionDigits: - type: "integer" - format: "int32" - currencyCode: - type: "string" JourneyState: type: "object" properties: @@ -2612,47 +2829,6 @@ definitions: - "PAST_TRIP" hasRealtimeData: type: "boolean" - Itinerary: - type: "object" - properties: - duration: - type: "integer" - format: "int64" - startTime: - type: "string" - format: "date" - endTime: - type: "string" - format: "date" - walkTime: - type: "integer" - format: "int64" - transitTime: - type: "integer" - format: "int64" - waitingTime: - type: "integer" - format: "int64" - walkDistance: - type: "number" - format: "double" - walkLimitExceeded: - type: "boolean" - elevationLost: - type: "number" - format: "double" - elevationGained: - type: "number" - format: "double" - transfers: - type: "integer" - format: "int32" - fare: - $ref: "#/definitions/FareWrapper" - legs: - type: "array" - items: - $ref: "#/definitions/Leg" FareComponent: type: "object" properties: @@ -2664,55 +2840,6 @@ definitions: type: "array" items: type: "string" - OtpGraphQLVariables: - type: "object" - properties: - arriveBy: - type: "boolean" - banned: - $ref: "#/definitions/OtpGraphQLRoutesAndTrips" - bikeReluctance: - type: "number" - format: "float" - carReluctance: - type: "number" - format: "float" - date: - type: "string" - fromPlace: - type: "string" - mobilityProfile: - type: "string" - modes: - type: "array" - items: - $ref: "#/definitions/OtpGraphQLTransportMode" - numItineraries: - type: "integer" - format: "int32" - preferred: - $ref: "#/definitions/OtpGraphQLRoutesAndTrips" - time: - type: "string" - toPlace: - type: "string" - unpreferred: - $ref: "#/definitions/OtpGraphQLRoutesAndTrips" - walkReluctance: - type: "number" - format: "float" - walkSpeed: - type: "number" - format: "float" - wheelchair: - type: "boolean" - FareWrapper: - type: "object" - properties: - fare: - $ref: "#/definitions/Fare" - details: - $ref: "#/definitions/FareDetails" Place: type: "object" properties: @@ -2758,105 +2885,6 @@ definitions: type: "string" address: type: "string" - LocalizedAlert: - type: "object" - properties: - alertHeaderText: - type: "string" - alertDescriptionText: - type: "string" - alertUrl: - type: "string" - effectiveStartDate: - type: "string" - format: "date" - effectiveEndDate: - type: "string" - format: "date" - id: - type: "string" - Route: - type: "object" - properties: - alerts: - type: "array" - items: - $ref: "#/definitions/LocalizedAlert" - color: - type: "string" - gtfsId: - type: "string" - id: - type: "string" - longName: - type: "string" - shortName: - type: "string" - textColor: - type: "string" - type: - type: "integer" - format: "int32" - MonitoredTrip: - type: "object" - properties: - userId: - type: "string" - tripName: - type: "string" - tripTime: - type: "string" - from: - $ref: "#/definitions/Place" - to: - $ref: "#/definitions/Place" - arriveBy: - type: "boolean" - leadTimeInMinutes: - type: "integer" - format: "int32" - monday: - type: "boolean" - tuesday: - type: "boolean" - wednesday: - type: "boolean" - thursday: - type: "boolean" - friday: - type: "boolean" - saturday: - type: "boolean" - sunday: - type: "boolean" - excludeFederalHolidays: - type: "boolean" - isActive: - type: "boolean" - snoozed: - type: "boolean" - queryParams: - type: "string" - otp2QueryParams: - $ref: "#/definitions/OtpGraphQLVariables" - itinerary: - $ref: "#/definitions/Itinerary" - notifyOnAlert: - type: "boolean" - departureVarianceMinutesThreshold: - type: "integer" - format: "int32" - arrivalVarianceMinutesThreshold: - type: "integer" - format: "int32" - notifyOnItineraryChange: - type: "boolean" - itineraryExistence: - $ref: "#/definitions/ItineraryExistence" - journeyState: - $ref: "#/definitions/JourneyState" - notifyAtLeadingInterval: - type: "boolean" StartTrackingPayload: type: "object" properties: