/*
* 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.routing.util;
import com.graphhopper.reader.ReaderNode;
import com.graphhopper.reader.ReaderRelation;
import com.graphhopper.reader.ReaderWay;
import com.graphhopper.routing.weighting.TurnWeighting;
import com.graphhopper.storage.Directory;
import com.graphhopper.storage.RAMDirectory;
import com.graphhopper.storage.StorableProperties;
import com.graphhopper.util.EdgeIteratorState;
import com.graphhopper.util.PMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Manager class to register encoder, assign their flag values and check objects with all encoders
* during parsing.
* <p>
*
* @author Peter Karich
* @author Nop
*/
public class EncodingManager {
private static final String ERR = "Encoders are requesting %s bits, more than %s bits of %s flags. ";
private static final String WAY_ERR = "Decrease the number of vehicles or increase the flags to take long via graph.bytes_for_flags=8";
private final List<AbstractFlagEncoder> edgeEncoders = new ArrayList<AbstractFlagEncoder>();
private final int bitsForEdgeFlags;
private final int bitsForTurnFlags = 8 * 4;
private int nextWayBit = 0;
private int nextNodeBit = 0;
private int nextRelBit = 0;
private int nextTurnBit = 0;
private boolean enableInstructions = true;
private String preferredLanguage = "";
/**
* Instantiate manager with the given list of encoders. The manager knows several default
* encoders ignoring case.
* <p>
*
* @param flagEncodersStr comma delimited list of encoders. The order does not matter.
*/
public EncodingManager(String flagEncodersStr) {
this(flagEncodersStr, 4);
}
public EncodingManager(String flagEncodersStr, int bytesForEdgeFlags) {
this(FlagEncoderFactory.DEFAULT, flagEncodersStr, bytesForEdgeFlags);
}
public EncodingManager(FlagEncoderFactory factory, String flagEncodersStr, int bytesForEdgeFlags) {
this(parseEncoderString(factory, flagEncodersStr), bytesForEdgeFlags);
}
/**
* Instantiate manager with the given list of encoders.
* <p>
*
* @param flagEncoders comma delimited list of encoders. The order does not matter.
*/
public EncodingManager(FlagEncoder... flagEncoders) {
this(Arrays.asList(flagEncoders));
}
/**
* Instantiate manager with the given list of encoders.
* <p>
*
* @param flagEncoders comma delimited list of encoders. The order does not matter.
*/
public EncodingManager(List<? extends FlagEncoder> flagEncoders) {
this(flagEncoders, 4);
}
public EncodingManager(List<? extends FlagEncoder> flagEncoders, int bytesForEdgeFlags) {
if (bytesForEdgeFlags != 4 && bytesForEdgeFlags != 8)
throw new IllegalStateException("For 'edge flags' currently only 4 or 8 bytes supported");
this.bitsForEdgeFlags = bytesForEdgeFlags * 8;
for (FlagEncoder flagEncoder : flagEncoders) {
registerEncoder((AbstractFlagEncoder) flagEncoder);
}
if (edgeEncoders.isEmpty())
throw new IllegalStateException("No vehicles found");
}
static List<FlagEncoder> parseEncoderString(FlagEncoderFactory factory, String encoderList) {
if (encoderList.contains(":"))
throw new IllegalArgumentException("EncodingManager does no longer use reflection instantiate encoders directly.");
if (!encoderList.equals(encoderList.toLowerCase()))
throw new IllegalArgumentException("Since 0.7 EncodingManager does no longer accept upper case profiles: " + encoderList);
String[] entries = encoderList.split(",");
List<FlagEncoder> resultEncoders = new ArrayList<FlagEncoder>();
for (String entry : entries) {
entry = entry.trim().toLowerCase();
if (entry.isEmpty())
continue;
String entryVal = "";
if (entry.contains("|")) {
entryVal = entry;
entry = entry.split("\\|")[0];
}
PMap configuration = new PMap(entryVal);
FlagEncoder fe = factory.createFlagEncoder(entry, configuration);
if (configuration.has("version") && fe.getVersion() != configuration.getInt("version", -1))
throw new IllegalArgumentException("Encoder " + entry + " was used in version "
+ configuration.getLong("version", -1) + ", but current version is " + fe.getVersion());
resultEncoders.add(fe);
}
return resultEncoders;
}
static String fixWayName(String str) {
if (str == null)
return "";
return str.replaceAll(";[ ]*", ", ");
}
/**
* Create the EncodingManager from the provided GraphHopper location. Throws an
* IllegalStateException if it fails. Used if no EncodingManager specified on load.
*/
public static EncodingManager create(FlagEncoderFactory factory, String ghLoc) {
Directory dir = new RAMDirectory(ghLoc, true);
StorableProperties properties = new StorableProperties(dir);
if (!properties.loadExisting())
throw new IllegalStateException("Cannot load properties to fetch EncodingManager configuration at: "
+ dir.getLocation());
// check encoding for compatibility
properties.checkVersions(false);
String acceptStr = properties.get("graph.flag_encoders");
if (acceptStr.isEmpty())
throw new IllegalStateException("EncodingManager was not configured. And no one was found in the graph: "
+ dir.getLocation());
int bytesForFlags = 4;
if ("8".equals(properties.get("graph.bytes_for_flags")))
bytesForFlags = 8;
return new EncodingManager(factory, acceptStr, bytesForFlags);
}
public int getBytesForFlags() {
return bitsForEdgeFlags / 8;
}
private void registerEncoder(AbstractFlagEncoder encoder) {
if (encoder.isRegistered())
throw new IllegalStateException("You must not register a FlagEncoder (" + encoder.toString() + ") twice!");
for (FlagEncoder fe : edgeEncoders) {
if (fe.toString().equals(encoder.toString()))
throw new IllegalArgumentException("Cannot register edge encoder. Name already exists: " + fe.toString());
}
encoder.setRegistered(true);
int encoderCount = edgeEncoders.size();
int usedBits = encoder.defineNodeBits(encoderCount, nextNodeBit);
if (usedBits > bitsForEdgeFlags)
throw new IllegalArgumentException(String.format(ERR, usedBits, bitsForEdgeFlags, "node"));
encoder.setNodeBitMask(usedBits - nextNodeBit, nextNodeBit);
nextNodeBit = usedBits;
usedBits = encoder.defineWayBits(encoderCount, nextWayBit);
if (usedBits > bitsForEdgeFlags)
throw new IllegalArgumentException(String.format(ERR, usedBits, bitsForEdgeFlags, "way") + WAY_ERR);
encoder.setWayBitMask(usedBits - nextWayBit, nextWayBit);
nextWayBit = usedBits;
usedBits = encoder.defineRelationBits(encoderCount, nextRelBit);
if (usedBits > bitsForEdgeFlags)
throw new IllegalArgumentException(String.format(ERR, usedBits, bitsForEdgeFlags, "relation"));
encoder.setRelBitMask(usedBits - nextRelBit, nextRelBit);
nextRelBit = usedBits;
// turn flag bits are independent from edge encoder bits
usedBits = encoder.defineTurnBits(encoderCount, nextTurnBit);
if (usedBits > bitsForTurnFlags)
throw new IllegalArgumentException(String.format(ERR, usedBits, bitsForTurnFlags, "turn"));
nextTurnBit = usedBits;
edgeEncoders.add(encoder);
}
/**
* @return true if the specified encoder is found
*/
public boolean supports(String encoder) {
return getEncoder(encoder, false) != null;
}
public FlagEncoder getEncoder(String name) {
return getEncoder(name, true);
}
private FlagEncoder getEncoder(String name, boolean throwExc) {
for (FlagEncoder encoder : edgeEncoders) {
if (name.equalsIgnoreCase(encoder.toString()))
return encoder;
}
if (throwExc)
throw new IllegalArgumentException("Encoder for " + name + " not found. Existing: " + toDetailsString());
return null;
}
/**
* Determine whether a way is routable for one of the added encoders.
*/
public long acceptWay(ReaderWay way) {
long includeWay = 0;
for (AbstractFlagEncoder encoder : edgeEncoders) {
includeWay |= encoder.acceptWay(way);
}
return includeWay;
}
public long handleRelationTags(ReaderRelation relation, long oldRelationFlags) {
long flags = 0;
for (AbstractFlagEncoder encoder : edgeEncoders) {
flags |= encoder.handleRelationTags(relation, oldRelationFlags);
}
return flags;
}
/**
* Processes way properties of different kind to determine speed and direction. Properties are
* directly encoded in 8 bytes.
* <p>
*
* @param relationFlags The preprocessed relation flags is used to influence the way properties.
* @return the encoded flags
*/
public long handleWayTags(ReaderWay way, long includeWay, long relationFlags) {
long flags = 0;
for (AbstractFlagEncoder encoder : edgeEncoders) {
flags |= encoder.handleWayTags(way, includeWay, relationFlags & encoder.getRelBitMask());
}
return flags;
}
@Override
public String toString() {
StringBuilder str = new StringBuilder();
for (FlagEncoder encoder : edgeEncoders) {
if (str.length() > 0)
str.append(",");
str.append(encoder.toString());
}
return str.toString();
}
public String toDetailsString() {
StringBuilder str = new StringBuilder();
for (AbstractFlagEncoder encoder : edgeEncoders) {
if (str.length() > 0)
str.append(",");
str.append(encoder.toString())
.append("|")
.append(encoder.getPropertiesString())
.append("|version=")
.append(encoder.getVersion());
}
return str.toString();
}
public long flagsDefault(boolean forward, boolean backward) {
long flags = 0;
for (AbstractFlagEncoder encoder : edgeEncoders) {
flags |= encoder.flagsDefault(forward, backward);
}
return flags;
}
/**
* Reverse flags, to do so all encoders are called.
*/
public long reverseFlags(long flags) {
// performance critical
int len = edgeEncoders.size();
for (int i = 0; i < len; i++) {
flags = edgeEncoders.get(i).reverseFlags(flags);
}
return flags;
}
@Override
public int hashCode() {
int hash = 5;
hash = 53 * hash + (this.edgeEncoders != null ? this.edgeEncoders.hashCode() : 0);
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final EncodingManager other = (EncodingManager) obj;
if (this.edgeEncoders != other.edgeEncoders
&& (this.edgeEncoders == null || !this.edgeEncoders.equals(other.edgeEncoders))) {
return false;
}
return true;
}
/**
* Analyze tags on osm node. Store node tags (barriers etc) for later usage while parsing way.
*/
public long handleNodeTags(ReaderNode node) {
long flags = 0;
for (AbstractFlagEncoder encoder : edgeEncoders) {
flags |= encoder.handleNodeTags(node);
}
return flags;
}
public EncodingManager setEnableInstructions(boolean enableInstructions) {
this.enableInstructions = enableInstructions;
return this;
}
public EncodingManager setPreferredLanguage(String preferredLanguage) {
if (preferredLanguage == null)
throw new IllegalArgumentException("preferred language cannot be null");
this.preferredLanguage = preferredLanguage;
return this;
}
public void applyWayTags(ReaderWay way, EdgeIteratorState edge) {
// storing the road name does not yet depend on the flagEncoder so manage it directly
if (enableInstructions) {
// String wayInfo = carFlagEncoder.getWayInfo(way);
// http://wiki.openstreetmap.org/wiki/Key:name
String name = "";
if (!preferredLanguage.isEmpty())
name = fixWayName(way.getTag("name:" + preferredLanguage));
if (name.isEmpty())
name = fixWayName(way.getTag("name"));
// http://wiki.openstreetmap.org/wiki/Key:ref
String refName = fixWayName(way.getTag("ref"));
if (!refName.isEmpty()) {
if (name.isEmpty())
name = refName;
else
name += ", " + refName;
}
edge.setName(name);
}
for (AbstractFlagEncoder encoder : edgeEncoders) {
encoder.applyWayTags(way, edge);
}
}
/**
* The returned list is never empty.
*/
public List<FlagEncoder> fetchEdgeEncoders() {
List<FlagEncoder> list = new ArrayList<FlagEncoder>();
list.addAll(edgeEncoders);
return list;
}
public boolean needsTurnCostsSupport() {
for (FlagEncoder encoder : edgeEncoders) {
if (encoder.supports(TurnWeighting.class))
return true;
}
return false;
}
}