From 116abafc094895b0d86e8f4fbdc68354efb24e96 Mon Sep 17 00:00:00 2001 From: Evan Morrison Date: Fri, 6 Mar 2026 15:46:39 +1100 Subject: [PATCH] init --- DESCRIPTION | 30 + LICENSE | 2 + NAMESPACE | 32 + NEWS.md | 14 + R/addProtomaps.R | 435 +++++++++++ R/colors.R | 147 ++++ R/layers.R | 109 +++ R/palette.R | 180 +++++ R/protomapr-package.R | 44 ++ R/rules.R | 106 +++ R/sample-tiles.R | 128 ++++ R/styles.R | 673 ++++++++++++++++++ R/symbolizers.R | 315 ++++++++ README.md | 351 +++++++++ _pkgdown.yml | 95 +++ cran-comments.md | 25 + .../protomaps-leaflet.js | 23 + man/addProtomaps.Rd | 107 +++ man/pmCenteredTextSymbolizer.Rd | 49 ++ man/pmCircleSymbolizer.Rd | 42 ++ man/pmCityLabels.Rd | 49 ++ man/pmColors.Rd | 96 +++ man/pmHideFeatures.Rd | 39 + man/pmLabelRule.Rd | 54 ++ man/pmLineLabelSymbolizer.Rd | 40 ++ man/pmLineSymbolizer.Rd | 54 ++ man/pmMinimal.Rd | 52 ++ man/pmModifyStyle.Rd | 62 ++ man/pmPaintRule.Rd | 54 ++ man/pmPalette.Rd | 61 ++ man/pmPaletteStyle.Rd | 57 ++ man/pmPolygonSymbolizer.Rd | 43 ++ man/pmShieldSymbolizer.Rd | 43 ++ man/pmStyle.Rd | 47 ++ man/pmTextSymbolizer.Rd | 48 ++ man/print.pm_style.Rd | 19 + man/protomapr-package.Rd | 73 ++ man/protomapsDependency.Rd | 23 + man/protomapsOptions.Rd | 28 + man/protomaps_clear_cache.Rd | 28 + man/protomaps_colors.Rd | 72 ++ man/protomaps_layers.Rd | 130 ++++ man/protomaps_sample_tiles.Rd | 54 ++ man/protomaps_url.Rd | 42 ++ man/set_protomaps_key.Rd | 33 + protomapr.Rproj | 21 + tests/testthat.R | 4 + tests/testthat/test-addProtomaps.R | 74 ++ tests/testthat/test-colors.R | 34 + tests/testthat/test-palette.R | 71 ++ tests/testthat/test-rules.R | 37 + tests/testthat/test-sample-tiles.R | 65 ++ tests/testthat/test-styles.R | 173 +++++ tests/testthat/test-symbolizers.R | 56 ++ vignettes/custom-styling.Rmd | 231 ++++++ vignettes/data-viz-basemaps.Rmd | 190 +++++ vignettes/getting-started.Rmd | 258 +++++++ vignettes/labels-and-filters.Rmd | 327 +++++++++ 58 files changed, 5749 insertions(+) create mode 100644 DESCRIPTION create mode 100644 LICENSE create mode 100644 NAMESPACE create mode 100644 NEWS.md create mode 100644 R/addProtomaps.R create mode 100644 R/colors.R create mode 100644 R/layers.R create mode 100644 R/palette.R create mode 100644 R/protomapr-package.R create mode 100644 R/rules.R create mode 100644 R/sample-tiles.R create mode 100644 R/styles.R create mode 100644 R/symbolizers.R create mode 100644 README.md create mode 100644 _pkgdown.yml create mode 100644 cran-comments.md create mode 100644 inst/htmlwidgets/lib/protomaps-leaflet-5.1.0/protomaps-leaflet.js create mode 100644 man/addProtomaps.Rd create mode 100644 man/pmCenteredTextSymbolizer.Rd create mode 100644 man/pmCircleSymbolizer.Rd create mode 100644 man/pmCityLabels.Rd create mode 100644 man/pmColors.Rd create mode 100644 man/pmHideFeatures.Rd create mode 100644 man/pmLabelRule.Rd create mode 100644 man/pmLineLabelSymbolizer.Rd create mode 100644 man/pmLineSymbolizer.Rd create mode 100644 man/pmMinimal.Rd create mode 100644 man/pmModifyStyle.Rd create mode 100644 man/pmPaintRule.Rd create mode 100644 man/pmPalette.Rd create mode 100644 man/pmPaletteStyle.Rd create mode 100644 man/pmPolygonSymbolizer.Rd create mode 100644 man/pmShieldSymbolizer.Rd create mode 100644 man/pmStyle.Rd create mode 100644 man/pmTextSymbolizer.Rd create mode 100644 man/print.pm_style.Rd create mode 100644 man/protomapr-package.Rd create mode 100644 man/protomapsDependency.Rd create mode 100644 man/protomapsOptions.Rd create mode 100644 man/protomaps_clear_cache.Rd create mode 100644 man/protomaps_colors.Rd create mode 100644 man/protomaps_layers.Rd create mode 100644 man/protomaps_sample_tiles.Rd create mode 100644 man/protomaps_url.Rd create mode 100644 man/set_protomaps_key.Rd create mode 100644 protomapr.Rproj create mode 100644 tests/testthat.R create mode 100644 tests/testthat/test-addProtomaps.R create mode 100644 tests/testthat/test-colors.R create mode 100644 tests/testthat/test-palette.R create mode 100644 tests/testthat/test-rules.R create mode 100644 tests/testthat/test-sample-tiles.R create mode 100644 tests/testthat/test-styles.R create mode 100644 tests/testthat/test-symbolizers.R create mode 100644 vignettes/custom-styling.Rmd create mode 100644 vignettes/data-viz-basemaps.Rmd create mode 100644 vignettes/getting-started.Rmd create mode 100644 vignettes/labels-and-filters.Rmd diff --git a/DESCRIPTION b/DESCRIPTION new file mode 100644 index 0000000..535d75c --- /dev/null +++ b/DESCRIPTION @@ -0,0 +1,30 @@ +Package: protomapr +Title: Add Protomaps Layers to Leaflet Maps +Version: 0.1.0 +Authors@R: c( + person("Evan", "Morrison", , "evan@p34.au", role = c("aut", "cre")), + person("Brandon", "Liu", role = "cph", + comment = "Author of protomaps-leaflet JavaScript library")) +Description: Provides functions to add Protomaps vector tile layers to leaflet + maps in R. Wraps the 'protomaps-leaflet' JavaScript library by Brandon Liu + . Supports PMTiles format, multiple built-in themes + (light, dark, white, grayscale, black), custom paint and label rules, and + various symbolizer types for styling map features. +License: MIT + file LICENSE +Encoding: UTF-8 +Roxygen: list(markdown = TRUE) +RoxygenNote: 7.3.2 +Imports: + leaflet (>= 2.0.0), + htmltools, + htmlwidgets, + jsonlite +Suggests: + testthat (>= 3.0.0), + knitr, + rmarkdown, + viridisLite, + RColorBrewer +VignetteBuilder: knitr +Config/testthat/edition: 3 +URL: https://github.com/evmo/protomapr diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1b4a713 --- /dev/null +++ b/LICENSE @@ -0,0 +1,2 @@ +YEAR: 2024 +COPYRIGHT HOLDER: protomapr authors diff --git a/NAMESPACE b/NAMESPACE new file mode 100644 index 0000000..0bdb99f --- /dev/null +++ b/NAMESPACE @@ -0,0 +1,32 @@ +# Generated by roxygen2: do not edit by hand + +S3method(print,pm_style) +export(addProtomaps) +export(pmCenteredTextSymbolizer) +export(pmCircleSymbolizer) +export(pmCityLabels) +export(pmColors) +export(pmHideFeatures) +export(pmLabelRule) +export(pmLineLabelSymbolizer) +export(pmLineSymbolizer) +export(pmMinimal) +export(pmModifyStyle) +export(pmPaintRule) +export(pmPalette) +export(pmPaletteStyle) +export(pmPolygonSymbolizer) +export(pmShieldSymbolizer) +export(pmStyle) +export(pmTextSymbolizer) +export(protomapsDependency) +export(protomapsOptions) +export(protomaps_clear_cache) +export(protomaps_demo_url) +export(protomaps_sample_tiles) +export(protomaps_url) +export(set_protomaps_key) +import(leaflet) +importFrom(htmltools,htmlDependency) +importFrom(htmlwidgets,onRender) +importFrom(jsonlite,toJSON) diff --git a/NEWS.md b/NEWS.md new file mode 100644 index 0000000..a2c4ede --- /dev/null +++ b/NEWS.md @@ -0,0 +1,14 @@ +# protomapr 0.1.0 + +* Initial CRAN release + +## Features + +* `addProtomaps()` - Add Protomaps vector tile layers to Leaflet maps +* Five built-in flavors: light, dark, white, grayscale, black +* `pmColors()` - Custom color overrides while preserving rendering rules +* Paint rules with `pmPaintRule()` for custom feature styling +* Label rules with `pmLabelRule()` for custom text labels +* Symbolizers for polygons, lines, circles, and text +* JavaScript filter expressions for feature-based styling +* Support for PMTiles format and tile API endpoints diff --git a/R/addProtomaps.R b/R/addProtomaps.R new file mode 100644 index 0000000..84d9fdf --- /dev/null +++ b/R/addProtomaps.R @@ -0,0 +1,435 @@ +# Package-level constants for base flavor colors +.baseFlavors <- list( + + light = list( + background = "#cccccc", earth = "#e0e0e0", park_a = "#cfddd5", park_b = "#9cd3b4", + hospital = "#e4dad9", industrial = "#d1dde1", school = "#e4ded7", + wood_a = "#d0ded0", wood_b = "#a0d9a0", pedestrian = "#e3e0d4", + scrub_a = "#cedcd7", scrub_b = "#99d2bb", glacier = "#e7e7e7", + sand = "#e2e0d7", beach = "#e8e4d0", aerodrome = "#dadbdf", + runway = "#e9e9ed", water = "#80deea", pier = "#e0e0e0", + zoo = "#c6dcdc", military = "#dcdcdc", ferry = "#5f9ea0", + boundary = "#aaaaaa", other = "#ebebeb", minor = "#ffffff", + link = "#ffffff", medium = "#f0eded", major = "#f5f5f5", + highway = "#ffffff", railway = "#a7b1b3", + ocean_label = "#5f9ea0", city_label = "#444444", + state_label = "#888888", country_label = "#666666" + ), + dark = list( + background = "#2d2d2d", earth = "#3d3d3d", park_a = "#3a4a40", park_b = "#4a5a50", + hospital = "#4a4040", industrial = "#3a4044", school = "#4a4540", + wood_a = "#3a4a3a", wood_b = "#4a5a4a", pedestrian = "#3a3a35", + scrub_a = "#3a4540", scrub_b = "#4a5550", glacier = "#4a4a4a", + sand = "#4a4840", beach = "#4a4840", aerodrome = "#3a3a40", + runway = "#4a4a50", water = "#28404a", pier = "#3d3d3d", + zoo = "#3a4545", military = "#3a3a3a", ferry = "#3a5a6a", + boundary = "#555555", other = "#4a4a4a", minor = "#4a4a4a", + link = "#4a4a4a", medium = "#5a5a5a", major = "#5a5a5a", + highway = "#6a6a6a", railway = "#555555", + ocean_label = "#5a8a9a", city_label = "#cccccc", + state_label = "#888888", country_label = "#aaaaaa" + ) +) + +#' Add a Protomaps layer to a Leaflet map +#' +#' @description +#' Adds a vector tile layer from a PMTiles source to a Leaflet map using +#' the protomaps-leaflet library. Supports built-in flavors and custom +#' styling rules. +#' +#' @param map A leaflet map object created with \code{\link[leaflet]{leaflet}}. +#' @param url Character. URL to a PMTiles file or a tile endpoint with +#' \code{{z}/{x}/{y}.mvt} placeholders. +#' @param style Optional style object created with \code{\link{pmMinimal}} or +#' \code{\link{pmStyle}}. Provides a convenient way to apply preset styles. +#' If provided, overrides \code{colors} and \code{labelRules}. +#' @param flavor Character. Built-in flavor/theme to use. One of "light", "dark", +#' "white", "grayscale", or "black". Default is "light". Ignored if \code{style} +#' is provided. +#' @param colors Optional list of color overrides. Use \code{\link{pmColors}} to +#' create this. Overrides specific colors while keeping built-in rendering rules. +#' @param paintRules Optional list of paint rules created with \code{\link{pmPaintRule}}. +#' If provided, completely overrides the flavor's default paint rules. +#' For simple color changes, use \code{colors} instead. +#' @param labelRules Optional list of label rules created with \code{\link{pmLabelRule}}. +#' If provided, completely overrides the flavor's default label rules. +#' @param backgroundColor Character. Background color for the canvas. +#' Default is NULL (uses flavor default). +#' @param lang Character. Language code for labels (e.g., "en", "de", "zh"). +#' Default is NULL (uses default language). +#' @param attribution Character. Attribution text for the layer. +#' Default is "Protomaps". +#' @param options A list of additional options created with \code{\link{protomapsOptions}}. +#' @param layerId Character. Layer ID for the protomaps layer. +#' @param group Character. Group name for layer control. +#' +#' @return A modified leaflet map object. +#' +#' @examples +#' \dontrun{ +#' library(leaflet) +#' library(protomapr) +#' +#' # Basic usage with demo tiles +#' leaflet() %>% +#' setView(lng = -122.4, lat = 37.8, zoom = 12) %>% +#' addProtomaps(url = protomaps_demo_url()) +#' +#' # Using dark flavor +#' leaflet() %>% +#' setView(lng = -122.4, lat = 37.8, zoom = 12) %>% +#' addProtomaps(url = protomaps_demo_url(), flavor = "dark") +#' +#' # Custom colors with proper rendering +#' leaflet() %>% +#' setView(lng = -122.4, lat = 37.8, zoom = 12) %>% +#' addProtomaps( +#' url = protomaps_demo_url(), +#' colors = pmColors(earth = "#d3d3d3", water = "#1a3a5c") +#' ) +#' +#' # Using preset styles (recommended for common use cases) +#' leaflet() %>% +#' setView(lng = -122.4, lat = 37.8, zoom = 10) %>% +#' addProtomaps(url = protomaps_demo_url(), style = pmStyle("minimal")) +#' +#' # Custom minimal style +#' leaflet() %>% +#' setView(lng = -122.4, lat = 37.8, zoom = 10) %>% +#' addProtomaps( +#' url = protomaps_demo_url(), +#' style = pmMinimal(land = "#f5f5f0", water = "#1a3a5c", labels = TRUE) +#' ) +#' } +#' +#' @export +#' @import leaflet +#' @importFrom htmlwidgets onRender +#' @importFrom jsonlite toJSON +addProtomaps <- function(map, + url, + style = NULL, + flavor = c("light", "dark", "white", "grayscale", "black"), + colors = NULL, + paintRules = NULL, + labelRules = NULL, + backgroundColor = NULL, + lang = NULL, + attribution = "Protomaps", + options = protomapsOptions(), + layerId = NULL, + group = NULL) { + + flavor <- match.arg(flavor) + + + # Handle style parameter + if (!is.null(style)) { + if (inherits(style, "pm_style")) { + colors <- style$colors + labelRules <- style$labelRules + } else if (is.list(style)) { + if (!is.null(style$colors)) colors <- style$colors + if (!is.null(style$labelRules)) labelRules <- style$labelRules + } + } + + # Build the options object for JavaScript + jsOptions <- list( + url = url, + flavor = flavor, + attribution = attribution + ) + + if (!is.null(colors)) { + jsOptions$customColors <- colors + jsOptions$baseFlavor <- .baseFlavors[[flavor]] %||% .baseFlavors$light + } + + if (!is.null(backgroundColor)) { + jsOptions$backgroundColor <- backgroundColor + } + + if (!is.null(lang)) { + jsOptions$lang <- lang + } + + if (!is.null(paintRules)) { + jsOptions$paintRules <- paintRules + } + + if (!is.null(labelRules)) { + jsOptions$labelRules <- labelRules + } + + # Merge additional options + jsOptions <- c(jsOptions, options) + + if (!is.null(layerId)) { + jsOptions$layerId <- layerId + } + + if (!is.null(group)) { + jsOptions$group <- group + } + + # Convert to JSON + jsOptionsJson <- jsonlite::toJSON(jsOptions, auto_unbox = TRUE, null = "null") + + # Add the protomaps dependency to the widget + map$dependencies <- c(map$dependencies, list(protomapsDependency())) + + # JavaScript code to add the layer + jsCode <- sprintf(" + function(el, x) { + var map = this; + var options = %s; + + // Helper to create symbolizer from config + var symTypes = { + polygon: protomapsL.PolygonSymbolizer, + line: protomapsL.LineSymbolizer, + circle: protomapsL.CircleSymbolizer, + text: protomapsL.TextSymbolizer, + centeredText: protomapsL.CenteredTextSymbolizer, + lineLabel: protomapsL.LineLabelSymbolizer, + shield: protomapsL.ShieldSymbolizer + }; + + function createSymbolizer(sym, defaultType) { + var Ctor = symTypes[sym.type] || symTypes[defaultType]; + return new Ctor(sym.options || {}); + } + + function processRule(rule, defaultSymType) { + var result = { + dataLayer: rule.dataLayer, + symbolizer: createSymbolizer(rule.symbolizer, defaultSymType) + }; + if (rule.minzoom !== null) result.minzoom = rule.minzoom; + if (rule.maxzoom !== null) result.maxzoom = rule.maxzoom; + if (rule.filter) { + try { + result.filter = new Function('zoom', 'feature', 'return ' + rule.filter); + } catch(e) { + console.error('Invalid filter:', rule.filter, e); + } + } + return result; + } + + // Build layer options + var layerOptions = { + url: options.url, + attribution: options.attribution + }; + + // Handle custom colors - merge with base flavor + if (options.customColors && options.baseFlavor && !options.paintRules) { + var customFlavor = Object.assign({}, options.baseFlavor, options.customColors); + layerOptions.paintRules = protomapsL.paintRules(customFlavor); + layerOptions.labelRules = []; + if (customFlavor.background) { + layerOptions.backgroundColor = customFlavor.background; + } + } + else if (options.flavor && !options.paintRules && !options.customColors) { + layerOptions.flavor = options.flavor; + } + + if (options.lang) layerOptions.lang = options.lang; + + if (options.paintRules) { + layerOptions.paintRules = options.paintRules.map(function(r) { + return processRule(r, 'polygon'); + }); + } + + if (options.labelRules) { + layerOptions.labelRules = options.labelRules.map(function(r) { + return processRule(r, 'centeredText'); + }); + } + + if (options.backgroundColor) { + layerOptions.backgroundColor = options.backgroundColor; + } + + if (options.maxDataZoom) { + layerOptions.maxDataZoom = options.maxDataZoom; + } + + if (options.tileSize) { + layerOptions.tileSize = options.tileSize; + } + + // Create and add the layer + var protoLayer = protomapsL.leafletLayer(layerOptions); + protoLayer.addTo(map); + + // Store reference for potential later use + if (options.layerId) { + if (!map._protomapsLayers) map._protomapsLayers = {}; + map._protomapsLayers[options.layerId] = protoLayer; + } + } + ", jsOptionsJson) + + htmlwidgets::onRender(map, jsCode) +} + + +#' Protomaps layer options +#' +#' @description +#' Create additional options for protomaps layer configuration. +#' +#' @param maxDataZoom Numeric. Maximum zoom level to fetch tile data. +#' Tiles beyond this zoom will be overzoomed. +#' @param tileSize Numeric. Size of tiles in pixels. Default is 256. +#' @param debug Logical. Enable debug mode to visualize tile boundaries. +#' @param ... Additional options passed to the layer. +#' +#' @return A list of options. +#' +#' @examples +#' protomapsOptions(maxDataZoom = 14, tileSize = 512) +#' +#' @export +protomapsOptions <- function(maxDataZoom = NULL, + tileSize = NULL, + debug = FALSE, + ...) { + opts <- list(...) + + if (!is.null(maxDataZoom)) opts$maxDataZoom <- maxDataZoom + if (!is.null(tileSize)) opts$tileSize <- tileSize + if (debug) opts$debug <- TRUE + + opts +} + + +#' Create Protomaps HTML dependency +#' +#' @description +#' Creates the HTML dependency for the protomaps-leaflet JavaScript library. +#' This is automatically included when using \code{\link{addProtomaps}}. +#' +#' @param version Character. Version of protomaps-leaflet to use. +#' Default is "5.1.0". +#' +#' @return An htmltools::htmlDependency object. +#' +#' @examples +#' protomapsDependency() +#' +#' @export +#' @importFrom htmltools htmlDependency +protomapsDependency <- function(version = "5.1.0") { + htmltools::htmlDependency( + name = "protomaps-leaflet", + version = version, + src = system.file( + sprintf("htmlwidgets/lib/protomaps-leaflet-%s", version), + package = "protomapr" + ), + script = "protomaps-leaflet.js" + ) +} + + +#' Get Protomaps API tile URL +#' +#' @description +#' Returns a URL template for the Protomaps tile API. Requires an API key, +#' which can be passed directly or set via the \code{PROTOMAPS_API_KEY} +#' environment variable. +#' +#' Get a free API key (for non-commercial use) at \url{https://protomaps.com/}. +#' For commercial use or high traffic, consider self-hosting PMTiles files. +#' +#' @param api_key Character. Your Protomaps API key. If NULL (default), +#' uses the \code{PROTOMAPS_API_KEY} environment variable. +#' +#' @return Character. URL template for the tile API. +#' +#' @examples +#' \dontrun{ +#' # Set your API key as an environment variable (recommended) +#' Sys.setenv(PROTOMAPS_API_KEY = "your-api-key-here") +#' leaflet() %>% +#' addProtomaps(url = protomaps_url()) +#' +#' # Or pass the key directly +#' leaflet() %>% +#' addProtomaps(url = protomaps_url(api_key = "your-api-key-here")) +#' } +#' +#' @seealso \code{\link{set_protomaps_key}} for a convenient way to set the API key. +#' +#' @export +protomaps_url <- function(api_key = NULL) { + if (is.null(api_key)) { + api_key <- Sys.getenv("PROTOMAPS_API_KEY", unset = "") + } + + if (api_key == "") { + stop( + "Protomaps API key required.\n", + "Get a free key at: https://protomaps.com/\n", + "Then either:\ +", + " + - Set PROTOMAPS_API_KEY environment variable: Sys.setenv(PROTOMAPS_API_KEY = 'your-key')\n", + " + - Or pass directly: protomaps_url(api_key = 'your-key')\n", + " + - Or self-host PMTiles: see vignette('getting-started')", + call. = FALSE + ) + } + + sprintf("https://api.protomaps.com/tiles/v4/{z}/{x}/{y}.mvt?key=%s", api_key) +} + + +#' Set Protomaps API key +#' +#' @description +#' Convenience function to set your Protomaps API key for the current session. +#' The key is stored in the \code{PROTOMAPS_API_KEY} environment variable. +#' +#' For persistent storage, add to your \code{.Renviron} file: +#' \code{PROTOMAPS_API_KEY=your-key-here} +#' +#' Get a free API key at \url{https://protomaps.com/}. +#' +#' @param api_key Character. Your Protomaps API key. +#' +#' @return Invisibly returns the API key. +#' +#' @examples +#' \dontrun{ +#' set_protomaps_key("your-api-key-here") +#' +#' # Now protomaps_url() will work without arguments +#' leaflet() %>% +#' addProtomaps(url = protomaps_url()) +#' } +#' +#' @export +set_protomaps_key <- function(api_key) { + Sys.setenv(PROTOMAPS_API_KEY = api_key) + message("Protomaps API key set for this session.") + invisible(api_key) +} + + +#' @rdname protomaps_url +#' @export +protomaps_demo_url <- function(api_key = NULL) { + .Deprecated("protomaps_url") + protomaps_url(api_key = api_key) +} diff --git a/R/colors.R b/R/colors.R new file mode 100644 index 0000000..22a066e --- /dev/null +++ b/R/colors.R @@ -0,0 +1,147 @@ +#' Create custom color overrides +#' +#' @description +#' Creates a list of color overrides that can be applied to a built-in flavor. +#' This is the recommended way to customize map colors while keeping the +#' proper rendering rules (zoom handling, polygon simplification, etc.). +#' +#' @param background Background color +#' @param earth Land/earth color +#' @param water Water color +#' @param park Park/green space color (also called park_a or park_b) +#' @param wood Forest/woodland color (also called wood_a or wood_b) +#' @param hospital Hospital area color +#' @param industrial Industrial area color +#' @param school School/university area color +#' @param beach Beach color +#' @param glacier Glacier color +#' @param highway Highway road color +#' @param major Major road color +#' @param minor Minor road color +#' @param city_label City label color +#' @param state_label State/region label color +#' @param country_label Country label color +#' @param ocean_label Ocean label color +#' @param ... Additional color properties +#' +#' @return A list of color overrides to pass to \code{\link{addProtomaps}}. +#' +#' @examples +#' # Simple earth and water colors +#' pmColors(earth = "#d3d3d3", water = "#1a3a5c") +#' +#' # Dark theme with custom colors +#' pmColors( +#' background = "#1a1a2e", +#' earth = "#1a1a2e", +#' water = "#16213e", +#' park = "#1f4037", +#' highway = "#4a4a6a" +#' ) +#' +#' # Minimal grayscale +#' pmColors( +#' background = "#ffffff", +#' earth = "#f5f5f5", +#' water = "#e0e0e0" +#' ) +#' +#' @seealso \code{\link{addProtomaps}}, \code{\link{protomaps_colors}} +#' @export +pmColors <- function(background = NULL, + earth = NULL, + water = NULL, + park = NULL, + wood = NULL, + hospital = NULL, + industrial = NULL, + school = NULL, + beach = NULL, + glacier = NULL, + highway = NULL, + major = NULL, + minor = NULL, + city_label = NULL, + state_label = NULL, + country_label = NULL, + ocean_label = NULL, + ...) { + + # Start with extra args, add named params, remove NULLs + colors <- c( + list(...), + list( + background = background, earth = earth, water = water, + hospital = hospital, industrial = industrial, school = school, + beach = beach, glacier = glacier, highway = highway, major = major, + city_label = city_label, state_label = state_label, + country_label = country_label, ocean_label = ocean_label, + # Expand paired colors + park_a = park, park_b = park, + wood_a = wood, wood_b = wood, + minor_a = minor, minor_b = minor + ) + ) + colors[!vapply(colors, is.null, logical(1))] +} + + +#' Protomaps Color Properties Reference +#' +#' @description +#' Reference documentation for all available color properties that can be +#' customized using \code{\link{pmColors}}. +#' +#' @section Base Colors: +#' \describe{ +#' \item{\code{background}}{Map background color} +#' \item{\code{earth}}{Land/terrain color} +#' \item{\code{water}}{Water bodies color} +#' } +#' +#' @section Land Use Colors: +#' \describe{ +#' \item{\code{park_a}, \code{park_b}}{Park colors (use \code{park} in pmColors)} +#' \item{\code{wood_a}, \code{wood_b}}{Forest/woodland colors (use \code{wood} in pmColors)} +#' \item{\code{hospital}}{Hospital areas} +#' \item{\code{industrial}}{Industrial zones} +#' \item{\code{school}}{Schools and universities} +#' \item{\code{beach}}{Beach areas} +#' \item{\code{zoo}}{Zoo areas} +#' \item{\code{aerodrome}}{Airport areas} +#' \item{\code{glacier}}{Glacier areas} +#' } +#' +#' @section Road Colors: +#' \describe{ +#' \item{\code{highway}}{Highway/motorway color} +#' \item{\code{major}}{Major road color} +#' \item{\code{minor_a}, \code{minor_b}}{Minor road colors (use \code{minor} in pmColors)} +#' \item{\code{railway}}{Railway lines} +#' \item{\code{pier}}{Pier/dock structures} +#' } +#' +#' @section Label Colors: +#' \describe{ +#' \item{\code{city_label}}{City name labels} +#' \item{\code{state_label}}{State/region labels} +#' \item{\code{country_label}}{Country name labels} +#' \item{\code{ocean_label}}{Ocean/sea labels} +#' \item{\code{roads_label_major}}{Major road name labels} +#' \item{\code{roads_label_minor}}{Minor road name labels} +#' } +#' +#' @section Landcover Colors (optional object): +#' These are specified as a nested object: +#' \describe{ +#' \item{\code{grassland}}{Grassland areas} +#' \item{\code{barren}}{Barren land} +#' \item{\code{urban_area}}{Urban zones} +#' \item{\code{farmland}}{Agricultural areas} +#' \item{\code{forest}}{Forest areas} +#' \item{\code{scrub}}{Scrubland} +#' } +#' +#' @name protomaps_colors +#' @seealso \code{\link{pmColors}}, \code{\link{addProtomaps}} +NULL diff --git a/R/layers.R b/R/layers.R new file mode 100644 index 0000000..33c41d7 --- /dev/null +++ b/R/layers.R @@ -0,0 +1,109 @@ +#' Protomaps Basemap Layers Reference +#' +#' @description +#' Reference documentation for the available layers and properties in the +#' Protomaps basemap. Use these layer names with \code{\link{pmPaintRule}} +#' and \code{\link{pmLabelRule}}, and filter on these properties. +#' +#' @section Layer Names: +#' The following layers are available for styling: +#' +#' \describe{ +#' \item{\code{earth}}{Land polygons} +#' \item{\code{water}}{Water polygons, lines, and label points} +#' \item{\code{landuse}}{Parks, forests, residential areas, etc.} +#' \item{\code{roads}}{Streets, highways, paths} +#' \item{\code{buildings}}{Building footprints and addresses} +#' \item{\code{places}}{City, town, and region labels} +#' \item{\code{pois}}{Points of interest} +#' \item{\code{boundaries}}{Administrative boundaries} +#' \item{\code{natural}}{Natural features like peaks, forests} +#' \item{\code{transit}}{Transit stations and lines} +#' } +#' +#' @section Places Layer Properties: +#' Use these in filter expressions like \code{filter = "feature.props.kind === 'locality'"} +#' +#' \describe{ +#' \item{\code{kind}}{Place type: "country", "region", "locality", "macrohood", "neighbourhood"} +#' \item{\code{kind_detail}}{Detailed type: "city", "town", "village", "hamlet", "state", "province", "country"} +#' \item{\code{name}}{Place name} +#' \item{\code{population}}{Population count (integer)} +#' \item{\code{population_rank}}{Population rank (integer, higher = larger)} +#' \item{\code{min_zoom}}{Minimum zoom level where label appears (lower = more important)} +#' \item{\code{capital}}{Capital status (string)} +#' \item{\code{wikidata}}{Wikidata ID} +#' } +#' +#' @section Water Layer Properties: +#' \describe{ +#' \item{\code{kind}}{Water type: "water", "lake", "playa", "ocean", "other"} +#' \item{\code{kind_detail}}{Detailed type: "basin", "canal", "ditch", "dock", "drain", "lake", "reservoir", "river", "riverbank", "stream"} +#' \item{\code{name}}{Water body name} +#' \item{\code{intermittent}}{Boolean, seasonal water} +#' \item{\code{reservoir}}{Boolean} +#' \item{\code{alkaline}}{Boolean} +#' } +#' +#' @section Roads Layer Properties: +#' \describe{ +#' \item{\code{kind}}{Road class: "highway", "major_road", "medium_road", "minor_road", "path"} +#' \item{\code{kind_detail}}{Detailed type: "motorway", "trunk", "primary", "secondary", "tertiary", "residential", "service", "pedestrian", "footway", "cycleway"} +#' \item{\code{ref}}{Road reference number (e.g., "I-80", "US-101")} +#' \item{\code{name}}{Street name} +#' \item{\code{oneway}}{Boolean} +#' \item{\code{is_bridge}}{Boolean} +#' \item{\code{is_tunnel}}{Boolean} +#' } +#' +#' @section Landuse Layer Properties: +#' \describe{ +#' \item{\code{kind}}{Land use type: "park", "forest", "residential", "commercial", "industrial", "aerodrome", "cemetery", "hospital", "school", "stadium", "zoo"} +#' \item{\code{sport}}{Sport type for sports facilities} +#' } +#' +#' @section Buildings Layer Properties: +#' \describe{ +#' \item{\code{kind}}{Building type: "address", "building", "building_part"} +#' \item{\code{height}}{Building height in meters} +#' \item{\code{min_height}}{Base height for building parts} +#' \item{\code{addr_housenumber}}{Street address number} +#' } +#' +#' @section POIs Layer Properties: +#' \describe{ +#' \item{\code{kind}}{POI type: "cafe", "restaurant", "hospital", "school", "bank", "pharmacy", "hotel", etc.} +#' \item{\code{name}}{POI name} +#' \item{\code{cuisine}}{Cuisine type for restaurants} +#' \item{\code{religion}}{Religion for places of worship} +#' } +#' +#' @section Filter Examples: +#' \preformatted{ +#' # Major cities only +#' filter = "feature.props.kind_detail === 'city'" +#' +#' # States/provinces +#' filter = "feature.props.kind === 'region'" +#' +#' # Important places (low min_zoom = important) +#' filter = "feature.props.min_zoom <= 6" +#' +#' # Large cities by population rank +#' filter = "feature.props.population_rank >= 10" +#' +#' # Highways only +#' filter = "feature.props.kind === 'highway'" +#' +#' # Parks +#' filter = "feature.props.kind === 'park'" +#' +#' # Combine conditions +#' filter = "feature.props.kind_detail === 'city' && feature.props.min_zoom <= 8" +#' } +#' +#' @name protomaps_layers +#' @aliases layers +#' @seealso \code{\link{pmPaintRule}}, \code{\link{pmLabelRule}} +#' @references \url{https://docs.protomaps.com/basemaps/layers} +NULL diff --git a/R/palette.R b/R/palette.R new file mode 100644 index 0000000..0dc3be6 --- /dev/null +++ b/R/palette.R @@ -0,0 +1,180 @@ +#' Apply Color Palette to Land Use Categories +#' +#' @description +#' Maps colors from a palette to land use categories, enabling use of +#' viridis, RColorBrewer, and other R color palettes with Protomaps. +#' +#' @param palette Character vector of colors, or a function that generates colors. +#' Can be output from viridis::viridis(), RColorBrewer::brewer.pal(), etc. +#' @param categories Character vector of category names to map colors to. +#' Default maps to common land use types: water, park, wood, residential, +#' commercial, industrial. +#' @param n Integer. Number of colors to generate if palette is a function. +#' Default is NULL (uses length of categories). +#' @param background Character. Background/default color for unassigned categories. +#' Default is "#f8f8f8". +#' +#' @return A list of color mappings suitable for pmColors() or addProtomaps(colors=). +#' +#' @examples +#' \dontrun{ +#' library(leaflet) +#' library(protomapr) +#' +#' # Using viridis palette for land use +#' if (requireNamespace("viridisLite", quietly = TRUE)) { +#' colors <- pmPalette(viridisLite::viridis(6)) +#' leaflet() %>% +#' setView(lng = -122.4, lat = 37.8, zoom = 12) %>% +#' addProtomaps(url = protomaps_url(), colors = colors) +#' } +#' +#' # Using RColorBrewer +#' if (requireNamespace("RColorBrewer", quietly = TRUE)) { +#' colors <- pmPalette(RColorBrewer::brewer.pal(6, "Set2")) +#' leaflet() %>% +#' setView(lng = -122.4, lat = 37.8, zoom = 12) %>% +#' addProtomaps(url = protomaps_url(), colors = colors) +#' } +#' +#' # Custom category mapping +#' colors <- pmPalette( +#' c("#264653", "#2a9d8f", "#e9c46a", "#f4a261", "#e76f51"), +#' categories = c("water", "park", "sand", "buildings", "highway") +#' ) +#' } +#' +#' @seealso \code{\link{pmPaletteStyle}}, \code{\link{pmColors}} +#' @export +pmPalette <- function(palette, + categories = NULL, + n = NULL, + background = "#f8f8f8") { + + if (is.null(categories)) { + categories <- c("water", "park", "wood", "residential", + "commercial", "industrial") + } + + if (is.function(palette)) { + n_colors <- n %||% length(categories) + colors <- palette(n_colors) + } else { + colors <- palette + } + + if (length(colors) < length(categories)) { + colors <- rep_len(colors, length(categories)) + } + + result <- list(background = background, earth = background) + + category_mapping <- list( + water = "water", + park = c("park_a", "park_b"), + wood = c("wood_a", "wood_b"), + forest = c("wood_a", "wood_b"), + residential = "pedestrian", + commercial = "other", + industrial = "industrial", + hospital = "hospital", + school = "school", + beach = "beach", + sand = "sand", + buildings = "buildings", + highway = "highway", + roads = c("highway", "major", "medium", "minor"), + railway = "railway" + ) + + for (i in seq_along(categories)) { + cat_name <- categories[i] + color <- colors[i] + + if (cat_name %in% names(category_mapping)) { + props <- category_mapping[[cat_name]] + for (prop in props) { + result[[prop]] <- color + } + } else { + result[[cat_name]] <- color + } + } + + result +} + + +#' Create a Themed Palette Style +#' +#' @description +#' Creates a complete pm_style using a color palette. Combines pmPalette() +#' with styling for a consistent look. +#' +#' @param palette Character vector of colors or palette function. +#' @param water_color Character. Color for water features. If NULL, uses +#' first color from palette. +#' @param land_color Character. Color for land/background. Default is "#f8f8f8". +#' @param labels Logical. Whether to include city labels. Default is TRUE. +#' @param label_color Character. Color for labels. Default is "#333333". +#' +#' @return A pm_style object. +#' +#' @examples +#' \dontrun{ +#' library(leaflet) +#' library(protomapr) +#' +#' # Viridis-themed map +#' if (requireNamespace("viridisLite", quietly = TRUE)) { +#' style <- pmPaletteStyle(viridisLite::viridis(5, option = "D")) +#' leaflet() %>% +#' setView(lng = -122.4, lat = 37.8, zoom = 12) %>% +#' addProtomaps(url = protomaps_url(), style = style) +#' } +#' +#' # Custom palette +#' style <- pmPaletteStyle( +#' c("#264653", "#2a9d8f", "#e9c46a", "#f4a261", "#e76f51"), +#' water_color = "#264653" +#' ) +#' } +#' +#' @seealso \code{\link{pmPalette}}, \code{\link{pmStyle}} +#' @export +pmPaletteStyle <- function(palette, + water_color = NULL, + land_color = "#f8f8f8", + labels = TRUE, + label_color = "#333333") { + + if (is.function(palette)) { + colors <- palette(5) + } else { + colors <- palette + } + + water <- water_color %||% colors[1] + + base_colors <- pmPalette(colors, background = land_color) + base_colors$water <- water + base_colors$earth <- land_color + base_colors$background <- land_color + + labelRules <- list() + if (labels) { + labelRules <- list( + pmLabelRule("places", pmCenteredTextSymbolizer( + font = "500 12px sans-serif", + fill = label_color, + stroke = land_color, + width = 2 + ), filter = "feature.props.kind === 'locality' && feature.props.min_zoom <= 6") + ) + } + + structure( + list(colors = base_colors, labelRules = labelRules), + class = "pm_style" + ) +} diff --git a/R/protomapr-package.R b/R/protomapr-package.R new file mode 100644 index 0000000..6e1e067 --- /dev/null +++ b/R/protomapr-package.R @@ -0,0 +1,44 @@ +#' protomapr: Add Protomaps Layers to Leaflet Maps +#' +#' The protomapr package provides functions to add Protomaps vector tile +#' layers to leaflet maps in R. Unlike raster tile providers, Protomaps +#' offers full customization of colors and features, self-hosting from a +#' single PMTiles file, and smooth vector rendering at any zoom level. +#' See \code{vignette("getting-started")} for why you might choose Protomaps +#' over standard provider tiles. +#' +#' @section Main Functions: +#' \describe{ +#' \item{\code{\link{addProtomaps}}}{Add a Protomaps layer to a Leaflet map} +#' \item{\code{\link{protomapsOptions}}}{Configure additional layer options} +#' } +#' +#' @section Symbolizers: +#' \describe{ +#' \item{\code{\link{pmPolygonSymbolizer}}}{Style polygon features} +#' \item{\code{\link{pmLineSymbolizer}}}{Style line features} +#' \item{\code{\link{pmCircleSymbolizer}}}{Style point features as circles} +#' \item{\code{\link{pmTextSymbolizer}}}{Add text labels} +#' \item{\code{\link{pmCenteredTextSymbolizer}}}{Add centered text labels} +#' \item{\code{\link{pmLineLabelSymbolizer}}}{Add labels along lines} +#' \item{\code{\link{pmShieldSymbolizer}}}{Add shield/badge labels} +#' } +#' +#' @section Rules: +#' \describe{ +#' \item{\code{\link{pmPaintRule}}}{Define how to paint features} +#' \item{\code{\link{pmLabelRule}}}{Define how to label features} +#' } +#' +#' @section Available Themes: +#' The following built-in themes are available: +#' \itemize{ +#' \item \code{"light"} - General-purpose light basemap +#' \item \code{"dark"} - General-purpose dark basemap +#' \item \code{"white"} - High-contrast white theme for data visualization +#' \item \code{"grayscale"} - Monochromatic theme +#' \item \code{"black"} - Dark theme for data visualization +#' } +#' +#' @keywords internal +"_PACKAGE" diff --git a/R/rules.R b/R/rules.R new file mode 100644 index 0000000..64a9e7e --- /dev/null +++ b/R/rules.R @@ -0,0 +1,106 @@ +#' Create a Paint Rule +#' +#' @description +#' Creates a paint rule that specifies how to render features from a +#' particular data layer. Paint rules control the visual appearance of +#' polygon, line, and point features. +#' +#' @param dataLayer Character. The name of the data layer in the vector +#' tile source (e.g., "water", "earth", "roads"). +#' @param symbolizer A symbolizer object created with one of the symbolizer +#' functions (e.g., \code{\link{pmPolygonSymbolizer}}, +#' \code{\link{pmLineSymbolizer}}). +#' @param minzoom Numeric. Minimum zoom level at which this rule applies. +#' Default is NULL (applies at all zoom levels). +#' @param maxzoom Numeric. Maximum zoom level at which this rule applies. +#' Default is NULL (applies at all zoom levels). +#' @param filter Character. A JavaScript expression string that filters +#' features. The expression has access to \code{zoom} and \code{feature} +#' variables. Default is NULL (no filter). +#' +#' @return A list representing the paint rule configuration. +#' +#' @examples +#' # Render water polygons in blue +#' pmPaintRule("water", pmPolygonSymbolizer(fill = "steelblue")) +#' +#' # Render roads with zoom-dependent visibility +#' pmPaintRule("roads", pmLineSymbolizer(color = "gray", width = 2), +#' minzoom = 10) +#' +#' # Filter to only show highways +#' pmPaintRule("roads", +#' pmLineSymbolizer(color = "orange", width = 4), +#' filter = "feature.props.kind === 'highway'") +#' +#' @export +pmPaintRule <- function(dataLayer, + symbolizer, + minzoom = NULL, + maxzoom = NULL, + filter = NULL) { + rule <- list( + dataLayer = dataLayer, + symbolizer = symbolizer + ) + + if (!is.null(minzoom)) rule$minzoom <- minzoom + if (!is.null(maxzoom)) rule$maxzoom <- maxzoom + if (!is.null(filter)) rule$filter <- filter + + rule +} + + +#' Create a Label Rule +#' +#' @description +#' Creates a label rule that specifies how to render text labels for +#' features from a particular data layer. Label rules control text +#' placement and styling, with automatic collision detection. +#' +#' @param dataLayer Character. The name of the data layer in the vector +#' tile source (e.g., "places", "roads"). +#' @param symbolizer A text symbolizer object created with one of +#' \code{\link{pmTextSymbolizer}}, \code{\link{pmCenteredTextSymbolizer}}, +#' \code{\link{pmLineLabelSymbolizer}}, or \code{\link{pmShieldSymbolizer}}. +#' @param minzoom Numeric. Minimum zoom level at which this rule applies. +#' Default is NULL (applies at all zoom levels). +#' @param maxzoom Numeric. Maximum zoom level at which this rule applies. +#' Default is NULL (applies at all zoom levels). +#' @param filter Character. A JavaScript expression string that filters +#' features. Default is NULL (no filter). +#' +#' @return A list representing the label rule configuration. +#' +#' @examples +#' # Label cities +#' pmLabelRule("places", +#' pmCenteredTextSymbolizer(font = "14px Arial", +#' fill = "black", +#' stroke = "white", +#' width = 2)) +#' +#' # Label streets along their paths +#' pmLabelRule("roads", +#' pmLineLabelSymbolizer(font = "11px Arial", +#' fill = "#333"), +#' minzoom = 14) +#' +#' @export +pmLabelRule <- function(dataLayer, + symbolizer, + minzoom = NULL, + maxzoom = NULL, + filter = NULL) { + rule <- list( + dataLayer = dataLayer, + symbolizer = symbolizer + ) + + if (!is.null(minzoom)) rule$minzoom <- minzoom + if (!is.null(maxzoom)) rule$maxzoom <- maxzoom + if (!is.null(filter)) rule$filter <- filter + + rule +} diff --git a/R/sample-tiles.R b/R/sample-tiles.R new file mode 100644 index 0000000..8970e7e --- /dev/null +++ b/R/sample-tiles.R @@ -0,0 +1,128 @@ +#' Get Path to Sample PMTiles File +#' +#' @description +#' Returns the path to a sample PMTiles file for demos and testing. +#' On first use, downloads a small regional extract to the user's cache +#' directory. +#' +#' @param region Character. Region to download. Currently only "sf-bay" +#' (San Francisco Bay Area) is available. Default is "sf-bay". +#' @param cache_dir Character. Directory to cache the downloaded file. +#' Default uses \code{tools::R_user_dir()}. +#' @param force_download Logical. Force re-download even if cached. +#' Default is FALSE. +#' +#' @return Character. Path to the PMTiles file. +#' +#' @details +#' The sample tiles are hosted on GitHub releases and downloaded on first use. +#' Subsequent calls use the cached file. The SF Bay Area extract is +#' approximately 10-15MB and covers the greater San Francisco region at all +#' zoom levels. +#' +#' For production use, consider self-hosting your own PMTiles file. See +#' \code{vignette("getting-started")} for options. +#' +#' @examples +#' \dontrun{ +#' library(leaflet) +#' library(protomapr) +#' +#' # Use sample tiles for demos (downloads on first use) +#' leaflet() %>% +#' setView(lng = -122.4, lat = 37.8, zoom = 12) %>% +#' addProtomaps(url = protomaps_sample_tiles()) +#' } +#' +#' @seealso \code{\link{protomaps_clear_cache}}, \code{\link{protomaps_url}} +#' @export +protomaps_sample_tiles <- function(region = "sf-bay", + cache_dir = NULL, + force_download = FALSE) { + + region <- match.arg(region, choices = c("sf-bay")) + + if (is.null(cache_dir)) { + cache_dir <- tools::R_user_dir("protomapr", which = "cache") + } + + if (!dir.exists(cache_dir)) { + dir.create(cache_dir, recursive = TRUE) + } + + filename <- sprintf("protomapr-sample-%s.pmtiles", region) + local_path <- file.path(cache_dir, filename) + + if (file.exists(local_path) && !force_download) { + message("Using cached sample tiles: ", local_path) + return(local_path) + } + + base_url <- "https://github.com/evmo/protomapr/releases/download" + version <- "sample-tiles-v1" + download_url <- sprintf("%s/%s/%s", base_url, version, filename) + + message("Downloading sample tiles (~10MB)...") + message("Source: ", download_url) + + tryCatch({ + utils::download.file( + url = download_url, + destfile = local_path, + mode = "wb", + quiet = FALSE + ) + message("Downloaded to: ", local_path) + }, error = function(e) { + stop( + "Failed to download sample tiles.\n", + "Error: ", conditionMessage(e), "\n", + "Try again later or use protomaps_url() with an API key instead.", + call. = FALSE + ) + }) + + local_path +} + + +#' Clear Cached Sample Tiles +#' +#' @description +#' Removes cached sample PMTiles files to free disk space. +#' +#' @param cache_dir Character. Cache directory. Default uses same as +#' \code{\link{protomaps_sample_tiles}}. +#' +#' @return Invisibly returns TRUE if files were removed, FALSE otherwise. +#' +#' @examples +#' \dontrun{ +#' # Clear all cached tiles +#' protomaps_clear_cache() +#' } +#' +#' @seealso \code{\link{protomaps_sample_tiles}} +#' @export +protomaps_clear_cache <- function(cache_dir = NULL) { + if (is.null(cache_dir)) { + cache_dir <- tools::R_user_dir("protomapr", which = "cache") + } + + if (!dir.exists(cache_dir)) { + message("Cache directory does not exist: ", cache_dir) + return(invisible(FALSE)) + } + + files <- list.files(cache_dir, pattern = "\\.pmtiles$", full.names = TRUE) + + if (length(files) == 0) { + message("No cached tiles found.") + return(invisible(FALSE)) + } + + unlink(files) + message(sprintf("Removed %d cached file(s).", length(files))) + + invisible(TRUE) +} diff --git a/R/styles.R b/R/styles.R new file mode 100644 index 0000000..b51a82c --- /dev/null +++ b/R/styles.R @@ -0,0 +1,673 @@ +#' Create a minimal basemap style +#' +#' @description +#' Creates a minimal style with uniform land color, hiding roads, buildings, +#' and most labels. Ideal for data visualization overlays. +#' +#' @param land Character. Color for all land features. Default is "#f8f8f8". +#' @param water Character. Color for water features. Default is "#e0e8f0". +#' @param labels Logical. Whether to show city labels. Default is FALSE. +#' @param label_color Character. Color for labels if shown. Default is "#666666". +#' +#' @return A list with `colors` and `labelRules` components to pass to +#' \code{\link{addProtomaps}}. +#' +#' @examples +#' \dontrun{ +#' library(leaflet) +#' library(protomapr) +#' +#' # Ultra-minimal basemap +#' style <- pmMinimal() +#' leaflet() %>% +#' setView(lng = -122.4, lat = 37.8, zoom = 10) %>% +#' addProtomaps(url = protomaps_url(), style = style) +#' +#' # Custom colors with major city labels +#' style <- pmMinimal(land = "#f5f5f0", water = "#1a3a5c", labels = TRUE) +#' leaflet() %>% +#' setView(lng = -122.4, lat = 37.8, zoom = 8) %>% +#' addProtomaps(url = protomaps_url(), style = style) +#' } +#' +#' @seealso \code{\link{pmStyle}}, \code{\link{addProtomaps}} +#' @export +pmMinimal <- function(land = "#f8f8f8", + water = "#e0e8f0", + labels = FALSE, + label_color = "#666666") { + + colors <- pmColors( + background = land, + earth = land, + water = water, + # Land use - all same as land + park = land, + wood = land, + scrub_a = land, + scrub_b = land, + glacier = land, + sand = land, + beach = land, + hospital = land, + school = land, + industrial = land, + pedestrian = land, + zoo = land, + military = land, + aerodrome = land, + # Roads - hidden + highway = land, + major = land, + medium = land, + minor = land, + link = land, + other = land, + railway = land, + pier = land, + boundary = land, + # Buildings - hidden + buildings = land + ) + + labelRules <- list() + if (labels) { + labelRules <- list( + # Major cities only + pmLabelRule("places", pmCenteredTextSymbolizer( + font = "500 12px sans-serif", + fill = label_color, + stroke = land, + width = 2 + ), filter = "feature.props.kind === 'locality' && feature.props.min_zoom <= 6") + ) + } + + structure( + list(colors = colors, labelRules = labelRules), + class = "pm_style" + ) +} + + +#' Get a preset map style +#' +#' @description +#' Returns a preset style configuration. Available presets provide common +#' styling patterns without manual configuration. +#' +#' @param name Character. Name of the preset style. One of: +#' \describe{ +#' \item{"minimal"}{Light gray land, light blue water, no labels} +#' \item{"minimal-dark"}{Dark land, dark blue water, no labels} +#' \item{"muted"}{Subtle colors, faint roads, major labels only} +#' \item{"watercolor"}{Soft, painterly aesthetic} +#' \item{"ink"}{Black lines on white, like a pen drawing} +#' \item{"terrain"}{Earthy tones with subtle elevation feel} +#' \item{"transit"}{Muted base with emphasized rail lines} +#' } +#' +#' @return A list with style components to pass to \code{\link{addProtomaps}}. +#' +#' @examples +#' \dontrun{ +#' library(leaflet) +#' library(protomapr) +#' +#' leaflet() %>% +#' setView(lng = -122.4, lat = 37.8, zoom = 10) %>% +#' addProtomaps(url = protomaps_url(), style = pmStyle("minimal")) +#' +#' leaflet() %>% +#' setView(lng = -122.4, lat = 37.8, zoom = 10) %>% +#' addProtomaps(url = protomaps_url(), style = pmStyle("watercolor")) +#' } +#' +#' @seealso \code{\link{pmMinimal}}, \code{\link{addProtomaps}} +#' @export +pmStyle <- function(name = c("minimal", "minimal-dark", "muted", "watercolor", + "ink", "terrain", "transit")) { + name <- match.arg(name) + + switch(name, + "minimal" = pmMinimal(), + + "minimal-dark" = pmMinimal(land = "#1a1a1a", water = "#0d1520", label_color = "#888888"), + + "muted" = { + colors <- pmColors( + background = "#fafafa", + earth = "#fafafa", + water = "#e8f0f4", + park = "#f0f4f0", + wood = "#eef2ee", + scrub_a = "#f5f5f5", + scrub_b = "#f5f5f5", + glacier = "#f8f8f8", + sand = "#f8f6f2", + beach = "#f8f6f2", + hospital = "#fafafa", + school = "#fafafa", + industrial = "#f5f5f5", + pedestrian = "#f8f8f8", + zoo = "#f0f4f0", + military = "#f5f5f5", + aerodrome = "#f5f5f5", + # Faint roads + highway = "#e0e0e0", + major = "#e8e8e8", + medium = "#f0f0f0", + minor = "#f5f5f5", + link = "#f5f5f5", + other = "#f8f8f8", + railway = "#e8e8e8", + pier = "#f0f0f0", + boundary = "#e8e8e8", + buildings = "#f0f0f0" + ) + labelRules <- list( + pmLabelRule("places", pmCenteredTextSymbolizer( + font = "500 11px sans-serif", + fill = "#666666", + stroke = "#fafafa", + width = 2 + ), filter = "feature.props.kind === 'locality' && feature.props.min_zoom <= 6"), + pmLabelRule("places", pmCenteredTextSymbolizer( + font = "italic 10px sans-serif", + fill = "#888888", + stroke = "#fafafa", + width = 1 + ), filter = "feature.props.kind === 'region'") + ) + structure(list(colors = colors, labelRules = labelRules), class = "pm_style") + }, + + "watercolor" = { + bg <- "#f4f1ea" + colors <- pmColors( + background = bg, + earth = bg, + water = "#a8c8d4", + park = "#d4e6c3", + wood = "#c8ddb8", + scrub_a = "#dce8d0", + scrub_b = "#d0e0c4", + glacier = "#e8f0f4", + sand = "#f0e8d8", + beach = "#f5e6c8", + hospital = "#f0ebe4", + school = "#f0ebe4", + industrial = "#ebe6de", + pedestrian = "#f0ece4", + zoo = "#dce8d0", + military = "#e8e4dc", + aerodrome = "#ebe6de", + highway = "#e8e4dc", + major = "#ebe7df", + medium = "#f0ece4", + minor = bg, + link = bg, + other = bg, + railway = "#e0dcd4", + pier = "#e8e4dc", + boundary = "#e0dcd4", + buildings = "#e8e4dc" + ) + labelRules <- list( + pmLabelRule("places", pmCenteredTextSymbolizer( + font = "italic 13px Georgia, serif", + fill = "#5c5c5c", + stroke = bg, + width = 2 + ), filter = "feature.props.min_zoom <= 6"), + pmLabelRule("water", pmCenteredTextSymbolizer( + font = "italic 11px Georgia, serif", + fill = "#4a7c8c", + stroke = "#a8c8d4", + width = 1, + lineHeight = 1.5 + )) + ) + structure(list(colors = colors, labelRules = labelRules), class = "pm_style") + }, + + "ink" = { + bg <- "#ffffff" + colors <- pmColors( + background = bg, + earth = bg, + water = bg, + park = bg, wood = bg, scrub_a = bg, scrub_b = bg, + glacier = bg, sand = bg, beach = bg, + hospital = bg, school = bg, industrial = bg, + pedestrian = bg, zoo = bg, military = bg, aerodrome = bg, + highway = "#000000", + major = "#000000", + medium = "#333333", + minor = "#666666", + link = "#666666", + other = "#999999", + railway = "#000000", + pier = "#666666", + boundary = "#cccccc", + buildings = bg + ) + labelRules <- list( + pmLabelRule("places", pmCenteredTextSymbolizer( + font = "500 12px 'Courier New', monospace", + fill = "#000000", + stroke = bg, + width = 2 + ), filter = "feature.props.kind === 'locality' && feature.props.min_zoom <= 6") + ) + structure(list(colors = colors, labelRules = labelRules), class = "pm_style") + }, + + "terrain" = { + bg <- "#f4f0e8" + colors <- pmColors( + background = bg, + earth = bg, + water = "#a8c8d8", + park = "#d4e4c8", + wood = "#c8dcc0", + scrub_a = "#e0e8d8", + scrub_b = "#d8e0d0", + glacier = "#e8f0f4", + sand = "#f0e8d4", + beach = "#f4ecd8", + hospital = "#f0ece8", + school = "#f0ece8", + industrial = "#e8e4dc", + pedestrian = "#f0ece4", + zoo = "#d8e4d0", + military = "#e4e0d8", + aerodrome = "#e8e4dc", + highway = "#d8d4c8", + major = "#e0dcd0", + medium = "#e8e4d8", + minor = "#f0ece4", + link = "#f0ece4", + other = bg, + railway = "#c8c4b8", + pier = "#e0dcd0", + boundary = "#c8c4b8", + buildings = "#e8e4dc" + ) + labelRules <- list( + pmLabelRule("places", pmCenteredTextSymbolizer( + font = "500 12px sans-serif", + fill = "#5a5040", + stroke = bg, + width = 2 + ), filter = "feature.props.kind === 'locality' && feature.props.min_zoom <= 6"), + pmLabelRule("natural", pmCenteredTextSymbolizer( + font = "italic 10px sans-serif", + fill = "#6a6050", + stroke = bg, + width = 1 + )) + ) + structure(list(colors = colors, labelRules = labelRules), class = "pm_style") + }, + + "transit" = { + bg <- "#fafafa" + colors <- pmColors( + background = bg, + earth = bg, + water = "#e0e8f0", + park = "#f0f4f0", + wood = "#eef2ee", + scrub_a = "#f5f5f5", + scrub_b = "#f5f5f5", + glacier = "#f8f8f8", + sand = "#f8f6f2", + beach = "#f8f6f2", + hospital = bg, + school = bg, + industrial = "#f5f5f5", + pedestrian = "#f8f8f8", + zoo = "#f0f4f0", + military = "#f5f5f5", + aerodrome = "#f0f0f4", + highway = "#e8e8e8", + major = "#f0f0f0", + medium = "#f5f5f5", + minor = bg, + link = bg, + other = bg, + railway = "#e63946", + pier = "#f0f0f0", + boundary = "#e8e8e8", + buildings = "#f0f0f0" + ) + labelRules <- list( + pmLabelRule("places", pmCenteredTextSymbolizer( + font = "500 11px sans-serif", + fill = "#333333", + stroke = bg, + width = 2 + ), filter = "feature.props.kind === 'locality' && feature.props.min_zoom <= 6"), + pmLabelRule("transit", pmCenteredTextSymbolizer( + font = "600 10px sans-serif", + fill = "#e63946", + stroke = bg, + width = 2 + )) + ) + structure(list(colors = colors, labelRules = labelRules), class = "pm_style") + } + ) +} + + +#' Create color overrides to hide specific features +#' +#' @description +#' Creates color settings that hide specified feature categories by making +#' them match the background color. +#' +#' @param features Character vector. Features to hide. Options include: +#' "roads", "buildings", "landuse", "boundaries", "labels". +#' @param background Character. Background color that hidden features will +#' match. Default is "#f8f8f8". +#' +#' @return A list of color overrides to pass to \code{\link{pmColors}} or +#' merge with other colors. +#' +#' @examples +#' \dontrun{ +#' library(leaflet) +#' library(protomapr) +#' +#' # Hide roads and buildings but keep parks visible +#' hidden <- pmHideFeatures(c("roads", "buildings")) +#' leaflet() %>% +#' setView(lng = -122.4, lat = 37.8, zoom = 12) %>% +#' addProtomaps( +#' url = protomaps_url(), +#' colors = modifyList(pmColors(water = "#1a3a5c"), hidden) +#' ) +#' } +#' +#' @export +pmHideFeatures <- function(features, background = "#f8f8f8") { + colors <- list() + + if ("roads" %in% features) { + colors <- c(colors, list( + highway = background, major = background, medium = background, + minor = background, link = background, other = background, + railway = background, pier = background + )) + } + + if ("buildings" %in% features) { + colors <- c(colors, list(buildings = background)) + } + + if ("landuse" %in% features) { + colors <- c(colors, list( + park_a = background, park_b = background, + wood_a = background, wood_b = background, + scrub_a = background, scrub_b = background, + glacier = background, sand = background, beach = background, + hospital = background, school = background, industrial = background, + pedestrian = background, zoo = background, military = background, + aerodrome = background + )) + } + + if ("boundaries" %in% features) { + colors <- c(colors, list(boundary = background)) + } + + colors +} + + +#' Create preset label rules for city names +#' +#' @description +#' Creates label rules for displaying city/place names with common styling +#' patterns. +#' +#' @param style Character. Label style preset: +#' \describe{ +#' \item{"hierarchical"}{Size varies by city importance (min_zoom)} +#' \item{"major-only"}{Only major cities (min_zoom <= 5)} +#' \item{"all"}{All cities with uniform styling} +#' } +#' @param color Character. Text color. Default is "#333333". +#' @param halo Character. Halo/stroke color. Default is "white". +#' @param include_regions Logical. Include state/region labels. Default is TRUE. +#' +#' @return A list of label rules to pass to \code{\link{addProtomaps}}. +#' +#' @examples +#' \dontrun{ +#' library(leaflet) +#' library(protomapr) +#' +#' leaflet() %>% +#' setView(lng = -122.4, lat = 37.8, zoom = 8) %>% +#' addProtomaps( +#' url = protomaps_url(), +#' colors = pmColors(earth = "#f0f0f0", water = "#1a3a5c"), +#' labelRules = pmCityLabels("hierarchical") +#' ) +#' } +#' +#' @export +pmCityLabels <- function(style = c("hierarchical", "major-only", "all"), + color = "#333333", + halo = "white", + include_regions = TRUE) { + style <- match.arg(style) + + rules <- switch(style, + "hierarchical" = list( + pmLabelRule("places", pmCenteredTextSymbolizer( + font = "600 16px sans-serif", fill = color, stroke = halo, width = 2 + ), filter = "feature.props.kind === 'locality' && feature.props.min_zoom <= 4"), + pmLabelRule("places", pmCenteredTextSymbolizer( + font = "600 13px sans-serif", fill = color, stroke = halo, width = 2 + ), filter = "feature.props.kind === 'locality' && feature.props.min_zoom > 4 && feature.props.min_zoom <= 6"), + pmLabelRule("places", pmCenteredTextSymbolizer( + font = "500 11px sans-serif", fill = color, stroke = halo, width = 2 + ), filter = "feature.props.kind === 'locality' && feature.props.min_zoom > 6 && feature.props.min_zoom <= 8"), + pmLabelRule("places", pmCenteredTextSymbolizer( + font = "400 9px sans-serif", fill = color, stroke = halo, width = 1 + ), filter = "feature.props.kind === 'locality' && feature.props.min_zoom > 8") + ), + + "major-only" = list( + pmLabelRule("places", pmCenteredTextSymbolizer( + font = "600 14px sans-serif", fill = color, stroke = halo, width = 2 + ), filter = "feature.props.kind === 'locality' && feature.props.min_zoom <= 5") + ), + + "all" = list( + pmLabelRule("places", pmCenteredTextSymbolizer( + font = "500 11px sans-serif", fill = color, stroke = halo, width = 2 + ), filter = "feature.props.kind === 'locality'") + ) + ) + + if (include_regions) { + rules <- c(rules, list( + pmLabelRule("places", pmCenteredTextSymbolizer( + font = "italic 12px sans-serif", + fill = "#888888", + stroke = halo, + width = 1 + ), filter = "feature.props.kind === 'region'") + )) + } + + rules +} + + +#' Print method for pm_style objects +#' +#' @description +#' Prints a formatted summary of a pm_style object showing colors and label rules. +#' +#' @param x A pm_style object. +#' @param ... Additional arguments (ignored). +#' +#' @return Invisibly returns x. +#' +#' @export +print.pm_style <- function(x, ...) { + cat("\n") + + if (!is.null(x$colors) && length(x$colors) > 0) { + cat("\nColors:\n") + + base_names <- c("background", "earth", "water") + road_names <- c("highway", "major", "medium", "minor", "link", "other", "railway", "pier") + landuse_names <- c("park", "park_a", "park_b", "wood", "wood_a", "wood_b", + "scrub_a", "scrub_b", "glacier", "sand", "beach", + "hospital", "school", "industrial", "pedestrian", + "zoo", "military", "aerodrome") + + # Base colors + base <- x$colors[names(x$colors) %in% base_names] + if (length(base) > 0) { + cat(" Base:\n") + for (nm in intersect(base_names, names(base))) { + cat(sprintf(" %s: %s\n", nm, base[[nm]])) + } + } + + # Roads - summarize if uniform + roads <- x$colors[names(x$colors) %in% road_names] + if (length(roads) > 0) { + unique_colors <- unique(unlist(roads)) + if (length(unique_colors) == 1) { + cat(sprintf(" Roads: %s (uniform)\n", unique_colors)) + } else { + cat(" Roads:\n") + for (nm in intersect(road_names, names(roads))) { + cat(sprintf(" %s: %s\n", nm, roads[[nm]])) + } + } + } + + # Land use - summarize if uniform + landuse <- x$colors[names(x$colors) %in% landuse_names] + if (length(landuse) > 0) { + unique_colors <- unique(unlist(landuse)) + if (length(unique_colors) == 1) { + cat(sprintf(" Land use: %s (uniform)\n", unique_colors)) + } else { + n_landuse <- length(landuse) + cat(sprintf(" Land use: %d custom colors\n", n_landuse)) + } + } + + # Other colors + other_names <- setdiff(names(x$colors), c(base_names, road_names, landuse_names)) + if (length(other_names) > 0) { + cat(sprintf(" Other: %d additional colors\n", length(other_names))) + } + } + + if (!is.null(x$labelRules) && length(x$labelRules) > 0) { + cat(sprintf("\nLabel Rules: %d\n", length(x$labelRules))) + for (i in seq_along(x$labelRules)) { + rule <- x$labelRules[[i]] + cat(sprintf(" [%d] %s", i, rule$dataLayer)) + if (!is.null(rule$filter)) { + filter_display <- if (nchar(rule$filter) > 40) { + paste0(substr(rule$filter, 1, 37), "...") + } else { + rule$filter + } + cat(sprintf(" | %s", filter_display)) + } + cat("\n") + } + } else { + cat("\nLabel Rules: none\n") + } + + invisible(x) +} + + +#' Modify an Existing Style +#' +#' @description +#' Creates a new pm_style by modifying an existing one. Useful for tweaking +#' preset styles without rebuilding from scratch. +#' +#' @param style A pm_style object to modify. +#' @param colors Named list of color overrides (from pmColors() or manual list). +#' @param labelRules Optional list of label rules to replace or add. +#' @param replace_labels Logical. If TRUE, replaces all label rules. If FALSE, +#' appends new rules. Default is FALSE. +#' @param ... Additional color overrides as named arguments. +#' +#' @return A new pm_style object with modifications applied. +#' +#' @examples +#' \dontrun{ +#' # Start with watercolor, change water color +#' my_style <- pmModifyStyle(pmStyle("watercolor"), water = "#1a3a5c") +#' +#' # Add label rules to minimal style +#' my_style <- pmModifyStyle( +#' pmMinimal(), +#' labelRules = pmCityLabels("major-only") +#' ) +#' +#' # Multiple modifications +#' my_style <- pmModifyStyle( +#' pmStyle("muted"), +#' colors = pmColors(water = "#2a4a6c", park = "#c0d8c0"), +#' replace_labels = TRUE, +#' labelRules = list( +#' pmLabelRule("places", pmCenteredTextSymbolizer( +#' font = "bold 14px Arial", +#' fill = "#333" +#' )) +#' ) +#' ) +#' } +#' +#' @seealso \code{\link{pmStyle}}, \code{\link{pmMinimal}} +#' @export +pmModifyStyle <- function(style, + colors = NULL, + labelRules = NULL, + replace_labels = FALSE, + ...) { + if (!inherits(style, "pm_style")) { + stop("'style' must be a pm_style object", call. = FALSE) + } + + new_style <- style + + extra_colors <- list(...) + if (!is.null(colors) || length(extra_colors) > 0) { + all_overrides <- c(colors, extra_colors) + new_style$colors <- utils::modifyList( + new_style$colors %||% list(), + all_overrides + ) + } + + if (!is.null(labelRules)) { + if (replace_labels) { + new_style$labelRules <- labelRules + } else { + new_style$labelRules <- c(new_style$labelRules, labelRules) + } + } + + structure(new_style, class = "pm_style") +} diff --git a/R/symbolizers.R b/R/symbolizers.R new file mode 100644 index 0000000..a6bd5f2 --- /dev/null +++ b/R/symbolizers.R @@ -0,0 +1,315 @@ +#' Create a Polygon Symbolizer +#' +#' @description +#' Creates a polygon symbolizer for rendering filled polygon features. +#' +#' @param fill Character. Fill color for the polygon. Can be a CSS color +#' string or a function specification. +#' @param stroke Character. Stroke (outline) color. Default is NULL (no stroke). +#' @param width Numeric. Stroke width in pixels. Default is 1. +#' @param opacity Numeric. Fill opacity from 0 to 1. Default is 1. +#' @param pattern Character. Fill pattern. One of NULL, "hatch", or "dot". +#' @param ... Additional symbolizer options. +#' +#' @return A list representing the symbolizer configuration. +#' +#' @examples +#' # Simple blue fill +#' pmPolygonSymbolizer(fill = "steelblue") +#' +#' # With stroke +#' pmPolygonSymbolizer(fill = "#f0f0f0", stroke = "#333", width = 2) +#' +#' @export +pmPolygonSymbolizer <- function(fill = "#cccccc", + stroke = NULL, + width = 1, + opacity = 1, + pattern = NULL, + ...) { + opts <- list(fill = fill, ...) + + if (!is.null(stroke)) opts$stroke <- stroke + if (width != 1) opts$width <- width + if (opacity != 1) opts$opacity <- opacity + if (!is.null(pattern)) opts$pattern <- pattern + + list( + type = "polygon", + options = opts + ) +} + + +#' Create a Line Symbolizer +#' +#' @description +#' Creates a line symbolizer for rendering line features. +#' +#' @param color Character. Line color. Default is "#000000". +#' @param width Numeric or function. Line width in pixels. Can be a fixed +#' value or a zoom-dependent specification. +#' @param dash List or NULL. Dash pattern as a vector of numbers, e.g., +#' \code{c(4, 2)} for 4px dash, 2px gap. +#' @param dashColor Character. Color for dashes if using dash pattern. +#' @param dashWidth Numeric. Width of dashes. +#' @param lineCap Character. Line cap style: "butt", "round", or "square". +#' @param lineJoin Character +#' . Line join style: "miter", "round", or "bevel". +#' @param opacity Numeric. Line opacity from 0 to 1. Default is 1. +#' @param ... Additional symbolizer options. +#' +#' @return A list representing the symbolizer configuration. +#' +#' @examples +#' # Simple black line +#' pmLineSymbolizer(color = "black", width = 2) +#' +#' # Dashed line +#' pmLineSymbolizer(color = "gray", width = 1, dash = c(4, 2)) +#' +#' @export +pmLineSymbolizer <- function(color = "#000000", + width = 1, + dash = NULL, + dashColor = NULL, + dashWidth = NULL, + lineCap = NULL, + lineJoin = NULL, + opacity = 1, + ...) { + opts <- list(color = color, width = width, ...) + + if (!is.null(dash)) opts$dash <- dash + if (!is.null(dashColor)) opts$dashColor <- dashColor + if (!is.null(dashWidth)) opts$dashWidth <- dashWidth + if (!is.null(lineCap)) opts$lineCap <- lineCap + if (!is.null(lineJoin)) opts$lineJoin <- lineJoin + if (opacity != 1) opts$opacity <- opacity + + list( + type = "line", + options = opts + ) +} + + +#' Create a Circle Symbolizer +#' +#' @description +#' Creates a circle symbolizer for rendering point features as circles. +#' +#' @param radius Numeric. Circle radius in pixels. Default is 4. +#' @param fill Character. Fill color for the circle. Default is "#000000". +#' @param stroke Character. Stroke (outline) color. Default is NULL. +#' @param width Numeric. Stroke width in pixels. Default is 1. +#' @param opacity Numeric. Fill opacity from 0 to 1. Default is 1. +#' @param ... Additional symbolizer options. +#' +#' @return A list representing the symbolizer configuration. +#' +#' @examples +#' # Simple red circle +#' pmCircleSymbolizer(radius = 6, fill = "red") +#' +#' # Circle with stroke +#' pmCircleSymbolizer(radius = 8, fill = "white", stroke = "black", width = 2) +#' +#' @export +pmCircleSymbolizer <- function(radius = 4, + fill = "#000000", + stroke = NULL, + width = 1, + opacity = 1, + ...) { + opts <- list(radius = radius, fill = fill, ...) + + if (!is.null(stroke)) opts$stroke <- stroke + if (width != 1) opts$width <- width + if (opacity != 1) opts$opacity <- opacity + + list( + type = "circle", + options = opts + ) +} + + +#' Create a Text Symbolizer +#' +#' @description +#' Creates a text symbolizer for rendering text labels. +#' +#' @param font Character. Font specification (e.g., "12px sans-serif"). +#' @param fill Character. Text fill color. Default is "#000000". +#' @param stroke Character. Text stroke (halo) color. Default is NULL. +#' @param width Numeric. Stroke width for text halo. Default is 0. +#' @param labelProps List. Properties to use for label text, in order of +#' preference. Default is \code{list("name")}. +#' @param textTransform Character. Text transformation: "uppercase", +#' "lowercase", or NULL. +#' @param ... Additional symbolizer options. +#' +#' @return A list representing the symbolizer configuration. +#' +#' @examples +#' # Simple text label +#' pmTextSymbolizer(font = "12px Arial", fill = "black") +#' +#' # Text with halo +#' pmTextSymbolizer(font = "14px sans-serif", fill = "black", +#' stroke = "white", width = 2) +#' +#' @export +pmTextSymbolizer <- function(font = "12px sans-serif", + fill = "#000000", + stroke = NULL, + width = 0, + labelProps = NULL, + textTransform = NULL, + ...) { + opts <- list(font = font, fill = fill, ...) + + if (!is.null(stroke)) opts$stroke <- stroke + if (width > 0) opts$width <- width + if (!is.null(labelProps)) opts$labelProps <- labelProps + if (!is.null(textTransform)) opts$textTransform <- textTransform + + list( + type = "text", + options = opts + ) +} + + +#' Create a Centered Text Symbolizer +#' +#' @description +#' Creates a centered text symbolizer for rendering text labels centered +#' on point features. +#' +#' @param font Character. Font specification (e.g., "12px sans-serif"). +#' @param fill Character. Text fill color. Default is "#000000". +#' @param stroke Character. Text stroke (halo) color. Default is NULL. +#' @param width Numeric. Stroke width for text halo. Default is 0. +#' @param lineHeight Numeric. Line height multiplier for multi-line labels. +#' Default is NULL (uses library default). Use values like 1.0-1.2 for +#' tighter spacing. +#' @param labelProps List. Properties to use for label text, in order of +#' preference. Default is \code{list("name")}. +#' @param ... Additional symbolizer options. +#' +#' @return A list representing the symbolizer configuration. +#' +#' @examples +#' pmCenteredTextSymbolizer(font = "14px Arial", fill = "black") +#' +#' # Tighter line spacing for multi-word labels +#' pmCenteredTextSymbolizer(font = "11px sans-serif", fill = "#444", +#' lineHeight = 1.1) +#' +#' @export +pmCenteredTextSymbolizer <- function(font = "12px sans-serif", + fill = "#000000", + stroke = NULL, + width = 0, + lineHeight = NULL, + labelProps = NULL, + ...) { + opts <- list(font = font, fill = fill, ...) + + if (!is.null(stroke)) opts$stroke <- stroke + if (width > 0) opts$width <- width + if (!is.null(lineHeight)) opts$lineHeight <- lineHeight + if (!is.null(labelProps)) opts$labelProps <- labelProps + + list( + type = "centeredText", + options = opts + ) +} + + +#' Create a Line Label Symbolizer +#' +#' @description +#' Creates a line label symbolizer for rendering text labels along line +#' features (e.g., street names). +#' +#' @param font Character. Font specification (e.g., "12px sans-serif"). +#' @param fill Character. Text fill color. Default is "#000000". +#' @param stroke Character. Text stroke (halo) color. Default is NULL. +#' @param width Numeric. Stroke width for text halo. Default is 0. +#' @param labelProps List. Properties to use for label text. +#' @param ... Additional symbolizer options. +#' +#' @return A list representing the symbolizer configuration. +#' +#' @examples +#' pmLineLabelSymbolizer(font = "11px Arial", fill = "#333", +#' stroke = "white", width = 2) +#' +#' @export +pmLineLabelSymbolizer <- function(font = "12px sans-serif", + fill = "#000000", + stroke = NULL, + width = 0, + labelProps = NULL, + ...) { + opts <- list(font = font, fill = fill, ...) + + if (!is.null(stroke)) opts$stroke <- stroke + if (width > 0) opts$width <- width + if (!is.null(labelProps)) opts$labelProps <- labelProps + + list( + type = "lineLabel", + options = opts + ) +} + + +#' Create a Shield Symbolizer +#' +#' @description +#' Creates a shield symbolizer for rendering labeled badges or shields +#' (e.g., highway route markers). +#' +#' @param font Character. Font specification for shield text. +#' @param fill Character. Text fill color. Default is "#000000". +#' @param background Character. Shield background color. Default is "#ffffff". +#' @param stroke Character. Shield border color. Default is "#000000". +#' @param padding Numeric. Padding inside the shield in pixels. Default is 2. +#' @param labelProps List. Properties to use for shield text. +#' @param ... Additional symbolizer options. +#' +#' @return A list representing the symbolizer configuration. +#' +#' @examples +#' pmShieldSymbolizer(font = "10px Arial", fill = "black", +#' background = "white", stroke = "black") +#' +#' @export +pmShieldSymbolizer <- function(font = "10px sans-serif", + fill = "#000000", + background = "#ffffff", + stroke = "#000000", + padding = 2, + labelProps = NULL, + ...) { + opts <- list( + font = font, + fill = fill, + background = background, + stroke = stroke, + padding = padding, + ... + ) + + if (!is.null(labelProps)) opts$labelProps <- labelProps + + list( + type = "shield", + options = opts + ) +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..4d8fa12 --- /dev/null +++ b/README.md @@ -0,0 +1,351 @@ +# protomapr + +An R package to add [Protomaps](https://protomaps.com/) vector tile layers to Leaflet maps. + +## Why Protomaps? + +Standard Leaflet maps use `addProviderTiles()` to load raster tiles from services like OpenStreetMap or CartoDB. Protomaps offers a vector tile alternative with key advantages: + +| | Raster Tiles | Protomaps (Vector) | +|---|---|---| +| **Customization** | Limited to provider styles | Full control over colors, labels, features | +| **Self-hosting** | Requires tile server | Single PMTiles file, no server needed | +| **Feature control** | Show everything or nothing | Hide roads, buildings, labels selectively | +| **Privacy** | Requests to third-party servers | Self-host for complete privacy | +| **Rate limits** | Often have API quotas | No limits when self-hosted | +| **Zoom quality** | Can pixelate | Smooth at any zoom level | +| **File size** | Large (pre-rendered images) | Smaller (compressed vectors) | + +**Use Protomaps when you need:** +- Custom branded maps matching your color scheme +- Minimal basemaps for data visualization (hide distracting features) +- Offline or embedded applications +- Privacy-sensitive contexts +- High-traffic apps without API rate limits + +**Use providerTiles when:** +- Default styling is fine +- You need satellite/aerial imagery +- Quick prototypes + +## Installation + +```r +# Install from local source +devtools::install_local("path/to/protomapr") + +# Or install dependencies and load +install.packages(c("leaflet", "htmltools", "htmlwidgets", "jsonlite")) +``` + +## Quick Start + +```r +library(leaflet) +library(protomapr) + +# Use the demo URL (free Protomaps daily build) +leaflet() %>% + setView(lng = -122.4, lat = 37.8, zoom = 12) %>% + addProtomaps(url = protomaps_demo_url()) +``` + +## Data Sources + +You have several options for PMTiles data: + +### 1. Demo/Development (free) + +Use `protomaps_demo_url()` which points to the Protomaps daily OpenStreetMap build: + +```r +leaflet() %>% + addProtomaps(url = protomaps_demo_url()) +``` + +### 2. Self-hosted (recommended for production) + +Download a PMTiles file and host it on cloud storage (S3, GCS, Cloudflare R2, etc.): + +- Download daily builds: https://maps.protomaps.com/builds/ +- Extract a region: https://slice.openstreetmap.us/ + +```r +leaflet() %>% + addProtomaps(url = "https://your-bucket.s3.amazonaws.com/tiles.pmtiles") +``` + +### 3. Local file + +For local development, you can serve a PMTiles file locally: + +```r +# Serve with a local HTTP server, then: +leaflet() %>% + addProtomaps(url = "http://localhost:8080/tiles.pmtiles") +``` + +## Basic Usage + +```r +library(leaflet) +library(protomapr) + +leaflet() %>% + setView(lng = -122.4, lat = 37.8, zoom = 12) %>% + addProtomaps( + url = protomaps_demo_url(), + flavor = "light" + ) +``` + +## Flavors + +Five built-in flavors are available: + +```r +# Light flavor (default) +leaflet() %>% + addProtomaps(url = protomaps_demo_url(), flavor = "light") + +# Dark flavor +leaflet() %>% + addProtomaps(url = protomaps_demo_url(), flavor = "dark") + +# White flavor (for data visualization) +leaflet() %>% + addProtomaps(url = protomaps_demo_url(), flavor = "white") + +# Grayscale flavor +leaflet() %>% + addProtomaps(url = protomaps_demo_url(), flavor = "grayscale") + +# Black flavor +leaflet() %>% + addProtomaps(url = protomaps_demo_url(), flavor = "black") +``` + +## Custom Styling + +### Paint Rules + +Control how features are rendered using paint rules: +```r +leaflet() %>% + setView(lng = -122.4, lat = 37.8, zoom = 12) %>% + addProtomaps( + url = protomaps_demo_url(), + paintRules = list( + pmPaintRule("water", pmPolygonSymbolizer(fill = "steelblue")), + pmPaintRule("earth", pmPolygonSymbolizer(fill = "#f0f0f0")), + pmPaintRule("roads", pmLineSymbolizer(color = "gray", width = 1)), + pmPaintRule("buildings", pmPolygonSymbolizer( + fill = "#d4d4d4", + stroke = "#999", + width = 0.5 + )) + ) + ) +``` + +### Label Rules + +Add text labels to features: + +```r +leaflet() %>% + setView(lng = -122.4, lat = 37.8, zoom = 14) %>% + addProtomaps( + url = protomaps_demo_url(), + labelRules = list( + pmLabelRule("places", + pmCenteredTextSymbolizer( + font = "14px Arial", + fill = "black", + stroke = "white", + width = 2 + ) + ), + pmLabelRule("roads", + pmLineLabelSymbolizer( + font = "11px Arial", + fill = "#333" + ), + minzoom = 14 + ) + ) + ) +``` + +### Filtering Features + +Use JavaScript filter expressions to style features based on their properties: + +```r +# Cities only (not towns/villages) +pmLabelRule("places", pmCenteredTextSymbolizer(font = "14px Arial", fill = "black"), + filter = "feature.props.kind_detail === 'city'") + +# States/regions +pmLabelRule("places", pmCenteredTextSymbolizer(font = "18px Arial", fill = "#666"), + filter = "feature.props.kind === 'region'") + +# Important places (low min_zoom = more important) +pmLabelRule("places", pmCenteredTextSymbolizer(font = "16px Arial", fill = "black"), + filter = "feature.props.min_zoom <= 6") + +# Highways only +pmPaintRule("roads", pmLineSymbolizer(color = "orange", width = 3), + filter = "feature.props.kind === 'highway'") + +# Show features at specific zoom levels +pmPaintRule("buildings", pmPolygonSymbolizer(fill = "#ccc"), + minzoom = 14, maxzoom = 18) +``` + +## Layers and Properties + +For full documentation run `?protomaps_layers` in R. Quick reference: + +### Layer Names + +| Layer | Description | +|-------|-------------| +| `earth` | Land polygons | +| `water` | Water bodies | +| `landuse` | Parks, forests, residential, etc. | +| `roads` | Streets and highways | +| `buildings` | Building footprints | +| `places` | City/town/region labels | +| `pois` | Points of interest | +| `boundaries` | Administrative boundaries | + +### Key Properties for Filtering + +**places:** +- `kind`: "country", "region", "locality" +- `kind_detail`: "city", "town", "village", "state", "province" +- `min_zoom`: importance (lower = more important) +- `population_rank`: size (higher = larger) + +**water:** +- `kind`: "water", "lake", "ocean" +- `kind_detail`: "river", "lake", "reservoir", "stream" + +**roads:** +- `kind`: "highway", "major_road", "minor_road", "path" +- `kind_detail`: "motorway", "primary", "residential", "footway" + +**landuse:** +- `kind`: "park", "forest", "residential", "industrial" + +## Symbolizer Reference + +### pmPolygonSymbolizer + +Style polygon features: + +```r +pmPolygonSymbolizer( + fill = "#cccccc", # Fill color + stroke = "#333", # Outline color + width = 1, # Outline width + opacity = 0.8 # Fill opacity (0-1) +) +``` + +### pmLineSymbolizer + +Style line features: + +```r +pmLineSymbolizer( + color = "#000000", # Line color + width = 2, # Line width in pixels + dash = c(4, 2), # Dash pattern (4px dash, 2px gap) + lineCap = "round", # "butt", "round", or "square" + lineJoin = "round", # "miter", "round", or "bevel" + opacity = 1 # Line opacity (0-1) +) +``` + +### pmCircleSymbolizer + +Style point features as circles: + +```r +pmCircleSymbolizer( + radius = 6, # Circle radius in pixels + fill = "red", # Fill color + stroke = "black", # Outline color + width = 1 # Outline width +) +``` + +### pmCenteredTextSymbolizer + +Add centered text labels: + +```r +pmCenteredTextSymbolizer( + font = "14px Arial", # CSS font specification + fill = "black", # Text color + stroke = "white", # Halo color + width = 2 # Halo width +) +``` + +### pmLineLabelSymbolizer + +Add labels along line features (e.g., street names): + +```r +pmLineLabelSymbolizer( + font = "11px Arial", + fill = "#333", + stroke = "white", + width = 1 +) +``` + +### pmShieldSymbolizer + +Add shield/badge labels (e.g., highway markers): + +```r +pmShieldSymbolizer( + font = "10px Arial", + fill = "black", + background = "white", + stroke = "black", + padding = 2 +) +``` + +## Additional Options + +```r +leaflet() %>% + addProtomaps( + url = protomaps_demo_url(), + flavor = "light", + lang = "en", # Language for labels + attribution = "Protomaps", # Attribution text + options = protomapsOptions( + maxDataZoom = 14, # Max zoom for tile data + tileSize = 256, # Tile size in pixels + debug = FALSE # Debug mode + ) + ) +``` + +## Acknowledgments + +This package is a wrapper around [protomaps-leaflet](https://github.com/protomaps/protomaps-leaflet), created by [Brandon Liu](https://bdon.org/). The Protomaps project provides an incredible open-source stack for self-hosted vector maps, including the [PMTiles](https://docs.protomaps.com/pmtiles/) single-file tile archive format. Thanks to Brandon for making beautiful, customizable maps accessible to everyone. + +## Links + +- [Protomaps](https://protomaps.com/) +- [protomaps-leaflet GitHub](https://github.com/protomaps/protomaps-leaflet) +- [PMTiles specification](https://docs.protomaps.com/pmtiles/) +- [Brandon Liu](https://bdon.org/) diff --git a/_pkgdown.yml b/_pkgdown.yml new file mode 100644 index 0000000..17beefd --- /dev/null +++ b/_pkgdown.yml @@ -0,0 +1,95 @@ +url: https://evmo.github.io/protomapr/ + +template: + bootstrap: 5 + +navbar: + structure: + left: [intro, reference, articles] + right: [search, github] + components: + articles: + text: Articles + menu: + - text: Getting Started + href: articles/getting-started.html + - text: Custom Styling + href: articles/custom-styling.html + - text: Data Visualization Basemaps + href: articles/data-viz-basemaps.html + - text: Labels and Filters + href: articles/labels-and-filters.html + +reference: + - title: Add Protomaps to Maps + desc: Core function for adding vector tile layers + contents: + - addProtomaps + - protomapsOptions + - protomapsDependency + + - title: Styles and Presets + desc: Pre-built style configurations + contents: + - pmStyle + - pmMinimal + - pmModifyStyle + - pmColors + - pmHideFeatures + - pmCityLabels + - print.pm_style + + - title: Color Palettes + desc: Apply color palettes to map features + contents: + - pmPalette + - pmPaletteStyle + + - title: Custom Rules + desc: Create custom paint and label rules + contents: + - pmPaintRule + - pmLabelRule + + - title: Symbolizers + desc: Visual appearance specifications + contents: + - pmPolygonSymbolizer + - pmLineSymbolizer + - pmCircleSymbolizer + - pmTextSymbolizer + - pmCenteredTextSymbolizer + - pmLineLabelSymbolizer + - pmShieldSymbolizer + + - title: Tile Sources + desc: Functions for tile URLs and API keys + contents: + - protomaps_url + - set_protomaps_key + - protomaps_sample_tiles + - protomaps_clear_cache + + - title: Reference Data + desc: Documentation for layer properties + contents: + - protomaps_layers + - protomaps_colors + +articles: + - title: Get Started + navbar: ~ + contents: + - getting-started + - title: Customization + contents: + - custom-styling + - labels-and-filters + - title: Use Cases + contents: + - data-viz-basemaps + +home: + links: + - text: Report a bug + href: https://github.com/evmo/protomapr/issues diff --git a/cran-comments.md b/cran-comments.md new file mode 100644 index 0000000..875b1ff --- /dev/null +++ b/cran-comments.md @@ -0,0 +1,25 @@ +## R CMD check results + +0 errors | 0 warnings | 0 notes + +## Test environments + +* local macOS (aarch64-apple-darwin), R 4.x +* GitHub Actions (ubuntu-latest), R release +* GitHub Actions (windows-latest), R release + +## Dependencies + +This package imports: +- leaflet (>= 2.0.0) +- htmltools +- htmlwidgets +- jsonlite + +## Notes + +This is a new submission to CRAN. + +The package provides R bindings for the protomaps-leaflet JavaScript library, +enabling vector tile map layers in Leaflet maps. The JavaScript library is +loaded from a CDN (unpkg.com) at runtime. diff --git a/inst/htmlwidgets/lib/protomaps-leaflet-5.1.0/protomaps-leaflet.js b/inst/htmlwidgets/lib/protomaps-leaflet-5.1.0/protomaps-leaflet.js new file mode 100644 index 0000000..f66d391 --- /dev/null +++ b/inst/htmlwidgets/lib/protomaps-leaflet-5.1.0/protomaps-leaflet.js @@ -0,0 +1,23 @@ +"use strict";var protomapsL=(()=>{var We=Object.defineProperty;var Ln=Object.getOwnPropertyDescriptor;var Pn=Object.getOwnPropertyNames;var Sn=Object.prototype.hasOwnProperty;var D=Math.pow;var l=(i,e)=>We(i,"name",{value:e,configurable:!0});var Tn=(i,e)=>{for(var t in e)We(i,t,{get:e[t],enumerable:!0})},Fn=(i,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of Pn(e))!Sn.call(i,r)&&r!==t&&We(i,r,{get:()=>e[r],enumerable:!(n=Ln(e,r))||n.enumerable});return i};var Mn=i=>Fn(We({},"__esModule",{value:!0}),i);var A=(i,e,t)=>new Promise((n,r)=>{var a=c=>{try{s(t.next(c))}catch(u){r(u)}},o=c=>{try{s(t.throw(c))}catch(u){r(u)}},s=c=>c.done?n(c.value):Promise.resolve(c.value).then(a,o);s((t=t.apply(i,e)).next())});var Sa={};Tn(Sa,{CenteredSymbolizer:()=>at,CenteredTextSymbolizer:()=>Q,CircleSymbolizer:()=>De,FlexSymbolizer:()=>Ut,Font:()=>La,GeomType:()=>rt,GroupSymbolizer:()=>Ae,IconSymbolizer:()=>Vt,Index:()=>dt,Justify:()=>cn,Labeler:()=>ye,Labelers:()=>xe,LineLabelPlacement:()=>hn,LineLabelSymbolizer:()=>ee,LineSymbolizer:()=>O,OffsetSymbolizer:()=>ot,OffsetTextSymbolizer:()=>Re,Padding:()=>Nt,PmtilesSource:()=>pe,PolygonSymbolizer:()=>M,Sheet:()=>yi,ShieldSymbolizer:()=>$t,Static:()=>pi,TextPlacements:()=>un,TextSymbolizer:()=>Be,TileCache:()=>Ce,View:()=>ut,ZxySource:()=>Me,arr:()=>la,covering:()=>gn,createPattern:()=>sa,exp:()=>V,getZoom:()=>mi,isCcw:()=>an,isInRing:()=>It,labelRules:()=>Oe,leafletLayer:()=>za,linear:()=>st,paint:()=>Ne,paintRules:()=>je,pointInPolygon:()=>on,pointMinDistToLines:()=>ln,pointMinDistToPoints:()=>sn,sourcesToViews:()=>Ue,step:()=>ha,toIndex:()=>G,transformGeom:()=>$e,wrap:()=>Ve});function x(i,e){this.x=i,this.y=e}l(x,"Point");x.prototype={clone(){return new x(this.x,this.y)},add(i){return this.clone()._add(i)},sub(i){return this.clone()._sub(i)},multByPoint(i){return this.clone()._multByPoint(i)},divByPoint(i){return this.clone()._divByPoint(i)},mult(i){return this.clone()._mult(i)},div(i){return this.clone()._div(i)},rotate(i){return this.clone()._rotate(i)},rotateAround(i,e){return this.clone()._rotateAround(i,e)},matMult(i){return this.clone()._matMult(i)},unit(){return this.clone()._unit()},perp(){return this.clone()._perp()},round(){return this.clone()._round()},mag(){return Math.sqrt(this.x*this.x+this.y*this.y)},equals(i){return this.x===i.x&&this.y===i.y},dist(i){return Math.sqrt(this.distSqr(i))},distSqr(i){let e=i.x-this.x,t=i.y-this.y;return e*e+t*t},angle(){return Math.atan2(this.y,this.x)},angleTo(i){return Math.atan2(this.y-i.y,this.x-i.x)},angleWith(i){return this.angleWithSep(i.x,i.y)},angleWithSep(i,e){return Math.atan2(this.x*e-this.y*i,this.x*i+this.y*e)},_matMult(i){let e=i[0]*this.x+i[1]*this.y,t=i[2]*this.x+i[3]*this.y;return this.x=e,this.y=t,this},_add(i){return this.x+=i.x,this.y+=i.y,this},_sub(i){return this.x-=i.x,this.y-=i.y,this},_mult(i){return this.x*=i,this.y*=i,this},_div(i){return this.x/=i,this.y/=i,this},_multByPoint(i){return this.x*=i.x,this.y*=i.y,this},_divByPoint(i){return this.x/=i.x,this.y/=i.y,this},_unit(){return this._div(this.mag()),this},_perp(){let i=this.y;return this.y=this.x,this.x=-i,this},_rotate(i){let e=Math.cos(i),t=Math.sin(i),n=e*this.x-t*this.y,r=t*this.x+e*this.y;return this.x=n,this.y=r,this},_rotateAround(i,e){let t=Math.cos(i),n=Math.sin(i),r=e.x+t*(this.x-e.x)-n*(this.y-e.y),a=e.y+n*(this.x-e.x)+t*(this.y-e.y);return this.x=r,this.y=a,this},_round(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this},constructor:x};x.convert=function(i){if(i instanceof x)return i;if(Array.isArray(i))return new x(+i[0],+i[1]);if(i.x!==void 0&&i.y!==void 0)return new x(+i.x,+i.y);throw new Error("Expected [x, y] or {x, y} point format")};var Cn=Object.defineProperty,J=l((i,e)=>Cn(i,"name",{value:e,configurable:!0}),"r");function I(i,e){let t="script";return i==="name"?t="script":i==="name2"?t="script2":i==="name3"&&(t="script3"),[["coalesce",["get",`pgf:${i}`],["get",i]],{"text-font":["case",["==",["get",t],"Devanagari"],["literal",["Noto Sans Devanagari Regular v1"]],["literal",[e||"Noto Sans Regular"]]]}]}l(I,"l");J(I,"get_name_block");function W(i,e,t){let n="name";return t==="name"?n="":t==="name2"?n="2":t==="name3"&&(n="3"),e==="Latin"?["has",`script${n}`]:i==="ja"?["all",["!=",["get",`script${n}`],"Han"],["!=",["get",`script${n}`],"Hiragana"],["!=",["get",`script${n}`],"Katakana"],["!=",["get",`script${n}`],"Mixed-Japanese"]]:["!=",["get",`script${n}`],e]}l(W,"c");J(W,"is_not_in_target_script");function N(i){return i==="Devanagari"?{"text-font":["literal",["Noto Sans Devanagari Regular v1"]]}:{}}l(N,"o");J(N,"get_font_formatting");function gt(i){let e=Dn.find(t=>t.lang===i);return e===void 0?"Latin":e.script}l(gt,"d");J(gt,"get_default_script");function Si(i,e){let t=e||gt(i),n;return t==="Devanagari"?n="pgf:":n="",["format",["coalesce",["get",`${n}name:${i}`],["get","name:en"]],N(t)]}l(Si,"_");J(Si,"get_country_name");function Z(i,e,t){let n=e||gt(i),r;return n==="Devanagari"?r="pgf:":r="",["case",["all",["any",["has","name"],["has","pgf:name"]],["!",["any",["has","name2"],["has","pgf:name2"]]],["!",["any",["has","name3"],["has","pgf:name3"]]]],["case",W(i,n,"name"),["case",["any",["is-supported-script",["get","name"]],["has","pgf:name"]],["format",["coalesce",["get",`${r}name:${i}`],["get","name:en"]],N(n),` +`,{},["case",["all",["!",["has",`${r}name:${i}`]],["has","name:en"],["!",["has","script"]]],"",["coalesce",["get","pgf:name"],["get","name"]]],{"text-font":["case",["==",["get","script"],"Devanagari"],["literal",["Noto Sans Devanagari Regular v1"]],["literal",[t||"Noto Sans Regular"]]]}],["get","name:en"]],["format",["coalesce",["get",`${r}name:${i}`],["get","pgf:name"],["get","name"]],N(n)]],["all",["any",["has","name"],["has","pgf:name"]],["any",["has","name2"],["has","pgf:name2"]],["!",["any",["has","name3"],["has","pgf:name3"]]]],["case",["all",W(i,n,"name"),W(i,n,"name2")],["format",["get",`${r}name:${i}`],N(n),` +`,{},...I("name"),` +`,{},...I("name2")],["case",W(i,n,"name2"),["format",["coalesce",["get",`${r}name:${i}`],["get","pgf:name"],["get","name"]],N(n),` +`,{},...I("name2")],["format",["coalesce",["get",`${r}name:${i}`],["get","pgf:name2"],["get","name2"]],N(n),` +`,{},...I("name")]]],["case",["all",W(i,n,"name"),W(i,n,"name2"),W(i,n,"name3")],["format",["get",`${r}name:${i}`],N(n),` +`,{},...I("name"),` +`,{},...I("name2"),` +`,{},...I("name3")],["case",["!",W(i,n,"name")],["format",["coalesce",["get",`${r}name:${i}`],["get","pgf:name"],["get","name"]],N(n),` +`,{},...I("name2"),` +`,{},...I("name3")],["!",W(i,n,"name2")],["format",["coalesce",["get",`${r}name:${i}`],["get","pgf:name2"],["get","name2"]],N(n),` +`,{},...I("name"),` +`,{},...I("name3")],["format",["coalesce",["get",`${r}name:${i}`],["get","pgf:name3"],["get","name3"]],N(n),` +`,{},...I("name"),` +`,{},...I("name2")]]]]}l(Z,"s");J(Z,"get_multiline_name");var Dn=[{lang:"ar",full_name:"Arabic",script:"Arabic"},{lang:"cs",full_name:"Czech",script:"Latin"},{lang:"bg",full_name:"Bulgarian",script:"Cyrillic"},{lang:"da",full_name:"Danish",script:"Latin"},{lang:"de",full_name:"German",script:"Latin"},{lang:"el",full_name:"Greek",script:"Greek"},{lang:"en",full_name:"English",script:"Latin"},{lang:"es",full_name:"Spanish",script:"Latin"},{lang:"et",full_name:"Estonian",script:"Latin"},{lang:"fa",full_name:"Persian",script:"Arabic"},{lang:"fi",full_name:"Finnish",script:"Latin"},{lang:"fr",full_name:"French",script:"Latin"},{lang:"ga",full_name:"Irish",script:"Latin"},{lang:"he",full_name:"Hebrew",script:"Hebrew"},{lang:"hi",full_name:"Hindi",script:"Devanagari"},{lang:"hr",full_name:"Croatian",script:"Latin"},{lang:"hu",full_name:"Hungarian",script:"Latin"},{lang:"id",full_name:"Indonesian",script:"Latin"},{lang:"it",full_name:"Italian",script:"Latin"},{lang:"ja",full_name:"Japanese",script:""},{lang:"ko",full_name:"Korean",script:"Hangul"},{lang:"lt",full_name:"Lithuanian",script:"Latin"},{lang:"lv",full_name:"Latvian",script:"Latin"},{lang:"ne",full_name:"Nepali",script:"Devanagari"},{lang:"nl",full_name:"Dutch",script:"Latin"},{lang:"no",full_name:"Norwegian",script:"Latin"},{lang:"mr",full_name:"Marathi",script:"Devanagari"},{lang:"mt",full_name:"Maltese",script:"Latin"},{lang:"pl",full_name:"Polish",script:"Latin"},{lang:"pt",full_name:"Portuguese",script:"Latin"},{lang:"ro",full_name:"Romanian",script:"Latin"},{lang:"ru",full_name:"Russian",script:"Cyrillic"},{lang:"sk",full_name:"Slovak",script:"Latin"},{lang:"sl",full_name:"Slovenian",script:"Latin"},{lang:"sv",full_name:"Swedish",script:"Latin"},{lang:"tr",full_name:"Turkish",script:"Latin"},{lang:"uk",full_name:"Ukrainian",script:"Cyrillic"},{lang:"ur",full_name:"Urdu",script:"Arabic"},{lang:"vi",full_name:"Vietnamese",script:"Latin"},{lang:"zh-Hans",full_name:"Chinese (Simplified)",script:"Han"},{lang:"zh-Hant",full_name:"Chinese (Traditional)",script:"Han"}];function Ti(i,e){return[{id:"background",type:"background",paint:{"background-color":e.background}},{id:"earth",type:"fill",filter:["==","$type","Polygon"],source:i,"source-layer":"earth",paint:{"fill-color":e.earth}},...e.landcover?[{id:"landcover",type:"fill",source:i,"source-layer":"landcover",paint:{"fill-color":["match",["get","kind"],"grassland",e.landcover.grassland,"barren",e.landcover.barren,"urban_area",e.landcover.urban_area,"farmland",e.landcover.farmland,"glacier",e.landcover.glacier,"scrub",e.landcover.scrub,e.landcover.forest],"fill-opacity":["interpolate",["linear"],["zoom"],5,1,7,0]}}]:[],{id:"landuse_park",type:"fill",source:i,"source-layer":"landuse",filter:["in","kind","national_park","park","cemetery","protected_area","nature_reserve","forest","golf_course","wood","nature_reserve","forest","scrub","grassland","grass","military","naval_base","airfield"],paint:{"fill-opacity":["interpolate",["linear"],["zoom"],6,0,11,1],"fill-color":["case",["in",["get","kind"],["literal",["national_park","park","cemetery","protected_area","nature_reserve","forest","golf_course"]]],e.park_b,["in",["get","kind"],["literal",["wood","nature_reserve","forest"]]],e.wood_b,["in",["get","kind"],["literal",["scrub","grassland","grass"]]],e.scrub_b,["in",["get","kind"],["literal",["glacier"]]],e.glacier,["in",["get","kind"],["literal",["sand"]]],e.sand,["in",["get","kind"],["literal",["military","naval_base","airfield"]]],e.zoo,e.earth]}},{id:"landuse_urban_green",type:"fill",source:i,"source-layer":"landuse",filter:["in","kind","allotments","village_green","playground"],paint:{"fill-color":e.park_b,"fill-opacity":.7}},{id:"landuse_hospital",type:"fill",source:i,"source-layer":"landuse",filter:["==","kind","hospital"],paint:{"fill-color":e.hospital}},{id:"landuse_industrial",type:"fill",source:i,"source-layer":"landuse",filter:["==","kind","industrial"],paint:{"fill-color":e.industrial}},{id:"landuse_school",type:"fill",source:i,"source-layer":"landuse",filter:["in","kind","school","university","college"],paint:{"fill-color":e.school}},{id:"landuse_beach",type:"fill",source:i,"source-layer":"landuse",filter:["in","kind","beach"],paint:{"fill-color":e.beach}},{id:"landuse_zoo",type:"fill",source:i,"source-layer":"landuse",filter:["in","kind","zoo"],paint:{"fill-color":e.zoo}},{id:"landuse_aerodrome",type:"fill",source:i,"source-layer":"landuse",filter:["in","kind","aerodrome"],paint:{"fill-color":e.aerodrome}},{id:"roads_runway",type:"line",source:i,"source-layer":"roads",filter:["==","kind_detail","runway"],paint:{"line-color":e.runway,"line-width":["interpolate",["exponential",1.6],["zoom"],10,0,12,4,18,30]}},{id:"roads_taxiway",type:"line",source:i,"source-layer":"roads",minzoom:13,filter:["==","kind_detail","taxiway"],paint:{"line-color":e.runway,"line-width":["interpolate",["exponential",1.6],["zoom"],13,0,13.5,1,15,6]}},{id:"landuse_runway",type:"fill",source:i,"source-layer":"landuse",filter:["any",["in","kind","runway","taxiway"]],paint:{"fill-color":e.runway}},{id:"water",type:"fill",filter:["==","$type","Polygon"],source:i,"source-layer":"water",paint:{"fill-color":e.water}},{id:"water_stream",type:"line",source:i,"source-layer":"water",minzoom:14,filter:["in","kind","stream"],paint:{"line-color":e.water,"line-width":.5}},{id:"water_river",type:"line",source:i,"source-layer":"water",minzoom:9,filter:["in","kind","river"],paint:{"line-color":e.water,"line-width":["interpolate",["exponential",1.6],["zoom"],9,0,9.5,1,18,12]}},{id:"landuse_pedestrian",type:"fill",source:i,"source-layer":"landuse",filter:["==","kind","pedestrian"],paint:{"fill-color":e.pedestrian}},{id:"landuse_pier",type:"fill",source:i,"source-layer":"landuse",filter:["==","kind","pier"],paint:{"fill-color":e.pier}},{id:"roads_tunnels_other_casing",type:"line",source:i,"source-layer":"roads",filter:["all",["has","is_tunnel"],["in","kind","other","path"]],paint:{"line-color":e.tunnel_other_casing,"line-gap-width":["interpolate",["exponential",1.6],["zoom"],14,0,20,7]}},{id:"roads_tunnels_minor_casing",type:"line",source:i,"source-layer":"roads",filter:["all",["has","is_tunnel"],["==","kind","minor_road"]],paint:{"line-color":e.tunnel_minor_casing,"line-dasharray":[3,2],"line-gap-width":["interpolate",["exponential",1.6],["zoom"],11,0,12.5,.5,15,2,18,11],"line-width":["interpolate",["exponential",1.6],["zoom"],12,0,12.5,1]}},{id:"roads_tunnels_link_casing",type:"line",source:i,"source-layer":"roads",filter:["all",["has","is_tunnel"],["has","is_link"]],paint:{"line-color":e.tunnel_link_casing,"line-dasharray":[3,2],"line-gap-width":["interpolate",["exponential",1.6],["zoom"],13,0,13.5,1,18,11],"line-width":["interpolate",["exponential",1.6],["zoom"],12,0,12.5,1]}},{id:"roads_tunnels_major_casing",type:"line",source:i,"source-layer":"roads",filter:["all",["!has","is_tunnel"],["!has","is_bridge"],["==","kind","major_road"]],paint:{"line-color":e.tunnel_major_casing,"line-dasharray":[3,2],"line-gap-width":["interpolate",["exponential",1.6],["zoom"],7,0,7.5,.5,18,13],"line-width":["interpolate",["exponential",1.6],["zoom"],9,0,9.5,1]}},{id:"roads_tunnels_highway_casing",type:"line",source:i,"source-layer":"roads",filter:["all",["!has","is_tunnel"],["!has","is_bridge"],["==","kind","highway"],["!has","is_link"]],paint:{"line-color":e.tunnel_highway_casing,"line-dasharray":[6,.5],"line-gap-width":["interpolate",["exponential",1.6],["zoom"],3,0,3.5,.5,18,15],"line-width":["interpolate",["exponential",1.6],["zoom"],7,0,7.5,1,20,15]}},{id:"roads_tunnels_other",type:"line",source:i,"source-layer":"roads",filter:["all",["has","is_tunnel"],["in","kind","other","path"]],paint:{"line-color":e.tunnel_other,"line-dasharray":[4.5,.5],"line-width":["interpolate",["exponential",1.6],["zoom"],14,0,20,7]}},{id:"roads_tunnels_minor",type:"line",source:i,"source-layer":"roads",filter:["all",["has","is_tunnel"],["==","kind","minor_road"]],paint:{"line-color":e.tunnel_minor,"line-width":["interpolate",["exponential",1.6],["zoom"],11,0,12.5,.5,15,2,18,11]}},{id:"roads_tunnels_link",type:"line",source:i,"source-layer":"roads",filter:["all",["has","is_tunnel"],["has","is_link"]],paint:{"line-color":e.tunnel_minor,"line-width":["interpolate",["exponential",1.6],["zoom"],13,0,13.5,1,18,11]}},{id:"roads_tunnels_major",type:"line",source:i,"source-layer":"roads",filter:["all",["has","is_tunnel"],["==","kind","major_road"]],paint:{"line-color":e.tunnel_major,"line-width":["interpolate",["exponential",1.6],["zoom"],6,0,12,1.6,15,3,18,13]}},{id:"roads_tunnels_highway",type:"line",source:i,"source-layer":"roads",filter:["all",["has","is_tunnel"],["==",["get","kind"],"highway"],["!",["has","is_link"]]],paint:{"line-color":e.tunnel_highway,"line-width":["interpolate",["exponential",1.6],["zoom"],3,0,6,1.1,12,1.6,15,5,18,15]}},{id:"buildings",type:"fill",source:i,"source-layer":"buildings",filter:["in","kind","building","building_part"],paint:{"fill-color":e.buildings,"fill-opacity":.5}},{id:"roads_pier",type:"line",source:i,"source-layer":"roads",filter:["==","kind_detail","pier"],paint:{"line-color":e.pier,"line-width":["interpolate",["exponential",1.6],["zoom"],12,0,12.5,.5,20,16]}},{id:"roads_minor_service_casing",type:"line",source:i,"source-layer":"roads",minzoom:13,filter:["all",["!has","is_tunnel"],["!has","is_bridge"],["==","kind","minor_road"],["==","kind_detail","service"]],paint:{"line-color":e.minor_service_casing,"line-gap-width":["interpolate",["exponential",1.6],["zoom"],13,0,18,8],"line-width":["interpolate",["exponential",1.6],["zoom"],13,0,13.5,.8]}},{id:"roads_minor_casing",type:"line",source:i,"source-layer":"roads",filter:["all",["!has","is_tunnel"],["!has","is_bridge"],["==","kind","minor_road"],["!=","kind_detail","service"]],paint:{"line-color":e.minor_casing,"line-gap-width":["interpolate",["exponential",1.6],["zoom"],11,0,12.5,.5,15,2,18,11],"line-width":["interpolate",["exponential",1.6],["zoom"],12,0,12.5,1]}},{id:"roads_link_casing",type:"line",source:i,"source-layer":"roads",minzoom:13,filter:["has","is_link"],paint:{"line-color":e.minor_casing,"line-gap-width":["interpolate",["exponential",1.6],["zoom"],13,0,13.5,1,18,11],"line-width":["interpolate",["exponential",1.6],["zoom"],13,0,13.5,1.5]}},{id:"roads_major_casing_late",type:"line",source:i,"source-layer":"roads",minzoom:12,filter:["all",["!has","is_tunnel"],["!has","is_bridge"],["==","kind","major_road"]],paint:{"line-color":e.major_casing_late,"line-gap-width":["interpolate",["exponential",1.6],["zoom"],6,0,12,1.6,15,3,18,13],"line-width":["interpolate",["exponential",1.6],["zoom"],9,0,9.5,1]}},{id:"roads_highway_casing_late",type:"line",source:i,"source-layer":"roads",minzoom:12,filter:["all",["!has","is_tunnel"],["!has","is_bridge"],["==","kind","highway"],["!has","is_link"]],paint:{"line-color":e.highway_casing_late,"line-gap-width":["interpolate",["exponential",1.6],["zoom"],3,0,3.5,.5,18,15],"line-width":["interpolate",["exponential",1.6],["zoom"],7,0,7.5,1,20,15]}},{id:"roads_other",type:"line",source:i,"source-layer":"roads",filter:["all",["!has","is_tunnel"],["!has","is_bridge"],["in","kind","other","path"],["!=","kind_detail","pier"]],paint:{"line-color":e.other,"line-dasharray":[3,1],"line-width":["interpolate",["exponential",1.6],["zoom"],14,0,20,7]}},{id:"roads_link",type:"line",source:i,"source-layer":"roads",filter:["has","is_link"],paint:{"line-color":e.link,"line-width":["interpolate",["exponential",1.6],["zoom"],13,0,13.5,1,18,11]}},{id:"roads_minor_service",type:"line",source:i,"source-layer":"roads",filter:["all",["!has","is_tunnel"],["!has","is_bridge"],["==","kind","minor_road"],["==","kind_detail","service"]],paint:{"line-color":e.minor_service,"line-width":["interpolate",["exponential",1.6],["zoom"],13,0,18,8]}},{id:"roads_minor",type:"line",source:i,"source-layer":"roads",filter:["all",["!has","is_tunnel"],["!has","is_bridge"],["==","kind","minor_road"],["!=","kind_detail","service"]],paint:{"line-color":["interpolate",["exponential",1.6],["zoom"],11,e.minor_a,16,e.minor_b],"line-width":["interpolate",["exponential",1.6],["zoom"],11,0,12.5,.5,15,2,18,11]}},{id:"roads_major_casing_early",type:"line",source:i,"source-layer":"roads",maxzoom:12,filter:["all",["!has","is_tunnel"],["!has","is_bridge"],["==","kind","major_road"]],paint:{"line-color":e.major_casing_early,"line-gap-width":["interpolate",["exponential",1.6],["zoom"],7,0,7.5,.5,18,13],"line-width":["interpolate",["exponential",1.6],["zoom"],9,0,9.5,1]}},{id:"roads_major",type:"line",source:i,"source-layer":"roads",filter:["all",["!has","is_tunnel"],["!has","is_bridge"],["==","kind","major_road"]],paint:{"line-color":e.major,"line-width":["interpolate",["exponential",1.6],["zoom"],6,0,12,1.6,15,3,18,13]}},{id:"roads_highway_casing_early",type:"line",source:i,"source-layer":"roads",maxzoom:12,filter:["all",["!has","is_tunnel"],["!has","is_bridge"],["==","kind","highway"],["!has","is_link"]],paint:{"line-color":e.highway_casing_early,"line-gap-width":["interpolate",["exponential",1.6],["zoom"],3,0,3.5,.5,18,15],"line-width":["interpolate",["exponential",1.6],["zoom"],7,0,7.5,1]}},{id:"roads_highway",type:"line",source:i,"source-layer":"roads",filter:["all",["!has","is_tunnel"],["!has","is_bridge"],["==","kind","highway"],["!has","is_link"]],paint:{"line-color":e.highway,"line-width":["interpolate",["exponential",1.6],["zoom"],3,0,6,1.1,12,1.6,15,5,18,15]}},{id:"roads_rail",type:"line",source:i,"source-layer":"roads",filter:["==","kind","rail"],paint:{"line-dasharray":[.3,.75],"line-opacity":.5,"line-color":e.railway,"line-width":["interpolate",["exponential",1.6],["zoom"],3,0,6,.15,18,9]}},{id:"boundaries_country",type:"line",source:i,"source-layer":"boundaries",filter:["<=","kind_detail",2],paint:{"line-color":e.boundaries,"line-width":.7,"line-dasharray":["step",["zoom"],["literal",[2]],4,["literal",[2,1]]]}},{id:"boundaries",type:"line",source:i,"source-layer":"boundaries",filter:[">","kind_detail",2],paint:{"line-color":e.boundaries,"line-width":.4,"line-dasharray":["step",["zoom"],["literal",[2]],4,["literal",[2,1]]]}},{id:"roads_bridges_other_casing",type:"line",source:i,"source-layer":"roads",minzoom:12,filter:["all",["has","is_bridge"],["in","kind","other","path"]],paint:{"line-color":e.bridges_other_casing,"line-gap-width":["interpolate",["exponential",1.6],["zoom"],14,0,20,7]}},{id:"roads_bridges_link_casing",type:"line",source:i,"source-layer":"roads",minzoom:12,filter:["all",["has","is_bridge"],["has","is_link"]],paint:{"line-color":e.bridges_minor_casing,"line-gap-width":["interpolate",["exponential",1.6],["zoom"],13,0,13.5,1,18,11],"line-width":["interpolate",["exponential",1.6],["zoom"],12,0,12.5,1.5]}},{id:"roads_bridges_minor_casing",type:"line",source:i,"source-layer":"roads",minzoom:12,filter:["all",["has","is_bridge"],["==","kind","minor_road"]],paint:{"line-color":e.bridges_minor_casing,"line-gap-width":["interpolate",["exponential",1.6],["zoom"],11,0,12.5,.5,15,2,18,11],"line-width":["interpolate",["exponential",1.6],["zoom"],13,0,13.5,.8]}},{id:"roads_bridges_major_casing",type:"line",source:i,"source-layer":"roads",minzoom:12,filter:["all",["has","is_bridge"],["==","kind","major_road"]],paint:{"line-color":e.bridges_major_casing,"line-gap-width":["interpolate",["exponential",1.6],["zoom"],7,0,7.5,.5,18,10],"line-width":["interpolate",["exponential",1.6],["zoom"],9,0,9.5,1.5]}},{id:"roads_bridges_other",type:"line",source:i,"source-layer":"roads",minzoom:12,filter:["all",["has","is_bridge"],["in","kind","other","path"]],paint:{"line-color":e.bridges_other,"line-dasharray":[2,1],"line-width":["interpolate",["exponential",1.6],["zoom"],14,0,20,7]}},{id:"roads_bridges_minor",type:"line",source:i,"source-layer":"roads",minzoom:12,filter:["all",["has","is_bridge"],["==","kind","minor_road"]],paint:{"line-color":e.bridges_minor,"line-width":["interpolate",["exponential",1.6],["zoom"],11,0,12.5,.5,15,2,18,11]}},{id:"roads_bridges_link",type:"line",source:i,"source-layer":"roads",minzoom:12,filter:["all",["has","is_bridge"],["has","is_link"]],paint:{"line-color":e.bridges_minor,"line-width":["interpolate",["exponential",1.6],["zoom"],13,0,13.5,1,18,11]}},{id:"roads_bridges_major",type:"line",source:i,"source-layer":"roads",minzoom:12,filter:["all",["has","is_bridge"],["==","kind","major_road"]],paint:{"line-color":e.bridges_major,"line-width":["interpolate",["exponential",1.6],["zoom"],6,0,12,1.6,15,3,18,13]}},{id:"roads_bridges_highway_casing",type:"line",source:i,"source-layer":"roads",minzoom:12,filter:["all",["has","is_bridge"],["==","kind","highway"],["!has","is_link"]],paint:{"line-color":e.bridges_highway_casing,"line-gap-width":["interpolate",["exponential",1.6],["zoom"],3,0,3.5,.5,18,15],"line-width":["interpolate",["exponential",1.6],["zoom"],7,0,7.5,1,20,15]}},{id:"roads_bridges_highway",type:"line",source:i,"source-layer":"roads",filter:["all",["has","is_bridge"],["==","kind","highway"],["!has","is_link"]],paint:{"line-color":e.bridges_highway,"line-width":["interpolate",["exponential",1.6],["zoom"],3,0,6,1.1,12,1.6,15,5,18,15]}}]}l(Ti,"f");J(Ti,"nolabels_layers");function Fi(i,e,t,n){return[{id:"address_label",type:"symbol",source:i,"source-layer":"buildings",minzoom:18,filter:["==","kind","address"],layout:{"symbol-placement":"point","text-font":[e.italic||"Noto Sans Italic"],"text-field":["get","addr_housenumber"],"text-size":12},paint:{"text-color":e.address_label,"text-halo-color":e.address_label_halo,"text-halo-width":1}},{id:"water_waterway_label",type:"symbol",source:i,"source-layer":"water",minzoom:13,filter:["in","kind","river","stream"],layout:{"symbol-placement":"line","text-font":[e.italic||"Noto Sans Italic"],"text-field":Z(t,n,e.regular),"text-size":12,"text-letter-spacing":.2},paint:{"text-color":e.ocean_label,"text-halo-color":e.water,"text-halo-width":1}},{id:"roads_labels_minor",type:"symbol",source:i,"source-layer":"roads",minzoom:15,filter:["in","kind","minor_road","other","path"],layout:{"symbol-sort-key":["get","min_zoom"],"symbol-placement":"line","text-font":[e.regular||"Noto Sans Regular"],"text-field":Z(t,n,e.regular),"text-size":12},paint:{"text-color":e.roads_label_minor,"text-halo-color":e.roads_label_minor_halo,"text-halo-width":1}},{id:"water_label_ocean",type:"symbol",source:i,"source-layer":"water",filter:["in","kind","sea","ocean","bay","strait","fjord"],layout:{"text-font":[e.italic||"Noto Sans Italic"],"text-field":Z(t,n,e.regular),"text-size":["interpolate",["linear"],["zoom"],3,10,10,12],"text-letter-spacing":.1,"text-max-width":9,"text-transform":"uppercase"},paint:{"text-color":e.ocean_label,"text-halo-width":1,"text-halo-color":e.water}},{id:"water_label_lakes",type:"symbol",source:i,"source-layer":"water",filter:["in","kind","lake","water"],layout:{"text-font":[e.italic||"Noto Sans Italic"],"text-field":Z(t,n,e.regular),"text-size":["interpolate",["linear"],["zoom"],3,10,6,12,10,12],"text-letter-spacing":.1,"text-max-width":9},paint:{"text-color":e.ocean_label,"text-halo-color":e.water,"text-halo-width":1}},{id:"roads_labels_major",type:"symbol",source:i,"source-layer":"roads",minzoom:11,filter:["in","kind","highway","major_road"],layout:{"symbol-sort-key":["get","min_zoom"],"symbol-placement":"line","text-font":[e.regular||"Noto Sans Regular"],"text-field":Z(t,n,e.regular),"text-size":12},paint:{"text-color":e.roads_label_major,"text-halo-color":e.roads_label_major_halo,"text-halo-width":1}},...e.pois?[{id:"pois",type:"symbol",source:i,"source-layer":"pois",filter:["all",["in",["get","kind"],["literal",["beach","forest","marina","park","peak","zoo","garden","bench","aerodrome","station","bus_stop","ferry_terminal","stadium","university","library","school","animal","toilets","drinking_water"]]],[">=",["zoom"],["+",["get","min_zoom"],0]]],layout:{"icon-image":["match",["get","kind"],"station","train_station",["get","kind"]],"text-font":[e.regular||"Noto Sans Regular"],"text-justify":"auto","text-field":Z(t,n,e.regular),"text-size":["interpolate",["linear"],["zoom"],17,10,19,16],"text-max-width":8,"text-offset":[1.1,0],"text-variable-anchor":["left","right"]},paint:{"text-color":["case",["in",["get","kind"],["literal",["beach","forest","marina","park","peak","zoo","garden","bench"]]],e.pois.green,["in",["get","kind"],["literal",["aerodrome","station","bus_stop","ferry_terminal"]]],e.pois.lapis,["in",["get","kind"],["literal",["stadium","university","library","school","animal","toilets","drinking_water"]]],e.pois.slategray,e.earth],"text-halo-color":e.earth,"text-halo-width":1}}]:[],{id:"places_subplace",type:"symbol",source:i,"source-layer":"places",filter:["==","kind","neighbourhood"],layout:{"symbol-sort-key":["get","min_zoom"],"text-field":Z(t,n,e.regular),"text-font":[e.regular||"Noto Sans Regular"],"text-max-width":7,"text-letter-spacing":.1,"text-padding":["interpolate",["linear"],["zoom"],5,2,8,4,12,18,15,20],"text-size":["interpolate",["exponential",1.2],["zoom"],11,8,14,14,18,24],"text-transform":"uppercase"},paint:{"text-color":e.subplace_label,"text-halo-color":e.subplace_label_halo,"text-halo-width":1}},{id:"places_locality",type:"symbol",source:i,"source-layer":"places",filter:["==","kind","locality"],layout:{"icon-image":["step",["zoom"],"townspot",8,""],"icon-size":.7,"text-field":Z(t,n,e.regular),"text-font":["case",["<=",["get","min_zoom"],5],["literal",[e.bold||"Noto Sans Medium"]],["literal",[e.regular||"Noto Sans Regular"]]],"text-padding":["interpolate",["linear"],["zoom"],5,3,8,7,12,11],"text-size":["interpolate",["linear"],["zoom"],2,["case",["<",["get","population_rank"],13],8,[">=",["get","population_rank"],13],13,0],4,["case",["<",["get","population_rank"],13],10,[">=",["get","population_rank"],13],15,0],6,["case",["<",["get","population_rank"],12],11,[">=",["get","population_rank"],12],17,0],8,["case",["<",["get","population_rank"],11],11,[">=",["get","population_rank"],11],18,0],10,["case",["<",["get","population_rank"],9],12,[">=",["get","population_rank"],9],20,0],15,["case",["<",["get","population_rank"],8],12,[">=",["get","population_rank"],8],22,0]],"icon-padding":["interpolate",["linear"],["zoom"],0,0,8,4,10,8,12,6,22,2],"text-justify":"auto","text-anchor":["step",["zoom"],"left",8,"center"],"text-radial-offset":.4},paint:{"text-color":e.city_label,"text-halo-color":e.city_label_halo,"text-halo-width":1}},{id:"places_region",type:"symbol",source:i,"source-layer":"places",filter:["==","kind","region"],layout:{"symbol-sort-key":["get","min_zoom"],"text-field":["step",["zoom"],["get","name:short"],6,Z(t,n,e.regular)],"text-font":[e.regular||"Noto Sans Regular"],"text-size":["interpolate",["linear"],["zoom"],3,11,7,16],"text-radial-offset":.2,"text-anchor":"center","text-transform":"uppercase"},paint:{"text-color":e.state_label,"text-halo-color":e.state_label_halo,"text-halo-width":1}},{id:"places_country",type:"symbol",source:i,"source-layer":"places",filter:["==","kind","country"],layout:{"symbol-sort-key":["get","min_zoom"],"text-field":Si(t,n),"text-font":[e.bold||"Noto Sans Medium"],"text-size":["interpolate",["linear"],["zoom"],2,["case",["<",["get","population_rank"],10],8,[">=",["get","population_rank"],10],12,0],6,["case",["<",["get","population_rank"],8],10,[">=",["get","population_rank"],8],18,0],8,["case",["<",["get","population_rank"],7],11,[">=",["get","population_rank"],7],20,0]],"icon-padding":["interpolate",["linear"],["zoom"],0,2,14,2,16,20,17,2,22,2],"text-transform":"uppercase"},paint:{"text-color":e.country_label,"text-halo-color":e.earth,"text-halo-width":1}}]}l(Fi,"p");J(Fi,"labels_layers");var An={background:"#cccccc",earth:"#e2dfda",park_a:"#cfddd5",park_b:"#9cd3b4",hospital:"#e4dad9",industrial:"#d1dde1",school:"#e4ded7",wood_a:"#d0ded0",wood_b:"#a0d9a0",pedestrian:"#e3e0d4",scrub_a:"#cedcd7",scrub_b:"#99d2bb",glacier:"#e7e7e7",sand:"#e2e0d7",beach:"#e8e4d0",aerodrome:"#dadbdf",runway:"#e9e9ed",water:"#80deea",zoo:"#c6dcdc",military:"#dcdcdc",tunnel_other_casing:"#e0e0e0",tunnel_minor_casing:"#e0e0e0",tunnel_link_casing:"#e0e0e0",tunnel_major_casing:"#e0e0e0",tunnel_highway_casing:"#e0e0e0",tunnel_other:"#d5d5d5",tunnel_minor:"#d5d5d5",tunnel_link:"#d5d5d5",tunnel_major:"#d5d5d5",tunnel_highway:"#d5d5d5",pier:"#e0e0e0",buildings:"#cccccc",minor_service_casing:"#e0e0e0",minor_casing:"#e0e0e0",link_casing:"#e0e0e0",major_casing_late:"#e0e0e0",highway_casing_late:"#e0e0e0",other:"#ebebeb",minor_service:"#ebebeb",minor_a:"#ebebeb",minor_b:"#ffffff",link:"#ffffff",major_casing_early:"#e0e0e0",major:"#ffffff",highway_casing_early:"#e0e0e0",highway:"#ffffff",railway:"#a7b1b3",boundaries:"#adadad",bridges_other_casing:"#e0e0e0",bridges_minor_casing:"#e0e0e0",bridges_link_casing:"#e0e0e0",bridges_major_casing:"#e0e0e0",bridges_highway_casing:"#e0e0e0",bridges_other:"#ebebeb",bridges_minor:"#ffffff",bridges_link:"#ffffff",bridges_major:"#f5f5f5",bridges_highway:"#ffffff",roads_label_minor:"#91888b",roads_label_minor_halo:"#ffffff",roads_label_major:"#938a8d",roads_label_major_halo:"#ffffff",ocean_label:"#728dd4",subplace_label:"#8f8f8f",subplace_label_halo:"#e0e0e0",city_label:"#5c5c5c",city_label_halo:"#e0e0e0",state_label:"#b3b3b3",state_label_halo:"#e0e0e0",country_label:"#a3a3a3",address_label:"#91888b",address_label_halo:"#ffffff",pois:{blue:"#1A8CBD",green:"#20834D",lapis:"#315BCF",pink:"#EF56BA",red:"#F2567A",slategray:"#6A5B8F",tangerine:"#CB6704",turquoise:"#00C3D4"},landcover:{grassland:"rgba(210, 239, 207, 1)",barren:"rgba(255, 243, 215, 1)",urban_area:"rgba(230, 230, 230, 1)",farmland:"rgba(216, 239, 210, 1)",glacier:"rgba(255, 255, 255, 1)",scrub:"rgba(234, 239, 210, 1)",forest:"rgba(196, 231, 210, 1)"}},Bn={background:"#34373d",earth:"#1f1f1f",park_a:"#1c2421",park_b:"#192a24",hospital:"#252424",industrial:"#222222",school:"#262323",wood_a:"#202121",wood_b:"#202121",pedestrian:"#1e1e1e",scrub_a:"#222323",scrub_b:"#222323",glacier:"#1c1c1c",sand:"#212123",beach:"#28282a",aerodrome:"#1e1e1e",runway:"#333333",water:"#31353f",zoo:"#222323",military:"#242323",tunnel_other_casing:"#141414",tunnel_minor_casing:"#141414",tunnel_link_casing:"#141414",tunnel_major_casing:"#141414",tunnel_highway_casing:"#141414",tunnel_other:"#292929",tunnel_minor:"#292929",tunnel_link:"#292929",tunnel_major:"#292929",tunnel_highway:"#292929",pier:"#333333",buildings:"#111111",minor_service_casing:"#1f1f1f",minor_casing:"#1f1f1f",link_casing:"#1f1f1f",major_casing_late:"#1f1f1f",highway_casing_late:"#1f1f1f",other:"#333333",minor_service:"#333333",minor_a:"#3d3d3d",minor_b:"#333333",link:"#3d3d3d",major_casing_early:"#1f1f1f",major:"#3d3d3d",highway_casing_early:"#1f1f1f",highway:"#474747",railway:"#000000",boundaries:"#5b6374",bridges_other_casing:"#2b2b2b",bridges_minor_casing:"#1f1f1f",bridges_link_casing:"#1f1f1f",bridges_major_casing:"#1f1f1f",bridges_highway_casing:"#1f1f1f",bridges_other:"#333333",bridges_minor:"#333333",bridges_link:"#3d3d3d",bridges_major:"#3d3d3d",bridges_highway:"#474747",roads_label_minor:"#525252",roads_label_minor_halo:"#1f1f1f",roads_label_major:"#666666",roads_label_major_halo:"#1f1f1f",ocean_label:"#717784",subplace_label:"#525252",subplace_label_halo:"#1f1f1f",city_label:"#7a7a7a",city_label_halo:"#212121",state_label:"#3d3d3d",state_label_halo:"#1f1f1f",country_label:"#5c5c5c",address_label:"#525252",address_label_halo:"#1f1f1f",pois:{blue:"#4299BB",green:"#30C573",lapis:"#2B5CEA",pink:"#EF56BA",red:"#F2567A",slategray:"#93939F",tangerine:"#F19B6E",turquoise:"#00C3D4"},landcover:{grassland:"rgba(30, 41, 31, 1)",barren:"rgba(38, 38, 36, 1)",urban_area:"rgba(28, 28, 28, 1)",farmland:"rgba(31, 36, 32, 1)",glacier:"rgba(43, 43, 43, 1)",scrub:"rgba(34, 36, 30, 1)",forest:"rgba(28, 41, 37, 1)"}},Rn={background:"#ffffff",earth:"#ffffff",park_a:"#fcfcfc",park_b:"#fcfcfc",hospital:"#f8f8f8",industrial:"#fcfcfc",school:"#f8f8f8",wood_a:"#fafafa",wood_b:"#fafafa",pedestrian:"#fdfdfd",scrub_a:"#fafafa",scrub_b:"#fafafa",glacier:"#fcfcfc",sand:"#fafafa",beach:"#f6f6f6",aerodrome:"#fdfdfd",runway:"#efefef",water:"#dcdcdc",zoo:"#f7f7f7",military:"#fcfcfc",tunnel_other_casing:"#d6d6d6",tunnel_minor_casing:"#fcfcfc",tunnel_link_casing:"#fcfcfc",tunnel_major_casing:"#fcfcfc",tunnel_highway_casing:"#fcfcfc",tunnel_other:"#d6d6d6",tunnel_minor:"#d6d6d6",tunnel_link:"#d6d6d6",tunnel_major:"#d6d6d6",tunnel_highway:"#d6d6d6",pier:"#efefef",buildings:"#efefef",minor_service_casing:"#ffffff",minor_casing:"#ffffff",link_casing:"#ffffff",major_casing_late:"#ffffff",highway_casing_late:"#ffffff",other:"#f5f5f5",minor_service:"#f5f5f5",minor_a:"#ebebeb",minor_b:"#f5f5f5",link:"#ebebeb",major_casing_early:"#ffffff",major:"#ebebeb",highway_casing_early:"#ffffff",highway:"#ebebeb",railway:"#d6d6d6",boundaries:"#adadad",bridges_other_casing:"#ffffff",bridges_minor_casing:"#ffffff",bridges_link_casing:"#ffffff",bridges_major_casing:"#ffffff",bridges_highway_casing:"#ffffff",bridges_other:"#f5f5f5",bridges_minor:"#f5f5f5",bridges_link:"#ebebeb",bridges_major:"#ebebeb",bridges_highway:"#ebebeb",roads_label_minor:"#adadad",roads_label_minor_halo:"#ffffff",roads_label_major:"#999999",roads_label_major_halo:"#ffffff",ocean_label:"#adadad",subplace_label:"#8f8f8f",subplace_label_halo:"#ffffff",city_label:"#5c5c5c",city_label_halo:"#ffffff",state_label:"#b3b3b3",state_label_halo:"#ffffff",country_label:"#b8b8b8",address_label:"#adadad",address_label_halo:"#ffffff"},jn={background:"#a3a3a3",earth:"#cccccc",park_a:"#c2c2c2",park_b:"#c2c2c2",hospital:"#d0d0d0",industrial:"#c6c6c6",school:"#d0d0d0",wood_a:"#c2c2c2",wood_b:"#c2c2c2",pedestrian:"#c4c4c4",scrub_a:"#c2c2c2",scrub_b:"#c2c2c2",glacier:"#d2d2d2",sand:"#d2d2d2",beach:"#d2d2d2",aerodrome:"#c9c9c9",runway:"#f5f5f5",water:"#a3a3a3",zoo:"#c7c7c7",military:"#bfbfbf",tunnel_other_casing:"#b8b8b8",tunnel_minor_casing:"#b8b8b8",tunnel_link_casing:"#b8b8b8",tunnel_major_casing:"#b8b8b8",tunnel_highway_casing:"#b8b8b8",tunnel_other:"#d6d6d6",tunnel_minor:"#d6d6d6",tunnel_link:"#d6d6d6",tunnel_major:"#d6d6d6",tunnel_highway:"#d6d6d6",pier:"#b8b8b8",buildings:"#e0e0e0",minor_service_casing:"#cccccc",minor_casing:"#cccccc",link_casing:"#cccccc",major_casing_late:"#cccccc",highway_casing_late:"#cccccc",other:"#e0e0e0",minor_service:"#e0e0e0",minor_a:"#ebebeb",minor_b:"#e0e0e0",link:"#ebebeb",major_casing_early:"#cccccc",major:"#ebebeb",highway_casing_early:"#cccccc",highway:"#ebebeb",railway:"#f5f5f5",boundaries:"#5c5c5c",bridges_other_casing:"#cccccc",bridges_minor_casing:"#cccccc",bridges_link_casing:"#cccccc",bridges_major_casing:"#cccccc",bridges_highway_casing:"#cccccc",bridges_other:"#e0e0e0",bridges_minor:"#e0e0e0",bridges_link:"#ebebeb",bridges_major:"#ebebeb",bridges_highway:"#ebebeb",roads_label_minor:"#999999",roads_label_minor_halo:"#e0e0e0",roads_label_major:"#8f8f8f",roads_label_major_halo:"#ebebeb",ocean_label:"#7a7a7a",subplace_label:"#7a7a7a",subplace_label_halo:"#cccccc",city_label:"#474747",city_label_halo:"#cccccc",state_label:"#999999",state_label_halo:"#cccccc",country_label:"#858585",address_label:"#999999",address_label_halo:"#e0e0e0"},On={background:"#2b2b2b",earth:"#141414",park_a:"#181818",park_b:"#181818",hospital:"#1d1d1d",industrial:"#101010",school:"#111111",wood_a:"#1a1a1a",wood_b:"#1a1a1a",pedestrian:"#191919",scrub_a:"#1c1c1c",scrub_b:"#1c1c1c",glacier:"#191919",sand:"#161616",beach:"#1f1f1f",aerodrome:"#191919",runway:"#323232",water:"#333333",zoo:"#191919",military:"#121212",tunnel_other_casing:"#101010",tunnel_minor_casing:"#101010",tunnel_link_casing:"#101010",tunnel_major_casing:"#101010",tunnel_highway_casing:"#101010",tunnel_other:"#292929",tunnel_minor:"#292929",tunnel_link:"#292929",tunnel_major:"#292929",tunnel_highway:"#292929",pier:"#0a0a0a",buildings:"#0a0a0a",minor_service_casing:"#141414",minor_casing:"#141414",link_casing:"#141414",major_casing_late:"#141414",highway_casing_late:"#141414",other:"#1f1f1f",minor_service:"#1f1f1f",minor_a:"#292929",minor_b:"#1f1f1f",link:"#1f1f1f",major_casing_early:"#141414",major:"#292929",highway_casing_early:"#141414",highway:"#292929",railway:"#292929",boundaries:"#707070",bridges_other_casing:"#141414",bridges_minor_casing:"#141414",bridges_link_casing:"#141414",bridges_major_casing:"#141414",bridges_highway_casing:"#141414",bridges_other:"#1f1f1f",bridges_minor:"#1f1f1f",bridges_link:"#292929",bridges_major:"#292929",bridges_highway:"#292929",roads_label_minor:"#525252",roads_label_minor_halo:"#141414",roads_label_major:"#5c5c5c",roads_label_major_halo:"#141414",ocean_label:"#707070",subplace_label:"#5c5c5c",subplace_label_halo:"#141414",city_label:"#999999",city_label_halo:"#141414",state_label:"#3d3d3d",state_label_halo:"#141414",country_label:"#707070",address_label:"#525252",address_label_halo:"#141414"};function we(i){switch(i){case"light":return An;case"dark":return Bn;case"white":return Rn;case"grayscale":return jn;case"black":return On}throw new Error("Flavor not found")}l(we,"C");J(we,"namedFlavor");function In(i,e,t){let n=[];return t!=null&&t.labelsOnly||(n=Ti(i,e)),t!=null&&t.lang&&(n=n.concat(Fi(i,e,t.lang))),n}l(In,"R");J(In,"layers");function re(i,e,t){return Math.min(Math.max(i,t),e)}l(re,"guard");var yt=class yt extends Error{constructor(e){super(`Failed to parse color: "${e}"`)}};l(yt,"ColorError");var bt=yt,ke=bt;function Mi(i){if(typeof i!="string")throw new ke(i);if(i.trim().toLowerCase()==="transparent")return[0,0,0,0];let e=i.trim();e=Hn.test(i)?Yn(i):i;let t=Vn.exec(e);if(t){let o=Array.from(t).slice(1);return[...o.slice(0,3).map(s=>parseInt(ve(s,2),16)),parseInt(ve(o[3]||"f",2),16)/255]}let n=$n.exec(e);if(n){let o=Array.from(n).slice(1);return[...o.slice(0,3).map(s=>parseInt(s,16)),parseInt(o[3]||"ff",16)/255]}let r=Un.exec(e);if(r){let o=Array.from(r).slice(1);return[...o.slice(0,3).map(s=>parseInt(s,10)),parseFloat(o[3]||"1")]}let a=Nn.exec(e);if(a){let[o,s,c,u]=Array.from(a).slice(1).map(parseFloat);if(re(0,100,s)!==s)throw new ke(i);if(re(0,100,c)!==c)throw new ke(i);return[...qn(o,s,c),Number.isNaN(u)?1:u]}throw new ke(i)}l(Mi,"parseToRgba");function En(i){let e=5381,t=i.length;for(;t;)e=e*33^i.charCodeAt(--t);return(e>>>0)%2341}l(En,"hash");var Ci=l(i=>parseInt(i.replace(/_/g,""),36),"colorToInt"),Xn="1q29ehhb 1n09sgk7 1kl1ekf_ _yl4zsno 16z9eiv3 1p29lhp8 _bd9zg04 17u0____ _iw9zhe5 _to73___ _r45e31e _7l6g016 _jh8ouiv _zn3qba8 1jy4zshs 11u87k0u 1ro9yvyo 1aj3xael 1gz9zjz0 _3w8l4xo 1bf1ekf_ _ke3v___ _4rrkb__ 13j776yz _646mbhl _nrjr4__ _le6mbhl 1n37ehkb _m75f91n _qj3bzfz 1939yygw 11i5z6x8 _1k5f8xs 1509441m 15t5lwgf _ae2th1n _tg1ugcv 1lp1ugcv 16e14up_ _h55rw7n _ny9yavn _7a11xb_ 1ih442g9 _pv442g9 1mv16xof 14e6y7tu 1oo9zkds 17d1cisi _4v9y70f _y98m8kc 1019pq0v 12o9zda8 _348j4f4 1et50i2o _8epa8__ _ts6senj 1o350i2o 1mi9eiuo 1259yrp0 1ln80gnw _632xcoy 1cn9zldc _f29edu4 1n490c8q _9f9ziet 1b94vk74 _m49zkct 1kz6s73a 1eu9dtog _q58s1rz 1dy9sjiq __u89jo3 _aj5nkwg _ld89jo3 13h9z6wx _qa9z2ii _l119xgq _bs5arju 1hj4nwk9 1qt4nwk9 1ge6wau6 14j9zlcw 11p1edc_ _ms1zcxe _439shk6 _jt9y70f _754zsow 1la40eju _oq5p___ _x279qkz 1fa5r3rv _yd2d9ip _424tcku _8y1di2_ _zi2uabw _yy7rn9h 12yz980_ __39ljp6 1b59zg0x _n39zfzp 1fy9zest _b33k___ _hp9wq92 1il50hz4 _io472ub _lj9z3eo 19z9ykg0 _8t8iu3a 12b9bl4a 1ak5yw0o _896v4ku _tb8k8lv _s59zi6t _c09ze0p 1lg80oqn 1id9z8wb _238nba5 1kq6wgdi _154zssg _tn3zk49 _da9y6tc 1sg7cv4f _r12jvtt 1gq5fmkz 1cs9rvci _lp9jn1c _xw1tdnb 13f9zje6 16f6973h _vo7ir40 _bt5arjf _rc45e4t _hr4e100 10v4e100 _hc9zke2 _w91egv_ _sj2r1kk 13c87yx8 _vqpds__ _ni8ggk8 _tj9yqfb 1ia2j4r4 _7x9b10u 1fc9ld4j 1eq9zldr _5j9lhpx _ez9zl6o _md61fzm".split(" ").reduce((i,e)=>{let t=Ci(e.substring(0,3)),n=Ci(e.substring(3)).toString(16),r="";for(let a=0;a<6-n.length;a++)r+="0";return i[t]=`${r}${n}`,i},{});function Yn(i){let e=i.toLowerCase().trim(),t=Xn[En(e)];if(!t)throw new ke(i);return`#${t}`}l(Yn,"nameToHex");var ve=l((i,e)=>Array.from(Array(e)).map(()=>i).join(""),"r"),Vn=new RegExp(`^#${ve("([a-f0-9])",3)}([a-f0-9])?$`,"i"),$n=new RegExp(`^#${ve("([a-f0-9]{2})",3)}([a-f0-9]{2})?$`,"i"),Un=new RegExp(`^rgba?\\(\\s*(\\d+)\\s*${ve(",\\s*(\\d+)\\s*",2)}(?:,\\s*([\\d.]+))?\\s*\\)$`,"i"),Nn=/^hsla?\(\s*([\d.]+)\s*,\s*([\d.]+)%\s*,\s*([\d.]+)%(?:\s*,\s*([\d.]+))?\s*\)$/i,Hn=/^[a-z]+$/i,Di=l(i=>Math.round(i*255),"roundColor"),qn=l((i,e,t)=>{let n=t/100;if(e===0)return[n,n,n].map(Di);let r=(i%360+360)%360/60,a=(1-Math.abs(2*n-1))*(e/100),o=a*(1-Math.abs(r%2-1)),s=0,c=0,u=0;r>=0&&r<1?(s=a,c=o):r>=1&&r<2?(s=o,c=a):r>=2&&r<3?(c=a,u=o):r>=3&&r<4?(c=o,u=a):r>=4&&r<5?(s=o,u=a):r>=5&&r<6&&(s=a,u=o);let d=n-a/2,h=s+d,f=c+d,m=u+d;return[h,f,m].map(Di)},"hslToRgb");function Wn(i,e,t,n){return`rgba(${re(0,255,i).toFixed()}, ${re(0,255,e).toFixed()}, ${re(0,255,t).toFixed()}, ${parseFloat(re(0,1,n).toFixed(3))})`}l(Wn,"rgba");function Ze(i,e,t){let n=l((b,S)=>S===3?b:b/255,"normalize"),[r,a,o,s]=Mi(i).map(n),[c,u,d,h]=Mi(e).map(n),f=h-s,m=t*2-1,y=((m*f===-1?m:m+f/(1+m*f))+1)/2,_=1-y,v=(r*_+c*y)*255,g=(a*_+u*y)*255,w=(o*_+d*y)*255,F=h*t+s*(1-t);return Wn(v,g,w,F)}l(Ze,"mix");var xt=class xt{constructor(e,t){this.str=e!=null?e:t,this.perFeature=typeof this.str=="function"&&this.str.length===2}get(e,t){return typeof this.str=="function"?this.str(e,t):this.str}};l(xt,"StringAttr");var B=xt,_t=class _t{constructor(e,t=1){this.value=e!=null?e:t,this.perFeature=typeof this.value=="function"&&this.value.length===2}get(e,t){return typeof this.value=="function"?this.value(e,t):this.value}};l(_t,"NumberAttr");var T=_t,wt=class wt{constructor(e){var t;this.labelProps=(t=e==null?void 0:e.labelProps)!=null?t:["name"],this.textTransform=e==null?void 0:e.textTransform}get(e,t){let n,r;typeof this.labelProps=="function"?r=this.labelProps(e,t):r=this.labelProps;for(let o of r)if(Object.prototype.hasOwnProperty.call(t.props,o)&&typeof t.props[o]=="string"){n=t.props[o];break}let a;return typeof this.textTransform=="function"?a=this.textTransform(e,t):a=this.textTransform,n&&a==="uppercase"?n=n.toUpperCase():n&&a==="lowercase"?n=n.toLowerCase():n&&a==="capitalize"&&(n=n.toLowerCase().split(" ").map(c=>c[0].toUpperCase()+c.slice(1)).join(" ")),n}};l(wt,"TextAttr");var ae=wt,kt=class kt{constructor(e){var t,n;e!=null&&e.font?this.font=e.font:(this.family=(t=e==null?void 0:e.fontFamily)!=null?t:"sans-serif",this.size=(n=e==null?void 0:e.fontSize)!=null?n:12,this.weight=e==null?void 0:e.fontWeight,this.style=e==null?void 0:e.fontStyle)}get(e,t){if(this.font)return typeof this.font=="function"?this.font(e,t):this.font;let n="";this.style&&(typeof this.style=="function"?n=`${this.style(e,t)} `:n=`${this.style} `);let r="";this.weight&&(typeof this.weight=="function"?r=`${this.weight(e,t)} `:r=`${this.weight} `);let a;typeof this.size=="function"?a=this.size(e,t):a=this.size;let o;return typeof this.family=="function"?o=this.family(e,t):o=this.family,`${n}${r}${a}px ${o}`}};l(kt,"FontAttr");var oe=kt,vt=class vt{constructor(e,t=[]){this.value=e!=null?e:t,this.perFeature=typeof this.value=="function"&&this.value.length===2}get(e,t){return typeof this.value=="function"?this.value(e,t):this.value}};l(vt,"ArrayAttr");var Je=vt;var Zn=l((i,e,t)=>{let n=[],r,a,o,s=0,c=0,u=0,d=0,h=0,f=0,m=0,p=0,y=0,_=0,v=0,g=0;if(i.length<2)return[];if(i.length===2)return u=Math.sqrt(D(i[1].x-i[0].x,2)+D(i[1].y-i[0].y,2)),[{length:u,beginIndex:0,beginDistance:0,endIndex:2,endDistance:u}];for(d=Math.sqrt(D(i[1].x-i[0].x,2)+D(i[1].y-i[0].y,2)),s=1,c=i.length-1;se||u-g>t)&&(n.push({length:u-g,beginDistance:g,beginIndex:v,endIndex:s+1,endDistance:u}),v=s,g=u),d=h;return s-v>0&&n.push({length:u-g+h,beginIndex:v,beginDistance:g,endIndex:s+1,endDistance:u+h}),n},"linelabel");function Ai(i,e,t,n){let r=[];for(let a of i){let o=Zn(a,Math.PI/45,e);for(let s of o)if(s.length>=e+n){let c=new x(a[s.beginIndex].x,a[s.beginIndex].y),u=a[s.endIndex-1],d=new x((u.x-c.x)/s.length,(u.y-c.y)/s.length);for(let h=n;h=0&&t-n>3}if(o--,a===1||a===2)s+=e.readSVarint(),c+=e.readSVarint(),a===1&&(r&&n.push(r),r=[]),r&&r.push(new x(s,c));else if(a===7)r&&r.push(r[0].clone());else throw new Error(`unknown command ${a}`)}return r&&n.push(r),n}bbox(){let e=this._pbf;e.pos=this._geometry;let t=e.readVarint()+e.pos,n=1,r=0,a=0,o=0,s=1/0,c=-1/0,u=1/0,d=-1/0;for(;e.pos>3}if(r--,n===1||n===2)a+=e.readSVarint(),o+=e.readSVarint(),ac&&(c=a),od&&(d=o);else if(n!==7)throw new Error(`unknown command ${n}`)}return[s,u,c,d]}toGeoJSON(e,t,n){let r=this.extent*Math.pow(2,n),a=this.extent*e,o=this.extent*t,s=this.loadGeometry();function c(f){return[(f.x+a)*360/r-180,360/Math.PI*Math.atan(Math.exp((1-(f.y+o)*2/r)*Math.PI))-90]}l(c,"projectPoint");function u(f){return f.map(c)}l(u,"projectLine");let d;if(this.type===1){let f=[];for(let p of s)f.push(p[0]);let m=u(f);d=f.length===1?{type:"Point",coordinates:m[0]}:{type:"MultiPoint",coordinates:m}}else if(this.type===2){let f=s.map(u);d=f.length===1?{type:"LineString",coordinates:f[0]}:{type:"MultiLineString",coordinates:f}}else if(this.type===3){let f=Qn(s),m=[];for(let p of f)m.push(p.map(u));d=m.length===1?{type:"Polygon",coordinates:m[0]}:{type:"MultiPolygon",coordinates:m}}else throw new Error("unknown feature type");let h={type:"Feature",geometry:d,properties:this.properties};return this.id!=null&&(h.id=this.id),h}};l(Pt,"VectorTileFeature");var Ke=Pt;Ke.types=["Unknown","Point","LineString","Polygon"];function Kn(i,e,t){i===1?e.id=t.readVarint():i===2?Gn(t,e):i===3?e.type=t.readVarint():i===4&&(e._geometry=t.pos)}l(Kn,"readFeature");function Gn(i,e){let t=i.readVarint()+i.pos;for(;i.pos=this._features.length)throw new Error("feature index out of bounds");this._pbf.pos=this._features[e];let t=this._pbf.readVarint()+this._pbf.pos;return new Ke(this._pbf,t,this.extent,this._keys,this._values)}};l(St,"VectorTileLayer");var Lt=St;function tr(i,e,t){i===15?e.version=t.readVarint():i===1?e.name=t.readString():i===5?e.extent=t.readVarint():i===2?e._features.push(t.pos):i===3?e._keys.push(t.readString()):i===4&&e._values.push(ir(t))}l(tr,"readLayer");function ir(i){let e=null,t=i.readVarint()+i.pos;for(;i.pos>3;e=n===1?i.readString():n===2?i.readFloat():n===3?i.readDouble():n===4?i.readVarint64():n===5?i.readVarint():n===6?i.readSVarint():n===7?i.readBoolean():null}return e}l(ir,"readValueMessage");var Tt=class Tt{constructor(e,t){this.layers=e.readFields(nr,{},t)}};l(Tt,"VectorTile");var Ge=Tt;function nr(i,e,t){if(i===3){let n=new Lt(t,t.readVarint()+t.pos);n.length&&(e[n.name]=n)}}l(nr,"readTile");var Ri=23283064365386963e-26,rr=12,ji=typeof TextDecoder=="undefined"?null:new TextDecoder("utf-8"),Ft=0,Qe=1,ze=2,et=5,Mt=class Mt{constructor(e=new Uint8Array(16)){this.buf=ArrayBuffer.isView(e)?e:new Uint8Array(e),this.dataView=new DataView(this.buf.buffer),this.pos=0,this.type=0,this.length=this.buf.length}readFields(e,t,n=this.length){for(;this.pos>3,o=this.pos;this.type=r&7,e(a,t,this),this.pos===o&&this.skip(r)}return t}readMessage(e,t){return this.readFields(e,t,this.readVarint()+this.pos)}readFixed32(){let e=this.dataView.getUint32(this.pos,!0);return this.pos+=4,e}readSFixed32(){let e=this.dataView.getInt32(this.pos,!0);return this.pos+=4,e}readFixed64(){let e=this.dataView.getUint32(this.pos,!0)+this.dataView.getUint32(this.pos+4,!0)*4294967296;return this.pos+=8,e}readSFixed64(){let e=this.dataView.getUint32(this.pos,!0)+this.dataView.getInt32(this.pos+4,!0)*4294967296;return this.pos+=8,e}readFloat(){let e=this.dataView.getFloat32(this.pos,!0);return this.pos+=4,e}readDouble(){let e=this.dataView.getFloat64(this.pos,!0);return this.pos+=8,e}readVarint(e){let t=this.buf,n,r;return r=t[this.pos++],n=r&127,r<128||(r=t[this.pos++],n|=(r&127)<<7,r<128)||(r=t[this.pos++],n|=(r&127)<<14,r<128)||(r=t[this.pos++],n|=(r&127)<<21,r<128)?n:(r=t[this.pos],n|=(r&15)<<28,ar(n,e,this))}readVarint64(){return this.readVarint(!0)}readSVarint(){let e=this.readVarint();return e%2===1?(e+1)/-2:e/2}readBoolean(){return!!this.readVarint()}readString(){let e=this.readVarint()+this.pos,t=this.pos;return this.pos=e,e-t>=rr&&ji?ji.decode(this.buf.subarray(t,e)):yr(this.buf,t,e)}readBytes(){let e=this.readVarint()+this.pos,t=this.buf.subarray(this.pos,e);return this.pos=e,t}readPackedVarint(e=[],t){let n=this.readPackedEnd();for(;this.pos127;);else if(t===ze)this.pos=this.readVarint()+this.pos;else if(t===et)this.pos+=4;else if(t===Qe)this.pos+=8;else throw new Error(`Unimplemented type: ${t}`)}writeTag(e,t){this.writeVarint(e<<3|t)}realloc(e){let t=this.length||16;for(;t268435455||e<0){or(e,this);return}this.realloc(4),this.buf[this.pos++]=e&127|(e>127?128:0),!(e<=127)&&(this.buf[this.pos++]=(e>>>=7)&127|(e>127?128:0),!(e<=127)&&(this.buf[this.pos++]=(e>>>=7)&127|(e>127?128:0),!(e<=127)&&(this.buf[this.pos++]=e>>>7&127)))}writeSVarint(e){this.writeVarint(e<0?-e*2-1:e*2)}writeBoolean(e){this.writeVarint(+e)}writeString(e){e=String(e),this.realloc(e.length*4),this.pos++;let t=this.pos;this.pos=xr(this.buf,e,this.pos);let n=this.pos-t;n>=128&&Oi(t,n,this),this.pos=t-1,this.writeVarint(n),this.pos+=n}writeFloat(e){this.realloc(4),this.dataView.setFloat32(this.pos,e,!0),this.pos+=4}writeDouble(e){this.realloc(8),this.dataView.setFloat64(this.pos,e,!0),this.pos+=8}writeBytes(e){let t=e.length;this.writeVarint(t),this.realloc(t);for(let n=0;n=128&&Oi(n,r,this),this.pos=n-1,this.writeVarint(r),this.pos+=r}writeMessage(e,t,n){this.writeTag(e,ze),this.writeRawMessage(t,n)}writePackedVarint(e,t){t.length&&this.writeMessage(e,cr,t)}writePackedSVarint(e,t){t.length&&this.writeMessage(e,ur,t)}writePackedBoolean(e,t){t.length&&this.writeMessage(e,fr,t)}writePackedFloat(e,t){t.length&&this.writeMessage(e,dr,t)}writePackedDouble(e,t){t.length&&this.writeMessage(e,hr,t)}writePackedFixed32(e,t){t.length&&this.writeMessage(e,mr,t)}writePackedSFixed32(e,t){t.length&&this.writeMessage(e,pr,t)}writePackedFixed64(e,t){t.length&&this.writeMessage(e,gr,t)}writePackedSFixed64(e,t){t.length&&this.writeMessage(e,br,t)}writeBytesField(e,t){this.writeTag(e,ze),this.writeBytes(t)}writeFixed32Field(e,t){this.writeTag(e,et),this.writeFixed32(t)}writeSFixed32Field(e,t){this.writeTag(e,et),this.writeSFixed32(t)}writeFixed64Field(e,t){this.writeTag(e,Qe),this.writeFixed64(t)}writeSFixed64Field(e,t){this.writeTag(e,Qe),this.writeSFixed64(t)}writeVarintField(e,t){this.writeTag(e,Ft),this.writeVarint(t)}writeSVarintField(e,t){this.writeTag(e,Ft),this.writeSVarint(t)}writeStringField(e,t){this.writeTag(e,ze),this.writeString(t)}writeFloatField(e,t){this.writeTag(e,et),this.writeFloat(t)}writeDoubleField(e,t){this.writeTag(e,Qe),this.writeDouble(t)}writeBooleanField(e,t){this.writeVarintField(e,+t)}};l(Mt,"Pbf");var Le=Mt;function ar(i,e,t){let n=t.buf,r,a;if(a=n[t.pos++],r=(a&112)>>4,a<128||(a=n[t.pos++],r|=(a&127)<<3,a<128)||(a=n[t.pos++],r|=(a&127)<<10,a<128)||(a=n[t.pos++],r|=(a&127)<<17,a<128)||(a=n[t.pos++],r|=(a&127)<<24,a<128)||(a=n[t.pos++],r|=(a&1)<<31,a<128))return se(i,r,e);throw new Error("Expected varint not more than 10 bytes")}l(ar,"readVarintRemainder");function se(i,e,t){return t?e*4294967296+(i>>>0):(e>>>0)*4294967296+(i>>>0)}l(se,"toNum");function or(i,e){let t,n;if(i>=0?(t=i%4294967296|0,n=i/4294967296|0):(t=~(-i%4294967296),n=~(-i/4294967296),t^4294967295?t=t+1|0:(t=0,n=n+1|0)),i>=18446744073709552e3||i<-18446744073709552e3)throw new Error("Given varint doesn't fit into 10 bytes");e.realloc(10),sr(t,n,e),lr(n,e)}l(or,"writeBigVarint");function sr(i,e,t){t.buf[t.pos++]=i&127|128,i>>>=7,t.buf[t.pos++]=i&127|128,i>>>=7,t.buf[t.pos++]=i&127|128,i>>>=7,t.buf[t.pos++]=i&127|128,i>>>=7,t.buf[t.pos]=i&127}l(sr,"writeBigVarintLow");function lr(i,e){let t=(i&7)<<4;e.buf[e.pos++]|=t|((i>>>=3)?128:0),i&&(e.buf[e.pos++]=i&127|((i>>>=7)?128:0),i&&(e.buf[e.pos++]=i&127|((i>>>=7)?128:0),i&&(e.buf[e.pos++]=i&127|((i>>>=7)?128:0),i&&(e.buf[e.pos++]=i&127|((i>>>=7)?128:0),i&&(e.buf[e.pos++]=i&127)))))}l(lr,"writeBigVarintHigh");function Oi(i,e,t){let n=e<=16383?1:e<=2097151?2:e<=268435455?3:Math.floor(Math.log(e)/(Math.LN2*7));t.realloc(n);for(let r=t.pos-1;r>=i;r--)t.buf[r+n]=t.buf[r]}l(Oi,"makeRoomForExtraLength");function cr(i,e){for(let t=0;t239?4:a>223?3:a>191?2:1;if(r+s>t)break;let c,u,d;s===1?a<128&&(o=a):s===2?(c=i[r+1],(c&192)===128&&(o=(a&31)<<6|c&63,o<=127&&(o=null))):s===3?(c=i[r+1],u=i[r+2],(c&192)===128&&(u&192)===128&&(o=(a&15)<<12|(c&63)<<6|u&63,(o<=2047||o>=55296&&o<=57343)&&(o=null))):s===4&&(c=i[r+1],u=i[r+2],d=i[r+3],(c&192)===128&&(u&192)===128&&(d&192)===128&&(o=(a&15)<<18|(c&63)<<12|(u&63)<<6|d&63,(o<=65535||o>=1114112)&&(o=null))),o===null?(o=65533,s=1):o>65535&&(o-=65536,n+=String.fromCharCode(o>>>10&1023|55296),o=56320|o&1023),n+=String.fromCharCode(o),r+=s}return n}l(yr,"readUtf8");function xr(i,e,t){for(let n=0,r,a;n55295&&r<57344)if(a)if(r<56320){i[t++]=239,i[t++]=191,i[t++]=189,a=r;continue}else r=a-55296<<10|r-56320|65536,a=null;else{r>56319||n+1===e.length?(i[t++]=239,i[t++]=191,i[t++]=189):a=r;continue}else a&&(i[t++]=239,i[t++]=191,i[t++]=189,a=null);r<128?i[t++]=r:(r<2048?i[t++]=r>>6|192:(r<65536?i[t++]=r>>12|224:(i[t++]=r>>18|240,i[t++]=r>>12&63|128),i[t++]=r>>6&63|128),i[t++]=r&63|128)}return t}l(xr,"writeUtf8");var ue=Math.pow,j=l((i,e,t)=>new Promise((n,r)=>{var a=l(c=>{try{s(t.next(c))}catch(u){r(u)}},"fulfilled"),o=l(c=>{try{s(t.throw(c))}catch(u){r(u)}},"rejected"),s=l(c=>c.done?n(c.value):Promise.resolve(c.value).then(a,o),"step");s((t=t.apply(i,e)).next())}),"__async"),Y=Uint8Array,ce=Uint16Array,_r=Int32Array,Xi=new Y([0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0]),Yi=new Y([0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0]),wr=new Y([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),Vi=l(function(i,e){for(var t=new ce(31),n=0;n<31;++n)t[n]=e+=1<>1|(z&21845)<<1,K=(K&52428)>>2|(K&13107)<<2,K=(K&61680)>>4|(K&3855)<<4,At[z]=((K&65280)>>8|(K&255)<<8)>>1;var K,z,Te=l(function(i,e,t){for(var n=i.length,r=0,a=new ce(e);r>c]=u}else for(s=new ce(n),r=0;r>15-i[r]);return s},"hMap"),Fe=new Y(288);for(z=0;z<144;++z)Fe[z]=8;var z;for(z=144;z<256;++z)Fe[z]=9;var z;for(z=256;z<280;++z)Fe[z]=7;var z;for(z=280;z<288;++z)Fe[z]=8;var z,Hi=new Y(32);for(z=0;z<32;++z)Hi[z]=5;var z,zr=Te(Fe,9,1),Lr=Te(Hi,5,1),Ct=l(function(i){for(var e=i[0],t=1;te&&(e=i[t]);return e},"max"),H=l(function(i,e,t){var n=e/8|0;return(i[n]|i[n+1]<<8)>>(e&7)&t},"bits"),Dt=l(function(i,e){var t=e/8|0;return(i[t]|i[t+1]<<8|i[t+2]<<16)>>(e&7)},"bits16"),Pr=l(function(i){return(i+7)/8|0},"shft"),Sr=l(function(i,e,t){(e==null||e<0)&&(e=0),(t==null||t>i.length)&&(t=i.length);var n=new Y(t-e);return n.set(i.subarray(e,t)),n},"slc"),Tr=["unexpected EOF","invalid block type","invalid length/literal","invalid distance","stream finished","no stream handler",,"no callback","invalid UTF-8 data","extra field too long","date not in range 1980-2099","filename too long","stream finishing","invalid zip data"],X=l(function(i,e,t){var n=new Error(e||Tr[i]);if(n.code=i,Error.captureStackTrace&&Error.captureStackTrace(n,X),!t)throw n;return n},"err"),jt=l(function(i,e,t,n){var r=i.length,a=n?n.length:0;if(!r||e.f&&!e.l)return t||new Y(0);var o=!t||e.i!=2,s=e.i;t||(t=new Y(r*3));var c=l(function(zi){var Li=t.length;if(zi>Li){var Pi=new Y(Math.max(Li*2,zi));Pi.set(t),t=Pi}},"cbuf"),u=e.f||0,d=e.p||0,h=e.b||0,f=e.l,m=e.d,p=e.m,y=e.n,_=r*8;do{if(!f){u=H(i,d,1);var v=H(i,d+1,3);if(d+=3,v)if(v==1)f=zr,m=Lr,p=9,y=5;else if(v==2){var b=H(i,d,31)+257,S=H(i,d+10,15)+4,P=b+H(i,d+5,31)+1;d+=14;for(var k=new Y(P),C=new Y(19),R=0;R>4;if(g<16)k[R++]=g;else{var ie=0,qe=0;for(g==16?(qe=3+H(i,d,3),d+=2,ie=k[R-1]):g==17?(qe=3+H(i,d,7),d+=3):g==18&&(qe=11+H(i,d,127),d+=7);qe--;)k[R++]=ie}}var wi=k.subarray(0,b),U=k.subarray(b);p=Ct(wi),y=Ct(U),f=Te(wi,p,1),m=Te(U,y,1)}else X(1);else{var g=Pr(d)+4,w=i[g-4]|i[g-3]<<8,F=g+w;if(F>r){s&&X(0);break}o&&c(h+w),t.set(i.subarray(g,F),h),e.b=h+=w,e.p=d=F*8,e.f=u;continue}if(d>_){s&&X(0);break}}o&&c(h+131072);for(var kn=(1<>4;if(d+=ie&15,d>_){s&&X(0);break}if(ie||X(2),ne<256)t[h++]=ne;else if(ne==256){ht=d,f=null;break}else{var ki=ne-254;if(ne>264){var R=ne-257,_e=Xi[R];ki=H(i,d,(1<<_e)-1)+Ui[R],d+=_e}var ft=m[Dt(i,d)&vn],mt=ft>>4;ft||X(3),d+=ft&15;var U=vr[mt];if(mt>3){var _e=Yi[mt];U+=Dt(i,d)&(1<<_e)-1,d+=_e}if(d>_){s&&X(0);break}o&&c(h+131072);var pt=h+ki;if(h>3&1)+(e>>4&1);n>0;n-=!i[t++]);return t+(e&2)},"gzs"),Cr=l(function(i){var e=i.length;return(i[e-4]|i[e-3]<<8|i[e-2]<<16|i[e-1]<<24)>>>0},"gzl"),Dr=l(function(i,e){return((i[0]&15)!=8||i[0]>>4>7||(i[0]<<8|i[1])%31)&&X(6,"invalid zlib data"),(i[1]>>5&1)==+!e&&X(6,"invalid zlib data: "+(i[1]&32?"need":"unexpected")+" dictionary"),(i[1]>>3&4)+2},"zls");function Ar(i,e){return jt(i,{i:2},e&&e.out,e&&e.dictionary)}l(Ar,"inflateSync");function Br(i,e){var t=Mr(i);return t+8>i.length&&X(6,"invalid gzip data"),jt(i.subarray(t,-8),{i:2},e&&e.out||new Y(Cr(i)),e&&e.dictionary)}l(Br,"gunzipSync");function Rr(i,e){return jt(i.subarray(Dr(i,e&&e.dictionary),-4),{i:2},e&&e.out,e&&e.dictionary)}l(Rr,"unzlibSync");function Bt(i,e){return i[0]==31&&i[1]==139&&i[2]==8?Br(i,e):(i[0]&15)!=8||i[0]>>4>7||(i[0]<<8|i[1])%31?Ar(i,e):Rr(i,e)}l(Bt,"decompressSync");var jr=typeof TextDecoder!="undefined"&&new TextDecoder,Or=0;try{jr.decode(Fr,{stream:!0}),Or=1}catch(i){}var qi=l((i,e)=>i*ue(2,e),"shift"),Pe=l((i,e)=>Math.floor(i/ue(2,e)),"unshift"),tt=l((i,e)=>qi(i.getUint16(e+1,!0),8)+i.getUint8(e),"getUint24"),Wi=l((i,e)=>qi(i.getUint32(e+2,!0),16)+i.getUint16(e,!0),"getUint48"),Ir=l((i,e,t,n,r)=>{if(i!==n.getUint8(r))return i-n.getUint8(r);let a=tt(n,r+1);if(e!==a)return e-a;let o=tt(n,r+4);return t!==o?t-o:0},"compare"),Er=l((i,e,t,n)=>{let r=Zi(i,e|128,t,n);return r?{z:e,x:t,y:n,offset:r[0],length:r[1],isDir:!0}:null},"queryLeafdir"),Ii=l((i,e,t,n)=>{let r=Zi(i,e,t,n);return r?{z:e,x:t,y:n,offset:r[0],length:r[1],isDir:!1}:null},"queryTile"),Zi=l((i,e,t,n)=>{let r=0,a=i.byteLength/17-1;for(;r<=a;){let o=a+r>>1,s=Ir(e,t,n,i,o*17);if(s>0)r=o+1;else if(s<0)a=o-1;else return[Wi(i,o*17+7),i.getUint32(o*17+13,!0)]}return null},"queryView"),Xr=l((i,e)=>i.isDir&&!e.isDir?1:!i.isDir&&e.isDir?-1:i.z!==e.z?i.z-e.z:i.x!==e.x?i.x-e.x:i.y-e.y,"entrySort"),Ji=l((i,e)=>{let t=i.getUint8(e*17);return{z:t&127,x:tt(i,e*17+1),y:tt(i,e*17+4),offset:Wi(i,e*17+7),length:i.getUint32(e*17+13,!0),isDir:t>>7===1}},"parseEntry"),Ei=l(i=>{let e=[],t=new DataView(i);for(let n=0;n{i.sort(Xr);let e=new ArrayBuffer(17*i.length),t=new Uint8Array(e);for(let n=0;n>8&255,t[n*17+3]=r.x>>16&255,t[n*17+4]=r.y&255,t[n*17+5]=r.y>>8&255,t[n*17+6]=r.y>>16&255,t[n*17+7]=r.offset&255,t[n*17+8]=Pe(r.offset,8)&255,t[n*17+9]=Pe(r.offset,16)&255,t[n*17+10]=Pe(r.offset,24)&255,t[n*17+11]=Pe(r.offset,32)&255,t[n*17+12]=Pe(r.offset,48)&255,t[n*17+13]=r.length&255,t[n*17+14]=r.length>>8&255,t[n*17+15]=r.length>>16&255,t[n*17+16]=r.length>>24&255}return e},"createDirectory"),Vr=l((i,e)=>{if(i.byteLength<17)return null;let t=i.byteLength/17,n=Ji(i,t-1);if(n.isDir){let r=n.z,a=e.z-r,o=Math.trunc(e.x/(1<>>0)*4294967296+(i>>>0)}l(le,"toNum");function Nr(i,e){let t=e.buf,n=t[e.pos++],r=(n&112)>>4;if(n<128||(n=t[e.pos++],r|=(n&127)<<3,n<128)||(n=t[e.pos++],r|=(n&127)<<10,n<128)||(n=t[e.pos++],r|=(n&127)<<17,n<128)||(n=t[e.pos++],r|=(n&127)<<24,n<128)||(n=t[e.pos++],r|=(n&1)<<31,n<128))return le(i,r);throw new Error("Expected varint not more than 10 bytes")}l(Nr,"readVarintRemainder");function Se(i){let e=i.buf,t=e[i.pos++],n=t&127;return t<128||(t=e[i.pos++],n|=(t&127)<<7,t<128)||(t=e[i.pos++],n|=(t&127)<<14,t<128)||(t=e[i.pos++],n|=(t&127)<<21,t<128)?n:(t=e[i.pos],n|=(t&15)<<28,Nr(n,i))}l(Se,"readVarint");function Hr(i,e,t,n){if(n===0){t===1&&(e[0]=i-1-e[0],e[1]=i-1-e[1]);let r=e[0];e[0]=e[1],e[1]=r}}l(Hr,"rotate");var qr=[0,1,5,21,85,341,1365,5461,21845,87381,349525,1398101,5592405,22369621,89478485,357913941,1431655765,5726623061,22906492245,91625968981,366503875925,1466015503701,5864062014805,23456248059221,93824992236885,375299968947541,0x5555555555555];function Wr(i,e,t){if(i>26)throw Error("Tile zoom level exceeds max safe number limit (26)");if(e>ue(2,i)-1||t>ue(2,i)-1)throw Error("tile x/y outside zoom level bounds");let n=qr[i],r=ue(2,i),a=0,o=0,s=0,c=[e,t],u=r/2;for(;u>0;)a=(c[0]&u)>0?1:0,o=(c[1]&u)>0?1:0,s+=u*u*(3*a^o),Hr(u,c,a,o),u=u/2;return n+s}l(Wr,"zxyToTileId");function Gi(i,e){return j(this,null,function*(){if(e===1||e===0)return i;if(e===2){if(typeof globalThis.DecompressionStream=="undefined")return Bt(new Uint8Array(i));let t=new Response(i).body;if(!t)throw Error("Failed to read response stream");let n=t.pipeThrough(new globalThis.DecompressionStream("gzip"));return new Response(n).arrayBuffer()}throw Error("Compression method not supported")})}l(Gi,"defaultDecompress");function Zr(i){return i===1?".mvt":i===2?".png":i===3?".jpg":i===4?".webp":i===5?".avif":""}l(Zr,"tileTypeExt");var Jr=127;function Kr(i,e){let t=0,n=i.length-1;for(;t<=n;){let r=n+t>>1,a=e-i[r].tileId;if(a>0)t=r+1;else if(a<0)n=r-1;else return i[r]}return n>=0&&(i[n].runLength===0||e-i[n].tileId-1,a=/Chrome|Chromium|Edg|OPR|Brave/.test(n);this.chromeWindowsNoCache=!1,r&&a&&(this.chromeWindowsNoCache=!0)}getKey(){return this.url}setHeaders(e){this.customHeaders=e}getBytes(e,t,n,r){return j(this,null,function*(){let a,o;n?o=n:(a=new AbortController,o=a.signal);let s=new Headers(this.customHeaders);s.set("range",`bytes=${e}-${e+t-1}`);let c;this.mustReload?c="reload":this.chromeWindowsNoCache&&(c="no-store");let u=yield fetch(this.url,{signal:o,cache:c,headers:s});if(e===0&&u.status===416){let m=u.headers.get("Content-Range");if(!m||!m.startsWith("bytes */"))throw Error("Missing content-length on 416 response");let p=+m.substr(8);u=yield fetch(this.url,{signal:o,cache:"reload",headers:{range:`bytes=0-${p-1}`}})}let d=u.headers.get("Etag");if(d!=null&&d.startsWith("W/")&&(d=null),u.status===416||r&&d&&d!==r)throw this.mustReload=!0,new Rt(`Server returned non-matching ETag ${r} after one retry. Check browser extensions and servers for issues that may affect correct ETag headers.`);if(u.status>=300)throw Error(`Bad response code: ${u.status}`);let h=u.headers.get("Content-Length");if(u.status===200&&(!h||+h>t))throw a&&a.abort(),Error("Server returned no content-length header or content-length exceeding request. Check that your storage backend supports HTTP Byte Serving.");return{data:yield u.arrayBuffer(),etag:d||void 0,cacheControl:u.headers.get("Cache-Control")||void 0,expires:u.headers.get("Expires")||void 0}})}},l(de,"FetchSource"),de);function q(i,e){let t=i.getUint32(e+4,!0),n=i.getUint32(e+0,!0);return t*ue(2,32)+n}l(q,"getUint64");function Qr(i,e){let t=new DataView(i),n=t.getUint8(7);if(n>3)throw Error(`Archive is spec version ${n} but this library supports up to spec version 3`);return{specVersion:n,rootDirectoryOffset:q(t,8),rootDirectoryLength:q(t,16),jsonMetadataOffset:q(t,24),jsonMetadataLength:q(t,32),leafDirectoryOffset:q(t,40),leafDirectoryLength:q(t,48),tileDataOffset:q(t,56),tileDataLength:q(t,64),numAddressedTiles:q(t,72),numTileEntries:q(t,80),numTileContents:q(t,88),clustered:t.getUint8(96)===1,internalCompression:t.getUint8(97),tileCompression:t.getUint8(98),tileType:t.getUint8(99),minZoom:t.getUint8(100),maxZoom:t.getUint8(101),minLon:t.getInt32(102,!0)/1e7,minLat:t.getInt32(106,!0)/1e7,maxLon:t.getInt32(110,!0)/1e7,maxLat:t.getInt32(114,!0)/1e7,centerZoom:t.getUint8(118),centerLon:t.getInt32(119,!0)/1e7,centerLat:t.getInt32(123,!0)/1e7,etag:e}}l(Qr,"bytesToHeader");function Qi(i){let e={buf:new Uint8Array(i),pos:0},t=Se(e),n=[],r=0;for(let a=0;a0?n[a].offset=n[a-1].offset+n[a-1].length:n[a].offset=o-1}return n}l(Qi,"deserializeIndex");function ea(i){let e=new DataView(i);return e.getUint16(2,!0)===2?(console.warn("PMTiles spec version 2 has been deprecated; please see github.com/protomaps/PMTiles for tools to upgrade"),2):e.getUint16(2,!0)===1?(console.warn("PMTiles spec version 1 has been deprecated; please see github.com/protomaps/PMTiles for tools to upgrade"),1):3}l(ea,"detectVersion");var he,Rt=(he=class extends Error{},l(he,"EtagMismatch"),he);function ta(i,e){return j(this,null,function*(){let t=yield i.getBytes(0,16384);if(new DataView(t.data).getUint16(0,!0)!==19792)throw new Error("Wrong magic number for PMTiles archive");if(ea(t.data)<3)return[yield Ki.getHeader(i)];let r=t.data.slice(0,Jr),a=Qr(r,t.etag),o=t.data.slice(a.rootDirectoryOffset,a.rootDirectoryOffset+a.rootDirectoryLength),s=`${i.getKey()}|${a.etag||""}|${a.rootDirectoryOffset}|${a.rootDirectoryLength}`,c=Qi(yield e(o,a.internalCompression));return[a,[s,c.length,c]]})}l(ta,"getHeaderAndRoot");function ia(i,e,t,n,r){return j(this,null,function*(){let a=yield i.getBytes(t,n,void 0,r.etag),o=yield e(a.data,r.internalCompression),s=Qi(o);if(s.length===0)throw new Error("Empty directory is invalid");return s})}l(ia,"getDirectory");var fe,na=(fe=class{constructor(e=100,t=!0,n=Gi){this.cache=new Map,this.invalidations=new Map,this.maxCacheEntries=e,this.counter=1,this.decompress=n}getHeader(e){return j(this,null,function*(){let t=e.getKey(),n=this.cache.get(t);if(n)return n.lastUsed=this.counter++,yield n.data;let r=new Promise((a,o)=>{ta(e,this.decompress).then(s=>{s[1]&&this.cache.set(s[1][0],{lastUsed:this.counter++,data:Promise.resolve(s[1][2])}),a(s[0]),this.prune()}).catch(s=>{o(s)})});return this.cache.set(t,{lastUsed:this.counter++,data:r}),r})}getDirectory(e,t,n,r){return j(this,null,function*(){let a=`${e.getKey()}|${r.etag||""}|${t}|${n}`,o=this.cache.get(a);if(o)return o.lastUsed=this.counter++,yield o.data;let s=new Promise((c,u)=>{ia(e,this.decompress,t,n,r).then(d=>{c(d),this.prune()}).catch(d=>{u(d)})});return this.cache.set(a,{lastUsed:this.counter++,data:s}),s})}getArrayBuffer(e,t,n,r){return j(this,null,function*(){let a=`${e.getKey()}|${r.etag||""}|${t}|${n}`,o=this.cache.get(a);if(o)return o.lastUsed=this.counter++,yield o.data;let s=new Promise((c,u)=>{e.getBytes(t,n,void 0,r.etag).then(d=>{c(d.data),this.cache.has(a),this.prune()}).catch(d=>{u(d)})});return this.cache.set(a,{lastUsed:this.counter++,data:s}),s})}prune(){if(this.cache.size>=this.maxCacheEntries){let e=1/0,t;this.cache.forEach((n,r)=>{n.lastUsed{this.getHeader(e).then(o=>{r(),this.invalidations.delete(t)}).catch(o=>{a(o)})});this.invalidations.set(t,n)})}},l(fe,"SharedPromiseCache"),fe),me,en=(me=class{constructor(e,t,n){typeof e=="string"?this.source=new Gr(e):this.source=e,n?this.decompress=n:this.decompress=Gi,t?this.cache=t:this.cache=new na}getHeader(){return j(this,null,function*(){return yield this.cache.getHeader(this.source)})}getZxyAttempt(e,t,n,r){return j(this,null,function*(){let a=Wr(e,t,n),o=yield this.cache.getHeader(this.source);if(o.specVersion<3)return Ki.getZxy(o,this.source,this.cache,e,t,n,r);if(eo.maxZoom)return;let s=o.rootDirectoryOffset,c=o.rootDirectoryLength;for(let u=0;u<=3;u++){let d=yield this.cache.getDirectory(this.source,s,c,o),h=Kr(d,a);if(h){if(h.runLength>0){let f=yield this.source.getBytes(o.tileDataOffset+h.offset,h.length,r,o.etag);return{data:yield this.decompress(f.data,o.tileCompression),cacheControl:f.cacheControl,expires:f.expires}}s=o.leafDirectoryOffset+h.offset,c=h.length}else return}throw Error("Maximum directory depth exceeded")})}getZxy(e,t,n,r){return j(this,null,function*(){try{return yield this.getZxyAttempt(e,t,n,r)}catch(a){if(a instanceof Rt)return this.cache.invalidate(this.source),yield this.getZxyAttempt(e,t,n,r);throw a}})}getMetadataAttempt(){return j(this,null,function*(){let e=yield this.cache.getHeader(this.source),t=yield this.source.getBytes(e.jsonMetadataOffset,e.jsonMetadataLength,void 0,e.etag),n=yield this.decompress(t.data,e.internalCompression),r=new TextDecoder("utf-8");return JSON.parse(r.decode(n))})}getMetadata(){return j(this,null,function*(){try{return yield this.getMetadataAttempt()}catch(e){if(e instanceof Rt)return this.cache.invalidate(this.source),yield this.getMetadataAttempt();throw e}})}getTileJson(e){return j(this,null,function*(){let t=yield this.getHeader(),n=yield this.getMetadata(),r=Zr(t.tileType);return{tilejson:"3.0.0",scheme:"xyz",tiles:[`${e}/{z}/{x}/{y}${r}`],vector_layers:n.vector_layers,attribution:n.attribution,description:n.description,name:n.name,version:n.version,bounds:[t.minLon,t.minLat,t.maxLon,t.maxLat],center:[t.centerLon,t.centerLat,t.centerZoom],minzoom:t.minZoom,maxzoom:t.maxZoom}})}},l(me,"PMTiles"),me);var rt=(n=>(n[n.Point=1]="Point",n[n.Line=2]="Line",n[n.Polygon=3]="Polygon",n))(rt||{});function G(i){return`${i.x}:${i.y}:${i.z}`}l(G,"toIndex");var ra=l((i,e,t)=>{i.pos=e;let n=i.readVarint()+i.pos,r=1,a=0,o=0,s=0,c=1/0,u=-1/0,d=1/0,h=-1/0,f=[],m=[];for(;i.pos>3}if(a--,r===1||r===2)o+=i.readSVarint()*t,s+=i.readSVarint()*t,ou&&(u=o),sh&&(h=s),r===1&&(m.length>0&&f.push(m),m=[]),m.push(new x(o,s));else if(r===7)m&&m.push(m[0].clone());else throw new Error(`unknown command ${r}`)}return m&&f.push(m),{geom:f,bbox:{minX:c,minY:d,maxX:u,maxY:h}}},"loadGeomAndBbox");function rn(i,e){let t=new Ge(new Le(i)),n=new Map;for(let[r,a]of Object.entries(t.layers)){let o=[],s=a;for(let c=0;co.z!==e.z?(o.controller.abort(),!1):!0));let n=new AbortController;this.zoomaborts.push({z:e.z,controller:n});let r=n.signal,a=yield this.p.getZxy(e.z,e.x,e.y,r);return a?rn(a.data,t):new Map})}};l(Et,"PmtilesSource");var pe=Et,Xt=class Xt{constructor(e,t){this.url=e,this.zoomaborts=[],this.shouldCancelZooms=t}get(e,t){return A(this,null,function*(){this.shouldCancelZooms&&(this.zoomaborts=this.zoomaborts.filter(o=>o.z!==e.z?(o.controller.abort(),!1):!0));let n=this.url.replace("{z}",e.z.toString()).replace("{x}",e.x.toString()).replace("{y}",e.y.toString()),r=new AbortController;this.zoomaborts.push({z:e.z,controller:r});let a=r.signal;return new Promise((o,s)=>{fetch(n,{signal:a}).then(c=>c.arrayBuffer()).then(c=>{let u=rn(c,t);o(u)}).catch(c=>{s(c)})})})}};l(Xt,"ZxySource");var Me=Xt,Ot=6378137,tn=85.0511287798,it=Ot*Math.PI,aa=l(i=>{let e=Math.PI/180,t=Math.max(Math.min(tn,i[0]),-tn),n=Math.sin(t*e);return new x(Ot*i[1]*e,Ot*Math.log((1+n)/(1-n))/2)},"project");function nn(i){return i*i}l(nn,"sqr");function nt(i,e){return nn(i.x-e.x)+nn(i.y-e.y)}l(nt,"dist2");function oa(i,e,t){let n=nt(e,t);if(n===0)return nt(i,e);let r=((i.x-e.x)*(t.x-e.x)+(i.y-e.y)*(t.y-e.y))/n;return r=Math.max(0,Math.min(1,r)),nt(i,new x(e.x+r*(t.x-e.x),e.y+r*(t.y-e.y)))}l(oa,"distToSegmentSquared");function It(i,e){let t=!1;for(let n=0,r=e.length-1;ni.y!=c>i.y&&i.x<(s-a)*(i.y-o)/(c-o)+a&&(t=!t)}return t}l(It,"isInRing");function an(i){let e=0;for(let t=0;t{let a=this.cache.get(t);if(a)a.used=performance.now(),n(a.data);else{let o=this.inflight.get(t);o?o.push({resolve:n,reject:r}):(this.inflight.set(t,[]),this.source.get(e,this.tileSize).then(s=>{this.cache.set(t,{used:performance.now(),data:s});let c=this.inflight.get(t);if(c)for(let u of c)u.resolve(s);if(this.inflight.delete(t),n(s),this.cache.size>=64){let u=1/0,d;this.cache.forEach((h,f)=>{h.used{let c=this.inflight.get(t);if(c)for(let u of c)u.reject(s);this.inflight.delete(t),r(s)}))}})})}queryFeatures(e,t,n,r){let a=aa([t,e]),o=new x((a.x+it)/(it*2),1-(a.y+it)/(it*2));o.x>1&&(o.x=o.x-Math.floor(o.x));let s=o.mult(1<(n[n.Left=1]="Left",n[n.Center=2]="Center",n[n.Right=3]="Right",n))(cn||{}),un=(c=>(c[c.N=1]="N",c[c.Ne=2]="Ne",c[c.E=3]="E",c[c.Se=4]="Se",c[c.S=5]="S",c[c.Sw=6]="Sw",c[c.W=7]="W",c[c.Nw=8]="Nw",c))(un||{}),sa=l((i,e,t)=>{let n=document.createElement("canvas"),r=n.getContext("2d");return n.width=i,n.height=e,r!==null&&t(n,r),n},"createPattern"),Ht=class Ht{constructor(e){var t;this.pattern=e.pattern,this.fill=new B(e.fill,"black"),this.opacity=new T(e.opacity,1),this.stroke=new B(e.stroke,"black"),this.width=new T(e.width,0),this.perFeature=(t=this.fill.perFeature||this.opacity.perFeature||this.stroke.perFeature||this.width.perFeature||e.perFeature)!=null?t:!1,this.doStroke=!1}before(e,t){if(!this.perFeature){e.globalAlpha=this.opacity.get(t),e.fillStyle=this.fill.get(t),e.strokeStyle=this.stroke.get(t);let n=this.width.get(t);n>0&&(this.doStroke=!0),e.lineWidth=n}if(this.pattern){let n=e.createPattern(this.pattern,"repeat");n&&(e.fillStyle=n)}}draw(e,t,n,r){let a=!1;if(this.perFeature){e.globalAlpha=this.opacity.get(n,r),e.fillStyle=this.fill.get(n,r);let s=this.width.get(n,r);s&&(a=!0,e.strokeStyle=this.stroke.get(n,r),e.lineWidth=s)}let o=l(()=>{e.fill(),(a||this.doStroke)&&e.stroke()},"drawPath");e.beginPath();for(let s of t)for(let c=0;c{let n=t-i;return n>=0&&n{if(e.length<1)return 0;if(t<=e[0][0])return e[0][1];if(t>=e[e.length-1][0])return e[e.length-1][1];let n=ca(t,e),r=da(t,n,i,e);return ua(r,e[n][1],e[n+1][1])}}l(V,"exp");function ha(i,e){return t=>{if(e.length<1)return 0;let n=i;for(let r=0;r=e[r][0]&&(n=e[r][1]);return n}}l(ha,"step");function st(i){return V(1,i)}l(st,"linear");var qt=class qt{constructor(e){var t;this.color=new B(e.color,"black"),this.width=new T(e.width),this.opacity=new T(e.opacity),this.dash=e.dash?new Je(e.dash):null,this.dashColor=new B(e.dashColor,"black"),this.dashWidth=new T(e.dashWidth,1),this.lineCap=new B(e.lineCap,"butt"),this.lineJoin=new B(e.lineJoin,"miter"),this.skip=!1,this.perFeature=!!((t=this.dash)!=null&&t.perFeature||this.color.perFeature||this.opacity.perFeature||this.width.perFeature||this.lineCap.perFeature||this.lineJoin.perFeature||e.perFeature)}before(e,t){this.perFeature||(e.strokeStyle=this.color.get(t),e.lineWidth=this.width.get(t),e.globalAlpha=this.opacity.get(t),e.lineCap=this.lineCap.get(t),e.lineJoin=this.lineJoin.get(t))}draw(e,t,n,r){if(this.skip)return;let a=l(()=>{this.perFeature&&(e.globalAlpha=this.opacity.get(n,r),e.lineCap=this.lineCap.get(n,r),e.lineJoin=this.lineJoin.get(n,r)),this.dash?(e.save(),this.perFeature?(e.lineWidth=this.dashWidth.get(n,r),e.strokeStyle=this.dashColor.get(n,r),e.setLineDash(this.dash.get(n,r))):e.setLineDash(this.dash.get(n)),e.stroke(),e.restore()):(e.save(),this.perFeature&&(e.lineWidth=this.width.get(n,r),e.strokeStyle=this.color.get(n,r)),e.stroke(),e.restore())},"strokePath");e.beginPath();for(let o of t)for(let s=0;s{h.globalAlpha=1,h.drawImage(this.sheet.canvas,o.x,o.y,o.w,o.h,-o.w/2/this.dpr,-o.h/2/this.dpr,o.w/2,o.h/2)},"draw")}]}};l(Wt,"IconSymbolizer");var Vt=Wt,Zt=class Zt{constructor(e){this.radius=new T(e.radius,3),this.fill=new B(e.fill,"black"),this.stroke=new B(e.stroke,"white"),this.width=new T(e.width,0),this.opacity=new T(e.opacity)}draw(e,t,n,r){e.globalAlpha=this.opacity.get(n,r);let a=this.radius.get(n,r),o=this.width.get(n,r);o>0&&(e.strokeStyle=this.stroke.get(n,r),e.lineWidth=o,e.beginPath(),e.arc(t[0][0].x,t[0][0].y,a+o/2,0,2*Math.PI),e.stroke()),e.fillStyle=this.fill.get(n,r),e.beginPath(),e.arc(t[0][0].x,t[0][0].y,a,0,2*Math.PI),e.fill()}place(e,t,n){let r=t[0],a=new x(t[0][0].x,t[0][0].y),o=this.radius.get(e.zoom,n),s={minX:a.x-o,minY:a.y-o,maxX:a.x+o,maxY:a.y+o};return[{anchor:a,bboxes:[s],draw:l(u=>{this.draw(u,[[new x(0,0)]],e.zoom,n)},"draw")}]}};l(Zt,"CircleSymbolizer");var De=Zt,Jt=class Jt{constructor(e){this.font=new oe(e),this.text=new ae(e),this.fill=new B(e.fill,"black"),this.background=new B(e.background,"white"),this.padding=new T(e.padding,0)}place(e,t,n){let r=this.text.get(e.zoom,n);if(!r)return;let a=this.font.get(e.zoom,n);e.scratch.font=a;let o=e.scratch.measureText(r),s=o.width,c=o.actualBoundingBoxAscent,u=o.actualBoundingBoxDescent,d=t[0],h=new x(t[0][0].x,t[0][0].y),f=this.padding.get(e.zoom,n),m={minX:h.x-s/2-f,minY:h.y-c-f,maxX:h.x+s/2+f,maxY:h.y+u+f};return[{anchor:h,bboxes:[m],draw:l(y=>{y.globalAlpha=1,y.fillStyle=this.background.get(e.zoom,n),y.fillRect(-s/2-f,-c-f,s+2*f,c+u+2*f),y.fillStyle=this.fill.get(e.zoom,n),y.font=a,y.fillText(r,-s/2,0)},"draw")}]}};l(Jt,"ShieldSymbolizer");var $t=Jt,Kt=class Kt{constructor(e){this.list=e}place(e,t,n){let r=this.list[0].place(e,t,n);if(!r)return;let a=r[0],o=a.anchor,s=a.bboxes[0],c=s.maxY-s.minY,u=[{draw:a.draw,translate:{x:0,y:0}}],d=[[new x(t[0][0].x,t[0][0].y+c)]];for(let f=1;f{for(let m of u)f.save(),f.translate(m.translate.x,m.translate.y),m.draw(f),f.restore()},"draw")}]}};l(Kt,"FlexSymbolizer");var Ut=Kt,dn=l((i,e)=>({minX:Math.min(i.minX,e.minX),minY:Math.min(i.minY,e.minY),maxX:Math.max(i.maxX,e.maxX),maxY:Math.max(i.maxY,e.maxY)}),"mergeBbox"),Gt=class Gt{constructor(e){this.list=e}place(e,t,n){let r=this.list[0];if(!r)return;let a=r.place(e,t,n);if(!a)return;let o=a[0],s=o.anchor,c=o.bboxes[0],u=[o.draw];for(let h=1;h{for(let f of u)f(h)},"draw")}]}};l(Gt,"GroupSymbolizer");var Ae=Gt,Qt=class Qt{constructor(e){this.symbolizer=e}place(e,t,n){let r=t[0][0],a=this.symbolizer.place(e,[[new x(0,0)]],n);if(!a||a.length===0)return;let o=a[0],s=o.bboxes[0],c=s.maxX-s.minX,u=s.maxY-s.minY,d={minX:r.x-c/2,maxX:r.x+c/2,minY:r.y-u/2,maxY:r.y+u/2};return[{anchor:r,bboxes:[d],draw:l(f=>{f.translate(-c/2,u/2-s.maxY),o.draw(f,{justify:2})},"draw")}]}};l(Qt,"CenteredSymbolizer");var at=Qt,ei=class ei{constructor(e,t){this.padding=new T(e,0),this.symbolizer=t}place(e,t,n){let r=this.symbolizer.place(e,t,n);if(!r||r.length===0)return;let a=this.padding.get(e.zoom,n);for(let o of r)for(let s of o.bboxes)s.minX-=a,s.minY-=a,s.maxX+=a,s.maxY+=a;return r}};l(ei,"Padding");var Nt=ei,ti=class ti{constructor(e){this.font=new oe(e),this.text=new ae(e),this.fill=new B(e.fill,"black"),this.stroke=new B(e.stroke,"black"),this.width=new T(e.width,0),this.lineHeight=new T(e.lineHeight,1),this.letterSpacing=new T(e.letterSpacing,0),this.maxLineCodeUnits=new T(e.maxLineChars,15),this.justify=e.justify}place(e,t,n){let r=this.text.get(e.zoom,n);if(!r)return;let a=this.font.get(e.zoom,n);e.scratch.font=a;let o=this.letterSpacing.get(e.zoom,n),s=zt(r,this.maxLineCodeUnits.get(e.zoom,n)),c="",u=0;for(let g of s)g.length>u&&(u=g.length,c=g);let d=e.scratch.measureText(c),h=d.width+o*(u-1),f=d.actualBoundingBoxAscent,m=d.actualBoundingBoxDescent,p=(f+m)*this.lineHeight.get(e.zoom,n),y=new x(t[0][0].x,t[0][0].y),_={minX:y.x,minY:y.y-f,maxX:y.x+h,maxY:y.y+m+(s.length-1)*p};return[{anchor:y,bboxes:[_],draw:l((g,w)=>{g.globalAlpha=1,g.font=a,g.fillStyle=this.fill.get(e.zoom,n);let F=this.width.get(e.zoom,n),b=0;for(let S of s){let P=0;if(this.justify===2||w&&w.justify===2?P=(h-g.measureText(S).width)/2:(this.justify===3||w&&w.justify===3)&&(P=h-g.measureText(S).width),F)if(g.lineWidth=F*2,g.strokeStyle=this.stroke.get(e.zoom,n),o>0){let k=P;for(let C of S)g.strokeText(C,k,b),k+=g.measureText(C).width+o}else g.strokeText(S,P,b);if(o>0){let k=P;for(let C of S)g.fillText(C,k,b),k+=g.measureText(C).width+o}else g.fillText(S,P,b);b+=p}},"draw")}]}};l(ti,"TextSymbolizer");var Be=ti,ii=class ii{constructor(e){this.centered=new at(new Be(e))}place(e,t,n){return this.centered.place(e,t,n)}};l(ii,"CenteredTextSymbolizer");var Q=ii,ni=class ni{constructor(e,t){var n,r,a;this.symbolizer=e,this.offsetX=new T(t.offsetX,0),this.offsetY=new T(t.offsetY,0),this.justify=(n=t.justify)!=null?n:void 0,this.placements=(r=t.placements)!=null?r:[2,6,8,4,1,3,5,7],this.ddValues=(a=t.ddValues)!=null?a:()=>({})}place(e,t,n){if(n.geomType!==1)return;let r=t[0][0],a=this.symbolizer.place(e,[[new x(0,0)]],n);if(!a||a.length===0)return;let o=a[0],s=o.bboxes[0],c=this.offsetX,u=this.offsetY,d=this.justify,h=this.placements,{offsetX:f,offsetY:m,justify:p,placements:y}=this.ddValues(e.zoom,n)||{};f&&(c=new T(f,0)),m&&(u=new T(m,0)),p&&(d=p),y&&(h=y);let _=c.get(e.zoom,n),v=u.get(e.zoom,n),g=l((P,k)=>({minX:P.x+k.x+s.minX,minY:P.y+k.y+s.minY,maxX:P.x+k.x+s.maxX,maxY:P.y+k.y+s.maxY}),"getBbox"),w=new x(_,v),F,b=l(P=>{P.translate(w.x,w.y),o.draw(P,{justify:F})},"draw"),S=l((P,k)=>{let C=g(P,k);if(!e.index.bboxCollides(C,e.order))return[{anchor:r,bboxes:[C],draw:b}]},"placeLabelInPoint");for(let P of h){let k=this.computeXaxisOffset(_,s,P),C=this.computeYaxisOffset(v,s,P);return F=this.computeJustify(d,P),w=new x(k,C),S(r,w)}}computeXaxisOffset(e,t,n){let r=t.maxX,a=r/2;return[1,5].includes(n)?e-a:[8,7,6].includes(n)?e-r:e}computeYaxisOffset(e,t,n){let r=Math.abs(t.minY),a=t.maxY,o=(t.minY+t.maxY)/2;return[3,7].includes(n)?e-o:[8,2,1].includes(n)?e-a:[6,4,5].includes(n)?e+r:e}computeJustify(e,t){return e||([1,5].includes(t)?2:[2,3,4].includes(t)?1:3)}};l(ni,"OffsetSymbolizer");var ot=ni,ri=class ri{constructor(e){this.symbolizer=new ot(new Be(e),e)}place(e,t,n){return this.symbolizer.place(e,t,n)}};l(ri,"OffsetTextSymbolizer");var Re=ri,hn=(n=>(n[n.Above=1]="Above",n[n.Center=2]="Center",n[n.Below=3]="Below",n))(hn||{}),ai=class ai{constructor(e){var t;this.font=new oe(e),this.text=new ae(e),this.fill=new B(e.fill,"black"),this.stroke=new B(e.stroke,"black"),this.width=new T(e.width,0),this.offset=new T(e.offset,0),this.position=(t=e.position)!=null?t:1,this.maxLabelCodeUnits=new T(e.maxLabelChars,40),this.repeatDistance=new T(e.repeatDistance,250)}place(e,t,n){let r=this.text.get(e.zoom,n);if(!r||r.length>this.maxLabelCodeUnits.get(e.zoom,n))return;let a=20,o=n.bbox;if(o.maxY-o.minY4&&(h*=1<({minX:b.x-f/2,minY:b.y-f/2,maxX:b.x+f/2,maxY:b.y+f/2})),F=l(b=>{b.globalAlpha=1,b.rotate(Math.atan2(v,_)),_<0&&(b.scale(-1,-1),b.translate(-u,0));let S=0;this.position===3?S+=d:this.position===2&&(S+=d/2),b.translate(0,S-this.offset.get(e.zoom,n)),b.font=s;let P=this.width.get(e.zoom,n);P&&(b.lineWidth=P,b.strokeStyle=this.stroke.get(e.zoom,n),b.strokeText(r,0,0)),b.fillStyle=this.fill.get(e.zoom,n),b.fillText(r,0,0)},"draw");p.push({anchor:y.start,bboxes:w,draw:F,deduplicationKey:r,deduplicationDistance:h})}return p}};l(ai,"LineLabelSymbolizer");var ee=ai;var E=l((i,e)=>{let t=i[e];return typeof t=="string"?t:""},"getString"),fn=l((i,e)=>{let t=i[e];return typeof t=="number"?t:0},"getNumber"),je=l(i=>[{dataLayer:"earth",symbolizer:new M({fill:i.earth})},...i.landcover?[{dataLayer:"landcover",symbolizer:new M({fill:l((e,t)=>{let n=i.landcover;if(!n||!t)return"";let r=E(t.props,"kind");return r==="grassland"?n.grassland:r==="barren"?n.barren:r==="urban_area"?n.urban_area:r==="farmland"?n.farmland:r==="glacier"?n.glacier:r==="scrub"?n.scrub:n.forest},"fill"),opacity:l((e,t)=>e===8?.5:1,"opacity")})}]:[],{dataLayer:"landuse",symbolizer:new M({fill:l((e,t)=>Ze(i.park_a,i.park_b,Math.min(Math.max(e/12,12),0)),"fill")}),filter:l((e,t)=>{let n=E(t.props,"kind");return["allotments","village_green","playground"].includes(n)},"filter")},{dataLayer:"landuse",symbolizer:new M({fill:i.park_b,opacity:l((e,t)=>e<8?0:e===8?.5:1,"opacity")}),filter:l((e,t)=>{let n=E(t.props,"kind");return["national_park","park","cemetery","protected_area","nature_reserve","forest","golf_course"].includes(n)},"filter")},{dataLayer:"landuse",symbolizer:new M({fill:i.hospital}),filter:l((e,t)=>t.props.kind==="hospital","filter")},{dataLayer:"landuse",symbolizer:new M({fill:i.industrial}),filter:l((e,t)=>t.props.kind==="industrial","filter")},{dataLayer:"landuse",symbolizer:new M({fill:i.school}),filter:l((e,t)=>{let n=E(t.props,"kind");return["school","university","college"].includes(n)},"filter")},{dataLayer:"landuse",symbolizer:new M({fill:i.beach}),filter:l((e,t)=>t.props.kind==="beach","filter")},{dataLayer:"landuse",symbolizer:new M({fill:i.zoo}),filter:l((e,t)=>t.props.kind==="zoo","filter")},{dataLayer:"landuse",symbolizer:new M({fill:i.zoo}),filter:l((e,t)=>{let n=E(t.props,"kind");return["military","naval_base","airfield"].includes(n)},"filter")},{dataLayer:"landuse",symbolizer:new M({fill:l((e,t)=>Ze(i.wood_a,i.wood_b,Math.min(Math.max(e/12,12),0)),"fill"),opacity:l((e,t)=>e<8?0:e===8?.5:1,"opacity")}),filter:l((e,t)=>{let n=E(t.props,"kind");return["wood","nature_reserve","forest"].includes(n)},"filter")},{dataLayer:"landuse",symbolizer:new M({fill:l((e,t)=>Ze(i.scrub_a,i.scrub_b,Math.min(Math.max(e/12,12),0)),"fill")}),filter:l((e,t)=>{let n=E(t.props,"kind");return["scrub","grassland","grass"].includes(n)},"filter")},{dataLayer:"landuse",symbolizer:new M({fill:i.scrub_b}),filter:l((e,t)=>{let n=E(t.props,"kind");return["scrub","grassland","grass"].includes(n)},"filter")},{dataLayer:"landuse",symbolizer:new M({fill:i.glacier}),filter:l((e,t)=>t.props.kind==="glacier","filter")},{dataLayer:"landuse",symbolizer:new M({fill:i.sand,opacity:l((e,t)=>e<8?0:e===8?.5:1,"opacity")}),filter:l((e,t)=>t.props.kind==="sand","filter")},{dataLayer:"landuse",symbolizer:new M({fill:i.aerodrome}),filter:l((e,t)=>t.props.kind==="aerodrome","filter")},{dataLayer:"water",symbolizer:new M({fill:i.water}),filter:l((e,t)=>t.geomType===3,"filter")},{dataLayer:"roads",symbolizer:new O({color:i.runway,width:l((e,t)=>V(1.6,[[11,0],[13,4],[19,30]])(e),"width")}),filter:l((e,t)=>t.props.kind_detail==="runway","filter")},{dataLayer:"roads",symbolizer:new O({color:i.runway,width:l((e,t)=>V(1.6,[[14,0],[14.5,1],[16,6]])(e),"width")}),filter:l((e,t)=>t.props.kind_detail==="taxiway","filter")},{dataLayer:"roads",symbolizer:new O({color:i.pier,width:l((e,t)=>V(1.6,[[13,0],[13.5,0,5],[21,16]])(e),"width")}),filter:l((e,t)=>t.props.kind==="path"&&t.props.kind_detail==="pier","filter")},{dataLayer:"water",minzoom:14,symbolizer:new O({color:i.water,width:l((e,t)=>V(1.6,[[9,0],[9.5,1],[18,12]])(e),"width")}),filter:l((e,t)=>t.geomType===2&&t.props.kind==="river","filter")},{dataLayer:"water",minzoom:14,symbolizer:new O({color:i.water,width:.5}),filter:l((e,t)=>t.geomType===2&&t.props.kind==="stream","filter")},{dataLayer:"landuse",symbolizer:new M({fill:i.pedestrian}),filter:l((e,t)=>t.props.kind==="pedestrian","filter")},{dataLayer:"landuse",symbolizer:new M({fill:i.pier}),filter:l((e,t)=>t.props.kind==="pier","filter")},{dataLayer:"buildings",symbolizer:new M({fill:i.buildings,opacity:.5})},{dataLayer:"roads",symbolizer:new O({color:i.major,width:l((e,t)=>V(1.6,[[14,0],[20,7]])(e),"width")}),filter:l((e,t)=>{let n=E(t.props,"kind");return["other","path"].includes(n)},"filter")},{dataLayer:"roads",symbolizer:new O({color:i.major,width:l((e,t)=>V(1.6,[[13,0],[18,8]])(e),"width")}),filter:l((e,t)=>t.props.kind==="minor_road","filter")},{dataLayer:"roads",symbolizer:new O({color:i.major,width:l((e,t)=>V(1.6,[[6,0],[12,1.6],[15,3],[18,13]])(e),"width")}),filter:l((e,t)=>t.props.kind==="major_road","filter")},{dataLayer:"roads",symbolizer:new O({color:i.major,width:l((e,t)=>V(1.6,[[3,0],[6,1.1],[12,1.6],[15,5],[18,15]])(e),"width")}),filter:l((e,t)=>t.props.kind==="highway","filter")},{dataLayer:"boundaries",symbolizer:new O({color:i.boundaries,width:1}),filter:l((e,t)=>{let n=t.props.kind_detail;return typeof n=="number"&&n<=2},"filter")},{dataLayer:"roads",symbolizer:new O({dash:[.3,.75],color:i.railway,dashWidth:l((e,t)=>V(1.6,[[4,0],[7,.15],[19,9]])(e),"dashWidth"),opacity:.5}),filter:l((e,t)=>t.props.kind==="rail","filter")},{dataLayer:"boundaries",symbolizer:new O({color:i.boundaries,width:.5}),filter:l((e,t)=>{let n=t.props.kind_detail;return typeof n=="number"&&n>2},"filter")}],"paintRules"),Oe=l((i,e)=>{let t=[`name:${e}`,"name"];return[{dataLayer:"roads",symbolizer:new ee({labelProps:t,fill:i.roads_label_minor,font:"400 12px sans-serif",width:2,stroke:i.roads_label_minor_halo}),minzoom:16,filter:l((n,r)=>{let a=E(r.props,"kind");return["minor_road","other","path"].includes(a)},"filter")},{dataLayer:"roads",symbolizer:new ee({labelProps:t,fill:i.roads_label_major,font:"400 12px sans-serif",width:2,stroke:i.roads_label_major_halo}),minzoom:12,filter:l((n,r)=>{let a=E(r.props,"kind");return["highway","major_road"].includes(a)},"filter")},{dataLayer:"roads",symbolizer:new ee({labelProps:t,fill:i.roads_label_major,font:"400 12px sans-serif",width:2,stroke:i.roads_label_major_halo}),minzoom:12,filter:l((n,r)=>{let a=E(r.props,"kind");return["highway","major_road"].includes(a)},"filter")},{dataLayer:"water",symbolizer:new Q({labelProps:t,fill:i.ocean_label,lineHeight:1.5,letterSpacing:1,font:l((n,r)=>`400 ${st([[3,10],[10,12]])(n)}px sans-serif`,"font"),textTransform:"uppercase"}),filter:l((n,r)=>{let a=E(r.props,"kind");return r.geomType===1&&["ocean","bay","strait","fjord"].includes(a)},"filter")},{dataLayer:"water",symbolizer:new Q({labelProps:t,fill:i.ocean_label,lineHeight:1.5,letterSpacing:1,font:l((n,r)=>`400 ${st([[3,10],[6,12],[10,12]])(n)}px sans-serif`,"font")}),filter:l((n,r)=>{let a=E(r.props,"kind");return r.geomType===1&&["sea","lake","water"].includes(a)},"filter")},{dataLayer:"places",symbolizer:new Q({labelProps:l((n,r)=>n<6?[`ref:${e}`,"ref"]:t,"labelProps"),fill:i.state_label,stroke:i.state_label_halo,width:1,lineHeight:1.5,font:l((n,r)=>"400 12px sans-serif","font"),textTransform:"uppercase"}),filter:l((n,r)=>r.props.kind==="region","filter")},{dataLayer:"places",symbolizer:new Q({labelProps:t,fill:i.country_label,lineHeight:1.5,font:l((n,r)=>(n<6,"600 12px sans-serif"),"font"),textTransform:"uppercase"}),filter:l((n,r)=>r.props.kind==="country","filter")},{dataLayer:"places",minzoom:9,symbolizer:new Q({labelProps:t,fill:i.city_label,lineHeight:1.5,font:l((n,r)=>{if(!r)return"400 12px sans-serif";let a=r.props.min_zoom,o=400;a&&a<=5&&(o=600);let s=12,c=r.props.population_rank;return c&&c>9&&(s=16),`${o} ${s}px sans-serif`},"font")}),sort:l((n,r)=>{let a=fn(n,"min_zoom"),o=fn(r,"min_zoom");return a-o},"sort"),filter:l((n,r)=>r.props.kind==="locality","filter")},{dataLayer:"places",maxzoom:8,symbolizer:new Ae([new De({radius:2,fill:i.city_label,stroke:i.city_label_halo,width:1.5}),new Re({labelProps:t,fill:i.city_label,stroke:i.city_label_halo,width:1,offsetX:6,offsetY:4.5,font:l((n,r)=>"400 12px sans-serif","font")})]),filter:l((n,r)=>r.props.kind==="locality","filter")}]},"labelRules");function oi(i,e,t,n,r){mn(i,e,t||0,n||i.length-1,r||fa)}l(oi,"quickselect");function mn(i,e,t,n,r){for(;n>t;){if(n-t>600){var a=n-t+1,o=e-t+1,s=Math.log(a),c=.5*Math.exp(2*s/3),u=.5*Math.sqrt(s*c*(a-c)/a)*(o-a/2<0?-1:1),d=Math.max(t,Math.floor(e-o*c/a+u)),h=Math.min(n,Math.floor(e+(a-o)*c/a+u));mn(i,e,d,h,r)}var f=i[e],m=t,p=n;for(Ie(i,t,e),r(i[n],f)>0&&Ie(i,t,n);m0;)p--}r(i[t],f)===0?Ie(i,t,p):(p++,Ie(i,p,n)),p<=e&&(t=p+1),e<=p&&(n=p-1)}}l(mn,"quickselectStep");function Ie(i,e,t){var n=i[e];i[e]=i[t],i[t]=n}l(Ie,"swap");function fa(i,e){return ie?1:0}l(fa,"defaultCompare");var ci=class ci{constructor(e=9){this._maxEntries=Math.max(4,e),this._minEntries=Math.max(2,Math.ceil(this._maxEntries*.4)),this.clear()}all(){return this._all(this.data,[])}search(e){let t=this.data,n=[];if(!ct(e,t))return n;let r=this.toBBox,a=[];for(;t;){for(let o=0;o=0&&a[t].children.length>this._maxEntries;)this._split(a,t),t--;this._adjustParentBBoxes(r,a,t)}_split(e,t){let n=e[t],r=n.children.length,a=this._minEntries;this._chooseSplitAxis(n,a,r);let o=this._chooseSplitIndex(n,a,r),s=be(n.children.splice(o,n.children.length-o));s.height=n.height,s.leaf=n.leaf,ge(n,this.toBBox),ge(s,this.toBBox),t?e[t-1].children.push(s):this._splitRoot(n,s)}_splitRoot(e,t){this.data=be([e,t]),this.data.height=e.height+1,this.data.leaf=!1,ge(this.data,this.toBBox)}_chooseSplitIndex(e,t,n){let r,a=1/0,o=1/0;for(let s=t;s<=n-t;s++){let c=Ee(e,0,s,this.toBBox),u=Ee(e,s,n,this.toBBox),d=ya(c,u),h=si(c)+si(u);d=t;u--){let d=e.children[u];Xe(s,e.leaf?a(d):d),c+=lt(s)}return c}_adjustParentBBoxes(e,t,n){for(let r=n;r>=0;r--)Xe(t[r],e)}_condense(e){for(let t=e.length-1,n;t>=0;t--)e[t].children.length===0?t>0?(n=e[t-1].children,n.splice(n.indexOf(e[t]),1)):this.clear():ge(e[t],this.toBBox)}};l(ci,"RBush");var Ye=ci;function ma(i,e,t){if(!t)return e.indexOf(i);for(let n=0;n=i.minX&&e.maxY>=i.minY}l(ct,"intersects");function be(i){return{children:i,height:1,leaf:!0,minX:1/0,minY:1/0,maxX:-1/0,maxY:-1/0}}l(be,"createNode");function pn(i,e,t,n,r){let a=[e,t];for(;a.length;){if(t=a.pop(),e=a.pop(),t-e<=n)continue;let o=e+Math.ceil((t-e)/n/2)*n;oi(i,o,e,t,r),a.push(e,o,o,t)}}l(pn,"multiSelect");var $e=l((i,e,t)=>{let n=[];for(let r of i){let a=[];for(let o of r)a.push(o.clone().mult(e).add(t));n.push(a)}return n},"transformGeom"),Ve=l((i,e)=>{let t=1<=t?i%t:i},"wrap"),ui=class ui{constructor(e,t,n){this.tileCache=e,this.maxDataLevel=t,this.levelDiff=n}dataTilesForBounds(e,t){let n=D(2,e)/D(2,Math.ceil(e)),r=[],a=1,o=this.tileCache.tileSize;if(ethis.tileCache.get(a.dataTile)))).map((a,o)=>{let s=n[o];return{data:a,z:e,dataTile:s.dataTile,scale:s.scale,dim:s.dim,origin:s.origin}})})}getDisplayTile(e){return A(this,null,function*(){let t=this.dataTileForDisplayTile(e);return{data:yield this.tileCache.get(t.dataTile),z:e.z,dataTile:t.dataTile,scale:t.scale,origin:t.origin,dim:t.dim}})}queryFeatures(e,t,n,r){let a=Math.round(n),o=Math.min(a-this.levelDiff,this.maxDataLevel),s=r/(1<{let e=l(n=>{let r=n.levelDiff===void 0?1:n.levelDiff,a=n.maxDataZoom||15,o;if(typeof n.url=="string")new URL(n.url,"http://example.com").pathname.endsWith(".pmtiles")?o=new pe(n.url,!0):o=new Me(n.url,!0);else if(n.url)o=new pe(n.url,!0);else throw new Error(`Invalid source ${n.url}`);let s=new Ce(o,256*1<{let r=e/256,a=Math.floor(t.minX/256),o=Math.floor(t.minY/256),s=Math.floor(t.maxX/256),c=Math.floor(t.maxY/256),u=Math.log2(r),d=[];for(let h=a;h<=s;h++){let f=h%(1<this.dim&&(s=!0);if(o||s){let c=o?this.dim:-this.dim,u=[];for(let f of e.bboxes)u.push({minX:f.minX+c,minY:f.minY,maxX:f.maxX+c,maxY:f.maxY});let d={anchor:new x(e.anchor.x+c,e.anchor.y),bboxes:u,draw:e.draw,order:t,tileKey:n},h=this.current.get(n);h&&h.add(d);for(let f of u)this.tree.insert({minX:f.minX,minY:f.minY,maxX:f.maxX,maxY:f.maxY,indexedLabel:d})}}pruneOrNoop(e){let t=e.split(":"),n,r=0,a=0;for(let o of this.current.keys()){let s=o.split(":");if(s[3]===t[3]){a++;let c=Math.sqrt(D(+s[0]-+t[0],2)+D(+s[1]-+t[1],2));c>r&&(r=c,n=o)}n&&a>this.maxLabeledTiles&&this.pruneKey(n)}}pruneKey(e){let t=this.current.get(e);if(!t)return;let n=[];for(let r of this.tree.all())t.has(r.indexedLabel)&&n.push(r);for(let r of n)this.tree.remove(r);this.current.delete(e)}removeLabel(e){let t=[];for(let r of this.tree.all())e===r.indexedLabel&&t.push(r);for(let r of t)this.tree.remove(r);let n=this.current.get(e.tileKey);n&&n.delete(e)}};l(di,"Index");var dt=di,hi=class hi{constructor(e,t,n,r,a){this.index=new dt(256*1<o.maxzoom)continue;let s=o.dataSource||"",c=e.get(s);if(c)for(let u of c){let d=`${G(u.dataTile)}:${s}`;if(!n.has(d))continue;let h=u.data.get(o.dataLayer);if(h===void 0)continue;let f=h;o.sort&&f.sort((p,y)=>o.sort?o.sort(p.props,y.props):0);let m={index:this.index,zoom:this.z,scratch:this.scratch,order:a,overzoom:this.z-u.dataTile.z};for(let p of f){if(o.filter&&!o.filter(this.z,p))continue;let y=$e(p.geom,u.scale,u.origin),_=o.symbolizer.place(m,y,p);if(_)for(let v of _){let g=!1;if(!(v.deduplicationKey&&this.index.deduplicationCollides(v))){if(this.index.labelCollides(v,1/0)){if(!this.index.labelCollides(v,a)){let w=this.index.searchLabel(v,1/0);for(let F of w){this.index.removeLabel(F);for(let b of F.bboxes)this.findInvalidatedTiles(r,u.dim,b,d)}this.index.insert(v,a,d),g=!0}}else this.index.insert(v,a,d),g=!0;if(g)for(let w of v.bboxes)(w.maxX>u.origin.x+u.dim||w.minXu.origin.y+u.dim)&&this.findInvalidatedTiles(r,u.dim,w,d)}}}}}for(let a of n)this.index.pruneOrNoop(a);return r.size>0&&this.callback&&this.callback(r),performance.now()-t}findInvalidatedTiles(e,t,n,r){let a=gn(this.z,t,n);for(let o of a)o.key!==r&&this.index.hasPrefix(o.key)&&e.add(o.display)}add(e){let t=!0;for(let[r,a]of e)for(let o of a)this.index.has(`${G(o.dataTile)}:${r}`)||(t=!1);return t?0:this.layout(e)}};l(hi,"Labeler");var ye=hi,fi=class fi{constructor(e,t,n,r){this.labelers=new Map,this.scratch=e,this.labelRules=t,this.maxLabeledTiles=n,this.callback=r}add(e,t){let n=this.labelers.get(e);return n||(n=new ye(e,this.scratch,this.labelRules,this.maxLabeledTiles,this.callback),this.labelers.set(e,n)),n.add(t)}getIndex(e){let t=this.labelers.get(e);if(t)return t.index}};l(fi,"Labelers");var xe=fi;function Ne(i,e,t,n,r,a,o,s,c){let u=performance.now();i.save(),i.miterLimit=2;for(let d of r){if(d.minzoom&&ed.maxzoom)continue;let h=t.get(d.dataSource||"");if(h)for(let f of h){let m=f.data.get(d.dataLayer);if(m===void 0)continue;d.symbolizer.before&&d.symbolizer.before(i,f.z);let p=f.origin,y=f.dim,_=f.scale;if(i.save(),s){i.beginPath();let v=Math.max(p.x-o.x,a.minX-o.x),g=Math.max(p.y-o.y,a.minY-o.y),w=Math.min(p.x-o.x+y,a.maxX-o.x),F=Math.min(p.y-o.y+y,a.maxY-o.y);i.rect(v,g,w-v,F-g),i.clip()}i.translate(p.x-o.x,p.y-o.y);for(let v of m){let g=v.geom,w=v.bbox;w.maxX*_+p.xa.maxX||w.minY*_+p.y>a.maxY||w.maxY*_+p.y{let e=Math.PI/180,t=Math.max(Math.min(bn,i.y),-bn),n=Math.sin(t*e);return new x(He*i.x*e,He*Math.log((1+n)/(1-n))/2)},"project"),xa=l(i=>{let e=180/Math.PI;return{lat:(2*Math.atan(Math.exp(i.y/He))-Math.PI/2)*e,lng:i.x*e/He}},"unproject"),_a=l((i,e)=>t=>{let n=yn(t);return new x((n.x+$)/($*2),1-(n.y+$)/($*2)).mult(D(2,e)*256).sub(i)},"instancedProject"),wa=l((i,e)=>t=>{let n=new x(t.x,t.y).add(i).div(D(2,e)*256),r=new x(n.x*($*2)-$,(1-n.y)*($*2)-$);return xa(r)},"instancedUnproject"),mi=l((i,e)=>{let t=e*(360/i);return Math.log2(t/256)},"getZoom"),gi=class gi{constructor(e){if(e.flavor){let t=we(e.flavor);this.paintRules=je(t),this.labelRules=Oe(t,e.lang||"en"),this.backgroundColor=t.background}else this.paintRules=e.paintRules||[],this.labelRules=e.labelRules||[],this.backgroundColor=e.backgroundColor;this.views=Ue(e),this.debug=e.debug||""}drawContext(e,t,n,r,a){return A(this,null,function*(){let o=yn(r),c=new x((o.x+$)/($*2),1-(o.y+$)/($*2)).clone().mult(D(2,a)*256).sub(new x(t/2,n/2)),u={minX:c.x,minY:c.y,maxX:c.x+t,maxY:c.y+n},d=[];for(let[g,w]of this.views){let F=w.getBbox(a,u);d.push({key:g,promise:F})}let h=yield Promise.all(d.map(g=>g.promise.then(w=>({status:"fulfilled",value:w,key:g.key}),w=>({status:"rejected",value:[],reason:w,key:g.key})))),f=new Map;for(let g of h)g.status==="fulfilled"&&f.set(g.key,g.value);let m=performance.now(),p=new ye(a,e,this.labelRules,16,void 0),y=p.add(f);this.backgroundColor&&(e.save(),e.fillStyle=this.backgroundColor,e.fillRect(0,0,t,n),e.restore());let _=this.paintRules,v=Ne(e,a,f,p.index,_,u,c,!0,this.debug);if(this.debug){e.save(),e.translate(-c.x,-c.y),e.strokeStyle=this.debug,e.fillStyle=this.debug,e.font="12px sans-serif";let g=0;for(let[w,F]of f){for(let b of F){e.strokeRect(b.origin.x,b.origin.y,b.dim,b.dim);let S=b.dataTile;e.fillText(`${w+(w?" ":"")+S.z} ${S.x} ${S.y}`,b.origin.x+4,b.origin.y+14*(1+g))}g++}e.restore()}return{elapsed:performance.now()-m,project:_a(c,a),unproject:wa(c,a)}})}drawCanvas(a,o,s){return A(this,arguments,function*(e,t,n,r={}){let c=window.devicePixelRatio,u=e.clientWidth,d=e.clientHeight;e.width===u*c&&e.height===d*c||(e.width=u*c,e.height=d*c),r.lang&&(e.lang=r.lang);let h=e.getContext("2d");if(!h){console.error("Failed to initialize canvas2d context.");return}return h.setTransform(c,0,0,c,0,0),this.drawContext(h,u,d,t,n)})}drawContextBounds(e,t,n,r,a){return A(this,null,function*(){let o=n.x-t.x,s=new x((t.x+n.x)/2,(t.y+n.y)/2);return this.drawContext(e,r,a,s,mi(o,r))})}drawCanvasBounds(o,s,c,u){return A(this,arguments,function*(e,t,n,r,a={}){let d=n.x-t.x,h=new x((t.x+n.x)/2,(t.y+n.y)/2);return this.drawCanvas(e,h,mi(d,r),a)})}};l(gi,"Static");var pi=gi;var ka=l(i=>new Promise(e=>{setTimeout(()=>{e()},i)}),"timer"),va=l(i=>i.then(e=>({status:"fulfilled",value:e}),e=>({status:"rejected",reason:e})),"reflect"),za=l((i={})=>{let t=class t extends L.GridLayer{constructor(r={}){var o;if(r.noWrap&&!r.bounds&&(r.bounds=[[-90,-180],[90,180]]),r.attribution==null&&(r.attribution='Protomaps \xA9 OpenStreetMap'),super(r),r.flavor){let s=we(r.flavor);this.paintRules=je(s),this.labelRules=Oe(s,r.lang||"en"),this.backgroundColor=s.background}else this.paintRules=r.paintRules||[],this.labelRules=r.labelRules||[],this.backgroundColor=r.backgroundColor;this.devicePixelRatio=(o=r.devicePixelRatio)!=null?o:window.devicePixelRatio,this.lastRequestedZ=void 0,this.tasks=r.tasks||[],this.views=Ue(r),this.debug=r.debug;let a=document.createElement("canvas").getContext("2d");this.scratch=a,this.onTilesInvalidated=s=>{for(let c of s)this.rerenderTile(c)},this.labelers=new xe(this.scratch,this.labelRules,16,this.onTilesInvalidated),this.tileSize=256*this.devicePixelRatio,this.tileDelay=r.tileDelay||3,this.lang=r.lang}renderTile(r,a,o,s=()=>{}){return A(this,null,function*(){this.lastRequestedZ=r.z;let c=[];for(let[k,C]of this.views){let R=C.getDisplayTile(r);c.push({key:k,promise:R})}let u=yield Promise.all(c.map(k=>k.promise.then(C=>({status:"fulfilled",value:C,key:k.key}),C=>({status:"rejected",reason:C,key:k.key})))),d=new Map;for(let k of u)k.status==="fulfilled"?d.set(k.key,[k.value]):k.reason.name==="AbortError"||console.error(k.reason);if(a.key!==o||this.lastRequestedZ!==r.z||(yield Promise.all(this.tasks.map(va)),a.key!==o)||this.lastRequestedZ!==r.z)return;let h=this.labelers.add(r.z,d);if(a.key!==o||this.lastRequestedZ!==r.z)return;let f=this.labelers.getIndex(r.z);if(!this._map)return;let m=this._map.getCenter().wrap(),p=this._getTiledPixelBounds(m),_=this._pxBoundsToTileRange(p).getCenter(),v=r.distanceTo(_)*this.tileDelay;if(yield ka(v),a.key!==o||this.lastRequestedZ!==r.z)return;let g=16,w={minX:256*r.x-g,minY:256*r.y-g,maxX:256*(r.x+1)+g,maxY:256*(r.y+1)+g},F=new x(256*r.x,256*r.y);a.width=this.tileSize,a.height=this.tileSize;let b=a.getContext("2d");if(!b){console.error("Failed to get Canvas context");return}b.setTransform(this.tileSize/256,0,0,this.tileSize/256,0,0),b.clearRect(0,0,256,256),this.backgroundColor&&(b.save(),b.fillStyle=this.backgroundColor,b.fillRect(0,0,256,256),b.restore());let S=0,P=this.paintRules;if(S=Ne(b,r.z,d,this.xray?null:f,P,w,F,!1,this.debug),this.debug){b.save(),b.fillStyle=this.debug,b.font="600 12px sans-serif",b.fillText(`${r.z} ${r.x} ${r.y}`,4,14),b.font="12px sans-serif";let k=28;for(let[C,R]of d){let te=R[0].dataTile;b.fillText(`${C+(C?" ":"")+te.z} ${te.x} ${te.y}`,4,k),k+=14}b.font="600 10px sans-serif",S>8&&(b.fillText(`${S.toFixed()} ms paint`,4,k),k+=14),h>8&&b.fillText(`${h.toFixed()} ms layout`,4,k),b.strokeStyle=this.debug,b.lineWidth=.5,b.beginPath(),b.moveTo(0,0),b.lineTo(0,256),b.stroke(),b.lineWidth=.5,b.beginPath(),b.moveTo(0,0),b.lineTo(256,0),b.stroke(),b.restore()}s()})}rerenderTile(r){for(let a in this._tiles){let o=this._wrapCoords(this._keyToTileCoords(a));r===this._tileCoordsToKey(o)&&this.renderTile(o,this._tiles[a].el,r)}}queryTileFeaturesDebug(r,a,o=16){let s=new Map;for(let[c,u]of this.views)s.set(c,u.queryFeatures(r,a,this._map.getZoom(),o));return s}clearLayout(){this.labelers=new xe(this.scratch,this.labelRules,16,this.onTilesInvalidated)}rerenderTiles(){for(let r in this._tiles){let a=this._wrapCoords(this._keyToTileCoords(r)),o=this._tileCoordsToKey(a);this.renderTile(a,this._tiles[r].el,o)}}createTile(r,a){let o=L.DomUtil.create("canvas","leaflet-tile");o.lang=this.lang;let s=this._tileCoordsToKey(r);return o.key=s,this.renderTile(r,o,s,()=>{a(void 0,o)}),o}_removeTile(r){let a=this._tiles[r];a&&(a.el.removed=!0,a.el.key=void 0,L.DomUtil.removeClass(a.el,"leaflet-tile-loaded"),a.el.width=a.el.height=0,L.DomUtil.remove(a.el),delete this._tiles[r],this.fire("tileunload",{tile:a.el,coords:this._keyToTileCoords(r)}))}};l(t,"LeafletLayer");let e=t;return new e(i)},"leafletLayer");function bi(i){let e=0,t=0;for(let s of i)e+=s.w*s.h,t=Math.max(t,s.w);i.sort((s,c)=>c.h-s.h);let r=[{x:0,y:0,w:Math.max(Math.ceil(Math.sqrt(e/.95)),t),h:1/0}],a=0,o=0;for(let s of i)for(let c=r.length-1;c>=0;c--){let u=r[c];if(!(s.w>u.w||s.h>u.h)){if(s.x=u.x,s.y=u.y,o=Math.max(o,s.y+s.h),a=Math.max(a,s.x+s.w),s.w===u.w&&s.h===u.h){let d=r.pop();c{let n=new FontFace(i,`url(${e})`,{weight:t});return document.fonts.add(n),n.load()},"Font"),xn=l(i=>A(void 0,null,function*(){return new Promise((e,t)=>{let n=new Image;n.onload=()=>e(n),n.onerror=()=>t("Invalid SVG"),n.src=i})}),"mkimg"),Pa=` + + + + + + +`,xi=class xi{constructor(e){this.src=e,this.canvas=document.createElement("canvas"),this.mapping=new Map,this.missingBox={x:0,y:0,w:0,h:0}}load(){return A(this,null,function*(){let e=this.src,t=window.devicePixelRatio;e.endsWith(".html")&&(e=yield(yield fetch(e)).text());let n=new window.DOMParser().parseFromString(e,"text/html"),r=Array.from(n.body.children),a=yield xn(`data:image/svg+xml;base64,${btoa(Pa)}`),o=[{w:a.width*t,h:a.height*t,img:a,id:""}],s=new XMLSerializer;for(let d of r){let f=`data:image/svg+xml;base64,${btoa(s.serializeToString(d))}`,m=yield xn(f);o.push({w:m.width*t,h:m.height*t,img:m,id:d.id})}let c=bi(o);this.canvas.width=c.w,this.canvas.height=c.h;let u=this.canvas.getContext("2d");if(u)for(let d of o)d.x!==void 0&&d.y!==void 0&&(u.drawImage(d.img,d.x,d.y,d.w,d.h),d.id?this.mapping.set(d.id,{x:d.x,y:d.y,w:d.w,h:d.h}):this.missingBox={x:d.x,y:d.y,w:d.w,h:d.h});return this})}get(e){let t=this.mapping.get(e);return t||(t=this.missingBox),t}};l(xi,"Sheet");var yi=xi;return Mn(Sa);})(); +//# sourceMappingURL=protomaps-leaflet.js.map \ No newline at end of file diff --git a/man/addProtomaps.Rd b/man/addProtomaps.Rd new file mode 100644 index 0000000..6b1dc34 --- /dev/null +++ b/man/addProtomaps.Rd @@ -0,0 +1,107 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/addProtomaps.R +\name{addProtomaps} +\alias{addProtomaps} +\title{Add a Protomaps layer to a Leaflet map} +\usage{ +addProtomaps( + map, + url, + style = NULL, + flavor = c("light", "dark", "white", "grayscale", "black"), + colors = NULL, + paintRules = NULL, + labelRules = NULL, + backgroundColor = NULL, + lang = NULL, + attribution = "Protomaps", + options = protomapsOptions(), + layerId = NULL, + group = NULL +) +} +\arguments{ +\item{map}{A leaflet map object created with \code{\link[leaflet]{leaflet}}.} + +\item{url}{Character. URL to a PMTiles file or a tile endpoint with +\code{{z}/{x}/{y}.mvt} placeholders.} + +\item{style}{Optional style object created with \code{\link{pmMinimal}} or +\code{\link{pmStyle}}. Provides a convenient way to apply preset styles. +If provided, overrides \code{colors} and \code{labelRules}.} + +\item{flavor}{Character. Built-in flavor/theme to use. One of "light", "dark", +"white", "grayscale", or "black". Default is "light". Ignored if \code{style} +is provided.} + +\item{colors}{Optional list of color overrides. Use \code{\link{pmColors}} to +create this. Overrides specific colors while keeping built-in rendering rules.} + +\item{paintRules}{Optional list of paint rules created with \code{\link{pmPaintRule}}. +If provided, completely overrides the flavor's default paint rules. +For simple color changes, use \code{colors} instead.} + +\item{labelRules}{Optional list of label rules created with \code{\link{pmLabelRule}}. +If provided, completely overrides the flavor's default label rules.} + +\item{backgroundColor}{Character. Background color for the canvas. +Default is NULL (uses flavor default).} + +\item{lang}{Character. Language code for labels (e.g., "en", "de", "zh"). +Default is NULL (uses default language).} + +\item{attribution}{Character. Attribution text for the layer. +Default is "Protomaps".} + +\item{options}{A list of additional options created with \code{\link{protomapsOptions}}.} + +\item{layerId}{Character. Layer ID for the protomaps layer.} + +\item{group}{Character. Group name for layer control.} +} +\value{ +A modified leaflet map object. +} +\description{ +Adds a vector tile layer from a PMTiles source to a Leaflet map using +the protomaps-leaflet library. Supports built-in flavors and custom +styling rules. +} +\examples{ +\dontrun{ +library(leaflet) +library(protomapr) + +# Basic usage with demo tiles +leaflet() \%>\% + setView(lng = -122.4, lat = 37.8, zoom = 12) \%>\% + addProtomaps(url = protomaps_demo_url()) + +# Using dark flavor +leaflet() \%>\% + setView(lng = -122.4, lat = 37.8, zoom = 12) \%>\% + addProtomaps(url = protomaps_demo_url(), flavor = "dark") + +# Custom colors with proper rendering +leaflet() \%>\% + setView(lng = -122.4, lat = 37.8, zoom = 12) \%>\% + addProtomaps( + url = protomaps_demo_url(), + colors = pmColors(earth = "#d3d3d3", water = "#1a3a5c") + ) + +# Using preset styles (recommended for common use cases) +leaflet() \%>\% + setView(lng = -122.4, lat = 37.8, zoom = 10) \%>\% + addProtomaps(url = protomaps_demo_url(), style = pmStyle("minimal")) + +# Custom minimal style +leaflet() \%>\% + setView(lng = -122.4, lat = 37.8, zoom = 10) \%>\% + addProtomaps( + url = protomaps_demo_url(), + style = pmMinimal(land = "#f5f5f0", water = "#1a3a5c", labels = TRUE) + ) +} + +} diff --git a/man/pmCenteredTextSymbolizer.Rd b/man/pmCenteredTextSymbolizer.Rd new file mode 100644 index 0000000..947e4ce --- /dev/null +++ b/man/pmCenteredTextSymbolizer.Rd @@ -0,0 +1,49 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/symbolizers.R +\name{pmCenteredTextSymbolizer} +\alias{pmCenteredTextSymbolizer} +\title{Create a Centered Text Symbolizer} +\usage{ +pmCenteredTextSymbolizer( + font = "12px sans-serif", + fill = "#000000", + stroke = NULL, + width = 0, + lineHeight = NULL, + labelProps = NULL, + ... +) +} +\arguments{ +\item{font}{Character. Font specification (e.g., "12px sans-serif").} + +\item{fill}{Character. Text fill color. Default is "#000000".} + +\item{stroke}{Character. Text stroke (halo) color. Default is NULL.} + +\item{width}{Numeric. Stroke width for text halo. Default is 0.} + +\item{lineHeight}{Numeric. Line height multiplier for multi-line labels. +Default is NULL (uses library default). Use values like 1.0-1.2 for +tighter spacing.} + +\item{labelProps}{List. Properties to use for label text, in order of +preference. Default is \code{list("name")}.} + +\item{...}{Additional symbolizer options.} +} +\value{ +A list representing the symbolizer configuration. +} +\description{ +Creates a centered text symbolizer for rendering text labels centered +on point features. +} +\examples{ +pmCenteredTextSymbolizer(font = "14px Arial", fill = "black") + +# Tighter line spacing for multi-word labels +pmCenteredTextSymbolizer(font = "11px sans-serif", fill = "#444", + lineHeight = 1.1) + +} diff --git a/man/pmCircleSymbolizer.Rd b/man/pmCircleSymbolizer.Rd new file mode 100644 index 0000000..3c68e84 --- /dev/null +++ b/man/pmCircleSymbolizer.Rd @@ -0,0 +1,42 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/symbolizers.R +\name{pmCircleSymbolizer} +\alias{pmCircleSymbolizer} +\title{Create a Circle Symbolizer} +\usage{ +pmCircleSymbolizer( + radius = 4, + fill = "#000000", + stroke = NULL, + width = 1, + opacity = 1, + ... +) +} +\arguments{ +\item{radius}{Numeric. Circle radius in pixels. Default is 4.} + +\item{fill}{Character. Fill color for the circle. Default is "#000000".} + +\item{stroke}{Character. Stroke (outline) color. Default is NULL.} + +\item{width}{Numeric. Stroke width in pixels. Default is 1.} + +\item{opacity}{Numeric. Fill opacity from 0 to 1. Default is 1.} + +\item{...}{Additional symbolizer options.} +} +\value{ +A list representing the symbolizer configuration. +} +\description{ +Creates a circle symbolizer for rendering point features as circles. +} +\examples{ +# Simple red circle +pmCircleSymbolizer(radius = 6, fill = "red") + +# Circle with stroke +pmCircleSymbolizer(radius = 8, fill = "white", stroke = "black", width = 2) + +} diff --git a/man/pmCityLabels.Rd b/man/pmCityLabels.Rd new file mode 100644 index 0000000..c150963 --- /dev/null +++ b/man/pmCityLabels.Rd @@ -0,0 +1,49 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/styles.R +\name{pmCityLabels} +\alias{pmCityLabels} +\title{Create preset label rules for city names} +\usage{ +pmCityLabels( + style = c("hierarchical", "major-only", "all"), + color = "#333333", + halo = "white", + include_regions = TRUE +) +} +\arguments{ +\item{style}{Character. Label style preset: +\describe{ +\item{"hierarchical"}{Size varies by city importance (min_zoom)} +\item{"major-only"}{Only major cities (min_zoom <= 5)} +\item{"all"}{All cities with uniform styling} +}} + +\item{color}{Character. Text color. Default is "#333333".} + +\item{halo}{Character. Halo/stroke color. Default is "white".} + +\item{include_regions}{Logical. Include state/region labels. Default is TRUE.} +} +\value{ +A list of label rules to pass to \code{\link{addProtomaps}}. +} +\description{ +Creates label rules for displaying city/place names with common styling +patterns. +} +\examples{ +\dontrun{ +library(leaflet) +library(protomapr) + +leaflet() \%>\% + setView(lng = -122.4, lat = 37.8, zoom = 8) \%>\% + addProtomaps( + url = protomaps_url(), + colors = pmColors(earth = "#f0f0f0", water = "#1a3a5c"), + labelRules = pmCityLabels("hierarchical") + ) +} + +} diff --git a/man/pmColors.Rd b/man/pmColors.Rd new file mode 100644 index 0000000..09d39d8 --- /dev/null +++ b/man/pmColors.Rd @@ -0,0 +1,96 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/colors.R +\name{pmColors} +\alias{pmColors} +\title{Create custom color overrides} +\usage{ +pmColors( + background = NULL, + earth = NULL, + water = NULL, + park = NULL, + wood = NULL, + hospital = NULL, + industrial = NULL, + school = NULL, + beach = NULL, + glacier = NULL, + highway = NULL, + major = NULL, + minor = NULL, + city_label = NULL, + state_label = NULL, + country_label = NULL, + ocean_label = NULL, + ... +) +} +\arguments{ +\item{background}{Background color} + +\item{earth}{Land/earth color} + +\item{water}{Water color} + +\item{park}{Park/green space color (also called park_a or park_b)} + +\item{wood}{Forest/woodland color (also called wood_a or wood_b)} + +\item{hospital}{Hospital area color} + +\item{industrial}{Industrial area color} + +\item{school}{School/university area color} + +\item{beach}{Beach color} + +\item{glacier}{Glacier color} + +\item{highway}{Highway road color} + +\item{major}{Major road color} + +\item{minor}{Minor road color} + +\item{city_label}{City label color} + +\item{state_label}{State/region label color} + +\item{country_label}{Country label color} + +\item{ocean_label}{Ocean label color} + +\item{...}{Additional color properties} +} +\value{ +A list of color overrides to pass to \code{\link{addProtomaps}}. +} +\description{ +Creates a list of color overrides that can be applied to a built-in flavor. +This is the recommended way to customize map colors while keeping the +proper rendering rules (zoom handling, polygon simplification, etc.). +} +\examples{ +# Simple earth and water colors +pmColors(earth = "#d3d3d3", water = "#1a3a5c") + +# Dark theme with custom colors +pmColors( + background = "#1a1a2e", + earth = "#1a1a2e", + water = "#16213e", + park = "#1f4037", + highway = "#4a4a6a" +) + +# Minimal grayscale +pmColors( + background = "#ffffff", + earth = "#f5f5f5", + water = "#e0e0e0" +) + +} +\seealso{ +\code{\link{addProtomaps}}, \code{\link{protomaps_colors}} +} diff --git a/man/pmHideFeatures.Rd b/man/pmHideFeatures.Rd new file mode 100644 index 0000000..a41650f --- /dev/null +++ b/man/pmHideFeatures.Rd @@ -0,0 +1,39 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/styles.R +\name{pmHideFeatures} +\alias{pmHideFeatures} +\title{Create color overrides to hide specific features} +\usage{ +pmHideFeatures(features, background = "#f8f8f8") +} +\arguments{ +\item{features}{Character vector. Features to hide. Options include: +"roads", "buildings", "landuse", "boundaries", "labels".} + +\item{background}{Character. Background color that hidden features will +match. Default is "#f8f8f8".} +} +\value{ +A list of color overrides to pass to \code{\link{pmColors}} or +merge with other colors. +} +\description{ +Creates color settings that hide specified feature categories by making +them match the background color. +} +\examples{ +\dontrun{ +library(leaflet) +library(protomapr) + +# Hide roads and buildings but keep parks visible +hidden <- pmHideFeatures(c("roads", "buildings")) +leaflet() \%>\% + setView(lng = -122.4, lat = 37.8, zoom = 12) \%>\% + addProtomaps( + url = protomaps_url(), + colors = modifyList(pmColors(water = "#1a3a5c"), hidden) + ) +} + +} diff --git a/man/pmLabelRule.Rd b/man/pmLabelRule.Rd new file mode 100644 index 0000000..436585c --- /dev/null +++ b/man/pmLabelRule.Rd @@ -0,0 +1,54 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/rules.R +\name{pmLabelRule} +\alias{pmLabelRule} +\title{Create a Label Rule} +\usage{ +pmLabelRule( + dataLayer, + symbolizer, + minzoom = NULL, + maxzoom = NULL, + filter = NULL +) +} +\arguments{ +\item{dataLayer}{Character. The name of the data layer in the vector +tile source (e.g., "places", "roads").} + +\item{symbolizer}{A text symbolizer object created with one of +\code{\link{pmTextSymbolizer}}, \code{\link{pmCenteredTextSymbolizer}}, +\code{\link{pmLineLabelSymbolizer}}, or \code{\link{pmShieldSymbolizer}}.} + +\item{minzoom}{Numeric. Minimum zoom level at which this rule applies. +Default is NULL (applies at all zoom levels).} + +\item{maxzoom}{Numeric. Maximum zoom level at which this rule applies. +Default is NULL (applies at all zoom levels).} + +\item{filter}{Character. A JavaScript expression string that filters +features. Default is NULL (no filter).} +} +\value{ +A list representing the label rule configuration. +} +\description{ +Creates a label rule that specifies how to render text labels for +features from a particular data layer. Label rules control text +placement and styling, with automatic collision detection. +} +\examples{ +# Label cities +pmLabelRule("places", + pmCenteredTextSymbolizer(font = "14px Arial", + fill = "black", + stroke = "white", + width = 2)) + +# Label streets along their paths +pmLabelRule("roads", + pmLineLabelSymbolizer(font = "11px Arial", + fill = "#333"), + minzoom = 14) + +} diff --git a/man/pmLineLabelSymbolizer.Rd b/man/pmLineLabelSymbolizer.Rd new file mode 100644 index 0000000..f34d7c5 --- /dev/null +++ b/man/pmLineLabelSymbolizer.Rd @@ -0,0 +1,40 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/symbolizers.R +\name{pmLineLabelSymbolizer} +\alias{pmLineLabelSymbolizer} +\title{Create a Line Label Symbolizer} +\usage{ +pmLineLabelSymbolizer( + font = "12px sans-serif", + fill = "#000000", + stroke = NULL, + width = 0, + labelProps = NULL, + ... +) +} +\arguments{ +\item{font}{Character. Font specification (e.g., "12px sans-serif").} + +\item{fill}{Character. Text fill color. Default is "#000000".} + +\item{stroke}{Character. Text stroke (halo) color. Default is NULL.} + +\item{width}{Numeric. Stroke width for text halo. Default is 0.} + +\item{labelProps}{List. Properties to use for label text.} + +\item{...}{Additional symbolizer options.} +} +\value{ +A list representing the symbolizer configuration. +} +\description{ +Creates a line label symbolizer for rendering text labels along line +features (e.g., street names). +} +\examples{ +pmLineLabelSymbolizer(font = "11px Arial", fill = "#333", + stroke = "white", width = 2) + +} diff --git a/man/pmLineSymbolizer.Rd b/man/pmLineSymbolizer.Rd new file mode 100644 index 0000000..59dc50b --- /dev/null +++ b/man/pmLineSymbolizer.Rd @@ -0,0 +1,54 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/symbolizers.R +\name{pmLineSymbolizer} +\alias{pmLineSymbolizer} +\title{Create a Line Symbolizer} +\usage{ +pmLineSymbolizer( + color = "#000000", + width = 1, + dash = NULL, + dashColor = NULL, + dashWidth = NULL, + lineCap = NULL, + lineJoin = NULL, + opacity = 1, + ... +) +} +\arguments{ +\item{color}{Character. Line color. Default is "#000000".} + +\item{width}{Numeric or function. Line width in pixels. Can be a fixed +value or a zoom-dependent specification.} + +\item{dash}{List or NULL. Dash pattern as a vector of numbers, e.g., +\code{c(4, 2)} for 4px dash, 2px gap.} + +\item{dashColor}{Character. Color for dashes if using dash pattern.} + +\item{dashWidth}{Numeric. Width of dashes.} + +\item{lineCap}{Character. Line cap style: "butt", "round", or "square".} + +\item{lineJoin}{Character +. Line join style: "miter", "round", or "bevel".} + +\item{opacity}{Numeric. Line opacity from 0 to 1. Default is 1.} + +\item{...}{Additional symbolizer options.} +} +\value{ +A list representing the symbolizer configuration. +} +\description{ +Creates a line symbolizer for rendering line features. +} +\examples{ +# Simple black line +pmLineSymbolizer(color = "black", width = 2) + +# Dashed line +pmLineSymbolizer(color = "gray", width = 1, dash = c(4, 2)) + +} diff --git a/man/pmMinimal.Rd b/man/pmMinimal.Rd new file mode 100644 index 0000000..ea64cac --- /dev/null +++ b/man/pmMinimal.Rd @@ -0,0 +1,52 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/styles.R +\name{pmMinimal} +\alias{pmMinimal} +\title{Create a minimal basemap style} +\usage{ +pmMinimal( + land = "#f8f8f8", + water = "#e0e8f0", + labels = FALSE, + label_color = "#666666" +) +} +\arguments{ +\item{land}{Character. Color for all land features. Default is "#f8f8f8".} + +\item{water}{Character. Color for water features. Default is "#e0e8f0".} + +\item{labels}{Logical. Whether to show city labels. Default is FALSE.} + +\item{label_color}{Character. Color for labels if shown. Default is "#666666".} +} +\value{ +A list with \code{colors} and \code{labelRules} components to pass to +\code{\link{addProtomaps}}. +} +\description{ +Creates a minimal style with uniform land color, hiding roads, buildings, +and most labels. Ideal for data visualization overlays. +} +\examples{ +\dontrun{ +library(leaflet) +library(protomapr) + +# Ultra-minimal basemap +style <- pmMinimal() +leaflet() \%>\% + setView(lng = -122.4, lat = 37.8, zoom = 10) \%>\% + addProtomaps(url = protomaps_url(), style = style) + +# Custom colors with major city labels +style <- pmMinimal(land = "#f5f5f0", water = "#1a3a5c", labels = TRUE) +leaflet() \%>\% + setView(lng = -122.4, lat = 37.8, zoom = 8) \%>\% + addProtomaps(url = protomaps_url(), style = style) +} + +} +\seealso{ +\code{\link{pmStyle}}, \code{\link{addProtomaps}} +} diff --git a/man/pmModifyStyle.Rd b/man/pmModifyStyle.Rd new file mode 100644 index 0000000..fb1f227 --- /dev/null +++ b/man/pmModifyStyle.Rd @@ -0,0 +1,62 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/styles.R +\name{pmModifyStyle} +\alias{pmModifyStyle} +\title{Modify an Existing Style} +\usage{ +pmModifyStyle( + style, + colors = NULL, + labelRules = NULL, + replace_labels = FALSE, + ... +) +} +\arguments{ +\item{style}{A pm_style object to modify.} + +\item{colors}{Named list of color overrides (from pmColors() or manual list).} + +\item{labelRules}{Optional list of label rules to replace or add.} + +\item{replace_labels}{Logical. If TRUE, replaces all label rules. If FALSE, +appends new rules. Default is FALSE.} + +\item{...}{Additional color overrides as named arguments.} +} +\value{ +A new pm_style object with modifications applied. +} +\description{ +Creates a new pm_style by modifying an existing one. Useful for tweaking +preset styles without rebuilding from scratch. +} +\examples{ +\dontrun{ +# Start with watercolor, change water color +my_style <- pmModifyStyle(pmStyle("watercolor"), water = "#1a3a5c") + +# Add label rules to minimal style +my_style <- pmModifyStyle( + pmMinimal(), + labelRules = pmCityLabels("major-only") +) + +# Multiple modifications +my_style <- pmModifyStyle( + pmStyle("muted"), + colors = pmColors(water = "#2a4a6c", park = "#c0d8c0"), + replace_labels = TRUE, + labelRules = list( + pmLabelRule("places", pmCenteredTextSymbolizer( + font = "bold 14px Arial", + fill = "#333" + )) + ) +) +} + +} +\seealso{ +\code{\link{pmStyle}}, \code{\link{pmMinimal}} +} diff --git a/man/pmPaintRule.Rd b/man/pmPaintRule.Rd new file mode 100644 index 0000000..e103aba --- /dev/null +++ b/man/pmPaintRule.Rd @@ -0,0 +1,54 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/rules.R +\name{pmPaintRule} +\alias{pmPaintRule} +\title{Create a Paint Rule} +\usage{ +pmPaintRule( + dataLayer, + symbolizer, + minzoom = NULL, + maxzoom = NULL, + filter = NULL +) +} +\arguments{ +\item{dataLayer}{Character. The name of the data layer in the vector +tile source (e.g., "water", "earth", "roads").} + +\item{symbolizer}{A symbolizer object created with one of the symbolizer +functions (e.g., \code{\link{pmPolygonSymbolizer}}, +\code{\link{pmLineSymbolizer}}).} + +\item{minzoom}{Numeric. Minimum zoom level at which this rule applies. +Default is NULL (applies at all zoom levels).} + +\item{maxzoom}{Numeric. Maximum zoom level at which this rule applies. +Default is NULL (applies at all zoom levels).} + +\item{filter}{Character. A JavaScript expression string that filters +features. The expression has access to \code{zoom} and \code{feature} +variables. Default is NULL (no filter).} +} +\value{ +A list representing the paint rule configuration. +} +\description{ +Creates a paint rule that specifies how to render features from a +particular data layer. Paint rules control the visual appearance of +polygon, line, and point features. +} +\examples{ +# Render water polygons in blue +pmPaintRule("water", pmPolygonSymbolizer(fill = "steelblue")) + +# Render roads with zoom-dependent visibility +pmPaintRule("roads", pmLineSymbolizer(color = "gray", width = 2), + minzoom = 10) + +# Filter to only show highways +pmPaintRule("roads", + pmLineSymbolizer(color = "orange", width = 4), + filter = "feature.props.kind === 'highway'") + +} diff --git a/man/pmPalette.Rd b/man/pmPalette.Rd new file mode 100644 index 0000000..7400f01 --- /dev/null +++ b/man/pmPalette.Rd @@ -0,0 +1,61 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/palette.R +\name{pmPalette} +\alias{pmPalette} +\title{Apply Color Palette to Land Use Categories} +\usage{ +pmPalette(palette, categories = NULL, n = NULL, background = "#f8f8f8") +} +\arguments{ +\item{palette}{Character vector of colors, or a function that generates colors. +Can be output from viridis::viridis(), RColorBrewer::brewer.pal(), etc.} + +\item{categories}{Character vector of category names to map colors to. +Default maps to common land use types: water, park, wood, residential, +commercial, industrial.} + +\item{n}{Integer. Number of colors to generate if palette is a function. +Default is NULL (uses length of categories).} + +\item{background}{Character. Background/default color for unassigned categories. +Default is "#f8f8f8".} +} +\value{ +A list of color mappings suitable for pmColors() or addProtomaps(colors=). +} +\description{ +Maps colors from a palette to land use categories, enabling use of +viridis, RColorBrewer, and other R color palettes with Protomaps. +} +\examples{ +\dontrun{ +library(leaflet) +library(protomapr) + +# Using viridis palette for land use +if (requireNamespace("viridisLite", quietly = TRUE)) { + colors <- pmPalette(viridisLite::viridis(6)) + leaflet() \%>\% + setView(lng = -122.4, lat = 37.8, zoom = 12) \%>\% + addProtomaps(url = protomaps_url(), colors = colors) +} + +# Using RColorBrewer +if (requireNamespace("RColorBrewer", quietly = TRUE)) { + colors <- pmPalette(RColorBrewer::brewer.pal(6, "Set2")) + leaflet() \%>\% + setView(lng = -122.4, lat = 37.8, zoom = 12) \%>\% + addProtomaps(url = protomaps_url(), colors = colors) +} + +# Custom category mapping +colors <- pmPalette( + c("#264653", "#2a9d8f", "#e9c46a", "#f4a261", "#e76f51"), + categories = c("water", "park", "sand", "buildings", "highway") +) +} + +} +\seealso{ +\code{\link{pmPaletteStyle}}, \code{\link{pmColors}} +} diff --git a/man/pmPaletteStyle.Rd b/man/pmPaletteStyle.Rd new file mode 100644 index 0000000..c9b8a1e --- /dev/null +++ b/man/pmPaletteStyle.Rd @@ -0,0 +1,57 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/palette.R +\name{pmPaletteStyle} +\alias{pmPaletteStyle} +\title{Create a Themed Palette Style} +\usage{ +pmPaletteStyle( + palette, + water_color = NULL, + land_color = "#f8f8f8", + labels = TRUE, + label_color = "#333333" +) +} +\arguments{ +\item{palette}{Character vector of colors or palette function.} + +\item{water_color}{Character. Color for water features. If NULL, uses +first color from palette.} + +\item{land_color}{Character. Color for land/background. Default is "#f8f8f8".} + +\item{labels}{Logical. Whether to include city labels. Default is TRUE.} + +\item{label_color}{Character. Color for labels. Default is "#333333".} +} +\value{ +A pm_style object. +} +\description{ +Creates a complete pm_style using a color palette. Combines pmPalette() +with styling for a consistent look. +} +\examples{ +\dontrun{ +library(leaflet) +library(protomapr) + +# Viridis-themed map +if (requireNamespace("viridisLite", quietly = TRUE)) { + style <- pmPaletteStyle(viridisLite::viridis(5, option = "D")) + leaflet() \%>\% + setView(lng = -122.4, lat = 37.8, zoom = 12) \%>\% + addProtomaps(url = protomaps_url(), style = style) +} + +# Custom palette +style <- pmPaletteStyle( + c("#264653", "#2a9d8f", "#e9c46a", "#f4a261", "#e76f51"), + water_color = "#264653" +) +} + +} +\seealso{ +\code{\link{pmPalette}}, \code{\link{pmStyle}} +} diff --git a/man/pmPolygonSymbolizer.Rd b/man/pmPolygonSymbolizer.Rd new file mode 100644 index 0000000..76fc83c --- /dev/null +++ b/man/pmPolygonSymbolizer.Rd @@ -0,0 +1,43 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/symbolizers.R +\name{pmPolygonSymbolizer} +\alias{pmPolygonSymbolizer} +\title{Create a Polygon Symbolizer} +\usage{ +pmPolygonSymbolizer( + fill = "#cccccc", + stroke = NULL, + width = 1, + opacity = 1, + pattern = NULL, + ... +) +} +\arguments{ +\item{fill}{Character. Fill color for the polygon. Can be a CSS color +string or a function specification.} + +\item{stroke}{Character. Stroke (outline) color. Default is NULL (no stroke).} + +\item{width}{Numeric. Stroke width in pixels. Default is 1.} + +\item{opacity}{Numeric. Fill opacity from 0 to 1. Default is 1.} + +\item{pattern}{Character. Fill pattern. One of NULL, "hatch", or "dot".} + +\item{...}{Additional symbolizer options.} +} +\value{ +A list representing the symbolizer configuration. +} +\description{ +Creates a polygon symbolizer for rendering filled polygon features. +} +\examples{ +# Simple blue fill +pmPolygonSymbolizer(fill = "steelblue") + +# With stroke +pmPolygonSymbolizer(fill = "#f0f0f0", stroke = "#333", width = 2) + +} diff --git a/man/pmShieldSymbolizer.Rd b/man/pmShieldSymbolizer.Rd new file mode 100644 index 0000000..69bd33f --- /dev/null +++ b/man/pmShieldSymbolizer.Rd @@ -0,0 +1,43 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/symbolizers.R +\name{pmShieldSymbolizer} +\alias{pmShieldSymbolizer} +\title{Create a Shield Symbolizer} +\usage{ +pmShieldSymbolizer( + font = "10px sans-serif", + fill = "#000000", + background = "#ffffff", + stroke = "#000000", + padding = 2, + labelProps = NULL, + ... +) +} +\arguments{ +\item{font}{Character. Font specification for shield text.} + +\item{fill}{Character. Text fill color. Default is "#000000".} + +\item{background}{Character. Shield background color. Default is "#ffffff".} + +\item{stroke}{Character. Shield border color. Default is "#000000".} + +\item{padding}{Numeric. Padding inside the shield in pixels. Default is 2.} + +\item{labelProps}{List. Properties to use for shield text.} + +\item{...}{Additional symbolizer options.} +} +\value{ +A list representing the symbolizer configuration. +} +\description{ +Creates a shield symbolizer for rendering labeled badges or shields +(e.g., highway route markers). +} +\examples{ +pmShieldSymbolizer(font = "10px Arial", fill = "black", + background = "white", stroke = "black") + +} diff --git a/man/pmStyle.Rd b/man/pmStyle.Rd new file mode 100644 index 0000000..e3b8cc8 --- /dev/null +++ b/man/pmStyle.Rd @@ -0,0 +1,47 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/styles.R +\name{pmStyle} +\alias{pmStyle} +\title{Get a preset map style} +\usage{ +pmStyle( + name = c("minimal", "minimal-dark", "muted", "watercolor", "ink", "terrain", "transit") +) +} +\arguments{ +\item{name}{Character. Name of the preset style. One of: +\describe{ +\item{"minimal"}{Light gray land, light blue water, no labels} +\item{"minimal-dark"}{Dark land, dark blue water, no labels} +\item{"muted"}{Subtle colors, faint roads, major labels only} +\item{"watercolor"}{Soft, painterly aesthetic} +\item{"ink"}{Black lines on white, like a pen drawing} +\item{"terrain"}{Earthy tones with subtle elevation feel} +\item{"transit"}{Muted base with emphasized rail lines} +}} +} +\value{ +A list with style components to pass to \code{\link{addProtomaps}}. +} +\description{ +Returns a preset style configuration. Available presets provide common +styling patterns without manual configuration. +} +\examples{ +\dontrun{ +library(leaflet) +library(protomapr) + +leaflet() \%>\% + setView(lng = -122.4, lat = 37.8, zoom = 10) \%>\% + addProtomaps(url = protomaps_url(), style = pmStyle("minimal")) + +leaflet() \%>\% + setView(lng = -122.4, lat = 37.8, zoom = 10) \%>\% + addProtomaps(url = protomaps_url(), style = pmStyle("watercolor")) +} + +} +\seealso{ +\code{\link{pmMinimal}}, \code{\link{addProtomaps}} +} diff --git a/man/pmTextSymbolizer.Rd b/man/pmTextSymbolizer.Rd new file mode 100644 index 0000000..6bba173 --- /dev/null +++ b/man/pmTextSymbolizer.Rd @@ -0,0 +1,48 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/symbolizers.R +\name{pmTextSymbolizer} +\alias{pmTextSymbolizer} +\title{Create a Text Symbolizer} +\usage{ +pmTextSymbolizer( + font = "12px sans-serif", + fill = "#000000", + stroke = NULL, + width = 0, + labelProps = NULL, + textTransform = NULL, + ... +) +} +\arguments{ +\item{font}{Character. Font specification (e.g., "12px sans-serif").} + +\item{fill}{Character. Text fill color. Default is "#000000".} + +\item{stroke}{Character. Text stroke (halo) color. Default is NULL.} + +\item{width}{Numeric. Stroke width for text halo. Default is 0.} + +\item{labelProps}{List. Properties to use for label text, in order of +preference. Default is \code{list("name")}.} + +\item{textTransform}{Character. Text transformation: "uppercase", +"lowercase", or NULL.} + +\item{...}{Additional symbolizer options.} +} +\value{ +A list representing the symbolizer configuration. +} +\description{ +Creates a text symbolizer for rendering text labels. +} +\examples{ +# Simple text label +pmTextSymbolizer(font = "12px Arial", fill = "black") + +# Text with halo +pmTextSymbolizer(font = "14px sans-serif", fill = "black", + stroke = "white", width = 2) + +} diff --git a/man/print.pm_style.Rd b/man/print.pm_style.Rd new file mode 100644 index 0000000..628bbe7 --- /dev/null +++ b/man/print.pm_style.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/styles.R +\name{print.pm_style} +\alias{print.pm_style} +\title{Print method for pm_style objects} +\usage{ +\method{print}{pm_style}(x, ...) +} +\arguments{ +\item{x}{A pm_style object.} + +\item{...}{Additional arguments (ignored).} +} +\value{ +Invisibly returns x. +} +\description{ +Prints a formatted summary of a pm_style object showing colors and label rules. +} diff --git a/man/protomapr-package.Rd b/man/protomapr-package.Rd new file mode 100644 index 0000000..fb6a4dd --- /dev/null +++ b/man/protomapr-package.Rd @@ -0,0 +1,73 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/protomapr-package.R +\docType{package} +\name{protomapr-package} +\alias{protomapr} +\alias{protomapr-package} +\title{protomapr: Add Protomaps Layers to Leaflet Maps} +\description{ +The protomapr package provides functions to add Protomaps vector tile +layers to leaflet maps in R. Unlike raster tile providers, Protomaps +offers full customization of colors and features, self-hosting from a +single PMTiles file, and smooth vector rendering at any zoom level. +See \code{vignette("getting-started")} for why you might choose Protomaps +over standard provider tiles. +} +\section{Main Functions}{ + +\describe{ +\item{\code{\link{addProtomaps}}}{Add a Protomaps layer to a Leaflet map} +\item{\code{\link{protomapsOptions}}}{Configure additional layer options} +} +} + +\section{Symbolizers}{ + +\describe{ +\item{\code{\link{pmPolygonSymbolizer}}}{Style polygon features} +\item{\code{\link{pmLineSymbolizer}}}{Style line features} +\item{\code{\link{pmCircleSymbolizer}}}{Style point features as circles} +\item{\code{\link{pmTextSymbolizer}}}{Add text labels} +\item{\code{\link{pmCenteredTextSymbolizer}}}{Add centered text labels} +\item{\code{\link{pmLineLabelSymbolizer}}}{Add labels along lines} +\item{\code{\link{pmShieldSymbolizer}}}{Add shield/badge labels} +} +} + +\section{Rules}{ + +\describe{ +\item{\code{\link{pmPaintRule}}}{Define how to paint features} +\item{\code{\link{pmLabelRule}}}{Define how to label features} +} +} + +\section{Available Themes}{ + +The following built-in themes are available: +\itemize{ +\item \code{"light"} - General-purpose light basemap +\item \code{"dark"} - General-purpose dark basemap +\item \code{"white"} - High-contrast white theme for data visualization +\item \code{"grayscale"} - Monochromatic theme +\item \code{"black"} - Dark theme for data visualization +} +} + +\seealso{ +Useful links: +\itemize{ + \item \url{https://github.com/evmo/protomapr} +} + +} +\author{ +\strong{Maintainer}: Evan Morrison \email{evan@p34.au} + +Other contributors: +\itemize{ + \item Brandon Liu (Author of protomaps-leaflet JavaScript library) [copyright holder] +} + +} +\keyword{internal} diff --git a/man/protomapsDependency.Rd b/man/protomapsDependency.Rd new file mode 100644 index 0000000..3b5829d --- /dev/null +++ b/man/protomapsDependency.Rd @@ -0,0 +1,23 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/addProtomaps.R +\name{protomapsDependency} +\alias{protomapsDependency} +\title{Create Protomaps HTML dependency} +\usage{ +protomapsDependency(version = "5.1.0") +} +\arguments{ +\item{version}{Character. Version of protomaps-leaflet to use. +Default is "5.1.0".} +} +\value{ +An htmltools::htmlDependency object. +} +\description{ +Creates the HTML dependency for the protomaps-leaflet JavaScript library. +This is automatically included when using \code{\link{addProtomaps}}. +} +\examples{ +protomapsDependency() + +} diff --git a/man/protomapsOptions.Rd b/man/protomapsOptions.Rd new file mode 100644 index 0000000..337554c --- /dev/null +++ b/man/protomapsOptions.Rd @@ -0,0 +1,28 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/addProtomaps.R +\name{protomapsOptions} +\alias{protomapsOptions} +\title{Protomaps layer options} +\usage{ +protomapsOptions(maxDataZoom = NULL, tileSize = NULL, debug = FALSE, ...) +} +\arguments{ +\item{maxDataZoom}{Numeric. Maximum zoom level to fetch tile data. +Tiles beyond this zoom will be overzoomed.} + +\item{tileSize}{Numeric. Size of tiles in pixels. Default is 256.} + +\item{debug}{Logical. Enable debug mode to visualize tile boundaries.} + +\item{...}{Additional options passed to the layer.} +} +\value{ +A list of options. +} +\description{ +Create additional options for protomaps layer configuration. +} +\examples{ +protomapsOptions(maxDataZoom = 14, tileSize = 512) + +} diff --git a/man/protomaps_clear_cache.Rd b/man/protomaps_clear_cache.Rd new file mode 100644 index 0000000..2c53582 --- /dev/null +++ b/man/protomaps_clear_cache.Rd @@ -0,0 +1,28 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/sample-tiles.R +\name{protomaps_clear_cache} +\alias{protomaps_clear_cache} +\title{Clear Cached Sample Tiles} +\usage{ +protomaps_clear_cache(cache_dir = NULL) +} +\arguments{ +\item{cache_dir}{Character. Cache directory. Default uses same as +\code{\link{protomaps_sample_tiles}}.} +} +\value{ +Invisibly returns TRUE if files were removed, FALSE otherwise. +} +\description{ +Removes cached sample PMTiles files to free disk space. +} +\examples{ +\dontrun{ +# Clear all cached tiles +protomaps_clear_cache() +} + +} +\seealso{ +\code{\link{protomaps_sample_tiles}} +} diff --git a/man/protomaps_colors.Rd b/man/protomaps_colors.Rd new file mode 100644 index 0000000..b240b6a --- /dev/null +++ b/man/protomaps_colors.Rd @@ -0,0 +1,72 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/colors.R +\name{protomaps_colors} +\alias{protomaps_colors} +\title{Protomaps Color Properties Reference} +\description{ +Reference documentation for all available color properties that can be +customized using \code{\link{pmColors}}. +} +\section{Base Colors}{ + +\describe{ +\item{\code{background}}{Map background color} +\item{\code{earth}}{Land/terrain color} +\item{\code{water}}{Water bodies color} +} +} + +\section{Land Use Colors}{ + +\describe{ +\item{\code{park_a}, \code{park_b}}{Park colors (use \code{park} in pmColors)} +\item{\code{wood_a}, \code{wood_b}}{Forest/woodland colors (use \code{wood} in pmColors)} +\item{\code{hospital}}{Hospital areas} +\item{\code{industrial}}{Industrial zones} +\item{\code{school}}{Schools and universities} +\item{\code{beach}}{Beach areas} +\item{\code{zoo}}{Zoo areas} +\item{\code{aerodrome}}{Airport areas} +\item{\code{glacier}}{Glacier areas} +} +} + +\section{Road Colors}{ + +\describe{ +\item{\code{highway}}{Highway/motorway color} +\item{\code{major}}{Major road color} +\item{\code{minor_a}, \code{minor_b}}{Minor road colors (use \code{minor} in pmColors)} +\item{\code{railway}}{Railway lines} +\item{\code{pier}}{Pier/dock structures} +} +} + +\section{Label Colors}{ + +\describe{ +\item{\code{city_label}}{City name labels} +\item{\code{state_label}}{State/region labels} +\item{\code{country_label}}{Country name labels} +\item{\code{ocean_label}}{Ocean/sea labels} +\item{\code{roads_label_major}}{Major road name labels} +\item{\code{roads_label_minor}}{Minor road name labels} +} +} + +\section{Landcover Colors (optional object)}{ + +These are specified as a nested object: +\describe{ +\item{\code{grassland}}{Grassland areas} +\item{\code{barren}}{Barren land} +\item{\code{urban_area}}{Urban zones} +\item{\code{farmland}}{Agricultural areas} +\item{\code{forest}}{Forest areas} +\item{\code{scrub}}{Scrubland} +} +} + +\seealso{ +\code{\link{pmColors}}, \code{\link{addProtomaps}} +} diff --git a/man/protomaps_layers.Rd b/man/protomaps_layers.Rd new file mode 100644 index 0000000..05a5f58 --- /dev/null +++ b/man/protomaps_layers.Rd @@ -0,0 +1,130 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/layers.R +\name{protomaps_layers} +\alias{protomaps_layers} +\alias{layers} +\title{Protomaps Basemap Layers Reference} +\description{ +Reference documentation for the available layers and properties in the +Protomaps basemap. Use these layer names with \code{\link{pmPaintRule}} +and \code{\link{pmLabelRule}}, and filter on these properties. +} +\section{Layer Names}{ + +The following layers are available for styling: + +\describe{ +\item{\code{earth}}{Land polygons} +\item{\code{water}}{Water polygons, lines, and label points} +\item{\code{landuse}}{Parks, forests, residential areas, etc.} +\item{\code{roads}}{Streets, highways, paths} +\item{\code{buildings}}{Building footprints and addresses} +\item{\code{places}}{City, town, and region labels} +\item{\code{pois}}{Points of interest} +\item{\code{boundaries}}{Administrative boundaries} +\item{\code{natural}}{Natural features like peaks, forests} +\item{\code{transit}}{Transit stations and lines} +} +} + +\section{Places Layer Properties}{ + +Use these in filter expressions like \code{filter = "feature.props.kind === 'locality'"} + +\describe{ +\item{\code{kind}}{Place type: "country", "region", "locality", "macrohood", "neighbourhood"} +\item{\code{kind_detail}}{Detailed type: "city", "town", "village", "hamlet", "state", "province", "country"} +\item{\code{name}}{Place name} +\item{\code{population}}{Population count (integer)} +\item{\code{population_rank}}{Population rank (integer, higher = larger)} +\item{\code{min_zoom}}{Minimum zoom level where label appears (lower = more important)} +\item{\code{capital}}{Capital status (string)} +\item{\code{wikidata}}{Wikidata ID} +} +} + +\section{Water Layer Properties}{ + +\describe{ +\item{\code{kind}}{Water type: "water", "lake", "playa", "ocean", "other"} +\item{\code{kind_detail}}{Detailed type: "basin", "canal", "ditch", "dock", "drain", "lake", "reservoir", "river", "riverbank", "stream"} +\item{\code{name}}{Water body name} +\item{\code{intermittent}}{Boolean, seasonal water} +\item{\code{reservoir}}{Boolean} +\item{\code{alkaline}}{Boolean} +} +} + +\section{Roads Layer Properties}{ + +\describe{ +\item{\code{kind}}{Road class: "highway", "major_road", "medium_road", "minor_road", "path"} +\item{\code{kind_detail}}{Detailed type: "motorway", "trunk", "primary", "secondary", "tertiary", "residential", "service", "pedestrian", "footway", "cycleway"} +\item{\code{ref}}{Road reference number (e.g., "I-80", "US-101")} +\item{\code{name}}{Street name} +\item{\code{oneway}}{Boolean} +\item{\code{is_bridge}}{Boolean} +\item{\code{is_tunnel}}{Boolean} +} +} + +\section{Landuse Layer Properties}{ + +\describe{ +\item{\code{kind}}{Land use type: "park", "forest", "residential", "commercial", "industrial", "aerodrome", "cemetery", "hospital", "school", "stadium", "zoo"} +\item{\code{sport}}{Sport type for sports facilities} +} +} + +\section{Buildings Layer Properties}{ + +\describe{ +\item{\code{kind}}{Building type: "address", "building", "building_part"} +\item{\code{height}}{Building height in meters} +\item{\code{min_height}}{Base height for building parts} +\item{\code{addr_housenumber}}{Street address number} +} +} + +\section{POIs Layer Properties}{ + +\describe{ +\item{\code{kind}}{POI type: "cafe", "restaurant", "hospital", "school", "bank", "pharmacy", "hotel", etc.} +\item{\code{name}}{POI name} +\item{\code{cuisine}}{Cuisine type for restaurants} +\item{\code{religion}}{Religion for places of worship} +} +} + +\section{Filter Examples}{ + +\preformatted{ +# Major cities only +filter = "feature.props.kind_detail === 'city'" + +# States/provinces +filter = "feature.props.kind === 'region'" + +# Important places (low min_zoom = important) +filter = "feature.props.min_zoom <= 6" + +# Large cities by population rank +filter = "feature.props.population_rank >= 10" + +# Highways only +filter = "feature.props.kind === 'highway'" + +# Parks +filter = "feature.props.kind === 'park'" + +# Combine conditions +filter = "feature.props.kind_detail === 'city' && feature.props.min_zoom <= 8" +} +} + +\references{ +\url{https://docs.protomaps.com/basemaps/layers} +} +\seealso{ +\code{\link{pmPaintRule}}, \code{\link{pmLabelRule}} +} diff --git a/man/protomaps_sample_tiles.Rd b/man/protomaps_sample_tiles.Rd new file mode 100644 index 0000000..5f6280d --- /dev/null +++ b/man/protomaps_sample_tiles.Rd @@ -0,0 +1,54 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/sample-tiles.R +\name{protomaps_sample_tiles} +\alias{protomaps_sample_tiles} +\title{Get Path to Sample PMTiles File} +\usage{ +protomaps_sample_tiles( + region = "sf-bay", + cache_dir = NULL, + force_download = FALSE +) +} +\arguments{ +\item{region}{Character. Region to download. Currently only "sf-bay" +(San Francisco Bay Area) is available. Default is "sf-bay".} + +\item{cache_dir}{Character. Directory to cache the downloaded file. +Default uses \code{tools::R_user_dir()}.} + +\item{force_download}{Logical. Force re-download even if cached. +Default is FALSE.} +} +\value{ +Character. Path to the PMTiles file. +} +\description{ +Returns the path to a sample PMTiles file for demos and testing. +On first use, downloads a small regional extract to the user's cache +directory. +} +\details{ +The sample tiles are hosted on GitHub releases and downloaded on first use. +Subsequent calls use the cached file. The SF Bay Area extract is +approximately 10-15MB and covers the greater San Francisco region at all +zoom levels. + +For production use, consider self-hosting your own PMTiles file. See +\code{vignette("getting-started")} for options. +} +\examples{ +\dontrun{ +library(leaflet) +library(protomapr) + +# Use sample tiles for demos (downloads on first use) +leaflet() \%>\% + setView(lng = -122.4, lat = 37.8, zoom = 12) \%>\% + addProtomaps(url = protomaps_sample_tiles()) +} + +} +\seealso{ +\code{\link{protomaps_clear_cache}}, \code{\link{protomaps_url}} +} diff --git a/man/protomaps_url.Rd b/man/protomaps_url.Rd new file mode 100644 index 0000000..75db925 --- /dev/null +++ b/man/protomaps_url.Rd @@ -0,0 +1,42 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/addProtomaps.R +\name{protomaps_url} +\alias{protomaps_url} +\alias{protomaps_demo_url} +\title{Get Protomaps API tile URL} +\usage{ +protomaps_url(api_key = NULL) + +protomaps_demo_url(api_key = NULL) +} +\arguments{ +\item{api_key}{Character. Your Protomaps API key. If NULL (default), +uses the \code{PROTOMAPS_API_KEY} environment variable.} +} +\value{ +Character. URL template for the tile API. +} +\description{ +Returns a URL template for the Protomaps tile API. Requires an API key, +which can be passed directly or set via the \code{PROTOMAPS_API_KEY} +environment variable. + +Get a free API key (for non-commercial use) at \url{https://protomaps.com/}. +For commercial use or high traffic, consider self-hosting PMTiles files. +} +\examples{ +\dontrun{ +# Set your API key as an environment variable (recommended) +Sys.setenv(PROTOMAPS_API_KEY = "your-api-key-here") +leaflet() \%>\% + addProtomaps(url = protomaps_url()) + +# Or pass the key directly +leaflet() \%>\% + addProtomaps(url = protomaps_url(api_key = "your-api-key-here")) +} + +} +\seealso{ +\code{\link{set_protomaps_key}} for a convenient way to set the API key. +} diff --git a/man/set_protomaps_key.Rd b/man/set_protomaps_key.Rd new file mode 100644 index 0000000..2e67fbc --- /dev/null +++ b/man/set_protomaps_key.Rd @@ -0,0 +1,33 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/addProtomaps.R +\name{set_protomaps_key} +\alias{set_protomaps_key} +\title{Set Protomaps API key} +\usage{ +set_protomaps_key(api_key) +} +\arguments{ +\item{api_key}{Character. Your Protomaps API key.} +} +\value{ +Invisibly returns the API key. +} +\description{ +Convenience function to set your Protomaps API key for the current session. +The key is stored in the \code{PROTOMAPS_API_KEY} environment variable. + +For persistent storage, add to your \code{.Renviron} file: +\code{PROTOMAPS_API_KEY=your-key-here} + +Get a free API key at \url{https://protomaps.com/}. +} +\examples{ +\dontrun{ +set_protomaps_key("your-api-key-here") + +# Now protomaps_url() will work without arguments +leaflet() \%>\% + addProtomaps(url = protomaps_url()) +} + +} diff --git a/protomapr.Rproj b/protomapr.Rproj new file mode 100644 index 0000000..270314b --- /dev/null +++ b/protomapr.Rproj @@ -0,0 +1,21 @@ +Version: 1.0 + +RestoreWorkspace: Default +SaveWorkspace: Default +AlwaysSaveHistory: Default + +EnableCodeIndexing: Yes +UseSpacesForTab: Yes +NumSpacesForTab: 2 +Encoding: UTF-8 + +RnwWeave: Sweave +LaTeX: pdfLaTeX + +AutoAppendNewline: Yes +StripTrailingWhitespace: Yes + +BuildType: Package +PackageUseDevtools: Yes +PackageInstallArgs: --no-multiarch --with-keep.source +PackageRoxygenize: rd,collate,namespace diff --git a/tests/testthat.R b/tests/testthat.R new file mode 100644 index 0000000..0751223 --- /dev/null +++ b/tests/testthat.R @@ -0,0 +1,4 @@ +library(testthat) +library(protomapr) + +test_check("protomapr") diff --git a/tests/testthat/test-addProtomaps.R b/tests/testthat/test-addProtomaps.R new file mode 100644 index 0000000..ef47ffc --- /dev/null +++ b/tests/testthat/test-addProtomaps.R @@ -0,0 +1,74 @@ +test_that("protomaps_url requires API key", { + # Clear any existing env var + + old_key <- Sys.getenv("PROTOMAPS_API_KEY") + Sys.unsetenv("PROTOMAPS_API_KEY") + on.exit(Sys.setenv(PROTOMAPS_API_KEY = old_key)) + + expect_error(protomaps_url(), "API key required") +}) + +test_that("protomaps_url accepts direct API key", { + url <- protomaps_url(api_key = "test-key") + expect_true(grepl("api.protomaps.com", url)) + expect_true(grepl("\\{z\\}/\\{x\\}/\\{y\\}", url)) + expect_true(grepl("key=test-key", url)) +}) + +test_that("protomaps_url uses environment variable", { + old_key <- Sys.getenv("PROTOMAPS_API_KEY") + Sys.setenv(PROTOMAPS_API_KEY = "env-test-key") + on.exit(Sys.setenv(PROTOMAPS_API_KEY = old_key)) + + url <- protomaps_url() + expect_true(grepl("key=env-test-key", url)) +}) + +test_that("set_protomaps_key sets environment variable", { + old_key <- Sys.getenv("PROTOMAPS_API_KEY") + on.exit(Sys.setenv(PROTOMAPS_API_KEY = old_key)) + + expect_message(set_protomaps_key("my-key"), "API key set") + expect_equal(Sys.getenv("PROTOMAPS_API_KEY"), "my-key") +}) + +test_that("protomaps_demo_url is deprecated", { + expect_warning(protomaps_demo_url(api_key = "test"), "deprecated") +}) + +test_that("protomapsOptions creates correct structure", { + opts <- protomapsOptions(maxDataZoom = 14, tileSize = 512) + expect_equal(opts$maxDataZoom, 14) + expect_equal(opts$tileSize, 512) +}) + +test_that("protomapsOptions handles debug mode", { + opts <- protomapsOptions(debug = TRUE) + expect_true(opts$debug) +}) + +test_that("protomapsDependency returns htmlDependency", { + dep <- protomapsDependency() + expect_s3_class(dep, "html_dependency") + expect_equal(dep$name, "protomaps-leaflet") +}) + +test_that("addProtomaps returns modified leaflet map", { + skip_if_not_installed("leaflet") + + map <- leaflet::leaflet() + # Use mock URL to avoid API key requirement + result <- addProtomaps(map, url = "https://example.com/tiles.pmtiles") + + expect_s3_class(result, "leaflet") +}) + +test_that("addProtomaps validates flavor argument", { + skip_if_not_installed("leaflet") + + map <- leaflet::leaflet() + expect_error( + addProtomaps(map, url = "https://example.com/tiles.pmtiles", flavor = "invalid"), + "should be one of" + ) +}) diff --git a/tests/testthat/test-colors.R b/tests/testthat/test-colors.R new file mode 100644 index 0000000..9bad049 --- /dev/null +++ b/tests/testthat/test-colors.R @@ -0,0 +1,34 @@ +test_that("pmColors creates correct structure", { + colors <- pmColors(earth = "#d3d3d3", water = "#1a3a5c") + + expect_equal(colors$earth, "#d3d3d3") + expect_equal(colors$water, "#1a3a5c") +}) + +test_that("pmColors expands park to park_a and park_b", { + colors <- pmColors(park = "#00ff00") + + expect_equal(colors$park_a, "#00ff00") + expect_equal(colors$park_b, "#00ff00") +}) + +test_that("pmColors expands wood to wood_a and wood_b", { + colors <- pmColors(wood = "#228b22") + + expect_equal(colors$wood_a, "#228b22") + expect_equal(colors$wood_b, "#228b22") +}) + +test_that("pmColors expands minor to minor_a and minor_b", { + colors <- pmColors(minor = "#ffffff") + + expect_equal(colors$minor_a, "#ffffff") + expect_equal(colors$minor_b, "#ffffff") +}) + +test_that("pmColors passes through additional properties", { + colors <- pmColors(earth = "#ccc", custom_prop = "#abc") + + expect_equal(colors$earth, "#ccc") + expect_equal(colors$custom_prop, "#abc") +}) diff --git a/tests/testthat/test-palette.R b/tests/testthat/test-palette.R new file mode 100644 index 0000000..55d03dd --- /dev/null +++ b/tests/testthat/test-palette.R @@ -0,0 +1,71 @@ +test_that("pmPalette creates color mapping", { + colors <- pmPalette(c("#ff0000", "#00ff00", "#0000ff")) + + expect_true("water" %in% names(colors)) + expect_true("background" %in% names(colors)) +}) + +test_that("pmPalette expands paired colors", { + colors <- pmPalette(c("#ff0000", "#00ff00"), categories = c("water", "park")) + + expect_equal(colors$park_a, "#00ff00") + expect_equal(colors$park_b, "#00ff00") +}) + +test_that("pmPalette handles custom categories", { + colors <- pmPalette( + c("#111", "#222", "#333"), + categories = c("water", "buildings", "highway") + ) + + expect_equal(colors$water, "#111") + expect_equal(colors$buildings, "#222") + expect_equal(colors$highway, "#333") +}) + +test_that("pmPalette recycles colors if needed", { + colors <- pmPalette(c("#aaa", "#bbb"), categories = c("a", "b", "c", "d")) + + expect_equal(length(colors), 6) # 4 categories + background + earth +}) + +test_that("pmPalette handles palette functions", { + skip_if_not_installed("viridisLite") + + colors <- pmPalette(viridisLite::viridis, n = 5) + expect_true("water" %in% names(colors)) +}) + +test_that("pmPaletteStyle creates pm_style", { + style <- pmPaletteStyle(c("#1a1a2e", "#16213e", "#0f3460", "#e94560", "#533483")) + + expect_s3_class(style, "pm_style") + expect_true(length(style$labelRules) > 0) +}) + +test_that("pmPaletteStyle respects labels parameter", { + style_with <- pmPaletteStyle(c("#000", "#fff"), labels = TRUE) + style_without <- pmPaletteStyle(c("#000", "#fff"), labels = FALSE) + + expect_true(length(style_with$labelRules) > 0) + expect_equal(length(style_without$labelRules), 0) +}) + +test_that("pmPaletteStyle uses water_color parameter", { + style <- pmPaletteStyle( + c("#aaa", "#bbb", "#ccc"), + water_color = "#123456" + ) + + expect_equal(style$colors$water, "#123456") +}) + +test_that("pmPaletteStyle uses land_color parameter", { + style <- pmPaletteStyle( + c("#aaa", "#bbb", "#ccc"), + land_color = "#fedcba" + ) + + expect_equal(style$colors$background, "#fedcba") + expect_equal(style$colors$earth, "#fedcba") +}) diff --git a/tests/testthat/test-rules.R b/tests/testthat/test-rules.R new file mode 100644 index 0000000..d2ff702 --- /dev/null +++ b/tests/testthat/test-rules.R @@ -0,0 +1,37 @@ +test_that("pmPaintRule creates correct structure", { + sym <- pmPolygonSymbolizer(fill = "blue") + rule <- pmPaintRule("water", sym) + + expect_equal(rule$dataLayer, "water") + expect_equal(rule$symbolizer$type, "polygon") +}) + +test_that("pmPaintRule handles zoom constraints", { + sym <- pmPolygonSymbolizer(fill = "gray") + rule <- pmPaintRule("buildings", sym, minzoom = 14, maxzoom = 18) + + expect_equal(rule$minzoom, 14) + expect_equal(rule$maxzoom, 18) +}) + +test_that("pmPaintRule handles filters", { + sym <- pmLineSymbolizer(color = "orange") + rule <- pmPaintRule("roads", sym, filter = "feature.props.kind === 'highway'") + + expect_equal(rule$filter, "feature.props.kind === 'highway'") +}) + +test_that("pmLabelRule creates correct structure", { + sym <- pmCenteredTextSymbolizer(font = "14px Arial", fill = "black") + rule <- pmLabelRule("places", sym) + + expect_equal(rule$dataLayer, "places") + expect_equal(rule$symbolizer$type, "centeredText") +}) + +test_that("pmLabelRule handles filters", { + sym <- pmCenteredTextSymbolizer(font = "14px Arial", fill = "black") + rule <- pmLabelRule("places", sym, filter = "feature.props.kind === 'locality'") + + expect_equal(rule$filter, "feature.props.kind === 'locality'") +}) diff --git a/tests/testthat/test-sample-tiles.R b/tests/testthat/test-sample-tiles.R new file mode 100644 index 0000000..b055815 --- /dev/null +++ b/tests/testthat/test-sample-tiles.R @@ -0,0 +1,65 @@ +test_that("protomaps_sample_tiles validates region", { + expect_error( + protomaps_sample_tiles(region = "invalid"), + "sf-bay" + ) +}) + +test_that("protomaps_sample_tiles uses cached file", { + temp_dir <- tempdir() + temp_file <- file.path(temp_dir, "protomapr-sample-sf-bay.pmtiles") + + # Create fake cached file + writeLines("test", temp_file) + + expect_message( + result <- protomaps_sample_tiles(cache_dir = temp_dir), + "Using cached" + ) + expect_equal(result, temp_file) + + # Cleanup + unlink(temp_file) +}) + +test_that("protomaps_clear_cache handles missing directory", { + expect_message( + protomaps_clear_cache(cache_dir = "/nonexistent/path/12345"), + "does not exist" + ) +}) + +test_that("protomaps_clear_cache handles empty cache", { + temp_dir <- tempdir() + cache_subdir <- file.path(temp_dir, "protomapr_test_cache") + dir.create(cache_subdir, showWarnings = FALSE) + + expect_message( + protomaps_clear_cache(cache_dir = cache_subdir), + "No cached tiles" + ) + + unlink(cache_subdir, recursive = TRUE) +}) + +test_that("protomaps_clear_cache removes pmtiles files", { + temp_dir <- tempdir() + cache_subdir <- file.path(temp_dir, "protomapr_test_cache2") + dir.create(cache_subdir, showWarnings = FALSE) + + # Create fake cached files + writeLines("test1", file.path(cache_subdir, "test1.pmtiles")) + writeLines("test2", file.path(cache_subdir, "test2.pmtiles")) + writeLines("keep", file.path(cache_subdir, "other.txt")) + + result <- expect_message( + protomaps_clear_cache(cache_dir = cache_subdir), + "Removed 2" + ) + + # Check pmtiles removed but other file kept + expect_false(file.exists(file.path(cache_subdir, "test1.pmtiles"))) + expect_true(file.exists(file.path(cache_subdir, "other.txt"))) + + unlink(cache_subdir, recursive = TRUE) +}) diff --git a/tests/testthat/test-styles.R b/tests/testthat/test-styles.R new file mode 100644 index 0000000..5d88391 --- /dev/null +++ b/tests/testthat/test-styles.R @@ -0,0 +1,173 @@ +test_that("pmMinimal creates correct structure", { + style <- pmMinimal() + + expect_s3_class(style, "pm_style") + expect_true("colors" %in% names(style)) + expect_true("labelRules" %in% names(style)) + expect_equal(length(style$labelRules), 0) +}) + +test_that("pmMinimal with labels creates label rules", { + style <- pmMinimal(labels = TRUE) + + expect_equal(length(style$labelRules), 1) + expect_equal(style$labelRules[[1]]$dataLayer, "places") +}) + +test_that("pmMinimal uses custom colors", { + style <- pmMinimal(land = "#ffffff", water = "#000000") + + expect_equal(style$colors$earth, "#ffffff") + expect_equal(style$colors$water, "#000000") + expect_equal(style$colors$background, "#ffffff") +}) + +test_that("pmStyle returns valid presets", { + minimal <- pmStyle("minimal") + expect_s3_class(minimal, "pm_style") + + dark <- pmStyle("minimal-dark") + expect_s3_class(dark, "pm_style") + expect_equal(dark$colors$earth, "#1a1a1a") + + muted <- pmStyle("muted") + expect_s3_class(muted, "pm_style") + expect_true(length(muted$labelRules) > 0) + + watercolor <- pmStyle("watercolor") + expect_s3_class(watercolor, "pm_style") +}) + +test_that("pmStyle validates name argument", { + expect_error(pmStyle("invalid"), "should be one of") +}) + +test_that("pmHideFeatures hides roads", { + hidden <- pmHideFeatures("roads", background = "#fff") + + expect_equal(hidden$highway, "#fff") + expect_equal(hidden$major, "#fff") + expect_equal(hidden$minor, "#fff") +}) + +test_that("pmHideFeatures hides buildings", { + hidden <- pmHideFeatures("buildings", background = "#eee") + + expect_equal(hidden$buildings, "#eee") +}) + +test_that("pmHideFeatures hides multiple categories", { + hidden <- pmHideFeatures(c("roads", "buildings", "landuse"), background = "#ccc") + + expect_equal(hidden$highway, "#ccc") + expect_equal(hidden$buildings, "#ccc") + expect_equal(hidden$park_a, "#ccc") +}) + +test_that("pmCityLabels creates hierarchical labels", { + labels <- pmCityLabels("hierarchical") + + expect_true(length(labels) >= 4) # 4 size levels + optional region + expect_equal(labels[[1]]$dataLayer, "places") +}) +test_that("pmCityLabels creates major-only labels", { + labels <- pmCityLabels("major-only", include_regions = FALSE) + + expect_equal(length(labels), 1) +}) + +test_that("pmCityLabels respects include_regions", { + with_regions <- pmCityLabels("major-only", include_regions = TRUE) + without_regions <- pmCityLabels("major-only", include_regions = FALSE) + + expect_equal(length(with_regions), length(without_regions) + 1) +}) + +test_that("addProtomaps accepts style parameter", { + skip_if_not_installed("leaflet") + + map <- leaflet::leaflet() + # Use a mock URL since we're just testing the R code, not actual tile loading + result <- addProtomaps(map, url = "https://example.com/tiles.pmtiles", style = pmStyle("minimal")) + + expect_s3_class(result, "leaflet") +}) + +# New style presets +test_that("pmStyle returns ink preset", { + ink <- pmStyle("ink") + expect_s3_class(ink, "pm_style") + expect_equal(ink$colors$background, "#ffffff") + expect_equal(ink$colors$highway, "#000000") +}) + +test_that("pmStyle returns terrain preset", { + terrain <- pmStyle("terrain") + expect_s3_class(terrain, "pm_style") + expect_true(length(terrain$labelRules) >= 1) +}) + +test_that("pmStyle returns transit preset", { + transit <- pmStyle("transit") + expect_s3_class(transit, "pm_style") + expect_equal(transit$colors$railway, "#e63946") +}) + +# print.pm_style +test_that("print.pm_style outputs formatted text", { + style <- pmMinimal(land = "#ffffff", water = "#000000", labels = TRUE) + + output <- capture.output(print(style)) + expect_true(any(grepl("", output))) + expect_true(any(grepl("Colors:", output))) + expect_true(any(grepl("Label Rules:", output))) +}) + +test_that("print.pm_style handles style with no labels", { + style <- pmMinimal() + + output <- capture.output(print(style)) + expect_true(any(grepl("Label Rules: none", output))) +}) + +test_that("print.pm_style summarizes uniform colors", { + style <- pmMinimal() + + output <- capture.output(print(style)) + expect_true(any(grepl("uniform", output))) +}) + +# pmModifyStyle +test_that("pmModifyStyle preserves class", { + modified <- pmModifyStyle(pmMinimal(), water = "#000000") + expect_s3_class(modified, "pm_style") +}) + +test_that("pmModifyStyle merges colors", { + original <- pmMinimal(land = "#ffffff", water = "#aaaaaa") + modified <- pmModifyStyle(original, water = "#000000") + + expect_equal(modified$colors$water, "#000000") + expect_equal(modified$colors$earth, "#ffffff") +}) + +test_that("pmModifyStyle appends label rules by default", { + original <- pmMinimal(labels = TRUE) + new_rules <- list(pmLabelRule("water", pmCenteredTextSymbolizer())) + + modified <- pmModifyStyle(original, labelRules = new_rules) + expect_equal(length(modified$labelRules), 2) +}) + +test_that("pmModifyStyle replaces labels when requested", { + original <- pmMinimal(labels = TRUE) + new_rules <- list(pmLabelRule("water", pmCenteredTextSymbolizer())) + + modified <- pmModifyStyle(original, labelRules = new_rules, replace_labels = TRUE) + expect_equal(length(modified$labelRules), 1) + expect_equal(modified$labelRules[[1]]$dataLayer, "water") +}) + +test_that("pmModifyStyle requires pm_style input", { + expect_error(pmModifyStyle(list()), "must be a pm_style") +}) diff --git a/tests/testthat/test-symbolizers.R b/tests/testthat/test-symbolizers.R new file mode 100644 index 0000000..6e18e7b --- /dev/null +++ b/tests/testthat/test-symbolizers.R @@ -0,0 +1,56 @@ +test_that("pmPolygonSymbolizer creates correct structure", { + sym <- pmPolygonSymbolizer(fill = "blue") + expect_equal(sym$type, "polygon") + expect_equal(sym$options$fill, "blue") +}) + +test_that("pmPolygonSymbolizer handles optional parameters", { + sym <- pmPolygonSymbolizer(fill = "red", stroke = "black", width = 2, opacity = 0.5) + expect_equal(sym$options$fill, "red") + expect_equal(sym$options$stroke, "black") + expect_equal(sym$options$width, 2) + expect_equal(sym$options$opacity, 0.5) +}) + +test_that("pmLineSymbolizer creates correct structure", { + sym <- pmLineSymbolizer(color = "gray", width = 3) + expect_equal(sym$type, "line") + expect_equal(sym$options$color, "gray") + expect_equal(sym$options$width, 3) +}) + +test_that("pmLineSymbolizer handles dash patterns", { + sym <- pmLineSymbolizer(color = "black", dash = c(4, 2)) + expect_equal(sym$options$dash, c(4, 2)) +}) + +test_that("pmCircleSymbolizer creates correct structure", { + sym <- pmCircleSymbolizer(radius = 8, fill = "red") + expect_equal(sym$type, "circle") + expect_equal(sym$options$radius, 8) + expect_equal(sym$options$fill, "red") +}) + +test_that("pmCenteredTextSymbolizer creates correct structure", { + sym <- pmCenteredTextSymbolizer(font = "14px Arial", fill = "black") + expect_equal(sym$type, "centeredText") + expect_equal(sym$options$font, "14px Arial") + expect_equal(sym$options$fill, "black") +}) + +test_that("pmCenteredTextSymbolizer handles lineHeight", { + sym <- pmCenteredTextSymbolizer(font = "12px sans-serif", lineHeight = 1.5) + expect_equal(sym$options$lineHeight, 1.5) +}) + +test_that("pmLineLabelSymbolizer creates correct structure", { + sym <- pmLineLabelSymbolizer(font = "11px Arial", fill = "#333") + expect_equal(sym$type, "lineLabel") + expect_equal(sym$options$font, "11px Arial") +}) + +test_that("pmShieldSymbolizer creates correct structure", { + sym <- pmShieldSymbolizer(font = "10px Arial", background = "yellow") + expect_equal(sym$type, "shield") + expect_equal(sym$options$background, "yellow") +}) diff --git a/vignettes/custom-styling.Rmd b/vignettes/custom-styling.Rmd new file mode 100644 index 0000000..35711c4 --- /dev/null +++ b/vignettes/custom-styling.Rmd @@ -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. diff --git a/vignettes/data-viz-basemaps.Rmd b/vignettes/data-viz-basemaps.Rmd new file mode 100644 index 0000000..d4cbba4 --- /dev/null +++ b/vignettes/data-viz-basemaps.Rmd @@ -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 diff --git a/vignettes/getting-started.Rmd b/vignettes/getting-started.Rmd new file mode 100644 index 0000000..ca4690a --- /dev/null +++ b/vignettes/getting-started.Rmd @@ -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. diff --git a/vignettes/labels-and-filters.Rmd b/vignettes/labels-and-filters.Rmd new file mode 100644 index 0000000..74ccf7b --- /dev/null +++ b/vignettes/labels-and-filters.Rmd @@ -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