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;
}
}
};
}
};
}