湖北疫情資料專題顯示系統
代碼github位址:
https://github.com/yunwei37/COVID-19-ArcEngine
新型冠狀病毒肺炎(COVID-19,簡稱“新冠肺炎”)疫情肆虐全球多個國家,2020年3月11日,世界衛生組織 (WHO) 正式宣布将新冠肺炎列為全球性大流行病。在全球抗擊新型冠狀病毒疫情的過程中,産生了前所未有的大規模疫情資料,利用大資料分析技術和方法能夠協助發現病毒傳染源、監測疫情發展、調配救援物資,進而更好地進行疫情防控工作。空間資料分析作為大資料分析的重要組成,将資料智能處理、直覺展示和互動分析有機地結合,使機器智能和人類智慧深度融合、優勢互補,為疫情防控中的分析、指揮和決策提供有效依據和指南。
簡介:
本系統基于ArcEngine進行開發,支援武漢疫情地圖根據不同日期的展示、操作以及添加圖例、導出為多種格式,支援屬性資料的編輯和查詢,支援指定時間區段統計疫情與軌迹分析功能;可以直覺地展示出疫情的發展态勢,為疫情分析和防控工作作出更好的決策參考。
程式功能設計與展示:
開始界面布局:
進入程式顯示的開始界面:

- 視窗上方為菜單欄,包含檔案、查詢、屬性編輯、地圖導出等标簽;
- 菜單欄的檔案選項包含打開MXD檔案、shapefile檔案,可以根據選擇的日期來渲染每日疫情地圖,也可以通過疫情統計按鈕打開疫情統計視窗;
- 下方頁面包含空間展示和屬性資料兩個标簽,分别展示地圖和屬性資料;
- 地圖包含左側的TOC和右側的地圖顯示視窗;
- 顯示視窗可以切換資料視圖和頁面視圖;
每日疫情地圖:
可以根據選擇的日期來渲染每日疫情地圖,通過分層渲染的方式來表現疫情人數的多少;支援各種正常的地圖操作,如放大縮小平移等等;
行進軌迹繪制:
可以根據行進軌迹資料檔案,選取時間段繪制軌迹并分析經過的地市和疫情狀況:
空間查詢:
本系統支援多種查詢方式,包含點選查詢、矩形、多邊形、圓等多種空間查詢,并可以檢視相應選擇集:
地圖導出
本系統支援導出為多種格式,如jpg/tif/pdf等;可以在頁面視圖中實時檢視所要導出的圖形;并支援添加圖例等。
屬性資料:
本系統支援檢視資料集和進行屬性資料的編輯;
疫情統計:
支援根據時間段顯示疫情的統計資料,包含總量和變化量;
附加功能:
- 支援記錄檔記錄功能,便于對程式的錯誤進行排查;
程式具體實作
資料存儲與操作方式:
- 将湖北市域圖形資料存儲在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層,将資料庫的增删改查等操作封裝在工具類中,和具體程式業務邏輯分隔開來,其中包含了三個類:
-
:建立資料庫連接配接、執行資料庫指令、 建立MySqlDataReader對象:SqlHelper
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);
-
程式子產品設計與檔案組織:
程式可以分為以下幾個子產品:
- 輔助類:
包含和資料庫操作相關的DAO層、圖例附加屬性定義和日志子產品;除了上述描述的資料操作類以外,還有:
- EnumMapSurroundType:圖例附加屬性定義類
- Log: 日志子產品類
-
地圖操作相關:
主要包含地圖操作(平移、縮放),地圖渲染,以及地圖導出等功能;
- Form1:地圖展示和操作相關的實作;
- GisClass:包含了打開MXD檔案、shp檔案,以及地圖渲染的一些輔助函數;
-
屬性操作相關
包含在地圖上進行空間查詢屬性、在屬性表中進行屬性編輯等;
- Form1:屬性表編輯和展示等操作
- SeletionForm:進行屬性查詢
- AddForm:添加資料
-
疫情資料統計子產品:
包含對疫情的統計圖表生成操作;
- StaticsForm類
從界面美觀的角度考慮,我們采用了DevExpress進行開發;DevExpress是一個比較有名的界面控件套件,提供了一系列的界面控件套件的DotNet界面控件。
視窗:
- 主窗體類為
;Form1.cs
- 進行屬性查詢選擇窗體類為
SeletionForm.cs
- 統計圖表類為
StaticsForm.cs
- 添加資料類為
AddForm.cs
主要功能實作流程與方法
- 地圖展示和正常地圖操作:
- 采用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;
}
- 每日疫情分布顯示:
- 通過打開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");
- 通過打開shp檔案按鈕加載
- 空間查詢操作:
- 通過點選圖形按鈕,繪制多邊形、圓、矩形等;
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); }
- 屬性資料編輯:
- 在屬性資料的頁面中,可以點選查詢、增加、删除等按鈕進行屬性資料的編輯;
//擷取修改的單元格 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;
}
-
- 在首頁面上點選疫情統計,可顯示查詢視窗,其中可完成對于疫情統計圖表的生成和檢視;
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"; }
- 在首頁面上點選疫情統計,可顯示查詢視窗,其中可完成對于疫情統計圖表的生成和檢視;
- 軌迹分析:
- 通過日期框進行日期區間的選擇;
- 軌迹資料已存放在資料庫中,通過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);
- 每日疫情圖輸出:
- 添加圖例:可為地圖添加指北針、比例尺等;
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
-
點選輸出按鈕,可将疫情圖輸出為多種格式:
如導出為圖檔:
除了導出為圖檔之外,支援多種其他格式,如pdf、jpg等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); } }
組員構成及分工:
- : 屬性資料相關編輯與空間查詢操作、每日疫情分布顯示與疫情統計;
- : 資料結構和資料庫設計與實施,地圖操作實作,軌迹分析與查詢,地圖輸出;
- : 地圖展示,屬性資料、疫情分布顯示等功能的完善和bug修複,以及文檔撰寫;
遺留的一些問題與思考
- 進行查詢需要操作的步驟較多,後續可以繼續優化;
- 可以适當豐富疫情統計功能;
- 從此次課程項目中也确實學到了許多,了解了一個GIS應用程式的完整開發流程;