点集合的三角剖分

简介: 点集合的三角剖分

点集合的三角剖分

点集合的三角剖分是指如何将一些离散的点集合组合成不均匀的三角形网格,使得每个点成为三角网中三角面的顶点。这个算法的用处很多,一个典型的意义在于可以通过一堆离散点构建的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;
}
CPP 折叠 复制 全屏

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

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

读取和写出比较好理解,关键是调用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++模板知识不熟悉的初学者,建议直接参考文档中的给出的实例,在实际使用过程中逐渐增加自己的认识。

分类: 计算几何

标签: CGAL , 计算几何 , 三角剖分


相关文章
|
安全 项目管理
一文搞懂需求流程规范的制定方法和落地技巧
随着业务和产品的发展、团队的不断扩大,很多团队都不可避免的会遇到需求流程混乱的问题。虽然有的团队也编写了一些“需求流程规范”的文档,但最终却流于纸面,难以在团队真正落地。如何科学制定并有效落实需求管理规范呢?对此,云效产品经理陈逊进行了非常详细的直播分享,本文是他经验的文字总结。
103871 19
|
前端开发
前端Vue3使用Moment Timezone处理不同时区时间
认识基本时间表示字符,UTC,GMT等,用 moment-timezone 这个插件来获取时区,同时获取带时区的时间字段,以便后续业务处理。
1079 1
|
Windows
windows 技术篇 - uispy 工具获取和使用,windows窗口属性快捷查看工具
windows 技术篇 - uispy 工具获取和使用,windows窗口属性快捷查看工具
2440 0
windows 技术篇 - uispy 工具获取和使用,windows窗口属性快捷查看工具
|
移动开发 Dart 前端开发
从架构到源码:一文了解Flutter渲染机制
Flutter从本质上来讲还是一个UI框架,它解决的是一套代码在多端渲染的问题。在渲染管线的设计上更加精简,加上自建渲染引擎,相比ReactNative、Weex以及WebView等方案,具有更好的性能体验。本文将从架构和源码的角度详细分析Flutter渲染机制的设计与实现。较长,同学们可收藏后再看。
8294 1
从架构到源码:一文了解Flutter渲染机制
|
8月前
|
资源调度 监控 搜索推荐
用户行为分析正在被保险行业广泛采纳-ClkLog埋点分析系统
近年来,除了那些已经走在数字化转型前沿的行业,传统的保险行业也开始觉醒,尝试通过用户行为分析来优化产品、提升服务体验。 这是一家由多家全球知名企业共同出资成立的全国性寿险公司。随着数字化浪潮的推进,他们的技术团队率先发起了“通过埋点分析优化产品决策”的探索。在这个过程中,技术验证成为他们迈出的第一步——不仅要评估方案的可行性,更要确保工具选型能支撑长期发展。 就是在这样的背景下,他们找到了ClkLog,开启了一段信任、验证与共建的合作之路。一起看看,方案发起人Alan是怎么讲述这个过程的。
207 61
|
Java
"揭秘Java IO三大模式:BIO、NIO、AIO背后的秘密!为何AIO成为高并发时代的宠儿,你的选择对了吗?"
【8月更文挑战第19天】在Java的IO编程中,BIO、NIO与AIO代表了三种不同的IO处理机制。BIO采用同步阻塞模型,每个连接需单独线程处理,适用于连接少且稳定的场景。NIO引入了非阻塞性质,利用Channel、Buffer与Selector实现多路复用,提升了效率与吞吐量。AIO则是真正的异步IO,在JDK 7中引入,通过回调或Future机制在IO操作完成后通知应用,适合高并发场景。选择合适的模型对构建高效网络应用至关重要。
369 2
|
网络协议 Unix Linux
精选2款C#/.NET开源且功能强大的网络通信框架
精选2款C#/.NET开源且功能强大的网络通信框架
461 0
|
Java API Apache
如何在Java中实现PDF生成
如何在Java中实现PDF生成
|
存储 缓存 小程序
【经验分享】解决input placeholder光标漂移问题
【经验分享】解决input placeholder光标漂移问题
657 7