Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
package org.opentripplanner.ext.siri;
import org.opentripplanner.model.FeedScopedId;
import org.opentripplanner.model.Route;
import org.opentripplanner.model.calendar.ServiceDate;
import org.opentripplanner.routing.alertpatch.AlertUrl;
import org.opentripplanner.routing.alertpatch.EntitySelector;
import org.opentripplanner.routing.alertpatch.StopCondition;
import org.opentripplanner.routing.alertpatch.TimePeriod;
import org.opentripplanner.routing.alertpatch.TransitAlert;
import org.opentripplanner.routing.core.TraverseMode;
import org.opentripplanner.routing.services.TransitAlertService;
import org.opentripplanner.util.I18NString;
import org.opentripplanner.util.NonLocalizedString;
import org.opentripplanner.util.TranslatedString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import uk.org.ifopt.siri20.StopPlaceRef;
import uk.org.siri.siri20.*;
import java.io.Serializable;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* This updater applies the equivalent of GTFS Alerts, but from SIRI Situation Exchange feeds.
* NOTE this cannot handle situations where there are multiple feeds with different IDs (for now it may only work in
* single-feed regions).
*/
public class SiriAlertsUpdateHandler {
private static final Logger LOG = LoggerFactory.getLogger(SiriAlertsUpdateHandler.class);
private TransitAlertService transitAlertService;
/** How long before the posted start of an event it should be displayed to users */
private long earlyStart;
private SiriFuzzyTripMatcher siriFuzzyTripMatcher;
private String feedId;
private final Set alerts = new HashSet<>();
public SiriAlertsUpdateHandler(String feedId) {
this.feedId = feedId;
}
public void update(ServiceDelivery delivery) {
for (SituationExchangeDeliveryStructure sxDelivery : delivery.getSituationExchangeDeliveries()) {
SituationExchangeDeliveryStructure.Situations situations = sxDelivery.getSituations();
if (situations != null) {
long t1 = System.currentTimeMillis();
Set idsToExpire = new HashSet<>();
for (PtSituationElement sxElement : situations.getPtSituationElements()) {
boolean expireSituation = (sxElement.getProgress() != null &&
sxElement.getProgress().equals(WorkflowStatusEnumeration.CLOSED));
String situationNumber;
if (sxElement.getSituationNumber() != null) {
situationNumber = sxElement.getSituationNumber().getValue();
} else {
situationNumber = null;
}
if (expireSituation) {
alerts.removeIf(transitAlert -> transitAlert.getId().equals(situationNumber));
} else {
TransitAlert alert = handleAlert(sxElement);
if (alert != null) {
alerts.removeIf(transitAlert -> transitAlert.getId().equals(situationNumber));
alerts.add(alert);
if (alert.getEntities().isEmpty()) {
LOG.info("No match found for Alert - ignoring situation with situationNumber {}", situationNumber);
}
}
}
}
transitAlertService.setAlerts(alerts);
LOG.info("Added {} alerts, expired {} alerts based on {} situations, current alert-count: {}, elapsed time {}ms", alerts.size(), idsToExpire.size(), situations.getPtSituationElements().size(), transitAlertService
.getAllAlerts().size(), (System.currentTimeMillis()-t1));
}
}
}
private TransitAlert handleAlert(PtSituationElement situation) {
TransitAlert alert = createAlertWithTexts(situation);
//ROR-54
//If situation is closed, it must be allowed - it will remove already existing alerts
if ((alert.alertHeaderText == null || alert.alertHeaderText.toString().isEmpty()) && (
alert.alertDescriptionText == null || alert.alertDescriptionText.toString().isEmpty()
) && (alert.alertDetailText == null || alert.alertDetailText.toString().isEmpty())) {
LOG.debug("Empty Alert - ignoring situationNumber: {}", situation.getSituationNumber() != null ? situation.getSituationNumber().getValue():null);
return null;
}
ArrayList periods = new ArrayList<>();
if(situation.getValidityPeriods().size() > 0) {
for (HalfOpenTimestampOutputRangeStructure activePeriod : situation.getValidityPeriods()) {
final long realStart = activePeriod.getStartTime() != null ? activePeriod.getStartTime().toInstant().toEpochMilli() : 0;
final long start = activePeriod.getStartTime() != null? realStart - earlyStart : 0;
final long realEnd = activePeriod.getEndTime() != null ? activePeriod.getEndTime().toInstant().toEpochMilli() : 0;
final long end = activePeriod.getEndTime() != null? realEnd : 0;
periods.add(new TimePeriod(start/1000, end/1000));
}
} else {
// Per the GTFS-rt spec, if an alert has no TimeRanges, than it should always be shown.
periods.add(new TimePeriod(0, Long.MAX_VALUE));
}
alert.setTimePeriods(periods);
AffectsScopeStructure affectsStructure = situation.getAffects();
if (affectsStructure != null) {
AffectsScopeStructure.Operators operators = affectsStructure.getOperators();
if (operators != null && !isListNullOrEmpty(operators.getAffectedOperators())) {
for (AffectedOperatorStructure affectedOperator : operators.getAffectedOperators()) {
OperatorRefStructure operatorRef = affectedOperator.getOperatorRef();
if (operatorRef == null || operatorRef.getValue() == null) {
continue;
}
// SIRI Operators are mapped to OTP Agency, this i probably wrong - but
// I leave this for now.
String agencyId = operatorRef.getValue();
alert.addEntity(new EntitySelector.Agency(new FeedScopedId(feedId, agencyId)));
}
}
AffectsScopeStructure.Networks networks = affectsStructure.getNetworks();
Set stopRoutes = new HashSet<>();
if (networks != null) {
for (AffectsScopeStructure.Networks.AffectedNetwork affectedNetwork : networks.getAffectedNetworks()) {
List affectedLines = affectedNetwork.getAffectedLines();
if (affectedLines != null && !isListNullOrEmpty(affectedLines)) {
for (AffectedLineStructure line : affectedLines) {
LineRef lineRef = line.getLineRef();
if (lineRef == null || lineRef.getValue() == null) {
continue;
}
stopRoutes.addAll(siriFuzzyTripMatcher.getRoutes(lineRef.getValue()));
}
}
}
}
AffectsScopeStructure.StopPoints stopPoints = affectsStructure.getStopPoints();
AffectsScopeStructure.StopPlaces stopPlaces = affectsStructure.getStopPlaces();
if (stopPoints != null && !isListNullOrEmpty(stopPoints.getAffectedStopPoints())) {
for (AffectedStopPointStructure stopPoint : stopPoints.getAffectedStopPoints()) {
StopPointRef stopPointRef = stopPoint.getStopPointRef();
if (stopPointRef == null || stopPointRef.getValue() == null) {
continue;
}
FeedScopedId stopId = siriFuzzyTripMatcher.getStop(stopPointRef.getValue());
if (stopId != null) {
if (stopRoutes.isEmpty()) {
alert.addEntity(new EntitySelector.Stop(stopId));
// TODO: is this correct? Should the stop conditions be in the entity selector?
updateStopConditions(alert, stopPoint.getStopConditions());
} else {
//Adding combination of stop & route
for (Route route : stopRoutes) {
alert.addEntity(new EntitySelector.StopAndRoute(stopId, route.getId()));
// TODO: is this correct? Should the stop conditions be in the entity selector?
updateStopConditions(alert, stopPoint.getStopConditions());
}
}
}
}
} else if (stopPlaces != null && !isListNullOrEmpty(stopPlaces.getAffectedStopPlaces())) {
for (AffectedStopPlaceStructure stopPoint : stopPlaces.getAffectedStopPlaces()) {
StopPlaceRef stopPlace = stopPoint.getStopPlaceRef();
if (stopPlace == null || stopPlace.getValue() == null) {
continue;
}
FeedScopedId stopId = siriFuzzyTripMatcher.getStop(stopPlace.getValue());
if (stopId != null) {
if (stopRoutes.isEmpty()) {
alert.addEntity(new EntitySelector.Stop(stopId));
} else {
//Adding combination of stop & route
for (Route route : stopRoutes) {
alert.addEntity(new EntitySelector.StopAndRoute(stopId, route.getId()));
}
}
}
}
} else if (networks != null && !isListNullOrEmpty(networks.getAffectedNetworks())) {
for (AffectsScopeStructure.Networks.AffectedNetwork affectedNetwork : networks.getAffectedNetworks()) {
List affectedLines = affectedNetwork.getAffectedLines();
if (affectedLines != null && !isListNullOrEmpty(affectedLines)) {
for (AffectedLineStructure line : affectedLines) {
LineRef lineRef = line.getLineRef();
if (lineRef == null || lineRef.getValue() == null) {
continue;
}
List affectedStops = new ArrayList<>();
AffectedLineStructure.Routes routes = line.getRoutes();
// Resolve AffectedStop-ids
if (routes != null) {
for (AffectedRouteStructure route : routes.getAffectedRoutes()) {
if (route.getStopPoints() != null) {
List stopPointsList = route.getStopPoints().getAffectedStopPointsAndLinkProjectionToNextStopPoints();
for (Serializable serializable : stopPointsList) {
if (serializable instanceof AffectedStopPointStructure) {
AffectedStopPointStructure stopPointStructure = (AffectedStopPointStructure) serializable;
affectedStops.add(stopPointStructure);
}
}
}
}
}
Set affectedRoutes = siriFuzzyTripMatcher.getRoutes(lineRef.getValue());
for (Route route : affectedRoutes) {
if (! affectedStops.isEmpty()) {
for (AffectedStopPointStructure affectedStop : affectedStops) {
FeedScopedId stop = siriFuzzyTripMatcher.getStop(affectedStop.getStopPointRef().getValue());
if (stop == null) {
continue;
}
alert.addEntity(new EntitySelector.StopAndRoute(stop, route.getId()));
// TODO: is this correct? Should the stop conditions be in the entity selector?
updateStopConditions(alert, affectedStop.getStopConditions());
}
} else {
alert.addEntity(new EntitySelector.Route(route.getId()));
}
}
}
} else {
NetworkRefStructure networkRef = affectedNetwork.getNetworkRef();
if (networkRef == null || networkRef.getValue() == null) {
continue;
}
String networkId = networkRef.getValue();
// TODO: What to do here?
}
}
}
AffectsScopeStructure.VehicleJourneys vjs = affectsStructure.getVehicleJourneys();
if (vjs != null && !isListNullOrEmpty(vjs.getAffectedVehicleJourneies())) {
for (AffectedVehicleJourneyStructure affectedVehicleJourney : vjs.getAffectedVehicleJourneies()) {
String lineRef = null;
if (affectedVehicleJourney.getLineRef() != null) {
lineRef = affectedVehicleJourney.getLineRef().getValue();
}
List affectedStops = new ArrayList<>();
List routes = affectedVehicleJourney.getRoutes();
// Resolve AffectedStop-ids
if (routes != null) {
for (AffectedRouteStructure route : routes) {
if (route.getStopPoints() != null) {
List stopPointsList = route.getStopPoints().getAffectedStopPointsAndLinkProjectionToNextStopPoints();
for (Serializable serializable : stopPointsList) {
if (serializable instanceof AffectedStopPointStructure) {
AffectedStopPointStructure stopPointStructure = (AffectedStopPointStructure) serializable;
affectedStops.add(stopPointStructure);
}
}
}
}
}
List vehicleJourneyReves = affectedVehicleJourney.getVehicleJourneyReves();
if (!isListNullOrEmpty(vehicleJourneyReves)) {
for (VehicleJourneyRef vehicleJourneyRef : vehicleJourneyReves) {
List tripIds = new ArrayList<>();
FeedScopedId tripIdFromVehicleJourney = siriFuzzyTripMatcher.getTripId(vehicleJourneyRef.getValue());
Date effectiveStartDate;
Date effectiveEndDate;
// Need to know if validity is set explicitly or calculated based on ServiceDate
boolean effectiveValiditySetExplicitly = false;
ZonedDateTime originAimedDepartureTime = (affectedVehicleJourney.getOriginAimedDepartureTime() != null ? affectedVehicleJourney.getOriginAimedDepartureTime():ZonedDateTime.now());
ServiceDate serviceDate = new ServiceDate(originAimedDepartureTime.getYear(), originAimedDepartureTime.getMonthValue(), originAimedDepartureTime.getDayOfMonth());
if (tripIdFromVehicleJourney != null) {
tripIds.add(tripIdFromVehicleJourney);
// ServiceJourneyId matches - use provided validity
effectiveStartDate = alert.getEffectiveStartDate();
effectiveEndDate = alert.getEffectiveEndDate();
effectiveValiditySetExplicitly = true;
} else {
// TODO - SIRI: Support submode when fuzzy-searching for trips
tripIds = siriFuzzyTripMatcher.getTripIdForTripShortNameServiceDateAndMode(vehicleJourneyRef.getValue(),
serviceDate, TraverseMode.RAIL/*, TransmodelTransportSubmode.RAIL_REPLACEMENT_BUS*/);
// ServiceJourneyId does NOT match - calculate validity based on originAimedDepartureTime
effectiveStartDate = serviceDate.getAsDate();
effectiveEndDate = serviceDate.next().getAsDate();
}
for (FeedScopedId tripId : tripIds) {
if (!effectiveValiditySetExplicitly) {
// Effective validity is set based on ServiceDate - need to calculate correct validity based on departuretimes
// Calculate validity based on actual, planned departure/arrival for trip
int tripDepartureTime = siriFuzzyTripMatcher.getTripDepartureTime(tripId);
int tripArrivalTime = siriFuzzyTripMatcher.getTripArrivalTime(tripId);
// ServiceJourneyId does NOT match - calculate validity based on serviceDate (calculated from originalAimedDepartureTime)
effectiveStartDate = new Date(serviceDate.getAsDate().getTime() + tripDepartureTime * 1000);
// Appending 6 hours to end-validity in case of delays.
effectiveEndDate = new Date(effectiveStartDate.getTime() + (tripArrivalTime - tripDepartureTime + 6 * 3600) * 1000);
// Verify that calculated validity does not exceed explicitly set validity
if (effectiveStartDate.before(alert.getEffectiveStartDate())) {
effectiveStartDate = alert.getEffectiveStartDate();
}
if (effectiveEndDate.after(alert.getEffectiveEndDate())) {
effectiveEndDate = alert.getEffectiveEndDate();
}
if (effectiveStartDate.after(effectiveEndDate) | effectiveStartDate.equals(effectiveEndDate)) {
//Ignore this as situation is no longer valid
continue;
}
}
if (effectiveEndDate == null) {
effectiveEndDate = new Date(Long.MAX_VALUE);
}
if (! affectedStops.isEmpty()) {
for (AffectedStopPointStructure affectedStop : affectedStops) {
FeedScopedId stop = siriFuzzyTripMatcher.getStop(affectedStop.getStopPointRef().getValue());
if (stop == null) {
continue;
}
// Creating unique, deterministic id for the alert
alert.addEntity(new EntitySelector.StopAndTrip(stop, tripId));
// TODO: is this correct? Should the stop conditions be in the entity selector?
updateStopConditions(alert, affectedStop.getStopConditions());
// A tripId for a given date may be reused for other dates not affected by this alert.
List timePeriodList = new ArrayList<>();
timePeriodList.add(new TimePeriod(effectiveStartDate.getTime()/1000, effectiveEndDate.getTime()/1000));
// TODO: Make it possible to add time periods for trip selectors
// alert.setTimePeriods(timePeriodList);
}
} else {
alert.addEntity(new EntitySelector.Trip(tripId));
// A tripId for a given date may be reused for other dates not affected by this alert.
List timePeriodList = new ArrayList<>();
timePeriodList.add(new TimePeriod(effectiveStartDate.getTime()/1000, effectiveEndDate.getTime()/1000));
// TODO: Make it possible to add time periods for trip selectors
// alert.setTimePeriods(timePeriodList);
}
}
}
}
if (lineRef != null) {
Set affectedRoutes = siriFuzzyTripMatcher.getRoutes(lineRef);
for (Route route : affectedRoutes) {
alert.addEntity(new EntitySelector.Route(route.getId()));
}
}
}
}
}
if (alert.getStopConditions().isEmpty()) {
updateStopConditions(alert, null);
}
alert.alertType = situation.getReportType();
if (situation.getSeverity() != null) {
alert.severity = situation.getSeverity().value();
} else {
// When severity is not set - use default
alert.severity = SeverityEnumeration.NORMAL.value();
}
if (situation.getParticipantRef() != null) {
String codespace = situation.getParticipantRef().getValue();
alert.setFeedId(codespace + ":Authority:" + codespace); //TODO - SIRI: Should probably not assume this codespace -> authority rule
}
return alert;
}
/*
* Creates alert from PtSituation with all textual content
*/
private TransitAlert createAlertWithTexts(PtSituationElement situation) {
TransitAlert alert = new TransitAlert();
alert.alertDescriptionText = getTranslatedString(situation.getDescriptions());
alert.alertDetailText = getTranslatedString(situation.getDetails());
alert.alertAdviceText = getTranslatedString(situation.getAdvices());
alert.alertHeaderText = getTranslatedString(situation.getSummaries());
alert.alertUrl = getInfoLinkAsString(situation.getInfoLinks());
alert.setAlertUrlList(getInfoLinks(situation.getInfoLinks()));
return alert;
}
/*
* Returns first InfoLink-uri as a String
*/
private I18NString getInfoLinkAsString(PtSituationElement.InfoLinks infoLinks) {
if (infoLinks != null) {
if (!isListNullOrEmpty(infoLinks.getInfoLinks())) {
InfoLinkStructure infoLinkStructure = infoLinks.getInfoLinks().get(0);
if (infoLinkStructure != null && infoLinkStructure.getUri() != null) {
return new NonLocalizedString(infoLinkStructure.getUri());
}
}
}
return null;
}
/*
* Returns all InfoLinks
*/
private List getInfoLinks(PtSituationElement.InfoLinks infoLinks) {
List alertUrls = new ArrayList<>();
if (infoLinks != null) {
if (!isListNullOrEmpty(infoLinks.getInfoLinks())) {
for (InfoLinkStructure infoLink : infoLinks.getInfoLinks()) {
AlertUrl alertUrl = new AlertUrl();
List labels = infoLink.getLabels();
if (labels != null && !labels.isEmpty()) {
NaturalLanguageStringStructure label = labels.get(0);
alertUrl.label = label.getValue();
}
alertUrl.uri = infoLink.getUri();
alertUrls.add(alertUrl);
}
}
}
return alertUrls;
}
private void updateStopConditions(TransitAlert alertPatch, List stopConditions) {
Set alertStopConditions = new HashSet<>();
if (stopConditions != null) {
for (RoutePointTypeEnumeration stopCondition : stopConditions) {
switch (stopCondition) {
case EXCEPTIONAL_STOP:
alertStopConditions.add(StopCondition.EXCEPTIONAL_STOP);
break;
case DESTINATION:
alertStopConditions.add(StopCondition.DESTINATION);
break;
case NOT_STOPPING:
alertStopConditions.add(StopCondition.NOT_STOPPING);
break;
case REQUEST_STOP:
alertStopConditions.add(StopCondition.REQUEST_STOP);
break;
case START_POINT:
alertStopConditions.add(StopCondition.START_POINT);
break;
}
}
}
if (alertStopConditions.isEmpty()) {
//No StopConditions are set - set default
alertStopConditions.add(StopCondition.START_POINT);
alertStopConditions.add(StopCondition.DESTINATION);
}
alertPatch.getStopConditions().addAll(alertStopConditions);
}
private boolean isListNullOrEmpty(List list) {
if (list == null || list.isEmpty()) {
return true;
}
return false;
}
/**
* convert a SIRI DefaultedTextStructure to a OTP TranslatedString
*
* @return A TranslatedString containing the same information as the input
* @param input
*/
private I18NString getTranslatedString(List input) {
Map translations = new HashMap<>();
if (input != null && input.size() > 0) {
for (DefaultedTextStructure textStructure : input) {
String language = "";
String value = "";
if (textStructure.getLang() != null) {
language = textStructure.getLang();
}
if (textStructure.getValue() != null) {
value = textStructure.getValue();
}
translations.put(language, value);
}
} else {
translations.put("", "");
}
return translations.isEmpty() ? null : TranslatedString.getI18NString(translations);
}
public void setTransitAlertService(TransitAlertService transitAlertService) {
this.transitAlertService = transitAlertService;
}
public long getEarlyStart() {
return earlyStart;
}
public void setEarlyStart(long earlyStart) {
this.earlyStart = earlyStart;
}
public void setSiriFuzzyTripMatcher(SiriFuzzyTripMatcher siriFuzzyTripMatcher) {
this.siriFuzzyTripMatcher = siriFuzzyTripMatcher;
}
}