Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Flight Tracker - An Application for Mapping Flight Routes

0.00/5 (No votes)
30 May 2004 1  
An entertaining exploration of .NET Compact Framework graphics and XML features.

Introduction

This program is more an exploration of the Compact Framework than an actual useful application. My first goal was to have some fun while getting away from the everyday "Hello World" type apps that are ordinarily used to illustrate features and techniques. When I was a teenager, I had a friend who was obsessed with airline flight schedules. He would spend countless hours drawing maps of airline routes by hand. They were not terribly useful (unless you were an airline industry analyst), but they were pretty cool to look at.

I decided to create a PocketPC solution for drawing airline maps. The "Flight Tracker", as I dubbed it, would included the ability to enter and maintain data for individual airports, airlines and flights. I limited my map area to North America, and only entered the data for a subset of the Southwest Airlines flight schedule. A comprehensive database would have taken months for data entry or required the purchase of commercial flight databases. This project would require the use of basic .NET form controls, as well as the Graphics interface and .NET XML classes for data storage.

Managing Flight Data

The first task was to compile and manage the data needed to draw flight maps. I created a class, FlightData.vb, to contain all data-related structures, arrays and methods. I prefer to use structures and arrays of structures to hold program data because it makes it so easy to see what you're doing in the code. This arrangement is also very efficient in terms of memory and processor usage. I wanted to store the data in an open, cross-platform format, so I decided to try out the XML class in .NET.

The System.XML.XmlDocument class makes it easy to load and save the contents of XML files. You simply instantiate the XmlDocument object and then call its Load or Save method with the desired filename. The data is stored in XmlNode and XmlAttribute objects within the XmlDocument. Each XmlNode contains a collection of child XmlNodes and a collection of XmlAttributes. I included in each of my structure definitions a method to load a new structure's member values from an XmlNode object and a method to return a new XmlNode containing the member values. Here I've included the simplest of my structures to illustrate this configuration:

Public Structure AirlineStruct
        Dim Airline As String

        Public Sub New(ByRef XmlNode As Xml.XmlNode)
            Dim xAttribute As Xml.XmlAttribute
            For Each xAttribute In XmlNode.Attributes
                Select Case xAttribute.Name
                    Case "Airline"
                        Airline = xAttribute.Value
                End Select
            Next
        End Sub

        Public Function GetXmlNode(ByRef Doc As Xml.XmlDocument) _
              As Xml.XmlNode
            Dim Node As Xml.XmlNode = Doc.CreateNode( _
               Xml.XmlNodeType.Element, "AIRLINE", "")
            Node.Attributes.Append(GetXmlAttribute(Doc, "Airline", Airline))
            Return Node
        End Function
    End Structure

One interesting limitation to the XmlNode and XmlAttribute classes is that they can only be instantiated by an XmlDocument object. This was a little annoying in my case since I had to pass a pointer to the XmlDocument to my structure's GetXmlNode method just for this purpose.

With these methods in place, saving the class's data consists of creating an XmlDocument and adding an XmlNode for each structure stored in the class's arrays. When XmlNodes have all been added, simply call the XmlDocument's Save method.

    Public Sub Save()
        Dim xDoc As New Xml.XmlDocument
        Try
            xDoc.AppendChild(xDoc.CreateElement("FlightData"))
            Dim Flight As FlightStruct
            For Each Flight In Flights
                xDoc.FirstChild.AppendChild(Flight.GetXmlNode(xDoc))
            Next
            Dim Airline As AirlineStruct
            For Each Airline In Airlines
                xDoc.FirstChild.AppendChild(Airline.GetXmlNode(xDoc))
            Next
            Dim Airport As AirportStruct
            For Each Airport In Airports
                xDoc.FirstChild.AppendChild(Airport.GetXmlNode(xDoc))
            Next
            xDoc.Save("\Program Files\Flight Tracker\FlightData.xml")
            HasUnsavedChanges = False
        Catch ex As Exception
            MsgBox("Error saving XML data file.", _
               MsgBoxStyle.Exclamation, "File Error")
        End Try
    End Sub

Note that the all the XmlDocument's other nodes are typically located within the document's first child node, which you must create and add yourself if when assembling the new file's contents.

Loading is easily accomplished by loading the XmlDocument from file and then sequencing through the collection of child nodes, loading each one into the appropriate structure type and adding the structure its corresponding array.

    Public Sub Load()
        Dim xDoc As New Xml.XmlDocument
        Try
            Dim Flight As FlightStruct
            Dim Airline As AirlineStruct
            Dim Airport As AirportStruct
            ReDim Flights(-1)
            ReDim Airlines(-1)
            ReDim Airports(-1)
            xDoc.Load("\Program Files\Flight Tracker\FlightData.xml")
            Dim Root As Xml.XmlNode = xDoc.FirstChild
            Dim Child As Xml.XmlNode
            For Each Child In Root.ChildNodes
                Select Case Child.Name
                    Case "FLIGHT"
                        Flight = New FlightStruct(Child)
                        ReDim Preserve Flights(UBound(Flights) + 1)
                        Flights(UBound(Flights)) = Flight
                    Case "AIRLINE"
                        Airline = New AirlineStruct(Child)
                        ReDim Preserve Airlines(UBound(Airlines) + 1)
                        Airlines(UBound(Airlines)) = Airline
                    Case "AIRPORT"
                        Airport = New AirportStruct(Child)
                        ReDim Preserve Airports(UBound(Airports) + 1)
                        Airports(UBound(Airports)) = Airport
                End Select
            Next
            HasUnsavedChanges = False
        Catch ex As Exception
            MsgBox("Error loading XML data file.", _
                MsgBoxStyle.Exclamation, "File Error")
        End Try
    End Sub

The rest of the data management portion of the program consisted of a tabbed dialog with ListView controls for Flights, Airlines and Airports, as well as buttons and menus for creating, editing, deleting and sorting the list elements.

Drawing The Flight Map

The first thing I needed was a background image for my map. I found a simple outline of the North American continent on a CorelDraw 8 clip art CD (I assume that it is royalty-free, but I couldn't find any documentation to verify this). I changed the color scheme of the map and converted it to a 240 x 274 JPG. Everything was going fine until I tried to run the program on my iPAQ 3765 (what I use for development purposes at work). The program started to load...and then exited with an ArgumentException! I tried to load the image dynamically at runtime but still ended up with the same error message. After a couple of hours scouring the internet for an answer, I finally found the following post on the OpenNetCF.org forums: 16bpp Bitmap allways an error - Terry Mohre. The posting stated that, "16bpp ALWAYS raises an 'ArgumentException' and fails to instantiate the Bitmap class". I checked my JPG and ,sure enough, it was in 16 bits. I converted it to 24 bit and it loaded without a hitch.

Next, as I began to fill in the Paint event handler for my map, I realized that all the familiar .NET desktop Graphics features had been pared down to a tiny subset in the Compact Framework. I set out in my web browser once again to see what solutions were available in place of the GDI+ double-buffering and anti-aliasing features. I quickly found a solid example for a double-buffering technique on MSDN: How to Create a Microsoft .NET Compact Framework-based Image Button - Alex Yakhnin. The first part of this technique was to override the OnPaintBackground event handler in the .NET Control class. Because this event handler is a protected class member, you must create a derived class to perform the override. This new class is used wherever a double buffered drawing surface is needed throughout the program.

Public Class MapCanvas
    Inherits System.Windows.Forms.Control

    Protected Overrides Sub OnPaintBackground(ByVal e As _
         System.Windows.Forms.PaintEventArgs)
        'do nothing
    End Sub
End Class

Although this class worked fine in the compiled program, I must say that it wreaked havoc in the development environment. Maybe this is somehow related to the fact that the Compact Framework does not support the UserControl class that is normally used for this sort of thing? As a result, I was constantly having to delete and re-create the canvas objects in my forms. Sometimes I had to restart Visual Studio to get it to recognize that the MapCanvases were valid objects.

The second part of the double-buffering technique was to draw everything in an off-screen bitmap first, and then copy the bitmap onto the MapCanvas object. This is illustrated by the Paint handler from the airport location picking dialog:

    Private Sub Canvas_Paint(ByVal sender As Object, ByVal e _
         As System.Windows.Forms.PaintEventArgs) Handles Canvas.Paint
        Dim bg As Graphics = Graphics.FromImage(bmBuffer)
        bg.DrawImage(pbMap.Image, 0, 0)
        bg.DrawLine(Pen, 0, YPos, 240, YPos)
        bg.DrawLine(Pen, XPos, 0, XPos, 272)
        bg.Dispose()
        e.Graphics.DrawImage(bmBuffer, 0, 0)
    End Sub

You can make the Paint handler run faster by maintaining graphics objects at the form-level...

    Private Brush As New SolidBrush(Color.Blue)
    Private Pen As New Pen(Color.Blue)
    Private bmBuffer As Bitmap

As for anti-aliasing, there doesn't appear to be any support for it in the PocketPC's GDI. I'm sure there are probably third-party libraries that could provided features equivalent to the desktop's GDI+, but I didn't spend any time looking for them.

Once my double-buffering code was in place, I processed the contents of the flight data class to fill an array of RectangleF objects, each of which represented a single flight path. A corresponding array of integers was used to store heading information which determined which corners of the rectangle were the origination point and destination point. In order to animate the flight paths on the map, a Timer was used to increment the rectangle sizes from 0 to 100% in a succession of screen refreshes. The timer and rectangles were a quick and dirty approach, but they worked rather well.

Using the Program

The program is easy to use. Just copy the .NET CF executable to your PocketPC (You must put it in "\Program Files\Flight Tracker". Create if necessary), along with the FlightData.xml file. The program will initially show a map of airport locations. Click Show/Flights to see the animated map of airline routes. Clicking Tools/Edit Flight Data will open the tabbed dialog where you can add the flight info for your favorite airline!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here