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);
    }
  }