This commit is contained in:
evmo 2026-03-06 15:46:39 +11:00
commit 116abafc09
58 changed files with 5749 additions and 0 deletions

View file

@ -0,0 +1,231 @@
---
title: "Custom Map Styling"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Custom Map Styling}
%\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
protomapr provides extensive customization options for map styling. This vignette shows how to create a minimal basemap with custom colors and filtered labels.
## Simple Color Customization
Use `pmColors()` to override specific colors while keeping proper rendering:
```{r}
library(leaflet)
library(protomapr)
leaflet() %>%
setView(lng = -122.4, lat = 37.8, zoom = 12) %>%
addProtomaps(
url = protomaps_url(),
colors = pmColors(earth = "#f5f5f5", water = "#1a3a5c")
)
```
## Creating a Minimal Basemap
This example creates a clean, minimal map with:
- Uniform gray land (no parks, forests, or other land use distinctions)
- Dark blue water
- No roads or buildings
- Hierarchical city labels based on importance
- Styled water and island labels
```{r}
leaflet() %>%
setView(lng = -122.4, lat = 37.8, zoom = 8) %>%
addProtomaps(
url = protomaps_url(),
colors = pmColors(
# Base colors - all land features same gray
background = "#d3d3d3",
earth = "#d3d3d3",
water = "#1a3a5c",
# Land use - all matching background
park = "#d3d3d3",
wood = "#d3d3d3",
scrub_a = "#d3d3d3",
scrub_b = "#d3d3d3",
glacier = "#d3d3d3",
sand = "#d3d3d3",
beach = "#d3d3d3",
hospital = "#d3d3d3",
school = "#d3d3d3",
industrial = "#d3d3d3",
pedestrian = "#d3d3d3",
zoo = "#d3d3d3",
military = "#d3d3d3",
aerodrome = "#d3d3d3",
# Hide roads
highway = "#d3d3d3",
major = "#d3d3d3",
medium = "#d3d3d3",
minor = "#d3d3d3",
link = "#d3d3d3",
other = "#d3d3d3",
railway = "#d3d3d3",
boundary = "#d3d3d3",
pier = "#d3d3d3",
buildings = "#d3d3d3"
),
labelRules = list(
# States/regions - large italic
pmLabelRule("places", pmCenteredTextSymbolizer(
font = "italic 20px sans-serif",
fill = "#666",
stroke = "white",
width = 2
), filter = "feature.props.kind === 'region'"),
# Major metros (min_zoom <= 4: LA, SF, San Diego, Phoenix, Vegas)
pmLabelRule("places", pmCenteredTextSymbolizer(
font = "600 18px sans-serif",
fill = "#111",
stroke = "white",
width = 2
), filter = "feature.props.kind === 'locality' && feature.props.min_zoom <= 4"),
# Large cities (min_zoom 5-6: Fresno, Sacramento, Long Beach, etc.)
pmLabelRule("places", pmCenteredTextSymbolizer(
font = "600 14px sans-serif",
fill = "#222",
stroke = "white",
width = 2
), filter = "feature.props.kind === 'locality' && feature.props.min_zoom > 4 && feature.props.min_zoom <= 6"),
# Medium cities (min_zoom 7)
pmLabelRule("places", pmCenteredTextSymbolizer(
font = "600 12px sans-serif",
fill = "#333",
stroke = "white",
width = 2
), filter = "feature.props.kind === 'locality' && feature.props.min_zoom === 7"),
# Small towns (min_zoom >= 8)
pmLabelRule("places", pmCenteredTextSymbolizer(
font = "500 10px sans-serif",
fill = "#444",
stroke = "white",
width = 1
), filter = "feature.props.kind === 'locality' && feature.props.min_zoom >= 8"),
# Water labels
pmLabelRule("water", pmCenteredTextSymbolizer(
font = "italic 11px sans-serif",
fill = "#0d2840",
stroke = "#d3d3d3",
width = 1,
lineHeight = 1.5
)),
# Natural features
pmLabelRule("natural", pmCenteredTextSymbolizer(
font = "italic 11px sans-serif",
fill = "#444",
stroke = "white",
width = 1,
lineHeight = 1.5
)),
# Earth features (islands)
pmLabelRule("earth", pmCenteredTextSymbolizer(
font = "italic 11px sans-serif",
fill = "#444",
stroke = "white",
width = 1,
lineHeight = 1.5
))
)
)
```
## Understanding Label Filters
Labels are filtered using JavaScript expressions on feature properties:
### Place Properties
| Property | Description | Example Values |
|----------|-------------|----------------|
| `kind` | Feature type | "country", "region", "locality" |
| `kind_detail` | Specific type | "city", "town", "village", "state" |
| `min_zoom` | Importance (lower = more important) | LA=2, SF=3, Fresno=5 |
| `population_rank` | Population size | Higher = larger |
### Filter Examples
```{r}
# Only states/regions
filter = "feature.props.kind === 'region'"
# Only cities (not towns/villages)
filter = "feature.props.kind_detail === 'city'"
# Major cities only
filter = "feature.props.kind === 'locality' && feature.props.min_zoom <= 4"
# Medium-sized cities
filter = "feature.props.kind === 'locality' && feature.props.min_zoom > 4 && feature.props.min_zoom <= 6"
```
## Symbolizer Options
### Text Symbolizers
```{r}
pmCenteredTextSymbolizer(
font = "600 14px sans-serif", # CSS font (weight size family)
fill = "#222", # Text color
stroke = "white", # Halo color
width = 2, # Halo width
lineHeight = 1.5 # Line spacing for multi-word labels
)
```
### Font Specifications
Fonts follow CSS syntax: `"[weight] [style] size family"`
```{r}
# Bold
font = "600 14px sans-serif"
font = "bold 14px Arial"
# Italic
font = "italic 12px sans-serif"
# Bold italic
font = "italic 600 14px sans-serif"
```
## Layer Reference
| Layer | Description |
|-------|-------------|
| `earth` | Land polygons, islands |
| `water` | Water bodies |
| `places` | City/region labels |
| `natural` | Natural features |
| `landuse` | Parks, forests, etc. |
| `roads` | Streets and highways |
| `buildings` | Building footprints |
See `?protomaps_layers` for complete documentation.

View file

@ -0,0 +1,190 @@
---
title: "Basemaps for Data Visualization"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Basemaps for Data Visualization}
%\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
When overlaying data on maps, busy basemaps can distract from your visualization. This vignette shows how to create minimal, muted basemaps that let your data stand out.
## Ultra-Minimal: Land and Water Only
Strip everything except land and water boundaries using `pmMinimal()`:
```{r}
library(leaflet)
library(protomapr)
# Using the convenience function - just two lines!
leaflet() %>%
setView(lng = -122.4, lat = 37.8, zoom = 10) %>%
addProtomaps(url = protomaps_url(), style = pmMinimal())
# Custom colors
leaflet() %>%
setView(lng = -122.4, lat = 37.8, zoom = 10) %>%
addProtomaps(
url = protomaps_url(),
style = pmMinimal(land = "#f5f5f0", water = "#c8dce8")
)
```
## Subtle Context: Faint Roads
Keep roads barely visible for orientation without distraction:
```{r}
leaflet() %>%
setView(lng = -73.98, lat = 40.75, zoom = 12) %>%
addProtomaps(
url = protomaps_url(),
colors = pmColors(
background = "#ffffff",
earth = "#fafafa",
water = "#f0f4f8",
# Muted land use
park = "#f5f7f5",
wood = "#f5f7f5",
scrub_a = "#fafafa",
scrub_b = "#fafafa",
glacier = "#fafafa",
sand = "#fafafa",
beach = "#fafafa",
hospital = "#fafafa",
school = "#fafafa",
industrial = "#fafafa",
pedestrian = "#fafafa",
zoo = "#fafafa",
military = "#fafafa",
aerodrome = "#fafafa",
# Faint roads - just slightly darker than background
highway = "#e8e8e8",
major = "#efefef",
medium = "#f5f5f5",
minor = "#fafafa",
link = "#fafafa",
other = "#fafafa",
railway = "#f0f0f0",
pier = "#fafafa",
boundary = "#fafafa",
buildings = "#fafafa"
),
labelRules = list()
)
```
## Dark Mode for Bright Data
Dark basemaps make colorful data points pop. Use the `minimal-dark` preset:
```{r}
leaflet() %>%
setView(lng = -122.4, lat = 37.8, zoom = 11) %>%
addProtomaps(url = protomaps_url(), style = pmStyle("minimal-dark"))
```
## Muted with Major Labels Only
Sometimes you need city names for context but nothing else. Use `pmMinimal()` with `labels = TRUE`:
```{r}
leaflet() %>%
setView(lng = -100, lat = 40, zoom = 4) %>%
addProtomaps(
url = protomaps_url(),
style = pmMinimal(land = "#f5f5f0", water = "#d4e4ed", labels = TRUE)
)
# Or use the "muted" preset which includes subtle roads and labels
leaflet() %>%
setView(lng = -100, lat = 40, zoom = 4) %>%
addProtomaps(url = protomaps_url(), style = pmStyle("muted"))
```
## Choropleth-Friendly: No Fill Competition
For choropleth maps, you want boundaries visible but no competing fill colors:
```{r}
leaflet() %>%
setView(lng = -98, lat = 38, zoom = 4) %>%
addProtomaps(
url = protomaps_url(),
colors = pmColors(
background = "#ffffff",
earth = "#ffffff",
water = "#ffffff",
# Hide all land use
park = "#ffffff",
wood = "#ffffff",
scrub_a = "#ffffff",
scrub_b = "#ffffff",
glacier = "#ffffff",
sand = "#ffffff",
beach = "#ffffff",
hospital = "#ffffff",
school = "#ffffff",
industrial = "#ffffff",
pedestrian = "#ffffff",
zoo = "#ffffff",
military = "#ffffff",
aerodrome = "#ffffff",
# Hide roads
highway = "#ffffff",
major = "#ffffff",
medium = "#ffffff",
minor = "#ffffff",
link = "#ffffff",
other = "#ffffff",
railway = "#ffffff",
pier = "#ffffff",
buildings = "#ffffff",
# Keep boundaries visible
boundary = "#cccccc"
),
labelRules = list(
pmLabelRule("places", pmCenteredTextSymbolizer(
font = "600 11px sans-serif",
fill = "#333333",
stroke = "#ffffff",
width = 2
), filter = "feature.props.kind === 'region'")
)
)
```
## Watercolor Style
Soft, hand-drawn aesthetic using the `watercolor` preset:
```{r}
leaflet() %>%
setView(lng = -122.4, lat = 37.8, zoom = 12) %>%
addProtomaps(url = protomaps_url(), style = pmStyle("watercolor"))
```
## Tips for Data Visualization Basemaps
1. **Match your data colors**: If your data uses warm colors, use cool-toned basemaps (and vice versa)
2. **Consider accessibility**: Ensure sufficient contrast between your data and basemap
3. **Remove labels when zoomed in**: Dense point data doesn't need city labels competing for attention
4. **Use transparency**: Leaflet markers and shapes can use alpha values to blend with basemaps
5. **Test at all zoom levels**: A basemap that works at zoom 10 may be too busy at zoom 15

View file

@ -0,0 +1,258 @@
---
title: "Getting Started with protomapr"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Getting Started with protomapr}
%\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
protomapr adds [Protomaps](https://protomaps.com/) vector tile layers to Leaflet maps in R. Protomaps provides fast, customizable map tiles using the PMTiles format.
## Why Protomaps?
Standard Leaflet maps use `addProviderTiles()` to load raster tiles from services like OpenStreetMap, Stadia, or CartoDB. These work well but have limitations:
| Feature | Raster Tiles (providerTiles) | Vector Tiles (Protomaps) |
|---------|------------------------------|--------------------------|
| Customization | Limited to provider's styles | Full control over colors, labels, features |
| Self-hosting | Requires tile server infrastructure | Single PMTiles file, no server needed |
| File size | Large (pre-rendered images) | Small (compressed vectors) |
| Zoom transitions | Can appear pixelated | Smooth at any zoom level |
| Offline use | Difficult | Easy with local PMTiles file |
| Privacy | Requests go to third-party servers | Self-host for complete privacy |
| Rate limits | Often have API limits | No limits when self-hosted |
| Feature filtering | Not possible | Hide roads, buildings, labels, etc. |
### When to use Protomaps
- **Custom branded maps**: Match your organization's color scheme
- **Minimal basemaps**: Hide distracting features for data visualization overlays
- **Offline/embedded applications**: Bundle a PMTiles file with your app
- **Privacy-sensitive contexts**: No third-party tile requests
- **High-traffic applications**: Avoid API rate limits
### When providerTiles may be simpler
- Quick prototypes where default styling is fine
- Satellite/aerial imagery (Protomaps is vector-only)
- When you don't need customization
## Installation
```{r, eval = FALSE}
# Install from CRAN
install.packages("protomapr")
# Or install development version
# devtools::install_github("yourusername/protomapr")
```
## API Key Setup
Before using the Protomaps tile API, you need a free API key:
1. Sign up at https://protomaps.com/
2. Set your key for the session:
```{r}
library(leaflet)
library(protomapr)
# Set your API key (do this once per session)
set_protomaps_key("your-api-key-here")
# Or set via environment variable
Sys.setenv(PROTOMAPS_API_KEY = "your-api-key-here")
```
For persistent storage, add to your `.Renviron` file: `PROTOMAPS_API_KEY=your-key-here`
## Basic Usage
```{r}
# Create a map with default light theme
leaflet() %>%
setView(lng = -122.4, lat = 37.8, zoom = 12) %>%
addProtomaps(url = protomaps_url())
```
## Built-in Flavors
protomapr includes five built-in flavors:
```{r}
# Light (default)
leaflet() %>%
setView(lng = -122.4, lat = 37.8, zoom = 12) %>%
addProtomaps(url = protomaps_url(), flavor = "light")
# Dark
leaflet() %>%
setView(lng = -122.4, lat = 37.8, zoom = 12) %>%
addProtomaps(url = protomaps_url(), flavor = "dark")
# White (minimal, good for data visualization overlays)
leaflet() %>%
setView(lng = -122.4, lat = 37.8, zoom = 12) %>%
addProtomaps(url = protomaps_url(), flavor = "white")
# Grayscale
leaflet() %>%
setView(lng = -122.4, lat = 37.8, zoom = 12) %>%
addProtomaps(url = protomaps_url(), flavor = "grayscale")
# Black
leaflet() %>%
setView(lng = -122.4, lat = 37.8, zoom = 12) %>%
addProtomaps(url = protomaps_url(), flavor = "black")
```
## Data Sources
### Demo/Development
Use `protomaps_url()` for testing (uses Protomaps daily OpenStreetMap build):
```{r}
leaflet() %>%
addProtomaps(url = protomaps_url())
```
### Self-hosted (recommended for production)
For production use, self-host a PMTiles file. This eliminates API rate limits, ensures privacy, and enables offline use.
#### Getting PMTiles files
**Option 1: Regional extract (recommended)**
Use [Protomaps Slice](https://slice.openstreetmap.us/) to extract just the region you need:
1. Go to https://slice.openstreetmap.us/
2. Draw a bounding box around your area of interest
3. Download the resulting PMTiles file (typically 10-500MB depending on region size)
**Option 2: Daily world builds**
Full planet builds are available at https://build.protomaps.com/ (~100GB). Only recommended if you need global coverage.
**Option 3: pmtiles CLI**
For scripted workflows, use the [pmtiles CLI tool](https://github.com/protomaps/go-pmtiles):
```bash
# Install (requires Go)
go install github.com/protomaps/go-pmtiles/cmd/pmtiles@latest
# Extract a region from a larger file
pmtiles extract world.pmtiles california.pmtiles --bbox=-124.5,32.5,-114.1,42.0
```
#### Hosting options
**Cloud storage (simplest)**
Upload to S3, Google Cloud Storage, or Cloudflare R2. PMTiles supports HTTP range requests, so no special server is needed.
```{r}
# S3
leaflet() %>%
addProtomaps(url = "https://your-bucket.s3.amazonaws.com/tiles.pmtiles")
# Cloudflare R2
leaflet() %>%
addProtomaps(url = "https://your-bucket.r2.cloudflarestorage.com/tiles.pmtiles")
```
**Important:** Enable CORS on your bucket to allow browser requests. Example S3 CORS policy:
```json
[{
"AllowedOrigins": ["*"],
"AllowedMethods": ["GET", "HEAD"],
"AllowedHeaders": ["Range"],
"ExposeHeaders": ["Content-Length", "Content-Range"]
}]
```
**Local file (Shiny apps)**
For Shiny applications, serve PMTiles from `www/`:
```r
# In your Shiny app, place tiles.pmtiles in www/ folder
leaflet() %>%
addProtomaps(url = "tiles.pmtiles")
```
**Local development**
For local testing, use a simple HTTP server:
```bash
# Python
cd /path/to/pmtiles/folder
python -m http.server 8000
# Then in R
leaflet() %>%
addProtomaps(url = "http://localhost:8000/tiles.pmtiles")
```
## Preset Styles
For common use cases, protomapr provides preset styles that are simpler than the built-in flavors:
```{r}
# Ultra-minimal basemap for data visualization
leaflet() %>%
setView(lng = -122.4, lat = 37.8, zoom = 10) %>%
addProtomaps(url = protomaps_url(), style = pmMinimal())
# Minimal with city labels
leaflet() %>%
setView(lng = -122.4, lat = 37.8, zoom = 10) %>%
addProtomaps(url = protomaps_url(), style = pmMinimal(labels = TRUE))
# Dark minimal
leaflet() %>%
setView(lng = -122.4, lat = 37.8, zoom = 10) %>%
addProtomaps(url = protomaps_url(), style = pmStyle("minimal-dark"))
# Soft watercolor aesthetic
leaflet() %>%
setView(lng = -122.4, lat = 37.8, zoom = 10) %>%
addProtomaps(url = protomaps_url(), style = pmStyle("watercolor"))
# Muted with subtle roads
leaflet() %>%
setView(lng = -122.4, lat = 37.8, zoom = 10) %>%
addProtomaps(url = protomaps_url(), style = pmStyle("muted"))
```
### Available presets
| Preset | Description |
|--------|-------------|
| `pmMinimal()` | Hides roads, buildings, land use - shows only land and water |
| `pmStyle("minimal")` | Same as `pmMinimal()` |
| `pmStyle("minimal-dark")` | Dark version of minimal |
| `pmStyle("muted")` | Subtle colors, faint roads, major city labels |
| `pmStyle("watercolor")` | Soft, hand-painted aesthetic |
## Next Steps
See `vignette("custom-styling")` to learn how to customize colors, labels, and map features.
## Acknowledgments
This package wraps [protomaps-leaflet](https://github.com/protomaps/protomaps-leaflet), created by [Brandon Liu](https://bdon.org/). The Protomaps project provides an open-source stack for self-hosted vector maps, including the PMTiles format. Thanks to Brandon for making beautiful, customizable maps accessible to everyone.

View file

@ -0,0 +1,327 @@
---
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