Instantiating a Graphhopper Instance without Webserver

(This is essentially a migration follow up from: Replicate maxPossibleSpeed through custom model file)

We are currently using the GraphHopper routing engine as a component inside another Java project. One of the key steps to get the thing off the ground is to create a GraphHopperConfig object. Since we are directly/programmatically accessing the resulting GraphHopper instance, we don’t need or want the whole webserver/dropwizard wrapper. Some parts of the config we set in our custom code but for the actual routing profiles we wanted to use config files that mirror how profiles are configured for a stand-alone GraphHopper+Server instance.

To achieve this we extracted, and marginally adjusted some of the config loading code from GraphHopperManaged, back in April 2021 (that was from GH v2.3). We managed to somehow keep things working up to GH v7.0 (though we probably don’t have all the config level features due to inevitable mismatches in the config loading code mismatches.)

I am now trying to migrate our setup to GH 8.0 and then subsequently to GH 9.0, but the way you do config loading, in particular for profiles, seems to have drastically changed through those releases.

Which brings me to my question: Do you have a minimal working example of how to create a graphhopper-config and -instance inside another java application from a file like your top level config-example.yml which only contains the graphhopper: section (i.e. no server: and logging: sections), and bypasses the whole dropwizard stuff?

Did you try one of the examples like the RoutingExample?

Those examples provide a good starting point and explain the outer scaffolding, but those examples don’t appear to load one of those yaml configurations I mentioned above, but do everything programmatically.

I guess for the 9.0 variant there is GHUtility.loadCustomModelFromJar() which I might take as a starting point for a custom loading function …

Some pointers/examples on how to manually deal with the whole config file and custom model loading (presumably through jackson databinding somehow), might be useful though, especially for GH 8.0

It is the dropwizard config. You can probably make it loading with some yaml reader, but IMO this is outside the scope of GraphHopper.

which I might take as a starting point for a custom loading function …

It is not necessary. You can do everything in Java.

I once did something like this:


 ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
 Jackson.initObjectMapper(objectMapper);
 GraphHopperConfig config = objectMapper.treeToValue(objectMapper.readTree(new File("/path/to/config.yml")).at("/graphhopper"), GraphHopperConfig.class);
 GraphHopper hopper = new GraphHopper();
 hopper.init(config);
 hopper.imporOrLoad();

@karussell We are aware that we theoretically can do everything in java, but we don’t want to. We want to be able to configure the routers at start time rather than hardcoding a number profiles, and we would like to be able to use configuration syntax which matches those that you officially document, rather than having to roll our own config layer there and trying to keep it in sync with GH upstream.

@easbar our current setup looks close to what you suggest, and what we are doing appears to work nicely when we simply specify profiles that use custom_models_file: []:

// Create object mapper for reading the GH config file
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
objectMapper.registerModule(new GraphHopperConfigModule());
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);

// load primary config
GraphHopperConfig graphHopperConfig = objectMapper.readValue(configStream, GraphHopperConfig.class);

The whole creation of the hopper object, incl. init() and importOrLoad() is happening later. For now I am concerned with correctly deserialising the config.

Now when I use the above code to try loading the following file, where I am specifying an explicit custom_model instead of custom_model_files: [] and which should be well-formed based on your documentation, I am getting some kind of jackson deserialisation exception, that I am unsure what to do with. Note that this is all done in GH 8.0:

##### Routing Profiles ####
graph.elevation.provider: srtm

profiles:
  - name: bike
    vehicle: bike
    custom_model: {
      "speed": [
        {
          "if": "average_slope >= 8",
          "multiply_by": "0.8"
        },
        {
          "else_if": "average_slope >= 4",
          "multiply_by": "0.9"
        },
        {
          "else_if": "average_slope <= -4",
          "multiply_by": "1.1"
        },
        {
          "if": "average_slope >= 12",
          "limit_to": "6"
        },
        {
          "if": "average_slope >= 15",
          "limit_to": "3"
        }
      ]
    }

profiles_ch:
  - profile: bike

prepare.min_network_size: 200

##### Path Details ######
graph.encoded_values: surface,average_slope,max_slope

##### Storage #####
import.osm.ignored_highways: motorway,trunk
graph.dataaccess: RAM_STORE

Exception (the class GraphHopperBridgeFactory is custom stuff from us and handles router configuraiton and creation):

[ERROR] 2024-09-05 13:43:30,067 GraphHopperBridgeFactory:73: Failed to load specified GH config file, returning null
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.graphhopper.json.Statement` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: (BufferedInputStream); line: 15, column: 11] (through reference chain: com.graphhopper.GraphHopperConfig["profiles"]->java.util.ArrayList[0]->com.graphhopper.config.Profile["custom_model"]->com.graphhopper.util.CustomModel["speed"]->java.util.ArrayList[0])
	at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
	at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1887)
	at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:414)
	at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1375)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1508)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:348)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:185)
	at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer._deserializeFromArray(CollectionDeserializer.java:361)
	at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:274)
	at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:30)
	at com.fasterxml.jackson.databind.deser.impl.SetterlessProperty.deserializeAndSet(SetterlessProperty.java:134)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:310)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:177)
	at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:129)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:310)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:177)
	at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer._deserializeFromArray(CollectionDeserializer.java:361)
	at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:246)
	at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:30)
	at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:129)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:310)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:177)
	at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:342)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4905)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3885)
	at com.ioki.mac.ktm.core.routing.GraphHopperBridgeFactory.loadConfig(GraphHopperBridgeFactory.java:156)

What am I missing here … it looks like the readValue thing is already trying to also deserialize the JSON model directly, rather than storing it as a string for later. While from your source code, it looks like the GraphHopper class itself rather expects to find a string under the custom_model key in its config class, which is then deserialised internally (as part of the init() call, I think) …

Update.

Replacing

objectMapper.registerModule(new GraphHopperConfigModule());
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);

with

Jackson.initObjectMapper(objectMapper);

solved the issue (at least I now get an actual, deserialised config object from my file).

After further investigation I think I misted a vital detail of the 4.0 release, concretely this:

Our loading code that was grafted from your 2.3 release still included those old-school mixins and old object registrations.

@easbar sry I didn’t spot this on the first read of your answer.

Hope this helps anybody following after with similar setups. Also would it make sense to add an example case for this (or maybe two, one with my direct call to readValue for configs that only have the graphhopper section, and one with the detour over the tree rep to subselect the graphhopper section from a full example config)

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.