With Mapbox Studio, it’s straightforward to apply styles to every building, park, or road label in the world. But sometimes you want more control. Regular expression filters are one way to get control. They’re just like regular filters, but instead of matching exact strings, they match patterns. With regular expressions, you can do things like select every city that starts with the letter “M” or every road label that has between 10 and 20 characters.
Local control with global datasets
To showcase the usefulness of regular expressions, I’m going to make a map of the 14th Street corridor in Washington, DC using Mapbox Streets data.
I’ll start by adding a single point to the map:
#poi_label[address='1811 14th Street NW'] {
shield-file: url('marker.svg');
shield-unlock-image: true;
shield-size: 12;
shield-fill: #e16363;
shield-name: '[name_en]';
shield-face-name: ‘Open Sans Bold’;
shield-text-dy: 8;
}
I’m filtering out every point of interest in the #poi_label
layer that doesn’t have an address that exactly matches the Black Cat’s address. Now that I have the Black Cat on my map, I could take the same approach to add the rest of the POIs along 14th Street to the map.
Instead of writing dozens of nearly duplicate selectors, I can use a regular expression to automatically match everything along 14th Street:
#poi_label[address=~'.*14th Street N.*'] {
shield-file: url('marker.svg');
shield-unlock-image: true;
shield-size: 12;
shield-fill: #e16363;
shield-name: '[name_en]';
shield-face-name: 'Open Sans Bold';
shield-text-dy: 8;
}
Lets break down how this works: the =~
operator tells my style that the following filter is a regular expression, and not a basic string match. That means I can use regular expression tokens. The .
at the beginning of the filter is a token meaning “any character”. The *
that follows means, “match 0 or more of the preceding token”. 14th Street N
is basic string. The .
and *
at the end mean the same thing as those at the beginning: match any character, any number of times.
Taken together, the whole filter translates to, “look for every feature that starts with any combination of characters, followed by exactly ‘14th Street N’, followed by any combination of characters”. The filter will match 1811 14th Street NW
as well as 1714 Rear 14th Street Northwest Washington 20009
, so it covers a range of address formats but is specific enough to avoid picking up unwanted points.
I’m now going to make the focus on 14th street clearer by aligning map labels relative to the street:
#poi_label[address=~'.*14th Street N.*']{
shield-file: url('marker.svg');
shield-unlock-image: true;
shield-size: 12;
shield-fill: #e16363;
shield-name: '[name_en]';
shield-face-name: 'Open Sans Bold';
[address=~'(...)[13579].*'] {
shield-text-dx: 8;
}
[address=~'(...)[24680].*'] {
shield-text-dx: -8;
}
}
I’m taking advantage of the fact that addresses on the East side of the street have odd numbers and addresses on the West side of the street have even numbers. The regular expression (…)[13579].*
means, “match any three characters, followed by 1,3,5,7, or 9, followed by any number of characters”. So 1835 14th Street NW
as well as 2231 14th Street North West
both match.
After a few more tweaks, here’s the final map:
Check out the project source code for details, and open it in Mapbox Studio to experiment more with regular expressions.
More Examples
Regular expressions are useful for conventional mapping techniques like controlling the size of shields on highway labels based on the contents of the labels. The Mapbox Studio default style Looseleaf uses this technique:
#road_label::us_shield[class='motorway'] {
// 1 & 2 digit US state highways
[ref =~ '^(AL|AK|AS|AZ|AR|CA|CO|CT|DE|DC|FM|FL|GA|GU|HI|ID|IL|IN|IA|KS|KY|LA|ME|MH|MD|MA|MI|MN|MS|MO|MT|NE|NV|NH|NJ|NM|NY|NC|ND|MT|OH|OK|OR|PW|PA|PR|RI|SC|SD|TN|TX|UT|VT|VI|VA|WA|WV|WI|WY|SR)\ ?\d[\dA-Z]?(;.*|$)'] {
shield-file: url(img/shield/us_state_2.png);
shield-name: @us-shield-name;
}
// 3 digit US state highways
[ref =~ '^(AL|AK|AS|AZ|AR|CA|CO|CT|DE|DC|FM|FL|GA|GU|HI|ID|IL|IN|IA|KS|KY|LA|ME|MH|MD|MA|MI|MN|MS|MO|MT|NE|NV|NH|NJ|NM|NY|NC|ND|MT|OH|OK|OR|PW|PA|PR|RI|SC|SD|TN|TX|UT|VT|VI|VA|WA|WV|WI|WY|SR)\ ?\d\d[\dA-Z](;.*|$)'] {
shield-file: url(img/shield/us_state_3.png);
shield-name: @us-shield-name;
}
// 1 & 2 digit US highways
[ref =~ '^US\ ?\d[\dA-Z]?(;.*|$)'] {
shield-file: url(img/shield/us_highway_2.png);
shield-name: @us-shield-name;
}
// 3 digit US highways
[ref =~ '^US\ ?\d\d[\dA-Z](;.*|$)'] {
shield-file: url(img/shield/us_highway_2.png);
shield-name: @us-shield-name;
}
// 1 & 2 digit US Interstates
[ref =~ '^I\ ?\d[\dA-Z]?(;.*|$)'] {
shield-file: url(img/shield/us_interstate_2.png);
shield-name: @us-shield-name;
shield-fill: #fefef1;
}
// 3 digit US Interstates
[ref =~ '^I\ ?\d\d[\dA-Z](;.*|$)'] {
shield-file: url(img/shield/us_interstate_3.png);
shield-name: @us-shield-name;
shield-fill: #fefef1;
}
}
Regular expressions are also a powerful tool for more expressive visualizations. The map Assemblage
uses regular expressions for random effects on buildings:
#building[osm_id =~ '.*[89]'][zoom>16] {
[osm_id =~ '.*8'] { polygon-fill: @pink;}
[osm_id =~ '.*9'] { polygon-fill: @lightblue;}
polygon-geometry-transform: rotate(20,0,0);
polygon-comp-op: multiply;
}
#building::a[osm_id =~ '.*[0123456]'][zoom>15] {
[osm_id =~ '.*4'] { polygon-fill: @yellow;}
[osm_id =~ '.*5'] { polygon-fill: @lightblue;}
[osm_id =~ '.*6'] { polygon-fill: @gray;}
polygon-fill: @green;
polygon-geometry-transform: rotate(-3,0,0);
polygon-comp-op: multiply;
[osm_id =~ '.*[0-3]'] {
polygon-pattern-geometry-transform: rotate(-3,0,0);
polygon-pattern-file: url("il6.png");
[zoom=19],[zoom=15] { polygon-pattern-file: url("il3.png"); }
[zoom=20],[zoom=16] { polygon-pattern-file: url("il4.png"); }
[zoom=21],[zoom=17] { polygon-pattern-file: url("il5.png"); }
[zoom=22] { polygon-pattern-file: url("il6.png"); }
[zoom=18] { polygon-pattern-file: url("il7.png"); }
}
}
Here’s a beautiful map style by AJ Ashton that wouldn’t be possible without regular expressions:
Map {
background-color: #012;
}
@seed: #f24;
['mapnik::geometry_type'>1] {
[osm_id =~ '.*0$'] { line-color: spin(@seed,36*0); }
[osm_id =~ '.*1$'] { line-color: spin(@seed,36*1); }
[osm_id =~ '.*2$'] { line-color: spin(@seed,36*2); }
[osm_id =~ '.*3$'] { line-color: spin(@seed,36*3); }
[osm_id =~ '.*4$'] { line-color: spin(@seed,36*4); }
[osm_id =~ '.*5$'] { line-color: spin(@seed,36*5); }
[osm_id =~ '.*6$'] { line-color: spin(@seed,36*6); }
[osm_id =~ '.*7$'] { line-color: spin(@seed,36*7); }
[osm_id =~ '.*8$'] { line-color: spin(@seed,36*8); }
[osm_id =~ '.*9$'] { line-color: spin(@seed,36*9); }
}
Learn more
For an excellent starting point for writing your own regular expressions for Mapbox Studio styles, visit www.regexr.com.