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