From 5e18c0a245d23ff2585a48b47ef83720b6e9643a Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Mon, 2 Sep 2024 13:53:40 +0300 Subject: [PATCH 01/80] GTFS graphql API supports querying itinerary leg status --- .../apis/gtfs/datafetchers/LegImpl.java | 11 +++++++++++ .../apis/gtfs/datafetchers/QueryTypeImpl.java | 11 +++++++++++ .../apis/gtfs/generated/GraphQLDataFetchers.java | 2 ++ .../org/opentripplanner/apis/gtfs/schema.graphqls | 4 +++- 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java index 9ec83a4bf67..f6c53f1c84f 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java @@ -1,5 +1,6 @@ package org.opentripplanner.apis.gtfs.datafetchers; +import graphql.relay.Relay; import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; import java.util.List; @@ -23,6 +24,7 @@ import org.opentripplanner.model.plan.StreetLeg; import org.opentripplanner.model.plan.TransitLeg; import org.opentripplanner.model.plan.WalkStep; +import org.opentripplanner.model.plan.legreference.LegReferenceSerializer; import org.opentripplanner.routing.alertpatch.TransitAlert; import org.opentripplanner.routing.alternativelegs.AlternativeLegs; import org.opentripplanner.routing.alternativelegs.AlternativeLegsFilter; @@ -324,4 +326,13 @@ public DataFetcher> nextLegs() { public DataFetcher accessibilityScore() { return environment -> NumberMapper.toDouble(getSource(environment).accessibilityScore()); } + + @Override + public DataFetcher id() { + return environment -> + new Relay.ResolvedGlobalId( + "Leg", + LegReferenceSerializer.encode(getSource(environment).getLegReference()) + ); + } } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java index 0e70c13074b..604cf585357 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java @@ -42,6 +42,8 @@ import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.gtfs.mapping.DirectionMapper; import org.opentripplanner.model.TripTimeOnDate; +import org.opentripplanner.model.plan.legreference.LegReference; +import org.opentripplanner.model.plan.legreference.LegReferenceSerializer; import org.opentripplanner.routing.alertpatch.EntitySelector; import org.opentripplanner.routing.alertpatch.TransitAlert; import org.opentripplanner.routing.api.request.RouteRequest; @@ -445,6 +447,15 @@ public DataFetcher node() { // TODO: Add geometry return new NearbyStop(stop, Integer.parseInt(parts[0]), null, null); } + case "Leg": + if (id.isBlank()) { + return null; + } + LegReference ref = LegReferenceSerializer.decode(id); + if (ref == null) { + return null; + } + return ref.getLeg(transitService); case "TicketType": return null; //TODO case "Trip": diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java index 67944543580..36d262ef164 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java @@ -482,6 +482,8 @@ public interface GraphQLLeg { public DataFetcher headsign(); + public DataFetcher id(); + public DataFetcher interlineWithPreviousLeg(); public DataFetcher intermediatePlace(); diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 927af19f8b1..85c2fb8b722 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -611,7 +611,7 @@ type Itinerary { walkTime: Long } -type Leg { +type Leg implements Node { """ Computes a numeric accessibility score between 0 and 1. @@ -669,6 +669,8 @@ type Leg { For non-transit legs, null. """ headsign: String + "An identifier for the leg, which can be used to re-fetch the information." + id: ID! """ Interlines with previous leg. This is true when the same vehicle is used for the previous leg as for this leg From 021e07cd1e623fe2a6160a53622eb296b4f5da33 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Thu, 5 Sep 2024 11:44:48 +0300 Subject: [PATCH 02/80] Catch non-transit legs properly --- .../opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java index 604cf585357..421899b2ce0 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java @@ -75,7 +75,6 @@ import org.opentripplanner.updater.GtfsRealtimeFuzzyTripMatcher; public class QueryTypeImpl implements GraphQLDataFetchers.GraphQLQueryType { - // TODO: figure out a runtime solution private static final DirectionMapper DIRECTION_MAPPER = new DirectionMapper( DataImportIssueStore.NOOP @@ -448,7 +447,7 @@ public DataFetcher node() { return new NearbyStop(stop, Integer.parseInt(parts[0]), null, null); } case "Leg": - if (id.isBlank()) { + if (id.equals("null") || id.isBlank()) { return null; } LegReference ref = LegReferenceSerializer.decode(id); From 01b460d8bdcfdd3e73a6fcfbd60e1219105219a4 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Thu, 5 Sep 2024 11:45:47 +0300 Subject: [PATCH 03/80] Add type resolver for Leg --- .../apis/gtfs/datafetchers/NodeTypeResolver.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/NodeTypeResolver.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/NodeTypeResolver.java index 437d75e03e9..ae712f17252 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/NodeTypeResolver.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/NodeTypeResolver.java @@ -8,6 +8,7 @@ import graphql.schema.TypeResolver; import org.opentripplanner.ext.fares.model.FareRuleSet; import org.opentripplanner.model.TripTimeOnDate; +import org.opentripplanner.model.plan.Leg; import org.opentripplanner.routing.alertpatch.TransitAlert; import org.opentripplanner.routing.graphfinder.NearbyStop; import org.opentripplanner.routing.graphfinder.PatternAtStop; @@ -85,6 +86,9 @@ public GraphQLObjectType getType(TypeResolutionEnvironment environment) { if (o instanceof Trip) { return schema.getObjectType("Trip"); } + if (o instanceof Leg) { + return schema.getObjectType("Leg"); + } return null; } From 218206650fa648f83c5c45ecf1c7369f3e38c049 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Thu, 5 Sep 2024 13:48:09 +0300 Subject: [PATCH 04/80] Fix formatting --- .../opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java index 421899b2ce0..5a2f016fbe1 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java @@ -75,6 +75,7 @@ import org.opentripplanner.updater.GtfsRealtimeFuzzyTripMatcher; public class QueryTypeImpl implements GraphQLDataFetchers.GraphQLQueryType { + // TODO: figure out a runtime solution private static final DirectionMapper DIRECTION_MAPPER = new DirectionMapper( DataImportIssueStore.NOOP From 7e2033f04d3199cc2d52037d8635c82a5356648d Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Fri, 6 Sep 2024 08:57:07 +0300 Subject: [PATCH 05/80] Do not encode null reference as 'null' string --- .../apis/gtfs/datafetchers/LegImpl.java | 10 +++++----- .../apis/gtfs/datafetchers/QueryTypeImpl.java | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java index f6c53f1c84f..292a60876c7 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java @@ -329,10 +329,10 @@ public DataFetcher accessibilityScore() { @Override public DataFetcher id() { - return environment -> - new Relay.ResolvedGlobalId( - "Leg", - LegReferenceSerializer.encode(getSource(environment).getLegReference()) - ); + return environment -> { + var ref = getSource(environment).getLegReference(); + var id = (ref == null) ? "" : LegReferenceSerializer.encode(ref); + return new Relay.ResolvedGlobalId("Leg", id); + }; } } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java index 5a2f016fbe1..604cf585357 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java @@ -448,7 +448,7 @@ public DataFetcher node() { return new NearbyStop(stop, Integer.parseInt(parts[0]), null, null); } case "Leg": - if (id.equals("null") || id.isBlank()) { + if (id.isBlank()) { return null; } LegReference ref = LegReferenceSerializer.decode(id); From 6b927413562a5d8f511432b0f7af6f64fbce12c0 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Fri, 6 Sep 2024 10:03:15 +0300 Subject: [PATCH 06/80] More accurate docs about leg query --- .../apis/transmodel/TransmodelGraphQLSchema.java | 2 +- .../opentripplanner/apis/transmodel/model/plan/LegType.java | 4 +++- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 2 +- .../org/opentripplanner/apis/transmodel/schema.graphql | 4 ++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java index 9ad43606420..65243af34d3 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java @@ -1596,7 +1596,7 @@ private GraphQLSchema create() { GraphQLFieldDefinition .newFieldDefinition() .name("leg") - .description("Refetch a single leg based on its id") + .description("Refetch a single transit leg based on its id") .withDirective(gqlUtil.timingData) .type(LegType.REF) .argument( diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/LegType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/LegType.java index be05d00e16d..ca48dce882a 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/LegType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/LegType.java @@ -63,7 +63,9 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("id") - .description("An identifier for the leg, which can be used to re-fetch the information.") + .description( + "An identifier for the leg, which can be used to re-fetch transit leg information." + ) .type(Scalars.GraphQLID) .dataFetcher(env -> LegReferenceSerializer.encode(leg(env).getLegReference())) .build() diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 85c2fb8b722..a4121eaae6c 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -669,7 +669,7 @@ type Leg implements Node { For non-transit legs, null. """ headsign: String - "An identifier for the leg, which can be used to re-fetch the information." + "An identifier for the leg, which can be used to re-fetch transit leg information." id: ID! """ Interlines with previous leg. diff --git a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql index 0d2bf71dcc6..37ca2f34945 100644 --- a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -325,7 +325,7 @@ type Leg { fromPlace: Place! "Generalized cost or weight of the leg. Used for debugging." generalizedCost: Int - "An identifier for the leg, which can be used to re-fetch the information." + "An identifier for the leg, which can be used to re-fetch transit leg information." id: ID interchangeFrom: Interchange interchangeTo: Interchange @@ -647,7 +647,7 @@ type QueryType { groupOfLines(id: String!): GroupOfLines "Get all groups of lines" groupsOfLines: [GroupOfLines!]! - "Refetch a single leg based on its id" + "Refetch a single transit leg based on its id" leg(id: ID!): Leg @timingData "Get a single line based on its id" line(id: ID!): Line @timingData From e489f0534bc160e8915c3ee927d3d8c56307c12e Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Fri, 6 Sep 2024 11:03:57 +0300 Subject: [PATCH 07/80] Add leg id to plan query test --- .../apis/gtfs/expectations/plan-extended.json | 14 +++++++++----- .../apis/gtfs/queries/plan-extended.graphql | 1 + 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-extended.json b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-extended.json index ea58480be8e..55da843bdfa 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-extended.json +++ b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-extended.json @@ -64,7 +64,8 @@ "intermediatePlaces" : null, "alerts" : [ ], "rideHailingEstimate" : null, - "accessibilityScore" : null + "accessibilityScore" : null, + "id" : "TGVnOg" }, { "mode" : "BUS", @@ -154,7 +155,8 @@ ], "alerts" : [ ], "rideHailingEstimate" : null, - "accessibilityScore" : null + "accessibilityScore" : null, + "id": "TGVnOnJPMEFCWGRCQUJoVFEwaEZSRlZNUlVSZlZGSkJUbE5KVkY5TVJVZGZWak1BQlVZNk1USXlBQW95TURJd0xUQXlMVEF5QUFBQUJRQUFBQWNBQTBZNlFnQURSanBEQUFBPQ" }, { "mode" : "RAIL", @@ -264,7 +266,8 @@ } ], "rideHailingEstimate" : null, - "accessibilityScore" : null + "accessibilityScore" : null, + "id": "TGVnOnJPMEFCWGRCQUJoVFEwaEZSRlZNUlVSZlZGSkJUbE5KVkY5TVJVZGZWak1BQlVZNk5ETTVBQW95TURJd0xUQXlMVEF5QUFBQUJRQUFBQWNBQTBZNlF3QURSanBFQUFBPQ" }, { "mode" : "CAR", @@ -334,11 +337,12 @@ }, "arrival" : "PT10M" }, - "accessibilityScore" : null + "accessibilityScore" : null, + "id" : "TGVnOg" } ] } ] } } -} \ No newline at end of file +} diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql b/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql index 76bf8aa84e0..a96eeca5e7a 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql +++ b/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql @@ -151,6 +151,7 @@ arrival } accessibilityScore + id } } } From 6bd5d041b058aaeaa51aa08c3e8e59f3e8dc910a Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Mon, 9 Sep 2024 09:48:59 +0300 Subject: [PATCH 08/80] Use self-documenting value for non-transit leg ids Also, move all encoding/decoding logig to LegReferenceSerializer class. --- .../org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java | 2 +- .../apis/gtfs/datafetchers/QueryTypeImpl.java | 3 --- .../model/plan/legreference/LegReferenceSerializer.java | 6 ++++-- .../model/plan/legreference/LegReferenceSerializerTest.java | 4 ++-- .../apis/gtfs/expectations/plan-extended.json | 4 ++-- 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java index 292a60876c7..1d8b72ac173 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java @@ -331,7 +331,7 @@ public DataFetcher accessibilityScore() { public DataFetcher id() { return environment -> { var ref = getSource(environment).getLegReference(); - var id = (ref == null) ? "" : LegReferenceSerializer.encode(ref); + var id = LegReferenceSerializer.encode(ref); return new Relay.ResolvedGlobalId("Leg", id); }; } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java index 604cf585357..b064582f5d8 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java @@ -448,9 +448,6 @@ public DataFetcher node() { return new NearbyStop(stop, Integer.parseInt(parts[0]), null, null); } case "Leg": - if (id.isBlank()) { - return null; - } LegReference ref = LegReferenceSerializer.decode(id); if (ref == null) { return null; diff --git a/src/main/java/org/opentripplanner/model/plan/legreference/LegReferenceSerializer.java b/src/main/java/org/opentripplanner/model/plan/legreference/LegReferenceSerializer.java index 0ceae6cb456..9e1424d3d47 100644 --- a/src/main/java/org/opentripplanner/model/plan/legreference/LegReferenceSerializer.java +++ b/src/main/java/org/opentripplanner/model/plan/legreference/LegReferenceSerializer.java @@ -18,6 +18,8 @@ */ public class LegReferenceSerializer { + public static final String notAvailable = "NotAvailable"; + private static final Logger LOG = LoggerFactory.getLogger(LegReferenceSerializer.class); /** private constructor to prevent instantiating this utility class */ @@ -26,7 +28,7 @@ private LegReferenceSerializer() {} @Nullable public static String encode(LegReference legReference) { if (legReference == null) { - return null; + return notAvailable; } LegReferenceType typeEnum = LegReferenceType .forClass(legReference.getClass()) @@ -47,7 +49,7 @@ public static String encode(LegReference legReference) { @Nullable public static LegReference decode(String legReference) { - if (legReference == null) { + if (legReference.equals(notAvailable)) { return null; } diff --git a/src/test/java/org/opentripplanner/model/plan/legreference/LegReferenceSerializerTest.java b/src/test/java/org/opentripplanner/model/plan/legreference/LegReferenceSerializerTest.java index 2d7df788ad3..8422e98fe69 100644 --- a/src/test/java/org/opentripplanner/model/plan/legreference/LegReferenceSerializerTest.java +++ b/src/test/java/org/opentripplanner/model/plan/legreference/LegReferenceSerializerTest.java @@ -89,8 +89,8 @@ void testScheduledTransitLegReferenceLegacyV2Deserialize() { } @Test - void testNullSerializedLegReference() { - assertNull(LegReferenceSerializer.decode(null)); + void testUnresolvedSerializedLegReference() { + assertNull(LegReferenceSerializer.decode(LegReferenceSerializer.notAvailable)); } @Test diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-extended.json b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-extended.json index 55da843bdfa..0a9ebd9807e 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-extended.json +++ b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-extended.json @@ -65,7 +65,7 @@ "alerts" : [ ], "rideHailingEstimate" : null, "accessibilityScore" : null, - "id" : "TGVnOg" + "id" : "TGVnOk5vdEF2YWlsYWJsZQ" }, { "mode" : "BUS", @@ -338,7 +338,7 @@ "arrival" : "PT10M" }, "accessibilityScore" : null, - "id" : "TGVnOg" + "id": "TGVnOk5vdEF2YWlsYWJsZQ" } ] } From a998e52bbf5cee7188a8c849c6e8429dcce411af Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Mon, 9 Sep 2024 09:58:35 +0300 Subject: [PATCH 09/80] Add more docs about leg re-fetching into gtfs graphql schema --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index a4121eaae6c..4c9b25c1048 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -669,7 +669,11 @@ type Leg implements Node { For non-transit legs, null. """ headsign: String - "An identifier for the leg, which can be used to re-fetch transit leg information." + """ + An identifier for the leg, which can be used to re-fetch transit leg information. + Re-fetching fails when the underlying transit data no longer exists. + Non-transit legs cannot be refetched using their id. + """ id: ID! """ Interlines with previous leg. From cf37e89dffbf00e4a9e61548410f3c43466943cd Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Wed, 11 Sep 2024 08:10:58 +0300 Subject: [PATCH 10/80] Support querying leg's realTimeState in GTFS graphql api --- .../opentripplanner/apis/gtfs/datafetchers/LegImpl.java | 3 +-- src/main/java/org/opentripplanner/model/plan/Leg.java | 5 +++++ .../opentripplanner/model/plan/ScheduledTransitLeg.java | 7 +++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java index 1d8b72ac173..4f54eadb3c0 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java @@ -191,10 +191,9 @@ public DataFetcher realTime() { return environment -> getSource(environment).getRealTime(); } - // TODO @Override public DataFetcher realtimeState() { - return environment -> null; + return environment -> getSource(environment).getRealTimeState().name(); } @Override diff --git a/src/main/java/org/opentripplanner/model/plan/Leg.java b/src/main/java/org/opentripplanner/model/plan/Leg.java index 2a0b6726560..218907f3ca8 100644 --- a/src/main/java/org/opentripplanner/model/plan/Leg.java +++ b/src/main/java/org/opentripplanner/model/plan/Leg.java @@ -22,6 +22,7 @@ import org.opentripplanner.transit.model.organization.Agency; import org.opentripplanner.transit.model.organization.Operator; import org.opentripplanner.transit.model.site.FareZone; +import org.opentripplanner.transit.model.timetable.RealTimeState; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripOnServiceDate; import org.opentripplanner.transit.model.timetable.booking.BookingInfo; @@ -244,6 +245,10 @@ default boolean getRealTime() { return false; } + default RealTimeState getRealTimeState() { + return RealTimeState.SCHEDULED; + } + /** * Whether this Leg describes a flexible trip. The reason we need this is that FlexTrip does not * inherit from Trip, so that the information that the Trip is flexible would be lost when diff --git a/src/main/java/org/opentripplanner/model/plan/ScheduledTransitLeg.java b/src/main/java/org/opentripplanner/model/plan/ScheduledTransitLeg.java index d94ec1895c2..25b329ed055 100644 --- a/src/main/java/org/opentripplanner/model/plan/ScheduledTransitLeg.java +++ b/src/main/java/org/opentripplanner/model/plan/ScheduledTransitLeg.java @@ -34,6 +34,7 @@ import org.opentripplanner.transit.model.organization.Agency; import org.opentripplanner.transit.model.organization.Operator; import org.opentripplanner.transit.model.site.StopLocation; +import org.opentripplanner.transit.model.timetable.RealTimeState; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripOnServiceDate; import org.opentripplanner.transit.model.timetable.TripTimes; @@ -227,6 +228,11 @@ public boolean getRealTime() { ); } + @Override + public RealTimeState getRealTimeState() { + return tripTimes.getRealTimeState(); + } + @Override public double getDistanceMeters() { return distanceMeters; @@ -427,6 +433,7 @@ public String toString() { .addEnum("alightRule", getAlightRule()) .addObj("transferFromPrevLeg", transferFromPrevLeg) .addObj("transferToNextLeg", transferToNextLeg) + .addEnum("realtimeState", getRealTimeState()) .toString(); } From cd54cbe378d8bb4691b6a88bffed87ed5cf820c4 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Wed, 11 Sep 2024 08:39:07 +0300 Subject: [PATCH 11/80] Query realtimeState in extended plan test --- .../apis/gtfs/expectations/plan-extended.json | 12 ++++++++---- .../apis/gtfs/queries/plan-extended.graphql | 1 + 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-extended.json b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-extended.json index 0a9ebd9807e..939df7dce3c 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-extended.json +++ b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-extended.json @@ -65,7 +65,8 @@ "alerts" : [ ], "rideHailingEstimate" : null, "accessibilityScore" : null, - "id" : "TGVnOk5vdEF2YWlsYWJsZQ" + "id" : "TGVnOk5vdEF2YWlsYWJsZQ", + "realtimeState": "SCHEDULED" }, { "mode" : "BUS", @@ -156,7 +157,8 @@ "alerts" : [ ], "rideHailingEstimate" : null, "accessibilityScore" : null, - "id": "TGVnOnJPMEFCWGRCQUJoVFEwaEZSRlZNUlVSZlZGSkJUbE5KVkY5TVJVZGZWak1BQlVZNk1USXlBQW95TURJd0xUQXlMVEF5QUFBQUJRQUFBQWNBQTBZNlFnQURSanBEQUFBPQ" + "id": "TGVnOnJPMEFCWGRCQUJoVFEwaEZSRlZNUlVSZlZGSkJUbE5KVkY5TVJVZGZWak1BQlVZNk1USXlBQW95TURJd0xUQXlMVEF5QUFBQUJRQUFBQWNBQTBZNlFnQURSanBEQUFBPQ", + "realtimeState": "UPDATED" }, { "mode" : "RAIL", @@ -267,7 +269,8 @@ ], "rideHailingEstimate" : null, "accessibilityScore" : null, - "id": "TGVnOnJPMEFCWGRCQUJoVFEwaEZSRlZNUlVSZlZGSkJUbE5KVkY5TVJVZGZWak1BQlVZNk5ETTVBQW95TURJd0xUQXlMVEF5QUFBQUJRQUFBQWNBQTBZNlF3QURSanBFQUFBPQ" + "id": "TGVnOnJPMEFCWGRCQUJoVFEwaEZSRlZNUlVSZlZGSkJUbE5KVkY5TVJVZGZWak1BQlVZNk5ETTVBQW95TURJd0xUQXlMVEF5QUFBQUJRQUFBQWNBQTBZNlF3QURSanBFQUFBPQ", + "realtimeState": "UPDATED" }, { "mode" : "CAR", @@ -338,7 +341,8 @@ "arrival" : "PT10M" }, "accessibilityScore" : null, - "id": "TGVnOk5vdEF2YWlsYWJsZQ" + "id": "TGVnOk5vdEF2YWlsYWJsZQ", + "realtimeState": "SCHEDULED" } ] } diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql b/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql index a96eeca5e7a..7823ae91bab 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql +++ b/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql @@ -152,6 +152,7 @@ } accessibilityScore id + realtimeState } } } From 4025d5b60d12d2e6807a76e4770892599d9cf56d Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Fri, 13 Sep 2024 08:22:25 +0300 Subject: [PATCH 12/80] Revert LegReferenceSerializer changes --- .../model/plan/legreference/LegReferenceSerializer.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/opentripplanner/model/plan/legreference/LegReferenceSerializer.java b/src/main/java/org/opentripplanner/model/plan/legreference/LegReferenceSerializer.java index 9e1424d3d47..0ceae6cb456 100644 --- a/src/main/java/org/opentripplanner/model/plan/legreference/LegReferenceSerializer.java +++ b/src/main/java/org/opentripplanner/model/plan/legreference/LegReferenceSerializer.java @@ -18,8 +18,6 @@ */ public class LegReferenceSerializer { - public static final String notAvailable = "NotAvailable"; - private static final Logger LOG = LoggerFactory.getLogger(LegReferenceSerializer.class); /** private constructor to prevent instantiating this utility class */ @@ -28,7 +26,7 @@ private LegReferenceSerializer() {} @Nullable public static String encode(LegReference legReference) { if (legReference == null) { - return notAvailable; + return null; } LegReferenceType typeEnum = LegReferenceType .forClass(legReference.getClass()) @@ -49,7 +47,7 @@ public static String encode(LegReference legReference) { @Nullable public static LegReference decode(String legReference) { - if (legReference.equals(notAvailable)) { + if (legReference == null) { return null; } From b206ce18b3d153a4a436ef7d9b1abb1e729fe8a1 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Fri, 13 Sep 2024 08:23:47 +0300 Subject: [PATCH 13/80] Generate unique id for non-transit legs --- .../apis/gtfs/datafetchers/LegImpl.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java index 4f54eadb3c0..4b3ddf9019d 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java @@ -329,8 +329,22 @@ public DataFetcher accessibilityScore() { @Override public DataFetcher id() { return environment -> { + var leg = getSource(environment); var ref = getSource(environment).getLegReference(); - var id = LegReferenceSerializer.encode(ref); + String id; + + if (ref == null) { + id = + String.join( + "", + leg.start().time().toString(), + leg.end().time().toString(), + leg.getFrom().toStringShort(), + leg.getTo().toStringShort() + ); + } else { + id = LegReferenceSerializer.encode(ref); + } return new Relay.ResolvedGlobalId("Leg", id); }; } From 15f1f3486ec69276013c0ec8c13c390ceea2371b Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Fri, 13 Sep 2024 08:29:31 +0300 Subject: [PATCH 14/80] Restore LegReferenceSerializer null test --- .../model/plan/legreference/LegReferenceSerializerTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/opentripplanner/model/plan/legreference/LegReferenceSerializerTest.java b/src/test/java/org/opentripplanner/model/plan/legreference/LegReferenceSerializerTest.java index 8422e98fe69..2d7df788ad3 100644 --- a/src/test/java/org/opentripplanner/model/plan/legreference/LegReferenceSerializerTest.java +++ b/src/test/java/org/opentripplanner/model/plan/legreference/LegReferenceSerializerTest.java @@ -89,8 +89,8 @@ void testScheduledTransitLegReferenceLegacyV2Deserialize() { } @Test - void testUnresolvedSerializedLegReference() { - assertNull(LegReferenceSerializer.decode(LegReferenceSerializer.notAvailable)); + void testNullSerializedLegReference() { + assertNull(LegReferenceSerializer.decode(null)); } @Test From 20f855a7059cd9fd3d2c825bcd406616518cd36b Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Fri, 13 Sep 2024 08:38:34 +0300 Subject: [PATCH 15/80] Update plan-extended expoctations to use unique leg ids --- .../opentripplanner/apis/gtfs/expectations/plan-extended.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-extended.json b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-extended.json index 939df7dce3c..348b10c816a 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-extended.json +++ b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-extended.json @@ -65,7 +65,7 @@ "alerts" : [ ], "rideHailingEstimate" : null, "accessibilityScore" : null, - "id" : "TGVnOk5vdEF2YWlsYWJsZQ", + "id": "TGVnOjIwMjAtMDItMDJUMTE6MDBaMjAyMC0wMi0wMlQxMTowMDoyMFpBIChGOkEpQiAoRjpCKQ", "realtimeState": "SCHEDULED" }, { @@ -341,7 +341,7 @@ "arrival" : "PT10M" }, "accessibilityScore" : null, - "id": "TGVnOk5vdEF2YWlsYWJsZQ", + "id": "TGVnOjIwMjAtMDItMDJUMTE6NTBaMjAyMC0wMi0wMlQxMjowMFpEIChGOkQpRSAoRjpFKQ", "realtimeState": "SCHEDULED" } ] From f7fc002b0b51669ea532a5d245dd2dd50b39310e Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Mon, 22 Jul 2024 14:00:41 +0200 Subject: [PATCH 16/80] Introduce timetable builder --- .../RealtimeResolverTest.java | 7 +- .../ext/siri/AddedTripBuilder.java | 18 +- .../module/TimeZoneAdjusterModule.java | 13 +- .../gtfs/GenerateTripPatternsOperation.java | 59 ++-- .../org/opentripplanner/model/Timetable.java | 89 ++---- .../model/TimetableBuilder.java | 131 ++++++++ .../model/TimetableSnapshot.java | 61 ++-- .../model/impl/OtpTransitServiceBuilder.java | 35 +- .../netex/mapping/TripPatternMapper.java | 4 +- .../transit/model/network/TripPattern.java | 73 +---- .../model/network/TripPatternBuilder.java | 39 ++- .../apis/gtfs/GraphQLIntegrationTest.java | 6 +- .../gtfs/PatternByServiceDatesFilterTest.java | 13 +- .../GenerateTripPatternsOperationTest.java | 302 ++++++++++++++++++ .../interlining/InterlineProcessorTest.java | 7 +- .../model/TimetableSnapshotTest.java | 75 +++-- .../opentripplanner/model/TimetableTest.java | 10 +- ...pTransitServiceBuilderLimitPeriodTest.java | 40 ++- .../model/plan/TestItineraryBuilder.java | 4 +- .../ScheduledTransitLegReferenceTest.java | 19 +- .../mappers/TripPatternForDateMapperTest.java | 3 +- .../transit/request/TestRouteData.java | 2 +- .../stoptimes/StopTimesHelperTest.java | 19 +- .../updater/trip/RealtimeTestEnvironment.java | 2 +- .../RealtimeVehicleMatcherTest.java | 6 +- 25 files changed, 739 insertions(+), 298 deletions(-) create mode 100644 src/main/java/org/opentripplanner/model/TimetableBuilder.java create mode 100644 src/test/java/org/opentripplanner/gtfs/GenerateTripPatternsOperationTest.java diff --git a/src/ext-test/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolverTest.java b/src/ext-test/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolverTest.java index 21d5a7f1696..e5b842a474a 100644 --- a/src/ext-test/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolverTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolverTest.java @@ -129,9 +129,12 @@ void testPopulateLegsWithRealtimeKeepStaySeated() { private static TripPattern delay(TripPattern pattern1, int seconds) { var originalTimeTable = pattern1.getScheduledTimetable(); - var delayedTimetable = new Timetable(pattern1); var delayedTripTimes = delay(originalTimeTable.getTripTimes(0), seconds); - delayedTimetable.addTripTimes(delayedTripTimes); + var delayedTimetable = Timetable + .of() + .withTripPattern(pattern1) + .addTripTimes(delayedTripTimes) + .build(); return pattern1.copy().withScheduledTimeTable(delayedTimetable).build(); } diff --git a/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java b/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java index cdbaa8f3d4c..ed2378a839d 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java +++ b/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java @@ -210,13 +210,6 @@ Result build() { // TODO: We always create a new TripPattern to be able to modify its scheduled timetable StopPattern stopPattern = new StopPattern(aimedStopTimes); - TripPattern pattern = TripPattern - .of(getTripPatternId.apply(trip)) - .withRoute(trip.getRoute()) - .withMode(trip.getMode()) - .withNetexSubmode(trip.getNetexSubMode()) - .withStopPattern(stopPattern) - .build(); RealTimeTripTimes tripTimes = TripTimesFactory.tripTimes( trip, @@ -229,7 +222,16 @@ Result build() { // therefore they must be valid tripTimes.validateNonIncreasingTimes(); tripTimes.setServiceCode(transitService.getServiceCodeForId(trip.getServiceId())); - pattern.add(tripTimes); + + TripPattern pattern = TripPattern + .of(getTripPatternId.apply(trip)) + .withRoute(trip.getRoute()) + .withMode(trip.getMode()) + .withNetexSubmode(trip.getNetexSubMode()) + .withStopPattern(stopPattern) + .withScheduledTimeTableBuilder(builder -> builder.addTripTimes(tripTimes)) + .build(); + RealTimeTripTimes updatedTripTimes = tripTimes.copyScheduledTimes(); // Loop through calls again and apply updates diff --git a/src/main/java/org/opentripplanner/graph_builder/module/TimeZoneAdjusterModule.java b/src/main/java/org/opentripplanner/graph_builder/module/TimeZoneAdjusterModule.java index eb3674bf76a..7168066afe3 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/TimeZoneAdjusterModule.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/TimeZoneAdjusterModule.java @@ -7,6 +7,7 @@ import java.util.HashMap; import java.util.Map; import org.opentripplanner.graph_builder.model.GraphBuilderModule; +import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.service.TransitModel; /** @@ -45,9 +46,15 @@ public void buildGraph() { return; } - pattern - .getScheduledTimetable() - .updateAllTripTimes(it -> it.adjustTimesToGraphTimeZone(timeShift)); + TripPattern updatedPattern = pattern + .copy() + .withScheduledTimeTableBuilder(builder -> + builder.updateAllTripTimes(tt -> tt.adjustTimesToGraphTimeZone(timeShift)) + ) + .build(); + // replace the original pattern with the updated pattern in the transit model + transitModel.addTripPattern(updatedPattern.getId(), updatedPattern); }); + transitModel.index(); } } diff --git a/src/main/java/org/opentripplanner/gtfs/GenerateTripPatternsOperation.java b/src/main/java/org/opentripplanner/gtfs/GenerateTripPatternsOperation.java index bf26f9c6d7b..84b25340e6c 100644 --- a/src/main/java/org/opentripplanner/gtfs/GenerateTripPatternsOperation.java +++ b/src/main/java/org/opentripplanner/gtfs/GenerateTripPatternsOperation.java @@ -23,6 +23,7 @@ import org.opentripplanner.transit.model.network.Route; import org.opentripplanner.transit.model.network.StopPattern; import org.opentripplanner.transit.model.network.TripPattern; +import org.opentripplanner.transit.model.network.TripPatternBuilder; import org.opentripplanner.transit.model.timetable.Direction; import org.opentripplanner.transit.model.timetable.FrequencyEntry; import org.opentripplanner.transit.model.timetable.Trip; @@ -40,13 +41,13 @@ public class GenerateTripPatternsOperation { private final Map tripPatternIdCounters = new HashMap<>(); - private final OtpTransitServiceBuilder transitDaoBuilder; + private final OtpTransitServiceBuilder transitServiceBuilder; private final DataImportIssueStore issueStore; private final Deduplicator deduplicator; private final Set calendarServiceIds; private final GeometryProcessor geometryProcessor; - private final Multimap tripPatterns; + private final Multimap tripPatternBuilders = ArrayListMultimap.create(); private final ListMultimap frequenciesForTrip = ArrayListMultimap.create(); private int freqCount = 0; @@ -59,18 +60,17 @@ public GenerateTripPatternsOperation( Set calendarServiceIds, GeometryProcessor geometryProcessor ) { - this.transitDaoBuilder = builder; + this.transitServiceBuilder = builder; this.issueStore = issueStore; this.deduplicator = deduplicator; this.calendarServiceIds = calendarServiceIds; this.geometryProcessor = geometryProcessor; - this.tripPatterns = transitDaoBuilder.getTripPatterns(); } public void run() { collectFrequencyByTrip(); - final Collection trips = transitDaoBuilder.getTripsById().values(); + final Collection trips = transitServiceBuilder.getTripsById().values(); var progressLogger = ProgressTracker.track("build trip patterns", 50_000, trips.size()); LOG.info(progressLogger.startMessage()); @@ -85,6 +85,14 @@ public void run() { } } + tripPatternBuilders + .values() + .stream() + .map(TripPatternBuilder::build) + .forEach(tripPattern -> + transitServiceBuilder.getTripPatterns().put(tripPattern.getStopPattern(), tripPattern) + ); + LOG.info(progressLogger.completeMessage()); LOG.info( "Added {} frequency-based and {} single-trip timetable entries.", @@ -107,7 +115,7 @@ public boolean hasScheduledTrips() { * the same trip can be added at once to the same Timetable/TripPattern. */ private void collectFrequencyByTrip() { - for (Frequency freq : transitDaoBuilder.getFrequencies()) { + for (Frequency freq : transitServiceBuilder.getFrequencies()) { frequenciesForTrip.put(freq.getTrip(), freq); } } @@ -119,7 +127,7 @@ private void buildTripPatternForTrip(Trip trip) { return; // Invalid trip, skip it, it will break later } - List stopTimes = transitDaoBuilder.getStopTimesSortedByTrip().get(trip); + List stopTimes = transitServiceBuilder.getStopTimesSortedByTrip().get(trip); // If after filtering this trip does not contain at least 2 stoptimes, it does not serve any purpose. var staticTripWithFewerThan2Stops = @@ -134,8 +142,7 @@ private void buildTripPatternForTrip(Trip trip) { // Get the existing TripPattern for this filtered StopPattern, or create one. StopPattern stopPattern = new StopPattern(stopTimes); - Direction direction = trip.getDirection(); - TripPattern tripPattern = findOrCreateTripPattern(stopPattern, trip, direction); + TripPatternBuilder tripPatternBuilder = findOrCreateTripPattern(stopPattern, trip); // Create a TripTimes object for this list of stoptimes, which form one trip. TripTimes tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, deduplicator); @@ -144,44 +151,42 @@ private void buildTripPatternForTrip(Trip trip) { List frequencies = frequenciesForTrip.get(trip); if (!frequencies.isEmpty()) { for (Frequency freq : frequencies) { - tripPattern.add(new FrequencyEntry(freq, tripTimes)); + tripPatternBuilder.withScheduledTimeTableBuilder(builder -> + builder.addFrequencyEntry(new FrequencyEntry(freq, tripTimes)) + ); freqCount++; } } // This trip was not frequency-based. Add the TripTimes directly to the TripPattern's scheduled timetable. else { - tripPattern.add(tripTimes); + tripPatternBuilder.withScheduledTimeTableBuilder(builder -> builder.addTripTimes(tripTimes)); scheduledCount++; } } - private TripPattern findOrCreateTripPattern( - StopPattern stopPattern, - Trip trip, - Direction direction - ) { + private TripPatternBuilder findOrCreateTripPattern(StopPattern stopPattern, Trip trip) { Route route = trip.getRoute(); - for (TripPattern tripPattern : tripPatterns.get(stopPattern)) { + Direction direction = trip.getDirection(); + for (TripPatternBuilder tripPatternBuilder : tripPatternBuilders.get(stopPattern)) { if ( - tripPattern.getRoute().equals(route) && - tripPattern.getDirection().equals(direction) && - tripPattern.getMode().equals(trip.getMode()) && - tripPattern.getNetexSubmode().equals(trip.getNetexSubMode()) + tripPatternBuilder.getRoute().equals(route) && + tripPatternBuilder.getDirection().equals(direction) && + tripPatternBuilder.getMode().equals(trip.getMode()) && + tripPatternBuilder.getNetexSubmode().equals(trip.getNetexSubMode()) ) { - return tripPattern; + return tripPatternBuilder; } } FeedScopedId patternId = generateUniqueIdForTripPattern(route, direction); - TripPattern tripPattern = TripPattern + TripPatternBuilder tripPatternBuilder = TripPattern .of(patternId) .withRoute(route) .withStopPattern(stopPattern) .withMode(trip.getMode()) .withNetexSubmode(trip.getNetexSubMode()) - .withHopGeometries(geometryProcessor.createHopGeometries(trip)) - .build(); - tripPatterns.put(stopPattern, tripPattern); - return tripPattern; + .withHopGeometries(geometryProcessor.createHopGeometries(trip)); + tripPatternBuilders.put(stopPattern, tripPatternBuilder); + return tripPatternBuilder; } /** diff --git a/src/main/java/org/opentripplanner/model/Timetable.java b/src/main/java/org/opentripplanner/model/Timetable.java index 10c384e6211..5e2e02c3ffd 100644 --- a/src/main/java/org/opentripplanner/model/Timetable.java +++ b/src/main/java/org/opentripplanner/model/Timetable.java @@ -19,10 +19,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; -import java.util.function.UnaryOperator; -import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.opentripplanner.framework.time.ServiceDateUtils; import org.opentripplanner.transit.model.framework.DataValidationException; @@ -48,8 +45,7 @@ * one Timetable when stop time updates are being applied: one for the scheduled stop times, one for * each snapshot of updated stop times, another for a working buffer of updated stop times, etc. *

- * TODO OTP2 - Move this to package: org.opentripplanner.model after as Entur NeTEx PRs are merged. - * Also consider moving its dependencies into package org.opentripplanner.routing. The NEW + * TODO OTP2 consider moving dependencies into package org.opentripplanner.routing. The NEW * Timetable should not have any dependencies to [?] */ public class Timetable implements Serializable { @@ -58,28 +54,31 @@ public class Timetable implements Serializable { private final TripPattern pattern; - private final List tripTimes = new ArrayList<>(); + private final List tripTimes; - private final List frequencyEntries = new ArrayList<>(); + private final List frequencyEntries; @Nullable private final LocalDate serviceDate; - /** Construct an empty Timetable. */ - public Timetable(TripPattern pattern) { - this.pattern = pattern; - this.serviceDate = null; + Timetable(TimetableBuilder timetableBuilder) { + this.pattern = timetableBuilder.getPattern(); + this.serviceDate = timetableBuilder.getServiceDate(); + tripTimes = List.copyOf(timetableBuilder.getTripTimes()); + frequencyEntries = List.copyOf(timetableBuilder.getFrequencies()); } /** * Copy constructor: create an un-indexed Timetable with the same TripTimes as the specified * timetable. */ - Timetable(Timetable tt, @Nonnull LocalDate serviceDate) { - Objects.requireNonNull(serviceDate); - tripTimes.addAll(tt.tripTimes); - this.serviceDate = serviceDate; - this.pattern = tt.pattern; + public static TimetableBuilder of(Timetable tt) { + return new TimetableBuilder(tt); + } + + /** Construct an empty Timetable. */ + public static TimetableBuilder of() { + return new TimetableBuilder(); } /** @return the index of TripTimes for this trip ID in this particular Timetable */ @@ -134,17 +133,6 @@ public TripTimes getTripTimes(FeedScopedId tripId) { return null; } - /** - * Set new trip times for trip given a trip index - * - * @param tripIndex trip index of trip - * @param tt new trip times for trip - * @return old trip times of trip - */ - public TripTimes setTripTimes(int tripIndex, TripTimes tt) { - return tripTimes.set(tripIndex, tt); - } - /** * Apply the TripUpdate to the appropriate TripTimes from this Timetable. The existing TripTimes * must not be modified directly because they may be shared with the underlying @@ -386,44 +374,6 @@ public Result createUpdatedTripTimesFromGTFSRT( return Result.success(new TripTimesPatch(newTimes, skippedStopIndices)); } - /** - * Add a trip to this Timetable. The Timetable must be analyzed, compacted, and indexed any time - * trips are added, but this is not done automatically because it is time consuming and should - * only be done once after an entire batch of trips are added. Note that the trip is not added to - * the enclosing pattern here, but in the pattern's wrapper function. Here we don't know if it's a - * scheduled trip or a realtime-added trip. - */ - public void addTripTimes(TripTimes tt) { - tripTimes.add(tt); - } - - /** - * Apply the same update to all trip-times inculuding scheduled and frequency based - * trip times. - *

- * THIS IS NOT THREAD-SAFE - ONLY USE THIS METHOD DURING GRAPH-BUILD! - */ - public void updateAllTripTimes(UnaryOperator update) { - tripTimes.replaceAll(update); - frequencyEntries.replaceAll(it -> - new FrequencyEntry( - it.startTime, - it.endTime, - it.headway, - it.exactTimes, - update.apply(it.tripTimes) - ) - ); - } - - /** - * Add a frequency entry to this Timetable. See addTripTimes method. Maybe Frequency Entries - * should just be TripTimes for simplicity. - */ - public void addFrequencyEntry(FrequencyEntry freq) { - frequencyEntries.add(freq); - } - public boolean isValidFor(LocalDate serviceDate) { return this.serviceDate == null || this.serviceDate.equals(serviceDate); } @@ -493,4 +443,13 @@ public TripTimes getRepresentativeTripTimes() { return null; } } + + /** + * @return true if the timetable was created by a real-time update, false if this + * timetable is based on scheduled data. + * Only real-time timetables have a service date. + */ + public boolean isCreatedByRealTimeUpdater() { + return serviceDate != null; + } } diff --git a/src/main/java/org/opentripplanner/model/TimetableBuilder.java b/src/main/java/org/opentripplanner/model/TimetableBuilder.java new file mode 100644 index 00000000000..e41507fdc1b --- /dev/null +++ b/src/main/java/org/opentripplanner/model/TimetableBuilder.java @@ -0,0 +1,131 @@ +package org.opentripplanner.model; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.UnaryOperator; +import org.opentripplanner.transit.model.network.TripPattern; +import org.opentripplanner.transit.model.timetable.Direction; +import org.opentripplanner.transit.model.timetable.FrequencyEntry; +import org.opentripplanner.transit.model.timetable.Trip; +import org.opentripplanner.transit.model.timetable.TripTimes; + +public class TimetableBuilder { + + private TripPattern pattern; + private LocalDate serviceDate; + private final List tripTimes = new ArrayList<>(); + private final List frequencies = new ArrayList<>(); + + TimetableBuilder() {} + + TimetableBuilder(Timetable tt) { + pattern = tt.getPattern(); + serviceDate = tt.getServiceDate(); + tripTimes.addAll(tt.getTripTimes()); + frequencies.addAll(tt.getFrequencyEntries()); + } + + public TimetableBuilder withTripPattern(TripPattern tripPattern) { + this.pattern = tripPattern; + return this; + } + + public TimetableBuilder withServiceDate(LocalDate serviceDate) { + this.serviceDate = serviceDate; + return this; + } + + public TimetableBuilder addTripTimes(TripTimes tripTimes) { + this.tripTimes.add(tripTimes); + return this; + } + + public TimetableBuilder addAllTripTimes(List tripTimes) { + this.tripTimes.addAll(tripTimes); + return this; + } + + public TimetableBuilder setTripTimes(int tripIndex, TripTimes tripTimes) { + this.tripTimes.set(tripIndex, tripTimes); + return this; + } + + public TimetableBuilder removeTripTimes(TripTimes tripTimesToRemove) { + tripTimes.remove(tripTimesToRemove); + return this; + } + + public TimetableBuilder removeAllTripTimes(Set tripTimesToBeRemoved) { + tripTimes.removeAll(tripTimesToBeRemoved); + return this; + } + + /** + * Apply the same update to all trip-times including scheduled and frequency based + * trip times. + *

+ */ + public TimetableBuilder updateAllTripTimes(UnaryOperator update) { + tripTimes.replaceAll(update); + frequencies.replaceAll(it -> + new FrequencyEntry( + it.startTime, + it.endTime, + it.headway, + it.exactTimes, + update.apply(it.tripTimes) + ) + ); + return this; + } + + public TimetableBuilder addFrequencyEntry(FrequencyEntry frequencyEntry) { + this.frequencies.add(frequencyEntry); + return this; + } + + public TripPattern getPattern() { + return pattern; + } + + public LocalDate getServiceDate() { + return serviceDate; + } + + public List getTripTimes() { + return tripTimes; + } + + public List getFrequencies() { + return frequencies; + } + + /** + * The direction for all the trips in this timetable. + */ + public Direction getDirection() { + return Optional + .ofNullable(getRepresentativeTripTimes()) + .map(TripTimes::getTrip) + .map(Trip::getDirection) + .orElse(Direction.UNKNOWN); + } + + private TripTimes getRepresentativeTripTimes() { + if (!tripTimes.isEmpty()) { + return tripTimes.getFirst(); + } else if (!frequencies.isEmpty()) { + return frequencies.getFirst().tripTimes; + } else { + // Pattern is created only for real-time updates + return null; + } + } + + public Timetable build() { + return new Timetable(this); + } +} diff --git a/src/main/java/org/opentripplanner/model/TimetableSnapshot.java b/src/main/java/org/opentripplanner/model/TimetableSnapshot.java index d076bf9f1f0..64f06362228 100644 --- a/src/main/java/org/opentripplanner/model/TimetableSnapshot.java +++ b/src/main/java/org/opentripplanner/model/TimetableSnapshot.java @@ -298,9 +298,7 @@ public Result update(RealTimeTripUpdate realTimeTrip } Timetable tt = resolve(pattern, serviceDate); - // we need to perform the copy of Timetable here rather than in Timetable.update() - // to avoid repeatedly copying in case several updates are applied to the same timetable - tt = copyTimetable(pattern, serviceDate, tt); + TimetableBuilder ttb = Timetable.of(tt).withServiceDate(serviceDate); // Assume all trips in a pattern are from the same feed, which should be the case. // Find trip index @@ -308,12 +306,15 @@ public Result update(RealTimeTripUpdate realTimeTrip int tripIndex = tt.getTripIndex(trip.getId()); if (tripIndex == -1) { // Trip not found, add it - tt.addTripTimes(updatedTripTimes); + ttb.addTripTimes(updatedTripTimes); } else { // Set updated trip times of trip - tt.setTripTimes(tripIndex, updatedTripTimes); + ttb.setTripTimes(tripIndex, updatedTripTimes); } + Timetable updated = ttb.build(); + swapTimetable(pattern, tt, updated); + if (pattern.isCreatedByRealtimeUpdater()) { // Remember this pattern for the added trip id and service date FeedScopedId tripId = trip.getId(); @@ -459,8 +460,11 @@ public boolean revertTripToScheduledTripPattern(FeedScopedId tripId, LocalDate s if (tripTimesToRemove != null) { for (Timetable originalTimetable : sortedTimetables) { if (originalTimetable.getTripTimes().contains(tripTimesToRemove)) { - Timetable updatedTimetable = copyTimetable(pattern, serviceDate, originalTimetable); - updatedTimetable.getTripTimes().remove(tripTimesToRemove); + Timetable updatedTimetable = Timetable + .of(originalTimetable) + .removeTripTimes(tripTimesToRemove) + .build(); + swapTimetable(pattern, originalTimetable, updatedTimetable); } } } @@ -579,36 +583,33 @@ private void addPatternToIndex(TripPattern tripPattern) { } /** - * Make a copy of the given timetable for a given pattern and service date. - * If the timetable was already copied-on write in this snapshot, the same instance will be - * returned. The SortedSet that holds the collection of Timetables for that pattern + * Replace the original Timetable by the updated one in the timetable index. + * The SortedSet that holds the collection of Timetables for that pattern * (sorted by service date) is shared between multiple snapshots and must be copied as well.
* Note on performance: if multiple Timetables are modified in a SortedSet, the SortedSet will be * copied multiple times. The impact on memory/garbage collection is assumed to be minimal * since the collection is small. * The SortedSet is made immutable to prevent change after snapshot publication. */ - private Timetable copyTimetable(TripPattern pattern, LocalDate serviceDate, Timetable tt) { - if (!dirtyTimetables.contains(tt)) { - Timetable old = tt; - tt = new Timetable(tt, serviceDate); - SortedSet sortedTimetables = timetables.get(pattern); - if (sortedTimetables == null) { - sortedTimetables = new TreeSet<>(new SortedTimetableComparator()); - } else { - SortedSet temp = new TreeSet<>(new SortedTimetableComparator()); - temp.addAll(sortedTimetables); - sortedTimetables = temp; - } - if (old.getServiceDate() != null) { - sortedTimetables.remove(old); - } - sortedTimetables.add(tt); - timetables.put(pattern, ImmutableSortedSet.copyOfSorted(sortedTimetables)); - dirtyTimetables.add(tt); - dirty = true; + private void swapTimetable(TripPattern pattern, Timetable original, Timetable updated) { + SortedSet sortedTimetables = timetables.get(pattern); + if (sortedTimetables == null) { + sortedTimetables = new TreeSet<>(new SortedTimetableComparator()); + } else { + SortedSet temp = new TreeSet<>(new SortedTimetableComparator()); + temp.addAll(sortedTimetables); + sortedTimetables = temp; + } + // This is a minor optimization: + // Since sortedTimetables contains only timetables created in real-time, no need to try to + // remove the original if it was not created by real-time. + if (original.isCreatedByRealTimeUpdater()) { + sortedTimetables.remove(original); } - return tt; + sortedTimetables.add(updated); + timetables.put(pattern, ImmutableSortedSet.copyOfSorted(sortedTimetables)); + dirtyTimetables.add(updated); + dirty = true; } protected static class SortedTimetableComparator implements Comparator { diff --git a/src/main/java/org/opentripplanner/model/impl/OtpTransitServiceBuilder.java b/src/main/java/org/opentripplanner/model/impl/OtpTransitServiceBuilder.java index 373b99f0bc6..aa82a66d0b9 100644 --- a/src/main/java/org/opentripplanner/model/impl/OtpTransitServiceBuilder.java +++ b/src/main/java/org/opentripplanner/model/impl/OtpTransitServiceBuilder.java @@ -8,6 +8,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import org.opentripplanner.ext.flex.trip.FlexTrip; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.gtfs.mapping.StaySeatedNotAllowed; @@ -15,6 +16,7 @@ import org.opentripplanner.model.Frequency; import org.opentripplanner.model.OtpTransitService; import org.opentripplanner.model.ShapePoint; +import org.opentripplanner.model.Timetable; import org.opentripplanner.model.TripStopTimes; import org.opentripplanner.model.calendar.CalendarServiceData; import org.opentripplanner.model.calendar.ServiceCalendar; @@ -51,6 +53,7 @@ import org.opentripplanner.transit.model.site.Station; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripOnServiceDate; +import org.opentripplanner.transit.model.timetable.TripTimes; import org.opentripplanner.transit.service.StopModel; import org.opentripplanner.transit.service.StopModelBuilder; import org.slf4j.Logger; @@ -375,21 +378,39 @@ private void removeStopTimesForNoneExistingTrips() { private void fixOrRemovePatternsWhichReferenceNoneExistingTrips() { int orgSize = tripPatterns.size(); List> removePatterns = new ArrayList<>(); + List updatedPatterns = new ArrayList<>(); for (Map.Entry e : tripPatterns.entries()) { TripPattern ptn = e.getValue(); - ptn.removeTrips(t -> !tripsById.containsKey(t.getId())); - if (ptn.scheduledTripsAsStream().findAny().isEmpty()) { + Set tripTimesToBeRemoved = ptn + .getScheduledTimetable() + .getTripTimes() + .stream() + .filter(tripTimes -> !tripsById.containsKey(tripTimes.getTrip().getId())) + .collect(Collectors.toUnmodifiableSet()); + if (!tripTimesToBeRemoved.isEmpty()) { removePatterns.add(e); + Timetable updatedTimetable = Timetable + .of(ptn.getScheduledTimetable()) + .removeAllTripTimes(tripTimesToBeRemoved) + .build(); + TripPattern updatedPattern = ptn.copy().withScheduledTimeTable(updatedTimetable).build(); + if (!updatedTimetable.getTripTimes().isEmpty()) { + updatedPatterns.add(updatedPattern); + } else { + issueStore.add( + "RemovedEmptyTripPattern", + "Removed trip pattern %s as it contains no trips", + updatedPattern.getId() + ); + } } } for (Map.Entry it : removePatterns) { tripPatterns.remove(it.getKey(), it.getValue()); - issueStore.add( - "RemovedEmptyTripPattern", - "Removed trip pattern %s as it contains no trips", - it.getValue().getId() - ); + } + for (TripPattern tripPattern : updatedPatterns) { + tripPatterns.put(tripPattern.getStopPattern(), tripPattern); } logRemove("TripPattern", orgSize, tripPatterns.size(), "No trips for pattern exist."); } diff --git a/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java b/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java index 740224b4489..c7b85e72af8 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java @@ -255,8 +255,10 @@ Optional mapTripPattern(JourneyPattern_VersionStructure .withHopGeometries( serviceLinkMapper.getGeometriesByJourneyPattern(journeyPattern, stopPattern) ) + .withScheduledTimeTableBuilder(builder -> + builder.addAllTripTimes(createTripTimes(trips, tripStopTimes)) + ) .build(); - createTripTimes(trips, tripStopTimes).forEach(tripPattern::add); return Optional.of( new TripPatternMapperResult( diff --git a/src/main/java/org/opentripplanner/transit/model/network/TripPattern.java b/src/main/java/org/opentripplanner/transit/model/network/TripPattern.java index 57c71d06113..8e39d353913 100644 --- a/src/main/java/org/opentripplanner/transit/model/network/TripPattern.java +++ b/src/main/java/org/opentripplanner/transit/model/network/TripPattern.java @@ -8,7 +8,6 @@ import java.util.Collection; import java.util.List; import java.util.Objects; -import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.annotation.Nonnull; @@ -28,7 +27,6 @@ import org.opentripplanner.transit.model.site.Station; import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.model.timetable.Direction; -import org.opentripplanner.transit.model.timetable.FrequencyEntry; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripTimes; import org.slf4j.Logger; @@ -122,20 +120,27 @@ public final class TripPattern private final RoutingTripPattern routingTripPattern; - public TripPattern(TripPatternBuilder builder) { + TripPattern(TripPatternBuilder builder) { super(builder.getId()); this.name = builder.getName(); this.route = builder.getRoute(); this.stopPattern = requireNonNull(builder.getStopPattern()); this.createdByRealtimeUpdater = builder.isCreatedByRealtimeUpdate(); - this.mode = requireNonNullElseGet(builder.getMode(), route::getMode); - this.netexSubMode = requireNonNullElseGet(builder.getNetexSubmode(), route::getNetexSubmode); + this.mode = requireNonNull(builder.getMode()); + this.netexSubMode = requireNonNull(builder.getNetexSubmode()); this.containsMultipleModes = builder.getContainsMultipleModes(); - this.scheduledTimetable = - builder.getScheduledTimetable() != null - ? builder.getScheduledTimetable() - : new Timetable(this); + if (builder.getScheduledTimetable() != null) { + if (builder.getScheduledTimetableBuilder() != null) { + throw new IllegalArgumentException( + "Cannot provide both scheduled timetable and scheduled timetable builder" + ); + } + this.scheduledTimetable = builder.getScheduledTimetable(); + } else { + this.scheduledTimetable = + builder.getScheduledTimetableBuilder().withTripPattern(this).build(); + } this.originalTripPattern = builder.getOriginalTripPattern(); @@ -331,56 +336,6 @@ public boolean isBoardAndAlightAt(int stopIndex, PickDrop value) { /* METHODS THAT DELEGATE TO THE SCHEDULED TIMETABLE */ - // TODO OTP2 this method modifies the state, it will be refactored in a subsequent step - /** - * Add the given tripTimes to this pattern's scheduled timetable, recording the corresponding trip - * as one of the scheduled trips on this pattern. - */ - public void add(TripTimes tt) { - // Only scheduled trips (added at graph build time, rather than directly to the timetable - // via updates) are in this list. - scheduledTimetable.addTripTimes(tt); - - // Check that all trips added to this pattern are on the initially declared route. - // Identity equality is valid on GTFS entity objects. - if (this.route != tt.getTrip().getRoute()) { - LOG.warn( - "The trip {} is on route {} but its stop pattern is on route {}.", - tt.getTrip(), - tt.getTrip().getRoute(), - route - ); - } - } - - // TODO OTP2 this method modifies the state, it will be refactored in a subsequent step - /** - * Add the given FrequencyEntry to this pattern's scheduled timetable, recording the corresponding - * trip as one of the scheduled trips on this pattern. - * TODO possible improvements: combine freq entries and TripTimes. Do not keep trips list in TripPattern - * since it is redundant. - */ - public void add(FrequencyEntry freq) { - scheduledTimetable.addFrequencyEntry(freq); - if (this.getRoute() != freq.tripTimes.getTrip().getRoute()) { - LOG.warn( - "The trip {} is on a different route than its stop pattern, which is on {}.", - freq.tripTimes.getTrip(), - route - ); - } - } - - // TODO OTP2 this method modifies the state, it will be refactored in a subsequent step - /** - * Remove all trips matching the given predicate. - * - * @param removeTrip it the predicate returns true - */ - public void removeTrips(Predicate removeTrip) { - scheduledTimetable.getTripTimes().removeIf(tt -> removeTrip.test(tt.getTrip())); - } - /** * Checks that this is TripPattern is based of the provided TripPattern and contains same stops * (but not necessarily with same pickup and dropoff values). diff --git a/src/main/java/org/opentripplanner/transit/model/network/TripPatternBuilder.java b/src/main/java/org/opentripplanner/transit/model/network/TripPatternBuilder.java index f34d206922b..91451d43cc2 100644 --- a/src/main/java/org/opentripplanner/transit/model/network/TripPatternBuilder.java +++ b/src/main/java/org/opentripplanner/transit/model/network/TripPatternBuilder.java @@ -1,13 +1,17 @@ package org.opentripplanner.transit.model.network; +import static java.util.Objects.requireNonNullElseGet; + import java.util.ArrayList; import java.util.List; +import java.util.function.UnaryOperator; import java.util.stream.IntStream; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.LineString; import org.opentripplanner.framework.geometry.CompactLineStringUtils; import org.opentripplanner.framework.geometry.GeometryUtils; import org.opentripplanner.model.Timetable; +import org.opentripplanner.model.TimetableBuilder; import org.opentripplanner.routing.algorithm.raptoradapter.api.SlackProvider; import org.opentripplanner.transit.model.basic.SubMode; import org.opentripplanner.transit.model.basic.TransitMode; @@ -24,6 +28,7 @@ public final class TripPatternBuilder private boolean containsMultipleModes; private StopPattern stopPattern; private Timetable scheduledTimetable; + private TimetableBuilder scheduledTimetableBuilder; private String name; private boolean createdByRealtimeUpdate; @@ -33,6 +38,7 @@ public final class TripPatternBuilder TripPatternBuilder(FeedScopedId id) { super(id); + this.scheduledTimetableBuilder = Timetable.of(); } TripPatternBuilder(TripPattern original) { @@ -86,10 +92,28 @@ public TripPatternBuilder withStopPattern(StopPattern stopPattern) { } public TripPatternBuilder withScheduledTimeTable(Timetable scheduledTimetable) { + if (scheduledTimetableBuilder != null) { + throw new IllegalStateException( + "Cannot set scheduled Timetable after scheduled Timetable builder is created" + ); + } this.scheduledTimetable = scheduledTimetable; return this; } + public TripPatternBuilder withScheduledTimeTableBuilder( + UnaryOperator producer + ) { + // create a builder for the scheduled timetable only if it needs to be modified. + // otherwise reuse the existing timetable + if (scheduledTimetableBuilder == null) { + scheduledTimetableBuilder = Timetable.of(scheduledTimetable); + scheduledTimetable = null; + } + producer.apply(scheduledTimetableBuilder); + return this; + } + public TripPatternBuilder withCreatedByRealtimeUpdater(boolean createdByRealtimeUpdate) { this.createdByRealtimeUpdate = createdByRealtimeUpdate; return this; @@ -115,6 +139,13 @@ public int transitReluctanceFactorIndex() { return route.getMode().ordinal(); } + public Object getDirection() { + if (scheduledTimetable != null) { + return scheduledTimetable.getDirection(); + } + return scheduledTimetableBuilder.getDirection(); + } + @Override protected TripPattern buildFromValues() { return new TripPattern(this); @@ -125,11 +156,11 @@ public Route getRoute() { } public TransitMode getMode() { - return mode; + return mode != null ? mode : route.getMode(); } public SubMode getNetexSubmode() { - return netexSubMode; + return netexSubMode != null ? netexSubMode : route.getNetexSubmode(); } public boolean getContainsMultipleModes() { @@ -144,6 +175,10 @@ public Timetable getScheduledTimetable() { return scheduledTimetable; } + public TimetableBuilder getScheduledTimetableBuilder() { + return scheduledTimetableBuilder; + } + public String getName() { return name; } diff --git a/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java b/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java index 79590ca2775..1f43b155a17 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java @@ -160,11 +160,13 @@ static void setup() { var model = stopModel.build(); var transitModel = new TransitModel(model, DEDUPLICATOR); - final TripPattern pattern = TEST_MODEL.pattern(BUS).build(); var trip = TransitModelForTest.trip("123").withHeadsign(I18NString.of("Trip Headsign")).build(); var stopTimes = TEST_MODEL.stopTimesEvery5Minutes(3, trip, T11_00); var tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, DEDUPLICATOR); - pattern.add(tripTimes); + final TripPattern pattern = TEST_MODEL + .pattern(BUS) + .withScheduledTimeTableBuilder(builder -> builder.addTripTimes(tripTimes)) + .build(); transitModel.addTripPattern(id("pattern-1"), pattern); diff --git a/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java b/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java index f01bac12006..ecea70e08d4 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java @@ -44,19 +44,18 @@ enum FilterExpectation { } private static TripPattern pattern() { - var pattern = TransitModelForTest - .tripPattern("1", ROUTE_1) - .withStopPattern(STOP_PATTERN) - .build(); - var tt = ScheduledTripTimes .of() .withTrip(TRIP) .withArrivalTimes("10:00 10:05") .withDepartureTimes("10:00 10:05") .build(); - pattern.add(tt); - return pattern; + + return TransitModelForTest + .tripPattern("1", ROUTE_1) + .withStopPattern(STOP_PATTERN) + .withScheduledTimeTableBuilder(builder -> builder.addTripTimes(tt)) + .build(); } static List invalidRangeCases() { diff --git a/src/test/java/org/opentripplanner/gtfs/GenerateTripPatternsOperationTest.java b/src/test/java/org/opentripplanner/gtfs/GenerateTripPatternsOperationTest.java new file mode 100644 index 00000000000..0451edbb40d --- /dev/null +++ b/src/test/java/org/opentripplanner/gtfs/GenerateTripPatternsOperationTest.java @@ -0,0 +1,302 @@ +package org.opentripplanner.gtfs; + +import static org.opentripplanner.transit.model._data.TransitModelForTest.trip; + +import java.util.Collection; +import java.util.List; +import java.util.Set; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +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.graph_builder.issue.api.DataImportIssueStore; +import org.opentripplanner.graph_builder.issue.service.DefaultDataImportIssueStore; +import org.opentripplanner.graph_builder.issues.TripDegenerate; +import org.opentripplanner.graph_builder.issues.TripUndefinedService; +import org.opentripplanner.graph_builder.module.geometry.GeometryProcessor; +import org.opentripplanner.model.StopTime; +import org.opentripplanner.model.impl.OtpTransitServiceBuilder; +import org.opentripplanner.transit.model._data.TransitModelForTest; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.framework.Deduplicator; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.model.site.RegularStop; +import org.opentripplanner.transit.model.timetable.Direction; +import org.opentripplanner.transit.model.timetable.Trip; +import org.opentripplanner.transit.service.StopModel; + +class GenerateTripPatternsOperationTest { + + private static StopModel stopModel; + private static RegularStop stopA; + private static RegularStop stopB; + private static RegularStop stopC; + private static Trip trip1; + private static Trip trip2; + private static Trip trip3; + private static Trip trip4; + private static Trip trip5; + private static StopTime stopTimeA; + private static StopTime stopTimeB; + private static StopTime stopTimeC; + + private Deduplicator deduplicator; + private DataImportIssueStore issueStore; + private OtpTransitServiceBuilder transitServiceBuilder; + private GeometryProcessor geometryProcessor; + + @BeforeAll + static void setupClass() { + TransitModelForTest transitModelForTest = TransitModelForTest.of(); + stopA = transitModelForTest.stop("stopA").build(); + stopB = transitModelForTest.stop("stopB").build(); + stopC = transitModelForTest.stop("stopC").build(); + stopModel = + transitModelForTest + .stopModelBuilder() + .withRegularStop(stopA) + .withRegularStop(stopB) + .withRegularStop(stopC) + .build(); + + stopTimeA = new StopTime(); + stopTimeA.setStop(stopA); + stopTimeB = new StopTime(); + stopTimeB.setStop(stopB); + stopTimeC = new StopTime(); + stopTimeC.setStop(stopC); + + FeedScopedId serviceId1 = TransitModelForTest.id("SERVICE_ID_1"); + trip1 = + trip("TRIP_ID_1") + .withServiceId(serviceId1) + .withMode(TransitMode.RAIL) + .withNetexSubmode("SUBMODE_1") + .withDirection(Direction.INBOUND) + .build(); + + // same route, mode, submode and direction as trip1 + FeedScopedId serviceId2 = TransitModelForTest.id("SERVICE_ID_2"); + trip2 = + trip("TRIP_ID_2") + .withServiceId(serviceId2) + .withRoute(trip1.getRoute()) + .withMode(trip1.getMode()) + .withNetexSubmode(trip1.getNetexSubMode().name()) + .withDirection(trip1.getDirection()) + .build(); + + // same route, direction as trip1, different mode + FeedScopedId serviceId3 = TransitModelForTest.id("SERVICE_ID_3"); + trip3 = + trip("TRIP_ID_3") + .withServiceId(serviceId3) + .withRoute(trip1.getRoute()) + .withMode(TransitMode.BUS) + .withDirection(trip1.getDirection()) + .build(); + + // same route, mode, direction as trip1, different submode + FeedScopedId serviceId4 = TransitModelForTest.id("SERVICE_ID_4"); + trip4 = + trip("TRIP_ID_4") + .withServiceId(serviceId4) + .withRoute(trip1.getRoute()) + .withMode(trip1.getMode()) + .withNetexSubmode("SUMODE_2") + .withDirection(trip1.getDirection()) + .build(); + + // same route, mode as trip1, different direction + FeedScopedId serviceId5 = TransitModelForTest.id("SERVICE_ID_5"); + trip5 = + trip("TRIP_ID_5") + .withServiceId(serviceId5) + .withRoute(trip1.getRoute()) + .withMode(trip1.getMode()) + .withNetexSubmode(trip1.getNetexSubMode().name()) + .withDirection(Direction.OUTBOUND) + .build(); + } + + @BeforeEach + void setup() { + deduplicator = new Deduplicator(); + issueStore = new DefaultDataImportIssueStore(); + transitServiceBuilder = new OtpTransitServiceBuilder(stopModel, issueStore); + double maxStopToShapeSnapDistance = 100; + geometryProcessor = + new GeometryProcessor(transitServiceBuilder, maxStopToShapeSnapDistance, issueStore); + } + + @Test + void testGenerateTripPatternsNoTrip() { + Set calendarServiceIds = Set.of(); + GenerateTripPatternsOperation generateTripPatternsOperation = new GenerateTripPatternsOperation( + transitServiceBuilder, + issueStore, + deduplicator, + calendarServiceIds, + geometryProcessor + ); + generateTripPatternsOperation.run(); + + Assertions.assertTrue(transitServiceBuilder.getTripPatterns().isEmpty()); + Assertions.assertTrue(issueStore.listIssues().isEmpty()); + } + + @Test + void testGenerateTripPatternsTripWithUndefinedService() { + transitServiceBuilder.getTripsById().computeIfAbsent(trip1.getId(), feedScopedId -> trip1); + Set calendarServiceIds = Set.of(); + + GenerateTripPatternsOperation generateTripPatternsOperation = new GenerateTripPatternsOperation( + transitServiceBuilder, + issueStore, + deduplicator, + calendarServiceIds, + geometryProcessor + ); + generateTripPatternsOperation.run(); + + Assertions.assertTrue(transitServiceBuilder.getTripPatterns().isEmpty()); + Assertions.assertFalse(issueStore.listIssues().isEmpty()); + Assertions.assertInstanceOf(TripUndefinedService.class, issueStore.listIssues().getFirst()); + } + + @Test + void testGenerateTripPatternsDegeneratedTrip() { + transitServiceBuilder.getTripsById().computeIfAbsent(trip1.getId(), feedScopedId -> trip1); + Set calendarServiceIds = Set.of(trip1.getServiceId()); + + GenerateTripPatternsOperation generateTripPatternsOperation = new GenerateTripPatternsOperation( + transitServiceBuilder, + issueStore, + deduplicator, + calendarServiceIds, + geometryProcessor + ); + generateTripPatternsOperation.run(); + + Assertions.assertTrue(transitServiceBuilder.getTripPatterns().isEmpty()); + Assertions.assertFalse(issueStore.listIssues().isEmpty()); + Assertions.assertInstanceOf(TripDegenerate.class, issueStore.listIssues().getFirst()); + } + + @Test + void testGenerateTripPatterns() { + transitServiceBuilder.getTripsById().computeIfAbsent(trip1.getId(), feedScopedId -> trip1); + Collection stopTimes = List.of(stopTimeA, stopTimeB); + transitServiceBuilder.getStopTimesSortedByTrip().put(trip1, stopTimes); + Set calendarServiceIds = Set.of(trip1.getServiceId()); + + GenerateTripPatternsOperation generateTripPatternsOperation = new GenerateTripPatternsOperation( + transitServiceBuilder, + issueStore, + deduplicator, + calendarServiceIds, + geometryProcessor + ); + generateTripPatternsOperation.run(); + + Assertions.assertEquals(1, transitServiceBuilder.getTripPatterns().size()); + Assertions.assertTrue(issueStore.listIssues().isEmpty()); + } + + @Test + void testGenerateTripPatterns2TripsSameStops() { + transitServiceBuilder.getTripsById().computeIfAbsent(trip1.getId(), feedScopedId -> trip1); + transitServiceBuilder.getTripsById().computeIfAbsent(trip2.getId(), feedScopedId -> trip2); + Collection stopTimes = List.of(stopTimeA, stopTimeB); + + transitServiceBuilder.getStopTimesSortedByTrip().put(trip1, stopTimes); + transitServiceBuilder.getStopTimesSortedByTrip().put(trip2, stopTimes); + + Set calendarServiceIds = Set.of(trip1.getServiceId(), trip2.getServiceId()); + + GenerateTripPatternsOperation generateTripPatternsOperation = new GenerateTripPatternsOperation( + transitServiceBuilder, + issueStore, + deduplicator, + calendarServiceIds, + geometryProcessor + ); + generateTripPatternsOperation.run(); + + Assertions.assertEquals(1, transitServiceBuilder.getTripPatterns().size()); + Assertions.assertEquals( + 2, + transitServiceBuilder + .getTripPatterns() + .values() + .stream() + .findFirst() + .orElseThrow() + .getScheduledTimetable() + .getTripTimes() + .size() + ); + Assertions.assertTrue(issueStore.listIssues().isEmpty()); + } + + @Test + void testGenerateTripPatterns2TripsDifferentStops() { + transitServiceBuilder.getTripsById().computeIfAbsent(trip1.getId(), feedScopedId -> trip1); + transitServiceBuilder.getTripsById().computeIfAbsent(trip2.getId(), feedScopedId -> trip2); + Collection stopTimesTrip1 = List.of(stopTimeA, stopTimeB); + Collection stopTimesTrip2 = List.of(stopTimeA, stopTimeC); + + transitServiceBuilder.getStopTimesSortedByTrip().put(trip1, stopTimesTrip1); + transitServiceBuilder.getStopTimesSortedByTrip().put(trip2, stopTimesTrip2); + + Set calendarServiceIds = Set.of(trip1.getServiceId(), trip2.getServiceId()); + + GenerateTripPatternsOperation generateTripPatternsOperation = new GenerateTripPatternsOperation( + transitServiceBuilder, + issueStore, + deduplicator, + calendarServiceIds, + geometryProcessor + ); + generateTripPatternsOperation.run(); + + Assertions.assertEquals(2, transitServiceBuilder.getTripPatterns().size()); + Assertions.assertTrue(issueStore.listIssues().isEmpty()); + } + + static List testCases() { + return List.of( + Arguments.of(trip1, trip3), + Arguments.of(trip1, trip4), + Arguments.of(trip1, trip5) + ); + } + + @ParameterizedTest + @MethodSource("testCases") + void testGenerateDifferentTripPatterns(Trip t1, Trip t2) { + transitServiceBuilder.getTripsById().computeIfAbsent(t1.getId(), feedScopedId -> t1); + transitServiceBuilder.getTripsById().computeIfAbsent(t2.getId(), feedScopedId -> t2); + Collection stopTimes = List.of(stopTimeA, stopTimeB); + + transitServiceBuilder.getStopTimesSortedByTrip().put(t1, stopTimes); + transitServiceBuilder.getStopTimesSortedByTrip().put(t2, stopTimes); + + Set calendarServiceIds = Set.of(t1.getServiceId(), t2.getServiceId()); + + GenerateTripPatternsOperation generateTripPatternsOperation = new GenerateTripPatternsOperation( + transitServiceBuilder, + issueStore, + deduplicator, + calendarServiceIds, + geometryProcessor + ); + generateTripPatternsOperation.run(); + + Assertions.assertEquals(2, transitServiceBuilder.getTripPatterns().size()); + Assertions.assertTrue(issueStore.listIssues().isEmpty()); + } +} diff --git a/src/test/java/org/opentripplanner/gtfs/interlining/InterlineProcessorTest.java b/src/test/java/org/opentripplanner/gtfs/interlining/InterlineProcessorTest.java index a1aa4f9d753..e14a33cd276 100644 --- a/src/test/java/org/opentripplanner/gtfs/interlining/InterlineProcessorTest.java +++ b/src/test/java/org/opentripplanner/gtfs/interlining/InterlineProcessorTest.java @@ -163,13 +163,12 @@ private static TripPattern tripPattern(String tripId, String blockId, String ser ); var stopPattern = new StopPattern(stopTimes); - var tp = TripPattern + var tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); + return TripPattern .of(TransitModelForTest.id(tripId)) .withRoute(trip.getRoute()) .withStopPattern(stopPattern) + .withScheduledTimeTableBuilder(builder -> builder.addTripTimes(tripTimes)) .build(); - var tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); - tp.add(tripTimes); - return tp; } } diff --git a/src/test/java/org/opentripplanner/model/TimetableSnapshotTest.java b/src/test/java/org/opentripplanner/model/TimetableSnapshotTest.java index 0a737630a5b..57bf11dd8f6 100644 --- a/src/test/java/org/opentripplanner/model/TimetableSnapshotTest.java +++ b/src/test/java/org/opentripplanner/model/TimetableSnapshotTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertNull; @@ -52,9 +53,9 @@ public static void setUp() throws Exception { @Test public void testCompare() { - Timetable orig = new Timetable(null); - Timetable a = new Timetable(orig, LocalDate.now(timeZone).minusDays(1)); - Timetable b = new Timetable(orig, LocalDate.now(timeZone)); + Timetable orig = Timetable.of().build(); + Timetable a = Timetable.of(orig).withServiceDate(LocalDate.now(timeZone).minusDays(1)).build(); + Timetable b = Timetable.of(orig).withServiceDate(LocalDate.now(timeZone)).build(); assertTrue(new TimetableSnapshot.SortedTimetableComparator().compare(a, b) < 0); } @@ -106,52 +107,50 @@ public void testResolve() { @Test public void testUpdate() { - assertThrows( - ConcurrentModificationException.class, - () -> { - LocalDate today = LocalDate.now(timeZone); - LocalDate yesterday = today.minusDays(1); - TripPattern pattern = patternIndex.get(new FeedScopedId(feedId, "1.1")); + LocalDate today = LocalDate.now(timeZone); + LocalDate yesterday = today.minusDays(1); + TripPattern pattern = patternIndex.get(new FeedScopedId(feedId, "1.1")); - TimetableSnapshot resolver = new TimetableSnapshot(); - Timetable origNow = resolver.resolve(pattern, today); + TimetableSnapshot resolver = new TimetableSnapshot(); + Timetable origNow = resolver.resolve(pattern, today); - TripDescriptor.Builder tripDescriptorBuilder = TripDescriptor.newBuilder(); + TripDescriptor.Builder tripDescriptorBuilder = TripDescriptor.newBuilder(); - tripDescriptorBuilder.setTripId("1.1"); - tripDescriptorBuilder.setScheduleRelationship(ScheduleRelationship.SCHEDULED); + tripDescriptorBuilder.setTripId("1.1"); + tripDescriptorBuilder.setScheduleRelationship(ScheduleRelationship.SCHEDULED); - TripUpdate.Builder tripUpdateBuilder = TripUpdate.newBuilder(); + TripUpdate.Builder tripUpdateBuilder = TripUpdate.newBuilder(); - tripUpdateBuilder.setTrip(tripDescriptorBuilder); + tripUpdateBuilder.setTrip(tripDescriptorBuilder); - var stopTimeUpdateBuilder = tripUpdateBuilder.addStopTimeUpdateBuilder(0); - stopTimeUpdateBuilder.setStopSequence(2); - stopTimeUpdateBuilder.setScheduleRelationship( - TripUpdate.StopTimeUpdate.ScheduleRelationship.SCHEDULED - ); - stopTimeUpdateBuilder.setDeparture( - TripUpdate.StopTimeEvent.newBuilder().setDelay(5).build() - ); + var stopTimeUpdateBuilder = tripUpdateBuilder.addStopTimeUpdateBuilder(0); + stopTimeUpdateBuilder.setStopSequence(2); + stopTimeUpdateBuilder.setScheduleRelationship( + TripUpdate.StopTimeUpdate.ScheduleRelationship.SCHEDULED + ); + stopTimeUpdateBuilder.setDeparture(TripUpdate.StopTimeEvent.newBuilder().setDelay(5).build()); - TripUpdate tripUpdate = tripUpdateBuilder.build(); + TripUpdate tripUpdate = tripUpdateBuilder.build(); - // new timetable for today - updateResolver(resolver, pattern, tripUpdate, today); - Timetable updatedNow = resolver.resolve(pattern, today); - assertNotSame(origNow, updatedNow); + // new timetable for today + updateResolver(resolver, pattern, tripUpdate, today); + Timetable updatedNow = resolver.resolve(pattern, today); + assertNotSame(origNow, updatedNow); - // reuse timetable for today - updateResolver(resolver, pattern, tripUpdate, today); - assertEquals(updatedNow, resolver.resolve(pattern, today)); + // a new timetable instance is created for today + updateResolver(resolver, pattern, tripUpdate, today); + assertNotEquals(updatedNow, resolver.resolve(pattern, today)); - // create new timetable for tomorrow - updateResolver(resolver, pattern, tripUpdate, yesterday); - assertNotSame(origNow, resolver.resolve(pattern, yesterday)); - assertNotSame(updatedNow, resolver.resolve(pattern, yesterday)); + // create new timetable for tomorrow + updateResolver(resolver, pattern, tripUpdate, yesterday); + assertNotSame(origNow, resolver.resolve(pattern, yesterday)); + assertNotSame(updatedNow, resolver.resolve(pattern, yesterday)); - // exception if we try to modify a snapshot - TimetableSnapshot snapshot = resolver.commit(); + // exception if we try to modify a snapshot + TimetableSnapshot snapshot = resolver.commit(); + assertThrows( + ConcurrentModificationException.class, + () -> { updateResolver(snapshot, pattern, tripUpdate, yesterday); } ); diff --git a/src/test/java/org/opentripplanner/model/TimetableTest.java b/src/test/java/org/opentripplanner/model/TimetableTest.java index 9e6a7467dc5..75a3af3f3ca 100644 --- a/src/test/java/org/opentripplanner/model/TimetableTest.java +++ b/src/test/java/org/opentripplanner/model/TimetableTest.java @@ -190,7 +190,7 @@ public void update() { result.ifSuccess(p -> { var updatedTripTimes = p.getTripTimes(); assertNotNull(updatedTripTimes); - timetable.setTripTimes(trip_1_1_index, updatedTripTimes); + timetable = Timetable.of(timetable).setTripTimes(trip_1_1_index, updatedTripTimes).build(); assertEquals(20 * 60 + 120, timetable.getTripTimes(trip_1_1_index).getArrivalTime(2)); }); @@ -217,7 +217,7 @@ public void update() { result.ifSuccess(p -> { var updatedTripTimes = p.getTripTimes(); assertNotNull(updatedTripTimes); - timetable.setTripTimes(trip_1_1_index, updatedTripTimes); + timetable = timetable.of(timetable).setTripTimes(trip_1_1_index, updatedTripTimes).build(); }); // update trip arrival time only @@ -246,7 +246,7 @@ public void update() { result.ifSuccess(p -> { var updatedTripTimes = p.getTripTimes(); assertNotNull(updatedTripTimes); - timetable.setTripTimes(trip_1_1_index, updatedTripTimes); + timetable = Timetable.of(timetable).setTripTimes(trip_1_1_index, updatedTripTimes).build(); }); // update trip departure time only @@ -273,7 +273,7 @@ public void update() { result.ifSuccess(p -> { var updatedTripTimes = p.getTripTimes(); assertNotNull(updatedTripTimes); - timetable.setTripTimes(trip_1_1_index, updatedTripTimes); + timetable = Timetable.of(timetable).setTripTimes(trip_1_1_index, updatedTripTimes).build(); }); // update trip using stop id @@ -299,7 +299,7 @@ public void update() { result.ifSuccess(p -> { var updatedTripTimes = p.getTripTimes(); assertNotNull(updatedTripTimes); - timetable.setTripTimes(trip_1_1_index, updatedTripTimes); + timetable = Timetable.of(timetable).setTripTimes(trip_1_1_index, updatedTripTimes).build(); }); } diff --git a/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceBuilderLimitPeriodTest.java b/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceBuilderLimitPeriodTest.java index 5d1fef21271..27e6e56c645 100644 --- a/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceBuilderLimitPeriodTest.java +++ b/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceBuilderLimitPeriodTest.java @@ -1,6 +1,7 @@ package org.opentripplanner.model.impl; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertTrue; import java.time.LocalDate; @@ -22,6 +23,7 @@ import org.opentripplanner.transit.model.network.Route; import org.opentripplanner.transit.model.network.StopPattern; import org.opentripplanner.transit.model.network.TripPattern; +import org.opentripplanner.transit.model.network.TripPatternBuilder; import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.timetable.Direction; import org.opentripplanner.transit.model.timetable.Trip; @@ -141,16 +143,27 @@ public void testLimitPeriod() { assertTrue(patterns.contains(patternInT1), patterns.toString()); assertTrue(patterns.contains(patternInT2), patterns.toString()); - // Verify trips in pattern (one trip is removed from patternInT1) - assertEquals(1, patternInT1.scheduledTripsAsStream().count()); - assertEquals(tripCSIn, patternInT1.scheduledTripsAsStream().findFirst().orElseThrow()); - - // Verify trips in pattern is unchanged (one trip) + // Verify patternInT1 is replaced by a copy that contains one less trip + TripPattern copyOfTripPattern1 = subject + .getTripPatterns() + .values() + .stream() + .filter(p -> p.getId().equals(patternInT1.getId())) + .findFirst() + .orElseThrow(); + assertNotSame(patternInT1, copyOfTripPattern1); + assertEquals(1, copyOfTripPattern1.scheduledTripsAsStream().count()); + assertEquals(tripCSIn, copyOfTripPattern1.scheduledTripsAsStream().findFirst().orElseThrow()); + + // Verify trips in patternInT2 is unchanged (one trip) assertEquals(1, patternInT2.scheduledTripsAsStream().count()); - // Verify scheduledTimetable trips (one trip is removed from patternInT1) - assertEquals(1, patternInT1.getScheduledTimetable().getTripTimes().size()); - assertEquals(tripCSIn, patternInT1.getScheduledTimetable().getTripTimes().get(0).getTrip()); + // Verify scheduledTimetable trips (one trip is removed from the copy of patternInT1) + assertEquals(1, copyOfTripPattern1.getScheduledTimetable().getTripTimes().size()); + assertEquals( + tripCSIn, + copyOfTripPattern1.getScheduledTimetable().getTripTimes().get(0).getTrip() + ); // Verify scheduledTimetable trips in pattern is unchanged (one trip) assertEquals(1, patternInT2.getScheduledTimetable().getTripTimes().size()); @@ -186,16 +199,17 @@ private TripPattern createTripPattern(Collection trips) { FeedScopedId patternId = TransitModelForTest.id( trips.stream().map(t -> t.getId().getId()).collect(Collectors.joining(":")) ); - TripPattern p = TripPattern + TripPatternBuilder tpb = TripPattern .of(patternId) .withRoute(route) - .withStopPattern(STOP_PATTERN) - .build(); + .withStopPattern(STOP_PATTERN); for (Trip trip : trips) { - p.add(TripTimesFactory.tripTimes(trip, STOP_TIMES, DEDUPLICATOR)); + tpb.withScheduledTimeTableBuilder(builder -> + builder.addTripTimes(TripTimesFactory.tripTimes(trip, STOP_TIMES, DEDUPLICATOR)) + ); } - return p; + return tpb.build(); } private Trip createTrip(String id, FeedScopedId serviceId) { diff --git a/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java b/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java index edaafabd753..ec04d9f6d96 100644 --- a/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java +++ b/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java @@ -482,13 +482,13 @@ public TestItineraryBuilder transit( stopTimes.add(toStopTime); StopPattern stopPattern = new StopPattern(stopTimes); + final TripTimes tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); TripPattern tripPattern = TripPattern .of(route.getId()) .withRoute(route) .withStopPattern(stopPattern) + .withScheduledTimeTableBuilder(builder -> builder.addTripTimes(tripTimes)) .build(); - final TripTimes tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); - tripPattern.add(tripTimes); ScheduledTransitLeg leg; diff --git a/src/test/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReferenceTest.java b/src/test/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReferenceTest.java index 480639e5bda..315b12dfefc 100644 --- a/src/test/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReferenceTest.java +++ b/src/test/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReferenceTest.java @@ -11,7 +11,6 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; -import org.opentripplanner.model.Timetable; import org.opentripplanner.model.calendar.CalendarServiceData; import org.opentripplanner.model.plan.PlanTestConstants; import org.opentripplanner.model.plan.ScheduledTransitLeg; @@ -54,23 +53,23 @@ static void buildTransitService() { stop4 = TEST_MODEL.stop("STOP4", 0, 0).withParentStation(parentStation).build(); // build transit data + Trip trip = TransitModelForTest.trip("1").build(); + var tripTimes = TripTimesFactory.tripTimes( + trip, + TEST_MODEL.stopTimesEvery5Minutes(5, trip, PlanTestConstants.T11_00), + new Deduplicator() + ); + tripTimes.setServiceCode(SERVICE_CODE); TripPattern tripPattern = TransitModelForTest .tripPattern("1", TransitModelForTest.route(id("1")).build()) .withStopPattern(TransitModelForTest.stopPattern(stop1, stop2, stop3)) + .withScheduledTimeTableBuilder(builder -> builder.addTripTimes(tripTimes)) .build(); - Timetable timetable = tripPattern.getScheduledTimetable(); - Trip trip = TransitModelForTest.trip("1").build(); + tripId = trip.getId(); stopIdAtPosition0 = tripPattern.getStop(0).getId(); stopIdAtPosition1 = tripPattern.getStop(1).getId(); stopIdAtPosition2 = tripPattern.getStop(2).getId(); - var tripTimes = TripTimesFactory.tripTimes( - trip, - TEST_MODEL.stopTimesEvery5Minutes(5, trip, PlanTestConstants.T11_00), - new Deduplicator() - ); - tripTimes.setServiceCode(SERVICE_CODE); - timetable.addTripTimes(tripTimes); // build transit model StopModel stopModel = TEST_MODEL diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TripPatternForDateMapperTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TripPatternForDateMapperTest.java index 020dc6305a4..27f80062555 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TripPatternForDateMapperTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TripPatternForDateMapperTest.java @@ -36,7 +36,6 @@ public class TripPatternForDateMapperTest { @BeforeAll public static void setUp() throws Exception { var pattern = TEST_MODEL.pattern(BUS).build(); - timetable = new Timetable(pattern); var trip = TransitModelForTest.trip("1").build(); var tripTimes = TripTimesFactory.tripTimes( trip, @@ -44,7 +43,7 @@ public static void setUp() throws Exception { new Deduplicator() ); tripTimes.setServiceCode(SERVICE_CODE); - timetable.addTripTimes(tripTimes); + timetable = Timetable.of().withTripPattern(pattern).addTripTimes(tripTimes).build(); } /** diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TestRouteData.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TestRouteData.java index f57f03705fd..8648ea20324 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TestRouteData.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TestRouteData.java @@ -62,8 +62,8 @@ public TestRouteData(Route route, List stops, List times) { .of(TransitModelForTest.id("TP:" + route)) .withRoute(this.route) .withStopPattern(new StopPattern(stopTimesFistTrip)) + .withScheduledTimeTableBuilder(builder -> builder.addAllTripTimes(tripTimes)) .build(); - tripTimes.forEach(tripPattern::add); RoutingTripPattern routingTripPattern = tripPattern.getRoutingTripPattern(); diff --git a/src/test/java/org/opentripplanner/routing/stoptimes/StopTimesHelperTest.java b/src/test/java/org/opentripplanner/routing/stoptimes/StopTimesHelperTest.java index 1432c68fd49..d9377ba2c4f 100644 --- a/src/test/java/org/opentripplanner/routing/stoptimes/StopTimesHelperTest.java +++ b/src/test/java/org/opentripplanner/routing/stoptimes/StopTimesHelperTest.java @@ -33,14 +33,21 @@ public static void setUp() throws Exception { transitService = new DefaultTransitService(transitModel); feedId = transitModel.getFeedIds().iterator().next(); stopId = new FeedScopedId(feedId, "J"); - pattern = - transitService.getPatternForTrip( - transitService.getTripForId(new FeedScopedId(feedId, "5.1")) - ); - var tt = transitService.getTimetableForTripPattern(pattern, LocalDate.now()); + var originalPattern = transitService.getPatternForTrip( + transitService.getTripForId(new FeedScopedId(feedId, "5.1")) + ); + var tt = originalPattern.getScheduledTimetable(); var newTripTimes = tt.getTripTimes(0).copyScheduledTimes(); newTripTimes.cancelTrip(); - tt.setTripTimes(0, newTripTimes); + pattern = + originalPattern + .copy() + .withScheduledTimeTableBuilder(builder -> builder.setTripTimes(0, newTripTimes)) + .build(); + // replace the original pattern by the updated pattern in the transit model + transitModel.addTripPattern(pattern.getId(), pattern); + transitModel.index(); + transitService = new DefaultTransitService(transitModel); } /** diff --git a/src/test/java/org/opentripplanner/updater/trip/RealtimeTestEnvironment.java b/src/test/java/org/opentripplanner/updater/trip/RealtimeTestEnvironment.java index a40bd8bd797..fa89ab3e26e 100644 --- a/src/test/java/org/opentripplanner/updater/trip/RealtimeTestEnvironment.java +++ b/src/test/java/org/opentripplanner/updater/trip/RealtimeTestEnvironment.java @@ -355,8 +355,8 @@ private Trip createTrip(String id, Route route, List stops) { final TripPattern pattern = TransitModelForTest .tripPattern(id + "Pattern", route) .withStopPattern(TransitModelForTest.stopPattern(stops.stream().map(StopCall::stop).toList())) + .withScheduledTimeTableBuilder(builder -> builder.addTripTimes(tripTimes)) .build(); - pattern.add(tripTimes); transitModel.addTripPattern(pattern.getId(), pattern); diff --git a/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java b/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java index ea5d86cd0e1..4f10fb2d64b 100644 --- a/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java +++ b/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java @@ -383,10 +383,10 @@ private static TripPattern tripPattern(Trip trip, List stopTimes) { .of(trip.getId()) .withStopPattern(stopPattern) .withRoute(ROUTE) + .withScheduledTimeTableBuilder(builder -> + builder.addTripTimes(TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator())) + ) .build(); - pattern - .getScheduledTimetable() - .addTripTimes(TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator())); return pattern; } From 307a745692e20311758758b227e268d89354f837 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 22 Aug 2024 19:43:18 +0200 Subject: [PATCH 17/80] refactor: Encapsulate ordering trip-times in Timetable All trip-times in a Timetable should be ordered by the first departure-time for the first stop in the trip pattern. This was not enforced in the Timetable <-> TimetableBuilder, but relied on the "outside" to do it. This PR enforces this and provides two methods for adding trip-times to a Timetable (add & addOrUpdate). --- .../org/opentripplanner/model/Timetable.java | 4 +- .../model/TimetableBuilder.java | 54 +++++++++++++------ .../model/TimetableSnapshot.java | 12 +---- .../opentripplanner/model/TimetableTest.java | 10 ++-- .../stoptimes/StopTimesHelperTest.java | 2 +- .../speed_test/SpeedIntegrationTest.java | 6 +-- 6 files changed, 51 insertions(+), 37 deletions(-) diff --git a/src/main/java/org/opentripplanner/model/Timetable.java b/src/main/java/org/opentripplanner/model/Timetable.java index 5e2e02c3ffd..7cb747b2c90 100644 --- a/src/main/java/org/opentripplanner/model/Timetable.java +++ b/src/main/java/org/opentripplanner/model/Timetable.java @@ -64,8 +64,8 @@ public class Timetable implements Serializable { Timetable(TimetableBuilder timetableBuilder) { this.pattern = timetableBuilder.getPattern(); this.serviceDate = timetableBuilder.getServiceDate(); - tripTimes = List.copyOf(timetableBuilder.getTripTimes()); - frequencyEntries = List.copyOf(timetableBuilder.getFrequencies()); + this.tripTimes = timetableBuilder.createImmutableOrderedListOfTripTimes(); + this.frequencyEntries = List.copyOf(timetableBuilder.getFrequencies()); } /** diff --git a/src/main/java/org/opentripplanner/model/TimetableBuilder.java b/src/main/java/org/opentripplanner/model/TimetableBuilder.java index e41507fdc1b..bd21856eecb 100644 --- a/src/main/java/org/opentripplanner/model/TimetableBuilder.java +++ b/src/main/java/org/opentripplanner/model/TimetableBuilder.java @@ -2,10 +2,13 @@ import java.time.LocalDate; import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; -import java.util.Set; import java.util.function.UnaryOperator; +import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.timetable.Direction; import org.opentripplanner.transit.model.timetable.FrequencyEntry; @@ -16,7 +19,7 @@ public class TimetableBuilder { private TripPattern pattern; private LocalDate serviceDate; - private final List tripTimes = new ArrayList<>(); + private final Map tripTimes = new HashMap<>(); private final List frequencies = new ArrayList<>(); TimetableBuilder() {} @@ -24,8 +27,8 @@ public class TimetableBuilder { TimetableBuilder(Timetable tt) { pattern = tt.getPattern(); serviceDate = tt.getServiceDate(); - tripTimes.addAll(tt.getTripTimes()); frequencies.addAll(tt.getFrequencyEntries()); + addAllTripTimes(tt.getTripTimes()); } public TimetableBuilder withTripPattern(TripPattern tripPattern) { @@ -38,28 +41,47 @@ public TimetableBuilder withServiceDate(LocalDate serviceDate) { return this; } + /** + * Add a new trip-times to the timetable. If the associated trip already exists, an exception is + * thrown. This is considered a programming error. Use {@link #addOrUpdateTripTimes(TripTimes)} + * if you want to replace an existing trip. + */ public TimetableBuilder addTripTimes(TripTimes tripTimes) { - this.tripTimes.add(tripTimes); - return this; + var trip = tripTimes.getTrip(); + if (this.tripTimes.containsKey(trip.getId())) { + throw new IllegalStateException( + "Error! TripTimes for the same trip is added twice. Trip: " + trip + ); + } + return addOrUpdateTripTimes(tripTimes); } - public TimetableBuilder addAllTripTimes(List tripTimes) { - this.tripTimes.addAll(tripTimes); + /** + * Add or update the trip-times. If the trip has an associated trip-times, then the trip-times + * are replaced. If not, the trip-times it is added. Consider using + * {@link #addTripTimes(TripTimes)}. + */ + public TimetableBuilder addOrUpdateTripTimes(TripTimes tripTimes) { + this.tripTimes.put(tripTimes.getTrip().getId(), tripTimes); return this; } - public TimetableBuilder setTripTimes(int tripIndex, TripTimes tripTimes) { - this.tripTimes.set(tripIndex, tripTimes); + public TimetableBuilder addAllTripTimes(List tripTimes) { + for (TripTimes it : tripTimes) { + addTripTimes(it); + } return this; } public TimetableBuilder removeTripTimes(TripTimes tripTimesToRemove) { - tripTimes.remove(tripTimesToRemove); + tripTimes.remove(tripTimesToRemove.getTrip().getId()); return this; } - public TimetableBuilder removeAllTripTimes(Set tripTimesToBeRemoved) { - tripTimes.removeAll(tripTimesToBeRemoved); + public TimetableBuilder removeAllTripTimes(Collection tripTimesToBeRemoved) { + for (TripTimes it : tripTimesToBeRemoved) { + tripTimes.remove(it.getTrip().getId()); + } return this; } @@ -69,7 +91,7 @@ public TimetableBuilder removeAllTripTimes(Set tripTimesToBeRemoved) *

*/ public TimetableBuilder updateAllTripTimes(UnaryOperator update) { - tripTimes.replaceAll(update); + tripTimes.replaceAll((t, tt) -> update.apply(tt)); frequencies.replaceAll(it -> new FrequencyEntry( it.startTime, @@ -95,8 +117,8 @@ public LocalDate getServiceDate() { return serviceDate; } - public List getTripTimes() { - return tripTimes; + List createImmutableOrderedListOfTripTimes() { + return tripTimes.values().stream().sorted().toList(); } public List getFrequencies() { @@ -116,7 +138,7 @@ public Direction getDirection() { private TripTimes getRepresentativeTripTimes() { if (!tripTimes.isEmpty()) { - return tripTimes.getFirst(); + return tripTimes.values().stream().findAny().get(); } else if (!frequencies.isEmpty()) { return frequencies.getFirst().tripTimes; } else { diff --git a/src/main/java/org/opentripplanner/model/TimetableSnapshot.java b/src/main/java/org/opentripplanner/model/TimetableSnapshot.java index 64f06362228..05011db5a48 100644 --- a/src/main/java/org/opentripplanner/model/TimetableSnapshot.java +++ b/src/main/java/org/opentripplanner/model/TimetableSnapshot.java @@ -301,20 +301,12 @@ public Result update(RealTimeTripUpdate realTimeTrip TimetableBuilder ttb = Timetable.of(tt).withServiceDate(serviceDate); // Assume all trips in a pattern are from the same feed, which should be the case. - // Find trip index - Trip trip = updatedTripTimes.getTrip(); - int tripIndex = tt.getTripIndex(trip.getId()); - if (tripIndex == -1) { - // Trip not found, add it - ttb.addTripTimes(updatedTripTimes); - } else { - // Set updated trip times of trip - ttb.setTripTimes(tripIndex, updatedTripTimes); - } + ttb.addOrUpdateTripTimes(updatedTripTimes); Timetable updated = ttb.build(); swapTimetable(pattern, tt, updated); + Trip trip = updatedTripTimes.getTrip(); if (pattern.isCreatedByRealtimeUpdater()) { // Remember this pattern for the added trip id and service date FeedScopedId tripId = trip.getId(); diff --git a/src/test/java/org/opentripplanner/model/TimetableTest.java b/src/test/java/org/opentripplanner/model/TimetableTest.java index 75a3af3f3ca..4b9748dd0d7 100644 --- a/src/test/java/org/opentripplanner/model/TimetableTest.java +++ b/src/test/java/org/opentripplanner/model/TimetableTest.java @@ -190,7 +190,7 @@ public void update() { result.ifSuccess(p -> { var updatedTripTimes = p.getTripTimes(); assertNotNull(updatedTripTimes); - timetable = Timetable.of(timetable).setTripTimes(trip_1_1_index, updatedTripTimes).build(); + timetable = Timetable.of(timetable).addOrUpdateTripTimes(updatedTripTimes).build(); assertEquals(20 * 60 + 120, timetable.getTripTimes(trip_1_1_index).getArrivalTime(2)); }); @@ -217,7 +217,7 @@ public void update() { result.ifSuccess(p -> { var updatedTripTimes = p.getTripTimes(); assertNotNull(updatedTripTimes); - timetable = timetable.of(timetable).setTripTimes(trip_1_1_index, updatedTripTimes).build(); + timetable = timetable.of(timetable).addOrUpdateTripTimes(updatedTripTimes).build(); }); // update trip arrival time only @@ -246,7 +246,7 @@ public void update() { result.ifSuccess(p -> { var updatedTripTimes = p.getTripTimes(); assertNotNull(updatedTripTimes); - timetable = Timetable.of(timetable).setTripTimes(trip_1_1_index, updatedTripTimes).build(); + timetable = Timetable.of(timetable).addOrUpdateTripTimes(updatedTripTimes).build(); }); // update trip departure time only @@ -273,7 +273,7 @@ public void update() { result.ifSuccess(p -> { var updatedTripTimes = p.getTripTimes(); assertNotNull(updatedTripTimes); - timetable = Timetable.of(timetable).setTripTimes(trip_1_1_index, updatedTripTimes).build(); + timetable = Timetable.of(timetable).addOrUpdateTripTimes(updatedTripTimes).build(); }); // update trip using stop id @@ -299,7 +299,7 @@ public void update() { result.ifSuccess(p -> { var updatedTripTimes = p.getTripTimes(); assertNotNull(updatedTripTimes); - timetable = Timetable.of(timetable).setTripTimes(trip_1_1_index, updatedTripTimes).build(); + timetable = Timetable.of(timetable).addOrUpdateTripTimes(updatedTripTimes).build(); }); } diff --git a/src/test/java/org/opentripplanner/routing/stoptimes/StopTimesHelperTest.java b/src/test/java/org/opentripplanner/routing/stoptimes/StopTimesHelperTest.java index d9377ba2c4f..61fffd7a905 100644 --- a/src/test/java/org/opentripplanner/routing/stoptimes/StopTimesHelperTest.java +++ b/src/test/java/org/opentripplanner/routing/stoptimes/StopTimesHelperTest.java @@ -42,7 +42,7 @@ public static void setUp() throws Exception { pattern = originalPattern .copy() - .withScheduledTimeTableBuilder(builder -> builder.setTripTimes(0, newTripTimes)) + .withScheduledTimeTableBuilder(builder -> builder.addOrUpdateTripTimes(newTripTimes)) .build(); // replace the original pattern by the updated pattern in the transit model transitModel.addTripPattern(pattern.getId(), pattern); diff --git a/src/test/java/org/opentripplanner/transit/speed_test/SpeedIntegrationTest.java b/src/test/java/org/opentripplanner/transit/speed_test/SpeedIntegrationTest.java index e5006d949b6..61a23b10ff6 100644 --- a/src/test/java/org/opentripplanner/transit/speed_test/SpeedIntegrationTest.java +++ b/src/test/java/org/opentripplanner/transit/speed_test/SpeedIntegrationTest.java @@ -24,14 +24,14 @@ import org.opentripplanner.transit.speed_test.options.SpeedTestConfig; /** - * This test run the SpeedTest on the Portland dataset. It tests all SpeedTest + * This test runs the SpeedTest on the Portland dataset. It tests all SpeedTest * profiles. This is also a good integration-test for the OTP routing query. */ public class SpeedIntegrationTest { /** - * We need to use a relative path here, because the test will update the results files in case - * the results differ. This make it easy to maintain the test. + * We need to use a relative path here, because the test will update the result files in case + * the results differ. This makes it easy to maintain the test. */ private static final File BASE_DIR = Path.of("src", "test", "resources", "speedtest").toFile(); From 2b6c7f0e49d05d72997f7ea78cbb112d6bb3f7d5 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 22 Aug 2024 19:47:41 +0200 Subject: [PATCH 18/80] refactor: Replace 'Timetable.of(tt)' with 'tt.copyOf()' --- .../java/org/opentripplanner/model/Timetable.java | 15 +++++++-------- .../opentripplanner/model/TimetableSnapshot.java | 6 +++--- .../model/impl/OtpTransitServiceBuilder.java | 5 +++-- .../transit/model/network/TripPatternBuilder.java | 2 +- .../model/TimetableSnapshotTest.java | 4 ++-- .../org/opentripplanner/model/TimetableTest.java | 10 +++++----- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/opentripplanner/model/Timetable.java b/src/main/java/org/opentripplanner/model/Timetable.java index 7cb747b2c90..97ec6eddb97 100644 --- a/src/main/java/org/opentripplanner/model/Timetable.java +++ b/src/main/java/org/opentripplanner/model/Timetable.java @@ -68,19 +68,18 @@ public class Timetable implements Serializable { this.frequencyEntries = List.copyOf(timetableBuilder.getFrequencies()); } - /** - * Copy constructor: create an un-indexed Timetable with the same TripTimes as the specified - * timetable. - */ - public static TimetableBuilder of(Timetable tt) { - return new TimetableBuilder(tt); - } - /** Construct an empty Timetable. */ public static TimetableBuilder of() { return new TimetableBuilder(); } + /** + * Copy timetable into a builder witch can be used to modify the timetable. + */ + public TimetableBuilder copyOf() { + return new TimetableBuilder(this); + } + /** @return the index of TripTimes for this trip ID in this particular Timetable */ public int getTripIndex(FeedScopedId tripId) { int ret = 0; diff --git a/src/main/java/org/opentripplanner/model/TimetableSnapshot.java b/src/main/java/org/opentripplanner/model/TimetableSnapshot.java index 05011db5a48..8cd0707d655 100644 --- a/src/main/java/org/opentripplanner/model/TimetableSnapshot.java +++ b/src/main/java/org/opentripplanner/model/TimetableSnapshot.java @@ -298,7 +298,7 @@ public Result update(RealTimeTripUpdate realTimeTrip } Timetable tt = resolve(pattern, serviceDate); - TimetableBuilder ttb = Timetable.of(tt).withServiceDate(serviceDate); + TimetableBuilder ttb = tt.copyOf().withServiceDate(serviceDate); // Assume all trips in a pattern are from the same feed, which should be the case. ttb.addOrUpdateTripTimes(updatedTripTimes); @@ -452,8 +452,8 @@ public boolean revertTripToScheduledTripPattern(FeedScopedId tripId, LocalDate s if (tripTimesToRemove != null) { for (Timetable originalTimetable : sortedTimetables) { if (originalTimetable.getTripTimes().contains(tripTimesToRemove)) { - Timetable updatedTimetable = Timetable - .of(originalTimetable) + Timetable updatedTimetable = originalTimetable + .copyOf() .removeTripTimes(tripTimesToRemove) .build(); swapTimetable(pattern, originalTimetable, updatedTimetable); diff --git a/src/main/java/org/opentripplanner/model/impl/OtpTransitServiceBuilder.java b/src/main/java/org/opentripplanner/model/impl/OtpTransitServiceBuilder.java index aa82a66d0b9..d62d0331a34 100644 --- a/src/main/java/org/opentripplanner/model/impl/OtpTransitServiceBuilder.java +++ b/src/main/java/org/opentripplanner/model/impl/OtpTransitServiceBuilder.java @@ -390,8 +390,9 @@ private void fixOrRemovePatternsWhichReferenceNoneExistingTrips() { .collect(Collectors.toUnmodifiableSet()); if (!tripTimesToBeRemoved.isEmpty()) { removePatterns.add(e); - Timetable updatedTimetable = Timetable - .of(ptn.getScheduledTimetable()) + Timetable updatedTimetable = ptn + .getScheduledTimetable() + .copyOf() .removeAllTripTimes(tripTimesToBeRemoved) .build(); TripPattern updatedPattern = ptn.copy().withScheduledTimeTable(updatedTimetable).build(); diff --git a/src/main/java/org/opentripplanner/transit/model/network/TripPatternBuilder.java b/src/main/java/org/opentripplanner/transit/model/network/TripPatternBuilder.java index 91451d43cc2..88a391c1e33 100644 --- a/src/main/java/org/opentripplanner/transit/model/network/TripPatternBuilder.java +++ b/src/main/java/org/opentripplanner/transit/model/network/TripPatternBuilder.java @@ -107,7 +107,7 @@ public TripPatternBuilder withScheduledTimeTableBuilder( // create a builder for the scheduled timetable only if it needs to be modified. // otherwise reuse the existing timetable if (scheduledTimetableBuilder == null) { - scheduledTimetableBuilder = Timetable.of(scheduledTimetable); + scheduledTimetableBuilder = scheduledTimetable.copyOf(); scheduledTimetable = null; } producer.apply(scheduledTimetableBuilder); diff --git a/src/test/java/org/opentripplanner/model/TimetableSnapshotTest.java b/src/test/java/org/opentripplanner/model/TimetableSnapshotTest.java index 57bf11dd8f6..ffdfc027b76 100644 --- a/src/test/java/org/opentripplanner/model/TimetableSnapshotTest.java +++ b/src/test/java/org/opentripplanner/model/TimetableSnapshotTest.java @@ -54,8 +54,8 @@ public static void setUp() throws Exception { @Test public void testCompare() { Timetable orig = Timetable.of().build(); - Timetable a = Timetable.of(orig).withServiceDate(LocalDate.now(timeZone).minusDays(1)).build(); - Timetable b = Timetable.of(orig).withServiceDate(LocalDate.now(timeZone)).build(); + Timetable a = orig.copyOf().withServiceDate(LocalDate.now(timeZone).minusDays(1)).build(); + Timetable b = orig.copyOf().withServiceDate(LocalDate.now(timeZone)).build(); assertTrue(new TimetableSnapshot.SortedTimetableComparator().compare(a, b) < 0); } diff --git a/src/test/java/org/opentripplanner/model/TimetableTest.java b/src/test/java/org/opentripplanner/model/TimetableTest.java index 4b9748dd0d7..9135abd96ee 100644 --- a/src/test/java/org/opentripplanner/model/TimetableTest.java +++ b/src/test/java/org/opentripplanner/model/TimetableTest.java @@ -190,7 +190,7 @@ public void update() { result.ifSuccess(p -> { var updatedTripTimes = p.getTripTimes(); assertNotNull(updatedTripTimes); - timetable = Timetable.of(timetable).addOrUpdateTripTimes(updatedTripTimes).build(); + timetable = timetable.copyOf().addOrUpdateTripTimes(updatedTripTimes).build(); assertEquals(20 * 60 + 120, timetable.getTripTimes(trip_1_1_index).getArrivalTime(2)); }); @@ -217,7 +217,7 @@ public void update() { result.ifSuccess(p -> { var updatedTripTimes = p.getTripTimes(); assertNotNull(updatedTripTimes); - timetable = timetable.of(timetable).addOrUpdateTripTimes(updatedTripTimes).build(); + timetable = timetable.copyOf().addOrUpdateTripTimes(updatedTripTimes).build(); }); // update trip arrival time only @@ -246,7 +246,7 @@ public void update() { result.ifSuccess(p -> { var updatedTripTimes = p.getTripTimes(); assertNotNull(updatedTripTimes); - timetable = Timetable.of(timetable).addOrUpdateTripTimes(updatedTripTimes).build(); + timetable = timetable.copyOf().addOrUpdateTripTimes(updatedTripTimes).build(); }); // update trip departure time only @@ -273,7 +273,7 @@ public void update() { result.ifSuccess(p -> { var updatedTripTimes = p.getTripTimes(); assertNotNull(updatedTripTimes); - timetable = Timetable.of(timetable).addOrUpdateTripTimes(updatedTripTimes).build(); + timetable = timetable.copyOf().addOrUpdateTripTimes(updatedTripTimes).build(); }); // update trip using stop id @@ -299,7 +299,7 @@ public void update() { result.ifSuccess(p -> { var updatedTripTimes = p.getTripTimes(); assertNotNull(updatedTripTimes); - timetable = Timetable.of(timetable).addOrUpdateTripTimes(updatedTripTimes).build(); + timetable = timetable.copyOf().addOrUpdateTripTimes(updatedTripTimes).build(); }); } From cc692c6f56c81df9a37ff5b518f8c32e78260a06 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Mon, 26 Aug 2024 11:26:56 +0200 Subject: [PATCH 19/80] Fix unit test (the timetable is now sorted) --- .../netex/NetexEpipBundleSmokeTest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/java/org/opentripplanner/netex/NetexEpipBundleSmokeTest.java b/src/test/java/org/opentripplanner/netex/NetexEpipBundleSmokeTest.java index b89e6883951..72e750c055f 100644 --- a/src/test/java/org/opentripplanner/netex/NetexEpipBundleSmokeTest.java +++ b/src/test/java/org/opentripplanner/netex/NetexEpipBundleSmokeTest.java @@ -84,7 +84,7 @@ private static FeedScopedId fId(String id) { private void assertAgencies(Collection agencies) { assertEquals(3, agencies.size()); - Agency a = list(agencies).get(0); + Agency a = list(agencies).getFirst(); assertEquals("DE::Authority:41::", a.getId().getId()); assertEquals("HOCHBAHN, Bus", a.getName()); assertNull(a.getUrl()); @@ -146,7 +146,7 @@ private void assertTripPatterns(Collection patterns) { p.getStops().toString() ); List trips = p.scheduledTripsAsStream().toList(); - assertEquals("Trip{HH:DE::ServiceJourney:36439031_0:: X86}", trips.get(0).toString()); + assertEquals("Trip{HH:DE::ServiceJourney:36439062_0:: X86}", trips.getFirst().toString()); assertEquals(55, trips.size()); assertEquals(4, patterns.size()); } @@ -176,12 +176,12 @@ private void assertServiceIds(Collection trips, Collection s private void assetServiceCalendar(CalendarServiceData cal) { ArrayList sIds = new ArrayList<>(cal.getServiceIds()); assertEquals(2, sIds.size()); - FeedScopedId serviceId1 = sIds.get(0); + FeedScopedId serviceId1 = sIds.getFirst(); List dates = cal.getServiceDatesForServiceId(serviceId1); - assertEquals("2023-02-02", dates.get(0).toString()); - assertEquals("2023-12-08", dates.get(dates.size() - 1).toString()); + assertEquals("2023-02-02", dates.getFirst().toString()); + assertEquals("2023-12-08", dates.getLast().toString()); assertEquals(214, dates.size()); } } From 15833df9dfa493e2dcb6b2e3f268978950459964 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Mon, 26 Aug 2024 11:27:23 +0200 Subject: [PATCH 20/80] Remove redundant timetable sorting in TransitLayerMapper --- .../transit/mappers/TransitLayerMapper.java | 14 -------------- .../mappers/TripPatternForDateMapper.java | 19 +------------------ 2 files changed, 1 insertion(+), 32 deletions(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransitLayerMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransitLayerMapper.java index d375b4c546c..13f8facef3d 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransitLayerMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransitLayerMapper.java @@ -7,15 +7,12 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; import javax.annotation.Nullable; import org.opentripplanner.framework.application.OTPFeature; -import org.opentripplanner.model.Timetable; import org.opentripplanner.routing.algorithm.raptoradapter.transit.Transfer; import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitLayer; import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitTuningParameters; @@ -26,7 +23,6 @@ import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.RaptorRequestTransferCache; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.site.StopTransferPriority; -import org.opentripplanner.transit.model.timetable.TripTimes; import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.StopModel; import org.opentripplanner.transit.service.TransitModel; @@ -64,16 +60,6 @@ public static TransitLayer map( return new TransitLayerMapper(transitModel).map(tuningParameters); } - // TODO We could save time by either pre-sorting these, or by using a sorting algorithm that is - // optimized for sorting nearly-sorted lists. - static List getSortedTripTimes(Timetable timetable) { - return timetable - .getTripTimes() - .stream() - .sorted(Comparator.comparing(TripTimes::sortIndex)) - .collect(Collectors.toList()); - } - private TransitLayer map(TransitTuningParameters tuningParameters) { HashMap> tripPatternsByStopByDate; List> transferByStopIndex; diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TripPatternForDateMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TripPatternForDateMapper.java index cd00b9356dc..256a268e2c7 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TripPatternForDateMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TripPatternForDateMapper.java @@ -3,12 +3,9 @@ import gnu.trove.set.TIntSet; import java.time.LocalDate; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; import java.util.stream.Collectors; import javax.annotation.Nullable; import org.opentripplanner.model.Timetable; @@ -33,8 +30,6 @@ public class TripPatternForDateMapper { private static final Logger LOG = LoggerFactory.getLogger(TripPatternForDateMapper.class); - private final ConcurrentMap> sortedTripTimesForTimetable = new ConcurrentHashMap<>(); - private final Map serviceCodesRunningForDate; /** @@ -69,19 +64,7 @@ public TripPatternForDate map(Timetable timetable, LocalDate serviceDate) { List times = new ArrayList<>(); - // The TripTimes are not sorted by departure time in the source timetable because - // OTP1 performs a simple/ linear search. Raptor results depend on trips being - // sorted. We reuse the same timetables many times on different days, so cache the - // sorted versions to avoid repeated compute-intensive sorting. Anecdotally this - // reduces mapping time by more than half, but it is still rather slow. NL Mapping - // takes 32 seconds sorting every timetable, 9 seconds with cached sorting, and 6 - // seconds with no timetable sorting at all. - List sortedTripTimes = sortedTripTimesForTimetable.computeIfAbsent( - timetable, - TransitLayerMapper::getSortedTripTimes - ); - - for (TripTimes tripTimes : sortedTripTimes) { + for (TripTimes tripTimes : timetable.getTripTimes()) { if (!serviceCodesRunning.contains(tripTimes.getServiceCode())) { continue; } From 4b6a019ec5f835b420fe13b34f1eae481a72945f Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Tue, 27 Aug 2024 15:34:27 +0200 Subject: [PATCH 21/80] Code clean-up --- src/main/java/org/opentripplanner/model/TimetableBuilder.java | 2 +- .../transit/model/network/TripPatternBuilder.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/model/TimetableBuilder.java b/src/main/java/org/opentripplanner/model/TimetableBuilder.java index bd21856eecb..d2856a52a98 100644 --- a/src/main/java/org/opentripplanner/model/TimetableBuilder.java +++ b/src/main/java/org/opentripplanner/model/TimetableBuilder.java @@ -138,7 +138,7 @@ public Direction getDirection() { private TripTimes getRepresentativeTripTimes() { if (!tripTimes.isEmpty()) { - return tripTimes.values().stream().findAny().get(); + return tripTimes.values().stream().findFirst().get(); } else if (!frequencies.isEmpty()) { return frequencies.getFirst().tripTimes; } else { diff --git a/src/main/java/org/opentripplanner/transit/model/network/TripPatternBuilder.java b/src/main/java/org/opentripplanner/transit/model/network/TripPatternBuilder.java index 88a391c1e33..ec2451ea66d 100644 --- a/src/main/java/org/opentripplanner/transit/model/network/TripPatternBuilder.java +++ b/src/main/java/org/opentripplanner/transit/model/network/TripPatternBuilder.java @@ -17,6 +17,7 @@ import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.framework.AbstractEntityBuilder; import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.model.timetable.Direction; @SuppressWarnings("UnusedReturnValue") public final class TripPatternBuilder @@ -139,7 +140,7 @@ public int transitReluctanceFactorIndex() { return route.getMode().ordinal(); } - public Object getDirection() { + public Direction getDirection() { if (scheduledTimetable != null) { return scheduledTimetable.getDirection(); } From 16c8264c0647506c5980175f5309ee15b14cddc2 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Mon, 16 Sep 2024 10:40:30 +0200 Subject: [PATCH 22/80] Ensure stable TripPattern creation order --- .../gtfs/GenerateTripPatternsOperation.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/gtfs/GenerateTripPatternsOperation.java b/src/main/java/org/opentripplanner/gtfs/GenerateTripPatternsOperation.java index 84b25340e6c..08ad300fa9a 100644 --- a/src/main/java/org/opentripplanner/gtfs/GenerateTripPatternsOperation.java +++ b/src/main/java/org/opentripplanner/gtfs/GenerateTripPatternsOperation.java @@ -3,6 +3,7 @@ import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ListMultimap; import com.google.common.collect.Multimap; +import com.google.common.collect.MultimapBuilder; import java.util.Collection; import java.util.HashMap; import java.util.List; @@ -47,7 +48,12 @@ public class GenerateTripPatternsOperation { private final Set calendarServiceIds; private final GeometryProcessor geometryProcessor; - private final Multimap tripPatternBuilders = ArrayListMultimap.create(); + // TODO the linked hashset configuration ensures that TripPatterns are created in the same order + // as Trips are imported, as a workaround for issue #6067 + private final Multimap tripPatternBuilders = MultimapBuilder + .linkedHashKeys() + .linkedHashSetValues() + .build(); private final ListMultimap frequenciesForTrip = ArrayListMultimap.create(); private int freqCount = 0; From c3553d5c87146847a83011245e472aa48cb8554f Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Mon, 16 Sep 2024 15:04:23 +0200 Subject: [PATCH 23/80] Code clean-up --- .../org/opentripplanner/model/Timetable.java | 60 ++++++++++++++----- .../model/TimetableBuilder.java | 19 +----- .../transit/model/network/TripPattern.java | 7 ++- 3 files changed, 52 insertions(+), 34 deletions(-) diff --git a/src/main/java/org/opentripplanner/model/Timetable.java b/src/main/java/org/opentripplanner/model/Timetable.java index 97ec6eddb97..6740e2cc99d 100644 --- a/src/main/java/org/opentripplanner/model/Timetable.java +++ b/src/main/java/org/opentripplanner/model/Timetable.java @@ -16,6 +16,7 @@ import java.time.LocalDate; import java.time.ZoneId; import java.util.ArrayList; +import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -422,25 +423,19 @@ public LocalDate getServiceDate() { } /** - * The direction for all the trips in this pattern. + * Return the direction for all the trips in this timetable. + * By construction, all trips in a timetable have the same direction. */ public Direction getDirection() { - return Optional - .ofNullable(getRepresentativeTripTimes()) - .map(TripTimes::getTrip) - .map(Trip::getDirection) - .orElse(Direction.UNKNOWN); + return getDirection(tripTimes, frequencyEntries); } + /** + * Return an arbitrary TripTimes in this Timetable. + * Return a scheduled trip times if it exists, otherwise return a frequency-based trip times. + */ public TripTimes getRepresentativeTripTimes() { - if (!getTripTimes().isEmpty()) { - return getTripTimes(0); - } else if (!getFrequencyEntries().isEmpty()) { - return getFrequencyEntries().get(0).tripTimes; - } else { - // Pattern is created only for real-time updates - return null; - } + return getRepresentativeTripTimes(tripTimes, frequencyEntries); } /** @@ -451,4 +446,41 @@ public TripTimes getRepresentativeTripTimes() { public boolean isCreatedByRealTimeUpdater() { return serviceDate != null; } + + /** + * The direction for the given collections of trip times. + * The method assumes that all trip times have the same directions and picks up one arbitrarily. + * @param scheduledTripTimes all the scheduled-based trip times in a timetable. + * @param frequencies all the frequency-based trip times in a timetable. + */ + static Direction getDirection( + Collection scheduledTripTimes, + Collection frequencies + ) { + return Optional + .ofNullable(getRepresentativeTripTimes(scheduledTripTimes, frequencies)) + .map(TripTimes::getTrip) + .map(Trip::getDirection) + .orElse(Direction.UNKNOWN); + } + + /** + * Return an arbitrary TripTimes. + * @param scheduledTripTimes all the scheduled-based trip times in a timetable. + * @param frequencies all the frequency-based trip times in a timetable. + * + */ + private static TripTimes getRepresentativeTripTimes( + Collection scheduledTripTimes, + Collection frequencies + ) { + if (!scheduledTripTimes.isEmpty()) { + return scheduledTripTimes.iterator().next(); + } else if (!frequencies.isEmpty()) { + return frequencies.iterator().next().tripTimes; + } else { + // Pattern is created only for real-time updates + return null; + } + } } diff --git a/src/main/java/org/opentripplanner/model/TimetableBuilder.java b/src/main/java/org/opentripplanner/model/TimetableBuilder.java index d2856a52a98..1c4e30dbdc1 100644 --- a/src/main/java/org/opentripplanner/model/TimetableBuilder.java +++ b/src/main/java/org/opentripplanner/model/TimetableBuilder.java @@ -6,13 +6,11 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.function.UnaryOperator; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.timetable.Direction; import org.opentripplanner.transit.model.timetable.FrequencyEntry; -import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripTimes; public class TimetableBuilder { @@ -129,22 +127,7 @@ public List getFrequencies() { * The direction for all the trips in this timetable. */ public Direction getDirection() { - return Optional - .ofNullable(getRepresentativeTripTimes()) - .map(TripTimes::getTrip) - .map(Trip::getDirection) - .orElse(Direction.UNKNOWN); - } - - private TripTimes getRepresentativeTripTimes() { - if (!tripTimes.isEmpty()) { - return tripTimes.values().stream().findFirst().get(); - } else if (!frequencies.isEmpty()) { - return frequencies.getFirst().tripTimes; - } else { - // Pattern is created only for real-time updates - return null; - } + return Timetable.getDirection(tripTimes.values(), frequencies); } public Timetable build() { diff --git a/src/main/java/org/opentripplanner/transit/model/network/TripPattern.java b/src/main/java/org/opentripplanner/transit/model/network/TripPattern.java index 8e39d353913..e7d7af45b6d 100644 --- a/src/main/java/org/opentripplanner/transit/model/network/TripPattern.java +++ b/src/main/java/org/opentripplanner/transit/model/network/TripPattern.java @@ -1,7 +1,6 @@ package org.opentripplanner.transit.model.network; import static java.util.Objects.requireNonNull; -import static java.util.Objects.requireNonNullElseGet; import static org.opentripplanner.framework.lang.ObjectUtils.requireNotInitialized; import java.util.ArrayList; @@ -349,7 +348,11 @@ public boolean isModifiedFromTripPatternWithEqualStops(TripPattern other) { } /** - * The direction for all the trips in this pattern. + * Return the direction for all the trips in this pattern. + * By construction, all trips in a pattern have the same direction: + * - trips derived from NeTEx data belong to a ServiceJourney that belongs to a JourneyPattern + * that belongs to a NeTEx Route that specifies a single direction. + * - trips derived from GTFS data are grouped by direction in a trip pattern, during graph build. */ public Direction getDirection() { return scheduledTimetable.getDirection(); From 1139e192e747b6bb5134aa6497a9630c217545ce Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 18 Sep 2024 13:07:20 +0200 Subject: [PATCH 24/80] Remove GTFS flex stop areas --- .../GTFSToOtpTransitServiceMapper.java | 6 -- .../gtfs/mapping/StopAreaMapper.java | 72 ------------------ .../gtfs/mapping/StopTimeMapper.java | 6 -- .../gtfs/mapping/StopAreaMapperTest.java | 75 ------------------- .../gtfs/mapping/StopTimeMapperTest.java | 6 -- .../gtfs/mapping/TransferMapperTest.java | 6 -- 6 files changed, 171 deletions(-) delete mode 100644 src/main/java/org/opentripplanner/gtfs/mapping/StopAreaMapper.java delete mode 100644 src/test/java/org/opentripplanner/gtfs/mapping/StopAreaMapperTest.java diff --git a/src/main/java/org/opentripplanner/gtfs/mapping/GTFSToOtpTransitServiceMapper.java b/src/main/java/org/opentripplanner/gtfs/mapping/GTFSToOtpTransitServiceMapper.java index d58410741f9..b7aea1c2e2f 100644 --- a/src/main/java/org/opentripplanner/gtfs/mapping/GTFSToOtpTransitServiceMapper.java +++ b/src/main/java/org/opentripplanner/gtfs/mapping/GTFSToOtpTransitServiceMapper.java @@ -72,7 +72,6 @@ public class GTFSToOtpTransitServiceMapper { private final FareTransferRuleMapper fareTransferRuleMapper; - private final StopAreaMapper stopAreaMapper; private final DirectionMapper directionMapper; private final DataImportIssueStore issueStore; @@ -112,9 +111,6 @@ public GTFSToOtpTransitServiceMapper( boardingAreaMapper = new BoardingAreaMapper(translationHelper, stopLookup); locationMapper = new LocationMapper(builder.stopModel(), issueStore); locationGroupMapper = new LocationGroupMapper(stopMapper, locationMapper, builder.stopModel()); - // the use of stop areas were reverted in the spec - // this code will go away, please migrate now! - stopAreaMapper = new StopAreaMapper(stopMapper, locationMapper, builder.stopModel()); pathwayMapper = new PathwayMapper(stopMapper, entranceMapper, pathwayNodeMapper, boardingAreaMapper); routeMapper = new RouteMapper(agencyMapper, issueStore, translationHelper); @@ -126,7 +122,6 @@ public GTFSToOtpTransitServiceMapper( stopMapper, locationMapper, locationGroupMapper, - stopAreaMapper, tripMapper, bookingRuleMapper, translationHelper @@ -166,7 +161,6 @@ public void mapStopTripAndRouteDataIntoBuilder() { // Stop areas and Stop groups are only used in FLEX routes builder.stopModel().withAreaStops(locationMapper.map(data.getAllLocations())); builder.stopModel().withGroupStops(locationGroupMapper.map(data.getAllLocationGroups())); - builder.stopModel().withGroupStops(stopAreaMapper.map(data.getAllStopAreas())); } builder.getPathways().addAll(pathwayMapper.map(data.getAllPathways())); diff --git a/src/main/java/org/opentripplanner/gtfs/mapping/StopAreaMapper.java b/src/main/java/org/opentripplanner/gtfs/mapping/StopAreaMapper.java deleted file mode 100644 index 55c836aa458..00000000000 --- a/src/main/java/org/opentripplanner/gtfs/mapping/StopAreaMapper.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.opentripplanner.gtfs.mapping; - -import static org.opentripplanner.gtfs.mapping.AgencyAndIdMapper.mapAgencyAndId; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import org.onebusaway.gtfs.model.Location; -import org.onebusaway.gtfs.model.Stop; -import org.opentripplanner.framework.collection.MapUtils; -import org.opentripplanner.framework.i18n.NonLocalizedString; -import org.opentripplanner.transit.model.site.GroupStop; -import org.opentripplanner.transit.model.site.GroupStopBuilder; -import org.opentripplanner.transit.service.StopModelBuilder; - -/** - * For a while GTFS Flex location groups were replaced by GTFS Fares v2 stop areas. After a few - * months, this decision was reverted and a new style of location groups were re-added to the Flex - * spec. - * @deprecated Arcadis tooling still produces stop areas and for a while we will support both. Please don't rely - * on this as the class will be removed in the future! - */ -@Deprecated -public class StopAreaMapper { - - private final StopMapper stopMapper; - - private final LocationMapper locationMapper; - - private final Map mappedStopAreas = new HashMap<>(); - private final StopModelBuilder stopModel; - - public StopAreaMapper( - StopMapper stopMapper, - LocationMapper locationMapper, - StopModelBuilder stopModel - ) { - this.stopMapper = stopMapper; - this.locationMapper = locationMapper; - this.stopModel = stopModel; - } - - Collection map(Collection allAreas) { - return MapUtils.mapToList(allAreas, this::map); - } - - /** Map from GTFS to OTP model, {@code null} safe. */ - GroupStop map(org.onebusaway.gtfs.model.StopArea original) { - return original == null ? null : mappedStopAreas.computeIfAbsent(original, this::doMap); - } - - private GroupStop doMap(org.onebusaway.gtfs.model.StopArea element) { - GroupStopBuilder groupStopBuilder = stopModel - .groupStop(mapAgencyAndId(element.getId())) - .withName(new NonLocalizedString(element.getName())); - - for (org.onebusaway.gtfs.model.StopLocation location : element.getLocations()) { - switch (location) { - case Stop stop -> groupStopBuilder.addLocation(stopMapper.map(stop)); - case Location loc -> groupStopBuilder.addLocation(locationMapper.map(loc)); - case org.onebusaway.gtfs.model.StopArea ignored -> throw new RuntimeException( - "Nested GroupStops are not allowed" - ); - case null, default -> throw new RuntimeException( - "Unknown location type: " + location.getClass().getSimpleName() - ); - } - } - - return groupStopBuilder.build(); - } -} diff --git a/src/main/java/org/opentripplanner/gtfs/mapping/StopTimeMapper.java b/src/main/java/org/opentripplanner/gtfs/mapping/StopTimeMapper.java index 67b250c5061..5f20b6e0224 100644 --- a/src/main/java/org/opentripplanner/gtfs/mapping/StopTimeMapper.java +++ b/src/main/java/org/opentripplanner/gtfs/mapping/StopTimeMapper.java @@ -7,7 +7,6 @@ import org.onebusaway.gtfs.model.Location; import org.onebusaway.gtfs.model.LocationGroup; import org.onebusaway.gtfs.model.Stop; -import org.onebusaway.gtfs.model.StopArea; import org.opentripplanner.framework.collection.MapUtils; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.model.StopTime; @@ -22,7 +21,6 @@ class StopTimeMapper { private final LocationMapper locationMapper; private final LocationGroupMapper locationGroupMapper; - private final StopAreaMapper stopAreaMapper; private final TripMapper tripMapper; private final BookingRuleMapper bookingRuleMapper; @@ -35,7 +33,6 @@ class StopTimeMapper { StopMapper stopMapper, LocationMapper locationMapper, LocationGroupMapper locationGroupMapper, - StopAreaMapper stopAreaMapper, TripMapper tripMapper, BookingRuleMapper bookingRuleMapper, TranslationHelper translationHelper @@ -43,7 +40,6 @@ class StopTimeMapper { this.stopMapper = stopMapper; this.locationMapper = locationMapper; this.locationGroupMapper = locationGroupMapper; - this.stopAreaMapper = stopAreaMapper; this.tripMapper = tripMapper; this.bookingRuleMapper = bookingRuleMapper; this.translationHelper = translationHelper; @@ -71,8 +67,6 @@ private StopTime doMap(org.onebusaway.gtfs.model.StopTime rhs) { case Stop stop -> lhs.setStop(stopMapper.map(stop)); case Location location -> lhs.setStop(locationMapper.map(location)); case LocationGroup locGroup -> lhs.setStop(locationGroupMapper.map(locGroup)); - // TODO: only here for backwards compatibility, this will be removed in the future - case StopArea area -> lhs.setStop(stopAreaMapper.map(area)); default -> throw new IllegalArgumentException( "Unknown location type: %s".formatted(stopLocation) ); diff --git a/src/test/java/org/opentripplanner/gtfs/mapping/StopAreaMapperTest.java b/src/test/java/org/opentripplanner/gtfs/mapping/StopAreaMapperTest.java deleted file mode 100644 index d58bcfcdd31..00000000000 --- a/src/test/java/org/opentripplanner/gtfs/mapping/StopAreaMapperTest.java +++ /dev/null @@ -1,75 +0,0 @@ -package org.opentripplanner.gtfs.mapping; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.opentripplanner.graph_builder.issue.api.DataImportIssueStore.NOOP; - -import java.util.Set; -import java.util.stream.Collectors; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.onebusaway.gtfs.model.AgencyAndId; -import org.onebusaway.gtfs.model.Area; -import org.onebusaway.gtfs.model.Location; -import org.onebusaway.gtfs.model.Stop; -import org.onebusaway.gtfs.model.StopArea; -import org.opentripplanner._support.geometry.Polygons; -import org.opentripplanner.transit.service.StopModel; - -class StopAreaMapperTest { - - private static final String NAME = "Floxjam"; - private static final AgencyAndId AREA_ID = agencyAndId("flox"); - - @Test - void map() { - var stopModel = StopModel.of(); - var stopMapper = new StopMapper(new TranslationHelper(), ignored -> null, stopModel); - var locationMapper = new LocationMapper(stopModel, NOOP); - var mapper = new StopAreaMapper(stopMapper, locationMapper, stopModel); - - var area = new Area(); - area.setId(AREA_ID); - area.setName(NAME); - - var stop1 = stop("stop1"); - var stop2 = stop("stop2"); - var location = location("location"); - - var stopArea = new StopArea(); - stopArea.setArea(area); - stopArea.addLocation(stop1); - stopArea.addLocation(stop2); - stopArea.addLocation(location); - var areaStop = mapper.map(stopArea); - - assertEquals(NAME, areaStop.getName().toString()); - var stopIds = areaStop - .getChildLocations() - .stream() - .map(l -> l.getId().toString()) - .collect(Collectors.toSet()); - assertEquals(Set.of("1:location", "1:stop1", "1:stop2"), stopIds); - } - - private static Stop stop(String id) { - var stop = new Stop(); - stop.setId(agencyAndId(id)); - stop.setLat(1); - stop.setLon(2); - stop.setName("A stop"); - return stop; - } - - private static Location location(String id) { - var stop = new Location(); - stop.setId(agencyAndId(id)); - stop.setName("A stop"); - stop.setGeometry(Polygons.toGeoJson(Polygons.BERLIN)); - return stop; - } - - @Nonnull - private static AgencyAndId agencyAndId(String id) { - return new AgencyAndId("1", id); - } -} diff --git a/src/test/java/org/opentripplanner/gtfs/mapping/StopTimeMapperTest.java b/src/test/java/org/opentripplanner/gtfs/mapping/StopTimeMapperTest.java index e45f7a8ff0d..d17c22c4abe 100644 --- a/src/test/java/org/opentripplanner/gtfs/mapping/StopTimeMapperTest.java +++ b/src/test/java/org/opentripplanner/gtfs/mapping/StopTimeMapperTest.java @@ -85,17 +85,11 @@ public class StopTimeMapperTest { locationMapper, stopModelBuilder ); - private final StopAreaMapper stopAreaMapper = new StopAreaMapper( - stopMapper, - locationMapper, - stopModelBuilder - ); private final TranslationHelper translationHelper = new TranslationHelper(); private final StopTimeMapper subject = new StopTimeMapper( stopMapper, locationMapper, locationGroupMapper, - stopAreaMapper, new TripMapper( new RouteMapper(new AgencyMapper(FEED_ID), ISSUE_STORE, translationHelper), new DirectionMapper(ISSUE_STORE), diff --git a/src/test/java/org/opentripplanner/gtfs/mapping/TransferMapperTest.java b/src/test/java/org/opentripplanner/gtfs/mapping/TransferMapperTest.java index 3581cca9ac8..3c5a1eff42a 100644 --- a/src/test/java/org/opentripplanner/gtfs/mapping/TransferMapperTest.java +++ b/src/test/java/org/opentripplanner/gtfs/mapping/TransferMapperTest.java @@ -56,11 +56,6 @@ public class TransferMapperTest { LOCATION_MAPPER, STOP_MODEL_BUILDER ); - private static final StopAreaMapper STOP_AREA_MAPPER = new StopAreaMapper( - STOP_MAPPER, - LOCATION_MAPPER, - STOP_MODEL_BUILDER - ); private static StopTimeMapper STOP_TIME_MAPPER; private static final Integer ID = 45; @@ -99,7 +94,6 @@ void prepare() { STOP_MAPPER, LOCATION_MAPPER, LOCATION_GROUP_MAPPER, - STOP_AREA_MAPPER, new TripMapper( new RouteMapper(new AgencyMapper(FEED_ID), ISSUE_STORE, TRANSLATION_HELPER), new DirectionMapper(ISSUE_STORE), From 1468498146e6b542457f2829cbcb061f20cbce09 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 18 Sep 2024 15:10:13 +0200 Subject: [PATCH 25/80] Fix remaining mentions of /docs --- doc/templates/BuildConfiguration.md | 2 +- doc/templates/Configuration.md | 2 +- doc/templates/GraphQL-Tutorial.md | 2 +- doc/templates/RouteRequest.md | 2 +- doc/templates/RouterConfiguration.md | 2 +- doc/templates/StopConsolidation.md | 2 +- doc/templates/UpdaterConfig.md | 2 +- doc/user/BuildConfiguration.md | 2 +- doc/user/Configuration.md | 2 +- doc/user/Developers-Guide.md | 4 ++-- doc/user/RouteRequest.md | 2 +- doc/user/RouterConfiguration.md | 2 +- doc/user/UpdaterConfig.md | 2 +- doc/user/apis/GraphQL-Tutorial.md | 2 +- doc/user/sandbox/StopConsolidation.md | 2 +- 15 files changed, 16 insertions(+), 16 deletions(-) diff --git a/doc/templates/BuildConfiguration.md b/doc/templates/BuildConfiguration.md index 7e768e30eb1..da18d8e4420 100644 --- a/doc/templates/BuildConfiguration.md +++ b/doc/templates/BuildConfiguration.md @@ -2,7 +2,7 @@ NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc. - Template directory is: /doc/templates - - Generated directory is: /docs + - Generated directory is: /docs/user --> # Graph Build Configuration diff --git a/doc/templates/Configuration.md b/doc/templates/Configuration.md index 6f6f4fc9960..6565323ba8c 100644 --- a/doc/templates/Configuration.md +++ b/doc/templates/Configuration.md @@ -2,7 +2,7 @@ NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc. - Template directory is: /doc/templates - - Generated directory is: /docs + - Generated directory is: /docs/user --> # Configuring OpenTripPlanner diff --git a/doc/templates/GraphQL-Tutorial.md b/doc/templates/GraphQL-Tutorial.md index 11a2e304119..804abb82638 100644 --- a/doc/templates/GraphQL-Tutorial.md +++ b/doc/templates/GraphQL-Tutorial.md @@ -2,7 +2,7 @@ NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc. - Template directory is: /doc/templates - - Generated directory is: /docs + - Generated directory is: /docs/user --> # GraphQL tutorial diff --git a/doc/templates/RouteRequest.md b/doc/templates/RouteRequest.md index a452e1d1480..85328cf53ab 100644 --- a/doc/templates/RouteRequest.md +++ b/doc/templates/RouteRequest.md @@ -2,7 +2,7 @@ NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc. - Template directory is: /doc/templates - - Generated directory is: /docs + - Generated directory is: /docs/user --> # Route Request diff --git a/doc/templates/RouterConfiguration.md b/doc/templates/RouterConfiguration.md index b6c6ccf9c4b..aba66ac6c4e 100644 --- a/doc/templates/RouterConfiguration.md +++ b/doc/templates/RouterConfiguration.md @@ -2,7 +2,7 @@ NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc. - Template directory is: /doc/templates - - Generated directory is: /docs + - Generated directory is: /docs/user --> # Router configuration diff --git a/doc/templates/StopConsolidation.md b/doc/templates/StopConsolidation.md index 6817ee47d4c..ab66bc609a7 100644 --- a/doc/templates/StopConsolidation.md +++ b/doc/templates/StopConsolidation.md @@ -2,7 +2,7 @@ NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc. - Template directory is: /doc/templates - - Generated directory is: /docs + - Generated directory is: /docs/user --> # Stop consolidation diff --git a/doc/templates/UpdaterConfig.md b/doc/templates/UpdaterConfig.md index 440cd96f733..1d611625c2c 100644 --- a/doc/templates/UpdaterConfig.md +++ b/doc/templates/UpdaterConfig.md @@ -2,7 +2,7 @@ NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc. - Template directory is: /doc/templates - - Generated directory is: /docs + - Generated directory is: /docs/user --> diff --git a/doc/user/BuildConfiguration.md b/doc/user/BuildConfiguration.md index b311991120e..74f5f1ccfc0 100644 --- a/doc/user/BuildConfiguration.md +++ b/doc/user/BuildConfiguration.md @@ -2,7 +2,7 @@ NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc. - Template directory is: /doc/templates - - Generated directory is: /docs + - Generated directory is: /docs/user --> # Graph Build Configuration diff --git a/doc/user/Configuration.md b/doc/user/Configuration.md index 832784d977b..a97b285650f 100644 --- a/doc/user/Configuration.md +++ b/doc/user/Configuration.md @@ -2,7 +2,7 @@ NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc. - Template directory is: /doc/templates - - Generated directory is: /docs + - Generated directory is: /docs/user --> # Configuring OpenTripPlanner diff --git a/doc/user/Developers-Guide.md b/doc/user/Developers-Guide.md index 368a12edb62..59bcf1991eb 100644 --- a/doc/user/Developers-Guide.md +++ b/doc/user/Developers-Guide.md @@ -56,7 +56,7 @@ There are several ways to get involved: * Join the [Gitter chat room](https://gitter.im/opentripplanner/OpenTripPlanner) and the [user mailing list](http://groups.google.com/group/opentripplanner-users). -* Fix typos and improve the documentation within the `/docs` directory of the project (details +* Fix typos and improve the documentation within the `/docs/user` directory of the project (details below). * [File a bug or new feature request](http://github.com/openplans/OpenTripPlanner/issues/new). @@ -133,7 +133,7 @@ control to be applied to documentation as well as program source code. All pull how OTP is used or configured should include changes to the documentation alongside code modifications. -The documentation files are in Markdown format and are in the `/docs` directory under the root of +The documentation files are in Markdown format and are in the `/docs/user` directory under the root of the project. On every push to the `dev-2.x` branch the documentation will be rebuilt and deployed as static pages to our subdomain of [Github Pages](https://github.com/opentripplanner/docs). MkDocs is a Python program and should run on any major platform. diff --git a/doc/user/RouteRequest.md b/doc/user/RouteRequest.md index 674ab238888..0e4bfa5178f 100644 --- a/doc/user/RouteRequest.md +++ b/doc/user/RouteRequest.md @@ -2,7 +2,7 @@ NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc. - Template directory is: /doc/templates - - Generated directory is: /docs + - Generated directory is: /docs/user --> # Route Request diff --git a/doc/user/RouterConfiguration.md b/doc/user/RouterConfiguration.md index 4e565cfe17d..1a917716bb5 100644 --- a/doc/user/RouterConfiguration.md +++ b/doc/user/RouterConfiguration.md @@ -2,7 +2,7 @@ NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc. - Template directory is: /doc/templates - - Generated directory is: /docs + - Generated directory is: /docs/user --> # Router configuration diff --git a/doc/user/UpdaterConfig.md b/doc/user/UpdaterConfig.md index f3a0d982e68..0bed51c097e 100644 --- a/doc/user/UpdaterConfig.md +++ b/doc/user/UpdaterConfig.md @@ -2,7 +2,7 @@ NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc. - Template directory is: /doc/templates - - Generated directory is: /docs + - Generated directory is: /docs/user --> diff --git a/doc/user/apis/GraphQL-Tutorial.md b/doc/user/apis/GraphQL-Tutorial.md index d65fbc144ba..663f3064c18 100644 --- a/doc/user/apis/GraphQL-Tutorial.md +++ b/doc/user/apis/GraphQL-Tutorial.md @@ -2,7 +2,7 @@ NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc. - Template directory is: /doc/templates - - Generated directory is: /docs + - Generated directory is: /docs/user --> # GraphQL tutorial diff --git a/doc/user/sandbox/StopConsolidation.md b/doc/user/sandbox/StopConsolidation.md index b36429b1f60..0c3d3f234f6 100644 --- a/doc/user/sandbox/StopConsolidation.md +++ b/doc/user/sandbox/StopConsolidation.md @@ -2,7 +2,7 @@ NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc. - Template directory is: /doc/templates - - Generated directory is: /docs + - Generated directory is: /docs/user --> # Stop consolidation From 3fe01614a71fa4927c65a7adb337395ceb0404af Mon Sep 17 00:00:00 2001 From: JustCris Date: Wed, 18 Sep 2024 15:25:09 +0200 Subject: [PATCH 26/80] fix /docs/user mentions --- doc/templates/BuildConfiguration.md | 2 +- doc/templates/Configuration.md | 2 +- doc/templates/GraphQL-Tutorial.md | 2 +- doc/templates/RouteRequest.md | 2 +- doc/templates/RouterConfiguration.md | 2 +- doc/templates/StopConsolidation.md | 2 +- doc/templates/UpdaterConfig.md | 2 +- doc/user/BuildConfiguration.md | 2 +- doc/user/Configuration.md | 2 +- doc/user/Developers-Guide.md | 4 ++-- doc/user/RouteRequest.md | 2 +- doc/user/RouterConfiguration.md | 2 +- doc/user/UpdaterConfig.md | 2 +- doc/user/apis/GraphQL-Tutorial.md | 2 +- doc/user/sandbox/StopConsolidation.md | 2 +- 15 files changed, 16 insertions(+), 16 deletions(-) diff --git a/doc/templates/BuildConfiguration.md b/doc/templates/BuildConfiguration.md index da18d8e4420..77b03dae500 100644 --- a/doc/templates/BuildConfiguration.md +++ b/doc/templates/BuildConfiguration.md @@ -2,7 +2,7 @@ NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc. - Template directory is: /doc/templates - - Generated directory is: /docs/user + - Generated directory is: /doc/user --> # Graph Build Configuration diff --git a/doc/templates/Configuration.md b/doc/templates/Configuration.md index 6565323ba8c..45b2c36c67b 100644 --- a/doc/templates/Configuration.md +++ b/doc/templates/Configuration.md @@ -2,7 +2,7 @@ NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc. - Template directory is: /doc/templates - - Generated directory is: /docs/user + - Generated directory is: /doc/user --> # Configuring OpenTripPlanner diff --git a/doc/templates/GraphQL-Tutorial.md b/doc/templates/GraphQL-Tutorial.md index 804abb82638..2a78be65cc2 100644 --- a/doc/templates/GraphQL-Tutorial.md +++ b/doc/templates/GraphQL-Tutorial.md @@ -2,7 +2,7 @@ NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc. - Template directory is: /doc/templates - - Generated directory is: /docs/user + - Generated directory is: /doc/user --> # GraphQL tutorial diff --git a/doc/templates/RouteRequest.md b/doc/templates/RouteRequest.md index 85328cf53ab..9b7cd6fd58f 100644 --- a/doc/templates/RouteRequest.md +++ b/doc/templates/RouteRequest.md @@ -2,7 +2,7 @@ NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc. - Template directory is: /doc/templates - - Generated directory is: /docs/user + - Generated directory is: /doc/user --> # Route Request diff --git a/doc/templates/RouterConfiguration.md b/doc/templates/RouterConfiguration.md index aba66ac6c4e..87e4c1693cc 100644 --- a/doc/templates/RouterConfiguration.md +++ b/doc/templates/RouterConfiguration.md @@ -2,7 +2,7 @@ NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc. - Template directory is: /doc/templates - - Generated directory is: /docs/user + - Generated directory is: /doc/user --> # Router configuration diff --git a/doc/templates/StopConsolidation.md b/doc/templates/StopConsolidation.md index ab66bc609a7..70866882bd1 100644 --- a/doc/templates/StopConsolidation.md +++ b/doc/templates/StopConsolidation.md @@ -2,7 +2,7 @@ NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc. - Template directory is: /doc/templates - - Generated directory is: /docs/user + - Generated directory is: /doc/user --> # Stop consolidation diff --git a/doc/templates/UpdaterConfig.md b/doc/templates/UpdaterConfig.md index 1d611625c2c..aab5631e6e2 100644 --- a/doc/templates/UpdaterConfig.md +++ b/doc/templates/UpdaterConfig.md @@ -2,7 +2,7 @@ NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc. - Template directory is: /doc/templates - - Generated directory is: /docs/user + - Generated directory is: /doc/user --> diff --git a/doc/user/BuildConfiguration.md b/doc/user/BuildConfiguration.md index 74f5f1ccfc0..0bf4a3c29ec 100644 --- a/doc/user/BuildConfiguration.md +++ b/doc/user/BuildConfiguration.md @@ -2,7 +2,7 @@ NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc. - Template directory is: /doc/templates - - Generated directory is: /docs/user + - Generated directory is: /doc/user --> # Graph Build Configuration diff --git a/doc/user/Configuration.md b/doc/user/Configuration.md index a97b285650f..98c8e424660 100644 --- a/doc/user/Configuration.md +++ b/doc/user/Configuration.md @@ -2,7 +2,7 @@ NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc. - Template directory is: /doc/templates - - Generated directory is: /docs/user + - Generated directory is: /doc/user --> # Configuring OpenTripPlanner diff --git a/doc/user/Developers-Guide.md b/doc/user/Developers-Guide.md index 59bcf1991eb..1b93e76b65a 100644 --- a/doc/user/Developers-Guide.md +++ b/doc/user/Developers-Guide.md @@ -56,7 +56,7 @@ There are several ways to get involved: * Join the [Gitter chat room](https://gitter.im/opentripplanner/OpenTripPlanner) and the [user mailing list](http://groups.google.com/group/opentripplanner-users). -* Fix typos and improve the documentation within the `/docs/user` directory of the project (details +* Fix typos and improve the documentation within the `/doc/user` directory of the project (details below). * [File a bug or new feature request](http://github.com/openplans/OpenTripPlanner/issues/new). @@ -133,7 +133,7 @@ control to be applied to documentation as well as program source code. All pull how OTP is used or configured should include changes to the documentation alongside code modifications. -The documentation files are in Markdown format and are in the `/docs/user` directory under the root of +The documentation files are in Markdown format and are in the `/doc/user` directory under the root of the project. On every push to the `dev-2.x` branch the documentation will be rebuilt and deployed as static pages to our subdomain of [Github Pages](https://github.com/opentripplanner/docs). MkDocs is a Python program and should run on any major platform. diff --git a/doc/user/RouteRequest.md b/doc/user/RouteRequest.md index 0e4bfa5178f..c00502b2726 100644 --- a/doc/user/RouteRequest.md +++ b/doc/user/RouteRequest.md @@ -2,7 +2,7 @@ NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc. - Template directory is: /doc/templates - - Generated directory is: /docs/user + - Generated directory is: /doc/user --> # Route Request diff --git a/doc/user/RouterConfiguration.md b/doc/user/RouterConfiguration.md index 1a917716bb5..2c42bea6d74 100644 --- a/doc/user/RouterConfiguration.md +++ b/doc/user/RouterConfiguration.md @@ -2,7 +2,7 @@ NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc. - Template directory is: /doc/templates - - Generated directory is: /docs/user + - Generated directory is: /doc/user --> # Router configuration diff --git a/doc/user/UpdaterConfig.md b/doc/user/UpdaterConfig.md index 0bed51c097e..3d03cb0a5ae 100644 --- a/doc/user/UpdaterConfig.md +++ b/doc/user/UpdaterConfig.md @@ -2,7 +2,7 @@ NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc. - Template directory is: /doc/templates - - Generated directory is: /docs/user + - Generated directory is: /doc/user --> diff --git a/doc/user/apis/GraphQL-Tutorial.md b/doc/user/apis/GraphQL-Tutorial.md index 663f3064c18..3d365de5862 100644 --- a/doc/user/apis/GraphQL-Tutorial.md +++ b/doc/user/apis/GraphQL-Tutorial.md @@ -2,7 +2,7 @@ NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc. - Template directory is: /doc/templates - - Generated directory is: /docs/user + - Generated directory is: /doc/user --> # GraphQL tutorial diff --git a/doc/user/sandbox/StopConsolidation.md b/doc/user/sandbox/StopConsolidation.md index 0c3d3f234f6..d0e18a9ce30 100644 --- a/doc/user/sandbox/StopConsolidation.md +++ b/doc/user/sandbox/StopConsolidation.md @@ -2,7 +2,7 @@ NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc. - Template directory is: /doc/templates - - Generated directory is: /docs/user + - Generated directory is: /doc/user --> # Stop consolidation From 3fe686bfaf5006767f85ce2e013b4f35b4fa9a00 Mon Sep 17 00:00:00 2001 From: JustCris Date: Wed, 18 Sep 2024 15:30:42 +0200 Subject: [PATCH 27/80] fix docs mentions in function comments --- .../generate/doc/framework/DocsTestConstants.java | 4 ++-- .../generate/doc/framework/GeneratesDocumentation.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/opentripplanner/generate/doc/framework/DocsTestConstants.java b/src/test/java/org/opentripplanner/generate/doc/framework/DocsTestConstants.java index a7f7afa806a..6c88488c281 100644 --- a/src/test/java/org/opentripplanner/generate/doc/framework/DocsTestConstants.java +++ b/src/test/java/org/opentripplanner/generate/doc/framework/DocsTestConstants.java @@ -14,7 +14,7 @@ public interface DocsTestConstants { File USER_DOC_PATH = new File(DOC_ROOT, "user"); /** - * This method return {@code true} if the /docs directory is available. If not, a warning is + * This method return {@code true} if the /doc directory is available. If not, a warning is * logged and the method returns {@code false}. This is used by the {@link GeneratesDocumentation} * annotation. */ @@ -24,7 +24,7 @@ static boolean docsExistOrWarn() { } LOG.warn( """ - SKIP TEST - '/docs' NOT FOUND + SKIP TEST - '/doc' NOT FOUND The doc/templates directory might not be available if you run the tests outside the root of the projects. This may happen if the project root is not the working directory, diff --git a/src/test/java/org/opentripplanner/generate/doc/framework/GeneratesDocumentation.java b/src/test/java/org/opentripplanner/generate/doc/framework/GeneratesDocumentation.java index d8ce7a529d2..2dc099b2a0b 100644 --- a/src/test/java/org/opentripplanner/generate/doc/framework/GeneratesDocumentation.java +++ b/src/test/java/org/opentripplanner/generate/doc/framework/GeneratesDocumentation.java @@ -8,7 +8,7 @@ import org.junit.jupiter.api.condition.EnabledIf; /** - * Use this annotation on tests that generate(access) the /docs directory outside the + * Use this annotation on tests that generate(access) the /doc directory outside the * source/resource. *

* All tests annotated with this annotation is tagged with "docs". You may include or exclude From 882730a506af90aa1afb68e97918108a54b56fe7 Mon Sep 17 00:00:00 2001 From: JustCris Date: Wed, 18 Sep 2024 17:28:35 +0200 Subject: [PATCH 28/80] fix documentation bash command doc folder --- doc/user/Developers-Guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user/Developers-Guide.md b/doc/user/Developers-Guide.md index 1b93e76b65a..ee0fe1eab9a 100644 --- a/doc/user/Developers-Guide.md +++ b/doc/user/Developers-Guide.md @@ -143,7 +143,7 @@ how to generate a live local preview of the documentation while you're writing i In short: ``` -$ pip install -r docs/requirements.txt +$ pip install -r doc/user/requirements.txt $ mkdocs serve ``` From 1cff559fe604db9f9ee094d240b22ca003c654f6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 19 Sep 2024 16:52:27 +0000 Subject: [PATCH 29/80] Update dependency org.apache.httpcomponents.client5:httpclient5 to v5.4 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3bd2e2ec71f..390ee6ad697 100644 --- a/pom.xml +++ b/pom.xml @@ -858,7 +858,7 @@ org.apache.httpcomponents.client5 httpclient5 - 5.3.1 + 5.4 commons-cli From c61819a200ed8ee4cb6b9bee8cc70a1a786e178c Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 19 Sep 2024 23:23:14 +0200 Subject: [PATCH 30/80] Return transit model timezone in server info --- .../api/model/serverinfo/ApiServerInfo.java | 5 +++- .../api/resource/ServerInfo.java | 30 +++++++++++++------ 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/opentripplanner/api/model/serverinfo/ApiServerInfo.java b/src/main/java/org/opentripplanner/api/model/serverinfo/ApiServerInfo.java index e90c0e37fdf..e4b42d48e59 100644 --- a/src/main/java/org/opentripplanner/api/model/serverinfo/ApiServerInfo.java +++ b/src/main/java/org/opentripplanner/api/model/serverinfo/ApiServerInfo.java @@ -1,5 +1,6 @@ package org.opentripplanner.api.model.serverinfo; +import java.time.ZoneId; import org.opentripplanner.model.projectinfo.OtpProjectInfo; public class ApiServerInfo { @@ -10,13 +11,15 @@ public class ApiServerInfo { public final ApiVersionControlInfo versionControl; public final ApiConfigInfo config; public final String otpSerializationVersionId; + public final String transitTimeZone; - public ApiServerInfo(String cpuName, int nCores, OtpProjectInfo projectInfo) { + public ApiServerInfo(String cpuName, int nCores, OtpProjectInfo projectInfo, ZoneId transitTimeZone) { this.cpuName = cpuName; this.nCores = nCores; this.version = new ApiProjectVersion(projectInfo.version); this.versionControl = new ApiVersionControlInfo(projectInfo.versionControl); this.config = new ApiConfigInfo(projectInfo); this.otpSerializationVersionId = projectInfo.getOtpSerializationVersionId(); + this.transitTimeZone = transitTimeZone.toString(); } } diff --git a/src/main/java/org/opentripplanner/api/resource/ServerInfo.java b/src/main/java/org/opentripplanner/api/resource/ServerInfo.java index bc8d91d4780..c11d9c8158f 100644 --- a/src/main/java/org/opentripplanner/api/resource/ServerInfo.java +++ b/src/main/java/org/opentripplanner/api/resource/ServerInfo.java @@ -3,19 +3,29 @@ import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; +import java.time.ZoneId; import org.opentripplanner.api.model.serverinfo.ApiServerInfo; import org.opentripplanner.model.projectinfo.OtpProjectInfo; +import org.opentripplanner.standalone.api.OtpServerRequestContext; @Path("/") public class ServerInfo { - private static final ApiServerInfo SERVER_INFO = createServerInfo(); + private final ZoneId timeZone; + private static final String cpuName; + private static final int nCores ; + + public ServerInfo(@Context OtpServerRequestContext serverContext) { + this.timeZone = serverContext.transitService().getTimeZone(); + } + /** * Determine the OTP version and CPU type of the running server. This information should not @@ -23,28 +33,30 @@ public class ServerInfo { * available before the graph is loaded, so for this to work this class should not be loaded * BEFORE that. */ - public static ApiServerInfo createServerInfo() { - String cpuName = "unknown"; - int nCores = 0; + static { + var cpu = "unknown"; + int cores = 0; try { InputStream fis = new FileInputStream("/proc/cpuinfo"); BufferedReader br = new BufferedReader(new InputStreamReader(fis, StandardCharsets.UTF_8)); String line; while ((line = br.readLine()) != null) { if (line.startsWith("model name")) { - cpuName = line.split(": ")[1]; - nCores += 1; + cpu = line.split(": ")[1]; + cores+= 1; } } fis.close(); } catch (Exception ignore) {} - - return new ApiServerInfo(cpuName, nCores, OtpProjectInfo.projectInfo()); + cpuName = cpu; + nCores = cores; } + + @GET @Produces(MediaType.APPLICATION_JSON) public ApiServerInfo getServerInfo() { - return SERVER_INFO; + return new ApiServerInfo(cpuName, nCores, OtpProjectInfo.projectInfo(), timeZone); } } From d6f1b806c687f07d159654ab67320fa2bc7cc068 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 20 Sep 2024 09:42:35 +0200 Subject: [PATCH 31/80] Use react to fetch time zone --- client/.env | 1 + client/.env.development | 3 ++- client/src/hooks/useTimeZone.ts | 19 +++++++++++++++++++ client/src/util/formatTime.ts | 3 +++ 4 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 client/src/hooks/useTimeZone.ts diff --git a/client/.env b/client/.env index 2f5d271b1e4..a4fbf511e26 100644 --- a/client/.env +++ b/client/.env @@ -1,3 +1,4 @@ VITE_API_URL=/otp/transmodel/v3 VITE_DEBUG_STYLE_URL=/otp/routers/default/inspector/vectortile/style.json VITE_GRAPHIQL_URL=/graphiql?flavor=transmodel +VITE_SERVER_INFO_URL=/otp/ diff --git a/client/.env.development b/client/.env.development index 1cb7d9235e3..70ad81eb7dd 100644 --- a/client/.env.development +++ b/client/.env.development @@ -1,3 +1,4 @@ VITE_API_URL=http://localhost:8080/otp/transmodel/v3 VITE_DEBUG_STYLE_URL=http://localhost:8080/otp/routers/default/inspector/vectortile/style.json -VITE_GRAPHIQL_URL=http://localhost:8080/graphiql?flavor=transmodel \ No newline at end of file +VITE_GRAPHIQL_URL=http://localhost:8080/graphiql?flavor=transmodel +VITE_SERVER_INFO_URL=http://localhost:8080/otp/ diff --git a/client/src/hooks/useTimeZone.ts b/client/src/hooks/useTimeZone.ts new file mode 100644 index 00000000000..36ab9d9d12a --- /dev/null +++ b/client/src/hooks/useTimeZone.ts @@ -0,0 +1,19 @@ +import { useEffect, useState } from 'react'; + +type ServerInfo = { + transitTimeZone: string; +}; + +const fetchServerInfo = (): Promise => fetch('http://localhost:8080/otp/').then((r) => r.json()); + +export const useTimeZone = () => { + const [data, setData] = useState(null); + useEffect(() => { + const fetchData = async () => { + setData(await fetchServerInfo()); + }; + fetchData(); + }, []); + + return data?.transitTimeZone || Intl.DateTimeFormat().resolvedOptions().timeZone; +}; diff --git a/client/src/util/formatTime.ts b/client/src/util/formatTime.ts index 1849640fe3f..1b14ab103c6 100644 --- a/client/src/util/formatTime.ts +++ b/client/src/util/formatTime.ts @@ -1,3 +1,5 @@ +import {useTimeZone} from "../hooks/useTimeZone.ts"; + /** * Format departure and arrival times from scalar dateTime strings * @@ -9,5 +11,6 @@ export function formatTime(dateTime: string, style?: 'short' | 'medium') { return parsed.toLocaleTimeString('en-US', { timeStyle: style ? style : parsed.getSeconds() === 0 ? 'short' : 'medium', hourCycle: 'h24', + timeZone: useTimeZone() }); } From 719dde7687c59e141834a6cbd0daed5aafc2d1f1 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 23 Sep 2024 14:22:34 +0200 Subject: [PATCH 32/80] Use polyfill, combine datetime into a single input --- client/package-lock.json | 18 +++++++++ client/package.json | 1 + .../components/SearchBar/DateInputField.tsx | 40 ------------------- client/src/components/SearchBar/SearchBar.tsx | 2 - .../components/SearchBar/TimeInputField.tsx | 24 +++++++---- client/src/hooks/useTimeZone.ts | 17 +------- 6 files changed, 37 insertions(+), 65 deletions(-) delete mode 100644 client/src/components/SearchBar/DateInputField.tsx diff --git a/client/package-lock.json b/client/package-lock.json index 5619317e0bd..00490ce16d6 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "@googlemaps/polyline-codec": "1.0.28", + "@js-temporal/polyfill": "0.4.4", "bootstrap": "5.3.3", "graphql": "16.9.0", "graphql-request": "7.1.0", @@ -2958,6 +2959,18 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@js-temporal/polyfill": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@js-temporal/polyfill/-/polyfill-0.4.4.tgz", + "integrity": "sha512-2X6bvghJ/JAoZO52lbgyAPFj8uCflhTo2g7nkFzEQdXd/D8rEeD4HtmTEpmtGCva260fcd66YNXBOYdnmHqSOg==", + "dependencies": { + "jsbi": "^4.3.0", + "tslib": "^2.4.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@kamilkisiela/fast-url-parser": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@kamilkisiela/fast-url-parser/-/fast-url-parser-1.1.4.tgz", @@ -8269,6 +8282,11 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbi": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/jsbi/-/jsbi-4.3.0.tgz", + "integrity": "sha512-SnZNcinB4RIcnEyZqFPdGPVgrg2AcnykiBy0sHVJQKHYeaLUvi3Exj+iaPpLnFVkDPZIV4U0yvgC9/R4uEAZ9g==" + }, "node_modules/jsdom": { "version": "25.0.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.0.tgz", diff --git a/client/package.json b/client/package.json index 2cd0d1f8937..0e822b4a641 100644 --- a/client/package.json +++ b/client/package.json @@ -18,6 +18,7 @@ }, "dependencies": { "@googlemaps/polyline-codec": "1.0.28", + "@js-temporal/polyfill": "0.4.4", "bootstrap": "5.3.3", "graphql": "16.9.0", "graphql-request": "7.1.0", diff --git a/client/src/components/SearchBar/DateInputField.tsx b/client/src/components/SearchBar/DateInputField.tsx deleted file mode 100644 index 0b0eca38869..00000000000 --- a/client/src/components/SearchBar/DateInputField.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { Form } from 'react-bootstrap'; -import { TripQueryVariables } from '../../gql/graphql.ts'; -import { ChangeEvent, useCallback, useMemo } from 'react'; - -export function DateInputField({ - tripQueryVariables, - setTripQueryVariables, -}: { - tripQueryVariables: TripQueryVariables; - setTripQueryVariables: (tripQueryVariables: TripQueryVariables) => void; -}) { - const current = useMemo( - () => new Date(tripQueryVariables.dateTime).toISOString().split('T')[0], - [tripQueryVariables.dateTime], - ); - - const onChange = useCallback( - (event: ChangeEvent) => { - const oldDate = new Date(tripQueryVariables.dateTime); - const newDate = new Date(event.target.value); - - newDate.setHours(oldDate.getHours(), oldDate.getMinutes(), oldDate.getSeconds()); - - setTripQueryVariables({ - ...tripQueryVariables, - dateTime: newDate.toISOString(), - }); - }, - [tripQueryVariables, setTripQueryVariables], - ); - - return ( - - - Date - - - - ); -} diff --git a/client/src/components/SearchBar/SearchBar.tsx b/client/src/components/SearchBar/SearchBar.tsx index dfcbc6ac36e..bb6b47abc3f 100644 --- a/client/src/components/SearchBar/SearchBar.tsx +++ b/client/src/components/SearchBar/SearchBar.tsx @@ -3,7 +3,6 @@ import { ServerInfo, TripQueryVariables } from '../../gql/graphql.ts'; import { LocationInputField } from './LocationInputField.tsx'; import { DepartureArrivalSelect } from './DepartureArrivalSelect.tsx'; import { TimeInputField } from './TimeInputField.tsx'; -import { DateInputField } from './DateInputField.tsx'; import { SearchWindowInput } from './SearchWindowInput.tsx'; import { AccessSelect } from './AccessSelect.tsx'; import { EgressSelect } from './EgressSelect.tsx'; @@ -41,7 +40,6 @@ export function SearchBar({ onRoute, tripQueryVariables, setTripQueryVariables, - diff --git a/client/src/components/SearchBar/TimeInputField.tsx b/client/src/components/SearchBar/TimeInputField.tsx index 71bb7325340..af2fcfd150c 100644 --- a/client/src/components/SearchBar/TimeInputField.tsx +++ b/client/src/components/SearchBar/TimeInputField.tsx @@ -1,6 +1,8 @@ import { Form } from 'react-bootstrap'; import { TripQueryVariables } from '../../gql/graphql.ts'; import { ChangeEvent, useCallback, useMemo } from 'react'; +import { Temporal } from '@js-temporal/polyfill'; +import { useTimeZone } from '../../hooks/useTimeZone.ts'; export function TimeInputField({ tripQueryVariables, @@ -10,19 +12,27 @@ export function TimeInputField({ setTripQueryVariables: (tripQueryVariables: TripQueryVariables) => void; }) { const current = useMemo( - () => new Date(tripQueryVariables.dateTime).toTimeString().split(' ')[0], + () => + Temporal.Instant.from(tripQueryVariables.dateTime) + .toZonedDateTime({ + calendar: 'gregory', + timeZone: useTimeZone(), + }) + .toPlainDateTime() + .toString({ smallestUnit: 'minute', calendarName: 'never' }), [tripQueryVariables.dateTime], ); const onChange = useCallback( (event: ChangeEvent) => { - const timeComponents = event.target.value.split(':'); - const newDate = new Date(tripQueryVariables.dateTime); - newDate.setHours(Number(timeComponents[0]), Number(timeComponents[1]), Number(timeComponents[2])); + const dateTime = Temporal.PlainDateTime.from(event.target.value) + .toZonedDateTime(useTimeZone()) + .toString({ calendarName: 'never', timeZoneName: 'never' }); + console.log(dateTime); setTripQueryVariables({ ...tripQueryVariables, - dateTime: newDate.toISOString(), + dateTime: dateTime, }); }, [tripQueryVariables, setTripQueryVariables], @@ -31,9 +41,9 @@ export function TimeInputField({ return ( - Time + Time ({useTimeZone()}) - + ); } diff --git a/client/src/hooks/useTimeZone.ts b/client/src/hooks/useTimeZone.ts index 36ab9d9d12a..b88eb0afb42 100644 --- a/client/src/hooks/useTimeZone.ts +++ b/client/src/hooks/useTimeZone.ts @@ -1,19 +1,4 @@ -import { useEffect, useState } from 'react'; - -type ServerInfo = { - transitTimeZone: string; -}; - -const fetchServerInfo = (): Promise => fetch('http://localhost:8080/otp/').then((r) => r.json()); - export const useTimeZone = () => { - const [data, setData] = useState(null); - useEffect(() => { - const fetchData = async () => { - setData(await fetchServerInfo()); - }; - fetchData(); - }, []); - return data?.transitTimeZone || Intl.DateTimeFormat().resolvedOptions().timeZone; + return "America/Los_Angeles" }; From e75409dd34b06d9af4bbb759263d75464910e5c6 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 23 Sep 2024 14:24:49 +0200 Subject: [PATCH 33/80] Simplify time zone display --- client/src/components/SearchBar/TimeInputField.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/components/SearchBar/TimeInputField.tsx b/client/src/components/SearchBar/TimeInputField.tsx index af2fcfd150c..8c7db133f91 100644 --- a/client/src/components/SearchBar/TimeInputField.tsx +++ b/client/src/components/SearchBar/TimeInputField.tsx @@ -40,8 +40,8 @@ export function TimeInputField({ return ( - - Time ({useTimeZone()}) + + Time From db17fab64ae5c6e230998e05ec9b70434ecb834b Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 23 Sep 2024 22:39:59 +0200 Subject: [PATCH 34/80] Store time zone in context --- .../ItineraryList/ItineraryHeaderContent.tsx | 9 ++-- .../ItineraryList/ItineraryListContainer.tsx | 6 +++ .../src/components/ItineraryList/LegTime.tsx | 9 ++-- ...eInputField.tsx => DateTimeInputField.tsx} | 17 +++---- client/src/components/SearchBar/SearchBar.tsx | 4 +- client/src/hooks/TimeZoneContext.ts | 3 ++ client/src/hooks/useTimeZone.ts | 17 ++++++- client/src/screens/App.tsx | 47 ++++++++++--------- client/src/style.css | 6 +++ client/src/util/formatTime.ts | 6 +-- 10 files changed, 80 insertions(+), 44 deletions(-) rename client/src/components/SearchBar/{TimeInputField.tsx => DateTimeInputField.tsx} (74%) create mode 100644 client/src/hooks/TimeZoneContext.ts diff --git a/client/src/components/ItineraryList/ItineraryHeaderContent.tsx b/client/src/components/ItineraryList/ItineraryHeaderContent.tsx index cf3ca227b63..0788ca1974e 100644 --- a/client/src/components/ItineraryList/ItineraryHeaderContent.tsx +++ b/client/src/components/ItineraryList/ItineraryHeaderContent.tsx @@ -1,8 +1,9 @@ import { TripPattern } from '../../gql/graphql.ts'; import { TIME_BOX_WIDTH, useHeaderContentStyleCalculations } from './useHeaderContentStyleCalculations.ts'; import { ItineraryHeaderLegContent } from './ItineraryHeaderLegContent.tsx'; -import { useMemo } from 'react'; +import { useContext, useMemo } from 'react'; import { formatTime } from '../../util/formatTime.ts'; +import { TimeZoneContext } from '../../hooks/TimeZoneContext.ts'; export function ItineraryHeaderContent({ tripPattern, @@ -24,13 +25,15 @@ export function ItineraryHeaderContent({ latestEndTime, ); + const timeZone = useContext(TimeZoneContext); + const formattedStartTime = useMemo( - () => formatTime(tripPattern.expectedStartTime, 'short'), + () => formatTime(tripPattern.expectedStartTime, timeZone, 'short'), [tripPattern.expectedStartTime], ); const formattedEndTime = useMemo( - () => formatTime(tripPattern.expectedEndTime, 'short'), + () => formatTime(tripPattern.expectedEndTime, timeZone, 'short'), [tripPattern.expectedEndTime], ); diff --git a/client/src/components/ItineraryList/ItineraryListContainer.tsx b/client/src/components/ItineraryList/ItineraryListContainer.tsx index feaf29aa514..b474d2eb5ec 100644 --- a/client/src/components/ItineraryList/ItineraryListContainer.tsx +++ b/client/src/components/ItineraryList/ItineraryListContainer.tsx @@ -5,6 +5,8 @@ import { ItineraryHeaderContent } from './ItineraryHeaderContent.tsx'; import { useEarliestAndLatestTimes } from './useEarliestAndLatestTimes.ts'; import { ItineraryDetails } from './ItineraryDetails.tsx'; import { ItineraryPaginationControl } from './ItineraryPaginationControl.tsx'; +import { useContext } from 'react'; +import { TimeZoneContext } from '../../hooks/TimeZoneContext.ts'; export function ItineraryListContainer({ tripQueryResult, @@ -21,6 +23,7 @@ export function ItineraryListContainer({ }) { const [earliestStartTime, latestEndTime] = useEarliestAndLatestTimes(tripQueryResult); const { containerRef, containerWidth } = useContainerWidth(); + const timeZone = useContext(TimeZoneContext); return (

@@ -56,6 +59,9 @@ export function ItineraryListContainer({ ))} +
+ All times in {timeZone} +
); } diff --git a/client/src/components/ItineraryList/LegTime.tsx b/client/src/components/ItineraryList/LegTime.tsx index 0a574a7bccc..19d852a97be 100644 --- a/client/src/components/ItineraryList/LegTime.tsx +++ b/client/src/components/ItineraryList/LegTime.tsx @@ -1,4 +1,6 @@ import { formatTime } from '../../util/formatTime.ts'; +import { useContext } from 'react'; +import { TimeZoneContext } from '../../hooks/TimeZoneContext.ts'; export function LegTime({ aimedTime, @@ -9,18 +11,19 @@ export function LegTime({ expectedTime: string; hasRealtime: boolean; }) { + const timeZone = useContext(TimeZoneContext); return aimedTime !== expectedTime ? ( <> - {formatTime(expectedTime, 'short')} + {formatTime(expectedTime, timeZone, 'short')} - {formatTime(aimedTime, 'short')} + {formatTime(aimedTime, timeZone, 'short')} ) : ( - {formatTime(expectedTime, 'short')} + {formatTime(expectedTime, timeZone, 'short')} {hasRealtime && (on time)} ); diff --git a/client/src/components/SearchBar/TimeInputField.tsx b/client/src/components/SearchBar/DateTimeInputField.tsx similarity index 74% rename from client/src/components/SearchBar/TimeInputField.tsx rename to client/src/components/SearchBar/DateTimeInputField.tsx index 8c7db133f91..c7e77ee8f05 100644 --- a/client/src/components/SearchBar/TimeInputField.tsx +++ b/client/src/components/SearchBar/DateTimeInputField.tsx @@ -1,23 +1,21 @@ import { Form } from 'react-bootstrap'; import { TripQueryVariables } from '../../gql/graphql.ts'; -import { ChangeEvent, useCallback, useMemo } from 'react'; +import { ChangeEvent, useCallback, useContext, useMemo } from 'react'; import { Temporal } from '@js-temporal/polyfill'; -import { useTimeZone } from '../../hooks/useTimeZone.ts'; +import { TimeZoneContext } from '../../hooks/TimeZoneContext.ts'; -export function TimeInputField({ +export function DateTimeInputField({ tripQueryVariables, setTripQueryVariables, }: { tripQueryVariables: TripQueryVariables; setTripQueryVariables: (tripQueryVariables: TripQueryVariables) => void; }) { + const timeZone = useContext(TimeZoneContext); const current = useMemo( () => Temporal.Instant.from(tripQueryVariables.dateTime) - .toZonedDateTime({ - calendar: 'gregory', - timeZone: useTimeZone(), - }) + .toZonedDateTimeISO(timeZone) .toPlainDateTime() .toString({ smallestUnit: 'minute', calendarName: 'never' }), [tripQueryVariables.dateTime], @@ -26,10 +24,9 @@ export function TimeInputField({ const onChange = useCallback( (event: ChangeEvent) => { const dateTime = Temporal.PlainDateTime.from(event.target.value) - .toZonedDateTime(useTimeZone()) + .toZonedDateTime(timeZone) .toString({ calendarName: 'never', timeZoneName: 'never' }); - console.log(dateTime); setTripQueryVariables({ ...tripQueryVariables, dateTime: dateTime, @@ -40,7 +37,7 @@ export function TimeInputField({ return ( - + Time diff --git a/client/src/components/SearchBar/SearchBar.tsx b/client/src/components/SearchBar/SearchBar.tsx index bb6b47abc3f..ca072fa5589 100644 --- a/client/src/components/SearchBar/SearchBar.tsx +++ b/client/src/components/SearchBar/SearchBar.tsx @@ -2,7 +2,7 @@ import { Button, Spinner } from 'react-bootstrap'; import { ServerInfo, TripQueryVariables } from '../../gql/graphql.ts'; import { LocationInputField } from './LocationInputField.tsx'; import { DepartureArrivalSelect } from './DepartureArrivalSelect.tsx'; -import { TimeInputField } from './TimeInputField.tsx'; +import { DateTimeInputField } from './DateTimeInputField.tsx'; import { SearchWindowInput } from './SearchWindowInput.tsx'; import { AccessSelect } from './AccessSelect.tsx'; import { EgressSelect } from './EgressSelect.tsx'; @@ -39,7 +39,7 @@ export function SearchBar({ onRoute, tripQueryVariables, setTripQueryVariables, - + diff --git a/client/src/hooks/TimeZoneContext.ts b/client/src/hooks/TimeZoneContext.ts new file mode 100644 index 00000000000..6a40921ebae --- /dev/null +++ b/client/src/hooks/TimeZoneContext.ts @@ -0,0 +1,3 @@ +import { createContext } from 'react'; + +export const TimeZoneContext = createContext('UTC'); diff --git a/client/src/hooks/useTimeZone.ts b/client/src/hooks/useTimeZone.ts index b88eb0afb42..36ab9d9d12a 100644 --- a/client/src/hooks/useTimeZone.ts +++ b/client/src/hooks/useTimeZone.ts @@ -1,4 +1,19 @@ +import { useEffect, useState } from 'react'; + +type ServerInfo = { + transitTimeZone: string; +}; + +const fetchServerInfo = (): Promise => fetch('http://localhost:8080/otp/').then((r) => r.json()); + export const useTimeZone = () => { + const [data, setData] = useState(null); + useEffect(() => { + const fetchData = async () => { + setData(await fetchServerInfo()); + }; + fetchData(); + }, []); - return "America/Los_Angeles" + return data?.transitTimeZone || Intl.DateTimeFormat().resolvedOptions().timeZone; }; diff --git a/client/src/screens/App.tsx b/client/src/screens/App.tsx index 3e5744e5ad6..cb29654a4ff 100644 --- a/client/src/screens/App.tsx +++ b/client/src/screens/App.tsx @@ -6,39 +6,44 @@ import { useState } from 'react'; import { useTripQuery } from '../hooks/useTripQuery.ts'; import { useServerInfo } from '../hooks/useServerInfo.ts'; import { useTripQueryVariables } from '../hooks/useTripQueryVariables.ts'; +import { TimeZoneContext } from '../hooks/TimeZoneContext.ts'; +import { useTimeZone } from '../hooks/useTimeZone.ts'; export function App() { + const serverInfo = useServerInfo(); + const timeZone = useTimeZone(); const { tripQueryVariables, setTripQueryVariables } = useTripQueryVariables(); const [tripQueryResult, loading, callback] = useTripQuery(tripQueryVariables); - const serverInfo = useServerInfo(); const [selectedTripPatternIndex, setSelectedTripPatternIndex] = useState(0); return (
- - - - - + - + + + + + +
); } diff --git a/client/src/style.css b/client/src/style.css index 1a24ac2c072..76b431510ad 100644 --- a/client/src/style.css +++ b/client/src/style.css @@ -60,6 +60,12 @@ overflow-y: auto; } +.itinerary-list-container .time-zone-info{ + margin: 10px 20px; + font-size: 12px; + text-align: right; +} + .itinerary-header-wrapper { position: relative; background: #0a53be; diff --git a/client/src/util/formatTime.ts b/client/src/util/formatTime.ts index 1b14ab103c6..8068c0e29b0 100644 --- a/client/src/util/formatTime.ts +++ b/client/src/util/formatTime.ts @@ -1,16 +1,14 @@ -import {useTimeZone} from "../hooks/useTimeZone.ts"; - /** * Format departure and arrival times from scalar dateTime strings * * If style argument is provided formatted with ('medium') or without ('short') seconds, * otherwise seconds are shown if not 0. */ -export function formatTime(dateTime: string, style?: 'short' | 'medium') { +export function formatTime(dateTime: string, timeZone: string, style?: 'short' | 'medium') { const parsed = new Date(dateTime); return parsed.toLocaleTimeString('en-US', { timeStyle: style ? style : parsed.getSeconds() === 0 ? 'short' : 'medium', hourCycle: 'h24', - timeZone: useTimeZone() + timeZone: timeZone }); } From b55ecd7d86b93f6b370d4258a780fa5112cf64fd Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 23 Sep 2024 22:48:04 +0200 Subject: [PATCH 35/80] Remove memo of time --- .../components/SearchBar/DateTimeInputField.tsx | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/client/src/components/SearchBar/DateTimeInputField.tsx b/client/src/components/SearchBar/DateTimeInputField.tsx index c7e77ee8f05..b97ef1a35a7 100644 --- a/client/src/components/SearchBar/DateTimeInputField.tsx +++ b/client/src/components/SearchBar/DateTimeInputField.tsx @@ -1,6 +1,6 @@ import { Form } from 'react-bootstrap'; import { TripQueryVariables } from '../../gql/graphql.ts'; -import { ChangeEvent, useCallback, useContext, useMemo } from 'react'; +import { ChangeEvent, useCallback, useContext } from 'react'; import { Temporal } from '@js-temporal/polyfill'; import { TimeZoneContext } from '../../hooks/TimeZoneContext.ts'; @@ -12,14 +12,10 @@ export function DateTimeInputField({ setTripQueryVariables: (tripQueryVariables: TripQueryVariables) => void; }) { const timeZone = useContext(TimeZoneContext); - const current = useMemo( - () => - Temporal.Instant.from(tripQueryVariables.dateTime) - .toZonedDateTimeISO(timeZone) - .toPlainDateTime() - .toString({ smallestUnit: 'minute', calendarName: 'never' }), - [tripQueryVariables.dateTime], - ); + const current = Temporal.Instant.from(tripQueryVariables.dateTime) + .toZonedDateTimeISO(timeZone) + .toPlainDateTime() + .toString({ smallestUnit: 'minute', calendarName: 'never' }); const onChange = useCallback( (event: ChangeEvent) => { From eda11ecd9b289efd971c9befc365f173dda510cf Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 23 Sep 2024 23:25:44 +0200 Subject: [PATCH 36/80] Use configuration value for URL --- client/src/hooks/useTimeZone.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/src/hooks/useTimeZone.ts b/client/src/hooks/useTimeZone.ts index 36ab9d9d12a..7fa5fa9ba29 100644 --- a/client/src/hooks/useTimeZone.ts +++ b/client/src/hooks/useTimeZone.ts @@ -1,10 +1,12 @@ import { useEffect, useState } from 'react'; +const serverInfoUrl = import.meta.env.VITE_SERVER_INFO_URL; + type ServerInfo = { transitTimeZone: string; }; -const fetchServerInfo = (): Promise => fetch('http://localhost:8080/otp/').then((r) => r.json()); +const fetchServerInfo = (): Promise => fetch(serverInfoUrl).then((r) => r.json()); export const useTimeZone = () => { const [data, setData] = useState(null); From 9b3c4d5e66fd829bd604ae7c7acf8ed601053b71 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Tue, 24 Sep 2024 09:00:07 +0300 Subject: [PATCH 37/80] Revert "Generate unique id for non-transit legs" This reverts commit b206ce18b3d153a4a436ef7d9b1abb1e729fe8a1. --- .../apis/gtfs/datafetchers/LegImpl.java | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java index 4b3ddf9019d..4f54eadb3c0 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java @@ -329,22 +329,8 @@ public DataFetcher accessibilityScore() { @Override public DataFetcher id() { return environment -> { - var leg = getSource(environment); var ref = getSource(environment).getLegReference(); - String id; - - if (ref == null) { - id = - String.join( - "", - leg.start().time().toString(), - leg.end().time().toString(), - leg.getFrom().toStringShort(), - leg.getTo().toStringShort() - ); - } else { - id = LegReferenceSerializer.encode(ref); - } + var id = LegReferenceSerializer.encode(ref); return new Relay.ResolvedGlobalId("Leg", id); }; } From 14fd13eb7205540cb4f15a642efd5f346572af5f Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Tue, 24 Sep 2024 09:53:18 +0300 Subject: [PATCH 38/80] Remove node interface from LegType and add leg query --- .../apis/gtfs/datafetchers/LegImpl.java | 3 +++ .../apis/gtfs/datafetchers/QueryTypeImpl.java | 21 +++++++++++++------ .../opentripplanner/apis/gtfs/schema.graphqls | 8 ++++--- .../apis/gtfs/expectations/plan-extended.json | 4 ++-- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java index 4f54eadb3c0..69a0e7b2f5e 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java @@ -330,6 +330,9 @@ public DataFetcher accessibilityScore() { public DataFetcher id() { return environment -> { var ref = getSource(environment).getLegReference(); + if (ref == null) { + return null; + } var id = LegReferenceSerializer.encode(ref); return new Relay.ResolvedGlobalId("Leg", id); }; diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java index b064582f5d8..c817d723a2c 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java @@ -42,6 +42,7 @@ import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.gtfs.mapping.DirectionMapper; import org.opentripplanner.model.TripTimeOnDate; +import org.opentripplanner.model.plan.Leg; import org.opentripplanner.model.plan.legreference.LegReference; import org.opentripplanner.model.plan.legreference.LegReferenceSerializer; import org.opentripplanner.routing.alertpatch.EntitySelector; @@ -364,6 +365,20 @@ public DataFetcher> nearest() { }; } + @Override + public DataFetcher leg() { + return environment -> { + TransitService transitService = getTransitService(environment); + var args = new GraphQLTypes.GraphQLQueryTypeLegArgs(environment.getArguments()); + String id = args.getGraphQLId().getId(); + LegReference ref = LegReferenceSerializer.decode(id); + if (ref == null) { + return null; + } + return ref.getLeg(transitService); + }; + } + @Override public DataFetcher node() { return environment -> { @@ -447,12 +462,6 @@ public DataFetcher node() { // TODO: Add geometry return new NearbyStop(stop, Integer.parseInt(parts[0]), null, null); } - case "Leg": - LegReference ref = LegReferenceSerializer.decode(id); - if (ref == null) { - return null; - } - return ref.getLeg(transitService); case "TicketType": return null; //TODO case "Trip": diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 4c9b25c1048..1d93c5bfa9a 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -611,7 +611,7 @@ type Itinerary { walkTime: Long } -type Leg implements Node { +type Leg { """ Computes a numeric accessibility score between 0 and 1. @@ -674,7 +674,7 @@ type Leg implements Node { Re-fetching fails when the underlying transit data no longer exists. Non-transit legs cannot be refetched using their id. """ - id: ID! + id: ID """ Interlines with previous leg. This is true when the same vehicle is used for the previous leg as for this leg @@ -1171,7 +1171,9 @@ type QueryType { "Departure time of the trip, format: seconds since midnight of the departure date" time: Int! ): Trip - """ + "Try refetching the current state of a transit leg using its ID" + leg(id: String!): Leg + """ Get all places (stops, stations, etc. with coordinates) within the specified radius from a location. The returned type is a Relay connection (see https://facebook.github.io/relay/graphql/connections.htm). The placeAtDistance diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-extended.json b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-extended.json index 348b10c816a..ab98d95e7ad 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-extended.json +++ b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-extended.json @@ -65,7 +65,7 @@ "alerts" : [ ], "rideHailingEstimate" : null, "accessibilityScore" : null, - "id": "TGVnOjIwMjAtMDItMDJUMTE6MDBaMjAyMC0wMi0wMlQxMTowMDoyMFpBIChGOkEpQiAoRjpCKQ", + "id": null, "realtimeState": "SCHEDULED" }, { @@ -341,7 +341,7 @@ "arrival" : "PT10M" }, "accessibilityScore" : null, - "id": "TGVnOjIwMjAtMDItMDJUMTE6NTBaMjAyMC0wMi0wMlQxMjowMFpEIChGOkQpRSAoRjpFKQ", + "id": null, "realtimeState": "SCHEDULED" } ] From 71a63154c1d1244f81a4d9c307668d8923a9e465 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Tue, 24 Sep 2024 10:50:48 +0300 Subject: [PATCH 39/80] Update auto generated files --- .../gtfs/generated/GraphQLDataFetchers.java | 2 ++ .../apis/gtfs/generated/GraphQLTypes.java | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java index 36d262ef164..4e63e87438b 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java @@ -781,6 +781,8 @@ public interface GraphQLQueryType { public DataFetcher fuzzyTrip(); + public DataFetcher leg(); + public DataFetcher> nearest(); public DataFetcher node(); diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java index ed3e9afefc9..3cd98b15652 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -2432,6 +2432,25 @@ public void setGraphQLTime(Integer time) { } } + public static class GraphQLQueryTypeLegArgs { + + private String id; + + public GraphQLQueryTypeLegArgs(Map args) { + if (args != null) { + this.id = (String) args.get("id"); + } + } + + public String getGraphQLId() { + return this.id; + } + + public void setGraphQLId(String id) { + this.id = id; + } + } + public static class GraphQLQueryTypeNearestArgs { private String after; From cd660b2bec216b398612af0952d727ce93aa1b7f Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Tue, 24 Sep 2024 11:15:09 +0300 Subject: [PATCH 40/80] Fix schema formatting --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 6635f9bbba5..af0fd5dd6be 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1173,7 +1173,7 @@ type QueryType { ): Trip "Try refetching the current state of a transit leg using its ID" leg(id: String!): Leg - """ + """ Get all places (stops, stations, etc. with coordinates) within the specified radius from a location. The returned type is a Relay connection (see https://facebook.github.io/relay/graphql/connections.htm). The placeAtDistance From 97d21803d7939db8afc6cc7baf026f5c8d4ba254 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Tue, 24 Sep 2024 11:15:28 +0300 Subject: [PATCH 41/80] Fix leg id access --- .../opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java index 266574f546b..95984ba6dd0 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java @@ -370,7 +370,7 @@ public DataFetcher leg() { return environment -> { TransitService transitService = getTransitService(environment); var args = new GraphQLTypes.GraphQLQueryTypeLegArgs(environment.getArguments()); - String id = args.getGraphQLId().getId(); + String id = args.getGraphQLId(); LegReference ref = LegReferenceSerializer.decode(id); if (ref == null) { return null; From 208c8ad69dc9397b45acabf3328fad3086f3b75f Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 24 Sep 2024 10:27:39 +0200 Subject: [PATCH 42/80] Add comment --- client/src/hooks/useTimeZone.ts | 3 +++ client/src/style.css | 2 +- client/src/util/formatTime.ts | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/client/src/hooks/useTimeZone.ts b/client/src/hooks/useTimeZone.ts index 7fa5fa9ba29..4f568880e93 100644 --- a/client/src/hooks/useTimeZone.ts +++ b/client/src/hooks/useTimeZone.ts @@ -8,6 +8,9 @@ type ServerInfo = { const fetchServerInfo = (): Promise => fetch(serverInfoUrl).then((r) => r.json()); +/** + * Fetch the transit model's time zone from the server and use the browser's as the fallback. + */ export const useTimeZone = () => { const [data, setData] = useState(null); useEffect(() => { diff --git a/client/src/style.css b/client/src/style.css index 76b431510ad..37100d65006 100644 --- a/client/src/style.css +++ b/client/src/style.css @@ -60,7 +60,7 @@ overflow-y: auto; } -.itinerary-list-container .time-zone-info{ +.itinerary-list-container .time-zone-info { margin: 10px 20px; font-size: 12px; text-align: right; diff --git a/client/src/util/formatTime.ts b/client/src/util/formatTime.ts index 8068c0e29b0..1818ced5cd1 100644 --- a/client/src/util/formatTime.ts +++ b/client/src/util/formatTime.ts @@ -9,6 +9,6 @@ export function formatTime(dateTime: string, timeZone: string, style?: 'short' | return parsed.toLocaleTimeString('en-US', { timeStyle: style ? style : parsed.getSeconds() === 0 ? 'short' : 'medium', hourCycle: 'h24', - timeZone: timeZone + timeZone: timeZone, }); } From c09387b012cd8193b6995ecb2a7d97d200b1aa0e Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 24 Sep 2024 10:41:47 +0200 Subject: [PATCH 43/80] Fix linting issues --- .../components/ItineraryList/ItineraryHeaderContent.tsx | 4 ++-- client/src/components/SearchBar/DateTimeInputField.tsx | 2 +- .../api/model/serverinfo/ApiServerInfo.java | 7 ++++++- .../java/org/opentripplanner/api/resource/ServerInfo.java | 7 ++----- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/client/src/components/ItineraryList/ItineraryHeaderContent.tsx b/client/src/components/ItineraryList/ItineraryHeaderContent.tsx index 0788ca1974e..fdfea81e7e4 100644 --- a/client/src/components/ItineraryList/ItineraryHeaderContent.tsx +++ b/client/src/components/ItineraryList/ItineraryHeaderContent.tsx @@ -29,12 +29,12 @@ export function ItineraryHeaderContent({ const formattedStartTime = useMemo( () => formatTime(tripPattern.expectedStartTime, timeZone, 'short'), - [tripPattern.expectedStartTime], + [tripPattern.expectedStartTime, timeZone], ); const formattedEndTime = useMemo( () => formatTime(tripPattern.expectedEndTime, timeZone, 'short'), - [tripPattern.expectedEndTime], + [tripPattern.expectedEndTime, timeZone], ); return ( diff --git a/client/src/components/SearchBar/DateTimeInputField.tsx b/client/src/components/SearchBar/DateTimeInputField.tsx index b97ef1a35a7..492f32912ab 100644 --- a/client/src/components/SearchBar/DateTimeInputField.tsx +++ b/client/src/components/SearchBar/DateTimeInputField.tsx @@ -28,7 +28,7 @@ export function DateTimeInputField({ dateTime: dateTime, }); }, - [tripQueryVariables, setTripQueryVariables], + [tripQueryVariables, setTripQueryVariables, timeZone], ); return ( diff --git a/src/main/java/org/opentripplanner/api/model/serverinfo/ApiServerInfo.java b/src/main/java/org/opentripplanner/api/model/serverinfo/ApiServerInfo.java index e4b42d48e59..bf8a09dea3e 100644 --- a/src/main/java/org/opentripplanner/api/model/serverinfo/ApiServerInfo.java +++ b/src/main/java/org/opentripplanner/api/model/serverinfo/ApiServerInfo.java @@ -13,7 +13,12 @@ public class ApiServerInfo { public final String otpSerializationVersionId; public final String transitTimeZone; - public ApiServerInfo(String cpuName, int nCores, OtpProjectInfo projectInfo, ZoneId transitTimeZone) { + public ApiServerInfo( + String cpuName, + int nCores, + OtpProjectInfo projectInfo, + ZoneId transitTimeZone + ) { this.cpuName = cpuName; this.nCores = nCores; this.version = new ApiProjectVersion(projectInfo.version); diff --git a/src/main/java/org/opentripplanner/api/resource/ServerInfo.java b/src/main/java/org/opentripplanner/api/resource/ServerInfo.java index c11d9c8158f..ef94f970280 100644 --- a/src/main/java/org/opentripplanner/api/resource/ServerInfo.java +++ b/src/main/java/org/opentripplanner/api/resource/ServerInfo.java @@ -20,13 +20,12 @@ public class ServerInfo { private final ZoneId timeZone; private static final String cpuName; - private static final int nCores ; + private static final int nCores; public ServerInfo(@Context OtpServerRequestContext serverContext) { this.timeZone = serverContext.transitService().getTimeZone(); } - /** * Determine the OTP version and CPU type of the running server. This information should not * change while the server is up, so it can safely be cached at startup. The project info is not @@ -43,7 +42,7 @@ public ServerInfo(@Context OtpServerRequestContext serverContext) { while ((line = br.readLine()) != null) { if (line.startsWith("model name")) { cpu = line.split(": ")[1]; - cores+= 1; + cores += 1; } } fis.close(); @@ -52,8 +51,6 @@ public ServerInfo(@Context OtpServerRequestContext serverContext) { nCores = cores; } - - @GET @Produces(MediaType.APPLICATION_JSON) public ApiServerInfo getServerInfo() { From 795cb52f138c2e0362d9371dc93147c2c7cbc742 Mon Sep 17 00:00:00 2001 From: JustCris Date: Tue, 24 Sep 2024 11:17:41 +0200 Subject: [PATCH 44/80] DocsTestConstants test check both user and templates directories --- .../generate/doc/framework/DocsTestConstants.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/opentripplanner/generate/doc/framework/DocsTestConstants.java b/src/test/java/org/opentripplanner/generate/doc/framework/DocsTestConstants.java index 6c88488c281..813f6e97d09 100644 --- a/src/test/java/org/opentripplanner/generate/doc/framework/DocsTestConstants.java +++ b/src/test/java/org/opentripplanner/generate/doc/framework/DocsTestConstants.java @@ -14,19 +14,20 @@ public interface DocsTestConstants { File USER_DOC_PATH = new File(DOC_ROOT, "user"); /** - * This method return {@code true} if the /doc directory is available. If not, a warning is + * This method return {@code true} if both the /doc/user and /doc/templates directories are available. If not, a warning is * logged and the method returns {@code false}. This is used by the {@link GeneratesDocumentation} * annotation. */ static boolean docsExistOrWarn() { - if (USER_DOC_PATH.exists()) { + if (USER_DOC_PATH.exists() && TEMPLATE_PATH.exists()) { return true; } + LOG.warn( """ - SKIP TEST - '/doc' NOT FOUND + SKIP TEST - '/doc/user' or '/doc/templates NOT FOUND - The doc/templates directory might not be available if you run the tests outside the + The /doc/user and /doc/templates directories might not be available if you run the tests outside the root of the projects. This may happen if the project root is not the working directory, if you run tests using jar files or in a Maven multi-module project. From 1bc92c3c250dd1b4286bb1077db5937c3d37b93c Mon Sep 17 00:00:00 2001 From: JustCris Date: Tue, 24 Sep 2024 11:18:39 +0200 Subject: [PATCH 45/80] fix typo --- .../generate/doc/framework/DocsTestConstants.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/opentripplanner/generate/doc/framework/DocsTestConstants.java b/src/test/java/org/opentripplanner/generate/doc/framework/DocsTestConstants.java index 813f6e97d09..cc306c133ac 100644 --- a/src/test/java/org/opentripplanner/generate/doc/framework/DocsTestConstants.java +++ b/src/test/java/org/opentripplanner/generate/doc/framework/DocsTestConstants.java @@ -25,7 +25,7 @@ static boolean docsExistOrWarn() { LOG.warn( """ - SKIP TEST - '/doc/user' or '/doc/templates NOT FOUND + SKIP TEST - '/doc/user' or '/doc/templates' NOT FOUND The /doc/user and /doc/templates directories might not be available if you run the tests outside the root of the projects. This may happen if the project root is not the working directory, From 0be72d37d05f41e56da738711d70e31b62d87cf7 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 24 Sep 2024 12:04:36 +0200 Subject: [PATCH 46/80] Add internal time zone to Transmodel GraphQL API --- client/src/hooks/useServerInfo.ts | 1 + client/src/hooks/useTimeZone.ts | 24 ----------------- client/src/screens/App.tsx | 3 +-- .../api/model/serverinfo/ApiServerInfo.java | 10 +------ .../api/resource/ServerInfo.java | 27 +++++++------------ .../transmodel/TransmodelGraphQLSchema.java | 4 ++- .../model/framework/ServerInfoType.java | 23 ++++++++++++++++ .../apis/transmodel/schema.graphql | 6 +++++ 8 files changed, 44 insertions(+), 54 deletions(-) delete mode 100644 client/src/hooks/useTimeZone.ts diff --git a/client/src/hooks/useServerInfo.ts b/client/src/hooks/useServerInfo.ts index 23ee23fc283..117c1357360 100644 --- a/client/src/hooks/useServerInfo.ts +++ b/client/src/hooks/useServerInfo.ts @@ -13,6 +13,7 @@ const query = graphql(` routerConfigVersion gitCommit gitBranch + internalTransitModelTimeZone } } `); diff --git a/client/src/hooks/useTimeZone.ts b/client/src/hooks/useTimeZone.ts deleted file mode 100644 index 4f568880e93..00000000000 --- a/client/src/hooks/useTimeZone.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { useEffect, useState } from 'react'; - -const serverInfoUrl = import.meta.env.VITE_SERVER_INFO_URL; - -type ServerInfo = { - transitTimeZone: string; -}; - -const fetchServerInfo = (): Promise => fetch(serverInfoUrl).then((r) => r.json()); - -/** - * Fetch the transit model's time zone from the server and use the browser's as the fallback. - */ -export const useTimeZone = () => { - const [data, setData] = useState(null); - useEffect(() => { - const fetchData = async () => { - setData(await fetchServerInfo()); - }; - fetchData(); - }, []); - - return data?.transitTimeZone || Intl.DateTimeFormat().resolvedOptions().timeZone; -}; diff --git a/client/src/screens/App.tsx b/client/src/screens/App.tsx index cb29654a4ff..1b6b86b7a81 100644 --- a/client/src/screens/App.tsx +++ b/client/src/screens/App.tsx @@ -7,14 +7,13 @@ import { useTripQuery } from '../hooks/useTripQuery.ts'; import { useServerInfo } from '../hooks/useServerInfo.ts'; import { useTripQueryVariables } from '../hooks/useTripQueryVariables.ts'; import { TimeZoneContext } from '../hooks/TimeZoneContext.ts'; -import { useTimeZone } from '../hooks/useTimeZone.ts'; export function App() { const serverInfo = useServerInfo(); - const timeZone = useTimeZone(); const { tripQueryVariables, setTripQueryVariables } = useTripQueryVariables(); const [tripQueryResult, loading, callback] = useTripQuery(tripQueryVariables); const [selectedTripPatternIndex, setSelectedTripPatternIndex] = useState(0); + const timeZone = serverInfo?.internalTransitModelTimeZone || Intl.DateTimeFormat().resolvedOptions().timeZone; return (
diff --git a/src/main/java/org/opentripplanner/api/model/serverinfo/ApiServerInfo.java b/src/main/java/org/opentripplanner/api/model/serverinfo/ApiServerInfo.java index bf8a09dea3e..e90c0e37fdf 100644 --- a/src/main/java/org/opentripplanner/api/model/serverinfo/ApiServerInfo.java +++ b/src/main/java/org/opentripplanner/api/model/serverinfo/ApiServerInfo.java @@ -1,6 +1,5 @@ package org.opentripplanner.api.model.serverinfo; -import java.time.ZoneId; import org.opentripplanner.model.projectinfo.OtpProjectInfo; public class ApiServerInfo { @@ -11,20 +10,13 @@ public class ApiServerInfo { public final ApiVersionControlInfo versionControl; public final ApiConfigInfo config; public final String otpSerializationVersionId; - public final String transitTimeZone; - public ApiServerInfo( - String cpuName, - int nCores, - OtpProjectInfo projectInfo, - ZoneId transitTimeZone - ) { + public ApiServerInfo(String cpuName, int nCores, OtpProjectInfo projectInfo) { this.cpuName = cpuName; this.nCores = nCores; this.version = new ApiProjectVersion(projectInfo.version); this.versionControl = new ApiVersionControlInfo(projectInfo.versionControl); this.config = new ApiConfigInfo(projectInfo); this.otpSerializationVersionId = projectInfo.getOtpSerializationVersionId(); - this.transitTimeZone = transitTimeZone.toString(); } } diff --git a/src/main/java/org/opentripplanner/api/resource/ServerInfo.java b/src/main/java/org/opentripplanner/api/resource/ServerInfo.java index ef94f970280..bc8d91d4780 100644 --- a/src/main/java/org/opentripplanner/api/resource/ServerInfo.java +++ b/src/main/java/org/opentripplanner/api/resource/ServerInfo.java @@ -3,28 +3,19 @@ import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; -import java.time.ZoneId; import org.opentripplanner.api.model.serverinfo.ApiServerInfo; import org.opentripplanner.model.projectinfo.OtpProjectInfo; -import org.opentripplanner.standalone.api.OtpServerRequestContext; @Path("/") public class ServerInfo { - private final ZoneId timeZone; - private static final String cpuName; - private static final int nCores; - - public ServerInfo(@Context OtpServerRequestContext serverContext) { - this.timeZone = serverContext.transitService().getTimeZone(); - } + private static final ApiServerInfo SERVER_INFO = createServerInfo(); /** * Determine the OTP version and CPU type of the running server. This information should not @@ -32,28 +23,28 @@ public ServerInfo(@Context OtpServerRequestContext serverContext) { * available before the graph is loaded, so for this to work this class should not be loaded * BEFORE that. */ - static { - var cpu = "unknown"; - int cores = 0; + public static ApiServerInfo createServerInfo() { + String cpuName = "unknown"; + int nCores = 0; try { InputStream fis = new FileInputStream("/proc/cpuinfo"); BufferedReader br = new BufferedReader(new InputStreamReader(fis, StandardCharsets.UTF_8)); String line; while ((line = br.readLine()) != null) { if (line.startsWith("model name")) { - cpu = line.split(": ")[1]; - cores += 1; + cpuName = line.split(": ")[1]; + nCores += 1; } } fis.close(); } catch (Exception ignore) {} - cpuName = cpu; - nCores = cores; + + return new ApiServerInfo(cpuName, nCores, OtpProjectInfo.projectInfo()); } @GET @Produces(MediaType.APPLICATION_JSON) public ApiServerInfo getServerInfo() { - return new ApiServerInfo(cpuName, nCores, OtpProjectInfo.projectInfo(), timeZone); + return SERVER_INFO; } } diff --git a/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java index 9ad43606420..b5a934722ea 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java @@ -1623,7 +1623,9 @@ private GraphQLSchema create() { GraphQLFieldDefinition .newFieldDefinition() .name("serverInfo") - .description("Get OTP server information") + .description( + "Get OTP deployment information. This is only useful for developers of OTP itself not regular API users." + ) .withDirective(gqlUtil.timingData) .type(new GraphQLNonNull(serverInfoType)) .dataFetcher(e -> projectInfo()) diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/framework/ServerInfoType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/ServerInfoType.java index ae6889ab033..98cd8be5bf4 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/framework/ServerInfoType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/ServerInfoType.java @@ -9,6 +9,7 @@ import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLOutputType; +import org.opentripplanner.apis.transmodel.support.GqlUtil; public class ServerInfoType { @@ -16,6 +17,13 @@ public static GraphQLOutputType create() { return GraphQLObjectType .newObject() .name("ServerInfo") + .description( + """ + Information about the deployment. This is only useful to developers of OTP itself. + It is not recommended for regular API consumers to use this type as it has no + stability guarantees. + """ + ) .field( GraphQLFieldDefinition .newFieldDefinition() @@ -99,6 +107,21 @@ public static GraphQLOutputType create() { .dataFetcher(e -> projectInfo().getOtpSerializationVersionId()) .build() ) + .field( + GraphQLFieldDefinition + .newFieldDefinition() + .name("internalTransitModelTimeZone") + .description( + """ + The internal time zone of the transit data. + + Note: Input data can be in several time zones but OTP internally operates on a single one. + """ + ) + .type(Scalars.GraphQLString) + .dataFetcher(e -> GqlUtil.getTransitService(e).getTimeZone()) + .build() + ) .build(); } } diff --git a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql index 0d2bf71dcc6..7ced7680a47 100644 --- a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -1075,6 +1075,12 @@ type ServerInfo { gitBranch: String gitCommit: String gitCommitTime: String + """ + The internal time zone of the transit data. + + Note: Input data can be in several time zones but OTP internally operates on a single one. + """ + internalTransitModelTimeZone: String "The 'configVersion' of the otp-config.json file." otpConfigVersion: String "The otp-serialization-version-id used to check graphs for compatibility with current version of OTP." From e9c029f01b1e76e24c899057fedee157e1bf343e Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 24 Sep 2024 12:48:38 +0200 Subject: [PATCH 47/80] Update schema docs --- .../org/opentripplanner/apis/transmodel/schema.graphql | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql index 7ced7680a47..9282ece1255 100644 --- a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -730,7 +730,7 @@ type QueryType { ): quayAtDistanceConnection @timingData "Get default routing parameters." routingParameters: RoutingParameters @timingData - "Get OTP server information" + "Get OTP deployment information. This is only useful for developers of OTP itself not regular API users." serverInfo: ServerInfo! @timingData "Get a single service journey based on its id" serviceJourney(id: String!): ServiceJourney @timingData @@ -1067,6 +1067,11 @@ type RoutingParameters { wheelChairAccessible: Boolean } +""" +Information about the deployment. This is only useful to developers of OTP itself. +It is not recommended for regular API consumers to use this type as it has no +stability guarantees. +""" type ServerInfo { "The 'configVersion' of the build-config.json file." buildConfigVersion: String From b75c047f0bcfa48385c014255da513fb80ec5d29 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Tue, 24 Sep 2024 14:09:18 +0300 Subject: [PATCH 48/80] Remove leg resolver from node type --- .../apis/gtfs/datafetchers/NodeTypeResolver.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/NodeTypeResolver.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/NodeTypeResolver.java index ae712f17252..f83e088024e 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/NodeTypeResolver.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/NodeTypeResolver.java @@ -86,9 +86,6 @@ public GraphQLObjectType getType(TypeResolutionEnvironment environment) { if (o instanceof Trip) { return schema.getObjectType("Trip"); } - if (o instanceof Leg) { - return schema.getObjectType("Leg"); - } return null; } From 769bb508f82fc598a2eb05a6e88ef4a800ac3520 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 24 Sep 2024 13:40:28 +0200 Subject: [PATCH 49/80] Remove unused env variables --- client/.env | 1 - client/.env.development | 1 - 2 files changed, 2 deletions(-) diff --git a/client/.env b/client/.env index a4fbf511e26..2f5d271b1e4 100644 --- a/client/.env +++ b/client/.env @@ -1,4 +1,3 @@ VITE_API_URL=/otp/transmodel/v3 VITE_DEBUG_STYLE_URL=/otp/routers/default/inspector/vectortile/style.json VITE_GRAPHIQL_URL=/graphiql?flavor=transmodel -VITE_SERVER_INFO_URL=/otp/ diff --git a/client/.env.development b/client/.env.development index 70ad81eb7dd..e3b3585a5eb 100644 --- a/client/.env.development +++ b/client/.env.development @@ -1,4 +1,3 @@ VITE_API_URL=http://localhost:8080/otp/transmodel/v3 VITE_DEBUG_STYLE_URL=http://localhost:8080/otp/routers/default/inspector/vectortile/style.json VITE_GRAPHIQL_URL=http://localhost:8080/graphiql?flavor=transmodel -VITE_SERVER_INFO_URL=http://localhost:8080/otp/ From 1a43221cb8ff1b1b1dbeb419f1c8eac66186ec0e Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Tue, 24 Sep 2024 14:43:16 +0300 Subject: [PATCH 50/80] Use ID parameter type for leg query --- .../apis/gtfs/datafetchers/QueryTypeImpl.java | 2 +- .../opentripplanner/apis/gtfs/generated/GraphQLTypes.java | 8 ++++---- .../org/opentripplanner/apis/gtfs/schema.graphqls | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java index 95984ba6dd0..266574f546b 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java @@ -370,7 +370,7 @@ public DataFetcher leg() { return environment -> { TransitService transitService = getTransitService(environment); var args = new GraphQLTypes.GraphQLQueryTypeLegArgs(environment.getArguments()); - String id = args.getGraphQLId(); + String id = args.getGraphQLId().getId(); LegReference ref = LegReferenceSerializer.decode(id); if (ref == null) { return null; diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java index 3cd98b15652..d0ab004e654 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -2434,19 +2434,19 @@ public void setGraphQLTime(Integer time) { public static class GraphQLQueryTypeLegArgs { - private String id; + private graphql.relay.Relay.ResolvedGlobalId id; public GraphQLQueryTypeLegArgs(Map args) { if (args != null) { - this.id = (String) args.get("id"); + this.id = (graphql.relay.Relay.ResolvedGlobalId) args.get("id"); } } - public String getGraphQLId() { + public graphql.relay.Relay.ResolvedGlobalId getGraphQLId() { return this.id; } - public void setGraphQLId(String id) { + public void setGraphQLId(graphql.relay.Relay.ResolvedGlobalId id) { this.id = id; } } diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index af0fd5dd6be..561ec9e38cc 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1172,7 +1172,7 @@ type QueryType { time: Int! ): Trip "Try refetching the current state of a transit leg using its ID" - leg(id: String!): Leg + leg(id: ID!): Leg """ Get all places (stops, stations, etc. with coordinates) within the specified radius from a location. The returned type is a Relay connection (see From e577006481abba7f947f0b656b5ce5f172231d6e Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 24 Sep 2024 13:46:33 +0200 Subject: [PATCH 51/80] Update schema docs Co-authored-by: Thomas Gran --- .../org/opentripplanner/apis/transmodel/schema.graphql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql index 9282ece1255..b53741fa828 100644 --- a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -1083,7 +1083,7 @@ type ServerInfo { """ The internal time zone of the transit data. - Note: Input data can be in several time zones but OTP internally operates on a single one. + Note: The input data can be in several time zones, but OTP internally operates on a single one. """ internalTransitModelTimeZone: String "The 'configVersion' of the otp-config.json file." From b5d7e13c3a3a0ed41ea43947af44322e10e191ad Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 24 Sep 2024 13:49:11 +0200 Subject: [PATCH 52/80] Update schema docs --- .../org/opentripplanner/apis/transmodel/schema.graphql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql index b53741fa828..9282ece1255 100644 --- a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -1083,7 +1083,7 @@ type ServerInfo { """ The internal time zone of the transit data. - Note: The input data can be in several time zones, but OTP internally operates on a single one. + Note: Input data can be in several time zones but OTP internally operates on a single one. """ internalTransitModelTimeZone: String "The 'configVersion' of the otp-config.json file." From 7080ebb0822e29e39e585686d97593129610acef Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 24 Sep 2024 13:56:08 +0200 Subject: [PATCH 53/80] Update src/main/java/org/opentripplanner/apis/transmodel/model/framework/ServerInfoType.java --- .../apis/transmodel/model/framework/ServerInfoType.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/framework/ServerInfoType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/ServerInfoType.java index 98cd8be5bf4..8f679cafda3 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/framework/ServerInfoType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/ServerInfoType.java @@ -115,7 +115,7 @@ public static GraphQLOutputType create() { """ The internal time zone of the transit data. - Note: Input data can be in several time zones but OTP internally operates on a single one. + Note: The input data can be in several time zones, but OTP internally operates on a single one. """ ) .type(Scalars.GraphQLString) From 60b738351a59bf9adb1b6530bbdcb41338ebb554 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 24 Sep 2024 13:57:12 +0200 Subject: [PATCH 54/80] Update src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql --- .../org/opentripplanner/apis/transmodel/schema.graphql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql index 9282ece1255..b53741fa828 100644 --- a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -1083,7 +1083,7 @@ type ServerInfo { """ The internal time zone of the transit data. - Note: Input data can be in several time zones but OTP internally operates on a single one. + Note: The input data can be in several time zones, but OTP internally operates on a single one. """ internalTransitModelTimeZone: String "The 'configVersion' of the otp-config.json file." From 9c10a0eff2e2f72920f6c811d8c2274163e17afe Mon Sep 17 00:00:00 2001 From: OTP Bot Date: Tue, 24 Sep 2024 12:05:43 +0000 Subject: [PATCH 55/80] Upgrade debug client to version 2024/09/2024-09-24T12:05 --- src/client/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/index.html b/src/client/index.html index 5b3dd90b341..6a7d7f32aaa 100644 --- a/src/client/index.html +++ b/src/client/index.html @@ -5,8 +5,8 @@ OTP Debug Client - - + +
From 0a95a2d7516206fa011470d1c14e2104c0837308 Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Tue, 24 Sep 2024 18:32:42 -0700 Subject: [PATCH 56/80] fix(orca-fares): remove fare attributes with no rules --- .../ext/fares/impl/DefaultFareServiceFactory.java | 2 +- .../opentripplanner/ext/fares/impl/OrcaFareFactory.java | 2 ++ .../org/opentripplanner/ext/fares/model/FareRuleSet.java | 9 +++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/ext/java/org/opentripplanner/ext/fares/impl/DefaultFareServiceFactory.java b/src/ext/java/org/opentripplanner/ext/fares/impl/DefaultFareServiceFactory.java index ee4c87924ea..d7a3a0425ec 100644 --- a/src/ext/java/org/opentripplanner/ext/fares/impl/DefaultFareServiceFactory.java +++ b/src/ext/java/org/opentripplanner/ext/fares/impl/DefaultFareServiceFactory.java @@ -92,7 +92,7 @@ protected void fillFareRules( FareRuleSet fareRule = fareRuleSet.get(id); if (fareRule == null) { // Should never happen by design - LOG.error("Inexistant fare ID in fare rule: " + id); + LOG.error("Nonexistent fare ID in fare rule: " + id); continue; } String contains = rule.getContainsId(); diff --git a/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareFactory.java b/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareFactory.java index 34a03c1fc06..d48cad48450 100644 --- a/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareFactory.java +++ b/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareFactory.java @@ -24,6 +24,8 @@ public FareService makeFareService() { @Override public void processGtfs(FareRulesData fareRuleService, OtpTransitService transitService) { fillFareRules(fareRuleService.fareAttributes(), fareRuleService.fareRules(), regularFareRules); + // ORCA agencies don't rely on fare attributes without rules, so let's remove them. + regularFareRules.entrySet().removeIf(entry -> !entry.getValue().hasRules()); } /** diff --git a/src/ext/java/org/opentripplanner/ext/fares/model/FareRuleSet.java b/src/ext/java/org/opentripplanner/ext/fares/model/FareRuleSet.java index 30119631216..97cc4e637d2 100644 --- a/src/ext/java/org/opentripplanner/ext/fares/model/FareRuleSet.java +++ b/src/ext/java/org/opentripplanner/ext/fares/model/FareRuleSet.java @@ -40,6 +40,15 @@ public Set getRouteOriginDestinations() { return routeOriginDestinations; } + public boolean hasRules() { + return ( + !routes.isEmpty() || + !originDestinations.isEmpty() || + !routeOriginDestinations.isEmpty() || + !contains.isEmpty() + ); + } + public void addContains(String containsId) { contains.add(containsId); } From fb4eef585bf7e35d2c12326cf0dd7991ff82639f Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Fri, 20 Sep 2024 11:54:52 +0200 Subject: [PATCH 57/80] Apply review suggestions --- .../model/TimetableBuilder.java | 26 +++++++++---------- .../GenerateTripPatternsOperationTest.java | 3 +++ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/opentripplanner/model/TimetableBuilder.java b/src/main/java/org/opentripplanner/model/TimetableBuilder.java index 1c4e30dbdc1..74e89d0d973 100644 --- a/src/main/java/org/opentripplanner/model/TimetableBuilder.java +++ b/src/main/java/org/opentripplanner/model/TimetableBuilder.java @@ -107,30 +107,30 @@ public TimetableBuilder addFrequencyEntry(FrequencyEntry frequencyEntry) { return this; } - public TripPattern getPattern() { - return pattern; + /** + * The direction for all the trips in this timetable. + */ + public Direction getDirection() { + return Timetable.getDirection(tripTimes.values(), frequencies); } - public LocalDate getServiceDate() { - return serviceDate; + public Timetable build() { + return new Timetable(this); } List createImmutableOrderedListOfTripTimes() { return tripTimes.values().stream().sorted().toList(); } - public List getFrequencies() { - return frequencies; + TripPattern getPattern() { + return pattern; } - /** - * The direction for all the trips in this timetable. - */ - public Direction getDirection() { - return Timetable.getDirection(tripTimes.values(), frequencies); + LocalDate getServiceDate() { + return serviceDate; } - public Timetable build() { - return new Timetable(this); + List getFrequencies() { + return frequencies; } } diff --git a/src/test/java/org/opentripplanner/gtfs/GenerateTripPatternsOperationTest.java b/src/test/java/org/opentripplanner/gtfs/GenerateTripPatternsOperationTest.java index 0451edbb40d..74819deabc2 100644 --- a/src/test/java/org/opentripplanner/gtfs/GenerateTripPatternsOperationTest.java +++ b/src/test/java/org/opentripplanner/gtfs/GenerateTripPatternsOperationTest.java @@ -269,8 +269,11 @@ void testGenerateTripPatterns2TripsDifferentStops() { static List testCases() { return List.of( + // trips with different modes Arguments.of(trip1, trip3), + // trips with different sub-modes Arguments.of(trip1, trip4), + // trips with different directions Arguments.of(trip1, trip5) ); } From e15c3d0cf376a252202f9e6a581a4461e688bd93 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Wed, 25 Sep 2024 10:09:42 +0200 Subject: [PATCH 58/80] Fix merge conflicts --- .../transit/model/_data/PatternTestModel.java | 13 ++++++------- .../trip/RealtimeTestEnvironmentBuilder.java | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/test/java/org/opentripplanner/transit/model/_data/PatternTestModel.java b/src/test/java/org/opentripplanner/transit/model/_data/PatternTestModel.java index c3afd0ddf61..0b149ae4ee4 100644 --- a/src/test/java/org/opentripplanner/transit/model/_data/PatternTestModel.java +++ b/src/test/java/org/opentripplanner/transit/model/_data/PatternTestModel.java @@ -29,18 +29,17 @@ public class PatternTestModel { * Creates a trip pattern that has a stop pattern, trip times and a trip with a service id. */ public static TripPattern pattern() { - var pattern = TransitModelForTest - .tripPattern("1", ROUTE_1) - .withStopPattern(STOP_PATTERN) - .build(); - var tt = ScheduledTripTimes .of() .withTrip(TRIP) .withArrivalTimes("10:00 10:05") .withDepartureTimes("10:00 10:05") .build(); - pattern.add(tt); - return pattern; + + return TransitModelForTest + .tripPattern("1", ROUTE_1) + .withStopPattern(STOP_PATTERN) + .withScheduledTimeTableBuilder(builder -> builder.addTripTimes(tt)) + .build(); } } diff --git a/src/test/java/org/opentripplanner/updater/trip/RealtimeTestEnvironmentBuilder.java b/src/test/java/org/opentripplanner/updater/trip/RealtimeTestEnvironmentBuilder.java index 7a79b27923e..88f4bf41012 100644 --- a/src/test/java/org/opentripplanner/updater/trip/RealtimeTestEnvironmentBuilder.java +++ b/src/test/java/org/opentripplanner/updater/trip/RealtimeTestEnvironmentBuilder.java @@ -88,8 +88,8 @@ private Trip createTrip(TripInput tripInput) { tripInput.stops().stream().map(TripInput.StopCall::stop).toList() ) ) + .withScheduledTimeTableBuilder(builder -> builder.addTripTimes(tripTimes)) .build(); - pattern.add(tripTimes); transitModel.addTripPattern(pattern.getId(), pattern); From 58b382fbfa384278ea50c8c7cf99a7ffb7553f06 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Thu, 26 Sep 2024 08:23:28 +0300 Subject: [PATCH 59/80] Update src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls Co-authored-by: Leonard Ehrenfried --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 561ec9e38cc..9bfb5f8af93 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1171,7 +1171,10 @@ type QueryType { "Departure time of the trip, format: seconds since midnight of the departure date" time: Int! ): Trip - "Try refetching the current state of a transit leg using its ID" + """ + Try refetching the current state of a transit leg using its ID. + This fails when the underlying transit data (mostly IDs) has changed or are no longer available. + """ leg(id: ID!): Leg """ Get all places (stops, stations, etc. with coordinates) within the specified From f6a8d8f57043a2571373e9e99ac44fdac642eded Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Thu, 26 Sep 2024 09:04:05 +0300 Subject: [PATCH 60/80] Suggested review changes --- .../org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java | 5 ++++- .../apis/gtfs/datafetchers/NodeTypeResolver.java | 1 - src/main/java/org/opentripplanner/model/plan/Leg.java | 2 +- .../org/opentripplanner/model/plan/ScheduledTransitLeg.java | 1 - .../apis/gtfs/expectations/plan-extended.json | 4 ++-- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java index 69a0e7b2f5e..832364fac70 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java @@ -193,7 +193,10 @@ public DataFetcher realTime() { @Override public DataFetcher realtimeState() { - return environment -> getSource(environment).getRealTimeState().name(); + return environment -> { + var state = getSource(environment).getRealTimeState(); + return (state != null) ? state.name() : null; + }; } @Override diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/NodeTypeResolver.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/NodeTypeResolver.java index f83e088024e..437d75e03e9 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/NodeTypeResolver.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/NodeTypeResolver.java @@ -8,7 +8,6 @@ import graphql.schema.TypeResolver; import org.opentripplanner.ext.fares.model.FareRuleSet; import org.opentripplanner.model.TripTimeOnDate; -import org.opentripplanner.model.plan.Leg; import org.opentripplanner.routing.alertpatch.TransitAlert; import org.opentripplanner.routing.graphfinder.NearbyStop; import org.opentripplanner.routing.graphfinder.PatternAtStop; diff --git a/src/main/java/org/opentripplanner/model/plan/Leg.java b/src/main/java/org/opentripplanner/model/plan/Leg.java index 218907f3ca8..d9e3a4589d8 100644 --- a/src/main/java/org/opentripplanner/model/plan/Leg.java +++ b/src/main/java/org/opentripplanner/model/plan/Leg.java @@ -246,7 +246,7 @@ default boolean getRealTime() { } default RealTimeState getRealTimeState() { - return RealTimeState.SCHEDULED; + return null; } /** diff --git a/src/main/java/org/opentripplanner/model/plan/ScheduledTransitLeg.java b/src/main/java/org/opentripplanner/model/plan/ScheduledTransitLeg.java index 25b329ed055..e774c9e0dbc 100644 --- a/src/main/java/org/opentripplanner/model/plan/ScheduledTransitLeg.java +++ b/src/main/java/org/opentripplanner/model/plan/ScheduledTransitLeg.java @@ -433,7 +433,6 @@ public String toString() { .addEnum("alightRule", getAlightRule()) .addObj("transferFromPrevLeg", transferFromPrevLeg) .addObj("transferToNextLeg", transferToNextLeg) - .addEnum("realtimeState", getRealTimeState()) .toString(); } diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-extended.json b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-extended.json index ab98d95e7ad..1148a8792b0 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-extended.json +++ b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-extended.json @@ -66,7 +66,7 @@ "rideHailingEstimate" : null, "accessibilityScore" : null, "id": null, - "realtimeState": "SCHEDULED" + "realtimeState": null }, { "mode" : "BUS", @@ -342,7 +342,7 @@ }, "accessibilityScore" : null, "id": null, - "realtimeState": "SCHEDULED" + "realtimeState": null } ] } From 5ef129a8349e091761f85cf83a0520a00f17af61 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 25 Sep 2024 18:01:08 +0200 Subject: [PATCH 61/80] Make search bar more compact --- .../components/ItineraryList/inputStyle.ts | 2 ++ .../SearchBar/GraphiQLRouteButton.tsx | 18 ++++++++++----- .../SearchBar/ItineraryFilterDebugSelect.tsx | 4 +++- .../SearchBar/LocationInputField.tsx | 2 ++ .../SearchBar/MultiSelectDropdown.tsx | 2 ++ .../SearchBar/NumTripPatternsInput.tsx | 4 +++- client/src/components/SearchBar/SearchBar.tsx | 22 ++++++++++--------- .../SearchBar/SearchWindowInput.tsx | 2 ++ client/src/static/img/graphql-solid.svg | 4 ++++ client/src/static/img/graphql.svg | 4 ++++ client/src/style.css | 4 ++-- 11 files changed, 49 insertions(+), 19 deletions(-) create mode 100644 client/src/components/ItineraryList/inputStyle.ts create mode 100644 client/src/static/img/graphql-solid.svg create mode 100644 client/src/static/img/graphql.svg diff --git a/client/src/components/ItineraryList/inputStyle.ts b/client/src/components/ItineraryList/inputStyle.ts new file mode 100644 index 00000000000..e700456a384 --- /dev/null +++ b/client/src/components/ItineraryList/inputStyle.ts @@ -0,0 +1,2 @@ +export const smallInputStyle = { maxWidth: '100px' }; +export const mediumInputStyle = { maxWidth: '130px' }; diff --git a/client/src/components/SearchBar/GraphiQLRouteButton.tsx b/client/src/components/SearchBar/GraphiQLRouteButton.tsx index 550964dd5a8..e86cf4f7670 100644 --- a/client/src/components/SearchBar/GraphiQLRouteButton.tsx +++ b/client/src/components/SearchBar/GraphiQLRouteButton.tsx @@ -1,6 +1,8 @@ import { Button } from 'react-bootstrap'; import { TripQueryVariables } from '../../gql/graphql.ts'; import { queryAsString } from '../../static/query/tripQuery.tsx'; +import logo from '../../static/img/graphql-solid.svg'; + const graphiQLUrl = import.meta.env.VITE_GRAPHIQL_URL; function GraphiQLRouteButton({ tripQueryVariables }: { tripQueryVariables: TripQueryVariables }) { @@ -8,11 +10,17 @@ function GraphiQLRouteButton({ tripQueryVariables }: { tripQueryVariables: TripQ const formattedQuery = encodeURIComponent(queryAsString); return ( -
- -
+ ); } + export default GraphiQLRouteButton; diff --git a/client/src/components/SearchBar/ItineraryFilterDebugSelect.tsx b/client/src/components/SearchBar/ItineraryFilterDebugSelect.tsx index 636ba551541..e32a63eee06 100644 --- a/client/src/components/SearchBar/ItineraryFilterDebugSelect.tsx +++ b/client/src/components/SearchBar/ItineraryFilterDebugSelect.tsx @@ -1,5 +1,6 @@ import { Form } from 'react-bootstrap'; import { ItineraryFilterDebugProfile, TripQueryVariables } from '../../gql/graphql.ts'; +import { mediumInputStyle } from '../ItineraryList/inputStyle.ts'; export function ItineraryFilterDebugSelect({ tripQueryVariables, @@ -11,11 +12,12 @@ export function ItineraryFilterDebugSelect({ return ( - Itinerary filter debug + Filter debug { setTripQueryVariables({ ...tripQueryVariables, diff --git a/client/src/components/SearchBar/LocationInputField.tsx b/client/src/components/SearchBar/LocationInputField.tsx index ffa66702e81..b5797b51ecc 100644 --- a/client/src/components/SearchBar/LocationInputField.tsx +++ b/client/src/components/SearchBar/LocationInputField.tsx @@ -1,6 +1,7 @@ import { Form } from 'react-bootstrap'; import { COORDINATE_PRECISION } from './constants.ts'; import { Location } from '../../gql/graphql.ts'; +import { mediumInputStyle } from '../ItineraryList/inputStyle.ts'; export function LocationInputField({ location, id, label }: { location: Location; id: string; label: string }) { return ( @@ -13,6 +14,7 @@ export function LocationInputField({ location, id, label }: { location: Location id={id} size="sm" placeholder="[Click in map]" + style={mediumInputStyle} // Intentionally empty for now, but needed because of // https://react.dev/reference/react-dom/components/input#controlling-an-input-with-a-state-variable onChange={() => {}} diff --git a/client/src/components/SearchBar/MultiSelectDropdown.tsx b/client/src/components/SearchBar/MultiSelectDropdown.tsx index fc20e6822ac..f053dab6f48 100644 --- a/client/src/components/SearchBar/MultiSelectDropdown.tsx +++ b/client/src/components/SearchBar/MultiSelectDropdown.tsx @@ -1,5 +1,6 @@ import { ChangeEvent, useState } from 'react'; import { Form } from 'react-bootstrap'; +import { mediumInputStyle } from '../ItineraryList/inputStyle.ts'; type MultiSelectDropdownOption = { id: T; @@ -40,6 +41,7 @@ const MultiSelectDropdown = ({ label, options, values, onChange }: type="text" id="multiSelectDropdown" size="sm" + style={mediumInputStyle} value={values.length > 0 ? values.join(', ') : 'Not selected'} onClick={toggleDropdown} onChange={() => {}} diff --git a/client/src/components/SearchBar/NumTripPatternsInput.tsx b/client/src/components/SearchBar/NumTripPatternsInput.tsx index b77e70adb81..f2fe42a5a0f 100644 --- a/client/src/components/SearchBar/NumTripPatternsInput.tsx +++ b/client/src/components/SearchBar/NumTripPatternsInput.tsx @@ -1,5 +1,6 @@ import { Form } from 'react-bootstrap'; import { TripQueryVariables } from '../../gql/graphql.ts'; +import { smallInputStyle } from '../ItineraryList/inputStyle.ts'; export function NumTripPatternsInput({ tripQueryVariables, @@ -11,7 +12,7 @@ export function NumTripPatternsInput({ return ( - Number of trip patterns + Num. results setTripQueryVariables({ diff --git a/client/src/components/SearchBar/SearchBar.tsx b/client/src/components/SearchBar/SearchBar.tsx index ca072fa5589..6bf03a29a18 100644 --- a/client/src/components/SearchBar/SearchBar.tsx +++ b/client/src/components/SearchBar/SearchBar.tsx @@ -1,4 +1,4 @@ -import { Button, Spinner } from 'react-bootstrap'; +import { Button, ButtonGroup, Spinner } from 'react-bootstrap'; import { ServerInfo, TripQueryVariables } from '../../gql/graphql.ts'; import { LocationInputField } from './LocationInputField.tsx'; import { DepartureArrivalSelect } from './DepartureArrivalSelect.tsx'; @@ -51,16 +51,18 @@ export function SearchBar({ onRoute, tripQueryVariables, setTripQueryVariables, setTripQueryVariables={setTripQueryVariables} />
- + + + +
-
); } diff --git a/client/src/components/SearchBar/SearchWindowInput.tsx b/client/src/components/SearchBar/SearchWindowInput.tsx index 5442784de8e..0bccf5ba643 100644 --- a/client/src/components/SearchBar/SearchWindowInput.tsx +++ b/client/src/components/SearchBar/SearchWindowInput.tsx @@ -1,5 +1,6 @@ import { Form } from 'react-bootstrap'; import { TripQueryVariables } from '../../gql/graphql.ts'; +import { smallInputStyle } from '../ItineraryList/inputStyle.ts'; export function SearchWindowInput({ tripQueryVariables, @@ -19,6 +20,7 @@ export function SearchWindowInput({ size="sm" placeholder="(in minutes)" min={1} + style={smallInputStyle} value={tripQueryVariables.searchWindow || ''} onChange={(event) => setTripQueryVariables({ diff --git a/client/src/static/img/graphql-solid.svg b/client/src/static/img/graphql-solid.svg new file mode 100644 index 00000000000..32d6e5e0f00 --- /dev/null +++ b/client/src/static/img/graphql-solid.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/src/static/img/graphql.svg b/client/src/static/img/graphql.svg new file mode 100644 index 00000000000..ef85915ffaa --- /dev/null +++ b/client/src/static/img/graphql.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/src/style.css b/client/src/style.css index 37100d65006..400c9e84b9d 100644 --- a/client/src/style.css +++ b/client/src/style.css @@ -7,7 +7,7 @@ margin-right: 14px; } -@media (min-width: 2160px) { +@media (min-width: 1790px) { .top-content { height: 75px; } @@ -17,7 +17,7 @@ } } -@media (max-width: 2159px) { +@media (max-width: 1791px) { .top-content { height: 150px; } From ef4a6c3eb602993c159824916047411821a4b7af Mon Sep 17 00:00:00 2001 From: a-limyr Date: Thu, 26 Sep 2024 10:34:30 +0200 Subject: [PATCH 62/80] Added wheelchair accessible checkbox parameter --- client/src/components/SearchBar/SearchBar.tsx | 6 +++++ .../WheelchairAccessibleCheckBox.tsx | 27 +++++++++++++++++++ client/src/static/query/tripQuery.tsx | 2 ++ 3 files changed, 35 insertions(+) create mode 100644 client/src/components/SearchBar/WheelchairAccessibleCheckBox.tsx diff --git a/client/src/components/SearchBar/SearchBar.tsx b/client/src/components/SearchBar/SearchBar.tsx index dfcbc6ac36e..4c4b13945b5 100644 --- a/client/src/components/SearchBar/SearchBar.tsx +++ b/client/src/components/SearchBar/SearchBar.tsx @@ -16,6 +16,7 @@ import { ServerInfoTooltip } from './ServerInfoTooltip.tsx'; import { useRef, useState } from 'react'; import logo from '../../static/img/otp-logo.svg'; import GraphiQLRouteButton from './GraphiQLRouteButton.tsx'; +import WheelchairAccessibleCheckBox from './WheelchairAccessibleCheckBox.tsx'; type SearchBarProps = { onRoute: () => void; @@ -52,6 +53,11 @@ export function SearchBar({ onRoute, tripQueryVariables, setTripQueryVariables, tripQueryVariables={tripQueryVariables} setTripQueryVariables={setTripQueryVariables} /> + +
{' '} ); } From b174c25cf5aab8e4a8496754ba4cef761f9ab287 Mon Sep 17 00:00:00 2001 From: OTP Bot Date: Mon, 30 Sep 2024 09:04:02 +0000 Subject: [PATCH 80/80] Upgrade debug client to version 2024/09/2024-09-30T09:03 --- src/client/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/index.html b/src/client/index.html index 4e77baa2010..88764c84a17 100644 --- a/src/client/index.html +++ b/src/client/index.html @@ -5,8 +5,8 @@ OTP Debug Client - - + +