Skip to content

Commit 00a85f8

Browse files
authored
Merge pull request opentripplanner#7092 from entur/fix-netex-duplicate-stop-points
Fix NeTEx graph builder crash on duplicate StopPointInJourneyPattern
2 parents 18b786f + 0f6be1a commit 00a85f8

3 files changed

Lines changed: 149 additions & 0 deletions

File tree

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package org.opentripplanner.netex.validation;
2+
3+
import java.util.HashSet;
4+
import java.util.Set;
5+
import org.opentripplanner.graph_builder.issue.api.DataImportIssue;
6+
import org.opentripplanner.graph_builder.issue.api.Issue;
7+
import org.rutebanken.netex.model.EntityStructure;
8+
import org.rutebanken.netex.model.JourneyPattern_VersionStructure;
9+
import org.rutebanken.netex.model.ServiceJourney;
10+
11+
/**
12+
* Validates that a JourneyPattern does not contain duplicate StopPointInJourneyPattern IDs.
13+
* Duplicate stop point IDs in a journey pattern indicate invalid NeTEx data and will cause
14+
* failures when creating lookup maps.
15+
*/
16+
class JourneyPatternDuplicateStopPoints extends AbstractHMapValidationRule<String, ServiceJourney> {
17+
18+
private String duplicateStopPointId;
19+
private String journeyPatternId;
20+
21+
@Override
22+
public Status validate(ServiceJourney sj) {
23+
journeyPatternId = sj.getJourneyPatternRef().getValue().getRef();
24+
JourneyPattern_VersionStructure journeyPattern = index
25+
.getJourneyPatternsById()
26+
.lookup(journeyPatternId);
27+
28+
Set<String> seenIds = new HashSet<>();
29+
30+
for (var point : journeyPattern
31+
.getPointsInSequence()
32+
.getPointInJourneyPatternOrStopPointInJourneyPatternOrTimingPointInJourneyPattern()) {
33+
String pointId = ((EntityStructure) point).getId();
34+
if (!seenIds.add(pointId)) {
35+
duplicateStopPointId = pointId;
36+
return Status.DISCARD;
37+
}
38+
}
39+
40+
return Status.OK;
41+
}
42+
43+
@Override
44+
public DataImportIssue logMessage(String key, ServiceJourney sj) {
45+
return Issue.issue(
46+
"JourneyPatternDuplicateStopPoints",
47+
"JourneyPattern contains duplicate StopPointInJourneyPattern. " +
48+
"ServiceJourney will be skipped. " +
49+
"ServiceJourney=%s, JourneyPattern=%s, Duplicate StopPointInJourneyPattern=%s",
50+
sj.getId(),
51+
journeyPatternId,
52+
duplicateStopPointId
53+
);
54+
}
55+
}

application/src/main/java/org/opentripplanner/netex/validation/Validator.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ private void run() {
3333
validate(index.quayIdByStopPointRef, new PassengerStopAssignmentQuayNotFound());
3434
validate(index.serviceJourneyById, new JourneyPatternNotFoundInSJ());
3535
validate(index.serviceJourneyById, new JourneyPatternSJMismatch());
36+
validate(index.serviceJourneyById, new JourneyPatternDuplicateStopPoints());
3637
validate(index.serviceJourneyById, ServiceJourneyNonIncreasingPassingTime::new);
3738
validate(index.datedServiceJourneys, new DSJOperatingDayNotFound());
3839
validate(index.datedServiceJourneys, new DSJServiceJourneyNotFound());
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package org.opentripplanner.netex.validation;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.opentripplanner.netex.index.api.HMapValidationRule.Status.DISCARD;
5+
import static org.opentripplanner.netex.index.api.HMapValidationRule.Status.OK;
6+
7+
import java.math.BigInteger;
8+
import org.junit.jupiter.api.Test;
9+
import org.opentripplanner.netex.index.NetexEntityIndex;
10+
import org.opentripplanner.netex.mapping.MappingSupport;
11+
import org.rutebanken.netex.model.JourneyPatternRefStructure;
12+
import org.rutebanken.netex.model.PointsInJourneyPattern_RelStructure;
13+
import org.rutebanken.netex.model.ServiceJourney;
14+
import org.rutebanken.netex.model.ServiceJourneyPattern;
15+
import org.rutebanken.netex.model.StopPointInJourneyPattern;
16+
17+
class JourneyPatternDuplicateStopPointsTest {
18+
19+
private static final String PATTERN_ID = "pattern";
20+
private static final String JOURNEY_ID = "journey";
21+
22+
@Test
23+
void noDuplicates() {
24+
var pattern = createPattern("P-1", "P-2", "P-3");
25+
26+
var index = new NetexEntityIndex();
27+
index.journeyPatternsById.add(pattern);
28+
29+
var journey = createJourney(PATTERN_ID);
30+
31+
var rule = new JourneyPatternDuplicateStopPoints();
32+
rule.setup(index.readOnlyView());
33+
34+
assertEquals(OK, rule.validate(journey));
35+
}
36+
37+
@Test
38+
void duplicateStopPoints() {
39+
var pattern = createPattern("P-1", "P-2", "P-2");
40+
41+
var index = new NetexEntityIndex();
42+
index.journeyPatternsById.add(pattern);
43+
44+
var journey = createJourney(PATTERN_ID);
45+
46+
var rule = new JourneyPatternDuplicateStopPoints();
47+
rule.setup(index.readOnlyView());
48+
49+
assertEquals(DISCARD, rule.validate(journey));
50+
}
51+
52+
@Test
53+
void duplicateStopPointsAtDifferentPositions() {
54+
var pattern = createPattern("P-1", "P-2", "P-3", "P-1");
55+
56+
var index = new NetexEntityIndex();
57+
index.journeyPatternsById.add(pattern);
58+
59+
var journey = createJourney(PATTERN_ID);
60+
61+
var rule = new JourneyPatternDuplicateStopPoints();
62+
rule.setup(index.readOnlyView());
63+
64+
assertEquals(DISCARD, rule.validate(journey));
65+
}
66+
67+
private ServiceJourneyPattern createPattern(String... pointIds) {
68+
var pattern = new ServiceJourneyPattern();
69+
pattern.setId(PATTERN_ID);
70+
71+
var points = new PointsInJourneyPattern_RelStructure();
72+
int order = 1;
73+
for (String pointId : pointIds) {
74+
var point = new StopPointInJourneyPattern();
75+
point.setId(pointId);
76+
point.setOrder(BigInteger.valueOf(order++));
77+
points
78+
.getPointInJourneyPatternOrStopPointInJourneyPatternOrTimingPointInJourneyPattern()
79+
.add(point);
80+
}
81+
82+
pattern.setPointsInSequence(points);
83+
return pattern;
84+
}
85+
86+
private ServiceJourney createJourney(String patternId) {
87+
var journey = new ServiceJourney();
88+
journey.setId(JOURNEY_ID);
89+
var ref = MappingSupport.createWrappedRef(patternId, JourneyPatternRefStructure.class);
90+
journey.withJourneyPatternRef(ref);
91+
return journey;
92+
}
93+
}

0 commit comments

Comments
 (0)