Skip to content

Commit

Permalink
feat: trip vs shape distance validation
Browse files Browse the repository at this point in the history
  • Loading branch information
cka-y committed Aug 7, 2023
1 parent 2637087 commit a4a34f4
Show file tree
Hide file tree
Showing 2 changed files with 216 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* Copyright 2021 MobilityData IO
*
* 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.validator;

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

import java.util.List;
import java.util.stream.Collectors;
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.NoticeContainer;
import org.mobilitydata.gtfsvalidator.notice.ValidationNotice;
import org.mobilitydata.gtfsvalidator.table.*;

/**
* Validates that the distance traveled by a trip is lesser or equals the max length of its shape.
*
* <p>Generated notice: {@link TripDistanceExceedsShapeDistanceNotice}.
*/
@GtfsValidator
public class TripAndShapeDistanceValidator extends FileValidator {

private final GtfsTripTableContainer tripTable;

private final GtfsStopTimeTableContainer stopTimeTable;

private final GtfsShapeTableContainer shapeTable;

@Inject
TripAndShapeDistanceValidator(
GtfsTripTableContainer tripTable,
GtfsStopTimeTableContainer stopTimeTable,
GtfsShapeTableContainer shapeTable) {
this.tripTable = tripTable;
this.stopTimeTable = stopTimeTable;
this.shapeTable = shapeTable;
}

@Override
public void validate(NoticeContainer noticeContainer) {
List<String> uniqueShapeIds =
shapeTable.getEntities().stream()
.map(GtfsShape::shapeId)
.distinct()
.collect(Collectors.toList());

uniqueShapeIds.forEach(
shapeId -> {
double maxShapeDist =
shapeTable.getEntities().stream()
.filter(s -> s.shapeId().equals(shapeId))
.mapToDouble(GtfsShape::shapeDistTraveled)
.max()
.orElse(Double.NEGATIVE_INFINITY);

tripTable
.byShapeId(shapeId)
.forEach(
trip -> {
double maxStopTimeDist =
stopTimeTable.byTripId(trip.tripId()).stream()
.mapToDouble(GtfsStopTime::shapeDistTraveled)
.max()
.orElse(Double.NEGATIVE_INFINITY);

if (maxStopTimeDist > maxShapeDist) {
noticeContainer.addValidationNotice(
new TripDistanceExceedsShapeDistanceNotice(
trip.tripId(), shapeId, maxStopTimeDist, maxShapeDist));
}
});
});
}

/** The distance traveled by a trip should be less or equal to the max length of its shape. */
@GtfsValidationNotice(
severity = WARNING,
files = @FileRefs({GtfsTrip.class, GtfsStopTime.class, GtfsShape.class}))
static class TripDistanceExceedsShapeDistanceNotice extends ValidationNotice {

/** The faulty record's trip id. */
private final String tripId;

/** The faulty record's shape id. */
private final String shapeId;

/** The faulty record's trip max distance traveled. */
private final double maxTripDistanceTraveled;

/** The faulty record's shape max distance traveled. */
private final double maxShapeDistanceTraveled;

TripDistanceExceedsShapeDistanceNotice(
String tripId,
String shapeId,
double maxTripDistanceTraveled,
double maxShapeDistanceTraveled) {
this.tripId = tripId;
this.shapeId = shapeId;
this.maxShapeDistanceTraveled = maxShapeDistanceTraveled;
this.maxTripDistanceTraveled = maxTripDistanceTraveled;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
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.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mobilitydata.gtfsvalidator.notice.NoticeContainer;
import org.mobilitydata.gtfsvalidator.notice.ValidationNotice;
import org.mobilitydata.gtfsvalidator.table.*;

@RunWith(JUnit4.class)
public class TripAndShapeDistanceValidatorTest {

private static List<GtfsTrip> createTripTable(int rows) {
ArrayList<GtfsTrip> trips = new ArrayList<>();
for (int i = 0; i < rows; i++) {
trips.add(
new GtfsTrip.Builder()
.setCsvRowNumber(i + 1)
.setTripId("t" + i)
.setServiceId("sr" + i)
.setRouteId("r" + i)
.setShapeId("s" + i)
.build());
}
return trips;
}

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

private static List<GtfsStopTime> createStopTimesTable(int rows, double shapeDistTraveled) {
ArrayList<GtfsStopTime> stopTimes = new ArrayList<>();
for (int i = 0; i < rows; i++) {
stopTimes.add(
new GtfsStopTime.Builder()
.setCsvRowNumber(i + 1)
.setTripId("t" + i)
.setStopSequence(0)
.setStopId("st" + i)
.setShapeDistTraveled(shapeDistTraveled + i)
.build());
}
return stopTimes;
}

private static List<ValidationNotice> generateNotices(
List<GtfsTrip> trips, List<GtfsStopTime> stopTimes, List<GtfsShape> shapes) {
NoticeContainer noticeContainer = new NoticeContainer();
new TripAndShapeDistanceValidator(
GtfsTripTableContainer.forEntities(trips, noticeContainer),
GtfsStopTimeTableContainer.forEntities(stopTimes, noticeContainer),
GtfsShapeTableContainer.forEntities(shapes, noticeContainer))
.validate(noticeContainer);
return noticeContainer.getValidationNotices();
}

@Test
public void testTripDistanceExceedsShapeDistance() {
assertThat(
generateNotices(
createTripTable(1), createStopTimesTable(1, 10.0), createShapeTable(1, 9.0)))
.isNotEmpty();
}

@Test
public void testValidTripVsShapeDistance1() {
assertThat(
generateNotices(
createTripTable(1), createStopTimesTable(1, 10.0), createShapeTable(1, 10.0)))
.isEmpty();
}

@Test
public void testValidTripVsShapeDistance2() {
assertThat(
generateNotices(
createTripTable(1), createStopTimesTable(1, 9.0), createShapeTable(1, 10.0)))
.isEmpty();
}
}

0 comments on commit a4a34f4

Please sign in to comment.