BLOG

C# Distance Between Two Post Codes

08 May 2009 by Stuart Cam

Google provides a fantastic Map API to retrieve address information for a particular post code (zip code to our fellow Americans).

I needed to calculate the distance between two UK post codes, and stumbled across a handy URL for querying Google:

http://maps.google.com/maps/geo?q=SW1A%202AA&output=xml

The URL above retrieves information for 10 Downing Street, London, UK.

Parse the longitude and latitude information from Google for two post codes, in conjuntion with the Haversine Formula for a working solution:

using System;
using System.Net;
using System.Web;
using System.Xml;

namespace Codebrain
{
    public static class Distance
    {
        // Handy structure for Long/Lat information
        public struct Coords
        {
            public double Longitude;
            public double Latitude;
        }

        // Unit calculations
        public enum Units
        {
            Miles,
            Kilometres
        }

        // Will return a null if the Google API is unable to find either post code, or the country constraint fails
        public static double? BetweenTwoPostCodes(string postcodeA, string postcodeB, string countryCodeWithin, Units units)
        {
            var ll1 = PostCodeToLongLat(postcodeA, countryCodeWithin);
            if (!ll1.HasValue) return null;
            var ll2 = PostCodeToLongLat(postcodeB, countryCodeWithin);
            if (!ll2.HasValue) return null;
            return ll1.Value.DistanceTo(ll2.Value, units);
        }

        // Overload for UK post codes
        public static double? BetweenTwoUKPostCodes(string postcodeA, string postcodeB)
        {
            return BetweenTwoPostCodes(postcodeA, postcodeB, "GB", Units.Miles);
        }

        // Uses the Google API to resolve a post code (within the specified country)
        public static Coords? PostCodeToLongLat(string postcode, string countryCodeWithin)
        {
            // Download the XML response from Google
            var client = new WebClient();
            var encodedPostCode = HttpUtility.UrlEncode(postcode);
            var url = string.Format("http://maps.google.com/maps/geo?q={0}&output=xml", encodedPostCode);
            var xml = client.DownloadString(url);
            var doc = new XmlDocument();
            doc.LoadXml(xml);

            // Create a custom namespace manager
            var nsmgr = new XmlNamespaceManager(doc.NameTable);
            nsmgr.AddNamespace("ge", "http://earth.google.com/kml/2.0");
            nsmgr.AddNamespace("oa", "urn:oasis:names:tc:ciq:xsdschema:xAL:2.0");

            // Any results?
            var nodelist = doc.SelectNodes("//ge:kml/ge:Response/ge:Placemark", nsmgr);
            if (nodelist == null || nodelist.Count == 0) return null;

            // Results are already ordered by accuracy, so take the first one
            var node = nodelist[0];

            // Check the Country constraint
            var countryname = node.SelectSingleNode("oa:AddressDetails/oa:Country/oa:CountryNameCode", nsmgr);
            if (countryname.FirstChild.Value != countryCodeWithin)
                return null;

            // Get the raw Long/Lat coordinates (I wish there was a nicer way..
            // perhaps averaging the LongLat enclosing box?)
            var coords = node.SelectSingleNode("ge:Point/ge:coordinates", nsmgr).FirstChild.Value.Split(',');
            double longitude;
            double lattitude;
            if (!Double.TryParse(coords[0], out longitude)) return null;
            if (!Double.TryParse(coords[1], out lattitude)) return null;

            return new Coords
                       {
                           Longitude = longitude,
                           Latitude = lattitude
                       };
        }

        public static double DistanceTo(this Coords from, Coords to, Units units)
        {
            // Haversine Formula...
            var dLat1InRad = from.Latitude * (Math.PI / 180.0);
            var dLong1InRad = from.Longitude * (Math.PI / 180.0);
            var dLat2InRad = to.Latitude * (Math.PI / 180.0);
            var dLong2InRad = to.Longitude * (Math.PI / 180.0);

            var dLongitude = dLong2InRad - dLong1InRad;
            var dLatitude = dLat2InRad - dLat1InRad;

            // Intermediate result a.
            var a = Math.Pow(Math.Sin(dLatitude / 2.0), 2.0) +
                    Math.Cos(dLat1InRad) * Math.Cos(dLat2InRad) *
                    Math.Pow(Math.Sin(dLongitude / 2.0), 2.0);

            // Intermediate result c (great circle distance in Radians).
            var c = 2.0 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1.0 - a));

            // Unit of measurement
            var radius = 6371;
            if (units == Units.Miles) radius = 3959;

            return radius * c;
        }
    }
}

Example Usage:

// The long way...
var distance = Distance.BetweenTwoPostCodes("BS1 6QQ", "SW1A 2AA", "GB", Distance.Units.Miles);
if (distance.HasValue) Console.WriteLine(distance.Value + " miles");
// ...or the short way
var distance = Distance.BetweenTwoUKPostCodes("BS1 6QQ", "SW1A 2AA");
if (distance.HasValue) Console.WriteLine(distance.Value + " miles");

It's not 100% accurate and technically speaking the distance is 'as the crow flies', but if you need simple information it'll do the trick nicely!

Tags: , , ,

Categories: .NET | C Sharp | Web


© Codebrain 2014. All Rights Reserved. Registered in England: 07744920. VAT: GB 119 4078 13