Skip to content

Commit

Permalink
Make matrix queries using Valhalla
Browse files Browse the repository at this point in the history
  • Loading branch information
inigo-cobian committed Feb 12, 2025
1 parent 5c00b77 commit 7d2a03a
Show file tree
Hide file tree
Showing 5 changed files with 244 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import kong.unirest.HttpResponse;
import kong.unirest.JsonNode;
import kong.unirest.Unirest;
import kong.unirest.json.JSONArray;
import kong.unirest.json.JSONObject;

/**
Expand Down Expand Up @@ -63,75 +64,6 @@ public Geometry isochrone(String input) throws ValhallaException {
return GeoJSONReader.parseGeometry(json.getJSONArray("features").getJSONObject(0).getJSONObject("geometry").toString());
}

public static void main(String[] args) throws ValhallaException {
Valhalla valhalla = new Valhalla("https://routing.integratedmodelling.org");

/*
* Matrix API example.
*/

// Coordinates of sources and targets for the travel-time matrix. Note the
// costing parameter which essentially
// is the means of transport. For testing make sure that coordinates are within
// the loaded OSM environment.
// String input = "{\"sources\":[{\"lat\":42.544014,\"lon\":1.5163911},{\"lat\":42.524014,\"lon\":1.5263911}],\"targets\":[{\"lat\":42.539735,\"lon\":1.4988},{\"lat\":42.541735,\"lon\":1.4888}],\"costing\":\"pedestrian\"}";

String input = "{\"sources\":[{\"lat\":40.544014,\"lon\":-103},{\"lat\":40.524014,\"lon\":-103}],\"targets\":[{\"lat\":40.539735,\"lon\":-103},{\"lat\":40.541735,\"lon\":-103}],\"costing\":\"auto\"}";

// Call to matrix method with input, the function returns the deserialized JSON
// string in a specific format.
ValhallaOutputDeserializer.Matrix matrix = valhalla.matrix(input);

// The adjacency list stores information on the distance/time between each
// source and target in a way that is
// very friendly for graph creation with JUNG, and probably also with JGraphT.
List<Map<String, Number>> list = matrix.getAdjacencyList();
System.out.println(list);

// Instantiate and populate the graph.
Graph<String, Double> g = new DirectedSparseGraph<>();
for (Map<String, Number> m : list) {
Integer source = (Integer) m.get("source");
Integer target = (Integer) m.get("target");
double time = (double) m.get("time");

// VertexIds are transformed to strings and prefixed with s or t to easily
// differentiate between sources and
// targets as the index starts at 0 for both. If needed to use integers ewe can
// always do
// target_index += max(source_index)
String sv = "s" + source.toString();
String tv = "t" + target.toString();

// In this case a time accessibility graph is created.
boolean added = g.addEdge(time, sv, tv, EdgeType.DIRECTED);

if (!added)
throw new ValhallaException("Could not add edge to graph");
}
System.out.println(g);

/*
* Optimized Route API example.
*/

// This is a back and forth trip in Andorra.
// input = "{\"locations\":[{\"lat\":42.544014,\"lon\":1.5163911},{\"lat\":42.539735,\"lon\":1.4988},{\"lat\":42.544014,\"lon\":1.5163911}],\"costing\":\"auto\"}";

input = "{\"locations\":[{\"lat\":40.544014,\"lon\":-103},{\"lat\":40.524014,\"lon\":-103}],\"costing\":\"auto\"}";

// Call to optimized route method with input, the function returns the
// deserialized JSON string in a specific format.
ValhallaOutputDeserializer.OptimizedRoute route = valhalla.optimized_route(input);
IShape path = route.getPath();
Map<String, Object> stats = route.getSummaryStatistics();
List<Map<String, Number>> waypoints = route.getWaypoints();

System.out.println(path);
System.out.println(stats);
System.out.println(waypoints);
}

public static String buildValhallaJsonInput(double[] source, double[] target,
String transportType) {
double sourceLat = source[1];
Expand All @@ -152,6 +84,21 @@ public static String buildValhallaIsochroneInput(double[] coordinates, String tr
.append("}],\"polygons\":true,\"reverse\":").append(isReverse).append("}").toString();
}

private static JSONArray coordinatesAsJson(List<double[]> coordinates) {
JSONArray ret = new JSONArray();
coordinates.forEach(c -> {
JSONObject coor = new JSONObject().put("lat", c[1]).put("lon", c[0]);
ret.put(coor);
});
return ret;
}

public static String buildValhallaMatrixInput(List<double[]> sources, List<double[]> targets, String transportType) {
JSONArray sourcesAsJson = coordinatesAsJson(sources);
JSONArray targetsAsJson = coordinatesAsJson(targets);

return new JSONObject().put("sources", sourcesAsJson).put("targets", targetsAsJson).put("costing", transportType).toString();
}
/*
* Sets the coordinates according to the selected geometry collapser.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,15 @@ static public class Matrix {

public String algorithm;
public String units;
public ArrayList<ArrayList<Coordinates>> sources;
public ArrayList<ArrayList<Coordinates>> targets;
public Collection<Collection<PairwiseDistance>> sourcesToTargets;
public ArrayList<Coordinates> sources;
public ArrayList<Coordinates> targets;
public Collection<List<PairwiseDistance>> sourcesToTargets;

@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public Matrix(@JsonProperty("algorithm") String algorithm, @JsonProperty("units") String units,
@JsonProperty("sources") ArrayList<ArrayList<Coordinates>> sources,
@JsonProperty("targets") ArrayList<ArrayList<Coordinates>> targets,
@JsonProperty("sources_to_targets") Collection<Collection<PairwiseDistance>> sourcesToTargets) {
@JsonProperty("sources") ArrayList<Coordinates> sources,
@JsonProperty("targets") ArrayList<Coordinates> targets,
@JsonProperty("sources_to_targets") Collection<List<PairwiseDistance>> sourcesToTargets) {
this.algorithm = algorithm;
this.units = units;
this.sources = sources;
Expand All @@ -87,17 +87,17 @@ public String units() {
}

@JsonProperty("sources")
public ArrayList<ArrayList<Coordinates>> sources() {
public ArrayList<Coordinates> sources() {
return sources;
}

@JsonProperty("targets")
public ArrayList<ArrayList<Coordinates>> targets() {
public ArrayList<Coordinates> targets() {
return targets;
}

@JsonProperty("sources_to_targets")
public Collection<Collection<PairwiseDistance>> sourcesToTargets() {
public Collection<List<PairwiseDistance>> sourcesToTargets() {
return sourcesToTargets;
}

Expand All @@ -110,17 +110,17 @@ public String getUnits() {
}

public List<Map<String, Double>> getSources() {
return this.sources.get(0).stream().map(Coordinates::exportAsMap).collect(Collectors.toList());
return this.sources.stream().map(Coordinates::exportAsMap).collect(Collectors.toList());
}

public List<Map<String, Double>> getTargets() {
return this.sources.get(0).stream().map(Coordinates::exportAsMap).collect(Collectors.toList());
return this.targets.stream().map(Coordinates::exportAsMap).collect(Collectors.toList());
}

public List<Map<String, Number>> getAdjacencyList() {
return this.sourcesToTargets.stream().flatMap(x -> x.stream().map(PairwiseDistance::exportAsMap))
.collect(Collectors.toList());
}
// public List<Map<String, Number>> getAdjacencyList() {
// return this.sourcesToTargets.stream().flatMap(x -> x.stream().map(PairwiseDistance::exportAsMap))
// .collect(Collectors.toList());
// }

public static class Coordinates {
public double lon;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package org.integratedmodelling.klab.components.network.services;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.integratedmodelling.klab.Observables;
import org.integratedmodelling.klab.api.data.artifacts.IObjectArtifact;
import org.integratedmodelling.klab.api.data.general.IExpression;
import org.integratedmodelling.klab.api.knowledge.IConcept;
import org.integratedmodelling.klab.api.knowledge.IObservable;
import org.integratedmodelling.klab.api.model.contextualization.IInstantiator;
import org.integratedmodelling.klab.api.observations.IDirectObservation;
import org.integratedmodelling.klab.api.observations.IObservation;
import org.integratedmodelling.klab.api.observations.IObservationGroup;
import org.integratedmodelling.klab.api.observations.scale.space.IShape;
import org.integratedmodelling.klab.api.provenance.IArtifact;
import org.integratedmodelling.klab.api.provenance.IArtifact.Type;
import org.integratedmodelling.klab.api.runtime.IContextualizationScope;
import org.integratedmodelling.klab.components.geospace.routing.Valhalla;
import org.integratedmodelling.klab.components.geospace.routing.ValhallaConfiguration.GeometryCollapser;
import org.integratedmodelling.klab.components.geospace.routing.ValhallaConfiguration.TransportType;
import org.integratedmodelling.klab.components.geospace.routing.ValhallaOutputDeserializer.Matrix.PairwiseDistance;
import org.integratedmodelling.klab.components.geospace.routing.ValhallaOutputDeserializer;
import org.integratedmodelling.klab.components.runtime.contextualizers.AbstractContextualizer;
import org.integratedmodelling.klab.data.Metadata;
import org.integratedmodelling.klab.exceptions.KlabException;
import org.integratedmodelling.klab.exceptions.KlabIllegalArgumentException;
import org.integratedmodelling.klab.exceptions.KlabRemoteException;
import org.integratedmodelling.klab.utils.Pair;
import org.integratedmodelling.klab.utils.Parameters;
import org.integratedmodelling.klab.utils.Utils;
import org.jgrapht.Graph;
import org.jgrapht.graph.DefaultDirectedGraph;
import org.jgrapht.graph.DefaultEdge;

public class MatrixRelationshipInstantiator extends AbstractContextualizer implements IExpression, IInstantiator {
private String sourceArtifact = null;
private String targetArtifact = null;

private Double timeThresholdInSeconds = null;
private Double distanceThresholdInKilometers = null;

private TransportType transportType = TransportType.Auto;
private GeometryCollapser geometryCollapser = GeometryCollapser.Centroid;
private String server;
private IContextualizationScope scope;
private Valhalla valhalla;
private Graph<IObjectArtifact, MatrixEdge> graph;
private Map<Pair<IDirectObservation, IDirectObservation>, IShape> trajectories;

public MatrixRelationshipInstantiator() {
/* to instantiate as expression - do not remove (or use) */}

public MatrixRelationshipInstantiator(Parameters<Object> parameters, IContextualizationScope scope) {
this.scope = scope;
this.sourceArtifact = parameters.get("source", String.class);
this.targetArtifact = parameters.get("target", String.class);
this.timeThresholdInSeconds = parameters.get("time_limit", Double.class);
this.distanceThresholdInKilometers = parameters.get("distance_limit", Double.class);

if (parameters.containsKey("transport")) {
this.transportType = TransportType.fromValue(Utils.removePrefix(parameters.get("transport", String.class)));
}
if (parameters.containsKey("collapse_geometry")) {
this.geometryCollapser = GeometryCollapser
.fromValue(Utils.removePrefix(parameters.get("collapse_geometry", String.class)));
}
if (parameters.get("server") == null || parameters.get("server", String.class).trim().isEmpty()) {
throw new KlabIllegalArgumentException("The server for Valhalla has not been defined.");
}
this.server = parameters.get("server", String.class);
if (Valhalla.isServerOnline(server)) {
this.valhalla = new Valhalla(server);
} else {
throw new KlabRemoteException("The server " + server + " is offline or not a valid Valhalla instance.");
}
}

@Override
public Type getType() {
return Type.OBJECT;
}

@Override
public List<IObjectArtifact> instantiate(IObservable semantics, IContextualizationScope context) throws KlabException {
int i = 1;
List<IObjectArtifact> ret = new ArrayList<>();

// TODO Lo que viene aquí es algo que también se da en RoutingRelationshipInstantiator -> fusionar
IConcept sourceConcept = Observables.INSTANCE.getRelationshipSource(semantics.getType());
IConcept targetConcept = Observables.INSTANCE.getRelationshipTarget(semantics.getType());

List<IObservation> sources = new ArrayList<>();
if (sourceArtifact == null) {
sources.addAll(context.getObservations(sourceConcept));
} else {
IArtifact src = context.getArtifact(sourceArtifact);
if (src instanceof IObservationGroup) {
for (IArtifact a : src) {
sources.add((IObservation) a);
}
}
}

List<IObservation> targets = new ArrayList<>();
if (targetArtifact == null) {
targets.addAll(context.getObservations(targetConcept));
} else {
IArtifact src = context.getArtifact(targetArtifact);
if (src instanceof IObservationGroup) {
for (IArtifact a : src) {
targets.add((IObservation) a);
}
}
}

// // all artifacts must be non-null and objects
// if (!validateThatAllElementsAreObjectArtifact(sources, targets)) {
// throw new IllegalArgumentException(
// "klab.networks.routing: at least one source or target artifact does not exist or is not an object artifact");
// }
List<double[]> sourcesCoordinates = sources.stream().map(s -> Valhalla.getCoordinates((IDirectObservation) s, geometryCollapser)).toList();
List<double[]> targetsCoordinates = targets.stream().map(t -> Valhalla.getCoordinates((IDirectObservation) t, geometryCollapser)).toList();

String matrixRequest = Valhalla.buildValhallaMatrixInput(sourcesCoordinates, targetsCoordinates, transportType.getType());
ValhallaOutputDeserializer.Matrix response = valhalla.matrix(matrixRequest);

graph = new DefaultDirectedGraph<>(MatrixEdge.class);
for (List<PairwiseDistance> connections : response.sourcesToTargets) {
for (PairwiseDistance connection : connections) {
IObservation source = sources.get(connection.sourceId);
IObservation target = targets.get(connection.targetId);

Parameters<String> routeParameters = new Parameters<String>();
routeParameters.put("distance", connection.distance);
routeParameters.put("time", connection.time);

graph.addVertex((IDirectObservation)source);
graph.addVertex((IDirectObservation)target);
graph.addEdge((IDirectObservation)source, (IDirectObservation)target,
new MatrixEdge(routeParameters));

ret.add(scope.newRelationship(semantics, semantics.getName() + "_" + i, scope.getScale(), (IDirectObservation)source, (IDirectObservation)target,
new Metadata(routeParameters)));
i++;
}
}

return ret;
}

class MatrixEdge extends DefaultEdge {
private static final long serialVersionUID = 964984629774455337L;

Parameters<String> routeParameters;

MatrixEdge() {
}

MatrixEdge(Parameters<String> rp) {
this.routeParameters = rp;
}

public Parameters<String> getParameters() {
return this.routeParameters;
}

}

@Override
public Object eval(IContextualizationScope context, Object... parameters) {
return new MatrixRelationshipInstantiator(Parameters.create(parameters), context);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,6 @@
import org.jgrapht.graph.DefaultDirectedGraph;
import org.jgrapht.graph.DefaultEdge;
import org.locationtech.jts.geom.Geometry;
import kong.unirest.HttpResponse;
import kong.unirest.JsonNode;
import kong.unirest.Unirest;
import org.jgrapht.Graph;

public class RoutingRelationshipInstantiator extends AbstractContextualizer implements IExpression, IInstantiator {
Expand Down
Loading

0 comments on commit 7d2a03a

Please sign in to comment.