【问题标题】:Write GeoDataFrame into SQL Database将 GeoDataFrame 写入 SQL 数据库
【发布时间】:2021-07-27 17:17:50
【问题描述】:

我希望我的问题不是荒谬的,因为令人惊讶的是,这个问题显然还没有在流行的网站上(据我所知)真正被问到。

情况是我有几个 csv 文件,总共包含超过 1 个 Mio 观察结果。除其他外,每个观察都包含一个邮政地址。我计划将所有文件读入单个 GeoDataFrame,对地址进行地理编码,在给定 shapefile 的情况下执行空间连接,并为每一行保存多边形中的一些信息。相当标准,我想。这是一次性数据清理过程的一部分。

我的目标是用这个最终数据集建立一个数据库。这是因为它使我可以很容易地共享和搜索数据,例如在网站上绘制一些观察结果。此外,它还可以很容易地根据一些标准选择观察结果,然后进行一些分析。

我的问题是,将 GeoDataFrame 插入数据库的功能似乎尚未实现 - 显然是因为 GeoPandas 应该是数据库的替代品(“GeoPandas 使您能够轻松地在 python 中执行原本需要空间数据库,例如 PostGIS")。

当然,我可以遍历每一行并“手动”插入每个数据点,但我在这里寻找最佳解决方案。对于任何解决方法,我也会担心数据类型可能与数据库的数据类型冲突。有没有“最好的方式”来这里?

感谢您的帮助。

【问题讨论】:

    标签: python mysql pandas geopandas


    【解决方案1】:

    如前所述,@Kartik 的答案仅适用于单个调用,对于附加数据,它会引发 DataError,因为 geom 列然后期望几何具有 SRID。您可以使用GeoAlchemy处理所有情况:

    # Imports
    from geoalchemy2 import Geometry, WKTElement
    from sqlalchemy import *
    
    # Use GeoAlchemy's WKTElement to create a geom with SRID
    def create_wkt_element(geom):
        return WKTElement(geom.wkt, srid = <your_SRID>)
    
    geodataframe['geom'] = geodataframe['geom'].apply(create_wkt_element)
    
    db_url = 'postgresql://username:password@host:socket/database'
    engine = create_engine(db_url, echo=False)
    
    # Use 'dtype' to specify column's type
    # For the geom column, we will use GeoAlchemy's type 'Geometry'
    your_geodataframe.to_sql(table_name, engine, if_exists='append', index=False, 
                             dtype={'geom': Geometry('POINT', srid= <your_srid>)})
    

    【讨论】:

    • 这不会关闭数据库连接。
    【解决方案2】:

    所以,我刚刚为 PostGIS 数据库实现了这个,我可以在这里粘贴我的方法。对于 MySQL,您必须调整代码。

    第一步是将地理编码的列转换为 WKB 十六进制字符串,因为我使用 SQLAlchemy 和基于 pyscopg 的引擎,并且这两个包本身都不理解地理类型。下一步是像往常一样将该数据写入 SQL DB(请注意,所有几何列都应转换为保存 WKB 十六进制字符串的文本列),最后通过执行查询将列的类型更改为几何。参考如下伪代码:

    # Imports
    import sqlalchemy as sal
    import geopandas as gpd
    
    # Function to generate WKB hex
    def wkb_hexer(line):
        return line.wkb_hex
    
    # Convert `'geom'` column in GeoDataFrame `gdf` to hex
        # Note that following this step, the GeoDataFrame is just a regular DataFrame
        # because it does not have a geometry column anymore. Also note that
        # it is assumed the `'geom'` column is correctly datatyped.
    gdf['geom'] = gdf['geom'].apply(wkb_hexer)
    
    # Create SQL connection engine
    engine = sal.create_engine('postgresql://username:password@host:socket/database')
    
    # Connect to database using a context manager
    with engine.connect() as conn, conn.begin():
        # Note use of regular Pandas `to_sql()` method.
        gdf.to_sql(table_name, con=conn, schema=schema_name,
                   if_exists='append', index=False)
        # Convert the `'geom'` column back to Geometry datatype, from text
        sql = """ALTER TABLE schema_name.table_name
                   ALTER COLUMN geom TYPE Geometry(LINESTRING, <SRID>)
                     USING ST_SetSRID(geom::Geometry, <SRID>)"""
        conn.execute(sql)
    

    【讨论】:

    • 酷!当表不存在时,这适用于第一次调用。但是连续打电话给我DataError: (psycopg2.DataError) Geometry SRID (0) does not match column SRID (4326)
    • psycopg-postgis 可能有一些灵感:github.com/yohanboniface/psycopg-postgis。但是,它们实现了自己的类型,而不是 shapely / geopandas 中的类型。
    • 这个可以只适用于单个调用,而不适用于附加数据,因为 geom 列然后期望几何具有 SRID。
    • @j08lue,是的,你是对的。但是,如果您期待多个插入,那么您只需避免运行最后一个 alter table 查询,直到所有插入完成。如果它是一个插入和查询可能交错的在线系统,您可以使用临时表从 python 插入数据,然后将其复制到您的工作表,同时适当地转换列。或者,您可以使用字符串插补生成 SQL 插入语句,并对列应用适当的类型转换,然后执行该语句。
    • @j08lue,最终,这取决于您的具体需求。如果一次只需要附加几行,我会使用字符串插补,以及用于批量插入的临时表或 csv 中间。除非软件包中内置了自动转换器,否则这可能仍然是唯一的解决方案。同样对于 PostGIS,WKB hex 是加载和读取地理类型数据的首选方法。
    【解决方案3】:

    Hamri Said 的答案的一个版本,但使用了 lambda,在我看来这更好一些,因为它是一个如此短的函数:

    # Imports
    from geoalchemy2 import Geometry, WKTElement
    from sqlalchemy import *
    
    geodataframe['geom'] = geodataframe['geom'].apply(lambda geom: WKTElement(geom.wkt, srid = <your_SRID>))
    
    db_url = 'postgresql://username:password@host:socket/database'
    engine = create_engine(db_url, echo=False)
    
    # Use 'dtype' to specify column's type
    # For the geom column, we will use GeoAlchemy's type 'Geometry'
    your_geodataframe.to_sql(table_name, engine, if_exists='append', index=False, 
                             dtype={'geom': Geometry('POINT', srid= <your_srid>)})
    

    【讨论】:

      【解决方案4】:

      我会回来给出更好的答案。 geopandas.GeoDataFrame 对象有一个 .to_postgis() 方法,该方法处理了许多处理几何​​类型的麻烦事。

      【讨论】:

        猜你喜欢
        • 2017-01-22
        • 2017-02-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-02-22
        • 2018-06-10
        • 2016-09-01
        相关资源
        最近更新 更多