Working with projections in Leaflet

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 the leafletCRS function 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 or

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 <- "{z}/{x}/{y}.png"
tile_attrib <- "Map data &copy; <a href=''>OpenStreetMap contributors</a>, Imagery &copy; 2013 <a href=''>Kartena</a>"

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

Again, it’s critical to use basemap tiles that are in the same projection that is specified in the leafletCRS function. In this case the 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 the leafletCRS function, 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 EPSG:2163 (US National Atlas Equal Area projection). The dataset we use (usa_composite from the albersusa package, though we’re not actually using Albers projection here) has Alaska and Hawaii moved closer to mainland US as well as rotated and resized accordingly.


spdf <- rmapshaper::ms_simplify(usa_composite())
pal <- colorNumeric("Blues", domain = spdf$pop_2014)
epsg2163 <- leafletCRS(
  crsClass = "L.Proj.CRS",
  code = "EPSG:2163",
  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))

leaflet(spdf, options = leafletOptions(crs = epsg2163)) %>%
  addPolygons(weight = 1, color = "#444444", opacity = 1,
    fillColor = ~pal(pop_2014), fillOpacity = 0.7, smoothFactor = 0.5,
    label = ~paste(name, pop_2014),
    labelOptions = labelOptions(direction = "auto"))

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. You can refer to this set of examples by Bhaskar Karambelkar to get started.

The "leaflet" R package is copyright © 2014-2016 RStudio, Inc.
The Leaflet JavaScript library is © 2010–2016 Vladimir Agafonkin, 2010–2011 CloudMade.
Maps © OpenStreetMap contributors unless otherwise noted.

Fork me on GitHub