Geosearch

    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.

    _geo field in v0.27, v0.28, and v0.29

    Due to Meilisearch allowing malformed _geo fields in the above-mentioned versions, 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
        }
      }
    ]
    
    WARNING

    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 'http://localhost:7700/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 establishes a circular area based on a central point and a radius. This filter rule requires three parameters: lat, lng and distance_in_meters.

    _geoRadius(lat, lng, distance_in_meters)
    

    lat and lng must be floating point numbers indicating a geographic position. distance_in_meters must be an integer indicating the radius covered by the _geoRadius filter.

    _geoBoundingBox

    _geoBoundingBox establishes a rectangular area based on the coordinates for its top right and bottom left corners. This filter rule requires two arrays:

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

    lat and lng must be floating point numbers indicating a geographic position. The first array indicates the geographic coordinates of the top right corner of the rectangular area. The second array indicates the coordinates of the bottom left corner of the rectangular area.

    Examples

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

    curl \
      -X POST 'http://localhost:7700/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 'http://localhost:7700/indexes/restaurants/search' \
      -H 'Content-type:application/json' \
      --data-binary '{ "filter": "_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])" }'

    In both cases, the results should look like this:

    [
      {
        "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 'http://localhost:7700/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
        }
      }
    ]
    

    The above command will only work if you have previously added type to filterableAttributes.

    WARNING

    _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

    You can use _geo data to sort results based on their distance from a specific location.

    Configuration

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

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

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

    You can read more about configuring sortableAttributes in our dedicated sorting guide.

    Usage

    First, ensure your documents contain valid geolocation data and that you have added the _geo attribute to the sortableAttributes list. Then, you can use the sort search parameter along with _geoPoint, a special sorting function, to order results based on their distance from a geographic location.

    _geoPoint(0.0, 0.0):asc
    

    _geoPoint requires two floating point numbers indicating a location's latitude and longitude. You must also specify whether the sort should be ascending (asc) or descending (desc). Ascending sort will prioritize results closer to the specified location, while descending sort will put the most distant results first.

    If either lat or lng is invalid or missing, Meilisearch will return an invalid_search_sort error. An error will also be thrown if you fail to indicate a sorting order.

    You can read more about sorting in our dedicated guide.

    WARNING

    _geo, _geoDistance, and _geoRadius are not valid sort values. Trying to use any of them with the sort search parameter will result in an invalid_search_sort error.

    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 'http://localhost:7700/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 'http://localhost:7700/indexes/restaurants/search' \
      -H 'Content-type:application/json' \
      --data-binary '{
        "sort": [
          "_geoPoint(48.8561446,2.2978204):asc",
          "rating:desc"
        ]
      }'

    The above command will only work if you have previously added rating to sortableAttributes.

    [
      {
        "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
        }
      }
    ]
    

    Ranking rules

    By default, Meilisearch emphasizes relevant sorting over exhaustive sorting. This means our engine first finds the most relevant results and only then orders matches based on values given to the sort search parameter. As a result, sorting with _geoPoint will rarely be the most important factor in deciding which results users see first. More often, it will be a tie-breaker between results that are considered equally relevant to the given search query.

    Since _geoPoint is part of the sort search parameter, its weight when ranking results is controlled by the position of the "sort" rule in the rankingRules array.

    You can read more about the "sort" ranking rule and how to customize it in our dedicated sorting guide.

    Finding the distance between a document and a _geoPoint

    When using _geoPoint, all returned documents will contain one extra field: _geoDistance. As its name indicates, _geoDistance contains the distance in meters between the specified _geoPoint and a document's _geo 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
        },
        "_geoDistance": 1532
      }
    ]
    
    WARNING

    Using _geoRadius filter will not cause results to include _geoDistance.

    _geoDistance will only be computed in a returned document if the query uses _geoPoint and the sort search parameter. Additionally, returned documents will only include _geoDistance if _geo is present in the displayedAttributes list.