Reuse in action: Bing Maps REST API and Community Megaphone

I am a fan of small changes that open up big opportunities. I’m also a fan of reuse, and leveraging code in multiple apps or scenarios. And those of you who’ve known or read my blog for any length of time also know that I’m fond of talking about Community Megaphone, which is my current case in point for reuse.

Overview

Over the past few weeks, I’ve been working on a project which will remain secret for now, but which will be revealed early next year. During the course of this project, I was doing some work with geolocation and mapping, specifically building a list of maps for items from the Community Megaphone OData feed. In my initial experiments, I was rendering the maps using the Bing Maps JavaScript API, and I quickly realized that rendering dozens of interactive maps was not conducive to good performance, and practically speaking, wasn’t necessary to give the user the information I was looking to provide.

Enter the Bing Maps REST API. The Bing Maps REST Services provide, among other things, the ability to easily retrieve map tiles that correspond to a given location via a simple URL-based query. This made it easy to generate said URL as a part of the databinding process, and get tiles for each event that had a location (i.e. – in-person events, as opposed to online events). To get the tile, I used the Imagery service. The URL syntax is as simple as this:

http://dev.virtualearth.net/REST/v1/Imagery/Map/Road/Lat,Lon/13?ms=200,200&pp=Lat,Lon&key=APIKey

Here’s the key to the values above:

Lat Latitude value
Lon Longitude value
13 Zoom level (used as part of the path)
ms MapSize argument (width,height)
pp PushPin argument (latitude,longitude)
APIKey Bing Maps API Key (get yours here)

This is just one of many templates you can use to construct your REST URL queries.

This neatly solved the performance concerns in my secret project. It also inspired me to look at where else I could use this neat feature. One place I came up with was in the admin reports section of the Community Megaphone website, which I use to generate reports for my weekly events blog posts, as well as for promoting events in the MSDN Flash newsletter. Here’s a look at the first events post using the new report format:

image_3

Now, instead of just a wall of text, readers get an instant sense of where a given event is located, so they can more easily decide which ones to attend.

Once having added maps to the reports page, I was further inspired to add them to the Community Megaphone RSS feed (link is for the Mid-Atlantic version) as well. You can see the result below:

image_6

Adding the maps to the RSS feed was easy…but during the process, I realized there were a couple of issues with the implementation. Requesting the map tiles via the REST Imagery API is very simple…but if you simply dynamically construct the URL using latitude and longitude parameters and hard-code the Bing Maps API key in the client-side code, then you’re exposing access to your Bing Maps key. In theory, since you must provide a URL when setting up your keys, someone might not be able to use your key on another site. Still…call me paranoid, I’d rather not expose the information if possible.

As importantly, while the reports page is available only to site administrators, and thus has very low traffic (meaning no worries about exceeding any API limits), the RSS feed gets a great deal more traffic, so simply embedding the URL for a given map tile query would result in many more hits to the REST service for a given event listing. Depending on the type of maps account you have, that might exceed the threshold requiring you to pay to use the API.

A New Way to Handle the Problem

Given those two concerns, it was time to think about a different way of embedding the maps, namely setting up an ASP.NET HttpHandler. The job of the handler is simple…take an event ID and width and height values, and turn those into a map tile from the Bing Maps REST Imagery service. For example, the URL:

http://www.communitymegaphone.com/MapTile.ashx?id=4655&w=200&h=200

Returns the following tile (which corresponds to an upcoming talk at the Roanoke .NET User Group):

The Code

Here’s the code for the HttpHandler (I’ve omitted the IsReusable code, since it’s boilerplate):

   1: <%@ WebHandler Language="VB" Class="MapTile" %>

   2:  

   3: Imports System

   4: Imports System.Web

   5: Imports System.Drawing

   6: Imports System.Net

   7: Imports System.IO

   8: Imports System.Drawing.Imaging

   9:  

  10: Public Class MapTile : Implements IHttpHandler

  11:     

  12:     Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest

  13:         context.Response.ContentType = "image/png"

  14:  

  15:         Dim EventID As String = context.Request.QueryString("id")

  16:         Dim Width As Integer

  17:         Dim Height As Integer

  18:         Dim WidthExists As Boolean = Integer.TryParse(context.Request.QueryString("w"), Width)

  19:         Dim HeightExists As Boolean = Integer.TryParse(context.Request.QueryString("h"), Height)

  20:         Dim Key As String = "EventMap" & EventID

  21:         

  22:         If (Not String.IsNullOrEmpty(EventID)) And WidthExists And HeightExists Then

  23:  

  24:             Dim MapTileBytes As Byte() = context.Cache.Get(Key)

  25:  

  26:             If IsNothing(MapTileBytes) Then

  27:  

  28:                 Dim Evt As New CMEventsTableAdapters.EventsTableAdapter

  29:                 Dim EvRow As CMEvents.EventsRow = Evt.GetEventByID(EventID).Rows(0)

  30:  

  31:                 Dim Lat As String = EvRow.latlong.Split(", ")(0)

  32:                 Dim Lon As String = EvRow.latlong.Split(", ")(1)

  33:                 

  34:                 Dim MapTileRequest As New WebClient()

  35:                 Dim MapTileUrl As New Uri("http://dev.virtualearth.net/REST/v1/Imagery/Map/Road/" & Lat & "," & _

  36:                         Lon & "/13?ms=" & Width & "," & Height & "&pp=" & Lat & "," & Lon & "&key=BingMapsKey")

  37:                 MapTileBytes = MapTileRequest.DownloadData(MapTileUrl)

  38:  

  39:                 'cache map tile image for one month

  40:                 context.Cache.Add(Key, MapTileBytes, Nothing, DateTime.MaxValue, New TimeSpan(672, 0, 0), _

  41:                     CacheItemPriority.Normal, Nothing)

  42:             End If

  43:  

  44:             Using MapTileStream As MemoryStream = New MemoryStream(MapTileBytes)

  45:                 Using MapTileImage As Bitmap = Bitmap.FromStream(MapTileStream)

  46:                     Using MapTileStreamOut As MemoryStream = New MemoryStream()

  47:                         MapTileImage.Save(MapTileStreamOut, ImageFormat.Png)

  48:                         MapTileStreamOut.WriteTo(context.Response.OutputStream)

  49:                     End Using

  50:                 End Using                

  51:             End Using

  52:  

  53:             context.Response.End()

  54:         End If

  55:     End Sub

  56: End Class

What it Does

After grabbing the contents of the querystring arguments, and verifying that they exist, the first thing the code does is attempt to get image bytes corresponding to the provided event ID from the cache. If the bytes are cached, we can skip calling the REST service, and just need to send them to the client (more on that in a minute). If the bytes aren’t already cached, the code hits up the database for the row corresponding to the event ID provided, and grabs the latitude and longitude values, then constructs the REST URL using these values, along with a static zoom level of 13, a static argument for a Road map (you could easily make either of these choices dynamic), and my Bing maps key. Then using an instance of WebClient, the code calls DownloadData for the constructed URL and returns the results as a set of bytes, which are then cached for one month (the tile image is pretty much static once the event has been geolocated, and I specifically dump the cache if the event information gets updated), which greatly reduces the number of API calls needed, as well as preventing unnecessary calls to the database.

Whether the bytes are obtained from the cache, or from the REST service, once we have them, they need to be written out to the client. Here’s where things got a little tricky, since you need to do several things just right in order for the images to display properly and actually be treated as images of the correct type, and there are some additional hurdles when it comes to PNGs.

Note that one of the first things the ProcessRequest function does is set the request’s content type to “image/png”. This tells the browser that you want to treat the resource its downloading as a PNG image. But that alone isn’t enough. You still need to make sure the actual image is sent in the correct format, which we do by loading the image bytes into an instance of the Bitmap class by way of the MemoryStream class (more on the why of this in a moment). Once we have the bitmap instance, we call its Save method, passing in another MemoryStream for the output, and the argument ImageFormat.Png to designate the image format we want to use. Then we call WriteTo on the output stream to write the output to the Response object’s OutputStream object. Finally we call Response.End to signal we’re done.

With the HttpHandler complete, I just replaced the code that generated the REST URL in the Reports page and the RSS feed with a call to the handler, and everything worked splendidly.

PNGs are Special

So, what about all these MemoryStreams? Are these really necessary? Well, when I tried saving the bitmap directly to the output stream as a PNG, I got the error “A generic error occurred in GDI+” (very helpful, I know). Bing to the rescue, helping me locate a post by Rick Strahl from way back in 2006 (and he has a related post here), which holds the answer in the comments that PNGs require a bi-directional stream, which is why we use the extra MemoryStream instances above.

It’s Not All Relative

Another hitch that I ran into was that while it worked fine to embed the images in the reports page via a relative URL, that didn’t work for the RSS feed, since most of the time the RSS isn’t being read directly on the website. I ended up manually building up the absolute URL in code using various properties from the Request object. I’m fairly certain that there’s probably a more elegant and succinct way of doing it, so if any of my kind readers have suggestions on that point, please share them.

Pros and Cons

Using an HttpHandler involves a bit of extra code, and is certainly more complex than simply plugging a dynamically-constructed URL into the src attribute of an image tag, but it also confers some nice benefits as well. Here’s a list of the Pros and Cons I see with this approach:

Pros:

  • Enables reuse within the Community Megaphone application, without worrying about the location information, accessing the database, etc.
  • Built-in caching means that retrieving tiles should be lighting fast the vast majority of the time, and reduces hits to the Bing Maps REST API
  • Because the Bing Maps Key is never sent to the client, there’s no risk of unwanted information disclosure
  • Images in blog posts based on the new reports can be automatically picked up by RSS readers and other applications (more on this below)

Cons:

  • Additional code to write and maintain
  • Additional potential attack surface (for DoS, for example, though mitigated significantly by caching)
  • Hard-coding of some values reduces flexibility (though this could be mitigated by adding a couple more querystring parameters for zoom and map style)

Any pros or cons I’m missing? I welcome feedback and/or suggestions on the code above, as I’m sure there’s always room for improvement and optimization.

Conclusion

Screen6_2What started as a way to optimize performance by substituting static resources for active content led to a fairly straightforward way to leverage the Bing Maps REST API to provide additional information for visitors to Community Megaphone, whether they’re finding out about events on my blog, or via the RSS feed. But there was an unexpected bonus as well. Many feed reader applications will represent a given post with the first image that they find in the post, so by adding the images to the feed, I can automatically take advantage of that. For example, I created a simple application using the FollowMyFeed app generator for Windows Phone 7 which reads the RSS feed from my blog and Community Megaphone, and displays them nicely formatted on the phone. To the left, you can see a screenshot from the Community Megaphone feed view in the app. The great thing about this is that I didn’t have to do anything special to make this happen, beyond adding the map tile images to the RSS feed.

An easy-to-use API, more information for site visitors and RSS subscribers, and bonus cost-free reusability in mobile apps, all adds up to a major win for some pretty straightforward code.

Do you have similar experiences with APIs that gave you big wins for a small amount of code? Feel free to share them in the comments!

11 thoughts on “Reuse in action: Bing Maps REST API and Community Megaphone”

  1. Really cool, though I feel like there must be a more efficient way to build the absolute url–I’ll let you know if I think of one. This post got me excited for Wherecamp!

    1. Glad you like it. I agree on the absolute URL, and the part that bugs me is that I’ve been doing this long enough that I have probably even written code that does it more elegantly, and simply managed to forget how. Clearly I didn’t blog it, though, because that’s how I usually end up remembering such things…using the web as an extension of my brain.

    2.  An added laugh…I went back to the code that generates the RSS feed and realized that I’d already had code in it to create the base URL for any relative links. Nothing like writing the same code twice and not even realizing you’ve done it. Ah well, at least I was able to save a few lines of code once I removed the redundant stuff.

  2. This was an excellent post! I also use the Community
    Megaphone RSS feed and like seeing the maps in there. Since you cracked open
    the RSS feed code, I do have one small request. Can you add the Event’s Website
    to it? This is the only piece of information that is currently not in the feed.
    To retrieve it you have to go back to the Community Megaphone website.
    Thanks a
    lot for your community involvement and have a great day presenting on the .NET
    Gadgeteer Nov 12th!

    1. Gerhard,

      Try the RSS feed now. And thank you for the feedback. I didn’t even realize that I wasn’t including the event website…a peril of staring at the same code for a long time…easy to miss stuff. So I went ahead and added the website to the feed, and it’s there now. Hope that will save you some effort.

      1. I noticed it this morning even before reading your reply above. Now I am one happy clicker! I also look forward to your presentation at the Great Lakes Area .NET Users Group (GANG). It is going to be exciting to finally meet you. What date was that going to be again? 😉

        1. LOL…thanks for reminding me…I’ve been buried in email, but yes, I owe you and David a response on that. Will check with my wife and see when we can arrange it.

      2. I cannot tell you how much I like having the map graphic in the RSS feed. Having a picture really spruces it up. I just noticed a RSS feed that did not have a picture and surprisingly enough it confused me. I thought why doesn’t it have one. Then it dawned on me it was a Virtual/Online event. Here is one last idea for the RSS feed. Could you show a static picture for Online/Vitural Events? This would be the same size as the map. It would also be a nice visual to show it is an on-line.event too. Another spot it comes in handy is when you are paging down the RSS feed all the text will be located in a simliar spot. Instead of the Virtual Events text being all on the left part of the page. http://www.communitymegaphone.com/madEventRss.ashx

        1. I agree that the map images add a nice dimension to the feed (and the reports).

          I like the idea of a graphic for the online events a lot, but off the top of my head, I’m not sure of the best way to graphically represent an online event, and still have the image have some of the richness of the map graphics. I’m concerned that if it’s too simple a representation, it would stand out like a sore thumb compared to the maps.

          Do you have any suggestions for how the online events should be represented? Perhaps you could point out some graphics elsewhere that are close to what you have in mind? 

        2. OK. Had a little time this morning, so I went ahead and implemented the icon for the online events. I just used the Community Megaphone logo, sized the same as the map tiles. Let me know what you think of this implementation.

Comments are closed.