Skip to content

Commit

Permalink
Merge pull request #6037 from leonardehrenfried/netex-pass-through-st…
Browse files Browse the repository at this point in the history
…op-point

Allow NeTEx ServiceJourneyPatterns with stopUse=passthrough
  • Loading branch information
leonardehrenfried authored Sep 5, 2024
2 parents f2d8a1e + 1551ea4 commit 95415dc
Show file tree
Hide file tree
Showing 2 changed files with 207 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
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;
import org.rutebanken.netex.model.ServiceJourney;
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<String, ServiceJourney> {

@Override
Expand All @@ -12,16 +22,29 @@ public Status validate(ServiceJourney sj) {
.getJourneyPatternsById()
.lookup(getPatternId(sj));

int nStopPointsInJourneyPattern = journeyPattern
int nStopPointsInJourneyPattern = (int) journeyPattern
.getPointsInSequence()
.getPointInJourneyPatternOrStopPointInJourneyPatternOrTimingPointInJourneyPattern()
.size();
.stream()
.filter(Predicate.not(JourneyPatternSJMismatch::isPassThrough))
.count();

int nTimetablePassingTimes = sj.getPassingTimes().getTimetabledPassingTime().size();

return nStopPointsInJourneyPattern != nTimetablePassingTimes ? Status.DISCARD : Status.OK;
}

/**
* 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
public DataImportIssue logMessage(String key, ServiceJourney sj) {
return new StopPointsMismatch(sj.getId(), getPatternId(sj));
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String> 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;
}
}
}

0 comments on commit 95415dc

Please sign in to comment.