Documentation

The MapBox documentation covers a variety of topics related to custom mapping tools.

The TileMill documentation contains information about generating custom map tiles in the cloud. There are instructions designing tiles and preparing jobs for TileMill.

There is a guide about adding the tiles to your site. It includes documentation for specific platforms like Drupal and Managing News, as well as steps to embed on any website with the Google Maps API.

TileMill

TileMill is a tile rendering engine designed to create map tiles by leveraging existing open source GIS tools and the power of Amazon's cloud.

This guide will help you design, prepare, and render custom map tiles with almost any geographic dataset.

Welcome to TileMill - Read Me First

We're excited that you decided to check out TileMill and the MapBox tools. At this stage, TileMill is a very early developer release, so you'll likely need to get down and dirty with code in order to make things work.

Specifically, it's difficult to diagnose rendering issues because the servers are unable to email or otherwise communicate outside of the cloud. It's also a little tricky to see when things are going well, besides simply checking to see whether the files are, indeed, present.

We've got great plans for TileMill 2.0 which will address these concerns and lower the bar for users so that people who feel comfortable designing websites can get up and rolling in minutes. If you're interested in beta testing, let us know.

Installation & Configuration

The TileMill Tools allows you start, stop, and submit jobs to tile rendering instances.

Preparing your AWS account

To use TileMill, you'll need an AWS account with the following services activated:

  • EC2
  • S3
  • SQS

Dependencies

The TileMill client tools require Python. On Mac OSX and Linux computers, Python is installed by default, but on Windows computers it will need to be installed. ActivePython is free and recommended for Windows users.

Mac OS X & Linux

  1. Download the TileMill tools source package
  2. Open a terminal and find the downloaded files
  3. Run sudo python setup.py install in tilemill
  4. Run sudo python setup.py install in tilemill/boto

Windows

  1. Download the TileMill tools Windows distribution
  2. Unzip the file and run tilemill.exe and then boto.exe

Configure the tools

Installation creates two configuration files called .boto and .tilemill. On Mac and Linux, these files are located in your home directory. On Windows systems, the files are located in the following directory:

C:\Documents and Settings\<your user name>\
  1. Open the .boto file in your text editor and specify your AWS credentials.
  2. Create two buckets on your S3 account. One will be used to store input files and the other will hold the outputted tiles.
  3. Open the .tilemill file and specify the names of the input and output buckets that you picked in the previous step. Additionally, specify names for the input and output SQS queues. These will be created automatically.

You're tools are now configured and you're ready to move on!

Creating a Mapnik XML file

The main file TileMill needs to start a rendering job is a Mapnik XML map definition. While it's possible to write these files directly, for even slightly complex maps it is much easier to generate one with one of a couple tools.

Cascadenik

Home Page | Support

Cascadenik is a code pre-processor that allows you to design a map using a language that is very similar to Cascading Style Sheets (CSS). A Cascadenik .mml file is like a Mapnik XML file with some useful features added:

  • you can define styles in a CSS-like way, either in the same document or referenced to an external .mss file
  • you can assign classes and IDs to your layers, and use them to select various layers in your style definitions

The stylesheet language itself offers many of the advantages of CSS, like inheritence and a variety of ways to select elements.

You will need to download and set up Mapnik and Cascadenik on your computer to generate Mapnik files this way and preview your map designs before you upload them. Information about setting up & using these tools be found on the Mapnik wiki and the Mapnik-Utils project page.

Example styles

Check out Development Seed's MapBox repository on GitHub for example Cascadenik styles which you are free to download, edit, and redistribute (see the LICENSE.txt for details).

Quantumnik (QuantumGIS plugin)

Home Page | Support

Quantumnik is a plugin that allows you to design and export Mapnik maps using QuantumGIS, a cross-platform desktop GIS application. With Quantumnik, you can quickly preview what maps will look like and then generate MapBox-compatible XML styles.

Why Cascadenik? Why Quantumnik?

If you aren't comfortable editing CSS, then Quantumnik is your best option: you can edit map styles without touching any code, and there's very little challenge to setting up your map-editing environment.

However, there are some limitations to Quantumnik which make Cascadenik the best choice for advanced users: Quantumnik does not allow zoom-dependent styles yet, and these are quite essential for many maps. Zooming in on a map will usually reveal gradually more data; the view of the entire world in OpenStreetMap doesn't show every road, and roads tend to become larger with higher zoom levels.

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.

Mapnik & Cascadenik Tips

Some differences between Cascadenik and CSS

While Cascadenik works in a similar way to CSS, there are some differences. Here is a short list of things that might catch you:

  • Colors can only be specified as #RGB or #RRGGBB - rgb(RRR,GGG,BBB) values won't work (even though Mapnik supports them)
  • Every attribute requires a semicolon at the end, even the last one.

Overlapping dotted lines

Borders in many data sets are defined on both sides, an thus are actually two lines overlapping each other. If you want to make these lines dashed, the dashes may not line up exactly and appear longer than you wish, or perhaps even create a solid line. For example, all of these lines are meant to have the same style:

Mapnik Dash Overlap

To work around this in Cascadenik, you can use an outline with a width of 0 that is the same color as the map background to block out whichever line is underneath. The code would look like this:

.class {
  line-color: #000;
  line-dasharray: 3, 4;
  line-width: 1;
  /* same as map background color: */
  outline-color: #fff;
  outline-width: 0;
}

The caveat to this method is that it will not work well if you are rendering a transparent background, or a background which has a wide range of colors.

Simplify your code with XML entities

A lot of the information you need to enter into a Cascadenik or Mapnik XML file will be repetative. You can save some time and keystrokes by defining internal XML entities at the beginning of your document.

A Cascadenik file using entities looks something like this:

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

<!DOCTYPE Map[
  <!-- projections -->
  <!ENTITY srs_osm "+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 srs_wgs84 "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs">
]>

<Map srs="&srs_osm;">

  <Stylesheet src="your-stylesheet.mss" />
 
  <Layer class="one-or-more class" srs="&srs_wgs84;">
    <Datasource>
      <Parameter name="file">/path/to/shapefile</Parameter>
      <Parameter name="type">shape</Parameter>
    </Datasource>
  </Layer>

</Map>

Entities are defined before the tag, but after the tag. In the above example there are two entities defined, named "srs_osm" and "srs_wgs84". The values for these entities are the projection strings I would normally have to write into the Map tag and every Layer tag. With entities defined, all you have to type is "&entity_name;", and it will be interpreted as if the full string had been typed out.

Another great use of entities is be to store PostGIS server information. If the server's URL or login credentials ever change, the information only has to be edited in one place.

Example PostGIS entity definitions:

<!DOCTYPE Map[
  <!-- database settings -->
  <!ENTITY host "gis.example.com">
  <!ENTITY port "5433">
  <!ENTITY user "username">
  <!ENTITY password "password">
  <!ENTITY dbname "database_name">
]>

With those entities defined, the Datasource entry in your PostGIS layers would look like this:

<Datasource>
  <Parameter name="type">postgis</Parameter>
  <Parameter name="host">&host;</Parameter>
  <Parameter name="port">&port;</Parameter>
  <Parameter name="user">&user;</Parameter>
  <Parameter name="password">&password;</Parameter>
  <Parameter name="dbname">&dbname;</Parameter>
  <Parameter name="table">table_name</Parameter>
</Datasource>

Preparing a job

Once you've designed a beautiful map you need to send it to a TileMill instance for rendering. There can be several files required for rendering––XML, graphics, rasters, and fonts for example. We've designed a simple method that lets you package multiple files into a signal Zip file for submission. You can use Mac's Archive Utility, WinZip, or zip on linux machines to make the files.

Before you create the package, you must organize the files properly. Start by naming your Mapnik XML file yourmap.map.xml. You can use anything in place of "yourmap" as long as the file ends with the ".map.xml" extension.

Next, place any custom fonts in a sub-directory called "fonts." The instance will automatically install each font before beginning the rendering process.

Other files like graphics and rasters can be located anywhere within the directory structure as long as they are reference by the Mapnik XML file using the proper relative path.

Note that if you are not running a recent version of Cascadenik, the compiled Mapnik XML files will use absolute paths even if the paths in the source mml/mss are relative. If you notice absolute URLs in the compiled XML, either upgrade Cascadenik or run a search & replace to make sure the paths are relative. Otherwise, Mapnik will not be able to find your data.

When you're done organizing, Zip up the files in your directory. The final package should have structure similar to this:

mymap.zip:
  mymap.map.xml
  fonts/
    Comic Sans.ttf
    Helvetical.ttf
  mymarkerimage.png
  mysmalltiff.tif

With you Zip package ready, you're ready to submit it.

Submitting a job

The TileMill tools you installed on your computer give you the ability to easily submit map files to TileMill instances.

Once you have prepared your job package you can submit it with the tillmill.py command. This command has several options, but all have default values (describe in detail below). The following simple command will submit the job file using all of the default values:

tilemill.py submit -f mapfile.zip

Available submission options

Option Type Default Description
-f PATH, --file=PATH required none The location of the zip file which contains your map and data.
--layer=LAYER_NAME optional zipfile name Name of the generated layer. Defines where the results of the render will be stored, and has no relationship to actual 'layers' in the mapfile.
-z ZOOM_LEVELS, --zoom=ZOOM_LEVELS optional 0-8 The range of zoom levels to render, in the form MIN-MAX.
--bbox=BBOX optional -180.0,-90.0,180.0,90.0 The area that will be rendered, in degrees.
--email=EMAIL optional none An email address which will receive notifications about tile rendering.
--srs=SRS optional EPSG:900913 SRS of the generated map. An advanced option that will make maps incompatible Google Maps etc, but may be useful for highly specialized cases.
--tms-type optional google The directory structure of the tileset. This is an advanced option.
--metaTile optional true MetaTiling is a technique that reduces render time in many cases.

Starting a Server

Once your job is in the queue, you'll need to start up a server that will find it and render it. The tilemill toolchain provides a command to do just that:

tilemill.py start

You can optionally specify a keypair with the -k parameter if you want to be able to log into the server and check on its status interactively.

Monitoring jobs

If you specified an e-mail address with the job submission, the EC2 instance will attempt to inform you when rendering begins via e-mail. It will also notify you when the job is complete.

Please note, that Gmail and other mail services frequently block the IP addresses used by the EC2 instances. This is a limitation that we are working to resolve. If you do not receive e-mail notification, you will be required to track the progress of the rendering job by checking the existence of the files on S3.

Deploying finished tiles

When rendering is complete, your output S3 bucket will contain all of the tiles. You can use S3 to test and even host your tiles, but Amazon's CloudFront CDN is a better option for optimized distribution. Luckily, turning a S3 bucket into a CloudFront distribution is quite simple.

  1. Sign in to the AWS Management Console and select the CloudFront tab.
  2. Click "Create Distribution"
  3. Select the output bundle that contains your tiles.
  4. Click "Create"

The tiles will then be accessible at the domain name specified in the table.

Map File Formats & Performance

Picking appropriate file formats and compression rates for map tiles is vital to keeping maps fast. We've researched the topic and found a surprising amount of diversity in implementation - formats like JPEG, which is rarely used for text, is used for roads and road labels on some maps, and GIF, which is believed to be superseded by PNG, is still used on some maps.

Here's a chart of several providers' choices:

Providers Choices

Note that bing's maps are served as .png files but contain image/jpeg data, and that Google serves different formats based on detected bandwidth.

Given that JPEG offers continuously varying image quality, we analyzed how the 'Quality Number' indicates filesize:

Quality vs Filesize Graph

Adding Tiles to Your Site

You can add MapBox tiles to any website using OpenLayers, an open source library for creating dynamic maps on the web, or the Google Maps API. If you're using Drupal or Managing News, you can add new layers with the OpenLayers module.

Adding Tiles with OpenLayers

Use the following code as a starting point. This code will produce a map with the World Light tileset. To use another tileset, simply change the layername and file_extension variables to match the values found on the desired tileset's page.

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
   "http://www.w3.org/TR/html4/strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>MapBox Tileset Demo</title>
    <script src="http://www.openlayers.org/dev/OpenLayers.js"></script>
    <script type="text/javascript">

      var map;
      OpenLayers.IMAGE_RELOAD_ATTEMPTS = 3;
      OpenLayers.ImgPath = "http://js.mapbox.com/theme/dark/";
      function init(){

        // Customize the values below to change the tileset.
        // This information is available on each tileset page.
        var layername = 'world-light';
        var file_extension = 'png';

        // Build the map
        var options = {
          projection: new OpenLayers.Projection("EPSG:900913"),
          displayProjection: new OpenLayers.Projection("EPSG:4326"),
          units: "m",
          numZoomLevels: 12,
          maxResolution: 156543.0339,
          maxExtent: new OpenLayers.Bounds(
            -20037500,
            -20037500,
            20037500,
            20037500
          )
        };
        map = new OpenLayers.Map('map', options);

        // Layer definitions
        var layer = new OpenLayers.Layer.TMS(
          "MapBox Layer",
          [ "http://a.tile.mapbox.com/","http://b.tile.mapbox.com/",
            "http://c.tile.mapbox.com/","http://d.tile.mapbox.com/" ],
          { 'layername': layername, 'type': file_extension }
        );

        // Add layers to the map
        map.addLayers([ layer ]);

        // Set the map's initial center point
        map.setCenter(new OpenLayers.LonLat(0, 0), 1);
      }

    </script>
  </head>
  <body onload="init()">
    <div id="map" style="width: 500px; height: 300px"></div>
  </body>
</html>

Using MapBox tiles in Drupal

MapBox and OpenLayers module

The MapBox tile integraton with Managing News is a working example of how you can use MapBox tiles with the Drupal Openlayers module. You can refer to the Using Mapbox Tiles in Managing News page for details on how it works. More specific documentation on MapBox and the Openlayers module will be available soon once recent development stabilizes.

MapBox and GMap module

The MapBox module for Drupal makes new map types available to the GMap module. Install the module and MapBox map types will appear on the GMap settings page. These map types can be used throughout your site just like the default map types from Google.

Using MapBox tiles in Managing News

The two essential elements of OpenLayers maps are map layers and map presets. Map layers define a certain layer - a basemap of Afhghanistan or points representing news stories. Map presets are collections and configurations of layers into the maps you can browse on a site. In the Drupal features system, map layers are stored in defaults.inc files.

In the ManagingNews install profile, the 'mn_world' feature (in /profiles/managingnews/modules/features/mn_world/) contains the default map layers in the file mn_world.defaults.inc.

A single layer definition within this function looks like this:

$export = array();
$openlayers_layers = new stdClass;
$openlayers_layers->disabled = FALSE;
$openlayers_layers->api_version = 1;

// Your internal name for this layer
$openlayers_layers->name = 'layer-name';
// The layer name which will appear in the layer switcher
$openlayers_layers->title = 'Layer Title (for the layer switcher)';
$openlayers_layers->description = 'A description of the layer.;
$openlayers_layers->data = array(
  'projection' => array(
    '0' => '900913',
  ),
  // Change in order to make this layer into an overlay - otherwise it is a base layer,
  // only one of which can be shown at a time.
  'baselayer' => TRUE,
  'type' => 'MapBox',
  'options' => array(
     // Change this value to correspond to the layername documented on MapBox.com
     // for example, 'layername' => 'world-light'
    'layername' => 'layer-name',
    'spherical_mercator' => TRUE,
    // This contains a subset of the resolutions from serverResolutions
    // Eliminating and adding values to this array restricts the zoom levels permitted
    // on a map
    'resolutions' => array(
      '0' => 2445.98490469,
      '1' => 1222.99245234,
      '2' => 611.496226172,
      '3' => 305.748113086,
      '4' => 152.874056543,
      '5' => 76.4370282715,
      '6' => 38.2185141357,
      '7' => 19.1092570679,
      '8' => 9.55462853394,
    ),
    // Required for layers to function; do not change
    'serverResolutions' => array(
      '0' => 156543.0339,
      '1' => 78271.51695,
      '2' => 39135.758475,
      '3' => 19567.8792375,
      '4' => 9783.93961875,
      '5' => 4891.96980938,
      '6' => 2445.98490469,
      '7' => 1222.99245234,
      '8' => 611.496226172,
      '9' => 305.748113086,
      '10' => 152.874056543,
      '11' => 76.4370282715,
      '12' => 38.2185141357,
      '13' => 19.1092570679,
      '14' => 9.55462853394,
      '15' => 4.77731426697,
      '16' => 2.38865713348,
      '17' => 1.19432856674,
      '18' => 0.597164283371,
    ),
    // Required for layers to function; do not change
    'maxExtent' => array(
      '0' => -20037508.34,
      '1' => -20037508.34,
      '2' => 20037508.34,
      '3' => 20037508.34,
    ),
    'type' => 'png',
  ),
  'events' => array(),
  'callback' => 'featurename_process_layers',
);
// Change layer-name to a unique layer name
$export['layer-name'] = $openlayers_layers;

To add a layer, copy the code above or an existing MapBox layer, and change its settings, remembering to change the last line to differentiate it from other layers. Then add a line to mn_world.info:

features[openlayers_layers][] = "your_layer_name"

After this is done you need to change the layer presets in any of the other features that use a Mapbox map. For instance, to add a layer to fullscreen map preset, edit mn_world.defaults.inc, and find the fullscreen preset, which begins with:

$openlayers_presets->name = 'mn_fullscreen';

Then define the available layers on that map:

    'default_layer' => 'world_light',
    'layers' => array(
      'world_light' => 'world_light',
      'world' => 'world',
      'osmmapnik' => 'osmmapnik',
      'mn_airports' => 'mn_airports',
      'mn_schools' => 'mn_schools',
      'mn_hospitals' => 'mn_hospitals',
      'mn_news_attachment_1' => 'mn_news_attachment_1',
    ),

Using MapBox tiles with Google Maps API

Why Use Google Maps API and MapBox Together?

Google Maps and MapBox are not mutually exclusive tools, you can use both to create custom, fast maps for your website. If you already use Google Maps on your website but would like your map to look like the "World Dark" maps here http://mapbox.com/tileset/world-dark or if you'd like to add a transparent overlay like the US Congressional Districts tiles here http://mapbox.com/tileset/us-congressional-districts you can do so using MapBox tiles while keeping the rest of your Google code.

There are advantages to using the Google Maps API, such as its speedy javascript library which makes navigating and zooming a map very quick. Google's API also provides for numerous other options like adding markers and popups to your map. There are also advantages to using MapBox custom tiles, such as showing specific details on the map which are pertinent to your application and not offered by Google's default map types.

How It Works - High Level Overview

When you use the Google Maps API, Google offers a handful of map types which you can use as the map type for your maps implementation. If you look at the "Constants" section on http://code.google.com/apis/maps/documentation/reference.html#GMapType you will see the names of several map types, including map types for the sky, moon, and mars. There are about six map types that work for the regular world. When you make a map with the Google Maps API, you can specify which map type you want to use. You can choose from one of the types Google provides, or, you can choose to use a custom map type provided by a third party, like MapBox.com. In order to use a custom map type, you must change some of the code used to implement the map you currently use. The following examples describe how to create an entirely new map implementation with Google's Maps API and MapBox, but if you have an existing Google Map this documentation should give you enough information to conjecture what you need to change to get your custom maps loading from MapBox instead of using one of Google's default map types. At the end of this example is another short example on how to add transparent overlay tiles from MapBox, like the US Congressional Districts or the Afghanistan MRGS tiles.

A basic implementation of a Google Map that uses a custom map type requires you to inform the Google API of a few details:

  • Where to find the custom tiles for your custom map type
  • The maximum, minimum, and default zoom levels supported by the custom map type
  • The map center latitude and longitude you want to use

The follow examples also do the following:

  • Adds the default Google Maps UI to the map

Hello, World with MapBox and Google Maps API

Google provides a "Simple Map" example of implementing a Google Map at http://code.google.com/apis/maps/documentation/examples/index.html. The following example will start with the code from the "Simple Map" example and show you how to modify it to use a custom map type using tiles from MapBox.com.

MapBox "World Light" tiles and Google Maps API

Google's "Simple Map" example code consists of the following:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml">
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
    <title>Google Maps JavaScript API Example: Simple Map</title>
    <script src="http://maps.google.com/maps?file=api&amp;v=2&amp;sensor=false&amp;key=ABQIAAAAzr2EBOXUKnm_jVnk0OJI7xSosDVG8KKPE1-m51RBrvYughuyMxQ-i1QfUnH94QxWIa6N4U6MouMmBA"
            type="text/javascript"></script>
    <script type="text/javascript">
    function initialize() {
      if (GBrowserIsCompatible()) {
        var map = new GMap2(document.getElementById("map_canvas"));
        map.setCenter(new GLatLng(37.4419, -122.1419), 13);
        map.setUIToDefault();
      }
    }
    </script>
  </head>

  <body onload="initialize()" onunload="GUnload()">
    <div id="map_canvas" style="width: 500px; height: 300px"></div>
  </body>
</html>

You will need to follow these steps in relation to the "Simple Map" code above in order to implement the same example using the MapBox "World Light" tile set instead of Google's default map type.

  1. Swap out the API key ABQIAAAAzr2EBOXUKnm_jVnk0OJI7xSosDVG8KKPE1-m51RBrvYughuyMxQ-i1QfUnH94QxWIa6N4U6MouMmBA to match your own API key(go here if you need to sign up for a google maps api key).

    <script src="http://maps.google.com/maps?file=api&amp;v=2&amp;sensor=false&amp;key=PASTE-YOUR-KEY-HERE" type="text/javascript"></script>

  2. Add the MapBox layer helper Javascript to your page, below the Google Maps javascript

    <script src="http://js.mapbox.com/g/2/mapbox.js" type="text/javascript"></script>

  3. Add the MapBox layer to your Google Map so the javascript in the head of the page should look like

        function initialize() {
          if (GBrowserIsCompatible()) {
            var options = {minZoom: 11, maxZoom: 17};  
            var custommap = GMapBox('dc-nightvision', 'Night', options);
            map.setCenter(new GLatLng(38.9168770,-77.0359010), 15)
            map.addMapType(custommap);
            map.setMapType(custommap);
            map.setUIToDefault();
          }
        }

    Note the arguments to the GMapBox object:

    var custommap = GMapBox('dc-nightvision', 'Night', options);

"options" should be an object that may contain:

  • minZoom - the minimum zoom of the map
  • maxZoom - the maximum zoom of the map
  • overlay - true or false, whether a transparent overlay or not (more on this below)
  • type - filetype, default png (only for transparent overlays)
  • opacity - int opacity of tile overlays (only for transparent overlays)
  • osm - true or false, whether to credit OpenStreetMap for map data

The final code should look like the following, but using your own API key instead of the placeholder text:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml">
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
    <title>Google Maps JavaScript API Example: Simple Map</title>
    <script src="http://maps.google.com/maps?file=api&amp;v=2&amp;sensor=false&amp;key=PASTE-YOUR-KEY-HERE"
            type="text/javascript"></script>
  <script src="http://js.mapbox.com/g/2/mapbox.js" type="text/javascript"></script>
  <script type="text/javascript">
    function initialize() {
      if (GBrowserIsCompatible()) {
        var map = new GMap2(document.getElementById("map"));
        // Add custom map type    
        var options = {minZoom: 11, maxZoom: 17};  
        var custommap = GMapBox('dc-nightvision', 'Night', options);
        map.setCenter(new GLatLng(38.9168770,-77.0359010), 15)
        map.addMapType(custommap);
        map.setMapType(custommap);
        map.setUIToDefault();
      }
      else {
        alert("The Google Maps API is not compatible with this browser");
      }
    }
    </script>
  </head>
  <body onload="initialize()" onunload="GUnload()">
    <div id="map_canvas" style="width: 500px; height: 300px"></div>
  </body>
</html>

Add transparent overlay to Google Map

You can use most of the same code above to add a transparent overlay to a Google Map instead of adding a whole custom map type.

An example that adds the US Congressional Districts overlay to the map is as follows:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
  <title>MapBox World Light with Google Maps API</title>
  <script src="http://js.mapbox.com/g/2/mapbox.js" type="text/javascript"></script>
  <script src="http://maps.google.com/maps?file=api&amp;v=2&amp;sensor=false&amp;key=PASTE-YOUR-KEY-HERE" type="text/javascript"></script>
  <link rel=stylesheet href="gmapbox.css" type="text/css" media=screen /> 
</head>
  <script type="text/javascript">
    function initialize() {
      if (GBrowserIsCompatible()) {
        var map = new GMap2(document.getElementById("map"));
        map.setCenter(new GLatLng(45,-103), 3);
        var options = {minZoom: 0, maxZoom: 12, overlay: true, type: 'png', opacity: 1.0};
        customoverlay = GMapBox('congressional-districts-110', 'Districts', options);
        map.addOverlay(customoverlay);
        map.setUIToDefault();
      }
      else {
        alert("The Google Maps API is not compatible with this browser");
      }
    }
    </script>

  <body onload="initialize()" onunload="GUnload()">
    <div id="map" style="width: 900px; height: 600px"></div>
  </body>
</html>

Notice you must provide more options in the options object, and that you must call addOverlay() with the GMapBox object instead of calling addMapType()

You can find other examples which implement the "World Light", "DC Nightvision" and "Afghanistan Winter" tile sets at on GitHub at http://github.com/developmentseed/GMapBox

Related Resources and Sources

OpenLayers Themes

Even though OpenLayers allows users to customize map control images, many maps stick with the default, which can detract from the custom feel of map-centric websites. MapBox is publishing a set of controls as well as the source vector files needed to create new controls:

Controls

To make your map use these images, just add the line to your OpenLayers map initialization:

OpenLayers.ImgPath = "http://js.mapbox.com/theme/dark/";

This package includes all of the images needed to restyle a map with common navigational elements. It's known to cover the OpenLayers controls:

  • OpenLayers.Control.LayerSwitcher
  • OpenLayers.Control.Pan
  • OpenLayers.Control.PanZoom
  • OpenLayers.Control.PanZoomBar
  • OpenLayers.Control.ZoomIn
  • OpenLayers.Control.ZoomOut
  • OpenLayers.Control.ZoomToMaxExtent
  • OpenLayers.Control.ZoomPanel

The images and source code are available on GitHub. The design is licensed under BSD.

Attribution for MapBox and OpenStreetMap

MapBox tiles are free for commercial and non-commercial use according to the terms of service. The tiles themselves are copyrighted by MapBox, and when tiles containing data from OpenStreetMap, they also require attribution of OpenStreetMap's contributors, who license their content under the Creative Commons Attribution-Share Alike 2.0 Generic license. This license allows people to continue to make derivative works like map tiles and static maps from OpenStreetMap's data without threat of litigation, so it is vital to display this license.

MapBox Attribution

MapBox Attribution

MapBox and OpenStreetMap Attribution

MapBox and OpenStreetMap Attribution

Note: OSM attribution can also be added when using the Google Maps API.

In order to make proper attribution easy, the OpenLayers MapBox JavaScript includes a preset attribution as well as an 'osm' parameter to add OpenStreetMap attribution to layers containing road data. For instance, a Haiti road layer with OSM attribution would look like

new OpenLayers.Layer.MapBox('Haiti Terrain Grey',
  {
    layername: 'haiti-roads',
    type: 'png',
    osm: true // adds OpenStreetMap attribution
)

Finally, in order for any attribution to display on an OpenLayers map, the Attribution control must be added. For a map called 'map', the code would be

map.addControl(new OpenLayers.Control.Attribution());

This control will automatically show and combine attribution for layers that require it. If a map contains MapBox layers with and without OSM attribution, it is best to add the osm: true parameter to all of them, so that the MapBox tagline is only shown once.

Customization

The default position for the Attribution control is high, in order for it to be stacked with other controls, like a scale bar. However, this can look awkward and can be alleviated by overriding its style.

<style type="text/css">
.olControlAttribution {
  bottom: 5px !important;
}

Also, if the colors of the attribution text are inappropriate for a map, they can be customized in the same way.

.olControlAttribution a,
.olControlAttribution span {
  color: #eee !important;
}

If you have any questions, or need help, contact us.

Maps on a Stick

Maps on a Stick is an offline mapping tool that lets users view custom tilesets and KML overlays through a simple browser that cane be run on a USB drive.

The documentation here contains information on how to customize maps on a stick and approaches to distributing maps on a stick on USB drives.

Customizing Maps on a Stick

The initial distribution of Maps on a Stick is only meant to be an example, and it is made to be modified with new data and layers for any purpose. Doing so requires some knowledge of javascript, but given that requirement, it is otherwise quite straightforward.

Adding new KML Layers

Although Maps on a Sticks supports the addition of KML layers on the fly by dropping them into the 'KML Data' folder and then adding them through the user interface, for some applications KML layers should already be on the map when they are loaded.

In order to set a base configuration like that, simply add the KML layers to the 'KML Data' folder and edit system/resources/layers.js. There's a snippet of javascript at the bottom of the file which is surrounded by comments (/* and */), and can be copied out of those comments and added to the space directly above to add a layer to the map on initialization. Then edit the path to the layer and the layer name as desired. Note that the path to the KML file should be changed by changing the filename, and keeping the path the same - setting this path to something absolute (like C:\Files\kml.kml) will cause the application to break on other systems and possibly when the drive is ejected and plugged in again.

Adding new Tile Layers

Maps on a Stick ships with a basic world tileset, but adding a new tileset is simple. More tilesets may be released by MapBox.com, and they can be created with the TileMill AMI or with a similar setup that produces static tilesets. Once you have a tileset created, add it to the folder /tiles/1.0.0/ so that, for a tileset named world-red, it forms the path /tiles/1.0.0/world-red/, and the path /tiles/1.0.0/world-red/0/0.png for the first tile. Then duplicate the layer type contained in the system/resources/layers.js file to add this layer to the map on load. To add layers of a different type, like TMS, OSM, or WMS, see the top of the layers.js file or OpenLayers.org for more information.

Distributing Maps on a Stick

Flash drives can be very fast for common operations, but for the task of transferring a large number of files at once, flash storage is extremely slow - transferring Maps on a Stick to a USB drive with the Mac OS X file would take more than 17 hours. A better (although more detailed) solution is to create an image of the USB drive, including the filesystem, and essentially 'burn' this virtual volume to the USB drive. This way, the filesystem is controlled and constructed on a fast disk and then the raw information of the files and filesystem is transferred directly to the storage device.

Requirements

  • This process will require familiarity with some low-level POSIX utilities, especially dd. This is not a guide you can run through by copying commands - it will require, at the very least, careful editing of the sample commands.
  • This is written for Apple Macintosh computers. POSIX equivalents to Mac helper applications like hdiutil certainly exist and a guide for doing this with Linux, etc., is certainly possible, but not provided here.

Create an Image from Folder

sudo hdiutil create -verbose -fs MS-DOS -fsargs "-F 32 -c 4" -volname "MAPSONSTICK" -srcfolder your_map_application your_map_application.dmg

This command creates an Apple Disk Image from a folder on your computer. Notably, the image will have a FAT32 filesystem - the -F 32 argument specifies that this will be the case, rather than FAT16, which is limited to a 2 gigabyte partition. The -c 4 option specifies that the clusters on the disk - which determine the minimum space used for a single file - are limited to 4K, which is conveniently a similar size to the size of a typical tile.

Attach the Image as a Device

hdiutil attach -nomount your_map_application.dmg

This command will typically return two paths, like

/dev/disk1          FDisk_partition_scheme        
/dev/disk1s1        DOS_FAT_32

Use the latter path (/dev/disk1s1) for future steps.

Attach USB Drive

Put the USB flash device in your computer's USB port, and then run df in the console and note the output. The leftmost column shows what the device of the USB flash drive is, and the rightmost will show the mount point. Remember the left column (which will be something like /dev/disk1s2) and then run

umount /Volumes/USBDrive

To unmount the drive

Image the drive

Make absolutely sure that the if and of parameters of the following command are accurate; the dd utility is able to write over essential data if you accidentally put the device of another drive in the of parameter.

sudo dd if=/dev/FROM_DISKIMAGE of=/dev/TO_USBDRIVE bs=8m

The last parameter, bs, indicates the blocksize of the transfer; the size of each chunk of data that is transferred. This value is tuned for setups we've encountered, but other values may lead to a faster process.

tip: on Mac computers, disks are exposed both as raw devices, under rdisk3, and buffered devices, under disk3, etc. Writing images to the raw disk, beginning with "r", is often much faster than writing to the standard buffered disk.

Checking progress

The dd operation that is now running can take a while, and it doesn't provide any status information. However, it can.

Open a new terminal and run

~$ ps aux | grep "dd"
root      1698   0.1  0.0  2434768    252 s000  U+   12:18PM   0:01.67 dd if=/dev/disk2 of=/dev/disk1s2 bs=32k

Note the process id: the first number in the row of your dd process. In the above output, it is 1698. Use this id in the following command.

~$ sudo kill -SIGINFO PROCESS_ID

Going back to the terminal which is running dd will show a count of how many bytes have been transferred. To get a figure in megabytes, you can use the units utility.

~$ units "70000 bytes" "megabytes"
* 0.066757202
/ 14.979657

So, 70000 bytes is 0.06 megabytes.

When dd completes, it will output a summary of how much information was transferred and in what timeframe.

Maki Icon Set

Maki

Maki is a consistent set of point of interest icons for use on maps of all kinds. The icons are freely available for use under the BSD License.

Maki Icons

You are welcome to hotlink to the icons below. The source SVG is available on GitHub and we've created a style guide to help people who are interested in expanding the set. The icons in the source file have been grouped and named to take advantage of Inkscape's batch export feature (though you can use any program that supports SVG to edit them).

Icon Filename
airport-10.png airport-10.png
airport-15.png airport-15.png
bank-10.png bank-10.png
bank-15.png bank-15.png
building-collapsed-10.png building-collapsed-10.png
building-collapsed-15.png building-collapsed-15.png
building-damaged-10.png building-damaged-10.png
building-damaged-15.png building-damaged-15.png
bus-10.png bus-10.png
bus-15.png bus-15.png
cafe-10.png cafe-10.png
cafe-15.png cafe-15.png
campsite-10.png campsite-10.png
campsite-15.png campsite-15.png
cemetery-10.png cemetery-10.png
cemetery-15.png cemetery-15.png
cemetery-christian-15.png cemetery-christian-15.png
cemetery-jewish-15.png cemetery-jewish-15.png
cemetery-muslim-15.png cemetery-muslim-15.png
cinema-10.png cinema-10.png
cinema-15.png cinema-15.png
circle-10.png circle-10.png
circle-7.png circle-7.png
embassy-10.png embassy-10.png
embassy-15.png embassy-15.png
fuel-10.png fuel-10.png
fuel-15.png fuel-15.png
garden-10.png garden-10.png
garden-15.png garden-15.png
grocery-10.png grocery-10.png
grocery-15.png grocery-15.png
historic-10.png historic-10.png
historic-15.png historic-15.png
infrastructure-damaged-10.png infrastructure-damaged-10.png
infrastructure-damaged-15.png infrastructure-damaged-15.png
infrastructure-destroyed-10.png infrastructure-destroyed-10.png
infrastructure-destroyed-15.png infrastructure-destroyed-15.png
landslide-10.png landslide-10.png
landslide-15.png landslide-15.png
library-10.png library-10.png
library-15.png library-15.png
lodging-10.png lodging-10.png
lodging-15.png lodging-15.png
medical-10.png medical-10.png
medical-15.png medical-15.png
metro-10.png metro-10.png
metro-15.png metro-15.png
monument-10.png monument-10.png
monument-15.png monument-15.png
museum-10.png museum-10.png
museum-15.png museum-15.png
park-10.png park-10.png
park-15.png park-15.png
parking-garage-10.png parking-garage-10.png
parking-garage-15.png parking-garage-15.png
parking-lot-10.png parking-lot-10.png
parking-lot-15.png parking-lot-15.png
playground-10.png playground-10.png
playground-15.png playground-15.png
port-10.png port-10.png
port-15.png port-15.png
post-10.png post-10.png
post-15.png post-15.png
religious-christian-10.png religious-christian-10.png
religious-christian-15.png religious-christian-15.png
religious-jewish-10.png religious-jewish-10.png
religious-jewish-15.png religious-jewish-15.png
religious-muslim-10.png religious-muslim-10.png
religious-muslim-15.png religious-muslim-15.png
restaurant-10.png restaurant-10.png
restaurant-15.png restaurant-15.png
school-college-10.png school-college-10.png
school-college-15.png school-college-15.png
school-elementary-10.png school-elementary-10.png
school-elementary-15.png school-elementary-15.png
settlement-temporary-10.png settlement-temporary-10.png
settlement-temporary-15.png settlement-temporary-15.png
shopping-10.png shopping-10.png
shopping-15.png shopping-15.png
sports-10.png sports-10.png
sports-15.png sports-15.png
tourism-10.png tourism-10.png
tourism-15.png tourism-15.png
town-hall-10.png town-hall-10.png
town-hall-15.png town-hall-15.png

Style Guide

For every icon, there is both a 10x10 pixel size for higher zoom levels, and a 15x15 pixel size for lower zoom levels.

Maki icons have a 1 pixel 50% opaque white border

1px Border

Both sizes of Maki icons have the same size border. This makes them legible against any background color and stand out against complex backgrounds. The outline color extends into the negative space of the icon and has rounded edges.

Maki icons have strong silhouettes

Silhouettes

Each Maki icon has a unique, descriptive silhouette. Instead of using basic shapes like circles to define edges, the contour of the image depicted in the icon defines the edge.

Maki icons use Internationally recognizable symbols

International recognizable symbols

Whenever possible, avoid alphanumeric characters or purely American/Western concepts. When this is not possible, make multiple versions. Some symbols are more universal than others. For example, letters represent post offices better than mailboxes because mailboxes look different in different parts of the world.

Maki icons are one color

Color

Maki icons are all one color. They are designed in black but intended to be usable in a variety of colors.

Design on the grid

Design on the Grid

Because Maki icons are so small, it is important to design them according to the pixel grid in order to preserve a crisp, clear appearance at actual size. In the image above to the right, the pink area shows the proper line placement of the icon on the grid.