1.MapInfo GIS二次开发概述
MapInfo是美国MapInfo公司的产品,是一种桌面地图信息系统。它为用户提供了 完整的地理信息解决方案,以帮助用户实现数据的可视化。它自带一个功能强大、面向对象的编程工具——MapBasic,这就给用户提供了一个很好的二次开 发环境。目前MapInfo已逐渐成为地理信息系统领域应用较为广泛的工具之一,在我国的测绘、铁路、邮电、水利、林业等部门得到了广泛应用,并收到了良 好的经济和社会效益。
目前MapInfo GIS的二次开发方法主要有以下几种方法:
(1)方法一:直接使用MapBasic编制应用或分析模型
MapBasic是与MapInfo密切集成的开发工具.通过它,用户可以定制MapInfo的界面,取代MapInfo的标准菜单,并能完成复杂、多样的数据查询及对地理对象进行各种空间操作。但MapBasic开发的界面较单调,且对话框等常用Windows控件的编写较为困难。
(2)方法二:动态数据交换(DDE)
通过DDE 建立MapInfo与应用模型之间的通信。DDE 会话是两个Windows应用程序交换信息的过程,,只有当两个支持DDE 的应用程序运行时,它们之间才可以进行DDE 会话。在一个会话中,只有一个应用程序是主动的,称为客户,被动的应用程序称为服务器。但这种G IS 与应用模型是分离的,不能保证服务器程序对客户程序的指令作出正确处理,故这种方式现在较为少用。
(3)方法三:通过OLE自动化技术(OLEAUTOMATION)
MapInfo向容器程序暴露其可编程对象,这与通常在office系列软件中嵌入OLE对象是同一原理。容器程序可以采用VC,VB,Delphi ,Pb等高级语言,编程手段灵活,但对地图的对象操作、控制仍然离不开MapBasic语言,且高级语言对嵌入MapBasic 的编写和解释较为困难。
(4) 方法四:利用MapX空间技术
MapX控件是Map Info公司向用户提供的具有强大地图分析功能的ActiveX控件,适用于大多数面向对象语言,可以无缝嵌入到各个领域的应用系统当中去。用户不但需掌握一门高级语言,且要十分了解MapX的对象结构体系,许多功能需用户利用MapX提供的方法属性组织编写。
综观以上4种二次开发方法,方法四是较好的一种。开发人员不仅可以编写强大的专业地理信息系统,而且可以利用各种各级语言,充分发挥自己的想象力和创造力,任意定制界面风格,并且可以脱离MapInfo软件环境独立运行,而前几种开发方法则需要MapInfo软件环境。
本文就如何采用VC++和MapX进行GIS二次开发,以及可能遇到的一些问题进行了总结,希望对有一定VC++ 基础,并想利用MapX控件进行二次开发的人们有所帮助。
2.MapX特性
在将如何编程之前,应该对MapX的空间数据结构以及其对象模型有所了解,才能很好的掌握利用MapX进行编程的技术。
2.1 MapX的空间数据结构
空间数据结构是GIS的基石,通过这些地理空间拓扑结构建立地理图形的空间数据模型并定义各空间数据之间的关系,从而实现地理图形和数据库的结合。如图所示,地理信息系统采用分层管理的方式管理地图数据,同一类型的空间对象存放在相同土层中。
图1.MapX的空间数据结构
2.2 MapX 对象模型
MapX控件采用面向对象的方法处理地理信息系统,对地理数据的操作实际上是对各类对象的 操作。MapX的基本组成单元是Object(单个对象)和Collection(集合)。其中集合包括对象,是多个对象的组合,每种对象与集合只能处理 地图某一方面的功能。MapX主要包括以下对象:
(1)Map
Map是MapX的顶层属性,每个Map对象主要包括Datasets、Layers、 Annotations三个对象集合。Map对象包括一些主要的属性,如Zoom用来设置放大级别(在地图上显示的大小),Rotation控制地图的旋 转角度,CenterX和CenterY用于设置地图显示窗口的中心坐标。
(2)Layers
在MapX中,地理信息按照图层的集合(Layers Collection)表示,每张单独的地图都被表示成单独的图层,所有的图层存储在Layers集合中。Layer对象由Features对象组成,Features对象又是由Feature对象组成,Feature对应于地图中的点、线、面以及符号等地图实体。
(3)GeoSet
GeoSet是在GeoManager中建立的GST文件,类似Maplnfo中的WorkSpace概念,是图层及其设置的集合,用于控制程序中显示的地图。
(4)Datasets
在MapX中,属性数据的操作主要通过数据绑定实现,Datasets用于实现地图与数据的绑定。数据绑定的数据源可以是DAO、ADO、ODBC数据源、RDO、Maplnfo Table文件,还可以是一个规定了格式的文本文件。要绑定一个数据源,首先要指定所绑定的图层,然后需要指定与图层中地图对象相匹配的关键字段。
(5)Annotations
Annotations集合提供了操纵地图中文字和符号的简单方法。Annotations位于所有其它图层的上方并且不与任何数据连接。
3.MapX在VC++ 环境下GIS基本功能的实现
在进行MapX 开发之前,确保已经在计算机中正确的安装了MapX 控件。下面就用一个具体的实例,介绍在VC+ +中集成MapX 的方法。
3.1 将MapX支持类库加入工程
在您的项目中包括MapX.cpp和.h文件。这两个文件包含用于对MapX控件进行访问的类定义和方法实现。MapX.h 和 MapX.cpp 文件可能位于安装有 MapX 的 Samples50\CPP 子目录中(如果您安装的事MapX 4.0,则路径为 …\ Samples40\C++\Cpp)。
利用VC++ 环境下的应用程序生成向导创建一单文档应用程序MapSample。将MapX.h和MapX.cpp文件拷贝到当前工程路径下,然后从Project菜单中选择Add to Project >Files命令,此时打开Insert Files into Project对话框,选择MapSample文件夹下的MapX.h和MapX〉cpp文件加入到工程中。
警告:不要选择Project菜单中的Add to Project > Components And Controls命令。这样做将会创建一个.cpp文件,但该文件将是不完整的。我一开始就是这么做的,结果很多常用函数都没有,郁闷了我很长时间。希望大家一起要切记这一点。
3.2 使用VC++创建MapX控件
在将包含该控件得视图中包括它:
1 #include “MapX.h”
2 :
3 :
4 :
5
6 Class CMapXSampleView : public CView
7 {
8 :
9 :
10 :
11
12 Protected:
13 CMapX m_ctrlMapX;
14 :
15 :
16 }
要声明表示用于MapX的控件ID的常数:
(1)选择“View”>”Resource Symble”。
(2)单击“New”。
(3)键入“IDC_MAP”来作为名称。
要在类向导中为WM_SIZE和WM_CREATE消息创建处理程序:
(1)转入”View”>”ClassWizard”;
(2)从“Class Name”组合框中选择您的视图类。
(3)在“Messages”列表框中,单击“WM_CREATE”,然后单击“Add Function”。
(4)接着还在该消息框中选择” WM_SIZE”并单击“Add Function”。
(5)然后,单击“Edit Code”
在创建视图时创建该控件。在CMapXSampleView::OnCreate中:
1 //create map with default size
2 //resize message will cause it to be size to the client area of the view
3 If(!m_ctrlMapX.Create(NULL,WS_VISIBLE, CRect(0,0,100,100),this,IDC_MAP))
4 Returen -1;
5 Keep the control’s size in sync with the containing window;
6 //resize the map to be the same size as our client area
7 Void CMapXSampleView:: OnSize(UINT nType, int cx, int cy)
8 {
9 CView::OnSize(nType, cx, cy);
10 if(cx!=0 && cy!=0)
11 m_ctrlMapX.MoveWindow(0,0,cx,cy);
12 }
像您为WM_CREATE消息所作的一样为WM_SETFOCUS消息创建新消息标头。
在我们的示例中,我们想要确保只要窗口被激活MapX就获得焦点:
1 void CMapXSampleView::OnSetFocus(CWnd* pOldWnd)
2 {
3 CView::OnSetFocus(pOldWnd);
4 m_ctrlMapX.SetFocus();
5 }
3.3 使用地图标准工具
在地图显示出来后,用户通常要以各种比例查看地图的全局、局部或细部,必须提供诸如放大、缩小和漫游等功能。采用MapX通用工具,可以非常方便地实现上 述功能。设定MapX使用标准工具的方法很简单,只需设定地图对象的CurrentTool属性。下面的例子是用标准放大工具实现放大功能。
添加新菜单项资源,输入标题“工具”,在“工具”下添加子菜单,输入标题“放大”及ID 为ID_ZOOM_ IN。打开类向导,选择视图类CMapXSampleView,为菜单项ID_ZOOM_IN 添加COMMAND 消息映射函数OnZoomIn ( ),并编辑码如下。
1 void CMapXSampleView::OnZoomIn()
2 {
3 m_ctrlMapX.SetCurrentTool(miZoomInTool);// miZoomInTool为放大工具常量
4 }
编译运行程序,选择“工具|放大”,就会看到此时光标变为放大镜,单击鼠标就会实现放大功能。可用相同方法实现其他标准工具的功能。MapX提供的可用标准工具如下表。
工具 | 常数 | 说明 |
添加线条 | miAddLineTool | 将线条图元添加到插入图层中。 |
添加点 | miAddPointTool | 单击该工具可将点图元添加到插入图层中。 |
添加折线 | miAddPolyLineTool | 将折线图元添加到插入图层中。 |
添加区域 | miAddRegionTool | 将区域图元添加到插入图层中。 |
箭头 | miArrowTool | 单击标题或注释此外,在可编辑图层中移动选定图元或调整选定图元的大小。 |
居中 | miCenterTool | 单击该工具可以重新将地图居中。 |
加标签 | miLabelTool | 在一个图元上单击可以给该图元加标签。 |
平移 | miPanTool | 拖动该工具可以重新将地图居中。 |
多边形选择 | miPolygonSelectTool | 单击该工具可以绘制一个多边形;该多边形内的对象将被选定。 |
半径选择 | miRadiusSelectTool | 拖动该工具可以选择半径内的图元。 |
矩形选择 | miRectSelectTool | 拖动该工具可以选择矩形内的图元。 |
选择工具 | miSelectTool | 单击该工具可以选择图元。 |
符号 | miSymbolTool | 放置符号注释。 |
文本 | miTextTool | 放置文本注释。 |
放大 | miZoomInTool | 放大。 |
缩小 | miZoomOutTool | 缩小。 |
3.4 使用自定义工具
MapX提供的地图标准工具能满足一般需要,但在一些特殊地方,用户可能需要某种特殊工具来完成某些特定的地图操作功能。因此,MapX提供了用户自定义工具的方法,这样可以大大扩展MapX的应用范围。开发者可使用地图对象的CreateCustomTool 方法创建自定义工具。一旦您已使用CreateCustomTool方法创建了定制工具后,就可以在用户使用定制工具时使用ToolUsed事件执行操作。
此示例进行测试以了解正使用哪一工具。然后,根据正在使用的工具,该示例或者:
• 更改光标下的地图图元的样式,或者
• 在用户单击处放置新符号。
1 void CMapXSampleView::OnToolUsed(short ToolNum, double X1, double Y1, double X2, double Y2, double Distance,BOOL Shift, BOOL Ctrl, BOOL* EnableDefault)
2 {
3 CString str;
4 CMapXPoint pnt;
5 str.Format("Tool=%d, [%f,%f] [%f, %f], dist=%f, %s %s\n",
6 ToolNum, X1,Y1,X2,Y2,Distance,
7 (Shift)?"Shift":"",(Ctrl)?"Ctrl":"");
8 TRACE(str);
9 // change the style of the feature under the cursor
10 if (ToolNum == MAP_TOOL_CHANGESTYLE) {
11 try {
12 // Need the dispatch to use the point
13 if (pnt.CreateDispatch(pnt.GetClsid())) {
14 pnt.Set(X1, Y1);
15 }
16 else {
17 // something went wrong, can't use the point... AfxThrowOleException(CO_E_CLASS_CREATE_FAILED);
18 }
19 CMapXLayers layers = m_ctrlMapX.GetLayers();
20 // Get the USA feature under the cursor
21 CMapXFeatures ftrs = layers.Item("USA").SearchAtPoint(LPDISPATCH(pnt));
22 // work on only the first feature
23 CMapXFeatureftr = ftrs.Item(1);
24 // get the style object from the feature
25 CMapXStylestyle = ftr.GetStyle();
26 style.SetRegionBackColor(255);
27 // update the feature in the layer
28 ftr.Update();
29 }
30 catch (COleDispatchException *e) {
31 e->ReportError();
32 e->Delete();
33 }
34 catch (COleException *e) {
35 e->ReportError();
36 e->Delete();
37 }
38 }
39 // place a new symbol at the point clicked on
40 else if (ToolNum == MAP_TOOL_NEWPOINT) {
41 try {
42 CMapXLayers layers = m_ctrlMapX.GetLayers();
43 CMapXFeatureftr;
44 // Need the dispatch id to use the feature
45 if (ftr.CreateDispatch(ftr.GetClsid())) {
46 // Symbol feature
47 ftr.SetType(miFeatureTypeSymbol);
48 // Get the point object from the feature
49 // and call the Set method
50 ftr.GetPoint().Set(X1, Y1);
51 // Add it to the layer
52 layers.Item("USA").AddFeature(ftr);
53 }
54 else { AfxThrowOleException(CO_E_CLASS_CREATE_FAILED);
55 }
56 }
57 catch (COleDispatchException *e) {
58 e->ReportError();
59 e->Delete();
60 }
61 catch (COleException *e) {
62 e->ReportError();
63 e->Delete();
64 }
65 }
66 }
3.5 处理MapX事件
若要处理MapX事件,您首先需要为感兴趣的事件建立eventsink地图。用于事件DISPATCH id 的常数是在MapX.h中为MapX定制事件定义的,并且是在 中为 OLE 普通事件定义的。
// From MapX.h
#define MAPX_DISPID_SELECTION_CHANGED 0x1
#define MAPX_DISPID_RESOLVEDATABIND 0x2
#define MAPX_DISPID_TOOLUSED 0x3
#define MAPX_DISPID_REQUESTDATA 0x4
#define MAPX_DISPID_DATAMISMATCH 0x5
#define MAPX_DISPID_MAPVIEWCHANGED 0x6
#define MAPX_DISPID_ANNOTATIONADDED 0x7
#define MAPX_DISPID_ANNOTATIONCHANGED 0x8
#define MAPX_DISPID_THEMEMODIFYREQUESTED 0x9
#define MAPX_DISPID_DRAWUSERLAYER 0x0a
#define MAPX_DISPID_POLYTOOLUSED 0x0b
// From
#define DISPID_CLICK (-600)
#define DISPID_DBLCLICK (-601)
#define DISPID_KEYDOWN (-602)
#define DISPID_KEYPRESS (-603)
#define DISPID_KEYUP (-604)
#define DISPID_MOUSEDOWN (-605)
#define DISPID_MOUSEMOVE (-606)
#define DISPID_MOUSEUP (-607)
#define DISPID_ERROREVENT (-608)
EVENT_SINK中的ON_EVENT 宏也为MapX控件指定一个ID (在该示例中为 IDC_MAP),指定该事件的参数,并且指定事件处理程序方法的名称。
“DECLARE_EVENTSINK_MAP ()”一行放置于“DECLARE_MESSAGE_MAP”行之下。
从CMapXSampleView.cpp:
BEGIN_EVENTSINK_MAP(CMapXSampleView, CView)
ON_EVENT(CMapXSampleView, IDC_MAP, DISPID_MOUSEMOVE,
OnMouseMoveInMap,VTS_I2 VTS_I2 VTS_XPOS_PIXELS VTS_YPOS_PIXELS)
ON_EVENT(CMapXSampleView, IDC_MAP, MAPX_DISPID_MAPVIEWCHANGED,
OnMapViewChanged, VTS_NONE)
ON_EVENT(CMapXSampleView, IDC_MAP, DISPID_MOUSEUP,
OnMouseUpInMap, VTS_I2 VTS_I2 VTS_XPOS_PIXELS VTS_YPOS_PIXELS)
ON_EVENT(CMapXSampleView, IDC_MAP, MAPX_DISPID_TOOLUSED,
OnToolUsed, VTS_I2 VTS_R8 VTS_R8 VTS_R8 VTS_R8 VTS_R8 VTS_BOOL
VTS_BOOL VTS_PBOOL)
ON_EVENT(CMapXSampleView, IDC_MAP,
MAPX_DISPID_THEMEMODIFYREQUESTED, OnThemeModifyRequested,
VTS_DISPATCH)
END_EVENTSINK_MAP()
随后是用于OnToolUsed 事件的事件处理程序代码。来自CMapXSampleView.h 的声明:
void OnToolUsed(short ToolNum, double X1, double Y1, double X2,double Y2, double
Distance, BOOL Shift, BOOL Ctrl, BOOL* EnableDefault);
…以及来自CMapXSampleView.cpp 的实现方式(我们只使用 TRACE 宏将参数输出到调试窗口):
void CMapxSampleView::OnToolUsed(short ToolNum, double X1, double Y1,
double X2, double Y2, double Distance, BOOL Shift, BOOL Ctrl, BOOL* EnableDefault)
{
CString str;
str.Format("Tool=%d, [%f,%f] [%f, %f], dist=%f, %s %s\n",ToolNum, X1,Y1,X2,Y2,Distance,
(Shift)?"Shift":"",(Ctrl)?"Ctrl":"");
TRACE(str);
}
3.6 添加快捷菜单
DISPID_MOUSEUP事件可用于添加快捷(鼠标右键按钮)菜单。为您的快捷菜单创建菜单资源(如果菜单资源的ID为IDR_CONTEXTMENU)。OnMouseUpInMap 处理程序类似于以下代码:
// if right mouse button, display the context menu
BOOL CMapxSampleView::OnMouseUpInMap(short Button, short Shift,
OLE_XPOS_PIXELS x,OLE_YPOS_PIXELS y)
{
if (Button == 2) { // right button
CMenu menu; // top-level menu
CMenu *pMenu=NULL; // pop-up menu
// Load the menu resource.
menu.LoadMenu(IDR_CONTEXTMENU);
// TrackPopupMenu cannot display the top-level menu, so get
// the handle of the first pop-up menu.
pMenu = menu.GetSubMenu(0);
if (!pMenu) {
return TRUE;
}
SetMenuDefaultItem(pMenu->m_hMenu, ID_VIEW_PROPERTIES,
FALSE);
// Display the floating pop-up menu. Track the right mouse
// button on the assumption that this function is called
// during WM_CONTEXTMENU processing.
POINT pt;
GetCursorPos(&pt);
pMenu->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,
pt.x, pt.y, this, NULL);
// Destroy the menu.
menu.DestroyMenu();
}
return TRUE;
}
3.7 MapX异常错误处理
MapX通过报告(引发)COleDispatchException 来报告大多数错误。在调用 MapX 时捕获异常错误是十分重要的,因为MFC的默认异常错误处理程序不捕获它们并且您的应用程序将退出。在消息框中显示对错误的说明也是非常有用的。错误消息的文本将帮助您确定是否正确使用了 MapX 图元,或者是否已发生某一其它问题。
COleDispatchException类还在公共成员m_wCode中包括错误代码,以便您的程序可以标识和处理不同类型的错误。对于错误代码及其说明的列表,请参见联机帮助中的 MapX 错误代码。
如果在尝试调用MapX时具有一般的OLE错误(在您使用过时的MapX.h 或 MapX.cpp 版本时可能发生),则 MFC 将引发 COleException。建议你在调用 MapX 时捕获全部两个异常错误类型。
MapX 也可能在 Error 事件中传递错误(尽管非常少见)。只有在不从MapX属性或方法直接调用的异步处理(像redraw)期间出现某种类型的错误时,才可能发生上述情况。
在此示例的 ThemeModifyRequested 事件的事件处理程序中,我们显示普通的“主题”属性对话框以让用户更改主题颜色等。请注意异常错误处理的方式。
// Note: in objects passed to events, the event handler
// does not change the reference count
// ie: do not call release on the object
void CMapxSampleView::OnThemeModifyRequested(LPDISPATCH Theme)
{
try {
CMapXTheme theme;
COptionalVariant vHelpFile, vHelpID;
// mark as optional since we don't have a helpfile
theme.AttachDispatch(Theme, FALSE); // don't auto release
theme.ThemeDlg(vHelpFile, vHelpID);
// could decide to bring up legend dlg here instead
//CMapXLegend leg(theme.GetLegend());
//leg.LegendDlg(vHelpFile, vHelpID);
}
catch (COleDispatchException *e) {
e->ReportError();
e->Delete();
}
catch (COleException *e) {
e->ReportError();
e->Delete();
}
}