MapObjects是一組基于COMTRANBBS技術的地圖應用組件,它由一個稱為Map的ActiveX控件(OCX)和約45個自動化對象組成,在標準的Windows編程環境下,能夠與其他圖形、多媒體、數據庫開發技術組成完全獨立的綜合性應用軟件,是基于前端應用業務的良好的地圖開發環境。
MapObjects是全球最大的GIS軟件供應商ESRI公司在業界最早推出的GIS軟件組件,它起點高、功能強、結構優雅。ESRI在推出其每一個版本時,都采取了非常嚴謹慎重的態度。從1.0版本算起,MapObjects正式問世已經5年,才發展到2.1版本,可見每個版本質量控制之嚴格。事實上,MapObjects是全球范圍內使用最廣的GIS組件,也是潛在錯誤被最充分暴露并得以糾正的軟件組件,其穩定可靠性無以置疑。有經驗的程序員都知道,在軟件開發過程中,穩定性壓倒一切,而這正是MapObjects能被成功應用的重要前提和保證。
作為ESRI公司GIS軟件族的重要成員,憑借ESRI公司在GIS領域的領先技術和市場地位,MapObjects操作的數據資源與ESRI的旗艦產品ArcGIS完全兼容,從結構簡練的桌面數據格式(Shape文件)到以拓撲關系為基礎的經典ArcInfo Coverage,以至基于數據庫(DBMS)和ArcSDE基礎之上的Geodatabase,都能夠被MapObjects讀取。除了矢量數據以外,MapObjects還能夠讀取多種格式的柵格數據,如BMP、TIF、JPEG、ArcInfo Grid、Erdas Image等。MapObjects本身也能夠生成Shape文件格式的GIS數據,該格式已經成為GIS業界事實上的基于桌面應用的標準。采用MapObjects,能夠最大限度地與主流GIS技術融合,保護用戶在數據生產、功能開發、以及人員培訓上所作的一切投資。
1、 MapObjects的體系結構
MapObjects可以說得上是最優雅簡潔的GIS軟件組件。我們這么說,是因為它以最少的接口提供了常用的GIS功能,甚至GPS的動態特性,同時做到了結構合理,簡單明了,容易理解和擴展。與之相比,其他的GIS組件,要不是由于提供的功能過多而破壞了其內在的結構美并影響了用戶擴展的靈活性,就是結構過于簡單而使功能大大弱化。可以說,MapObjects在功能和結構兩者之間,取得了完美平衡,體現出了軟件的藝術性,給人帶來愉悅的感受。
1.1 數據結構
從MapObjects對地圖數據的組織方式來看,概念清晰,易于理解。它認為一個綜合性的地圖由多個圖層構成,圖層數據來源廣泛,既可以是GIS矢量圖層,也可以是CAD圖層,甚至影像數據。對于GIS和CAD的矢量圖層,其內部統一用記錄集(Recordset)來表達,這樣就抹平了各種不同格式數據之間在內存中的表達鴻溝,簡化了程序員的數據觀點。記錄集正象關系數據庫中的二維表,行表示每個要素、列表示每個屬性。如果含有名稱為 “Shape”的列,則該記錄集表示的是地圖要素,否則表示的是普通的數據庫表格。這樣,在數據結構內部就消除了GIS和MIS數據之間的差異,使程序員開發GMIS綜合應用系統易如反掌。
Shape字段以面向對象的技術封裝了要素的圖形部分。它把圖形劃分為三種類型,即點、線、面。這些幾何類型既簡單又復雜,說它簡單,是因為很容易理解,所有的圖形歸根到底都是由點、線、面構成的;說它復雜,是因為這些點、線、面并不是簡單的點、線、面,實際上它引入了一個部件(Part)的概念,由點構成部件,部件由復合點構成、線和面則由部件構成。
如果只有一個部件,則它是簡單的線或面,如果有兩個以上的部件,則構成復合線或面。例如夏威夷群島,由很多小島嶼構成,但它是一個整體,其中的每個小島都可以用Part來表示,多個Part構成的多邊形放到一個Shape字段中。
至于注記,則被描述為屬性字段的自動標注。標注的位置由Shape字段中的圖形要素決定,它可以是點、線或面,通常是線。標注的內容由普通屬性表示,既可以是字符型,也可以是整型或浮點型,通常是字符型。這樣,一切要素既可以按圖形表示,也可以按其屬性內容標注顯示,甚至可以兩者同時顯示,地圖顯示和制圖的方式是極為靈活的。
1.2 功能接口
再從MapObjects所提供的功能接口來看,常用的地圖應用基本上都能實現,例如:
* 創建新的Shape文件
* 更新屬性或圖形數據
* 繪制點、線、橢圓、矩形和多邊形等圖形要素
* 繪制描述性的文本注記
* 地圖簡單點取查詢、空間查詢、相對位置查詢、SQL邏輯條件查詢等
* 空間統計
* 地圖縮放和漫游
* 豐富的繪圖方式,如按值潤色、分類顯示、繪制密度圖、產生含各類圖表的專題圖等。
* 屬性自動標注
* 顯示航空和衛星遙感影像
* 動態顯示實時或順時數據,如GPS動態監測
* 地址匹配
* 投影變換
* Buffer、Union、Intersect等空間分析算子
通過調用這些接口,能夠開發從簡單的電子地圖應用,至復雜的基于GIS/GPS/RS的3S應用。利用空間分析算子,甚至可以構造出具有一定復雜度的空間分析模型。由于提供了數據更新接口,用戶能夠擴展出自己的編輯工具,富融公司便基于MapObjects 2.0/2.0a/2.1擴展了近50種的編輯工具,使之能夠滿足更為廣泛的應用要求。
2、 MapObjects的開發過程
MapObjects的使用和開發過程與其他的ActiveX控件沒有兩樣,在Visual Basic、Delphi、Visual C++等能夠支持控件開發的編程環境下,一旦把控件插入到編程項目中,就可以通過接口使用控件所提供的各種方法。
由于MapObjects聯機幫助和隨機手冊中的大部分編程說明都采用Visual Basic作為代碼示例,而Visual C++的說明相對較少,為了方便Visual C++程序員的工作,以下的開發過程和代碼示例使用Visual C++,在其他環境下的編程過程大致類似。
以下說明均假設在Windows下已成功地安裝了MapObjects控件。我們將構造一個簡單的電子地圖應用程序,它可以增加一個新圖層,實現地圖的放大、縮小,并且可以實現要素的定位功能。
2.1 生成Visual C++編程項目
使用Visual C++“File | New”打開新建工程對話框,使用MFC AppWizard(exe)產生一個新的MFC應用程序,我們把工程名稱取為“mmap”,該向導一共有6步,前5步都按缺省選項,第6步把Basic Class由CView改為CFormView,這樣完成后產生的應用程序將有一個表單模板,其標識號為IDD_MMAP_FROM,初始狀態是一行靜態文本,內容為“TODO:在這個對話框里設置表格控制”(如果是英文版或選擇的語言為英文,則提示換成英文)。把這行字刪掉,以便在此表單中加入地圖控件。
2.2 加入MapObjects地圖控件
使用“Project | Add To Project | Components and Controls...”菜單功能打開“Components and Controls Gallery”對話框,從其“Registered ActiveX Controls”文件夾下的已登記控件列表中找到“MapObjects 2.1 Map Control”并按“Insert”按鈕,系統將生成該控件及其自動化對象的一系列包裹類(Wrapper Class),把第一個類名稱由CMap1改為CMoMap,實現文件保持不變,即map.h和map.cpp。結束該對話框后,在控制條中會增加一個地圖控件圖標,把該圖標插入到IDD_MMAP_FORM表單中。然后選中剛插入的控件圖標,在用左手按住鍵盤的“Ctrl”鍵的情況下,右手雙擊鼠標左鍵,將彈出一個“Add Member Variable”對話框,在成員變量名稱欄輸入“m_map”,下面兩欄保持不變,即Category為Control,Variable Type為CMoMap。做完這些工作后,回到ClassView中觀察CMmapView類,將發現增加了一個類型為CMoMap的m_map對象,利用它就可以操作地圖了。
2.3 使用Map對象增加圖層
這時編譯程序,應該不會出錯,但在運行時,發現除了在表單視圖中增加了一個空白的代表地圖控件的小圖標外,應用程序與剛生成時的情況并沒有什么太大的區別。為了增加圖層數據,首先要把地圖控件的尺寸放大到與表單視圖一樣大,然后把圖層數據加入到地圖對象中。
第一步、找到CMmapView的OnInitialUpdate()方法,作如下操作:
(1)如果原來有“ResizeParentToFit();”語句,注釋掉或刪除它;
(2)在返回語句之前增加如下語句:
// 將顯示尺寸調整到整個客戶區
CRect client;
GetClientRect(&client);
int cx=client.Width();
int cy=client.Height();
m_map.SetWindowPos(0, 0, 0, cx, cy, SWP_NOZORDER);
第二步、找到CChildFrame,重載其OnClientCreate()方法,該方法將在創建表單客戶區時被調用,在其返回語句之前增加語句:“MDIMaximine();”。
完成上面兩步后,再次編譯程序并運行,在原來表單客戶區應該出現一個空白的地圖,接下來往這個空白圖上增加圖層。為了簡單起見,我們假設圖層數據放在C:\data目錄下,圖層格式為Shape文件,其文件名為test.shp。
第三步、在CMmapView中創建一個增加shape文件的方法AddShpLayer(),其實現為:
增加SHP圖層,返回圖層內部名稱,為空表示不成功。
CString CMmapView::AddShpLayer(const CString & path, COLORREF color, short symbolSize, short symbolStyle)
{
CMoDataConnection conn;
if (!conn.CreateDispatch(TEXT("MapObjects2.DataConnection"))) return "";
conn.SetDatabase(GetFileDirectory(path));
if (!conn.Connect()) return "";
// Add layer specified by path
CMoLayers layers=m_map.GetLayers();
CMoMapLayer layer;
if (!layer.CreateDispatch(TEXT("MapObjects2.MapLayer"))) return "";
CString LayerName = GetFileTitle(path);
CMoGeoDataset geoDataset=conn.FindGeoDataset(LayerName);
if(!geoDataset) return "";
layer.SetGeoDataset(geoDataset);
CMoSymbol layerSymbol(layer.GetSymbol());
if (color != -1) layerSymbol.SetColor(color); // Set color if specified
layerSymbol.SetSize(symbolSize);
layerSymbol.SetStyle(symbolStyle);
layers.Add(layer);
return(layer.GetName());
}
為了使這段代碼能夠被順利編譯,還要在mmapview.cpp的文件開始處增加如下include語句:
#include "modataconnection.h"
#include "molayers.h"
#include "momaplayer.h"
#include "mogeodataset.h"
#include "mosymbol.h"
另外,在CMmapView中增加幾個輔助函數,用于分析圖層文件路徑中的文件名、目錄名:
CString CMmapView::GetFileDirectory(const CString& path)
{
int pos = path.ReverseFind('\\');
if (pos >= 0) return path.Left(pos);
return "";
}
CString CMmapView::GetFileTitle(const CString& path)
{
CString strResult = GetFileName(path);
int pos = strResult.ReverseFind('.');
if (pos >= 0) return strResult.Left(pos);
return strResult;
}
CString CMmapView::GetFileName(const CString& path)
{
int pos = path.ReverseFind('\\');
if (pos >= 0) return path.Right(path.GetLength() - pos - 1);
return path;
}
第四步,回到CMmapView的OnInitialUpdate(),在其返回之前加上如下語句:
AddShpLayer("c:\\data\\test.shp", RGB(125,125,125), 0, 0);
再次編譯后運行(運行前確保C:\data\test.shp圖層文件存在,即至少包括c:\data\test.dbf、c:\data\test.shp、c:\data\test.shx三個文件),應該能夠觀察到test圖層被加入到了地圖中。
2.4 使用map對象操作地圖
接下來我們完成對地圖的放大縮小操作,當點擊鼠標左鍵時,地圖放大1倍,當點擊鼠標左鍵時,地圖回到全圖顯示。
第一步,使用類向導(Class Wizzard)在CMmapView中增加一個地圖消息響應函數。即選中CMmapView中的IDC_MAP1,在Message列表框中雙擊MouseDown,將生成一個
OnMouseDownMap1()消息函數。
第二步,在OnMouseDownMap1()中加入如下語句:
if(Button==1)
{
CMoRectangle rect=m_map.GetExtent();
rect.ScaleRectangle(0.5);
m_map.SetExtent(rect);
}
else if(Button==2)
{
CMoRectangle rect=m_map.GetFullExtent();
m_map.SetExtent(rect);
}
為了使用CMoRectangle,還需要在mmapview.cpp的開始部分加一個include語句,即:
#include "morectangle.h"
按下鼠標左鍵時,在消息響應函數中的Button參數記錄按下的是哪個鍵,1表示左鍵,2表示右鍵。編譯后運行,分別點擊鼠標左右鍵,應該觀察到地圖放大和縮回到全圖的效果。
2.5 使用Recordset對象檢索數據
接下來我們想找到圖層中的第一個要素,即其FeatureId為1(在MapObjects要素圖層中,FeatureId是其固有的字段,用于記錄每個要素在圖層中的序號),找到這個要素后,把它放在視圖窗口的中央顯示,這就類似于一個條件定位的功能。
第一步,在IDR_MMAPTYPE菜單中增加一個“定位”菜單項至“查看”菜單下,設其ID號為ID_FEATURE_LOCATE。
第二步,使用類向導(Class Wizzard)產生該菜單項在CMmapView中的消息響應函數,即與COMMAND和UPDATA_COMMAND_UI兩個消息對應的OnFeatureLocate()和OnUpdateFeatureLocate(),在OnUpdateFeatureLocate()的實現中增加下行語句,使該菜單總是處于激活狀態:
pCmdUI->Enable();
第三步,在OnFeatureLocate()中增加如下語句:
void CMmapView::OnFeatureLocate()
{
CMoLayers layers=m_map.GetLayers();
CMoMapLayer layer=layers.Item(COleVariant(TEXT("test")));
if(layer)
{
CMoRecordset recs=layer.SearchExpression(_T("FeatureId = 1"));
recs.MoveFirst();
if(!recs.GetEof())
{
CMoFields fields=recs.GetFields();
CMoField shapeField=fields.Item(COleVariant(TEXT("Shape")));
if(shapeField)
{
switch(shapeField.GetType())
{
case 21:
{
CMoPoint point=shapeField.GetValue().pdispVal;
if(point) m_map.CenterAt(point.GetX(), point.GetY());
}
break;
case 22:
{
CMoLine line=shapeField.GetValue().pdispVal;
if(line)
{
CMoRectangle rect=line.GetExtent();
rect.ScaleRectangle(1.5);
m_map.SetExtent(rect);
}
}
break;
case 23:
{
CMoPolygon polygon=shapeField.GetValue().pdispVal;
if(polygon)
{
CMoRectangle rect=polygon.GetExtent();
rect.ScaleRectangle(1.5);
m_map.SetExtent(rect);
}
}
break;
}
}
}
}
}
在這里使用到了記錄集、字段、點、線、面等對象,因此在文件頭部還要增加如下include文件:
#include "morecordset.h"
#include "mofields.h"
#include "mofield.h"
#include "mopoint.h"
#include "moline.h"
#include "mopolygon.h"
在switch/case語句中的21表示要素的幾何類型為點,22表示線,23表示面。編譯后運行,并選擇“定位”菜單,程序將找到test圖層中的第一個要素,并把它放在窗口的中央顯示出來。如果加入的圖層數據是點層,我們建議把其symbolSize設為10,以便觀察到點位。即把OnInitialUpdate()中的AddShpLayer方法調用換作:
AddShpLayer("c:\\data\test.shp", RGB(255,0,0), 10, 0);
從這個例子中我們看到了如何通過圖層(Layer)執行一個SQL查詢語句,獲得記錄集后如何對其進行檢索,并提取出具體字段內容。對于圖形圖層,“Shape”字段也是固定存在的,其中存放了該要素的圖形幾何部分,通過使用字段的GetType()方法可以獲得該圖層是點層、線層或面層,并作出相應的定位處理。字段的GetValue()方法返回的是一個VARIANT類型值,其中封裝了各種各樣的數據類型,在Shape字段中,它封裝的是一個圖形要素,可以通過pdispVal取得它的真正內容,并根據圖層類型轉換為相應的圖形要素,作為計算地圖顯示范圍的依據。