Spatial search
|
This section documents the current Apache Lucene 10.x line — Lucene 10 requires Java 21 — as published
at the Apache Lucene documentation and Javadoc, which is the reference these
pages are written and verified against. No specific patch version is pinned; examples target This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production, as Lucene iterates quickly. This section’s bibliography lists the reference material consulted while preparing these pages. |
Lucene stores latitude/longitude points and 2D geometries as BKD-tree numeric fields in
lucene-core, so the common cases — "within this box", "within this radius", "inside this polygon",
"sort by distance" — need no extra module. lucene-spatial-extras adds grid (prefix-tree) and
serialized strategies for richer shape relations and non-geodetic planes, and lucene-spatial3d
computes on the actual ellipsoid so shapes that cross a pole or the date line behave correctly.
Points with LatLonPoint (lucene-core)
LatLonPoint encodes one (latitude, longitude) pair into an indexed BKD point. Add a matching
LatLonDocValuesField when you also need distance sorting or per-hit distance values — the indexed
point drives the query, the doc-values field drives the sort.
// https://lucene.apache.org/core/10_0_0/core/org/apache/lucene/document/LatLonPoint.html
import org.apache.lucene.document.Document;
import org.apache.lucene.document.LatLonPoint;
import org.apache.lucene.document.LatLonDocValuesField;
import org.apache.lucene.search.*;
Document d = new Document();
d.add(new LatLonPoint("location", 40.7128, -74.0060)); // indexed, for queries
d.add(new LatLonDocValuesField("location", 40.7128, -74.0060)); // doc values, for sort / retrieval
writer.addDocument(d);
// --- query forms, all built as static factories on LatLonPoint ---
Query box = LatLonPoint.newBoxQuery("location",
40.68, 40.75, -74.05, -73.95); // minLat, maxLat, minLon, maxLon
Query radius = LatLonPoint.newDistanceQuery("location",
40.7128, -74.0060, 5_000.0); // metres (great-circle)
Query poly = LatLonPoint.newPolygonQuery("location",
new org.apache.lucene.geo.Polygon(
new double[] {40.6, 40.6, 40.8, 40.8, 40.6},
new double[] {-74.1, -73.9, -73.9, -74.1, -74.1}));
// distance sort needs the LatLonDocValuesField, not the point
Sort nearest = new Sort(
LatLonDocValuesField.newDistanceSort("location", 40.7128, -74.0060));
TopFieldDocs hits = searcher.search(radius, 20, nearest);
// relevance boost that decays with distance from a pivot (recency-style, but geographic)
Query feature = LatLonPoint.newDistanceFeatureQuery(
"location", 3.0f, 40.7128, -74.0060, 10_000.0); // boost weight, origin, pivot distance
newBoxQuery handles date-line crossing when minLon > maxLon. newDistanceFeatureQuery is the
geographic sibling of LongPoint.newDistanceFeatureQuery used for recency in
Function & custom scoring — it contributes a
score that falls off with distance rather than filtering. Nearest-neighbour retrieval
(LatLonPoint.nearest) returns the k closest points directly without a bounding query. The
points & range queries page covers the BKD
structure these all ride on.
Geometries with LatLonShape and XYShape (lucene-core)
LatLonPoint only indexes points. To index polygons, lines, or a point that must answer
CONTAINS/WITHIN/DISJOINT relations, use LatLonShape: it tessellates each geometry into
triangles and indexes those as shape-encoded points. XYShape is the identical API over a flat
Cartesian plane (floats, no Earth curvature) for floor plans, game maps, or projected data.
// https://lucene.apache.org/core/10_0_0/core/org/apache/lucene/document/LatLonShape.html
import org.apache.lucene.document.Field;
import org.apache.lucene.document.LatLonShape;
import org.apache.lucene.document.ShapeField;
import org.apache.lucene.geo.Polygon;
import org.apache.lucene.search.Query;
Polygon zone = new Polygon(
new double[] {40.68, 40.68, 40.75, 40.75, 40.68},
new double[] {-74.05, -73.95, -73.95, -74.05, -74.05});
Document d = new Document();
for (Field f : LatLonShape.createIndexableFields("area", zone)) { // Field[]
d.add(f);
}
writer.addDocument(d);
// query: which indexed shapes intersect this polygon?
Query q = LatLonShape.newPolygonQuery("area", ShapeField.QueryRelation.INTERSECTS, zone);
// also: newBoxQuery, newLineQuery, newDistanceQuery (against a Circle),
// newGeometryQuery (mix of shapes), with WITHIN / CONTAINS / DISJOINT relations
ShapeField.QueryRelation is the relation vocabulary (INTERSECTS, WITHIN, CONTAINS,
DISJOINT); it is Lucene’s equivalent of the Elasticsearch geo_shape relation parameter described
in Elasticsearch geospatial. See the
XYShape Javadoc
for the planar variant.
Prefix-tree strategies in lucene-spatial-extras
lucene-spatial-extras predates the BKD shape fields and is still the route when you need
spatial4j shapes, custom grids, or the serialized
doc-values approach. A SpatialStrategy maps a spatial4j Shape to indexable fields and turns a
SpatialArgs (operation + query shape) into a Query.
| Strategy | What it does |
|---|---|
|
Indexes a shape as grid cells at increasing precision over a |
|
The simpler |
|
Stores the full geometry in a |
|
Two numeric doc-values fields for a plain point — distance sort / bbox only. |
// https://lucene.apache.org/core/10_0_0/spatial-extras/org/apache/lucene/spatial/prefix/RecursivePrefixTreeStrategy.html
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
import org.apache.lucene.spatial.query.SpatialArgs;
import org.apache.lucene.spatial.query.SpatialOperation;
import org.apache.lucene.document.Field;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.shape.Shape;
SpatialContext ctx = SpatialContext.GEO;
SpatialPrefixTree grid = new GeohashPrefixTree(ctx, 11); // ~ 1 m leaf cells
RecursivePrefixTreeStrategy strategy =
new RecursivePrefixTreeStrategy(grid, "zone");
// index
Shape polygon = ctx.getShapeFactory().polygon()
.pointXY(-74.05, 40.68).pointXY(-73.95, 40.68)
.pointXY(-73.95, 40.75).pointXY(-74.05, 40.75)
.pointXY(-74.05, 40.68).build();
Document d = new Document();
for (Field f : strategy.createIndexableFields(polygon)) { // Field[]
d.add(f);
}
writer.addDocument(d);
// query
Shape queryShape = ctx.getShapeFactory().rect(-74.1, -73.9, 40.6, 40.8);
Query q = strategy.makeQuery(new SpatialArgs(SpatialOperation.Intersects, queryShape));
SpatialContext.GEO uses spatial4j’s own geometry; adding the JTS-backed context
(org.locationtech.jts) enables polygons with holes and validity repair. The
lucene-spatial-extras module
overview lists every strategy and prefix-tree implementation. Solr’s
SpatialRecursivePrefixTreeFieldType in Solr spatial search
is this strategy wrapped as a field type.
geo3d — geometry on the ellipsoid (lucene-spatial3d)
Grid and projected-plane approaches distort near the poles and across the date line.
lucene-spatial3d (the geo3d code) represents every point as an (x, y, z) position on a
PlanetModel (WGS84 or SPHERE), so great-circle distance, polygons that enclose a pole, and
paths are computed without a projection.
// https://lucene.apache.org/core/10_0_0/spatial3d/org/apache/lucene/spatial3d/Geo3DPoint.html
import org.apache.lucene.spatial3d.Geo3DPoint;
import org.apache.lucene.spatial3d.geom.GeoPolygonFactory;
import org.apache.lucene.spatial3d.geom.GeoPoint;
import org.apache.lucene.spatial3d.geom.GeoPolygon;
import org.apache.lucene.spatial3d.geom.PlanetModel;
import java.util.List;
Document d = new Document();
d.add(new Geo3DPoint("location", 40.7128, -74.0060)); // degrees in, x/y/z stored (WGS84)
writer.addDocument(d);
Query radius = Geo3DPoint.newDistanceQuery(
"location", PlanetModel.WGS84, 40.7128, -74.0060, 5_000.0); // metres
// a polygon covering the North Pole -- no special-casing needed
GeoPolygon capPoly = GeoPolygonFactory.makeGeoPolygon(PlanetModel.WGS84, List.of(
new GeoPoint(PlanetModel.WGS84, Math.toRadians(85), Math.toRadians(0)),
new GeoPoint(PlanetModel.WGS84, Math.toRadians(85), Math.toRadians(120)),
new GeoPoint(PlanetModel.WGS84, Math.toRadians(85), Math.toRadians(-120))));
Query polar = Geo3DPoint.newShapeQuery("location", capPoly);
Geo3DPoint also offers newBoxQuery and newPathQuery (a corridor of fixed width along a
polyline). The tradeoff is index size and per-query CPU: three encoded dimensions instead of two.
The lucene-spatial3d module overview
documents the GeoShape hierarchy and the planet models.
Parsing WKT and GeoJSON
Geometries usually arrive as text. SimpleWKTShapeParser in lucene-core parses a
Well-Known Text string into
the org.apache.lucene.geo objects the shape fields expect; Polygon.fromGeoJSON parses a GeoJSON
Polygon / MultiPolygon document.
// https://lucene.apache.org/core/10_0_0/core/org/apache/lucene/geo/SimpleWKTShapeParser.html
import org.apache.lucene.geo.SimpleWKTShapeParser;
import org.apache.lucene.geo.Polygon;
import org.apache.lucene.document.LatLonShape;
Object shape = SimpleWKTShapeParser.parse(
"POLYGON((-74.05 40.68, -73.95 40.68, -73.95 40.75, -74.05 40.75, -74.05 40.68))");
// returns Polygon / Line / double[] point / Polygon[] / Line[] depending on the WKT type
if (shape instanceof Polygon p) {
for (var f : LatLonShape.createIndexableFields("area", p)) {
d.add(f);
}
}
// GeoJSON -> Polygon[] (an outer ring plus any holes / multipolygon parts)
Polygon[] fromJson = Polygon.fromGeoJSON(geoJsonString);
SimpleWKTShapeParser.parseExpectedType restricts the accepted type when you know it in advance.
For a document store that speaks GeoJSON natively rather than as a parsing step, contrast
MongoDB’s 2dsphere indexes.
Related pages
-
Points & range queries — the BKD tree that
LatLonPointandLatLonShapeare built on. -
Function & custom scoring — distance-decay scoring with
newDistanceFeatureQueryandDoubleValuesSource. -
Filtering & faceting — prefix-tree heatmap faceting from
lucene-spatial-extras. -
Solr spatial search — the same
LatLonPointand RPT machinery exposed as Solr field types and query parsers. -
Elasticsearch geospatial data & queries — the
geo_point/geo_shapefields and relation queries that wrap these Lucene classes. -
MongoDB special indexes & search —
2d/2dspheregeospatial indexes and GeoJSON queries in a document database.