Skip to content
This repository has been archived by the owner on Dec 21, 2020. It is now read-only.

Commit

Permalink
Add then voice and banner instruction
Browse files Browse the repository at this point in the history
Add comment and minor rename
  • Loading branch information
boldtrn committed Oct 31, 2018
1 parent 4df67bd commit cfb7152
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@

public class MapboxResponseConverter {

private static final int VOICE_INSTRUCTION_MERGE_TRESHHOLD = 100;

/**
* Converts a GHResponse into Mapbox compatible json
*/
Expand Down Expand Up @@ -156,7 +158,7 @@ private static ObjectNode putInstruction(InstructionList instructions, int index
// Voice and banner instructions are empty for the last element
if (index + 1 < instructions.size()) {
putVoiceInstructions(instructions, distance, index, locale, translationMap, mapboxResponseConverterTranslationMap, voiceInstructions);
putBannerInstruction(instructions, distance, index, locale, translationMap, bannerInstructions);
putBannerInstructions(instructions, distance, index, locale, translationMap, bannerInstructions);
}

return instructionJson;
Expand Down Expand Up @@ -190,17 +192,19 @@ private static void putVoiceInstructions(InstructionList instructions, double di
double close = 400;
double veryClose = 200;

String thenVoiceInstruction = getThenVoiceInstructionpart(instructions, index, locale, translationMap, mapboxResponseConverterTranslationMap);

if (distance > far) {
putSingleVoiceInstruction(far, mapboxResponseConverterTranslationMap.getWithFallBack(locale).tr("in_km", 2) + " " + turnDescription, voiceInstructions);
}
if (distance > mid) {
putSingleVoiceInstruction(mid, mapboxResponseConverterTranslationMap.getWithFallBack(locale).tr("in_km_singular") + " " + turnDescription, voiceInstructions);
}
if (distance > close) {
putSingleVoiceInstruction(close, mapboxResponseConverterTranslationMap.getWithFallBack(locale).tr("in_m", 400) + " " + turnDescription, voiceInstructions);
putSingleVoiceInstruction(close, mapboxResponseConverterTranslationMap.getWithFallBack(locale).tr("in_m", 400) + " " + turnDescription + thenVoiceInstruction, voiceInstructions);
} else if (distance > veryClose) {
// This is an edge case when turning on narrow roads in cities, too close for the close turn, but too far for the direct turn
putSingleVoiceInstruction(veryClose, mapboxResponseConverterTranslationMap.getWithFallBack(locale).tr("in_m", 200) + " " + turnDescription, voiceInstructions)
putSingleVoiceInstruction(veryClose, mapboxResponseConverterTranslationMap.getWithFallBack(locale).tr("in_m", 200) + " " + turnDescription + thenVoiceInstruction, voiceInstructions)
;
}

Expand All @@ -212,7 +216,7 @@ private static void putVoiceInstructions(InstructionList instructions, double di
if (index + 2 == instructions.size())
distanceAlongGeometry = Helper.round(Math.min(distance, 25), 1);

putSingleVoiceInstruction(distanceAlongGeometry, turnDescription, voiceInstructions);
putSingleVoiceInstruction(distanceAlongGeometry, turnDescription + thenVoiceInstruction, voiceInstructions);
}

private static void putSingleVoiceInstruction(double distanceAlongGeometry, String turnDescription, ArrayNode voiceInstructions) {
Expand All @@ -223,7 +227,32 @@ private static void putSingleVoiceInstruction(double distanceAlongGeometry, Stri
voiceInstruction.put("ssmlAnnouncement", "<speak><amazon:effect name=\"drc\"><prosody rate=\"1.08\">" + turnDescription + "</prosody></amazon:effect></speak>");
}

private static void putBannerInstruction(InstructionList instructions, double distance, int index, Locale locale, TranslationMap translationMap, ArrayNode bannerInstructions) {
/**
* For close turns, it is important to announce the next turn in the earlier instruction.
* e.g.: instruction i+1= turn right, instruction i+2=turn left, with instruction i+1 distance < VOICE_INSTRUCTION_MERGE_TRESHHOLD
* The voice instruction should be like "turn right, then turn left"
*
* For instruction i+1 distance > VOICE_INSTRUCTION_MERGE_TRESHHOLD an empty String will be returned
*/
private static String getThenVoiceInstructionpart(InstructionList instructions, int index, Locale locale, TranslationMap translationMap, TranslationMap mapboxResponseConverterTranslationMap) {
if (instructions.size() > index + 2) {
Instruction firstInstruction = instructions.get(index + 1);
if (firstInstruction.getDistance() < VOICE_INSTRUCTION_MERGE_TRESHHOLD) {
Instruction secondInstruction = instructions.get(index + 2);
if (secondInstruction.getSign() != Instruction.REACHED_VIA)
return ", " + mapboxResponseConverterTranslationMap.getWithFallBack(locale).tr("then") + " " + secondInstruction.getTurnDescription(translationMap.getWithFallBack(locale));
}
}

return "";
}

/**
* Banner instructions are the turn instructions that are shown to the user in the top bar.
*
* Between two instructions we can show multiple banner instructions, you can control when they pop up using distanceAlongGeometry.
*/
private static void putBannerInstructions(InstructionList instructions, double distance, int index, Locale locale, TranslationMap translationMap, ArrayNode bannerInstructions) {
/*
A BannerInstruction looks like this
distanceAlongGeometry: 107,
Expand All @@ -242,47 +271,56 @@ private static void putBannerInstruction(InstructionList instructions, double di
*/

ObjectNode bannerInstruction = bannerInstructions.addObject();
Instruction nextInstruction = instructions.get(index + 1);

//Show from the beginning
bannerInstruction.put("distanceAlongGeometry", distance);

ObjectNode primary = bannerInstruction.putObject("primary");
String bannerInstructionName = nextInstruction.getName();
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);
}
}

private static void putSingleBannerInstruction(Instruction instruction, Locale locale, TranslationMap translationMap, ObjectNode singleBannerInstruction) {
String bannerInstructionName = instruction.getName();
if (bannerInstructionName == null || bannerInstructionName.isEmpty()) {
// Fix for final instruction and for instructions without name
bannerInstructionName = nextInstruction.getTurnDescription(translationMap.getWithFallBack(locale));
bannerInstructionName = instruction.getTurnDescription(translationMap.getWithFallBack(locale));

// Uppercase first letter
// TODO: should we do this for all cases? Then we might change the spelling of street names though
bannerInstructionName = Helper.firstBig(bannerInstructionName);
}

primary.put("text", bannerInstructionName);
singleBannerInstruction.put("text", bannerInstructionName);

ArrayNode components = primary.putArray("components");
ArrayNode components = singleBannerInstruction.putArray("components");
ObjectNode component = components.addObject();
component.put("text", bannerInstructionName);
component.put("type", "text");

primary.put("type", getTurnType(nextInstruction, false));
String modifier = getModifier(nextInstruction);
singleBannerInstruction.put("type", getTurnType(instruction, false));
String modifier = getModifier(instruction);
if (modifier != null)
primary.put("modifier", modifier);
singleBannerInstruction.put("modifier", modifier);

if (nextInstruction.getSign() == Instruction.USE_ROUNDABOUT) {
if (nextInstruction instanceof RoundaboutInstruction) {
double turnAngle = ((RoundaboutInstruction) nextInstruction).getTurnAngle();
if (instruction.getSign() == Instruction.USE_ROUNDABOUT) {
if (instruction instanceof RoundaboutInstruction) {
double turnAngle = ((RoundaboutInstruction) instruction).getTurnAngle();
if (Double.isNaN(turnAngle)) {
primary.putNull("degrees");
singleBannerInstruction.putNull("degrees");
} else {
double degree = (Math.abs(turnAngle) * 180) / Math.PI;
primary.put("degrees", degree);
singleBannerInstruction.put("degrees", degree);
}
}
}

bannerInstruction.putNull("secondary");
}

private static void putManeuver(Instruction instruction, ObjectNode instructionJson, Locale locale, TranslationMap translationMap, boolean isFirstInstructionOfLeg) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
in_km_singular=In 1 Kilometer
in_km=In %1$s Kilometern
in_m=In %1$s Metern
for_km=für %1$s Kilometer
for_km=für %1$s Kilometer
then=dann
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
in_km_singular=In 1 kilometer
in_km=In %1$s kilometers
in_m=In %1$s meters
for_km=for %1$s kilometer
for_km=for %1$s kilometer
then=then
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public void basicTest() {
assertEquals(1, voiceInstructions.size());
JsonNode voiceInstruction = voiceInstructions.get(0);
assertTrue(voiceInstruction.get("distanceAlongGeometry").asDouble() <= instructionDistance);
assertEquals("turn sharp left onto la Callisa", voiceInstruction.get("announcement").asText());
assertEquals("turn sharp left onto la Callisa, then keep left", voiceInstruction.get("announcement").asText());

JsonNode bannerInstructions = step.get("bannerInstructions");
assertEquals(1, bannerInstructions.size());
Expand Down Expand Up @@ -149,7 +149,7 @@ public void voiceInstructionsTest() {
assertEquals(2, voiceInstructions.size());
JsonNode voiceInstruction = voiceInstructions.get(0);
assertEquals(200, voiceInstruction.get("distanceAlongGeometry").asDouble(), 1);
assertEquals("In 200 meters At roundabout, take exit 2 onto CS-340", voiceInstruction.get("announcement").asText());
assertEquals("In 200 meters At roundabout, take exit 2 onto CS-340, then At roundabout, take exit 2 onto CG-3", voiceInstruction.get("announcement").asText());

// Step 15 is over 3km long
step = steps.get(15);
Expand Down

0 comments on commit cfb7152

Please sign in to comment.