[原]C#绘制等值线三 等值线标注
时间:2011-05-04 来源:think8848
转载请注明作者及出处,谢谢
上文提到了等值线追踪解决方案,在此基础上,我们就可以把等值线画出来了,但是只光秃秃的线条,没有标注还是不行的,别人哪知道那条像蚯蚓一样的线条代表什么呢,本文我们就来讨论下如何在等值线上进行标注。
感谢《等值线标注的一种算法探讨》一文的作者,我正在是使用这篇论文中的重要算法指导了我的工作。
首先标注那些小的封闭式的等值线。
这里我也没有想出来好的方法,就使用方法,找出封闭式等值线中点坐标X最小值,Y最小值,X最大值及最大值;如果XMax - XMin < 指定值以及YMax - YMin < 指定值,则在P((XMin + XMax) / 2,(YMin + YMax) / 2)处把等值线的值画出来,如下代码所示:
if (points[0].X == points[points.Count - 1].X && points[0].Y == points[points.Count - 1].Y) { float xMin = points.Min<VPoint>(p => p.X), xMax = points.Max<VPoint>(p => p.X), yMin = points.Min<VPoint>(p => p.Y), yMax = points.Max<VPoint>(p => p.Y); if (xMax - xMin < 25 && yMax - yMin < 25) { g.DrawString(value.ToString(), font, brush, (xMax + xMin - sf.Width) / 2f, (yMax + yMin - sf.Height) / 2f); continue; } }
效果如图中红圈所示:
图1
把过于小的封闭式等值线标注后,大点的封闭式和开放式等值线的标注方法就一样了。
根据《等值线标注的一种算法探讨》一文中的指导思想,需要把曲线转换为折线或是多边形,啥意思咧,看下图所示:
如果我告诉你,现在如果每条线段的长度大于高程值的字符串(value.ToString())所需的长度 - 空白长度,就在那里画一个标注应该就问题不大了吧,对于那些太小的线段,就不要去画了,小到不像话的封闭式等值线,我们在上一步中已经处理过了。
那么如何把一条曲线转换为折线或是多边形的呢?《等值线标注的一种算法探讨》一文中告诉我们(见论文2.2):“设一个dif参数,用来控制多边形近似等值线的误差,将曲线上的点从第0个开始,偶数点相连作为多边形的边,依次查看等值线上的3个点,如果中间的等值点(奇数点)到多边形的边的距离小于参数dif,舍弃中间的等值点,否则保存中间的点。如此不断循环,直到最后生成的多边形的边数不在(再 think8848注)减少为止。这样就得到了等值线近似的多边形。”dif越大,表明曲线越陡,dif越小,表明曲线越平缓。
算法代码示例:
int n = (points[0].X != points[points.Count - 1].X && points[0].Y != points[points.Count - 1].Y) ? points.Count : points.Count - 1, minEdge = n + 1, k = n; float tolerance = 8f; var indexes = new List<int>(); for (int i = 0; i < points.Count; i++) { indexes.Add(i); } while (k < minEdge) { minEdge = k; var p = 0; while (p < minEdge - (n % 2 == 0 ? 3 : 2)) { float straight = this.GetPointToStraight(points[indexes[p + 1]], points[indexes[p]], points[indexes[p + 2]]); if (Math.Abs(straight) < tolerance) { indexes[p + 1] = -1; k -= 1; } p++; p++; } indexes = indexes.Where<int>(index => index != -1).ToList<int>(); }
另附求点到直线的距离的方法,下列代表示例如何计算p点到直线p1p2最短距离
private float GetPointToStraight(VPoint p, VPoint p1, VPoint p2) { if (p1.X == p2.X) { return (float)Math.Abs(p1.Y - p2.Y); } if (p1.Y == p2.Y) { return (float)Math.Abs(p1.X - p2.X); } double k = (p2.Y - p1.Y) / (p2.X - p1.X); double c = (p2.X * p1.Y - p1.X * p2.Y) / (p2.X - p1.X); return (float)((k * p.X - p.Y + c) / (Math.Sqrt(k * k + 1))); }
最终indexes里面保存了在等值点列表中构成多边形的点的索引。
在本文的最后,我们再来谈谈如果标注字符旋转的问题,有一条线段作为基准,将画布旋转与线段倾斜角度相同度数应该不是一件难事,是的,.NET很容易就能做到,唯一的问题是如何线段的倾斜角度:
var alpha = (float)(Math.Atan((p2.Y - p1.Y) / (p2.X - p1.X)) * 180 / Math.PI);
就是这个角度了,三角函数已经忘了的兄弟可以到网上重新温习下高中数学,呵呵。
g.TranslateTransform(xOffset, yOffset); g.RotateTransform(alpha); g.DrawString(value.ToString(), font, brush, new PointF(0, 0)); g.RotateTransform(-alpha); g.TranslateTransform(-xOffset, -yOffset);
变换坐标系的原点到将要绘制标注的左上角位置,然后旋转画布,(注意,角度为正时为顺时针,角度为负时为逆时针方向,好像和我们数学课中的方向不一致。)绘制完成后,再把坐标系归位,循环,直至将所有的标注都绘制完成。
至此,使用C#绘制等值线的工作基本完成。