tmap icon indicating copy to clipboard operation
tmap copied to clipboard

Can tmap internally cast points to lines

Open ratnanil opened this issue 10 months ago • 7 comments

Amazing work on Version 4!!

My Question: I have trajectory data that I want to plot. I store the data as points, since each location sample has metadata (e.g. timestamp). However, I would like to have the points joined by direct lines to see the order of the location samples.

Currently, I cast the data myself using the approach below. I then realized that some tmap-layers do spatial operations internally, e.g. calculation of the centroid. Is this also possible with POINT to LINESTRING casting?


library(sf)
library(tmap)
set.seed(42)

# create some sample data
traj <- data.frame(time = Sys.time()+1:10, x = rnorm(10), y = rnorm(10)) |> 
  st_as_sf(coords = c("x","y"))

# plot the data as points
p <- tm_shape(traj) + tm_dots()

# p (uncommented for brevity)

# plot the data as lines
traj_lines <- st_cast(st_combine(traj), "LINESTRING")

p +
tm_shape(traj_lines) + tm_lines()

Image

ratnanil avatar Feb 20 '25 10:02 ratnanil

Very interesting use case!

If I understood correctly, you would like a tm_connect_the_dots as in

Image

It should be quite easy to create a layer function for this. The same as tm_lines, but indeed with a spatial operation that create a polyline strings.

Open questions (also tagging @Nowosad @luukvdmeer @agila5 @Robinlovelace):

  • What is a good layer name? tm_trajectories? (Or tm_paths, tm_routes, ...?)
  • What to do with MULTI-features? What we could do: if there are only 'POINT's, connect them. If there are MULTIPOINTs, connect each MULTIPOINT feature with a separate line.
  • We can a variable similar to plot.order to decide the order of points. By default the order in which they appear, but should also be possible to use another variable (e.g. timestamp).

Last but not least: Would it be a good idea to:

  • add this layer function in base tmap4,
  • create an extension package for this,
  • add this to tmap.networks ?

mtennekes avatar Feb 21 '25 07:02 mtennekes

tm_path() with a nod to https://ggplot2.tidyverse.org/reference/geom_path.html?

Robinlovelace avatar Feb 21 '25 11:02 Robinlovelace

  • Keeping up with the naming of the other layers' functions -- tm_paths() seems good (plural)
  • I think that, in general, it would be preferable to put such functions in external (extension) packages, and then if they become popular/generally useful -- to incorporate them into tmap
  • I do not know if it is better to have a new extension package or to add this to tmap.networks
  • Also -- in this case, tm_paths() could be quite experimental (e.g., I can see some possible customizations, like adding arrows to the paths, bundling similar paths, etc.)

Nowosad avatar Feb 21 '25 15:02 Nowosad

Yes, agree with tm_paths.

I think that, in general, it would be preferable to put such functions in external (extension) packages, and then if they become popular/generally useful -- to incorporate them into tmap ... Also -- in this case, tm_paths() could be quite experimental (e.g., I can see some possible customizations, like adding arrows to the paths, bundling similar paths, etc.)

You could also argue the other way round: put this (as experimental) in tmap, and if it becomes popular and larger (with customuzations) migrate it to another package. (And if it doesn't, keep it in tmap).

But generally speaking I also prefer to put it in an extension package.

Probably tmap.networks makes most sense, because it is strongly correlated to network analysis tools/methods, like routing, and edge bundling. Putting tm_paths in another package say tmap.routes may add confusion.

mtennekes avatar Feb 22 '25 09:02 mtennekes

Yes, agree with tm_paths. ... But generally speaking I also prefer to put it in an extension package. ... Probably tmap.networks makes most sense...

This all sounds good to me! Should I try and implement this in tmap.networks? It just might be a while till I find the time...

ratnanil avatar Feb 25 '25 10:02 ratnanil

Hi everyone! I like the idea and I also want to point out that, to some extent, this idea is already available in sfnetworks v1.0: https://luukvdmeer.github.io/sfnetworks/dev/articles/sfn02_create_represent.html#from-spatial-points

For example:

# packages
library(sfnetworks)
library(tmap.networks)
library(tmap)
library(sf)

# Sample data
traj <- data.frame(x = 1:10 + rnorm(10, 0, 0.1), y = 1:10 + rnorm(10, 0, 0.1)) |> 
  st_as_sf(coords = c("x","y"))

# Build network from points connecting them in sequence
sfn <- as_sfnetwork(traj, "sequence")

# Plot it
tm_shape(sfn) + 
  tm_network()

(not sure why but I cannot create a reprex from that code, will add another issue in tmap.networks repo)

agila5 avatar Feb 25 '25 13:02 agila5

This is not the first time something cool is already unknowingly implemented sf_networks!

Quick question to see if this fits different scenarios with trajectory data. Would it be possible to respect a grouping column, e.g. id (see below):

(I'm using dplyr because I don't know how to st_union an sfobject by a column!)

library(sf)
library(tmap)
library(dplyr)
set.seed(42)

# create some sample data (with IDs!)
traj <- data.frame(time = Sys.time()+1:10, x = rnorm(10), y = rnorm(10), id = rep(c("A","B"),each = 5)) |> 
  st_as_sf(coords = c("x","y"))


# cast the data to lines, respecting the IDs
traj_lines <- traj |> 
  group_by(id) |> 
  summarise(do_union = TRUE) |> 
  st_cast("LINESTRING")

p +
  tm_shape(traj_lines) + tm_lines(col = "id")

Image

ratnanil avatar Feb 26 '25 05:02 ratnanil