基于Wms协议的Map开发(六:通讯模块)

声明:此系列文章,不是说教,不是告诉任何人如何利用 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 次确定buflen2 次真正的操作

DLL_API bool   GetCapability (int index , CAPABILITYDATAS itemsName [], int & count );

// 设置 用户关心的层

DLL_API LATLONBB SetCapability (int index , CAPABILITYDATAS itemsName [],const int count );

// 获取图层2 次获取 1 次确定buflen2 次真正的操作

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 * LayerTitleElementLayerElement ->FirstChildElement ("Title" );

      while (LayerElement )

      {  

           indextemp ++;

           TiXmlElement * LayerSrsElement = LayerElement ->FirstChildElement ("SRS" );

           TiXmlElement * LayerTitleElementLayerElement ->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 ].BoundBoxDatas [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 ;

}

................................................ 未完待续 ................................................

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值