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

com.graphhopper.GraphHopper 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;

import com.bedatadriven.jackson.datatype.jts.JtsModule;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.graphhopper.config.CHProfile;
import com.graphhopper.config.LMProfile;
import com.graphhopper.config.Profile;
import com.graphhopper.jackson.Jackson;
import com.graphhopper.reader.dem.*;
import com.graphhopper.reader.osm.OSMReader;
import com.graphhopper.reader.osm.RestrictionTagParser;
import com.graphhopper.reader.osm.conditional.DateRangeParser;
import com.graphhopper.routing.*;
import com.graphhopper.routing.ch.CHPreparationHandler;
import com.graphhopper.routing.ch.PrepareContractionHierarchies;
import com.graphhopper.routing.ev.*;
import com.graphhopper.routing.lm.LMConfig;
import com.graphhopper.routing.lm.LMPreparationHandler;
import com.graphhopper.routing.lm.LandmarkStorage;
import com.graphhopper.routing.lm.PrepareLandmarks;
import com.graphhopper.routing.subnetwork.PrepareRoutingSubnetworks;
import com.graphhopper.routing.subnetwork.PrepareRoutingSubnetworks.PrepareJob;
import com.graphhopper.routing.util.*;
import com.graphhopper.routing.util.countryrules.CountryRuleFactory;
import com.graphhopper.routing.util.parsers.OSMBikeNetworkTagParser;
import com.graphhopper.routing.util.parsers.OSMFootNetworkTagParser;
import com.graphhopper.routing.util.parsers.OSMMtbNetworkTagParser;
import com.graphhopper.routing.util.parsers.TagParser;
import com.graphhopper.routing.weighting.Weighting;
import com.graphhopper.routing.weighting.custom.CustomModelParser;
import com.graphhopper.routing.weighting.custom.CustomWeighting;
import com.graphhopper.routing.weighting.custom.NameValidator;
import com.graphhopper.storage.*;
import com.graphhopper.storage.index.LocationIndex;
import com.graphhopper.storage.index.LocationIndexTree;
import com.graphhopper.util.*;
import com.graphhopper.util.Parameters.Landmark;
import com.graphhopper.util.Parameters.Routing;
import com.graphhopper.util.details.PathDetailsBuilderFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.DateFormat;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.graphhopper.util.GHUtility.readCountries;
import static com.graphhopper.util.Helper.*;
import static com.graphhopper.util.Parameters.Algorithms.RoundTrip;

/**
 * Easy to use access point to configure import and (offline) routing.
 *
 * @author Peter Karich
 */
public class GraphHopper {
    private static final Logger logger = LoggerFactory.getLogger(GraphHopper.class);
    private MaxSpeedCalculator maxSpeedCalculator;
    private final Map profilesByName = new LinkedHashMap<>();
    private final String fileLockName = "gh.lock";
    // utils
    private final TranslationMap trMap = new TranslationMap().doImport();
    boolean removeZipped = true;
    boolean calcChecksums = false;
    // for country rules:
    private CountryRuleFactory countryRuleFactory = null;
    // for custom areas:
    private String customAreasDirectory = "";
    // for graph:
    private BaseGraph baseGraph;
    private StorableProperties properties;
    protected EncodingManager encodingManager;
    private OSMParsers osmParsers;
    private int defaultSegmentSize = -1;
    private String ghLocation = "";
    private DAType dataAccessDefaultType = DAType.RAM_STORE;
    private final LinkedHashMap dataAccessConfig = new LinkedHashMap<>();
    private boolean elevation = false;
    private LockFactory lockFactory = new NativeFSLockFactory();
    private boolean allowWrites = true;
    private boolean fullyLoaded = false;
    private final OSMReaderConfig osmReaderConfig = new OSMReaderConfig();
    // for routing
    private final RouterConfig routerConfig = new RouterConfig();
    // for index
    private LocationIndex locationIndex;
    private int preciseIndexResolution = 300;
    private int maxRegionSearch = 4;
    // subnetworks
    private int minNetworkSize = 200;
    private int subnetworksThreads = 1;
    // residential areas
    private double residentialAreaRadius = 400;
    private double residentialAreaSensitivity = 6000;
    private double cityAreaRadius = 1500;
    private double cityAreaSensitivity = 1000;
    private int urbanDensityCalculationThreads = 0;

    // preparation handlers
    private final LMPreparationHandler lmPreparationHandler = new LMPreparationHandler();
    private final CHPreparationHandler chPreparationHandler = new CHPreparationHandler();
    private Map chGraphs = Collections.emptyMap();
    private Map landmarks = Collections.emptyMap();

    // for data reader
    private String osmFile;
    private ElevationProvider eleProvider = ElevationProvider.NOOP;
    private ImportRegistry importRegistry = new DefaultImportRegistry();
    private PathDetailsBuilderFactory pathBuilderFactory = new PathDetailsBuilderFactory();

    private String dateRangeParserString = "";
    private String encodedValuesString = "";

    public GraphHopper setEncodedValuesString(String encodedValuesString) {
        this.encodedValuesString = encodedValuesString;
        return this;
    }

    public String getEncodedValuesString() {
        return encodedValuesString;
    }

    public EncodingManager getEncodingManager() {
        if (encodingManager == null)
            throw new IllegalStateException("EncodingManager not yet built");
        return encodingManager;
    }

    public OSMParsers getOSMParsers() {
        if (osmParsers == null)
            throw new IllegalStateException("OSMParsers not yet built");
        return osmParsers;
    }

    public ElevationProvider getElevationProvider() {
        return eleProvider;
    }

    public GraphHopper setElevationProvider(ElevationProvider eleProvider) {
        if (eleProvider == null || eleProvider == ElevationProvider.NOOP)
            setElevation(false);
        else
            setElevation(true);
        this.eleProvider = eleProvider;
        return this;
    }

    public GraphHopper setPathDetailsBuilderFactory(PathDetailsBuilderFactory pathBuilderFactory) {
        this.pathBuilderFactory = pathBuilderFactory;
        return this;
    }

    public PathDetailsBuilderFactory getPathDetailsBuilderFactory() {
        return pathBuilderFactory;
    }

    /**
     * Precise location resolution index means also more space (disc/RAM) could be consumed and
     * probably slower query times, which would be e.g. not suitable for Android. The resolution
     * specifies the tile width (in meter).
     */
    public GraphHopper setPreciseIndexResolution(int precision) {
        ensureNotLoaded();
        preciseIndexResolution = precision;
        return this;
    }

    public GraphHopper setMinNetworkSize(int minNetworkSize) {
        ensureNotLoaded();
        this.minNetworkSize = minNetworkSize;
        return this;
    }

    /**
     * Configures the urban density classification. Each edge will be classified as 'rural','residential' or 'city', {@link UrbanDensity}
     *
     * @param residentialAreaRadius      in meters. The higher this value the longer the calculation will take and the bigger the area for
     *                                   which the road density used to identify residential areas is calculated.
     * @param residentialAreaSensitivity Use this to find a trade-off between too many roads being classified as residential (too high
     *                                   values) and not enough roads being classified as residential (too small values)
     * @param cityAreaRadius             in meters. The higher this value the longer the calculation will take and the bigger the area for
     *                                   which the road density used to identify city areas is calculated. Set this to zero
     *                                   to skip the city classification.
     * @param cityAreaSensitivity        Use this to find a trade-off between too many roads being classified as city (too high values)
     *                                   and not enough roads being classified as city (too small values)
     * @param threads                    the number of threads used for the calculation. If this is zero the urban density
     *                                   calculation is skipped entirely
     */
    public GraphHopper setUrbanDensityCalculation(double residentialAreaRadius, double residentialAreaSensitivity,
                                                  double cityAreaRadius, double cityAreaSensitivity, int threads) {
        ensureNotLoaded();
        this.residentialAreaRadius = residentialAreaRadius;
        this.residentialAreaSensitivity = residentialAreaSensitivity;
        this.cityAreaRadius = cityAreaRadius;
        this.cityAreaSensitivity = cityAreaSensitivity;
        this.urbanDensityCalculationThreads = threads;
        return this;
    }

    /**
     * Only valid option for in-memory graph and if you e.g. want to disable store on flush for unit
     * tests. Specify storeOnFlush to true if you want that existing data will be loaded FROM disc
     * and all in-memory data will be flushed TO disc after flush is called e.g. while OSM import.
     *
     * @param storeOnFlush true by default
     */
    public GraphHopper setStoreOnFlush(boolean storeOnFlush) {
        ensureNotLoaded();
        if (storeOnFlush)
            dataAccessDefaultType = DAType.RAM_STORE;
        else
            dataAccessDefaultType = DAType.RAM;
        return this;
    }

    /**
     * Sets the routing profiles that shall be supported by this GraphHopper instance. The (and only the) given profiles
     * can be used for routing without preparation and for CH/LM preparation.
     * 

* Here is an example how to setup two CH profiles and one LM profile (via the Java API) * *

     * {@code
     *   hopper.setProfiles(
     *     new Profile("my_car"),
     *     new Profile("your_bike")
     *   );
     *   hopper.getCHPreparationHandler().setCHProfiles(
     *     new CHProfile("my_car"),
     *     new CHProfile("your_bike")
     *   );
     *   hopper.getLMPreparationHandler().setLMProfiles(
     *     new LMProfile("your_bike")
     *   );
     * }
     * 
*

* See also https://github.com/graphhopper/graphhopper/pull/1922. * * @see CHPreparationHandler#setCHProfiles * @see LMPreparationHandler#setLMProfiles */ public GraphHopper setProfiles(Profile... profiles) { return setProfiles(Arrays.asList(profiles)); } public GraphHopper setProfiles(List profiles) { if (!profilesByName.isEmpty()) throw new IllegalArgumentException("Cannot initialize profiles multiple times"); if (encodingManager != null) throw new IllegalArgumentException("Cannot set profiles after EncodingManager was built"); for (Profile profile : profiles) { Profile previous = this.profilesByName.put(profile.getName(), profile); if (previous != null) throw new IllegalArgumentException("Profile names must be unique. Duplicate name: '" + profile.getName() + "'"); } return this; } public List getProfiles() { return new ArrayList<>(profilesByName.values()); } /** * Returns the profile for the given profile name, or null if it does not exist */ public Profile getProfile(String profileName) { return profilesByName.get(profileName); } /** * @return true if storing and fetching elevation data is enabled. Default is false */ public boolean hasElevation() { return elevation; } /** * Enable storing and fetching elevation data. Default is false */ public GraphHopper setElevation(boolean includeElevation) { this.elevation = includeElevation; return this; } public String getGraphHopperLocation() { return ghLocation; } /** * Sets the graphhopper folder. */ public GraphHopper setGraphHopperLocation(String ghLocation) { ensureNotLoaded(); if (ghLocation == null) throw new IllegalArgumentException("graphhopper location cannot be null"); this.ghLocation = ghLocation; return this; } public String getOSMFile() { return osmFile; } /** * This file can be an osm xml (.osm), a compressed xml (.osm.zip or .osm.gz) or a protobuf file * (.pbf). */ public GraphHopper setOSMFile(String osmFile) { ensureNotLoaded(); if (isEmpty(osmFile)) throw new IllegalArgumentException("OSM file cannot be empty."); this.osmFile = osmFile; return this; } public GraphHopper setMaxSpeedCalculator(MaxSpeedCalculator maxSpeedCalculator) { this.maxSpeedCalculator = maxSpeedCalculator; return this; } /** * The underlying graph used in algorithms. * * @throws IllegalStateException if graph is not instantiated. */ public BaseGraph getBaseGraph() { if (baseGraph == null) throw new IllegalStateException("GraphHopper storage not initialized"); return baseGraph; } public void setBaseGraph(BaseGraph baseGraph) { this.baseGraph = baseGraph; setFullyLoaded(); } public StorableProperties getProperties() { return properties; } /** * @return a mapping between profile names and according CH preparations. The map will be empty before loading * or import. */ public Map getCHGraphs() { return chGraphs; } /** * @return a mapping between profile names and according landmark preparations. The map will be empty before loading * or import. */ public Map getLandmarks() { return landmarks; } /** * The location index created from the graph. * * @throws IllegalStateException if index is not initialized */ public LocationIndex getLocationIndex() { if (locationIndex == null) throw new IllegalStateException("LocationIndex not initialized"); return locationIndex; } protected void setLocationIndex(LocationIndex locationIndex) { this.locationIndex = locationIndex; } public boolean isAllowWrites() { return allowWrites; } /** * Specifies if it is allowed for GraphHopper to write. E.g. for read only filesystems it is not * possible to create a lock file and so we can avoid write locks. */ public GraphHopper setAllowWrites(boolean allowWrites) { this.allowWrites = allowWrites; return this; } public TranslationMap getTranslationMap() { return trMap; } public GraphHopper setImportRegistry(ImportRegistry importRegistry) { this.importRegistry = importRegistry; return this; } public ImportRegistry getImportRegistry() { return importRegistry; } public GraphHopper setCustomAreasDirectory(String customAreasDirectory) { this.customAreasDirectory = customAreasDirectory; return this; } public String getCustomAreasDirectory() { return this.customAreasDirectory; } /** * Sets the factory used to create country rules. Use `null` to disable country rules */ public GraphHopper setCountryRuleFactory(CountryRuleFactory countryRuleFactory) { this.countryRuleFactory = countryRuleFactory; return this; } public CountryRuleFactory getCountryRuleFactory() { return this.countryRuleFactory; } /** * Reads the configuration from a {@link GraphHopperConfig} object which can be manually filled, or more typically * is read from `config.yml`. *

* Important note: Calling this method overwrites the configuration done in some of the setter methods of this class, * so generally it is advised to either use this method to configure GraphHopper or the different setter methods, * but not both. Unfortunately, this still does not cover all cases and sometimes you have to use both, but then you * should make sure there are no conflicts. If you need both it might also help to call the init before calling the * setters, because this way the init method won't apply defaults to configuration options you already chose using * the setters. */ public GraphHopper init(GraphHopperConfig ghConfig) { ensureNotLoaded(); // disabling_allowed config options were removed for GH 3.0 if (ghConfig.has("routing.ch.disabling_allowed")) throw new IllegalArgumentException("The 'routing.ch.disabling_allowed' configuration option is no longer supported"); if (ghConfig.has("routing.lm.disabling_allowed")) throw new IllegalArgumentException("The 'routing.lm.disabling_allowed' configuration option is no longer supported"); if (ghConfig.has("osmreader.osm")) throw new IllegalArgumentException("Instead of osmreader.osm use datareader.file, for other changes see CHANGELOG.md"); String tmpOsmFile = ghConfig.getString("datareader.file", ""); if (!isEmpty(tmpOsmFile)) osmFile = tmpOsmFile; String graphHopperFolder = ghConfig.getString("graph.location", ""); if (isEmpty(graphHopperFolder) && isEmpty(ghLocation)) { if (isEmpty(osmFile)) throw new IllegalArgumentException("If no graph.location is provided you need to specify an OSM file."); graphHopperFolder = pruneFileEnd(osmFile) + "-gh"; } ghLocation = graphHopperFolder; countryRuleFactory = ghConfig.getBool("country_rules.enabled", false) ? new CountryRuleFactory() : null; customAreasDirectory = ghConfig.getString("custom_areas.directory", customAreasDirectory); defaultSegmentSize = ghConfig.getInt("graph.dataaccess.segment_size", defaultSegmentSize); String daTypeString = ghConfig.getString("graph.dataaccess.default_type", ghConfig.getString("graph.dataaccess", "RAM_STORE")); dataAccessDefaultType = DAType.fromString(daTypeString); for (Map.Entry entry : ghConfig.asPMap().toMap().entrySet()) { if (entry.getKey().startsWith("graph.dataaccess.type.")) dataAccessConfig.put(entry.getKey().substring("graph.dataaccess.type.".length()), entry.getValue().toString()); if (entry.getKey().startsWith("graph.dataaccess.mmap.preload.")) dataAccessConfig.put(entry.getKey().substring("graph.dataaccess.mmap.".length()), entry.getValue().toString()); } if (ghConfig.getBool("max_speed_calculator.enabled", false)) maxSpeedCalculator = new MaxSpeedCalculator(MaxSpeedCalculator.createLegalDefaultSpeeds()); removeZipped = ghConfig.getBool("graph.remove_zipped", removeZipped); if (!ghConfig.getString("spatial_rules.location", "").isEmpty()) throw new IllegalArgumentException("spatial_rules.location has been deprecated. Please use custom_areas.directory instead and read the documentation for custom areas."); if (!ghConfig.getString("spatial_rules.borders_directory", "").isEmpty()) throw new IllegalArgumentException("spatial_rules.borders_directory has been deprecated. Please use custom_areas.directory instead and read the documentation for custom areas."); // todo: maybe introduce custom_areas.max_bbox if this is needed later if (!ghConfig.getString("spatial_rules.max_bbox", "").isEmpty()) throw new IllegalArgumentException("spatial_rules.max_bbox has been deprecated. There is no replacement, all custom areas will be considered."); String customAreasDirectory = ghConfig.getString("custom_areas.directory", ""); JsonFeatureCollection globalAreas = GraphHopper.resolveCustomAreas(customAreasDirectory); String customModelFolder = ghConfig.getString("custom_models.directory", ghConfig.getString("custom_model_folder", "")); setProfiles(GraphHopper.resolveCustomModelFiles(customModelFolder, ghConfig.getProfiles(), globalAreas)); if (ghConfig.has("graph.vehicles")) throw new IllegalArgumentException("The option graph.vehicles is no longer supported. Use the appropriate turn_costs and custom_model instead, see docs/migration/config-migration-08-09.md"); if (ghConfig.has("graph.flag_encoders")) throw new IllegalArgumentException("The option graph.flag_encoders is no longer supported."); encodedValuesString = ghConfig.getString("graph.encoded_values", encodedValuesString); dateRangeParserString = ghConfig.getString("datareader.date_range_parser_day", dateRangeParserString); if (ghConfig.getString("graph.locktype", "native").equals("simple")) lockFactory = new SimpleFSLockFactory(); else lockFactory = new NativeFSLockFactory(); // elevation if (ghConfig.has("graph.elevation.smoothing")) throw new IllegalArgumentException("Use 'graph.elevation.edge_smoothing: moving_average' or the new 'graph.elevation.edge_smoothing: ramer'. See #2634."); osmReaderConfig.setElevationSmoothing(ghConfig.getString("graph.elevation.edge_smoothing", osmReaderConfig.getElevationSmoothing())); osmReaderConfig.setSmoothElevationAverageWindowSize(ghConfig.getDouble("graph.elevation.edge_smoothing.moving_average.window_size", osmReaderConfig.getSmoothElevationAverageWindowSize())); osmReaderConfig.setElevationSmoothingRamerMax(ghConfig.getInt("graph.elevation.edge_smoothing.ramer.max_elevation", osmReaderConfig.getElevationSmoothingRamerMax())); osmReaderConfig.setLongEdgeSamplingDistance(ghConfig.getDouble("graph.elevation.long_edge_sampling_distance", osmReaderConfig.getLongEdgeSamplingDistance())); osmReaderConfig.setElevationMaxWayPointDistance(ghConfig.getDouble("graph.elevation.way_point_max_distance", osmReaderConfig.getElevationMaxWayPointDistance())); routerConfig.setElevationWayPointMaxDistance(ghConfig.getDouble("graph.elevation.way_point_max_distance", routerConfig.getElevationWayPointMaxDistance())); ElevationProvider elevationProvider = createElevationProvider(ghConfig); setElevationProvider(elevationProvider); if (osmReaderConfig.getLongEdgeSamplingDistance() < Double.MAX_VALUE && !elevationProvider.canInterpolate()) logger.warn("Long edge sampling enabled, but bilinear interpolation disabled. See #1953"); // optimizable prepare minNetworkSize = ghConfig.getInt("prepare.min_network_size", minNetworkSize); subnetworksThreads = ghConfig.getInt("prepare.subnetworks.threads", subnetworksThreads); // prepare CH&LM chPreparationHandler.init(ghConfig); lmPreparationHandler.init(ghConfig); // osm import // We do a few checks for import.osm.ignored_highways to prevent configuration errors when migrating from an older // GH version. if (!ghConfig.has("import.osm.ignored_highways")) throw new IllegalArgumentException("Missing 'import.osm.ignored_highways'. Not using this parameter can decrease performance, see config-example.yml for more details"); String ignoredHighwaysString = ghConfig.getString("import.osm.ignored_highways", ""); if ((ignoredHighwaysString.contains("footway") || ignoredHighwaysString.contains("path")) && ghConfig.getProfiles().stream().map(Profile::getName).anyMatch(p -> p.contains("foot") || p.contains("hike"))) throw new IllegalArgumentException("You should not use import.osm.ignored_highways=footway or =path in conjunction with pedestrian profiles. This is probably an error in your configuration."); if ((ignoredHighwaysString.contains("cycleway") || ignoredHighwaysString.contains("path")) && ghConfig.getProfiles().stream().map(Profile::getName).anyMatch(p -> p.contains("mtb") || p.contains("bike"))) throw new IllegalArgumentException("You should not use import.osm.ignored_highways=cycleway or =path in conjunction with bicycle profiles. This is probably an error in your configuration"); osmReaderConfig.setIgnoredHighways(Arrays.stream(ghConfig.getString("import.osm.ignored_highways", String.join(",", osmReaderConfig.getIgnoredHighways())) .split(",")).map(String::trim).collect(Collectors.toList())); osmReaderConfig.setParseWayNames(ghConfig.getBool("datareader.instructions", osmReaderConfig.isParseWayNames())); osmReaderConfig.setPreferredLanguage(ghConfig.getString("datareader.preferred_language", osmReaderConfig.getPreferredLanguage())); osmReaderConfig.setMaxWayPointDistance(ghConfig.getDouble(Routing.INIT_WAY_POINT_MAX_DISTANCE, osmReaderConfig.getMaxWayPointDistance())); osmReaderConfig.setWorkerThreads(ghConfig.getInt("datareader.worker_threads", osmReaderConfig.getWorkerThreads())); // index preciseIndexResolution = ghConfig.getInt("index.high_resolution", preciseIndexResolution); maxRegionSearch = ghConfig.getInt("index.max_region_search", maxRegionSearch); // urban density calculation residentialAreaRadius = ghConfig.getDouble("graph.urban_density.residential_radius", residentialAreaRadius); residentialAreaSensitivity = ghConfig.getDouble("graph.urban_density.residential_sensitivity", residentialAreaSensitivity); cityAreaRadius = ghConfig.getDouble("graph.urban_density.city_radius", cityAreaRadius); cityAreaSensitivity = ghConfig.getDouble("graph.urban_density.city_sensitivity", cityAreaSensitivity); urbanDensityCalculationThreads = ghConfig.getInt("graph.urban_density.threads", urbanDensityCalculationThreads); // routing routerConfig.setMaxVisitedNodes(ghConfig.getInt(Routing.INIT_MAX_VISITED_NODES, routerConfig.getMaxVisitedNodes())); routerConfig.setTimeoutMillis(ghConfig.getLong(Routing.INIT_TIMEOUT_MS, routerConfig.getTimeoutMillis())); routerConfig.setMaxRoundTripRetries(ghConfig.getInt(RoundTrip.INIT_MAX_RETRIES, routerConfig.getMaxRoundTripRetries())); routerConfig.setNonChMaxWaypointDistance(ghConfig.getInt(Parameters.NON_CH.MAX_NON_CH_POINT_DISTANCE, routerConfig.getNonChMaxWaypointDistance())); routerConfig.setInstructionsEnabled(ghConfig.getBool(Routing.INIT_INSTRUCTIONS, routerConfig.isInstructionsEnabled())); int activeLandmarkCount = ghConfig.getInt(Landmark.ACTIVE_COUNT_DEFAULT, Math.min(8, lmPreparationHandler.getLandmarks())); if (activeLandmarkCount > lmPreparationHandler.getLandmarks()) throw new IllegalArgumentException("Default value for active landmarks " + activeLandmarkCount + " should be less or equal to landmark count of " + lmPreparationHandler.getLandmarks()); routerConfig.setActiveLandmarkCount(activeLandmarkCount); calcChecksums = ghConfig.getBool("graph.calc_checksums", false); return this; } protected EncodingManager buildEncodingManager(Map encodedValuesWithProps, Map activeImportUnits, Map> restrictionVehicleTypesByProfile) { List encodedValues = new ArrayList<>(activeImportUnits.entrySet().stream() .map(e -> { Function f = e.getValue().getCreateEncodedValue(); return f == null ? null : f.apply(encodedValuesWithProps.getOrDefault(e.getKey(), new PMap())); }) .filter(Objects::nonNull) .toList()); profilesByName.values().forEach(profile -> encodedValues.add(Subnetwork.create(profile.getName()))); List sortedEVs = getEVSortIndex(profilesByName); encodedValues.sort(Comparator.comparingInt(ev -> sortedEVs.indexOf(ev.getName()))); EncodingManager.Builder emBuilder = new EncodingManager.Builder(); encodedValues.forEach(emBuilder::add); restrictionVehicleTypesByProfile.entrySet().stream() .filter(e -> !e.getValue().isEmpty()) .forEach(e -> emBuilder.addTurnCostEncodedValue(TurnRestriction.create(e.getKey()))); return emBuilder.build(); } protected List getEVSortIndex(Map profilesByName) { return Collections.emptyList(); } protected OSMParsers buildOSMParsers(Map encodedValuesWithProps, Map activeImportUnits, Map> restrictionVehicleTypesByProfile, List ignoredHighways, String dateRangeParserString) { ImportUnitSorter sorter = new ImportUnitSorter(activeImportUnits); Map sortedImportUnits = new LinkedHashMap<>(); sorter.sort().forEach(name -> sortedImportUnits.put(name, activeImportUnits.get(name))); DateRangeParser dateRangeParser = DateRangeParser.createInstance(dateRangeParserString); List sortedParsers = new ArrayList<>(); sortedImportUnits.forEach((name, importUnit) -> { BiFunction createTagParser = importUnit.getCreateTagParser(); if (createTagParser != null) sortedParsers.add(createTagParser.apply(encodingManager, encodedValuesWithProps.getOrDefault(name, new PMap().putObject("date_range_parser", dateRangeParser)))); }); OSMParsers osmParsers = new OSMParsers(); ignoredHighways.forEach(osmParsers::addIgnoredHighway); sortedParsers.forEach(osmParsers::addWayTagParser); if (maxSpeedCalculator != null) { maxSpeedCalculator.checkEncodedValues(encodingManager); osmParsers.addWayTagParser(maxSpeedCalculator.getParser()); } if (encodingManager.hasEncodedValue(BikeNetwork.KEY)) osmParsers.addRelationTagParser(relConfig -> new OSMBikeNetworkTagParser(encodingManager.getEnumEncodedValue(BikeNetwork.KEY, RouteNetwork.class), relConfig)); if (encodingManager.hasEncodedValue(MtbNetwork.KEY)) osmParsers.addRelationTagParser(relConfig -> new OSMMtbNetworkTagParser(encodingManager.getEnumEncodedValue(MtbNetwork.KEY, RouteNetwork.class), relConfig)); if (encodingManager.hasEncodedValue(FootNetwork.KEY)) osmParsers.addRelationTagParser(relConfig -> new OSMFootNetworkTagParser(encodingManager.getEnumEncodedValue(FootNetwork.KEY, RouteNetwork.class), relConfig)); restrictionVehicleTypesByProfile.forEach((profile, restrictionVehicleTypes) -> { osmParsers.addRestrictionTagParser(new RestrictionTagParser( restrictionVehicleTypes, encodingManager.getTurnBooleanEncodedValue(TurnRestriction.key(profile)))); }); return osmParsers; } public static Map parseEncodedValueString(String encodedValuesStr) { Map encodedValuesWithProps = new LinkedHashMap<>(); Arrays.stream(encodedValuesStr.split(",")) .filter(evStr -> !evStr.isBlank()) .forEach(evStr -> encodedValuesWithProps.put(evStr.trim().split("\\|")[0], new PMap(evStr))); return encodedValuesWithProps; } private static Map> getRestrictionVehicleTypesByProfile(Collection profiles) { Map> result = new LinkedHashMap<>(); for (Profile profile : profiles) if (profile.hasTurnCosts()) result.put(profile.getName(), profile.getTurnCostsConfig().getVehicleTypes()); return result; } private static ElevationProvider createElevationProvider(GraphHopperConfig ghConfig) { String eleProviderStr = toLowerCase(ghConfig.getString("graph.elevation.provider", "noop")); if (ghConfig.has("graph.elevation.calcmean")) throw new IllegalArgumentException("graph.elevation.calcmean is deprecated, use graph.elevation.interpolate"); String cacheDirStr = ghConfig.getString("graph.elevation.cache_dir", ""); if (cacheDirStr.isEmpty() && ghConfig.has("graph.elevation.cachedir")) throw new IllegalArgumentException("use graph.elevation.cache_dir not cachedir in configuration"); ElevationProvider elevationProvider = ElevationProvider.NOOP; if (eleProviderStr.equalsIgnoreCase("hgt")) { elevationProvider = new HGTProvider(cacheDirStr); } else if (eleProviderStr.equalsIgnoreCase("srtm")) { elevationProvider = new SRTMProvider(cacheDirStr); } else if (eleProviderStr.equalsIgnoreCase("cgiar")) { elevationProvider = new CGIARProvider(cacheDirStr); } else if (eleProviderStr.equalsIgnoreCase("gmted")) { elevationProvider = new GMTEDProvider(cacheDirStr); } else if (eleProviderStr.equalsIgnoreCase("srtmgl1")) { elevationProvider = new SRTMGL1Provider(cacheDirStr); } else if (eleProviderStr.equalsIgnoreCase("multi")) { elevationProvider = new MultiSourceElevationProvider(cacheDirStr); } else if (eleProviderStr.equalsIgnoreCase("skadi")) { elevationProvider = new SkadiProvider(cacheDirStr); } if (elevationProvider instanceof TileBasedElevationProvider) { TileBasedElevationProvider provider = (TileBasedElevationProvider) elevationProvider; String baseURL = ghConfig.getString("graph.elevation.base_url", ""); if (baseURL.isEmpty() && ghConfig.has("graph.elevation.baseurl")) throw new IllegalArgumentException("use graph.elevation.base_url not baseurl in configuration"); DAType elevationDAType = DAType.fromString(ghConfig.getString("graph.elevation.dataaccess", "MMAP")); boolean interpolate = ghConfig.has("graph.elevation.interpolate") ? "bilinear".equals(ghConfig.getString("graph.elevation.interpolate", "none")) : ghConfig.getBool("graph.elevation.calc_mean", false); boolean removeTempElevationFiles = ghConfig.getBool("graph.elevation.cgiar.clear", true); removeTempElevationFiles = ghConfig.getBool("graph.elevation.clear", removeTempElevationFiles); provider .setAutoRemoveTemporaryFiles(removeTempElevationFiles) .setInterpolate(interpolate) .setDAType(elevationDAType); if (!baseURL.isEmpty()) provider.setBaseURL(baseURL); } return elevationProvider; } private void printInfo() { logger.info("version " + Constants.VERSION + "|" + Constants.BUILD_DATE + " (" + Constants.getVersions() + ")"); if (baseGraph != null) logger.info("graph " + getBaseGraphString() + ", details:" + baseGraph.toDetailsString()); } private String getBaseGraphString() { return encodingManager + "|" + baseGraph.getDirectory().getDefaultType() + "|" + baseGraph.getNodeAccess().getDimension() + "D" + "|" + (baseGraph.getTurnCostStorage() != null ? baseGraph.getTurnCostStorage() : "no_turn_cost") + "|" + getVersionsString(); } private String getVersionsString() { return "nodes:" + Constants.VERSION_NODE + ",edges:" + Constants.VERSION_EDGE + ",geometry:" + Constants.VERSION_GEOMETRY + ",location_index:" + Constants.VERSION_LOCATION_IDX + ",string_index:" + Constants.VERSION_KV_STORAGE + ",nodesCH:" + Constants.VERSION_NODE_CH + ",shortcuts:" + Constants.VERSION_SHORTCUT; } /** * Imports provided data from disc and creates graph. Depending on the settings the resulting * graph will be stored to disc so on a second call this method will only load the graph from * disc which is usually a lot faster. */ public GraphHopper importOrLoad() { if (!load()) { printInfo(); process(false); } else { printInfo(); } return this; } /** * Imports and processes data, storing it to disk when complete. */ public void importAndClose() { if (!load()) { printInfo(); process(true); } else { printInfo(); logger.info("Graph already imported into " + ghLocation); } close(); } /** * Creates the graph from OSM data. */ protected void process(boolean closeEarly) { prepareImport(); if (encodingManager == null) throw new IllegalStateException("The EncodingManager must be created in `prepareImport()`"); GHDirectory directory = new GHDirectory(ghLocation, dataAccessDefaultType); directory.configure(dataAccessConfig); baseGraph = new BaseGraph.Builder(getEncodingManager()) .setDir(directory) .set3D(hasElevation()) .withTurnCosts(encodingManager.needsTurnCostsSupport()) .setSegmentSize(defaultSegmentSize) .build(); properties = new StorableProperties(directory); checkProfilesConsistency(); GHLock lock = null; try { if (directory.getDefaultType().isStoring()) { lockFactory.setLockDir(new File(ghLocation)); lock = lockFactory.create(fileLockName, true); if (!lock.tryLock()) throw new RuntimeException("To avoid multiple writers we need to obtain a write lock but it failed. In " + ghLocation, lock.getObtainFailedReason()); } ensureWriteAccess(); importOSM(); postImportOSM(); cleanUp(); properties.put("profiles", getProfilesString()); writeEncodingManagerToProperties(); postProcessing(closeEarly); flush(); } finally { if (lock != null) lock.release(); } } protected void prepareImport() { Map encodedValuesWithProps = parseEncodedValueString(encodedValuesString); NameValidator nameValidator = s -> importRegistry.createImportUnit(s) != null; Set missing = new LinkedHashSet<>(); profilesByName.values(). forEach(profile -> CustomModelParser.findVariablesForEncodedValuesString(profile.getCustomModel(), nameValidator, s -> ""). forEach(var -> { if (!encodedValuesWithProps.containsKey(var)) missing.add(var); encodedValuesWithProps.putIfAbsent(var, new PMap()); })); if (!missing.isEmpty()) { String encodedValuesString = encodedValuesWithProps.entrySet().stream() .map(e -> e.getKey() + (e.getValue().isEmpty() ? "" : ("|" + e.getValue().toMap().entrySet().stream().map(p -> p.getKey() + "=" + p.getValue()).collect(Collectors.joining("|"))))) .collect(Collectors.joining(", ")); throw new IllegalArgumentException("Encoded values missing: " + String.join(", ", missing) + ".\n" + "To avoid that certain encoded values are automatically removed when you change the custom model later, you need to set the encoded values manually:\n" + "graph.encoded_values: " + encodedValuesString); } // these are used in the snap prevention filter (avoid motorway, tunnel, etc.) so they have to be there encodedValuesWithProps.putIfAbsent(RoadClass.KEY, new PMap()); encodedValuesWithProps.putIfAbsent(RoadEnvironment.KEY, new PMap()); // used by instructions... encodedValuesWithProps.putIfAbsent(Roundabout.KEY, new PMap()); encodedValuesWithProps.putIfAbsent(RoadClassLink.KEY, new PMap()); encodedValuesWithProps.putIfAbsent(MaxSpeed.KEY, new PMap()); Map> restrictionVehicleTypesByProfile = getRestrictionVehicleTypesByProfile(profilesByName.values()); if (urbanDensityCalculationThreads > 0) encodedValuesWithProps.put(UrbanDensity.KEY, new PMap()); if (maxSpeedCalculator != null) encodedValuesWithProps.put(MaxSpeedEstimated.KEY, new PMap()); Map activeImportUnits = new LinkedHashMap<>(); ArrayDeque deque = new ArrayDeque<>(encodedValuesWithProps.keySet()); while (!deque.isEmpty()) { String ev = deque.removeFirst(); ImportUnit importUnit = importRegistry.createImportUnit(ev); if (importUnit == null) throw new IllegalArgumentException("Unknown encoded value: " + ev); if (activeImportUnits.put(ev, importUnit) == null) deque.addAll(importUnit.getRequiredImportUnits()); } encodingManager = buildEncodingManager(encodedValuesWithProps, activeImportUnits, restrictionVehicleTypesByProfile); osmParsers = buildOSMParsers(encodedValuesWithProps, activeImportUnits, restrictionVehicleTypesByProfile, osmReaderConfig.getIgnoredHighways(), dateRangeParserString); } protected void postImportOSM() { // Important note: To deal with via-way turn restrictions we introduce artificial edges in OSMReader (#2689). // These are simply copies of real edges. Any further modifications of the graph edges must take care of keeping // the artificial edges in sync with their real counterparts. So if an edge attribute shall be changed this change // must also be applied to the corresponding artificial edge. calculateUrbanDensity(); if (maxSpeedCalculator != null) { maxSpeedCalculator.fillMaxSpeed(getBaseGraph(), encodingManager); maxSpeedCalculator.close(); } if (hasElevation()) interpolateBridgesTunnelsAndFerries(); } protected void importOSM() { if (osmFile == null) throw new IllegalStateException("Couldn't load from existing folder: " + ghLocation + " but also cannot use file for DataReader as it wasn't specified!"); List customAreas = readCountries(); if (isEmpty(customAreasDirectory)) { logger.info("No custom areas are used, custom_areas.directory not given"); } else { logger.info("Creating custom area index, reading custom areas from: '" + customAreasDirectory + "'"); customAreas.addAll(readCustomAreas()); } AreaIndex areaIndex = new AreaIndex<>(customAreas); if (countryRuleFactory == null || countryRuleFactory.getCountryToRuleMap().isEmpty()) { logger.info("No country rules available"); } else { logger.info("Applying rules for the following countries: {}", countryRuleFactory.getCountryToRuleMap().keySet()); } logger.info("start creating graph from " + osmFile); OSMReader reader = new OSMReader(baseGraph.getBaseGraph(), osmParsers, osmReaderConfig).setFile(_getOSMFile()). setAreaIndex(areaIndex). setElevationProvider(eleProvider). setCountryRuleFactory(countryRuleFactory); logger.info("using " + getBaseGraphString() + ", memory:" + getMemInfo()); createBaseGraphAndProperties(); try { reader.readGraph(); } catch (IOException ex) { throw new RuntimeException("Cannot read file " + getOSMFile(), ex); } DateFormat f = createFormatter(); properties.put("datareader.import.date", f.format(new Date())); if (reader.getDataDate() != null) properties.put("datareader.data.date", f.format(reader.getDataDate())); } protected void createBaseGraphAndProperties() { baseGraph.getDirectory().create(); baseGraph.create(100); properties.create(100); if (maxSpeedCalculator != null) maxSpeedCalculator.createDataAccessForParser(baseGraph.getDirectory()); } private void calculateUrbanDensity() { if (encodingManager.hasEncodedValue(UrbanDensity.KEY)) { EnumEncodedValue urbanDensityEnc = encodingManager.getEnumEncodedValue(UrbanDensity.KEY, UrbanDensity.class); if (!encodingManager.hasEncodedValue(RoadClass.KEY)) throw new IllegalArgumentException("Urban density calculation requires " + RoadClass.KEY); if (!encodingManager.hasEncodedValue(RoadClassLink.KEY)) throw new IllegalArgumentException("Urban density calculation requires " + RoadClassLink.KEY); EnumEncodedValue roadClassEnc = encodingManager.getEnumEncodedValue(RoadClass.KEY, RoadClass.class); BooleanEncodedValue roadClassLinkEnc = encodingManager.getBooleanEncodedValue(RoadClassLink.KEY); UrbanDensityCalculator.calcUrbanDensity(baseGraph, urbanDensityEnc, roadClassEnc, roadClassLinkEnc, residentialAreaRadius, residentialAreaSensitivity, cityAreaRadius, cityAreaSensitivity, urbanDensityCalculationThreads); } } private void writeEncodingManagerToProperties() { EncodingManager.putEncodingManagerIntoProperties(encodingManager, properties); } private List readCustomAreas() { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerModule(new JtsModule()); final Path bordersDirectory = Paths.get(customAreasDirectory); List jsonFeatureCollections = new ArrayList<>(); try (DirectoryStream stream = Files.newDirectoryStream(bordersDirectory, "*.{geojson,json}")) { for (Path borderFile : stream) { try (BufferedReader reader = Files.newBufferedReader(borderFile, StandardCharsets.UTF_8)) { JsonFeatureCollection jsonFeatureCollection = objectMapper.readValue(reader, JsonFeatureCollection.class); jsonFeatureCollections.add(jsonFeatureCollection); } } } catch (IOException e) { throw new UncheckedIOException(e); } return jsonFeatureCollections.stream().flatMap(j -> j.getFeatures().stream()) .map(CustomArea::fromJsonFeature) .collect(Collectors.toList()); } /** * Currently we use this for a few tests where the dataReaderFile is loaded from the classpath */ protected File _getOSMFile() { return new File(osmFile); } /** * Load from existing graph folder. */ public boolean load() { if (isEmpty(ghLocation)) throw new IllegalStateException("GraphHopperLocation is not specified. Call setGraphHopperLocation or init before"); if (fullyLoaded) throw new IllegalStateException("graph is already successfully loaded"); File tmpFileOrFolder = new File(ghLocation); if (!tmpFileOrFolder.isDirectory() && tmpFileOrFolder.exists()) { throw new IllegalArgumentException("GraphHopperLocation cannot be an existing file. Has to be either non-existing or a folder."); } else { File compressed = new File(ghLocation + ".ghz"); if (compressed.exists() && !compressed.isDirectory()) { try { new Unzipper().unzip(compressed.getAbsolutePath(), ghLocation, removeZipped); } catch (IOException ex) { throw new RuntimeException("Couldn't extract file " + compressed.getAbsolutePath() + " to " + ghLocation, ex); } } } // todo: this does not really belong here, we abuse the load method to derive the dataAccessDefaultType setting from others if (!allowWrites && dataAccessDefaultType.isMMap()) dataAccessDefaultType = DAType.MMAP_RO; if (!new File(ghLocation).exists()) // there is just nothing to load return false; GHDirectory directory = new GHDirectory(ghLocation, dataAccessDefaultType); directory.configure(dataAccessConfig); GHLock lock = null; try { // create locks only if writes are allowed, if they are not allowed a lock cannot be created // (e.g. on a read only filesystem locks would fail) if (directory.getDefaultType().isStoring() && isAllowWrites()) { lockFactory.setLockDir(new File(ghLocation)); lock = lockFactory.create(fileLockName, false); if (!lock.tryLock()) throw new RuntimeException("To avoid reading partial data we need to obtain the read lock but it failed. In " + ghLocation, lock.getObtainFailedReason()); } properties = new StorableProperties(directory); if (!properties.loadExisting()) // the -gh folder exists, but there is no properties file. it might be just empty, so let's act as if // the import did not run yet or is not complete for some reason return false; encodingManager = EncodingManager.fromProperties(properties); baseGraph = new BaseGraph.Builder(encodingManager) .setDir(directory) .set3D(hasElevation()) .withTurnCosts(encodingManager.needsTurnCostsSupport()) .setSegmentSize(defaultSegmentSize) .build(); baseGraph.loadExisting(); String storedProfiles = properties.get("profiles"); String configuredProfiles = getProfilesString(); if (!storedProfiles.equals(configuredProfiles)) throw new IllegalStateException("Profiles do not match:" + "\nGraphhopper config: " + configuredProfiles + "\nGraph: " + storedProfiles + "\nChange configuration to match the graph or delete " + baseGraph.getDirectory().getLocation()); checkProfilesConsistency(); postProcessing(false); directory.loadMMap(); setFullyLoaded(); return true; } finally { if (lock != null) lock.release(); } } private String getProfilesString() { return profilesByName.values().stream().map(p -> p.getName() + "|" + p.getVersion()).collect(Collectors.joining(",")); } public void checkProfilesConsistency() { if (profilesByName.isEmpty()) throw new IllegalArgumentException("There has to be at least one profile"); for (Profile profile : profilesByName.values()) { try { createWeighting(profile, new PMap()); } catch (IllegalArgumentException e) { throw new IllegalArgumentException("Could not create weighting for profile: '" + profile.getName() + "'.\n" + "Profile: " + profile + "\n" + "Error: " + e.getMessage()); } if (CustomWeighting.NAME.equals(profile.getWeighting()) && profile.getCustomModel() == null) throw new IllegalArgumentException("custom model for profile '" + profile.getName() + "' was empty"); if (!CustomWeighting.NAME.equals(profile.getWeighting()) && profile.getCustomModel() != null) throw new IllegalArgumentException("profile '" + profile.getName() + "' has a custom model but " + "weighting=" + profile.getWeighting() + " was defined"); } Set chProfileSet = new LinkedHashSet<>(chPreparationHandler.getCHProfiles().size()); for (CHProfile chProfile : chPreparationHandler.getCHProfiles()) { boolean added = chProfileSet.add(chProfile.getProfile()); if (!added) { throw new IllegalArgumentException("Duplicate CH reference to profile '" + chProfile.getProfile() + "'"); } if (!profilesByName.containsKey(chProfile.getProfile())) { throw new IllegalArgumentException("CH profile references unknown profile '" + chProfile.getProfile() + "'"); } } Map lmProfileMap = new LinkedHashMap<>(lmPreparationHandler.getLMProfiles().size()); for (LMProfile lmProfile : lmPreparationHandler.getLMProfiles()) { LMProfile previous = lmProfileMap.put(lmProfile.getProfile(), lmProfile); if (previous != null) { throw new IllegalArgumentException("Multiple LM profiles are using the same profile '" + lmProfile.getProfile() + "'"); } if (!profilesByName.containsKey(lmProfile.getProfile())) { throw new IllegalArgumentException("LM profile references unknown profile '" + lmProfile.getProfile() + "'"); } if (lmProfile.usesOtherPreparation() && !profilesByName.containsKey(lmProfile.getPreparationProfile())) { throw new IllegalArgumentException("LM profile references unknown preparation profile '" + lmProfile.getPreparationProfile() + "'"); } } for (LMProfile lmProfile : lmPreparationHandler.getLMProfiles()) { if (lmProfile.usesOtherPreparation() && !lmProfileMap.containsKey(lmProfile.getPreparationProfile())) { throw new IllegalArgumentException("Unknown LM preparation profile '" + lmProfile.getPreparationProfile() + "' in LM profile '" + lmProfile.getProfile() + "' cannot be used as preparation_profile"); } if (lmProfile.usesOtherPreparation() && lmProfileMap.get(lmProfile.getPreparationProfile()).usesOtherPreparation()) { throw new IllegalArgumentException("Cannot use '" + lmProfile.getPreparationProfile() + "' as preparation_profile for LM profile '" + lmProfile.getProfile() + "', because it uses another profile for preparation itself."); } } } public final CHPreparationHandler getCHPreparationHandler() { return chPreparationHandler; } private List createCHConfigs(List chProfiles) { List chConfigs = new ArrayList<>(); for (CHProfile chProfile : chProfiles) { Profile profile = profilesByName.get(chProfile.getProfile()); if (profile.hasTurnCosts()) { chConfigs.add(CHConfig.edgeBased(profile.getName(), createWeighting(profile, new PMap()))); } else { chConfigs.add(CHConfig.nodeBased(profile.getName(), createWeighting(profile, new PMap()))); } } return chConfigs; } public final LMPreparationHandler getLMPreparationHandler() { return lmPreparationHandler; } private List createLMConfigs(List lmProfiles) { List lmConfigs = new ArrayList<>(); for (LMProfile lmProfile : lmProfiles) { if (lmProfile.usesOtherPreparation()) continue; Profile profile = profilesByName.get(lmProfile.getProfile()); // Note that we have to make sure the weighting used for LM preparation does not include turn costs, because // the LM preparation is running node-based and the landmark weights will be wrong if there are non-zero // turn costs, see discussion in #1960 // Running the preparation without turn costs is also useful to allow e.g. changing the u_turn_costs per // request (we have to use the minimum weight settings (= no turn costs) for the preparation) Weighting weighting = createWeighting(profile, new PMap(), true); lmConfigs.add(new LMConfig(profile.getName(), weighting)); } return lmConfigs; } /** * Runs both after the import and when loading an existing Graph * * @param closeEarly release resources as early as possible */ protected void postProcessing(boolean closeEarly) { calcChecksums(); initLocationIndex(); importPublicTransit(); if (closeEarly) { boolean includesCustomProfiles = profilesByName.values().stream().anyMatch(p -> CustomWeighting.NAME.equals(p.getWeighting())); if (!includesCustomProfiles) // when there are custom profiles we must not close way geometry or KVStorage, because // they might be needed to evaluate the custom weightings for the following preparations baseGraph.flushAndCloseGeometryAndNameStorage(); } if (lmPreparationHandler.isEnabled()) loadOrPrepareLM(closeEarly); if (closeEarly) // we needed the location index for the LM preparation, but we don't need it for CH locationIndex.close(); if (chPreparationHandler.isEnabled()) loadOrPrepareCH(closeEarly); } protected void importPublicTransit() { } void interpolateBridgesTunnelsAndFerries() { if (encodingManager.hasEncodedValue(RoadEnvironment.KEY)) { EnumEncodedValue roadEnvEnc = encodingManager.getEnumEncodedValue(RoadEnvironment.KEY, RoadEnvironment.class); StopWatch sw = new StopWatch().start(); new EdgeElevationInterpolator(baseGraph.getBaseGraph(), roadEnvEnc, RoadEnvironment.TUNNEL).execute(); float tunnel = sw.stop().getSeconds(); sw = new StopWatch().start(); new EdgeElevationInterpolator(baseGraph.getBaseGraph(), roadEnvEnc, RoadEnvironment.BRIDGE).execute(); float bridge = sw.stop().getSeconds(); // The SkadiProvider contains bathymetric data. For ferries this can result in bigger elevation changes // See #2098 for mor information sw = new StopWatch().start(); new EdgeElevationInterpolator(baseGraph.getBaseGraph(), roadEnvEnc, RoadEnvironment.FERRY).execute(); logger.info("Bridge interpolation " + (int) bridge + "s, " + "tunnel interpolation " + (int) tunnel + "s, ferry interpolation " + (int) sw.stop().getSeconds() + "s"); } } public final Weighting createWeighting(Profile profile, PMap hints) { return createWeighting(profile, hints, false); } public final Weighting createWeighting(Profile profile, PMap hints, boolean disableTurnCosts) { return createWeightingFactory().createWeighting(profile, hints, disableTurnCosts); } protected WeightingFactory createWeightingFactory() { return new DefaultWeightingFactory(baseGraph.getBaseGraph(), getEncodingManager()); } public GHResponse route(GHRequest request) { return createRouter().route(request); } private Router createRouter() { if (baseGraph == null || !fullyLoaded) throw new IllegalStateException("Do a successful call to load or importOrLoad before routing"); if (baseGraph.isClosed()) throw new IllegalStateException("You need to create a new GraphHopper instance as it is already closed"); if (locationIndex == null) throw new IllegalStateException("Location index not initialized"); return doCreateRouter(baseGraph, encodingManager, locationIndex, profilesByName, pathBuilderFactory, trMap, routerConfig, createWeightingFactory(), chGraphs, landmarks); } protected Router doCreateRouter(BaseGraph baseGraph, EncodingManager encodingManager, LocationIndex locationIndex, Map profilesByName, PathDetailsBuilderFactory pathBuilderFactory, TranslationMap trMap, RouterConfig routerConfig, WeightingFactory weightingFactory, Map chGraphs, Map landmarks) { return new Router(baseGraph, encodingManager, locationIndex, profilesByName, pathBuilderFactory, trMap, routerConfig, weightingFactory, chGraphs, landmarks ); } protected LocationIndex createLocationIndex(Directory dir) { LocationIndexTree tmpIndex = new LocationIndexTree(baseGraph, dir); tmpIndex.setResolution(preciseIndexResolution); tmpIndex.setMaxRegionSearch(maxRegionSearch); if (!tmpIndex.loadExisting()) { ensureWriteAccess(); tmpIndex.prepareIndex(); } return tmpIndex; } private void calcChecksums() { if (!calcChecksums) return; logger.info("Calculating checksums for {} profiles", profilesByName.size()); StopWatch sw = StopWatch.started(); double[] checksums_fwd = new double[profilesByName.size()]; double[] checksums_bwd = new double[profilesByName.size()]; List weightings = profilesByName.values().stream().map(profile -> createWeighting(profile, new PMap())).toList(); AllEdgesIterator edge = baseGraph.getAllEdges(); while (edge.next()) { for (int i = 0; i < profilesByName.size(); i++) { double weightFwd = weightings.get(i).calcEdgeWeight(edge, false); if (Double.isInfinite(weightFwd)) weightFwd = -1; weightFwd *= (i % 2 == 0) ? -1 : 1; double weightBwd = weightings.get(i).calcEdgeWeight(edge, true); if (Double.isInfinite(weightBwd)) weightBwd = -1; weightBwd *= (i % 2 == 0) ? -1 : 1; checksums_fwd[i] += weightFwd; checksums_bwd[i] += weightBwd; } } int index = 0; for (Profile profile : profilesByName.values()) { properties.put("checksum.fwd." + profile.getName(), checksums_fwd[index]); properties.put("checksum.bwd." + profile.getName(), checksums_bwd[index]); logger.info("checksum.fwd." + profile.getName() + ": " + checksums_fwd[index]); logger.info("checksum.bwd." + profile.getName() + ": " + checksums_bwd[index]); index++; } logger.info("Calculating checksums took: " + sw.stop().getTimeString()); } /** * Initializes the location index after the import is done. */ protected void initLocationIndex() { if (locationIndex != null) throw new IllegalStateException("Cannot initialize locationIndex twice!"); locationIndex = createLocationIndex(baseGraph.getDirectory()); } private String getCHProfileVersion(String profile) { return properties.get("graph.profiles.ch." + profile + ".version"); } private void setCHProfileVersion(String profile, int version) { properties.put("graph.profiles.ch." + profile + ".version", version); } private String getLMProfileVersion(String profile) { return properties.get("graph.profiles.lm." + profile + ".version"); } private void setLMProfileVersion(String profile, int version) { properties.put("graph.profiles.lm." + profile + ".version", version); } protected void loadOrPrepareCH(boolean closeEarly) { for (CHProfile profile : chPreparationHandler.getCHProfiles()) if (!getCHProfileVersion(profile.getProfile()).isEmpty() && !getCHProfileVersion(profile.getProfile()).equals("" + profilesByName.get(profile.getProfile()).getVersion())) throw new IllegalArgumentException("CH preparation of " + profile.getProfile() + " already exists in storage and doesn't match configuration"); // we load ch graphs that already exist and prepare the other ones List chConfigs = createCHConfigs(chPreparationHandler.getCHProfiles()); Map loaded = chPreparationHandler.load(baseGraph.getBaseGraph(), chConfigs); List configsToPrepare = chConfigs.stream().filter(c -> !loaded.containsKey(c.getName())).collect(Collectors.toList()); Map prepared = prepareCH(closeEarly, configsToPrepare); // we map all profile names for which there is CH support to the according CH graphs chGraphs = new LinkedHashMap<>(); for (CHProfile profile : chPreparationHandler.getCHProfiles()) { if (loaded.containsKey(profile.getProfile()) && prepared.containsKey(profile.getProfile())) throw new IllegalStateException("CH graph should be either loaded or prepared, but not both: " + profile.getProfile()); else if (prepared.containsKey(profile.getProfile())) { setCHProfileVersion(profile.getProfile(), profilesByName.get(profile.getProfile()).getVersion()); PrepareContractionHierarchies.Result res = prepared.get(profile.getProfile()); chGraphs.put(profile.getProfile(), RoutingCHGraphImpl.fromGraph(baseGraph.getBaseGraph(), res.getCHStorage(), res.getCHConfig())); } else if (loaded.containsKey(profile.getProfile())) { chGraphs.put(profile.getProfile(), loaded.get(profile.getProfile())); } else throw new IllegalStateException("CH graph should be either loaded or prepared: " + profile.getProfile()); } } protected Map prepareCH(boolean closeEarly, List configsToPrepare) { if (!configsToPrepare.isEmpty()) ensureWriteAccess(); if (!baseGraph.isFrozen()) baseGraph.freeze(); return chPreparationHandler.prepare(baseGraph, properties, configsToPrepare, closeEarly); } /** * For landmarks it is required to always call this method: either it creates the landmark data or it loads it. */ protected void loadOrPrepareLM(boolean closeEarly) { for (LMProfile profile : lmPreparationHandler.getLMProfiles()) if (!getLMProfileVersion(profile.getProfile()).isEmpty() && !getLMProfileVersion(profile.getProfile()).equals("" + profilesByName.get(profile.getProfile()).getVersion())) throw new IllegalArgumentException("LM preparation of " + profile.getProfile() + " already exists in storage and doesn't match configuration"); // we load landmark storages that already exist and prepare the other ones List lmConfigs = createLMConfigs(lmPreparationHandler.getLMProfiles()); List loaded = lmPreparationHandler.load(lmConfigs, baseGraph, encodingManager); List loadedConfigs = loaded.stream().map(LandmarkStorage::getLMConfig).collect(Collectors.toList()); List configsToPrepare = lmConfigs.stream().filter(c -> !loadedConfigs.contains(c)).collect(Collectors.toList()); List prepared = prepareLM(closeEarly, configsToPrepare); // we map all profile names for which there is LM support to the according LM storages landmarks = new LinkedHashMap<>(); for (LMProfile lmp : lmPreparationHandler.getLMProfiles()) { // cross-querying String prepProfile = lmp.usesOtherPreparation() ? lmp.getPreparationProfile() : lmp.getProfile(); Optional loadedLMS = loaded.stream().filter(lms -> lms.getLMConfig().getName().equals(prepProfile)).findFirst(); Optional preparedLMS = prepared.stream().filter(pl -> pl.getLandmarkStorage().getLMConfig().getName().equals(prepProfile)).findFirst(); if (loadedLMS.isPresent() && preparedLMS.isPresent()) throw new IllegalStateException("LM should be either loaded or prepared, but not both: " + prepProfile); else if (preparedLMS.isPresent()) { setLMProfileVersion(lmp.getProfile(), profilesByName.get(lmp.getProfile()).getVersion()); landmarks.put(lmp.getProfile(), preparedLMS.get().getLandmarkStorage()); } else loadedLMS.ifPresent(landmarkStorage -> landmarks.put(lmp.getProfile(), landmarkStorage)); } } protected List prepareLM(boolean closeEarly, List configsToPrepare) { if (!configsToPrepare.isEmpty()) ensureWriteAccess(); if (!baseGraph.isFrozen()) baseGraph.freeze(); return lmPreparationHandler.prepare(configsToPrepare, baseGraph, encodingManager, properties, locationIndex, closeEarly); } /** * Internal method to clean up the graph. */ protected void cleanUp() { PrepareRoutingSubnetworks preparation = new PrepareRoutingSubnetworks(baseGraph.getBaseGraph(), buildSubnetworkRemovalJobs()); preparation.setMinNetworkSize(minNetworkSize); preparation.setThreads(subnetworksThreads); preparation.doWork(); logger.info("nodes: " + Helper.nf(baseGraph.getNodes()) + ", edges: " + Helper.nf(baseGraph.getEdges())); } private List buildSubnetworkRemovalJobs() { List jobs = new ArrayList<>(); for (Profile profile : profilesByName.values()) { // if turn costs are enabled use u-turn costs of zero as we only want to make sure the graph is fully connected assuming finite u-turn costs Weighting weighting = createWeighting(profile, new PMap().putObject(Parameters.Routing.U_TURN_COSTS, 0)); jobs.add(new PrepareJob(encodingManager.getBooleanEncodedValue(Subnetwork.key(profile.getName())), weighting)); } return jobs; } protected void flush() { logger.info("flushing graph " + getBaseGraphString() + ", details:" + baseGraph.toDetailsString() + ", " + getMemInfo() + ")"); baseGraph.flush(); properties.flush(); logger.info("flushed graph " + getMemInfo() + ")"); setFullyLoaded(); } /** * Releases all associated resources like memory or files. But it does not remove them. To * remove the files created in graphhopperLocation you have to call clean(). */ public void close() { if (baseGraph != null) baseGraph.close(); if (properties != null) properties.close(); chGraphs.values().forEach(RoutingCHGraph::close); landmarks.values().forEach(LandmarkStorage::close); if (locationIndex != null) locationIndex.close(); try { lockFactory.forceRemove(fileLockName, true); } catch (Exception ex) { // silently fail e.g. on Windows where we cannot remove an unreleased native lock } } /** * Removes the on-disc routing files. Call only after calling close or before importOrLoad or * load */ public void clean() { if (getGraphHopperLocation().isEmpty()) throw new IllegalStateException("Cannot clean GraphHopper without specified graphHopperLocation"); File folder = new File(getGraphHopperLocation()); removeDir(folder); } protected void ensureNotLoaded() { if (fullyLoaded) throw new IllegalStateException("No configuration changes are possible after loading the graph"); } protected void ensureWriteAccess() { if (!allowWrites) throw new IllegalStateException("Writes are not allowed!"); } private void setFullyLoaded() { fullyLoaded = true; } public boolean getFullyLoaded() { return fullyLoaded; } public RouterConfig getRouterConfig() { return routerConfig; } public OSMReaderConfig getReaderConfig() { return osmReaderConfig; } public static JsonFeatureCollection resolveCustomAreas(String customAreasDirectory) { JsonFeatureCollection globalAreas = new JsonFeatureCollection(); if (!customAreasDirectory.isEmpty()) { ObjectMapper mapper = new ObjectMapper().registerModule(new JtsModule()); try (DirectoryStream stream = Files.newDirectoryStream(Paths.get(customAreasDirectory), "*.{geojson,json}")) { for (Path customAreaFile : stream) { try (BufferedReader reader = Files.newBufferedReader(customAreaFile, StandardCharsets.UTF_8)) { globalAreas.getFeatures().addAll(mapper.readValue(reader, JsonFeatureCollection.class).getFeatures()); } } logger.info("Will make " + globalAreas.getFeatures().size() + " areas available to all custom profiles. Found in " + customAreasDirectory); } catch (IOException e) { throw new UncheckedIOException(e); } } return globalAreas; } public static List resolveCustomModelFiles(String customModelFolder, List profiles, JsonFeatureCollection globalAreas) { ObjectMapper jsonOM = Jackson.newObjectMapper(); List newProfiles = new ArrayList<>(); for (Profile profile : profiles) { if (!CustomWeighting.NAME.equals(profile.getWeighting())) { newProfiles.add(profile); continue; } Object cm = profile.getHints().getObject(CustomModel.KEY, null); CustomModel customModel; if (cm != null) { if (!profile.getHints().getObject("custom_model_files", Collections.emptyList()).isEmpty()) throw new IllegalArgumentException("Do not use custom_model_files and custom_model together"); try { // custom_model can be an object tree (read from config) or an object (e.g. from tests) customModel = jsonOM.readValue(jsonOM.writeValueAsBytes(cm), CustomModel.class); newProfiles.add(profile.setCustomModel(customModel)); } catch (Exception ex) { throw new RuntimeException("Cannot load custom_model from " + cm + " for profile " + profile.getName() + ". If you are trying to load from a file, use 'custom_model_files' instead.", ex); } } else { if (!profile.getHints().getString("custom_model_file", "").isEmpty()) throw new IllegalArgumentException("Since 8.0 you must use a custom_model_files array instead of custom_model_file string"); List customModelFileNames = profile.getHints().getObject("custom_model_files", null); if (customModelFileNames == null) throw new IllegalArgumentException("Missing 'custom_model' or 'custom_model_files' field in profile '" + profile.getName() + "'. To use default specify custom_model_files: []"); if (customModelFileNames.isEmpty()) { newProfiles.add(profile.setCustomModel(customModel = new CustomModel())); } else { customModel = new CustomModel(); for (String file : customModelFileNames) { if (file.contains(File.separator)) throw new IllegalArgumentException("Use custom_models.directory for the custom_model_files parent"); if (!file.endsWith(".json")) throw new IllegalArgumentException("Yaml is no longer supported, see #2672. Use JSON with optional comments //"); try { String string; // 1. try to load custom model from jar InputStream is = GHUtility.class.getResourceAsStream("/com/graphhopper/custom_models/" + file); // dropwizard makes it very hard to find out the folder of config.yml -> use an extra parameter for the folder Path customModelFile = Paths.get(customModelFolder).resolve(file); if (is != null) { if (Files.exists(customModelFile)) throw new RuntimeException("Custom model file name '" + file + "' is already used for built-in profiles. Use another name"); string = readJSONFileWithoutComments(new InputStreamReader(is)); } else { // 2. try to load custom model file from external location string = readJSONFileWithoutComments(customModelFile.toFile().getAbsolutePath()); } customModel = CustomModel.merge(customModel, jsonOM.readValue(string, CustomModel.class)); } catch (IOException ex) { throw new RuntimeException("Cannot load custom_model from location " + file + ", profile:" + profile.getName(), ex); } } newProfiles.add(profile.setCustomModel(customModel)); } } // we can fill in all areas here as in the created template we include only the areas that are used in // statements (see CustomModelParser) customModel.addAreas(globalAreas); } return newProfiles; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy