private TurnType processRoundaboutTurn(
     List<RouteSegmentResult> result,
     int i,
     boolean leftSide,
     RouteSegmentResult prev,
     RouteSegmentResult rr) {
   int exit = 1;
   RouteSegmentResult last = rr;
   for (int j = i; j < result.size(); j++) {
     RouteSegmentResult rnext = result.get(j);
     last = rnext;
     if (rnext.getObject().roundabout()) {
       boolean plus = rnext.getStartPointIndex() < rnext.getEndPointIndex();
       int k = rnext.getStartPointIndex();
       if (j == i) {
         k = plus ? k + 1 : k - 1;
       }
       while (k != rnext.getEndPointIndex()) {
         if (rnext.getAttachedRoutes(k).size() > 0) {
           exit++;
         }
         k = plus ? k + 1 : k - 1;
       }
     } else {
       break;
     }
   }
   // combine all roundabouts
   TurnType t = TurnType.valueOf("EXIT" + exit, leftSide);
   t.setTurnAngle((float) MapUtils.degreesDiff(last.getBearingBegin(), prev.getBearingEnd()));
   return t;
 }
 private TurnType getTurnInfo(List<RouteSegmentResult> result, int i, boolean leftSide) {
   if (i == 0) {
     return TurnType.valueOf(TurnType.C, false);
   }
   RouteSegmentResult prev = result.get(i - 1);
   if (prev.getObject().roundabout()) {
     // already analyzed!
     return null;
   }
   RouteSegmentResult rr = result.get(i);
   if (rr.getObject().roundabout()) {
     return processRoundaboutTurn(result, i, leftSide, prev, rr);
   }
   TurnType t = null;
   if (prev != null) {
     boolean noAttachedRoads = rr.getAttachedRoutes(rr.getStartPointIndex()).size() == 0;
     // add description about turn
     double mpi = MapUtils.degreesDiff(prev.getBearingEnd(), rr.getBearingBegin());
     if (noAttachedRoads) {
       // TODO VICTOR : look at the comment inside direction route
       //				double begin = rr.getObject().directionRoute(rr.getStartPointIndex(),
       // rr.getStartPointIndex() <
       //						rr.getEndPointIndex(), 25);
       //				mpi = MapUtils.degreesDiff(prev.getBearingEnd(), begin);
     }
     if (mpi >= TURN_DEGREE_MIN) {
       if (mpi < 60) {
         t = TurnType.valueOf(TurnType.TSLL, leftSide);
       } else if (mpi < 120) {
         t = TurnType.valueOf(TurnType.TL, leftSide);
       } else if (mpi < 135 || leftSide) {
         t = TurnType.valueOf(TurnType.TSHL, leftSide);
       } else {
         t = TurnType.valueOf(TurnType.TU, leftSide);
       }
     } else if (mpi < -TURN_DEGREE_MIN) {
       if (mpi > -60) {
         t = TurnType.valueOf(TurnType.TSLR, leftSide);
       } else if (mpi > -120) {
         t = TurnType.valueOf(TurnType.TR, leftSide);
       } else if (mpi > -135 || !leftSide) {
         t = TurnType.valueOf(TurnType.TSHR, leftSide);
       } else {
         t = TurnType.valueOf(TurnType.TU, leftSide);
       }
     } else {
       t = attachKeepLeftInfoAndLanes(leftSide, prev, rr, t);
     }
     if (t != null) {
       t.setTurnAngle((float) -mpi);
     }
   }
   return t;
 }
  private TurnType attachKeepLeftInfoAndLanes(
      boolean leftSide, RouteSegmentResult prev, RouteSegmentResult rr, TurnType t) {
    // keep left/right
    int[] lanes = null;
    boolean kl = false;
    boolean kr = false;
    List<RouteSegmentResult> attachedRoutes = rr.getAttachedRoutes(rr.getStartPointIndex());
    int ls = prev.getObject().getLanes();
    if (ls >= 0 && prev.getObject().getOneway() == 0) {
      ls = (ls + 1) / 2;
    }
    int left = 0;
    int right = 0;
    boolean speak = false;
    int speakPriority =
        Math.max(
            highwaySpeakPriority(prev.getObject().getHighway()),
            highwaySpeakPriority(rr.getObject().getHighway()));
    if (attachedRoutes != null) {
      for (RouteSegmentResult rs : attachedRoutes) {
        double ex = MapUtils.degreesDiff(rs.getBearingBegin(), rr.getBearingBegin());
        double mpi = Math.abs(MapUtils.degreesDiff(prev.getBearingEnd(), rs.getBearingBegin()));
        int rsSpeakPriority = highwaySpeakPriority(rs.getObject().getHighway());
        if (rsSpeakPriority != MAX_SPEAK_PRIORITY || speakPriority == MAX_SPEAK_PRIORITY) {
          if ((ex < TURN_DEGREE_MIN || mpi < TURN_DEGREE_MIN) && ex >= 0) {
            kl = true;
            int lns = rs.getObject().getLanes();
            if (rs.getObject().getOneway() == 0) {
              lns = (lns + 1) / 2;
            }
            if (lns > 0) {
              right += lns;
            }
            speak = speak || rsSpeakPriority <= speakPriority;
          } else if ((ex > -TURN_DEGREE_MIN || mpi < TURN_DEGREE_MIN) && ex <= 0) {
            kr = true;
            int lns = rs.getObject().getLanes();
            if (rs.getObject().getOneway() == 0) {
              lns = (lns + 1) / 2;
            }
            if (lns > 0) {
              left += lns;
            }
            speak = speak || rsSpeakPriority <= speakPriority;
          }
        }
      }
    }
    if (kr && left == 0) {
      left = 1;
    } else if (kl && right == 0) {
      right = 1;
    }
    int current = rr.getObject().getLanes();
    if (rr.getObject().getOneway() == 0) {
      current = (current + 1) / 2;
    }
    if (current <= 0) {
      current = 1;
    }
    if (ls >= 0 /*&& current + left + right >= ls*/) {
      lanes = new int[current + left + right];
      ls = current + left + right;
      for (int it = 0; it < ls; it++) {
        if (it < left || it >= left + current) {
          lanes[it] = 0;
        } else {
          lanes[it] = 1;
        }
      }
      // sometimes links are
      if ((current <= left + right) && (left > 1 || right > 1)) {
        speak = true;
      }
    }

    if (kl) {
      t = TurnType.valueOf(TurnType.KL, leftSide);
      t.setSkipToSpeak(!speak);
    } else if (kr) {
      t = TurnType.valueOf(TurnType.KR, leftSide);
      t.setSkipToSpeak(!speak);
    }
    if (t != null && lanes != null) {
      t.setLanes(lanes);
    }
    return t;
  }
  private void calculateTimeSpeedAndAttachRoadSegments(
      RoutingContext ctx, List<RouteSegmentResult> result) throws IOException {
    for (int i = 0; i < result.size(); i++) {
      if (ctx.checkIfMemoryLimitCritical(ctx.config.memoryLimitation)) {
        ctx.unloadUnusedTiles(ctx.config.memoryLimitation);
      }
      RouteSegmentResult rr = result.get(i);
      RouteDataObject road = rr.getObject();
      checkAndInitRouteRegion(ctx, road);
      double distOnRoadToPass = 0;
      double speed = ctx.getRouter().defineSpeed(road);
      if (speed == 0) {
        speed = ctx.getRouter().getMinDefaultSpeed();
      }
      boolean plus = rr.getStartPointIndex() < rr.getEndPointIndex();
      int next;
      double distance = 0;
      for (int j = rr.getStartPointIndex(); j != rr.getEndPointIndex(); j = next) {
        next = plus ? j + 1 : j - 1;
        if (j == rr.getStartPointIndex()) {
          attachRoadSegments(ctx, result, i, j, plus);
        }
        if (next != rr.getEndPointIndex()) {
          attachRoadSegments(ctx, result, i, next, plus);
        }

        double d =
            measuredDist(
                road.getPoint31XTile(j),
                road.getPoint31YTile(j),
                road.getPoint31XTile(next),
                road.getPoint31YTile(next));
        distance += d;
        double obstacle = ctx.getRouter().defineObstacle(road, j);
        if (obstacle < 0) {
          obstacle = 0;
        }
        distOnRoadToPass += d / speed + obstacle;

        List<RouteSegmentResult> attachedRoutes = rr.getAttachedRoutes(next);
        if (next != rr.getEndPointIndex()
            && !rr.getObject().roundabout()
            && attachedRoutes != null) {
          float before = rr.getBearing(next, !plus);
          float after = rr.getBearing(next, plus);
          boolean straight = Math.abs(MapUtils.degreesDiff(before + 180, after)) < TURN_DEGREE_MIN;
          boolean isSplit = false;
          // split if needed
          for (RouteSegmentResult rs : attachedRoutes) {
            double diff = MapUtils.degreesDiff(before + 180, rs.getBearingBegin());
            if (Math.abs(diff) <= TURN_DEGREE_MIN) {
              isSplit = true;
            } else if (!straight && Math.abs(diff) < 100) {
              isSplit = true;
            }
          }
          if (isSplit) {
            int endPointIndex = rr.getEndPointIndex();
            RouteSegmentResult split = new RouteSegmentResult(rr.getObject(), next, endPointIndex);
            split.copyPreattachedRoutes(rr, Math.abs(next - rr.getStartPointIndex()));
            rr.setSegmentTime((float) distOnRoadToPass);
            rr.setSegmentSpeed((float) speed);
            rr.setDistance((float) distance);
            rr.setEndPointIndex(next);
            result.add(i + 1, split);
            i++;
            // switch current segment to the splitted
            rr = split;
            distOnRoadToPass = 0;
            distance = 0;
          }
        }
      }
      // last point turn time can be added
      // if(i + 1 < result.size()) { distOnRoadToPass += ctx.getRouter().calculateTurnTime(); }
      rr.setSegmentTime((float) distOnRoadToPass);
      rr.setSegmentSpeed((float) speed);
      rr.setDistance((float) distance);
    }
  }
  void printResults(RoutingContext ctx, LatLon start, LatLon end, List<RouteSegmentResult> result) {
    float completeTime = 0;
    float completeDistance = 0;
    for (RouteSegmentResult r : result) {
      completeTime += r.getSegmentTime();
      completeDistance += r.getDistance();
    }

    println("ROUTE : ");
    double startLat = start.getLatitude();
    double startLon = start.getLongitude();
    double endLat = end.getLatitude();
    double endLon = end.getLongitude();
    StringBuilder add = new StringBuilder();
    add.append("loadedTiles = \"").append(ctx.loadedTiles).append("\" ");
    add.append("visitedSegments = \"").append(ctx.visitedSegments).append("\" ");
    add.append("complete_distance = \"").append(completeDistance).append("\" ");
    add.append("complete_time = \"").append(completeTime).append("\" ");
    add.append("routing_time = \"").append(ctx.routingTime).append("\" ");
    println(
        MessageFormat.format(
            "<test regions=\"\" description=\"\" best_percent=\"\" vehicle=\"{4}\" \n"
                + "    start_lat=\"{0}\" start_lon=\"{1}\" target_lat=\"{2}\" target_lon=\"{3}\" {5} >",
            startLat + "",
            startLon + "",
            endLat + "",
            endLon + "",
            ctx.config.routerName,
            add.toString()));
    if (PRINT_TO_CONSOLE_ROUTE_INFORMATION_TO_TEST) {
      for (RouteSegmentResult res : result) {
        String name = res.getObject().getName();
        String ref = res.getObject().getRef();
        if (name == null) {
          name = "";
        }
        if (ref != null) {
          name += " (" + ref + ") ";
        }
        StringBuilder additional = new StringBuilder();
        additional.append("time = \"").append(res.getSegmentTime()).append("\" ");
        additional.append("name = \"").append(name).append("\" ");
        //				float ms = res.getSegmentSpeed();
        float ms = res.getObject().getMaximumSpeed();
        if (ms > 0) {
          additional
              .append("maxspeed = \"")
              .append(ms * 3.6f)
              .append("\" ")
              .append(res.getObject().getHighway())
              .append(" ");
        }
        additional.append("distance = \"").append(res.getDistance()).append("\" ");
        if (res.getTurnType() != null) {
          additional.append("turn = \"").append(res.getTurnType()).append("\" ");
          additional
              .append("turn_angle = \"")
              .append(res.getTurnType().getTurnAngle())
              .append("\" ");
          if (res.getTurnType().getLanes() != null) {
            additional
                .append("lanes = \"")
                .append(Arrays.toString(res.getTurnType().getLanes()))
                .append("\" ");
          }
        }
        additional.append("start_bearing = \"").append(res.getBearingBegin()).append("\" ");
        additional.append("end_bearing = \"").append(res.getBearingEnd()).append("\" ");
        additional.append("description = \"").append(res.getDescription()).append("\" ");
        println(
            MessageFormat.format(
                "\t<segment id=\"{0}\" start=\"{1}\" end=\"{2}\" {3}/>",
                (res.getObject().getId()) + "",
                res.getStartPointIndex() + "",
                res.getEndPointIndex() + "",
                additional.toString()));
      }
    }
    println("</test>");
  }