Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
3a87dc9
Fix and improve C# Typemaps
Mbucari Aug 20, 2026
35e456a
Add obsolete messages for removed methods
Mbucari Aug 20, 2026
f0e6a40
Clean up SWIGTYPES and improve safety
Mbucari Aug 20, 2026
f54abd2
Use multi-argument typemap for Geometry wkb constructor
Mbucari Aug 21, 2026
701087d
Enable C# overloads for default arguments
Mbucari Aug 21, 2026
66a98ad
Rename wrapper methods to friendly names
Mbucari Aug 21, 2026
0c114b7
Mark static GCP property getters as obsolete
Mbucari Aug 21, 2026
560138d
Add C# field value getters and setters
Mbucari Aug 21, 2026
5405d9e
Add better DateTime support
Mbucari Aug 22, 2026
0068f42
Add C# tests for GDAL vector interface and new typemaps
Mbucari Aug 22, 2026
231f1d3
Extend and simplify C# RasterIO
Mbucari Aug 22, 2026
ea3650a
Add additional GDAL functions and helper functions to C#
Mbucari Aug 23, 2026
765ce46
Add C# support for OGRCodedValue (#10534)
Mbucari Aug 23, 2026
45c1597
Fix GetDefaultHistogram and AdviseRead for c#
Mbucari Aug 24, 2026
2c7afb7
Modernize RasterizeLayer C# binding
Mbucari Aug 24, 2026
d9acbb9
Enable C# defaultargs feature in SWIG
Mbucari Aug 24, 2026
b64a6ca
Update C# GetHistogram
Mbucari Aug 25, 2026
17569ee
Add Obsolete methods for Band Checksum and AdviseRead
Mbucari Aug 26, 2026
68244c4
Additional C# typemap simplification and new GDAL methods
Mbucari Aug 27, 2026
eaf67b0
Revise migration guide for C# user changes
Mbucari Aug 27, 2026
eb94da6
Additional tests for new C# bindings
Mbucari Aug 29, 2026
157746e
Expose Layer GetExtentEx and add Geometry helper methods
Mbucari Aug 30, 2026
c069c48
Merge branch 'OSGeo:master' into csharp_overhaul
Mbucari Oct 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions doc/source/user/migration_guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,20 @@ From GDAL 3.13 to GDAL 3.14
``gdal.DontUseExceptions()`` mode now also sets ``gdal.OF_VERBOSE_ERROR``
automatically, unless the new ``gdal.OF_SILENT_ERROR`` flag is set.

- Changes impacting C# users:

* Several call sites have been removed from from the C# bindings due to corrected and improved type mapping

+ The overloads of ``OSGeo.GDAL.Group.CreateAttribute``, ``OSGeo.GDAL.MDArray.CreateAttribute``, and ``OSGeo.GDAL.MDArray.Resize`` which accepted ``uint[]`` are no longer available due to that parameter being incorrectly type mapped to ``uint[]`` instead of ``ulong[]``.

+ The overload of ``OSGeo.GDAL.Transformer.TransformPoints`` which has parameter ``double[] panSuccess`` has been removed due to that parameter being incorrectly type mapped to ``double[]`` instead of ``int[]``.

+ ``Gdal.GCPsToHomography(int nGCPs, IntPtr pGCPs, double[] argout)`` has been removed due to the difficulty of requiring users to marshal their own ``GCP`` value arrays. It has been replaced with ``GCPsToHomography(GCP[] nGCPs, double[] argout)``.

+ ``Gdal.wrapper_GDALBuildVRT_objects`` and ``Gdal.wrapper_GDALMultiDimTranslateDestName`` have been removed due to the difficulty of requiring users to marshal their own `Dataset` reference arrays. They have been replaced with ``Dataset.BuildVRT`` and ``Dataset.MultiDimTranslate``, respectively.

+ The ``OSGeo.GDAL.MDArray.BuildOverviews`` method previously accepted a ``SWIGTYPE_p_int`` parameter argument but now takes an ``int[]``.

From GDAL 3.12 to GDAL 3.13
---------------------------

Expand Down
14 changes: 14 additions & 0 deletions swig/csharp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,14 @@ if (CSHARP_BUILD_SAMPLES)
SYSTEM_DEPENDS
OSGeo.GDAL)

gdal_build_csharp_sample(
OUTPUT GDALFieldValues.exe
SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/apps/GDALFieldValues.cs
DEPENDS
gdal_csharp
SYSTEM_DEPENDS
OSGeo.GDAL)

gdal_build_csharp_sample(
OUTPUT GDALDatasetWrite.exe
SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/apps/GDALDatasetWrite.cs
Expand Down Expand Up @@ -661,6 +669,7 @@ if (CSHARP_BUILD_SAMPLES)
OSGeo.GDAL.Samples.GDALAdjustContrast
OSGeo.GDAL.Samples.GDALDatasetRasterIO
OSGeo.GDAL.Samples.GDALWrite
OSGeo.GDAL.Samples.GDALFieldValues
OSGeo.GDAL.Samples.GDALDatasetWrite
OSGeo.GDAL.Samples.GDALColorTable
OSGeo.GDAL.Samples.WKT2WKB
Expand Down Expand Up @@ -707,6 +716,11 @@ if (CSHARP_RUN_TESTS)
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/GDALWrite/GDALWrite Data/sample.tif)
set_property(TEST csharp_gdalwrite PROPERTY ENVIRONMENT "${TEST_ENV}")
set_property(TEST csharp_gdalwrite PROPERTY DEPENDS csharp_createdata)
add_test(
NAME csharp_gdalfieldvalues
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/GDALFieldValues/GDALFieldValues)
set_property(TEST csharp_gdalfieldvalues PROPERTY ENVIRONMENT "${TEST_ENV}")
add_test(
NAME csharp_gdaldatasetwrite
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
Expand Down
40 changes: 16 additions & 24 deletions swig/csharp/apps/GDALDatasetRasterIOEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,15 @@ public static void usage()
System.Environment.Exit(-1);
}

public static int ProgressFunc(double Complete, IntPtr Message, IntPtr Data)
public static bool ProgressFunc(double Complete, string Message, TestData Data)
{
Console.Write("Processing ... " + Complete * 100 + "% Completed.");
if (Message != IntPtr.Zero)
Console.Write(" Message:" + System.Runtime.InteropServices.Marshal.PtrToStringAnsi(Message));
if (Data != IntPtr.Zero)
Console.Write(" Data:" + System.Runtime.InteropServices.Marshal.PtrToStringUni(Data));
Console.Write("\e[G\e[KProcessing ... " + Complete * 100 + "% Completed.");
if (Message != null)
Console.Write(" Message:" + Message);
if (Data != null)
Console.Write(" Data1:" + Data.Text1 + " Data2:" + Data.Text2);

Console.WriteLine("");
return 1;
return true;
}

private static RIOResampleAlg resampleAlg = RIOResampleAlg.GRIORA_NearestNeighbour;
Expand Down Expand Up @@ -291,21 +290,12 @@ private static void SaveBitmapDirect(string filename, Dataset ds, int xOff, int

using (RasterIOExtraArg arg = new RasterIOExtraArg())
{
GCHandle handle = GCHandle.Alloc("Test data", GCHandleType.Pinned);
try
{
arg.nVersion = argVersion;
arg.eResampleAlg = resampleAlg;
arg.pfnProgress = new Gdal.GDALProgressFuncDelegate(ProgressFunc);
arg.pProgressData = handle.AddrOfPinnedObject(); // or IntPtr.Zero if not data to be added;
arg.bFloatingPointWindowValidity = 0;
ds.ReadRaster(xOff, yOff, width, height, buf, imageWidth, imageHeight, dataType,
channelCount, bandMap, pixelSpace, stride, 1, arg);
}
finally
{
handle.Free();
}
arg.nVersion = argVersion;
arg.eResampleAlg = resampleAlg;
arg.SetProgressDelegate(ProgressFunc, new TestData("First Message", "Second Message"));
arg.bFloatingPointWindowValidity = 0;
ds.ReadRaster(xOff, yOff, width, height, buf, imageWidth, imageHeight, dataType,
channelCount, bandMap, pixelSpace, stride, 1, arg);
}
}
finally
Expand All @@ -314,6 +304,8 @@ private static void SaveBitmapDirect(string filename, Dataset ds, int xOff, int
}

bitmap.Save(filename);
ct.Dispose();
ct?.Dispose();
}
}

record TestData(string Text1, string Text2);
50 changes: 42 additions & 8 deletions swig/csharp/apps/GDALDatasetWrite.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
*****************************************************************************/

using System;
using System.Linq;
using System.Globalization;

using OSGeo.GDAL;
Expand Down Expand Up @@ -94,21 +95,54 @@ public static void Main(string[] args)

byte[] buffers = new byte[w * h * 3];

for (int i = 0; i < w; i++)
for (int y = 0; y < h; y++)
{
for (int j = 0; j < h; j++)
for (int x = 0; x < w; x++)
{
buffers[(i * w + j)] = (byte)(256 - i * 256 / w);
buffers[w * h + (i * w + j)] = 0;
buffers[2 * w * h + (i * w + j)] = (byte)(i * 256 / w);
//red
buffers[(y * w + x)] = ToByte(256 * x * (h - y) / (w * h));
//green
buffers[w * h + (y * w + x)] = ToByte(256 - 256 * (w - x) * y / (w * h));
//blue
buffers[2 * w * h + (y * w + x)] = ToByte(Random.Shared.Next(0, 256));
}
}

int[] iBandMap = { 1, 2, 3 };
ds.WriteRaster(0, 0, w, h, buffers, w, h, 3, iBandMap, 0, 0, 0);

ds.WriteRaster(0, 0, w, h, buffers, w, h, 3);
ds.FlushCache();

for (int i = 1; i <= ds.RasterCount; i++)
{
using var band = ds.GetRasterBand(i);
Console.WriteLine($"Band {i} Histogram (0 -> 255)");
Console.WriteLine($"================================");
DrawHistogram(band);
Console.WriteLine();
}
}
}
}

private static byte ToByte(double value, int min = byte.MinValue, int max = byte.MaxValue)
=> value < min ? (byte)min : value > max ? (byte)max : (byte)value;

private static void DrawHistogram(Band band)
{
const int height = 22;
const int width = 32;
const char block = '█';
int[] histogram = new int[width];
band.GetHistogram(histogram: histogram, approx_ok: false);

double value = histogram.Max();
double vStep = value / height;
for (int y = 0; y < height; y++, value -= vStep)
{
for (int x = 0; x < histogram.Length; x++)
{
Console.Write(histogram[x] >= value ? block : ' ');
}
Console.WriteLine();
}
}
}
210 changes: 210 additions & 0 deletions swig/csharp/apps/GDALFieldValues.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
/******************************************************************************
*
* Name: GDALFieldValues.cs
* Project: GDAL CSharp Interface
* Purpose: A sample app to demonstrate getting and setting field values of
* different types of field values.
* Author: Michael Bucari-Tovo, mbucari1@gmail.com
*
******************************************************************************
* Copyright (c) 2026, Michael Bucari-Tovo
*
* SPDX-License-Identifier: MIT
*****************************************************************************/

using OSGeo.GDAL;
using OSGeo.OGR;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

namespace TestGdalCSharp;

public static class GDALVector
{
const string UnicodeString = "Ĥĕļĺō Ŵŏŕľď";

public static async Task Main()
{
Console.OutputEncoding = System.Text.Encoding.UTF8;
Gdal.AllRegister();
using var reg = Gdal.GetGlobalAlgorithmRegistry();

Console.WriteLine("Listing all algorithms and their sub-algorithms with parameters:");
foreach (var alg in reg.Algorithms())
{
Console.WriteLine($" Algorithm: {alg.GetName()}");
foreach (var subAlg in alg.SubAlgorithms())
{
Console.WriteLine($" SubAlgorithm: {subAlg.GetName()}");
foreach (var arg in subAlg.Args().OfListType(true))
{
Console.WriteLine($" Parameter: {arg.GetName()}, Type: {Gdal.AlgorithmArgTypeName(arg.GetType_())}");
}
}
}

TestAlgorithmArgsIntegerList(reg);
TestAlgorithmArgsDoubleList(reg);
TestGdalVectorFieldsReadWrite();
}

private static void TestAlgorithmArgsDoubleList(AlgorithmRegistry reg)
{
Console.WriteLine("Testing algorithm arguments with double list type...");
var band = reg.Algorithms().OfName("raster").SubAlgorithms().OfName("footprint").Args().OfName("input-nodata");
var doubles = Enumerable.Range(0, 50).Select(_ => Random.Shared.NextDouble()).ToArray();
if (!band.SetAsDoubleList(doubles))
throw new Exception("Failed to set double list.");
band.GetAsDoubleList(out var outDoubles);
if (!doubles.SequenceEqual(outDoubles))
throw new Exception("Mismatch between set and retrieved double list.");
Console.WriteLine($"Successfully tested getting and setting algorithm arguments of type {Gdal.AlgorithmArgTypeName(band.GetType_())}.");
}
private static void TestAlgorithmArgsIntegerList(AlgorithmRegistry reg)
{
Console.WriteLine("Testing algorithm arguments with integer list type...");
var band = reg.Algorithms().OfName("raster").SubAlgorithms().OfName("as-features").Args().OfName("band");
var ints = Enumerable.Range(0, 50).Select(_ => Random.Shared.Next()).ToArray();
if (!band.SetAsIntegerList(ints))
throw new Exception("Failed to set integer list.");
band.GetAsIntegerList(out var outInts);
if (!ints.SequenceEqual(outInts))
throw new Exception("Mismatch between set and retrieved integer list.");
Console.WriteLine($"Successfully tested getting and setting algorithm arguments of type {Gdal.AlgorithmArgTypeName(band.GetType_())}.");
}
private static void TestGdalVectorFieldsReadWrite()
{
Console.WriteLine("Testing feature fields with various types...");
using var driver = Gdal.GetDriverByName("GeoJSON");
if (File.Exists("test.geojson"))
File.Delete("test.geojson");
using var ds = driver.CreateVector("test.geojson");

using var dLayer = ds.CreateLayer("double_layer");
CreateFieldDefn(dLayer, "int32_field", FieldType.OFTInteger);
CreateFieldDefn(dLayer, "int64_field", FieldType.OFTInteger64);
CreateFieldDefn(dLayer, "double_field", FieldType.OFTReal);
CreateFieldDefn(dLayer, "string_field", FieldType.OFTString);
CreateFieldDefn(dLayer, "date_time_field", FieldType.OFTDateTime);
CreateFieldDefn(dLayer, "int32_list_field", FieldType.OFTIntegerList);
CreateFieldDefn(dLayer, "int64_list_field", FieldType.OFTInteger64List);
CreateFieldDefn(dLayer, "double_list_field", FieldType.OFTRealList);
CreateFieldDefn(dLayer, "string_list_field", FieldType.OFTStringList);
CreateFieldDefn(dLayer, "binary_field", FieldType.OFTBinary);

using var dFeatureDef = dLayer.GetLayerDefn();
using var feature = new Feature(dFeatureDef);
TestField(
"int32_field",
Random.Shared.Next(),
feature.SetField,
feature.GetFieldAsInteger);

TestField(
"int64_field",
Random.Shared.NextInt64(),
feature.SetFieldInteger64,
feature.GetFieldAsInteger64);

TestField(
"double_field",
Random.Shared.NextDouble(),
feature.SetField,
feature.GetFieldAsDouble);

TestField(
"string_field",
UnicodeString,
feature.SetField,
feature.GetFieldAsString);

TestField(
"date_time_field",
DateTime.Now,
feature.SetField,
feature.GetFieldAsDateTime,
TimesEqualAtMilliseconds);

TestField(
"date_time_field",
DateTime.UtcNow,
feature.SetField,
feature.GetFieldAsDateTime,
TimesEqualAtMilliseconds);

TestField(
"int32_list_field",
Enumerable.Range(0, 50).Select(_ => Random.Shared.Next()).ToArray(),
feature.SetFieldIntegerList,
n => feature.GetFieldAsIntegerList(n, out var _),
(a, b) => a.SequenceEqual(b));

TestField(
"int64_list_field",
Enumerable.Range(0, 50).Select(_ => Random.Shared.NextInt64()).ToArray(),
feature.SetFieldInteger64List,
n => feature.GetFieldAsInteger64List(n, out var _),
(a, b) => a.SequenceEqual(b));

TestField(
"double_list_field",
Enumerable.Range(0, 50).Select(_ => Random.Shared.NextDouble()).ToArray(),
feature.SetFieldDoubleList,
n => feature.GetFieldAsDoubleList(n, out var _),
(a, b) => a.SequenceEqual(b));

TestField(
"string_list_field",
Enumerable.Range(0, 50).Select(_ => Random.Shared.Next().ToString(CultureInfo.InvariantCulture)).ToArray(),
feature.SetFieldStringList,
feature.GetFieldAsStringList,
(a, b) => a.SequenceEqual(b));

TestField(
"binary_field",
Enumerable.Range(0, 50).Select(_ => (byte)Random.Shared.Next(0, 256)).ToArray(),
feature.SetFieldBinary,
n => feature.GetFieldAsBinary(n, out var _),
(a, b) => a.SequenceEqual(b));
}
private static void CreateFieldDefn(Layer dLayer, string fieldName, FieldType type)
{
using var fieldDef = new FieldDefn(fieldName, type);
if (dLayer.CreateField(fieldDef, 1) != 0)
throw new Exception("Failed to create a field definition on layer.");
}
private static void TestField<T>(string fieldName, T value, Action<string, T> setField, Func<string, T> getField, Func<T, T, bool> equals = null)
{
Console.WriteLine($" Testing field '{fieldName}' with type {typeof(T).Name}...");
Console.WriteLine($" Setting '{fieldName}'");
setField(fieldName, value);
Console.WriteLine($" Getting '{fieldName}'");
var valuesGet = getField(fieldName);
Console.WriteLine($" Comparing '{fieldName}' set values with retrieved values");
if (!equals?.Invoke(value, valuesGet) ?? !value.Equals(valuesGet))
throw new Exception($"Mismatch between '{fieldName}' set and retrieved values.");

Console.WriteLine($" Successfully set and got value from '{fieldName}'");
}
private static bool TimesEqualAtMilliseconds(DateTime a, DateTime b)
=> a.Ticks / 10_000 == b.Ticks / 10_000 && a.Kind == b.Kind;

public static AlgorithmArg OfName(this IEnumerable<AlgorithmArg> algorithms, string name)
=> algorithms.Single(arg => arg.GetName() == name);
public static IEnumerable<AlgorithmArg> OfTypeName(this IEnumerable<AlgorithmArg> algorithms, string typeName)
=> algorithms.Where(arg => Gdal.AlgorithmArgTypeName(arg.GetType_()) == typeName);
public static IEnumerable<AlgorithmArg> OfListType(this IEnumerable<AlgorithmArg> algorithms, bool isList)
=> algorithms.Where(arg => Gdal.AlgorithmArgTypeIsList(arg.GetType_()) == isList);
public static IEnumerable<AlgorithmArg> Args(this Algorithm algorithm)
=> algorithm.GetArgNames().Select(argName => algorithm.GetArg(argName));
public static Algorithm OfName(this IEnumerable<Algorithm> algorithms, string name)
=> algorithms.Single(alg => alg.GetName() == name);
public static IEnumerable<Algorithm> SubAlgorithms(this Algorithm algorithm)
=> algorithm.HasSubAlgorithms() ? algorithm.GetSubAlgorithmNames().Select(name => algorithm.InstantiateSubAlgorithm(name)) : Enumerable.Empty<Algorithm>();
public static IEnumerable<Algorithm> Algorithms(this AlgorithmRegistry reg)
=> reg.GetAlgNames().Select(algName => reg.InstantiateAlg(algName));
}
Loading