首页 > 其他分享 >点集合的三角剖分

点集合的三角剖分

时间:2023-10-24 13:33:19浏览次数:38  
标签:typedef ogrPoint 剖分 CGAL 三角 vertexList 集合 Delaunay

点集合的三角剖分是指如何将一些离散的点集合组合成不均匀的三角形网格,使得每个点成为三角网中三角面的顶点。这个算法的用处很多,一个典型的意义在于可以通过一堆离散点构建的TIN实现对整个构网区域的线性控制,比如用带高程的离散点构建的TIN来表达地形。

在实际工作中,使用最多的三角剖分是Delaunay三角剖分。通过Delaunay三角剖分算法能够构建一个具有空圆特性和最大化最小角特性的三角网。空圆特性其实就是对于两个共边的三角形,任意一个三角形的外接圆中都不能包含有另一个三角形的顶点,这种形式的剖分产生的最小角最大。这些特性可能有些难以理解,但是我们可以先谨记一点:Delaunay三角网是一种特性最优的三角剖分。

通过CGAL,我们可以直接通过离散点集生成Delaunay三角网,实现代码如下:

#include <CGAL/Delaunay_triangulation_2.h>
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Projection_traits_xy_3.h>

typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
typedef CGAL::Projection_traits_xy_3<K> Gt;
typedef CGAL::Delaunay_triangulation_2<Gt> Delaunay;
typedef K::Point_3 Point;

#include <ogrsf_frmts.h>

#include <iostream>
#include <string>

using namespace std;

bool ReadVector(vector<Point> &vertexList) {
  string srcFile = "Data/Vector/points.shp";

  GDALDataset *poDS = (GDALDataset *)GDALOpenEx(srcFile.c_str(), GDAL_OF_VECTOR,
                                                NULL, NULL, NULL);
  if (!poDS) {
    printf("无法读取该文件,请检查数据是否存在问题!");
    return false;
  }

  if (poDS->GetLayerCount() < 1) {
    printf("该文件的层数小于1,请检查数据是否存在问题!");
    return false;
  }

  for (int li = 0; li < poDS->GetLayerCount(); li++) {
    OGRLayer *poLayer = poDS->GetLayer(li);  //读取层
    poLayer->ResetReading();

    //遍历特征
    OGRFeature *poFeature = nullptr;
    while ((poFeature = poLayer->GetNextFeature()) != nullptr) {
      OGRGeometry *geometry = poFeature->GetGeometryRef();
      OGRwkbGeometryType geometryType = geometry->getGeometryType();

      switch (geometryType) {
        case wkbPoint:
        case wkbPointM:
        case wkbPointZM: {
          OGRPoint *ogrPoint = dynamic_cast<OGRPoint *>(geometry);
          if (ogrPoint) {
            vertexList.emplace_back(ogrPoint->getX(), ogrPoint->getY(), 0);
          }
          break;
        }
        case wkbMultiPoint:
        case wkbMultiPointM:
        case wkbMultiPointZM: {
          OGRMultiPoint *ogrMultiPoint =
              dynamic_cast<OGRMultiPoint *>(geometry);
          if (!ogrMultiPoint) {
            continue;
          }

          for (int gi = 0; gi < ogrMultiPoint->getNumGeometries(); gi++) {
            OGRPoint *ogrPoint =
                dynamic_cast<OGRPoint *>(ogrMultiPoint->getGeometryRef(gi));
            if (ogrPoint) {
              vertexList.emplace_back(ogrPoint->getX(), ogrPoint->getY(), 0);
            }
          }

          break;
        }
        default: {
          printf("未处理的特征类型\n");
          break;
        }
      }

      OGRFeature::DestroyFeature(poFeature);
    }
  }

  GDALClose(poDS);
  poDS = nullptr;

  return true;
}

bool WriteVector(const Delaunay &dt) {
  string dstFile = "Data/Out.shp";

  GDALDriver *driver =
      GetGDALDriverManager()->GetDriverByName("ESRI Shapefile");
  if (!driver) {
    printf("Get Driver ESRI Shapefile Error!\n");
    return false;
  }

  GDALDataset *dataset =
      driver->Create(dstFile.c_str(), 0, 0, 0, GDT_Unknown, NULL);
  OGRLayer *poLayer = dataset->CreateLayer("tin", NULL, wkbPolygon, NULL);

  //创建面要素
  for (const auto &f : dt.finite_face_handles()) {
    OGRFeature *poFeature = new OGRFeature(poLayer->GetLayerDefn());

    OGRLinearRing ogrring;
    for (int i = 0; i < 3; i++) {
      ogrring.setPoint(i, f->vertex(i)->point().x(), f->vertex(i)->point().y());
    }
    ogrring.closeRings();

    OGRPolygon polygon;
    polygon.addRing(&ogrring);
    poFeature->SetGeometry(&polygon);

    if (poLayer->CreateFeature(poFeature) != OGRERR_NONE) {
      printf("Failed to create feature in shapefile.\n");
      return false;
    }
  }

  //释放
  GDALClose(dataset);
  dataset = nullptr;

  return true;
}

int main() {
  GDALAllRegister();
  CPLSetConfigOption("GDAL_FILENAME_IS_UTF8", "NO");  //支持中文路径
  CPLSetConfigOption("SHAPE_ENCODING", "");           //解决中文乱码问题

  vector<Point> vertexList;
  ReadVector(vertexList);

  Delaunay dt(vertexList.begin(), vertexList.end());

  WriteVector(dt);
  return 0;
}

这里我们先从一个矢量中读取了离散点集,在QGIS中显示如下图4.21所示:

三角构网前的离散点集

在程序最后,将生成的Delaunay三角网输出成另外一个矢量文件,在QGIS中显示如下图4.22所示:

三角构网后的TIN

读取和写出比较好理解,关键是调用CGAL进行构建Delaunay三角网,其实相当简短:

typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
typedef CGAL::Projection_traits_xy_3<K> Gt;
typedef CGAL::Delaunay_triangulation_2<Gt> Delaunay;
typedef K::Point_3 Point;

int main() {
{
  //...
  vector<Point> vertexList;
  //...
  Delaunay dt(vertexList.begin(), vertexList.end());
  //...
}

CGAL大量应用了C++的模板(泛型)技术,因而使用的接口比较抽象可能难以理解,这里可以解释一下CGAL的设计逻辑。学过任何一门编程语言的都知道,浮点型数值的相等判断不能直接使用相等运算符;正确的做法是使用两者相减的绝对值与容差进行判断,因为计算机表达的浮点型是个近似值。计算几何的核心问题正在于此,内置数据类型的精度是有限的,处理容差是非常麻烦的事情。所以数值需要更为精确的表达,比如0.5就应该就是0.5,不能是0.49999999。因此CGAL确定了一个Kernel(核)的概念,通过模板来控制不同精度。

这里的typedef CGAL::Exact_predicates_inexact_constructions_kernel K;表示精确谓词,但不精确构造的内核。predicates(谓词)表示一个操作;(constructions)构造意味着会有新的数值对象作为结果,如果算法是一个不进行构造的算法中,就可以使用精确谓词但不精确构造的内核。比如这里的构建Delaunay三角网,并没有新的点对象生成出来,只是对点集进行了组织,点还是原来哪些点,并没有变化。

另外,typedef K::Point_3 Point;表示我们使用该精度下的内置三维点类型。但是另外一个问题在于,如果我们需要定义三个维度中的哪两个维度数值参与构网计算,或者使用自定义数据结构该怎么办呢?所以可以传入Traits类型,这其实是C++的模板中的traits技术,描述了传入数据的数值特性:比如类型,排序,方向测试或者相等判断等。每个Kernel中都有定义好的Traits类型,这里使用的就是typedef CGAL::Projection_traits_xy_3<K> Gt;,使用点的xy值参与构网计算。最后将该类型作为模板参数传入到Delaunay三角网构建类中:typedef CGAL::Delaunay_triangulation_2<Gt> Delaunay;

上述的解析读者如果没有一定的C++模板知识的基础,肯定看的云里雾里。其实不要紧,笔者也只是希望大家能够理解CGAL如此设计接口的内在逻辑,并不是故意设计的如此抽象和繁琐,而是希望最大程度的保证精度和性能。更多更具体的解析,读者可以参看CGAL文档。对C++模板知识不熟悉的初学者,建议直接参考文档中的给出的实例,在实际使用过程中逐渐增加自己的认识。

标签:typedef,ogrPoint,剖分,CGAL,三角,vertexList,集合,Delaunay
From: https://www.cnblogs.com/charlee44/p/17784602.html

相关文章

  • webgpu用最简短的代码画一个三角形
    1.包含webgpu的初始化2.三角形顶点缓冲的创建以及将cpu数据填充到gpu里3.webgpu里着色器的编写,以及通过代码创建webgpu的着色器程序对象4.通过顶点和像素阶段的描述创建一个渲染管线话不多说直接贴代码:<html><head> <metacharset="utf-8"> <title>WebGPUHelloTri......
  • spring data jpa 使用原生sql查询数据库 原生sql中有in关键字 该如何传参?直接传List集
    springdatajpa使用原生sql查询数据库原生sql中有in关键字该如何传参?直接传List集合就能找到数据,解析List集合交给springdatajpa框架去做遇到问题?第一次写的时候in关键字后面传的是将List集合转化为一个这样的字符串,"'123','23','23'" @Query(nativeQuery=true,......
  • Day19_叠加多个装饰器_生成器_三元表达式_列表、字典、集合生成式_生成器表达式
    1.叠加多个装饰器运行顺序: 2.生成器的运行: 3..send()方法可以为yield传输返回值: 4..send()一个None相当于把None添加到yield后: 5..close关闭之后无法传值: 6.三元表达式: 7.列表生成式: 8.字典生成式: 9.集合生成式: 10.生成器表达式: ......
  • 什么是java集合框架
    Java集合框架是Java编程语言提供的一组类和接口,用于处理和存储数据集合。它提供了各种数据结构和算法,以便开发者能够高效地操作数据,无需自行实现这些数据结构。Java集合框架的主要目标是提供一种通用的、标准的方法来处理和存储不同类型的数据,使开发更加方便和高效。以下是Java集......
  • 12_集合框架
    ......
  • 14_数据结构与集合源码
    ......
  • Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法
    Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,此处测试代码如下,这里使用add方法:1publicclassmain{2publicstaticvoidmain(String[]args){3int[]num={1,2,3};4Listlist=Arrays.asList(num);5list.add(4);......
  • 关于三角形的四种心(外心,内心,重心,垂心)
    外心三条边垂直平分线的交点为外心。到三顶点距离相等内心三条内角平分线的交点为内心。到三条边的距离相等同时是内切圆的圆心重心三条中心的交点为重心同时是物理意义上的重心公式:\(G(x_0,y_0),x_0=\frac{x_1+x_2+x_3}{3},y_0=\frac{y_1+y_2+y_3}{3}\)垂心......
  • mongo数据库$out输出覆盖原集合
    数据库版本:4.2.8操作系统:ubuntu20mongoaggregate中$out输出可以将原集合覆盖。问题复现:1、写入测试数据rs0:PRIMARY>useceshirs0:PRIMARY>db.t1.insert({id:1})rs0:PRIMARY>db.t1.insert({id:2})rs0:PRIMARY>db.t1.insert({id:3})rs0:PRIMARY>db.t1.insert({id:......
  • python基础-数据类型(none、集合、字典、浮点数)
    目录1.了解hash2.None类型3.集合(set)3.1定义3.2独有功能3.3公共功能3.4转换3.5其他3.5.1集合的存储原理3.5.2元素必须可哈希3.5.3集合查找元素速度快3.5.4对比和嵌套集合练习题4.字典(dict)4.1定义4.2独有功能练习题4.3公共功能4.4转换4.5其他4.5.1存储原......