All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.graphhopper.util.GHUtility Maven / Gradle / Ivy

/*
 *  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.IntArrayList;
import com.carrotsearch.hppc.IntIndexedContainer;
import com.graphhopper.coll.GHBitSet;
import com.graphhopper.coll.GHBitSetImpl;
import com.graphhopper.coll.GHTBitSet;
import com.graphhopper.routing.ev.*;
import com.graphhopper.routing.util.*;
import com.graphhopper.routing.weighting.Weighting;
import com.graphhopper.storage.*;
import com.graphhopper.storage.index.LocationIndex;
import com.graphhopper.storage.index.Snap;
import com.graphhopper.util.shapes.BBox;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

import static com.graphhopper.util.DistanceCalcEarth.DIST_EARTH;

/**
 * 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.
 *
 * @author Peter Karich
 */
public class GHUtility {
    private static final Logger LOGGER = LoggerFactory.getLogger(GHUtility.class);

    /**
     * This method could throw an exception if problems like index out of bounds etc
     */
    public static List getProblems(Graph g) {
        List problems = new ArrayList<>();
        int nodes = g.getNodes();
        int nodeIndex = 0;
        NodeAccess na = g.getNodeAccess();
        try {
            EdgeExplorer explorer = g.createEdgeExplorer();
            for (; nodeIndex < nodes; nodeIndex++) {
                double lat = na.getLat(nodeIndex);
                if (lat > 90 || lat < -90)
                    problems.add("latitude is not within its bounds " + lat);

                double lon = na.getLon(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 asSet(int... values) {
        Set s = new HashSet<>();
        for (int v : values) {
            s.add(v);
        }
        return s;
    }

    public static Set getNeighbors(EdgeIterator iter) {
        // make iteration order over set static => linked
        Set list = new LinkedHashSet<>();
        while (iter.next()) {
            list.add(iter.getAdjNode());
        }
        return list;
    }

    public static List getEdgeIds(EdgeIterator iter) {
        List list = new ArrayList<>();
        while (iter.next()) {
            list.add(iter.getEdge());
        }
        return list;
    }

    public static void printEdgeInfo(final Graph g, FlagEncoder encoder) {
        System.out.println("-- Graph nodes:" + g.getNodes() + " edges:" + g.getEdges() + " ---");
        AllEdgesIterator iter = g.getAllEdges();
        BooleanEncodedValue accessEnc = encoder.getAccessEnc();
        while (iter.next()) {
            String prefix = (iter instanceof AllCHEdgesIterator && ((AllCHEdgesIterator) iter).isShortcut()) ? "sc" : "  ";
            String fwdStr = iter.get(accessEnc) ? "fwd" : "   ";
            String bwdStr = iter.getReverse(accessEnc) ? "bwd" : "   ";
            System.out.println(prefix + " " + iter + " " + fwdStr + " " + bwdStr + " " + iter.getDistance());
        }
    }

    public static void printGraphForUnitTest(Graph g, FlagEncoder encoder) {
        printGraphForUnitTest(g, encoder, new BBox(
                Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY));
    }

    public static void printGraphForUnitTest(Graph g, FlagEncoder encoder, BBox bBox) {
        System.out.println("WARNING: printGraphForUnitTest does not pay attention to custom edge speeds at the moment");
        NodeAccess na = g.getNodeAccess();
        for (int node = 0; node < g.getNodes(); ++node) {
            if (bBox.contains(na.getLat(node), na.getLon(node))) {
                System.out.printf(Locale.ROOT, "na.setNode(%d, %f, %f);\n", node, na.getLat(node), na.getLon(node));
            }
        }
        AllEdgesIterator iter = g.getAllEdges();
        while (iter.next()) {
            if (bBox.contains(na.getLat(iter.getBaseNode()), na.getLon(iter.getBaseNode())) &&
                    bBox.contains(na.getLat(iter.getAdjNode()), na.getLon(iter.getAdjNode()))) {
                printUnitTestEdge(encoder, iter);
            }
        }
    }

    private static void printUnitTestEdge(FlagEncoder encoder, EdgeIteratorState edge) {
        BooleanEncodedValue accessEnc = encoder.getAccessEnc();
        boolean fwd = edge.get(accessEnc);
        boolean bwd = edge.getReverse(accessEnc);
        if (!fwd && !bwd) {
            return;
        }
        int from = fwd ? edge.getBaseNode() : edge.getAdjNode();
        int to = fwd ? edge.getAdjNode() : edge.getBaseNode();
        System.out.printf(Locale.ROOT,
                "graph.edge(%d, %d, %f, %s); // edgeId=%s\n", from, to, edge.getDistance(), fwd && bwd ? "true" : "false", edge.getEdge());
    }

    /**
     * @param speed if null a random speed will be assign to every edge
     */
    public static void buildRandomGraph(Graph graph, Random random, int numNodes, double meanDegree, boolean allowLoops,
                                        boolean allowZeroDistance, BooleanEncodedValue accessEnc, DecimalEncodedValue speedEnc, Double speed,
                                        double pNonZeroLoop, double pBothDir, double pRandomDistanceOffset) {
        if (numNodes < 2 || meanDegree < 1) {
            throw new IllegalArgumentException("numNodes must be >= 2, meanDegree >= 1");
        }
        for (int i = 0; i < numNodes; ++i) {
            double lat = 49.4 + (random.nextDouble() * 0.01);
            double lon = 9.7 + (random.nextDouble() * 0.01);
            graph.getNodeAccess().setNode(i, lat, lon);
        }
        double minDist = Double.MAX_VALUE;
        double maxDist = Double.MIN_VALUE;
        int totalNumEdges = (int) (0.5 * meanDegree * numNodes);
        int numEdges = 0;
        while (numEdges < totalNumEdges) {
            int from = random.nextInt(numNodes);
            int to = random.nextInt(numNodes);
            if (!allowLoops && from == to) {
                continue;
            }
            double distance = GHUtility.getDistance(from, to, graph.getNodeAccess());
            // allow loops with non-zero distance
            if (from == to && random.nextDouble() < pNonZeroLoop) {
                distance = random.nextDouble() * 1000;
            }
            if (!allowZeroDistance) {
                distance = Math.max(0.001, distance);
            }
            // add some random offset, but also allow duplicate edges with same weight
            if (random.nextDouble() < pRandomDistanceOffset)
                distance += random.nextDouble() * distance * 0.01;
            minDist = Math.min(minDist, distance);
            maxDist = Math.max(maxDist, distance);
            // using bidirectional edges will increase mean degree of graph above given value
            boolean bothDirections = random.nextDouble() < pBothDir;
            EdgeIteratorState edge = graph.edge(from, to).setDistance(distance).set(accessEnc, true);
            if (bothDirections) edge.setReverse(accessEnc, true);
            double fwdSpeed = 10 + random.nextDouble() * 110;
            double bwdSpeed = 10 + random.nextDouble() * 110;
            // if an explicit speed is given we discard the random speeds and use the given one instead
            if (speed != null) {
                fwdSpeed = bwdSpeed = speed;
            }
            edge.set(speedEnc, fwdSpeed);
            if (speedEnc.isStoreTwoDirections())
                edge.setReverse(speedEnc, bwdSpeed);
            numEdges++;
        }
        LOGGER.debug(String.format(Locale.ROOT, "Finished building random graph" +
                        ", nodes: %d, edges: %d , min distance: %.2f, max distance: %.2f\n",
                graph.getNodes(), graph.getEdges(), minDist, maxDist));
    }

    public static double getDistance(int from, int to, NodeAccess nodeAccess) {
        double fromLat = nodeAccess.getLat(from);
        double fromLon = nodeAccess.getLon(from);
        double toLat = nodeAccess.getLat(to);
        double toLon = nodeAccess.getLon(to);
        return DistancePlaneProjection.DIST_PLANE.calcDist(fromLat, fromLon, toLat, toLon);
    }

    public static void addRandomTurnCosts(Graph graph, long seed, EncodingManager em, FlagEncoder encoder, int maxTurnCost, TurnCostStorage turnCostStorage) {
        Random random = new Random(seed);
        double pNodeHasTurnCosts = 0.3;
        double pEdgePairHasTurnCosts = 0.6;
        double pCostIsRestriction = 0.1;

        DecimalEncodedValue turnCostEnc = em.getDecimalEncodedValue(TurnCost.key(encoder.toString()));
        EdgeExplorer inExplorer = graph.createEdgeExplorer(AccessFilter.inEdges(encoder.getAccessEnc()));
        EdgeExplorer outExplorer = graph.createEdgeExplorer(AccessFilter.outEdges(encoder.getAccessEnc()));
        for (int node = 0; node < graph.getNodes(); ++node) {
            if (random.nextDouble() < pNodeHasTurnCosts) {
                EdgeIterator inIter = inExplorer.setBaseNode(node);
                while (inIter.next()) {
                    EdgeIterator outIter = outExplorer.setBaseNode(node);
                    while (outIter.next()) {
                        if (inIter.getEdge() == outIter.getEdge()) {
                            // leave u-turns as they are
                            continue;
                        }
                        if (random.nextDouble() < pEdgePairHasTurnCosts) {
                            double cost = random.nextDouble() < pCostIsRestriction ? Double.POSITIVE_INFINITY : random.nextDouble() * maxTurnCost;
                            turnCostStorage.set(turnCostEnc, inIter.getEdge(), node, outIter.getEdge(), cost);
                        }
                    }
                }
            }
        }
    }

    public static List createRandomSnaps(BBox bbox, LocationIndex locationIndex, Random rnd, int numPoints, boolean acceptTower, EdgeFilter filter) {
        int maxTries = numPoints * 100;
        int tries = 0;
        List snaps = new ArrayList<>(numPoints);
        while (snaps.size() < numPoints) {
            if (tries > maxTries)
                throw new IllegalArgumentException("Could not create " + numPoints + " random points. tries: " + tries + ", maxTries: " + maxTries);
            Snap snap = getRandomSnap(locationIndex, rnd, bbox, filter);
            boolean accepted = snap.isValid();
            if (!acceptTower)
                accepted = accepted && !snap.getSnappedPosition().equals(Snap.Position.TOWER);
            if (accepted)
                snaps.add(snap);
            tries++;
        }
        return snaps;
    }

    public static Snap getRandomSnap(LocationIndex locationIndex, Random rnd, BBox bbox, EdgeFilter filter) {
        return locationIndex.findClosest(
                randomDoubleInRange(rnd, bbox.minLat, bbox.maxLat),
                randomDoubleInRange(rnd, bbox.minLon, bbox.maxLon),
                filter
        );
    }

    public static double randomDoubleInRange(Random rnd, double min, double max) {
        return min + rnd.nextDouble() * (max - min);
    }

    public static void printInfo(final Graph g, int startNode, final int counts, final EdgeFilter filter) {
        new BreadthFirstSearch() {
            int counter = 0;

            @Override
            protected GHBitSet createBitSet() {
                return new GHTBitSet();
            }

            @Override
            protected boolean goFurther(int nodeId) {
                System.out.println(getNodeInfo(g, nodeId, filter));
                return counter++ <= counts;
            }
        }.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.getBaseGraph().getNodeAccess();
        String str = nodeId + ":" + na.getLat(nodeId) + "," + na.getLon(nodeId) + "\n";
        while (iter.next()) {
            str += "  ->" + iter.getAdjNode() + "(" + iter.getSkippedEdge1() + "," + iter.getSkippedEdge2() + ") "
                    + iter.getEdge() + " \t" + BitUtil.BIG.toBitString(iter.getFlags().ints[0], 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.getLat(nodeId) + "," + na.getLon(nodeId) + "\n";
        while (iter.next()) {
            str += "  ->" + iter.getAdjNode() + " (" + iter.getDistance() + ") pillars:"
                    + iter.fetchWayGeometry(FetchMode.PILLAR_ONLY).getSize() + ", edgeId:" + iter.getEdge()
                    + "\t" + BitUtil.BIG.toBitString(iter.getFlags().ints[0], 8) + "\n";
        }
        return str;
    }

    public static Graph shuffle(Graph g, Graph sortedGraph) {
        if (g.getTurnCostStorage() != null)
            throw new IllegalArgumentException("Shuffling the graph is currently not supported in the presence of turn costs");
        IntArrayList nodes = ArrayUtil.permutation(g.getNodes(), new Random());
        IntArrayList edges = ArrayUtil.permutation(g.getEdges(), new Random());
        return createSortedGraph(g, sortedGraph, nodes, edges);
    }

    /**
     * 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) {
        if (g.getTurnCostStorage() != null) {
            throw new IllegalArgumentException("Sorting the graph is currently not supported in the presence of turn costs");
        }
        int nodes = g.getNodes();
        final IntArrayList nodeList = ArrayUtil.constant(nodes, -1);
        final GHBitSetImpl nodeBitset = new GHBitSetImpl(nodes);
        final AtomicInteger nodeRef = new AtomicInteger(-1);

        int edges = g.getEdges();
        final IntArrayList edgeList = ArrayUtil.constant(edges, -1);
        final GHBitSetImpl edgeBitset = new GHBitSetImpl(edges);
        final AtomicInteger edgeRef = new AtomicInteger(-1);

        EdgeExplorer explorer = g.createEdgeExplorer();
        for (int startNode = 0; startNode >= 0 && startNode < nodes;
             startNode = nodeBitset.nextClear(startNode + 1)) {
            new DepthFirstSearch() {
                @Override
                protected GHBitSet createBitSet() {
                    return nodeBitset;
                }

                @Override
                protected boolean checkAdjacent(EdgeIteratorState edge) {
                    int edgeId = edge.getEdge();
                    if (!edgeBitset.contains(edgeId)) {
                        edgeBitset.add(edgeId);
                        edgeList.set(edgeRef.incrementAndGet(), edgeId);
                    }
                    return super.checkAdjacent(edge);
                }

                @Override
                protected boolean goFurther(int nodeId) {
                    nodeList.set(nodeId, nodeRef.incrementAndGet());
                    return super.goFurther(nodeId);
                }
            }.start(explorer, startNode);
        }
        return createSortedGraph(g, sortedGraph, nodeList, edgeList);
    }

    static Graph createSortedGraph(Graph fromGraph, Graph toSortedGraph, final IntIndexedContainer oldToNewNodeList, final IntIndexedContainer newToOldEdgeList) {
        if (fromGraph.getTurnCostStorage() != null) {
            throw new IllegalArgumentException("Sorting the graph is currently not supported in the presence of turn costs");
        }
        int edges = fromGraph.getEdges();
        for (int i = 0; i < edges; i++) {
            int edgeId = newToOldEdgeList.get(i);
            if (edgeId < 0)
                continue;

            EdgeIteratorState eIter = fromGraph.getEdgeIteratorState(edgeId, Integer.MIN_VALUE);

            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;

            toSortedGraph.edge(newBaseIndex, newAdjIndex).copyPropertiesFrom(eIter);
        }

        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.getLat(old), na.getLon(old), na.getEle(old));
            else
                sna.setNode(newIndex, na.getLat(old), na.getLon(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) {
        if (fromGraph.getTurnCostStorage() != null) {
            throw new IllegalArgumentException("Copying a graph is currently not supported in the presence of turn costs");
        }
        AllEdgesIterator eIter = fromGraph.getAllEdges();
        while (eIter.next()) {
            int base = eIter.getBaseNode();
            int adj = eIter.getAdjNode();
            toGraph.edge(base, adj).copyPropertiesFrom(eIter);
        }

        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.getLat(node), fna.getLon(node), fna.getEle(node));
            else
                tna.setNode(node, fna.getLat(node), fna.getLon(node));
        }
        return toGraph;
    }

    static Directory guessDirectory(GraphStorage store) {
        if (store.getDirectory() instanceof MMapDirectory) {
            throw new IllegalStateException("not supported yet: mmap will overwrite existing storage at the same location");
        }
        String location = store.getDirectory().getLocation();
        boolean isStoring = ((GHDirectory) store.getDirectory()).isStoring();
        return new RAMDirectory(location, isStoring);
    }

    /**
     * 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 GraphBuilder(store.getEncodingManager())
                .withTurnCosts(store.getTurnCostStorage() != null)
                .set3D(is3D)
                .setDir(outdir)
                .setCHConfigs(store.getCHConfigs())
                .create();
    }

    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 IntsRef flags) {
        return createMockedEdgeIteratorState(distance, flags, 0, 1, 2, 3, 4);
    }

    public static EdgeIteratorState createMockedEdgeIteratorState(final double distance, final IntsRef flags,
                                                                  final int base, final int adj, final int edge, final int origFirst, final int origLast) {
        return new GHUtility.DisabledEdgeIterator() {
            @Override
            public double getDistance() {
                return distance;
            }

            @Override
            public IntsRef getFlags() {
                return flags;
            }

            @Override
            public boolean get(BooleanEncodedValue property) {
                return property.getBool(false, flags);
            }

            @Override
            public boolean getReverse(BooleanEncodedValue property) {
                return property.getBool(true, flags);
            }

            @Override
            public double get(DecimalEncodedValue property) {
                return property.getDecimal(false, flags);
            }

            @Override
            public double getReverse(DecimalEncodedValue property) {
                return property.getDecimal(true, flags);
            }

            @Override
            public > T get(EnumEncodedValue property) {
                return property.getEnum(false, flags);
            }

            @Override
            public > T getReverse(EnumEncodedValue property) {
                return property.getEnum(true, flags);
            }

            @Override
            public int getEdge() {
                return edge;
            }

            @Override
            public int getBaseNode() {
                return base;
            }

            @Override
            public int getAdjNode() {
                return adj;
            }

            @Override
            public PointList fetchWayGeometry(FetchMode type) {
                return Helper.createPointList(0, 2, 6, 4);
            }

            @Override
            public int getOrigEdgeFirst() {
                return origFirst;
            }

            @Override
            public int getOrigEdgeLast() {
                return origLast;
            }
        };
    }

    /**
     * @return the first 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;
    }

    public static CHEdgeIteratorState getEdge(CHGraph graph, int base, int adj) {
        CHEdgeIterator 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;
    }

    /**
     * Creates an edge key, i.e. an integer number that encodes an edge ID and the direction of an edge
     */
    public static int createEdgeKey(int edgeId, boolean reverse) {
        // edge state in storage direction -> edge key is even
        // edge state against storage direction -> edge key is odd
        return (edgeId << 1) + (reverse ? 1 : 0);
    }

    /**
     * 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;
    }

    public static IntsRef setSpeed(double fwdSpeed, double bwdSpeed, FlagEncoder encoder, IntsRef edgeFlags) {
        if (fwdSpeed < 0 || bwdSpeed < 0)
            throw new IllegalArgumentException("Speed must be positive but wasn't! fwdSpeed:" + fwdSpeed + ", bwdSpeed:" + bwdSpeed);

        BooleanEncodedValue accessEnc = encoder.getAccessEnc();
        DecimalEncodedValue avgSpeedEnc = encoder.getAverageSpeedEnc();

        avgSpeedEnc.setDecimal(false, edgeFlags, fwdSpeed);
        if (fwdSpeed > 0)
            accessEnc.setBool(false, edgeFlags, true);

        if (bwdSpeed > 0 && (fwdSpeed != bwdSpeed || avgSpeedEnc.isStoreTwoDirections())) {
            if (!avgSpeedEnc.isStoreTwoDirections())
                throw new IllegalArgumentException("EncodedValue " + avgSpeedEnc.getName() + " supports only one direction " +
                        "but two different speeds were specified " + fwdSpeed + " " + bwdSpeed);
            avgSpeedEnc.setDecimal(true, edgeFlags, bwdSpeed);
        }
        if (bwdSpeed > 0)
            accessEnc.setBool(true, edgeFlags, true);
        return edgeFlags;
    }

    public static void setSpeed(double fwdSpeed, double bwdSpeed, FlagEncoder encoder, EdgeIteratorState... edges) {
        setSpeed(fwdSpeed, bwdSpeed, encoder, Arrays.asList(edges));
    }

    public static void setSpeed(double fwdSpeed, double bwdSpeed, FlagEncoder encoder, Collection edges) {
        if (fwdSpeed < 0 || bwdSpeed < 0)
            throw new IllegalArgumentException("Speed must be positive but wasn't! fwdSpeed:" + fwdSpeed + ", bwdSpeed:" + bwdSpeed);
        BooleanEncodedValue accessEnc = encoder.getAccessEnc();
        DecimalEncodedValue avgSpeedEnc = encoder.getAverageSpeedEnc();
        for (EdgeIteratorState edge : edges) {
            edge.set(avgSpeedEnc, fwdSpeed);
            if (fwdSpeed > 0)
                edge.set(accessEnc, true);

            if (bwdSpeed > 0 && (fwdSpeed != bwdSpeed || avgSpeedEnc.isStoreTwoDirections())) {
                if (!avgSpeedEnc.isStoreTwoDirections())
                    throw new IllegalArgumentException("EncodedValue " + avgSpeedEnc.getName() + " supports only one direction " +
                            "but two different speeds were specified " + fwdSpeed + " " + bwdSpeed);
                edge.setReverse(avgSpeedEnc, bwdSpeed);
            }
            if (bwdSpeed > 0)
                edge.setReverse(accessEnc, true);
        }
    }

    public static EdgeIteratorState setSpeed(double averageSpeed, boolean fwd, boolean bwd, FlagEncoder encoder, EdgeIteratorState edge) {
        if (averageSpeed < 0.0001 && (fwd || bwd))
            throw new IllegalStateException("Zero speed is only allowed if edge will get inaccessible. Otherwise Weighting can produce inconsistent results");

        BooleanEncodedValue accessEnc = encoder.getAccessEnc();
        DecimalEncodedValue avSpeedEnc = encoder.getAverageSpeedEnc();
        edge.set(accessEnc, fwd, bwd);
        if (fwd)
            edge.set(avSpeedEnc, averageSpeed);
        if (bwd && avSpeedEnc.isStoreTwoDirections())
            edge.setReverse(avSpeedEnc, averageSpeed);
        return edge;
    }

    public static void updateDistancesFor(Graph g, int node, double lat, double lon) {
        NodeAccess na = g.getNodeAccess();
        na.setNode(node, lat, lon);
        EdgeIterator iter = g.createEdgeExplorer().setBaseNode(node);
        while (iter.next()) {
            iter.setDistance(DIST_EARTH.calcDistance(iter.fetchWayGeometry(FetchMode.ALL)));
            // System.out.println(node + "->" + adj + ": " + iter.getDistance());
        }
    }

    public static double calcWeightWithTurnWeightWithAccess(Weighting weighting, EdgeIteratorState edgeState, boolean reverse, int prevOrNextEdgeId) {
        BooleanEncodedValue accessEnc = weighting.getFlagEncoder().getAccessEnc();
        if (edgeState.getBaseNode() == edgeState.getAdjNode()) {
            if (!edgeState.get(accessEnc) && !edgeState.getReverse(accessEnc))
                return Double.POSITIVE_INFINITY;
        } else if ((!reverse && !edgeState.get(accessEnc)) || (reverse && !edgeState.getReverse(accessEnc))) {
            return Double.POSITIVE_INFINITY;
        }
        return calcWeightWithTurnWeight(weighting, edgeState, reverse, prevOrNextEdgeId);
    }

    /**
     * Calculates the weight of a given edge like {@link Weighting#calcEdgeWeight} and adds the transition
     * cost (the turn weight, {@link Weighting#calcTurnWeight}) associated with transitioning from/to the edge with ID prevOrNextEdgeId.
     *
     * @param prevOrNextEdgeId if reverse is false this has to be the previous edgeId, if true it
     *                         has to be the next edgeId in the direction from start to end.
     */
    public static double calcWeightWithTurnWeight(Weighting weighting, EdgeIteratorState edgeState, boolean reverse, int prevOrNextEdgeId) {
        final double edgeWeight = weighting.calcEdgeWeight(edgeState, reverse);
        if (!EdgeIterator.Edge.isValid(prevOrNextEdgeId)) {
            return edgeWeight;
        }
        final int origEdgeId = reverse ? edgeState.getOrigEdgeLast() : edgeState.getOrigEdgeFirst();
        double turnWeight = reverse
                ? weighting.calcTurnWeight(origEdgeId, edgeState.getBaseNode(), prevOrNextEdgeId)
                : weighting.calcTurnWeight(prevOrNextEdgeId, edgeState.getBaseNode(), origEdgeId);
        return edgeWeight + turnWeight;
    }

    /**
     * @see #calcWeightWithTurnWeight(Weighting, EdgeIteratorState, boolean, int)
     */
    public static long calcMillisWithTurnMillis(Weighting weighting, EdgeIteratorState edgeState, boolean reverse, int prevOrNextEdgeId) {
        long edgeMillis = weighting.calcEdgeMillis(edgeState, reverse);
        if (!EdgeIterator.Edge.isValid(prevOrNextEdgeId)) {
            return edgeMillis;
        }
        // should we also separate weighting vs. time for turn? E.g. a fast but dangerous turn - is this common?
        // todo: why no first/last orig edge here as in calcWeight ?
//        final int origEdgeId = reverse ? edgeState.getOrigEdgeLast() : edgeState.getOrigEdgeFirst();
        final int origEdgeId = edgeState.getEdge();
        long turnMillis = reverse
                ? weighting.calcTurnMillis(origEdgeId, edgeState.getBaseNode(), prevOrNextEdgeId)
                : weighting.calcTurnMillis(prevOrNextEdgeId, edgeState.getBaseNode(), origEdgeId);
        return edgeMillis + turnMillis;
    }

    /**
     * This edge iterator can be used in tests to mock specific iterator behaviour via overloading
     * certain methods.
     */
    public static class DisabledEdgeIterator implements EdgeIterator {
        @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(IntsRef 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 getEdgeKey() {
            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 IntsRef getFlags() {
            throw new UnsupportedOperationException("Not supported. Edge is empty.");
        }

        @Override
        public PointList fetchWayGeometry(FetchMode 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 get(BooleanEncodedValue property) {
            throw new UnsupportedOperationException("Not supported. Edge is empty.");
        }

        @Override
        public EdgeIteratorState set(BooleanEncodedValue property, boolean value) {
            throw new UnsupportedOperationException("Not supported. Edge is empty.");
        }

        @Override
        public boolean getReverse(BooleanEncodedValue property) {
            throw new UnsupportedOperationException("Not supported. Edge is empty.");
        }

        @Override
        public EdgeIteratorState setReverse(BooleanEncodedValue property, boolean value) {
            throw new UnsupportedOperationException("Not supported. Edge is empty.");
        }

        @Override
        public EdgeIteratorState set(BooleanEncodedValue property, boolean fwd, boolean bwd) {
            throw new UnsupportedOperationException("Not supported. Edge is empty.");
        }

        @Override
        public int get(IntEncodedValue property) {
            throw new UnsupportedOperationException("Not supported. Edge is empty.");
        }

        @Override
        public EdgeIteratorState set(IntEncodedValue property, int value) {
            throw new UnsupportedOperationException("Not supported. Edge is empty.");
        }

        @Override
        public int getReverse(IntEncodedValue property) {
            throw new UnsupportedOperationException("Not supported. Edge is empty.");
        }

        @Override
        public EdgeIteratorState setReverse(IntEncodedValue property, int value) {
            throw new UnsupportedOperationException("Not supported. Edge is empty.");
        }

        @Override
        public EdgeIteratorState set(IntEncodedValue property, int fwd, int bwd) {
            throw new UnsupportedOperationException("Not supported. Edge is empty.");
        }

        @Override
        public double get(DecimalEncodedValue property) {
            throw new UnsupportedOperationException("Not supported. Edge is empty.");
        }

        @Override
        public EdgeIteratorState set(DecimalEncodedValue property, double value) {
            throw new UnsupportedOperationException("Not supported. Edge is empty.");
        }

        @Override
        public double getReverse(DecimalEncodedValue property) {
            throw new UnsupportedOperationException("Not supported. Edge is empty.");
        }

        @Override
        public EdgeIteratorState setReverse(DecimalEncodedValue property, double value) {
            throw new UnsupportedOperationException("Not supported. Edge is empty.");
        }

        @Override
        public EdgeIteratorState set(DecimalEncodedValue property, double fwd, double bwd) {
            throw new UnsupportedOperationException("Not supported. Edge is empty.");
        }

        @Override
        public > T get(EnumEncodedValue property) {
            throw new UnsupportedOperationException("Not supported. Edge is empty.");
        }

        @Override
        public > EdgeIteratorState set(EnumEncodedValue property, T value) {
            throw new UnsupportedOperationException("Not supported. Edge is empty.");
        }

        @Override
        public > T getReverse(EnumEncodedValue property) {
            throw new UnsupportedOperationException("Not supported. Edge is empty.");
        }

        @Override
        public > EdgeIteratorState setReverse(EnumEncodedValue property, T value) {
            throw new UnsupportedOperationException("Not supported. Edge is empty.");
        }

        @Override
        public > EdgeIteratorState set(EnumEncodedValue property, T fwd, T bwd) {
            throw new UnsupportedOperationException("Not supported. Edge is empty.");
        }

        @Override
        public String get(StringEncodedValue property) {
            throw new UnsupportedOperationException("Not supported. Edge is empty.");
        }

        @Override
        public EdgeIteratorState set(StringEncodedValue property, String value) {
            throw new UnsupportedOperationException("Not supported. Edge is empty.");
        }

        @Override
        public String getReverse(StringEncodedValue property) {
            throw new UnsupportedOperationException("Not supported. Edge is empty.");
        }

        @Override
        public EdgeIteratorState setReverse(StringEncodedValue property, String value) {
            throw new UnsupportedOperationException("Not supported. Edge is empty.");
        }

        @Override
        public EdgeIteratorState set(StringEncodedValue property, String fwd, String bwd) {
            throw new UnsupportedOperationException("Not supported. Edge is empty.");
        }

        @Override
        public EdgeIteratorState copyPropertiesFrom(EdgeIteratorState edge) {
            throw new UnsupportedOperationException("Not supported. Edge is empty.");
        }

        @Override
        public int getOrigEdgeFirst() {
            throw new UnsupportedOperationException("Not supported. Edge is empty.");
        }

        @Override
        public int getOrigEdgeLast() {
            throw new UnsupportedOperationException("Not supported. Edge is empty.");
        }

    }

    /**
     * This node access can be used in tests to mock specific iterator behaviour via overloading
     * certain methods.
     */
    public static class DisabledNodeAccess implements NodeAccess {

        @Override
        public int getTurnCostIndex(int nodeId) {
            throw new UnsupportedOperationException("Not supported.");
        }

        @Override
        public void setTurnCostIndex(int nodeId, int additionalValue) {
            throw new UnsupportedOperationException("Not supported.");
        }

        @Override
        public boolean is3D() {
            throw new UnsupportedOperationException("Not supported.");
        }

        @Override
        public int getDimension() {
            throw new UnsupportedOperationException("Not supported.");
        }

        @Override
        public void ensureNode(int nodeId) {
            throw new UnsupportedOperationException("Not supported.");
        }

        @Override
        public void setNode(int nodeId, double lat, double lon, double ele) {
            throw new UnsupportedOperationException("Not supported.");
        }

        public double getLat(int nodeId) {
            throw new UnsupportedOperationException("Not supported.");
        }

        @Override
        public double getLon(int nodeId) {
            throw new UnsupportedOperationException("Not supported.");
        }

        @Override
        public double getEle(int nodeId) {
            throw new UnsupportedOperationException("Not supported.");
        }
    }

    public static BBox createBBox(EdgeIteratorState edgeState) {
        PointList towerNodes = edgeState.fetchWayGeometry(FetchMode.TOWER_ONLY);
        int secondIndex = towerNodes.getSize() == 1 ? 0 : 1;
        return BBox.fromPoints(towerNodes.getLat(0), towerNodes.getLon(0),
                towerNodes.getLat(secondIndex), towerNodes.getLon(secondIndex));
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy