public void loadBorderPoints() throws IOException {
    Iterator<Entry<RouteRegion, BinaryMapIndexReader>> it = reverseMap.entrySet().iterator();
    int sleft = Math.min(startX, targetX);
    int sright = Math.max(startX, targetX);
    int stop = Math.min(startY, targetY);
    int sbottom = Math.max(startY, targetY);
    // one tile of 12th zoom around (?)
    int zoomAround = 10;
    int distAround = 1 << (31 - zoomAround);
    leftBorderBoundary = sleft - distAround;
    rightBorderBoundary = sright + distAround;
    SearchRequest<RouteDataBorderLinePoint> req =
        BinaryMapIndexReader.buildSearchRouteBorderRequest(sleft, sright, stop, sbottom);
    while (it.hasNext()) {
      Entry<RouteRegion, BinaryMapIndexReader> entry = it.next();
      entry.getValue().searchBorderPoints(req, entry.getKey());
    }
    TIntObjectHashMap<RouteDataBorderLine> lines =
        new TIntObjectHashMap<RoutingContext.RouteDataBorderLine>();
    for (RouteDataBorderLinePoint p : req.getSearchResults()) {
      if (config.router.acceptLine(p) && p.x > leftBorderBoundary && p.x < rightBorderBoundary) {
        if (!lines.containsKey(p.y)) {
          RouteDataBorderLine line = new RouteDataBorderLine(p.y);
          lines.put(p.y, line);
          RouteDataBorderLinePoint lft = new RouteDataBorderLinePoint(p.region);
          lft.y = p.y;
          lft.id = Long.MIN_VALUE;
          lft.x = leftBorderBoundary;
          line.borderPoints.add(lft);
          RouteDataBorderLinePoint rht = new RouteDataBorderLinePoint(p.region);
          rht.y = p.y;
          rht.id = Long.MIN_VALUE;
          rht.x = rightBorderBoundary;
          line.borderPoints.add(rht);
        }
        lines.get(p.y).borderPoints.add(p);
      }
    }
    borderLines = lines.values(new RouteDataBorderLine[lines.size()]);
    Arrays.sort(borderLines);
    borderLineCoordinates = new int[borderLines.length];
    for (int i = 0; i < borderLineCoordinates.length; i++) {
      borderLineCoordinates[i] = borderLines[i].borderLine;
      // FIXME borders approach
      // not less then 14th zoom
      if (i > 0 && borderLineCoordinates[i - 1] >> 17 == borderLineCoordinates[i] >> 17) {
        throw new IllegalStateException();
      }
      System.out.println(
          "Line "
              + (borderLineCoordinates[i] >> 17)
              + " points "
              + borderLines[i].borderPoints.size() /* + " " +borderLines[i].borderPoints*/);
    }

    updateDistanceForBorderPoints(startX, startY, true);
    updateDistanceForBorderPoints(targetX, targetY, false);
  }
  private void readBorderLines(
      SearchRequest<RouteDataBorderLinePoint> req, TIntArrayList blocksToRead) throws IOException {
    while (true) {
      int t = codedIS.readTag();
      int tag = WireFormat.getTagFieldNumber(t);
      switch (tag) {
        case 0:
          return;
        case RouteBorderBox.BORDERLINES_FIELD_NUMBER:
          {
            int fp = codedIS.getTotalBytesRead();
            int length = codedIS.readRawVarint32();
            int old = codedIS.pushLimit(length);

            RouteBorderLine ln = readBorderLine();
            if (ln.hasTox() && req.intersects(ln.getX(), ln.getY(), ln.getTox(), ln.getY())) {
              blocksToRead.add(ln.getShiftToPointsBlock() + fp);
              // FIXME borders approach
              //				} else if(ln.hasToy() && req.intersects(ln.getX(), ln.getY(), ln.getX(),
              // ln.getToy())) {
              //					blocksToRead.add(ln.getShiftToPointsBlock() + fp);
            }
            codedIS.popLimit(old);
            break;
          }
        case RouteBorderBox.BLOCKS_FIELD_NUMBER:
          return;
        default:
          skipUnknownField(t);
          break;
      }
    }
  }
 public void initRouteTypesIfNeeded(SearchRequest<RouteDataObject> req, List<RouteSubregion> list)
     throws IOException {
   for (RouteSubregion rs : list) {
     if (req.intersects(rs.left, rs.top, rs.right, rs.bottom)) {
       initRouteRegion(rs.routeReg);
     }
   }
 }
  public List<RouteSubregion> loadInteresectedPoints(
      SearchRequest<RouteDataObject> req, List<RouteSubregion> list, List<RouteSubregion> toLoad)
      throws IOException {
    for (RouteSubregion rs : list) {
      if (req.intersects(rs.left, rs.top, rs.right, rs.bottom)) {
        if (rs.subregions == null) {
          codedIS.seek(rs.filePointer);
          int old = codedIS.pushLimit(rs.length);
          readRouteTree(
              rs, null, req.contains(rs.left, rs.top, rs.right, rs.bottom) ? -1 : 1, false);
          codedIS.popLimit(old);
        }
        searchRouteRegionTree(req, rs.subregions, toLoad);

        if (rs.shiftToData != 0) {
          toLoad.add(rs);
        }
      }
    }
    return toLoad;
  }
 private void readBorderLinePoints(SearchRequest<RouteDataBorderLinePoint> req, RouteRegion r)
     throws IOException {
   int x = 0;
   int y = 0;
   long id = 0;
   while (true) {
     int t = codedIS.readTag();
     int tag = WireFormat.getTagFieldNumber(t);
     switch (tag) {
       case 0:
         return;
       case RouteBorderPointsBlock.X_FIELD_NUMBER:
         {
           x = codedIS.readInt32();
           break;
         }
       case RouteBorderPointsBlock.Y_FIELD_NUMBER:
         {
           y = codedIS.readInt32();
           break;
         }
       case RouteBorderPointsBlock.BASEID_FIELD_NUMBER:
         {
           id = codedIS.readInt64();
           break;
         }
       case RouteBorderPointsBlock.POINTS_FIELD_NUMBER:
         int len = codedIS.readRawVarint32();
         int oldLimit = codedIS.pushLimit(len);
         RouteDataBorderLinePoint p =
             readBorderLinePoint(new RouteDataBorderLinePoint(r), x, y, id);
         codedIS.popLimit(oldLimit);
         x = p.x;
         y = p.y;
         id = p.id;
         req.publish(p);
         break;
       default:
         skipUnknownField(t);
         break;
     }
   }
 }
  public List<RouteDataBorderLinePoint> searchBorderPoints(
      SearchRequest<RouteDataBorderLinePoint> req, RouteRegion r) throws IOException {
    if (r.borderBoxPointer != 0) {
      codedIS.seek(r.borderBoxPointer);
      int old = codedIS.pushLimit(r.borderBoxLength);
      TIntArrayList blocksToRead = new TIntArrayList();
      readBorderLines(req, blocksToRead);

      blocksToRead.sort();
      for (int j = 0; j < blocksToRead.size(); j++) {
        codedIS.seek(blocksToRead.get(j));
        int len = codedIS.readRawVarint32();
        int oldLimit = codedIS.pushLimit(len);
        readBorderLinePoints(req, r);
        codedIS.popLimit(oldLimit);
      }
      codedIS.popLimit(old);
    }
    return req.getSearchResults();
  }
  protected void searchTransportTreeBounds(
      int pleft, int pright, int ptop, int pbottom, SearchRequest<TransportStop> req)
      throws IOException {
    int init = 0;
    int lastIndexResult = -1;
    int cright = 0;
    int cleft = 0;
    int ctop = 0;
    int cbottom = 0;
    req.numberOfReadSubtrees++;
    while (true) {
      if (req.isInterrupted()) {
        return;
      }
      int t = codedIS.readTag();
      int tag = WireFormat.getTagFieldNumber(t);
      if (init == 0xf) {
        // coordinates are init
        init = 0;
        if (cright < req.left || cleft > req.right || ctop > req.bottom || cbottom < req.top) {
          return;
        } else {
          req.numberOfAcceptedSubtrees++;
        }
      }
      switch (tag) {
        case 0:
          return;
        case OsmandOdb.TransportStopsTree.BOTTOM_FIELD_NUMBER:
          cbottom = codedIS.readSInt32() + pbottom;
          init |= 1;
          break;
        case OsmandOdb.TransportStopsTree.LEFT_FIELD_NUMBER:
          cleft = codedIS.readSInt32() + pleft;
          init |= 2;
          break;
        case OsmandOdb.TransportStopsTree.RIGHT_FIELD_NUMBER:
          cright = codedIS.readSInt32() + pright;
          init |= 4;
          break;
        case OsmandOdb.TransportStopsTree.TOP_FIELD_NUMBER:
          ctop = codedIS.readSInt32() + ptop;
          init |= 8;
          break;
        case OsmandOdb.TransportStopsTree.LEAFS_FIELD_NUMBER:
          int stopOffset = codedIS.getTotalBytesRead();
          int length = codedIS.readRawVarint32();
          int oldLimit = codedIS.pushLimit(length);
          if (lastIndexResult == -1) {
            lastIndexResult = req.searchResults.size();
          }
          req.numberOfVisitedObjects++;
          TransportStop transportStop =
              readTransportStop(stopOffset, cleft, cright, ctop, cbottom, req);
          if (transportStop != null) {
            req.searchResults.add(transportStop);
          }
          codedIS.popLimit(oldLimit);
          break;
        case OsmandOdb.TransportStopsTree.SUBTREES_FIELD_NUMBER:
          // left, ... already initialized
          length = readInt();
          int filePointer = codedIS.getTotalBytesRead();
          if (req.limit == -1 || req.limit >= req.searchResults.size()) {
            oldLimit = codedIS.pushLimit(length);
            searchTransportTreeBounds(cleft, cright, ctop, cbottom, req);
            codedIS.popLimit(oldLimit);
          }
          codedIS.seek(filePointer + length);

          if (lastIndexResult >= 0) {
            throw new IllegalStateException();
          }
          break;
        case OsmandOdb.TransportStopsTree.BASEID_FIELD_NUMBER:
          long baseId = codedIS.readUInt64();
          if (lastIndexResult != -1) {
            for (int i = lastIndexResult; i < req.searchResults.size(); i++) {
              TransportStop rs = req.searchResults.get(i);
              rs.setId(rs.getId() + baseId);
            }
          }
          break;
        default:
          skipUnknownField(t);
          break;
      }
    }
  }
  public void loadRoutes(final RoutingContext ctx, int tileX, int tileY) throws IOException {
    int tileC = (tileX << ctx.getZoomToLoadTileWithRoads()) + tileY;
    if (ctx.loadedTiles.contains(tileC)) {
      return;
    }
    long now = System.nanoTime();

    int zoomToLoad = 31 - ctx.getZoomToLoadTileWithRoads();
    SearchFilter searchFilter =
        new BinaryMapIndexReader.SearchFilter() {
          @Override
          public boolean accept(TIntArrayList types, MapIndex index) {
            for (int j = 0; j < types.size(); j++) {
              int wholeType = types.get(j);
              TagValuePair pair = index.decodeType(wholeType);
              if (pair != null) {
                int t = wholeType & 3;
                if (t == MapRenderingTypes.POINT_TYPE) {
                  if (ctx.getRouter().acceptPoint(pair)) {
                    return true;
                  }
                } else if (t == MapRenderingTypes.POLYLINE_TYPE) {
                  if (ctx.getRouter().acceptLine(pair)) {
                    return true;
                  }
                }
              }
            }
            return false;
          }
        };
    SearchRequest<BinaryMapDataObject> request =
        BinaryMapIndexReader.buildSearchRequest(
            tileX << zoomToLoad,
            (tileX + 1) << zoomToLoad,
            tileY << zoomToLoad,
            (tileY + 1) << zoomToLoad,
            15,
            searchFilter);
    for (BinaryMapIndexReader r : map) {
      r.searchMapIndex(request);
      for (BinaryMapDataObject o : request.getSearchResults()) {
        BinaryMapDataObject old = ctx.idObjects.get(o.getId());
        // sometimes way are presented only partially in one index
        if (old != null && old.getPointsLength() >= o.getPointsLength()) {
          continue;
        }
        ctx.idObjects.put(o.getId(), o);
        for (int j = 0; j < o.getPointsLength(); j++) {
          long l = (((long) o.getPoint31XTile(j)) << 31) + (long) o.getPoint31YTile(j);
          RouteSegment segment = new RouteSegment();
          segment.road = o;
          segment.segmentEnd = segment.segmentStart = j;
          if (ctx.routes.get(l) != null) {
            segment.next = ctx.routes.get(l);
          }
          ctx.routes.put(l, segment);
        }
      }
      ctx.loadedTiles.add(tileC);
      ctx.timeToLoad += (System.nanoTime() - now);
    }
  }
  public List<GeocodingResult> justifyReverseGeocodingSearch(
      final GeocodingResult road,
      BinaryMapIndexReader reader,
      double knownMinBuildingDistance,
      final ResultMatcher<GeocodingResult> result)
      throws IOException {
    // test address index search
    final List<GeocodingResult> streetsList = new ArrayList<GeocodingResult>();
    final List<String> streetNamePacked = prepareStreetName(road.streetName);
    if (streetNamePacked.size() > 0) {
      log.info("Search street by name " + road.streetName + " " + streetNamePacked);
      String mainWord = "";
      for (int i = 0; i < streetNamePacked.size(); i++) {
        String s = streetNamePacked.get(i);
        if (!getSuffixesSet().contains(s) && s.length() > mainWord.length()) {
          mainWord = s;
        }
      }
      if (Algorithms.isEmpty(mainWord)) {
        mainWord = streetNamePacked.get(0);
      }
      SearchRequest<MapObject> req =
          BinaryMapIndexReader.buildAddressByNameRequest(
              new ResultMatcher<MapObject>() {
                @Override
                public boolean publish(MapObject object) {
                  if (object instanceof Street
                      && prepareStreetName(object.getName()).equals(streetNamePacked)) {
                    double d =
                        MapUtils.getDistance(
                            object.getLocation(),
                            road.searchPoint.getLatitude(),
                            road.searchPoint.getLongitude());
                    // double check to suport old format
                    if (d < DISTANCE_STREET_NAME_PROXIMITY_BY_NAME) {
                      GeocodingResult rs = new GeocodingResult(road);
                      rs.street = (Street) object;
                      // set connection point to sort
                      rs.connectionPoint = rs.street.getLocation();
                      rs.city = rs.street.getCity();
                      streetsList.add(rs);
                      return true;
                    }
                    return false;
                  }
                  return false;
                }

                @Override
                public boolean isCancelled() {
                  return result != null && result.isCancelled();
                }
              },
              mainWord,
              StringMatcherMode.CHECK_EQUALS_FROM_SPACE);
      req.setBBoxRadius(
          road.getLocation().getLatitude(),
          road.getLocation().getLongitude(),
          DISTANCE_STREET_NAME_PROXIMITY_BY_NAME);
      reader.searchAddressDataByName(req);
    }

    final List<GeocodingResult> res = new ArrayList<GeocodingResult>();
    if (streetsList.size() == 0) {
      res.add(road);
    } else {
      Collections.sort(streetsList, DISTANCE_COMPARATOR);
      double streetDistance = 0;
      for (GeocodingResult street : streetsList) {
        if (streetDistance == 0) {
          streetDistance = street.getDistance();
        } else if (street.getDistance()
            > streetDistance + DISTANCE_STREET_FROM_CLOSEST_WITH_SAME_NAME) {
          continue;
        }
        street.connectionPoint = road.connectionPoint;
        final List<GeocodingResult> streetBuildings = loadStreetBuildings(road, reader, street);
        Collections.sort(streetBuildings, DISTANCE_COMPARATOR);
        if (streetBuildings.size() > 0) {
          Iterator<GeocodingResult> it = streetBuildings.iterator();
          if (knownMinBuildingDistance == 0) {
            GeocodingResult firstBld = it.next();
            knownMinBuildingDistance = firstBld.getDistance();
            res.add(firstBld);
          }
          while (it.hasNext()) {
            GeocodingResult nextBld = it.next();
            if (nextBld.getDistance()
                > knownMinBuildingDistance * THRESHOLD_MULTIPLIER_SKIP_BUILDINGS_AFTER) {
              break;
            }
            res.add(nextBld);
          }
        }
        res.add(street);
      }
    }
    Collections.sort(res, DISTANCE_COMPARATOR);
    return res;
  }