Index and query geographic points and shapes using LatLonPoint, LatLonShape, and their cartesian XY equivalents.
Lucene provides two coordinate systems for geo-spatial indexing:
System
Classes
Use case
LatLon (WGS84 geodetic)
LatLonPoint, LatLonShape, LatLonDocValuesField
Real-world latitude/longitude on the Earth’s surface
XY (cartesian)
XYPointField, XYShape, XYDocValuesField
Flat 2-D space — floor plans, game maps, projected coordinates
Both systems encode coordinates with some loss of precision. For LatLonPoint the error is approximately 4.19×10⁻⁸ degrees in latitude and 8.38×10⁻⁸ degrees in longitude.
LatLonPoint stores a latitude/longitude pair as two 4-byte integers in a BKD tree. It supports fast range and distance queries but does not support sorting by distance on its own — add a companion LatLonDocValuesField for that.
import org.apache.lucene.document.LatLonPoint;import org.apache.lucene.search.Query;// Match all points within 50 km of ParisQuery distanceQuery = LatLonPoint.newDistanceQuery( "location", 48.8566, // center latitude 2.3522, // center longitude 50_000.0 // radius in meters);
import org.apache.lucene.document.LatLonPoint;import org.apache.lucene.geo.Polygon;import org.apache.lucene.search.Query;// CCW polygon — lats and lons must have the same length and at least 4 entries// (first and last point must be identical to close the ring)Polygon polygon = new Polygon( new double[]{48.0, 49.0, 49.0, 48.0, 48.0}, // latitudes new double[]{1.5, 1.5, 3.5, 3.5, 1.5} // longitudes);Query polyQuery = LatLonPoint.newPolygonQuery("location", polygon);
LatLonDocValuesField stores the same encoded lat/lon pair as a SORTED_NUMERIC doc value. Use LatLonDocValuesField.newDistanceSort() to create a SortField that orders results by ascending distance from a given point.
import org.apache.lucene.document.LatLonDocValuesField;import org.apache.lucene.search.IndexSearcher;import org.apache.lucene.search.MatchAllDocsQuery;import org.apache.lucene.search.Sort;import org.apache.lucene.search.SortField;import org.apache.lucene.search.TopFieldDocs;// Build a sort: closest to Paris firstSortField distanceSort = LatLonDocValuesField.newDistanceSort("location", 48.8566, 2.3522);Sort sort = new Sort(distanceSort);// The returned FieldDoc.fields[0] contains the distance in meters as a DoubleTopFieldDocs hits = searcher.search(new MatchAllDocsQuery(), 10, sort);
Always add both a LatLonPoint (for filtering) and a LatLonDocValuesField (for sorting) if you need to filter by area and sort by distance. They can share the same field name.
LatLonShape supports indexing and querying polygons, lines, and points as geo shapes. Internally, polygons are tessellated into triangles using an ear-clipping algorithm; the triangles are stored as point values in a BKD tree.
import org.apache.lucene.document.Document;import org.apache.lucene.document.Field;import org.apache.lucene.document.LatLonShape;import org.apache.lucene.geo.Line;import org.apache.lucene.geo.Polygon;Document doc = new Document();// Index a polygonPolygon polygon = new Polygon( new double[]{48.0, 49.0, 49.0, 48.0, 48.0}, new double[]{1.5, 1.5, 3.5, 3.5, 1.5});// createIndexableFields returns multiple Field objects (one per triangle)for (Field f : LatLonShape.createIndexableFields("shape", polygon)) { doc.add(f);}// Index a lineLine line = new Line( new double[]{48.5, 48.8, 49.0}, new double[]{2.0, 2.3, 2.5});for (Field f : LatLonShape.createIndexableFields("shape", line)) { doc.add(f);}// Index a point as a shapefor (Field f : LatLonShape.createIndexableFields("shape", 48.8566, 2.3522)) { doc.add(f);}writer.addDocument(doc);
Lucene uses the Haversine formula (via SloppyMath.haversinMeters) for distance queries. The sloppy implementation trades a small amount of accuracy for significantly faster execution; the error is well under 1 metre at any distance that matters for search.The value placed into FieldDoc.fields[0] by a distance sort is the distance in metres as a Double.
For flat 2-D spaces where the WGS84 geodetic model is inappropriate, use XYPointField and XYShape. The API mirrors the LatLon equivalents but takes float coordinates instead of double.
import org.apache.lucene.document.Document;import org.apache.lucene.document.XYPointField;import org.apache.lucene.geo.XYPolygon;import org.apache.lucene.geo.XYCircle;import org.apache.lucene.search.Query;// Index a 2-D pointDocument doc = new Document();doc.add(new XYPointField("pos", 10.5f, 20.3f)); // x, ywriter.addDocument(doc);// Bounding box queryQuery boxQuery = XYPointField.newBoxQuery( "pos", /* minX */ 5.0f, /* maxX */ 15.0f, /* minY */ 15.0f, /* maxY */ 25.0f);// Circle query (distance in the same unit as your coordinates)Query circleQuery = XYPointField.newDistanceQuery( "pos", /* centerX */ 10.0f, /* centerY */ 20.0f, /* radius */ 5.0f);// Polygon queryXYPolygon poly = new XYPolygon( new float[]{5f, 15f, 15f, 5f, 5f}, new float[]{15f, 15f, 25f, 25f, 15f});Query polyQuery = XYPointField.newPolygonQuery("pos", poly);
XYPointField uses float (32-bit) coordinates. This gives roughly 7 significant decimal digits of precision — adequate for most projected coordinate systems (e.g. metres in a local UTM zone).