From 344047688d1f33fd4d3215f37f4b648fd98b678c Mon Sep 17 00:00:00 2001 From: Sascha Fendrich Date: Tue, 21 Jan 2025 11:28:31 +0100 Subject: [PATCH 1/5] refactor: move unrelated methods to a better place RoutingProfileManager contained several methods that are unrelated to it. These are moved to RoutingRequest, which is more appropriate, but maybe not the final destination. --- .../ors/api/services/RoutingService.java | 2 +- .../ors/routing/RoutingProfileManager.java | 353 ------------------ .../heigit/ors/routing/RoutingRequest.java | 353 +++++++++++++++++- 3 files changed, 349 insertions(+), 359 deletions(-) diff --git a/ors-api/src/main/java/org/heigit/ors/api/services/RoutingService.java b/ors-api/src/main/java/org/heigit/ors/api/services/RoutingService.java index 9dcd60c5ea..e48be2ab97 100644 --- a/ors-api/src/main/java/org/heigit/ors/api/services/RoutingService.java +++ b/ors-api/src/main/java/org/heigit/ors/api/services/RoutingService.java @@ -132,7 +132,7 @@ public RouteResult[] generateRouteFromRequest(RouteRequest routeApiRequest) thro } } - return RoutingProfileManager.getInstance().computeRoute(routingRequest); + return routingRequest.computeRoute(RoutingProfileManager.getInstance()); } catch (StatusCodeException e) { throw e; } catch (Exception e) { diff --git a/ors-engine/src/main/java/org/heigit/ors/routing/RoutingProfileManager.java b/ors-engine/src/main/java/org/heigit/ors/routing/RoutingProfileManager.java index 7bedcbabdd..d94556c1d9 100644 --- a/ors-engine/src/main/java/org/heigit/ors/routing/RoutingProfileManager.java +++ b/ors-engine/src/main/java/org/heigit/ors/routing/RoutingProfileManager.java @@ -13,21 +13,12 @@ */ package org.heigit.ors.routing; -import com.graphhopper.GHResponse; -import com.graphhopper.util.AngleCalc; -import com.graphhopper.util.PointList; -import com.graphhopper.util.exceptions.MaximumNodesExceededException; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.log4j.Logger; import org.heigit.ors.config.EngineProperties; import org.heigit.ors.config.profile.ProfileProperties; -import org.heigit.ors.exceptions.*; -import org.heigit.ors.routing.pathprocessors.ExtraInfoProcessor; -import org.heigit.ors.util.FormatUtility; import org.heigit.ors.util.RuntimeUtility; -import org.heigit.ors.util.StringUtility; import org.heigit.ors.util.TimeUtility; -import org.locationtech.jts.geom.Coordinate; import java.io.FileNotFoundException; import java.util.ArrayList; @@ -35,7 +26,6 @@ import java.util.List; import java.util.Map; import java.util.concurrent.*; -import java.util.stream.Collectors; public class RoutingProfileManager { private static final Logger LOGGER = Logger.getLogger(RoutingProfileManager.class.getName()); @@ -162,347 +152,4 @@ private void fail(String message) { RoutingProfileManagerStatus.setFailed(true); RoutingProfileManagerStatus.setShutdown(true); } - - public static RouteResult[] computeRoundTripRoute(RoutingRequest req) throws Exception { - List routes = new ArrayList<>(); - - RoutingProfile rp = req.profile(); - RouteSearchParameters searchParams = req.getSearchParameters(); - ProfileProperties profileProperties = rp.getProfileConfiguration(); - - if (profileProperties.getService().getMaximumDistanceRoundTripRoutes() != null && profileProperties.getService().getMaximumDistanceRoundTripRoutes() < searchParams.getRoundTripLength()) { - throw new ServerLimitExceededException( - RoutingErrorCodes.REQUEST_EXCEEDS_SERVER_LIMIT, - "The requested route length must not be greater than %s meters.".formatted(profileProperties.getService().getMaximumDistanceRoundTripRoutes()) - ); - } - - Coordinate[] coords = req.getCoordinates(); - Coordinate c0 = coords[0]; - - ExtraInfoProcessor extraInfoProcessor = null; - - WayPointBearing bearing = null; - if (searchParams.getBearings() != null) { - bearing = searchParams.getBearings()[0]; - } - - GHResponse gr = req.computeRoundTripRoute(c0.y, c0.x, bearing, searchParams, req.getGeometrySimplify(), rp); - - if (gr.hasErrors()) { - if (!gr.getErrors().isEmpty()) { - if (gr.getErrors().get(0) instanceof com.graphhopper.util.exceptions.ConnectionNotFoundException) { - throw new RouteNotFoundException( - RoutingErrorCodes.ROUTE_NOT_FOUND, - "Unable to find a route for point (%s).".formatted(FormatUtility.formatCoordinate(c0)) - ); - } else if (gr.getErrors().get(0) instanceof com.graphhopper.util.exceptions.PointNotFoundException) { - StringBuilder message = new StringBuilder(); - for (Throwable error : gr.getErrors()) { - if (!message.isEmpty()) - message.append("; "); - message.append(error.getMessage()); - } - throw new PointNotFoundException(message.toString()); - } else { - throw new InternalServerException(RoutingErrorCodes.UNKNOWN, gr.getErrors().get(0).getMessage()); - } - } else { - // If there are no errors stored but there is indication that there are errors, something strange - // has happened, so return that a route could not be found - throw new RouteNotFoundException( - RoutingErrorCodes.ROUTE_NOT_FOUND, - "Unable to find a route for point (%s).".formatted( - FormatUtility.formatCoordinate(c0) - )); - } - } - - try { - for (Object obj : gr.getReturnObjects()) { - if (obj instanceof ExtraInfoProcessor processor) { - if (extraInfoProcessor == null) { - extraInfoProcessor = processor; - if (!StringUtility.isNullOrEmpty(processor.getSkippedExtraInfo())) { - gr.getHints().putObject(KEY_SKIPPED_EXTRA_INFO, processor.getSkippedExtraInfo()); - } - } else { - extraInfoProcessor.appendData(processor); - } - } - } - } catch (Exception e) { - LOGGER.error(e); - } - - routes.add(gr); - - List extraInfos = extraInfoProcessor != null ? extraInfoProcessor.getExtras() : null; - return new RouteResultBuilder().createRouteResults(routes, req, new List[]{extraInfos}); - } - - public static RouteResult[] computeRoute(RoutingRequest req) throws Exception { - if (req.getSearchParameters().getRoundTripLength() > 0) { - return computeRoundTripRoute(req); - } else { - return computeLinearRoute(req); - } - } - - public static RouteResult[] computeLinearRoute(RoutingRequest req) throws Exception { - List skipSegments = req.getSkipSegments(); - List routes = new ArrayList<>(); - - RoutingProfile rp = req.profile(); - RouteSearchParameters searchParams = req.getSearchParameters(); - - Coordinate[] coords = req.getCoordinates(); - Coordinate c0 = coords[0]; - Coordinate c1; - int nSegments = coords.length - 1; - GHResponse prevResp = null; - WayPointBearing[] bearings = (req.getContinueStraight() || searchParams.getBearings() != null) ? new WayPointBearing[2] : null; - String profileName = req.getSearchParameters().getProfileName(); - double[] radiuses = null; - - if (req.getSearchParameters().getAlternativeRoutesCount() > 1 && coords.length > 2) { - throw new InternalServerException(RoutingErrorCodes.INVALID_PARAMETER_VALUE, "Alternative routes algorithm does not support more than two way points."); - } - - int numberOfExpectedExtraInfoProcessors = req.getSearchParameters().getAlternativeRoutesCount() < 0 ? 1 : req.getSearchParameters().getAlternativeRoutesCount(); - ExtraInfoProcessor[] extraInfoProcessors = new ExtraInfoProcessor[numberOfExpectedExtraInfoProcessors]; - - for (int i = 1; i <= nSegments; ++i) { - c1 = coords[i]; - - if (bearings != null) { - bearings[0] = null; - if (prevResp != null && req.getContinueStraight()) { - bearings[0] = new WayPointBearing(getHeadingDirection(prevResp)); - } - - if (searchParams.getBearings() != null) { - bearings[0] = searchParams.getBearings()[i - 1]; - bearings[1] = (i == nSegments && searchParams.getBearings().length != nSegments + 1) ? new WayPointBearing(Double.NaN) : searchParams.getBearings()[i]; - } - } - - if (searchParams.getMaximumRadiuses() != null) { - radiuses = new double[2]; - radiuses[0] = searchParams.getMaximumRadiuses()[i - 1]; - radiuses[1] = searchParams.getMaximumRadiuses()[i]; - } else { - try { - int maximumSnappingRadius = req.profile().getProfileConfiguration().getService().getMaximumSnappingRadius(); - radiuses = new double[2]; - radiuses[0] = maximumSnappingRadius; - radiuses[1] = maximumSnappingRadius; - } catch (Exception ex) { - // do nothing - } - } - - GHResponse gr = req.computeRoute(c0.y, c0.x, c1.y, c1.x, bearings, radiuses, skipSegments.contains(i), searchParams, req.getGeometrySimplify(), rp); - - if (gr.hasErrors()) { - if (!gr.getErrors().isEmpty()) { - if (gr.getErrors().get(0) instanceof com.graphhopper.util.exceptions.ConnectionNotFoundException ex) { - Map details = ex.getDetails(); - if (!details.isEmpty()) { - int code = RoutingErrorCodes.ROUTE_NOT_FOUND; - if (details.containsKey("entry_not_reached") && details.containsKey("exit_not_reached")) { - code = RoutingErrorCodes.PT_NOT_REACHED; - } else if (details.containsKey("entry_not_reached")) { - code = RoutingErrorCodes.PT_ENTRY_NOT_REACHED; - } else if (details.containsKey("exit_not_reached")) { - code = RoutingErrorCodes.PT_EXIT_NOT_REACHED; - } else if (details.containsKey("combined_not_reached")) { - code = RoutingErrorCodes.PT_ROUTE_NOT_FOUND; - } - throw new RouteNotFoundException( - code, - "Unable to find a route between points %d (%s) and %d (%s). %s".formatted( - i, - FormatUtility.formatCoordinate(c0), - i + 1, - FormatUtility.formatCoordinate(c1), - details.values().stream().map(Object::toString).collect(Collectors.joining(" ")) - ) - ); - } - throw new RouteNotFoundException( - RoutingErrorCodes.ROUTE_NOT_FOUND, - "Unable to find a route between points %d (%s) and %d (%s).".formatted( - i, - FormatUtility.formatCoordinate(c0), - i + 1, - FormatUtility.formatCoordinate(c1) - ) - ); - } else if (gr.getErrors().get(0) instanceof com.graphhopper.util.exceptions.MaximumNodesExceededException ex) { - Map details = ex.getDetails(); - throw new RouteNotFoundException( - RoutingErrorCodes.PT_MAX_VISITED_NODES_EXCEEDED, - "Unable to find a route between points %d (%s) and %d (%s). Maximum number of nodes exceeded: %s".formatted( - i, - FormatUtility.formatCoordinate(c0), - i + 1, - FormatUtility.formatCoordinate(c1), - details.get(MaximumNodesExceededException.NODES_KEY).toString() - ) - ); - } else if (gr.getErrors().get(0) instanceof com.graphhopper.util.exceptions.PointNotFoundException) { - StringBuilder message = new StringBuilder(); - for (Throwable error : gr.getErrors()) { - if (!message.isEmpty()) - message.append("; "); - if (error instanceof com.graphhopper.util.exceptions.PointNotFoundException pointNotFoundException) { - int pointReference = (i - 1) + pointNotFoundException.getPointIndex(); - - Coordinate pointCoordinate = (pointNotFoundException.getPointIndex() == 0) ? c0 : c1; - assert radiuses != null; - double pointRadius = radiuses[pointNotFoundException.getPointIndex()]; - - // -1 is used to indicate the use of internal limits instead of specifying it in the request. - // we should therefore let them know that they are already using the limit. - if (pointRadius == -1) { - pointRadius = req.profile().getProfileConfiguration().getService().getMaximumSnappingRadius(); - message.append("Could not find routable point within the maximum possible radius of %.1f meters of specified coordinate %d: %s.".formatted( - pointRadius, - pointReference, - FormatUtility.formatCoordinate(pointCoordinate))); - } else { - message.append("Could not find routable point within a radius of %.1f meters of specified coordinate %d: %s.".formatted( - pointRadius, - pointReference, - FormatUtility.formatCoordinate(pointCoordinate))); - } - - } else { - message.append(error.getMessage()); - } - } - throw new PointNotFoundException(message.toString()); - } else { - throw new InternalServerException(RoutingErrorCodes.UNKNOWN, gr.getErrors().get(0).getMessage()); - } - } else { - // If there are no errors stored but there is indication that there are errors, something strange - // has happened, so return that a route could not be found - throw new RouteNotFoundException( - RoutingErrorCodes.ROUTE_NOT_FOUND, - "Unable to find a route between points %d (%s) and %d (%s).".formatted( - i, - FormatUtility.formatCoordinate(c0), - i + 1, - FormatUtility.formatCoordinate(c1)) - ); - } - } - - if (numberOfExpectedExtraInfoProcessors > 1) { - int extraInfoProcessorIndex = 0; - for (Object o : gr.getReturnObjects()) { - if (o instanceof ExtraInfoProcessor processor) { - extraInfoProcessors[extraInfoProcessorIndex] = processor; - extraInfoProcessorIndex++; - if (!StringUtility.isNullOrEmpty(processor.getSkippedExtraInfo())) { - gr.getHints().putObject(KEY_SKIPPED_EXTRA_INFO, processor.getSkippedExtraInfo()); - } - } - } - } else { - for (Object o : gr.getReturnObjects()) { - if (o instanceof ExtraInfoProcessor processor) { - if (extraInfoProcessors[0] == null) { - extraInfoProcessors[0] = processor; - if (!StringUtility.isNullOrEmpty(processor.getSkippedExtraInfo())) { - gr.getHints().putObject(KEY_SKIPPED_EXTRA_INFO, processor.getSkippedExtraInfo()); - } - } else { - extraInfoProcessors[0].appendData(processor); - } - } - } - } - - prevResp = gr; - routes.add(gr); - c0 = c1; - } - routes = enrichDirectRoutesTime(routes); - - List[] extraInfos = new List[numberOfExpectedExtraInfoProcessors]; - int i = 0; - for (ExtraInfoProcessor e : extraInfoProcessors) { - extraInfos[i] = e != null ? e.getExtras() : null; - i++; - } - return new RouteResultBuilder().createRouteResults(routes, req, extraInfos); - } - - /** - * This will enrich all direct routes with an approximated travel time that is being calculated from the real graphhopper - * results. The routes object should contain all routes, so the function can maintain and return the proper order! - * - * @param routes Should hold all the routes that have been calculated, not only the direct routes. - * @return will return routes object with enriched direct routes if any we're found in the same order as the input object. - */ - private static List enrichDirectRoutesTime(List routes) { - List graphhopperRoutes = new ArrayList<>(); - List directRoutes = new ArrayList<>(); - long graphHopperTravelTime = 0; - double graphHopperTravelDistance = 0; - double averageTravelTimePerMeter; - - for (GHResponse ghResponse : routes) { - if (!ghResponse.getHints().has("skipped_segment")) { - graphHopperTravelDistance += ghResponse.getBest().getDistance(); - graphHopperTravelTime += ghResponse.getBest().getTime(); - graphhopperRoutes.add(ghResponse); - } else { - directRoutes.add(ghResponse); - } - } - - if (graphhopperRoutes.isEmpty() || directRoutes.isEmpty()) { - return routes; - } - - if (graphHopperTravelDistance == 0) { - return routes; - } - - averageTravelTimePerMeter = graphHopperTravelTime / graphHopperTravelDistance; - for (GHResponse ghResponse : routes) { - if (ghResponse.getHints().has("skipped_segment")) { - double directRouteDistance = ghResponse.getBest().getDistance(); - ghResponse.getBest().setTime(Math.round(directRouteDistance * averageTravelTimePerMeter)); - double directRouteInstructionDistance = ghResponse.getBest().getInstructions().get(0).getDistance(); - ghResponse.getBest().getInstructions().get(0).setTime(Math.round(directRouteInstructionDistance * averageTravelTimePerMeter)); - } - } - - return routes; - } - - private static double getHeadingDirection(GHResponse resp) { - PointList points = resp.getBest().getPoints(); - int nPoints = points.size(); - if (nPoints > 1) { - double lon1 = points.getLon(nPoints - 2); - double lat1 = points.getLat(nPoints - 2); - double lon2 = points.getLon(nPoints - 1); - double lat2 = points.getLat(nPoints - 1); - // For some reason, GH may return a response where the last two points are identical - if (lon1 == lon2 && lat1 == lat2 && nPoints > 2) { - lon1 = points.getLon(nPoints - 3); - lat1 = points.getLat(nPoints - 3); - } - return AngleCalc.ANGLE_CALC.calcAzimuth(lat1, lon1, lat2, lon2); - } else - return 0; - } - - } diff --git a/ors-engine/src/main/java/org/heigit/ors/routing/RoutingRequest.java b/ors-engine/src/main/java/org/heigit/ors/routing/RoutingRequest.java index 4a1acb3eeb..051783a093 100644 --- a/ors-engine/src/main/java/org/heigit/ors/routing/RoutingRequest.java +++ b/ors-engine/src/main/java/org/heigit/ors/routing/RoutingRequest.java @@ -17,22 +17,24 @@ import com.graphhopper.GHResponse; import com.graphhopper.gtfs.*; import com.graphhopper.util.*; +import com.graphhopper.util.exceptions.MaximumNodesExceededException; import com.graphhopper.util.shapes.GHPoint; import org.apache.log4j.Logger; import org.heigit.ors.common.DistanceUnit; import org.heigit.ors.common.ServiceRequest; -import org.heigit.ors.exceptions.IncompatibleParameterException; -import org.heigit.ors.exceptions.InternalServerException; +import org.heigit.ors.config.profile.ProfileProperties; +import org.heigit.ors.exceptions.*; import org.heigit.ors.routing.graphhopper.extensions.ORSGraphHopper; -import org.heigit.ors.util.DebugUtility; -import org.heigit.ors.util.ProfileTools; -import org.heigit.ors.util.TemporaryUtilShelter; +import org.heigit.ors.routing.pathprocessors.ExtraInfoProcessor; +import org.heigit.ors.util.*; import org.locationtech.jts.geom.Coordinate; import java.time.*; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; public class RoutingRequest extends ServiceRequest { private static final Logger LOGGER = Logger.getLogger(RoutingRequest.class); @@ -71,6 +73,69 @@ public RoutingRequest() { searchParameters = new RouteSearchParameters(); } + public static double getHeadingDirection(GHResponse resp) { + PointList points = resp.getBest().getPoints(); + int nPoints = points.size(); + if (nPoints > 1) { + double lon1 = points.getLon(nPoints - 2); + double lat1 = points.getLat(nPoints - 2); + double lon2 = points.getLon(nPoints - 1); + double lat2 = points.getLat(nPoints - 1); + // For some reason, GH may return a response where the last two points are identical + if (lon1 == lon2 && lat1 == lat2 && nPoints > 2) { + lon1 = points.getLon(nPoints - 3); + lat1 = points.getLat(nPoints - 3); + } + return AngleCalc.ANGLE_CALC.calcAzimuth(lat1, lon1, lat2, lon2); + } else + return 0; + } + + /** + * This will enrich all direct routes with an approximated travel time that is being calculated from the real graphhopper + * results. The routes object should contain all routes, so the function can maintain and return the proper order! + * + * @param routes Should hold all the routes that have been calculated, not only the direct routes. + * @return will return routes object with enriched direct routes if any we're found in the same order as the input object. + */ + public static List enrichDirectRoutesTime(List routes) { + List graphhopperRoutes = new ArrayList<>(); + List directRoutes = new ArrayList<>(); + long graphHopperTravelTime = 0; + double graphHopperTravelDistance = 0; + double averageTravelTimePerMeter; + + for (GHResponse ghResponse : routes) { + if (!ghResponse.getHints().has("skipped_segment")) { + graphHopperTravelDistance += ghResponse.getBest().getDistance(); + graphHopperTravelTime += ghResponse.getBest().getTime(); + graphhopperRoutes.add(ghResponse); + } else { + directRoutes.add(ghResponse); + } + } + + if (graphhopperRoutes.isEmpty() || directRoutes.isEmpty()) { + return routes; + } + + if (graphHopperTravelDistance == 0) { + return routes; + } + + averageTravelTimePerMeter = graphHopperTravelTime / graphHopperTravelDistance; + for (GHResponse ghResponse : routes) { + if (ghResponse.getHints().has("skipped_segment")) { + double directRouteDistance = ghResponse.getBest().getDistance(); + ghResponse.getBest().setTime(Math.round(directRouteDistance * averageTravelTimePerMeter)); + double directRouteInstructionDistance = ghResponse.getBest().getInstructions().get(0).getDistance(); + ghResponse.getBest().getInstructions().get(0).setTime(Math.round(directRouteInstructionDistance * averageTravelTimePerMeter)); + } + } + + return routes; + } + public RoutingProfile profile() { return routingProfile; } @@ -539,4 +604,282 @@ public GHResponse computeRoundTripRoute(double lat0, double lon0, WayPointBearin return resp; } + + public RouteResult[] computeRoundTripRoute() throws Exception { + List routes = new ArrayList<>(); + + RoutingProfile rp = profile(); + RouteSearchParameters searchParams = getSearchParameters(); + ProfileProperties profileProperties = rp.getProfileConfiguration(); + + if (profileProperties.getService().getMaximumDistanceRoundTripRoutes() != null && profileProperties.getService().getMaximumDistanceRoundTripRoutes() < searchParams.getRoundTripLength()) { + throw new ServerLimitExceededException( + RoutingErrorCodes.REQUEST_EXCEEDS_SERVER_LIMIT, + "The requested route length must not be greater than %s meters.".formatted(profileProperties.getService().getMaximumDistanceRoundTripRoutes()) + ); + } + + Coordinate[] coords = getCoordinates(); + Coordinate c0 = coords[0]; + + ExtraInfoProcessor extraInfoProcessor = null; + + WayPointBearing bearing = null; + if (searchParams.getBearings() != null) { + bearing = searchParams.getBearings()[0]; + } + + GHResponse gr = computeRoundTripRoute(c0.y, c0.x, bearing, searchParams, getGeometrySimplify(), rp); + + if (gr.hasErrors()) { + if (!gr.getErrors().isEmpty()) { + if (gr.getErrors().get(0) instanceof com.graphhopper.util.exceptions.ConnectionNotFoundException) { + throw new RouteNotFoundException( + RoutingErrorCodes.ROUTE_NOT_FOUND, + "Unable to find a route for point (%s).".formatted(FormatUtility.formatCoordinate(c0)) + ); + } else if (gr.getErrors().get(0) instanceof com.graphhopper.util.exceptions.PointNotFoundException) { + StringBuilder message = new StringBuilder(); + for (Throwable error : gr.getErrors()) { + if (!message.isEmpty()) + message.append("; "); + message.append(error.getMessage()); + } + throw new PointNotFoundException(message.toString()); + } else { + throw new InternalServerException(RoutingErrorCodes.UNKNOWN, gr.getErrors().get(0).getMessage()); + } + } else { + // If there are no errors stored but there is indication that there are errors, something strange + // has happened, so return that a route could not be found + throw new RouteNotFoundException( + RoutingErrorCodes.ROUTE_NOT_FOUND, + "Unable to find a route for point (%s).".formatted( + FormatUtility.formatCoordinate(c0) + )); + } + } + + try { + for (Object obj : gr.getReturnObjects()) { + if (obj instanceof ExtraInfoProcessor processor) { + if (extraInfoProcessor == null) { + extraInfoProcessor = processor; + if (!StringUtility.isNullOrEmpty(processor.getSkippedExtraInfo())) { + gr.getHints().putObject(RoutingProfileManager.KEY_SKIPPED_EXTRA_INFO, processor.getSkippedExtraInfo()); + } + } else { + extraInfoProcessor.appendData(processor); + } + } + } + } catch (Exception e) { + LOGGER.error(e); + } + + routes.add(gr); + + List extraInfos = extraInfoProcessor != null ? extraInfoProcessor.getExtras() : null; + return new RouteResultBuilder().createRouteResults(routes, this, new List[]{extraInfos}); + } + + public RouteResult[] computeRoute(RoutingProfileManager routingProfileManager) throws Exception { + if (getSearchParameters().getRoundTripLength() > 0) { + return computeRoundTripRoute(); + } else { + return computeLinearRoute(); + } + } + + public RouteResult[] computeLinearRoute() throws Exception { + List skipSegments = getSkipSegments(); + List routes = new ArrayList<>(); + + RoutingProfile rp = profile(); + RouteSearchParameters searchParams = getSearchParameters(); + + Coordinate[] coords = getCoordinates(); + Coordinate c0 = coords[0]; + Coordinate c1; + int nSegments = coords.length - 1; + GHResponse prevResp = null; + WayPointBearing[] bearings = (getContinueStraight() || searchParams.getBearings() != null) ? new WayPointBearing[2] : null; + String profileName = getSearchParameters().getProfileName(); + double[] radiuses = null; + + if (getSearchParameters().getAlternativeRoutesCount() > 1 && coords.length > 2) { + throw new InternalServerException(RoutingErrorCodes.INVALID_PARAMETER_VALUE, "Alternative routes algorithm does not support more than two way points."); + } + + int numberOfExpectedExtraInfoProcessors = getSearchParameters().getAlternativeRoutesCount() < 0 ? 1 : getSearchParameters().getAlternativeRoutesCount(); + ExtraInfoProcessor[] extraInfoProcessors = new ExtraInfoProcessor[numberOfExpectedExtraInfoProcessors]; + + for (int i = 1; i <= nSegments; ++i) { + c1 = coords[i]; + + if (bearings != null) { + bearings[0] = null; + if (prevResp != null && getContinueStraight()) { + bearings[0] = new WayPointBearing(getHeadingDirection(prevResp)); + } + + if (searchParams.getBearings() != null) { + bearings[0] = searchParams.getBearings()[i - 1]; + bearings[1] = (i == nSegments && searchParams.getBearings().length != nSegments + 1) ? new WayPointBearing(Double.NaN) : searchParams.getBearings()[i]; + } + } + + if (searchParams.getMaximumRadiuses() != null) { + radiuses = new double[2]; + radiuses[0] = searchParams.getMaximumRadiuses()[i - 1]; + radiuses[1] = searchParams.getMaximumRadiuses()[i]; + } else { + try { + int maximumSnappingRadius = profile().getProfileConfiguration().getService().getMaximumSnappingRadius(); + radiuses = new double[2]; + radiuses[0] = maximumSnappingRadius; + radiuses[1] = maximumSnappingRadius; + } catch (Exception ex) { + // do nothing + } + } + + GHResponse gr = computeRoute(c0.y, c0.x, c1.y, c1.x, bearings, radiuses, skipSegments.contains(i), searchParams, getGeometrySimplify(), rp); + + if (gr.hasErrors()) { + if (!gr.getErrors().isEmpty()) { + if (gr.getErrors().get(0) instanceof com.graphhopper.util.exceptions.ConnectionNotFoundException ex) { + Map details = ex.getDetails(); + if (!details.isEmpty()) { + int code = RoutingErrorCodes.ROUTE_NOT_FOUND; + if (details.containsKey("entry_not_reached") && details.containsKey("exit_not_reached")) { + code = RoutingErrorCodes.PT_NOT_REACHED; + } else if (details.containsKey("entry_not_reached")) { + code = RoutingErrorCodes.PT_ENTRY_NOT_REACHED; + } else if (details.containsKey("exit_not_reached")) { + code = RoutingErrorCodes.PT_EXIT_NOT_REACHED; + } else if (details.containsKey("combined_not_reached")) { + code = RoutingErrorCodes.PT_ROUTE_NOT_FOUND; + } + throw new RouteNotFoundException( + code, + "Unable to find a route between points %d (%s) and %d (%s). %s".formatted( + i, + FormatUtility.formatCoordinate(c0), + i + 1, + FormatUtility.formatCoordinate(c1), + details.values().stream().map(Object::toString).collect(Collectors.joining(" ")) + ) + ); + } + throw new RouteNotFoundException( + RoutingErrorCodes.ROUTE_NOT_FOUND, + "Unable to find a route between points %d (%s) and %d (%s).".formatted( + i, + FormatUtility.formatCoordinate(c0), + i + 1, + FormatUtility.formatCoordinate(c1) + ) + ); + } else if (gr.getErrors().get(0) instanceof com.graphhopper.util.exceptions.MaximumNodesExceededException ex) { + Map details = ex.getDetails(); + throw new RouteNotFoundException( + RoutingErrorCodes.PT_MAX_VISITED_NODES_EXCEEDED, + "Unable to find a route between points %d (%s) and %d (%s). Maximum number of nodes exceeded: %s".formatted( + i, + FormatUtility.formatCoordinate(c0), + i + 1, + FormatUtility.formatCoordinate(c1), + details.get(MaximumNodesExceededException.NODES_KEY).toString() + ) + ); + } else if (gr.getErrors().get(0) instanceof com.graphhopper.util.exceptions.PointNotFoundException) { + StringBuilder message = new StringBuilder(); + for (Throwable error : gr.getErrors()) { + if (!message.isEmpty()) + message.append("; "); + if (error instanceof com.graphhopper.util.exceptions.PointNotFoundException pointNotFoundException) { + int pointReference = (i - 1) + pointNotFoundException.getPointIndex(); + + Coordinate pointCoordinate = (pointNotFoundException.getPointIndex() == 0) ? c0 : c1; + assert radiuses != null; + double pointRadius = radiuses[pointNotFoundException.getPointIndex()]; + + // -1 is used to indicate the use of internal limits instead of specifying it in the request. + // we should therefore let them know that they are already using the limit. + if (pointRadius == -1) { + pointRadius = profile().getProfileConfiguration().getService().getMaximumSnappingRadius(); + message.append("Could not find routable point within the maximum possible radius of %.1f meters of specified coordinate %d: %s.".formatted( + pointRadius, + pointReference, + FormatUtility.formatCoordinate(pointCoordinate))); + } else { + message.append("Could not find routable point within a radius of %.1f meters of specified coordinate %d: %s.".formatted( + pointRadius, + pointReference, + FormatUtility.formatCoordinate(pointCoordinate))); + } + + } else { + message.append(error.getMessage()); + } + } + throw new PointNotFoundException(message.toString()); + } else { + throw new InternalServerException(RoutingErrorCodes.UNKNOWN, gr.getErrors().get(0).getMessage()); + } + } else { + // If there are no errors stored but there is indication that there are errors, something strange + // has happened, so return that a route could not be found + throw new RouteNotFoundException( + RoutingErrorCodes.ROUTE_NOT_FOUND, + "Unable to find a route between points %d (%s) and %d (%s).".formatted( + i, + FormatUtility.formatCoordinate(c0), + i + 1, + FormatUtility.formatCoordinate(c1)) + ); + } + } + + if (numberOfExpectedExtraInfoProcessors > 1) { + int extraInfoProcessorIndex = 0; + for (Object o : gr.getReturnObjects()) { + if (o instanceof ExtraInfoProcessor processor) { + extraInfoProcessors[extraInfoProcessorIndex] = processor; + extraInfoProcessorIndex++; + if (!StringUtility.isNullOrEmpty(processor.getSkippedExtraInfo())) { + gr.getHints().putObject(RoutingProfileManager.KEY_SKIPPED_EXTRA_INFO, processor.getSkippedExtraInfo()); + } + } + } + } else { + for (Object o : gr.getReturnObjects()) { + if (o instanceof ExtraInfoProcessor processor) { + if (extraInfoProcessors[0] == null) { + extraInfoProcessors[0] = processor; + if (!StringUtility.isNullOrEmpty(processor.getSkippedExtraInfo())) { + gr.getHints().putObject(RoutingProfileManager.KEY_SKIPPED_EXTRA_INFO, processor.getSkippedExtraInfo()); + } + } else { + extraInfoProcessors[0].appendData(processor); + } + } + } + } + + prevResp = gr; + routes.add(gr); + c0 = c1; + } + routes = enrichDirectRoutesTime(routes); + + List[] extraInfos = new List[numberOfExpectedExtraInfoProcessors]; + int i = 0; + for (ExtraInfoProcessor e : extraInfoProcessors) { + extraInfos[i] = e != null ? e.getExtras() : null; + i++; + } + return new RouteResultBuilder().createRouteResults(routes, this, extraInfos); + } } From 8d0bf5f5c71101d9ce74c2a9ba7733334a6e75be Mon Sep 17 00:00:00 2001 From: Sascha Fendrich Date: Tue, 21 Jan 2025 11:52:18 +0100 Subject: [PATCH 2/5] refactor: move unrelated methods This commit moves methods unrelated to RoutingProfile to more appropriate classes. --- .../heigit/ors/routing/RoutingProfile.java | 220 +---------------- .../extensions/ORSGraphHopperConfig.java | 225 +++++++++++++++++- 2 files changed, 224 insertions(+), 221 deletions(-) diff --git a/ors-engine/src/main/java/org/heigit/ors/routing/RoutingProfile.java b/ors-engine/src/main/java/org/heigit/ors/routing/RoutingProfile.java index e1f30314fb..3000c041a5 100644 --- a/ors-engine/src/main/java/org/heigit/ors/routing/RoutingProfile.java +++ b/ors-engine/src/main/java/org/heigit/ors/routing/RoutingProfile.java @@ -15,33 +15,22 @@ import com.graphhopper.GHRequest; import com.graphhopper.config.CHProfile; -import com.graphhopper.config.LMProfile; -import com.graphhopper.config.Profile; import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.routing.util.FlagEncoder; -import com.graphhopper.routing.weighting.custom.CustomProfile; import com.graphhopper.storage.ConditionalEdges; import com.graphhopper.storage.GraphHopperStorage; import com.graphhopper.storage.StorableProperties; -import com.graphhopper.util.CustomModel; -import com.graphhopper.util.Helper; -import com.graphhopper.util.PMap; import com.graphhopper.util.Parameters; import org.apache.log4j.Logger; -import org.heigit.ors.config.ElevationProperties; import org.heigit.ors.config.EngineProperties; -import org.heigit.ors.config.profile.BuildProperties; import org.heigit.ors.config.profile.ExecutionProperties; -import org.heigit.ors.config.profile.PreparationProperties; import org.heigit.ors.config.profile.ProfileProperties; import org.heigit.ors.routing.graphhopper.extensions.*; import org.heigit.ors.routing.graphhopper.extensions.manage.ORSGraphManager; import org.heigit.ors.routing.graphhopper.extensions.storages.builders.BordersGraphStorageBuilder; import org.heigit.ors.routing.graphhopper.extensions.storages.builders.GraphStorageBuilder; -import org.heigit.ors.routing.graphhopper.extensions.util.ORSParameters; import org.heigit.ors.routing.pathprocessors.ORSPathProcessorFactory; import org.heigit.ors.util.ProfileTools; -import org.heigit.ors.util.StringUtility; import org.heigit.ors.util.TimeUtility; import java.io.File; @@ -96,7 +85,7 @@ public ORSGraphHopper initGraphHopper(RoutingProfileLoadContext loadCntx) throws ORSGraphManager orsGraphManager = ORSGraphManager.initializeGraphManagement(graphVersion, engineProperties, profileProperties); profileProperties = orsGraphManager.loadProfilePropertiesFromActiveGraph(orsGraphManager, profileProperties); - ORSGraphHopperConfig args = createGHSettings(profileProperties, engineProperties, orsGraphManager.getActiveGraphDirAbsPath()); + ORSGraphHopperConfig args = ORSGraphHopperConfig.createGHSettings(profileProperties, engineProperties, orsGraphManager.getActiveGraphDirAbsPath()); int profileId; synchronized (lockObj) { @@ -158,213 +147,6 @@ public ORSGraphHopper initGraphHopper(RoutingProfileLoadContext loadCntx) throws return gh; } - private static ORSGraphHopperConfig createGHSettings(ProfileProperties profile, EngineProperties engineConfig, String graphLocation) { - ORSGraphHopperConfig ghConfig = new ORSGraphHopperConfig(); - ghConfig.putObject("graph.dataaccess", engineConfig.getGraphsDataAccess().toString()); - ghConfig.putObject("datareader.file", Optional.ofNullable(profile).map(ProfileProperties::getBuild).map(BuildProperties::getSourceFile).map(Path::toString).orElse(null)); - ghConfig.putObject("graph.bytes_for_flags", profile.getBuild().getEncoderFlagsSize()); - ghConfig.putObject("graph.location", graphLocation); - - if (Boolean.FALSE.equals(profile.getBuild().getInstructions())) { - ghConfig.putObject("instructions", false); - } - - ElevationProperties elevationProps = engineConfig.getElevation(); - if (elevationProps.getProvider() != null && elevationProps.getCachePath() != null) { - ghConfig.putObject("graph.elevation.provider", StringUtility.trimQuotes(elevationProps.getProvider())); - ghConfig.putObject("graph.elevation.cache_dir", StringUtility.trimQuotes(elevationProps.getCachePath().toString())); - ghConfig.putObject("graph.elevation.dataaccess", StringUtility.trimQuotes(elevationProps.getDataAccess().toString())); - ghConfig.putObject("graph.elevation.clear", elevationProps.getCacheClear()); - if (Boolean.TRUE.equals(profile.getBuild().getInterpolateBridgesAndTunnels())) - ghConfig.putObject("graph.encoded_values", "road_environment"); - if (Boolean.TRUE.equals(profile.getBuild().getElevationSmoothing())) - ghConfig.putObject("graph.elevation.smoothing", true); - } - - boolean prepareCH = false; - boolean prepareLM = false; - boolean prepareCore = false; - boolean prepareFI = false; - - Integer[] profilesTypes = profile.getProfilesTypes(); - Map profiles = new LinkedHashMap<>(); - - // TODO Future improvement : Multiple profiles were used to share the graph for several - // bike profiles. We don't use this feature now but it might be - // desireable in the future. However, this behavior is standard - // in original GH through an already existing mechanism. - if (profilesTypes.length != 1) - throw new IllegalStateException("Expected single profile in config"); - - String vehicle = RoutingProfileType.getEncoderName(profilesTypes[0]); - - boolean hasTurnCosts = Boolean.TRUE.equals(profile.getBuild().getEncoderOptions().getTurnCosts()); - - // TODO Future improvement : make this list of weightings configurable for each vehicle as in GH - String[] weightings = {ProfileTools.VAL_FASTEST, ProfileTools.VAL_SHORTEST, ProfileTools.VAL_RECOMMENDED}; - for (String weighting : weightings) { - if (hasTurnCosts) { - String profileName = ProfileTools.makeProfileName(vehicle, weighting, true); - profiles.put(profileName, new Profile(profileName).setVehicle(vehicle).setWeighting(weighting).setTurnCosts(true)); - } - String profileName = ProfileTools.makeProfileName(vehicle, weighting, false); - profiles.put(profileName, new Profile(profileName).setVehicle(vehicle).setWeighting(weighting).setTurnCosts(false)); - } - - if (profile.getBuild().getEncoderOptions().getEnableCustomModels()) { - if (hasTurnCosts) { - profiles.put(vehicle + "_custom_with_turn_costs", new CustomProfile(vehicle + "_custom_with_turn_costs").setCustomModel(new CustomModel().setDistanceInfluence(0)).setVehicle(vehicle).setTurnCosts(true)); - } - profiles.put(vehicle + "_custom", new CustomProfile(vehicle + "_custom").setCustomModel(new CustomModel().setDistanceInfluence(0)).setVehicle(vehicle).setTurnCosts(false)); - } - - ghConfig.putObject(ProfileTools.KEY_PREPARE_CORE_WEIGHTINGS, "no"); - if (profile.getBuild().getPreparation() != null) { - PreparationProperties preparations = profile.getBuild().getPreparation(); - - - if (preparations.getMinNetworkSize() != null) - ghConfig.putObject("prepare.min_network_size", preparations.getMinNetworkSize()); - - if (!preparations.getMethods().isEmpty()) { - if (!preparations.getMethods().getCh().isEmpty()) { - PreparationProperties.MethodsProperties.CHProperties chOpts = preparations.getMethods().getCh(); - prepareCH = Boolean.TRUE.equals(chOpts.getEnabled()); - if (prepareCH) { - if (chOpts.getThreadsSave() != null) - ghConfig.putObject("prepare.ch.threads", chOpts.getThreadsSave()); - if (chOpts.getWeightings() != null) { - List chProfiles = new ArrayList<>(); - String chWeightingsString = StringUtility.trimQuotes(chOpts.getWeightings()); - for (String weighting : chWeightingsString.split(",")) - chProfiles.add(new CHProfile(ProfileTools.makeProfileName(vehicle, weighting, false))); - ghConfig.setCHProfiles(chProfiles); - } - } - } - - if (!preparations.getMethods().getLm().isEmpty()) { - PreparationProperties.MethodsProperties.LMProperties lmOpts = preparations.getMethods().getLm(); - prepareLM = Boolean.TRUE.equals(lmOpts.getEnabled()); - if (prepareLM) { - if (lmOpts.getThreadsSave() != null) - ghConfig.putObject("prepare.lm.threads", lmOpts.getThreadsSave()); - if (lmOpts.getWeightings() != null) { - List lmProfiles = new ArrayList<>(); - String lmWeightingsString = StringUtility.trimQuotes(lmOpts.getWeightings()); - for (String weighting : lmWeightingsString.split(",")) - lmProfiles.add(new LMProfile(ProfileTools.makeProfileName(vehicle, weighting, hasTurnCosts))); - ghConfig.setLMProfiles(lmProfiles); - } - if (lmOpts.getLandmarks() != null) - ghConfig.putObject("prepare.lm.landmarks", lmOpts.getLandmarks()); - } - } - - if (!preparations.getMethods().getCore().isEmpty()) { - PreparationProperties.MethodsProperties.CoreProperties coreOpts = preparations.getMethods().getCore(); - prepareCore = Boolean.TRUE.equals(coreOpts.getEnabled()); - if (prepareCore) { - if (coreOpts.getThreadsSave() != null) { - Integer threadsCore = coreOpts.getThreadsSave(); - ghConfig.putObject("prepare.core.threads", threadsCore); - ghConfig.putObject("prepare.corelm.threads", threadsCore); - } - if (coreOpts.getWeightings() != null) { - List coreProfiles = new ArrayList<>(); - List coreLMProfiles = new ArrayList<>(); - String coreWeightingsString = StringUtility.trimQuotes(coreOpts.getWeightings()); - for (String weighting : coreWeightingsString.split(",")) { - String configStr = ""; - if (weighting.contains("|")) { - configStr = weighting; - weighting = weighting.split("\\|")[0]; - } - PMap configMap = new PMap(configStr); - boolean considerTurnRestrictions = configMap.getBool("edge_based", hasTurnCosts); - - String profileName = ProfileTools.makeProfileName(vehicle, weighting, considerTurnRestrictions); - profiles.put(profileName, new Profile(profileName).setVehicle(vehicle).setWeighting(weighting).setTurnCosts(considerTurnRestrictions)); - coreProfiles.add(new CHProfile(profileName)); - coreLMProfiles.add(new LMProfile(profileName)); - } - ghConfig.setCoreProfiles(coreProfiles); - ghConfig.setCoreLMProfiles(coreLMProfiles); - } - if (coreOpts.getLmsets() != null) - ghConfig.putObject("prepare.corelm.lmsets", StringUtility.trimQuotes(coreOpts.getLmsets())); - if (coreOpts.getLandmarks() != null) - ghConfig.putObject("prepare.corelm.landmarks", coreOpts.getLandmarks()); - } else { - ghConfig.putObject(ProfileTools.KEY_PREPARE_CORE_WEIGHTINGS, "no"); - } - } - - if (!preparations.getMethods().getFastisochrones().isEmpty()) { - PreparationProperties.MethodsProperties.FastIsochroneProperties fastisochroneOpts = preparations.getMethods().getFastisochrones(); - prepareFI = Boolean.TRUE.equals(fastisochroneOpts.getEnabled()); - if (prepareFI) { - ghConfig.putObject(ORSParameters.FastIsochrone.PROFILE, profile.getEncoderName().toString()); - //Copied from core - if (fastisochroneOpts.getThreadsSave() != null) - ghConfig.putObject("prepare.fastisochrone.threads", fastisochroneOpts.getThreadsSave()); - if (fastisochroneOpts.getMaxcellnodes() != null) - ghConfig.putObject("prepare.fastisochrone.maxcellnodes", fastisochroneOpts.getMaxcellnodes()); - if (fastisochroneOpts.getWeightings() != null) { - List fastisochronesProfiles = new ArrayList<>(); - String fastisochronesWeightingsString = StringUtility.trimQuotes(fastisochroneOpts.getWeightings()); - for (String weighting : fastisochronesWeightingsString.split(",")) { - String configStr = ""; - weighting = weighting.trim(); - if (weighting.contains("|")) { - configStr = weighting; - weighting = weighting.split("\\|")[0]; - } - PMap configMap = new PMap(configStr); - boolean considerTurnRestrictions = configMap.getBool("edge_based", hasTurnCosts); - - String profileName = ProfileTools.makeProfileName(vehicle, weighting, considerTurnRestrictions); - Profile ghProfile = new Profile(profileName).setVehicle(vehicle).setWeighting(weighting).setTurnCosts(considerTurnRestrictions); - profiles.put(profileName, ghProfile); - fastisochronesProfiles.add(ghProfile); - } - ghConfig.setFastisochroneProfiles(fastisochronesProfiles); - } - } else { - ghConfig.putObject(ProfileTools.KEY_PREPARE_FASTISOCHRONE_WEIGHTINGS, "no"); - } - } - } - } - - if (profile.getService().getExecution() != null) { - ExecutionProperties execution = profile.getService().getExecution(); - if (!execution.getMethods().getCore().isEmpty() && execution.getMethods().getCore().getActiveLandmarks() != null) - ghConfig.putObject("routing.corelm.active_landmarks", execution.getMethods().getCore().getActiveLandmarks()); - - if (!execution.getMethods().getLm().isEmpty() && execution.getMethods().getLm().getActiveLandmarks() != null) - ghConfig.putObject("routing.lm.active_landmarks", execution.getMethods().getLm().getActiveLandmarks()); - } - - if (Boolean.TRUE.equals(profile.getBuild().getOptimize()) && !prepareCH) - ghConfig.putObject("graph.do_sort", true); - - // Check if getGTFSFile exists - if (profile.getBuild().getGtfsFile() != null && !profile.getBuild().getGtfsFile().toString().isEmpty()) - ghConfig.putObject("gtfs.file", profile.getBuild().getGtfsFile().toAbsolutePath().toString()); - - String flagEncoder = vehicle; - if (!Helper.isEmpty(profile.getBuild().getEncoderOptionsString())) - flagEncoder += "|" + profile.getBuild().getEncoderOptionsString(); - - ghConfig.putObject("graph.flag_encoders", flagEncoder.toLowerCase()); - ghConfig.putObject("index.high_resolution", profile.getBuild().getLocationIndexResolution()); - ghConfig.putObject("index.max_region_search", profile.getBuild().getLocationIndexSearchIterations()); - ghConfig.setProfiles(new ArrayList<>(profiles.values())); - - return ghConfig; - } - public boolean hasCHProfile(String profileName) { boolean hasCHProfile = false; for (CHProfile chProfile : getGraphhopper().getCHPreparationHandler().getCHProfiles()) { diff --git a/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/ORSGraphHopperConfig.java b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/ORSGraphHopperConfig.java index 1e431efa98..b02cc9645c 100644 --- a/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/ORSGraphHopperConfig.java +++ b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/ORSGraphHopperConfig.java @@ -4,15 +4,236 @@ import com.graphhopper.config.CHProfile; import com.graphhopper.config.LMProfile; import com.graphhopper.config.Profile; +import com.graphhopper.routing.weighting.custom.CustomProfile; +import com.graphhopper.util.CustomModel; +import com.graphhopper.util.Helper; +import com.graphhopper.util.PMap; +import org.heigit.ors.config.ElevationProperties; +import org.heigit.ors.config.EngineProperties; +import org.heigit.ors.config.profile.BuildProperties; +import org.heigit.ors.config.profile.ExecutionProperties; +import org.heigit.ors.config.profile.PreparationProperties; +import org.heigit.ors.config.profile.ProfileProperties; +import org.heigit.ors.routing.RoutingProfileType; +import org.heigit.ors.routing.graphhopper.extensions.util.ORSParameters; +import org.heigit.ors.util.ProfileTools; +import org.heigit.ors.util.StringUtility; -import java.util.ArrayList; -import java.util.List; +import java.nio.file.Path; +import java.util.*; public class ORSGraphHopperConfig extends GraphHopperConfig { private List coreProfiles = new ArrayList<>(); private List coreLMProfiles = new ArrayList<>(); private List fastisochroneProfiles = new ArrayList<>(); + public static ORSGraphHopperConfig createGHSettings(ProfileProperties profile, EngineProperties engineConfig, String graphLocation) { + ORSGraphHopperConfig ghConfig = new ORSGraphHopperConfig(); + ghConfig.putObject("graph.dataaccess", engineConfig.getGraphsDataAccess().toString()); + ghConfig.putObject("datareader.file", Optional.ofNullable(profile).map(ProfileProperties::getBuild).map(BuildProperties::getSourceFile).map(Path::toString).orElse(null)); + ghConfig.putObject("graph.bytes_for_flags", profile.getBuild().getEncoderFlagsSize()); + ghConfig.putObject("graph.location", graphLocation); + + if (Boolean.FALSE.equals(profile.getBuild().getInstructions())) { + ghConfig.putObject("instructions", false); + } + + ElevationProperties elevationProps = engineConfig.getElevation(); + if (elevationProps.getProvider() != null && elevationProps.getCachePath() != null) { + ghConfig.putObject("graph.elevation.provider", StringUtility.trimQuotes(elevationProps.getProvider())); + ghConfig.putObject("graph.elevation.cache_dir", StringUtility.trimQuotes(elevationProps.getCachePath().toString())); + ghConfig.putObject("graph.elevation.dataaccess", StringUtility.trimQuotes(elevationProps.getDataAccess().toString())); + ghConfig.putObject("graph.elevation.clear", elevationProps.getCacheClear()); + if (Boolean.TRUE.equals(profile.getBuild().getInterpolateBridgesAndTunnels())) + ghConfig.putObject("graph.encoded_values", "road_environment"); + if (Boolean.TRUE.equals(profile.getBuild().getElevationSmoothing())) + ghConfig.putObject("graph.elevation.smoothing", true); + } + + boolean prepareCH = false; + boolean prepareLM = false; + boolean prepareCore = false; + boolean prepareFI = false; + + Integer[] profilesTypes = profile.getProfilesTypes(); + Map profiles = new LinkedHashMap<>(); + + // TODO Future improvement : Multiple profiles were used to share the graph for several + // bike profiles. We don't use this feature now but it might be + // desireable in the future. However, this behavior is standard + // in original GH through an already existing mechanism. + if (profilesTypes.length != 1) + throw new IllegalStateException("Expected single profile in config"); + + String vehicle = RoutingProfileType.getEncoderName(profilesTypes[0]); + + boolean hasTurnCosts = Boolean.TRUE.equals(profile.getBuild().getEncoderOptions().getTurnCosts()); + + // TODO Future improvement : make this list of weightings configurable for each vehicle as in GH + String[] weightings = {ProfileTools.VAL_FASTEST, ProfileTools.VAL_SHORTEST, ProfileTools.VAL_RECOMMENDED}; + for (String weighting : weightings) { + if (hasTurnCosts) { + String profileName = ProfileTools.makeProfileName(vehicle, weighting, true); + profiles.put(profileName, new Profile(profileName).setVehicle(vehicle).setWeighting(weighting).setTurnCosts(true)); + } + String profileName = ProfileTools.makeProfileName(vehicle, weighting, false); + profiles.put(profileName, new Profile(profileName).setVehicle(vehicle).setWeighting(weighting).setTurnCosts(false)); + } + + if (profile.getBuild().getEncoderOptions().getEnableCustomModels()) { + if (hasTurnCosts) { + profiles.put(vehicle + "_custom_with_turn_costs", new CustomProfile(vehicle + "_custom_with_turn_costs").setCustomModel(new CustomModel().setDistanceInfluence(0)).setVehicle(vehicle).setTurnCosts(true)); + } + profiles.put(vehicle + "_custom", new CustomProfile(vehicle + "_custom").setCustomModel(new CustomModel().setDistanceInfluence(0)).setVehicle(vehicle).setTurnCosts(false)); + } + + ghConfig.putObject(ProfileTools.KEY_PREPARE_CORE_WEIGHTINGS, "no"); + if (profile.getBuild().getPreparation() != null) { + PreparationProperties preparations = profile.getBuild().getPreparation(); + + + if (preparations.getMinNetworkSize() != null) + ghConfig.putObject("prepare.min_network_size", preparations.getMinNetworkSize()); + + if (!preparations.getMethods().isEmpty()) { + if (!preparations.getMethods().getCh().isEmpty()) { + PreparationProperties.MethodsProperties.CHProperties chOpts = preparations.getMethods().getCh(); + prepareCH = Boolean.TRUE.equals(chOpts.getEnabled()); + if (prepareCH) { + if (chOpts.getThreadsSave() != null) + ghConfig.putObject("prepare.ch.threads", chOpts.getThreadsSave()); + if (chOpts.getWeightings() != null) { + List chProfiles = new ArrayList<>(); + String chWeightingsString = StringUtility.trimQuotes(chOpts.getWeightings()); + for (String weighting : chWeightingsString.split(",")) + chProfiles.add(new CHProfile(ProfileTools.makeProfileName(vehicle, weighting, false))); + ghConfig.setCHProfiles(chProfiles); + } + } + } + + if (!preparations.getMethods().getLm().isEmpty()) { + PreparationProperties.MethodsProperties.LMProperties lmOpts = preparations.getMethods().getLm(); + prepareLM = Boolean.TRUE.equals(lmOpts.getEnabled()); + if (prepareLM) { + if (lmOpts.getThreadsSave() != null) + ghConfig.putObject("prepare.lm.threads", lmOpts.getThreadsSave()); + if (lmOpts.getWeightings() != null) { + List lmProfiles = new ArrayList<>(); + String lmWeightingsString = StringUtility.trimQuotes(lmOpts.getWeightings()); + for (String weighting : lmWeightingsString.split(",")) + lmProfiles.add(new LMProfile(ProfileTools.makeProfileName(vehicle, weighting, hasTurnCosts))); + ghConfig.setLMProfiles(lmProfiles); + } + if (lmOpts.getLandmarks() != null) + ghConfig.putObject("prepare.lm.landmarks", lmOpts.getLandmarks()); + } + } + + if (!preparations.getMethods().getCore().isEmpty()) { + PreparationProperties.MethodsProperties.CoreProperties coreOpts = preparations.getMethods().getCore(); + prepareCore = Boolean.TRUE.equals(coreOpts.getEnabled()); + if (prepareCore) { + if (coreOpts.getThreadsSave() != null) { + Integer threadsCore = coreOpts.getThreadsSave(); + ghConfig.putObject("prepare.core.threads", threadsCore); + ghConfig.putObject("prepare.corelm.threads", threadsCore); + } + if (coreOpts.getWeightings() != null) { + List coreProfiles = new ArrayList<>(); + List coreLMProfiles = new ArrayList<>(); + String coreWeightingsString = StringUtility.trimQuotes(coreOpts.getWeightings()); + for (String weighting : coreWeightingsString.split(",")) { + String configStr = ""; + if (weighting.contains("|")) { + configStr = weighting; + weighting = weighting.split("\\|")[0]; + } + PMap configMap = new PMap(configStr); + boolean considerTurnRestrictions = configMap.getBool("edge_based", hasTurnCosts); + + String profileName = ProfileTools.makeProfileName(vehicle, weighting, considerTurnRestrictions); + profiles.put(profileName, new Profile(profileName).setVehicle(vehicle).setWeighting(weighting).setTurnCosts(considerTurnRestrictions)); + coreProfiles.add(new CHProfile(profileName)); + coreLMProfiles.add(new LMProfile(profileName)); + } + ghConfig.setCoreProfiles(coreProfiles); + ghConfig.setCoreLMProfiles(coreLMProfiles); + } + if (coreOpts.getLmsets() != null) + ghConfig.putObject("prepare.corelm.lmsets", StringUtility.trimQuotes(coreOpts.getLmsets())); + if (coreOpts.getLandmarks() != null) + ghConfig.putObject("prepare.corelm.landmarks", coreOpts.getLandmarks()); + } else { + ghConfig.putObject(ProfileTools.KEY_PREPARE_CORE_WEIGHTINGS, "no"); + } + } + + if (!preparations.getMethods().getFastisochrones().isEmpty()) { + PreparationProperties.MethodsProperties.FastIsochroneProperties fastisochroneOpts = preparations.getMethods().getFastisochrones(); + prepareFI = Boolean.TRUE.equals(fastisochroneOpts.getEnabled()); + if (prepareFI) { + ghConfig.putObject(ORSParameters.FastIsochrone.PROFILE, profile.getEncoderName().toString()); + //Copied from core + if (fastisochroneOpts.getThreadsSave() != null) + ghConfig.putObject("prepare.fastisochrone.threads", fastisochroneOpts.getThreadsSave()); + if (fastisochroneOpts.getMaxcellnodes() != null) + ghConfig.putObject("prepare.fastisochrone.maxcellnodes", fastisochroneOpts.getMaxcellnodes()); + if (fastisochroneOpts.getWeightings() != null) { + List fastisochronesProfiles = new ArrayList<>(); + String fastisochronesWeightingsString = StringUtility.trimQuotes(fastisochroneOpts.getWeightings()); + for (String weighting : fastisochronesWeightingsString.split(",")) { + String configStr = ""; + weighting = weighting.trim(); + if (weighting.contains("|")) { + configStr = weighting; + weighting = weighting.split("\\|")[0]; + } + PMap configMap = new PMap(configStr); + boolean considerTurnRestrictions = configMap.getBool("edge_based", hasTurnCosts); + + String profileName = ProfileTools.makeProfileName(vehicle, weighting, considerTurnRestrictions); + Profile ghProfile = new Profile(profileName).setVehicle(vehicle).setWeighting(weighting).setTurnCosts(considerTurnRestrictions); + profiles.put(profileName, ghProfile); + fastisochronesProfiles.add(ghProfile); + } + ghConfig.setFastisochroneProfiles(fastisochronesProfiles); + } + } else { + ghConfig.putObject(ProfileTools.KEY_PREPARE_FASTISOCHRONE_WEIGHTINGS, "no"); + } + } + } + } + + if (profile.getService().getExecution() != null) { + ExecutionProperties execution = profile.getService().getExecution(); + if (!execution.getMethods().getCore().isEmpty() && execution.getMethods().getCore().getActiveLandmarks() != null) + ghConfig.putObject("routing.corelm.active_landmarks", execution.getMethods().getCore().getActiveLandmarks()); + + if (!execution.getMethods().getLm().isEmpty() && execution.getMethods().getLm().getActiveLandmarks() != null) + ghConfig.putObject("routing.lm.active_landmarks", execution.getMethods().getLm().getActiveLandmarks()); + } + + if (Boolean.TRUE.equals(profile.getBuild().getOptimize()) && !prepareCH) + ghConfig.putObject("graph.do_sort", true); + + // Check if getGTFSFile exists + if (profile.getBuild().getGtfsFile() != null && !profile.getBuild().getGtfsFile().toString().isEmpty()) + ghConfig.putObject("gtfs.file", profile.getBuild().getGtfsFile().toAbsolutePath().toString()); + + String flagEncoder = vehicle; + if (!Helper.isEmpty(profile.getBuild().getEncoderOptionsString())) + flagEncoder += "|" + profile.getBuild().getEncoderOptionsString(); + + ghConfig.putObject("graph.flag_encoders", flagEncoder.toLowerCase()); + ghConfig.putObject("index.high_resolution", profile.getBuild().getLocationIndexResolution()); + ghConfig.putObject("index.max_region_search", profile.getBuild().getLocationIndexSearchIterations()); + ghConfig.setProfiles(new ArrayList<>(profiles.values())); + + return ghConfig; + } + public List getCoreProfiles() { return coreProfiles; } From fbabe15b196eaac5987a2b71562c154236526bcb Mon Sep 17 00:00:00 2001 From: Sascha Fendrich Date: Tue, 21 Jan 2025 12:03:27 +0100 Subject: [PATCH 3/5] docs: update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18d921e68a..afd77ee84b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ RELEASING: ### Changed - improved dutch translations ([#1913](https://github.com/GIScience/openrouteservice/issues/1913)) - update dependencies: spring, geotools, jackson, springdoc, swagger, jupiter, testcontainers, surefire ([#1939](https://github.com/GIScience/openrouteservice/pull/1939)) +- refactor routing profile management ### Deprecated From 4b131cecb951149e1d7d82e517f548afbf581127 Mon Sep 17 00:00:00 2001 From: Sascha Fendrich Date: Tue, 21 Jan 2025 12:22:48 +0100 Subject: [PATCH 4/5] refactor: minor cleanup This commit removes unused parameters and makes local methods private. --- .../org/heigit/ors/api/services/RoutingService.java | 2 +- .../java/org/heigit/ors/routing/RoutingRequest.java | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ors-api/src/main/java/org/heigit/ors/api/services/RoutingService.java b/ors-api/src/main/java/org/heigit/ors/api/services/RoutingService.java index e48be2ab97..2e25cabce8 100644 --- a/ors-api/src/main/java/org/heigit/ors/api/services/RoutingService.java +++ b/ors-api/src/main/java/org/heigit/ors/api/services/RoutingService.java @@ -132,7 +132,7 @@ public RouteResult[] generateRouteFromRequest(RouteRequest routeApiRequest) thro } } - return routingRequest.computeRoute(RoutingProfileManager.getInstance()); + return routingRequest.computeRoute(); } catch (StatusCodeException e) { throw e; } catch (Exception e) { diff --git a/ors-engine/src/main/java/org/heigit/ors/routing/RoutingRequest.java b/ors-engine/src/main/java/org/heigit/ors/routing/RoutingRequest.java index 051783a093..78c36ac2ed 100644 --- a/ors-engine/src/main/java/org/heigit/ors/routing/RoutingRequest.java +++ b/ors-engine/src/main/java/org/heigit/ors/routing/RoutingRequest.java @@ -416,7 +416,7 @@ public Request createPTRequest(double lat0, double lon0, double lat1, double lon return ptRequest; } - public GHResponse computeRoute(double lat0, double lon0, double lat1, double lon1, WayPointBearing[] bearings, + private GHResponse computeRoute(double lat0, double lon0, double lat1, double lon1, WayPointBearing[] bearings, double[] radiuses, boolean directedSegment, RouteSearchParameters searchParams, Boolean geometrySimplify, RoutingProfile routingProfile) throws Exception { @@ -544,7 +544,7 @@ else if (bearings[1] == null) return resp; } - public GHResponse computeRoundTripRoute(double lat0, double lon0, WayPointBearing + private GHResponse computeRoundTripRoute(double lat0, double lon0, WayPointBearing bearing, RouteSearchParameters searchParams, Boolean geometrySimplify, RoutingProfile routingProfile) throws Exception { GHResponse resp; @@ -605,7 +605,7 @@ public GHResponse computeRoundTripRoute(double lat0, double lon0, WayPointBearin return resp; } - public RouteResult[] computeRoundTripRoute() throws Exception { + private RouteResult[] computeRoundTripRoute() throws Exception { List routes = new ArrayList<>(); RoutingProfile rp = profile(); @@ -683,7 +683,7 @@ public RouteResult[] computeRoundTripRoute() throws Exception { return new RouteResultBuilder().createRouteResults(routes, this, new List[]{extraInfos}); } - public RouteResult[] computeRoute(RoutingProfileManager routingProfileManager) throws Exception { + public RouteResult[] computeRoute() throws Exception { if (getSearchParameters().getRoundTripLength() > 0) { return computeRoundTripRoute(); } else { @@ -691,7 +691,7 @@ public RouteResult[] computeRoute(RoutingProfileManager routingProfileManager) t } } - public RouteResult[] computeLinearRoute() throws Exception { + private RouteResult[] computeLinearRoute() throws Exception { List skipSegments = getSkipSegments(); List routes = new ArrayList<>(); From 6d85ee0c0ad1e35911d612e099fa0730a6022978 Mon Sep 17 00:00:00 2001 From: takb Date: Tue, 21 Jan 2025 14:52:38 +0100 Subject: [PATCH 5/5] fix: Make properties checks null safe --- .../main/java/org/heigit/ors/api/services/RoutingService.java | 4 ++-- .../routing/graphhopper/extensions/ORSGraphHopperConfig.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ors-api/src/main/java/org/heigit/ors/api/services/RoutingService.java b/ors-api/src/main/java/org/heigit/ors/api/services/RoutingService.java index 2e25cabce8..c7e8fa6e74 100644 --- a/ors-api/src/main/java/org/heigit/ors/api/services/RoutingService.java +++ b/ors-api/src/main/java/org/heigit/ors/api/services/RoutingService.java @@ -122,11 +122,11 @@ public RouteResult[] generateRouteFromRequest(RouteRequest routeApiRequest) thro routingRequest.setRoutingProfile(profile); validateRouteProfileForRequest(routingRequest); if (routeApiRequest.hasCustomModel()) { - if (!profile.getProfileProperties().getBuild().getEncoderOptions().getEnableCustomModels()) { + if (Boolean.FALSE == profile.getProfileProperties().getBuild().getEncoderOptions().getEnableCustomModels()) { throw new StatusCodeException(StatusCode.INTERNAL_SERVER_ERROR, RoutingErrorCodes.UNSUPPORTED_REQUEST_OPTION, "Custom model not available for profile '" + profile.name() + "'."); } - if (!profile.getProfileProperties().getService().getAllowCustomModels()) { + if (Boolean.FALSE == profile.getProfileProperties().getService().getAllowCustomModels()) { throw new StatusCodeException(StatusCode.INTERNAL_SERVER_ERROR, RoutingErrorCodes.UNSUPPORTED_REQUEST_OPTION, "Custom model disabled for profile '" + profile.name() + "'."); } diff --git a/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/ORSGraphHopperConfig.java b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/ORSGraphHopperConfig.java index b02cc9645c..d1a607bc4f 100644 --- a/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/ORSGraphHopperConfig.java +++ b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/ORSGraphHopperConfig.java @@ -80,7 +80,7 @@ public static ORSGraphHopperConfig createGHSettings(ProfileProperties profile, E profiles.put(profileName, new Profile(profileName).setVehicle(vehicle).setWeighting(weighting).setTurnCosts(false)); } - if (profile.getBuild().getEncoderOptions().getEnableCustomModels()) { + if (Boolean.TRUE == profile.getBuild().getEncoderOptions().getEnableCustomModels()) { if (hasTurnCosts) { profiles.put(vehicle + "_custom_with_turn_costs", new CustomProfile(vehicle + "_custom_with_turn_costs").setCustomModel(new CustomModel().setDistanceInfluence(0)).setVehicle(vehicle).setTurnCosts(true)); }