Repository: graphhopper/graphhopper
Pull request #936 (2017-01-16 06:41:11)

Support 'height', 'weight', 'width' road attributes
Unrecognized values will be logged and ignored.

karussell: When look into the responsibility the OSM related parsing and converting should be done in the OSM reader module not in the encoder. But when I move it there we have to convert regardless if it is enabled or not (e.g. using generic or not). Hmmh, interesting ;)
karussell: But this is worth a separate issue #939
karussell: Interesting. I was able to push to the road_attributes branch of Transtech/graphhopper ... even a force push. Seems that github has improved this to work together with different repos on the same pull request, nice. Please have a look- hope I didn't messed up something!
tmnguyen-swin: Guys, I have committed a new change that makes road attribute encoders configurable. Merging fine with master. Let me know if there is anything else you would like me to change.
karussell: Thanks, this looks good.

Let us (shortly) discuss if we need three configurations or if one e.g. `"store_truck_attributes":true` is simpler (are there meaningful use cases where one would just need e.g. the weight?). Also the name is important, I prefer something with 'store_xy' and how the weighting interprets this value is up to the weighting.

Furthermore the boolean methods should be setXY instead of enableXY

> Btw, I have resolved some conflicts when merging Peter's changes. Hopefully I didn't miss out anything :)

Sorry, I feared that. Force push is never a good idea if one works on the same branch with others :(. So instead I should have made my changes and the rebase/squash should have been done as the last step.
karussell: Would you mind to just rename the methods? We do not need to debate on configuring and should just change it afterwards when necessary (?)
tmnguyen-swin: I have renamed the methods and variables as requested. This naming convention makes more sense to me now. Please review and advise.

In relation to choosing single configuration field vs multiple configuration fields for road attributes, IMHO, I would go with multiple fields. That would make it more flexible as you would be able to select the attributes of interest, rather than all or nothing. This is especially true when we are still limited to 64 bit length of the flag & the cost of having all fields is expensive.
karussell: This looks good now - thanks again!

> This is especially true when we are still limited to 64 bit length of the flag & the cost of having all fields is expensive.

Yeah, this is ugly and needs a fix (better sooner than later :))

Patch:
diff --git a/core/src/main/java/com/graphhopper/routing/util/DataFlagEncoder.java b/core/src/main/java/com/graphhopper/routing/util/DataFlagEncoder.java
index 4c564761c..e58f96a86 100644
--- a/core/src/main/java/com/graphhopper/routing/util/DataFlagEncoder.java
+++ b/core/src/main/java/com/graphhopper/routing/util/DataFlagEncoder.java
@@ -22,7 +22,10 @@ import com.graphhopper.reader.ReaderWay;
 import com.graphhopper.routing.weighting.GenericWeighting;
 import com.graphhopper.util.ConfigMap;
 import com.graphhopper.util.EdgeIteratorState;
+import com.graphhopper.util.Helper;
 import com.graphhopper.util.PMap;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.util.*;
 import java.util.Map.Entry;
@@ -38,6 +41,8 @@ import java.util.Map.Entry;
  */
 public class DataFlagEncoder extends AbstractFlagEncoder {
 
+    private static final Logger LOG = LoggerFactory.getLogger(DataFlagEncoder.class);
+
     private static final Map<String, Double> DEFAULT_SPEEDS = new LinkedHashMap<String, Double>() {
         {
             put("motorway", 100d);
@@ -70,14 +75,34 @@ public class DataFlagEncoder extends AbstractFlagEncoder {
     private long bit0;
     private EncodedDoubleValue carFwdMaxspeedEncoder;
     private EncodedDoubleValue carBwdMaxspeedEncoder;
+    private EncodedDoubleValue heightEncoder;
+    private EncodedDoubleValue weightEncoder;
+    private EncodedDoubleValue widthEncoder;
     private EncodedValue surfaceEncoder;
     private EncodedValue highwayEncoder;
     private EncodedValue transportModeEncoder;
     private EncodedValue accessEncoder;
+    private boolean storeHeight = false;
+    private boolean storeWeight = false;
+    private boolean storeWidth = false;
 
     public DataFlagEncoder() {
+        this(5, 5, 0);
+    }
+
+    public DataFlagEncoder(PMap properties) {
+        this((int) properties.getLong("speed_bits", 5),
+                properties.getDouble("speed_factor", 5),
+                properties.getBool("turn_costs", false) ? 1 : 0);
+        this.properties = properties;
+        this.setStoreHeight(properties.getBool("store_height", false));
+        this.setStoreWeight(properties.getBool("store_weight", false));
+        this.setStoreWidth(properties.getBool("store_width", false));
+    }
+
+    public DataFlagEncoder(int speedBits, double speedFactor, int maxTurnCosts) {
         // TODO include turn information
-        super(5, 5, 0);
+        super(speedBits, speedFactor, maxTurnCosts);
 
         maxPossibleSpeed = 140;
         //
@@ -131,7 +156,6 @@ public class DataFlagEncoder extends AbstractFlagEncoder {
                 "no"
         );
 
-
         counter = 0;
         for (String s : accessList) {
             accessMap.put(s, counter++);
@@ -156,6 +180,24 @@ public class DataFlagEncoder extends AbstractFlagEncoder {
         carBwdMaxspeedEncoder = new EncodedDoubleValue("car bwd maxspeed", shift, speedBits, speedFactor, 0, maxPossibleSpeed, true);
         shift += carBwdMaxspeedEncoder.getBits();
 
+        /* Value range: [3.0m, 5.4m] */
+        if (isStoreHeight()) {
+            heightEncoder = new EncodedDoubleValue("height restriction", shift, 7, 0.1, 0, 12, true);
+            shift += heightEncoder.getBits();
+        }
+
+        /* Value range: [1.0t, 59.5t] */
+        if (isStoreWeight()) {
+            weightEncoder = new EncodedDoubleValue("weight restriction", shift, 10, 0.1, 0, 100, true);
+            shift += weightEncoder.getBits();
+        }
+
+        /* Value range: [2.5m, 3.5m] */
+        if (isStoreWidth()) {
+            widthEncoder = new EncodedDoubleValue("width restriction", shift, 6, 0.1, 0, 6, true);
+            shift += widthEncoder.getBits();
+        }
+
         highwayEncoder = new EncodedValue("highway", shift, 5, 1, 0, highwayMap.size(), true);
         shift += highwayEncoder.getBits();
 
@@ -276,6 +318,22 @@ public class DataFlagEncoder extends AbstractFlagEncoder {
             if (bwdSpeed > 0)
                 flags = carBwdMaxspeedEncoder.setDoubleValue(flags, bwdSpeed);
 
+            // Road attributes (height, weight, width)
+            if (isStoreHeight()) {
+                List<String> heightTags = Arrays.asList("maxheight", "maxheight:physical");
+                flags = extractMeter(way, flags, heightEncoder, heightTags);
+            }
+
+            if (isStoreWeight()) {
+                List<String> weightTags = Arrays.asList("maxweight", "maxgcweight");
+                flags = extractTons(way, flags, weightEncoder, weightTags);
+            }
+
+            if (isStoreWidth()) {
+                List<String> widthTags = Arrays.asList("maxwidth", "maxwidth:physical");
+                flags = extractMeter(way, flags, widthEncoder, widthTags);
+            }
+
             // SURFACE
             String surfaceValue = way.getTag("surface");
             Integer sValue = surfaceMap.get(surfaceValue);
@@ -327,6 +385,101 @@ public class DataFlagEncoder extends AbstractFlagEncoder {
         }
     }
 
+    private long extractMeter(ReaderWay way, long flags, EncodedDoubleValue valueEncoder, List<String> keys) {
+        String value = way.getFirstPriorityTag(keys);
+        if (Helper.isEmpty(value)) return flags;
+
+        double val;
+        try {
+            val = stringToMeter(value);
+        } catch (Exception ex) {
+            LOG.warn("Unable to extract meter from malformed road attribute '{}' for way (OSM_ID = {}).", value, way.getId(), ex);
+            return flags;
+        }
+
+        try {
+            flags = valueEncoder.setDoubleValue(flags, val);
+        }
+        catch (IllegalArgumentException e) {
+            LOG.warn("Unable to process value '{}' for way (OSM_ID = {}).", val, way.getId(), e);
+        }
+
+        return flags;
+    }
+
+    private long extractTons(ReaderWay way, long flags, EncodedDoubleValue valueEncoder, List<String> keys) {
+        String value = way.getFirstPriorityTag(keys);
+        if (Helper.isEmpty(value)) return flags;
+
+        double val;
+        try {
+            val = stringToTons(value);
+        } catch (Throwable t) {
+            LOG.warn("Unable to extract tons from malformed road attribute '{}' for way (OSM_ID = {}).", value, way.getId(), t);
+            return flags;
+        }
+
+        try {
+            flags = valueEncoder.setDoubleValue(flags, val);
+        } catch (IllegalArgumentException e) {
+            LOG.warn("Unable to process tons value '{}' for way (OSM_ID = {}).", val, way.getId(), e);
+        }
+
+        return flags;
+    }
+
+    public static double stringToTons(String value) {
+        value = value.toLowerCase().replaceAll(" ", "").replaceAll("(tons|ton)", "t");
+        value = value.replace("mgw", "").trim();
+        double factor = 1;
+        if (value.endsWith("t")) {
+            value = value.substring(0, value.length() - 1);
+        } else if (value.endsWith("lbs")) {
+            value = value.substring(0, value.length() - 3);
+            factor = 0.00045359237;
+        }
+
+        return Double.parseDouble(value) * factor;
+    }
+
+    public static double stringToMeter(String value) {
+        value = value.toLowerCase().replaceAll(" ", "").replaceAll("(meters|meter|mtrs|mtr|mt|m\\.)", "m");
+        double factor = 1;
+        double offset = 0;
+        value = value.replaceAll("(\\\"|\'\')", "in").replaceAll("(\'|feet)", "ft");
+        if (value.startsWith("~") || value.contains("approx")) {
+            value = value.replaceAll("(\\~|approx)", "").trim();
+            factor = 0.8;
+        }
+
+        if (value.endsWith("in")) {
+            int startIndex = value.indexOf("ft");
+            String inchValue;
+            if (startIndex < 0) {
+                startIndex = 0;
+            } else {
+                startIndex += 2;
+            }
+
+            inchValue = value.substring(startIndex, value.length() - 2);
+            value = value.substring(0, startIndex);
+            offset = Double.parseDouble(inchValue) * 0.0254;
+        }
+
+        if (value.endsWith("ft")) {
+            value = value.substring(0, value.length() - 2);
+            factor *= 0.3048;
+        } else if (value.endsWith("m")) {
+            value = value.substring(0, value.length() - 1);
+        }
+
+        if (value.isEmpty()) {
+            return offset;
+        } else {
+            return Double.parseDouble(value) * factor + offset;
+        }
+    }
+
     @Override
     public long reverseFlags(long flags) {
         // see #728 for an explanation
@@ -475,6 +628,21 @@ public class DataFlagEncoder extends AbstractFlagEncoder {
         return val;
     }
 
+    public double getHeight(EdgeIteratorState edge) {
+        long flags = edge.getFlags();
+        return heightEncoder.getDoubleValue(flags);
+    }
+
+    public double getWeight(EdgeIteratorState edge) {
+        long flags = edge.getFlags();
+        return weightEncoder.getDoubleValue(flags);
+    }
+
+    public double getWidth(EdgeIteratorState edge) {
+        long flags = edge.getFlags();
+        return widthEncoder.getDoubleValue(flags);
+    }
+
     @Override
     public long flagsDefault(boolean forward, boolean backward) {
         // just pick car mode to set access values?
@@ -553,6 +721,41 @@ public class DataFlagEncoder extends AbstractFlagEncoder {
         return GenericWeighting.class.isAssignableFrom(feature);
     }
 
+    public DataFlagEncoder setStoreHeight(boolean storeHeight) {
+        this.storeHeight = storeHeight;
+        return this;
+    }
+
+    public boolean isStoreHeight() {
+        return storeHeight;
+    }
+
+    public DataFlagEncoder setStoreWeight(boolean storeWeight) {
+        this.storeWeight = storeWeight;
+        return this;
+    }
+
+    public boolean isStoreWeight() {
+        return storeWeight;
+    }
+
+    public DataFlagEncoder setStoreWidth(boolean storeWidth) {
+        this.storeWidth = storeWidth;
+        return this;
+    }
+
+    public boolean isStoreWidth() {
+        return storeWidth;
+    }
+
+    @Override
+    protected String getPropertiesString() {
+        return super.getPropertiesString() +
+                "|store_height=" + storeHeight +
+                "|store_weight=" + storeWeight +
+                "|store_width=" + storeWidth;
+    }
+
     @Override
     public int getVersion() {
         return 1;
@@ -575,9 +778,20 @@ public class DataFlagEncoder extends AbstractFlagEncoder {
 
         ConfigMap cMap = new ConfigMap();
         cMap.put("highways", map);
+
+        cloneDoubleAttribute(weightingMap, cMap, GenericWeighting.HEIGHT_LIMIT, 0d);
+        cloneDoubleAttribute(weightingMap, cMap, GenericWeighting.WEIGHT_LIMIT, 0d);
+        cloneDoubleAttribute(weightingMap, cMap, GenericWeighting.WIDTH_LIMIT, 0d);
+
         return cMap;
     }
 
+    private void cloneDoubleAttribute(PMap weightingMap, ConfigMap cMap, String key, double _default) {
+        if (weightingMap.has(key))
+            cMap.put(key, weightingMap.getDouble(key, _default));
+
+    }
+
     public enum AccessValue {
 
         ACCESSIBLE,
diff --git a/core/src/main/java/com/graphhopper/routing/util/DefaultFlagEncoderFactory.java b/core/src/main/java/com/graphhopper/routing/util/DefaultFlagEncoderFactory.java
index 4a8dc89a7..2da33085b 100644
--- a/core/src/main/java/com/graphhopper/routing/util/DefaultFlagEncoderFactory.java
+++ b/core/src/main/java/com/graphhopper/routing/util/DefaultFlagEncoderFactory.java
@@ -28,7 +28,7 @@ public class DefaultFlagEncoderFactory implements FlagEncoderFactory {
     @Override
     public FlagEncoder createFlagEncoder(String name, PMap configuration) {
         if (name.equals(GENERIC))
-            return new DataFlagEncoder();
+            return new DataFlagEncoder(configuration);
 
         else if (name.equals(CAR))
             return new CarFlagEncoder(configuration);
diff --git a/core/src/main/java/com/graphhopper/routing/util/EncodedDoubleValue.java b/core/src/main/java/com/graphhopper/routing/util/EncodedDoubleValue.java
index 2773a961b..1afc77a23 100644
--- a/core/src/main/java/com/graphhopper/routing/util/EncodedDoubleValue.java
+++ b/core/src/main/java/com/graphhopper/routing/util/EncodedDoubleValue.java
@@ -50,7 +50,7 @@ public class EncodedDoubleValue extends EncodedValue {
 
     public long setDoubleValue(long flags, double value) {
         if (Double.isNaN(value))
-            throw new IllegalStateException("Value cannot be NaN");
+            throw new IllegalArgumentException("Value cannot be NaN");
 
         // scale value
         long tmpValue = Math.round(value / factor);
diff --git a/core/src/main/java/com/graphhopper/routing/util/EncodedValue.java b/core/src/main/java/com/graphhopper/routing/util/EncodedValue.java
index f6d9074ad..db8faf9ef 100644
--- a/core/src/main/java/com/graphhopper/routing/util/EncodedValue.java
+++ b/core/src/main/java/com/graphhopper/routing/util/EncodedValue.java
@@ -85,6 +85,10 @@ public class EncodedValue {
         return flags | value;
     }
 
+    public String getName() {
+        return name;
+    }
+
     public long getValue(long flags) {
         // find value
         flags &= mask;
diff --git a/core/src/main/java/com/graphhopper/routing/weighting/GenericWeighting.java b/core/src/main/java/com/graphhopper/routing/weighting/GenericWeighting.java
index da7f8f04c..3b9fa3fb9 100644
--- a/core/src/main/java/com/graphhopper/routing/weighting/GenericWeighting.java
+++ b/core/src/main/java/com/graphhopper/routing/weighting/GenericWeighting.java
@@ -37,21 +37,28 @@ import java.util.List;
  * @author Peter Karich
  */
 public class GenericWeighting extends AbstractWeighting {
+
+    public static final String HEIGHT_LIMIT = "height";
+    public static final String WEIGHT_LIMIT = "weight";
+    public static final String WIDTH_LIMIT = "width";
     /**
      * Convert to milliseconds for correct calcMillis.
      */
-    private final static double SPEED_CONV = 3600;
-    private final double headingPenalty;
-    private final long headingPenaltyMillis;
-    private final double maxSpeed;
-    private final DataFlagEncoder gEncoder;
-    private final double[] speedArray;
-    private final int accessType;
-    private final int eventuallAccessiblePenalty = 10;
-
-    private final GHIntHashSet blockedEdges;
-    private final List<Shape> blockedShapes;
-    private NodeAccess na;
+    protected final static double SPEED_CONV = 3600;
+    protected final double headingPenalty;
+    protected final long headingPenaltyMillis;
+    protected final double maxSpeed;
+    protected final DataFlagEncoder gEncoder;
+    protected final double[] speedArray;
+    protected final int accessType;
+    protected final int eventuallAccessiblePenalty = 10;
+
+    protected final double height;
+    protected final double weight;
+    protected final double width;
+    protected final GHIntHashSet blockedEdges;
+    protected final List<Shape> blockedShapes;
+    protected NodeAccess na;
 
     public GenericWeighting(DataFlagEncoder encoder, ConfigMap cMap) {
         super(encoder);
@@ -72,6 +79,9 @@ public class GenericWeighting extends AbstractWeighting {
         accessType = gEncoder.getAccessType("motor_vehicle");
         blockedEdges = cMap.get(GraphEdgeIdFinder.BLOCKED_EDGES, new GHIntHashSet(0));
         blockedShapes = cMap.get(GraphEdgeIdFinder.BLOCKED_SHAPES, Collections.EMPTY_LIST);
+        height = cMap.getDouble(HEIGHT_LIMIT, 0d);
+        weight = cMap.getDouble(WEIGHT_LIMIT, 0d);
+        width = cMap.getDouble(WIDTH_LIMIT, 0d);
     }
 
     @Override
@@ -88,6 +98,11 @@ public class GenericWeighting extends AbstractWeighting {
         } else if (!gEncoder.isForward(edgeState, accessType))
             return Double.POSITIVE_INFINITY;
 
+        if ((gEncoder.isStoreHeight() && overLimit(height, gEncoder.getHeight(edgeState))) ||
+                (gEncoder.isStoreWeight() && overLimit(weight, gEncoder.getWeight(edgeState))) ||
+                (gEncoder.isStoreWidth() && overLimit(width, gEncoder.getWidth(edgeState))))
+            return Double.POSITIVE_INFINITY;
+
         if(!blockedEdges.isEmpty() && blockedEdges.contains(edgeState.getEdge())){
             return Double.POSITIVE_INFINITY;
         }
@@ -114,6 +129,10 @@ public class GenericWeighting extends AbstractWeighting {
         return time;
     }
 
+    private boolean overLimit(double height, double heightLimit) {
+        return height > 0 && heightLimit > 0 && height >= heightLimit;
+    }
+
     @Override
     public long calcMillis(EdgeIteratorState edgeState, boolean reverse, int prevOrNextEdgeId) {
         // TODO to avoid expensive reverse flags include oneway accessibility


Test patch:
diff --git a/core/src/test/java/com/graphhopper/routing/util/DataFlagEncoderTest.java b/core/src/test/java/com/graphhopper/routing/util/DataFlagEncoderTest.java
index 1510a5a98..553982224 100644
--- a/core/src/test/java/com/graphhopper/routing/util/DataFlagEncoderTest.java
+++ b/core/src/test/java/com/graphhopper/routing/util/DataFlagEncoderTest.java
@@ -1,12 +1,11 @@
 package com.graphhopper.routing.util;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
+import java.util.Arrays;
 import java.util.LinkedHashMap;
 import java.util.Map;
 
+import com.graphhopper.util.PMap;
+import org.junit.Ignore;
 import org.junit.Test;
 
 import com.graphhopper.reader.ReaderWay;
@@ -16,21 +15,46 @@ import com.graphhopper.util.EdgeIteratorState;
 import com.graphhopper.util.GHUtility;
 import com.graphhopper.util.Helper;
 
+import static org.junit.Assert.*;
+
 /**
  * @author Peter Karich
  */
 public class DataFlagEncoderTest {
+    private final PMap properties;
     private final DataFlagEncoder encoder;
     private final EncodingManager encodingManager;
     private final int motorVehicleInt;
 
+    private final double DELTA = 0.1;
+
     public DataFlagEncoderTest() {
-        encoder = new DataFlagEncoder();
-        encodingManager = new EncodingManager(encoder);
+        properties = new PMap();
+        properties.put("store_height", true);
+        properties.put("store_weight", true);
+        properties.put("store_width", true);
+        encoder = new DataFlagEncoder(properties);
+        encodingManager = new EncodingManager(Arrays.asList(encoder), 8);
 
         motorVehicleInt = encoder.getAccessType("motor_vehicle");
     }
 
+    @Test(expected = IllegalArgumentException.class)
+    public void testInsufficientEncoderBitLength() {
+        EncodingManager em = new EncodingManager(Arrays.asList(new DataFlagEncoder(properties)));
+    }
+
+    @Test
+    public void testSufficientEncoderBitLength() {
+        try {
+            EncodingManager em = new EncodingManager(Arrays.asList(new DataFlagEncoder(properties)), 8);
+            EncodingManager em1 = new EncodingManager(Arrays.asList(new DataFlagEncoder()));
+        }
+        catch (Throwable t) {
+            fail();
+        }
+    }
+
     @Test
     public void testHighway() {
         ReaderWay osmWay = new ReaderWay(0);
@@ -223,4 +247,57 @@ public class DataFlagEncoderTest {
         osmWay.setTag("highway", "building");
         assertTrue(encoder.acceptWay(osmWay) == 0);
     }
+
+    @Test
+    public void stringToMeter() {
+        assertEquals(1.5, DataFlagEncoder.stringToMeter("1.5"), DELTA);
+        assertEquals(1.5, DataFlagEncoder.stringToMeter("1.5m"), DELTA);
+        assertEquals(1.5, DataFlagEncoder.stringToMeter("1.5 m"), DELTA);
+        assertEquals(1.5, DataFlagEncoder.stringToMeter("1.5   m"), DELTA);
+        assertEquals(1.5, DataFlagEncoder.stringToMeter("1.5 meter"), DELTA);
+        assertEquals(1.5, DataFlagEncoder.stringToMeter("4 ft 11 in"), DELTA);
+        assertEquals(1.5, DataFlagEncoder.stringToMeter("4'11''"), DELTA);
+
+
+        assertEquals(3, DataFlagEncoder.stringToMeter("3 m."), DELTA);
+        assertEquals(3, DataFlagEncoder.stringToMeter("3meters"), DELTA);
+        assertEquals(0.8 * 3, DataFlagEncoder.stringToMeter("~3"), DELTA);
+        assertEquals(3 * 0.8, DataFlagEncoder.stringToMeter("3 m approx"), DELTA);
+
+        // 2.743 + 0.178
+        assertEquals(2.921, DataFlagEncoder.stringToMeter("9 ft 7in"), DELTA);
+        assertEquals(2.921, DataFlagEncoder.stringToMeter("9'7\""), DELTA);
+        assertEquals(2.921, DataFlagEncoder.stringToMeter("9'7''"), DELTA);
+        assertEquals(2.921, DataFlagEncoder.stringToMeter("9' 7\""), DELTA);
+
+        assertEquals(2.743, DataFlagEncoder.stringToMeter("9'"), DELTA);
+        assertEquals(2.743, DataFlagEncoder.stringToMeter("9 feet"), DELTA);
+    }
+
+    @Test(expected = NumberFormatException.class)
+    public void stringToMeterException() {
+        // Unexpected values
+        DataFlagEncoder.stringToMeter("height limit 1.5m");
+    }
+
+    @Test
+    public void stringToTons() {
+        assertEquals(1.5, DataFlagEncoder.stringToTons("1.5"), DELTA);
+        assertEquals(1.5, DataFlagEncoder.stringToTons("1.5 t"), DELTA);
+        assertEquals(1.5, DataFlagEncoder.stringToTons("1.5   t"), DELTA);
+        assertEquals(1.5, DataFlagEncoder.stringToTons("1.5 tons"), DELTA);
+        assertEquals(1.5, DataFlagEncoder.stringToTons("1.5 ton"), DELTA);
+        assertEquals(1.5, DataFlagEncoder.stringToTons("3306.9 lbs"), DELTA);
+        assertEquals(3, DataFlagEncoder.stringToTons("3 T"), DELTA);
+        assertEquals(3, DataFlagEncoder.stringToTons("3ton"), DELTA);
+
+        // maximum gross weight
+        assertEquals(6, DataFlagEncoder.stringToTons("6t mgw"), DELTA);
+    }
+
+    @Test(expected = NumberFormatException.class)
+    public void stringToTonsException() {
+        // Unexpected values
+        DataFlagEncoder.stringToTons("weight limit 1.5t");
+    }
 }
diff --git a/core/src/test/java/com/graphhopper/routing/weighting/GenericWeightingTest.java b/core/src/test/java/com/graphhopper/routing/weighting/GenericWeightingTest.java
index 02f0ca3d1..d94f7da4b 100644
--- a/core/src/test/java/com/graphhopper/routing/weighting/GenericWeightingTest.java
+++ b/core/src/test/java/com/graphhopper/routing/weighting/GenericWeightingTest.java
@@ -33,6 +33,7 @@ import org.junit.Before;
 import org.junit.Test;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 import static org.junit.Assert.assertEquals;
@@ -41,17 +42,28 @@ import static org.junit.Assert.assertEquals;
  * @author Peter Karich
  */
 public class GenericWeightingTest {
-    private final DataFlagEncoder encoder = new DataFlagEncoder();
-    private final EncodingManager em = new EncodingManager(encoder);
+    private final PMap properties;
+    private final DataFlagEncoder encoder;
+    private final EncodingManager em;
     private Graph graph;
 
     private final double edgeWeight = 566111;
 
+    public GenericWeightingTest() {
+        properties = new PMap();
+        properties.put("store_height", true);
+        properties.put("store_weight", true);
+        properties.put("store_width", true);
+        encoder = new DataFlagEncoder(properties);
+        em = new EncodingManager(Arrays.asList(encoder), 8);
+    }
+
     @Before
     public void setUp() {
         ReaderWay way = new ReaderWay(27l);
         way.setTag("highway", "primary");
         way.setTag("maxspeed", "10");
+        way.setTag("maxheight", "4.4");
 
         graph = new GraphBuilder(em).create();
         // 0-1
@@ -113,4 +125,42 @@ public class GenericWeightingTest {
         GenericWeighting weighting = new GenericWeighting(encoder, cMap);
         weighting.setGraph(null);
     }
+
+    @Test
+    public void testRoadAttributeRestriction() {
+        EdgeIteratorState edge = graph.getEdgeIteratorState(0, 1);
+        ConfigMap cMap = encoder.readStringMap(new PMap());
+        cMap.put(GenericWeighting.HEIGHT_LIMIT, 4.0);
+        Weighting instance = new GenericWeighting(encoder, cMap);
+        assertEquals(edgeWeight, instance.calcWeight(edge, false, EdgeIterator.NO_EDGE), 1e-8);
+
+        cMap.put(GenericWeighting.HEIGHT_LIMIT, 5.0);
+        instance = new GenericWeighting(encoder, cMap);
+        assertEquals(Double.POSITIVE_INFINITY, instance.calcWeight(edge, false, EdgeIterator.NO_EDGE), 1e-8);
+    }
+
+    @Test
+    public void testDisabledRoadAttributes() {
+        DataFlagEncoder simpleEncoder = new DataFlagEncoder();
+        EncodingManager simpleEncodingManager = new EncodingManager(simpleEncoder);
+        Graph simpleGraph = new GraphBuilder(simpleEncodingManager).create();
+
+        ReaderWay way = new ReaderWay(27l);
+        way.setTag("highway", "primary");
+        way.setTag("maxspeed", "10");
+        way.setTag("maxheight", "4.4");
+
+        // 0-1
+        simpleGraph.edge(0, 1, 1, true);
+        AbstractRoutingAlgorithmTester.updateDistancesFor(simpleGraph, 0, 0.00, 0.00);
+        AbstractRoutingAlgorithmTester.updateDistancesFor(simpleGraph, 1, 0.01, 0.01);
+        simpleGraph.getEdgeIteratorState(0, 1).setFlags(simpleEncoder.handleWayTags(way, 1, 0));
+
+        EdgeIteratorState edge = simpleGraph.getEdgeIteratorState(0, 1);
+        ConfigMap cMap = simpleEncoder.readStringMap(new PMap());
+        cMap.put(GenericWeighting.HEIGHT_LIMIT, 5.0);
+        Weighting instance = new GenericWeighting(simpleEncoder, cMap);
+
+        assertEquals(edgeWeight, instance.calcWeight(edge, false, EdgeIterator.NO_EDGE), 1e-8);
+    }
 }
diff --git a/core/src/test/java/com/graphhopper/storage/GraphHopperStorageForDataFlagEncoderTest.java b/core/src/test/java/com/graphhopper/storage/GraphHopperStorageForDataFlagEncoderTest.java
new file mode 100644
index 000000000..82c849d83
--- /dev/null
+++ b/core/src/test/java/com/graphhopper/storage/GraphHopperStorageForDataFlagEncoderTest.java
@@ -0,0 +1,102 @@
+package com.graphhopper.storage;
+
+import com.graphhopper.GraphHopper;
+import com.graphhopper.reader.ReaderWay;
+import com.graphhopper.routing.AbstractRoutingAlgorithmTester;
+import com.graphhopper.routing.util.DataFlagEncoder;
+import com.graphhopper.routing.util.EncodingManager;
+import com.graphhopper.routing.util.FlagEncoder;
+import com.graphhopper.util.Helper;
+import com.graphhopper.util.PMap;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.Closeable;
+import java.io.File;
+import java.util.Arrays;
+
+import static org.junit.Assert.*;
+
+/**
+ * Created by tuan on 20/01/17.
+ */
+public class GraphHopperStorageForDataFlagEncoderTest {
+    private final String locationParent = "./target/graphstorage";
+    private int defaultSize = 100;
+    private String defaultGraphLoc = "./target/graphstorage/default";
+    private GraphHopperStorage graph;
+
+    private final PMap properties;
+    private final DataFlagEncoder encoder;
+    private final EncodingManager encodingManager;
+
+    public GraphHopperStorageForDataFlagEncoderTest() {
+        properties = new PMap();
+        properties.put("store_height", true);
+        properties.put("store_weight", true);
+        properties.put("store_width", false);
+        encoder = new DataFlagEncoder(properties);
+        encodingManager = new EncodingManager(Arrays.asList(encoder), 8);
+    }
+
+    @Before
+    public void setUp() {
+        Helper.removeDir(new File(locationParent));
+    }
+
+    @After
+    public void tearDown() {
+        Helper.close((Closeable) graph);
+        Helper.removeDir(new File(locationParent));
+    }
+
+
+    @Test
+    public void testStorageProperties() {
+        graph = new GraphBuilder(encodingManager).setStore(true).setLocation(defaultGraphLoc).create();
+
+        // 0-1
+        ReaderWay way_0_1 = new ReaderWay(27l);
+        way_0_1.setTag("highway", "primary");
+        way_0_1.setTag("maxheight", "4.4");
+
+        graph.edge(0, 1, 1, true);
+        AbstractRoutingAlgorithmTester.updateDistancesFor(graph, 0, 0.00, 0.00);
+        AbstractRoutingAlgorithmTester.updateDistancesFor(graph, 1, 0.01, 0.01);
+        graph.getEdgeIteratorState(0, 1).setFlags(encoder.handleWayTags(way_0_1, 1, 0));
+
+        // 1-2
+        ReaderWay way_1_2 = new ReaderWay(28l);
+        way_1_2.setTag("highway", "primary");
+        way_1_2.setTag("maxweight", "45");
+
+        graph.edge(1, 2, 1, true);
+        AbstractRoutingAlgorithmTester.updateDistancesFor(graph, 2, 0.02, 0.02);
+        graph.getEdgeIteratorState(1, 2).setFlags(encoder.handleWayTags(way_1_2, 1, 0));
+
+        // 2-0
+        ReaderWay way_2_0 = new ReaderWay(29l);
+        way_2_0.setTag("highway", "primary");
+        way_2_0.setTag("maxwidth", "5");
+
+        graph.edge(2, 0, 1, true);
+        graph.getEdgeIteratorState(2, 0).setFlags(encoder.handleWayTags(way_2_0, 1, 0));
+
+        graph.flush();
+        graph.close();
+
+        GraphHopper hopper = new GraphHopper().setGraphHopperLocation(defaultGraphLoc).setCHEnabled(false).importOrLoad();
+        EncodingManager em = hopper.getEncodingManager();
+        assertNotNull(em);
+        assertEquals(1, em.fetchEdgeEncoders().size());
+
+        FlagEncoder flagEncoder = em.fetchEdgeEncoders().get(0);
+        assertTrue(flagEncoder instanceof DataFlagEncoder);
+
+        DataFlagEncoder dataFlagEncoder = (DataFlagEncoder)flagEncoder;
+        assertTrue(dataFlagEncoder.isStoreHeight());
+        assertTrue(dataFlagEncoder.isStoreWeight());
+        assertFalse(dataFlagEncoder.isStoreWidth());
+    }
+}
diff --git a/reader-osm/src/test/java/com/graphhopper/GraphHopperIT.java b/reader-osm/src/test/java/com/graphhopper/GraphHopperIT.java
index f44cda5dc..768d943ca 100644
--- a/reader-osm/src/test/java/com/graphhopper/GraphHopperIT.java
+++ b/reader-osm/src/test/java/com/graphhopper/GraphHopperIT.java
@@ -253,7 +253,7 @@ public class GraphHopperIT {
                 setOSMFile(DIR + "/north-bayreuth.osm.gz").
                 setCHEnabled(false).
                 setGraphHopperLocation(tmpGraphFile).
-                setEncodingManager(new EncodingManager("generic", 4));
+                setEncodingManager(new EncodingManager("generic", 8));
         tmpHopper.importOrLoad();
 
         GHRequest req = new GHRequest(49.985272, 11.506151, 49.986107, 11.507202).
diff --git a/reader-osm/src/test/java/com/graphhopper/reader/osm/OSMReaderTest.java b/reader-osm/src/test/java/com/graphhopper/reader/osm/OSMReaderTest.java
index 58408b3ed..92f7c323c 100644
--- a/reader-osm/src/test/java/com/graphhopper/reader/osm/OSMReaderTest.java
+++ b/reader-osm/src/test/java/com/graphhopper/reader/osm/OSMReaderTest.java
@@ -64,6 +64,7 @@ public class OSMReaderTest {
     private final String fileNegIds = "test-osm-negative-ids.xml";
     private final String fileBarriers = "test-barriers.xml";
     private final String fileTurnRestrictions = "test-restrictions.xml";
+    private final String fileRoadAttributes = "test-road-attributes.xml";
     private final String dir = "./target/tmp/test-db";
     private CarFlagEncoder carEncoder;
     private BikeFlagEncoder bikeEncoder;
@@ -511,6 +512,49 @@ public class OSMReaderTest {
         assertTrue(bikeEncoder.isTurnRestricted(costsFlags));
     }
 
+    @Test
+    public void testRoadAttributes() {
+        GraphHopper hopper = new GraphHopperFacade(fileRoadAttributes);
+        DataFlagEncoder dataFlagEncoder = (new DataFlagEncoder()).setStoreHeight(true).setStoreWeight(true).setStoreWidth(true);
+        hopper.setEncodingManager(new EncodingManager(Arrays.asList(dataFlagEncoder), 8));
+        hopper.importOrLoad();
+
+        Graph graph = hopper.getGraphHopperStorage();
+        DataFlagEncoder encoder = (DataFlagEncoder) hopper.getEncodingManager().getEncoder("generic");
+        assertEquals(5, graph.getNodes());
+
+        int na = AbstractGraphStorageTester.getIdOf(graph, 11.1, 50);
+        int nb = AbstractGraphStorageTester.getIdOf(graph, 12, 51);
+        int nc = AbstractGraphStorageTester.getIdOf(graph, 11.2, 52);
+        int nd = AbstractGraphStorageTester.getIdOf(graph, 11.3, 51);
+        int ne = AbstractGraphStorageTester.getIdOf( graph, 10, 51 );
+
+        EdgeIteratorState edge_ab = GHUtility.getEdge(graph, na, nb);
+        EdgeIteratorState edge_ad = GHUtility.getEdge(graph, na, nd);
+        EdgeIteratorState edge_ae = GHUtility.getEdge(graph, na, ne);
+        EdgeIteratorState edge_bc = GHUtility.getEdge(graph, nb, nc);
+        EdgeIteratorState edge_bd = GHUtility.getEdge(graph, nb, nd);
+        EdgeIteratorState edge_cd = GHUtility.getEdge(graph, nc, nd);
+        EdgeIteratorState edge_ce = GHUtility.getEdge(graph, nc, ne);
+        EdgeIteratorState edge_de = GHUtility.getEdge(graph, nd, ne);
+
+        assertEquals(4.0, encoder.getHeight(edge_ab), 1e-5);
+        assertEquals(2.5, encoder.getWidth(edge_ab), 1e-5);
+        assertEquals(4.4, encoder.getWeight(edge_ab), 1e-5);
+
+        assertEquals(4.0, encoder.getHeight(edge_bc), 1e-5);
+        assertEquals(2.5, encoder.getWidth(edge_bc), 1e-5);
+        assertEquals(4.4, encoder.getWeight(edge_bc), 1e-5);
+
+        assertEquals(4.4, encoder.getHeight(edge_ad), 1e-5);
+        assertEquals(3.5, encoder.getWidth(edge_ad), 1e-5);
+        assertEquals(17.5, encoder.getWeight(edge_ad), 1e-5);
+
+        assertEquals(4.4, encoder.getHeight(edge_cd), 1e-5);
+        assertEquals(3.5, encoder.getWidth(edge_cd), 1e-5);
+        assertEquals(17.5, encoder.getWeight(edge_cd), 1e-5);
+    }
+
     @Test
     public void testEstimatedCenter() {
         final CarFlagEncoder encoder = new CarFlagEncoder() {
diff --git a/reader-osm/src/test/resources/com/graphhopper/reader/osm/test-road-attributes.xml b/reader-osm/src/test/resources/com/graphhopper/reader/osm/test-road-attributes.xml
new file mode 100644
index 000000000..8f919672c
--- /dev/null
+++ b/reader-osm/src/test/resources/com/graphhopper/reader/osm/test-road-attributes.xml
@@ -0,0 +1,67 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<osm version="0.6" generator="pbf2osm">
+
+    <!--  __B__
+         /  |  \
+        A___D__C
+         \  |  /
+          \_E_/
+    -->
+
+    <node id="10" lat="11.1" lon="50" uid="24853">
+        <tag k="name" v="A" />
+    </node>
+    <node id="20" lat="12" lon="51" uid="24854">
+        <tag k="name" v="B" />
+    </node>
+    <node id="30" lat="11.2" lon="52" uid="24855">
+        <tag k="name" v="C" />
+    </node>
+    <node id="40" lat="11.3" lon="51" uid="24854">
+        <tag k="name" v="D" />
+    </node>
+    <node id="50" lat="10" lon="51">
+        <tag k="name" v="E" />
+    </node>
+
+    <way id="10" uid="85761">
+        <nd ref="10"/>
+        <nd ref="20"/>
+        <nd ref="30"/>
+        <tag k="name" v="A B C" />
+        <tag k="maxheight" v="4" />
+        <tag k="maxwidth" v="2.5" />
+        <tag k="maxweight" v="4.4" />
+        <tag k="highway" v="secondary" />
+    </way>
+
+    <way id="12" uid="85762">
+        <nd ref="10"/>
+        <nd ref="40"/>
+        <nd ref="30"/>
+        <tag k="name" v="A D C" />
+        <tag k="maxheight:physical" v="4.4" />
+        <tag k="maxwidth:physical" v="3.5" />
+        <tag k="maxgcweight" v="17.5" />
+        <tag k="highway" v="secondary" />
+    </way>
+
+    <way id="11" uid="85761">
+        <nd ref="10"/>
+        <nd ref="50"/>
+        <nd ref="30"/>
+        <tag k="name" v="A E C" />
+        <tag k="highway" v="secondary" />
+    </way>
+
+    <way id="13">
+        <nd ref="20"/>
+        <nd ref="40"/>
+        <nd ref="50"/>
+        <tag k="name" v="B D E" />
+        <tag k="highway" v="secondary" />
+        <tag k="maxspeed" v="40" />
+    </way>
+
+    <!-- TODO relations -->
+</osm>
\ No newline at end of file