手动选择显示_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协议