Skip to content

Commit

Permalink
Add mismatched transport mode validator (#604)
Browse files Browse the repository at this point in the history
  • Loading branch information
vpaturet authored Dec 3, 2024
1 parent eb4b9e9 commit d47608a
Show file tree
Hide file tree
Showing 13 changed files with 1,023 additions and 17 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
<entur.google.pubsub.emulator.download.skip>false</entur.google.pubsub.emulator.download.skip>
<camel.version>4.4.4</camel.version>
<entur.helpers.version>3.0</entur.helpers.version>
<netex-validator-java.version>7.0.0</netex-validator-java.version>
<netex-validator-java.version>8.0.0</netex-validator-java.version>
<netex-parser-java.version>3.1.29</netex-parser-java.version>
<jts-core.version>1.20.0</jts-core.version>
<commons-io.version>2.11.0</commons-io.version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
import java.util.Map;
import org.entur.netex.validation.validator.jaxb.JAXBValidationContext;
import org.entur.netex.validation.validator.jaxb.NetexDataCollector;
import org.entur.netex.validation.validator.jaxb.support.FlexibleLineUtils;
import org.entur.netex.validation.validator.model.SimpleLine;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.rutebanken.netex.model.FlexibleLineTypeEnumeration;

public class LineInfoCollector extends NetexDataCollector {

Expand Down Expand Up @@ -70,7 +70,9 @@ private SimpleLine lineInfo(JAXBValidationContext validationContext) {
validationContext
.flexibleLines()
.stream()
.filter(FlexibleLineUtils::isFixedFlexibleLine)
.filter(f ->
f.getFlexibleLineType() == FlexibleLineTypeEnumeration.FIXED
)
.findFirst()
.map(line -> SimpleLine.of(line, validationContext.getFileName()))
.orElse(null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.entur.netex.validation.validator.jaxb.JAXBValidationContext;
import org.entur.netex.validation.validator.model.QuayCoordinates;
import org.entur.netex.validation.validator.model.ScheduledStopPointId;
import org.entur.netex.validation.validator.model.TransportModeAndSubMode;
import org.rutebanken.netex.model.AllVehicleModesOfTransportEnumeration;
import org.rutebanken.netex.model.JourneyPattern;

Expand Down Expand Up @@ -45,9 +46,11 @@ public Builder(JAXBValidationContext validationContext) {
public UnexpectedDistanceBetweenStopPointsContext build(
JourneyPattern journeyPattern
) {
TransportModeAndSubMode transportModeAndSubMode =
validationContext.transportModeAndSubMode(journeyPattern);
return new UnexpectedDistanceBetweenStopPointsContext(
journeyPattern.getId(),
validationContext.transportMode(journeyPattern),
transportModeAndSubMode == null ? null : transportModeAndSubMode.mode(),
NetexUtils
.stopPointsInJourneyPattern(journeyPattern)
.stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public UnexpectedSpeedContext build(ServiceJourney serviceJourney) {

return new UnexpectedSpeedContext(
serviceJourney,
validationContext.transportMode(serviceJourney),
validationContext.transportModeAndSubMode(serviceJourney).mode(),
validationContext
.timetabledPassingTimes(serviceJourney)
.stream()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
package no.entur.antu.validation.validator.servicejourney.transportmode;

import static no.entur.antu.validation.validator.support.NetexUtils.stopPointsInJourneyPattern;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.entur.netex.validation.validator.Severity;
import org.entur.netex.validation.validator.ValidationIssue;
import org.entur.netex.validation.validator.ValidationRule;
import org.entur.netex.validation.validator.jaxb.JAXBValidationContext;
import org.entur.netex.validation.validator.jaxb.JAXBValidator;
import org.entur.netex.validation.validator.model.QuayId;
import org.entur.netex.validation.validator.model.ScheduledStopPointId;
import org.entur.netex.validation.validator.model.TransportModeAndSubMode;
import org.rutebanken.netex.model.AllVehicleModesOfTransportEnumeration;
import org.rutebanken.netex.model.BusSubmodeEnumeration;
import org.rutebanken.netex.model.JourneyPattern;
import org.rutebanken.netex.model.ServiceJourney;
import org.rutebanken.netex.model.StopPointInJourneyPattern;

/**
* Validates that the transport mode and sub-mode of a service journey matches the quays it visits.
* Chouette reference: 4-VehicleJourney-3
*/
public class MismatchedTransportModeSubModeValidator implements JAXBValidator {

static final ValidationRule RULE_INVALID_TRANSPORT_MODE = new ValidationRule(
"INVALID_TRANSPORT_MODE",
"Invalid transport mode",
"Invalid transport mode: The quay %s accepts %s, but the ServiceJourney %s is defined as %s",
Severity.ERROR
);

static final ValidationRule RULE_INVALID_TRANSPORT_SUB_MODE =
new ValidationRule(
"INVALID_TRANSPORT_SUB_MODE",
"Invalid transport sub-mode",
"Invalid transport sub-mode: The quay %s accepts %s, but the ServiceJourney %s is defined as %s",
Severity.ERROR
);

/**
* Iterate through all stop points of all service journeys and compare the transport mode of the associated quay with
* the transport mode of the service journey.
*/
@Override
public List<ValidationIssue> validate(
JAXBValidationContext validationContext
) {
List<ValidationIssue> issues = new ArrayList<>();

for (ServiceJourney serviceJourney : validationContext.serviceJourneys()) {
TransportModeAndSubMode serviceJourneyTransportMode =
validationContext.transportModeAndSubMode(serviceJourney);

// skip if neither the service journey nor the line have a transport mode.
// this should be validated separately.
if (serviceJourneyTransportMode == null) {
continue;
}

JourneyPattern journeyPattern = validationContext.journeyPattern(
serviceJourney
);
List<StopPointInJourneyPattern> stopPointInJourneyPatterns =
stopPointsInJourneyPattern(journeyPattern);

for (StopPointInJourneyPattern stopPointInJourneyPattern : stopPointInJourneyPatterns) {
QuayId quayId = validationContext.quayIdForScheduledStopPoint(
ScheduledStopPointId.of(stopPointInJourneyPattern)
);

// skip if the scheduled stop point is not mapped to a quay.
// this should be validated separately.
if (quayId == null) {
continue;
}
TransportModeAndSubMode quayTransportModeAndSubMode =
validationContext.transportModeAndSubModeForQuayId(quayId);

// skip if the quay does not have a transport mode.
// this can be caused by a data issue in the external stop register.
if (quayTransportModeAndSubMode == null) {
continue;
}

validationIssue(
quayId.id(),
serviceJourney.getId(),
quayTransportModeAndSubMode,
serviceJourneyTransportMode,
validationContext
)
.ifPresent(issues::add);
}
}
return issues;
}

/**
* Return a validation issue if the service journey transport mode/submode does not match
* the quay transport mode/submode.
*/
private Optional<ValidationIssue> validationIssue(
String quayId,
String serviceJourneyId,
TransportModeAndSubMode quayTransportModeAndSubMode,
TransportModeAndSubMode serviceJourneyTransportModeAndSubMode,
JAXBValidationContext validationContext
) {
if (
quayTransportModeAndSubMode.mode() !=
serviceJourneyTransportModeAndSubMode.mode()
) {
// Coach and bus are interchangeable.
if (
busServingCoachStop(
quayTransportModeAndSubMode,
serviceJourneyTransportModeAndSubMode
) ||
coachServingBusStop(
quayTransportModeAndSubMode,
serviceJourneyTransportModeAndSubMode
)
) {
return Optional.empty();
}

// Taxi can stop on bus and coach stops.
if (
taxiServingBusOrCoachStop(
quayTransportModeAndSubMode,
serviceJourneyTransportModeAndSubMode
)
) {
return Optional.empty();
}

return Optional.of(
new ValidationIssue(
RULE_INVALID_TRANSPORT_MODE,
validationContext.dataLocation(serviceJourneyId),
quayId,
quayTransportModeAndSubMode.mode(),
serviceJourneyId,
serviceJourneyTransportModeAndSubMode.mode()
)
);
}

// Only rail replacement bus service can visit rail replacement bus stops.
if (
quayTransportModeAndSubMode.subMode() != null &&
BusSubmodeEnumeration.RAIL_REPLACEMENT_BUS
.value()
.equals(quayTransportModeAndSubMode.subMode().name()) &&
!BusSubmodeEnumeration.RAIL_REPLACEMENT_BUS
.value()
.equals(serviceJourneyTransportModeAndSubMode.subMode().name())
) {
return Optional.of(
new ValidationIssue(
RULE_INVALID_TRANSPORT_SUB_MODE,
validationContext.dataLocation(serviceJourneyId),
quayId,
quayTransportModeAndSubMode.subMode(),
serviceJourneyId,
serviceJourneyTransportModeAndSubMode.subMode()
)
);
}

return Optional.empty();
}

private static boolean taxiServingBusOrCoachStop(
TransportModeAndSubMode quayTransportModeAndSubMode,
TransportModeAndSubMode serviceJourneyTransportModeAndSubMode
) {
return (
serviceJourneyTransportModeAndSubMode.mode() ==
AllVehicleModesOfTransportEnumeration.TAXI &&
(
quayTransportModeAndSubMode.mode() ==
AllVehicleModesOfTransportEnumeration.BUS ||
quayTransportModeAndSubMode.mode() ==
AllVehicleModesOfTransportEnumeration.COACH
)
);
}

private static boolean coachServingBusStop(
TransportModeAndSubMode quayTransportModeAndSubMode,
TransportModeAndSubMode serviceJourneyTransportModeAndSubMode
) {
return (
quayTransportModeAndSubMode.mode() ==
AllVehicleModesOfTransportEnumeration.BUS &&
serviceJourneyTransportModeAndSubMode.mode() ==
AllVehicleModesOfTransportEnumeration.COACH
);
}

private static boolean busServingCoachStop(
TransportModeAndSubMode quayTransportModeAndSubMode,
TransportModeAndSubMode serviceJourneyTransportModeAndSubMode
) {
return (
quayTransportModeAndSubMode.mode() ==
AllVehicleModesOfTransportEnumeration.COACH &&
serviceJourneyTransportModeAndSubMode.mode() ==
AllVehicleModesOfTransportEnumeration.BUS
);
}

@Override
public Set<ValidationRule> getRules() {
return Set.of(RULE_INVALID_TRANSPORT_MODE, RULE_INVALID_TRANSPORT_SUB_MODE);
}
}
26 changes: 18 additions & 8 deletions src/test/java/no/entur/antu/stop/DefaultStopPlaceResourceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,12 @@ class DefaultStopPlaceResourceTest {
private static final String NSR_STOP_PLACE_NAME_2 = "Stop Place 456";
private static final AllVehicleModesOfTransportEnumeration NSR_STOP_PLACE_MODE_1 =
AllVehicleModesOfTransportEnumeration.BUS;
private static final BusSubmodeEnumeration NSR_STOP_PLACE_SUBMODE_1 =
BusSubmodeEnumeration.LOCAL_BUS;
private static final AllVehicleModesOfTransportEnumeration NSR_STOP_PLACE_MODE_2 =
AllVehicleModesOfTransportEnumeration.RAIL;
private static final RailSubmodeEnumeration NSR_STOP_PLACE_SUBMODE_2 =
RailSubmodeEnumeration.LOCAL;

private static final ObjectFactory netexFactory = new ObjectFactory();

Expand Down Expand Up @@ -61,13 +65,15 @@ void setUp() {
.withVersion("1")
.withName(new MultilingualString().withValue(NSR_STOP_PLACE_NAME_1))
.withTransportMode(NSR_STOP_PLACE_MODE_1)
.withBusSubmode(NSR_STOP_PLACE_SUBMODE_1)
),
netexFactory.createStopPlace_(
new StopPlace()
.withId(NSR_STOP_PLACE_2)
.withVersion("1")
.withName(new MultilingualString().withValue(NSR_STOP_PLACE_NAME_2))
.withTransportMode(NSR_STOP_PLACE_MODE_2)
.withRailSubmode(NSR_STOP_PLACE_SUBMODE_2)
)
);
netexEntitiesIndex
Expand Down Expand Up @@ -116,18 +122,22 @@ void getStopPlaces() {
DefaultStopPlaceResource defaultStopPlaceResource =
new DefaultStopPlaceResource(() -> netexEntitiesIndex);
Assertions.assertEquals(2, defaultStopPlaceResource.getStopPlaces().size());
TransportModeAndSubMode transportModeAndSubMode1 =
new TransportModeAndSubMode(
NSR_STOP_PLACE_MODE_1,
new TransportSubMode(NSR_STOP_PLACE_SUBMODE_1.value())
);
TransportModeAndSubMode transportModeAndSubMode2 =
new TransportModeAndSubMode(
NSR_STOP_PLACE_MODE_2,
new TransportSubMode(NSR_STOP_PLACE_SUBMODE_2.value())
);
Assertions.assertEquals(
Map.of(
new StopPlaceId(NSR_STOP_PLACE_1),
new SimpleStopPlace(
NSR_STOP_PLACE_NAME_1,
new TransportModeAndSubMode(NSR_STOP_PLACE_MODE_1, null)
),
new SimpleStopPlace(NSR_STOP_PLACE_NAME_1, transportModeAndSubMode1),
new StopPlaceId(NSR_STOP_PLACE_2),
new SimpleStopPlace(
NSR_STOP_PLACE_NAME_2,
new TransportModeAndSubMode(NSR_STOP_PLACE_MODE_2, null)
)
new SimpleStopPlace(NSR_STOP_PLACE_NAME_2, transportModeAndSubMode2)
),
defaultStopPlaceResource.getStopPlaces()
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ void sharedFileShouldBeInvalidated() {
validationIssues
.stream()
.anyMatch(validationIssue ->
validationIssue.rule().code().equals(FileNameValidator.RULE.code())
validationIssue.rule().equals(FileNameValidator.RULE)
)
);
}
Expand Down Expand Up @@ -89,7 +89,7 @@ void lineFileShouldBeInvalidated() {
validationIssues
.stream()
.anyMatch(validationIssue ->
validationIssue.rule().code().equals(FileNameValidator.RULE.code())
validationIssue.rule().equals(FileNameValidator.RULE)
)
);
}
Expand Down
Loading

0 comments on commit d47608a

Please sign in to comment.