轉載請註明作者及出處,謝謝
上文提到了等值線追蹤解決方案,在此基礎上,我們就可以把等值線畫出來了,但是只光禿禿的線條,沒有標註還是不行的,別人哪知道那條像蚯蚓一樣的線條代表什麼呢,本文我們就來討論下如何在等值線上進行標註。
感謝《等值線標註的一種演算法探討》一文的作者,我正在是使用這篇論文中的重要演算法指導了我的工作。
首先標註那些小的封閉式的等值線。
這裡我也沒有想出來好的方法,就使用方法,找出封閉式等值線中點座標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#繪製等值線的工作基本完成。