Ajouter un cadre de données GeoPandas à une table PostGIS?

15

J'ai un simple Dataframe GeoPandas:

entrez la description de l'image ici

Je voudrais télécharger ce GeoDataframe dans une table PostGIS. J'ai déjà une configuration de base de données avec l'extension PostGIS mais je n'arrive pas à ajouter ce Dataframe en tant que table.

J'ai essayé ce qui suit:

engine = <>
meta = MetaData(engine)
eld_test = Table('eld_test', meta, Column('id', Integer, primary_key=True), Column('key_comb_drvr', Text), 
                 Column('geometry', Geometry('Point', srid=4326))) 
eld_test.create(engine) 
conn = engine.connect() 
conn.execute(eld_test.insert(), df.to_dict('records'))
thecornman
la source
J'ai essayé ce qui suit: engine = <> # create table meta = MetaData (engine) eld_test = Table ('eld_test', meta, Column ('id', Integer, primary_key = True), Column ('key_comb_drvr', Text) , Column ('geometry', Geometry ('Point', srid = 4326))) eld_test.create (engine) # Exécution de DBAPI avec la liste des dicts conn = engine.connect () conn.execute (eld_test.insert (), df .to_dict ('records'))
thecornman
1
Bienvenue à GIS SE, veuillez lire notre visite ! Pourriez-vous modifier votre message pour inclure votre code publié dans les commentaires?
GISKid

Réponses:

30

En utilisant la méthode to_sql de Panda et SQLAlchemy, vous pouvez stocker une trame de données dans Postgres. Et puisque vous stockez un Geodataframe, GeoAlchemy gérera la colonne geom pour vous. Voici un exemple de code:

# Imports
from geoalchemy2 import Geometry, WKTElement
from sqlalchemy import *
import pandas as pd
import geopandas as gpd

# Creating SQLAlchemy's engine to use
engine = create_engine('postgresql://username:password@host:socket/database')


geodataframe = gpd.GeoDataFrame(pd.DataFrame.from_csv('<your dataframe source>'))
#... [do something with the geodataframe]

geodataframe['geom'] = geodataframe['geometry'].apply(lambda x: WKTElement(x.wkt, srid=<your_SRID>)

#drop the geometry column as it is now duplicative
geodataframe.drop('geometry', 1, inplace=True)

# Use 'dtype' to specify column's type
# For the geom column, we will use GeoAlchemy's type 'Geometry'
geodataframe.to_sql(table_name, engine, if_exists='append', index=False, 
                         dtype={'geom': Geometry('POINT', srid= <your_srid>)})

Il convient de noter que le paramètre «if_exists» vous permet de gérer la façon dont la trame de données sera ajoutée à votre table postgres:

    if_exists = replace: If table exists, drop it, recreate it, and insert data.
    if_exists = fail: If table exists, do nothing.
    if_exists = append: If table exists, insert data. Create if does not exist.
Hamri Said
la source
Y a-t-il une possibilité de reprojeter ici en spécifiant un SRID différent de celui de la colonne de géométrie, ou faut-il utiliser le SRID actuel? De plus , quelle est la meilleure façon d'obtenir le SRID entier de la colonne de géométrie ?
rovyko
4

J'ai également eu la même question que vous avez posée et j'y ai passé de très nombreux jours (plus que je ne veux l'admettre) à la recherche d'une solution. En supposant que la table postgreSQL suivante avec l'extension postGIS,

postgres=> \d cldmatchup.geo_points;
Table "cldmatchup.geo_points"
Column   |         Type         |                               Modifiers                                
-----------+----------------------+------------------------------------------------------------------------
gridid    | bigint               | not null default nextval('cldmatchup.geo_points_gridid_seq'::regclass)
lat       | real                 | 
lon       | real                 | 
the_point | geography(Point,4326) | 

Indexes:
"geo_points_pkey" PRIMARY KEY, btree (gridid)

voici ce que j'ai finalement réussi à faire:

import geopandas as gpd
from geoalchemy2 import Geography, Geometry
from sqlalchemy import create_engine, MetaData, Table
from sqlalchemy.orm import sessionmaker
from shapely.geometry import Point
from psycopg2.extensions import adapt, register_adapter, AsIs

# From http://initd.org/psycopg/docs/advanced.html#adapting-new-types but 
# modified to accomodate postGIS point type rather than a postgreSQL 
# point type format
def adapt_point(point):
    from psycopg2.extensions import adapt, AsIs
    x = adapt(point.x).getquoted()
    y = adapt(point.y).getquoted()
    return AsIs("'POINT (%s %s)'" % (x, y))

register_adapter(Point, adapt_point)

engine = create_engine('postgresql://<yourUserName>:postgres@localhost:5432/postgres', echo=False)
Session = sessionmaker(bind=engine)
session = Session()
meta = MetaData(engine, schema='cldmatchup')

# Create reference to pre-existing "geo_points" table in schema "cldmatchup"
geoPoints = Table('geo_points', meta, autoload=True, schema='cldmatchup', autoload_with=engine)

df = gpd.GeoDataFrame({'lat':[45.15, 35., 57.], 'lon':[-35, -150, -90.]})

# Create a shapely.geometry point 
the_point = [Point(xy) for xy in zip(df.lon, df.lat)]

# Create a GeoDataFrame specifying 'the_point' as the column with the 
# geometry data
crs = {'init': 'epsg:4326'}
geo_df = gpd.GeoDataFrame(df.copy(), crs=crs, geometry=the_point)

# Rename the geometry column to match the database table's column name.
# From https://media.readthedocs.org/pdf/geopandas/latest/geopandas.pdf,
# Section 1.2.2 p 7
geo_df = geo_df.rename(columns{'geometry':'the_point'}).set_geometry('the_point')

# Write to sql table 'geo_points'
geo_df.to_sql(geoPoints.name, engine, if_exists='append', schema='cldmatchup', index=False)

session.close()

Je ne peux pas dire si ma logique de connexion à la base de données est la meilleure car j'ai essentiellement copié cela à partir d'un autre lien et j'étais simplement heureux d'avoir réussi à mapper automatiquement (ou à refléter) ma table existante avec la définition de la géométrie reconnue. J'écris du python au code spatial sql depuis seulement quelques mois, donc je sais qu'il y a beaucoup à apprendre.

user1745564
la source
0

J'ai une solution qui ne nécessite que psycopg2 et galbée (en plus des géopandas bien sûr). Il est généralement mauvais de parcourir les (Geo)DataFrameobjets car ils sont lents, mais pour les petits ou pour des tâches ponctuelles, le travail sera toujours effectué.

Fondamentalement, cela fonctionne en vidant la géométrie au format WKB dans une autre colonne, puis en la réinjectant pour la GEOMETRYsaisir lors de l'insertion.

Notez que vous devrez créer le tableau à l'avance avec les bonnes colonnes.

import psycopg2 as pg2
from shapely.wkb import dumps as wkb_dumps
import geopandas as gpd


# Assuming you already have a GeoDataFrame called "gdf"...

# Copy the gdf if you want to keep the original intact
insert_gdf = gdf.copy()

# Make a new field containing the WKB dumped from the geometry column, then turn it into a regular 
insert_gdf["geom_wkb"] = insert_gdf["geometry"].apply(lambda x: wkb_dumps(x))

# Define an insert query which will read the WKB geometry and cast it to GEOMETRY type accordingly
insert_query = """
    INSERT INTO my_table (id, geom)
    VALUES (%(id)s, ST_GeomFromWKB(%(geom_wkb)s));
"""

# Build a list of execution parameters by iterating through the GeoDataFrame
# This is considered bad practice by the pandas community because it is slow.
params_list = [
    {
        "id": i,
        "geom_wkb": row["geom_wkb"]
    } for i, row in insert_gdf.iterrows()
]

# Connect to the database and make a cursor
conn = pg2.connect(host=<your host>, port=<your port>, dbname=<your dbname>, user=<your username>, password=<your password>)
cur = conn.cursor()

# Iterate through the list of execution parameters and apply them to an execution of the insert query
for params in params_list:
    cur.execute(insert_query, params)
conn.commit()
wfgeo
la source