Inheriting a legacy codebase someone else wrote is always an interesting experience. Especially if the previous developer is no longer with the company. As a freelance consultant, a lot of my time is spent reviewing other people’s code and helping optimize and modernize it when I can.
With Google’s announcement last December about their pricing restructure for their Maps API, a client was going to see a monthly price increase by thousands of dollars because of the way their site was using Google Maps.
Across their site, hundreds of pages show a travel itinerary, usually accompanied by a static map with the destination, parking options, and activity. The map makes a new request to Google’s API every page load. Now, this is fine… but the map rarely needs to change. The map also isn’t interactive; it uses the static map API to serve an image. There’s no reason to make a new call for every user.
So what can we do? WordPress makes it incredibly easy to upload images. Why not upload the static image after it’s generated from the API and serve that to an end user instead?
But first, let's take a look at the original code.
<?php
$location_post_id = $location->ID;
$map_data = get_field('attraction_map', $location_post_id);
$location_title = get_the_title($location_post_id);
$location_stitle = get_field('location_subtitle', $location_post_id);
//$location_address = get_field( 'address', $location_post_id );
if (have_rows('nearby_parking', $location_post_id)) {
while (have_rows('nearby_parking', $location_post_id)) {
the_row();
$parking_data['title'] = get_sub_field('title');
$parking_data['subtitle'] = get_sub_field('subtitle');
$parking_data['address'] = get_sub_field('parking_address');
$parking_data['map'] = get_sub_field('nearby_parking_map');
$parking_data['type'] = 'parking';
// Parking facility map data
if ($args['static'] === true) {
$parking_markers .= "&markers=icon:" . get_template_directory_uri() . "/images/maps/pin-32.png%7Cshadow:true%7C" . $parking_data['map']['lat'] . "," . $parking_data['map']['lng'];
} else {
// Deprecated
$parking_markers .= '<div class="marker" data-type="' . $parking_data['type'] . '" data-lat="' . $parking_data['map']['lat'] . '" data-lng="' . $parking_data['map']['lng'] . '" style="display:none;"><h5>' . $parking_data['title'] . '</h5>' . $parking_data['map']['address'] . '</div>';
}
}
}
$map_style = "background:url(https://maps.googleapis.com/maps/api/staticmap?size=670x400&zoom=14&maptype=roadmap&key=APIKEY&format=png&visual_refresh=true" .
"&markers=icon:" . get_template_directory_uri() . "/images/maps/pin.png%7Cshadow:true%7C" . $map_data['lat'] . "," . $map_data['lng'] .
$parking_markers . ") no-repeat center;background-size:cover;";
ob_start();
?>
<div id="map-<?php echo $location_post_id; ?>" class="map-container acf-map" data-lat="<?php echo $map_data['lat']; ?>" data-lng="<?php echo $map_data['lng']; ?>" data-zoom="15" style="<?php echo $map_style; ?>;position:relative;">
<a href="<?php echo $map_link[strtolower($location_title)]; ?>" target="_blank" style="position:absolute;top:0;bottom:0;left:0;right:0;"></a>
</div>
<div data-for="#map-<?php echo $location_post_id; ?>" class="address">
<div class="marker" data-type="location" data-lat="<?php echo $map_data['lat']; ?>" data-lng="<?php echo $map_data['lng']; ?>" style="display:none;">
<h5><?php echo $location_stitle ? $location_stitle : $location_title; ?></h5>
<p><?php echo $map_data['address']; ?></p>
</div>
</div>
The details are all controlled with ACF fields in the backend, allowing for easy address lookup, zooming, etc. We then pass along the longitude and latitude with our zoom level to Google. Before that though, we’ll first want to generate an MD5 hash so we can store each map image combination with a unique filename:
$cache_key = md5($location_post_id . $location_lat . $location_lng . $location_zoom . $parking_markers);
$map_cache_filename = 'location_parking_map_' . $cache_key . '.png';
$map_cache_file_path = wp_upload_dir()['basedir'] . '/maps/' . $map_cache_filename;
$map_cache_file_url = wp_upload_dir()['baseurl'] . '/maps/' . $map_cache_filename;
This way, if the lng/lat or anything in the map changes, we’ll force a new map request generation from Google's API.
Next, we check if the file exists. If it does, we simply load the image in the uploads directory and we’re done. If it's not found, we send a new request it from Google, save it, then load it:
if (!file_exists($map_cache_file_path)) {
// Construct Google Maps API request URL
$map_request = "https://maps.googleapis.com/maps/api/staticmap"
. "?size=670x400"
. "&scale=2" // Keeps high resolution
. "&zoom=" . $location_zoom
. "&maptype=roadmap"
. "&key=APIKEY"
. "&format=png"
. "&markers=icon: " . get_template_directory_uri() . " /images/maps/pin.png%7Cshadow:true%7C" . $location_lat . "," . $location_lng
. "&markers=" . $location_lat . "," . $location_lng // Default Google marker
. $parking_markers;
// Fetch and save the image locally
$image_data = file_get_contents($map_request);
if ($image_data) {
// Ensure the maps directory exists
$map_dir = wp_upload_dir()['basedir'] . '/maps/';
if (!is_dir($map_dir)) {
mkdir($map_dir, 0755, true);
}
file_put_contents($map_cache_file_path, $image_data);
}
}
And after we put it all together, we get something that looks like this:
$location_lat = $map_data['lat'];
$location_lng = $map_data['lng'];
$location_zoom = $map_data['zoom'] ?? 15;
// Generate a unique filename based on location ID, lat/lng, and parking markers
$cache_key = md5($location_post_id . $location_lat . $location_lng . $location_zoom . $parking_markers);
$map_cache_filename = 'location_parking_map_' . $cache_key . '.png';
$map_cache_file_path = wp_upload_dir()['basedir'] . '/maps/' . $map_cache_filename;
$map_cache_file_url = wp_upload_dir()['baseurl'] . '/maps/' . $map_cache_filename;
// Check if the cached image exists
if (!file_exists($map_cache_file_path)) {
// Construct Google Maps API request URL
$map_request = "https://maps.googleapis.com/maps/api/staticmap"
. "?size=670x400"
. "&scale=2" // Keeps high resolution
. "&zoom=" . $location_zoom
. "&maptype=roadmap"
. "&key=APIKEY"
. "&format=png"
. "&markers=icon: " . get_template_directory_uri() . " /images/maps/pin.png%7Cshadow:true%7C" . $location_lat . "," . $location_lng
. "&markers=" . $location_lat . "," . $location_lng // Default Google marker
. $parking_markers;
// Fetch and save the image locally
$image_data = file_get_contents($map_request);
if ($image_data) {
// Ensure the maps directory exists
$map_dir = wp_upload_dir()['basedir'] . '/maps/';
if (!is_dir($map_dir)) {
mkdir($map_dir, 0755, true);
}
file_put_contents($map_cache_file_path, $image_data);
}
}
// Use the cached image URL
$map_img_url = $map_cache_file_url;
?>
<div id="map-<?php echo esc_attr($location_post_id); ?>" class="map-container acf-map"
data-lat="<?php echo esc_attr($location_lat); ?>"
data-lng="<?php echo esc_attr($location_lng); ?>"
data-zoom="15"
style="background:url(<?php echo esc_url($map_img_url); ?>) no-repeat center;background-size:cover;position:relative;max-width:670px;">
<a href="<?php echo esc_url($map_link[strtolower($location_title)]); ?>" target="_blank"
style="position:absolute;top:0;bottom:0;left:0;right:0;"></a>
</div>
By implementing it this way, we no longer serve the request directly from Google and cut API map calls down from 1,500+ requests per day to around just 10—sometimes 0, if nothing in the maps has changed. Not only is it less reliant on Google, but it’s also faster because now it’s truly a static image served from a CDN, and it falls within Google’s free tier.
There are further improvements we can still make, like automatically deleting old map images and error handling if Google's API can't be reached. But the goal today was to try and reduce that bill from Google for March, and we accomplished that!