Skip to content

Commit

Permalink
Merge branch 'master' into 1534-service-window-in-summary-report
Browse files Browse the repository at this point in the history
  • Loading branch information
qcdyx committed Sep 10, 2024
2 parents a20686a + 014bac0 commit 2e5cfa7
Show file tree
Hide file tree
Showing 10 changed files with 259 additions and 3 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,5 @@ app/pkg/bin/
processor/notices/bin/
processor/notices/tests/bin/
web/service/bin/

RULES.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ public class FeedMetadata {
new Pair<>("Zone-Based Fares", GtfsArea.FILENAME),
new Pair<>("Transfer Fares", GtfsFareTransferRule.FILENAME),
new Pair<>("Time-Based Fares", GtfsTimeframe.FILENAME),
new Pair<>("Levels", GtfsLevel.FILENAME));
new Pair<>("Levels", GtfsLevel.FILENAME),
new Pair<>("Booking Rules", GtfsBookingRules.FILENAME),
new Pair<>("Fixed-Stops Demand Responsive Transit", GtfsLocationGroups.FILENAME));

protected FeedMetadata() {}

Expand Down Expand Up @@ -165,6 +167,60 @@ private void loadSpecFeaturesBasedOnFieldPresence(GtfsFeedContainer feedContaine
loadPathwayExtraFeature(feedContainer);
loadRouteBasedFaresFeature(feedContainer);
loadContinuousStopsFeature(feedContainer);
loadZoneBasedDemandResponsiveTransitFeature(feedContainer);
loadDeviatedFixedRouteFeature(feedContainer);
}

private void loadDeviatedFixedRouteFeature(GtfsFeedContainer feedContainer) {
specFeatures.put("Deviated Fixed Route", hasAtLeastOneTripWithAllFields(feedContainer));
}

private boolean hasAtLeastOneTripWithAllFields(GtfsFeedContainer feedContainer) {
Optional<GtfsTableContainer<?>> optionalStopTimeTable =
feedContainer.getTableForFilename(GtfsStopTime.FILENAME);
if (optionalStopTimeTable.isPresent()) {
for (GtfsEntity entity : optionalStopTimeTable.get().getEntities()) {
if (entity instanceof GtfsStopTime) {
GtfsStopTime stopTime = (GtfsStopTime) entity;
return stopTime.hasTripId()
&& stopTime.tripId() != null
&& stopTime.hasLocationId()
&& stopTime.locationId() != null
&& stopTime.hasStopId()
&& stopTime.stopId() != null
&& stopTime.hasArrivalTime()
&& stopTime.arrivalTime() != null
&& stopTime.hasDepartureTime()
&& stopTime.departureTime() != null;
}
}
}
return false;
}

private void loadZoneBasedDemandResponsiveTransitFeature(GtfsFeedContainer feedContainer) {
specFeatures.put(
"Zone-Based Demand Responsive Transit", hasAtLeastOneTripWithOnlyLocationId(feedContainer));
}

private boolean hasAtLeastOneTripWithOnlyLocationId(GtfsFeedContainer feedContainer) {
Optional<GtfsTableContainer<?>> optionalStopTimeTable =
feedContainer.getTableForFilename(GtfsStopTime.FILENAME);
if (optionalStopTimeTable.isPresent()) {
for (GtfsEntity entity : optionalStopTimeTable.get().getEntities()) {
if (entity instanceof GtfsStopTime) {
GtfsStopTime stopTime = (GtfsStopTime) entity;
if (stopTime.hasTripId()
&& stopTime.tripId() != null
&& stopTime.hasLocationId()
&& stopTime.locationId() != null
&& (!stopTime.hasStopId() || stopTime.stopId() == null)) {
return true;
}
}
}
}
return false;
}

private void loadContinuousStopsFeature(GtfsFeedContainer feedContainer) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package org.mobilitydata.gtfsvalidator.table;

import org.mobilitydata.gtfsvalidator.annotation.ConditionallyRequired;
import org.mobilitydata.gtfsvalidator.annotation.FieldType;
import org.mobilitydata.gtfsvalidator.annotation.FieldTypeEnum;
import org.mobilitydata.gtfsvalidator.annotation.GtfsTable;
import org.mobilitydata.gtfsvalidator.annotation.MixedCase;
import org.mobilitydata.gtfsvalidator.annotation.PrimaryKey;
import org.mobilitydata.gtfsvalidator.annotation.Required;
import org.mobilitydata.gtfsvalidator.type.GtfsTime;

@GtfsTable("booking_rules.txt")
public interface GtfsBookingRulesSchema extends GtfsEntity {
@FieldType(FieldTypeEnum.ID)
@PrimaryKey
@Required
String bookingRuleId();

@Required
GtfsBookingType bookingType();

@ConditionallyRequired
int priorNoticeDurationMin();

@ConditionallyRequired
int priorNoticeDurationMax();

@ConditionallyRequired
int priorNoticeStartDay();

@ConditionallyRequired
GtfsTime priorNoticeStartTime();

@ConditionallyRequired
int priorNoticeLastDay();

@ConditionallyRequired
GtfsTime priorNoticeLastTime();

@ConditionallyRequired
String priorNoticeServiceId();

@MixedCase
String message();

@MixedCase
String pickupMessage();

@MixedCase
String dropOffMessage();

@FieldType(FieldTypeEnum.PHONE_NUMBER)
String phoneNumber();

@FieldType(FieldTypeEnum.URL)
String infoUrl();

@FieldType(FieldTypeEnum.URL)
String bookingUrl();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mobilitydata.gtfsvalidator.table;

import org.mobilitydata.gtfsvalidator.annotation.GtfsEnumValue;

@GtfsEnumValue(name = "REALTIME", value = 0)
@GtfsEnumValue(name = "SAMEDAY", value = 1)
@GtfsEnumValue(name = "PRIORDAY", value = 2)
public interface GtfsBookingTypeEnum {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.mobilitydata.gtfsvalidator.table;

import org.mobilitydata.gtfsvalidator.annotation.FieldType;
import org.mobilitydata.gtfsvalidator.annotation.FieldTypeEnum;
import org.mobilitydata.gtfsvalidator.annotation.GtfsTable;
import org.mobilitydata.gtfsvalidator.annotation.MixedCase;
import org.mobilitydata.gtfsvalidator.annotation.PrimaryKey;
import org.mobilitydata.gtfsvalidator.annotation.Required;

@GtfsTable("location_groups.txt")
public interface GtfsLocationGroupsSchema extends GtfsEntity {
@FieldType(FieldTypeEnum.ID)
@PrimaryKey
@Required
String locationGroupId();

@MixedCase
String locationGroupName();
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ public interface GtfsStopTimeSchema extends GtfsEntity {
@ForeignKey(table = "stops.txt", field = "stop_id")
String stopId();

@FieldType(FieldTypeEnum.ID)
String locationId();

@PrimaryKey(isSequenceUsedForSorting = true, translationRecordIdType = RECORD_SUB_ID)
@Required
@NonNegative
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.mobilitydata.gtfsvalidator.validator;

import static org.mobilitydata.gtfsvalidator.notice.SeverityLevel.ERROR;

import org.mobilitydata.gtfsvalidator.annotation.GtfsValidationNotice;
import org.mobilitydata.gtfsvalidator.annotation.GtfsValidationNotice.FileRefs;
import org.mobilitydata.gtfsvalidator.annotation.GtfsValidator;
import org.mobilitydata.gtfsvalidator.notice.NoticeContainer;
import org.mobilitydata.gtfsvalidator.notice.ValidationNotice;
import org.mobilitydata.gtfsvalidator.table.*;

/**
* Validates that:
*
* <ul>
* <li>Exit gates (pathway_mode=7) must not be bidirectional.
* </ul>
*/
@GtfsValidator
public class BidirectionalExitGateValidator extends SingleEntityValidator<GtfsPathway> {

@Override
public void validate(GtfsPathway entity, NoticeContainer noticeContainer) {
if (entity.pathwayMode().getNumber() == 7 && entity.isBidirectional().getNumber() == 1) {
noticeContainer.addValidationNotice(new BidirectionalExitGateNotice(entity));
}
}

/**
* Pathway is bidirectional and has mode 7 (exit gate).
*
* <p>Exit gates (pathway_mode=7) must not be bidirectional.
*/
@GtfsValidationNotice(severity = ERROR, files = @FileRefs({GtfsPathwaySchema.class}))
static class BidirectionalExitGateNotice extends ValidationNotice {
/** The row number of the validated record. */
private final int csvRowNumber;
/** The pathway mode. */
private final int pathwayMode;
/** Whether the pathway is bidirectional. */
private final int isBidirectional;

BidirectionalExitGateNotice(GtfsPathway pathway) {
this.csvRowNumber = pathway.csvRowNumber();
this.pathwayMode = pathway.pathwayMode().getNumber();
this.isBidirectional = pathway.isBidirectional().getNumber();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.mobilitydata.gtfsvalidator.validator;

import static com.google.common.truth.Truth.assertThat;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mobilitydata.gtfsvalidator.notice.NoticeContainer;
import org.mobilitydata.gtfsvalidator.table.GtfsPathway;
import org.mobilitydata.gtfsvalidator.table.GtfsPathwayIsBidirectional;

@RunWith(JUnit4.class)
public class BidirectionalExitGateValidatorTest {
public static GtfsPathway createPathway(
int csvRowNumber,
Integer pathwayMode,
GtfsPathwayIsBidirectional gtfsPathwayIsBidirectional) {
return new GtfsPathway.Builder()
.setCsvRowNumber(csvRowNumber)
.setPathwayMode(pathwayMode)
.setIsBidirectional(gtfsPathwayIsBidirectional)
.build();
}

/** Tests that a pathway with bidirectional exit gates generates a notice. */
@Test
public void isBidirectionalExitGateShouldGenerateNotice() {
GtfsPathway entity = createPathway(1, 7, GtfsPathwayIsBidirectional.BIDIRECTIONAL);
NoticeContainer noticeContainer = new NoticeContainer();
new BidirectionalExitGateValidator().validate(entity, noticeContainer);
assertThat(noticeContainer.getValidationNotices())
.containsExactly(new BidirectionalExitGateValidator.BidirectionalExitGateNotice(entity));
}

@Test
public void isNotBidirectionalExitGateShouldNotGenerateNotice() {
GtfsPathway entity = createPathway(1, 7, GtfsPathwayIsBidirectional.UNIDIRECTIONAL);
NoticeContainer noticeContainer = new NoticeContainer();
new BidirectionalExitGateValidator().validate(entity, noticeContainer);
assertThat(noticeContainer.getValidationNotices()).isEmpty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ public void testThatNoticeFieldsAreDocumented() {
.map(f -> f.getDeclaringClass().getSimpleName() + "." + f.getName())
.collect(Collectors.toList());
assertWithMessage(
"Every field of a validation notice much be documented with a JavaDoc comment (aka /** */, not //). The following fields are undocumented:")
"Every field of a validation notice must be documented with a JavaDoc comment (aka /** */, not //). The following fields are undocumented:")
.that(fieldsWithoutComments)
.isEmpty();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,9 @@ public void testNoticeClassFieldNames() {
"maxShapeDistanceTraveled",
"maxTripDistanceTraveled",
"fileNameA",
"fileNameB");
"fileNameB",
"pathwayMode",
"isBidirectional");
}

private static List<String> discoverValidationNoticeFieldNames() {
Expand Down

0 comments on commit 2e5cfa7

Please sign in to comment.