Skip to main content

Census blocks - unioning census geographies

This example uses United States Census data for Connecticut to show how to union features differently at different zoom levels. The example recipe uses the union filter to group census blocks into larger geographies as you zoom out, so the size of features is appropriate to each zoom level. The example recipe also sums the land area attribute available in the source data for any features that are unioned.

How to manage U.S. Census data on a map

  1. Define the simplification value of features. The recipe starts from a tileset source of Census blocks and initially simplifies them outward (outward_only: true) with a small simplification distance (distance: 1). For MTS, recipe simplification values range from 0 - 40, with a value 20 indicating moderate simplification. 4 is the default parameter if you do not specify a value in your recipe. The default simplification value simplifies your data so that is not distinguishable visually but will still make the tiles as small as possible under the constraints of tile boundaries. Setting simplification to a value of 1 ensures the highest amount of precision possible of a feature, aiding the outward_only: true operation. Outward simplification is helpful because it ensures more precise block unioning by defaulting to the largest area possible for unioned polygons, although that is at the expense of a larger tile size.

  2. Define the hierarchy of features per zoom level. The recipe then uses set to create a new attribute, key, whose value is equal to a combination of existing data attributes depending on the zoom level. At zoom levels >= 0, the value for key is only the state ID (STATEFP10), then at zoom level >= 5, county ID (COUNTYFP10) is added, at zoom levels >= 8 tract ID (TRACTCE10) is added, and finally at zoom levels >= 11 block ID (BLOCKCE10) is added. concat is used within the set expression to combine what are individual data attributes in the source data into a single attribute for the resulting feature in the tileset. For example, at zoom level 5, a key will have a state level reference, such as 09003, but at zoom level 6, it will concatenate the STATE and COUNTY reference ids to a value such as 09003520202.

  3. Union features. The union field of the recipe then combines features based on the newly created key attribute using group_by. Because we are unioning based on key and the value of key changes with zoom level, this also means the way features are unioned changes based on zoom level. We're also using sum in the aggregate option of the union to sum the land area (ALAND10) attribute of each feature that is being unioned. Once the union is complete, bidirectional (outward_only: false) simplification is applied with a standard simplification distance (distance: 4). Setting outward simplification to false reduces the size of the tiles in the resulting tileset by defaulting to the smallest area possible when unioning polygon features. This means better map performance, but less precise unioning among polygons.

This is helpful because it ensures more precise block unioning, although that is at the expense of a larger tile size.

{
"version": 1,
"layers": {
"census": {
"minzoom": 0,
"maxzoom": 11,
"source": "mapbox://tileset-source/{username}/census-blocks",
"features": {
"simplification": {
"outward_only": true,
"distance": 1
},
"attributes": {
"set": {
"key": [ "concat",
[ "case", [ ">=", [ "zoom" ], 0 ], [ "get", "STATEFP10" ], "" ],
[ "case", [ ">=", [ "zoom" ], 5 ], [ "get", "COUNTYFP10" ], "" ],
[ "case", [ ">=", [ "zoom" ], 8 ], [ "get", "TRACTCE10" ], "" ],
[ "case", [ ">=", [ "zoom" ], 11 ], [ "get", "BLOCKCE10" ], "" ]
]
}
}
},
"tiles": {
"union": [
{
"group_by": [ "key" ],
"aggregate": {
"ALAND10": "sum"
},
"simplification": {
"distance": 4,
"outward_only": false
}
}
]
}
}
}
}

The tileset on the left was generated without using any recipe options. The source data contains too much information to be viewed at a world view; features are randomly dropped to meet the 1250 Kilobyte tile size limit. You see fragmented roads because there is no logic in the recipe to dictate prioritization of features at the city and street-level perspective.

The map on the right is a tileset generated with the sample recipe above. The map is rendered at a state-level view, where you can view STATE census blocks. When you zoom in, the census blocks become increasingly smaller at the COUNTY, TRACT, and BLOCK levels. If you hover over the features, you will see the generated key values and the associated census block areas under ALAND10.

Using the Tilesets CLI to generate a tileset

This section describes how to use the Tilesets CLI to generate a tileset.

  1. Download the data you'll use to create the tileset:
arrow-downDownload line-delimited GeoJSON
  1. Create a tileset source named census-blocks with the data you downloaded
tilesets upload-source username census-blocks ~/your/local/path/census.ldgeojson
  1. Create your recipe as a local JSON file (for example, census-blocks-recipe.json) with your census-blocks tileset source.
{
"version": 1,
"layers": {
"census": {
"minzoom": 0,
"maxzoom": 11,
"source": "mapbox://tileset-source/{username}/census-blocks",
"features": {
"simplification": {
"outward_only": true,
"distance": 1
},
"attributes": {
"set": {
"key": [ "concat",
[ "case", [ ">=", [ "zoom" ], 0 ], [ "get", "STATEFP10" ], "" ],
[ "case", [ ">=", [ "zoom" ], 5 ], [ "get", "COUNTYFP10" ], "" ],
[ "case", [ ">=", [ "zoom" ], 8 ], [ "get", "TRACTCE10" ], "" ],
[ "case", [ ">=", [ "zoom" ], 11 ], [ "get", "BLOCKCE10" ], "" ]
]
}
}
},
"tiles": {
"union": [
{
"group_by": [ "key" ],
"aggregate": {
"ALAND10": "sum"
},
"simplification": {
"distance": 4,
"outward_only": false
}
}
]
}
}
}
}
  1. Create a tileset with the id username.census-blocks-unioned using your recipe and name it "census blocks". This will be an empty tileset to start, until you publish it.
tilesets create username.census-blocks-unioned --recipe ~/your/local/path/census-blocks-recipe.json --name "census blocks"
  1. Now you're ready to publish your tileset and start processing your data.
tilesets publish username.census-blocks-unioned

Preview your tileset

  1. You can now add your new tileset to a map style to visualize its data. Below is an example of how to add the username.census-blocks-unioned tileset as a vector tile source to a Mapbox GL JS map style.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Census blocks - unioning census geographies</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<link href="https://api.mapbox.com/mapbox-gl-js/v3.5.1/mapbox-gl.css" rel="stylesheet">
<script src="https://api.mapbox.com/mapbox-gl-js/v3.5.1/mapbox-gl.js"></script>
<style>
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
</style>
</head>
<body>
<div id="map"></div>
<script>
// TO MAKE THE MAP APPEAR YOU MUST
// ADD YOUR ACCESS TOKEN FROM
// https://account.mapbox.com
mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN';
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/light-v10',
zoom: 5,
center: [-72.619628, 41.754922]
});

map.on('load', () => {
map.addSource('census', {
type: 'vector',
url: 'mapbox://examples.census-blocks-unioned'
});
map.addLayer({
'id': 'census-id',
'type': 'line',
'source': 'census',
'source-layer': 'census_block_layer',
'layout': {
'line-join': 'round',
'line-cap': 'round'
},
'paint': {
'line-color': '#ff69b4',
'line-width': 1
}
});
});
</script>

</body>
</html>

Recipe options used

FieldDescriptionData type
** source**The source data to use for this layer. Tileset sources are created with the Create a tileset source endpoint of Mapbox Tiling Service (MTS).String
minzoomSpecify the minimum zoom at which your tileset will be available. A value of 0 here ensures that the census blocks will be visible at a global level.Integer
maxzoomSpecify the maximum zoom at which your tileset will be available. A value of 11 here ensures that the census blocks will be visible as the user zooms to block level, and will be magnified as they zoom deeper.Integer
features.simplification.outward_onlySpecifies that features will be simplified only outward, so they will overlap slightly and union correctly rather than leaving gaps.Boolean expression
features.simplification.distanceSpecifies that features will be simplified only by a single unit of distance, so even at low zoom levels small blocks will not be simplified away, so they will union correctly rather than leaving gaps.Integer expression
features.attributes.setCreates a new feature attribute from the conditional concatenation of existing attributesObject mapping String to Expression
features.tiles.union.group_bySpecifies that features will be unioned if the key attribute created above matchesArray of String
features.tiles.union.aggregateSpecifies that the land area (ALAND10) attribute of features will be summed as they are unionedObject mapping String to String
features.tiles.union.simplification.distanceSpecifies that after unioning, the features will be simplified to the normal distance of 4.Integer Expression
features.tiles.union.simplification.outward_onlySpecifies that after unioning, the features will may simplified either inward or outward, allowing the smallest final tilesBoolean Expression
Was this example helpful?