Saturday, August 28, 2010

Using a Google Map as a database – Part 2

Splitting the ATOM
Yeah, sorry about that. Democritus claimed that ATOM was indivisible, the smallest piece of matter there was. But using the new SyndicationFeed API, we can easily parse the ATOM feed into its component parts, breaking each individual entry (corresponding to a hotel, or guesthouse, in my accommodation example from Part 1) into its title, date published, summary, etc.

Now these properties of the entry are not where our data payload is: it's in the <content> tag. That's just where Google places the info-window markup. And that's where you'd want to put it if you were generating the feed yourself. Unless you're simply returning a collection of items with properties corresponding to a title, LastUpdatedTime, PublishDate and the like, you want to resist the urge to shoehorn your data into one of those slots. If your datastore is a pair of really big feet, then these properties are ladies' shoes. Small ladies. And sure, it may feel fantastic to flounce around in them for a while, but it'll end in fear and shame, trust me.

Unpacking the content from the description node in the KML is a little tricky, and if anyone knows a better way of doing this, please let me know. For a start, it's wrapped in XML comments. Second, as I mentioned earlier, because of the restrictions within the Google Map UI, you can't easily make it XML-compliant. In any case, it's passed through as unstructured. The fact that it happens to be sort of structured just makes it a little bit easier for me at the unpacking end, but it's not at all necessary. So, I simply pass it through a sanitising method, closing the img tags, reinstating the angle brackets, ending up with an XElement.

But deserializing the individual feed entries is much easier, despite the fact that they seem to be wrapped in up ATOM feed entries, and would seem to need to be tackled using LINQ2XML, or XPath, or some XML API. Using the new HttpClient to get the response,
feedUrl = "http://www.connemara.net/Services/AccommodationMap/Feed.svc/type/" + type;
var client = new HttpClient(feedUrl);
var response = client.Get();
response.EnsureStatusIsSuccessful();
var MapAtomFeed = response.Content.ReadAsSyndicationFeed();
we simply run through the feed entries, and deserialize them into our domain objects:
var providers = new Providers();
foreach (var item in MapAtomFeed.Items) {
    var provider = (item.Content as XmlSyndicationContent).ReadContent<Provider>;
    providers.Add(provider);
}
There's the goodness! ReadContent of type Provider. I can automatically turn that ATOM feed entry back into my (Accommodation) Provider domain object! The key to this lies in the namespace accompanying each feed entry's Provider node, which tells .Net how to unpack the entry.

ATOM screenshot

See it there? The
<Provider xmlns="http://schemas.datacontract.org/2004/07/ConnemaraNet.Services.AccommodationMap" ... >
part? That last section - ConnemaraNet.Services.AccommodationMap - is the namespace of my original datacontract. Once that's recreated on the client side of the service, you're in business.

And that's it. Here's the map that I use as my database. And here's my accommodation page, using that map.

Wednesday, August 25, 2010

Using a Google Map as a database – Part 1

In a nutshell: Create a Google Map to save placemarks that contain other (than geodata) types of business information, like name, URL, description, that can be read using the Google Maps Data API in the form of an ATOM feed, that can be easily parsed using the new WCF REST Starter Kit and integrated into your website.

I had a need recently to create a small dataset (around 50 items, and not expected to grow much beyond that) to form the basis for a new Accommodation section of my website. The dataset would consist of a list of accommodation providers, each item having properties like name, URL, type, etc. Since these were actual hotels and guesthouses, etc, I also wanted to store the location of each provider. Up to that point I had stored the list as individual bookmarks in Delicious, and populated my links page by requesting the Delicious feed of my bookmarks, filtering by the tag "accommodation". But Delicious doesn't allow you to associate geodata with bookmarks. Nowadays, any page showing a list whose items have a physical location meaningful within the app should show a map of those items, no question.

Organising (this corner of) the world's data
So, I decided to move the data to a Google Map, and wondered about where within the map I could squeeze the non-geographical information for each hotel, etc. When creating a map by hand, as opposed to programmatically as the new Maps Data API allows, the only place to save anything relating to a placemark is in the info-window for that placemark. One of the reasons for using a Google Map to store data like this is to benefit from the searchability and indexing of your map data - this is Google property, after all. So, unless you turn your back on the web and make the map private, the map has to work and make sense as a browseable object, independent of any data-storage purpose to which you might co-opt it. You want to have that info-window show something that looks ok.
Google Map

I decided to use a simple pseudo-HTML schema to store each hotel's information. An image, a link, and some social media links would do, and would result in a good-looking info-window, as well as storing all the information I needed. It's a combination of first-class data (URL: http://www.dolphinbeachhouse.com, Name: Dolphin Beach House, Location: Clifden, etc), presentation markup to make the info-window look ok (img align="absmiddle"...), and repetitive absolute references to the base URL of my site that I'd rather not have to set over and over, but which are necessary here.
<img src="http://www.connemara.net/Accommodation/images/providers/Dolphin.jpg">
<div>
    <a href="http://www.dolphinbeachhouse.com">Dolphin Beach House</a>
</div>
<div>
    <span type="type">Guesthouse</span>, <span type="location">Clifden, Sky Road</span>
</div>
<div>
    <span type="facebook">
    <img align="absmiddle"src="http://www.connemara.net/images/facebook.jpg">&nbsp;
<a href="http://www.facebook.com/DolphinBeachHouse">Facebook</a>
    </span>
</div>
If you try and make this XHTML-compliant, by the way, you won't be able to, or at least you'd probably end up in a semantic mess. Place a closing "/>", for instance, on the initial img tag, and watch it disappear when you save. Using span tags and type attributes to convey meaning, I had something that looked ok in the placemark's info-window, and a simple storage schema.

The Feed
Having created a map, and entered some placemarks, you can then get a feed of that map with the RSS link supplied by Google Maps. Parsing that feed gives you your data. So why bother using the Maps Data API? Well, here is how the RSS feed gives you that info-window HTML store:
<title>Dolphin Beach House</title>
<description><![CDATA[
<img src="http://www.connemara.net/Accommodation/images/providers/Dolphin.jpg">
<div>
    <a href="http://www.dolphinbeachhouse.com">Dolphin Beach House</a>
</div>
<div>
    <span type="type">Guesthouse</span>, <span type="location">Clifden, Sky Road</span>
</div>
<div>
    <span type="facebook"><img align="absmiddle" src="http://www.connemara.net/images/facebook.jpg">&nbsp;
<a href="http://www.facebook.com/DolphinBeachHouse">Facebook</a>
</div>]]>
</description>
...
<georss:point>53.497799 -10.094558</georss:point>
<georss:elev>0.000000</georss:elev>
but when you use the Data API feed, you get an ATOM feed, and within each entry: proper KML business (which will become important later on):
<content type="application/vnd.google-earth.kml+xml">
<Placemark xmlns="http://www.opengis.net/kml/2.2">
<name>Dolphin Beach House</name>
<description><![CDATA[
<img src="http://www.connemara.net/Accommodation/images/providers/Dolphin.jpg">
<div>
    <a href="http://www.dolphinbeachhouse.com">Dolphin Beach House</a>
</div>
<div>
    <span type="type">Guesthouse</span>, <span type="location">Clifden, Sky Road</span>
</div>
<div>
    <img src="http://www.connemara.net/Accommodation/images/facebook.jpg" align="absmiddle">&nbsp;
<a href="http://www.facebook.com/DolphinBeachHouse">Facebook</a>
</div>]]>
</description>
<Style>
<IconStyle>
<Icon>
<href>http://maps.gstatic.com/.../blue-dot.png</href>
</Icon>
</IconStyle>
</Style>
<Point>
<coordinates>-10.094558,53.497798,0.0</coordinates>
</Point>
</Placemark>
</content>
Sitting within the content tags - that there's a KML snippet, and that's important. All you'd have to do is wrap it in a
<?xml version="1.0" encoding="UTF-8" ?>
<kml xmlns="http://www.opengis.net/kml/2.2">
and a
</kml>
and you'd have a Google Earth file.

KML is a standard - it transcends the Google universe - and so you have one important standard, KML, delivered via another - ATOM. Standards are important: old farts who play jazz instinctively know this. If we could just get 'Summertime' in there, we'd have 3.

The Data API feed is in ATOM format, as I mentioned, and ATOM is fast-becoming a well-supported means by which collections (such as a map's placemarks) can be returned from a service to a web application for processing, as well as being of course an alternative to RSS for syndicating content on the web.

So, how do you read an ATOM feed containing your placemarks' datastore into your web application? Well, one way would be to use WCF with REST. More in part 2.