手动选择显示_QGIS 二次开发笔记(2)——显示图层
基于 QGIS 二次開發,最首要的功能就是顯示圖層。這是個看似非常簡單的功能,但是在 QGIS 中寫了非常復雜的代碼,以支持各種數據源。
但是我們在二次開發中,一般不會支持那么多的數據源。這篇博客首先以 ESRI Shapefile 數據源為例,展示加載圖層的過程。
博客以創建好的工程開始,創建工程的過程網上資料很多,這里就不再贅述了。
添加地圖框
要想顯示圖層,首先要有一個顯示圖層的地方。在 QGIS SDK 中,使用類 QgsMapCanvas 顯示地圖。這個類需要 QT 中的 svg 組件,即
# QgsSdkApp.pro QT += core gui xml svg如果我們想在 QMainWindow 派生類(我的工程中創建的類名是 QgsSdkApp ,以后直接用這個名稱)添加一個 QgsMapCanvas 類型的組件,
有以下三種方法:
下面一一展示。
提升類型的方式
選擇一個組件,右鍵單擊之后,單擊“提升為”按鈕,可以彈出提升窗口部件的對話框。
打開“提升的窗口部件”對話框的方法提升的窗口部件對話框上面這個對話框,現在 1 所示的位置輸入要提升的類型的名稱,然后點擊 2 位置上的按鈕,在上面的列表上選中剛剛添加的提升類型,
點擊 3 位置上的按鈕,即可將該組件提升為指定的類型(即 QgsMapCanvas 類型)。
然后,在 cpp 文件中已經可以訪問到這個類型的組件了。
手動創建的方式
手動創建的方式就是在窗口類的構造函數中,構造一個 QgsMapCanvas 類型的對象,然后添加到窗口上。
這種情況下和其他在 QT 中手動創建對象沒有差別,和其他一樣處理即可。
添加圖層
添加了地圖框(在類內使用一個指針 mMapCanvas ,指向這個地圖框)之后,下面就可以來添加圖層了。
首先以 ESRI Shapefile 為例,介紹一下添加 QgsVectorLayer 的基本方法。
添加矢量圖層的方法
在 QgsSdkApp.ui 中可以添加一個 action ,并拖到工具欄上創建工具按鈕。點擊這個按鈕后開始添加圖層。這個過程網上有很多教程,這里就不再贅述了。
如果要添加 ESRI Shapefile 圖層,我們需要先選擇這個文件。我們可以直接彈出一個文件選擇對話框。
我們以選擇的文件路徑作為數據源的路徑,文件名(不含擴展名)為圖層名稱。
創建 addVectorLayer() 函數,用于添加圖層。我們可以以一種簡單的方式添加圖層,即直接創建 QgsVectorLayer 對象,并保存到一個列表(mMapLayerList)里。
然后讓地圖框加載這個列表,即可顯示地圖。
這個函數中除了添加了地圖,同時也限制了地圖框地顯示范圍,即設置為第一個圖層地顯示范圍。最后對地圖進行了刷新。但是這種方式會遇到很多問題:
在 QGIS 中,使用以下代碼以較為完善地設置添加地圖層,同時支持了很多其他功能。函數中創建的圖層直接添加到 QgsProject 中,以支持 Layout 等功能。
QgsVectorLayer *QgsSdkApp::addVectorLayer(const QString &vectorLayerPath, const QString &name, const QString &providerKey, bool guiWarning) {QString baseName = QgsMapLayer::formatLayerName( name );/* Eliminate the need to instantiate the layer based on provider type.The caller is responsible for cobbling together the needed information toopen the layer*/QgsDebugMsg( "Creating new vector layer using " + vectorLayerPath+ " with baseName of " + baseName+ " and providerKey of " + providerKey );// if the layer needs authentication, ensure the master password is setbool authok = true;QRegExp rx( "authcfg=([a-z]|[A-Z]|[0-9]){7}" );if ( rx.indexIn( vectorLayerPath ) != -1 ){authok = false;if ( !QgsAuthGuiUtils::isDisabled( messageBar(), messageTimeout() ) ){authok = QgsApplication::authManager()->setMasterPassword( true );}}// create the layerQgsVectorLayer::LayerOptions options { QgsProject::instance()->transformContext() };// Default style is loaded later in this methodoptions.loadDefaultStyle = false;QgsVectorLayer *layer = new QgsVectorLayer( vectorLayerPath, baseName, providerKey, options );if ( authok && layer && layer->isValid() ){QStringList sublayers = layer->dataProvider()->subLayers();QgsDebugMsg( QStringLiteral( "got valid layer with %1 sublayers" ).arg( sublayers.count() ) );// If the newly created layer has more than 1 layer of data available, we show the// sublayers selection dialog so the user can select the sublayers to actually load.if ( sublayers.count() > 1 &&! vectorLayerPath.contains( QStringLiteral( "layerid=" ) ) &&! vectorLayerPath.contains( QStringLiteral( "layername=" ) ) ){QList< QgsMapLayer * > addedLayers = askUserForOGRSublayers( layer );// The first layer loaded is not useful in that case. The user can select it in// the list if he wants to load it.delete layer;layer = addedLayers.isEmpty() ? nullptr : qobject_cast< QgsVectorLayer * >( addedLayers.at( 0 ) );for ( QgsMapLayer *l : addedLayers )askUserForDatumTransform( l->crs(), QgsProject::instance()->crs(), l );}else{// Register this layer with the layers registryQList<QgsMapLayer *> myList;//set friendly name for datasources with only one layerQStringList sublayers = layer->dataProvider()->subLayers();if ( !sublayers.isEmpty() ){setupVectorLayer( vectorLayerPath, sublayers, layer,providerKey, options );}myList << layer;QgsProject::instance()->addMapLayers( myList );askUserForDatumTransform( layer->crs(), QgsProject::instance()->crs(), layer );bool ok;layer->loadDefaultStyle( ok );layer->loadDefaultMetadata( ok );}}else{if ( guiWarning ){QString message = layer->dataProvider() ? layer->dataProvider()->error().message( QgsErrorMessage::Text ) : tr( "Invalid provider" );QString msg = tr( "The layer %1 is not a valid layer and can not be added to the map. Reason: %2" ).arg( vectorLayerPath, message );visibleMessageBar()->pushMessage( tr( "Layer is not valid" ), msg, Qgis::Critical, messageTimeout() );}delete layer;return nullptr;}// Let render() do its own cursor management// QApplication::restoreOverrideCursor();return layer; } 注意其中的 QgsProject::instance()->addMapLayers( myList ); 一行,其實在 QGIS 中所有圖層都是通過 QgsProject 進行管理的,一般來說用戶無需自己管理。QGIS SDK 也提供了 QgsLayerTreeView 等一套類,用于顯示圖層的層級結構,使用也比較方便。
但是如果有一些特別需要的功能,再進行自己管理。
這個函數中還需要其他幾個函數,分別定義如下:
QList<QgsMapLayer *> QgsSdkApp::askUserForOGRSublayers(QgsVectorLayer *layer) {QList<QgsMapLayer *> result;if ( !layer ){layer = qobject_cast<QgsVectorLayer *>( activeLayer() );if ( !layer || layer->providerType() != QLatin1String( "ogr" ) )return result;}QStringList sublayers = layer->dataProvider()->subLayers();QgsSublayersDialog::LayerDefinitionList list;QMap< QString, int > mapLayerNameToCount;bool uniqueNames = true;int lastLayerId = -1;const auto constSublayers = sublayers;for ( const QString &sublayer : constSublayers ){// OGR provider returns items in this format:// <layer_index>:<name>:<feature_count>:<geom_type>QStringList elements = splitSubLayerDef( sublayer );if ( elements.count() >= 4 ){QgsSublayersDialog::LayerDefinition def;def.layerId = elements[0].toInt();def.layerName = elements[1];def.count = elements[2].toInt();def.type = elements[3];// ignore geometry column name at elements[4]if ( elements.count() >= 6 )def.description = elements[5];if ( lastLayerId != def.layerId ){int count = ++mapLayerNameToCount[def.layerName];if ( count > 1 || def.layerName.isEmpty() )uniqueNames = false;lastLayerId = def.layerId;}list << def;}else{QgsDebugMsg( "Unexpected output from OGR provider's subLayers()! " + sublayer );}}// Check if the current layer uri contains the// We initialize a selection dialog and display it.QgsSublayersDialog chooseSublayersDialog( QgsSublayersDialog::Ogr, QStringLiteral( "ogr" ), this );chooseSublayersDialog.setShowAddToGroupCheckbox( true );chooseSublayersDialog.populateLayerTable( list );if ( !chooseSublayersDialog.exec() )return result;QString name = layer->name();auto uriParts = QgsProviderRegistry::instance()->decodeUri(layer->providerType(), layer->dataProvider()->dataSourceUri() );QString uri( uriParts.value( QStringLiteral( "path" ) ).toString() );// The uri must contain the actual uri of the vectorLayer from which we are// going to load the sublayers.QString fileName = QFileInfo( uri ).baseName();const auto constSelection = chooseSublayersDialog.selection();for ( const QgsSublayersDialog::LayerDefinition &def : constSelection ){QString layerGeometryType = def.type;QString composedURI = uri;if ( uniqueNames ){composedURI += "|layername=" + def.layerName;}else{// Only use layerId if there are ambiguities with namescomposedURI += "|layerid=" + QString::number( def.layerId );}if ( !layerGeometryType.isEmpty() ){composedURI += "|geometrytype=" + layerGeometryType;}QgsDebugMsg( "Creating new vector layer using " + composedURI );QString name = fileName + " " + def.layerName;QgsVectorLayer::LayerOptions options { QgsProject::instance()->transformContext() };options.loadDefaultStyle = false;QgsVectorLayer *layer = new QgsVectorLayer( composedURI, name, QStringLiteral( "ogr" ), options );if ( layer && layer->isValid() ){result << layer;}else{QString msg = tr( "%1 is not a valid or recognized data source" ).arg( composedURI );visibleMessageBar()->pushMessage( tr( "Invalid Data Source" ), msg, Qgis::Critical, messageTimeout() );delete layer;}}if ( !result.isEmpty() ){QgsSettings settings;bool addToGroup = settings.value( QStringLiteral( "/qgis/openSublayersInGroup" ), true ).toBool();bool newLayersVisible = settings.value( QStringLiteral( "/qgis/new_layers_visible" ), true ).toBool();QgsLayerTreeGroup *group = nullptr;if ( addToGroup )group = QgsProject::instance()->layerTreeRoot()->insertGroup( 0, name );QgsProject::instance()->addMapLayers( result, ! addToGroup );for ( QgsMapLayer *l : qgis::as_const( result ) ){bool ok;l->loadDefaultStyle( ok );l->loadDefaultMetadata( ok );if ( addToGroup )group->addLayer( l );}// Respect if user don't want the new group of layers visible.if ( addToGroup && ! newLayersVisible )group->setItemVisibilityCheckedRecursive( newLayersVisible );}return result; }bool QgsSdkApp::askUserForDatumTransform(const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs, const QgsMapLayer *layer) {Q_ASSERT( qApp->thread() == QThread::currentThread() );QString title;if ( layer ){// try to make a user-friendly (short!) identifier for the layerQString layerIdentifier;if ( !layer->name().isEmpty() ){layerIdentifier = layer->name();}else{const QVariantMap parts = QgsProviderRegistry::instance()->decodeUri( layer->providerType(), layer->source() );if ( parts.contains( QStringLiteral( "path" ) ) ){const QFileInfo fi( parts.value( QStringLiteral( "path" ) ).toString() );layerIdentifier = fi.fileName();}else if ( layer->dataProvider() ){const QgsDataSourceUri uri( layer->source() );layerIdentifier = uri.table();}}if ( !layerIdentifier.isEmpty() )title = tr( "Select Transformation for %1" ).arg( layerIdentifier );}return QgsDatumTransformDialog::run( sourceCrs, destinationCrs, this, mMapCanvas, title ); }static QStringList splitSubLayerDef( const QString &subLayerDef ) {return subLayerDef.split( QgsDataProvider::sublayerSeparator() ); }static void setupVectorLayer( const QString &vectorLayerPath,const QStringList &sublayers,QgsVectorLayer *&layer,const QString &providerKey,QgsVectorLayer::LayerOptions options ) {//set friendly name for datasources with only one layerQgsSettings settings;QStringList elements = splitSubLayerDef( sublayers.at( 0 ) );QString rawLayerName = elements.size() >= 2 ? elements.at( 1 ) : QString();QString subLayerNameFormatted = rawLayerName;if ( settings.value( QStringLiteral( "qgis/formatLayerName" ), false ).toBool() ){subLayerNameFormatted = QgsMapLayer::formatLayerName( subLayerNameFormatted );}if ( elements.size() >= 4 && layer->name().compare( rawLayerName, Qt::CaseInsensitive ) != 0&& layer->name().compare( subLayerNameFormatted, Qt::CaseInsensitive ) != 0 ){layer->setName( QStringLiteral( "%1 %2" ).arg( layer->name(), rawLayerName ) );}// Systematically add a layername= option to OGR datasets in case// the current single layer dataset becomes layer a multi-layer one.// Except for a few select extensions, known to be always single layer dataset.QFileInfo fi( vectorLayerPath );QString ext = fi.suffix().toLower();if ( providerKey == QLatin1String( "ogr" ) &&ext != QLatin1String( "shp" ) &&ext != QLatin1String( "mif" ) &&ext != QLatin1String( "tab" ) &&ext != QLatin1String( "csv" ) &&ext != QLatin1String( "geojson" ) &&! vectorLayerPath.contains( QStringLiteral( "layerid=" ) ) &&! vectorLayerPath.contains( QStringLiteral( "layername=" ) ) ){auto uriParts = QgsProviderRegistry::instance()->decodeUri(layer->providerType(), layer->dataProvider()->dataSourceUri() );QString composedURI( uriParts.value( QStringLiteral( "path" ) ).toString() );composedURI += "|layername=" + rawLayerName;auto newLayer = qgis::make_unique<QgsVectorLayer>( composedURI, layer->name(), QStringLiteral( "ogr" ), options );if ( newLayer && newLayer->isValid() ){delete layer;layer = newLayer.release();}} }這個函數中還需要以下幾個 Getter 函數,我們在窗口中提供對應的組件即可:
但是現在,還不能在地圖上顯示,需要將 QgsMapCanvas 和 QgsProject 建立關聯,才能將 QgsProject 中的圖層同步到 QgsMapCanvas 中。
使用的方法是在構造函數中添加如下代碼:
這樣添加了圖層之后,就可以在地圖上顯示了。
顯示矢量圖層添加其他圖層
其他圖層的添加方法,都可以從 QGIS 的代碼中進行參考。在 qgisapp.cpp 文件中,有這個函數
void QgisApp::dataSourceManager( const QString &pageName ) {if ( ! mDataSourceManagerDialog ){mDataSourceManagerDialog = new QgsDataSourceManagerDialog( mBrowserModel, this, mapCanvas() );connect( this, &QgisApp::connectionsChanged, mDataSourceManagerDialog, &QgsDataSourceManagerDialog::refresh );connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::connectionsChanged, this, &QgisApp::connectionsChanged );connect( mDataSourceManagerDialog, SIGNAL( addRasterLayer( QString const &, QString const &, QString const & ) ),this, SLOT( addRasterLayer( QString const &, QString const &, QString const & ) ) );connect( mDataSourceManagerDialog, SIGNAL( addVectorLayer( QString const &, QString const &, QString const & ) ),this, SLOT( addVectorLayer( QString const &, QString const &, QString const & ) ) );connect( mDataSourceManagerDialog, SIGNAL( addVectorLayers( QStringList const &, QString const &, QString const & ) ),this, SLOT( addVectorLayers( QStringList const &, QString const &, QString const & ) ) );connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::addMeshLayer, this, &QgisApp::addMeshLayer );connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::showStatusMessage, this, &QgisApp::showStatusMessage );connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::addDatabaseLayers, this, &QgisApp::addDatabaseLayers );connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::replaceSelectedVectorLayer, this, &QgisApp::replaceSelectedVectorLayer );connect( mDataSourceManagerDialog, static_cast<void ( QgsDataSourceManagerDialog::* )()>( &QgsDataSourceManagerDialog::addRasterLayer ), this, static_cast<void ( QgisApp::* )()>( &QgisApp::addRasterLayer ) );connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::handleDropUriList, this, &QgisApp::handleDropUriList );connect( this, &QgisApp::newProject, mDataSourceManagerDialog, &QgsDataSourceManagerDialog::updateProjectHome );connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::openFile, this, &QgisApp::openFile );}else{mDataSourceManagerDialog->reset();}// Try to open the dialog on a particular pageif ( ! pageName.isEmpty() ){mDataSourceManagerDialog->openPage( pageName );}if ( QgsSettings().value( QStringLiteral( "/qgis/dataSourceManagerNonModal" ), true ).toBool() ){mDataSourceManagerDialog->show();}else{mDataSourceManagerDialog->exec();} }這個函數中有 addRasterLayer addVectorLayer addMeshLayer 等函數,是添加不同類型的圖層的方法。可以直接查看這些方法,學習其中的方法,放到工程中來。
在下篇博客中,我計劃介紹添加 CSV 類型數據的方法。顯示圖層樹
一般情況下我們都需要使用圖層樹來對圖層進行管理。下面我們就在界面上添加 QgsLayerTreeView 對象。
我們首先在界面上創建一個 QWidget 組件,提升為 QgsLayerTreeView 類型。然后再構造函數中給其設置 Model 等。
QgsSdkApp::QgsSdkApp(QWidget *parent) : QMainWindow(parent), ui(new Ui::QgsSdkApp) {ui->setupUi(this);mMapCanvas = ui->centralwidget;mMapCanvas->setObjectName(QStringLiteral("theMapCanvas"));mLayerTreeCanvasBridge = new QgsLayerTreeMapCanvasBridge(QgsProject::instance()->layerTreeRoot(), mMapCanvas, this);/** [BEGIN] 設置 QgsLayerTreeView 的 Model */QgsLayerTreeModel* model = new QgsLayerTreeModel(QgsProject::instance()->layerTreeRoot(), this);ui->layerTreeView->setModel(model);ui->layerTreeView->setObjectName(QStringLiteral( "theLayerTreeView" ));/** [END] */mInfoBar = new QgsMessageBar(this);ui->statusbar->addWidget(mInfoBar);connect(ui->actionAdd_Shp_Layer, &QAction::triggered, this, &QgsSdkApp::on_actionShp_Layer_triggered); } QT 中采用的 MVC 模型。 QT 中提供了 QTreeView 、 QListView 、 QTableView 等視圖(View),也提供了 QAbstractItemModel 、 QAbstractListModel 、 QAbstractTableModel 三種模型(Model),
也會提供了一些 Delegate 委托(相當于控制器 Controller)。顯示圖層樹
但是我們這個圖層樹僅僅有一個最基本的功能,而在 QGIS 中排序、右鍵菜單豐富的功能。
對于右鍵菜單,QGIS 中使用了 Provider 的方式提供右鍵菜單的菜單項,我們需要將這些 Provider 的代碼復制過來,添加到工程中。
對于排序等其他功能,我們可以按需添加。
總結
以上是生活随笔為你收集整理的手动选择显示_QGIS 二次开发笔记(2)——显示图层的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java 双声道音频_java实现切割w
- 下一篇: YModem协议