You might think that graphically intensive operations such as map rendering can be done only using languages like C++ to make Win32 API calls to the operating system. In fact, the .NET Framework and its GDI+ libraries can be used not only to render intense complex graphics but can do so efficiently. With Spatially Aware's Map Suite components for .NET it is not only possible but extremely easy to add mapping functionality to your .NET applications.
It's early morning and Cornell University's Unmanned Aerial Vehicle Team is taking to the skies for another test flight of their entry in the Association for Unmanned Vehicle Systems International's (AUVSI) second annual competition. On board the small unmanned aircraft is a processor running embedded Windows XP and the .NET Framework. As the aircraft moves through the sky, its progress is tracked via an onboard global positioning system that sends data to a ground station and on to Map Suite for rendering in a full-color, feature-rich map. Map Suite in turn provides visual information about the flight path and waypoints so the aircraft can stay on course and the operators can ultimately make corrections if needed.
Cornell's team chose to use the Map Suite components because they needed mapping functionality that would plug right in to their .NET flight tracking client software. In a nutshell, this is accomplished by the aforementioned GPS, located on the underbelly of the aircraft, sending coordinates and gyroscopic headings via a wireless modem to a ground control system. The coordinates, provided in longitude and latitude, are then loaded into Map Suite and the position and heading of a "point" on the map (in this case the aircraft) are updated in real time.
Building that sort of functionality from scratch would not only have been extremely difficult, even for this talented team, but nearly impossible in the amount of time they had to complete the project and get ready for competition. While the details of how the Cornell team uses the hardware and computer systems to fly the plane are complex and way beyond the scope of this article, the mapping portion is surprisingly easy and required only a few lines of code to integrate into their existing .NET client application.
Introduction to Map Suite
Map Suite utilizes the Graphics Device Interface (GDI+) libraries of the .NET Framework. Previous versions of the GDI interface were somewhat difficult to use, but the most recent version implemented in .NET is much simpler and requires a lot less coding. Even with the ease of use, writing code to render maps is still a very tedious, time-consuming task that can be both graphically and mathematically intensive. The .NET Framework, however, can handle this sort of graphically intense operation, and Map Suite's easy-to-use APIs allow you to have a map-aware application up and running literally in minutes.
Map Suite uses open source Shape Data (see sidebar) to render maps. The Cornell team used the shape data correlating to their flight area to load in various layers of the map, including polygon-based structures like cities, counties, and states, as well as features like rivers, lakes, roads, and points that represent cities and other landmarks. They incorporated this into their client (aptly named Hermes), which is a host of several different tools, each designed to help with navigation or monitoring of the aircraft.
A Sample Flight-Tracking Application
To demonstrate how easy it was for the Cornell team to use Map Suite for tracking their aircraft, we will look at a sample flight-tracking application that will track an aircraft flying around a map of the city of Austin, TX. Let's start out by taking a look at Map Suite's map object. A map object is the highest-level object that encompasses the layers, thresholds, renderers, and symbols that make up a map. I will touch on these other objects later, but for now a map can be thought of as the container for everything that helps to render map data.
The first - and one of the most important parts of building the map - is to specify the MapUnit property (e.g., decimal degrees, feet, meters) so the map will render properly.
Map1.MapUnit = _
MapSuite.Geometry.MapLengthUnits.DecimalDegrees
The next step is to create a new layer that will be added to the layer Collection of the map object. A layer in a map correlates to a single shape file such as a road network or a boundary. You can think of layers as being similar to actual terrain in the real world. The bare earth might be a layer and has either physically defined boundaries, such as a fence around a military installation; or legal boundaries, such as the border of a country. Adding layers to the map in order is important, as they need to be added in the logical order they would actually occur in the real world. In other words, you would not want to lay down roads and then cover them with lakes or rivers so they could not be seen or used by vehicles. I will keep our example simple and add only a single layer, which is the shape file containing the streets of Austin:
Dim StreetLayer As New _
Layer("..\data\austinstreets.shp")
StreetLayer.ThresholdUnit = _
Geometry.ThresholdUnit.Miles
We also set the Threshold ExtentUnits property for the layer. This property defines which unit (miles, for example) the upper and lower thresholds will be in so that the Layer renders properly. Thresholds tell layers when to render in the map. To illustrate this point, imagine that you are looking at an aerial view of the landscape of the Earth through your computer monitor. You can see only a limited area of the map, from the left side of the viewable screen to the right side. As you move further away from the surface, what you can see in the viewable area expands and reaches further across the width of the screen. As you move closer, what is available to be seen in the viewable width of the screen becomes less and less. A threshold works much the same way, by telling a layer when it is appropriate to render or display and when it is no longer appropriate to display - by defining the ranges. Thresholds also use renderers, which render symbols. These give the map the actual look and feel (e.g., color, shading, width of lines). The following code snippet shows how a threshold can be defined, along with a renderer, and added to the Threshold Collection of the Layer:
Dim NonDetailedThreshold As New _
Threshold(100, 2)
Dim NonDetailedSymbol As New _
SymbolRenderer(New LineSymbol( _
New Pen(Color.ForestGreen)))
NonDetailedThreshold.SymbolRenderers.Add( _
NonDetailedSymbol)
StreetLayer.Thresholds.Add( _
NonDetailedThreshold)
In this code snippet, the threshold is created and an upper limit of 100 miles and a lower limit of 2 miles are set. A new SymbolRenderer, which is used to tell the threshold how the streets are to be rendered, is also created and added to the SymbolRenderers Collection of the threshold. Since we are rendering roads, we use a LineSymbol, which can take an object of type pen. The pen is a GDI+ object that is used to draw lines and curves and can take a color object as an argument to the constructor. Map Suite uses objects such as the pen and brush from GDI+ to render primitives like lines, curves and figures. Note: Map Suite does not limit the interaction with GDI+ through its interface. Developers can be as creative as they want with the GDI+ libraries.
The last line of the code snippet adds the threshold to the threshold Collection of the layer. Now the layer has information about how to render the road shape data that it has been associated with. We can now add the layer to the map:
Map1.Layers.Add(StreetLayer)
With the map in place, we need to have a way to track an aircraft flying in the airspace over Austin. As mentioned earlier, the Cornell team used GPS coordinates to accomplish this. Our coordinates come from a simple text file to simulate the flight path and heading of the aircraft. The coordinates are read in and a PointSymbol is created so we can show the plane as an icon on the map, along with its current location and heading:
Dim PlaneSymbol As New PointSymbol( _
New Bitmap("..\data\plane.gif"), 0, 0, CurrentAngle)
The code uses a PointSymbol to show the aircraft and loads a new bitmap, giving the X and Y offset (in this case 0 for both the X and Y axis, meaning there is no desired offset) and an angle of the plane. Cornell used a gyroscope to provide this information to Map Suite during flight. An example of the data is shown in the following:
-97.7511007462687 : 30.3060302405498 : 125
The first two numbers are longitude and latitude coordinates, and the third is the angle at which the aircraft is heading. A PointShape is used to hold the GPS coordinates. The code to load the data from the text file is omitted here for clarity but is available in the sample application:
Dim PlanePosition As New PointShape
PlanePosition.X = -97.7511007462687
PlanePosition.Y = 30.3060302405498
A PointMapShape object is then created to encompass the PointSymbol and the PointShape, and it is added to the MapShapes Collection of the map:
Dim PlaneMapShape As New PointMapShape( _
PlanePosition, PlaneSymbol)
Map1.MapShapes.Clear()
Map1.MapShapes.Add(PlaneMapShape)
Finally, the RefreshDynamic() method is called on the Map so the plane can be redrawn with the coordinates and heading that have just been loaded. RefreshDynamic() is used because it allows dynamic or moving items such as the aircraft to be rerendered without rerendering the entire map. We will now put all of this into a Timer that loads a line of data and updates the position of the aircraft on the map (see Figure 1).
Notice in Figure 1 how the roads look with the threshold that has been applied. You might expect that as you move in closer to the map that the way our roads Layer is rendered would change significantly. This is correct, but of course we need to tell Map Suite how we want the roads to look when we reach a certain threshold. Luckily, layers can have multiple thresholds in their collections. Following is the code to add our second threshold:
Dim DetailedThreshold As New Threshold(2, 0)
Dim DetailedSymbol1 As New SymbolRenderer(New _
LineSymbol(New Pen(Color.ForestGreen, 6)))
Dim DetailedSymbol2 As New SymbolRenderer( _
New LineSymbol(New Pen(Color.White, 4)))
DetailedThreshold.SymbolRenderers.Add(DetailedSymbol1)
DetailedThreshold.SymbolRenderers.Add(DetailedSymbol2)
This threshold is defined to start at 2 miles and go to 0 miles. Remember that we defined our first threshold as 100 miles down to 2 miles. When the map reaches below 2 miles, it will switch over to this threshold. Something else that would be nice is to display the names of the streets as we get closer. This is accomplished very easily as well because thresholds can also have what are called LabelRenderers. In the following code we define a LabelRenderer and correlate it with a field in our associated DBF file (see sidebar on Shape Data).
Dim DetailedLabel As New _
LabelRenderer("Name", New TextSymbol( _
New Font("Arial", 10), New SolidBrush(Color.Black), 0, 10))
DetailedThreshold.LabelRenderers.Add(DetailedLabel)
Adding this new threshold to the layer now gives our application a whole new view when we reach the 2-mile threshold (see Figure 2).
The Competition Results
The Cornell team ended up receiving accolades for the best software in the competition by demonstrating to the judges the software utilizing Map Suite and an interface they had built using Microsoft's Flight Simulator for testing. This alone earned them the right to be finalists in Microsoft's Imagine Cup Competition, in both subsequent San Francisco and the Princeton rounds of competition. They also attracted the attention of major sponsors and had the most interest in their project, due to their fully custom system.
Shape Data
Shape Data, an open-standard format created by ESRI (the Environmental Systems Research Institute), is used by Map Suite to provide data about how a map is going to be rendered. Shape files store binary vector coordinates to be used by the component and have two other supplementary files; the SHX file, used to provide a simple index of the data, and a DBF file, which holds tabular data associated with the main shape file. There are several sites on the Internet that provide free shape data. Some of them are listed at www.spatiallyaware.com/dndj.
Summary
This article has given you an overview of the power of Map Suite and the .NET Framework's GDI+ libraries and how they provided a practical solution for the Cornell team. The example FlightTracker application showed only a small portion of what can be graphically rendered by Map Suite and is limited only by your imagination (see Figure 3). Using Map Suite, any .NET developer can quickly and easily build map-aware applications with incredible power and built-in functionality, including panning, zooming, and spatial querying, just to name a few. Map Suite comes in both Windows Forms and Web Forms versions for both desktop and ASP.NET development. You can download a fully functional demo version from www.spatiallyaware.com/dndj.