ArcGIS API for Silverlight 使用GeometryService求解線與面的交點(一)


最近在做項目的時候遇到一個問題,大致情況如下:

已知河流的面要素,需要根據用戶輸入的矩形以及設定的步長對河流進行網格划分,並得到網格與兩邊河岸的交點。

查了查資料,發現原生的ArcGIS API for Silverlight並沒有提供實現該功能的借口,但是GeometryService提供了一個類似的功能:Intersect.

Intersect:其相交的情況有如下三種:

這里需要注意的時最后一個:線與線相交,從幾何的角度來說,最后得到的結果應該是一個點,然而在GeometryService中,最后得到的結果是PolyLine,即是線,而且該線的Extent屬性為null,也就是說線段長度為0.因此如果你想通過Intersect來求解兩條線的交點實不可取的。那么這里的用處是什么呢?就是可以用來判斷交點的個數。

從上圖來看我們的情況是屬於第二種:即面與線相交。

但是我們發現,這里得到的是相交的線,而我們要的是點,這個該怎么解決呢?

其實我們再一想,就可以發現,既然我們能夠得到相交的線,那么我們是不是可以得到線的兩個端點呢?如果可以得到兩個端點是不是我們的問題就解決了呢?

答案只對了一半,因為我們再仔細一想,又會發現一個問題就是雖然我們可以得到兩個端點,但是有的端點並不是網格與河流面要素的交點,例如下圖所示:

上圖表示對河流進行划分的網格

如果按照上面的思路我們將得到如下的結果:

我們發現,雖然得到了與河流的交點但是左右兩側水平線的端點也包含了進來,而顯然它們都不是與河流的交點,那么這里該怎么去除呢?

從上圖可以知道,左右邊界上非河岸的交點都位於網格的邊緣,假設我們設網格矩形的區域為(Xmin,Ymin,Xmax,Ymax),則這些點的X坐標都為Xmin或者Xmax,所以我們由此可以去除邊界上的點,然而這里有一個問題是:

當我們按照一定的步長划分時,不一定會整分,即出現如下的情況:

我們發現上面和右邊並不是剛好等分的,上面和右面部分線條超出了“范圍”,雖然這並不影響河流的交點的提取,但是多少顯得不那么好看。

下面我們先看看構造網格的代碼:

 public void CreateGrid(double xMin,double xMax,double yMin,double yMax,double xstep,double ystep)
        {
            //定義兩個Point,確定一條直線
            MapPoint mp1;
            MapPoint mp2;
            //由步長確定划分成多少份
            int Nx = Convert.ToInt32((xMax - xMin)/xstep);
            int Ny = Convert.ToInt32((yMax - yMin) / ystep);
            //構造豎直方向的線
            for (int i = 0; i <Nx+1; i++)
            {
                mp1=CreateMapPoint(xMin+i*xstep, yMin);
                mp2=CreateMapPoint(xMin + i * xstep,yMax);
                CreateLine(mp1, mp2);
            }
            //構造水平方向的線
            for (int i = 0; i < Ny+1; i++)
            {
                mp1 = CreateMapPoint(xMin, yMin +i * ystep);
                mp2 = CreateMapPoint(xMax, yMin + i * ystep);
                CreateLine(mp1, mp2);
            }
            //RectExtent為自定義的一種結構體,存儲每一次網格划分時的矩形范圍
            recExtent = new RectExtent()
            {
                Xmin = Convert.ToDouble((xMin).ToString("#0.000")),
                Xmax = Convert.ToDouble((xMin + Nx * xstep).ToString("#0.000")),
                Ymin = Convert.ToDouble((yMin).ToString("#0.000")),
                Ymax = Convert.ToDouble((yMin + Ny * ystep).ToString("#0.000"))
            };
        }

以上的CreateMapPoint為自定義方法,表示根據X,Y坐標,新建一個點(MapPoint),示例代碼如下:

 public MapPoint CreateMapPoint(double x,double y)
        {
            return new MapPoint(x, y);
        }

CreateLine表示根據得到的兩個點,繪制一條直線:

 public void CreateLine(MapPoint p1,MapPoint p2)
        {
            ESRI.ArcGIS.Client.Geometry.Polyline polyline = new ESRI.ArcGIS.Client.Geometry.Polyline();
            ESRI.ArcGIS.Client.Geometry.PointCollection pc=new ESRI.ArcGIS.Client.Geometry.PointCollection ();
            pc.Add(p1);
            pc.Add(p2);
            polyline.Paths.Add(pc);
            //記得對空間坐標系賦值,否則Simplify會出錯。
            polyline.SpatialReference = map1.SpatialReference;
            AddLineGraphic(polyline);
            
        }

AddLineGraphic表示將生成的線添加到圖層中:

 public void AddLineGraphic(ESRI.ArcGIS.Client.Geometry.Polyline polyline)
        {
            Graphic g = new Graphic()
            {
                Geometry = polyline,
                Symbol = LayoutRoot.Resources["LineSymbol"] as SimpleLineSymbol
            };
          
            glayer.Graphics.Add(g);
        }

同理,之后的AddPointGraphic即向圖層中添加點:

 public void AddPointGraphic(ESRI.ArcGIS.Client.Geometry.MapPoint point)
        {
            Graphic g = new Graphic()
            {
                Geometry = point,
                Symbol = LayoutRoot.Resources["PointSymbol"] as SimpleMarkerSymbol,
            };

            glayer.Graphics.Add(g);
        }

這里我們實現了網格的繪制,但是你會發現得到的網格如本文第3張圖片所示。

這並不是我們所希望的,這里我們修改一下構造水平和垂直直線的代碼,如下所示:

  //構造豎直方向的線
            for (int i = 0; i <Nx+1; i++)
            {
                mp1=CreateMapPoint(xMin+i*xstep, yMin);
                //mp2=CreateMapPoint(xMin + i * xstep,yMax);
                mp2 = CreateMapPoint(xMin + i * xstep, yMin + Ny * ystep);
                CreateLine(mp1, mp2);
            }
            //構造水平方向的線
            for (int i = 0; i < Ny+1; i++)
            {
                mp1 = CreateMapPoint(xMin, yMin +i * ystep);
                mp2 = CreateMapPoint(xMin + Nx * xstep, yMin + i * ystep);
                //mp2 = CreateMapPoint(xMax, yMin + i * ystep);
                CreateLine(mp1, mp2);
            }

這里我們將Xmax改成了Xmax+Nx*Xstep,同理其他。

這樣我們就能得到閉合的網格了。

但是我們還是沒有說如何解決上面說的那個問題,上面我們說到通過Xmin和Xmax來判斷是否是河流的交點,但是實際運行程序時,是無法實現的,為什么呢?

因為我們再使用Geometry的Intersect時,再Intersect的過程中對每一個點(交線的端點)的坐標值都會進行相應的簡化(四舍五入),這樣我們在Intersect完成事件函數中獲得端點值就已經不等於Intersect之前的了,因此也就無法根據線段端點的Xmin和Xmax值是否等於邊界值來取舍交點。

具體過程可以看下面的示意圖:

因此,通過Xmin和Xmax的方式也不行。那到底該怎么辦呢?

這里我們仔細一想就會發現,Intersect之后的Xmin和Xmax和實際上的X范圍很相近,因為Intersect實際上就是對X范圍進行了四舍五入。

而一般來說Intersect之后的的X范圍和初始時的X范圍相差不超過1.甚至是0.1。於是我們將Intersect之后的值與初始時的值做作差取絕對值,這樣就可以確定Intersect之后,線段的端點是不是交點了。

下面給出GeometryService的Intersect完成事件處理函數的代碼:

   void geometryService_IntersectCompleted(object sender, GraphicsEventArgs e)
        {
            glayer.Graphics.Clear();

            foreach (Graphic g in e.Results)
            {
                g.Symbol = LayoutRoot.Resources["ResultsLineSymbol"] as SimpleLineSymbol;
                if (g.Geometry.Extent != null)
                {
                    #region 垂直線
                    if (g.Geometry.Extent.Width == 0.0)//垂直線兩個端點即為交點
                    {
                       foreach (ESRI.ArcGIS.Client.Geometry.PointCollection pc in (g.Geometry as ESRI.ArcGIS.Client.Geometry.Polyline).Paths)
                        {
                            foreach (MapPoint mp in pc)
                            {
                                if (Math.Abs(mp.Y - recExtent.Ymin) < 0.1 ||
                                    Math.Abs(mp.Y - recExtent.Ymax) < 0.1)
                                {
                                    continue;
                                }
                                else
                                {
                                    AddPointGraphic(mp);
                                }
                            }
                        }
                    }
                    #endregion
                    #region 水平線
                    if (g.Geometry.Extent.Height == 0.0)
                    {
                        foreach (ESRI.ArcGIS.Client.Geometry.PointCollection pc in (g.Geometry as ESRI.ArcGIS.Client.Geometry.Polyline).Paths)
                        {
                            foreach (MapPoint mp in pc)
                            {
                                if (Math.Abs(mp.X - recExtent.Xmin) < 0.1 ||
                                    Math.Abs(mp.X - recExtent.Xmax) < 0.1)
                                {
                                    continue;
                                }
                                else
                                {
                                    AddPointGraphic(mp);
                                }
                            }
                        }
                    }
                    #endregion
                }
                glayer.Graphics.Add(g);
            }
        }
這里我們將邊界點的值與交線的端點值比較,如果兩者之差小於0.1,則表示該點為邊界點,舍去

以上解釋了實現過程中的關鍵部分,接下來看看具體實現的流程:

(1):繪制矩形,網格划分

自定義結構體:

 public struct RectExtent
    {
       public double Xmin;
       public double Ymin;
       public double Xmax;
       public double Ymax;
    }

聲明如下變量:

       //定義繪制矩形的畫筆
          private Draw drawRect = null;
        //定義集合服務變量
        private GeometryService geometryService = null;
        //用來承載繪制的線和點
        GraphicsLayer glayer = null;
        //河流要素
        FeatureLayer fpolygonlayer = null;
        //自定義結構體變量
        private RectExtent recExtent;

在構造函數中實例化:

   public MainPage()
        {
            InitializeComponent();
            geometryService = new GeometryService("http://qzj-pc/ArcGIS/rest/services/Geometry/GeometryServer");
            geometryService.IntersectCompleted += new EventHandler<GraphicsEventArgs>(geometryService_IntersectCompleted);
            geometryService.SimplifyCompleted += new EventHandler<GraphicsEventArgs>(geometryService_SimplifyCompleted);
            
            geometryService.Failed += new EventHandler<TaskFailedEventArgs>(geometryService_Failed);

            glayer=map1.Layers["LineLayer"] as GraphicsLayer;
            fpolygonlayer = map1.Layers["RiverPolygon"] as FeatureLayer;

            drawGridButton.Click += new RoutedEventHandler(drawGridButton_Click);
            intersectButton.Click += new RoutedEventHandler(intersectButton_Click);
            ClearButton.Click += new RoutedEventHandler(ClearButton_Click);

            drawRect = new Draw(map1);
            drawRect.DrawMode = DrawMode.Rectangle;
            drawRect.DrawComplete += new EventHandler<DrawEventArgs>(drawRect_DrawComplete);
            drawRect.IsEnabled = false;
        }

點擊繪制要輸Button,繪制網格

 void drawGridButton_Click(object sender, RoutedEventArgs e)
        {
            //確定用戶輸入了步長信息
            if (xStepTextBox.Text.Trim() == "" || yStepTextBox.Text.Trim() == "")
            {
                MessageBox.Show("請輸入步長信息");
                return;
            }
            drawRect.IsEnabled = true;
        }
 void drawRect_DrawComplete(object sender, DrawEventArgs e)
        {
            drawRect.IsEnabled = false;
          
            CreateGrid(e.Geometry.Extent.XMin, e.Geometry.Extent.XMax, e.Geometry.Extent.YMin, e.Geometry.Extent.YMax, Convert.ToDouble(xStepTextBox.Text), Convert.ToDouble(yStepTextBox.Text));
        }

CreateGrid在上面已經給出,在此不再說明。

(2)Silmplify河流要素

 void intersectButton_Click(object sender, RoutedEventArgs e)
        {
            geometryService.SimplifyAsync(fpolygonlayer.Graphics);
        }

(3)在Simplify事件完成函數中進行Intersect操作。

由於這里使用的河流要素只有一個圖層所以生成的結果Results只有一個要素,即索引值為0.

  void geometryService_SimplifyCompleted(object sender, GraphicsEventArgs e)
        {
            geometryService.IntersectAsync(glayer.Graphics, e.Results[0].Geometry);
        }

(4).在Intersect事件完成函數中篩選正確的交點,代碼上面已經給出,在此不在說明。

(5)最后將正確的點添加到圖層中。

最后便可以得到正確的結果:

思考:如何繪制出網格的交點呢?

預告:下一篇將講解線與線交點的求解,歡迎繼續關注!

 (版權所有,轉載請標明出處)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM