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
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package org.mobilitydata.gtfsvalidator.validator;

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

import javax.inject.Inject;
import org.mobilitydata.gtfsvalidator.annotation.GtfsValidationNotice;
import org.mobilitydata.gtfsvalidator.annotation.GtfsValidationNotice.FileRefs;
import org.mobilitydata.gtfsvalidator.annotation.GtfsValidator;
import org.mobilitydata.gtfsvalidator.notice.MissingRecommendedFileNotice;
import org.mobilitydata.gtfsvalidator.notice.NoticeContainer;
import org.mobilitydata.gtfsvalidator.notice.ValidationNotice;
import org.mobilitydata.gtfsvalidator.table.GtfsLocationGroupsTableContainer;
import org.mobilitydata.gtfsvalidator.table.GtfsShapeTableContainer;
import org.mobilitydata.gtfsvalidator.table.GtfsStopTime;
import org.mobilitydata.gtfsvalidator.table.GtfsStopTimeSchema;
import org.mobilitydata.gtfsvalidator.table.GtfsStopTimeTableContainer;
import org.mobilitydata.gtfsvalidator.table.GtfsTripSchema;

/**
* Validates that the feed has either a `shapes.txt` file, or uses zone-based DRT or fixed-stops
* DRT.
*
* <p>Generated notice: {@link MissingRecommendedFileNotice}.
*/
@GtfsValidator
public class MissingShapesFileValidator extends FileValidator {
private final GtfsShapeTableContainer shapeTable;
private final GtfsStopTimeTableContainer stopTimeTable;
private final GtfsLocationGroupsTableContainer locationGroupsTable;

@Inject
MissingShapesFileValidator(
GtfsShapeTableContainer shapeTable,
GtfsStopTimeTableContainer stopTimeTable,
GtfsLocationGroupsTableContainer locationGroupsTable) {
this.shapeTable = shapeTable;
this.stopTimeTable = stopTimeTable;
this.locationGroupsTable = locationGroupsTable;
}

@Override
public void validate(NoticeContainer noticeContainer) {
Boolean missingShapes = shapeTable.isMissingFile();
Boolean hasLocationId = stopTimeTable.hasColumn("location_id");
Boolean hasLocationGroupId = stopTimeTable.hasColumn("location_group_id");
Boolean hasLocationGroupsRecord =
!locationGroupsTable.isMissingFile() && locationGroupsTable.entityCount() > 0;
// Detect DRT usage from the data, not just from column presence.
boolean hasLocationIdInData = false;
boolean hasLocationGroupIdInData = false;
for (GtfsStopTime stopTime : stopTimeTable.getEntities()) {
if (stopTime.hasLocationId()) {
hasLocationIdInData = true;
}
if (stopTime.hasLocationGroupId()) {
hasLocationGroupIdInData = true;
}
if (hasLocationIdInData && hasLocationGroupIdInData) {
break;
}
}

// Do we not have: a shapes.txt file and not have a location_id (required for Zone-Based DRT),
// and also not have a record in location_groups.txt and not have a trip in stop_times.txt that
// references location_group_id (required for Fixed-Stop DRT)?
if (missingShapes && !hasLocationId && !hasLocationGroupsRecord && !hasLocationGroupId) {
for (GtfsStopTime stopTime : stopTimeTable.getEntities()) {
noticeContainer.addValidationNotice(
new MissingRecommendedFileNotice("shapes.txt"));
// This is a feed-level warning; emit it at most once.
break;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package org.mobilitydata.gtfsvalidator.validator;

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

import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.mobilitydata.gtfsvalidator.notice.MissingRecommendedFileNotice;
import org.mobilitydata.gtfsvalidator.notice.NoticeContainer;
import org.mobilitydata.gtfsvalidator.notice.ValidationNotice;
import org.mobilitydata.gtfsvalidator.table.GtfsLocationGroups;
import org.mobilitydata.gtfsvalidator.table.GtfsLocationGroupsTableContainer;
import org.mobilitydata.gtfsvalidator.table.GtfsShape;
import org.mobilitydata.gtfsvalidator.table.GtfsShapeTableContainer;
import org.mobilitydata.gtfsvalidator.table.GtfsStopTime;
import org.mobilitydata.gtfsvalidator.table.GtfsStopTimeTableContainer;
import org.mobilitydata.gtfsvalidator.table.TableStatus;

public class MissingShapesFileValidatorTest {

private static List<GtfsShape> createShapeTable(int rows) {
ArrayList<GtfsShape> shapes = new ArrayList<>();
for (int i = 0; i < rows; i++) {
shapes.add(new GtfsShape.Builder().setCsvRowNumber(i + 1).setShapeId("s" + i).build());
}
return shapes;
}

private static List<GtfsStopTime> createStopTimesTable(
int rows, String locationGroupId, String locationId) {
ArrayList<GtfsStopTime> stopTimes = new ArrayList<>();
for (int i = 0; i < rows; i++) {
stopTimes.add(
new GtfsStopTime.Builder()
.setCsvRowNumber(i + 1)
.setLocationGroupId(locationGroupId)
.setLocationId(locationId)
.setTripId(locationGroupId)
.setStopSequence(i + 1)
.build());
}
return stopTimes;
}

private static List<GtfsLocationGroups> createLocationGroupsTable(
int rows, String groupId, String groupName) {
ArrayList<GtfsLocationGroups> locationGroups = new ArrayList<>();
for (int i = 0; i < rows; i++) {
locationGroups.add(
new GtfsLocationGroups.Builder()
.setCsvRowNumber(i + 1)
.setLocationGroupId(groupId)
.setLocationGroupName(groupName)
.build());
}
return locationGroups;
}

@Test
public void testShapesFileAndFixedDrtPresent() {
List<ValidationNotice> notices =
generateNotices(
createShapeTable(1),
GtfsShapeTableContainer.forStatus(null),
createStopTimesTable(1, "a", null),
createLocationGroupsTable(1, "b", "testgroup"));
boolean found =
notices.stream()
.anyMatch(
notice ->
notice instanceof MissingRecommendedFileNotice);
assertThat(found).isFalse();
}

@Test
public void testShapesFileAndZoneBasedDrtPresent() {
List<ValidationNotice> notices =
generateNotices(
createShapeTable(1),
GtfsShapeTableContainer.forStatus(null),
createStopTimesTable(1, null, "c"),
createLocationGroupsTable(1, "d", "t3stgroup"));
boolean found =
notices.stream()
.anyMatch(
notice ->
notice instanceof MissingRecommendedFileNotice);
assertThat(found).isFalse();
}

@Test
public void testNoShapesFileAndNoDrtPresent() {
List<ValidationNotice> notices =
generateNotices(
createShapeTable(0),
GtfsShapeTableContainer.forStatus(TableStatus.MISSING_FILE),
createStopTimesTable(1, null, null),
createLocationGroupsTable(0, null, null));
long missingRecommendedFileNoticesCount =
notices.stream()
.filter(
notice ->
notice instanceof MissingRecommendedFileNotice)
.count();
assertThat(missingRecommendedFileNoticesCount).isAtLeast(1);
}

private static List<ValidationNotice> generateNotices(
List<GtfsShape> shapes,
GtfsShapeTableContainer shapeContainer,
List<GtfsStopTime> stopTimes,
List<GtfsLocationGroups> locationGroups) {
NoticeContainer noticeContainer = new NoticeContainer();
new MissingShapesFileValidator(
GtfsShapeTableContainer.forEntities(shapes, noticeContainer),
GtfsStopTimeTableContainer.forEntities(stopTimes, noticeContainer),
GtfsLocationGroupsTableContainer.forEntities(locationGroups, noticeContainer))
.validate(noticeContainer);
return noticeContainer.getValidationNotices();
}
}
Loading