Gdal.Npgsql 8.2.0

dotnet add package Gdal.Npgsql --version 8.2.0
                    
NuGet\Install-Package Gdal.Npgsql -Version 8.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Gdal.Npgsql" Version="8.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Gdal.Npgsql" Version="8.2.0" />
                    
Directory.Packages.props
<PackageReference Include="Gdal.Npgsql" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Gdal.Npgsql --version 8.2.0
                    
#r "nuget: Gdal.Npgsql, 8.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Gdal.Npgsql@8.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Gdal.Npgsql&version=8.2.0
                    
Install as a Cake Addin
#tool nuget:?package=Gdal.Npgsql&version=8.2.0
                    
Install as a Cake Tool

The package is an Npgsql plugin that allows you to interact with spatial data provided by the PostgreSQL PostGIS extension. On the .NET side, the plugin adds support for geometry types from the GDAL(OGR) library, allowing you to read and write them directly to PostgreSQL.

GDAL is connected via the nuget package MaxRev.Gdal.Core

Usage

Add a dependency on this package, then register the plugin on an NpgsqlDataSource. PostGIS geometry and geography columns — and arrays of them — are mapped to PgGeometry.

using Npgsql;
using Npgsql.Gdal;
using OSGeo.OGR;

var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString);

dataSourceBuilder.UseGdal();

var dataSource = dataSourceBuilder.Build();
using (var conn = await dataSource.OpenConnectionAsync())
{
    using var point = Geometry.CreateFromWkt("POINT (30 10)");

    using (var cmd = new NpgsqlCommand("INSERT INTO data (geom) VALUES (@p)", conn))
    {
        cmd.Parameters.AddWithValue("@p", PgGeometry.FromGeometry(point, srid: 4326));
        cmd.ExecuteNonQuery();
    }

    using (var cmd = new NpgsqlCommand("SELECT geom FROM data", conn))
    using (var reader = cmd.ExecuteReader())
    {
        reader.Read();
        var value = reader.GetFieldValue<PgGeometry>(0);

        Console.WriteLine(value.GeometryType);   // no OGR object is created
        Console.WriteLine(value.SRID);

        using var geometry = value.CreateGeometry();   // you own it, you dispose it
    }
}

Creating a geometry, and getting the bytes back

PgGeometry always holds the geometry as EWKB internally, whichever way it was built. These members cover the conversions:

in out
WKB, SRID taken from the bytes FromWkb(byte[] wkb) GetWkb() — without the SRID
WKB, SRID stated by the caller FromWkb(byte[] wkb, int srid)
EWKB, SRID carried by the bytes FromEWkb(byte[] ewkb) GetEWkb() — with the SRID
OGR geometry FromGeometry(Geometry g, int srid = 0) CreateGeometry()

All of the inputs accept either encoding of Z and M — the ISO form (+1000/+2000/+3000 on the geometry type) and the flag form PostGIS uses — and either byte order. Whatever comes in is converted to EWKB right away, so the encoding of an existing PgGeometry never depends on how it was made. Byte order is preserved.

The one-argument FromWkb takes the SRID from the bytes and settles on 0 when they carry none, so it accepts anything without complaint; FromEWkb is the same operation under a name that says what the bytes are.

Naming an SRID in the second argument is an assertion about the bytes, not an override. Bytes that carry no SRID get the one you name; bytes that carry a different one raise ArgumentException rather than silently picking a winner. That applies to 0 as well — FromWkb(bytes, 0) asserts the geometry has no SRID, which is why "don't care" is a separate overload rather than a default value.

var fromDatabaseBytes = PgGeometry.FromWkb(ewkb);          // SRID: whatever the bytes say
var withKnownSrid     = PgGeometry.FromWkb(wkb, 4326);     // attaches 4326, or throws on a clash

byte[] ewkbOut  = withKnownSrid.GetEWkb();   // for PostGIS: carries the SRID
byte[] plainOut = withKnownSrid.GetWkb();    // for everything else: no SRID
int srid        = withKnownSrid.SRID;

GetWkb() and GetEWkb() both return a fresh array every time, so the caller is free to keep or modify what they get.

Bulk insert

GetEWkb() is what a bulk insert needs when the geometries do not go through Npgsql's own type mapping — for example when they are copied into a bytea staging column and turned into geometries on the server:

using (var writer = await conn.BeginBinaryImportAsync(
           "COPY staging (id, ewkb) FROM STDIN (FORMAT BINARY)"))
{
    foreach (var (id, geometry) in rows)
    {
        await writer.StartRowAsync();
        await writer.WriteAsync(id);
        await writer.WriteAsync(PgGeometry.FromGeometry(geometry, srid: 4326).GetEWkb());
    }

    await writer.CompleteAsync();
}

// INSERT INTO data (id, geom) SELECT id, ST_GeomFromEWKB(ewkb) FROM staging;

When the target column is a geometry and the plugin is registered, a PgGeometry can also be written to the COPY stream directly, without touching the bytes at all.

Why PgGeometry and not Geometry

OSGeo.OGR.Geometry wraps native memory and has to be disposed. If a query result were mapped straight to it, every geometry read from the database would be created with no owner — including the ones your code never looks at. Their memory is invisible to the .NET garbage collector, so it is released late and unpredictably.

PgGeometry holds the geometry as EWKB bytes instead:

  • reading a row costs a buffer copy, and no native object is created at all;
  • CreateGeometry() builds the OGR object only when you ask for one, and the caller owns it;
  • FromGeometry(geometry, srid) serialises immediately — the geometry you pass stays yours;
  • GetEWkb() hands you the PostGIS wire encoding when you need to feed the bytes to the server yourself, and GetWkb() the same geometry without the SRID;
  • the type is immutable, so it is safe to cache, store and pass between threads;
  • the database path never touches native GDAL, so reading and writing works without GdalBase.ConfigureAll(). Only CreateGeometry() requires the native libraries.

PgGeometry accepts both the ISO and the PostGIS encoding of Z and M on input, and preserves whatever byte order was used.

Migration from 8.0.x

Version 8.2.0 is a breaking change.

8.0.x 8.1.0
UseGdal() mapping to OSGeo.OGR.Geometry removed — use UseGdal() with PgGeometry
UseGdalExt() removed — UseGdal() now registers PgGeometry
ExtGeometry PgGeometry
ExtGeometry.Create(geometry, srid) PgGeometry.FromGeometry(geometry, srid)
extGeometry.Geometry pgGeometry.CreateGeometry() — returns a new object you must dispose
ExtGeometry.SRID (int?, null when unset) PgGeometry.SRID (int, 0 when unset)
— PgGeometry.FromWkb(wkb[, srid]) / FromEWkb(ewkb) / GetWkb() / GetEWkb()

Reading a column now looks like this:

// 8.0.x
var eg = (reader[0] as ExtGeometry)!;
DoSomething(eg.Geometry);          // never disposed

// 8.1.0
var value = reader.GetFieldValue<PgGeometry>(0);
using var geometry = value.CreateGeometry();
DoSomething(geometry);

Fixed along the way:

  • M values of POINTM/LINESTRINGM geometries were written as 0;
  • the SRID of a geometry was read from the database but never written back to it;
  • array mappings (geometry[], geography[]) were registered but never took effect;
  • rings and sub-geometries were cloned and leaked while reading collections.
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
8.2.0 137 9/1/2026
8.1.0 113 9/1/2026
8.0.3.2 698 7/15/2024
8.0.3.1 240 7/15/2024
8.0.3 253 7/15/2024
8.0.2.2 255 5/3/2024
8.0.2.1 249 5/3/2024
8.0.2 234 5/3/2024

8.2.0 is a breaking change. ExtGeometry and the direct mapping to OSGeo.OGR.Geometry are replaced by the immutable PgGeometry, which holds the geometry as EWKB bytes and creates an OGR object only when explicitly asked. Conversions go through FromWkb/FromEWkb/FromGeometry on the way in and GetWkb/GetEWkb/CreateGeometry on the way out. Fixes M values being written as zero, the SRID never being written back to the database, and array mappings never taking effect. See the Migration section in the README.