diff --git a/klab.engine/src/main/java/org/integratedmodelling/klab/components/geospace/routing/Valhalla.java b/klab.engine/src/main/java/org/integratedmodelling/klab/components/geospace/routing/Valhalla.java index 76af1e190..855543441 100644 --- a/klab.engine/src/main/java/org/integratedmodelling/klab/components/geospace/routing/Valhalla.java +++ b/klab.engine/src/main/java/org/integratedmodelling/klab/components/geospace/routing/Valhalla.java @@ -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; /** @@ -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> list = matrix.getAdjacencyList(); - System.out.println(list); - - // Instantiate and populate the graph. - Graph g = new DirectedSparseGraph<>(); - for (Map 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 stats = route.getSummaryStatistics(); - List> 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]; @@ -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 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 sources, List 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. */ diff --git a/klab.engine/src/main/java/org/integratedmodelling/klab/components/geospace/routing/ValhallaOutputDeserializer.java b/klab.engine/src/main/java/org/integratedmodelling/klab/components/geospace/routing/ValhallaOutputDeserializer.java index 263127fdd..1850abff7 100644 --- a/klab.engine/src/main/java/org/integratedmodelling/klab/components/geospace/routing/ValhallaOutputDeserializer.java +++ b/klab.engine/src/main/java/org/integratedmodelling/klab/components/geospace/routing/ValhallaOutputDeserializer.java @@ -60,15 +60,15 @@ static public class Matrix { public String algorithm; public String units; - public ArrayList> sources; - public ArrayList> targets; - public Collection> sourcesToTargets; + public ArrayList sources; + public ArrayList targets; + public Collection> sourcesToTargets; @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) public Matrix(@JsonProperty("algorithm") String algorithm, @JsonProperty("units") String units, - @JsonProperty("sources") ArrayList> sources, - @JsonProperty("targets") ArrayList> targets, - @JsonProperty("sources_to_targets") Collection> sourcesToTargets) { + @JsonProperty("sources") ArrayList sources, + @JsonProperty("targets") ArrayList targets, + @JsonProperty("sources_to_targets") Collection> sourcesToTargets) { this.algorithm = algorithm; this.units = units; this.sources = sources; @@ -87,17 +87,17 @@ public String units() { } @JsonProperty("sources") - public ArrayList> sources() { + public ArrayList sources() { return sources; } @JsonProperty("targets") - public ArrayList> targets() { + public ArrayList targets() { return targets; } @JsonProperty("sources_to_targets") - public Collection> sourcesToTargets() { + public Collection> sourcesToTargets() { return sourcesToTargets; } @@ -110,17 +110,17 @@ public String getUnits() { } public List> 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> 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> getAdjacencyList() { - return this.sourcesToTargets.stream().flatMap(x -> x.stream().map(PairwiseDistance::exportAsMap)) - .collect(Collectors.toList()); - } +// public List> getAdjacencyList() { +// return this.sourcesToTargets.stream().flatMap(x -> x.stream().map(PairwiseDistance::exportAsMap)) +// .collect(Collectors.toList()); +// } public static class Coordinates { public double lon; diff --git a/klab.engine/src/main/java/org/integratedmodelling/klab/components/network/services/MatrixRelationshipInstantiator.java b/klab.engine/src/main/java/org/integratedmodelling/klab/components/network/services/MatrixRelationshipInstantiator.java new file mode 100644 index 000000000..1bed7cf50 --- /dev/null +++ b/klab.engine/src/main/java/org/integratedmodelling/klab/components/network/services/MatrixRelationshipInstantiator.java @@ -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 graph; + private Map, IShape> trajectories; + + public MatrixRelationshipInstantiator() { + /* to instantiate as expression - do not remove (or use) */} + + public MatrixRelationshipInstantiator(Parameters 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 instantiate(IObservable semantics, IContextualizationScope context) throws KlabException { + int i = 1; + List 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 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 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 sourcesCoordinates = sources.stream().map(s -> Valhalla.getCoordinates((IDirectObservation) s, geometryCollapser)).toList(); + List 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 connections : response.sourcesToTargets) { + for (PairwiseDistance connection : connections) { + IObservation source = sources.get(connection.sourceId); + IObservation target = targets.get(connection.targetId); + + Parameters routeParameters = new Parameters(); + 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 routeParameters; + + MatrixEdge() { + } + + MatrixEdge(Parameters rp) { + this.routeParameters = rp; + } + + public Parameters getParameters() { + return this.routeParameters; + } + + } + + @Override + public Object eval(IContextualizationScope context, Object... parameters) { + return new MatrixRelationshipInstantiator(Parameters.create(parameters), context); + } + +} diff --git a/klab.engine/src/main/java/org/integratedmodelling/klab/components/network/services/RoutingRelationshipInstantiator.java b/klab.engine/src/main/java/org/integratedmodelling/klab/components/network/services/RoutingRelationshipInstantiator.java index fe254355b..4c8df6946 100644 --- a/klab.engine/src/main/java/org/integratedmodelling/klab/components/network/services/RoutingRelationshipInstantiator.java +++ b/klab.engine/src/main/java/org/integratedmodelling/klab/components/network/services/RoutingRelationshipInstantiator.java @@ -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 { diff --git a/klab.engine/src/main/resources/components/org.integratedmodelling.network/services/network.kdl b/klab.engine/src/main/resources/components/org.integratedmodelling.network/services/network.kdl index a6e3478e9..ca99264df 100644 --- a/klab.engine/src/main/resources/components/org.integratedmodelling.network/services/network.kdl +++ b/klab.engine/src/main/resources/components/org.integratedmodelling.network/services/network.kdl @@ -99,6 +99,44 @@ export object route } +export object matrix + "Connects subjects through routes computed along a specified spatial configuration. Relationships are only + created if a route exists. Output values are given in kilometers and seconds." + // TODO make clear the difference between this and route +{ + optional import object sources + "Semantics for the type of the subjects used as source for the relationships" + default "" + + optional import object targets + "Semantics for the type of the subjects used as target for the relationships" + default "" + + optional number time_limit + "Time threshold to consider a route a valid option in seconds" + default "No limit" + + optional number distance_limit + "Distance threshold to consider a route a valid option in kilometers" + default "No limit" + + optional enum transport + "Type of transport used to find a route" + values Auto, Pedestrian, Bicycle, Bus, Truck, Taxi, MotorScooter, Multimodal + default "Auto" + + optional enum collapse_geometry + "Method used to collapse line and polygon sources and targets to a single point or a set of points that allow route + finding." + values Centroid + default "Centroid" + + optional text server + "Specify the endpoint for the Valhalla server." + + class org.integratedmodelling.klab.components.network.services.MatrixRelationshipInstantiator + +} export object communities