Repository: graphhopper/graphhopper (java)

Issue:
PathDetails Country, DEU / AUT switched
I just tried the new SpatilRule PR #1853

I am not yet sure if it's related to the PR or not.

If I set the config to:

```
graph.encoded_values: country
```

And request a route, the resulting PathDetails are switched. Germany is AUT and Austria is DEU.

![Screenshot from 2020-01-28 14-57-19](https://user-images.githubusercontent.com/1553525/73270276-a1b8a800-41de-11ea-8e98-96ab11f7b29a.png)
![Screenshot from 2020-01-28 14-57-14](https://user-images.githubusercontent.com/1553525/73270277-a1b8a800-41de-11ea-9d4d-05fe01fb5563.png)

Pull request:
fix country rule ordering
fixes #1875

As we currently already sort the strings https://github.com/graphhopper/graphhopper/blob/master/core/src/main/java/com/graphhopper/routing/util/spatialrules/SpatialRuleLookupBuilder.java#L86

the only required change for #1875 would be to reorder the Country enum (AUT should come before DEU).

But as this is too error-prone I decided for a better & special handling when using the Country enum in combination with the SpatialRuleFactory. This is currently done in SpatialRuleLookupHelper.buildAndInjectSpatialRuleIntoGH. I have renamed this to a more Country-specific name as it is nothing generic. ~~Also `spatial_rules.borders_directory` is renamed to `country.borders_directory` to make usage more clear~~. To avoid mixing this in the future I have removed the SpatialRuleParser constructor the with enum-Country default.

Patch:
diff --git a/core/files/changelog.txt b/core/files/changelog.txt
index a3d29e034..681772d19 100644
--- a/core/files/changelog.txt
+++ b/core/files/changelog.txt
@@ -1,5 +1,4 @@
 1.0
-    replaced command line arguments -Dgraphhopper.. with -Ddw.graphhopper.. due to #1879, see also #1897
     GraphHopper.init(CmdArgs)->GraphHopper.init(GraphHopperConfig), see #1879
     revert compression of landmark preparation data, see #1749 and #1376
     removed GenericWeighting and DataFlagEncoder as a normal CarFlagEncoder does the job too
diff --git a/core/src/main/java/com/graphhopper/routing/profiles/Country.java b/core/src/main/java/com/graphhopper/routing/profiles/Country.java
index d89b54954..53d6c3b6e 100644
--- a/core/src/main/java/com/graphhopper/routing/profiles/Country.java
+++ b/core/src/main/java/com/graphhopper/routing/profiles/Country.java
@@ -30,6 +30,10 @@ public enum Country {
         this.name = name;
     }
 
+    public static EnumEncodedValue<Country> create() {
+        return new EnumEncodedValue<>(Country.KEY, Country.class);
+    }
+
     @Override
     public String toString() {
         return name;
diff --git a/core/src/main/java/com/graphhopper/routing/profiles/RoadClass.java b/core/src/main/java/com/graphhopper/routing/profiles/RoadClass.java
index 0abc2f2c9..80daf7b6a 100644
--- a/core/src/main/java/com/graphhopper/routing/profiles/RoadClass.java
+++ b/core/src/main/java/com/graphhopper/routing/profiles/RoadClass.java
@@ -17,6 +17,8 @@
  */
 package com.graphhopper.routing.profiles;
 
+import com.graphhopper.util.Helper;
+
 /**
  * This enum defines the road class of an edge. It is heavily influenced from the highway tag in OSM that can be
  * primary, cycleway etc.
@@ -27,8 +29,7 @@ public enum RoadClass {
     TERTIARY("tertiary"), RESIDENTIAL("residential"), UNCLASSIFIED("unclassified"),
     SERVICE("service"), ROAD("road"), TRACK("track"),
     BRIDLEWAY("bridleway"), STEPS("steps"), CYCLEWAY("cycleway"),
-    PATH("path"), LIVING_STREET("living_street"), FOOTWAY("footway"),
-    PEDESTRIAN("pedestrian"), PLATFORM("platform"), CORRIDOR("corridor");
+    PATH("path"), LIVING_STREET("living_street");
 
     public static final String KEY = "road_class";
 
@@ -44,15 +45,12 @@ public enum RoadClass {
     }
 
     public static RoadClass find(String name) {
-        if (name == null || name.isEmpty())
+        if (name == null)
+            return OTHER;
+        try {
+            return RoadClass.valueOf(Helper.toUpperCase(name));
+        } catch (IllegalArgumentException ex) {
             return OTHER;
-        
-        for (RoadClass roadClass : values()) {
-            if (roadClass.name().equalsIgnoreCase(name)) {
-                return roadClass;
-            }
         }
-        
-        return OTHER;
     }
 }
diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/DefaultTagParserFactory.java b/core/src/main/java/com/graphhopper/routing/util/parsers/DefaultTagParserFactory.java
index 999d788a0..f125e53c5 100644
--- a/core/src/main/java/com/graphhopper/routing/util/parsers/DefaultTagParserFactory.java
+++ b/core/src/main/java/com/graphhopper/routing/util/parsers/DefaultTagParserFactory.java
@@ -64,6 +64,10 @@ public class DefaultTagParserFactory implements TagParserFactory {
             return new OSMHazmatTunnelParser();
         else if (name.equals(HazmatWater.KEY))
             return new OSMHazmatWaterParser();
+        else if (name.equals(Country.KEY))
+            throw new IllegalArgumentException("The property spatial_rules.borders_directory is required in the configuration " +
+                    "when using 'country' in encoded_values");
+
         throw new IllegalArgumentException("entry in encoder list not supported " + name);
     }
 }
diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/SpatialRuleParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/SpatialRuleParser.java
index f015c99f2..7d8302819 100644
--- a/core/src/main/java/com/graphhopper/routing/util/parsers/SpatialRuleParser.java
+++ b/core/src/main/java/com/graphhopper/routing/util/parsers/SpatialRuleParser.java
@@ -34,10 +34,6 @@ public class SpatialRuleParser implements TagParser {
     private final IntEncodedValue spatialRuleEnc;
     private SpatialRuleLookup spatialRuleLookup;
 
-    public SpatialRuleParser(SpatialRuleLookup spatialRuleLookup) {
-        this(spatialRuleLookup, new EnumEncodedValue<>(Country.KEY, Country.class));
-    }
-
     public SpatialRuleParser(SpatialRuleLookup spatialRuleLookup, IntEncodedValue spatialRuleEnc) {
         this.spatialRuleLookup = spatialRuleLookup;
         this.spatialRuleEnc = spatialRuleEnc;
diff --git a/core/src/main/java/com/graphhopper/routing/util/spatialrules/CountriesSpatialRuleFactory.java b/core/src/main/java/com/graphhopper/routing/util/spatialrules/CountriesSpatialRuleFactory.java
index 34e802c56..5b0e17ade 100644
--- a/core/src/main/java/com/graphhopper/routing/util/spatialrules/CountriesSpatialRuleFactory.java
+++ b/core/src/main/java/com/graphhopper/routing/util/spatialrules/CountriesSpatialRuleFactory.java
@@ -1,25 +1,33 @@
 package com.graphhopper.routing.util.spatialrules;
 
+import com.graphhopper.routing.profiles.Country;
 import com.graphhopper.routing.util.spatialrules.countries.AustriaSpatialRule;
 import com.graphhopper.routing.util.spatialrules.countries.GermanySpatialRule;
+import org.locationtech.jts.geom.Polygon;
 
 import java.util.List;
 
-import org.locationtech.jts.geom.Polygon;
+import static com.graphhopper.util.Helper.toUpperCase;
 
 public class CountriesSpatialRuleFactory implements SpatialRuleLookupBuilder.SpatialRuleFactory {
+
     @Override
     public SpatialRule createSpatialRule(String id, List<Polygon> polygons) {
-        switch (id) {
-            case "AUT":
-                AustriaSpatialRule austriaSpatialRule = new AustriaSpatialRule();
-                austriaSpatialRule.setBorders(polygons);
-                return austriaSpatialRule;
-            case "DEU":
-                GermanySpatialRule germanySpatialRule = new GermanySpatialRule();
-                germanySpatialRule.setBorders(polygons);
-                return germanySpatialRule;
+        try {
+            Country country = Country.valueOf(toUpperCase(id));
+            switch (country) {
+                case AUT:
+                    AustriaSpatialRule austriaSpatialRule = new AustriaSpatialRule();
+                    austriaSpatialRule.setBorders(polygons);
+                    return austriaSpatialRule;
+                case DEU:
+                    GermanySpatialRule germanySpatialRule = new GermanySpatialRule();
+                    germanySpatialRule.setBorders(polygons);
+                    return germanySpatialRule;
+            }
+        } catch (Exception ex) {
         }
+
         return SpatialRule.EMPTY;
     }
 }
diff --git a/core/src/main/java/com/graphhopper/routing/util/spatialrules/SpatialRuleLookupBuilder.java b/core/src/main/java/com/graphhopper/routing/util/spatialrules/SpatialRuleLookupBuilder.java
index 165811c0a..39b5af07e 100644
--- a/core/src/main/java/com/graphhopper/routing/util/spatialrules/SpatialRuleLookupBuilder.java
+++ b/core/src/main/java/com/graphhopper/routing/util/spatialrules/SpatialRuleLookupBuilder.java
@@ -2,18 +2,14 @@ package com.graphhopper.routing.util.spatialrules;
 
 import com.graphhopper.json.geo.JsonFeature;
 import com.graphhopper.json.geo.JsonFeatureCollection;
-
+import com.graphhopper.util.Helper;
 import org.locationtech.jts.geom.Envelope;
 import org.locationtech.jts.geom.Geometry;
 import org.locationtech.jts.geom.Polygon;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
+import java.util.*;
 
 import static com.graphhopper.util.Helper.toLowerCase;
 
@@ -24,13 +20,6 @@ public class SpatialRuleLookupBuilder {
     }
 
     private static final Logger logger = LoggerFactory.getLogger(SpatialRuleLookupBuilder.class);
-    
-    private static final Comparator<SpatialRule> SPATIAL_ID_COMP = new Comparator<SpatialRule>() {
-        @Override
-        public int compare(SpatialRule o1, SpatialRule o2) {
-            return o1.getId().compareTo(o2.getId());
-        }
-    };
 
     /**
      * Builds a SpatialRuleLookup by passing the provided JSON features into the provided
@@ -51,13 +40,16 @@ public class SpatialRuleLookupBuilder {
                                                SpatialRuleFactory spatialRuleFactory, Envelope maxBBox) {
         Envelope envelope = new Envelope();
         List<SpatialRule> spatialRules = new ArrayList<>();
-
+        Map<String, JsonFeature> featureMap = new HashMap<>();
         for (JsonFeatureCollection featureCollection : jsonFeatureCollections) {
             for (JsonFeature jsonFeature : featureCollection.getFeatures()) {
                 String id = jsonIdField.isEmpty() || toLowerCase(jsonIdField).equals("id") ? jsonFeature.getId() : (String) jsonFeature.getProperty(jsonIdField);
                 if (id == null || id.isEmpty())
                     throw new IllegalArgumentException("ID cannot be empty but was for JsonFeature " + featureCollection.getFeatures().indexOf(jsonFeature));
 
+                JsonFeature old = featureMap.put(id, jsonFeature);
+                if (old != null)
+                    throw new IllegalStateException("SpatialRule with ID " + id + " already exists: " + old.getProperties());
                 List<Polygon> borders = new ArrayList<>();
                 for (int i = 0; i < jsonFeature.getGeometry().getNumGeometries(); i++) {
                     Geometry poly = jsonFeature.getGeometry().getGeometryN(i);
@@ -66,8 +58,8 @@ public class SpatialRuleLookupBuilder {
                     else
                         throw new IllegalArgumentException("Geometry for " + id + " (" + i + ") not supported " + poly.getClass().getSimpleName());
                 }
-                
-                SpatialRule spatialRule = spatialRuleFactory.createSpatialRule(id, borders);
+
+                SpatialRule spatialRule = spatialRuleFactory.createSpatialRule(Helper.toLowerCase(id), borders);
                 if (spatialRule != SpatialRule.EMPTY) {
                     spatialRules.add(spatialRule);
                     for (Polygon polygon : spatialRule.getBorders()) {
@@ -81,13 +73,8 @@ public class SpatialRuleLookupBuilder {
         if (calculatedBounds.isNull())
             return SpatialRuleLookup.EMPTY;
 
-
-        // ensure that the spatial ID is stable
-        Collections.sort(spatialRules, SPATIAL_ID_COMP);
         SpatialRuleLookup spatialRuleLookup = new SpatialRuleLookupJTS(spatialRules, calculatedBounds);
-
         logger.info("Created the SpatialRuleLookup with the following rules: {}", Arrays.toString(spatialRules.toArray()));
-
         return spatialRuleLookup;
     }
 
diff --git a/core/src/main/java/com/graphhopper/routing/util/spatialrules/SpatialRuleLookupHelper.java b/core/src/main/java/com/graphhopper/routing/util/spatialrules/SpatialRuleLookupHelper.java
index f657e93b2..4cf195c6c 100644
--- a/core/src/main/java/com/graphhopper/routing/util/spatialrules/SpatialRuleLookupHelper.java
+++ b/core/src/main/java/com/graphhopper/routing/util/spatialrules/SpatialRuleLookupHelper.java
@@ -18,19 +18,20 @@
 package com.graphhopper.routing.util.spatialrules;
 
 import com.graphhopper.GraphHopper;
+import com.graphhopper.json.geo.JsonFeature;
 import com.graphhopper.json.geo.JsonFeatureCollection;
 import com.graphhopper.routing.profiles.Country;
-import com.graphhopper.routing.util.parsers.TagParserFactory;
 import com.graphhopper.routing.util.parsers.SpatialRuleParser;
 import com.graphhopper.routing.util.parsers.TagParser;
+import com.graphhopper.routing.util.parsers.TagParserFactory;
+import com.graphhopper.util.Helper;
 import com.graphhopper.util.PMap;
-
-import java.util.List;
-
 import org.locationtech.jts.geom.Envelope;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.*;
+
 /**
  * Helper class to build the spatial rule index. This is kind of an ugly plugin mechanism to avoid requiring a
  * jackson dependency on the core.
@@ -40,9 +41,43 @@ import org.slf4j.LoggerFactory;
 public class SpatialRuleLookupHelper {
 
     private static final Logger logger = LoggerFactory.getLogger(SpatialRuleLookupHelper.class);
+    static String JSON_ID_FIELD = "ISO_A3";
+
+    /**
+     * This method limits the JsonFeatures to the specified subset
+     */
+    static List<JsonFeatureCollection> reorder(List<JsonFeatureCollection> jsonFeatureCollections, List<String> subset) {
+        Map<String, JsonFeature> map = new LinkedHashMap<>();
+        for (JsonFeatureCollection featureCollection : jsonFeatureCollections) {
+            for (JsonFeature jsonFeature : featureCollection.getFeatures()) {
+                String id = (String) jsonFeature.getProperty(JSON_ID_FIELD);
+                if (!Helper.isEmpty(id))
+                    map.put(Helper.toLowerCase(id), jsonFeature);
+            }
+        }
+        if (map.isEmpty())
+            throw new IllegalArgumentException("Input JsonFeatureCollection cannot be empty. Subset: " + subset + ", original.size:" + jsonFeatureCollections.size());
+
+        List<JsonFeature> newCollection = new ArrayList<>();
+        for (String val : subset) {
+            JsonFeature jsonFeature = map.get(val);
+            if (jsonFeature == null)
+                throw new IllegalArgumentException("SpatialRule does not exist. ID: " + val);
+            newCollection.add(jsonFeature);
+        }
+        JsonFeatureCollection coll = new JsonFeatureCollection();
+        coll.getFeatures().addAll(newCollection);
+        return Arrays.asList(coll);
+    }
 
-    public static void buildAndInjectSpatialRuleIntoGH(GraphHopper graphHopper, Envelope maxBounds, List<JsonFeatureCollection> jsonFeatureCollections) {
-        final SpatialRuleLookup index = SpatialRuleLookupBuilder.buildIndex(jsonFeatureCollections, "ISO_A3", new CountriesSpatialRuleFactory(), maxBounds);
+    public static void buildAndInjectCountrySpatialRules(GraphHopper graphHopper, Envelope maxBounds, List<JsonFeatureCollection> jsonFeatureCollections) {
+        List<String> subset = new ArrayList<>();
+        for (Country c : Country.values()) {
+            if (c != Country.DEFAULT)
+                subset.add(c.toString());
+        }
+        final SpatialRuleLookup index = SpatialRuleLookupBuilder.buildIndex(reorder(jsonFeatureCollections, subset),
+                JSON_ID_FIELD, new CountriesSpatialRuleFactory(), maxBounds);
         logger.info("Set spatial rule lookup with {} rules", index.size());
         final TagParserFactory oldTPF = graphHopper.getTagParserFactory();
         graphHopper.setTagParserFactory(new TagParserFactory() {
@@ -50,7 +85,7 @@ public class SpatialRuleLookupHelper {
             @Override
             public TagParser create(String name, PMap configuration) {
                 if (name.equals(Country.KEY))
-                    return new SpatialRuleParser(index);
+                    return new SpatialRuleParser(index, Country.create());
 
                 return oldTPF.create(name, configuration);
             }
diff --git a/core/src/main/java/com/graphhopper/routing/util/spatialrules/SpatialRuleLookupJTS.java b/core/src/main/java/com/graphhopper/routing/util/spatialrules/SpatialRuleLookupJTS.java
index 1165280ad..127c2b4b8 100644
--- a/core/src/main/java/com/graphhopper/routing/util/spatialrules/SpatialRuleLookupJTS.java
+++ b/core/src/main/java/com/graphhopper/routing/util/spatialrules/SpatialRuleLookupJTS.java
@@ -17,35 +17,25 @@
  */
 package com.graphhopper.routing.util.spatialrules;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.locationtech.jts.geom.Coordinate;
-import org.locationtech.jts.geom.Envelope;
-import org.locationtech.jts.geom.GeometryFactory;
-import org.locationtech.jts.geom.Point;
-import org.locationtech.jts.geom.Polygon;
+import com.graphhopper.util.shapes.GHPoint;
+import org.locationtech.jts.geom.*;
 import org.locationtech.jts.index.strtree.STRtree;
 
-import com.graphhopper.util.shapes.GHPoint;
+import java.util.*;
 
 /**
  * @author Thomas Butz
  */
 public class SpatialRuleLookupJTS implements SpatialRuleLookup {
-    
+
     private final GeometryFactory geometryFactory = new GeometryFactory();
     private final List<SpatialRule> rules;
     private final Envelope maxBounds;
     private final STRtree index;
-    
+
     public SpatialRuleLookupJTS(List<SpatialRule> spatialRules, Envelope maxBounds) {
         this.index = new STRtree();
-        
+
         Map<Polygon, SpatialRuleContainer> containerMap = new HashMap<>();
         Set<String> ruleIDs = new HashSet<>();
         for (SpatialRule rule : spatialRules) {
@@ -54,16 +44,16 @@ public class SpatialRuleLookupJTS implements SpatialRuleLookup {
 
             if (rule.equals(SpatialRule.EMPTY))
                 throw new IllegalArgumentException("rule cannot be EMPTY");
-            
+
             if (!ruleIDs.add(rule.getId()))
                 throw new IllegalArgumentException("Duplicate rule ID: \"" + rule.getId() + "\"");
-            
+
             for (Polygon border : rule.getBorders()) {
                 Envelope borderEnvelope = border.getEnvelopeInternal();
                 if (!maxBounds.intersects(borderEnvelope)) {
                     continue;
                 }
-                
+
                 SpatialRuleContainer container = containerMap.get(border);
                 if (container == null) {
                     container = new SpatialRuleContainer(border);
@@ -89,14 +79,14 @@ public class SpatialRuleLookupJTS implements SpatialRuleLookup {
         }
 
         Envelope searchEnv = new Envelope(lon, lon, lat, lat);
-        
+
         @SuppressWarnings("unchecked")
         List<SpatialRuleContainer> containers = index.query(searchEnv);
-        
+
         if (containers.isEmpty()) {
             return SpatialRule.EMPTY;
         }
-        
+
         Point point = geometryFactory.createPoint(new Coordinate(lon, lat));
         List<SpatialRule> applicableRules = new ArrayList<>();
         for (SpatialRuleContainer container : containers) {
@@ -104,7 +94,7 @@ public class SpatialRuleLookupJTS implements SpatialRuleLookup {
                 applicableRules.addAll(container.getRules());
             }
         }
-        
+
         if (applicableRules.isEmpty()) {
             return SpatialRule.EMPTY;
         }
@@ -117,12 +107,12 @@ public class SpatialRuleLookupJTS implements SpatialRuleLookup {
     public SpatialRule lookupRule(GHPoint point) {
         return lookupRule(point.lat, point.lon);
     }
-    
+
     @Override
     public int getSpatialId(SpatialRule rule) {
         return rules.indexOf(rule);
     }
-    
+
     @Override
     public SpatialRule getSpatialRule(int spatialId) {
         return rules.get(spatialId);
diff --git a/reader-osm/src/main/java/com/graphhopper/reader/osm/GraphHopperOSM.java b/reader-osm/src/main/java/com/graphhopper/reader/osm/GraphHopperOSM.java
index ffe1f5d3e..7f3a130f5 100644
--- a/reader-osm/src/main/java/com/graphhopper/reader/osm/GraphHopperOSM.java
+++ b/reader-osm/src/main/java/com/graphhopper/reader/osm/GraphHopperOSM.java
@@ -73,17 +73,18 @@ public class GraphHopperOSM extends GraphHopper {
             return;
 
         if (landmarkSplittingFeatureCollection != null && !landmarkSplittingFeatureCollection.getFeatures().isEmpty()) {
-            SpatialRuleLookup ruleLookup = SpatialRuleLookupBuilder.buildIndex(Collections.singletonList(landmarkSplittingFeatureCollection), "area", new SpatialRuleLookupBuilder.SpatialRuleFactory() {
-                @Override
-                public SpatialRule createSpatialRule(final String id, List<Polygon> polygons) {
-                    return new DefaultSpatialRule() {
+            SpatialRuleLookup ruleLookup = SpatialRuleLookupBuilder.buildIndex(
+                    Collections.singletonList(landmarkSplittingFeatureCollection), "area", new SpatialRuleLookupBuilder.SpatialRuleFactory() {
                         @Override
-                        public String getId() {
-                            return id;
+                        public SpatialRule createSpatialRule(final String id, List<Polygon> polygons) {
+                            return new DefaultSpatialRule() {
+                                @Override
+                                public String getId() {
+                                    return id;
+                                }
+                            }.setBorders(polygons);
                         }
-                    }.setBorders(polygons);
-                }
-            });
+                    });
             for (PrepareLandmarks prep : getLMPreparationHandler().getPreparations()) {
                 // the ruleLookup splits certain areas from each other but avoids making this a permanent change so that other algorithms still can route through these regions.
                 if (ruleLookup != null && ruleLookup.size() > 0) {
diff --git a/web-bundle/src/main/java/com/graphhopper/http/GraphHopperManaged.java b/web-bundle/src/main/java/com/graphhopper/http/GraphHopperManaged.java
index 9849721ac..64ce9cd29 100644
--- a/web-bundle/src/main/java/com/graphhopper/http/GraphHopperManaged.java
+++ b/web-bundle/src/main/java/com/graphhopper/http/GraphHopperManaged.java
@@ -84,7 +84,8 @@ public class GraphHopperManaged implements Managed {
             } catch (IOException e) {
                 throw new RuntimeException(e);
             }
-            SpatialRuleLookupHelper.buildAndInjectSpatialRuleIntoGH(graphHopper, new Envelope(maxBounds.minLon, maxBounds.maxLon, maxBounds.minLat, maxBounds.maxLat), jsonFeatureCollections);
+            SpatialRuleLookupHelper.buildAndInjectCountrySpatialRules(graphHopper,
+                    new Envelope(maxBounds.minLon, maxBounds.maxLon, maxBounds.minLat, maxBounds.maxLat), jsonFeatureCollections);
         }
         graphHopper.init(configuration);
     }
@@ -93,9 +94,9 @@ public class GraphHopperManaged implements Managed {
     public void start() {
         graphHopper.importOrLoad();
         logger.info("loaded graph at:{}, data_reader_file:{}, encoded values:{}, {}",
-                        graphHopper.getGraphHopperLocation(), graphHopper.getDataReaderFile(),
-                        graphHopper.getEncodingManager().toEncodedValuesAsString(),
-                        graphHopper.getGraphHopperStorage().toDetailsString());
+                graphHopper.getGraphHopperLocation(), graphHopper.getDataReaderFile(),
+                graphHopper.getEncodingManager().toEncodedValuesAsString(),
+                graphHopper.getGraphHopperStorage().toDetailsString());
     }
 
     public GraphHopper getGraphHopper() {


Test patch:
diff --git a/core/src/test/java/com/graphhopper/GraphHopperAPITest.java b/core/src/test/java/com/graphhopper/GraphHopperAPITest.java
index 108bc24cd..7de12478d 100644
--- a/core/src/test/java/com/graphhopper/GraphHopperAPITest.java
+++ b/core/src/test/java/com/graphhopper/GraphHopperAPITest.java
@@ -18,6 +18,7 @@
 package com.graphhopper;
 
 import com.graphhopper.json.geo.JsonFeature;
+import com.graphhopper.routing.util.CarFlagEncoder;
 import com.graphhopper.routing.util.EncodingManager;
 import com.graphhopper.storage.Graph;
 import com.graphhopper.storage.GraphBuilder;
@@ -113,7 +114,8 @@ public class GraphHopperAPITest {
     public void testDoNotInterpolateTwice1645() {
         String loc = "./target/issue1645";
         Helper.removeDir(new File(loc));
-        GraphHopperStorage graph = GraphBuilder.start(encodingManager).setRAM(loc, true).set3D(true).create();
+        EncodingManager em = new EncodingManager.Builder().add(new CarFlagEncoder()).build();
+        GraphHopperStorage graph = GraphBuilder.start(em).setRAM(loc, true).set3D(true).create();
 
         // we need elevation
         NodeAccess na = graph.getNodeAccess();
@@ -126,32 +128,33 @@ public class GraphHopperAPITest {
         graph.edge(2, 3, 10, true);
 
         final AtomicInteger counter = new AtomicInteger(0);
-        GraphHopper instance = createGraphHopper()
+        GraphHopper instance = new GraphHopper()
+                .setEncodingManager(em)
                 .setElevation(true).setGraphHopperLocation(loc).setCHEnabled(false)
                 .loadGraph(graph);
         instance.flush();
         instance.close();
         assertEquals(0, counter.get());
 
-        instance = setupGraphHopper(new GraphHopper() {
+        instance = new GraphHopper() {
             @Override
             void interpolateBridgesAndOrTunnels() {
                 counter.incrementAndGet();
                 super.interpolateBridgesAndOrTunnels();
             }
-        }).setElevation(true).setCHEnabled(false);
+        }.setEncodingManager(em).setElevation(true).setCHEnabled(false);
         instance.load(loc);
         instance.flush();
         instance.close();
         assertEquals(1, counter.get());
 
-        instance = setupGraphHopper(new GraphHopper() {
+        instance = new GraphHopper() {
             @Override
             void interpolateBridgesAndOrTunnels() {
                 counter.incrementAndGet();
                 super.interpolateBridgesAndOrTunnels();
             }
-        }).setElevation(true).setCHEnabled(false);
+        }.setEncodingManager(em).setElevation(true).setCHEnabled(false);
         instance.load(loc);
         instance.flush();
         instance.close();
@@ -189,7 +192,7 @@ public class GraphHopperAPITest {
 
         final CountDownLatch latch = new CountDownLatch(1);
         final AtomicInteger checkPointCounter = new AtomicInteger(0);
-        final GraphHopper graphHopper = setupGraphHopper(new GraphHopper() {
+        final GraphHopper graphHopper = new GraphHopper() {
             @Override
             protected ChangeGraphHelper createChangeGraphHelper(Graph graph, LocationIndex locationIndex) {
                 return new ChangeGraphHelper(graph, locationIndex) {
@@ -207,7 +210,7 @@ public class GraphHopperAPITest {
                     }
                 };
             }
-        }).setStoreOnFlush(false).setCHEnabled(false).
+        }.setStoreOnFlush(false).setEncodingManager(encodingManager).setCHEnabled(false).
                 loadGraph(graph);
 
         GHResponse rsp = graphHopper.route(new GHRequest(42, 10.4, 42, 10));
@@ -244,11 +247,7 @@ public class GraphHopperAPITest {
     }
 
     private GraphHopper createGraphHopper() {
-        return setupGraphHopper(new GraphHopper());
-    }
-
-    private GraphHopper setupGraphHopper(GraphHopper hopper) {
-        hopper.setEncodingManager(encodingManager);
-        return hopper;
+        return new GraphHopper().
+                setEncodingManager(encodingManager);
     }
 }
diff --git a/core/src/test/java/com/graphhopper/routing/profiles/EnumEncodedValueTest.java b/core/src/test/java/com/graphhopper/routing/profiles/EnumEncodedValueTest.java
index 678039209..6ae5fcc73 100644
--- a/core/src/test/java/com/graphhopper/routing/profiles/EnumEncodedValueTest.java
+++ b/core/src/test/java/com/graphhopper/routing/profiles/EnumEncodedValueTest.java
@@ -11,8 +11,8 @@ public class EnumEncodedValueTest {
     public void testInit() {
         EnumEncodedValue<RoadClass> prop = new EnumEncodedValue<>("road_class", RoadClass.class);
         EncodedValue.InitializerConfig init = new EncodedValue.InitializerConfig();
-        assertEquals(5, prop.init(init));
-        assertEquals(5, prop.bits);
+        assertEquals(4, prop.init(init));
+        assertEquals(4, prop.bits);
         assertEquals(0, init.dataIndex);
         assertEquals(0, init.shift);
         IntsRef ref = new IntsRef(1);
diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/SpatialRuleParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/SpatialRuleParserTest.java
index e303e7b0c..7281be1e3 100644
--- a/core/src/test/java/com/graphhopper/routing/util/parsers/SpatialRuleParserTest.java
+++ b/core/src/test/java/com/graphhopper/routing/util/parsers/SpatialRuleParserTest.java
@@ -10,7 +10,7 @@ public class SpatialRuleParserTest {
 
     @Test
     public void testMixParserAdding() {
-        EncodingManager em = new EncodingManager.Builder().add(new SpatialRuleParser(null)).build();
+        EncodingManager em = new EncodingManager.Builder().add(new SpatialRuleParser(null, Country.create())).build();
         assertTrue(em.hasEncodedValue(Country.KEY));
     }
 }
\ No newline at end of file
diff --git a/core/src/test/java/com/graphhopper/routing/util/spatialrules/SpatialRuleLookupBuilderTest.java b/core/src/test/java/com/graphhopper/routing/util/spatialrules/SpatialRuleLookupBuilderTest.java
index 78af3370c..a8542af89 100644
--- a/core/src/test/java/com/graphhopper/routing/util/spatialrules/SpatialRuleLookupBuilderTest.java
+++ b/core/src/test/java/com/graphhopper/routing/util/spatialrules/SpatialRuleLookupBuilderTest.java
@@ -166,7 +166,7 @@ public class SpatialRuleLookupBuilderTest {
             }
         };
 
-        EncodingManager em = new EncodingManager.Builder().add(new SpatialRuleParser(index)).add(new CarFlagEncoder(new PMap())).build();
+        EncodingManager em = new EncodingManager.Builder().add(new SpatialRuleParser(index, Country.create())).add(new CarFlagEncoder(new PMap())).build();
         IntEncodedValue countrySpatialIdEnc = em.getIntEncodedValue(Country.KEY);
         EnumEncodedValue<RoadAccess> tmpRoadAccessEnc = em.getEnumEncodedValue(RoadAccess.KEY, RoadAccess.class);
         DecimalEncodedValue tmpCarMaxSpeedEnc = em.getDecimalEncodedValue(MaxSpeed.KEY);
@@ -184,13 +184,13 @@ public class SpatialRuleLookupBuilderTest {
 
         IntsRef relFlags = em.createRelationFlags();
         EncodingManager.AcceptWay map = new EncodingManager.AcceptWay().put("car", EncodingManager.Access.WAY);
-        ReaderWay way = new ReaderWay(27l);
+        ReaderWay way = new ReaderWay(27L);
         way.setTag("highway", "track");
         way.setTag("estimated_center", new GHPoint(0.005, 0.005));
         e1.setFlags(em.handleWayTags(way, map, relFlags));
         assertEquals(RoadAccess.DESTINATION, e1.get(tmpRoadAccessEnc));
 
-        ReaderWay way2 = new ReaderWay(28l);
+        ReaderWay way2 = new ReaderWay(28L);
         way2.setTag("highway", "track");
         way2.setTag("estimated_center", new GHPoint(-0.005, -0.005));
         e2.setFlags(em.handleWayTags(way2, map, relFlags));
@@ -199,13 +199,13 @@ public class SpatialRuleLookupBuilderTest {
         assertEquals(index.getSpatialId(new GermanySpatialRule()), e1.get(countrySpatialIdEnc));
         assertEquals(index.getSpatialId(SpatialRule.EMPTY), e2.get(countrySpatialIdEnc));
 
-        ReaderWay livingStreet = new ReaderWay(29l);
+        ReaderWay livingStreet = new ReaderWay(29L);
         livingStreet.setTag("highway", "living_street");
         livingStreet.setTag("estimated_center", new GHPoint(0.005, 0.005));
         e3.setFlags(em.handleWayTags(livingStreet, map, relFlags));
         assertEquals(5, e3.get(tmpCarMaxSpeedEnc), .1);
 
-        ReaderWay livingStreet2 = new ReaderWay(30l);
+        ReaderWay livingStreet2 = new ReaderWay(30L);
         livingStreet2.setTag("highway", "living_street");
         livingStreet2.setTag("estimated_center", new GHPoint(-0.005, -0.005));
         e4.setFlags(em.handleWayTags(livingStreet2, map, relFlags));
diff --git a/core/src/test/java/com/graphhopper/routing/util/spatialrules/SpatialRuleLookupHelperTest.java b/core/src/test/java/com/graphhopper/routing/util/spatialrules/SpatialRuleLookupHelperTest.java
new file mode 100644
index 000000000..ea8c3f9c1
--- /dev/null
+++ b/core/src/test/java/com/graphhopper/routing/util/spatialrules/SpatialRuleLookupHelperTest.java
@@ -0,0 +1,35 @@
+package com.graphhopper.routing.util.spatialrules;
+
+import com.graphhopper.json.geo.JsonFeature;
+import com.graphhopper.json.geo.JsonFeatureCollection;
+import org.junit.Test;
+
+import java.util.*;
+
+import static com.graphhopper.routing.util.spatialrules.SpatialRuleLookupHelper.JSON_ID_FIELD;
+import static com.graphhopper.routing.util.spatialrules.SpatialRuleLookupHelper.reorder;
+import static org.junit.Assert.assertEquals;
+
+public class SpatialRuleLookupHelperTest {
+
+    static JsonFeatureCollection createJsonFeatureCollection(String key) {
+        JsonFeatureCollection coll = new JsonFeatureCollection();
+        Map<String, Object> map = new HashMap<>();
+        map.put(JSON_ID_FIELD, key);
+        coll.getFeatures().add(new JsonFeature(null, null, null, null, map));
+        return coll;
+    }
+
+    @Test
+    public void testReorder() {
+        List<JsonFeatureCollection> allFeatureList = new ArrayList<>();
+        allFeatureList.add(createJsonFeatureCollection("first_country"));
+        allFeatureList.add(createJsonFeatureCollection("sec_country"));
+        allFeatureList.add(createJsonFeatureCollection("3rd_country"));
+
+        List<JsonFeatureCollection> result = reorder(allFeatureList, Arrays.asList("3rd_country", "sec_country"));
+        assertEquals(1, result.size());
+        assertEquals("3rd_country", result.get(0).getFeatures().get(0).getProperties().get(JSON_ID_FIELD));
+        assertEquals("sec_country", result.get(0).getFeatures().get(1).getProperties().get(JSON_ID_FIELD));
+    }
+}
\ No newline at end of file