转载请注明作者及出处,谢谢

上文提到了绘制等值线的一些基本原理及构建三角网的思路,本文将着重介绍等值线追踪方法。

在我一开始的想法中,绘制等值线肯定就是把所有具有相同值的点连接起来就OK了,一想那不是一个蜘蛛网嘛,不同高程值的等值线都交叉了,那还叫什么等值线?了解到使用三角网剖分方法来生成等值线后,又是以为三角形延着三个点来游走就能得到等值线,又一想不行,因为如果值恰好在某一个点上时,那到底向哪条边游走呢?最重要的是,如果我要绘制照度为500的等值线,如果三角网中所有的点上的值没有500怎么办?那岂不是很滑稽:有600,有400,但是没有500这条线,这就好比有爷爷,也有孙子,但是没有儿子,那孙子是从哪儿来的呢?这才发现原来人家早提出方案了,使用插值法,更重要一点是论文中还特意使用等值线不从三角网上的点通过,为什么?因为从点上过的话,等值线的方向不好计算。

知道了等值线都是与三角形的边交叉后(当然,除了三角网的边框),这里还需要对等值线的另一个特性有个了解,即等值线按照其类型可以分为开放式和封闭式。所谓开放式是指,等值线的两个端点都在三角网的边框上,所谓封闭式是指,等值线是一个闭环,起点同时也是终点。

假定我们需要绘制高程值为500的等值线,那么在三角网中追踪等值线的基本思路是:先查找所有开放式等值线,然后再查找封闭式等值线。开放式等值线的具体作法为:

1. 将所有至少两个边都包含500这个值的三角形找出来放到一个列表valTrangles中,判断一个边上是否包含500这个值的点的方法:

图1

            //为了避免边的两个端点的值正好等于指定值,从而造成难以判断等值线方向
            //因此当端点值正好等于指定值时给端点值减去一个很小的正数,使指定值所
            //在的点和两个端点不重合
            decimal tolerance = 0.000001M;

            decimal value1 = edge.P1.Value, value2 = edge.P2.Value;
            if (value1 == value) { value1 -= tolerance; }
            if (value2 == value) { value2 -= tolerance; }

            return (value1 - value) * (value2 - value) <= 0;

从图1和上面的代码中我们可以清楚的看到,如果V点介于V1和V2的话,那么value1 - value和value2 - value中必然有一个是负数,从而使代码返回值是true。

2. 从valTrangle中任意一个三角网边框边的三角形开始,判断这个边上是否具有500这个点,如果有,记录当前三角形以及当前边,并将该点添加到等值线构成点列表(var Contour = new List<VPoint>())中,开始在这个三角形剩下的两个边上查找“出路”,(我的代码没有处理剩余的两条边上同时具有指定高程值的情况,但是直至目前没有出现不合理的等值线。)。这里有一个问题:知道一个边上有某个点,那么这个点的坐标是多少呢?因为我们的三角形的边长度比较小,所以我们可以利用两个点,通过线性关系得到指定值所在的坐标:

                var factor = (float)((value - edge.P1.Value) / (edge.P2.Value - edge.P1.Value));
                result.X = edge.P1.X + factor * (edge.P2.X - edge.P1.X);
                result.Y = edge.P1.Y + factor * (edge.P2.Y - edge.P1.Y);

3. 找到第一个“出口”点后,将该点添加到Contour中,并将当前三角形标记为-1,即临时已使用。然后使用当前边为“出口”点所在的边,使当前三角形为当前边的另一个所属三角形,还记得我们Edge类型中的Trangle1和Trangle2的定义吧。

  currentTrangle = currentTrangle == currentEdge.Triangle1 ? currentEdge.Triangle2 : currentEdge.Triangle1;

4.在得到当前三角形,当前边后循环调用第3步,又会得到第3个等值线的点,依照这个方法,最终会有两种可能,a:某个边只有属于一个三角形(到达三角网的边框了);b:到达的三角形的UsedStatus为-1(这个三角形已经被前面的步骤使用过了)。这时退出循环。

这个时候,对于某个值的开放式的等值线就已经查找完了,这里使用一个边所属的两个三角形这种方法,可以在很大程度上降低使用点阵获取三角形和边的麻烦,那种方法虽然谈不上非常困难,但是一大堆的索引值足以使你心烦意乱。

查找封闭式等值的方法和开放式基本上没有区别,所不同的只是退出条件为找到的出口和等值线第一点的坐标相同而已。

这里给出核心代码(同时包含开放式和封闭式):

        private IList<VPoint> GetContour(IList<Triangle> triangles, Edge currentEdge, decimal value)
        {
            IList<VPoint> result = new List<VPoint>();

            var firstEdge = currentEdge;
            var currentTrangle = currentEdge.Triangle1;
            var currentPoint = this.TryGetContourPoints(currentEdge, value);
            result.Add(currentPoint);

            while (true)
            {
                currentTrangle.UsedStatus = -1;

                var edges = currentTrangle.Get2OtherEdge(currentEdge);
                currentPoint = this.TryGetContourPoints(edges[0], value);

                int useEdge = 0;

                if (currentPoint == null)
                {
                    currentPoint = this.TryGetContourPoints(edges[1], value);
                    useEdge = 1;
                }

                result.Add(currentPoint);
                currentEdge = edges[useEdge];
                currentTrangle = currentTrangle == currentEdge.Triangle1 ? currentEdge.Triangle2 : currentEdge.Triangle1;

                if (currentTrangle == null || currentTrangle.UsedStatus == -1 || currentTrangle.UsedStatus == 1)
                {
                    int usedStatus = 0;

                    //开放式等值线 
                    if ((firstEdge.IsBorder && currentEdge.IsBorder && result.Count >= 2) ||
                        //封闭式等值线
                        (firstEdge.IsBorder == false && currentTrangle != null && currentTrangle.UsedStatus == -1 && result.Count >= 6 && currentEdge == firstEdge))
                    {
                        usedStatus = 1;
                        this.CompleteContour(triangles, usedStatus);
                    }
                    else
                    {
                        this.CompleteContour(triangles, usedStatus);
                        result = null;
                    }

                    break;
                }
            }

            return result;
        }

当查找结束后,如果等值线合法,即开放式到达了边框,封闭式到达了起点,则“提交”一下候选的三角形,使用从临时使用状态回退到未使用状态1,不再参与下次查找,反之,则“回滚”一下候选三角形,使用从临时使用状态回退到未使用状态0。这里还有一个“保险”,开放式的等值线,一定是大于等于2个点,封闭式的等值线,一定是大于等于6个点。

至此,三角形追踪完成,稍事休息,接下来讲等值线标注

posted on 2011-05-03 16:56  think8848  阅读(7198)  评论(5编辑  收藏  举报