TileMill’s ability to map 3D complex structures continues to improve. This is a quick post sharing new details since Dave previously wrote about using Mapnik’s Building Symbolizer, demonstrating how to render building polygons with a height attribute from within TileMill

DC Buildings

The District of Columbia, as part of its open government data program, publishes a building polygons dataset and a 3D Buildings dataset. The main differences between the two datasets are the level of detail and the geometry type. I’m going to be working with the 3D Buildings layer, which contains highly detailed structures and uses ESRI’s Multipatch Geometry Type.

Because the Multipatch geometry type is not as broadly supported in GDAL as are 2 dimensional geometries, we need to include an additional flag -nlt "MULTIPOLYGON25D" in our script to convert the Shapefile to a PostGIS layer using the command line utility ogr2ogr.

#!/bin/bash

PG_GDAL="user=postgres host=localhost port=5432 dbname=postgis"

ogr2ogr \
   -f PostgreSQL \
   -t_srs EPSG:900913  \
   PG:"$PG_GDAL" \
   BldgPly_3D.shp \
   -nln building_3d \
   -nlt "MULTIPOLYGON25D"

By including the -nlt option, we ensure that the new PostGIS table’s geometry column contains latitude, longitude, and altitude for each multipolygon. This allows us to use PostGIS’ 3D Spatial Functions.

As Dave explained in an earlier TileMill 3D blog post, we use CartoCSS to control how TileMill renders the buildings. First, we need to pull out the altitude value from each geometry, and add it to a new column in our table for TileMill. It’s easy to add a height column to the table using the PostGIS ST_ZMax() function. I found the buildings rendered most accurately when I divided the altitude by 2. Here’s the script to create a table ready for loading in TileMill.

DROP TABLE tilemill_buildings;
CREATE TABLE tilemill_buildings
AS SELECT
ogc_fid,
CAST(size_sqm AS INT) as size_sqm,
CAST(ROUND(CAST(ST_ZMax(wkb_geometry)/2 AS NUMERIC),0) AS INT) AS z_xlarge,
CAST(ROUND(CAST(ST_ZMax(wkb_geometry)/2.5 AS NUMERIC),0) AS INT) AS z_large,
CAST(ROUND(CAST(ST_ZMax(wkb_geometry)/3 AS NUMERIC),0) AS INT) AS z_med,
CAST(ROUND(CAST(ST_ZMax(wkb_geometry)/4 AS NUMERIC),0) AS INT) AS z_small,
wkb_geometry AS wkb_geometry
FROM building_3d
ORDER BY
ST_YMax(Box3D(wkb_geometry)) DESC;

The last ORDER BY part is important, controlling the order in which TileMill renders each building/element. From here, it’s a matter of loading the layer in TileMill, and styling with CartoCSS.

[![DC Buildings TileMill Building Symbolizer](https://farm9.staticflickr.com/8509/8486512820_78b8f449da_o.png)](http://tiles.mapbox.com/herwig/map/map-onzoztaf#17.00/38.91619/-77.04398)
@polygon:rgb(100,100,100);
@building:rgba(189,168,144,.9);

#buildings {
  [size_sqm > 50][zoom < 15] {
  line-color:transparent;
  polygon-fill:@building;
  }
  [zoom = 12] {polygon-opacity:.2;}
  [zoom = 13] {polygon-opacity:.4;}
  [zoom = 14] {polygon-opacity:.9;}
  [zoom > 15] {
    polygon-fill:transparent;
    building-fill:@building;
    building-fill-opacity:1;
    [zoom = 15][size_sqm > 100] {
      building-height: '[z_small]';
    }
    [zoom = 16] {
      building-height: '[z_med]';
    }
    [zoom >= 17] {
      building-height: '[z_xlarge]';
    }
  }
}
[![DC Buildings TileMill Building Symbolizer](https://farm9.staticflickr.com/8517/8485364827_2a81a7d918_o.png)](http://tiles.mapbox.com/herwig/map/map-onzoztaf#17.00/38.91619/-77.04398)

Building Skeletons

Experimenting with compositing operations, I created a second map, DC Building Skeletons to visualize what’s going on in TileMill as it renders these incredibly complex structures.

Building Skeletons in TileMill

@building:rgba(0,255,255,1);
@under:rgb(0,0,0);

#buildings {
  [size_sqm > 50][zoom <= 15] {
  ::under {
    polygon-fill:rgba(255,255,255,.5);
    polygon-comp-op:color-burn;
  }
  line-color:transparent;
  polygon-fill:@building;
  polygon-opacity:.3;
  }
  [zoom = 12] {polygon-opacity:.2;}
  [zoom = 13] {polygon-opacity:.4;}
  [zoom = 15] {polygon-opacity:.5;}
  [zoom > 15]{polygon-opacity:0;}
  [zoom > 15]::under {
    building-fill:@under;
    building-fill-opacity:.1;
    [zoom = 16 ][size_sqm > 100] {
      building-height: '[z_small]';
    }
    [zoom = 17] {
      building-height: '[z_large]';
    }
    [zoom > 17] {
      building-height: '[z_xlarge]';
    }
  }
  [zoom > 15] {
    polygon-fill:transparent;
    line-color:transparent;
    building-fill:@building;
    building-fill-opacity:.8;
    [zoom = 16 ][size_sqm > 100] {
      building-height: '[z_small]';
    }
    [zoom = 17] {
      building-height: '[z_large]';
    }
    [zoom > 17] {
      building-height: '[z_xlarge]';
    }
  }
}

Each rendered building is actually a collection of many smaller polygons, each with individual geometry and elevation values, that are sequentially rendered on top of one another.

[![DC Building Skeletons TileMill Building Symbolizer](https://farm9.staticflickr.com/8376/8486426452_1f6537c29a_o.png)](http://a.tiles.mapbox.com/v3/herwig.map-okz7199j.html#17.00/38.88570/-77.02521)

Check out the full-screen DC 3D Buildings and DC Building Skeletons maps hosted on MapBox.