Randoom a Michael Friis production

Posted
23 April 2010 @ 10pm

Categories
C#, Geocoding, WCF

You're reading Randoom, a Michael Friis production

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;
	}
}

9 Comments

Posted by
Laypyay
18 May 2010 @ 9am

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


Posted by
Chris
4 June 2010 @ 4pm

Thanks for posting this.Very useful.


Posted by
Claus Pedersen
17 July 2010 @ 10pm

Great post. Works fantastic.
Thank you for sharing.


Posted by
Stefcio
25 July 2010 @ 1pm

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


Posted by
Paul Hayes
3 August 2010 @ 1pm

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


Posted by
Gareth Dirlam
12 September 2010 @ 2pm

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; }

}
}
}
}


Posted by
michael brehm
11 March 2011 @ 3pm

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


Posted by
Theekshana
2 August 2012 @ 6am

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


Posted by
Perry
19 October 2012 @ 4pm

“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.


Leave a Comment