Skip to content

Commit

Permalink
Merge pull request #5660 from leonardehrenfried/iso-datetime
Browse files Browse the repository at this point in the history
ISO-8601 date time for GTFS API itinerary responses
  • Loading branch information
leonardehrenfried authored Mar 13, 2024
2 parents 8c78db1 + 2f763e9 commit 370191e
Show file tree
Hide file tree
Showing 37 changed files with 1,114 additions and 193 deletions.
24 changes: 16 additions & 8 deletions docs/apis/GraphQL-Tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,25 +88,33 @@ Most people want to get routing results out of OTP, so lets see the query for th
},
]) {
itineraries {
startTime
endTime
start
end
legs {
mode
startTime
endTime
from {
name
lat
lon
departureTime
arrivalTime
departure {
scheduledTime
estimated {
time
delay
}
}
}
to {
name
lat
lon
departureTime
arrivalTime
arrival {
scheduledTime
estimated {
time
delay
}
}
}
route {
gtfsId
Expand Down
3 changes: 2 additions & 1 deletion magidoc.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ To learn how to deactivate it, read the
appTitle: 'OTP GTFS GraphQL API',
queryGenerationFactories: {
'Polyline': '<>',
'GeoJson': '<>'
'GeoJson': '<>',
'OffsetDateTime': '2024-02-05T18:04:23+01:00'
},
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.opentripplanner.framework.collection.ListUtils;
import org.opentripplanner.model.fare.FareProductUse;
import org.opentripplanner.model.plan.Leg;
import org.opentripplanner.model.plan.LegTime;
import org.opentripplanner.model.plan.Place;
import org.opentripplanner.model.plan.StopArrival;
import org.opentripplanner.model.plan.TransitLeg;
Expand Down Expand Up @@ -56,6 +57,16 @@ public Trip getTrip() {
return first.getTrip();
}

@Override
public LegTime start() {
return first.start();
}

@Override
public LegTime end() {
return second.end();
}

@Override
public ZonedDateTime getStartTime() {
return first.getStartTime();
Expand Down
11 changes: 11 additions & 0 deletions src/ext/java/org/opentripplanner/ext/flex/FlexibleTransitLeg.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.opentripplanner.model.PickDrop;
import org.opentripplanner.model.fare.FareProductUse;
import org.opentripplanner.model.plan.Leg;
import org.opentripplanner.model.plan.LegTime;
import org.opentripplanner.model.plan.Place;
import org.opentripplanner.model.plan.StopArrival;
import org.opentripplanner.model.plan.TransitLeg;
Expand Down Expand Up @@ -84,6 +85,16 @@ public Accessibility getTripWheelchairAccessibility() {
return edge.getFlexTrip().getTrip().getWheelchairBoarding();
}

@Override
public LegTime start() {
return LegTime.ofStatic(startTime);
}

@Override
public LegTime end() {
return LegTime.ofStatic(endTime);
}

@Override
@Nonnull
public TransitMode getMode() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ public List<ApiPlace> mapStopArrivals(Collection<StopArrival> domain) {
public ApiPlace mapStopArrival(StopArrival domain) {
return mapPlace(
domain.place,
domain.arrival,
domain.departure,
domain.arrival.time(),
domain.departure.time(),
domain.stopPosInPattern,
domain.gtfsStopSequence
);
Expand Down
72 changes: 67 additions & 5 deletions src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,24 @@
import graphql.schema.CoercingParseValueException;
import graphql.schema.CoercingSerializeException;
import graphql.schema.GraphQLScalarType;
import java.text.ParseException;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import javax.annotation.Nonnull;
import org.locationtech.jts.geom.Geometry;
import org.opentripplanner.framework.geometry.GeometryUtils;
import org.opentripplanner.framework.graphql.scalar.DurationScalarFactory;
import org.opentripplanner.framework.model.Grams;
import org.opentripplanner.framework.time.OffsetDateTimeParser;

public class GraphQLScalars {

private static final ObjectMapper geoJsonMapper = new ObjectMapper()
.registerModule(new JtsModule(GeometryUtils.getGeometryFactory()));
public static GraphQLScalarType durationScalar = DurationScalarFactory.createDurationScalar();
public static GraphQLScalarType DURATION_SCALAR = DurationScalarFactory.createDurationScalar();

public static GraphQLScalarType polylineScalar = GraphQLScalarType
public static final GraphQLScalarType POLYLINE_SCALAR = GraphQLScalarType
.newScalar()
.name("Polyline")
.description(
Expand Down Expand Up @@ -50,7 +56,63 @@ public String parseLiteral(Object input) {
)
.build();

public static GraphQLScalarType geoJsonScalar = GraphQLScalarType
public static final GraphQLScalarType OFFSET_DATETIME_SCALAR = GraphQLScalarType
.newScalar()
.name("OffsetDateTime")
.coercing(
new Coercing<OffsetDateTime, String>() {
@Override
public String serialize(@Nonnull Object dataFetcherResult)
throws CoercingSerializeException {
if (dataFetcherResult instanceof ZonedDateTime zdt) {
return zdt.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
} else if (dataFetcherResult instanceof OffsetDateTime odt) {
return odt.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
} else {
throw new CoercingSerializeException(
"Cannot serialize object of class %s".formatted(
dataFetcherResult.getClass().getSimpleName()
)
);
}
}

@Override
public OffsetDateTime parseValue(Object input) throws CoercingParseValueException {
if (input instanceof CharSequence cs) {
try {
return OffsetDateTimeParser.parseLeniently(cs);
} catch (ParseException e) {
int errorOffset = e.getErrorOffset();
throw new CoercingParseValueException(
"Cannot parse %s into an OffsetDateTime. Error at character index %s".formatted(
input,
errorOffset
)
);
}
}
throw new CoercingParseValueException(
"Cannot parse %s into an OffsetDateTime. Must be a string."
);
}

@Override
public OffsetDateTime parseLiteral(Object input) throws CoercingParseLiteralException {
if (input instanceof StringValue sv) {
try {
return OffsetDateTimeParser.parseLeniently(sv.getValue());
} catch (ParseException e) {
throw new CoercingSerializeException();
}
}
throw new CoercingParseLiteralException();
}
}
)
.build();

public static final GraphQLScalarType GEOJSON_SCALAR = GraphQLScalarType
.newScalar()
.name("GeoJson")
.description("Geographic data structures in JSON format. See: https://geojson.org/")
Expand Down Expand Up @@ -78,7 +140,7 @@ public Geometry parseLiteral(Object input) throws CoercingParseLiteralException
)
.build();

public static GraphQLScalarType graphQLIDScalar = GraphQLScalarType
public static final GraphQLScalarType GRAPHQL_ID_SCALAR = GraphQLScalarType
.newScalar()
.name("ID")
.coercing(
Expand Down Expand Up @@ -118,7 +180,7 @@ public Relay.ResolvedGlobalId parseLiteral(Object input)
)
.build();

public static GraphQLScalarType gramsScalar = GraphQLScalarType
public static final GraphQLScalarType GRAMS_SCALAR = GraphQLScalarType
.newScalar()
.name("Grams")
.coercing(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,12 @@ protected static GraphQLSchema buildSchema() {
IntrospectionTypeWiring typeWiring = new IntrospectionTypeWiring(typeRegistry);
RuntimeWiring runtimeWiring = RuntimeWiring
.newRuntimeWiring()
.scalar(GraphQLScalars.durationScalar)
.scalar(GraphQLScalars.polylineScalar)
.scalar(GraphQLScalars.geoJsonScalar)
.scalar(GraphQLScalars.graphQLIDScalar)
.scalar(GraphQLScalars.gramsScalar)
.scalar(GraphQLScalars.DURATION_SCALAR)
.scalar(GraphQLScalars.POLYLINE_SCALAR)
.scalar(GraphQLScalars.GEOJSON_SCALAR)
.scalar(GraphQLScalars.GRAPHQL_ID_SCALAR)
.scalar(GraphQLScalars.GRAMS_SCALAR)
.scalar(GraphQLScalars.OFFSET_DATETIME_SCALAR)
.scalar(ExtendedScalars.GraphQLLong)
.type("Node", type -> type.typeResolver(new NodeTypeResolver()))
.type("PlaceInterface", type -> type.typeResolver(new PlaceInterfaceTypeResolver()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import java.time.OffsetDateTime;
import java.util.List;
import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers;
import org.opentripplanner.apis.gtfs.mapping.NumberMapper;
Expand Down Expand Up @@ -32,6 +33,12 @@ public DataFetcher<Double> elevationLost() {
return environment -> getSource(environment).getElevationLost();
}

@Override
public DataFetcher<OffsetDateTime> end() {
return environment -> getSource(environment).endTime().toOffsetDateTime();
}

@Deprecated
@Override
public DataFetcher<Long> endTime() {
return environment -> getSource(environment).endTime().toInstant().toEpochMilli();
Expand All @@ -57,6 +64,12 @@ public DataFetcher<Integer> numberOfTransfers() {
return environment -> getSource(environment).getNumberOfTransfers();
}

@Override
public DataFetcher<OffsetDateTime> start() {
return environment -> getSource(environment).startTime().toOffsetDateTime();
}

@Deprecated
@Override
public DataFetcher<Long> startTime() {
return environment -> getSource(environment).startTime().toInstant().toEpochMilli();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.opentripplanner.model.PickDrop;
import org.opentripplanner.model.fare.FareProductUse;
import org.opentripplanner.model.plan.Leg;
import org.opentripplanner.model.plan.LegTime;
import org.opentripplanner.model.plan.ScheduledTransitLeg;
import org.opentripplanner.model.plan.StopArrival;
import org.opentripplanner.model.plan.StreetLeg;
Expand Down Expand Up @@ -78,6 +79,12 @@ public DataFetcher<Double> duration() {
}

@Override
public DataFetcher<LegTime> end() {
return environment -> getSource(environment).end();
}

@Override
@Deprecated
public DataFetcher<Long> endTime() {
return environment -> getSource(environment).getEndTime().toInstant().toEpochMilli();
}
Expand All @@ -93,8 +100,8 @@ public DataFetcher<StopArrival> from() {
Leg source = getSource(environment);
return new StopArrival(
source.getFrom(),
source.getStartTime(),
source.getStartTime(),
source.start(),
source.start(),
source.getBoardStopPosInPattern(),
source.getBoardingGtfsStopSequence()
);
Expand Down Expand Up @@ -216,6 +223,12 @@ public DataFetcher<String> serviceDate() {
}

@Override
public DataFetcher<LegTime> start() {
return environment -> getSource(environment).start();
}

@Override
@Deprecated
public DataFetcher<Long> startTime() {
return environment -> getSource(environment).getStartTime().toInstant().toEpochMilli();
}
Expand All @@ -231,8 +244,8 @@ public DataFetcher<StopArrival> to() {
Leg source = getSource(environment);
return new StopArrival(
source.getTo(),
source.getEndTime(),
source.getEndTime(),
source.end(),
source.end(),
source.getAlightStopPosInPattern(),
source.getAlightGtfsStopSequence()
);
Expand Down
Loading

0 comments on commit 370191e

Please sign in to comment.