Find results near a specified location

FacetWP Proximity facet - filter by distance to locationThe Proximity facet lets you find results near a specified location, entered manually, or by clicking the “Locate me” icon.

After entering a location, a radius can be chosen from a pre-defined list, with a dropdown or slider. Results within that radius from the set location are then displayed.

How it works

After entering a location, FacetWP uses Google to grab the latitude, longitude of that location’s central point. Results within the user-defined radius of that point are displayed.

Proximity facet radius and countries / regions
A Proximity search for the region “Lombardy, Italy” (with the red border and blue marker at its geographical center). When the radius is set to 40km, it does not return any results in its capital Milan.

It does not understand geographic / political boundaries. If you search for a country (e.g. “Italy”), the facet will return results extending from that country’s geographical center point (as defined by Google), which may include results from other countries if the chosen radius is large enough.

Be aware that this behavior is possibly confusing for users. For example, if a user searches for the region Lombardy (Italy), they may expect to see results within the whole region (including its capital Milan). They may even expect results with a radius extending from the border of the region, not the center point. The Lombardy region is about 220km from west to east. So if a user searches it with a radius set to 40km, the facet will only display results within a circle of 80km diameter around Lombardy’s center point, which covers only a small part of the region, and does not include its capital Milan.

Keep this behavior in mind when choosing radius settings for your situation. If you expect users to often search for regions or even countries, it makes sense to offer higher radius settings that cover whole regions or countries. Also, setting the default radius to a higher value can make the initial results more intuitively correct.

Another approach would be to limit what “place types” are returned by the autocomplete results, and exclude regions and countries. And/or to limit results to a specific country.

Combine a Proximity facet with a Map facet

If you use a Proximity facet on its own, it will return results within the chosen radius from the specified location. But there is no map for the user to interact with, nor any other feedback that includes distances.

You can show the resulting posts’ distances to the specified location, and/or let users sort results by distance. But to give users visual feedback, a Proximity facet can very well be combined with a Map facet.

When a location and radius are set (or changed) with the Proximity facet, the map will automatically zoom in or out, showing the locations that are within the radius of the set location. It will also display a marker pin at the set location (which can be customized or disabled).

Available options

Name Description
Data source The Data source should be a custom field containing a comma-separated latitude, longitude, or a latitude only, if you use a separate custom field for the longitude.
Longitude Choose a custom field to use for the longitude value, if stored separately from the latitude (optional). See below for more info.
Unit of measurement Miles or Kilometers
Radius UI The UI element to use for the radius selector (Dropdown, Slider or None)
Radius options A comma-separated list of radius choices. The values need to be integers. Default: “10, 25, 50, 100, 250”
Default radius The default radius to be shown in the radius selector. Default: 25

Data source

The Data source should be a custom field containing a comma-separated latitude, longitude.

You can also use separate custom fields for the latitude and longitude, see the available options.

Other data sources

The following plugins and themes offer dedicated custom fields for latitude and longitude that can be used as data source for the Proximity facet. Click the links for specific instructions on using these fields:

Google Maps API key

Generate a Google Maps API key

The Proximity facet requires a valid Google Maps API key. To generate a key, you have to log into Google Cloud Console, set up a project, add an API key under API's & Services > Credentials, and enable the following three API services for the key:

  1. Maps JavaScript API: required for the Proximity / Map facets to work
  2. Geocoding API: required for the Proximity facet’s “Locate me” button
  3. Places API: required for the Proximity facet’s autocomplete box

To be able to use the key, you also need to set up a Billing Account in Google Cloud Console, and add your project to it. You will need a creditcard to set up billing. There is a free monthly credit of $200, which amounts to 28000+ map loads per month.

Add the Google Maps API key to FacetWP

After generating an API key, add it to: Settings > FacetWP > Settings > Google Maps API key.
Both the Proximity facet and the Map facet use this key.

Add the Google Maps API key to Advanced Custom Fields

If you are using the Advanced Custom Fields Google Map field as the data source, make sure you pass the same Google Maps API key into ACF. ACF needs the Google Maps API in the backend to geolocate the address entered for each post.

Add the Google Maps API key to Listify, Listable, WP Job Manager

If you are using Listify theme, Listable theme and/or the WP Job Manager plugin, make sure to also add the Google Maps API key to:
(Job) Listings > Settings > Google Maps API Key

The Listify theme itself has a second settings to enter the Google Maps API key, at:
Appearance > Customize > Listings > Map settings

Accuracy of the “Locate me” button

When the “Locate me” button is pressed, FacetWP attempts to determine the user’s location. FacetWP first makes a browser Geolocation API request, which returns an approximate latitude/longitude (based on the user’s IP address).

Then FacetWP performs a reverse lookup to determine the human-friendly physical address. This not an exact science, and occasionally the location can be off by several kilometers.

Limit autocomplete results to a specific country

It is possible to limit the autocomplete results to one or more countries with the “componentRestrictions” option. You can specify a single country, or an array of up to five country code strings. The country code needs to be a two-letter ISO 3166-1 Alpha-2 country code and is case insensitive.

This code example forces the autocomplete to show only choices in Germany (country code = de):

add_filter( 'facetwp_proximity_autocomplete_options', function( $options ) {
    $options['componentRestrictions'] = [
        'country' => ['de'],
    ];
    return $options;
});

Limit autocomplete results to specific place types

It is possible to limit the autocomplete results to certain “place types”, like countries, regions, cities, municipalities or postal codes. A list of available place types can be found in the Places API reference.

This code example limits autocomplete results to cities only:

add_filter( 'facetwp_proximity_autocomplete_options', function( $options ) {
    $options['types'] = ['(cities)'];
    return $options;
});

Bias autocomplete results towards a specific area

It is possible to bias the autocomplete prediction results towards a specific area by setting a location with lat/lng coordinates, and defining a “bounding box” surrounding that location.

The following example sets New York City as location and defines a bounding box with sides of approximately 10km away from the specified center.

The results will biased towards, but not restricted to to places within the bounding box. Unfortunately, restricting the results is currently not possible with the available options the Google Places API provides (but it could very well become possible in the future).

You can test this example code by entering “Amsterdam” in the Proximity facet’s field. The first prediction result will be a billiards club in New York with that name, and not the capital of the Netherlands, which would otherwise be the first.

add_filter( 'facetwp_proximity_autocomplete_options', function( $options ) {

  // New York City. Test results with "Amsterdam"
  $mylocation = [ 'lat' => 40.6974034, 'lng' => -74.1197615 ];

  // Create a bounding box with sides ~10km away from the location point
  $myBounds = [
    'north' => $mylocation['lat'] + 0.1,
    'south' => $mylocation['lat'] - 0.1,
    'east'  => $mylocation['lng'] + 0.1,
    'west'  => $mylocation['lng'] - 0.1,
  ];

  $options['bounds'] = $myBounds;

  return $options;
} );

Change the minimum character length and delay

This code example limits the minimum characters to 3 and sets the delay to 250 ms:

add_filter( 'facetwp_assets', function( $assets ) {
    FWP()->display->json['proximity']['queryDelay'] = 250; // default = 250 milliseconds
    FWP()->display->json['proximity']['minLength'] = 3; // default = 3 characters
    return $assets;
} );

Enable and show post distance

When using the proximity facet, it’s possible to show each post’s distance from the specified location.

Storing each post’s distance requires a little extra memory, so it’s disabled by default. To enable it, add the following to your theme’s functions.php:

add_filter( 'facetwp_proximity_store_distance', '__return_true' );

Next, use the facetwp_get_distance() function to display the distance for each post. It returns the numeric distance if the proximity facet is in use, or FALSE.

$distance = facetwp_get_distance();
if ( false !== $distance ) {
    echo round( $distance, 2 );
}

Sort results by distance

When a Proximity facet is in use as a filter (with a location entered), the post IDs are automatically re-sorted in the post__in argument of the query, from nearest to furthest. (See below for how to prevent that).

This makes it possible to sort results by distance with the following code. This order is retained until a Sort facet is also used, which then overrules the sort order. Make sure to replace yoursortfacet with the name of your Sort facet:

add_action( 'pre_get_posts', function( $query ) {
  if ( ! class_exists( 'FacetWP_Helper' ) ) {
    return;
  }

  $facets_in_use = FWP()->facet->facets;
  $prefix        = FWP()->helper->get_setting( 'prefix' );

  // replace  'yoursortfacet' with the name of your sort facet
  $using_sort    = isset( FWP()->facet->http_params['get'][ $prefix . 'yoursortfacet' ] ); 

  $is_main_query = false;
  if ( is_array( FWP()->facet->template ) ) {
    if ( 'wp' != FWP()->facet->template['name'] || true === $query->get( 'facetwp' ) ) {
      $is_main_query = true;
    }
  }

  if ( ! empty( $facets_in_use ) && ! $using_sort && $is_main_query ) {
    foreach ( $facets_in_use as $f ) {
      if ( 'proximity' == $f['type'] && ! empty( $f['selected_values'] ) ) {
        $query->set( 'orderby', array( 'post__in' => 'ASC' ) ); // or 'DESC'
      }
    }
  }
}, 1000 );

Sort results by distance with a Sort facet

Sort by distance
Make sure sort options have unique names.

If you want to allow users to sort by distance as a sorting option in a Sort facet, create a new sort option and set its data source to post__in with ASC or DESC order.

If you make both a ASC and a DESC sort option, make sure each sort option has a unique technical name set.

Be aware that sorting by distance only works when a location has been entered in the Proximity facet.

Prevent ordering by distance of post__in argument

If you want to prevent the Proximity facet from re-ordering the post IDs within the post__in argument of the query, add the the following code to your (child) theme’s functions.php:

add_filter( 'facetwp_query_args', function ( $query_args ) {
	remove_filter( 'facetwp_filtered_post_ids', [ FWP()->helper->facet_types['proximity'], 'sort_by_distance' ] );
	return $query_args;
} );

See also