Back to Typesense

Geosearch

docs-site/content/29.0/api/geosearch.md

latest13.2 KB
Original Source

Geosearch

Typesense supports geo search on fields containing latitude and longitude values, specified as the geopoint or geopoint[] field types.

Let's create a collection called places with a field called location of type geopoint.

<Tabs :tabs="['JavaScript','PHP','Python','Ruby','Dart','Java','Go','Shell']"> <template v-slot:JavaScript>
js
let schema = {
  'name': 'places',
  'fields': [
    {
      'name': 'title',
      'type': 'string'
    },
    {
      'name': 'points',
      'type': 'int32'
    },
    {
      'name': 'location',
      'type': 'geopoint'
    }
  ],
  'default_sorting_field': 'points'
}

client.collections().create(schema)
</template> <template v-slot:PHP>
php
$schema = [
  'name'      => 'places',
  'fields'    => [
    [
      'name'  => 'title',
      'type'  => 'string'
    ],
    [
      'name'  => 'points',
      'type'  => 'int32'
    ],
    [
      'name'  => 'location',
      'type'  => 'geopoint'
    ]
  ],
  'default_sorting_field' => 'points'
];

$client->collections->create($schema);
</template> <template v-slot:Python>
py
schema = {
  'name': 'places',
  'fields': [
    {
      'name'  :  'title',
      'type'  :  'string'
    },
    {
      'name'  :  'points',
      'type'  :  'int32'
    },
    {
      'name'  :  'location',
      'type'  :  'geopoint'
    }
  ],
  'default_sorting_field': 'points'
}

client.collections.create(schema)
</template> <template v-slot:Ruby>
rb
schema = {
  'name'      => 'places',
  'fields'    => [
    {
      'name'  => 'title',
      'type'  => 'string'
    },
    {
      'name'  => 'points',
      'type'  => 'int32'
    },
    {
      'name'  => 'location',
      'type'  => 'geopoint'
    }
  ],
  'default_sorting_field' => 'points'
}

client.collections.create(schema)
</template> <template v-slot:Dart>
dart
final schema = Schema(
  'places',
  {
    Field('title', Type.string),
    Field('points', Type.int32),
    Field('location', Type.geopoint),
  },
  defaultSortingField: Field('points', Type.int32),
);

await client.collections.create(schema);
</template> <template v-slot:Java>
java
CollectionSchema collectionSchema = new CollectionSchema();

collectionschema.name("places")
                .addFieldsItem(new Field().name("title").type("string"))
                .addFieldsItem(new Field().name("points").type("int32"))
                .addFieldsItem(new Field().name("location").type("geopoint"))
                .defaultSortingField("points");

CollectionResponse collectionResponse = client.collections().create(collectionSchema);
</template> <template v-slot:Go>
go
schema := &api.CollectionSchema{
  Name: "places",
  Fields: []api.Field{
    {
      Name: "title",
      Type: "string",
    },
    {
      Name: "points",
      Type: "int32",
    },
    {
      Name: "location",
      Type: "geopoint",
    },
  },
  DefaultSortingField: pointer.String("points"),
}

client.Collections().Create(context.Background(), schema)
</template> <template v-slot:Shell>
bash
curl -k "http://localhost:8108/collections" -X POST
      -H "Content-Type: application/json" \
      -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" -d '{
        "name": "places",
        "fields": [
          {"name": "title", "type": "string" },
          {"name": "points", "type": "int32" },
          {"name": "location", "type": "geopoint"}
        ],
        "default_sorting_field": "points"
      }'
</template> </Tabs>

Let's now index a document.

<Tabs :tabs="['JavaScript','PHP','Python','Ruby','Dart','Java','Go','Shell']"> <template v-slot:JavaScript>
js
let document = {
  'title': 'Louvre Museuem',
  'points': 1,
  'location': [48.86093481609114, 2.33698396872901]
}

client.collections('places').documents().create(document)
</template> <template v-slot:PHP>
php
$document = [
  'title'   => 'Louvre Museuem',
  'points'  => 1,
  'location' => array(48.86093481609114, 2.33698396872901)
];

$client->collections['places']->documents->create($document);
</template> <template v-slot:Python>
py
document = {
  'title': 'Louvre Museuem',
  'points': 1,
  'location': [48.86093481609114, 2.33698396872901]
}

client.collections['places'].documents.create(document)
</template> <template v-slot:Ruby>
rb
document = {
  'title'    =>   'Louvre Museuem',
  'points'   =>   1,
  'location' =>  [48.86093481609114, 2.33698396872901]
}

client.collections['places'].documents.create(document)
</template> <template v-slot:Dart>
dart
final document = {
  'title': 'Louvre Museuem',
  'points': 1,
  'location': [48.86093481609114, 2.33698396872901]
};

await client.collection('places').documents.create(document};
</template> <template v-slot:Java>
java
HaashMap<String, Object> document = new HashMap<>();
float[] location =  {48.86093481609114, 2.33698396872901}

document.add("title", "Louvre Museuem");
document.add("points", 1);
document.add("location", location);

client.collection("places").documents.create(document);
</template> <template v-slot:Go>
go
document := struct {
  Title    int       `json:"title"`
  Points   int       `json:"points"`
  Location []float64 `json:"location"`
}{
  Title:    1984,
  Points:   100,
  Location: []float64{48.86093481609114, 2.33698396872901},
}

client.Collection("places").Documents().Create(context.Background(), document, &api.DocumentIndexParameters{})
</template> <template v-slot:Shell>
bash
curl "http://localhost:8108/collections/places/documents" -X POST \
        -H "Content-Type: application/json" \
        -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" \
        -d '{"points":1,"title":"Louvre Museuem", "location": [48.86093481609114, 2.33698396872901]}'
</template> </Tabs>

:::warning NOTE Make sure to set the coordinates in the correct order: [Latitude, Longitude]. GeoJSON often uses [Longitude, Latitude] which is invalid! :::

Searching within a Radius

We can now search for places within a given radius of a given latlong (use mi for miles and km for kilometers) using the filter_by search parameter.

In addition, let's also sort the records that are closest to a given location (this location can be the same or different from the latlong used for filtering).

<Tabs :tabs="['JavaScript','PHP','Python','Ruby','Dart','Java','Go','Shell']"> <template v-slot:JavaScript>
js
let searchParameters = {
  'q'         : '*',
  'query_by'  : 'title',
  'filter_by' : 'location:(48.90615915923891, 2.3435897727061175, 5.1 km)',
  'sort_by'   : 'location(48.853, 2.344):asc'
}

client.collections('companies').documents().search(searchParameters)
</template> <template v-slot:PHP>
php
$searchParameters = [
  'q'         => '*',
  'query_by'  => 'title',
  'filter_by' => 'location:(48.90615915923891, 2.3435897727061175, 5.1 km)',
  'sort_by'   => 'location(48.853, 2.344):asc'
];

$client->collections['companies']->documents->search($searchParameters);
</template> <template v-slot:Python>
py
search_parameters = {
  'q'         : '*',
  'query_by'  : 'title',
  'filter_by' : 'location:(48.90615915923891, 2.3435897727061175, 5.1 km)',
  'sort_by'   : 'location(48.853, 2.344):asc'
}

client.collections['companies'].documents.search(search_parameters)
</template> <template v-slot:Ruby>
rb
search_parameters = {
  'q'         => '*',
  'query_by'  => 'title',
  'filter_by' => 'location:(48.90615915923891, 2.3435897727061175, 5.1 km)',
  'sort_by'   => 'location(48.853, 2.344):asc'
}

client.collections['companies'].documents.search(search_parameters)
</template> <template v-slot:Dart>
dart
final searchParameters = {
  'q'         : '*',
  'query_by'  : 'title',
  'filter_by' : 'location:(48.90615915923891, 2.3435897727061175, 5.1 km)',
  'sort_by'   : 'location(48.853, 2.344):asc'
};

client.collections('companies').documents().search(searchParameters)
</template> <template v-slot:Java>
java
SearchParameters searchParameters = new SearchParameters()
                                        .q("*")
                                        .queryBy("title")
                                        .filterBy("location:(48.90615915923891, 2.3435897727061175, 5.1 km)")
                                        .sortBy("location(48.853, 2.344):asc");
SearchResult searchResult = client.collections("places").documents().search(searchParameters);
</template> <template v-slot:Go>
go
searchParameters := &api.SearchCollectionParams{
  Q:        pointer.String("*"),
  QueryBy:  pointer.String("title"),
  FilterBy: pointer.String("location:(48.90615915923891, 2.3435897727061175, 5.1 km)"),
  SortBy:   pointer.String("location(48.853, 2.344):asc"),
}

client.Collection("places").Documents().Search(context.Background(), searchParameters)
</template> <template v-slot:Shell>
bash
curl -G -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" \
  --data-urlencode 'q=*' \
  --data-urlencode 'query_by=title' \
  --data-urlencode 'filter_by=location:(48.853,2.344,5.1 km)' \
  --data-urlencode 'sort_by=location(48.853, 2.344):asc' \
'http://localhost:8108/collections/places/documents/search'
</template> </Tabs>

Sample Response

<Tabs :tabs="['JSON']"> <template v-slot:JSON>
json
{
  "facet_counts": [],
  "found": 1,
  "hits": [
    {
      "document": {
        "id": 0,
        "location": [48.86093481609114, 2.33698396872901],
        "points": 1,
        "title": "Louvre Museuem"
      },
      "geo_distance_meters": {"location": 1020},
      "highlights": [],
      "text_match": 16737280
    }
  ],
  "out_of": 1,
  "page": 1,
  "request_params": {"collection_name": "places", "per_page": 10, "q": "*"},
  "search_time_ms": 0
}
</template> </Tabs>

The above example uses "5.1 km" as the radius, but you can also use miles, e.g. location:(48.90615915923891, 2.3435897727061175, 2 mi).

Searching Within a Geo Polygon

You can also filter for documents within any arbitrary shaped polygon.

You want to specify the geo-points of the polygon as lat, lng pairs.

shell
'filter_by' : 'location:(48.8662, 2.3255, 48.8581, 2.3209, 48.8561, 2.3448, 48.8641, 2.3469)'

Geographic Polygons

You can also store polygonal geographic areas using the geopolygon field type and then check if points fall within these areas.

Creating a Collection with Geopolygons

Let's create a collection with a field to store polygon areas:

<Tabs :tabs="['Shell']"> <template v-slot:Shell>
bash
curl -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" \
     -H "Content-Type: application/json" \
     "http://localhost:8108/collections" -X POST \
     -d '{
       "name": "territories",
       "fields": [
         {"name": "name", "type": "string"},
         {"name": "area", "type": "geopolygon"}
       ]
     }'
</template> </Tabs>

Adding Polygon Areas

Add documents containing polygon areas by specifying the coordinates in counter-clockwise (CCW) or clockwise (CW) order:

<Tabs :tabs="['Shell']"> <template v-slot:Shell>
bash
curl "http://localhost:8108/collections/territories/documents" -X POST \
     -H "Content-Type: application/json" \
     -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" \
     -d '{
       "name": "square",
       "area": [0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0]
     }'
</template> </Tabs>

:::warning NOTE Coordinates must be specified in proper CCW or CW order to form a valid polygon. Incorrect ordering will result in an error. :::

Searching Points in Polygons

You can search for documents whose polygon areas contain a specific point:

<Tabs :tabs="['Shell']"> <template v-slot:Shell>
bash
curl -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" \
     "http://localhost:8108/collections/territories/documents/search\
      ?q=*&filter_by=area:(0.5, 0.5)"
</template> </Tabs>

This will return all polygons that contain the point (0.5, 0.5).

Sample Response

<Tabs :tabs="['JSON']"> <template v-slot:JSON>
json
{
  "found": 1,
  "hits": [
    {
      "document": {
        "area": [0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0],
        "id": "0",
        "name": "square"
      }
    }
  ]
}
</template> </Tabs>

Sorting by Additional Attributes within a Radius

exclude_radius

Sometimes, it's useful to sort nearby places within a radius based on another attribute like popularity, and then sort by distance outside this radius. You can use the exclude_radius option for that.

shell
'sort_by' : 'location(48.853, 2.344, exclude_radius: 2mi):asc, popularity:desc'

This makes all documents within a 2 mile radius to "tie" with the same value for distance. To break the tie, these records will be sorted by the next field in the list popularity:desc. Records outside the 2 mile radius are sorted first on their distance and then on popularity:desc as usual.

precision

Similarly, you can bucket all geo points into "groups" using the precision parameter, so that all results within this group will have the same "geo distance score".

shell
'sort_by' : 'location(48.853, 2.344, precision: 2mi):asc, popularity:desc'

This will bucket the results into 2-mile groups and force records within each bucket into a tie for "geo score", so that the popularity metric can be used to tie-break and sort results within each bucket.