Repository: SheffieldSolar/Geocode (language: python)

Issue:
Break geocode into separate modules
Create separate modules for:
- Loading ONS/NRS files
- utilities (print_progress, query_yes_no, reverse_geocode etc)
- managing cache files on disk


Discussion / hints:

Fix patch:
diff --git a/#egg=geocode-0.1 b/#egg=geocode-0.1
deleted file mode 100644
index e69de29..0000000
diff --git a/Dockerfile_dev b/Dockerfile
similarity index 100%
rename from Dockerfile_dev
rename to Dockerfile
diff --git a/Docs/build/html/_static/sphinx_highlight.js b/Docs/build/html/_static/sphinx_highlight.js
new file mode 100644
index 0000000..aae669d
--- /dev/null
+++ b/Docs/build/html/_static/sphinx_highlight.js
@@ -0,0 +1,144 @@
+/* Highlighting utilities for Sphinx HTML documentation. */
+"use strict";
+
+const SPHINX_HIGHLIGHT_ENABLED = true
+
+/**
+ * highlight a given string on a node by wrapping it in
+ * span elements with the given class name.
+ */
+const _highlight = (node, addItems, text, className) => {
+  if (node.nodeType === Node.TEXT_NODE) {
+    const val = node.nodeValue;
+    const parent = node.parentNode;
+    const pos = val.toLowerCase().indexOf(text);
+    if (
+      pos >= 0 &&
+      !parent.classList.contains(className) &&
+      !parent.classList.contains("nohighlight")
+    ) {
+      let span;
+
+      const closestNode = parent.closest("body, svg, foreignObject");
+      const isInSVG = closestNode && closestNode.matches("svg");
+      if (isInSVG) {
+        span = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
+      } else {
+        span = document.createElement("span");
+        span.classList.add(className);
+      }
+
+      span.appendChild(document.createTextNode(val.substr(pos, text.length)));
+      parent.insertBefore(
+        span,
+        parent.insertBefore(
+          document.createTextNode(val.substr(pos + text.length)),
+          node.nextSibling
+        )
+      );
+      node.nodeValue = val.substr(0, pos);
+
+      if (isInSVG) {
+        const rect = document.createElementNS(
+          "http://www.w3.org/2000/svg",
+          "rect"
+        );
+        const bbox = parent.getBBox();
+        rect.x.baseVal.value = bbox.x;
+        rect.y.baseVal.value = bbox.y;
+        rect.width.baseVal.value = bbox.width;
+        rect.height.baseVal.value = bbox.height;
+        rect.setAttribute("class", className);
+        addItems.push({ parent: parent, target: rect });
+      }
+    }
+  } else if (node.matches && !node.matches("button, select, textarea")) {
+    node.childNodes.forEach((el) => _highlight(el, addItems, text, className));
+  }
+};
+const _highlightText = (thisNode, text, className) => {
+  let addItems = [];
+  _highlight(thisNode, addItems, text, className);
+  addItems.forEach((obj) =>
+    obj.parent.insertAdjacentElement("beforebegin", obj.target)
+  );
+};
+
+/**
+ * Small JavaScript module for the documentation.
+ */
+const SphinxHighlight = {
+
+  /**
+   * highlight the search words provided in localstorage in the text
+   */
+  highlightSearchWords: () => {
+    if (!SPHINX_HIGHLIGHT_ENABLED) return;  // bail if no highlight
+
+    // get and clear terms from localstorage
+    const url = new URL(window.location);
+    const highlight =
+        localStorage.getItem("sphinx_highlight_terms")
+        || url.searchParams.get("highlight")
+        || "";
+    localStorage.removeItem("sphinx_highlight_terms")
+    url.searchParams.delete("highlight");
+    window.history.replaceState({}, "", url);
+
+    // get individual terms from highlight string
+    const terms = highlight.toLowerCase().split(/\s+/).filter(x => x);
+    if (terms.length === 0) return; // nothing to do
+
+    // There should never be more than one element matching "div.body"
+    const divBody = document.querySelectorAll("div.body");
+    const body = divBody.length ? divBody[0] : document.querySelector("body");
+    window.setTimeout(() => {
+      terms.forEach((term) => _highlightText(body, term, "highlighted"));
+    }, 10);
+
+    const searchBox = document.getElementById("searchbox");
+    if (searchBox === null) return;
+    searchBox.appendChild(
+      document
+        .createRange()
+        .createContextualFragment(
+          '<p class="highlight-link">' +
+            '<a href="javascript:SphinxHighlight.hideSearchWords()">' +
+            _("Hide Search Matches") +
+            "</a></p>"
+        )
+    );
+  },
+
+  /**
+   * helper function to hide the search marks again
+   */
+  hideSearchWords: () => {
+    document
+      .querySelectorAll("#searchbox .highlight-link")
+      .forEach((el) => el.remove());
+    document
+      .querySelectorAll("span.highlighted")
+      .forEach((el) => el.classList.remove("highlighted"));
+    localStorage.removeItem("sphinx_highlight_terms")
+  },
+
+  initEscapeListener: () => {
+    // only install a listener if it is really needed
+    if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return;
+
+    document.addEventListener("keydown", (event) => {
+      // bail for input elements
+      if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return;
+      // bail with special keys
+      if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return;
+      if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) {
+        SphinxHighlight.hideSearchWords();
+        event.preventDefault();
+      }
+    });
+  },
+};
+
+_ready(SphinxHighlight.highlightSearchWords);
+_ready(SphinxHighlight.initEscapeListener);
diff --git a/MANIFEST.in b/MANIFEST.in
index e9165f2..6d48c4d 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,5 +1,6 @@
-include geocode/gov/constituency_centroids_Dec2020.psv
-include geocode/gov/lad_centroids_May2021.psv
+include geocode/ons/constituency_centroids.psv
+include geocode/ons/constituency_centroids_Dec2020.psv
+include geocode/ons/lad_centroids_May2021.psv
+include geocode/ons/nrs.zip
+include geocode/ons/PCD_OA_LSOA_MSOA_LAD_MAY22_UK_LU.zip
 include geocode/code_point_open/codepo_gb.zip
-include geocode/ons_nrs/nrs.zip
-include geocode/ons_nrs/PCD_OA_LSOA_MSOA_LAD_MAY22_UK_LU.zip
\ No newline at end of file
diff --git a/README.md b/README.md
index ad0f8f0..168385a 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
 # Geocode
 
-Geocode various geographical entities including postcodes and LLSOAs. Reverse-geocode to LLSOA or GSP.
+Geocode various geographical entities including postcodes and LLSOAs. Reverse-geocode to LLSOA or GSP/GNode.
 
-*Latest Version: 0.10.5*
+*Latest Version: 0.11.0*
 
 ## What is this repository for?
 
@@ -10,7 +10,7 @@ Geocode various geographical entities including postcodes and LLSOAs. Reverse-ge
 * Use ONS & NRS LLSOA Population Weighted Centroids to geocode Lower Layer Super Output Areas.
 * Use GIS data from data.gov.uk to geocode GB constituencies based on geospatial centroid.
 * Use GIS boundaries data from ONS and NRS to reverse-geocode lat/lon to LLSOA.
-* Use GIS data from National Grid ESO's data portal to reverse-geocode to GSP. 
+* Use GIS data from National Grid ESO's data portal to reverse-geocode to GSP / GNode. 
 
 ## Benefits
 * Prioritises Code Point Open for postcode lookup to save expensive GMaps API bills.
@@ -19,7 +19,7 @@ Geocode various geographical entities including postcodes and LLSOAs. Reverse-ge
 
 ## How do I get set up?
 
-Developed and tested with Python 3.7, should work for 3.6+.
+Developed and tested with Python 3.9, should work for 3.7+.
 
 Make sure you have Git installed - [Download Git](https://git-scm.com/downloads)
 
@@ -32,24 +32,25 @@ Check that the installation was successful by running the following command from
 This will print the helper for the limited command line interface which provides tools to help get set up and to clear the cache when needed:
 
 ```
-usage: geocode.py [-h] [--clear-cache] [--debug] [--setup] [--setup-eso]
+usage: geocode.py [-h] [--clear-cache] [--debug] [--setup SETUP [SETUP ...]]
                   [--load-cpo-zip </path/to/zip-file>] [--load-gmaps-key <gmaps-api-key>]
 
-This is a command line interface (CLI) for the Geocode module version 0.10.5.
+This is a command line interface (CLI) for the Geocode module version 0.11.0.
 
 optional arguments:
   -h, --help            show this help message and exit
   --clear-cache         Specify to delete the cache files.
   --debug               Geocode some sample postcodes/addresses/LLSOAs.
-  --setup               Force download all datasets to local cache (useful if running
-                        inside a Docker container i.e. run this as part of image build).
-  --setup-eso           Force download all ESO datasets to local cache.
+  --setup SETUP [SETUP ...]
+                        Force download all datasets to local cache (useful if running inside a
+                        Docker container i.e. run this as part of image build). Possible values
+                        are 'ngeso', 'cpo', 'ons' or 'all'.
   --load-cpo-zip </path/to/zip-file>
                         Load the Code Point Open data from a local zip file.
   --load-gmaps-key <gmaps-api-key>
                         Load a Google Maps API key.
 
-Jamie Taylor, 2019-10-08
+Jamie Taylor & Ethan Jones, 2019-10-08
 ```
 
 No additional set up is needed at this stage - the required datasets will be downloaded (or extracted from the packaged data) the first time you use the associated method. If you want to force the Geocode library to download/extract all data, you can run the following command:
@@ -73,6 +74,9 @@ All data required by this library is either packaged with the code or is downloa
 Within your Python code, I recommend using the context manager so that GMaps cache will be automatically flushed on exit. See `example.py`:
 
 ```Python
+import os
+import logging
+
 from geocode import Geocoder
 
 def main():
@@ -80,7 +84,7 @@ def main():
         # Geocode some postcodes / addresses...
         postcodes = ["S3 7RH", "S3 7", "S3", None, None, "S3 7RH"]
         addresses = [None, None, None, "Hicks Building, Sheffield", "Hicks", "Hicks Building"]
-        results = geocoder.geocode(postcodes, addresses)
+        results = geocoder.geocode(postcodes, "postcode", address=addresses)
         for postcode, address, (lat, lon, status) in zip(postcodes, addresses, results):
             print(f"Postcode: `{postcode}`    Address: `{address}`")
             if status == 0:
@@ -99,7 +103,7 @@ def main():
         for llsoa, (lat, lon) in zip(results, latlons):
             print(f"LATLON: {lat:.3f}, {lon:.3f}:")
             print(f"    `{llsoa}`")
-        # Reverse-geocode some lat/lons to GSP...
+        # Reverse-geocode some lat/lons to GSP/GNode...
         latlons = [(53.384, -1.467), (53.388, -1.470)]
         results = geocoder.reverse_geocode_gsp(latlons)
         for (lat, lon), region_id in zip(latlons, results):
@@ -113,6 +117,10 @@ def main():
             print(f"    {lat:.3f}, {lon:.3f}")
 
 if __name__ == "__main__":
+    log_fmt = "%(asctime)s [%(levelname)s] [%(filename)s:%(funcName)s] - %(message)s"
+    fmt = os.environ.get("GEOCODE_LOGGING_FMT", log_fmt)
+    datefmt = os.environ.get("GEOCODE_LOGGING_DATEFMT", "%Y-%m-%dT%H:%M:%SZ")
+    logging.basicConfig(format=fmt, datefmt=datefmt, level=os.environ.get("LOGLEVEL", "DEBUG"))
     main()
 ```
 
@@ -139,22 +147,22 @@ LATLON: 53.384, -1.467:
 LATLON: 53.388, -1.470:
     `E01033262`
 LATLON: 53.384, -1.467:
-    ('PITS_3', '_M')
+    179
+        {'ng_id': 310, 'ggd_id': 310, 'gnode_id': 310, 'gnode_name': 'PIT1', 'gnode_lat': 53.400459999999995, 'gnode_lon': -1.44215, 'gsp_id': 293, 'gsp_name': 'PITS_3', 'gsp_lat': 53.400459999999995, 'gsp_lon': -1.44215, 'dc_id': <NA>, 'dc_name': <NA>, 'dc_lat': <NA>, 'dc_lon': <NA>, 'region_id': 179, 'region_name': 'Pitsmoor', 'has_pv': 1, 'pes_id': 23, 'pes_name': '_M'}
+        {'ng_id': 311, 'ggd_id': 311, 'gnode_id': 311, 'gnode_name': 'PIT2', 'gnode_lat': 53.400459999999995, 'gnode_lon': -1.44215, 'gsp_id': 293, 'gsp_name': 'PITS_3', 'gsp_lat': 53.400459999999995, 'gsp_lon': -1.44215, 'dc_id': <NA>, 'dc_name': <NA>, 'dc_lat': <NA>, 'dc_lon': <NA>, 'region_id': 179, 'region_name': 'Pitsmoor', 'has_pv': 1, 'pes_id': 23, 'pes_name': '_M'}
 LATLON: 53.388, -1.470:
-    ('NEEP_3', '_M')
+    179
+        {'ng_id': 310, 'ggd_id': 310, 'gnode_id': 310, 'gnode_name': 'PIT1', 'gnode_lat': 53.400459999999995, 'gnode_lon': -1.44215, 'gsp_id': 293, 'gsp_name': 'PITS_3', 'gsp_lat': 53.400459999999995, 'gsp_lon': -1.44215, 'dc_id': <NA>, 'dc_name': <NA>, 'dc_lat': <NA>, 'dc_lon': <NA>, 'region_id': 179, 'region_name': 'Pitsmoor', 'has_pv': 1, 'pes_id': 23, 'pes_name': '_M'}
+        {'ng_id': 311, 'ggd_id': 311, 'gnode_id': 311, 'gnode_name': 'PIT2', 'gnode_lat': 53.400459999999995, 'gnode_lon': -1.44215, 'gsp_id': 293, 'gsp_name': 'PITS_3', 'gsp_lat': 53.400459999999995, 'gsp_lon': -1.44215, 'dc_id': <NA>, 'dc_name': <NA>, 'dc_lat': <NA>, 'dc_lon': <NA>, 'region_id': 179, 'region_name': 'Pitsmoor', 'has_pv': 1, 'pes_id': 23, 'pes_name': '_M'}
 Constituency: `Sheffield Central`
     53.376, -1.474
 Constituency: `Sheffield Hallam`
     53.382, -1.590
 ```
 
-It is also possible to avoid using the context manager, but if doing so you should manually call the `Geocoder.flush_gmaps_cache()` method when finished to take advantage of cached GMaps API queries.
-
 In the above example, `postcodes` and `addresses` are lists of strings, but it should be fine to use any iterator such as Numpy arrays or Pandas DataFrame columns, although the `geocode()` method will still return a list of tuples.
 
-When reverse-geocoding to GSP, the `reverse_geocode_gsp()` method will use the 20220314 GSP region definitions by default and return a list of tuples containing `[(GSPs, GSPGroup), (GSPs, GSPGroup), ...]`.
-
-An alternative method is preserved for reverse-geocoding to the 20181031 GSP region definitions: `reverse_geocode_gsp_20181031()`. This method takes identical inputs to the `reverse_geocode_gsp()` method but returns both a list of Region IDs and a corresponding list of GSP / GNodes etc. Since the relationship between Region:GSP:GNode is theoretically MANY:MANY:MANY, the second object returned is a list of lists of dicts. This is rather clunky and support for the 20181031 GSP region definitions will likely be removed in a future release. An alternative use case could disregard this second return object and instead make use of the `Geocoder.gsp_lookup` instance attribute - this is a Pandas DataFrame giving the full lookup between Regions / GSPs / GNodes / DNO License Areas (i.e. [this](https://data.nationalgrideso.com/system/gis-boundaries-for-gb-grid-supply-points/r/gsp_-_gnode_-_direct_connect_-_region_lookup) dataset on the ESO Data Portal).
+When reverse-geocoding to GSP, the `reverse_geocode_gsp()` method returns both a list of Region IDs and a corresponding list of GSP / GNodes etc. Since the relationship between Region:GSP:GNode is theoretically MANY:MANY:MANY, the second object returned is a list of lists of dicts. This is rather clunky and will likely be refined in a future release. An alternative use case could disregard this second return object and instead make use of the `Geocoder.gsp_lookup` instance attribute - this is a Pandas DataFrame giving the full lookup between Regions / GSPs / GNodes / DNO License Areas (i.e. [this](https://data.nationalgrideso.com/system/gis-boundaries-for-gb-grid-supply-points/r/gsp_-_gnode_-_direct_connect_-_region_lookup) dataset on the ESO Data Portal). In testing, the `reverse_geocode_gsp()` method was able to allocate ~1 million random lat/lons to the correct GSP in average wall-clock time of around 300 seconds.
 
 ### Command Line Utilities
 
@@ -184,7 +192,7 @@ Jamie Taylor, 2020-04-16
 
 #### latlons2gsp
 
-This utility can be used to load a CSV file containing latitudes and longitudes and to reverse-geocode them to GSPs.
+This utility can be used to load a CSV file containing latitudes and longitudes and to reverse-geocode them to GSPs/GNodes.
 
 ```
 >> latlons2gsp -h
@@ -339,7 +347,7 @@ The Geocode library makes use of the Westminster Parliamentary Constituencies (D
 
 ### NGESO Data Portal
 
-The Geocode library makes use of GSP GIS boundaries developed by Sheffield Solar. In May 2020, these region definitions were uploaded to National Grid ESO's Data Portal - see [here](https://data.nationalgrideso.com/system/gis-boundaries-for-gb-grid-supply-points). By default, the `geocoder.reverse_geocode_gsp()` method will use the 20220314 region definitions. The first time you make use of the `Geocoder.reverse_geocode_gsp()` method, the GIS data is downloaded from the Data Portal API at runtime.
+The Geocode library makes use of GSP/GNode GIS boundaries developed by Sheffield Solar. In May 2020, these region definitions were uploaded to National Grid ESO's Data Portal - see [here](https://data.nationalgrideso.com/system/gis-boundaries-for-gb-grid-supply-points). The first time you make use of the `Geocoder.reverse_geocode_gsp()` method, the GIS data is downloaded from the Data Portal API at runtime.
 
 Supported by National Grid ESO Open Data
 
diff --git a/docs/build/doctrees/environment.pickle b/docs/build/doctrees/environment.pickle
index f76c3e1..47857d5 100644
Binary files a/docs/build/doctrees/environment.pickle and b/docs/build/doctrees/environment.pickle differ
diff --git a/docs/build/doctrees/index.doctree b/docs/build/doctrees/index.doctree
index aae9d1a..fde14e0 100644
Binary files a/docs/build/doctrees/index.doctree and b/docs/build/doctrees/index.doctree differ
diff --git a/docs/build/doctrees/modules.doctree b/docs/build/doctrees/modules.doctree
index a9b2d17..b15ec2b 100644
Binary files a/docs/build/doctrees/modules.doctree and b/docs/build/doctrees/modules.doctree differ
diff --git a/docs/build/html/.buildinfo b/docs/build/html/.buildinfo
index 2b3d444..1be7dbe 100644
--- a/docs/build/html/.buildinfo
+++ b/docs/build/html/.buildinfo
@@ -1,4 +1,4 @@
 # Sphinx build info version 1
 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
-config: 9b508ebd62eb4a756beb78718439adf8
+config: 1963b8917f6e7f4703d7aa66c7c8a4dd
 tags: 645f666f9bcd5a90fca523b33c5a78b7
diff --git a/docs/build/html/_modules/geocode.html b/docs/build/html/_modules/geocode.html
index e331bd3..6264c7b 100644
--- a/docs/build/html/_modules/geocode.html
+++ b/docs/build/html/_modules/geocode.html
@@ -3,7 +3,7 @@
 <head>
   <meta charset="utf-8" />
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-  <title>geocode &mdash; Geocode 0.10.5 documentation</title>
+  <title>geocode &mdash; Geocode 0.11.0 documentation</title>
       <link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
       <link rel="stylesheet" href="../_static/css/theme.css" type="text/css" />
   <!--[if lt IE 9]>
@@ -70,35 +70,24 @@ <h1>Source code for geocode</h1><div class="highlight"><pre>
 <span></span><span class="ch">#!/usr/bin/env python3</span>
 
 <span class="sd">&quot;&quot;&quot;</span>
-<span class="sd">A lightweight geocoder that uses OS Code Point Open where possible for postcodes and GMaps API for</span>
-<span class="sd">everything else.</span>
+<span class="sd">Geocode various geographical entities including postcodes and LLSOAs. Reverse-geocode to LLSOA or GSP.</span>
+
 <span class="sd">- Jamie Taylor &lt;jamie.taylor@sheffield.ac.uk&gt;</span>
 <span class="sd">- Ethan Jones &lt;ejones18@sheffield.ac.uk&gt;</span>
 <span class="sd">- First Authored: 2019-10-08</span>
 <span class="sd">&quot;&quot;&quot;</span>
 
-<span class="n">__version__</span> <span class="o">=</span> <span class="s2">&quot;0.10.5&quot;</span>
-
 <span class="kn">import</span> <span class="nn">os</span>
 <span class="kn">import</span> <span class="nn">sys</span>
-<span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">Iterable</span><span class="p">,</span> <span class="n">Tuple</span><span class="p">,</span> <span class="n">Union</span><span class="p">,</span> <span class="n">List</span><span class="p">,</span> <span class="n">Dict</span>
 <span class="kn">import</span> <span class="nn">logging</span>
-<span class="kn">import</span> <span class="nn">pickle</span>
 <span class="kn">import</span> <span class="nn">time</span> <span class="k">as</span> <span class="nn">TIME</span>
 <span class="kn">import</span> <span class="nn">argparse</span>
-<span class="kn">import</span> <span class="nn">zipfile</span>
+<span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span>
 <span class="kn">from</span> <span class="nn">shutil</span> <span class="kn">import</span> <span class="n">copyfile</span>
-<span class="kn">from</span> <span class="nn">io</span> <span class="kn">import</span> <span class="n">StringIO</span>
-<span class="kn">import</span> <span class="nn">json</span>
-<span class="kn">import</span> <span class="nn">csv</span>
-<span class="kn">import</span> <span class="nn">glob</span>
-<span class="kn">import</span> <span class="nn">requests</span>
-<span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span>
-<span class="kn">import</span> <span class="nn">pandas</span> <span class="k">as</span> <span class="nn">pd</span>
-<span class="kn">import</span> <span class="nn">geopandas</span> <span class="k">as</span> <span class="nn">gpd</span>
-<span class="kn">import</span> <span class="nn">googlemaps</span>
+
+<span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">Iterable</span><span class="p">,</span> <span class="n">Tuple</span><span class="p">,</span> <span class="n">Union</span><span class="p">,</span> <span class="n">List</span><span class="p">,</span> <span class="n">Dict</span>
 <span class="kn">import</span> <span class="nn">pyproj</span>
-<span class="kn">import</span> <span class="nn">shapefile</span>
+
 <span class="k">try</span><span class="p">:</span>
     <span class="kn">from</span> <span class="nn">shapely.geometry</span> <span class="kn">import</span> <span class="n">shape</span><span class="p">,</span> <span class="n">Point</span>
     <span class="kn">from</span> <span class="nn">shapely.ops</span> <span class="kn">import</span> <span class="n">unary_union</span>
@@ -110,16 +99,13 @@ <h1>Source code for geocode</h1><div class="highlight"><pre>
 
 <span class="n">SCRIPT_DIR</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">realpath</span><span class="p">(</span><span class="vm">__file__</span><span class="p">))</span>
 
-<span class="k">class</span> <span class="nc">GenericException</span><span class="p">(</span><span class="ne">Exception</span><span class="p">):</span>
-<span class="w">    </span><span class="sd">&quot;&quot;&quot;A generic exception for anticipated errors.&quot;&quot;&quot;</span>
-    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">msg</span><span class="p">,</span> <span class="n">err</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">msg</span> <span class="o">=</span> <span class="n">msg</span>
-        <span class="k">if</span> <span class="n">err</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
-            <span class="bp">self</span><span class="o">.</span><span class="n">msg</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&quot;</span><span class="se">\n</span><span class="si">{</span><span class="nb">repr</span><span class="p">(</span><span class="n">err</span><span class="p">)</span><span class="si">}</span><span class="s2">&quot;</span>
-        <span class="n">logging</span><span class="o">.</span><span class="n">exception</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">msg</span><span class="p">)</span>
-
-    <span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
-        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">msg</span>
+<span class="kn">import</span> <span class="nn">utilities</span> <span class="k">as</span> <span class="nn">utils</span>
+<span class="kn">import</span> <span class="nn">cpo</span>
+<span class="kn">import</span> <span class="nn">ngeso</span>
+<span class="kn">import</span> <span class="nn">ons_nrs</span>
+<span class="kn">import</span> <span class="nn">gmaps</span>
+<span class="kn">import</span> <span class="nn">cache_manager</span>
+<span class="kn">from</span> <span class="nn">version</span> <span class="kn">import</span> <span class="n">__version__</span>
 
 <div class="viewcode-block" id="Geocoder"><a class="viewcode-back" href="../modules.html#geocode.Geocoder">[docs]</a><span class="k">class</span> <span class="nc">Geocoder</span><span class="p">:</span>
 <span class="w">    </span><span class="sd">&quot;&quot;&quot;</span>
@@ -127,8 +113,7 @@ <h1>Source code for geocode</h1><div class="highlight"><pre>
 <span class="sd">    longitudes.</span>
 <span class="sd">    &quot;&quot;&quot;</span>
     <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span>
-                 <span class="n">progress_bar</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span><span class="p">,</span>
-                 <span class="n">prefix</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
+                 <span class="n">cache_dir</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
                  <span class="n">gmaps_key_file</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
 <span class="w">        </span><span class="sd">&quot;&quot;&quot;</span>
 <span class="sd">        Geocode addresses, postcodes, LLSOAs or Constituencies or reverse-geocode latitudes and</span>
@@ -136,81 +121,17 @@ <h1>Source code for geocode</h1><div class="highlight"><pre>
 
 <span class="sd">        Parameters</span>
 <span class="sd">        ----------</span>
-<span class="sd">        `progress_bar` : boolean</span>
-<span class="sd">            Optionally print a progress bar to stdout during iterations. Defaults to False.</span>
-<span class="sd">        `prefix` : string</span>
-<span class="sd">            Optionally add a prefix string to the progress bar.</span>
+<span class="sd">        `cache_dir` : string</span>
+<span class="sd">            Optionally specify a directory to use for caching.</span>
 <span class="sd">        `gmaps_key_file` : string</span>
 <span class="sd">            Path to an API key file for Google Maps Geocode API.</span>
 <span class="sd">        &quot;&quot;&quot;</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">prefix</span> <span class="o">=</span> <span class="n">prefix</span> <span class="k">if</span> <span class="n">prefix</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="k">else</span> <span class="s2">&quot;&quot;</span>
-        <span class="n">version_string</span> <span class="o">=</span> <span class="n">__version__</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&quot;.&quot;</span><span class="p">,</span> <span class="s2">&quot;-&quot;</span><span class="p">)</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">gmaps_dir</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">SCRIPT_DIR</span><span class="p">,</span> <span class="s2">&quot;google_maps&quot;</span><span class="p">)</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">gmaps_cache_file</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">gmaps_dir</span><span class="p">,</span> <span class="s2">&quot;gmaps_cache.p&quot;</span><span class="p">)</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">cpo_dir</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">SCRIPT_DIR</span><span class="p">,</span> <span class="s2">&quot;code_point_open&quot;</span><span class="p">)</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">cache_dir</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">SCRIPT_DIR</span><span class="p">,</span> <span class="s2">&quot;cache&quot;</span><span class="p">)</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">cpo_zipfile</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">cpo_dir</span><span class="p">,</span> <span class="s2">&quot;codepo_gb.zip&quot;</span><span class="p">)</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">cpo_cache_file</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">cpo_dir</span><span class="p">,</span>
-                                           <span class="sa">f</span><span class="s2">&quot;code_point_open_</span><span class="si">{</span><span class="n">version_string</span><span class="si">}</span><span class="s2">.p&quot;</span><span class="p">)</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">ons_dir</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">SCRIPT_DIR</span><span class="p">,</span> <span class="s2">&quot;ons_nrs&quot;</span><span class="p">)</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">eso_dir</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">SCRIPT_DIR</span><span class="p">,</span> <span class="s2">&quot;ngeso&quot;</span><span class="p">)</span>
-        <span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isdir</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">ons_dir</span><span class="p">):</span>
-            <span class="n">os</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">ons_dir</span><span class="p">)</span>
-        <span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isdir</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">eso_dir</span><span class="p">):</span>
-            <span class="n">os</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">eso_dir</span><span class="p">)</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">llsoa_cache_file</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">ons_dir</span><span class="p">,</span>
-                                             <span class="sa">f</span><span class="s2">&quot;llsoa_centroids_</span><span class="si">{</span><span class="n">version_string</span><span class="si">}</span><span class="s2">.p&quot;</span><span class="p">)</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">llsoa_boundaries_cache_file</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span>
-            <span class="bp">self</span><span class="o">.</span><span class="n">ons_dir</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&quot;llsoa_boundaries_</span><span class="si">{</span><span class="n">version_string</span><span class="si">}</span><span class="s2">.p&quot;</span>
-        <span class="p">)</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">gsp_boundaries_20181031_cache_file</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span>
-            <span class="bp">self</span><span class="o">.</span><span class="n">eso_dir</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&quot;gsp_boundaries_20181031_</span><span class="si">{</span><span class="n">version_string</span><span class="si">}</span><span class="s2">.p&quot;</span>
-        <span class="p">)</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">gsp_boundaries_20220314_cache_file</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span>
-            <span class="bp">self</span><span class="o">.</span><span class="n">eso_dir</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&quot;gsp_boundaries_20220314_</span><span class="si">{</span><span class="n">version_string</span><span class="si">}</span><span class="s2">.p&quot;</span>
-        <span class="p">)</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">dno_boundaries_cache_file</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span>
-            <span class="bp">self</span><span class="o">.</span><span class="n">eso_dir</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&quot;dno_boundaries_</span><span class="si">{</span><span class="n">version_string</span><span class="si">}</span><span class="s2">.p&quot;</span>
-        <span class="p">)</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">gsp_lookup_20181031_cache_file</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span>
-            <span class="bp">self</span><span class="o">.</span><span class="n">eso_dir</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&quot;gsp_lookup_20181031_</span><span class="si">{</span><span class="n">version_string</span><span class="si">}</span><span class="s2">.p&quot;</span>
-        <span class="p">)</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">dz_lookup_cache_file</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">ons_dir</span><span class="p">,</span>
-                                                 <span class="sa">f</span><span class="s2">&quot;datazone_lookup_</span><span class="si">{</span><span class="n">version_string</span><span class="si">}</span><span class="s2">.p&quot;</span><span class="p">)</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">nrs_zipfile</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">ons_dir</span><span class="p">,</span> <span class="s2">&quot;nrs.zip&quot;</span><span class="p">)</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">pc_llsoa_lookup_cache_file</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">ons_dir</span><span class="p">,</span>
-                                                       <span class="sa">f</span><span class="s2">&quot;pc_llsoa_lookup_</span><span class="si">{</span><span class="n">version_string</span><span class="si">}</span><span class="s2">.p&quot;</span><span class="p">)</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">pc_llsoa_zipfile</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">ons_dir</span><span class="p">,</span>
-                                             <span class="s2">&quot;PCD_OA_LSOA_MSOA_LAD_MAY22_UK_LU.zip&quot;</span><span class="p">)</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">gov_dir</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">SCRIPT_DIR</span><span class="p">,</span> <span class="s2">&quot;gov&quot;</span><span class="p">)</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">constituency_lookup_file</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">gov_dir</span><span class="p">,</span>
-                                                     <span class="s2">&quot;constituency_centroids_Dec2020.psv&quot;</span><span class="p">)</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">constituency_cache_file</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">gov_dir</span><span class="p">,</span>
-                                                    <span class="sa">f</span><span class="s2">&quot;constituency_centroids_</span><span class="si">{</span><span class="n">version_string</span><span class="si">}</span><span class="s2">.p&quot;</span><span class="p">)</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">lad_lookup_file</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">gov_dir</span><span class="p">,</span> <span class="s2">&quot;lad_centroids_May2021.psv&quot;</span><span class="p">)</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">lad_cache_file</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">gov_dir</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&quot;lad_centroids_</span><span class="si">{</span><span class="n">version_string</span><span class="si">}</span><span class="s2">.p&quot;</span><span class="p">)</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">gmaps_key_file</span> <span class="o">=</span> <span class="n">gmaps_key_file</span> <span class="k">if</span> <span class="n">gmaps_key_file</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> \
-                                  <span class="k">else</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">gmaps_dir</span><span class="p">,</span> <span class="s2">&quot;key.txt&quot;</span><span class="p">)</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">gmaps_key</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_load_gmaps_key</span><span class="p">()</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">llsoa_lookup</span> <span class="o">=</span> <span class="kc">None</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">llsoa_regions</span> <span class="o">=</span> <span class="kc">None</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">gsp_regions</span> <span class="o">=</span> <span class="kc">None</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">gsp_regions_20181031</span> <span class="o">=</span> <span class="kc">None</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">dno_regions</span> <span class="o">=</span> <span class="kc">None</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">gsp_lookup_20181031</span> <span class="o">=</span> <span class="kc">None</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">llsoa_reverse_lookup</span> <span class="o">=</span> <span class="kc">None</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">constituency_lookup</span> <span class="o">=</span> <span class="kc">None</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">lad_lookup</span> <span class="o">=</span> <span class="kc">None</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">pc_llsoa_lookup</span> <span class="o">=</span> <span class="kc">None</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">dz_lookup</span> <span class="o">=</span> <span class="kc">None</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">cpo</span> <span class="o">=</span> <span class="kc">None</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">gmaps</span> <span class="o">=</span> <span class="kc">None</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">gmaps_cache</span> <span class="o">=</span> <span class="kc">None</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">timer</span> <span class="o">=</span> <span class="n">TIME</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">progress_bar</span> <span class="o">=</span> <span class="n">progress_bar</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">cache_file</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">cache_dir</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&quot;cache_</span><span class="si">{</span><span class="n">version_string</span><span class="si">}</span><span class="s2">.p&quot;</span><span class="p">)</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">clear_cache</span><span class="p">(</span><span class="n">delete_gmaps_cache</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">old_versions_only</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">cache</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_load_cache</span><span class="p">()</span>
+        <span class="bp">self</span><span class="o">.</span><span class="n">cache_manager</span> <span class="o">=</span> <span class="n">cache_manager</span><span class="o">.</span><span class="n">CacheManager</span><span class="p">(</span><span class="n">cache_dir</span><span class="p">)</span>
+        <span class="bp">self</span><span class="o">.</span><span class="n">cache_manager</span><span class="o">.</span><span class="n">clear</span><span class="p">(</span><span class="n">delete_gmaps_cache</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">old_versions_only</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
+        <span class="bp">self</span><span class="o">.</span><span class="n">cpo</span> <span class="o">=</span> <span class="n">cpo</span><span class="o">.</span><span class="n">CodePointOpen</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">cache_manager</span><span class="p">)</span>
+        <span class="bp">self</span><span class="o">.</span><span class="n">ngeso</span> <span class="o">=</span> <span class="n">ngeso</span><span class="o">.</span><span class="n">NationalGrid</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">cache_manager</span><span class="p">)</span>
+        <span class="bp">self</span><span class="o">.</span><span class="n">ons_nrs</span> <span class="o">=</span> <span class="n">ons_nrs</span><span class="o">.</span><span class="n">ONS_NRS</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">cache_manager</span><span class="p">)</span>
+        <span class="bp">self</span><span class="o">.</span><span class="n">gmaps</span> <span class="o">=</span> <span class="n">gmaps</span><span class="o">.</span><span class="n">GMaps</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">cache_manager</span><span class="p">,</span> <span class="n">gmaps_key_file</span><span class="p">)</span>
         <span class="bp">self</span><span class="o">.</span><span class="n">status_codes</span> <span class="o">=</span> <span class="p">{</span>
             <span class="mi">0</span><span class="p">:</span> <span class="s2">&quot;Failed&quot;</span><span class="p">,</span>
             <span class="mi">1</span><span class="p">:</span> <span class="s2">&quot;Full match with Code Point Open&quot;</span><span class="p">,</span>
@@ -223,352 +144,18 @@ <h1>Source code for geocode</h1><div class="highlight"><pre>
 <span class="w">        </span><span class="sd">&quot;&quot;&quot;Context manager.&quot;&quot;&quot;</span>
         <span class="k">return</span> <span class="bp">self</span>
 
-    <span class="k">def</span> <span class="fm">__exit__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">):</span>
-<span class="w">        </span><span class="sd">&quot;&quot;&quot;Context manager - flush GMaps cache on exit.&quot;&quot;&quot;</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">_flush_gmaps_cache</span><span class="p">()</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">_flush_cache</span><span class="p">()</span>
-
-<div class="viewcode-block" id="Geocoder.clear_cache"><a class="viewcode-back" href="../modules.html#geocode.Geocoder.clear_cache">[docs]</a>    <span class="k">def</span> <span class="nf">clear_cache</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span>
-                    <span class="n">delete_gmaps_cache</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
-                    <span class="n">old_versions_only</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
-<span class="w">        </span><span class="sd">&quot;&quot;&quot;</span>
-<span class="sd">        Clear any cache files from the installation directory including from old versions.</span>
-
-<span class="sd">        Parameters</span>
-<span class="sd">        ----------</span>
-<span class="sd">        delete_gmaps_cache : boolean</span>
-<span class="sd">            Optional boolean deciding whether or not to clear the Google Maps data in cache.</span>
-<span class="sd">            Defaults to None, resulting in stdout/stdin prompts.</span>
-<span class="sd">        old_versions_only : boolean</span>
-<span class="sd">            Optional boolean deciding whether or not to clear all cache files or just those</span>
-<span class="sd">            corresponding to previous versions of the Geocode library. Defaults to False.</span>
-<span class="sd">        &quot;&quot;&quot;</span>
-        <span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Deleting cache files (delete_gmaps_cache=</span><span class="si">%s</span><span class="s2">, old_versions_only=</span><span class="si">%s</span><span class="s2">)&quot;</span><span class="p">,</span>
-                      <span class="n">delete_gmaps_cache</span><span class="p">,</span> <span class="n">old_versions_only</span><span class="p">)</span>
-        <span class="k">if</span> <span class="n">delete_gmaps_cache</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
-            <span class="n">delete_gmaps_cache</span> <span class="o">=</span> <span class="n">query_yes_no</span><span class="p">(</span><span class="s2">&quot;Do you want to delete the GMaps cache?&quot;</span><span class="p">,</span> <span class="s2">&quot;no&quot;</span><span class="p">)</span>
-        <span class="k">if</span> <span class="n">delete_gmaps_cache</span><span class="p">:</span>
-            <span class="n">gmaps_cache_files</span> <span class="o">=</span> <span class="n">glob</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">gmaps_dir</span><span class="p">,</span> <span class="s2">&quot;gmaps_cache.p&quot;</span><span class="p">))</span>
-            <span class="k">for</span> <span class="n">gmaps_cache_file</span> <span class="ow">in</span> <span class="n">gmaps_cache_files</span><span class="p">:</span>
-                <span class="n">os</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="n">gmaps_cache_file</span><span class="p">)</span>
-                <span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Deleted &#39;</span><span class="si">%s</span><span class="s2">&#39;&quot;</span><span class="p">,</span> <span class="n">gmaps_cache_file</span><span class="p">)</span>
-        <span class="n">cache_files</span> <span class="o">=</span> <span class="n">glob</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">cache_dir</span><span class="p">,</span> <span class="s2">&quot;cache_*.p&quot;</span><span class="p">))</span> <span class="o">+</span> \
-                      <span class="n">glob</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">cpo_dir</span><span class="p">,</span> <span class="s2">&quot;code_point_open_*.p&quot;</span><span class="p">))</span> <span class="o">+</span> \
-                      <span class="n">glob</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">ons_dir</span><span class="p">,</span> <span class="s2">&quot;llsoa_centroids_*.p&quot;</span><span class="p">))</span> <span class="o">+</span> \
-                      <span class="n">glob</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">ons_dir</span><span class="p">,</span> <span class="s2">&quot;llsoa_boundaries_*.p&quot;</span><span class="p">))</span> <span class="o">+</span> \
-                      <span class="n">glob</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">eso_dir</span><span class="p">,</span> <span class="s2">&quot;gsp_boundaries_*.p&quot;</span><span class="p">))</span> <span class="o">+</span> \
-                      <span class="n">glob</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">eso_dir</span><span class="p">,</span> <span class="s2">&quot;dno_boundaries_*.p&quot;</span><span class="p">))</span> <span class="o">+</span> \
-                      <span class="n">glob</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">eso_dir</span><span class="p">,</span> <span class="s2">&quot;gsp_lookup_*.p&quot;</span><span class="p">))</span> <span class="o">+</span> \
-                      <span class="n">glob</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">ons_dir</span><span class="p">,</span> <span class="s2">&quot;datazone_lookup_*.p&quot;</span><span class="p">))</span> <span class="o">+</span> \
-                      <span class="n">glob</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">ons_dir</span><span class="p">,</span> <span class="s2">&quot;pc_llsoa_lookup_*.p&quot;</span><span class="p">))</span> <span class="o">+</span> \
-                      <span class="n">glob</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">gov_dir</span><span class="p">,</span> <span class="s2">&quot;constituency_centroids_*.p&quot;</span><span class="p">))</span> <span class="o">+</span> \
-                      <span class="n">glob</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">gov_dir</span><span class="p">,</span> <span class="s2">&quot;lad_centroids_*.p&quot;</span><span class="p">))</span>
-        <span class="k">for</span> <span class="n">cache_file</span> <span class="ow">in</span> <span class="n">cache_files</span><span class="p">:</span>
-            <span class="k">if</span> <span class="n">old_versions_only</span> <span class="ow">and</span> <span class="n">__version__</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&quot;.&quot;</span><span class="p">,</span> <span class="s2">&quot;-&quot;</span><span class="p">)</span> <span class="ow">in</span> <span class="n">cache_file</span><span class="p">:</span>
-                <span class="k">continue</span>
-            <span class="n">os</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="n">cache_file</span><span class="p">)</span>
-            <span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Deleted &#39;</span><span class="si">%s</span><span class="s2">&#39;&quot;</span><span class="p">,</span> <span class="n">cache_file</span><span class="p">)</span></div>
+    <span class="k">def</span> <span class="fm">__exit__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="nb">type</span><span class="p">,</span> <span class="n">value</span><span class="p">,</span> <span class="n">traceback</span><span class="p">):</span>
+<span class="w">        </span><span class="sd">&quot;&quot;&quot;Context manager.&quot;&quot;&quot;</span>
+        <span class="k">pass</span>
 
-<div class="viewcode-block" id="Geocoder.force_setup"><a class="viewcode-back" href="../modules.html#geocode.Geocoder.force_setup">[docs]</a>    <span class="k">def</span> <span class="nf">force_setup</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
+<div class="viewcode-block" id="Geocoder.force_setup"><a class="viewcode-back" href="../modules.html#geocode.Geocoder.force_setup">[docs]</a>    <span class="k">def</span> <span class="nf">force_setup</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">ngeso_setup</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">cpo_setup</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">ons_setup</span><span class="o">=</span><span class="kc">True</span><span class="p">):</span>
 <span class="w">        </span><span class="sd">&quot;&quot;&quot;Download all data and setup caches.&quot;&quot;&quot;</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">_load_code_point_open</span><span class="p">(</span><span class="n">force_reload</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">_load_llsoa_lookup</span><span class="p">()</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">_load_llsoa_boundaries</span><span class="p">()</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">_load_datazone_lookup</span><span class="p">()</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">_load_postcode_llsoa_lookup</span><span class="p">()</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">_load_constituency_lookup</span><span class="p">()</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">_load_lad_lookup</span><span class="p">()</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">force_setup_eso</span><span class="p">()</span></div>
-
-<div class="viewcode-block" id="Geocoder.force_setup_eso"><a class="viewcode-back" href="../modules.html#geocode.Geocoder.force_setup_eso">[docs]</a>    <span class="k">def</span> <span class="nf">force_setup_eso</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
-<span class="w">        </span><span class="sd">&quot;&quot;&quot;Download all ESO data and setup caches.&quot;&quot;&quot;</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">_load_gsp_boundaries_20220314</span><span class="p">()</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">_load_dno_boundaries</span><span class="p">()</span></div>
-
-    <span class="k">def</span> <span class="nf">_load_gmaps_key</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
-<span class="w">        </span><span class="sd">&quot;&quot;&quot;Load the user&#39;s GMaps API key from installation directory.&quot;&quot;&quot;</span>
-        <span class="k">try</span><span class="p">:</span>
-            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">gmaps_key_file</span><span class="p">)</span> <span class="k">as</span> <span class="n">fid</span><span class="p">:</span>
-                <span class="n">key</span> <span class="o">=</span> <span class="n">fid</span><span class="o">.</span><span class="n">read</span><span class="p">()</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
-        <span class="k">except</span> <span class="ne">FileNotFoundError</span><span class="p">:</span>
-            <span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="s2">&quot;Failed to load Google Maps API key from &#39;</span><span class="si">%s</span><span class="s2">&#39; - you will not be able &quot;</span>
-                            <span class="s2">&quot;to make new queries to the Google Maps API!&quot;</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">gmaps_key_file</span><span class="p">)</span>
-            <span class="k">return</span> <span class="kc">None</span>
-        <span class="k">return</span> <span class="n">key</span>
-
-    <span class="k">def</span> <span class="nf">_load_gmaps_cache</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
-<span class="w">        </span><span class="sd">&quot;&quot;&quot;Load the cache of prior GMaps queries to avoid repeated API queries.&quot;&quot;&quot;</span>
-        <span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isfile</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">gmaps_cache_file</span><span class="p">):</span>
-            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">gmaps_cache_file</span><span class="p">,</span> <span class="s2">&quot;rb&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">pickle_fid</span><span class="p">:</span>
-                <span class="k">return</span> <span class="n">pickle</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">pickle_fid</span><span class="p">)</span>
-        <span class="k">else</span><span class="p">:</span>
-            <span class="k">return</span> <span class="p">{}</span>
-
-    <span class="k">def</span> <span class="nf">_load_cache</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
-<span class="w">        </span><span class="sd">&quot;&quot;&quot;Load the cache of prior addresses/postcodes for better performance.&quot;&quot;&quot;</span>
-        <span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isfile</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">cache_file</span><span class="p">):</span>
-            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">cache_file</span><span class="p">,</span> <span class="s2">&quot;rb&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">pickle_fid</span><span class="p">:</span>
-                <span class="k">return</span> <span class="n">pickle</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">pickle_fid</span><span class="p">)</span>
-        <span class="k">else</span><span class="p">:</span>
-            <span class="k">return</span> <span class="n">pd</span><span class="o">.</span><span class="n">DataFrame</span><span class="p">({</span><span class="s2">&quot;input_postcode&quot;</span><span class="p">:</span> <span class="p">[],</span> <span class="s2">&quot;input_address&quot;</span><span class="p">:</span> <span class="p">[],</span> <span class="s2">&quot;latitude&quot;</span><span class="p">:</span> <span class="p">[],</span>
-                                 <span class="s2">&quot;longitude&quot;</span><span class="p">:</span> <span class="p">[],</span> <span class="s2">&quot;match_status&quot;</span><span class="p">:</span> <span class="p">[]})</span>
-
-    <span class="k">def</span> <span class="nf">_flush_gmaps_cache</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
-<span class="w">        </span><span class="sd">&quot;&quot;&quot;Flush any new GMaps API queries to the GMaps cache.&quot;&quot;&quot;</span>
-        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">gmaps_cache</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
-            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">gmaps_cache_file</span><span class="p">,</span> <span class="s2">&quot;wb&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">pickle_fid</span><span class="p">:</span>
-                <span class="n">pickle</span><span class="o">.</span><span class="n">dump</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">gmaps_cache</span><span class="p">,</span> <span class="n">pickle_fid</span><span class="p">)</span>
-
-    <span class="k">def</span> <span class="nf">_flush_cache</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
-<span class="w">        </span><span class="sd">&quot;&quot;&quot;Flush any new address/postcode queries to the query cache.&quot;&quot;&quot;</span>
-        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">cache</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
-            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">cache_file</span><span class="p">,</span> <span class="s2">&quot;wb&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">pickle_fid</span><span class="p">:</span>
-                <span class="n">pickle</span><span class="o">.</span><span class="n">dump</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">cache</span><span class="p">,</span> <span class="n">pickle_fid</span><span class="p">)</span>
-
-    <span class="k">def</span> <span class="nf">_load_code_point_open</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">force_reload</span><span class="o">=</span><span class="kc">False</span><span class="p">):</span>
-<span class="w">        </span><span class="sd">&quot;&quot;&quot;Load the OS Code Point Open Database, either from raw zip file or local cache.&quot;&quot;&quot;</span>
-        <span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isfile</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">cpo_cache_file</span><span class="p">)</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">force_reload</span><span class="p">:</span>
-            <span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Loading Code Point Open data from cache (&#39;</span><span class="si">%s</span><span class="s2">&#39;)&quot;</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">cpo_cache_file</span><span class="p">)</span>
-            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">cpo_cache_file</span><span class="p">,</span> <span class="s2">&quot;rb&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">pickle_fid</span><span class="p">:</span>
-                <span class="k">return</span> <span class="n">pickle</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">pickle_fid</span><span class="p">)</span>
-        <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;Extracting the Code Point Open data (this only needs to be done once)&quot;</span><span class="p">)</span>
-        <span class="k">if</span> <span class="ow">not</span> <span class="n">zipfile</span><span class="o">.</span><span class="n">is_zipfile</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">cpo_zipfile</span><span class="p">):</span>
-            <span class="k">raise</span> <span class="n">GenericException</span><span class="p">(</span><span class="s2">&quot;Could not find the OS Code Point Open data: &quot;</span>
-                                   <span class="sa">f</span><span class="s2">&quot;&#39;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">cpo_zipfile</span><span class="si">}</span><span class="s2">&#39;&quot;</span><span class="p">)</span>
-        <span class="n">columns</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&quot;Postcode&quot;</span><span class="p">,</span> <span class="s2">&quot;Positional_quality_indicator&quot;</span><span class="p">,</span> <span class="s2">&quot;Eastings&quot;</span><span class="p">,</span> <span class="s2">&quot;Northings&quot;</span><span class="p">,</span>
-                   <span class="s2">&quot;Country_code&quot;</span><span class="p">,</span> <span class="s2">&quot;NHS_regional_HA_code&quot;</span><span class="p">,</span> <span class="s2">&quot;NHS_HA_code&quot;</span><span class="p">,</span> <span class="s2">&quot;Admin_county_code&quot;</span><span class="p">,</span>
-                   <span class="s2">&quot;Admin_district_code&quot;</span><span class="p">,</span> <span class="s2">&quot;Admin_ward_code&quot;</span><span class="p">]</span>
-        <span class="n">dtypes</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;Postcode&quot;</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="s2">&quot;Eastings&quot;</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="s2">&quot;Northings&quot;</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span>
-                  <span class="s2">&quot;Positional_quality_indicator&quot;</span><span class="p">:</span> <span class="nb">int</span><span class="p">}</span>
-        <span class="n">cpo</span> <span class="o">=</span> <span class="kc">None</span>
-        <span class="k">with</span> <span class="n">zipfile</span><span class="o">.</span><span class="n">ZipFile</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">cpo_zipfile</span><span class="p">,</span> <span class="s2">&quot;r&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">cpo_zip</span><span class="p">:</span>
-            <span class="k">for</span> <span class="n">cpo_file</span> <span class="ow">in</span> <span class="n">cpo_zip</span><span class="o">.</span><span class="n">namelist</span><span class="p">():</span>
-                <span class="k">if</span> <span class="s2">&quot;Data/CSV/&quot;</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">cpo_file</span><span class="p">:</span>
-                    <span class="k">continue</span>
-                <span class="k">with</span> <span class="n">cpo_zip</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="n">cpo_file</span><span class="p">)</span> <span class="k">as</span> <span class="n">cpo_file_part</span><span class="p">:</span>
-                    <span class="n">data</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">read_csv</span><span class="p">(</span><span class="n">cpo_file_part</span><span class="p">,</span> <span class="n">names</span><span class="o">=</span><span class="n">columns</span><span class="p">,</span> <span class="n">dtype</span><span class="o">=</span><span class="n">dtypes</span><span class="p">,</span>
-                                       <span class="n">usecols</span><span class="o">=</span><span class="p">[</span><span class="s2">&quot;Postcode&quot;</span><span class="p">,</span> <span class="s2">&quot;Positional_quality_indicator&quot;</span><span class="p">,</span>
-                                                <span class="s2">&quot;Eastings&quot;</span><span class="p">,</span> <span class="s2">&quot;Northings&quot;</span><span class="p">])</span>
-                    <span class="n">cpo</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">concat</span><span class="p">([</span><span class="n">cpo</span><span class="p">,</span> <span class="n">data</span><span class="p">])</span> <span class="k">if</span> <span class="n">cpo</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="k">else</span> <span class="n">data</span>
-        <span class="n">cpo</span><span class="p">[</span><span class="s2">&quot;Postcode&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="n">cpo</span><span class="p">[</span><span class="s2">&quot;Postcode&quot;</span><span class="p">]</span><span class="o">.</span><span class="n">str</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&quot; &quot;</span><span class="p">,</span> <span class="s2">&quot;&quot;</span><span class="p">,</span> <span class="n">regex</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
-        <span class="n">cpo</span><span class="p">[</span><span class="s2">&quot;Postcode&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="n">cpo</span><span class="p">[</span><span class="s2">&quot;Postcode&quot;</span><span class="p">]</span><span class="o">.</span><span class="n">str</span><span class="o">.</span><span class="n">upper</span><span class="p">()</span>
-        <span class="n">nn_indices</span> <span class="o">=</span> <span class="n">cpo</span><span class="p">[</span><span class="s2">&quot;Eastings&quot;</span><span class="p">]</span><span class="o">.</span><span class="n">notnull</span><span class="p">()</span> <span class="o">&amp;</span> <span class="n">cpo</span><span class="p">[</span><span class="s2">&quot;Positional_quality_indicator&quot;</span><span class="p">]</span> <span class="o">&lt;</span> <span class="mi">90</span>
-        <span class="n">lons</span><span class="p">,</span> <span class="n">lats</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_bng2latlon</span><span class="p">(</span><span class="n">cpo</span><span class="o">.</span><span class="n">loc</span><span class="p">[</span><span class="n">nn_indices</span><span class="p">,</span> <span class="p">(</span><span class="s2">&quot;Eastings&quot;</span><span class="p">)]</span><span class="o">.</span><span class="n">to_numpy</span><span class="p">(),</span>
-                                      <span class="n">cpo</span><span class="o">.</span><span class="n">loc</span><span class="p">[</span><span class="n">nn_indices</span><span class="p">,</span> <span class="p">(</span><span class="s2">&quot;Northings&quot;</span><span class="p">)]</span><span class="o">.</span><span class="n">to_numpy</span><span class="p">())</span>
-        <span class="n">cpo</span><span class="o">.</span><span class="n">loc</span><span class="p">[</span><span class="n">nn_indices</span><span class="p">,</span> <span class="s2">&quot;longitude&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="n">lons</span>
-        <span class="n">cpo</span><span class="o">.</span><span class="n">loc</span><span class="p">[</span><span class="n">nn_indices</span><span class="p">,</span> <span class="s2">&quot;latitude&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="n">lats</span>
-        <span class="n">cpo</span><span class="p">[</span><span class="s2">&quot;outward_postcode&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="n">cpo</span><span class="p">[</span><span class="s2">&quot;Postcode&quot;</span><span class="p">]</span><span class="o">.</span><span class="n">str</span><span class="o">.</span><span class="n">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">3</span><span class="p">)</span><span class="o">.</span><span class="n">str</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
-        <span class="n">cpo</span><span class="p">[</span><span class="s2">&quot;inward_postcode&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="n">cpo</span><span class="p">[</span><span class="s2">&quot;Postcode&quot;</span><span class="p">]</span><span class="o">.</span><span class="n">str</span><span class="o">.</span><span class="n">slice</span><span class="p">(</span><span class="o">-</span><span class="mi">3</span><span class="p">)</span><span class="o">.</span><span class="n">str</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
-        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">cpo_cache_file</span><span class="p">,</span> <span class="s2">&quot;wb&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">pickle_fid</span><span class="p">:</span>
-            <span class="n">pickle</span><span class="o">.</span><span class="n">dump</span><span class="p">(</span><span class="n">cpo</span><span class="p">,</span> <span class="n">pickle_fid</span><span class="p">)</span>
-        <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;Code Point Open extracted and pickled to &#39;</span><span class="si">%s</span><span class="s2">&#39;&quot;</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">cpo_cache_file</span><span class="p">)</span>
-        <span class="k">return</span> <span class="n">cpo</span>
-
-    <span class="k">def</span> <span class="nf">_fetch_from_api</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">url</span><span class="p">):</span>
-<span class="w">        </span><span class="sd">&quot;&quot;&quot;Generic function to GET data from web API with retries.&quot;&quot;&quot;</span>
-        <span class="n">retries</span> <span class="o">=</span> <span class="mi">0</span>
-        <span class="k">while</span> <span class="n">retries</span> <span class="o">&lt;</span> <span class="mi">3</span><span class="p">:</span>
-            <span class="k">try</span><span class="p">:</span>
-                <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
-                <span class="n">response</span><span class="o">.</span><span class="n">raise_for_status</span><span class="p">()</span>
-                <span class="k">if</span> <span class="n">response</span><span class="o">.</span><span class="n">status_code</span> <span class="o">!=</span> <span class="mi">200</span><span class="p">:</span>
-                    <span class="n">retries</span> <span class="o">+=</span> <span class="mi">1</span>
-                    <span class="k">continue</span>
-                <span class="k">return</span> <span class="mi">1</span><span class="p">,</span> <span class="n">response</span>
-            <span class="k">except</span><span class="p">:</span>
-                <span class="n">retries</span> <span class="o">+=</span> <span class="mi">1</span>
-        <span class="k">return</span> <span class="mi">0</span><span class="p">,</span> <span class="kc">None</span>
-
-    <span class="k">def</span> <span class="nf">_load_llsoa_lookup</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
-<span class="w">        </span><span class="sd">&quot;&quot;&quot;Load the lookup of LLSOA -&gt; Population Weighted Centroid.&quot;&quot;&quot;</span>
-        <span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isfile</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">llsoa_cache_file</span><span class="p">):</span>
-            <span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Loading LLSOA lookup from cache (&#39;</span><span class="si">%s</span><span class="s2">&#39;)&quot;</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">llsoa_cache_file</span><span class="p">)</span>
-            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">llsoa_cache_file</span><span class="p">,</span> <span class="s2">&quot;rb&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">pickle_fid</span><span class="p">:</span>
-                <span class="k">return</span> <span class="n">pickle</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">pickle_fid</span><span class="p">)</span>
-        <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;Extracting the ONS and NRS LLSOA centroids data (this only needs to be done &quot;</span>
-                     <span class="s2">&quot;once)&quot;</span><span class="p">)</span>
-        <span class="c1"># ons_url = &quot;https://opendata.arcgis.com/datasets/b7c49538f0464f748dd7137247bbc41c_0.geojson&quot;</span>
-        <span class="n">ons_url</span> <span class="o">=</span> <span class="s2">&quot;https://services1.arcgis.com/ESMARspQHYMw9BZ9/arcgis/rest/services/LSOA_Dec_2011_PWC_in_England_and_Wales_2022/FeatureServer/0/query?outFields=*&amp;where=1%3D1&amp;f=geojson&quot;</span>
-        <span class="n">success</span><span class="p">,</span> <span class="n">api_response</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_fetch_from_api</span><span class="p">(</span><span class="n">ons_url</span><span class="p">)</span>
-        <span class="k">if</span> <span class="n">success</span><span class="p">:</span>
-            <span class="n">raw</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">api_response</span><span class="o">.</span><span class="n">text</span><span class="p">)</span>
-            <span class="n">engwales_lookup</span> <span class="o">=</span> <span class="p">{</span><span class="n">f</span><span class="p">[</span><span class="s2">&quot;properties&quot;</span><span class="p">][</span><span class="s2">&quot;lsoa11cd&quot;</span><span class="p">]:</span>
-                               <span class="nb">tuple</span><span class="p">(</span><span class="n">f</span><span class="p">[</span><span class="s2">&quot;geometry&quot;</span><span class="p">][</span><span class="s2">&quot;coordinates&quot;</span><span class="p">][::</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span>
-                               <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">raw</span><span class="p">[</span><span class="s2">&quot;features&quot;</span><span class="p">]}</span>
-        <span class="k">else</span><span class="p">:</span>
-            <span class="k">raise</span> <span class="n">GenericException</span><span class="p">(</span><span class="s2">&quot;Encountered an error while extracting LLSOA data from ONS API.&quot;</span><span class="p">)</span>
-        <span class="n">codes</span><span class="p">,</span> <span class="n">eastings</span><span class="p">,</span> <span class="n">northings</span> <span class="o">=</span> <span class="p">[],</span> <span class="p">[],</span> <span class="p">[]</span>
-        <span class="n">datazones</span><span class="p">,</span> <span class="n">dzeastings</span><span class="p">,</span> <span class="n">dznorthings</span> <span class="o">=</span> <span class="p">[],</span> <span class="p">[],</span> <span class="p">[]</span>
-        <span class="k">with</span> <span class="n">zipfile</span><span class="o">.</span><span class="n">ZipFile</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">nrs_zipfile</span><span class="p">,</span> <span class="s2">&quot;r&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">nrs_zip</span><span class="p">:</span>
-            <span class="k">with</span> <span class="n">nrs_zip</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="s2">&quot;OutputArea2011_PWC_WGS84.csv&quot;</span><span class="p">,</span> <span class="s2">&quot;r&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">fid</span><span class="p">:</span>
-                <span class="nb">next</span><span class="p">(</span><span class="n">fid</span><span class="p">)</span>
-                <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">fid</span><span class="p">:</span>
-                    <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">code</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">easting</span><span class="p">,</span> <span class="n">northing</span> <span class="o">=</span> <span class="n">line</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s1">&#39;UTF-8&#39;</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&quot;,&quot;</span><span class="p">)</span>
-                    <span class="n">codes</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">code</span><span class="p">)</span>
-                    <span class="n">eastings</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="nb">float</span><span class="p">(</span><span class="n">easting</span><span class="p">))</span>
-                    <span class="n">northings</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="nb">float</span><span class="p">(</span><span class="n">northing</span><span class="p">))</span>
-            <span class="k">with</span> <span class="n">nrs_zip</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="s2">&quot;SG_DataZone_Cent_2011.csv&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">fid</span><span class="p">:</span>
-                <span class="nb">next</span><span class="p">(</span><span class="n">fid</span><span class="p">)</span>
-                <span class="n">contents</span> <span class="o">=</span> <span class="p">[</span><span class="n">l</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s1">&#39;UTF-8&#39;</span><span class="p">)</span> <span class="k">for</span> <span class="n">l</span> <span class="ow">in</span> <span class="n">fid</span><span class="o">.</span><span class="n">readlines</span><span class="p">()]</span>
-                <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">csv</span><span class="o">.</span><span class="n">reader</span><span class="p">(</span><span class="n">contents</span><span class="p">,</span> <span class="n">quotechar</span><span class="o">=</span><span class="s2">&quot;</span><span class="se">\&quot;</span><span class="s2">&quot;</span><span class="p">,</span> <span class="n">delimiter</span><span class="o">=</span><span class="s2">&quot;,&quot;</span><span class="p">,</span>
-                                       <span class="n">quoting</span><span class="o">=</span><span class="n">csv</span><span class="o">.</span><span class="n">QUOTE_ALL</span><span class="p">,</span> <span class="n">skipinitialspace</span><span class="o">=</span><span class="kc">True</span><span class="p">):</span>
-                    <span class="n">datazone</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">dzeast</span><span class="p">,</span> <span class="n">dznorth</span> <span class="o">=</span> <span class="n">line</span>
-                    <span class="n">datazones</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">datazone</span><span class="p">)</span>
-                    <span class="n">dzeastings</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="nb">float</span><span class="p">(</span><span class="n">dzeast</span><span class="o">.</span><span class="n">strip</span><span class="p">(</span><span class="s2">&quot;</span><span class="se">\&quot;</span><span class="s2">&quot;</span><span class="p">)))</span>
-                    <span class="n">dznorthings</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="nb">float</span><span class="p">(</span><span class="n">dznorth</span><span class="o">.</span><span class="n">strip</span><span class="p">(</span><span class="s2">&quot;</span><span class="se">\&quot;</span><span class="s2">&quot;</span><span class="p">)))</span>
-        <span class="n">lons</span><span class="p">,</span> <span class="n">lats</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_bng2latlon</span><span class="p">(</span><span class="n">eastings</span><span class="p">,</span> <span class="n">northings</span><span class="p">)</span>
-        <span class="n">dzlons</span><span class="p">,</span> <span class="n">dzlats</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_bng2latlon</span><span class="p">(</span><span class="n">dzeastings</span><span class="p">,</span> <span class="n">dznorthings</span><span class="p">)</span>
-        <span class="n">scots_lookup</span> <span class="o">=</span> <span class="p">{</span><span class="n">code</span><span class="p">:</span> <span class="p">(</span><span class="n">lat</span><span class="p">,</span> <span class="n">lon</span><span class="p">)</span> <span class="k">for</span> <span class="n">code</span><span class="p">,</span> <span class="n">lon</span><span class="p">,</span> <span class="n">lat</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">codes</span><span class="p">,</span> <span class="n">lons</span><span class="p">,</span> <span class="n">lats</span><span class="p">)}</span>
-        <span class="n">scots_dz_lookup</span> <span class="o">=</span> <span class="p">{</span><span class="n">dz</span><span class="p">:</span> <span class="p">(</span><span class="n">lat</span><span class="p">,</span> <span class="n">lon</span><span class="p">)</span> <span class="k">for</span> <span class="n">dz</span><span class="p">,</span> <span class="n">lon</span><span class="p">,</span> <span class="n">lat</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">datazones</span><span class="p">,</span> <span class="n">dzlons</span><span class="p">,</span> <span class="n">dzlats</span><span class="p">)}</span>
-        <span class="n">llsoa_lookup</span> <span class="o">=</span> <span class="p">{</span><span class="o">**</span><span class="n">engwales_lookup</span><span class="p">,</span> <span class="o">**</span><span class="n">scots_lookup</span><span class="p">,</span> <span class="o">**</span><span class="n">scots_dz_lookup</span><span class="p">}</span>
-        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">llsoa_cache_file</span><span class="p">,</span> <span class="s2">&quot;wb&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">pickle_fid</span><span class="p">:</span>
-            <span class="n">pickle</span><span class="o">.</span><span class="n">dump</span><span class="p">(</span><span class="n">llsoa_lookup</span><span class="p">,</span> <span class="n">pickle_fid</span><span class="p">)</span>
-        <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;LSOA centroids extracted and pickled to &#39;</span><span class="si">%s</span><span class="s2">&#39;&quot;</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">llsoa_cache_file</span><span class="p">)</span>
-        <span class="k">return</span> <span class="n">llsoa_lookup</span>
-
-    <span class="k">def</span> <span class="nf">_load_llsoa_boundaries</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
-<span class="w">        </span><span class="sd">&quot;&quot;&quot;</span>
-<span class="sd">        Load the LLSOA boundaries, either from local cache if available, else fetch from raw API</span>
-<span class="sd">        (England and Wales) and packaged data (Scotland).</span>
-<span class="sd">        &quot;&quot;&quot;</span>
-        <span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isfile</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">llsoa_boundaries_cache_file</span><span class="p">):</span>
-            <span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Loading LLSOA boundaries from cache (&#39;</span><span class="si">%s</span><span class="s2">&#39;)&quot;</span><span class="p">,</span>
-                          <span class="bp">self</span><span class="o">.</span><span class="n">llsoa_boundaries_cache_file</span><span class="p">)</span>
-            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">llsoa_boundaries_cache_file</span><span class="p">,</span> <span class="s2">&quot;rb&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">pickle_fid</span><span class="p">:</span>
-                <span class="k">return</span> <span class="n">pickle</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">pickle_fid</span><span class="p">)</span>
-        <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;Extracting the LLSOA boundary data from ONS and NRS (this only needs to be &quot;</span>
-                     <span class="s2">&quot;done once)&quot;</span><span class="p">)</span>
-        <span class="c1"># ons_url = &quot;https://opendata.arcgis.com/datasets/007577eeb8e34c62a1844df090a93128_0.geojson&quot;</span>
-        <span class="c1"># ons_url = &quot;https://opendata.arcgis.com/datasets/f213065139e3441195803b4155e71e00_0.geojson&quot;</span>
-        <span class="c1"># ons_url = &quot;https://opendata.arcgis.com/datasets/e0b761d78e51491d84a3df33dff044c7_0.geojson&quot;</span>
-        <span class="n">ons_url</span> <span class="o">=</span> <span class="s2">&quot;https://services1.arcgis.com/ESMARspQHYMw9BZ9/arcgis/rest/services/Lower_Layer_Super_Output_Areas_Dec_2011_Boundaries_Full_Extent_BFE_EW_V3_2022/FeatureServer/0/query?outFields=*&amp;where=1%3D1&amp;f=geojson&quot;</span>
-        <span class="c1"># Loading NRS data from URL abandoned due to need for re-projection</span>
-        <span class="c1"># nrs_url = &quot;https://www.nrscotland.gov.uk/files/geography/output-area-2011-eor.zip&quot;</span>
-        <span class="n">nrs_shp_file</span> <span class="o">=</span> <span class="s2">&quot;OutputArea2011_EoR_WGS84.shp&quot;</span>
-        <span class="n">nrs_dbf_file</span> <span class="o">=</span> <span class="s2">&quot;OutputArea2011_EoR_WGS84.dbf&quot;</span>
-        <span class="n">success</span><span class="p">,</span> <span class="n">api_response</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_fetch_from_api</span><span class="p">(</span><span class="n">ons_url</span><span class="p">)</span>
-        <span class="k">if</span> <span class="n">success</span><span class="p">:</span>
-            <span class="n">raw</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">api_response</span><span class="o">.</span><span class="n">text</span><span class="p">)</span>
-            <span class="n">engwales_regions</span> <span class="o">=</span> <span class="p">{</span><span class="n">f</span><span class="p">[</span><span class="s2">&quot;properties&quot;</span><span class="p">][</span><span class="s2">&quot;LSOA11CD&quot;</span><span class="p">]:</span> <span class="n">shape</span><span class="p">(</span><span class="n">f</span><span class="p">[</span><span class="s2">&quot;geometry&quot;</span><span class="p">])</span><span class="o">.</span><span class="n">buffer</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
-                                <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">raw</span><span class="p">[</span><span class="s2">&quot;features&quot;</span><span class="p">]}</span>
-        <span class="k">else</span><span class="p">:</span>
-            <span class="k">raise</span> <span class="n">GenericException</span><span class="p">(</span><span class="s2">&quot;Encountered an error while extracting LLSOA data from ONS API.&quot;</span><span class="p">)</span>
-        <span class="k">with</span> <span class="n">zipfile</span><span class="o">.</span><span class="n">ZipFile</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">nrs_zipfile</span><span class="p">,</span> <span class="s2">&quot;r&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">nrs_zip</span><span class="p">:</span>
-            <span class="k">with</span> <span class="n">nrs_zip</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="n">nrs_shp_file</span><span class="p">,</span> <span class="s2">&quot;r&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">shp</span><span class="p">:</span>
-                <span class="k">with</span> <span class="n">nrs_zip</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="n">nrs_dbf_file</span><span class="p">,</span> <span class="s2">&quot;r&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">dbf</span><span class="p">:</span>
-                    <span class="n">sf</span> <span class="o">=</span> <span class="n">shapefile</span><span class="o">.</span><span class="n">Reader</span><span class="p">(</span><span class="n">shp</span><span class="o">=</span><span class="n">shp</span><span class="p">,</span> <span class="n">dbf</span><span class="o">=</span><span class="n">dbf</span><span class="p">)</span>
-                    <span class="n">scots_regions</span> <span class="o">=</span> <span class="p">{</span><span class="n">sr</span><span class="o">.</span><span class="n">record</span><span class="p">[</span><span class="mi">1</span><span class="p">]:</span> <span class="n">shape</span><span class="p">(</span><span class="n">sr</span><span class="o">.</span><span class="n">shape</span><span class="o">.</span><span class="n">__geo_interface__</span><span class="p">)</span><span class="o">.</span><span class="n">buffer</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
-                                     <span class="k">for</span> <span class="n">sr</span> <span class="ow">in</span> <span class="n">sf</span><span class="o">.</span><span class="n">shapeRecords</span><span class="p">()}</span>
-        <span class="n">llsoa_regions</span> <span class="o">=</span> <span class="p">{</span><span class="o">**</span><span class="n">engwales_regions</span><span class="p">,</span> <span class="o">**</span><span class="n">scots_regions</span><span class="p">}</span>
-        <span class="n">llsoa_regions</span> <span class="o">=</span> <span class="p">{</span><span class="n">llsoacd</span><span class="p">:</span> <span class="p">(</span><span class="n">llsoa_regions</span><span class="p">[</span><span class="n">llsoacd</span><span class="p">],</span> <span class="n">llsoa_regions</span><span class="p">[</span><span class="n">llsoacd</span><span class="p">]</span><span class="o">.</span><span class="n">bounds</span><span class="p">)</span>
-                         <span class="k">for</span> <span class="n">llsoacd</span> <span class="ow">in</span> <span class="n">llsoa_regions</span><span class="p">}</span>
-        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">llsoa_boundaries_cache_file</span><span class="p">,</span> <span class="s2">&quot;wb&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">pickle_fid</span><span class="p">:</span>
-            <span class="n">pickle</span><span class="o">.</span><span class="n">dump</span><span class="p">(</span><span class="n">llsoa_regions</span><span class="p">,</span> <span class="n">pickle_fid</span><span class="p">)</span>
-        <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;LSOA boundaries extracted and pickled to &#39;</span><span class="si">%s</span><span class="s2">&#39;&quot;</span><span class="p">,</span>
-                     <span class="bp">self</span><span class="o">.</span><span class="n">llsoa_boundaries_cache_file</span><span class="p">)</span>
-        <span class="k">return</span> <span class="n">llsoa_regions</span>
-
-    <span class="k">def</span> <span class="nf">_load_gsp_boundaries_20181031</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
-<span class="w">        </span><span class="sd">&quot;&quot;&quot;</span>
-<span class="sd">        Load the 20181031 GSP / GNode boundaries, either from local cache if available, else fetch</span>
-<span class="sd">        from ESO Data Portal API.</span>
-<span class="sd">        &quot;&quot;&quot;</span>
-        <span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isfile</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">gsp_boundaries_20181031_cache_file</span><span class="p">):</span>
-            <span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Loading 20181031 GSP boundaries from cache (&#39;</span><span class="si">%s</span><span class="s2">&#39;)&quot;</span><span class="p">,</span>
-                          <span class="bp">self</span><span class="o">.</span><span class="n">gsp_boundaries_20181031_cache_file</span><span class="p">)</span>
-            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">gsp_boundaries_20181031_cache_file</span><span class="p">,</span> <span class="s2">&quot;rb&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">pickle_fid</span><span class="p">:</span>
-                <span class="k">return</span> <span class="n">pickle</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">pickle_fid</span><span class="p">)</span>
-        <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;Extracting the 20181031 GSP boundary data from NGESO&#39;s Data Portal API (this &quot;</span>
-                     <span class="s2">&quot;only needs to be done once)&quot;</span><span class="p">)</span>
-        <span class="n">eso_url</span> <span class="o">=</span> <span class="s2">&quot;http://data.nationalgrideso.com/backend/dataset/2810092e-d4b2-472f-b955-d8bea01f9ec0/resource/a3ed5711-407a-42a9-a63a-011615eea7e0/download/gsp_regions_20181031.geojson&quot;</span>
-        <span class="n">success</span><span class="p">,</span> <span class="n">api_response</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_fetch_from_api</span><span class="p">(</span><span class="n">eso_url</span><span class="p">)</span>
-        <span class="k">if</span> <span class="n">success</span><span class="p">:</span>
-            <span class="n">raw</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">api_response</span><span class="o">.</span><span class="n">text</span><span class="p">)</span>
-            <span class="n">gsp_regions</span> <span class="o">=</span> <span class="p">{}</span>
-            <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">raw</span><span class="p">[</span><span class="s2">&quot;features&quot;</span><span class="p">]:</span>
-                <span class="n">region_id</span> <span class="o">=</span> <span class="n">f</span><span class="p">[</span><span class="s2">&quot;properties&quot;</span><span class="p">][</span><span class="s2">&quot;RegionID&quot;</span><span class="p">]</span>
-                <span class="k">if</span> <span class="n">region_id</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">gsp_regions</span><span class="p">:</span>
-                    <span class="n">gsp_regions</span><span class="p">[</span><span class="n">region_id</span><span class="p">]</span> <span class="o">=</span> <span class="n">shape</span><span class="p">(</span><span class="n">f</span><span class="p">[</span><span class="s2">&quot;geometry&quot;</span><span class="p">])</span><span class="o">.</span><span class="n">buffer</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
-                <span class="k">else</span><span class="p">:</span> <span class="c1"># Sometimes a region is in multiple pieces due to PES boundary e.g. Axminster</span>
-                    <span class="n">gsp_regions</span><span class="p">[</span><span class="n">region_id</span><span class="p">]</span> <span class="o">=</span> <span class="n">unary_union</span><span class="p">([</span><span class="n">gsp_regions</span><span class="p">[</span><span class="n">region_id</span><span class="p">],</span>
-                                                          <span class="n">shape</span><span class="p">(</span><span class="n">f</span><span class="p">[</span><span class="s2">&quot;geometry&quot;</span><span class="p">])</span><span class="o">.</span><span class="n">buffer</span><span class="p">(</span><span class="mi">0</span><span class="p">)])</span>
-        <span class="k">else</span><span class="p">:</span>
-            <span class="k">raise</span> <span class="n">GenericException</span><span class="p">(</span><span class="s2">&quot;Encountered an error while extracting GSP region data from ESO &quot;</span>
-                                   <span class="s2">&quot;API.&quot;</span><span class="p">)</span>
-        <span class="n">gsp_regions</span> <span class="o">=</span> <span class="p">{</span><span class="n">region_id</span><span class="p">:</span> <span class="p">(</span><span class="n">gsp_regions</span><span class="p">[</span><span class="n">region_id</span><span class="p">],</span> <span class="n">gsp_regions</span><span class="p">[</span><span class="n">region_id</span><span class="p">]</span><span class="o">.</span><span class="n">bounds</span><span class="p">)</span>
-                       <span class="k">for</span> <span class="n">region_id</span> <span class="ow">in</span> <span class="n">gsp_regions</span><span class="p">}</span>
-        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">gsp_boundaries_20181031_cache_file</span><span class="p">,</span> <span class="s2">&quot;wb&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">pickle_fid</span><span class="p">:</span>
-            <span class="n">pickle</span><span class="o">.</span><span class="n">dump</span><span class="p">(</span><span class="n">gsp_regions</span><span class="p">,</span> <span class="n">pickle_fid</span><span class="p">)</span>
-        <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;20181031 GSP boundaries extracted and pickled to &#39;</span><span class="si">%s</span><span class="s2">&#39;&quot;</span><span class="p">,</span>
-                     <span class="bp">self</span><span class="o">.</span><span class="n">gsp_boundaries_20181031_cache_file</span><span class="p">)</span>
-        <span class="k">return</span> <span class="n">gsp_regions</span>
-
-    <span class="k">def</span> <span class="nf">_load_gsp_boundaries_20220314</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
-<span class="w">        </span><span class="sd">&quot;&quot;&quot;</span>
-<span class="sd">        Load the 20220314 GSP / GNode boundaries, either from local cache if available, else fetch</span>
-<span class="sd">        from ESO Data Portal API.</span>
-<span class="sd">        &quot;&quot;&quot;</span>
-        <span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isfile</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">gsp_boundaries_20220314_cache_file</span><span class="p">):</span>
-            <span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Loading 20220314 GSP boundaries from cache (&#39;</span><span class="si">%s</span><span class="s2">&#39;)&quot;</span><span class="p">,</span>
-                          <span class="bp">self</span><span class="o">.</span><span class="n">gsp_boundaries_20220314_cache_file</span><span class="p">)</span>
-            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">gsp_boundaries_20220314_cache_file</span><span class="p">,</span> <span class="s2">&quot;rb&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">pickle_fid</span><span class="p">:</span>
-                <span class="k">return</span> <span class="n">pickle</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">pickle_fid</span><span class="p">)</span>
-        <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;Extracting the 20220314 GSP boundary data from NGESO&#39;s Data Portal API (this &quot;</span>
-                     <span class="s2">&quot;only needs to be done once)&quot;</span><span class="p">)</span>
-        <span class="n">eso_url</span> <span class="o">=</span> <span class="s2">&quot;https://data.nationalgrideso.com/backend/dataset/2810092e-d4b2-472f-b955-d8bea01f9ec0/resource/08534dae-5408-4e31-8639-b579c8f1c50b/download/gsp_regions_20220314.geojson&quot;</span>
-        <span class="n">success</span><span class="p">,</span> <span class="n">api_response</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_fetch_from_api</span><span class="p">(</span><span class="n">eso_url</span><span class="p">)</span>
-        <span class="k">if</span> <span class="n">success</span><span class="p">:</span>
-            <span class="n">raw</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">api_response</span><span class="o">.</span><span class="n">text</span><span class="p">)</span>
-            <span class="n">gsp_regions</span> <span class="o">=</span> <span class="n">gpd</span><span class="o">.</span><span class="n">GeoDataFrame</span><span class="o">.</span><span class="n">from_features</span><span class="p">(</span><span class="n">raw</span><span class="p">[</span><span class="s2">&quot;features&quot;</span><span class="p">],</span>
-                                                         <span class="n">crs</span><span class="o">=</span><span class="n">raw</span><span class="p">[</span><span class="s2">&quot;crs&quot;</span><span class="p">][</span><span class="s2">&quot;properties&quot;</span><span class="p">][</span><span class="s2">&quot;name&quot;</span><span class="p">])</span>
-            <span class="n">gsp_regions</span><span class="o">.</span><span class="n">geometry</span> <span class="o">=</span> <span class="n">gsp_regions</span><span class="o">.</span><span class="n">buffer</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
-        <span class="k">else</span><span class="p">:</span>
-            <span class="k">raise</span> <span class="n">GenericException</span><span class="p">(</span><span class="s2">&quot;Encountered an error while extracting GSP region data from ESO &quot;</span>
-                                   <span class="s2">&quot;API.&quot;</span><span class="p">)</span>
-        <span class="c1">### For backwards compatibility pending https://github.com/SheffieldSolar/Geocode/issues/6</span>
-        <span class="n">gsp_regions</span><span class="p">[</span><span class="s2">&quot;bounds&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="n">gsp_regions</span><span class="o">.</span><span class="n">bounds</span><span class="o">.</span><span class="n">apply</span><span class="p">(</span><span class="nb">tuple</span><span class="p">,</span> <span class="n">axis</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
-        <span class="n">gsp_regions_dict</span> <span class="o">=</span> <span class="n">gsp_regions</span><span class="o">.</span><span class="n">set_index</span><span class="p">([</span><span class="s2">&quot;GSPs&quot;</span><span class="p">,</span> <span class="s2">&quot;GSPGroup&quot;</span><span class="p">])</span><span class="o">.</span><span class="n">to_dict</span><span class="p">(</span><span class="s2">&quot;index&quot;</span><span class="p">)</span>
-        <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">gsp_regions_dict</span><span class="p">:</span>
-            <span class="n">gsp_regions_dict</span><span class="p">[</span><span class="n">r</span><span class="p">]</span> <span class="o">=</span> <span class="nb">tuple</span><span class="p">(</span><span class="n">gsp_regions_dict</span><span class="p">[</span><span class="n">r</span><span class="p">]</span><span class="o">.</span><span class="n">values</span><span class="p">())</span>
-        <span class="c1">######</span>
-        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">gsp_boundaries_20220314_cache_file</span><span class="p">,</span> <span class="s2">&quot;wb&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">pickle_fid</span><span class="p">:</span>
-            <span class="n">pickle</span><span class="o">.</span><span class="n">dump</span><span class="p">(</span><span class="n">gsp_regions_dict</span><span class="p">,</span> <span class="n">pickle_fid</span><span class="p">)</span>
-        <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;20220314 GSP boundaries extracted and pickled to &#39;</span><span class="si">%s</span><span class="s2">&#39;&quot;</span><span class="p">,</span>
-                     <span class="bp">self</span><span class="o">.</span><span class="n">gsp_boundaries_20220314_cache_file</span><span class="p">)</span>
-        <span class="k">return</span> <span class="n">gsp_regions_dict</span>
-
-    <span class="k">def</span> <span class="nf">_load_dno_boundaries</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
-<span class="w">        </span><span class="sd">&quot;&quot;&quot;</span>
-<span class="sd">        Load the DNO License Area boundaries, either from local cache if available, else fetch from</span>
-<span class="sd">        ESO Data Portal API.</span>
-<span class="sd">        &quot;&quot;&quot;</span>
-        <span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isfile</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">dno_boundaries_cache_file</span><span class="p">):</span>
-            <span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Loading DNO boundaries from cache (&#39;</span><span class="si">%s</span><span class="s2">&#39;)&quot;</span><span class="p">,</span>
-                          <span class="bp">self</span><span class="o">.</span><span class="n">dno_boundaries_cache_file</span><span class="p">)</span>
-            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">dno_boundaries_cache_file</span><span class="p">,</span> <span class="s2">&quot;rb&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">pickle_fid</span><span class="p">:</span>
-                <span class="k">return</span> <span class="n">pickle</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">pickle_fid</span><span class="p">)</span>
-        <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;Extracting the DNO License Area boundary data from NGESO&#39;s Data Portal API &quot;</span>
-                     <span class="s2">&quot;(this only needs to be done once)&quot;</span><span class="p">)</span>
-        <span class="n">eso_url</span> <span class="o">=</span> <span class="s2">&quot;http://data.nationalgrideso.com/backend/dataset/0e377f16-95e9-4c15-a1fc-49e06a39cfa0/resource/e96db306-aaa8-45be-aecd-65b34d38923a/download/dno_license_areas_20200506.geojson&quot;</span>
-        <span class="n">success</span><span class="p">,</span> <span class="n">api_response</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_fetch_from_api</span><span class="p">(</span><span class="n">eso_url</span><span class="p">)</span>
-        <span class="k">if</span> <span class="n">success</span><span class="p">:</span>
-            <span class="n">raw</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">api_response</span><span class="o">.</span><span class="n">text</span><span class="p">)</span>
-            <span class="n">dno_regions</span> <span class="o">=</span> <span class="p">{}</span>
-            <span class="n">dno_names</span> <span class="o">=</span> <span class="p">{}</span>
-            <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">raw</span><span class="p">[</span><span class="s2">&quot;features&quot;</span><span class="p">]:</span>
-                <span class="n">region_id</span> <span class="o">=</span> <span class="n">f</span><span class="p">[</span><span class="s2">&quot;properties&quot;</span><span class="p">][</span><span class="s2">&quot;ID&quot;</span><span class="p">]</span>
-                <span class="n">dno_regions</span><span class="p">[</span><span class="n">region_id</span><span class="p">]</span> <span class="o">=</span> <span class="n">shape</span><span class="p">(</span><span class="n">f</span><span class="p">[</span><span class="s2">&quot;geometry&quot;</span><span class="p">])</span><span class="o">.</span><span class="n">buffer</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
-                <span class="n">dno_names</span><span class="p">[</span><span class="n">region_id</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="n">f</span><span class="p">[</span><span class="s2">&quot;properties&quot;</span><span class="p">][</span><span class="s2">&quot;Name&quot;</span><span class="p">],</span> <span class="n">f</span><span class="p">[</span><span class="s2">&quot;properties&quot;</span><span class="p">][</span><span class="s2">&quot;LongName&quot;</span><span class="p">])</span>
-        <span class="k">else</span><span class="p">:</span>
-            <span class="k">raise</span> <span class="n">GenericException</span><span class="p">(</span><span class="s2">&quot;Encountered an error while extracting DNO License Area &quot;</span>
-                                   <span class="s2">&quot;boundary data from ESO API.&quot;</span><span class="p">)</span>
-        <span class="n">dno_regions</span> <span class="o">=</span> <span class="p">{</span><span class="n">region_id</span><span class="p">:</span> <span class="p">(</span><span class="n">dno_regions</span><span class="p">[</span><span class="n">region_id</span><span class="p">],</span> <span class="n">dno_regions</span><span class="p">[</span><span class="n">region_id</span><span class="p">]</span><span class="o">.</span><span class="n">bounds</span><span class="p">)</span>
-                       <span class="k">for</span> <span class="n">region_id</span> <span class="ow">in</span> <span class="n">dno_regions</span><span class="p">}</span>
-        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">dno_boundaries_cache_file</span><span class="p">,</span> <span class="s2">&quot;wb&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">pickle_fid</span><span class="p">:</span>
-            <span class="n">pickle</span><span class="o">.</span><span class="n">dump</span><span class="p">((</span><span class="n">dno_regions</span><span class="p">,</span> <span class="n">dno_names</span><span class="p">),</span> <span class="n">pickle_fid</span><span class="p">)</span>
-        <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;DNO License Area boundaries extracted and pickled to &#39;</span><span class="si">%s</span><span class="s2">&#39;&quot;</span><span class="p">,</span>
-                     <span class="bp">self</span><span class="o">.</span><span class="n">dno_boundaries_cache_file</span><span class="p">)</span>
-        <span class="k">return</span> <span class="n">dno_regions</span><span class="p">,</span> <span class="n">dno_names</span>
+        <span class="k">if</span> <span class="n">ngeso_setup</span><span class="p">:</span>
+            <span class="bp">self</span><span class="o">.</span><span class="n">ngeso</span><span class="o">.</span><span class="n">force_setup</span><span class="p">()</span>
+        <span class="k">if</span> <span class="n">cpo_setup</span><span class="p">:</span>
+            <span class="bp">self</span><span class="o">.</span><span class="n">cpo</span><span class="o">.</span><span class="n">force_setup</span><span class="p">()</span>
+        <span class="k">if</span> <span class="n">ons_setup</span><span class="p">:</span>
+            <span class="bp">self</span><span class="o">.</span><span class="n">ons_nrs</span><span class="o">.</span><span class="n">force_setup</span><span class="p">()</span></div>
 
 <div class="viewcode-block" id="Geocoder.get_dno_regions"><a class="viewcode-back" href="../modules.html#geocode.Geocoder.get_dno_regions">[docs]</a>    <span class="k">def</span> <span class="nf">get_dno_regions</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
 <span class="w">        </span><span class="sd">&quot;&quot;&quot;</span>
@@ -584,651 +171,174 @@ <h1>Source code for geocode</h1><div class="highlight"><pre>
 <span class="sd">            Dict whose keys are the region IDs and whose values are a tuple containing:</span>
 <span class="sd">            (Name, LongName).</span>
 <span class="sd">        &quot;&quot;&quot;</span>
-        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">dno_regions</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
-            <span class="bp">self</span><span class="o">.</span><span class="n">dno_regions</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_load_dno_boundaries</span><span class="p">()</span>
-        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">dno_regions</span></div>
-
-    <span class="k">def</span> <span class="nf">_load_gsp_lookup_20181031</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
-<span class="w">        </span><span class="sd">&quot;&quot;&quot;Load the lookup of Region &lt;-&gt; GSP &lt;-&gt; GNode.&quot;&quot;&quot;</span>
-        <span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isfile</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">gsp_lookup_20181031_cache_file</span><span class="p">):</span>
-            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">gsp_lookup_20181031_cache_file</span><span class="p">,</span> <span class="s2">&quot;rb&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">pickle_fid</span><span class="p">:</span>
-                <span class="k">return</span> <span class="n">pickle</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">pickle_fid</span><span class="p">)</span>
-        <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;Extracting the GSP lookup data from NGESO&#39;s Data Portal API (this only needs &quot;</span>
-                     <span class="s2">&quot;to be done once)&quot;</span><span class="p">)</span>
-        <span class="n">eso_url</span> <span class="o">=</span> <span class="s2">&quot;http://data.nationalgrideso.com/backend/dataset/2810092e-d4b2-472f-b955-d8bea01f9ec0/resource/bbe2cc72-a6c6-46e6-8f4e-48b879467368/download/gsp_gnode_directconnect_region_lookup.csv&quot;</span>
-        <span class="n">success</span><span class="p">,</span> <span class="n">api_response</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_fetch_from_api</span><span class="p">(</span><span class="n">eso_url</span><span class="p">)</span>
-        <span class="k">if</span> <span class="n">success</span><span class="p">:</span>
-            <span class="n">f</span> <span class="o">=</span> <span class="n">StringIO</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">api_response</span><span class="o">.</span><span class="n">text</span><span class="p">)</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&quot;</span><span class="se">\ufeff</span><span class="s2">&quot;</span><span class="p">,</span> <span class="s2">&quot;&quot;</span><span class="p">))</span> <span class="c1"># Remove BOM character</span>
-            <span class="n">gsp_lookup</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">read_csv</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>
-            <span class="n">gsp_lookup</span> <span class="o">=</span> <span class="n">gsp_lookup</span><span class="o">.</span><span class="n">loc</span><span class="p">[</span><span class="n">gsp_lookup</span><span class="o">.</span><span class="n">region_id</span><span class="o">.</span><span class="n">notnull</span><span class="p">()]</span><span class="o">.</span><span class="n">convert_dtypes</span><span class="p">()</span>
-        <span class="k">else</span><span class="p">:</span>
-            <span class="k">raise</span> <span class="n">GenericException</span><span class="p">(</span><span class="s2">&quot;Encountered an error while extracting GSP lookup data from ESO &quot;</span>
-                                   <span class="s2">&quot;API.&quot;</span><span class="p">)</span>
-        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">gsp_lookup_20181031_cache_file</span><span class="p">,</span> <span class="s2">&quot;wb&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">pickle_fid</span><span class="p">:</span>
-            <span class="n">pickle</span><span class="o">.</span><span class="n">dump</span><span class="p">(</span><span class="n">gsp_lookup</span><span class="p">,</span> <span class="n">pickle_fid</span><span class="p">)</span>
-        <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;GSP lookup extracted and pickled to &#39;</span><span class="si">%s</span><span class="s2">&#39;&quot;</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">gsp_lookup_20181031_cache_file</span><span class="p">)</span>
-        <span class="k">return</span> <span class="n">gsp_lookup</span>
-
-    <span class="k">def</span> <span class="nf">_load_datazone_lookup</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
-<span class="w">        </span><span class="sd">&quot;&quot;&quot;Load a lookup of Scottish LLSOA &lt;-&gt; Datazone.&quot;&quot;&quot;</span>
-        <span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isfile</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">dz_lookup_cache_file</span><span class="p">):</span>
-            <span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Loading LLSOA&lt;-&gt;Datazone lookup from cache (&#39;</span><span class="si">%s</span><span class="s2">&#39;)&quot;</span><span class="p">,</span>
-                          <span class="bp">self</span><span class="o">.</span><span class="n">dz_lookup_cache_file</span><span class="p">)</span>
-            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">dz_lookup_cache_file</span><span class="p">,</span> <span class="s2">&quot;rb&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">pickle_fid</span><span class="p">:</span>
-                <span class="k">return</span> <span class="n">pickle</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">pickle_fid</span><span class="p">)</span>
-        <span class="k">with</span> <span class="n">zipfile</span><span class="o">.</span><span class="n">ZipFile</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">nrs_zipfile</span><span class="p">,</span> <span class="s2">&quot;r&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">nrs_zip</span><span class="p">:</span>
-            <span class="k">with</span> <span class="n">nrs_zip</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="s2">&quot;OA_DZ_IZ_2011.csv&quot;</span><span class="p">,</span> <span class="s2">&quot;r&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">fid</span><span class="p">:</span>
-                <span class="n">dz_lookup</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">read_csv</span><span class="p">(</span><span class="n">fid</span><span class="p">)</span>
-        <span class="n">dz_lookup</span><span class="o">.</span><span class="n">set_index</span><span class="p">(</span><span class="s2">&quot;OutputArea2011Code&quot;</span><span class="p">,</span> <span class="n">inplace</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
-        <span class="n">dz_lookup</span><span class="o">.</span><span class="n">drop</span><span class="p">(</span><span class="n">columns</span><span class="o">=</span><span class="p">[</span><span class="s2">&quot;IntermediateZone2011Code&quot;</span><span class="p">],</span> <span class="n">inplace</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
-        <span class="n">dz_lookup</span> <span class="o">=</span> <span class="n">dz_lookup</span><span class="o">.</span><span class="n">to_dict</span><span class="p">()[</span><span class="s2">&quot;DataZone2011Code&quot;</span><span class="p">]</span>
-        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">dz_lookup_cache_file</span><span class="p">,</span> <span class="s2">&quot;wb&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">pickle_fid</span><span class="p">:</span>
-            <span class="n">pickle</span><span class="o">.</span><span class="n">dump</span><span class="p">(</span><span class="n">dz_lookup</span><span class="p">,</span> <span class="n">pickle_fid</span><span class="p">)</span>
-        <span class="k">return</span> <span class="n">dz_lookup</span>
-
-    <span class="k">def</span> <span class="nf">_load_postcode_llsoa_lookup</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
-<span class="w">        </span><span class="sd">&quot;&quot;&quot;Load a lookup of postcode &lt;-&gt; LLSOA.&quot;&quot;&quot;</span>
-        <span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isfile</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">pc_llsoa_lookup_cache_file</span><span class="p">):</span>
-            <span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Loading postcode&lt;-&gt;LLSOA lookup from cache (&#39;</span><span class="si">%s</span><span class="s2">&#39;)&quot;</span><span class="p">,</span>
-                          <span class="bp">self</span><span class="o">.</span><span class="n">pc_llsoa_lookup_cache_file</span><span class="p">)</span>
-            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">pc_llsoa_lookup_cache_file</span><span class="p">,</span> <span class="s2">&quot;rb&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">pickle_fid</span><span class="p">:</span>
-                <span class="k">return</span> <span class="n">pickle</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">pickle_fid</span><span class="p">)</span>
-        <span class="n">pc_llsoa_lookup</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">read_csv</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">pc_llsoa_zipfile</span><span class="p">,</span> <span class="n">dtype</span><span class="o">=</span><span class="nb">str</span><span class="p">)</span>
-        <span class="n">pc_llsoa_lookup</span><span class="p">[</span><span class="s2">&quot;postcode&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="n">pc_llsoa_lookup</span><span class="o">.</span><span class="n">pcds</span><span class="o">.</span><span class="n">str</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">str</span><span class="o">.</span><span class="n">upper</span><span class="p">()</span>\
-                                                                      <span class="o">.</span><span class="n">str</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&quot; &quot;</span><span class="p">,</span> <span class="s2">&quot;&quot;</span><span class="p">)</span>
-        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">pc_llsoa_lookup_cache_file</span><span class="p">,</span> <span class="s2">&quot;wb&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">pickle_fid</span><span class="p">:</span>
-            <span class="n">pickle</span><span class="o">.</span><span class="n">dump</span><span class="p">(</span><span class="n">pc_llsoa_lookup</span><span class="p">,</span> <span class="n">pickle_fid</span><span class="p">)</span>
-        <span class="k">return</span> <span class="n">pc_llsoa_lookup</span>
-
-    <span class="k">def</span> <span class="nf">_load_constituency_lookup</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
-<span class="w">        </span><span class="sd">&quot;&quot;&quot;Load a lookup of UK constituency -&gt; Geospatial Centroid.&quot;&quot;&quot;</span>
-        <span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isfile</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">constituency_cache_file</span><span class="p">):</span>
-            <span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Loading Constituency lookup from cache (&#39;</span><span class="si">%s</span><span class="s2">&#39;)&quot;</span><span class="p">,</span>
-                          <span class="bp">self</span><span class="o">.</span><span class="n">constituency_cache_file</span><span class="p">)</span>
-            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">constituency_cache_file</span><span class="p">,</span> <span class="s2">&quot;rb&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">pickle_fid</span><span class="p">:</span>
-                <span class="k">return</span> <span class="n">pickle</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">pickle_fid</span><span class="p">)</span>
-        <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;Extracting the Constituency Centroids data (this only needs to be done &quot;</span>
-                     <span class="s2">&quot;once)&quot;</span><span class="p">)</span>
-        <span class="n">constituency_lookup</span> <span class="o">=</span> <span class="p">{}</span>
-        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">constituency_lookup_file</span><span class="p">)</span> <span class="k">as</span> <span class="n">fid</span><span class="p">:</span>
-            <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">fid</span><span class="p">:</span>
-                <span class="n">_</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">longitude</span><span class="p">,</span> <span class="n">latitude</span> <span class="o">=</span> <span class="n">line</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&quot;|&quot;</span><span class="p">)</span>
-                <span class="n">match_str</span> <span class="o">=</span> <span class="n">name</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&quot; &quot;</span><span class="p">,</span> <span class="s2">&quot;&quot;</span><span class="p">)</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&quot;,&quot;</span><span class="p">,</span> <span class="s2">&quot;&quot;</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
-                <span class="n">constituency_lookup</span><span class="p">[</span><span class="n">match_str</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="nb">float</span><span class="p">(</span><span class="n">latitude</span><span class="p">),</span> <span class="nb">float</span><span class="p">(</span><span class="n">longitude</span><span class="p">))</span>
-        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">constituency_cache_file</span><span class="p">,</span> <span class="s2">&quot;wb&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">pickle_fid</span><span class="p">:</span>
-            <span class="n">pickle</span><span class="o">.</span><span class="n">dump</span><span class="p">(</span><span class="n">constituency_lookup</span><span class="p">,</span> <span class="n">pickle_fid</span><span class="p">)</span>
-        <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;Constituency lookup extracted and pickled to &#39;</span><span class="si">%s</span><span class="s2">&#39;&quot;</span><span class="p">,</span>
-                     <span class="bp">self</span><span class="o">.</span><span class="n">constituency_cache_file</span><span class="p">)</span>
-        <span class="k">return</span> <span class="n">constituency_lookup</span>
-
-    <span class="k">def</span> <span class="nf">_load_lad_lookup</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
-<span class="w">        </span><span class="sd">&quot;&quot;&quot;Load a lookup of UK Local Authority District -&gt; Geospatial Centroid.&quot;&quot;&quot;</span>
-        <span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isfile</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">lad_cache_file</span><span class="p">):</span>
-            <span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Loading Local Authority District lookup from cache (&#39;</span><span class="si">%s</span><span class="s2">&#39;)&quot;</span><span class="p">,</span>
-                          <span class="bp">self</span><span class="o">.</span><span class="n">lad_cache_file</span><span class="p">)</span>
-            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">lad_cache_file</span><span class="p">,</span> <span class="s2">&quot;rb&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">pickle_fid</span><span class="p">:</span>
-                <span class="k">return</span> <span class="n">pickle</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">pickle_fid</span><span class="p">)</span>
-        <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;Extracting the Local Authority District Centroids data (this only needs to &quot;</span>
-                     <span class="s2">&quot;be done once)&quot;</span><span class="p">)</span>
-        <span class="n">lad_lookup</span> <span class="o">=</span> <span class="p">{}</span>
-        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">lad_lookup_file</span><span class="p">)</span> <span class="k">as</span> <span class="n">fid</span><span class="p">:</span>
-            <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">fid</span><span class="p">:</span>
-                <span class="n">_</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">longitude</span><span class="p">,</span> <span class="n">latitude</span> <span class="o">=</span> <span class="n">line</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&quot;|&quot;</span><span class="p">)</span>
-                <span class="n">match_str</span> <span class="o">=</span> <span class="n">name</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&quot; &quot;</span><span class="p">,</span> <span class="s2">&quot;&quot;</span><span class="p">)</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&quot;,&quot;</span><span class="p">,</span> <span class="s2">&quot;&quot;</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
-                <span class="n">lad_lookup</span><span class="p">[</span><span class="n">match_str</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="nb">float</span><span class="p">(</span><span class="n">latitude</span><span class="p">),</span> <span class="nb">float</span><span class="p">(</span><span class="n">longitude</span><span class="p">))</span>
-        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">lad_cache_file</span><span class="p">,</span> <span class="s2">&quot;wb&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">pickle_fid</span><span class="p">:</span>
-            <span class="n">pickle</span><span class="o">.</span><span class="n">dump</span><span class="p">(</span><span class="n">lad_lookup</span><span class="p">,</span> <span class="n">pickle_fid</span><span class="p">)</span>
-        <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;Local Authority District lookup extracted and pickled to &#39;</span><span class="si">%s</span><span class="s2">&#39;&quot;</span><span class="p">,</span>
-                     <span class="bp">self</span><span class="o">.</span><span class="n">lad_cache_file</span><span class="p">)</span>
-        <span class="k">return</span> <span class="n">lad_lookup</span>
-
-<div class="viewcode-block" id="Geocoder.cpo_geocode"><a class="viewcode-back" href="../modules.html#geocode.Geocoder.cpo_geocode">[docs]</a>    <span class="k">def</span> <span class="nf">cpo_geocode</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">pcs</span><span class="p">:</span> <span class="n">pd</span><span class="o">.</span><span class="n">DataFrame</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">pd</span><span class="o">.</span><span class="n">DataFrame</span><span class="p">:</span>
-<span class="w">        </span><span class="sd">&quot;&quot;&quot;</span>
-<span class="sd">        Geocode multiple postcodes using Code Point Open.</span>
-
-<span class="sd">        Parameters</span>
-<span class="sd">        ----------</span>
-<span class="sd">        `pcs` : Pandas.DataFrame</span>
-<span class="sd">            A Pandas DataFrame containing the postcodes (or partial postcodes) to geocode. Must</span>
-<span class="sd">            contain the column &#39;input_postcode&#39;.</span>
-
-<span class="sd">        Returns</span>
-<span class="sd">        -------</span>
-<span class="sd">        Pandas.DataFrame</span>
-<span class="sd">            A Pandas DataFrame with columns: input_postcode (str), latitude (float), longitude</span>
-<span class="sd">            (float), match_status (int). The `match_status` shows the quality of the postcode lookup</span>
-<span class="sd">            - use the class attribute *self.status_codes* (a dict) to get a string representation.</span>
-<span class="sd">        &quot;&quot;&quot;</span>
-        <span class="n">pcs</span><span class="p">[</span><span class="s2">&quot;id&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">arange</span><span class="p">(</span><span class="n">pcs</span><span class="o">.</span><span class="n">shape</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
-        <span class="n">pcs</span><span class="p">[</span><span class="s2">&quot;postcode_to_match&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="n">pcs</span><span class="o">.</span><span class="n">input_postcode</span><span class="o">.</span><span class="n">str</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">str</span><span class="o">.</span><span class="n">upper</span><span class="p">()</span><span class="o">.</span><span class="n">str</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&quot; &quot;</span><span class="p">,</span> <span class="s2">&quot;&quot;</span><span class="p">)</span>
-        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">cpo</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
-            <span class="bp">self</span><span class="o">.</span><span class="n">cpo</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_load_code_point_open</span><span class="p">()</span>
-        <span class="n">pcs</span> <span class="o">=</span> <span class="n">pcs</span><span class="o">.</span><span class="n">merge</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">cpo</span><span class="p">[[</span><span class="s2">&quot;Postcode&quot;</span><span class="p">,</span> <span class="s2">&quot;latitude&quot;</span><span class="p">,</span> <span class="s2">&quot;longitude&quot;</span><span class="p">]],</span> <span class="n">how</span><span class="o">=</span><span class="s2">&quot;left&quot;</span><span class="p">,</span>
-                        <span class="n">left_on</span><span class="o">=</span><span class="s2">&quot;postcode_to_match&quot;</span><span class="p">,</span> <span class="n">right_on</span><span class="o">=</span><span class="s2">&quot;Postcode&quot;</span><span class="p">)</span>
-        <span class="n">pcs</span> <span class="o">=</span> <span class="n">pcs</span><span class="o">.</span><span class="n">groupby</span><span class="p">(</span><span class="s2">&quot;id&quot;</span><span class="p">)</span><span class="o">.</span><span class="n">agg</span><span class="p">(</span><span class="n">input_postcode</span><span class="o">=</span><span class="p">(</span><span class="s2">&quot;input_postcode&quot;</span><span class="p">,</span> <span class="s2">&quot;first&quot;</span><span class="p">),</span>
-                                    <span class="n">postcode_to_match</span><span class="o">=</span><span class="p">(</span><span class="s2">&quot;postcode_to_match&quot;</span><span class="p">,</span> <span class="s2">&quot;first&quot;</span><span class="p">),</span>
-                                    <span class="n">postcode_matched</span><span class="o">=</span><span class="p">(</span><span class="s2">&quot;Postcode&quot;</span><span class="p">,</span> <span class="s2">&quot;first&quot;</span><span class="p">),</span>
-                                    <span class="n">latitude</span><span class="o">=</span><span class="p">(</span><span class="s2">&quot;latitude&quot;</span><span class="p">,</span> <span class="n">np</span><span class="o">.</span><span class="n">nanmean</span><span class="p">),</span>
-                                    <span class="n">longitude</span><span class="o">=</span><span class="p">(</span><span class="s2">&quot;longitude&quot;</span><span class="p">,</span> <span class="n">np</span><span class="o">.</span><span class="n">nanmean</span><span class="p">))</span><span class="o">.</span><span class="n">reset_index</span><span class="p">()</span>
-        <span class="n">success</span> <span class="o">=</span> <span class="n">pcs</span><span class="o">.</span><span class="n">postcode_matched</span><span class="o">.</span><span class="n">notnull</span><span class="p">()</span>
-        <span class="n">pcs</span><span class="o">.</span><span class="n">loc</span><span class="p">[</span><span class="n">success</span><span class="p">,</span> <span class="s2">&quot;match_status&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span>
-        <span class="k">if</span> <span class="ow">not</span> <span class="n">pcs</span><span class="o">.</span><span class="n">postcode_matched</span><span class="o">.</span><span class="n">isnull</span><span class="p">()</span><span class="o">.</span><span class="n">values</span><span class="o">.</span><span class="n">any</span><span class="p">():</span>
-            <span class="k">return</span> <span class="n">pcs</span><span class="p">[[</span><span class="s2">&quot;input_postcode&quot;</span><span class="p">,</span> <span class="s2">&quot;latitude&quot;</span><span class="p">,</span> <span class="s2">&quot;longitude&quot;</span><span class="p">,</span> <span class="s2">&quot;match_status&quot;</span><span class="p">]]</span>
-        <span class="n">cpo_geocode_one</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">p</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">cpo_geocode_one</span><span class="p">(</span><span class="n">p</span><span class="o">.</span><span class="n">input_postcode</span><span class="p">)</span>
-        <span class="n">pcs</span><span class="o">.</span><span class="n">loc</span><span class="p">[</span><span class="o">~</span><span class="n">success</span><span class="p">,</span> <span class="p">[</span><span class="s2">&quot;latitude&quot;</span><span class="p">,</span> <span class="s2">&quot;longitude&quot;</span><span class="p">,</span> <span class="s2">&quot;match_status&quot;</span><span class="p">]]</span> <span class="o">=</span> \
-            <span class="n">pcs</span><span class="o">.</span><span class="n">loc</span><span class="p">[</span><span class="o">~</span><span class="n">success</span><span class="p">]</span><span class="o">.</span><span class="n">apply</span><span class="p">(</span><span class="n">cpo_geocode_one</span><span class="p">,</span> <span class="n">axis</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
-        <span class="k">return</span> <span class="n">pcs</span><span class="p">[[</span><span class="s2">&quot;input_postcode&quot;</span><span class="p">,</span> <span class="s2">&quot;latitude&quot;</span><span class="p">,</span> <span class="s2">&quot;longitude&quot;</span><span class="p">,</span> <span class="s2">&quot;match_status&quot;</span><span class="p">]]</span></div>
+        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">ngeso</span><span class="o">.</span><span class="n">_load_dno_boundaries</span><span class="p">()</span></div>
 
-<div class="viewcode-block" id="Geocoder.cpo_geocode_one"><a class="viewcode-back" href="../modules.html#geocode.Geocoder.cpo_geocode_one">[docs]</a>    <span class="k">def</span> <span class="nf">cpo_geocode_one</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">postcode</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">pd</span><span class="o">.</span><span class="n">Series</span><span class="p">:</span>
+<div class="viewcode-block" id="Geocoder.get_gsp_regions"><a class="viewcode-back" href="../modules.html#geocode.Geocoder.get_gsp_regions">[docs]</a>    <span class="k">def</span> <span class="nf">get_gsp_regions</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
 <span class="w">        </span><span class="sd">&quot;&quot;&quot;</span>
-<span class="sd">        Use the OS Code Point Open Database to geocode a postcode (or partial postcode using as much</span>
-<span class="sd">        accuracy as the partial code permits).</span>
-
-<span class="sd">        Parameters</span>
-<span class="sd">        ----------</span>
-<span class="sd">        `postcode` : string</span>
-<span class="sd">            The postcode (or partial postcode) to geocode.</span>
-
-<span class="sd">        Returns</span>
-<span class="sd">        -------</span>
-<span class="sd">        Pandas.Series</span>
-<span class="sd">            A Pandas Series with fields: input_postcode (str), latitude (float), longitude (float),</span>
-<span class="sd">            match_status (int). The status code shows the quality of the postcode lookup - use the</span>
-<span class="sd">            class attribute *self.status_codes* (a dict) to get a string representation.</span>
+<span class="sd">        Get the 20220314 GSP / GNode boundaries from the ESO Data Portal API.</span>
 <span class="sd">        &quot;&quot;&quot;</span>
-        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">cpo</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
-            <span class="bp">self</span><span class="o">.</span><span class="n">cpo</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_load_code_point_open</span><span class="p">()</span>
-        <span class="n">postcode</span> <span class="o">=</span> <span class="n">postcode</span><span class="o">.</span><span class="n">upper</span><span class="p">()</span>
-        <span class="c1"># match = self.cpo[self.cpo[&quot;Postcode&quot;] == postcode.replace(&quot; &quot;, &quot;&quot;)].agg(&quot;mean&quot;,</span>
-                                                                                <span class="c1"># numeric_only=True)</span>
-        <span class="c1"># if not np.isnan(match.latitude):</span>
-            <span class="c1"># match[&quot;match_status&quot;] = 1</span>
-            <span class="c1"># return match[[&quot;latitude&quot;, &quot;longitude&quot;, &quot;match_status&quot;]]</span>
-        <span class="k">if</span> <span class="s2">&quot; &quot;</span> <span class="ow">in</span> <span class="n">postcode</span><span class="p">:</span>
-            <span class="n">outward</span><span class="p">,</span> <span class="n">inward</span> <span class="o">=</span> <span class="n">postcode</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&quot; &quot;</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
-            <span class="n">myfilter</span> <span class="o">=</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">cpo</span><span class="p">[</span><span class="s2">&quot;outward_postcode&quot;</span><span class="p">]</span> <span class="o">==</span> <span class="n">outward</span><span class="p">)</span> <span class="o">&amp;</span> \
-                       <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">cpo</span><span class="p">[</span><span class="s2">&quot;inward_postcode&quot;</span><span class="p">]</span><span class="o">.</span><span class="n">str</span><span class="o">.</span><span class="n">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">inward</span><span class="p">))</span> <span class="o">==</span> <span class="n">inward</span><span class="p">)</span>
-        <span class="k">else</span><span class="p">:</span>
-            <span class="n">outward</span> <span class="o">=</span> <span class="n">postcode</span>
-            <span class="n">myfilter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">cpo</span><span class="p">[</span><span class="s2">&quot;outward_postcode&quot;</span><span class="p">]</span> <span class="o">==</span> <span class="n">outward</span>
-        <span class="n">match</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">cpo</span><span class="p">[</span><span class="n">myfilter</span><span class="p">]</span><span class="o">.</span><span class="n">agg</span><span class="p">(</span><span class="s2">&quot;mean&quot;</span><span class="p">,</span> <span class="n">numeric_only</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
-        <span class="k">if</span> <span class="ow">not</span> <span class="n">np</span><span class="o">.</span><span class="n">isnan</span><span class="p">(</span><span class="n">match</span><span class="o">.</span><span class="n">latitude</span><span class="p">):</span>
-            <span class="k">match</span><span class="p">[</span><span class="s2">&quot;match_status&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="mi">2</span>
-            <span class="k">return</span> <span class="n">match</span><span class="p">[[</span><span class="s2">&quot;latitude&quot;</span><span class="p">,</span> <span class="s2">&quot;longitude&quot;</span><span class="p">,</span> <span class="s2">&quot;match_status&quot;</span><span class="p">]]</span>
-        <span class="k">return</span> <span class="n">pd</span><span class="o">.</span><span class="n">Series</span><span class="p">({</span><span class="s2">&quot;latitude&quot;</span><span class="p">:</span> <span class="n">np</span><span class="o">.</span><span class="n">nan</span><span class="p">,</span> <span class="s2">&quot;longitude&quot;</span><span class="p">:</span> <span class="n">np</span><span class="o">.</span><span class="n">nan</span><span class="p">,</span> <span class="s2">&quot;match_status&quot;</span><span class="p">:</span> <span class="mi">0</span><span class="p">})</span></div>
+        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">ngeso</span><span class="o">.</span><span class="n">_load_gsp_boundaries_20220314</span><span class="p">()</span></div>
 
-<div class="viewcode-block" id="Geocoder.gmaps_geocode_one"><a class="viewcode-back" href="../modules.html#geocode.Geocoder.gmaps_geocode_one">[docs]</a>    <span class="k">def</span> <span class="nf">gmaps_geocode_one</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">postcode</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">address</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">pd</span><span class="o">.</span><span class="n">Series</span><span class="p">:</span>
+<div class="viewcode-block" id="Geocoder.get_llsoa_boundaries"><a class="viewcode-back" href="../modules.html#geocode.Geocoder.get_llsoa_boundaries">[docs]</a>    <span class="k">def</span> <span class="nf">get_llsoa_boundaries</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
 <span class="w">        </span><span class="sd">&quot;&quot;&quot;</span>
-<span class="sd">        Use the GMaps API to geocode a postcode (or partial postcode) and/or an address.</span>
-
-<span class="sd">        Parameters</span>
-<span class="sd">        ----------</span>
-<span class="sd">        `postcode` : string</span>
-<span class="sd">            The postcode (or partial postcode) to geocode.</span>
-<span class="sd">        `address` : string</span>
-<span class="sd">            The address to geocode.</span>
-
-<span class="sd">        Returns</span>
-<span class="sd">        -------</span>
-<span class="sd">        Pandas.Series</span>
-<span class="sd">            A Pandas Series with fields: input_postcode (str), latitude (float), longitude (float),</span>
-<span class="sd">            match_status (int). The status code shows the quality of the postcode lookup - use the</span>
-<span class="sd">            class attribute *self.status_codes* (a dict) to get a string representation.</span>
+<span class="sd">        Load the LLSOA boundaries, either from local cache if available, else fetch from raw API</span>
+<span class="sd">        (England and Wales) and packaged data (Scotland).</span>
 <span class="sd">        &quot;&quot;&quot;</span>
-        <span class="k">if</span> <span class="n">postcode</span> <span class="ow">is</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">address</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
-            <span class="k">raise</span> <span class="n">GenericException</span><span class="p">(</span><span class="s2">&quot;You must pass either postcode or address, or both.&quot;</span><span class="p">)</span>
-        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">gmaps_cache</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
-            <span class="bp">self</span><span class="o">.</span><span class="n">gmaps_cache</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_load_gmaps_cache</span><span class="p">()</span>
-        <span class="n">sep</span> <span class="o">=</span> <span class="s2">&quot;, &quot;</span> <span class="k">if</span> <span class="n">address</span> <span class="ow">and</span> <span class="n">postcode</span> <span class="k">else</span> <span class="s2">&quot;&quot;</span>
-        <span class="n">postcode</span> <span class="o">=</span> <span class="n">postcode</span> <span class="k">if</span> <span class="n">postcode</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="k">else</span> <span class="s2">&quot;&quot;</span>
-        <span class="n">address</span> <span class="o">=</span> <span class="n">address</span> <span class="k">if</span> <span class="n">address</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="k">else</span> <span class="s2">&quot;&quot;</span>
-        <span class="n">search_term</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&quot;</span><span class="si">{</span><span class="n">address</span><span class="si">}{</span><span class="n">sep</span><span class="si">}{</span><span class="n">postcode</span><span class="si">}</span><span class="s2">&quot;</span>
-        <span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Querying Google Maps Geocoder API for &#39;</span><span class="si">%s</span><span class="s2">&#39;&quot;</span><span class="p">,</span> <span class="n">search_term</span><span class="p">)</span>
-        <span class="k">if</span> <span class="n">search_term</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">gmaps_cache</span><span class="p">:</span>
-            <span class="n">geocode_result</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">gmaps_cache</span><span class="p">[</span><span class="n">search_term</span><span class="p">]</span>
-        <span class="k">else</span><span class="p">:</span>
-            <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">gmaps_key</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
-                <span class="k">return</span> <span class="n">pd</span><span class="o">.</span><span class="n">Series</span><span class="p">({</span><span class="s2">&quot;latitude&quot;</span><span class="p">:</span> <span class="n">np</span><span class="o">.</span><span class="n">nan</span><span class="p">,</span> <span class="s2">&quot;longitude&quot;</span><span class="p">:</span> <span class="n">np</span><span class="o">.</span><span class="n">nan</span><span class="p">,</span> <span class="s2">&quot;match_status&quot;</span><span class="p">:</span> <span class="mi">0</span><span class="p">})</span>
-            <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">gmaps</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
-                <span class="bp">self</span><span class="o">.</span><span class="n">gmaps</span> <span class="o">=</span> <span class="n">googlemaps</span><span class="o">.</span><span class="n">Client</span><span class="p">(</span><span class="n">key</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">gmaps_key</span><span class="p">)</span>
-            <span class="n">geocode_result</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">gmaps</span><span class="o">.</span><span class="n">geocode</span><span class="p">(</span><span class="n">search_term</span><span class="p">,</span> <span class="n">region</span><span class="o">=</span><span class="s2">&quot;uk&quot;</span><span class="p">)</span>
-            <span class="bp">self</span><span class="o">.</span><span class="n">gmaps_cache</span><span class="p">[</span><span class="n">search_term</span><span class="p">]</span> <span class="o">=</span> <span class="n">geocode_result</span>
-        <span class="k">if</span> <span class="ow">not</span> <span class="n">geocode_result</span> <span class="ow">or</span> <span class="nb">len</span><span class="p">(</span><span class="n">geocode_result</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">:</span>
-            <span class="k">return</span> <span class="n">pd</span><span class="o">.</span><span class="n">Series</span><span class="p">({</span><span class="s2">&quot;latitude&quot;</span><span class="p">:</span> <span class="n">np</span><span class="o">.</span><span class="n">nan</span><span class="p">,</span> <span class="s2">&quot;longitude&quot;</span><span class="p">:</span> <span class="n">np</span><span class="o">.</span><span class="n">nan</span><span class="p">,</span> <span class="s2">&quot;match_status&quot;</span><span class="p">:</span> <span class="mi">0</span><span class="p">})</span>
-        <span class="n">geometry</span> <span class="o">=</span> <span class="n">geocode_result</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="s2">&quot;geometry&quot;</span><span class="p">]</span>
-        <span class="n">ok_loc_types</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&quot;ROOFTOP&quot;</span><span class="p">,</span> <span class="s2">&quot;GEOMETRIC_CENTER&quot;</span><span class="p">]</span>
-        <span class="k">if</span> <span class="n">geometry</span><span class="p">[</span><span class="s2">&quot;location_type&quot;</span><span class="p">]</span> <span class="ow">in</span> <span class="n">ok_loc_types</span> <span class="ow">or</span> \
-            <span class="n">geocode_result</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="s2">&quot;types&quot;</span><span class="p">]</span> <span class="o">==</span> <span class="p">[</span><span class="s2">&quot;postal_code&quot;</span><span class="p">]:</span>
-            <span class="k">return</span> <span class="n">pd</span><span class="o">.</span><span class="n">Series</span><span class="p">({</span><span class="s2">&quot;latitude&quot;</span><span class="p">:</span> <span class="n">geometry</span><span class="p">[</span><span class="s2">&quot;location&quot;</span><span class="p">][</span><span class="s2">&quot;lat&quot;</span><span class="p">],</span>
-                              <span class="s2">&quot;longitude&quot;</span><span class="p">:</span> <span class="n">geometry</span><span class="p">[</span><span class="s2">&quot;location&quot;</span><span class="p">][</span><span class="s2">&quot;lng&quot;</span><span class="p">],</span>
-                              <span class="s2">&quot;match_status&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">})</span>
-        <span class="k">return</span> <span class="n">pd</span><span class="o">.</span><span class="n">Series</span><span class="p">({</span><span class="s2">&quot;latitude&quot;</span><span class="p">:</span> <span class="n">np</span><span class="o">.</span><span class="n">nan</span><span class="p">,</span> <span class="s2">&quot;longitude&quot;</span><span class="p">:</span> <span class="n">np</span><span class="o">.</span><span class="n">nan</span><span class="p">,</span> <span class="s2">&quot;match_status&quot;</span><span class="p">:</span> <span class="mi">0</span><span class="p">})</span></div>
+        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">ons_nrs</span><span class="o">.</span><span class="n">_load_llsoa_boundaries</span><span class="p">()</span></div>
 
-<div class="viewcode-block" id="Geocoder.geocode"><a class="viewcode-back" href="../modules.html#geocode.Geocoder.geocode">[docs]</a>    <span class="k">def</span> <span class="nf">geocode</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span>
-                <span class="n">postcodes</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Iterable</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
-                <span class="n">addresses</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Iterable</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">Tuple</span><span class="p">[</span><span class="nb">float</span><span class="p">,</span> <span class="nb">float</span><span class="p">,</span> <span class="nb">int</span><span class="p">]]:</span>
+<div class="viewcode-block" id="Geocoder.geocode_llsoa"><a class="viewcode-back" href="../modules.html#geocode.Geocoder.geocode_llsoa">[docs]</a>    <span class="k">def</span> <span class="nf">geocode_llsoa</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">llsoa_boundaries</span><span class="p">):</span>
 <span class="w">        </span><span class="sd">&quot;&quot;&quot;</span>
-<span class="sd">        Geocode several postcodes and/or addresses.</span>
-
+<span class="sd">        Function to geocode a collection of llsoa boundaries into latlons.</span>
+<span class="sd">        </span>
 <span class="sd">        Parameters</span>
 <span class="sd">        ----------</span>
-<span class="sd">        `postcodes` : iterable of strings</span>
-<span class="sd">            The postcodes (or partial postcodes) to geocode. Must align with *addresses* if both are</span>
-<span class="sd">            passed.</span>
-<span class="sd">        `addresses` : iterable of strings</span>
-<span class="sd">            The addresses to geocode. Must align with *postcodes* if both are passed. If addresses</span>
-<span class="sd">            are passed, the GMaps API will be used since OS Code Point Open does not provide address</span>
-<span class="sd">            lookup.</span>
-
-<span class="sd">        Returns</span>
-<span class="sd">        -------</span>
-<span class="sd">        list of tuples</span>
-<span class="sd">            The output list will align with the input postcodes/addresses, with each element being a</span>
-<span class="sd">            tuple containing: latitude (float), longitude (float), status code (int). The status</span>
-<span class="sd">            code shows the quality of the postcode lookup - use the class attribute</span>
-<span class="sd">            *self.status_codes* (a dict) to get a string representation.</span>
-
-<span class="sd">        Notes</span>
-<span class="sd">        -----</span>
-<span class="sd">        The input iterables can be any Python object which can be interpreted by Pandas.DataFrame()</span>
-<span class="sd">        e.g. a list, tuple, Numpy array etc.</span>
-<span class="sd">        If you pass only postcodes, this method will priotiise OS Code Point Open as the geocoder.</span>
-<span class="sd">        If a postcode fails to geocode using OS CPO, it will be geocoded with GMaps.</span>
+<span class="sd">        `llsoa_boundaries` : iterable of strings</span>
+<span class="sd">            Specific llsoa boundaries to geocode to latlons</span>
 <span class="sd">        &quot;&quot;&quot;</span>
-        <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
-        <span class="k">if</span> <span class="n">postcodes</span> <span class="ow">is</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">addresses</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
-            <span class="k">raise</span> <span class="n">GenericException</span><span class="p">(</span><span class="s2">&quot;You must pass either postcodes or addresses, or both.&quot;</span><span class="p">)</span>
-        <span class="n">postcodes</span> <span class="o">=</span> <span class="p">[</span><span class="kc">None</span> <span class="k">for</span> <span class="n">a</span> <span class="ow">in</span> <span class="n">addresses</span><span class="p">]</span> <span class="k">if</span> <span class="n">postcodes</span> <span class="ow">is</span> <span class="kc">None</span> <span class="k">else</span> <span class="nb">list</span><span class="p">(</span><span class="n">postcodes</span><span class="p">)</span>
-        <span class="n">addresses</span> <span class="o">=</span> <span class="p">[</span><span class="kc">None</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">postcodes</span><span class="p">]</span> <span class="k">if</span> <span class="n">addresses</span> <span class="ow">is</span> <span class="kc">None</span> <span class="k">else</span> <span class="nb">list</span><span class="p">(</span><span class="n">addresses</span><span class="p">)</span>
-        <span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Geocoding </span><span class="si">%s</span><span class="s2"> postcodes (</span><span class="si">%s</span><span class="s2"> addresses)&quot;</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">postcodes</span><span class="p">),</span> <span class="nb">len</span><span class="p">(</span><span class="n">addresses</span><span class="p">))</span>
-        <span class="n">inputs</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">DataFrame</span><span class="p">({</span><span class="s2">&quot;input_postcode&quot;</span><span class="p">:</span> <span class="n">postcodes</span><span class="p">,</span> <span class="s2">&quot;input_address&quot;</span><span class="p">:</span> <span class="n">addresses</span><span class="p">})</span>
-        <span class="n">inputs</span><span class="p">[</span><span class="s2">&quot;id&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">arange</span><span class="p">(</span><span class="n">inputs</span><span class="o">.</span><span class="n">shape</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
-        <span class="n">results</span> <span class="o">=</span> <span class="n">inputs</span><span class="o">.</span><span class="n">merge</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">cache</span><span class="p">,</span> <span class="n">how</span><span class="o">=</span><span class="s2">&quot;left&quot;</span><span class="p">,</span> <span class="n">on</span><span class="o">=</span><span class="p">[</span><span class="s2">&quot;input_postcode&quot;</span><span class="p">,</span> <span class="s2">&quot;input_address&quot;</span><span class="p">])</span>
-        <span class="n">results_cols</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&quot;latitude&quot;</span><span class="p">,</span> <span class="s2">&quot;longitude&quot;</span><span class="p">,</span> <span class="s2">&quot;match_status&quot;</span><span class="p">]</span>
-        <span class="k">if</span> <span class="ow">not</span> <span class="n">results</span><span class="o">.</span><span class="n">latitude</span><span class="o">.</span><span class="n">isnull</span><span class="p">()</span><span class="o">.</span><span class="n">any</span><span class="p">():</span>
-            <span class="k">return</span> <span class="n">results</span><span class="p">[</span><span class="n">results_cols</span><span class="p">]</span><span class="o">.</span><span class="n">to_records</span><span class="p">(</span><span class="n">index</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
-        <span class="n">use_cpo</span> <span class="o">=</span> <span class="n">results</span><span class="o">.</span><span class="n">latitude</span><span class="o">.</span><span class="n">isnull</span><span class="p">()</span> <span class="o">&amp;</span> <span class="n">results</span><span class="o">.</span><span class="n">input_address</span><span class="o">.</span><span class="n">isnull</span><span class="p">()</span>
-        <span class="k">if</span> <span class="n">use_cpo</span><span class="o">.</span><span class="n">any</span><span class="p">():</span>
-            <span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Geocoding </span><span class="si">%s</span><span class="s2"> postcodes using Code Point Open&quot;</span><span class="p">,</span> <span class="n">use_cpo</span><span class="o">.</span><span class="n">sum</span><span class="p">())</span>
-            <span class="n">results</span><span class="o">.</span><span class="n">loc</span><span class="p">[</span><span class="n">use_cpo</span><span class="p">,</span> <span class="n">results_cols</span><span class="p">]</span> <span class="o">=</span> \
-                <span class="bp">self</span><span class="o">.</span><span class="n">cpo_geocode</span><span class="p">(</span><span class="n">inputs</span><span class="o">.</span><span class="n">loc</span><span class="p">[</span><span class="n">use_cpo</span><span class="p">,</span> <span class="p">[</span><span class="s2">&quot;input_postcode&quot;</span><span class="p">]])[</span><span class="n">results_cols</span><span class="p">]</span>
-            <span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Successfully geocoded </span><span class="si">%s</span><span class="s2"> postcodes using Code Point Open&quot;</span><span class="p">,</span>
-                          <span class="p">(</span><span class="n">results</span><span class="o">.</span><span class="n">latitude</span><span class="o">.</span><span class="n">notnull</span><span class="p">())</span><span class="o">.</span><span class="n">sum</span><span class="p">())</span>
-        <span class="n">use_gmaps</span> <span class="o">=</span> <span class="n">results</span><span class="o">.</span><span class="n">latitude</span><span class="o">.</span><span class="n">isnull</span><span class="p">()</span>
-        <span class="k">if</span> <span class="n">use_gmaps</span><span class="o">.</span><span class="n">any</span><span class="p">():</span>
-            <span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Geocoding </span><span class="si">%s</span><span class="s2"> postcodes/addresses using Google Maps&quot;</span><span class="p">,</span> <span class="n">use_gmaps</span><span class="o">.</span><span class="n">sum</span><span class="p">())</span>
-            <span class="n">gmaps_geocode</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">i</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">gmaps_geocode_one</span><span class="p">(</span><span class="n">i</span><span class="o">.</span><span class="n">input_postcode</span><span class="p">,</span> <span class="n">i</span><span class="o">.</span><span class="n">input_address</span><span class="p">)</span>
-            <span class="n">results</span><span class="o">.</span><span class="n">loc</span><span class="p">[</span><span class="n">use_gmaps</span><span class="p">,</span> <span class="n">results_cols</span><span class="p">]</span> <span class="o">=</span> \
-                <span class="n">inputs</span><span class="o">.</span><span class="n">loc</span><span class="p">[</span><span class="n">use_gmaps</span><span class="p">]</span><span class="o">.</span><span class="n">apply</span><span class="p">(</span><span class="n">gmaps_geocode</span><span class="p">,</span> <span class="n">axis</span><span class="o">=</span><span class="mi">1</span><span class="p">)[</span><span class="n">results_cols</span><span class="p">]</span>
-            <span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Successfully geocoded </span><span class="si">%s</span><span class="s2"> postcodes/addresses using Google Maps&quot;</span><span class="p">,</span>
-                          <span class="p">(</span><span class="n">use_gmaps</span> <span class="o">&amp;</span> <span class="n">results</span><span class="o">.</span><span class="n">latitude</span><span class="o">.</span><span class="n">notnull</span><span class="p">())</span><span class="o">.</span><span class="n">sum</span><span class="p">())</span>
-        <span class="n">results</span><span class="p">[</span><span class="s2">&quot;match_status&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="n">results</span><span class="o">.</span><span class="n">match_status</span><span class="o">.</span><span class="n">astype</span><span class="p">(</span><span class="nb">int</span><span class="p">)</span>
-        <span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Adding postcodes/addresses to cache&quot;</span><span class="p">)</span>
-        <span class="n">cache</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">concat</span><span class="p">([</span><span class="bp">self</span><span class="o">.</span><span class="n">cache</span><span class="p">,</span> <span class="n">results</span><span class="p">[</span><span class="n">results</span><span class="o">.</span><span class="n">latitude</span><span class="o">.</span><span class="n">notnull</span><span class="p">()]</span><span class="o">.</span><span class="n">drop</span><span class="p">(</span><span class="n">columns</span><span class="o">=</span><span class="s2">&quot;id&quot;</span><span class="p">)],</span>
-                          <span class="n">ignore_index</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
-        <span class="bp">self</span><span class="o">.</span><span class="n">cache</span> <span class="o">=</span> <span class="n">cache</span><span class="o">.</span><span class="n">drop_duplicates</span><span class="p">(</span><span class="n">subset</span><span class="o">=</span><span class="p">[</span><span class="s2">&quot;input_postcode&quot;</span><span class="p">,</span> <span class="s2">&quot;input_address&quot;</span><span class="p">],</span> <span class="n">keep</span><span class="o">=</span><span class="s2">&quot;last&quot;</span><span class="p">,</span>
-                                           <span class="n">ignore_index</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
-        <span class="k">return</span> <span class="n">results</span><span class="p">[</span><span class="n">results_cols</span><span class="p">]</span><span class="o">.</span><span class="n">to_records</span><span class="p">(</span><span class="n">index</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span></div>
+        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">geocode</span><span class="p">(</span><span class="n">llsoa_boundaries</span><span class="p">,</span> <span class="s2">&quot;llsoa&quot;</span><span class="p">)</span></div>
 
-<div class="viewcode-block" id="Geocoder.geocode_one"><a class="viewcode-back" href="../modules.html#geocode.Geocoder.geocode_one">[docs]</a>    <span class="k">def</span> <span class="nf">geocode_one</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span>
-                    <span class="n">postcode</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
-                    <span class="n">address</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Tuple</span><span class="p">[</span><span class="nb">float</span><span class="p">,</span> <span class="nb">float</span><span class="p">,</span> <span class="nb">int</span><span class="p">]:</span>
+<div class="viewcode-block" id="Geocoder.reverse_geocode_llsoa"><a class="viewcode-back" href="../modules.html#geocode.Geocoder.reverse_geocode_llsoa">[docs]</a>    <span class="k">def</span> <span class="nf">reverse_geocode_llsoa</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">latlons</span><span class="p">,</span> <span class="n">dz</span><span class="o">=</span><span class="kc">True</span><span class="p">):</span>
 <span class="w">        </span><span class="sd">&quot;&quot;&quot;</span>
-<span class="sd">        Geocode a single postcode and/or address.</span>
-
+<span class="sd">        Function to reverse geocode a collection of latlons into llsoa boundaries.</span>
+<span class="sd">        </span>
 <span class="sd">        Parameters</span>
 <span class="sd">        ----------</span>
-<span class="sd">        `postcode` : string</span>
-<span class="sd">            The postcode (or partial postcode) to geocode.</span>
-<span class="sd">        `address` : string</span>
-<span class="sd">            The address to geocode. If address is passed, the GMaps API will be used since OS Code</span>
-<span class="sd">            Point Open does not provide address lookup.</span>
-
-<span class="sd">        Returns</span>
-<span class="sd">        -------</span>
-<span class="sd">        tuple</span>
-<span class="sd">            A tuple containing: latitude (float), longitude (float), status code (int). The status</span>
-<span class="sd">            code shows the quality of the postcode lookup - use the class attribute</span>
-<span class="sd">            *self.status_codes* (a dict) to get a string representation.</span>
-
-<span class="sd">        Notes</span>
-<span class="sd">        -----</span>
-<span class="sd">        If you pass only postcode, this method will priotiise OS Code Point Open as the geocoder.</span>
-<span class="sd">        If a postcode fails to geocode using OS CPO, it will be geocoded with GMaps.</span>
+<span class="sd">        `latlons` : iterable of strings</span>
+<span class="sd">            Specific latlons to geocode to llsoa boundaries.</span>
+<span class="sd">        </span>
+<span class="sd">        `dz` : Boolean</span>
+<span class="sd">            Indication whether to consider datazones</span>
 <span class="sd">        &quot;&quot;&quot;</span>
-        <span class="k">if</span> <span class="n">postcode</span> <span class="ow">is</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">address</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
-            <span class="k">raise</span> <span class="n">GenericException</span><span class="p">(</span><span class="s2">&quot;You must specify either a postcode or an address, or both.&quot;</span><span class="p">)</span>
-        <span class="k">if</span> <span class="n">address</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
-            <span class="n">lat</span><span class="p">,</span> <span class="n">lon</span><span class="p">,</span> <span class="n">status</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">cpo_geocode_one</span><span class="p">(</span><span class="n">postcode</span><span class="p">)</span>
-            <span class="k">if</span> <span class="n">status</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">:</span>
-                <span class="k">return</span> <span class="n">lat</span><span class="p">,</span> <span class="n">lon</span><span class="p">,</span> <span class="n">status</span>
-        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">gmaps_geocode_one</span><span class="p">(</span><span class="n">postcode</span><span class="p">,</span> <span class="n">address</span><span class="p">)</span><span class="o">.</span><span class="n">to_records</span><span class="p">(</span><span class="n">index</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span></div>
+        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">reverse_geocode</span><span class="p">(</span><span class="n">latlons</span><span class="p">,</span> <span class="s2">&quot;llsoa&quot;</span><span class="p">,</span> <span class="n">datazones</span><span class="o">=</span><span class="n">dz</span><span class="p">)</span></div>
 
-<div class="viewcode-block" id="Geocoder.geocode_llsoa"><a class="viewcode-back" href="../modules.html#geocode.Geocoder.geocode_llsoa">[docs]</a>    <span class="k">def</span> <span class="nf">geocode_llsoa</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span>
-                      <span class="n">llsoa</span><span class="p">:</span> <span class="n">Union</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Iterable</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span>
-                     <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Union</span><span class="p">[</span><span class="n">Tuple</span><span class="p">[</span><span class="nb">float</span><span class="p">,</span> <span class="nb">float</span><span class="p">],</span> <span class="n">List</span><span class="p">[</span><span class="n">Tuple</span><span class="p">[</span><span class="nb">float</span><span class="p">,</span> <span class="nb">float</span><span class="p">]]]:</span>
+<div class="viewcode-block" id="Geocoder.geocode_constituency"><a class="viewcode-back" href="../modules.html#geocode.Geocoder.geocode_constituency">[docs]</a>    <span class="k">def</span> <span class="nf">geocode_constituency</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">constituencies</span><span class="p">):</span>
 <span class="w">        </span><span class="sd">&quot;&quot;&quot;</span>
-<span class="sd">        Geocode an LLSOA using the Population Weighted Centroid.</span>
-
+<span class="sd">        Function to geocode a collection of constituencies into latlons.</span>
+<span class="sd">        </span>
 <span class="sd">        Parameters</span>
 <span class="sd">        ----------</span>
-<span class="sd">        `llsoa` : string or iterable of strings</span>
-<span class="sd">            The LLSOA(s) to geocode.</span>
-
-<span class="sd">        Returns</span>
-<span class="sd">        -------</span>
-<span class="sd">        Tuple or List of Tuples</span>
-<span class="sd">            If a single LLSOA was passed (i.e. a string), the output be a tuple containing: latitude</span>
-<span class="sd">            (float), longitude (float). If several LLSOAs are passed (i.e. an iterable of strings),</span>
-<span class="sd">            the output will be a list of tuples which aligns with the input iterable.</span>
-
-<span class="sd">        Notes</span>
-<span class="sd">        -----</span>
-<span class="sd">        The input *llsoa* iterable can be any Python object which can be looped over with a for loop</span>
-<span class="sd">        e.g. a list, tuple, Numpy array etc.</span>
-<span class="sd">        LLSOAs are identified by their LLSOA Code, sometimes abbreviated to `llsoacd`.</span>
-<span class="sd">        In Scotland, one can use either LLSOA codes or Datazones, this method supports both.</span>
+<span class="sd">        `constituencies` : iterable of strings</span>
+<span class="sd">            Specific constituencies to geocode to latlons</span>
 <span class="sd">        &quot;&quot;&quot;</span>
-        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">llsoa_lookup</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
-            <span class="bp">self</span><span class="o">.</span><span class="n">llsoa_lookup</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_load_llsoa_lookup</span><span class="p">()</span>
-        <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">llsoa</span><span class="p">,</span> <span class="nb">str</span><span class="p">):</span>
-            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_llsoa_centroid</span><span class="p">(</span><span class="n">llsoa</span><span class="p">)</span>
-        <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
-        <span class="n">tot</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">llsoa</span><span class="p">)</span>
-        <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">llsoa_</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">llsoa</span><span class="p">):</span>
-            <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_llsoa_centroid</span><span class="p">(</span><span class="n">llsoa_</span><span class="p">))</span>
-            <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">progress_bar</span> <span class="ow">and</span> <span class="p">(</span><span class="n">i</span> <span class="o">%</span> <span class="mi">10</span> <span class="o">==</span> <span class="mi">0</span> <span class="ow">or</span> <span class="n">i</span> <span class="o">==</span> <span class="n">tot</span> <span class="o">-</span> <span class="mi">1</span><span class="p">):</span>
-                <span class="n">print_progress</span><span class="p">(</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span> <span class="n">tot</span><span class="p">,</span> <span class="n">prefix</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">prefix</span><span class="o">+</span><span class="s2">&quot;[Geocode]     LLSOA&quot;</span><span class="p">,</span> <span class="n">suffix</span><span class="o">=</span><span class="s2">&quot;&quot;</span><span class="p">,</span>
-                               <span class="n">decimals</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="n">bar_length</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
-        <span class="k">return</span> <span class="n">results</span></div>
+        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">geocode</span><span class="p">(</span><span class="n">constituencies</span><span class="p">,</span> <span class="s2">&quot;constituency&quot;</span><span class="p">)</span></div>
 
-<div class="viewcode-block" id="Geocoder.reverse_geocode_llsoa"><a class="viewcode-back" href="../modules.html#geocode.Geocoder.reverse_geocode_llsoa">[docs]</a>    <span class="k">def</span> <span class="nf">reverse_geocode_llsoa</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span>
-                              <span class="n">latlons</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Tuple</span><span class="p">[</span><span class="nb">float</span><span class="p">,</span> <span class="nb">float</span><span class="p">]],</span>
-                              <span class="n">datazones</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
+<div class="viewcode-block" id="Geocoder.geocode_local_authority"><a class="viewcode-back" href="../modules.html#geocode.Geocoder.geocode_local_authority">[docs]</a>    <span class="k">def</span> <span class="nf">geocode_local_authority</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">lads</span><span class="p">):</span>
 <span class="w">        </span><span class="sd">&quot;&quot;&quot;</span>
-<span class="sd">        Reverse-geocode latitudes and longitudes to LLSOA.</span>
-
+<span class="sd">        Function to geocode a collection of LADs (Local Authority Districts) into latlons.</span>
+<span class="sd">        </span>
 <span class="sd">        Parameters</span>
 <span class="sd">        ----------</span>
-<span class="sd">        `latlons` : list of tuples</span>
-<span class="sd">            A list of tuples containing (latitude, longitude).</span>
-<span class="sd">        `datazones` : bool</span>
-<span class="sd">            Set this to True to return Datazones rather than LLSOA codes in Scotland (default is</span>
-<span class="sd">            False).</span>
-
-<span class="sd">        Returns</span>
-<span class="sd">        -------</span>
-<span class="sd">        list of strings</span>
-<span class="sd">            The LLSOA codes that the input latitudes and longitudes fall within. Any lat/lons which</span>
-<span class="sd">            do not fall inside an LLSOA boundary will return None.</span>
+<span class="sd">        `lads` : iterable of strings</span>
+<span class="sd">            Specific LADs to geocode to latlons</span>
 <span class="sd">        &quot;&quot;&quot;</span>
-        <span class="k">if</span> <span class="ow">not</span> <span class="n">SHAPELY_AVAILABLE</span><span class="p">:</span>
-            <span class="k">raise</span> <span class="n">GenericException</span><span class="p">(</span><span class="s2">&quot;Geocode was unable to import the Shapely library, follow the &quot;</span>
-                                   <span class="s2">&quot;installation instructions at &quot;</span>
-                                   <span class="s2">&quot;https://github.com/SheffieldSolar/Geocode&quot;</span><span class="p">)</span>
-        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">llsoa_regions</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
-            <span class="bp">self</span><span class="o">.</span><span class="n">llsoa_regions</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_load_llsoa_boundaries</span><span class="p">()</span>
-        <span class="n">results</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_reverse_geocode</span><span class="p">(</span><span class="n">latlons</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">llsoa_regions</span><span class="p">)</span>
-        <span class="k">if</span> <span class="n">datazones</span><span class="p">:</span>
-            <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">dz_lookup</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
-                <span class="bp">self</span><span class="o">.</span><span class="n">dz_lookup</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_load_datazone_lookup</span><span class="p">()</span>
-            <span class="n">results</span> <span class="o">=</span> <span class="p">[</span><span class="n">llsoacd</span> <span class="k">if</span> <span class="n">llsoacd</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">dz_lookup</span> <span class="k">else</span> <span class="bp">self</span><span class="o">.</span><span class="n">dz_lookup</span><span class="p">[</span><span class="n">llsoacd</span><span class="p">]</span>
-                       <span class="k">for</span> <span class="n">llsoacd</span> <span class="ow">in</span> <span class="n">results</span><span class="p">]</span>
-        <span class="k">return</span> <span class="n">results</span></div>
+        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">geocode</span><span class="p">(</span><span class="n">lads</span><span class="p">,</span> <span class="s2">&quot;lad&quot;</span><span class="p">)</span></div>
 
-<div class="viewcode-block" id="Geocoder.postcode2llsoa"><a class="viewcode-back" href="../modules.html#geocode.Geocoder.postcode2llsoa">[docs]</a>    <span class="k">def</span> <span class="nf">postcode2llsoa</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">pcs</span><span class="p">:</span> <span class="n">pd</span><span class="o">.</span><span class="n">DataFrame</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">pd</span><span class="o">.</span><span class="n">DataFrame</span><span class="p">:</span>
+<div class="viewcode-block" id="Geocoder.reverse_geocode_gsp"><a class="viewcode-back" href="../modules.html#geocode.Geocoder.reverse_geocode_gsp">[docs]</a>    <span class="k">def</span> <span class="nf">reverse_geocode_gsp</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">latlons</span><span class="p">):</span>
 <span class="w">        </span><span class="sd">&quot;&quot;&quot;</span>
-<span class="sd">        Reverse-geocode multiple postcodes to LLSOA.</span>
-
+<span class="sd">        Function to reverse geocode a collection of latlons into gsp regions.</span>
+<span class="sd">        </span>
 <span class="sd">        Parameters</span>
 <span class="sd">        ----------</span>
-<span class="sd">        `pcs` : Pandas.DataFrame</span>
-<span class="sd">            A Pandas DataFrame containing the postcodes (or partial postcodes) to geocode. Must</span>
-<span class="sd">            contain the column &#39;input_postcode&#39;.</span>
-
-<span class="sd">        Returns</span>
-<span class="sd">        -------</span>
-<span class="sd">        Pandas.DataFrame</span>
-<span class="sd">            A Pandas DataFrame with columns: input_postcode (str), lsoa11cd (str), plus any other</span>
-<span class="sd">            cols that were in `pcs`.</span>
+<span class="sd">        `latlons` : iterable of strings</span>
+<span class="sd">            Specific latlons to geocode to gsp regions.</span>
 <span class="sd">        &quot;&quot;&quot;</span>
-        <span class="n">pcs</span><span class="p">[</span><span class="s2">&quot;postcode_to_match&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="n">pcs</span><span class="o">.</span><span class="n">input_postcode</span><span class="o">.</span><span class="n">str</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">str</span><span class="o">.</span><span class="n">upper</span><span class="p">()</span><span class="o">.</span><span class="n">str</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&quot; &quot;</span><span class="p">,</span> <span class="s2">&quot;&quot;</span><span class="p">)</span>
-        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">pc_llsoa_lookup</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
-            <span class="bp">self</span><span class="o">.</span><span class="n">pc_llsoa_lookup</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_load_postcode_llsoa_lookup</span><span class="p">()</span>
-        <span class="n">pcs</span> <span class="o">=</span> <span class="n">pcs</span><span class="o">.</span><span class="n">merge</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">pc_llsoa_lookup</span><span class="p">[[</span><span class="s2">&quot;postcode&quot;</span><span class="p">,</span> <span class="s2">&quot;lsoa11cd&quot;</span><span class="p">]],</span> <span class="n">how</span><span class="o">=</span><span class="s2">&quot;left&quot;</span><span class="p">,</span>
-                        <span class="n">left_on</span><span class="o">=</span><span class="s2">&quot;postcode_to_match&quot;</span><span class="p">,</span> <span class="n">right_on</span><span class="o">=</span><span class="s2">&quot;postcode&quot;</span><span class="p">)</span>
-        <span class="n">pcs</span><span class="o">.</span><span class="n">drop</span><span class="p">(</span><span class="n">columns</span><span class="o">=</span><span class="p">[</span><span class="s2">&quot;postcode_to_match&quot;</span><span class="p">],</span> <span class="n">inplace</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
-        <span class="k">return</span> <span class="n">pcs</span></div>
+        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">reverse_geocode</span><span class="p">(</span><span class="n">latlons</span><span class="p">,</span> <span class="s2">&quot;gsp&quot;</span><span class="p">)</span></div>
 
-    <span class="k">def</span> <span class="nf">_reverse_geocode</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span>
-                         <span class="n">coords</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Tuple</span><span class="p">[</span><span class="nb">float</span><span class="p">,</span> <span class="nb">float</span><span class="p">]],</span>
-                         <span class="n">regions</span><span class="p">:</span> <span class="n">Dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">:</span>
+<div class="viewcode-block" id="Geocoder.geocode_postcode"><a class="viewcode-back" href="../modules.html#geocode.Geocoder.geocode_postcode">[docs]</a>    <span class="k">def</span> <span class="nf">geocode_postcode</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">postcodes</span><span class="p">,</span> <span class="n">method</span><span class="o">=</span><span class="s2">&quot;cpo&quot;</span><span class="p">):</span>
 <span class="w">        </span><span class="sd">&quot;&quot;&quot;</span>
-<span class="sd">        Generic method to reverse-geocode x, y coordinates to regions.</span>
-
+<span class="sd">        Function to geocode a collection of postcodes into latlons.</span>
+<span class="sd">        </span>
 <span class="sd">        Parameters</span>
 <span class="sd">        ----------</span>
-<span class="sd">        `coords` : list of tuples</span>
-<span class="sd">            A list of tuples containing (y, x).</span>
-<span class="sd">        `regions` : dict</span>
-<span class="sd">            Dict whose keys are the region IDs and whose values are a tuple containing:</span>
-<span class="sd">            (region_boundary, region_bounds). The region boundary must be a Shapely</span>
-<span class="sd">            Polygon/MultiPolygon and the bounds should be a tuple containing (xmin, ymin, xmax,</span>
-<span class="sd">            ymax).</span>
-
-<span class="sd">        Returns</span>
-<span class="sd">        -------</span>
-<span class="sd">        list</span>
-<span class="sd">            The region IDs that the input coords fall within. Any coords which do not fall inside an</span>
-<span class="sd">            LLSOA boundary will return None.</span>
-
-<span class="sd">        Notes</span>
-<span class="sd">        -----</span>
-<span class="sd">        The region bounds are used to improve performance by first scanning for potential region</span>
-<span class="sd">        candidates using a simple inequality, since the performance of</span>
-<span class="sd">        `Shapely.MultiPolygon.contains()` is not great.</span>
+<span class="sd">        `postcodes` : iterable of strings</span>
+<span class="sd">            Specific postcodes to geocode to latlons</span>
 <span class="sd">        &quot;&quot;&quot;</span>
-        <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
-        <span class="n">tot</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">coords</span><span class="p">)</span>
-        <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="p">(</span><span class="n">y</span><span class="p">,</span> <span class="n">x</span><span class="p">)</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">coords</span><span class="p">):</span>
-            <span class="n">success</span> <span class="o">=</span> <span class="kc">False</span>
-            <span class="n">possible_matches</span> <span class="o">=</span> <span class="p">[]</span>
-            <span class="k">for</span> <span class="n">reg_id</span> <span class="ow">in</span> <span class="n">regions</span><span class="p">:</span>
-                <span class="n">bounds</span> <span class="o">=</span> <span class="n">regions</span><span class="p">[</span><span class="n">reg_id</span><span class="p">][</span><span class="mi">1</span><span class="p">]</span>
-                <span class="k">if</span> <span class="n">bounds</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">&lt;=</span> <span class="n">x</span> <span class="o">&lt;=</span> <span class="n">bounds</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="ow">and</span> <span class="n">bounds</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">&lt;=</span> <span class="n">y</span> <span class="o">&lt;=</span> <span class="n">bounds</span><span class="p">[</span><span class="mi">3</span><span class="p">]:</span>
-                    <span class="n">possible_matches</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">reg_id</span><span class="p">)</span>
-            <span class="k">for</span> <span class="n">reg_id</span> <span class="ow">in</span> <span class="n">possible_matches</span><span class="p">:</span>
-                <span class="k">if</span> <span class="n">regions</span><span class="p">[</span><span class="n">reg_id</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">contains</span><span class="p">(</span><span class="n">Point</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)):</span>
-                    <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">reg_id</span><span class="p">)</span>
-                    <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">progress_bar</span> <span class="ow">and</span> <span class="p">(</span><span class="n">i</span> <span class="o">%</span> <span class="mi">10</span> <span class="o">==</span> <span class="mi">0</span> <span class="ow">or</span> <span class="n">i</span> <span class="o">==</span> <span class="n">tot</span> <span class="o">-</span> <span class="mi">1</span><span class="p">):</span>
-                        <span class="n">print_progress</span><span class="p">(</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span> <span class="n">tot</span><span class="p">,</span> <span class="n">prefix</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">prefix</span><span class="o">+</span><span class="s2">&quot;[Geocode]     REVERSE-GEOCODE&quot;</span><span class="p">,</span>
-                                       <span class="n">suffix</span><span class="o">=</span><span class="s2">&quot;&quot;</span><span class="p">,</span> <span class="n">decimals</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="n">bar_length</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
-                    <span class="n">success</span> <span class="o">=</span> <span class="kc">True</span>
-                    <span class="k">break</span>
-            <span class="k">if</span> <span class="ow">not</span> <span class="n">success</span><span class="p">:</span>
-                <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="kc">None</span><span class="p">)</span>
-        <span class="k">return</span> <span class="n">results</span>
+        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">geocode</span><span class="p">(</span><span class="n">postcodes</span><span class="p">,</span> <span class="s2">&quot;postcode&quot;</span><span class="p">,</span> <span class="n">method</span><span class="o">=</span><span class="n">method</span><span class="p">)</span></div>
 
-<div class="viewcode-block" id="Geocoder.reverse_geocode_gsp"><a class="viewcode-back" href="../modules.html#geocode.Geocoder.reverse_geocode_gsp">[docs]</a>    <span class="k">def</span> <span class="nf">reverse_geocode_gsp</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span>
-                            <span class="n">latlons</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Tuple</span><span class="p">[</span><span class="nb">float</span><span class="p">,</span> <span class="nb">float</span><span class="p">]]</span>
-                           <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Tuple</span><span class="p">[</span><span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">],</span> <span class="n">List</span><span class="p">[</span><span class="n">List</span><span class="p">[</span><span class="n">Dict</span><span class="p">]]]:</span>
+<div class="viewcode-block" id="Geocoder.geocode"><a class="viewcode-back" href="../modules.html#geocode.Geocoder.geocode">[docs]</a>    <span class="k">def</span> <span class="nf">geocode</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">entity_ids</span><span class="p">,</span> <span class="n">entity</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
 <span class="w">        </span><span class="sd">&quot;&quot;&quot;</span>
-<span class="sd">        Reverse-geocode latitudes and longitudes to GSP using the 20220314 definitions.</span>
+<span class="sd">        Geocode a selection of GSP regions, llsoa boundaries, constituencies, LADs, postcodes or</span>
+<span class="sd">        addresses to latitudes and longitudes.</span>
 
 <span class="sd">        Parameters</span>
 <span class="sd">        ----------</span>
-<span class="sd">        `latlons` : list of tuples</span>
-<span class="sd">            A list of tuples containing (latitude, longitude).</span>
-
-<span class="sd">        Returns</span>
-<span class="sd">        -------</span>
-<span class="sd">        `results` : list of ints</span>
-<span class="sd">            A list of tuples containing (&lt;GSPs&gt;, &lt;GSPGroup&gt;), aligned with the input *latlons*.</span>
-
-<span class="sd">        Notes</span>
-<span class="sd">        -----</span>
-<span class="sd">        Return format needs some work, maybe switch to DataFrames in future release.</span>
+<span class="sd">        `entity_ids` : iterable of strings</span>
+<span class="sd">            The specific entities to Geocode.</span>
+<span class="sd">        `entity` : string</span>
+<span class="sd">            Specify the entity type to Geocode from i.e., lad or postcode.</span>
+<span class="sd">        `**kwargs`</span>
+<span class="sd">            Options to pass to the underlying geocode method.</span>
 <span class="sd">        &quot;&quot;&quot;</span>
-        <span class="k">if</span> <span class="ow">not</span> <span class="n">SHAPELY_AVAILABLE</span><span class="p">:</span>
-            <span class="k">raise</span> <span class="n">GenericException</span><span class="p">(</span><span class="s2">&quot;Geocode was unable to import the Shapely library, follow the &quot;</span>
-                                   <span class="s2">&quot;installation instructions at &quot;</span>
-                                   <span class="s2">&quot;https://github.com/SheffieldSolar/Geocode&quot;</span><span class="p">)</span>
-        <span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Reverse geocoding </span><span class="si">%s</span><span class="s2"> latlons to 20220314 GSP&quot;</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">latlons</span><span class="p">))</span>
-        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">gsp_regions</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
-            <span class="bp">self</span><span class="o">.</span><span class="n">gsp_regions</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_load_gsp_boundaries_20220314</span><span class="p">()</span>
-        <span class="n">lats</span> <span class="o">=</span> <span class="p">[</span><span class="n">l</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="k">for</span> <span class="n">l</span> <span class="ow">in</span> <span class="n">latlons</span><span class="p">]</span>
-        <span class="n">lons</span> <span class="o">=</span> <span class="p">[</span><span class="n">l</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="k">for</span> <span class="n">l</span> <span class="ow">in</span> <span class="n">latlons</span><span class="p">]</span>
-        <span class="c1"># Rather than re-project the region boundaries, re-project the input lat/lons</span>
-        <span class="c1"># (easier, but slightly slower if reverse-geocoding a lot)</span>
-        <span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Converting latlons to BNG&quot;</span><span class="p">)</span>
-        <span class="n">eastings</span><span class="p">,</span> <span class="n">northings</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_latlon2bng</span><span class="p">(</span><span class="n">lons</span><span class="p">,</span> <span class="n">lats</span><span class="p">)</span>
-        <span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Reverse geocoding&quot;</span><span class="p">)</span>
-        <span class="n">results</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_reverse_geocode</span><span class="p">(</span><span class="nb">list</span><span class="p">(</span><span class="nb">zip</span><span class="p">(</span><span class="n">northings</span><span class="p">,</span> <span class="n">eastings</span><span class="p">)),</span> <span class="bp">self</span><span class="o">.</span><span class="n">gsp_regions</span><span class="p">)</span>
-        <span class="k">return</span> <span class="n">results</span></div>
+        <span class="n">entity</span> <span class="o">=</span> <span class="n">entity</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
+        <span class="k">if</span> <span class="n">entity</span> <span class="o">==</span> <span class="s2">&quot;gsp&quot;</span><span class="p">:</span>
+            <span class="k">raise</span> <span class="n">utils</span><span class="o">.</span><span class="n">GenericException</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;Entity &#39;</span><span class="si">{</span><span class="n">entity</span><span class="si">}</span><span class="s2">&#39; is not supported.&quot;</span><span class="p">)</span>
+        <span class="k">elif</span> <span class="n">entity</span> <span class="o">==</span> <span class="s2">&quot;llsoa&quot;</span><span class="p">:</span>
+            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">ons_nrs</span><span class="o">.</span><span class="n">geocode_llsoa</span><span class="p">(</span><span class="n">llsoa</span><span class="o">=</span><span class="n">entity_ids</span><span class="p">)</span>
+        <span class="k">elif</span> <span class="n">entity</span> <span class="o">==</span> <span class="s2">&quot;constituency&quot;</span><span class="p">:</span>
+            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">ons_nrs</span><span class="o">.</span><span class="n">geocode_constituency</span><span class="p">(</span><span class="n">constituency</span><span class="o">=</span><span class="n">entity_ids</span><span class="p">)</span>
+        <span class="k">elif</span> <span class="n">entity</span> <span class="o">==</span> <span class="s2">&quot;lad&quot;</span><span class="p">:</span>
+            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">ons_nrs</span><span class="o">.</span><span class="n">geocode_local_authority</span><span class="p">(</span><span class="n">local_authority</span><span class="o">=</span><span class="n">entity_ids</span><span class="p">)</span>
+        <span class="k">elif</span> <span class="n">entity</span> <span class="o">==</span> <span class="s2">&quot;postcode&quot;</span><span class="p">:</span>
+            <span class="n">method</span> <span class="o">=</span> <span class="n">kwargs</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;method&quot;</span><span class="p">,</span> <span class="s2">&quot;cpo&quot;</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
+            <span class="n">address</span> <span class="o">=</span> <span class="n">kwargs</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;address&quot;</span><span class="p">,</span> <span class="kc">None</span><span class="p">)</span>
+            <span class="k">if</span> <span class="n">address</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
+                <span class="k">if</span> <span class="n">method</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&quot; &quot;</span><span class="p">,</span> <span class="s2">&quot;&quot;</span><span class="p">)</span> <span class="ow">in</span> <span class="p">[</span><span class="s2">&quot;cpo&quot;</span><span class="p">,</span> <span class="s2">&quot;codepointopen&quot;</span><span class="p">]:</span>
+                    <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">cpo</span><span class="o">.</span><span class="n">geocode_postcode</span><span class="p">(</span><span class="n">postcodes</span><span class="o">=</span><span class="n">entity_ids</span><span class="p">)</span>
+                <span class="k">elif</span> <span class="n">method</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&quot; &quot;</span><span class="p">,</span> <span class="s2">&quot;&quot;</span><span class="p">)</span> <span class="ow">in</span> <span class="p">[</span><span class="s2">&quot;gmaps&quot;</span><span class="p">,</span> <span class="s2">&quot;googlemaps&quot;</span><span class="p">]:</span>
+                    <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">gmaps</span><span class="o">.</span><span class="n">geocode_postcode</span><span class="p">(</span><span class="n">postcode</span><span class="o">=</span><span class="n">entity_ids</span><span class="p">)</span>
+            <span class="k">else</span><span class="p">:</span>
+                <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">gmaps</span><span class="o">.</span><span class="n">geocode_postcode</span><span class="p">(</span><span class="n">postcode</span><span class="o">=</span><span class="n">entity_ids</span><span class="p">,</span> <span class="n">address</span><span class="o">=</span><span class="n">address</span><span class="p">)</span>
+        <span class="k">else</span><span class="p">:</span>
+            <span class="k">raise</span> <span class="n">utils</span><span class="o">.</span><span class="n">GenericException</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;Entity &#39;</span><span class="si">{</span><span class="n">entity</span><span class="si">}</span><span class="s2">&#39; is not supported.&quot;</span><span class="p">)</span></div>
 
-<div class="viewcode-block" id="Geocoder.reverse_geocode_gsp_20181031"><a class="viewcode-back" href="../modules.html#geocode.Geocoder.reverse_geocode_gsp_20181031">[docs]</a>    <span class="k">def</span> <span class="nf">reverse_geocode_gsp_20181031</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span>
-                                     <span class="n">latlons</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Tuple</span><span class="p">[</span><span class="nb">float</span><span class="p">,</span> <span class="nb">float</span><span class="p">]]</span>
-                                    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Tuple</span><span class="p">[</span><span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">],</span> <span class="n">List</span><span class="p">[</span><span class="n">List</span><span class="p">[</span><span class="n">Dict</span><span class="p">]]]:</span>
+<div class="viewcode-block" id="Geocoder.reverse_geocode"><a class="viewcode-back" href="../modules.html#geocode.Geocoder.reverse_geocode">[docs]</a>    <span class="k">def</span> <span class="nf">reverse_geocode</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">latlons</span><span class="p">,</span> <span class="n">entity</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
 <span class="w">        </span><span class="sd">&quot;&quot;&quot;</span>
-<span class="sd">        Reverse-geocode latitudes and longitudes to GSP using the 20181031 definitions.</span>
+<span class="sd">        Reverse geocode a set of latitudes and longitudes to either GSP regions or llsoa boundaries.</span>
 
 <span class="sd">        Parameters</span>
 <span class="sd">        ----------</span>
 <span class="sd">        `latlons` : list of tuples</span>
 <span class="sd">            A list of tuples containing (latitude, longitude).</span>
-
-<span class="sd">        Returns</span>
-<span class="sd">        -------</span>
-<span class="sd">        `results` : list of ints</span>
-<span class="sd">            A list of region IDs, aligned with the input *latlons*.</span>
-<span class="sd">        `results_more` : list of lists of dicts</span>
-<span class="sd">            The full MANY:MANY lookup giving the Region &lt;-&gt; GSP &lt;-&gt; GNode which the lat/lon falls</span>
-<span class="sd">            within. The relationship between GSP: GNode is MANY:MANY, so each element of the outer</span>
-<span class="sd">            list is another list of matches, each element of which is a dictionary giving the</span>
-<span class="sd">            matched GSP / GNode.</span>
-
-<span class="sd">        Notes</span>
-<span class="sd">        -----</span>
-<span class="sd">        Return format needs some work, maybe switch to DataFrames in future release.</span>
-<span class="sd">        &quot;&quot;&quot;</span>
-        <span class="k">if</span> <span class="ow">not</span> <span class="n">SHAPELY_AVAILABLE</span><span class="p">:</span>
-            <span class="k">raise</span> <span class="n">GenericException</span><span class="p">(</span><span class="s2">&quot;Geocode was unable to import the Shapely library, follow the &quot;</span>
-                                   <span class="s2">&quot;installation instructions at &quot;</span>
-                                   <span class="s2">&quot;https://github.com/SheffieldSolar/Geocode&quot;</span><span class="p">)</span>
-        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">gsp_regions_20181031</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
-            <span class="bp">self</span><span class="o">.</span><span class="n">gsp_regions_20181031</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_load_gsp_boundaries_20181031</span><span class="p">()</span>
-        <span class="n">lats</span> <span class="o">=</span> <span class="p">[</span><span class="n">l</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="k">for</span> <span class="n">l</span> <span class="ow">in</span> <span class="n">latlons</span><span class="p">]</span>
-        <span class="n">lons</span> <span class="o">=</span> <span class="p">[</span><span class="n">l</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="k">for</span> <span class="n">l</span> <span class="ow">in</span> <span class="n">latlons</span><span class="p">]</span>
-        <span class="c1"># Rather than re-project the region boundaries, re-project the input lat/lons</span>
-        <span class="c1"># (easier, but slightly slower if reverse-geocoding a lot)</span>
-        <span class="n">eastings</span><span class="p">,</span> <span class="n">northings</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_latlon2bng</span><span class="p">(</span><span class="n">lons</span><span class="p">,</span> <span class="n">lats</span><span class="p">)</span>
-        <span class="n">results</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_reverse_geocode</span><span class="p">(</span><span class="nb">list</span><span class="p">(</span><span class="nb">zip</span><span class="p">(</span><span class="n">northings</span><span class="p">,</span> <span class="n">eastings</span><span class="p">)),</span> <span class="bp">self</span><span class="o">.</span><span class="n">gsp_regions_20181031</span><span class="p">)</span>
-        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">gsp_lookup_20181031</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
-            <span class="bp">self</span><span class="o">.</span><span class="n">gsp_lookup_20181031</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_load_gsp_lookup_20181031</span><span class="p">()</span>
-        <span class="n">lookup</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">gsp_lookup_20181031</span>
-        <span class="n">reg_lookup</span> <span class="o">=</span> <span class="p">{</span><span class="n">r</span><span class="p">:</span> <span class="n">lookup</span><span class="p">[</span><span class="n">lookup</span><span class="o">.</span><span class="n">region_id</span> <span class="o">==</span> <span class="n">r</span><span class="p">]</span><span class="o">.</span><span class="n">to_dict</span><span class="p">(</span><span class="n">orient</span><span class="o">=</span><span class="s2">&quot;records&quot;</span><span class="p">)</span>
-                      <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="nb">list</span><span class="p">(</span><span class="nb">set</span><span class="p">(</span><span class="n">results</span><span class="p">))}</span>
-        <span class="n">results_more</span> <span class="o">=</span> <span class="p">[</span><span class="n">reg_lookup</span><span class="p">[</span><span class="n">r</span><span class="p">]</span> <span class="k">if</span> <span class="n">r</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="k">else</span> <span class="kc">None</span> <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span><span class="p">]</span>
-        <span class="k">return</span> <span class="n">results</span><span class="p">,</span> <span class="n">results_more</span></div>
-
-    <span class="k">def</span> <span class="nf">_llsoa_centroid</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">llsoa</span><span class="p">):</span>
-<span class="w">        </span><span class="sd">&quot;&quot;&quot;Lookup the PWC for a given LLSOA code.&quot;&quot;&quot;</span>
-        <span class="k">try</span><span class="p">:</span>
-            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">llsoa_lookup</span><span class="p">[</span><span class="n">llsoa</span><span class="p">]</span>
-        <span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
-            <span class="k">return</span> <span class="kc">None</span><span class="p">,</span> <span class="kc">None</span>
-
-<div class="viewcode-block" id="Geocoder.geocode_constituency"><a class="viewcode-back" href="../modules.html#geocode.Geocoder.geocode_constituency">[docs]</a>    <span class="k">def</span> <span class="nf">geocode_constituency</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span>
-                             <span class="n">constituency</span><span class="p">:</span> <span class="n">Union</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Iterable</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span>
-                            <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Union</span><span class="p">[</span><span class="n">Tuple</span><span class="p">[</span><span class="nb">float</span><span class="p">,</span> <span class="nb">float</span><span class="p">],</span> <span class="n">List</span><span class="p">[</span><span class="n">Tuple</span><span class="p">[</span><span class="nb">float</span><span class="p">,</span> <span class="nb">float</span><span class="p">]]]:</span>
-<span class="w">        </span><span class="sd">&quot;&quot;&quot;</span>
-<span class="sd">        Geocode a UK Constituency using the geospatial centroid.</span>
-
-<span class="sd">        Parameters</span>
-<span class="sd">        ----------</span>
-<span class="sd">        `constituency` : string or iterable of strings</span>
-<span class="sd">            The constituency names to geocode.</span>
-
-<span class="sd">        Returns</span>
-<span class="sd">        -------</span>
-<span class="sd">        tuple or list of tuples</span>
-<span class="sd">            If a single constituency name was passed (i.e. a string), the output be a tuple</span>
-<span class="sd">            containing: latitude (float), longitude (float). If several constituency names are</span>
-<span class="sd">            passed (i.e. an iterable of strings), the output will be a list of tuples which aligns</span>
-<span class="sd">            with the input iterable.</span>
-
-<span class="sd">        Notes</span>
-<span class="sd">        -----</span>
-<span class="sd">        The input *constituency* iterable can be any Python object which can be looped over with a</span>
-<span class="sd">        for loop e.g. a list, tuple, Numpy array etc.</span>
-<span class="sd">        Constituencies are identified by their full name, case-insensitive, ignoring spaces.</span>
+<span class="sd">        `entity` : string</span>
+<span class="sd">            Specify the entity type to Geocode from i.e., gsp or llsoa.</span>
+<span class="sd">        `**kwargs`</span>
+<span class="sd">            Options to pass to the underlying reverse-geocode method.</span>
 <span class="sd">        &quot;&quot;&quot;</span>
-        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">constituency_lookup</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
-            <span class="bp">self</span><span class="o">.</span><span class="n">constituency_lookup</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_load_constituency_lookup</span><span class="p">()</span>
-        <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">constituency</span><span class="p">,</span> <span class="nb">str</span><span class="p">):</span>
-            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_constituency_centroid</span><span class="p">(</span><span class="n">constituency</span><span class="p">)</span>
-        <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
-        <span class="n">tot</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">constituency</span><span class="p">)</span>
-        <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">constituency_</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">constituency</span><span class="p">):</span>
-            <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_constituency_centroid</span><span class="p">(</span><span class="n">constituency_</span><span class="p">))</span>
-            <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">progress_bar</span> <span class="ow">and</span> <span class="p">(</span><span class="n">i</span> <span class="o">%</span> <span class="mi">10</span> <span class="o">==</span> <span class="mi">0</span> <span class="ow">or</span> <span class="n">i</span> <span class="o">==</span> <span class="n">tot</span> <span class="o">-</span> <span class="mi">1</span><span class="p">):</span>
-                <span class="n">print_progress</span><span class="p">(</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span> <span class="n">tot</span><span class="p">,</span> <span class="n">prefix</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">prefix</span><span class="o">+</span><span class="s2">&quot;[Geocode]     CONSTITUENCY&quot;</span><span class="p">,</span> <span class="n">suffix</span><span class="o">=</span><span class="s2">&quot;&quot;</span><span class="p">,</span>
-                               <span class="n">decimals</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="n">bar_length</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
-        <span class="k">return</span> <span class="n">results</span></div>
-
-    <span class="k">def</span> <span class="nf">_constituency_centroid</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">constituency</span><span class="p">):</span>
-<span class="w">        </span><span class="sd">&quot;&quot;&quot;Lookup the GC for a given constituency.&quot;&quot;&quot;</span>
-        <span class="k">try</span><span class="p">:</span>
-            <span class="n">match_str</span> <span class="o">=</span> <span class="n">constituency</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&quot; &quot;</span><span class="p">,</span> <span class="s2">&quot;&quot;</span><span class="p">)</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&quot;,&quot;</span><span class="p">,</span> <span class="s2">&quot;&quot;</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
-            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">constituency_lookup</span><span class="p">[</span><span class="n">match_str</span><span class="p">]</span>
-        <span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
-            <span class="k">return</span> <span class="kc">None</span><span class="p">,</span> <span class="kc">None</span>
+        <span class="n">entity</span> <span class="o">=</span> <span class="n">entity</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
+        <span class="k">if</span> <span class="n">entity</span> <span class="o">==</span> <span class="s2">&quot;gsp&quot;</span><span class="p">:</span>
+            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">ngeso</span><span class="o">.</span><span class="n">reverse_geocode_gsp</span><span class="p">(</span><span class="n">latlons</span><span class="p">)</span>
+        <span class="k">elif</span> <span class="n">entity</span> <span class="o">==</span> <span class="s2">&quot;llsoa&quot;</span><span class="p">:</span>
+            <span class="n">datazones</span> <span class="o">=</span> <span class="n">kwargs</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;datazones&quot;</span><span class="p">,</span> <span class="kc">False</span><span class="p">)</span>
+            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">ons_nrs</span><span class="o">.</span><span class="n">reverse_geocode_llsoa</span><span class="p">(</span><span class="n">latlons</span><span class="o">=</span><span class="n">latlons</span><span class="p">,</span> <span class="n">datazones</span><span class="o">=</span><span class="n">datazones</span><span class="p">)</span>
+        <span class="k">else</span><span class="p">:</span>
+            <span class="k">raise</span> <span class="n">utils</span><span class="o">.</span><span class="n">GenericException</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;Entity &#39;</span><span class="si">{</span><span class="n">entity</span><span class="si">}</span><span class="s2">&#39; is not supported.&quot;</span><span class="p">)</span></div>
 
-<div class="viewcode-block" id="Geocoder.geocode_local_authority"><a class="viewcode-back" href="../modules.html#geocode.Geocoder.geocode_local_authority">[docs]</a>    <span class="k">def</span> <span class="nf">geocode_local_authority</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span>
-                                <span class="n">local_authority</span><span class="p">:</span> <span class="n">Union</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Iterable</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span>
-                               <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Union</span><span class="p">[</span><span class="n">Tuple</span><span class="p">[</span><span class="nb">float</span><span class="p">,</span> <span class="nb">float</span><span class="p">],</span> <span class="n">List</span><span class="p">[</span><span class="n">Tuple</span><span class="p">[</span><span class="nb">float</span><span class="p">,</span> <span class="nb">float</span><span class="p">]]]:</span>
+    <span class="nd">@staticmethod</span>
+    <span class="k">def</span> <span class="nf">_latlon2bng</span><span class="p">(</span><span class="n">lons</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">float</span><span class="p">],</span> <span class="n">lats</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">float</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">Tuple</span><span class="p">[</span><span class="n">List</span><span class="p">[</span><span class="nb">float</span><span class="p">],</span> <span class="n">List</span><span class="p">[</span><span class="nb">float</span><span class="p">]]:</span>
 <span class="w">        </span><span class="sd">&quot;&quot;&quot;</span>
-<span class="sd">        Geocode a UK Local Authority using the geospatial centroid.</span>
-
+<span class="sd">        Convert latitudes and longitudes (WGS 1984) to Eastings and Northings (a.k.a British</span>
+<span class="sd">        National Grid a.k.a OSGB 1936).</span>
 <span class="sd">        Parameters</span>
 <span class="sd">        ----------</span>
-<span class="sd">        `local_authority` : string or iterable of strings</span>
-<span class="sd">            The Local Authority names to geocode.</span>
-
+<span class="sd">        `lons` : list of floats</span>
+<span class="sd">            Corresponding longitude co-ordinates in WGS 1984 CRS.</span>
+<span class="sd">        `lats` : list of floats</span>
+<span class="sd">            Corresponding latitude co-ordinates in WGS 1984 CRS.</span>
 <span class="sd">        Returns</span>
 <span class="sd">        -------</span>
-<span class="sd">        tuple or list of tuples</span>
-<span class="sd">            If a single Local Authority name was passed (i.e. a string), the output be a tuple</span>
-<span class="sd">            containing: latitude (float), longitude (float). If several Local Authority names are</span>
-<span class="sd">            passed (i.e. an iterable of strings), the output will be a list of tuples which aligns</span>
-<span class="sd">            with the input iterable.</span>
-
+<span class="sd">        `eastings` : list of floats or ints</span>
+<span class="sd">            Easting co-ordinates.</span>
+<span class="sd">        `northings` : list of floats or ints</span>
+<span class="sd">            Northing co-ordinates.</span>
 <span class="sd">        Notes</span>
 <span class="sd">        -----</span>
-<span class="sd">        The input *local_authority* iterable can be any Python object which can be looped over with</span>
-<span class="sd">        a for loop e.g. a list, tuple, Numpy array etc.</span>
-<span class="sd">        Local Authorities are identified by their full name, case-insensitive, ignoring spaces.</span>
+<span class="sd">        Be careful! This method uses the same convention of ordering (eastings, northings) and</span>
+<span class="sd">        (lons, lats) as pyproj i.e. (x, y). Elsewhere in this module the convention is typically</span>
+<span class="sd">        (lats, lons) due to personal preference.</span>
 <span class="sd">        &quot;&quot;&quot;</span>
-        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">lad_lookup</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
-            <span class="bp">self</span><span class="o">.</span><span class="n">lad_lookup</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_load_lad_lookup</span><span class="p">()</span>
-        <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">local_authority</span><span class="p">,</span> <span class="nb">str</span><span class="p">):</span>
-            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_lad_centroid</span><span class="p">(</span><span class="n">local_authority</span><span class="p">)</span>
-        <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
-        <span class="n">tot</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">local_authority</span><span class="p">)</span>
-        <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">local_authority_</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">local_authority</span><span class="p">):</span>
-            <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_lad_centroid</span><span class="p">(</span><span class="n">local_authority_</span><span class="p">))</span>
-            <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">progress_bar</span> <span class="ow">and</span> <span class="p">(</span><span class="n">i</span> <span class="o">%</span> <span class="mi">10</span> <span class="o">==</span> <span class="mi">0</span> <span class="ow">or</span> <span class="n">i</span> <span class="o">==</span> <span class="n">tot</span> <span class="o">-</span> <span class="mi">1</span><span class="p">):</span>
-                <span class="n">print_progress</span><span class="p">(</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span> <span class="n">tot</span><span class="p">,</span> <span class="n">prefix</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">prefix</span><span class="o">+</span><span class="s2">&quot;[Geocode]     LOCAL AUTHORITY&quot;</span><span class="p">,</span>
-                               <span class="n">suffix</span><span class="o">=</span><span class="s2">&quot;&quot;</span><span class="p">,</span> <span class="n">decimals</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="n">bar_length</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
-        <span class="k">return</span> <span class="n">results</span></div>
-
-    <span class="k">def</span> <span class="nf">_lad_centroid</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">local_authority</span><span class="p">):</span>
-<span class="w">        </span><span class="sd">&quot;&quot;&quot;Lookup the GC for a given Local Authority.&quot;&quot;&quot;</span>
-        <span class="k">try</span><span class="p">:</span>
-            <span class="n">match_str</span> <span class="o">=</span> <span class="n">local_authority</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&quot; &quot;</span><span class="p">,</span> <span class="s2">&quot;&quot;</span><span class="p">)</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&quot;,&quot;</span><span class="p">,</span> <span class="s2">&quot;&quot;</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
-            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">lad_lookup</span><span class="p">[</span><span class="n">match_str</span><span class="p">]</span>
-        <span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
-            <span class="k">return</span> <span class="kc">None</span><span class="p">,</span> <span class="kc">None</span>
+        <span class="n">proj</span> <span class="o">=</span> <span class="n">pyproj</span><span class="o">.</span><span class="n">Transformer</span><span class="o">.</span><span class="n">from_crs</span><span class="p">(</span><span class="mi">4326</span><span class="p">,</span> <span class="mi">27700</span><span class="p">,</span> <span class="n">always_xy</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
+        <span class="n">eastings</span><span class="p">,</span> <span class="n">northings</span> <span class="o">=</span> <span class="n">proj</span><span class="o">.</span><span class="n">transform</span><span class="p">(</span><span class="n">lons</span><span class="p">,</span> <span class="n">lats</span><span class="p">)</span>
+        <span class="k">return</span> <span class="n">eastings</span><span class="p">,</span> <span class="n">northings</span>
 
     <span class="nd">@staticmethod</span>
     <span class="k">def</span> <span class="nf">_bng2latlon</span><span class="p">(</span><span class="n">eastings</span><span class="p">:</span> <span class="n">Iterable</span><span class="p">[</span><span class="n">Union</span><span class="p">[</span><span class="nb">float</span><span class="p">,</span> <span class="nb">int</span><span class="p">]],</span>
@@ -1236,21 +346,18 @@ <h1>Source code for geocode</h1><div class="highlight"><pre>
 <span class="w">        </span><span class="sd">&quot;&quot;&quot;</span>
 <span class="sd">        Convert Eastings and Northings (a.k.a British National Grid a.k.a OSGB 1936) to latitudes</span>
 <span class="sd">        and longitudes (WGS 1984).</span>
-
 <span class="sd">        Parameters</span>
 <span class="sd">        ----------</span>
 <span class="sd">        `eastings` : iterable of floats or ints</span>
 <span class="sd">            Easting co-ordinates.</span>
 <span class="sd">        `northings` : iterable of floats or ints</span>
 <span class="sd">            Northing co-ordinates.</span>
-
 <span class="sd">        Returns</span>
 <span class="sd">        -------</span>
 <span class="sd">        `lons` : list of floats</span>
 <span class="sd">            Corresponding longitude co-ordinates in WGS 1984 CRS.</span>
 <span class="sd">        `lats` : list of floats</span>
 <span class="sd">            Corresponding latitude co-ordinates in WGS 1984 CRS.</span>
-
 <span class="sd">        Notes</span>
 <span class="sd">        -----</span>
 <span class="sd">        Be careful! This method uses the same convention of ordering (eastings, northings) and</span>
@@ -1259,125 +366,22 @@ <h1>Source code for geocode</h1><div class="highlight"><pre>
 <span class="sd">        &quot;&quot;&quot;</span>
         <span class="n">proj</span> <span class="o">=</span> <span class="n">pyproj</span><span class="o">.</span><span class="n">Transformer</span><span class="o">.</span><span class="n">from_crs</span><span class="p">(</span><span class="mi">27700</span><span class="p">,</span> <span class="mi">4326</span><span class="p">,</span> <span class="n">always_xy</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
         <span class="n">lons</span><span class="p">,</span> <span class="n">lats</span> <span class="o">=</span> <span class="n">proj</span><span class="o">.</span><span class="n">transform</span><span class="p">(</span><span class="n">eastings</span><span class="p">,</span> <span class="n">northings</span><span class="p">)</span>
-        <span class="k">return</span> <span class="n">lons</span><span class="p">,</span> <span class="n">lats</span>
-
-    <span class="nd">@staticmethod</span>
-    <span class="k">def</span> <span class="nf">_latlon2bng</span><span class="p">(</span><span class="n">lons</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">float</span><span class="p">],</span> <span class="n">lats</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">float</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">Tuple</span><span class="p">[</span><span class="n">List</span><span class="p">[</span><span class="nb">float</span><span class="p">],</span> <span class="n">List</span><span class="p">[</span><span class="nb">float</span><span class="p">]]:</span>
-<span class="w">        </span><span class="sd">&quot;&quot;&quot;</span>
-<span class="sd">        Convert latitudes and longitudes (WGS 1984) to Eastings and Northings (a.k.a British</span>
-<span class="sd">        National Grid a.k.a OSGB 1936).</span>
-
-<span class="sd">        Parameters</span>
-<span class="sd">        ----------</span>
-<span class="sd">        `lons` : list of floats</span>
-<span class="sd">            Corresponding longitude co-ordinates in WGS 1984 CRS.</span>
-<span class="sd">        `lats` : list of floats</span>
-<span class="sd">            Corresponding latitude co-ordinates in WGS 1984 CRS.</span>
-
-<span class="sd">        Returns</span>
-<span class="sd">        -------</span>
-<span class="sd">        `eastings` : list of floats or ints</span>
-<span class="sd">            Easting co-ordinates.</span>
-<span class="sd">        `northings` : list of floats or ints</span>
-<span class="sd">            Northing co-ordinates.</span>
-
-<span class="sd">        Notes</span>
-<span class="sd">        -----</span>
-<span class="sd">        Be careful! This method uses the same convention of ordering (eastings, northings) and</span>
-<span class="sd">        (lons, lats) as pyproj i.e. (x, y). Elsewhere in this module the convention is typically</span>
-<span class="sd">        (lats, lons) due to personal preference.</span>
-<span class="sd">        &quot;&quot;&quot;</span>
-        <span class="n">proj</span> <span class="o">=</span> <span class="n">pyproj</span><span class="o">.</span><span class="n">Transformer</span><span class="o">.</span><span class="n">from_crs</span><span class="p">(</span><span class="mi">4326</span><span class="p">,</span> <span class="mi">27700</span><span class="p">,</span> <span class="n">always_xy</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
-        <span class="n">eastings</span><span class="p">,</span> <span class="n">northings</span> <span class="o">=</span> <span class="n">proj</span><span class="o">.</span><span class="n">transform</span><span class="p">(</span><span class="n">lons</span><span class="p">,</span> <span class="n">lats</span><span class="p">)</span>
-        <span class="k">return</span> <span class="n">eastings</span><span class="p">,</span> <span class="n">northings</span></div>
-
-<span class="k">def</span> <span class="nf">print_progress</span><span class="p">(</span><span class="n">iteration</span><span class="p">,</span> <span class="n">total</span><span class="p">,</span> <span class="n">prefix</span><span class="o">=</span><span class="s2">&quot;&quot;</span><span class="p">,</span> <span class="n">suffix</span><span class="o">=</span><span class="s2">&quot;&quot;</span><span class="p">,</span> <span class="n">decimals</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="n">bar_length</span><span class="o">=</span><span class="mi">100</span><span class="p">):</span>
-<span class="w">    </span><span class="sd">&quot;&quot;&quot;</span>
-<span class="sd">    Call in a loop to create terminal progress bar.</span>
-
-<span class="sd">    Parameters</span>
-<span class="sd">    ----------</span>
-<span class="sd">    `iteration` : int</span>
-<span class="sd">        current iteration (required)</span>
-<span class="sd">    `total` : int</span>
-<span class="sd">        total iterations (required)</span>
-<span class="sd">    `prefix` : string</span>
-<span class="sd">        prefix string (optional)</span>
-<span class="sd">    `suffix` : string</span>
-<span class="sd">        suffix string (optional)</span>
-<span class="sd">    `decimals` : int</span>
-<span class="sd">        number of decimals in percent complete (optional)</span>
-<span class="sd">    `bar_length` : int</span>
-<span class="sd">        character length of bar (optional)</span>
-
-<span class="sd">    Notes</span>
-<span class="sd">    -----</span>
-<span class="sd">    Taken from `Stack Overflow &lt;http://stackoverflow.com/a/34325723&gt;`_.</span>
-<span class="sd">    &quot;&quot;&quot;</span>
-    <span class="n">filled_length</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="nb">round</span><span class="p">(</span><span class="n">bar_length</span> <span class="o">*</span> <span class="n">iteration</span> <span class="o">/</span> <span class="nb">float</span><span class="p">(</span><span class="n">total</span><span class="p">)))</span>
-    <span class="n">percents</span> <span class="o">=</span> <span class="nb">round</span><span class="p">(</span><span class="mf">100.00</span> <span class="o">*</span> <span class="p">(</span><span class="n">iteration</span> <span class="o">/</span> <span class="nb">float</span><span class="p">(</span><span class="n">total</span><span class="p">)),</span> <span class="n">decimals</span><span class="p">)</span>
-    <span class="n">progress_bar</span> <span class="o">=</span> <span class="s2">&quot;#&quot;</span> <span class="o">*</span> <span class="n">filled_length</span> <span class="o">+</span> <span class="s2">&quot;-&quot;</span> <span class="o">*</span> <span class="p">(</span><span class="n">bar_length</span> <span class="o">-</span> <span class="n">filled_length</span><span class="p">)</span>
-    <span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="s2">&quot;</span><span class="se">\r</span><span class="si">%s</span><span class="s2"> |</span><span class="si">%s</span><span class="s2">| </span><span class="si">%s%s</span><span class="s2"> </span><span class="si">%s</span><span class="s2">&quot;</span> <span class="o">%</span> <span class="p">(</span><span class="n">prefix</span><span class="p">,</span> <span class="n">progress_bar</span><span class="p">,</span> <span class="n">percents</span><span class="p">,</span> <span class="s2">&quot;%&quot;</span><span class="p">,</span> <span class="n">suffix</span><span class="p">))</span>
-    <span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">flush</span><span class="p">()</span>
-    <span class="k">if</span> <span class="n">iteration</span> <span class="o">==</span> <span class="n">total</span><span class="p">:</span>
-        <span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="s2">&quot;</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">)</span>
-        <span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">flush</span><span class="p">()</span>
-
-<span class="k">def</span> <span class="nf">query_yes_no</span><span class="p">(</span><span class="n">question</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="s2">&quot;yes&quot;</span><span class="p">):</span>
-<span class="w">    </span><span class="sd">&quot;&quot;&quot;</span>
-<span class="sd">    Ask a yes/no question via input() and return the answer as boolean.</span>
-
-<span class="sd">    Parameters</span>
-<span class="sd">    ----------</span>
-<span class="sd">    `question` : string</span>
-<span class="sd">        The question presented to the user.</span>
-<span class="sd">    `default` : string</span>
-<span class="sd">        The presumed answer if the user just hits &lt;Enter&gt;. It must be &quot;yes&quot; (the default), &quot;no&quot; or</span>
-<span class="sd">        None (meaning an answer is required of the user).</span>
-
-<span class="sd">    Returns</span>
-<span class="sd">    -------</span>
-<span class="sd">    boolean</span>
-<span class="sd">        Return value is True for &quot;yes&quot; or False for &quot;no&quot;.</span>
-
-<span class="sd">    Notes</span>
-<span class="sd">    -----</span>
-<span class="sd">    See http://stackoverflow.com/a/3041990</span>
-<span class="sd">    &quot;&quot;&quot;</span>
-    <span class="n">valid</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;yes&quot;</span><span class="p">:</span> <span class="kc">True</span><span class="p">,</span> <span class="s2">&quot;y&quot;</span><span class="p">:</span> <span class="kc">True</span><span class="p">,</span> <span class="s2">&quot;ye&quot;</span><span class="p">:</span> <span class="kc">True</span><span class="p">,</span>
-             <span class="s2">&quot;no&quot;</span><span class="p">:</span> <span class="kc">False</span><span class="p">,</span> <span class="s2">&quot;n&quot;</span><span class="p">:</span> <span class="kc">False</span><span class="p">}</span>
-    <span class="k">if</span> <span class="n">default</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
-        <span class="n">prompt</span> <span class="o">=</span> <span class="s2">&quot; [y/n] &quot;</span>
-    <span class="k">elif</span> <span class="n">default</span> <span class="o">==</span> <span class="s2">&quot;yes&quot;</span><span class="p">:</span>
-        <span class="n">prompt</span> <span class="o">=</span> <span class="s2">&quot; [Y/n] &quot;</span>
-    <span class="k">elif</span> <span class="n">default</span> <span class="o">==</span> <span class="s2">&quot;no&quot;</span><span class="p">:</span>
-        <span class="n">prompt</span> <span class="o">=</span> <span class="s2">&quot; [y/N] &quot;</span>
-    <span class="k">else</span><span class="p">:</span>
-        <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&quot;invalid default answer: &#39;</span><span class="si">%s</span><span class="s2">&#39;&quot;</span> <span class="o">%</span> <span class="n">default</span><span class="p">)</span>
-    <span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
-        <span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">question</span> <span class="o">+</span> <span class="n">prompt</span><span class="p">)</span>
-        <span class="n">choice</span> <span class="o">=</span> <span class="nb">input</span><span class="p">()</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
-        <span class="k">if</span> <span class="n">default</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">choice</span> <span class="o">==</span> <span class="s1">&#39;&#39;</span><span class="p">:</span>
-            <span class="k">return</span> <span class="n">valid</span><span class="p">[</span><span class="n">default</span><span class="p">]</span>
-        <span class="k">if</span> <span class="n">choice</span> <span class="ow">in</span> <span class="n">valid</span><span class="p">:</span>
-            <span class="k">return</span> <span class="n">valid</span><span class="p">[</span><span class="n">choice</span><span class="p">]</span>
-        <span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="s2">&quot;Please respond with &#39;yes&#39; or &#39;no&#39; (or &#39;y&#39; or &#39;n&#39;).</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">)</span>
+        <span class="k">return</span> <span class="n">lons</span><span class="p">,</span> <span class="n">lats</span></div>
 
 <span class="k">def</span> <span class="nf">parse_options</span><span class="p">():</span>
 <span class="w">    </span><span class="sd">&quot;&quot;&quot;Parse command line options.&quot;&quot;&quot;</span>
     <span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="p">(</span><span class="s2">&quot;This is a command line interface (CLI) for &quot;</span>
                                                   <span class="sa">f</span><span class="s2">&quot;the Geocode module version </span><span class="si">{</span><span class="n">__version__</span><span class="si">}</span><span class="s2">.&quot;</span><span class="p">),</span>
-                                     <span class="n">epilog</span><span class="o">=</span><span class="s2">&quot;Jamie Taylor, 2019-10-08&quot;</span><span class="p">)</span>
+                                     <span class="n">epilog</span><span class="o">=</span><span class="s2">&quot;Jamie Taylor &amp; Ethan Jones, 2019-10-08&quot;</span><span class="p">)</span>
     <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s2">&quot;--clear-cache&quot;</span><span class="p">,</span> <span class="n">dest</span><span class="o">=</span><span class="s2">&quot;clear_cache&quot;</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="s2">&quot;store_true&quot;</span><span class="p">,</span>
                         <span class="n">required</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s2">&quot;Specify to delete the cache files.&quot;</span><span class="p">)</span>
     <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s2">&quot;--debug&quot;</span><span class="p">,</span> <span class="n">dest</span><span class="o">=</span><span class="s2">&quot;debug&quot;</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="s2">&quot;store_true&quot;</span><span class="p">,</span>
                         <span class="n">required</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s2">&quot;Geocode some sample postcodes/addresses/LLSOAs.&quot;</span><span class="p">)</span>
-    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s2">&quot;--setup&quot;</span><span class="p">,</span> <span class="n">dest</span><span class="o">=</span><span class="s2">&quot;setup&quot;</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="s2">&quot;store_true&quot;</span><span class="p">,</span>
+    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s2">&quot;--setup&quot;</span><span class="p">,</span> <span class="n">dest</span><span class="o">=</span><span class="s2">&quot;setup&quot;</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="s2">&quot;store&quot;</span><span class="p">,</span> <span class="n">nargs</span><span class="o">=</span><span class="s2">&quot;+&quot;</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
                         <span class="n">required</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s2">&quot;Force download all datasets to local cache (useful &quot;</span>
                                              <span class="s2">&quot;if running inside a Docker container i.e. run this &quot;</span>
-                                             <span class="s2">&quot;as part of image build).&quot;</span><span class="p">)</span>
-    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s2">&quot;--setup-eso&quot;</span><span class="p">,</span> <span class="n">dest</span><span class="o">=</span><span class="s2">&quot;setup_eso&quot;</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="s2">&quot;store_true&quot;</span><span class="p">,</span>
-                        <span class="n">required</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s2">&quot;Force download all ESO datasets to local cache.&quot;</span><span class="p">)</span>
+                                             <span class="s2">&quot;as part of image build). Possible values are &quot;</span>
+                                             <span class="s2">&quot;&#39;ngeso&#39;, &#39;cpo&#39;, &#39;ons&#39; or &#39;all&#39;.&quot;</span><span class="p">)</span>
     <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s2">&quot;--load-cpo-zip&quot;</span><span class="p">,</span> <span class="n">dest</span><span class="o">=</span><span class="s2">&quot;cpo_zip&quot;</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="s2">&quot;store&quot;</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">str</span><span class="p">,</span>
                         <span class="n">required</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">metavar</span><span class="o">=</span><span class="s2">&quot;&lt;/path/to/zip-file&gt;&quot;</span><span class="p">,</span>
                         <span class="n">help</span><span class="o">=</span><span class="s2">&quot;Load the Code Point Open data from a local zip file.&quot;</span><span class="p">)</span>
@@ -1385,113 +389,97 @@ <h1>Source code for geocode</h1><div class="highlight"><pre>
                         <span class="n">required</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">metavar</span><span class="o">=</span><span class="s2">&quot;&lt;gmaps-api-key&gt;&quot;</span><span class="p">,</span>
                         <span class="n">help</span><span class="o">=</span><span class="s2">&quot;Load a Google Maps API key.&quot;</span><span class="p">)</span>
     <span class="n">options</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
-    <span class="k">return</span> <span class="n">options</span>
+    <span class="k">def</span> <span class="nf">handle_options</span><span class="p">(</span><span class="n">options</span><span class="p">):</span>
+        <span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">setup</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
+            <span class="n">valid_options</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&quot;ngeso&quot;</span><span class="p">,</span> <span class="s2">&quot;cpo&quot;</span><span class="p">,</span> <span class="s2">&quot;ons&quot;</span><span class="p">,</span> <span class="s2">&quot;all&quot;</span><span class="p">]</span>
+            <span class="n">options</span><span class="o">.</span><span class="n">setup</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="nb">map</span><span class="p">(</span><span class="nb">str</span><span class="o">.</span><span class="n">lower</span><span class="p">,</span> <span class="n">options</span><span class="o">.</span><span class="n">setup</span><span class="p">))</span>
+            <span class="k">if</span> <span class="nb">any</span><span class="p">(</span><span class="n">s</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">valid_options</span> <span class="k">for</span> <span class="n">s</span> <span class="ow">in</span> <span class="n">options</span><span class="o">.</span><span class="n">setup</span><span class="p">):</span>
+                <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;Invalid value for `--setup` - valid values are </span><span class="si">{</span><span class="n">valid_options</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>
+        <span class="k">return</span> <span class="n">options</span>
+    <span class="k">return</span> <span class="n">handle_options</span><span class="p">(</span><span class="n">options</span><span class="p">)</span>
 
 <span class="k">def</span> <span class="nf">debug</span><span class="p">():</span>
 <span class="w">    </span><span class="sd">&quot;&quot;&quot;Useful for debugging code (runs each public method in turn with sample inputs).&quot;&quot;&quot;</span>
     <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;Running some example code (`--debug`)&quot;</span><span class="p">)</span>
-    <span class="c1">### LLSOA -&gt; latlon ###</span>
     <span class="n">timerstart</span> <span class="o">=</span> <span class="n">TIME</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
     <span class="n">sample_llsoas</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&quot;E01025397&quot;</span><span class="p">,</span> <span class="s2">&quot;E01003065&quot;</span><span class="p">,</span> <span class="s2">&quot;E01017548&quot;</span><span class="p">,</span> <span class="s2">&quot;E01023301&quot;</span><span class="p">,</span> <span class="s2">&quot;E01021142&quot;</span><span class="p">,</span> <span class="s2">&quot;E01019037&quot;</span><span class="p">,</span>
                      <span class="s2">&quot;E01013873&quot;</span><span class="p">,</span> <span class="s2">&quot;S00092417&quot;</span><span class="p">,</span> <span class="s2">&quot;S01012390&quot;</span><span class="p">]</span>
     <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;Geocoding some LSOAs&quot;</span><span class="p">)</span>
-    <span class="k">with</span> <span class="n">Geocoder</span><span class="p">(</span><span class="n">progress_bar</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span> <span class="k">as</span> <span class="n">geocoder</span><span class="p">:</span>
-        <span class="n">results</span> <span class="o">=</span> <span class="n">geocoder</span><span class="o">.</span><span class="n">geocode_llsoa</span><span class="p">(</span><span class="n">sample_llsoas</span><span class="p">)</span>
+    <span class="k">with</span> <span class="n">Geocoder</span><span class="p">()</span> <span class="k">as</span> <span class="n">geocoder</span><span class="p">:</span>
+        <span class="n">results</span> <span class="o">=</span> <span class="n">geocoder</span><span class="o">.</span><span class="n">geocode</span><span class="p">(</span><span class="n">entity</span><span class="o">=</span><span class="s2">&quot;llsoa&quot;</span><span class="p">,</span> <span class="n">entity_ids</span><span class="o">=</span><span class="n">sample_llsoas</span><span class="p">)</span>
     <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;Time taken: </span><span class="si">{:.1f}</span><span class="s2"> seconds&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">TIME</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">timerstart</span><span class="p">))</span>
     <span class="k">for</span> <span class="n">llsoa</span><span class="p">,</span> <span class="p">(</span><span class="n">lat</span><span class="p">,</span> <span class="n">lon</span><span class="p">)</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">sample_llsoas</span><span class="p">,</span> <span class="n">results</span><span class="p">):</span>
         <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;</span><span class="si">%s</span><span class="s2"> :    </span><span class="si">%s</span><span class="s2">, </span><span class="si">%s</span><span class="s2">&quot;</span><span class="p">,</span> <span class="n">llsoa</span><span class="p">,</span> <span class="n">lat</span><span class="p">,</span> <span class="n">lon</span><span class="p">)</span>
-    <span class="c1">### latlon -&gt; LLSOA ###</span>
     <span class="n">sample_latlons</span> <span class="o">=</span> <span class="p">[</span>
         <span class="p">(</span><span class="mf">53.705</span><span class="p">,</span> <span class="o">-</span><span class="mf">2.328</span><span class="p">),</span> <span class="p">(</span><span class="mf">51.430</span><span class="p">,</span> <span class="o">-</span><span class="mf">0.093</span><span class="p">),</span> <span class="p">(</span><span class="mf">52.088</span><span class="p">,</span> <span class="o">-</span><span class="mf">0.457</span><span class="p">),</span> <span class="p">(</span><span class="mf">51.706</span><span class="p">,</span> <span class="o">-</span><span class="mf">0.036</span><span class="p">),</span> <span class="p">(</span><span class="mf">50.882</span><span class="p">,</span> <span class="mf">0.169</span><span class="p">),</span>
         <span class="p">(</span><span class="mf">50.409</span><span class="p">,</span> <span class="o">-</span><span class="mf">4.672</span><span class="p">),</span> <span class="p">(</span><span class="mf">52.940</span><span class="p">,</span> <span class="o">-</span><span class="mf">1.146</span><span class="p">),</span> <span class="p">(</span><span class="mf">57.060</span><span class="p">,</span> <span class="o">-</span><span class="mf">2.874</span><span class="p">),</span> <span class="p">(</span><span class="mf">56.31</span><span class="p">,</span> <span class="o">-</span><span class="mf">4.</span><span class="p">)</span>
     <span class="p">]</span>
     <span class="n">timerstart</span> <span class="o">=</span> <span class="n">TIME</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
     <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;Reverse geocoding some latlons to LSOAs&quot;</span><span class="p">)</span>
-    <span class="k">with</span> <span class="n">Geocoder</span><span class="p">(</span><span class="n">progress_bar</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span> <span class="k">as</span> <span class="n">geocoder</span><span class="p">:</span>
-        <span class="n">results</span> <span class="o">=</span> <span class="n">geocoder</span><span class="o">.</span><span class="n">reverse_geocode_llsoa</span><span class="p">(</span><span class="n">sample_latlons</span><span class="p">,</span> <span class="n">datazones</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
+    <span class="k">with</span> <span class="n">Geocoder</span><span class="p">()</span> <span class="k">as</span> <span class="n">geocoder</span><span class="p">:</span>
+        <span class="n">results</span> <span class="o">=</span> <span class="n">geocoder</span><span class="o">.</span><span class="n">reverse_geocode</span><span class="p">(</span><span class="n">latlons</span><span class="o">=</span><span class="n">sample_latlons</span><span class="p">,</span> <span class="n">entity</span><span class="o">=</span><span class="s2">&quot;llsoa&quot;</span><span class="p">,</span> <span class="n">datazones</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
     <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;Time taken: </span><span class="si">%s</span><span class="s2"> seconds&quot;</span><span class="p">,</span> <span class="nb">round</span><span class="p">(</span><span class="n">TIME</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">timerstart</span><span class="p">,</span> <span class="mi">1</span><span class="p">))</span>
     <span class="k">for</span> <span class="p">(</span><span class="n">lat</span><span class="p">,</span> <span class="n">lon</span><span class="p">),</span> <span class="n">llsoa</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">sample_latlons</span><span class="p">,</span> <span class="n">results</span><span class="p">):</span>
         <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;</span><span class="si">%s</span><span class="s2">, </span><span class="si">%s</span><span class="s2"> :    </span><span class="si">%s</span><span class="s2">&quot;</span><span class="p">,</span> <span class="n">lat</span><span class="p">,</span> <span class="n">lon</span><span class="p">,</span> <span class="n">llsoa</span><span class="p">)</span>
-    <span class="c1">### latlon -&gt; GSP ###</span>
     <span class="n">sample_file</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">SCRIPT_DIR</span><span class="p">,</span> <span class="s2">&quot;sample_latlons.txt&quot;</span><span class="p">)</span>
     <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">sample_file</span><span class="p">)</span> <span class="k">as</span> <span class="n">fid</span><span class="p">:</span>
         <span class="n">sample_latlons</span> <span class="o">=</span> <span class="p">[</span><span class="nb">tuple</span><span class="p">(</span><span class="nb">map</span><span class="p">(</span><span class="nb">float</span><span class="p">,</span> <span class="n">line</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&quot;,&quot;</span><span class="p">)))</span>
                           <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">fid</span> <span class="k">if</span> <span class="n">line</span><span class="o">.</span><span class="n">strip</span><span class="p">()][:</span><span class="mi">10</span><span class="p">]</span>
     <span class="n">timerstart</span> <span class="o">=</span> <span class="n">TIME</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
     <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;Reverse geocoding some latlons to GSPs&quot;</span><span class="p">)</span>
-    <span class="k">with</span> <span class="n">Geocoder</span><span class="p">(</span><span class="n">progress_bar</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span> <span class="k">as</span> <span class="n">geocoder</span><span class="p">:</span>
-        <span class="n">results</span> <span class="o">=</span> <span class="n">geocoder</span><span class="o">.</span><span class="n">reverse_geocode_gsp</span><span class="p">(</span><span class="n">sample_latlons</span><span class="p">)</span>
+    <span class="k">with</span> <span class="n">Geocoder</span><span class="p">()</span> <span class="k">as</span> <span class="n">geocoder</span><span class="p">:</span>
+        <span class="n">results</span> <span class="o">=</span> <span class="n">geocoder</span><span class="o">.</span><span class="n">reverse_geocode</span><span class="p">(</span><span class="n">latlons</span><span class="o">=</span><span class="n">sample_latlons</span><span class="p">,</span> <span class="n">entity</span><span class="o">=</span><span class="s2">&quot;gsp&quot;</span><span class="p">)</span>
     <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;Time taken: </span><span class="si">%s</span><span class="s2"> seconds&quot;</span><span class="p">,</span> <span class="nb">round</span><span class="p">(</span><span class="n">TIME</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">timerstart</span><span class="p">,</span> <span class="mi">1</span><span class="p">))</span>
     <span class="k">for</span> <span class="p">(</span><span class="n">lat</span><span class="p">,</span> <span class="n">lon</span><span class="p">),</span> <span class="n">region_id</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">sample_latlons</span><span class="p">,</span> <span class="n">results</span><span class="p">):</span>
         <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;</span><span class="si">%s</span><span class="s2">, </span><span class="si">%s</span><span class="s2"> :    </span><span class="si">%s</span><span class="s2">&quot;</span><span class="p">,</span> <span class="n">lat</span><span class="p">,</span> <span class="n">lon</span><span class="p">,</span> <span class="n">region_id</span><span class="p">)</span>
-    <span class="c1">### constituency -&gt; latlon ###</span>
     <span class="n">sample_constituencies</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&quot;Berwickshire Roxburgh and Selkirk&quot;</span><span class="p">,</span> <span class="s2">&quot;Argyll and Bute&quot;</span><span class="p">,</span>
-                             <span class="s2">&quot;Inverness Nairn Badenoch and Strathspey&quot;</span><span class="p">,</span> <span class="c1"># missing commas :(</span>
+                             <span class="s2">&quot;Inverness Nairn Badenoch and Strathspey&quot;</span><span class="p">,</span>
                              <span class="s2">&quot;Dumfries and Galloway&quot;</span><span class="p">]</span>
     <span class="n">timerstart</span> <span class="o">=</span> <span class="n">TIME</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
     <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;Geocoding some constituencies&quot;</span><span class="p">)</span>
-    <span class="k">with</span> <span class="n">Geocoder</span><span class="p">(</span><span class="n">progress_bar</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span> <span class="k">as</span> <span class="n">geocoder</span><span class="p">:</span>
-        <span class="n">results</span> <span class="o">=</span> <span class="n">geocoder</span><span class="o">.</span><span class="n">geocode_constituency</span><span class="p">(</span><span class="n">sample_constituencies</span><span class="p">)</span>
+    <span class="k">with</span> <span class="n">Geocoder</span><span class="p">()</span> <span class="k">as</span> <span class="n">geocoder</span><span class="p">:</span>
+        <span class="n">results</span> <span class="o">=</span> <span class="n">geocoder</span><span class="o">.</span><span class="n">geocode</span><span class="p">(</span><span class="n">entity</span><span class="o">=</span><span class="s2">&quot;constituency&quot;</span><span class="p">,</span> <span class="n">entity_ids</span><span class="o">=</span><span class="n">sample_constituencies</span><span class="p">)</span>
     <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;Time taken: </span><span class="si">%s</span><span class="s2"> seconds&quot;</span><span class="p">,</span> <span class="nb">round</span><span class="p">(</span><span class="n">TIME</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">timerstart</span><span class="p">,</span> <span class="mi">1</span><span class="p">))</span>
     <span class="k">for</span> <span class="n">constituency</span><span class="p">,</span> <span class="p">(</span><span class="n">lat</span><span class="p">,</span> <span class="n">lon</span><span class="p">)</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">sample_constituencies</span><span class="p">,</span> <span class="n">results</span><span class="p">):</span>
         <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;</span><span class="si">%s</span><span class="s2"> :    </span><span class="si">%s</span><span class="s2">, </span><span class="si">%s</span><span class="s2">&quot;</span><span class="p">,</span> <span class="n">constituency</span><span class="p">,</span> <span class="n">lat</span><span class="p">,</span> <span class="n">lon</span><span class="p">)</span>
-    <span class="c1">### Local Authority -&gt; latlon ###</span>
-    <span class="n">sample_lads</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&quot;Hartlepool&quot;</span><span class="p">,</span> <span class="s2">&quot;Middlesbrough&quot;</span><span class="p">,</span> <span class="s2">&quot;Redcar and Cleveland&quot;</span><span class="p">]</span>
-    <span class="n">timerstart</span> <span class="o">=</span> <span class="n">TIME</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
-    <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;Geocoding some Local Authorities&quot;</span><span class="p">)</span>
-    <span class="k">with</span> <span class="n">Geocoder</span><span class="p">(</span><span class="n">progress_bar</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span> <span class="k">as</span> <span class="n">geocoder</span><span class="p">:</span>
-        <span class="n">results</span> <span class="o">=</span> <span class="n">geocoder</span><span class="o">.</span><span class="n">geocode_local_authority</span><span class="p">(</span><span class="n">sample_lads</span><span class="p">)</span>
-    <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;Time taken: </span><span class="si">%s</span><span class="s2"> seconds&quot;</span><span class="p">,</span> <span class="nb">round</span><span class="p">(</span><span class="n">TIME</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">timerstart</span><span class="p">,</span> <span class="mi">1</span><span class="p">))</span>
-    <span class="k">for</span> <span class="n">lad</span><span class="p">,</span> <span class="p">(</span><span class="n">lat</span><span class="p">,</span> <span class="n">lon</span><span class="p">)</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">sample_lads</span><span class="p">,</span> <span class="n">results</span><span class="p">):</span>
-        <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;</span><span class="si">%s</span><span class="s2"> :    </span><span class="si">%s</span><span class="s2">, </span><span class="si">%s</span><span class="s2">&quot;</span><span class="p">,</span> <span class="n">lad</span><span class="p">,</span> <span class="n">lat</span><span class="p">,</span> <span class="n">lon</span><span class="p">)</span>
-    <span class="c1">### postcode/address -&gt; latlon ###</span>
     <span class="n">sample_file</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">SCRIPT_DIR</span><span class="p">,</span> <span class="s2">&quot;sample_postcodes.txt&quot;</span><span class="p">)</span>
     <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">sample_file</span><span class="p">)</span> <span class="k">as</span> <span class="n">fid</span><span class="p">:</span>
         <span class="n">postcodes</span> <span class="o">=</span> <span class="p">[</span><span class="n">line</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">fid</span> <span class="k">if</span> <span class="n">line</span><span class="o">.</span><span class="n">strip</span><span class="p">()][:</span><span class="mi">10</span><span class="p">]</span>
     <span class="n">timerstart</span> <span class="o">=</span> <span class="n">TIME</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
     <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;Geocoding some postcodes&quot;</span><span class="p">)</span>
-    <span class="k">with</span> <span class="n">Geocoder</span><span class="p">(</span><span class="n">progress_bar</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span> <span class="k">as</span> <span class="n">geocoder</span><span class="p">:</span>
-        <span class="n">results</span> <span class="o">=</span> <span class="n">geocoder</span><span class="o">.</span><span class="n">geocode</span><span class="p">(</span><span class="n">postcodes</span><span class="o">=</span><span class="n">postcodes</span><span class="p">)</span>
+    <span class="k">with</span> <span class="n">Geocoder</span><span class="p">()</span> <span class="k">as</span> <span class="n">geocoder</span><span class="p">:</span>
+        <span class="n">results</span> <span class="o">=</span> <span class="n">geocoder</span><span class="o">.</span><span class="n">geocode</span><span class="p">(</span><span class="n">entity</span><span class="o">=</span><span class="s2">&quot;postcode&quot;</span><span class="p">,</span> <span class="n">entity_ids</span><span class="o">=</span><span class="n">postcodes</span><span class="p">)</span>
     <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;Time taken: </span><span class="si">%s</span><span class="s2"> seconds&quot;</span><span class="p">,</span> <span class="nb">round</span><span class="p">(</span><span class="n">TIME</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">timerstart</span><span class="p">,</span> <span class="mi">1</span><span class="p">))</span>
     <span class="k">for</span> <span class="n">postcode</span><span class="p">,</span> <span class="p">(</span><span class="n">lat</span><span class="p">,</span> <span class="n">lon</span><span class="p">,</span> <span class="n">status</span><span class="p">)</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">postcodes</span><span class="p">,</span> <span class="n">results</span><span class="p">):</span>
         <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;</span><span class="si">%s</span><span class="s2"> :    </span><span class="si">%s</span><span class="s2">, </span><span class="si">%s</span><span class="s2">    -&gt; </span><span class="si">%s</span><span class="s2">&quot;</span><span class="p">,</span> <span class="n">postcode</span><span class="p">,</span> <span class="n">lat</span><span class="p">,</span> <span class="n">lon</span><span class="p">,</span> <span class="n">geocoder</span><span class="o">.</span><span class="n">status_codes</span><span class="p">[</span><span class="n">status</span><span class="p">])</span>
-    <span class="c1">### postcode -&gt; LLSOA ###</span>
-    <span class="n">postcodes</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">DataFrame</span><span class="p">({</span><span class="s2">&quot;input_postcode&quot;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&quot;S6 6RP&quot;</span><span class="p">,</span> <span class="s2">&quot;DE22 2SZ&quot;</span><span class="p">,</span> <span class="s2">&quot;S7 2QZ&quot;</span><span class="p">]})</span>
-    <span class="n">timerstart</span> <span class="o">=</span> <span class="n">TIME</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
-    <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;Reverse geocoding some postcodes to LLSOA&quot;</span><span class="p">)</span>
-    <span class="k">with</span> <span class="n">Geocoder</span><span class="p">(</span><span class="n">progress_bar</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span> <span class="k">as</span> <span class="n">geocoder</span><span class="p">:</span>
-        <span class="n">results</span> <span class="o">=</span> <span class="n">geocoder</span><span class="o">.</span><span class="n">postcode2llsoa</span><span class="p">(</span><span class="n">pcs</span><span class="o">=</span><span class="n">postcodes</span><span class="p">)</span>
-    <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;Time taken: </span><span class="si">%s</span><span class="s2"> seconds&quot;</span><span class="p">,</span> <span class="nb">round</span><span class="p">(</span><span class="n">TIME</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">timerstart</span><span class="p">,</span> <span class="mi">1</span><span class="p">))</span>
-    <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;</span><span class="se">\n</span><span class="si">%s</span><span class="s2">&quot;</span><span class="p">,</span> <span class="n">results</span><span class="p">)</span>
 
 <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
 <span class="w">    </span><span class="sd">&quot;&quot;&quot;Run the Command Line Interface.&quot;&quot;&quot;</span>
     <span class="n">options</span> <span class="o">=</span> <span class="n">parse_options</span><span class="p">()</span>
     <span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">clear_cache</span><span class="p">:</span>
         <span class="n">geocoder</span> <span class="o">=</span> <span class="n">Geocoder</span><span class="p">()</span>
-        <span class="n">geocoder</span><span class="o">.</span><span class="n">clear_cache</span><span class="p">()</span>
+        <span class="n">geocoder</span><span class="o">.</span><span class="n">cache_manager</span><span class="o">.</span><span class="n">clear</span><span class="p">()</span>
     <span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">cpo_zip</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
         <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;Updating Code Point Open data&quot;</span><span class="p">)</span>
         <span class="k">with</span> <span class="n">Geocoder</span><span class="p">()</span> <span class="k">as</span> <span class="n">geocoder</span><span class="p">:</span>
-            <span class="n">copyfile</span><span class="p">(</span><span class="n">options</span><span class="o">.</span><span class="n">cpo_zip</span><span class="p">,</span> <span class="n">geocoder</span><span class="o">.</span><span class="n">cpo_zipfile</span><span class="p">)</span>
-            <span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Copied file &#39;</span><span class="si">%s</span><span class="s2">&#39; to &#39;</span><span class="si">%s</span><span class="s2">&#39;&quot;</span><span class="p">,</span> <span class="n">options</span><span class="o">.</span><span class="n">cpo_zip</span><span class="p">,</span> <span class="n">geocoder</span><span class="o">.</span><span class="n">cpo_zipfile</span><span class="p">)</span>
-            <span class="n">geocoder</span><span class="o">.</span><span class="n">_load_code_point_open</span><span class="p">(</span><span class="n">force_reload</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
+            <span class="n">copyfile</span><span class="p">(</span><span class="n">options</span><span class="o">.</span><span class="n">cpo_zip</span><span class="p">,</span> <span class="n">geocoder</span><span class="o">.</span><span class="n">cpo</span><span class="o">.</span><span class="n">cpo_zipfile</span><span class="p">)</span>
+            <span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Copied file &#39;</span><span class="si">%s</span><span class="s2">&#39; to &#39;</span><span class="si">%s</span><span class="s2">&#39;&quot;</span><span class="p">,</span> <span class="n">options</span><span class="o">.</span><span class="n">cpo_zip</span><span class="p">,</span> <span class="n">geocoder</span><span class="o">.</span><span class="n">cpo</span><span class="o">.</span><span class="n">cpo_zipfile</span><span class="p">)</span>
+            <span class="n">geocoder</span><span class="o">.</span><span class="n">cpo</span><span class="o">.</span><span class="n">_load</span><span class="p">(</span><span class="n">force_reload</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
         <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;Finished updating Code Point Open data&quot;</span><span class="p">)</span>
     <span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">gmaps_key</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
         <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;Loading GMaps key&quot;</span><span class="p">)</span>
         <span class="k">with</span> <span class="n">Geocoder</span><span class="p">()</span> <span class="k">as</span> <span class="n">geocoder</span><span class="p">:</span>
-            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">geocoder</span><span class="o">.</span><span class="n">gmaps_key_file</span><span class="p">,</span> <span class="s2">&quot;w&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">fid</span><span class="p">:</span>
+            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">geocoder</span><span class="o">.</span><span class="n">gmaps</span><span class="o">.</span><span class="n">gmaps_key_file</span><span class="p">,</span> <span class="s2">&quot;w&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">fid</span><span class="p">:</span>
                 <span class="n">fid</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">options</span><span class="o">.</span><span class="n">gmaps_key</span><span class="p">)</span>
-            <span class="k">if</span> <span class="n">geocoder</span><span class="o">.</span><span class="n">_load_gmaps_key</span><span class="p">()</span> <span class="o">==</span> <span class="n">options</span><span class="o">.</span><span class="n">gmaps_key</span><span class="p">:</span>
-                <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;GMaps key saved to &#39;</span><span class="si">%s</span><span class="s2">&#39;&quot;</span><span class="p">,</span> <span class="n">geocoder</span><span class="o">.</span><span class="n">gmaps_key_file</span><span class="p">)</span>
-    <span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">setup</span><span class="p">:</span>
+            <span class="k">if</span> <span class="n">geocoder</span><span class="o">.</span><span class="n">gmaps</span><span class="o">.</span><span class="n">_load_key</span><span class="p">()</span> <span class="o">==</span> <span class="n">options</span><span class="o">.</span><span class="n">gmaps_key</span><span class="p">:</span>
+                <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;GMaps key saved to &#39;</span><span class="si">%s</span><span class="s2">&#39;&quot;</span><span class="p">,</span> <span class="n">geocoder</span><span class="o">.</span><span class="n">gmaps</span><span class="o">.</span><span class="n">gmaps_key_file</span><span class="p">)</span>
+    <span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">setup</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
+        <span class="n">ngeso_setup</span> <span class="o">=</span>  <span class="s2">&quot;ngeso&quot;</span> <span class="ow">in</span> <span class="n">options</span><span class="o">.</span><span class="n">setup</span> <span class="ow">or</span> <span class="s2">&quot;all&quot;</span> <span class="ow">in</span> <span class="n">options</span><span class="o">.</span><span class="n">setup</span>
+        <span class="n">cpo_setup</span> <span class="o">=</span> <span class="s2">&quot;cpo&quot;</span> <span class="ow">in</span> <span class="n">options</span><span class="o">.</span><span class="n">setup</span> <span class="ow">or</span> <span class="s2">&quot;all&quot;</span> <span class="ow">in</span> <span class="n">options</span><span class="o">.</span><span class="n">setup</span>
+        <span class="n">ons_setup</span> <span class="o">=</span> <span class="s2">&quot;ons&quot;</span> <span class="ow">in</span> <span class="n">options</span><span class="o">.</span><span class="n">setup</span> <span class="ow">or</span> <span class="s2">&quot;all&quot;</span> <span class="ow">in</span> <span class="n">options</span><span class="o">.</span><span class="n">setup</span>
         <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;Running forced setup&quot;</span><span class="p">)</span>
         <span class="k">with</span> <span class="n">Geocoder</span><span class="p">()</span> <span class="k">as</span> <span class="n">geocoder</span><span class="p">:</span>
-            <span class="n">geocoder</span><span class="o">.</span><span class="n">force_setup</span><span class="p">()</span>
-    <span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">setup_eso</span><span class="p">:</span>
-        <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;Running forced setup of ESO data&quot;</span><span class="p">)</span>
-        <span class="k">with</span> <span class="n">Geocoder</span><span class="p">()</span> <span class="k">as</span> <span class="n">geocoder</span><span class="p">:</span>
-            <span class="n">geocoder</span><span class="o">.</span><span class="n">force_setup_eso</span><span class="p">()</span>
+            <span class="n">geocoder</span><span class="o">.</span><span class="n">force_setup</span><span class="p">(</span><span class="n">ngeso_setup</span><span class="o">=</span><span class="n">ngeso_setup</span><span class="p">,</span> <span class="n">cpo_setup</span><span class="o">=</span><span class="n">cpo_setup</span><span class="p">,</span> <span class="n">ons_setup</span><span class="o">=</span><span class="n">ons_setup</span><span class="p">)</span>
     <span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">debug</span><span class="p">:</span>
         <span class="n">debug</span><span class="p">()</span>
 
@@ -1499,7 +487,7 @@ <h1>Source code for geocode</h1><div class="highlight"><pre>
     <span class="n">DEFAULT_FMT</span> <span class="o">=</span> <span class="s2">&quot;</span><span class="si">%(asctime)s</span><span class="s2"> [</span><span class="si">%(levelname)s</span><span class="s2">] [</span><span class="si">%(filename)s</span><span class="s2">:</span><span class="si">%(funcName)s</span><span class="s2">] - </span><span class="si">%(message)s</span><span class="s2">&quot;</span>
     <span class="n">FMT</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;GEOCODE_LOGGING_FMT&quot;</span><span class="p">,</span> <span class="n">DEFAULT_FMT</span><span class="p">)</span>
     <span class="n">DATEFMT</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;GEOCODE_LOGGING_DATEFMT&quot;</span><span class="p">,</span> <span class="s2">&quot;%Y-%m-</span><span class="si">%d</span><span class="s2">T%H:%M:%SZ&quot;</span><span class="p">)</span>
-    <span class="n">logging</span><span class="o">.</span><span class="n">basicConfig</span><span class="p">(</span><span class="nb">format</span><span class="o">=</span><span class="n">FMT</span><span class="p">,</span> <span class="n">datefmt</span><span class="o">=</span><span class="n">DATEFMT</span><span class="p">,</span> <span class="n">level</span><span class="o">=</span><span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;LOGLEVEL&quot;</span><span class="p">,</span> <span class="s2">&quot;DEBUG&quot;</span><span class="p">))</span>
+    <span class="n">logging</span><span class="o">.</span><span class="n">basicConfig</span><span class="p">(</span><span class="nb">format</span><span class="o">=</span><span class="n">FMT</span><span class="p">,</span> <span class="n">datefmt</span><span class="o">=</span><span class="n">DATEFMT</span><span class="p">,</span> <span class="n">level</span><span class="o">=</span><span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;LOGLEVEL&quot;</span><span class="p">,</span> <span class="s2">&quot;INFO&quot;</span><span class="p">))</span>
     <span class="n">main</span><span class="p">()</span>
 </pre></div>
 
diff --git a/docs/build/html/_modules/index.html b/docs/build/html/_modules/index.html
index b454c1c..6d735b4 100644
--- a/docs/build/html/_modules/index.html
+++ b/docs/build/html/_modules/index.html
@@ -3,7 +3,7 @@
 <head>
   <meta charset="utf-8" />
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-  <title>Overview: module code &mdash; Geocode 0.10.5 documentation</title>
+  <title>Overview: module code &mdash; Geocode 0.11.0 documentation</title>
       <link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
       <link rel="stylesheet" href="../_static/css/theme.css" type="text/css" />
   <!--[if lt IE 9]>
diff --git a/docs/build/html/_static/documentation_options.js b/docs/build/html/_static/documentation_options.js
index 899519a..9dc22d6 100644
--- a/docs/build/html/_static/documentation_options.js
+++ b/docs/build/html/_static/documentation_options.js
@@ -1,6 +1,6 @@
 var DOCUMENTATION_OPTIONS = {
     URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
-    VERSION: '0.10.5',
+    VERSION: '0.11.0',
     LANGUAGE: 'en',
     COLLAPSE_INDEX: false,
     BUILDER: 'html',
diff --git a/docs/build/html/genindex.html b/docs/build/html/genindex.html
index 2c9dcb6..4a9a907 100644
--- a/docs/build/html/genindex.html
+++ b/docs/build/html/genindex.html
@@ -3,7 +3,7 @@
 <head>
   <meta charset="utf-8" />
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-  <title>Index &mdash; Geocode 0.10.5 documentation</title>
+  <title>Index &mdash; Geocode 0.11.0 documentation</title>
       <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
       <link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
   <!--[if lt IE 9]>
@@ -69,35 +69,15 @@
 <h1 id="index">Index</h1>
 
 <div class="genindex-jumpbox">
- <a href="#C"><strong>C</strong></a>
- | <a href="#F"><strong>F</strong></a>
+ <a href="#F"><strong>F</strong></a>
  | <a href="#G"><strong>G</strong></a>
- | <a href="#P"><strong>P</strong></a>
  | <a href="#R"><strong>R</strong></a>
  
 </div>
-<h2 id="C">C</h2>
-<table style="width: 100%" class="indextable genindextable"><tr>
-  <td style="width: 33%; vertical-align: top;"><ul>
-      <li><a href="modules.html#geocode.Geocoder.clear_cache">clear_cache() (geocode.Geocoder method)</a>
-</li>
-  </ul></td>
-  <td style="width: 33%; vertical-align: top;"><ul>
-      <li><a href="modules.html#geocode.Geocoder.cpo_geocode">cpo_geocode() (geocode.Geocoder method)</a>
-</li>
-      <li><a href="modules.html#geocode.Geocoder.cpo_geocode_one">cpo_geocode_one() (geocode.Geocoder method)</a>
-</li>
-  </ul></td>
-</tr></table>
-
 <h2 id="F">F</h2>
 <table style="width: 100%" class="indextable genindextable"><tr>
   <td style="width: 33%; vertical-align: top;"><ul>
       <li><a href="modules.html#geocode.Geocoder.force_setup">force_setup() (geocode.Geocoder method)</a>
-</li>
-  </ul></td>
-  <td style="width: 33%; vertical-align: top;"><ul>
-      <li><a href="modules.html#geocode.Geocoder.force_setup_eso">force_setup_eso() (geocode.Geocoder method)</a>
 </li>
   </ul></td>
 </tr></table>
@@ -115,21 +95,15 @@ <h2 id="G">G</h2>
 </li>
   </ul></td>
   <td style="width: 33%; vertical-align: top;"><ul>
-      <li><a href="modules.html#geocode.Geocoder.geocode_one">geocode_one() (geocode.Geocoder method)</a>
+      <li><a href="modules.html#geocode.Geocoder.geocode_postcode">geocode_postcode() (geocode.Geocoder method)</a>
 </li>
       <li><a href="modules.html#geocode.Geocoder">Geocoder (class in geocode)</a>
 </li>
       <li><a href="modules.html#geocode.Geocoder.get_dno_regions">get_dno_regions() (geocode.Geocoder method)</a>
 </li>
-      <li><a href="modules.html#geocode.Geocoder.gmaps_geocode_one">gmaps_geocode_one() (geocode.Geocoder method)</a>
+      <li><a href="modules.html#geocode.Geocoder.get_gsp_regions">get_gsp_regions() (geocode.Geocoder method)</a>
 </li>
-  </ul></td>
-</tr></table>
-
-<h2 id="P">P</h2>
-<table style="width: 100%" class="indextable genindextable"><tr>
-  <td style="width: 33%; vertical-align: top;"><ul>
-      <li><a href="modules.html#geocode.Geocoder.postcode2llsoa">postcode2llsoa() (geocode.Geocoder method)</a>
+      <li><a href="modules.html#geocode.Geocoder.get_llsoa_boundaries">get_llsoa_boundaries() (geocode.Geocoder method)</a>
 </li>
   </ul></td>
 </tr></table>
@@ -137,11 +111,11 @@ <h2 id="P">P</h2>
 <h2 id="R">R</h2>
 <table style="width: 100%" class="indextable genindextable"><tr>
   <td style="width: 33%; vertical-align: top;"><ul>
-      <li><a href="modules.html#geocode.Geocoder.reverse_geocode_gsp">reverse_geocode_gsp() (geocode.Geocoder method)</a>
+      <li><a href="modules.html#geocode.Geocoder.reverse_geocode">reverse_geocode() (geocode.Geocoder method)</a>
 </li>
   </ul></td>
   <td style="width: 33%; vertical-align: top;"><ul>
-      <li><a href="modules.html#geocode.Geocoder.reverse_geocode_gsp_20181031">reverse_geocode_gsp_20181031() (geocode.Geocoder method)</a>
+      <li><a href="modules.html#geocode.Geocoder.reverse_geocode_gsp">reverse_geocode_gsp() (geocode.Geocoder method)</a>
 </li>
       <li><a href="modules.html#geocode.Geocoder.reverse_geocode_llsoa">reverse_geocode_llsoa() (geocode.Geocoder method)</a>
 </li>
diff --git a/docs/build/html/index.html b/docs/build/html/index.html
index 56ab349..83bdd3a 100644
--- a/docs/build/html/index.html
+++ b/docs/build/html/index.html
@@ -4,7 +4,7 @@
   <meta charset="utf-8" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
 
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-  <title>Welcome to Geocode’s documentation! &mdash; Geocode 0.10.5 documentation</title>
+  <title>Welcome to Geocode’s documentation! &mdash; Geocode 0.11.0 documentation</title>
       <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
       <link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
   <!--[if lt IE 9]>
diff --git a/docs/build/html/modules.html b/docs/build/html/modules.html
index dc72622..00a8194 100644
--- a/docs/build/html/modules.html
+++ b/docs/build/html/modules.html
@@ -4,7 +4,7 @@
   <meta charset="utf-8" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
 
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-  <title>Modules &mdash; Geocode 0.10.5 documentation</title>
+  <title>Modules &mdash; Geocode 0.11.0 documentation</title>
       <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
       <link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
   <!--[if lt IE 9]>
@@ -44,21 +44,17 @@
 <li class="toctree-l1 current"><a class="current reference internal" href="#">Modules</a><ul>
 <li class="toctree-l2"><a class="reference internal" href="#geocode">geocode</a><ul>
 <li class="toctree-l3"><a class="reference internal" href="#geocode.Geocoder"><code class="docutils literal notranslate"><span class="pre">Geocoder</span></code></a><ul>
-<li class="toctree-l4"><a class="reference internal" href="#geocode.Geocoder.clear_cache"><code class="docutils literal notranslate"><span class="pre">Geocoder.clear_cache()</span></code></a></li>
-<li class="toctree-l4"><a class="reference internal" href="#geocode.Geocoder.cpo_geocode"><code class="docutils literal notranslate"><span class="pre">Geocoder.cpo_geocode()</span></code></a></li>
-<li class="toctree-l4"><a class="reference internal" href="#geocode.Geocoder.cpo_geocode_one"><code class="docutils literal notranslate"><span class="pre">Geocoder.cpo_geocode_one()</span></code></a></li>
 <li class="toctree-l4"><a class="reference internal" href="#geocode.Geocoder.force_setup"><code class="docutils literal notranslate"><span class="pre">Geocoder.force_setup()</span></code></a></li>
-<li class="toctree-l4"><a class="reference internal" href="#geocode.Geocoder.force_setup_eso"><code class="docutils literal notranslate"><span class="pre">Geocoder.force_setup_eso()</span></code></a></li>
 <li class="toctree-l4"><a class="reference internal" href="#geocode.Geocoder.geocode"><code class="docutils literal notranslate"><span class="pre">Geocoder.geocode()</span></code></a></li>
 <li class="toctree-l4"><a class="reference internal" href="#geocode.Geocoder.geocode_constituency"><code class="docutils literal notranslate"><span class="pre">Geocoder.geocode_constituency()</span></code></a></li>
 <li class="toctree-l4"><a class="reference internal" href="#geocode.Geocoder.geocode_llsoa"><code class="docutils literal notranslate"><span class="pre">Geocoder.geocode_llsoa()</span></code></a></li>
 <li class="toctree-l4"><a class="reference internal" href="#geocode.Geocoder.geocode_local_authority"><code class="docutils literal notranslate"><span class="pre">Geocoder.geocode_local_authority()</span></code></a></li>
-<li class="toctree-l4"><a class="reference internal" href="#geocode.Geocoder.geocode_one"><code class="docutils literal notranslate"><span class="pre">Geocoder.geocode_one()</span></code></a></li>
+<li class="toctree-l4"><a class="reference internal" href="#geocode.Geocoder.geocode_postcode"><code class="docutils literal notranslate"><span class="pre">Geocoder.geocode_postcode()</span></code></a></li>
 <li class="toctree-l4"><a class="reference internal" href="#geocode.Geocoder.get_dno_regions"><code class="docutils literal notranslate"><span class="pre">Geocoder.get_dno_regions()</span></code></a></li>
-<li class="toctree-l4"><a class="reference internal" href="#geocode.Geocoder.gmaps_geocode_one"><code class="docutils literal notranslate"><span class="pre">Geocoder.gmaps_geocode_one()</span></code></a></li>
-<li class="toctree-l4"><a class="reference internal" href="#geocode.Geocoder.postcode2llsoa"><code class="docutils literal notranslate"><span class="pre">Geocoder.postcode2llsoa()</span></code></a></li>
+<li class="toctree-l4"><a class="reference internal" href="#geocode.Geocoder.get_gsp_regions"><code class="docutils literal notranslate"><span class="pre">Geocoder.get_gsp_regions()</span></code></a></li>
+<li class="toctree-l4"><a class="reference internal" href="#geocode.Geocoder.get_llsoa_boundaries"><code class="docutils literal notranslate"><span class="pre">Geocoder.get_llsoa_boundaries()</span></code></a></li>
+<li class="toctree-l4"><a class="reference internal" href="#geocode.Geocoder.reverse_geocode"><code class="docutils literal notranslate"><span class="pre">Geocoder.reverse_geocode()</span></code></a></li>
 <li class="toctree-l4"><a class="reference internal" href="#geocode.Geocoder.reverse_geocode_gsp"><code class="docutils literal notranslate"><span class="pre">Geocoder.reverse_geocode_gsp()</span></code></a></li>
-<li class="toctree-l4"><a class="reference internal" href="#geocode.Geocoder.reverse_geocode_gsp_20181031"><code class="docutils literal notranslate"><span class="pre">Geocoder.reverse_geocode_gsp_20181031()</span></code></a></li>
 <li class="toctree-l4"><a class="reference internal" href="#geocode.Geocoder.reverse_geocode_llsoa"><code class="docutils literal notranslate"><span class="pre">Geocoder.reverse_geocode_llsoa()</span></code></a></li>
 </ul>
 </li>
@@ -108,205 +104,74 @@ <h1><a class="toc-backref" href="#id1">Modules</a><a class="headerlink" href="#m
 <h2><a class="toc-backref" href="#id2">geocode</a><a class="headerlink" href="#geocode" title="Permalink to this heading"></a></h2>
 <dl class="py class">
 <dt class="sig sig-object py" id="geocode.Geocoder">
-<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">geocode.</span></span><span class="sig-name descname"><span class="pre">Geocoder</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">progress_bar</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">bool</span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">prefix</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">gmaps_key_file</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/geocode.html#Geocoder"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#geocode.Geocoder" title="Permalink to this definition"></a></dt>
+<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">geocode.</span></span><span class="sig-name descname"><span class="pre">Geocoder</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">cache_dir</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">gmaps_key_file</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/geocode.html#Geocoder"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#geocode.Geocoder" title="Permalink to this definition"></a></dt>
 <dd><p>Bases: <code class="xref py py-class docutils literal notranslate"><span class="pre">object</span></code></p>
 <p>Geocode addresses, postcodes, LLSOAs or Constituencies or reverse-geocode latitudes and
 longitudes.</p>
-<dl class="py method">
-<dt class="sig sig-object py" id="geocode.Geocoder.clear_cache">
-<span class="sig-name descname"><span class="pre">clear_cache</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">delete_gmaps_cache</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">bool</span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">old_versions_only</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">bool</span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">False</span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">None</span></span></span><a class="reference internal" href="_modules/geocode.html#Geocoder.clear_cache"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#geocode.Geocoder.clear_cache" title="Permalink to this definition"></a></dt>
-<dd><p>Clear any cache files from the installation directory including from old versions.</p>
-<dl class="field-list simple">
-<dt class="field-odd">Parameters</dt>
-<dd class="field-odd"><ul class="simple">
-<li><p><strong>delete_gmaps_cache</strong> (<em>boolean</em>) – Optional boolean deciding whether or not to clear the Google Maps data in cache.
-Defaults to None, resulting in stdout/stdin prompts.</p></li>
-<li><p><strong>old_versions_only</strong> (<em>boolean</em>) – Optional boolean deciding whether or not to clear all cache files or just those
-corresponding to previous versions of the Geocode library. Defaults to False.</p></li>
-</ul>
-</dd>
-</dl>
-</dd></dl>
-
-<dl class="py method">
-<dt class="sig sig-object py" id="geocode.Geocoder.cpo_geocode">
-<span class="sig-name descname"><span class="pre">cpo_geocode</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">pcs</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">DataFrame</span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">DataFrame</span></span></span><a class="reference internal" href="_modules/geocode.html#Geocoder.cpo_geocode"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#geocode.Geocoder.cpo_geocode" title="Permalink to this definition"></a></dt>
-<dd><p>Geocode multiple postcodes using Code Point Open.</p>
-<dl class="field-list simple">
-<dt class="field-odd">Parameters</dt>
-<dd class="field-odd"><p><strong>pcs</strong> (<em>Pandas.DataFrame</em>) – A Pandas DataFrame containing the postcodes (or partial postcodes) to geocode. Must
-contain the column ‘input_postcode’.</p>
-</dd>
-<dt class="field-even">Returns</dt>
-<dd class="field-even"><p>A Pandas DataFrame with columns: input_postcode (str), latitude (float), longitude
-(float), match_status (int). The <cite>match_status</cite> shows the quality of the postcode lookup
-- use the class attribute <em>self.status_codes</em> (a dict) to get a string representation.</p>
-</dd>
-<dt class="field-odd">Return type</dt>
-<dd class="field-odd"><p>Pandas.DataFrame</p>
-</dd>
-</dl>
-</dd></dl>
-
-<dl class="py method">
-<dt class="sig sig-object py" id="geocode.Geocoder.cpo_geocode_one">
-<span class="sig-name descname"><span class="pre">cpo_geocode_one</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">postcode</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">str</span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">Series</span></span></span><a class="reference internal" href="_modules/geocode.html#Geocoder.cpo_geocode_one"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#geocode.Geocoder.cpo_geocode_one" title="Permalink to this definition"></a></dt>
-<dd><p>Use the OS Code Point Open Database to geocode a postcode (or partial postcode using as much
-accuracy as the partial code permits).</p>
-<dl class="field-list simple">
-<dt class="field-odd">Parameters</dt>
-<dd class="field-odd"><p><strong>postcode</strong> (<em>string</em>) – The postcode (or partial postcode) to geocode.</p>
-</dd>
-<dt class="field-even">Returns</dt>
-<dd class="field-even"><p>A Pandas Series with fields: input_postcode (str), latitude (float), longitude (float),
-match_status (int). The status code shows the quality of the postcode lookup - use the
-class attribute <em>self.status_codes</em> (a dict) to get a string representation.</p>
-</dd>
-<dt class="field-odd">Return type</dt>
-<dd class="field-odd"><p>Pandas.Series</p>
-</dd>
-</dl>
-</dd></dl>
-
 <dl class="py method">
 <dt class="sig sig-object py" id="geocode.Geocoder.force_setup">
-<span class="sig-name descname"><span class="pre">force_setup</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="reference internal" href="_modules/geocode.html#Geocoder.force_setup"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#geocode.Geocoder.force_setup" title="Permalink to this definition"></a></dt>
+<span class="sig-name descname"><span class="pre">force_setup</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">ngeso_setup</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">True</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">cpo_setup</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">True</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">ons_setup</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">True</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/geocode.html#Geocoder.force_setup"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#geocode.Geocoder.force_setup" title="Permalink to this definition"></a></dt>
 <dd><p>Download all data and setup caches.</p>
 </dd></dl>
 
-<dl class="py method">
-<dt class="sig sig-object py" id="geocode.Geocoder.force_setup_eso">
-<span class="sig-name descname"><span class="pre">force_setup_eso</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="reference internal" href="_modules/geocode.html#Geocoder.force_setup_eso"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#geocode.Geocoder.force_setup_eso" title="Permalink to this definition"></a></dt>
-<dd><p>Download all ESO data and setup caches.</p>
-</dd></dl>
-
 <dl class="py method">
 <dt class="sig sig-object py" id="geocode.Geocoder.geocode">
-<span class="sig-name descname"><span class="pre">geocode</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">postcodes</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">Iterable</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">addresses</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">Iterable</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">List</span><span class="p"><span class="pre">[</span></span><span class="pre">Tuple</span><span class="p"><span class="pre">[</span></span><span class="pre">float</span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="pre">float</span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="pre">int</span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">]</span></span></span></span><a class="reference internal" href="_modules/geocode.html#Geocoder.geocode"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#geocode.Geocoder.geocode" title="Permalink to this definition"></a></dt>
-<dd><p>Geocode several postcodes and/or addresses.</p>
+<span class="sig-name descname"><span class="pre">geocode</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">entity_ids</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">entity</span></span></em>, <em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/geocode.html#Geocoder.geocode"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#geocode.Geocoder.geocode" title="Permalink to this definition"></a></dt>
+<dd><p>Geocode a selection of GSP regions, llsoa boundaries, constituencies, LADs, postcodes or
+addresses to latitudes and longitudes.</p>
 <dl class="field-list simple">
 <dt class="field-odd">Parameters</dt>
 <dd class="field-odd"><ul class="simple">
-<li><p><strong>postcodes</strong> (<em>iterable</em><em> of </em><em>strings</em>) – The postcodes (or partial postcodes) to geocode. Must align with <em>addresses</em> if both are
-passed.</p></li>
-<li><p><strong>addresses</strong> (<em>iterable</em><em> of </em><em>strings</em>) – The addresses to geocode. Must align with <em>postcodes</em> if both are passed. If addresses
-are passed, the GMaps API will be used since OS Code Point Open does not provide address
-lookup.</p></li>
+<li><p><strong>entity_ids</strong> (<em>iterable</em><em> of </em><em>strings</em>) – The specific entities to Geocode.</p></li>
+<li><p><strong>entity</strong> (<em>string</em>) – Specify the entity type to Geocode from i.e., lad or postcode.</p></li>
+<li><p><strong>**kwargs</strong> – Options to pass to the underlying geocode method.</p></li>
 </ul>
 </dd>
-<dt class="field-even">Returns</dt>
-<dd class="field-even"><p>The output list will align with the input postcodes/addresses, with each element being a
-tuple containing: latitude (float), longitude (float), status code (int). The status
-code shows the quality of the postcode lookup - use the class attribute
-<em>self.status_codes</em> (a dict) to get a string representation.</p>
-</dd>
-<dt class="field-odd">Return type</dt>
-<dd class="field-odd"><p>list of tuples</p>
-</dd>
 </dl>
-<p class="rubric">Notes</p>
-<p>The input iterables can be any Python object which can be interpreted by Pandas.DataFrame()
-e.g. a list, tuple, Numpy array etc.
-If you pass only postcodes, this method will priotiise OS Code Point Open as the geocoder.
-If a postcode fails to geocode using OS CPO, it will be geocoded with GMaps.</p>
 </dd></dl>
 
 <dl class="py method">
 <dt class="sig sig-object py" id="geocode.Geocoder.geocode_constituency">
-<span class="sig-name descname"><span class="pre">geocode_constituency</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">constituency</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Union</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="pre">Iterable</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">]</span></span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">Union</span><span class="p"><span class="pre">[</span></span><span class="pre">Tuple</span><span class="p"><span class="pre">[</span></span><span class="pre">float</span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="pre">float</span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="pre">List</span><span class="p"><span class="pre">[</span></span><span class="pre">Tuple</span><span class="p"><span class="pre">[</span></span><span class="pre">float</span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="pre">float</span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">]</span></span></span></span><a class="reference internal" href="_modules/geocode.html#Geocoder.geocode_constituency"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#geocode.Geocoder.geocode_constituency" title="Permalink to this definition"></a></dt>
-<dd><p>Geocode a UK Constituency using the geospatial centroid.</p>
+<span class="sig-name descname"><span class="pre">geocode_constituency</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">constituencies</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/geocode.html#Geocoder.geocode_constituency"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#geocode.Geocoder.geocode_constituency" title="Permalink to this definition"></a></dt>
+<dd><p>Function to geocode a collection of constituencies into latlons.</p>
 <dl class="field-list simple">
 <dt class="field-odd">Parameters</dt>
-<dd class="field-odd"><p><strong>constituency</strong> (<em>string</em><em> or </em><em>iterable</em><em> of </em><em>strings</em>) – The constituency names to geocode.</p>
-</dd>
-<dt class="field-even">Returns</dt>
-<dd class="field-even"><p>If a single constituency name was passed (i.e. a string), the output be a tuple
-containing: latitude (float), longitude (float). If several constituency names are
-passed (i.e. an iterable of strings), the output will be a list of tuples which aligns
-with the input iterable.</p>
-</dd>
-<dt class="field-odd">Return type</dt>
-<dd class="field-odd"><p>tuple or list of tuples</p>
+<dd class="field-odd"><p><strong>constituencies</strong> (<em>iterable</em><em> of </em><em>strings</em>) – Specific constituencies to geocode to latlons</p>
 </dd>
 </dl>
-<p class="rubric">Notes</p>
-<p>The input <em>constituency</em> iterable can be any Python object which can be looped over with a
-for loop e.g. a list, tuple, Numpy array etc.
-Constituencies are identified by their full name, case-insensitive, ignoring spaces.</p>
 </dd></dl>
 
 <dl class="py method">
 <dt class="sig sig-object py" id="geocode.Geocoder.geocode_llsoa">
-<span class="sig-name descname"><span class="pre">geocode_llsoa</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">llsoa</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Union</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="pre">Iterable</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">]</span></span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">Union</span><span class="p"><span class="pre">[</span></span><span class="pre">Tuple</span><span class="p"><span class="pre">[</span></span><span class="pre">float</span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="pre">float</span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="pre">List</span><span class="p"><span class="pre">[</span></span><span class="pre">Tuple</span><span class="p"><span class="pre">[</span></span><span class="pre">float</span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="pre">float</span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">]</span></span></span></span><a class="reference internal" href="_modules/geocode.html#Geocoder.geocode_llsoa"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#geocode.Geocoder.geocode_llsoa" title="Permalink to this definition"></a></dt>
-<dd><p>Geocode an LLSOA using the Population Weighted Centroid.</p>
+<span class="sig-name descname"><span class="pre">geocode_llsoa</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">llsoa_boundaries</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/geocode.html#Geocoder.geocode_llsoa"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#geocode.Geocoder.geocode_llsoa" title="Permalink to this definition"></a></dt>
+<dd><p>Function to geocode a collection of llsoa boundaries into latlons.</p>
 <dl class="field-list simple">
 <dt class="field-odd">Parameters</dt>
-<dd class="field-odd"><p><strong>llsoa</strong> (<em>string</em><em> or </em><em>iterable</em><em> of </em><em>strings</em>) – The LLSOA(s) to geocode.</p>
-</dd>
-<dt class="field-even">Returns</dt>
-<dd class="field-even"><p>If a single LLSOA was passed (i.e. a string), the output be a tuple containing: latitude
-(float), longitude (float). If several LLSOAs are passed (i.e. an iterable of strings),
-the output will be a list of tuples which aligns with the input iterable.</p>
-</dd>
-<dt class="field-odd">Return type</dt>
-<dd class="field-odd"><p>Tuple or List of Tuples</p>
+<dd class="field-odd"><p><strong>llsoa_boundaries</strong> (<em>iterable</em><em> of </em><em>strings</em>) – Specific llsoa boundaries to geocode to latlons</p>
 </dd>
 </dl>
-<p class="rubric">Notes</p>
-<p>The input <em>llsoa</em> iterable can be any Python object which can be looped over with a for loop
-e.g. a list, tuple, Numpy array etc.
-LLSOAs are identified by their LLSOA Code, sometimes abbreviated to <cite>llsoacd</cite>.
-In Scotland, one can use either LLSOA codes or Datazones, this method supports both.</p>
 </dd></dl>
 
 <dl class="py method">
 <dt class="sig sig-object py" id="geocode.Geocoder.geocode_local_authority">
-<span class="sig-name descname"><span class="pre">geocode_local_authority</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">local_authority</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Union</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="pre">Iterable</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">]</span></span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">Union</span><span class="p"><span class="pre">[</span></span><span class="pre">Tuple</span><span class="p"><span class="pre">[</span></span><span class="pre">float</span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="pre">float</span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="pre">List</span><span class="p"><span class="pre">[</span></span><span class="pre">Tuple</span><span class="p"><span class="pre">[</span></span><span class="pre">float</span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="pre">float</span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">]</span></span></span></span><a class="reference internal" href="_modules/geocode.html#Geocoder.geocode_local_authority"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#geocode.Geocoder.geocode_local_authority" title="Permalink to this definition"></a></dt>
-<dd><p>Geocode a UK Local Authority using the geospatial centroid.</p>
+<span class="sig-name descname"><span class="pre">geocode_local_authority</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">lads</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/geocode.html#Geocoder.geocode_local_authority"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#geocode.Geocoder.geocode_local_authority" title="Permalink to this definition"></a></dt>
+<dd><p>Function to geocode a collection of LADs (Local Authority Districts) into latlons.</p>
 <dl class="field-list simple">
 <dt class="field-odd">Parameters</dt>
-<dd class="field-odd"><p><strong>local_authority</strong> (<em>string</em><em> or </em><em>iterable</em><em> of </em><em>strings</em>) – The Local Authority names to geocode.</p>
-</dd>
-<dt class="field-even">Returns</dt>
-<dd class="field-even"><p>If a single Local Authority name was passed (i.e. a string), the output be a tuple
-containing: latitude (float), longitude (float). If several Local Authority names are
-passed (i.e. an iterable of strings), the output will be a list of tuples which aligns
-with the input iterable.</p>
-</dd>
-<dt class="field-odd">Return type</dt>
-<dd class="field-odd"><p>tuple or list of tuples</p>
+<dd class="field-odd"><p><strong>lads</strong> (<em>iterable</em><em> of </em><em>strings</em>) – Specific LADs to geocode to latlons</p>
 </dd>
 </dl>
-<p class="rubric">Notes</p>
-<p>The input <em>local_authority</em> iterable can be any Python object which can be looped over with
-a for loop e.g. a list, tuple, Numpy array etc.
-Local Authorities are identified by their full name, case-insensitive, ignoring spaces.</p>
 </dd></dl>
 
 <dl class="py method">
-<dt class="sig sig-object py" id="geocode.Geocoder.geocode_one">
-<span class="sig-name descname"><span class="pre">geocode_one</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">postcode</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">address</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">Tuple</span><span class="p"><span class="pre">[</span></span><span class="pre">float</span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="pre">float</span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="pre">int</span><span class="p"><span class="pre">]</span></span></span></span><a class="reference internal" href="_modules/geocode.html#Geocoder.geocode_one"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#geocode.Geocoder.geocode_one" title="Permalink to this definition"></a></dt>
-<dd><p>Geocode a single postcode and/or address.</p>
+<dt class="sig sig-object py" id="geocode.Geocoder.geocode_postcode">
+<span class="sig-name descname"><span class="pre">geocode_postcode</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">postcodes</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">method</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">'cpo'</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/geocode.html#Geocoder.geocode_postcode"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#geocode.Geocoder.geocode_postcode" title="Permalink to this definition"></a></dt>
+<dd><p>Function to geocode a collection of postcodes into latlons.</p>
 <dl class="field-list simple">
 <dt class="field-odd">Parameters</dt>
-<dd class="field-odd"><ul class="simple">
-<li><p><strong>postcode</strong> (<em>string</em>) – The postcode (or partial postcode) to geocode.</p></li>
-<li><p><strong>address</strong> (<em>string</em>) – The address to geocode. If address is passed, the GMaps API will be used since OS Code
-Point Open does not provide address lookup.</p></li>
-</ul>
-</dd>
-<dt class="field-even">Returns</dt>
-<dd class="field-even"><p>A tuple containing: latitude (float), longitude (float), status code (int). The status
-code shows the quality of the postcode lookup - use the class attribute
-<em>self.status_codes</em> (a dict) to get a string representation.</p>
-</dd>
-<dt class="field-odd">Return type</dt>
-<dd class="field-odd"><p>tuple</p>
+<dd class="field-odd"><p><strong>postcodes</strong> (<em>iterable</em><em> of </em><em>strings</em>) – Specific postcodes to geocode to latlons</p>
 </dd>
 </dl>
-<p class="rubric">Notes</p>
-<p>If you pass only postcode, this method will priotiise OS Code Point Open as the geocoder.
-If a postcode fails to geocode using OS CPO, it will be geocoded with GMaps.</p>
 </dd></dl>
 
 <dl class="py method">
@@ -328,107 +193,55 @@ <h2><a class="toc-backref" href="#id2">geocode</a><a class="headerlink" href="#g
 </dd></dl>
 
 <dl class="py method">
-<dt class="sig sig-object py" id="geocode.Geocoder.gmaps_geocode_one">
-<span class="sig-name descname"><span class="pre">gmaps_geocode_one</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">postcode</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">str</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">address</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">Series</span></span></span><a class="reference internal" href="_modules/geocode.html#Geocoder.gmaps_geocode_one"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#geocode.Geocoder.gmaps_geocode_one" title="Permalink to this definition"></a></dt>
-<dd><p>Use the GMaps API to geocode a postcode (or partial postcode) and/or an address.</p>
-<dl class="field-list simple">
-<dt class="field-odd">Parameters</dt>
-<dd class="field-odd"><ul class="simple">
-<li><p><strong>postcode</strong> (<em>string</em>) – The postcode (or partial postcode) to geocode.</p></li>
-<li><p><strong>address</strong> (<em>string</em>) – The address to geocode.</p></li>
-</ul>
-</dd>
-<dt class="field-even">Returns</dt>
-<dd class="field-even"><p>A Pandas Series with fields: input_postcode (str), latitude (float), longitude (float),
-match_status (int). The status code shows the quality of the postcode lookup - use the
-class attribute <em>self.status_codes</em> (a dict) to get a string representation.</p>
-</dd>
-<dt class="field-odd">Return type</dt>
-<dd class="field-odd"><p>Pandas.Series</p>
-</dd>
-</dl>
+<dt class="sig sig-object py" id="geocode.Geocoder.get_gsp_regions">
+<span class="sig-name descname"><span class="pre">get_gsp_regions</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="reference internal" href="_modules/geocode.html#Geocoder.get_gsp_regions"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#geocode.Geocoder.get_gsp_regions" title="Permalink to this definition"></a></dt>
+<dd><p>Get the 20220314 GSP / GNode boundaries from the ESO Data Portal API.</p>
 </dd></dl>
 
 <dl class="py method">
-<dt class="sig sig-object py" id="geocode.Geocoder.postcode2llsoa">
-<span class="sig-name descname"><span class="pre">postcode2llsoa</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">pcs</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">DataFrame</span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">DataFrame</span></span></span><a class="reference internal" href="_modules/geocode.html#Geocoder.postcode2llsoa"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#geocode.Geocoder.postcode2llsoa" title="Permalink to this definition"></a></dt>
-<dd><p>Reverse-geocode multiple postcodes to LLSOA.</p>
-<dl class="field-list simple">
-<dt class="field-odd">Parameters</dt>
-<dd class="field-odd"><p><strong>pcs</strong> (<em>Pandas.DataFrame</em>) – A Pandas DataFrame containing the postcodes (or partial postcodes) to geocode. Must
-contain the column ‘input_postcode’.</p>
-</dd>
-<dt class="field-even">Returns</dt>
-<dd class="field-even"><p>A Pandas DataFrame with columns: input_postcode (str), lsoa11cd (str), plus any other
-cols that were in <cite>pcs</cite>.</p>
-</dd>
-<dt class="field-odd">Return type</dt>
-<dd class="field-odd"><p>Pandas.DataFrame</p>
-</dd>
-</dl>
+<dt class="sig sig-object py" id="geocode.Geocoder.get_llsoa_boundaries">
+<span class="sig-name descname"><span class="pre">get_llsoa_boundaries</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="reference internal" href="_modules/geocode.html#Geocoder.get_llsoa_boundaries"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#geocode.Geocoder.get_llsoa_boundaries" title="Permalink to this definition"></a></dt>
+<dd><p>Load the LLSOA boundaries, either from local cache if available, else fetch from raw API
+(England and Wales) and packaged data (Scotland).</p>
 </dd></dl>
 
 <dl class="py method">
-<dt class="sig sig-object py" id="geocode.Geocoder.reverse_geocode_gsp">
-<span class="sig-name descname"><span class="pre">reverse_geocode_gsp</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">latlons</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">List</span><span class="p"><span class="pre">[</span></span><span class="pre">Tuple</span><span class="p"><span class="pre">[</span></span><span class="pre">float</span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="pre">float</span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">]</span></span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">Tuple</span><span class="p"><span class="pre">[</span></span><span class="pre">List</span><span class="p"><span class="pre">[</span></span><span class="pre">int</span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="pre">List</span><span class="p"><span class="pre">[</span></span><span class="pre">List</span><span class="p"><span class="pre">[</span></span><span class="pre">Dict</span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">]</span></span></span></span><a class="reference internal" href="_modules/geocode.html#Geocoder.reverse_geocode_gsp"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#geocode.Geocoder.reverse_geocode_gsp" title="Permalink to this definition"></a></dt>
-<dd><p>Reverse-geocode latitudes and longitudes to GSP using the 20220314 definitions.</p>
+<dt class="sig sig-object py" id="geocode.Geocoder.reverse_geocode">
+<span class="sig-name descname"><span class="pre">reverse_geocode</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">latlons</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">entity</span></span></em>, <em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/geocode.html#Geocoder.reverse_geocode"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#geocode.Geocoder.reverse_geocode" title="Permalink to this definition"></a></dt>
+<dd><p>Reverse geocode a set of latitudes and longitudes to either GSP regions or llsoa boundaries.</p>
 <dl class="field-list simple">
 <dt class="field-odd">Parameters</dt>
-<dd class="field-odd"><p><strong>latlons</strong> (<em>list</em><em> of </em><em>tuples</em>) – A list of tuples containing (latitude, longitude).</p>
-</dd>
-<dt class="field-even">Returns</dt>
-<dd class="field-even"><p><strong>`results`</strong> – A list of tuples containing (&lt;GSPs&gt;, &lt;GSPGroup&gt;), aligned with the input <em>latlons</em>.</p>
-</dd>
-<dt class="field-odd">Return type</dt>
-<dd class="field-odd"><p>list of ints</p>
+<dd class="field-odd"><ul class="simple">
+<li><p><strong>latlons</strong> (<em>list</em><em> of </em><em>tuples</em>) – A list of tuples containing (latitude, longitude).</p></li>
+<li><p><strong>entity</strong> (<em>string</em>) – Specify the entity type to Geocode from i.e., gsp or llsoa.</p></li>
+<li><p><strong>**kwargs</strong> – Options to pass to the underlying reverse-geocode method.</p></li>
+</ul>
 </dd>
 </dl>
-<p class="rubric">Notes</p>
-<p>Return format needs some work, maybe switch to DataFrames in future release.</p>
 </dd></dl>
 
 <dl class="py method">
-<dt class="sig sig-object py" id="geocode.Geocoder.reverse_geocode_gsp_20181031">
-<span class="sig-name descname"><span class="pre">reverse_geocode_gsp_20181031</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">latlons</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">List</span><span class="p"><span class="pre">[</span></span><span class="pre">Tuple</span><span class="p"><span class="pre">[</span></span><span class="pre">float</span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="pre">float</span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">]</span></span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">Tuple</span><span class="p"><span class="pre">[</span></span><span class="pre">List</span><span class="p"><span class="pre">[</span></span><span class="pre">int</span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="pre">List</span><span class="p"><span class="pre">[</span></span><span class="pre">List</span><span class="p"><span class="pre">[</span></span><span class="pre">Dict</span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">]</span></span></span></span><a class="reference internal" href="_modules/geocode.html#Geocoder.reverse_geocode_gsp_20181031"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#geocode.Geocoder.reverse_geocode_gsp_20181031" title="Permalink to this definition"></a></dt>
-<dd><p>Reverse-geocode latitudes and longitudes to GSP using the 20181031 definitions.</p>
+<dt class="sig sig-object py" id="geocode.Geocoder.reverse_geocode_gsp">
+<span class="sig-name descname"><span class="pre">reverse_geocode_gsp</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">latlons</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/geocode.html#Geocoder.reverse_geocode_gsp"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#geocode.Geocoder.reverse_geocode_gsp" title="Permalink to this definition"></a></dt>
+<dd><p>Function to reverse geocode a collection of latlons into gsp regions.</p>
 <dl class="field-list simple">
 <dt class="field-odd">Parameters</dt>
-<dd class="field-odd"><p><strong>latlons</strong> (<em>list</em><em> of </em><em>tuples</em>) – A list of tuples containing (latitude, longitude).</p>
-</dd>
-<dt class="field-even">Returns</dt>
-<dd class="field-even"><p><ul class="simple">
-<li><p><strong>`results`</strong> (<em>list of ints</em>) – A list of region IDs, aligned with the input <em>latlons</em>.</p></li>
-<li><p><strong>`results_more`</strong> (<em>list of lists of dicts</em>) – The full MANY:MANY lookup giving the Region &lt;-&gt; GSP &lt;-&gt; GNode which the lat/lon falls
-within. The relationship between GSP: GNode is MANY:MANY, so each element of the outer
-list is another list of matches, each element of which is a dictionary giving the
-matched GSP / GNode.</p></li>
-</ul>
-</p>
+<dd class="field-odd"><p><strong>latlons</strong> (<em>iterable</em><em> of </em><em>strings</em>) – Specific latlons to geocode to gsp regions.</p>
 </dd>
 </dl>
-<p class="rubric">Notes</p>
-<p>Return format needs some work, maybe switch to DataFrames in future release.</p>
 </dd></dl>
 
 <dl class="py method">
 <dt class="sig sig-object py" id="geocode.Geocoder.reverse_geocode_llsoa">
-<span class="sig-name descname"><span class="pre">reverse_geocode_llsoa</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">latlons</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">List</span><span class="p"><span class="pre">[</span></span><span class="pre">Tuple</span><span class="p"><span class="pre">[</span></span><span class="pre">float</span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="pre">float</span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">]</span></span></span></em>, <em class="sig-param"><span class="n"><span class="pre">datazones</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">bool</span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">False</span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">List</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span></span></span><a class="reference internal" href="_modules/geocode.html#Geocoder.reverse_geocode_llsoa"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#geocode.Geocoder.reverse_geocode_llsoa" title="Permalink to this definition"></a></dt>
-<dd><p>Reverse-geocode latitudes and longitudes to LLSOA.</p>
+<span class="sig-name descname"><span class="pre">reverse_geocode_llsoa</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">latlons</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">dz</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">True</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/geocode.html#Geocoder.reverse_geocode_llsoa"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#geocode.Geocoder.reverse_geocode_llsoa" title="Permalink to this definition"></a></dt>
+<dd><p>Function to reverse geocode a collection of latlons into llsoa boundaries.</p>
 <dl class="field-list simple">
 <dt class="field-odd">Parameters</dt>
 <dd class="field-odd"><ul class="simple">
-<li><p><strong>latlons</strong> (<em>list</em><em> of </em><em>tuples</em>) – A list of tuples containing (latitude, longitude).</p></li>
-<li><p><strong>datazones</strong> (<em>bool</em>) – Set this to True to return Datazones rather than LLSOA codes in Scotland (default is
-False).</p></li>
+<li><p><strong>latlons</strong> (<em>iterable</em><em> of </em><em>strings</em>) – Specific latlons to geocode to llsoa boundaries.</p></li>
+<li><p><strong>dz</strong> (<em>Boolean</em>) – Indication whether to consider datazones</p></li>
 </ul>
 </dd>
-<dt class="field-even">Returns</dt>
-<dd class="field-even"><p>The LLSOA codes that the input latitudes and longitudes fall within. Any lat/lons which
-do not fall inside an LLSOA boundary will return None.</p>
-</dd>
-<dt class="field-odd">Return type</dt>
-<dd class="field-odd"><p>list of strings</p>
-</dd>
 </dl>
 </dd></dl>
 
diff --git a/docs/build/html/objects.inv b/docs/build/html/objects.inv
index a45cf7a..5e90e5e 100644
Binary files a/docs/build/html/objects.inv and b/docs/build/html/objects.inv differ
diff --git a/docs/build/html/search.html b/docs/build/html/search.html
index 3247196..8aa4cdf 100644
--- a/docs/build/html/search.html
+++ b/docs/build/html/search.html
@@ -3,7 +3,7 @@
 <head>
   <meta charset="utf-8" />
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-  <title>Search &mdash; Geocode 0.10.5 documentation</title>
+  <title>Search &mdash; Geocode 0.11.0 documentation</title>
       <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
       <link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
     
diff --git a/docs/build/html/searchindex.js b/docs/build/html/searchindex.js
index 95e4815..8484e7f 100644
--- a/docs/build/html/searchindex.js
+++ b/docs/build/html/searchindex.js
@@ -1,1 +1,1 @@
-Search.setIndex({"docnames": ["index", "modules"], "filenames": ["index.rst", "modules.rst"], "titles": ["Welcome to Geocode\u2019s documentation!", "Modules"], "terms": {"modul": 0, "index": 0, "search": 0, "page": 0, "class": 1, "progress_bar": 1, "bool": 1, "fals": 1, "prefix": 1, "option": 1, "str": 1, "none": 1, "gmaps_key_fil": 1, "sourc": 1, "base": 1, "object": 1, "address": 1, "postcod": 1, "llsoa": 1, "constitu": 1, "revers": 1, "latitud": 1, "longitud": 1, "clear_cach": 1, "delete_gmaps_cach": 1, "old_versions_onli": 1, "clear": 1, "ani": 1, "cach": 1, "file": 1, "from": 1, "instal": 1, "directori": 1, "includ": 1, "old": 1, "version": 1, "paramet": 1, "boolean": 1, "decid": 1, "whether": 1, "googl": 1, "map": 1, "data": 1, "default": 1, "result": 1, "stdout": 1, "stdin": 1, "prompt": 1, "all": 1, "just": 1, "those": 1, "correspond": 1, "previou": 1, "librari": 1, "cpo_geocod": 1, "pc": 1, "datafram": 1, "multipl": 1, "us": 1, "code": 1, "point": 1, "open": 1, "panda": 1, "A": 1, "contain": 1, "partial": 1, "must": 1, "column": 1, "input_postcod": 1, "return": 1, "float": 1, "match_statu": 1, "int": 1, "The": 1, "show": 1, "qualiti": 1, "lookup": 1, "attribut": 1, "self": 1, "status_cod": 1, "dict": 1, "get": 1, "string": 1, "represent": 1, "type": 1, "cpo_geocode_on": 1, "seri": 1, "o": 1, "databas": 1, "much": 1, "accuraci": 1, "permit": 1, "field": 1, "statu": 1, "force_setup": 1, "download": 1, "setup": 1, "force_setup_eso": 1, "eso": 1, "iter": 1, "list": 1, "tupl": 1, "sever": 1, "align": 1, "both": 1, "ar": 1, "pass": 1, "If": 1, "gmap": 1, "api": 1, "sinc": 1, "doe": 1, "provid": 1, "output": 1, "input": 1, "each": 1, "element": 1, "being": 1, "note": 1, "can": 1, "python": 1, "which": 1, "interpret": 1, "e": 1, "g": 1, "numpi": 1, "arrai": 1, "etc": 1, "you": 1, "onli": 1, "thi": 1, "method": 1, "priotiis": 1, "fail": 1, "cpo": 1, "geocode_constitu": 1, "union": 1, "uk": 1, "geospati": 1, "centroid": 1, "name": 1, "singl": 1, "wa": 1, "i": 1, "an": 1, "loop": 1, "over": 1, "identifi": 1, "full": 1, "case": 1, "insensit": 1, "ignor": 1, "space": 1, "geocode_llsoa": 1, "popul": 1, "weight": 1, "": 1, "sometim": 1, "abbrevi": 1, "llsoacd": 1, "In": 1, "scotland": 1, "one": 1, "either": 1, "datazon": 1, "support": 1, "geocode_local_author": 1, "local_author": 1, "local": 1, "author": 1, "geocode_on": 1, "get_dno_region": 1, "dno": 1, "licens": 1, "area": 1, "boundari": 1, "portal": 1, "dno_region": 1, "whose": 1, "kei": 1, "region": 1, "id": 1, "valu": 1, "region_boundari": 1, "region_bound": 1, "shape": 1, "polygon": 1, "multipolygon": 1, "bound": 1, "xmin": 1, "ymin": 1, "xmax": 1, "ymax": 1, "dno_nam": 1, "longnam": 1, "gmaps_geocode_on": 1, "postcode2llsoa": 1, "lsoa11cd": 1, "plu": 1, "other": 1, "col": 1, "were": 1, "reverse_geocode_gsp": 1, "latlon": 1, "gsp": 1, "20220314": 1, "definit": 1, "gspgroup": 1, "format": 1, "need": 1, "some": 1, "work": 1, "mayb": 1, "switch": 1, "futur": 1, "releas": 1, "reverse_geocode_gsp_20181031": 1, "20181031": 1, "results_mor": 1, "mani": 1, "give": 1, "gnode": 1, "lat": 1, "lon": 1, "fall": 1, "within": 1, "relationship": 1, "between": 1, "so": 1, "outer": 1, "anoth": 1, "match": 1, "dictionari": 1, "reverse_geocode_llsoa": 1, "set": 1, "true": 1, "rather": 1, "than": 1, "do": 1, "insid": 1}, "objects": {"geocode": [[1, 0, 1, "", "Geocoder"]], "geocode.Geocoder": [[1, 1, 1, "", "clear_cache"], [1, 1, 1, "", "cpo_geocode"], [1, 1, 1, "", "cpo_geocode_one"], [1, 1, 1, "", "force_setup"], [1, 1, 1, "", "force_setup_eso"], [1, 1, 1, "", "geocode"], [1, 1, 1, "", "geocode_constituency"], [1, 1, 1, "", "geocode_llsoa"], [1, 1, 1, "", "geocode_local_authority"], [1, 1, 1, "", "geocode_one"], [1, 1, 1, "", "get_dno_regions"], [1, 1, 1, "", "gmaps_geocode_one"], [1, 1, 1, "", "postcode2llsoa"], [1, 1, 1, "", "reverse_geocode_gsp"], [1, 1, 1, "", "reverse_geocode_gsp_20181031"], [1, 1, 1, "", "reverse_geocode_llsoa"]]}, "objtypes": {"0": "py:class", "1": "py:method"}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "method", "Python method"]}, "titleterms": {"welcom": 0, "geocod": [0, 1], "": 0, "document": 0, "content": [0, 1], "indic": 0, "tabl": 0, "modul": 1}, "envversion": {"sphinx.domains.c": 2, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 8, "sphinx.domains.index": 1, "sphinx.domains.javascript": 2, "sphinx.domains.math": 2, "sphinx.domains.python": 3, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.viewcode": 1, "sphinx": 57}, "alltitles": {"Welcome to Geocode\u2019s documentation!": [[0, "welcome-to-geocode-s-documentation"]], "Contents:": [[0, null]], "Indices and tables": [[0, "indices-and-tables"]], "Modules": [[1, "modules"]], "Contents": [[1, "contents"]], "geocode": [[1, "geocode"]]}, "indexentries": {"geocoder (class in geocode)": [[1, "geocode.Geocoder"]], "clear_cache() (geocode.geocoder method)": [[1, "geocode.Geocoder.clear_cache"]], "cpo_geocode() (geocode.geocoder method)": [[1, "geocode.Geocoder.cpo_geocode"]], "cpo_geocode_one() (geocode.geocoder method)": [[1, "geocode.Geocoder.cpo_geocode_one"]], "force_setup() (geocode.geocoder method)": [[1, "geocode.Geocoder.force_setup"]], "force_setup_eso() (geocode.geocoder method)": [[1, "geocode.Geocoder.force_setup_eso"]], "geocode() (geocode.geocoder method)": [[1, "geocode.Geocoder.geocode"]], "geocode_constituency() (geocode.geocoder method)": [[1, "geocode.Geocoder.geocode_constituency"]], "geocode_llsoa() (geocode.geocoder method)": [[1, "geocode.Geocoder.geocode_llsoa"]], "geocode_local_authority() (geocode.geocoder method)": [[1, "geocode.Geocoder.geocode_local_authority"]], "geocode_one() (geocode.geocoder method)": [[1, "geocode.Geocoder.geocode_one"]], "get_dno_regions() (geocode.geocoder method)": [[1, "geocode.Geocoder.get_dno_regions"]], "gmaps_geocode_one() (geocode.geocoder method)": [[1, "geocode.Geocoder.gmaps_geocode_one"]], "postcode2llsoa() (geocode.geocoder method)": [[1, "geocode.Geocoder.postcode2llsoa"]], "reverse_geocode_gsp() (geocode.geocoder method)": [[1, "geocode.Geocoder.reverse_geocode_gsp"]], "reverse_geocode_gsp_20181031() (geocode.geocoder method)": [[1, "geocode.Geocoder.reverse_geocode_gsp_20181031"]], "reverse_geocode_llsoa() (geocode.geocoder method)": [[1, "geocode.Geocoder.reverse_geocode_llsoa"]]}})
\ No newline at end of file
+Search.setIndex({"docnames": ["index", "modules"], "filenames": ["index.rst", "modules.rst"], "titles": ["Welcome to Geocode\u2019s documentation!", "Modules"], "terms": {"modul": 0, "index": 0, "search": 0, "page": 0, "class": 1, "cache_dir": 1, "option": 1, "str": 1, "none": 1, "gmaps_key_fil": 1, "sourc": 1, "base": 1, "object": 1, "address": 1, "postcod": 1, "llsoa": 1, "constitu": 1, "revers": 1, "latitud": 1, "longitud": 1, "force_setup": 1, "ngeso_setup": 1, "true": 1, "cpo_setup": 1, "ons_setup": 1, "download": 1, "all": 1, "data": 1, "setup": 1, "cach": 1, "entity_id": 1, "entiti": 1, "kwarg": 1, "select": 1, "gsp": 1, "region": 1, "boundari": 1, "lad": 1, "paramet": 1, "string": 1, "specifi": 1, "type": 1, "from": 1, "i": 1, "e": 1, "iter": 1, "The": 1, "specif": 1, "geocode_constitu": 1, "function": 1, "collect": 1, "latlon": 1, "geocode_llsoa": 1, "llsoa_boundari": 1, "geocode_local_author": 1, "local": 1, "author": 1, "district": 1, "geocode_postcod": 1, "method": 1, "cpo": 1, "get_dno_region": 1, "get": 1, "dno": 1, "licens": 1, "area": 1, "eso": 1, "portal": 1, "return": 1, "dno_region": 1, "dict": 1, "whose": 1, "kei": 1, "ar": 1, "id": 1, "valu": 1, "tupl": 1, "contain": 1, "region_boundari": 1, "region_bound": 1, "shape": 1, "polygon": 1, "multipolygon": 1, "bound": 1, "xmin": 1, "ymin": 1, "xmax": 1, "ymax": 1, "dno_nam": 1, "name": 1, "longnam": 1, "get_gsp_region": 1, "20220314": 1, "gnode": 1, "api": 1, "get_llsoa_boundari": 1, "load": 1, "either": 1, "avail": 1, "els": 1, "fetch": 1, "raw": 1, "england": 1, "wale": 1, "packag": 1, "scotland": 1, "reverse_geocod": 1, "set": 1, "list": 1, "A": 1, "reverse_geocode_gsp": 1, "reverse_geocode_llsoa": 1, "dz": 1, "boolean": 1, "indic": 1, "whether": 1, "consid": 1, "datazon": 1, "pass": 1, "underli": 1}, "objects": {"geocode": [[1, 0, 1, "", "Geocoder"]], "geocode.Geocoder": [[1, 1, 1, "", "force_setup"], [1, 1, 1, "", "geocode"], [1, 1, 1, "", "geocode_constituency"], [1, 1, 1, "", "geocode_llsoa"], [1, 1, 1, "", "geocode_local_authority"], [1, 1, 1, "", "geocode_postcode"], [1, 1, 1, "", "get_dno_regions"], [1, 1, 1, "", "get_gsp_regions"], [1, 1, 1, "", "get_llsoa_boundaries"], [1, 1, 1, "", "reverse_geocode"], [1, 1, 1, "", "reverse_geocode_gsp"], [1, 1, 1, "", "reverse_geocode_llsoa"]]}, "objtypes": {"0": "py:class", "1": "py:method"}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "method", "Python method"]}, "titleterms": {"welcom": 0, "geocod": [0, 1], "": 0, "document": 0, "content": [0, 1], "indic": 0, "tabl": 0, "modul": 1}, "envversion": {"sphinx.domains.c": 2, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 8, "sphinx.domains.index": 1, "sphinx.domains.javascript": 2, "sphinx.domains.math": 2, "sphinx.domains.python": 3, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.viewcode": 1, "sphinx": 57}, "alltitles": {"Welcome to Geocode\u2019s documentation!": [[0, "welcome-to-geocode-s-documentation"]], "Contents:": [[0, null]], "Indices and tables": [[0, "indices-and-tables"]], "Modules": [[1, "modules"]], "Contents": [[1, "contents"]], "geocode": [[1, "geocode"]]}, "indexentries": {"geocoder (class in geocode)": [[1, "geocode.Geocoder"]], "force_setup() (geocode.geocoder method)": [[1, "geocode.Geocoder.force_setup"]], "geocode() (geocode.geocoder method)": [[1, "geocode.Geocoder.geocode"]], "geocode_constituency() (geocode.geocoder method)": [[1, "geocode.Geocoder.geocode_constituency"]], "geocode_llsoa() (geocode.geocoder method)": [[1, "geocode.Geocoder.geocode_llsoa"]], "geocode_local_authority() (geocode.geocoder method)": [[1, "geocode.Geocoder.geocode_local_authority"]], "geocode_postcode() (geocode.geocoder method)": [[1, "geocode.Geocoder.geocode_postcode"]], "get_dno_regions() (geocode.geocoder method)": [[1, "geocode.Geocoder.get_dno_regions"]], "get_gsp_regions() (geocode.geocoder method)": [[1, "geocode.Geocoder.get_gsp_regions"]], "get_llsoa_boundaries() (geocode.geocoder method)": [[1, "geocode.Geocoder.get_llsoa_boundaries"]], "reverse_geocode() (geocode.geocoder method)": [[1, "geocode.Geocoder.reverse_geocode"]], "reverse_geocode_gsp() (geocode.geocoder method)": [[1, "geocode.Geocoder.reverse_geocode_gsp"]], "reverse_geocode_llsoa() (geocode.geocoder method)": [[1, "geocode.Geocoder.reverse_geocode_llsoa"]]}})
\ No newline at end of file
diff --git a/docs/source/conf.py b/docs/source/conf.py
index fd3b0d4..31173fa 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -12,17 +12,17 @@
 #
 import os
 import sys
-sys.path.insert(0, os.path.abspath('../../geocode'))
+sys.path.insert(0, os.path.abspath("../../geocode"))
 
 
 # -- Project information -----------------------------------------------------
 
-project = 'Geocode'
-copyright = '2022, Jamie Taylor'
-author = 'Jamie Taylor'
+project = "Geocode"
+copyright = "2022, Jamie Taylor"
+author = "Jamie Taylor"
 
 # The full version, including alpha/beta/rc tags
-release = '0.10.5'
+release = "0.11.0"
 
 
 # -- General configuration ---------------------------------------------------
@@ -31,19 +31,19 @@
 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
 # ones.
 extensions = [
-    'sphinx.ext.autodoc',
-    'sphinx.ext.napoleon',
-    'sphinx.ext.doctest',
-    'sphinx.ext.coverage',
-    'sphinx.ext.autosummary',
-    'sphinx.ext.mathjax',
-    'sphinx.ext.viewcode',
-    'sphinx.ext.githubpages',
-    'numpydoc',
+    "sphinx.ext.autodoc",
+    "sphinx.ext.napoleon",
+    "sphinx.ext.doctest",
+    "sphinx.ext.coverage",
+    "sphinx.ext.autosummary",
+    "sphinx.ext.mathjax",
+    "sphinx.ext.viewcode",
+    "sphinx.ext.githubpages",
+    "numpydoc",
 ]
 
 # Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
+templates_path = ["_templates"]
 
 # List of patterns, relative to source directory, that match files and
 # directories to ignore when looking for source files.
@@ -56,13 +56,13 @@
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # a list of builtin themes.
 #
-# html_theme = 'alabaster'
-html_theme = 'sphinx_rtd_theme'
+# html_theme = "alabaster"
+html_theme = "sphinx_rtd_theme"
 
 # Add any paths that contain custom static files (such as style sheets) here,
 # relative to this directory. They are copied after the builtin static files,
 # so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
+html_static_path = ["_static"]
 
 # -- Custom ------------------------------------------------------------------
 numpydoc_show_class_members = False 
\ No newline at end of file
diff --git a/example.py b/example.py
index f66edf5..da052bf 100644
--- a/example.py
+++ b/example.py
@@ -8,7 +8,7 @@ def main():
         # Geocode some postcodes / addresses...
         postcodes = ["S3 7RH", "S3 7", "S3", None, None, "S3 7RH"]
         addresses = [None, None, None, "Hicks Building, Sheffield", "Hicks", "Hicks Building"]
-        results = geocoder.geocode(postcodes, addresses)
+        results = geocoder.geocode(postcodes, "postcode", address=addresses)
         for postcode, address, (lat, lon, status) in zip(postcodes, addresses, results):
             print(f"Postcode: `{postcode}`    Address: `{address}`")
             if status == 0:
diff --git a/geocode/__init__.py b/geocode/__init__.py
index a10206e..e69de29 100644
--- a/geocode/__init__.py
+++ b/geocode/__init__.py
@@ -1,8 +0,0 @@
-try:
-    #py2
-    from geocode import Geocoder, query_yes_no
-except:
-    #py3+
-    from geocode.geocode import Geocoder, query_yes_no
-
-__all__ = ["Geocoder", "query_yes_no"]
\ No newline at end of file
diff --git a/geocode/bng2latlon.py b/geocode/bng2latlon.py
index 1247123..3e8329f 100644
--- a/geocode/bng2latlon.py
+++ b/geocode/bng2latlon.py
@@ -3,6 +3,7 @@
 Load a list of lat/lons from a CSV file and reverse-geocode them to LLSOA.
 
 - Jamie Taylor <jamie.taylor@sheffield.ac.uk>
+- Ethan Jones <ejones18@sheffield.ac.uk>
 - First Authored: 2020-04-16
 """
 
@@ -12,13 +13,14 @@
 import time as TIME
 import pandas as pd
 
-from geocode import Geocoder, query_yes_no
+from geocode import Geocoder
+from utilities import query_yes_no, GenericException
 
 def parse_options():
     """Parse command line options."""
     parser = argparse.ArgumentParser(description=("This is a command line interface (CLI) for "
                                                   "the bng2latlon.py module"),
-                                     epilog="Jamie Taylor, 2020-06-12")
+                                     epilog="Jamie Taylor & Ethan Jones, 2020-06-12")
     parser.add_argument("-f", "--input-file", dest="infile", action="store", type=str,
                         required=True, metavar="</path/to/file>",
                         help="Specify a CSV file containing a list of eastings and northings to "
@@ -28,7 +30,7 @@ def parse_options():
                         required=True, metavar="</path/to/file>", help="Specify an output file.")
     options = parser.parse_args()
     if not os.path.isfile(options.infile):
-        raise Exception(f"The input file '{options.infile}' does not exist.")
+        raise GenericException(f"The input file '{options.infile}' does not exist.")
     if os.path.isfile(options.outfile):
         check = query_yes_no(f"The outfile '{options.outfile}' already exists, are you sure you "
                              "wish to overwrite?", "no")
@@ -42,7 +44,7 @@ def main():
     options = parse_options()
     with open(options.infile, "r") as fid:
         df = pd.read_csv(fid)
-    with Geocoder(progress_bar=True) as geo:
+    with Geocoder() as geo:
         lons, lats = geo._bng2latlon(df["eastings"].to_numpy(), df["northings"].to_numpy())
     df["latitude"] = lats
     df["longitude"] = lons
diff --git a/geocode/cache_manager.py b/geocode/cache_manager.py
new file mode 100644
index 0000000..e96645a
--- /dev/null
+++ b/geocode/cache_manager.py
@@ -0,0 +1,111 @@
+"""
+A generalised cache manager module for the Geocode library.
+
+- Ethan Jones <ejones18@sheffield.ac.uk>
+- Jamie Taylor <jamie.taylor@sheffield.ac.uk>
+- First Authored: 2022-10-19
+"""
+
+import os
+import pickle
+import glob
+import logging
+import errno
+from typing import Any, Optional
+
+SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
+
+import utilities as utils
+from version import __version__
+
+class CacheManager:
+    """Cache Python variables to files using Pickle."""
+    def __init__(self, cache_dir: Optional[str] = None):
+        """
+        Cache Python variables to files using Pickle.
+
+        Parameters
+        ----------
+        cache_dir : str
+            Path to a directory to use for writing cache files. If not set, or set to None, default
+            location will be `os.path.join(os.path.dirname(os.path.realpath(__file__)), "cache")`.
+        """
+        self.cache_dir = os.path.join(SCRIPT_DIR, "cache") if cache_dir is None else cache_dir
+        if not os.path.isdir(self.cache_dir):
+            raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), self.cache_dir)
+        self.version_string = __version__.replace(".", "-")
+
+    def _get_filename(self, label):
+        """Generate a filename using `label` and the current version string."""
+        file_name = f"{label}_{self.version_string}.p"
+        return os.path.join(self.cache_dir, file_name)
+
+    def retrieve(self, label: str) -> Any:
+        """
+        Retrieve a Python variable from a cache file using Pickle.
+
+        Parameters
+        ----------
+        label : str
+            Provide a unique label to use when identifying the data.
+
+        Returns
+        -------
+        Any or None
+            The Python variable that was stored in the cache file with `label`. Returns None if the
+            cache was not found.
+        """
+        cache_file = self._get_filename(label)
+        if os.path.isfile(cache_file):
+            with open(cache_file, "rb") as pickle_fid:
+                return pickle.load(pickle_fid)
+        return None
+
+    def write(self, label: str, data: Any):
+        """
+        Write a Python variable to a cache file using Pickle.
+
+        Parameters
+        ----------
+        label : str
+            Provide a unique label to use when identifying the data.
+        data : Any
+            Data to cache - must be a data type that can be pickled, see
+            https://docs.python.org/3/library/pickle.html#what-can-be-pickled-and-unpickled
+        """
+        cache_file = self._get_filename(label)
+        with open(cache_file, "wb") as pickle_fid:
+            pickle.dump(data, pickle_fid)
+
+    def clear(self,
+              delete_gmaps_cache: bool = False,
+              old_versions_only: bool = False) -> None:
+        """
+        Clear all cache files from the cache directory including from old versions.
+
+        Parameters
+        ----------
+        delete_gmaps_cache : boolean
+            Optional boolean deciding whether or not to clear the Google Maps data in cache.
+            Defaults to False.
+        old_versions_only : boolean
+            Optional boolean deciding whether or not to clear all cache files or just those
+            corresponding to previous versions of the Geocode library. Defaults to False.
+        """
+        logging.debug("Deleting cache files (delete_gmaps_cache=%s, old_versions_only=%s)",
+                      delete_gmaps_cache, old_versions_only)
+        cache_files = glob.glob(os.path.join(self.cache_dir, "*.p"))
+        for cache_file in cache_files:
+            if not delete_gmaps_cache and "gmaps" in cache_file:
+                continue
+            if old_versions_only:
+                if self.version_string in cache_file:
+                    continue
+            try:
+                os.remove(cache_file)
+            except FileNotFoundError:
+                pass
+            except:
+                raise Exception("Error deleting cache file: ", cache_file)
+            logging.debug("Deleted '%s'", cache_file)
+
diff --git a/geocode/cpo.py b/geocode/cpo.py
new file mode 100644
index 0000000..e7bda18
--- /dev/null
+++ b/geocode/cpo.py
@@ -0,0 +1,182 @@
+"""
+Manage data from Ordnance Survey's Code Point Open dataset.
+
+- Ethan Jones <ejones18@sheffield.ac.uk>
+- Jamie Taylor <jamie.taylor@sheffield.ac.uk>
+- First Authored: 2022-10-19
+"""
+
+import os
+import sys
+import pickle
+import zipfile
+import logging
+import pandas as pd
+import numpy as np
+from typing import Optional, Iterable, Tuple, Union, List, Dict
+
+import utilities as utils
+
+SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
+
+class CodePointOpen:
+    """The Code Point Open data manager for the Geocode class."""
+    def __init__(self, cache_manager):
+        """The Code Point Open data manager for the Geocode class."""
+        self.cache_manager = cache_manager
+        self.cpo_zipfile = os.path.join(SCRIPT_DIR, "code_point_open", "codepo_gb.zip")
+        self.cpo_cache_file = "code_point_open"
+        self.cpo = None
+        self.cache = None
+
+    def force_setup(self):
+        """
+        Function to setup the OS Code Point Open Database.
+        """
+        self._load(force_reload=True)
+
+    def _load(self, force_reload : bool = False):
+        """Load the OS Code Point Open Database, either from raw zip file or local cache."""
+        if self.cpo is not None and not force_reload:
+            return
+        cpo_cache_contents = self.cache_manager.retrieve(self.cpo_cache_file)
+        if cpo_cache_contents is not None and not force_reload:
+            logging.debug("Loading Code Point Open data from cache ('%s')", self.cpo_cache_file)
+            self.cpo = cpo_cache_contents
+            return
+        logging.info("Extracting the Code Point Open data (this only needs to be done once)")
+        if not zipfile.is_zipfile(self.cpo_zipfile):
+            raise utils.GenericException("Could not find the OS Code Point Open data: "
+                                   f"'{self.cpo_zipfile}'")
+        columns = ["Postcode", "Positional_quality_indicator", "Eastings", "Northings",
+                   "Country_code", "NHS_regional_HA_code", "NHS_HA_code", "Admin_county_code",
+                   "Admin_district_code", "Admin_ward_code"]
+        dtypes = {"Postcode": str, "Eastings": int, "Northings": int,
+                  "Positional_quality_indicator": int}
+        cpo = None
+        with zipfile.ZipFile(self.cpo_zipfile, "r") as cpo_zip:
+            for cpo_file in cpo_zip.namelist():
+                if "Data/CSV/" not in cpo_file:
+                    continue
+                with cpo_zip.open(cpo_file) as cpo_file_part:
+                    data = pd.read_csv(cpo_file_part, names=columns, dtype=dtypes,
+                                       usecols=["Postcode", "Positional_quality_indicator",
+                                                "Eastings", "Northings"])
+                    cpo = pd.concat([cpo, data]) if cpo is not None else data
+        cpo["Postcode"] = cpo["Postcode"].str.replace(" ", "", regex=False)
+        cpo["Postcode"] = cpo["Postcode"].str.upper()
+        nn_indices = cpo["Eastings"].notnull() & cpo["Positional_quality_indicator"] < 90
+        lons, lats = utils.bng2latlon(cpo.loc[nn_indices, ("Eastings")].to_numpy(),
+                                      cpo.loc[nn_indices, ("Northings")].to_numpy())
+        cpo.loc[nn_indices, "longitude"] = lons
+        cpo.loc[nn_indices, "latitude"] = lats
+        cpo["outward_postcode"] = cpo["Postcode"].str.slice(0, -3).str.strip()
+        cpo["inward_postcode"] = cpo["Postcode"].str.slice(-3).str.strip()
+        self.cache_manager.write(self.cpo_cache_file, cpo)
+        logging.info("Code Point Open extracted and pickled to '%s'", self.cpo_cache_file)
+        self.cpo = cpo
+        return
+    
+    def geocode_postcode(self,
+                         postcodes: Iterable[str]) -> List[Tuple[float, float, int]]:
+        """
+        Geocode several postcodes using CodePointOpen.
+
+        Parameters
+        ----------
+        `postcode` : iterable of strings
+            The postcodes (or partial postcodes) to geocode.
+
+        Returns
+        -------
+        list of tuples
+            The output list will align with the input postcodes, with each element being a
+            tuple containing: latitude (float), longitude (float), status code (int). The status
+            code shows the quality of the postcode lookup - use the class attribute
+            *Geocode.status_codes* (a dict) to get a string representation.
+
+        Notes
+        -----
+        The input iterables can be any Python object which can be interpreted by Pandas.DataFrame()
+        e.g. a list, tuple, Numpy array etc.
+        """
+        if self.cache is None:
+            self._load_cache()
+        results = []
+        logging.debug("Geocoding %s postcodes using Code Point Open", len(postcodes))
+        inputs = pd.DataFrame({"input_postcode": postcodes})
+        inputs["id"] = np.arange(inputs.shape[0])
+        results = inputs.merge(self.cache, how="left", on=["input_postcode"])
+        results_cols = ["latitude", "longitude", "match_status"]
+        if not results.latitude.isnull().any():
+            return results[results_cols].to_records(index=False)
+        new_postcodes = results.latitude.isnull()
+        inputs_ = inputs.loc[new_postcodes, ["input_postcode", "id"]]
+        inputs_["postcode_to_match"] = inputs_.input_postcode.str.strip().str.upper().str.replace(" ", "")
+        self._load()
+        results_ = inputs_.merge(self.cpo[["Postcode", "latitude", "longitude"]], how="left",
+                                 left_on="postcode_to_match", right_on="Postcode")
+        results_ = results_.groupby("id").agg(input_postcode=("input_postcode", "first"),
+                                              postcode_to_match=("postcode_to_match", "first"),
+                                              postcode_matched=("Postcode", "first"),
+                                              latitude=("latitude", np.nanmean),
+                                              longitude=("longitude", np.nanmean)).reset_index()
+        success = results_.postcode_matched.notnull()
+        results_.loc[success, "match_status"] = 1
+        results = pd.concat((results.loc[~new_postcodes], results_))
+        if results.latitude.isnull().any():
+            cpo_geocode_one = lambda p: self.geocode_one(p.input_postcode)
+            results.loc[~success, ["latitude", "longitude", "match_status"]] = \
+                results.loc[~success].apply(cpo_geocode_one, axis=1)
+        results["match_status"] = results.match_status.astype(int)
+        logging.debug("Adding postcodes to lookup")
+        cache = pd.concat([self.cache, 
+                           results[results.latitude.notnull()].drop(columns="id")],
+                           ignore_index=True)
+        self.cache = cache.drop_duplicates(subset=["input_postcode"], 
+                                           keep="last", ignore_index=True)
+        return results.sort_values("id")[results_cols].to_records(index=False)
+
+    def geocode_one(self, postcode: str) -> pd.Series:
+        """
+        Use the OS Code Point Open Database to geocode a postcode (or partial postcode using as much
+        accuracy as the partial code permits).
+
+        Parameters
+        ----------
+        `postcode` : string
+            The postcode (or partial postcode) to geocode.
+
+        Returns
+        -------
+        Pandas.Series
+            A Pandas Series with fields: input_postcode (str), latitude (float), longitude (float),
+            match_status (int). The status code shows the quality of the postcode lookup - use the
+            class attribute *self.status_codes* (a dict) to get a string representation.
+        """
+        self._load()
+        try:
+            postcode = postcode.upper()
+        except:
+            return pd.Series({"latitude": np.nan, "longitude": np.nan, "match_status": 0})
+        
+        if " " in postcode:
+            outward, inward = postcode.split(" ", 1)
+            myfilter = (self.cpo["outward_postcode"] == outward) & \
+                       (self.cpo["inward_postcode"].str.slice(0, len(inward)) == inward)
+        else:
+            outward = postcode
+            myfilter = self.cpo["outward_postcode"] == outward
+        match = self.cpo[myfilter].agg("mean", numeric_only=True)
+        if not np.isnan(match.latitude):
+            match["match_status"] = 2
+            return match[["latitude", "longitude", "match_status"]]
+        return pd.Series({"latitude": np.nan, "longitude": np.nan, "match_status": 0})
+
+    def _load_cache(self):
+        """Load the cache of prior postcodes for better performance."""
+        if self.cache is None:
+            self.cache = pd.DataFrame({"input_postcode": [], "input_address": [], "latitude": [],
+                                       "longitude": [], "match_status": []})
+        return
+    
\ No newline at end of file
diff --git a/geocode/geocode.py b/geocode/geocode.py
index 79067a4..6cfe97b 100644
--- a/geocode/geocode.py
+++ b/geocode/geocode.py
@@ -1,35 +1,24 @@
 #!/usr/bin/env python3
 
 """
-A lightweight geocoder that uses OS Code Point Open where possible for postcodes and GMaps API for
-everything else.
+Geocode various geographical entities including postcodes and LLSOAs. Reverse-geocode to LLSOA or GSP.
+
 - Jamie Taylor <jamie.taylor@sheffield.ac.uk>
 - Ethan Jones <ejones18@sheffield.ac.uk>
 - First Authored: 2019-10-08
 """
 
-__version__ = "0.10.5"
-
 import os
 import sys
-from typing import Optional, Iterable, Tuple, Union, List, Dict
 import logging
-import pickle
 import time as TIME
 import argparse
-import zipfile
+from typing import Optional
 from shutil import copyfile
-from io import StringIO
-import json
-import csv
-import glob
-import requests
-import numpy as np
-import pandas as pd
-import geopandas as gpd
-import googlemaps
+
+from typing import Optional, Iterable, Tuple, Union, List, Dict
 import pyproj
-import shapefile
+
 try:
     from shapely.geometry import shape, Point
     from shapely.ops import unary_union
@@ -41,16 +30,13 @@
 
 SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
 
-class GenericException(Exception):
-    """A generic exception for anticipated errors."""
-    def __init__(self, msg, err=None):
-        self.msg = msg
-        if err is not None:
-            self.msg += f"\n{repr(err)}"
-        logging.exception(self.msg)
-
-    def __str__(self):
-        return self.msg
+import utilities as utils
+import cpo
+import ngeso
+import ons_nrs
+import gmaps
+import cache_manager
+from version import __version__
 
 class Geocoder:
     """
@@ -58,8 +44,7 @@ class Geocoder:
     longitudes.
     """
     def __init__(self,
-                 progress_bar: bool = False,
-                 prefix: Optional[str] = None,
+                 cache_dir: Optional[str] = None,
                  gmaps_key_file: Optional[str] = None) -> None:
         """
         Geocode addresses, postcodes, LLSOAs or Constituencies or reverse-geocode latitudes and
@@ -67,81 +52,17 @@ def __init__(self,
 
         Parameters
         ----------
-        `progress_bar` : boolean
-            Optionally print a progress bar to stdout during iterations. Defaults to False.
-        `prefix` : string
-            Optionally add a prefix string to the progress bar.
+        `cache_dir` : string
+            Optionally specify a directory to use for caching.
         `gmaps_key_file` : string
             Path to an API key file for Google Maps Geocode API.
         """
-        self.prefix = prefix if prefix is not None else ""
-        version_string = __version__.replace(".", "-")
-        self.gmaps_dir = os.path.join(SCRIPT_DIR, "google_maps")
-        self.gmaps_cache_file = os.path.join(self.gmaps_dir, "gmaps_cache.p")
-        self.cpo_dir = os.path.join(SCRIPT_DIR, "code_point_open")
-        self.cache_dir = os.path.join(SCRIPT_DIR, "cache")
-        self.cpo_zipfile = os.path.join(self.cpo_dir, "codepo_gb.zip")
-        self.cpo_cache_file = os.path.join(self.cpo_dir,
-                                           f"code_point_open_{version_string}.p")
-        self.ons_dir = os.path.join(SCRIPT_DIR, "ons_nrs")
-        self.eso_dir = os.path.join(SCRIPT_DIR, "ngeso")
-        if not os.path.isdir(self.ons_dir):
-            os.mkdir(self.ons_dir)
-        if not os.path.isdir(self.eso_dir):
-            os.mkdir(self.eso_dir)
-        self.llsoa_cache_file = os.path.join(self.ons_dir,
-                                             f"llsoa_centroids_{version_string}.p")
-        self.llsoa_boundaries_cache_file = os.path.join(
-            self.ons_dir, f"llsoa_boundaries_{version_string}.p"
-        )
-        self.gsp_boundaries_20181031_cache_file = os.path.join(
-            self.eso_dir, f"gsp_boundaries_20181031_{version_string}.p"
-        )
-        self.gsp_boundaries_20220314_cache_file = os.path.join(
-            self.eso_dir, f"gsp_boundaries_20220314_{version_string}.p"
-        )
-        self.dno_boundaries_cache_file = os.path.join(
-            self.eso_dir, f"dno_boundaries_{version_string}.p"
-        )
-        self.gsp_lookup_20181031_cache_file = os.path.join(
-            self.eso_dir, f"gsp_lookup_20181031_{version_string}.p"
-        )
-        self.dz_lookup_cache_file = os.path.join(self.ons_dir,
-                                                 f"datazone_lookup_{version_string}.p")
-        self.nrs_zipfile = os.path.join(self.ons_dir, "nrs.zip")
-        self.pc_llsoa_lookup_cache_file = os.path.join(self.ons_dir,
-                                                       f"pc_llsoa_lookup_{version_string}.p")
-        self.pc_llsoa_zipfile = os.path.join(self.ons_dir,
-                                             "PCD_OA_LSOA_MSOA_LAD_MAY22_UK_LU.zip")
-        self.gov_dir = os.path.join(SCRIPT_DIR, "gov")
-        self.constituency_lookup_file = os.path.join(self.gov_dir,
-                                                     "constituency_centroids_Dec2020.psv")
-        self.constituency_cache_file = os.path.join(self.gov_dir,
-                                                    f"constituency_centroids_{version_string}.p")
-        self.lad_lookup_file = os.path.join(self.gov_dir, "lad_centroids_May2021.psv")
-        self.lad_cache_file = os.path.join(self.gov_dir, f"lad_centroids_{version_string}.p")
-        self.gmaps_key_file = gmaps_key_file if gmaps_key_file is not None \
-                                  else os.path.join(self.gmaps_dir, "key.txt")
-        self.gmaps_key = self._load_gmaps_key()
-        self.llsoa_lookup = None
-        self.llsoa_regions = None
-        self.gsp_regions = None
-        self.gsp_regions_20181031 = None
-        self.dno_regions = None
-        self.gsp_lookup_20181031 = None
-        self.llsoa_reverse_lookup = None
-        self.constituency_lookup = None
-        self.lad_lookup = None
-        self.pc_llsoa_lookup = None
-        self.dz_lookup = None
-        self.cpo = None
-        self.gmaps = None
-        self.gmaps_cache = None
-        self.timer = TIME.time()
-        self.progress_bar = progress_bar
-        self.cache_file = os.path.join(self.cache_dir, f"cache_{version_string}.p")
-        self.clear_cache(delete_gmaps_cache=False, old_versions_only=True)
-        self.cache = self._load_cache()
+        self.cache_manager = cache_manager.CacheManager(cache_dir)
+        self.cache_manager.clear(delete_gmaps_cache=False, old_versions_only=True)
+        self.cpo = cpo.CodePointOpen(self.cache_manager)
+        self.ngeso = ngeso.NationalGrid(self.cache_manager)
+        self.ons_nrs = ons_nrs.ONS_NRS(self.cache_manager)
+        self.gmaps = gmaps.GMaps(self.cache_manager, gmaps_key_file)
         self.status_codes = {
             0: "Failed",
             1: "Full match with Code Point Open",
@@ -154,352 +75,18 @@ def __enter__(self):
         """Context manager."""
         return self
 
-    def __exit__(self, *args):
-        """Context manager - flush GMaps cache on exit."""
-        self._flush_gmaps_cache()
-        self._flush_cache()
-
-    def clear_cache(self,
-                    delete_gmaps_cache: Optional[bool] = None,
-                    old_versions_only: bool = False) -> None:
-        """
-        Clear any cache files from the installation directory including from old versions.
-
-        Parameters
-        ----------
-        delete_gmaps_cache : boolean
-            Optional boolean deciding whether or not to clear the Google Maps data in cache.
-            Defaults to None, resulting in stdout/stdin prompts.
-        old_versions_only : boolean
-            Optional boolean deciding whether or not to clear all cache files or just those
-            corresponding to previous versions of the Geocode library. Defaults to False.
-        """
-        logging.debug("Deleting cache files (delete_gmaps_cache=%s, old_versions_only=%s)",
-                      delete_gmaps_cache, old_versions_only)
-        if delete_gmaps_cache is None:
-            delete_gmaps_cache = query_yes_no("Do you want to delete the GMaps cache?", "no")
-        if delete_gmaps_cache:
-            gmaps_cache_files = glob.glob(os.path.join(self.gmaps_dir, "gmaps_cache.p"))
-            for gmaps_cache_file in gmaps_cache_files:
-                os.remove(gmaps_cache_file)
-                logging.debug("Deleted '%s'", gmaps_cache_file)
-        cache_files = glob.glob(os.path.join(self.cache_dir, "cache_*.p")) + \
-                      glob.glob(os.path.join(self.cpo_dir, "code_point_open_*.p")) + \
-                      glob.glob(os.path.join(self.ons_dir, "llsoa_centroids_*.p")) + \
-                      glob.glob(os.path.join(self.ons_dir, "llsoa_boundaries_*.p")) + \
-                      glob.glob(os.path.join(self.eso_dir, "gsp_boundaries_*.p")) + \
-                      glob.glob(os.path.join(self.eso_dir, "dno_boundaries_*.p")) + \
-                      glob.glob(os.path.join(self.eso_dir, "gsp_lookup_*.p")) + \
-                      glob.glob(os.path.join(self.ons_dir, "datazone_lookup_*.p")) + \
-                      glob.glob(os.path.join(self.ons_dir, "pc_llsoa_lookup_*.p")) + \
-                      glob.glob(os.path.join(self.gov_dir, "constituency_centroids_*.p")) + \
-                      glob.glob(os.path.join(self.gov_dir, "lad_centroids_*.p"))
-        for cache_file in cache_files:
-            if old_versions_only and __version__.replace(".", "-") in cache_file:
-                continue
-            os.remove(cache_file)
-            logging.debug("Deleted '%s'", cache_file)
+    def __exit__(self, type, value, traceback):
+        """Context manager."""
+        pass
 
-    def force_setup(self):
+    def force_setup(self, ngeso_setup=True, cpo_setup=True, ons_setup=True):
         """Download all data and setup caches."""
-        self._load_code_point_open(force_reload=False)
-        self._load_llsoa_lookup()
-        self._load_llsoa_boundaries()
-        self._load_datazone_lookup()
-        self._load_postcode_llsoa_lookup()
-        self._load_constituency_lookup()
-        self._load_lad_lookup()
-        self.force_setup_eso()
-
-    def force_setup_eso(self):
-        """Download all ESO data and setup caches."""
-        self._load_gsp_boundaries_20220314()
-        self._load_dno_boundaries()
-
-    def _load_gmaps_key(self):
-        """Load the user's GMaps API key from installation directory."""
-        try:
-            with open(self.gmaps_key_file) as fid:
-                key = fid.read().strip()
-        except FileNotFoundError:
-            logging.warning("Failed to load Google Maps API key from '%s' - you will not be able "
-                            "to make new queries to the Google Maps API!", self.gmaps_key_file)
-            return None
-        return key
-
-    def _load_gmaps_cache(self):
-        """Load the cache of prior GMaps queries to avoid repeated API queries."""
-        if os.path.isfile(self.gmaps_cache_file):
-            with open(self.gmaps_cache_file, "rb") as pickle_fid:
-                return pickle.load(pickle_fid)
-        else:
-            return {}
-
-    def _load_cache(self):
-        """Load the cache of prior addresses/postcodes for better performance."""
-        if os.path.isfile(self.cache_file):
-            with open(self.cache_file, "rb") as pickle_fid:
-                return pickle.load(pickle_fid)
-        else:
-            return pd.DataFrame({"input_postcode": [], "input_address": [], "latitude": [],
-                                 "longitude": [], "match_status": []})
-
-    def _flush_gmaps_cache(self):
-        """Flush any new GMaps API queries to the GMaps cache."""
-        if self.gmaps_cache is not None:
-            with open(self.gmaps_cache_file, "wb") as pickle_fid:
-                pickle.dump(self.gmaps_cache, pickle_fid)
-
-    def _flush_cache(self):
-        """Flush any new address/postcode queries to the query cache."""
-        if self.cache is not None:
-            with open(self.cache_file, "wb") as pickle_fid:
-                pickle.dump(self.cache, pickle_fid)
-
-    def _load_code_point_open(self, force_reload=False):
-        """Load the OS Code Point Open Database, either from raw zip file or local cache."""
-        if os.path.isfile(self.cpo_cache_file) and not force_reload:
-            logging.debug("Loading Code Point Open data from cache ('%s')", self.cpo_cache_file)
-            with open(self.cpo_cache_file, "rb") as pickle_fid:
-                return pickle.load(pickle_fid)
-        logging.info("Extracting the Code Point Open data (this only needs to be done once)")
-        if not zipfile.is_zipfile(self.cpo_zipfile):
-            raise GenericException("Could not find the OS Code Point Open data: "
-                                   f"'{self.cpo_zipfile}'")
-        columns = ["Postcode", "Positional_quality_indicator", "Eastings", "Northings",
-                   "Country_code", "NHS_regional_HA_code", "NHS_HA_code", "Admin_county_code",
-                   "Admin_district_code", "Admin_ward_code"]
-        dtypes = {"Postcode": str, "Eastings": int, "Northings": int,
-                  "Positional_quality_indicator": int}
-        cpo = None
-        with zipfile.ZipFile(self.cpo_zipfile, "r") as cpo_zip:
-            for cpo_file in cpo_zip.namelist():
-                if "Data/CSV/" not in cpo_file:
-                    continue
-                with cpo_zip.open(cpo_file) as cpo_file_part:
-                    data = pd.read_csv(cpo_file_part, names=columns, dtype=dtypes,
-                                       usecols=["Postcode", "Positional_quality_indicator",
-                                                "Eastings", "Northings"])
-                    cpo = pd.concat([cpo, data]) if cpo is not None else data
-        cpo["Postcode"] = cpo["Postcode"].str.replace(" ", "", regex=False)
-        cpo["Postcode"] = cpo["Postcode"].str.upper()
-        nn_indices = cpo["Eastings"].notnull() & cpo["Positional_quality_indicator"] < 90
-        lons, lats = self._bng2latlon(cpo.loc[nn_indices, ("Eastings")].to_numpy(),
-                                      cpo.loc[nn_indices, ("Northings")].to_numpy())
-        cpo.loc[nn_indices, "longitude"] = lons
-        cpo.loc[nn_indices, "latitude"] = lats
-        cpo["outward_postcode"] = cpo["Postcode"].str.slice(0, -3).str.strip()
-        cpo["inward_postcode"] = cpo["Postcode"].str.slice(-3).str.strip()
-        with open(self.cpo_cache_file, "wb") as pickle_fid:
-            pickle.dump(cpo, pickle_fid)
-        logging.info("Code Point Open extracted and pickled to '%s'", self.cpo_cache_file)
-        return cpo
-
-    def _fetch_from_api(self, url):
-        """Generic function to GET data from web API with retries."""
-        retries = 0
-        while retries < 3:
-            try:
-                response = requests.get(url)
-                response.raise_for_status()
-                if response.status_code != 200:
-                    retries += 1
-                    continue
-                return 1, response
-            except:
-                retries += 1
-        return 0, None
-
-    def _load_llsoa_lookup(self):
-        """Load the lookup of LLSOA -> Population Weighted Centroid."""
-        if os.path.isfile(self.llsoa_cache_file):
-            logging.debug("Loading LLSOA lookup from cache ('%s')", self.llsoa_cache_file)
-            with open(self.llsoa_cache_file, "rb") as pickle_fid:
-                return pickle.load(pickle_fid)
-        logging.info("Extracting the ONS and NRS LLSOA centroids data (this only needs to be done "
-                     "once)")
-        # ons_url = "https://opendata.arcgis.com/datasets/b7c49538f0464f748dd7137247bbc41c_0.geojson"
-        ons_url = "https://services1.arcgis.com/ESMARspQHYMw9BZ9/arcgis/rest/services/LSOA_Dec_2011_PWC_in_England_and_Wales_2022/FeatureServer/0/query?outFields=*&where=1%3D1&f=geojson"
-        success, api_response = self._fetch_from_api(ons_url)
-        if success:
-            raw = json.loads(api_response.text)
-            engwales_lookup = {f["properties"]["lsoa11cd"]:
-                               tuple(f["geometry"]["coordinates"][::-1])
-                               for f in raw["features"]}
-        else:
-            raise GenericException("Encountered an error while extracting LLSOA data from ONS API.")
-        codes, eastings, northings = [], [], []
-        datazones, dzeastings, dznorthings = [], [], []
-        with zipfile.ZipFile(self.nrs_zipfile, "r") as nrs_zip:
-            with nrs_zip.open("OutputArea2011_PWC_WGS84.csv", "r") as fid:
-                next(fid)
-                for line in fid:
-                    _, _, code, _, easting, northing = line.decode('UTF-8').strip().split(",")
-                    codes.append(code)
-                    eastings.append(float(easting))
-                    northings.append(float(northing))
-            with nrs_zip.open("SG_DataZone_Cent_2011.csv") as fid:
-                next(fid)
-                contents = [l.decode('UTF-8') for l in fid.readlines()]
-                for line in csv.reader(contents, quotechar="\"", delimiter=",",
-                                       quoting=csv.QUOTE_ALL, skipinitialspace=True):
-                    datazone, _, _, _, _, dzeast, dznorth = line
-                    datazones.append(datazone)
-                    dzeastings.append(float(dzeast.strip("\"")))
-                    dznorthings.append(float(dznorth.strip("\"")))
-        lons, lats = self._bng2latlon(eastings, northings)
-        dzlons, dzlats = self._bng2latlon(dzeastings, dznorthings)
-        scots_lookup = {code: (lat, lon) for code, lon, lat in zip(codes, lons, lats)}
-        scots_dz_lookup = {dz: (lat, lon) for dz, lon, lat in zip(datazones, dzlons, dzlats)}
-        llsoa_lookup = {**engwales_lookup, **scots_lookup, **scots_dz_lookup}
-        with open(self.llsoa_cache_file, "wb") as pickle_fid:
-            pickle.dump(llsoa_lookup, pickle_fid)
-        logging.info("LSOA centroids extracted and pickled to '%s'", self.llsoa_cache_file)
-        return llsoa_lookup
-
-    def _load_llsoa_boundaries(self):
-        """
-        Load the LLSOA boundaries, either from local cache if available, else fetch from raw API
-        (England and Wales) and packaged data (Scotland).
-        """
-        if os.path.isfile(self.llsoa_boundaries_cache_file):
-            logging.debug("Loading LLSOA boundaries from cache ('%s')",
-                          self.llsoa_boundaries_cache_file)
-            with open(self.llsoa_boundaries_cache_file, "rb") as pickle_fid:
-                return pickle.load(pickle_fid)
-        logging.info("Extracting the LLSOA boundary data from ONS and NRS (this only needs to be "
-                     "done once)")
-        # ons_url = "https://opendata.arcgis.com/datasets/007577eeb8e34c62a1844df090a93128_0.geojson"
-        # ons_url = "https://opendata.arcgis.com/datasets/f213065139e3441195803b4155e71e00_0.geojson"
-        # ons_url = "https://opendata.arcgis.com/datasets/e0b761d78e51491d84a3df33dff044c7_0.geojson"
-        ons_url = "https://services1.arcgis.com/ESMARspQHYMw9BZ9/arcgis/rest/services/Lower_Layer_Super_Output_Areas_Dec_2011_Boundaries_Full_Extent_BFE_EW_V3_2022/FeatureServer/0/query?outFields=*&where=1%3D1&f=geojson"
-        # Loading NRS data from URL abandoned due to need for re-projection
-        # nrs_url = "https://www.nrscotland.gov.uk/files/geography/output-area-2011-eor.zip"
-        nrs_shp_file = "OutputArea2011_EoR_WGS84.shp"
-        nrs_dbf_file = "OutputArea2011_EoR_WGS84.dbf"
-        success, api_response = self._fetch_from_api(ons_url)
-        if success:
-            raw = json.loads(api_response.text)
-            engwales_regions = {f["properties"]["LSOA11CD"]: shape(f["geometry"]).buffer(0)
-                                for f in raw["features"]}
-        else:
-            raise GenericException("Encountered an error while extracting LLSOA data from ONS API.")
-        with zipfile.ZipFile(self.nrs_zipfile, "r") as nrs_zip:
-            with nrs_zip.open(nrs_shp_file, "r") as shp:
-                with nrs_zip.open(nrs_dbf_file, "r") as dbf:
-                    sf = shapefile.Reader(shp=shp, dbf=dbf)
-                    scots_regions = {sr.record[1]: shape(sr.shape.__geo_interface__).buffer(0)
-                                     for sr in sf.shapeRecords()}
-        llsoa_regions = {**engwales_regions, **scots_regions}
-        llsoa_regions = {llsoacd: (llsoa_regions[llsoacd], llsoa_regions[llsoacd].bounds)
-                         for llsoacd in llsoa_regions}
-        with open(self.llsoa_boundaries_cache_file, "wb") as pickle_fid:
-            pickle.dump(llsoa_regions, pickle_fid)
-        logging.info("LSOA boundaries extracted and pickled to '%s'",
-                     self.llsoa_boundaries_cache_file)
-        return llsoa_regions
-
-    def _load_gsp_boundaries_20181031(self):
-        """
-        Load the 20181031 GSP / GNode boundaries, either from local cache if available, else fetch
-        from ESO Data Portal API.
-        """
-        if os.path.isfile(self.gsp_boundaries_20181031_cache_file):
-            logging.debug("Loading 20181031 GSP boundaries from cache ('%s')",
-                          self.gsp_boundaries_20181031_cache_file)
-            with open(self.gsp_boundaries_20181031_cache_file, "rb") as pickle_fid:
-                return pickle.load(pickle_fid)
-        logging.info("Extracting the 20181031 GSP boundary data from NGESO's Data Portal API (this "
-                     "only needs to be done once)")
-        eso_url = "http://data.nationalgrideso.com/backend/dataset/2810092e-d4b2-472f-b955-d8bea01f9ec0/resource/a3ed5711-407a-42a9-a63a-011615eea7e0/download/gsp_regions_20181031.geojson"
-        success, api_response = self._fetch_from_api(eso_url)
-        if success:
-            raw = json.loads(api_response.text)
-            gsp_regions = {}
-            for f in raw["features"]:
-                region_id = f["properties"]["RegionID"]
-                if region_id not in gsp_regions:
-                    gsp_regions[region_id] = shape(f["geometry"]).buffer(0)
-                else: # Sometimes a region is in multiple pieces due to PES boundary e.g. Axminster
-                    gsp_regions[region_id] = unary_union([gsp_regions[region_id],
-                                                          shape(f["geometry"]).buffer(0)])
-        else:
-            raise GenericException("Encountered an error while extracting GSP region data from ESO "
-                                   "API.")
-        gsp_regions = {region_id: (gsp_regions[region_id], gsp_regions[region_id].bounds)
-                       for region_id in gsp_regions}
-        with open(self.gsp_boundaries_20181031_cache_file, "wb") as pickle_fid:
-            pickle.dump(gsp_regions, pickle_fid)
-        logging.info("20181031 GSP boundaries extracted and pickled to '%s'",
-                     self.gsp_boundaries_20181031_cache_file)
-        return gsp_regions
-
-    def _load_gsp_boundaries_20220314(self):
-        """
-        Load the 20220314 GSP / GNode boundaries, either from local cache if available, else fetch
-        from ESO Data Portal API.
-        """
-        if os.path.isfile(self.gsp_boundaries_20220314_cache_file):
-            logging.debug("Loading 20220314 GSP boundaries from cache ('%s')",
-                          self.gsp_boundaries_20220314_cache_file)
-            with open(self.gsp_boundaries_20220314_cache_file, "rb") as pickle_fid:
-                return pickle.load(pickle_fid)
-        logging.info("Extracting the 20220314 GSP boundary data from NGESO's Data Portal API (this "
-                     "only needs to be done once)")
-        eso_url = "https://data.nationalgrideso.com/backend/dataset/2810092e-d4b2-472f-b955-d8bea01f9ec0/resource/08534dae-5408-4e31-8639-b579c8f1c50b/download/gsp_regions_20220314.geojson"
-        success, api_response = self._fetch_from_api(eso_url)
-        if success:
-            raw = json.loads(api_response.text)
-            gsp_regions = gpd.GeoDataFrame.from_features(raw["features"],
-                                                         crs=raw["crs"]["properties"]["name"])
-            gsp_regions.geometry = gsp_regions.buffer(0)
-        else:
-            raise GenericException("Encountered an error while extracting GSP region data from ESO "
-                                   "API.")
-        ### For backwards compatibility pending https://github.com/SheffieldSolar/Geocode/issues/6
-        gsp_regions["bounds"] = gsp_regions.bounds.apply(tuple, axis=1)
-        gsp_regions_dict = gsp_regions.set_index(["GSPs", "GSPGroup"]).to_dict("index")
-        for r in gsp_regions_dict:
-            gsp_regions_dict[r] = tuple(gsp_regions_dict[r].values())
-        ######
-        with open(self.gsp_boundaries_20220314_cache_file, "wb") as pickle_fid:
-            pickle.dump(gsp_regions_dict, pickle_fid)
-        logging.info("20220314 GSP boundaries extracted and pickled to '%s'",
-                     self.gsp_boundaries_20220314_cache_file)
-        return gsp_regions_dict
-
-    def _load_dno_boundaries(self):
-        """
-        Load the DNO License Area boundaries, either from local cache if available, else fetch from
-        ESO Data Portal API.
-        """
-        if os.path.isfile(self.dno_boundaries_cache_file):
-            logging.debug("Loading DNO boundaries from cache ('%s')",
-                          self.dno_boundaries_cache_file)
-            with open(self.dno_boundaries_cache_file, "rb") as pickle_fid:
-                return pickle.load(pickle_fid)
-        logging.info("Extracting the DNO License Area boundary data from NGESO's Data Portal API "
-                     "(this only needs to be done once)")
-        eso_url = "http://data.nationalgrideso.com/backend/dataset/0e377f16-95e9-4c15-a1fc-49e06a39cfa0/resource/e96db306-aaa8-45be-aecd-65b34d38923a/download/dno_license_areas_20200506.geojson"
-        success, api_response = self._fetch_from_api(eso_url)
-        if success:
-            raw = json.loads(api_response.text)
-            dno_regions = {}
-            dno_names = {}
-            for f in raw["features"]:
-                region_id = f["properties"]["ID"]
-                dno_regions[region_id] = shape(f["geometry"]).buffer(0)
-                dno_names[region_id] = (f["properties"]["Name"], f["properties"]["LongName"])
-        else:
-            raise GenericException("Encountered an error while extracting DNO License Area "
-                                   "boundary data from ESO API.")
-        dno_regions = {region_id: (dno_regions[region_id], dno_regions[region_id].bounds)
-                       for region_id in dno_regions}
-        with open(self.dno_boundaries_cache_file, "wb") as pickle_fid:
-            pickle.dump((dno_regions, dno_names), pickle_fid)
-        logging.info("DNO License Area boundaries extracted and pickled to '%s'",
-                     self.dno_boundaries_cache_file)
-        return dno_regions, dno_names
+        if ngeso_setup:
+            self.ngeso.force_setup()
+        if cpo_setup:
+            self.cpo.force_setup()
+        if ons_setup:
+            self.ons_nrs.force_setup()
 
     def get_dno_regions(self):
         """
@@ -515,651 +102,174 @@ def get_dno_regions(self):
             Dict whose keys are the region IDs and whose values are a tuple containing:
             (Name, LongName).
         """
-        if self.dno_regions is None:
-            self.dno_regions = self._load_dno_boundaries()
-        return self.dno_regions
-
-    def _load_gsp_lookup_20181031(self):
-        """Load the lookup of Region <-> GSP <-> GNode."""
-        if os.path.isfile(self.gsp_lookup_20181031_cache_file):
-            with open(self.gsp_lookup_20181031_cache_file, "rb") as pickle_fid:
-                return pickle.load(pickle_fid)
-        logging.info("Extracting the GSP lookup data from NGESO's Data Portal API (this only needs "
-                     "to be done once)")
-        eso_url = "http://data.nationalgrideso.com/backend/dataset/2810092e-d4b2-472f-b955-d8bea01f9ec0/resource/bbe2cc72-a6c6-46e6-8f4e-48b879467368/download/gsp_gnode_directconnect_region_lookup.csv"
-        success, api_response = self._fetch_from_api(eso_url)
-        if success:
-            f = StringIO(str(api_response.text).replace("\ufeff", "")) # Remove BOM character
-            gsp_lookup = pd.read_csv(f)
-            gsp_lookup = gsp_lookup.loc[gsp_lookup.region_id.notnull()].convert_dtypes()
-        else:
-            raise GenericException("Encountered an error while extracting GSP lookup data from ESO "
-                                   "API.")
-        with open(self.gsp_lookup_20181031_cache_file, "wb") as pickle_fid:
-            pickle.dump(gsp_lookup, pickle_fid)
-        logging.info("GSP lookup extracted and pickled to '%s'", self.gsp_lookup_20181031_cache_file)
-        return gsp_lookup
-
-    def _load_datazone_lookup(self):
-        """Load a lookup of Scottish LLSOA <-> Datazone."""
-        if os.path.isfile(self.dz_lookup_cache_file):
-            logging.debug("Loading LLSOA<->Datazone lookup from cache ('%s')",
-                          self.dz_lookup_cache_file)
-            with open(self.dz_lookup_cache_file, "rb") as pickle_fid:
-                return pickle.load(pickle_fid)
-        with zipfile.ZipFile(self.nrs_zipfile, "r") as nrs_zip:
-            with nrs_zip.open("OA_DZ_IZ_2011.csv", "r") as fid:
-                dz_lookup = pd.read_csv(fid)
-        dz_lookup.set_index("OutputArea2011Code", inplace=True)
-        dz_lookup.drop(columns=["IntermediateZone2011Code"], inplace=True)
-        dz_lookup = dz_lookup.to_dict()["DataZone2011Code"]
-        with open(self.dz_lookup_cache_file, "wb") as pickle_fid:
-            pickle.dump(dz_lookup, pickle_fid)
-        return dz_lookup
-
-    def _load_postcode_llsoa_lookup(self):
-        """Load a lookup of postcode <-> LLSOA."""
-        if os.path.isfile(self.pc_llsoa_lookup_cache_file):
-            logging.debug("Loading postcode<->LLSOA lookup from cache ('%s')",
-                          self.pc_llsoa_lookup_cache_file)
-            with open(self.pc_llsoa_lookup_cache_file, "rb") as pickle_fid:
-                return pickle.load(pickle_fid)
-        pc_llsoa_lookup = pd.read_csv(self.pc_llsoa_zipfile, dtype=str)
-        pc_llsoa_lookup["postcode"] = pc_llsoa_lookup.pcds.str.strip().str.upper()\
-                                                                      .str.replace(" ", "")
-        with open(self.pc_llsoa_lookup_cache_file, "wb") as pickle_fid:
-            pickle.dump(pc_llsoa_lookup, pickle_fid)
-        return pc_llsoa_lookup
-
-    def _load_constituency_lookup(self):
-        """Load a lookup of UK constituency -> Geospatial Centroid."""
-        if os.path.isfile(self.constituency_cache_file):
-            logging.debug("Loading Constituency lookup from cache ('%s')",
-                          self.constituency_cache_file)
-            with open(self.constituency_cache_file, "rb") as pickle_fid:
-                return pickle.load(pickle_fid)
-        logging.info("Extracting the Constituency Centroids data (this only needs to be done "
-                     "once)")
-        constituency_lookup = {}
-        with open(self.constituency_lookup_file) as fid:
-            for line in fid:
-                _, name, longitude, latitude = line.strip().split("|")
-                match_str = name.strip().replace(" ", "").replace(",", "").lower()
-                constituency_lookup[match_str] = (float(latitude), float(longitude))
-        with open(self.constituency_cache_file, "wb") as pickle_fid:
-            pickle.dump(constituency_lookup, pickle_fid)
-        logging.info("Constituency lookup extracted and pickled to '%s'",
-                     self.constituency_cache_file)
-        return constituency_lookup
-
-    def _load_lad_lookup(self):
-        """Load a lookup of UK Local Authority District -> Geospatial Centroid."""
-        if os.path.isfile(self.lad_cache_file):
-            logging.debug("Loading Local Authority District lookup from cache ('%s')",
-                          self.lad_cache_file)
-            with open(self.lad_cache_file, "rb") as pickle_fid:
-                return pickle.load(pickle_fid)
-        logging.info("Extracting the Local Authority District Centroids data (this only needs to "
-                     "be done once)")
-        lad_lookup = {}
-        with open(self.lad_lookup_file) as fid:
-            for line in fid:
-                _, name, longitude, latitude = line.strip().split("|")
-                match_str = name.strip().replace(" ", "").replace(",", "").lower()
-                lad_lookup[match_str] = (float(latitude), float(longitude))
-        with open(self.lad_cache_file, "wb") as pickle_fid:
-            pickle.dump(lad_lookup, pickle_fid)
-        logging.info("Local Authority District lookup extracted and pickled to '%s'",
-                     self.lad_cache_file)
-        return lad_lookup
-
-    def cpo_geocode(self, pcs: pd.DataFrame) -> pd.DataFrame:
-        """
-        Geocode multiple postcodes using Code Point Open.
-
-        Parameters
-        ----------
-        `pcs` : Pandas.DataFrame
-            A Pandas DataFrame containing the postcodes (or partial postcodes) to geocode. Must
-            contain the column 'input_postcode'.
-
-        Returns
-        -------
-        Pandas.DataFrame
-            A Pandas DataFrame with columns: input_postcode (str), latitude (float), longitude
-            (float), match_status (int). The `match_status` shows the quality of the postcode lookup
-            - use the class attribute *self.status_codes* (a dict) to get a string representation.
-        """
-        pcs["id"] = np.arange(pcs.shape[0])
-        pcs["postcode_to_match"] = pcs.input_postcode.str.strip().str.upper().str.replace(" ", "")
-        if self.cpo is None:
-            self.cpo = self._load_code_point_open()
-        pcs = pcs.merge(self.cpo[["Postcode", "latitude", "longitude"]], how="left",
-                        left_on="postcode_to_match", right_on="Postcode")
-        pcs = pcs.groupby("id").agg(input_postcode=("input_postcode", "first"),
-                                    postcode_to_match=("postcode_to_match", "first"),
-                                    postcode_matched=("Postcode", "first"),
-                                    latitude=("latitude", np.nanmean),
-                                    longitude=("longitude", np.nanmean)).reset_index()
-        success = pcs.postcode_matched.notnull()
-        pcs.loc[success, "match_status"] = 1
-        if not pcs.postcode_matched.isnull().values.any():
-            return pcs[["input_postcode", "latitude", "longitude", "match_status"]]
-        cpo_geocode_one = lambda p: self.cpo_geocode_one(p.input_postcode)
-        pcs.loc[~success, ["latitude", "longitude", "match_status"]] = \
-            pcs.loc[~success].apply(cpo_geocode_one, axis=1)
-        return pcs[["input_postcode", "latitude", "longitude", "match_status"]]
+        return self.ngeso._load_dno_boundaries()
 
-    def cpo_geocode_one(self, postcode: str) -> pd.Series:
+    def get_gsp_regions(self):
         """
-        Use the OS Code Point Open Database to geocode a postcode (or partial postcode using as much
-        accuracy as the partial code permits).
-
-        Parameters
-        ----------
-        `postcode` : string
-            The postcode (or partial postcode) to geocode.
-
-        Returns
-        -------
-        Pandas.Series
-            A Pandas Series with fields: input_postcode (str), latitude (float), longitude (float),
-            match_status (int). The status code shows the quality of the postcode lookup - use the
-            class attribute *self.status_codes* (a dict) to get a string representation.
+        Get the 20220314 GSP / GNode boundaries from the ESO Data Portal API.
         """
-        if self.cpo is None:
-            self.cpo = self._load_code_point_open()
-        postcode = postcode.upper()
-        # match = self.cpo[self.cpo["Postcode"] == postcode.replace(" ", "")].agg("mean",
-                                                                                # numeric_only=True)
-        # if not np.isnan(match.latitude):
-            # match["match_status"] = 1
-            # return match[["latitude", "longitude", "match_status"]]
-        if " " in postcode:
-            outward, inward = postcode.split(" ", 1)
-            myfilter = (self.cpo["outward_postcode"] == outward) & \
-                       (self.cpo["inward_postcode"].str.slice(0, len(inward)) == inward)
-        else:
-            outward = postcode
-            myfilter = self.cpo["outward_postcode"] == outward
-        match = self.cpo[myfilter].agg("mean", numeric_only=True)
-        if not np.isnan(match.latitude):
-            match["match_status"] = 2
-            return match[["latitude", "longitude", "match_status"]]
-        return pd.Series({"latitude": np.nan, "longitude": np.nan, "match_status": 0})
+        return self.ngeso._load_gsp_boundaries_20220314()
 
-    def gmaps_geocode_one(self, postcode: str, address: Optional[str] = None) -> pd.Series:
+    def get_llsoa_boundaries(self):
         """
-        Use the GMaps API to geocode a postcode (or partial postcode) and/or an address.
-
-        Parameters
-        ----------
-        `postcode` : string
-            The postcode (or partial postcode) to geocode.
-        `address` : string
-            The address to geocode.
-
-        Returns
-        -------
-        Pandas.Series
-            A Pandas Series with fields: input_postcode (str), latitude (float), longitude (float),
-            match_status (int). The status code shows the quality of the postcode lookup - use the
-            class attribute *self.status_codes* (a dict) to get a string representation.
+        Load the LLSOA boundaries, either from local cache if available, else fetch from raw API
+        (England and Wales) and packaged data (Scotland).
         """
-        if postcode is None and address is None:
-            raise GenericException("You must pass either postcode or address, or both.")
-        if self.gmaps_cache is None:
-            self.gmaps_cache = self._load_gmaps_cache()
-        sep = ", " if address and postcode else ""
-        postcode = postcode if postcode is not None else ""
-        address = address if address is not None else ""
-        search_term = f"{address}{sep}{postcode}"
-        logging.debug("Querying Google Maps Geocoder API for '%s'", search_term)
-        if search_term in self.gmaps_cache:
-            geocode_result = self.gmaps_cache[search_term]
-        else:
-            if self.gmaps_key is None:
-                return pd.Series({"latitude": np.nan, "longitude": np.nan, "match_status": 0})
-            if self.gmaps is None:
-                self.gmaps = googlemaps.Client(key=self.gmaps_key)
-            geocode_result = self.gmaps.geocode(search_term, region="uk")
-            self.gmaps_cache[search_term] = geocode_result
-        if not geocode_result or len(geocode_result) > 1:
-            return pd.Series({"latitude": np.nan, "longitude": np.nan, "match_status": 0})
-        geometry = geocode_result[0]["geometry"]
-        ok_loc_types = ["ROOFTOP", "GEOMETRIC_CENTER"]
-        if geometry["location_type"] in ok_loc_types or \
-            geocode_result[0]["types"] == ["postal_code"]:
-            return pd.Series({"latitude": geometry["location"]["lat"],
-                              "longitude": geometry["location"]["lng"],
-                              "match_status": 3})
-        return pd.Series({"latitude": np.nan, "longitude": np.nan, "match_status": 0})
+        return self.ons_nrs._load_llsoa_boundaries()
 
-    def geocode(self,
-                postcodes: Optional[Iterable[str]] = None,
-                addresses: Optional[Iterable[str]] = None) -> List[Tuple[float, float, int]]:
+    def geocode_llsoa(self, llsoa_boundaries):
         """
-        Geocode several postcodes and/or addresses.
-
+        Function to geocode a collection of llsoa boundaries into latlons.
+        
         Parameters
         ----------
-        `postcodes` : iterable of strings
-            The postcodes (or partial postcodes) to geocode. Must align with *addresses* if both are
-            passed.
-        `addresses` : iterable of strings
-            The addresses to geocode. Must align with *postcodes* if both are passed. If addresses
-            are passed, the GMaps API will be used since OS Code Point Open does not provide address
-            lookup.
-
-        Returns
-        -------
-        list of tuples
-            The output list will align with the input postcodes/addresses, with each element being a
-            tuple containing: latitude (float), longitude (float), status code (int). The status
-            code shows the quality of the postcode lookup - use the class attribute
-            *self.status_codes* (a dict) to get a string representation.
-
-        Notes
-        -----
-        The input iterables can be any Python object which can be interpreted by Pandas.DataFrame()
-        e.g. a list, tuple, Numpy array etc.
-        If you pass only postcodes, this method will priotiise OS Code Point Open as the geocoder.
-        If a postcode fails to geocode using OS CPO, it will be geocoded with GMaps.
+        `llsoa_boundaries` : iterable of strings
+            Specific llsoa boundaries to geocode to latlons
         """
-        results = []
-        if postcodes is None and addresses is None:
-            raise GenericException("You must pass either postcodes or addresses, or both.")
-        postcodes = [None for a in addresses] if postcodes is None else list(postcodes)
-        addresses = [None for p in postcodes] if addresses is None else list(addresses)
-        logging.debug("Geocoding %s postcodes (%s addresses)", len(postcodes), len(addresses))
-        inputs = pd.DataFrame({"input_postcode": postcodes, "input_address": addresses})
-        inputs["id"] = np.arange(inputs.shape[0])
-        results = inputs.merge(self.cache, how="left", on=["input_postcode", "input_address"])
-        results_cols = ["latitude", "longitude", "match_status"]
-        if not results.latitude.isnull().any():
-            return results[results_cols].to_records(index=False)
-        use_cpo = results.latitude.isnull() & results.input_address.isnull()
-        if use_cpo.any():
-            logging.debug("Geocoding %s postcodes using Code Point Open", use_cpo.sum())
-            results.loc[use_cpo, results_cols] = \
-                self.cpo_geocode(inputs.loc[use_cpo, ["input_postcode"]])[results_cols]
-            logging.debug("Successfully geocoded %s postcodes using Code Point Open",
-                          (results.latitude.notnull()).sum())
-        use_gmaps = results.latitude.isnull()
-        if use_gmaps.any():
-            logging.debug("Geocoding %s postcodes/addresses using Google Maps", use_gmaps.sum())
-            gmaps_geocode = lambda i: self.gmaps_geocode_one(i.input_postcode, i.input_address)
-            results.loc[use_gmaps, results_cols] = \
-                inputs.loc[use_gmaps].apply(gmaps_geocode, axis=1)[results_cols]
-            logging.debug("Successfully geocoded %s postcodes/addresses using Google Maps",
-                          (use_gmaps & results.latitude.notnull()).sum())
-        results["match_status"] = results.match_status.astype(int)
-        logging.debug("Adding postcodes/addresses to cache")
-        cache = pd.concat([self.cache, results[results.latitude.notnull()].drop(columns="id")],
-                          ignore_index=True)
-        self.cache = cache.drop_duplicates(subset=["input_postcode", "input_address"], keep="last",
-                                           ignore_index=True)
-        return results[results_cols].to_records(index=False)
+        return self.geocode(llsoa_boundaries, "llsoa")
 
-    def geocode_one(self,
-                    postcode: Optional[str] = None,
-                    address: Optional[str] = None) -> Tuple[float, float, int]:
+    def reverse_geocode_llsoa(self, latlons, dz=True):
         """
-        Geocode a single postcode and/or address.
-
+        Function to reverse geocode a collection of latlons into llsoa boundaries.
+        
         Parameters
         ----------
-        `postcode` : string
-            The postcode (or partial postcode) to geocode.
-        `address` : string
-            The address to geocode. If address is passed, the GMaps API will be used since OS Code
-            Point Open does not provide address lookup.
-
-        Returns
-        -------
-        tuple
-            A tuple containing: latitude (float), longitude (float), status code (int). The status
-            code shows the quality of the postcode lookup - use the class attribute
-            *self.status_codes* (a dict) to get a string representation.
-
-        Notes
-        -----
-        If you pass only postcode, this method will priotiise OS Code Point Open as the geocoder.
-        If a postcode fails to geocode using OS CPO, it will be geocoded with GMaps.
+        `latlons` : iterable of strings
+            Specific latlons to geocode to llsoa boundaries.
+        
+        `dz` : Boolean
+            Indication whether to consider datazones
         """
-        if postcode is None and address is None:
-            raise GenericException("You must specify either a postcode or an address, or both.")
-        if address is None:
-            lat, lon, status = self.cpo_geocode_one(postcode)
-            if status > 0:
-                return lat, lon, status
-        return self.gmaps_geocode_one(postcode, address).to_records(index=False)
+        return self.reverse_geocode(latlons, "llsoa", datazones=dz)
 
-    def geocode_llsoa(self,
-                      llsoa: Union[str, Iterable[str]]
-                     ) -> Union[Tuple[float, float], List[Tuple[float, float]]]:
+    def geocode_constituency(self, constituencies):
         """
-        Geocode an LLSOA using the Population Weighted Centroid.
-
+        Function to geocode a collection of constituencies into latlons.
+        
         Parameters
         ----------
-        `llsoa` : string or iterable of strings
-            The LLSOA(s) to geocode.
-
-        Returns
-        -------
-        Tuple or List of Tuples
-            If a single LLSOA was passed (i.e. a string), the output be a tuple containing: latitude
-            (float), longitude (float). If several LLSOAs are passed (i.e. an iterable of strings),
-            the output will be a list of tuples which aligns with the input iterable.
-
-        Notes
-        -----
-        The input *llsoa* iterable can be any Python object which can be looped over with a for loop
-        e.g. a list, tuple, Numpy array etc.
-        LLSOAs are identified by their LLSOA Code, sometimes abbreviated to `llsoacd`.
-        In Scotland, one can use either LLSOA codes or Datazones, this method supports both.
+        `constituencies` : iterable of strings
+            Specific constituencies to geocode to latlons
         """
-        if self.llsoa_lookup is None:
-            self.llsoa_lookup = self._load_llsoa_lookup()
-        if isinstance(llsoa, str):
-            return self._llsoa_centroid(llsoa)
-        results = []
-        tot = len(llsoa)
-        for i, llsoa_ in enumerate(llsoa):
-            results.append(self._llsoa_centroid(llsoa_))
-            if self.progress_bar and (i % 10 == 0 or i == tot - 1):
-                print_progress(i+1, tot, prefix=self.prefix+"[Geocode]     LLSOA", suffix="",
-                               decimals=2, bar_length=100)
-        return results
+        return self.geocode(constituencies, "constituency")
 
-    def reverse_geocode_llsoa(self,
-                              latlons: List[Tuple[float, float]],
-                              datazones: bool = False) -> List[str]:
+    def geocode_local_authority(self, lads):
         """
-        Reverse-geocode latitudes and longitudes to LLSOA.
-
+        Function to geocode a collection of LADs (Local Authority Districts) into latlons.
+        
         Parameters
         ----------
-        `latlons` : list of tuples
-            A list of tuples containing (latitude, longitude).
-        `datazones` : bool
-            Set this to True to return Datazones rather than LLSOA codes in Scotland (default is
-            False).
-
-        Returns
-        -------
-        list of strings
-            The LLSOA codes that the input latitudes and longitudes fall within. Any lat/lons which
-            do not fall inside an LLSOA boundary will return None.
+        `lads` : iterable of strings
+            Specific LADs to geocode to latlons
         """
-        if not SHAPELY_AVAILABLE:
-            raise GenericException("Geocode was unable to import the Shapely library, follow the "
-                                   "installation instructions at "
-                                   "https://github.com/SheffieldSolar/Geocode")
-        if self.llsoa_regions is None:
-            self.llsoa_regions = self._load_llsoa_boundaries()
-        results = self._reverse_geocode(latlons, self.llsoa_regions)
-        if datazones:
-            if self.dz_lookup is None:
-                self.dz_lookup = self._load_datazone_lookup()
-            results = [llsoacd if llsoacd not in self.dz_lookup else self.dz_lookup[llsoacd]
-                       for llsoacd in results]
-        return results
+        return self.geocode(lads, "lad")
 
-    def postcode2llsoa(self, pcs: pd.DataFrame) -> pd.DataFrame:
+    def reverse_geocode_gsp(self, latlons):
         """
-        Reverse-geocode multiple postcodes to LLSOA.
-
+        Function to reverse geocode a collection of latlons into gsp regions.
+        
         Parameters
         ----------
-        `pcs` : Pandas.DataFrame
-            A Pandas DataFrame containing the postcodes (or partial postcodes) to geocode. Must
-            contain the column 'input_postcode'.
-
-        Returns
-        -------
-        Pandas.DataFrame
-            A Pandas DataFrame with columns: input_postcode (str), lsoa11cd (str), plus any other
-            cols that were in `pcs`.
+        `latlons` : iterable of strings
+            Specific latlons to geocode to gsp regions.
         """
-        pcs["postcode_to_match"] = pcs.input_postcode.str.strip().str.upper().str.replace(" ", "")
-        if self.pc_llsoa_lookup is None:
-            self.pc_llsoa_lookup = self._load_postcode_llsoa_lookup()
-        pcs = pcs.merge(self.pc_llsoa_lookup[["postcode", "lsoa11cd"]], how="left",
-                        left_on="postcode_to_match", right_on="postcode")
-        pcs.drop(columns=["postcode_to_match"], inplace=True)
-        return pcs
+        return self.reverse_geocode(latlons, "gsp")
 
-    def _reverse_geocode(self,
-                         coords: List[Tuple[float, float]],
-                         regions: Dict) -> List:
+    def geocode_postcode(self, postcodes, method="cpo"):
         """
-        Generic method to reverse-geocode x, y coordinates to regions.
-
+        Function to geocode a collection of postcodes into latlons.
+        
         Parameters
         ----------
-        `coords` : list of tuples
-            A list of tuples containing (y, x).
-        `regions` : dict
-            Dict whose keys are the region IDs and whose values are a tuple containing:
-            (region_boundary, region_bounds). The region boundary must be a Shapely
-            Polygon/MultiPolygon and the bounds should be a tuple containing (xmin, ymin, xmax,
-            ymax).
-
-        Returns
-        -------
-        list
-            The region IDs that the input coords fall within. Any coords which do not fall inside an
-            LLSOA boundary will return None.
-
-        Notes
-        -----
-        The region bounds are used to improve performance by first scanning for potential region
-        candidates using a simple inequality, since the performance of
-        `Shapely.MultiPolygon.contains()` is not great.
+        `postcodes` : iterable of strings
+            Specific postcodes to geocode to latlons
         """
-        results = []
-        tot = len(coords)
-        for i, (y, x) in enumerate(coords):
-            success = False
-            possible_matches = []
-            for reg_id in regions:
-                bounds = regions[reg_id][1]
-                if bounds[0] <= x <= bounds[2] and bounds[1] <= y <= bounds[3]:
-                    possible_matches.append(reg_id)
-            for reg_id in possible_matches:
-                if regions[reg_id][0].contains(Point(x, y)):
-                    results.append(reg_id)
-                    if self.progress_bar and (i % 10 == 0 or i == tot - 1):
-                        print_progress(i+1, tot, prefix=self.prefix+"[Geocode]     REVERSE-GEOCODE",
-                                       suffix="", decimals=2, bar_length=100)
-                    success = True
-                    break
-            if not success:
-                results.append(None)
-        return results
+        return self.geocode(postcodes, "postcode", method=method)
 
-    def reverse_geocode_gsp(self,
-                            latlons: List[Tuple[float, float]]
-                           ) -> Tuple[List[int], List[List[Dict]]]:
+    def geocode(self, entity_ids, entity, **kwargs):
         """
-        Reverse-geocode latitudes and longitudes to GSP using the 20220314 definitions.
+        Geocode a selection of GSP regions, llsoa boundaries, constituencies, LADs, postcodes or
+        addresses to latitudes and longitudes.
 
         Parameters
         ----------
-        `latlons` : list of tuples
-            A list of tuples containing (latitude, longitude).
-
-        Returns
-        -------
-        `results` : list of ints
-            A list of tuples containing (<GSPs>, <GSPGroup>), aligned with the input *latlons*.
-
-        Notes
-        -----
-        Return format needs some work, maybe switch to DataFrames in future release.
-        """
-        if not SHAPELY_AVAILABLE:
-            raise GenericException("Geocode was unable to import the Shapely library, follow the "
-                                   "installation instructions at "
-                                   "https://github.com/SheffieldSolar/Geocode")
-        logging.debug("Reverse geocoding %s latlons to 20220314 GSP", len(latlons))
-        if self.gsp_regions is None:
-            self.gsp_regions = self._load_gsp_boundaries_20220314()
-        lats = [l[0] for l in latlons]
-        lons = [l[1] for l in latlons]
-        # Rather than re-project the region boundaries, re-project the input lat/lons
-        # (easier, but slightly slower if reverse-geocoding a lot)
-        logging.debug("Converting latlons to BNG")
-        eastings, northings = self._latlon2bng(lons, lats)
-        logging.debug("Reverse geocoding")
-        results = self._reverse_geocode(list(zip(northings, eastings)), self.gsp_regions)
-        return results
+        `entity_ids` : iterable of strings
+            The specific entities to Geocode.
+        `entity` : string
+            Specify the entity type to Geocode from i.e., lad or postcode.
+        `**kwargs`
+            Options to pass to the underlying geocode method.
+        """
+        entity = entity.lower()
+        if entity == "gsp":
+            raise utils.GenericException(f"Entity '{entity}' is not supported.")
+        elif entity == "llsoa":
+            return self.ons_nrs.geocode_llsoa(llsoa=entity_ids)
+        elif entity == "constituency":
+            return self.ons_nrs.geocode_constituency(constituency=entity_ids)
+        elif entity == "lad":
+            return self.ons_nrs.geocode_local_authority(local_authority=entity_ids)
+        elif entity == "postcode":
+            method = kwargs.get("method", "cpo").lower()
+            address = kwargs.get("address", None)
+            if address is None:
+                if method.replace(" ", "") in ["cpo", "codepointopen"]:
+                    return self.cpo.geocode_postcode(postcodes=entity_ids)
+                elif method.replace(" ", "") in ["gmaps", "googlemaps"]:
+                    return self.gmaps.geocode_postcode(postcode=entity_ids)
+            else:
+                return self.gmaps.geocode_postcode(postcode=entity_ids, address=address)
+        else:
+            raise utils.GenericException(f"Entity '{entity}' is not supported.")
 
-    def reverse_geocode_gsp_20181031(self,
-                                     latlons: List[Tuple[float, float]]
-                                    ) -> Tuple[List[int], List[List[Dict]]]:
+    def reverse_geocode(self, latlons, entity, **kwargs):
         """
-        Reverse-geocode latitudes and longitudes to GSP using the 20181031 definitions.
+        Reverse geocode a set of latitudes and longitudes to either GSP regions or llsoa boundaries.
 
         Parameters
         ----------
         `latlons` : list of tuples
             A list of tuples containing (latitude, longitude).
+        `entity` : string
+            Specify the entity type to Geocode from i.e., gsp or llsoa.
+        `**kwargs`
+            Options to pass to the underlying reverse-geocode method.
+        """
+        entity = entity.lower()
+        if entity == "gsp":
+            return self.ngeso.reverse_geocode_gsp(latlons)
+        elif entity == "llsoa":
+            datazones = kwargs.get("datazones", False)
+            return self.ons_nrs.reverse_geocode_llsoa(latlons=latlons, datazones=datazones)
+        else:
+            raise utils.GenericException(f"Entity '{entity}' is not supported.")
 
-        Returns
-        -------
-        `results` : list of ints
-            A list of region IDs, aligned with the input *latlons*.
-        `results_more` : list of lists of dicts
-            The full MANY:MANY lookup giving the Region <-> GSP <-> GNode which the lat/lon falls
-            within. The relationship between GSP: GNode is MANY:MANY, so each element of the outer
-            list is another list of matches, each element of which is a dictionary giving the
-            matched GSP / GNode.
-
-        Notes
-        -----
-        Return format needs some work, maybe switch to DataFrames in future release.
-        """
-        if not SHAPELY_AVAILABLE:
-            raise GenericException("Geocode was unable to import the Shapely library, follow the "
-                                   "installation instructions at "
-                                   "https://github.com/SheffieldSolar/Geocode")
-        if self.gsp_regions_20181031 is None:
-            self.gsp_regions_20181031 = self._load_gsp_boundaries_20181031()
-        lats = [l[0] for l in latlons]
-        lons = [l[1] for l in latlons]
-        # Rather than re-project the region boundaries, re-project the input lat/lons
-        # (easier, but slightly slower if reverse-geocoding a lot)
-        eastings, northings = self._latlon2bng(lons, lats)
-        results = self._reverse_geocode(list(zip(northings, eastings)), self.gsp_regions_20181031)
-        if self.gsp_lookup_20181031 is None:
-            self.gsp_lookup_20181031 = self._load_gsp_lookup_20181031()
-        lookup = self.gsp_lookup_20181031
-        reg_lookup = {r: lookup[lookup.region_id == r].to_dict(orient="records")
-                      for r in list(set(results))}
-        results_more = [reg_lookup[r] if r is not None else None for r in results]
-        return results, results_more
-
-    def _llsoa_centroid(self, llsoa):
-        """Lookup the PWC for a given LLSOA code."""
-        try:
-            return self.llsoa_lookup[llsoa]
-        except KeyError:
-            return None, None
-
-    def geocode_constituency(self,
-                             constituency: Union[str, Iterable[str]]
-                            ) -> Union[Tuple[float, float], List[Tuple[float, float]]]:
-        """
-        Geocode a UK Constituency using the geospatial centroid.
-
-        Parameters
-        ----------
-        `constituency` : string or iterable of strings
-            The constituency names to geocode.
-
-        Returns
-        -------
-        tuple or list of tuples
-            If a single constituency name was passed (i.e. a string), the output be a tuple
-            containing: latitude (float), longitude (float). If several constituency names are
-            passed (i.e. an iterable of strings), the output will be a list of tuples which aligns
-            with the input iterable.
-
-        Notes
-        -----
-        The input *constituency* iterable can be any Python object which can be looped over with a
-        for loop e.g. a list, tuple, Numpy array etc.
-        Constituencies are identified by their full name, case-insensitive, ignoring spaces.
-        """
-        if self.constituency_lookup is None:
-            self.constituency_lookup = self._load_constituency_lookup()
-        if isinstance(constituency, str):
-            return self._constituency_centroid(constituency)
-        results = []
-        tot = len(constituency)
-        for i, constituency_ in enumerate(constituency):
-            results.append(self._constituency_centroid(constituency_))
-            if self.progress_bar and (i % 10 == 0 or i == tot - 1):
-                print_progress(i+1, tot, prefix=self.prefix+"[Geocode]     CONSTITUENCY", suffix="",
-                               decimals=2, bar_length=100)
-        return results
-
-    def _constituency_centroid(self, constituency):
-        """Lookup the GC for a given constituency."""
-        try:
-            match_str = constituency.strip().replace(" ", "").replace(",", "").lower()
-            return self.constituency_lookup[match_str]
-        except KeyError:
-            return None, None
-
-    def geocode_local_authority(self,
-                                local_authority: Union[str, Iterable[str]]
-                               ) -> Union[Tuple[float, float], List[Tuple[float, float]]]:
+    @staticmethod
+    def _latlon2bng(lons: List[float], lats: List[float]) -> Tuple[List[float], List[float]]:
         """
-        Geocode a UK Local Authority using the geospatial centroid.
-
+        Convert latitudes and longitudes (WGS 1984) to Eastings and Northings (a.k.a British
+        National Grid a.k.a OSGB 1936).
         Parameters
         ----------
-        `local_authority` : string or iterable of strings
-            The Local Authority names to geocode.
-
+        `lons` : list of floats
+            Corresponding longitude co-ordinates in WGS 1984 CRS.
+        `lats` : list of floats
+            Corresponding latitude co-ordinates in WGS 1984 CRS.
         Returns
         -------
-        tuple or list of tuples
-            If a single Local Authority name was passed (i.e. a string), the output be a tuple
-            containing: latitude (float), longitude (float). If several Local Authority names are
-            passed (i.e. an iterable of strings), the output will be a list of tuples which aligns
-            with the input iterable.
-
+        `eastings` : list of floats or ints
+            Easting co-ordinates.
+        `northings` : list of floats or ints
+            Northing co-ordinates.
         Notes
         -----
-        The input *local_authority* iterable can be any Python object which can be looped over with
-        a for loop e.g. a list, tuple, Numpy array etc.
-        Local Authorities are identified by their full name, case-insensitive, ignoring spaces.
+        Be careful! This method uses the same convention of ordering (eastings, northings) and
+        (lons, lats) as pyproj i.e. (x, y). Elsewhere in this module the convention is typically
+        (lats, lons) due to personal preference.
         """
-        if self.lad_lookup is None:
-            self.lad_lookup = self._load_lad_lookup()
-        if isinstance(local_authority, str):
-            return self._lad_centroid(local_authority)
-        results = []
-        tot = len(local_authority)
-        for i, local_authority_ in enumerate(local_authority):
-            results.append(self._lad_centroid(local_authority_))
-            if self.progress_bar and (i % 10 == 0 or i == tot - 1):
-                print_progress(i+1, tot, prefix=self.prefix+"[Geocode]     LOCAL AUTHORITY",
-                               suffix="", decimals=2, bar_length=100)
-        return results
-
-    def _lad_centroid(self, local_authority):
-        """Lookup the GC for a given Local Authority."""
-        try:
-            match_str = local_authority.strip().replace(" ", "").replace(",", "").lower()
-            return self.lad_lookup[match_str]
-        except KeyError:
-            return None, None
+        proj = pyproj.Transformer.from_crs(4326, 27700, always_xy=True)
+        eastings, northings = proj.transform(lons, lats)
+        return eastings, northings
 
     @staticmethod
     def _bng2latlon(eastings: Iterable[Union[float, int]],
@@ -1167,21 +277,18 @@ def _bng2latlon(eastings: Iterable[Union[float, int]],
         """
         Convert Eastings and Northings (a.k.a British National Grid a.k.a OSGB 1936) to latitudes
         and longitudes (WGS 1984).
-
         Parameters
         ----------
         `eastings` : iterable of floats or ints
             Easting co-ordinates.
         `northings` : iterable of floats or ints
             Northing co-ordinates.
-
         Returns
         -------
         `lons` : list of floats
             Corresponding longitude co-ordinates in WGS 1984 CRS.
         `lats` : list of floats
             Corresponding latitude co-ordinates in WGS 1984 CRS.
-
         Notes
         -----
         Be careful! This method uses the same convention of ordering (eastings, northings) and
@@ -1192,123 +299,20 @@ def _bng2latlon(eastings: Iterable[Union[float, int]],
         lons, lats = proj.transform(eastings, northings)
         return lons, lats
 
-    @staticmethod
-    def _latlon2bng(lons: List[float], lats: List[float]) -> Tuple[List[float], List[float]]:
-        """
-        Convert latitudes and longitudes (WGS 1984) to Eastings and Northings (a.k.a British
-        National Grid a.k.a OSGB 1936).
-
-        Parameters
-        ----------
-        `lons` : list of floats
-            Corresponding longitude co-ordinates in WGS 1984 CRS.
-        `lats` : list of floats
-            Corresponding latitude co-ordinates in WGS 1984 CRS.
-
-        Returns
-        -------
-        `eastings` : list of floats or ints
-            Easting co-ordinates.
-        `northings` : list of floats or ints
-            Northing co-ordinates.
-
-        Notes
-        -----
-        Be careful! This method uses the same convention of ordering (eastings, northings) and
-        (lons, lats) as pyproj i.e. (x, y). Elsewhere in this module the convention is typically
-        (lats, lons) due to personal preference.
-        """
-        proj = pyproj.Transformer.from_crs(4326, 27700, always_xy=True)
-        eastings, northings = proj.transform(lons, lats)
-        return eastings, northings
-
-def print_progress(iteration, total, prefix="", suffix="", decimals=2, bar_length=100):
-    """
-    Call in a loop to create terminal progress bar.
-
-    Parameters
-    ----------
-    `iteration` : int
-        current iteration (required)
-    `total` : int
-        total iterations (required)
-    `prefix` : string
-        prefix string (optional)
-    `suffix` : string
-        suffix string (optional)
-    `decimals` : int
-        number of decimals in percent complete (optional)
-    `bar_length` : int
-        character length of bar (optional)
-
-    Notes
-    -----
-    Taken from `Stack Overflow <http://stackoverflow.com/a/34325723>`_.
-    """
-    filled_length = int(round(bar_length * iteration / float(total)))
-    percents = round(100.00 * (iteration / float(total)), decimals)
-    progress_bar = "#" * filled_length + "-" * (bar_length - filled_length)
-    sys.stdout.write("\r%s |%s| %s%s %s" % (prefix, progress_bar, percents, "%", suffix))
-    sys.stdout.flush()
-    if iteration == total:
-        sys.stdout.write("\n")
-        sys.stdout.flush()
-
-def query_yes_no(question, default="yes"):
-    """
-    Ask a yes/no question via input() and return the answer as boolean.
-
-    Parameters
-    ----------
-    `question` : string
-        The question presented to the user.
-    `default` : string
-        The presumed answer if the user just hits <Enter>. It must be "yes" (the default), "no" or
-        None (meaning an answer is required of the user).
-
-    Returns
-    -------
-    boolean
-        Return value is True for "yes" or False for "no".
-
-    Notes
-    -----
-    See http://stackoverflow.com/a/3041990
-    """
-    valid = {"yes": True, "y": True, "ye": True,
-             "no": False, "n": False}
-    if default is None:
-        prompt = " [y/n] "
-    elif default == "yes":
-        prompt = " [Y/n] "
-    elif default == "no":
-        prompt = " [y/N] "
-    else:
-        raise ValueError("invalid default answer: '%s'" % default)
-    while True:
-        sys.stdout.write(question + prompt)
-        choice = input().lower()
-        if default is not None and choice == '':
-            return valid[default]
-        if choice in valid:
-            return valid[choice]
-        sys.stdout.write("Please respond with 'yes' or 'no' (or 'y' or 'n').\n")
-
 def parse_options():
     """Parse command line options."""
     parser = argparse.ArgumentParser(description=("This is a command line interface (CLI) for "
                                                   f"the Geocode module version {__version__}."),
-                                     epilog="Jamie Taylor, 2019-10-08")
+                                     epilog="Jamie Taylor & Ethan Jones, 2019-10-08")
     parser.add_argument("--clear-cache", dest="clear_cache", action="store_true",
                         required=False, help="Specify to delete the cache files.")
     parser.add_argument("--debug", dest="debug", action="store_true",
                         required=False, help="Geocode some sample postcodes/addresses/LLSOAs.")
-    parser.add_argument("--setup", dest="setup", action="store_true",
+    parser.add_argument("--setup", dest="setup", action="store", nargs="+", default=None,
                         required=False, help="Force download all datasets to local cache (useful "
                                              "if running inside a Docker container i.e. run this "
-                                             "as part of image build).")
-    parser.add_argument("--setup-eso", dest="setup_eso", action="store_true",
-                        required=False, help="Force download all ESO datasets to local cache.")
+                                             "as part of image build). Possible values are "
+                                             "'ngeso', 'cpo', 'ons' or 'all'.")
     parser.add_argument("--load-cpo-zip", dest="cpo_zip", action="store", type=str,
                         required=False, default=None, metavar="</path/to/zip-file>",
                         help="Load the Code Point Open data from a local zip file.")
@@ -1316,113 +320,97 @@ def parse_options():
                         required=False, default=None, metavar="<gmaps-api-key>",
                         help="Load a Google Maps API key.")
     options = parser.parse_args()
-    return options
+    def handle_options(options):
+        if options.setup is not None:
+            valid_options = ["ngeso", "cpo", "ons", "all"]
+            options.setup = list(map(str.lower, options.setup))
+            if any(s not in valid_options for s in options.setup):
+                raise ValueError(f"Invalid value for `--setup` - valid values are {valid_options}")
+        return options
+    return handle_options(options)
 
 def debug():
     """Useful for debugging code (runs each public method in turn with sample inputs)."""
     logging.info("Running some example code (`--debug`)")
-    ### LLSOA -> latlon ###
     timerstart = TIME.time()
     sample_llsoas = ["E01025397", "E01003065", "E01017548", "E01023301", "E01021142", "E01019037",
                      "E01013873", "S00092417", "S01012390"]
     logging.info("Geocoding some LSOAs")
-    with Geocoder(progress_bar=False) as geocoder:
-        results = geocoder.geocode_llsoa(sample_llsoas)
+    with Geocoder() as geocoder:
+        results = geocoder.geocode(entity="llsoa", entity_ids=sample_llsoas)
     logging.info("Time taken: {:.1f} seconds".format(TIME.time() - timerstart))
     for llsoa, (lat, lon) in zip(sample_llsoas, results):
         logging.info("%s :    %s, %s", llsoa, lat, lon)
-    ### latlon -> LLSOA ###
     sample_latlons = [
         (53.705, -2.328), (51.430, -0.093), (52.088, -0.457), (51.706, -0.036), (50.882, 0.169),
         (50.409, -4.672), (52.940, -1.146), (57.060, -2.874), (56.31, -4.)
     ]
     timerstart = TIME.time()
     logging.info("Reverse geocoding some latlons to LSOAs")
-    with Geocoder(progress_bar=False) as geocoder:
-        results = geocoder.reverse_geocode_llsoa(sample_latlons, datazones=True)
+    with Geocoder() as geocoder:
+        results = geocoder.reverse_geocode(latlons=sample_latlons, entity="llsoa", datazones=True)
     logging.info("Time taken: %s seconds", round(TIME.time() - timerstart, 1))
     for (lat, lon), llsoa in zip(sample_latlons, results):
         logging.info("%s, %s :    %s", lat, lon, llsoa)
-    ### latlon -> GSP ###
     sample_file = os.path.join(SCRIPT_DIR, "sample_latlons.txt")
     with open(sample_file) as fid:
         sample_latlons = [tuple(map(float, line.strip().split(",")))
                           for line in fid if line.strip()][:10]
     timerstart = TIME.time()
     logging.info("Reverse geocoding some latlons to GSPs")
-    with Geocoder(progress_bar=False) as geocoder:
-        results = geocoder.reverse_geocode_gsp(sample_latlons)
+    with Geocoder() as geocoder:
+        results = geocoder.reverse_geocode(latlons=sample_latlons, entity="gsp")
     logging.info("Time taken: %s seconds", round(TIME.time() - timerstart, 1))
     for (lat, lon), region_id in zip(sample_latlons, results):
         logging.info("%s, %s :    %s", lat, lon, region_id)
-    ### constituency -> latlon ###
     sample_constituencies = ["Berwickshire Roxburgh and Selkirk", "Argyll and Bute",
-                             "Inverness Nairn Badenoch and Strathspey", # missing commas :(
+                             "Inverness Nairn Badenoch and Strathspey",
                              "Dumfries and Galloway"]
     timerstart = TIME.time()
     logging.info("Geocoding some constituencies")
-    with Geocoder(progress_bar=False) as geocoder:
-        results = geocoder.geocode_constituency(sample_constituencies)
+    with Geocoder() as geocoder:
+        results = geocoder.geocode(entity="constituency", entity_ids=sample_constituencies)
     logging.info("Time taken: %s seconds", round(TIME.time() - timerstart, 1))
     for constituency, (lat, lon) in zip(sample_constituencies, results):
         logging.info("%s :    %s, %s", constituency, lat, lon)
-    ### Local Authority -> latlon ###
-    sample_lads = ["Hartlepool", "Middlesbrough", "Redcar and Cleveland"]
-    timerstart = TIME.time()
-    logging.info("Geocoding some Local Authorities")
-    with Geocoder(progress_bar=False) as geocoder:
-        results = geocoder.geocode_local_authority(sample_lads)
-    logging.info("Time taken: %s seconds", round(TIME.time() - timerstart, 1))
-    for lad, (lat, lon) in zip(sample_lads, results):
-        logging.info("%s :    %s, %s", lad, lat, lon)
-    ### postcode/address -> latlon ###
     sample_file = os.path.join(SCRIPT_DIR, "sample_postcodes.txt")
     with open(sample_file) as fid:
         postcodes = [line.strip() for line in fid if line.strip()][:10]
     timerstart = TIME.time()
     logging.info("Geocoding some postcodes")
-    with Geocoder(progress_bar=False) as geocoder:
-        results = geocoder.geocode(postcodes=postcodes)
+    with Geocoder() as geocoder:
+        results = geocoder.geocode(entity="postcode", entity_ids=postcodes)
     logging.info("Time taken: %s seconds", round(TIME.time() - timerstart, 1))
     for postcode, (lat, lon, status) in zip(postcodes, results):
         logging.info("%s :    %s, %s    -> %s", postcode, lat, lon, geocoder.status_codes[status])
-    ### postcode -> LLSOA ###
-    postcodes = pd.DataFrame({"input_postcode": ["S6 6RP", "DE22 2SZ", "S7 2QZ"]})
-    timerstart = TIME.time()
-    logging.info("Reverse geocoding some postcodes to LLSOA")
-    with Geocoder(progress_bar=False) as geocoder:
-        results = geocoder.postcode2llsoa(pcs=postcodes)
-    logging.info("Time taken: %s seconds", round(TIME.time() - timerstart, 1))
-    logging.info("\n%s", results)
 
 def main():
     """Run the Command Line Interface."""
     options = parse_options()
     if options.clear_cache:
         geocoder = Geocoder()
-        geocoder.clear_cache()
+        geocoder.cache_manager.clear()
     if options.cpo_zip is not None:
         logging.info("Updating Code Point Open data")
         with Geocoder() as geocoder:
-            copyfile(options.cpo_zip, geocoder.cpo_zipfile)
-            logging.debug("Copied file '%s' to '%s'", options.cpo_zip, geocoder.cpo_zipfile)
-            geocoder._load_code_point_open(force_reload=True)
+            copyfile(options.cpo_zip, geocoder.cpo.cpo_zipfile)
+            logging.debug("Copied file '%s' to '%s'", options.cpo_zip, geocoder.cpo.cpo_zipfile)
+            geocoder.cpo._load(force_reload=True)
         logging.info("Finished updating Code Point Open data")
     if options.gmaps_key is not None:
         logging.info("Loading GMaps key")
         with Geocoder() as geocoder:
-            with open(geocoder.gmaps_key_file, "w") as fid:
+            with open(geocoder.gmaps.gmaps_key_file, "w") as fid:
                 fid.write(options.gmaps_key)
-            if geocoder._load_gmaps_key() == options.gmaps_key:
-                logging.info("GMaps key saved to '%s'", geocoder.gmaps_key_file)
-    if options.setup:
+            if geocoder.gmaps._load_key() == options.gmaps_key:
+                logging.info("GMaps key saved to '%s'", geocoder.gmaps.gmaps_key_file)
+    if options.setup is not None:
+        ngeso_setup =  "ngeso" in options.setup or "all" in options.setup
+        cpo_setup = "cpo" in options.setup or "all" in options.setup
+        ons_setup = "ons" in options.setup or "all" in options.setup
         logging.info("Running forced setup")
         with Geocoder() as geocoder:
-            geocoder.force_setup()
-    if options.setup_eso:
-        logging.info("Running forced setup of ESO data")
-        with Geocoder() as geocoder:
-            geocoder.force_setup_eso()
+            geocoder.force_setup(ngeso_setup=ngeso_setup, cpo_setup=cpo_setup, ons_setup=ons_setup)
     if options.debug:
         debug()
 
@@ -1430,5 +418,5 @@ def main():
     DEFAULT_FMT = "%(asctime)s [%(levelname)s] [%(filename)s:%(funcName)s] - %(message)s"
     FMT = os.environ.get("GEOCODE_LOGGING_FMT", DEFAULT_FMT)
     DATEFMT = os.environ.get("GEOCODE_LOGGING_DATEFMT", "%Y-%m-%dT%H:%M:%SZ")
-    logging.basicConfig(format=FMT, datefmt=DATEFMT, level=os.environ.get("LOGLEVEL", "DEBUG"))
+    logging.basicConfig(format=FMT, datefmt=DATEFMT, level=os.environ.get("LOGLEVEL", "INFO"))
     main()
diff --git a/geocode/gmaps.py b/geocode/gmaps.py
new file mode 100644
index 0000000..13f2254
--- /dev/null
+++ b/geocode/gmaps.py
@@ -0,0 +1,141 @@
+"""
+Manage data from Google Maps.
+
+- Ethan Jones <ejones18@sheffield.ac.uk>
+- Jamie Taylor <jamie.taylor@sheffield.ac.uk>
+- First Authored: 2022-10-19
+"""
+
+import os
+import sys
+import logging
+import pickle
+import numpy as np
+import pandas as pd
+from typing import Optional, Iterable, Tuple, Union, List, Dict
+
+import utilities as utils
+
+SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
+
+class GMaps:
+    """The Gmaps data manager for the Geocode class."""
+    def __init__(self, cache_manager, gmaps_key_file=None):
+        """The Gmaps data manager for the Geocode class."""
+        self.cache_manager = cache_manager
+        self.gmaps_key = None
+        self.gmaps_client = None
+        self.cache_file = "gmaps_cache"
+        self.gmaps_key_file = gmaps_key_file if gmaps_key_file is not None \
+                                  else os.path.join(self.cache_manager.cache_dir, "key.txt")
+        self._load_cache()
+
+    def __enter__(self):
+        """Context manager."""
+        return self
+
+    def __exit__(self, *args):
+        """Context manager - flush GMaps cache on exit."""
+        self.flush_cache(self.cache, self.cache_file)
+
+    def _load_key(self):
+        """Load the user's GMaps API key from installation directory."""
+        try:
+            with open(self.gmaps_key_file) as fid:
+                key = fid.read().strip()
+        except FileNotFoundError:
+            logging.warning("Failed to load Google Maps API key from '%s' - you "
+                            "will not be able to make new queries to the Google Maps API!",
+                            self.gmaps_key_file)
+            return None
+        return key
+
+    def flush_cache(self, gmaps_cache_contents):
+        """Flush any new GMaps API queries to the GMaps cache."""
+        self.cache_manager.write(self.cache_file, self.cache)
+
+    def geocode_postcode(self, postcode: [str],
+                         address: Optional[str] = None) -> Union[Tuple[float, float], List[Tuple[float, float]]]:
+        """
+        Geocode several postcodes and/or addresses.
+
+        Parameters
+        ----------
+        `postcodes` : iterable of strings
+            The postcodes (or partial postcodes) to geocode. Must align with *addresses* if both are
+            passed.
+        `addresses` : iterable of strings
+            The addresses to geocode. Must align with *postcodes* if both are passed.
+
+        Returns
+        -------
+        tuple or list of tuples
+            If a single constituency name was passed (i.e. a string), the output be a tuple
+            containing: latitude (float), longitude (float). If several constituency names are
+            passed (i.e. an iterable of strings), the output will be a list of tuples which aligns
+            with the input iterable.
+        """
+        address = [None for a in address] if address is None else list(address)
+        logging.debug("Geocoding %s postcodes (%s addresses)", len(postcode), len(address))
+        results = []
+        for pc, addr in zip(postcode, address):
+            results.append(self.geocode_one(postcode=pc, address=addr))
+        return results
+
+    def geocode_one(self, postcode: str, address: Optional[str] = None) -> pd.Series:
+        """
+        Use the GMaps API to geocode a postcode (or partial postcode) and/or an address.
+
+        Parameters
+        ----------
+        `postcode` : string
+            The postcode (or partial postcode) to geocode.
+        `address` : string
+            The address to geocode.
+
+        Returns
+        -------
+        Pandas.Series
+            A Pandas Series with fields: input_postcode (str), latitude (float), longitude (float),
+            match_status (int). The status code shows the quality of the postcode lookup - use the
+            class attribute *self.status_codes* (a dict) to get a string representation.
+        """
+        if postcode is None and address is None:
+            raise utils.GenericException("You must pass either postcode or address, or both.")
+        if self.gmaps_key is None:
+            self.gmaps_key = self._load_key()
+            if self.gmaps_key is not None:
+                self.gmaps_client = googlemaps.Client(key=self.gmaps_key)
+        if self.cache is None:
+            self._load_cache()
+        sep = ", " if address and postcode else ""
+        postcode = postcode if postcode is not None else ""
+        address = address if address is not None else ""
+        search_term = f"{address}{sep}{postcode}"
+        logging.debug("Querying Google Maps Geocoder API for '%s'", search_term)
+        if search_term in self.cache:
+            geocode_result = self.cache[search_term]
+        else:
+            if self.gmaps_key is None:
+                return pd.Series({"latitude": np.nan, "longitude": np.nan, "match_status": 0})
+            geocode_result = self.gmaps_client.geocode(search_term, region="uk")
+            self.gmaps_cache[search_term] = geocode_result
+        if not geocode_result or len(geocode_result) > 1:
+            return pd.Series({"latitude": np.nan, "longitude": np.nan, "match_status": 0})
+        geometry = geocode_result[0]["geometry"]
+        ok_loc_types = ["ROOFTOP", "GEOMETRIC_CENTER"]
+        if geometry["location_type"] in ok_loc_types or \
+            geocode_result[0]["types"] == ["postal_code"]:
+            return pd.Series({"latitude": geometry["location"]["lat"],
+                              "longitude": geometry["location"]["lng"],
+                              "match_status": 3})
+        return pd.Series({"latitude": np.nan, "longitude": np.nan, "match_status": 0})
+
+    def _load_cache(self):
+        """Load the cache of prior addresses/postcodes for better performance."""
+        self.cache = self.cache_manager.retrieve(self.cache_file)
+        if self.cache is None:
+            self.cache = pd.DataFrame({"input_postcode": [], "input_address": [], "latitude": [],
+                                       "longitude": [], "match_status": []})
+        return
+
diff --git a/geocode/gov/__init__.py b/geocode/gov/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/geocode/latlons2gsp.py b/geocode/latlons2gsp.py
index d58aa64..ba54eed 100644
--- a/geocode/latlons2gsp.py
+++ b/geocode/latlons2gsp.py
@@ -1,8 +1,9 @@
 #!/usr/bin/env python3
 """
-Load a list of lat/lons from a CSV file and reverse-geocode them to GSP.
+Load a list of lat/lons from a CSV file and reverse-geocode them to GSP / GNode.
 
 - Jamie Taylor <jamie.taylor@sheffield.ac.uk>
+- Ethan Jones <ejones18@sheffield.ac.uk>
 - First Authored: 2020-05-29
 """
 
@@ -12,13 +13,14 @@
 import time as TIME
 import pandas as pd
 
-from geocode import Geocoder, query_yes_no
+from geocode import Geocoder
+from utilities import query_yes_no, GenericException
 
 def parse_options():
     """Parse command line options."""
     parser = argparse.ArgumentParser(description=("This is a command line interface (CLI) for "
                                                   "the latlons2gsp.py module"),
-                                     epilog="Jamie Taylor, 2020-05-29")
+                                     epilog="Jamie Taylor & Ethan Jones, 2020-05-29")
     parser.add_argument("-f", "--input-file", dest="infile", action="store", type=str,
                         required=True, metavar="</path/to/file>",
                         help="Specify a CSV file containing a list of latitudes and longitudes to "
@@ -28,7 +30,7 @@ def parse_options():
                         required=True, metavar="</path/to/file>", help="Specify an output file.")
     options = parser.parse_args()
     if not os.path.isfile(options.infile):
-        raise Exception(f"The input file '{options.infile}' does not exist.")
+        raise GenericException(f"The input file '{options.infile}' does not exist.")
     if os.path.isfile(options.outfile):
         check = query_yes_no(f"The outfile '{options.outfile}' already exists, are you sure you "
                              "wish to overwrite?", "no")
@@ -42,14 +44,11 @@ def main():
     options = parse_options()
     with open(options.infile, "r") as fid:
         df = pd.read_csv(fid)
-    with Geocoder(progress_bar=True) as geo:
-        region_ids = geo.reverse_geocode_gsp(df[["latitude", "longitude"]].to_numpy())
-    # Following code is a bit clunky and performance needs improving
-    output = []
-    for i, row in df.iterrows():
-        output.append(row.tolist() + list(region_ids[i]))
-    columns = row.index.to_list() + ["GSPs", "GSPGroup"]
-    output = pd.DataFrame(output, columns=columns)
+    with Geocoder() as geo:
+        results_ = geo.reverse_geocode_gsp(df[["latitude", "longitude"]].to_numpy())
+    results_ = [(None, None) if gsp_pair is None else gsp_pair for gsp_pair in results_]
+    gsps, gsp_groups = [x[0] for x in results_], [x[1] for x in results_]
+    output = pd.DataFrame({'gsp': gsps, 'gsp_group': gsp_groups})
     output.to_csv(options.outfile, index=False)
     print(f"Finished, time taken: {TIME.time() - timerstart:.1f} seconds")
 
diff --git a/geocode/latlons2llsoa.py b/geocode/latlons2llsoa.py
index 49bef0b..b70b50a 100644
--- a/geocode/latlons2llsoa.py
+++ b/geocode/latlons2llsoa.py
@@ -3,6 +3,7 @@
 Load a list of lat/lons from a CSV file and reverse-geocode them to LLSOA.
 
 - Jamie Taylor <jamie.taylor@sheffield.ac.uk>
+- Ethan Jones <ejones18@sheffield.ac.uk>
 - First Authored: 2020-04-16
 """
 
@@ -12,13 +13,14 @@
 import time as TIME
 import pandas as pd
 
-from geocode import Geocoder, query_yes_no
+from geocode import Geocoder
+from utilities import query_yes_no, GenericException
 
 def parse_options():
     """Parse command line options."""
     parser = argparse.ArgumentParser(description=("This is a command line interface (CLI) for "
                                                   "the latlons2llsoa.py module"),
-                                     epilog="Jamie Taylor, 2020-04-16")
+                                     epilog="Jamie Taylor & Ethan Jones, 2020-04-16")
     parser.add_argument("-f", "--input-file", dest="infile", action="store", type=str,
                         required=True, metavar="</path/to/file>",
                         help="Specify a CSV file containing a list of latitudes and longitudes to "
@@ -30,7 +32,7 @@ def parse_options():
                         required=False, help="Specify to use Data Zones in Scotland.")
     options = parser.parse_args()
     if not os.path.isfile(options.infile):
-        raise Exception(f"The input file '{options.infile}' does not exist.")
+        raise GenericException(f"The input file '{options.infile}' does not exist.")
     if os.path.isfile(options.outfile):
         check = query_yes_no(f"The outfile '{options.outfile}' already exists, are you sure you "
                              "wish to overwrite?", "no")
@@ -44,8 +46,7 @@ def main():
     options = parse_options()
     with open(options.infile, "r") as fid:
         df = pd.read_csv(fid)
-    with Geocoder(progress_bar=True) as geo:
-        # import pdb; pdb.set_trace()
+    with Geocoder() as geo:
         df["llsoacd"] = geo.reverse_geocode_llsoa(df[["latitude", "longitude"]].to_numpy(),
                                                   options.datazones)
     df.to_csv(options.outfile, index=False)
diff --git a/geocode/ngeso.py b/geocode/ngeso.py
new file mode 100644
index 0000000..3c33caf
--- /dev/null
+++ b/geocode/ngeso.py
@@ -0,0 +1,217 @@
+"""
+Manage data from the NGESO Data Portal.
+
+- Ethan Jones <ejones18@sheffield.ac.uk>
+- Jamie Taylor <jamie.taylor@sheffield.ac.uk>
+- First Authored: 2022-10-19
+"""
+
+import os
+import sys
+import pickle
+import logging
+import json
+import pandas as pd
+import geopandas as gpd
+
+from typing import Optional, Iterable, Tuple, Union, List, Dict
+try:
+    from shapely.geometry import shape, Point
+    from shapely.ops import unary_union
+    SHAPELY_AVAILABLE = True
+except ImportError:
+    logging.warning("Failed to import Shapely library - you will not be able to reverse-geocode! "
+                    "See notes in the README about installing Shapely on Windows machines.")
+    SHAPELY_AVAILABLE = False
+
+import utilities as utils
+
+SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
+
+class NationalGrid:
+    """The NGESO data manager for the Geocode class."""
+    def __init__(self, cache_manager):
+        """The NGESO data manager for the Geocode class."""
+        self.cache_manager = cache_manager
+        self.gsp_lookup_20181031_cache_file = "gsp_lookup_20181031"
+        self.gsp_boundaries_20220314_cache_file = "gsp_boundaries_20220314"
+        self.gsp_boundaries_20181031_cache_file = "gsp_boundaries_20181031"
+        self.dno_boundaries_cache_file = "dno_boundaries"
+        self.gsp_regions = None
+        self.gsp_regions_20181031 = None
+        self.dno_regions = None
+        self.gsp_lookup_20181031 = None
+
+    def force_setup(self):
+        """
+        Function to setup lookup files.
+        """
+        self._load_gsp_boundaries_20220314()
+        self._load_dno_boundaries()
+
+    def _load_gsp_lookup_20181031(self):
+        """Load the lookup of Region <-> GSP <-> GNode."""
+        gsp_lookup_cache_contents = self.cache_manager.retrieve(self.gsp_lookup_20181031_cache_file)
+        if gsp_lookup_cache_contents is not None:
+            logging.debug("Loading GSP lookup data from cache ('%s')",
+                          self.gsp_lookup_20181031_cache_file)
+            return gsp_lookup_cache_contents
+        logging.info("Extracting the GSP lookup data from NGESO's Data Portal API (this only needs "
+                     "to be done once)")
+        eso_url = "http://data.nationalgrideso.com/backend/dataset/2810092e-d4b2-472f-b955-d8bea01f9ec0/resource/bbe2cc72-a6c6-46e6-8f4e-48b879467368/download/gsp_gnode_directconnect_region_lookup.csv"
+        success, api_response = utils.fetch_from_api(eso_url)
+        if success:
+            f = StringIO(str(api_response.text).replace("\ufeff", "")) # Remove BOM character
+            gsp_lookup = pd.read_csv(f)
+            gsp_lookup = gsp_lookup.loc[gsp_lookup.region_id.notnull()].convert_dtypes()
+        else:
+            raise utils.GenericException("Encountered an error while extracting GSP lookup data from ESO "
+                                   "API.")
+        self.cache_manager.write(self.gsp_lookup_20181031_cache_file, gsp_lookup)
+        logging.info("GSP lookup extracted and pickled to '%s'", self.gsp_lookup_20181031_cache_file)
+        return gsp_lookup
+
+    def _load_gsp_boundaries_20220314(self):
+        """
+        Load the 20220314 GSP / GNode boundaries, either from local cache if available, else fetch
+        from ESO Data Portal API.
+        """
+        gsp_boundaries_cache_contents = self.cache_manager.retrieve(self.gsp_boundaries_20220314_cache_file)
+        if gsp_boundaries_cache_contents is not None:
+            logging.debug("Loading 20220314 GSP boundaries from cache ('%s')",
+                          self.gsp_boundaries_20220314_cache_file)
+            return gsp_boundaries_cache_contents
+        logging.info("Extracting the 20220314 GSP boundary data from NGESO's Data Portal API (this "
+                     "only needs to be done once)")
+        eso_url = "https://data.nationalgrideso.com/backend/dataset/2810092e-d4b2-472f-b955-d8bea01f9ec0/resource/08534dae-5408-4e31-8639-b579c8f1c50b/download/gsp_regions_20220314.geojson"
+        success, api_response = utils.fetch_from_api(eso_url)
+        if success:
+            raw = json.loads(api_response.text)
+            gsp_regions = gpd.GeoDataFrame.from_features(raw["features"],
+                                                         crs=raw["crs"]["properties"]["name"])
+            gsp_regions.geometry = gsp_regions.buffer(0)
+        else:
+            raise utils.GenericException("Encountered an error while extracting GSP region data from ESO "
+                                   "API.")
+        ### For backwards compatibility pending https://github.com/SheffieldSolar/Geocode/issues/6
+        gsp_regions["bounds"] = gsp_regions.bounds.apply(tuple, axis=1)
+        gsp_regions_dict = gsp_regions.set_index(["GSPs", "GSPGroup"]).to_dict("index")
+        for r in gsp_regions_dict:
+            gsp_regions_dict[r] = tuple(gsp_regions_dict[r].values())
+        ######
+        self.cache_manager.write(self.gsp_boundaries_20220314_cache_file, gsp_regions_dict)
+        logging.info("20220314 GSP boundaries extracted and pickled to '%s'",
+                     self.gsp_boundaries_20220314_cache_file)
+        return gsp_regions_dict
+
+    def _load_dno_boundaries(self):
+        """
+        Load the DNO License Area boundaries, either from local cache if available, else fetch from
+        ESO Data Portal API.
+        """
+        dno_boundaries_cache_contents = self.cache_manager.retrieve(self.dno_boundaries_cache_file)
+        if dno_boundaries_cache_contents is not None:
+            logging.debug("Loading DNO boundaries from cache ('%s')",
+                          self.dno_boundaries_cache_file)
+            return dno_boundaries_cache_contents
+        logging.info("Extracting the DNO License Area boundary data from NGESO's Data Portal API "
+                     "(this only needs to be done once)")
+        eso_url = "http://data.nationalgrideso.com/backend/dataset/0e377f16-95e9-4c15-a1fc-49e06a39cfa0/resource/e96db306-aaa8-45be-aecd-65b34d38923a/download/dno_license_areas_20200506.geojson"
+        success, api_response = utils.fetch_from_api(eso_url)
+        if success:
+            raw = json.loads(api_response.text)
+            dno_regions = {}
+            dno_names = {}
+            for f in raw["features"]:
+                region_id = f["properties"]["ID"]
+                dno_regions[region_id] = shape(f["geometry"]).buffer(0)
+                dno_names[region_id] = (f["properties"]["Name"], f["properties"]["LongName"])
+        else:
+            raise utils.GenericException("Encountered an error while extracting DNO License Area "
+                                   "boundary data from ESO API.")
+        dno_regions = {region_id: (dno_regions[region_id], dno_regions[region_id].bounds)
+                       for region_id in dno_regions}
+        self.cache_manager.write(self.dno_boundaries_cache_file, (dno_regions, dno_names))
+        logging.info("DNO License Area boundaries extracted and pickled to '%s'",
+                     self.dno_boundaries_cache_file)
+        return dno_regions, dno_names
+
+    def reverse_geocode_gsp(self,
+                            latlons: List[Tuple[float, float]]
+                           ) -> Tuple[List[int], List[List[Dict]]]:
+        """
+        Reverse-geocode latitudes and longitudes to GSP using the 20220314 definitions.
+
+        Parameters
+        ----------
+        `latlons` : list of tuples
+            A list of tuples containing (latitude, longitude).
+
+        Returns
+        -------
+        `results` : list of ints
+            A list of tuples containing (<GSPs>, <GSPGroup>), aligned with the input *latlons*.
+
+        Notes
+        -----
+        Return format needs some work, maybe switch to DataFrames in future release.
+        """
+        #if not SHAPELY_AVAILABLE:
+        #    raise utilities.GenericException("Geocode was unable to import the Shapely library, follow the "
+        #                           "installation instructions at "
+        #                           "https://github.com/SheffieldSolar/Geocode")
+        logging.debug("Reverse geocoding %s latlons to 20220314 GSP", len(latlons))
+        if self.gsp_regions is None:
+            self.gsp_regions = self._load_gsp_boundaries_20220314()
+        lats = [l[0] for l in latlons]
+        lons = [l[1] for l in latlons]
+        # Rather than re-project the region boundaries, re-project the input lat/lons
+        # (easier, but slightly slower if reverse-geocoding a lot)
+        logging.debug("Converting latlons to BNG")
+        eastings, northings = utils.latlon2bng(lons, lats)
+        logging.debug("Reverse geocoding")
+        results = utils.reverse_geocode(list(zip(northings, eastings)), self.gsp_regions)
+        return results
+
+    def reverse_geocode_gsp_20181031(self,
+                                     latlons: List[Tuple[float, float]]
+                                    ) -> Tuple[List[int], List[List[Dict]]]:
+        """
+        Reverse-geocode latitudes and longitudes to GSP using the 20181031 definitions.
+
+        Parameters
+        ----------
+        `latlons` : list of tuples
+            A list of tuples containing (latitude, longitude).
+
+        Returns
+        -------
+        `results` : list of ints
+            A list of region IDs, aligned with the input *latlons*.
+        `results_more` : list of lists of dicts
+            The full MANY:MANY lookup giving the Region <-> GSP <-> GNode which the lat/lon falls
+            within. The relationship between GSP: GNode is MANY:MANY, so each element of the outer
+            list is another list of matches, each element of which is a dictionary giving the
+            matched GSP / GNode.
+
+        Notes
+        -----
+        Return format needs some work, maybe switch to DataFrames in future release.
+        """
+        if not SHAPELY_AVAILABLE:
+            raise utils.GenericException("Geocode was unable to import the Shapely library, follow the "
+                                   "installation instructions at "
+                                   "https://github.com/SheffieldSolar/Geocode")
+        if self.gsp_regions_20181031 is None:
+            self.gsp_regions_20181031 = self.load_gsp_boundaries_20181031()
+        lats = [l[0] for l in latlons]
+        lons = [l[1] for l in latlons]
+        eastings, northings = utils.latlon2bng(lons, lats)
+        results = utils.reverse_geocode(list(zip(northings, eastings)), self.gsp_regions_20181031)
+        if self.gsp_lookup_20181031 is None:
+            self.gsp_lookup_20181031 = self.load_gsp_lookup_20181031()
+        lookup = self.gsp_lookup_20181031
+        reg_lookup = {r: lookup[lookup.region_id == r].to_dict(orient="records")
+                      for r in list(set(results))}
+        results_more = [reg_lookup[r] if r is not None else None for r in results]
+        return results, results_more
diff --git a/geocode/ons_nrs/PCD_OA_LSOA_MSOA_LAD_MAY22_UK_LU.zip b/geocode/ons/PCD_OA_LSOA_MSOA_LAD_MAY22_UK_LU.zip
similarity index 100%
rename from geocode/ons_nrs/PCD_OA_LSOA_MSOA_LAD_MAY22_UK_LU.zip
rename to geocode/ons/PCD_OA_LSOA_MSOA_LAD_MAY22_UK_LU.zip
diff --git a/geocode/google_maps/__init__.py b/geocode/ons/__init__.py
similarity index 100%
rename from geocode/google_maps/__init__.py
rename to geocode/ons/__init__.py
diff --git a/geocode/gov/constituency_centroids.psv b/geocode/ons/constituency_centroids.psv
similarity index 100%
rename from geocode/gov/constituency_centroids.psv
rename to geocode/ons/constituency_centroids.psv
diff --git a/geocode/gov/constituency_centroids_Dec2020.psv b/geocode/ons/constituency_centroids_Dec2020.psv
similarity index 99%
rename from geocode/gov/constituency_centroids_Dec2020.psv
rename to geocode/ons/constituency_centroids_Dec2020.psv
index fafcacb..64fb698 100644
--- a/geocode/gov/constituency_centroids_Dec2020.psv
+++ b/geocode/ons/constituency_centroids_Dec2020.psv
@@ -1,4 +1,4 @@
-﻿E14000530|Aldershot|-0.7841|51.288952
+E14000530|Aldershot|-0.7841|51.288952
 E14000531|Aldridge-Brownhills|-1.9316601|52.620869
 E14000532|Altrincham and Sale West|-2.3904901|53.397659
 E14000533|Amber Valley|-1.3977|53.042831
@@ -647,4 +647,4 @@ W07000076|Caerphilly|-3.2014401|51.587181
 W07000077|Islwyn|-3.15048|51.659618
 W07000078|Vale of Glamorgan|-3.4074199|51.448238
 W07000079|Cardiff West|-3.2629399|51.502571
-W07000080|Cardiff South and Penarth|-3.18191|51.439289
+W07000080|Cardiff South and Penarth|-3.18191|51.439289
\ No newline at end of file
diff --git a/geocode/gov/lad_centroids_May2021.psv b/geocode/ons/lad_centroids_May2021.psv
similarity index 99%
rename from geocode/gov/lad_centroids_May2021.psv
rename to geocode/ons/lad_centroids_May2021.psv
index 2ed7f9e..71dd718 100644
--- a/geocode/gov/lad_centroids_May2021.psv
+++ b/geocode/ons/lad_centroids_May2021.psv
@@ -371,4 +371,4 @@ W06000020|Torfaen|-3.0510099|51.69836
 W06000021|Monmouthshire|-2.9028001|51.778271
 W06000022|Newport|-2.8976901|51.58231
 W06000023|Powys|-3.4353099|52.34864
-W06000024|Merthyr Tydfil|-3.3642499|51.748581
+W06000024|Merthyr Tydfil|-3.3642499|51.748581
\ No newline at end of file
diff --git a/geocode/ons_nrs/nrs.zip b/geocode/ons/nrs.zip
similarity index 100%
rename from geocode/ons_nrs/nrs.zip
rename to geocode/ons/nrs.zip
diff --git a/geocode/ons_nrs.py b/geocode/ons_nrs.py
new file mode 100644
index 0000000..ba0edf1
--- /dev/null
+++ b/geocode/ons_nrs.py
@@ -0,0 +1,390 @@
+"""
+Manage data from the Office for National Statistics (ONS) and National Records Scotland (NRS).
+
+- Ethan Jones <ejones18@sheffield.ac.uk>
+- Jamie Taylor <jamie.taylor@sheffield.ac.uk>
+- First Authored: 2022-10-19
+"""
+
+import os
+import sys
+import zipfile
+import json
+import csv
+import logging
+import pandas as pd
+import shapefile
+
+from typing import Optional, Iterable, Tuple, Union, List, Dict
+try:
+    from shapely.geometry import shape, Point
+    from shapely.ops import unary_union
+    SHAPELY_AVAILABLE = True
+except ImportError:
+    logging.warning("Failed to import Shapely library - you will not be able to reverse-geocode! "
+                    "See notes in the README about installing Shapely on Windows machines.")
+    SHAPELY_AVAILABLE = False
+
+import utilities as utils
+
+SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
+
+class ONS_NRS:
+    """
+    Manage data from the Office for National Statistics (ONS) and National Records Scotland (NRS).
+    """
+    def __init__(self, cache_manager):
+        """
+        Manage data from the Office for National Statistics (ONS) and National Records Scotland
+        (NRS).
+        """
+        self.cache_manager = cache_manager
+        data_dir = os.path.join(SCRIPT_DIR, "ons")
+        self.nrs_zipfile = os.path.join(data_dir, "nrs.zip")
+        self.dz_lookup_cache_file = "datazone_lookup"
+        self.constituency_lookup_file = os.path.join(data_dir, "constituency_centroids_Dec2020.psv")
+        self.constituency_cache_file = os.path.join(data_dir, "constituency_centroids.psv")
+        self.lad_lookup_file = os.path.join(data_dir, "lad_centroids_May2021.psv")
+        self.lad_cache_file = "lad_centroids"
+        self.pc_llsoa_lookup_cache_file = "pc_llsoa_lookup"
+        self.pc_llsoa_zipfile = os.path.join(data_dir, "PCD_OA_LSOA_MSOA_LAD_MAY22_UK_LU.zip")
+        self.llsoa_lookup = None
+        self.llsoa_regions = None
+        self.llsoa_reverse_lookup = None
+        self.constituency_lookup = None
+        self.dz_lookup = None
+        self.lad_lookup = None
+        self.pc_llsoa_lookup = None
+
+    def force_setup(self):
+        """
+        Function to setup all lookup files.
+        """
+        self._load_llsoa_lookup()
+        self._load_llsoa_boundaries()
+        self._load_datazone_lookup()
+        self._load_constituency_lookup()
+        self._load_lad_lookup()
+        self._load_postcode_llsoa_lookup()
+
+    def _load_llsoa_lookup(self):
+        """Load the lookup of LLSOA -> Population Weighted Centroid."""
+        cache_label = "llsoa_centroids"
+        llsoa_lookup_cache_contents = self.cache_manager.retrieve(cache_label)
+        if llsoa_lookup_cache_contents is not None:
+            logging.debug("Loading LLSOA lookup from cache ('%s')", cache_label)
+            return llsoa_lookup_cache_contents
+        logging.info("Extracting the ONS and NRS LLSOA centroids data (this only needs to be done "
+                     "once)")
+        ons_url = "https://services1.arcgis.com/ESMARspQHYMw9BZ9/arcgis/rest/services/LSOA_Dec_2011_PWC_in_England_and_Wales_2022/FeatureServer/0/query?where=1%3D1&outFields=*&outSR=4326&f=geojson"
+        pages = utils._fetch_from_ons_api(ons_url)
+        engwales_lookup = {f["properties"]["lsoa11cd"]:
+                               tuple(f["geometry"]["coordinates"][::-1])
+                               for page in pages for f in page["features"]}
+        codes, eastings, northings = [], [], []
+        datazones, dzeastings, dznorthings = [], [], []
+        with zipfile.ZipFile(self.nrs_zipfile, "r") as nrs_zip:
+            with nrs_zip.open("OutputArea2011_PWC_WGS84.csv", "r") as fid:
+                next(fid)
+                for line in fid:
+                    _, _, code, _, easting, northing = line.decode('UTF-8').strip().split(",")
+                    codes.append(code)
+                    eastings.append(float(easting))
+                    northings.append(float(northing))
+            with nrs_zip.open("SG_DataZone_Cent_2011.csv") as fid:
+                next(fid)
+                contents = [l.decode('UTF-8') for l in fid.readlines()]
+                for line in csv.reader(contents, quotechar="\"", delimiter=",",
+                                       quoting=csv.QUOTE_ALL, skipinitialspace=True):
+                    datazone, _, _, _, _, dzeast, dznorth = line
+                    datazones.append(datazone)
+                    dzeastings.append(float(dzeast.strip("\"")))
+                    dznorthings.append(float(dznorth.strip("\"")))
+        lons, lats = utils.bng2latlon(eastings, northings)
+        dzlons, dzlats = utils.bng2latlon(dzeastings, dznorthings)
+        scots_lookup = {code: (lat, lon) for code, lon, lat in zip(codes, lons, lats)}
+        scots_dz_lookup = {dz: (lat, lon) for dz, lon, lat in zip(datazones, dzlons, dzlats)}
+        llsoa_lookup = {**engwales_lookup, **scots_lookup, **scots_dz_lookup}
+        self.cache_manager.write(cache_label, llsoa_lookup)
+        logging.info("LLSOA centroids extracted and pickled to file ('%s')", cache_label)
+        return llsoa_lookup
+
+    def _load_llsoa_boundaries(self):
+        """
+        Load the LLSOA boundaries, either from local cache if available, else fetch from raw API
+        (England and Wales) and packaged data (Scotland).
+        """
+        cache_label = "llsoa_boundaries"
+        llsoa_boundaries_cache_contents = self.cache_manager.retrieve(cache_label)
+        if llsoa_boundaries_cache_contents is not None:
+            logging.debug("Loading LLSOA boundaries from cache ('%s')", cache_label)
+            return llsoa_boundaries_cache_contents
+        logging.info("Extracting the LLSOA boundary data from ONS and NRS (this only needs to be "
+                     "done once)")
+        ons_url = "https://services1.arcgis.com/ESMARspQHYMw9BZ9/arcgis/rest/services/Lower_Layer_Super_Output_Areas_Dec_2011_Boundaries_Full_Extent_BFE_EW_V3_2022/FeatureServer/0/query?outFields=*&where=1%3D1&f=geojson"
+        nrs_shp_file = "OutputArea2011_EoR_WGS84.shp"
+        nrs_dbf_file = "OutputArea2011_EoR_WGS84.dbf"
+        pages = utils._fetch_from_ons_api(ons_url)
+        engwales_regions = {f["properties"]["LSOA11CD"]: shape(f["geometry"]).buffer(0)
+                            for page in pages for f in page["features"]}
+        with zipfile.ZipFile(self.nrs_zipfile, "r") as nrs_zip:
+            with nrs_zip.open(nrs_shp_file, "r") as shp:
+                with nrs_zip.open(nrs_dbf_file, "r") as dbf:
+                    sf = shapefile.Reader(shp=shp, dbf=dbf)
+                    scots_regions = {sr.record[1]: shape(sr.shape.__geo_interface__).buffer(0)
+                                     for sr in sf.shapeRecords()}
+        llsoa_regions = {**engwales_regions, **scots_regions}
+        llsoa_regions = {llsoacd: (llsoa_regions[llsoacd], llsoa_regions[llsoacd].bounds)
+                         for llsoacd in llsoa_regions}
+        self.cache_manager.write(cache_label, llsoa_regions)
+        logging.info("LSOA boundaries extracted and pickled to file ('%s')", cache_label)
+        return llsoa_regions
+
+    def _load_datazone_lookup(self):
+        """Load a lookup of Scottish LLSOA <-> Datazone."""
+        datazone_lookup_cache_contents = self.cache_manager.retrieve(self.dz_lookup_cache_file)
+        if datazone_lookup_cache_contents is not None:
+            logging.debug("Loading LLSOA<->Datazone lookup from cache ('%s')",
+                          self.dz_lookup_cache_file)
+            return datazone_lookup_cache_contents
+        with zipfile.ZipFile(self.nrs_zipfile, "r") as nrs_zip:
+            with nrs_zip.open("OA_DZ_IZ_2011.csv", "r") as fid:
+                dz_lookup = pd.read_csv(fid)
+        dz_lookup.set_index("OutputArea2011Code", inplace=True)
+        dz_lookup.drop(columns=["IntermediateZone2011Code"], inplace=True)
+        dz_lookup = dz_lookup.to_dict()["DataZone2011Code"]
+        self.cache_manager.write(self.dz_lookup_cache_file, dz_lookup)
+        return dz_lookup
+
+    def _load_constituency_lookup(self):
+        """Load a lookup of UK constituency -> Geospatial Centroid."""
+        constituency_lookup_cache_contents = self.cache_manager.retrieve(self.constituency_cache_file)
+        if constituency_lookup_cache_contents is not None:
+            logging.debug("Loading Constituency lookup from cache ('%s')",
+                      self.constituency_cache_file)
+            return constituency_lookup_cache_contents
+        logging.info("Extracting the Constituency Centroids data (this only needs to be done "
+                     "once)")
+        constituency_lookup = {}
+        with open(self.constituency_lookup_file) as fid:
+            for line in fid:
+                _, name, longitude, latitude = line.strip().split("|")
+                match_str = name.strip().replace(" ", "").replace(",", "").lower()
+                constituency_lookup[match_str] = (float(latitude), float(longitude))
+        self.cache_manager.write(self.constituency_cache_file, constituency_lookup)
+        logging.info("Constituency lookup extracted and pickled to '%s'",
+                     self.constituency_cache_file)
+        return constituency_lookup
+
+    def geocode_llsoa(self,
+                      llsoa: Union[str, Iterable[str]]
+                     ) -> Union[Tuple[float, float], List[Tuple[float, float]]]:
+        """
+        Geocode an LLSOA using the Population Weighted Centroid.
+
+        Parameters
+        ----------
+        `llsoa` : string or iterable of strings
+            The LLSOA(s) to geocode.
+
+        Returns
+        -------
+        Tuple or List of Tuples
+            If a single LLSOA was passed (i.e. a string), the output be a tuple containing: latitude
+            (float), longitude (float). If several LLSOAs are passed (i.e. an iterable of strings),
+            the output will be a list of tuples which aligns with the input iterable.
+
+        Notes
+        -----
+        The input *llsoa* iterable can be any Python object which can be looped over with a for loop
+        e.g. a list, tuple, Numpy array etc.
+        LLSOAs are identified by their LLSOA Code, sometimes abbreviated to `llsoacd`.
+        In Scotland, one can use either LLSOA codes or Datazones, this method supports both.
+        """
+        if self.llsoa_lookup is None:
+            self.llsoa_lookup = self._load_llsoa_lookup()
+        if isinstance(llsoa, str):
+            return self._llsoa_centroid(llsoa)
+        results = []
+        tot = len(llsoa)
+        for i, llsoa_ in enumerate(llsoa):
+            results.append(self._llsoa_centroid(llsoa_))
+        return results
+
+    def reverse_geocode_llsoa(self,
+                              latlons: List[Tuple[float, float]],
+                              datazones: bool = False) -> List[str]:
+        """
+        Reverse-geocode latitudes and longitudes to LLSOA.
+
+        Parameters
+        ----------
+        `latlons` : list of tuples
+            A list of tuples containing (latitude, longitude).
+        `datazones` : bool
+            Set this to True to return Datazones rather than LLSOA codes in Scotland (default is
+            False).
+
+        Returns
+        -------
+        list of strings
+            The LLSOA codes that the input latitudes and longitudes fall within. Any lat/lons which
+            do not fall inside an LLSOA boundary will return None.
+        """
+        if not SHAPELY_AVAILABLE:
+            raise utils.GenericException("Geocode was unable to import the Shapely library, follow the "
+                                   "installation instructions at "
+                                   "https://github.com/SheffieldSolar/Geocode")
+        if self.llsoa_regions is None:
+            self.llsoa_regions = self._load_llsoa_boundaries()
+        results = utils.reverse_geocode(latlons, self.llsoa_regions)
+        if datazones:
+            if self.dz_lookup is None:
+                self.dz_lookup = self._load_datazone_lookup()
+            results = [llsoacd if llsoacd not in self.dz_lookup else self.dz_lookup[llsoacd]
+                       for llsoacd in results]
+        return results
+
+    def geocode_constituency(self,
+                             constituency: Union[str, Iterable[str]]
+                            ) -> Union[Tuple[float, float], List[Tuple[float, float]]]:
+        """
+        Geocode a UK Constituency using the geospatial centroid.
+
+        Parameters
+        ----------
+        `constituency` : string or iterable of strings
+            The constituency names to geocode.
+
+        Returns
+        -------
+        tuple or list of tuples
+            If a single constituency name was passed (i.e. a string), the output be a tuple
+            containing: latitude (float), longitude (float). If several constituency names are
+            passed (i.e. an iterable of strings), the output will be a list of tuples which aligns
+            with the input iterable.
+
+        Notes
+        -----
+        The input *constituency* iterable can be any Python object which can be looped over with a
+        for loop e.g. a list, tuple, Numpy array etc.
+        Constituencies are identified by their full name, case-insensitive, ignoring spaces.
+        """
+        if self.constituency_lookup is None:
+            self.constituency_lookup = self._load_constituency_lookup()
+        if isinstance(constituency, str):
+            return self._constituency_centroid(constituency)
+        results = []
+        for constituency_ in constituency:
+            results.append(self._constituency_centroid(constituency_))
+        return results
+
+    def geocode_local_authority(self,
+                                local_authority: Union[str, Iterable[str]]
+                               ) -> Union[Tuple[float, float], List[Tuple[float, float]]]:
+        """
+        Geocode a UK Local Authority using the geospatial centroid.
+        Parameters
+        ----------
+        `local_authority` : string or iterable of strings
+            The Local Authority names to geocode.
+        Returns
+        -------
+        tuple or list of tuples
+            If a single Local Authority name was passed (i.e. a string), the output be a tuple
+            containing: latitude (float), longitude (float). If several Local Authority names are
+            passed (i.e. an iterable of strings), the output will be a list of tuples which aligns
+            with the input iterable.
+        Notes
+        -----
+        The input *local_authority* iterable can be any Python object which can be looped over with
+        a for loop e.g. a list, tuple, Numpy array etc.
+        Local Authorities are identified by their full name, case-insensitive, ignoring spaces.
+        """
+        if self.lad_lookup is None:
+            self.lad_lookup = self._load_lad_lookup()
+        if isinstance(local_authority, str):
+            return self._lad_centroid(local_authority)
+        results = []
+        tot = len(local_authority)
+        for i, local_authority_ in enumerate(local_authority):
+            results.append(self._lad_centroid(local_authority_))
+        return results
+
+    def postcode2llsoa(self, pcs: pd.DataFrame) -> pd.DataFrame:
+        """
+        Reverse-geocode multiple postcodes to LLSOA.
+        Parameters
+        ----------
+        `pcs` : Pandas.DataFrame
+            A Pandas DataFrame containing the postcodes (or partial postcodes) to geocode. Must
+            contain the column 'input_postcode'.
+        Returns
+        -------
+        Pandas.DataFrame
+            A Pandas DataFrame with columns: input_postcode (str), lsoa11cd (str), plus any other
+            cols that were in `pcs`.
+        """
+        pcs["postcode_to_match"] = pcs.input_postcode.str.strip().str.upper().str.replace(" ", "")
+        if self.pc_llsoa_lookup is None:
+            self.pc_llsoa_lookup = self._load_postcode_llsoa_lookup()
+        pcs = pcs.merge(self.pc_llsoa_lookup[["postcode", "lsoa11cd"]], how="left",
+                        left_on="postcode_to_match", right_on="postcode")
+        pcs.drop(columns=["postcode_to_match"], inplace=True)
+        return pcs
+
+    def _load_lad_lookup(self):
+        """Load a lookup of UK Local Authority District -> Geospatial Centroid."""
+        lad_lookup_cache_contents = self.cache_manager.retrieve(self.lad_cache_file)
+        if lad_lookup_cache_contents is not None:
+            logging.debug("Loading Local Authority District lookup from cache ('%s')",
+                          self.lad_cache_file)
+            return lad_lookup_cache_contents
+        logging.info("Extracting the Local Authority District Centroids data (this only needs to "
+                     "be done once)")
+        lad_lookup = {}
+        with open(self.lad_lookup_file) as fid:
+            for line in fid:
+                _, name, longitude, latitude = line.strip().split("|")
+                match_str = name.strip().replace(" ", "").replace(",", "").lower()
+                lad_lookup[match_str] = (float(latitude), float(longitude))
+        self.cache_manager.write(self.lad_cache_file, lad_lookup)
+        logging.info("Local Authority District lookup extracted and pickled to '%s'",
+                     self.lad_cache_file)
+        return lad_lookup
+
+    def _load_postcode_llsoa_lookup(self):
+        """Load a lookup of postcode <-> LLSOA."""
+        postcode_llsoa_lookup_cache_contents = self.cache_manager.retrieve(self.pc_llsoa_lookup_cache_file)
+        if postcode_llsoa_lookup_cache_contents is not None:
+            logging.debug("Loading postcode<->LLSOA lookup from cache ('%s')",
+                          self.pc_llsoa_lookup_cache_file)
+            return postcode_llsoa_lookup_cache_contents
+        pc_llsoa_lookup = pd.read_csv(self.pc_llsoa_zipfile, dtype=str)
+        pc_llsoa_lookup["postcode"] = pc_llsoa_lookup.pcds.str.strip().str.upper()\
+                                                                      .str.replace(" ", "")
+        self.cache_manager.write(self.pc_llsoa_lookup_cache_file, pc_llsoa_lookup)
+        return pc_llsoa_lookup
+
+    def _lad_centroid(self, local_authority):
+        """Lookup the GC for a given Local Authority."""
+        try:
+            match_str = local_authority.strip().replace(" ", "").replace(",", "").lower()
+            return self.lad_lookup[match_str]
+        except KeyError:
+            return None, None
+
+    def _constituency_centroid(self, constituency):
+        """Lookup the GC for a given constituency."""
+        try:
+            match_str = constituency.strip().replace(" ", "").replace(",", "").lower()
+            return self.constituency_lookup[match_str]
+        except KeyError:
+            return None, None
+
+    def _llsoa_centroid(self, llsoa):
+        """Lookup the PWC for a given LLSOA code."""
+        try:
+            return self.llsoa_lookup[llsoa]
+        except KeyError:
+            return None, None
diff --git a/geocode/ons_nrs/__init__.py b/geocode/ons_nrs/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/geocode/postcodes2latlon.py b/geocode/postcodes2latlon.py
index 739aafa..b8f8f69 100644
--- a/geocode/postcodes2latlon.py
+++ b/geocode/postcodes2latlon.py
@@ -3,6 +3,7 @@
 Load a list of postcodes from a CSV file and geocode them to latitude/longitude.
 
 - Jamie Taylor <jamie.taylor@sheffield.ac.uk>
+- Ethan Jones <ejones18@sheffield.ac.uk>
 - First Authored: 2020-06-12
 """
 
@@ -12,13 +13,14 @@
 import time as TIME
 import pandas as pd
 
-from geocode import Geocoder, query_yes_no
+from geocode import Geocoder
+from utilities import query_yes_no, GenericException
 
 def parse_options():
     """Parse command line options."""
     parser = argparse.ArgumentParser(description=("This is a command line interface (CLI) for "
                                                   "the postcodes2latlon.py module"),
-                                     epilog="Jamie Taylor, 2020-06-12")
+                                     epilog="Jamie Taylor & Ethan Jones, 2020-06-12")
     parser.add_argument("-f", "--input-file", dest="infile", action="store", type=str,
                         required=True, metavar="</path/to/file>",
                         help="Specify a CSV file containing a list of postcodes to be geocoded. "
@@ -28,7 +30,7 @@ def parse_options():
                         required=True, metavar="</path/to/file>", help="Specify an output file.")
     options = parser.parse_args()
     if not os.path.isfile(options.infile):
-        raise Exception(f"The input file '{options.infile}' does not exist.")
+        raise GenericException(f"The input file '{options.infile}' does not exist.")
     if os.path.isfile(options.outfile):
         check = query_yes_no(f"The outfile '{options.outfile}' already exists, are you sure you "
                              "wish to overwrite?", "no")
@@ -42,8 +44,8 @@ def main():
     options = parse_options()
     with open(options.infile, "r") as fid:
         df = pd.read_csv(fid).iloc[:100]
-    with Geocoder(progress_bar=True) as geo:
-        results = geo.geocode(postcodes=df["postcode"].to_numpy())
+    with Geocoder() as geo:
+        results = geo.geocode_postcode(df["postcode"].to_numpy())
         lats = [r[0] if r[0] is not None else pd.NA for r in results]
         lons = [r[1] if r[1] is not None else pd.NA for r in results]
         status = [geo.status_codes[r[2]] for r in results]
diff --git a/geocode/utilities.py b/geocode/utilities.py
new file mode 100644
index 0000000..56dfa81
--- /dev/null
+++ b/geocode/utilities.py
@@ -0,0 +1,352 @@
+"""
+Utilities for the Geocode library.
+
+- Ethan Jones <ejones18@sheffield.ac.uk>
+- Jamie Taylor <jamie.taylor@sheffield.ac.uk>
+- First Authored: 2022-10-19
+"""
+
+import sys
+import logging
+import requests
+import json
+
+from typing import Optional, Iterable, Tuple, Union, List, Dict
+import pyproj
+
+try:
+    from shapely.geometry import shape, Point
+    from shapely.ops import unary_union
+    SHAPELY_AVAILABLE = True
+except ImportError:
+    logging.warning("Failed to import Shapely library - you will not be able to reverse-geocode! "
+                    "See notes in the README about installing Shapely on Windows machines.")
+    SHAPELY_AVAILABLE = False
+
+class GenericException(Exception):
+    """A generic exception for anticipated errors."""
+    def __init__(self, msg, err=None):
+        self.msg = msg
+        if err is not None:
+            self.msg += f"\n{repr(err)}"
+        logging.exception(self.msg)
+
+    def __str__(self):
+        return self.msg
+
+def print_progress(iteration, total, prefix="", suffix="", decimals=2, bar_length=100):
+    """
+    Call in a loop to create terminal progress bar.
+
+    Parameters
+    ----------
+    `iteration` : int
+        current iteration (required)
+    `total` : int
+        total iterations (required)
+    `prefix` : string
+        prefix string (optional)
+    `suffix` : string
+        suffix string (optional)
+    `decimals` : int
+        number of decimals in percent complete (optional)
+    `bar_length` : int
+        character length of bar (optional)
+
+    Notes
+    -----
+    Taken from `Stack Overflow <http://stackoverflow.com/a/34325723>`_.
+    """
+    filled_length = int(round(bar_length * iteration / float(total)))
+    percents = round(100.00 * (iteration / float(total)), decimals)
+    progress_bar = "#" * filled_length + "-" * (bar_length - filled_length)
+    sys.stdout.write("\r%s |%s| %s%s %s" % (prefix, progress_bar, percents, "%", suffix))
+    sys.stdout.flush()
+    if iteration == total:
+        sys.stdout.write("\n")
+        sys.stdout.flush()
+
+def query_yes_no(question, default="yes"):
+    """
+    Ask a yes/no question via input() and return the answer as boolean.
+
+    Parameters
+    ----------
+    `question` : string
+        The question presented to the user.
+    `default` : string
+        The presumed answer if the user just hits <Enter>. It must be "yes" (the default), "no" or
+        None (meaning an answer is required of the user).
+
+    Returns
+    -------
+    boolean
+        Return value is True for "yes" or False for "no".
+
+    Notes
+    -----
+    See http://stackoverflow.com/a/3041990
+    """
+    valid = {"yes": True, "y": True, "ye": True,
+             "no": False, "n": False}
+    if default is None:
+        prompt = " [y/n] "
+    elif default == "yes":
+        prompt = " [Y/n] "
+    elif default == "no":
+        prompt = " [y/N] "
+    else:
+        raise ValueError("invalid default answer: '%s'" % default)
+    while True:
+        sys.stdout.write(question + prompt)
+        choice = input().lower()
+        if default is not None and choice == '':
+            return valid[default]
+        if choice in valid:
+            return valid[choice]
+        sys.stdout.write("Please respond with 'yes' or 'no' (or 'y' or 'n').\n")
+
+def geocode(self,
+                postcodes: Optional[Iterable[str]] = None,
+                addresses: Optional[Iterable[str]] = None) -> List[Tuple[float, float, int]]:
+        """
+        Geocode several postcodes and/or addresses.
+
+        Parameters
+        ----------
+        `postcodes` : iterable of strings
+            The postcodes (or partial postcodes) to geocode. Must align with *addresses* if both are
+            passed.
+        `addresses` : iterable of strings
+            The addresses to geocode. Must align with *postcodes* if both are passed. If addresses
+            are passed, the GMaps API will be used since OS Code Point Open does not provide address
+            lookup.
+
+        Returns
+        -------
+        list of tuples
+            The output list will align with the input postcodes/addresses, with each element being a
+            tuple containing: latitude (float), longitude (float), status code (int). The status
+            code shows the quality of the postcode lookup - use the class attribute
+            *self.status_codes* (a dict) to get a string representation.
+
+        Notes
+        -----
+        The input iterables can be any Python object which can be interpreted by Pandas.DataFrame()
+        e.g. a list, tuple, Numpy array etc.
+        If you pass only postcodes, this method will priotiise OS Code Point Open as the geocoder.
+        If a postcode fails to geocode using OS CPO, it will be geocoded with GMaps.
+        """
+        raise utilities.GenericException("Function is deprecated")
+        results = []
+        if postcodes is None and addresses is None:
+            raise utilities.GenericException("You must pass either postcodes or addresses, or both.")
+        postcodes = [None for a in addresses] if postcodes is None else list(postcodes)
+        addresses = [None for p in postcodes] if addresses is None else list(addresses)
+        logging.debug("Geocoding %s postcodes (%s addresses)", len(postcodes), len(addresses))
+        inputs = pd.DataFrame({"input_postcode": postcodes, "input_address": addresses})
+        inputs["id"] = np.arange(inputs.shape[0])
+        results = inputs.merge(self.cache, how="left", on=["input_postcode", "input_address"])
+        results_cols = ["latitude", "longitude", "match_status"]
+        if not results.latitude.isnull().any():
+            return results[results_cols].to_records(index=False)
+        use_cpo = results.latitude.isnull() & results.input_address.isnull()
+        if use_cpo.any():
+            logging.debug("Geocoding %s postcodes using Code Point Open", use_cpo.sum())
+            results.loc[use_cpo, results_cols] = \
+                cpo_manager.cpo_geocode(inputs.loc[use_cpo, ["input_postcode"]])[results_cols]
+            logging.debug("Successfully geocoded %s postcodes using Code Point Open",
+                          (results.latitude.notnull()).sum())
+        use_gmaps = results.latitude.isnull()
+        if use_gmaps.any():
+            logging.debug("Geocoding %s postcodes/addresses using Google Maps", use_gmaps.sum())
+            gmaps_geocode = lambda i: gmaps_manager.gmaps_geocode_one(i.input_postcode, i.input_address)
+            results.loc[use_gmaps, results_cols] = \
+                inputs.loc[use_gmaps].apply(gmaps_geocode, axis=1)[results_cols]
+            logging.debug("Successfully geocoded %s postcodes/addresses using Google Maps",
+                          (use_gmaps & results.latitude.notnull()).sum())
+        results["match_status"] = results.match_status.astype(int)
+        logging.debug("Adding postcodes/addresses to cache")
+        cache = pd.concat([self.cache, results[results.latitude.notnull()].drop(columns="id")],
+                          ignore_index=True)
+        self.cache = cache.drop_duplicates(subset=["input_postcode", "input_address"], keep="last",
+                                           ignore_index=True)
+        return results[results_cols].to_records(index=False)
+
+def geocode_one(self,
+                postcode: Optional[str] = None,
+                address: Optional[str] = None) -> Tuple[float, float, int]:
+    """
+    Geocode a single postcode and/or address.
+
+    Parameters
+    ----------
+    `postcode` : string
+        The postcode (or partial postcode) to geocode.
+    `address` : string
+        The address to geocode. If address is passed, the GMaps API will be used since OS Code
+        Point Open does not provide address lookup.
+
+    Returns
+    -------
+    tuple
+        A tuple containing: latitude (float), longitude (float), status code (int). The status
+        code shows the quality of the postcode lookup - use the class attribute
+        *self.status_codes* (a dict) to get a string representation.
+
+    Notes
+    -----
+    If you pass only postcode, this method will priotiise OS Code Point Open as the geocoder.
+    If a postcode fails to geocode using OS CPO, it will be geocoded with GMaps.
+    """
+    raise utilities.GenericException("Function is deprecated")
+    if postcode is None and address is None:
+        raise utilities.GenericException("You must specify either a postcode or an address, or both.")
+    if address is None:
+        lat, lon, status = cpo_manager.cpo_geocode_one(postcode)
+        if status > 0:
+            return lat, lon, status
+    return gmaps_manager.gmaps_geocode_one(postcode, address).to_records(index=False)
+
+def reverse_geocode(coords: List[Tuple[float, float]],
+                    regions: Dict,
+                    show_progress : bool = None,
+                    prefix : str = None) -> List:
+    """
+    Generic method to reverse-geocode x, y coordinates to regions.
+
+    Parameters
+    ----------
+    `coords` : list of tuples
+        A list of tuples containing (y, x).
+    `regions` : dict
+        Dict whose keys are the region IDs and whose values are a tuple containing:
+        (region_boundary, region_bounds). The region boundary must be a Shapely
+        Polygon/MultiPolygon and the bounds should be a tuple containing (xmin, ymin, xmax,
+        ymax).
+
+    Returns
+    -------
+    list
+        The region IDs that the input coords fall within. Any coords which do not fall inside an
+        LLSOA boundary will return None.
+
+    Notes
+    -----
+    The region bounds are used to improve performance by first scanning for potential region
+    candidates using a simple inequality, since the performance of
+    `Shapely.MultiPolygon.contains()` is not great.
+    """
+    if not SHAPELY_AVAILABLE:
+        raise utilities.GenericException("Geocode was unable to import the Shapely library, follow the "
+                                         "installation instructions at "
+                                         "https://github.com/SheffieldSolar/Geocode")
+    results = []
+    tot = len(coords)
+    for i, (y, x) in enumerate(coords):
+        success = False
+        possible_matches = []
+        for reg_id in regions:
+            bounds = regions[reg_id][1]
+            if bounds[0] <= x <= bounds[2] and bounds[1] <= y <= bounds[3]:
+                possible_matches.append(reg_id)
+        for reg_id in possible_matches:
+            if regions[reg_id][0].contains(Point(x, y)):
+                results.append(reg_id)
+                success = True
+                break
+        if not success:
+            results.append(None)
+    return results
+
+def _fetch_from_ons_api(url):
+    """Download data from the ONS ARCGIS API which uses pagination."""
+    exceeded_transfer_limit = True
+    offset = 0
+    record_count = 2000
+    pages = []
+    while exceeded_transfer_limit:
+        url_ = f"{url}&resultOffset={offset}&resultRecordCount={record_count}"
+        success, api_response = fetch_from_api(url_)
+        if success:
+            page = json.loads(api_response.text)
+            exceeded_transfer_limit = "properties" in page and \
+                                      "exceededTransferLimit" in page["properties"] and \
+                                      page["properties"]["exceededTransferLimit"]
+            pages.append(page)
+            offset += record_count
+        else:
+            raise utils.GenericException("Encountered an error while extracting LLSOA data from ONS API.")
+    return pages
+
+def fetch_from_api(url):
+    """Generic function to GET data from web API with retries."""
+    retries = 0
+    while retries < 3:
+        try:
+            response = requests.get(url)
+            response.raise_for_status()
+            if response.status_code != 200:
+                retries += 1
+                continue
+            return 1, response
+        except:
+            retries += 1
+    return 0, None
+
+def latlon2bng(lons: List[float], lats: List[float]) -> Tuple[List[float], List[float]]:
+    """
+    Convert latitudes and longitudes (WGS 1984) to Eastings and Northings (a.k.a British
+    National Grid a.k.a OSGB 1936).
+
+    Parameters
+    ----------
+    `lons` : list of floats
+        Corresponding longitude co-ordinates in WGS 1984 CRS.
+    `lats` : list of floats
+        Corresponding latitude co-ordinates in WGS 1984 CRS.
+
+    Returns
+    -------
+    `eastings` : list of floats or ints
+        Easting co-ordinates.
+    `northings` : list of floats or ints
+        Northing co-ordinates.
+
+    Notes
+    -----
+    Be careful! This method uses the same convention of ordering (eastings, northings) and
+    (lons, lats) as pyproj i.e. (x, y). Elsewhere in this module the convention is typically
+    (lats, lons) due to personal preference.
+    """
+    proj = pyproj.Transformer.from_crs(4326, 27700, always_xy=True)
+    eastings, northings = proj.transform(lons, lats)
+    return eastings, northings
+
+def bng2latlon(eastings: Iterable[Union[float, int]],
+                    northings: Iterable[Union[float, int]]) -> Tuple[List[float], List[float]]:
+        """
+        Convert Eastings and Northings (a.k.a British National Grid a.k.a OSGB 1936) to latitudes
+        and longitudes (WGS 1984).
+        Parameters
+        ----------
+        `eastings` : iterable of floats or ints
+            Easting co-ordinates.
+        `northings` : iterable of floats or ints
+            Northing co-ordinates.
+        Returns
+        -------
+        `lons` : list of floats
+            Corresponding longitude co-ordinates in WGS 1984 CRS.
+        `lats` : list of floats
+            Corresponding latitude co-ordinates in WGS 1984 CRS.
+        Notes
+        -----
+        Be careful! This method uses the same convention of ordering (eastings, northings) and
+        (lons, lats) as pyproj i.e. (x, y). Elsewhere in this module the convention is typically
+        (lats, lons) due to personal preference.
+        """
+        proj = pyproj.Transformer.from_crs(27700, 4326, always_xy=True)
+        lons, lats = proj.transform(eastings, northings)
+        return lons, lats
+
diff --git a/geocode/version.py b/geocode/version.py
new file mode 100644
index 0000000..a4219a8
--- /dev/null
+++ b/geocode/version.py
@@ -0,0 +1,1 @@
+__version__ = "0.11.0"
\ No newline at end of file
diff --git a/setup.py b/setup.py
index 690345e..1b0b317 100644
--- a/setup.py
+++ b/setup.py
@@ -16,7 +16,7 @@
 
 # Get the long description from the README file
 with open(path.join(here, "README.md"), encoding="utf-8") as f:
-    long_description = f.read()
+    long_description = f.read().replace("\r", "")
 
 setup(
     name="geocode",
@@ -24,10 +24,11 @@
     # Versions should comply with PEP440.  For a discussion on single-sourcing
     # the version across setup.py and the project code, see
     # https://packaging.python.org/en/latest/single_source_version.html
-    version="0.10.5",
+    version="0.11.0",
 
     description="Geocode postcodes, addresses or LLSOA using the Code Point Open database and GMaps API.",
     long_description=long_description,
+    long_description_content_type="text/markdown",
 
     # The project's main homepage.
     url="https://github.com/SheffieldSolar/Geocode",
@@ -60,9 +61,10 @@
         # Specify the Python versions you support here. In particular, ensure
         # that you indicate whether you support Python 2, Python 3 or both.
         "Programming Language :: Python :: 3 :: Only",
-        "Programming Language :: Python :: 3.6",
         "Programming Language :: Python :: 3.7",
         "Programming Language :: Python :: 3.8",
+        "Programming Language :: Python :: 3.9",
+        "Programming Language :: Python :: 3.10",
     ],
 
     # What does your project relate to?
@@ -85,10 +87,14 @@
         "pandas",
         "googlemaps",
         "requests",
-        "shapely",
+        "shapely>=1.7.0",
         "pyshp",
-        "pyproj",
-        "geopandas",
+        "pyproj>=2.6.0",
+        "cython>0.15.1",
+        "cartopy",
+        "fiona",
+        "rtree",
+        "geopandas"
     ],
 
     # List additional groups of dependencies here (e.g. development
@@ -96,7 +102,10 @@
     # for example:
     # $ pip install -e .[dev,test]
     extras_require={
-        
+        "pylint",
+        "sphinx",
+        "numpydoc",
+        "sphinx_rtd_theme"
     },
 
     # If there are data files included in your packages that need to be


Test patch:
diff --git a/Tests/test_geocode.py b/Tests/test_geocode.py
index 0d7fbd4..f94979e 100644
--- a/Tests/test_geocode.py
+++ b/Tests/test_geocode.py
@@ -6,21 +6,22 @@
 
     $ python -m unittest Tests.test_geocode
 
-Jamie Taylor 2020-05-22
+Jamie Taylor & Ethan Jones 2020-05-22
 """
 
+import sys
 import unittest
-from numpy.testing import assert_almost_equal
-import pandas as pd
-from pandas.testing import assert_frame_equal
+from numpy.testing import assert_almost_equal, assert_equal
 
+sys.path.append("../geocode/")
 from geocode import Geocoder
 
 class geocodeTestCase(unittest.TestCase):
     """Tests for `geocode.py`."""
-    def setUp(self):
-        with Geocoder(progress_bar=False) as geo:
-            geo.clear_cache(delete_gmaps_cache=True)
+    #def setUp(self):
+    #    with Geocoder() as geo:
+    #        geo.cache_manager.clear(delete_gmaps_cache=True)
+    #        geo.force_setup()
 
     def test_geocode_llsoa(self):
         """
@@ -35,74 +36,75 @@ def test_geocode_llsoa(self):
             (55.94492620443608, -4.333451009831742),
             (55.91836588770352, -4.21934323024909)
         ]
-        with Geocoder(progress_bar=False) as geo:
+        with Geocoder() as geo:
             assert_almost_equal(geo.geocode_llsoa(llsoas), centroids)
 
     def test_reverse_geocode_llsoa(self):
         """
         Test the `reverse_geocode_llsoa()` function with several test cases.
         """
-        llsoas = ["E01012082", "E01011214", "E01002050", "W01000323", "S00101253"]
+        llsoas = ["E01012082", "E01011214", "E01002050", "W01000323"]
         datazones = ["S01008087"]
         latlons = [
             (54.547776537068664, -1.195629080286167),
             (53.666095344794648, -1.703771184460476),
             (51.578729873335718, -0.068445270723745),
-            (53.207256254835059, -3.13247635788833),
-            (55.94492620443608, -4.333451009831742)
+            (53.207256254835059, -3.13247635788833)
         ]
         datazone_latlons = [(55.91836588770352, -4.21934323024909)]
-        with Geocoder(progress_bar=False) as geo:
+        with Geocoder() as geo:
             self.assertEqual(geo.reverse_geocode_llsoa(latlons), llsoas)
-            self.assertEqual(geo.reverse_geocode_llsoa(datazone_latlons, datazones=True), datazones)
+            self.assertEqual(geo.reverse_geocode_llsoa(datazone_latlons, dz=True), datazones)
+
+    def test_reverse_geocode_gsp(self):
+        """
+        Test the `reverse_geocode_gsp` function with several test cases.
+        """
+        gsp_regions = [("BRED_1", "_G"), ('DEWP', '_N')]
+        latlons = [
+            (53.33985, -2.051880),
+            (55.950095, -3.178485)
+        ]
+        with Geocoder() as geo:
+            assert_equal(geo.reverse_geocode_gsp(latlons), gsp_regions)
 
     def test_geocode_constituency(self):
         """
         Test the `geocode_constituency()` function with several test cases.
         """
-        constituencies = ["Banbury", "Cardiff Central",
-                          "Inverclyde"]
+        constituencies = ["Poplar and Limehouse", "Blyth Valley"]
         latlons = [
-            (52.000099, -1.4026),
-            (51.505779, -3.16395),
-            (55.900299, -4.75387),
+            (51.507938, -0.015729999),
+            (55.092758, -1.56095)
         ]
-        with Geocoder(progress_bar=False) as geo:
+        with Geocoder() as geo:
             assert_almost_equal(geo.geocode_constituency(constituencies), latlons)
 
     def test_geocode_local_authority(self):
         """
         Test the `geocode_local_authority()` function with several test cases.
         """
-        lads = ["Blackpool", "Armagh City, Banbridge and Craigavon", "Bristol, City of"]
+        lads = ["Medway", "Barrow-in-Furness", "Derry City and Strabane"]
         latlons = [
-            (53.82164, -3.0219901),
-            (54.3867, -6.4345498),
-            (51.471149, -2.57742),
+            (51.44772, 0.56317401),
+            (54.15731, -3.1998999),
+            (54.80904, -7.42064)
         ]
-        with Geocoder(progress_bar=False) as geo:
-            assert_almost_equal(geo.geocode_local_authority(lads), latlons)
+        with Geocoder() as geo:
+           assert_almost_equal(geo.geocode_local_authority(lads), latlons)
 
-    def test_postcode2llsoa(self):
+    def test_geocode_postcode(self):
         """
-        Test the `postcode2llsoa()` function with several test cases.
+        Test the `geocode_postcode()` function with several test cases.
         """
-        postcodes = pd.DataFrame({"input_postcode": ["IP1 5HX", "NE25 8EA", "SK14 2SF", "UB4 9UA"]})
-        expected = pd.DataFrame({
-            "input_postcode": ["IP1 5HX", "NE25 8EA", "SK14 2SF", "UB4 9UA"],
-            "postcode": ["IP15HX", "NE258EA", "SK142SF", "UB49UA"],
-            "lsoa11cd": ["E01030028", "E01008520", "E01006028", "E01002545"],
-        })
-        with Geocoder(progress_bar=False) as geo:
-            assert_frame_equal(geo.postcode2llsoa(postcodes), expected)
-
-    # def test_reverse_geocode_gsp(self):
-        # """
-        # Test the `reverse_geocode_gsp` function with several test cases.
-        # """
-        
-        # with Geocoder(progress_bar=False) as geo:
-            # assertAlmostEqual(geo.geocode_constituency(constituencies), latlons)
+        postcodes = ["RG1 3PE", "S10 2FR", "FY2 0SQ"]
+        latlons = [
+            (51.45511, -0.94070, 1),
+            (53.37708, -1.48700, 1),
+            (53.85414,-3.02139, 1)
+        ]
+        with Geocoder() as geo:
+           assert_almost_equal(geo.geocode_postcode(postcodes).tolist(), latlons, decimal=4)
 
 if __name__ == "__main__":
     unittest.main()