三角网生成等高线

3 篇文章 0 订阅

2023年6月16日更新:

  1. 优化了等高线生成算法,大幅提升效率
  2. 修复test.py绘制报错的问题(与依赖库更新有关)

一、研究背景

GIS场景中,有很多面要素类型的数据,如地层面、水位面等,这些面的数据格式基本都是三角网,而基于这些数据生成等高线是用户常用的需求,网上搜索了一番也没有找到可直接用于生成的库,为此自研了基于python语言的三角网生成等高线的算法。

二、等高线的两种情况

使用过等高线的用户应该知道,在一个有限范围的平面图上,等高线是一系列高程相同的点的连线,由于范围有限,这些连线会出现闭合和不闭合两种情况。

三、算法思考

3.1 从几何的角度看等高线

  1. 从宏观上看:等高线可以看成是具有相同高程间隔的一系列水平面与目标面(如地层面、水位面等)求交得到的交线集合。且不闭合的线的端点在面的边缘。
  2. 从细节上看:等高线是具有相同间隔的z平面集与三角网的三角形的三条边的交点的连线的集合。且不闭合的线的端点在三角形的非共有的边(该边不同时属于两个三角形)上。

3.2 基本准则

根据上述的思考,建立如下算法准则

  1. 如果三角形的某条边与某z平面相交,则有且仅有另一条边与该z平面相交
  2. 如果等高线的起点为三角形的非共有的边,则终点必然为另一个三角形的非共有的边
  3. 如果一个点为闭合等高线上的点,则不论沿哪个方向搜索,都能遍历完所有的点并回到自身原点
  4. 非闭合等高线的点从端点开始可遍历完线上所有的点,且不会回到自身原点

3.3 实现算法

根据上述准则,建立如下某z平面与三角网求交线的算法

  1. 先从三角网数据中解析出所有不重复的线段,并记录每条线段所属的三角形索引号
  2. 计算每条线段与z平面的交点(插值计算),如果有则保存点坐标并记录该点所属的线段索引号
  3. 为每个点设置是否被使用的标记
  4. 先寻找属于非闭合等高线的点:循环遍历交点集,判断点是否被使用,已使用则跳过,未使用则根据点所属的线索引号找到线所属的三角形列表,判断列表大小,如果为2,则说明该线段为共有边,此时跳过该点;如果所属三角形列表大小为1,则说明为非共有边,可作为非闭合等高线的起点开始搜索。根据准则1,可以寻找当前点所属的线所属的三角形(因为是非共有边,可以确定该三角形)的另外一个交点,如此往复,可以得到一条非闭合等高线线的点集,这些点集都标记为已使用。一条线遍历完后,重复上述流程,直至找不到非共有边上的点。这样我们就得到了所有非闭合的等高线。
  5. 然后寻找闭合等高线的点:循环遍历交点集,判断点是否被使用,已使用则跳过,未使用则根据准则1,可以寻找当前点所属的线所属的三角形(因为是共有边,有2个三角形,选任意一个即可)的另外一个交点,如此往复,可以得到一条闭合等高线线的点集,这些点集都标记为已使用。一条线遍历完后,重复上述流程,直至找不到未标记的点。这样我们就得到了所有闭合的等高线。

根据上述流程可以得到某个z对应的线,用户一般只提供z_interval(间隔),并不指定具体是哪些z值需要算,此时还需要计算z值列表,本文的算法是先获取面的z_maxz_min,然后找到大于z_minz_interval的倍数值,以此为起始累加z_interval,直到大于z_max

四 算法编写

  1. 构建Tin类来承载三角网的数据结构以及相关计算方法,三角网的数据源选用市面上常用的stl文件(测试文件百度网盘下载链接: https://pan.baidu.com/s/1T1DRpI3nExAcryXt_izB9g 提取码: sxvg
  2. 由于用到了stl文件,需安装stl解析库 pip install numpy-stl
# pip install numpy-stl
from math import ceil
from stl import mesh


class Tin(object):
    """三角网类:用于封装三角网的数据结构,以及外部三角网数据的读取、三角网生成等高线等功能
    """
    def __init__(self) -> None:
        self.__tin = None # 三角网格格式数据:由三个点坐标构成的三角形集合,如[[[0, 0, 0], [1, 0, 0], [1, 1, 0]], [[1, 0, 0], [2, 1, 0], [1, 1, 0]]]
        self.__box = None # 三角网的包围盒:由左下角点和右上角点组成,如[[0, 0, 0], [1, 1, 1]]

    def init_from_data(self, tin:list):
        """直接用三角网数据初始化
        :param list tin: 三角网格格式数据
        """
        self.__tin = tin
        
    def init_from_stl(self, path:str):
        """从stl文件读取三角网数据
        :param str path: stl文件路径
        :param bool return: True-读取成功/False-读取失败
        """
        try:
            data = mesh.Mesh.from_file(path)
            self.__tin = data.vectors.tolist()
            self.__box = [[data.x.min(),data.y.min(),data.z.min(),], 
                [data.x.max(),data.y.max(),data.z.max(),]]
               
            return True
        except Exception as e:
            print(repr(e))
            return False

    def get_tin(self):
        """获取三角网的数据
        :return list self.__tin: 三角网数据
        """
        return self.__tin

    def get_box(self):
        """获取三角网的包围盒
        :return list self.__box: 包围盒
        """
        return self.__box

    def generate_contour(self, z_interval:float):
        """生成等高线
        :param float z_interval: 等高线z间隔
        :return list contour: 等高线数据
        """

        # step1: 判断三角网是否有存在
        if self.__tin == None or len(self.__tin) == 0:
            print("Error: 三角网格不存在")
            return None

        # step2: 从tin中获取线的信息以及坐标的最值
        z_min, z_max, lines, lines_belongto_tri = self.__get_lines_from_tin()

        # step3: 获取等高线的z值列表
        z_set = self.__get_z_value_set_by_interval(z_min, z_max, z_interval)

        # step4: 使用每个z平面与三角网格面求交线
        contour = []
        for z in z_set:
    
            pts, pts_belongto_line = self.__get_inter_pts_between_lines_and_z_plane(lines, z)
            uses_pt = [False]*len(pts) # 点是否被使用,默认False
        
            polylines = []
        
            # 先获取非闭合的
            while True:
                start = -1
                for idx, use in enumerate(uses_pt):
                    if not use and len(lines_belongto_tri[pts_belongto_line[idx]]) == 1:
                        start = idx
                        break
                
                if start == -1:
                    break
        
                polyline = []
        
                self.__get_next_pt(start, uses_pt, pts_belongto_line, lines_belongto_tri, polyline)
        
                polylines.append(polyline)
        
            # 再获取闭合的
            while True:
                start = -1
                for idx, use in enumerate(uses_pt):
                    if not use:
                        start = idx
                        break
                
                if start == -1:
                    break
        
                polyline = []
        
                self.__get_next_pt(start, uses_pt, pts_belongto_line, lines_belongto_tri, polyline)
                polyline.append(polyline[0])
        
                polylines.append(polyline)
        
            # 将索引转成坐标
            for idx_line in polylines:
                contour_pts = []
                for idx_pt in idx_line:
                    contour_pts.append(pts[idx_pt])
            
                contour.append(contour_pts)
    
        return contour

    def __get_lines_from_tin(self):
        """从self.__tin中解析出所有不重复的线段以及点z坐标的最值
        :return float z_min: 三角网的最小z值
        :return float z_max: 三角网的最大z值
        :return list lines: 三角网中的线段(两个点),每一项都是两个点的集合,如[[[],[]],[[],[]],[[],[]]...]
        :return list lines_belongto_tri: 每条线所属的三角形索引号,每一项都是一个列表,记录所属三角形索引号的集合
        """
        lines = []
        lines_belongto_tri = []
    
        z_min = 1e10
        z_max = -1e10

        # 遍历三角网
        for idxTri, tri in enumerate(self.__tin):
            # 一个三角形有三条线,默认设为未记录
            line1_exist = False
            line2_exist = False
            line3_exist = False

            # 判断线是否已被记录
            for idxLine, line in enumerate(lines):
                if len(lines_belongto_tri[idxLine]) >= 2:
                    continue
                    
                if (not line1_exist and tri[0] == line[0] and tri[1] == line[1]) or (tri[0] == line[1] and tri[1] == line[0]):
                    lines_belongto_tri[idxLine].append(idxTri)
                    line1_exist = True
    
                if (not line2_exist and tri[0] == line[0] and tri[2] == line[1]) or (tri[0] == line[1] and tri[2] == line[0]):
                    lines_belongto_tri[idxLine].append(idxTri)
                    line2_exist = True
    
                if (not line3_exist and tri[1] == line[0] and tri[2] == line[1]) or (tri[1] == line[1] and tri[2] == line[0]):
                    lines_belongto_tri[idxLine].append(idxTri)
                    line3_exist = True

            # 如果没有记录,则将线记录到lines,同时记录该线所属三角形的索引    
            if not line1_exist:       
                lines.append([tri[0], tri[1]])
                lines_belongto_tri.append([idxTri])
    
            if not line2_exist:  
                lines.append([tri[0], tri[2]])
                lines_belongto_tri.append([idxTri])
    
            if not line3_exist:       
                lines.append([tri[1], tri[2]])
                lines_belongto_tri.append([idxTri])
    
            # 获取Z的最值
            for pt in tri:
                if pt[2] < z_min:
                    z_min = pt[2]
    
                if pt[2] > z_max:
                    z_max = pt[2]
    
        return z_min, z_max, lines, lines_belongto_tri
    
    def __get_z_value_set_by_interval(self, z_min:float, z_max:float, z_interval:float):
        """根据阈值和步长得到z数组
        param: float z_min: z的最小值
        param: float z_max: z的最大值
        param: float z_interval: z的步长
        return: list z_set: z数值列表
        """
        z_set = []
        z_interval_new = int(z_interval)
        # 找到z的起始
        z_input = ceil(z_min/z_interval_new) * z_interval_new
        while True:
            if z_input > z_max:
                break
    
            z_set.append(z_input)
    
            z_input += z_interval_new
    
        return z_set

    def __get_inter_pts_between_lines_and_z_plane(self, lines:list, z:float):
        """获取线集和z平面的交点
        param: list lines: 线段集合
        param: float z: 高程值
        return: list pts: 交点集合
        return: list pts_belongto_line: 每个点所属的线段索引号
        """
        pts = []
        pts_belongto_line = []
        for idx, line in enumerate(lines):
            # 判断z是否在线段之内
            if line[0][2] <= line[1][2]:
                pt = self.__inter_by_z(line[0], line[1], z)
            else:
                pt = self.__inter_by_z(line[1], line[0], z)
    
            if pt != None:
                pts.append(pt)
                pts_belongto_line.append(idx)
    
        return pts, pts_belongto_line

    def __inter_by_z(self, pt_min:float, pt_max:float, z:float):
        """计算线段与z平面的交点
        param: list pt_min: 线段中z较小的点
        param: list pt_max: 线段中z较大的点
        param: float z: z值
        return: list pt: 交点
        """
        pt = None
        if pt_min[2] != pt_max[2] and z >= pt_min[2] and z <= pt_max[2]:
            rate = (z - pt_min[2])/(pt_max[2] - pt_min[2])
            x = pt_min[0] + rate*(pt_max[0] - pt_min[0])
            y = pt_min[1] + rate*(pt_max[1] - pt_min[1])
            pt = [x, y, z]
    
        return pt

    def __get_next_pt(self, start:int, uses_pt:list, pts_belongto_line:list, lines_belongto_tri:list, contour:list):
        """获取同一个三角形的其他边的交点
        param: int start: 交点的索引号
        param: list uses_pt: 交点是否被使用的标记
        param: list pts_belongto_line: 交点所属的线段索引号
        param: list lines_belongto_tri: 线段所属的三角形索引号
        param: list contour: 由点索引号组成的等高线数据
        """
        contour.append(start)
        uses_pt[start] = True
    
        for cur_tri_idx in lines_belongto_tri[pts_belongto_line[start]]:
            for idx_pt, use_pt in enumerate(uses_pt):
                if use_pt:
                    continue
        
                for idx_tri in lines_belongto_tri[pts_belongto_line[idx_pt]]:
                    if cur_tri_idx == idx_tri:
                        self.__get_next_pt(idx_pt, uses_pt, pts_belongto_line, lines_belongto_tri, contour)
                        return
    

五 算法测试

使用python绘制三角网以及等高线如下图所示,结果正常。

# pip install matplotlib
# file: test.py
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
from tin import Tin


# 参数准备:stl文件路径以及等高线的z间隔
path = "test.stl"
z_interval = 2

# 构建Tin类
obj = Tin()

# 读取stl文件中的三角网数据
obj.init_from_stl(path)

# 根据z间隔计算等高线
contour = obj.generate_contour(z_interval)

# 获取包围盒(绘制的时候用于控制显示范围)
box = obj.get_box()

# 获取用于绘制三角网数据
triangles = obj.get_tin()

# 初始化三维绘制视图
fig = plt.figure()
# ax = fig.gca(projection="3d")
ax = fig.add_subplot(projection="3d")

# 绘制三角网
ax.add_collection(Poly3DCollection(triangles))

# 绘制等高线
for polyline in contour:
    polyline_x = []
    polyline_y = []
    polyline_z = []

    for point in polyline:
        polyline_x.append(point[0])
        polyline_y.append(point[1])
        polyline_z.append(point[2])
    
    ax.plot(polyline_x, polyline_y, polyline_z, color="red")

# 设置视图的视角范围
ax.set_xlim([box[0][0], box[1][0]])
ax.set_ylim([box[0][1], box[1][1]])
ax.set_zlim([box[0][2], box[1][2]])

# 显示视图
plt.show()

在这里插入图片描述

  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
### 回答1: ArcGIS是一款功能强大的地理信息系统软件,可以用于处理和分析各种地理数据。在使用ArcGIS时,我们可以通过将Excel数据导入到软件中,并利用其中的高程数据来生成等高线。 首先,我们需要确保Excel数据的格式符合ArcGIS的要求。我们可以将Excel数据保存为.csv格式,或使用ArcMap中的文件导入向导直接导入Excel文件。导入Excel数据后,我们需要指定高程字段,该字段包含每个点的高程信息。 然后,我们需要创建一个TIN(地形不规则网络)数据集,该数据集将用于生成等高线。在ArcGIS中,我们可以通过选择Tools菜单下的Data Management Tools,然后选择TIN菜单下的Create TIN工具来创建TIN数据集。在创建TIN数据集时,我们需要指定输入的高程字段和输出的TIN文件的位置。 接下来,我们可以使用TIN数据集来生成等高线。使用ArcToolbox里的Spatial Analyst Tools,我们可以选择Surface菜单下的Contour工具。在Contour工具中,我们需要指定输入的TIN文件和输出的等高线文件的位置。我们还需要指定等高线的间隔距离,这取决于我们希望生成等高线的精度和密度。 最后,单击运行按钮,ArcGIS将根据Excel数据中的高程信息和指定的间隔距离,生成相应的等高线图层。我们可以通过在ArcMap中加载生成的图层来查看和分析结果。 总而言之,通过ArcGIS的功能,我们可以方便地将Excel数据转换为地理数据,并利用其中的高程信息来生成等高线。这为我们提供了一个强大的工具,用于对地理数据进行处理和分析。 ### 回答2: ArcGIS是一种强大的地理信息系统软件,可以根据Excel数据生成等高线。 首先,我们需要确保Excel数据中包含高程信息。在Excel表格中,我们可以将高程数据放置在一列中,每个数据对应着地理位置。 接下来,我们需要将Excel数据导入到ArcGIS中。在ArcGIS中,我们可以通过创建点要素来代表每个地理位置,并将高程数据与每个点要素关联起来。可以使用"Add XY Data"工具将Excel中的数据导入到ArcGIS中。 然后,我们可以使用ArcGIS中的插值工具来生成等高线。插值工具可以通过使用Excel中的高程数据来推算出缺失位置的高程值,并创建连续的等高线。 在插值工具中,我们需要选择合适的方法来插值。常用的方法包括逆距离加权(IDW)、克里金(Kriging)等。这些方法可以根据附近的已知高程点来推算未知点的高程值。 一旦生成等高线,我们可以对其进行样式和标签设置,以便更好地展示地形特征。我们可以修改线型、颜色和标注等属性。 最后,我们可以输出生成等高线图层为各种常见的地图格式,比如shapefile或者GeoTIFF等。 总结来说,ArcGIS可以根据Excel数据生成等高线的过程包括数据导入、插值和样式设置。通过这些步骤,我们可以利用ArcGIS强大的功能来生成高质量的等高线图,帮助我们更好地理解和分析地形数据。 ### 回答3: ArcGIS 是一款强大的地理信息系统(GIS)软件,它提供了许多功能来分析和展示地理数据。要根据 Excel 数据生成等高线,可以按照以下步骤进行操作。 1. 准备 Excel 数据:确保您的 Excel 数据包含高程或海拔信息,通常用于生成等高线图。确保该列以数字形式呈现,并且只有一列。 2. 导入 Excel 数据:打开 ArcGIS 并导入您的 Excel 数据。这可以通过“添加数据”工具栏中的“添加 XY 数据”选项完成。选择导入的 Excel 文件,并正确设置数据字段选项。 3. 创建注记:在导入数据之后,如果您需要在生成等高线图上添加高程标签或注记,可以创建注记。在制图之前,您可以选择相应的图层和地图元素进行创建注记。 4. 生成等高线:在 ArcGIS 中,生成等高线是通过创建 TIN(三角不规则网络)来完成的。首先,确保您已激活 Spatial Analyst (空间分析)扩展。进入 "ArcToolbox"(工具箱)中的 "Spatial Analyst Tools"(空间分析工具)并选择 "Conversion"(转换)下的 "To Raster"(转换为栅格)工具。 5. 设置参数:在 "To Raster"(转换为栅格)对话框中,选择正确的输入 points ,然后设置输出的栅格数据集和分辨率。确保选择 "Contour"(等高线)为输出栅格类型。 6. 运行工具:单击 "OK" 按钮运行工具,ArcGIS 将生成一个包含等高线的栅格数据集。 7. 制作等高线图:将生成的栅格数据集添加到 ArcMap 中,并根据您的需求设置样式、注记和其他图层设置。根据需要进行进一步的图形或地理处理来增强等高线图的表现力。 总结:通过以上步骤,您可以使用 ArcGIS 根据 Excel 数据生成等高线图。ArcGIS 提供了强大的分析和制图功能,使得生成等高线变得更加简单和灵活。了解和掌握 ArcGIS 的功能可以帮助您更好地分析和展示地理数据。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小何又沐风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值