Crater Lake National Park

NPMap

Digital maps for the National Park Service

Using CartoDB to Label Detailed Lines in TileMill

Posted on 19 May 2014 by Mamata Akella

The best practice for labeling detailed lines at smaller scales in TileMill is to create a generalized line dataset for labeling while using the more detailed line dataset for display. There are several approaches that can be used to accomplish this task, some of which are described below.

A more detailed how-to is provided for an approach we found to be best suited for a recent project where we used CartoDB to generalize lines to label roads in TileMill.

QGIS or ArcGIS

One approach it to use either QGIS or ArcGIS to generalize lines at multiple tolerances for labeling at different scales. The result of this, as many of you know, is multiple generalized output datasets that are styled in TileMill depending on the scale level. This approach requires bringing each simplified dataset into TileMill in order to see if the simplification results were successful and to test out the labels, and, if the results aren't desirable, going back to either QGIS or ArcGIS and re-running the process.

TileMill

A TileMill workflow is to connect directly to a PostGIS database and create a simplification query. This is a good solution if you are comfortable setting up and importing data into PostgreSQL/PostGIS, but it should be noted that tile creation can slow down if TileMill has to wait for simplification to take place in the database. This is especially true for large datasets.

An alternative (and recommended) solution for TileMill is to use PostgreSQL/PostGIS and add multiple attributes to the line dataset where different simplified geometries can be stored. Those attributes can then be used in TileMill to label lines at different scales. This can be a little intimidating if you aren't familiar with PostgreSQL/PostGIS, but storing simplified values in the line dataset minimizes the amount of additional datasets that need to be managed and the amount of work TileMill has to do.

CartoDB

The solution that I found to be most efficient and least intimidating was to use CartoDB which is basically PostgreSQL and PostGIS running in the cloud. When you upload a dataset to CartoDB, it is read into a spatially-enabled database.

A really neat (and powerful) feature of CartoDB is the ability to write SQL queries using the SQL API. This means that data stored in CartoDB can be modified and queried on the fly using SQL statements run through the SQL API.

To relate this back to our generalized lines for labeling roads:

  • We can store our line dataset in CartoDB
  • We can query that dataset, via the SQL API, multiple times with different simplification tolerances
  • We can then access the simplified version in TileMill and style it accordingly
  • If the simplification results are not satisfactory, we can easily change the query and see a different simplification tolerance on the fly in TileMill

The map I designed is meant to display road closure status for zooms 9-14. Given the map's intended use, it is important that road labels display at all zoom levels. Using the original line dataset at zooms 9 and 10 was the biggest issue; some roads were being labeled, but not as many as I wanted. At larger zooms (11-14), generalized lines were not needed and default label placement in TileMill worked well.

Road labels at zoom 10 with non-generalized lines Road labels at zoom 10 with non-generalized lines.

Road labels at zoom 10 with generalized lines Road labels at zoom 10 with generalized lines.

You'll notice that using generalized lines for labeling at zoom 10 allows more roads to be labeled and also results in better placement and distribution.

How-to

Here is an outline of the workflow we used to bring in generalized line data from CartoDB to label our roads in TileMill:

  1. Upload roads data to CartoDB
  2. Construct a SQL API request to run simplification on the roads table stored in CartoDB
    • Follow this basic template from CartoDB to get started:
      http://{account}.cartodb.com/api/v2/sql?q={SQL statement}
    • For our map, we constructed the following query where ST_SIMPILFY is the simplification method:
      https://{account}.cartodb.com/api/v2/sql?q=SELECT ST_SIMPLIFY(the_geom,  1700) as the_geom,name FROM roads&format=geojson
  3. Paste the constructed URL in TileMill's layer editor:
  4. Apply styling to the road labels in TileMill using CartoCSS:
    #road-names-z10[zoom=10]{
     text-name: [name];
     text-size: 12;
     text-face-name:'Open Sans Regular';
     text-fill: red;
     text-halo-fill: white;
     text-halo-radius: 1.5;
     text-placement: line;
    }
  5. If you aren't satisfied with the results, modify the SQL query to test out different simplification values:
    • From our constructed URL example above, the simplification tolerance is 1700 (meters because our data are in Web Mercator)
    • To experiment with different values, open the layer editor in TileMill, change the simplification tolerance and resave the project
    • Since the CartoCSS for road labels is already defined, the road labels will automatically update using the new simplification value
  6. If you want to use generalized lines to label at multiple zoom levels, add another layer to TileMill and modify the SQL query to best suite the zoom level you are designing for
  7. Experiment with different values until you are satisfied with the results!

Using this method, I only had one dataset to manage, could test out different simplification values on the fly, and avoided the hassle of setting up a database.