ArcEngine + DevPress GIS二次開發:湖北疫情交互式數據分析、地圖輸出、專題可視化系統 具體實現

代碼github地址:

https://github.com/yunwei37/COVID-19-ArcEngine

實現效果

具體實現效果可參照我的前一篇文章:
https://blog.csdn.net/qq_42779423/article/details/106885322

程序具體實現

數據存儲與操作方式:

  • 將湖北市域圖形數據存儲在shp文件中,通過加載shp按鈕進行載入;

  • 選擇網易的疫情實時動態播報平臺作爲數據源,其地址如下:

    https://wp.m.163.com/163/page/news/virus_report/index.html?nw=1&anw=1

    通過爬蟲請求獲取數據(從1.1日至5.31日),經過數據清洗後保存爲csv文件;

  • 在具有公網ip地址的 windows server 上搭建mysql數據庫,將確診人數數據存入數據庫中,連接數據庫獲取確診數據信息;可以便於後續在服務器上繼續更新數據;

  • 創建了DAO層,將數據庫的增刪改查等操作封裝在工具類中,和具體程序業務邏輯分隔開來,其中包含了三個類:

    • SqlHelper:創建數據庫連接、執行數據庫命令、 創建MySqlDataReader對象:

      其中定義的接口:

      public MySqlConnection getMySqlCon();
      public int getMySqlCom(string M_str_sqlstr, params MySqlParameter[] parameters);
      public DataTable getMySqlRead(string M_str_sqlstr, params MySqlParameter[] parameters);
      
    • sqlDataFormat:進行數據格式的修改:

      其中定義的接口:

      public static string dataFormat(string str);
      
    • OperateDatabase:定義了數據庫增加、刪除、修改、查找的接口;

      其中定義的接口:

      public static int Insert(string TableName,ArrayList arr);
      public static DataTable select(string TableName, ArrayList arr);
      public static int Update(string TableName, ArrayList arr,ArrayList arr_where);
      public static int Delete(string TableName, ArrayList arr_where);
      

程序模塊設計與文件組織:

程序可以分爲以下幾個模塊:

  1. 輔助類:

    包含和數據庫操作相關的DAO層、圖例附加屬性定義和日誌模塊;除了上述描述的數據操作類以外,還有:

    • EnumMapSurroundType:圖例附加屬性定義類
    • Log: 日誌模塊類
  2. 地圖操作相關:

    主要包含地圖操作(平移、縮放),地圖渲染,以及地圖導出等功能;

    • Form1:地圖展示和操作相關的實現;
    • GisClass:包含了打開MXD文件、shp文件,以及地圖渲染的一些輔助函數;
  3. 屬性操作相關

    包含在地圖上進行空間查詢屬性、在屬性表中進行屬性編輯等;

    • Form1:屬性表編輯和展示等操作
    • SeletionForm:進行屬性查詢
    • AddForm:添加數據
  4. 疫情數據統計模塊:

    包含對疫情的統計圖表生成操作;

    • StaticsForm類

從界面美觀的角度考慮,我們採用了DevExpress進行開發;DevExpress是一個比較有名的界面控件套件,提供了一系列的界面控件套件的DotNet界面控件。

窗口:

  • 主窗體類爲Form1.cs
  • 進行屬性查詢選擇窗體類爲SeletionForm.cs
  • 統計圖表類爲StaticsForm.cs
  • 添加數據類爲AddForm.cs

主要功能實現流程與方法

  1. 地圖展示和常規地圖操作:

    • 採用ArcEngine的mapControl控件進行地圖展示:
    • 採用ArcEngine的ToolbarControl控件完成常規的地圖操作,如放大、縮小、平移、全圖;
    • 加載shp/mxd文件:

    打開mxd文件:

      private void openMxd_ItemClick(object sender, DevExpress.XtraBars.ItemClickEventArgs e)
         {
             String MxdPath=GisClass.OpenMxd();
             axMapControl1.LoadMxFile(MxdPath);
         }
    
     public static string OpenMxd()
         {
             string MxdPath = "";
             OpenFileDialog OpenMXD = new OpenFileDialog();
             OpenMXD.Title = "打開地圖";
             OpenMXD.InitialDirectory = "E:";
    
             OpenMXD.Filter = "Map Documents (*.mxd)|*.mxd";
             if (OpenMXD.ShowDialog() == DialogResult.OK)
             {
                 MxdPath = OpenMXD.FileName;
             }
             return MxdPath;
         }
    

    打開shp文件:

    public static string[] OpenShapeFile()
        {
            string[] ShpFile = new string[2];
            OpenFileDialog OpenShpFile = new OpenFileDialog();
            OpenShpFile.Title = "打開Shape文件";
            OpenShpFile.InitialDirectory = "E:";
            OpenShpFile.Filter = "Shape文件(*.shp)|*.shp";
    
            if (OpenShpFile.ShowDialog() == DialogResult.OK)
            {
                string ShapPath = OpenShpFile.FileName;
                //利用"\\"將文件路徑分成兩部分
                int Position = ShapPath.LastIndexOf("\\");
    
                string FilePath = ShapPath.Substring(0, Position);
                string ShpName = ShapPath.Substring(Position + 1);
                ShpFile[0] = FilePath;
    
                ShpFile[1] = ShpName;
    
            }
            else
            {
                return null;
            }
            return ShpFile;
        }
    
  2. 每日疫情分佈顯示:

    • 通過打開shp文件按鈕加載市域.shp,再遍歷圖層獲取湖北市域空間數據;如未加載,系統會報錯如下:
        //遍歷,尋找市域圖層
            for (int i = 0; i < this.axMapControl1.Map.LayerCount; i++) {
                ILayer layer1 = this.axMapControl1.Map.get_Layer(i);
                if (layer1.Name == "市域")
                {
                    layer = layer1 as IFeatureLayer;
                    break;
                }
            }
            if (layer == null) {
                MessageBox.Show("請打開市域圖層");
                return;
            } 
    
    • 點擊每日疫情按鈕,首先獲取圖層的相應字段,然後根據選擇的日期在數據庫中進行查詢,獲取疫情數據;
    //獲取圖層字段,沒有則添加一個num字段
            IFeatureClass featureClass = layer.FeatureClass;
            int isExist=featureClass.FindField("num");
            if (isExist == -1) { 
                //添加一個字段
                IFields pFields = featureClass.Fields;
                IFieldsEdit pFieldsEdit = pFields as IFieldsEdit;
                IField fld = new FieldClass();
                IFieldEdit2 fldE = fld as IFieldEdit2;
                fldE.Name_2 = "num";
                fldE.AliasName_2 = "數量";
                fldE.Type_2 = esriFieldType.esriFieldTypeSingle;
                featureClass.AddField(fld);
            }
            //給字段賦值
            IFeatureCursor pFtCursor = featureClass.Search(null, false);
            IFeature pFt = pFtCursor.NextFeature();
            int index1 = pFt.Fields.FindField("num");
            IDataset dataset = (IDataset)featureClass;
            IWorkspace workspace = dataset.Workspace;
            IWorkspaceEdit workspaceEdit = (IWorkspaceEdit)workspace;
            workspaceEdit.StartEditing(true);
            workspaceEdit.StartEditOperation();
            while (pFt != null) {
                int index = pFt.Fields.FindField("code");
                String code = pFt.get_Value(index).ToString();
    
                DataRow[] drs=dt.Select("CODE=" + code);
                DataTable dtNew = dt.Clone();
                for (int i = 0; i < drs.Length; i++)
                {
                    dtNew.ImportRow(drs[i]);
    
                }
                String num = dtNew.Rows[0]["AllConfiemed"].ToString();
                if (num == "") {
                    num = "0";
                }
    
                pFt.set_Value(index1,  Convert.ToInt32(num));
                pFt.Store();
                pFt = pFtCursor.NextFeature();
            }
    
    • 根據獲取的數據對圖層進行渲染
    GisClass.ClassRender(this.axMapControl1.ActiveView, layer, 6, "num");
    
  3. 空間查詢操作:

    • 通過點擊圖形按鈕,繪製多邊形、圓、矩形等;

    如繪製多邊形:先設置繪製類型爲多邊形,再創建一個多邊形元素,設置相應屬性,在pGraphicsContainer中添加該多邊形;然後鼠標點擊時追蹤多邊形,並局部刷新map

        private void drawPolygon_ItemClick(object sender, DevExpress.XtraBars.ItemClickEventArgs e)
        {
            this.type = 1;
            IPolygonElement polygonElement = new PolygonElementClass();
            pElement = polygonElement as IElement;
            ISimpleFillSymbol simpleFill = new SimpleFillSymbolClass();
            simpleFill.Style = esriSimpleFillStyle.esriSFSNull;
            simpleFill.Color = GisClass.GetRgbColor(255,0,0);
            //設置邊線顏色
            ILineSymbol lineSymbol = new SimpleLineSymbol();
            lineSymbol.Color = GisClass.GetRgbColor(255, 0, 0);
            IFillShapeElement shapeEle = pElement as IFillShapeElement;
    
            simpleFill.Outline = lineSymbol;
            shapeEle.Symbol = simpleFill;
            pGraphicsContainer.AddElement(pElement, 0);
        
          
        }
        private void axMapControl1_OnMouseDown(object sender, IMapControlEvents2_OnMouseDownEvent e{
        .......
                if (this.type == 1)
                {
    
                    IGeometry Polygon = axMapControl1.TrackPolygon();
                    pElement.Geometry = Polygon;
                    axMapControl1.ActiveView.PartialRefresh(esriViewDrawPhase.esriViewBackground, null, null);
                }
        ......
        }
    
    • 通過點擊查詢,對所選範圍執行空間查詢操作,對地圖上查詢到的部分進行高亮顯示;
    private void query_btn_ItemClick(object sender, DevExpress.XtraBars.ItemClickEventArgs e)
        {
            ArrayList arr = new ArrayList();
            DataTable dt = OperateDatabase.select("data", arr);
            this.gridControl1.DataSource = dt;
            this.tabControl2.SelectedIndex = 1;
        }
    
    
    
    • 點擊進行屬性查詢,打開屬性表;
        private void shapeQuery_ItemClick(object sender, DevExpress.XtraBars.ItemClickEventArgs e)
        {
            axMapControl1.Map.ClearSelection();
            IGraphicsContainer graphicsContainer = axMapControl1.Map as IGraphicsContainer;
            graphicsContainer.Reset();
            IElement element = graphicsContainer.Next();
            //獲取圖形幾何信息
            if (element == null) {
                MessageBox.Show("請在工具欄選擇繪製矩形,多邊形,或者圓");
                return;
            }
            IGeometry geometry = element.Geometry;
            axMapControl1.Map.SelectByShape(geometry,null,false);
            //進行部分刷新顯示最新要素
            axMapControl1.ActiveView.PartialRefresh(esriViewDrawPhase.esriViewGeoSelection,null,axMapControl1.ActiveView.Extent);
        }
    
    
  4. 屬性數據編輯:

    • 在屬性數據的頁面中,可以點擊查詢、增加、刪除等按鈕進行屬性數據的編輯;

    修改單元格內容:

            //獲取修改的單元格
            string CellValue = this.gridView1.GetFocusedValue().ToString();
            //獲取單元格的列名
            string ColumnName = this.gridView1.FocusedColumn.FieldName;
            //獲取所在列的id
            DataRow dr = this.gridView1.GetDataRow(e.RowHandle);
            string id = dr["id"].ToString();
            //修改
            ArrayList arr = new ArrayList();
            if (ColumnName == "name" || ColumnName == "YMD")
            {
                arr.Add(ColumnName + ":'" + CellValue + "'");
            }
            else
            {
                arr.Add(ColumnName + ":" + CellValue);               
            }
            ArrayList arr_where = new ArrayList();
            arr_where.Add("id:" + id);
            int result = OperateDatabase.Update("data", arr, arr_where);
            if (result == 0)
            {
                MessageBox.Show("該值修改失敗");
            }
    

    添加數據:

    private void add_btn_Click(object sender, EventArgs e)
        {
            ArrayList arr = new ArrayList();
            arr.Add("code:"+this.textBox_code.Text);
            arr.Add("name:'" + this.textBox_name.Text+"'");
            arr.Add("YMD:'" + this.date_edit.Text+"'");
            arr.Add("AllConfiemed:" + this.spinEdit_AllConfiemed.Text);
            arr.Add("CurConfirmeed:" + this.spinEdit_CurConfirmeed.Text);
            arr.Add("Cured:" + this.spinEdit_Cured.Text);
            arr.Add("Death:" + this.spinEdit_Death.Text);
            int result = OperateDatabase.Insert("data",arr);
            if (result == 1)
            {
                MessageBox.Show("添加成功");
                return;
            }else {
                MessageBox.Show("添加失敗");
                return;
            }
        }
    

    屬性查詢結果:

    在屬性查詢結果中是以樹的方式展示不同圖層的查詢結果:

        private void treeView1_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
        {
            this.gridView1.Columns.Clear();
            currentLayer = e.Node.Tag as IFeatureLayer;
            if (currentLayer == null) {
                return;
            }
            IFeatureSelection featureSelection = currentLayer as IFeatureSelection;
            //獲取選中得要素幾何
            ISelectionSet selectionSet = featureSelection.SelectionSet;
            //獲取字段
            IFields fields = currentLayer.FeatureClass.Fields;
            DataTable dt = new DataTable();
            for (int i = 0; i < fields.FieldCount; i++) {
                dt.Columns.Add(fields.get_Field(i).Name);
            }
            //獲取整個數據集
            ICursor cursor;
            selectionSet.Search(null,false,out cursor);
            //獲取每個要素
            IFeatureCursor featureCursor = cursor as IFeatureCursor;
            //遍歷
            IFeature feature = featureCursor.NextFeature();
            String[] strs;
            while (feature != null) {
                strs = new String[fields.FieldCount];
                for (int i = 0; i < fields.FieldCount; i++) {
                    strs[i] = feature.get_Value(i).ToString();
                }
                dt.Rows.Add(strs);
                feature = featureCursor.NextFeature();
            }
            this.gridControl1.DataSource = dt;
           
        }
    
  5. 疫情統計:

    • 在主頁面上點擊疫情統計,可顯示查詢窗口,其中可完成對於疫情統計圖表的生成和查看;
    private void statics_btn_Click(object sender, EventArgs e)
        {
            //查詢起始日期的數字
            if (this.dateEdit_start.Text == "" || this.dateEdit_target.Text == "") {
                MessageBox.Show("請填寫起止日期");
                return;
            }
            ArrayList arr1 = new ArrayList();
            arr1.Add("YMD:'" + this.dateEdit_start.Text + "'");
            DataTable dt1 = OperateDatabase.select("data",arr1);
            ArrayList arr2 = new ArrayList();
            arr1.Add("YMD:'" + this.dateEdit_target.Text + "'");
            DataTable dt2 = OperateDatabase.select("data", arr1);
            Series s1 = this.chartControl1.Series[0];
            s1.DataSource = dt1;
            s1.ArgumentDataMember = "name";
            s1.ValueDataMembers[0] = "CurConfirmeed";
        }
    
  6. 軌跡分析:

    • 通過日期框進行日期區間的選擇;
    • 軌跡數據已存放在數據庫中,通過sql查詢載入軌跡數據:
    • 進行軌跡查詢:
    • 繪製軌跡:
        if (this.start_time.EditValue == "" || this.end_time.EditValue == "") {
                MessageBox.Show("請選擇起止日期");
                return;
            }
            SqlHelper help = new SqlHelper();
            String sql = "select * from route where tm between '" + this.start_time.EditValue + "' and '" + this.end_time.EditValue+"'";
            DataTable dt = help.getMySqlRead(sql);
            ISimpleMarkerSymbol simpleMarkerSymbol = new SimpleMarkerSymbol();
            simpleMarkerSymbol.Style = esriSimpleMarkerStyle.esriSMSCircle;
            IColor color = GisClass.GetRgbColor(0,255,0);
            simpleMarkerSymbol.Color = color;
            ILineElement lineElement = new LineElementClass();
            IElement ele1 = lineElement as IElement;
            ISegment pSegment;
            ILine pLine=null;
            object o = Type.Missing;
            ISegmentCollection pPath = new PathClass();
            for (int i = 0; i < dt.Rows.Count; i++) {
    
                IMarkerElement markerEle = new MarkerElementClass();
                IElement ele=markerEle as IElement;
                IPoint point = new PointClass();
                markerEle.Symbol = simpleMarkerSymbol;
                point.PutCoords(Double.Parse(dt.Rows[i]["x"].ToString()),Double.Parse(dt.Rows[i]["y"].ToString()));
                ele.Geometry = point;
                pGraphicsContainer.AddElement(ele,0);
                //逐段添加線
                if (i > 0 && i < dt.Rows.Count) {
                    IPoint point1 = new PointClass();
                    point1.PutCoords(Double.Parse(dt.Rows[i-1]["x"].ToString()), Double.Parse(dt.Rows[i-1]["y"].ToString()));
                    pLine = new LineClass();
                    pLine.PutCoords(point1, point);
                    pSegment = pLine as ISegment;
                    pPath.AddSegment(pSegment, ref o, ref o);
                }
              
               
                axMapControl1.ActiveView.PartialRefresh(esriViewDrawPhase.esriViewBackground, null, null);
            }
            IGeometryCollection pPolyline = new PolylineClass();
            pPolyline.AddGeometry(pPath as IGeometry, ref o, ref o);
            IPolyline polyline = pPolyline as IPolyline;
            //獲取範圍
            IEnvelope ev = polyline.Envelope;
            this.axMapControl1.ActiveView.Extent = ev;
            ele1.Geometry = pPolyline as IPolyline;
            pGraphicsContainer.AddElement(ele1, 0);
    
  7. 每日疫情圖輸出:

    • 添加圖例:可爲地圖添加指北針、比例尺等;

    添加指北針:

    void addNorthArrow(IPageLayout pPageLayout, IEnvelope pEnv, IActiveView pActiveView)
        {
            if (pPageLayout == null || pActiveView == null)
            {
                return;
            }
         
    
            ESRI.ArcGIS.esriSystem.IUID uid = new ESRI.ArcGIS.esriSystem.UIDClass();
            uid.Value = "esriCarto.MarkerNorthArrow";
    
            ESRI.ArcGIS.Carto.IGraphicsContainer graphicsContainer = pPageLayout as ESRI.ArcGIS.Carto.IGraphicsContainer; 
            ESRI.ArcGIS.Carto.IActiveView activeView = pPageLayout as ESRI.ArcGIS.Carto.IActiveView; 
            ESRI.ArcGIS.Carto.IFrameElement frameElement = graphicsContainer.FindFrame(pActiveView.FocusMap);
            ESRI.ArcGIS.Carto.IMapFrame mapFrame = frameElement as ESRI.ArcGIS.Carto.IMapFrame; // Dynamic Cast
            ESRI.ArcGIS.Carto.IMapSurroundFrame mapSurroundFrame = mapFrame.CreateSurroundFrame(uid as ESRI.ArcGIS.esriSystem.UID, null); // Dynamic Cast
            ESRI.ArcGIS.Carto.IElement element = mapSurroundFrame as ESRI.ArcGIS.Carto.IElement; // Dynamic Cast
            element.Geometry = pEnv;
            element.Activate(activeView.ScreenDisplay);
            graphicsContainer.AddElement(element, 0);
            ESRI.ArcGIS.Carto.IMapSurround mapSurround = mapSurroundFrame.MapSurround;
    
            // Change out the default north arrow
            ESRI.ArcGIS.Carto.IMarkerNorthArrow markerNorthArrow = mapSurround as ESRI.ArcGIS.Carto.IMarkerNorthArrow; // Dynamic Cast
            ESRI.ArcGIS.Display.IMarkerSymbol markerSymbol = markerNorthArrow.MarkerSymbol;
            ESRI.ArcGIS.Display.ICharacterMarkerSymbol characterMarkerSymbol = markerSymbol as ESRI.ArcGIS.Display.ICharacterMarkerSymbol; // Dynamic Cast
            characterMarkerSymbol.CharacterIndex = 200; // change the symbol for the North Arrow
            markerNorthArrow.MarkerSymbol = characterMarkerSymbol;
        }
    

    添加比例尺:

    public void makeScaleBar(IActiveView pActiveView, IPageLayout pPageLayout, IEnvelope pEnv)
        {
            IGraphicsContainer container = pPageLayout as IGraphicsContainer;
            // 獲得MapFrame  
            IFrameElement frameElement = container.FindFrame(pActiveView.FocusMap);
            IMapFrame mapFrame = frameElement as IMapFrame;
            //根據MapSurround的uid,創建相應的MapSurroundFrame和MapSurround  
            UID uid = new UIDClass();
            uid.Value = "esriCarto.AlternatingScaleBar";
            IMapSurroundFrame mapSurroundFrame = mapFrame.CreateSurroundFrame(uid, null);
            //設置MapSurroundFrame中比例尺的樣式  
            IMapSurround mapSurround = mapSurroundFrame.MapSurround;
            IScaleBar markerScaleBar = ((IScaleBar)mapSurround);
            markerScaleBar.LabelPosition = esriVertPosEnum.esriBelow;
            markerScaleBar.UseMapSettings();
            //QI,確定mapSurroundFrame的位置  
            IElement element = mapSurroundFrame as IElement;
    
            element.Geometry = pEnv;
            //使用IGraphicsContainer接口添加顯示  
            container.AddElement(element, 0);
            pActiveView.Refresh();  
        }
        #endregion     
    
    • 點擊輸出按鈕,可將疫情圖輸出爲多種格式:

    如導出爲圖片:

    private void ExportMapToImage()
        {
            try
            {
                SaveFileDialog pSaveDialog = new SaveFileDialog();
                pSaveDialog.FileName = "";
                pSaveDialog.Filter = "JPG圖片(*.JPG)|*.jpg|tif圖片(*.tif)|*.tif|PDF文檔(*.PDF)|*.pdf";
                if (pSaveDialog.ShowDialog() == DialogResult.OK)
                {
                    double iScreenDispalyResolution =this.axPageLayoutControl1.ActiveView.ScreenDisplay.DisplayTransformation.Resolution;// 獲取屏幕分辨率的值
                    IExporter pExporter = null;
                    if (pSaveDialog.FilterIndex == 1)
                    {
                        pExporter = new JpegExporterClass();
                    }
                    else if (pSaveDialog.FilterIndex == 2)
                    {
                        pExporter = new TiffExporterClass();
                    }
                    else if (pSaveDialog.FilterIndex == 3)
                    {
                        pExporter = new PDFExporterClass();
                    }
                    pExporter.ExportFileName = pSaveDialog.FileName;
                    pExporter.Resolution = (short)iScreenDispalyResolution; //分辨率
                    tagRECT deviceRect = this.axPageLayoutControl1.ActiveView.ScreenDisplay.DisplayTransformation.get_DeviceFrame();
                    IEnvelope pDeviceEnvelope = new EnvelopeClass();
                    pDeviceEnvelope.PutCoords(deviceRect.left, deviceRect.bottom, deviceRect.right, deviceRect.top);
                    pExporter.PixelBounds = pDeviceEnvelope; // 輸出圖片的範圍
                    ITrackCancel pCancle = new CancelTrackerClass();//可用ESC鍵取消操作
                    this.axPageLayoutControl1.ActiveView.Output(pExporter.StartExporting(), pExporter.Resolution, ref deviceRect, this.axPageLayoutControl1.ActiveView.Extent, pCancle);
                    Application.DoEvents();
                    pExporter.FinishExporting();
                }
    
            }
            catch (Exception Err)
            {
                MessageBox.Show(Err.Message, "輸出圖片", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }
    

    除了導出爲圖片之外,支持多種其他格式,如pdf、jpg等

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章