MapBox

Designing DC Nightvision

A MapBox theme design walkthrough

DC Nightvision

This page will walk you through the creation of the DC Nightvision MapBox theme. All of the source stylesheets and shapefiles are available to download so you can follow along and experiment with changes.

Download the files

To simplify things, we've packaged everything you need into a single zip file. Download and extract it anywhere on your computer.

The XML File

First, we'll create the structure of our map in a Cascadenik XML file. It's called 'dc-nightvision.mml' in the source package. Here's what it looks like to start off, without any layers:

<?xml version="1.0" encoding="utf-8"?>

<!DOCTYPE Map[
  <!ENTITY srs900913 "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs">
  <!ENTITY srsWGS84 "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs">
  <!ENTITY srsDC "+proj=lcc +lat_1=38.3 +lat_2=39.45 +lat_0=37.66666666666666 +lon_0=-77 +x_0=400000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs">
]>

<Map srs="&srs900913;">

</Map>

The first line defines the file as an XML file. After that we've defined a few entities for different projections to keep the layer definitions a bit cleaner. (See Mapnik & Cascadenik Tips for more on XML entities.) Note that in addition to the common 900913 and WGS84 projections, we also have projection specifically for the District of Columbia. This is how most of the GIS data we used in this map is projected.

The srs defined in the tag is how the final map will be projected. All of the TileSets on MapBox.com, as well as those on OpenStreetMap and Google Maps use 900913 (Google) projection.

Adding Layers

Between the <Map> and </Map> tags, we can begin adding layers for each shapefile. You can use QauntumGIS to help determine the proper order of the layers. From the order they are entered in the XML, layers are rendered bottom-to-top. So the last layer in the file will appear on top.

Also note that you can assign multiple classes to the same layer. This is useful when you have similar layers that will share some (but not all) style definitions. You can also assign a unique id to a layer if you want to easily select just that one.

  <Layer class="boundary fill" srs="&srsDC;">
    <Datasource>
      <Parameter name="file">./DCBndyPly/DCGIS_DCBndyPly</Parameter>
      <Parameter name="type">shape</Parameter>
    </Datasource>
  </Layer>
 
  <Layer class="park" srs="&srsDC;">
    <Datasource>
      <Parameter name="file">./ParkPly/ParkPly</Parameter>
      <Parameter name="type">shape</Parameter>
    </Datasource>
  </Layer>
 
  <Layer class="water major" srs="&srsDC;">
    <Datasource>
      <Parameter name="file">./WaterPly/WaterPly</Parameter>
      <Parameter name="type">shape</Parameter>
    </Datasource>
  </Layer>

...and so on. See 'dc-nightvision.mml' for the full layer layout.

To achieve particular visual results and to ensure the proper ordering of certain elements, some layers are added twice. Different classes are used to distinguish them, for example major/minor, fill/outline.

In this map there are duplicate water layers - one for large bodies (such as the Potomac River) that other objects/areas should go on top of, and one for small bodies (such as fountains) that should go on top of other objects/areas.

There are also duplicate road layers - one for the fill color and one for the outlines. We can't define outline and fill on the same layer, otherwise some outlines will appear on top of the roads.

The Stylesheet

With the layers in place, we can start working on a stylesheet. First, put a stylesheet definition in the XML file, right below the tag:

  <Stylesheet src="dc-nightvision.mss" />

Then create a file named "dc-nightvision.mss" (or find the one included in the source package) and open it in your text editor. We'll start by defining a background color and the roads.

Map {
  map-bgcolor: #284036;
}

.road.fill {
  polygon-fill: #000;
}

Selective styling

If you preview this map right now (or open up the RoadPly shapefile in QuantumGIS), you will notice that it contains a lot of things that are not strictly roads, such as parking lots, traffic islands, private driveways, and alleys. These are all rather different things, so it makes sense to style them differently.

You can select a subset of elements using one or more attribute selectors after the class, in the format "[attribute=value]". The code to apply different styles to different types of roads in the same shapefile looks like this:

.road.fill[DESCRIPTIO="Intersection"],
.road.outline[DESCRIPTIO="Road"] {
  polygon-fill: #000;
}

.road.fill[DESCRIPTIO="Alley"] {
  polygon-fill: #161616;
}

.road.fill[DESCRIPTIO="Parking Lot"],
.road.fill[DESCRIPTIO="Paved Drive"] {
  polygon-fill: #222222;
}

And the result:

RoadPly.shp

So how do you know which attributes and values are within the shapefile for you to use? Either open the shapefile in QuantumGIS and select Layer -> Show Attribute Table, or open up the .dbf with a spreadsheet program. The column headers are the possible attributes and the the column contents are the possible values.

QuantumGIS Attribute Table

Zoom-dependent styling

It's extremely useful to be able to change the style of elements based on the current zoom level. Cascadenik makes this easy, with a special kind of attribute selector:

.class[zoom<5] {
  /* style will be applied at zoom levels 0 through 4, inclusive */
}

.class[zoom=5] {
  /* style will be applied only at zoom level 5 */
}

.class[zoom>=6][zoom<=10] {
  /* style will be applied at zoom levels 5 through 10, inclusive */
}

.class[zoom>10] {
  /* style will be applied at zoom levels 11 and higher */
}

Labels

You can use any of the attributes within the shapefile metadata to place labels on object. In the DC Nightvision map, we labeled many of the roads.

To add a label, you need to add the title of the column you want to pull the label from after the class selector. Here, we have more major roads appearing at lower zoom levels

.road.label[zoom>=15][TYPE='highway'] NAME,
.road.label[zoom>=15][TYPE='motorway'] NAME,
.road.label[zoom>=16][TYPE='primary'] NAME,
.road.label[zoom>=16][TYPE='secondary'] NAME,
.road.label[zoom>=16][TYPE='trunk'] NAME,
.road.label[zoom>=17][TYPE='residential'] NAME {
  text-face-name: "Liberation Sans Regular";
  text-fill: #fff;
  text-halo-fill: #000;
  text-halo-radius: 1;
  text-max-char-angle-delta: 20;
  text-min-distance: 200;
  text-placement: line;
  text-size: 9;
  text-spacing: 400;
}

Icons for points of interest

On the DC Nightvision map, we included icons for all of the city's metro stations. (The image files for these are located in the 'res' subdirectory.) To place an icon on a map, you need a shapefile of points - in this case it is the 'MetroStnPt' shapefile. The Cascadenik code that adds different sized icons at different zoom levels looks like this:

.metro[zoom=14] { point-file: url(./res/metro-9px-trans.png); }

.metro[zoom=15] { point-file: url(./res/metro-9px.png); }

.metro[zoom=16] { point-file: url(./res/metro-11px.png); }

.metro[zoom>=17] { point-file: url(./res/metro-14px.png); }

Notes on the design process.

Designing a map entirely within a text editor is not exacltly practical, and a couple of graphical tools were important in the design process of this and other MapBox maps.

First, QuantumGIS is excellent for exploring shapefiles & PostGIS databases and for testing and color schemes. Second, Inkscape helps to quickly design & adjust color schemes. We also use Inkscape to create custom icons for the maps.

Further Reading

Read though the stylesheet and play around with it to learn more about what can be done with Cascadenik - this walkthrough does not cover all of the details.

You can also visit the Mapnik Utils wiki, which has a full list of properties and values accepted by Cascadenik.

Once your style is looking the way you want, you can package it up for TileMill and submitted to render.