【问题标题】:dbWriteTable with geometry (point) type to MariaDB带有几何(点)类型的 dbWriteTable 到 MariaDB
【发布时间】:2020-12-13 19:22:10
【问题描述】:

使用R 中的sf 对象,以及MariaDB 中带有geometry(在本例中为point)列的表,我正在努力在两者之间有效地移动数据(@987654326 @对象到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对象

理想情况下,我希望能够使用dbWriteTabledbAppendTable 将这样的sf 对象直接写入MariaDB。目前,这会出现兼容性错误。

如果我尝试使用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, "temp", 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, "CREATE OR REPLACE TEMPORARY TABLE temp (name VARCHAR(10), geometry POINT)")
dbAppendTable(consdb, "temp", pnt)

# Error in result_bind(res@ptr, 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 表之间传递数据的问题?

编辑添加:根据@SymbolixAU 的评论,我现在尝试了以下方法

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_write()吗?例如,当我写信给 Postgres 时,这对我有用:sf::st_write( obj = sf, dsn = consdb, layer = tbl, append = TRUE, layer_options = c('OVERWRITE=false', 'APPEND=true' ) )
  • 感谢@SymbolixAU 的提示,我只是尝试了一下,但没有成功。我将编辑帖子以添加它作为尝试。
  • 您的坐标应该按“lon”、“lat”而不是“lat”、“lon”的顺序排列吗? (你的纬度是 145,我认为是 miatake)
  • 好地方@SymbolixAU 是的,这是一个错误。不幸的是,它不能解决问题。有趣的是,我可以让st_write 编写一个新表(而不是追加),但几何值会转换为奇怪的值。在这种情况下,例如:“010100000000000000000043c00000000000206240”
  • 这是众所周知的二进制,它是几何的二进制表示。这是存储数据的标准方式。

标签: r mariadb sf rmariadb


【解决方案1】:

对此我想出了一些老生常谈的解决方案。这并不理想,但我认为它可能已经足够了。

由于sf 包可以使用st_as_text 将几何列转换为WKT 字符串,而MariaDB 可以使用ST_GeomFromText 进行相反的操作,因此我可以使用它们来使事情正常进行。

一个问题是我想传递给MariaDB的函数调用(类似ST_GeomFromText('POINT(1 2)')的函数不能传递给任何常用的表写入函数,比如dbAppendTable,因为它们会将函数调用转换为文本字符串(我假设通过引用它),所以我必须创建自己的插入查询并使用dbExecute 调用它。

这是我想出的函数,其目的是在要写入的对象是sf对象时扮演dbAppendTable的角色。

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);",
        name,
        paste(col_names, collapse = ","),
        paste(apply(value, 1, paste, collapse = ","), collapse = "),(")
    )
    
    # execute the query
    dbExecute(conn, sql_query)
    
}

这似乎对我有用,但我确信它远不如 dbAppendTable 之类的强大或高效。一方面,我使用单个查询字符串,它不适用于大型查询,并且不如某些包设法利用的 LOAD DATA INFILE 方法高效。

如果有人有更好的解决方案,我仍然很乐意听到。

【讨论】:

    猜你喜欢
    • 2014-06-07
    • 1970-01-01
    • 1970-01-01
    • 2021-10-01
    • 2019-04-22
    • 2020-09-28
    • 2021-06-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多