tmap
tmap copied to clipboard
'shapeless' map (e.g. basemap only) specs
How should users be able to set the specs, like crs, bbox, zoom level, etc. for shapeless maps, by which I mean maps without using 'shape' objects (sf,stars,terra )?
So far, the only example I could think of is a basemap without additional data layers:
Few options:
- (= the option illustrated above) Set these specs as options.
- Same as 1, but in addition make them available in
tm_view
andtm_plot
. This is the same behaviour as in tmap3.tm_plot
is new, and added to allow plot-mode specific options. - Specify a tm_shape object without the spatial object itself. So e.g.
tm_shape(bbox = "Amsterdam") + tm_basemap()
- Specify it in the
tm_basemap
layer itself, as layer-specific properties.
What are your thoughts/options/ideas? (@Nowosad @Robinlovelace others)
I am leaning toward option 4 (my thinking is that the tm_basemap is somewhat similar to tm_graticules), but I also think that option 3 makes a lot of sense.
After a quick nap, I like option 3 better. IT seems to be more consistent with the rest of the tmap code.
Great to see what naps can do, not just with the little ones among us:-)
I also thought of a new option:
- Introduce
tm_map
where crs, bbox, and zoom levels (min, max and current) are determined. In that case,tm_shape
will only be used to specify the spatial object, and optionallyis.main
in case there are multiple objects with different bboxes. In casetm_map
is not called, the map properties are extracted from the main shape. In casetm_map
is used, it will determine the properties of the produced map.
Still have to think about the pros and cons, but so far:
Pro:
- The functionality between shape object and map properties are split, which I think better reflects the workflow:
-
tm_map
is supposed to be called once per map (in case there are multiple, the latest values are taken, just liketm_layout(bg.color = "red') + tm_layout(bg.color = "blue")
will ultimately result in a blue background color. ) - The user doesn't have to worry where, i.e., in which
tm_shape
call, to specify the bounding box.
-
Cons:
- The name
tm_map
may be confusing, sincetm
already stands for thematic map, and the "mapping" is also used to distinguish visual variables from transformation variables. - It is a bit further away from v3. Backwards compatibility is not an issue, since I can simply create a
tm_shape() + tm_map()
stack in casetm_shape(box = )
is used.
I think tm_map()
would add unnecessary complexity (for the users point of view) for fairly small benefits.
No strong opinions but from a quick read
tm_shape(bbox = "Amsterdam") + tm_basemap()
Sounds logical to me.
For now I went with the tm_shape
option, but in essence (and implementation wise) a tm_shape
call without shape is still a tm_options
call. That means that:
tm_basemap() + tm_shape(bbox = "Amsterdam")
also works. And also without tm_basemap
:
tm_shape(World) + tm_polygons() + tm_shape(bbox = "Amsterdam")
We could prevent this to bahavior, and make tm_shape 'connected' to tm_basemap, but then we run into a new problem with a common situation, namely where you are in view mode and do not explicitly call tm_basemap
.
So, I am okay with this choice, but from a purely conceptually point of view, I'd like tm_map
better. However, I also increase with Jakub that it may add complexity (and confusion) to some users.
Furthermore, while implementing this, I encountered two other related issues:
- You can set the zoom level in
tm_basemap
. This was already implemented a while ago. The only purpose is to decide on which zoom level to render the tiles in plot mode. However, this could clash with the zoom level set viatm_shape
(ortm_map
), which is via the argumentset.view
(see next issue about the naming). For now, I applied this simple rule: if the zoom level is specified intm_basemap
, and not intm_shape
(ortm_map
), use that. - There are three arguments in v3
tm_view
that determine the map view:set.bounds
,set.view
andset.zoom.limits
. The input specifications and the names are directly taken from the upstream leaflet functions (though the names are in camel case). Problem: I don't find these user friendly. I'd rather changeset.view
tozoom
. Viaset.view
you can also set the center point in lat/lon coordinates, but this also follows from the bounding box. Furthermore,set.zoom.limits
can be changed tozoom.limits
, andset.bounds
probably can be changed tobound
(logical that determined whether to bound the map or not. - Also related, I'm not sure to what extend these options also apply to other not-yet-implemented interactive modes, for instance rayshader or deck.gl.
Ideas / opinions?
So a tm_shape()
call with no spatial object creates a map with only a basemap?
I do think having a tm_basemap()
function is useful, if only to know where to put basemap argument like starting zoom, max zoom, etc that can feed into the interactive mapping functions below.
I agree that the zoom level in tm_basemap()
should take precedence.
Regarding 2, I agree they are not particularly intuitive. Could those updated argument names go into tm_basemap()
and tm_view
(which implies tm_basemap()
, no?) be deprecated?
Re. 3 I think getting it working from currently working interactive mode and then iterate and ask others to implement for other interactive back-ends is reasonable.
Just my quickfire thoughts after quick read, hope useful.
Thx Robin, certainly useful!
So a
tm_shape()
call with no spatial object creates a map with only a basemap?
In view mode, yes, but in plot mode nothing happens except the message "Nothing to show". This is because basemap(s) are by default added in view mode, but not in plot mode.
In essence, tm_shape
without spatial object is just setting tmap options (just like tm_options
/ tm_layout
). At least, in the current implementation, which we are reconsidering in this discussion.
I do think having a
tm_basemap()
function is useful, if only to know where to put basemap argument like starting zoom, max zoom, etc that can feed into the interactive mapping functions below.I agree that the zoom level in
tm_basemap()
should take precedence.Regarding 2, I agree they are not particularly intuitive. Could those updated argument names go into
tm_basemap()
andtm_view
(which impliestm_basemap()
, no?) be deprecated?
Yes, tm_basemap()
is definitely useful, especially for choosing the tile server. We can specify the starting/min/max zoom in tm_basemap
, but these zoom levels are also relevant for interactive maps without basemap. So that could make things a bit confusing, because then you would have to do something like tm_basemap(server = NULL, zoom = 9, zoom.max = 12)
Re. 3 I think getting it working from currently working interactive mode and then iterate and ask others to implement for other interactive back-ends is reasonable.
Yes, true. The point is, if we can guess a little bit ahead what would be required, we can already anticipate, so that we don't have to change the user interface (function/arguments) after tmap4 is on cran.