Skip to content

Note: This functionality is for advanced users and may not be supported across all functions (for example, addRasterImage() currently works only with EPSG:3857 Web Mercator).

The Leaflet package expects all point, line, and shape data to be specified in latitude and longitude using WGS 84 (a.k.a. EPSG:4326). By default, when displaying this data it projects everything to EPSG:3857 and expects that any map tiles are also displayed in EPSG:3857.

For users that need to display data with a different projection, we’ve integrated the Proj4Leaflet plugin, which in theory gives Leaflet access to any CRS that is supported by Proj4js.

Note that whatever map tiles you use must be designed to work with the CRS of your Leaflet map.

Defining a custom CRS

Once you’ve decided on a custom projection, and have map tiles to match it (if necessary), you can use leafletCRS() to create a custom projection.

crs <- leafletCRS(crsClass = "L.Proj.CRS", code = "ESRI:102003",
  proj4def = "+proj=aea +lat_1=29.5 +lat_2=45.5 +lat_0=37.5 +lon_0=-96 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs",
  resolutions = 1.5^(25:15))

The crsClass parameter lets you specify the JavaScript constructor to use to generate the Leaflet.js CRS object. In this case, we’re using L.Proj.CRS, which is the class that comes with Proj4Leaflet. (Only a specific list of CRS classes can be used here; see ?leafletCRS for more details.)

The code parameter is the CRS identifier, usually an EPSG identifier or similar. (For the most part, this doesn’t affect us, and you can treat it like documentation; it’s primarily used by Proj4Leaflet for purposes that don’t apply to the R package.)

The proj4def parameter is either a PROJ.4 or WKT string that defines the CRS. If you don’t know the PROJ.4 or WKT string, you can generally find these on epsg.io or spatialreference.org.

The resulting object can be passed to the leaflet function via the parameter crs = leafletOptions(crs = ...).

Displaying basemap tiles with custom projections

This example shows Gothenberg, Sweden in EPSG:3006 (SWEREF99 TM) projection.

epsg3006 <- leafletCRS(crsClass = "L.Proj.CRS", code = "EPSG:3006",
  proj4def = "+proj=utm +zone=33 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs",
  resolutions = 2^(13:-1), # 8192 down to 0.5
  origin = c(0, 0)
)

tile_url <- "http://api.geosition.com/tile/osm-bright-3006/{z}/{x}/{y}.png"
tile_attrib <- "Map data &copy; <a href='http://www.openstreetmap.org/copyright'>OpenStreetMap contributors</a>, Imagery &copy; 2013 <a href='http://www.kartena.se/'>Kartena</a>"

leaflet(options = leafletOptions(worldCopyJump = FALSE, crs = epsg3006)) %>%
  setView(11.965053, 57.70451, 13) %>%
  addTiles(urlTemplate = tile_url,
    attribution = tile_attrib,
    options = tileOptions(minZoom = 0, maxZoom = 14, continuousWorld = TRUE)) %>%
  addMarkers(11.965053, 57.70451)

Again, it’s critical to use basemap tiles that are in the same projection that is specified in leafletCRS(). In this case the api.geosition.com server does indeed uses EPSG:3006 for its tiles.

We can render a similar map with the default EPSG:3857 projection for comparison. If the custom projection worked correctly, the markers should appear at the same position.

leaflet() %>%
  setView(11.965053, 57.70451, 16) %>%
  addTiles() %>%
  addMarkers(11.965053, 57.70451)

Displaying shapes with custom projections

While tiles must be in the same projection as used in leafletCRS(), you must always use WGS 84 longitude/latitude data for markers, circles, polygons, and lines. Leaflet will automatically project the coordinates when displaying.

This example uses data from the rnaturalearth package and projects it to the EPSG:9311 (US National Atlas Equal Area) coordinate system. Compare the first map, which uses the default CRS, to the second map, which is reprojected.

north_america <-
  rnaturalearth::countries110 |>
  dplyr::filter(CONTINENT == "North America")

epsg9311 <- leafletCRS(
  crsClass = "L.Proj.CRS",
  code = "EPSG:9311",
  proj4def = "+proj=laea +lat_0=45 +lon_0=-100 +x_0=0 +y_0=0 +a=6370997 +b=6370997 +units=m +no_defs",
  resolutions = 2 ^ (16:7)
)

pal <- leaflet::colorNumeric(palette = "viridis", domain = north_america$POP_EST)

plot_na_map <- function(opts = leafletOptions()) {
  leaflet(north_america, options = opts) %>%
    addPolygons(
      weight = 1,
      color = "#444444",
      opacity = 1,
      fillColor = ~ pal(POP_EST),
      fillOpacity = 0.7,
      smoothFactor = 0.5,
      label = ~ paste(SOVEREIGNT, POP_EST),
      labelOptions = labelOptions(direction = "auto")
    )
}

plot_na_map()
plot_na_map(opts = leafletOptions(crs = epsg9311))

Polar projections

It’s possible to use polar projections, though you may encounter even more problems and incompatibilities with other Leaflet.js plugins than when using other types of custom projections.