ORSGraphHopperStorage.java

/*  This file is part of Openrouteservice.
 *
 *  Openrouteservice is free software; you can redistribute it and/or modify it under the terms of the
 *  GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1
 *  of the License, or (at your option) any later version.

 *  This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *  See the GNU Lesser General Public License for more details.

 *  You should have received a copy of the GNU Lesser General Public License along with this library;
 *  if not, see <https://www.gnu.org/licenses/>.
 */
package org.heigit.ors.routing.graphhopper.extensions;

import com.graphhopper.routing.util.EncodingManager;
import com.graphhopper.storage.*;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

public class ORSGraphHopperStorage extends GraphHopperStorage {
    private final Collection<CHEntry> coreEntries;

    public ORSGraphHopperStorage(Directory dir, EncodingManager encodingManager, boolean withElevation, boolean withTurnCosts, int segmentSize) {
        super(dir, encodingManager, withElevation, withTurnCosts, segmentSize);
        coreEntries = new ArrayList<>();
    }

    /**
     * Adds a {@link CHStorage} for the given {@link CHConfig}. You need to call this method before calling {@link #create(long)}
     * or {@link #loadExisting()}.
     */
    public ORSGraphHopperStorage addCoreGraph(CHConfig chConfig) {
        if (getCoreConfigs().contains(chConfig))
            throw new IllegalArgumentException("For the given CH profile a CHStorage already exists: '" + chConfig.getName() + "'");
        coreEntries.add(createCHEntry(chConfig));
        return this;
    }

    /**
     * @see #addCHGraph(CHConfig)
     */
    public ORSGraphHopperStorage addCoreGraphs(List<CHConfig> chConfigs) {
        for (CHConfig chConfig : chConfigs) {
            addCoreGraph(chConfig);
        }
        return this;
    }

    /**
     * @return the {@link CHStorage} for the specified profile name, or null if it does not exist
     */
    public CHStorage getCoreStore(String chName) {
        CHEntry chEntry = getCoreEntry(chName);
        return chEntry == null ? null : chEntry.chStore;
    }

    /**
     * @return the {@link RoutingCHGraph} for the specified profile name, or null if it does not exist
     */
    public RoutingCHGraph getCoreGraph(String chName) {
        CHEntry chEntry = getCoreEntry(chName);
        return chEntry == null ? null : chEntry.chGraph;
    }

    public CHEntry getCoreEntry(String chName) {
        for (CHEntry cg : coreEntries) {
            if (cg.chConfig.getName().equals(chName))
                return cg;
        }
        return null;
    }

    public List<String> getCoreGraphNames() {
        return coreEntries.stream().map(ch -> ch.chConfig.getName()).collect(Collectors.toList());
    }

    public List<CHConfig> getCoreConfigs() {
        return coreEntries.stream().map(c -> c.chConfig).collect(Collectors.toList());
    }

    /**
     * After configuring this storage you need to create it explicitly.
     */
    public ORSGraphHopperStorage create(long byteCount) {
        super.create(byteCount);

        coreEntries.forEach(ch -> ch.chStore.create());

        List<CHConfig> coreConfigs = getCoreConfigs();
        List<String> coreProfileNames = new ArrayList<>(coreConfigs.size());
        for (CHConfig chConfig : coreConfigs) {
            coreProfileNames.add(chConfig.getName());
        }
        getProperties().put("graph.core.profiles", coreProfileNames.toString());
        return this;
    }

    @Override
    public void loadExistingORS() {
        coreEntries.forEach(cg -> {
            if (!cg.chStore.loadExisting())
                throw new IllegalStateException("Cannot load " + cg);
        });
        if (getExtensions() != null) {
            getExtensions().loadExisting();
        }
    }

    public void flush() {
        super.flush();
        coreEntries.stream().map(ch -> ch.chStore).filter(s -> !s.isClosed()).forEach(CHStorage::flush);
    }

    @Override
    public void close() {
        super.close();
        coreEntries.stream().map(ch -> ch.chStore).filter(s -> !s.isClosed()).forEach(CHStorage::close);
    }

    @Override
    public long getCapacity() {
        return super.getCapacity() + coreEntries.stream().mapToLong(ch -> ch.chStore.getCapacity()).sum();
    }

    /**
     * Avoid that edges and nodes of the base graph are further modified. Necessary as hook for e.g.
     * ch graphs on top to initialize themselves
     */
    public synchronized void freeze() {
        if (isFrozen())
            return;
        super.freeze();
        coreEntries.forEach(ch -> {
            // we use a rather small value here. this might result in more allocations later, but they should
            // not matter that much. if we expect a too large value the shortcuts DataAccess will end up
            // larger than needed, because we do not do something like trimToSize in the end.
            double expectedShortcuts = 0.3 * getBaseGraph().getEdges();
            ch.chStore.init(getBaseGraph().getNodes(), (int) expectedShortcuts);
        });
    }

    @Override
    public String toDetailsString() {
        StringBuilder str = new StringBuilder(super.toDetailsString());
        for (CHEntry ch : coreEntries) {
            str.append(", ").append(ch.chStore.toDetailsString());
        }

        return str.toString();
    }

    // estimated number of core nodes used for array initialization in Tarjan
    public int getCoreNodes() {
        for (CHEntry cg : coreEntries) {
            if (cg.chGraph.getCoreNodes() == -1) continue;
            return cg.chGraph.getCoreNodes();
        }
        throw new IllegalStateException("No prepared core graph was found");
    }
}