Apache Solr Documentation

6.2 Ref Guide (PDF Download)
Solr Tutorial
Solr Community Wiki

Older Versions of this Guide (PDF)

6.3 Draft Ref Guide Topics


This Unreleased Guide Will Cover Apache Solr 6.3

Skip to end of metadata
Go to start of metadata

Solr supports location data for use in spatial/geospatial searches. Using spatial search, you can:

  • Index points or other shapes
  • Filter search results by a bounding box or circle or by other shapes
  • Sort or boost scoring by distance between points, or relative area between rectangles
  • Generate a 2D grid of facet count numbers for heatmap generation or point-plotting.

There are three main field types available for spatial search:

  • LatLonType and its non-geodetic twin PointType
  • SpatialRecursivePrefixTreeFieldType (RPT for short), including RptWithGeometrySpatialField, a derivative
  • BBoxField

RPT offers more features than LatLonType and fast filter performance, although LatLonType is more appropriate when efficient distance sorting/boosting is desired. They can both be used simultaneously for what each does best – LatLonType for sorting/boosting, RPT for filtering.  If you need to index shapes other than points (e.g. a circle or polygon) then use RPT.

BBoxField is for indexing bounding boxes, querying by a box, specifying a search predicate (Intersects,Within,Contains,Disjoint,Equals), and a relevancy sort/boost like overlapRatio or simply the area.

Some details that are not in this guide can be found at http://wiki.apache.org/solr/SpatialSearch.  

Indexing and Configuration

For indexing geodetic points (latitude and longitude), supply the pair of numbers as a string with a comma separating them in latitude then longitude order. For non-geodetic points, the order is x,y for PointType, and for RPT you must use a space instead of a comma, or use WKT or GeoJSON.

See the section SpatialRecursivePrefixTreeFieldType below for RPT configuration specifics.

Spatial Filters

There are 2 types of Spatial filters, which both support the following parameters:




the radial distance, usually in kilometers. (RPT & BBoxField can set other units via the setting distanceUnits)


the center point using the format "lat,lon" if latitude & longitude. Otherwise, "x,y" for PointType or "x y" for RPT field types.


a spatial indexed field


(Advanced option; RPT and BBoxField field types only) If the query is used in a scoring context (e.g. as the main query in q), this local parameter determines what scores will be produced. Valid values are:

  • none - A fixed score of 1.0. (the default)
  • kilometers - distance in kilometers between the field value and the specified center point
  • miles - distance in miles between the field value and the specified center point
  • degrees - distance in degrees between the field value and the specified center point
  • distance - distance between the field value and the specified center point in the distanceUnits configured for this field
  • recipDistance - 1 / the distance

Don't use this for indexed non-point shapes (e.g. polygons). The results will be erroneous. And with RPT, it's only recommended for multi-valued point data, as the implementation doesn't scale very well and for single-valued fields, you should instead use a separate LatLonType field purely for distance sorting.

When used with BBoxField,additional options are supported:

  • overlapRatio - The relative overlap between the indexed shape & query shape.
  • area - haversine based area of the overlapping shapes expressed in terms of the distanceUnits configured for this field
  • area2D - cartesian coordinates based area of the overlapping shapes expressed in terms of the distanceUnits configured for this field
filter(Advanced option; RPT and BBoxField field types only) If you only want the query to score (with the above score local parameter), not filter, then set this local parameter to false.


The geofilt filter allows you to retrieve results based on the geospatial distance (AKA the "great circle distance") from a given point. Another way of looking at it is that it creates a circular shape filter.  For example, to find all documents within five kilometers of a given lat/lon point, you could enter &q=*:*&fq={!geofilt sfield=store}&pt=45.15,-93.85&d=5. This filter returns all results within a circle of the given radius around the initial point:


The bbox filter is very similar to geofilt except it uses the bounding box of the calculated circle. See the blue box in the diagram below. It takes the same parameters as geofilt. Here's a sample query: &q=*:*&fq={!bbox sfield=store}&pt=45.15,-93.85&d=5. The rectangular shape is faster to compute and so it's sometimes used as an alternative to geofilt when it's acceptable to return points outside of the radius. However, if the ideal goal is a circle but you want it to run faster, then instead consider using the RPT field and try a large "distErrPct" value like 0.1 (10% radius). This will return results outside the radius but it will do so somewhat uniformly around the shape.

When a bounding box includes a pole, the bounding box ends up being a "bounding bowl" (a spherical cap) that includes all values north of the lowest latitude of the circle if it touches the north pole (or south of the highest latitude if it touches the south pole).

Filtering by an arbitrary rectangle

Sometimes the spatial search requirement calls for finding everything in a rectangular area, such as the area covered by a map the user is looking at.  For this case, geofilt and bbox won't cut it.  This is somewhat of a trick, but you can use Solr's range query syntax for this by supplying the lower-left corner as the start of the range and the upper-right corner as the end of the range.  Here's an example:  &q=*:*&fq=store:[45,-94 TO 46,-93]. LatLonType does not support rectangles that cross the dateline, but RPT does.  If you are using RPT with non-geospatial coordinates (geo="false") then you must quote the points due to the space, e.g. "x y".

Optimization: Solr Post Filtering

Most likely, the fastest spatial filters will be to simply use the RPT field type.  However, sometimes it may be faster to use LatLonType with Solr post filtering in circumstances when both the spatial query isn't worth caching and there aren't many matching documents that match the non-spatial filters (e.g. keyword queries and other filters).  To use Solr post filtering with LatLonType, use the bbox or geofilt query parsers in a filter query but specify cache=false and cost=100 (or greater) as local parameters. Here's a short example:

&q=...mykeywords...&fq=...someotherfilters...&fq={!geofilt cache=false cost=100}&sfield=store&pt=45.15,-93.85&d=5

Distance Sorting or Boosting (Function Queries)

There are four distance function queries: geodist, see below, usually the most appropriate;  dist , to calculate the p-norm distance between multi-dimensional vectors; hsin , to calculate the distance between two points on a sphere; and sqedist , to calculate the squared Euclidean distance between two points. For more information about these function queries, see the section on Function Queries.


geodist is a distance function that takes three optional parameters: (sfield,latitude,longitude). You can use the geodist function to sort results by distance or score return results.

For example, to sort your results by ascending distance, enter ...&q=*:*&fq={!geofilt}&sfield=store&pt=45.15,-93.85&d=50&sort=geodist() asc.

To return the distance as the document score, enter ...&q={!func}geodist()&sfield=store&pt=45.15,-93.85&sort=score+asc.

More Examples

Here are a few more useful examples of what you can do with spatial search in Solr.

Use as a Sub-Query to Expand Search Results

Here we will query for results in Jacksonville, Florida, or within 50 kilometers of 45.15,-93.85 (near Buffalo, Minnesota):

&q=*:*&fq=(state:"FL" AND city:"Jacksonville") OR {!geofilt}&sfield=store&pt=45.15,-93.85&d=50&sort=geodist()+asc

Facet by Distance

To facet by distance, you can use the Frange query parser:

&q=*:*&sfield=store&pt=45.15,-93.85&facet.query={!frange l=0 u=5}geodist()&facet.query={!frange l=5.001 u=3000}geodist()

There are other ways to do it too, like using a {!geofilt} in each facet.query.

Boost Nearest Results

Using the DisMax or Extended DisMax, you can combine spatial search with the boost function to boost the nearest results:

&q.alt=*:*&fq={!geofilt}&sfield=store&pt=45.15,-93.85&d=50&bf=recip(geodist(),2,200,20)&sort=score desc


RPT refers to either SpatialRecursivePrefixTreeFieldType (aka simply RPT) and an extended version: RptWithGeometrySpatialField (aka RPT with Geometry).  RPT offers several functional improvements over LatLonType:

  • Query by polygons and other complex shapes, in addition to circles & rectangles
  • Multi-valued indexed fields
  • Ability to index non-point shapes (e.g. polygons) as well as points
  • Rectangles with user-specified corners that can cross the dateline
  • Multi-value distance sort and score boosting (warning: non-optimized)
  • Well-Known-Text (WKT) shape syntax (required for specifying polygons & other complex shapes), and GeoJSON too. In addition to indexing and searching, this works with the wt=geojson (GeoJSON Solr response-writer) and [geo f=myfield] (geo Solr document-transformer).
  • Heatmap grid faceting capability

RPT incorporates the basic features of LatLonType and PointType, such as lat-lon bounding boxes and circles, in addition to supporting geofilt, bbox, geodist, and a range-queries.  RPT with Geometry is defined further below.

Schema configuration

To use RPT, the field type must be registered and configured in schema.xml. There are many options for this field type.  




The name of the field type.


This should be solr.SpatialRecursivePrefixTreeFieldType. But be aware that the Lucene spatial module includes some other so-called "spatial strategies" other than RPT, notably TermQueryPT*, BBox, PointVector*, and SerializedDV. Solr requires a field type to parallel these in order to use them. The asterisked ones have them.


This is a Java class name to an internal extension point governing support for shape definitions & parsing. If you require polygon support, set this to JTS – an alias for org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; otherwise it can be omitted. See important info below about JTS. (note: prior to Solr 6, the "org.locationtech.spatial4j" part was "com.spatial4j.core" and there used to be no convenience JTS alias)


If true, the default, latitude and longitude coordinates will be used and the mathematical model will generally be a sphere. If false, the coordinates will be generic X & Y on a 2D plane using Euclidean/Cartesian geometry.

formatDefines the shape syntax/format to be used. Defaults to WKT but GeoJSON is another popular format. Spatial4j governs this feature and supports other formats. If a given shape is parseable as "lat,lon" or "x y" then that is always supported.

This is used to specify the units for distance measurements used throughout the use of this field. This can be degrees, kilometers or miles. It is applied to nearly all distance measurements involving the field: maxDistErr, distErr, d, geodist and the score when score is distance, area, or area2d. However, it doesn't affect distances embedded in WKT strings, (eg: "BUFFER(POINT(200 10),0.2)"), which are still in degrees.

distanceUnits defaults to either "kilometers" if geo is "true", or "degress" if geo is "false".

distanceUnits replaces the units attribute; which is now deprecated and mutually exclusive with this attribute.


Defines the default precision of non-point shapes (both index & query), as a fraction between 0.0 (fully precise) to 0.5. The closer this number is to zero, the more accurate the shape will be. However, more precise indexed shapes use more disk space and take longer to index. Bigger distErrPct values will make queries faster but less accurate. At query time this can be overridden in the query syntax, such as to 0.0 so as to not approximate the search shape. The default for the RPT field is 0.025. Note: For RPTWithGeometrySpatialField (see below), there's always complete accuracy with the serialized geometry and so this doesn't control accuracy so much as it controls the trade-off of how big the index should be. distErrPct defaults to 0.15 for that field.


Defines the highest level of detail required for indexed data. If left blank, the default is one meter – just a bit less than 0.000009 degrees. This setting is used internally to compute an appropriate maxLevels (see below).


Defines the valid numerical ranges for x and y, in the format of ENVELOPE(minX, maxX, maxY, minY). If geo="true", the standard lat-lon world boundaries are assumed. If geo=false, you should define your boundaries.


Defines the distance calculation algorithm. If geo=true, "haversine" is the default. If geo=false, "cartesian" will be the default. Other possible values are "lawOfCosines", "vincentySphere" and "cartesian^2".


Defines the spatial grid implementation. Since a PrefixTree (such as RecursivePrefixTree) maps the world as a grid, each grid cell is decomposed to another set of grid cells at the next level. If geo=true then the default prefix tree is "geohash", otherwise it's "quad". Geohash has 32 children at each level, quad has 4. Geohash can only be used for geo=true as it's strictly geospatial. A third choice is "packedQuad", which is generally more efficient than plain "quad", provided there are many levels -- perhaps 20 or more.


Sets the maximum grid depth for indexed data. Instead, it's usually more intuitive to compute an appropriate maxLevels by specifying maxDistErr .

And there are others: normWrapLongitude , datelineRule, validationRule, autoIndex, allowMultiOverlap, precisionModel.  For further info, see notes below about spatialContextFactory implementations referenced above, especially the link to the JTS based one.

JTS and Polygons

As indicated above, spatialContextFactory must be set to JTS for polygon support, including multi-polygon.  All other shapes, including even line-strings, are supported without JTS.  JTS stands for JTS Topology Suite, which does not come with Solr due to its LGPL license. You must download it (a JAR file) and put that in a special location internal to Solr: SOLR_INSTALL/server/solr-webapp/webapp/WEB-INF/lib/. You can readily download it here: https://repo1.maven.org/maven2/com/vividsolutions/jts-core/ It will not work if placed in other more typical Solr lib directories, unfortunately. When activated, there are additional configuration attributes available; see  org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory for the Javadocs, and remember to look at the superclass's options in SpatialContextFactory as well. One option in particular you should most likely enable is autoIndex (i.e. use JTS's PreparedGeometry) as it's been shown to be a major performance boost for non-trivial polygons.

Once the field type has been defined, define a field that uses it.

Here's an example polygon query for a field "geo" that can be either solr.SpatialRecursivePrefixTreeFieldType or RptWithGeometrySpatialField:

&q=*:*&fq={!field f=geo}Intersects(POLYGON((-10 30, -40 40, -10 -20, 40 20, 0 0, -10 30)))

Inside the parenthesis following the search predicate is the shape definition.  The format of that shape is governed by the format attribute on the field type, defaulting to WKT.  If you prefer GeoJSON, you can specify that instead.

Beyond this reference guide and Spatila4j's docs, there are some details that remain at the Solr Wiki at http://wiki.apache.org/solr/SolrAdaptersForLuceneSpatial4


The RptWithGeometrySpatialField field type is a derivative of SpatialRecursivePrefixTreeFieldType that also stores the original geometry internally in Lucene DocValues, which it uses to achieve accurate search.  It can also be used for indexed point fields.  The Intersects predicate (the default) is particularly fast, since many search results can be returned as an accurate hit without requiring a geometry check.  This field type is configured just like RPT except that the default distErrPct is 0.15 (higher than 0.025) because the grid squares are purely for performance and not to fundamentally represent the shape.

An optional in-memory cache can be defined in solrconfig.xml, which should be done when the data tends to have shapes with many vertices. Assuming you name your field "geom", you can configure an optional cache in solrconfig.xml by adding the following – notice the suffix of the cache name:

When using this field type, you will likely not want to mark the field as stored because it's redundant with the DocValues data and surely larger because of the formatting (be it WKT or GeoJSON).  To retrieve the spatial data in search results from DocValues, use the [geo] transformer -- Transforming Result Documents.

Heatmap Faceting

The RPT field supports generating a 2D grid of facet counts for documents having spatial data in each grid cell.  For high-detail grids, this can be used to plot points, and for lesser detail it can be used for heatmap generation.  The grid cells are determined at index-time based on RPT's configuration.  At facet counting time, the indexed cells in the region of interest are traversed and a grid of counters corresponding to each cell are incremented.  Solr can return the data in a straight-forward 2D array of integers or in a PNG which compresses better for larger data sets but must be decoded.

The heatmap feature is accessed from Solr's faceting feature.  As a part of faceting, it supports the key local parameter as well as excluding tagged filter queries, just like other types of faceting do.  This allows multiple heatmaps to be returned on the same field with different filters.

facetSet to true to enable faceting
facet.heatmapThe field name of type RPT
facet.heatmap.geomThe region to compute the heatmap on, specified using the rectangle-range syntax or WKT. It defaults to the world. ex: ["-180 -90" TO "180 90"]

A specific grid level, which determines how big each grid cell is. Defaults to being computed via distErrPct (or distErr)

facet.heatmap.distErrPctA fraction of the size of geom used to compute gridLevel. Defaults to 0.15. It's computed the same as a similarly named parameter for RPT.
facet.heatmap.distErrA cell error distance used to pick the grid level indirectly. It's computed the same as a similarly named parameter for RPT.
facet.heatmap.formatThe format, either ints2D (default) or png.


You'll experiment with different distErrPct values (probably 0.10 - 0.20) with various input geometries till the default size is what you're looking for. The specific details of how it's computed isn't important. For high-detail grids used in point-plotting (loosely one cell per pixel), set distErr to be the number of decimal-degrees of several pixels or so of the map being displayed. Also, you probably don't want to use a geohash based grid because the cell orientation between grid levels flip-flops between being square and rectangle. Quad is consistent and has more levels, albeit at the expense of a larger index.

Here's some sample output in JSON (with some ..... inserted for brevity):

The output shows the gridLevel which is interesting since it's often computed from other parameters.  If an interface being developed allows an explicit resolution increase/decrease feature then subsequent requests can specify the gridLevel explicitly.

The minX, maxX, minY, maxY reports the region where the counts are.  This is the minimally enclosing bounding rectangle of the input geom at the target grid level.  This may wrap the dateline. The columns and rows values are how many columns and rows that the output rectangle is to be divided by evenly.  Note: Don't divide an on-screen projected map rectangle evenly to plot these rectangles/points since the cell data is in the coordinate space of decimal degrees if geo=true or whatever units were given if geo=false.  This could be arranged to be the same as an on-screen map but won't necessarily be.

The counts_ints2D key has a 2D array of integers.  The initial outer level is in row order (top-down), then the inner arrays are the columns (left-right).  If any array would be all zeros, a null is returned instead for efficiency reasons.  The entire value is null if there is no matching spatial data.

If format=png then the output key is counts_png.  It's a base-64 encoded string of a 4-byte PNG.  The PNG logically holds exactly the same data that the ints2D format does.  Note that the alpha channel byte is flipped to make it easier to view the PNG for diagnostic purposes, since otherwise counts would have to exceed 2^24 before it becomes non-opague.  Thus counts greater than this value will become opaque.


The BBoxField field type indexes a single rectangle (bounding box) per document field and supports searching via a bounding box.  It supports most spatial search predicates, it has enhanced relevancy modes based on the overlap or area between the search rectangle and the indexed rectangle.  It's particularly useful for its relevancy modes.  To configure it in the schema, use a configuration like this:

BBoxField is actually based off of 4 instances of another field type referred to by numberType.  It also uses a boolean to flag a dateline cross.  Assuming you want to use the relevancy feature, docValues is required.  Some of the attributes are in common with the RPT field like geo, units, worldBounds, and spatialContextFactory because they share some of the same spatial infrastructure.

To index a box, add a field value to a bbox field that's a string in the WKT/CQL ENVELOPE syntax.  Example: ENVELOPE(-10, 20, 15, 10) which is minX, maxX, maxY, minY order.  The parameter ordering is unintuitive but that's what the spec calls for.  Alternatively, you could provide a rectangular polygon in WKT (or GeoJSON if you set set format="GeoJSON").

To search, you can use the {!bbox} query parser, or the range syntax e.g. [10,-10 TO 15,20], or the ENVELOPE syntax wrapped in parenthesis with a leading search predicate.  The latter is the only way to choose a predicate other than Intersects.  For example: 

&q={!field f=bbox}Contains(ENVELOPE(-10, 20, 15, 10))

Now to sort the results by one of the relevancy modes, use it like this:

&q={!field f=bbox score=overlapRatio}Intersects(ENVELOPE(-10, 20, 15, 10))

The score local parameter can be one of overlapRatio, area, and area2D. area scores by the document area using surface-of-a-sphere (assuming geo=true) math, while area2D uses simple width * height.  overlapRatio computes a [0-1] ranged score based on how much overlap exists relative to the document's area and the query area.  The javadocs of BBoxOverlapRatioValueSource have more info on the formula. There is an additional parameter queryTargetProportion that allows you to weight the query side of the formula to the index (target) side of the formula. You can also use &debug=results to see useful score computation info.


  • No labels


  1. Note this comment reflects using Solr Spatial features without the JTS Topology Suite

    In Solr < 5.x, filter queries were able to support the field query type syntax (documented here: http://wiki.apache.org/solr/SolrAdaptersForLuceneSpatial4#Search) e.g.

    In Solr > 5.x queries using this syntax throw the following error: 

    Could the documentation be updated to reflect which field types support which type of query syntax? This still seems unclear to me. Specifically, what is supported with/without JTS for the various field types.

    1. The syntax you referred to was for rectangles; it was old and it was removed in v5 in favor of something more standardized  – WKT/CQL "ENVELOPE".  It has nothing to do with wether or not JTS is used.  But note if someone defines a custom SpatialContext, then that has the ability to possibly define new syntaxes.  No special syntaxes exist between JTS and the default non-JTS.

  2. How do you query by polygon?

    4.x had (from the page Jack references above):

    fq=geo:"IsWithin(POLYGON((-10 30, -40 40, -10 -20, 40 20, 0 0, -10 30))) distErrPct=0"

    Above, the RPT type mentions that it has:

    • Query by polygons and other complex shapes, in addition to circles & rectangles

    But I don't see any examples of the syntax, and a search for 'IsWithin' doesn't return anything on this Confluence.

    1. I added an example, using Intersects predicate, which I think is most commonly what people want since people most commonly index point data, and Intersects is semantically equivalent to IsWithin but faster.

      As to why it doesn't work for you – I'm not sure.  Ask on the solr-user list to get help.

    1. Copying David's reply above:

      Barry, the comments here are for improving the documentation.  Please ask your question on the mailing list.  p.s. your Q and my reply will be deleted sometime soon-ish