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:
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.
Sentence type | Used for | Notes |
Fixed data | Signal validity, location, number of satellites, time, and altitude | This sentence starts with
characters 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 | This sentence starts with
characters 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.
Index | Name | Example/Value | Notes |
0 | Sentence ID | | 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.
Index | Name | Example/Value | Notes |
0 | Sentence ID | | 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:
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.
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.
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.
Create an extensibility project for a shared Add-In using Visual Studio .NET’s New Project dialog, as shown in Figure 4-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.
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.
Once you select the application host, on the next page, enter a name and description for your Add-In (Figure 4-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.
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.
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.