diff --git a/src/frontend/src/components/layout/error/ErrorModal.vue b/src/frontend/src/components/layout/error/ErrorModal.vue index 18e12e4..be18870 100644 --- a/src/frontend/src/components/layout/error/ErrorModal.vue +++ b/src/frontend/src/components/layout/error/ErrorModal.vue @@ -49,7 +49,7 @@ export default { props: {isSelected: false, error: this.error}, }, { - title: 'Pinia store', + title: 'Frontend storage', component: markRaw(ErrorModalPiniaStore), props: {isSelected: false, error: this.error}, }, diff --git a/src/frontend/src/components/layout/error/ErrorModalPiniaStore.vue b/src/frontend/src/components/layout/error/ErrorModalPiniaStore.vue index 468aab9..a04264f 100644 --- a/src/frontend/src/components/layout/error/ErrorModalPiniaStore.vue +++ b/src/frontend/src/components/layout/error/ErrorModalPiniaStore.vue @@ -5,7 +5,7 @@
-

No pinia data

+ No frontend data available
@@ -82,9 +82,13 @@ export default { } .no-data { - padding: 1rem; - text-align: center; - color: #6b7280; - font-style: italic; + display: flex; + justify-content: center; + align-items: center; + font-size: 1.6rem; +} + +.space-around { + margin: 3rem; } \ No newline at end of file diff --git a/src/frontend/src/pages/ErrorLog.vue b/src/frontend/src/pages/ErrorLog.vue index edf8459..8af885b 100644 --- a/src/frontend/src/pages/ErrorLog.vue +++ b/src/frontend/src/pages/ErrorLog.vue @@ -65,7 +65,7 @@ export default { const query = { searchTerm: '', page: 1, - pageSize: 10, + pageSize: 20, } await this.fetchData(query); } diff --git a/src/main/java/de/avatic/lcc/model/db/nodes/Distance.java b/src/main/java/de/avatic/lcc/model/db/nodes/Distance.java index 9f7a316..27546d2 100644 --- a/src/main/java/de/avatic/lcc/model/db/nodes/Distance.java +++ b/src/main/java/de/avatic/lcc/model/db/nodes/Distance.java @@ -40,12 +40,15 @@ public class Distance { private DistanceMatrixState state; - @NotNull + private Integer fromNodeId; - @NotNull private Integer toNodeId; + private Integer fromUserNodeId; + + private Integer toUserNodeId; + public Integer getFromNodeId() { return fromNodeId; } @@ -126,4 +129,19 @@ public class Distance { this.state = state; } + public Integer getFromUserNodeId() { + return fromUserNodeId; + } + + public void setFromUserNodeId(Integer fromUserNodeId) { + this.fromUserNodeId = fromUserNodeId; + } + + public Integer getToUserNodeId() { + return toUserNodeId; + } + + public void setToUserNodeId(Integer toUserNodeId) { + this.toUserNodeId = toUserNodeId; + } } diff --git a/src/main/java/de/avatic/lcc/model/db/nodes/Node.java b/src/main/java/de/avatic/lcc/model/db/nodes/Node.java index c04e8d8..baf9b0a 100644 --- a/src/main/java/de/avatic/lcc/model/db/nodes/Node.java +++ b/src/main/java/de/avatic/lcc/model/db/nodes/Node.java @@ -56,6 +56,8 @@ public class Node { private Collection outboundCountries; + private boolean isUserNode = false; + public Integer getId() { return id; } @@ -176,6 +178,14 @@ public class Node { this.outboundCountries = outboundCountries; } + public boolean isUserNode() { + return isUserNode; + } + + public void setUserNode(boolean userNode) { + isUserNode = userNode; + } + public String getDebugText() { return externalMappingId == null ? "\uD83D\uDC64" + name : externalMappingId; } diff --git a/src/main/java/de/avatic/lcc/repositories/DistanceMatrixRepository.java b/src/main/java/de/avatic/lcc/repositories/DistanceMatrixRepository.java index 8cbafef..24f5307 100644 --- a/src/main/java/de/avatic/lcc/repositories/DistanceMatrixRepository.java +++ b/src/main/java/de/avatic/lcc/repositories/DistanceMatrixRepository.java @@ -3,7 +3,6 @@ package de.avatic.lcc.repositories; import de.avatic.lcc.model.db.nodes.Distance; import de.avatic.lcc.model.db.nodes.DistanceMatrixState; import de.avatic.lcc.model.db.nodes.Node; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jdbc.core.JdbcTemplate; @@ -24,12 +23,12 @@ public class DistanceMatrixRepository { this.jdbcTemplate = jdbcTemplate; } - public Optional getDistance(Node src, Node dest) { - String query = "SELECT * FROM distance_matrix WHERE from_node_id = ? AND to_node_id = ? AND state = ?"; + public Optional getDistance(Node src, boolean isUsrFrom, Node dest, boolean isUsrTo) { + String query = "SELECT * FROM distance_matrix WHERE ? = ? AND ? = ? AND state = ?"; - var distance = jdbcTemplate.query(query, new DistanceMapper(), src.getId(), dest.getId(), DistanceMatrixState.VALID.name()); + var distance = jdbcTemplate.query(query, new DistanceMapper(), isUsrFrom ? "from_user_node_id" : "from_node_id", src.getId(), isUsrTo ? "to_user_node_id" : "to_node_id", dest.getId(), DistanceMatrixState.VALID.name()); - if(distance.isEmpty()) + if (distance.isEmpty()) return Optional.empty(); return Optional.of(distance.getFirst()); @@ -37,26 +36,29 @@ public class DistanceMatrixRepository { public void saveDistance(Distance distance) { try { + // First, check if an entry already exists - String checkQuery = "SELECT id FROM distance_matrix WHERE from_node_id = ? AND to_node_id = ?"; + String checkQuery = "SELECT id FROM distance_matrix WHERE ? = ? AND ? = ?"; var existingIds = jdbcTemplate.query(checkQuery, (rs, rowNum) -> rs.getInt("id"), - distance.getFromNodeId(), - distance.getToNodeId()); + distance.getFromUserNodeId() != null ? "from_user_node_id" : "from_node_id", + distance.getFromUserNodeId() != null ? distance.getFromUserNodeId() : distance.getFromNodeId(), + distance.getToUserNodeId() != null ? "to_user_node_id" : "to_node_id", + distance.getToUserNodeId() != null ? distance.getToUserNodeId() : distance.getToNodeId()); if (!existingIds.isEmpty()) { // Update existing entry String updateQuery = """ - UPDATE distance_matrix - SET from_geo_lat = ?, - from_geo_lng = ?, - to_geo_lat = ?, - to_geo_lng = ?, - distance = ?, - state = ?, - updated_at = ? - WHERE from_node_id = ? AND to_node_id = ? - """; + UPDATE distance_matrix + SET from_geo_lat = ?, + from_geo_lng = ?, + to_geo_lat = ?, + to_geo_lng = ?, + distance = ?, + state = ?, + updated_at = ? + WHERE ? = ? AND ? = ? + """; jdbcTemplate.update(updateQuery, distance.getFromGeoLat(), @@ -66,22 +68,26 @@ public class DistanceMatrixRepository { distance.getDistance(), distance.getState().name(), distance.getUpdatedAt(), - distance.getFromNodeId(), - distance.getToNodeId()); + distance.getFromUserNodeId() != null ? "from_user_node_id" : "from_node_id", + distance.getFromUserNodeId() != null ? distance.getFromUserNodeId() : distance.getFromNodeId(), + distance.getToUserNodeId() != null ? "to_user_node_id" : "to_node_id", + distance.getToUserNodeId() != null ? distance.getToUserNodeId() : distance.getToNodeId()); logger.info("Updated existing distance entry for nodes {} -> {}", distance.getFromNodeId(), distance.getToNodeId()); } else { // Insert new entry String insertQuery = """ - INSERT INTO distance_matrix - (from_node_id, to_node_id, from_geo_lat, from_geo_lng, to_geo_lat, to_geo_lng, distance, state, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) - """; + INSERT INTO distance_matrix + (from_node_id, to_node_id, from_user_node_id, to_user_node_id, from_geo_lat, from_geo_lng, to_geo_lat, to_geo_lng, distance, state, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """; jdbcTemplate.update(insertQuery, distance.getFromNodeId(), distance.getToNodeId(), + distance.getFromUserNodeId(), + distance.getToUserNodeId(), distance.getFromGeoLat(), distance.getFromGeoLng(), distance.getToGeoLat(), @@ -107,6 +113,8 @@ public class DistanceMatrixRepository { entity.setFromNodeId(rs.getInt("from_node_id")); entity.setToNodeId(rs.getInt("to_node_id")); + entity.setFromNodeId(rs.getInt("from_user_node_id")); + entity.setToNodeId(rs.getInt("to_user_node_id")); entity.setDistance(rs.getBigDecimal("distance")); entity.setFromGeoLng(rs.getBigDecimal("from_geo_lng")); entity.setFromGeoLat(rs.getBigDecimal("from_geo_lat")); diff --git a/src/main/java/de/avatic/lcc/repositories/users/UserNodeRepository.java b/src/main/java/de/avatic/lcc/repositories/users/UserNodeRepository.java index 9708fc4..311e83c 100644 --- a/src/main/java/de/avatic/lcc/repositories/users/UserNodeRepository.java +++ b/src/main/java/de/avatic/lcc/repositories/users/UserNodeRepository.java @@ -169,6 +169,8 @@ public class UserNodeRepository { node.setIntermediate(false); node.setSource(true); + node.setUserNode(true); + return node; } diff --git a/src/main/java/de/avatic/lcc/service/api/DistanceApiService.java b/src/main/java/de/avatic/lcc/service/api/DistanceApiService.java index 2f6176d..a659cce 100644 --- a/src/main/java/de/avatic/lcc/service/api/DistanceApiService.java +++ b/src/main/java/de/avatic/lcc/service/api/DistanceApiService.java @@ -3,6 +3,7 @@ package de.avatic.lcc.service.api; import de.avatic.lcc.model.azuremaps.route.RouteDirectionsResponse; import de.avatic.lcc.model.db.nodes.Distance; import de.avatic.lcc.model.db.nodes.DistanceMatrixState; +import de.avatic.lcc.model.db.nodes.Location; import de.avatic.lcc.model.db.nodes.Node; import de.avatic.lcc.repositories.DistanceMatrixRepository; import org.slf4j.Logger; @@ -13,7 +14,6 @@ import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; import java.math.BigDecimal; -import java.net.URI; import java.time.LocalDateTime; import java.util.Optional; @@ -35,7 +35,27 @@ public class DistanceApiService { this.restTemplate = restTemplate; } - public Optional getDistance(Node from, Node to) { + public Optional getDistance(Location from, Location to) { + if (from == null || to == null) { + logger.warn("Source or destination location is null"); + return Optional.empty(); + } + + if (from.getLatitude() == null || from.getLongitude() == null || + to.getLatitude() == null || to.getLongitude() == null) { + return Optional.empty(); + } + + RouteDirectionsResponse response = fetchDistanceFromAzureMaps(BigDecimal.valueOf(from.getLatitude()), BigDecimal.valueOf(from.getLongitude()), BigDecimal.valueOf(to.getLatitude()), BigDecimal.valueOf(to.getLongitude())); + + if (response != null && response.getRoutes() != null && !response.getRoutes().isEmpty()) { + return Optional.of(response.getRoutes().getFirst().getSummary().getLengthInMeters()); + } + + return Optional.empty(); + } + + public Optional getDistance(Node from, boolean isUsrFrom, Node to, boolean isUsrTo) { if (from == null || to == null) { logger.warn("Source or destination node is null"); @@ -48,7 +68,7 @@ public class DistanceApiService { return Optional.empty(); } - Optional cachedDistance = distanceMatrixRepository.getDistance(from, to); + Optional cachedDistance = distanceMatrixRepository.getDistance(from, isUsrFrom, to, isUsrTo); if (cachedDistance.isPresent()) { logger.debug("Found cached distance from node {} to node {}", from.getId(), to.getId()); @@ -56,7 +76,7 @@ public class DistanceApiService { } logger.debug("Fetching distance from Azure Maps for nodes {} to {}", from.getId(), to.getId()); - Optional fetchedDistance = fetchDistanceFromAzureMaps(from, to); + Optional fetchedDistance = fetchDistanceFromAzureMaps(from, isUsrFrom, to, isUsrTo); if (fetchedDistance.isPresent()) { distanceMatrixRepository.saveDistance(fetchedDistance.get()); @@ -66,25 +86,45 @@ public class DistanceApiService { return Optional.empty(); } - private Optional fetchDistanceFromAzureMaps(Node from, Node to) { - try { - String url = UriComponentsBuilder.fromUriString(AZURE_MAPS_ROUTE_API) - .queryParam("api-version", "1.0") - .queryParam("subscription-key", subscriptionKey) - .queryParam("query", String.format("%s,%s:%s,%s", - from.getGeoLat(), from.getGeoLng(), - to.getGeoLat(), to.getGeoLng())) - .encode() - .toUriString(); + private RouteDirectionsResponse fetchDistanceFromAzureMaps(BigDecimal fromLat, BigDecimal fromLng, BigDecimal toLat, BigDecimal toLng) { + String url = UriComponentsBuilder.fromUriString(AZURE_MAPS_ROUTE_API) + .queryParam("api-version", "1.0") + .queryParam("subscription-key", subscriptionKey) + .queryParam("query", String.format("%s,%s:%s,%s", + fromLat, fromLng, + toLat, toLng)) + .encode() + .toUriString(); - RouteDirectionsResponse response = restTemplate.getForObject(url, RouteDirectionsResponse.class); + return restTemplate.getForObject(url, RouteDirectionsResponse.class); + } + + private Optional fetchDistanceFromAzureMaps(Node from, boolean isUsrFrom, Node to, boolean isUsrTo) { + try { + + RouteDirectionsResponse response = fetchDistanceFromAzureMaps(from.getGeoLat(), from.getGeoLng(), to.getGeoLat(), to.getGeoLng()); if (response != null && response.getRoutes() != null && !response.getRoutes().isEmpty()) { Integer distanceInMeters = response.getRoutes().getFirst().getSummary().getLengthInMeters(); Distance distance = new Distance(); - distance.setFromNodeId(from.getId()); - distance.setToNodeId(to.getId()); + + if (isUsrFrom) { + distance.setFromUserNodeId(from.getId()); + distance.setFromNodeId(null); + } else { + distance.setFromUserNodeId(null); + distance.setFromNodeId(from.getId()); + } + + if (isUsrTo) { + distance.setToUserNodeId(to.getId()); + distance.setToNodeId(null); + } else { + distance.setToUserNodeId(null); + distance.setToNodeId(from.getId()); + } + distance.setFromGeoLat(from.getGeoLat()); distance.setFromGeoLng(from.getGeoLng()); distance.setToGeoLat(to.getGeoLat()); diff --git a/src/main/java/de/avatic/lcc/service/calculation/DistanceService.java b/src/main/java/de/avatic/lcc/service/calculation/DistanceService.java index 869c669..774013c 100644 --- a/src/main/java/de/avatic/lcc/service/calculation/DistanceService.java +++ b/src/main/java/de/avatic/lcc/service/calculation/DistanceService.java @@ -13,26 +13,34 @@ import org.springframework.stereotype.Service; public class DistanceService { private static final double EARTH_RADIUS = 6371.0; - private final DistanceMatrixRepository distanceMatrixRepository; private final CountryRepository countryRepository; private final DistanceApiService distanceApiService; - public DistanceService(DistanceMatrixRepository distanceMatrixRepository, CountryRepository countryRepository, DistanceApiService distanceApiService) { - this.distanceMatrixRepository = distanceMatrixRepository; + public DistanceService(CountryRepository countryRepository, DistanceApiService distanceApiService) { this.countryRepository = countryRepository; this.distanceApiService = distanceApiService; } + public double getDistance(Integer srcCountryId, Location src, Integer destCountryId, Location dest, boolean fast) { + if (fast) return getDistanceFast(srcCountryId, src, destCountryId, dest); + + var distance = distanceApiService.getDistance(src, dest); + if (distance.isEmpty()) return getDistanceFast(srcCountryId, src, destCountryId, dest); + + return distance.map(value -> value/1000).orElse(0); + } + public double getDistance(Node src, Node dest, boolean fast) { if (fast) return getDistanceFast(src, dest); - var distance = distanceApiService.getDistance(src, dest); + var distance = distanceApiService.getDistance(src, src.isUserNode(), dest, dest.isUserNode()); if (distance.isEmpty()) return getDistanceFast(src, dest); return distance.map(value -> value.getDistance().intValue()/1000).orElse(0); } - public double getDistanceFast(Integer srcCountryId, Location src, Integer destCountryId, Location dest ) { + + public double getDistanceFast(Integer srcCountryId, Location src, Integer destCountryId, Location dest ) { double srcLatitudeRadians = Math.toRadians(src.getLatitude()); double srcLongitudeRad = Math.toRadians(src.getLongitude()); double destLatitudeRadians = Math.toRadians(dest.getLatitude()); diff --git a/src/main/java/de/avatic/lcc/service/calculation/RoutingService.java b/src/main/java/de/avatic/lcc/service/calculation/RoutingService.java index b4bab92..e4767ae 100644 --- a/src/main/java/de/avatic/lcc/service/calculation/RoutingService.java +++ b/src/main/java/de/avatic/lcc/service/calculation/RoutingService.java @@ -43,7 +43,7 @@ public class RoutingService { public List findRoutes(Node destination, Node source, boolean sourceIsUserNode) { List routeInformation = new ArrayList<>(); - TemporaryContainer container = new TemporaryContainer(source, destination); + TemporaryContainer container = new TemporaryContainer(source, destination, sourceIsUserNode); /* * Get the source and destination node from database. @@ -740,6 +740,7 @@ public class RoutingService { * Source and destination node */ private final Node source; + private final boolean sourceIsUserNode; private final Node destination; private final Map>> chains = new HashMap<>(); /* @@ -752,8 +753,9 @@ public class RoutingService { private Map> postRuns; private MatrixRate sourceMatrixRate; - public TemporaryContainer(Node source, Node destination) { + public TemporaryContainer(Node source, Node destination, boolean sourceIsUserNode) { this.source = source; + this.sourceIsUserNode = sourceIsUserNode; this.destination = destination; this.mainRuns = null; this.postRuns = null; @@ -821,6 +823,10 @@ public class RoutingService { public void setChain(Integer id, List> chain) { this.chains.put(id, chain); } + + public boolean isSourceIsUserNode() { + return sourceIsUserNode; + } } @Renderer(text = "getFullDebugText()") diff --git a/src/main/java/de/avatic/lcc/service/calculation/execution/steps/RouteSectionCostCalculationService.java b/src/main/java/de/avatic/lcc/service/calculation/execution/steps/RouteSectionCostCalculationService.java index cf7a482..257751c 100644 --- a/src/main/java/de/avatic/lcc/service/calculation/execution/steps/RouteSectionCostCalculationService.java +++ b/src/main/java/de/avatic/lcc/service/calculation/execution/steps/RouteSectionCostCalculationService.java @@ -21,6 +21,7 @@ import de.avatic.lcc.repositories.premise.RouteNodeRepository; import de.avatic.lcc.repositories.properties.PropertyRepository; import de.avatic.lcc.repositories.rates.ContainerRateRepository; import de.avatic.lcc.repositories.rates.MatrixRateRepository; +import de.avatic.lcc.repositories.users.UserNodeRepository; import de.avatic.lcc.service.calculation.DistanceService; import org.springframework.stereotype.Service; @@ -38,8 +39,9 @@ public class RouteSectionCostCalculationService { private final PropertyRepository propertyRepository; private final ChangeRiskFactorCalculationService changeRiskFactorCalculationService; private final NodeRepository nodeRepository; + private final UserNodeRepository userNodeRepository; - public RouteSectionCostCalculationService(ContainerRateRepository containerRateRepository, MatrixRateRepository matrixRateRepository, RouteNodeRepository routeNodeRepository, DistanceService distanceService, PropertyRepository propertyRepository, ChangeRiskFactorCalculationService changeRiskFactorCalculationService, NodeRepository nodeRepository) { + public RouteSectionCostCalculationService(ContainerRateRepository containerRateRepository, MatrixRateRepository matrixRateRepository, RouteNodeRepository routeNodeRepository, DistanceService distanceService, PropertyRepository propertyRepository, ChangeRiskFactorCalculationService changeRiskFactorCalculationService, NodeRepository nodeRepository, UserNodeRepository userNodeRepository) { this.containerRateRepository = containerRateRepository; this.matrixRateRepository = matrixRateRepository; this.routeNodeRepository = routeNodeRepository; @@ -47,6 +49,7 @@ public class RouteSectionCostCalculationService { this.propertyRepository = propertyRepository; this.changeRiskFactorCalculationService = changeRiskFactorCalculationService; this.nodeRepository = nodeRepository; + this.userNodeRepository = userNodeRepository; } public CalculationJobRouteSection doD2dCalculation(Integer setId, Integer periodId, Premise premise, Destination destination, ContainerCalculationResult containerCalculation) { @@ -274,9 +277,25 @@ public class RouteSectionCostCalculationService { } private double getDistance(RouteNode fromNode, RouteNode toNode) { - return distanceService.getDistanceFast( - fromNode.getCountryId(), new Location(fromNode.getGeoLng().doubleValue(), fromNode.getGeoLat().doubleValue()), - toNode.getCountryId(), new Location(toNode.getGeoLng().doubleValue(), toNode.getGeoLat().doubleValue())); + + if(fromNode.getOutdated() || toNode.getOutdated()) { + return distanceService.getDistance( + fromNode.getCountryId(), new Location(fromNode.getGeoLng().doubleValue(), fromNode.getGeoLat().doubleValue()), + toNode.getCountryId(), new Location(toNode.getGeoLng().doubleValue(), toNode.getGeoLat().doubleValue()), false); + } + + var optSrcNode = fromNode.getNodeId() == null ? userNodeRepository.getById(fromNode.getUserNodeId()) : nodeRepository.getById(fromNode.getNodeId()); + var optDestNode = toNode.getNodeId() == null ? userNodeRepository.getById(toNode.getUserNodeId()) : nodeRepository.getById(toNode.getNodeId()); + + if(optSrcNode.isEmpty() ) { + throw new NoSuchElementException("Source node not found for route section " + fromNode.getName()); + } + if(optDestNode.isEmpty() ) { + throw new NoSuchElementException("Destination node not found for route section" + toNode.getName()); + } + + return distanceService.getDistance(optSrcNode.get(), optDestNode.get(), false); + }