QGis二次开发基础 -- 添加在线地图服务

来源:互联网 发布:淘宝没有生产许可证 编辑:程序博客网 时间:2024/04/30 11:59

OpenGIS 规范致力于为地理信息系统间的数据和服务互操作提供统一,提供了很多在线的 GIS 数据,包括Web Map Service (WMS),Web Feature Service(WFS),Web Coverage Service(WCS)等在线地图服务。为了能够方便使用这样的在线地图数据,QGis专门做了支持在线地图数据的功能,只要电脑联网,就可以轻松访问 OGC 的各种地图服务器,并轻易的将所需要的地图数据下载下来。本文就来与大家探讨一下如何在QGis二次开发时添加这些在线地图图层。

这里写图片描述

以添加 WMS 图层为例

WMS 图层属于图像图层,也就是栅格图层。既然是栅格,先来看一看 QgsRasterLayer 有没有相关的支持。在 API 文档中,QgsRasterLayer 的构造函数有三个,如下图

这里写图片描述

其中,第三个构造函数,正是我们需要的根据地图文件位置、地图图层名称以及地图数据提供者来构造栅格图像的方式。

看到这里似乎就很简单了,要添加 WMS 图层,与之前添加本地栅格图层一个道理,只不过将原来的本地路径改为地图服务所在的网址就行了。于是,有了下面的代码。

// **这个函数的定义见下文**addOpenSourceRasterLayer("contextualWMSLegend=0&crs=EPSG:4326&dpiMode=all&featureCount=10&format=image/gif&layers=DC&styles=&url=http://wms.lizardtech.com/lizardtech/iserv/ows",                 "DC",                 "wms" );

只要触发上面这行代码,就能够添加一个基本的 WMS 图层,效果如下。
这里写图片描述
如果你的需求仅仅是为了打开某个特定的在线地图数据,到这里应该就能实现了。

然而,有没有办法如同 QGis 那样可以查询可用的地图服务,实时下载显示呢?

源码分析

为了解决上面的问题,实际上,我们需要模仿QGis的做法,于是还是从源码上面找答案。打开源码,找到添加 WMS 图层的相应代码段,如下

void QgisApp::addWmsLayer(){    // Fudge for now    QgsDebugMsg( "about to addRasterLayer" );    // TODO: QDialog for now, switch to QWidget in future    QDialog *wmss = dynamic_cast<QDialog*>( QgsProviderRegistry::instance()->selectWidget( QString( "wms" ), this ) );    if ( !wmss )    {        QMessageBox::warning( this, tr( "WMS" ), tr( "Cannot get WMS select dialog from provider." ) );        return;    }    connect( wmss, SIGNAL( addRasterLayer( QString const &, QString const &, QString const & ) ),             this, SLOT( addRasterLayer( QString const &, QString const &, QString const & ) ) );    wmss->exec();    delete wmss;}

其中,重点关注这一句

QDialog *wmss = dynamic_cast<QDialog*>( QgsProviderRegistry::instance()->selectWidget( QString( "wms" ), this ) );

这个返回了一个 Dialog类,这个Dialog就是长下图这个造型的
这里写图片描述

如果有这个东西,岂不是就能直接查找可用的在线地图服务了?是的,实现起来就这么简单,只需要用 QgsProviderRegistry 这个类的实例,通过传入类似 “wms”这种服务源的字符串,就能构造出相应的在线地图查询窗口。这也是为什么我用 WMS 图层作为例子的原因,因为 WCS 和 WFS 图层只是服务源不同而已,传入不同的字符串就行了。

瞬间觉得 so easy。

首先,在应用的菜单栏添加一个功能按钮,添加 WMS 图层,然后将这个按钮的事件方法定义如下:

void qgis_dev::addWMSLayers(){    QDialog *wms = dynamic_cast<QDialog*>( QgsProviderRegistry::instance()->selectWidget( QString( "wms" ), this ) );    if ( !wms )    {        statusBar()->showMessage( tr( "cannot add wms layer." ), 10 );    }    connect( wms, SIGNAL( addRasterLayer( QString const &, QString const &, QString const & ) ),             this, SLOT( addOpenSourceRasterLayer( QString const &, QString const &, QString const & ) ) );    wms->exec();    delete wms;}

唯一需要注意的就是 connect 这个对话框的方法到相应的添加图层方法上,这个添加图层的方法实现如下

void qgis_dev::addOpenSourceRasterLayer( const QString& url, const QString& basename, const QString& providerKey ){    QgsRasterLayer *rasterLayer = 0;    if ( providerKey.isEmpty() )    {        rasterLayer = new QgsRasterLayer( url, basename );    }    else    {        rasterLayer = new QgsRasterLayer( url, basename, providerKey );    }    if ( !rasterLayer->isValid() )    {        QMessageBox::critical( this, "error", "layer is invalid" );        return;    }    QgsMapLayerRegistry::instance()->addMapLayer( rasterLayer );    mapCanvasLayerSet.append( rasterLayer );    m_mapCanvas->setExtent( rasterLayer->extent() );    m_mapCanvas->setLayerSet( mapCanvasLayerSet );    m_mapCanvas->setVisible( true );    m_mapCanvas->freeze( false );    m_mapCanvas->refresh();}

其实跟添加栅格图层的方式没什么特别的不同。

有了以上这些操作,好像就OK了,下面运行测试。结果……
这里写图片描述

可用服务列表里面一个也没有,点击 add default servers 也依然没用。为何?

这个问题实际上刚开始也困扰了我很久,按照原理来说,这个接口已经非常简单了,不明白哪里没有做。

还是回到源代码,一路追踪到 add default servers 这个方法的代码段,如下

void QgsOWSSourceSelect::addDefaultServers(){  QMap<QString, QString> exampleServers;  exampleServers["DM Solutions GMap"] = "http://www2.dmsolutions.ca/cgi-bin/mswms_gmap";  exampleServers["Lizardtech server"] =  "http://wms.lizardtech.com/lizardtech/iserv/ows";  // Nice to have the qgis users map, but I'm not sure of the URL at the moment.  //  exampleServers["Qgis users map"] = "http://qgis.org/wms.cgi";  QSettings settings; // 这里其实才是问题的关键  settings.beginGroup( "/Qgis/connections-" + mService.toLower() );  QMap<QString, QString>::const_iterator i = exampleServers.constBegin();  for ( ; i != exampleServers.constEnd(); ++i )  {    // Only do a server if it's name doesn't already exist.    QStringList keys = settings.childGroups();    if ( !keys.contains( i.key() ) )    {      QString path = i.key();      settings.setValue( path + "/url", i.value() );    }  }  settings.endGroup();  populateConnectionList();  QMessageBox::information( this, tr( "WMS proxies" ), "<p>" + tr( "Several WMS servers have "                            "been added to the server list. Note that if "                            "you access the internet via a web proxy, you will "                            "need to set the proxy settings in the QGIS options dialog." ) + "</p>" );}

看到这个代码段里面对默认服务器的配置采用了 QSettings 这个类,这个类可能很多 Qt 的初学者不太熟悉,找源代码也不容易将问题定位到这个类这里。关于这个类的使用方法,我截取 Qt 的官方帮助的一段话
这里写图片描述

关于这个类的使用方法,我这里就不展开了,大家可以自己去查查官方的帮助。这里关注一个点,上面的文字描述中说的很清楚,如果想要全局使用这个类来做为配置选项的辅助,需要实现设置好全局应用的名称和单位的名称。而我们的代码中使用到了 QSettings 这个类,却只用到了默认的无参数的构造函数,程序无法定位到相应的配置上,所以导致了默认的服务器添加不到 comboBox 上去(这个需要一点理解,看似添加图层对话框的使用与 QSettings 没什么关系,但实际上是就是这个小细节在作怪)。

通过以上的描述,我们要做的就很明白了,在 main 函数里面按照 Qt 官方帮助的描述,对 QSettings 做相应的配置。我的配置如下

// 为了使用 QSettingsQCoreApplication::setOrganizationName( "Jacory" );QCoreApplication::setOrganizationDomain( "jacory.com" ); // 域名好像是可以不用加的QCoreApplication::setApplicationName( "QGis_Dev" );

这样配置好后,我们再运行整个项目,就OK了
这里写图片描述

点击 connect 按钮,就能连接到相应服务器了,选择好需要添加的图层数据,点击 Add 按钮,稍等片刻,图层就加载到地图窗口中了。

添加 WCS 图层

通过上面的描述,添加 WCS 图层就很明白了。只需要在构造服务器选择窗口的时候,将服务器源字符串替换成 “wcs”就好了。完整代码如下

void qgis_dev::addWCSLayers(){    QDialog *wcs = dynamic_cast<QDialog*>( QgsProviderRegistry::instance()->selectWidget( QString( "wcs" ), this ) );    if ( !wcs )    {        statusBar()->showMessage( tr( "cannot add wcs layer." ), 10 );    }    connect( wcs, SIGNAL( addRasterLayer( QString const &, QString const &, QString const & ) ),             this, SLOT( addOpenSourceRasterLayer( QString const &, QString const &, QString const & ) ) );    wcs->exec();    delete wcs;}

需要说明的是,这个是没有默认的地图服务器的,需要自己用 New 按钮来新建。

添加 WFS 图层

这个其实也和上面的两种图层添加方式差不多,需要注意的就是,WFS 图层是矢量图层,因此,添加的时候需要使用矢量的添加方式。

void qgis_dev::addWFSLayers(){    if ( !m_mapCanvas ) {return;}    QDialog *wfs = dynamic_cast<QDialog*>( QgsProviderRegistry::instance()->selectWidget( QString( "WFS" ), this ) );    if ( !wfs )    {        QMessageBox::warning( this, tr( "WFS" ), tr( "Cannot get WFS select dialog from provider." ) );        return;    }    connect( wfs, SIGNAL( addWfsLayer( QString, QString ) ),             this, SLOT( addWfsLayer( const QString, const QString ) ) );    //re-enable wfs with extent setting: pass canvas info to source select    wfs->setProperty( "MapExtent", m_mapCanvas->extent().toString() );    if ( m_mapCanvas->mapSettings().hasCrsTransformEnabled() )    {        //if "on the fly" reprojection is active, pass canvas CRS        wfs->setProperty( "MapCRS", m_mapCanvas->mapSettings().destinationCrs().authid() );    }    bool bkRenderFlag = m_mapCanvas->renderFlag();    m_mapCanvas->setRenderFlag( false );    wfs->exec();    m_mapCanvas->setRenderFlag( bkRenderFlag );    delete wfs;}// 这个是添加矢量图层的方式void qgis_dev::addWFSLayer( const QString& url, const QString& typeName ){    QgsVectorLayer* vecLayer = new QgsVectorLayer( url, typeName, "WFS", false );    if ( !vecLayer->isValid() )    {        QMessageBox::critical( this, "error", "layer is invalid" );        return;    }    QgsMapLayerRegistry::instance()->addMapLayer( vecLayer );    mapCanvasLayerSet.append( vecLayer );    m_mapCanvas->setExtent( vecLayer->extent() );    m_mapCanvas->setLayerSet( mapCanvasLayerSet );    m_mapCanvas->setVisible( true );    m_mapCanvas->freeze( false );    m_mapCanvas->refresh();}

WFS 图层添加窗口也是没有默认服务器的,需要自己使用 New 按钮新建服务器。

最后

在 QGis 源码学习过程中,有很多小细节往往是功能的关键,但又不是那么容易发现(如本例中的 QSettings)。由于 QGis 工程的庞大,很多功能的实现隐藏的比较深,需要耐心的挖掘、思考。

希望本文能够帮助到正在困惑的朋友,我的 QGis 二次开发博客系列,也是希望可以帮到更多的朋友,提供一个基础知识的文档,让更多的开发者将 QGis 这个强大的工具用起来,并最终学习到 QGis 这样优秀的开源软件的开发思想,为我们的软件开发服务。

如本文有遗漏的地方,还请指出,有错误的地方,请不吝指正!谢谢!

2 0
原创粉丝点击