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 void addRouteSegmentToResult(
     List<RouteSegmentResult> result, RouteSegmentResult res, boolean reverse) {
   if (res.getStartPointIndex() != res.getEndPointIndex()) {
     if (result.size() > 0) {
       RouteSegmentResult last = result.get(result.size() - 1);
       if (last.getObject().id == res.getObject().id) {
         if (combineTwoSegmentResult(res, last, reverse)) {
           return;
         }
       }
     }
     result.add(res);
   }
 }
 private String getLanesString(RouteSegmentResult segment) {
   final int[] lns = segment.getTurnType().getLanes();
   if (lns != null) {
     String s = "";
     for (int h = 0; h < lns.length; h++) {
       if (h > 0) {
         s += "|";
       }
       if (lns[h] % 2 == 1) {
         s += "+";
       }
       int pt = TurnType.getPrimaryTurn(lns[h]);
       if (pt == 0) {
         pt = 1;
       }
       s += TurnType.valueOf(pt, false).toXmlString();
       int st = TurnType.getSecondaryTurn(lns[h]);
       if (st != 0) {
         s += "," + TurnType.valueOf(st, false).toXmlString();
       }
       int tt = TurnType.getTertiaryTurn(lns[h]);
       if (tt != 0) {
         s += "," + TurnType.valueOf(tt, false).toXmlString();
       }
     }
     s += "";
     return s;
   }
   return null;
 }
 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 void validateAllPointsConnected(List<RouteSegmentResult> result) {
   for (int i = 1; i < result.size(); i++) {
     RouteSegmentResult rr = result.get(i);
     RouteSegmentResult pr = result.get(i - 1);
     double d =
         MapUtils.getDistance(
             pr.getPoint(pr.getEndPointIndex()), rr.getPoint(rr.getStartPointIndex()));
     if (d > 0) {
       System.err.println(
           "Points are not connected : "
               + pr.getObject()
               + "("
               + pr.getEndPointIndex()
               + ") -> "
               + rr.getObject()
               + "("
               + rr.getStartPointIndex()
               + ") "
               + d
               + " meters");
     }
   }
 }
  private List<RouteSegmentResult> prepareResult(
      RoutingContext ctx,
      RouteSegment start,
      RouteSegment end,
      long startNanoTime,
      RouteSegment finalDirectRoute,
      RouteSegment finalReverseRoute) {
    List<RouteSegmentResult> result = new ArrayList<RouteSegmentResult>();

    RouteSegment segment = finalReverseRoute;
    int parentSegmentStart = segment == null ? 0 : segment.segmentEnd;
    while (segment != null) {
      RouteSegmentResult res = new RouteSegmentResult();
      res.object = segment.road;
      res.endPointIndex = segment.segmentStart;
      res.startPointIndex = parentSegmentStart;
      parentSegmentStart = segment.parentSegmentEnd;
      segment = segment.parentRoute;
      // reverse start and end point for start if needed
      // rely that point.segmentStart <= point.segmentEnd for end, start
      if (segment == null
          && res.startPointIndex >= res.endPointIndex
          && res.endPointIndex < res.object.getPointsLength() - 1) {
        res.endPointIndex++;
      }
      // do not add segments consists from 1 point
      if (res.startPointIndex != res.endPointIndex) {
        result.add(res);
      }
      res.startPoint = convertPoint(res.object, res.startPointIndex);
      res.endPoint = convertPoint(res.object, res.endPointIndex);
    }
    Collections.reverse(result);

    segment = finalDirectRoute;
    int parentSegmentEnd = segment == null ? 0 : segment.segmentEnd;
    while (segment != null) {
      RouteSegmentResult res = new RouteSegmentResult();
      res.object = segment.road;
      res.endPointIndex = parentSegmentEnd;
      res.startPointIndex = segment.segmentStart;
      parentSegmentEnd = segment.parentSegmentEnd;

      segment = segment.parentRoute;
      // reverse start and end point for start if needed
      // rely that point.segmentStart <= point.segmentEnd for end, start
      if (segment == null && res.startPointIndex < res.endPointIndex) {
        res.startPointIndex++;
      }
      // do not add segments consists from 1 point
      if (res.startPointIndex != res.endPointIndex) {
        result.add(res);
      }
      res.startPoint = convertPoint(res.object, res.startPointIndex);
      res.endPoint = convertPoint(res.object, res.endPointIndex);
    }
    Collections.reverse(result);

    if (PRINT_TO_CONSOLE_ROUTE_INFORMATION_TO_TEST) {
      System.out.println("ROUTE : ");
      double startLat = MapUtils.get31LatitudeY(start.road.getPoint31YTile(start.segmentEnd));
      double startLon = MapUtils.get31LongitudeX(start.road.getPoint31XTile(start.segmentEnd));
      double endLat = MapUtils.get31LatitudeY(end.road.getPoint31YTile(end.segmentStart));
      double endLon = MapUtils.get31LongitudeX(end.road.getPoint31XTile(end.segmentEnd));
      System.out.println(
          MessageFormat.format(
              "<test regions=\"\" description=\"\" best_percent=\"\" vehicle=\"\" \n"
                  + "    start_lat=\"{0}\" start_lon=\"{1}\" target_lat=\"{2}\" target_lon=\"{3}\">",
              startLat + "", startLon + "", endLat + "", endLon + ""));
      for (RouteSegmentResult res : result) {
        // (res.object.getId() >> 1)
        System.out.println(
            MessageFormat.format(
                "\t<segment id=\"{0}\" start=\"{1}\" end=\"{2}\" name=\"{3}\"/>",
                (res.object.getId() >> 1) + "",
                res.startPointIndex,
                res.endPointIndex,
                (res.object.getName() + "").replace(MapRenderingTypes.REF_CHAR, ' ')));
      }
      System.out.println("</test>");
    }

    ctx.timeToCalculate = (System.nanoTime() - startNanoTime);
    log.info(
        "Time to calculate : "
            + ctx.timeToCalculate / 1e6
            + ", time to load : "
            + ctx.timeToLoad / 1e6
            + ", loaded tiles : "
            + ctx.loadedTiles.size()
            + ", visited segments "
            + ctx.visitedSegments);
    return result;
  }
  private void attachRoadSegments(
      RoutingContext ctx, List<RouteSegmentResult> result, int routeInd, int pointInd, boolean plus)
      throws IOException {
    RouteSegmentResult rr = result.get(routeInd);
    RouteDataObject road = rr.getObject();
    long nextL = pointInd < road.getPointsLength() - 1 ? getPoint(road, pointInd + 1) : 0;
    long prevL = pointInd > 0 ? getPoint(road, pointInd - 1) : 0;

    // attach additional roads to represent more information about the route
    RouteSegmentResult previousResult = null;

    // by default make same as this road id
    long previousRoadId = road.getId();
    if (pointInd == rr.getStartPointIndex() && routeInd > 0) {
      previousResult = result.get(routeInd - 1);
      previousRoadId = previousResult.getObject().getId();
      if (previousRoadId != road.getId()) {
        if (previousResult.getStartPointIndex() < previousResult.getEndPointIndex()
            && previousResult.getEndPointIndex()
                < previousResult.getObject().getPointsLength() - 1) {
          rr.attachRoute(
              pointInd,
              new RouteSegmentResult(
                  previousResult.getObject(),
                  previousResult.getEndPointIndex(),
                  previousResult.getObject().getPointsLength() - 1));
        } else if (previousResult.getStartPointIndex() > previousResult.getEndPointIndex()
            && previousResult.getEndPointIndex() > 0) {
          rr.attachRoute(
              pointInd,
              new RouteSegmentResult(
                  previousResult.getObject(), previousResult.getEndPointIndex(), 0));
        }
      }
    }
    Iterator<RouteSegment> it;
    if (rr.getPreAttachedRoutes(pointInd) != null) {
      final RouteSegmentResult[] list = rr.getPreAttachedRoutes(pointInd);
      it =
          new Iterator<BinaryRoutePlanner.RouteSegment>() {
            int i = 0;

            @Override
            public boolean hasNext() {
              return i < list.length;
            }

            @Override
            public RouteSegment next() {
              RouteSegmentResult r = list[i++];
              return new RouteSegment(r.getObject(), r.getStartPointIndex());
            }

            @Override
            public void remove() {}
          };
    } else {
      RouteSegment rt =
          ctx.loadRouteSegment(
              road.getPoint31XTile(pointInd),
              road.getPoint31YTile(pointInd),
              ctx.config.memoryLimitation);
      it = rt.getIterator();
    }
    // try to attach all segments except with current id
    while (it.hasNext()) {
      RouteSegment routeSegment = it.next();
      if (routeSegment.road.getId() != road.getId()
          && routeSegment.road.getId() != previousRoadId) {
        RouteDataObject addRoad = routeSegment.road;
        checkAndInitRouteRegion(ctx, addRoad);
        // TODO restrictions can be considered as well
        int oneWay = ctx.getRouter().isOneWay(addRoad);
        if (oneWay >= 0 && routeSegment.getSegmentStart() < addRoad.getPointsLength() - 1) {
          long pointL = getPoint(addRoad, routeSegment.getSegmentStart() + 1);
          if (pointL != nextL && pointL != prevL) {
            // if way contains same segment (nodes) as different way (do not attach it)
            rr.attachRoute(
                pointInd,
                new RouteSegmentResult(
                    addRoad, routeSegment.getSegmentStart(), addRoad.getPointsLength() - 1));
          }
        }
        if (oneWay <= 0 && routeSegment.getSegmentStart() > 0) {
          long pointL = getPoint(addRoad, routeSegment.getSegmentStart() - 1);
          // if way contains same segment (nodes) as different way (do not attach it)
          if (pointL != nextL && pointL != prevL) {
            rr.attachRoute(
                pointInd, new RouteSegmentResult(addRoad, routeSegment.getSegmentStart(), 0));
          }
        }
      }
    }
  }
  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>");
  }
 private boolean combineTwoSegmentResult(
     RouteSegmentResult toAdd, RouteSegmentResult previous, boolean reverse) {
   boolean ld = previous.getEndPointIndex() > previous.getStartPointIndex();
   boolean rd = toAdd.getEndPointIndex() > toAdd.getStartPointIndex();
   if (rd == ld) {
     if (toAdd.getStartPointIndex() == previous.getEndPointIndex() && !reverse) {
       previous.setEndPointIndex(toAdd.getEndPointIndex());
       return true;
     } else if (toAdd.getEndPointIndex() == previous.getStartPointIndex() && reverse) {
       previous.setStartPointIndex(toAdd.getStartPointIndex());
       return true;
     }
   }
   return false;
 }