PostGIS

This page explores PostGIS functionality. Given that the back-end in use in this app is Django, it is necessary to use some Django functionality that enables the direct querying of the backend database (rather than using the in-built Django ORM). The first two examples examine different ways in which to directly perform SQL queries in Django (a further alternative not discussed here would be to use db stored views). The latter examples focus more on PostGIS itself.

The Django documentation can be found here: https://docs.djangoproject.com/en/5.1/topics/db/sql/
The PostGIS documentation can be found here: https://postgis.net/docs/manual-3.5/reference.html

The datasets and examples used here are drawn primarily from the training materials from the PostGIS website. The documents and datasets can be found here: https://postgis.net/workshops/postgis-intro/


Executing SQL in Django (raw() manager)

The raw() manager method allows for SQL statements to be executed while returning a Django queryset in response.
This allows for basic direct SQL querying although it pushes you towards using the ORM.

    
    manhattan_subway_stations = NycSubwayStations.objects.raw(
        "SELECT * \
        FROM geo_nycsubwaystations \
        WHERE borough='Manhattan';"
    )
       

Executing SQL in Django (cursor)

With this approach a direct connection to the database is opened. A cursor contains a tuple of the rows returned from the SQL query. There are two options for returning the row(s) - cursor.fetchone() and cursor.fetchall().

The response is a raw tuple of row data with no reference to the field names. In the example below the field names are sought from the cursor and then these are zipped into a dictionary to ease later manipulation. Note the Django documentation above also gives the option to use a namedtuple instead of a dictionary.

NOTE: the geometry fields in this instance are returned as hex and therefore need to be converted into GEOSGeometry objects.

    
    with connection.cursor() as cursor:

        cursor.execute(
            "SELECT * \
            FROM geo_nycsubwaystations \
            WHERE borough='Manhattan';"
        )
        # get the column names 
        columns = [col[0] for col in cursor.description]

        # retrieve data and zip with the column names in a dictionary
        manhattan_subway_stations = [
            dict(zip(columns, row)) for row in cursor.fetchall()
            ]
       

Basic PostGIS - ST_Area()

In this simple test example the ST_Area() function is used to return the area of the census blocks in Manhattan. This returns a single tuple (row) containing the result. Note the division to get the value in acres.

    
    with connection.cursor() as cursor:

        cursor.execute(
            """ 
            SELECT Sum(ST_Area(geom)) / 4047 \
            FROM geo_nyccensusblocks \
            WHERE boroname = 'Manhattan';
            """
        )
        row = cursor.fetchone()
       

Tabulated Data - ST_Length()

In this example ST_Length is used to find the length of the roads in New York grouped by type of road. The response here is a tabulated list of rows with no geospatial data.

    
    SELECT type, Sum(ST_Length(geom)) AS length \
    FROM geo_nycstreets \
    GROUP BY type \
    ORDER BY length DESC;
       

Spatial Join ST_Contains()

This example, drawn from the postgis tutorial, uses a spatial join on the neighbourhoods and subways geometries to to return the specific details (borough and neighbourhood) for the selected station.

    
    SELECT
        subways.name AS subway_name,
        neighbourhoods.name AS neighborhood_name,
        neighbourhoods.boroname AS borough
    FROM geo_nycneighbourhoods AS neighbourhoods
    JOIN geo_nycsubwaystations AS subways
    ON ST_Contains(neighbourhoods.geom, subways.geom)
    WHERE subways.name = 'Broad St';
       

Spatial Query ST_Intersect

In this example the the subway stations table is joined with the neighbourhoods table. A neighbourhood is selected ('Downtown') and then the ST_Intersects function is used to return the subway stations within this neighbourhood.

    
    SELECT
        subways.name AS subway_name,
        subways.geom AS subway_geom
    FROM geo_nycsubwaystations AS subways
    JOIN geo_nycneighbourhoods AS neighbourhoods
    ON ST_Intersects(neighbourhoods.geom, subways.geom)
    WHERE neighbourhoods.name = 'Downtown';