Section 4.1. Interfacing MapPoint 2004 with a GPS Device


4.1. Interfacing MapPoint 2004 with a GPS Device

While MapPoint 2004 makes most of its features available via the APIs, there is one feature that you may wish to use that is not available: the ability to interface with a GPS (Global Positioning System) device. Interfacing with a GPS device is fairly independent of any specifics of MapPoint 2004 implementation. The details of MapPoint 2004 interfacing with a GPS device lie in serial port communication and parsing standard NMEA GPS sentence streams. By interfacing them, you can write applications that use MapPoint 2004 and a GPS device to show location in real time.

The first step in building a location tracker application is to understand how GPS works. While it is beyond the scope of this book to discuss it in detail, I want to provide some basics so that it will be easier for those unfamiliar with GPS to understand. If you are already familiar with GPS technology, you can skip the following section.

4.1.1. GPS Basics

GPS is a free radio navigation system built with space satellites launched and managed by the United States Department of Defense . At any given time, there are a minimum of 24 satellites orbiting the Earth 11,000 miles above its surface. The satellites provide GPS receivers on the ground with accurate information about their position, speed, altitude, and direction. GPS receivers receive the GPS radio signals, analyze them, and stream the information in ASCII format for our use. A GPS receiver needs clear signals from at least three satellites to calculate valid position information. Usually, GPS receivers stream the data to a computer at 4,800 bits per second (baud rate); however, this speed can vary depending on the GPS receiver device hardware.

In order to allow MapPoint 2004 to work with a GPS receiver device (or simply, GPS device), you must capture the output ASCII stream and parse the data to obtain the necessary information. The ASCII stream can be in many formats, depending on the GPS receiver device hardware manufacturer. The National Marine Electronics Association (NMEA) 0183 format is one of the most popular formats for the ASCII stream. I will be using this format to program with a GPS device in this section.

The NMEA-0183 specification categorizes the GPS ASCII streams into comma-separated text sentences categorized based on the information contained in each sentence. A basic understanding of these sentences is required, since you need to parse these sentences to obtain the location and other information received from a GPS system.

4.1.2. Understanding NMEA GPS Sentences

While NMEA-0183 defines many GPS sentences, you need to understand only a handful to obtain the required information.

An official copy of NMEA-0183 sentence specification can be obtained from the NMEA web site at http://www.nmea.org/pub/0183/index.html.


Table 4-1 shows some of the key NMEA GPS sentences and their use in application programming. We will be using these sentences in the location tracker application that we will build.

Table 4-1. Useful NMEA-0183 GPS sentences

Sentence type

Used for

Notes

Fixed data

Signal validity, location, number of satellites, time, and altitude

This sentence starts with characters "$GPGGA".

Use this sentence to see whether you are getting valid location information. You can also use this sentence to see the number of satellites that your GPS receiver can access. Examples of this sentence are:

Invalid Signal: $GPGGA,235947.000,0000.0000,N,00000.0000,E,0,00,0.0,0.0,M,,,,0000*00

Valid Signal: $GPGGA,092204.999,4250.5589,S,14718.5084,E,1,04,24.4,19.7,M,,,,0000*1F

Position and time

Location (lat/long), time, ground speed , and bearing

This sentence starts with characters "$GPRMC".

Use this sentence to obtain location (latitude, longitude), time, ground speed, and direction (or bearing). This sentence also provides you with information about the validity of the location information. Examples of this sentence are:

Invalid Signal: $GPRMC,235947.000,V,0000.0000,N,00000.0000,E,,,041299,,*1D

Valid Signal: $GPRMC,092204.999,A,4250.5589,S,14718.5084,E,0.00,89.68,211200,,*25


These sentences may look odd, but they're not difficult to parse. In the following section, I will show how to parse these sentences to obtain location, time, speed, and direction values. If you are not interested in understanding how these sentences are formed, you can simply use the sample assembly (GPS library) packaged with this chapter's information in the companion material, where I also explain how to use this library.

4.1.3. Parsing NMEA Sentences

NMEA sentences are comma-separated lines of text. You need to parse any given sentence using the part index for that sentence. Consider the following fixed data sentence:

     string sentence =     @"$GPGGA,092204.999,4250.5589,S,14718.5084,E,1,04,24.4,19.7,M,,,,0000*1F"

To parse it, use the String.Split method to separate the parts within that sentence:

     string[] parts = sentence.Split(new char[] {','});

Now you can access the individual parts by using an index; for example, the first part is always the sentence header, and the value of the fix data sentence header is always "$GPGGA", so you can use the parts in your application:

     if(parts[0] == "$GPGGA")     {      //Process values      //Get latitude from the third word - index 2      double latitude = this.MyLatConversionRoutine(words[2]);      . . .     }

Since you now know how to parse sentences, let's see how to parse the key fixed data and position/time sentences.

4.1.4. Fields in the Fixed Data Sentence

The fixed data GPS sentence can be used to extract location and other useful information such as time, altitude, and number of satellites. The fields of interest from this sentence are shown in Table 4-2.

Table 4-2. Fields in the fixed data sentence

Index

Name

Example/Value

Notes

0

Sentence ID

"$GPGGA"

Standard header

1

UTC Time

92204.999

Expressed in hhmmss.sss format

2

Latitude

4750.5589

Expressed in degrees, minutes, and decimal minutes format: ddmm.mmmm

3

N/S Indicator

N

N=North (positive), S=South (negative)

4

Longitude

12218.5084

Expressed in degrees, minutes, and decimal minutes format: dddmm.mmmm

5

E/W Indicator

W

E=East (positive), W=West (negative)

6

Position Fix

1

0 = Invalid

1 = Valid SPS

2 = Valid DGPS

3 = Valid PPS

7

Satellites Used

4

Number of satellites being used (0-12)

9

Altitude

19.7

Altitude in meters according to WGS-84 ellipsoid

10

Altitude Units

M

Expressed in meters


Using Table 4-2, you can access the information in a given fixed data sentence:

     //Split the data to get the parts     string[] parts = sentence.Split(new char[] {','});     //Get Time     //2nd part is the UTC Time if(parts[1].Length > 0)         time = ConvertToDateTimeExact(parts[1]);     //Get LatLong     if(parts[2].Length > 0 &&          parts[4].Length > 0)     {         lat = ConvertDegreeMinuteSecondToDegrees(parts[2]);         lon = ConvertDegreeMinuteSecondToDegrees(parts[4]);     }     . . .

There is a problem, however; the words in the sentence are not readily available to be used. Merely extracting the value itself is not useful unless you transform the value into one that is compatible with the application that you are using. In this case, the latitude and longitude must be expressed in degrees using the degrees, minutes, and decimal minutes format used with MapPoint 2004. Similarly, the UTC time expressed in hours, minutes, and seconds must be converted to local time before being displayed in your application. Clearly, we need to convert these values into a format that is understood by our applications. I'll discuss how to make these conversions after we look at the fields in the position and time sentence .

4.1.5. Fields in the Position and Time Sentence

The position and time sentence can be used to extract location information, ground speed information, and bearing information. Table 4-3 shows the fields, along with their indexes, used to extract this information.

Table 4-3. Fields in the position and time sentence

Index

Name

Example/Value

Notes

0

Sentence ID

"$GPRMC"

Standard header

1

UTC Time

92204.999

Expressed in hhmmss.sss format

2

Status

A

A = Valid

V = Invalid

3

Latitude

4750.5589

Expressed in degrees, minutes, and decimal minutes format: ddmm.mmmm

4

N/S Indicator

N

N = North (+ve)

S = South (-ve)

5

Longitude

12218.5084

Expressed in degrees, minutes, and decimal minutes format: dddmm.mmmm

6

E/W Indicator

W

E = East (+ve)

W = West (-ve)

7

Ground Speed

11.39

Speed expressed in knots

8

Ground Course

65.99

Bearing expressed indegrees

9

UTC Date

010105

Date expressed in DDMMYY


4.1.6. Converting NMEA Values

The information in the standard NMEA-0183 sentences needs conversion. For example, ground speed expressed in knots should be converted to either miles per hour or kilometers per hour before it is used in your application, unless you're writing marine applications.

4.1.7. Converting Latitude and Longitude Information

The latitude and longitude information in the NMEA-0183 sentences is expressed in degrees, minutes, and decimal minutes format (dddmm.mmmm) with the direction expressed as a character. To convert this value into decimal degrees, use this formula: degrees = ddd + (mm.mmmm)/60.

The following function performs this conversion:

 public double ConvertDegreeMinuteSecondToDegrees(string input)      {        //GPS Sentences input comes in dddmm.mmmm format        if(input == null || input.Length <=0)           return 0;        double inputvalue = Convert.ToDouble(input);        int degrees = Convert.ToInt32(inputvalue/100);        return degrees + (inputvalue - degrees * 100)/60;      }

The input argument to this function is a string in dddmm.mmmm format. The output is a double value in degrees. There is one additional piece of information you need to add before it is complete: the direction of the latitude/longitude. For NMEA-0183 sentences, the direction is expressed as a character (N, S, E, or W). You can use this character or substitute a plus or minus sign in front of the latitude/longitude in degrees. When the direction is South (S), a negative value is applied to the latitude value; similarly, when the direction is West (W), a negative value is applied to the longitude value.

      //Get direction for Latitude      if(parts[4] == "S")       {          lat = -1 * ConvertDegreeMinuteSecondToDegrees(parts[3]);       } else       {         lat = ConvertDegreeMinuteSecondToDegrees(parts[3]);       }     . . .

4.1.8. Converting the Speed Information

Speed information is expressed as knots in NMEA-0183 sentences; to convert this value into miles per hour, use this formula:

     milesperhour = knots * 1.151

Similarly, to convert the speed into kilometers per hour, use this formula:

     kmperhour = knots * 1.852

This code implements the conversion:

     public double ConvertKnotsToSpeed(string input, string unit)     {          if(input == null || input.Length <= 0)             return 0;         double knots = Convert.ToDouble(input);         if(unit == "Kilometers")          {            //Return KMPH            return knots * 1.852;          }          else          {            //Return MPH            return knots * 1.151;          }     }

The unit (KMPH or MPH) is expressed as a string value; however, it would be good practice to implement it as a .NET enumeration type (refer to the sample code provided in the companion material for more details on how to implement this).

4.1.9. Converting the Bearing Information

The bearing information is typically used to display the direction of travel on the ground. The bearing information is expressed as degrees, but you need to convert it into a meaningful format that can be understood by your application users. Conversion offers two possible displays:

  • A compass image to display the angle of bearing

  • The corresponding direction to the text (for example, N or NE)

The following example shows how to convert the bearing angle into text:

     public static string GetDirectionFromOrientation(double degrees)      {         string direction = string.Empty;         //Anything between 0-20 and 340-360 is showed as north         if((degrees >= 0 && degrees <20) || (degrees <= 360 && degrees > 340))          {             direction = "N";          }          // degrees in between 70 and 110        else if(degrees >= 70 && degrees <= 110)        {           direction = "E";        }        //degrees in between 160 - 200        else if(degrees >= 160 && degrees <= 200)        {           direction = "S";        }        //degrees in between 250 and 290        else if(degrees >= 250 && degrees <= 290)        {           direction = "W";        }        else if(degrees > 0 && degrees < 90)        {           direction = "NE";        }        else if(degrees > 90 && degrees < 180)        {           direction = "SE";        }        else if(degrees > 180 && degrees < 270)        {           direction = "SW";        }        else if(degrees > 270 && degrees < 360)        {           direction = "NW";        }        return direction;      }

I have assumed a 20 degree range on both sides of the 0, 90, 180, 270, and 360 degree marks to approximate it as the corresponding direction; however, it's not mandatory to use the 20 degree range in your applications. You can extend this logic to approximate the bearing information to suit your application needs.

4.1.10. Converting the UTC Time Information

For our last conversion, let's look at the UTC time information . The NMEA-0183 sentences contain the time information in UTC hhmmssss (hours, minutes, and seconds) format; to convert this time information into your local time, use the .NET Framework's DateTime.ToLocalTime method:

     private DateTime ConvertToDateTimeExact(string hhmmsssss)      {         if(hhmmsssss == null || hhmmsssss.Length <= 0)             return DateTime.MinValue;         //Extract hours         int hours = Convert.ToInt32(hhmmsssss.Substring(0, 2));         //Extract minutes         int minutes = Convert.ToInt32(hhmmsssss.Substring(2,2));         //Extract seconds         int seconds = Convert.ToInt32(hhmmsssss.Substring(4,2));     DateTime nowInUniversal = DateTime.Now.ToUniversalTime( );     return new DateTime(nowInUniversal.Year, nowInUniversal.Month,                            nowInUniversal.Day, hours, minutes,     }

The individual hour, minute, and second values are first extracted and then converted to the local time.

Now that you know how to parse and convert the NMEA-0183 GPS sentences, it's time to make your application talk to a GPS receiver device.

4.1.11. Communicating with a GPS device

When you develop your GPS-enabled application, you need to scan the serial communication ports (or simply COM Ports) for a GPS receiver device. Unfortunately, there is no built-in support for serial communication available in the .NET Framework, Version 1.1. There is a shared source Serial Port Communications library available from Microsoft on the GotDotNet samples portal at http://www.gotdotnet.com/. Since building a serial port communication is beyond the scope of this chapter, I will be using these sample libraries, with some minor tweaks, for reading the GPS receiver device ASCII streams.

You can download the Serial Port library sample code from: http://www.gotdotnet.com/Community/UserSamples/Details.aspx?SampleGuid=b06e30f9-1301-4cc6-ac14-dfe325097c69

This library (with these tweaks) is also available with full source code in the companion material.


If you are using the .NET Framework, Version 2.0, the serial port communication libraries are built into the Framework class libraries and are available under the System.IO.Ports namespace.

Let's look at the following code to see how to scan for a GPS receiver device connected to your computer on one of the ports from COM ports 1 to 10:

     string comPortString = string.Empty;     string gpsStream = string.Empty;     bool GPSFound = false;     int comIndex = 1;     while(comIndex < 10)      {        comPortString = "COM" + comIndex.ToString( );        System.IO.Ports.SerialPort comport = null;        try         {            comport                = new System.IO.Ports.SerialPort(comPortString, 4800,                      System.IO.Ports.Parity.None,                      8, System.IO.Ports.StopBits.One);            //Open the port            comport.Open( );            //Set timer            comport.ReadTimeout = 5000;            //Read a line to see if the stream is open            string  sentence = comport.ReadLine( );           if(sentence != null && sentence.Length > 0)             {                GPSFound = true;                break;             }          }          catch(Exception ex)          {          }          finally          {            //Cleanup            if(comport != null && comport.IsOpen)            {               comport.Dispose( );            }          }       }

In the previous code, I'm scanning for a GPS device from ports 1 to 10. How do you do that? Like any IO process, this task involves three steps: open a port, read the port, and dispose of it at the end. Once you detect a GPS device at any COM port, as long as the GPS device is connected, you can treat that COM port as an infinite data source and keep reading the ASCII streams. Once you have the ASCII streams available to your application, you can parse the NMEA-0183 GPS sentences and extract the location, speed, and direction information as shown earlier in this chapter.

This all sounds easy, right? But if you are careful in reading the above code, you might notice that I set a time-out value of 5 seconds before reading a port. This indicates that the COM port scan routine takes a long time to finish. So, if you are using a single-threaded application, users may get annoyed when the UI freezes on them while the main thread is busy waiting for the GPS response.

Similarly, once you detect a GPS device, reading the ASCII streams continuously may make your application unusable if you use a single-threaded approach because of the amount of time required. An event-driven application with multi-thread support should be built into your application to communicate with the GPS receiver device.

4.1.12. Event-Based Architecture for Reading GPS Sentences

Since a GPS device receives the satellite communication continuously, the ASCII sentences will also be available continuously. However, as we have discussed before, since we are only interested in a couple of sentences (fixed data, position, and time), you can deploy a thread to read the GPS device continuously and fire an event when the thread reads either a fixed data sentence or a position and time sentence. Ideally, you would define the corresponding event arguments to wrap the information (such as location, speed, direction, and so on) received from the GPS receiver.

I have included a sample GPS reader class in the companion material, which implements event-driven architecture and multi-threading . You can use this in your applications if you don't want to deal with implementing your own GPS reader classes from scratch. In the following section, I will give details about how to use the sample GPS reader class.

4.1.13. How to Use the Sample API in Your Applications

The sample GPS utility API that is discussed in this section is available in the companion material under Chapter 04.

Follow these steps to use the sample API in your GPS based application (assuming that you have already created a new project):

Add the assemblies ProgrammingMapPoint.GPSUtil.dll and SerialPort.dll as a reference to your project. These assemblies are provided in the Assemblies directory under the root directory.

In your application code, import the GPS utilities namespace:

     //Add GPS Programming Utilities     using ProgrammingMapPoint.GPSUtilities;

Define an instance of the GPSReader class:

     //Define a GPS Reader     private ProgrammingMapPoint.GPSUtilities.GPSReader gpsreader;

Create an instance of the GPSReader class either in either test mode (which reads GPS sentences from a text file) or real mode (which reads GPS sentences from a real GPS device). The following code shows how to create instances in both cases:

  • Create an instance in test mode:

         //TEST MODE - Reads from a file     string filename = @"C:\GPS_Log.txt";     gpsreader = new GPSReader(filename, Units.Miles);

  • Create an instance in real mode:

         //REAL MODE     gpsreader = new GPSReader(Units.Miles);

The next step is to wire up the events. The GPSReader class exposes the events listed in Table 4-4 that you can use in your application.

Table 4-4. Events exposed by the GPSReader class

Event

Description

ScanCommPortEvent

This event is fired when the GPSReader scans a particular COM port. The argument for this event is ScanCommPortEventArgs class, which includes the COM port name and the scan status.

InitializeCompleteEvent

This event is fired when the GPSReader completes the COM port scan process. The argument for this event is InitializeCompleteEventArgs class, which includes the COM port name if a GPS device is available.

SatelliteInfoEvent

This event is fired when the GPSReader receives a satellite info sentence (which starts with "$GPGSA") from a GPS device. The argument for this event is SatelliteInfoEventArgs class, which includes the information about satellites in view.

LocationInfoEvent!!F020!!

This event is fired when the GPSReader receives a position and time sentence (which starts with "$GPRMC") from a GPS device. The argument for this event is LocationInfoEventArgs class, which includes the information about the location (latitude/longitude) and direction.

GroundSpeedInfoEvent

This event is fired when the GPSReader receives a ground speed information sentence (starts with "$GPVTG") from a GPS device. The argument for this event is GroundSpeedInfoEventArgs class, which includes the information about the ground speed.


You can subscribe to these events as follows:

     //Wireup for all events     //PortScan Info     gpsreader.ScanCommPortEvent +=         new ScanCommPortEventHandler(gpsreader_ScanCommPortEvent);     //Loc Info     gpsreader.LocationInfoEvent +=         new LocationInfoEventHander(gpsreader_LocationInfoEvent); //Speed Info     gpsreader.GroundSpeedInfoEvent +=         new GroundSpeedInfoEventHandler(gpsreader_GroundSpeedInfoEvent);     //Satellite Info     gpsreader.SatelliteInfoEvent +=         new SatelliteInfoEventHander(gpsreader_SatelliteInfoEvent);     //Initialize complete     gpsreader.InitializeCompleteEvent +=        new InitializeCompleteEventHandler(gpsreader_InitializeCompleteEvent); 

The event handler methods such as gpsreader_InitializeCompleteEvent implement the necessary logic to display the information received from the GPSReader class. A word of caution: if you are using this class with a Windows application, and you intend to update the UI using these event handler methods, make sure that your code is thread safe by using the Invoke or BeginInvoke methods of the Windows form (refer to the companion material for sample code on this implementation). Wrap your UI update code in a method and invoke it from the event handler method using the Invoke or BeginInvoke method.

Next, scan all COM ports to see whether there is a GPS device. Call the Initialize method:

     //Scan for all COM ports by initializing     gpsreader.Initialize( ); 

Once the initialization is complete, and if a GPS device is found, the GPSReader class is ready to receive GPS sentences. To start receiving them, call the Start method:

     //Start receiving GPS sentences     gpsreader.Start( );

You can also temporarily stop receiving the GPS sentences by calling the Pause method.

Once you start receiving the GPS sentences, the event handler methods take care of updating the location information for your application. When you are done using the GPSReader, make sure to call the Dispose method.

Now that you know how to wire up the GPSReader events and receive the location and speed information, let's look at how you can integrate this information into MapPoint 2004.

4.1.14. Displaying GPS Sentences Using MapPoint 2004

Once you have the location information in latitude and longitude form, you can use the GetLocation method on the MapPoint.Map class. Once you get the MapPoint.Location instance from this method, you can add a pushpin to display the location on the map:

     MapPoint.Location loc          = axMappointControl1.ActiveMap.GetLocation(latitude, longitude,                                  axMappointControl1.ActiveMap.Altitude);     currPushpin          = axMappointControl1.ActiveMap.AddPushpin(loc, "mylocation");     //Symbol 208 is the icon for "1"     currPushpin.Symbol = 208;     currPushpin.GoTo( );     //Set street level view     axMappointControl1.ActiveMap.Altitude = 4;     //Set Highlight     currPushpin.Highlight = true;

Once you create the pushpin on the map to represent your current location, you can keep updating the pushpin location simply by using the Pushpin.Location property:

     //Update the location     currPushpin.Location = loc;

Another challenge that you may see coming is how to keep the map centered on the current location.

4.1.15. Centering the Map on the Current Location

If you or the GPS device is moving, your pushpin on the map also moves. So, how do you keep the map centered on the current location indicated by the GPS device?

Centering the map on a location is easy and is done by calling the Location.GoTo method. The problem with the GoTo method is that it redraws the entire map, making the map flicker each time you call it. To avoid this flickering , (which can happen at the rate of 10 or more times per second) you can define a buffer area (say, an invisible, arbitrary rectangle) around the center of the map and call the GoTo method only when the location lies outside that rectangle.

This technique is as follows:

     //Get X and Y coordinates from the locations     int X = axMappointControl1.ActiveMap.LocationToX(loc);     int Y = axMappointControl1.ActiveMap.LocationToY(loc);     //See if this is outside the 1/3 bounding box     //Simple way of checking this is to calculate     //height (or width) divided by 6;     //which means checking to see if the X or Y     //coord is just 1/3 distance away from either edge. if(axMappointControl1.Height/6 > Y)      {        //Center the map around this location        axMappointControl1.ActiveMap.Location = loc;      }     else if(axMappointControl1.Width/6 > X)      {        axMappointControl1.ActiveMap.Location = loc;      }

In this code example, we get the x and y coordinates of a location on the screen and see whether they lie within a rectangle one-third of the size of the map, according to our specifications. If the current location is inside the fictitious rectangle, there is no need to re-center the map; if it is not, re-center it by assigning the location to the Map.Location property.

You can find this entire implementation in the sample Location Tracker application included in the companion material. The sample application UI is shown in Figure 4-1.

Figure 4-1. Location tracker sample UI


Now that you have seen how to extend MapPoint capabilities by interfacing with a GPS device, let's take another important aspect of advanced MapPoint 2004 programming: integrating your applications with MapPoint 2004 by writing Add-Ins.




Programming MapPoint in  .NET
Programming MapPoint in .NET
ISBN: 0596009062
EAN: 2147483647
Year: 2005
Pages: 136
Authors: Chandu Thota

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net