Merge pull request #8359 from elpaso/handle-bad-layers2

[needs-docs][feature] Deferred handling of bad layers
This commit is contained in:
Alessandro Pasotti 2018-11-06 08:43:18 +01:00 committed by GitHub
commit c8b26771cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 2741 additions and 256 deletions

View File

@ -56,7 +56,14 @@ Returns true if any of the layers is modified
static void removeInvalidLayers( QgsLayerTreeGroup *group );
%Docstring
Remove layer nodes that refer to invalid layers
Removes layer nodes that refer to invalid layers
%End
static void storeOriginalLayersProperties( QgsLayerTreeGroup *group, const QDomDocument *doc );
%Docstring
Stores in a layer's originalXmlProperties the layer properties information
.. versionadded:: 3.6
%End
static void replaceChildrenOfEmbeddedGroups( QgsLayerTreeGroup *group );

View File

@ -180,7 +180,7 @@ Returns the display name of the layer.
virtual QgsDataProvider *dataProvider();
%Docstring
Returns the layer's data provider.
Returns the layer's data provider, it may be null.
%End
@ -966,6 +966,31 @@ Write just the symbology information for the layer into the document
.. versionadded:: 2.16
%End
virtual void setDataSource( const QString &dataSource, const QString &baseName, const QString &provider, const QgsDataProvider::ProviderOptions &options, bool loadDefaultStyleFlag = false );
%Docstring
Updates the data source of the layer. The layer's renderer and legend will be preserved only
if the geometry type of the new data source matches the current geometry type of the layer.
Subclasses should override this method: default implementation does nothing.
:param dataSource: new layer data source
:param baseName: base name of the layer
:param provider: provider string
:param options: provider options
:param loadDefaultStyleFlag: set to true to reset the layer's style to the default for the
data source
.. seealso:: :py:func:`dataSourceChanged`
.. versionadded:: 3.6
%End
QString providerType() const;
%Docstring
Returns the provider type (provider key) for this layer
%End
QUndoStack *undoStack();
%Docstring
Returns pointer to layer's undo stack
@ -1210,6 +1235,25 @@ Returns the message that should be notified by the provider to triggerRepaint
Returns true if the refresh on provider nofification is enabled
.. versionadded:: 3.0
%End
QString originalXmlProperties() const;
%Docstring
Returns the XML properties of the original layer as they were when the layer
was first read from the project file. In case of new layers this is normally empty.
The storage format for the XML is qlr
.. versionadded:: 3.6
%End
void setOriginalXmlProperties( const QString &originalXmlProperties );
%Docstring
Sets the original XML properties for the layer to ``originalXmlProperties``
The storage format for the XML is qlr
.. versionadded:: 3.6
%End
public slots:
@ -1432,6 +1476,15 @@ Emitted when layer's flags have been modified.
.. seealso:: :py:func:`flags`
.. versionadded:: 3.4
%End
void dataSourceChanged();
%Docstring
Emitted whenever the layer's data source has been changed.
.. seealso:: :py:func:`setDataSource`
.. versionadded:: 3.5
%End
protected:
@ -1535,6 +1588,11 @@ Read style data common to all layer types
.. versionadded:: 3.0
%End
void setProviderType( const QString &providerType );
%Docstring
Sets the ``providerType`` (provider key)
%End
void appendError( const QgsErrorMessage &error );
%Docstring
@ -1563,6 +1621,8 @@ Checks whether a new set of dependencies will introduce a cycle
%End
};
QFlags<QgsMapLayer::LayerFlag> operator|(QgsMapLayer::LayerFlag f1, QFlags<QgsMapLayer::LayerFlag> f2);

View File

@ -37,6 +37,13 @@ Constructor for QgsMapLayerStore.
Returns the number of layers contained in the store.
%End
int validCount() const;
%Docstring
Returns the number of valid layers contained in the store.
.. versionadded:: 3.6
%End
int __len__() const;
%Docstring
@ -89,6 +96,19 @@ Returns a map of all layers by layer ID.
.. seealso:: :py:func:`layers`
%End
QMap<QString, QgsMapLayer *> validMapLayers() const;
%Docstring
Returns a map of all valid layers by layer ID.
.. seealso:: :py:func:`mapLayer`
.. seealso:: :py:func:`mapLayersByName`
.. seealso:: :py:func:`layers`
.. versionadded:: 3.6
%End
QList<QgsMapLayer *> addMapLayers( const QList<QgsMapLayer *> &layers /Transfer/);
@ -104,7 +124,7 @@ The layersAdded() and layerWasAdded() signals will always be emitted.
the layers yourself. Not available in Python.
:return: a list of the map layers that were added
successfully. If a layer is invalid, or already exists in the store,
successfully. If a layer already exists in the store,
it will not be part of the returned list.

View File

@ -700,6 +700,11 @@ Returns a pointer to the project's internal layer store.
int count() const;
%Docstring
Returns the number of registered layers.
%End
int validCount() const;
%Docstring
Returns the number of registered valid layers.
%End
QgsMapLayer *mapLayer( const QString &layerId ) const;
@ -728,10 +733,12 @@ Retrieve a list of matching registered layers by layer name.
.. seealso:: :py:func:`mapLayers`
%End
QMap<QString, QgsMapLayer *> mapLayers() const;
QMap<QString, QgsMapLayer *> mapLayers( const bool validOnly = false ) const;
%Docstring
Returns a map of all registered layers by layer ID.
:param validOnly: if set only valid layers will be returned
.. seealso:: :py:func:`mapLayer`
.. seealso:: :py:func:`mapLayersByName`
@ -763,7 +770,7 @@ The legendLayersAdded() signal is emitted only if addToLegend is true.
the layers yourself. Not available in Python.
:return: a list of the map layers that were added
successfully. If a layer is invalid, or already exists in the registry,
successfully. If a layer or already exists in the registry,
it will not be part of the returned QList.

View File

@ -273,6 +273,7 @@ Returns a list of attributes used to form the referencing fields
bool isValid() const;
%Docstring
Returns the validity of this relation. Don't use the information if it's not valid.
A relation is considered valid if both referenced and referencig layers are valid.
:return: true if the relation is valid
%End
@ -300,6 +301,14 @@ Gets the referenced field counterpart given a referencing field.
Gets the referencing field counterpart given a referenced field.
.. versionadded:: 3.0
%End
void updateRelationStatus();
%Docstring
Updates the validity status of this relation.
Will be called internally whenever a member is changed.
.. versionadded:: 3.6
%End
};

View File

@ -45,6 +45,8 @@ Gets access to the relations managed by this class.
void addRelation( const QgsRelation &relation );
%Docstring
Add a relation.
Invalid relations are added only if both referencing layer and referenced
layer exist.
:param relation: The relation to add.
%End
@ -134,6 +136,13 @@ This signal is emitted when the relations were loaded after reading a project
Emitted when relations are added or removed to the manager.
.. versionadded:: 2.5
%End
public slots:
void updateRelationsStatus();
%Docstring
Updates relations status
%End
};

View File

@ -736,11 +736,6 @@ Returns point, line or polygon
%Docstring
Returns the WKBType or WKBUnknown in case of error
%End
QString providerType() const;
%Docstring
Returns the provider type for this layer
%End
virtual QgsCoordinateReferenceSystem sourceCrs() const ${SIP_FINAL};
@ -988,7 +983,8 @@ if the geometry type of the new data source matches the current geometry type of
.. deprecated:: Use version with ProviderOptions argument instead
%End
void setDataSource( const QString &dataSource, const QString &baseName, const QString &provider, const QgsDataProvider::ProviderOptions &options, bool loadDefaultStyleFlag = false );
virtual void setDataSource( const QString &dataSource, const QString &baseName, const QString &provider, const QgsDataProvider::ProviderOptions &options, bool loadDefaultStyleFlag = false );
%Docstring
Updates the data source of the layer. The layer's renderer and legend will be preserved only
if the geometry type of the new data source matches the current geometry type of the layer.
@ -2326,15 +2322,6 @@ by the backend data provider).
signals:
void dataSourceChanged();
%Docstring
Emitted whenever the layer's data source has been changed.
.. seealso:: :py:func:`setDataSource`
.. versionadded:: 3.4
%End
void selectionChanged( const QgsFeatureIds &selected, const QgsFeatureIds &deselected, bool clearAndSelect );
%Docstring
This signal is emitted when selection was changed

View File

@ -114,7 +114,7 @@ Constructor for LayerOptions.
explicit QgsRasterLayer( const QString &uri,
const QString &baseName = QString(),
const QString &providerKey = "gdal",
const QString &providerType = "gdal",
const QgsRasterLayer::LayerOptions &options = QgsRasterLayer::LayerOptions() );
%Docstring
This is the constructor for the RasterLayer class.
@ -192,6 +192,24 @@ Set the data provider.
:param options: provider options
.. versionadded:: 3.2
%End
virtual void setDataSource( const QString &dataSource, const QString &baseName, const QString &provider, const QgsDataProvider::ProviderOptions &options, bool loadDefaultStyleFlag = false );
%Docstring
Updates the data source of the layer. The layer's renderer and legend will be preserved only
if the geometry type of the new data source matches the current geometry type of the layer.
:param dataSource: new layer data source
:param baseName: base name of the layer
:param provider: provider string
:param options: provider options
:param loadDefaultStyleFlag: set to true to reset the layer's style to the default for the
data source
.. seealso:: :py:func:`dataSourceChanged`
.. versionadded:: 3.6
%End
LayerType rasterType();

View File

@ -31,17 +31,22 @@ will return a (possibly invalid) QgsMimeDataUtils.Uri.
%End
public:
QgsDataSourceSelectDialog( bool setFilterByLayerType = false,
QgsDataSourceSelectDialog( QgsBrowserModel *browserModel = 0,
bool setFilterByLayerType = false,
const QgsMapLayer::LayerType &layerType = QgsMapLayer::LayerType::VectorLayer,
QWidget *parent = 0 );
%Docstring
Constructs a QgsDataSourceSelectDialog, optionally filtering by layer type
:param browserModel: an existing browser model (typically from app), if null an instance will be created
:param setFilterByLayerType: activates filtering by layer type
:param layerType: sets the layer type filter, this is in effect only if filtering by layer type is also active
:param parent: the object
%End
~QgsDataSourceSelectDialog();
void setLayerTypeFilter( QgsMapLayer::LayerType layerType );
%Docstring
Sets layer type filter to ``layerType`` and activates the filtering

View File

@ -101,6 +101,7 @@
#include "qgsgui.h"
#include "qgsnative.h"
#include "qgsdatasourceselectdialog.h"
#ifdef HAVE_OPENCL
#include "qgsopenclutils.h"
@ -3941,7 +3942,8 @@ void QgisApp::initLayerTreeView()
new QgsLayerTreeViewFilterIndicatorProvider( mLayerTreeView ); // gets parented to the layer view
new QgsLayerTreeViewEmbeddedIndicatorProvider( mLayerTreeView ); // gets parented to the layer view
new QgsLayerTreeViewMemoryIndicatorProvider( mLayerTreeView ); // gets parented to the layer view
new QgsLayerTreeViewBadLayerIndicatorProvider( mLayerTreeView ); // gets parented to the layer view
QgsLayerTreeViewBadLayerIndicatorProvider *badLayerIndicatorProvider = new QgsLayerTreeViewBadLayerIndicatorProvider( mLayerTreeView ); // gets parented to the layer view
connect( badLayerIndicatorProvider, &QgsLayerTreeViewBadLayerIndicatorProvider::requestChangeDataSource, this, &QgisApp::changeDataSource );
new QgsLayerTreeViewNonRemovableIndicatorProvider( mLayerTreeView ); // gets parented to the layer view
setupLayerTreeViewFromSettings();
@ -6935,6 +6937,62 @@ void QgisApp::refreshFeatureActions()
updateDefaultFeatureAction( mFeatureActionMenu->activeAction() );
}
void QgisApp::changeDataSource( QgsMapLayer *layer )
{
// Get provider type
QString providerType( layer->providerType() );
QgsMapLayer::LayerType layerType( layer->type() );
QgsDataSourceSelectDialog dlg( mBrowserModel, true, layerType );
if ( dlg.exec() == QDialog::Accepted )
{
QgsMimeDataUtils::Uri uri( dlg.uri() );
if ( uri.isValid() )
{
bool layerIsValid( layer->isValid() );
layer->setDataSource( uri.uri, layer->name(), uri.providerKey, QgsDataProvider::ProviderOptions() );
// Re-apply style
if ( !( layerIsValid || layer->originalXmlProperties().isEmpty() ) )
{
QgsReadWriteContext context;
context.setPathResolver( QgsProject::instance()->pathResolver() );
context.setProjectTranslator( QgsProject::instance() );
QString errorMsg;
QDomDocument doc;
if ( doc.setContent( layer->originalXmlProperties() ) )
{
QDomNode layer_node( doc.firstChild( ) );
if ( ! layer->readSymbology( layer_node, errorMsg, context ) )
{
QgsDebugMsg( QStringLiteral( "Failed to restore original layer style from stored XML for layer %1: %2" )
.arg( layer->name( ) )
.arg( errorMsg ) );
}
}
else
{
QgsDebugMsg( QStringLiteral( "Failed to create XML QDomDocument for layer %1: %2" )
.arg( layer->name( ) )
.arg( errorMsg ) );
}
}
// All the following code is necessary to refresh the layer
QgsLayerTreeModel *model = qobject_cast<QgsLayerTreeModel *>( mLayerTreeView->model() );
if ( model )
{
QgsLayerTreeLayer *tl( model->rootGroup()->findLayer( layer->id() ) );
if ( tl && tl->itemVisibilityChecked() )
{
tl->setItemVisibilityChecked( false );
tl->setItemVisibilityChecked( true );
}
}
}
}
}
void QgisApp::measure()
{
mMapCanvas->setMapTool( mMapTools.mMeasureDist );

View File

@ -942,6 +942,16 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
*/
QgsBrowserModel *browserModel();
/*
* Change data source for \a layer, a data source selection dialog
* will be opened and if accepted the data selected source will be
* applied.
*
* In case the layer was originally invalid and it had the original
* XML layer properties, the properties will be applied.
*/
void changeDataSource( QgsMapLayer *layer );
/**
* Add a raster layer directly without prompting user for location
The caller must provide information compatible with the provider plugin
@ -971,9 +981,9 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
/**
* \brief overloaded version of the private addLayer method that takes a list of
* file names instead of prompting user with a dialog.
\param enc encoding type for the layer
\param dataSourceType type of ogr datasource
\returns true if successfully added layer
* \param enc encoding type for the layer
* \param dataSourceType type of ogr datasource
* \returns true if successfully added layer
*/
bool addVectorLayers( const QStringList &layerQStringList, const QString &enc, const QString &dataSourceType );

View File

@ -199,6 +199,18 @@ QMenu *QgsAppLayerTreeViewMenuProvider::createContextMenu()
menu->addSeparator();
// change data source is only supported for vectors and rasters
if ( vlayer || rlayer )
{
QAction *a = new QAction( tr( "Change data source…" ), menu );
connect( a, &QAction::triggered, [ = ]
{
QgisApp::instance()->changeDataSource( layer );
} );
menu->addAction( a );
}
if ( vlayer )
{
QAction *toggleEditingAction = QgisApp::instance()->actionToggleEditing();
@ -210,7 +222,7 @@ QMenu *QgsAppLayerTreeViewMenuProvider::createContextMenu()
QgisApp::instance(), [ = ] { QgisApp::instance()->attributeTable(); } );
// allow editing
int cap = vlayer->dataProvider()->capabilities();
unsigned int cap = vlayer->dataProvider()->capabilities();
if ( cap & QgsVectorDataProvider::EditingCapabilities )
{
if ( toggleEditingAction )

View File

@ -26,6 +26,7 @@
#include "qgsproviderregistry.h"
#include "qgsmessagebar.h"
#include "qgssettings.h"
#include "qgslayertreeregistrybridge.h"
#include <QDomDocument>
#include <QDomElement>
@ -33,6 +34,7 @@
#include <QPushButton>
#include <QToolButton>
#include <QMessageBox>
#include <QDialogButtonBox>
#include <QUrl>
void QgsHandleBadLayersHandler::handleBadLayers( const QList<QDomNode> &layers )
@ -40,6 +42,10 @@ void QgsHandleBadLayersHandler::handleBadLayers( const QList<QDomNode> &layers )
QApplication::setOverrideCursor( Qt::ArrowCursor );
QgsHandleBadLayers *dialog = new QgsHandleBadLayers( layers );
dialog->buttonBox->button( QDialogButtonBox::Ignore )->setToolTip( tr( "Import all bad layers unmodified (you can fix them later)." ) );
dialog->buttonBox->button( QDialogButtonBox::Apply )->setToolTip( tr( "Apply fixes to bad layers (remaining bad layers will be removed from the project)." ) );
dialog->buttonBox->button( QDialogButtonBox::Discard )->setToolTip( tr( "Remove all bad layers from the project" ) );
if ( dialog->layerCount() < layers.size() )
QgisApp::instance()->messageBar()->pushMessage(
tr( "Handle bad layers" ),
@ -72,6 +78,8 @@ QgsHandleBadLayers::QgsHandleBadLayers( const QList<QDomNode> &layers )
connect( mLayerList, &QTableWidget::itemSelectionChanged, this, &QgsHandleBadLayers::selectionChanged );
connect( mBrowseButton, &QAbstractButton::clicked, this, &QgsHandleBadLayers::browseClicked );
connect( buttonBox->button( QDialogButtonBox::Apply ), &QAbstractButton::clicked, this, &QgsHandleBadLayers::apply );
connect( buttonBox->button( QDialogButtonBox::Ignore ), &QPushButton::clicked, this, &QgsHandleBadLayers::reject );
connect( buttonBox->button( QDialogButtonBox::Discard ), &QPushButton::clicked, this, &QgsHandleBadLayers::accept );
mLayerList->clear();
mLayerList->setSortingEnabled( true );
@ -340,6 +348,17 @@ void QgsHandleBadLayers::editAuthCfg()
void QgsHandleBadLayers::apply()
{
QgsProject::instance()->layerTreeRegistryBridge()->setEnabled( true );
QList<QgsMapLayer *> toRemove;
for ( const auto &l : QgsProject::instance()->mapLayers( ) )
{
if ( ! l->isValid() )
toRemove << l;
}
QgsProject::instance()->removeMapLayers( toRemove );
for ( int i = 0; i < mLayerList->rowCount(); i++ )
{
int idx = mLayerList->item( i, 0 )->data( Qt::UserRole ).toInt();
@ -358,11 +377,15 @@ void QgsHandleBadLayers::apply()
item->setForeground( QBrush( Qt::red ) );
}
}
QgsProject::instance()->layerTreeRegistryBridge()->setEnabled( false );
if ( mLayerList->rowCount() == 0 )
accept();
}
void QgsHandleBadLayers::accept()
{
apply();
if ( mLayerList->rowCount() > 0 &&
QMessageBox::warning( this,
@ -375,28 +398,20 @@ void QgsHandleBadLayers::accept()
{
return;
}
QList<QgsMapLayer *> toRemove;
for ( const auto &l : QgsProject::instance()->mapLayers( ) )
{
if ( ! l->isValid() )
toRemove << l;
}
QgsProject::instance()->layerTreeRegistryBridge()->setEnabled( true );
QgsProject::instance()->removeMapLayers( toRemove );
QgsProject::instance()->layerTreeRegistryBridge()->setEnabled( false );
mLayerList->clear();
QDialog::accept();
}
void QgsHandleBadLayers::reject()
{
if ( mLayerList->rowCount() > 0 &&
QMessageBox::warning( this,
tr( "Unhandled layer will be lost." ),
tr( "There are still %n unhandled layer(s), that will be lost if you closed now.",
"unhandled layers",
mLayerList->rowCount() ),
QMessageBox::Ok | QMessageBox::Cancel,
QMessageBox::Cancel ) == QMessageBox::Cancel )
{
return;
}
QDialog::reject();
}
int QgsHandleBadLayers::layerCount()
{
return mLayerList->rowCount();

View File

@ -39,7 +39,7 @@ class QPushButton;
class APP_EXPORT QgsHandleBadLayers
: public QDialog
, private Ui::QgsHandleBadLayersBase
, public Ui::QgsHandleBadLayersBase
{
Q_OBJECT
@ -54,7 +54,6 @@ class APP_EXPORT QgsHandleBadLayers
void editAuthCfg();
void apply();
void accept() override;
void reject() override;
private:
QPushButton *mBrowseButton = nullptr;

View File

@ -20,7 +20,16 @@
#include "qgslayertreeutils.h"
#include "qgslayertreemodel.h"
#include "qgsvectorlayer.h"
#include "qgsrasterlayer.h"
#include "qgisapp.h"
#include "qgsbrowsermodel.h"
#include "qgsbrowsertreeview.h"
#include "qgsbrowserproxymodel.h"
#include <functional>
#include <QDialog>
#include <QVBoxLayout>
#include <QDialogButtonBox>
QgsLayerTreeViewBadLayerIndicatorProvider::QgsLayerTreeViewBadLayerIndicatorProvider( QgsLayerTreeView *view )
: QgsLayerTreeViewIndicatorProvider( view )
@ -33,11 +42,12 @@ void QgsLayerTreeViewBadLayerIndicatorProvider::onIndicatorClicked( const QModel
if ( !QgsLayerTree::isLayer( node ) )
return;
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( QgsLayerTree::toLayer( node )->layer() );
if ( !vlayer )
QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( QgsLayerTree::toLayer( node )->layer() );
if ( !layer )
return;
// TODO: open source select dialog
emit requestChangeDataSource( layer );
}
QString QgsLayerTreeViewBadLayerIndicatorProvider::iconName( QgsMapLayer *layer )
@ -49,8 +59,7 @@ QString QgsLayerTreeViewBadLayerIndicatorProvider::iconName( QgsMapLayer *layer
QString QgsLayerTreeViewBadLayerIndicatorProvider::tooltipText( QgsMapLayer *layer )
{
Q_UNUSED( layer );
// TODO, click here to set a new data source.
return tr( "<b>Bad layer!</b><br>Layer data source could not be found." );
return tr( "<b>Bad layer!</b><br>Layer data source could not be found. Click to set a new data source" );
}
bool QgsLayerTreeViewBadLayerIndicatorProvider::acceptLayer( QgsMapLayer *layer )

View File

@ -29,9 +29,18 @@ class QgsLayerTreeViewBadLayerIndicatorProvider : public QgsLayerTreeViewIndicat
public:
explicit QgsLayerTreeViewBadLayerIndicatorProvider( QgsLayerTreeView *view );
signals:
/**
* This signal is emitted when the user clicks on the bad layer indicator icon
* \param maplayer for change data source request
*/
void requestChangeDataSource( QgsMapLayer *maplayer );
protected slots:
void onIndicatorClicked( const QModelIndex &index ) override;
private:
QString iconName( QgsMapLayer *layer ) override;
QString tooltipText( QgsMapLayer *layer ) override;

View File

@ -20,6 +20,7 @@
#include "qgslayertreeutils.h"
#include "qgslayertreeview.h"
#include "qgsvectorlayer.h"
#include "qgsrasterlayer.h"
#include "qgisapp.h"
QgsLayerTreeViewIndicatorProvider::QgsLayerTreeViewIndicatorProvider( QgsLayerTreeView *view )
@ -89,16 +90,20 @@ void QgsLayerTreeViewIndicatorProvider::onWillRemoveChildren( QgsLayerTreeNode *
void QgsLayerTreeViewIndicatorProvider::onLayerLoaded()
{
QgsLayerTreeLayer *layerNode = qobject_cast<QgsLayerTreeLayer *>( sender() );
if ( !layerNode )
return;
if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layerNode->layer() ) )
if ( !( qobject_cast<QgsVectorLayer *>( layerNode->layer() ) || qobject_cast<QgsRasterLayer *>( layerNode->layer() ) ) )
return;
if ( QgsMapLayer *mapLayer = qobject_cast<QgsMapLayer *>( layerNode->layer() ) )
{
if ( vlayer )
if ( mapLayer )
{
connectSignals( vlayer );
addOrRemoveIndicator( layerNode, vlayer );
connectSignals( mapLayer );
addOrRemoveIndicator( layerNode, mapLayer );
}
}
}
@ -123,18 +128,18 @@ void QgsLayerTreeViewIndicatorProvider::onLayerChanged()
void QgsLayerTreeViewIndicatorProvider::connectSignals( QgsMapLayer *layer )
{
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
if ( !vlayer )
if ( !( qobject_cast<QgsVectorLayer *>( layer ) || qobject_cast<QgsRasterLayer *>( layer ) ) )
return;
connect( vlayer, &QgsVectorLayer::dataSourceChanged, this, &QgsLayerTreeViewIndicatorProvider::onLayerChanged );
QgsMapLayer *mapLayer = qobject_cast<QgsMapLayer *>( layer );
connect( mapLayer, &QgsMapLayer::dataSourceChanged, this, &QgsLayerTreeViewIndicatorProvider::onLayerChanged );
}
void QgsLayerTreeViewIndicatorProvider::disconnectSignals( QgsMapLayer *layer )
{
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
if ( !vlayer )
if ( !( qobject_cast<QgsVectorLayer *>( layer ) || qobject_cast<QgsRasterLayer *>( layer ) ) )
return;
disconnect( vlayer, &QgsVectorLayer::dataSourceChanged, this, &QgsLayerTreeViewIndicatorProvider::onLayerChanged );
QgsMapLayer *mapLayer = qobject_cast<QgsMapLayer *>( layer );
disconnect( mapLayer, &QgsMapLayer::dataSourceChanged, this, &QgsLayerTreeViewIndicatorProvider::onLayerChanged );
}
std::unique_ptr< QgsLayerTreeViewIndicator > QgsLayerTreeViewIndicatorProvider::newIndicator( QgsMapLayer *layer )

View File

@ -38,8 +38,8 @@ class QgsMapLayer;
*
* Subclasses may override:
* - onIndicatorClicked() default implementation does nothing
* - connectSignals() default implementation connects vector layers to dataSourceChanged()
* - disconnectSignals() default implementation disconnects vector layers from dataSourceChanged()
* - connectSignals() default implementation connects layers to dataSourceChanged()
* - disconnectSignals() default implementation disconnects layers from dataSourceChanged()
*/
class QgsLayerTreeViewIndicatorProvider : public QObject
{
@ -51,9 +51,9 @@ class QgsLayerTreeViewIndicatorProvider : public QObject
protected:
// Subclasses MAY override:
//! Connect signals, default implementation connects vector layers to dataSourceChanged()
//! Connect signals, default implementation connects layers to dataSourceChanged()
virtual void connectSignals( QgsMapLayer *layer );
//! Disconnect signals, default implementation disconnects vector layers from dataSourceChanged()
//! Disconnect signals, default implementation disconnects layers from dataSourceChanged()
virtual void disconnectSignals( QgsMapLayer *layer );
protected slots:

View File

@ -49,6 +49,9 @@ void QgsRelationManagerDialog::setLayers( const QList< QgsVectorLayer * > &layer
void QgsRelationManagerDialog::addRelation( const QgsRelation &rel )
{
if ( ! rel.isValid() )
return;
mRelationsTable->setSortingEnabled( false );
int row = mRelationsTable->rowCount();
mRelationsTable->insertRow( row );

View File

@ -636,10 +636,13 @@ QImage QgsWmsLegendNode::getLegendGraphic() const
QgsRasterLayer *layer = qobject_cast<QgsRasterLayer *>( mLayerNode->layer() );
const QgsLayerTreeModel *mod = model();
if ( ! mod ) return mImage;
if ( ! mod )
return mImage;
const QgsMapSettings *ms = mod->legendFilterMapSettings();
QgsRasterDataProvider *prov = layer->dataProvider();
if ( ! prov )
return mImage;
Q_ASSERT( ! mFetcher );
mFetcher.reset( prov->getLegendGraphicFetcher( ms ) );

View File

@ -306,6 +306,40 @@ void QgsLayerTreeUtils::removeInvalidLayers( QgsLayerTreeGroup *group )
group->removeChildNode( node );
}
void QgsLayerTreeUtils::storeOriginalLayersProperties( QgsLayerTreeGroup *group, const QDomDocument *doc )
{
const QDomNodeList mlNodeList( doc->documentElement()
.firstChildElement( QStringLiteral( "projectlayers" ) )
.elementsByTagName( QStringLiteral( "maplayer" ) ) );
for ( QgsLayerTreeNode *node : group->children() )
{
if ( QgsLayerTree::isLayer( node ) )
{
QgsMapLayer *l( QgsLayerTree::toLayer( node )->layer() );
if ( l )
{
for ( int i = 0; i < mlNodeList.count(); i++ )
{
QDomNode mlNode( mlNodeList.at( i ) );
QString id( mlNode.firstChildElement( QStringLiteral( "id" ) ).firstChild().nodeValue() );
if ( id == l->id() )
{
QDomImplementation DomImplementation;
QDomDocumentType documentType = DomImplementation.createDocumentType( QStringLiteral( "qgis" ), QStringLiteral( "http://mrcc.com/qgis.dtd" ), QStringLiteral( "SYSTEM" ) );
QDomDocument document( documentType );
QDomElement element = mlNode.toElement();
document.appendChild( element );
QString str;
QTextStream stream( &str );
document.save( stream, 4 /*indent*/ );
l->setOriginalXmlProperties( str );
}
}
}
}
}
}
QStringList QgsLayerTreeUtils::invisibleLayerList( QgsLayerTreeNode *node )
{
QStringList list;

View File

@ -19,6 +19,7 @@
#include <qnamespace.h>
#include <QList>
#include <QPair>
#include <QDomNodeList>
#include "qgis_core.h"
class QDomElement;
@ -58,9 +59,15 @@ class CORE_EXPORT QgsLayerTreeUtils
//! Returns true if any of the layers is modified
static bool layersModified( const QList<QgsLayerTreeLayer *> &layerNodes );
//! Remove layer nodes that refer to invalid layers
//! Removes layer nodes that refer to invalid layers
static void removeInvalidLayers( QgsLayerTreeGroup *group );
/**
* Stores in a layer's originalXmlProperties the layer properties information
* \since 3.6
*/
static void storeOriginalLayersProperties( QgsLayerTreeGroup *group, const QDomDocument *doc );
//! Remove subtree of embedded groups and replaces it with a custom property embedded-visible-layers
static void replaceChildrenOfEmbeddedGroups( QgsLayerTreeGroup *group );

View File

@ -283,12 +283,6 @@ bool QgsMapLayer::readLayerXml( const QDomElement &layerElement, QgsReadWriteCon
QgsCoordinateReferenceSystem::setCustomCrsValidation( savedValidation );
mCRS = savedCRS;
// Abort if any error in layer, such as not found.
if ( layerError )
{
return false;
}
// the internal name is just the data source basename
//QFileInfo dataSourceFileInfo( mDataSource );
//internalName = dataSourceFileInfo.baseName();
@ -387,7 +381,7 @@ bool QgsMapLayer::readLayerXml( const QDomElement &layerElement, QgsReadWriteCon
QDomElement metadataElem = layerElement.firstChildElement( QStringLiteral( "resourceMetadata" ) );
mMetadata.readMetadataXml( metadataElem );
return true;
return ! layerError;
} // bool QgsMapLayer::readLayerXML
@ -1579,6 +1573,21 @@ bool QgsMapLayer::writeStyle( QDomNode &node, QDomDocument &doc, QString &errorM
return false;
}
void QgsMapLayer::setDataSource( const QString &dataSource, const QString &baseName, const QString &provider, const QgsDataProvider::ProviderOptions &options, bool loadDefaultStyleFlag )
{
Q_UNUSED( dataSource );
Q_UNUSED( baseName );
Q_UNUSED( provider );
Q_UNUSED( options );
Q_UNUSED( loadDefaultStyleFlag );
}
QString QgsMapLayer::providerType() const
{
return mProviderKey;
}
void QgsMapLayer::readCommonStyle( const QDomElement &layerElement, const QgsReadWriteContext &context,
QgsMapLayer::StyleCategories categories )
{
@ -1824,6 +1833,21 @@ bool QgsMapLayer::isReadOnly() const
return true;
}
QString QgsMapLayer::originalXmlProperties() const
{
return mOriginalXmlProperties;
}
void QgsMapLayer::setOriginalXmlProperties( const QString &originalXmlProperties )
{
mOriginalXmlProperties = originalXmlProperties;
}
void QgsMapLayer::setProviderType( const QString &providerType )
{
mProviderKey = providerType;
}
QSet<QgsMapLayerDependency> QgsMapLayer::dependencies() const
{
return mDependencies;

View File

@ -38,6 +38,7 @@
#include "qgslayermetadata.h"
#include "qgsmaplayerstyle.h"
#include "qgsreadwritecontext.h"
#include "qgsdataprovider.h"
class QgsAbstract3DRenderer;
class QgsDataProvider;
@ -231,12 +232,12 @@ class CORE_EXPORT QgsMapLayer : public QObject
QString name() const;
/**
* Returns the layer's data provider.
* Returns the layer's data provider, it may be null.
*/
virtual QgsDataProvider *dataProvider();
/**
* Returns the layer's data provider in a const-correct manner
* Returns the layer's data provider in a const-correct manner, it may be null.
* \note not available in Python bindings
*/
virtual const QgsDataProvider *dataProvider() const SIP_SKIP;
@ -886,6 +887,29 @@ class CORE_EXPORT QgsMapLayer : public QObject
virtual bool writeStyle( QDomNode &node, QDomDocument &doc, QString &errorMessage, const QgsReadWriteContext &context,
StyleCategories categories = AllStyleCategories ) const;
/**
* Updates the data source of the layer. The layer's renderer and legend will be preserved only
* if the geometry type of the new data source matches the current geometry type of the layer.
*
* Subclasses should override this method: default implementation does nothing.
*
* \param dataSource new layer data source
* \param baseName base name of the layer
* \param provider provider string
* \param options provider options
* \param loadDefaultStyleFlag set to true to reset the layer's style to the default for the
* data source
* \see dataSourceChanged()
* \since QGIS 3.6
*/
virtual void setDataSource( const QString &dataSource, const QString &baseName, const QString &provider, const QgsDataProvider::ProviderOptions &options, bool loadDefaultStyleFlag = false );
/**
* Returns the provider type (provider key) for this layer
*/
QString providerType() const;
//! Returns pointer to layer's undo stack
QUndoStack *undoStack();
@ -1077,6 +1101,25 @@ class CORE_EXPORT QgsMapLayer : public QObject
*/
bool isRefreshOnNotifyEnabled() const { return mIsRefreshOnNofifyEnabled; }
/**
* Returns the XML properties of the original layer as they were when the layer
* was first read from the project file. In case of new layers this is normally empty.
*
* The storage format for the XML is qlr
*
* \since QGIS 3.6
*/
QString originalXmlProperties() const;
/**
* Sets the original XML properties for the layer to \a originalXmlProperties
*
* The storage format for the XML is qlr
*
* \since QGIS 3.6
*/
void setOriginalXmlProperties( const QString &originalXmlProperties );
public slots:
/**
@ -1253,6 +1296,15 @@ class CORE_EXPORT QgsMapLayer : public QObject
*/
void flagsChanged();
/**
* Emitted whenever the layer's data source has been changed.
*
* \see setDataSource()
*
* \since QGIS 3.5
*/
void dataSourceChanged();
private slots:
void onNotifiedTriggerRepaint( const QString &message );
@ -1340,6 +1392,9 @@ class CORE_EXPORT QgsMapLayer : public QObject
void readCommonStyle( const QDomElement &layerElement, const QgsReadWriteContext &context,
StyleCategories categories = AllStyleCategories );
//! Sets the \a providerType (provider key)
void setProviderType( const QString &providerType );
#ifndef SIP_RUN
#if 0
//! Debugging member - invoked when a connect() is made to this object
@ -1400,6 +1455,10 @@ class CORE_EXPORT QgsMapLayer : public QObject
bool mIsRefreshOnNofifyEnabled = false;
QString mRefreshOnNofifyMessage;
//! Data provider key (name of the data provider)
QString mProviderKey;
private:
virtual QString baseURI( PropertyType type ) const;
@ -1465,6 +1524,13 @@ class CORE_EXPORT QgsMapLayer : public QObject
//! Renderer for 3D views
QgsAbstract3DRenderer *m3DRenderer = nullptr;
/**
* Stores the original XML properties of the layer when loaded from the project
*
* This information can be used to pass through the bad layers or to reset changes on a good layer
*/
QString mOriginalXmlProperties;
};
Q_DECLARE_METATYPE( QgsMapLayer * )

View File

@ -307,7 +307,7 @@ QList<QgsLayerTreeModelLegendNode *> QgsDefaultRasterLayerLegend::createLayerTre
QList<QgsLayerTreeModelLegendNode *> nodes;
// temporary solution for WMS. Ideally should be done with a delegate.
if ( mLayer->dataProvider()->supportsLegendGraphic() )
if ( mLayer->dataProvider() && mLayer->dataProvider()->supportsLegendGraphic() )
{
nodes << new QgsWmsLegendNode( nodeLayer );
}

View File

@ -16,7 +16,9 @@
***************************************************************************/
#include "qgsmaplayerstore.h"
#include "qgsmaplayer.h"
#include "qgslogger.h"
#include <QList>
QgsMapLayerStore::QgsMapLayerStore( QObject *parent )
: QObject( parent )
@ -32,6 +34,18 @@ int QgsMapLayerStore::count() const
return mMapLayers.size();
}
int QgsMapLayerStore::validCount() const
{
int i = 0;
const QList<QgsMapLayer *> cLayers = mMapLayers.values();
for ( const auto l : cLayers )
{
if ( l->isValid() )
i++;
}
return i;
}
QgsMapLayer *QgsMapLayerStore::mapLayer( const QString &layerId ) const
{
return mMapLayers.value( layerId );
@ -55,9 +69,9 @@ QList<QgsMapLayer *> QgsMapLayerStore::addMapLayers( const QList<QgsMapLayer *>
QList<QgsMapLayer *> myResultList;
Q_FOREACH ( QgsMapLayer *myLayer, layers )
{
if ( !myLayer || !myLayer->isValid() )
if ( !myLayer )
{
QgsDebugMsg( QStringLiteral( "Cannot add invalid layers" ) );
QgsDebugMsg( QStringLiteral( "Cannot add null layers" ) );
continue;
}
//check the layer is not already registered!
@ -212,3 +226,14 @@ QMap<QString, QgsMapLayer *> QgsMapLayerStore::mapLayers() const
{
return mMapLayers;
}
QMap<QString, QgsMapLayer *> QgsMapLayerStore::validMapLayers() const
{
QMap<QString, QgsMapLayer *> validLayers;
for ( const auto &id : mMapLayers.keys() )
{
if ( mMapLayers[id]->isValid() )
validLayers[id] = mMapLayers[id];
}
return validLayers;
}

View File

@ -50,6 +50,12 @@ class CORE_EXPORT QgsMapLayerStore : public QObject
*/
int count() const;
/**
* Returns the number of valid layers contained in the store.
* \since QGIS 3.6
*/
int validCount() const;
#ifdef SIP_RUN
/**
@ -93,6 +99,15 @@ class CORE_EXPORT QgsMapLayerStore : public QObject
*/
QMap<QString, QgsMapLayer *> mapLayers() const;
/**
* Returns a map of all valid layers by layer ID.
* \see mapLayer()
* \see mapLayersByName()
* \see layers()
* \since QGIS 3.6
*/
QMap<QString, QgsMapLayer *> validMapLayers() const;
#ifndef SIP_RUN
/**
@ -135,7 +150,7 @@ class CORE_EXPORT QgsMapLayerStore : public QObject
* the layers yourself. Not available in Python.
*
* \returns a list of the map layers that were added
* successfully. If a layer is invalid, or already exists in the store,
* successfully. If a layer already exists in the store,
* it will not be part of the returned list.
*
* \see addMapLayer()

View File

@ -368,6 +368,20 @@ QgsProject::QgsProject( QObject *parent )
connect( mLayerStore.get(), &QgsMapLayerStore::layerWasAdded, this, &QgsProject::layerWasAdded );
if ( QgsApplication::instance() )
connect( QgsApplication::instance(), &QgsApplication::requestForTranslatableObjects, this, &QgsProject::registerTranslatableObjects );
connect( mLayerStore.get(), static_cast<void ( QgsMapLayerStore::* )( const QList<QgsMapLayer *> & )>( &QgsMapLayerStore::layersWillBeRemoved ),
[ = ]( const QList<QgsMapLayer *> &layers )
{
for ( const auto &layer : layers )
disconnect( layer, &QgsMapLayer::dataSourceChanged, mRelationManager, &QgsRelationManager::updateRelationsStatus );
}
);
connect( mLayerStore.get(), static_cast<void ( QgsMapLayerStore::* )( const QList<QgsMapLayer *> & )>( &QgsMapLayerStore::layersAdded ),
[ = ]( const QList<QgsMapLayer *> &layers )
{
for ( const auto &layer : layers )
connect( layer, &QgsMapLayer::dataSourceChanged, mRelationManager, &QgsRelationManager::updateRelationsStatus );
}
);
}
@ -926,31 +940,29 @@ bool QgsProject::addLayer( const QDomElement &layerElem, QList<QDomNode> &broken
if ( !mapLayer )
{
QgsDebugMsg( QStringLiteral( "Unable to create layer" ) );
return false;
}
Q_CHECK_PTR( mapLayer ); // NOLINT
// have the layer restore state that is stored in Dom node
if ( mapLayer->readLayerXml( layerElem, context ) && mapLayer->isValid() )
bool layerIsValid = mapLayer->readLayerXml( layerElem, context ) && mapLayer->isValid();
QList<QgsMapLayer *> newLayers;
newLayers << mapLayer;
if ( layerIsValid )
{
emit readMapLayer( mapLayer, layerElem );
QList<QgsMapLayer *> myLayers;
myLayers << mapLayer;
addMapLayers( myLayers );
return true;
addMapLayers( newLayers );
}
else
{
delete mapLayer;
// It's a bad layer: do not add to legend (the user will decide if she wants to do so)
addMapLayers( newLayers, false );
newLayers.first();
QgsDebugMsg( "Unable to load " + type + " layer" );
brokenNodes.push_back( layerElem );
return false;
}
return layerIsValid;
}
bool QgsProject::read( const QString &filename )
@ -1293,8 +1305,10 @@ bool QgsProject::readProjectFile( const QString &filename )
}
}
// make sure the are just valid layers
QgsLayerTreeUtils::removeInvalidLayers( mRootGroup );
// After bad layer handling we might still have invalid layers,
// store them in case the user wanted to handle them later
// or wanted to pass them through when saving
QgsLayerTreeUtils::storeOriginalLayersProperties( mRootGroup, doc.get() );
mRootGroup->removeCustomProperty( QStringLiteral( "loading" ) );
@ -1500,43 +1514,46 @@ void QgsProject::onMapLayersAdded( const QList<QgsMapLayer *> &layers )
Q_FOREACH ( QgsMapLayer *layer, layers )
{
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
if ( vlayer )
if ( layer->isValid() )
{
if ( autoTransaction() )
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
if ( vlayer )
{
if ( QgsTransaction::supportsTransaction( vlayer ) )
if ( autoTransaction() )
{
QString connString = QgsDataSourceUri( vlayer->source() ).connectionInfo();
QString key = vlayer->providerType();
QgsTransactionGroup *tg = mTransactionGroups.value( qMakePair( key, connString ) );
if ( !tg )
if ( QgsTransaction::supportsTransaction( vlayer ) )
{
tg = new QgsTransactionGroup();
mTransactionGroups.insert( qMakePair( key, connString ), tg );
tgChanged = true;
QString connString = QgsDataSourceUri( vlayer->source() ).connectionInfo();
QString key = vlayer->providerType();
QgsTransactionGroup *tg = mTransactionGroups.value( qMakePair( key, connString ) );
if ( !tg )
{
tg = new QgsTransactionGroup();
mTransactionGroups.insert( qMakePair( key, connString ), tg );
tgChanged = true;
}
tg->addLayer( vlayer );
}
tg->addLayer( vlayer );
}
vlayer->dataProvider()->setProviderProperty( QgsVectorDataProvider::EvaluateDefaultValues, evaluateDefaultValues() );
}
vlayer->dataProvider()->setProviderProperty( QgsVectorDataProvider::EvaluateDefaultValues, evaluateDefaultValues() );
}
if ( tgChanged )
emit transactionGroupsChanged();
if ( tgChanged )
emit transactionGroupsChanged();
connect( layer, &QgsMapLayer::configChanged, this, [ = ] { setDirty(); } );
connect( layer, &QgsMapLayer::configChanged, this, [ = ] { setDirty(); } );
// check if we have to update connections for layers with dependencies
for ( QMap<QString, QgsMapLayer *>::iterator it = existingMaps.begin(); it != existingMaps.end(); it++ )
{
QSet<QgsMapLayerDependency> deps = it.value()->dependencies();
if ( deps.contains( layer->id() ) )
// check if we have to update connections for layers with dependencies
for ( QMap<QString, QgsMapLayer *>::iterator it = existingMaps.begin(); it != existingMaps.end(); it++ )
{
// reconnect to change signals
it.value()->setDependencies( deps );
QSet<QgsMapLayerDependency> deps = it.value()->dependencies();
if ( deps.contains( layer->id() ) )
{
// reconnect to change signals
it.value()->setDependencies( deps );
}
}
}
}
@ -1751,10 +1768,26 @@ bool QgsProject::writeProjectFile( const QString &filename )
QHash< QString, QPair< QString, bool> >::const_iterator emIt = mEmbeddedLayers.constFind( ml->id() );
if ( emIt == mEmbeddedLayers.constEnd() )
{
// general layer metadata
QDomElement maplayerElem = doc->createElement( QStringLiteral( "maplayer" ) );
ml->writeLayerXml( maplayerElem, *doc, context );
QDomElement maplayerElem;
// If layer is not valid, let's try to restore saved properties from invalidLayerProperties
if ( ml->isValid() )
{
// general layer metadata
maplayerElem = doc->createElement( QStringLiteral( "maplayer" ) );
ml->writeLayerXml( maplayerElem, *doc, context );
}
else if ( ! ml->originalXmlProperties().isEmpty() )
{
QDomDocument document;
if ( document.setContent( ml->originalXmlProperties() ) )
{
maplayerElem = document.firstChildElement();
}
else
{
QgsDebugMsg( QStringLiteral( "Could not restore layer properties for layer %1" ).arg( ml->id() ) );
}
}
emit writeMapLayer( ml, maplayerElem, *doc );
@ -2534,6 +2567,11 @@ int QgsProject::count() const
return mLayerStore->count();
}
int QgsProject::validCount() const
{
return mLayerStore->validCount();
}
QgsMapLayer *QgsProject::mapLayer( const QString &layerId ) const
{
return mLayerStore->mapLayer( layerId );
@ -2723,9 +2761,9 @@ void QgsProject::reloadAllLayers()
}
}
QMap<QString, QgsMapLayer *> QgsProject::mapLayers() const
QMap<QString, QgsMapLayer *> QgsProject::mapLayers( const bool validOnly ) const
{
return mLayerStore->mapLayers();
return validOnly ? mLayerStore->validMapLayers() : mLayerStore->mapLayers();
}
QgsTransactionGroup *QgsProject::transactionGroup( const QString &providerKey, const QString &connString )

View File

@ -690,6 +690,9 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera
//! Returns the number of registered layers.
int count() const;
//! Returns the number of registered valid layers.
int validCount() const;
/**
* Retrieve a pointer to a registered layer by layer ID.
* \param layerId ID of layer to retrieve
@ -710,11 +713,13 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera
/**
* Returns a map of all registered layers by layer ID.
*
* \param validOnly if set only valid layers will be returned
* \see mapLayer()
* \see mapLayersByName()
* \see layers()
*/
QMap<QString, QgsMapLayer *> mapLayers() const;
QMap<QString, QgsMapLayer *> mapLayers( const bool validOnly = false ) const;
/**
* Returns true if the project comes from a zip archive, false otherwise.
@ -757,7 +762,7 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera
* the layers yourself. Not available in Python.
*
* \returns a list of the map layers that were added
* successfully. If a layer is invalid, or already exists in the registry,
* successfully. If a layer or already exists in the registry,
* it will not be part of the returned QList.
*
* \note As a side-effect QgsProject is made dirty.

View File

@ -22,7 +22,7 @@
void QgsProjectBadLayerHandler::handleBadLayers( const QList<QDomNode> &layers )
{
QgsApplication::messageLog()->logMessage( QObject::tr( "%1 bad layers dismissed:" ).arg( layers.size() ) );
QgsApplication::messageLog()->logMessage( QObject::tr( "%1 bad layers found:" ).arg( layers.size() ) );
Q_FOREACH ( const QDomNode &layer, layers )
{
QgsApplication::messageLog()->logMessage( QObject::tr( " * %1" ).arg( dataSource( layer ) ) );

View File

@ -325,7 +325,7 @@ QgsAttributeList QgsRelation::referencingFields() const
bool QgsRelation::isValid() const
{
return d->mValid && !d->mReferencingLayer.isNull() && !d->mReferencedLayer.isNull();
return d->mValid && !d->mReferencingLayer.isNull() && !d->mReferencedLayer.isNull() && d->mReferencingLayer.data()->isValid() && d->mReferencedLayer.data()->isValid();
}
bool QgsRelation::hasEqualDefinition( const QgsRelation &other ) const

View File

@ -338,6 +338,7 @@ class CORE_EXPORT QgsRelation
/**
* Returns the validity of this relation. Don't use the information if it's not valid.
* A relation is considered valid if both referenced and referencig layers are valid.
*
* \returns true if the relation is valid
*/
@ -366,14 +367,16 @@ class CORE_EXPORT QgsRelation
*/
Q_INVOKABLE QString resolveReferencingField( const QString &referencedField ) const;
private:
/**
* Updates the validity status of this relation.
* Will be called internally whenever a member is changed.
*
* \since QGIS 3.6
*/
void updateRelationStatus();
private:
mutable QExplicitlySharedDataPointer<QgsRelationPrivate> d;
};

View File

@ -50,7 +50,8 @@ QMap<QString, QgsRelation> QgsRelationManager::relations() const
void QgsRelationManager::addRelation( const QgsRelation &relation )
{
if ( !relation.isValid() )
// Do not add relations to layers that do not exist
if ( !( relation.referencingLayer() && relation.referencedLayer() ) )
return;
mRelations.insert( relation.id(), relation );
@ -60,6 +61,16 @@ void QgsRelationManager::addRelation( const QgsRelation &relation )
emit changed();
}
void QgsRelationManager::updateRelationsStatus()
{
for ( auto relation : mRelations )
{
relation.updateRelationStatus();
}
}
void QgsRelationManager::removeRelation( const QString &id )
{
mRelations.remove( id );

View File

@ -59,6 +59,8 @@ class CORE_EXPORT QgsRelationManager : public QObject
/**
* Add a relation.
* Invalid relations are added only if both referencing layer and referenced
* layer exist.
*
* \param relation The relation to add.
*/
@ -141,6 +143,13 @@ class CORE_EXPORT QgsRelationManager : public QObject
*/
void changed();
public slots:
/**
* Updates relations status
*/
void updateRelationsStatus();
private slots:
void readProject( const QDomDocument &doc, QgsReadWriteContext &context );
void writeProject( QDomDocument &doc );

View File

@ -143,11 +143,13 @@ QgsVectorLayer::QgsVectorLayer( const QString &vectorLayerPath,
const QString &providerKey,
const LayerOptions &options )
: QgsMapLayer( VectorLayer, baseName, vectorLayerPath )
, mProviderKey( providerKey )
, mAuxiliaryLayer( nullptr )
, mAuxiliaryLayerKey( QString() )
, mReadExtentFromXml( options.readExtentFromXml )
{
setProviderType( providerKey );
mGeometryOptions = qgis::make_unique<QgsGeometryOptions>();
mActions = new QgsActionManager( this );
mConditionalStyles = new QgsConditionalLayerStyles();
@ -313,12 +315,6 @@ QString QgsVectorLayer::dataComment() const
return QString();
}
QString QgsVectorLayer::providerType() const
{
return mProviderKey;
}
QgsCoordinateReferenceSystem QgsVectorLayer::sourceCrs() const
{
return crs();
@ -599,16 +595,7 @@ QgsWkbTypes::GeometryType QgsVectorLayer::geometryType() const
{
QgsDebugMsgLevel( QStringLiteral( "invalid layer or pointer to mDataProvider is null" ), 3 );
}
// We shouldn't get here, and if we have, other things are likely to
// go wrong. Code that uses the type() return value should be
// rewritten to cope with a value of Qgis::Unknown. To make this
// need known, the following message is printed every time we get
// here.
// AP: it looks like we almost always get here, since 2.x ... either we remove this
// warning of take care of the problems that may occur
QgsDebugMsg( QStringLiteral( "WARNING: This code should never be reached. Problems may occur..." ) );
QgsDebugMsgLevel( QStringLiteral( "Vector layer with unknown geometry type." ), 3 );
return QgsWkbTypes::UnknownGeometry;
}
@ -1425,14 +1412,14 @@ bool QgsVectorLayer::readXml( const QDomNode &layer_node, QgsReadWriteContext &c
QgsDataProvider::ProviderOptions options;
if ( !setDataProvider( mProviderKey, options ) )
{
return false;
QgsDebugMsg( QStringLiteral( "Could not set data provider for layer %1" ).arg( publicSource() ) );
}
QDomElement pkeyElem = pkeyNode.toElement();
if ( !pkeyElem.isNull() )
{
QString encodingString = pkeyElem.attribute( QStringLiteral( "encoding" ) );
if ( !encodingString.isEmpty() )
if ( mDataProvider && !encodingString.isEmpty() )
{
mDataProvider->setEncoding( encodingString );
}
@ -1591,6 +1578,7 @@ bool QgsVectorLayer::setDataProvider( QString const &provider, const QgsDataProv
mDataProvider = qobject_cast<QgsVectorDataProvider *>( QgsProviderRegistry::instance()->createProvider( provider, dataSource, options ) );
if ( !mDataProvider )
{
mValid = false;
QgsDebugMsgLevel( QStringLiteral( "Unable to get data provider" ), 2 );
return false;
}
@ -1604,7 +1592,6 @@ bool QgsVectorLayer::setDataProvider( QString const &provider, const QgsDataProv
if ( !mValid )
{
QgsDebugMsgLevel( QStringLiteral( "Invalid provider plugin %1" ).arg( QString( mDataSource.toUtf8() ) ), 2 );
return false;
}
if ( mDataProvider->capabilities() & QgsVectorDataProvider::ReadLayerMetadata )

View File

@ -763,9 +763,6 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
//! Returns the WKBType or WKBUnknown in case of error
QgsWkbTypes::Type wkbType() const FINAL;
//! Returns the provider type for this layer
QString providerType() const;
QgsCoordinateReferenceSystem sourceCrs() const FINAL;
QString sourceName() const FINAL;
@ -987,7 +984,7 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
* \see dataSourceChanged()
* \since QGIS 3.2
*/
void setDataSource( const QString &dataSource, const QString &baseName, const QString &provider, const QgsDataProvider::ProviderOptions &options, bool loadDefaultStyleFlag = false );
void setDataSource( const QString &dataSource, const QString &baseName, const QString &provider, const QgsDataProvider::ProviderOptions &options, bool loadDefaultStyleFlag = false ) override;
QString loadDefaultStyle( bool &resultFlag SIP_OUT ) FINAL;
@ -2135,15 +2132,6 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
signals:
/**
* Emitted whenever the layer's data source has been changed.
*
* \see setDataSource()
*
* \since QGIS 3.4
*/
void dataSourceChanged();
/**
* This signal is emitted when selection was changed
*
@ -2444,9 +2432,6 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
QString mMapTipTemplate;
//! Data provider key
QString mProviderKey;
//! The user-defined actions that are accessed from the Identify Results dialog box
QgsActionManager *mActions = nullptr;

View File

@ -116,29 +116,14 @@ QgsRasterLayer::QgsRasterLayer( const QString &uri,
// Constant that signals property not used.
, QSTRING_NOT_SET( QStringLiteral( "Not Set" ) )
, TRSTRING_NOT_SET( tr( "Not Set" ) )
, mProviderKey( providerKey )
{
QgsDebugMsgLevel( QStringLiteral( "Entered" ), 4 );
init();
setProviderType( providerKey );
QgsDataProvider::ProviderOptions providerOptions;
setDataProvider( providerKey, providerOptions );
if ( !mValid ) return;
// load default style
bool defaultLoadedFlag = false;
if ( mValid && options.loadDefaultStyle )
{
loadDefaultStyle( defaultLoadedFlag );
}
if ( !defaultLoadedFlag )
{
setDefaultContrastEnhancement();
}
setDataSource( uri, baseName, providerKey, providerOptions, options.loadDefaultStyle );
// TODO: Connect signals from the dataprovider to the qgisapp
emit statusChanged( tr( "QgsRasterLayer created" ) );
} // QgsRasterLayer ctor
QgsRasterLayer::~QgsRasterLayer()
@ -797,7 +782,81 @@ void QgsRasterLayer::setDataProvider( QString const &provider, const QgsDataProv
mValid = true;
QgsDebugMsgLevel( QStringLiteral( "exiting." ), 4 );
} // QgsRasterLayer::setDataProvider
}
void QgsRasterLayer::setDataSource( const QString &dataSource, const QString &baseName, const QString &provider, const QgsDataProvider::ProviderOptions &options, bool loadDefaultStyleFlag )
{
bool wasValid( isValid() );
QDomImplementation domImplementation;
QDomDocumentType documentType;
QDomDocument doc;
QString errorMsg;
QDomElement rootNode;
// Store the original style
if ( wasValid && ! loadDefaultStyleFlag )
{
documentType = domImplementation.createDocumentType(
QStringLiteral( "qgis" ), QStringLiteral( "http://mrcc.com/qgis.dtd" ), QStringLiteral( "SYSTEM" ) );
doc = QDomDocument( documentType );
rootNode = doc.createElement( QStringLiteral( "qgis" ) );
rootNode.setAttribute( QStringLiteral( "version" ), Qgis::QGIS_VERSION );
doc.appendChild( rootNode );
QgsReadWriteContext writeContext;
if ( ! writeSymbology( rootNode, doc, errorMsg, writeContext ) )
{
QgsDebugMsg( QStringLiteral( "Could not store symbology for layer %1: %2" )
.arg( name() )
.arg( errorMsg ) );
}
}
if ( mDataProvider )
closeDataProvider();
init();
for ( int i = mPipe.size() - 1; i >= 0; --i )
{
mPipe.remove( i );
}
mDataSource = dataSource;
mLayerName = baseName;
setDataProvider( provider, options );
if ( mValid )
{
// load default style
bool defaultLoadedFlag = false;
if ( loadDefaultStyleFlag )
{
loadDefaultStyle( defaultLoadedFlag );
}
else if ( wasValid && errorMsg.isEmpty() ) // Restore the style
{
QgsReadWriteContext readContext;
if ( ! readSymbology( rootNode, errorMsg, readContext ) )
{
QgsDebugMsg( QStringLiteral( "Could not restore symbology for layer %1: %2" )
.arg( name() )
.arg( errorMsg ) );
}
}
if ( !defaultLoadedFlag )
{
setDefaultContrastEnhancement();
}
emit statusChanged( tr( "QgsRasterLayer created" ) );
}
emit dataSourceChanged();
emit dataChanged();
}
void QgsRasterLayer::closeDataProvider()
{
@ -1251,9 +1310,13 @@ QImage QgsRasterLayer::previewAsImage( QSize size, const QColor &bgColor, QImage
{
QImage myQImage( size, format );
if ( ! isValid( ) )
return QImage();
myQImage.setColor( 0, bgColor.rgba() );
myQImage.fill( 0 ); //defaults to white, set to transparent for rendering on a map
QgsRasterViewPort *myRasterViewPort = new QgsRasterViewPort();
double myMapUnitsPerPixel;
@ -1474,7 +1537,12 @@ bool QgsRasterLayer::readXml( const QDomNode &layer_node, QgsReadWriteContext &c
QgsDataProvider::ProviderOptions providerOptions;
setDataProvider( mProviderKey, providerOptions );
if ( !mValid ) return false;
if ( ! mDataProvider )
{
QgsDebugMsg( QStringLiteral( "Raster data provider could not be created for %1" ).arg( mDataSource ) );
return false;
}
QString error;
bool res = readSymbology( layer_node, error, context );

View File

@ -199,7 +199,7 @@ class CORE_EXPORT QgsRasterLayer : public QgsMapLayer
* */
explicit QgsRasterLayer( const QString &uri,
const QString &baseName = QString(),
const QString &providerKey = "gdal",
const QString &providerType = "gdal",
const QgsRasterLayer::LayerOptions &options = QgsRasterLayer::LayerOptions() );
~QgsRasterLayer() override;
@ -259,6 +259,20 @@ class CORE_EXPORT QgsRasterLayer : public QgsMapLayer
*/
void setDataProvider( const QString &provider, const QgsDataProvider::ProviderOptions &options );
/**
* Updates the data source of the layer. The layer's renderer and legend will be preserved only
* if the geometry type of the new data source matches the current geometry type of the layer.
* \param dataSource new layer data source
* \param baseName base name of the layer
* \param provider provider string
* \param options provider options
* \param loadDefaultStyleFlag set to true to reset the layer's style to the default for the
* data source
* \see dataSourceChanged()
* \since QGIS 3.6
*/
void setDataSource( const QString &dataSource, const QString &baseName, const QString &provider, const QgsDataProvider::ProviderOptions &options, bool loadDefaultStyleFlag = false ) override;
/**
* Returns the raster layer type (which is a read only property).
*/
@ -453,9 +467,6 @@ class CORE_EXPORT QgsRasterLayer : public QgsMapLayer
QgsRasterViewPort mLastViewPort;
//! [ data provider interface ] Data provider key
QString mProviderKey;
LayerType mRasterType;
QgsRasterPipe mPipe;

View File

@ -22,17 +22,30 @@
#include <QPushButton>
QgsDataSourceSelectDialog::QgsDataSourceSelectDialog( bool setFilterByLayerType,
const QgsMapLayer::LayerType &layerType,
QWidget *parent )
QgsDataSourceSelectDialog::QgsDataSourceSelectDialog(
QgsBrowserModel *browserModel,
bool setFilterByLayerType,
const QgsMapLayer::LayerType &layerType,
QWidget *parent )
: QDialog( parent )
{
if ( ! browserModel )
{
mBrowserModel = qgis::make_unique<QgsBrowserModel>();
mOwnModel = true;
}
else
{
mBrowserModel.reset( browserModel );
mOwnModel = false;
}
setupUi( this );
setWindowTitle( tr( "Select a Data Source" ) );
QgsGui::enableAutoGeometryRestore( this );
mBrowserModel.initialize();
mBrowserProxyModel.setBrowserModel( &mBrowserModel );
mBrowserModel->initialize();
mBrowserProxyModel.setBrowserModel( mBrowserModel.get() );
mBrowserTreeView->setHeaderHidden( true );
if ( setFilterByLayerType )
@ -47,6 +60,12 @@ QgsDataSourceSelectDialog::QgsDataSourceSelectDialog( bool setFilterByLayerType,
connect( mBrowserTreeView, &QgsBrowserTreeView::clicked, this, &QgsDataSourceSelectDialog::onLayerSelected );
}
QgsDataSourceSelectDialog::~QgsDataSourceSelectDialog()
{
if ( ! mOwnModel )
mBrowserModel.release();
}
void QgsDataSourceSelectDialog::setLayerTypeFilter( QgsMapLayer::LayerType layerType )
{
mBrowserProxyModel.setFilterByLayerType( true );

View File

@ -50,14 +50,19 @@ class GUI_EXPORT QgsDataSourceSelectDialog: public QDialog, private Ui::QgsDataS
/**
* Constructs a QgsDataSourceSelectDialog, optionally filtering by layer type
*
* \param browserModel an existing browser model (typically from app), if null an instance will be created
* \param setFilterByLayerType activates filtering by layer type
* \param layerType sets the layer type filter, this is in effect only if filtering by layer type is also active
* \param parent the object
*/
QgsDataSourceSelectDialog( bool setFilterByLayerType = false,
QgsDataSourceSelectDialog( QgsBrowserModel *browserModel = nullptr,
bool setFilterByLayerType = false,
const QgsMapLayer::LayerType &layerType = QgsMapLayer::LayerType::VectorLayer,
QWidget *parent = nullptr );
~QgsDataSourceSelectDialog() override;
/**
* Sets layer type filter to \a layerType and activates the filtering
*/
@ -75,8 +80,9 @@ class GUI_EXPORT QgsDataSourceSelectDialog: public QDialog, private Ui::QgsDataS
private:
QgsBrowserModel mBrowserModel;
QgsBrowserProxyModel mBrowserProxyModel;
std::unique_ptr<QgsBrowserModel> mBrowserModel;
bool mOwnModel = true;
QgsMimeDataUtils::Uri mUri;
};

View File

@ -70,6 +70,8 @@ QgsRasterTransparencyWidget::QgsRasterTransparencyWidget( QgsRasterLayer *layer,
void QgsRasterTransparencyWidget::syncToLayer()
{
if ( ! mRasterLayer->isValid() )
return;
QgsRasterDataProvider *provider = mRasterLayer->dataProvider();
QgsRasterRenderer *renderer = mRasterLayer->renderer();
if ( provider )

View File

@ -57,7 +57,8 @@ QgsRendererRasterPropertiesWidget::QgsRendererRasterPropertiesWidget( QgsMapLaye
{
mRasterLayer = qobject_cast<QgsRasterLayer *>( layer );
if ( !mRasterLayer )
if ( !( mRasterLayer && mRasterLayer->isValid() ) )
return;
setupUi( this );
@ -126,6 +127,10 @@ void QgsRendererRasterPropertiesWidget::rendererChanged()
void QgsRendererRasterPropertiesWidget::apply()
{
if ( ! mRasterLayer->isValid() )
return;
mRasterLayer->brightnessFilter()->setBrightness( mSliderBrightness->value() );
mRasterLayer->brightnessFilter()->setContrast( mSliderContrast->value() );

View File

@ -892,7 +892,9 @@ QStringList QgsOgrProvider::_subLayers( bool withFeatureCount ) const
void QgsOgrProvider::setEncoding( const QString &e )
{
QgsSettings settings;
if ( ( mGDALDriverName == QLatin1String( "ESRI Shapefile" ) && settings.value( QStringLiteral( "qgis/ignoreShapeEncoding" ), true ).toBool() ) || !mOgrLayer->TestCapability( OLCStringsAsUTF8 ) )
if ( ( mGDALDriverName == QLatin1String( "ESRI Shapefile" ) &&
settings.value( QStringLiteral( "qgis/ignoreShapeEncoding" ), true ).toBool() ) ||
( mOgrLayer && !mOgrLayer->TestCapability( OLCStringsAsUTF8 ) ) )
{
QgsVectorDataProvider::setEncoding( e );
}
@ -900,7 +902,6 @@ void QgsOgrProvider::setEncoding( const QString &e )
{
QgsVectorDataProvider::setEncoding( QStringLiteral( "UTF-8" ) );
}
loadFields();
}

View File

@ -27,45 +27,12 @@
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
<set>QDialogButtonBox::Apply|QDialogButtonBox::Discard|QDialogButtonBox::Ignore</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>QgsHandleBadLayersBase</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>QgsHandleBadLayersBase</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
<connections/>
</ui>

View File

@ -55,6 +55,7 @@ ADD_PYTHON_TEST(PyQgsFieldComboBoxTest test_qgsfieldcombobox.py)
ADD_PYTHON_TEST(PyQgsFieldFormattersTest test_qgsfieldformatters.py)
ADD_PYTHON_TEST(PyQgsFillSymbolLayers test_qgsfillsymbollayers.py)
ADD_PYTHON_TEST(PyQgsProject test_qgsproject.py)
ADD_PYTHON_TEST(PyQgsProjectBadLayers test_qgsprojectbadlayers.py)
ADD_PYTHON_TEST(PyQgsFeatureIterator test_qgsfeatureiterator.py)
ADD_PYTHON_TEST(PyQgsFeedback test_qgsfeedback.py)
ADD_PYTHON_TEST(PyQgsFields test_qgsfields.py)

View File

@ -66,9 +66,11 @@ class TestQgsMapLayerStore(unittest.TestCase):
""" test that invalid map layers can't be added to store """
store = QgsMapLayerStore()
self.assertEqual(store.addMapLayer(QgsVectorLayer("Point?field=x:string", 'test', "xxx")), None)
self.assertEqual(len(store.mapLayersByName('test')), 0)
self.assertEqual(store.count(), 0)
vl = QgsVectorLayer("Point?field=x:string", 'test', "xxx")
self.assertEqual(store.addMapLayer(vl), vl)
self.assertEqual(len(store.mapLayersByName('test')), 1)
self.assertEqual(store.count(), 1)
self.assertEqual(store.validCount(), 0)
def test_addMapLayerSignals(self):
""" test that signals are correctly emitted when adding map layer"""
@ -120,12 +122,14 @@ class TestQgsMapLayerStore(unittest.TestCase):
store.removeAllMapLayers()
def test_addMapLayersInvalid(self):
""" test that invalid map layersd can't be added to store """
""" test that invalid map layers can be added to store """
store = QgsMapLayerStore()
self.assertEqual(store.addMapLayers([QgsVectorLayer("Point?field=x:string", 'test', "xxx")]), [])
self.assertEqual(len(store.mapLayersByName('test')), 0)
self.assertEqual(store.count(), 0)
vl = QgsVectorLayer("Point?field=x:string", 'test', "xxx")
self.assertEqual(store.addMapLayers([vl]), [vl])
self.assertEqual(len(store.mapLayersByName('test')), 1)
self.assertEqual(store.count(), 1)
self.assertEqual(store.validCount(), 0)
def test_addMapLayersAlreadyAdded(self):
""" test that already added layers can't be readded to store """

View File

@ -249,12 +249,17 @@ class TestQgsProject(unittest.TestCase):
QgsProject.instance().removeAllMapLayers()
def test_addMapLayerInvalid(self):
""" test that invalid map layersd can't be added to registry """
""" test that invalid map layers can be added to registry """
QgsProject.instance().removeAllMapLayers()
self.assertEqual(QgsProject.instance().addMapLayer(QgsVectorLayer("Point?field=x:string", 'test', "xxx")), None)
self.assertEqual(len(QgsProject.instance().mapLayersByName('test')), 0)
self.assertEqual(QgsProject.instance().count(), 0)
vl = QgsVectorLayer("Point?field=x:string", 'test', "xxx")
self.assertEqual(QgsProject.instance().addMapLayer(vl), vl)
self.assertFalse(vl in QgsProject.instance().mapLayers(True).values())
self.assertEqual(len(QgsProject.instance().mapLayersByName('test')), 1)
self.assertEqual(QgsProject.instance().count(), 1)
self.assertEqual(QgsProject.instance().validCount(), 0)
self.assertEqual(len(QgsProject.instance().mapLayers(True)), 0)
QgsProject.instance().removeAllMapLayers()
@ -313,12 +318,15 @@ class TestQgsProject(unittest.TestCase):
QgsProject.instance().removeAllMapLayers()
def test_addMapLayersInvalid(self):
""" test that invalid map layersd can't be added to registry """
""" test that invalid map layers can be added to registry """
QgsProject.instance().removeAllMapLayers()
self.assertEqual(QgsProject.instance().addMapLayers([QgsVectorLayer("Point?field=x:string", 'test', "xxx")]), [])
self.assertEqual(len(QgsProject.instance().mapLayersByName('test')), 0)
self.assertEqual(QgsProject.instance().count(), 0)
vl = QgsVectorLayer("Point?field=x:string", 'test', "xxx")
self.assertEqual(QgsProject.instance().addMapLayers([vl]), [vl])
self.assertFalse(vl in QgsProject.instance().mapLayers(True).values())
self.assertEqual(len(QgsProject.instance().mapLayersByName('test')), 1)
self.assertEqual(QgsProject.instance().count(), 1)
self.assertEqual(QgsProject.instance().validCount(), 0)
QgsProject.instance().removeAllMapLayers()

View File

@ -0,0 +1,298 @@
# -*- coding: utf-8 -*-
"""QGIS Unit tests for QgsProject bad layers handling.
.. note:: This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
"""
from builtins import chr
from builtins import range
__author__ = 'Alessandro Pasotti'
__date__ = '20/10/2018'
__copyright__ = 'Copyright 2018, The QGIS Project'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
import os
import filecmp
import qgis # NOQA
from qgis.core import (QgsProject,
QgsVectorLayer,
QgsCoordinateTransform,
QgsMapSettings,
QgsRasterLayer,
QgsMapLayer,
QgsRectangle,
QgsDataProvider,
QgsCoordinateReferenceSystem,
)
from qgis.gui import (QgsLayerTreeMapCanvasBridge,
QgsMapCanvas)
from qgis.PyQt.QtGui import QFont, QColor
from qgis.PyQt.QtTest import QSignalSpy
from qgis.PyQt.QtCore import QT_VERSION_STR, QTemporaryDir, QSize
from qgis.testing import start_app, unittest
from utilities import (unitTestDataPath, renderMapToImage)
from shutil import copyfile
app = start_app()
TEST_DATA_DIR = unitTestDataPath()
class TestQgsProjectBadLayers(unittest.TestCase):
def setUp(self):
p = QgsProject.instance()
p.removeAllMapLayers()
@classmethod
def getBaseMapSettings(cls):
"""
:rtype: QgsMapSettings
"""
ms = QgsMapSettings()
crs = QgsCoordinateReferenceSystem()
""":type: QgsCoordinateReferenceSystem"""
crs.createFromSrid(4326)
ms.setBackgroundColor(QColor(152, 219, 249))
ms.setOutputSize(QSize(420, 280))
ms.setOutputDpi(72)
ms.setFlag(QgsMapSettings.Antialiasing, True)
ms.setFlag(QgsMapSettings.UseAdvancedEffects, False)
ms.setFlag(QgsMapSettings.ForceVectorOutput, False) # no caching?
ms.setDestinationCrs(crs)
return ms
def test_project_roundtrip(self):
"""Tests that a project with bad layers can be saved and restored"""
p = QgsProject.instance()
temp_dir = QTemporaryDir()
for ext in ('shp', 'dbf', 'shx', 'prj'):
copyfile(os.path.join(TEST_DATA_DIR, 'lines.%s' % ext), os.path.join(temp_dir.path(), 'lines.%s' % ext))
copyfile(os.path.join(TEST_DATA_DIR, 'raster', 'band1_byte_ct_epsg4326.tif'), os.path.join(temp_dir.path(), 'band1_byte_ct_epsg4326.tif'))
copyfile(os.path.join(TEST_DATA_DIR, 'raster', 'band1_byte_ct_epsg4326.tif'), os.path.join(temp_dir.path(), 'band1_byte_ct_epsg4326_copy.tif'))
l = QgsVectorLayer(os.path.join(temp_dir.path(), 'lines.shp'), 'lines', 'ogr')
self.assertTrue(l.isValid())
rl = QgsRasterLayer(os.path.join(temp_dir.path(), 'band1_byte_ct_epsg4326.tif'), 'raster', 'gdal')
self.assertTrue(rl.isValid())
rl_copy = QgsRasterLayer(os.path.join(temp_dir.path(), 'band1_byte_ct_epsg4326_copy.tif'), 'raster_copy', 'gdal')
self.assertTrue(rl_copy.isValid())
self.assertTrue(p.addMapLayers([l, rl, rl_copy]))
# Save project
project_path = os.path.join(temp_dir.path(), 'project.qgs')
self.assertTrue(p.write(project_path))
# Re-load the project, checking for the XML properties
self.assertTrue(p.read(project_path))
vector = list(p.mapLayersByName('lines'))[0]
raster = list(p.mapLayersByName('raster'))[0]
raster_copy = list(p.mapLayersByName('raster_copy'))[0]
self.assertTrue(vector.originalXmlProperties() != '')
self.assertTrue(raster.originalXmlProperties() != '')
self.assertTrue(raster_copy.originalXmlProperties() != '')
# Test setter
raster.setOriginalXmlProperties('pippo')
self.assertEqual(raster.originalXmlProperties(), 'pippo')
# Now create and invalid project:
bad_project_path = os.path.join(temp_dir.path(), 'project_bad.qgs')
with open(project_path, 'r') as infile:
with open(bad_project_path, 'w+') as outfile:
outfile.write(infile.read().replace('./lines.shp', './lines-BAD_SOURCE.shp').replace('band1_byte_ct_epsg4326_copy.tif', 'band1_byte_ct_epsg4326_copy-BAD_SOURCE.tif'))
# Load the bad project
self.assertTrue(p.read(bad_project_path))
# Check layer is invalid
vector = list(p.mapLayersByName('lines'))[0]
raster = list(p.mapLayersByName('raster'))[0]
raster_copy = list(p.mapLayersByName('raster_copy'))[0]
self.assertIsNotNone(vector.dataProvider())
self.assertIsNotNone(raster.dataProvider())
self.assertIsNotNone(raster_copy.dataProvider())
self.assertFalse(vector.isValid())
self.assertFalse(raster_copy.isValid())
# Try a getFeatures
self.assertEqual([f for f in vector.getFeatures()], [])
self.assertTrue(raster.isValid())
self.assertEqual(vector.providerType(), 'ogr')
# Save the project
bad_project_path2 = os.path.join(temp_dir.path(), 'project_bad2.qgs')
p.write(bad_project_path2)
# Re-save the project, with fixed paths
good_project_path = os.path.join(temp_dir.path(), 'project_good.qgs')
with open(bad_project_path2, 'r') as infile:
with open(good_project_path, 'w+') as outfile:
outfile.write(infile.read().replace('./lines-BAD_SOURCE.shp', './lines.shp').replace('band1_byte_ct_epsg4326_copy-BAD_SOURCE.tif', 'band1_byte_ct_epsg4326_copy.tif'))
# Load the good project
self.assertTrue(p.read(good_project_path))
# Check layer is valid
vector = list(p.mapLayersByName('lines'))[0]
raster = list(p.mapLayersByName('raster'))[0]
raster_copy = list(p.mapLayersByName('raster_copy'))[0]
self.assertTrue(vector.isValid())
self.assertTrue(raster.isValid())
self.assertTrue(raster_copy.isValid())
def test_project_relations(self):
"""Tests that a project with bad layers and relations can be saved with relations"""
temp_dir = QTemporaryDir()
p = QgsProject.instance()
for ext in ('qgs', 'gpkg'):
copyfile(os.path.join(TEST_DATA_DIR, 'projects', 'relation_reference_test.%s' % ext), os.path.join(temp_dir.path(), 'relation_reference_test.%s' % ext))
# Load the good project
project_path = os.path.join(temp_dir.path(), 'relation_reference_test.qgs')
self.assertTrue(p.read(project_path))
point_a = list(p.mapLayersByName('point_a'))[0]
point_b = list(p.mapLayersByName('point_b'))[0]
point_a_source = point_a.publicSource()
point_b_source = point_b.publicSource()
self.assertTrue(point_a.isValid())
self.assertTrue(point_b.isValid())
# Check relations
def _check_relations():
relation = list(p.relationManager().relations().values())[0]
self.assertTrue(relation.isValid())
self.assertEqual(relation.referencedLayer().id(), point_b.id())
self.assertEqual(relation.referencingLayer().id(), point_a.id())
_check_relations()
# Now build a bad project
bad_project_path = os.path.join(temp_dir.path(), 'relation_reference_test_bad.qgs')
with open(project_path, 'r') as infile:
with open(bad_project_path, 'w+') as outfile:
outfile.write(infile.read().replace('./relation_reference_test.gpkg', './relation_reference_test-BAD_SOURCE.gpkg'))
# Load the bad project
self.assertTrue(p.read(bad_project_path))
point_a = list(p.mapLayersByName('point_a'))[0]
point_b = list(p.mapLayersByName('point_b'))[0]
self.assertFalse(point_a.isValid())
self.assertFalse(point_b.isValid())
# This fails because relations are not valid anymore
with self.assertRaises(AssertionError):
_check_relations()
# Changing data source, relations should be restored:
point_a.setDataSource(point_a_source, 'point_a', 'ogr')
point_b.setDataSource(point_b_source, 'point_b', 'ogr')
self.assertTrue(point_a.isValid())
self.assertTrue(point_b.isValid())
# Check if relations were restored
_check_relations()
# Reload the bad project
self.assertTrue(p.read(bad_project_path))
point_a = list(p.mapLayersByName('point_a'))[0]
point_b = list(p.mapLayersByName('point_b'))[0]
self.assertFalse(point_a.isValid())
self.assertFalse(point_b.isValid())
# This fails because relations are not valid anymore
with self.assertRaises(AssertionError):
_check_relations()
# Save the bad project
bad_project_path2 = os.path.join(temp_dir.path(), 'relation_reference_test_bad2.qgs')
p.write(bad_project_path2)
# Now fix the bad project
bad_project_path_fixed = os.path.join(temp_dir.path(), 'relation_reference_test_bad_fixed.qgs')
with open(bad_project_path2, 'r') as infile:
with open(bad_project_path_fixed, 'w+') as outfile:
outfile.write(infile.read().replace('./relation_reference_test-BAD_SOURCE.gpkg', './relation_reference_test.gpkg'))
# Load the fixed project
self.assertTrue(p.read(bad_project_path_fixed))
point_a = list(p.mapLayersByName('point_a'))[0]
point_b = list(p.mapLayersByName('point_b'))[0]
point_a_source = point_a.publicSource()
point_b_source = point_b.publicSource()
self.assertTrue(point_a.isValid())
self.assertTrue(point_b.isValid())
_check_relations()
def testStyles(self):
"""Test that styles for rasters and vectors are kept when setDataSource is called"""
options = QgsDataProvider.ProviderOptions()
temp_dir = QTemporaryDir()
p = QgsProject.instance()
project_path = os.path.join(temp_dir.path(), 'good_layers_test.qgs')
copyfile(os.path.join(TEST_DATA_DIR, 'projects', 'good_layers_test.qgs'), project_path)
copyfile(os.path.join(TEST_DATA_DIR, 'projects', 'bad_layers_test.gpkg'), os.path.join(temp_dir.path(), 'bad_layers_test.gpkg'))
for f in (
'bad_layer_raster_test.tfw',
'bad_layer_raster_test.tiff',
'bad_layer_raster_test.tiff.aux.xml',
'bad_layers_test.gpkg',
'good_layers_test.qgs'):
copyfile(os.path.join(TEST_DATA_DIR, 'projects', f), os.path.join(temp_dir.path(), f))
p = QgsProject().instance()
self.assertTrue(p.read(project_path))
self.assertEqual(p.count(), 3)
ms = self.getBaseMapSettings()
point_a = list(p.mapLayersByName('point_a'))[0]
point_b = list(p.mapLayersByName('point_b'))[0]
raster = list(p.mapLayersByName('bad_layer_raster_test'))[0]
self.assertTrue(point_a.isValid())
self.assertTrue(point_b.isValid())
self.assertTrue(raster.isValid())
ms.setExtent(QgsRectangle(2.81861, 41.98138, 2.81952, 41.9816))
ms.setLayers([point_a, point_b, raster])
image = renderMapToImage(ms)
print(os.path.join(temp_dir.path(), 'expected.png'))
self.assertTrue(image.save(os.path.join(temp_dir.path(), 'expected.png'), 'PNG'))
point_a_source = point_a.publicSource()
point_b_source = point_b.publicSource()
raster_source = raster.publicSource()
point_a.setDataSource(point_a_source, point_a.name(), 'ogr', options)
point_b.setDataSource(point_b_source, point_b.name(), 'ogr', options)
raster.setDataSource(raster_source, raster.name(), 'gdal', options)
self.assertTrue(image.save(os.path.join(temp_dir.path(), 'actual.png'), 'PNG'))
self.assertTrue(filecmp.cmp(os.path.join(temp_dir.path(), 'actual.png'), os.path.join(temp_dir.path(), 'expected.png')), False)
# Now build a bad project
bad_project_path = os.path.join(temp_dir.path(), 'bad_layers_test.qgs')
with open(project_path, 'r') as infile:
with open(bad_project_path, 'w+') as outfile:
outfile.write(infile.read().replace('./bad_layers_test.', './bad_layers_test-BAD_SOURCE.').replace('bad_layer_raster_test.tiff', 'bad_layer_raster_test-BAD_SOURCE.tiff'))
self.assertTrue(p.read(bad_project_path))
self.assertEqual(p.count(), 3)
point_a = list(p.mapLayersByName('point_a'))[0]
point_b = list(p.mapLayersByName('point_b'))[0]
raster = list(p.mapLayersByName('bad_layer_raster_test'))[0]
self.assertFalse(point_a.isValid())
self.assertFalse(point_b.isValid())
self.assertFalse(raster.isValid())
point_a.setDataSource(point_a_source, point_a.name(), 'ogr', options)
point_b.setDataSource(point_b_source, point_b.name(), 'ogr', options)
raster.setDataSource(raster_source, raster.name(), 'gdal', options)
self.assertTrue(image.save(os.path.join(temp_dir.path(), 'actual_fixed.png'), 'PNG'))
self.assertTrue(filecmp.cmp(os.path.join(temp_dir.path(), 'actual_fixed.png'), os.path.join(temp_dir.path(), 'expected.png')), False)
if __name__ == '__main__':
unittest.main()

View File

@ -16,16 +16,25 @@ __revision__ = '$Format:%H$'
import qgis # NOQA
import os
import filecmp
from qgis.PyQt.QtCore import QFileInfo
from qgis.PyQt.QtGui import QColor
from qgis.PyQt.QtCore import QSize, QFileInfo, Qt, QTemporaryDir
from qgis.PyQt.QtGui import (
QColor,
QImage,
QPainter,
QResizeEvent
)
from qgis.PyQt.QtXml import QDomDocument
from qgis.core import (QgsRaster,
QgsRasterLayer,
QgsReadWriteContext,
QgsColorRampShader,
QgsContrastEnhancement,
QgsDataProvider,
QgsProject,
QgsMapSettings,
QgsPointXY,
@ -40,6 +49,7 @@ from qgis.core import (QgsRaster,
QgsGradientColorRamp)
from utilities import unitTestDataPath
from qgis.testing import start_app, unittest
from qgis.testing.mocked import get_iface
# Convenience instances in case you may need them
# not used in this test
@ -48,6 +58,14 @@ start_app()
class TestQgsRasterLayer(unittest.TestCase):
def setUp(self):
self.iface = get_iface()
QgsProject.instance().removeAllMapLayers()
self.iface.mapCanvas().viewport().resize(400, 400)
# For some reason the resizeEvent is not delivered, fake it
self.iface.mapCanvas().resizeEvent(QResizeEvent(QSize(400, 400), self.iface.mapCanvas().size()))
def testIdentify(self):
myPath = os.path.join(unitTestDataPath(), 'landsat.tif')
myFileInfo = QFileInfo(myPath)
@ -694,6 +712,35 @@ class TestQgsRasterLayer(unittest.TestCase):
# compare xml documents
self.assertEqual(layer_doc.toString(), clone_doc.toString())
def testSetDataSource(self):
"""Test change data source"""
temp_dir = QTemporaryDir()
options = QgsDataProvider.ProviderOptions()
myPath = os.path.join(unitTestDataPath('raster'),
'band1_float32_noct_epsg4326.tif')
myFileInfo = QFileInfo(myPath)
myBaseName = myFileInfo.baseName()
layer = QgsRasterLayer(myPath, myBaseName)
renderer = QgsSingleBandGrayRenderer(layer.dataProvider(), 2)
image = layer.previewAsImage(QSize(400, 400))
self.assertFalse(image.isNull())
self.assertTrue(image.save(os.path.join(temp_dir.path(), 'expected.png'), "PNG"))
layer.setDataSource(myPath.replace('4326.tif', '4326-BAD_SOURCE.tif'), 'bad_layer', 'gdal', options)
self.assertFalse(layer.isValid())
image = layer.previewAsImage(QSize(400, 400))
self.assertTrue(image.isNull())
layer.setDataSource(myPath.replace('4326-BAD_SOURCE.tif', '4326.tif'), 'bad_layer', 'gdal', options)
self.assertTrue(layer.isValid())
image = layer.previewAsImage(QSize(400, 400))
self.assertFalse(image.isNull())
self.assertTrue(image.save(os.path.join(temp_dir.path(), 'actual.png'), "PNG"))
self.assertTrue(filecmp.cmp(os.path.join(temp_dir.path(), 'actual.png'), os.path.join(temp_dir.path(), 'expected.png')), False)
if __name__ == '__main__':
unittest.main()

View File

@ -20,6 +20,7 @@ from qgis.core import (QgsVectorLayer,
QgsGeometry,
QgsPointXY,
QgsAttributeEditorElement,
QgsAttributeEditorRelation,
QgsProject
)
from utilities import unitTestDataPath
@ -162,7 +163,10 @@ class TestQgsRelation(unittest.TestCase):
def testValidRelationAfterChangingStyle(self):
# load project
myPath = os.path.join(unitTestDataPath(), 'relations.qgs')
QgsProject.instance().read(myPath)
p = QgsProject.instance()
self.assertTrue(p.read(myPath))
for l in p.mapLayers().values():
self.assertTrue(l.isValid())
# get referenced layer
relations = QgsProject.instance().relationManager().relations()
@ -171,6 +175,7 @@ class TestQgsRelation(unittest.TestCase):
# check that the relation is valid
valid = False
self.assertEqual(len(referencedLayer.editFormConfig().tabs()[0].children()), 7)
for tab in referencedLayer.editFormConfig().tabs():
for t in tab.children():
if (t.type() == QgsAttributeEditorElement.AeTypeRelation):
@ -180,6 +185,11 @@ class TestQgsRelation(unittest.TestCase):
# update style
referencedLayer.styleManager().setCurrentStyle("custom")
for l in p.mapLayers().values():
self.assertTrue(l.isValid())
self.assertEqual(len(referencedLayer.editFormConfig().tabs()[0].children()), 7)
# check that the relation is still valid
referencedLayer = relation.referencedLayer()
valid = False

View File

@ -0,0 +1,6 @@
0.00000055284657534
0
0
-0.00000055284657534
2.8182844964232876
41.98181469976164948

Binary file not shown.

View File

@ -0,0 +1,36 @@
<PAMDataset>
<PAMRasterBand band="1">
<Histograms>
<HistItem>
<HistMin>-0.498046875</HistMin>
<HistMax>255.498046875</HistMax>
<BucketCount>256</BucketCount>
<IncludeOutOfRange>0</IncludeOutOfRange>
<Approximate>0</Approximate>
<HistCounts>13|18|50|12|7|13|16|15|14|20|6|13|13|8|9|58|31|15|21|16|15|25|51|20|19|22|16|47|30|20|29|32|22|38|24|32|32|39|35|47|70|68|52|35|121|46|42|60|53|37|39|44|49|83|52|63|54|67|77|73|61|72|56|76|63|73|72|76|99|69|93|90|80|85|92|110|100|106|112|109|100|99|92|113|137|151|136|129|182|156|152|146|126|147|162|163|195|157|145|202|219|164|180|175|213|187|206|193|195|214|211|208|192|211|191|925|454|420|397|455|428|333|414|313|402|378|392|418|364|400|427|420|453|444|477|403|447|513|438|537|510|527|530|544|570|546|619|530|595|651|597|660|738|714|668|876|828|20253|2012|1713|1589|1342|1401|1213|1242|1274|1335|1360|1307|1406|1363|1342|3316|1661|1674|1552|1719|1705|1667|1638|1449|1642|1468|1484|1550|1517|1605|1545|1617|1599|1683|2325|2934|3369|3918|4835|5909|7068|7857|148605|9058|10029|10103|9572|9936|10530|10904|11245|10726|10940|10999|11303|11583|12851|13863|15968|20827|1929455|12350|9950|9683|9843|10560|14977|666396|5951|5178|4871|4578|4364|4380|4142|4039|3754|3589|4403|3554|3073|2813|2513|2423|2209|2196|2167|2204|2215|2434|115706|1815|1722|1771|1920|2175|2521|3127|120133</HistCounts>
</HistItem>
</Histograms>
<Metadata>
<MDI key="STATISTICS_MAXIMUM">255</MDI>
<MDI key="STATISTICS_MEAN">217.8975575084</MDI>
<MDI key="STATISTICS_MINIMUM">0</MDI>
<MDI key="STATISTICS_STDDEV">16.402309158646</MDI>
</Metadata>
</PAMRasterBand>
<PAMRasterBand band="2">
<Metadata>
<MDI key="STATISTICS_MAXIMUM">255</MDI>
<MDI key="STATISTICS_MEAN">211.56457158206</MDI>
<MDI key="STATISTICS_MINIMUM">0</MDI>
<MDI key="STATISTICS_STDDEV">19.453039894122</MDI>
</Metadata>
</PAMRasterBand>
<PAMRasterBand band="3">
<Metadata>
<MDI key="STATISTICS_MAXIMUM">255</MDI>
<MDI key="STATISTICS_MEAN">203.74267168054</MDI>
<MDI key="STATISTICS_MINIMUM">8</MDI>
<MDI key="STATISTICS_STDDEV">21.479123268522</MDI>
</Metadata>
</PAMRasterBand>
</PAMDataset>

Binary file not shown.

View File

@ -0,0 +1,832 @@
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
<qgis version="3.5.0-Master" projectname="">
<homePath path=""/>
<title></title>
<autotransaction active="0"/>
<evaluateDefaultValues active="0"/>
<trust active="0"/>
<projectCrs>
<spatialrefsys>
<proj4>+proj=longlat +datum=WGS84 +no_defs</proj4>
<srsid>3452</srsid>
<srid>4326</srid>
<authid>EPSG:4326</authid>
<description>WGS 84</description>
<projectionacronym>longlat</projectionacronym>
<ellipsoidacronym>WGS84</ellipsoidacronym>
<geographicflag>true</geographicflag>
</spatialrefsys>
</projectCrs>
<layer-tree-group>
<customproperties/>
<layer-tree-layer providerKey="ogr" id="point_b_d23a7df9_c9d6_4b48_9162_5fc1a7db2b96" name="point_b" checked="Qt::Checked" source="./bad_layers_test.gpkg|layername=point_b" expanded="1">
<customproperties/>
</layer-tree-layer>
<layer-tree-layer providerKey="ogr" id="point_a_e99cf1b1_e13e_44a8_b912_58505e7ac967" name="point_a" checked="Qt::Checked" source="./bad_layers_test.gpkg|layername=point_a" expanded="1">
<customproperties/>
</layer-tree-layer>
<layer-tree-layer providerKey="gdal" id="bad_layer_raster_test_18978e96_6781_4a5d_b0bc_474994ed231a" name="bad_layer_raster_test" checked="Qt::Checked" source="./bad_layer_raster_test.tiff" expanded="1">
<customproperties/>
</layer-tree-layer>
<custom-order enabled="0">
<item>point_a_e99cf1b1_e13e_44a8_b912_58505e7ac967</item>
<item>point_b_d23a7df9_c9d6_4b48_9162_5fc1a7db2b96</item>
<item>bad_layer_raster_test_18978e96_6781_4a5d_b0bc_474994ed231a</item>
</custom-order>
</layer-tree-group>
<snapping-settings unit="1" enabled="0" mode="2" intersection-snapping="0" type="1" tolerance="12">
<individual-layer-settings>
<layer-setting id="point_b_d23a7df9_c9d6_4b48_9162_5fc1a7db2b96" enabled="0" units="1" type="1" tolerance="12"/>
<layer-setting id="point_a_e99cf1b1_e13e_44a8_b912_58505e7ac967" enabled="0" units="1" type="1" tolerance="12"/>
</individual-layer-settings>
</snapping-settings>
<relations>
<relation id="point_a_e9_point_b_ref_point_b_d2_fid" name="point a to b" strength="Association" referencingLayer="point_a_e99cf1b1_e13e_44a8_b912_58505e7ac967" referencedLayer="point_b_d23a7df9_c9d6_4b48_9162_5fc1a7db2b96">
<fieldRef referencingField="point_b_ref" referencedField="fid"/>
</relation>
</relations>
<mapcanvas name="theMapCanvas" annotationsVisible="1">
<units>degrees</units>
<extent>
<xmin>2.81828421961071651</xmin>
<ymin>41.9812628573046851</ymin>
<xmax>2.82010032075159867</xmax>
<ymax>41.9817810775760023</ymax>
</extent>
<rotation>0</rotation>
<destinationsrs>
<spatialrefsys>
<proj4>+proj=longlat +datum=WGS84 +no_defs</proj4>
<srsid>3452</srsid>
<srid>4326</srid>
<authid>EPSG:4326</authid>
<description>WGS 84</description>
<projectionacronym>longlat</projectionacronym>
<ellipsoidacronym>WGS84</ellipsoidacronym>
<geographicflag>true</geographicflag>
</spatialrefsys>
</destinationsrs>
<rendermaptile>0</rendermaptile>
</mapcanvas>
<projectModels/>
<legend updateDrawingOrder="true">
<legendlayer name="point_b" checked="Qt::Checked" drawingOrder="-1" showFeatureCount="0" open="true">
<filegroup open="true" hidden="false">
<legendlayerfile visible="1" layerid="point_b_d23a7df9_c9d6_4b48_9162_5fc1a7db2b96" isInOverview="0"/>
</filegroup>
</legendlayer>
<legendlayer name="point_a" checked="Qt::Checked" drawingOrder="-1" showFeatureCount="0" open="true">
<filegroup open="true" hidden="false">
<legendlayerfile visible="1" layerid="point_a_e99cf1b1_e13e_44a8_b912_58505e7ac967" isInOverview="0"/>
</filegroup>
</legendlayer>
<legendlayer name="bad_layer_raster_test" checked="Qt::Checked" drawingOrder="-1" showFeatureCount="0" open="true">
<filegroup open="true" hidden="false">
<legendlayerfile visible="1" layerid="bad_layer_raster_test_18978e96_6781_4a5d_b0bc_474994ed231a" isInOverview="0"/>
</filegroup>
</legendlayer>
</legend>
<mapViewDocks/>
<mapViewDocks3D/>
<projectlayers>
<maplayer autoRefreshTime="0" minScale="1e+8" autoRefreshEnabled="0" maxScale="0" refreshOnNotifyMessage="" hasScaleBasedVisibilityFlag="0" type="raster" refreshOnNotifyEnabled="0" styleCategories="AllStyleCategories">
<extent>
<xmin>2.81828421999999978</xmin>
<ymin>41.98122895881507333</ymin>
<xmax>2.82010032099999153</xmax>
<ymax>41.98181497618493552</ymax>
</extent>
<id>bad_layer_raster_test_18978e96_6781_4a5d_b0bc_474994ed231a</id>
<datasource>./bad_layer_raster_test.tiff</datasource>
<keywordList>
<value></value>
</keywordList>
<layername>bad_layer_raster_test</layername>
<srs>
<spatialrefsys>
<proj4>+proj=longlat +datum=WGS84 +no_defs</proj4>
<srsid>3452</srsid>
<srid>4326</srid>
<authid>EPSG:4326</authid>
<description>WGS 84</description>
<projectionacronym>longlat</projectionacronym>
<ellipsoidacronym>WGS84</ellipsoidacronym>
<geographicflag>true</geographicflag>
</spatialrefsys>
</srs>
<resourceMetadata>
<identifier></identifier>
<parentidentifier></parentidentifier>
<language></language>
<type></type>
<title></title>
<abstract></abstract>
<contact>
<name></name>
<organization></organization>
<position></position>
<voice></voice>
<fax></fax>
<email></email>
<role></role>
</contact>
<links/>
<fees></fees>
<encoding></encoding>
<crs>
<spatialrefsys>
<proj4></proj4>
<srsid>0</srsid>
<srid>0</srid>
<authid></authid>
<description></description>
<projectionacronym></projectionacronym>
<ellipsoidacronym></ellipsoidacronym>
<geographicflag>true</geographicflag>
</spatialrefsys>
</crs>
<extent>
<spatial minz="0" maxx="0" maxy="0" dimensions="2" crs="" minx="0" miny="0" maxz="0"/>
<temporal>
<period>
<start></start>
<end></end>
</period>
</temporal>
</extent>
</resourceMetadata>
<provider>gdal</provider>
<noData>
<noDataList bandNo="1" useSrcNoData="0"/>
<noDataList bandNo="2" useSrcNoData="0"/>
<noDataList bandNo="3" useSrcNoData="0"/>
<noDataList bandNo="4" useSrcNoData="0"/>
</noData>
<map-layer-style-manager current="default">
<map-layer-style name="default"/>
</map-layer-style-manager>
<flags>
<Identifiable>1</Identifiable>
<Removable>1</Removable>
<Searchable>1</Searchable>
</flags>
<customproperties>
<property value="false" key="WMSBackgroundLayer"/>
<property value="false" key="WMSPublishDataSourceUrl"/>
<property value="0" key="embeddedWidgets/count"/>
<property value="Value" key="identify/format"/>
</customproperties>
<pipe>
<rasterrenderer grayBand="1" opacity="1" alphaBand="4" gradient="BlackToWhite" type="singlebandgray">
<rasterTransparency/>
<minMaxOrigin>
<limits>MinMax</limits>
<extent>WholeRaster</extent>
<statAccuracy>Estimated</statAccuracy>
<cumulativeCutLower>0.02</cumulativeCutLower>
<cumulativeCutUpper>0.98</cumulativeCutUpper>
<stdDevFactor>2</stdDevFactor>
</minMaxOrigin>
<contrastEnhancement>
<minValue>0</minValue>
<maxValue>255</maxValue>
<algorithm>StretchToMinimumMaximum</algorithm>
</contrastEnhancement>
</rasterrenderer>
<brightnesscontrast brightness="0" contrast="0"/>
<huesaturation colorizeGreen="128" colorizeStrength="100" colorizeBlue="128" saturation="0" colorizeRed="255" colorizeOn="0" grayscaleMode="0"/>
<rasterresampler maxOversampling="2"/>
</pipe>
<blendMode>0</blendMode>
</maplayer>
<maplayer autoRefreshTime="0" geometry="Point" minScale="1e+8" labelsEnabled="0" autoRefreshEnabled="0" maxScale="0" refreshOnNotifyMessage="" simplifyAlgorithm="0" simplifyDrawingTol="1" simplifyMaxScale="1" hasScaleBasedVisibilityFlag="0" type="vector" simplifyDrawingHints="0" refreshOnNotifyEnabled="0" styleCategories="AllStyleCategories" readOnly="0" simplifyLocal="1">
<extent>
<xmin>2.81884431838989258</xmin>
<ymin>41.9814453125</ymin>
<xmax>2.81894969940185547</xmax>
<ymax>41.98154067993164063</ymax>
</extent>
<id>point_a_e99cf1b1_e13e_44a8_b912_58505e7ac967</id>
<datasource>./bad_layers_test.gpkg|layername=point_a</datasource>
<keywordList>
<value></value>
</keywordList>
<layername>point_a</layername>
<srs>
<spatialrefsys>
<proj4>+proj=longlat +datum=WGS84 +no_defs</proj4>
<srsid>3452</srsid>
<srid>4326</srid>
<authid>EPSG:4326</authid>
<description>WGS 84</description>
<projectionacronym>longlat</projectionacronym>
<ellipsoidacronym>WGS84</ellipsoidacronym>
<geographicflag>true</geographicflag>
</spatialrefsys>
</srs>
<resourceMetadata>
<identifier></identifier>
<parentidentifier></parentidentifier>
<language></language>
<type></type>
<title></title>
<abstract></abstract>
<contact>
<name></name>
<organization></organization>
<position></position>
<voice></voice>
<fax></fax>
<email></email>
<role></role>
</contact>
<links/>
<fees></fees>
<encoding></encoding>
<crs>
<spatialrefsys>
<proj4></proj4>
<srsid>0</srsid>
<srid>0</srid>
<authid></authid>
<description></description>
<projectionacronym></projectionacronym>
<ellipsoidacronym></ellipsoidacronym>
<geographicflag>false</geographicflag>
</spatialrefsys>
</crs>
<extent>
<spatial minz="0" maxx="0" maxy="0" dimensions="2" crs="" minx="0" miny="0" maxz="0"/>
<temporal>
<period>
<start></start>
<end></end>
</period>
</temporal>
</extent>
</resourceMetadata>
<provider encoding="UTF-8">ogr</provider>
<vectorjoins/>
<layerDependencies/>
<dataDependencies/>
<legend type="default-vector"/>
<expressionfields/>
<map-layer-style-manager current="default">
<map-layer-style name="default"/>
</map-layer-style-manager>
<auxiliaryLayer/>
<flags>
<Identifiable>1</Identifiable>
<Removable>1</Removable>
<Searchable>1</Searchable>
</flags>
<renderer-v2 forceraster="0" symbollevels="0" enableorderby="0" type="singleSymbol">
<symbols>
<symbol alpha="1" name="0" clip_to_extent="1" type="marker">
<layer locked="0" pass="0" enabled="1" class="SimpleMarker">
<prop k="angle" v="0"/>
<prop k="color" v="219,30,42,255"/>
<prop k="horizontal_anchor_point" v="1"/>
<prop k="joinstyle" v="bevel"/>
<prop k="name" v="diamond"/>
<prop k="offset" v="0,0"/>
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="offset_unit" v="MM"/>
<prop k="outline_color" v="128,17,25,255"/>
<prop k="outline_style" v="solid"/>
<prop k="outline_width" v="0.4"/>
<prop k="outline_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="outline_width_unit" v="MM"/>
<prop k="scale_method" v="diameter"/>
<prop k="size" v="4.4"/>
<prop k="size_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="size_unit" v="MM"/>
<prop k="vertical_anchor_point" v="1"/>
<data_defined_properties>
<Option type="Map">
<Option value="" name="name" type="QString"/>
<Option name="properties"/>
<Option value="collection" name="type" type="QString"/>
</Option>
</data_defined_properties>
</layer>
</symbol>
</symbols>
<rotation/>
<sizescale/>
</renderer-v2>
<customproperties>
<property value="0" key="embeddedWidgets/count"/>
<property key="variableNames"/>
<property key="variableValues"/>
</customproperties>
<blendMode>0</blendMode>
<featureBlendMode>0</featureBlendMode>
<layerOpacity>1</layerOpacity>
<SingleCategoryDiagramRenderer diagramType="Histogram" attributeLegend="1">
<DiagramCategory enabled="0" labelPlacementMethod="XHeight" lineSizeScale="3x:0,0,0,0,0,0" minimumSize="0" penAlpha="255" lineSizeType="MM" minScaleDenominator="0" sizeScale="3x:0,0,0,0,0,0" height="15" maxScaleDenominator="1e+8" opacity="1" barWidth="5" diagramOrientation="Up" width="15" scaleBasedVisibility="0" backgroundColor="#ffffff" scaleDependency="Area" sizeType="MM" rotationOffset="270" penColor="#000000" penWidth="0" backgroundAlpha="255">
<fontProperties description="Noto Sans,9,-1,5,50,0,0,0,0,0" style=""/>
<attribute color="#000000" label="" field=""/>
</DiagramCategory>
</SingleCategoryDiagramRenderer>
<DiagramLayerSettings priority="0" obstacle="0" placement="0" zIndex="0" showAll="1" linePlacementFlags="18" dist="0">
<properties>
<Option type="Map">
<Option value="" name="name" type="QString"/>
<Option name="properties"/>
<Option value="collection" name="type" type="QString"/>
</Option>
</properties>
</DiagramLayerSettings>
<geometryOptions geometryPrecision="0" removeDuplicateNodes="0">
<activeChecks/>
<checkConfiguration/>
</geometryOptions>
<fieldConfiguration>
<field name="fid">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field name="name">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field name="point_b_ref">
<editWidget type="RelationReference">
<config>
<Option type="Map">
<Option value="false" name="AllowAddFeatures" type="bool"/>
<Option value="false" name="AllowNULL" type="bool"/>
<Option value="false" name="MapIdentification" type="bool"/>
<Option value="false" name="OrderByValue" type="bool"/>
<Option value="false" name="ReadOnly" type="bool"/>
<Option value="point_a_e9_point_b_ref_point_b_d2_fid" name="Relation" type="QString"/>
<Option value="false" name="ShowForm" type="bool"/>
<Option value="true" name="ShowOpenFormButton" type="bool"/>
</Option>
</config>
</editWidget>
</field>
</fieldConfiguration>
<aliases>
<alias index="0" name="" field="fid"/>
<alias index="1" name="" field="name"/>
<alias index="2" name="" field="point_b_ref"/>
</aliases>
<excludeAttributesWMS/>
<excludeAttributesWFS/>
<defaults>
<default applyOnUpdate="0" expression="" field="fid"/>
<default applyOnUpdate="0" expression="" field="name"/>
<default applyOnUpdate="0" expression="" field="point_b_ref"/>
</defaults>
<constraints>
<constraint constraints="3" exp_strength="0" unique_strength="1" notnull_strength="1" field="fid"/>
<constraint constraints="0" exp_strength="0" unique_strength="0" notnull_strength="0" field="name"/>
<constraint constraints="0" exp_strength="0" unique_strength="0" notnull_strength="0" field="point_b_ref"/>
</constraints>
<constraintExpressions>
<constraint exp="" field="fid" desc=""/>
<constraint exp="" field="name" desc=""/>
<constraint exp="" field="point_b_ref" desc=""/>
</constraintExpressions>
<expressionfields/>
<attributeactions>
<defaultAction value="{00000000-0000-0000-0000-000000000000}" key="Canvas"/>
</attributeactions>
<attributetableconfig actionWidgetStyle="dropDown" sortExpression="" sortOrder="0">
<columns>
<column width="-1" name="fid" type="field" hidden="0"/>
<column width="-1" name="name" type="field" hidden="0"/>
<column width="-1" name="point_b_ref" type="field" hidden="0"/>
<column width="-1" type="actions" hidden="1"/>
</columns>
</attributetableconfig>
<conditionalstyles>
<rowstyles/>
<fieldstyles/>
</conditionalstyles>
<editform tolerant="1"></editform>
<editforminit/>
<editforminitcodesource>0</editforminitcodesource>
<editforminitfilepath></editforminitfilepath>
<editforminitcode><![CDATA[# -*- coding: utf-8 -*-
"""
QGIS forms can have a Python function that is called when the form is
opened.
Use this function to add extra logic to your forms.
Enter the name of the function in the "Python Init function"
field.
An example follows:
"""
from qgis.PyQt.QtWidgets import QWidget
def my_form_open(dialog, layer, feature):
geom = feature.geometry()
control = dialog.findChild(QWidget, "MyLineEdit")
]]></editforminitcode>
<featformsuppress>0</featformsuppress>
<editorlayout>generatedlayout</editorlayout>
<editable>
<field name="fid" editable="1"/>
<field name="name" editable="1"/>
<field name="point_b_ref" editable="1"/>
</editable>
<labelOnTop>
<field labelOnTop="0" name="fid"/>
<field labelOnTop="0" name="name"/>
<field labelOnTop="0" name="point_b_ref"/>
</labelOnTop>
<widgets/>
<previewExpression>fid</previewExpression>
<mapTip></mapTip>
</maplayer>
<maplayer autoRefreshTime="0" geometry="Point" minScale="1e+8" labelsEnabled="0" autoRefreshEnabled="0" maxScale="0" refreshOnNotifyMessage="" simplifyAlgorithm="0" simplifyDrawingTol="1" simplifyMaxScale="1" hasScaleBasedVisibilityFlag="0" type="vector" simplifyDrawingHints="0" refreshOnNotifyEnabled="0" styleCategories="AllStyleCategories" readOnly="0" simplifyLocal="1">
<extent>
<xmin>2.81895375251770108</xmin>
<ymin>41.98152542114257813</ymin>
<xmax>2.81904959678649902</xmax>
<ymax>41.981597900390625</ymax>
</extent>
<id>point_b_d23a7df9_c9d6_4b48_9162_5fc1a7db2b96</id>
<datasource>./bad_layers_test.gpkg|layername=point_b</datasource>
<keywordList>
<value></value>
</keywordList>
<layername>point_b</layername>
<srs>
<spatialrefsys>
<proj4>+proj=longlat +datum=WGS84 +no_defs</proj4>
<srsid>3452</srsid>
<srid>4326</srid>
<authid>EPSG:4326</authid>
<description>WGS 84</description>
<projectionacronym>longlat</projectionacronym>
<ellipsoidacronym>WGS84</ellipsoidacronym>
<geographicflag>true</geographicflag>
</spatialrefsys>
</srs>
<resourceMetadata>
<identifier></identifier>
<parentidentifier></parentidentifier>
<language></language>
<type></type>
<title></title>
<abstract></abstract>
<contact>
<name></name>
<organization></organization>
<position></position>
<voice></voice>
<fax></fax>
<email></email>
<role></role>
</contact>
<links/>
<fees></fees>
<encoding></encoding>
<crs>
<spatialrefsys>
<proj4></proj4>
<srsid>0</srsid>
<srid>0</srid>
<authid></authid>
<description></description>
<projectionacronym></projectionacronym>
<ellipsoidacronym></ellipsoidacronym>
<geographicflag>false</geographicflag>
</spatialrefsys>
</crs>
<extent>
<spatial minz="0" maxx="0" maxy="0" dimensions="2" crs="" minx="0" miny="0" maxz="0"/>
<temporal>
<period>
<start></start>
<end></end>
</period>
</temporal>
</extent>
</resourceMetadata>
<provider encoding="UTF-8">ogr</provider>
<vectorjoins/>
<layerDependencies/>
<dataDependencies/>
<legend type="default-vector"/>
<expressionfields/>
<map-layer-style-manager current="default">
<map-layer-style name="default"/>
</map-layer-style-manager>
<auxiliaryLayer/>
<flags>
<Identifiable>1</Identifiable>
<Removable>1</Removable>
<Searchable>1</Searchable>
</flags>
<renderer-v2 forceraster="0" symbollevels="0" enableorderby="0" type="singleSymbol">
<symbols>
<symbol alpha="1" name="0" clip_to_extent="1" type="marker">
<layer locked="0" pass="0" enabled="1" class="SimpleMarker">
<prop k="angle" v="0"/>
<prop k="color" v="84,176,74,255"/>
<prop k="horizontal_anchor_point" v="1"/>
<prop k="joinstyle" v="bevel"/>
<prop k="name" v="diamond"/>
<prop k="offset" v="0,0"/>
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="offset_unit" v="MM"/>
<prop k="outline_color" v="61,128,53,255"/>
<prop k="outline_style" v="solid"/>
<prop k="outline_width" v="0.4"/>
<prop k="outline_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="outline_width_unit" v="MM"/>
<prop k="scale_method" v="diameter"/>
<prop k="size" v="4.4"/>
<prop k="size_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="size_unit" v="MM"/>
<prop k="vertical_anchor_point" v="1"/>
<data_defined_properties>
<Option type="Map">
<Option value="" name="name" type="QString"/>
<Option name="properties"/>
<Option value="collection" name="type" type="QString"/>
</Option>
</data_defined_properties>
</layer>
</symbol>
</symbols>
<rotation/>
<sizescale/>
</renderer-v2>
<customproperties>
<property value="0" key="embeddedWidgets/count"/>
<property key="variableNames"/>
<property key="variableValues"/>
</customproperties>
<blendMode>0</blendMode>
<featureBlendMode>0</featureBlendMode>
<layerOpacity>1</layerOpacity>
<SingleCategoryDiagramRenderer diagramType="Histogram" attributeLegend="1">
<DiagramCategory enabled="0" labelPlacementMethod="XHeight" lineSizeScale="3x:0,0,0,0,0,0" minimumSize="0" penAlpha="255" lineSizeType="MM" minScaleDenominator="0" sizeScale="3x:0,0,0,0,0,0" height="15" maxScaleDenominator="1e+8" opacity="1" barWidth="5" diagramOrientation="Up" width="15" scaleBasedVisibility="0" backgroundColor="#ffffff" scaleDependency="Area" sizeType="MM" rotationOffset="270" penColor="#000000" penWidth="0" backgroundAlpha="255">
<fontProperties description="Noto Sans,9,-1,5,50,0,0,0,0,0" style=""/>
<attribute color="#000000" label="" field=""/>
</DiagramCategory>
</SingleCategoryDiagramRenderer>
<DiagramLayerSettings priority="0" obstacle="0" placement="0" zIndex="0" showAll="1" linePlacementFlags="18" dist="0">
<properties>
<Option type="Map">
<Option value="" name="name" type="QString"/>
<Option name="properties"/>
<Option value="collection" name="type" type="QString"/>
</Option>
</properties>
</DiagramLayerSettings>
<geometryOptions geometryPrecision="0" removeDuplicateNodes="0">
<activeChecks/>
<checkConfiguration/>
</geometryOptions>
<fieldConfiguration>
<field name="fid">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field name="name">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
</fieldConfiguration>
<aliases>
<alias index="0" name="" field="fid"/>
<alias index="1" name="" field="name"/>
</aliases>
<excludeAttributesWMS/>
<excludeAttributesWFS/>
<defaults>
<default applyOnUpdate="0" expression="" field="fid"/>
<default applyOnUpdate="0" expression="" field="name"/>
</defaults>
<constraints>
<constraint constraints="3" exp_strength="0" unique_strength="1" notnull_strength="1" field="fid"/>
<constraint constraints="0" exp_strength="0" unique_strength="0" notnull_strength="0" field="name"/>
</constraints>
<constraintExpressions>
<constraint exp="" field="fid" desc=""/>
<constraint exp="" field="name" desc=""/>
</constraintExpressions>
<expressionfields/>
<attributeactions>
<defaultAction value="{00000000-0000-0000-0000-000000000000}" key="Canvas"/>
</attributeactions>
<attributetableconfig actionWidgetStyle="dropDown" sortExpression="" sortOrder="0">
<columns>
<column width="-1" name="fid" type="field" hidden="0"/>
<column width="-1" name="name" type="field" hidden="0"/>
<column width="-1" type="actions" hidden="1"/>
</columns>
</attributetableconfig>
<conditionalstyles>
<rowstyles/>
<fieldstyles/>
</conditionalstyles>
<editform tolerant="1"></editform>
<editforminit/>
<editforminitcodesource>0</editforminitcodesource>
<editforminitfilepath></editforminitfilepath>
<editforminitcode><![CDATA[# -*- coding: utf-8 -*-
"""
QGIS forms can have a Python function that is called when the form is
opened.
Use this function to add extra logic to your forms.
Enter the name of the function in the "Python Init function"
field.
An example follows:
"""
from qgis.PyQt.QtWidgets import QWidget
def my_form_open(dialog, layer, feature):
geom = feature.geometry()
control = dialog.findChild(QWidget, "MyLineEdit")
]]></editforminitcode>
<featformsuppress>0</featformsuppress>
<editorlayout>generatedlayout</editorlayout>
<editable>
<field name="fid" editable="1"/>
<field name="name" editable="1"/>
</editable>
<labelOnTop>
<field labelOnTop="0" name="fid"/>
<field labelOnTop="0" name="name"/>
</labelOnTop>
<widgets>
<widget name="point_a_e9_point_b_ref_point_b_d2_fid">
<config type="Map">
<Option value="" name="nm-rel" type="QString"/>
</config>
</widget>
</widgets>
<previewExpression>fid</previewExpression>
<mapTip></mapTip>
</maplayer>
</projectlayers>
<layerorder>
<layer id="point_a_e99cf1b1_e13e_44a8_b912_58505e7ac967"/>
<layer id="point_b_d23a7df9_c9d6_4b48_9162_5fc1a7db2b96"/>
<layer id="bad_layer_raster_test_18978e96_6781_4a5d_b0bc_474994ed231a"/>
</layerorder>
<properties>
<Macros>
<pythonCode type="QString"></pythonCode>
</Macros>
<WFSTLayers>
<Delete type="QStringList"/>
<Insert type="QStringList"/>
<Update type="QStringList"/>
</WFSTLayers>
<WMTSMinScale type="int">5000</WMTSMinScale>
<WMSAddWktGeometry type="bool">false</WMSAddWktGeometry>
<Legend>
<filterByMap type="bool">false</filterByMap>
</Legend>
<WFSLayers type="QStringList"/>
<WMSPrecision type="QString">8</WMSPrecision>
<WMTSUrl type="QString"></WMTSUrl>
<WMSFees type="QString">conditions unknown</WMSFees>
<PositionPrecision>
<Automatic type="bool">true</Automatic>
<DecimalPlaces type="int">2</DecimalPlaces>
<DegreeFormat type="QString">MU</DegreeFormat>
</PositionPrecision>
<WMSImageQuality type="int">90</WMSImageQuality>
<Measure>
<Ellipsoid type="QString">WGS84</Ellipsoid>
</Measure>
<SpatialRefSys>
<ProjectionsEnabled type="int">1</ProjectionsEnabled>
</SpatialRefSys>
<WMTSPngLayers>
<Layer type="QStringList"/>
<Project type="bool">false</Project>
<Group type="QStringList"/>
</WMTSPngLayers>
<WMSAccessConstraints type="QString">None</WMSAccessConstraints>
<WMSSegmentizeFeatureInfoGeometry type="bool">false</WMSSegmentizeFeatureInfoGeometry>
<WMSRequestDefinedDataSources type="bool">false</WMSRequestDefinedDataSources>
<WMSServiceCapabilities type="bool">false</WMSServiceCapabilities>
<WMSUrl type="QString"></WMSUrl>
<WCSLayers type="QStringList"/>
<WMSContactPerson type="QString"></WMSContactPerson>
<WMSContactOrganization type="QString"></WMSContactOrganization>
<Identify>
<disabledLayers type="QStringList"/>
</Identify>
<DefaultStyles>
<Fill type="QString"></Fill>
<Line type="QString"></Line>
<Opacity type="double">1</Opacity>
<RandomColors type="bool">true</RandomColors>
<Marker type="QString"></Marker>
<ColorRamp type="QString"></ColorRamp>
</DefaultStyles>
<WMSKeywordList type="QStringList">
<value></value>
</WMSKeywordList>
<WFSUrl type="QString"></WFSUrl>
<WMTSJpegLayers>
<Layer type="QStringList"/>
<Project type="bool">false</Project>
<Group type="QStringList"/>
</WMTSJpegLayers>
<Paths>
<Absolute type="bool">false</Absolute>
</Paths>
<WMSServiceAbstract type="QString"></WMSServiceAbstract>
<WMSUseLayerIDs type="bool">false</WMSUseLayerIDs>
<WMSServiceTitle type="QString"></WMSServiceTitle>
<PAL>
<DrawOutlineLabels type="bool">true</DrawOutlineLabels>
<CandidatesPolygon type="int">30</CandidatesPolygon>
<ShowingPartialsLabels type="bool">true</ShowingPartialsLabels>
<CandidatesLine type="int">50</CandidatesLine>
<SearchMethod type="int">0</SearchMethod>
<CandidatesPoint type="int">16</CandidatesPoint>
<ShowingCandidates type="bool">false</ShowingCandidates>
<ShowingAllLabels type="bool">false</ShowingAllLabels>
<DrawRectOnly type="bool">false</DrawRectOnly>
</PAL>
<Measurement>
<AreaUnits type="QString">m2</AreaUnits>
<DistanceUnits type="QString">meters</DistanceUnits>
</Measurement>
<WMSOnlineResource type="QString"></WMSOnlineResource>
<WCSUrl type="QString"></WCSUrl>
<WMTSLayers>
<Layer type="QStringList"/>
<Project type="bool">false</Project>
<Group type="QStringList"/>
</WMTSLayers>
<WMSContactPhone type="QString"></WMSContactPhone>
<RequiredLayers>
<Layers type="QStringList"/>
</RequiredLayers>
<Variables>
<variableNames type="QStringList">
<value>qgisce_catalog_autoload</value>
<value>qgisce_template_version</value>
</variableNames>
<variableValues type="QStringList">
<value>true</value>
<value>1.0</value>
</variableValues>
</Variables>
<WMSContactMail type="QString"></WMSContactMail>
<Gui>
<CanvasColorBluePart type="int">255</CanvasColorBluePart>
<SelectionColorAlphaPart type="int">255</SelectionColorAlphaPart>
<SelectionColorRedPart type="int">255</SelectionColorRedPart>
<CanvasColorGreenPart type="int">255</CanvasColorGreenPart>
<SelectionColorBluePart type="int">0</SelectionColorBluePart>
<SelectionColorGreenPart type="int">255</SelectionColorGreenPart>
<CanvasColorRedPart type="int">255</CanvasColorRedPart>
</Gui>
<WMSContactPosition type="QString"></WMSContactPosition>
</properties>
<visibility-presets/>
<transformContext/>
<projectMetadata>
<identifier></identifier>
<parentidentifier></parentidentifier>
<language></language>
<type></type>
<title></title>
<abstract></abstract>
<contact>
<name></name>
<organization></organization>
<position></position>
<voice></voice>
<fax></fax>
<email></email>
<role></role>
</contact>
<links/>
<author>Alessandro Pasotti</author>
<creation>2018-07-06T13:56:35</creation>
</projectMetadata>
<Annotations/>
<Layouts/>
</qgis>

Binary file not shown.

View File

@ -0,0 +1,710 @@
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
<qgis projectname="" version="3.5.0-Master">
<homePath path=""/>
<title></title>
<autotransaction active="0"/>
<evaluateDefaultValues active="0"/>
<trust active="0"/>
<projectCrs>
<spatialrefsys>
<proj4>+proj=utm +zone=30 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs</proj4>
<srsid>2103</srsid>
<srid>25830</srid>
<authid>EPSG:25830</authid>
<description>ETRS89 / UTM zone 30N</description>
<projectionacronym>utm</projectionacronym>
<ellipsoidacronym>GRS80</ellipsoidacronym>
<geographicflag>false</geographicflag>
</spatialrefsys>
</projectCrs>
<layer-tree-group>
<customproperties/>
<layer-tree-layer providerKey="ogr" checked="Qt::Checked" name="point_b" source="./relation_reference_test.gpkg|layername=point_b" expanded="1" id="point_b_d23a7df9_c9d6_4b48_9162_5fc1a7db2b96">
<customproperties/>
</layer-tree-layer>
<layer-tree-layer providerKey="ogr" checked="Qt::Checked" name="point_a" source="./relation_reference_test.gpkg|layername=point_a" expanded="1" id="point_a_e99cf1b1_e13e_44a8_b912_58505e7ac967">
<customproperties/>
</layer-tree-layer>
<custom-order enabled="0">
<item>point_a_e99cf1b1_e13e_44a8_b912_58505e7ac967</item>
<item>point_b_d23a7df9_c9d6_4b48_9162_5fc1a7db2b96</item>
</custom-order>
</layer-tree-group>
<snapping-settings unit="1" enabled="0" type="1" tolerance="12" mode="2" intersection-snapping="0">
<individual-layer-settings>
<layer-setting enabled="0" type="1" tolerance="12" id="point_b_d23a7df9_c9d6_4b48_9162_5fc1a7db2b96" units="1"/>
<layer-setting enabled="0" type="1" tolerance="12" id="point_a_e99cf1b1_e13e_44a8_b912_58505e7ac967" units="1"/>
</individual-layer-settings>
</snapping-settings>
<relations>
<relation strength="Association" referencingLayer="point_a_e99cf1b1_e13e_44a8_b912_58505e7ac967" referencedLayer="point_b_d23a7df9_c9d6_4b48_9162_5fc1a7db2b96" name="point a to b" id="point_a_e9_point_b_ref_point_b_d2_fid">
<fieldRef referencingField="point_b_ref" referencedField="fid"/>
</relation>
</relations>
<mapcanvas annotationsVisible="1" name="theMapCanvas">
<units>meters</units>
<extent>
<xmin>982072.13989259675145149</xmin>
<ymin>4664077.95563993975520134</ymin>
<xmax>982298.28138825763016939</xmax>
<ymax>4664173.45214581862092018</ymax>
</extent>
<rotation>0</rotation>
<destinationsrs>
<spatialrefsys>
<proj4>+proj=utm +zone=30 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs</proj4>
<srsid>2103</srsid>
<srid>25830</srid>
<authid>EPSG:25830</authid>
<description>ETRS89 / UTM zone 30N</description>
<projectionacronym>utm</projectionacronym>
<ellipsoidacronym>GRS80</ellipsoidacronym>
<geographicflag>false</geographicflag>
</spatialrefsys>
</destinationsrs>
<rendermaptile>0</rendermaptile>
</mapcanvas>
<projectModels/>
<legend updateDrawingOrder="true">
<legendlayer showFeatureCount="0" checked="Qt::Checked" name="point_b" drawingOrder="-1" open="true">
<filegroup hidden="false" open="true">
<legendlayerfile visible="1" layerid="point_b_d23a7df9_c9d6_4b48_9162_5fc1a7db2b96" isInOverview="0"/>
</filegroup>
</legendlayer>
<legendlayer showFeatureCount="0" checked="Qt::Checked" name="point_a" drawingOrder="-1" open="true">
<filegroup hidden="false" open="true">
<legendlayerfile visible="1" layerid="point_a_e99cf1b1_e13e_44a8_b912_58505e7ac967" isInOverview="0"/>
</filegroup>
</legendlayer>
</legend>
<mapViewDocks/>
<mapViewDocks3D/>
<projectlayers>
<maplayer autoRefreshTime="0" type="vector" geometry="Point" styleCategories="AllStyleCategories" minScale="1e+8" readOnly="0" simplifyDrawingHints="0" refreshOnNotifyMessage="" labelsEnabled="0" simplifyMaxScale="1" refreshOnNotifyEnabled="0" autoRefreshEnabled="0" simplifyAlgorithm="0" hasScaleBasedVisibilityFlag="0" maxScale="0" simplifyDrawingTol="1" simplifyLocal="1">
<extent>
<xmin>2.81884431838989258</xmin>
<ymin>41.9814453125</ymin>
<xmax>2.81894969940185547</xmax>
<ymax>41.98154067993164063</ymax>
</extent>
<id>point_a_e99cf1b1_e13e_44a8_b912_58505e7ac967</id>
<datasource>./relation_reference_test.gpkg|layername=point_a</datasource>
<keywordList>
<value></value>
</keywordList>
<layername>point_a</layername>
<srs>
<spatialrefsys>
<proj4>+proj=longlat +datum=WGS84 +no_defs</proj4>
<srsid>3452</srsid>
<srid>4326</srid>
<authid>EPSG:4326</authid>
<description>WGS 84</description>
<projectionacronym>longlat</projectionacronym>
<ellipsoidacronym>WGS84</ellipsoidacronym>
<geographicflag>true</geographicflag>
</spatialrefsys>
</srs>
<resourceMetadata>
<identifier></identifier>
<parentidentifier></parentidentifier>
<language></language>
<type></type>
<title></title>
<abstract></abstract>
<contact>
<name></name>
<organization></organization>
<position></position>
<voice></voice>
<fax></fax>
<email></email>
<role></role>
</contact>
<links/>
<fees></fees>
<encoding></encoding>
<crs>
<spatialrefsys>
<proj4></proj4>
<srsid>0</srsid>
<srid>0</srid>
<authid></authid>
<description></description>
<projectionacronym></projectionacronym>
<ellipsoidacronym></ellipsoidacronym>
<geographicflag>false</geographicflag>
</spatialrefsys>
</crs>
<extent>
<spatial miny="0" maxz="0" crs="" maxy="0" dimensions="2" maxx="0" minz="0" minx="0"/>
<temporal>
<period>
<start></start>
<end></end>
</period>
</temporal>
</extent>
</resourceMetadata>
<provider encoding="UTF-8">ogr</provider>
<vectorjoins/>
<layerDependencies/>
<dataDependencies/>
<legend type="default-vector"/>
<expressionfields/>
<map-layer-style-manager current="default">
<map-layer-style name="default"/>
</map-layer-style-manager>
<auxiliaryLayer/>
<flags>
<Identifiable>1</Identifiable>
<Removable>1</Removable>
<Searchable>1</Searchable>
</flags>
<renderer-v2 forceraster="0" enableorderby="0" symbollevels="0" type="singleSymbol">
<symbols>
<symbol alpha="1" type="marker" name="0" clip_to_extent="1">
<layer pass="0" enabled="1" locked="0" class="SimpleMarker">
<prop k="angle" v="0"/>
<prop k="color" v="145,82,45,255"/>
<prop k="horizontal_anchor_point" v="1"/>
<prop k="joinstyle" v="bevel"/>
<prop k="name" v="circle"/>
<prop k="offset" v="0,0"/>
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="offset_unit" v="MM"/>
<prop k="outline_color" v="35,35,35,255"/>
<prop k="outline_style" v="solid"/>
<prop k="outline_width" v="0"/>
<prop k="outline_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="outline_width_unit" v="MM"/>
<prop k="scale_method" v="diameter"/>
<prop k="size" v="2"/>
<prop k="size_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="size_unit" v="MM"/>
<prop k="vertical_anchor_point" v="1"/>
<data_defined_properties>
<Option type="Map">
<Option type="QString" name="name" value=""/>
<Option name="properties"/>
<Option type="QString" name="type" value="collection"/>
</Option>
</data_defined_properties>
</layer>
</symbol>
</symbols>
<rotation/>
<sizescale/>
</renderer-v2>
<customproperties>
<property key="embeddedWidgets/count" value="0"/>
<property key="variableNames"/>
<property key="variableValues"/>
</customproperties>
<blendMode>0</blendMode>
<featureBlendMode>0</featureBlendMode>
<layerOpacity>1</layerOpacity>
<SingleCategoryDiagramRenderer diagramType="Histogram" attributeLegend="1">
<DiagramCategory backgroundColor="#ffffff" scaleBasedVisibility="0" sizeType="MM" width="15" maxScaleDenominator="1e+8" diagramOrientation="Up" backgroundAlpha="255" enabled="0" penAlpha="255" barWidth="5" lineSizeType="MM" height="15" penWidth="0" labelPlacementMethod="XHeight" minScaleDenominator="0" rotationOffset="270" penColor="#000000" lineSizeScale="3x:0,0,0,0,0,0" opacity="1" minimumSize="0" scaleDependency="Area" sizeScale="3x:0,0,0,0,0,0">
<fontProperties description="Noto Sans,9,-1,5,50,0,0,0,0,0" style=""/>
</DiagramCategory>
</SingleCategoryDiagramRenderer>
<DiagramLayerSettings linePlacementFlags="18" showAll="1" priority="0" obstacle="0" zIndex="0" dist="0" placement="0">
<properties>
<Option type="Map">
<Option type="QString" name="name" value=""/>
<Option name="properties"/>
<Option type="QString" name="type" value="collection"/>
</Option>
</properties>
</DiagramLayerSettings>
<geometryOptions geometryPrecision="0" removeDuplicateNodes="0">
<activeChecks/>
<checkConfiguration/>
</geometryOptions>
<fieldConfiguration>
<field name="fid">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field name="name">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field name="point_b_ref">
<editWidget type="RelationReference">
<config>
<Option type="Map">
<Option type="bool" name="AllowAddFeatures" value="false"/>
<Option type="bool" name="AllowNULL" value="false"/>
<Option type="bool" name="MapIdentification" value="false"/>
<Option type="bool" name="OrderByValue" value="false"/>
<Option type="bool" name="ReadOnly" value="false"/>
<Option type="QString" name="Relation" value="point_a_e9_point_b_ref_point_b_d2_fid"/>
<Option type="bool" name="ShowForm" value="false"/>
<Option type="bool" name="ShowOpenFormButton" value="true"/>
</Option>
</config>
</editWidget>
</field>
</fieldConfiguration>
<aliases>
<alias name="" index="0" field="fid"/>
<alias name="" index="1" field="name"/>
<alias name="" index="2" field="point_b_ref"/>
</aliases>
<excludeAttributesWMS/>
<excludeAttributesWFS/>
<defaults>
<default expression="" applyOnUpdate="0" field="fid"/>
<default expression="" applyOnUpdate="0" field="name"/>
<default expression="" applyOnUpdate="0" field="point_b_ref"/>
</defaults>
<constraints>
<constraint exp_strength="0" notnull_strength="1" field="fid" unique_strength="1" constraints="3"/>
<constraint exp_strength="0" notnull_strength="0" field="name" unique_strength="0" constraints="0"/>
<constraint exp_strength="0" notnull_strength="0" field="point_b_ref" unique_strength="0" constraints="0"/>
</constraints>
<constraintExpressions>
<constraint exp="" desc="" field="fid"/>
<constraint exp="" desc="" field="name"/>
<constraint exp="" desc="" field="point_b_ref"/>
</constraintExpressions>
<expressionfields/>
<attributeactions>
<defaultAction key="Canvas" value="{00000000-0000-0000-0000-000000000000}"/>
</attributeactions>
<attributetableconfig sortExpression="" sortOrder="0" actionWidgetStyle="dropDown">
<columns>
<column type="field" hidden="0" name="fid" width="-1"/>
<column type="field" hidden="0" name="name" width="-1"/>
<column type="field" hidden="0" name="point_b_ref" width="-1"/>
<column type="actions" hidden="1" width="-1"/>
</columns>
</attributetableconfig>
<conditionalstyles>
<rowstyles/>
<fieldstyles/>
</conditionalstyles>
<editform tolerant="1"></editform>
<editforminit/>
<editforminitcodesource>0</editforminitcodesource>
<editforminitfilepath></editforminitfilepath>
<editforminitcode><![CDATA[# -*- coding: utf-8 -*-
"""
QGIS forms can have a Python function that is called when the form is
opened.
Use this function to add extra logic to your forms.
Enter the name of the function in the "Python Init function"
field.
An example follows:
"""
from qgis.PyQt.QtWidgets import QWidget
def my_form_open(dialog, layer, feature):
geom = feature.geometry()
control = dialog.findChild(QWidget, "MyLineEdit")
]]></editforminitcode>
<featformsuppress>0</featformsuppress>
<editorlayout>generatedlayout</editorlayout>
<editable>
<field editable="1" name="fid"/>
<field editable="1" name="name"/>
<field editable="1" name="point_b_ref"/>
</editable>
<labelOnTop>
<field name="fid" labelOnTop="0"/>
<field name="name" labelOnTop="0"/>
<field name="point_b_ref" labelOnTop="0"/>
</labelOnTop>
<widgets/>
<previewExpression>fid</previewExpression>
<mapTip></mapTip>
</maplayer>
<maplayer autoRefreshTime="0" type="vector" geometry="Point" styleCategories="AllStyleCategories" minScale="1e+8" readOnly="0" simplifyDrawingHints="0" refreshOnNotifyMessage="" labelsEnabled="0" simplifyMaxScale="1" refreshOnNotifyEnabled="0" autoRefreshEnabled="0" simplifyAlgorithm="0" hasScaleBasedVisibilityFlag="0" maxScale="0" simplifyDrawingTol="1" simplifyLocal="1">
<extent>
<xmin>2.81895381989531746</xmin>
<ymin>41.9815272025749664</ymin>
<xmax>2.81904954416376663</xmax>
<ymax>41.98159776111032926</ymax>
</extent>
<id>point_b_d23a7df9_c9d6_4b48_9162_5fc1a7db2b96</id>
<datasource>./relation_reference_test.gpkg|layername=point_b</datasource>
<keywordList>
<value></value>
</keywordList>
<layername>point_b</layername>
<srs>
<spatialrefsys>
<proj4>+proj=longlat +datum=WGS84 +no_defs</proj4>
<srsid>3452</srsid>
<srid>4326</srid>
<authid>EPSG:4326</authid>
<description>WGS 84</description>
<projectionacronym>longlat</projectionacronym>
<ellipsoidacronym>WGS84</ellipsoidacronym>
<geographicflag>true</geographicflag>
</spatialrefsys>
</srs>
<resourceMetadata>
<identifier></identifier>
<parentidentifier></parentidentifier>
<language></language>
<type></type>
<title></title>
<abstract></abstract>
<contact>
<name></name>
<organization></organization>
<position></position>
<voice></voice>
<fax></fax>
<email></email>
<role></role>
</contact>
<links/>
<fees></fees>
<encoding></encoding>
<crs>
<spatialrefsys>
<proj4></proj4>
<srsid>0</srsid>
<srid>0</srid>
<authid></authid>
<description></description>
<projectionacronym></projectionacronym>
<ellipsoidacronym></ellipsoidacronym>
<geographicflag>false</geographicflag>
</spatialrefsys>
</crs>
<extent>
<spatial miny="0" maxz="0" crs="" maxy="0" dimensions="2" maxx="0" minz="0" minx="0"/>
<temporal>
<period>
<start></start>
<end></end>
</period>
</temporal>
</extent>
</resourceMetadata>
<provider encoding="UTF-8">ogr</provider>
<vectorjoins/>
<layerDependencies/>
<dataDependencies/>
<legend type="default-vector"/>
<expressionfields/>
<map-layer-style-manager current="default">
<map-layer-style name="default"/>
</map-layer-style-manager>
<auxiliaryLayer/>
<flags>
<Identifiable>1</Identifiable>
<Removable>1</Removable>
<Searchable>1</Searchable>
</flags>
<renderer-v2 forceraster="0" enableorderby="0" symbollevels="0" type="singleSymbol">
<symbols>
<symbol alpha="1" type="marker" name="0" clip_to_extent="1">
<layer pass="0" enabled="1" locked="0" class="SimpleMarker">
<prop k="angle" v="0"/>
<prop k="color" v="196,60,57,255"/>
<prop k="horizontal_anchor_point" v="1"/>
<prop k="joinstyle" v="bevel"/>
<prop k="name" v="circle"/>
<prop k="offset" v="0,0"/>
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="offset_unit" v="MM"/>
<prop k="outline_color" v="35,35,35,255"/>
<prop k="outline_style" v="solid"/>
<prop k="outline_width" v="0"/>
<prop k="outline_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="outline_width_unit" v="MM"/>
<prop k="scale_method" v="diameter"/>
<prop k="size" v="2"/>
<prop k="size_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="size_unit" v="MM"/>
<prop k="vertical_anchor_point" v="1"/>
<data_defined_properties>
<Option type="Map">
<Option type="QString" name="name" value=""/>
<Option name="properties"/>
<Option type="QString" name="type" value="collection"/>
</Option>
</data_defined_properties>
</layer>
</symbol>
</symbols>
<rotation/>
<sizescale/>
</renderer-v2>
<customproperties>
<property key="embeddedWidgets/count" value="0"/>
<property key="variableNames"/>
<property key="variableValues"/>
</customproperties>
<blendMode>0</blendMode>
<featureBlendMode>0</featureBlendMode>
<layerOpacity>1</layerOpacity>
<SingleCategoryDiagramRenderer diagramType="Histogram" attributeLegend="1">
<DiagramCategory backgroundColor="#ffffff" scaleBasedVisibility="0" sizeType="MM" width="15" maxScaleDenominator="1e+8" diagramOrientation="Up" backgroundAlpha="255" enabled="0" penAlpha="255" barWidth="5" lineSizeType="MM" height="15" penWidth="0" labelPlacementMethod="XHeight" minScaleDenominator="0" rotationOffset="270" penColor="#000000" lineSizeScale="3x:0,0,0,0,0,0" opacity="1" minimumSize="0" scaleDependency="Area" sizeScale="3x:0,0,0,0,0,0">
<fontProperties description="Noto Sans,9,-1,5,50,0,0,0,0,0" style=""/>
</DiagramCategory>
</SingleCategoryDiagramRenderer>
<DiagramLayerSettings linePlacementFlags="18" showAll="1" priority="0" obstacle="0" zIndex="0" dist="0" placement="0">
<properties>
<Option type="Map">
<Option type="QString" name="name" value=""/>
<Option name="properties"/>
<Option type="QString" name="type" value="collection"/>
</Option>
</properties>
</DiagramLayerSettings>
<geometryOptions geometryPrecision="0" removeDuplicateNodes="0">
<activeChecks/>
<checkConfiguration/>
</geometryOptions>
<fieldConfiguration>
<field name="fid">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field name="name">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
</fieldConfiguration>
<aliases>
<alias name="" index="0" field="fid"/>
<alias name="" index="1" field="name"/>
</aliases>
<excludeAttributesWMS/>
<excludeAttributesWFS/>
<defaults>
<default expression="" applyOnUpdate="0" field="fid"/>
<default expression="" applyOnUpdate="0" field="name"/>
</defaults>
<constraints>
<constraint exp_strength="0" notnull_strength="1" field="fid" unique_strength="1" constraints="3"/>
<constraint exp_strength="0" notnull_strength="0" field="name" unique_strength="0" constraints="0"/>
</constraints>
<constraintExpressions>
<constraint exp="" desc="" field="fid"/>
<constraint exp="" desc="" field="name"/>
</constraintExpressions>
<expressionfields/>
<attributeactions>
<defaultAction key="Canvas" value="{00000000-0000-0000-0000-000000000000}"/>
</attributeactions>
<attributetableconfig sortExpression="" sortOrder="0" actionWidgetStyle="dropDown">
<columns>
<column type="field" hidden="0" name="fid" width="-1"/>
<column type="field" hidden="0" name="name" width="-1"/>
<column type="actions" hidden="1" width="-1"/>
</columns>
</attributetableconfig>
<conditionalstyles>
<rowstyles/>
<fieldstyles/>
</conditionalstyles>
<editform tolerant="1"></editform>
<editforminit/>
<editforminitcodesource>0</editforminitcodesource>
<editforminitfilepath></editforminitfilepath>
<editforminitcode><![CDATA[# -*- coding: utf-8 -*-
"""
QGIS forms can have a Python function that is called when the form is
opened.
Use this function to add extra logic to your forms.
Enter the name of the function in the "Python Init function"
field.
An example follows:
"""
from qgis.PyQt.QtWidgets import QWidget
def my_form_open(dialog, layer, feature):
geom = feature.geometry()
control = dialog.findChild(QWidget, "MyLineEdit")
]]></editforminitcode>
<featformsuppress>0</featformsuppress>
<editorlayout>generatedlayout</editorlayout>
<editable>
<field editable="1" name="fid"/>
<field editable="1" name="name"/>
</editable>
<labelOnTop>
<field name="fid" labelOnTop="0"/>
<field name="name" labelOnTop="0"/>
</labelOnTop>
<widgets>
<widget name="point_a_e9_point_b_ref_point_b_d2_fid">
<config type="Map">
<Option type="QString" name="nm-rel" value=""/>
</config>
</widget>
</widgets>
<previewExpression>fid</previewExpression>
<mapTip></mapTip>
</maplayer>
</projectlayers>
<layerorder>
<layer id="point_a_e99cf1b1_e13e_44a8_b912_58505e7ac967"/>
<layer id="point_b_d23a7df9_c9d6_4b48_9162_5fc1a7db2b96"/>
</layerorder>
<properties>
<WMSAddWktGeometry type="bool">false</WMSAddWktGeometry>
<WMSAccessConstraints type="QString">None</WMSAccessConstraints>
<Macros>
<pythonCode type="QString"></pythonCode>
</Macros>
<WFSLayers type="QStringList"/>
<PositionPrecision>
<DecimalPlaces type="int">2</DecimalPlaces>
<DegreeFormat type="QString">MU</DegreeFormat>
<Automatic type="bool">true</Automatic>
</PositionPrecision>
<WMSContactPhone type="QString"></WMSContactPhone>
<WMSRequestDefinedDataSources type="bool">false</WMSRequestDefinedDataSources>
<WMTSLayers>
<Project type="bool">false</Project>
<Group type="QStringList"/>
<Layer type="QStringList"/>
</WMTSLayers>
<Identify>
<disabledLayers type="QStringList"/>
</Identify>
<Measurement>
<AreaUnits type="QString">m2</AreaUnits>
<DistanceUnits type="QString">meters</DistanceUnits>
</Measurement>
<WMSImageQuality type="int">90</WMSImageQuality>
<WMSServiceCapabilities type="bool">false</WMSServiceCapabilities>
<WMTSUrl type="QString"></WMTSUrl>
<WMSPrecision type="QString">8</WMSPrecision>
<WMTSMinScale type="int">5000</WMTSMinScale>
<WMSContactMail type="QString"></WMSContactMail>
<RequiredLayers>
<Layers type="QStringList"/>
</RequiredLayers>
<WMSContactPerson type="QString"></WMSContactPerson>
<SpatialRefSys>
<ProjectionsEnabled type="int">1</ProjectionsEnabled>
</SpatialRefSys>
<WMSFees type="QString">conditions unknown</WMSFees>
<WMSKeywordList type="QStringList">
<value></value>
</WMSKeywordList>
<WMTSPngLayers>
<Project type="bool">false</Project>
<Group type="QStringList"/>
<Layer type="QStringList"/>
</WMTSPngLayers>
<WMSSegmentizeFeatureInfoGeometry type="bool">false</WMSSegmentizeFeatureInfoGeometry>
<Measure>
<Ellipsoid type="QString">GRS80</Ellipsoid>
</Measure>
<WMSOnlineResource type="QString"></WMSOnlineResource>
<WMSServiceAbstract type="QString"></WMSServiceAbstract>
<WMSUrl type="QString"></WMSUrl>
<WFSTLayers>
<Delete type="QStringList"/>
<Update type="QStringList"/>
<Insert type="QStringList"/>
</WFSTLayers>
<WFSUrl type="QString"></WFSUrl>
<WCSLayers type="QStringList"/>
<Variables>
<variableNames type="QStringList">
<value>qgisce_catalog_autoload</value>
<value>qgisce_template_version</value>
</variableNames>
<variableValues type="QStringList">
<value>true</value>
<value>1.0</value>
</variableValues>
</Variables>
<WMSContactPosition type="QString"></WMSContactPosition>
<Legend>
<filterByMap type="bool">false</filterByMap>
</Legend>
<WMSUseLayerIDs type="bool">false</WMSUseLayerIDs>
<WMSServiceTitle type="QString"></WMSServiceTitle>
<Gui>
<CanvasColorBluePart type="int">255</CanvasColorBluePart>
<SelectionColorAlphaPart type="int">255</SelectionColorAlphaPart>
<CanvasColorRedPart type="int">255</CanvasColorRedPart>
<SelectionColorRedPart type="int">255</SelectionColorRedPart>
<SelectionColorBluePart type="int">0</SelectionColorBluePart>
<CanvasColorGreenPart type="int">255</CanvasColorGreenPart>
<SelectionColorGreenPart type="int">255</SelectionColorGreenPart>
</Gui>
<PAL>
<SearchMethod type="int">0</SearchMethod>
<CandidatesPoint type="int">16</CandidatesPoint>
<ShowingCandidates type="bool">false</ShowingCandidates>
<ShowingAllLabels type="bool">false</ShowingAllLabels>
<ShowingPartialsLabels type="bool">true</ShowingPartialsLabels>
<CandidatesPolygon type="int">30</CandidatesPolygon>
<CandidatesLine type="int">50</CandidatesLine>
<DrawRectOnly type="bool">false</DrawRectOnly>
<DrawOutlineLabels type="bool">true</DrawOutlineLabels>
</PAL>
<DefaultStyles>
<Marker type="QString"></Marker>
<RandomColors type="bool">true</RandomColors>
<Line type="QString"></Line>
<Opacity type="double">1</Opacity>
<ColorRamp type="QString"></ColorRamp>
<Fill type="QString"></Fill>
</DefaultStyles>
<Paths>
<Absolute type="bool">false</Absolute>
</Paths>
<WMSContactOrganization type="QString"></WMSContactOrganization>
<WMTSJpegLayers>
<Project type="bool">false</Project>
<Group type="QStringList"/>
<Layer type="QStringList"/>
</WMTSJpegLayers>
<WCSUrl type="QString"></WCSUrl>
</properties>
<visibility-presets/>
<transformContext/>
<projectMetadata>
<identifier></identifier>
<parentidentifier></parentidentifier>
<language></language>
<type></type>
<title></title>
<abstract></abstract>
<contact>
<name></name>
<organization></organization>
<position></position>
<voice></voice>
<fax></fax>
<email></email>
<role></role>
</contact>
<links/>
<author>Alessandro Pasotti</author>
<creation>2018-07-06T13:56:35</creation>
</projectMetadata>
<Annotations/>
<Layouts/>
</qgis>