Create a Proximity Search in WordPress
In this tutorial we are going to build a store locator using a proximity search. Below is the final result.
1. Register for Google Maps Platform
In order to run a proximity search, as well as use the Advanced Custom Fields Google Map, you will need to sign up for Google Maps Platform. Google has recently changed their mapping API, but at the time of this writing, it costs $5.00 USD per 1000 requests.
- Go to Google Maps Platform
- Click Get Started
- When the modal opens, select Places and then click Continue
Click to expand
- On the next screen enter a new project name and click Next. I named my project proximity-search-demo
Click to expand
- You will be prompted to create a billing account.
Click to expand
- Once you add your billing information, confirm that you have the Geocoding API enabled for your project
Click to expand
- Navigate to the Credentionals page, and copy your API Key
Click to expand
- Make sure you restrict your API key
Click to expand
- Test that your key is working by navigating to https://maps.googleapis.com/maps/api/geocode/json?address=1600+Amphitheatre+Parkway,+Mountain+View,+CA&key=YOUR_API_KEY. You should get a successful JSON response back.
2. Create a Custom Post Type (Optional)
Now that we have our API Key, we need to create a custom post type to search against. If you already have built a custom post type, you can skip this section.
I will be using WP-CLI to generate the custom post type. I strongly recommend you install the cli and use it for your daily WordPress development.
- Create a mu-plugins directory within the wp-content directory
- Creating a mu-plugins directory ensures any code in this directory will be loaded. This is more effective than adding code to your theme’s functions.php file, since switching themes would remove the custom post type.
- Withing the mu-plugins directory, create a custom-post-types.php file
-
In a terminal window, run
wp scaffold post-type store --dashicon=dashicons-store | pbcopy
.- The
| pbcopy
command will copy the generated code. Note that you won’t see any output in your terminal. - Paste the code into custom-post-types.php
- The
- Navigate to /wp-admin/options-permalink.php and save your permalinks. This is needed to flush the permalink structure and ensure the new post type will have an archive page
You should now see your new Store post type in the admin menu
3. Install and Configure Advanced Custom Fields
We need a way to store geocoded data to our post type. This will be necessary when calculating the proximity between a store and the origin. Luckily Advanced Custom Fields provides an address field.
- Install and activate Advanced Custom Fields
-
Navigate to /wp-admin/post-new.php?post_type=acf-field-group and add a new field group
- Name the field group Address
- Add a new field
- Set the Field Label to Address
- Set the Field Name to address
- Set the Field Type to Google Map
- Set Required to Yes
- Under Location configure the following rule
- Show this field group if Post Type is equal to Store (or your custom post type)
- Click Publish
However, you’ll notice that when you add a new post the map is broken.
In order to fix this, we need to use our API Key we created in step 1.7 According to the ACF Docs we need to add the following code to your site’s functions.php.
if using ACF PRO, you may find it easier to update the ‘google_api_key’ setting instead:
However, I recommend you create an include file to keep the functions.php file organized.
- Create a inc directory in your theme’s directory
- Create a acf-google-map-api.php file within the inc directory
- Add one of the code snippets to this file acf-google-map-api.php
- Include acf-google-map-api.php at the bottom of your theme’s functions.php file
Now if you navigate back to you post type, you’ll notice the map works.
4. Create Functions to Calculate Proximity
Now that we have a post type capable of storing a geoceded address, we can create functions that will calculate the proximity between that address and an origin provided by the user.
I want to give full credit to Brian Johnson’s Article. His article is the basis for everything in the remaining sections.
- Create a proximity-search.php file within the inc directory in your theme’s folder.
-
Include proximity-search.php at the bottom of your theme’s functions.php file
-
Create a function to return the latitude and longitude from a location
This function takes a location (address, zip code, etc), sends it to Google’s Geocoding API. The API will then return a whole lot of JSON. We already did this manually in step 1.9. The only difference is that this function will only return the latitude and longitude.
You can manually test the function is working by placing the following code in your index.php file.
- Create a function that calculates the distance between two locations.
This function depends on the YOUR_THEME_NAME_get_lat_and_lng function we just created.
You can manually test the function is working by placing the following code in your index.php file.
The code worked! I can confirm that Worcester, MA is 40 miles from Quincy, MA
Your proximity-search.php should now look like this:
5. Create a Proximity Search Form
We have everything we need to run a proximity search on our custom post type. Now we just need to create a form and display the results
- Create sample data with a variety of locations. Add a few that are close together.
-
Create a custom page template to display the form and results. I named mine template-proximity-search.php
-
Add the form markup to the page template.
- The value for each field is dynamically rendered based on the parameters in the URL. This means that the values will persist. For example, if I navigate to my search page and append the url with /?proximity=100&units=Miles, the proximity input will have 100 as a value
- Pay special attention to the value of the units select list. One option is set to K because that is a value the YOUR_THEME_NAME_get_distance function can calculate (
if ($unit == "K")
). If no value is set, or if the value is not K or N, YOUR_THEME_NAME_get_distance will just calculate in miles. -
The form action is set to
<?php echo get_permalink(); ?>
to ensure get request is made to the current page. - The form reset is actually a link, not an input. I’ve set it to
<?php echo get_permalink(); ?>
, which will clear the form values by reloading the page.
You should see something like this on your custom page template.
If you fill out the form with data, the page should refresh and the url should contain new query parameters your-slug/?proximity=10&units=Miles&origin=Quincy%2C+MA
6. Determine if a Post is Within the User’s Proximity
Now that we have a working form, we need to loop through each post and determine if it’s within the proximity of the user’s location.
-
Add the following after the form we just created in step 5.3. This will save the proximity, origin and units parameters values into variables which we can use later.
-
Now we need to loop over each store, and determine if it’s within the proximity of the user’s search.
- We create an empty $results array to hold the ID of each store that is within the proximity.
- Then, we loop over each store, and run YOUR_THEME_NAME_get_distance against it’s address.
- If the distance is less than the $proximity entered by the user, we add the store ID to the $results array
At this point, we still have not displayed anything to the user. In the next section we will take the data from the $results array and use that to display the results.
7. Display the Results
Now we need to craft the correct query in order to display the results.
-
We need to create an array of arguments to pass to a new WP_Query. The arguments will depend on if the user made a search, and if there were any results. Add the following code to the code from 6.2.
- If a search was made, and there are results in the $results array, we use the post__in parameter. This will limit our results to ones within our proximity.
- If a search was made, but there are no results in the $results array, we return an empty array. This will in turn yield no results.
- Finally, if no search was made we update the $results_args to display all results.
-
Now that we’ve created our $results_args, we can create a simple WordPress Loop. Add the following to the code from 7.1.
- If a user has made a search, we conditionally display a distance column.
- The get_field(‘address’) is a custom method the Advanced Custom Fields provides.
The final code should look like this: