Skip to main content

Roads - unioning features based on shared attributes

This example uses a TIGER road export from Lafayette, Indiana and performs a join on roads that have the same name. In the final tileset, this will turn multiple features into a single, continuous feature. Performing a union operation on features based on attributes is helpful for keeping tileset size down and preventing unnecessary dropped features.

In the recipe below, we set a minzoom to 5 because our data only contains a subset of roads in Lafayette; it's not necessary to view this city-level data from a global or country-level perspective. We set maxzoom to 11 because this is an optimal resolution for viewing less precise, street-level data.

To adjust the TIGER data to meet our needs, our recipe filters out features from the source data. First, we keep all features with an MTFCC attribute that has a value between S1000 and S2000 (TIGER's classifications for roads). This filter-expression performs a lexical comparison on this string attribute.

After we filter out all unnecessary features, we union roads based on their FULLNAME, using the recipe option union.group_by. We then apply maintain_direction to allow multiple streets to be unioned together where the roads run in opposite directions.

Let's break down how roads can run in opposite directions. In the figure above, there is a major road, Main Street. Montgomery Street intersects Main Street. The streets north of Main St run north and the streets south of Main St run south. Since road LineStrings follow the direction of house numbering, if you want Montgomery Street south of Main Street to union nicely with Montgomery Street north of Main Street, you want one of them to be reversed, setting maintain_direction to false.

Why would you want to union road networks?

  1. Reduce tile size. Since every block of a street gets represented as part of a single line instead of having to have a separate feature for each block, the tiles are smaller. Combining features like this reduce the space needed for geometries and avoids repeating attributes for each feature.
  2. Improve visualizations of a continuous road network at city and street level views. The storage format of road networks can often prevent a seamless visualization of this data. Unioning ensures accuracy and continuity in visualizations of roads, removing road fragments and improving the appearance.
{
"version": 1,
"layers": {
"roads": {
"source": "mapbox://tileset-source/{username}/tiger-data",
"minzoom": 5,
"maxzoom": 11,
"features": {
"filter": [
"all",
[ ">=", [ "get", "MTFCC" ], "S1000" ],
[ "<=", [ "get", "MTFCC" ], "S2000" ]
]
},
"tiles": {
"union": [
{
"group_by": [ "FULLNAME" ],
"maintain_direction": false
}
]
}
}
}
}

The map on the left is a tileset generated without any recipe options. The map on the right is a tileset generated with the sample recipe above.

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 tiger-data with the data you downloaded
tilesets upload-source username tiger-data ~/your/local/path/tiger-roads.ldgeojson
  1. Create your recipe as a local JSON file (for example roads-unioning-recipe.json) with your tiger-data tileset source.
{
"version": 1,
"layers": {
"roads": {
"source": "mapbox://tileset-source/{username}/tiger-data",
"minzoom": 5,
"maxzoom": 11,
"features": {
"filter": [
"all",
[ ">=", [ "get", "MTFCC" ], "S1000" ],
[ "<=", [ "get", "MTFCC" ], "S2000" ]
]
},
"tiles": {
"union": [
{
"group_by": [ "FULLNAME" ],
"maintain_direction": false
}
]
}
}
}
}
  1. Create a tileset with the id username.tiger-roads-v1 using your recipe and name it "unioning tiger roads""

This will be an empty tileset to start, until you publish it.

tilesets create username.tiger-roads-v1 --recipe ~/your/local/path/roads-unioning-recipe.json --name "unioning tiger roads"
  1. Now you're ready to publish your tileset and start processing your data
tilesets publish username.tiger-roads-v1

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.tiger-roads-v1 tileset as a vector tile source to a Mapbox GL JS map style.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Roads - unioning features based on shared attributes</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<link href="https://api.mapbox.com/mapbox-gl-js/v3.0.1/mapbox-gl.css" rel="stylesheet">
<script src="https://api.mapbox.com/mapbox-gl-js/v3.0.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: 13,
center: [-86.838033, 40.4070669]
});

map.on('load', () => {
map.addSource('tiger-roads-src', {
type: 'vector',
url: 'mapbox://examples.tiger-roads-v1'
});
map.addLayer({
'id': 'tiger-roads-id',
'type': 'line',
'source': 'tiger-roads-src',
'source-layer': 'roads',
'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 the data in your tileset will be available. A value of 5 here ensures that the roads will be visible only when a user zooms in.Integer
maxzoomSpecify the maximum zoom at which the data your tileset will be tiled. Your map can be viewed past this zoom level because of overzooming, but the data at zoom levels greater than the maxzoom will be less precise. A value of 11 is high enough for 5-meter precision, which is good enough to show an overview of a city's roads from a city and street level perspective.Integer

| features.filter | Keep all features with an MTFCC attribute that has a value between S1000 and S2000 (TIGER's classifications for roads). This filter-expression performs a lexical comparison on this string attribute.| Array<Expression> | | tiles.union | Use the group_by method to union any features that have the same FULLNAME attribute. Set maintain_direction to false so that block faces can be connected together no matter which direction their house numbering runs. | Array<Object> |

Was this example helpful?