public Weight createWeight()

in lucene/core/src/java/org/apache/lucene/document/LatLonPointDistanceQuery.java [81:324]


  public Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float boost)
      throws IOException {
    Rectangle box = Rectangle.fromPointDistance(latitude, longitude, radiusMeters);
    // create bounding box(es) for the distance range
    // these are pre-encoded with LatLonPoint's encoding
    final int minLat = encodeLatitude(box.minLat);
    final int maxLat = encodeLatitude(box.maxLat);
    int minLon;
    int maxLon;
    // second set of longitude ranges to check (for cross-dateline case)
    int minLon2;

    // crosses dateline: split
    if (box.crossesDateline()) {
      // box1
      minLon = Integer.MIN_VALUE;
      maxLon = encodeLongitude(box.maxLon);
      // box2
      minLon2 = encodeLongitude(box.minLon);
    } else {
      minLon = encodeLongitude(box.minLon);
      maxLon = encodeLongitude(box.maxLon);
      // disable box2
      minLon2 = Integer.MAX_VALUE;
    }

    // compute exact sort key: avoid any asin() computations
    final double sortKey = GeoUtils.distanceQuerySortKey(radiusMeters);

    final double axisLat = Rectangle.axisLat(latitude, radiusMeters);

    return new ConstantScoreWeight(this, boost) {

      final GeoEncodingUtils.DistancePredicate distancePredicate =
          GeoEncodingUtils.createDistancePredicate(latitude, longitude, radiusMeters);

      @Override
      public boolean isCacheable(LeafReaderContext ctx) {
        return true;
      }

      @Override
      public ScorerSupplier scorerSupplier(LeafReaderContext context) throws IOException {
        LeafReader reader = context.reader();
        PointValues values = reader.getPointValues(field);
        if (values == null) {
          // No docs in this segment had any points fields
          return null;
        }
        FieldInfo fieldInfo = reader.getFieldInfos().fieldInfo(field);
        if (fieldInfo == null) {
          // No docs in this segment indexed this field at all
          return null;
        }
        LatLonPoint.checkCompatible(fieldInfo);

        // matching docids
        DocIdSetBuilder result = new DocIdSetBuilder(reader.maxDoc(), values);
        final IntersectVisitor visitor = getIntersectVisitor(result);

        return new ScorerSupplier() {

          long cost = -1;

          @Override
          public Scorer get(long leadCost) throws IOException {
            if (values.getDocCount() == reader.maxDoc()
                && values.getDocCount() == values.size()
                && cost() > reader.maxDoc() / 2) {
              // If all docs have exactly one value and the cost is greater
              // than half the leaf size then maybe we can make things faster
              // by computing the set of documents that do NOT match the range
              final FixedBitSet result = new FixedBitSet(reader.maxDoc());
              result.set(0, reader.maxDoc());
              long[] cost = new long[] {reader.maxDoc()};
              values.intersect(getInverseIntersectVisitor(result, cost));
              final DocIdSetIterator iterator = new BitSetIterator(result, cost[0]);
              return new ConstantScoreScorer(score(), scoreMode, iterator);
            }
            values.intersect(visitor);
            return new ConstantScoreScorer(score(), scoreMode, result.build().iterator());
          }

          @Override
          public long cost() {
            if (cost == -1) {
              cost = values.estimateDocCount(visitor);
            }
            assert cost >= 0;
            return cost;
          }
        };
      }

      private boolean matches(byte[] packedValue) {
        int lat = NumericUtils.sortableBytesToInt(packedValue, 0);
        // bounding box check
        if (lat > maxLat || lat < minLat) {
          // latitude out of bounding box range
          return false;
        }
        int lon = NumericUtils.sortableBytesToInt(packedValue, Integer.BYTES);
        if ((lon > maxLon || lon < minLon) && lon < minLon2) {
          // longitude out of bounding box range
          return false;
        }
        return distancePredicate.test(lat, lon);
      }

      // algorithm: we create a bounding box (two bounding boxes if we cross the dateline).
      // 1. check our bounding box(es) first. if the subtree is entirely outside of those, bail.
      // 2. check if the subtree is disjoint. it may cross the bounding box but not intersect with
      // circle
      // 3. see if the subtree is fully contained. if the subtree is enormous along the x axis,
      // wrapping half way around the world, etc: then this can't work, just go to step 4.
      // 4. recurse naively (subtrees crossing over circle edge)
      private Relation relate(byte[] minPackedValue, byte[] maxPackedValue) {
        int latLowerBound = NumericUtils.sortableBytesToInt(minPackedValue, 0);
        int latUpperBound = NumericUtils.sortableBytesToInt(maxPackedValue, 0);
        if (latLowerBound > maxLat || latUpperBound < minLat) {
          // latitude out of bounding box range
          return Relation.CELL_OUTSIDE_QUERY;
        }

        int lonLowerBound = NumericUtils.sortableBytesToInt(minPackedValue, LatLonPoint.BYTES);
        int lonUpperBound = NumericUtils.sortableBytesToInt(maxPackedValue, LatLonPoint.BYTES);
        if ((lonLowerBound > maxLon || lonUpperBound < minLon) && lonUpperBound < minLon2) {
          // longitude out of bounding box range
          return Relation.CELL_OUTSIDE_QUERY;
        }

        double latMin = decodeLatitude(latLowerBound);
        double lonMin = decodeLongitude(lonLowerBound);
        double latMax = decodeLatitude(latUpperBound);
        double lonMax = decodeLongitude(lonUpperBound);

        return GeoUtils.relate(
            latMin, latMax, lonMin, lonMax, latitude, longitude, sortKey, axisLat);
      }

      /** Create a visitor that collects documents matching the range. */
      private IntersectVisitor getIntersectVisitor(DocIdSetBuilder result) {
        return new IntersectVisitor() {

          DocIdSetBuilder.BulkAdder adder;

          @Override
          public void grow(int count) {
            adder = result.grow(count);
          }

          @Override
          public void visit(int docID) {
            adder.add(docID);
          }

          @Override
          public void visit(IntsRef ref) {
            adder.add(ref);
          }

          @Override
          public void visit(DocIdSetIterator iterator) throws IOException {
            adder.add(iterator);
          }

          @Override
          public void visit(int docID, byte[] packedValue) {
            if (matches(packedValue)) {
              visit(docID);
            }
          }

          @Override
          public void visit(DocIdSetIterator iterator, byte[] packedValue) throws IOException {
            if (matches(packedValue)) {
              adder.add(iterator);
            }
          }

          @Override
          public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
            return relate(minPackedValue, maxPackedValue);
          }
        };
      }

      /** Create a visitor that clears documents that do NOT match the range. */
      private IntersectVisitor getInverseIntersectVisitor(FixedBitSet result, long[] cost) {
        return new IntersectVisitor() {

          @Override
          public void visit(int docID) {
            result.clear(docID);
            cost[0]--;
          }

          @Override
          public void visit(IntsRef ref) {
            for (int i = 0; i < ref.length; i++) {
              result.clear(ref.ints[ref.offset + i]);
            }
            cost[0] = Math.max(0, cost[0] - ref.length);
          }

          @Override
          public void visit(DocIdSetIterator iterator) throws IOException {
            result.andNot(iterator);
            cost[0] = Math.max(0, cost[0] - iterator.cost());
          }

          @Override
          public void visit(int docID, byte[] packedValue) {
            if (matches(packedValue) == false) {
              visit(docID);
            }
          }

          @Override
          public void visit(DocIdSetIterator iterator, byte[] packedValue) throws IOException {
            if (matches(packedValue) == false) {
              visit(iterator);
            }
          }

          @Override
          public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
            Relation relation = relate(minPackedValue, maxPackedValue);
            switch (relation) {
              case CELL_INSIDE_QUERY:
                // all points match, skip this subtree
                return Relation.CELL_OUTSIDE_QUERY;
              case CELL_OUTSIDE_QUERY:
                // none of the points match, clear all documents
                return Relation.CELL_INSIDE_QUERY;
              case CELL_CROSSES_QUERY:
              default:
                return relation;
            }
          }
        };
      }
    };
  }