3.8. Where Are We?
In this chapter, you have learned how to work with native demographic data and your business data to generate data maps in different map styles. You have also seen how to import data into MapPoint from variety of data source such as text, Excel, Access, SQL, and so on. We also have examined working with
Working with the
DataSets
and
Shapes
collections can be among the most interesting
In the
|
Chapter 4. Advanced MapPoint 2004 Programming
In the previous chapters, you learned how to use MapPoint 2004 APIs to perform simple location-oriented tasks, business data display, and management
Sample applications used in this chapter are available in the companion material under the Chapter04 directory. |
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
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
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 SentencesWhile NMEA-0183 defines many GPS sentences, you need to understand only a handful to obtain the required information.
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
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
4.1.3. Parsing NMEA SentencesNMEA 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
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 SentenceThe 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
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
4.1.5. Fields in the Position and Time SentenceThe 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
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
4.1.7. Converting Latitude and Longitude InformationThe 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
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 InformationSpeed 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 InformationThe 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:
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 InformationFor 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 deviceWhen 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.
{% if main.adsdop %}{% include 'adsenceinline.tpl' %}{% endif %} 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
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 SentencesSince 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
4.1.13. How to Use the Sample API in Your ApplicationsThe 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 (
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:
The
Table 4-4. Events exposed by the GPSReader class
You can subscribe to these events as
//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
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 2004Once 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 LocationIf 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
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
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. |