Geolocation in WordPress

The explosion of geolocation services has made it relatively easy to do custom geolocation of just about any dataset you want.

In this post, I’ll walk through setting up a “Location” custom-post type in WordPress. We’ll calculate a latitude and longitude for each location, include a search function, and then display the resulting locations in a table and on a map.

Create latitude/longitude database table

We want the location data to be searchable, so we can show people the closest locations. In order to avoid WordPress’s complex metadata relationships and simplify the MYSQL calls, I want to put my latitude/longitude data into a separate table in the WordPress database. That way I can use WordPress’s database functions to access the table, but keep the queries themselves simple.

In your WordPress database, create a table named “lat_lng_post” and give it the following columns:

Create the custom post type

Next, let’s create a custom “Location” post type with address information. I’ve built this as a plugin so it can be easily added/removed to any website. If you’ve never created a plugin before, you can check out my post on the subject.

Most of the code is basic “custom-post-type” code. The things to notice are:

Metaboxes: We’re just asking for basic address info. The State input is a textbox just to keep things simple; you’ll probably want to turn that into a select dropdown. There are also read-only fields for the latitude/longitude, so users can see the values and check them if necessary.

Getting latitude/longitude: When the metabox data is saved, you’ll see the following code:

This gets the address info, puts it together into a URL-friendly string, and passes it to Google’s geocode service, which returns a JSON object for that location. We extract the latitude and longitude values and save them with the metadata for that location. We can now grab those values any time we want, without having to hit Google or parse JSON every time. They’re happy, we’re happy. Win-win!

This might be obvious, but if you enter a street address, Google Maps can only provide a location if that address is valid. If you give it a bad location, you’ll get a bad result. Double check that your location actually exists!

Also, you don’t need to provide a street address unless you want that level of detail. If all you need is a city/state, or a ZIP code, modify the $address string appropriately.

Get rid of the content editor: This custom post-type doesn’t have a need for a rich-text editor, so to simplify the interface, I’ve removed it:

Add search criteria to query_vars: In order to allow users to search on my custom data, I need to add the search terms to WordPress’ query_vars global variable.

Search box

Having created the Location post type, Let’s build a search-box widget that lets users enter their ZIP code and search for locations within a certain radius of them.

I like to use plugins to create widgets, so that I can add/remove them easily. So to create the search widget, create a new plugin and paste the following code into it:

You may have noticed that all this widget does is execute a shortcode called [‘findalocation’]. I set it up this way so a user could place the search form in a page if they wanted to. So let’s create the shortcode now.

Create search box shortcode

In functions.php, add the following code:

Again, pretty simple: all it does is display a PHP file called “findalocation.php“. That file is where the actual form resides. This lets you update the form without having to dig through your function and plugin files (and risking your whole website breaking because of a typo).

Create the search form

We’re now ready to create the search form. Create a file named “findalocation.php” and paste the following code into it:

It’s a straightforward HTML form, with a few tweaks:

ob_start() and _ob_get_clean(): Because WordPress’ shortcode function requires output to be returned as a variable, I use output-buffering to capture the form output, and then empty the buffer into the variable $form using ob_get_clean().

get_query_var: The widget checks to see if any of the values are present in the query_var global. If I include the box on the search-results page, it will retain the user’s search values, rather than requiring users to re-enter everything if they want to refine the search.

The hidden input field: This isn’t crucial, but I like to pass the name of the form to my form-parsing scripts, in case I want to use a single script to handle multiple forms, or handle common variables differently depending on the context.

Google Maps API Key

Because the search-results page is going to include a custom embedded Google Map, you need to get an API key from Google. It’s free and fairly easy; Google provides detailed instructions.

Search results page

All right, we now have our custom-post type, and a widget containing our search form. Now let’s add the search-results page. This page is set up so if a user comes to the page directly, they will see a table of all locations. If they come to the page through a search form, it will display both a table and a map.

Create a page called “location_results.php” and make sure the search form’s “action” property points to it. For the demo this is a regular PHP page, but you could also put this code inside a WordPress page template (or call it using a shortcode).

There’s a lot to see here, so let’s go through it piece by piece.

The first part of the code is explained in the comments. It checks to see if the page has been reached through a “search locations” submission. If so, it grabs the user’s ZIP code and chosen radius.

If the ZIP code is valid, it loads an embedded Google Map that we will later populate with data. It then calculates the latitude and longitude of the user’s ZIP code, using the same basic code we used to calculate the latitude/longitude of each location.

Next, we write the MYSQL query. If it’s a search, we do a custom query that takes the user’s location and chosen radius into account. Otherwise, we just grab all the locations:

In the search query, you’ll notice that we added a filter called “location_posts_where”. This function is shown and discussed in detail in the next section, but basically it modifies the query to add a WHERE clause that only returns locations within the given radius of the user’s location.

Once we have the list of returned locations, we start building the table.  We set up the table headers, and then use a loop to build each row. If a street address is provided, we automatically turn it into a Google Maps link. We could also have just grabbed the stored latitude/longitude for each location and used them to build the link:

If this is a search result, we add a column called “distance” that will show each location’s distance from the user’s location. We also add each location’s information to an array called “$locations” that will be used to populate the embedded map. Note that I’m able to put arbitrary HTML into $locations; this HTML is what will be shown in the pop-up window for each location:

Note that we have called a function called “distance” that calculates the distance between two points (in this case, the user’s ZIP code and the latitude/longitude of each location). That function is explained in detail in the next section.

With the table built, we now turn to populating the embedded map. First, we define a default location — in this case, the center of Minnesota. This code should never actually run, since further up I declared that if there is not a valid ZIP code, then no map is shown. I show it here in case you want to modify the code to show a map even if there is no search.

Next, we initialize the Google Maps script, and pass it the $locations array containing information on all the returned locations:

Next, we define a few variables. “infowindow” is the pop-up you get when you click on a marker in the embedded map; We’re going to fill it with custom content. “marker” is the marker itself; and “bounds” will be used to ensure the map displays all the returned locations.

Next, we loop through all the locations. For each one we place a marker, using the latitude/longitude values for that location. We also add the location’s position to “bounds”. This object uses Google’s LatLngBounds() class to calculate the smallest rectangle containing all the positions inside it. Every time we add a location to it, it adjusts the rectangle to include that location.

Next, we add an event listener to the marker so that when it’s clicked, an Info Window displays. Using Google’s InfoWindow() class, we populate the Info Window with the custom HTML from our locations array.

Finally, we reset the zoom level of the embedded map to include all of the returned locations:

 

Create latitude/longitude calculation functions

As noted above, the search-results page uses a couple of functions to calculate the distance between two points. Add these functions to functions.php:

The first function modifies the WordPress query that is looking for locations. It adds a WHERE clause, so that it only returns locations within the chosen radius of the user.

The second function calculates the distance between each returned location and the user’s ZIP code, so that we can display it in the results table. It’s a general function that can calculate the distance between any two points.

Summary

Phew! That was a lot of detail, but I hope it was worth it. Using the above code, we have done the following:

1. Created a custom post type called “Locations” that automatically calculates the latitude/longitude of each location.

2. Created a search form that can be rendered as either a widget or a shortcode;

3. Created a results page that displays a table of locations;

4. If a user supplies a ZIP code, the results page also returns an embedded map showing all locations within the chosen distance from the user. Clicking on a location shows a popup containing that location’s name and address.

  • See more at: http://www.avtex.com/blog/2014/04/28/geolocation-in-wordpress/#sthash.4jAeGiKX.dpuf

댓글 남기기