Import GeoJSON files into a PostGIS database

python
gis
Published

April 11, 2025

When importing data in a GeoJSON file to a PostGIS database, we will do so using Python and apply the following process:

  1. import the data into a GeoDataFrame using geopandas, specifically the read_file method.
  2. write the GeoDataFrame to a PostGIS databse using the to_postgis method in geopandas.

The example below illustrates in detail how this can be achieved.

Read data to a GeoDataFrame

The data is available here.
To work directly with geopandas, we will download the data in GeoJSON format.

import pandas as pd
import geopandas as gpd

gdf = gpd.read_file('data/brisbane-park-locations.geojson')
gdf.head(5)

Write to PostGIS

geopandas can now write gdf to a PostGIS with its to_postgis method. As mentioned in the documentation, this method requires SQLAlchemy, GeoAlchemy2 and a PostgreSQL Python driver to be installed. If not already present, these can be installed as follows:

uv add sqlalchemy geoalchemy2 psycopg[binary]

Note that since we’re using psycopg (version 3), and sqlalchemy uses psycopg2 as default, we need to change the driver name from postgresql to postgresql+psycopg. See this StackOverflow answer.

from sqlalchemy import create_engine

user = 'sandeep'
host = 'localhost'
database = 'brisbane'
driver = 'postgresql+psycopg'
connection_str = f'{driver}://{user}@{host}/{database}'

engine = create_engine(connection_str)

Now that we have the engine setup, we can write gdf to the PostGIS database.

gdf.to_postgis(name='parks', con=engine, if_exists='fail')

Verify that the table has been written to the database

Finally, we can use psycopg to verify that the data has been copied to PostGIS.

import psycopg

with psycopg.connect("dbname=brisbane user=sandeep") as conn:
    with conn.cursor() as cur:
        cur.execute("SELECT * FROM parks LIMIT 10")
        print(cur.fetchone())
('1', 'D2496', "O'REILLY STREET PARK", 'B-RE-0337', '10', "O'REILLY STREET", 'WAKERLEY', '4154', 153.15384614879127, -27.477642702086637, 10020.313720703125, 439.2917820727244, '{ "lon": 153.15384614879127, "lat": -27.477642702086637 }', '10000+ sq metres', 1, 'W, WAKERLEY', "O, O'REILLY STREET PARK", '0103000020E61000000100000019000000CC7D63FFF124634048B12FFE547A3BC01A5AABB8F22463403859CF0E637A3BC034B5EB3FF02463409E345309607A3BC062992F63EE24634042A971C25D7A3BC092DA2F9AEC246340723AD0935B7A3BC0DD89B79AEB2463405F48735A5A7A3BC045D32BD1EA24634022391265597A3BC07A483308E9246340C1CA6D36577A3BC022FA233FE72463402FCF9107557A3BC0B1330BF2E5246340FD197170537A3BC01D82D743E624634097F8D011467A3BC00CDB5F7EE6246340363030803C7A3BC03093E3B5E624634014252E6D337A3BC0AEF0A3EEE62463403EBBEF252A7A3BC066879BB0E8246340755D51492C7A3BC03DCCA76DEA246340B498D0662E7A3BC08DABF3FDEA2463400BD76F6D2F7A3BC032CFC782EB246340239CB195307A3BC03798DB73EC2463401890D1E2327A3BC08092878BEC246340D0BB91FF327A3BC054937B24F22463402571D3CF397A3BC0BFFE5F60F2246340FBA32E943A7A3BC025C37F86F22463401FFB0F143C7A3BC022AC978CF2246340E0EA51E83D7A3BC0CC7D63FFF124634048B12FFE547A3BC0')

Complete the round-trip

Now, we’ll read the data from PostGIS into a GeoDataFrame.

sql = 'SELECT park_number, park_name, geometry FROM parks'
parks = gpd.read_postgis(sql, engine, geom_col='geometry')
parks.plot(color='green')