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,從而