@Test
  public void testPathRecursiveUnpacking() {
    // use an encoder where it is possible to store 2 weights per edge
    FlagEncoder encoder = new Bike2WeightFlagEncoder();
    ShortestWeighting weighting = new ShortestWeighting(encoder);
    EncodingManager em = new EncodingManager(encoder);
    GraphHopperStorage ghStorage =
        createGHStorage(em, Collections.<Weighting>singleton(weighting), false);
    CHGraphImpl g2 = (CHGraphImpl) ghStorage.getGraph(CHGraph.class, weighting);
    g2.edge(0, 1, 1, true);
    EdgeIteratorState iter1_1 = g2.edge(0, 2, 1.4, false);
    EdgeIteratorState iter1_2 = g2.edge(2, 5, 1.4, false);
    g2.edge(1, 2, 1, true);
    g2.edge(1, 3, 3, true);
    g2.edge(2, 3, 1, true);
    g2.edge(4, 3, 1, true);
    g2.edge(2, 5, 1.4, true);
    g2.edge(3, 5, 1, true);
    g2.edge(5, 6, 1, true);
    g2.edge(4, 6, 1, true);
    g2.edge(6, 7, 1, true);
    EdgeIteratorState iter2_2 = g2.edge(5, 7);
    iter2_2.setDistance(1.4).setFlags(encoder.setProperties(10, true, false));

    ghStorage.freeze();
    // simulate preparation
    CHEdgeIteratorState iter2_1 = g2.shortcut(0, 5);
    iter2_1.setDistance(2.8).setFlags(encoder.setProperties(10, true, false));
    iter2_1.setSkippedEdges(iter1_1.getEdge(), iter1_2.getEdge());
    CHEdgeIteratorState tmp = g2.shortcut(0, 7);
    tmp.setDistance(4.2).setFlags(encoder.setProperties(10, true, false));
    tmp.setSkippedEdges(iter2_1.getEdge(), iter2_2.getEdge());
    g2.setLevel(1, 0);
    g2.setLevel(3, 1);
    g2.setLevel(4, 2);
    g2.setLevel(6, 3);
    g2.setLevel(2, 4);
    g2.setLevel(5, 5);
    g2.setLevel(7, 6);
    g2.setLevel(0, 7);

    AlgorithmOptions opts = new AlgorithmOptions(AlgorithmOptions.DIJKSTRA_BI, encoder, weighting);
    Path p =
        new PrepareContractionHierarchies(
                new GHDirectory("", DAType.RAM_INT),
                ghStorage,
                g2,
                encoder,
                weighting,
                TraversalMode.NODE_BASED)
            .createAlgo(g2, opts)
            .calcPath(0, 7);

    assertEquals(Helper.createTList(0, 2, 5, 7), p.calcNodes());
    assertEquals(1064, p.getTime());
    assertEquals(4.2, p.getDistance(), 1e-5);
  }
  public TestAlgoCollector assertDistance(
      AlgoHelperEntry algoEntry, List<QueryResult> queryList, OneRun oneRun) {
    List<Path> altPaths = new ArrayList<Path>();
    QueryGraph queryGraph = new QueryGraph(algoEntry.getQueryGraph());
    queryGraph.lookup(queryList);
    AlgorithmOptions opts = algoEntry.opts;
    FlagEncoder encoder = opts.getFlagEncoder();
    if (encoder.supports(TurnWeighting.class))
      algoEntry.setAlgorithmOptions(
          AlgorithmOptions.start(opts)
              .weighting(
                  new TurnWeighting(
                      opts.getWeighting(),
                      opts.getFlagEncoder(),
                      (TurnCostExtension) queryGraph.getExtension()))
              .build());

    for (int i = 0; i < queryList.size() - 1; i++) {
      RoutingAlgorithm algo = algoEntry.createAlgo(queryGraph);
      Path path =
          algo.calcPath(queryList.get(i).getClosestNode(), queryList.get(i + 1).getClosestNode());
      // System.out.println(path.calcInstructions().createGPX("temp", 0, "GMT"));
      altPaths.add(path);
    }

    PathMerger pathMerger =
        new PathMerger().setCalcPoints(true).setSimplifyResponse(false).setEnableInstructions(true);
    AltResponse rsp = new AltResponse();
    pathMerger.doWork(rsp, altPaths, trMap.getWithFallBack(Locale.US));

    if (rsp.hasErrors()) {
      errors.add(
          algoEntry
              + " response contains errors. Expected distance: "
              + rsp.getDistance()
              + ", expected points: "
              + oneRun
              + ". "
              + queryList
              + ", errors:"
              + rsp.getErrors());
      return this;
    }

    PointList pointList = rsp.getPoints();
    double tmpDist = pointList.calcDistance(distCalc);
    if (Math.abs(rsp.getDistance() - tmpDist) > 2) {
      errors.add(
          algoEntry
              + " path.getDistance was  "
              + rsp.getDistance()
              + "\t pointList.calcDistance was "
              + tmpDist
              + "\t (expected points "
              + oneRun.getLocs()
              + ", expected distance "
              + oneRun.getDistance()
              + ") "
              + queryList);
    }

    if (Math.abs(rsp.getDistance() - oneRun.getDistance()) > 2) {
      errors.add(
          algoEntry
              + " returns path not matching the expected distance of "
              + oneRun.getDistance()
              + "\t Returned was "
              + rsp.getDistance()
              + "\t (expected points "
              + oneRun.getLocs()
              + ", was "
              + pointList.getSize()
              + ") "
              + queryList);
    }

    // There are real world instances where A-B-C is identical to A-C (in meter precision).
    if (Math.abs(pointList.getSize() - oneRun.getLocs()) > 1) {
      errors.add(
          algoEntry
              + " returns path not matching the expected points of "
              + oneRun.getLocs()
              + "\t Returned was "
              + pointList.getSize()
              + "\t (expected distance "
              + oneRun.getDistance()
              + ", was "
              + rsp.getDistance()
              + ") "
              + queryList);
    }
    return this;
  }
  @Test
  public void testTurnFlagEncoding_withCosts() {
    FlagEncoder tmpEncoder = new CarFlagEncoder(8, 5, 127);
    EncodingManager em = new EncodingManager(tmpEncoder);

    long flags_r0 = tmpEncoder.getTurnFlags(true, 0);
    long flags_0 = tmpEncoder.getTurnFlags(false, 0);
    assertTrue(Double.isInfinite(tmpEncoder.getTurnCost(flags_r0)));
    assertEquals(0, tmpEncoder.getTurnCost(flags_0), 1e-1);
    assertTrue(tmpEncoder.isTurnRestricted(flags_r0));
    assertFalse(tmpEncoder.isTurnRestricted(flags_0));

    long flags_r20 = tmpEncoder.getTurnFlags(true, 0);
    long flags_20 = tmpEncoder.getTurnFlags(false, 20);
    assertTrue(Double.isInfinite(tmpEncoder.getTurnCost(flags_r20)));
    assertEquals(20, tmpEncoder.getTurnCost(flags_20), 1e-1);
    assertTrue(tmpEncoder.isTurnRestricted(flags_r20));
    assertFalse(tmpEncoder.isTurnRestricted(flags_20));

    long flags_r220 = tmpEncoder.getTurnFlags(true, 0);
    try {
      tmpEncoder.getTurnFlags(false, 220);
      assertTrue(false);
    } catch (Exception ex) {
    }
    assertTrue(Double.isInfinite(tmpEncoder.getTurnCost(flags_r220)));
    assertTrue(tmpEncoder.isTurnRestricted(flags_r220));
  }
  @Test
  public void testTurnFlagEncoding_noCosts() {
    FlagEncoder tmpEnc = new CarFlagEncoder(8, 5, 0);
    EncodingManager em = new EncodingManager(tmpEnc);

    long flags_r0 = tmpEnc.getTurnFlags(true, 0);
    long flags_0 = tmpEnc.getTurnFlags(false, 0);

    long flags_r20 = tmpEnc.getTurnFlags(true, 0);
    long flags_20 = tmpEnc.getTurnFlags(false, 20);

    assertEquals(0, tmpEnc.getTurnCost(flags_r0), 1e-1);
    assertEquals(0, tmpEnc.getTurnCost(flags_0), 1e-1);

    assertEquals(0, tmpEnc.getTurnCost(flags_r20), 1e-1);
    assertEquals(0, tmpEnc.getTurnCost(flags_20), 1e-1);

    assertFalse(tmpEnc.isTurnRestricted(flags_r0));
    assertFalse(tmpEnc.isTurnRestricted(flags_0));

    assertFalse(tmpEnc.isTurnRestricted(flags_r20));
    assertFalse(tmpEnc.isTurnRestricted(flags_20));
  }
 @Override
 public String toString() {
   return encoder.toString() + ", in:" + in + ", out:" + out;
 }
 @Override
 public boolean accept(EdgeIteratorState iter) {
   long flags = iter.getFlags();
   return out && encoder.isForward(flags) || in && encoder.isBackward(flags);
 }