-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add turn lanes support to navigate endpoint #2997
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -174,6 +174,22 @@ private static ObjectNode putInstruction(PointList points, InstructionList instr | |
if (intersectionValue.containsKey("out")) { | ||
intersection.put("out", (int) intersectionValue.get("out")); | ||
} | ||
// lanes | ||
if (!instruction.getInstructionDetails().isEmpty()) { | ||
InstructionDetails lastDetail = instruction.getInstructionDetails().stream() | ||
.sorted(Comparator.comparingDouble(InstructionDetails::getBeforeTurn)) | ||
.findFirst() | ||
.get(); | ||
Comment on lines
+179
to
+182
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I chose the last lane information because in intersections we can't have more than one lane info. I checked the docs and the mapbox API and it had similar results |
||
ArrayNode lanes = intersection.putArray("lanes"); | ||
for (LaneInfo li : lastDetail.getLanes()) { | ||
ObjectNode n = lanes.addObject(); | ||
n.put("valid", li.isValid()); | ||
n.put("active", li.isValid()); | ||
ArrayNode indications = n.putArray("indications"); | ||
putDirections(instruction, li, indications, n, "active_indication"); | ||
} | ||
|
||
} | ||
} | ||
} | ||
|
||
|
@@ -309,7 +325,6 @@ private static void putBannerInstructions(InstructionList instructions, double d | |
}, | ||
secondary: null, | ||
*/ | ||
|
||
ObjectNode bannerInstruction = bannerInstructions.addObject(); | ||
|
||
//Show from the beginning | ||
|
@@ -319,11 +334,32 @@ private static void putBannerInstructions(InstructionList instructions, double d | |
putSingleBannerInstruction(instructions.get(index + 1), locale, translationMap, primary); | ||
|
||
bannerInstruction.putNull("secondary"); | ||
|
||
if (instructions.size() > index + 2 && instructions.get(index + 2).getSign() != Instruction.REACHED_VIA) { | ||
// Sub shows the instruction after the current one | ||
ObjectNode sub = bannerInstruction.putObject("sub"); | ||
putSingleBannerInstruction(instructions.get(index + 2), locale, translationMap, sub); | ||
if (instructions.get(index + 1).getInstructionDetails().isEmpty()) { | ||
if (instructions.size() > index + 2 && instructions.get(index + 2).getSign() != Instruction.REACHED_VIA) { | ||
// Sub shows the instruction after the current one | ||
ObjectNode sub = bannerInstruction.putObject("sub"); | ||
putSingleBannerInstruction(instructions.get(index + 2), locale, translationMap, sub); | ||
} | ||
} else { | ||
for (InstructionDetails details: instructions.get(index + 1).getInstructionDetails()) { | ||
ObjectNode subBannerInstruction = bannerInstructions.addObject(); | ||
subBannerInstruction.put("distanceAlongGeometry", details.getBeforeTurn()); | ||
|
||
ObjectNode subPrimary = subBannerInstruction.putObject("primary"); | ||
putSingleBannerInstruction(instructions.get(index + 1), locale, translationMap, subPrimary); | ||
|
||
ObjectNode sub = subBannerInstruction.putObject("sub"); | ||
sub.put("text", ""); | ||
ArrayNode components = sub.putArray("components"); | ||
for (LaneInfo lane : details.getLanes()) { | ||
ObjectNode component = components.addObject(); | ||
component.put("text", ""); | ||
component.put("type", "lane"); | ||
component.put("active", lane.isValid()); | ||
ArrayNode directions = component.putArray("directions"); | ||
putDirections(instructions.get(index + 1), lane, directions, primary, "active_direction"); | ||
Comment on lines
+344
to
+360
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This requires some explanation, I checked the mapbox navigation API and how it works is: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, makes sense then. Were you able to test your changes with the mapbox or maplibre SDK? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not yet, but I'll certainly do. I'll set up the environment and try it out. |
||
} | ||
} | ||
} | ||
} | ||
|
||
|
@@ -469,4 +505,32 @@ public static ObjectNode convertFromGHResponseError(GHResponse ghResponse) { | |
json.put("message", ghResponse.getErrors().get(0).getMessage()); | ||
return json; | ||
} | ||
} | ||
|
||
public static void putDirections(Instruction instruction, LaneInfo lane, ArrayNode directions, | ||
ObjectNode laneNode, String activeDirectionFieldName) { | ||
for (String direction: lane.getDirections()){ | ||
if (direction.equals("continue") || direction.equals("none")) { | ||
direction = "straight"; | ||
} | ||
direction = direction.replace("_", " "); | ||
directions.add(direction); | ||
if (lane.isValid()) { | ||
if (lane.getDirections().size() == 1) { | ||
laneNode.put(activeDirectionFieldName, direction); | ||
} else { | ||
if (direction.startsWith("merge")) { | ||
return; | ||
} | ||
String modifier = getModifier(instruction); | ||
if (modifier != null) { | ||
String[] dir = modifier.split(" "); | ||
String[] laneDir = direction.split(" "); | ||
if (dir[dir.length - 1].equals(laneDir[laneDir.length - 1])) { | ||
laneNode.put(activeDirectionFieldName, direction); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
package com.graphhopper.navigation; | ||
|
||
import com.fasterxml.jackson.databind.JsonNode; | ||
import com.fasterxml.jackson.databind.node.JsonNodeCreator; | ||
import com.fasterxml.jackson.databind.node.JsonNodeFactory; | ||
import com.fasterxml.jackson.databind.node.ObjectNode; | ||
import org.junit.jupiter.api.AfterAll; | ||
import org.junit.jupiter.api.BeforeAll; | ||
import org.junit.jupiter.api.Test; | ||
import com.graphhopper.GHRequest; | ||
import com.graphhopper.GHResponse; | ||
import com.graphhopper.GraphHopper; | ||
import com.graphhopper.GraphHopperConfig; | ||
import com.graphhopper.config.Profile; | ||
import com.graphhopper.routing.TestProfiles; | ||
import com.graphhopper.util.Helper; | ||
import com.graphhopper.util.PMap; | ||
import com.graphhopper.util.TranslationMap; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
import static org.junit.jupiter.api.Assertions.assertNull; | ||
|
||
import java.io.File; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
import java.util.Locale; | ||
|
||
class NavigationResponseConverterTurnLanesTest { | ||
|
||
private static final String graphFolder = "target/graphhopper-turn-lanes-test-car"; | ||
private static final String osmFile = "../core/files/bautzen.osm"; | ||
private static GraphHopper hopper; | ||
private static final String profile = "my_car"; | ||
|
||
private final TranslationMap trMap = hopper.getTranslationMap(); | ||
private final DistanceConfig distanceConfig = new DistanceConfig(DistanceUtils.Unit.METRIC, trMap, Locale.ENGLISH); | ||
|
||
@BeforeAll | ||
public static void beforeClass() { | ||
// make sure we are using fresh files with correct vehicle | ||
Helper.removeDir(new File(graphFolder)); | ||
hopper = new GraphHopper().setStoreOnFlush(true); | ||
hopper = hopper.init(new GraphHopperConfig() | ||
.putObject("graph.location", graphFolder) | ||
.putObject("datareader.file", osmFile) | ||
.putObject("import.osm.ignored_highways", "footway,cycleway,path,pedestrian,steps") | ||
.putObject("datareader.turn_lanes_profiles", profile) | ||
.setProfiles(List.of(TestProfiles.accessAndSpeed(profile, "car"))) | ||
).importOrLoad(); | ||
} | ||
|
||
@AfterAll | ||
public static void afterClass() { | ||
Helper.removeDir(new File(graphFolder)); | ||
} | ||
|
||
@Test | ||
public void intersectionsTest() { | ||
GHResponse rsp = hopper.route(new GHRequest(51.186861,14.412755,51.18958,14.41242). | ||
setProfile(profile).setPathDetails(Arrays.asList("intersection"))); | ||
|
||
ObjectNode json = NavigateResponseConverter.convertFromGHResponse(rsp, trMap, Locale.ENGLISH, distanceConfig); | ||
JsonNode lanes = json.get("routes").get(0).get("legs").get(0).get("steps") | ||
.get(1).get("intersections").get(0).get("lanes"); | ||
|
||
assertEquals(2, lanes.size()); | ||
|
||
JsonNode firstLane = lanes.get(0); | ||
assertEquals(1, firstLane.get("indications").size()); | ||
assertEquals("left", firstLane.get("indications").get(0).asText()); | ||
assertEquals(true, firstLane.get("valid").asBoolean()); | ||
assertEquals(true, firstLane.get("active").asBoolean()); | ||
assertEquals("left", firstLane.get("active_indication").asText()); | ||
|
||
JsonNode secondLane = lanes.get(1); | ||
assertEquals(1, secondLane.get("indications").size()); | ||
assertEquals("straight", secondLane.get("indications").get(0).asText()); | ||
assertEquals(false, secondLane.get("valid").asBoolean()); | ||
assertEquals(false, secondLane.get("active").asBoolean()); | ||
} | ||
|
||
@Test | ||
public void bannerInstructionsTest() { | ||
GHResponse rsp = hopper.route(new GHRequest(51.186861,14.412755,51.18958,14.41242). | ||
setProfile(profile).setPathDetails(Arrays.asList("intersection"))); | ||
|
||
ObjectNode json = NavigateResponseConverter.convertFromGHResponse(rsp, trMap, Locale.ENGLISH, distanceConfig); | ||
JsonNode bannerInstructions = json.get("routes").get(0).get("legs").get(0).get("steps") | ||
.get(0).get("bannerInstructions"); | ||
|
||
assertEquals(2, bannerInstructions.size()); | ||
|
||
JsonNode firstBannerInstruction = bannerInstructions.get(0); | ||
|
||
assertEquals("Turn left and take A 4 toward Dresden", firstBannerInstruction.get("primary").get("text").asText()); | ||
assertEquals("left", firstBannerInstruction.get("primary").get("modifier").asText()); | ||
assertEquals("left", firstBannerInstruction.get("primary").get("active_direction").asText()); | ||
assertEquals("turn", firstBannerInstruction.get("primary").get("type").asText()); | ||
assertEquals("[{\"text\":\"Turn left and take A 4 toward Dresden\",\"type\":\"text\"}]", firstBannerInstruction.get("primary").get("components").toString()); | ||
assertNull(firstBannerInstruction.get("sub")); | ||
assertEquals(597, firstBannerInstruction.get("distanceAlongGeometry").asDouble(), 0.001); | ||
|
||
JsonNode secondBannerInstruction = bannerInstructions.get(1); | ||
assertEquals("Turn left and take A 4 toward Dresden", secondBannerInstruction.get("primary").get("text").asText()); | ||
assertEquals("left", secondBannerInstruction.get("primary").get("modifier").asText()); | ||
assertEquals("turn", secondBannerInstruction.get("primary").get("type").asText()); | ||
assertEquals("[{\"text\":\"Turn left and take A 4 toward Dresden\",\"type\":\"text\"}]", secondBannerInstruction.get("primary").get("components").toString()); | ||
assertEquals(161.593, secondBannerInstruction.get("distanceAlongGeometry").asDouble(), 0.001); | ||
|
||
assertEquals("", secondBannerInstruction.get("sub").get("text").asText()); | ||
assertEquals("[{\"text\":\"\",\"type\":\"lane\",\"active\":true,\"directions\":[\"left\"]},{\"text\":\"\",\"type\":\"lane\",\"active\":false,\"directions\":[\"straight\"]}]", | ||
secondBannerInstruction.get("sub").get("components").toString()); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor: as the list has lexicographical order can you move it a bit up :) ?
(btw: will hopefully be able to get some time to test your PR out in the next days)