天天看點

C#窗體繪制等值線

繪制等值線的基本步驟基本上都是差不多的(當然每個人會有些差别因個人而異)(PS:下面的是矩陣點的計算不是離散點的計算)

資料處理->擷取網格資料->通過三角剖分法計算等值線(當然還有其他的各種剖分方式)->繪制等值線(在繪制之前肯定會記錄等值線的一些資料友善之後進行追蹤)->填充等值線

關于等值線的一些基礎概念和三角剖分的基本思想可以參考這篇->
[http://www.cnblogs.com/think8848/archive/2011/04/29/2032751.html](http://www.cnblogs.com/think8848/archive/2011/04/29/2032751.html)
           

我就簡單的表示一下三角剖分法:

- 圖檔連結和圖檔上傳

C#窗體繪制等值線

- 這個可以很清楚的看出來,當有三條等值線(5,15,25)要繪制的時候,這個最小三角形就肯定能畫出這樣子的2條,當我們把整個區域劃分為很多的三角形的時候,然後每個三角形都這樣子判斷過去循環所有的等值線,能繪制的就繪制不能繪制的就跳過,當繪制完成的時候就能等到等值線了,當劃分的等值線越多精度越高,網格點越多精度越高。

/// <summary>
    /// 資料結構
    /// </summary>

    public class DataPoint
    {

    /// <summary>
    /// 坐标X
    /// </summary>
    public float X;
    /// <summary>
    /// 坐标Y
    /// </summary>
    public float Y;
    /// <summary>
    /// 溫度T
    /// </summary>
    public float T;
    /// <summary>
    /// 矩陣坐标i
    /// </summary>

    public float PosI;
    /// <summary>
    /// 矩陣坐标j
    /// </summary>
    public float PosJ;
    /// <summary>
    /// 構造函數
    /// </summary>
    /// <param name="x">坐标X</param>
    /// <param name="y">坐标Y</param>
    /// <param name="t">溫度T</param>

    public DataPoint(float x, float y, float t)
    {
        X = x;
        Y = y;
        T = t;
    }

    /// <summary>
    /// 預設構造函數
    /// </summary>
    public DataPoint()
    {

    }
    /// <summary>
    /// 判斷2個對象的XY是否相等
    /// </summary>
    /// <param name="point">點坐标</param>
    /// <returns></returns>
    public bool EqualXY(Point point)
    {
        return (this.X == point.X && this.Y == point.Y) ? true : false;
    }
    /// <summary>
    /// 判斷2個對象的共有值是否都相等
    /// </summary>
    /// <param name="left">左邊對象</param>
    /// <param name="right">右邊對象</param>
    /// <returns>是否相等</returns>
    public static bool operator ==(DataPoint left, DataPoint right)
    {
        if (left.X == right.X && left.Y == right.Y && left.T == right.T)
            return true;
        else
            return false;
    }
    /// <summary>
    /// 判斷2個對象的共有值是否不等
    /// </summary>
    /// <param name="left"></param>
    /// <param name="right"></param>
    /// <returns></returns>
    public static bool operator !=(DataPoint left, DataPoint right)
    {
        if (left.X != right.X || left.Y != right.Y || left.T != right.T)
            return true;
        else
            return false;
    }
    /// <summary>
    /// 哈希值
    /// </summary>
    /// <returns></returns>
    public override int GetHashCode()
    {
        return base.GetHashCode();
    }
    /// <summary>
    /// 是否指向同一個對象位址
    /// </summary>
    /// <param name="obj"></param>
    /// <returns></returns>
    public override bool Equals(object obj)
    {
        return base.Equals(obj);
    }
    }
           

-這是我的代碼中作為傳入資料點的類的結構,X,Y作為在外面指派好的原始坐标,後面要轉換到PosI和PosJ中 當然如果已經計算好的可以直接指派PosI和PosJ(後面簡稱I和J),I和J的含義是指這個資料點是矩陣點的第幾行和第幾列的點。

/// <summary>
        /// 等值線分析及繪圖
        /// </summary>
        /// <param name="size"></param>
        private void CurLines(Size size)
        {
            DataPoint maxPoint = new DataPoint();  ///三角網格中最大溫度值的點
            DataPoint midPoint = new DataPoint();  ///三角網格中中間溫度值的點
            DataPoint minPoint = new DataPoint();  ///三角網格中最低溫度值的點
            int pos1, pos2, pos3;
            float temp = ;

            for (int j = ; j < Data.GetLength() - ; j++)
            {
                for (int i = ; i < Data.GetLength() - ; i++)
                {
                    //網格四個角坐标,變量值
                    CurPoints[] = Data[i, j];
                    CurPoints[] = Data[i + , j];
                    CurPoints[] = Data[i + , j + ];
                    CurPoints[] = Data[i, j + ];

                    CurPoints[] = new DataPoint();
                    CurPoints[].T = F * (CurPoints[].T + CurPoints[].T + CurPoints[].T + CurPoints[].T);
                    CurPoints[].PosI = F * (CurPoints[].PosI + CurPoints[].PosI);
                    CurPoints[].PosJ = F * (CurPoints[].PosJ + CurPoints[].PosJ);
                    pos3 = ;

                    //巡檢單元格範圍内所有的點資訊
                    for (pos1 = ; pos1 < ; pos1++)
                    {
                        pos2 = pos1 + ;
                        if (pos1 == ) pos2 = ;
                        PointF p1, p2;  ///繪制用的點

                        OrderDataPoint(CurPoints[pos1], CurPoints[pos2], CurPoints[pos3], ref maxPoint, ref midPoint, ref minPoint);
                        //巡檢點範圍内所有的點資訊
                        for (int lines = ; lines < ContourValues.Length; lines++)
                        {
                            temp = ContourValues[lines];

                            if (minPoint.T < temp && temp < maxPoint.T)
                            {
                                p1 = interp(minPoint, maxPoint, temp, this.Size);
                                if (temp > midPoint.T)
                                {

                                    p2 = interp(midPoint, maxPoint, temp, this.Size);
                                }
                                else
                                {
                                    p2 = interp(minPoint, midPoint, temp, this.Size);
                                }
                                Drawings.Add(new DrawingPoints(p1, p2, GetTempColor(temp), temp));
                            }
                        }
                    }
                }
            }
        }
           

這段代碼就是利用上面圖上的三角剖分法去計算等值線的函數,最重要的在計算的時候一定要吧相鄰的點儲存下來Drawings.Add(new DrawingPoints(p1, p2, GetTempColor(temp), temp));。。。。這裡就是吧等值線最小機關線段儲存下來,資料有線段的2個端點,該線段的等值線的值,以及繪制的顔色。下面就是這個儲存線段的類的結構

/// <summary>
    /// 畫等值線時候的點的集合
    /// </summary>
    public class DrawingPoints
    {
        /// <summary>
        /// 起始點
        /// </summary>
        public PointF StartPoint;
        /// <summary>
        /// 結束點
        /// </summary>
        public PointF EndPoint;
        /// <summary>
        /// 等值線的值
        /// </summary>
        public float Value;
        /// <summary>
        /// 2個點之間的連線的顔色
        /// </summary>
        public Color LineColor;
        /// <summary>
        /// 計算的時候使用表示是否被使用過
        /// </summary>
        public bool IsUser;
        /// <summary>
        /// 構造函數
        /// </summary>
        /// <param name="p1">起始點</param>
        /// <param name="p2">結束點</param>
        public DrawingPoints(PointF p1, PointF p2, Color color, float Value = , bool IsUser = false)
        {
            StartPoint = p1;
            EndPoint = p2;
            LineColor = color;
            this.Value = Value;
            this.IsUser = IsUser;
        }
        /// <summary>
        /// 設定是否使用過
        /// </summary>
        /// <param name="isUser"></param>
        public void SetUser(bool isUser)
        {
            this.IsUser = isUser;
        }
    }
           

由于上面我們儲存下來所有等值線的最小機關:線段以及線段對應的等值線的值。這樣下面就可以開始檢索。根據等值線的值把所有的和該等值線的值相同的線段找出來,由于線段都是2個端點的所有我們隻要根據2根線段首位相同就可以吧線段的點組合起來這樣就能變成一條完整的等值線。

C#窗體繪制等值線

-根據上圖可以吧同樣值的等值線線段 連接配接起來就是等值線。

/// <summary>
        /// 檢索
        /// </summary>
        /// <param name="listPoints"></param>
        /// <param name="firstPoint"></param>
        /// <param name="temp"></param>
        /// <param name="isUpSideNotSide">當不是邊界的時候也需要倒置一次進行尋找</param>
        protected bool FindNode(List<PointF> listPoints, ref PointF firstPoint, float temp, bool isUpSideNotSide = false)
        {
            for (int i = ; i < Drawings.Count; i++)
            {
                DrawingPoints point = Drawings[i];
                if (point.Value == temp && !point.IsUser)
                {
                    if (firstPoint.X == -)
                    {
                        listPoints.Add(point.StartPoint);
                        listPoints.Add(point.EndPoint);
                        firstPoint = point.EndPoint;
                        point.IsUser = true;
                        return true;
                    }
                    else if (GetEqualPointF(firstPoint, point.StartPoint))
                    {
                        listPoints.Add(point.EndPoint);
                        firstPoint = point.EndPoint;
                        point.IsUser = true;
                        ///檢測邊界
                        return IsUpSide(ref firstPoint, ref listPoints);
                    }
                    else if (GetEqualPointF(firstPoint, point.EndPoint))
                    {
                        listPoints.Add(point.StartPoint);
                        firstPoint = point.StartPoint;
                        point.IsUser = true;
                        ///檢測邊界                   
                        return IsUpSide(ref firstPoint, ref listPoints);
                    }
                }
            }
            /*  加快運作速度
            ///當不是邊界的時候也需要倒置一次進行尋找
            if (!isUpSideNotSide && listPoints.Count > 1)
            {
                firstPoint = listPoints[0];
                Tools.UpsideArray(ref listPoints);
                FindNode(listPoints, ref firstPoint, temp, true);
            }
            */
            return false;
        }
           

這個是檢索函數,這個隻是部分 (原先寫的遞歸的方式檢索的感覺不是很理想後來改成循環檢索)檢索完後 吧相對應的點都組合成了對應的等值線,當然同一個值可能有多條等值線,是以檢索的時候當我們随着一個線段的一端檢索成一個環的時候就可以退出 或者到達邊界,到達邊界的情況必須是2端都是邊界,是以當一端到達邊界的時候需要倒置然後再次檢索。

這樣一個值的等值線最後出來的結構是PointF [] [] pointfs 這樣的二維數組。

C#窗體繪制等值線

最後得出的等值線的情況就是上圖所示的情況,不管是那種情況我們都可以讓它們和自己或者和邊界形成一個多邊形,不管是凸多邊形還是凹多邊形。這時候我們可以計算多變的面積

C#窗體繪制等值線

這個就是任意多邊形的面積公式,推導過程我就不再說明了網上自行查閱,我也是看來的。

最後吧我們得到的圖形面積計算,然後排序将面積從大到小排列,最後依次填充就完成所有工作了。

如果有問題,歡迎指出我會及時修改。謝謝!

PS:轉載請注明出處。