Open marine-ecologist opened 3 months ago
Apparently, we have to pass the zoom limits (set.zoom.limits
) not only to options
of leaflet()
, but also to providerTileOptions
in addProviderTiles
.
So far so good. However, the maxNativeZoom
depends on the tile provider, and needs to be specified manually. Leaving it undefined gives a blank layer, e.g.
tmp_map |> tmap_leaflet() |>
leaflet::addProviderTiles('Esri.WorldImagery', options=leaflet::providerTileOptions(maxZoom=100))
does not work.
As a user, you always want to be able to zoom in to the most detailed zoom level available. In other words, I see no reason to set the general max zoom to 18, but the max native zoom level to 10 (if 18 is also available).
Relevant post: https://github.com/Leaflet/Leaflet/issues/6316
@tim-salabim how did you deal with this issue in mapview?
We don't use maxNativeZoom
at all in mapview. The maxZoom
for the basemaps is set to 52 here. I can't quite remember why we set this to 52 exactly, but I vaguely remember that some issues arose if it was set to something higher. The general reason for maxZoom
to be set so high is that it sometimes helps to see very small sliver polygons that can be produced by operations like st_intersection
et al.
The issue seems to be quite tough, as maxZoom
can even vary within basemap providers. Take the following code and zoom into North America somewhere, you will see that you can zoom past 19 and still get some basemap. However, if you zoom somewhere into Africa, it stops at zoom 17 and hence will not enable zooming past that (you can zoom, but no imagery). If you set maxNativeZoom = 17
then you will be able to zoom further into Africa, but at the same time you loose zoom levels 18 & 19 in North America...
library(leaflet)
leaflet() |>
leaflet::addProviderTiles('Esri.WorldImagery', options=leaflet::providerTileOptions(maxZoom=52, maxNativeZoom = 19)) |> leafem::addMouseCoordinates()
Not sure if there is a good one-fits-all solution here...
Would it be feasible to set a max zoom internally for the common
basemap providers?
providers_max_zoom <- list(
"OpenStreetMap" = 19,
"CartoDB.Positron" = 19,
"Stamen.Toner" = 20,
"Esri.WorldImagery" = 17
)
That's already done internally on the JavaScript side by the leaflet.providers
package if I'm not mistaken
Good idea @marine-ecologist ! I can include such a list in tmap, but it is preferable to do maintain such a list somewhere upstream (JS side). Can't find it in the leaflet.providers
@tim-salabim, at least from the R side.
I've added max.native.zoom
to tm_tiles
:
tm_basemap("Esri.WorldImagery", max.native.zoom = 18) +
tm_shape(heron_island_coords) +
tm_dots() +
tm_mouse_coordinates() +
tm_view(set.view = 16, set.zoom.limits=c(2,20))
This https://github.com/leaflet-extras/leaflet-providers/blob/master/leaflet-providers.js#L78-L1217
Just to mention it here, maybe python xyzservices
has a solution to the maxNativeZoom
issue? @martinfleis
https://github.com/geopandas/xyzservices
Not sure I follow what the actual issue is here. xyzservices
stores metadata for min_zoom
and max_zoom
. When this gets consumed by folium
(Python leaflet.js wrapper), these get used within the map unless a user overrides any of them manually. maxNativeZoom
is, by default, equal to max_zoom
but you can also override that.
We don't use maxNativeZoom at all in mapview.
I believe that if you don't set it, tiles at higher zoom levels just disappear. If you do, the tiles are autoscaled (blurred).
I believe that if you don't set it, tiles at higher zoom levels just disappear. If you do, the tiles are autoscaled (blurred).
In theory, yes. But if you take the example from https://github.com/r-tmap/tmap/issues/880#issuecomment-2131154655 you will see that it only autoscales in regions within the map where the map provider actually has tiles until maxNativeZoom
(North America in this example). If there are no tiles at this depth (as in Africa in the example), then can zoom until maxNativeZoom
is reached, but you get the same behaviour as if maxNativeZoom
was not set (i.e. tiles disappear; no autoscaling). Hence, I think given the current implementation of the JavaScript code in leaflet(Providers), there is no consistent way of setting maxNativeZoom
to get identical behaviour everywhere. Does that clarify the issue at hand @martinfleis ?
@tim-salabim - I hadn't realised maxNativeZoom
was dynamic. One solution/workaround implemented elsewhere is Leaflet.TileLayer.Fallback that replaces missing Tiles (404 error) with scaled lower zoom Tiles.
I've tried implementing this via leaflet
and htmltools
(with a West Africa coast) as a Dependency but to with limited success either inline or via unpkg
:
library(leaflet)
library(htmlwidgets)
library(htmltools)
# Create the JavaScript dependency
js_dep <- htmlDependency(
name = "leaflet-tilelayer-fallback",
version = "1.0.4",
src = c(href = "https://unpkg.com/leaflet.tilelayer.fallback@1.0.4/dist/"),
script = "leaflet.tilelayer.fallback.js"
)
# Create the leaflet map
map <- leaflet() %>%
addProviderTiles(
providers$Esri.WorldImagery,
options = providerTileOptions(maxNativeZoom = 18, maxZoom = 100)
) %>%
addMarkers(lng = 10.54925, lat = -51.16226) %>%
onRender("
function(el, x) {
var map = this;
L.tileLayer.fallback('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
minNativeZoom: 0,
maxNativeZoom: 20,
maxZoom: 100
}).addTo(map);
}
")
# Attach the JavaScript dependency to the map
map <- htmltools::attachDependencies(map, js_dep)
map
In theory, yes. But if you take the example from https://github.com/r-tmap/tmap/issues/880#issuecomment-2131154655 you will see that it only autoscales in regions within the map where the map provider actually has tiles until maxNativeZoom
That is not what is happening. The issue with these specific ESRI tiles is that they do provide tiles for higher zoom levels but those images just say "no data...". If you test on something else, like OpenStreetMap.HOT
, it actually behaves the way I described. The issue you are facing here is with tiles not software.
@martinfleis thanks for the clarification. I think there's not much we can do here (apart from utilising @marine-ecologist "s suggestion using Leaflet.TileLayer.Fallback ), though, for me, this is not high priority at the moment.
?tm_view
mentions passing options toleafletOptions()
vialeaflet.option=
, but I can't seem to get this to work forleaflet::providerTileOptions
. Example below:not working:
working via
tmap_leaflet()
: