/*
 * 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.document;

import org.apache.lucene.search.Query;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.NumericUtils;

import static org.apache.lucene.geo.GeoEncodingUtils.decodeLatitude;
import static org.apache.lucene.geo.GeoEncodingUtils.decodeLongitude;
import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude;
import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;

An indexed 2-Dimension Bounding Box field for the Geospatial Lat/Lon Coordinate system

This field indexes 2-dimension Latitude, Longitude based Geospatial Bounding Boxes. The bounding boxes are defined as minLat, minLon, maxLat, maxLon where min/max lat,lon pairs using double floating point precision.

Multiple values for the same field in one document is supported.

This field defines the following static factory methods for common search operations over double ranges:

  • newIntersectsQuery() matches bounding boxes that intersect the defined search bounding box.
  • newWithinQuery() matches bounding boxes that are within the defined search bounding box.
  • newContainsQuery() matches bounding boxes that contain the defined search bounding box.
  • newCrosses() matches bounding boxes that cross the defined search bounding box.

The following Field limitations and restrictions apply:

  • Dateline wrapping is not supported.
  • Due to an encoding limitation Eastern and Western Hemisphere Bounding Boxes that share the dateline are not supported.
/** * An indexed 2-Dimension Bounding Box field for the Geospatial Lat/Lon Coordinate system * <p> * This field indexes 2-dimension Latitude, Longitude based Geospatial Bounding Boxes. The bounding boxes are defined as * {@code minLat, minLon, maxLat, maxLon} where min/max lat,lon pairs using double floating point precision. * <p> * Multiple values for the same field in one document is supported. * * <p> * This field defines the following static factory methods for common search operations over double ranges: * <ul> * <li>{@link #newIntersectsQuery newIntersectsQuery()} matches bounding boxes that intersect the defined search bounding box. * <li>{@link #newWithinQuery newWithinQuery()} matches bounding boxes that are within the defined search bounding box. * <li>{@link #newContainsQuery newContainsQuery()} matches bounding boxes that contain the defined search bounding box. * <li>{@link #newCrossesQuery newCrosses()} matches bounding boxes that cross the defined search bounding box. * </ul> * * <p> * The following Field limitations and restrictions apply: * <ul> * <li>Dateline wrapping is not supported. * <li>Due to an encoding limitation Eastern and Western Hemisphere Bounding Boxes that share the dateline are not supported. * </ul> */
public class LatLonBoundingBox extends Field {
uses same encoding as LatLonPoint so numBytes is the same
/** uses same encoding as {@link LatLonPoint} so numBytes is the same */
public static final int BYTES = LatLonPoint.BYTES;
Create a new 2D GeoBoundingBoxField representing a 2 dimensional geospatial bounding box
Params:
  • name – field name. must not be null
  • minLat – minimum latitude value (in degrees); valid in [-90.0 : 90.0]
  • minLon – minimum longitude value (in degrees); valid in [-180.0 : 180.0]
  • maxLat – maximum latitude value (in degrees); valid in [minLat : 90.0]
  • maxLon – maximum longitude value (in degrees); valid in [minLon : 180.0]
/** * Create a new 2D GeoBoundingBoxField representing a 2 dimensional geospatial bounding box * * @param name field name. must not be null * @param minLat minimum latitude value (in degrees); valid in [-90.0 : 90.0] * @param minLon minimum longitude value (in degrees); valid in [-180.0 : 180.0] * @param maxLat maximum latitude value (in degrees); valid in [minLat : 90.0] * @param maxLon maximum longitude value (in degrees); valid in [minLon : 180.0] */
public LatLonBoundingBox(String name, final double minLat, final double minLon, final double maxLat, final double maxLon) { super(name, getType(2)); setRangeValues(minLat, minLon, maxLat, maxLon); }
set the field type
/** set the field type */
static FieldType getType(int geoDimensions) { FieldType ft = new FieldType(); ft.setDimensions(geoDimensions*2, BYTES); ft.freeze(); return ft; }
Changes the values of the field
Params:
  • minLat – minimum latitude value (in degrees); valid in [-90.0 : 90.0]
  • minLon – minimum longitude value (in degrees); valid in [-180.0 : 180.0]
  • maxLat – maximum latitude value (in degrees); valid in [minLat : 90.0]
  • maxLon – maximum longitude value (in degrees); valid in [minLon : 180.0]
Throws:
/** * Changes the values of the field * @param minLat minimum latitude value (in degrees); valid in [-90.0 : 90.0] * @param minLon minimum longitude value (in degrees); valid in [-180.0 : 180.0] * @param maxLat maximum latitude value (in degrees); valid in [minLat : 90.0] * @param maxLon maximum longitude value (in degrees); valid in [minLon : 180.0] * @throws IllegalArgumentException if {@code min} or {@code max} is invalid */
public void setRangeValues(double minLat, double minLon, double maxLat, double maxLon) { checkArgs(minLat, minLon, maxLat, maxLon); final byte[] bytes; if (fieldsData == null) { bytes = new byte[4*BYTES]; fieldsData = new BytesRef(bytes); } else { bytes = ((BytesRef)fieldsData).bytes; } encode(minLat, minLon, bytes, 0); encode(maxLat, maxLon, bytes, 2 * BYTES); }
validate the two-dimension arguments
/** validate the two-dimension arguments */
static void checkArgs(final double minLat, final double minLon, final double maxLat, final double maxLon) { // dateline crossing not supported if (minLon > maxLon) { throw new IllegalArgumentException("cannot have minLon [" + minLon + "] exceed maxLon [" + maxLon + "]."); } // pole crossing not supported if (minLat > maxLat) { throw new IllegalArgumentException("cannot have minLat [" + minLat + "] exceed maxLat [" + maxLat + "]."); } }
Create a new 2d query that finds all indexed 2d GeoBoundingBoxField values that intersect the defined 3d bounding ranges
Params:
  • field – field name. must not be null
  • minLat – minimum latitude value (in degrees); valid in [-90.0 : 90.0]
  • minLon – minimum longitude value (in degrees); valid in [-180.0 : 180.0]
  • maxLat – maximum latitude value (in degrees); valid in [minLat : 90.0]
  • maxLon – maximum longitude value (in degrees); valid in [minLon : 180.0]
Returns:query for matching intersecting 2d bounding boxes
/** * Create a new 2d query that finds all indexed 2d GeoBoundingBoxField values that intersect the defined * 3d bounding ranges * @param field field name. must not be null * @param minLat minimum latitude value (in degrees); valid in [-90.0 : 90.0] * @param minLon minimum longitude value (in degrees); valid in [-180.0 : 180.0] * @param maxLat maximum latitude value (in degrees); valid in [minLat : 90.0] * @param maxLon maximum longitude value (in degrees); valid in [minLon : 180.0] * @return query for matching intersecting 2d bounding boxes */
public static Query newIntersectsQuery(String field, final double minLat, final double minLon, final double maxLat, final double maxLon) { return newRangeQuery(field, minLat, minLon, maxLat, maxLon, RangeFieldQuery.QueryType.INTERSECTS); }
Create a new 2d query that finds all indexed 2d GeoBoundingBoxField values that are within the defined 2d bounding box
Params:
  • field – field name. must not be null
  • minLat – minimum latitude value (in degrees); valid in [-90.0 : 90.0]
  • minLon – minimum longitude value (in degrees); valid in [-180.0 : 180.0]
  • maxLat – maximum latitude value (in degrees); valid in [minLat : 90.0]
  • maxLon – maximum longitude value (in degrees); valid in [minLon : 180.0]
Returns:query for matching 3d bounding boxes that are within the defined bounding box
/** * Create a new 2d query that finds all indexed 2d GeoBoundingBoxField values that are within the defined * 2d bounding box * @param field field name. must not be null * @param minLat minimum latitude value (in degrees); valid in [-90.0 : 90.0] * @param minLon minimum longitude value (in degrees); valid in [-180.0 : 180.0] * @param maxLat maximum latitude value (in degrees); valid in [minLat : 90.0] * @param maxLon maximum longitude value (in degrees); valid in [minLon : 180.0] * @return query for matching 3d bounding boxes that are within the defined bounding box */
public static Query newWithinQuery(String field, final double minLat, final double minLon, final double maxLat, final double maxLon) { return newRangeQuery(field, minLat, minLon, maxLat, maxLon, RangeFieldQuery.QueryType.WITHIN); }
Create a new 2d query that finds all indexed 2d GeoBoundingBoxField values that contain the defined 2d bounding box
Params:
  • field – field name. must not be null
  • minLat – minimum latitude value (in degrees); valid in [-90.0 : 90.0]
  • minLon – minimum longitude value (in degrees); valid in [-180.0 : 180.0]
  • maxLat – maximum latitude value (in degrees); valid in [minLat : 90.0]
  • maxLon – maximum longitude value (in degrees); valid in [minLon : 180.0]
Returns:query for matching 2d bounding boxes that contain the defined bounding box
/** * Create a new 2d query that finds all indexed 2d GeoBoundingBoxField values that contain the defined * 2d bounding box * @param field field name. must not be null * @param minLat minimum latitude value (in degrees); valid in [-90.0 : 90.0] * @param minLon minimum longitude value (in degrees); valid in [-180.0 : 180.0] * @param maxLat maximum latitude value (in degrees); valid in [minLat : 90.0] * @param maxLon maximum longitude value (in degrees); valid in [minLon : 180.0] * @return query for matching 2d bounding boxes that contain the defined bounding box */
public static Query newContainsQuery(String field, final double minLat, final double minLon, final double maxLat, final double maxLon) { return newRangeQuery(field, minLat, minLon, maxLat, maxLon, RangeFieldQuery.QueryType.CONTAINS); }
Create a new 2d query that finds all indexed 2d GeoBoundingBoxField values that cross the defined 3d bounding box
Params:
  • field – field name. must not be null
  • minLat – minimum latitude value (in degrees); valid in [-90.0 : 90.0]
  • minLon – minimum longitude value (in degrees); valid in [-180.0 : 180.0]
  • maxLat – maximum latitude value (in degrees); valid in [minLat : 90.0]
  • maxLon – maximum longitude value (in degrees); valid in [minLon : 180.0]
Returns:query for matching 2d bounding boxes that cross the defined bounding box
/** * Create a new 2d query that finds all indexed 2d GeoBoundingBoxField values that cross the defined * 3d bounding box * @param field field name. must not be null * @param minLat minimum latitude value (in degrees); valid in [-90.0 : 90.0] * @param minLon minimum longitude value (in degrees); valid in [-180.0 : 180.0] * @param maxLat maximum latitude value (in degrees); valid in [minLat : 90.0] * @param maxLon maximum longitude value (in degrees); valid in [minLon : 180.0] * @return query for matching 2d bounding boxes that cross the defined bounding box */
public static Query newCrossesQuery(String field, final double minLat, final double minLon, final double maxLat, final double maxLon) { return newRangeQuery(field, minLat, minLon, maxLat, maxLon, RangeFieldQuery.QueryType.CROSSES); }
helper method to create a two-dimensional geospatial bounding box query
/** helper method to create a two-dimensional geospatial bounding box query */
private static Query newRangeQuery(String field, final double minLat, final double minLon, final double maxLat, final double maxLon, final RangeFieldQuery.QueryType queryType) { checkArgs(minLat, minLon, maxLat, maxLon); return new RangeFieldQuery(field, encode(minLat, minLon, maxLat, maxLon), 2, queryType) { @Override protected String toString(byte[] ranges, int dimension) { return LatLonBoundingBox.toString(ranges, dimension); } }; }
encodes a two-dimensional geo bounding box into a byte array
/** encodes a two-dimensional geo bounding box into a byte array */
static byte[] encode(double minLat, double minLon, double maxLat, double maxLon) { byte[] b = new byte[BYTES * 4]; encode(minLat, minLon, b, 0); encode(maxLat, maxLon, b, BYTES*2); return b; }
encodes a two-dimensional geopoint (lat, lon) into a byte array
/** encodes a two-dimensional geopoint (lat, lon) into a byte array */
static void encode(double lat, double lon, byte[] result, int offset) { if (result == null) { result = new byte[BYTES*4]; } NumericUtils.intToSortableBytes(encodeLatitude(lat), result, offset); NumericUtils.intToSortableBytes(encodeLongitude(lon), result, offset + BYTES); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getClass().getSimpleName()); sb.append(" <"); sb.append(name); sb.append(':'); sb.append('['); byte[] b = ((BytesRef)fieldsData).bytes; sb.append(toString(b, 0)); sb.append(','); sb.append(toString(b, 1)); sb.append(']'); sb.append('>'); return sb.toString(); } private static String toString(byte[] ranges, int dimension) { double lat, lon; switch (dimension) { case 0: lat = decodeLatitude(ranges, 0); lon = decodeLongitude(ranges, 4); break; case 1: lat = decodeLatitude(ranges, 8); lon = decodeLongitude(ranges, 12); break; default: throw new IllegalArgumentException("invalid dimension [" + dimension + "] in toString"); } return lat + "," + lon; } }