I found the cause of the shifting cells/pixels in some of the raster tiles was due an a function in DBI
. When using dbWriteTable
to write the values of each raster tile to the database, the table became unordered in some cases.
library(stars)
library(dplyr)
library(keyring)
library(DBI)
flood = read_stars("floodextent200.tif")
xy = dim(flood)
values = tibble(val = c(flood[[1]]), x = sort(rep(1:xy[2],xy[1])))
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])
)
dbWriteTable(conn,"value_test",values, overwrite =T)
write_test = dbReadTable(conn,"value_test")
dbRemoveTable(conn,"value_test")
sum(write_test$x != values$x)
[1] 70
cbind(write_test,values)[write_test$x != values$x,]
Here there are at least 70 rows in the written table where the values do not match the original table, resulting in the shifting of values. I believe this may be due to the many NAs in the value column. This is not due to the NAs in the column as I had originally thought, but is by design.
rm_na = flood
rm_na[is.na(rm_na)] = 0
zeros = tibble(val = c(rm_na[[1]]), x = sort(rep(1:xy[2],xy[1])))
dbWriteTable(conn,"value_test",zeros, overwrite = T)
write_test = dbReadTable(conn,"value_test")
dbRemoveTable(conn,"value_test")
cbind(write_test,values)[write_test$x != values$x,]
Removing the NAs from the raster before writing it to the database seems to solve the problem, though I think it would be best to refrain from altering the raster in this way. A better solution would be to add a column with row numbers to the values table and use this column to order the table after it is written to the database. I used this simple solution to fix pg_write_stars
.
pg_write_stars = function(conn, name, layer, tile_size = NULL,bit_depth = NULL) {
# writes raster (stars) to a postgis database
# conn: DBIConnection object
# name: name of table to hold raster
# layer: stars object
# tile_size: size of tiles to split raster into specified as vector with length 1 or 2, expressed as pixel width by height
# bit_depth: bit depth of raster
library(stars)
library(tibble)
library(glue)
library(purrr)
library(DBI)
if (!("postgis_raster" %in% dbGetQuery(conn, "select * FROM pg_available_extensions;")$name))
stop("PostGIS extension not available.")
if (any(class(layer) != "stars"))
stop("Object should be of class stars.")
crs = st_crs(layer)$epsg
if (is.na(crs)) {crs = 0; warning("SRID set to 0")}
dims = st_dimensions(layer)
res = c(dims$x$delta, dims$y$delta)
bbox = st_bbox(layer)
val = c(dims$x$values,dims$y$values)
aff = attr(dims,"raster")$affine
if (attr(dims, "raster")$curvilinear |
any(!is.null(val)) |
any(is.na(res))) {
stop("Raster must be regular.")
}
# determine bit depth
# from https://github.com/mablab/rpostgis/blob/master/R/pgWriteRast.R
if (is.null(bit_depth)) {
if (is.integer(layer[[1]])) {
if (min(layer[[1]], na.rm = TRUE) >= 0) {
bit_depth = "32BUI"
} else {
bit_depth = "32BSI"
}
} else {
bit_depth = "32BF"
}
}
# determine tile size
if (!is.null(tile_size)) {
if (length(tile_size) == 2) {
bx = tile_size[1]
by = tile_size[2]
} else {
bx = by = tile_size[1]
}
} else {
bx = by = 100
}
to = dim(layer)
x1 = seq(1,to[1],bx)
if (x1[length(x1)] != to[1]) x2 = c(x1[-1]-1,to[[1]])
width = map2(x1,x2,~c(.x,.y)) %>% do.call(what=rbind)
y1 = seq(1,to[2],by)
if (y1[length(y1)] != to[2]) y2 = c(y1[-1]-1,to[[2]])
height = map2(y1,y2,~c(.x,.y))
tiles = map(height,~cbind(width,.x[1],.x[2])) %>% do.call(what=rbind)
# create a list with tile dimensions and values
info = map(1:nrow(tiles), function(x) {
tile = tiles[x,]
tile = layer[1,tile[1]:tile[2],tile[3]:tile[4]]
list(dim(tile), st_bbox(tile)[c("xmin","ymax")], as.vector(tile[[1]]))
})
dbExecute(conn, glue("drop table if exists {name}"))
dbExecute(conn, glue("create table {name}(rid serial primary key, rast raster)"))
for (i in seq_along(info)) {
xy = info[[i]][[1]]
# create table with each value assigned to a row
dbWriteTable(conn,
"rst_tile_val",
tibble(
val = info[[i]][[3]],
x = sort(rep(1:xy[2], xy[1])),
row_n = 1:prod(xy)
) ,
overwrite = T)
offset = info[[i]][[2]]
# use tile dimensions to create empty raster and populate raster with values from rst_tile_val
dbExecute(conn, glue(
"insert into {name}(rid, rast)
values({i},
st_setvalues(
st_addband(
st_makeemptyraster({xy[1]}, {xy[2]}, {offset['xmin']},{offset['ymax']},{res[1]},{res[2]},{aff[1]},{aff[2]},{crs}),
array[row(1,'{bit_depth}',0,-99999)]::addbandarg[]),
1, 1, 1,(select array(select array_agg(val)from (select val, x from rst_tile_val order by row_n) as rst group by x order by x))::double precision[][]
))"))
}
dbExecute(conn,"drop table if exists rst_tile_val")
dbExecute(conn, glue("drop index if exists {name}_rast_idx"))
# create spatial index
dbExecute(conn, glue("create index {name}_rast_idx on {name} using gist(st_convexhull(rast))"))
}
To implement the ordering solution in pg_write_stars
, I had to first add a column with row numbers to the table of values when they are being written to the database.
dbWriteTable(con,
"rst_tile_val",
tibble(
val = info[[i]][[3]], #tile values
x = sort(rep(1:xy[2], xy[1])), # raster row number
row_n = 1:prod(xy) # row number (new column)
) ,
overwrite = T)
I then had to edit the SQL for inputting into an empty raster by adding a subquery to order the values by the new column with row numbers. This ensured that values of each column were ordered correctly before they were inputted in the raster.
select array(
select array_agg(val)
from (
select val, x from rst_tile_val order by row_n) as rst
group by x order by x
)
I also changed how the tiles were created, using a matrix of indices to subset the raster rather relying on intersections to break it up. I have yet to test whether this method is faster, though I like it more.
# creating a matrix with indices for subsetting
x1 = seq(1,to[1],bx)
if (x1[length(x1)] != to[1]) x2 = c(x1[-1]-1,to[[1]])
width = map2(x1,x2,~c(.x,.y)) %>% do.call(what=rbind)
y1 = seq(1,to[2],by)
if (y1[length(y1)] != to[2]) y2 = c(y1[-1]-1,to[[2]])
height = map2(y1,y2,~c(.x,.y))
tiles = map(height,~cbind(width,.x[1],.x[2])) %>% do.call(what=rbind)
# creating a list with tile dimensions and values
info = map(1:nrow(tiles), function(x) {
tile = tiles[x,]
tile = layer[1,tile[1]:tile[2],tile[3]:tile[4]]
list(dim(tile), st_bbox(tile)[c("xmin","ymax")], as.vector(tile[[1]]))
})
library(mapview)
initial = flood
pg_write_stars(conn,"pg_stars_test", initial)
[1] 0
postgis = pg_read_stars(conn,"pg_stars_test")
sum((initial == postgis)[[1]],na.rm=T) == sum(!is.na(initial)[[1]])
[1] TRUE
mapview(initial, col.regions = "#EA3546", layer.name = "Initial Raster") +mapview(postgis, col.regions = "#226CE0", layer.name = "PostGIS Raster")
Unlike with the first version of pg_write_stars
, there now is no longer any random shifting and the raster and the rasters appear to be the same based on a visual inspection and a logical test. Because of this,I would consider this fix to be a success, albeit a small one.
LS0tDQp0aXRsZTogImZpeGluZyBwZ193cml0ZV9zdGFycyINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCkkgZm91bmQgdGhlIGNhdXNlIG9mIHRoZSBzaGlmdGluZyBjZWxscy9waXhlbHMgaW4gc29tZSBvZiB0aGUgcmFzdGVyIHRpbGVzIHdhcyBkdWUgYW4gYSBmdW5jdGlvbiBpbiBgREJJYC4gV2hlbiB1c2luZyBgZGJXcml0ZVRhYmxlYCB0byB3cml0ZSB0aGUgdmFsdWVzIG9mIGVhY2ggcmFzdGVyIHRpbGUgdG8gdGhlIGRhdGFiYXNlLCB0aGUgdGFibGUgYmVjYW1lIHVub3JkZXJlZCBpbiBzb21lIGNhc2VzLiANCg0KYGBge3IgZXhhbXBsZXMsIG1lc3NhZ2UgPSBGLHdhcm5pbmdzPSBGfQ0KbGlicmFyeShzdGFycykNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGtleXJpbmcpDQpsaWJyYXJ5KERCSSkNCg0KZmxvb2QgPSByZWFkX3N0YXJzKCJmbG9vZGV4dGVudDIwMC50aWYiKQ0KDQp4eSA9IGRpbShmbG9vZCkNCg0KdmFsdWVzID0gdGliYmxlKHZhbCA9IGMoZmxvb2RbWzFdXSksIHggPSBzb3J0KHJlcCgxOnh5WzJdLHh5WzFdKSkpDQoNCmNvbm4gPSBEQkk6OmRiQ29ubmVjdCgNCiAgUlBvc3RncmVzOjpQb3N0Z3JlcygpLA0KICBkYm5hbWUgPSBrZXlfbGlzdCgpWzEsIDFdLA0KICB1c2VyID0ga2V5X2xpc3QoKVsxLCAyXSwNCiAgcGFzc3dvcmQgPSBrZXlfZ2V0KGtleV9saXN0KClbMSwgMV0sIGtleV9saXN0KClbMSwgMl0pDQopDQoNCmRiV3JpdGVUYWJsZShjb25uLCJ2YWx1ZV90ZXN0Iix2YWx1ZXMsIG92ZXJ3cml0ZSA9VCkNCg0Kd3JpdGVfdGVzdCA9IGRiUmVhZFRhYmxlKGNvbm4sInZhbHVlX3Rlc3QiKQ0KDQpkYlJlbW92ZVRhYmxlKGNvbm4sInZhbHVlX3Rlc3QiKQ0KDQpzdW0od3JpdGVfdGVzdCR4ICE9IHZhbHVlcyR4KQ0KDQpjYmluZCh3cml0ZV90ZXN0LHZhbHVlcylbd3JpdGVfdGVzdCR4ICE9IHZhbHVlcyR4LF0NCmBgYA0KSGVyZSB0aGVyZSBhcmUgYXQgbGVhc3QgNzAgcm93cyBpbiB0aGUgd3JpdHRlbiB0YWJsZSB3aGVyZSB0aGUgdmFsdWVzIGRvIG5vdCBtYXRjaCB0aGUgb3JpZ2luYWwgdGFibGUsIHJlc3VsdGluZyBpbiB0aGUgc2hpZnRpbmcgb2YgdmFsdWVzLiB+fkkgYmVsaWV2ZSB0aGlzIG1heSBiZSBkdWUgdG8gdGhlIG1hbnkgTkFzIGluIHRoZSB2YWx1ZSBjb2x1bW4ufn4gVGhpcyBpcyBub3QgZHVlIHRvIHRoZSBOQXMgaW4gdGhlIGNvbHVtbiBhcyBJIGhhZCBvcmlnaW5hbGx5IHRob3VnaHQsIGJ1dCBpcyBieSBbZGVzaWduXShodHRwczovL2dpdGh1Yi5jb20vci1kYmkvb2RiYy9pc3N1ZXMvMjQyKS4NCg0KYGBge3J9DQpybV9uYSA9IGZsb29kDQoNCnJtX25hW2lzLm5hKHJtX25hKV0gPSAwDQoNCnplcm9zID0gdGliYmxlKHZhbCA9IGMocm1fbmFbWzFdXSksIHggPSBzb3J0KHJlcCgxOnh5WzJdLHh5WzFdKSkpDQoNCmRiV3JpdGVUYWJsZShjb25uLCJ2YWx1ZV90ZXN0Iix6ZXJvcywgb3ZlcndyaXRlID0gVCkNCg0Kd3JpdGVfdGVzdCA9IGRiUmVhZFRhYmxlKGNvbm4sInZhbHVlX3Rlc3QiKQ0KDQpkYlJlbW92ZVRhYmxlKGNvbm4sInZhbHVlX3Rlc3QiKQ0KDQpjYmluZCh3cml0ZV90ZXN0LHZhbHVlcylbd3JpdGVfdGVzdCR4ICE9IHZhbHVlcyR4LF0NCmBgYA0KUmVtb3ZpbmcgdGhlIE5BcyBmcm9tIHRoZSByYXN0ZXIgYmVmb3JlIHdyaXRpbmcgaXQgdG8gdGhlIGRhdGFiYXNlIHNlZW1zIHRvIHNvbHZlIHRoZSBwcm9ibGVtLCB0aG91Z2ggSSB0aGluayBpdCB3b3VsZCBiZSBiZXN0IHRvIHJlZnJhaW4gZnJvbSBhbHRlcmluZyB0aGUgcmFzdGVyIGluIHRoaXMgd2F5LiBBIGJldHRlciBzb2x1dGlvbiB3b3VsZCBiZSB0byBhZGQgYSBjb2x1bW4gd2l0aCByb3cgbnVtYmVycyB0byB0aGUgdmFsdWVzIHRhYmxlIGFuZCB1c2UgdGhpcyBjb2x1bW4gdG8gb3JkZXIgdGhlIHRhYmxlIGFmdGVyIGl0IGlzIHdyaXR0ZW4gdG8gdGhlIGRhdGFiYXNlLiBJIHVzZWQgdGhpcyBzaW1wbGUgc29sdXRpb24gdG8gZml4IGBwZ193cml0ZV9zdGFyc2AuIA0KDQpgYGB7ciBmaXhlZCBmdW5jdGlvbn0NCnBnX3dyaXRlX3N0YXJzID0gIGZ1bmN0aW9uKGNvbm4sIG5hbWUsIGxheWVyLCB0aWxlX3NpemUgPSBOVUxMLGJpdF9kZXB0aCA9IE5VTEwpIHsNCiAgIyB3cml0ZXMgcmFzdGVyIChzdGFycykgdG8gYSBwb3N0Z2lzIGRhdGFiYXNlDQogICMgY29ubjogREJJQ29ubmVjdGlvbiBvYmplY3QNCiAgIyBuYW1lOiBuYW1lIG9mIHRhYmxlIHRvIGhvbGQgcmFzdGVyDQogICMgbGF5ZXI6IHN0YXJzIG9iamVjdCANCiAgIyB0aWxlX3NpemU6IHNpemUgb2YgdGlsZXMgdG8gc3BsaXQgcmFzdGVyIGludG8gc3BlY2lmaWVkIGFzIHZlY3RvciB3aXRoIGxlbmd0aCAxIG9yIDIsIGV4cHJlc3NlZCBhcyBwaXhlbCB3aWR0aCBieSBoZWlnaHQgDQogICMgYml0X2RlcHRoOiBiaXQgZGVwdGggb2YgcmFzdGVyDQogIA0KICBsaWJyYXJ5KHN0YXJzKQ0KICBsaWJyYXJ5KHRpYmJsZSkNCiAgbGlicmFyeShnbHVlKQ0KICBsaWJyYXJ5KHB1cnJyKQ0KICBsaWJyYXJ5KERCSSkNCiAgDQogIGlmICghKCJwb3N0Z2lzX3Jhc3RlciIgJWluJSBkYkdldFF1ZXJ5KGNvbm4sICJzZWxlY3QgKiBGUk9NIHBnX2F2YWlsYWJsZV9leHRlbnNpb25zOyIpJG5hbWUpKQ0KICAgIHN0b3AoIlBvc3RHSVMgZXh0ZW5zaW9uIG5vdCBhdmFpbGFibGUuIikNCiAgDQogIGlmIChhbnkoY2xhc3MobGF5ZXIpICE9ICJzdGFycyIpKSANCiAgICBzdG9wKCJPYmplY3Qgc2hvdWxkIGJlIG9mIGNsYXNzIHN0YXJzLiIpDQoNCiAgY3JzID0gc3RfY3JzKGxheWVyKSRlcHNnDQogIGlmIChpcy5uYShjcnMpKSB7Y3JzID0gMDsgd2FybmluZygiU1JJRCBzZXQgdG8gMCIpfQ0KICANCiAgZGltcyA9IHN0X2RpbWVuc2lvbnMobGF5ZXIpDQogIHJlcyA9IGMoZGltcyR4JGRlbHRhLCBkaW1zJHkkZGVsdGEpDQogIGJib3ggPSBzdF9iYm94KGxheWVyKQ0KICB2YWwgPSBjKGRpbXMkeCR2YWx1ZXMsZGltcyR5JHZhbHVlcykNCiAgYWZmID0gYXR0cihkaW1zLCJyYXN0ZXIiKSRhZmZpbmUNCiAgDQogIGlmIChhdHRyKGRpbXMsICJyYXN0ZXIiKSRjdXJ2aWxpbmVhciB8DQogICAgICBhbnkoIWlzLm51bGwodmFsKSkgfA0KICAgICAgYW55KGlzLm5hKHJlcykpKSB7DQogICAgc3RvcCgiUmFzdGVyIG11c3QgYmUgcmVndWxhci4iKQ0KICB9DQogIA0KICAjIGRldGVybWluZSBiaXQgZGVwdGggDQogICMgZnJvbSBodHRwczovL2dpdGh1Yi5jb20vbWFibGFiL3Jwb3N0Z2lzL2Jsb2IvbWFzdGVyL1IvcGdXcml0ZVJhc3QuUiANCiAgaWYgKGlzLm51bGwoYml0X2RlcHRoKSkgew0KICAgIGlmIChpcy5pbnRlZ2VyKGxheWVyW1sxXV0pKSB7DQogICAgICBpZiAobWluKGxheWVyW1sxXV0sIG5hLnJtID0gVFJVRSkgPj0gMCkgew0KICAgICAgICBiaXRfZGVwdGggPSAiMzJCVUkiDQogICAgICB9IGVsc2Ugew0KICAgICAgICBiaXRfZGVwdGggPSAiMzJCU0kiDQogICAgICB9DQogICAgfSBlbHNlIHsNCiAgICAgIGJpdF9kZXB0aCA9ICIzMkJGIg0KICAgIH0NCiAgfQ0KICANCiAgIyBkZXRlcm1pbmUgdGlsZSBzaXplDQogIGlmICghaXMubnVsbCh0aWxlX3NpemUpKSB7DQogICAgaWYgKGxlbmd0aCh0aWxlX3NpemUpID09IDIpIHsNCiAgICAgIGJ4ID0gdGlsZV9zaXplWzFdDQogICAgICBieSA9IHRpbGVfc2l6ZVsyXQ0KICAgIH0gZWxzZSB7DQogICAgICBieCA9IGJ5ID0gdGlsZV9zaXplWzFdDQogICAgfQ0KICB9IGVsc2Ugew0KICAgIGJ4ID0gYnkgPSAxMDANCiAgfQ0KICANCiAgdG8gPSBkaW0obGF5ZXIpDQogIA0KICB4MSA9IHNlcSgxLHRvWzFdLGJ4KQ0KICBpZiAoeDFbbGVuZ3RoKHgxKV0gIT0gdG9bMV0pIHgyID0gYyh4MVstMV0tMSx0b1tbMV1dKQ0KICB3aWR0aCA9IG1hcDIoeDEseDIsfmMoLngsLnkpKSAlPiUgZG8uY2FsbCh3aGF0PXJiaW5kKQ0KIA0KICB5MSA9IHNlcSgxLHRvWzJdLGJ5KQ0KICBpZiAoeTFbbGVuZ3RoKHkxKV0gIT0gdG9bMl0pIHkyID0gYyh5MVstMV0tMSx0b1tbMl1dKQ0KICBoZWlnaHQgPSBtYXAyKHkxLHkyLH5jKC54LC55KSkNCiAgDQogIHRpbGVzID0gbWFwKGhlaWdodCx+Y2JpbmQod2lkdGgsLnhbMV0sLnhbMl0pKSAlPiUgZG8uY2FsbCh3aGF0PXJiaW5kKQ0KICANCiAgDQogICMgY3JlYXRlIGEgbGlzdCB3aXRoIHRpbGUgZGltZW5zaW9ucyBhbmQgdmFsdWVzIA0KICBpbmZvID0gbWFwKDE6bnJvdyh0aWxlcyksIGZ1bmN0aW9uKHgpIHsNCiAgICB0aWxlID0gdGlsZXNbeCxdDQogICAgdGlsZSA9IGxheWVyWzEsdGlsZVsxXTp0aWxlWzJdLHRpbGVbM106dGlsZVs0XV0NCiAgICBsaXN0KGRpbSh0aWxlKSwgc3RfYmJveCh0aWxlKVtjKCJ4bWluIiwieW1heCIpXSwgYXMudmVjdG9yKHRpbGVbWzFdXSkpDQogIH0pDQogIA0KICBkYkV4ZWN1dGUoY29ubiwgZ2x1ZSgiZHJvcCB0YWJsZSBpZiBleGlzdHMge25hbWV9IikpDQogIGRiRXhlY3V0ZShjb25uLCBnbHVlKCJjcmVhdGUgdGFibGUge25hbWV9KHJpZCBzZXJpYWwgcHJpbWFyeSBrZXksIHJhc3QgcmFzdGVyKSIpKQ0KICANCiAgZm9yIChpIGluIHNlcV9hbG9uZyhpbmZvKSkgew0KICAgIHh5ID0gaW5mb1tbaV1dW1sxXV0NCiAgICAjIGNyZWF0ZSB0YWJsZSB3aXRoIGVhY2ggdmFsdWUgYXNzaWduZWQgdG8gYSByb3cgDQogICAgZGJXcml0ZVRhYmxlKGNvbm4sDQogICAgICAgICAgICAgICAgICJyc3RfdGlsZV92YWwiLA0KICAgICAgICAgICAgICAgICB0aWJibGUoDQogICAgICAgICAgICAgICAgICAgdmFsID0gaW5mb1tbaV1dW1szXV0sDQogICAgICAgICAgICAgICAgICAgeCA9IHNvcnQocmVwKDE6eHlbMl0sIHh5WzFdKSksDQogICAgICAgICAgICAgICAgICAgcm93X24gPSAxOnByb2QoeHkpDQogICAgICAgICAgICAgICAgICkgLA0KICAgICAgICAgICAgICAgICBvdmVyd3JpdGUgPSBUKQ0KICAgIA0KICAgIG9mZnNldCA9IGluZm9bW2ldXVtbMl1dDQogICAgDQogICAgIyB1c2UgdGlsZSBkaW1lbnNpb25zIHRvIGNyZWF0ZSBlbXB0eSByYXN0ZXIgYW5kIHBvcHVsYXRlIHJhc3RlciB3aXRoIHZhbHVlcyBmcm9tIHJzdF90aWxlX3ZhbA0KICAgIGRiRXhlY3V0ZShjb25uLCBnbHVlKA0KICAgICJpbnNlcnQgaW50byB7bmFtZX0ocmlkLCByYXN0KSANCiAgICAgdmFsdWVzKHtpfSwNCiAgICAgc3Rfc2V0dmFsdWVzKA0KICAgICAgICBzdF9hZGRiYW5kKA0KICAgICAgICAgICAgc3RfbWFrZWVtcHR5cmFzdGVyKHt4eVsxXX0sIHt4eVsyXX0sIHtvZmZzZXRbJ3htaW4nXX0se29mZnNldFsneW1heCddfSx7cmVzWzFdfSx7cmVzWzJdfSx7YWZmWzFdfSx7YWZmWzJdfSx7Y3JzfSksDQogICAgICAgICAgICBhcnJheVtyb3coMSwne2JpdF9kZXB0aH0nLDAsLTk5OTk5KV06OmFkZGJhbmRhcmdbXSksDQogICAgICAgIDEsIDEsIDEsKHNlbGVjdCBhcnJheShzZWxlY3QgYXJyYXlfYWdnKHZhbClmcm9tIChzZWxlY3QgdmFsLCB4IGZyb20gcnN0X3RpbGVfdmFsIG9yZGVyIGJ5IHJvd19uKSBhcyByc3QgZ3JvdXAgYnkgeCBvcmRlciBieSB4KSk6OmRvdWJsZSBwcmVjaXNpb25bXVtdDQogICAgICkpIikpDQogIH0NCiAgZGJFeGVjdXRlKGNvbm4sImRyb3AgdGFibGUgaWYgZXhpc3RzIHJzdF90aWxlX3ZhbCIpIA0KICBkYkV4ZWN1dGUoY29ubiwgZ2x1ZSgiZHJvcCBpbmRleCBpZiBleGlzdHMge25hbWV9X3Jhc3RfaWR4IikpDQogICMgY3JlYXRlIHNwYXRpYWwgaW5kZXgNCiAgZGJFeGVjdXRlKGNvbm4sIGdsdWUoImNyZWF0ZSBpbmRleCB7bmFtZX1fcmFzdF9pZHggb24ge25hbWV9IHVzaW5nIGdpc3Qoc3RfY29udmV4aHVsbChyYXN0KSkiKSkgDQp9DQpgYGANCg0KVG8gaW1wbGVtZW50IHRoZSBvcmRlcmluZyBzb2x1dGlvbiBpbiBgcGdfd3JpdGVfc3RhcnNgLCBJIGhhZCB0byBmaXJzdCBhZGQgYSBjb2x1bW4gd2l0aCByb3cgbnVtYmVycyB0byB0aGUgdGFibGUgb2YgdmFsdWVzIHdoZW4gdGhleSBhcmUgYmVpbmcgd3JpdHRlbiB0byB0aGUgZGF0YWJhc2UuDQpgYGB7ciwgZXZhbCA9IEZ9DQpkYldyaXRlVGFibGUoY29uLA0KICAgICAgICAgICAgICJyc3RfdGlsZV92YWwiLA0KICAgICAgICAgICAgIHRpYmJsZSgNCiAgICAgICAgICAgICAgIHZhbCA9IGluZm9bW2ldXVtbM11dLCAjdGlsZSB2YWx1ZXMNCiAgICAgICAgICAgICAgIHggPSBzb3J0KHJlcCgxOnh5WzJdLCB4eVsxXSkpLCAjIHJhc3RlciByb3cgbnVtYmVyDQogICAgICAgICAgICAgICByb3dfbiA9IDE6cHJvZCh4eSkgIyByb3cgbnVtYmVyIChuZXcgY29sdW1uKQ0KICAgICAgICAgICAgICkgLA0KICAgICAgICAgICAgIG92ZXJ3cml0ZSA9IFQpDQpgYGANCg0KSSB0aGVuIGhhZCB0byBlZGl0IHRoZSBTUUwgZm9yIGlucHV0dGluZyBpbnRvIGFuIGVtcHR5IHJhc3RlciBieSBhZGRpbmcgYSBzdWJxdWVyeSB0byBvcmRlciB0aGUgdmFsdWVzIGJ5IHRoZSBuZXcgY29sdW1uIHdpdGggcm93IG51bWJlcnMuIFRoaXMgZW5zdXJlZCB0aGF0IHZhbHVlcyBvZiBlYWNoIGNvbHVtbiB3ZXJlIG9yZGVyZWQgY29ycmVjdGx5IGJlZm9yZSB0aGV5IHdlcmUgaW5wdXR0ZWQgaW4gdGhlIHJhc3Rlci4gDQoNCmBgYHtzcWwsIGV2YWwgPUZ9DQpzZWxlY3QgYXJyYXkoDQogICAgc2VsZWN0IGFycmF5X2FnZyh2YWwpIA0KICAgIGZyb20gKA0KICAgICAgICBzZWxlY3QgdmFsLCB4IGZyb20gcnN0X3RpbGVfdmFsIG9yZGVyIGJ5IHJvd19uKSBhcyByc3QNCiAgICAgICAgZ3JvdXAgYnkgeCBvcmRlciBieSB4DQogICAgICApIA0KYGBgDQoNCkkgYWxzbyBjaGFuZ2VkIGhvdyB0aGUgdGlsZXMgd2VyZSBjcmVhdGVkLCB1c2luZyBhIG1hdHJpeCBvZiBpbmRpY2VzIHRvIHN1YnNldCB0aGUgcmFzdGVyIHJhdGhlciByZWx5aW5nIG9uIGludGVyc2VjdGlvbnMgdG8gYnJlYWsgaXQgdXAuIEkgaGF2ZSB5ZXQgdG8gdGVzdCB3aGV0aGVyIHRoaXMgbWV0aG9kIGlzIGZhc3RlciwgdGhvdWdoIEkgbGlrZSBpdCBtb3JlLiANCmBgYHtyIGNyZWF0aW5nIHRoZXQgdGlsZXMsIGV2YWw9Rn0NCiAgIyBjcmVhdGluZyBhIG1hdHJpeCB3aXRoIGluZGljZXMgZm9yIHN1YnNldHRpbmcgDQogIHgxID0gc2VxKDEsdG9bMV0sYngpDQogIGlmICh4MVtsZW5ndGgoeDEpXSAhPSB0b1sxXSkgeDIgPSBjKHgxWy0xXS0xLHRvW1sxXV0pDQogIHdpZHRoID0gbWFwMih4MSx4Mix+YygueCwueSkpICU+JSBkby5jYWxsKHdoYXQ9cmJpbmQpDQogDQogIHkxID0gc2VxKDEsdG9bMl0sYnkpDQogIGlmICh5MVtsZW5ndGgoeTEpXSAhPSB0b1syXSkgeTIgPSBjKHkxWy0xXS0xLHRvW1syXV0pDQogIGhlaWdodCA9IG1hcDIoeTEseTIsfmMoLngsLnkpKQ0KICANCiAgdGlsZXMgPSBtYXAoaGVpZ2h0LH5jYmluZCh3aWR0aCwueFsxXSwueFsyXSkpICU+JSBkby5jYWxsKHdoYXQ9cmJpbmQpDQogIA0KICAjIGNyZWF0aW5nIGEgbGlzdCB3aXRoIHRpbGUgZGltZW5zaW9ucyBhbmQgdmFsdWVzIA0KICBpbmZvID0gbWFwKDE6bnJvdyh0aWxlcyksIGZ1bmN0aW9uKHgpIHsNCiAgICB0aWxlID0gdGlsZXNbeCxdDQogICAgdGlsZSA9IGxheWVyWzEsdGlsZVsxXTp0aWxlWzJdLHRpbGVbM106dGlsZVs0XV0NCiAgICBsaXN0KGRpbSh0aWxlKSwgc3RfYmJveCh0aWxlKVtjKCJ4bWluIiwieW1heCIpXSwgYXMudmVjdG9yKHRpbGVbWzFdXSkpDQogIH0pDQpgYGANCg0KYGBge3IgcGdfcmVhZF9zdGFycywgZWNobz1GfQ0KcGdfcmVhZF9zdGFycyA9IGZ1bmN0aW9uKGNvbm4sIGxheWVyLCByYXN0ID0gInJhc3QiKXsNCiAgIyByZWFkcyByYXN0ZXIgZnJvbSBwb3N0Z2lzIGRhdGFiYXNlDQogICMgY29ubjogREJJQ29ubmVjdGlvbiBvYmplY3QNCiAgIyBsYXllcjogbmFtZSBvZiB0YWJsZSB3aXRoIHJhc3RlciANCiAgIyByYXN0OiBuYW1lIG9mIGNvbHVtbiB0aGF0IGNvbnRhaW5zIHJhc3RlciANCiAgDQogIGxpYnJhcnkoc3RhcnMpDQogIGxpYnJhcnkocHVycnIpDQogIGxpYnJhcnkoREJJKQ0KICBsaWJyYXJ5KGdsdWUpDQogIA0KICB2YWx1ZXMgPSBkYkdldFF1ZXJ5KA0KICAgIGNvbm4sDQogICAgZ2x1ZSgNCiAgICAgICJ3aXRoIG9yZGVyZWQgYXMgKA0KICAgICAgICAgIHNlbGVjdCANCiAgICAgICAgICB7cmFzdH0gYXMgcmFzdCwgDQogICAgICAgICAgcmlkIGZyb20ge2xheWVyfSANCiAgICAgICAgICBvcmRlciBieSByaWQgYXNjKQ0KICAgICAgIHNlbGVjdA0KICAgICAgIHVubmVzdChzdF9kdW1wdmFsdWVzKHJhc3QsMSkpIGFzIHZhbHVlcywNCiAgICAgICByb3dfbnVtYmVyKCkgb3ZlciAob3JkZXIgYnkgcmlkKSBhcyBpZA0KICAgICAgIGZyb20gb3JkZXJlZCINCiAgICApDQogICkNCiAgDQogIHNyaWQgPSBkYkdldFF1ZXJ5KA0KICAgIGNvbm4sDQogICAgZ2x1ZSgNCiAgICAgICJzZWxlY3QgZGlzdGluY3QgDQogICAgICAgc3Rfc3JpZCh7cmFzdH0pIGFzIHNyaWQgDQogICAgICAgZnJvbSB7bGF5ZXJ9IikpJHNyaWQgJT4lIA0KICAgIHN0X2Nycw0KICANCiAgYm94ZXMgPSBkYkdldFF1ZXJ5KA0KICAgIGNvbm4sIA0KICAgIGdsdWUoDQogICAgICAic2VsZWN0IA0KICAgICAgIHN0X2FzZXdrYihzdF9lbnZlbG9wZSh7cmFzdH0pKSBhcyBnZW9tIA0KICAgICAgIGZyb20ge2xheWVyfQ0KICAgICAgIG9yZGVyIGJ5IHJpZCBhc2MiKSkkZ2VvbSAlPiUgDQogICAgc3RfYXNfc2ZjKEVXS0IgPSBUKSANCiAgDQogIGRpbXMgPSBkYkdldFF1ZXJ5KA0KICAgIGNvbm4sIA0KICAgIGdsdWUoDQogICAgICAic2VsZWN0ICANCiAgICAgICBzdF9waXhlbHdpZHRoKHtyYXN0fSkgYXMgZHgsIA0KICAgICAgIHN0X3BpeGVsaGVpZ2h0KHtyYXN0fSkgYXMgZHksDQogICAgICAgc3Rfc2tld3goe3Jhc3R9KSBhcyBzeCwNCiAgICAgICBzdF9za2V3eSh7cmFzdH0pIGFzIHN5DQogICAgICAgZnJvbSB7bGF5ZXJ9DQogICAgICAgbGltaXQgMSIpKQ0KICANCiAgcmVzdWx0ID0gbWFwMihib3hlcywgdW5pcXVlKHZhbHVlcyRpZCksIGZ1bmN0aW9uKHgsIHkpDQogICAgc3RfYXNfc3RhcnMoDQogICAgICBzdF9iYm94KHgpLA0KICAgICAgZHggPSBkaW1zJGR4LA0KICAgICAgZHkgPSBkaW1zJGR5LA0KICAgICAgdmFsdWVzID0gdmFsdWVzW3ZhbHVlcyRpZCA9PSB5LCBdJHZhbHVlcw0KICAgICkgJT4lIHN0X3NldF9jcnMoc3JpZCkpICU+JQ0KICAgIGRvLmNhbGwod2hhdCA9IHN0X21vc2FpYykNCiAgDQogIGF0dHIoYXR0cihyZXN1bHQsICJkaW1lbnNpb25zIiksICJyYXN0ZXIiKSRhZmZpbmUgPSBjKGRpbXNbWzNdXSxkaW1zW1s0XV0pDQogIA0KICByZXN1bHQNCn0NCmBgYA0KDQpgYGB7ciByZWFkaW5nIGFuZCB3cml0aW5nLCBtZXNzYWdlPUYsIHdhcm5pbmc9RiwgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9OX0NCmxpYnJhcnkobWFwdmlldykNCg0KDQppbml0aWFsID0gZmxvb2QNCg0KcGdfd3JpdGVfc3RhcnMoY29ubiwicGdfc3RhcnNfdGVzdCIsIGluaXRpYWwpDQoNCnBvc3RnaXMgPSBwZ19yZWFkX3N0YXJzKGNvbm4sInBnX3N0YXJzX3Rlc3QiKQ0KDQpzdW0oKGluaXRpYWwgPT0gcG9zdGdpcylbWzFdXSxuYS5ybT1UKSA9PSBzdW0oIWlzLm5hKGluaXRpYWwpW1sxXV0pDQoNCm1hcHZpZXcoaW5pdGlhbCwgY29sLnJlZ2lvbnMgPSAiI0VBMzU0NiIsIGxheWVyLm5hbWUgPSAiSW5pdGlhbCBSYXN0ZXIiKSArbWFwdmlldyhwb3N0Z2lzLCBjb2wucmVnaW9ucyA9ICIjMjI2Q0UwIiwgbGF5ZXIubmFtZSA9ICJQb3N0R0lTIFJhc3RlciIpIA0KYGBgDQoNClVubGlrZSB3aXRoIHRoZSBmaXJzdCB2ZXJzaW9uIG9mIGBwZ193cml0ZV9zdGFyc2AsIHRoZXJlIG5vdyBpcyBubyBsb25nZXIgYW55IHJhbmRvbSBzaGlmdGluZyBhbmQgdGhlIHJhc3RlciBhbmQgdGhlIHJhc3RlcnMgYXBwZWFyIHRvIGJlIHRoZSBzYW1lIGJhc2VkIG9uIGEgdmlzdWFsIGluc3BlY3Rpb24gYW5kIGEgbG9naWNhbCB0ZXN0LiBCZWNhdXNlIG9mIHRoaXMsSSB3b3VsZCBjb25zaWRlciB0aGlzIGZpeCB0byBiZSBhIHN1Y2Nlc3MsIGFsYmVpdCBhIHNtYWxsIG9uZS4gDQo=