Using Ordnance Survey Landform Panorama Elevation Data

Introduction

This page explains and demonstrates with working scripts how to read Ordnance Survey Landform Panorama (OSLP) elevation data for use in an on-line map or similar geo-app.

Obtaining And Understanding Ordnance Survey Landform Panorama Data

OSLP can be obtained from Ordnance Survey free for use under their Open Data Terms & Conditions:
     http://www.ordnancesurvey.co.uk/business-and-government/products/land-form-panorama.html

The area covered is all of England, Scotland, & Wales including offshore islands and the Isle Of Man, but not including Northern Ireland.  OSLP 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 401 data lines each with 401 columns, separated by spaces, of floating point numbers representing elevation, giving a 401 x 401 grid with 50m spacing.  Note that, like the SRTM dataset but unlike OS's more recent Terrain 50 dataset, the extra row and column allow the rows and columns at the edges of each tile to repeat the corresponding data from neighbouring tiles, thus making it easier programmatically to scan from tile to tile.  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 Landform Panorama:

OS Landform Panorama Tile Metadata
Landform Panorama All Files ASCII tiles only
File Count2442812
Size5.1 GB628 MB
File List na OSLP_File_List_Asc.txt

Ordnance Survey Landform Panorama Tile Map

Example Scripts

These are two versions of fundamentally the same underlying Python script.  The first is designed to run as a standard CGI script on a webserver, the second as a Google App.  Both use OSLP tiles, which by default are expected to be in a sub-directory called OSLP beneath that where the script is hosted.  Debugging output is HTML5 compliant and requires two other files, an HTML template, which varies at least slightly from script to script and therefore is named after the script to distinguish it from others1, and an invariant CSS stylesheet, by default named Minimal.css.

1  As it happens, here the only difference between these two HTML templates is the line that loads the stylesheet.

Obviously, in both cases these defaults can be changed to suit individual requirements, but the scripts (for the OSLP data and template paths) and/or the templates (for the stylesheet path) then must be edited accordingly.

Both scripts are self-documented.  Instructions for use are included as leading comments, and also are displayed in response to errors in calling them.

A profile point is returned for each intermediate OSLP 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.

MinimalCSS

For both script / template versions the stylesheet lists as follows:

1 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/OSLP.py
/cgi-bin/OSLP/<*.asc OSLP data files in their original directory layout>
/Resources/Templates/OSLP.html
/Resources/Styles/Minimal.css

Python Script (Server CGI version)

OSLP.py

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

HTML Template (Server CGI version)

OSLP.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.  The webserver CGI version lists as follows:

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

2 Google App

It is beyond the scope of this article to document and support Google Apps.  Novices are referred to Google's online documentation and the self-help groups they host.  This article merely gives the basics for setting up this Google App, assuming you have already created a default app called <tag>oslp where <tag> is an id of your own choosing.

You may wish to investigate using the app's free quota, 5GB at the time of writing, of Google Cloud Storage (GCS) to store the tiles, so that they can be shared between app versions rather than having a separate copy of invariant data for each one  particularly as OS do not supply zipped versions of these tiles (although of course you could zip them yourself), and as supplied unzipped they take up more than half the free quota of app local file storage space, 1GB at time of writing, so attempting to upload a second version will fail.  However, GCS is significantly slower than local file storage, particularly where a path goes over a sea area with no tiles, though this particular case could probably be significantly improved by adjusting retry parameters.

In addition to the default contents of the app's app.yaml file, it will will need to import both the jinja2 and webapp2 libraries, and to declare a static handler for the CSS file, as follows:

libraries:
- name: jinja2
  version: latest
- name: webapp2
  version: latest

handlers:
- url: /Resources/(.*\.css)
  upload: Resources/(.*\.css)
  static_files: Resources/\1

By default the app script would normally be put in the root directory of the app space, giving a default expected directory structure of …

/<tag>oslp.py
/OSLP/<*.asc OSLP data files in their original directory layout>
/Resources/Templates/<tag>oslp.html
/Resources/Styles/Minimal.css

Python Script (Google App version)

<tag>oslp.py

By default this is expected to be in the root directory of the Google App space.  The Google App version lists as follows:

HTML Template (Google App version)

<tag>oslp.html

By default this is expected to be in the directory /Resources/Templates off root of the Google App space and have the same base name as the Python script with an .html extension.  The Google App version lists as follows:

Right-click this link and choose Save As for a zip of the three files required for a Google App: OSLPGoogleApp.zip

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 June 2015, 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 …

<div id="ProfDiv"></div>
<script>
  <!--
  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");
    img.id = "ProfImg"
    img.style.width = imgWidth + "px"
    img.style.height = imgHeight + "px"
    img.src = profData.url
    document.getElementById("ProfDiv").appendChild( img );
    }
  //-->
</script>
<script src="http://www.<tag>oslp.appspot.com/?x1=437900&y1=1151200&x2=435408&y2=1168047&cb=callBack&pr=true"></script>