声明:此系列文章,不是说教,不是告诉任何人如何利用 C++ 开发电子地图的教程,而且本人水平有限,也达不到教程的地步。只是利用此系列的文章,来记录开发基于 C++ 应用在 Windows 下 开发电子地图的过程。愿对 C++ 开发感兴趣的朋友,熟知 Gis 开发的朋友,了解 Wms 、 WFS 协议的朋友,亦或是对 GoogleMap 之类感兴趣的朋友,共同讨论。(废话到此结束)。
通讯模块
此通讯模块目的在于根据输入,下载指定 URL 的内容。由于本人很少从事网络方面的编码,这部分很不擅长,当初,设计时,欲寻找一套简单易用的库,所以选择了 WinInet 实现此部分逻辑,但是随着知识的增长,了解到 WinINet 是极不稳定的,所以对网络编程熟悉的朋友可自行实现此部分功能。例如:可以使用 Socket 来完成此功能。
重中之重
GetCapability
bool BStarWmsPostMan ::SaveCapFile (char * strAddr ,char buf [], int & buflen )
{
char * temp = "SERVICE=WMS&VERSION=1.1.1&REQUEST=GetCapabilities" ;
char url [512];
memcpy_s (url ,512, strAddr ,256);
strcat_s (url ,512,temp );
if (!ReciveBuffer (url ,buf , buflen ))
{
delete [] buf ;
buf = NULL ;
return false ;
}
}
解释:
strAddr :是你服务器的地址字符串
char buf [] :接收到的字符串。
int & buflen :接收到的字符串的长度。
GetMap
我给出一个如何拼接 GetMap 命令的函数,主要目的是让大家了解 GetMap 字符串如何拼接的,这样有利于大家自己实现下载功能。
bool BStarWmsPostMan ::GetMap (LATLONBB maparea , char buf [], int & buflen )
{
char strTemp [1280];;
char url [2560];
memcpy_s (url ,2560,m_strAddr ,256);
sprintf_s (strTemp ,1280,"BBOX=%f,%f,%f,%f&SRS=EPSG:%s&WIDTH=%d&HEIGHT=%d&LAYERS=%s&STYLES=&FORMAT=%s&DPI=96"
,maparea .minx ,maparea .miny
,maparea .maxx ,maparea .maxy
,m_layerProperty .Srs
,m_layerProperty .Width ,m_layerProperty .Height
,m_layerProperty .Name
,m_layerProperty .ImageType );
strcat_s (url ,2560, "SERVICE=WMS&version=1.0.0&request=getmap&" );
strcat_s (url , 2560, strTemp );
if (!ReciveBuffer (url ,buf ,buflen ))
{
return false ;
}
return true ;
}
解释:
LATLONBB maparea :这个数据结构大家看过前文的,应该记得,由四个double 组成,代表左右上下四个边界的经纬度值。
char buf [] :接收到的字符串。
int & buflen :接收到的字符串的长度。
如果你看明白了,而且想做个稳定的通讯模块,好的,就别再往下看了,自己实现吧。主要工作就是用你自己的方法实现ReciveBuffer() 这个函数。
模块功能
先让我们来看看此功能模块是做什么用的,有哪些事情是要处理的。
流程图如下:
再让我们用文字描述一下这个流程:
明确任务
首先明确,通讯模块需要得到什么样的数据呢?
(1) 还记得 Wms 协议中的 GetCapability 吗?对了,得到服务器属性的功能,我们需要通过 GetCapability 来得到一个关于当前地图服务器描述的 XML 表。从而得到很多我们需要的信息,例如当前服务器上存在的地图图层有哪一些,每个图层的经纬度范围是什么,每个图层的名称是什么,投影系是什么等等。
(2) 重点当然是我们要通过 GetMap 来获取我们指定图层、指定坐标范围内的图片了。此模块当然还要能下载这些指定的图片。
其次,我们通过 GetCapability 得到的关于服务器属性的 XML 描述,要经过分析得到我们关心的数据集合了。我这里采用了一个轻量级的 XML 库来做到这一点。选择了 TinyXML (上网看一下关于此的简介吧)。由于我希望透视 XML 分析过程,所以这里没有采用他的库文件,而是直接使用了他的源代码( TinyXML 是开源的)。经过分析、确认的数据会被我存到我定义好的数据结构中。
再回首
明确了功能后,回过头来,再来看一下流程图 。
( 1 ) 其中“设置服务器”是一个拼接命令给 GetCapability 作为输入的过程。简单的说来,比如你的服务器地址是 http://wms.lizardtech.com/lizardtech/iserv/ows
如果你使用的是一个 1.1.1 版本的 WMS 服务器 , 那么如果加上“ ?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetCapabilities ”这个字符串之后,就相当于设置完成 GetCapability 命令的拼接。只需要打开“ http://wms.lizardtech.com/lizardtech/iserv/ows ?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetCapabilities ”这个 URL ,那么你就会得到一个 XML 文件。
( 2 )接下来,“获取服务器属性”以及“得到服务器相关属性”的两个步骤,就是我要通过 TinyXML 分析这个 XML 文件,并将我们关心的数据存入我们自定义的数据结构中。
( 3 ) 随后,“工作对象的相关属性”,应为我们是要针对对象开发的,意思就是我可以建立多个独立的地图对象,所以,每个独立的地图对象要拥有各自独立的通讯模 块,所以,通讯模块的实现要通过建立类,用类的实例来实现,所以工作对象的属性,就是指每个类实例中的一些私有化成员变量将得到赋值。
( 4 )最后,每个通讯对象的实例,要根据自己的输入去下载数据到本地保存,我的是现实是用 WinINet ,强烈建议大家用 socket 实现,这里要说的是, XML 下载每个通讯实例只需要完成一次,而图片的下载要完成多次。(这里是根据上层模块的多线程安排下载的,之后会在文件模块的说明中介绍)。
( 5 ) 补充说明,这里有个问题,不知道大家注意到没有,下载文件的过程相对于其他操作是耗时的工作,如果用户没耐心看完当前的地图,或者不关心当前地图,从而又 开始了新的操作,我们需要一种中断机制,让正在下载的工作停止。这是很重要的,个人在工作中感觉到,好的功能代码要做到全代码调用的可控。相当于开线程, 不能让线程成为脱缰野马的道理一样。
模块接口及数据结构
数据结构
// 经纬度范围
typedef struct tagLATLONBB
{
double minx ; // 最小x
double miny ; // 最小y
double maxx ; // 最大x
double maxy ; // 最大y
}LATLONBB , *PLATLONBB ;
#endif
//wms 标准协议中包含数据
typedef struct tagCAPABILITYDATAS
{
int Width ; // 图片像素宽度
int Height ; // 图片像素高度
char ImageType [BUFFER_SIZE_COMMON ]; // 图片类型
char Srs [BUFFER_SIZE_COMMON ]; // 投影类型
char Name [BUFFER_SIZE_SPECIAL ]; // 层名(用于拼接)
char Title [BUFFER_SIZE_SPECIAL ]; // 层名(用于显示)
LATLONBB BoundBox ; // 层范围(该层最大范围)
}CAPABILITYDATAS ,*PCAPABILITYDATAS ;
对外接口
// 登录服务器
DLL_API int LogIn (const char * strAddr );
// 2 次调用 ,1 次确定buflen ,2 次真正的操作
DLL_API bool GetCapability (int index , CAPABILITYDATAS itemsName [], int & count );
// 设置 用户关心的层
DLL_API LATLONBB SetCapability (int index , CAPABILITYDATAS itemsName [],const int count );
// 获取图层2 次获取 ,1 次确定buflen ,2 次真正的操作
DLL_API bool GetMap (int index , LATLONBB maparea , char buf [], int & buflen );
// 中断操作 ,舍弃当前下载的工作
DLL_API void AbortMap (int index , BOOL IsAbort );
// 登出服务器
DLL_API void LogOut (int index );
// 复制服务器信息
DLL_API void CopyServerData (int indexSrs , int indexDes );
TinyXML
我们看一下针对我们的应用,TinyXML 的使用。(这只是个建议,可以使用其他XML 操作的库),其实使用起来还是很简单的,至少是读取很简单吧。
bool BStarWmsPostMan ::GetCapItems (CAPABILITYDATAS items [], int & count )
{
LATLONBB tempLatlon ;
TiXmlDocument * myDocument = new TiXmlDocument ();
BOOL bHaveLatlon = FALSE ;
char filename [64];
sprintf_s (filename ,"%s-%d" ,"GetCapability" ,m_Index );
if (!myDocument ->LoadFile (filename ,TIXML_ENCODING_UNKNOWN ))
{
return FALSE ;
}
TiXmlElement * rootElement = myDocument ->RootElement ();//class
TiXmlElement * CapabilityElement = rootElement ->FirstChildElement ("Capability" );
TiXmlElement * LayersElement = CapabilityElement ->FirstChildElement ("Layer" );
TiXmlElement * allName = LayersElement ->FirstChildElement ("Name" );
TiXmlElement * LayersTitleElement = LayersElement ->FirstChildElement ("Title" );
TiXmlElement * LayerElement = LayersElement ->FirstChildElement ("Layer" );
TiXmlElement * LayerTitleElement = LayerElement ->FirstChildElement ("Title" );
while (LayerElement )
{
indextemp ++;
TiXmlElement * LayerSrsElement = LayerElement ->FirstChildElement ("SRS" );
TiXmlElement * LayerTitleElement = LayerElement ->FirstChildElement ("Title" );
TiXmlElement * LayerNameElement = LayerElement ->FirstChildElement ("Name" );
TiXmlElement * LayerLatLonBoundingBox = LayerElement ->FirstChildElement ("LatLonBoundingBox" );
TiXmlElement * LayerBoundingBox = LayerElement ->FirstChildElement ("BoundingBox" );
TiXmlElement * LayerSrs = LayerElement ->FirstChildElement ("SRS" );
TiXmlAttribute * RectofLayerLatLonBoundingBox = LayerLatLonBoundingBox ->FirstAttribute ();
Datas [indextemp ].BoundBox .minx = atof (RectofLayerLatLonBoundingBox ->Value ());
RectofLayerLatLonBoundingBox =RectofLayerLatLonBoundingBox ->Next ();
Datas [indextemp ].BoundBox .miny = atof (RectofLayerLatLonBoundingBox ->Value ());
RectofLayerLatLonBoundingBox =RectofLayerLatLonBoundingBox ->Next ();
Datas [indextemp ].BoundBox .maxx = atof (RectofLayerLatLonBoundingBox ->Value ());
RectofLayerLatLonBoundingBox =RectofLayerLatLonBoundingBox ->Next ();
Datas [indextemp ].BoundBox .maxy = atof (RectofLayerLatLonBoundingBox ->Value ());
LATLONBB tempsort ;
tempsort = Datas [indextemp ].BoundBox ;
if (tempsort .minx > tempsort .maxx )
{
Datas [indextemp ].BoundBox .minx = tempsort .maxx ;
Datas [indextemp ].BoundBox .maxx = tempsort .minx ;
}
if (tempsort .miny > tempsort .maxy )
{
Datas [indextemp ].BoundBox .miny = tempsort .maxy ;
Datas [indextemp ].BoundBox .maxy = tempsort .miny ;
}
memcpy (Datas [indextemp ].Name ,(char *)LayerNameElement ->GetText (),128);
memcpy (Datas [indextemp ].Srs , "4326" ,sizeof ( "4326" ));
memcpy_s (Datas [indextemp ].Title , BUFFER_SIZE_SPECIAL ,(char *) LayerTitleElement ->GetText (),128);
memcpy_s (Datas [indextemp ].ImageType , BUFFER_SIZE_COMMON , "image/jpeg" ,sizeof ("image/jpeg" ));
Datas [indextemp ].Width = 256;
Datas [indextemp ].Height = 256;
if (items != NULL )
{
memcpy_s (items [indextemp ].Name ,BUFFER_SIZE_SPECIAL ,Datas [indextemp ].Name ,BUFFER_SIZE_SPECIAL );
items [indextemp ].BoundBox = Datas [indextemp ].BoundBox ;
memcpy_s (items [indextemp ].Title ,BUFFER_SIZE_SPECIAL , Datas [indextemp ].Title ,BUFFER_SIZE_SPECIAL );
memcpy_s (items [indextemp ].ImageType ,BUFFER_SIZE_COMMON ,Datas [indextemp ].ImageType ,BUFFER_SIZE_COMMON );
items [indextemp ].Width = Datas [indextemp ].Width ;
items [indextemp ].Height = Datas [indextemp ].Height ;
memcpy_s (items [indextemp ].Srs ,BUFFER_SIZE_COMMON , Datas [indextemp ].Srs ,BUFFER_SIZE_COMMON );
}
LayerElement = LayerElement ->NextSiblingElement ("Layer" );
}
count = indextemp + 1;
delete myDocument ;
myDocument = NULL ;
return true ;
}
GetMap 下载过程
虽然强烈建议大家用 socket 开发图片下载这部分,不过还是给出 WinInet 的实现吧。
我定义了一个类
头文件
#ifndef _CVodStreamCache_H
#define _CVodStreamCache_H
#include <iostream>
#include <Windows.h>
#include <WinInet.h>
#include <tchar.h>
#include <string>
using namespace std ;
#pragma comment (lib , "wininet.lib" )
class CVodStreamCache
{
public :
CVodStreamCache ();
~CVodStreamCache ();
bool Create (char token [], char url []);
int ReadBlock (long offset , int len , char *buffer );
bool Recive (char buf [], int & buflen );
void Destory ();
void CacheInfo ();
void Abort (BOOL IsAbort );
private :
HINTERNET internetOpen ;
HINTERNET internetOpenUrl ;
DWORD dwStatusCode ;
DWORD dwContentLength ;
HANDLE createFile ;
static const int BUFFERSIZE = 9216;
BOOL m_IsAbort ;
};
#endif //_CVodStreamCache_H
CPP 文件
#include "stdafx.h"
#include <stdio.h>
#include "CVodStreamCache.h"
CVodStreamCache ::CVodStreamCache ()
{
internetOpen = NULL ;
internetOpenUrl = NULL ;
m_IsAbort = false ;
}
CVodStreamCache ::~CVodStreamCache ()
{
}
bool CVodStreamCache ::Create (char token [], char url [])
{
internetOpen = InternetOpen (NULL , INTERNET_OPEN_TYPE_PRECONFIG , NULL , NULL , 0);
if (internetOpen == NULL )
{
cout << "Internet open failed!" << endl ;
return false ;
}
if (m_IsAbort )
{
InternetCloseHandle (internetOpen );
return false ;
}
if (internetOpen == NULL )
{
cout << "Internet open failed!" << endl ;
return false ;
}
internetOpenUrl = InternetOpenUrlA (internetOpen , url ,
NULL , 0, INTERNET_FLAG_TRANSFER_BINARY | INTERNET_FLAG_PRAGMA_NOCACHE , 0);
if (internetOpenUrl == NULL )
{
cout << "Internet open url failed! Error code = " << GetLastError () << endl ;
InternetCloseHandle (internetOpen );
return false ;
}
return true ;
}
int CVodStreamCache ::ReadBlock (long offset , int len , char *buffer )
{
return 0;
}
bool CVodStreamCache ::Recive (char * buf , int & buflen )
{
BOOL internetReadFile ;
char buffer [BUFFERSIZE ];
memset (buffer , 0, sizeof (buffer ));
DWORD byteRead = 0;
DWORD byteDown = 0;
while (1)
{
if (m_IsAbort )
{
InternetCloseHandle (internetOpenUrl );
InternetCloseHandle (internetOpen );
return false ;
}
if (!internetOpenUrl )
{
InternetCloseHandle (internetOpenUrl );
InternetCloseHandle (internetOpen );
return false ;
}
internetReadFile = InternetReadFile (internetOpenUrl , buffer , sizeof (buffer ), &byteRead );
if (byteRead == 0)
{
break ;
}
if (m_IsAbort )
{
InternetCloseHandle (internetOpenUrl );
InternetCloseHandle (internetOpen );
return false ;
}
if (NULL != buf )
{
memcpy_s (buf +byteDown , buflen - byteDown , buffer , byteRead );
}
byteDown += byteRead ;
}
InternetCloseHandle (internetOpenUrl );
internetOpenUrl = NULL ;
InternetCloseHandle (internetOpen );
internetOpen = NULL ;
buflen = byteDown ;
return 1;
}
void CVodStreamCache ::Abort (BOOL IsAbort )
{
m_IsAbort = IsAbort ;
}
................................................ 未完待续 ................................................