LogoLogo
OS Docs HomeOS NGDOS APIsOS Download ProductsMore than MapsContact Us
  • More than Maps
  • Geographic Data Visualisation
    • Guide to cartography
      • Introduction to cartography
      • Types of maps
      • Symbology
      • Colour
      • Text on maps
      • Generalisation
      • Coordinate reference systems
      • Projections
      • Scale
      • Map legends
      • Map layout
      • Relief representation
      • North arrows
    • Guide to data visualisation
      • Introduction to data visualisation
      • GeoDataViz design principles
      • Types of visualisation
      • Thematic mapping techniques
      • Data visualisation critique
      • Accessible data visualisation
      • Ethical data visualisation
      • Software
      • Data
    • GeoDataViz assets
      • GeoDataViz basemaps
      • Stylesheets
      • GeoDataViz virtual gallery
      • Equal area cartograms
      • How did I make that?
        • Apollo 11 Landing
        • North York Moors National Park, 70 years
        • Snowdonia National Park, 70 years
        • Great Britain's National Parks
        • Great Britain's Islands
        • Great Britain's AONB's and National Scenic Areas
        • Famous shipwrecks of Pembrokeshire
        • Trig pillars today
        • Britain's most complex motorway junctions
      • #30DayMapChallenge
  • Data in Action
    • Examples
  • Demonstrators
    • 🆕Product Viewer
    • Addressing & location demonstrators
      • Address Portfolio overview
      • Which address product should you use?
      • AddressBase
      • AddressBase Core
      • AddressBase Plus
      • AddressBase Premium
      • Address Classifications
      • Addressing Lifecycle
      • OS Emergency Services Gazetteer
      • What are Vertical Streets?
      • Why are there differences in boundaries?
    • Contextual demonstrators
    • Customer best practice
      • Channel Shift
      • Data Management and OS Data Hub
      • End User Licence vs Contractor Licence
      • 🆕 IDs vs Spatial Relationships
      • Why we should capture good quality addresses at source
      • Why we Snap and Trace
    • Network Demonstrators
      • OS Detailed Path Network
      • OS Multi Modal Routing Network
        • OS Multi Modal Routing Network
      • Water Networks overview
      • OS MasterMap Highways Network and OS NGD Speeds
      • OS MasterMap® Highways Network and OS Open Roadsâ„¢
    • OS MasterMap Generation APIs
      • Using the OS Features API
      • Using the OS Features API Archive
      • Using the OS Downloads API
      • Using OS APIs in ESRI Software
    • 🆕OS NGD (National Geographic Database)
      • OS NGD Address
      • OS NGD Boundaries
      • 🆕OS NGD Buildings
        • 🆕Building and Building Access Feature Types
        • Building Part and Building Line Feature Types
      • 🆕OS NGD Geographical Names
      • OS NGD Land
      • OS NGD Land Cover enhancements
      • 🆕OS NGD Land Use
      • OS NGD Land Use enhancements
      • 🆕OS NGD Structures
        • 🆕OS NGD Structures
        • Field Boundaries
      • 🆕OS NGD Transport Features
      • 🆕OS NGD Transport Network
      • OS NGD Transport RAMI
      • OS NGD Water Features
      • OS NGD Water Network
      • OS NGD API - Features
      • Ordering OS NGD data
      • Change only updates
      • OS NGD Versioning
      • Creating a topographic map from OS NGD Data
      • Analytical styling for OS NGD data
    • OS MasterMap® demonstrators
    • 🆕Product & API Comparisons
      • 🆕Comparison of Water Network Products
  • Tutorials
    • GeoDataViz
      • Thematic Mapping Techniques
      • Downloading and using data from the OS Data Hub
      • How to download and use OS stylesheets
      • How to use the OS Maps API
      • Creating a bespoke style in Maputnik
    • GIS
      • Analysing pavement widths
      • Basic routing with OS Open Data and QGIS
      • Walktime analysis using OS Multi-modal Routing Network and QGIS
      • Creating 3D Symbols for GIS Applications
      • Constructing a Single Line Address using a Geographic Address
      • Creating a Digital Terrain Model (DTM)
      • Visualising a road gradient using a Digital Terrain Model
      • Visualising a road gradient using OSMM Highways
    • 🆕APIs
      • 🆕Using OS APIs with EPC API
      • 🆕OS APIs and ArcGIS
  • Deep Dive
    • Introduction to address matching
    • Guide to routing for the Public Sector
      • Part 1: Guide to routing
      • Part 2: Routing software and data options
      • Part 3: Building a routable network
    • Unlocking the Power of Geospatial Data
    • Using Blender for Geospatial Projects
    • A Guide to Coordinate Systems in Great Britain
      • Myths about coordinate systems
      • The shape of the Earth
      • What is position?
        • Types of coordinates
        • We need a datum
        • Position summary
      • Modern GNSS coordinate systems
        • Realising WGS84 with a TRF
        • The WGS84 broadcast TRF
        • The International Terrestrial Reference Frame (ITRF)
        • The International GNSS Service (IGS)
        • European Terrestrial Reference System 1989 (ETRS89)
      • Ordnance Survey coordinate systems
        • ETRS89 realised through OS Net
        • National Grid and the OSGB36 TRF
        • Ordnance Datum Newlyn
        • The future of British mapping coordinate systems
        • The future of British mapping coordinate systems
      • From one coordinate system to another: geodetic transformations
        • What is a geodetic transformation?
        • Helmert datum transformations
        • National Grid Transformation OSTN15 (ETRS89–OSGB36)
        • National Geoid Model OSGM15 (ETRS89-Orthometric height)
        • ETRS89 to and from ITRS
        • Approximate WGS84 to OSGB36/ODN transformation
        • Transformation between OS Net v2001 and v2009 realisations
      • Transverse Mercator map projections
        • The National Grid reference convention
      • Datum, ellipsoid and projection information
      • Converting between 3D Cartesian and ellipsoidal latitude, longitude and height coordinates
      • Converting between grid eastings and northings and ellipsoidal latitude and longitude
      • Helmert transformation worked example
      • Further information
  • Code
    • Ordnance Survey APIs
    • Mapping
    • Routing with pgRouting
      • Getting started with OS MasterMap Highways and pgRouting
      • Getting started with OS MasterMap Highways Network - Paths and pgRouting
      • Getting started with OS NGD Transport Theme and pgRouting
      • Getting started with OS NGD Transport Path features and pgRouting
  • RESOURCES
    • 🆕Data Visualisation External Resources
Powered by GitBook

Website

  • Ordnance Survey

Data

  • OS Data Hub
On this page
  • Tools and APIs
  • Tutorial
  • Download, install and run

Was this helpful?

  1. Tutorials
  2. APIs

Automated open data download

Last updated 1 year ago

Was this helpful?

In this tutorial we'll collect Ordnance Survey data from the Downloads API with an automated download and extract process.

Tools and APIs

We will walk through how to fetch the OS Terrain50 Digital Elevation Model (DEM) dataset from the using NodeJS and the command line, then unzipping and extracting all of the appropriate .asc files into a single folder.

We will be using:

  • NodeJS and npm, with and npm packages; fs and path standard modules.

  • Command line

Tutorial

Working with large datasets can be a challenge. With a few programming tools, however, we can radically improve the efficiency of collecting and manipulating these datasets.

We'll be using to download and extract Ordnance Survey's Terrain50 Digital Elevation Model dataset from the .

  1. Download zipped directory with the data.

  2. Unzip all contained zipped directories.

  3. Copy all .asc files into one folder, ready to be loaded into QGIS for our .

This tutorial was created using NodeJS v14.1.0. If you don't already have Node installed, on installing Node and npm on Windows and Mac.

This tutorial will walk you through creating this code from scratch. If you would rather download, install and run it all, there are instructions for that of this page.

Download & Extract

First, we'll download the Terrain 50 dataset from the OS Downloads API. In your terminal, make a directory where you'll want to store your project. At the outset we'll install the package from NPM, which is used for HTTP requests, and , which we'll use a bit later on for extracting zipped folders.

mkdir os-downloads-tutorial
cd os-downloads-tutorial

npm install axios extract-zip

If you're not familiar with Node, this npm install {package names} command will create a node_modules folder and download packages there.

Download File

We'll be breaking our Node program into modules to keep it organised. For our first step - downloading the dataset - create a file called downloadFile.js in the os-downloads-tutorial directory.

We'll be using the fs module to write data to the disk as it downloads via an axios HTTP request.

const fs = require("fs");
const path = require("path");
const axios = require("axios");
const extract = require("extract-zip");

/* ============================================================
Function: Uses Axios to download file as stream using Promise

/downloadFiles.js
============================================================ */
const download_file = (url, filename) =>
  axios({
    url,
    responseType: "stream"
  }).then(
    (response) =>
      new Promise((resolve, reject) => {
        response.data
          .pipe(fs.createWriteStream(filename))
          .on("finish", () => resolve())
          .on("error", (e) => reject(e));
      })
  );

This function will fetch the resource at the url passed in, and write the file returned to the local directory as filename. We will call it from the function we'll export, adding in some error handling and visual feedback so the user knows the file is downloading. We'll also unzip the downloaded file into the targetdir.

In the same file, below the download_file declaration, create the following function:

/* ============================================================
Download File

/downloadFiles.js
============================================================ */
async function downloadFile(url, targetdir) {
  try {
    // Giving user ongoing feedback in the terminal:
    console.log("Download starting ...");
    let interval = setInterval(() => console.log("..."), 5000);

    let targetfile = path.resolve(targetdir + ".zip");

    // Wait until the file is fully downloaded
    await download_file(url, targetfile);

    // Now make the target directory, extract the zipped file into it, and delete the downloaded zipfile.
    await fs.mkdirSync(targetdir);
    await extract(targetfile, { dir: path.resolve(targetdir) });
    await fs.unlinkSync(targetfile);

    // Complete!
    clearInterval(interval);
    console.log("Completed downloading files");
  } catch (error) {
    console.error(error);
  }
}

module.exports = downloadFile; // <- so we can import the function into another script

Recursive Unzip

Before we import and call the code above, we'll also define a script that will unzip all the zipped folders in the downloaded zipfile.

Create a file called unzipAll.js. This function will accept one parameter - a path to a directory (zipped or not). We'll then pull paths of all contained zipfiles, extract the contents, and delete the zipfiles. Since zipped directories can contain zipped sub-directories and files, we need to execute this recursively until all files ending in .zip are extracted and deleted.

// Import dependencies, including the extract-zip module we installed with npm
const fs = require("fs");
const path = require("path");
const extract = require("extract-zip");
const getFilePaths = require("./getFilePaths.js");

async function unzipAll(dir) {
  dir = path.resolve(dir);

  // Create an array of all .zip file paths in the directory:
  let filepaths = getFilePaths(dir);
  filepaths = filepaths.filter((filepath) => path.extname(filepath) === ".zip");

  // Loop through filepaths, extracting and deleting each:
  while (filepaths.length > 0) {
    for (let file of filepaths) {
      let parsedpath = path.parse(file);
      let targetdir = path.resolve(parsedpath.dir);

      try {
        // Unzip the compressed file into a directory with the same name
        await extract(file, { dir: targetdir });
        await fs.unlinkSync(file); // <- delete the .zip file
        console.log("Unzipped", parsedpath.base + ", deleted .zip file.");
      } catch (err) {
        console.log(err);
      }
    }

    // If the extracted folders contained zipfiles, they'd be included here:
    filepaths = getFilePaths(dir);
    filepaths = filepaths.filter(
      (filepath) => path.extname(filepath) === ".zip"
    );
  }
}

// Since we'll be importing it into our main JS file, we export the function:
module.exports = unzipAll;

getFilesPaths()

You'll notice we required a module to unzipAll - getFilePaths.js.

This function accepts a directory path string, then loops through the directory, building an array of all the paths to directories and files contained.

A note: this is another recursive function - one that calls itself. In this way it is able to move through the directory structure no matter its depth.

const fs = require("fs");
const path = require("path");

/** Retrieve file paths from a given folder and its subfolders. */
/* Big thanks to @darioblanco on https://gist.github.com/kethinov/6658166
    for sharing this code!! */
const getFilePaths = (folderPath) => {
  // Edge case: if a .zip file is passed in, we'll return that ready to unzip
  if (
    fs.statSync(folderPath).isFile() &&
    path.parse(folderPath).ext === ".zip"
  ) {
    return [folderPath];
  }

  // An array of path strings for the contents of the directory
  const entryPaths = fs
    .readdirSync(folderPath)
    .map((entry) => path.join(folderPath, entry));
  // An array of all the files in the directory
  const filePaths = entryPaths.filter((entryPath) =>
    fs.statSync(entryPath).isFile()
  );
  // An array of all the directories in the directory
  const dirPaths = entryPaths.filter(
    (entryPath) => !filePaths.includes(entryPath)
  );
  // Recursively travel down the directory tree, concatenating the contents of each subdirectory
  // dirFiles contains all the files with a directory 'depth' greater than one.
  const dirFiles = dirPaths.reduce(
    (prev, curr) => prev.concat(getFilePaths(curr)),
    []
  );

  // Combine all the paths into a single array with the ES6 spread operator
  return [...filePaths, ...dirFiles];
};

module.exports = getFilePaths;

Now we've downloaded the DEM data from the OS Downloads API and unzipped all the contained files. It should be ready to bring into QGIS.

app.js

Let's put this all together in a new file, /app.js. We'll import the modules we need - Node's fs and path, along with the custom modules we wrote above.

We don't want to have to click through every single folder to select the .asc files we need to load into QGIS - and QQGIS won't accept a directory containing loads of subdirectories and files of different types.

Fortunately, with Node - and the code we've already written - we can easily copy all the .asc files from their locations in the directory structure into one folder. (This could be done with many other programming languages like Python, bash, Ruby, C++ - the power of code.)

This pattern is necessary for us to be able to use async / await syntax, which makes it clean and easy to write code that will halt until the end of processes like downloading a large file.

// app.js
const fs = require("fs").promises;
const path = require("path");
const getFilePaths = require("./getFilePaths.js");
const downloadFile = require("./downloadFile.js");
const unzipAll = require("./unzipAll.js");

// We use an IIFE because`await` can only work inside an `async` function.
(async () => {
  const terrain50url =
    "https://osdatahubapi.os.uk/downloads/v1/products/Terrain50/downloads?area=GB&format=ASCII+Grid+and+GML+%28Grid%29&redirect";
  const targetDir = "./working_data";

  // // Await download and unzip:
  await downloadFile(terrain50url, targetDir);
  await unzipAll(targetDir);

  // Now we have a directory with several subdirectories containing, among other files, .asc grids representing elevations of 50m raster cells.
  // Let's extract an array of all paths then filter .asc files in the NG grid square:
  let allPaths = getFilePaths(targetDir);
  let ascPaths = allPaths.filter(
    (filepath) =>
      path.parse(filepath).ext === ".asc" && filepath.includes("/ng/")
  );

  // We'll create a directory to hold our .asc files
  let ascTarget = path.resolve(targetDir, "asc_skye/");
  await fs.mkdir(ascTarget);

  // Then loop through and copy each file into this ./asc folder
  for (let i = 0; i < ascPaths.length; i++) {
    let parsedpath = path.parse(ascPaths[i]);
    let target = path.resolve(ascTarget, parsedpath.base);
    await fs.copyFile(ascPaths[i], target);
    console.log("Copied", parsedpath.base);
  }

  console.log("Completed copying .asc files!");
})(); // <- Invoke the function immediately!

Running the program

We now have a complete Node program and modules, which we can execute by running node app.js on the command line in our os-downloads-tutorial directory.

Download, install and run

If you'd rather just download and run the code we described in the tutorial, you can clone the repository, install the npm packages and run it using the following commands:

git clone https://github.com/OrdnanceSurvey/os-data-hub-tutorials.git
cd os-data-hub-tutorials/web-development/automated-open-data-download/code
npm install

node app.js

If all goes well you'll see the console printing statements as the download and extract process starts!

We will place all of this code inside the body of an , which you can see in /app.js. By wrapping the function expression in parentheses, we can immediately invoke it (with or without arguments): (function (param) { /* body ...*/})(arg)

This may take a little time as the OS Terrain 50 dataset is 161MB. Once this is completed, there should be a new folder, working_data/asc_skye with the .asc files, ready to work with in QGIS. If you want to complete the tutorial and create a shaded relief map with the DEM data downloaded, find the tutorial .

Let us know if you automate a process fetching, manipulating, analysing or storing OS data - we'd love to know. Tweet at and tag .

🆕
OS Downloads API
axios
extract-zip
NodeJS
OS Downloads API
Shaded Relief Map tutorial
here is a great tutorial
at the bottom
axios
extract-zip
asynchronous immediately-invoked function expression
here
@OrdnanceSurvey
#OSDeveloper