Using UK Ordnance Survey Terrain 50 Elevation Data


This page explains and demonstrates with a working script how to obtain and read Ordnance Survey Terrain 50 (OST50) elevation data for use in an on-line map or similar geo-app.

Obtaining And Understanding Ordnance Survey Terrain 50 Data

OST50 can be obtained from Ordnance Survey free for use under their Open Data Terms & Conditions:

The area covered is all of England, Scotland, & Wales including offshore islands, but not including Northern Ireland, nor, unlike its predecessor Landform Panorama, the Isle Of Man.  OST50 data comes as ASCII text files called tiles, each of which begins with five header lines giving details about the data lines that follow.  There are 200 data lines each with 200 columns, separated by spaces, of floating point numbers representing elevation, giving a 200 x 200 grid with 50m spacing.  Note that, unlike the SRTM and OS's earlier Landform Panorama datasets, the edge rows and columns do NOT overlap and replicate the edge rows & colummns of neighbouring tiles.  Also unlike SRTM and LP, the coordinates mark the centre of each cell, not the SE corner of it.  Note also that within each tile the data is stored in row-major order (x easting within y northing), and, like an image, from the top of the tile to the bottom.

Some points to note about using Terrain 50 in this manner:

OS Terrain 50 Tile Metadata
Terrain 50 All Files ASCII tiles onlyOS Zips
File Count14,2902,8582,858
Unzipped (MB)584563na
Zipped (MB)152150152
File List OST50_File_List_All.txt OST50_File_List_Asc.txt OST50_File_List_Zip.txt

Ordnance Survey Terrain 50 Tile Map

Example Script

This Python script is designed to run as a standard CGI script on a webserver.

It uses OST50 *.asc tiles, which by default are expected to be in a sub-directory called OST50 beneath that where the script is located.  Debugging output is HTML5 compliant and requires two other files, an HTML template, which can vary slightly from script to script and therefore is named after the associated script to distinguish it from others, and an invariant CSS stylesheet, by default named Minimal.css.

Obviously, these defaults can be changed to suit individual requirements, but the script (for the OST50 data and template paths) and/or the template (for the stylesheet path) then must be edited accordingly.

The script is self-documented.  Instructions for use are included as leading comments, and also are displayed in response to errors in calling it.

If using this script, and scarcity of available server or web space so demands, the tiles can be left zipped, as, when looking for a tile, the script will look first for an unzipped version of it, but if that is not found it will then look for a zipped version.  Inevitably however, leaving the data in its original zips will slow things down a little.

A profile point is returned for each intermediate OST50 cell up to 100 (the empirically determined maximum realistically displayable using a URL to the Google Charts API to draw the profile).  If a profile path crosses more than 100 cells, then each of the 100 points returned is the average, or maximum if given the mx switch, of the cells crossed by the path that are closest to that sample point  that is, the profile returned is derived from all the cells it crosses, not just a sample of them.

The profiles returned follow a straight line on an OS map, and therefore will not normally be an accurate geodesic.

Server CGI

It is beyond the scope of this article to document and support CGI.  If required, novices are referred to their webhost's online documentation and support for how to enable Python CGI on their webservers.  This article merely gives the basics for setting up this CGI script.  However, two points to note are to ensure that the 'shebang', the first commented line containing the path to the Python interpreter, is correctly set, and that on Linux-hosted systems the execute attribute is set on the script file, for example by using:
     chmod +x <path to file>.

By default the script would normally be put in the /cgi-bin directory off the site root.  The default expected directory structure is therefore …

/cgi-bin/OST50/<*.asc or *.zip OST50 data files in their original directory layout>

CGI Python Script ''

By default this is expected to be in the directory /cgi-bin off site root.  It lists as follows:

HTML Template 'OST50.html'

By default this is expected to be in the directory /Resources/Templates off site root and have the same base name as the Python script with an .html extension.   It lists as follows:

Stylesheet 'MinimalCSS'

By default this is expected to be in the directory /Resources/Styles off site root and be called Minimal.css.   It lists as follows:

Right-click this link and choose Save As for a zip of the three files required for Server-side CGI:

Displaying The Profile

Unfortunately, the only convenient way that I know of displaying a profile, the Google Static Image Charts API, has been deprecated since 2012.  As per this discussion in their forum, they have made soothing noises, but these remain unofficial, and there is still no definite word on any possible replacement.  If it were to go, this wouldn't be the first time that they've broken my work in this manner, and, as I don't suppose it will be the last, ideally what is needed is some sort of standard library that can be installed on one's own site, but I don't know of anything remotely comparable to the ease of just feeding Google a suitable URL.

However, at the time of writing in November 2021, it's still running(!), so here's how to use the service.  It really is as simple as taking the URL returned by either Python script, replacing ~Width~ and ~Height~ by suitable dimensions (pixels), ensuring that the two when multiplied together come to less than Google's limit of 300,000, and then making the result the src attribute of an img tag in your web-page.  Here's a demo profile between the Outer Hebrides and St Kilda, which also demonstrates the problems caused by the lack of a flat sea as discussed above …

<div id="ProfDiv"></div>
  var imgWidth = 650;
  var imgHeight = 250;
  var profData;
  function callBack( data )
    profData = data;
    profData.url = profData.url.replace( "~Width~", "" + imgWidth + "" ).replace( "~Height~", "" + imgHeight + "" );
    var img = document.createElement( "img" ); = "ProfImg" = imgWidth + "px" = imgHeight + "px"
    img.src = profData.url
    document.getElementById("ProfDiv").appendChild( img );
<script src=""></script>