Offline-capable geomap, meant for storing location bookmarks
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add layer expression builders

+479 -454
+1
www/index.d.ts
··· 2 2 3 3 declare global { 4 4 var __APP_VERSION__: string 5 + var __DEV__: string 5 6 6 7 interface Navigator { 7 8 standalone?: boolean
+64 -53
www/index.html
··· 1 1 <!DOCTYPE html> 2 2 <html lang="en"> 3 + <head> 4 + <meta charset="utf-8" /> 5 + <meta 6 + name="viewport" 7 + content="width=device-width, initial-scale=1, viewport-fit=cover" 8 + > 9 + <meta name="mobile-web-app-capable" content="yes"> 10 + <meta name="apple-mobile-web-app-capable" content="yes"> 11 + <meta name="description" content="An offline-capable world map"> 3 12 4 - <head> 5 - <meta charset="utf-8" /> 6 - <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"> 7 - <meta name="mobile-web-app-capable" content="yes"> 8 - <meta name="apple-mobile-web-app-capable" content="yes"> 9 - <meta name="description" content="An offline-capable world map"> 13 + <link rel="manifest" href="manifest.json" /> 14 + <link rel="icon" type="image/x-icon" href="/dist/icons/icon.ico" /> 15 + <link rel="apple-touch-icon" href="/dist/icons/icon.png"> 10 16 11 - <link rel="manifest" href="manifest.json" /> 12 - <link rel="icon" type="image/x-icon" href="/dist/icons/icon.ico" /> 13 - <link rel="apple-touch-icon" href="/dist/icons/icon.png"> 17 + <link rel="canonical" href="https://maps.bpev.me" /> 18 + <title>MapsApp</title> 14 19 15 - <link rel="canonical" href="https://maps.bpev.me" /> 16 - <title>MapsApp</title> 20 + <link 21 + rel="stylesheet" 22 + type="text/css" 23 + href="/static/styles/maplibre-gl.css" 24 + > 25 + <link 26 + rel="stylesheet" 27 + type="text/css" 28 + href="/static/styles/civility.min.css" 29 + > 30 + <link rel="stylesheet" type="text/css" href="/static/styles/theme.css"> 17 31 18 - <link rel="stylesheet" type="text/css" href="/static/styles/maplibre-gl.css"> 19 - <link rel="stylesheet" type="text/css" href="/static/styles/civility.min.css"> 20 - <link rel="stylesheet" type="text/css" href="/static/styles/theme.css"> 32 + <script src="/dist/index.js" type="module"></script> 33 + </head> 21 34 22 - <script src="/dist/index.js" type="module"></script> 23 - </head> 35 + <body> 36 + <a href="#main" class="skip-to-main">Skip to main content</a> 24 37 25 - <body> 26 - <a href="#main" class="skip-to-main">Skip to main content</a> 38 + <header hidden> 39 + <a id="header-back" href="#!/" hidden aria-label="Back"> 40 + ← Back 41 + </a> 42 + <strong id="page-title">MapsApp</strong> 43 + </header> 27 44 28 - <header hidden> 29 - <a id="header-back" href="#!/" hidden aria-label="Back"> 30 - ← Back 31 - </a> 32 - <strong id="page-title">MapsApp</strong> 33 - </header> 34 - 35 - <main id="main"> 36 - <div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%)"> 37 - <ui-spinner size="lg"></ui-spinner> 38 - </div> 39 - </main> 40 - 41 - <m-map></m-map> 45 + <main id="main"> 46 + <div 47 + style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%)" 48 + > 49 + <ui-spinner size="lg"></ui-spinner> 50 + </div> 51 + </main> 42 52 43 - <footer fixed> 44 - <ui-bottom-bar role="navigation" aria-label="Bottom navigation"> 45 - <a href="/" data-route aria-current="page"> 46 - <img src="/static/icons/home.svg" alt="" aria-hidden="true"> 47 - <span>Map</span> 48 - </a> 49 - <a href="/search" data-route> 50 - <img src="/static/icons/navigation.svg" alt="" aria-hidden="true"> 51 - <span>Search</span> 52 - </a> 53 - <a href="/bookmarks" data-route> 54 - <img src="/static/icons/bookmark.svg" alt="" aria-hidden="true"> 55 - <span>Bookmarks</span> 56 - </a> 57 - <a href="/settings" data-route> 58 - <img src="/static/icons/tool.svg" alt="" aria-hidden="true"> 59 - <span>Settings</span> 60 - </a> 61 - </ui-bottom-bar> 62 - </footer> 63 - </body> 53 + <m-map></m-map> 64 54 55 + <footer fixed> 56 + <ui-bottom-bar role="navigation" aria-label="Bottom navigation"> 57 + <a href="/" data-route aria-current="page"> 58 + <img src="/static/icons/home.svg" alt="" aria-hidden="true"> 59 + <span>Map</span> 60 + </a> 61 + <a href="/search" data-route> 62 + <img src="/static/icons/navigation.svg" alt="" aria-hidden="true"> 63 + <span>Search</span> 64 + </a> 65 + <a href="/bookmarks" data-route> 66 + <img src="/static/icons/bookmark.svg" alt="" aria-hidden="true"> 67 + <span>Bookmarks</span> 68 + </a> 69 + <a href="/settings" data-route> 70 + <img src="/static/icons/tool.svg" alt="" aria-hidden="true"> 71 + <span>Settings</span> 72 + </a> 73 + </ui-bottom-bar> 74 + </footer> 75 + </body> 65 76 </html>
+9 -10
www/static/styles/theme.css
··· 59 59 overflow: hidden; 60 60 } 61 61 62 - body:has(r-home)>main { 62 + body:has(r-home) > main { 63 63 overflow: hidden; 64 64 } 65 65 ··· 99 99 z-index: 100; 100 100 } 101 101 102 - m-map>.download-btn { 102 + m-map > .download-btn { 103 103 z-index: 10; 104 104 } 105 105 ··· 232 232 margin-bottom: var(--s3); 233 233 } 234 234 235 - r-settings section>p, 236 - r-settings-downloads section>p, 237 - r-settings-about section>p { 235 + r-settings section > p, 236 + r-settings-downloads section > p, 237 + r-settings-about section > p { 238 238 opacity: 0.6; 239 239 margin-bottom: var(--s3); 240 240 font-size: var(--f5); ··· 424 424 transform: none; 425 425 } 426 426 427 - .search-history-item>span { 427 + .search-history-item > span { 428 428 flex: 1; 429 429 min-width: 0; 430 430 overflow: hidden; ··· 432 432 white-space: nowrap; 433 433 } 434 434 435 - .search-history-item>img { 435 + .search-history-item > img { 436 436 flex-shrink: 0; 437 437 opacity: 0.35; 438 438 } ··· 754 754 border-radius: var(--br-base); 755 755 } 756 756 757 - .bm-import-group+.bm-import-group { 757 + .bm-import-group + .bm-import-group { 758 758 border-top: 1px solid currentColor; 759 759 } 760 760 ··· 775 775 gap: 1px; 776 776 } 777 777 778 - .bm-import-item+.bm-import-item { 778 + .bm-import-item + .bm-import-item { 779 779 border-top: 1px solid color-mix(in srgb, currentColor 15%, transparent); 780 780 } 781 781 ··· 792 792 } 793 793 794 794 @media (max-width: 768px) { 795 - 796 795 input, 797 796 textarea, 798 797 select {
+129
www/utils/expr.ts
··· 1 + // deno-lint-ignore-file no-explicit-any 2 + /** 3 + * MapLibre expression builders — named helpers that replace raw expression 4 + * arrays, making layer definitions more readable. 5 + * 6 + * @module 7 + */ 8 + 9 + // ─── Property access ──────────────────────────────────────────────────────── 10 + 11 + /** Get a feature property value: `['get', prop]` */ 12 + export const get = (prop: string): any[] => ['get', prop] 13 + 14 + /** Return the first non-null value: `['coalesce', ...args]` */ 15 + export const coalesce = (...args: any[]): any[] => ['coalesce', ...args] 16 + 17 + /** Wrap a value as a literal: `['literal', value]` */ 18 + export const literal = (value: unknown): any[] => ['literal', value] 19 + 20 + // ─── Interpolation ────────────────────────────────────────────────────────── 21 + 22 + type ZoomStop = [number, any] 23 + 24 + /** Linear interpolation over zoom, with `[zoom, value]` stop pairs */ 25 + export const linear = ( 26 + ...zoomStops: ZoomStop[] 27 + ): any[] => ['interpolate', ['linear'], ['zoom'], ...zoomStops.flat()] 28 + 29 + /** Exponential interpolation over zoom, with `[zoom, value]` stop pairs */ 30 + export const exp = ( 31 + base: number, 32 + ...zoomStops: ZoomStop[] 33 + ): any[] => [ 34 + 'interpolate', 35 + ['exponential', base], 36 + ['zoom'], 37 + ...zoomStops.flat(), 38 + ] 39 + 40 + // ─── Conditional ───────────────────────────────────────────────────────────── 41 + 42 + /** 43 + * Match a feature property against value/result pairs. 44 + * 45 + * `match('class', { minor: '#efefef', service: '#ddd' }, '#fff')` 46 + * produces `['match', ['get', 'class'], 'minor', '#efefef', 'service', '#ddd', '#fff']` 47 + */ 48 + export function match( 49 + property: string, 50 + cases: Record<string, unknown>, 51 + fallback: unknown, 52 + ): any[] { 53 + return ['match', ['get', property], ...Object.entries(cases).flat(), fallback] 54 + } 55 + 56 + /** Conditional expression: `['case', ...args]` */ 57 + export const cond = (...args: any[]): any[] => ['case', ...args] 58 + 59 + // ─── mz fade ───────────────────────────────────────────────────────────────── 60 + 61 + const MZ: any[] = ['coalesce', ['get', 'mz'], 0] 62 + 63 + /** 64 + * Zoom-stop pair for mz-based feature fade-in. 65 + * Returns a `[zoom, value]` tuple — pass directly to `linear()`/`exp()`. 66 + * 67 + * Features with mz <= zFinish show at target opacity; others are hidden. 68 + * @param zFinish - mz threshold (opacity reaches target) 69 + * @param zStart - zoom level for this stop 70 + * @param opacity - target opacity (default 1) 71 + */ 72 + export const mzFade = ( 73 + zFinish: number, 74 + zStart: number, 75 + opacity: any = 1, 76 + ): [number, any[]] => [zStart, ['case', ['<=', MZ, zFinish], opacity, 0]] 77 + 78 + // ─── Legacy zoom stops ────────────────────────────────────────────────────── 79 + 80 + type Stop = [number, unknown] 81 + 82 + /** Legacy zoom function with optional exponential base */ 83 + export function stops(first: number | Stop, ...rest: Stop[]) { 84 + if (typeof first === 'number') { 85 + return { base: first, stops: rest } 86 + } 87 + return { stops: [first, ...rest] } 88 + } 89 + 90 + // ─── Filters ───────────────────────────────────────────────────────────────── 91 + 92 + /** Geometry type: LineString */ 93 + export const isLine: any[] = ['==', '$type', 'LineString'] 94 + /** Geometry type: Polygon */ 95 + export const isPoly: any[] = ['==', '$type', 'Polygon'] 96 + /** Geometry type: Point */ 97 + export const isPoint: any[] = ['==', '$type', 'Point'] 98 + 99 + /** Combine filters: `['all', ...conditions]` */ 100 + export const all = (...conditions: any[]): any[] => ['all', ...conditions] 101 + 102 + /** Equality filter: `['==', prop, value]` */ 103 + export const eq = (prop: string, value: unknown): any[] => ['==', prop, value] 104 + /** Inequality filter: `['!=', prop, value]` */ 105 + export const neq = (prop: string, value: unknown): any[] => ['!=', prop, value] 106 + /** Less-than-or-equal filter: `['<=', prop, value]` */ 107 + export const lte = (prop: string, value: unknown): any[] => ['<=', prop, value] 108 + /** Greater-than-or-equal filter: `['>=', prop, value]` */ 109 + export const gte = (prop: string, value: unknown): any[] => ['>=', prop, value] 110 + 111 + /** Membership filter: `['in', prop, ...values]` */ 112 + export const isIn = ( 113 + prop: string, 114 + ...values: unknown[] 115 + ): any[] => ['in', prop, ...values] 116 + /** Negated membership filter: `['!in', prop, ...values]` */ 117 + export const notIn = ( 118 + prop: string, 119 + ...values: unknown[] 120 + ): any[] => ['!in', prop, ...values] 121 + 122 + /** Property existence filter: `['has', prop]` */ 123 + export const has = (prop: string): any[] => ['has', prop] 124 + 125 + /** Expression-style membership: `['in', ['get', prop], ['literal', values]]` */ 126 + export const includes = ( 127 + prop: string, 128 + values: unknown[], 129 + ): any[] => ['in', ['get', prop], ['literal', values]]
+276 -391
www/utils/layers.ts
··· 3 3 * @reference https://github.com/protomaps/basemaps/issues/15#issuecomment-1436048491 4 4 * @reference https://github.com/openmaptiles/maptiler-basic-gl-style 5 5 */ 6 - const ALL = 'all' 7 - const LINE = ['==', '$type', 'LineString'] 8 - const POLYGON = ['==', '$type', 'Polygon'] 6 + import { 7 + all, 8 + coalesce, 9 + cond, 10 + eq, 11 + exp, 12 + get, 13 + gte, 14 + has, 15 + includes, 16 + isIn, 17 + isLine, 18 + isPoint, 19 + isPoly, 20 + linear, 21 + literal, 22 + lte, 23 + match, 24 + mzFade, 25 + neq, 26 + notIn, 27 + stops, 28 + } from './expr.ts' 29 + 9 30 const MINOR_ROAD = ['minor', 'primary', 'secondary', 'tertiary', 'trunk'] 10 - const MATCH = ['match', ['get', 'class']] 11 31 12 - const FONT_MED = ['literal', ['Noto Sans Medium']] 13 - const FONT_REG = ['literal', ['Noto Sans Regular']] 32 + const FONT_MED = literal(['Noto Sans Medium']) 33 + const FONT_REG = literal(['Noto Sans Regular']) 14 34 15 35 const BLACK = 'hsl(0, 0%, 0%)' 16 36 const WHITE = 'hsl(0, 0%, 100%)' ··· 21 41 const COLOR_GRASS_2 = 'hsl(70, 60%, 81%)' 22 42 const COLOR_RAIL = 'hsl(34, 12%, 66%)' 23 43 const COLOR_SAND = 'hsl(54, 81%, 53%)' 24 - const MZ = ['coalesce', ['get', 'mz'], 0] 25 44 26 - /** 27 - * Features with mz ≤ threshold show at target opacity; others are hidden. 28 - * @param zFinish - Opacity is 1 (or `opacity`) 29 - * @param zStart - Opacity is at 0 30 - * @param opacity - custom opacity to complete at 31 - */ 32 - const mzFade = ( 33 - zFinish: number, 34 - zStart: number, 35 - // deno-lint-ignore no-explicit-any 36 - opacity: any = 1, 37 - ) => [zStart, ['case', ['<=', MZ, zFinish], opacity, 0]] 38 - 39 - const WATER_OPACITY = ['case', ['==', ['get', 'intermittent'], 1], 0.7, 1] 45 + const WATER_OPACITY = cond(['==', get('intermittent'), 1], 0.7, 1) 40 46 const WATER_LINE = { 41 47 'line-color': COLOR_WATER, 42 - 'line-opacity': [ 43 - 'interpolate', 44 - ['linear'], 45 - ['zoom'], 46 - 8, 47 - 0, 48 - ...mzFade(8, 9), 49 - ...mzFade(11, 12), 50 - 13, 51 - 1, 52 - ], 53 - 'line-width': { 'base': 1.4, 'stops': [[8, 1], [20, 8]] }, 48 + 'line-opacity': linear([8, 0], mzFade(8, 9), mzFade(11, 12), [13, 1]), 49 + 'line-width': stops(1.4, [8, 1], [20, 8]), 54 50 } 55 51 56 52 export default function (sourceName: string) { ··· 61 57 'source': sourceName, 62 58 'source-layer': 'landuse', 63 59 'minzoom': 11, 64 - 'filter': [ALL, POLYGON, ['==', 'class', 'residential']], 60 + 'filter': all(isPoly, eq('class', 'residential')), 65 61 'layout': { 'visibility': 'visible' }, 66 62 'paint': { 'fill-color': 'hsl(47, 13%, 86%)', 'fill-opacity': 0.7 }, 67 63 }, ··· 70 66 'type': 'fill', 71 67 'source': sourceName, 72 68 'source-layer': 'landcover', 73 - 'filter': [ 74 - 'in', 75 - ['get', 'class'], 76 - ['literal', ['grass', 'wood', 'sand']], 77 - ], 69 + 'filter': includes('class', ['grass', 'wood', 'sand']), 78 70 'paint': { 79 - 'fill-color': [...MATCH, 'sand', COLOR_SAND, COLOR_GRASS], 80 - 'fill-opacity': [ 81 - 'interpolate', 82 - ['linear'], 83 - ['zoom'], 84 - 8, 85 - [...MATCH, 'grass', 0.45, 'wood', 0.6, 0.3], 86 - 22, 87 - [...MATCH, 'grass', 0.45, 'wood', 1, 0.3], 88 - ], 71 + 'fill-color': match('class', { sand: COLOR_SAND }, COLOR_GRASS), 72 + 'fill-opacity': linear( 73 + [8, match('class', { grass: 0.45, wood: 0.6 }, 0.3)], 74 + [22, match('class', { grass: 0.45, wood: 1 }, 0.3)], 75 + ), 89 76 }, 90 77 }, 91 78 { ··· 93 80 'type': 'fill', 94 81 'source': sourceName, 95 82 'source-layer': 'water', 96 - 'filter': [ALL, POLYGON, ['!=', 'brunnel', 'tunnel']], 83 + 'filter': all(isPoly, neq('brunnel', 'tunnel')), 97 84 'paint': { 98 85 'fill-color': COLOR_WATER, 99 - 'fill-opacity': [ 100 - 'interpolate', 101 - ['linear'], 102 - ['zoom'], 103 - ...mzFade(9, 10, WATER_OPACITY), 104 - ...mzFade(10, 11, WATER_OPACITY), 105 - ...mzFade(11, 12, WATER_OPACITY), 106 - ...mzFade(12, 13, WATER_OPACITY), 107 - ...mzFade(13, 14, WATER_OPACITY), 108 - 15, 109 - WATER_OPACITY, 110 - ], 86 + 'fill-opacity': linear( 87 + mzFade(9, 10, WATER_OPACITY), 88 + mzFade(10, 11, WATER_OPACITY), 89 + mzFade(11, 12, WATER_OPACITY), 90 + mzFade(12, 13, WATER_OPACITY), 91 + mzFade(13, 14, WATER_OPACITY), 92 + [15, WATER_OPACITY], 93 + ), 111 94 }, 112 95 }, 113 96 { ··· 115 98 'type': 'fill', 116 99 'source': sourceName, 117 100 'source-layer': 'landcover', 118 - 'filter': ['==', 'subclass', 'ice_shelf'], 101 + 'filter': eq('subclass', 'ice_shelf'), 119 102 'layout': { 'visibility': 'visible' }, 120 103 'paint': { 'fill-color': COLOR_ICE, 'fill-opacity': 0.8 }, 121 104 }, ··· 124 107 'type': 'fill', 125 108 'source': sourceName, 126 109 'source-layer': 'landcover', 127 - 'filter': ['==', 'subclass', 'glacier'], 110 + 'filter': eq('subclass', 'glacier'), 128 111 'layout': { 'visibility': 'visible' }, 129 112 'paint': { 130 113 'fill-color': COLOR_ICE_2, 131 - 'fill-opacity': { 'base': 1, 'stops': [[0, 1], [8, 0.5]] }, 114 + 'fill-opacity': stops(1, [0, 1], [8, 0.5]), 132 115 }, 133 116 }, 134 117 { ··· 136 119 'type': 'fill', 137 120 'source': sourceName, 138 121 'source-layer': 'landcover', 139 - 'filter': ['==', 'class', 'farmland'], 122 + 'filter': eq('class', 'farmland'), 140 123 'layout': { 'visibility': 'visible' }, 141 124 'paint': { 'fill-color': '#eae0d0' }, 142 125 }, ··· 145 128 'type': 'fill', 146 129 'source': sourceName, 147 130 'source-layer': 'park', 148 - 'filter': ['in', 'class', 'national_park', 'nature_reserve'], 131 + 'filter': isIn('class', 'national_park', 'nature_reserve'), 149 132 'paint': { 150 133 'fill-color': COLOR_GRASS_2, 151 - 'fill-opacity': { 'base': 1, 'stops': [[11, 0], [13, 0.75]] }, 134 + 'fill-opacity': stops(1, [11, 0], [13, 0.75]), 152 135 }, 153 136 }, 154 137 { ··· 156 139 'type': 'line', 157 140 'source': sourceName, 158 141 'source-layer': 'waterway', 159 - 'filter': [ALL, LINE, ['==', 'brunnel', 'tunnel']], 142 + 'filter': all(isLine, eq('brunnel', 'tunnel')), 160 143 'layout': { 'visibility': 'visible' }, 161 144 'paint': { 162 145 'line-color': COLOR_WATER, 163 146 'line-dasharray': [3, 3], 164 - 'line-gap-width': { 'stops': [[12, 0], [20, 6]] }, 147 + 'line-gap-width': stops([12, 0], [20, 6]), 165 148 'line-opacity': 1, 166 - 'line-width': { 'base': 1.4, 'stops': [[8, 1], [20, 2]] }, 149 + 'line-width': stops(1.4, [8, 1], [20, 2]), 167 150 }, 168 151 }, 169 152 { ··· 171 154 'type': 'line', 172 155 'source': sourceName, 173 156 'source-layer': 'waterway', 174 - 'filter': [ 175 - ALL, 176 - LINE, 177 - ['!in', 'brunnel', 'tunnel', 'bridge'], 178 - ['!=', 'intermittent', 1], 179 - ], 157 + 'filter': all( 158 + isLine, 159 + notIn('brunnel', 'tunnel', 'bridge'), 160 + neq('intermittent', 1), 161 + ), 180 162 'layout': { 'visibility': 'visible' }, 181 163 'paint': WATER_LINE, 182 164 }, ··· 185 167 'type': 'line', 186 168 'source': sourceName, 187 169 'source-layer': 'waterway', 188 - 'filter': [ 189 - ALL, 190 - LINE, 191 - ['!in', 'brunnel', 'tunnel', 'bridge'], 192 - ['==', 'intermittent', 1], 193 - ], 170 + 'filter': all( 171 + isLine, 172 + notIn('brunnel', 'tunnel', 'bridge'), 173 + eq('intermittent', 1), 174 + ), 194 175 'layout': { 'visibility': 'visible' }, 195 176 'paint': { ...WATER_LINE, 'line-dasharray': [2, 1] }, 196 177 }, ··· 200 181 'source': sourceName, 201 182 'source-layer': 'transportation', 202 183 'minzoom': 0, 203 - 'filter': [ 204 - ALL, 205 - LINE, 206 - ['==', 'brunnel', 'tunnel'], 207 - ['==', 'class', 'transit'], 208 - ], 184 + 'filter': all(isLine, eq('brunnel', 'tunnel'), eq('class', 'transit')), 209 185 'layout': { 'line-cap': 'butt', 'line-join': 'miter' }, 210 186 'paint': { 211 187 'line-color': COLOR_RAIL, 212 188 'line-dasharray': [3, 3], 213 - 'line-opacity': { 'base': 1, 'stops': [[8, 0], [16, 1]] }, 189 + 'line-opacity': stops(1, [8, 0], [16, 1]), 214 190 }, 215 191 }, 216 192 { ··· 221 197 'paint': { 222 198 'fill-antialias': true, 223 199 'fill-color': 'rgba(222, 211, 190, 1)', 224 - 'fill-opacity': [ 225 - 'interpolate', 226 - ['linear'], 227 - ['zoom'], 228 - 12, 229 - 0, 230 - ...mzFade(12, 13), 231 - ...mzFade(13, 14), 232 - 15, 233 - 1, 234 - ], 200 + 'fill-opacity': linear( 201 + [12, 0], 202 + mzFade(12, 13), 203 + mzFade(13, 14), 204 + [15, 1], 205 + ), 235 206 'fill-outline-color': 'rgba(212, 177, 146, 0.5)', 236 207 }, 237 208 }, ··· 241 212 'source': sourceName, 242 213 'source-layer': 'housenumber', 243 214 'minzoom': 17, 244 - 'filter': ['==', '$type', 'Point'], 215 + 'filter': isPoint, 245 216 'layout': { 246 217 'text-field': '{housenumber}', 247 218 'text-font': ['Noto Sans Regular'], ··· 256 227 'source': sourceName, 257 228 'source-layer': 'transportation', 258 229 'minzoom': 13, 259 - 'filter': [ALL, POLYGON, ['==', 'class', 'pier']], 230 + 'filter': all(isPoly, eq('class', 'pier')), 260 231 'layout': { 'visibility': 'visible' }, 261 232 'paint': { 'fill-antialias': true, 'fill-color': COLOR_ICE }, 262 233 }, ··· 267 238 'source': sourceName, 268 239 'source-layer': 'transportation', 269 240 'minzoom': 13, 270 - 'filter': [ALL, LINE, ['in', 'class', 'pier']], 241 + 'filter': all(isLine, isIn('class', 'pier')), 271 242 'layout': { 'line-cap': 'round', 'line-join': 'round' }, 272 243 'paint': { 273 244 'line-color': COLOR_ICE, 274 - 'line-width': { 'base': 1.2, 'stops': [[15, 1], [17, 4]] }, 245 + 'line-width': stops(1.2, [15, 1], [17, 4]), 275 246 }, 276 247 }, 277 248 { ··· 280 251 'source': sourceName, 281 252 'source-layer': 'transportation', 282 253 'minzoom': 12, 283 - 'filter': [ALL, POLYGON, ['in', 'brunnel', 'bridge']], 254 + 'filter': all(isPoly, isIn('brunnel', 'bridge')), 284 255 'layout': {}, 285 256 'paint': { 'fill-color': COLOR_ICE, 'fill-opacity': 0.5 }, 286 257 }, ··· 289 260 'type': 'line', 290 261 'source': sourceName, 291 262 'source-layer': 'transportation', 292 - 'filter': [ALL, LINE, ['in', 'class', 'path', 'track']], 263 + 'filter': all(isLine, isIn('class', 'path', 'track')), 293 264 'layout': { 'line-cap': 'square', 'line-join': 'bevel' }, 294 265 'paint': { 295 266 'line-color': 'hsl(0, 0%, 97%)', 296 267 'line-dasharray': [1, 1], 297 - 'line-width': { 'base': 1.55, 'stops': [[4, 0.25], [20, 10]] }, 268 + 'line-width': stops(1.55, [4, 0.25], [20, 10]), 298 269 }, 299 270 }, 300 271 { ··· 302 273 'type': 'line', 303 274 'source': sourceName, 304 275 'source-layer': 'transportation', 305 - 'filter': [ 306 - ALL, 307 - LINE, 308 - ['==', 'brunnel', 'tunnel'], 309 - ['in', 'class', ...MINOR_ROAD], 310 - ], 276 + 'filter': all( 277 + isLine, 278 + eq('brunnel', 'tunnel'), 279 + isIn('class', ...MINOR_ROAD), 280 + ), 311 281 'layout': { 'line-cap': 'butt', 'line-join': 'miter' }, 312 282 'paint': { 313 - 'line-color': [...MATCH, 'minor', '#efefef', WHITE], 283 + 'line-color': match('class', { minor: '#efefef' }, WHITE), 314 284 'line-dasharray': [0.28, 0.14], 315 - 'line-width': { 'base': 1.4, 'stops': [[6, 0.5], [20, 30]] }, 285 + 'line-width': stops(1.4, [6, 0.5], [20, 30]), 316 286 }, 317 287 }, 318 288 { ··· 322 292 'source': sourceName, 323 293 'source-layer': 'aeroway', 324 294 'minzoom': 4, 325 - 'filter': [ALL, POLYGON, ['in', 'class', 'runway', 'taxiway']], 295 + 'filter': all(isPoly, isIn('class', 'runway', 'taxiway')), 326 296 'layout': { 'visibility': 'visible' }, 327 297 'paint': { 328 298 'fill-color': WHITE, 329 - 'fill-opacity': { 'base': 1, 'stops': [[13, 0], [14, 1]] }, 299 + 'fill-opacity': stops(1, [13, 0], [14, 1]), 330 300 }, 331 301 }, 332 302 { ··· 336 306 'source': sourceName, 337 307 'source-layer': 'aeroway', 338 308 'minzoom': 12, 339 - 'filter': [ALL, ['in', 'class', 'taxiway'], LINE], 309 + 'filter': all(isIn('class', 'taxiway'), isLine), 340 310 'layout': { 341 311 'line-cap': 'round', 342 312 'line-join': 'round', ··· 345 315 'paint': { 346 316 'line-color': WHITE, 347 317 'line-opacity': 1, 348 - 'line-width': { 'base': 1.5, 'stops': [[12, 1], [17, 10]] }, 318 + 'line-width': stops(1.5, [12, 1], [17, 10]), 349 319 }, 350 320 }, 351 321 { ··· 355 325 'source': sourceName, 356 326 'source-layer': 'aeroway', 357 327 'minzoom': 4, 358 - 'filter': [ALL, ['in', 'class', 'runway'], LINE], 328 + 'filter': all(isIn('class', 'runway'), isLine), 359 329 'layout': { 360 330 'line-cap': 'round', 361 331 'line-join': 'round', ··· 364 334 'paint': { 365 335 'line-color': WHITE, 366 336 'line-opacity': 1, 367 - 'line-width': { 'base': 1.5, 'stops': [[11, 4], [17, 50]] }, 337 + 'line-width': stops(1.5, [11, 4], [17, 50]), 368 338 }, 369 339 }, 370 340 // Roads — merged minor through motorway. All use white/near-white fill. ··· 375 345 'type': 'line', 376 346 'source': sourceName, 377 347 'source-layer': 'transportation', 378 - 'filter': [ 379 - ALL, 380 - LINE, 381 - ['in', 'class', 'motorway', 'service', ...MINOR_ROAD], 348 + 'filter': all( 349 + isLine, 350 + isIn('class', 'motorway', 'service', ...MINOR_ROAD), 382 351 ['!has', 'brunnel'], 383 - ], 352 + ), 384 353 'layout': { 'line-cap': 'round', 'line-join': 'round' }, 385 354 'paint': { 386 355 'line-color': WHITE, 387 - 'line-opacity': [ 388 - 'interpolate', 389 - ['linear'], 390 - ['zoom'], 391 - 13, 392 - [...MATCH, 'tertiary', 0, 'minor', 0, 'service', 0, 1], 393 - 14, 394 - [...MATCH, 'minor', 0, 'service', 0, 1], 395 - 16, 396 - 1, 397 - ], 398 - 'line-width': [ 399 - 'interpolate', 400 - ['exponential', 1.4], 401 - ['zoom'], 402 - 6, 356 + 'line-opacity': linear( 357 + [13, match('class', { tertiary: 0, minor: 0, service: 0 }, 1)], 358 + [14, match('class', { minor: 0, service: 0 }, 1)], 359 + [16, 1], 360 + ), 361 + 'line-width': exp( 362 + 1.4, 363 + [ 364 + 6, 365 + match('class', { 366 + motorway: 0.5, 367 + trunk: 0.5, 368 + primary: 0.5, 369 + secondary: 0.5, 370 + tertiary: 0.5, 371 + }, 0), 372 + ], 373 + [8, match('class', { motorway: 1 }, 0.5)], 403 374 [ 404 - ...MATCH, 405 - 'motorway', 406 - 0.5, 407 - 'trunk', 408 - 0.5, 409 - 'primary', 410 - 0.5, 411 - 'secondary', 412 - 0.5, 413 - 'tertiary', 414 - 0.5, 415 - 0, 375 + 11, 376 + match('class', { 377 + motorway: 3, 378 + trunk: 2, 379 + primary: 2, 380 + secondary: 1.5, 381 + tertiary: 1.5, 382 + }, 0.5), 416 383 ], 417 - 8, 418 - [...MATCH, 'motorway', 1, 0.5], 419 - 11, 420 384 [ 421 - ...MATCH, 422 - 'motorway', 423 - 3, 424 - 'trunk', 425 - 2, 426 - 'primary', 427 - 2, 428 - 'secondary', 429 - 1.5, 430 - 'tertiary', 431 - 1.5, 432 - 0.5, 385 + 13, 386 + match('class', { 387 + motorway: 6, 388 + trunk: 4, 389 + primary: 4, 390 + secondary: 3, 391 + tertiary: 3, 392 + }, 2.5), 433 393 ], 434 - 13, 435 394 [ 436 - ...MATCH, 437 - 'motorway', 438 - 6, 439 - 'trunk', 440 - 4, 441 - 'primary', 442 - 4, 443 - 'secondary', 444 - 3, 445 - 'tertiary', 446 - 3, 447 - 2.5, 395 + 16, 396 + match('class', { 397 + motorway: 10, 398 + trunk: 14, 399 + primary: 14, 400 + secondary: 10, 401 + tertiary: 10, 402 + }, 12), 448 403 ], 449 - 16, 450 404 [ 451 - ...MATCH, 452 - 'motorway', 453 - 10, 454 - 'trunk', 455 - 14, 456 - 'primary', 457 - 14, 458 - 'secondary', 459 - 10, 460 - 'tertiary', 461 - 10, 462 - 12, 405 + 20, 406 + match('class', { motorway: 10, secondary: 20, tertiary: 20 }, 30), 463 407 ], 464 - 20, 465 - [...MATCH, 'motorway', 10, 'secondary', 20, 'tertiary', 20, 30], 466 - ], 408 + ), 467 409 }, 468 410 }, 469 411 { ··· 471 413 'type': 'line', 472 414 'source': sourceName, 473 415 'source-layer': 'transportation', 474 - 'filter': ['in', 'class', 'rail', 'transit'], 416 + 'filter': isIn('class', 'rail', 'transit'), 475 417 'paint': { 476 418 'line-color': COLOR_RAIL, 477 - 'line-opacity': [ 478 - 'interpolate', 479 - ['linear'], 480 - ['zoom'], 481 - 8, 482 - 0, 483 - ...mzFade(8, 9, 0.125), 484 - ...mzFade(9, 10, 0.25), 485 - ...mzFade(10, 11, 0.375), 486 - ...mzFade(11, 12, 0.5), 487 - ...mzFade(12, 13, 0.625), 488 - ...mzFade(13, 14, 0.75), 489 - ...mzFade(14, 15, 0.875), 490 - 16, 491 - 1, 492 - ], 419 + 'line-opacity': linear( 420 + [8, 0], 421 + mzFade(8, 9, 0.125), 422 + mzFade(9, 10, 0.25), 423 + mzFade(10, 11, 0.375), 424 + mzFade(11, 12, 0.5), 425 + mzFade(12, 13, 0.625), 426 + mzFade(13, 14, 0.75), 427 + mzFade(14, 15, 0.875), 428 + [16, 1], 429 + ), 493 430 }, 494 431 }, 495 432 { ··· 497 434 'type': 'line', 498 435 'source': sourceName, 499 436 'source-layer': 'waterway', 500 - 'filter': [ALL, LINE, ['==', 'brunnel', 'bridge']], 437 + 'filter': all(isLine, eq('brunnel', 'bridge')), 501 438 'layout': { 'line-cap': 'butt', 'line-join': 'miter' }, 502 439 'paint': { 503 440 'line-color': '#bbb', 504 - 'line-gap-width': { 'base': 1.55, 'stops': [[4, 0.25], [20, 30]] }, 505 - 'line-width': { 'base': 1.6, 'stops': [[12, 0.5], [20, 10]] }, 441 + 'line-gap-width': stops(1.55, [4, 0.25], [20, 30]), 442 + 'line-width': stops(1.6, [12, 0.5], [20, 10]), 506 443 }, 507 444 }, 508 445 { ··· 510 447 'type': 'line', 511 448 'source': sourceName, 512 449 'source-layer': 'waterway', 513 - 'filter': [ALL, LINE, ['==', 'brunnel', 'bridge']], 450 + 'filter': all(isLine, eq('brunnel', 'bridge')), 514 451 'layout': { 'line-cap': 'round', 'line-join': 'round' }, 515 452 'paint': { 516 453 'line-color': COLOR_WATER, 517 - 'line-width': { 'base': 1.55, 'stops': [[4, 0.25], [20, 30]] }, 454 + 'line-width': stops(1.55, [4, 0.25], [20, 30]), 518 455 }, 519 456 }, 520 457 { ··· 522 459 'type': 'line', 523 460 'source': sourceName, 524 461 'source-layer': 'transportation', 525 - 'filter': [ 526 - ALL, 527 - LINE, 528 - ['==', 'brunnel', 'bridge'], 529 - ['in', 'class', ...MINOR_ROAD], 530 - ], 462 + 'filter': all( 463 + isLine, 464 + eq('brunnel', 'bridge'), 465 + isIn('class', ...MINOR_ROAD), 466 + ), 531 467 'layout': { 'line-cap': 'butt', 'line-join': 'miter' }, 532 468 'paint': { 533 469 'line-color': '#dedede', 534 - 'line-gap-width': { 'base': 1.55, 'stops': [[4, 0.25], [20, 30]] }, 535 - 'line-opacity': [ 536 - 'interpolate', 537 - ['linear'], 538 - ['zoom'], 539 - 13, 540 - [...MATCH, 'tertiary', 0, 'minor', 0, 1], 541 - 14, 542 - [...MATCH, 'minor', 0, 1], 543 - 16, 544 - 1, 545 - ], 546 - 'line-width': { 'base': 1.6, 'stops': [[12, 0.5], [20, 10]] }, 470 + 'line-gap-width': stops(1.55, [4, 0.25], [20, 30]), 471 + 'line-opacity': linear( 472 + [13, match('class', { tertiary: 0, minor: 0 }, 1)], 473 + [14, match('class', { minor: 0 }, 1)], 474 + [16, 1], 475 + ), 476 + 'line-width': stops(1.6, [12, 0.5], [20, 10]), 547 477 }, 548 478 }, 549 479 { ··· 551 481 'type': 'line', 552 482 'source': sourceName, 553 483 'source-layer': 'transportation', 554 - 'filter': [ 555 - ALL, 556 - LINE, 557 - ['==', 'brunnel', 'bridge'], 558 - ['in', 'class', ...MINOR_ROAD], 559 - ], 484 + 'filter': all( 485 + isLine, 486 + eq('brunnel', 'bridge'), 487 + isIn('class', ...MINOR_ROAD), 488 + ), 560 489 'layout': { 'line-cap': 'round', 'line-join': 'round' }, 561 490 'paint': { 562 - 'line-color': [...MATCH, 'minor', '#efefef', WHITE], 563 - 'line-opacity': [ 564 - 'interpolate', 565 - ['linear'], 566 - ['zoom'], 567 - 13, 568 - [...MATCH, 'tertiary', 0, 'minor', 0, 1], 569 - 14, 570 - [...MATCH, 'minor', 0, 1], 571 - 16, 572 - 1, 573 - ], 574 - 'line-width': { 'base': 1.4, 'stops': [[6, 0.5], [20, 30]] }, 491 + 'line-color': match('class', { minor: '#efefef' }, WHITE), 492 + 'line-opacity': linear( 493 + [13, match('class', { tertiary: 0, minor: 0 }, 1)], 494 + [14, match('class', { minor: 0 }, 1)], 495 + [16, 1], 496 + ), 497 + 'line-width': stops(1.4, [6, 0.5], [20, 30]), 575 498 }, 576 499 }, 577 500 { ··· 579 502 'type': 'line', 580 503 'source': sourceName, 581 504 'source-layer': 'boundary', 582 - 'filter': ['in', 'admin_level', 4, 6, 8], 505 + 'filter': isIn('admin_level', 4, 6, 8), 583 506 'layout': { 'visibility': 'visible' }, 584 507 'paint': { 585 508 'line-color': 'hsla(0, 0%, 60%, 0.5)', ··· 594 517 'source': sourceName, 595 518 'source-layer': 'boundary', 596 519 'minzoom': 5, 597 - 'filter': [ALL, ['<=', 'admin_level', 2], LINE], 520 + 'filter': all(lte('admin_level', 2), isLine), 598 521 'layout': { 'line-cap': 'round', 'line-join': 'round' }, 599 522 'paint': { 600 523 'line-color': 'hsl(0, 0%, 60%)', 601 - 'line-width': { 'base': 1.3, 'stops': [[3, 0.5], [22, 15]] }, 524 + 'line-width': stops(1.3, [3, 0.5], [22, 15]), 602 525 }, 603 526 }, 604 527 { ··· 607 530 'source': sourceName, 608 531 'source-layer': 'poi', 609 532 'minzoom': 15, 610 - 'filter': [ALL, ['==', '$type', 'Point'], ['==', 'rank', 1]], 533 + 'filter': all(isPoint, eq('rank', 1)), 611 534 'layout': { 612 535 'text-anchor': 'top', 613 536 'text-field': '{name:latin}', ··· 630 553 'source': sourceName, 631 554 'source-layer': 'aerodrome_label', 632 555 'minzoom': 11, 633 - 'filter': [ALL, ['has', 'iata']], 556 + 'filter': has('iata'), 634 557 'layout': { 635 558 'text-anchor': 'center', 636 559 'text-field': '{name:latin}', ··· 652 575 'source': sourceName, 653 576 'source-layer': 'transportation_name', 654 577 'minzoom': 14, 655 - 'filter': LINE, 578 + 'filter': isLine, 656 579 'layout': { 657 580 'symbol-placement': 'line', 658 581 'text-field': '{name:latin}', 659 582 'text-font': ['Noto Sans Regular'], 660 583 'text-letter-spacing': 0.05, 661 584 'text-rotation-alignment': 'map', 662 - 'text-size': { 'base': 1, 'stops': [[13, 10], [16, 12]] }, 585 + 'text-size': stops(1, [13, 10], [16, 12]), 663 586 'visibility': 'visible', 664 587 }, 665 588 'paint': { ··· 678 601 'source-layer': 'place', 679 602 'minzoom': 11, 680 603 'maxzoom': 16, 681 - 'filter': [ 682 - ALL, 683 - ['==', '$type', 'Point'], 684 - ['in', 'class', 'city', 'town', 'suburb', 'neighbourhood', 'quarter'], 685 - ], 604 + 'filter': all( 605 + isPoint, 606 + isIn('class', 'city', 'town', 'suburb', 'neighbourhood', 'quarter'), 607 + ), 686 608 'layout': { 687 609 'text-field': '{name:latin}', 688 - 'text-font': [ 689 - 'case', 690 - ['==', ['get', 'class'], 'city'], 691 - FONT_MED, 692 - FONT_REG, 693 - ], 694 - 'text-max-width': [...MATCH, 'city', 10, 'town', 8, 6], 695 - 'text-size': [ 696 - 'interpolate', 697 - ['linear'], 698 - ['zoom'], 699 - 11, 700 - [...MATCH, 'city', 11, 0], 701 - 12, 702 - [...MATCH, 'city', 12, 'town', 10, 0], 703 - 13, 704 - [...MATCH, 'city', 13.5, 'town', 11, 10], 705 - 15, 706 - [...MATCH, 'city', 15, 'town', 13, 11], 707 - 16, 708 - [...MATCH, 'city', 15, 'town', 0, 12], 709 - ], 710 - 'text-transform': [ 711 - ...MATCH, 712 - 'suburb', 713 - 'uppercase', 714 - 'neighbourhood', 715 - 'uppercase', 716 - 'quarter', 717 - 'uppercase', 718 - 'none', 719 - ], 720 - 'text-letter-spacing': [ 721 - ...MATCH, 722 - 'suburb', 723 - 0.05, 724 - 'neighbourhood', 725 - 0.05, 726 - 'quarter', 727 - 0.05, 728 - 0, 729 - ], 730 - 'symbol-sort-key': ['coalesce', ['get', 'rank'], 10], 610 + 'text-font': cond(['==', get('class'), 'city'], FONT_MED, FONT_REG), 611 + 'text-max-width': match('class', { city: 10, town: 8 }, 6), 612 + 'text-size': linear( 613 + [11, match('class', { city: 11 }, 0)], 614 + [12, match('class', { city: 12, town: 10 }, 0)], 615 + [13, match('class', { city: 13.5, town: 11 }, 10)], 616 + [15, match('class', { city: 15, town: 13 }, 11)], 617 + [16, match('class', { city: 15, town: 0 }, 12)], 618 + ), 619 + 'text-transform': match('class', { 620 + suburb: 'uppercase', 621 + neighbourhood: 'uppercase', 622 + quarter: 'uppercase', 623 + }, 'none'), 624 + 'text-letter-spacing': match('class', { 625 + suburb: 0.05, 626 + neighbourhood: 0.05, 627 + quarter: 0.05, 628 + }, 0), 629 + 'symbol-sort-key': coalesce(get('rank'), 10), 731 630 'text-allow-overlap': false, 732 631 }, 733 632 'paint': { 734 - 'text-color': [ 735 - ...MATCH, 736 - 'city', 737 - BLACK, 738 - 'town', 739 - 'hsl(0, 0%, 30%)', 633 + 'text-color': match( 634 + 'class', 635 + { city: BLACK, town: 'hsl(0, 0%, 30%)' }, 740 636 'hsl(0, 0%, 45%)', 741 - ], 637 + ), 742 638 'text-halo-blur': 0, 743 - 'text-halo-color': [...MATCH, 'city', 'hsla(0, 0%, 100%, 0.75)', WHITE], 744 - 'text-halo-width': [...MATCH, 'city', 2, 'town', 1.5, 1], 639 + 'text-halo-color': match( 640 + 'class', 641 + { city: 'hsla(0, 0%, 100%, 0.75)' }, 642 + WHITE, 643 + ), 644 + 'text-halo-width': match('class', { city: 2, town: 1.5 }, 1), 745 645 }, 746 646 }, 747 647 { ··· 751 651 'source-layer': 'place', 752 652 'minzoom': 7, 753 653 'maxzoom': 12, 754 - 'filter': [ALL, ['==', '$type', 'Point'], ['==', 'class', 'country']], 654 + 'filter': all(isPoint, eq('class', 'country')), 755 655 'layout': { 756 656 'text-field': '{name:latin}', 757 - 'text-font': ['case', ['has', 'iso_a2'], FONT_MED, FONT_REG], 657 + 'text-font': cond(has('iso_a2'), FONT_MED, FONT_REG), 758 658 'text-max-width': 10, 759 - 'text-size': { 'stops': [[3, 12], [8, 22]] }, 659 + 'text-size': stops([3, 12], [8, 22]), 760 660 'visibility': 'visible', 761 661 }, 762 662 'paint': { ··· 777 677 * landcover — wood, wetland, sand, farmland, ice, rock, grass 778 678 * landuse — school, hospital, military, residential, commercial, etc. 779 679 * park — national_park, nature_reserve 780 - * boundary — administrative boundaries (admin_level 2–6) 680 + * boundary — administrative boundaries (admin_level 2-6) 781 681 * waterway — named rivers 782 682 * place — continent, country, state, city, town, village labels 783 683 * mountain_peak — peaks and volcanoes ··· 795 695 'type': 'fill', 796 696 'source': 'world', 797 697 'source-layer': 'landcover', 798 - 'filter': [ 799 - 'in', 800 - ['get', 'class'], 801 - ['literal', ['grass', 'wood', 'wetland', 'sand', 'rock', 'ice']], 802 - ], 698 + 'filter': includes('class', [ 699 + 'grass', 700 + 'wood', 701 + 'wetland', 702 + 'sand', 703 + 'rock', 704 + 'ice', 705 + ]), 803 706 'paint': { 804 - 'fill-color': [ 805 - ...MATCH, 806 - 'grass', 807 - COLOR_GRASS, 808 - 'wood', 809 - COLOR_GRASS, 810 - 'wetland', 811 - 'hsl(180, 25%, 78%)', 812 - 'sand', 813 - COLOR_SAND, 814 - 'rock', 815 - 'hsl(30, 8%, 72%)', 816 - 'ice', 817 - COLOR_ICE_2, 818 - COLOR_GRASS, 819 - ], 820 - 'fill-opacity': [ 821 - 'interpolate', 822 - ['linear'], 823 - ['zoom'], 824 - 0, 825 - [...MATCH, 'ice', 1, 'wood', 0.6, 'sand', 0.3, 'grass', 0.45, 0.5], 826 - 6, 827 - [...MATCH, 'ice', 0.75, 'wood', 0.6, 'sand', 0.3, 'grass', 0.45, 0.5], 828 - 9, 829 - [...MATCH, 'ice', 0.5, 'wood', 0.8, 'sand', 0.3, 'grass', 0.45, 0.5], 830 - ], 707 + 'fill-color': match('class', { 708 + grass: COLOR_GRASS, 709 + wood: COLOR_GRASS, 710 + wetland: 'hsl(180, 25%, 78%)', 711 + sand: COLOR_SAND, 712 + rock: 'hsl(30, 8%, 72%)', 713 + ice: COLOR_ICE_2, 714 + }, COLOR_GRASS), 715 + 'fill-opacity': linear( 716 + [0, match('class', { ice: 1, wood: 0.6, sand: 0.3, grass: 0.45 }, 0.5)], 717 + [ 718 + 6, 719 + match('class', { ice: 0.75, wood: 0.6, sand: 0.3, grass: 0.45 }, 0.5), 720 + ], 721 + [ 722 + 9, 723 + match('class', { ice: 0.5, wood: 0.8, sand: 0.3, grass: 0.45 }, 0.5), 724 + ], 725 + ), 831 726 }, 832 727 }, 833 728 ··· 848 743 'source-layer': 'park', 849 744 'paint': { 850 745 'fill-color': COLOR_GRASS_2, 851 - 'fill-opacity': { 'base': 1, 'stops': [[3, 0], [7, 0.75]] }, 746 + 'fill-opacity': stops(1, [3, 0], [7, 0.75]), 852 747 }, 853 748 }, 854 749 ··· 862 757 'paint': { 863 758 'line-color': COLOR_WATER, 864 759 'line-opacity': 1, 865 - 'line-width': { 'base': 1.4, 'stops': [[6, 0.5], [9, 3]] }, 760 + 'line-width': stops(1.4, [6, 0.5], [9, 3]), 866 761 }, 867 762 }, 868 763 ··· 872 767 'type': 'line', 873 768 'source': 'world', 874 769 'source-layer': 'boundary', 875 - 'filter': ['all', ['>=', 'admin_level', 3], ['<=', 'admin_level', 6]], 770 + 'filter': all(gte('admin_level', 3), lte('admin_level', 6)), 876 771 'minzoom': 3, 877 772 'paint': { 878 773 'line-color': 'hsla(0, 0%, 60%, 0.5)', ··· 885 780 'type': 'line', 886 781 'source': 'world', 887 782 'source-layer': 'boundary', 888 - 'filter': ['==', 'admin_level', 2], 783 + 'filter': eq('admin_level', 2), 889 784 'paint': { 890 785 'line-color': 'hsl(0, 0%, 60%)', 891 - 'line-width': { 'base': 1.3, 'stops': [[0, 0.5], [4, 1], [7, 1.5]] }, 786 + 'line-width': stops(1.3, [0, 0.5], [4, 1], [7, 1.5]), 892 787 }, 893 788 }, 894 789 ··· 898 793 'type': 'symbol', 899 794 'source': 'world', 900 795 'source-layer': 'place', 901 - 'filter': ['==', 'class', 'continent'], 796 + 'filter': eq('class', 'continent'), 902 797 'maxzoom': 3, 903 798 'layout': { 904 - 'text-field': ['coalesce', ['get', 'name:latin'], ['get', 'name']], 799 + 'text-field': coalesce(get('name:latin'), get('name')), 905 800 'text-font': ['Noto Sans Medium'], 906 801 'text-size': 13, 907 802 'text-transform': 'uppercase', ··· 918 813 'type': 'symbol', 919 814 'source': 'world', 920 815 'source-layer': 'place', 921 - 'filter': ['==', 'class', 'country'], 816 + 'filter': eq('class', 'country'), 922 817 'maxzoom': 7, 923 818 'layout': { 924 - 'text-field': ['coalesce', ['get', 'name:latin'], ['get', 'name']], 925 - 'text-font': ['case', ['has', 'iso_a2'], FONT_MED, FONT_REG], 819 + 'text-field': coalesce(get('name:latin'), get('name')), 820 + 'text-font': cond(has('iso_a2'), FONT_MED, FONT_REG), 926 821 'text-max-width': 10, 927 - 'text-size': { 'stops': [[3, 12], [8, 22]] }, 822 + 'text-size': stops([3, 12], [8, 22]), 928 823 }, 929 824 'paint': { 930 825 'text-color': 'hsl(0, 0%, 13%)', ··· 938 833 'type': 'symbol', 939 834 'source': 'world', 940 835 'source-layer': 'place', 941 - 'filter': ['in', 'class', 'state', 'province'], 836 + 'filter': isIn('class', 'state', 'province'), 942 837 'minzoom': 4, 943 838 'maxzoom': 8, 944 839 'layout': { 945 - 'text-field': ['coalesce', ['get', 'name:latin'], ['get', 'name']], 840 + 'text-field': coalesce(get('name:latin'), get('name')), 946 841 'text-font': ['Noto Sans Regular'], 947 842 'text-size': 10, 948 843 'text-transform': 'uppercase', ··· 961 856 'type': 'symbol', 962 857 'source': 'world', 963 858 'source-layer': 'place', 964 - 'filter': [ 965 - 'in', 966 - ['get', 'class'], 967 - ['literal', ['city', 'town', 'village']], 968 - ], 859 + 'filter': includes('class', ['city', 'town', 'village']), 969 860 'minzoom': 6, 970 861 'maxzoom': 11, 971 862 'layout': { 972 - 'text-field': ['coalesce', ['get', 'name:latin'], ['get', 'name']], 863 + 'text-field': coalesce(get('name:latin'), get('name')), 973 864 'text-font': ['Noto Sans Regular'], 974 - 'text-max-width': [...MATCH, 'city', 10, 6], 975 - 'text-size': [ 976 - 'interpolate', 977 - ['linear'], 978 - ['zoom'], 979 - 6, 980 - [...MATCH, 'city', 11, 'town', 10, 0], 981 - 8, 982 - [...MATCH, 'city', 12, 'town', 11, 10], 983 - 9, 984 - [...MATCH, 'city', 15, 'town', 14, 14], 985 - ], 865 + 'text-max-width': match('class', { city: 10 }, 6), 866 + 'text-size': linear( 867 + [6, match('class', { city: 11, town: 10 }, 0)], 868 + [8, match('class', { city: 12, town: 11 }, 10)], 869 + [9, match('class', { city: 15, town: 14 }, 14)], 870 + ), 986 871 }, 987 872 'paint': { 988 - 'text-color': [...MATCH, 'city', BLACK, 'hsl(0, 0%, 25%)'], 873 + 'text-color': match('class', { city: BLACK }, 'hsl(0, 0%, 25%)'), 989 874 'text-halo-blur': 0, 990 875 'text-halo-color': 'rgba(255, 255, 255, 0.75)', 991 876 'text-halo-width': 2,