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 tasks. Now it’s time to learn how to integrate and extend MapPoint 2004 APIs, as well as how to improve the performance and memory usage of your MapPoint 2004 applications. You’ll learn how to:

  • Extend MapPoint 2004 by interfacing with a GPS device

  • Integrate your applications by writing COM Add-Ins

  • Improve your MapPoint 2004 application performance

Sample applications used in this chapter are available in the companion material under the Chapter04 directory.

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.

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.

Understanding NMEA GPS Sentences

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

Tip

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.

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.

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 .

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

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.

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]);
      }
    . . .

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).

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.

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.

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.

Tip

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.

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.

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.

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.

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.

Location tracker sample UI
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.

Integrating Your Applications with MapPoint 2004

Like any other Microsoft Office application (and also like Visual Studio .NET), MapPoint 2004 supports extensibility architecture, which means that you can develop applications that can plug right into the MapPoint 2004 application. In this section, I will show how to develop Add-Ins for MapPoint 2004.

Before getting into the details of writing Add-Ins, there are two reasons why you need Add-Ins:

Integration

Add-Ins are great if you want to develop applications that work with MapPoint 2004 and are tightly integrated with MapPoint UI. For example, your company may develop a cool data import application that can be used with MapPoint 2004. If you develop this application as an Add-In, you can potentially sell that application to all MapPoint 2004 customers who could use your application directly from inside the MapPoint 2004 UI.

Performance

Add-Ins are a way to improve your application performance because MapPoint 2004 runs in its own process space. When you develop an application that runs in a different process space, the data transfer (and associated type conversion, and/or marshalling) between the process spaces causes some serious performance limitations. Developing your application as an Add-In enables MapPoint 2004 to host your application inside its own process space, dramatically reducing the marshalling requirement across the process boundary.

Developing a MapPoint Add-In is no different from developing an Add-In for any other Microsoft Office application. Visual Studio .NET provides you with a project type to develop Add-In applications. The steps below show how to develop a MapPoint Add-In using Visual Studio .NET.

  1. Create an extensibility project for a shared Add-In using Visual Studio .NET’s New Project dialog, as shown in Figure 4-2.

  2. Select the Shared Add-In option to launch a wizard that allows you to select a programming language, as shown in Figure 4-3.

  3. Upon selecting a language, the next page of the wizard allows you to select the application host for your Add-In. Since you are developing an Add-In for MapPoint 2004, select Microsoft MapPoint, as shown in Figure 4-4.

  4. Once you select the application host, on the next page, enter a name and description for your Add-In (Figure 4-5).

  5. After you give your Add-In a name and description, on the next page, set Add-In options, such as loading the Add-In on host application load and installing the Add-In for all users on a computer, as shown in Figure 4-6.

Creating an Add-In project
Figure 4-2. Creating an Add-In project
Selecting programming language
Figure 4-3. Selecting programming language
Selecting an application host
Figure 4-4. Selecting an application host
Name and description for your Add-In
Figure 4-5. Name and description for your Add-In
Add-In options page
Figure 4-6. Add-In options page

I will leave choosing the installation options to your discretion, but there are a few things to consider about Add-In load options. How do you decide whether you want your Add-In to be loaded at the time of loading the host application? If your Add-In takes a long time to load (because it’s waiting for a resource, for example), do not load the Add-In at the time of the host load. Doing so will slow down the entire application load. When this is an issue, you should load the Add-In explicitly, either manually or programmatically, before using it. If your Add-In initialization process is quick, you can choose to load your Add-In when the host application is loaded.

Once you set the Add-In options, the next wizard page presents the summary of your Add-In. When you click Finish, the wizard creates Add-In and setup projects for your Add-In application.

If you take a close look at the Add-In setup project, it includes the file MPNA82.tlb (or MPEU82.tlb in the European version of MapPoint 2004). This happens to be the type library installed with MapPoint 2004. You must exclude this file from the setup files by right-clicking on it, as shown in Figure 4-7.

Exclude MapPoint type library from setup files
Figure 4-7. Exclude MapPoint type library from setup files

Warning

If you don’t exclude this file, when you install the Add-In, the installer looks for this type library on the target machine, and if it does not exist, the installer copies it onto the target machine. The biggest problem comes when you uninstall the Add-In; the installer removes the type library you haven’t excluded from the target machine, leaving all MapPoint 2004-based applications broken. The only way to fix this issue once that has happened is to install the MapPoint 2004 product again. To avoid this issue altogether, make sure to exclude the MPNA82.tlb from the setup files.

Developing the Add-In

Now we are ready to develop an Add-In. The application logic for your Add-In must go into a Connect.cs (or Connect.vb if you are working with VB.NET) file. Before you start implementing specifics of your Add-In, add the MapPoint type library to your project as a reference. This way, you can use any of the MapPoint 2004 APIs in your Add-In if you want to.

If you take a close look at the Connect class, it implements the IDTExtensibility2 interface requiring you to implement the extensibility event handler methods, such as OnConnection, OnDisconnection, and so on. However, only implement these methods if you really need them, with one exception: the OnConnection method required to wire up the Add-In with the MapPoint 2004 UI. For example, to add a command button to the MapPoint 2004 application UI, add the following code in the OnConnection event handler method:

    public void OnConnection(object application,
                 Extensibility.ext_ConnectMode connectMode,
                 object addInInst, ref System.Array custom)
    {
        MapPoint.Application app
                = application as MapPoint.Application;

if(app == null)
            return;

        //Add the command
        app.AddCommand("Import NorthWind Customers",
                        "ImportNorthWindCustomers", this);
    }

The ApplicationClass.AddCommand method takes the UI display name, the method name, and the type that declares the method name. In the previous code, the AddCommand method takes the UI name of Import NorthWind Customers and the method name ImportNorthWindCustomers that is implemented in the Connect class itself.

If you have the specific initialization code needed for your Add-In, use the OnStartupComplete event handler method to implement it. Using this method does not slow down the host application load time due to the Add-In initialization process.

You can also implement other methods in your Add-In as you would do in any other .NET class. If you need a reference to the host MapPoint.ApplicationClass to perform any MapPoint related tasks, declare a local field in the Connect class that can be assigned to the MapPoint.ApplicationClass instance in the OnConnection event handler method:

    //Declare a local field to hold application
    //class reference
    private object applicationObject;

Assign it to the current MapPoint instance using the OnConnection method:

    public void OnConnection(object application,
            Extensibility.ext_ConnectMode connectMode,
            object addInInst, ref System.Array custom)
     {
         applicationObject = application;
         . . .

Since you have a reference to the host application class (MapPoint 2004 in our case), you can perform any MapPoint 2004-related task in your Add-In using that class instance.

To implement SQL data import Add-In, simply add a method to the Connect class that makes use of the applicationObject reference:

    public void ImportNorthWindCustomers( )
    {
       //Get a reference to the MapPoint Application
       MapPoint.Application app = applicationObject as MapPoint.Application;
       if(app == null)
           return;

       //Get the current map
       MapPoint.Map map = app.ActiveMap;

      //This sample uses System.Data namespace and standard
      //NorthWind sample database that comes with SQL server
      //See whether there is an existing dataset with the same name
      object datasetName = "NorthWind Orders";
      if(map.DataSets.Count > 0 &&
          map.DataSets.get_Item(ref datasetName) != null)
       {
           //If so, delete it
           map.DataSets.get_Item(ref datasetName).Delete( );
       }

       //Now create a new dataset
       MapPoint.DataSet dataset
            = map.DataSets.AddPushpinSet("NorthWind Orders");

       . . .


     }

That’s it! Once this Add-In is built and installed on a machine, you can invoke the method from the MapPoint UI using the command we added earlier using the ApplicationClass.AddCommand method in the OnConnection method.

Sometimes, however, you may want to invoke the previous method from another application, especially when you build Add-Ins to improve the application performance. In that case, you can access an Add-In from the MapPoint.ApplicationClass.AddIns collection.

Invoking Add-In Methods from an External Application

Suppose you are building a Windows application to import data from the SQL Server North Wind database. Since we have already built the appropriate Add-In, simply create a reference to this Add-In and invoke the ImportNorthWindCustomers method.

First, create a new MapPoint.Application instance:

    //Get a reference to the MapPoint Application
    MapPoint.Application app = new MapPoint.ApplicationClass( );

Next, connect to the SQL data import Add-In that we built earlier in this chapter, if you chose not to load the Add-In when the host application is loaded:

    //GUID: "E33E751B-0BA4-49D7-B5C8-ED2A539F9803"
    app.AddIns.Connect("E33E751B-0BA4-49D7-B5C8-ED2A539F9803");

The AddIns collection exposes the Connect method, allowing you to connect to the Add-In that we built earlier. You can either use the GUID or Program ID (also called ProgId) to connect to the Add-In. How do you get this information? When you created the Add-In, the Connect class was created with a GUID and a ProgId:

    [GuidAttribute("E33E751B-0BA4-49D7-B5C8-ED2A539F9803"),
    ProgId("MapPointAddIn.Connect")]
    public class Connect : Object, Extensibility.IDTExtensibility2
    . . .

So, in this case, you can use either the GUID or the ProgId to connect to the Add-In.

Once the Add-In is connected to the application host, get the instance of the Connect class from the Add-In collection:

    //Get the Add-In instance
    object addin = app.AddIns.get_Item(ref index);

Now that you have the Add-In instance, invoke the Add-In method using the .NET Framework reflection APIs, which allow you to access and invoke a type’s methods at runtime dynamically:

    //Get the Add-In type
    Type t = addin.GetType( );

    //Invoke the ImportNorthWindCustomers method
    t.InvokeMember("ImportNorthWindCustomers",
                   System.Reflection.BindingFlags.InvokeMethod,
                   null, addin, null);
    }

The Type.InvokeMember method call invokes the ImportNorthWindCustomers method on the Add-In instance. Now you know how to invoke a method on a MapPoint 2004 Add-In from your application.

Performance Considerations

When I mentioned that you would be using MapPoint 2004 COM interoperable assemblies from your managed code, you may have been worried about performance. There is reason to worry, mainly because the COM interoperable assemblies add a layer of abstraction that necessitates marshalling across the process boundaries and type conversion between the managed and unmanaged types.

The good news is that you can avoid (or at least minimize) these issues by using:

  • Add-Ins to minimize the marshalling across the process boundaries

  • Visual C++ (with or without Managed Extensions) to write your client applications, minimizing type conversions

If you are curious about performance gains, here are my findings: when I ran performance tests for SQL data imported from the North Wind database using the C# import process and using the Add-Ins, the Add-Ins method ran 25% faster than the direct application programming with MapPoint types. That’s considerable performance improvement.

Along the same lines, using Visual C++ (with or without managed extensions) can improve the speed and reduce memory requirements of your application.

Where Are We?

In this chapter, you have learned how to interface with a GPS device using MapPoint 2004 and how to write Add-Ins. We also discussed how to integrate and improve performance using Add-Ins in your applications.

MapPoint 2004 provides a rich set of APIs to develop windows applications that can work as standalone applications or as Add-Ins that can be integrated into the MapPoint 2004 UI. Yet, we have seen only Windows applications (which could be command line executables as well). So, what are our options if you want to develop applications of other kinds, such as applications for the Web or for other platforms, such as the Pocket PC or SmartPhone? If you are looking to use one of these platforms or application types to develop location-based applications, look at MapPoint Web Service in the next chapter.

Get Programming MapPoint in .NET now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.