Skip to content

Instantly share code, notes, and snippets.

@RadGH
Last active July 19, 2023 19:16
Show Gist options
  • Star 20 You must be signed in to star a gist
  • Fork 10 You must be signed in to fork a gist
  • Save RadGH/428bd8133c34dae60c0c to your computer and use it in GitHub Desktop.
Save RadGH/428bd8133c34dae60c0c to your computer and use it in GitHub Desktop.
Get latitude and longitude for addresses saved in Advanced Custom Fields, using Google's GeoLocation API
<?php
global $acf_recalc_settings;
// IMPORTANT: Customize these settings for your website.
$acf_recalc_settings = array(
// How many updates to do each page load. As of November 2018, Google's GeoLocation API limit is 100 per second.
'posts_per_run' => 16,
// The post type which has the addresses (This code can be adapted for users or terms, but these settings are designed for Custom Post Types).
'post_types' => array( 'post' ),
// GeoLocation API Key is required as of November 2018. Get an api key here: https://developers.google.com/maps/documentation/geolocation/get-api-key
// More info from google here: http://g.co/dev/maps-no-account
'geolocation_api_key' => 'ENTER_YOUR_API_KEY',
// Custom field mapping. Put your field names or field keys as the values. If you do not use one, just leave the value blank.
'fields' => array(
// If you use a Google Map field to store the latitude/longitude:
'google_map' => 'field_5de9a6b17b438',
// Address field that will be used for Geo Location
'address' => 'field_5de9a2797a43c',
// Optional. If your address is split into multiple fields, list them here. You can leave these blank.
'address2' => 'field_5de9a9e3eccb5',
// Second line of an address usually for Apartment/Suite number
'city' => 'field_5de9a679cc0e4',
'state' => 'field_5de9a678cc0e3',
'zip' => 'field_5de9a678cc0e2',
'country' => 'field_5de9a9ddeccb4',
),
// Latitude and Longitude will be saved to the google map field, but you probably want them as separate meta keys as well if you ever need to sort by distance or that sort of thing.
// This should be compatible with update_post_meta(), do not use ACF Field Keys.
'latitude_key' => 'latitude',
'longitude_key' => 'longitude',
// Change this if you want to repeat the scan for items that were already updated.
'scan_identifier' => '20191205',
);
function recalc_acf_locations_init() {
if ( !isset( $_REQUEST['acf-recalc-locations'] ) ) return;
global $acf_recalc_settings;
$args = array(
'post_type' => $acf_recalc_settings['post_types'],
'meta_query' => array(
'relation' => 'OR',
array(
'key' => 'acf-recalculated-scan',
'compare' => 'NOT EXISTS',
'value' => ' ',
),
array(
'key' => 'acf-recalculated-scan',
'compare' => '!=',
'value' => $acf_recalc_settings['scan_identifier'],
),
),
'posts_per_page' => $acf_recalc_settings['posts_per_run'],
);
$locations = new WP_Query( $args );
if ( ! $locations->have_posts() ) {
wp_die( '<p><strong>ACF Recalc Locations:</strong> All locations have latitude/longitude present! All done!</p>' );
exit;
}
echo '<h2>Scanning ' . min( $locations->found_posts, $acf_recalc_settings['posts_per_run'] ) . ' of ' . $locations->found_posts . '...</h2>';
echo '<div style="font-size: 14px; font-family: Arial, sans-serif;">';
foreach( $locations->posts as $i => $p ) {
echo '<div style="width: 50%; float: left; overflow: auto;">';
$url = get_permalink( $p->ID );
$edit = get_edit_post_link( $p->ID );
echo '<p><strong>' . ( $i + 1 ) . ') ' . ucwords( str_replace( array(
'-',
'_',
), ' ', $p->post_type ) ) . ' #' . $p->ID . '</strong> - <a href="' . esc_attr( $url ) . '" target="_blank">' . esc_html( $p->post_title ) . '</a> (<a href="' . esc_attr( $edit ) . '" target="_blank">Edit</a>)</p>';
echo '<pre style="border-bottom: 1px solid #666; padding-bottom: 15px; margin: 15px 0;">';
recalc_acf_location_single( $p->ID );
echo '</pre>';
echo '</div>';
}
echo '</div>';
exit;
}
add_action( 'init', 'recalc_acf_locations_init' );
/**
* Collects the full address of a location, then retrieves the Latitude/Longitude
*
* @param $post_id
*
* @return bool
*/
function recalc_acf_location_single( $post_id ) {
global $acf_recalc_settings;
$location = get_field( $acf_recalc_settings['fields']['google_map'], $post_id );
$lookup_address = null;
$result = null;
$full_address = null;
// Get the address from custom fields that are set
$f = $acf_recalc_settings['fields'];
$address = $f['address'] ? get_field( $f['address'], $post_id ) : "";
$address2 = $f['address2'] ? get_field( $f['address2'], $post_id ) : "";
$city = $f['city'] ? get_field( $f['city'], $post_id ) : "";
$state = $f['state'] ? get_field( $f['state'], $post_id ) : "";
$zip = $f['zip'] ? get_field( $f['zip'], $post_id ) : "";
$country = $f['country'] ? get_field( $f['country'], $post_id ) : "";
// Combine all pieces above, and remove any that are empty using array_filter
$parts = array_filter( array(
$address,
$address2,
$city,
$state,
$zip,
$country,
) );
// Join the array with comma separation
$full_address = implode( ', ', $parts );
if ( empty( $full_address ) ) {
// Give an error if an address is invalid which halts the process so it can be fixed manually.
echo '<strong>ERROR:</strong> Location does not have a valid google map address, and no fallback address fields are given. Aborting operation, consider fixing this manually.';
exit;
}else{
// Show the full address that we determined
$map_url = add_query_arg( array( 'q' => rawurlencode( $full_address ) ), 'https://maps.google.com/' );
echo '<strong>Address</strong>: &ldquo;' . $full_address . '&rdquo; (<a href="' . esc_attr( $map_url ) . '" target="_blank">map it</a>)<br>';
}
// Get the latitude/longitude for that address, and save it to the post
$result = recalc_acf_location_lookup( $post_id, $full_address );
if ( $result ) {
if ( $result === true ) {
echo 'Location was already up to date.';
}else{
echo 'Location has been found and saved: ' . esc_html( $result['lat'] ) . ', ' . esc_html( $result['lng'] ) . '.';
return true;
}
}else{
echo '<h2>ERROR! Google map could not locate this address. Aborting operation.</h2>';
exit;
}
}
function recalc_acf_location_lookup( $post_id, $full_address ) {
global $acf_recalc_settings;
$address_one_line = preg_replace( '/ *(\r\n|\r|\n)+ */', " ", $full_address );
$coords = recalc_acf_get_latlng( $address_one_line );
if ( $coords ) {
$location = get_field( $acf_recalc_settings['fields']['google_map'], $post_id );
if ( !is_array( $location ) ) {
$location = array(
'address' => '',
'lat' => '',
'lng' => '',
);
}
if ( empty( $location['address'] ) ) {
$location['address'] = $address_one_line;
}else{
if ( $address_one_line === $location['address'] && $location['lat'] === $coords['lat'] && $location['lng'] === $coords['lng'] ) {
// Save the scan identiifer which will prevent repeating on the same post unless the identifier changes
update_post_meta( $post_id, 'acf-recalculated-scan', $acf_recalc_settings['scan_identifier'] );
return true;
}
}
$location['lat'] = $coords['lat'];
$location['lng'] = $coords['lng'];
$result = update_field( $acf_recalc_settings['fields']['google_map'], $location, $post_id );
if ( $result ) {
// Save lat/long as separate meta keys
update_post_meta( $post_id, $acf_recalc_settings['latitude_key'], $location['lat'] );
update_post_meta( $post_id, $acf_recalc_settings['longitude_key'], $location['lng'] );
// Save the scan identiifer which will prevent repeating on the same post unless the identifier changes
update_post_meta( $post_id, 'acf-recalculated-scan', $acf_recalc_settings['scan_identifier'] );
return $location;
}else{
// Give an error that requires manual attention.
echo '<h2>ERROR! Failed to locate map location for post ID #' . $post_id . '.</h2><p>Please review this entry manually to continue.</p>';
exit;
}
}
return false;
}
function recalc_acf_get_latlng( $address ) {
global $acf_recalc_settings;
// http://stackoverflow.com/a/8633623/470480
$address = urlencode( $address ); // Spaces as + signs
// Legacy API -- No longer supported
// $geolocation_url = "http://maps.google.com/maps/api/geocode/json?address=$address&sensor=false";
// November 2018 update with api key support and higher limits
$geolocation_url = "https://maps.googleapis.com/maps/api/geocode/json?address=$address&key=" . $acf_recalc_settings['geolocation_api_key'];
$request = wp_remote_get( $geolocation_url );
$json = wp_remote_retrieve_body( $request );
if ( !$json ) {
echo 'Google Maps returned an empty response';
return false;
}
$data = json_decode( $json );
if ( !$data ) {
echo '<h2>ERROR! Google Maps returned an invalid response, expected JSON data:</h2>';
echo esc_html( print_r( $json, true ) );
exit;
}
if ( isset( $data->{'error_message'} ) ) {
echo '<h2>ERROR! Google Maps API returned an error:</h2>';
echo '<strong>' . esc_html( $data->{'status'} ) . '</strong> ' . esc_html( $data->{'error_message'} ) . '<br>';
exit;
}
if ( empty( $data->{'results'}[0]->{'geometry'}->{'location'}->{'lat'} ) || empty( $data->{'results'}[0]->{'geometry'}->{'location'}->{'lng'} ) ) {
echo '<h2>ERROR! Latitude/Longitude could not be found:</h2>';
echo esc_html( print_r( $data, true ) );
exit;
}
$lat = $data->{'results'}[0]->{'geometry'}->{'location'}->{'lat'};
$lng = $data->{'results'}[0]->{'geometry'}->{'location'}->{'lng'};
// Value can be negative, so check for specifically 0.
if ( floatval( $lat ) === 0 || floatval( $lng ) === 0 ) {
echo '<h2>ERROR! Latitude/Longitude is invalid (exactly zero):</h2>';
var_dump( 'Latitude:', $lat );
var_dump( 'Longitude:', $lng );
var_dump( 'Result:', $data->{'results'}[0] );
exit;
}
return array(
'lat' => $lat,
'lng' => $lng,
);
}
function recalc_acf_clean_address( $address ) {
$address = preg_replace( '/ *(\r\n|\r|\n)+ */', " ", $address ); // No linebreaks
return $address;
}
@ktoombs-sm2dev
Copy link

@RadGH Thanks for the info. This project was put on hold temporarily but I'll look at slashing code since I'm using WP All Import. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment