Geo-Search Overview

Introduction

Geo search overview 1

In this tutorial, we will build several types of geo-search using all the geo-related search parameters you can use with Algolia: search in an area, search around a location, rank results by distance to a specific location…

We’ll take the example of a search of Airports, and use Google Maps to display the results of the Algolia search.

Importing Data

We’ve created an index containing more than 3000+ of the biggest airports in the world. To do the same, you can download this JSON file and use our web interface to upload it without writing a single line of code.

If you want to import your own data, you can have a look at our guide on importing data via the API.

[
 {
    objectID: "3797",
    name: "John F Kennedy Intl",
    city: "New York",
    country: "United States",
    iata_code: "JFK",
    _geoloc: {
      lat: 40.639751,
      lng: -73.778925
    },
    links_count: 911
  },
  // […]
]

As you can see, our records have 3 types of information:

  • Attributes you want to search in: name, city, country and iata_code. The IATA code is the airport code, like JFK or SFO.
  • A numerical value links_count, which is the number of other airports connected to that one. We will use it to rank the results
  • A couple of _geoloc.lat and _geoloc.lng attributes that gives the geographical position of the airport

We don’t need it in this demo, but it is possible to associate multiple locations to a record:


"_geoloc": [
  { "lat": 47.279430, "lng": 5.106450 },
  { "lat": 47.293228, "lng": 5.004570 },
  { "lat": 47.316669, "lng": 5.016670 }
]

Index configuration

Now that our index is created, we need to set some parameters to refine the search relevance.

Attributes To Index

We’re going to search in our 4 textual attributes: name, city, country and iata_code.

Custom Ranking

We use the number of other connected airports to any airport as a ranking metric.

Remove Words If No Results

To avoid being in a no-result situation, we’re going to set removeWordsIfNoResults = allOptional.

Algolia.init_index('demo-geosearch').set_settings({
  searchableAttributes: ['country', 'city', 'name', 'iata_code'],
  customRanking: ['desc(links_count)'],
  removeWordsIfNoResults: "allOptional"
})
<?php
$client->initIndex("demo-geosearch")->setSettings(array(
  "searchableAttributes" => array('country', 'city', 'name', 'iata_code'),
  "customRanking" => array('desc(links_count)'),
  "removeWordsIfNoResults" => 'allOptional')
);
client.initIndex('demo-geosearch').setSettings({
  "searchableAttributes":['country', 'city', 'name', 'iata_code'],
  "customRanking":['desc(links_count)'],
  "removeWordsIfNoResults":"allOptional",
});
client.init_index('demo-geosearch').set_settings({
  "searchableAttributes":['country', 'city', 'name', 'iata_code'],
  "customRanking":['desc(links_count)'],
  "removeWordsIfNoResults":"allOptional",
})
client.initIndex('movies').setSettings(
	new IndexSettings()
		.setsearchableAttributes(Arrays.asList("country", "city", "name", "iata_code"))
		.setCustomRanking(Arrays.asList("desc(links_count)"))
		.setRemoveWordsIfNoResults("allOptional")
	);
val result: Future[Task] = client.execute {
  changeSettings of "myIndex" `with` IndexSettings(
    searchableAttributes = Some(Seq(SearchableAttributes.attributes("title_eng", "title_fr", "title_es"))),
    customRanking = Some(Seq(
      CustomRanking.desc("number_of_likes")
    )),
    removeWordsIfNoResults = Some("allOptional")
  )
}
res, err := client.InitIndex("demo-geosearch").SetSettings(algoliasearch.Map{
	"searchableAttributes":   []string{"country", "city", "name", "iata_code"},
	"customRanking":          []string{"desc(links_count)"},
	"removeWordsIfNoResults": "allOptional",
})

To sort the results by distance, it is necessary to have the default geo attribute in your Ranking Formula.

Layout and initialization.

For our UI, we’ll use jQuery, Google Maps (to display the results) and the AlgoliaSearch JavaScript Helper.

Dependencies

<script src="https://cdn.jsdelivr.net/jquery/2.1.4/jquery.min.js">
<script src="https://cdn.jsdelivr.net/algoliasearch/3/algoliasearch.min.js">
<script src="https://cdn.jsdelivr.net/algoliasearch.helper/2/algoliasearch.helper.min.js">
<script src="//maps.googleapis.com/maps/api/js?key=YOUR_GOOGLE_MAPS_API_KEY">
<script src="js/app.js">

HTML

For our HTML markup, we only need a div to display the Google Map:

<div id="map" style="width: 600px; height: 600px"></div>

JS Initialization

The JavaScript initialization includes

  • Your search parameters: APPLICATION_ID, SEARCH_ONLY_API_KEY, INDEX_NAME and PARAMS. Please use a Search Only API key, which is shareable publicly.

  • The algolia client and the algoliaHelper initialization.

  • The map initialization.

// INITIALIZATION
// ==============
var APPLICATION_ID = 'latency';
var SEARCH_ONLY_API_KEY = '6be0576ff61c053d5f9a3225e2a90f76';
var INDEX_NAME = 'demo-geosearch';
var PARAMS = { hitsPerPage: 60 };
// Client + Helper initialization
var algolia = algoliasearch(APPLICATION_ID, SEARCH_ONLY_API_KEY);
var algoliaHelper = algoliasearchHelper(algolia, INDEX_NAME, PARAMS);
// Map initialization
var map = new google.maps.Map($('#map'), { streetViewControl: false, mapTypeControl: false, zoom: 4, minZoom: 3, maxZoom: 12 });

Displaying the results on the map

var markers = [];
var fitMapToMarkersAutomatically = true;
algoliaHelper.on('result', function(content, state) {
  var i;
  // Add the markers to the map
  for (i = 0; i < content.hits.length; ++i) {
    var hit = content.hits[i];
    var marker = new google.maps.Marker({
      position: {lat: hit._geoloc.lat, lng: hit._geoloc.lng},
      map: map
    });
    markers.push(marker);
  }
  // Automatically fit the map zoom and position to see the markers
  if (fitMapToMarkersAutomatically) {
    var mapBounds = new google.maps.LatLngBounds();
    for (i = 0; i < markers.length; i++) {
      mapBounds.extend(markers[i].getPosition());
    }
    map.fitBounds(mapBounds);
  }
});

Filter inside an area

If you want to restrict the results to a given area, you can provide a bounding shape in your search parameters. All results outside of this shape will be excluded from the results.

This feature only filters the records and doesn’t have any impact on the ranking of results.   However, it cannot be used along with aroundLatLng/aroundLatLngViaIP. Those parameters will be ignored when using a bounding box.

insideBoundingBox

The setting insideBoundingBox filters the results inside the area defined by the two extreme points of a rectangle.

Geo search overview 2


// p1Lat, p1Lng, p2Lat, p2Lng
algoliaHelper.setQueryParameter("insideBoundingBox", "60, 16, 40, -4").search();

insidePolygon

The setting insidePolygon filters the results inside the area defined by a shape.

Geo search overview 3

// p1Lat, p1Lng, p2Lat, p2Lng, p3Lat, p3Lng...
algoliaHelper.setQueryParameter("insidePolygon", "42.01,-124.31,42,-120.00,39.01,-120.00,35,-114.63,36.99,-114.03,36.99,-109.05,31.36,-109.05,31.36,-111.09,32.48,-114.88,32.75,-114.75,32.37,-121.19,40.09,-125.81,42.01,-125.94,42.01,-124.31");
algoliaHelper.search();

Filter and sort around a location

We learned how to filter the results inside a given area. Now, we’re going to find results near a specific location.

Even better, we’re going to rank those results by distance to that central point.

Geo search overview 4

The 60 nearest airports from the center of Paris.

This feature cannot be used in conjunction with any of the area filters described above.

Specify a location

By providing a latitude and longitude

The Lat/Lng coordinates provided will be used as the center, and Algolia will search for objects near this location.


algoliaHelper.setQueryParameter('aroundLatLng', '40.71, -74.01').search(); // Lat, Lng of New York City

By looking at the IP address

If you don’t have the Lat/Lng coordinates, we can rely on the IP address, that we’ll automatically associate to a location.

algoliaHelper.setQueryParameter('aroundLatLngViaIP', true).search();

If you’re doing the queries from the back-end, you can provide us the IP address via the extra (standard) header Algolia.set_extra_header(“X-Forwarded-For”, “IP OF THE USER”).

Limit the Radius

Automatic radius

By default, the radius in which Algolia will search around the Lat/Lng expands until approximatively 1000 hits are found. If the area is dense, the radius will be small. If it’s not dense, the radius will be bigger.

The radius used is returned in the JSON answer, under the attribute automaticRadius.

Around Radius

If you don’t want the radius to expand automatically, you can set a fixed radius. Algolia will look for all of the objects inside this radius exclusively.

algoliaHelper.setQueryParameter('aroundRadius', 5000).search(); // 5km Radius

Minimum Around Radius

When using the automatic radius feature, you can specify a minimum radius, to ensure the search will cover at least this minimum area.

algoliaHelper.setQueryParameter('minimumAroundRadius', 2500).search(); // 2.5km Minimum Radius

Sorting by distance

When searching around a location, you can sort the results by distance: the closer an object is to the Lat/Lng you provided, the higher it will be in the results.

You can only sort results by increasing distance

Ranking Formula

The sorting by distance happens in the criterion geo of the ranking formula. If you don’t have this criterion active, you cannot sort by distance.

# Set the default Ranking formula
Algolia.init_index('demo-geosearch').set_settings({ ranking: ['typo', 'geo', 'words', 'attribute', 'proximity', 'exact', 'custom'] })
<?php
// Set the default Ranking formula
$client->initIndex("demo-geosearch")->setSettings("ranking" => array('typo', 'geo', 'words', 'attribute', 'proximity', 'exact', 'custom'));
// Set the default Ranking formula
client.initIndex('demo-geosearch').setSettings({ ranking: ['typo', 'geo', 'words', 'attribute', 'proximity', 'exact', 'custom'] });
# Set the default Ranking formula
client.init_index('demo-geosearch').set_settings({ ranking: ['typo', 'geo', 'words', 'attribute', 'proximity', 'exact', 'custom'] })
client.initIndex('movies').setSettings(new IndexSettings().setRanking(Arrays.asList("typo", "geo", "words", "attribute", "proximity", "exact", "custom")));
val result: Future[Task] = client.execute {
  changeSettings of "myIndex" `with` IndexSettings(
    ranking = Some(Seq(
    	Ranking.typo,
    	Ranking.geo,
    	Ranking.words,
    	Ranking.attribute,
    	Ranking.proximity,
    	Ranking.exact,
    	Ranking.custom
    ))
  )
}
res, err := client.InitIndex("demo-geosearch").SetSettings(algoliasearch.Map{
	"ranking": []string{"typo", "geo", "words", "attribute", "proximity", "exact", "custom"},
})

Around Precision

This parameter defines the precision of the sorting: results are sorted by ranges of distance whose size is equal to aroundPrecision.

If you set aroundPrecision to 200, you’ll have the following search results:

  1. Group of results in the 0-200 meters range will be the best ranked

  2. Group of results in the 200-400 meters range will be ranked a bit lower

  3. Group of results in the 400-600 meters range will be ranked even lower

  4. […]

algoliaHelper.setQueryParameter('aroundPrecision', 200); // Rank results by ranges of 200 meters

The results are ranked by ranges. That means there’s no difference between an object being 1 meter away or 199 meters.

Display the distance

Our engine calculates the distance of each result to the location specified. To retrieve this value, you need to set the parameter getRankingInfo:

algoliaHelper.setQueryParameter('getRankingInfo', true);
console.log(hit._rankingInfo.matchedGeoLocation.distance); // Distance of the hit in meters