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

com.graphhopper.storage.BaseGraphNodesAndEdges Maven / Gradle / Ivy

Go to download

GraphHopper is a fast and memory efficient Java road routing engine working seamlessly with OpenStreetMap data.

There is a newer version: 10.0
Show newest version
/*
 *  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.storage;

import com.graphhopper.util.*;
import com.graphhopper.util.shapes.BBox;

import java.util.Locale;

import static com.graphhopper.util.EdgeIterator.NO_EDGE;
import static com.graphhopper.util.Helper.nf;

/**
 * Underlying storage for nodes and edges of {@link BaseGraph}. Nodes and edges are stored using two {@link DataAccess}
 * instances. Nodes and edges are simply stored sequentially, see the memory layout in the constructor.
 */
class BaseGraphNodesAndEdges {
    // Currently distances are stored as 4 byte integers. using a conversion factor of 1000 the minimum distance
    // that is not considered zero is 0.0005m (=0.5mm) and the maximum distance per edge is about 2.147.483m=2147km.
    // See OSMReader.addEdge and #1871.
    private static final double INT_DIST_FACTOR = 1000d;
    static double MAX_DIST = Integer.MAX_VALUE / INT_DIST_FACTOR;

    // nodes
    private final DataAccess nodes;
    private final int N_EDGE_REF, N_LAT, N_LON, N_ELE, N_TC;
    private int nodeEntryBytes;
    private int nodeCount;

    // edges
    private final DataAccess edges;
    private final int E_NODEA, E_NODEB, E_LINKA, E_LINKB, E_FLAGS, E_DIST, E_GEO_1, E_GEO_2, E_KV;
    private final int intsForFlags;
    private int edgeEntryBytes;
    private int edgeCount;

    private final boolean withTurnCosts;
    private final boolean withElevation;

    // we do not write the bounding box directly to storage, but rather to this bbox object. we only write to storage
    // when flushing. why? just because we did it like this in the past, and otherwise we run into rounding errors,
    // because of: #2393
    public final BBox bounds;
    private boolean frozen;

    public BaseGraphNodesAndEdges(Directory dir, int intsForFlags, boolean withElevation, boolean withTurnCosts, int segmentSize) {
        nodes = dir.create("nodes", dir.getDefaultType("nodes", true), segmentSize);
        edges = dir.create("edges", dir.getDefaultType("edges", true), segmentSize);
        this.intsForFlags = intsForFlags;
        this.withTurnCosts = withTurnCosts;
        this.withElevation = withElevation;
        bounds = BBox.createInverse(withElevation);

        // memory layout for nodes
        N_EDGE_REF = 0;
        N_LAT = 4;
        N_LON = 8;
        N_ELE = N_LON + (withElevation ? 4 : 0);
        N_TC = N_ELE + (withTurnCosts ? 4 : 0);
        nodeEntryBytes = N_TC + 4;

        // memory layout for edges
        E_NODEA = 0;
        E_NODEB = 4;
        E_LINKA = 8;
        E_LINKB = 12;
        E_FLAGS = 16;
        E_DIST = E_FLAGS + intsForFlags * 4;
        E_GEO_1 = E_DIST + 4;
        E_GEO_2 = E_GEO_1 + 4;
        E_KV = E_GEO_2 + 4;
        edgeEntryBytes = E_KV + 4;
    }

    public void create(long initSize) {
        nodes.create(initSize);
        edges.create(initSize);
    }

    public boolean loadExisting() {
        if (!nodes.loadExisting() || !edges.loadExisting())
            return false;

        // now load some properties from stored data
        final int nodesVersion = nodes.getHeader(0 * 4);
        GHUtility.checkDAVersion("nodes", Constants.VERSION_NODE, nodesVersion);
        nodeEntryBytes = nodes.getHeader(1 * 4);
        nodeCount = nodes.getHeader(2 * 4);
        bounds.minLon = Helper.intToDegree(nodes.getHeader(3 * 4));
        bounds.maxLon = Helper.intToDegree(nodes.getHeader(4 * 4));
        bounds.minLat = Helper.intToDegree(nodes.getHeader(5 * 4));
        bounds.maxLat = Helper.intToDegree(nodes.getHeader(6 * 4));
        boolean hasElevation = nodes.getHeader(7 * 4) == 1;
        if (hasElevation != withElevation)
            // :( we should load data from disk to create objects, not the other way around!
            throw new IllegalStateException("Configured dimension elevation=" + withElevation + " is not equal "
                    + "to dimension of loaded graph elevation =" + hasElevation);
        if (withElevation) {
            bounds.minEle = Helper.intToEle(nodes.getHeader(8 * 4));
            bounds.maxEle = Helper.intToEle(nodes.getHeader(9 * 4));
        }
        frozen = nodes.getHeader(10 * 4) == 1;

        final int edgesVersion = edges.getHeader(0 * 4);
        GHUtility.checkDAVersion("edges", Constants.VERSION_EDGE, edgesVersion);
        edgeEntryBytes = edges.getHeader(1 * 4);
        edgeCount = edges.getHeader(2 * 4);
        return true;
    }

    public void flush() {
        nodes.setHeader(0 * 4, Constants.VERSION_NODE);
        nodes.setHeader(1 * 4, nodeEntryBytes);
        nodes.setHeader(2 * 4, nodeCount);
        nodes.setHeader(3 * 4, Helper.degreeToInt(bounds.minLon));
        nodes.setHeader(4 * 4, Helper.degreeToInt(bounds.maxLon));
        nodes.setHeader(5 * 4, Helper.degreeToInt(bounds.minLat));
        nodes.setHeader(6 * 4, Helper.degreeToInt(bounds.maxLat));
        nodes.setHeader(7 * 4, withElevation ? 1 : 0);
        if (withElevation) {
            nodes.setHeader(8 * 4, Helper.eleToInt(bounds.minEle));
            nodes.setHeader(9 * 4, Helper.eleToInt(bounds.maxEle));
        }
        nodes.setHeader(10 * 4, frozen ? 1 : 0);

        edges.setHeader(0 * 4, Constants.VERSION_EDGE);
        edges.setHeader(1 * 4, edgeEntryBytes);
        edges.setHeader(2 * 4, edgeCount);

        edges.flush();
        nodes.flush();
    }

    public void close() {
        edges.close();
        nodes.close();
    }

    public int getNodes() {
        return nodeCount;
    }

    public int getEdges() {
        return edgeCount;
    }

    public int getIntsForFlags() {
        return intsForFlags;
    }

    public boolean withElevation() {
        return withElevation;
    }

    public boolean withTurnCosts() {
        return withTurnCosts;
    }

    public BBox getBounds() {
        return bounds;
    }

    public long getCapacity() {
        return nodes.getCapacity() + edges.getCapacity();
    }

    public boolean isClosed() {
        assert nodes.isClosed() == edges.isClosed();
        return nodes.isClosed();
    }

    public int edge(int nodeA, int nodeB) {
        if (edgeCount == Integer.MAX_VALUE)
            throw new IllegalStateException("Maximum edge count exceeded: " + edgeCount);
        if (nodeA == nodeB)
            throw new IllegalArgumentException("Loop edges are not supported, got: " + nodeA + " - " + nodeB);
        ensureNodeCapacity(Math.max(nodeA, nodeB));
        final int edge = edgeCount;
        final long edgePointer = (long) edgeCount * edgeEntryBytes;
        edgeCount++;
        edges.ensureCapacity((long) edgeCount * edgeEntryBytes);

        setNodeA(edgePointer, nodeA);
        setNodeB(edgePointer, nodeB);
        // we keep a linked list of edges at each node. here we prepend the new edge at the already existing linked
        // list of edges.
        long nodePointerA = toNodePointer(nodeA);
        int edgeRefA = getEdgeRef(nodePointerA);
        setLinkA(edgePointer, EdgeIterator.Edge.isValid(edgeRefA) ? edgeRefA : NO_EDGE);
        setEdgeRef(nodePointerA, edge);

        if (nodeA != nodeB) {
            long nodePointerB = toNodePointer(nodeB);
            int edgeRefB = getEdgeRef(nodePointerB);
            setLinkB(edgePointer, EdgeIterator.Edge.isValid(edgeRefB) ? edgeRefB : NO_EDGE);
            setEdgeRef(nodePointerB, edge);
        }
        return edge;
    }

    public void ensureNodeCapacity(int node) {
        if (node < nodeCount)
            return;

        int oldNodes = nodeCount;
        nodeCount = node + 1;
        nodes.ensureCapacity((long) nodeCount * nodeEntryBytes);
        for (int n = oldNodes; n < nodeCount; ++n) {
            setEdgeRef(toNodePointer(n), NO_EDGE);
            if (withTurnCosts)
                setTurnCostRef(toNodePointer(n), TurnCostStorage.NO_TURN_ENTRY);
        }
    }

    public long toNodePointer(int node) {
        if (node < 0 || node >= nodeCount)
            throw new IllegalArgumentException("node: " + node + " out of bounds [0," + nodeCount + "[");
        return (long) node * nodeEntryBytes;
    }

    public long toEdgePointer(int edge) {
        if (edge < 0 || edge >= edgeCount)
            throw new IllegalArgumentException("edge: " + edge + " out of bounds [0," + edgeCount + "[");
        return (long) edge * edgeEntryBytes;
    }

    public void readFlags(long edgePointer, IntsRef edgeFlags) {
        int size = edgeFlags.ints.length;
        for (int i = 0; i < size; ++i)
            edgeFlags.ints[i] = getFlagInt(edgePointer, i);
    }

    public void writeFlags(long edgePointer, IntsRef edgeFlags) {
        int size = edgeFlags.ints.length;
        for (int i = 0; i < size; ++i)
            setFlagInt(edgePointer, i, edgeFlags.ints[i]);
    }

    public int getFlagInt(long edgePointer, int index) {
        return edges.getInt(edgePointer + E_FLAGS + index * 4);
    }

    public void setFlagInt(long edgePointer, int index, int value) {
        edges.setInt(edgePointer + E_FLAGS + index * 4, value);
    }

    public void setNodeA(long edgePointer, int nodeA) {
        edges.setInt(edgePointer + E_NODEA, nodeA);
    }

    public void setNodeB(long edgePointer, int nodeB) {
        edges.setInt(edgePointer + E_NODEB, nodeB);
    }

    public void setLinkA(long edgePointer, int linkA) {
        edges.setInt(edgePointer + E_LINKA, linkA);
    }

    public void setLinkB(long edgePointer, int linkB) {
        edges.setInt(edgePointer + E_LINKB, linkB);
    }

    public void setDist(long edgePointer, double distance) {
        edges.setInt(edgePointer + E_DIST, distToInt(distance));
    }

    public void setGeoRef(long edgePointer, long geoRef) {
        int geo1 = BitUtil.LITTLE.getIntLow(geoRef);
        int geo2 = BitUtil.LITTLE.getIntHigh(geoRef);
        edges.setInt(edgePointer + E_GEO_1, geo1);
        edges.setInt(edgePointer + E_GEO_2, geo2);
    }

    public void setKeyValuesRef(long edgePointer, int nameRef) {
        edges.setInt(edgePointer + E_KV, nameRef);
    }

    public int getNodeA(long edgePointer) {
        return edges.getInt(edgePointer + E_NODEA);
    }

    public int getNodeB(long edgePointer) {
        return edges.getInt(edgePointer + E_NODEB);
    }

    public int getLinkA(long edgePointer) {
        return edges.getInt(edgePointer + E_LINKA);
    }

    public int getLinkB(long edgePointer) {
        return edges.getInt(edgePointer + E_LINKB);
    }

    public double getDist(long pointer) {
        int val = edges.getInt(pointer + E_DIST);
        // do never return infinity even if INT MAX, see #435
        return val / INT_DIST_FACTOR;
    }

    public long getGeoRef(long edgePointer) {
        return BitUtil.LITTLE.toLong(
                edges.getInt(edgePointer + E_GEO_1),
                edges.getInt(edgePointer + E_GEO_2)
        );
    }

    public int getKeyValuesRef(long edgePointer) {
        return edges.getInt(edgePointer + E_KV);
    }

    public void setEdgeRef(long nodePointer, int edgeRef) {
        nodes.setInt(nodePointer + N_EDGE_REF, edgeRef);
    }

    public void setLat(long nodePointer, double lat) {
        nodes.setInt(nodePointer + N_LAT, Helper.degreeToInt(lat));
    }

    public void setLon(long nodePointer, double lon) {
        nodes.setInt(nodePointer + N_LON, Helper.degreeToInt(lon));
    }

    public void setEle(long elePointer, double ele) {
        nodes.setInt(elePointer + N_ELE, Helper.eleToInt(ele));
    }

    public void setTurnCostRef(long nodePointer, int tcRef) {
        nodes.setInt(nodePointer + N_TC, tcRef);
    }

    public int getEdgeRef(long nodePointer) {
        return nodes.getInt(nodePointer + N_EDGE_REF);
    }

    public double getLat(long nodePointer) {
        return Helper.intToDegree(nodes.getInt(nodePointer + N_LAT));
    }

    public double getLon(long nodePointer) {
        return Helper.intToDegree(nodes.getInt(nodePointer + N_LON));
    }

    public double getEle(long nodePointer) {
        return Helper.intToEle(nodes.getInt(nodePointer + N_ELE));
    }

    public int getTurnCostRef(long nodePointer) {
        return nodes.getInt(nodePointer + N_TC);
    }

    public void setFrozen(boolean frozen) {
        this.frozen = frozen;
    }

    public boolean getFrozen() {
        return frozen;
    }

    public void debugPrint() {
        final int printMax = 100;
        System.out.println("nodes:");
        String formatNodes = "%12s | %12s | %12s | %12s \n";
        System.out.format(Locale.ROOT, formatNodes, "#", "N_EDGE_REF", "N_LAT", "N_LON");
        for (int i = 0; i < Math.min(nodeCount, printMax); ++i) {
            long nodePointer = toNodePointer(i);
            System.out.format(Locale.ROOT, formatNodes, i, getEdgeRef(nodePointer), getLat(nodePointer), getLon(nodePointer));
        }
        if (nodeCount > printMax) {
            System.out.format(Locale.ROOT, " ... %d more nodes\n", nodeCount - printMax);
        }
        System.out.println("edges:");
        String formatEdges = "%12s | %12s | %12s | %12s | %12s | %12s | %12s \n";
        System.out.format(Locale.ROOT, formatEdges, "#", "E_NODEA", "E_NODEB", "E_LINKA", "E_LINKB", "E_FLAGS", "E_DIST");
        IntsRef intsRef = new IntsRef(intsForFlags);
        for (int i = 0; i < Math.min(edgeCount, printMax); ++i) {
            long edgePointer = toEdgePointer(i);
            readFlags(edgePointer, intsRef);
            System.out.format(Locale.ROOT, formatEdges, i,
                    getNodeA(edgePointer),
                    getNodeB(edgePointer),
                    getLinkA(edgePointer),
                    getLinkB(edgePointer),
                    intsRef,
                    getDist(edgePointer));
        }
        if (edgeCount > printMax) {
            System.out.printf(Locale.ROOT, " ... %d more edges", edgeCount - printMax);
        }
    }

    private int distToInt(double distance) {
        if (distance < 0)
            throw new IllegalArgumentException("Distance cannot be negative: " + distance);
        if (distance > MAX_DIST) {
            distance = MAX_DIST;
        }
        int intDist = (int) Math.round(distance * INT_DIST_FACTOR);
        assert intDist >= 0 : "distance out of range";
        return intDist;
    }

    public String toDetailsString() {
        return "edges: " + nf(edgeCount) + "(" + edges.getCapacity() / Helper.MB + "MB), "
                + "nodes: " + nf(nodeCount) + "(" + nodes.getCapacity() / Helper.MB + "MB), "
                + "bounds: " + bounds;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy