Geo-sorting: Using Device Geolocation to Sort by Distance

In this article we take a look at how location information can be used to sort a list of items on a webpage. This might be useful for local search results; for example ‘Restaurants near me’ type searches, or for sorting a predefined list of locations such as a company’s office locations in order of distance from the user.

Contextualising information based on location offers excellent usability benefits, particularly for mobile users where input is often more difficult. Who hasn’t felt the pain of scrolling through a long list of countries in a drop-down list for example? Such usability issues can make or break the mobile user-experience—users may not even realise that things have been reorganised for them based on their location, but they may well get frustrated when they aren’t!

In the example that follows, we’ll assume we have a list of points of interest (POIs) and that each POI has location information in the form of a latitude, longitude (latlon) pair. For this article we’re not concerned with where this list of POIs comes from, whether it’s the result of a local search, or a predefined list of locations.

We’ll also consider how we might handle low-end devices as well as high-end. For high-end devices, we’ll use the HTML5 Geolocation API to determine the user’s location, and to perform the sorting based on this data. For low-end devices that don’t support geolocation it would be easy to simply output an ‘unsupported’ or ‘upgrade your device message’ for functionality like this. However, we’ll go one better and consider how, with a little bit of server side help, we can provide a similar experience for such devices. View a live example right now, or continue reading to see how it works.

  
Location based sorting example: high-end (left) before location has been acquired—note the permissions dialog,
and low-end (right) after location has been acquired—note the manual entry. Click to view the demo.

Location sorting for high end devices

For this example, we have three international location items in our list, and without any information about the user’s location at this point, we have a default ordering of the list, as shown in the HTML below:

Next we turn to the Geolocation API to get some information about the user’s location. After the page has loaded, we call the Geolocation API, and pass it the name of a callback function, sortResults, which we use to sort the results list based on the user’s location:

(Note: error checking code has been ommitted here for brevity—see this article for more details about how to handle errors).

So, now we have two tasks:

  • to determine the distance from the user to each of the POIs, and
  • to sort the list of POIs based on these distances

To figure out the distance between the user and the POIs, we’ll turn to a very useful script provided by Chris Veness of Movable Type. It defines a simple JavaScript API around latitude/longitude spherical geodesy formulae, so that we can easily compute the distance between two latlon points.

To use this script, we need to define our POI latlons as LatLon objects as defined in Veness’ script. Then we’ll be able to apply the distanceTo and bearingTo functions to any pair of LatLon objects e.g.

So, back to our sortResults function. First of all we grab the user’s position from the geolocation API, and coerce it into a LatLon object:

Next we want to iterate over the list of POIs, and determine the distance from the user. To do this, we use the JavaScript array sort method, and pass it a custom comparator function that takes two of our location elements as arguments, and determines which is closer to the user.

We obtain the list of location elements like this:

Since querySelectorAll returns a NodeList object, we must first convert this to an array, so that we can sort it:

Now we can sort our array. We call the array sort method with inline custom comparator function:

The comparator function retrieves the latlon from our POIs. We can then determine the distance between each POI and the user, compare these distances, and return the result:

Finally, we can update the DOM with the distance-sorted list:

Don’t block while waiting!

So, why bother to render the list at the beginning if we are going to sort again it anyway? The reason for this is that we don’t want to block while waiting for the location permission to be granted by the user, or while we wait for the user’s device to get a location fix. If we do it this way, we can display something to the user right away, and if and when the user’s location is determined, then the presentation of the information can be improved.

The full code is listed below.

Geosorting for low-end devices

This is all fine when the Geolocation API is supported on capable devices. But what about the low-end devices that support neither JavaScript nor Geolocation sensors? One option would be to omit this functionality altogether, but adopting an easy things should be easy, hard things should be possible approach we can go one better and try to bridge the gap between the high-end and low-end experiences.

One option is the use of GeoIP techniques. GeoIP uses the device IP address to determine the approximate location of the user. It is reliable to country, and often city resolution, and so could be used for applications which don’t require finer granularity than country or city level, such as rearranging a list of countries in a drop-down list, to take an example we mentioned earlier.

However, if the application needs to make use of local information, such as restaurants near me type searches, then the usefulness of GeoIP techniques will be limited.

A simple but effective solution for low-end devices is to allow the user to manually enter their location. Yes, it’s a low-tech solution to the problem, but even a partial address can be geocoded accurately enough to be usable for local search.

To implement this, a form will be presented as a simple unobtrusive textbox, and if the user chooses to ignore it, then the webpage will still work with the default list ordering. When a user enters an address or location however, we’ll determine the latlon for the entered location using Google’s free Geocoding API, and then we’ll sort the list of locations as we did for high-end devices, but this time on the server. So, similar functionality will be available for the low-end user, but the user will have to work just a little bit harder for it.

We show the initial list of locations1 with default ordering to the low-end device on first page request. We also add the form and textbox so that the user can specify a location. So, the HTML for low-end will look something like:

We use PHP as the language for the server code in this is example, but equally any could be used. In the code above, we loop over an array of locations, our POIs, which looks like this:

We also add some rudimentary form processing to take the address entered by the user, geocode it using the Google API (see below), and then call our sort function to find the closest POIs:

As with the high-end device version, we need to sort our results based on the distance from the device to the user. We can write a similar custom comparison function as we did earlier, and then call the PHP usort function with this comparator:

Determine the distance between two latlon points

We use a similar algorithm as before to compute the distance between two latlon points, but this time on the server. Various different distance algorithms are possible, the following code uses the Haversine distance.

Geocode an address with the Google API

To determine the latlon based on the location specified by the user, we use the following function. Given a string representing the location by the user, the function makes a request the the Geocoding API, and is returned a JSON object containing, among other things, the latitude and longitude of the location:

Adding a Google Map to a location

For completeness, and to make the example a little more appealing, we’ll add a Google map for each of our POIs.

We modify our HTML POI list slightly so that it now contains an extra div into which we are going to load our map:

Now we iterate over our list of POIs, and this time we will use the Google Maps API to render a map centered of the latlon of each of the POIs into the divs we just defined. So, we set up our loop as before, but this time, for each POI, we define some mapOptions which includes the latlon as well as some other map settings (see the Google Maps API documentation for more details):

Now we’re ready to draw the map, making sure we have the right id for each of the map divs we defined earlier:

The full loop is given below:

Dynamic maps for high-end devices, static maps for low-end

One final improvement that we can make to this code is to serve up a static map image tile for low-end devices, rather than the more intensive and likely-to-make-a-low-end-device-implode dynamic JavaScript maps we just outlined. This can be achieved by using server side device detection to determine the capabilities of the device, and then outputting the appropriate map type, dynamic vs static.

Serving up a static map tile with the Google Maps API is almost trivial! We need simply request a static tile around a latlon coordinate as the src URL of an image tag. We can also specify other sensible options such as zoom level and tile size:

So the loop for our low-end device is changed to the following:

View the live demo now!

Conclusion

In this article we showed how location information can be used to change the sort order of data presented to a user. For high-end devices, we used the HTML5 Geolocation API to determine the user’s location. We also considered low-end devices that might not have location capabilities, and in this case, we allow the user to manually enter a location. In both cases, a default sort-order of the data was presented when no location information was available. In the high-end case, this is so that we are not blocking page rendering while waiting for location permission to be granted, or while waiting for a GPS fix for example. In the low-end case, the list order is updated if and when location information is provided by the user. But in either case, there is always some information that can be displayed right away, and this is refined later if possible.

Links and References


1. The example used in this article was chosen because the audience of mobiForge is an international one. If a local example was chosen, with the POIs all in the same city, then most visitors would not notice any difference in the ordering of the locations. By choosing international locations for the example, more visitors will see a change in the sort order when the user’s location has been acquired. This example could be solved using GeoIP for the low-end version, but the goal of the article was demonstrate a solution that would apply to local searches too.

Leave a Reply

Exclusive tips, how-tos, news and comment

Receive monthly updates on the world of mobile dev.

Other Afilias Products

Try the world’s leading device detection solution at
DeviceAtlas - Try the world’s leading mobile device detection solution

Create amazing web presences on any screen with
goMobi - Create amazing web presences on any screen.

Evaluate your websites’ mobile readiness with
mobiReady - Evaluate your websites’ mobile readiness.

© 2017 Afilias Technologies Ltd. All rights reserved.

This is a website of Afilias Technologies Ltd, a private company limited by shares, incorporated and registered in the Republic of Ireland with registered number 398040 and registered office at 6th Floor, 2 Grand Canal Square, Dublin 2, Ireland