/*
* Licensed to GraphHopper GmbH under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*
* GraphHopper GmbH licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.graphhopper.util;
import com.carrotsearch.hppc.IntIndexedContainer;
import com.carrotsearch.hppc.LongArrayList;
import com.carrotsearch.hppc.LongIndexedContainer;
import com.graphhopper.coll.GHBitSet;
import com.graphhopper.coll.GHBitSetImpl;
import com.graphhopper.coll.GHIntArrayList;
import com.graphhopper.routing.util.AllCHEdgesIterator;
import com.graphhopper.routing.util.AllEdgesIterator;
import com.graphhopper.routing.util.EdgeFilter;
import com.graphhopper.routing.util.FlagEncoder;
import com.graphhopper.storage.*;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* A helper class to avoid cluttering the Graph interface with all the common methods. Most of the
* methods are useful for unit tests or debugging only.
* <p>
*
* @author Peter Karich
*/
public class GHUtility {
/**
* This method could throw exception if uncatched problems like index out of bounds etc
*/
public static List<String> getProblems(Graph g) {
List<String> problems = new ArrayList<String>();
int nodes = g.getNodes();
int nodeIndex = 0;
NodeAccess na = g.getNodeAccess();
try {
EdgeExplorer explorer = g.createEdgeExplorer();
for (; nodeIndex < nodes; nodeIndex++) {
double lat = na.getLatitude(nodeIndex);
if (lat > 90 || lat < -90)
problems.add("latitude is not within its bounds " + lat);
double lon = na.getLongitude(nodeIndex);
if (lon > 180 || lon < -180)
problems.add("longitude is not within its bounds " + lon);
EdgeIterator iter = explorer.setBaseNode(nodeIndex);
while (iter.next()) {
if (iter.getAdjNode() >= nodes) {
problems.add("edge of " + nodeIndex + " has a node " + iter.getAdjNode() + " greater or equal to getNodes");
}
if (iter.getAdjNode() < 0) {
problems.add("edge of " + nodeIndex + " has a negative node " + iter.getAdjNode());
}
}
}
} catch (Exception ex) {
throw new RuntimeException("problem with node " + nodeIndex, ex);
}
// for (int i = 0; i < nodes; i++) {
// new BreadthFirstSearch().start(g, i);
// }
return problems;
}
/**
* Counts reachable edges.
*/
public static int count(EdgeIterator iter) {
int counter = 0;
while (iter.next()) {
counter++;
}
return counter;
}
public static Set<Integer> asSet(int... values) {
Set<Integer> s = new HashSet<Integer>();
for (int v : values) {
s.add(v);
}
return s;
}
public static Set<Integer> getNeighbors(EdgeIterator iter) {
// make iteration order over set static => linked
Set<Integer> list = new LinkedHashSet<Integer>();
while (iter.next()) {
list.add(iter.getAdjNode());
}
return list;
}
public static List<Integer> getEdgeIds(EdgeIterator iter) {
List<Integer> list = new ArrayList<Integer>();
while (iter.next()) {
list.add(iter.getEdge());
}
return list;
}
public static void printEdgeInfo(final Graph g, FlagEncoder encoder) {
System.out.println("-- Graph n:" + g.getNodes() + " e:" + g.getAllEdges().getMaxId() + " ---");
AllEdgesIterator iter = g.getAllEdges();
while (iter.next()) {
String sc = "";
if (iter instanceof AllCHEdgesIterator) {
AllCHEdgesIterator aeSkip = (AllCHEdgesIterator) iter;
sc = aeSkip.isShortcut() ? "sc" : " ";
}
String fwdStr = iter.isForward(encoder) ? "fwd" : " ";
String bckStr = iter.isBackward(encoder) ? "bckwd" : "";
System.out.println(sc + " " + iter + " " + fwdStr + " " + bckStr);
}
}
public static void printInfo(final Graph g, int startNode, final int counts, final EdgeFilter filter) {
new BreadthFirstSearch() {
int counter = 0;
@Override
protected boolean goFurther(int nodeId) {
System.out.println(getNodeInfo(g, nodeId, filter));
if (counter++ > counts) {
return false;
}
return true;
}
}.start(g.createEdgeExplorer(), startNode);
}
public static String getNodeInfo(CHGraph g, int nodeId, EdgeFilter filter) {
CHEdgeExplorer ex = g.createEdgeExplorer(filter);
CHEdgeIterator iter = ex.setBaseNode(nodeId);
NodeAccess na = g.getNodeAccess();
String str = nodeId + ":" + na.getLatitude(nodeId) + "," + na.getLongitude(nodeId) + "\n";
while (iter.next()) {
str += " ->" + iter.getAdjNode() + "(" + iter.getSkippedEdge1() + "," + iter.getSkippedEdge2() + ") "
+ iter.getEdge() + " \t" + BitUtil.BIG.toBitString(iter.getFlags(), 8) + "\n";
}
return str;
}
public static String getNodeInfo(Graph g, int nodeId, EdgeFilter filter) {
EdgeIterator iter = g.createEdgeExplorer(filter).setBaseNode(nodeId);
NodeAccess na = g.getNodeAccess();
String str = nodeId + ":" + na.getLatitude(nodeId) + "," + na.getLongitude(nodeId) + "\n";
while (iter.next()) {
str += " ->" + iter.getAdjNode() + " (" + iter.getDistance() + ") pillars:"
+ iter.fetchWayGeometry(0).getSize() + ", edgeId:" + iter.getEdge()
+ "\t" + BitUtil.BIG.toBitString(iter.getFlags(), 8) + "\n";
}
return str;
}
public static Graph shuffle(Graph g, Graph sortedGraph) {
int nodes = g.getNodes();
GHIntArrayList list = new GHIntArrayList(nodes);
list.fill(nodes, -1);
for (int i = 0; i < nodes; i++) {
list.set(i, i);
}
list.shuffle(new Random());
return createSortedGraph(g, sortedGraph, list);
}
/**
* Sorts the graph according to depth-first search traversal. Other traversals have either no
* significant difference (bfs) for querying or are worse (z-curve).
*/
public static Graph sortDFS(Graph g, Graph sortedGraph) {
int nodes = g.getNodes();
final GHIntArrayList list = new GHIntArrayList(nodes);
list.fill(nodes, -1);
final GHBitSetImpl bitset = new GHBitSetImpl(nodes);
final AtomicInteger ref = new AtomicInteger(-1);
EdgeExplorer explorer = g.createEdgeExplorer();
for (int startNode = 0; startNode >= 0 && startNode < nodes;
startNode = bitset.nextClear(startNode + 1)) {
new DepthFirstSearch() {
@Override
protected GHBitSet createBitSet() {
return bitset;
}
@Override
protected boolean goFurther(int nodeId) {
list.set(nodeId, ref.incrementAndGet());
return super.goFurther(nodeId);
}
}.start(explorer, startNode);
}
return createSortedGraph(g, sortedGraph, list);
}
static Graph createSortedGraph(Graph fromGraph, Graph toSortedGraph, final IntIndexedContainer oldToNewNodeList) {
AllEdgesIterator eIter = fromGraph.getAllEdges();
while (eIter.next()) {
int base = eIter.getBaseNode();
int newBaseIndex = oldToNewNodeList.get(base);
int adj = eIter.getAdjNode();
int newAdjIndex = oldToNewNodeList.get(adj);
// ignore empty entries
if (newBaseIndex < 0 || newAdjIndex < 0)
continue;
eIter.copyPropertiesTo(toSortedGraph.edge(newBaseIndex, newAdjIndex));
}
int nodes = fromGraph.getNodes();
NodeAccess na = fromGraph.getNodeAccess();
NodeAccess sna = toSortedGraph.getNodeAccess();
for (int old = 0; old < nodes; old++) {
int newIndex = oldToNewNodeList.get(old);
if (sna.is3D())
sna.setNode(newIndex, na.getLatitude(old), na.getLongitude(old), na.getElevation(old));
else
sna.setNode(newIndex, na.getLatitude(old), na.getLongitude(old));
}
return toSortedGraph;
}
/**
* @return the specified toGraph which is now filled with data from fromGraph
*/
// TODO very similar to createSortedGraph -> use a 'int map(int)' interface
public static Graph copyTo(Graph fromGraph, Graph toGraph) {
AllEdgesIterator eIter = fromGraph.getAllEdges();
while (eIter.next()) {
int base = eIter.getBaseNode();
int adj = eIter.getAdjNode();
eIter.copyPropertiesTo(toGraph.edge(base, adj));
}
NodeAccess fna = fromGraph.getNodeAccess();
NodeAccess tna = toGraph.getNodeAccess();
int nodes = fromGraph.getNodes();
for (int node = 0; node < nodes; node++) {
if (tna.is3D())
tna.setNode(node, fna.getLatitude(node), fna.getLongitude(node), fna.getElevation(node));
else
tna.setNode(node, fna.getLatitude(node), fna.getLongitude(node));
}
return toGraph;
}
static Directory guessDirectory(GraphStorage store) {
String location = store.getDirectory().getLocation();
Directory outdir;
if (store.getDirectory() instanceof MMapDirectory) {
throw new IllegalStateException("not supported yet: mmap will overwrite existing storage at the same location");
} else {
boolean isStoring = ((GHDirectory) store.getDirectory()).isStoring();
outdir = new RAMDirectory(location, isStoring);
}
return outdir;
}
/**
* Create a new storage from the specified one without copying the data.
*/
public static GraphHopperStorage newStorage(GraphHopperStorage store) {
Directory outdir = guessDirectory(store);
boolean is3D = store.getNodeAccess().is3D();
return new GraphHopperStorage(store.getCHWeightings(), outdir, store.getEncodingManager(),
is3D, store.getExtension()).
create(store.getNodes());
}
public static int getAdjNode(Graph g, int edge, int adjNode) {
if (EdgeIterator.Edge.isValid(edge)) {
EdgeIteratorState iterTo = g.getEdgeIteratorState(edge, adjNode);
return iterTo.getAdjNode();
}
return adjNode;
}
public static EdgeIteratorState createMockedEdgeIteratorState(final double distance, final long flags) {
return new GHUtility.DisabledEdgeIterator() {
@Override
public double getDistance() {
return distance;
}
@Override
public long getFlags() {
return flags;
}
@Override
public boolean getBool(int key, boolean _default) {
return _default;
}
};
}
;
/**
* @return the <b>first</b> edge containing the specified nodes base and adj. Returns null if
* not found.
*/
public static EdgeIteratorState getEdge(Graph graph, int base, int adj) {
EdgeIterator iter = graph.createEdgeExplorer().setBaseNode(base);
while (iter.next()) {
if (iter.getAdjNode() == adj)
return iter;
}
return null;
}
/**
* Creates unique positive number for specified edgeId taking into account the direction defined
* by nodeA, nodeB and reverse.
*/
public static int createEdgeKey(int nodeA, int nodeB, int edgeId, boolean reverse) {
edgeId = edgeId << 1;
if (reverse)
return (nodeA > nodeB) ? edgeId : edgeId + 1;
return (nodeA > nodeB) ? edgeId + 1 : edgeId;
}
/**
* Returns if the specified edgeKeys (created by createEdgeKey) are identical regardless of the
* direction.
*/
public static boolean isSameEdgeKeys(int edgeKey1, int edgeKey2) {
return edgeKey1 / 2 == edgeKey2 / 2;
}
/**
* Returns the edgeKey of the opposite direction
*/
public static int reverseEdgeKey(int edgeKey) {
return edgeKey % 2 == 0 ? edgeKey + 1 : edgeKey - 1;
}
/**
* @return edge ID for edgeKey
*/
public static int getEdgeFromEdgeKey(int edgeKey) {
return edgeKey / 2;
}
/**
* This edge iterator can be used in tests to mock specific iterator behaviour via overloading
* certain methods.
*/
public static class DisabledEdgeIterator implements CHEdgeIterator {
@Override
public EdgeIterator detach(boolean reverse) {
throw new UnsupportedOperationException("Not supported. Edge is empty.");
}
@Override
public EdgeIteratorState setDistance(double dist) {
throw new UnsupportedOperationException("Not supported. Edge is empty.");
}
@Override
public EdgeIteratorState setFlags(long flags) {
throw new UnsupportedOperationException("Not supported. Edge is empty.");
}
@Override
public boolean next() {
throw new UnsupportedOperationException("Not supported. Edge is empty.");
}
@Override
public int getEdge() {
throw new UnsupportedOperationException("Not supported. Edge is empty.");
}
@Override
public int getBaseNode() {
throw new UnsupportedOperationException("Not supported. Edge is empty.");
}
@Override
public int getAdjNode() {
throw new UnsupportedOperationException("Not supported. Edge is empty.");
}
@Override
public double getDistance() {
throw new UnsupportedOperationException("Not supported. Edge is empty.");
}
@Override
public long getFlags() {
throw new UnsupportedOperationException("Not supported. Edge is empty.");
}
@Override
public PointList fetchWayGeometry(int type) {
throw new UnsupportedOperationException("Not supported. Edge is empty.");
}
@Override
public EdgeIteratorState setWayGeometry(PointList list) {
throw new UnsupportedOperationException("Not supported. Edge is empty.");
}
@Override
public String getName() {
throw new UnsupportedOperationException("Not supported. Edge is empty.");
}
@Override
public EdgeIteratorState setName(String name) {
throw new UnsupportedOperationException("Not supported. Edge is empty.");
}
@Override
public boolean getBool(int key, boolean _default) {
throw new UnsupportedOperationException("Not supported. Edge is empty.");
}
@Override
public boolean isBackward(FlagEncoder encoder) {
throw new UnsupportedOperationException("Not supported. Edge is empty.");
}
@Override
public boolean isForward(FlagEncoder encoder) {
throw new UnsupportedOperationException("Not supported. Edge is empty.");
}
@Override
public int getAdditionalField() {
throw new UnsupportedOperationException("Not supported. Edge is empty.");
}
@Override
public EdgeIteratorState setAdditionalField(int value) {
throw new UnsupportedOperationException("Not supported. Edge is empty.");
}
@Override
public EdgeIteratorState copyPropertiesTo(EdgeIteratorState edge) {
throw new UnsupportedOperationException("Not supported. Edge is empty.");
}
@Override
public boolean isShortcut() {
return false;
}
@Override
public int getSkippedEdge1() {
throw new UnsupportedOperationException("Not supported. Edge is empty.");
}
@Override
public int getSkippedEdge2() {
throw new UnsupportedOperationException("Not supported. Edge is empty.");
}
@Override
public void setSkippedEdges(int edge1, int edge2) {
throw new UnsupportedOperationException("Not supported. Edge is empty.");
}
@Override
public double getWeight() {
throw new UnsupportedOperationException("Not supported. Edge is empty.");
}
@Override
public CHEdgeIteratorState setWeight(double weight) {
throw new UnsupportedOperationException("Not supported. Edge is empty.");
}
@Override
public int getMergeStatus(long flags) {
throw new UnsupportedOperationException("Not supported. Edge is empty.");
}
}
}