Interesting Tech Projects

# Map Scales and Printing with Mapnik

Mapnik is a nice open source library for generating maps. The typical data source is OpenStreetMap style data stored in a PostgreSQL/PostGIS database. This post examines how to understand and control the map scale and generate maps suitable for printing.

**Map Scales **

A map scale looks something like 1:1000. This means that for every 1 inch on the map there are 1000 inches in the real world. The units don’t matter, for example it also means that 1 meter on the map is 1000 meters in the real world. The value 1000 shown in this example is called the Scale Denominator.

Maps (in the context of this discussion) are generated using pixels. This is true even if printing because ultimately the printer has to print the pixels onto the paper (assuming a raster output). At low resolutions the pixels will be easily seen. Not so at high resolutions.

It is therefore useful for us to know the size of a pixel in meters. If we know this then we can work out the map scale and set the map scale.

**Universal Transverse Mercator**

The world is round and not flat (no comments on this please!) and therefore we need a way of converting a section of it onto a flat piece of paper. The method of converting a curved section of the world to a flat representation is called the projection. There are many different projections to choose from and software available to convert data using the different projections. Universal Transverse Mercator (UTM) is a useful projection because it’s units are in meters. Typical map data uses latitude and longitude in degrees, which are difficult to use in calculations. However meters are much easier to work with.

The first step therefore is to get the bounding box of your map in degrees and convert it to meters in UTM. Mapnik can also do this for you using the projection class.

A bounding box is defined by the two coordinates that locate two opposite corners of the rectangle which defines the area in the map. The UTM zone is not needed, and we will assume the map area fits within a single UTM zone.

**Maps For Screens**

We assume that a pixel on a screen is 0.28mm on each side. This may or may not be true in reality, but it’s called the “standardized rendering pixel size” and is defined in the OpenGIS Styled Layer Descriptor (SLD) Implementation Specification. This assumption is made because software typically doesn’t know the pixel size on a screen and it’s a reasonable assumption. If the actual pixel size is known then it should be used. 0.28mm = 0.00028 m

We know the width of the area we wish to draw in meters from the UTM bounding box. We will call this map_realwidth_m. We also know the width of the map in pixels, because we choose that based on our requirements for the map. For example if the map is to go on a web page then the maximum width is probably something like 800px. We will call that image_width_px. Therefore:

scale_denominator = map_realwidth_m / (image_width_px x 0.00028)

The scale denominator is simply the ratio of the real world area width to the map width.

To summarize, for a bounding box in UTM coordinates and a given map width in pixels you can now calculate the scale of the map. However by rearranging the formula a UTM bounding box width can be calculated from a specific map scale and image width in pixels. By choosing a coordinate for the center of the map or one corner the UTM bounding box can be fixed.

Here is an example. The width of the UTM bounding box is 1600 meters. The image width is 800px. Therefore:

scale_denominator = 1600 / (800 x 0.00028) = 7143.

Map scale is 1:7143.

For a map scale of 1:2000 and an image width of 800px, the UTM bounding box width will be:

map_realwidth_m = 2000 x (800 x 0.00028) = 448 meters

**Maps For Printing**

Generating maps for printing is the same as for a screen except the resolution is different. For example a pixel size of 0.00028m corresponds to a resolution of approximately 90.7 Pixels Per Inch (PPI). This is a relatively low resolution and will likely appear quite poor when printed. Printing has a much better resolution than a screen. For example printing an 8″ x 10″ map might need 300 PPI to get enough detail. Large posters might be printed at 150 PPI. Note that sometimes PPI is referred to as Dots Per Inch (DPI) although DPI is specifically related to printing whereas PPI is more generic.

The first step in creating a map for printing is to determine the PPI to use. This is defined by the size of the print and the printer. For the examples that follow we will use 300 PPI, but any value can be used.

Next we need to work out the pixel size for the resolution.

300 PPI = 1 / 300 inches per pixel. 1/300 x 25.4mm/inch = 0.0846mm = 0.000085 meters

Now we need to work out the width of the map in pixels. For 300 PPI and a width of 7.5 inches we get:

image_width_px = 300 x 7.5 = 2250px

We can now put these values into the map scale formula to calculate the scale from the UTM bounding box, or calculate the UTM bounding box width from the scale. For a UTM bounding box that is 1600 meters wide:

scale_denominator = 1600 / (2250 x 0.000085) = 8366

Map scale is 1:8366

**XML Stylesheet**

The Mapnik XML stylesheet tells Mapnik what to draw for different map scales. The rules are enabled by specifying a maximum and minimum scale denominator.

When creating maps for printing it is important to remember that internally Mapnik is assuming the map is for the screen and therefore has a different scale denominator. To get the scale denominator which Mapnik thinks is being used:

# zoom to bounding box then…

internal_scale_denom = mymap.scale_denominator()

Use this value when constructing the rules for the XML stylesheet.

**Notes**

If you tell Mapnik to render a specific UTM bounding box to an image of a specific dimensions then it will expand the bounding box in one direction so it has the same proportions as the image to be created. This can change the width of the UTM bounding box and therefore the scale calculation. The solution is to base any scale calculations off the actual bounding box. Mapnik can give you the values by calling the envelope() function on the map object.

The scale is an approximation and might only be valid for the center of the map. The larger the area represented in the map the more distortion there will be and therefore less accuracy when using the scale.

OpenStreetMap uses zoom levels numbered one to 18. This file shows the relationship between the zoom levels and the scale denominators.

Print article | This entry was posted by Andy on September 22, 2009 at 4:36 pm, and is filed under Mapping. Follow any responses to this post through RSS 2.0. You can leave a response or trackback from your own site. |

BalRamabout 9 years ago

Hi Andrew,

May be you remembered me. You had replied my query on mapnik forum.

Thanks for it again.

Actually, I have gone throgh your way of zoom_to_box() and called zoom() function. Now its working fine.

Now, I am facing problem in measure scale in terms of kilometer or meter with current zoom level.

i.e. if I am at specific zoom level then what is scale of my map in term of kilometer; so that I can show scale factor in my map.

Getting more specific, I have to speficy zoom() in range of values 2km to 16000km and map to mapnik zoom() function which takes value in 0.1 to 1.0.

Could you please help me out of this?

Please give me some fruitful information.

Thanks!!

BalRam

BalRamabout 9 years ago

I am unable to convert latlong values into UTM.

Please do provide me code snippet for it too.

Thanks !!!

about 9 years ago

Once you have zoomed your map call:

`MyMap.scale_denominator()`

to get the current scale denominator. This will assume a 0.00028m pixel size, so you will have to scale it to your PPI value for printable maps. Then you can calculate how far one mile and one kilometer is for your map.

I’m not sure of the relationship between zoom factor and scale in Mapnik, sorry. Try asking on their mailing list.

BalRamabout 9 years ago

Hi Andrew,

I will try it out.

Thanks for your help !!!

BalRam

Markabout 9 years ago

In the notes section of this tutorial you mention to get the actual bounding box of the image you need to call the envelope() function on the map object. Using the mapnik python script generate_image.py could you explain how you call the envelope() function on the map object once you’ve used zoom-to-box(). Thank you,

Mark