A Mapstraction ViewComponent for MonoRail
The Mapstraction JavaScript library provides a common API for working with various mapping providers. While the API is reasonably simple, there are aspects which lend themselves to the reusability provided by a MonoRail ViewComponent. This article will demonstrate how to wrap the Mapstraction API in an easy to use ViewComponent. The sample provided here will be expanded on over time. When it is more complete, updates will be posted and made available for download.
Since most of the output for the sample is to be JavaScript, it's convenient to have a base class that extends ViewComponent to provide some formatting methods. The two methods below will serve to provide both string formatting and new lines for each chunk of rendered JavaScript.
public class ViewComponentBase : ViewComponent
{
public void RenderTextLine(string textToRender, params object[] args)
{
RenderText(string.Format(string.Concat(textToRender, "\n"), args));
}
public void RenderTextLine(string textToRender)
{
RenderText(string.Concat(textToRender, "\n"));
}
}
It will also be necessary to represent points on a map (Latitude, longitude, etc.). For this purpose, a rather generic Location class will
be used. Location could very easily be an ActiveRecord class that maps to a table of location data. Building out the AR version of Location will be left for another article.
public class Location
{
public int Id { get; set; }
public string City { get; set; }
public string State { get; set; }
public float Latitude { get; set; }
public float Longitude { get; set; }
}
The view files on which the MapstractionComponent will be used need to include the required provider and Mapstraction JavaScript files. The Mapstraction JavaScript file should be downloaded and saved into a local directory.
<script type="text/javascript" src="http://api.maps.yahoo.com/ajaxymap?v=3.0&appid=API_KEY_GOES_HERE"></script>
<script language="javascript" type="text/javascript" src="$SiteRoot/Content/Scripts/Mapstraction.js"></script>
Note: Most mapping providers allow for the API libraries to be included directly from the provider's servers. However, API keys are typically required. Any site using Mapstraction will need to have its own appropriate API keys.
Again, the MapstractionComponent will extend the custom ViewComponentBase class defined in the above section. Two sections are included for providing a flexible mechanism to add HTML to the markers (labeled points) on the map.
[ViewComponentDetails("Mapstraction", Sections = "CenterPointBubbleHtml,NearbyPointBubbleHtml")]
public class MapstractionComponent : ViewComponentBase
{
...
}
Though several properties could be defined on the MapstractionComponent to expose additional functionality, this sample will be kept intentionally simple. Map control features (pan, zoom, etc.) will not be supported in this version. The set of component parameters will include those necessary for setting a center point, nearby points, map provider, containing DIV id and the name of the Mapstraction class instance. Point properties will use the Location class defined above.
Click to View Sample Map
[ViewComponentParam]
public string Name { get; set; }
[ViewComponentParam]
public string Container { get; set; }
[ViewComponentParam]
public string Provider { get; set; }
[ViewComponentParam]
public Location CenterPoint { get; set; }
[ViewComponentParam]
public Location[] NearbyLocations { get; set; }
The body of the MapstractionComponent's Render method begins by checking for the Name and Container values, defaulting both to 'mapstraction' when not supplied. The Name parameter identifies an instance and the Container identifies the DIV into which the map should be placed.
if (string.IsNullOrEmpty(Name)) Name = "mapstraction";
if (string.IsNullOrEmpty(Container)) Container = "mapstraction";
RenderTextLine("<script type=\"text/javascript\">");
RenderTextLine("var {0} = new Mapstraction('{1}','{2}');", Name, Container, Provider);
Next, the map is zoomed on to the center point and a marker that identifies the center point is added.
RenderTextLine("var centerPoint = new LatLonPoint({0}, {1});", CenterPoint.Latitude, CenterPoint.Longitude);
RenderTextLine("{0}.setCenterAndZoom(centerPoint, {1});", Name, DEFAULT_ZOOM_LEVEL);
RenderTextLine("{0}.addControls({{pan: true, zoom: 'large', map_type: true}});", Name);
RenderTextLine("var marker = new Marker(centerPoint);");
RenderTextLine("{0}.addMarker(marker);", Name);
HTML may be added to this marker by way of the CenterPointBubbleHtml section. The component is simplified by assuming that this section contains a variable named centerPointBubbleHtml that contains the required HTML.
#CenterPointBubbleHtml
var centerPointBubbleHtml = '<strong>$CenterPoint.City</strong>';
#end
The component renders the section if present.
if (Context.HasSection("CenterPointBubbleHtml"))
{
PropertyBag["CenterPoint"] = CenterPoint;
RenderSection("CenterPointBubbleHtml");
RenderTextLine("marker.setInfoBubble(centerPointBubbleHtml);");
}
The next chunk of the Render method loops over the nearby locations and creates new points and corresponding point markers.
if (Context.HasSection("NearbyPointBubbleHtml"))
{
foreach (Location location in NearbyPoints)
{
PropertyBag["NearbyPoint"] = location;
RenderTextLine("var point{0} = new LatLonPoint({1},{2});", location.Id, location.Latitude, location.Longitude);
RenderTextLine("var marker{0} = new Marker(point{0});", location.Id);
RenderSection("NearbyPointBubbleHtml");
RenderTextLine("marker{0}.setInfoBubble(nearbyPointBubbleHtml);", location.Id);
RenderTextLine("{0}.addMarker(marker{1});", Name, location.Id);
}
}
The nearbyPointBubbleHtml is added through the NearbyPointBubbleHtml, much as the CenterPointBubbleHtml provided HTML for the center point's marker.
#NearbyPointBubbleHtml
var nearbyPointBubbleHtml = '<a href=\'$SiteRoot/Location/$NearbyLocation.City/Default.aspx\'>$NearbyLocation.City</a>';
#end
Finally, the center point's marker is opened, which shows the centerPointBubbleHtml. All nearbyPointBubbleHtml values are displayed only on marker clicks.
RenderTextLine("marker.openBubble();");
RenderTextLine("</script>");
The sample app simply hardcodes some location data to show that the component actually displays a map.
PropertyBag["CenterPoint"] = new Location()
{
Id = 1,
City = "Fairfield",
State = "Conn",
Latitude = 41.16f,
Longitude = -73.26f
};
PropertyBag["NearbyPoints"] = new Location[]
{
new Location()
{
Id = 1,
City = "Fairfield",
State = "Conn",
Latitude = 41.05f,
Longitude = -73.54f
},
new Location()
{
Id = 1,
City = "New Haven",
State = "Conn",
Latitude = 41.3f,
Longitude = -72.94f
}
};
}/
Using the new component in our view requires setting the provider and Location parameters.
<div id="mapstraction" style="width:600px;height:400px;">
#blockcomponent(MapstractionComponent with "Provider=yahoo" "CenterPoint=$CenterPoint" "NearbyPoints=$NearbyPoints")
#CenterPointBubbleHtml
var centerPointBubbleHtml = '<strong>$CenterPoint.City</strong>';
#end
#NearbyPointBubbleHtml
var nearbyPointBubbleHtml = '<a href=\'Local/${NearbyPoint.City}.aspx\'>Link about $NearbyPoint.City</a>';
#end
#end
</div>
Download Sample Project
References
Mapstraction
Yahoo! Maps API Guide
The Zip Code Project - free US zip code database
Sample project source code
Article Posted: Thursday, January 03, 2008