Using Python to Control MapPoint
This article is based on a previous article published in the MP2K Magazine.
Python is an easy to use but powerful scripting language. Python is ideal for processing data and text. This sounds a bit like Perl, but unlike Perl, I find Python much easier to read and maintain. A wide range of available extensions make Python an ideal tool for scripting MapPoint. Part 1 of this series shows you the basics of controlling MapPoint with Python. Part 2 will use some basic Python text processing to read a file of summary US Census data and plot the filtered results in MapPoint.
Part 1: Basic MapPoint Programming with Python
These examples use Python 2.4. This can be downloaded from http://www.python.org.
I have installed it into the installer's suggested directory of c:\Python24. If you use a different
directory, you will need to modify the following instructions accordingly.
To access COM, I used the Python Windows Extensions ("PythonWin"). These extensions also include a simple IDE (Interactive Development Environment), and some Windows GUI extensions. The PythonWin installer can be downloaded from http://www.python.org/windows/pythonwin. Ensure that you download the version that matches your version of Python. Install PythonWin after Python.
In order to access MapPoint's objects and methods, you need to convert MapPoint's COM Type Information into a form that Python can read. This is performed using the PythonWin make-py Python script. From the command line, this is performed as follows:
C:\> cd "\Program Files\Microsoft MapPoint 2004"
C:\> \Python24\Lib\site-packages\win32com\client\make-py.py mpna82.tlb > \Python24\mpna82.py
This example is for MapPoint 2004 North America. You will need to change the name of the directory and MapPoint's tlb file if you are working with a different version.
The Type Information will now be contained in mpna82.py in the main Python directory.
Start Python. Type the following code in at the command prompt, or enter it into a text file and run it as a program:
# Import the PythonWin win32com components that we require
from win32com.client import constants, Dispatch
# Useful shorthand: Change the object name for Europe,
# MapPoint 2002,etc.
MAPPOINT = 'MapPoint.Application.NA.11'
# Create the MapPoint Application object
myApp = Dispatch(MAPPOINT)
# We can easily call MapPoint methods like this:
myApp.NewMap
# Setting and getting MapPoint properties is just as simple:
myApp.Visible = 1
myMap = myApp.ActiveMap
# Let's do something interesting…
# Create a Location object, and then use it to create a new pushpin
myLoc = myMap.GetLocation(32.0, -96.0)
myMap.AddPushpin( myLoc, "A pin near Corsicana, Texas")
Part 2: An Example: Creating a Map from US Census Data
In Part 1, we used Python to control MapPoint. Python is a scripting language that is ideally suited to text processing. Hence it can be used as a replacement for MapPoint's Data Import Wizard in situations that require some processing intelligence. As an example, we will use it to plot the "250 largest cities in California".
Our source data is the US Census Bureau's 2000 Gazetteer of Places. This file (places2k.txt) and format specification can be found at: http://www.census.gov/geo/www/gazetteer/places2k.html.
The data file is a text file, with one line per place. A place is typically a town or city, although it may also be a CDP (Census Designated Place) - ie. an unincorporated town. Each line follows a fixed column format, and includes information such as location (longitude, latitude), area, and population.
So let's get started with some Python! First we import the required libraries and MapPoint COM information:
# Import the required libraries
import string
from win32com.client import constants, Dispatch
MAPPOINT = 'MapPoint.Application.NA.11'
Python supports object oriented programming. This lets us represent a city's information in the form of a simple object with only a constructor and some data members. This constructor takes a line of text (read from the data file) and creates a city object.
# This is a helper function used by the CityInfo constructor
def removeSuffix( str, suffix):
idx = string.rfind(str,suffix)
if (idx>-1):
return str[0:idx]
else:
return str
# This class stores and handles a city's information
class CityInfo:
def __init__(self, sline):
self.state = sline[0:2]
self.city = string.strip(sline[9:73])
self.population = string.atol(sline[73:82])
self.housing = string.atoi(sline[82:91])
self.land_area = string.atof(sline[119:131])
self.water_area = string.atof(sline[131:143])
self.latitude = string.atof(sline[143:153])
self.longitude = string.atof(sline[153:164])
# trim the extra city/town/CDP/village off the end
self.city = removeSuffix(self.city, " (balance)")
self.city = removeSuffix(self.city, " city")
self.city = removeSuffix(self.city, " CDP")
self.city = removeSuffix(self.city, " town")
self.city = removeSuffix(self.city, " borough")
self.city = removeSuffix(self.city, " village")
self.city = removeSuffix(self.city, " comunidad")
self.city = removeSuffix(self.city, " municipality")
self.city = removeSuffix(self.city, " zona urbana")
Note that Python's tabulation is important. If a statement leads into a block, it ends with a ':' and the members
of the block are tabbed. So in the above code, the class CityInfo has one method, the __init__
method. This is the constructor. self refers to the object being created, and sline is a
string parameter holding the text line that is to be used. Most of the lines should be self-explanatory.
The strip, atoi, and atof functions are a member of the string library. These
are used to extract the string and numeric data from sline, before saving it into the relevant CityInfo
members.
The city name needs a bit more work. Unlike the fixed 2 character State code, city names can have a variable
length and are hence padded out with space characters. The call to strip removes these spaces. The US Census Bureau
also appends a city type label to the end of each city name. Examples include "city", "town", and "village". These
are always lower case and are removed by the removeSuffix() function, which is defined at the top of the
above code snippet. This function simply looks for a particular suffix and removes it if it is present.
We will store the cities in arrays, but we also need to sort them. The array's sort method can take custom comparison functions. We could write some quite complex comparison functions that sort on multiple parameters or calculate parameters to compare, but for now we only need to sort by population:
# comparison function to compare according to population
# Note that this is a reverse comparison: returns greatest first
# used for sorting
def sortByPopulation( a, b):
return cmp( b.population, a.population )
Now we can start with the main code. First, we must read all the city data. These are stored as CityInfo
objects in a dictionary called citiesByState.
######################################
# Main Code
citiesByState = {}
print "Example US Census Program: Reading data..."
# You will probably need to change the following path
cFile = open('c:\\Projects\\census\\places2k.txt', 'r')
city_line = 'empty'
city_line = cFile.readline()
while len(city_line) > 0 :
thisCity = CityInfo( city_line )
if (not citiesByState.has_key(thisCity.state) ):
citiesByState[thisCity.state] = []
citiesByState[thisCity.state].append(thisCity)
# fetch next line (and loop)
city_line = cFile.readline()
cFile.close()
print "US census data has been read"
This code simply loops over each line, reading it in and creating a CityInfo object with it
(referenced by the variable thisCity). These are stored in the citiesByState dictionary.
Python Dictionaries work a bit like STL maps or Visual Basic collections. They store collections of objects indexed
by a key. In this case, we use the State as key, and for each key we store a list of CityInfo objects.
This allows us to quickly find the list of cities for a particular State. Python lists work in a similar way as
dynamic arrays.
After reading all these cities, we can perform a variety of actions - for example extract cities with specific population densities. However, here we simply want the 250 largest cities in California. So first, we get a reference to the list of California cities, and then sort them by population:
print "Sorting the California cities by population..."
myCities = citiesByState[ "CA" ]
myCities.sort(sortByPopulation)
Now that we have the cities sorted in order of population (largest first), we can extract the first 250 and plot them in MapPoint. Calling MapPoint in Python was covered in Part 1 of this article. Here is the city plotting code:
print "Starting MapPoint and creating pushpins..."
# start MapPoint, with a new map
myApp = Dispatch(MAPPOINT)
myApp.NewMap
myApp.Visible = 1
myMap = myApp.ActiveMap
# Loop over each city of these 250 cities, creating a pin for each
for iCity in range(250):
thisCity = myCities[iCity]
thisLoc = myMap.GetLocation( thisCity.latitude, thisCity.longitude)
myPin = myMap.AddPushpin( thisLoc, thisCity.city )
myPin.Symbol = 52
# Tidy Mappoint up, leaving MapPoint open for the user to see
myApp.UserControl = 1
myMap = 0
myApp = 0
print "Finished!"
The Python for loop is more flexible than the Visual Basic for loop, and is capable of iterating over a list of objects. Here we just want it to iterate over the range 1-250 - hence the call to range(250). Each pin is inserted at the city's location with the city's name.
This is what the resulting map looks like:
A similar script was combined with the Single State Mapper to produce the maps used on http://www.mileage-charts.com website.
Return to the Main MapPoint Articles Page.
Further Reading
"Learning Python", Mark Lutz & David Ascher, Publ. O'Reilly.

