Follow us on Twitter

Add City Coverage to MapPoint using the GeoNames Database

Share:

Users of Microsoft MapPoint often request the ability to import their own data — especially for regions not covered by the North American and European editions. It is not possible to modify MapPoint’s road database or dataset structure, but it is possible to add annotation in the form of pushpins and shapes. Here I demonstrate how to write a Python script to add pushpins for cities in countries with limited MapPoint coverage.  A frequent problem is that of finding a suitable source of data. I shall use the open source global GeoNames database of places.

GeoNames

The GeoNames database is available at http://www.geonames.org. The database contains over eight million place names, covers all of the world’s countries, and is distributed under the Creative Commons Attribution 3.0 License. The complete database or small extracts can be downloaded as text files, and it can also be accessed using a series of web services. Premium services are available for those who wish to provide financial support, or require professional-grade availability and a service level agreement.

Extracts include: countries, largest cities, highest mountains, capitals, and post codes. We shall use the “all cities with a population > 1000” subset. This can be found in the file cities1000.zip and can be downloaded from the Download Server at http://download.geonames.org/export/dump/

This file unzips to give the text file cities1000.txt . This is a simple tab-separated table database that uses the standard GeoNames table format. This is described at the above URL. Information available includes place name, country, feature type (town, mountain, etc), longitude, latitude, population, administration level (four levels available), elevation, time zone, and aliases.

Country codes are specified with ISO 3166 two-character codes. These are listed in their own GeoNames database, and at http://www.iso.org/iso/country_codes. Feature types are detailed in the GeoNames documentation.

Python

Python is ideal as a scripting language to read and interpret this data due to its text manipulation and list handling abilities.

I have covered the use of MapPoint and Python before and I will assume that you are familiar with the basics. I shall use Python 3, and the PythonWin extensions (see: http://www.python.org/download/windows/ ) for COM support.

The Code

For this example, we shall display pushpins for all cities in South Africa with a population above 1000. The pins will be sized according to population. The pushpin ‘balloons’ will also include population and synonym (alias) information for each city. The code will be written so that it can work with multiple countries if desired.

The pushpins are a series of colored circle bitmaps kindly provided by Eric Frost. These are labeled SA1.bmp (small, yellow) through SA8.bmp (large, red).

Let’s get started!  First we import the required libraries and define the name of MapPoint classes we wish to use (North America, default):

Next we define the countries we wish to import. We do this here, so it is easy to change. We shall only import South Africa (ISO code ‘ZA’) for this example, but multiple countries can be used (cf. the commented example). We define these as a set to enable quick look ups of the form “Is this city in one of the requested countries?”

We will define a CityInfo class to store each city’s information. This is a simple class definition, consisting of a number of data members, and a constructor. The constructor creates a new CityInfo object by parsing a single line of the GeoNames database. The class contains a ‘valid’ flag. This is set if the place is a city with a population of 1000. This is not strictly true for all of the places in the database (e.g. Grytviken in South Georgia).

Here is the definition:

That is all that is required to parse the GeoNames data. We just need to feed it each line, and to process the results.

We have now defined everything, so we can now start with the main code. First we initialize a few variables, log some information to the user, and open the cities1000.txt file:

listCities is simply a list of all the cities we wish to write to MapPoint. We could write them to MapPoint as we read them, but storing them into memory first allows us to calculate statistics or perform other operations if we wished to.

Next we read each line from cFile and parse a new CityInfo object from it. We then store it into listCities if it is valid and is in one of the requested countries:

All of the city data has been read in, so now it is time to start MapPoint:

Next we need to load our custom pushpin symbols. The MapPoint Symbols.Add() method returns the index of the new symbol that is created. We store these in a new list, myPinList. We have a total of eight pushpins, but only use four of them. These are allocated in a log-10 manner, eg. The first pin is used for all cities with a population from 1,000 to 9,999. The final pushpin is used for all cities with a population above 1,000,000. For some countries, this includes cities above 10,000,000. Here is the code that loads the pushpins and stores their indexes:

After creating the pushpins, we create a series of corresponding pushpin sets. Doing this allows us to create a meaningful key. Also, by creating the pushpin sets in order from low to high, we automatically arrange for the higher population cities (=later pushpin sets) to be positioned over the lower population cities (=earlier pushpin sets). The pushpin sets are created using the DataSets.AddPushpinSet() method, and are stored in their own list, myPushpinSets. Finally the symbols are allocated to the pushpin sets using a simple loop:

We are finally ready to start drawing the pushpins. All we need to do is to loop over the cities, creating a pushpin for each city. The pin is chosen using a log10 formula arranged so that a population of 1,000-9,999 receives a pin index of 0; a population of 10,000-99,999 receives a pin index of 1; etc. The pin index is then used to choose both the pin symbol and the pushpin set for the pushpin.

The balloon text is then created from the city’s population, and the list of synonyms (if any). All balloons are set to “hide”, but the pushpin names  are displayed for the largest cities with the largest pushpin symbol.

Here’s the code:

There we have it. All we need to do is to zoom to the imported data, and to tidy up:

Here are the results with the balloon open for Port Saint John’s to demonstrate the synonym information:

Sample map of GeoNames cities plotted for South Africa

Sample map of GeoNames cities plotted for South Africa

Due to the scripting nature of Python and some of the design of the CityInfo class, the above code is easily modified to produce different plots, calculate various statistics, or apply other pre-processing to the data. By choosing different GeoNames datasets, it is possible to plot smaller cities or other types of places.

The above Python script, icons, and a snapshot of the Geonames database can be downloaded here.

Leave a Reply

 

 

 

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">