湖北疫情數據專題顯示系統
代碼github地址:
新型冠狀病毒肺炎(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層,將數據庫的增刪改查等操作封裝在工具類中,和具體程序業務邏輯分隔開來,其中包含了三個類:
-
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);
-
程序模塊設計與文件組織:
程序可以分為以下幾個模塊:
- 輔助類:
包含和數據庫操作相關的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");
-
-
空間查詢操作:
- 通過點擊圖形按鈕,繪製多邊形、圓、矩形等;
如繪製多邊形:先設置繪製類型為多邊形,再創建一個多邊形元素,設置相應屬性,在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); }
-
屬性數據編輯:
- 在屬性數據的頁面中,可以點擊查詢、增加、刪除等按鈕進行屬性數據的編輯;
修改單元格內容:
//獲取修改的單元格 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
-
點擊輸出按鈕,可將疫情圖輸出為多種格式:
如導出為圖片:
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等
組員構成及分工:
- : 屬性數據相關編輯與空間查詢操作、每日疫情分佈顯示與疫情統計;
- : 數據結構和數據庫設計與實施,地圖操作實現,軌跡分析與查詢,地圖輸出;
- : 地圖展示,屬性數據、疫情分佈顯示等功能的完善和bug修復,以及文檔撰寫;
遺留的一些問題與思考
- 進行查詢需要操作的步驟較多,後續可以繼續優化;
- 可以適當豐富疫情統計功能;
- 從此次課程項目中也確實學到了許多,瞭解了一個GIS應用程序的完整開發流程;