protomapr/vignettes/labels-and-filters.Rmd
2026-03-06 15:46:39 +11:00

327 lines
9.3 KiB
Text

---
title: "Labels, Filters, and Feature Selection"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Labels, Filters, and Feature Selection}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
eval = FALSE
)
```
*Note: Code examples are not evaluated in this vignette. Copy and run them in your R console to see the interactive maps.*
## Introduction
Protomaps vector tiles contain rich metadata about each feature. This vignette shows how to use filters to selectively style and label features based on their properties.
## Understanding Feature Properties
Each feature in the vector tiles has a `props` object with metadata. Common properties include:
| Layer | Property | Values |
|-------|----------|--------|
| places | `kind` | "country", "region", "locality" |
| places | `min_zoom` | 1-15 (lower = more important) |
| roads | `kind` | "highway", "major_road", "minor_road", "path" |
| water | `kind` | "ocean", "lake", "river" |
| landuse | `kind` | "park", "forest", "residential", "industrial" |
## Hierarchical City Labels
Style cities by importance using `min_zoom`:
```{r}
library(leaflet)
library(protomapr)
leaflet() %>%
setView(lng = -100, lat = 40, zoom = 5) %>%
addProtomaps(
url = protomaps_url(),
flavor = "light",
labelRules = list(
# Capital cities and major metros (min_zoom 1-3)
pmLabelRule("places", pmCenteredTextSymbolizer(
font = "700 16px sans-serif",
fill = "#1a1a1a",
stroke = "white",
width = 3
), filter = "feature.props.kind === 'locality' && feature.props.min_zoom <= 3"),
# Large cities (min_zoom 4-5)
pmLabelRule("places", pmCenteredTextSymbolizer(
font = "600 13px sans-serif",
fill = "#333333",
stroke = "white",
width = 2
), filter = "feature.props.kind === 'locality' && feature.props.min_zoom > 3 && feature.props.min_zoom <= 5"),
# Medium cities (min_zoom 6-7)
pmLabelRule("places", pmCenteredTextSymbolizer(
font = "500 11px sans-serif",
fill = "#555555",
stroke = "white",
width = 2
), filter = "feature.props.kind === 'locality' && feature.props.min_zoom > 5 && feature.props.min_zoom <= 7"),
# States/provinces
pmLabelRule("places", pmCenteredTextSymbolizer(
font = "italic 300 14px sans-serif",
fill = "#888888",
stroke = "white",
width = 1
), filter = "feature.props.kind === 'region'")
)
)
```
## Road Hierarchy Styling
Style different road types with distinct colors and widths:
```{r}
leaflet() %>%
setView(lng = -122.4, lat = 37.78, zoom = 13) %>%
addProtomaps(
url = protomaps_url(),
paintRules = list(
# Base layers
pmPaintRule("earth", pmPolygonSymbolizer(fill = "#f5f5f5")),
pmPaintRule("water", pmPolygonSymbolizer(fill = "#cce0eb")),
pmPaintRule("landuse", pmPolygonSymbolizer(fill = "#e8f0e8"),
filter = "feature.props.kind === 'park'"),
# Highways - orange, thick
pmPaintRule("roads", pmLineSymbolizer(color = "#f59e0b", width = 4),
filter = "feature.props.kind === 'highway'"),
# Major roads - dark gray, medium
pmPaintRule("roads", pmLineSymbolizer(color = "#6b7280", width = 2.5),
filter = "feature.props.kind === 'major_road'"),
# Minor roads - light gray, thin
pmPaintRule("roads", pmLineSymbolizer(color = "#9ca3af", width = 1.5),
filter = "feature.props.kind === 'minor_road'"),
# Paths - dashed
pmPaintRule("roads", pmLineSymbolizer(color = "#d1d5db", width = 1, dash = c(3, 2)),
filter = "feature.props.kind === 'path'")
),
labelRules = list(
pmLabelRule("roads", pmLineLabelSymbolizer(
font = "600 10px sans-serif",
fill = "#f59e0b",
stroke = "white",
width = 2
), filter = "feature.props.kind === 'highway'", minzoom = 12),
pmLabelRule("roads", pmLineLabelSymbolizer(
font = "500 9px sans-serif",
fill = "#4b5563",
stroke = "white",
width = 2
), filter = "feature.props.kind === 'major_road'", minzoom = 14)
)
)
```
## Water Feature Labels
Style different water bodies appropriately:
```{r}
leaflet() %>%
setView(lng = -122.4, lat = 37.8, zoom = 10) %>%
addProtomaps(
url = protomaps_url(),
colors = pmColors(
earth = "#f0f0f0",
water = "#1e40af"
),
labelRules = list(
# Ocean labels - large, bold
pmLabelRule("water", pmCenteredTextSymbolizer(
font = "italic 700 18px sans-serif",
fill = "#1e3a5f",
stroke = "#e0f0ff",
width = 2,
lineHeight = 1.5
), filter = "feature.props.kind === 'ocean'"),
# Bay/gulf labels
pmLabelRule("water", pmCenteredTextSymbolizer(
font = "italic 500 14px sans-serif",
fill = "#1e4070",
stroke = "#e0f0ff",
width = 2,
lineHeight = 1.5
), filter = "feature.props.kind === 'bay' || feature.props.kind === 'gulf'"),
# Lakes
pmLabelRule("water", pmCenteredTextSymbolizer(
font = "italic 11px sans-serif",
fill = "#2050a0",
stroke = "#e0f0ff",
width = 1,
lineHeight = 1.5
), filter = "feature.props.kind === 'lake' || feature.props.kind === 'water'")
)
)
```
## Zoom-Dependent Visibility
Show different features at different zoom levels:
```{r}
leaflet() %>%
setView(lng = -122.4, lat = 37.78, zoom = 14) %>%
addProtomaps(
url = protomaps_url(),
paintRules = list(
pmPaintRule("earth", pmPolygonSymbolizer(fill = "#f8f8f8")),
pmPaintRule("water", pmPolygonSymbolizer(fill = "#d0e8f0")),
pmPaintRule("landuse", pmPolygonSymbolizer(fill = "#e0f0e0")),
# Buildings only visible at zoom 14+
pmPaintRule("buildings", pmPolygonSymbolizer(
fill = "#e0e0e0",
stroke = "#cccccc",
width = 0.5
), minzoom = 14),
# Roads
pmPaintRule("roads", pmLineSymbolizer(color = "#888888", width = 2))
),
labelRules = list(
# Neighborhoods at medium zoom
pmLabelRule("places", pmCenteredTextSymbolizer(
font = "600 12px sans-serif",
fill = "#444444",
stroke = "white",
width = 2
), filter = "feature.props.kind === 'neighbourhood'", minzoom = 13, maxzoom = 16),
# Street names at high zoom only
pmLabelRule("roads", pmLineLabelSymbolizer(
font = "500 9px sans-serif",
fill = "#666666",
stroke = "white",
width = 2
), minzoom = 15)
)
)
```
## POI (Points of Interest) Styling
Show specific categories of points of interest:
```{r}
leaflet() %>%
setView(lng = -122.4, lat = 37.78, zoom = 15) %>%
addProtomaps(
url = protomaps_url(),
flavor = "light",
paintRules = list(
# Restaurants
pmPaintRule("pois", pmCircleSymbolizer(
radius = 5,
fill = "#ef4444",
stroke = "white",
width = 1
), filter = "feature.props.kind === 'restaurant'"),
# Cafes
pmPaintRule("pois", pmCircleSymbolizer(
radius = 5,
fill = "#8b5cf6",
stroke = "white",
width = 1
), filter = "feature.props.kind === 'cafe'"),
# Parks
pmPaintRule("pois", pmCircleSymbolizer(
radius = 6,
fill = "#22c55e",
stroke = "white",
width = 1
), filter = "feature.props.kind === 'park'")
)
)
```
## Combining Multiple Filters
Use logical operators for complex filtering:
```{r}
leaflet() %>%
setView(lng = -122.4, lat = 37.78, zoom = 12) %>%
addProtomaps(
url = protomaps_url(),
colors = pmColors(earth = "#f5f5f5", water = "#ddeeff"),
labelRules = list(
# Large cities in California (using bounding box approximation)
pmLabelRule("places", pmCenteredTextSymbolizer(
font = "600 14px sans-serif",
fill = "#1a1a1a",
stroke = "white",
width = 2
), filter = "feature.props.kind === 'locality' && feature.props.min_zoom <= 6"),
# Exclude small water features
pmLabelRule("water", pmCenteredTextSymbolizer(
font = "italic 11px sans-serif",
fill = "#3366aa",
stroke = "#ddeeff",
width = 1,
lineHeight = 1.5
), filter = "feature.props.kind !== 'stream' && feature.props.kind !== 'river'")
)
)
```
## Filter Expression Reference
Filters are JavaScript expressions with access to `zoom` and `feature`:
```javascript
// Equality
feature.props.kind === 'highway'
// Inequality
feature.props.min_zoom <= 5
// Logical AND
feature.props.kind === 'locality' && feature.props.min_zoom <= 4
// Logical OR
feature.props.kind === 'lake' || feature.props.kind === 'reservoir'
// NOT
feature.props.kind !== 'path'
// Zoom-based (though minzoom/maxzoom params are preferred)
zoom >= 12 && feature.props.kind === 'minor_road'
// Check if property exists
feature.props.name !== undefined
```
## Tips
1. **Use minzoom/maxzoom params** instead of zoom filters when possible - they're more efficient
2. **Order matters**: Rules are applied in order; later rules can override earlier ones
3. **Test with the built-in flavors first** to understand which features appear at which zooms
4. **Check the console**: Invalid filter expressions log errors to the browser console