Skip to content

Commit

Permalink
Fix incorrectly loaded sublayers if they had the same name (fixes #15168
Browse files Browse the repository at this point in the history
)

Use "layerid=N" instead of "layername=XYZ" for OGR sublayers
  • Loading branch information
wonder-sk committed Jun 30, 2016
1 parent 3cba1aa commit 5f66276
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 80 deletions.
35 changes: 30 additions & 5 deletions python/gui/qgssublayersdialog.sip
Expand Up @@ -12,14 +12,39 @@ class QgsSublayersDialog : QDialog
Vsifile
};

//! A structure that defines layers for the purpose of this dialog
//! @note added in 2.16
struct LayerDefinition
{
LayerDefinition();

int layerId; //!< identifier of the layer (one unique layer id may have multiple types though)
QString layerName; //!< name of the layer (not necessarily unique)
int count; //!< number of features (might be unused)
QString type; //!< extra type depending on the use (e.g. geometry type for vector sublayers)
};

//! List of layer definitions for the purpose of this dialog
//! @note added in 2.16
typedef QList<QgsSublayersDialog::LayerDefinition> LayerDefinitionList;

QgsSublayersDialog( ProviderType providerType, const QString& name, QWidget* parent /TransferThis/ = 0, const Qt::WindowFlags& fl = 0 );
~QgsSublayersDialog();

void populateLayerTable( const QStringList& theList, const QString& delim = ":" );
// Returns list of selected layers, if there are more layers with the same name,
// geometry type is appended separated by semicolon, example: <layer>:<geometryType>
QStringList selectionNames();
QList<int> selectionIndexes();
//! Populate the table with layers
//! @note added in 2.16
void populateLayerTable( const QgsSublayersDialog::LayerDefinitionList& list );

//! Returns list of selected layers
//! @note added in 2.16
QgsSublayersDialog::LayerDefinitionList selection();

//! @deprecated since 2.16 - use other populateLayerTable() variant
void populateLayerTable( const QStringList& theList, const QString& delim = ":" ) /Deprecated/;
//! @deprecated since 2.16 - use selection()
QStringList selectionNames() /Deprecated/;
//! @deprecated since 2.16 - use selection()
QList<int> selectionIndexes() /Deprecated/;

public slots:
void on_buttonBox_helpRequested();
Expand Down
144 changes: 75 additions & 69 deletions src/app/qgisapp.cpp
Expand Up @@ -3667,33 +3667,38 @@ bool QgisApp::askUserForZipItemLayers( QString path )
{
// We initialize a selection dialog and display it.
QgsSublayersDialog chooseSublayersDialog( QgsSublayersDialog::Vsifile, "vsi", this );
QgsSublayersDialog::LayerDefinitionList layers;

QStringList layers;
for ( int i = 0; i < zipItem->children().size(); i++ )
{
QgsDataItem *item = zipItem->children().at( i );
QgsLayerItem *layerItem = dynamic_cast<QgsLayerItem *>( item );
if ( layerItem )
{
QgsDebugMsgLevel( QString( "item path=%1 provider=%2" ).arg( item->path(), layerItem->providerKey() ), 2 );
}
if ( layerItem && layerItem->providerKey() == "gdal" )
if ( !layerItem )
continue;

QgsDebugMsgLevel( QString( "item path=%1 provider=%2" ).arg( item->path(), layerItem->providerKey() ), 2 );

QgsSublayersDialog::LayerDefinition def;
def.layerId = i;
def.layerName = item->name();
if ( layerItem->providerKey() == "gdal" )
{
layers << QString( "%1|%2|%3" ).arg( i ).arg( item->name(), "Raster" );
def.type = tr( "Raster" );
}
else if ( layerItem && layerItem->providerKey() == "ogr" )
else if ( layerItem->providerKey() == "ogr" )
{
layers << QString( "%1|%2|%3" ).arg( i ).arg( item->name(), tr( "Vector" ) );
def.type = tr( "Vector" );
}
layers << def;
}

chooseSublayersDialog.populateLayerTable( layers, "|" );
chooseSublayersDialog.populateLayerTable( layers );

if ( chooseSublayersDialog.exec() )
{
Q_FOREACH ( int i, chooseSublayersDialog.selectionIndexes() )
Q_FOREACH ( const QgsSublayersDialog::LayerDefinition& def, chooseSublayersDialog.selection() )
{
childItems << zipItem->children().at( i );
childItems << zipItem->children().at( def.layerId );
}
}
}
Expand Down Expand Up @@ -3751,7 +3756,7 @@ void QgisApp::askUserForGDALSublayers( QgsRasterLayer *layer )
// We initialize a selection dialog and display it.
QgsSublayersDialog chooseSublayersDialog( QgsSublayersDialog::Gdal, "gdal", this );

QStringList layers;
QgsSublayersDialog::LayerDefinitionList layers;
QStringList names;
for ( int i = 0; i < sublayers.size(); i++ )
{
Expand Down Expand Up @@ -3784,19 +3789,24 @@ void QgisApp::askUserForGDALSublayers( QgsRasterLayer *layer )
name.chop( 1 );

names << name;
layers << QString( "%1|%2" ).arg( i ).arg( name );

QgsSublayersDialog::LayerDefinition def;
def.layerId = i;
def.layerName = name;
layers << def;
}

chooseSublayersDialog.populateLayerTable( layers, "|" );
chooseSublayersDialog.populateLayerTable( layers );

if ( chooseSublayersDialog.exec() )
{
// create more informative layer names, containing filename as well as sublayer name
QRegExp rx( "\"(.*)\"" );
QString uri, name;

Q_FOREACH ( int i, chooseSublayersDialog.selectionIndexes() )
Q_FOREACH ( const QgsSublayersDialog::LayerDefinition& def, chooseSublayersDialog.selection() )
{
int i = def.layerId;
if ( rx.indexIn( sublayers[i] ) != -1 )
{
uri = rx.cap( 1 );
Expand Down Expand Up @@ -3874,79 +3884,75 @@ void QgisApp::askUserForOGRSublayers( QgsVectorLayer *layer )
QStringList sublayers = layer->dataProvider()->subLayers();
QString layertype = layer->dataProvider()->storageType();

QgsSublayersDialog::LayerDefinitionList list;
Q_FOREACH ( const QString& sublayer, sublayers )
{
// OGR provider returns items in this format:
// <layer_index>:<name>:<feature_count>:<geom_type>

QStringList elements = sublayer.split( ":" );
// merge back parts of the name that may have been split
while ( elements.size() > 4 )
{
elements[1] += ":" + elements[2];
elements.removeAt( 2 );
}

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];
list << def;
}
else
{
QgsDebugMsg( "Unexpected output from OGR provider's subLayers()! " + sublayer );
}
}


// We initialize a selection dialog and display it.
QgsSublayersDialog chooseSublayersDialog( QgsSublayersDialog::Ogr, "ogr", this );
chooseSublayersDialog.populateLayerTable( sublayers );
chooseSublayersDialog.populateLayerTable( list );

if ( chooseSublayersDialog.exec() )
if ( !chooseSublayersDialog.exec() )
return;

QString uri = layer->source();
//the separator char & was changed to | to be compatible
//with url for protocol drivers
if ( uri.contains( '|', Qt::CaseSensitive ) )
{
QString uri = layer->source();
//the separator char & was changed to | to be compatible
//with url for protocol drivers
if ( uri.contains( '|', Qt::CaseSensitive ) )
{
// If we get here, there are some options added to the filename.
// A valid uri is of the form: filename&option1=value1&option2=value2,...
// We want only the filename here, so we get the first part of the split.
QStringList theURIParts = uri.split( '|' );
uri = theURIParts.at( 0 );
}
QgsDebugMsg( "Layer type " + layertype );
// the user has done his choice
loadOGRSublayers( layertype, uri, chooseSublayersDialog.selectionNames() );
// If we get here, there are some options added to the filename.
// A valid uri is of the form: filename&option1=value1&option2=value2,...
// We want only the filename here, so we get the first part of the split.
QStringList theURIParts = uri.split( '|' );
uri = theURIParts.at( 0 );
}
}
QgsDebugMsg( "Layer type " + layertype );

// This method will load with OGR the layers in parameter.
// This method has been conceived to use the new URI
// format of the ogrprovider so as to give precisions about which
// sublayer to load into QGIS. It is normally triggered by the
// sublayer selection dialog.
void QgisApp::loadOGRSublayers( const QString& layertype, const QString& uri, const QStringList& list )
{
// The uri must contain the actual uri of the vectorLayer from which we are
// going to load the sublayers.
QString fileName = QFileInfo( uri ).baseName();
QList<QgsMapLayer *> myList;
for ( int i = 0; i < list.size(); i++ )
Q_FOREACH ( const QgsSublayersDialog::LayerDefinition& def, chooseSublayersDialog.selection() )
{
QString composedURI;
QStringList elements = list.at( i ).split( ':' );
while ( elements.size() > 2 )
{
elements[0] += ':' + elements[1];
elements.removeAt( 1 );
}

QString layerName = elements.value( 0 );
QString layerGeometryType = elements.value( 1 );
if ( layerGeometryType == "any" )
{
layerGeometryType = "";
elements.removeAt( 1 );
}

if ( layertype != "GRASS" )
{
composedURI = uri + "|layername=" + layerName;
}
else
{
composedURI = uri + "|layerindex=" + layerName;
}
QString layerGeometryType = def.type;
QString composedURI = uri + "|layerid=" + QString::number( def.layerId );

if ( !layerGeometryType.isEmpty() )
{
composedURI += "|geometrytype=" + layerGeometryType;
}

// addVectorLayer( composedURI, list.at( i ), "ogr" );

QgsDebugMsg( "Creating new vector layer using " + composedURI );
QString name = layerName;
QString name = fileName + " " + def.layerName;
if ( !layerGeometryType.isEmpty() )
name += " " + layerGeometryType;
QgsVectorLayer *layer = new QgsVectorLayer( composedURI, fileName + " " + name, "ogr", false );
QgsVectorLayer *layer = new QgsVectorLayer( composedURI, name, "ogr", false );
if ( layer && layer->isValid() )
{
myList << layer;
Expand Down
1 change: 0 additions & 1 deletion src/app/qgisapp.h
Expand Up @@ -633,7 +633,6 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow

//! copies features to internal clipboard
void copyFeatures( QgsFeatureStore & featureStore );
void loadOGRSublayers( const QString& layertype, const QString& uri, const QStringList& list );
void loadGDALSublayers( const QString& uri, const QStringList& list );

/** Deletes the selected attributes for the currently selected vector layer*/
Expand Down
71 changes: 71 additions & 0 deletions src/gui/qgssublayersdialog.cpp
Expand Up @@ -26,6 +26,8 @@ QgsSublayersDialog::QgsSublayersDialog( ProviderType providerType, const QString
QWidget* parent, const Qt::WindowFlags& fl )
: QDialog( parent, fl )
, mName( name )
, mShowCount( false )
, mShowType( false )
{
setupUi( this );

Expand All @@ -34,6 +36,8 @@ QgsSublayersDialog::QgsSublayersDialog( ProviderType providerType, const QString
setWindowTitle( tr( "Select vector layers to add..." ) );
layersTable->setHeaderLabels( QStringList() << tr( "Layer ID" ) << tr( "Layer name" )
<< tr( "Number of features" ) << tr( "Geometry type" ) );
mShowCount = true;
mShowType = true;
}
else if ( providerType == QgsSublayersDialog::Gdal )
{
Expand All @@ -45,6 +49,7 @@ QgsSublayersDialog::QgsSublayersDialog( ProviderType providerType, const QString
setWindowTitle( tr( "Select layers to add..." ) );
layersTable->setHeaderLabels( QStringList() << tr( "Layer ID" ) << tr( "Layer name" )
<< tr( "Type" ) );
mShowType = true;
}

// add a "Select All" button - would be nicer with an icon
Expand All @@ -65,6 +70,72 @@ QgsSublayersDialog::~QgsSublayersDialog()
layersTable->header()->saveState() );
}

static bool _isLayerIdUnique( int layerId, QTreeWidget* layersTable )
{
int count = 0;
for ( int j = 0; j < layersTable->topLevelItemCount(); j++ )
{
if ( layersTable->topLevelItem( j )->text( 0 ).toInt() == layerId )
{
count++;
}
}
return count == 1;
}

QgsSublayersDialog::LayerDefinitionList QgsSublayersDialog::selection()
{
LayerDefinitionList list;
for ( int i = 0; i < layersTable->selectedItems().size(); i++ )
{
QTreeWidgetItem* item = layersTable->selectedItems().at( i );

LayerDefinition def;
def.layerId = item->text( 0 ).toInt();
def.layerName = item->text( 1 );
if ( mShowType )
{
// If there are more sub layers of the same name (virtual for geometry types),
// add geometry type
if ( !_isLayerIdUnique( def.layerId, layersTable ) )
def.type = item->text( mShowCount ? 3 : 2 );
}

list << def;
}
return list;
}


void QgsSublayersDialog::populateLayerTable( const QgsSublayersDialog::LayerDefinitionList& list )
{
Q_FOREACH ( const LayerDefinition& item, list )
{
QStringList elements;
elements << QString::number( item.layerId ) << item.layerName;
if ( mShowCount )
elements << QString::number( item.count );
if ( mShowType )
elements << item.type;
layersTable->addTopLevelItem( new QTreeWidgetItem( elements ) );
}

// resize columns
QSettings settings;
QByteArray ba = settings.value( "/Windows/" + mName + "SubLayers/headerState" ).toByteArray();
if ( ! ba.isNull() )
{
layersTable->header()->restoreState( ba );
}
else
{
for ( int i = 0; i < layersTable->columnCount(); i++ )
layersTable->resizeColumnToContents( i );
layersTable->setColumnWidth( 1, layersTable->columnWidth( 1 ) + 10 );
}
}


QStringList QgsSublayersDialog::selectionNames()
{
QStringList list;
Expand Down

0 comments on commit 5f66276

Please sign in to comment.