Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@
<dependency>
<groupId>org.onebusaway</groupId>
<artifactId>onebusaway-gtfs</artifactId>
<version>12.0.1</version>
<version>14.0.0</version>
</dependency>
<!-- Used in DegreeGridNEDTileSource to fetch tiles from Amazon S3 -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.opentripplanner.apis.gtfs.datafetchers.LocationImpl;
import org.opentripplanner.apis.gtfs.datafetchers.MoneyImpl;
import org.opentripplanner.apis.gtfs.datafetchers.NodeTypeResolver;
import org.opentripplanner.apis.gtfs.datafetchers.NoticeImpl;
import org.opentripplanner.apis.gtfs.datafetchers.OpeningHoursImpl;
import org.opentripplanner.apis.gtfs.datafetchers.PatternImpl;
import org.opentripplanner.apis.gtfs.datafetchers.PlaceImpl;
Expand Down Expand Up @@ -216,6 +217,7 @@ public static GraphQLSchema createSchema() {
.type(typeWiring.build(StairsUseImpl.class))
.type(typeWiring.build(ElevatorUseImpl.class))
.type(typeWiring.build(RiderCategoryImpl.class))
.type(typeWiring.build(NoticeImpl.class))
.build();

SchemaGenerator schemaGenerator = new SchemaGenerator();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.opentripplanner.apis.gtfs.datafetchers;

import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers;
import org.opentripplanner.transit.model.basic.Notice;

public class NoticeImpl implements GraphQLDataFetchers.GraphQLNotice {

@Override
public DataFetcher<String> text() {
return env -> source(env).text();
}

private static Notice source(DataFetchingEnvironment env) {
return env.getSource();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.opentripplanner.routing.alertpatch.EntitySelector;
import org.opentripplanner.routing.alertpatch.TransitAlert;
import org.opentripplanner.routing.services.TransitAlertService;
import org.opentripplanner.transit.model.basic.Notice;
import org.opentripplanner.transit.model.network.Route;
import org.opentripplanner.transit.model.network.TripPattern;
import org.opentripplanner.transit.model.organization.Agency;
Expand Down Expand Up @@ -185,6 +186,11 @@ public DataFetcher<GraphQLTransitMode> mode() {
return environment -> TransitModeMapper.map(getSource(environment).getMode());
}

@Override
public DataFetcher<Iterable<Notice>> notices() {
return env -> getTransitService(env).findNotices(getSource(env));
}

@Override
public DataFetcher<Iterable<TripPattern>> patterns() {
return environment -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.opentripplanner.routing.alertpatch.TransitAlert;
import org.opentripplanner.routing.services.TransitAlertService;
import org.opentripplanner.service.realtimevehicles.RealtimeVehicleService;
import org.opentripplanner.transit.model.basic.Notice;
import org.opentripplanner.transit.model.network.Route;
import org.opentripplanner.transit.model.network.TripPattern;
import org.opentripplanner.transit.model.organization.Agency;
Expand Down Expand Up @@ -265,6 +266,11 @@ public DataFetcher<Boolean> isReplacement() {
.isReplacementTrip(getSource(environment));
}

@Override
public DataFetcher<Iterable<Notice>> notices() {
return env -> getTransitService(env).findNotices(getSource(env));
}

@Override
public DataFetcher<TripPattern> pattern() {
return this::getTripPattern;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,10 @@ public default DataFetcher<graphql.relay.Relay.ResolvedGlobalId> id() {
}
}

public interface GraphQLNotice {
public DataFetcher<String> text();
}

public interface GraphQLOpeningHours {
public DataFetcher<Iterable<Object>> dates();
public DataFetcher<String> osm();
Expand Down Expand Up @@ -834,6 +838,7 @@ public interface GraphQLRoute {
public DataFetcher<Boolean> isReplacement();
public DataFetcher<String> longName();
public DataFetcher<GraphQLTransitMode> mode();
public DataFetcher<Iterable<org.opentripplanner.transit.model.basic.Notice>> notices();
public DataFetcher<Iterable<TripPattern>> patterns();
public DataFetcher<Boolean> replacementsExist();
public DataFetcher<String> shortName();
Expand Down Expand Up @@ -1022,6 +1027,7 @@ public interface GraphQLTrip {
public DataFetcher<String> gtfsId();
public DataFetcher<graphql.relay.Relay.ResolvedGlobalId> id();
public DataFetcher<Boolean> isReplacement();
public DataFetcher<Iterable<org.opentripplanner.transit.model.basic.Notice>> notices();
public DataFetcher<TripOccupancy> occupancy();
public DataFetcher<TripPattern> pattern();
public DataFetcher<Boolean> replacementsExist();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,4 @@ config:
CanceledTripsSummary: org.opentripplanner.apis.gtfs.model.CanceledTripsSummary#CanceledTripsSummary
CanceledTripsSummaryPattern: org.opentripplanner.apis.gtfs.model.CanceledTripsSummaryPattern#CanceledTripsSummaryPattern
CanceledTripsSummaryRoute: org.opentripplanner.apis.gtfs.model.CanceledTripsSummaryRoute#CanceledTripsSummaryRoute
Notice: org.opentripplanner.transit.model.basic.Notice
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ public class GTFSToTransitDataImportMapper {

private final StopAreaMapper stopAreaMapper;

private final NoticeMapper noticeMapper;
private final NoticeAssignmentMapper noticeAssignmentMapper;

private final TranslationHelper translationHelper;
private final boolean discardMinTransferTimes;

Expand Down Expand Up @@ -167,6 +170,14 @@ public GTFSToTransitDataImportMapper(
);
fareTransferRuleMapper = new FareTransferRuleMapper(idFactory, fareProductMapper);
stopAreaMapper = new StopAreaMapper(idFactory);
noticeMapper = new NoticeMapper(idFactory);
noticeAssignmentMapper = new NoticeAssignmentMapper(
idFactory,
issueStore,
noticeMapper,
tripMapper,
routeMapper
);
}

public TransitDataImportBuilder getBuilder() {
Expand Down Expand Up @@ -218,6 +229,11 @@ public void mapStopTripAndRouteDataIntoBuilder(GtfsRelationalDao data) {
.fareTransferRules()
.addAll(fareTransferRuleMapper.map(data.getAllFareTransferRules()));
fareRulesBuilder.stopAreas().putAll(stopAreaMapper.map(data.getAllStopAreaElements()));

noticeMapper.map(data.getAllNotices());
builder
.getNoticeAssignments()
.putAll(noticeAssignmentMapper.map(data.getAllNoticeAssignments()));
}

private void mapGtfsStopsToOtpTypes(Collection<Stop> stops) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package org.opentripplanner.gtfs.mapping;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.onebusaway.gtfs.model.NoticeAssignment;
import org.opentripplanner.core.model.id.FeedScopedId;
import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore;
import org.opentripplanner.transit.model.basic.Notice;
import org.opentripplanner.transit.model.framework.AbstractTransitEntity;
import org.opentripplanner.transit.model.network.Route;
import org.opentripplanner.transit.model.timetable.Trip;

/**
* Maps GTFS notice_assignments.txt entries to OTP notice assignments, connecting each
* {@link Notice} to its target {@link Route} or {@link Trip}.
*/
class NoticeAssignmentMapper {

private final IdFactory idFactory;
private final DataImportIssueStore issueStore;
private final NoticeMapper noticeMapper;
private final RouteMapper routeMapper;
private final TripMapper tripMapper;

NoticeAssignmentMapper(
IdFactory idFactory,
DataImportIssueStore issueStore,
NoticeMapper noticeMapper,
TripMapper tripMapper,
RouteMapper routeMapper
) {
this.idFactory = idFactory;
this.issueStore = issueStore;
this.noticeMapper = noticeMapper;
this.routeMapper = routeMapper;
this.tripMapper = tripMapper;
}

Multimap<AbstractTransitEntity, Notice> map(Collection<NoticeAssignment> assignments) {
Multimap<AbstractTransitEntity, Notice> result = ArrayListMultimap.create();
var trips = tripMapper
.getMappedTrips()
.stream()
.collect(Collectors.toMap(Trip::getId, Function.identity()));
var routes = routeMapper.mappedRoutes();
for (var assignment : assignments) {
mapOne(assignment, noticeMapper.mappedNotices(), trips, routes).ifPresent(entry ->
result.put(entry.getKey(), entry.getValue())
);
}
return result;
}

private Optional<Map.Entry<AbstractTransitEntity, Notice>> mapOne(
NoticeAssignment assignment,
Comment thread
leonardehrenfried marked this conversation as resolved.
Map<FeedScopedId, Notice> notices,
Map<FeedScopedId, Trip> trips,
Map<FeedScopedId, Route> routes
) {
var notice = notices.get(
idFactory.createId(assignment.getNoticeId(), "notice_id in notice assignment")
);
if (notice == null) {
issueStore.add(
"NoticeAssignmentWithoutNotice",
"Notice in notice assignment is missing for assignment %s",
assignment
);
return Optional.empty();
}

var recordId = idFactory.createId(assignment.getRecordId(), "NoticeAssignment.recordId");

AbstractTransitEntity entity = switch (assignment.getTableName()) {
case routes -> routes.get(recordId);
case trips -> trips.get(recordId);
};

if (entity == null) {
issueStore.add(
"NoticeAssignmentWithUnknownEntity",
"Could not map notice assignment %s for %s with id %s",
assignment.getId(),
assignment.getTableName(),
assignment.getRecordId()
);
return Optional.empty();
}

return Optional.of(Map.entry(entity, notice));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.opentripplanner.gtfs.mapping;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.opentripplanner.core.model.id.FeedScopedId;
import org.opentripplanner.transit.model.basic.Notice;

/**
* Responsible for mapping onebusaway GTFS Notice into the OTP model.
* Caches mapped instances so that the same GTFS notice produces the same OTP notice object.
*/
class NoticeMapper {

private final IdFactory idFactory;
private final Map<FeedScopedId, Notice> cache = new HashMap<>();

NoticeMapper(IdFactory idFactory) {
this.idFactory = idFactory;
}

Collection<Notice> map(Collection<org.onebusaway.gtfs.model.Notice> notices) {
return notices.stream().map(this::map).toList();
}

Notice map(org.onebusaway.gtfs.model.Notice gtfsNotice) {
var notice = Notice.of(idFactory.createId(gtfsNotice.getId(), "Notice"))
.withText(gtfsNotice.getDisplayText())
.build();
return cache.computeIfAbsent(notice.getId(), _ -> notice);
}

Map<FeedScopedId, Notice> mappedNotices() {
return Collections.unmodifiableMap(cache);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.opentripplanner.core.model.i18n.I18NString;
import org.opentripplanner.core.model.id.FeedScopedId;
import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore;
Expand Down Expand Up @@ -104,4 +106,11 @@ private Collection<FeedScopedId> networkId(org.onebusaway.gtfs.model.Route rhs)
return List.of(idFactory.createId(rhs.getNetworkId(), "route's network"));
}
}

Map<FeedScopedId, Route> mappedRoutes() {
return mappedRoutes
.values()
.stream()
.collect(Collectors.toMap(Route::getId, Function.identity()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1050,6 +1050,17 @@ type Money {
currency: Currency!
}

"""
A textual message about a transit entity that is already known at planning time.

It is not intended to convey real-time or emergency updates of the transit system but information that
is known well ahead of time.
"""
type Notice {
"Textual representation of the message"
text: String
}

type OpeningHours {
"""
Opening hours for the selected dates using the local time of the parking lot.
Expand Down Expand Up @@ -2243,6 +2254,8 @@ type Route implements Node {
): String
"Transport mode of this route, e.g. `BUS`"
mode: TransitMode
"Notices of this trip which are known ahead of time."
notices: [Notice!]! @deprecated(reason : "Experimental API that can be changed without notice.")
Comment thread
leonardehrenfried marked this conversation as resolved.
"List of patterns which operate on this route"
patterns(
"""
Expand Down Expand Up @@ -2749,6 +2762,8 @@ type Trip implements Node {
link in a DatedServiceJourney.
"""
isReplacement: Boolean!
"Notices of the trip that are known ahead of time."
notices: [Notice!]! @deprecated(reason : "Experimental API that can be changed without notice.")
"""
The latest real-time occupancy information for the latest occurance of this
trip.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.opentripplanner.apis.gtfs.datafetchers;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.opentripplanner.apis.support.graphql.DataFetchingSupport.dataFetchingEnvironment;
import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id;

import org.junit.jupiter.api.Test;
import org.opentripplanner.transit.model.basic.Notice;

class NoticeImplTest {

private static final NoticeImpl SUBJECT = new NoticeImpl();
private static final Notice NOTICE = Notice.of(id(1)).withText("AAA").build();

@Test
void text() throws Exception {
var env = dataFetchingEnvironment(NOTICE);
assertEquals("AAA", SUBJECT.text().get(env));
}
}
Loading
Loading