Skip to content

Commit

Permalink
feat: Added a validator for forbidden shape_distance (#1896)
Browse files Browse the repository at this point in the history
Added a validator for the presence of shape_distance with location_id or location_group_id
  • Loading branch information
jcpitre authored Oct 21, 2024
1 parent 15e153b commit b8012a5
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public class ForbiddenGeographyIdNotice extends ValidationNotice {
/** The row of the faulty record. */
private final int csvRowNumber;

/** The sThe id that already exists. */
/** The id that already exists. */
private final String stopId;

/** The id that already exists. */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Copyright 2024 MobilityData
*
* 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.annotation.GtfsValidationNotice.SectionRef.FILE_REQUIREMENTS;
import static org.mobilitydata.gtfsvalidator.notice.SeverityLevel.ERROR;

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

/**
* Check that only entries with stop_id have shape_dist_traveled. A GeoJSON location or location
* group is forbidden to have an associated shape_dist_traveled field in stop_times.txt.
*
* <p>Generated notice: {@link ForbiddenShapeDistTraveledNotice}.
*/
@GtfsValidator
public class StopTimesShapeDistTraveledPresenceValidator
extends SingleEntityValidator<GtfsStopTime> {

@Override
public void validate(GtfsStopTime stopTime, NoticeContainer noticeContainer) {
if (stopTime.hasStopId()) {
return;
}
if (stopTime.hasLocationGroupId() || stopTime.hasLocationId()) {
if (stopTime.hasShapeDistTraveled()) {
noticeContainer.addValidationNotice(
new ForbiddenShapeDistTraveledNotice(
stopTime.csvRowNumber(),
stopTime.tripId(),
stopTime.locationGroupId(),
stopTime.locationId(),
stopTime.shapeDistTraveled()));
}
}
}

@Override
public boolean shouldCallValidate(ColumnInspector header) {
// No point in validating if there is no shape_dist_traveled column
// And we need to have either location_id or location_group_id for this validator to make sense
return header.hasColumn(GtfsStopTime.SHAPE_DIST_TRAVELED_FIELD_NAME)
&& (header.hasColumn(GtfsStopTime.LOCATION_ID_FIELD_NAME)
|| header.hasColumn(GtfsStopTime.LOCATION_GROUP_ID_FIELD_NAME));
}

/**
* A stop_time entry has a `shape_dist_traveled` without a `stop_id` value.
*
* <p>A GeoJSON location or location group has an associated shape_dist_traveled field in
* stop_times.txt. shape_dist_traveled values should only be provided for stops.
*/
@GtfsValidationNotice(
severity = ERROR,
sections = @GtfsValidationNotice.SectionRefs(FILE_REQUIREMENTS))
public static class ForbiddenShapeDistTraveledNotice extends ValidationNotice {

/** The row of the faulty record. */
private final int csvRowNumber;

/** The trip_id for which the shape_dist_traveled is defined */
private final String tripId;

/** The location_grpup_id for which the shape_dist_traveled is defined */
private final String locationGroupId;

/** The location_id for which the shape_dist_traveled is defined */
private final String locationId;

/** The shape_dist_traveled value */
private final double shapeDistTraveled;

public ForbiddenShapeDistTraveledNotice(
int csvRowNumber,
String tripId,
String locationGroupId,
String locationId,
double shapeDistTraveled) {
this.csvRowNumber = csvRowNumber;
this.tripId = tripId;
this.locationGroupId = locationGroupId;
this.locationId = locationId;
this.shapeDistTraveled = shapeDistTraveled;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* 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 com.google.common.truth.Truth.assertThat;

import com.google.common.collect.ImmutableList;
import java.util.List;
import org.junit.Test;
import org.mobilitydata.gtfsvalidator.notice.NoticeContainer;
import org.mobilitydata.gtfsvalidator.notice.ValidationNotice;
import org.mobilitydata.gtfsvalidator.table.GtfsStopTime;

public class StopTimesShapeDistTraveledPresenceValidatorTest {
public static GtfsStopTime createStopTime(
int csvRowNumber,
String tripId,
String stopId,
String locationGroupId,
String locationId,
int stopSequence,
double shapeDistTraveled) {
var builder =
new GtfsStopTime.Builder()
.setCsvRowNumber(csvRowNumber)
.setTripId(tripId)
.setStopSequence(stopSequence)
.setShapeDistTraveled(shapeDistTraveled);
if (stopId != null) {
builder.setStopId(stopId);
}
if (locationGroupId != null) {
builder.setLocationGroupId(locationGroupId);
}
if (locationId != null) {
builder.setLocationId(locationId);
}

return builder.build();
}

private static List<ValidationNotice> generateNotices(List<GtfsStopTime> stopTimes) {
NoticeContainer noticeContainer = new NoticeContainer();

var validator = new StopTimesShapeDistTraveledPresenceValidator();
for (var stopTime : stopTimes) {
validator.validate(stopTime, noticeContainer);
}
return noticeContainer.getValidationNotices();
}

@Test
public void locationWithShapeDistanceShouldGenerateNotice() {
assertThat(
generateNotices(
ImmutableList.of(
createStopTime(1, "first trip", null, "loc1", null, 2, 10.0d),
createStopTime(2, "first trip", null, null, "loc2", 42, 45.0d),
createStopTime(3, "first trip", "stop1", null, null, 46, 64.0d))))
.containsExactly(
new StopTimesShapeDistTraveledPresenceValidator.ForbiddenShapeDistTraveledNotice(
1, "first trip", "loc1", "", 10.0d),
new StopTimesShapeDistTraveledPresenceValidator.ForbiddenShapeDistTraveledNotice(
2, "first trip", "", "loc2", 45.0d));
}
}

0 comments on commit b8012a5

Please sign in to comment.