Global Land Temperature Anomalies (1850-2007)

The data set used here is available by HTTP at http://www.cru.uea.ac.uk/cru/data/temperature/.

For the animation on this page, I used the CRUTEM3 of land air temperature anomalies on a 5 degree by 5 degree gridbox basis.

∆T>5 5≥∆T>4 4≥∆T>3 3≥∆T>2 2≥∆T>1
1≥∆T>0 0≥∆T>-1 -1≥∆T>-2 -2≥∆T>-3 -4≥∆T

The Data File

For each year month combination, the data file contains 36 rows of 72 columns which divide the planet into five degree by five degree gridboxes. This data set only contains land air temperature anomalies, so there is a lot of missing data. Next on my list is HadCRUT3 which contains combined land and sea temperature anomalies for the same period.

I transformed the data file so that each row corresponded to one observation per month/year/gridbox combination. In the process, I got rid of the gridboxes with missing anomaly values. Here is the Perl script:

Script : crutem-xform.pl

#!/usr/bin/perl

use strict;
use warnings;

use Data::Dumper;

use FindBin qw( $Bin );
use File::Spec::Functions qw( catfile );

my $dat_file = catfile( $Bin, 'crutem3.dat' );

open my $dat, '<', $dat_file
    or die "Cannot open '$dat_file' : $!";

FILE: while( my $line = <$dat> ) {
    chomp $line;
    next unless $line =~ m{\A
                          \s+
                          (\d{4})
                          \s+
                          (\d+)
                          \s+
                          (\d+)
                          \s+
                          (\d+) \s+ rows
                          \s+
                          (\d+) \s+ columns.
                          \s+
                          Missing=(\S+)
                          \z
                     }x;
    my ($year, $month, $rows, $cols, $missing) = ($1, $2, $4, $5, $6);

    my $output_buffer;

    GRID_DATA: for ( my $row_i = 0; $row_i < $rows; ++ $row_i ) {
        my $row_data = <$dat>;
        $row_data =~ s/\A\s+//;
        $row_data =~ s/\s+\z//;
        my @temps = split /\s+/, $row_data;
        
        unless ( @temps == $cols ) {
            warn sprintf(
                "Data format mismatch: Expected %d columns, got %d: '%s'",
                $cols, scalar @temps, $row_data
            );
            next GRID_DATA;
        }
        
        for ( my $col_i = 0; $col_i < $cols; ++ $col_i ) {
            unless ( $temps[ $col_i ] eq '-1.000e+30' ) {
                $output_buffer .= sprintf(
                    "%4.4d|%2.2d|%.0f|%.0f|%.0f|%.0f|%.2f\n",
                    $year,
                    $month,
                    grid_coord( $row_i, $col_i ),
                    $temps[ $col_i ],
                );
            }
        }
    }
    print $output_buffer;
}

sub grid_coord {
    my ($row, $col) = @_;

    my $south = 5 * ( 17 - $row );
    my $west  = 5 * ( $col - 36 );
    my $north = 5 * ( 18 - $row );
    my $east  = 5 * ( $col - 35 );
    
    return ( $south, $west, $north, $east ) if wantarray;

    return sprintf( '(%.0f%s,%.0f%s)-(%.0f%s,%.0f%s)',
        abs( $south ),
        $south >= 0 ? 'N' : 'S',
        abs( $west ),
        $west  >= 0 ? 'E' : 'W',
        abs( $north ),
        $north >= 0 ? 'N' : 'S',
        abs( $east ),
        $east  >= 0 ? 'E' : 'W',
    );
}
__END__

Output and Visualization

With the experience from graphing the GISS series, it was now fairly easy to graph all the data at one frame per month.

Script : crutem-graph.pl

This script uses the same My::Plotter module I used for the GISS data. I am not going to repeat the code for that module here.

#!/usr/bin/perl

use strict;
use warnings;

use FindBin qw( $Bin );
use File::Slurp;
use File::Spec::Functions qw( catfile );
use lib qw( C:/Home/asu1/Src/lib );

use My::Plotter;

my $map_bg = catfile( $Bin, 'etopo-landmask-gray-oceans.png' );

my $data_file = catfile $Bin, 'crutem3.txt';

open my $dat, '<', $data_file
    or die "Cannot open '$data_file': $!";

my ($this_year, $this_month, @data) = qw( 1850 01 );

OBS: while ( my $obs = <$dat> ) {
    chomp $obs;
    print STDERR "$this_month/$this_year . " unless $. % 2500;
    my ($obs_year, $obs_month, @obs ) = split /\|/, $obs;
    if ( $obs_month eq $this_month and $obs_year eq $this_year ) {
        push @data, \@obs;
        next OBS;
    }
    else {
        plot( 
            data  => \@data, 
            bg    => $map_bg, 
            year  => $this_year,
            month => $this_month
        );

        ($this_year, $this_month) = ($obs_year, $obs_month);
        @data = \@obs;

        next OBS;
    }
}

if ( @data ) {
    plot( 
        data  => \@data, 
        bg    => $map_bg, 
        year  => $this_year,
        month => $this_month
    );
}

print STDERR "\nDone\n";

sub plot {
    my %args = @_;

    my $plotter = My::Plotter->new( $args{bg} );

    for my $obs ( @{ $args{data} } ) {
        $plotter->plot( 
            { 
                south   => $obs->[0],
                west    => $obs->[1],
                north   => $obs->[2],
                east    => $obs->[3],
                celsius => $obs->[4],
            }
        );
    }

    write_file( 
        catfile( qw( images crutem ), "$args{year}-$args{month}.png" ),
        { binmode => ':raw' },
        $plotter->to_png( $args{year}, $args{month} ),
    );
}
__END__

Now that I had these 1885 files (every month between January 1850 and January 2007), plotting them was easier. For the background, I used modified version of etopo-landmask created by Dave Pape. I think the resulting image gives a good result in terms of visualization of both anomalies and grids with no data.

Here is the the frame for January 1881 along with the same frame from the GISS data set for comparison:

Jan 1881 (CRUTEM3)Jan 1881 (GISS)
[ CRUTEM3
                World Temperature Anomalies, January 1881 ][ GISS World Temperature
                Anomalies, January 1881 ]

reduced in both dimensions by 50%. I do not know the reason for the difference. I am more than a little baffled by it. I will keep examining my work to see if the discrepancy is due to an error I made.

The Video

Once I had the sequence of 1885 frames (one frame per month, from January 1850 to January 2007), I used VirtualDub to convert it to an MPEG4 encoded AVI at 4 frames per second (that is, every second in the animation corresponds to four months).

FYI, it takes about 10 minutes to generate all the frames and about five minutes to encode them on a low end laptop. The resulting video file is about 50 Mb.