Skip to content

Commit

Permalink
Merge pull request #992 from GIScience/rebase_td_isochrones_routing
Browse files Browse the repository at this point in the history
Traffic-enabled routing and isochrones
  • Loading branch information
takb authored Jan 3, 2022
2 parents f19cc55 + ff39cda commit eb02107
Show file tree
Hide file tree
Showing 46 changed files with 4,569 additions and 1,956 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.lessThan;

@EndPointAnnotation(name = "isochrones")
@VersionAnnotation(version = "v2")
Expand Down Expand Up @@ -404,17 +403,14 @@ public void testIntersections() {

@Test
public void testSmoothingFactor() {

JSONObject body = new JSONObject();
body.put("locations", getParameter("locations_1"));
body.put("range", getParameter("ranges_2000"));
body.put("smoothing", "10");
body.put("range_type", "distance");

// Updated in the GH 0.12 update from size = 52 as there is a difference in the order that edges are returned and
// so neighbourhood search results in slightly different results

given()
int lowSmoothingCoordinatesSize = given()
.header("Accept", "application/geo+json")
.header("Content-Type", "application/json")
.pathParam("profile", getParameter("cyclingProfile"))
Expand All @@ -424,8 +420,7 @@ public void testSmoothingFactor() {
.then()
.body("any { it.key == 'type' }", is(true))
.body("any { it.key == 'features' }", is(true))
.body("features[0].geometry.coordinates[0].size", is(51))
.statusCode(200);
.extract().jsonPath().getInt("features[0].geometry.coordinates[0].size()");

body.put("smoothing", "100");

Expand All @@ -439,7 +434,7 @@ public void testSmoothingFactor() {
.then()
.body("any { it.key == 'type' }", is(true))
.body("any { it.key == 'features' }", is(true))
.body("features[0].geometry.coordinates[0].size", is(19))
.body("features[0].geometry.coordinates[0].size()", lessThan(lowSmoothingCoordinatesSize))
.statusCode(200);
}

Expand Down
18 changes: 17 additions & 1 deletion openrouteservice/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,18 @@
<version>${geotools.version}</version>
</dependency>

<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-swing</artifactId>
<version>${geotools.version}</version>
</dependency>

<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-shapefile</artifactId>
<version>${geotools.version}</version>
</dependency>

<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j</artifactId>
Expand Down Expand Up @@ -377,7 +389,11 @@
<version>${slf4j.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>me.tongfei</groupId>
<artifactId>progressbar</artifactId>
<version>0.5.5</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.heigit.ors.services.isochrones.IsochronesServiceSettings;
import org.heigit.ors.util.DistanceUnitUtil;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;

Expand All @@ -61,6 +62,7 @@ public class IsochronesRequest extends APIRequest {
public static final String PARAM_ATTRIBUTES = "attributes";
public static final String PARAM_INTERVAL = "interval";
public static final String PARAM_SMOOTHING = "smoothing";
public static final String PARAM_TIME = "time";


@ApiModelProperty(name = PARAM_LOCATIONS, value = "The locations to use for the route as an array of `longitude/latitude` pairs",
Expand Down Expand Up @@ -163,14 +165,37 @@ public class IsochronesRequest extends APIRequest {
@JsonIgnore
private boolean hasSmoothing = false;

@ApiModelProperty(name = PARAM_TIME, value = "Departure date and time provided in local time zone" +
"CUSTOM_KEYS:{'validWhen':{'ref':'arrival','valueNot':['*']}}",
example = "2020-01-31T12:45:00")
@JsonProperty(PARAM_TIME)
private LocalDateTime time;
@JsonIgnore
private boolean hasTime = false;

private IsochroneMapCollection isoMaps;
private IsochroneRequest isochroneRequest;


@JsonCreator
public IsochronesRequest() {
}

static String[] convertAttributes(IsochronesRequestEnums.Attributes[] attributes) {
return convertAPIEnumListToStrings(attributes);
}

protected static int convertToIsochronesProfileType(APIEnums.Profile profile) throws ParameterValueException {
try {
int profileFromString = RoutingProfileType.getFromString(profile.toString());
if (profileFromString == 0) {
throw new ParameterValueException(IsochronesErrorCodes.INVALID_PARAMETER_VALUE, "profile");
}
return profileFromString;
} catch (Exception e) {
throw new ParameterValueException(IsochronesErrorCodes.INVALID_PARAMETER_VALUE, "profile");
}
}

public APIEnums.Units getAreaUnit() {
return areaUnit;
}
Expand Down Expand Up @@ -269,7 +294,7 @@ public void setLocationType(IsochronesRequestEnums.LocationType locationType) {
public boolean hasLocationType() {
return hasLocationType;
}

public RouteRequestOptions getIsochronesOptions() {
return isochronesOptions;
}
Expand Down Expand Up @@ -322,6 +347,19 @@ public boolean hasInterval() {
return hasInterval;
}

public LocalDateTime getTime() {
return time;
}

public void setTime(LocalDateTime time) {
this.time = time;
hasTime = true;
}

public boolean hasTime() {
return hasTime;
}

public void generateIsochronesFromRequest() throws Exception {
this.isochroneRequest = this.convertIsochroneRequest();
// request object is built, now check if ors config allows all settings
Expand Down Expand Up @@ -454,7 +492,7 @@ IsochroneRequest convertIsochroneRequest() throws Exception {
convertedIsochroneRequest.setSmoothingFactor(convertSmoothing(smoothing));
if (this.hasIntersections())
convertedIsochroneRequest.setIncludeIntersections(intersections);
if(this.hasOptions())
if (this.hasOptions())
convertedIsochroneRequest.setCalcMethod(convertCalcMethod(CONCAVE_BALLS));
else
convertedIsochroneRequest.setCalcMethod(convertCalcMethod(FASTISOCHRONE));
Expand Down Expand Up @@ -499,6 +537,10 @@ RouteSearchParameters constructRouteSearchParameters() throws Exception {
if (this.hasOptions()) {
routeSearchParameters = this.processIsochronesRequestOptions(routeSearchParameters);
}
if (this.hasTime()) {
routeSearchParameters.setDeparture(this.getTime());
routeSearchParameters.setArrival(this.getTime());
}
routeSearchParameters.setConsiderTurnRestrictions(false);
return routeSearchParameters;
}
Expand Down Expand Up @@ -556,18 +598,14 @@ void setRangeAndIntervals(TravellerInfo travellerInfo, List<Double> rangeValues,
}
// interval, only use if one range is defined

if (rangeValues.size() == 1 && rangeValue != -1 && intervalValue != null){
if (rangeValues.size() == 1 && rangeValue != -1 && intervalValue != null) {
if (intervalValue > rangeValue) {
throw new ParameterOutOfRangeException (IsochronesErrorCodes.PARAMETER_VALUE_EXCEEDS_MAXIMUM, IsochronesRequest.PARAM_INTERVAL, Double.toString(intervalValue), Double.toString(rangeValue));
throw new ParameterOutOfRangeException(IsochronesErrorCodes.PARAMETER_VALUE_EXCEEDS_MAXIMUM, IsochronesRequest.PARAM_INTERVAL, Double.toString(intervalValue), Double.toString(rangeValue));
}
travellerInfo.setRanges(rangeValue, intervalValue);
}
}

static String[] convertAttributes(IsochronesRequestEnums.Attributes[] attributes) {
return convertAPIEnumListToStrings(attributes);
}

String convertCalcMethod(IsochronesRequestEnums.CalculationMethod bareCalcMethod) throws ParameterValueException {
try {
switch (bareCalcMethod) {
Expand All @@ -585,18 +623,6 @@ String convertCalcMethod(IsochronesRequestEnums.CalculationMethod bareCalcMethod
}
}

protected static int convertToIsochronesProfileType(APIEnums.Profile profile) throws ParameterValueException {
try {
int profileFromString = RoutingProfileType.getFromString(profile.toString());
if (profileFromString == 0) {
throw new ParameterValueException(IsochronesErrorCodes.INVALID_PARAMETER_VALUE, "profile");
}
return profileFromString;
} catch (Exception e) {
throw new ParameterValueException(IsochronesErrorCodes.INVALID_PARAMETER_VALUE, "profile");
}
}

public IsochroneMapCollection getIsoMaps() {
return isoMaps;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import org.heigit.ors.fastisochrones.partitioning.storage.CellStorage;
import org.heigit.ors.fastisochrones.partitioning.storage.IsochroneNodeStorage;
import org.heigit.ors.isochrones.builders.concaveballs.PointItemVisitor;
import org.opensphere.geometry.algorithm.ConcaveHull;
import org.opensphere.geometry.algorithm.ConcaveHullOpenSphere;

import java.util.*;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -208,7 +208,7 @@ private Geometry concHullOfNodes(List<Coordinate> coordinates, boolean useHighDe
geometries[g++] = geomFactory.createPoint(point);
GeometryCollection treePoints = new GeometryCollection(geometries, geomFactory);

ConcaveHull ch = new ConcaveHull(treePoints, CONCAVE_HULL_THRESHOLD, false);
ConcaveHullOpenSphere ch = new ConcaveHullOpenSphere(treePoints, CONCAVE_HULL_THRESHOLD, false);
return ch.getConcaveHull();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
/* This file is part of Openrouteservice.
*
* Openrouteservice is free software; you can redistribute it and/or modify it under the terms of the
* GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1
* Openrouteservice is free software; you can redistribute it and/or modify it under the terms of the
* GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1
* of the License, or (at your option) any later version.
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License along with this library;
* if not, see <https://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Lesser General Public License along with this library;
* if not, see <https://www.gnu.org/licenses/>.
*/
package org.heigit.ors.isochrones;

Expand All @@ -20,57 +20,106 @@
import com.graphhopper.routing.util.FlagEncoder;
import com.graphhopper.routing.util.TraversalMode;
import com.graphhopper.routing.weighting.FastestWeighting;
import com.graphhopper.routing.weighting.TurnWeighting;
import com.graphhopper.routing.weighting.Weighting;
import com.graphhopper.storage.GraphHopperStorage;
import com.graphhopper.storage.SPTEntry;
import com.graphhopper.storage.index.QueryResult;
import com.graphhopper.util.HelperORS;
import com.graphhopper.util.shapes.GHPoint3D;
import com.vividsolutions.jts.geom.Coordinate;
import org.heigit.ors.common.TravelRangeType;
import org.heigit.ors.exceptions.InternalServerException;
import org.heigit.ors.routing.RouteSearchContext;
import org.heigit.ors.routing.algorithms.DijkstraCostCondition;
import org.heigit.ors.routing.algorithms.TDDijkstraCostCondition;
import org.heigit.ors.routing.graphhopper.extensions.AccessibilityMap;
import org.heigit.ors.routing.graphhopper.extensions.ORSEdgeFilterFactory;
import org.heigit.ors.routing.graphhopper.extensions.weighting.DistanceWeighting;
import org.heigit.ors.routing.traffic.TrafficSpeedCalculator;

import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;

public class GraphEdgeMapFinder {
private GraphEdgeMapFinder() {}

public static AccessibilityMap findEdgeMap(RouteSearchContext searchCntx, IsochroneSearchParameters parameters) throws Exception {
GraphHopper gh = searchCntx.getGraphHopper();
FlagEncoder encoder = searchCntx.getEncoder();
GraphHopperStorage graph = gh.getGraphHopperStorage();
private GraphEdgeMapFinder() {
}

ORSEdgeFilterFactory edgeFilterFactory = new ORSEdgeFilterFactory();
EdgeFilter edgeFilter = edgeFilterFactory.createEdgeFilter(searchCntx.getProperties(), encoder, graph);

Coordinate loc = parameters.getLocation();
QueryResult res = gh.getLocationIndex().findClosest(loc.y, loc.x, edgeFilter);
List<QueryResult> queryResults = new ArrayList<>(1);
queryResults.add(res);
QueryGraph queryGraph = new QueryGraph(graph);
queryGraph.lookup(queryResults);
public static AccessibilityMap findEdgeMap(RouteSearchContext searchCntx, IsochroneSearchParameters parameters) throws Exception {
GraphHopper gh = searchCntx.getGraphHopper();
FlagEncoder encoder = searchCntx.getEncoder();
GraphHopperStorage graph = gh.getGraphHopperStorage();

GHPoint3D snappedPosition = res.getSnappedPoint();
ORSEdgeFilterFactory edgeFilterFactory = new ORSEdgeFilterFactory();
EdgeFilter edgeFilter = edgeFilterFactory.createEdgeFilter(searchCntx.getProperties(), encoder, graph);

int fromId = res.getClosestNode();
Coordinate loc = parameters.getLocation();
QueryResult res = gh.getLocationIndex().findClosest(loc.y, loc.x, edgeFilter);
List<QueryResult> queryResults = new ArrayList<>(1);
queryResults.add(res);
QueryGraph queryGraph = new QueryGraph(graph);
queryGraph.lookup(queryResults);

if (fromId == -1)
throw new InternalServerException(IsochronesErrorCodes.UNKNOWN, "The closest node is null.");

Weighting weighting = parameters.getRangeType() == TravelRangeType.TIME ? new FastestWeighting(encoder) : new DistanceWeighting(encoder);
GHPoint3D snappedPosition = res.getSnappedPoint();

// IMPORTANT: It only works with TraversalMode.NODE_BASED.
DijkstraCostCondition dijkstraAlg = new DijkstraCostCondition(queryGraph, weighting, parameters.getMaximumRange(), parameters.getReverseDirection(),
TraversalMode.NODE_BASED);
dijkstraAlg.setEdgeFilter(edgeFilter);
dijkstraAlg.calcPath(fromId, Integer.MIN_VALUE);
int fromId = res.getClosestNode();

IntObjectMap<SPTEntry> edgeMap = dijkstraAlg.getMap();
return new AccessibilityMap(edgeMap, dijkstraAlg.getCurrentEdge(), snappedPosition);
}
if (fromId == -1)
throw new InternalServerException(IsochronesErrorCodes.UNKNOWN, "The closest node is null.");
Weighting weighting = createWeighting(parameters, encoder);

if (parameters.isTimeDependent()) {
return calculateTimeDependentAccessibilityMap(parameters, encoder, graph, edgeFilter, queryGraph, snappedPosition, fromId, weighting);
} else {
// IMPORTANT: It only works with TraversalMode.NODE_BASED.
DijkstraCostCondition dijkstraAlg = new DijkstraCostCondition(queryGraph, weighting, parameters.getMaximumRange(), parameters.getReverseDirection(),
TraversalMode.NODE_BASED);
dijkstraAlg.setEdgeFilter(edgeFilter);
dijkstraAlg.calcPath(fromId, Integer.MIN_VALUE);

IntObjectMap<SPTEntry> edgeMap = dijkstraAlg.getMap();
return new AccessibilityMap(edgeMap, dijkstraAlg.getCurrentEdge(), snappedPosition);
}
}

/**
* Calculate all nodes that are within the reach of the maximum range and return a map of them.
*
* @param parameters IsochroneSearchParameters
* @param encoder FlagEncoder
* @param graph GraphHopperStorage
* @param edgeFilter The EdgeFilter to be used for finding the nodes
* @param queryGraph Graph containing all normal nodes and virtual node of the queried location
* @param snappedPosition Position the query has been snapped to on the querygraph
* @param fromId origin of query
* @param weighting weighting to be used
* @return accessibility map containing all reachable nodes
*/
private static AccessibilityMap calculateTimeDependentAccessibilityMap(IsochroneSearchParameters parameters, FlagEncoder encoder, GraphHopperStorage graph, EdgeFilter edgeFilter, QueryGraph queryGraph, GHPoint3D snappedPosition, int fromId, Weighting weighting) {
//Time-dependent means traffic dependent for isochrones (for now)
TrafficSpeedCalculator trafficSpeedCalculator = new TrafficSpeedCalculator(weighting.getSpeedCalculator());
trafficSpeedCalculator.init(graph, encoder);
weighting.setSpeedCalculator(trafficSpeedCalculator);
if (HelperORS.getTurnCostExtensions(graph.getExtension()) != null)
weighting = new TurnWeighting(weighting, HelperORS.getTurnCostExtensions(graph.getExtension()));
TDDijkstraCostCondition tdDijkstraCostCondition = new TDDijkstraCostCondition(queryGraph, weighting, parameters.getMaximumRange(), parameters.getReverseDirection(),
TraversalMode.NODE_BASED);
tdDijkstraCostCondition.setEdgeFilter(edgeFilter);
//Time is defined to be in UTC + 1 because original implementation was for German traffic data
//If changed, this needs to be adapted in the traffic storage, too
ZonedDateTime zdt = parameters.getRouteParameters().getDeparture().atZone(trafficSpeedCalculator.getZoneId());
trafficSpeedCalculator.setZonedDateTime(zdt);
int toId = parameters.getReverseDirection() ? fromId : Integer.MIN_VALUE;
fromId = parameters.getReverseDirection() ? Integer.MIN_VALUE : fromId;
tdDijkstraCostCondition.calcPath(fromId, toId, zdt.toInstant().toEpochMilli());
IntObjectMap<SPTEntry> edgeMap = tdDijkstraCostCondition.getMap();
return new AccessibilityMap(edgeMap, tdDijkstraCostCondition.getCurrentEdge(), snappedPosition);
}

private static Weighting createWeighting(IsochroneSearchParameters parameters, FlagEncoder encoder) {
Weighting weighting = parameters.getRangeType() == TravelRangeType.TIME ? new FastestWeighting(encoder)
: new DistanceWeighting(encoder);
return weighting;
}
}
Loading

0 comments on commit eb02107

Please sign in to comment.