I have been using a great package called rpostgis
as an interface to my PostGIS database to read and write rasters. As much as I enjoy using the package, I do have one reservation with it: its use of raster
and proj4strings. To better understand how the package works and to find a way to use stars
objects in place of raster
in the reading and writing of PostGIS rasters, I went on to find a way to read PostGIS rasters into R as stars
objects with the function pg_read_stars
.
pg_read_stars = function(conn, layer, rast = "rast"){
# reads raster from postgis database
# conn: DBIConnection object
# layer: name of table with raster
# rast: name of column that contains raster
library(stars)
library(purrr)
library(DBI)
library(glue)
values = dbGetQuery(
conn,
glue(
"with ordered as (
select
{rast} as rast,
rid from {layer}
order by rid asc)
select
unnest(st_dumpvalues(rast,1)) as values,
row_number() over (order by rid) as id
from ordered"
)
)
srid = dbGetQuery(
conn,
glue(
"select distinct
st_srid({rast}) as srid
from {layer}"))$srid %>%
st_crs
boxes = dbGetQuery(
conn,
glue(
"select
st_asewkb(st_envelope({rast})) as geom
from {layer}
order by rid asc"))$geom %>%
st_as_sfc(EWKB = T)
dims = dbGetQuery(
conn,
glue(
"select
st_pixelwidth({rast}) as dx,
st_pixelheight({rast}) as dy,
st_skewx({rast}) as sx,
st_skewy({rast}) as sy
from {layer}
limit 1"))
result = map2(boxes, unique(values$id), function(x, y)
st_as_stars(
st_bbox(x),
dx = dims$dx,
dy = dims$dy,
values = values[values$id == y, ]$values
) %>% st_set_crs(srid)) %>%
do.call(what = st_mosaic)
attr(attr(result, "dimensions"), "raster")$affine = c(dims[[3]],dims[[4]])
result
}
pg_read_stars
is a simplification of pgGetRast
from rpostgis
. It can only read one band and a spatial filter cannot be used to limit what is read, though the latter should not be too difficult to implement. For example, this is one possible way of using a spatial filter.
# bounding box of sf object/sfc converted to well-known text to be used as a filter
ewkt = boundary %>%
st_transform(crs) %>%
st_bbox %>%
st_as_sfc() %>%
st_as_text(EWKT = T)
values = dbGetQuery(
conn,
glue(
"with clip as
(select
st_clip({raster_column},st_geomfromewkt('{ewkt}')) as clipped,
rid
from {layer})
select unnest(st_dumpvalues(clipped,1)) as values,
row_number() over (order by rid) as id
from clip"
)
)
boxes = dbGetQuery(
conn,
glue(
"with clip as
(select
st_clip({raster_column},st_geomfromewkt('{ewkt}')) as clipped
from {layer}
where st_intersects({raster_column},st_geomfromewkt('{ewkt}')))
select
st_asbinary(st_envelope(clipped)) as geom
from clip"
)
)$geom %>%
st_as_sfc(crs = srid)
pg_read_stars
works by first obtaining the values of the raster and assigning an ID based on which tile the value is from. The bounding boxes of the tiles that compose the raster are then read into R as well as the raster’s SRID and resolution. map2
from purrr
is then used to make each bounding box/tile into a stars
object inputted with the appropriate values and the resulting objects are then mosaicked to create the complete raster.
library(keyring)
library(mapview)
conn = DBI::dbConnect(
RPostgres::Postgres(),
dbname = key_list()[1, 1],
user = key_list()[1, 2],
password = key_get(key_list()[1, 1], key_list()[1, 2])
)
flood = pg_read_stars(conn,"floods")
mapview(flood, col.regions = "#226CE0", layer.name = "Flooding in Houston")
LS0tDQp0aXRsZTogInJlYWRpbmcgcG9zdGdpcyByYXN0ZXJzIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCkkgaGF2ZSBiZWVuIHVzaW5nIGEgZ3JlYXQgcGFja2FnZSBjYWxsZWQgW2BycG9zdGdpc2BdKGh0dHBzOi8vZ2l0aHViLmNvbS9tYWJsYWIvcnBvc3RnaXMpIGFzIGFuIGludGVyZmFjZSB0byBteSBQb3N0R0lTIGRhdGFiYXNlIHRvIHJlYWQgYW5kIHdyaXRlIHJhc3RlcnMuIEFzIG11Y2ggYXMgSSBlbmpveSB1c2luZyB0aGUgcGFja2FnZSwgSSBkbyBoYXZlIG9uZSByZXNlcnZhdGlvbiB3aXRoIGl0OiBpdHMgdXNlIG9mIGByYXN0ZXJgIGFuZCBwcm9qNHN0cmluZ3MuIFRvIGJldHRlciB1bmRlcnN0YW5kIGhvdyB0aGUgcGFja2FnZSB3b3JrcyBhbmQgdG8gZmluZCBhIHdheSB0byB1c2UgYHN0YXJzYCBvYmplY3RzIGluIHBsYWNlIG9mIGByYXN0ZXJgIGluIHRoZSByZWFkaW5nIGFuZCB3cml0aW5nIG9mIFBvc3RHSVMgcmFzdGVycywgSSB3ZW50IG9uIHRvIGZpbmQgYSB3YXkgdG8gcmVhZCBQb3N0R0lTIHJhc3RlcnMgaW50byBSIGFzIGBzdGFyc2Agb2JqZWN0cyB3aXRoIHRoZSBmdW5jdGlvbiAgYHBnX3JlYWRfc3RhcnNgLg0KIA0KYGBge3IgcGdfcmVhZF9zdGFyc30NCnBnX3JlYWRfc3RhcnMgPSBmdW5jdGlvbihjb25uLCBsYXllciwgcmFzdCA9ICJyYXN0Iil7DQogICMgcmVhZHMgcmFzdGVyIGZyb20gcG9zdGdpcyBkYXRhYmFzZQ0KICAjIGNvbm46IERCSUNvbm5lY3Rpb24gb2JqZWN0DQogICMgbGF5ZXI6IG5hbWUgb2YgdGFibGUgd2l0aCByYXN0ZXIgDQogICMgcmFzdDogbmFtZSBvZiBjb2x1bW4gdGhhdCBjb250YWlucyByYXN0ZXIgDQogIA0KICBsaWJyYXJ5KHN0YXJzKQ0KICBsaWJyYXJ5KHB1cnJyKQ0KICBsaWJyYXJ5KERCSSkNCiAgbGlicmFyeShnbHVlKQ0KICANCiAgdmFsdWVzID0gZGJHZXRRdWVyeSgNCiAgICBjb25uLA0KICAgIGdsdWUoDQogICAgICAid2l0aCBvcmRlcmVkIGFzICgNCiAgICAgICAgICBzZWxlY3QgDQogICAgICAgICAge3Jhc3R9IGFzIHJhc3QsIA0KICAgICAgICAgIHJpZCBmcm9tIHtsYXllcn0gDQogICAgICAgICAgb3JkZXIgYnkgcmlkIGFzYykNCiAgICAgICBzZWxlY3QNCiAgICAgICB1bm5lc3Qoc3RfZHVtcHZhbHVlcyhyYXN0LDEpKSBhcyB2YWx1ZXMsDQogICAgICAgcm93X251bWJlcigpIG92ZXIgKG9yZGVyIGJ5IHJpZCkgYXMgaWQNCiAgICAgICBmcm9tIG9yZGVyZWQiDQogICAgKQ0KICApDQogIA0KICBzcmlkID0gZGJHZXRRdWVyeSgNCiAgICBjb25uLA0KICAgIGdsdWUoDQogICAgICAic2VsZWN0IGRpc3RpbmN0IA0KICAgICAgIHN0X3NyaWQoe3Jhc3R9KSBhcyBzcmlkIA0KICAgICAgIGZyb20ge2xheWVyfSIpKSRzcmlkICU+JSANCiAgICBzdF9jcnMNCiAgDQogIGJveGVzID0gZGJHZXRRdWVyeSgNCiAgICBjb25uLCANCiAgICBnbHVlKA0KICAgICAgInNlbGVjdCANCiAgICAgICBzdF9hc2V3a2Ioc3RfZW52ZWxvcGUoe3Jhc3R9KSkgYXMgZ2VvbSANCiAgICAgICBmcm9tIHtsYXllcn0NCiAgICAgICBvcmRlciBieSByaWQgYXNjIikpJGdlb20gJT4lIA0KICAgIHN0X2FzX3NmYyhFV0tCID0gVCkgDQogIA0KICBkaW1zID0gZGJHZXRRdWVyeSgNCiAgICBjb25uLCANCiAgICBnbHVlKA0KICAgICAgInNlbGVjdCAgDQogICAgICAgc3RfcGl4ZWx3aWR0aCh7cmFzdH0pIGFzIGR4LCANCiAgICAgICBzdF9waXhlbGhlaWdodCh7cmFzdH0pIGFzIGR5LA0KICAgICAgIHN0X3NrZXd4KHtyYXN0fSkgYXMgc3gsDQogICAgICAgc3Rfc2tld3koe3Jhc3R9KSBhcyBzeQ0KICAgICAgIGZyb20ge2xheWVyfQ0KICAgICAgIGxpbWl0IDEiKSkNCiAgDQogIHJlc3VsdCA9IG1hcDIoYm94ZXMsIHVuaXF1ZSh2YWx1ZXMkaWQpLCBmdW5jdGlvbih4LCB5KQ0KICAgIHN0X2FzX3N0YXJzKA0KICAgICAgc3RfYmJveCh4KSwNCiAgICAgIGR4ID0gZGltcyRkeCwNCiAgICAgIGR5ID0gZGltcyRkeSwNCiAgICAgIHZhbHVlcyA9IHZhbHVlc1t2YWx1ZXMkaWQgPT0geSwgXSR2YWx1ZXMNCiAgICApICU+JSBzdF9zZXRfY3JzKHNyaWQpKSAlPiUNCiAgICBkby5jYWxsKHdoYXQgPSBzdF9tb3NhaWMpDQogIA0KICBhdHRyKGF0dHIocmVzdWx0LCAiZGltZW5zaW9ucyIpLCAicmFzdGVyIikkYWZmaW5lID0gYyhkaW1zW1szXV0sZGltc1tbNF1dKQ0KICANCiAgcmVzdWx0DQp9DQpgYGANCmBwZ19yZWFkX3N0YXJzYCBpcyBhIHNpbXBsaWZpY2F0aW9uIG9mIGBwZ0dldFJhc3RgIGZyb20gYHJwb3N0Z2lzYC4gSXQgY2FuIG9ubHkgcmVhZCBvbmUgYmFuZCBhbmQgYSBzcGF0aWFsIGZpbHRlciBjYW5ub3QgYmUgdXNlZCB0byBsaW1pdCB3aGF0IGlzIHJlYWQsIHRob3VnaCB0aGUgbGF0dGVyIHNob3VsZCBub3QgYmUgdG9vIGRpZmZpY3VsdCB0byBpbXBsZW1lbnQuIEZvciBleGFtcGxlLCB0aGlzIGlzIG9uZSBwb3NzaWJsZSB3YXkgb2YgdXNpbmcgYSBzcGF0aWFsIGZpbHRlci4gDQoNCmBgYHtyIHVzaW5nIGEgc3BhdGlhbCBmaWx0ZXIsIGV2YWwgPSBGfQ0KIyBib3VuZGluZyBib3ggb2Ygc2Ygb2JqZWN0L3NmYyBjb252ZXJ0ZWQgdG8gd2VsbC1rbm93biB0ZXh0IHRvIGJlIHVzZWQgYXMgYSBmaWx0ZXINCiAgZXdrdCA9IGJvdW5kYXJ5ICU+JQ0KICAgICAgc3RfdHJhbnNmb3JtKGNycykgJT4lDQogICAgICBzdF9iYm94ICU+JQ0KICAgICAgc3RfYXNfc2ZjKCkgJT4lDQogICAgICBzdF9hc190ZXh0KEVXS1QgPSBUKQ0KICAgIA0KICAgIHZhbHVlcyA9IGRiR2V0UXVlcnkoDQogICAgICBjb25uLA0KICAgICAgZ2x1ZSgNCiAgICAgICAgIndpdGggY2xpcCBhcw0KICAgICAgICAgIChzZWxlY3QNCiAgICAgICAgICAgc3RfY2xpcCh7cmFzdGVyX2NvbHVtbn0sc3RfZ2VvbWZyb21ld2t0KCd7ZXdrdH0nKSkgYXMgY2xpcHBlZCwNCiAgICAgICAgICAgcmlkDQogICAgICAgICAgIGZyb20ge2xheWVyfSkNCiAgICAgICAgIHNlbGVjdCB1bm5lc3Qoc3RfZHVtcHZhbHVlcyhjbGlwcGVkLDEpKSBhcyB2YWx1ZXMsDQogICAgICAgICByb3dfbnVtYmVyKCkgb3ZlciAob3JkZXIgYnkgcmlkKSBhcyBpZA0KICAgICAgICAgZnJvbSBjbGlwIg0KICAgICAgKQ0KICAgICkNCiAgICANCiAgICBib3hlcyA9IGRiR2V0UXVlcnkoDQogICAgICBjb25uLA0KICAgICAgZ2x1ZSgNCiAgICAgICAgIndpdGggY2xpcCBhcw0KICAgICAgICAgICAgKHNlbGVjdA0KICAgICAgICAgICAgIHN0X2NsaXAoe3Jhc3Rlcl9jb2x1bW59LHN0X2dlb21mcm9tZXdrdCgne2V3a3R9JykpIGFzIGNsaXBwZWQNCiAgICAgICAgICAgICBmcm9tIHtsYXllcn0NCiAgICAgICAgICAgICB3aGVyZSBzdF9pbnRlcnNlY3RzKHtyYXN0ZXJfY29sdW1ufSxzdF9nZW9tZnJvbWV3a3QoJ3tld2t0fScpKSkNCiAgICAgICAgIHNlbGVjdA0KICAgICAgICAgc3RfYXNiaW5hcnkoc3RfZW52ZWxvcGUoY2xpcHBlZCkpIGFzIGdlb20NCiAgICAgICAgIGZyb20gY2xpcCINCiAgICAgICkNCiAgICApJGdlb20gJT4lDQogICAgICBzdF9hc19zZmMoY3JzID0gc3JpZCkNCmBgYA0KDQpgcGdfcmVhZF9zdGFyc2Agd29ya3MgYnkgZmlyc3Qgb2J0YWluaW5nIHRoZSB2YWx1ZXMgb2YgdGhlIHJhc3RlciBhbmQgYXNzaWduaW5nIGFuIElEIGJhc2VkIG9uIHdoaWNoIHRpbGUgdGhlIHZhbHVlIGlzIGZyb20uIFRoZSBib3VuZGluZyBib3hlcyBvZiB0aGUgdGlsZXMgdGhhdCBjb21wb3NlIHRoZSByYXN0ZXIgYXJlIHRoZW4gcmVhZCBpbnRvIFIgYXMgd2VsbCBhcyB0aGUgcmFzdGVyJ3MgU1JJRCBhbmQgcmVzb2x1dGlvbi4gYG1hcDJgIGZyb20gYHB1cnJyYCBpcyB0aGVuIHVzZWQgdG8gbWFrZSBlYWNoIGJvdW5kaW5nIGJveC90aWxlIGludG8gYSBgc3RhcnNgIG9iamVjdCBpbnB1dHRlZCB3aXRoIHRoZSBhcHByb3ByaWF0ZSB2YWx1ZXMgYW5kIHRoZSByZXN1bHRpbmcgb2JqZWN0cyBhcmUgdGhlbiBtb3NhaWNrZWQgdG8gY3JlYXRlIHRoZSBjb21wbGV0ZSByYXN0ZXIuICANCg0KDQpgYGB7ciBmdW5jdGlvbiBleGFtcGxlLCBtZXNzYWdlPUYsIHdhcm5pbmc9RiwgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9OX0NCmxpYnJhcnkoa2V5cmluZykNCmxpYnJhcnkobWFwdmlldykNCg0KY29ubiA9IERCSTo6ZGJDb25uZWN0KA0KICBSUG9zdGdyZXM6OlBvc3RncmVzKCksDQogIGRibmFtZSA9IGtleV9saXN0KClbMSwgMV0sDQogIHVzZXIgPSBrZXlfbGlzdCgpWzEsIDJdLA0KICBwYXNzd29yZCA9IGtleV9nZXQoa2V5X2xpc3QoKVsxLCAxXSwga2V5X2xpc3QoKVsxLCAyXSkNCikNCg0KZmxvb2QgPSBwZ19yZWFkX3N0YXJzKGNvbm4sImZsb29kcyIpDQptYXB2aWV3KGZsb29kLCBjb2wucmVnaW9ucyA9ICIjMjI2Q0UwIiwgbGF5ZXIubmFtZSA9ICJGbG9vZGluZyBpbiBIb3VzdG9uIikNCmBgYA==