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!