Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Event Finder - Google Places Event App

0.00/5 (No votes)
25 Oct 2012 1  
Event Finder - A WinRT app making use of the Google Places API and other data sources to display a lists of local events

This article is an entry in our AppInnovation Contest. Articles in this sub-section are not required to be full articles so care should be taken when voting.

Introduction

Event Finder is a C# WinRT app that makes use of the Google Places API, the WinRT Geolocation feature, microformats and RSS to discover event venues in the local area and display up-coming events. Some example code snippets are included below, and the full source to the semi-working prototype is available from github.

Finding Our Place in the World

The Google Places API is an experimental API for searching for places on the basis of position data, types of place, distance, etc. For example (making use of a little bit of wrapping code and dropping the result straight into a JSON parser with some pre-prepared object types), we can search for cinemas and art galleries within a specified radius of a known point:

public async Task<String> GoogleRequest(String url)
{
    WebRequest req = WebRequest.Create(url);
    WebResponse res = await req.GetResponseAsync();
    Stream inputStream = res.GetResponseStream();
    StreamReader reader = new StreamReader(inputStream);
    return reader.ReadToEnd();
}

public async Task<GooglePlaceSearchResult> FetchVenuesAsync() 
{
    var pos = await GetPositionAsync();
    var venues = JsonConvert.DeserializeObject<GooglePlaceSearchResult>(
    await GoogleRequest("https://maps.googleapis.com/maps/api/place/search/json?" +
            "key=APIKEY&sensor=true" +
                        "&types=movie_theater|art_gallery&radius=6000&" +
                        "&location=" +
                        pos.Coordinate.Latitude.ToString() +"," +
                        pos.Coordinate.Longitude.ToString() 
        )
    );
}

And in order to find that known point, we can use the WinRT Geolocator:

private async Task<Geoposition> GetPositionAsync()
{
    Geolocator geo = new Geolocator();
    geo.DesiredAccuracy = PositionAccuracy.High;
    Geoposition pos = await geo.GetGeopositionAsync();
    return pos;
}

Looking Around

The data returned from the Places API is fairly extensive, normally including the full postal address of the venue, coordinates, phone number, website URL and often user reviews. A feature of the API is that it can also include event information for locations; for example a list of film showings at a local cinema. Those events can be published by event organisers using the API, and presumably will be retrieved from other sources too. Unfortunately, while writing this app, I was unable to find any places that have event data available through the API, so either that feature isn't implemented yet, or none of the venues I looked at are publishing their events.

The information provided by the Places API seems to match up with that which would be provided by microdata using the schema.org data formats. In the absence of event data direct from the Places API, perhaps we could instead follow the URL received from the API and see if the venue's website publishes microdata. We would expect to find pages marked up like this:

<div itemscope itemtype="http://schema.org/Event">
    <header><h1 itemprop="name">Josephine Foster</h1></header>
    <div style="float: right">
            <img itemprop="image" src="imgsmall.jpg" alt="" width="399" height="264">
        </div>
    <h3>
        <time itemprop="startDate"

                      datetime="2012-10-31T20:00:00+00:00">8:00 p.m.</time>
                to
                <time itemprop="endDate"

                      datetime="2012-10-31T23:00:00">
                11:00 p.m. Wednesday 31 October 2012</time> 
    </h3>
    <p itemprop="description">The wonderful singer-songwriter Josephine Foster
        (USA) tours her newalbum 'Blood Rushing' (Fire Records) with full band.</p>
        <a itemprop="url" href="http://www.starandshadow.org.uk/on/gig/504">
            http://www.starandshadow.org.uk/on/gig/504
        </a> 
</div> 

Again however, there don't seem to be many venues yet publishing data like this. An example of a venue that does is the Star And Shadow Cinema in Newcastle (I'm slightly biased as I wrote their website). As you can see above, the start time for the event, its title and various other fields are presented in a standardised format. Here's an example of how you might extract data from a page like this:

private async void GetByMicroFormat(SyndicationItem feeditem)
{
    HtmlDocument doc = await EventDataSource._docGetter.LoadFromWebAsync(_uri.ToString());
    this._subtitle = GetMicroFormatByProp(doc, "startDate", "time");
    this._image = new BitmapImage(new Uri(GetMicroFormatByProp(doc, "image", "meta", "content")));
    this._description = GetMicroFormatByProp(doc, "description", "td");
    this._content = GetMicroFormatByProp(doc, "about", "div");
}
private string GetMicroFormatByProp(HtmlDocument doc, string itemprop, 
        string elementtype, string itemattr)
{
    var result =
	from el in doc.DocumentNode.Descendants(elementtype)
	    .Where(x => x.Attributes.Contains("itemprop")
            && x.Attributes["itemprop"].Value.Contains(itemprop))
	select el.Attributes[itemattr].Value;
    var res = result.FirstOrDefault();
    return res;
}
private string GetMicroFormatByProp(HtmlDocument doc, string itemprop, string elementtype)
{
    var result =
	from el in doc.DocumentNode.Descendants(elementtype)
	    .Where(x => x.Attributes.Contains("itemprop") 
            && x.Attributes["itemprop"].Value.Contains(itemprop))
	select el.InnerText;
    var res = result.FirstOrDefault();
    return res;
}   

The next possibility would be good old fashioned RSS. There are a number of fairly standard ways to get from a website URL to an RSS feed (link headers and the like), and the main bulk of the future development of this app will be coding up the various possibilities in order to extract data from as many sites as possible. So far, the app very trustingly retrieves the main webpage for each venue and scrapes out the first likely looking link element, then assumes that's a list of events:

private async Task<Uri> FindRss(Uri websiteUrl)
{
    HtmlDocument doc = await _docGetter.LoadFromWebAsync(websiteUrl.ToString());
    var hrefs =
	from link in doc.DocumentNode.Descendants("link")
	    .Where(x => x.Attributes["rel"].Value == "alternate")
	    .Where(x => x.Attributes["type"].Value == "application/rss+xml")
	select link.Attributes["href"].Value;
    string href = hrefs.FirstOrDefault();
    return new Uri(href, UriKind.RelativeOrAbsolute);
}
SyndicationFeed feeditems = await client.RetrieveFeedAsync(feedurl);
List<EventDataEevent> items = new List<EventDataEevent>();
foreach (var item in feeditems.Items.ToList())
{
    items.Add(new EventDataEevent(item));
}

Showing What We've Found

Visual Studio 2012 comes with several project templates that make putting together a new Windows App Store app nice and simple. This app started off as the Split App (XAML) C# example. From that starting point, there are just a few steps required to put together a data source, making use of the features mentioned above, and display the data in a familiar and easy to use manner.

The code snippets above were added as methods to the EventDataSource class (renamed from the original SampleDataSource), and used to populate an ObservableCollection<EventDataVenue> called AllVenues:

public sealed class EventDataSource
{
    public static EventDataSource _eventDataSource = new EventDataSource();

    ...

    public async Task FetchVenuesAsync()
    {
        var pos = await GetPositionAsync();
        var venues = JsonConvert.DeserializeObject<GooglePlaceSearchResult>(
            await GoogleRequest("https://maps.googleapis.com/" +
                                "maps/api/place/search/json?" +
                "key=APIKEY&sensor=true" +
                "&types=movie_theater|amusement_park|art_gallery" + 
                                "|night_club|stadium&radius=6000&" +
                "&location=" +
                                pos.Coordinate.Latitude.ToString() + "," +
                                pos.Coordinate.Longitude.ToString()
            )
        );
        foreach (GooglePlaceSearchResult.Result venueDetails in venues.results)
        {
            var venueFullDetails = await FetchVenueDetailsAsync(venueDetails);
            var edv = new EventDataVenue(venueDetails, venueFullDetails.result);
            if (edv.EventFeed == null)
            {
                edv.Items.Add(new EventDataEevent(
                                      venueDetails.name + "-noevent",
                                     "No Events Found", edv));
            }
            else
            {
                SyndicationFeed feeditems = 
                                   await _client.RetrieveFeedAsync(edv.EventFeed);
                foreach (var item in feeditems.Items.ToList())
                {
                    edv.Items.Add(new EventDataEevent(item, edv));
                }
            }
            this.AllVenues.Add(edv);
        }
    }
    private ObservableCollection<EventDataVenue> _allVenues = 
                  new ObservableCollection<EventDataVenue>();
    public ObservableCollection<EventDataVenue> AllVenues
    {
        get { return this._allVenues; }
    }
    public static IEnumerable<EventDataVenue> GetVenues(string uniqueId)
    {
        return _eventDataSource.AllVenues;
    }

    ...

    public EventDataSource()
    {
        FetchVenuesAsync();
    }
}

Then in the ItemsPage.xaml.cs code, AllVenues is called upon to provide items for the page:

protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
    var venueDatavenues = EventDataSource.GetVenues((String)navigationParameter);
    this.DefaultViewModel["Items"] = venueDatavenues;
}

At this point, we have enough to show the first screen of the app, showing a list of venues. It's worth noting that this just displays the information received from the Google Places API. The images are retrieved directly from Google using the URL provided in the API responses.

In the SplitPage.xaml.cs code (again pre-populated by Visual Studio when the project is created), the GetVenue and GetItem methods are used rather than GetVenues. Again however, it's a fairly simple task to arrange your data such that what drops out is ready for display on the page.

The XAML design view simplifies the task of adjusting the templates to suit your data. After some adjustment, and retrieving data from the venue's website, we now have all the information we need:

Points of Interest

With WinRT and Visual Studio 2012, it's remarkably easy to put together a usable app with a presentable appearance. Geolocation and always-on mobile data connections are familiar parts of everyday life now, and technologies like microdata schemas and the data services that people like Google are building on top of them will open up even more information for easy access and ubiquitous availability.

Try It Out

If you want to give it a go, feel free to grab the code from github and build it. If you impersonate the location 54.974227,-1.594066, then you'll see a list of venues in Newcastle upon Tyne. If you select The Star And Shadow Cinema, you'll get a list of up-coming events, with live data and images retrieved from their site. Quite a few of the other venues will also display data of one sort or another, though not always event data.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here