Address Geocoding with C#

Sure an address class is simple, create a few properties for city, state, zip code, and away you go. Wouldn’t it be nice to also have your address automatically be able to geocode itself for automatic googlemapping at a later date? This article describes just how this is accomplished using c#.

Getting Started

Get yourself an API Key by going to: http://code.google.com/apis/maps/signup.html.

This article will use a bit of xml to parse the results that google will send out, so it might be good, if you have not used xml before to check out the MSDN Documentation

Let’s check out the namespaces we will be using:

//For getting our settings from our app.config or web.config file
using System.Configuration;

//For the streamreader object we will be using to get our results from the goog.
using System.IO;

//For reading the results that the Goog returns.
using System.Xml;

//For our webclient to contact Google.
System.Net;

You will need to add a reference to System.Configuration into your project so that the System.ConfigurationManager object will have the AppSettings object attached to it.

Once we have our imports all squared away we will need to get our class and basic properties all setup.

public class Address
{
	//These are abbreviated property declarations as we do not need the private
	//variables for these simple properties.
	public string Latitude { get; set; }
	public string Longitude { get; set; }
	public string Line1 { get; set; }
	public string Line2 { get; set; }
	public string City { get; set; }
	public string State { get; set; }
	public string Zip { get; set; }
	public string Country { get; set; }

	//whereas this property is a read only and, therefore, we need some way to
	//set our property from inside the class without someone outside the object
	//being able to overwrite.
	private LatLngAccurateToTypes _LatLngAccuracy = 0;
	public LatLngAccurateToTypes LatLngAccuracy
	{
		get { return _LatLngAccuracy; }
	}

Quick Tip: The prop snippet.

I love snippets; I use them all the time. If you type in “prop” and hit the tab key twice, a nicely formatted property will appear before you for your quick entry.

Add Some Flavor

To make our address class a bit more functional, we are going override our ToString method to give us a nicely formatted address string.

	public override string ToString()
	{
		//Using the stringbuilder gives us better performance than stinky concatenation.
		System.Text.StringBuilder sb = new System.Text.StringBuilder();

		//Assume we have a line 1
		sb.AppendLine(Line1);

		//now check if the string is null or empty before appending
		if (!string.IsNullOrEmpty(Line2)) sb.AppendLine(Line2);

		//one could expand upon this to check for values in the other properties.
		sb.Append(City);
		sb.Append(", ");
		sb.Append(State);
		sb.Append(" ");
		sb.Append(Zip);
		sb.Append(" ");
		sb.Append(Country);

		//send out our new string
		return sb.ToString();
	}

Ok, Let’s GeoCode

Even though we can assume that Google already knows what we are about to send them, we can still assume that they would like to be asked, so we will now send our data to Google for GeoCoding.

	public void GeoCode()
	{
		//Setup a streamreader for retrieving data from Google.
		//This is line that requires System.IO.
		StreamReader sr = null;

		//Get the maps key in the web config file.
		//This is what we needed the reference to System.Configuration for.
		string mapskey = ConfigurationManager.AppSettings["GoogleMapsAPIKey"];

		//Check to see if our maps key exists
		if (string.IsNullOrEmpty(mapskey))
		{
			//if the key does not exist, we have an error so throw an exception.
			throw new Exception("No valid google maps api key to use for geocoding.  Please add an app key named \"GoogleMapsAPIKey\" to the web.config file.");
		}

		//Create the url string to send our request to googsie.
		string url = string.Format("http://maps.google.com/maps/geo?q={0} +{1} +{2} +{3} +{4}&output=xml&oe=utf8&sensor=false&key={5}", this.Line1, this.City + ", ", this.State, this.Zip, this.Country, mapskey);

		//Create a web request client.
		//This is where the System.Net import is used.
		WebClient wc = new WebClient();

		//Even though logic would tell us that having a site outage is a bit evil and Google is never evil,
		//therefore Google will never have a site outage, we will still throw a try catch around our call
		//just in case Google does become evil for a few minutes on some random day.
		try
		{
			//retrieve our result and put it in a streamreader
			sr = new StreamReader(wc.OpenRead(url));
		}
		catch (Exception ex)
		{
			//This is where we can assume they were evil.
			throw new Exception("An error occured while retrieving GeoCoded results from Google, the error was: " + ex.Message);
		}

		try
		{
			//Now that we have the results, lets parse the returned data with an xmltextreader.
			//Why an XMLTextReader vs. An XMLDocument?  Simple, XMLTextReaders are faster and this is
			//Simple enough not to warrant a full XMLDocument.
			XmlTextReader xtr = new XmlTextReader(sr);

			bool coordread = false;
			bool accread = false;

			//Start reading our xml document.
			while (xtr.Read())
			{
				xtr.MoveToElement();
				switch (xtr.Name)
				{
					case "AddressDetails": //Check for the address details node
						while (xtr.MoveToNextAttribute())
						{
							switch (xtr.Name)
							{
								case "Accuracy": //move into the accuracy attrib and....
									if (!accread)
									{
										//get the accuracy, convert it to our enum and save it to the record
										this._LatLngAccuracy = (LatLngAccurateToTypes)Convert.ToInt32(xtr.Value);
										accread = true;
									}
									break;
							}
						}
						break;
					case "coordinates": //the coordinates element
						if (!coordread)
						{
							//move into the element value
							xtr.Read();

							//split the coords and then..
							string[] coords = xtr.Value.Split(new char[] { ',' });

							//save the values
							Longitude = coords[0];
							Latitude = coords[1];

							//finally, once this has been done, we don't want the process to run again on the same file
							coordread = true;
						}
						break;
				}
			}
		}
		catch (Exception ex)
		{
			throw new Exception("An error occured while parsing GeoCoded results from Google, the error was: " + ex.Message);
		}
	}

Ok, so there we go, we have now GeoCoded our address for saving into our database or throwing onto a dynamic Google map. There is only one thing we are missing. Google returns an enum of accuracy types for their results. If our address only contains a city, the GeoCode will be far less accurate than if we were to put in a full address. The enum below helps us to track that.

	public enum LatLngAccurateToTypes : int
	{
		Unknown = 0,
		Country = 1,
		Region = 2,
		SubRegion = 3,
		Town = 4,
		PostCode = 5,
		Street = 6,
		Intersection = 7,
		Address = 8,
		Premises = 9
	}

Full Source

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Xml.Linq;
using System.Net;
using System.Web;
using System.Configuration;
using System.IO;
using System.Xml;

public class Address
{
	public string Latitude { get; set; }
	public string Longitude { get; set; }
	public string Line1 { get; set; }
	public string Line2 { get; set; }
	public string City { get; set; }
	public string State { get; set; }
	public string Zip { get; set; }
	public string Country { get; set; }

	private LatLngAccurateToTypes _LatLngAccuracy = 0;
	public LatLngAccurateToTypes LatLngAccuracy
	{
		get { return _LatLngAccuracy; }
	}

	public override string ToString()
	{
		System.Text.StringBuilder sb = new System.Text.StringBuilder();
		sb.AppendLine(Line1);
		if (!string.IsNullOrEmpty(Line2)) sb.AppendLine(Line2);
		sb.Append(City);
		sb.Append(", ");
		sb.Append(State);
		sb.Append(" ");
		sb.Append(Zip);
		sb.Append(" ");
		sb.Append(Country);
		return sb.ToString();
	}

	public Address()
	{
	}

	public void GeoCode()
	{
		//get the maps key in the web config file
		string mapskey = ConfigurationManager.AppSettings["GoogleMapsAPIKey"];
		//setup a streamreader for retrieving data from Google.
		StreamReader sr = null;

		//Check to see if our maps key exists
		if (string.IsNullOrEmpty(mapskey))
		{
			throw new Exception("No valid google maps api key to use for geocoding.  Please add an app key named \"GoogleMapsAPIKey\" to the web.config file.");
		}

		//Create the url string to send our request to googs.
		string url = string.Format("http://maps.google.com/maps/geo?q={0} +{1} +{2} +{3} +{4}&output=xml&oe=utf8&sensor=false&key={5}", this.Line1, this.City + ", ", this.State, this.Zip, this.Country, mapskey);

		//Create a web request client.
		WebClient wc = new WebClient();

		try
		{
			//retrieve our result and put it in a streamreader
			sr = new StreamReader(wc.OpenRead(url));
		}
		catch (Exception ex)
		{
			throw new Exception("An error occured while retrieving GeoCoded results from Google, the error was: " + ex.Message);
		}

		try
		{
			//now lets parse the returned data as an xml
			XmlTextReader xtr = new XmlTextReader(sr);
			bool coordread = false;
			bool accread = false;
			while (xtr.Read())
			{
				xtr.MoveToElement();
				switch (xtr.Name)
				{
					case "AddressDetails": //Check for the address details node
						while (xtr.MoveToNextAttribute())
						{
							switch (xtr.Name)
							{
								case "Accuracy": //move into the accuracy attrib and....
									if (!accread)
									{
										//get the accuracy, convert it to our enum and save it to the record
										this._LatLngAccuracy = (LatLngAccurateToTypes)Convert.ToInt32(xtr.Value);
										accread = true;
									}
									break;
							}
						}
						break;
					case "coordinates": //the coordinates element
						if (!coordread)
						{
							//move into the element value
							xtr.Read();

							//split the coords and then..
							string[] coords = xtr.Value.Split(new char[] { ',' });

							//save the values
							Longitude = coords[0];
							Latitude = coords[1];

							//finally, once this has been done, we don't want the process to run again on the same file
							coordread = true;
						}
						break;
				}
			}
		}
		catch (Exception ex)
		{
			throw new Exception("An error occured while parsing GeoCoded results from Google, the error was: " + ex.Message);
		}
	}

	public enum LatLngAccurateToTypes : int
	{
		Unknown = 0,
		Country = 1,
		Region = 2,
		SubRegion = 3,
		Town = 4,
		PostCode = 5,
		Street = 6,
		Intersection = 7,
		Address = 8,
		Premises = 9
	}
}
Advertisements

4 responses to this post.

  1. Thank you very much for this…I’ve spend last 2 days looking for how to verify my list of addresses and update it with lat and long.

    Reply

  2. Is there a way to modify this code so I can read from a SQL Server 2005 database table, geocode each address, and then update the table with its corresponding latitude and longitude? Thanks.

    Reply

  3. Posted by Rich on June 21, 2010 at 4:44 am

    Could you provide an example of how to utilize this class?

    Reply

  4. Posted by simmer on June 22, 2011 at 8:59 pm

    getting this error An error occurred while parsing EntityName. on (xtr.Read())

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: