mirror of https://github.com/apache/lucene.git
LUCENE-7664: remove GeoPointField and its queries
This commit is contained in:
parent
7f13105fbe
commit
3e730f9bd2
|
@ -163,7 +163,6 @@ class WordDictionary extends AbstractDictionary {
|
|||
output.writeObject(charIndexTable);
|
||||
output.writeObject(wordItem_charArrayTable);
|
||||
output.writeObject(wordItem_frequencyTable);
|
||||
output.close();
|
||||
// log.info("serialize core dict.");
|
||||
} catch (Exception e) {
|
||||
// log.warn(e.getMessage());
|
||||
|
|
|
@ -1,266 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.lucene.spatial.geopoint.document;
|
||||
|
||||
import org.apache.lucene.analysis.Analyzer;
|
||||
import org.apache.lucene.analysis.TokenStream;
|
||||
import org.apache.lucene.document.Field;
|
||||
import org.apache.lucene.document.FieldType;
|
||||
import org.apache.lucene.index.DocValuesType;
|
||||
import org.apache.lucene.index.IndexOptions;
|
||||
import org.apache.lucene.geo.GeoUtils;
|
||||
import org.apache.lucene.util.BitUtil;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
import org.apache.lucene.util.BytesRefBuilder;
|
||||
|
||||
import static org.apache.lucene.spatial.util.MortonEncoder.encode;
|
||||
import static org.apache.lucene.geo.GeoUtils.MIN_LAT_INCL;
|
||||
import static org.apache.lucene.geo.GeoUtils.MIN_LON_INCL;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Field that indexes <code>latitude</code> <code>longitude</code> decimal-degree values
|
||||
* for efficient encoding, sorting, and querying. This Geo capability is intended
|
||||
* to provide a basic and efficient out of the box field type for indexing and
|
||||
* querying 2 dimensional points in WGS-84 decimal degrees. An example usage is as follows:
|
||||
*
|
||||
* <pre class="prettyprint">
|
||||
* document.add(new GeoPointField(name, -96.33, 32.66, Field.Store.NO));
|
||||
* </pre>
|
||||
*
|
||||
* <p>To perform simple geospatial queries against a <code>GeoPointField</code>,
|
||||
* see {@link org.apache.lucene.spatial.geopoint.search.GeoPointInBBoxQuery}, {@link org.apache.lucene.spatial.geopoint.search.GeoPointInPolygonQuery},
|
||||
* or {@link org.apache.lucene.spatial.geopoint.search.GeoPointDistanceQuery}
|
||||
*
|
||||
* @lucene.experimental
|
||||
*/
|
||||
public final class GeoPointField extends Field {
|
||||
/** encoding step value for GeoPoint prefix terms */
|
||||
public static final int PRECISION_STEP = 9;
|
||||
|
||||
/** number of bits used for quantizing latitude and longitude values */
|
||||
public static final short BITS = 31;
|
||||
/** scaling factors to convert lat/lon into unsigned space */
|
||||
private static final double LAT_SCALE = (0x1L<<BITS)/180.0D;
|
||||
private static final double LON_SCALE = (0x1L<<BITS)/360.0D;
|
||||
|
||||
/**
|
||||
* The maximum term length (used for <code>byte[]</code> buffer size)
|
||||
* for encoding <code>geoEncoded</code> values.
|
||||
* @see #geoCodedToPrefixCodedBytes(long, int, BytesRefBuilder)
|
||||
*/
|
||||
private static final int BUF_SIZE_LONG = 28/8 + 1;
|
||||
|
||||
/**
|
||||
* Type for a GeoPointField that is not stored:
|
||||
* normalization factors, frequencies, and positions are omitted.
|
||||
*/
|
||||
public static final FieldType TYPE_NOT_STORED = new FieldType();
|
||||
static {
|
||||
TYPE_NOT_STORED.setTokenized(false);
|
||||
TYPE_NOT_STORED.setOmitNorms(true);
|
||||
TYPE_NOT_STORED.setIndexOptions(IndexOptions.DOCS);
|
||||
TYPE_NOT_STORED.setDocValuesType(DocValuesType.SORTED_NUMERIC);
|
||||
TYPE_NOT_STORED.freeze();
|
||||
}
|
||||
|
||||
/**
|
||||
* Type for a stored GeoPointField:
|
||||
* normalization factors, frequencies, and positions are omitted.
|
||||
*/
|
||||
public static final FieldType TYPE_STORED = new FieldType();
|
||||
static {
|
||||
TYPE_STORED.setTokenized(false);
|
||||
TYPE_STORED.setOmitNorms(true);
|
||||
TYPE_STORED.setIndexOptions(IndexOptions.DOCS);
|
||||
TYPE_STORED.setDocValuesType(DocValuesType.SORTED_NUMERIC);
|
||||
TYPE_STORED.setStored(true);
|
||||
TYPE_STORED.freeze();
|
||||
}
|
||||
|
||||
/** Creates a stored or un-stored GeoPointField
|
||||
* @param name field name
|
||||
* @param latitude latitude double value [-90.0 : 90.0]
|
||||
* @param longitude longitude double value [-180.0 : 180.0]
|
||||
* @param stored Store.YES if the content should also be stored
|
||||
* @throws IllegalArgumentException if the field name is null.
|
||||
*/
|
||||
public GeoPointField(String name, double latitude, double longitude, Store stored) {
|
||||
this(name, latitude, longitude, getFieldType(stored));
|
||||
}
|
||||
|
||||
/** Expert: allows you to customize the {@link
|
||||
* FieldType}.
|
||||
* @param name field name
|
||||
* @param latitude latitude double value [-90.0 : 90.0]
|
||||
* @param longitude longitude double value [-180.0 : 180.0]
|
||||
* @param type customized field type
|
||||
* @throws IllegalArgumentException if the field name or type is null
|
||||
*/
|
||||
public GeoPointField(String name, double latitude, double longitude, FieldType type) {
|
||||
super(name, type);
|
||||
|
||||
GeoUtils.checkLatitude(latitude);
|
||||
GeoUtils.checkLongitude(longitude);
|
||||
|
||||
// field must be indexed
|
||||
// todo does it make sense here to provide the ability to store a GeoPointField but not index?
|
||||
if (type.indexOptions() == IndexOptions.NONE && type.stored() == false) {
|
||||
throw new IllegalArgumentException("type.indexOptions() is set to NONE but type.stored() is false");
|
||||
} else if (type.indexOptions() == IndexOptions.DOCS) {
|
||||
if (type.docValuesType() != DocValuesType.SORTED_NUMERIC) {
|
||||
throw new IllegalArgumentException("type.docValuesType() must be SORTED_NUMERIC but got " + type.docValuesType());
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("type.indexOptions() must be one of NONE or DOCS but got " + type.indexOptions());
|
||||
}
|
||||
|
||||
// set field data
|
||||
fieldsData = encodeLatLon(latitude, longitude);
|
||||
}
|
||||
|
||||
/**
|
||||
* Static helper method for returning a valid FieldType based on termEncoding and stored options
|
||||
*/
|
||||
private static FieldType getFieldType(Store stored) {
|
||||
if (stored == Store.YES) {
|
||||
return TYPE_STORED;
|
||||
} else if (stored == Store.NO) {
|
||||
return TYPE_NOT_STORED;
|
||||
} else {
|
||||
throw new IllegalArgumentException("stored option must be NO or YES but got " + stored);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TokenStream tokenStream(Analyzer analyzer, TokenStream reuse) {
|
||||
if (fieldType().indexOptions() == IndexOptions.NONE) {
|
||||
// not indexed
|
||||
return null;
|
||||
}
|
||||
|
||||
if (reuse instanceof GeoPointTokenStream == false) {
|
||||
reuse = new GeoPointTokenStream();
|
||||
}
|
||||
|
||||
final GeoPointTokenStream gpts = (GeoPointTokenStream)reuse;
|
||||
gpts.setGeoCode(((Number) fieldsData).longValue());
|
||||
|
||||
return reuse;
|
||||
}
|
||||
|
||||
/** access latitude value */
|
||||
public double getLat() {
|
||||
return decodeLatitude((long) fieldsData);
|
||||
}
|
||||
|
||||
/** access longitude value */
|
||||
public double getLon() {
|
||||
return decodeLongitude((long) fieldsData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (fieldsData == null) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(decodeLatitude((long) fieldsData));
|
||||
sb.append(',');
|
||||
sb.append(decodeLongitude((long) fieldsData));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/*************************
|
||||
* 31 bit encoding utils *
|
||||
*************************/
|
||||
public static long encodeLatLon(final double lat, final double lon) {
|
||||
long result = encode(lat, lon);
|
||||
if (result == 0xFFFFFFFFFFFFFFFFL) {
|
||||
return result & 0xC000000000000000L;
|
||||
}
|
||||
return result >>> 2;
|
||||
}
|
||||
|
||||
/** decode longitude value from morton encoded geo point */
|
||||
public static final double decodeLongitude(final long hash) {
|
||||
return unscaleLon(BitUtil.deinterleave(hash));
|
||||
}
|
||||
|
||||
/** decode latitude value from morton encoded geo point */
|
||||
public static final double decodeLatitude(final long hash) {
|
||||
return unscaleLat(BitUtil.deinterleave(hash >>> 1));
|
||||
}
|
||||
|
||||
private static final double unscaleLon(final long val) {
|
||||
return (val / LON_SCALE) + MIN_LON_INCL;
|
||||
}
|
||||
|
||||
private static final double unscaleLat(final long val) {
|
||||
return (val / LAT_SCALE) + MIN_LAT_INCL;
|
||||
}
|
||||
|
||||
/** Convert a geocoded morton long into a prefix coded geo term */
|
||||
public static void geoCodedToPrefixCoded(long hash, int shift, BytesRefBuilder bytes) {
|
||||
geoCodedToPrefixCodedBytes(hash, shift, bytes);
|
||||
}
|
||||
|
||||
/** Convert a prefix coded geo term back into the geocoded morton long */
|
||||
public static long prefixCodedToGeoCoded(final BytesRef val) {
|
||||
final long result = 0L
|
||||
| (val.bytes[val.offset+0] & 255L) << 24
|
||||
| (val.bytes[val.offset+1] & 255L) << 16
|
||||
| (val.bytes[val.offset+2] & 255L) << 8
|
||||
| val.bytes[val.offset+3] & 255L;
|
||||
|
||||
return result << 32;
|
||||
}
|
||||
|
||||
/**
|
||||
* GeoTerms are coded using 4 prefix bytes + 1 byte to record number of prefix bits
|
||||
*
|
||||
* example prefix at shift 54 (yields 10 significant prefix bits):
|
||||
* pppppppp pp000000 00000000 00000000 00001010
|
||||
* (byte 1) (byte 2) (byte 3) (byte 4) (sigbits)
|
||||
*/
|
||||
private static void geoCodedToPrefixCodedBytes(final long hash, final int shift, final BytesRefBuilder bytes) {
|
||||
// ensure shift is 32..63
|
||||
if (shift < 32 || shift > 63) {
|
||||
throw new IllegalArgumentException("Illegal shift value, must be 32..63; got shift=" + shift);
|
||||
}
|
||||
int nChars = BUF_SIZE_LONG + 1; // one extra for the byte that contains the number of significant bits
|
||||
bytes.setLength(nChars);
|
||||
bytes.grow(nChars--);
|
||||
final int sigBits = 64 - shift;
|
||||
bytes.setByteAt(BUF_SIZE_LONG, (byte)(sigBits));
|
||||
long sortableBits = hash;
|
||||
sortableBits >>>= shift;
|
||||
sortableBits <<= 32 - sigBits;
|
||||
do {
|
||||
bytes.setByteAt(--nChars, (byte)(sortableBits));
|
||||
sortableBits >>>= 8;
|
||||
} while (nChars > 0);
|
||||
}
|
||||
|
||||
/** Get the prefix coded geo term shift value */
|
||||
public static int getPrefixCodedShift(final BytesRef val) {
|
||||
final int shift = val.bytes[val.offset + BUF_SIZE_LONG];
|
||||
if (shift > 63 || shift < 0)
|
||||
throw new NumberFormatException("Invalid shift value (" + shift + ") in prefixCoded bytes (is encoded value really a geo point?)");
|
||||
return shift;
|
||||
}
|
||||
}
|
|
@ -1,218 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.geopoint.document;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.lucene.analysis.TokenStream;
|
||||
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
|
||||
import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
|
||||
import org.apache.lucene.analysis.tokenattributes.TermToBytesRefAttribute;
|
||||
import org.apache.lucene.util.Attribute;
|
||||
import org.apache.lucene.util.AttributeFactory;
|
||||
import org.apache.lucene.util.AttributeImpl;
|
||||
import org.apache.lucene.util.AttributeReflector;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
import org.apache.lucene.util.BytesRefBuilder;
|
||||
|
||||
import static org.apache.lucene.spatial.geopoint.document.GeoPointField.geoCodedToPrefixCoded;
|
||||
import static org.apache.lucene.spatial.geopoint.document.GeoPointField.PRECISION_STEP;
|
||||
|
||||
/**
|
||||
* <b>Expert:</b> This class provides a {@link TokenStream} used by {@link GeoPointField}
|
||||
* for encoding GeoPoint terms.
|
||||
*
|
||||
* This class encodes terms up to a maximum of {@link #MAX_SHIFT} using a fixed precision step defined by
|
||||
* {@link GeoPointField#PRECISION_STEP}. This yields a total of 4 terms per GeoPoint
|
||||
* each consisting of 5 bytes (4 prefix bytes + 1 precision byte).
|
||||
*
|
||||
* Here's an example usage:
|
||||
*
|
||||
* <pre class="prettyprint">
|
||||
* // using prefix terms
|
||||
* GeoPointField geoPointField = new GeoPointField(fieldName1, lat, lon, GeoPointField.TYPE_NOT_STORED);
|
||||
* document.add(geoPointField);
|
||||
*
|
||||
* // query by bounding box
|
||||
* Query q = new GeoPointInBBoxQuery(fieldName1, minLat, maxLat, minLon, maxLon);
|
||||
*
|
||||
* // query by distance
|
||||
* q = new GeoPointDistanceQuery(fieldName2, centerLat, centerLon, radiusMeters);
|
||||
* </pre>
|
||||
*
|
||||
* @lucene.experimental
|
||||
*/
|
||||
final class GeoPointTokenStream extends TokenStream {
|
||||
private static final int MAX_SHIFT = PRECISION_STEP * 4;
|
||||
|
||||
private final GeoPointTermAttribute geoPointTermAtt = addAttribute(GeoPointTermAttribute.class);
|
||||
private final PositionIncrementAttribute posIncrAtt = addAttribute(PositionIncrementAttribute.class);
|
||||
|
||||
private boolean isInit = false;
|
||||
|
||||
/**
|
||||
* Expert: Creates a token stream for geo point fields with the specified
|
||||
* <code>precisionStep</code> using the given
|
||||
* {@link org.apache.lucene.util.AttributeFactory}.
|
||||
* The stream is not yet initialized,
|
||||
* before using set a value using the various setGeoCode method.
|
||||
*/
|
||||
public GeoPointTokenStream() {
|
||||
super(new GeoPointAttributeFactory(AttributeFactory.DEFAULT_ATTRIBUTE_FACTORY));
|
||||
assert PRECISION_STEP > 0;
|
||||
}
|
||||
|
||||
public GeoPointTokenStream setGeoCode(final long geoCode) {
|
||||
geoPointTermAtt.init(geoCode, MAX_SHIFT-PRECISION_STEP);
|
||||
isInit = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
if (isInit == false) {
|
||||
throw new IllegalStateException("call setGeoCode() before usage");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean incrementToken() {
|
||||
if (isInit == false) {
|
||||
throw new IllegalStateException("call setGeoCode() before usage");
|
||||
}
|
||||
|
||||
// this will only clear all other attributes in this TokenStream
|
||||
clearAttributes();
|
||||
|
||||
final int shift = geoPointTermAtt.incShift();
|
||||
posIncrAtt.setPositionIncrement((shift == MAX_SHIFT) ? 1 : 0);
|
||||
return (shift < 63);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks shift values during encoding
|
||||
*/
|
||||
public interface GeoPointTermAttribute extends Attribute {
|
||||
/** Returns current shift value, undefined before first token */
|
||||
int getShift();
|
||||
|
||||
/** <em>Don't call this method!</em>
|
||||
* @lucene.internal */
|
||||
void init(long value, int shift);
|
||||
|
||||
/** <em>Don't call this method!</em>
|
||||
* @lucene.internal */
|
||||
int incShift();
|
||||
}
|
||||
|
||||
// just a wrapper to prevent adding CTA
|
||||
private static final class GeoPointAttributeFactory extends AttributeFactory {
|
||||
private final AttributeFactory delegate;
|
||||
|
||||
GeoPointAttributeFactory(AttributeFactory delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeImpl createAttributeInstance(Class<? extends Attribute> attClass) {
|
||||
if (CharTermAttribute.class.isAssignableFrom(attClass)) {
|
||||
throw new IllegalArgumentException("GeoPointTokenStream does not support CharTermAttribute.");
|
||||
}
|
||||
return delegate.createAttributeInstance(attClass);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class GeoPointTermAttributeImpl extends AttributeImpl implements GeoPointTermAttribute,TermToBytesRefAttribute {
|
||||
private long value = 0L;
|
||||
private int shift = 0;
|
||||
private BytesRefBuilder bytes = new BytesRefBuilder();
|
||||
|
||||
public GeoPointTermAttributeImpl() {
|
||||
this.shift = MAX_SHIFT-PRECISION_STEP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BytesRef getBytesRef() {
|
||||
geoCodedToPrefixCoded(value, shift, bytes);
|
||||
return bytes.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(long value, int shift) {
|
||||
this.value = value;
|
||||
this.shift = shift;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getShift() { return shift; }
|
||||
|
||||
@Override
|
||||
public int incShift() {
|
||||
return (shift += PRECISION_STEP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
// this attribute has no contents to clear!
|
||||
// we keep it untouched as it's fully controlled by outer class.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reflectWith(AttributeReflector reflector) {
|
||||
reflector.reflect(TermToBytesRefAttribute.class, "bytes", getBytesRef());
|
||||
reflector.reflect(GeoPointTermAttribute.class, "shift", shift);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyTo(AttributeImpl target) {
|
||||
final GeoPointTermAttribute a = (GeoPointTermAttribute) target;
|
||||
a.init(value, shift);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeoPointTermAttributeImpl clone() {
|
||||
GeoPointTermAttributeImpl t = (GeoPointTermAttributeImpl)super.clone();
|
||||
// Do a deep clone
|
||||
t.bytes = new BytesRefBuilder();
|
||||
t.bytes.copyBytes(getBytesRef());
|
||||
return t;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(shift, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (getClass() != obj.getClass()) return false;
|
||||
GeoPointTermAttributeImpl other = (GeoPointTermAttributeImpl) obj;
|
||||
if (shift != other.shift) return false;
|
||||
if (value != other.value) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/** override toString because it can throw cryptic "illegal shift value": */
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + "(precisionStep=" + PRECISION_STEP + " shift=" + geoPointTermAtt.getShift() + ")";
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Geospatial Field Implementations for Core Lucene
|
||||
*/
|
||||
package org.apache.lucene.spatial.geopoint.document;
|
|
@ -1,171 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.lucene.spatial.geopoint.search;
|
||||
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.search.BooleanClause;
|
||||
import org.apache.lucene.search.BooleanQuery;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.apache.lucene.geo.Rectangle;
|
||||
import org.apache.lucene.geo.GeoUtils;
|
||||
|
||||
/** Implements a simple point distance query on a GeoPoint field. This is based on
|
||||
* {@link GeoPointInBBoxQuery} and is implemented using a two phase approach. First,
|
||||
* like {@code GeoPointInBBoxQueryImpl} candidate terms are queried using the numeric ranges based on
|
||||
* the morton codes of the min and max lat/lon pairs that intersect the boundary of the point-radius
|
||||
* circle. Terms
|
||||
* passing this initial filter are then passed to a secondary {@code postFilter} method that verifies whether the
|
||||
* decoded lat/lon point fall within the specified query distance (see {@link org.apache.lucene.util.SloppyMath#haversinMeters(double, double, double, double)}.
|
||||
* Distance comparisons are subject to the accuracy of the haversine formula
|
||||
* (from R.W. Sinnott, "Virtues of the Haversine", Sky and Telescope, vol. 68, no. 2, 1984, p. 159)
|
||||
*
|
||||
* <p>Note: This query currently uses haversine which is a sloppy distance calculation (see above reference). For large
|
||||
* queries one can expect upwards of 400m error. Vincenty shrinks this to ~40m error but pays a penalty for computing
|
||||
* using the spheroid
|
||||
*
|
||||
* @lucene.experimental */
|
||||
public class GeoPointDistanceQuery extends GeoPointInBBoxQuery {
|
||||
/** latitude value (in degrees) for query location */
|
||||
protected final double centerLat;
|
||||
/** longitude value (in degrees) for query location */
|
||||
protected final double centerLon;
|
||||
/** distance (in meters) from lat, lon center location */
|
||||
protected final double radiusMeters;
|
||||
/** partial haversin computation */
|
||||
protected final double sortKey;
|
||||
|
||||
// we must check these before passing to superclass or circleToBBox, or users can get a strange exception!
|
||||
private static double checkRadius(double radiusMeters) {
|
||||
if (Double.isFinite(radiusMeters) == false || radiusMeters < 0) {
|
||||
throw new IllegalArgumentException("invalid radiusMeters " + radiusMeters);
|
||||
}
|
||||
return radiusMeters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a Query for all {@link org.apache.lucene.spatial.geopoint.document.GeoPointField} types within a
|
||||
* distance (in meters) from a given point
|
||||
**/
|
||||
public GeoPointDistanceQuery(final String field, final double centerLat, final double centerLon, final double radiusMeters) {
|
||||
this(field, Rectangle.fromPointDistance(centerLat, centerLon, checkRadius(radiusMeters)), centerLat, centerLon, radiusMeters);
|
||||
}
|
||||
|
||||
private GeoPointDistanceQuery(final String field, final Rectangle bbox,
|
||||
final double centerLat, final double centerLon, final double radiusMeters) {
|
||||
super(field, bbox.minLat, bbox.maxLat, bbox.minLon, bbox.maxLon);
|
||||
|
||||
this.centerLat = centerLat;
|
||||
this.centerLon = centerLon;
|
||||
this.radiusMeters = radiusMeters;
|
||||
this.sortKey = GeoUtils.distanceQuerySortKey(radiusMeters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query rewrite(IndexReader reader) {
|
||||
// query crosses dateline; split into left and right queries
|
||||
if (maxLon < minLon) {
|
||||
BooleanQuery.Builder bqb = new BooleanQuery.Builder();
|
||||
|
||||
// unwrap the longitude iff outside the specified min/max lon range
|
||||
double unwrappedLon = centerLon;
|
||||
if (unwrappedLon > maxLon) {
|
||||
// unwrap left
|
||||
unwrappedLon += -360.0D;
|
||||
}
|
||||
GeoPointDistanceQueryImpl left = new GeoPointDistanceQueryImpl(field, this, unwrappedLon,
|
||||
new Rectangle(minLat, maxLat, GeoUtils.MIN_LON_INCL, maxLon));
|
||||
bqb.add(new BooleanClause(left, BooleanClause.Occur.SHOULD));
|
||||
|
||||
if (unwrappedLon < maxLon) {
|
||||
// unwrap right
|
||||
unwrappedLon += 360.0D;
|
||||
}
|
||||
GeoPointDistanceQueryImpl right = new GeoPointDistanceQueryImpl(field, this, unwrappedLon,
|
||||
new Rectangle(minLat, maxLat, minLon, GeoUtils.MAX_LON_INCL));
|
||||
bqb.add(new BooleanClause(right, BooleanClause.Occur.SHOULD));
|
||||
|
||||
return bqb.build();
|
||||
}
|
||||
return new GeoPointDistanceQueryImpl(field, this, centerLon,
|
||||
new Rectangle(this.minLat, this.maxLat, this.minLon, this.maxLon));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof GeoPointDistanceQuery)) return false;
|
||||
if (!super.equals(o)) return false;
|
||||
|
||||
GeoPointDistanceQuery that = (GeoPointDistanceQuery) o;
|
||||
|
||||
if (Double.compare(that.centerLat, centerLat) != 0) return false;
|
||||
if (Double.compare(that.centerLon, centerLon) != 0) return false;
|
||||
if (Double.compare(that.radiusMeters, radiusMeters) != 0) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = super.hashCode();
|
||||
long temp;
|
||||
temp = Double.doubleToLongBits(centerLon);
|
||||
result = 31 * result + (int) (temp ^ (temp >>> 32));
|
||||
temp = Double.doubleToLongBits(centerLat);
|
||||
result = 31 * result + (int) (temp ^ (temp >>> 32));
|
||||
temp = Double.doubleToLongBits(radiusMeters);
|
||||
result = 31 * result + (int) (temp ^ (temp >>> 32));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(String field) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(getClass().getSimpleName());
|
||||
sb.append(':');
|
||||
if (!this.field.equals(field)) {
|
||||
sb.append(" field=");
|
||||
sb.append(this.field);
|
||||
sb.append(':');
|
||||
}
|
||||
return sb.append( " Center: [")
|
||||
.append(centerLat)
|
||||
.append(',')
|
||||
.append(centerLon)
|
||||
.append(']')
|
||||
.append(" Distance: ")
|
||||
.append(radiusMeters)
|
||||
.append(" meters")
|
||||
.append("]")
|
||||
.toString();
|
||||
}
|
||||
|
||||
/** getter method for center longitude value */
|
||||
public double getCenterLon() {
|
||||
return this.centerLon;
|
||||
}
|
||||
|
||||
/** getter method for center latitude value */
|
||||
public double getCenterLat() {
|
||||
return this.centerLat;
|
||||
}
|
||||
|
||||
/** getter method for distance value (in meters) */
|
||||
public double getRadiusMeters() {
|
||||
return this.radiusMeters;
|
||||
}
|
||||
}
|
|
@ -1,132 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.lucene.spatial.geopoint.search;
|
||||
|
||||
import org.apache.lucene.geo.Rectangle;
|
||||
import org.apache.lucene.index.PointValues;
|
||||
import org.apache.lucene.search.MultiTermQuery;
|
||||
import org.apache.lucene.util.SloppyMath;
|
||||
|
||||
/** Package private implementation for the public facing GeoPointDistanceQuery delegate class.
|
||||
*
|
||||
* @lucene.experimental
|
||||
*/
|
||||
final class GeoPointDistanceQueryImpl extends GeoPointInBBoxQueryImpl {
|
||||
private final GeoPointDistanceQuery distanceQuery;
|
||||
private final double centerLon;
|
||||
|
||||
// optimization, used for detecting axis cross
|
||||
final double axisLat;
|
||||
|
||||
GeoPointDistanceQueryImpl(final String field, final GeoPointDistanceQuery q,
|
||||
final double centerLonUnwrapped, final Rectangle bbox) {
|
||||
super(field, bbox.minLat, bbox.maxLat, bbox.minLon, bbox.maxLon);
|
||||
distanceQuery = q;
|
||||
centerLon = centerLonUnwrapped;
|
||||
axisLat = Rectangle.axisLat(distanceQuery.centerLat, distanceQuery.radiusMeters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRewriteMethod(MultiTermQuery.RewriteMethod method) {
|
||||
throw new UnsupportedOperationException("cannot change rewrite method");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CellComparator newCellComparator() {
|
||||
return new GeoPointRadiusCellComparator(this);
|
||||
}
|
||||
|
||||
private final class GeoPointRadiusCellComparator extends CellComparator {
|
||||
GeoPointRadiusCellComparator(GeoPointDistanceQueryImpl query) {
|
||||
super(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PointValues.Relation relate(final double minLat, final double maxLat, final double minLon, final double maxLon) {
|
||||
// bounding box check
|
||||
if (cellIntersectsMBR(minLat, maxLat, minLon, maxLon) == false) {
|
||||
return PointValues.Relation.CELL_OUTSIDE_QUERY;
|
||||
}
|
||||
if ((centerLon < minLon || centerLon > maxLon) && (axisLat + Rectangle.AXISLAT_ERROR < minLat
|
||||
|| axisLat- Rectangle.AXISLAT_ERROR > maxLat)) {
|
||||
if (SloppyMath.haversinSortKey(distanceQuery.centerLat, centerLon, minLat, minLon) > distanceQuery.sortKey &&
|
||||
SloppyMath.haversinSortKey(distanceQuery.centerLat, centerLon, minLat, maxLon) > distanceQuery.sortKey &&
|
||||
SloppyMath.haversinSortKey(distanceQuery.centerLat, centerLon, maxLat, minLon) > distanceQuery.sortKey &&
|
||||
SloppyMath.haversinSortKey(distanceQuery.centerLat, centerLon, maxLat, maxLon) > distanceQuery.sortKey) {
|
||||
return PointValues.Relation.CELL_OUTSIDE_QUERY;
|
||||
}
|
||||
}
|
||||
|
||||
if (maxLon - centerLon < 90 && centerLon - minLon < 90 &&
|
||||
SloppyMath.haversinSortKey(distanceQuery.centerLat, centerLon, minLat, minLon) <= distanceQuery.sortKey &&
|
||||
SloppyMath.haversinSortKey(distanceQuery.centerLat, centerLon, minLat, maxLon) <= distanceQuery.sortKey &&
|
||||
SloppyMath.haversinSortKey(distanceQuery.centerLat, centerLon, maxLat, minLon) <= distanceQuery.sortKey &&
|
||||
SloppyMath.haversinSortKey(distanceQuery.centerLat, centerLon, maxLat, maxLon) <= distanceQuery.sortKey) {
|
||||
// we are fully enclosed, collect everything within this subtree
|
||||
return PointValues.Relation.CELL_INSIDE_QUERY;
|
||||
}
|
||||
|
||||
return PointValues.Relation.CELL_CROSSES_QUERY;
|
||||
}
|
||||
|
||||
/**
|
||||
* The two-phase query approach. The parent {@link GeoPointTermsEnum} class matches
|
||||
* encoded terms that fall within the minimum bounding box of the point-radius circle. Those documents that pass
|
||||
* the initial bounding box filter are then post filter compared to the provided distance using the
|
||||
* {@link org.apache.lucene.util.SloppyMath#haversinMeters(double, double, double, double)} method.
|
||||
*/
|
||||
@Override
|
||||
protected boolean postFilter(final double lat, final double lon) {
|
||||
// check bbox
|
||||
if (lat < minLat || lat > maxLat || lon < minLon || lon > maxLon) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// first check the partial distance, if its more than that, it can't be <= radiusMeters
|
||||
double h1 = SloppyMath.haversinSortKey(distanceQuery.centerLat, centerLon, lat, lon);
|
||||
if (h1 <= distanceQuery.sortKey) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof GeoPointDistanceQueryImpl)) return false;
|
||||
if (!super.equals(o)) return false;
|
||||
|
||||
GeoPointDistanceQueryImpl that = (GeoPointDistanceQueryImpl) o;
|
||||
|
||||
if (!distanceQuery.equals(that.distanceQuery)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = super.hashCode();
|
||||
result = 31 * result + distanceQuery.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
public double getRadiusMeters() {
|
||||
return distanceQuery.getRadiusMeters();
|
||||
}
|
||||
}
|
|
@ -1,171 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.lucene.spatial.geopoint.search;
|
||||
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.search.BooleanClause;
|
||||
import org.apache.lucene.search.BooleanQuery;
|
||||
import org.apache.lucene.search.FieldValueQuery;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.apache.lucene.geo.GeoUtils;
|
||||
|
||||
/** Implements a simple bounding box query on a GeoPoint field. This is implemented using a
|
||||
* two phase approach. First, candidate terms are queried using a numeric
|
||||
* range based on the morton codes of the min and max lat/lon pairs. Terms
|
||||
* passing this initial filter are passed to a final check that verifies whether
|
||||
* the decoded lat/lon falls within (or on the boundary) of the query bounding box.
|
||||
*
|
||||
* NOTES:
|
||||
* 1. All latitude/longitude values must be in decimal degrees.
|
||||
* 2. Complex computational geometry (e.g., dateline wrapping) is not supported
|
||||
* 3. For more advanced GeoSpatial indexing and query operations see spatial module
|
||||
* 4. This is well suited for small rectangles, large bounding boxes may result
|
||||
* in many terms, depending whether the bounding box falls on the boundary of
|
||||
* many cells (degenerate case)
|
||||
*
|
||||
* @lucene.experimental
|
||||
*/
|
||||
public class GeoPointInBBoxQuery extends Query {
|
||||
/** field name */
|
||||
protected final String field;
|
||||
/** minimum latitude value (in degrees) */
|
||||
protected final double minLat;
|
||||
/** minimum longitude value (in degrees) */
|
||||
protected final double minLon;
|
||||
/** maximum latitude value (in degrees) */
|
||||
protected final double maxLat;
|
||||
/** maximum longitude value (in degrees) */
|
||||
protected final double maxLon;
|
||||
|
||||
/**
|
||||
* Constructs a query for all {@link org.apache.lucene.spatial.geopoint.document.GeoPointField} types that fall within a
|
||||
* defined bounding box.
|
||||
*/
|
||||
public GeoPointInBBoxQuery(final String field, final double minLat, final double maxLat, final double minLon, final double maxLon) {
|
||||
if (field == null) {
|
||||
throw new IllegalArgumentException("field must not be null");
|
||||
}
|
||||
GeoUtils.checkLatitude(minLat);
|
||||
GeoUtils.checkLatitude(maxLat);
|
||||
GeoUtils.checkLongitude(minLon);
|
||||
GeoUtils.checkLongitude(maxLon);
|
||||
this.field = field;
|
||||
this.minLat = minLat;
|
||||
this.maxLat = maxLat;
|
||||
this.minLon = minLon;
|
||||
this.maxLon = maxLon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query rewrite(IndexReader reader) {
|
||||
// short-circuit to match all if specifying the whole map
|
||||
if (minLat == GeoUtils.MIN_LAT_INCL && maxLat == GeoUtils.MAX_LAT_INCL &&
|
||||
minLon == GeoUtils.MIN_LON_INCL && maxLon == GeoUtils.MAX_LON_INCL) {
|
||||
// FieldValueQuery is valid since DocValues are *required* for GeoPointField
|
||||
return new FieldValueQuery(field);
|
||||
}
|
||||
|
||||
if (maxLon < minLon) {
|
||||
BooleanQuery.Builder bqb = new BooleanQuery.Builder();
|
||||
|
||||
GeoPointInBBoxQueryImpl left = new GeoPointInBBoxQueryImpl(field, minLat, maxLat, -180.0D, maxLon);
|
||||
bqb.add(new BooleanClause(left, BooleanClause.Occur.SHOULD));
|
||||
GeoPointInBBoxQueryImpl right = new GeoPointInBBoxQueryImpl(field, minLat, maxLat, minLon, 180.0D);
|
||||
bqb.add(new BooleanClause(right, BooleanClause.Occur.SHOULD));
|
||||
return bqb.build();
|
||||
}
|
||||
return new GeoPointInBBoxQueryImpl(field, minLat, maxLat, minLon, maxLon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(String field) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(getClass().getSimpleName());
|
||||
sb.append(':');
|
||||
if (!this.field.equals(field)) {
|
||||
sb.append(" field=");
|
||||
sb.append(this.field);
|
||||
sb.append(':');
|
||||
}
|
||||
return sb.append(" Lower Left: [")
|
||||
.append(minLat)
|
||||
.append(',')
|
||||
.append(minLon)
|
||||
.append(']')
|
||||
.append(" Upper Right: [")
|
||||
.append(maxLat)
|
||||
.append(',')
|
||||
.append(maxLon)
|
||||
.append("]")
|
||||
.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return sameClassAs(other) &&
|
||||
equalsTo(getClass().cast(other));
|
||||
}
|
||||
|
||||
private boolean equalsTo(GeoPointInBBoxQuery other) {
|
||||
return Double.compare(other.minLat, minLat) == 0 &&
|
||||
Double.compare(other.maxLat, maxLat) == 0 &&
|
||||
Double.compare(other.minLon, minLon) == 0 &&
|
||||
Double.compare(other.maxLon, maxLon) == 0 &&
|
||||
field.equals(other.field);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = classHash();
|
||||
long temp;
|
||||
result = 31 * result + field.hashCode();
|
||||
temp = Double.doubleToLongBits(minLat);
|
||||
result = 31 * result + (int) (temp ^ (temp >>> 32));
|
||||
temp = Double.doubleToLongBits(maxLat);
|
||||
result = 31 * result + (int) (temp ^ (temp >>> 32));
|
||||
temp = Double.doubleToLongBits(minLon);
|
||||
result = 31 * result + (int) (temp ^ (temp >>> 32));
|
||||
temp = Double.doubleToLongBits(maxLon);
|
||||
result = 31 * result + (int) (temp ^ (temp >>> 32));
|
||||
return result;
|
||||
}
|
||||
|
||||
/** getter method for retrieving the field name */
|
||||
public final String getField() {
|
||||
return this.field;
|
||||
}
|
||||
|
||||
/** getter method for retrieving the minimum latitude (in degrees) */
|
||||
public final double getMinLat() {
|
||||
return this.minLat;
|
||||
}
|
||||
|
||||
/** getter method for retrieving the maximum latitude (in degrees) */
|
||||
public final double getMaxLat() {
|
||||
return this.maxLat;
|
||||
}
|
||||
|
||||
/** getter method for retrieving the minimum longitude (in degrees) */
|
||||
public final double getMinLon() {
|
||||
return this.minLon;
|
||||
}
|
||||
|
||||
/** getter method for retrieving the maximum longitude (in degrees) */
|
||||
public final double getMaxLon() {
|
||||
return this.maxLon;
|
||||
}
|
||||
}
|
|
@ -1,148 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.lucene.spatial.geopoint.search;
|
||||
|
||||
import org.apache.lucene.index.PointValues.Relation;
|
||||
import org.apache.lucene.search.MultiTermQuery;
|
||||
import org.apache.lucene.util.SloppyMath;
|
||||
import org.apache.lucene.spatial.geopoint.document.GeoPointField;
|
||||
import org.apache.lucene.spatial.util.GeoRelationUtils;
|
||||
|
||||
/** Package private implementation for the public facing GeoPointInBBoxQuery delegate class.
|
||||
*
|
||||
* @lucene.experimental
|
||||
*/
|
||||
class GeoPointInBBoxQueryImpl extends GeoPointMultiTermQuery {
|
||||
/**
|
||||
* Constructs a new GeoBBoxQuery that will match encoded GeoPoint terms that fall within or on the boundary
|
||||
* of the bounding box defined by the input parameters
|
||||
* @param field the field name
|
||||
* @param minLon lower longitude (x) value of the bounding box
|
||||
* @param minLat lower latitude (y) value of the bounding box
|
||||
* @param maxLon upper longitude (x) value of the bounding box
|
||||
* @param maxLat upper latitude (y) value of the bounding box
|
||||
*/
|
||||
GeoPointInBBoxQueryImpl(final String field, final double minLat, final double maxLat, final double minLon, final double maxLon) {
|
||||
super(field, minLat, maxLat, minLon, maxLon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRewriteMethod(MultiTermQuery.RewriteMethod method) {
|
||||
throw new UnsupportedOperationException("cannot change rewrite method");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected short computeMaxShift() {
|
||||
final short shiftFactor;
|
||||
|
||||
// compute diagonal radius
|
||||
double midLat = (minLat + maxLat) * 0.5;
|
||||
double midLon = (minLon + maxLon) * 0.5;
|
||||
|
||||
if (SloppyMath.haversinMeters(minLat, minLon, midLat, midLon) > 1000000) {
|
||||
shiftFactor = 5;
|
||||
} else {
|
||||
shiftFactor = 4;
|
||||
}
|
||||
|
||||
return (short)(GeoPointField.PRECISION_STEP * shiftFactor);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CellComparator newCellComparator() {
|
||||
return new GeoPointInBBoxCellComparator(this);
|
||||
}
|
||||
|
||||
private final class GeoPointInBBoxCellComparator extends CellComparator {
|
||||
GeoPointInBBoxCellComparator(GeoPointMultiTermQuery query) {
|
||||
super(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Relation relate(final double minLat, final double maxLat, final double minLon, final double maxLon) {
|
||||
if (GeoRelationUtils.rectCrosses(minLat, maxLat, minLon, maxLon, GeoPointInBBoxQueryImpl.this.minLat,
|
||||
GeoPointInBBoxQueryImpl.this.maxLat, GeoPointInBBoxQueryImpl.this.minLon, GeoPointInBBoxQueryImpl.this.maxLon)) {
|
||||
return Relation.CELL_CROSSES_QUERY;
|
||||
} else if (GeoRelationUtils.rectWithin(minLat, maxLat, minLon, maxLon, GeoPointInBBoxQueryImpl.this.minLat,
|
||||
GeoPointInBBoxQueryImpl.this.maxLat,
|
||||
GeoPointInBBoxQueryImpl.this.minLon, GeoPointInBBoxQueryImpl.this.maxLon)) {
|
||||
return Relation.CELL_INSIDE_QUERY;
|
||||
}
|
||||
return Relation.CELL_OUTSIDE_QUERY;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean postFilter(final double lat, final double lon) {
|
||||
return GeoRelationUtils.pointInRectPrecise(lat, lon, minLat, maxLat, minLon, maxLon);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"unchecked","rawtypes"})
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
if (!super.equals(o)) return false;
|
||||
|
||||
GeoPointInBBoxQueryImpl that = (GeoPointInBBoxQueryImpl) o;
|
||||
|
||||
if (Double.compare(that.minLat, minLat) != 0) return false;
|
||||
if (Double.compare(that.maxLat, maxLat) != 0) return false;
|
||||
if (Double.compare(that.minLon, minLon) != 0) return false;
|
||||
if (Double.compare(that.maxLon, maxLon) != 0) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = super.hashCode();
|
||||
long temp;
|
||||
temp = Double.doubleToLongBits(minLat);
|
||||
result = 31 * result + (int) (temp ^ (temp >>> 32));
|
||||
temp = Double.doubleToLongBits(maxLat);
|
||||
result = 31 * result + (int) (temp ^ (temp >>> 32));
|
||||
temp = Double.doubleToLongBits(minLon);
|
||||
result = 31 * result + (int) (temp ^ (temp >>> 32));
|
||||
temp = Double.doubleToLongBits(maxLon);
|
||||
result = 31 * result + (int) (temp ^ (temp >>> 32));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(String field) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(getClass().getSimpleName());
|
||||
sb.append(':');
|
||||
if (!getField().equals(field)) {
|
||||
sb.append(" field=");
|
||||
sb.append(getField());
|
||||
sb.append(':');
|
||||
}
|
||||
return sb.append(" Lower Left: [")
|
||||
.append(minLat)
|
||||
.append(',')
|
||||
.append(minLon)
|
||||
.append(']')
|
||||
.append(" Upper Right: [")
|
||||
.append(maxLat)
|
||||
.append(',')
|
||||
.append(maxLon)
|
||||
.append("]")
|
||||
.toString();
|
||||
}
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.lucene.spatial.geopoint.search;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.apache.lucene.geo.Rectangle;
|
||||
import org.apache.lucene.geo.Polygon;
|
||||
|
||||
/** Implements a simple point in polygon query on a GeoPoint field. This is based on
|
||||
* {@code GeoPointInBBoxQueryImpl} and is implemented using a
|
||||
* three phase approach. First, like {@code GeoPointInBBoxQueryImpl}
|
||||
* candidate terms are queried using a numeric range based on the morton codes
|
||||
* of the min and max lat/lon pairs. Terms passing this initial filter are passed
|
||||
* to a secondary filter that verifies whether the decoded lat/lon point falls within
|
||||
* (or on the boundary) of the bounding box query. Finally, the remaining candidate
|
||||
* term is passed to the final point in polygon check.
|
||||
*
|
||||
* @see Polygon
|
||||
* @lucene.experimental
|
||||
*/
|
||||
public final class GeoPointInPolygonQuery extends GeoPointInBBoxQuery {
|
||||
/** array of polygons being queried */
|
||||
final Polygon[] polygons;
|
||||
|
||||
/**
|
||||
* Constructs a new GeoPolygonQuery that will match encoded {@link org.apache.lucene.spatial.geopoint.document.GeoPointField} terms
|
||||
* that fall within or on the boundary of the polygons defined by the input parameters.
|
||||
*/
|
||||
public GeoPointInPolygonQuery(String field, Polygon... polygons) {
|
||||
this(field, Rectangle.fromPolygon(polygons), polygons);
|
||||
}
|
||||
|
||||
// internal constructor
|
||||
private GeoPointInPolygonQuery(String field, Rectangle boundingBox, Polygon... polygons) {
|
||||
super(field, boundingBox.minLat, boundingBox.maxLat, boundingBox.minLon, boundingBox.maxLon);
|
||||
this.polygons = polygons.clone();
|
||||
}
|
||||
|
||||
/** throw exception if trying to change rewrite method */
|
||||
@Override
|
||||
public Query rewrite(IndexReader reader) {
|
||||
return new GeoPointInPolygonQueryImpl(field, this, this.minLat, this.maxLat, this.minLon, this.maxLon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = super.hashCode();
|
||||
result = prime * result + Arrays.hashCode(polygons);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!super.equals(obj)) return false;
|
||||
if (getClass() != obj.getClass()) return false;
|
||||
GeoPointInPolygonQuery other = (GeoPointInPolygonQuery) obj;
|
||||
if (!Arrays.equals(polygons, other.polygons)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/** print out this polygon query */
|
||||
@Override
|
||||
public String toString(String field) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(getClass().getSimpleName());
|
||||
sb.append(':');
|
||||
if (!getField().equals(field)) {
|
||||
sb.append(" field=");
|
||||
sb.append(getField());
|
||||
sb.append(':');
|
||||
}
|
||||
sb.append(" Polygon: ");
|
||||
sb.append(Arrays.toString(polygons));
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* API utility method for returning copy of the polygon array
|
||||
*/
|
||||
public Polygon[] getPolygons() {
|
||||
return polygons.clone();
|
||||
}
|
||||
}
|
|
@ -1,94 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.lucene.spatial.geopoint.search;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.lucene.search.MultiTermQuery;
|
||||
import org.apache.lucene.geo.Polygon2D;
|
||||
import org.apache.lucene.index.PointValues.Relation;
|
||||
|
||||
/** Package private implementation for the public facing GeoPointInPolygonQuery delegate class.
|
||||
*
|
||||
* @lucene.experimental
|
||||
*/
|
||||
final class GeoPointInPolygonQueryImpl extends GeoPointInBBoxQueryImpl {
|
||||
private final GeoPointInPolygonQuery polygonQuery;
|
||||
private final Polygon2D polygons;
|
||||
|
||||
GeoPointInPolygonQueryImpl(final String field, final GeoPointInPolygonQuery q,
|
||||
final double minLat, final double maxLat, final double minLon, final double maxLon) {
|
||||
super(field, minLat, maxLat, minLon, maxLon);
|
||||
this.polygonQuery = Objects.requireNonNull(q);
|
||||
this.polygons = Polygon2D.create(q.polygons);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRewriteMethod(MultiTermQuery.RewriteMethod method) {
|
||||
throw new UnsupportedOperationException("cannot change rewrite method");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CellComparator newCellComparator() {
|
||||
return new GeoPolygonCellComparator(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom {@code org.apache.lucene.spatial.geopoint.search.GeoPointMultiTermQuery.CellComparator} that computes morton hash
|
||||
* ranges based on the defined edges of the provided polygon.
|
||||
*/
|
||||
private final class GeoPolygonCellComparator extends CellComparator {
|
||||
GeoPolygonCellComparator(GeoPointMultiTermQuery query) {
|
||||
super(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Relation relate(final double minLat, final double maxLat, final double minLon, final double maxLon) {
|
||||
return polygons.relate(minLat, maxLat, minLon, maxLon);
|
||||
}
|
||||
|
||||
/**
|
||||
* The two-phase query approach. The parent
|
||||
* {@link org.apache.lucene.spatial.geopoint.search.GeoPointTermsEnum#accept} method is called to match
|
||||
* encoded terms that fall within the bounding box of the polygon. Those documents that pass the initial
|
||||
* bounding box filter are then compared to the provided polygon using the
|
||||
* {@link Polygon2D#contains(double, double)} method.
|
||||
*/
|
||||
@Override
|
||||
protected boolean postFilter(final double lat, final double lon) {
|
||||
return polygons.contains(lat, lon);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = super.hashCode();
|
||||
result = prime * result + polygonQuery.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!super.equals(obj)) return false;
|
||||
if (getClass() != obj.getClass()) return false;
|
||||
GeoPointInPolygonQueryImpl other = (GeoPointInPolygonQueryImpl) obj;
|
||||
if (!polygonQuery.equals(other.polygonQuery)) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,148 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.lucene.spatial.geopoint.search;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.index.PointValues.Relation;
|
||||
import org.apache.lucene.index.Terms;
|
||||
import org.apache.lucene.index.TermsEnum;
|
||||
import org.apache.lucene.search.MultiTermQuery;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.apache.lucene.util.AttributeSource;
|
||||
import org.apache.lucene.spatial.geopoint.document.GeoPointField;
|
||||
import org.apache.lucene.spatial.util.GeoRelationUtils;
|
||||
import org.apache.lucene.util.BitUtil;
|
||||
import org.apache.lucene.util.SloppyMath;
|
||||
|
||||
/**
|
||||
* TermQuery for GeoPointField for overriding {@link org.apache.lucene.search.MultiTermQuery} methods specific to
|
||||
* Geospatial operations
|
||||
*
|
||||
* @lucene.experimental
|
||||
*/
|
||||
abstract class GeoPointMultiTermQuery extends MultiTermQuery {
|
||||
// simple bounding box optimization - no objects used to avoid dependencies
|
||||
protected final double minLon;
|
||||
protected final long minEncoded;
|
||||
protected final int minX;
|
||||
protected final double minLat;
|
||||
protected final int minY;
|
||||
protected final double maxLon;
|
||||
protected final int maxX;
|
||||
protected final double maxLat;
|
||||
protected final int maxY;
|
||||
|
||||
protected final short maxShift;
|
||||
protected final CellComparator cellComparator;
|
||||
|
||||
/**
|
||||
* Constructs a query matching terms that cannot be represented with a single
|
||||
* Term.
|
||||
*/
|
||||
public GeoPointMultiTermQuery(String field, final double minLat, final double maxLat, final double minLon, final double maxLon) {
|
||||
super(field);
|
||||
|
||||
this.minEncoded = GeoPointField.encodeLatLon(minLat, minLon);
|
||||
final long maxEncoded = GeoPointField.encodeLatLon(maxLat, maxLon);
|
||||
|
||||
this.minX = (int)BitUtil.deinterleave(minEncoded);
|
||||
this.maxX = (int)BitUtil.deinterleave(maxEncoded);
|
||||
this.minY = (int)BitUtil.deinterleave(minEncoded >>> 1);
|
||||
this.maxY = (int)BitUtil.deinterleave(maxEncoded >>> 1);
|
||||
|
||||
this.minLat = minLat;
|
||||
this.maxLat = maxLat;
|
||||
this.minLon = minLon;
|
||||
this.maxLon = maxLon;
|
||||
|
||||
this.maxShift = computeMaxShift();
|
||||
this.cellComparator = newCellComparator();
|
||||
|
||||
this.rewriteMethod = GEO_CONSTANT_SCORE_REWRITE;
|
||||
}
|
||||
|
||||
public static final RewriteMethod GEO_CONSTANT_SCORE_REWRITE = new RewriteMethod() {
|
||||
@Override
|
||||
public Query rewrite(IndexReader reader, MultiTermQuery query) {
|
||||
return new GeoPointTermQueryConstantScoreWrapper<>((GeoPointMultiTermQuery)query);
|
||||
}
|
||||
};
|
||||
|
||||
@Override @SuppressWarnings("unchecked")
|
||||
protected TermsEnum getTermsEnum(final Terms terms, AttributeSource atts) throws IOException {
|
||||
return new GeoPointTermsEnum(terms.iterator(), this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the maximum shift based on the diagonal distance of the bounding box
|
||||
*/
|
||||
protected short computeMaxShift() {
|
||||
// in this case a factor of 4 brings the detail level to ~0.001/0.002 degrees lat/lon respectively (or ~111m/222m)
|
||||
final short shiftFactor;
|
||||
|
||||
// compute diagonal distance
|
||||
double midLon = (minLon + maxLon) * 0.5;
|
||||
double midLat = (minLat + maxLat) * 0.5;
|
||||
|
||||
if (SloppyMath.haversinMeters(minLat, minLon, midLat, midLon) > 1000000) {
|
||||
shiftFactor = 5;
|
||||
} else {
|
||||
shiftFactor = 4;
|
||||
}
|
||||
|
||||
return (short)(GeoPointField.PRECISION_STEP * shiftFactor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract method to construct the class that handles all geo point relations
|
||||
* (e.g., GeoPointInPolygon)
|
||||
*/
|
||||
abstract protected CellComparator newCellComparator();
|
||||
|
||||
/**
|
||||
* Base class for all geo point relation comparators
|
||||
*/
|
||||
static abstract class CellComparator {
|
||||
protected final GeoPointMultiTermQuery geoPointQuery;
|
||||
|
||||
CellComparator(GeoPointMultiTermQuery query) {
|
||||
this.geoPointQuery = query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary driver for cells intersecting shape boundaries
|
||||
*/
|
||||
protected boolean cellIntersectsMBR(final double minLat, final double maxLat, final double minLon, final double maxLon) {
|
||||
return GeoRelationUtils.rectIntersects(minLat, maxLat, minLon, maxLon, geoPointQuery.minLat, geoPointQuery.maxLat,
|
||||
geoPointQuery.minLon, geoPointQuery.maxLon);
|
||||
}
|
||||
|
||||
/** uses encoded values to check whether quad cell intersects the shape bounding box */
|
||||
protected boolean cellIntersectsMBR(final long min, final long max) {
|
||||
return !(Integer.compareUnsigned((int)BitUtil.deinterleave(max), geoPointQuery.minX) < 0
|
||||
|| Integer.compareUnsigned((int)BitUtil.deinterleave(min), geoPointQuery.maxX) > 0
|
||||
|| Integer.compareUnsigned((int)BitUtil.deinterleave(max >>> 1), geoPointQuery.minY) < 0
|
||||
|| Integer.compareUnsigned((int)BitUtil.deinterleave(min >>> 1), geoPointQuery.maxY) > 0);
|
||||
}
|
||||
|
||||
abstract protected Relation relate(final double minLat, final double maxLat, final double minLon, final double maxLon);
|
||||
|
||||
abstract protected boolean postFilter(final double lat, final double lon);
|
||||
}
|
||||
}
|
|
@ -1,160 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.lucene.spatial.geopoint.search;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.lucene.index.LeafReader;
|
||||
import org.apache.lucene.index.LeafReaderContext;
|
||||
import org.apache.lucene.index.PostingsEnum;
|
||||
import org.apache.lucene.index.SortedNumericDocValues;
|
||||
import org.apache.lucene.index.Terms;
|
||||
import org.apache.lucene.search.ConstantScoreScorer;
|
||||
import org.apache.lucene.search.ConstantScoreWeight;
|
||||
import org.apache.lucene.search.DocIdSet;
|
||||
import org.apache.lucene.search.DocIdSetIterator;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.apache.lucene.search.Scorer;
|
||||
import org.apache.lucene.search.TwoPhaseIterator;
|
||||
import org.apache.lucene.search.Weight;
|
||||
import org.apache.lucene.spatial.geopoint.document.GeoPointField;
|
||||
import org.apache.lucene.util.BitSet;
|
||||
import org.apache.lucene.util.DocIdSetBuilder;
|
||||
import org.apache.lucene.util.FixedBitSet;
|
||||
import org.apache.lucene.util.SparseFixedBitSet;
|
||||
|
||||
/**
|
||||
* Custom ConstantScoreWrapper for {@code GeoPointMultiTermQuery} that cuts over to DocValues
|
||||
* for post filtering boundary ranges. Multi-valued GeoPoint documents are supported.
|
||||
*
|
||||
* @lucene.experimental
|
||||
*/
|
||||
final class GeoPointTermQueryConstantScoreWrapper <Q extends GeoPointMultiTermQuery> extends Query {
|
||||
protected final Q query;
|
||||
|
||||
protected GeoPointTermQueryConstantScoreWrapper(Q query) {
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the encapsulated query.
|
||||
*/
|
||||
public Q getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(String field) {
|
||||
return query.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean equals(final Object other) {
|
||||
return sameClassAs(other) &&
|
||||
query.equals(((GeoPointTermQueryConstantScoreWrapper<?>) other).query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int hashCode() {
|
||||
return 31 * classHash() + query.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Weight createWeight(IndexSearcher searcher, boolean needsScores, float boost) throws IOException {
|
||||
return new ConstantScoreWeight(this, boost) {
|
||||
|
||||
@Override
|
||||
public Scorer scorer(LeafReaderContext context) throws IOException {
|
||||
final Terms terms = context.reader().terms(query.getField());
|
||||
if (terms == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final GeoPointTermsEnum termsEnum = (GeoPointTermsEnum)(query.getTermsEnum(terms, null));
|
||||
assert termsEnum != null;
|
||||
|
||||
LeafReader reader = context.reader();
|
||||
// approximation (postfiltering has not yet been applied)
|
||||
DocIdSetBuilder builder = new DocIdSetBuilder(reader.maxDoc(), terms);
|
||||
// subset of documents that need no postfiltering, this is purely an optimization
|
||||
final BitSet preApproved;
|
||||
// dumb heuristic: if the field is really sparse, use a sparse impl
|
||||
if (terms.getDocCount() * 100L < reader.maxDoc()) {
|
||||
preApproved = new SparseFixedBitSet(reader.maxDoc());
|
||||
} else {
|
||||
preApproved = new FixedBitSet(reader.maxDoc());
|
||||
}
|
||||
PostingsEnum docs = null;
|
||||
|
||||
while (termsEnum.next() != null) {
|
||||
docs = termsEnum.postings(docs, PostingsEnum.NONE);
|
||||
// boundary terms need post filtering
|
||||
if (termsEnum.boundaryTerm()) {
|
||||
builder.add(docs);
|
||||
} else {
|
||||
int numDocs = termsEnum.docFreq();
|
||||
DocIdSetBuilder.BulkAdder adder = builder.grow(numDocs);
|
||||
for (int i = 0; i < numDocs; ++i) {
|
||||
int docId = docs.nextDoc();
|
||||
adder.add(docId);
|
||||
preApproved.set(docId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DocIdSet set = builder.build();
|
||||
final DocIdSetIterator disi = set.iterator();
|
||||
if (disi == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// return two-phase iterator using docvalues to postfilter candidates
|
||||
SortedNumericDocValues sdv = reader.getSortedNumericDocValues(query.getField());
|
||||
TwoPhaseIterator iterator = new TwoPhaseIterator(disi) {
|
||||
@Override
|
||||
public boolean matches() throws IOException {
|
||||
int docId = disi.docID();
|
||||
if (preApproved.get(docId)) {
|
||||
return true;
|
||||
} else {
|
||||
if (docId > sdv.docID()) {
|
||||
sdv.advance(docId);
|
||||
}
|
||||
if (docId == sdv.docID()) {
|
||||
int count = sdv.docValueCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
long hash = sdv.nextValue();
|
||||
if (termsEnum.postFilter(GeoPointField.decodeLatitude(hash), GeoPointField.decodeLongitude(hash))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float matchCost() {
|
||||
return 20; // TODO: make this fancier
|
||||
}
|
||||
};
|
||||
return new ConstantScoreScorer(this, score(), iterator);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,199 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.lucene.spatial.geopoint.search;
|
||||
|
||||
import org.apache.lucene.index.FilteredTermsEnum;
|
||||
import org.apache.lucene.index.PointValues.Relation;
|
||||
import org.apache.lucene.index.TermsEnum;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
import org.apache.lucene.util.BytesRefBuilder;
|
||||
import org.apache.lucene.spatial.geopoint.document.GeoPointField;
|
||||
|
||||
import static org.apache.lucene.spatial.geopoint.document.GeoPointField.decodeLatitude;
|
||||
import static org.apache.lucene.spatial.geopoint.document.GeoPointField.decodeLongitude;
|
||||
import static org.apache.lucene.spatial.geopoint.document.GeoPointField.geoCodedToPrefixCoded;
|
||||
import static org.apache.lucene.spatial.geopoint.document.GeoPointField.prefixCodedToGeoCoded;
|
||||
import static org.apache.lucene.spatial.geopoint.document.GeoPointField.getPrefixCodedShift;
|
||||
|
||||
/**
|
||||
* Decomposes a given {@link GeoPointMultiTermQuery} into a set of terms that represent the query criteria. The terms
|
||||
* are then enumerated by the {@link GeoPointTermQueryConstantScoreWrapper} and all docs whose GeoPoint fields match
|
||||
* the prefix terms or pass the {@link GeoPointMultiTermQuery.CellComparator#postFilter} criteria are returned in the
|
||||
* resulting DocIdSet.
|
||||
*
|
||||
* @lucene.experimental
|
||||
*/
|
||||
final class GeoPointTermsEnum extends FilteredTermsEnum {
|
||||
private final short maxShift;
|
||||
private final GeoPointMultiTermQuery.CellComparator relationImpl;
|
||||
private final BytesRefBuilder currentCellBRB;
|
||||
private final Range range;
|
||||
|
||||
private short shift; // shift mask
|
||||
private long start; // range start as encoded long
|
||||
private long end; // range end as encoded long
|
||||
private boolean hasNext = false;
|
||||
|
||||
public GeoPointTermsEnum(final TermsEnum tenum, final GeoPointMultiTermQuery query) {
|
||||
super(tenum);
|
||||
this.maxShift = query.maxShift;
|
||||
this.relationImpl = query.cellComparator;
|
||||
// start shift at maxShift value (from computeMaxShift)
|
||||
this.shift = maxShift;
|
||||
final long mask = (1L << shift) - 1;
|
||||
this.start = query.minEncoded & ~mask;
|
||||
this.end = start | mask;
|
||||
this.currentCellBRB = new BytesRefBuilder();
|
||||
this.range = new Range(-1, shift, true);
|
||||
}
|
||||
|
||||
private boolean nextRelation() {
|
||||
Relation relation;
|
||||
do {
|
||||
// within or a boundary
|
||||
if ((shift % GeoPointField.PRECISION_STEP) == 0 &&
|
||||
(relation = relationImpl.relate(decodeLatitude(start), decodeLatitude(end),
|
||||
decodeLongitude(start), decodeLongitude(end))) != Relation.CELL_OUTSIDE_QUERY) {
|
||||
// if at max depth or cell completely within
|
||||
if (shift == maxShift || relation == Relation.CELL_INSIDE_QUERY) {
|
||||
setRange(relation == Relation.CELL_CROSSES_QUERY);
|
||||
advanceVariables();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// within cell but not at a depth factor of PRECISION_STEP
|
||||
if (shift != maxShift && relationImpl.cellIntersectsMBR(start, end) == true) {
|
||||
// descend: start need not change since shift handles end of range
|
||||
end = start | (1L<<--shift) - 1;
|
||||
} else {
|
||||
advanceVariables();
|
||||
}
|
||||
} while(shift < 62);
|
||||
return false;
|
||||
}
|
||||
|
||||
private void setRange(final boolean boundary) {
|
||||
range.start = start;
|
||||
range.shift = shift;
|
||||
range.boundary = boundary;
|
||||
hasNext = true;
|
||||
}
|
||||
|
||||
private void advanceVariables() {
|
||||
/** set next variables */
|
||||
long shiftMask = 1L << shift;
|
||||
// pop-up if shift bit is set
|
||||
while ((start & shiftMask) != 0) {
|
||||
shiftMask = 1L << ++shift;
|
||||
}
|
||||
final long shiftMOne = shiftMask - 1;
|
||||
start = start & ~shiftMOne | shiftMask;
|
||||
end = start | shiftMOne;
|
||||
}
|
||||
|
||||
private void seek(long term, short res) {
|
||||
if (term < start && res < maxShift) {
|
||||
throw new IllegalArgumentException("trying to seek backwards");
|
||||
} else if (term == start && res == shift) {
|
||||
return;
|
||||
}
|
||||
shift = res;
|
||||
start = term;
|
||||
end = start | ((1L<<shift)-1);
|
||||
}
|
||||
|
||||
private final boolean hasNext() {
|
||||
if (hasNext == false) {
|
||||
return nextRelation();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final BytesRef nextSeekTerm(BytesRef term) {
|
||||
if (hasNext() == false) {
|
||||
return null;
|
||||
}
|
||||
geoCodedToPrefixCoded(range.start, range.shift, currentCellBRB);
|
||||
hasNext = false;
|
||||
return currentCellBRB.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* The two-phase query approach. {@link #nextSeekTerm} is called to obtain the next term that matches a numeric
|
||||
* range of the bounding box. Those terms that pass the initial range filter are then compared against the
|
||||
* decoded min/max latitude and longitude values of the bounding box only if the range is not a "boundary" range
|
||||
* (e.g., a range that straddles the boundary of the bbox).
|
||||
* @param term term for candidate document
|
||||
* @return match status
|
||||
*/
|
||||
@Override
|
||||
protected AcceptStatus accept(BytesRef term) {
|
||||
final long encodedTerm = prefixCodedToGeoCoded(term);
|
||||
final short termShift = (short)(64-getPrefixCodedShift(term));
|
||||
// range < term
|
||||
while (range.compare(encodedTerm, termShift) < 0) {
|
||||
// no more ranges, be gone
|
||||
if (hasNext() == false) {
|
||||
return AcceptStatus.END;
|
||||
}
|
||||
|
||||
// peek next range, if the range > term then seek
|
||||
final int peekCompare = range.compare(encodedTerm, termShift);
|
||||
if (peekCompare > 0) {
|
||||
return AcceptStatus.NO_AND_SEEK;
|
||||
} else if (peekCompare < 0) {
|
||||
seek(encodedTerm, termShift);
|
||||
}
|
||||
hasNext = false;
|
||||
}
|
||||
return AcceptStatus.YES;
|
||||
}
|
||||
|
||||
/** Returns true if the current range term is a boundary of the query shape */
|
||||
protected boolean boundaryTerm() {
|
||||
if (range.start == -1) {
|
||||
throw new IllegalStateException("GeoPointTermsEnum empty or not initialized");
|
||||
}
|
||||
return range.boundary;
|
||||
}
|
||||
|
||||
protected boolean postFilter(final double lat, final double lon) {
|
||||
return relationImpl.postFilter(lat, lon);
|
||||
}
|
||||
|
||||
protected final class Range {
|
||||
private short shift;
|
||||
private long start;
|
||||
private boolean boundary;
|
||||
|
||||
public Range(final long start, final short shift, final boolean boundary) {
|
||||
this.boundary = boundary;
|
||||
this.start = start;
|
||||
this.shift = shift;
|
||||
}
|
||||
|
||||
private int compare(long encoded, short shift) {
|
||||
final int result = Long.compare(this.start, encoded);
|
||||
if (result == 0) {
|
||||
return Short.compare(shift, this.shift);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Geospatial Query Implementations for Core Lucene
|
||||
*/
|
||||
package org.apache.lucene.spatial.geopoint.search;
|
|
@ -1,85 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.lucene.spatial.geopoint.search;
|
||||
|
||||
import org.apache.lucene.document.Document;
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.index.RandomIndexWriter;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.apache.lucene.geo.BaseGeoPointTestCase;
|
||||
import org.apache.lucene.geo.Polygon;
|
||||
import org.apache.lucene.spatial.geopoint.document.GeoPointField;
|
||||
import org.apache.lucene.store.Directory;
|
||||
|
||||
/**
|
||||
* random testing for GeoPoint query logic
|
||||
*
|
||||
* @lucene.experimental
|
||||
*/
|
||||
public class TestGeoPointQuery extends BaseGeoPointTestCase {
|
||||
|
||||
@Override
|
||||
protected double quantizeLat(double lat) {
|
||||
return GeoPointField.decodeLatitude(GeoPointField.encodeLatLon(lat, 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double quantizeLon(double lon) {
|
||||
return GeoPointField.decodeLongitude(GeoPointField.encodeLatLon(0, lon));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addPointToDoc(String field, Document doc, double lat, double lon) {
|
||||
doc.add(new GeoPointField(field, lat, lon, GeoPointField.TYPE_NOT_STORED));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Query newRectQuery(String field, double minLat, double maxLat, double minLon, double maxLon) {
|
||||
return new GeoPointInBBoxQuery(field, minLat, maxLat, minLon, maxLon);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Query newDistanceQuery(String field, double centerLat, double centerLon, double radiusMeters) {
|
||||
return new GeoPointDistanceQuery(field, centerLat, centerLon, radiusMeters);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Query newPolygonQuery(String field, Polygon... polygons) {
|
||||
return new GeoPointInPolygonQuery(field, polygons);
|
||||
}
|
||||
|
||||
/** explicit test failure for LUCENE-7325 */
|
||||
public void testInvalidShift() throws Exception {
|
||||
Directory dir = newDirectory();
|
||||
RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
|
||||
|
||||
// add a doc with a point
|
||||
Document document = new Document();
|
||||
addPointToDoc("field", document, 80, -65);
|
||||
writer.addDocument(document);
|
||||
|
||||
// search and verify we found our doc
|
||||
IndexReader reader = writer.getReader();
|
||||
IndexSearcher searcher = newSearcher(reader);
|
||||
assertEquals(0, searcher.count(newRectQuery("field", 90, 90, -180, 0)));
|
||||
|
||||
reader.close();
|
||||
writer.close();
|
||||
dir.close();
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.util;
|
||||
|
||||
import org.apache.lucene.spatial.geopoint.document.GeoPointField;
|
||||
import org.apache.lucene.util.BytesRefBuilder;
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
|
||||
import static org.apache.lucene.geo.GeoTestUtil.nextLatitude;
|
||||
import static org.apache.lucene.geo.GeoTestUtil.nextLongitude;
|
||||
import static org.apache.lucene.spatial.geopoint.document.GeoPointField.encodeLatLon;
|
||||
import static org.apache.lucene.spatial.geopoint.document.GeoPointField.geoCodedToPrefixCoded;
|
||||
import static org.apache.lucene.spatial.geopoint.document.GeoPointField.prefixCodedToGeoCoded;
|
||||
|
||||
/**
|
||||
* Tests encoding methods in {@link GeoPointField}
|
||||
*/
|
||||
public class TestGeoPointField extends LuceneTestCase {
|
||||
/**
|
||||
* Tests stability of {@link GeoPointField#geoCodedToPrefixCoded}
|
||||
*/
|
||||
public void testGeoPrefixCoding() throws Exception {
|
||||
int numIters = atLeast(1000);
|
||||
long hash;
|
||||
long decodedHash;
|
||||
BytesRefBuilder brb = new BytesRefBuilder();
|
||||
while (numIters-- >= 0) {
|
||||
hash = encodeLatLon(nextLatitude(), nextLongitude());
|
||||
for (int i=32; i<64; ++i) {
|
||||
geoCodedToPrefixCoded(hash, i, brb);
|
||||
decodedHash = prefixCodedToGeoCoded(brb.get());
|
||||
assertEquals((hash >>> i) << i, decodedHash);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue