graphhopper / graphhopper

Open source routing engine for OpenStreetMap. Use it as Java library or standalone web server.

Home Page:https://www.graphhopper.com/open-source/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Motorcycle profile speed section is ignored

NicholasRasi opened this issue · comments

Describe the bug
When using the suggested motorcycle profile, the resulting route duration is incorrect. In my opinion, this line { "if": "true", "multiply_by": "0.9 * car_average_speed" } produces a wrong result (the base speed is multiplied by 90% of the car_average_speed). However, the main bug is that if I change the speed section to { "if": "true", "limit_to": "car_average_speed" }, the resulting route time does not change. Meanwhile, if I append another custom_model_files with only the speed section, it works correctly.

To Reproduce

Steps to reproduce the behavior.

  1. checkout recent and stable version of GraphHopper v8
  2. pbf https://download.geofabrik.de/europe/italy/nord-ovest-latest.osm.pbf
  3. config.yml
graphhopper:
  datareader.file: "/mnt/disk/GRAPHHOPPER/nord-ovest-latest.osm.pbf"
  graph.location: "/home/ubuntu/GRAPHHOPPER/graph-cache-2"

  graph.vehicles: roads|transportation_mode=MOTORCYCLE|turn_costs=true,car,bike,foot
  profiles:
      # Moto Street
      - name: motorcycle
        vehicle: roads
        weighting: custom
        turn_costs: true
        custom_model_files: [motorcycle.json]
  profiles_lm:
      - profile: motorcycle

  graph.encoded_values: average_slope,curvature,hike_rating,max_slope,max_width,mtb_rating,surface,toll,track_type

Expected behavior
I expect that I can change the speed section of the motorcycle.json file. It seems that the speed section of the first custom model file is ignored.

System Information

  • Ubuntu 22
  • Java 18
  • GraphHopper v8.0

However, the main bug is that if I change the speed section to { "if": "true", "limit_to": "car_average_speed" }, the resulting route time does not change. Meanwhile, if I append another custom_model_files with only the speed section, it works correctly.

Can you explain what you did in detail to solve this?

In general the custom model for motorcycle is not yet production-ready. E.g. motorcycle access restrictions are not considered yet.

I append another custom model file (i.e. motorcycle_speed.json) to the config file, as follow:

    - name: motorcycle
      vehicle: roads
      weighting: custom
      turn_costs: true
      custom_model_files: [motorcycle.json,curvature.json,motorcycle_speed.json]

And set the speed in the motorcycle_speed.json file

{
    "speed": [
        { "if": "true", "limit_to": "car_average_speed" }
    ]
}

Hm, the correct way would be if the model starts with

{ "if": "true", "limit_to": "0.9 * car_average_speed" }

Here it is indeed the bug you mentioned ( multiply->limit_to ).

Can you retry without motorcycle_speed.json and you make sure that you remove the graph folder before you try again?

Yes, I already removed the cache and tried with

    - name: motorcycle
      vehicle: roads
      weighting: custom
      turn_costs: true
      custom_model_files: [motorcycle.json,curvature.json]

motorcycle.json

{
  "distance_influence": 90,
  "priority": [
    { "if": "!car_access", "multiply_by": "0"},
    { "if": "track_type.ordinal() > 1", "multiply_by": "0" },
    { "if": "road_access == PRIVATE", "multiply_by": "0" },
    { "if": "road_class == MOTORWAY || road_class == TRUNK", "multiply_by": "0.1" }
    // { "if": "urban_density != RURAL", "multiply_by": "0.3" },
  ],
  "speed": [
    { "if": "true", "limit_to": "0.9 * car_average_speed" },
    { "if": "surface==COBBLESTONE || surface==GRASS || surface==GRAVEL || surface==SAND || surface==PAVING_STONES || surface==DIRT || >
      "limit_to": "30"
    }
  ]

}

but it does not work. It seems that the speed section of the first custom model is ignored. If I send the same json as custom model json at runtime, it works.

What exactly does not work? I just tried it and it "worked" as expected and is e.g. 90% of the car speed.

You can see the now lower average_speed in the widget in the bottom right corner:

image

Also the route time increased compared to car in my example.

The traveling time is wrong.

Look, I define motorcycle.json as

{
  "distance_influence": 90,
  "priority": [
    { "if": "!car_access", "multiply_by": "0"},
    { "if": "track_type.ordinal() > 1", "multiply_by": "0" },
    { "if": "road_access == PRIVATE", "multiply_by": "0" },
    { "if": "road_class == MOTORWAY || road_class == TRUNK", "multiply_by": "0.1" }
    // { "if": "urban_density != RURAL", "multiply_by": "0.3" },
  ],
  "speed": [
    { "if": "true", "limit_to": "0.9 * car_average_speed" },
    { "if": "surface==COBBLESTONE || surface==GRASS || surface==GRAVEL || surface==SAND || surface==PAVING_STONES || surface==DIRT || surface==GROUND || surface==UNPAVED || surface==COMPACTED",
      "limit_to": "30"
    }
  ]

}

Then, I make a request for this route
image
I get a traveling time of 8 min (that is wrong). If I send this custom model

{
  "speed": [
    { "if": "true", "limit_to": "0.9 * car_average_speed" }
  ]

}

that is the same rule defined in motorcycle.json I get the following:
image
And the traveling time is 18 min (correct).

The profile is defined as already mentioned

    - name: motorcycle
      vehicle: roads
      weighting: custom
      turn_costs: true
      custom_model_files: [motorcycle.json,curvature.json]

(curvature.json does not add any speed rule)

Shouldn't it be the same?

What you see is that the (still) incorrect motorcycle.json is picked from the 8.0 jar and the limitation is basically only done from the statement if:true,limit_to:120.

You can either use the latest master (where this is already fixed due to your request) or a correct motorcycle-fixed.json (don't forget to specify custom_models.directory: xy) and this should no longer happen.

And yes, in general if you define a server-side custom model there is no need to send the custom model in the request and you are right that in the second case the query with "if": "true", "limit_to": "0.9 * car_average_speed" should result in the same time, because the statement already exists on the server-side.

Please note that the statement order still can be important. So it matters if you first limit to X and multiply by Y or if you first do the multiplication and after that the limitation.

Thank you, so I suppose that if I set a motorcycle.json in the custom_model_files it takes the file picked from the jar, even if there exists a file with the same name in the folder specified by custom_models.directory: xy. I didn't know that.

Maybe, it may be better to use the file defined by the user, if it is present. Or, it can be useful to write that in the documentation, but it is just a suggestion.

Yes and we should probably throw an exception to avoid the same name.

Ok, in my opinion there are two ways to solve the problem:

  1. throw an exception saying there is another custom model with the same name and exit
  2. read the user's file after the one loaded from the jar and merge them

This should be the interested code section

} else {
customModel = new CustomModel();
for (String file : customModelFileNames) {
if (file.contains(File.separator))
throw new IllegalArgumentException("Use custom_models.directory for the custom_model_files parent");
if (!file.endsWith(".json"))
throw new IllegalArgumentException("Yaml is no longer supported, see #2672. Use JSON with optional comments //");
try {
String string;
// 1. try to load custom model from jar
InputStream is = GHUtility.class.getResourceAsStream("/com/graphhopper/custom_models/" + file);
if (is != null) {
string = readJSONFileWithoutComments(new InputStreamReader(is));
} else {
// 2. try to load custom model file from external location
// dropwizard makes it very hard to find out the folder of config.yml -> use an extra parameter for the folder
string = readJSONFileWithoutComments(Paths.get(customModelFolder).
resolve(file).toFile().getAbsolutePath());
}
customModel = CustomModel.merge(customModel, jsonOM.readValue(string, CustomModel.class));
} catch (IOException ex) {
throw new RuntimeException("Cannot load custom_model from location " + file + ", profile:" + profile.getName(), ex);
}
}