36

我一直致力于二维连续数据的可视化项目。这是您可以用来研究 2D 地图上的高程数据或温度模式的东西。从本质上讲,它实际上是一种将 3 维扁平化为 2 维加颜色的方法。在我的特定研究领域中,我实际上并没有使用地理高程数据,但这是一个很好的比喻,所以我将在这篇文章中坚持使用它。

无论如何,在这一点上,我有一个我非常满意的“连续颜色”渲染器:

连续彩色渲染器

渐变是标准色轮,其中红色像素表示坐标值较高,紫色像素表示坐标值较低。

底层数据结构使用一些非常聪明的(如果我自己这么说的话)插值算法来实现对地图细节的任意深度放大。

在这一点上,我想绘制一些地形轮廓线(使用二次贝塞尔曲线),但我还没有找到任何好的文献来描述找到这些曲线的有效算法。

为了让您了解我的想法,这是一个穷人的实现(渲染器在遇到与轮廓线相交的像素时只使用黑色 RGB 值):

带有贫民区地形线的连续颜色

但是,这种方法存在几个问题:

  • 具有较陡坡度的图形区域会导致地形线较细(并且通常是断线)。理想情况下,所有地形线都应该是连续的。

  • 具有更平坦斜率的图形区域会导致更宽的地形线(通常是整个黑色区域,尤其是在渲染区域的外围)。

所以我正在寻找一种矢量绘图方法来获得那些漂亮的、完美的 1 像素厚的曲线。算法的基本结构必须包括以下步骤:

  1. 在我要绘制地形线的每个离散高程处,找到一组坐标,其中该坐标处的高程与所需的高程非常接近(给定任意 epsilon 值)。

  2. 消除多余的点。例如,如果三个点在一条完全直线上,则中心点是多余的,因为可以在不改变曲线形状的情况下消除它。同样,对于贝塞尔曲线,通常可以通过调整相邻控制点的位置来消除某些锚点。

  3. 将剩余的点组装成一个序列,使得两点之间的每个线段都近似于高度中性轨迹,并且没有两条线段会交叉路径。每个点序列必须要么创建一个封闭的多边形,要么必须与渲染区域的边界框相交。

  4. 对于每个顶点,找到一对控制点,使得结果曲线相对于步骤 #2 中消除的冗余点显示出最小误差。

  5. 确保在当前渲染比例下可见的所有地形特征都由适当的地形线表示。例如,如果数据包含高海拔但直径极小的尖峰,则仍应绘制地形线。仅当特征直径小于图像的整体渲染粒度时,才应忽略垂直特征。

但即使在这些限制下,我仍然可以想到几种不同的启发式方法来寻找线条:

  • 找到渲染边界框内的高点。从那个高点开始,沿着几条不同的轨迹下坡。每当遍历线越过高程阈值时,将该点添加到特定于高程的存储桶中。当遍历路径达到局部最小值时,改变路线并上坡。

  • 沿渲染区域的矩形边界框执行高分辨率遍历。在每个高程阈值处(以及在拐点处,无论坡度反转方向),将这些点添加到特定于高程的存储桶中。完成边界遍历后,从这些桶中的边界点开始向内追踪。

  • 扫描整个渲染区域,以稀疏的规则间隔进行高程测量。对于每个测量,使用它与高程阈值的接近度作为决定是否对其邻居进行插值测量的机制。使用这种技术可以更好地保证整个渲染区域的覆盖,但是很难将结果点组装成合理的顺序来构建路径。

所以,这些是我的一些想法......

在深入研究实现之前,我想看看 StackOverflow 上的其他人是否有解决此类问题的经验,并且可以为准确有效的实现提供指导。

编辑:

我对 ellisbben 提出的“渐变”建议特别感兴趣。而我的核心数据结构(忽略一些优化插值快捷方式)可以表示为一组二维高斯函数的总和,这是完全可微的。

我想我需要一个数据结构来表示一个三维斜率,以及一个用于计算任意点的斜率向量的函数。在我的脑海中,我不知道该怎么做(虽然看起来应该很容易),但如果你有一个解释数学的链接,我会非常感激!

更新:

感谢 ellisbben 和 Azim 的出色贡献,我现在可以计算该领域中任意点的轮廓角。绘制真正的地形线将很快出现!

这是更新的渲染,有和没有我一直在使用的基于 ghetto raster 的拓扑渲染器。每个图像包含一千个随机样本点,用红点表示。该点的轮廓角由一条白线表示。在某些情况下,在给定点无法测量斜率(基于插值的粒度),因此红点出现时没有相应的等高线角度线。

享受!

(注意:这些渲染使用与以前的渲染不同的表面形貌——因为我在每次迭代时随机生成数据结构,而我正在制作原型——但核心渲染方法是相同的,所以我相信你得到这个想法。)

替代文字

替代文字

这是一个有趣的事实:在这些渲染图的右侧,您会看到一堆奇怪的轮廓线,它们处于完美的水平和垂直角度。这些是插值过程的产物,它使用插值器网格来减少执行核心渲染操作所需的计算数量(约 500%)。所有这些奇怪的轮廓线都出现在两个插值器网格单元之间的边界上。

幸运的是,这些文物实际上并不重要。尽管在斜率计算期间可以检测到伪影,但最终渲染器不会注意到它们,因为它以不同的位深度运行。


再次更新:

啊啊啊,作为我睡觉前的最后一次放纵,这是另一对效果图,一个是老式的“连续颜色”风格,一个有 20,000 个渐变样本。在这组渲染中,我已经消除了点样本的红点,因为它不必要地使图像混乱。

在这里,您可以真正看到我之前提到的那些插值工件,这要归功于插值器集合的网格结构。我应该强调的是,这些伪影在最终的轮廓渲染中将完全不可见(因为任何两个相邻插值器单元之间的幅度差异小于渲染图像的位深度)。

开胃!!

替代文字

替代文字

4

9 回答 9

9

梯度是一个可以帮助你的数学运算符。

如果您可以将插值转换为可微函数,则高度的梯度将始终指向最陡峭的上升方向。所有相等高度的曲线都垂直于在该点评估的高度梯度。

您从最高点开始的想法是明智的,但如果有多个局部最大值,则可能会错过特征。

我建议

  1. 选择您将绘制线条的高度值
  2. 在精细、规则间隔的网格上创建一堆点,然后沿着渐变方向以小步长将每个点移向要绘制线条的最近高度
  3. 通过将每个点垂直于渐变来创建曲线;当另一条曲线太靠近它时,通过杀死一个点来消除多余的点 - 但为了避免破坏像图形一样的沙漏中心,您可能需要检查两个点的垂直于梯度的定向向量之间的角度。(当我说定向时,我的意思是确保梯度和您计算的垂直值之间的角度始终在同一方向上为 90 度。)
于 2008-11-04T21:45:52.053 回答
3

或者,有一个似乎适合您的问题的行进正方形算法,尽管如果您使用粗网格,您可能希望平滑结果。

您要绘制的地形曲线是二维标量场的等值面。对于 3 维的等值面,有行进立方体算法。

于 2008-11-04T21:58:56.503 回答
3

回应您对@erickson 的评论并回答有关计算函数梯度的问题。您可以按如下方式进行数值微分,而不是计算 300 项函数的导数。

给定图像中的一个点 [x,y],您可以计算梯度(最陡的方向)

g={  ( f(x+dx,y)-f(x-dx,y) )/(2*dx), 
  {  ( f(x,y+dy)-f(x,y-dy) )/(2*dy) 

其中 dx 和 dy 可能是网格中的间距。等高线将垂直于渐变。因此,要获得轮廓方向 c,我们可以将 g=[v,w] 乘以矩阵 A=[0 -1, 1 0] 给出

c = [-w,v]
于 2008-11-04T23:35:36.963 回答
2

我自己也想要这样的东西,但还没有找到基于矢量的解决方案。

不过,基于栅格的解决方案并不是那么糟糕,尤其是在您的数据是基于栅格的情况下。如果你的数据也是基于矢量的(换句话说,你有一个表面的 3D 模型),你应该能够做一些真正的数学运算来找到不同高度的水平面的相交曲线。

对于基于光栅的方法,我查看每一对相邻像素。如果一个在等高线之上,一个在等高线之下,显然它们之间有一条等高线。我用来消除轮廓线锯齿的技巧是将轮廓线颜色混合到两个像素中,与它们与理想轮廓线的接近程度成正比。

也许一些例子会有所帮助。假设当前像素处于 12 英尺的“高程”,邻居处于 8 英尺的高程,等高线是每 10 英尺。那么,中间有一条等高线;用 50% 不透明度的轮廓线颜色绘制当前像素。另一个像素在 11 英尺处,并且在 6 英尺处有一个相邻像素。以 80% 的不透明度为当前像素着色。

alpha = (contour - neighbor) / (current - neighbor)

不幸的是,我手边没有代码,而且可能还有更多代码(我隐约记得也看过对角线邻居,并通过 调整sqrt(2) / 2)。我希望这足以为您提供要点。

于 2008-11-04T22:00:56.343 回答
1

我突然想到,你想要做的事情在 MATLAB 中很容易做到,使用轮廓函数。对轮廓进行低密度近似等操作可能可以通过对轮廓进行一些相当简单的后处理来完成。

幸运的是,MATLAB 克隆版 GNU Octave 实现了各种等高线绘图功能。您可以查看该代码以获得几乎可以肯定在数学上合理的算法和实现。或者,您可能只是能够将处理卸载到 Octave。查看与其他语言交互的页面,看看是否会更容易。

披露:我没有经常使用 Octave,也没有实际测试过它的等高线绘图。但是,根据我使用 MATLAB 的经验,我可以说,只要您将数据输入 MATLAB,它只需几行代码即可为您提供几乎所有您需要的东西。

此外,祝贺你制作了一个非常 VanGough 式的坡地图。

于 2009-01-21T05:05:23.167 回答
0

在自己深入之前,我总是会检查http://mathworld.wolfram.com之类的地方 :)

也许他们的曲线部分会有所帮助?或者也许是地图上的条目。

于 2008-11-04T20:39:56.647 回答
0

将您渲染的内容与真实世界的地形图进行比较——它们看起来和我一模一样!我不会改变任何事情...

于 2008-11-04T21:03:22.340 回答
0

将数据写成HGT 文件(USGS 使用的非常简单的数字高程数据格式),并使用免费和开源的gdal_contour工具创建等高线。这对地面地图非常有效,限制是数据点是有符号的 16 位数字,非常适合地球上以米为单位的高度范围,但对于您的数据可能还不够,我认为这不是实际地形图 - 尽管您确实提到了地形图。

于 2013-04-01T21:09:36.477 回答
0

我推荐CONREC方法:

  • 创建一个空的线段列表
  • 将您的数据拆分为规则的网格方块
  • 对于每个网格正方形,将正方形分成 4 个组成三角形:
    • 对于每个三角形,处理情况(a 到 j):
      • 如果线段穿过其中一种情况:
        • 计算其端点
        • 将线段存储在列表中
  • 绘制线段列表中的每条线段

如果线条过于锯齿,请使用较小的网格。如果线条足够平滑且算法耗时过长,请使用更大的网格。

于 2013-12-18T16:42:41.173 回答