1. 程式人生 > >QGis二次開發基礎 -- 新增線上地圖服務

QGis二次開發基礎 -- 新增線上地圖服務

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 做相應的配置。我的配置如下

// 為了使用 QSettings
QCoreApplication::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 這樣優秀的開源軟體的開發思想,為我們的軟體開發服務。

如本文有遺漏的地方,還請指出,有錯誤的地方,請不吝指正!謝謝!

相關推薦

QGis開發基礎 -- 新增線上地圖服務

OpenGIS 規範致力於為地理資訊系統間的資料和服務互操作提供統一,提供了很多線上的 GIS 資料,包括Web Map Service (WMS),Web Feature Service(WFS),Web Coverage Service(WCS)等線上地圖服

QGis開發基礎 -- 銷燬當前地圖工具

本文註定很短。 由某位朋友需求,在設定了地圖控制元件之後,怎麼才能將當前的地圖工具釋放掉,返回原來的空地圖狀態。比如剛剛開啟程式的時候,滑鼠指標是一個箭頭,表示沒有地圖工具,也不能在地圖上直接操作。而點選某個地圖工具後(例如漫遊工具,滑鼠指標會變成

QGis開發基礎 -- 屬性識別工具的實現

屬性識別工具,也就是常用的 identify 工具,它常常與諸如放大、縮小等地圖工具放在一起,提供瀏覽地圖要素的一項基本功能。為什麼要單獨討論一下這個工具,是因為它與普通的地圖瀏覽工具的實現有一些微小的差異。下面通過原始碼的學習,來了解這個工具的實現方法以及掌握

QGis開發基礎 -- 向量圖層的顯示樣式

帶座標的向量圖層作為GIS的核心資料,具有非常豐富的用途。人們往往喜歡在地圖上做各種標記,不僅美觀,而且使地圖清晰,一目瞭然。於是應運而生了使用各種各樣的圖示作為地圖示記的功能需求,在很多GIS軟體上,這早已不是什麼新鮮事了。然而在QGis二次開發的時候,同學們

QGis開發基礎 -- 構建圖層管理器

為了迴應有些同學對上一篇博文的建議,這篇文章主要關注於QGis二次開發中的“圖層管理器”的實現。 使用QGis構建獨立應用系統,我相信大部分同學應該還是關注於GIS基本功能框架構建上,也就是一些基本的GIS功能,例如: 資料的顯示、漫遊瀏覽等 讀入資料的管

QGis開發基礎 -- 根據屬性查詢要素

屬性查詢是GIS應用不可缺少的重要功能,尤其是在各種業務系統中,根據使用者輸入相應的查詢條件,從屬性要素中快速定位到使用者感興趣的要素,為業務應用提供了便利。本文就來聊一聊QGis二次開發中如何實現屬性查詢功能。 其實這個功能我在寫屬性表格功能的實現時就

fiddler開發基礎知識

一、官方開發文件 連線在這:http://www.telerik.com/fiddler 開發文件在這:http://docs.telerik.com/fiddler/Extend-Fiddler/ExtendWithDotNet 二、Fiddler介面 Fiddler4 公開介面有以下

009-Ambari開發新增自定義元件Redis()

上一篇我們主要介紹了Ambari新增元件的答題流程並以REDIS為例說明了流程,本篇在上一篇的基礎上,進一步完善說明流程並介紹如何給元件新增metric 掃描二維碼,關注BearData,獲取最新文章 上篇中,我們已經制作出了redis的rpm包,並重新編譯了我們修改後的Ambar

008-Ambari開發新增自定義元件Redis(一)

Ambari目前支援的元件有HDFS、YARN、HBase、Hive、Pig、ZooKeeper、Sqoop、Storm、Flume、Tez、Oozie、Falcon、Storm、Altas、Knox、Spark、Ranger、Mahout、Kerberos等,已經涵蓋了從大資料應用的

Revit開發基礎知識

獲取應用、文件及當前檢視資訊 public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements) {

C# Revit開發基礎/核心程式設計---元素過濾器

//============程式碼片段3-37:元素過濾器============ /// <summary> /// 使用ElementCategoryFilter過濾元素 /// </summary> void TestElementCate

UEditor開發新增自定義按鈕

需求 現狀描述:目前預覽正文內容只是文字框放大的樣式 優化方向:建議點選預覽可直接預覽門戶介面的樣式 也就是現狀我們呼叫的是Ueditor自帶的預覽功能,而需求中的預覽功能則相當於要我們重寫預覽按鈕 在之前老版本的Ueditor中新增按鈕很麻煩,具體可以參考

QGIS開發入坑指南

日前由於專案需求,學習了QGIS,並在QGIS上進行二次開發。在這裡記錄一下到目前為止的——入坑過程。 下載QGIS 下載QGIS可以從官網上直接下載。首先,我也是想著編譯一下原始碼,從原始碼學習。當我開始下載原始碼之後,並按照網上的一系列教程開始編譯時

QGIS(PYTHON3.5、QT5.7.1、QGIS3)系列開發環境

qgis c++ python 二次開發 QGIS(PYTHON3.5、QT5.7.1、QGIS3系列二次開發環境,所有環境以虛擬機方式提供,以便研究或直接使用。 具體包括:獨立應用二次開發環境(WINDOWS,LINUX);android二次開發;獨立應用PYTHON二次開發環境(WI

百度地圖開發Demo

sso http fill log 百度地圖 popu art .com b2c 單點標註:電子顯示對應位置的圖片,信息框 多點標註(批量點標註): 多點連線(基於多個點形成路徑): 若須要Demo源

QGIS C++開發環境

QGIS網上看了一下QGIS C++的二次開發環境大部分都還停留在QGIS2.9之前的版本和QT4的基礎上,應該是三年前的版本吧,而且多數使用的是RelWithDebInfo。模式,個人感覺那種模式是QGIS開發社區為學習QGIS代碼的人提供的一種折中模式,並不是合真正的應用開發,而且,當用這種版本調試時很多

QGIS for android 開發環境

qgis出售QGIS for android 二次開發環境,有示例程序!聯系:315022850QGIS for android 二次開發環境

Revit開發新增按鈕

整體結構佈局  命令類  namespace HW { [Transaction(TransactionMode.Manual)] class Cmd : IExternalCommand { public Result Ex

Revit開發--為管道新增標註

程式碼如下: public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements) { UIDocument uiDoc = comman

QGIS編譯--QGIS3.03+VS2017(64)+QT5.92原始碼編譯過程及開發準備經驗

    已經3年沒有接觸編碼了,因專案需要,開始接觸QGIS。經過大半個十一長假,終於在各種文章及QGIS提供的幫助文件的支援下,實現了兩個目標:(1)將QGIS在QT+VS的開發環境下,呼叫執行起來,初步具備二次開發能力;(2)編譯QGIS原始碼,生成qgis.exe,從而