class LocationIndexTree implements LocationIndex {

    public List<QueryResult> findNClosest(final double queryLat, final double queryLon,
                                          final EdgeFilter edgeFilter, double radius) {
        // Return ALL results which are very close and e.g. within the GPS signal accuracy.
        // Also important to get all edges if GPS point is close to a junction.
        final double returnAllResultsWithin = distCalc.calcNormalizedDist(radius);

        // implement a cheap priority queue via List, sublist and Collections.sort
        final List<QueryResult> queryResults = new ArrayList<>();
        GHIntHashSet set = new GHIntHashSet();

        // Doing 2 iterations means searching 9 tiles.
        for (int iteration = 0; iteration < 2; iteration++) {
            // should we use the return value of earlyFinish?
            findNetworkEntries(queryLat, queryLon, set, iteration);

            final GHBitSet exploredNodes = new GHTBitSet(new GHIntHashSet(set));
            final EdgeExplorer explorer = graph.createEdgeExplorer(edgeFilter);

            set.forEach(new IntPredicate() {

                @Override
                public boolean apply(int node) {
                    new XFirstSearchCheck(queryLat, queryLon, exploredNodes, edgeFilter) {
                        @Override
                        protected double getQueryDistance() {
                            // do not skip search if distance is 0 or near zero (equalNormedDelta)
                            return Double.MAX_VALUE;
                        }

                        @Override
                        protected boolean check(int node, double normedDist, int wayIndex, EdgeIteratorState edge, QueryResult.Position pos) {
                            if (normedDist < returnAllResultsWithin
                                    || queryResults.isEmpty()
                                    || queryResults.get(0).getQueryDistance() > normedDist) {
                                // TODO: refactor below:
                                // - should only add edges within search radius (below allows the
                                // returning of a candidate outside search radius if it's the only
                                // one). Removing this test would simplify it a lot and probably
                                // match the expected behaviour (see method description)
                                // - create QueryResult first and the add/set as required - clean up
                                // the index tracking business.

                                int index = -1;
                                for (int qrIndex = 0; qrIndex < queryResults.size(); qrIndex++) {
                                    QueryResult qr = queryResults.get(qrIndex);
                                    // overwrite older queryResults which are potentially more far away than returnAllResultsWithin
                                    if (qr.getQueryDistance() > returnAllResultsWithin) {
                                        index = qrIndex;
                                        break;
                                    }

                                    // avoid duplicate edges
                                    if (qr.getClosestEdge().getEdge() == edge.getEdge()) {
                                        if (qr.getQueryDistance() < normedDist) {
                                            // do not add current edge
                                            return true;
                                        } else {
                                            // overwrite old edge with current
                                            index = qrIndex;
                                            break;
                                        }
                                    }
                                }

                                QueryResult qr = new QueryResult(queryLat, queryLon);
                                qr.setQueryDistance(normedDist);
                                qr.setClosestNode(node);
                                qr.setClosestEdge(edge.detach(false));
                                qr.setWayIndex(wayIndex);
                                qr.setSnappedPosition(pos);

                                if (index < 0) {
                                    queryResults.add(qr);
                                } else {
                                    queryResults.set(index, qr);
                                }
                            }
                            return true;
                        }
                    }.start(explorer, node);
                    return true;
                }
            });
        }

        // TODO: pass boolean argument for whether or not to sort? Can be expensive if not required.
        Collections.sort(queryResults, QR_COMPARATOR);

        for (QueryResult qr : queryResults) {
            if (qr.isValid()) {
                // denormalize distance
                qr.setQueryDistance(distCalc.calcDenormalizedDist(qr.getQueryDistance()));
                qr.calcSnappedPoint(distCalc);
            } else {
                throw new IllegalStateException("Invalid QueryResult should not happen here: " + qr);
            }
        }

        return queryResults;
    }

    public  LocationIndexTree(Graph g, Directory dir);

}