Guides / Building Search UI / UI & UX patterns

Geographical search with InstantSearch.js

You can use Algolia’s geographical search capabilities of Algolia with the geoSearch widget. The widget is implemented on top of Google Maps but the core logic is not tied to any map provider. You can build your own map widget with the connectGeoSearch connector and a different provider (such as Leaflet).

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.

Dataset

This guide uses a dataset of over 3,000 records of the biggest airports in the world.

1
2
3
4
5
6
7
8
9
10
11
12
{
    "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
    }
}

To be able to display hits on the map, latitude and longitude are stored in the record. You should you store this data it in the _geoloc attribute as it will allow geographical filtering and sorting.

You can download the dataset on GitHub. Have a look at how to import it in Algolia.

Configure index settings

When displaying on a map, you still want the relevance to be good. To do that, configure the index:

  • Searchable attributes. Allow searching in the four textual attributes: name, city, country and iata_code.
  • Custom ranking. Use the number of other connected airports links_count as a ranking metric. The more connections the better.
1
2
3
4
$index->setSettings([
  'searchableAttributes' => ['name', 'city', 'country', 'iata_code'],
  'customRanking' => ['desc(links_count)']
]);

Hits on the map

This is the most simple use case for the geoSearch 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 and initialPosition . By default, once you move the map the widget will use the bounding box of the map to filter the results. You can find all the available options of the widget in the documentation.

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 attribute of the geoSearch widget and they will be forwarded to the Google Maps instance.

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 for more information.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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. The source code is available on GitHub.

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 option. The widget accept a regular template like any other InstantSearch.js widgets.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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. The source code is available on GitHub.

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 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 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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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. The source code is available on GitHub.

Did you find this page helpful?