Custom Turn Restriction Example

Hey,

as of 7446 I try to create my own Turn Restriction example. I made an attached simple.osm file in JSOM with a turn restriction correctly displayed in JOSM:

I updated the config-example.yml with graph.flag_encoders: car|turn_costs=true and

  profiles:
    - name: car
      vehicle: car
      weighting: fastest
      turn_costs: true

However, the turn restriction is not recognized in the routing:

I tried it also in Java (see attached code), but it also navigates via “Ostring” instead of “Kaeppele”:

distance 123.62 for instruction: continue onto Gerwig
distance 97.94 for instruction: turn left onto Ostring
distance 123.38 for instruction: turn left onto Gerwig
distance 0.0 for instruction: arrive at destination
simple.osm
<?xml version='1.0' encoding='UTF-8'?>
<osm version='0.6' generator='JOSM'>
<bounds minlat='49.00492' minlon='8.43571' maxlat='49.00814' maxlon='8.44413' origin='JOSM' />
<node id='-138082' visible='true' lat='49.00601320632' lon='8.4378525903' />
<node id='-138083' visible='true' lat='49.00600598625' lon='8.43919538768' />
<node id='-138084' visible='true' lat='49.00600598625' lon='8.440516172' />
<node id='-138085' visible='true' lat='49.00600598625' lon='8.4414957537' />
<node id='-138086' visible='true' lat='49.00675686751' lon='8.4414957537' />
<node id='-138087' visible='true' lat='49.00675686751' lon='8.44050516546' />
<node id='-138088' visible='true' lat='49.00675686751' lon='8.43916236808' />
<node id='-138089' visible='true' lat='49.00674242759' lon='8.43790762298' />
<node id='-138090' visible='true' lat='49.00536339619' lon='8.44058221121' />
<node id='-138091' visible='true' lat='49.00752217713' lon='8.44048315239' />
<node id='-138092' visible='true' lat='49.00755827635' lon='8.43916236808' />
<node id='-138093' visible='true' lat='49.00538505666' lon='8.43918438115' />
<node id='-138094' visible='true' lat='49.00752939698' lon='8.44123159683' />
<node id='-138095' visible='true' lat='49.00718284318' lon='8.43792963605' />
<node id='-138096' visible='true' lat='49.0053778365' lon='8.43786359683' />
<node id='-138097' visible='true' lat='49.00539227682' lon='8.43867808049' />
<node id='-138098' visible='true' lat='49.00537061635' lon='8.44102247265' />
<node id='-138099' visible='true' lat='49.00755827635' lon='8.43846895631' />
<way id='-103991' visible='true'>
  <nd ref='-138082' />
  <nd ref='-138083' />
  <tag k='highway' v='primary' />
  <tag k='name' v='Ostring' />
  <tag k='oneway' v='yes' />
</way>
<way id='-103992' visible='true'>
  <nd ref='-138086' />
  <nd ref='-138087' />
  <tag k='highway' v='primary' />
  <tag k='name' v='Ostring' />
  <tag k='oneway' v='yes' />
</way>
<way id='-103993' visible='true'>
  <nd ref='-138090' />
  <nd ref='-138084' />
  <tag k='highway' v='secondary' />
  <tag k='name' v='Gerwig' />
  <tag k='oneway' v='yes' />
</way>
<way id='-103994' visible='true'>
  <nd ref='-138092' />
  <nd ref='-138088' />
  <tag k='highway' v='secondary' />
  <tag k='name' v='Gerwig' />
  <tag k='oneway' v='yes' />
</way>
<way id='-103995' visible='true'>
  <nd ref='-138094' />
  <nd ref='-138091' />
  <tag k='highway' v='secondary' />
  <tag k='name' v='Kaeppele' />
</way>
<way id='-103996' visible='true'>
  <nd ref='-138095' />
  <nd ref='-138089' />
  <tag k='highway' v='primary' />
  <tag k='name' v='Durlacher' />
</way>
<way id='-103997' visible='true'>
  <nd ref='-138097' />
  <nd ref='-138093' />
  <tag k='highway' v='secondary' />
  <tag k='name' v='Grossmarkt' />
</way>
<way id='-104013' visible='true'>
  <nd ref='-138087' />
  <nd ref='-138088' />
  <tag k='highway' v='primary' />
  <tag k='name' v='Ostring' />
  <tag k='oneway' v='yes' />
</way>
<way id='-104014' visible='true'>
  <nd ref='-138088' />
  <nd ref='-138089' />
  <tag k='highway' v='primary' />
  <tag k='name' v='Ostring' />
  <tag k='oneway' v='yes' />
</way>
<way id='-104017' visible='true'>
  <nd ref='-138091' />
  <nd ref='-138092' />
  <tag k='highway' v='secondary' />
  <tag k='name' v='Kaeppele' />
</way>
<way id='-104018' visible='true'>
  <nd ref='-138092' />
  <nd ref='-138099' />
  <tag k='highway' v='secondary' />
  <tag k='name' v='Kaeppele' />
</way>
<way id='-104021' visible='true'>
  <nd ref='-138089' />
  <nd ref='-138082' />
  <tag k='highway' v='primary' />
  <tag k='name' v='Durlacher' />
</way>
<way id='-104022' visible='true'>
  <nd ref='-138082' />
  <nd ref='-138096' />
  <tag k='highway' v='primary' />
  <tag k='name' v='Durlacher' />
</way>
<way id='-104025' visible='true'>
  <nd ref='-138083' />
  <nd ref='-138084' />
  <tag k='highway' v='primary' />
  <tag k='name' v='Ostring' />
  <tag k='oneway' v='yes' />
</way>
<way id='-104026' visible='true'>
  <nd ref='-138084' />
  <nd ref='-138085' />
  <tag k='highway' v='primary' />
  <tag k='name' v='Ostring' />
  <tag k='oneway' v='yes' />
</way>
<way id='-104029' visible='true'>
  <nd ref='-138093' />
  <nd ref='-138090' />
  <tag k='highway' v='secondary' />
  <tag k='name' v='Grossmarkt' />
</way>
<way id='-104030' visible='true'>
  <nd ref='-138090' />
  <nd ref='-138098' />
  <tag k='highway' v='secondary' />
  <tag k='name' v='Grossmarkt' />
</way>
<way id='-104033' visible='true'>
  <nd ref='-138088' />
  <nd ref='-138083' />
  <tag k='highway' v='secondary' />
  <tag k='name' v='Gerwig' />
  <tag k='oneway' v='yes' />
</way>
<way id='-104034' visible='true'>
  <nd ref='-138083' />
  <nd ref='-138093' />
  <tag k='highway' v='secondary' />
  <tag k='name' v='Gerwig' />
  <tag k='oneway' v='yes' />
</way>
<way id='-104037' visible='true'>
  <nd ref='-138084' />
  <nd ref='-138087' />
  <tag k='highway' v='secondary' />
  <tag k='name' v='Gerwig' />
  <tag k='oneway' v='yes' />
</way>
<way id='-104038' visible='true'>
  <nd ref='-138087' />
  <nd ref='-138091' />
  <tag k='highway' v='secondary' />
  <tag k='name' v='Gerwig' />
  <tag k='oneway' v='yes' />
</way>
<relation id="1" version="1">
  <member type="way" ref="-104037" role="from"/>
  <member type="way" ref="-104013" role="to"/>
  <member type="node" ref="-138087" role="via"/>
  <tag k="restriction" v="no_left_turn"/>
  <tag k="type" v="restriction"/>
</relation>
</osm>

RoutingExample.java
package graphhopper_complex;

import com.graphhopper.GHRequest;
import com.graphhopper.GHResponse;
import com.graphhopper.GraphHopper;
import com.graphhopper.ResponsePath;
import com.graphhopper.config.CHProfile;
import com.graphhopper.config.Profile;
import com.graphhopper.storage.BaseGraph;
import com.graphhopper.util.*;

import com.graphhopper.config.LMProfile;

import java.util.Locale;

public class RoutingExample {

  private static final String GH_LOCATION = "target/routing-algorithm-with-osm-test-gh";

  public static void main(String[] args) {

  	String relDir = args.length == 1 ? args[0] : "";
  	GraphHopper hopper = createHopper(relDir + "files/simple.osm",
  			new Profile("car").setVehicle("car").setWeighting("fastest").setTurnCosts(true));
  	hopper.importOrLoad();

  	BaseGraph b = hopper.getBaseGraph();
  	b.debugPrint();
  	routing(hopper);

  	hopper.close();

  }

  static GraphHopper createHopper(String osmFile, Profile... profiles) {
  	GraphHopper hopper = new GraphHopper().setStoreOnFlush(true).setOSMFile(osmFile).setProfiles(profiles)
  			.setGraphHopperLocation(GH_LOCATION);
  	hopper.getRouterConfig().setSimplifyResponse(false);
  	hopper.setMinNetworkSize(0);
  	hopper.getReaderConfig().setMaxWayPointDistance(0);
  	hopper.getLMPreparationHandler().setLMProfiles(new LMProfile(profiles[0].getName()));
  	hopper.getCHPreparationHandler().setCHProfiles(new CHProfile(profiles[0].getName()));
  	return hopper;
  }
  
  public static void routing(GraphHopper hopper) {
  	// simple configuration of the request object
  	GHRequest req = new GHRequest(49.005648, 8.440601, 49.005648, 8.43912).
  			setProfile("car").
  			setLocale(Locale.ENGLISH);
  	GHResponse rsp = hopper.route(req);

  	// handle errors
  	if (rsp.hasErrors())
  		throw new RuntimeException(rsp.getErrors().toString());

  	// use the best path, see the GHResponse class for more possibilities.
  	ResponsePath path = rsp.getBest();

  	// points, distance in meters and time in millis of the full path
  	PointList pointList = path.getPoints();
  	double distance = path.getDistance();
  	long timeInMs = path.getTime();

  	Translation tr = hopper.getTranslationMap().getWithFallBack(Locale.UK);
  	InstructionList il = path.getInstructions();
  	// iterate over all turn instructions
  	for (Instruction instruction : il) {
  		System.out.println("distance " + instruction.getDistance() + " for instruction: "
  				+ instruction.getTurnDescription(tr));
  	}
  	assert il.size() == 6;
  	assert Helper.round(path.getDistance(), -2) == 900;
  }
}

Did you use a debugger to take a look at what happens in e.g. OSMTurnRelationParser.java?

The program enters the OSMTurnRelationParser constructor once but won’t step into one of the functions

createTurnCostEncodedValues
handleTurnRelationTags
getInExplorer
getOutExplorer

It also does not step into

OSMParsers.handleTurnRelationTags()

@easbar can you give me a hint on where the OSMParsers are actually applied on the data?

I’ve got it now:

in OSMReader#createTurnRelation the osmTurnRelation is only created if the following is true:

fromWayID >= 0 && toWayID >= 0 && viaNodeID >= 0

in my dummy example, JOSM created negative IDs,

so -104037 >= 0 broke the whole execution.

@easbar are you always checking ids as >= 0?

Since the rest of the graphs creation worked perfectly fine with negative values I might open a pull request which makes a “negative friendly” approach just that some other idiot ( :slight_smile: ) doesn’t run in the same issue.

Thanks for figuring this out. To find out where e.g. OSMParsers is used etc. I think it is easiest to use the ‘Find usages’ feature (or similar) of your IDE.

We use artificial negative OSM IDs here: https://github.com/graphhopper/graphhopper/blob/5a369778fd3d6b98ea16dcde63c475d8edf67005/core/src/main/java/com/graphhopper/reader/osm/OSMNodeData.java#L79-L80

So as long as there is no real need to allow negative ids in the imported file I think we should not allow them. But actually I would have expected we throw an error for negative ids, rather than simply ignoring the turn restriction in this case?!

I think it is easiest to use the ‘Find usages’ feature (or similar) of your IDE.

Thanks for the tip!

But actually I would have expected we throw an error for negative ids, rather than simply ignoring the turn restriction in this case?!

Yeah it would’ve helped me. Do you want to prevent negative ids in general?

Yes, I think we could enforce there are no negative IDs in the imported OSM files and only use negative IDs internally for the code I pointed to, or is there any reason we should allow negative IDs?

we could enforce there are no negative IDs in the imported OSM files

:+1:

is there any reason we should allow negative IDs

no there isn’t, it should be an error.

I can try to add the exception and add a unit test for it.

I would target the two constructors of graphhopper/ReaderElement.java if you think it fits there.

Yes, this could work.

Also note this discussion: consider multiple from members for no_entry by karussell · Pull Request #2648 · graphhopper/graphhopper · GitHub and that the >=0 check was meant to check if the ids were still negative like they are set initially at the beginning of the method.

Powered by Discourse