-
Notifications
You must be signed in to change notification settings - Fork 123
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
georeferenced output #914
Comments
Related discussion: #383 |
Thanks. Avenza is an app for mobile use of geo-referenced PDFs (including in a disconnected back-country hiking use case.) They are an end-user platform for US Bureau of Land Management final products https://www.blm.gov/maps/georeferenced-PDFs (expand "How to download and use"). Right now those two major players might be georeferencing maps manually, and may be locked into an old ArcGIS workflow: Avenza-ready Google Earth imagery georeferenced PDF in QGIS. (QGIS is a GIS system that cannot compete with ArcGIS for feature-completeness.) Given this technological state, tmap georeferenced output would be a powerful and potentially widely-consumed application of the R sf + tmap architecture. |
Thx for this input @potrykus Just tested, and it is already possible in
So now the question is how replace the sf plotting mechanism with tmap. Any ideas @edzer or @tim-salabim ? If someone could help me with the |
|
Can I use tm = tm_shape(World) + tm_poygons()
tmap_save(tm, "World.pdf")
#within tmap_save:
pdf(file)
print(tm)
dev.off()
sf::st_write(?, "World.pdf", offset = , scale = , crs = ) Is so, do you know via which of its arguments these can be passed on ( |
I think |
I cannot add much here, as |
Oh, yes, and there's the raster driver too: https://gdal.org/en/latest/drivers/raster/pdf.html#raster-pdf |
Yes, but if I understand that correctly, outpu can either be RGB(A) or not. In case it's not I guess it'll be greyscale? |
would have to get the "plotted" extents of the map for the geotransform, i.e, if it was rendered and written out to file with base graphics the Tools will complain about coordinates out of range for longlat and potentially other crs (the margin of a plot in Mollweide won't have a valid inverse for example, but I don't think that's a huge problem. I'll explore where the render happens and if the overall frame extent can be gotten. (I wrote something for this elsewhere but I can't find it). Just to clarify, we're talking about raster graphics yeah? In GDAL terms you need the geotransform, which is a rejig of extent and dimension (shape) mixed together as offset and scale, i.e. equivalent of f <- function(x, dimension) {
px <- c(diff(x[1:2])/dimension[1L], -diff(x[3:4])/dimension[2L])
c(xmin = x[1], xres = px[1], yskew = 0, ymax = x[4], xskew = 0, yres = px[2])
}
f(par("usr"), dev.size("px"))
but, extent is enough reordered into 'a_ullr' with VRT or vrt::// or write to .wld file even. |
oops sorry, not "usr", it needs the full expanded plot range - in base might need to use 'plt' to get that (which I think I did before somewhere) at any rate we need the grid viewport extent but handy to have some more examples I expect |
grconvertX/Y is your friend for base graphics: device_extent <- function() {
c(grconvertX(c(0, 1) , "ndc", "user"),
grconvertY(c(0, 1), "ndc", "user"))
}
pdf(tf <- tempfile(fileext = ".pdf"))
maps::map(col = "grey")
## get this information relative to the current plot
dext <- device_extent()
dev.off()
print(dext)
## (equivalent to a call to gdal_translate {tf} out.vrt -a_ullr ...) since GDAL 3.7
dsn <- sprintf("vrt://%s?a_srs=EPSG:4326&a_ullr=%s", tf, paste0(dext[c(1, 4, 2, 3)], collapse = ","))
terra::plot(terra::rast(dsn))
maps::map(add = TRUE, col = "red")
|
is it possible to get this information? Surely in the pipeline from a tmap object to a rendered graphic we could get information about the frame within the device coordinates? We need this for getting decent images from web servers in #975 and #502 also. With base graphics you can do ideal_dim <- function(x, ...) {
if (!missing(x)) plot(x, ...)
dcx <- grconvertX(c(0, 1), "npc", "device")
dcy <- grconvertY(c(0, 1), "npc", "device")
## "ideal" dimension then is
ceiling(c(diff(dcx), diff(dcy[2:1])))
} to get the right dimensions to fit within an existing plot, the xsize and ysize to provide to GDAL (using non-nearest resampling is also important, for maps with text or other features on them). I see that tm_rgb() and the tm_lines() and other vector plotting families set up different framing to each other, and to base graphics. I don't know how to get these equivalent values, |
Thx @mdsumner for following up on this. I use a grid layout of about 20ish rows and columns, and more if facets are used. Some of these grid cell viewports contain the actual maps with the native coordinates. Which ones exactly is stored internally. What I can do is to look up this information and pass this on to |
I realized I can do this now with the exploring I did, it's the same question - what is the right resolution and extent of data for the warper in a facet, is the same information as what is the extent of the entire rendered canvas. I'll get back to it soonish |
here's a very rough example, I don't know how to export the graphic via code in a way that is faithful to what I see on the current device, so I do that manually - we could simply write a .wld file with the transform or write a relevant VRT - I also use VRT to expand the RGB because I don't know otherwise how to have terra plot the thing again fathfully library(tmap)
data(land, World)
tm_shape(land) +
tm_raster("cover")
## here I don't know how to export the current graphic as-is relative to the current device,
## so I use the UI to "export->save->PNG" my dev.size("px") is 500x500
tree <- grid::grid.ls(viewports = TRUE, flatten = TRUE, grobs = FALSE)
tree$name
vpname <- grep("vp_map", tree$name, value = TRUE)[1] ## how to get the right vp ... but this is the one we want
grid::seekViewport(vpname)
vp <- grid::current.viewport()
## not sure how to reset our seek viewport ...
## pix is where we are in the current device
pix <- lapply(grid::deviceLoc(grid::unit(c(0, 1), "npc"), grid::unit(c(0, 1), "npc"), device = T), as.numeric)
dm <- unlist(lapply(pix, \(.x) abs(ceiling(diff(.x)))))
ex <- c(vp$xscale, vp$yscale)
## the size of the pixels
res <- diff(ex)[c(1, 3)]/dm
px <- dev.size("px")
## so overall extent of current device is
ovex <- c(ex[1] - pix$x[1] * res[1], ex[2] + (px[1] - pix$x[2]) * res[1],
ex[3] - (px[2] - pix$y[1]) * res[2], ex[4] + (pix$y[2]) * res[2])
on my setup this 'ovex' is c(-187.5, 187.5, -270.2, 104.8 )
r <- rast("vrt://afile.png?expand=rgb")
r <- flip(r)
ext(r) <- ext(ovex)
plotRGB(r)
maps::map(add = T)
see attached afile.png |
bundling this up a little more, we get color table with raster but actual RGB with the polygons so there's still a lot to worry about, also I have no idea how to relate the rendered PDF to this (also because of my viewport traversing I don't leave the plot in a correct state that tmap did) library(tmap)
data(land, World)
tm_shape(land) +
tm_raster("cover")
render_tmap <- function(filename = tempfile(fileext = ".png")) {
px <- dev.size("px")
## here I don't know how to export the current graphic as-is relative to the current device,
## so I use the UI to "export->save->PNG" my dev.size("px") is 500x500
rstudioapi::savePlotAsImage(filename, width = px[1], height = px[2])
tree <- grid::grid.ls(viewports = TRUE, flatten = TRUE, grobs = FALSE)
vpname <- grep("vp_map", tree$name, value = TRUE)[1] ## how to get the right vp ... but this is the one we want
grid::seekViewport(vpname)
vp <- grid::current.viewport()
## not sure how to reset our seek viewport ...
## pix is where we are in the current device
pix <- lapply(grid::deviceLoc(grid::unit(c(0, 1), "npc"), grid::unit(c(0, 1), "npc"), device = T), as.numeric)
dm <- unlist(lapply(pix, \(.x) abs(ceiling(diff(.x)))))
ex <- c(vp$xscale, vp$yscale)
## the size of the pixels
res <- diff(ex)[c(1, 3)]/dm
## so overall extent of current device is
ovex <- c(ex[1] - pix$x[1] * res[1], ex[2] + (px[1] - pix$x[2]) * res[1],
ex[3] - (px[2] - pix$y[1]) * res[2], ex[4] + (pix$y[2]) * res[2])
## in world file coordinates
res[2] <- -res[2]
world <- c(xres = res[1L], yskew = 0, xskew = 0, yres = res[2L],
xmin = ovex[1L] + res[1L]/2, ymax = ovex[4L] + res[2L]/2)
worldfile <- gsub("png$", "wld", filename)
writeLines(as.character(world), worldfile)
filename
}
## we need this for the Africa example
#data(World)
#Africa = World[World$continent == "Africa", ]
#tm_shape(Africa) + tm_polygons()
#v <- render_tmap()
#plot(rast(sprintf("vrt://%s?expand=rgb", v)))
## this for the raster example
v <- render_tmap()
plotRGB(rast(v))
maps::map(add = T)
So, there's a rough prototype but not that promising |
tmap produces PDFs wih clean vector data. Shouldn't tmap automatically georeference that output - i.e. make its output geo-enabled? Here is a python overview: https://gis.stackexchange.com/questions/49646/is-it-possible-to-georeference-an-existing-un-georeferenced-pdf. This is what I found for the R raster package: https://rpubs.com/Rubio-Polania/1123497.
The text was updated successfully, but these errors were encountered: