From edf98676b48c5e120c5518d476952c6f7eecfca5 Mon Sep 17 00:00:00 2001 From: Praneeth Devunuri Date: Thu, 6 Jun 2024 10:24:25 -0400 Subject: [PATCH] feat: New rule - SingleShapePointValidator for flagging shapes with a single shape point (#1753) * new rule for shapes with single shape point * Ensure Google Java formatting * formatted code * change error description --------- Co-authored-by: David Gamez <1192523+davidgamez@users.noreply.github.com> Co-authored-by: Jingsi Lu --- .../validator/SingleShapePointValidator.java | 69 +++++++++++++++++++ .../SingleShapePointValidatorTest.java | 59 ++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 main/src/main/java/org/mobilitydata/gtfsvalidator/validator/SingleShapePointValidator.java create mode 100644 main/src/test/java/org/mobilitydata/gtfsvalidator/validator/SingleShapePointValidatorTest.java diff --git a/main/src/main/java/org/mobilitydata/gtfsvalidator/validator/SingleShapePointValidator.java b/main/src/main/java/org/mobilitydata/gtfsvalidator/validator/SingleShapePointValidator.java new file mode 100644 index 0000000000..491a7027cd --- /dev/null +++ b/main/src/main/java/org/mobilitydata/gtfsvalidator/validator/SingleShapePointValidator.java @@ -0,0 +1,69 @@ +package org.mobilitydata.gtfsvalidator.validator; + +import static org.mobilitydata.gtfsvalidator.notice.SeverityLevel.WARNING; + +import java.util.HashMap; +import java.util.Map; +import javax.inject.Inject; +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.GtfsShape; +import org.mobilitydata.gtfsvalidator.table.GtfsShapeSchema; +import org.mobilitydata.gtfsvalidator.table.GtfsShapeTableContainer; + +/** + * Validates that every shape (identified by shape_id) has more than one shape_point + * + *

Generated notice: {@link SingleShapePointNotice} + */ +@GtfsValidator +public class SingleShapePointValidator extends FileValidator { + private final GtfsShapeTableContainer shapeTable; + + @Inject + SingleShapePointValidator(GtfsShapeTableContainer shapeTable) { + this.shapeTable = shapeTable; + } + + @Override + public void validate(NoticeContainer noticeContainer) { + Map shapePointCounts = new HashMap<>(); + Map shapePointRowNumbers = new HashMap<>(); + for (GtfsShape shape : shapeTable.getEntities()) { + String shapeId = shape.shapeId(); + shapePointCounts.put(shapeId, shapePointCounts.getOrDefault(shapeId, 0) + 1); + shapePointRowNumbers.put(shapeId, shape.csvRowNumber()); + } + + for (Map.Entry entry : shapePointCounts.entrySet()) { + if (entry.getValue() == 1) { + noticeContainer.addValidationNotice( + new SingleShapePointNotice( + entry.getKey(), shapePointRowNumbers.getOrDefault(entry.getKey(), 0))); + } + } + } + + /** + * The shape within `shapes.txt` contains a single shape point. + * + *

A shape should contain more than one shape point to visualize the route + */ + @GtfsValidationNotice( + severity = WARNING, + files = @GtfsValidationNotice.FileRefs({GtfsShapeSchema.class})) + static class SingleShapePointNotice extends ValidationNotice { + /** The faulty record's id. */ + private final String shapeId; + + /** The row number of the faulty record. */ + private final int csvRowNumber; + + SingleShapePointNotice(String shapeId, int csvRowNumber) { + this.shapeId = shapeId; + this.csvRowNumber = csvRowNumber; + } + } +} diff --git a/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/SingleShapePointValidatorTest.java b/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/SingleShapePointValidatorTest.java new file mode 100644 index 0000000000..d81a010791 --- /dev/null +++ b/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/SingleShapePointValidatorTest.java @@ -0,0 +1,59 @@ +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.GtfsShape; +import org.mobilitydata.gtfsvalidator.table.GtfsShapeTableContainer; + +public class SingleShapePointValidatorTest { + public static GtfsShape createShapePoint( + int csvRowNumber, + String shapeId, + double shapePtLat, + double shapePtLon, + int shapePtSequence, + double shapeDistTraveled) { + return new GtfsShape.Builder() + .setCsvRowNumber(csvRowNumber) + .setShapeId(shapeId) + .setShapePtLat(shapePtLat) + .setShapePtLon(shapePtLon) + .setShapePtSequence(shapePtSequence) + .setShapeDistTraveled(shapeDistTraveled) + .build(); + } + + private static List generateNotices(List shapes) { + NoticeContainer noticeContainer = new NoticeContainer(); + new SingleShapePointValidator(GtfsShapeTableContainer.forEntities(shapes, noticeContainer)) + .validate(noticeContainer); + return noticeContainer.getValidationNotices(); + } + + @Test + public void shapeWithMoreThanOneShapePointShouldNotGenerateNotice() { + assertThat( + generateNotices( + ImmutableList.of( + createShapePoint(1, "first shape id", 45.0d, 45.0d, 1, 0.0d), + createShapePoint(2, "first shape id", 45.1d, 45.0d, 2, 40.0d)))) + .isEmpty(); + } + + @Test + public void unusedShapeShouldGenerateNotice() { + assertThat( + generateNotices( + ImmutableList.of( + createShapePoint(1, "first shape id", 45.0d, 45.0d, 1, 40.0d), + createShapePoint(2, "first shape id", 45.1d, 45.0d, 2, 40.0d), + createShapePoint(3, "second shape id", 45.0d, 45.0d, 1, 40.0d)))) + .containsExactly( + new SingleShapePointValidator.SingleShapePointNotice("second shape id", 3)); + } +}