问题描述
使用sf
中的R
对象,以及MariaDB
(在本例中为geometry
)列中的point
中的表,我都在努力以便在两个数据之间有效地移动数据(sf
对象到MariaDB
表,反之亦然)。
请注意,我正在使用RMariaDB
包连接到MariaDB
,并在此处将我的连接定义为consdb
。
示例数据:
library(sf)
pnt <- data.frame( name = c("first","second"),lon = c(145,146),lat = c(-38,-39) )
pnt <- st_as_sf( pnt,coords = c("lon","lat") )
尝试直接写sf对象
理想情况下,我希望能够使用MariaDB
或dbWriteTable
将类似sf的对象直接写入dbAppendTable
。目前,这看起来像是兼容性错误。
如果我尝试使用dbWriteTable
:
dbWriteTable(consdb,"temp",pnt,temporary=TRUE,overwrite=TRUE)
# Error in result_bind(res@ptr,params) : Cannot get geometry object from data you send to the GEOMETRY field [1416]
或首先创建表格:
dbExecute(consdb,"CREATE OR REPLACE TEMPORARY TABLE temp (name VARCHAR(10),geometry POINT)")
dbAppendTable(consdb,pnt)
# Error in result_bind(res@ptr,params) : Unsupported column type list
尝试在插入时转换为点类型
如果我要通过sql插入查询进行插入,则可以像这样使用PointFromText
INSERT INTO temp (name,geometry) VALUES ('new point',PointFromText('POINT(145 38)',4326));
所以我尝试使用它来将数据作为字符串发送。我编写了一些函数将sf
几何列转换为适当的字符串列:
# to convert 1 value
point_to_text <- function(x,srid = 4326) {
sprintf("PointFromText('POINT(%f %f)',%i)",x[1],x[2],srid)
}
# to apply the above over a whole column
points_to_text <- function(x,srid = 4326) {
vapply(x,point_to_text,srid = srid,NA_character_)
}
用于将sf
对象变成data.frame
for_sql <- data.frame(pnt)
for_sql$geometry <- points_to_text(for_sql$geometry)
“几何”列现在是一个字符列,例如:PointFromText('POINT(145.000000 -38.000000)',4326)
使用dbWriteTable
只会创建一个文本列,因此我尝试创建表,然后使用dbAppendTable
:
dbExecute(consdb,params) : Cannot get geometry object from data you send to the GEOMETRY field [1416]
有效的方法,但似乎很愚蠢
如果我创建一个临时sql表,将列更改为文本,从R中插入数据,在sql中转换列,然后将其附加到原始sql表中,则可以使此方法起作用。似乎令人费解,但是只是为了证明它有效:
# create temporary table
dbExecute(consdb,"CREATE OR REPLACE TEMPORARY TABLE temp_geom LIKE temp")
# change the geometry column to text
dbExecute(consdb,"ALTER TABLE temp_geom MODIFY COLUMN geometry TEXT")
# add the data to the temporary table
dbAppendTable(consdb,"temp_geom",for_sql)
# add a new point column
dbExecute(consdb,"ALTER TABLE temp_geom ADD COLUMN geom_conv POINT")
# convert strings to points
dbExecute(consdb,"UPDATE temp_geom SET geom_conv = PointFromText(geometry,4326)")
# drop the old column and replace it with the new one
dbExecute(consdb,"ALTER TABLE temp_geom DROP COLUMN geometry")
dbExecute(consdb,"ALTER TABLE temp_geom CHANGE COLUMN geom_conv geometry POINT")
# append the data from the temporary table to the main one
dbExecute(consdb,"INSERT INTO temp SELECT * FROM temp_geom")
是否有其他解决方案,或者任何可能解决在sf
对象和MariaDB
表之间传递数据的问题的解决方案?
st_write(
obj=pnt,# the sf class object,as created above
dsn=consdb,# the MariaDB connection
layer="temp",# the table name on MariaDB
append=TRUE,layer_options=c('OVERWRITE=false','APPEND=true')
)
# Error in result_bind(res@ptr,params) :
Cannot get geometry object from data you send to the GEOMETRY field [1416]
解决方法
我为此提出了一些棘手的解决方案。这不是理想的,但我认为可能已经足够了。
由于sf
包可以使用st_as_text
将几何列转换为WKT字符串,并且MariaDB
可以对ST_GeomFromText
做相反的处理,因此我可以使用它们来获得一切正常。
一个问题是我要传递给MariaDB
的函数调用(类似ST_GeomFromText('POINT(1 2)')
的东西不能传递给任何dbAppendTable
这样的常用表写函数,因为它们会转换该函数将调用文本字符串(假定为带引号),因此我必须创建自己的插入查询并使用dbExecute
进行调用。
这是我想出的功能,当要写入的对象是dbAppendTable
对象时,其作用是扮演sf
的角色。
sf_dbAppendTable <- function(conn,name,value,srid = 4326) {
# convert the geometry columns to MariaDB function calls
sfc_cols <- vapply(value,inherits,NA,"sfc")
for(col in which(sfc_cols)) {
value[[col]] <- sprintf(
"ST_GeomFromText('%s',%i)",sf::st_as_text( value[[col]] ),srid
)
}
# when inserting to sql,surround some values in quotes,except a few types
# specifically exclude the geometry columns from this
cols_to_enquote <- vapply(value,function(x) {
if (inherits(x,"logical")) return( FALSE )
if (inherits(x,"integer")) return( FALSE )
if (inherits(x,"double")) return( FALSE )
return( TRUE )
},NA) & !sfc_cols
# set aside column names
col_names <- names(value)
# convert to a matrix
value <- as.matrix(value)
# it should be character
if (typeof(value) != "character") value <- as.character(value)
# enquote the columns that need it,except for `NA` values,replace with `NULL`
value[,which(cols_to_enquote) ] <- ifelse(
is.na(value[,which(cols_to_enquote) ]),"NULL",paste0("'",value[,which(cols_to_enquote) ],"'")
)
# any `NA` values still remaining,also replace with `NULL`
value[ is.na(value) ] <- "NULL"
# create a single insert query
sql_query <- sprintf(
"INSERT INTO %s (%s) VALUES (%s);",paste(col_names,collapse = ","),paste(apply(value,1,paste,collapse = "),(")
)
# execute the query
dbExecute(conn,sql_query)
}
这似乎对我有用,但是我敢肯定,它远没有dbAppendTable
这样的健壮或高效。一方面,我使用的是单个查询字符串,不适用于大型查询,并且效率不如某些软件包设法利用的LOAD DATA INFILE
方法有效。
如果有人有更好的解决方案,我还是很想听听。