From f4df20745a16b688a6a94b3723796f9f2e530e68 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 2 Sep 2024 14:41:39 +0200 Subject: [PATCH 1/3] Take into account pass-through for mismatch check --- .../validation/JourneyPatternSJMismatch.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/netex/validation/JourneyPatternSJMismatch.java b/src/main/java/org/opentripplanner/netex/validation/JourneyPatternSJMismatch.java index 10c990a31d0..216008621c3 100644 --- a/src/main/java/org/opentripplanner/netex/validation/JourneyPatternSJMismatch.java +++ b/src/main/java/org/opentripplanner/netex/validation/JourneyPatternSJMismatch.java @@ -2,7 +2,10 @@ import org.opentripplanner.graph_builder.issue.api.DataImportIssue; import org.rutebanken.netex.model.JourneyPattern_VersionStructure; +import org.rutebanken.netex.model.PointInLinkSequence_VersionedChildStructure; import org.rutebanken.netex.model.ServiceJourney; +import org.rutebanken.netex.model.StopPointInJourneyPattern; +import org.rutebanken.netex.model.StopUseEnumeration; class JourneyPatternSJMismatch extends AbstractHMapValidationRule { @@ -12,16 +15,25 @@ public Status validate(ServiceJourney sj) { .getJourneyPatternsById() .lookup(getPatternId(sj)); - int nStopPointsInJourneyPattern = journeyPattern + int nStopPointsInJourneyPattern = (int) journeyPattern .getPointsInSequence() .getPointInJourneyPatternOrStopPointInJourneyPatternOrTimingPointInJourneyPattern() - .size(); + .stream() + .filter(JourneyPatternSJMismatch::stopsAtQuay) + .count(); int nTimetablePassingTimes = sj.getPassingTimes().getTimetabledPassingTime().size(); return nStopPointsInJourneyPattern != nTimetablePassingTimes ? Status.DISCARD : Status.OK; } + private static boolean stopsAtQuay(PointInLinkSequence_VersionedChildStructure point) { + return switch (point) { + case StopPointInJourneyPattern spijp -> spijp.getStopUse() != StopUseEnumeration.PASSTHROUGH; + default -> true; + }; + } + @Override public DataImportIssue logMessage(String key, ServiceJourney sj) { return new StopPointsMismatch(sj.getId(), getPatternId(sj)); From 1f8cea06e2c11e074f12a643b82f45d6ad460d9e Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 2 Sep 2024 17:33:57 +0200 Subject: [PATCH 2/3] Add tests --- .../JourneyPatternSJMismatchTest.java | 182 ++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 src/test/java/org/opentripplanner/netex/validation/JourneyPatternSJMismatchTest.java diff --git a/src/test/java/org/opentripplanner/netex/validation/JourneyPatternSJMismatchTest.java b/src/test/java/org/opentripplanner/netex/validation/JourneyPatternSJMismatchTest.java new file mode 100644 index 00000000000..f1c87342800 --- /dev/null +++ b/src/test/java/org/opentripplanner/netex/validation/JourneyPatternSJMismatchTest.java @@ -0,0 +1,182 @@ +package org.opentripplanner.netex.validation; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.netex.index.api.HMapValidationRule.Status.DISCARD; +import static org.opentripplanner.netex.index.api.HMapValidationRule.Status.OK; +import static org.rutebanken.netex.model.StopUseEnumeration.ACCESS; +import static org.rutebanken.netex.model.StopUseEnumeration.PASSTHROUGH; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.opentripplanner.netex.index.NetexEntityIndex; +import org.opentripplanner.netex.mapping.MappingSupport; +import org.rutebanken.netex.model.EntityStructure; +import org.rutebanken.netex.model.JourneyPatternRefStructure; +import org.rutebanken.netex.model.PointInJourneyPatternRefStructure; +import org.rutebanken.netex.model.PointInLinkSequence_VersionedChildStructure; +import org.rutebanken.netex.model.PointsInJourneyPattern_RelStructure; +import org.rutebanken.netex.model.ServiceJourney; +import org.rutebanken.netex.model.ServiceJourneyPattern; +import org.rutebanken.netex.model.StopPointInJourneyPattern; +import org.rutebanken.netex.model.StopUseEnumeration; +import org.rutebanken.netex.model.TimetabledPassingTime; +import org.rutebanken.netex.model.TimetabledPassingTimes_RelStructure; + +class JourneyPatternSJMismatchTest { + + private static final String PATTERN_ID = "pattern"; + private static final String JOURNEY_ID = "journey"; + + @Test + void patternAndJourneyMatch() { + var pattern = new ServiceJourneyPatternBuilder(PATTERN_ID) + .withPointsInSequence(1, 2, 3) + .build(); + + var index = new NetexEntityIndex(); + index.journeyPatternsById.add(pattern); + + var journey = new ServiceJourneyBuilder(JOURNEY_ID) + .withPatternId(PATTERN_ID) + .withPassingTimes(pattern.getPointsInSequence()) + .build(); + + var rule = new JourneyPatternSJMismatch(); + rule.setup(index.readOnlyView()); + + assertEquals(OK, rule.validate(journey)); + } + + @Test + void differentNumberOfPassingTimes() { + var pattern = new ServiceJourneyPatternBuilder(PATTERN_ID) + .withPointsInSequence(1, 2, 3) + .build(); + + var index = new NetexEntityIndex(); + index.journeyPatternsById.add(pattern); + + var journey = new ServiceJourneyBuilder(JOURNEY_ID) + .withPatternId(PATTERN_ID) + .withPassingTimes(List.of("P-1", "P-2")) + .build(); + + var rule = new JourneyPatternSJMismatch(); + rule.setup(index.readOnlyView()); + + assertEquals(DISCARD, rule.validate(journey)); + } + + @Test + void passThrough() { + var pattern = new ServiceJourneyPatternBuilder(PATTERN_ID) + .addStopPointInSequence(1, ACCESS) + .addStopPointInSequence(2, PASSTHROUGH) + .addStopPointInSequence(3, ACCESS) + .build(); + + var index = new NetexEntityIndex(); + index.journeyPatternsById.add(pattern); + + var journey = new ServiceJourneyBuilder(JOURNEY_ID) + .withPatternId(PATTERN_ID) + .withPassingTimes(List.of("P-1", "P-3")) + .build(); + + var rule = new JourneyPatternSJMismatch(); + rule.setup(index.readOnlyView()); + + assertEquals(OK, rule.validate(journey)); + } + + static class ServiceJourneyPatternBuilder { + + private final ServiceJourneyPattern pattern = new ServiceJourneyPattern(); + private final PointsInJourneyPattern_RelStructure points = new PointsInJourneyPattern_RelStructure(); + + ServiceJourneyPatternBuilder(String id) { + pattern.setId(id); + pattern.setPointsInSequence(points); + } + + ServiceJourneyPatternBuilder withPointsInSequence(int... orders) { + var items = Arrays.stream(orders).mapToObj(order -> pointInPattern(order, ACCESS)).toList(); + points.withPointInJourneyPatternOrStopPointInJourneyPatternOrTimingPointInJourneyPattern( + items + ); + return this; + } + + ServiceJourneyPatternBuilder addStopPointInSequence(int order, StopUseEnumeration stopUse) { + var point = pointInPattern(order, stopUse); + points + .getPointInJourneyPatternOrStopPointInJourneyPatternOrTimingPointInJourneyPattern() + .add(point); + return this; + } + + ServiceJourneyPattern build() { + return pattern; + } + + private static PointInLinkSequence_VersionedChildStructure pointInPattern( + int order, + StopUseEnumeration stopUse + ) { + var p = new StopPointInJourneyPattern(); + p.setId("P-%s".formatted(order)); + p.setOrder(BigInteger.valueOf(order)); + p.setStopUse(stopUse); + return p; + } + } + + static class ServiceJourneyBuilder { + + private final ServiceJourney journey = new ServiceJourney(); + + ServiceJourneyBuilder(String id) { + journey.setId(id); + } + + ServiceJourneyBuilder withPatternId(String id) { + var ref = MappingSupport.createWrappedRef(id, JourneyPatternRefStructure.class); + journey.withJourneyPatternRef(ref); + return this; + } + + ServiceJourneyBuilder withPassingTimes(PointsInJourneyPattern_RelStructure pointsInSequence) { + return withPassingTimes( + pointsInSequence + .getPointInJourneyPatternOrStopPointInJourneyPatternOrTimingPointInJourneyPattern() + .stream() + .map(EntityStructure::getId) + .toList() + ); + } + + ServiceJourneyBuilder withPassingTimes(Collection ids) { + var passingTimes = new TimetabledPassingTimes_RelStructure(); + passingTimes.withTimetabledPassingTime( + ids.stream().map(ServiceJourneyBuilder::timetabledPassingTime).toList() + ); + journey.withPassingTimes(passingTimes); + return this; + } + + ServiceJourney build() { + return journey; + } + + private static TimetabledPassingTime timetabledPassingTime(String pointInPatternRef) { + var passingTime = new TimetabledPassingTime(); + passingTime.withPointInJourneyPatternRef( + MappingSupport.createWrappedRef(pointInPatternRef, PointInJourneyPatternRefStructure.class) + ); + return passingTime; + } + } +} From 1551ea4ac28f764b35f52aec09b1f136e2b20f99 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 2 Sep 2024 18:02:46 +0200 Subject: [PATCH 3/3] Update documentation --- .../validation/JourneyPatternSJMismatch.java | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/opentripplanner/netex/validation/JourneyPatternSJMismatch.java b/src/main/java/org/opentripplanner/netex/validation/JourneyPatternSJMismatch.java index 216008621c3..28fc710a6ae 100644 --- a/src/main/java/org/opentripplanner/netex/validation/JourneyPatternSJMismatch.java +++ b/src/main/java/org/opentripplanner/netex/validation/JourneyPatternSJMismatch.java @@ -1,5 +1,6 @@ package org.opentripplanner.netex.validation; +import java.util.function.Predicate; import org.opentripplanner.graph_builder.issue.api.DataImportIssue; import org.rutebanken.netex.model.JourneyPattern_VersionStructure; import org.rutebanken.netex.model.PointInLinkSequence_VersionedChildStructure; @@ -7,6 +8,12 @@ import org.rutebanken.netex.model.StopPointInJourneyPattern; import org.rutebanken.netex.model.StopUseEnumeration; +/** + * Validates that the number of passing times in the journey and the number of stop points in the + * pattern are equal. + * It also takes into account that some points in the pattern can be set to stopUse=passthrough + * which means that those must not be referenced in the journey. + */ class JourneyPatternSJMismatch extends AbstractHMapValidationRule { @Override @@ -19,7 +26,7 @@ public Status validate(ServiceJourney sj) { .getPointsInSequence() .getPointInJourneyPatternOrStopPointInJourneyPatternOrTimingPointInJourneyPattern() .stream() - .filter(JourneyPatternSJMismatch::stopsAtQuay) + .filter(Predicate.not(JourneyPatternSJMismatch::isPassThrough)) .count(); int nTimetablePassingTimes = sj.getPassingTimes().getTimetabledPassingTime().size(); @@ -27,11 +34,15 @@ public Status validate(ServiceJourney sj) { return nStopPointsInJourneyPattern != nTimetablePassingTimes ? Status.DISCARD : Status.OK; } - private static boolean stopsAtQuay(PointInLinkSequence_VersionedChildStructure point) { - return switch (point) { - case StopPointInJourneyPattern spijp -> spijp.getStopUse() != StopUseEnumeration.PASSTHROUGH; - default -> true; - }; + /** + * Does the stop point in the sequence represent a stop where the vehicle passes through without + * stopping? + */ + private static boolean isPassThrough(PointInLinkSequence_VersionedChildStructure point) { + return ( + point instanceof StopPointInJourneyPattern spijp && + spijp.getStopUse() == StopUseEnumeration.PASSTHROUGH + ); } @Override