1.前言
前一段公司出于成本和效率的原因,要求在我们的产品中用开源的 GIS 组件来代替以前使用的MapX,经过一段时间的查找,我发现了MapWinGIS这个东西基本可以满足我们的要求。经过几天的摸索,我写了这篇文章,希望能对其他使用MapWinGIS的人有所帮助。我平常接触GIS很少,也最多就是在开发系统中偶尔用到MapX,很多基本概念也不了解,所以可能有很多错误的地方,忘见谅。
2.MapWinGIS简单介绍
MapWinGIS是一个开源的ActiveX组件,功能上类似MapX,开发人员可以利用这个ActiveX组件在自己的系统中完成GIS的相关功能,例如地图,IMAGE,GRID的显示;在 图层 上标绘点,线,图形;计算长度,存取GIS 数据 等相关工作.
它的主要功能包括:
直接打开,编辑,保存Image,Grid,Shapfile,TIN,DBF格式的文件
在地图中对图形进行浏览,标注,设置颜色等信息。
在地图中进行空间数据查询
动态的在图层上标绘空间数据。
存取地图中的相关数据。
对不同的格式进行转换,例如从TIN到GIRD等。
MapWinGIS团队的开发人员似乎主要来自爱荷华大学,MapWinGIS按Mozilla Public License 1.1.发布。在MapWinGIS 的基础上,他们又开发了MapWindow GIS,这是一套GIS系统,用户可以直接通过它浏览数据,并通过一系列插件来完成其他功能。还有一套MapWinX,是DOTNET平台下的一个辅助工具。
MapWinGIS 是在VS2003下由VC开发而成,MapWindow GIS 是VB.NET开发的。其中MapWinGIS 代码大约有15万行,大约50%的注释率。在MapWindow的网站提供了一个VB6的简单例子代码,也可以通过观看MapWindow GIS来学习它的一些更高级的功能。
它的官方网站是 [ www.mapwindow.org ] ,在网站上提供了二进制 程序 下载,源代码下载,并且有相关的论坛和文档,资料算是比较丰富的,似乎好像有一个开发人员是华人,所以还有个中文论坛,就是帖子太少。
3.文件格式的转换
由于用户提供的是MapInfo的TAB格式的底图,而MapWinGIS不能处理该格式的文件,所以必须要进行相关的转换工作,具体做法为:首先将MapInfo的TAB格式的底图转换为MapInfo的MIF格式(MIF格式是MapInfo的文本交换格式文件).然后再将MIF文件格式转换为MapWinGIS可以直接读取的SHP格式(SHP格式是ESRI公司定义的空间数据文件格式).
我们可以用MITAB将TAB格式转换为MIF格式,MITAB是一套开源的C++库(具体请参见[mitab.maptools.org]),用户可以根据这个库在自己的应用程序中实现转换,MITAB也提供了一个命令行工具tab2tab.exe 直接将TAB格式转换为MIF格式,具体命令为:
tab2tab source.tab dist.mif
在得到MIF文件后,可以用OGR将MIF格式转换为SHP格式,OGR也是一套开源的C++库(具体请参见[ogr.maptools.org]),用户可以根据这个库在自己的应用程序中实现转换,ORG也提供了一个命令行工具ogr2ogr.ext来实现转换,转换的命令为:
ogr2ogr -f "ESRI Shapefile" dist.shp source.mif
在根据TAB文件得到SHP文件后,我们就可以使用MapWinGIS了。
4.对象介绍
在MapWinGIS中,最常用的对象有如下几个:
Map:MapWinGIS最主要的对象,用于显示Grid,Image和Shapefile,它管理所有的图层,并响应鼠标操作,类似于MapX中的Map对象。
Shapefile:一个图层对象,可以由用户创建,也可以从图层文件中生成(SHP文件),类似于MapX中的Layer对象,可以在该对象上标点,标线,标图形等。
Shape:一个图形对象,可以是点,先,面等。
Point:代表了一个坐标点
Field:存储相关地理信息的一个字段,类似于DBF 表中的字段。
Table:存储相关地理信息的一个表。
5.MapWinGIS的使用
MapWinGIS在VB下使用的比较简单,网站上也提供了VB的例子,已经很详细了,我这里主要讲一下在VC中的调用和一些我使用上的碰到的一些问题
5.1 调用方式
在VC6中,似乎由于一些特殊原因(原因将在后面谈及),无法象使用其他ActiveX一样,直接从Project->Add To Project ->Components and Controls 中选择MapWinGIS.ocx,生成相关的封装类,它将报一个错,错误是"The ActiveX Control is not registered properly ,or its type library version number is incorrect…"。因而只能从Gallery/ Registered ActiveX Controls中选择已经注册的Map Control加入到这个项目中,但用这种方式只能生成Map对象的CMap封装类,其他对象无法生成封装类。如果从Type Library 中生成"COleDispatchDriver"的子类封装的化,则在调用相关的封装类的方法或属性时,例如CShapefile的Open方法,则会产生一个异常,异常的消息代码为"8002801D"。关于这个异常,MS有一个相关的解释,大致就是ATL的版本似乎不对,前面无法正常插入这个OCX 对象大概也是这个原因,这篇文章的地址为:"http://support.microsoft.com/default.aspx?scid=kb%3Ben-us%3B221792"。在MapWindow的论坛上我也看到了有人提出过这个问题,大概也有人碰到过。所以,我试出来正确的调用方式是使用"#import "MapWinGIS.ocx"rename_namespace("mapWindow") rename("GetObject", "MapWinGISGetObject")",为了省事,仍然可用前面插入OCX的方法创建一个CMap的封装类,使用Map对象时直接调用这个Map的封装类,而其他对象使用IXXXXPtr的smart pointer,下面是调用过程:
1.在Project->Add To Project ->Components and Controls插入OCX对象,生成CMap1封装类
2.在Stdafx头文件中增加#import "MapWinGIS.ocx" rename_namespace("mapWindow") rename("GetObject", "MapWinGISGetObject")"
3.创建一个资源ID,选择View菜单中的Resource Symbols,创建一个新的资源ID,命名为IDC_MAP
4.在View的头文件中,Include产生的封装类头文件,然后加入封装类的对象,如下代码:
#include "Map.h"
Class CTestMapWindowView :public CView
{
Protected:
CMap1 m_Map;
}
5.在视图(假设为文档-视图结构)的应用中对View类创建WM_CREATE的消息映射函数,加入如下代码:
if(m_Map.Create(NULL, WS_VISIBLE, CRect(0,0,400,400), this, IDC_MAP) == false){
return -1;
}
6.在View类中创建WM_SIZE的消息映射函数,加入如下代码
void CTestMapWindowView::OnSize(UINT nType, int cx, int cy)
{
CView::OnSize(nType, cx, cy);
// TODO: Add your message handler code here
m_Map.Resize(cx,cy);
}
这样,应用程序的框架基本完成,可以编译,运行了。
5.2 加入图层
由于在图层Shapefile对象中使用了smart pointer进行封装,因而加入图层的代码为
mapWindow::IShapefilePtr sf;
HRESULT hr=sf.CreateInstance("MapWinGIS.Shapefile");
if (!SUCCEEDED(hr)){
return ;
}
USES_CONVERSION;
//打开图层文件
if(!sf->Open(A2BSTR("C:\\temp\\testgis\\shp\\11.shp"),NULL)){
return ;
}
//将设置颜色,标签等信息,将图层加入到Map中
long hanlde=m_Map.AddLayer(sf,true); m_Map.SetShapeLayerFillColor(handle,RGB((255-1)*rand(),(255-1)*rand(),(255-1)*rand()));
m_Map.SetShapeLayerLineColor(handle,RGB((255-1)*rand(),(255-1)*rand(),(255-1)*rand()));
m_Map.SetLayerLabelsVisible(handle,TRUE);
5.3 处理事件
要处理MapWinGIS的事件,需要创建一个事件槽(eventsink).具体方法如下,在View类的头文件中,在DECLARE_MESSAGE_MAP()行下加入"DECLARE_EVENTSINK_MAP()" ,在CPP文件中加入如下代码:
BEGIN_EVENTSINK_MAP(CTestMapWindowView,CView)
END_EVENTSINK_MAP()
为了响应MouseMove事件,在上述两行代码中加入如下代码
ON_EVENT(CTestMapWindowView,IDC_MAP,0x03,OnMouseMove,VTS_I2 VTS_I2 VTS_I4 VTS_I4)
然后对CTestMapWindowView 类增加一个方法,函数声明为:
afx_msg void OnMouseMove(short _button,short _shift,long _x,long _y);
需要说明的是,在MapWinGIS中,为了能够响应事件,必须在调用SetSendMouseMove(true)后才能响应鼠标移动事件,其他响应事件也是如此类推。
5.4改变显示位置
在用户希望地图能够自动漫游时,先调用Map对象的GetExtents对象得到Map的Extents 对象,然后调用Extents的对象的SetBounds来设置相关边界,最后调用Map对象的SetExtents将这个Extents对象设置到Map对象中.
5.5 动态标绘
当用户希望比较频繁的动态标绘数据时,可以使用NewDrawing来创建一个绘画层,然后调用DrawPoint ,DrawLine 或DrawPolygon 来在刚才的创建的层上标出点,线和多边形。用ClearDrawing可以清除在这个层上所绘制的所有图形,对于动态轨迹的显示,就是在调用Draw后,用ClearDrawing清除,然后再重画相应的轨迹。
用户可以多次调用NewDrawing来创建新的绘画层,对新的绘画层上的相关操作并不影响到其他已经创建的绘画层。用户可以调用ClearDrawing来清除指定的绘画层上所有的图形。但是,在创建了新的绘画层后,无法对其他的绘画层再进行任何绘画操作;同时在调用了Draw后,也无法对所绘制的对象进行操作。唯一可用的是可以可以对刚才创建的对象进行设置一个Key,对刚才绘制的对象设置一个字符串,并可以读取这个字符串。但是无法得到以前设置的其他对象的字符串。
5.6 动态绘制图层
除了用NewDrawing来创建一个绘画图层外,还可以动态的创建一个Shapefile,在这个Shapefile上进行点,线,多边形的绘制操作,在创建这个图层过程中可以增加其他附加数据。即增加一个或若干个Field,在插入一个Shape后,可以指定在Field上插入该行的数据。在该图层上,所有的图形对象均可进行操作。
5.7 ShapefileColorBreak对象和ShapefileColorScheme对象
在创建ShapefileColorBreak对象和ShapefileColorScheme对象时,他们的ProgID分别为"MapWinGIS.ShapefileLegendBreak"和"MapWinGIS.ShapefileLegend",而不是"MapWinGIS.ShapefileColorBreak"和"MapWinGIS.ShapefileColorSchema"。在一开始我想当然的人为对象的名字就是ProgId的名字,后来发现不对,这里提醒一下。
6.其他说明
MapWinGIS的显示速度同MapX相比快几倍个数量级左右,我曾经做过试验,如果在图上标绘100万个点,如果使用MapX,大约要5-10分钟,如果使用MapWinGIS,在Shapefile上绘制要1分钟左右,使用NewDraw则更快。
可能是由于SHP本身格式的一些原因,一些MapInfo地图中特有的信息,例如铁路线,特殊标记的点(例如医疗机构的红十字),还有线的宽度,在转换到SHP后就没有了。因而显示出来的地图没有在MapX中显示的好看,当然可以通过其他方法来显示这些信息,不过就比较麻烦了,如果用户对GIS系统的要求比较高的化,用MapWinGIS可能就会有一些麻烦。
在下了MapWinGIS的源码后我用VS2005编译了一下,发现无法通过编译;用VS2003,在Debug版本下连接出现错误,Release版本则编译通过了,具体什么原因,有时间还要好好琢磨一下
前一段公司出于成本和效率的原因,要求在我们的产品中用开源的 GIS 组件来代替以前使用的MapX,经过一段时间的查找,我发现了MapWinGIS这个东西基本可以满足我们的要求。经过几天的摸索,我写了这篇文章,希望能对其他使用MapWinGIS的人有所帮助。我平常接触GIS很少,也最多就是在开发系统中偶尔用到MapX,很多基本概念也不了解,所以可能有很多错误的地方,忘见谅。
2.MapWinGIS简单介绍
MapWinGIS是一个开源的ActiveX组件,功能上类似MapX,开发人员可以利用这个ActiveX组件在自己的系统中完成GIS的相关功能,例如地图,IMAGE,GRID的显示;在 图层 上标绘点,线,图形;计算长度,存取GIS 数据 等相关工作.
它的主要功能包括:
直接打开,编辑,保存Image,Grid,Shapfile,TIN,DBF格式的文件
在地图中对图形进行浏览,标注,设置颜色等信息。
在地图中进行空间数据查询
动态的在图层上标绘空间数据。
存取地图中的相关数据。
对不同的格式进行转换,例如从TIN到GIRD等。
MapWinGIS团队的开发人员似乎主要来自爱荷华大学,MapWinGIS按Mozilla Public License 1.1.发布。在MapWinGIS 的基础上,他们又开发了MapWindow GIS,这是一套GIS系统,用户可以直接通过它浏览数据,并通过一系列插件来完成其他功能。还有一套MapWinX,是DOTNET平台下的一个辅助工具。
MapWinGIS 是在VS2003下由VC开发而成,MapWindow GIS 是VB.NET开发的。其中MapWinGIS 代码大约有15万行,大约50%的注释率。在MapWindow的网站提供了一个VB6的简单例子代码,也可以通过观看MapWindow GIS来学习它的一些更高级的功能。
它的官方网站是 [ www.mapwindow.org ] ,在网站上提供了二进制 程序 下载,源代码下载,并且有相关的论坛和文档,资料算是比较丰富的,似乎好像有一个开发人员是华人,所以还有个中文论坛,就是帖子太少。
3.文件格式的转换
由于用户提供的是MapInfo的TAB格式的底图,而MapWinGIS不能处理该格式的文件,所以必须要进行相关的转换工作,具体做法为:首先将MapInfo的TAB格式的底图转换为MapInfo的MIF格式(MIF格式是MapInfo的文本交换格式文件).然后再将MIF文件格式转换为MapWinGIS可以直接读取的SHP格式(SHP格式是ESRI公司定义的空间数据文件格式).
我们可以用MITAB将TAB格式转换为MIF格式,MITAB是一套开源的C++库(具体请参见[mitab.maptools.org]),用户可以根据这个库在自己的应用程序中实现转换,MITAB也提供了一个命令行工具tab2tab.exe 直接将TAB格式转换为MIF格式,具体命令为:
tab2tab source.tab dist.mif
在得到MIF文件后,可以用OGR将MIF格式转换为SHP格式,OGR也是一套开源的C++库(具体请参见[ogr.maptools.org]),用户可以根据这个库在自己的应用程序中实现转换,ORG也提供了一个命令行工具ogr2ogr.ext来实现转换,转换的命令为:
ogr2ogr -f "ESRI Shapefile" dist.shp source.mif
在根据TAB文件得到SHP文件后,我们就可以使用MapWinGIS了。
4.对象介绍
在MapWinGIS中,最常用的对象有如下几个:
Map:MapWinGIS最主要的对象,用于显示Grid,Image和Shapefile,它管理所有的图层,并响应鼠标操作,类似于MapX中的Map对象。
Shapefile:一个图层对象,可以由用户创建,也可以从图层文件中生成(SHP文件),类似于MapX中的Layer对象,可以在该对象上标点,标线,标图形等。
Shape:一个图形对象,可以是点,先,面等。
Point:代表了一个坐标点
Field:存储相关地理信息的一个字段,类似于DBF 表中的字段。
Table:存储相关地理信息的一个表。
5.MapWinGIS的使用
MapWinGIS在VB下使用的比较简单,网站上也提供了VB的例子,已经很详细了,我这里主要讲一下在VC中的调用和一些我使用上的碰到的一些问题
5.1 调用方式
在VC6中,似乎由于一些特殊原因(原因将在后面谈及),无法象使用其他ActiveX一样,直接从Project->Add To Project ->Components and Controls 中选择MapWinGIS.ocx,生成相关的封装类,它将报一个错,错误是"The ActiveX Control is not registered properly ,or its type library version number is incorrect…"。因而只能从Gallery/ Registered ActiveX Controls中选择已经注册的Map Control加入到这个项目中,但用这种方式只能生成Map对象的CMap封装类,其他对象无法生成封装类。如果从Type Library 中生成"COleDispatchDriver"的子类封装的化,则在调用相关的封装类的方法或属性时,例如CShapefile的Open方法,则会产生一个异常,异常的消息代码为"8002801D"。关于这个异常,MS有一个相关的解释,大致就是ATL的版本似乎不对,前面无法正常插入这个OCX 对象大概也是这个原因,这篇文章的地址为:"http://support.microsoft.com/default.aspx?scid=kb%3Ben-us%3B221792"。在MapWindow的论坛上我也看到了有人提出过这个问题,大概也有人碰到过。所以,我试出来正确的调用方式是使用"#import "MapWinGIS.ocx"rename_namespace("mapWindow") rename("GetObject", "MapWinGISGetObject")",为了省事,仍然可用前面插入OCX的方法创建一个CMap的封装类,使用Map对象时直接调用这个Map的封装类,而其他对象使用IXXXXPtr的smart pointer,下面是调用过程:
1.在Project->Add To Project ->Components and Controls插入OCX对象,生成CMap1封装类
2.在Stdafx头文件中增加#import "MapWinGIS.ocx" rename_namespace("mapWindow") rename("GetObject", "MapWinGISGetObject")"
3.创建一个资源ID,选择View菜单中的Resource Symbols,创建一个新的资源ID,命名为IDC_MAP
4.在View的头文件中,Include产生的封装类头文件,然后加入封装类的对象,如下代码:
#include "Map.h"
Class CTestMapWindowView :public CView
{
Protected:
CMap1 m_Map;
}
5.在视图(假设为文档-视图结构)的应用中对View类创建WM_CREATE的消息映射函数,加入如下代码:
if(m_Map.Create(NULL, WS_VISIBLE, CRect(0,0,400,400), this, IDC_MAP) == false){
return -1;
}
6.在View类中创建WM_SIZE的消息映射函数,加入如下代码
void CTestMapWindowView::OnSize(UINT nType, int cx, int cy)
{
CView::OnSize(nType, cx, cy);
// TODO: Add your message handler code here
m_Map.Resize(cx,cy);
}
这样,应用程序的框架基本完成,可以编译,运行了。
5.2 加入图层
由于在图层Shapefile对象中使用了smart pointer进行封装,因而加入图层的代码为
mapWindow::IShapefilePtr sf;
HRESULT hr=sf.CreateInstance("MapWinGIS.Shapefile");
if (!SUCCEEDED(hr)){
return ;
}
USES_CONVERSION;
//打开图层文件
if(!sf->Open(A2BSTR("C:\\temp\\testgis\\shp\\11.shp"),NULL)){
return ;
}
//将设置颜色,标签等信息,将图层加入到Map中
long hanlde=m_Map.AddLayer(sf,true); m_Map.SetShapeLayerFillColor(handle,RGB((255-1)*rand(),(255-1)*rand(),(255-1)*rand()));
m_Map.SetShapeLayerLineColor(handle,RGB((255-1)*rand(),(255-1)*rand(),(255-1)*rand()));
m_Map.SetLayerLabelsVisible(handle,TRUE);
5.3 处理事件
要处理MapWinGIS的事件,需要创建一个事件槽(eventsink).具体方法如下,在View类的头文件中,在DECLARE_MESSAGE_MAP()行下加入"DECLARE_EVENTSINK_MAP()" ,在CPP文件中加入如下代码:
BEGIN_EVENTSINK_MAP(CTestMapWindowView,CView)
END_EVENTSINK_MAP()
为了响应MouseMove事件,在上述两行代码中加入如下代码
ON_EVENT(CTestMapWindowView,IDC_MAP,0x03,OnMouseMove,VTS_I2 VTS_I2 VTS_I4 VTS_I4)
然后对CTestMapWindowView 类增加一个方法,函数声明为:
afx_msg void OnMouseMove(short _button,short _shift,long _x,long _y);
需要说明的是,在MapWinGIS中,为了能够响应事件,必须在调用SetSendMouseMove(true)后才能响应鼠标移动事件,其他响应事件也是如此类推。
5.4改变显示位置
在用户希望地图能够自动漫游时,先调用Map对象的GetExtents对象得到Map的Extents 对象,然后调用Extents的对象的SetBounds来设置相关边界,最后调用Map对象的SetExtents将这个Extents对象设置到Map对象中.
5.5 动态标绘
当用户希望比较频繁的动态标绘数据时,可以使用NewDrawing来创建一个绘画层,然后调用DrawPoint ,DrawLine 或DrawPolygon 来在刚才的创建的层上标出点,线和多边形。用ClearDrawing可以清除在这个层上所绘制的所有图形,对于动态轨迹的显示,就是在调用Draw后,用ClearDrawing清除,然后再重画相应的轨迹。
用户可以多次调用NewDrawing来创建新的绘画层,对新的绘画层上的相关操作并不影响到其他已经创建的绘画层。用户可以调用ClearDrawing来清除指定的绘画层上所有的图形。但是,在创建了新的绘画层后,无法对其他的绘画层再进行任何绘画操作;同时在调用了Draw后,也无法对所绘制的对象进行操作。唯一可用的是可以可以对刚才创建的对象进行设置一个Key,对刚才绘制的对象设置一个字符串,并可以读取这个字符串。但是无法得到以前设置的其他对象的字符串。
5.6 动态绘制图层
除了用NewDrawing来创建一个绘画图层外,还可以动态的创建一个Shapefile,在这个Shapefile上进行点,线,多边形的绘制操作,在创建这个图层过程中可以增加其他附加数据。即增加一个或若干个Field,在插入一个Shape后,可以指定在Field上插入该行的数据。在该图层上,所有的图形对象均可进行操作。
5.7 ShapefileColorBreak对象和ShapefileColorScheme对象
在创建ShapefileColorBreak对象和ShapefileColorScheme对象时,他们的ProgID分别为"MapWinGIS.ShapefileLegendBreak"和"MapWinGIS.ShapefileLegend",而不是"MapWinGIS.ShapefileColorBreak"和"MapWinGIS.ShapefileColorSchema"。在一开始我想当然的人为对象的名字就是ProgId的名字,后来发现不对,这里提醒一下。
6.其他说明
MapWinGIS的显示速度同MapX相比快几倍个数量级左右,我曾经做过试验,如果在图上标绘100万个点,如果使用MapX,大约要5-10分钟,如果使用MapWinGIS,在Shapefile上绘制要1分钟左右,使用NewDraw则更快。
可能是由于SHP本身格式的一些原因,一些MapInfo地图中特有的信息,例如铁路线,特殊标记的点(例如医疗机构的红十字),还有线的宽度,在转换到SHP后就没有了。因而显示出来的地图没有在MapX中显示的好看,当然可以通过其他方法来显示这些信息,不过就比较麻烦了,如果用户对GIS系统的要求比较高的化,用MapWinGIS可能就会有一些麻烦。
在下了MapWinGIS的源码后我用VS2005编译了一下,发现无法通过编译;用VS2003,在Debug版本下连接出现错误,Release版本则编译通过了,具体什么原因,有时间还要好好琢磨一下