Use MATLAB with GridLive to explore UK energy consumption.
Working with the GridLive API is fairly easy with MATLAB. The main thing to look out for is MATLAB's JSON decoder does not auto-detect int64 numbers. An example of how to deal with this is provided below in the Example Code. Everything else you need to access GridLive is built-in to MATLAB. With access to the Mapping Toolbox the data can be explored geographically.
% Example of fetching and plotting data from the GridLive API using MATLAB
clear
clc
%% Setup
% An OS grid reference to fetch data from
os_grid_reference = 'ST17'; % ST17 - Cardiff
% Your GridLive API key
api_key = 'YOUR API KEY HERE';
% The date to extract data from
data_from = datetime('2025-05-05');
duration = 7; % Number of days to extract
% A helper function to remove the first element from a matrix
skipfirst = @(x)x(2:end);
%% Metadata
fprintf("Fetching metadata...\n")
% Fetch the OS grid reference metadata from the API
options = weboptions('ContentType', 'text');
metadata_json = webread(['https://api.gridlive.shef.ac.uk/esa_metadata/in/' os_grid_reference '?limit=0'], options);
% Convert the JSON to a MATLAB table
metadata = struct2table(jsondecode(metadata_json));
metadata.license_area_name = categorical(metadata.license_area_name);
metadata.dno_name = categorical(metadata.dno_name);
metadata.esa_level = categorical(metadata.esa_level);
% MATLAB JSON doesn't handle int64s nicely, so handle these manually
% We will split the JSON string into substrings at the esa_id field and
% then extract the int64 it using textscan
metadata.esa_id = cell2mat(cellfun(@(x)textscan(x, '%d64'), skipfirst(split(metadata_json,'"esa_id":'))));
% Organise the metadata by esa_id
metadata = sortrows(metadata, 'esa_id');
clear metadata_json
fprintf("Done fetching\n")
%% Time-series data
data_from.Format = 'yyyy-MM-dd''T''HH:mm''Z''';
data = cell([duration,1]);
% Add the API key to the request
options.HeaderFields = {'Authorization', api_key};
for ii=1:duration
% Setup the day's worth of data we're requesting from the total request
start_date = char(data_from + days(ii-1));
% End date is inclusive so we need to be just before
end_date = char(data_from + days(ii)-minutes(1));
fprintf("Fetching time-series data %s to %s...\n", start_date, end_date)
% Fetch that data
temp_json = webread(['https://api.gridlive.shef.ac.uk/smart_meter/in/' os_grid_reference '?limit=0&start_datetime=' start_date '&end_datetime=' end_date], options);
temp_data = struct2table(jsondecode(temp_json));
temp_data.esa_id = cell2mat(cellfun(@(x)textscan(x, '%d64'), skipfirst(split(temp_json,'"esa_id":'))));
% Store the fetched data in a cell array to convert it into one big table later
data{ii} = temp_data;
end
data = vertcat(data{:});
% Convert timestamps as strings to MATLAB datetimes
data.data_timestamp = datetime(data.data_timestamp, 'InputFormat', "yyyy-MM-dd'T'HH:mm:ss'+00:00'", 'TimeZone', 'UTC');
clear temp_data temp_json ii start_date end_date
fprintf("Done fetching\n")
%% Plot (animate) data
fprintf("Animating consumption from %s until %s...\n", data_from, data_from + days(duration))
% Set the speed of the animation - higher is faster
framerate = 10;
% Scale the size of the bubbles plotted
scale = 5;
% Set the colour axis limit in kWh
limit = 80;
% Get the coordinates we will display at and convert to latitude and longitude
[coordinates, ~, duplicate_coordinates] = unique(metadata(:,{'esa_location_eastings', 'esa_location_northings'}));
OSGB36 = projcrs(27700);
[latitude, longitude] = projinv(OSGB36, coordinates.esa_location_eastings, coordinates.esa_location_northings);
coordinates.latitude = latitude;
coordinates.longitude = longitude;
clear latitude longitude
% Get the times we will display energy consumption at
timestamps = unique(data.data_timestamp)';
timestamps.Format = 'eeee MMM dd yyyy HH:mm';
% Un-comment the next 3 lines and other lines dealing with 'v' to save to video
% v = VideoWriter('out.avi', 'Uncompressed AVI');
% v.FrameRate = framerate;
% open(v)
% Iterate through each time finding the consumption data and displaying it
for time=timestamps
timer = tic(); % Keep track of how long it takes to plot
% Get the consumption data for this timestamp and combine it with the location
k = data.data_timestamp == time;
active_import = sortrows(data(k,{'esa_id', 'active_primary_consumption_import'}), 'esa_id');
active_import = outerjoin(active_import, metadata(:,{'esa_id', 'esa_location_eastings', 'esa_location_northings'}),'MergeKeys',true);
assert(sum(active_import.esa_id == metadata.esa_id) == height(metadata))
% The data may contain multiple readings at the same coordinates
% If so, average the readings at those matching locations
averaged_active_import = nan([height(coordinates),1]);
for ii=1:height(coordinates)
k = find(ii == duplicate_coordinates);
% Convert to total consumption by factoring in the number of devices
averaged_active_import(ii) = sum(active_import.active_primary_consumption_import(k), "omitnan");
end
% Convert to kWh & do some rough data cleaning
averaged_active_import = averaged_active_import / 1000;
averaged_active_import(averaged_active_import > 1000) = NaN;
averaged_active_import(averaged_active_import == 0) = NaN;
% Set up an initial plot of the data at the first timestamp
if time == timestamps(1)
% Create a geoscatter plot & label with timestamp, scaling the bubble size
h = geoscatter(coordinates.latitude, coordinates.longitude, averaged_active_import * scale, averaged_active_import, 'filled');
th = title(char(time));
% Color the symbols and set a maximum scale value
colormap summer
clim([0 limit])
cb = colorbar;
ylabel(cb, 'Active Consumption Import (kWh)')
% Draw a box around the OS grid reference
xl = [round(min(coordinates.esa_location_eastings), -4) round(max(coordinates.esa_location_eastings), -4)];
yl = [round(min(coordinates.esa_location_northings), -4) round(max(coordinates.esa_location_northings), -4)];
[xl, yl] = projinv(OSGB36, xl, yl);
geolimits(xl.*[0.9999 1.0001], yl.*[0.9999 1.0001])
line(xl([1 2 2 1 1]), yl([1 1 2 2 1]), 'color', 'k', 'LineWidth', 1.5)
else
% At other timestamps update the existing plot with the new
% consumption values and new title
h.SizeData = averaged_active_import * scale;
h.CData = averaged_active_import;
th.String = char(time);
end
% Un-comment to save to video
% writeVideo(v,getframe(gcf));
% Delay for to make the animation run evenly
elapsed = toc(timer);
pause(1/framerate - elapsed)
end
% Un-comment to save to video
% close(v);
fprintf("Done animating\n")
% Convert the video to gif by running (change delay to 100/framerate)
% ffmpeg -i out.avi -c:v pam -f image2pipe - | convert -delay 10 - -loop 0 -layers optimize output.gif