Examples

Rendering

Simple input

In this section, we will see how places.js turns any HTML <input> into an autocomplete address search bar.

Selected: none

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<input type="search" id="address" class="form-control" placeholder="Where are we going?" />

<p>Selected: <strong id="address-value">none</strong></p>
<script src="https://cdn.jsdelivr.net/npm/places.js@1.13.0"></script>
<script>
(function() {
  var placesAutocomplete = places({
    appId: '<YOUR_PLACES_APP_ID>',
    apiKey: '<YOUR_PLACES_API_KEY>',
    container: document.querySelector('#address')
  });

  var $address = document.querySelector('#address-value')
  placesAutocomplete.on('change', function(e) {
    $address.textContent = e.suggestion.value
  });

  placesAutocomplete.on('clear', function() {
    $address.textContent = 'none';
  });

})();
</script>

You should consider using this snippet when you want your users to easily find their addresses, and have it presented in a good and reliable format.

Complete form

In this section, we will see how you can interact with Places in order to fill in a complete address form.

Having an address autocomplete offers great user experience, but if the address is meant to be processed internally or used for shipping, you may have to reformat or access parts of the address, which can be cumbersome when reading an already formatted address.

In order to help you with this, Places.js emits a change event when a user selects an address from its dropdown. The event includes the selected address, also called a suggestion, in a structured format, which can then be used to populate other fields. You can learn more about the suggestion object in the documentation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<form action="/billing" class="form">
  <div class="form-group">
    <label for="form-address">Address*</label>
    <input type="search" class="form-control" id="form-address" placeholder="Where do you live?" />
  </div>
  <div class="form-group">
    <label for="form-address2">Address 2</label>
    <input type="text" class="form-control" id="form-address2" placeholder="Street number and name" />
  </div>
  <div class="form-group">
    <label for="form-city">City*</label>
    <input type="text" class="form-control" id="form-city" placeholder="City">
  </div>
  <div class="form-group">
    <label for="form-zip">ZIP code*</label>
    <input type="text" class="form-control" id="form-zip" placeholder="ZIP code">
  </div>
</form>

<script src="https://cdn.jsdelivr.net/npm/places.js@1.13.0"></script>
<script>
(function() {
  var placesAutocomplete = places({
    appId: '<YOUR_PLACES_APP_ID>',
    apiKey: '<YOUR_PLACES_API_KEY>',
    container: document.querySelector('#form-address'),
    type: 'address',
    templates: {
      value: function(suggestion) {
        return suggestion.name;
      }
    }
  });
  placesAutocomplete.on('change', function resultSelected(e) {
    document.querySelector('#form-address2').value = e.suggestion.administrative || '';
    document.querySelector('#form-city').value = e.suggestion.city || '';
    document.querySelector('#form-zip').value = e.suggestion.postcode || '';
  });
})();
</script>

This template can be used as a helper for shipping forms.

Note: This template intercepts the suggestion from the change event and updates other <input> fields, so that the user can then validate and/or modify the data if it is needed. This is considered a best practice because Places addresses can sometimes be incorrect or incomplete: new streets are built all the time; cities are sometimes renamed; or simply because the underlying OpenStreetMap data is not perfect in all areas. Therefore, you should not use the suggestion data without allowing the end user the possibility to modify the address.

Note: There are some challenges with address forms that have not been addressed in this example for the sake of simplicity. For instance, in some countries, there can be multiple postal codes for a single city, and you should then use a dropdown with the possible values provided in the suggestion, while still allowing the user to correct it if needed.

Displaying on a map

We will use the Leaflet JavaScript library as an example to display the places.js results on the map and update them when needed.

To try this example, you need to add leaflet in your code:

1
2
<link rel="stylesheet" href="https://cdn.jsdelivr.net/leaflet/1/leaflet.css" />
<script src="https://cdn.jsdelivr.net/leaflet/1/leaflet.js"></script>

Then use this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
<div id="map-example-container"></div>
<input type="search" id="input-map" class="form-control" placeholder="Where are we going?" />

<style>
  #map-example-container {height: 300px};
</style>

<script src="https://cdn.jsdelivr.net/npm/places.js@1.13.0"></script>
<script>
(function() {
  var placesAutocomplete = places({
    appId: '<YOUR_PLACES_APP_ID>',
    apiKey: '<YOUR_PLACES_API_KEY>',
    container: document.querySelector('#input-map')
  });

  var map = L.map('map-example-container', {
    scrollWheelZoom: false,
    zoomControl: false
  });

  var osmLayer = new L.TileLayer(
    'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
      minZoom: 1,
      maxZoom: 13,
      attribution: 'Map data © <a href="https://openstreetmap.org">OpenStreetMap</a> contributors'
    }
  );

  var markers = [];

  map.setView(new L.LatLng(0, 0), 1);
  map.addLayer(osmLayer);

  placesAutocomplete.on('suggestions', handleOnSuggestions);
  placesAutocomplete.on('cursorchanged', handleOnCursorchanged);
  placesAutocomplete.on('change', handleOnChange);
  placesAutocomplete.on('clear', handleOnClear);

  function handleOnSuggestions(e) {
    markers.forEach(removeMarker);
    markers = [];

    if (e.suggestions.length === 0) {
      map.setView(new L.LatLng(0, 0), 1);
      return;
    }

    e.suggestions.forEach(addMarker);
    findBestZoom();
  }

  function handleOnChange(e) {
    markers
      .forEach(function(marker, markerIndex) {
        if (markerIndex === e.suggestionIndex) {
          markers = [marker];
          marker.setOpacity(1);
          findBestZoom();
        } else {
          removeMarker(marker);
        }
      });
  }

  function handleOnClear() {
    map.setView(new L.LatLng(0, 0), 1);
    markers.forEach(removeMarker);
  }

  function handleOnCursorchanged(e) {
    markers
      .forEach(function(marker, markerIndex) {
        if (markerIndex === e.suggestionIndex) {
          marker.setOpacity(1);
          marker.setZIndexOffset(1000);
        } else {
          marker.setZIndexOffset(0);
          marker.setOpacity(0.5);
        }
      });
  }

  function addMarker(suggestion) {
    var marker = L.marker(suggestion.latlng, {opacity: .4});
    marker.addTo(map);
    markers.push(marker);
  }

  function removeMarker(marker) {
    map.removeLayer(marker);
  }

  function findBestZoom() {
    var featureGroup = L.featureGroup(markers);
    map.fitBounds(featureGroup.getBounds().pad(0.5), {animate: false});
  }
})();
</script>

Templates

Warning: This is an advanced feature.

Although Places.js comes with a good default template that should fit most use cases, you may want to customize both the input value and dropdown suggestion templates to better fit your needs.

In order to help you modify the rendered values for both objects, Places exposes a templates option that can be configured for both the value and suggestion components.

Templates are functions called with a suggestion object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<input type="search" id="address-templates" class="form-control" placeholder="Where are we going?" />

<script src="https://cdn.jsdelivr.net/npm/places.js@1.13.0"></script>
<script>
(function() {
  var placesAutocomplete = places({
    appId: '<YOUR_PLACES_APP_ID>',
    apiKey: '<YOUR_PLACES_API_KEY>',
    container: document.querySelector('#address-templates'),
    templates: {
      value: function(suggestion) {
        return 'Maybe ' + suggestion.name + ' in ' + suggestion.country + '?';
      },
      suggestion: function(suggestion) {
        return '<u>Click here to select ' + suggestion.name + ' from ' + suggestion.country + '</u>';
      }
    }
  });
})();
</script>

Disable styling

Warning: This is an advanced feature.

The default Algolia Places styling can be enhanced by overriding the default rules.

If you need full control and want to disable all default styling, you can do it by setting the style option to false.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<div id="input-styling-address">
  <input type="search" placeholder="Where are we going?" />
</div>

<script src="https://cdn.jsdelivr.net/npm/places.js@1.13.0"></script>
<style>
#input-styling-address input {
  display: inline-block;
  border: 1px solid #d9d9d9;
  border-radius: 12px;
  background: #ffffff;
  padding: 1em 0 1em 45px;
  width: 100%;
}

#input-styling-address input:focus, #input-styling-address input:active {
  outline: 0;
  border-color: #aaaaaa;
  background: #ffffff;
}

#input-styling-address .ap-nostyle-dropdown-menu {
  box-shadow: none;
  border: 1px solid #dadada;
  border-radius: 0;
  background: #fff;
  width: 100%;
}

#input-styling-address .ap-nostyle-input-icon {
  display: block;
  position: absolute;
  background: none;
  border: none;
}

#input-styling-address .algolia-places-nostyle { width: 50%; }
#input-styling-address .ap-nostyle-icon-pin { left: 5px;top: 10px; }
#input-styling-address .ap-nostyle-icon-clear { right: 5px;top: 15px }
#input-styling-address input:hover { border-color: silver; }
#input-styling-address input::placeholder { color: #aaaaaa; }
#input-styling-address .ap-nostyle-suggestion { border-bottom: 1px solid #efefef; }
</style>

<script>
(function() {
  var placesAutocomplete = places({
    appId: '<YOUR_PLACES_APP_ID>',
    apiKey: '<YOUR_PLACES_API_KEY>',
    container: document.querySelector('#input-styling-address input'),
    style: false,
    debug: true
  });
})();
</script>

See our documentation about styling for more details.

Important: Regardless of how you want to style your results, your updated styles must still be compliant with our Usage Policy and must display the Algolia Logo.

Places offers many ways to restrict the scope of its search to either certain types of records, certain countries, or even certain locations.

In this section, we will explore how we can use these restrictions to improve the relevance to best match the end user expectations.

Concept: type parameter.

In this section, we will see how you can restrict the scope of Places to only a certain type.

There are many use-cases where having a full blown address search is not necessary, and simply searching for a city is enough.

In order to help you with this, Places.js exposes a configuration parameter to restrict the type of records you want to search against. You can build a city search input by using the type parameter.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<input type="search" id="city" class="form-control" placeholder="In which city do you live?" />

<script src="https://cdn.jsdelivr.net/npm/places.js@1.13.0"></script>
<script>
(function() {
  var placesAutocomplete = places({
    appId: '<YOUR_PLACES_APP_ID>',
    apiKey: '<YOUR_PLACES_API_KEY>',
    container: document.querySelector('#city'),
    type: 'city',
    aroundLatLngViaIP: false,
    templates: {
      value: function(suggestion) {
        return suggestion.name;
      }
    }
  });
})();
</script>

Note: It is possible to restrict the type of records to more than a single type by passing an array of types that you want to search against. For instance, you can combine the city type with the airport type. However, be careful when searching for cities and other types at the same time. Due to the fact that cities are prioritized in the ranking, other types of records can be pushed back and have difficulties appearing in the results.

You can also search in countries only.

places.js turns any HTML <input> into a Country as-you-type search bar. You can filter on a specific list of countries if needed:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<link rel="stylesheet" type="text/css" href="//github.com/downloads/lafeber/world-flags-sprite/flags16.css" />

<div class="f16">
  <input type="search" id="country" class="form-control" placeholder="What's your favorite country?" />
</div>

<script src="https://cdn.jsdelivr.net/npm/places.js@1.13.0"></script>
<script>
(function() {
  var placesAutocomplete = places({
    appId: '<YOUR_PLACES_APP_ID>',
    apiKey: '<YOUR_PLACES_API_KEY>',
    container: document.querySelector('#country'),
    type: 'country',
    templates: {
      suggestion: function(suggestion) {
        return '<i class="flag ' + suggestion.countryCode + '"></i> ' +
          suggestion.highlight.name;
      }
    }
  });
})();
</script>

Searching in a country

Concept: countries parameter.

In this section, we will see how you can restrict the scope of Places to search only in some countries.

Places.js exposes a countries parameter will restrict the search to records that belong to this array of countries. For instance, if you want to restrict the search to records in France:

Selected: none

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<input type="search" id="single-country-search" class="form-control" placeholder="Where are we going?" />

<p>Selected: <strong id="single_country-address-value">none</strong></p>
<script src="https://cdn.jsdelivr.net/npm/places.js@1.13.0"></script>
<script>
(function() {
  var placesAutocomplete = places({
    appId: '<YOUR_PLACES_APP_ID>',
    apiKey: '<YOUR_PLACES_API_KEY>',
    container: document.querySelector('#single-country-search'),
    countries: ['fr']
  });

  var $address = document.querySelector('#single-country-address-value')
  placesAutocomplete.on('change', function(e) {
    $address.textContent = e.suggestion.value
  });

  placesAutocomplete.on('clear', function() {
    $address.textContent = 'none';
  });

})();
</script>

Note: It is considered a best practice to restrict Places search to only the countries that are relevant to your use case. Restricting Places to a single country can greatly improve the relevance of the results.

Search around lat/lng

Concepts: aroundLatLng and aroundRadius parameters.

Reusing the map example, we want to only search around Paris, France.

This is useful when you really want to display results around a specific area.

To try this example, you need to add leaflet in your code:

1
2
<link rel="stylesheet" href="https://cdn.jsdelivr.net/leaflet/1/leaflet.css" />
<script src="https://cdn.jsdelivr.net/leaflet/1/leaflet.js"></script>

Then use this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
<div id="map-example-container-paris"></div>
<input type="search" id="input-map-paris" class="form-control" placeholder="Find a street in Paris, France. Try &quot;Rivoli&quot;" />

<style>
  #map-example-container-paris {
    height: 300px
  }
</style>

<script src="https://cdn.jsdelivr.net/npm/places.js@1.13.0"></script>
<script>
(function() {
  var latlng = {
    lat: 48.8566,
    lng: 2.34287
  };

  var placesAutocomplete = places({
    appId: '<YOUR_PLACES_APP_ID>',
    apiKey: '<YOUR_PLACES_API_KEY>',
    container: document.querySelector('#input-map-paris'),
    aroundLatLng: latlng.lat + ',' + latlng.lng, // Paris latitude longitude
    aroundRadius: 10 * 1000, // 10km radius
    type: 'address'
  });

  var map = L.map('map-example-container-paris', {
    scrollWheelZoom: false,
    zoomControl: false
  });

  var osmLayer = new L.TileLayer(
    'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
      minZoom: 12,
      maxZoom: 18,
      attribution: 'Map data © <a href="https://openstreetmap.org">OpenStreetMap</a> contributors'
    }
  );

  var markers = [];

  map.setView(new L.LatLng(latlng.lat, latlng.lng), 12);
  map.addLayer(osmLayer);

  placesAutocomplete.on('suggestions', handleOnSuggestions);
  placesAutocomplete.on('cursorchanged', handleOnCursorchanged);
  placesAutocomplete.on('change', handleOnChange);

  function handleOnSuggestions(e) {
    markers.forEach(removeMarker);
    markers = [];

    if (e.suggestions.length === 0) {
      map.setView(new L.LatLng(latlng.lat, latlng.lng), 12);
      return;
    }

    e.suggestions.forEach(addMarker);
    findBestZoom();
  }

  function handleOnChange(e) {
    markers
      .forEach(function(marker, markerIndex) {
        if (markerIndex === e.suggestionIndex) {
          markers = [marker];
          marker.setOpacity(1);
          findBestZoom();
        } else {
          removeMarker(marker);
        }
      });
  }

  function handleOnClear() {
    map.setView(new L.LatLng(latlng.lat, latlng.lng), 12);
  }

  function handleOnCursorchanged(e) {
    markers
      .forEach(function(marker, markerIndex) {
        if (markerIndex === e.suggestionIndex) {
          marker.setOpacity(1);
          marker.setZIndexOffset(1000);
        } else {
          marker.setZIndexOffset(0);
          marker.setOpacity(0.5);
        }
      });
  }

  function addMarker(suggestion) {
    var marker = L.marker(suggestion.latlng, {opacity: .4});
    marker.addTo(map);
    markers.push(marker);
  }

  function removeMarker(marker) {
    map.removeLayer(marker);
  }

  function findBestZoom() {
    var featureGroup = L.featureGroup(markers);
    map.fitBounds(featureGroup.getBounds().pad(0.5), {animate: false});
  }
})();
</script>

Note: Both parameters are key to restrict the search to a certain area. If a location is passed in aroundLatLng without the aroundRadius parameter being set, Places will prioritize results around the location but will also search the rest of the world for additional results. This is why the aroundLatLng parameter should be considered as a wait to first search around a location rather than as a filter.

Advanced

Places + Custom data

Using Algolia's autocomplete.js library, you can search in your own data along with showing Algolia Places results.

You need an Algolia account to do so and the Algolia Places dataset:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
<input type="search" id="autocomplete-dataset" class="form-control" placeholder="Search for vacation rentals or cities" />

<script src="https://cdn.jsdelivr.net/algoliasearch/3/algoliasearchLite.min.js"></script>
<script src="https://cdn.jsdelivr.net/autocomplete.js/0/autocomplete.js"></script>
<style>
.algolia-autocomplete {
  width: 100%;
}
.ad-example-dropdown-menu {
  width: 100%;
  color: black;
  background-color: #fff;
  border: 1px solid #ccc;
  border-top: none;
  border-radius: 5px;
  padding: .5em;
  box-shadow: 1px 1px 32px -10px rgba(0,0,0,0.62);
}
.ad-example-dropdown-menu .ad-example-suggestion {
  cursor: pointer;
  padding: 5px 4px;
}
.ad-example-dropdown-menu .ad-example-suggestion img {
  height: 2em;
  margin-top: .5em;
  margin-right: 10px;
  float: left;
}
.ad-example-dropdown-menu .ad-example-suggestion small {
  font-size: .8em;
  color: #bbb;
}
.ad-example-dropdown-menu .ad-example-suggestion.ad-example-cursor {
  background-color: #B2D7FF;
}
.ad-example-dropdown-menu .ad-example-suggestion em {
  font-weight: bold;
  font-style: normal;
}
.ad-example-header {
  font-weight: bold;
  padding: .5em 0;
  margin-bottom: 1em;
  border-bottom: 1px solid #ccc;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/places.js@1.13.0/dist/cdn/placesAutocompleteDataset.min.js"></script>
<script>
(function() {
  var client = algoliasearch('latency', '6be0576ff61c053d5f9a3225e2a90f76');
  var index = client.initIndex('airbnb');

  // create the first autocomplete.js dataset: vacation rentals
  var rentalsDataset = {
    source: autocomplete.sources.hits(index, {hitsPerPage: 2}),
    displayKey: 'name',
    name: 'rentals',
    templates: {
      header: '<div class="ad-example-header">Vacation rentals</div>',
      suggestion: function(suggestion) {
        return '<img src="' + suggestion.thumbnail_url + '" />' +
          '<div>' +
            suggestion._highlightResult.name.value + '<br />' +
            '<small>' + suggestion._highlightResult.city.value + '</small>' +
         '</div>';
      }
    }
  };

  // create the second dataset: places
  // we automatically inject the default CSS
  // all the places.js options are available
  var placesDataset = placesAutocompleteDataset({
    appId: '<YOUR_PLACES_APP_ID>',
    apiKey: '<YOUR_PLACES_API_KEY>',
    algoliasearch: algoliasearch,
    templates: {
      header: '<div class="ad-example-header">Cities</div>'
    },
    hitsPerPage: 3
  });

  // init
  var autocompleteInstance = autocomplete(document.querySelector('#autocomplete-dataset'), {
    hint: false,
    debug: true,
    cssClasses: {prefix: 'ad-example'}
  }, [
    rentalsDataset,
    placesDataset
  ]);

  var autocompleteChangeEvents = ['selected', 'autocompleted'];

  autocompleteChangeEvents.forEach(function(eventName) {
    autocompleteInstance.on('autocomplete:'+ eventName, function(event, suggestion, datasetName) {
      console.log(datasetName, suggestion);
    });
  });
})();
</script>

See the documentation about the placesAutocompleteDataset function.

Places + InstantSearch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<input type="search" id="input-map-instantsearch" class="form-control" placeholder="Where are you looking for a coffee?" />
<div id="map-instantsearch-container"></div>

<style>
  #map-instantsearch-container {height: 300px};
</style>

<script src="https://cdn.jsdelivr.net/instantsearch.js/2.10.1/instantsearch.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/places.js@1.13.0/dist/cdn/placesInstantsearchWidget.min.js"></script>
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBawL8VbstJDdU5397SUX7pEt9DslAwWgQ"></script>
<script>
(function() {
var search = instantsearch({
  appId: 'latency',
  apiKey: 'ffc36feb6e9df06e1c3c4549b5af2b31',
  indexName: 'starbucks',
});

var configure = instantsearch.widgets.configure({
  hitsPerPage: 25,
});

var searchBox = placesInstantsearchWidget({
  appId: '<YOUR_PLACES_APP_ID>',
  apiKey: '<YOUR_PLACES_API_KEY>',
  container: document.querySelector('#input-map-instantsearch')
});

var geosearch = instantsearch.widgets.geoSearch({
  container: '#map-instantsearch-container',
  googleReference: window.google,
  builtInMarker: {
    createOptions: function(item) {
      return {
        title: item.Brand + ' ' + item.Name
      };
    }
  }
});

search.addWidget(configure);
search.addWidget(searchBox);
search.addWidget(geosearch);
search.start();
})();
</script>

Concepts: type parameter, aroundLatLngViaIP, aroundPrecision, Places API Client.

In this section, we will see how you can do emulate reverse geocoding to find city closest to a user using the Places API Client.

When constructing a search UI using InstantSearch, you may want to filter your dataset to only display some of your products based on the distance to the end user. This is usually done using the aroundLatLngViaIP filter, or aroundLatLng filter if you have access to precise geolocation information. However geographical filters are hard to interpret when displayed in raw format, as noone really knows where the coordinates 48.8566, 2.34287 are.

Using Places, you can do a query to find the city in which your user is located and display that city name instead of a geolocation.

Example:

Searching around:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<div>
  <strong>Searching around: </strong>
  <div id="reverse-city"></div>
</div>

<script src="https://cdn.jsdelivr.net/algoliasearch/3/algoliasearch.min.js"></script>
<script>
const placesClient = algoliasearch.initPlaces(
  '<YOUR_PLACES_APP_ID>',
  '<YOUR_PLACES_API_KEY>'
);

const preferredLanguage = window.navigator.language.split('-')[0];

placesClient.search({
  query: '',
  aroundLatLngViaIP: true,
  type: 'city',
  hitsPerPage: 1,
  language: preferredLanguage,
}).then(({ hits }) => {
  const { locale_names, country }= hits[0];
  const formattedCity = `${locale_names[0]}, ${country}`;

  const infoElt = document.querySelector("#reverse-city");
  infoElt.textContent = formattedCity;
});
</script>

Note: This example uses the standard JavaScript API client in which the Places API is integrated. This example does not use the Places.js library.

Note: This is not a reverse geocoding API, and it is still bound by the ranking used for search, which means that in some cases large close-by cities can be promoted over a more closely located city.

Using _rankingInfo

Concepts: _rankingInfo, query strategy, geolocation precision.

In this section, we will see how we can leverage _rankingInfo to provide additional information to our user about certain behaviours of the query strategy.

As with any Algolia query, _rankingInfo will include information about how the ranking was computed based on the words, exact, filters, nbTypos, etc.
However, Places also enriches the _rankingInfo with additional information based on which part of the query strategy was used to return this result.
Finally, in countries where Places supports house level precision queries, it also provides some data on how precise the geolocation resolution was.

As such, on top of the regular _rankingInfo fields, Places exposes the two following:

  • query
  • roadNumberPrecision
query

The query field of the _rankingInfo object is a reference to which query strategy was used to find this result while executing the Algolia Places Query.
You can read more about the Algolia Places query strategy here.

It can take 3 values depending of the strategy used to find this record:

  • worldwide_query: Places searched the entire world to find this record.
  • local_query: Places searched the country of the user to find this record.
  • geo_query: Places searched around the user to find this record.

You can leverage this attribute to better understand how your constraints impact the relevance of your search.

Note: This is a very advanced feature, which will be irrelevant to most users.

Note: All searches that Places does internally still take into account the filters that you applied, so if you restrict a query to only a few countries, a worldwide query will only look into these few countries.

roadNumberPrecision

The roadNumberPrecision field of the _rankingInfo object provides additional information on how precise the geolocation of the record is.

By default, Places only offers precision up to the street level, which means that all the house numbers of a street will have the same geolocation. However, Places offers house level precision in France, if you are part of the opt-in beta for this program.
In this case, the roadNumberPrecision field will return either:

  • exact: the house number passed in the query was found exactly in the data
  • closest: interpolate the position based on known datapoints
  • centroid: computed center of mass of the geometric area
Demo

In this demo, we will visually show which query strategy was used to find each suggestion (on the left of the suggestion), as well as show the geolocation precision of results on the right.
Remember that house level precision is only available in France, so here are some example queries that you can try to see how things work under the hood:

  • L (or any first letter that is not the first letter of a large city that is close to you)
  • 55 rue d'Amsterdam, Paris (look at how there are 2 records and only one has the exact house number - 3 circles vs 2 circles)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
<input type="search" id="ranking-info" class="form-control" placeholder="Where are we going?" />

<script src="https://cdn.jsdelivr.net/npm/places.js@1.13.0"></script>
<style>
  .suggestion {
    display: flex;
    flex-direction: row;
    box-sizing: border-box;
    align-items: center;
    width: 100%;
    padding-right: 18px;
  }

  .suggestion-icon {
    width: 20px;
    height: 20px;
    margin-right: 8px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    opacity: 0.2;
  }

  .suggestion-name {
    margin-left: 4px;
    margin-right: 4px;
    flex-grow: 0;
    flex-shrink: 0;
  }

  .suggestion-address {
    flex-grow: 1;
    flex-shrink: 1;
    margin-right: 8px;
    font-size: 0.8em;
    color: rgba(74, 74, 76, 0.5);
  }

  .suggestion-precision {
    width: 20px;
    height: 20px;
    margin-left: 8px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    animation: none;
  }
</style>
<script>
(function() {
  const formatIcon = (query) => {
    switch (query) {
      case "worldwide_query":
        // globe icon - result found while searching in the whole world (still respects filter restrictions)
        return `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"><path d="M10 0C4.48 0 0 4.48 0 10s4.48 10 10 10 10-4.48 10-10S15.52 0 10 0zM9 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L7 13v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H6V8h2c.55 0 1-.45 1-1V5h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/></svg>`
      case "local_query":
        // building icon - result found while searching in the country of the user (still respects filter restrictions)
        return `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"><path d="M12 .6L2.5 6.9h18.9L12 .6zM3.8 8.2c-.7 0-1.3.6-1.3 1.3v8.8L.3 22.1c-.2.3-.3.5-.3.6 0 .6.8.6 1.3.6h21.5c.4 0 1.3 0 1.3-.6 0-.2-.1-.3-.3-.6l-2.2-3.8V9.5c0-.7-.6-1.3-1.3-1.3H3.8zm2.5 2.5c.7 0 1.1.6 1.3 1.3v7.6H5.1V12c0-.7.5-1.3 1.2-1.3zm5.7 0c.7 0 1.3.6 1.3 1.3v7.6h-2.5V12c-.1-.7.5-1.3 1.2-1.3zm5.7 0c.7 0 1.3.6 1.3 1.3v7.6h-2.5V12c-.1-.7.5-1.3 1.2-1.3z"/></svg>`
      case "geo_query":
        // pin icon - result found while searching near location of the user (still respects filter restrictions)
        return `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 14 20"><path d="M7 0C3.13 0 0 3.13 0 7c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5C5.62 9.5 4.5 8.38 4.5 7S5.62 4.5 7 4.5 9.5 5.62 9.5 7 8.38 9.5 7 9.5z"/></svg>`
      default:
        return ``;
    }
  }

  const formatName = ({ name }) => name
  const formatAddress = ({ suburb, city, postcode, administrative, country }) => {
    return [ suburb, city, postcode, administrative, country ]
      .filter(v => !!v)
      .join(', ')
  }

  const formatPrecision = (type, roadNumberPrecision) => {
    if (type === 'city') {
      // 0 circle - city level precision
      return ``;
    }

    switch (roadNumberPrecision) {
      case "exact":
        // 3 circles - house level precision
        return `<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
  <circle r="12" cy="16" cx="16" fill="#ddd"/>
  <circle r="8" cy="16" cx="16" fill="#aaa"/>
  <circle r="4" cy="16" cx="16" fill="#000"/>
</svg>`;
      case "closest":
        // 2 circles - house number not found - returned geolocation of the closest house number.
        return `<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
  <circle r="12" cy="16" cx="16" fill="#ddd"/>
  <circle r="8" cy="16" cx="16" fill="#aaa"/>
</svg>`;
      default:
        // 1 circle - street level precision
        return `<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
  <circle r="12" cy="16" cx="16" fill="#ddd"/>
</svg>`;
    }
  }

  const formatSuggestion = ({ highlight, hit: { _rankingInfo }, type }) => `<div class="suggestion">
    <span class="suggestion-icon">${formatIcon(_rankingInfo.query)}</span>
    <span class="suggestion-name">${formatName(highlight)}</span>
    <span class="suggestion-address">${formatAddress(highlight)}</span>
    <span class="suggestion-precision">${formatPrecision(type, _rankingInfo.roadNumberPrecision)}</span>
  </div>`;

  const placesAutocomplete = places({
    appId: '<YOUR_PLACES_APP_ID>',
    apiKey: '<YOUR_PLACES_API_KEY>',
    getRankingInfo: true,
    container: document.querySelector('#ranking-info'),
    templates: {
      suggestion: formatSuggestion
    }
  });
})();
</script>