Michael Friis' Blog

About


C# and Google Geocoding Web Service v3

Need to geocode addresses using the v3 Google Geocoding Web Service? There are some good reasons to choose the new v3 edition — most importantly, you don’t need an API key. You could use geocoding.net which — at the time of writing —  has some support for v3. I decided to hack up my own wrapper though, and using Windows Communication Foundation, it turned out to be really simple! Note that if you need more of the attributes returned by the Web Service, you should add them to the DataContract classes.

using System;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Net;
using System.Web;

.
.
.

private static GeoResponse CallGeoWS(string address)
{
	string url = string.Format(
		"http://maps.google.com/maps/api/geocode/json?address={0}&region=dk&sensor=false",
		HttpUtility.UrlEncode(address)
		);
	var request = (HttpWebRequest)HttpWebRequest.Create(url);
	request.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip,deflate");
	request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
	DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(GeoResponse));
	var res = (GeoResponse)serializer.ReadObject(request.GetResponse().GetResponseStream());
	return res;
}

[DataContract]
class GeoResponse
{
	[DataMember(Name="status")]
	public string Status { get; set; }
	[DataMember(Name="results")]
	public CResult[] Results { get; set; }

	[DataContract]
	public class CResult
	{
		[DataMember(Name="geometry")]
		public CGeometry Geometry { get; set; }

		[DataContract]
		public class CGeometry
		{
			[DataMember(Name="location")]
			public CLocation Location { get; set; }

			[DataContract]
			public class CLocation
			{
				[DataMember(Name="lat")]
				public double Lat { get; set; }
				[DataMember(Name = "lng")]
				public double Lng { get; set; }
			}
		}
	}
}

If you need to geocode a lot of addresses, you need to manage your request rate. Google will help you throttle requests by returning OVER_QUERY_LIMIT statuses if you are going too fast. I use the method below to manage this. It’s decidedly unelegant, please post a reply if you come up with something better.

private static int sleepinterval = 200;

private static GeoResponse CallWSCount(string address, int badtries)
{
	Thread.Sleep(sleepinterval);
	GeoResponse res;
	try
	{
		res = CallGeoWS(address);
	}
	catch (Exception e)
	{
		Console.WriteLine("Caught exception: " + e);
		res = null;
	}
	if (res == null || res.Status == "OVER_QUERY_LIMIT")
	{
		// we're hitting Google too fast, increase interval
		sleepinterval = Math.Min(sleepinterval + ++badtries * 1000, 60000);

		Console.WriteLine("Interval:" + sleepinterval + "                           \r");
		return CallWSCount(address, badtries);
	}
	else
	{
		// no throttling, go a little bit faster
		if (sleepinterval > 10000)
			sleepinterval = 200;
		else
			sleepinterval = Math.Max(sleepinterval / 2, 50);

		Console.WriteLine("Interval:" + sleepinterval);
		return res;
	}
}

Comments

Laypyay on

Thanks for sharing this information.. it’s work well.

Reply

Chris on

Thanks for posting this.Very useful.

Reply

Claus Pedersen on

Great post. Works fantastic.
Thank you for sharing.

Reply

Stefcio on

Hi, Could you also add some bit which will describe how to get the actual values of lat and lng from GeoResponse object ?

Reply

Paul Hayes on

very cool, i added it to a wcf service and it works a treat. thank you very much.

Reply

Gareth Dirlam on

Very nice code. I needed the address information also and I will be including it for what it is worth. Thanks for the insight into how to make this happen.

[DataContract]
public class GeoResponse
{
[DataMember(Name = “status”)]
public string Status { get; set; }
[DataMember(Name = “results”)]
public CResult[] Results { get; set; }

[DataContract]
public class CResult
{

[DataMember(Name = “formatted_address”)]
public string formatted_address { get; set; }
[DataMember(Name = “address_components”)]
public GeocodeAddressComponent[] AddressComponents { get; set; }

[DataContract]
public class GeocodeAddressComponent
{
[DataMember(Name = “long_name”)]
public string LongName { get; set; }
[DataMember(Name = “short_name”)]
public string ShortName { get; set; }
[DataMember(Name = “types”)]
public string[] Type { get; set; }
}

[DataMember(Name = “geometry”)]
public CGeometry Geometry { get; set; }

[DataContract]
public class CGeometry
{
[DataMember(Name = “location”)]
public CLocation Location { get; set; }

[DataContract]
public class CLocation
{
[DataMember(Name = “lat”)]
public double Latitude { get; set; }
[DataMember(Name = “lng”)]
public double Longitude { get; set; }

}
}
}
}

Reply

michael brehm on

Thanks for this information. Helped me to understand the json structures returned from google geocoding-service.
I added the location type to geometry. Gives you information about how precise the returned geocodes are.

[DataMember(Name = “location_type”)]
public string Location_Type { get; set; }

http://code.google.com/intl/en/apis/maps/documentation/javascript/services.html#GeocodingResults

Reply

Theekshana on

Its really cool. I have added it to my wcf services for suggest address. working very well.
Thank you.

Reply

Perry on

“most importantly, you don’t need an API key”

Actually, moving away from the “key” model of v2 is not beneficially in some environments. The v3 api looks at the IP from which requests are coming. If you happen to share rackspace at an ISP with other applications that are doing the same thing, you may hit the “OVER_QUERY_LIMIT” before your application has made 2500 requests. Decidedly inconvenient and a very poor model in my opinion. The key was much better.

Reply

Leave a Reply

Your email address will not be published. Required fields are marked *