matrix routing:

- only allow <2 countries to be passed.
- do not route over chains with first node is port or station
- check that intermediate nodes have all "intermediate"-proeprty (exception near by routing nodes)
This commit is contained in:
Jan 2025-09-27 18:48:21 +02:00
parent 931f5d6713
commit 3a9691dc6f
3 changed files with 63 additions and 8 deletions

View file

@ -128,6 +128,8 @@ public class ContainerRateRepository {
LEFT JOIN node AS from_node ON from_node.id = container_rate.from_node_id
LEFT JOIN validity_period ON validity_period.id = container_rate.validity_period_id
WHERE validity_period.state = ?
AND to_node.is_deprecated = FALSE
AND from_node.is_deprecated = FALSE
AND (container_rate.container_rate_type = ? OR container_rate.container_rate_type = ?)
AND container_rate.from_node_id = ? AND to_node.country_id IN (%s)""".formatted(
destinationCountryPlaceholders);
@ -162,6 +164,8 @@ public class ContainerRateRepository {
LEFT JOIN node AS from_node ON from_node.id = container_rate.from_node_id
LEFT JOIN validity_period ON validity_period.id = container_rate.validity_period_id
WHERE validity_period.state = ?
AND to_node.is_deprecated = FALSE
AND from_node.is_deprecated = FALSE
AND container_rate.from_node_id = ? AND container_rate.container_rate_type = ?""";
return jdbcTemplate.query(query, new ContainerRateMapper(true), ValidityPeriodState.VALID.name(), mainRun.getToNodeId(), TransportType.POST_RUN.name());
@ -206,6 +210,20 @@ public class ContainerRateRepository {
);
}
@Transactional
public boolean hasMainRun(Integer nodeId) {
String query = """
SELECT EXISTS(
SELECT 1 FROM container_rate
WHERE (from_node_id = ? OR to_node_id = ?)
AND (container_rate_type = ? OR container_rate_type = ?)
)
""";
return Boolean.TRUE.equals(jdbcTemplate.queryForObject(query, Boolean.class,
nodeId, nodeId, TransportType.SEA.name(), TransportType.RAIL.name()));
}
private static class ContainerRateMapper implements RowMapper<ContainerRate> {

View file

@ -61,7 +61,7 @@ public class ChainResolver {
while (!chainStack.isEmpty()) {
var validationObject = chainStack.pop();
if(!validationObject.checkCircularReference()) {
if (!validationObject.checkCircularReference()) {
while (validationObject.hasNext()) {
Node currentNode = validationObject.getCurrentNode();
chainStack.addAll(validationObject.validateChains(nodeRepository.getChainsById(currentNode.getId())));
@ -72,7 +72,7 @@ public class ChainResolver {
validationObject.next();
}
if (validationObject.valid())
if (validationObject.valid() && !checkDeprecated(validationObject.getChain()))
foundChains.add(new ArrayList<>(validationObject.getChain()));
} else {
log.warn("Circular reference detected while building predecessor chain for node {}", nodeId);
@ -80,11 +80,19 @@ public class ChainResolver {
}
// remove chains if there is a node that cannot act as intermediate.
foundChains = foundChains.stream().filter(chain -> chain.stream().anyMatch(n -> !n.getIntermediate())).toList();
log.info("Found {} chains for node {}", foundChains.size(), nodeId);
log.info("Found chains: {}", foundChains);
return foundChains;
}
private boolean checkDeprecated(List<Node> chain) {
return chain.stream().anyMatch(Node::getDeprecated);
}
/**
* Helper class for chain validation and processing.
* <p>
@ -96,13 +104,19 @@ public class ChainResolver {
@Renderer(text = "getFullChainView()")
private static class ChainValidationObject {
/** Current position in the chain being processed */
/**
* Current position in the chain being processed
*/
int chainPointer;
/** List of nodes representing the current chain */
/**
* List of nodes representing the current chain
*/
List<Node> chain;
/** Flag indicating if the chain is valid */
/**
* Flag indicating if the chain is valid
*/
private boolean chainValid;
/**
@ -163,7 +177,7 @@ public class ChainResolver {
* the chain object itself is a solution -> return empty.
*/
if(chainPointer == chain.size() - 1 && foreignChains.stream().allMatch(List::isEmpty))
if (chainPointer == chain.size() - 1 && foreignChains.stream().allMatch(List::isEmpty))
return Collections.emptyList();
int foreignIdx = 0;

View file

@ -309,6 +309,10 @@ public class RoutingService {
Collection<TemporaryRateObject> rates = container.getRates();
for (var chain : chains) {
if (skipCain(chain, container))
continue;
var toNode = chain.isEmpty() ? container.getDestinationNode() : chain.getLast();
TemporaryRateObject finalSection = new TemporaryRateObject(container.getSourceNode(), toNode, TemporaryRateObject.TemporaryRateObjectType.MATRIX);
@ -363,9 +367,21 @@ public class RoutingService {
}
}
private boolean skipCain(List<Node> chain, TemporaryContainer container) {
if (containerRateRepository.hasMainRun(chain.getFirst().getId()))
return true;
Set<Integer> countryIds = new HashSet<>();
countryIds.add(container.getSourceNode().getCountryId());
chain.forEach(node -> countryIds.add(node.getCountryId()));
return countryIds.size() > 2;
}
private TemporaryRateObject connectNearByNodes(Node chainEnd, List<Node> nearByNodes, TemporaryContainer container) {
if(nearByNodes == null)
return null;
for (var nearbyNode : nearByNodes) {
TemporaryRateObject nearByRate = connectNodes(nearbyNode, chainEnd, container);
@ -541,6 +557,9 @@ public class RoutingService {
Node mainRunEndNode = nodeRepository.getById(mainRun.getToNodeId()).orElseThrow();
Node mainRunStartNode = outboundNodes.stream().filter(n -> n.getId().equals(mainRun.getFromNodeId())).findFirst().orElseThrow();
if(!mainRunStartNode.getIntermediate() || !mainRunEndNode.getIntermediate())
continue;
TemporaryRateObject mainRunObj = new TemporaryRateObject(mainRunStartNode, mainRunEndNode, TemporaryRateObject.TemporaryRateObjectType.MAIN_RUN, mainRun);
var postRuns = container.getPostRuns().get(mainRun.getId());
@ -556,6 +575,10 @@ public class RoutingService {
for (var postRun : container.getPostRuns().get(mainRun.getId())) {
Node postRunEndNode = nodeRepository.getById(postRun.getToNodeId()).orElseThrow();
if (!postRunEndNode.getIntermediate())
continue;
TemporaryRateObject postRunObj = new TemporaryRateObject(mainRunEndNode, postRunEndNode, TemporaryRateObject.TemporaryRateObjectType.POST_RUN, postRun);
var sortedChains = sortedChainMap.get(quality).get(postRun.getId());