第02章-环境配置与项目创建

第02章:环境配置与项目创建

2.1 开发环境准备

2.1.1 .NET SDK 安装

NetTopologySuite 支持 .NET Standard 2.0 及以上版本,建议使用 .NET 6.0 或更高版本进行开发。

下载安装 .NET SDK:

  1. 访问 .NET 官方下载页面
  2. 选择最新的 LTS 版本(如 .NET 8.0)
  3. 下载并安装对应操作系统的安装包

验证安装:

# 查看已安装的 SDK 版本
dotnet --list-sdks

# 查看 dotnet 版本
dotnet --version

2.1.2 开发工具选择

Visual Studio 2022

推荐配置:

  • Visual Studio 2022 Community 或更高版本
  • 安装工作负载:
    • ASP.NET 和 Web 开发
    • .NET 桌面开发
    • 或者仅安装 .NET Core 跨平台开发

安装步骤:

  1. 下载 Visual Studio 2022 安装程序
  2. 选择 ".NET 桌面开发" 工作负载
  3. 确保勾选 ".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 开发环境的配置和各类项目的创建方法:

  1. 环境准备:安装 .NET SDK 和开发工具
  2. 控制台项目:创建简单的控制台应用进行快速测试
  3. 类库项目:创建结构化的多项目解决方案
  4. Web API 项目:创建支持空间数据的 RESTful API
  5. 依赖注入:配置 NTS 服务的依赖注入
  6. 包管理:使用中央包管理统一版本
  7. 调试技巧:几何对象的调试和可视化方法

2.9 下一步

下一章我们将深入学习 NetTopologySuite 的几何对象模型,包括:

  • 所有几何类型的详细说明
  • GeometryFactory 的高级用法
  • 坐标序列的处理
  • 几何对象的属性和方法

相关资源

posted @ 2025-12-29 10:21  我才是银古  阅读(1)  评论(0)    收藏  举报