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:
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:
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
What 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!

