第02章-环境配置与项目创建
第02章:环境配置与项目创建
2.1 开发环境准备
2.1.1 .NET SDK 安装
NetTopologySuite 支持 .NET Standard 2.0 及以上版本,建议使用 .NET 6.0 或更高版本进行开发。
下载安装 .NET SDK:
- 访问 .NET 官方下载页面
- 选择最新的 LTS 版本(如 .NET 8.0)
- 下载并安装对应操作系统的安装包
验证安装:
# 查看已安装的 SDK 版本
dotnet --list-sdks
# 查看 dotnet 版本
dotnet --version
2.1.2 开发工具选择
Visual Studio 2022
推荐配置:
- Visual Studio 2022 Community 或更高版本
- 安装工作负载:
- ASP.NET 和 Web 开发
- .NET 桌面开发
- 或者仅安装 .NET Core 跨平台开发
安装步骤:
- 下载 Visual Studio 2022 安装程序
- 选择 ".NET 桌面开发" 工作负载
- 确保勾选 ".NET SDK"
Visual Studio Code
推荐扩展:
- C# for Visual Studio Code (ms-dotnettools.csharp)
- C# Dev Kit (ms-dotnettools.csdevkit)
- NuGet Package Manager
安装扩展:
# 通过命令行安装扩展
code --install-extension ms-dotnettools.csharp
code --install-extension ms-dotnettools.csdevkit
JetBrains Rider
JetBrains Rider 是一款功能强大的跨平台 .NET IDE,提供了优秀的代码补全和重构功能。
推荐版本:Rider 2023.1 或更高版本
2.2 创建控制台项目
2.2.1 使用 .NET CLI 创建
# 创建项目目录
mkdir NtsDemo
cd NtsDemo
# 创建控制台项目
dotnet new console -n NtsDemo
# 进入项目目录
cd NtsDemo
# 添加 NetTopologySuite 包
dotnet add package NetTopologySuite
dotnet add package NetTopologySuite.Features
dotnet add package NetTopologySuite.IO.GeoJSON
# 构建项目
dotnet build
# 运行项目
dotnet run
2.2.2 项目文件配置
创建完成后,NtsDemo.csproj 文件内容如下:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NetTopologySuite" Version="2.5.0" />
<PackageReference Include="NetTopologySuite.Features" Version="2.2.0" />
<PackageReference Include="NetTopologySuite.IO.GeoJSON" Version="4.0.0" />
</ItemGroup>
</Project>
2.2.3 编写入门代码
修改 Program.cs 文件:
using NetTopologySuite.Geometries;
using NetTopologySuite.IO;
// 创建几何工厂
var factory = new GeometryFactory(new PrecisionModel(), 4326);
// 创建北京市中心点
var beijing = factory.CreatePoint(new Coordinate(116.4074, 39.9042));
Console.WriteLine($"北京市中心: {beijing.AsText()}");
// 创建上海市中心点
var shanghai = factory.CreatePoint(new Coordinate(121.4737, 31.2304));
Console.WriteLine($"上海市中心: {shanghai.AsText()}");
// 计算两点之间的距离(度数)
var distance = beijing.Distance(shanghai);
Console.WriteLine($"两点距离(度): {distance:F4}");
// 创建一条连接两个城市的线
var line = factory.CreateLineString(new[]
{
beijing.Coordinate,
shanghai.Coordinate
});
Console.WriteLine($"连接线: {line.AsText()}");
Console.WriteLine($"线长度(度): {line.Length:F4}");
// 使用 WKT 读取器
var reader = new WKTReader();
var polygon = reader.Read(@"
POLYGON ((
116.3 39.8,
116.5 39.8,
116.5 40.0,
116.3 40.0,
116.3 39.8
))
");
Console.WriteLine($"多边形面积(平方度): {polygon.Area:F6}");
// 判断北京是否在多边形内
var isWithin = beijing.Within(polygon);
Console.WriteLine($"北京在多边形内: {isWithin}");
运行结果:
北京市中心: POINT (116.4074 39.9042)
上海市中心: POINT (121.4737 31.2304)
两点距离(度): 10.0293
连接线: LINESTRING (116.4074 39.9042, 121.4737 31.2304)
线长度(度): 10.0293
多边形面积(平方度): 0.040000
北京在多边形内: True
2.3 创建类库项目
2.3.1 创建解决方案结构
对于较大的项目,建议使用解决方案组织多个项目:
# 创建解决方案目录
mkdir NtsSolution
cd NtsSolution
# 创建解决方案
dotnet new sln -n NtsSolution
# 创建类库项目
dotnet new classlib -n NtsCore
dotnet new classlib -n NtsIO
dotnet new console -n NtsApp
dotnet new xunit -n NtsTests
# 将项目添加到解决方案
dotnet sln add NtsCore/NtsCore.csproj
dotnet sln add NtsIO/NtsIO.csproj
dotnet sln add NtsApp/NtsApp.csproj
dotnet sln add NtsTests/NtsTests.csproj
# 添加项目引用
dotnet add NtsIO/NtsIO.csproj reference NtsCore/NtsCore.csproj
dotnet add NtsApp/NtsApp.csproj reference NtsCore/NtsCore.csproj
dotnet add NtsApp/NtsApp.csproj reference NtsIO/NtsIO.csproj
dotnet add NtsTests/NtsTests.csproj reference NtsCore/NtsCore.csproj
# 为核心库添加 NuGet 包
cd NtsCore
dotnet add package NetTopologySuite
dotnet add package NetTopologySuite.Features
cd ../NtsIO
dotnet add package NetTopologySuite.IO.GeoJSON
dotnet add package NetTopologySuite.IO.Esri.Shapefile
2.3.2 项目结构
NtsSolution/
├── NtsSolution.sln
├── NtsCore/ # 核心业务逻辑
│ ├── NtsCore.csproj
│ ├── Services/
│ │ ├── GeometryService.cs
│ │ └── SpatialAnalysisService.cs
│ └── Models/
│ └── SpatialEntity.cs
├── NtsIO/ # 输入输出模块
│ ├── NtsIO.csproj
│ ├── Readers/
│ │ ├── GeoJsonReader.cs
│ │ └── ShapefileReader.cs
│ └── Writers/
│ ├── GeoJsonWriter.cs
│ └── ShapefileWriter.cs
├── NtsApp/ # 控制台应用
│ ├── NtsApp.csproj
│ └── Program.cs
└── NtsTests/ # 单元测试
├── NtsTests.csproj
└── GeometryServiceTests.cs
2.3.3 核心服务示例
NtsCore/Services/GeometryService.cs:
using NetTopologySuite.Geometries;
namespace NtsCore.Services;
/// <summary>
/// 几何操作服务类
/// </summary>
public class GeometryService
{
private readonly GeometryFactory _factory;
/// <summary>
/// 初始化几何服务
/// </summary>
/// <param name="srid">空间参考ID,默认 4326 (WGS84)</param>
public GeometryService(int srid = 4326)
{
_factory = new GeometryFactory(new PrecisionModel(), srid);
}
/// <summary>
/// 创建点几何
/// </summary>
public Point CreatePoint(double x, double y)
{
return _factory.CreatePoint(new Coordinate(x, y));
}
/// <summary>
/// 创建线几何
/// </summary>
public LineString CreateLine(IEnumerable<(double x, double y)> coordinates)
{
var coords = coordinates.Select(c => new Coordinate(c.x, c.y)).ToArray();
return _factory.CreateLineString(coords);
}
/// <summary>
/// 创建多边形几何
/// </summary>
public Polygon CreatePolygon(IEnumerable<(double x, double y)> coordinates)
{
var coords = coordinates.Select(c => new Coordinate(c.x, c.y)).ToArray();
// 确保多边形闭合
if (!coords.First().Equals2D(coords.Last()))
{
coords = coords.Append(coords.First()).ToArray();
}
return _factory.CreatePolygon(coords);
}
/// <summary>
/// 创建圆形缓冲区
/// </summary>
public Geometry CreateBuffer(Geometry geometry, double distance)
{
return geometry.Buffer(distance);
}
/// <summary>
/// 计算几何并集
/// </summary>
public Geometry Union(Geometry geom1, Geometry geom2)
{
return geom1.Union(geom2);
}
/// <summary>
/// 计算几何交集
/// </summary>
public Geometry Intersection(Geometry geom1, Geometry geom2)
{
return geom1.Intersection(geom2);
}
/// <summary>
/// 计算几何差集
/// </summary>
public Geometry Difference(Geometry geom1, Geometry geom2)
{
return geom1.Difference(geom2);
}
/// <summary>
/// 判断两个几何是否相交
/// </summary>
public bool Intersects(Geometry geom1, Geometry geom2)
{
return geom1.Intersects(geom2);
}
/// <summary>
/// 判断几何是否包含另一个几何
/// </summary>
public bool Contains(Geometry container, Geometry contained)
{
return container.Contains(contained);
}
/// <summary>
/// 计算两个几何之间的距离
/// </summary>
public double Distance(Geometry geom1, Geometry geom2)
{
return geom1.Distance(geom2);
}
}
2.3.4 单元测试示例
NtsTests/GeometryServiceTests.cs:
using NetTopologySuite.Geometries;
using NtsCore.Services;
using Xunit;
namespace NtsTests;
public class GeometryServiceTests
{
private readonly GeometryService _service;
public GeometryServiceTests()
{
_service = new GeometryService();
}
[Fact]
public void CreatePoint_ShouldCreateValidPoint()
{
// Arrange
double x = 116.4074;
double y = 39.9042;
// Act
var point = _service.CreatePoint(x, y);
// Assert
Assert.NotNull(point);
Assert.Equal(x, point.X);
Assert.Equal(y, point.Y);
Assert.Equal("Point", point.GeometryType);
}
[Fact]
public void CreatePolygon_ShouldCreateClosedPolygon()
{
// Arrange
var coordinates = new (double x, double y)[]
{
(0, 0), (10, 0), (10, 10), (0, 10)
};
// Act
var polygon = _service.CreatePolygon(coordinates);
// Assert
Assert.NotNull(polygon);
Assert.True(polygon.IsValid);
Assert.Equal(100, polygon.Area);
}
[Fact]
public void Intersects_OverlappingPolygons_ShouldReturnTrue()
{
// Arrange
var polygon1 = _service.CreatePolygon(new (double, double)[]
{
(0, 0), (10, 0), (10, 10), (0, 10)
});
var polygon2 = _service.CreatePolygon(new (double, double)[]
{
(5, 5), (15, 5), (15, 15), (5, 15)
});
// Act
var result = _service.Intersects(polygon1, polygon2);
// Assert
Assert.True(result);
}
[Fact]
public void Contains_PointInPolygon_ShouldReturnTrue()
{
// Arrange
var polygon = _service.CreatePolygon(new (double, double)[]
{
(0, 0), (10, 0), (10, 10), (0, 10)
});
var point = _service.CreatePoint(5, 5);
// Act
var result = _service.Contains(polygon, point);
// Assert
Assert.True(result);
}
[Fact]
public void Buffer_ShouldIncreaseArea()
{
// Arrange
var point = _service.CreatePoint(0, 0);
// Act
var buffer = _service.CreateBuffer(point, 5);
// Assert
Assert.True(buffer.Area > 0);
Assert.Equal("Polygon", buffer.GeometryType);
}
}
运行测试:
cd NtsTests
dotnet test
2.4 创建 Web API 项目
2.4.1 创建项目
# 创建 Web API 项目
dotnet new webapi -n NtsWebApi
cd NtsWebApi
# 添加 NuGet 包
dotnet add package NetTopologySuite
dotnet add package NetTopologySuite.Features
dotnet add package NetTopologySuite.IO.GeoJSON
2.4.2 配置 GeoJSON 序列化
Program.cs:
using NetTopologySuite.IO.Converters;
using System.Text.Json;
var builder = WebApplication.CreateBuilder(args);
// 添加 GeoJSON 序列化支持
builder.Services.AddControllers()
.AddJsonOptions(options =>
{
// 添加 NTS GeoJSON 转换器
options.JsonSerializerOptions.Converters.Add(
new GeoJsonConverterFactory());
// 设置 JSON 选项
options.JsonSerializerOptions.WriteIndented = true;
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
});
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
2.4.3 创建空间 API 控制器
Controllers/SpatialController.cs:
using Microsoft.AspNetCore.Mvc;
using NetTopologySuite.Geometries;
using NetTopologySuite.Features;
using NetTopologySuite.IO;
namespace NtsWebApi.Controllers;
[ApiController]
[Route("api/[controller]")]
public class SpatialController : ControllerBase
{
private readonly GeometryFactory _factory;
private readonly WKTReader _wktReader;
public SpatialController()
{
_factory = new GeometryFactory(new PrecisionModel(), 4326);
_wktReader = new WKTReader(_factory);
}
/// <summary>
/// 创建点
/// </summary>
[HttpGet("point")]
public ActionResult<Point> CreatePoint(double x, double y)
{
var point = _factory.CreatePoint(new Coordinate(x, y));
return Ok(point);
}
/// <summary>
/// 创建缓冲区
/// </summary>
[HttpPost("buffer")]
public ActionResult<Geometry> CreateBuffer([FromBody] BufferRequest request)
{
var geometry = _wktReader.Read(request.Wkt);
var buffer = geometry.Buffer(request.Distance);
return Ok(buffer);
}
/// <summary>
/// 判断相交关系
/// </summary>
[HttpPost("intersects")]
public ActionResult<IntersectsResponse> CheckIntersects([FromBody] TwoGeometriesRequest request)
{
var geom1 = _wktReader.Read(request.Wkt1);
var geom2 = _wktReader.Read(request.Wkt2);
return Ok(new IntersectsResponse
{
Intersects = geom1.Intersects(geom2),
Geometry1Type = geom1.GeometryType,
Geometry2Type = geom2.GeometryType
});
}
/// <summary>
/// 计算交集
/// </summary>
[HttpPost("intersection")]
public ActionResult<Geometry> CalculateIntersection([FromBody] TwoGeometriesRequest request)
{
var geom1 = _wktReader.Read(request.Wkt1);
var geom2 = _wktReader.Read(request.Wkt2);
var intersection = geom1.Intersection(geom2);
return Ok(intersection);
}
/// <summary>
/// 计算并集
/// </summary>
[HttpPost("union")]
public ActionResult<Geometry> CalculateUnion([FromBody] TwoGeometriesRequest request)
{
var geom1 = _wktReader.Read(request.Wkt1);
var geom2 = _wktReader.Read(request.Wkt2);
var union = geom1.Union(geom2);
return Ok(union);
}
/// <summary>
/// 获取 Feature 示例
/// </summary>
[HttpGet("feature")]
public ActionResult<Feature> GetFeature()
{
var point = _factory.CreatePoint(new Coordinate(116.4074, 39.9042));
var attributes = new AttributesTable
{
{ "name", "北京" },
{ "population", 21540000 },
{ "area", 16410.54 }
};
var feature = new Feature(point, attributes);
return Ok(feature);
}
/// <summary>
/// 获取 FeatureCollection 示例
/// </summary>
[HttpGet("features")]
public ActionResult<FeatureCollection> GetFeatures()
{
var features = new FeatureCollection();
// 添加北京
var beijing = _factory.CreatePoint(new Coordinate(116.4074, 39.9042));
features.Add(new Feature(beijing, new AttributesTable
{
{ "name", "北京" },
{ "type", "capital" }
}));
// 添加上海
var shanghai = _factory.CreatePoint(new Coordinate(121.4737, 31.2304));
features.Add(new Feature(shanghai, new AttributesTable
{
{ "name", "上海" },
{ "type", "municipality" }
}));
// 添加广州
var guangzhou = _factory.CreatePoint(new Coordinate(113.2644, 23.1291));
features.Add(new Feature(guangzhou, new AttributesTable
{
{ "name", "广州" },
{ "type", "provincial_capital" }
}));
return Ok(features);
}
}
// 请求和响应模型
public class BufferRequest
{
public string Wkt { get; set; } = "";
public double Distance { get; set; }
}
public class TwoGeometriesRequest
{
public string Wkt1 { get; set; } = "";
public string Wkt2 { get; set; } = "";
}
public class IntersectsResponse
{
public bool Intersects { get; set; }
public string Geometry1Type { get; set; } = "";
public string Geometry2Type { get; set; } = "";
}
2.4.4 测试 API
启动项目后,访问 Swagger UI:https://localhost:5001/swagger
测试缓冲区 API:
curl -X POST "https://localhost:5001/api/spatial/buffer" \
-H "Content-Type: application/json" \
-d '{"wkt": "POINT (0 0)", "distance": 1}'
测试相交判断 API:
curl -X POST "https://localhost:5001/api/spatial/intersects" \
-H "Content-Type: application/json" \
-d '{
"wkt1": "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))",
"wkt2": "POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))"
}'
2.5 依赖注入配置
2.5.1 创建服务接口
IGeometryService.cs:
using NetTopologySuite.Geometries;
public interface IGeometryService
{
GeometryFactory Factory { get; }
Point CreatePoint(double x, double y);
LineString CreateLine(Coordinate[] coordinates);
Polygon CreatePolygon(Coordinate[] coordinates);
Geometry Buffer(Geometry geometry, double distance);
bool Intersects(Geometry geom1, Geometry geom2);
Geometry Intersection(Geometry geom1, Geometry geom2);
Geometry Union(Geometry geom1, Geometry geom2);
}
2.5.2 实现服务
GeometryService.cs:
using NetTopologySuite.Geometries;
public class GeometryService : IGeometryService
{
public GeometryFactory Factory { get; }
public GeometryService(int srid = 4326)
{
Factory = new GeometryFactory(new PrecisionModel(), srid);
}
public Point CreatePoint(double x, double y)
{
return Factory.CreatePoint(new Coordinate(x, y));
}
public LineString CreateLine(Coordinate[] coordinates)
{
return Factory.CreateLineString(coordinates);
}
public Polygon CreatePolygon(Coordinate[] coordinates)
{
var ring = Factory.CreateLinearRing(coordinates);
return Factory.CreatePolygon(ring);
}
public Geometry Buffer(Geometry geometry, double distance)
{
return geometry.Buffer(distance);
}
public bool Intersects(Geometry geom1, Geometry geom2)
{
return geom1.Intersects(geom2);
}
public Geometry Intersection(Geometry geom1, Geometry geom2)
{
return geom1.Intersection(geom2);
}
public Geometry Union(Geometry geom1, Geometry geom2)
{
return geom1.Union(geom2);
}
}
2.5.3 注册服务
Program.cs:
// 注册几何服务
builder.Services.AddSingleton<IGeometryService>(
provider => new GeometryService(4326));
// 或者使用工厂方法
builder.Services.AddSingleton<GeometryFactory>(
provider => new GeometryFactory(new PrecisionModel(), 4326));
2.5.4 在控制器中使用
[ApiController]
[Route("api/[controller]")]
public class SpatialController : ControllerBase
{
private readonly IGeometryService _geometryService;
public SpatialController(IGeometryService geometryService)
{
_geometryService = geometryService;
}
[HttpGet("point")]
public ActionResult<Point> CreatePoint(double x, double y)
{
var point = _geometryService.CreatePoint(x, y);
return Ok(point);
}
}
2.6 NuGet 包版本管理
2.6.1 中央包管理
对于多项目解决方案,建议使用中央包管理:
Directory.Packages.props:
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<!-- NetTopologySuite 核心 -->
<PackageVersion Include="NetTopologySuite" Version="2.5.0" />
<PackageVersion Include="NetTopologySuite.Features" Version="2.2.0" />
<!-- IO 模块 -->
<PackageVersion Include="NetTopologySuite.IO.GeoJSON" Version="4.0.0" />
<PackageVersion Include="NetTopologySuite.IO.Esri.Shapefile" Version="1.0.0" />
<!-- 数据库集成 -->
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite" Version="8.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite" Version="8.0.0" />
<!-- 坐标转换 -->
<PackageVersion Include="ProjNet" Version="2.0.0" />
</ItemGroup>
</Project>
项目文件中引用:
<ItemGroup>
<PackageReference Include="NetTopologySuite" />
<PackageReference Include="NetTopologySuite.Features" />
</ItemGroup>
2.6.2 版本兼容性
| 包名称 | 最新版本 | .NET 版本要求 |
|---|---|---|
| NetTopologySuite | 2.5.0 | .NET Standard 2.0+ |
| NetTopologySuite.Features | 2.2.0 | .NET Standard 2.0+ |
| NetTopologySuite.IO.GeoJSON | 4.0.0 | .NET Standard 2.0+ |
| NetTopologySuite.IO.Esri.Shapefile | 1.0.0 | .NET Standard 2.0+ |
| ProjNet | 2.0.0 | .NET Standard 2.0+ |
2.7 调试技巧
2.7.1 几何对象可视化
在调试时查看几何对象的 WKT 表示:
// 在调试器中查看
var polygon = factory.CreatePolygon(...);
// 使用 AsText() 方法获取 WKT
Debug.WriteLine($"WKT: {polygon.AsText()}");
// 使用 ToString() 方法
Debug.WriteLine($"ToString: {polygon}");
2.7.2 调试输出扩展
创建调试辅助扩展方法:
public static class GeometryDebugExtensions
{
public static void DebugPrint(this Geometry geometry, string name = "")
{
var prefix = string.IsNullOrEmpty(name) ? "" : $"[{name}] ";
Debug.WriteLine($"{prefix}Type: {geometry.GeometryType}");
Debug.WriteLine($"{prefix}WKT: {geometry.AsText()}");
Debug.WriteLine($"{prefix}Area: {geometry.Area:F6}");
Debug.WriteLine($"{prefix}Length: {geometry.Length:F6}");
Debug.WriteLine($"{prefix}IsValid: {geometry.IsValid}");
Debug.WriteLine($"{prefix}IsEmpty: {geometry.IsEmpty}");
Debug.WriteLine($"{prefix}SRID: {geometry.SRID}");
}
}
// 使用
polygon.DebugPrint("测试多边形");
2.7.3 使用 WKT 测试工具
推荐使用在线 WKT 可视化工具:
2.8 本章小结
本章介绍了 NetTopologySuite 开发环境的配置和各类项目的创建方法:
- 环境准备:安装 .NET SDK 和开发工具
- 控制台项目:创建简单的控制台应用进行快速测试
- 类库项目:创建结构化的多项目解决方案
- Web API 项目:创建支持空间数据的 RESTful API
- 依赖注入:配置 NTS 服务的依赖注入
- 包管理:使用中央包管理统一版本
- 调试技巧:几何对象的调试和可视化方法
2.9 下一步
下一章我们将深入学习 NetTopologySuite 的几何对象模型,包括:
- 所有几何类型的详细说明
- GeometryFactory 的高级用法
- 坐标序列的处理
- 几何对象的属性和方法
相关资源:

浙公网安备 33010602011771号