Using the tomorrow.io API to Fetch Weather Data on a WordPress Website

tomorrow.io is a great real-time weather API you can use to fetch weather information and display it on your website or application. Here’s how I am using the API to display my local weather information on my WordPress website.

Firstly, I decided I didn’t want to have to hit the API every time a user visits my page. Since I am only display today’s average condition, high and low temperatures, and sunrise and sunset data—all data which isn’t going to change from minute to minute—there is no need to hit the API on every page load. And, if you have a website with a large amount of traffic, hitting the API that frequently might make you go over your Quota.

Instead, I’m just hitting the API hourly, at most, and saving the data to my database. My PHP then draws that data from my database directly. Hourly is still overkill in my use case, but still well within limits.

First I set up an ACF (Advanced Custom Fields) options page for my weather data, and included fields for the following:

  1. Last Weather Fetch Date
  2. Last Weather Fetch Hour
  3. Weather JSON
  4. Weather Fetch Error
  5. Weather Coordinates
Weather Options Page

All of these fields will be automatically populated by my PHP weather fetching function except for the weather coordinates—which is the latitude and longitude of the location I want to get my weather data for. You can easily get these via Google Maps by right-clicking any location on the map and copying the coordinates.

Next, I wrote the function to fetch the weather:

/**
 * Function - Fetch Weather
 */
function fetch_weather($current_date, $current_hour)
{

	$weather_coordinates = get_field('weather_coordinates', 'option');
	// API endpoint URL
	if ($weather_coordinates) {
		$api_url = 'https://api.tomorrow.io/v4/weather/forecast?location=' . $weather_coordinates . '&apikey=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
	} else {
		$api_url = 'https://api.tomorrow.io/v4/weather/forecast?location=34.67718086406146, -118.45199326849152&apikey=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
	}

	// Make the API request
	$response = wp_remote_get($api_url);

	// Check if the request was successful
	if (is_wp_error($response)) {
		// Handle the error
		// echo 'Error: ' . $response->get_error_message();
		$weather_fetch_error_field = get_field('weather_fetch_error', 'option');

		if ($weather_fetch_error_field) {
			delete_field('weather_fetch_error', 'option');
		}

		update_field('weather_fetch_error', $response->get_error_messages(), 'option');
	} else {
		// The request was successful, and $response contains the API response
		$body = wp_remote_retrieve_body($response);

		// Now you can work with the API response, for example, decode JSON
		$data = json_decode($body, true);

		// Save the data to the "Weather JSON" option field
		update_field('weather_json', $body, 'option');
		update_field('last_weather_fetch_date', $current_date, 'option');
		update_field('last_weather_fetch_hour', $current_hour, 'option');
	}
}

There is another function that will call fetch_weather() only if it’s needed (if the weather has not been updated within the last hour) and return the weather as JSON. If the weather is current, then it just gets the weather that is already saved in my database.

function get_weather_json()
{
    date_default_timezone_set('America/Los_Angeles');
    $current_date = date('Ymd');
    $current_hour = date('G');
    $last_weather_fetch_date = get_field('last_weather_fetch_date', 'option');
    $last_weather_fetch_hour = get_field('last_weather_fetch_hour', 'option');

    // Fetch new data, if necessary
    if (!$last_weather_fetch_date || $last_weather_fetch_date != $current_date || !$last_weather_fetch_hour || $current_hour > $last_weather_fetch_hour) {
        fetch_weather($current_date, $current_hour);
    }

    // Display
    $weather_json = get_field('weather_json', 'option');

    return $weather_json;
}

Next, I created a function to display the weather data that gets saved to my database.

/**
 * Function - Display Weather Card
 */
function display_weather_card($weather_json)
{
    $data = json_decode($weather_json, true);

    $minute_index = 0;
    $day_index = 0;

    $current_temperature_fahrenheight = number_format(($data['timelines']['minutely'][$minute_index]['values']['temperature'] * 9 / 5) + 32, 0);

    $today_high_temperature_fahrenheight = number_format(($data['timelines']['daily'][$day_index]['values']['temperatureMax'] * 9 / 5) + 32, 0);

    $today_low_temperature_fahrenheight = number_format(($data['timelines']['daily'][$day_index]['values']['temperatureMin'] * 9 / 5) + 32, 0);

    $today_rain_accumulation_sum = $data['timelines']['daily'][$day_index]['values']['rainAccumulationSum'];

    $today_cloud_cover_avg = $data['timelines']['daily'][$day_index]['values']['cloudCoverAvg'];

    $today_snow_accumulation_avg = $data['timelines']['daily'][$day_index]['values']['snowAccumulationAvg'];

    // Rain
    $will_rain = false;
    if ($today_rain_accumulation_sum >= 1) {
        $will_rain = true;
    }

    $will_snow = false;
    if ($today_snow_accumulation_avg >= 1) {
        $will_snow = true;
    }

    // Cloudy
    $cloudy = false;
    if ($today_cloud_cover_avg >= 33) {
        $cloudy = true;
    }

    $condition = 'Sunny';
    $weather_icon = '<i class="fa-light fa-sun"></i>';

    if ($cloudy) {
        $condition = 'Cloudy';
        $weather_icon = '<i class="fa-light fa-clouds"></i>';
    }
    if ($will_rain) {
        $condition = 'Rainy';
        $weather_icon = '<i class="fa-light fa-cloud-rain"></i>';
    }
    if ($will_snow) {
        $condition = 'Snowy';
        $weather_icon = '<i class="fa-light fa-cloud-snow"></i>';
    }

    // Sunsrise & Sunset times
    $today_sunrise_time = $data['timelines']['daily'][$day_index]['values']['sunriseTime'];
    $today_sunset_time = $data['timelines']['daily'][$day_index]['values']['sunsetTime'];

    // Create a DateTime object from the string
    $today_sunrise_time = new DateTime($today_sunrise_time);
    $today_sunset_time = new DateTime($today_sunset_time);

    // Set the timezone to Los Angeles
    $losAngelesTimezone = new DateTimeZone('America/Los_Angeles');
    $today_sunrise_time->setTimezone($losAngelesTimezone);
    $today_sunset_time->setTimezone($losAngelesTimezone);

    // Format the time in 12-hour format
    $today_sunrise_time = $today_sunrise_time->format('g:i A');
    $today_sunset_time = $today_sunset_time->format('g:i A');

    ?>
    <div class="cards addfade">
        <div class="card decoration-1">
            <div class="columns">
                <div class="column weather-icon">
                    <p>
                        <?php echo $weather_icon; ?>
                    </p>
                </div>
                <div class="column current-temperature">
                    <p>
                        <?php echo $current_temperature_fahrenheight . '°F'; ?>
                    </p>
                </div>
                <div class="column high-low-temps">
                    <p>
                        <span><i class="fa-solid fa-temperature-full"></i><i class="fa-solid fa-arrow-up"></i></span>
                        <span>
                            <?php echo $today_high_temperature_fahrenheight . '°F'; ?>
                        </span>
                    </p>
                    <p>
                        <span><i class="fa-solid fa-temperature-quarter"></i><i class="fa-solid fa-arrow-down"></i></span>
                        <span>
                            <?php echo $today_low_temperature_fahrenheight . '°F'; ?>
                        </span>
                    </p>
                </div>
                <div class="column sunrise-sunset-times">
                    <p>
                        <span><i class="fa-solid fa-sunrise"></i></span>
                        <span>
                            <?php echo $today_sunrise_time; ?>
                        </span>
                    </p>
                    <p>
                        <span><i class="fa-solid fa-sunset"></i></span>
                        <span>
                            <?php echo $today_sunset_time; ?>
                        </span>
                    </p>
                </div>
            </div>
        </div>
    </div>
    <?php
}

It also uses FontAwesome icons to display the general condition of the day, depending on the weather.

Lastly, my custom weather WordPress block calls the appropriate functions and displays the weather.

display_weather_card(get_weather_json());

And there you have it!

Optimizing WordPress Image Scaling for a Better Performing Website

In my previous post discussing the importance of serving appropriately scaled images on your web pages, let’s delve into how WordPress handles image scaling.

The Problem

When you upload an image to your WordPress media library, the default behavior is for WordPress to automatically generate three downscaled versions of that image. For example, if you upload an image sized 2048 x 1536 pixels, WordPress will create a thumbnail (150 x 150 pixels), a medium-sized version (maximum width and height of 300 pixels), and a large version (maximum width and height of 1024 pixels).

Now, consider a scenario where you have a card on your site that is 300 pixels wide, and you need to place an image in it. While you could use the original full-size image, it’s not optimal because it will only be displayed at 300 pixels wide, resulting in unnecessary resource consumption. Instead, this is an ideal situation to utilize the medium-scaled image size.

However, if you have a card that is 600 pixels wide and you want to use an image that spans the entire width of the card, the medium image size is inadequate. Enlarging it to fill 600 pixels will make it appear pixelated. The next available scaled image size, large (1024 pixels), is too large for the container, although it’s still better than using the original full-size image.

The Solution

To address this, you can generate a new scaled image size tailored to your specific needs. Here’s how you can do it:

In your functions.php file, include the following code:

/**
 * Register new image sizes
 */
add_image_size('image_size_600', 600, 600);

The first parameter is the name you choose for your new scaled image, the second parameter is the image width in pixels, the third parameter is the image height in pixels, and the fourth parameter (not used in the example above) is for image cropping.

For more details about this function, refer to WordPress add_image_size function.

Once you’ve added your new custom-scaled image size, you can use a plugin like Regenerate Thumbnails to process the images in your media library and create new images of that scale. Subsequently added images to your media library will be automatically scaled.

Now, you can serve appropriately sized images according to your site design requirements.

Caution

Keep in mind that each time you add an image to your media library, three additional versions are created by default. Introducing new image sizes means more versions for each image stored on your server, potentially bloating the size of your media library. Therefore, use this feature strategically to avoid unnecessary increases in server storage.

Run PHP Function When Updating WordPress ACF Options Page

I needed to run a custom PHP function that updates custom fields (using Advanced Custom Fields) attached to users when an admin clicks “update” on a back-end options page. The way I did this was using the acf/save_post action.

By itself, the function would be triggered when updating any options page. I didn’t want this. I only wanted the function to run when updating one specific options page. To fix this, I wrapped the function’s code in an if statement that checks if get_current_screen() was equal to my options page.

add_action('acf/save_post', 'update_users_reporting_log'); function update_users_reporting_log() {     $screen = get_current_screen();     if (strpos($screen->id, "reported-content") == true) {         // Function code here:               endif;     } }

strpost() will check to see if a particular string occurs inside another. Actually, it will find the position of the occurance, but I didn’t care about the position, only if it did occur. And the string I was checking inside of is the return of get_current_screen(). The name of my options page is “reported-content”.

Now, when a user clicks the “update” button on that particular options page, my custom function will run, updating custom fields elsewhere on the site.