第09章 - C#/.NET绑定开发指南
第09章:C#/.NET绑定开发指南
9.1 .NET GDAL 简介
9.1.1 概述
GDAL 为 .NET 平台提供了完整的绑定支持,允许 C# 开发者在 Windows 和跨平台环境中使用 GDAL 的全部功能。.NET GDAL 绑定特别适合企业级 Windows 应用开发。
┌─────────────────────────────────────────────────────────────┐
│ .NET GDAL 架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ .NET 应用层 │ │
│ │ WPF/WinForms │ ASP.NET │ 控制台应用 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ GDAL .NET 绑定 │ │
│ │ OSGeo.GDAL │ OSGeo.OGR │ OSGeo.OSR │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ P/Invoke │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ GDAL 本地库 (C/C++) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
9.1.2 可用的 NuGet 包
| 包名 | 描述 | 平台支持 |
|---|---|---|
| MaxRev.Gdal.Universal | 自动配置,跨平台支持 | Windows, Linux, macOS |
| MaxRev.Gdal.WindowsRuntime.Minimal | Windows 最小运行时 | Windows |
| MaxRev.Gdal.LinuxRuntime.Minimal | Linux 最小运行时 | Linux |
| GDAL.Native | 官方原生包 | 平台特定 |
9.2 环境配置
9.2.1 创建项目
# 创建新项目
dotnet new console -n GdalDemo -f net6.0
cd GdalDemo
# 添加 NuGet 包
dotnet add package MaxRev.Gdal.Universal
9.2.2 项目文件配置
<!-- GdalDemo.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MaxRev.Gdal.Universal" Version="3.7.0.100" />
</ItemGroup>
</Project>
9.2.3 初始化 GDAL
using MaxRev.Gdal.Core;
using OSGeo.GDAL;
using OSGeo.OGR;
using OSGeo.OSR;
namespace GdalDemo;
/// <summary>
/// GDAL 初始化帮助类
/// </summary>
public static class GdalInitializer
{
private static bool _initialized = false;
private static readonly object _lock = new object();
/// <summary>
/// 初始化 GDAL
/// </summary>
public static void Initialize()
{
if (_initialized) return;
lock (_lock)
{
if (_initialized) return;
// 使用 MaxRev.Gdal 自动配置
GdalBase.ConfigureAll();
// 可选:设置配置选项
Gdal.SetConfigOption("GDAL_FILENAME_IS_UTF8", "YES");
Gdal.SetConfigOption("SHAPE_ENCODING", "UTF-8");
// 打印版本信息
Console.WriteLine($"GDAL 版本: {Gdal.VersionInfo("VERSION_NUM")}");
Console.WriteLine($"栅格驱动数: {Gdal.GetDriverCount()}");
Console.WriteLine($"矢量驱动数: {Ogr.GetDriverCount()}");
_initialized = true;
}
}
/// <summary>
/// 列出所有驱动
/// </summary>
public static void ListDrivers()
{
Initialize();
Console.WriteLine("\n栅格驱动:");
for (int i = 0; i < Gdal.GetDriverCount(); i++)
{
using var driver = Gdal.GetDriver(i);
Console.WriteLine($" {driver.ShortName}: {driver.LongName}");
}
Console.WriteLine("\n矢量驱动:");
for (int i = 0; i < Ogr.GetDriverCount(); i++)
{
using var driver = Ogr.GetDriver(i);
Console.WriteLine($" {driver.GetName()}");
}
}
}
9.3 栅格数据处理
9.3.1 读取栅格数据
using OSGeo.GDAL;
using OSGeo.OSR;
namespace GdalDemo.Raster;
/// <summary>
/// 栅格数据读取器
/// </summary>
public class RasterReader
{
/// <summary>
/// 读取栅格数据信息
/// </summary>
public static void ReadRasterInfo(string filepath)
{
GdalInitializer.Initialize();
using var ds = Gdal.Open(filepath, Access.GA_ReadOnly);
if (ds == null)
{
Console.WriteLine($"无法打开文件: {filepath}");
return;
}
// 基本信息
Console.WriteLine($"文件: {ds.GetDescription()}");
Console.WriteLine($"驱动: {ds.GetDriver().ShortName}");
Console.WriteLine($"尺寸: {ds.RasterXSize} x {ds.RasterYSize}");
Console.WriteLine($"波段数: {ds.RasterCount}");
// 地理变换
double[] geoTransform = new double[6];
ds.GetGeoTransform(geoTransform);
Console.WriteLine($"原点: ({geoTransform[0]}, {geoTransform[3]})");
Console.WriteLine($"像素大小: ({geoTransform[1]}, {geoTransform[5]})");
// 投影信息
string projection = ds.GetProjection();
if (!string.IsNullOrEmpty(projection))
{
using var srs = new SpatialReference(projection);
Console.WriteLine($"坐标系: {srs.GetName()}");
}
// 波段信息
for (int i = 1; i <= ds.RasterCount; i++)
{
using var band = ds.GetRasterBand(i);
Console.WriteLine($"\n波段 {i}:");
Console.WriteLine($" 数据类型: {Gdal.GetDataTypeName(band.DataType)}");
double nodata;
int hasNodata;
band.GetNoDataValue(out nodata, out hasNodata);
if (hasNodata != 0)
{
Console.WriteLine($" 无效值: {nodata}");
}
double min, max, mean, stddev;
band.GetStatistics(0, 1, out min, out max, out mean, out stddev);
Console.WriteLine($" 统计: min={min:F2}, max={max:F2}, mean={mean:F2}, std={stddev:F2}");
}
}
/// <summary>
/// 读取波段数据为数组
/// </summary>
public static double[,] ReadBandAsArray(string filepath, int bandIndex = 1)
{
GdalInitializer.Initialize();
using var ds = Gdal.Open(filepath, Access.GA_ReadOnly);
if (ds == null)
{
throw new Exception($"无法打开文件: {filepath}");
}
int width = ds.RasterXSize;
int height = ds.RasterYSize;
using var band = ds.GetRasterBand(bandIndex);
// 读取为一维数组
double[] data = new double[width * height];
band.ReadRaster(0, 0, width, height, data, width, height, 0, 0);
// 转换为二维数组
double[,] result = new double[height, width];
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
result[y, x] = data[y * width + x];
}
}
return result;
}
/// <summary>
/// 分块读取栅格数据
/// </summary>
public static void ReadInBlocks(string filepath, Action<int, int, int, int, double[]> processor)
{
GdalInitializer.Initialize();
using var ds = Gdal.Open(filepath, Access.GA_ReadOnly);
if (ds == null)
{
throw new Exception($"无法打开文件: {filepath}");
}
int width = ds.RasterXSize;
int height = ds.RasterYSize;
using var band = ds.GetRasterBand(1);
int blockWidth, blockHeight;
band.GetBlockSize(out blockWidth, out blockHeight);
Console.WriteLine($"块大小: {blockWidth} x {blockHeight}");
for (int y = 0; y < height; y += blockHeight)
{
for (int x = 0; x < width; x += blockWidth)
{
int actualWidth = Math.Min(blockWidth, width - x);
int actualHeight = Math.Min(blockHeight, height - y);
double[] data = new double[actualWidth * actualHeight];
band.ReadRaster(x, y, actualWidth, actualHeight, data, actualWidth, actualHeight, 0, 0);
processor(x, y, actualWidth, actualHeight, data);
}
}
}
}
9.3.2 创建栅格数据
using OSGeo.GDAL;
using OSGeo.OSR;
namespace GdalDemo.Raster;
/// <summary>
/// 栅格数据写入器
/// </summary>
public class RasterWriter
{
/// <summary>
/// 创建 GeoTIFF 文件
/// </summary>
public static void CreateGeoTiff(
string filepath,
int width,
int height,
int bands,
DataType dataType,
double[] geoTransform,
int epsg,
double[,]? data = null)
{
GdalInitializer.Initialize();
using var driver = Gdal.GetDriverByName("GTiff");
if (driver == null)
{
throw new Exception("GTiff 驱动不可用");
}
// 创建选项
string[] options = new string[]
{
"COMPRESS=LZW",
"TILED=YES",
"BLOCKXSIZE=256",
"BLOCKYSIZE=256"
};
using var ds = driver.Create(filepath, width, height, bands, dataType, options);
if (ds == null)
{
throw new Exception($"无法创建文件: {filepath}");
}
// 设置地理变换
if (geoTransform != null)
{
ds.SetGeoTransform(geoTransform);
}
// 设置投影
if (epsg > 0)
{
using var srs = new SpatialReference("");
srs.ImportFromEPSG(epsg);
ds.SetProjection(srs.ExportToWkt());
}
// 写入数据
if (data != null)
{
using var band = ds.GetRasterBand(1);
// 转换为一维数组
double[] flatData = new double[width * height];
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
flatData[y * width + x] = data[y, x];
}
}
band.WriteRaster(0, 0, width, height, flatData, width, height, 0, 0);
band.SetNoDataValue(-9999);
band.ComputeStatistics(false, out _, out _, out _, out _, null, null);
}
ds.FlushCache();
Console.WriteLine($"创建完成: {filepath}");
}
/// <summary>
/// 创建示例 DEM
/// </summary>
public static void CreateSampleDEM(string filepath)
{
int width = 1000;
int height = 1000;
// 生成模拟高程数据
double[,] data = new double[height, width];
Random rand = new Random();
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
data[y, x] = 100 +
50 * Math.Sin(x * Math.PI / 100) * Math.Cos(y * Math.PI / 100) +
rand.NextDouble() * 10;
}
}
// 地理变换
double[] geoTransform = new double[]
{
116.0, // 原点X
0.001, // 像素宽度
0, // 旋转
40.0, // 原点Y
0, // 旋转
-0.001 // 像素高度
};
CreateGeoTiff(filepath, width, height, 1, DataType.GDT_Float32,
geoTransform, 4326, data);
}
/// <summary>
/// 创建 RGB 彩色影像
/// </summary>
public static void CreateRGBImage(
string filepath,
int width,
int height,
byte[,] red,
byte[,] green,
byte[,] blue,
double[] geoTransform,
int epsg)
{
GdalInitializer.Initialize();
using var driver = Gdal.GetDriverByName("GTiff");
string[] options = new string[]
{
"COMPRESS=LZW",
"PHOTOMETRIC=RGB"
};
using var ds = driver.Create(filepath, width, height, 3, DataType.GDT_Byte, options);
if (geoTransform != null)
{
ds.SetGeoTransform(geoTransform);
}
if (epsg > 0)
{
using var srs = new SpatialReference("");
srs.ImportFromEPSG(epsg);
ds.SetProjection(srs.ExportToWkt());
}
// 写入各波段
byte[][] bandData = new byte[][] {
FlattenArray(red),
FlattenArray(green),
FlattenArray(blue)
};
ColorInterp[] colorInterps = new ColorInterp[]
{
ColorInterp.GCI_RedBand,
ColorInterp.GCI_GreenBand,
ColorInterp.GCI_BlueBand
};
for (int i = 0; i < 3; i++)
{
using var band = ds.GetRasterBand(i + 1);
band.WriteRaster(0, 0, width, height, bandData[i], width, height, 0, 0);
band.SetColorInterpretation(colorInterps[i]);
}
ds.FlushCache();
}
private static byte[] FlattenArray(byte[,] array)
{
int height = array.GetLength(0);
int width = array.GetLength(1);
byte[] result = new byte[width * height];
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
result[y * width + x] = array[y, x];
}
}
return result;
}
}
9.3.3 栅格处理操作
using OSGeo.GDAL;
namespace GdalDemo.Raster;
/// <summary>
/// 栅格处理操作
/// </summary>
public class RasterProcessor
{
/// <summary>
/// 重投影栅格
/// </summary>
public static void Reproject(string srcPath, string dstPath, string dstSRS)
{
GdalInitializer.Initialize();
using var srcDs = Gdal.Open(srcPath, Access.GA_ReadOnly);
if (srcDs == null)
{
throw new Exception($"无法打开文件: {srcPath}");
}
var options = new GDALWarpAppOptions(new string[]
{
"-t_srs", dstSRS,
"-r", "bilinear",
"-co", "COMPRESS=LZW",
"-co", "TILED=YES"
});
using var dstDs = Gdal.Warp(dstPath, new Dataset[] { srcDs }, options, null, null);
Console.WriteLine($"重投影完成: {dstPath}");
}
/// <summary>
/// 裁剪栅格
/// </summary>
public static void Clip(string srcPath, string dstPath,
double minX, double minY, double maxX, double maxY)
{
GdalInitializer.Initialize();
using var srcDs = Gdal.Open(srcPath, Access.GA_ReadOnly);
var options = new GDALTranslateOptions(new string[]
{
"-projwin", minX.ToString(), maxY.ToString(), maxX.ToString(), minY.ToString(),
"-co", "COMPRESS=LZW"
});
using var dstDs = Gdal.Translate(dstPath, srcDs, options, null, null);
Console.WriteLine($"裁剪完成: {dstPath}");
}
/// <summary>
/// 使用矢量边界裁剪
/// </summary>
public static void ClipByVector(string srcPath, string dstPath, string cutlinePath)
{
GdalInitializer.Initialize();
using var srcDs = Gdal.Open(srcPath, Access.GA_ReadOnly);
var options = new GDALWarpAppOptions(new string[]
{
"-cutline", cutlinePath,
"-crop_to_cutline",
"-dstnodata", "-9999",
"-co", "COMPRESS=LZW"
});
using var dstDs = Gdal.Warp(dstPath, new Dataset[] { srcDs }, options, null, null);
Console.WriteLine($"裁剪完成: {dstPath}");
}
/// <summary>
/// 镶嵌多个栅格
/// </summary>
public static void Mosaic(string[] srcPaths, string dstPath)
{
GdalInitializer.Initialize();
// 打开所有源数据集
Dataset[] srcDatasets = new Dataset[srcPaths.Length];
for (int i = 0; i < srcPaths.Length; i++)
{
srcDatasets[i] = Gdal.Open(srcPaths[i], Access.GA_ReadOnly);
if (srcDatasets[i] == null)
{
throw new Exception($"无法打开文件: {srcPaths[i]}");
}
}
try
{
// 创建 VRT
var vrtOptions = new GDALBuildVRTOptions(Array.Empty<string>());
using var vrtDs = Gdal.BuildVRT("", srcDatasets, vrtOptions, null, null);
// 转换为 GeoTIFF
var translateOptions = new GDALTranslateOptions(new string[]
{
"-co", "COMPRESS=LZW",
"-co", "TILED=YES",
"-co", "BIGTIFF=IF_SAFER"
});
using var dstDs = Gdal.Translate(dstPath, vrtDs, translateOptions, null, null);
Console.WriteLine($"镶嵌完成: {dstPath}");
}
finally
{
foreach (var ds in srcDatasets)
{
ds?.Dispose();
}
}
}
/// <summary>
/// 格式转换
/// </summary>
public static void Convert(string srcPath, string dstPath, string format)
{
GdalInitializer.Initialize();
using var srcDs = Gdal.Open(srcPath, Access.GA_ReadOnly);
var options = new GDALTranslateOptions(new string[]
{
"-of", format
});
using var dstDs = Gdal.Translate(dstPath, srcDs, options, null, null);
Console.WriteLine($"转换完成: {dstPath}");
}
/// <summary>
/// 创建金字塔
/// </summary>
public static void BuildOverviews(string filepath, int[] levels)
{
GdalInitializer.Initialize();
using var ds = Gdal.Open(filepath, Access.GA_Update);
if (ds == null)
{
throw new Exception($"无法打开文件: {filepath}");
}
if (levels == null || levels.Length == 0)
{
levels = new int[] { 2, 4, 8, 16, 32 };
}
ds.BuildOverviews("AVERAGE", levels, null, null);
Console.WriteLine($"金字塔创建完成: {filepath}");
}
}
9.4 矢量数据处理
9.4.1 读取矢量数据
using OSGeo.OGR;
using OSGeo.OSR;
namespace GdalDemo.Vector;
/// <summary>
/// 矢量数据读取器
/// </summary>
public class VectorReader
{
/// <summary>
/// 读取矢量数据信息
/// </summary>
public static void ReadVectorInfo(string filepath)
{
GdalInitializer.Initialize();
using var ds = Ogr.Open(filepath, 0);
if (ds == null)
{
Console.WriteLine($"无法打开文件: {filepath}");
return;
}
Console.WriteLine($"数据源: {ds.GetDescription()}");
Console.WriteLine($"图层数: {ds.GetLayerCount()}");
for (int i = 0; i < ds.GetLayerCount(); i++)
{
using var layer = ds.GetLayerByIndex(i);
Console.WriteLine($"\n图层 {i}: {layer.GetName()}");
Console.WriteLine($" 要素数: {layer.GetFeatureCount(1)}");
Console.WriteLine($" 几何类型: {Ogr.GeometryTypeToName(layer.GetGeomType())}");
// 范围
Envelope extent = new Envelope();
layer.GetExtent(extent, 1);
Console.WriteLine($" 范围: ({extent.MinX:F4}, {extent.MinY:F4}) - ({extent.MaxX:F4}, {extent.MaxY:F4})");
// 空间参考
using var srs = layer.GetSpatialRef();
if (srs != null)
{
Console.WriteLine($" 坐标系: {srs.GetName()}");
}
// 字段定义
using var layerDefn = layer.GetLayerDefn();
Console.WriteLine($" 字段数: {layerDefn.GetFieldCount()}");
for (int j = 0; j < layerDefn.GetFieldCount(); j++)
{
using var fieldDefn = layerDefn.GetFieldDefn(j);
Console.WriteLine($" - {fieldDefn.GetName()}: {fieldDefn.GetTypeName()}({fieldDefn.GetWidth()})");
}
}
}
/// <summary>
/// 遍历要素
/// </summary>
public static void IterateFeatures(string filepath, Action<Feature>? processor = null)
{
GdalInitializer.Initialize();
using var ds = Ogr.Open(filepath, 0);
if (ds == null)
{
throw new Exception($"无法打开文件: {filepath}");
}
using var layer = ds.GetLayerByIndex(0);
layer.ResetReading();
Feature? feature;
while ((feature = layer.GetNextFeature()) != null)
{
using (feature)
{
if (processor != null)
{
processor(feature);
}
else
{
Console.WriteLine($"FID: {feature.GetFID()}");
using var geom = feature.GetGeometryRef();
if (geom != null)
{
Console.WriteLine($" 几何类型: {geom.GetGeometryName()}");
Console.WriteLine($" WKT: {geom.ExportToWkt().Substring(0, Math.Min(50, geom.ExportToWkt().Length))}...");
}
}
}
}
}
/// <summary>
/// 读取要素为字典列表
/// </summary>
public static List<Dictionary<string, object?>> ReadFeatures(
string filepath,
string? whereClause = null,
(double MinX, double MinY, double MaxX, double MaxY)? bbox = null)
{
GdalInitializer.Initialize();
using var ds = Ogr.Open(filepath, 0);
if (ds == null)
{
throw new Exception($"无法打开文件: {filepath}");
}
using var layer = ds.GetLayerByIndex(0);
// 设置过滤器
if (!string.IsNullOrEmpty(whereClause))
{
layer.SetAttributeFilter(whereClause);
}
if (bbox.HasValue)
{
layer.SetSpatialFilterRect(bbox.Value.MinX, bbox.Value.MinY,
bbox.Value.MaxX, bbox.Value.MaxY);
}
var features = new List<Dictionary<string, object?>>();
using var layerDefn = layer.GetLayerDefn();
Feature? feature;
layer.ResetReading();
while ((feature = layer.GetNextFeature()) != null)
{
using (feature)
{
var dict = new Dictionary<string, object?>
{
["fid"] = feature.GetFID()
};
using var geom = feature.GetGeometryRef();
if (geom != null)
{
dict["geometry"] = geom.ExportToWkt();
}
for (int i = 0; i < layerDefn.GetFieldCount(); i++)
{
using var fieldDefn = layerDefn.GetFieldDefn(i);
string fieldName = fieldDefn.GetName();
if (feature.IsFieldNull(i))
{
dict[fieldName] = null;
}
else
{
dict[fieldName] = fieldDefn.GetFieldType() switch
{
FieldType.OFTInteger => feature.GetFieldAsInteger(i),
FieldType.OFTInteger64 => feature.GetFieldAsInteger64(i),
FieldType.OFTReal => feature.GetFieldAsDouble(i),
_ => feature.GetFieldAsString(i)
};
}
}
features.Add(dict);
}
}
return features;
}
}
9.4.2 创建矢量数据
using OSGeo.OGR;
using OSGeo.OSR;
namespace GdalDemo.Vector;
/// <summary>
/// 矢量数据写入器
/// </summary>
public class VectorWriter
{
/// <summary>
/// 创建 Shapefile
/// </summary>
public static void CreateShapefile(
string filepath,
wkbGeometryType geomType,
int epsg,
List<(string Name, FieldType Type, int Width)> fields,
List<Dictionary<string, object?>> features)
{
GdalInitializer.Initialize();
using var driver = Ogr.GetDriverByName("ESRI Shapefile");
if (driver == null)
{
throw new Exception("Shapefile 驱动不可用");
}
// 删除已存在的文件
if (File.Exists(filepath))
{
driver.DeleteDataSource(filepath);
}
using var ds = driver.CreateDataSource(filepath, Array.Empty<string>());
if (ds == null)
{
throw new Exception($"无法创建数据源: {filepath}");
}
// 创建空间参考
using var srs = new SpatialReference("");
srs.ImportFromEPSG(epsg);
// 创建图层
using var layer = ds.CreateLayer("layer", srs, geomType, Array.Empty<string>());
// 创建字段
foreach (var (name, type, width) in fields)
{
using var fieldDefn = new FieldDefn(name, type);
if (width > 0)
{
fieldDefn.SetWidth(width);
}
layer.CreateField(fieldDefn, 1);
}
// 获取图层定义
using var layerDefn = layer.GetLayerDefn();
// 添加要素
foreach (var featureData in features)
{
using var feature = new Feature(layerDefn);
// 设置几何
if (featureData.TryGetValue("geometry", out var geomObj) && geomObj is string wkt)
{
using var geom = Geometry.CreateFromWkt(wkt);
feature.SetGeometry(geom);
}
// 设置属性
foreach (var (name, _, _) in fields)
{
if (featureData.TryGetValue(name, out var value) && value != null)
{
int fieldIndex = feature.GetFieldIndex(name);
if (fieldIndex >= 0)
{
switch (value)
{
case int intVal:
feature.SetField(fieldIndex, intVal);
break;
case long longVal:
feature.SetField(fieldIndex, longVal);
break;
case double doubleVal:
feature.SetField(fieldIndex, doubleVal);
break;
default:
feature.SetField(fieldIndex, value.ToString());
break;
}
}
}
}
layer.CreateFeature(feature);
}
Console.WriteLine($"创建完成: {filepath}");
}
/// <summary>
/// 创建 GeoJSON
/// </summary>
public static void CreateGeoJSON(
string filepath,
wkbGeometryType geomType,
int epsg,
List<(string Name, FieldType Type, int Width)> fields,
List<Dictionary<string, object?>> features)
{
GdalInitializer.Initialize();
using var driver = Ogr.GetDriverByName("GeoJSON");
if (driver == null)
{
throw new Exception("GeoJSON 驱动不可用");
}
using var ds = driver.CreateDataSource(filepath, Array.Empty<string>());
using var srs = new SpatialReference("");
srs.ImportFromEPSG(epsg);
using var layer = ds.CreateLayer("features", srs, geomType, Array.Empty<string>());
foreach (var (name, type, width) in fields)
{
using var fieldDefn = new FieldDefn(name, type);
if (width > 0)
{
fieldDefn.SetWidth(width);
}
layer.CreateField(fieldDefn, 1);
}
using var layerDefn = layer.GetLayerDefn();
foreach (var featureData in features)
{
using var feature = new Feature(layerDefn);
if (featureData.TryGetValue("geometry", out var geomObj) && geomObj is string wkt)
{
using var geom = Geometry.CreateFromWkt(wkt);
feature.SetGeometry(geom);
}
foreach (var (name, _, _) in fields)
{
if (featureData.TryGetValue(name, out var value) && value != null)
{
int fieldIndex = feature.GetFieldIndex(name);
if (fieldIndex >= 0)
{
feature.SetField(fieldIndex, value.ToString());
}
}
}
layer.CreateFeature(feature);
}
Console.WriteLine($"创建完成: {filepath}");
}
/// <summary>
/// 创建示例点数据
/// </summary>
public static void CreateSamplePoints(string filepath)
{
var fields = new List<(string Name, FieldType Type, int Width)>
{
("name", FieldType.OFTString, 50),
("population", FieldType.OFTInteger64, 0)
};
var features = new List<Dictionary<string, object?>>
{
new() { ["geometry"] = "POINT(116.4 39.9)", ["name"] = "北京", ["population"] = 21540000L },
new() { ["geometry"] = "POINT(121.5 31.2)", ["name"] = "上海", ["population"] = 24280000L },
new() { ["geometry"] = "POINT(113.3 23.1)", ["name"] = "广州", ["population"] = 18680000L }
};
CreateShapefile(filepath, wkbGeometryType.wkbPoint, 4326, fields, features);
}
}
9.5 坐标转换
using OSGeo.OGR;
using OSGeo.OSR;
namespace GdalDemo.Coordinate;
/// <summary>
/// 坐标转换工具
/// </summary>
public class CoordinateTransformer
{
/// <summary>
/// 转换坐标点
/// </summary>
public static (double X, double Y) TransformPoint(
double x, double y,
int srcEPSG, int dstEPSG)
{
GdalInitializer.Initialize();
using var srcSRS = new SpatialReference("");
srcSRS.ImportFromEPSG(srcEPSG);
using var dstSRS = new SpatialReference("");
dstSRS.ImportFromEPSG(dstEPSG);
using var transform = new CoordinateTransformation(srcSRS, dstSRS);
double[] point = new double[] { x, y, 0 };
transform.TransformPoint(point);
return (point[0], point[1]);
}
/// <summary>
/// 批量转换坐标
/// </summary>
public static List<(double X, double Y)> TransformPoints(
List<(double X, double Y)> points,
int srcEPSG, int dstEPSG)
{
GdalInitializer.Initialize();
using var srcSRS = new SpatialReference("");
srcSRS.ImportFromEPSG(srcEPSG);
using var dstSRS = new SpatialReference("");
dstSRS.ImportFromEPSG(dstEPSG);
using var transform = new CoordinateTransformation(srcSRS, dstSRS);
var results = new List<(double X, double Y)>();
foreach (var (x, y) in points)
{
double[] point = new double[] { x, y, 0 };
transform.TransformPoint(point);
results.Add((point[0], point[1]));
}
return results;
}
/// <summary>
/// 转换几何对象
/// </summary>
public static Geometry TransformGeometry(
Geometry geom,
int srcEPSG, int dstEPSG)
{
GdalInitializer.Initialize();
using var srcSRS = new SpatialReference("");
srcSRS.ImportFromEPSG(srcEPSG);
using var dstSRS = new SpatialReference("");
dstSRS.ImportFromEPSG(dstEPSG);
using var transform = new CoordinateTransformation(srcSRS, dstSRS);
var newGeom = geom.Clone();
newGeom.Transform(transform);
return newGeom;
}
}
9.6 实用工具类
using OSGeo.GDAL;
using OSGeo.OGR;
namespace GdalDemo.Utils;
/// <summary>
/// GDAL 资源包装器
/// </summary>
public class GdalResource : IDisposable
{
private Dataset? _rasterDs;
private DataSource? _vectorDs;
private bool _disposed = false;
public Dataset? RasterDataset => _rasterDs;
public DataSource? VectorDataSource => _vectorDs;
private GdalResource() { }
public static GdalResource OpenRaster(string filepath, Access access = Access.GA_ReadOnly)
{
GdalInitializer.Initialize();
var resource = new GdalResource();
resource._rasterDs = Gdal.Open(filepath, access);
if (resource._rasterDs == null)
{
throw new Exception($"无法打开栅格文件: {filepath}");
}
return resource;
}
public static GdalResource OpenVector(string filepath, int update = 0)
{
GdalInitializer.Initialize();
var resource = new GdalResource();
resource._vectorDs = Ogr.Open(filepath, update);
if (resource._vectorDs == null)
{
throw new Exception($"无法打开矢量文件: {filepath}");
}
return resource;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_rasterDs?.Dispose();
_vectorDs?.Dispose();
}
_rasterDs = null;
_vectorDs = null;
_disposed = true;
}
}
~GdalResource()
{
Dispose(false);
}
}
9.7 主程序示例
using GdalDemo;
using GdalDemo.Raster;
using GdalDemo.Vector;
using GdalDemo.Coordinate;
// 初始化 GDAL
GdalInitializer.Initialize();
Console.WriteLine("=== GDAL .NET 示例 ===\n");
// 1. 创建示例 DEM
Console.WriteLine("1. 创建示例 DEM...");
RasterWriter.CreateSampleDEM("sample_dem.tif");
// 2. 读取栅格信息
Console.WriteLine("\n2. 读取栅格信息...");
RasterReader.ReadRasterInfo("sample_dem.tif");
// 3. 创建示例点数据
Console.WriteLine("\n3. 创建示例点数据...");
VectorWriter.CreateSamplePoints("cities.shp");
// 4. 读取矢量信息
Console.WriteLine("\n4. 读取矢量信息...");
VectorReader.ReadVectorInfo("cities.shp");
// 5. 坐标转换
Console.WriteLine("\n5. 坐标转换...");
var (x, y) = CoordinateTransformer.TransformPoint(116.4, 39.9, 4326, 3857);
Console.WriteLine($"WGS84 (116.4, 39.9) -> Web Mercator ({x:F2}, {y:F2})");
Console.WriteLine("\n=== 完成 ===");
9.8 本章小结
本章介绍了 C#/.NET GDAL 开发:
- 环境配置:使用 MaxRev.Gdal.Universal NuGet 包
- 栅格处理:读取、创建和处理栅格数据
- 矢量处理:读取、创建和查询矢量数据
- 坐标转换:使用 OSR 进行坐标转换
- 最佳实践:资源管理和 IDisposable 模式
9.9 思考与练习
- 比较 MaxRev.Gdal.Universal 和其他 GDAL NuGet 包的优缺点。
- 如何在 ASP.NET Core Web API 中使用 GDAL?
- 实现一个支持异步操作的栅格处理类。
- 编写一个批量格式转换工具。
- 如何处理 GDAL 中的大文件和内存管理?

浙公网安备 33010602011771号