-1

I look after the website for a walking club in SW England, which shows a list of forthcoming walks. Each walk entry has an OS Grid Reference for the start of the walk, and a UK Postcode which a walker can enter to their SatNav device, to help them drive to the start of the walk. I currently get the nearest postcode to the starting point grid reference for each walk, using the API for Nearby UK, which also gives me the compass bearing and distance from my grid reference to the centre of the postcode, which in a country area can be a mile or more from the walk start. From these I work out the grid reference at the postcode centre, and then I can show both points as markers on an OS map - so far, so good.

Having recently completed the migration from OS Open Space to OS Data Hub, I wonder if I could also use OS Data Hub to give me the nearest postcode to a grid reference, plus either bearing and distance from one to the other, or the grid reference of the postcode centre, rather than needing to use the Nearby API for this purpose.

I asked the Customer Success team at Ordnance Survey about this, about a month ago, but no help from them yet. I've also tried various ways of using the UK Postcodes Database, which lists every UK postcode with its Eastings and Northings co-ordinates, but searching through the entire list looking for the nearest co-ordinates, using Pythagoras to work out the distance to a walk starting point, takes minutes. This may be because I have to make the search using our walks database, which is written in Visual Basic, but that's another story.

Any pointers as to how to get a nearest postcode and its location from OS Data Hub, for a given grid reference, would be most welcome.

1 Answers1

0

You can find the nearest postcode as long as it is centred no more than 1km away, for example

https://api.os.uk/search/names/v1/nearest?point=440200,458300&radius=1000&fq=LOCAL_TYPE:Postcode&key=yourkey

Beyond 1km you would need further searches in an outer circle around the original point

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Basic Map</title>
    <link rel="stylesheet" href="https://labs.os.uk/public/os-api-branding/v0.2.0/os-api-branding.css" />
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.5.0/css/ol.css" />
    <style>
        body { margin:0; padding:0; }
        #map { position:absolute; top:0; bottom:0; width:100%; }
    </style>
</head>
<body>

<div id="map"></div>

<script src="https://labs.os.uk/public/os-api-branding/v0.2.0/os-api-branding.js"></script>
<script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.5.0/build/ol.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.5.0/proj4.js"></script>
<script>

    var apiKey = 'ufArYa1UUPHJcOYbiJDaKkA7Fb4oCkEs';

    var serviceUrl = 'https://api.os.uk/maps/raster/v1/zxy';

    // Setup the EPSG:27700 (British National Grid) projection.
    proj4.defs("EPSG:27700", "+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +towgs84=446.448,-125.157,542.06,0.15,0.247,0.842,-20.489 +units=m +no_defs");
    ol.proj.proj4.register(proj4);

    var tilegrid = new ol.tilegrid.TileGrid({
        resolutions: [ 896.0, 448.0, 224.0, 112.0, 56.0, 28.0, 14.0, 7.0, 3.5, 1.75 ],
        origin: [ -238375.0, 1376256.0 ]
    });

    var gridReference = new ol.Feature();
    gridReference.setStyle(
        new ol.style.Style({
            image: new ol.style.Circle({
                radius: 6,
                fill: new ol.style.Fill({
                    color: 'red'
                })
            }),
            text: new ol.style.Text({
                font: 'bold 14px sans-serif',
                offsetY: -6,
                textBaseline: 'bottom'
            })
        })
    );

    var postcode = new ol.Feature();
    postcode.setStyle(
        new ol.style.Style({
            image: new ol.style.Circle({
                radius: 6,
                fill: new ol.style.Fill({
                    color: 'blue'
                })
            }),
            text: new ol.style.Text({
                font: 'bold 14px sans-serif',
                offsetY: -6,
                textBaseline: 'bottom'
            })
        })
    );

    // Initialize the map object.
    var map = new ol.Map({
        layers: [
            new ol.layer.Tile({
                source: new ol.source.XYZ({
                    url: serviceUrl + '/Road_27700/{z}/{x}/{y}.png?key=' + apiKey,
                    projection: 'EPSG:27700',
                    tileGrid: tilegrid
                })
            }),
            new ol.layer.Vector({
                source: new ol.source.Vector({
                    features: [gridReference, postcode]
                })
            })
        ],
        target: 'map',
        view: new ol.View({
            projection: 'EPSG:27700',
            extent: [ -238375.0, 0.0, 900000.0, 1376256.0 ],
            resolutions: tilegrid.getResolutions(),
            minZoom: 0,
            maxZoom: 9,
            center: [ 337297, 503695 ],
            zoom: 7
        })
    });

    map.on('singleclick', function(evt) {
        gridReference.setGeometry(new ol.geom.Point(evt.coordinate));
        postcode.setGeometry(undefined);

        var x = (Math.round(evt.coordinate[0]/10)/10) + 10000;
        var y = (Math.round(evt.coordinate[1]/10)/10) + 5000;

        var a1y = (4 - (Math.floor(y/5000)%5))*5;
        var a2y = (4 - (Math.floor(y/1000)%5))*5;
        var y1 = Math.floor(y/100)%10;
        var y2 = Math.floor(y/10)%10;
        var y3 = Math.floor(y)%10;
        a1y += (Math.floor(x/5000)%5);
        a2y += (Math.floor(x/1000)%5);
        var x1 = Math.floor(x/100)%10;
        var x2 = Math.floor(x/10)%10;
        var x3 = Math.floor(x)%10;

        var grid500km = String.fromCharCode(a1y + Math.floor((a1y+17)/25) + "A".charCodeAt(0));
        var grid100km = grid500km + String.fromCharCode(a2y + Math.floor((a2y+17)/25) + "A".charCodeAt(0));
        var gridText = grid100km + x1 + x2 + x3 + y1 + y2 + y3;

        gridReference.getStyle().getText().setText(gridText);

        var minDistSq = Infinity;
        var postcodeCoord, postcodeText;
        var radius = 0;
        tryPoints([evt.coordinate]);

        function tryPoints(coordinates) {

            var promises = [];

            coordinates.forEach(function(coordinate) {
                promises.push(
                    fetch(
                        'https://api.os.uk/search/names/v1/nearest?point=' +
                        coordinate[0].toFixed(2) + ',' + coordinate[1].toFixed(2) +
                        '&radius=1000&fq=LOCAL_TYPE:Postcode&key=' + apiKey
                    ).then(function(response) {
                        return response.json();
                    })
                );
            });

            Promise.all(promises).then(function(results) {
                results.forEach(function(result) {
                    if (result.results && result.results.length > 0) {
                        var entry = result.results[0]['GAZETTEER_ENTRY'];
                        var dx = entry['GEOMETRY_X'] - evt.coordinate[0];
                        var dy = entry['GEOMETRY_Y'] - evt.coordinate[1];
                        var distSq = dx * dx + dy * dy;
                        if (distSq < minDistSq) {
                            minDistSq = distSq;
                            postcodeCoord = [entry['GEOMETRY_X'], entry['GEOMETRY_Y']];
                            postcodeText = entry['NAME1'];
                        }
                    }
                });
                if (postcodeCoord) {
                    postcode.setGeometry(new ol.geom.Point(postcodeCoord));
                    postcode.getStyle().getText().setText(postcodeText);
                } else if (radius < 4) {
                    radius++;
                    var outerCircle = ol.geom.Polygon.fromCircle(new ol.geom.Circle(evt.coordinate, radius * 1000), 16 * radius);
                    tryPoints(outerCircle.getCoordinates()[0].slice(0, -1));
                }
            });

        }

    });

</script>

</body>
</html>
Mike
  • 16,042
  • 2
  • 14
  • 30
  • This is a great starting point and much appreciated, but as Mike has said, it only allows a search up to 1km radius. Many of our walks start at public car parks on Dartmoor, and some of these are a mile or more from the centre of the nearest postcode. The worst case I have found so far is Saddle Tor at 1.36 miles = 2.2km so to allow a safety margin, I need to be able to search up to say 3km. Nearby UK returns results up to 10km or more quite happily, so is maybe not using OS Data Hub. – Allan Stead Mar 19 '21 at 08:01
  • You can do further searches in an outer circle around the original point, and as it is Open Data you can use a free key. I've added an example to the answer, rings of 1km searches extending up to 4km from the original point should be sufficient for anywhere accessible by road except remote areas of Scotland (click on the map to show the grid reference and the nearest postcode). – Mike Mar 19 '21 at 14:28
  • Mike's latest answer moves me on a whole lot further, and I am very grateful. From work I have already done in migrating from OS Open Space to OS Data Hub, I can see the general drift of his example JavaScript code, but given my limited expertise, it will probably take me several days to understand it more fully! I see that for some map references, such as SX 930 704, Mike's code and the Nearby UK API return different postcodes, which will be interesting to investigate. Once again, many thanks. – Allan Stead Mar 21 '21 at 08:53
  • My code uses 16 sample points on the circumference for each 1km radius, so they are about 200 metres apart. That is not as good as using a direct line, and if there are two candidate postcodes whose distance from the starting point differ by less than 200 metres the wrong one might be chosen. Using more sample points would increase accuracy but would need more API calls and take longer. – Mike Mar 21 '21 at 11:18
  • Oh dear - egg on face time! Having modified Mike's code to display the distance of the postcode from the original grid reference, I now find that for the example I quoted (SX 930 704) the postcode returned by Mike's code is actually closer than the one provided by the Nearby UK API. His explanation of how his code overcomes the 1km limit on postcode searches was nevertheless most welcome. – Allan Stead Mar 23 '21 at 08:35