Meilisearch allows you to filter and sort results based on their geographic location. This can be useful when you only want results within a specific area or when sorting results based on their distance from a specific location.

Due to Meilisearch allowing malformed _geo fields in the following versions (v0.27, v0.28 and v0.29), please ensure the _geo field follows the correct format.

In order to start filtering and sorting documents based on their geographic location, you must make sure they contain a valid _geo field.

_geo is a reserved field. If you include it in your documents, Meilisearch expects its value to conform to a specific format.

When using JSON and NDJSON, _geo must contain an object with two keys: lat and lng. Both fields must contain either a floating point number or a string indicating, respectively, latitude and longitude:

{

  "_geo": {
    "lat": 0.0,
    "lng": "0.0"
  }
}

Examples

Suppose we have a JSON array containing a few restaurants:

[
  {
    "id": 1,
    "name": "Nàpiz' Milano",
    "address": "Viale Vittorio Veneto, 30, 20124, Milan, Italy",
    "type": "pizza",
    "rating": 9
  },
  {
    "id": 2,
    "name": "Bouillon Pigalle",
    "address": "22 Bd de Clichy, 75018 Paris, France",
    "type": "french",
    "rating": 8
  },
  {
    "id": 3,
    "name": "Artico Gelateria Tradizionale",
    "address": "Via Dogana, 1, 20123 Milan, Italy",
    "type": "ice cream",
    "rating": 10
  }
]

Our restaurant dataset looks like this once we add geopositioning data:

[
  {
    "id": 1,
    "name": "Nàpiz' Milano",
    "address": "Viale Vittorio Veneto, 30, 20124, Milan, Italy",
    "type": "pizza",
    "rating": 9,
    "_geo": {
      "lat": 45.4777599,
      "lng": 9.1967508
    }
  },
  {
    "id": 2,
    "name": "Bouillon Pigalle",
    "address": "22 Bd de Clichy, 75018 Paris, France",
    "type": "french",
    "rating": 8,
    "_geo": {
      "lat": 48.8826517,
      "lng": 2.3352748
    }
  },
  {
    "id": 3,
    "name": "Artico Gelateria Tradizionale",
    "address": "Via Dogana, 1, 20123 Milan, Italy",
    "type": "ice cream",
    "rating": 10,
    "_geo": {
      "lat": 45.4632046,
      "lng": 9.1719421
    }
  }
]

Trying to index a dataset with one or more documents containing badly formatted _geo values will cause Meilisearch to throw an invalid_document_geo_field error. In this case, the update will fail and no documents will be added or modified.

Using _geo with CSV

If your dataset is formatted as CSV, the file header must have a _geo column. Each row in the dataset must then contain a column with a comma-separated string indicating latitude and longitude:

"id:number","name:string","address:string","type:string","rating:number","_geo:string"
"1","Nàpiz Milano","Viale Vittorio Veneto, 30, 20124, Milan, Italy","pizzeria",9,"45.4777599,9.1967508"
"2","Bouillon Pigalle","22 Bd de Clichy, 75018 Paris, France","french",8,"48.8826517,2.3352748"
"3","Artico Gelateria Tradizionale","Via Dogana, 1, 20123 Milan, Italy","ice cream",10,"48.8826517,2.3352748"

Filtering results with _geoRadius and _geoBoundingBox

You can use _geo data to filter queries so you only receive results located within a given geographic area.

Configuration

In order to filter results based on their location, you must add the _geo attribute to the filterableAttributes list:

curl \
  -X PUT 'MEILISEARCH_URL/indexes/restaurants/settings/filterable-attributes' \
  -H 'Content-type:application/json' \
  --data-binary '["_geo"]'

Meilisearch will rebuild your index whenever you update filterableAttributes. Depending on the size of your dataset, this might take a considerable amount of time.

You can read more about configuring filterableAttributes in our dedicated filtering guide.

Usage

Use the filter search parameter along with _geoRadius or _geoBoundingBox. These are special filter rules that ensure Meilisearch only returns results located within a specific geographic area.

_geoRadius

_geoRadius(lat, lng, distance_in_meters)

_geoBoundingBox

_geoBoundingBox([{lat}, {lng}], [{lat}, {lng}])

Examples

Using our example dataset, we can search for places to eat near the center of Milan with _geoRadius:

curl \
  -X POST 'MEILISEARCH_URL/indexes/restaurants/search' \
  -H 'Content-type:application/json' \
  --data-binary '{ "filter": "_geoRadius(45.472735, 9.184019, 2000)" }'

We also make a similar query using _geoBoundingBox:

curl \
  -X POST 'MEILISEARCH_URL/indexes/restaurants/search' \
  -H 'Content-type:application/json' \
  --data-binary '{ "filter": "_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])" }'
[
  {
    "id": 1,
    "name": "Nàpiz' Milano",
    "address": "Viale Vittorio Veneto, 30, 20124, Milan, Italy",
    "type": "pizza",
    "rating": 9,
    "_geo": {
      "lat": 45.4777599,
      "lng": 9.1967508
    }
  },
  {
    "id": 3,
    "name": "Artico Gelateria Tradizionale",
    "address": "Via Dogana, 1, 20123 Milan, Italy",
    "type": "ice cream",
    "rating": 10,
    "_geo": {
      "lat": 45.4632046,
      "lng": 9.1719421
    }
  }
]

It is also possible to combine _geoRadius and _geoBoundingBox with other filters. We can narrow down our previous search so it only includes pizzerias:

curl \
  -X POST 'MEILISEARCH_URL/indexes/restaurants/search' \
  -H 'Content-type:application/json' \
  --data-binary '{ "filter": "_geoRadius(45.472735, 9.184019, 2000) AND type = pizza" }'
[
  {
    "id": 1,
    "name": "Nàpiz' Milano",
    "address": "Viale Vittorio Veneto, 30, 20124, Milan, Italy",
    "type": "pizza",
    "rating": 9,
    "_geo": {
      "lat": 45.4777599,
      "lng": 9.1967508
    }
  }
]

_geo, _geoDistance, and _geoPoint are not valid filter rules. Trying to use any of them with the filter search parameter will result in an invalid_search_filter error.

Sorting results with _geoPoint

Configuration

Before using geosearch for sorting, you must add the _geo attribute to the sortableAttributes list:

curl \
  -X PUT 'MEILISEARCH_URL/indexes/restaurants/settings/sortable-attributes' \
  -H 'Content-type:application/json' \
  --data-binary '["_geo"]'

Read more about sortableAttributes here.

Usage

_geoPoint(0.0, 0.0):asc

Examples

The _geoPoint sorting function can be used like any other sorting rule. We can order documents based on how close they are to the Eiffel Tower:

curl \
  -X POST 'MEILISEARCH_URL/indexes/restaurants/search' \
  -H 'Content-type:application/json' \
  --data-binary '{ "sort": ["_geoPoint(48.8561446,2.2978204):asc"] }'

With our restaurants dataset, the results look like this:

[
  {
    "id": 2,
    "name": "Bouillon Pigalle",
    "address": "22 Bd de Clichy, 75018 Paris, France",
    "type": "french",
    "rating": 8,
    "_geo": {
      "lat": 48.8826517,
      "lng": 2.3352748
    }
  },
  {
    "id": 3,
    "name": "Artico Gelateria Tradizionale",
    "address": "Via Dogana, 1, 20123 Milan, Italy",
    "type": "ice cream",
    "rating": 10,
    "_geo": {
      "lat": 45.4632046,
      "lng": 9.1719421
    }
  },
  {
    "id": 1,
    "name": "Nàpiz' Milano",
    "address": "Viale Vittorio Veneto, 30, 20124, Milan, Italy",
    "type": "pizza",
    "rating": 9,
    "_geo": {
      "lat": 45.4777599,
      "lng": 9.1967508
    }
  }
]

_geoPoint also works when used together with other sorting rules. We can sort restaurants based on their proximity to the Eiffel Tower and their rating:

curl \
  -X POST 'MEILISEARCH_URL/indexes/restaurants/search' \
  -H 'Content-type:application/json' \
  --data-binary '{
    "sort": [
      "_geoPoint(48.8561446,2.2978204):asc",
      "rating:desc"
    ]
  }'
[
  {
    "id": 2,
    "name": "Bouillon Pigalle",
    "address": "22 Bd de Clichy, 75018 Paris, France",
    "type": "french",
    "rating": 8,
    "_geo": {
      "lat": 48.8826517,
      "lng": 2.3352748
    }
  },
  {
    "id": 3,
    "name": "Artico Gelateria Tradizionale",
    "address": "Via Dogana, 1, 20123 Milan, Italy",
    "type": "ice cream",
    "rating": 10,
    "_geo": {
      "lat": 45.4632046,
      "lng": 9.1719421
    }
  },
  {
    "id": 1,
    "name": "Nàpiz' Milano",
    "address": "Viale Vittorio Veneto, 30, 20124, Milan, Italy",
    "type": "pizza",
    "rating": 9,
    "_geo": {
      "lat": 45.4777599,
      "lng": 9.1967508
    }
  }
]