> ## Documentation Index
> Fetch the complete documentation index at: https://algolia.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Geo search

> Learn how to use geographical search in your InstantSearch.js app.

export const Records = () => <Tooltip tip="A record is a searchable object in an Algolia index. Each record consists of named attributes." cta="Algolia records" href="/doc/guides/sending-and-managing-data/prepare-your-data#algolia-records">
    records
  </Tooltip>;

export const Index = () => <Tooltip tip="An Algolia index is a searchable dataset that consists of records and configuration settings. These settings define how the records are searched and ranked.">
    index
  </Tooltip>;

<div className="not-prose algolia-flavor-switcher">
  <div className="afs-dropdown">
    <div className="afs-trigger" role="button" tabIndex="0" aria-haspopup="listbox">
      <span className="afs-current">JavaScript</span>

      <svg className="afs-chevron lucide lucide-chevron-down" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
        <path d="m6 9 6 6 6-6" />
      </svg>
    </div>

    <ul className="afs-menu" role="listbox">
      <li role="option" aria-selected="true"><a className="afs-option is-current" href="/doc/guides/building-search-ui/ui-and-ux-patterns/geo-search/js"><span className="afs-option-name">JavaScript</span><span className="afs-option-lib">InstantSearch.js</span></a></li>
      <li role="option" aria-selected="false"><a className="afs-option" href="/doc/guides/building-search-ui/ui-and-ux-patterns/geo-search/react"><span className="afs-option-name">React</span><span className="afs-option-lib">React InstantSearch</span></a></li>
      <li role="option" aria-selected="false"><a className="afs-option" href="/doc/guides/building-search-ui/ui-and-ux-patterns/geo-search/vue"><span className="afs-option-name">Vue</span><span className="afs-option-lib">Vue InstantSearch</span></a></li>
      <li role="option" aria-selected="false"><a className="afs-option" href="/doc/guides/building-search-ui/ui-and-ux-patterns/geo-search/ios"><span className="afs-option-name">iOS</span><span className="afs-option-lib">InstantSearch iOS</span></a></li>
      <li role="option" aria-selected="false"><a className="afs-option" href="/doc/guides/building-search-ui/ui-and-ux-patterns/geo-search/android"><span className="afs-option-name">Android</span><span className="afs-option-lib">InstantSearch Android</span></a></li>
    </ul>
  </div>
</div>

You can use Algolia's [geographical search capabilities](/doc/guides/managing-results/refine-results/geolocation#geo-search) of Algolia with the [`geoSearch`](/doc/api-reference/widgets/geo-search/js) widget.
The widget is implemented on top of [Google Maps](https://developers.google.com/maps/documentation/javascript/overview) but the core logic isn't tied to any map provider.
You can build your own map widget with the  [`connectGeoSearch`](/doc/api-reference/widgets/geo-search/js#customize-the-ui-with-connectgeosearch) connector and a different provider (such as [Leaflet](https://leafletjs.com)).

Before diving into the usage keep in mind that you are responsible for loading the Google Maps library. You can find more information about that [in the Google Maps documentation](https://developers.google.com/maps/documentation/javascript/tutorial).

## Dataset

This guide use a dataset of over 3,000 <Records /> of the biggest airports in the world.

```json JSON icon=braces theme={"system"}
{
  "objectID": "3797",
  "name": "John F Kennedy Intl",
  "city": "New York",
  "country": "United States",
  "iata_code": "JFK",
  "links_count": 911,
  "_geoloc": {
    "lat": 40.639751,
    "lng": -73.778925
  }
}
```

Latitude and longitude are stored in the record to allow hits to be displayed on the map.
You should store them in the `_geoloc` attribute to enable geo-filtering and geo-sorting.

You can download the dataset on [GitHub](https://github.com/algolia/datasets/tree/master/airports).
Have a look at how to import it in [Algolia](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/importing-from-the-dashboard).

### Configure index settings

When displaying on a map, you still want the relevance to be good.
For that, configure the <Index /> as follows:

* [Searchable attributes](/doc/guides/managing-results/must-do/searchable-attributes) should be set to enable search in the four textual attributes: `name`, `city`, `country`, and `iata_code`.
* [Custom ranking](/doc/guides/managing-results/must-do/custom-ranking): use the **number of other connected airports** `links_count` as a ranking metric. The more connections the better.

<CodeGroup>
  ```cs C# theme={"system"}
  var response = await client.SetSettingsAsync(
    "INDEX_NAME",
    new IndexSettings
    {
      SearchableAttributes = new List<string> { "name", "country", "city", "iata_code" },
      CustomRanking = new List<string> { "desc(links_count)" },
    }
  );
  ```

  ```dart Dart theme={"system"}
  final response = await client.setSettings(
    indexName: "INDEX_NAME",
    indexSettings: IndexSettings(
      searchableAttributes: [
        "name",
        "country",
        "city",
        "iata_code",
      ],
      customRanking: [
        "desc(links_count)",
      ],
    ),
  );
  ```

  ```go Go theme={"system"}
  response, err := client.SetSettings(client.NewApiSetSettingsRequest(
    "INDEX_NAME",
    search.NewEmptyIndexSettings().SetSearchableAttributes(
      []string{"name", "country", "city", "iata_code"}).SetCustomRanking(
      []string{"desc(links_count)"})))
  if err != nil {
    // handle the eventual error
    panic(err)
  }
  ```

  ```java Java theme={"system"}
  UpdatedAtResponse response = client.setSettings(
    "INDEX_NAME",
    new IndexSettings()
      .setSearchableAttributes(Arrays.asList("name", "country", "city", "iata_code"))
      .setCustomRanking(Arrays.asList("desc(links_count)"))
  );
  ```

  ```js JavaScript theme={"system"}
  const response = await client.setSettings({
    indexName: 'theIndexName',
    indexSettings: {
      searchableAttributes: ['name', 'country', 'city', 'iata_code'],
      customRanking: ['desc(links_count)'],
    },
  });
  ```

  ```kotlin Kotlin theme={"system"}
  var response =
    client.setSettings(
      indexName = "INDEX_NAME",
      indexSettings =
        IndexSettings(
          searchableAttributes = listOf("name", "country", "city", "iata_code"),
          customRanking = listOf("desc(links_count)"),
        ),
    )
  ```

  ```php PHP theme={"system"}
  $response = $client->setSettings(
      'INDEX_NAME',
      ['searchableAttributes' => [
          'name',

          'country',

          'city',

          'iata_code',
      ],
          'customRanking' => [
              'desc(links_count)',
          ],
      ],
  );
  ```

  ```python Python theme={"system"}
  response = client.set_settings(
      index_name="INDEX_NAME",
      index_settings={
          "searchableAttributes": [
              "name",
              "country",
              "city",
              "iata_code",
          ],
          "customRanking": [
              "desc(links_count)",
          ],
      },
  )
  ```

  ```ruby Ruby theme={"system"}
  response = client.set_settings(
    "INDEX_NAME",
    Algolia::Search::IndexSettings.new(
      searchable_attributes: ["name", "country", "city", "iata_code"],
      custom_ranking: ["desc(links_count)"]
    )
  )
  ```

  ```scala Scala theme={"system"}
  val response = Await.result(
    client.setSettings(
      indexName = "INDEX_NAME",
      indexSettings = IndexSettings(
        searchableAttributes = Some(Seq("name", "country", "city", "iata_code")),
        customRanking = Some(Seq("desc(links_count)"))
      )
    ),
    Duration(100, "sec")
  )
  ```

  ```swift Swift theme={"system"}
  let response = try await client.setSettings(
      indexName: "INDEX_NAME",
      indexSettings: IndexSettings(
          searchableAttributes: ["name", "country", "city", "iata_code"],
          customRanking: ["desc(links_count)"]
      )
  )
  ```
</CodeGroup>

## Hits on the map

This is the most simple use case for the [`geoSearch`](/doc/api-reference/widgets/geo-search/js) widget.
It displays your results on a Google Maps.
In the example, the `height` of the parent container is explicitly set.
This is a requirement of Google Maps: **if you don't set it, the map won't be displayed.**

The widget will set the zoom and position of the map according to the hits retrieved by the search. In case the search doesn't return any results the map will fall back to its [`initialZoom`](/doc/api-reference/widgets/geo-search/js#param-initial-zoom) and [`initialPosition`](/doc/api-reference/widgets/geo-search/js#param-initial-position) . By default, once you move the map the widget will use [the bounding box](/doc/api-reference/api-parameters/insideBoundingBox) of the map to filter the results. You can find all the available options of the widget [in the documentation](/doc/api-reference/widgets/geo-search/js).

Since the widget is built on top of Google Maps you might want to apply some options to your maps. For example you might want to switch on a satellite image rather than a normal street map. You can provide those extra options to the [`mapOptions`](/doc/api-reference/widgets/geo-search/js#param-map-options) attribute of the [`geoSearch`](/doc/api-reference/widgets/geo-search/js) widget and they will be forwarded to the Google Maps instance.

<Note>
  All examples in this guide assume you've included InstantSearch.js in your web page from a CDN. If, instead, you're using it with a package manager, adjust how you [import InstantSearch.js and its widgets](/doc/guides/building-search-ui/installation/js) for more information.
</Note>

```js JavaScript icon=code theme={"system"}
const search = instantsearch({
	indexName: "airports",
	searchClient,
});

search.addWidgets([
	instantsearch.widgets.searchBox({
		container: "#searchbox",
	}),

	instantsearch.widgets.geoSearch({
		container: "#maps",
		googleReference: window.google,
		mapOptions: {
			mapTypeId: window.google.maps.MapTypeId.SATELLITE,
		},
	}),
]);

search.start();
```

You can find a live example on [CodeSandbox](https://codesandbox.io/s/github/algolia/doc-code-samples/tree/master/instantsearch.js/geo-search-hits). The source code is available on [GitHub](https://github.com/algolia/doc-code-samples/tree/master/instantsearch.js/geo-search-hits).

## Custom marker

The standard marker API of Google Maps let you update the image of the marker.
You can also use custom styling to add information to the marker with the [`customHTMLMarker`](/doc/api-reference/widgets/geo-search/js#param-custom-html-marker) option.
The widget accept a regular template like any other InstantSearch.js widgets.

```js JavaScript icon=code theme={"system"}
const search = instantsearch({
	indexName: "airports",
	searchClient,
});

search.addWidgets([
	instantsearch.widgets.searchBox({
		container: "#searchbox",
	}),

	instantsearch.widgets.geoSearch({
		container: "#maps",
		googleReference: window.google,
		templates: {
			HTMLMarker: (hit, { html }) => html`
        <span class="marker">
          ${hit.city} - ${hit.airport_id}
        </span>
      `,
		},
	}),
]);

search.start();
```

You can find a live example on [CodeSandbox](https://codesandbox.io/s/github/algolia/doc-code-samples/tree/master/instantsearch.js/geo-search-custom-marker). The source code is available on [GitHub](https://github.com/algolia/doc-code-samples/tree/master/instantsearch.js/geo-search-custom-marker).

## Control

A common pattern that comes with a geographical search experience is the ability to control how the refinement behaves from the map.
By default the widget will use [the map bounding box](/doc/api-reference/api-parameters/insideBoundingBox) to filter the results as soon the map has moved.
But sometimes it's better for the experience to let users decide when to apply the refinement.
This way, they can explore the map without having the results changing every time.
For this pattern, the [`enableRefineControl`](/doc/api-reference/widgets/geo-search/js#param-enable-refine-control) option lets users control how the refinement behaves directly from the widget.
You can also control the default value applied to the checkbox of the control component with [`enableRefineOnMapMove`](/doc/api-reference/widgets/geo-search/js#param-enable-refine-on-map-move).

```js JavaScript icon=code theme={"system"}
const search = instantsearch({
	indexName: "airports",
	searchClient,
});

search.addWidgets([
	instantsearch.widgets.searchBox({
		container: "#searchbox",
	}),

	instantsearch.widgets.geoSearch({
		container: "#maps",
		googleReference: window.google,
		enableRefineControl: true,
		enableRefineOnMapMove: false,
	}),
]);

search.start();
```

You can find a live example on [CodeSandbox](https://codesandbox.io/s/github/algolia/doc-code-samples/tree/master/instantsearch.js/geo-search-control). The source code is available on [GitHub](https://github.com/algolia/doc-code-samples/tree/master/instantsearch.js/geo-search-control).
