Using C# .NET to retrieve business data from Google Places

The Google Places Api allows us to retrieve business data in either xml or json format. In this post I’m going to use the xml format, and go through what we need to do.

Setup: Get PlaceId, Api Key and web request url

First you need to visit the Google developers console, sign in, create a project, enable the google places api, and add a server key credential. Once you have your api key from this, next you need to format your url. For this post I’m going to use the following link, but feel free to use your own:

https://maps.googleapis.com/maps/api/place/details/xml?placeid=ChIJN1t_tDeuEmsRUsoyG83frY4&key={apiKey}

Replace {apiKey} with your own. Here I’m specifying that I want it formatted into xml, but you can choose json if you like.

Retrieving the xml and parsing the contents

The container classes:

internal class GooglePlacesResult
{
	internal string Name { get; set; }
	internal string Address { get; set; }
	internal string PhoneNumber { get; set; }
	internal string City { get; set; }
	internal string State { get; set; }
	internal string Country { get; set; }
	internal string Zip { get; set; }
	internal decimal Latitude { get; set; }
	internal decimal Longitude { get; set; }
	internal decimal Rating { get; set; }
	internal string Url { get; set; }
	internal string InternationalPhoneNumber { get; set; }
	internal IEnumerable<GooglePlacesReview> Reviews { get; set; }
	internal bool OpenNow { get; set; }
	internal IEnumerable<string> OpenHours { get; set; }
	internal int UtcOffset { get; set; }
	internal int UserRatingsTotal { get; set; }
}

internal class GooglePlacesReview 
{
	internal DateTime ReviewDate { get; set; }
	internal string ReviewText { get; set; }
	internal string AuthorName { get; set; }
	internal decimal Rating { get; set; }
	internal string Language { get; set; }
}

The extension method:

internal static T TryParseValue<T>(this string s) where T : struct
{
	var method = typeof(T).GetMethod(
		"TryParse",
		new []{typeof(string), typeof(T).MakeByRefType()}
		);
	T result = default(T);
	var parameters = new object[] { s, result };
	method.Invoke(null, parameters);
	return (T)parameters[1];
}

Also a method to convert an unix time value to a DateTime:

private DateTime FromUnixTime(long unixTime)
{
	var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
	return epoch.AddSeconds(unixTime);
}

The code:

internal GooglePlacesResult GetGooglePlacesReviews(string placeId)
{
	string apiKey = ""; // Your api key
	string url = string.Format(@"https://maps.googleapis.com/maps/api/place/details/xml?placeid={0}&key={1}", placeId, apiKey);
	XDocument doc = XDocument.Load(url);
	var businessResults = doc.Root.Element("result");
			
	var addressComponents = businessResults.Elements("address_component").Where(e => e.Element("type") != null);
	var street = businessResults.Element("vicinity").Value;
	var cityTypes = new[] { "locality", "sublocality", "sublocality_level_1", "sublocality_level_2", "sublocality_level_3", "sublocality_level_4", "sublocality_level_5" };
	var stateTypes = new[] { "administrative_area_level_1" };
	var countryTypes = new[] { "country" };
	var zipTypes = new[] { "postal_code" };
	var location = businessResults.Element("geometry").Element("location");
	var reviews = businessResults.Elements("review").Select(r => new GooglePlacesReview
	{
		ReviewDate = FromUnixTime(r.Element("time").Value.TryParseValue<Int64>()),
		ReviewText = r.Element("text").Value,
		AuthorName = r.Element("author_name").Value,
		Rating = r.Element("rating").Value.TryParseValue<Decimal>(),
		Language = r.Element("language").Value
	});
	var openingHours = businessResults.Element("opening_hours");
			
	return new GooglePlacesResult 
	{
		Name = businessResults.Element("name").Value,
		Address = street.Remove(street.LastIndexOf(",")),
		PhoneNumber = businessResults.Element("formatted_phone_number").Value,
		City = addressComponents.FirstOrDefault(e => cityTypes.Any(s => s == e.Element("type").Value)).Element("long_name").Value,
		State = addressComponents.FirstOrDefault(e => stateTypes.Any(s => s == e.Element("type").Value)).Element("long_name").Value,
		Country = addressComponents.FirstOrDefault(e => countryTypes.Any(s => s == e.Element("type").Value)).Element("long_name").Value,
		Zip = addressComponents.FirstOrDefault(e => zipTypes.Any(s => s == e.Element("type").Value)).Element("long_name").Value,
		Latitude = location.Element("lat").Value.TryParseValue<Decimal>(),
		Longitude = location.Element("lng").Value.TryParseValue<Decimal>(),
		Rating = businessResults.Element("rating").Value.TryParseValue<Decimal>(),
		Url = businessResults.Element("url").Value,
		InternationalPhoneNumber = businessResults.Element("international_phone_number").Value,
		Reviews = reviews,
		OpenNow = openingHours.Element("open_now").Value.TryParseValue<bool>(),
		OpenHours = openingHours.Elements("weekday_text").Select(e => e.Value),
		UtcOffset = businessResults.Element("utc_offset").Value.TryParseValue<Int32>(),
		UserRatingsTotal = businessResults.Element("user_ratings_total").Value.TryParseValue<Int32>()
	};
}

Some things to talk about. Google places returns a collection of address_component nodes. Each of these has a type that defines what it is. The definitions can be found here. Using Linq we’re able to simplify the data retrieval process.

Wrapping Up

So hopefully this has been informative, if you’ve enjoyed reading please leave a comment below or see if I’ve made any mistakes or a better way of doing this I would love to hear it, thanks!

Leave a Reply

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