| /* |
| * 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.spatial3d; |
| |
| import org.apache.lucene.document.Field; |
| import org.apache.lucene.document.FieldType; |
| import org.apache.lucene.index.PointValues; |
| import org.apache.lucene.geo.Polygon; |
| import org.apache.lucene.geo.GeoUtils; |
| import org.apache.lucene.spatial3d.geom.GeoPoint; |
| import org.apache.lucene.spatial3d.geom.GeoShape; |
| import org.apache.lucene.spatial3d.geom.PlanetModel; |
| import org.apache.lucene.search.Query; |
| import org.apache.lucene.util.BytesRef; |
| import org.apache.lucene.util.NumericUtils; |
| |
| /** |
| * Add this to a document to index lat/lon or x/y/z point, indexed as a 3D point. |
| * Multiple values are allowed: just add multiple Geo3DPoint to the document with the |
| * same field name. |
| * <p> |
| * This field defines static factory methods for creating a shape query: |
| * <ul> |
| * <li>{@link #newShapeQuery newShapeQuery()} for matching all points inside a specified shape |
| * </ul> |
| * @see PointValues |
| * @lucene.experimental */ |
| public final class Geo3DPoint extends Field { |
| |
| /** Planet Model for this Geo3DPoint */ |
| protected final PlanetModel planetModel; |
| |
| /** Indexing {@link FieldType}. */ |
| public static final FieldType TYPE = new FieldType(); |
| static { |
| TYPE.setDimensions(3, Integer.BYTES); |
| TYPE.freeze(); |
| } |
| |
| /** |
| * Creates a new Geo3DPoint field with the specified latitude, longitude (in degrees), with default WGS84 PlanetModel. |
| * |
| * @throws IllegalArgumentException if the field name is null or latitude or longitude are out of bounds |
| */ |
| public Geo3DPoint(String name, double lat, double lon) { |
| this(name, PlanetModel.WGS84, lat, lon); |
| } |
| |
| /** |
| * Creates a new Geo3DPoint field with the specified x,y,z, using default WGS84 planet model. |
| * |
| * @throws IllegalArgumentException if the field name is null or latitude or longitude are out of bounds |
| */ |
| public Geo3DPoint(String name, double x, double y, double z) { |
| this(name, PlanetModel.WGS84, x, y, z); |
| } |
| |
| /** |
| * Creates a new Geo3DPoint field with the specified x,y,z, and given planet model. |
| * |
| * @throws IllegalArgumentException if the field name is null or latitude or longitude are out of bounds |
| */ |
| public Geo3DPoint(String name, PlanetModel planetModel, double x, double y, double z) { |
| super(name, TYPE); |
| this.planetModel = planetModel; |
| fillFieldsData(planetModel, x, y, z); |
| } |
| |
| /** |
| * Creates a new Geo3DPoint field with the specified latitude, longitude (in degrees), given a planet model. |
| * |
| * @throws IllegalArgumentException if the field name is null or latitude or longitude are out of bounds |
| */ |
| public Geo3DPoint(String name, PlanetModel planetModel, double latitude, double longitude) { |
| super(name, TYPE); |
| GeoUtils.checkLatitude(latitude); |
| GeoUtils.checkLongitude(longitude); |
| this.planetModel = planetModel; |
| // Translate latitude/longitude to x,y,z: |
| final GeoPoint point = new GeoPoint(planetModel, Geo3DUtil.fromDegrees(latitude), Geo3DUtil.fromDegrees(longitude)); |
| fillFieldsData(planetModel, point.x, point.y, point.z); |
| } |
| |
| /** |
| * Create a query for matching points within the specified distance of the supplied location. |
| * @param field field name. must not be null. Note that if |
| * {@link PlanetModel#WGS84} is used, the query is approximate and may have up |
| * to 0.5% error. |
| * |
| * @param latitude latitude at the center: must be within standard +/-90 coordinate bounds. |
| * @param longitude longitude at the center: must be within standard +/-180 coordinate bounds. |
| * @param radiusMeters maximum distance from the center in meters: must be non-negative and finite. |
| * @return query matching points within this distance |
| * @throws IllegalArgumentException if {@code field} is null, location has invalid coordinates, or radius is invalid. |
| */ |
| public static Query newDistanceQuery(final String field, final PlanetModel planetModel, final double latitude, final double longitude, final double radiusMeters) { |
| final GeoShape shape = Geo3DUtil.fromDistance(planetModel, latitude, longitude, radiusMeters); |
| return newShapeQuery(field, shape); |
| } |
| |
| /** |
| * Create a query for matching a box. |
| * <p> |
| * The box may cross over the dateline. |
| * @param field field name. must not be null. |
| * @param minLatitude latitude lower bound: must be within standard +/-90 coordinate bounds. |
| * @param maxLatitude latitude upper bound: must be within standard +/-90 coordinate bounds. |
| * @param minLongitude longitude lower bound: must be within standard +/-180 coordinate bounds. |
| * @param maxLongitude longitude upper bound: must be within standard +/-180 coordinate bounds. |
| * @return query matching points within this box |
| * @throws IllegalArgumentException if {@code field} is null, or the box has invalid coordinates. |
| */ |
| public static Query newBoxQuery(final String field, final PlanetModel planetModel, final double minLatitude, final double maxLatitude, final double minLongitude, final double maxLongitude) { |
| final GeoShape shape = Geo3DUtil.fromBox(planetModel, minLatitude, maxLatitude, minLongitude, maxLongitude); |
| return newShapeQuery(field, shape); |
| } |
| |
| /** |
| * Create a query for matching a polygon. The polygon should have a limited number of edges (less than 100) and be well-defined, |
| * with well-separated vertices. |
| * <p> |
| * The supplied {@code polygons} must be clockwise on the outside level, counterclockwise on the next level in, etc. |
| * @param field field name. must not be null. |
| * @param polygons is the list of polygons to use to construct the query; must be at least one. |
| * @return query matching points within this polygon |
| */ |
| public static Query newPolygonQuery(final String field, final PlanetModel planetModel, final Polygon... polygons) { |
| final GeoShape shape = Geo3DUtil.fromPolygon(planetModel, polygons); |
| return newShapeQuery(field, shape); |
| } |
| |
| /** |
| * Create a query for matching a large polygon. This differs from the related newPolygonQuery in that it |
| * does little or no legality checking and is optimized for very large numbers of polygon edges. |
| * <p> |
| * The supplied {@code polygons} must be clockwise on the outside level, counterclockwise on the next level in, etc. |
| * @param field field name. must not be null. |
| * @param polygons is the list of polygons to use to construct the query; must be at least one. |
| * @return query matching points within this polygon |
| */ |
| public static Query newLargePolygonQuery(final String field, PlanetModel planetModel, final Polygon... polygons) { |
| final GeoShape shape = Geo3DUtil.fromLargePolygon(planetModel, polygons); |
| return newShapeQuery(field, shape); |
| } |
| |
| /** |
| * Create a query for matching a path. |
| * @param field field name. must not be null. |
| * @param pathLatitudes latitude values for points of the path: must be within standard +/-90 coordinate bounds. |
| * @param pathLongitudes longitude values for points of the path: must be within standard +/-180 coordinate bounds. |
| * @param pathWidthMeters width of the path in meters. |
| * @return query matching points within this polygon |
| */ |
| public static Query newPathQuery(final String field, final double[] pathLatitudes, final double[] pathLongitudes, final double pathWidthMeters, PlanetModel planetModel) { |
| final GeoShape shape = Geo3DUtil.fromPath(planetModel, pathLatitudes, pathLongitudes, pathWidthMeters); |
| return newShapeQuery(field, shape); |
| } |
| |
| private void fillFieldsData(PlanetModel planetModel, double x, double y, double z) { |
| byte[] bytes = new byte[12]; |
| encodeDimension(x, bytes, 0, planetModel); |
| encodeDimension(y, bytes, Integer.BYTES, planetModel); |
| encodeDimension(z, bytes, 2*Integer.BYTES, planetModel); |
| fieldsData = new BytesRef(bytes); |
| } |
| |
| // public helper methods (e.g. for queries) |
| |
| /** Encode single dimension */ |
| public static void encodeDimension(double value, byte bytes[], int offset, PlanetModel planetModel) { |
| NumericUtils.intToSortableBytes(planetModel.encodeValue(value), bytes, offset); |
| } |
| |
| /** Decode single dimension */ |
| public static double decodeDimension(byte value[], int offset, PlanetModel planetModel) { |
| return planetModel.decodeValue(NumericUtils.sortableBytesToInt(value, offset)); |
| } |
| |
| /** Returns a query matching all points inside the provided shape. |
| * |
| * @param field field name. must not be {@code null}. |
| * @param shape Which {@link GeoShape} to match |
| */ |
| public static Query newShapeQuery(String field, GeoShape shape) { |
| return new PointInGeo3DShapeQuery(field, shape); |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder result = new StringBuilder(); |
| result.append(getClass().getSimpleName()); |
| result.append(" <"); |
| result.append(name); |
| result.append(':'); |
| |
| BytesRef bytes = (BytesRef) fieldsData; |
| result.append(" x=").append(decodeDimension(bytes.bytes, bytes.offset, this.planetModel)); |
| result.append(" y=").append(decodeDimension(bytes.bytes, bytes.offset + Integer.BYTES, this.planetModel)); |
| result.append(" z=").append(decodeDimension(bytes.bytes, bytes.offset + 2 * Integer.BYTES, this.planetModel)); |
| result.append('>'); |
| return result.toString(); |
| } |
| |
| } |