發(fā)入門(mén):手把手教你構(gòu)建第一個(gè)GIS桌面應(yīng)用)
簡(jiǎn)介組件式GIS將地圖顯示、數(shù)據(jù)管理、空間分析等能力封裝為可復(fù)用的開(kāi)發(fā)組件是構(gòu)建桌面級(jí)GIS應(yīng)用的主流技術(shù)路徑。ArcGIS Engine作為經(jīng)典的組件式GIS開(kāi)發(fā)平臺(tái)基于ArcObjects對(duì)象模型通過(guò)工作空間Workspace、要素類FeatureClass和圖層Layer的層級(jí)關(guān)系幫助開(kāi)發(fā)者靈活組織地理數(shù)據(jù)。它允許在Visual StudioC#環(huán)境下快速集成地圖控件實(shí)現(xiàn)圖層加載、屬性查詢、空間查詢等核心功能并在不安裝ArcMap的機(jī)器上獨(dú)立部署。無(wú)論是自然資源、城市規(guī)劃還是測(cè)繪工程凡需要定制化地圖工具的領(lǐng)域ArcGIS Engine都能提供穩(wěn)定成熟的解決方案。內(nèi)容涵蓋環(huán)境搭建、許可初始化并通過(guò)示例跑通第一個(gè)顯示地圖和屬性查詢的程序同時(shí)梳理高頻踩坑點(diǎn)與優(yōu)化經(jīng)驗(yàn)。 做GIS開(kāi)發(fā)的朋友只要想在桌面端做定制化地圖應(yīng)用基本都繞不開(kāi)ArcGIS Engine行業(yè)內(nèi)一般簡(jiǎn)稱為Arc Engine。它不是一套拿來(lái)即用的成品軟件而是一套龐大的GIS組件庫(kù)你可以把它理解成一套積木地圖顯示、數(shù)據(jù)管理、空間分析、符號(hào)渲染這些能力全都封裝成了獨(dú)立的組件你用Visual Studio或Eclipse把這些組件按自己的需求拼裝起來(lái)就能做出一個(gè)完全屬于自己的GIS桌面程序。這篇教程面向的是剛接觸Arc Engine、想快速上手的開(kāi)發(fā)者我會(huì)從整體思路講到環(huán)境搭建再帶你把第一個(gè)能顯示地圖、能查屬性的小工具跑起來(lái)最后把那些網(wǎng)上很少講清楚的坑一次性說(shuō)透。在動(dòng)手之前我覺(jué)得有必要先想明白一個(gè)問(wèn)題你到底要用Arc Engine做什么因?yàn)檫@東西的學(xué)習(xí)曲線確實(shí)不緩如果一開(kāi)始方向就錯(cuò)了后面會(huì)很痛苦。本文的內(nèi)容主要基于C#和.NET環(huán)境來(lái)展開(kāi)這也是目前Arc Engine開(kāi)發(fā)最主流的組合功能完整度高資料也相對(duì)好找。1. 先把Arc GIS Engine的定位和設(shè)計(jì)思路搞清楚1.1 為什么選擇Arc GIS Engine而不是其他方案很多人第一次接觸Arc Engine時(shí)會(huì)問(wèn)我用ArcMap或ArcGIS Pro也能打開(kāi)地圖、導(dǎo)出圖片為什么還要寫(xiě)代碼這個(gè)問(wèn)題問(wèn)到點(diǎn)子上了。ArcMap能做的是“通用的事”而Arc Engine能做的是“你的事”。舉個(gè)例子假設(shè)你所在的項(xiàng)目需要給外業(yè)人員做一個(gè)地圖瀏覽工具界面上只保留“加載地塊”、“查看屬性”、“按坐標(biāo)定位”三個(gè)按鈕其他功能一概不要。用ArcMap做不到這種定制而用Arc Engine就可以把它做成一個(gè)只有幾兆的獨(dú)立安裝包部署到?jīng)]有安裝ArcMap的機(jī)器上照樣能跑。另外相比ArcGIS Pro的SDK基于新的ArcGIS Pro架構(gòu)Arc Engine對(duì)應(yīng)的ArcObjects開(kāi)發(fā)模式雖然老了點(diǎn)但優(yōu)勢(shì)在于穩(wěn)定和成熟。很多企事業(yè)單位的存量GIS系統(tǒng)都是基于Arc Engine 10.x構(gòu)建的版本迭代需求長(zhǎng)期存在所以這套技術(shù)到今天依然有很廣的就業(yè)面和實(shí)用場(chǎng)景。如果你所在的公司或項(xiàng)目要求對(duì)接老平臺(tái)學(xué)會(huì)Arc Engine幾乎是必選項(xiàng)。1.2 先理解對(duì)象模型再談寫(xiě)代碼Arc Engine學(xué)習(xí)曲線陡主要陡在它的對(duì)象模型上。它里面的類非常多而且類與類之間有嚴(yán)格的層級(jí)關(guān)系。很多新手上來(lái)就查“怎么加圖層”的代碼復(fù)制粘貼跑通了但換一個(gè)數(shù)據(jù)源就不會(huì)了原因就是沒(méi)有理解背后的對(duì)象模型關(guān)系。我們可以打個(gè)比方。整個(gè)數(shù)據(jù)世界可以想象成一本書(shū)Workspace工作空間是書(shū)架它管理著一批數(shù)據(jù)存放的位置。一個(gè)文件夾可以是一個(gè)工作空間對(duì)應(yīng)Shapefile目錄一個(gè)地理數(shù)據(jù)庫(kù)File GDB也是一個(gè)工作空間。FeatureClass要素類是書(shū)架上的一本書(shū)對(duì)應(yīng)一張屬性表。比如一個(gè)省界圖層、一個(gè)道路圖層都是單獨(dú)的要素類。Feature要素是書(shū)里的一頁(yè)對(duì)應(yīng)一個(gè)具體的地理對(duì)象一個(gè)多邊形、一條線或一個(gè)點(diǎn)。Layer圖層是書(shū)簽。它指向某本書(shū)并記錄了這本書(shū)該怎么表現(xiàn)——比如用紅色還是藍(lán)色渲染。同一個(gè)要素類可以被多個(gè)圖層引用顯示樣式互不影響。寫(xiě)Arc Engine代碼的本質(zhì)就是沿著這條鏈一路往下找“從工作空間打開(kāi)要素類把要素類包進(jìn)圖層再把圖層加到地圖控件里”。順序不能亂引用不能丟。理解了這個(gè)鏈條后面所有代碼都只是這個(gè)鏈條的變體。2. 環(huán)境搭建與版本選型2.1 千萬(wàn)別在版本上踩坑Arc Engine的版本和操作系統(tǒng)、開(kāi)發(fā)環(huán)境之間有嚴(yán)格的對(duì)應(yīng)關(guān)系這是我見(jiàn)過(guò)的翻車(chē)率最高的環(huán)節(jié)。我先給出一份基于經(jīng)驗(yàn)的對(duì)照表供你提前規(guī)劃ArcGIS Engine版本推薦開(kāi)發(fā)環(huán)境Visual Studio推薦.NET框架常見(jiàn)操作系統(tǒng)10.2.xVS2010 / VS2012.NET Framework 4.5Windows 7 / 1010.4.xVS2013 / VS2015.NET Framework 4.5.2Windows 7 / 1010.8.xVS2017 / VS2019.NET Framework 4.6.1Windows 10 / 11這里要重點(diǎn)提醒不要盲目追求最新版本。Arc Engine 10.8雖然在功能和兼容性上都更好但它的安裝包和許可管理更嚴(yán)格部署時(shí)踩坑概率高。我自己在項(xiàng)目里用得最多的反而是10.2.x因?yàn)榫W(wǎng)上能搜到的代碼、博客、解決方案基本都是基于這個(gè)版本寫(xiě)的遇到問(wèn)題好排查。如果你只是學(xué)習(xí)入門(mén)建議用10.2.2加VS2012或VS2010體驗(yàn)最省心。2.2 安裝時(shí)需要注意的細(xì)節(jié)安裝Arc Engine的Developer Kit只是第一步很多新手會(huì)漏掉兩個(gè)關(guān)鍵組件License Manager許可管理器和Runtime運(yùn)行時(shí)。開(kāi)發(fā)機(jī)上裝Developer Kit時(shí)會(huì)自動(dòng)帶上Runtime但真正的坑在許可授權(quán)這里。Arc Engine的許可分為兩種一種是單機(jī)版許可License Fixed一種是網(wǎng)絡(luò)版許可License Floating。單機(jī)版需要在License Manager里手動(dòng)輸入許可碼并啟動(dòng)服務(wù)網(wǎng)絡(luò)版則連接局域網(wǎng)內(nèi)的許可服務(wù)器。啟動(dòng)開(kāi)發(fā)前務(wù)必先確認(rèn)系統(tǒng)服務(wù)列表里的“ArcGIS License Manager”服務(wù)已經(jīng)在運(yùn)行。很多報(bào)錯(cuò)信息比如0x80040228追根溯源都是這個(gè)服務(wù)沒(méi)起來(lái)。2.3 項(xiàng)目里的許可初始化代碼創(chuàng)建的WinForms或WPF項(xiàng)目后第一件事不是拖控件而是初始化許可。不初始化許可Arc Engine的組件會(huì)直接罷工。常用的初始化方式有兩種松散綁定和強(qiáng)綁定。強(qiáng)綁定需要手動(dòng)指定用到的ProductCode代碼比較繁瑣日常開(kāi)發(fā)我建議用松散綁定代碼少適配性也強(qiáng)。下面這段是我在每個(gè)項(xiàng)目里都會(huì)寫(xiě)的模板using ESRI.ArcGIS; using ESRI.ArcGIS.esriSystem; public static class LicenseInitializer { public static IAoInitialize BindLicense() { ESRI.ArcGIS.RuntimeManager.Bind(ProductCode.EngineOrDesktop); IAoInitialize aoInit new AoInitializeClass(); esriLicenseStatus status aoInit.Initialize(esriLicenseProductCode.esriLicenseProductCodeEngine); if (status ! esriLicenseStatus.esriLicenseCheckedOut) { status aoInit.Initialize(esriLicenseProductCode.esriLicenseProductCodeBasic); } return aoInit; } }注意第一行的RuntimeManager.Bind這行代碼負(fù)責(zé)綁定運(yùn)行時(shí)版本。在Arc Engine 10.x里如果你發(fā)現(xiàn)自己引用的DLL版本和運(yùn)行時(shí)版本對(duì)不上程序啟動(dòng)時(shí)會(huì)直接拋出異常先執(zhí)行Bind可以避免絕大多數(shù)的DLL版本沖突問(wèn)題。3. 從零搭建第一個(gè)地圖瀏覽程序3.1 創(chuàng)建項(xiàng)目并搭建界面環(huán)境準(zhǔn)備好之后我們開(kāi)始寫(xiě)第一個(gè)真正能跑起來(lái)的程序。這里我以C# WinForms為例因?yàn)锳rc Engine的Windows Forms控件集成度最高幾乎不需要自己處理繪制細(xì)節(jié)。新建一個(gè).NET Framework版本的Windows窗體應(yīng)用程序注意Arc Engine 10.x不支持.NET Core/.NET 5別選錯(cuò)了。在工具箱中右鍵選擇“選擇項(xiàng)”在.NET Framework組件里勾選MapControl、PageLayoutControl、ToolbarControl、LicenseControl、TOCControl圖層目錄控件。往窗體上拖一個(gè)LicenseControl用于界面設(shè)計(jì)時(shí)的許可綁定、一個(gè)MapControl地圖顯示區(qū)、一個(gè)ToolbarControl工具條和一個(gè)TOCControl圖層列表。把ToolbarControl的Buddy屬性設(shè)為MapControl表示這個(gè)工具條控制的是哪張地圖同理TOCControl的Buddy也設(shè)為MapControl。設(shè)置Buddy后工具條上會(huì)自動(dòng)綁定放大、縮小、平移等內(nèi)置命令不用寫(xiě)一行代碼這是Arc Engine對(duì)新手最友好的地方。3.2 加載Shapefile數(shù)據(jù)接下來(lái)寫(xiě)核心代碼加載一個(gè)Shapefile文件到地圖里。這一步要嚴(yán)格按照前面說(shuō)的對(duì)象模型鏈條來(lái)寫(xiě)先打開(kāi)工作空間再取要素類再包裝成圖層最后加到地圖。using ESRI.ArcGIS.Carto; using ESRI.ArcGIS.Geodatabase; using ESRI.ArcGIS.DataSourcesFile; private void btnLoadData_Click(object sender, EventArgs e) { // 1. 打開(kāi)文件夾工作空間 IWorkspaceFactory workspaceFactory new ShapefileWorkspaceFactoryClass(); IFeatureWorkspace featureWorkspace workspaceFactory.OpenFromFile(D:\GISData, 0) as IFeatureWorkspace; // 2. 打開(kāi)要素類 IFeatureClass featureClass featureWorkspace.OpenFeatureClass(counties.shp); // 3. 將要素類轉(zhuǎn)換為圖層 IFeatureLayer featureLayer new FeatureLayerClass(); featureLayer.FeatureClass featureClass; featureLayer.Name featureClass.AliasName; // 4. 添加圖層到地圖 IMap map axMapControl1.Map; map.AddLayer(featureLayer); axMapControl1.ActiveView.Refresh(); }這段代碼里第2步的OpenFeatureClass參數(shù)只需要寫(xiě)文件名不帶擴(kuò)展名。第4步的ActiveView.Refresh()一定要寫(xiě)否則圖層雖然加進(jìn)去了但界面上不刷新看起來(lái)就像沒(méi)反應(yīng)一樣。3.3 寫(xiě)一個(gè)屬性查詢功能地圖能顯示出來(lái)只是入門(mén)的一半另一半是讓圖層和數(shù)據(jù)交互起來(lái)。屬性查詢是最常用的功能用戶輸入一個(gè)地名程序定位到對(duì)應(yīng)要素并閃爍高亮。這里的關(guān)鍵是構(gòu)造IQueryFilterusing ESRI.ArcGIS.Geodatabase; using ESRI.ArcGIS.Carto; using ESRI.ArcGIS.Display; private void SearchAndFlash(IFeatureLayer layer, string fieldName, string keyword) { IQueryFilter queryFilter new QueryFilterClass(); queryFilter.WhereClause fieldName keyword ; IFeatureCursor cursor layer.Search(queryFilter, false); IFeature feature cursor.NextFeature(); if (feature ! null) { axMapControl1.FlashFeature(feature); MessageBox.Show(找到要素ObjectID feature.OID); } else { MessageBox.Show(未找到匹配記錄); } }IFeatureCursor是查詢結(jié)果集類似數(shù)據(jù)庫(kù)里的游標(biāo)。layer.Search(queryFilter, false)的第二個(gè)參數(shù)false表示不需要做回收復(fù)用的優(yōu)化一般寫(xiě)false即可。FlashFeature是MapControl自帶的高亮閃爍方法在調(diào)試階段非常方便不需要自己控制符號(hào)閃爍節(jié)奏。3.4 空間查詢與按幾何范圍篩選屬性查詢解的是“按字段值找要素”的問(wèn)題空間查詢則解決“按位置關(guān)系找要素”的問(wèn)題。比如“選出所有與某條道路相交的地塊”這在用地分析、選址評(píng)估里非常常用。Arc Engine里用ISpatialFilter實(shí)現(xiàn)using ESRI.ArcGIS.Geodatabase; using ESRI.ArcGIS.Geometry; private IFeatureCursor SelectBySpatial(IFeatureLayer targetLayer, IGeometry geometry, esriSpatialRelEnum relation) { ISpatialFilter spatialFilter new SpatialFilterClass(); spatialFilter.Geometry geometry; spatialFilter.GeometryField targetLayer.FeatureClass.ShapeFieldName; spatialFilter.SpatialRel relation; IFeatureCursor cursor targetLayer.FeatureClass.Search(spatialFilter, false); return cursor; }這里最容易出錯(cuò)的是GeometryField必須設(shè)置為要素類的Shape字段名通常就是字符串SHAPE但最好通過(guò)FeatureClass.ShapeFieldName動(dòng)態(tài)獲取避免因?yàn)樽侄蚊远x而踩坑。esriSpatialRelEnum是一個(gè)枚舉esriSpatialRelIntersects表示相交esriSpatialRelWithin表示在內(nèi)部esriSpatialRelContains表示包含。具體用哪個(gè)取決于你的業(yè)務(wù)語(yǔ)義。4. 常見(jiàn)問(wèn)題與排查技巧實(shí)錄4.1 高發(fā)問(wèn)題速查表Arc Engine開(kāi)發(fā)中反復(fù)遇到的坑我整理成了一張速查表方便你遇到問(wèn)題時(shí)快速定位現(xiàn)象根本原因解決辦法啟動(dòng)時(shí)彈出“Failed to create object. Check that the component is properly registered”對(duì)應(yīng)DLL未注冊(cè)或未引用重新安裝對(duì)應(yīng)組件檢查是否以管理員身份運(yùn)行過(guò)注冊(cè)工具啟動(dòng)時(shí)提示“0x80040228”或“No such interface supported”許可未初始化或License Manager服務(wù)未啟動(dòng)檢查Windows服務(wù)中的ArcGIS License Manager并重新執(zhí)行許可證初始化地圖控件白屏不顯示數(shù)據(jù)圖層添加后未刷新或數(shù)據(jù)源路徑錯(cuò)誤執(zhí)行ActiveView.Refresh()用System.IO.File.Exists先驗(yàn)證路徑部署到其他機(jī)器后無(wú)法打開(kāi)地圖目標(biāo)機(jī)器未安裝Runtime或沒(méi)有授權(quán)安裝對(duì)應(yīng)版本的Arc Engine Runtime并完成許可配置打開(kāi)大數(shù)據(jù)量Shapefile時(shí)非常卡缺少空間索引或做了全圖實(shí)時(shí)渲染柵格化或使用IFeatureCache、降低繪制頻率必要時(shí)檢查數(shù)據(jù)坐標(biāo)系4.2 許可初始化的那些坑許可問(wèn)題占了Arc Engine開(kāi)發(fā)排錯(cuò)量的一半。除了服務(wù)沒(méi)啟動(dòng)還有一個(gè)高頻錯(cuò)誤項(xiàng)目里啟動(dòng)了許可但控件拖出來(lái)之后沒(méi)有放到LicenseControl上。Arc Engine在設(shè)計(jì)器里渲染控件時(shí)也需要檢查許可如果你新建窗體后直接拖MapControl而沒(méi)有先放一個(gè)LicenseControl在窗體上設(shè)計(jì)器會(huì)報(bào)錯(cuò)“There is no license for this product”。解決辦法是先拖LicenseControl讓它自動(dòng)完成設(shè)計(jì)期的許可綁定再拖其他GIS控件。還有一點(diǎn)容易被忽略如果開(kāi)發(fā)機(jī)裝的是ArcGIS Desktop比如ArcMap許可類型是esriLicenseProductCodeArcInfo而不是esriLicenseProductCodeEngine。這時(shí)候用Engine的授權(quán)初始化可能會(huì)失敗。比較穩(wěn)妥的做法是初始化時(shí)做兩級(jí)降級(jí)嘗試先Engine再Basic最后Generic。這就是我前面模板代碼里寫(xiě)了兩行Initialize的原因。4.3 性能優(yōu)化的個(gè)人經(jīng)驗(yàn)當(dāng)你把功能都跑通之后會(huì)面臨真正的挑戰(zhàn)數(shù)據(jù)量上來(lái)之后程序卡頓明顯。關(guān)于性能優(yōu)化我有幾個(gè)實(shí)測(cè)有效的經(jīng)驗(yàn)避免頻繁刷新不是每次操作都調(diào)用ActiveView.Refresh()這樣會(huì)重繪所有圖層。局部變化用ActiveView.PartialRefresh(ViewDrawPhase.esriViewGeography, layer, null)只刷新單個(gè)圖層性能差距是數(shù)量級(jí)的。盡量不用不支持空間索引的格式Shapefile雖然通用但做空間查詢時(shí)性能確實(shí)不如File GDB。如果數(shù)據(jù)量超過(guò)幾百萬(wàn)要素建議入庫(kù)到File GDB里再處理。及時(shí)釋放COM對(duì)象Arc Engine的對(duì)象大多繼承自COM不主動(dòng)釋放會(huì)占用資源。對(duì)不再使用的IFeatureCursor、IFeature等對(duì)象調(diào)用System.Runtime.InteropServices.Marshal.ReleaseComObject()或者直接用finally塊統(tǒng)一處理。這點(diǎn)在長(zhǎng)時(shí)間運(yùn)行的工具里尤其重要否則內(nèi)存占用會(huì)像手機(jī)緩存一樣越堆越高。4.4 關(guān)于部署的一個(gè)提醒開(kāi)發(fā)完成后你可能需要把程序部署到其他電腦上。注意Arc Engine Runtime不能和ArcGIS Desktop共存這一點(diǎn)很多項(xiàng)目都在這上面栽過(guò)跟頭。目標(biāo)機(jī)器如果已經(jīng)裝了ArcMap再裝Runtime會(huì)導(dǎo)致授權(quán)沖突程序無(wú)法運(yùn)行。部署時(shí)還需要把項(xiàng)目引用的所有ESRI開(kāi)頭的DLL拷貝到輸出目錄最好一并打包Visual C運(yùn)行庫(kù)和.NET Framework環(huán)境??梢允褂肐nstallShield或其他打包工具將這些做成一個(gè)離線安裝包目標(biāo)機(jī)器解壓后一鍵安裝效率會(huì)高很多。我個(gè)人在實(shí)際操作中體會(huì)比較深的一點(diǎn)不要為了追求“寫(xiě)得多”而忽視最基礎(chǔ)的對(duì)象模型理解。Arc Engine的語(yǔ)法結(jié)構(gòu)幾十年沒(méi)大變過(guò)理解了Workspace、FeatureClass、Layer這條主線再去看網(wǎng)上那些復(fù)雜的代碼你會(huì)發(fā)現(xiàn)其實(shí)都是這一條主線加上不同的業(yè)務(wù)包裝而已。建議你在跑通本文的示例后試著把加載的數(shù)據(jù)換成File GDB、把查詢條件換成模糊匹配、把渲染換成按字段分類著色每做一步你對(duì)這套東西的掌控感就會(huì)增強(qiáng)一分。等你把這些基礎(chǔ)點(diǎn)都玩熟了再去碰復(fù)雜的編輯操作和地圖制圖輸出就會(huì)順暢很多。本文還有配套的精品資源點(diǎn)擊獲取