One of the most notable and asked for additions to MapPoint 2010,
is the ability to switch different map features on or off. A dozen or so
different features can have their graphical representations (aka ‘symbols’)
and/or labels switched on (default), off, or to display additional detail. The
ability to switch labels on or off has been frequently requested, but this new
feature goes further and provides a much finer level of control than was
expected.
This new functionality is also implemented in the API using
the MapFeatures property and
collection in the MapPoint Map
object. This is covered in the documentation, although some of the
documentation is incomplete. This article describes how to use this MapFeatures collection, and describes
a couple of pitfalls. Examples are given using C#.
Calling MapPoint 2010 from C#
The example code (see below) is implemented in a series of
functions. These are called with a test harness, implemented as a standard C#
console application. When creating this console project, you must add a
reference to the MapPoint COM object model. MapPoint 2010’s object model is
version 17.0. The reference dialog box will use the name “Microsoft MapPoint 17.0 Object Library (North America)”.
Additional “using” references should also be added to the System.IO and System.Collections .NET libraries. These are only
required for my demonstration code and are not required by MapPoint.
The test harness is below. This will be very familiar to
anyone who has programmed previous versions of MapPoint with C#, and much of it
should be familiar to programmers of other languages. It creates an Application object, displays MapPoint,
gets a reference to the Map
object; runs the test code; then shuts MapPoint down.
static void Main(string[]
args)
{
// A series of development tests for the MapFeature
and MapFeatures objects in MapPoint 2010
// Project must reference Microsoft MapPoint COM
Object Model 17.0 or later
MapPoint.Application myApp = new MapPoint.Application();
myApp.Visible = true;
MapPoint.Map myMap = myApp.ActiveMap;
// Get the MapFeatures collection
MapPoint.MapFeatures myFeatureList =
myMap.MapFeatures;
// Some sample routines
Console.WriteLine("Available
Map Features:");
DisplayMapFeatures(myFeatureList);
Console.WriteLine("\nSorted
list of Map Features:");
DisplaySortedMapFeatures(myFeatureList);
Console.WriteLine("\nModify
some features...");
ModifyMapFeatures(myFeatureList);
Console.WriteLine("...Features
modified.");
// Standard shutdown (but only when the user
says it is okay)
Console.Write("Press
ENTER to shutdown");
Console.ReadLine();
try
{ // (user might have closed things - therefore
trap and ignore any errors
// this is a simple demo, after all. A commercial
App should handle things better)
myMap.Saved = true;
myMap = null;
(myApp as MapPoint._Application).Quit();
myApp = null;
}
catch
{
}
}
The only line of code which is new for MapPoint 2010, is the
MapPoint.MapFeatures line. This
obtains a reference to the MapFeatures
collection using the Map
object’s new MapFeatures
property. This reference is passed to each individual test routine.
Accessing the MapFeatures Collection
Most if not all of MapPoint’s collections can be accessed
using C#’s foreach statement.
Our first test routine demonstrates that the same applies to the MapFeatures collection:
// Display Map Features: Demonstrates using foreach
to iterate through a MapFeature collection
static public void DisplayMapFeatures(MapPoint.MapFeatures myFeatureList)
{
// Loop through all MapFeature objects
foreach (MapPoint.MapFeature
thisFeature in myFeatureList)
{
Console.WriteLine("Index:{0},
Feature:\"{1}\"", thisFeature.Index, thisFeature.Name);
// This approach could also be used to set all
features to the same setting, eg:
// (Options are: geoNeverDisplay, geoDefault,
geoMoreDetail)
// thisFeature.DetailLevel =
MapPoint.GeoMapDetail.geoNeverDisplay;
}
}
All this routine does, is to iterate through the collection
querying every MapFeature
object. Each map feature’s index and name is displayed. The resulting list is
reproduced at the end of this article, for reference.
The foreach
construct can also be used to set all map features to the same setting – eg.
All on, all off, or extra detail for everything. See the commented code for a
sample of how to do this using the DetailedLevel
property. This property uses a new enumeration called MapPoint.GeoMapDetail. Use GeoMapDetail.geoNeverDisplay
to switch the feature off. Use GeoMapDetail.geoDefault
to switch the feature on at the default level. Use GeoMapDetail.geoMoreDetail if you wish to display
additional detail. The final example in this article uses this last option to
show extra river detail.
The DetailedLevel
property could also be used to query feature visibilities (e.g. to serialize
all map feature visibilities).
Obtaining an Alphabetical List of Map Feature Names
The following example is very similar to the above example,
except the map feature names are displayed in alphabetical order:
// A useful alphabetical list
static public void DisplaySortedMapFeatures(MapPoint.MapFeatures myFeatureList)
{
// It is actually quicker to collect the names and
then sort, rather than to use
// a Dictionary to maintain a sorted list.
ArrayList alFeatureNames = new ArrayList();
// Iterate through the feature list, adding each name
foreach (MapPoint.MapFeature
thisFeature in myFeatureList)
{
alFeatureNames.Add(thisFeature.Name);
}
alFeatureNames.Sort();
// Display the sorted list
foreach (string
sFeature in alFeatureNames)
{
Console.WriteLine(sFeature);
}
}
Rather than using a .NET Dictionary
object which keeps its contents sorted on the fly, an ArrayList is used instead. This is only sorted once after
all the names have been added. This approach can be considerably faster for a
simple “load/read” sequence like this.
Accessing Individual MapFeature Objects
The MapFeatures
collection has an Item method
which can be used to access individual MapFeature
objects. As with other MapPoint collections, this appears in C# as the get_Item() method and expects an
object reference as the parameter. This allows the calling application to refer
to an object in the collection by its index or by its name. The downside is
that it complicates the interface in C#. This is because the index (an integer)
or the name (a string) has to be boxed as an object, and a reference has to be
passed. This typically requires at least one extra line of code, so I have
implemented two new functions which automate the process. These take an integer
or a string, and return a reference to the resulting MapFeature object:
// Note in C#, the item reference for all collections
is with a method called "get_Item"
// Also, the parameter is an object (Variant) because
it can be a string or an integer
// Here we simplify the code by creating an integer
index implementation
static public
MapPoint.MapFeature GetMapFeatureByIndex(MapPoint.MapFeatures myFeatures, int
idx)
{
object oIdx;
oIdx = idx as object;
return myFeatures.get_Item( ref oIdx);
}
// Similar to above, but using a string name
implementation
static public
MapPoint.MapFeature
GetMapFeatureByName(MapPoint.MapFeatures
myFeatures, string sName)
{
object oName;
oName = sName as object;
return myFeatures.get_Item(ref oName);
}
GetMapFeatureByIndex
works as expected. Unfortunately, GetMapFeatureByName
does not work if you pass the name (eg. "Boundaries - Country -
Symbols") of the required MapFeature
object. An ‘index not found’ exception is thrown. This method can be made to
work, but only if you pass the required index as a string, eg. “8” instead of "Boundaries
- Country - Symbols"! I observe that the string implementation is of
little use for this collection, and developers should use the integer index
instead.
The integer index might pose problems in the future. There
is nothing in the documentation to say that these numbers will remain the same.
A conscientious developer should write code that checks the MapFeature names are what the
developer expects. The alternative is code that might break or behave in an
unexpected manner when a new version of MapPoint is released with new MapFeatures or different index codes.
Here is some example code which uses GetMapFeatureByIndex (and to a lesser extent GetMapFeatureByName) to change a
number of feature visibility settings:
// Modify some of the feature settings
static public void ModifyMapFeatures(MapPoint.MapFeatures myFeatureList)
{
// Switch roads and railroads off, using their
indices:
GetMapFeatureByIndex(myFeatureList, 11).DetailLevel = MapPoint.GeoMapDetail.geoNeverDisplay;
GetMapFeatureByIndex(myFeatureList, 12).DetailLevel = MapPoint.GeoMapDetail.geoNeverDisplay;
GetMapFeatureByIndex(myFeatureList, 13).DetailLevel = MapPoint.GeoMapDetail.geoNeverDisplay;
GetMapFeatureByIndex(myFeatureList, 26).DetailLevel = MapPoint.GeoMapDetail.geoNeverDisplay;
GetMapFeatureByIndex(myFeatureList, 27).DetailLevel = MapPoint.GeoMapDetail.geoNeverDisplay;
GetMapFeatureByIndex(myFeatureList, 32).DetailLevel = MapPoint.GeoMapDetail.geoNeverDisplay;
GetMapFeatureByIndex(myFeatureList, 33).DetailLevel = MapPoint.GeoMapDetail.geoNeverDisplay;
// Also switch small cities and their names off
GetMapFeatureByIndex(myFeatureList, 4).DetailLevel = MapPoint.GeoMapDetail.geoNeverDisplay;
GetMapFeatureByIndex(myFeatureList, 5).DetailLevel = MapPoint.GeoMapDetail.geoNeverDisplay;
GetMapFeatureByIndex(myFeatureList, 19).DetailLevel = MapPoint.GeoMapDetail.geoNeverDisplay;
GetMapFeatureByIndex(myFeatureList, 20).DetailLevel = MapPoint.GeoMapDetail.geoNeverDisplay;
// The following do NOT work
// Note: Many MapPoint collections can take names
like this in their Item methods
// GetMapFeatureByName(myFeatureList,
"Miscellaneous - Water Features - Labels").DetailLevel =
MapPoint.GeoMapDetail.geoMoreDetail;
// GetMapFeatureByName(myFeatureList,
"Miscellaneous - Water Features - Symbols").DetailLevel =
MapPoint.GeoMapDetail.geoMoreDetail;
// Instead, if we pass a string, it must be of the
index number, like this:
GetMapFeatureByName(myFeatureList, "1").DetailLevel
= MapPoint.GeoMapDetail.geoMoreDetail;
GetMapFeatureByName(myFeatureList, "16").DetailLevel
= MapPoint.GeoMapDetail.geoMoreDetail;
// Switch some parks on
GetMapFeatureByIndex(myFeatureList, 2).DetailLevel = MapPoint.GeoMapDetail.geoMoreDetail;
GetMapFeatureByIndex(myFeatureList, 17).DetailLevel = MapPoint.GeoMapDetail.geoMoreDetail;
}
This code switches all roads, railroads, and smaller cities
off. Rivers and parks are switched on with “additional detail”. This produces
the display below. As can be seen, “additional detail” adds a number of
additional park icons, but it also adds a lot of additional smaller rivers and
lakes.
Note that the above methods return references which could
also be used to query the visibility of individual map features.

Conclusions
And that is it! The new map feature functionality implements
a much-requested feature, and includes more capabilities than anyone was
expecting. It should greatly enhance MapPoint’s utility in producing
presentation maps.
The programming interface has a few quirks. The
documentation could be more complete, and the get_Item()
function could implement names. Or better still, it would be great if MapFeature objects could be queried
using an enumeration. A correctly implemented enumeration should be immune to
any future changes or additions to the available map features.
A complete list of the Map Feature Names and Indices is posted on Mapping-Tools.com here:
http://www.mapping-tools.com/info/pushpins/features_2010.shtml