mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-05 00:09:32 -04:00
3426 lines
103 KiB
C++
3426 lines
103 KiB
C++
/***************************************************************************
|
|
qgsmaplayer.cpp - description
|
|
-------------------
|
|
begin : Fri Jun 28 2002
|
|
copyright : (C) 2002 by Gary E.Sherman
|
|
email : sherman at mrcc.com
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* 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. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
|
|
#include "qgssqliteutils.h"
|
|
#include "qgs3drendererregistry.h"
|
|
#include "qgsabstract3drenderer.h"
|
|
#include "qgsapplication.h"
|
|
#include "qgscoordinatereferencesystem.h"
|
|
#include "qgscoordinatereferencesystemutils.h"
|
|
#include "qgsdatasourceuri.h"
|
|
#include "qgsfileutils.h"
|
|
#include "qgslogger.h"
|
|
#include "qgsauthmanager.h"
|
|
#include "qgsmaplayer.h"
|
|
#include "moc_qgsmaplayer.cpp"
|
|
#include "qgsmaplayerlegend.h"
|
|
#include "qgsmaplayerstylemanager.h"
|
|
#include "qgspathresolver.h"
|
|
#include "qgsprojectfiletransform.h"
|
|
#include "qgsproject.h"
|
|
#include "qgsproviderregistry.h"
|
|
#include "qgsprovidermetadata.h"
|
|
#include "qgsrasterlayer.h"
|
|
#include "qgsreadwritecontext.h"
|
|
#include "qgsrectangle.h"
|
|
#include "qgsscaleutils.h"
|
|
#include "qgssldexportcontext.h"
|
|
#include "qgsvectorlayer.h"
|
|
#include "qgsxmlutils.h"
|
|
#include "qgsstringutils.h"
|
|
#include "qgsmessagelog.h"
|
|
#include "qgsmaplayertemporalproperties.h"
|
|
#include "qgsmaplayerelevationproperties.h"
|
|
#include "qgslayernotesutils.h"
|
|
#include "qgsdatums.h"
|
|
#include "qgsprojoperation.h"
|
|
#include "qgsthreadingutils.h"
|
|
#include "qgsunittypes.h"
|
|
|
|
#include <QDir>
|
|
#include <QDomDocument>
|
|
#include <QDomElement>
|
|
#include <QDomImplementation>
|
|
#include <QDomNode>
|
|
#include <QFile>
|
|
#include <QFileInfo>
|
|
#include <QLocale>
|
|
#include <QTextStream>
|
|
#include <QUrl>
|
|
#include <QTimer>
|
|
#include <QStandardPaths>
|
|
#include <QUuid>
|
|
#include <QRegularExpression>
|
|
#include <QXmlStreamReader>
|
|
|
|
#include <sqlite3.h>
|
|
|
|
QString QgsMapLayer::extensionPropertyType( QgsMapLayer::PropertyType type )
|
|
{
|
|
switch ( type )
|
|
{
|
|
case Metadata:
|
|
return QStringLiteral( ".qmd" );
|
|
|
|
case Style:
|
|
return QStringLiteral( ".qml" );
|
|
}
|
|
return QString();
|
|
}
|
|
|
|
QgsMapLayer::QgsMapLayer( Qgis::LayerType type,
|
|
const QString &lyrname,
|
|
const QString &source )
|
|
: mDataSource( source )
|
|
, mLayerName( lyrname )
|
|
, mLayerType( type )
|
|
, mServerProperties( std::make_unique<QgsMapLayerServerProperties>( this ) )
|
|
, mUndoStack( new QUndoStack( this ) )
|
|
, mUndoStackStyles( new QUndoStack( this ) )
|
|
, mStyleManager( std::make_unique<QgsMapLayerStyleManager>( this ) )
|
|
, mRefreshTimer( new QTimer( this ) )
|
|
{
|
|
mID = generateId( lyrname );
|
|
connect( this, &QgsMapLayer::crsChanged, this, &QgsMapLayer::configChanged );
|
|
connect( this, &QgsMapLayer::nameChanged, this, &QgsMapLayer::configChanged );
|
|
connect( mRefreshTimer, &QTimer::timeout, this, [this]
|
|
{
|
|
|
|
switch ( mAutoRefreshMode )
|
|
{
|
|
case Qgis::AutoRefreshMode::Disabled:
|
|
break;
|
|
case Qgis::AutoRefreshMode::RedrawOnly:
|
|
triggerRepaint( true );
|
|
break;
|
|
case Qgis::AutoRefreshMode::ReloadData:
|
|
reload();
|
|
break;
|
|
}
|
|
} );
|
|
}
|
|
|
|
QgsMapLayer::~QgsMapLayer()
|
|
{
|
|
if ( project() && project()->pathResolver().writePath( mDataSource ).startsWith( "attachment:" ) )
|
|
{
|
|
project()->removeAttachedFile( mDataSource );
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
void QgsMapLayer::clone( QgsMapLayer *layer ) const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
QgsDebugMsgLevel( QStringLiteral( "Cloning layer '%1'" ).arg( name() ), 3 );
|
|
layer->setBlendMode( blendMode() );
|
|
|
|
const auto constStyles = styleManager()->styles();
|
|
for ( const QString &s : constStyles )
|
|
{
|
|
layer->styleManager()->addStyle( s, styleManager()->style( s ) );
|
|
}
|
|
|
|
layer->setName( name() );
|
|
|
|
if ( layer->dataProvider() && layer->dataProvider()->elevationProperties() )
|
|
{
|
|
if ( layer->dataProvider()->elevationProperties()->containsElevationData() )
|
|
layer->mExtent3D = mExtent3D;
|
|
else
|
|
layer->mExtent2D = mExtent2D;
|
|
}
|
|
|
|
layer->setMaximumScale( maximumScale() );
|
|
layer->setMinimumScale( minimumScale() );
|
|
layer->setScaleBasedVisibility( hasScaleBasedVisibility() );
|
|
layer->setDependencies( dependencies() );
|
|
layer->mShouldValidateCrs = mShouldValidateCrs;
|
|
layer->setCrs( crs() );
|
|
layer->setCustomProperties( mCustomProperties );
|
|
layer->setOpacity( mLayerOpacity );
|
|
layer->setMetadata( mMetadata );
|
|
layer->serverProperties()->copyTo( mServerProperties.get() );
|
|
}
|
|
|
|
Qgis::LayerType QgsMapLayer::type() const
|
|
{
|
|
// because QgsVirtualLayerProvider is not anywhere NEAR thread safe:
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
|
|
|
|
return mLayerType;
|
|
}
|
|
|
|
QgsMapLayer::LayerFlags QgsMapLayer::flags() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return mFlags;
|
|
}
|
|
|
|
void QgsMapLayer::setFlags( QgsMapLayer::LayerFlags flags )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( flags == mFlags )
|
|
return;
|
|
|
|
mFlags = flags;
|
|
emit flagsChanged();
|
|
}
|
|
|
|
Qgis::MapLayerProperties QgsMapLayer::properties() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return Qgis::MapLayerProperties();
|
|
}
|
|
|
|
QString QgsMapLayer::id() const
|
|
{
|
|
// because QgsVirtualLayerProvider is not anywhere NEAR thread safe:
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
|
|
|
|
return mID;
|
|
}
|
|
|
|
bool QgsMapLayer::setId( const QString &id )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
if ( qobject_cast< QgsMapLayerStore * >( parent() ) )
|
|
{
|
|
// layer is already registered, cannot change id
|
|
return false;
|
|
}
|
|
|
|
if ( id == mID )
|
|
return false;
|
|
|
|
mID = id;
|
|
emit idChanged( id );
|
|
return true;
|
|
}
|
|
|
|
void QgsMapLayer::setName( const QString &name )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( name == mLayerName )
|
|
return;
|
|
|
|
mLayerName = name;
|
|
|
|
emit nameChanged();
|
|
}
|
|
|
|
QString QgsMapLayer::name() const
|
|
{
|
|
// because QgsVirtualLayerProvider is not anywhere NEAR thread safe:
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
|
|
|
|
QgsDebugMsgLevel( "returning name '" + mLayerName + '\'', 4 );
|
|
return mLayerName;
|
|
}
|
|
|
|
QgsDataProvider *QgsMapLayer::dataProvider()
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const QgsDataProvider *QgsMapLayer::dataProvider() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
QgsProviderMetadata *QgsMapLayer::providerMetadata() const
|
|
{
|
|
return QgsProviderRegistry::instance()->providerMetadata( providerType() );
|
|
}
|
|
|
|
void QgsMapLayer::setShortName( const QString &shortName )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
mServerProperties->setShortName( shortName );
|
|
}
|
|
|
|
QString QgsMapLayer::shortName() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return mServerProperties->shortName();
|
|
}
|
|
|
|
void QgsMapLayer::setTitle( const QString &title )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
mServerProperties->setTitle( title );
|
|
}
|
|
|
|
QString QgsMapLayer::title() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
|
|
|
|
return mServerProperties->title();
|
|
}
|
|
|
|
void QgsMapLayer::setAbstract( const QString &abstract )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
mServerProperties->setAbstract( abstract );
|
|
}
|
|
|
|
QString QgsMapLayer::abstract() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
|
|
|
|
return mServerProperties->abstract();
|
|
}
|
|
|
|
void QgsMapLayer::setKeywordList( const QString &keywords )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
mServerProperties->setKeywordList( keywords );
|
|
}
|
|
|
|
QString QgsMapLayer::keywordList() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
|
|
|
|
return mServerProperties->keywordList();
|
|
}
|
|
|
|
void QgsMapLayer::setDataUrl( const QString &dataUrl )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
mServerProperties->setDataUrl( dataUrl );
|
|
}
|
|
|
|
QString QgsMapLayer::dataUrl() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
|
|
|
|
return mServerProperties->dataUrl();
|
|
}
|
|
|
|
void QgsMapLayer::setDataUrlFormat( const QString &dataUrlFormat )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
mServerProperties->setDataUrlFormat( dataUrlFormat );
|
|
}
|
|
|
|
QString QgsMapLayer::dataUrlFormat() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
|
|
|
|
return mServerProperties->dataUrlFormat();
|
|
}
|
|
|
|
void QgsMapLayer::setAttribution( const QString &attrib )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
mServerProperties->setAttribution( attrib );
|
|
}
|
|
|
|
QString QgsMapLayer::attribution() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
|
|
|
|
return mServerProperties->attribution();
|
|
}
|
|
|
|
void QgsMapLayer::setAttributionUrl( const QString &attribUrl )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
mServerProperties->setAttributionUrl( attribUrl );
|
|
}
|
|
|
|
QString QgsMapLayer::attributionUrl() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
|
|
|
|
return mServerProperties->attributionUrl();
|
|
}
|
|
|
|
void QgsMapLayer::setLegendUrl( const QString &legendUrl )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
mServerProperties->setLegendUrl( legendUrl );
|
|
}
|
|
|
|
QString QgsMapLayer::legendUrl() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return mServerProperties->legendUrl();
|
|
}
|
|
|
|
void QgsMapLayer::setLegendUrlFormat( const QString &legendUrlFormat )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
mServerProperties->setLegendUrlFormat( legendUrlFormat );
|
|
}
|
|
|
|
QString QgsMapLayer::legendUrlFormat() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return mServerProperties->legendUrlFormat();
|
|
}
|
|
|
|
void QgsMapLayer::setMetadataUrl( const QString &metaUrl )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
QList<QgsMapLayerServerProperties::MetadataUrl> urls = serverProperties()->metadataUrls();
|
|
if ( urls.isEmpty() )
|
|
{
|
|
const QgsMapLayerServerProperties::MetadataUrl newItem = QgsMapLayerServerProperties::MetadataUrl( metaUrl, QLatin1String(), QLatin1String() );
|
|
urls.prepend( newItem );
|
|
}
|
|
else
|
|
{
|
|
const QgsMapLayerServerProperties::MetadataUrl old = urls.takeFirst();
|
|
const QgsMapLayerServerProperties::MetadataUrl newItem( metaUrl, old.type, old.format );
|
|
urls.prepend( newItem );
|
|
}
|
|
serverProperties()->setMetadataUrls( urls );
|
|
}
|
|
|
|
QString QgsMapLayer::metadataUrl() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( mServerProperties->metadataUrls().isEmpty() )
|
|
{
|
|
return QLatin1String();
|
|
}
|
|
else
|
|
{
|
|
return mServerProperties->metadataUrls().first().url;
|
|
}
|
|
}
|
|
|
|
void QgsMapLayer::setMetadataUrlType( const QString &metaUrlType )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
QList<QgsMapLayerServerProperties::MetadataUrl> urls = mServerProperties->metadataUrls();
|
|
if ( urls.isEmpty() )
|
|
{
|
|
const QgsMapLayerServerProperties::MetadataUrl newItem( QLatin1String(), metaUrlType, QLatin1String() );
|
|
urls.prepend( newItem );
|
|
}
|
|
else
|
|
{
|
|
const QgsMapLayerServerProperties::MetadataUrl old = urls.takeFirst();
|
|
const QgsMapLayerServerProperties::MetadataUrl newItem( old.url, metaUrlType, old.format );
|
|
urls.prepend( newItem );
|
|
}
|
|
mServerProperties->setMetadataUrls( urls );
|
|
}
|
|
|
|
QString QgsMapLayer::metadataUrlType() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( mServerProperties->metadataUrls().isEmpty() )
|
|
{
|
|
return QLatin1String();
|
|
}
|
|
else
|
|
{
|
|
return mServerProperties->metadataUrls().first().type;
|
|
}
|
|
}
|
|
|
|
void QgsMapLayer::setMetadataUrlFormat( const QString &metaUrlFormat )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
QList<QgsMapLayerServerProperties::MetadataUrl> urls = mServerProperties->metadataUrls();
|
|
if ( urls.isEmpty() )
|
|
{
|
|
const QgsMapLayerServerProperties::MetadataUrl newItem( QLatin1String(), QLatin1String(), metaUrlFormat );
|
|
urls.prepend( newItem );
|
|
}
|
|
else
|
|
{
|
|
const QgsMapLayerServerProperties::MetadataUrl old = urls.takeFirst( );
|
|
const QgsMapLayerServerProperties::MetadataUrl newItem( old.url, old.type, metaUrlFormat );
|
|
urls.prepend( newItem );
|
|
}
|
|
mServerProperties->setMetadataUrls( urls );
|
|
}
|
|
|
|
QString QgsMapLayer::metadataUrlFormat() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( mServerProperties->metadataUrls().isEmpty() )
|
|
{
|
|
return QString();
|
|
}
|
|
else
|
|
{
|
|
return mServerProperties->metadataUrls().first().format;
|
|
}
|
|
}
|
|
|
|
QString QgsMapLayer::publicSource( bool redactCredentials ) const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
// Redo this every time we're asked for it, as we don't know if
|
|
// dataSource has changed.
|
|
if ( const QgsProviderMetadata *metadata = QgsProviderRegistry::instance()->providerMetadata( providerType() ) )
|
|
{
|
|
return metadata->cleanUri( mDataSource, redactCredentials ? Qgis::UriCleaningFlag::RedactCredentials : Qgis::UriCleaningFlag::RemoveCredentials );
|
|
}
|
|
else
|
|
{
|
|
return QgsDataSourceUri::removePassword( mDataSource, redactCredentials );
|
|
}
|
|
}
|
|
|
|
QString QgsMapLayer::source() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return mDataSource;
|
|
}
|
|
|
|
QgsRectangle QgsMapLayer::extent() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return mExtent2D.isNull() ? mExtent3D.toRectangle() : mExtent2D;
|
|
}
|
|
|
|
QgsBox3D QgsMapLayer::extent3D() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return mExtent3D;
|
|
}
|
|
|
|
void QgsMapLayer::setBlendMode( const QPainter::CompositionMode blendMode )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( mBlendMode == blendMode )
|
|
return;
|
|
|
|
mBlendMode = blendMode;
|
|
emit blendModeChanged( blendMode );
|
|
emitStyleChanged();
|
|
}
|
|
|
|
QPainter::CompositionMode QgsMapLayer::blendMode() const
|
|
{
|
|
// non fatal for now -- the "rasterize" processing algorithm is not thread safe and calls this
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
|
|
|
|
return mBlendMode;
|
|
}
|
|
|
|
void QgsMapLayer::setOpacity( double opacity )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( qgsDoubleNear( mLayerOpacity, opacity ) )
|
|
return;
|
|
mLayerOpacity = opacity;
|
|
emit opacityChanged( opacity );
|
|
emitStyleChanged();
|
|
}
|
|
|
|
double QgsMapLayer::opacity() const
|
|
{
|
|
// non fatal for now -- the "rasterize" processing algorithm is not thread safe and calls this
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
|
|
|
|
return mLayerOpacity;
|
|
}
|
|
|
|
bool QgsMapLayer::readLayerXml( const QDomElement &layerElement, QgsReadWriteContext &context, QgsMapLayer::ReadFlags flags, QgsDataProvider *preloadedProvider )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
mPreloadedProvider.reset( preloadedProvider );
|
|
|
|
bool layerError;
|
|
mReadFlags = flags;
|
|
|
|
QDomNode mnl;
|
|
QDomElement mne;
|
|
|
|
// read provider
|
|
QString provider;
|
|
mnl = layerElement.namedItem( QStringLiteral( "provider" ) );
|
|
mne = mnl.toElement();
|
|
provider = mne.text();
|
|
|
|
// set data source
|
|
mnl = layerElement.namedItem( QStringLiteral( "datasource" ) );
|
|
mne = mnl.toElement();
|
|
const QString dataSourceRaw = mne.text();
|
|
|
|
// if the layer needs authentication, ensure the master password is set
|
|
const thread_local QRegularExpression rx( "authcfg=([a-z]|[A-Z]|[0-9]){7}" );
|
|
if ( rx.match( dataSourceRaw ).hasMatch()
|
|
&& !QgsApplication::authManager()->setMasterPassword( true ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
mDataSource = decodedSource( dataSourceRaw, provider, context );
|
|
|
|
// Set the CRS from project file, asking the user if necessary.
|
|
// Make it the saved CRS to have WMS layer projected correctly.
|
|
// We will still overwrite whatever GDAL etc picks up anyway
|
|
// further down this function.
|
|
mnl = layerElement.namedItem( QStringLiteral( "layername" ) );
|
|
mne = mnl.toElement();
|
|
|
|
QgsCoordinateReferenceSystem savedCRS;
|
|
CUSTOM_CRS_VALIDATION savedValidation;
|
|
|
|
const QDomNode srsNode = layerElement.namedItem( QStringLiteral( "srs" ) );
|
|
mCRS.readXml( srsNode );
|
|
mCRS.setValidationHint( tr( "Specify CRS for layer %1" ).arg( mne.text() ) );
|
|
if ( isSpatial() && type() != Qgis::LayerType::Annotation )
|
|
mCRS.validate();
|
|
savedCRS = mCRS;
|
|
|
|
// Do not validate any projections in children, they will be overwritten anyway.
|
|
// No need to ask the user for a projections when it is overwritten, is there?
|
|
savedValidation = QgsCoordinateReferenceSystem::customCrsValidation();
|
|
QgsCoordinateReferenceSystem::setCustomCrsValidation( nullptr );
|
|
|
|
const QgsReadWriteContextCategoryPopper p = context.enterCategory( tr( "Layer" ), mne.text() );
|
|
|
|
// the internal name is just the data source basename
|
|
//QFileInfo dataSourceFileInfo( mDataSource );
|
|
//internalName = dataSourceFileInfo.baseName();
|
|
|
|
// set ID
|
|
mnl = layerElement.namedItem( QStringLiteral( "id" ) );
|
|
if ( ! mnl.isNull() )
|
|
{
|
|
mne = mnl.toElement();
|
|
if ( ! mne.isNull() && mne.text().length() > 10 ) // should be at least 17 (yyyyMMddhhmmsszzz)
|
|
{
|
|
const QString newId = mne.text();
|
|
if ( newId != mID )
|
|
{
|
|
mID = mne.text();
|
|
emit idChanged( mID );
|
|
}
|
|
}
|
|
}
|
|
|
|
// set name
|
|
mnl = layerElement.namedItem( QStringLiteral( "layername" ) );
|
|
mne = mnl.toElement();
|
|
|
|
//name can be translated
|
|
setName( context.projectTranslator()->translate( QStringLiteral( "project:layers:%1" ).arg( layerElement.namedItem( QStringLiteral( "id" ) ).toElement().text() ), mne.text() ) );
|
|
|
|
// now let the children grab what they need from the Dom node.
|
|
layerError = !readXml( layerElement, context );
|
|
|
|
const QgsCoordinateReferenceSystem oldVerticalCrs = verticalCrs();
|
|
const QgsCoordinateReferenceSystem oldCrs3D = mCrs3D;
|
|
|
|
// overwrite CRS with what we read from project file before the raster/vector
|
|
// file reading functions changed it. They will if projections is specified in the file.
|
|
// FIXME: is this necessary? Yes, it is (autumn 2019)
|
|
QgsCoordinateReferenceSystem::setCustomCrsValidation( savedValidation );
|
|
mCRS = savedCRS;
|
|
|
|
//vertical CRS
|
|
{
|
|
QgsCoordinateReferenceSystem verticalCrs;
|
|
const QDomNode verticalCrsNode = layerElement.firstChildElement( QStringLiteral( "verticalCrs" ) );
|
|
if ( !verticalCrsNode.isNull() )
|
|
{
|
|
verticalCrs.readXml( verticalCrsNode );
|
|
}
|
|
mVerticalCrs = verticalCrs;
|
|
}
|
|
rebuildCrs3D();
|
|
|
|
serverProperties()->readXml( layerElement );
|
|
|
|
// mMetadata.readFromLayer( this );
|
|
const QDomElement metadataElem = layerElement.firstChildElement( QStringLiteral( "resourceMetadata" ) );
|
|
mMetadata.readMetadataXml( metadataElem, context );
|
|
|
|
setAutoRefreshInterval( layerElement.attribute( QStringLiteral( "autoRefreshTime" ), QStringLiteral( "0" ) ).toInt() );
|
|
if ( layerElement.hasAttribute( QStringLiteral( "autoRefreshMode" ) ) )
|
|
{
|
|
setAutoRefreshMode( qgsEnumKeyToValue( layerElement.attribute( QStringLiteral( "autoRefreshMode" ) ), Qgis::AutoRefreshMode::Disabled ) );
|
|
}
|
|
else
|
|
{
|
|
setAutoRefreshMode( layerElement.attribute( QStringLiteral( "autoRefreshEnabled" ), QStringLiteral( "0" ) ).toInt() ? Qgis::AutoRefreshMode::RedrawOnly : Qgis::AutoRefreshMode::Disabled );
|
|
}
|
|
setRefreshOnNofifyMessage( layerElement.attribute( QStringLiteral( "refreshOnNotifyMessage" ), QString() ) );
|
|
setRefreshOnNotifyEnabled( layerElement.attribute( QStringLiteral( "refreshOnNotifyEnabled" ), QStringLiteral( "0" ) ).toInt() );
|
|
|
|
// geographic extent is read only if necessary
|
|
if ( mReadFlags & QgsMapLayer::ReadFlag::FlagTrustLayerMetadata )
|
|
{
|
|
const QDomNode wgs84ExtentNode = layerElement.namedItem( QStringLiteral( "wgs84extent" ) );
|
|
if ( !wgs84ExtentNode.isNull() )
|
|
mWgs84Extent = QgsXmlUtils::readRectangle( wgs84ExtentNode.toElement() );
|
|
}
|
|
|
|
mLegendPlaceholderImage = layerElement.attribute( QStringLiteral( "legendPlaceholderImage" ) );
|
|
|
|
if ( verticalCrs() != oldVerticalCrs )
|
|
emit verticalCrsChanged();
|
|
if ( mCrs3D != oldCrs3D )
|
|
emit crs3DChanged();
|
|
|
|
return ! layerError;
|
|
} // bool QgsMapLayer::readLayerXML
|
|
|
|
|
|
bool QgsMapLayer::readXml( const QDomNode &layer_node, QgsReadWriteContext &context )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
Q_UNUSED( layer_node )
|
|
Q_UNUSED( context )
|
|
// NOP by default; children will over-ride with behavior specific to them
|
|
|
|
// read Extent
|
|
if ( mReadFlags & QgsMapLayer::FlagReadExtentFromXml )
|
|
{
|
|
const QDomNode extent3DNode = layer_node.namedItem( QStringLiteral( "extent3D" ) );
|
|
if ( extent3DNode.isNull() )
|
|
{
|
|
const QDomNode extentNode = layer_node.namedItem( QStringLiteral( "extent" ) );
|
|
if ( !extentNode.isNull() )
|
|
{
|
|
mExtent2D = QgsXmlUtils::readRectangle( extentNode.toElement() );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mExtent3D = QgsXmlUtils::readBox3D( extent3DNode.toElement() );
|
|
}
|
|
}
|
|
|
|
return true;
|
|
} // void QgsMapLayer::readXml
|
|
|
|
|
|
bool QgsMapLayer::writeLayerXml( QDomElement &layerElement, QDomDocument &document, const QgsReadWriteContext &context ) const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( !mExtent3D.isNull() && dataProvider() && dataProvider()->elevationProperties() && dataProvider()->elevationProperties()->containsElevationData() )
|
|
layerElement.appendChild( QgsXmlUtils::writeBox3D( mExtent3D, document ) );
|
|
else
|
|
{
|
|
// Extent might be null because lazily set
|
|
const QgsRectangle extent2D { mExtent2D.isNull() ? extent() : mExtent2D };
|
|
if ( !extent2D.isNull() )
|
|
{
|
|
layerElement.appendChild( QgsXmlUtils::writeRectangle( extent2D, document ) );
|
|
}
|
|
}
|
|
|
|
if ( const QgsRectangle lWgs84Extent = wgs84Extent( true ); !lWgs84Extent.isNull() )
|
|
{
|
|
layerElement.appendChild( QgsXmlUtils::writeRectangle( lWgs84Extent, document, QStringLiteral( "wgs84extent" ) ) );
|
|
}
|
|
|
|
layerElement.setAttribute( QStringLiteral( "autoRefreshTime" ), QString::number( mRefreshTimer->interval() ) );
|
|
layerElement.setAttribute( QStringLiteral( "autoRefreshMode" ), qgsEnumValueToKey( mAutoRefreshMode ) );
|
|
layerElement.setAttribute( QStringLiteral( "refreshOnNotifyEnabled" ), mIsRefreshOnNofifyEnabled ? 1 : 0 );
|
|
layerElement.setAttribute( QStringLiteral( "refreshOnNotifyMessage" ), mRefreshOnNofifyMessage );
|
|
|
|
// ID
|
|
QDomElement layerId = document.createElement( QStringLiteral( "id" ) );
|
|
const QDomText layerIdText = document.createTextNode( id() );
|
|
layerId.appendChild( layerIdText );
|
|
|
|
layerElement.appendChild( layerId );
|
|
|
|
if ( mVerticalCrs.isValid() )
|
|
{
|
|
QDomElement verticalSrsNode = document.createElement( QStringLiteral( "verticalCrs" ) );
|
|
mVerticalCrs.writeXml( verticalSrsNode, document );
|
|
layerElement.appendChild( verticalSrsNode );
|
|
}
|
|
|
|
// data source
|
|
QDomElement dataSource = document.createElement( QStringLiteral( "datasource" ) );
|
|
const QString src = encodedSource( source(), context );
|
|
const QDomText dataSourceText = document.createTextNode( src );
|
|
dataSource.appendChild( dataSourceText );
|
|
layerElement.appendChild( dataSource );
|
|
|
|
// layer name
|
|
QDomElement layerName = document.createElement( QStringLiteral( "layername" ) );
|
|
const QDomText layerNameText = document.createTextNode( name() );
|
|
layerName.appendChild( layerNameText );
|
|
layerElement.appendChild( layerName );
|
|
|
|
// timestamp if supported
|
|
if ( timestamp() > QDateTime() )
|
|
{
|
|
QDomElement stamp = document.createElement( QStringLiteral( "timestamp" ) );
|
|
const QDomText stampText = document.createTextNode( timestamp().toString( Qt::ISODate ) );
|
|
stamp.appendChild( stampText );
|
|
layerElement.appendChild( stamp );
|
|
}
|
|
|
|
layerElement.appendChild( layerName );
|
|
|
|
// zorder
|
|
// This is no longer stored in the project file. It is superfluous since the layers
|
|
// are written and read in the proper order.
|
|
|
|
// spatial reference system id
|
|
QDomElement mySrsElement = document.createElement( QStringLiteral( "srs" ) );
|
|
mCRS.writeXml( mySrsElement, document );
|
|
layerElement.appendChild( mySrsElement );
|
|
|
|
// layer metadata
|
|
QDomElement myMetadataElem = document.createElement( QStringLiteral( "resourceMetadata" ) );
|
|
mMetadata.writeMetadataXml( myMetadataElem, document );
|
|
layerElement.appendChild( myMetadataElem );
|
|
|
|
layerElement.setAttribute( QStringLiteral( "legendPlaceholderImage" ), mLegendPlaceholderImage );
|
|
|
|
serverProperties()->writeXml( layerElement, document );
|
|
|
|
// now append layer node to map layer node
|
|
return writeXml( layerElement, document, context );
|
|
}
|
|
|
|
void QgsMapLayer::writeCommonStyle( QDomElement &layerElement, QDomDocument &document,
|
|
const QgsReadWriteContext &context, QgsMapLayer::StyleCategories categories ) const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
// save categories
|
|
const QMetaEnum metaEnum = QMetaEnum::fromType<QgsMapLayer::StyleCategories>();
|
|
const QString categoriesKeys( metaEnum.valueToKeys( static_cast<int>( categories ) ) );
|
|
layerElement.setAttribute( QStringLiteral( "styleCategories" ), categoriesKeys );
|
|
|
|
if ( categories.testFlag( Rendering ) )
|
|
{
|
|
// use scale dependent visibility flag
|
|
layerElement.setAttribute( QStringLiteral( "hasScaleBasedVisibilityFlag" ), hasScaleBasedVisibility() ? 1 : 0 );
|
|
layerElement.setAttribute( QStringLiteral( "maxScale" ), QString::number( maximumScale() ) );
|
|
layerElement.setAttribute( QStringLiteral( "minScale" ), QString::number( minimumScale() ) );
|
|
layerElement.setAttribute( QStringLiteral( "autoRefreshMode" ), qgsEnumValueToKey( mAutoRefreshMode ) );
|
|
layerElement.setAttribute( QStringLiteral( "autoRefreshTime" ), QString::number( autoRefreshInterval() ) );
|
|
}
|
|
|
|
if ( categories.testFlag( Symbology3D ) )
|
|
{
|
|
if ( m3DRenderer )
|
|
{
|
|
QDomElement renderer3DElem = document.createElement( QStringLiteral( "renderer-3d" ) );
|
|
renderer3DElem.setAttribute( QStringLiteral( "type" ), m3DRenderer->type() );
|
|
m3DRenderer->writeXml( renderer3DElem, context );
|
|
layerElement.appendChild( renderer3DElem );
|
|
}
|
|
}
|
|
|
|
if ( categories.testFlag( LayerConfiguration ) )
|
|
{
|
|
// flags
|
|
// this code is saving automatically all the flags entries
|
|
QDomElement layerFlagsElem = document.createElement( QStringLiteral( "flags" ) );
|
|
const auto enumMap = qgsEnumMap<QgsMapLayer::LayerFlag>();
|
|
for ( auto it = enumMap.constBegin(); it != enumMap.constEnd(); ++it )
|
|
{
|
|
const bool flagValue = mFlags.testFlag( it.key() );
|
|
QDomElement flagElem = document.createElement( it.value() );
|
|
flagElem.appendChild( document.createTextNode( QString::number( flagValue ) ) );
|
|
layerFlagsElem.appendChild( flagElem );
|
|
}
|
|
layerElement.appendChild( layerFlagsElem );
|
|
}
|
|
|
|
if ( categories.testFlag( Temporal ) )
|
|
{
|
|
if ( QgsMapLayerTemporalProperties *properties = const_cast< QgsMapLayer * >( this )->temporalProperties() )
|
|
properties->writeXml( layerElement, document, context );
|
|
}
|
|
|
|
if ( categories.testFlag( Elevation ) )
|
|
{
|
|
if ( QgsMapLayerElevationProperties *properties = const_cast< QgsMapLayer * >( this )->elevationProperties() )
|
|
properties->writeXml( layerElement, document, context );
|
|
}
|
|
|
|
if ( categories.testFlag( Notes ) && QgsLayerNotesUtils::layerHasNotes( this ) )
|
|
{
|
|
QDomElement notesElem = document.createElement( QStringLiteral( "userNotes" ) );
|
|
notesElem.setAttribute( QStringLiteral( "value" ), QgsLayerNotesUtils::layerNotes( this ) );
|
|
layerElement.appendChild( notesElem );
|
|
}
|
|
|
|
// custom properties
|
|
if ( categories.testFlag( CustomProperties ) )
|
|
{
|
|
writeCustomProperties( layerElement, document );
|
|
}
|
|
}
|
|
|
|
|
|
bool QgsMapLayer::writeXml( QDomNode &layer_node, QDomDocument &document, const QgsReadWriteContext &context ) const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
Q_UNUSED( layer_node )
|
|
Q_UNUSED( document )
|
|
Q_UNUSED( context )
|
|
// NOP by default; children will over-ride with behavior specific to them
|
|
|
|
return true;
|
|
}
|
|
|
|
QString QgsMapLayer::encodedSource( const QString &source, const QgsReadWriteContext &context ) const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
Q_UNUSED( context )
|
|
return source;
|
|
}
|
|
|
|
QString QgsMapLayer::decodedSource( const QString &source, const QString &dataProvider, const QgsReadWriteContext &context ) const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
Q_UNUSED( context )
|
|
Q_UNUSED( dataProvider )
|
|
return source;
|
|
}
|
|
|
|
void QgsMapLayer::resolveReferences( QgsProject *project )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
emit beforeResolveReferences( project );
|
|
if ( m3DRenderer )
|
|
m3DRenderer->resolveReferences( *project );
|
|
}
|
|
|
|
|
|
void QgsMapLayer::readCustomProperties( const QDomNode &layerNode, const QString &keyStartsWith )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
const QgsObjectCustomProperties oldKeys = mCustomProperties;
|
|
|
|
mCustomProperties.readXml( layerNode, keyStartsWith );
|
|
|
|
for ( const QString &key : mCustomProperties.keys() )
|
|
{
|
|
if ( !oldKeys.contains( key ) || mCustomProperties.value( key ) != oldKeys.value( key ) )
|
|
{
|
|
emit customPropertyChanged( key );
|
|
}
|
|
}
|
|
}
|
|
|
|
void QgsMapLayer::writeCustomProperties( QDomNode &layerNode, QDomDocument &doc ) const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
mCustomProperties.writeXml( layerNode, doc );
|
|
}
|
|
|
|
void QgsMapLayer::readStyleManager( const QDomNode &layerNode )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
const QDomElement styleMgrElem = layerNode.firstChildElement( QStringLiteral( "map-layer-style-manager" ) );
|
|
if ( !styleMgrElem.isNull() )
|
|
mStyleManager->readXml( styleMgrElem );
|
|
else
|
|
mStyleManager->reset();
|
|
}
|
|
|
|
void QgsMapLayer::writeStyleManager( QDomNode &layerNode, QDomDocument &doc ) const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( mStyleManager )
|
|
{
|
|
QDomElement styleMgrElem = doc.createElement( QStringLiteral( "map-layer-style-manager" ) );
|
|
mStyleManager->writeXml( styleMgrElem );
|
|
layerNode.appendChild( styleMgrElem );
|
|
}
|
|
}
|
|
|
|
QString QgsMapLayer::mapTipTemplate() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return mMapTipTemplate;
|
|
}
|
|
|
|
void QgsMapLayer::setMapTipTemplate( const QString &mapTip )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( mMapTipTemplate == mapTip )
|
|
return;
|
|
|
|
mMapTipTemplate = mapTip;
|
|
emit mapTipTemplateChanged();
|
|
}
|
|
|
|
void QgsMapLayer::setMapTipsEnabled( bool enabled )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( mMapTipsEnabled == enabled )
|
|
return;
|
|
|
|
mMapTipsEnabled = enabled;
|
|
emit mapTipsEnabledChanged();
|
|
}
|
|
|
|
bool QgsMapLayer::mapTipsEnabled() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return mMapTipsEnabled;
|
|
}
|
|
|
|
Qgis::DataProviderReadFlags QgsMapLayer::providerReadFlags( const QDomNode &layerNode, QgsMapLayer::ReadFlags layerReadFlags )
|
|
{
|
|
Qgis::DataProviderReadFlags flags;
|
|
if ( layerReadFlags & QgsMapLayer::FlagTrustLayerMetadata )
|
|
{
|
|
flags |= Qgis::DataProviderReadFlag::TrustDataSource;
|
|
}
|
|
if ( layerReadFlags & QgsMapLayer::FlagForceReadOnly )
|
|
{
|
|
flags |= Qgis::DataProviderReadFlag::ForceReadOnly;
|
|
}
|
|
|
|
if ( layerReadFlags & QgsMapLayer::FlagReadExtentFromXml )
|
|
{
|
|
const QDomNode extent3DNode = layerNode.namedItem( QStringLiteral( "extent3D" ) );
|
|
if ( extent3DNode.isNull() )
|
|
{
|
|
const QDomNode extentNode = layerNode.namedItem( QStringLiteral( "extent" ) );
|
|
if ( !extentNode.isNull() )
|
|
{
|
|
flags |= Qgis::DataProviderReadFlag::SkipGetExtent;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
flags |= Qgis::DataProviderReadFlag::SkipGetExtent;
|
|
}
|
|
}
|
|
|
|
return flags;
|
|
}
|
|
|
|
bool QgsMapLayer::isValid() const
|
|
{
|
|
// because QgsVirtualLayerProvider is not anywhere NEAR thread safe:
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
|
|
|
|
return mValid;
|
|
}
|
|
|
|
#if 0
|
|
void QgsMapLayer::connectNotify( const char *signal )
|
|
{
|
|
Q_UNUSED( signal )
|
|
QgsDebugMsgLevel( "QgsMapLayer connected to " + QString( signal ), 3 );
|
|
} // QgsMapLayer::connectNotify
|
|
#endif
|
|
|
|
bool QgsMapLayer::isInScaleRange( double scale ) const
|
|
{
|
|
// non fatal for now -- the "rasterize" processing algorithm is not thread safe and calls this
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
|
|
|
|
// mMinScale (denominator!) is inclusive ( >= --> In range )
|
|
// mMaxScale (denominator!) is exclusive ( < --> In range )
|
|
return !mScaleBasedVisibility
|
|
|| ( ( mMinScale == 0 || !QgsScaleUtils::lessThanMaximumScale( scale, mMinScale ) )
|
|
&& ( mMaxScale == 0 || !QgsScaleUtils::equalToOrGreaterThanMinimumScale( scale, mMaxScale ) ) );
|
|
}
|
|
|
|
bool QgsMapLayer::hasScaleBasedVisibility() const
|
|
{
|
|
// non fatal for now -- the "rasterize" processing algorithm is not thread safe and calls this
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
|
|
|
|
return mScaleBasedVisibility;
|
|
}
|
|
|
|
bool QgsMapLayer::hasAutoRefreshEnabled() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return mAutoRefreshMode != Qgis::AutoRefreshMode::Disabled;;
|
|
}
|
|
|
|
Qgis::AutoRefreshMode QgsMapLayer::autoRefreshMode() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return mAutoRefreshMode;
|
|
}
|
|
|
|
int QgsMapLayer::autoRefreshInterval() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return mRefreshTimer->interval();
|
|
}
|
|
|
|
void QgsMapLayer::setAutoRefreshInterval( int interval )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( interval <= 0 )
|
|
{
|
|
mRefreshTimer->stop();
|
|
mRefreshTimer->setInterval( 0 );
|
|
setAutoRefreshMode( Qgis::AutoRefreshMode::Disabled );
|
|
}
|
|
else
|
|
{
|
|
mRefreshTimer->setInterval( interval );
|
|
}
|
|
emit autoRefreshIntervalChanged( mRefreshTimer->isActive() ? mRefreshTimer->interval() : 0 );
|
|
}
|
|
|
|
void QgsMapLayer::setAutoRefreshEnabled( bool enabled )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
setAutoRefreshMode( enabled ? Qgis::AutoRefreshMode::RedrawOnly : Qgis::AutoRefreshMode::Disabled );
|
|
}
|
|
|
|
void QgsMapLayer::setAutoRefreshMode( Qgis::AutoRefreshMode mode )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( mode == mAutoRefreshMode )
|
|
return;
|
|
|
|
mAutoRefreshMode = mode;
|
|
switch ( mAutoRefreshMode )
|
|
{
|
|
case Qgis::AutoRefreshMode::Disabled:
|
|
mRefreshTimer->stop();
|
|
break;
|
|
|
|
case Qgis::AutoRefreshMode::RedrawOnly:
|
|
case Qgis::AutoRefreshMode::ReloadData:
|
|
if ( mRefreshTimer->interval() > 0 )
|
|
mRefreshTimer->start();
|
|
break;
|
|
}
|
|
|
|
emit autoRefreshIntervalChanged( mRefreshTimer->isActive() ? mRefreshTimer->interval() : 0 );
|
|
}
|
|
|
|
const QgsLayerMetadata &QgsMapLayer::metadata() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return mMetadata;
|
|
}
|
|
|
|
void QgsMapLayer::setMaximumScale( double scale )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
mMinScale = scale;
|
|
}
|
|
|
|
double QgsMapLayer::maximumScale() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return mMinScale;
|
|
}
|
|
|
|
void QgsMapLayer::setMinimumScale( double scale )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
mMaxScale = scale;
|
|
}
|
|
|
|
void QgsMapLayer::setScaleBasedVisibility( const bool enabled )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
mScaleBasedVisibility = enabled;
|
|
}
|
|
|
|
double QgsMapLayer::minimumScale() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return mMaxScale;
|
|
}
|
|
|
|
QStringList QgsMapLayer::subLayers() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return QStringList();
|
|
}
|
|
|
|
void QgsMapLayer::setLayerOrder( const QStringList &layers )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
Q_UNUSED( layers )
|
|
}
|
|
|
|
void QgsMapLayer::setSubLayerVisibility( const QString &name, bool vis )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
Q_UNUSED( name )
|
|
Q_UNUSED( vis )
|
|
}
|
|
|
|
bool QgsMapLayer::supportsEditing() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return false;
|
|
}
|
|
|
|
QgsCoordinateReferenceSystem QgsMapLayer::crs() const
|
|
{
|
|
// non fatal for now -- the "rasterize" processing algorithm is not thread safe and calls this
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
|
|
|
|
return mCRS;
|
|
}
|
|
|
|
QgsCoordinateReferenceSystem QgsMapLayer::verticalCrs() const
|
|
{
|
|
// non fatal for now -- the "rasterize" processing algorithm is not thread safe and calls this
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
|
|
|
|
switch ( mCRS.type() )
|
|
{
|
|
case Qgis::CrsType::Vertical: // would hope this never happens!
|
|
QgsDebugError( QStringLiteral( "Layer has a vertical CRS set as the horizontal CRS!" ) );
|
|
return mCRS;
|
|
|
|
case Qgis::CrsType::Compound:
|
|
return mCRS.verticalCrs();
|
|
|
|
case Qgis::CrsType::Unknown:
|
|
case Qgis::CrsType::Geodetic:
|
|
case Qgis::CrsType::Geocentric:
|
|
case Qgis::CrsType::Geographic2d:
|
|
case Qgis::CrsType::Geographic3d:
|
|
case Qgis::CrsType::Projected:
|
|
case Qgis::CrsType::Temporal:
|
|
case Qgis::CrsType::Engineering:
|
|
case Qgis::CrsType::Bound:
|
|
case Qgis::CrsType::Other:
|
|
case Qgis::CrsType::DerivedProjected:
|
|
break;
|
|
}
|
|
return mVerticalCrs;
|
|
}
|
|
|
|
QgsCoordinateReferenceSystem QgsMapLayer::crs3D() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return mCrs3D.isValid() ? mCrs3D : mCRS;
|
|
}
|
|
|
|
void QgsMapLayer::setCrs( const QgsCoordinateReferenceSystem &srs, bool emitSignal )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
const bool needToValidateCrs = mShouldValidateCrs && isSpatial() && !srs.isValid() && type() != Qgis::LayerType::Annotation;
|
|
|
|
if ( mCRS == srs && !needToValidateCrs )
|
|
return;
|
|
|
|
const QgsCoordinateReferenceSystem oldVerticalCrs = verticalCrs();
|
|
const QgsCoordinateReferenceSystem oldCrs3D = mCrs3D;
|
|
const QgsCoordinateReferenceSystem oldCrs = mCRS;
|
|
|
|
mCRS = srs;
|
|
|
|
if ( needToValidateCrs )
|
|
{
|
|
mCRS.setValidationHint( tr( "Specify CRS for layer %1" ).arg( name() ) );
|
|
mCRS.validate();
|
|
}
|
|
|
|
rebuildCrs3D();
|
|
|
|
if ( emitSignal && mCRS != oldCrs )
|
|
emit crsChanged();
|
|
|
|
// Did vertical crs also change as a result of this? If so, emit signal
|
|
if ( oldVerticalCrs != verticalCrs() )
|
|
emit verticalCrsChanged();
|
|
if ( oldCrs3D != mCrs3D )
|
|
emit crs3DChanged();
|
|
}
|
|
|
|
bool QgsMapLayer::setVerticalCrs( const QgsCoordinateReferenceSystem &crs, QString *errorMessage )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
bool res = true;
|
|
if ( crs.isValid() )
|
|
{
|
|
// validate that passed crs is a vertical crs
|
|
switch ( crs.type() )
|
|
{
|
|
case Qgis::CrsType::Vertical:
|
|
break;
|
|
|
|
case Qgis::CrsType::Unknown:
|
|
case Qgis::CrsType::Compound:
|
|
case Qgis::CrsType::Geodetic:
|
|
case Qgis::CrsType::Geocentric:
|
|
case Qgis::CrsType::Geographic2d:
|
|
case Qgis::CrsType::Geographic3d:
|
|
case Qgis::CrsType::Projected:
|
|
case Qgis::CrsType::Temporal:
|
|
case Qgis::CrsType::Engineering:
|
|
case Qgis::CrsType::Bound:
|
|
case Qgis::CrsType::Other:
|
|
case Qgis::CrsType::DerivedProjected:
|
|
if ( errorMessage )
|
|
*errorMessage = QObject::tr( "Specified CRS is a %1 CRS, not a Vertical CRS" ).arg( qgsEnumValueToKey( crs.type() ) );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ( crs != mVerticalCrs )
|
|
{
|
|
const QgsCoordinateReferenceSystem oldVerticalCrs = verticalCrs();
|
|
const QgsCoordinateReferenceSystem oldCrs3D = mCrs3D;
|
|
|
|
switch ( mCRS.type() )
|
|
{
|
|
case Qgis::CrsType::Compound:
|
|
if ( crs != oldVerticalCrs )
|
|
{
|
|
if ( errorMessage )
|
|
*errorMessage = QObject::tr( "Layer CRS is a Compound CRS, specified Vertical CRS will be ignored" );
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case Qgis::CrsType::Geographic3d:
|
|
if ( crs != oldVerticalCrs )
|
|
{
|
|
if ( errorMessage )
|
|
*errorMessage = QObject::tr( "Layer CRS is a Geographic 3D CRS, specified Vertical CRS will be ignored" );
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case Qgis::CrsType::Geocentric:
|
|
if ( crs != oldVerticalCrs )
|
|
{
|
|
if ( errorMessage )
|
|
*errorMessage = QObject::tr( "Layer CRS is a Geocentric CRS, specified Vertical CRS will be ignored" );
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case Qgis::CrsType::Projected:
|
|
if ( mCRS.hasVerticalAxis() && crs != oldVerticalCrs )
|
|
{
|
|
if ( errorMessage )
|
|
*errorMessage = QObject::tr( "Layer CRS is a Projected 3D CRS, specified Vertical CRS will be ignored" );
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case Qgis::CrsType::Unknown:
|
|
case Qgis::CrsType::Geodetic:
|
|
case Qgis::CrsType::Geographic2d:
|
|
case Qgis::CrsType::Temporal:
|
|
case Qgis::CrsType::Engineering:
|
|
case Qgis::CrsType::Bound:
|
|
case Qgis::CrsType::Other:
|
|
case Qgis::CrsType::Vertical:
|
|
case Qgis::CrsType::DerivedProjected:
|
|
break;
|
|
}
|
|
|
|
mVerticalCrs = crs;
|
|
res = rebuildCrs3D( errorMessage );
|
|
|
|
// only emit signal if vertical crs was actually changed, so eg if mCrs is compound
|
|
// then we haven't actually changed the vertical crs by this call!
|
|
if ( verticalCrs() != oldVerticalCrs )
|
|
emit verticalCrsChanged();
|
|
if ( mCrs3D != oldCrs3D )
|
|
emit crs3DChanged();
|
|
}
|
|
return res;
|
|
}
|
|
|
|
QgsCoordinateTransformContext QgsMapLayer::transformContext() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
const QgsDataProvider *lDataProvider = dataProvider();
|
|
return lDataProvider ? lDataProvider->transformContext() : QgsCoordinateTransformContext();
|
|
}
|
|
|
|
QString QgsMapLayer::formatLayerName( const QString &name )
|
|
{
|
|
QString layerName( name );
|
|
layerName.replace( '_', ' ' );
|
|
layerName = QgsStringUtils::capitalize( layerName, Qgis::Capitalization::ForceFirstLetterToCapital );
|
|
return layerName;
|
|
}
|
|
|
|
QString QgsMapLayer::baseURI( PropertyType type ) const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
QString myURI = publicSource();
|
|
|
|
// first get base path for delimited text, spatialite and OGR layers,
|
|
// as in these cases URI may contain layer name and/or additional
|
|
// information. This also strips prefix in case if VSIFILE mechanism
|
|
// is used
|
|
if ( providerType() == QLatin1String( "ogr" ) || providerType() == QLatin1String( "delimitedtext" )
|
|
|| providerType() == QLatin1String( "gdal" ) || providerType() == QLatin1String( "spatialite" ) )
|
|
{
|
|
QVariantMap components = QgsProviderRegistry::instance()->decodeUri( providerType(), myURI );
|
|
myURI = components["path"].toString();
|
|
}
|
|
|
|
QFileInfo myFileInfo( myURI );
|
|
QString key;
|
|
|
|
if ( myFileInfo.exists() )
|
|
{
|
|
// if file is using the /vsizip/ or /vsigzip/ mechanism, cleanup the name
|
|
if ( myURI.endsWith( QLatin1String( ".gz" ), Qt::CaseInsensitive ) )
|
|
myURI.chop( 3 );
|
|
else if ( myURI.endsWith( QLatin1String( ".zip" ), Qt::CaseInsensitive ) )
|
|
myURI.chop( 4 );
|
|
else if ( myURI.endsWith( QLatin1String( ".tar" ), Qt::CaseInsensitive ) )
|
|
myURI.chop( 4 );
|
|
else if ( myURI.endsWith( QLatin1String( ".tar.gz" ), Qt::CaseInsensitive ) )
|
|
myURI.chop( 7 );
|
|
else if ( myURI.endsWith( QLatin1String( ".tgz" ), Qt::CaseInsensitive ) )
|
|
myURI.chop( 4 );
|
|
myFileInfo.setFile( myURI );
|
|
// get the file name for our .qml style file
|
|
key = myFileInfo.path() + QDir::separator() + myFileInfo.completeBaseName() + QgsMapLayer::extensionPropertyType( type );
|
|
}
|
|
else
|
|
{
|
|
key = publicSource();
|
|
}
|
|
|
|
return key;
|
|
}
|
|
|
|
QString QgsMapLayer::metadataUri() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return baseURI( PropertyType::Metadata );
|
|
}
|
|
|
|
QString QgsMapLayer::saveDefaultMetadata( bool &resultFlag )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( const QgsProviderMetadata *metadata = QgsProviderRegistry::instance()->providerMetadata( providerType() ) )
|
|
{
|
|
if ( metadata->providerCapabilities() & QgsProviderMetadata::SaveLayerMetadata )
|
|
{
|
|
try
|
|
{
|
|
QString errorMessage;
|
|
resultFlag = QgsProviderRegistry::instance()->saveLayerMetadata( providerType(), mDataSource, mMetadata, errorMessage );
|
|
if ( resultFlag )
|
|
return tr( "Successfully saved default layer metadata" );
|
|
else
|
|
return errorMessage;
|
|
}
|
|
catch ( QgsNotSupportedException &e )
|
|
{
|
|
resultFlag = false;
|
|
return e.what();
|
|
}
|
|
}
|
|
}
|
|
|
|
// fallback default metadata saving method, for providers which don't support (or implement) saveLayerMetadata
|
|
return saveNamedMetadata( metadataUri(), resultFlag );
|
|
}
|
|
|
|
QString QgsMapLayer::loadDefaultMetadata( bool &resultFlag )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return loadNamedMetadata( metadataUri(), resultFlag );
|
|
}
|
|
|
|
QString QgsMapLayer::styleURI() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return baseURI( PropertyType::Style );
|
|
}
|
|
|
|
QString QgsMapLayer::loadDefaultStyle( bool &resultFlag )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return loadNamedStyle( styleURI(), resultFlag, QgsMapLayer::AllStyleCategories, Qgis::LoadStyleFlag::IgnoreMissingStyleErrors );
|
|
}
|
|
|
|
bool QgsMapLayer::loadNamedMetadataFromDatabase( const QString &db, const QString &uri, QString &qmd )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return loadNamedPropertyFromDatabase( db, uri, qmd, PropertyType::Metadata );
|
|
}
|
|
|
|
bool QgsMapLayer::loadNamedStyleFromDatabase( const QString &db, const QString &uri, QString &qml )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return loadNamedPropertyFromDatabase( db, uri, qml, PropertyType::Style );
|
|
}
|
|
|
|
bool QgsMapLayer::loadNamedPropertyFromDatabase( const QString &db, const QString &uri, QString &xml, QgsMapLayer::PropertyType type )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
QgsDebugMsgLevel( QStringLiteral( "db = %1 uri = %2" ).arg( db, uri ), 4 );
|
|
|
|
bool resultFlag = false;
|
|
|
|
// read from database
|
|
sqlite3_database_unique_ptr database;
|
|
sqlite3_statement_unique_ptr statement;
|
|
|
|
int myResult;
|
|
|
|
QgsDebugMsgLevel( QStringLiteral( "Trying to load style or metadata for \"%1\" from \"%2\"" ).arg( uri, db ), 4 );
|
|
|
|
if ( db.isEmpty() || !QFile( db ).exists() )
|
|
return false;
|
|
|
|
myResult = database.open_v2( db, SQLITE_OPEN_READONLY, nullptr );
|
|
if ( myResult != SQLITE_OK )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
QString mySql;
|
|
switch ( type )
|
|
{
|
|
case Metadata:
|
|
mySql = QStringLiteral( "select qmd from tbl_metadata where metadata=?" );
|
|
break;
|
|
|
|
case Style:
|
|
mySql = QStringLiteral( "select qml from tbl_styles where style=?" );
|
|
break;
|
|
}
|
|
|
|
statement = database.prepare( mySql, myResult );
|
|
if ( myResult == SQLITE_OK )
|
|
{
|
|
QByteArray param = uri.toUtf8();
|
|
|
|
if ( sqlite3_bind_text( statement.get(), 1, param.data(), param.length(), SQLITE_STATIC ) == SQLITE_OK &&
|
|
sqlite3_step( statement.get() ) == SQLITE_ROW )
|
|
{
|
|
xml = QString::fromUtf8( reinterpret_cast< const char * >( sqlite3_column_text( statement.get(), 0 ) ) );
|
|
resultFlag = true;
|
|
}
|
|
}
|
|
return resultFlag;
|
|
}
|
|
|
|
|
|
QString QgsMapLayer::loadNamedStyle( const QString &uri, bool &resultFlag, QgsMapLayer::StyleCategories categories, Qgis::LoadStyleFlags flags )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return loadNamedStyle( uri, resultFlag, false, categories, flags );
|
|
}
|
|
|
|
QString QgsMapLayer::loadNamedProperty( const QString &uri, QgsMapLayer::PropertyType type, bool &namedPropertyExists, bool &propertySuccessfullyLoaded, StyleCategories categories, Qgis::LoadStyleFlags flags )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
QgsDebugMsgLevel( QStringLiteral( "uri = %1 myURI = %2" ).arg( uri, publicSource() ), 4 );
|
|
|
|
namedPropertyExists = false;
|
|
propertySuccessfullyLoaded = false;
|
|
if ( uri.isEmpty() )
|
|
return QString();
|
|
|
|
QDomDocument myDocument( QStringLiteral( "qgis" ) );
|
|
|
|
// location of problem associated with errorMsg
|
|
int line, column;
|
|
QString myErrorMessage;
|
|
|
|
QFile myFile( uri );
|
|
if ( myFile.open( QFile::ReadOnly ) )
|
|
{
|
|
QgsDebugMsgLevel( QStringLiteral( "file found %1" ).arg( uri ), 2 );
|
|
namedPropertyExists = true;
|
|
|
|
// read file
|
|
propertySuccessfullyLoaded = myDocument.setContent( &myFile, &myErrorMessage, &line, &column );
|
|
if ( !propertySuccessfullyLoaded )
|
|
myErrorMessage = tr( "%1 at line %2 column %3" ).arg( myErrorMessage ).arg( line ).arg( column );
|
|
myFile.close();
|
|
}
|
|
else
|
|
{
|
|
const QFileInfo project( QgsProject::instance()->fileName() ); // skip-keyword-check
|
|
QgsDebugMsgLevel( QStringLiteral( "project fileName: %1" ).arg( project.absoluteFilePath() ), 4 );
|
|
|
|
QString xml;
|
|
switch ( type )
|
|
{
|
|
case QgsMapLayer::Style:
|
|
{
|
|
if ( loadNamedStyleFromDatabase( QDir( QgsApplication::qgisSettingsDirPath() ).absoluteFilePath( QStringLiteral( "qgis.qmldb" ) ), uri, xml ) ||
|
|
( project.exists() && loadNamedStyleFromDatabase( project.absoluteDir().absoluteFilePath( project.baseName() + ".qmldb" ), uri, xml ) ) ||
|
|
loadNamedStyleFromDatabase( QDir( QgsApplication::pkgDataPath() ).absoluteFilePath( QStringLiteral( "resources/qgis.qmldb" ) ), uri, xml ) )
|
|
{
|
|
namedPropertyExists = true;
|
|
propertySuccessfullyLoaded = myDocument.setContent( xml, &myErrorMessage, &line, &column );
|
|
if ( !propertySuccessfullyLoaded )
|
|
{
|
|
myErrorMessage = tr( "%1 at line %2 column %3" ).arg( myErrorMessage ).arg( line ).arg( column );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( !flags.testFlag( Qgis::LoadStyleFlag::IgnoreMissingStyleErrors ) )
|
|
{
|
|
myErrorMessage = tr( "Style not found in database" );
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case QgsMapLayer::Metadata:
|
|
{
|
|
if ( loadNamedMetadataFromDatabase( QDir( QgsApplication::qgisSettingsDirPath() ).absoluteFilePath( QStringLiteral( "qgis.qmldb" ) ), uri, xml ) ||
|
|
( project.exists() && loadNamedMetadataFromDatabase( project.absoluteDir().absoluteFilePath( project.baseName() + ".qmldb" ), uri, xml ) ) ||
|
|
loadNamedMetadataFromDatabase( QDir( QgsApplication::pkgDataPath() ).absoluteFilePath( QStringLiteral( "resources/qgis.qmldb" ) ), uri, xml ) )
|
|
{
|
|
namedPropertyExists = true;
|
|
propertySuccessfullyLoaded = myDocument.setContent( xml, &myErrorMessage, &line, &column );
|
|
if ( !propertySuccessfullyLoaded )
|
|
{
|
|
myErrorMessage = tr( "%1 at line %2 column %3" ).arg( myErrorMessage ).arg( line ).arg( column );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
myErrorMessage = tr( "Metadata not found in database" );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !propertySuccessfullyLoaded )
|
|
{
|
|
return myErrorMessage;
|
|
}
|
|
|
|
switch ( type )
|
|
{
|
|
case QgsMapLayer::Style:
|
|
propertySuccessfullyLoaded = importNamedStyle( myDocument, myErrorMessage, categories );
|
|
if ( !propertySuccessfullyLoaded )
|
|
myErrorMessage = tr( "Loading style file %1 failed because:\n%2" ).arg( uri, myErrorMessage );
|
|
break;
|
|
case QgsMapLayer::Metadata:
|
|
propertySuccessfullyLoaded = importNamedMetadata( myDocument, myErrorMessage );
|
|
if ( !propertySuccessfullyLoaded )
|
|
myErrorMessage = tr( "Loading metadata file %1 failed because:\n%2" ).arg( uri, myErrorMessage );
|
|
break;
|
|
}
|
|
return myErrorMessage;
|
|
}
|
|
|
|
bool QgsMapLayer::importNamedMetadata( QDomDocument &document, QString &errorMessage )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
const QDomElement myRoot = document.firstChildElement( QStringLiteral( "qgis" ) );
|
|
if ( myRoot.isNull() )
|
|
{
|
|
errorMessage = tr( "Root <qgis> element could not be found" );
|
|
return false;
|
|
}
|
|
|
|
return mMetadata.readMetadataXml( myRoot );
|
|
}
|
|
|
|
bool QgsMapLayer::importNamedStyle( QDomDocument &myDocument, QString &myErrorMessage, QgsMapLayer::StyleCategories categories )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
const QDomElement myRoot = myDocument.firstChildElement( QStringLiteral( "qgis" ) );
|
|
if ( myRoot.isNull() )
|
|
{
|
|
myErrorMessage = tr( "Root <qgis> element could not be found" );
|
|
return false;
|
|
}
|
|
|
|
// get style file version string, if any
|
|
const QgsProjectVersion fileVersion( myRoot.attribute( QStringLiteral( "version" ) ) );
|
|
const QgsProjectVersion thisVersion( Qgis::version() );
|
|
|
|
if ( thisVersion > fileVersion )
|
|
{
|
|
QgsProjectFileTransform styleFile( myDocument, fileVersion );
|
|
styleFile.updateRevision( thisVersion );
|
|
}
|
|
|
|
// Get source categories
|
|
const QgsMapLayer::StyleCategories sourceCategories = QgsXmlUtils::readFlagAttribute( myRoot, QStringLiteral( "styleCategories" ), QgsMapLayer::AllStyleCategories );
|
|
|
|
//Test for matching geometry type on vector layers when applying, if geometry type is given in the style
|
|
if ( ( sourceCategories.testFlag( QgsMapLayer::Symbology ) || sourceCategories.testFlag( QgsMapLayer::Symbology3D ) ) &&
|
|
( categories.testFlag( QgsMapLayer::Symbology ) || categories.testFlag( QgsMapLayer::Symbology3D ) ) )
|
|
{
|
|
if ( type() == Qgis::LayerType::Vector && !myRoot.firstChildElement( QStringLiteral( "layerGeometryType" ) ).isNull() )
|
|
{
|
|
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( this );
|
|
const Qgis::GeometryType importLayerGeometryType = static_cast<Qgis::GeometryType>( myRoot.firstChildElement( QStringLiteral( "layerGeometryType" ) ).text().toInt() );
|
|
if ( importLayerGeometryType != Qgis::GeometryType::Unknown && vl->geometryType() != importLayerGeometryType )
|
|
{
|
|
myErrorMessage = tr( "Cannot apply style with symbology to layer with a different geometry type" );
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
QgsReadWriteContext context = QgsReadWriteContext();
|
|
return readSymbology( myRoot, myErrorMessage, context, categories ); // TODO: support relative paths in QML?
|
|
}
|
|
|
|
void QgsMapLayer::exportNamedMetadata( QDomDocument &doc, QString &errorMsg ) const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
QDomImplementation DomImplementation;
|
|
const QDomDocumentType documentType = DomImplementation.createDocumentType( QStringLiteral( "qgis" ), QStringLiteral( "http://mrcc.com/qgis.dtd" ), QStringLiteral( "SYSTEM" ) );
|
|
QDomDocument myDocument( documentType );
|
|
|
|
QDomElement myRootNode = myDocument.createElement( QStringLiteral( "qgis" ) );
|
|
myRootNode.setAttribute( QStringLiteral( "version" ), Qgis::version() );
|
|
myDocument.appendChild( myRootNode );
|
|
|
|
if ( !mMetadata.writeMetadataXml( myRootNode, myDocument ) )
|
|
{
|
|
errorMsg = QObject::tr( "Could not save metadata" );
|
|
return;
|
|
}
|
|
|
|
doc = myDocument;
|
|
}
|
|
|
|
void QgsMapLayer::exportNamedStyle( QDomDocument &doc, QString &errorMsg, const QgsReadWriteContext &context, QgsMapLayer::StyleCategories categories ) const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
QDomImplementation DomImplementation;
|
|
const QDomDocumentType documentType = DomImplementation.createDocumentType( QStringLiteral( "qgis" ), QStringLiteral( "http://mrcc.com/qgis.dtd" ), QStringLiteral( "SYSTEM" ) );
|
|
QDomDocument myDocument( documentType );
|
|
|
|
QDomElement myRootNode = myDocument.createElement( QStringLiteral( "qgis" ) );
|
|
myRootNode.setAttribute( QStringLiteral( "version" ), Qgis::version() );
|
|
myDocument.appendChild( myRootNode );
|
|
|
|
if ( !writeSymbology( myRootNode, myDocument, errorMsg, context, categories ) ) // TODO: support relative paths in QML?
|
|
{
|
|
errorMsg = QObject::tr( "Could not save symbology because:\n%1" ).arg( errorMsg );
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Check to see if the layer is vector - in which case we should also export its geometryType
|
|
* to avoid eventually pasting to a layer with a different geometry
|
|
*/
|
|
if ( type() == Qgis::LayerType::Vector )
|
|
{
|
|
//Getting the selectionLayer geometry
|
|
const QgsVectorLayer *vl = qobject_cast<const QgsVectorLayer *>( this );
|
|
const QString geoType = QString::number( static_cast<int>( vl->geometryType() ) );
|
|
|
|
//Adding geometryinformation
|
|
QDomElement layerGeometryType = myDocument.createElement( QStringLiteral( "layerGeometryType" ) );
|
|
const QDomText type = myDocument.createTextNode( geoType );
|
|
|
|
layerGeometryType.appendChild( type );
|
|
myRootNode.appendChild( layerGeometryType );
|
|
}
|
|
|
|
doc = myDocument;
|
|
}
|
|
|
|
QString QgsMapLayer::saveDefaultStyle( bool &resultFlag )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return saveDefaultStyle( resultFlag, AllStyleCategories );
|
|
}
|
|
|
|
QString QgsMapLayer::saveDefaultStyle( bool &resultFlag, StyleCategories categories )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return saveNamedStyle( styleURI(), resultFlag, categories );
|
|
}
|
|
|
|
QString QgsMapLayer::saveNamedMetadata( const QString &uri, bool &resultFlag )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return saveNamedProperty( uri, QgsMapLayer::Metadata, resultFlag );
|
|
}
|
|
|
|
QString QgsMapLayer::loadNamedMetadata( const QString &uri, bool &resultFlag )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
bool metadataExists = false;
|
|
bool metadataSuccessfullyLoaded = false;
|
|
const QString message = loadNamedProperty( uri, QgsMapLayer::Metadata, metadataExists, metadataSuccessfullyLoaded );
|
|
|
|
// TODO QGIS 4.0 -- fix API for loadNamedMetadata so we can return metadataExists too
|
|
( void )metadataExists;
|
|
resultFlag = metadataSuccessfullyLoaded;
|
|
return message;
|
|
}
|
|
|
|
QString QgsMapLayer::saveNamedProperty( const QString &uri, QgsMapLayer::PropertyType type, bool &resultFlag, StyleCategories categories )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
// check if the uri is a file or ends with .qml/.qmd,
|
|
// which indicates that it should become one
|
|
// everything else goes to the database
|
|
QString filename;
|
|
|
|
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( this );
|
|
if ( vlayer && vlayer->providerType() == QLatin1String( "ogr" ) )
|
|
{
|
|
QStringList theURIParts = uri.split( '|' );
|
|
filename = theURIParts[0];
|
|
}
|
|
else if ( vlayer && vlayer->providerType() == QLatin1String( "gpx" ) )
|
|
{
|
|
QStringList theURIParts = uri.split( '?' );
|
|
filename = theURIParts[0];
|
|
}
|
|
else if ( vlayer && vlayer->providerType() == QLatin1String( "delimitedtext" ) )
|
|
{
|
|
filename = QUrl::fromEncoded( uri.toLatin1() ).toLocalFile();
|
|
// toLocalFile() returns an empty string if theURI is a plain Windows-path, e.g. "C:/style.qml"
|
|
if ( filename.isEmpty() )
|
|
filename = uri;
|
|
}
|
|
else
|
|
{
|
|
filename = uri;
|
|
}
|
|
|
|
QString myErrorMessage;
|
|
QDomDocument myDocument;
|
|
switch ( type )
|
|
{
|
|
case Metadata:
|
|
exportNamedMetadata( myDocument, myErrorMessage );
|
|
break;
|
|
|
|
case Style:
|
|
const QgsReadWriteContext context;
|
|
exportNamedStyle( myDocument, myErrorMessage, context, categories );
|
|
break;
|
|
}
|
|
|
|
const QFileInfo myFileInfo( filename );
|
|
if ( myFileInfo.exists() || filename.endsWith( QgsMapLayer::extensionPropertyType( type ), Qt::CaseInsensitive ) )
|
|
{
|
|
const QFileInfo myDirInfo( myFileInfo.path() ); //excludes file name
|
|
if ( !myDirInfo.isWritable() )
|
|
{
|
|
resultFlag = false;
|
|
return tr( "The directory containing your dataset needs to be writable!" );
|
|
}
|
|
|
|
// now construct the file name for our .qml or .qmd file
|
|
const QString myFileName = myFileInfo.path() + QDir::separator() + myFileInfo.completeBaseName() + QgsMapLayer::extensionPropertyType( type );
|
|
|
|
QFile myFile( myFileName );
|
|
if ( myFile.open( QFile::WriteOnly | QFile::Truncate ) )
|
|
{
|
|
QTextStream myFileStream( &myFile );
|
|
// save as utf-8 with 2 spaces for indents
|
|
myDocument.save( myFileStream, 2 );
|
|
myFile.close();
|
|
resultFlag = true;
|
|
switch ( type )
|
|
{
|
|
case Metadata:
|
|
return tr( "Created default metadata file as %1" ).arg( myFileName );
|
|
|
|
case Style:
|
|
return tr( "Created default style file as %1" ).arg( myFileName );
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
resultFlag = false;
|
|
switch ( type )
|
|
{
|
|
case Metadata:
|
|
return tr( "ERROR: Failed to created default metadata file as %1. Check file permissions and retry." ).arg( myFileName );
|
|
|
|
case Style:
|
|
return tr( "ERROR: Failed to created default style file as %1. Check file permissions and retry." ).arg( myFileName );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const QString qml = myDocument.toString();
|
|
|
|
// read from database
|
|
sqlite3_database_unique_ptr database;
|
|
sqlite3_statement_unique_ptr statement;
|
|
|
|
int myResult = database.open( QDir( QgsApplication::qgisSettingsDirPath() ).absoluteFilePath( QStringLiteral( "qgis.qmldb" ) ) );
|
|
if ( myResult != SQLITE_OK )
|
|
{
|
|
return tr( "User database could not be opened." );
|
|
}
|
|
|
|
QByteArray param0 = uri.toUtf8();
|
|
QByteArray param1 = qml.toUtf8();
|
|
|
|
QString mySql;
|
|
switch ( type )
|
|
{
|
|
case Metadata:
|
|
mySql = QStringLiteral( "create table if not exists tbl_metadata(metadata varchar primary key,qmd varchar)" );
|
|
break;
|
|
|
|
case Style:
|
|
mySql = QStringLiteral( "create table if not exists tbl_styles(style varchar primary key,qml varchar)" );
|
|
break;
|
|
}
|
|
|
|
statement = database.prepare( mySql, myResult );
|
|
if ( myResult == SQLITE_OK )
|
|
{
|
|
if ( sqlite3_step( statement.get() ) != SQLITE_DONE )
|
|
{
|
|
resultFlag = false;
|
|
switch ( type )
|
|
{
|
|
case Metadata:
|
|
return tr( "The metadata table could not be created." );
|
|
|
|
case Style:
|
|
return tr( "The style table could not be created." );
|
|
}
|
|
}
|
|
}
|
|
|
|
switch ( type )
|
|
{
|
|
case Metadata:
|
|
mySql = QStringLiteral( "insert into tbl_metadata(metadata,qmd) values (?,?)" );
|
|
break;
|
|
|
|
case Style:
|
|
mySql = QStringLiteral( "insert into tbl_styles(style,qml) values (?,?)" );
|
|
break;
|
|
}
|
|
statement = database.prepare( mySql, myResult );
|
|
if ( myResult == SQLITE_OK )
|
|
{
|
|
if ( sqlite3_bind_text( statement.get(), 1, param0.data(), param0.length(), SQLITE_STATIC ) == SQLITE_OK &&
|
|
sqlite3_bind_text( statement.get(), 2, param1.data(), param1.length(), SQLITE_STATIC ) == SQLITE_OK &&
|
|
sqlite3_step( statement.get() ) == SQLITE_DONE )
|
|
{
|
|
resultFlag = true;
|
|
switch ( type )
|
|
{
|
|
case Metadata:
|
|
myErrorMessage = tr( "The metadata %1 was saved to database" ).arg( uri );
|
|
break;
|
|
|
|
case Style:
|
|
myErrorMessage = tr( "The style %1 was saved to database" ).arg( uri );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !resultFlag )
|
|
{
|
|
QString mySql;
|
|
switch ( type )
|
|
{
|
|
case Metadata:
|
|
mySql = QStringLiteral( "update tbl_metadata set qmd=? where metadata=?" );
|
|
break;
|
|
|
|
case Style:
|
|
mySql = QStringLiteral( "update tbl_styles set qml=? where style=?" );
|
|
break;
|
|
}
|
|
statement = database.prepare( mySql, myResult );
|
|
if ( myResult == SQLITE_OK )
|
|
{
|
|
if ( sqlite3_bind_text( statement.get(), 2, param0.data(), param0.length(), SQLITE_STATIC ) == SQLITE_OK &&
|
|
sqlite3_bind_text( statement.get(), 1, param1.data(), param1.length(), SQLITE_STATIC ) == SQLITE_OK &&
|
|
sqlite3_step( statement.get() ) == SQLITE_DONE )
|
|
{
|
|
resultFlag = true;
|
|
switch ( type )
|
|
{
|
|
case Metadata:
|
|
myErrorMessage = tr( "The metadata %1 was updated in the database." ).arg( uri );
|
|
break;
|
|
|
|
case Style:
|
|
myErrorMessage = tr( "The style %1 was updated in the database." ).arg( uri );
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
resultFlag = false;
|
|
switch ( type )
|
|
{
|
|
case Metadata:
|
|
myErrorMessage = tr( "The metadata %1 could not be updated in the database." ).arg( uri );
|
|
break;
|
|
|
|
case Style:
|
|
myErrorMessage = tr( "The style %1 could not be updated in the database." ).arg( uri );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
resultFlag = false;
|
|
switch ( type )
|
|
{
|
|
case Metadata:
|
|
myErrorMessage = tr( "The metadata %1 could not be inserted into database." ).arg( uri );
|
|
break;
|
|
|
|
case Style:
|
|
myErrorMessage = tr( "The style %1 could not be inserted into database." ).arg( uri );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return myErrorMessage;
|
|
}
|
|
|
|
QString QgsMapLayer::saveNamedStyle( const QString &uri, bool &resultFlag, StyleCategories categories )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return saveNamedProperty( uri, QgsMapLayer::Style, resultFlag, categories );
|
|
}
|
|
|
|
void QgsMapLayer::exportSldStyle( QDomDocument &doc, QString &errorMsg ) const
|
|
{
|
|
QgsSldExportContext exportContext;
|
|
doc = exportSldStyleV3( exportContext );
|
|
if ( !exportContext.errors().empty() )
|
|
errorMsg = exportContext.errors().join( "\n" );
|
|
}
|
|
|
|
void QgsMapLayer::exportSldStyleV2( QDomDocument &doc, QString &errorMsg, QgsSldExportContext &exportContext ) const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
doc = exportSldStyleV3( exportContext );
|
|
if ( !exportContext.errors().empty() )
|
|
errorMsg = exportContext.errors().join( "\n" );
|
|
}
|
|
|
|
QDomDocument QgsMapLayer::exportSldStyleV3( QgsSldExportContext &exportContext ) const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
QDomDocument myDocument = QDomDocument();
|
|
|
|
const QDomNode header = myDocument.createProcessingInstruction( QStringLiteral( "xml" ), QStringLiteral( "version=\"1.0\" encoding=\"UTF-8\"" ) );
|
|
myDocument.appendChild( header );
|
|
|
|
const QgsVectorLayer *vlayer = qobject_cast<const QgsVectorLayer *>( this );
|
|
const QgsRasterLayer *rlayer = qobject_cast<const QgsRasterLayer *>( this );
|
|
if ( !vlayer && !rlayer )
|
|
{
|
|
exportContext.pushError( tr( "Could not save symbology because:\n%1" )
|
|
.arg( tr( "Only vector and raster layers are supported" ) ) );
|
|
return myDocument;
|
|
}
|
|
|
|
// Create the root element
|
|
QDomElement root = myDocument.createElementNS( QStringLiteral( "http://www.opengis.net/sld" ), QStringLiteral( "StyledLayerDescriptor" ) );
|
|
QDomElement layerNode;
|
|
if ( vlayer )
|
|
{
|
|
root.setAttribute( QStringLiteral( "version" ), QStringLiteral( "1.1.0" ) );
|
|
root.setAttribute( QStringLiteral( "xsi:schemaLocation" ), QStringLiteral( "http://www.opengis.net/sld http://schemas.opengis.net/sld/1.1.0/StyledLayerDescriptor.xsd" ) );
|
|
root.setAttribute( QStringLiteral( "xmlns:ogc" ), QStringLiteral( "http://www.opengis.net/ogc" ) );
|
|
root.setAttribute( QStringLiteral( "xmlns:se" ), QStringLiteral( "http://www.opengis.net/se" ) );
|
|
root.setAttribute( QStringLiteral( "xmlns:xlink" ), QStringLiteral( "http://www.w3.org/1999/xlink" ) );
|
|
root.setAttribute( QStringLiteral( "xmlns:xsi" ), QStringLiteral( "http://www.w3.org/2001/XMLSchema-instance" ) );
|
|
myDocument.appendChild( root );
|
|
|
|
// Create the NamedLayer element
|
|
layerNode = myDocument.createElement( QStringLiteral( "NamedLayer" ) );
|
|
root.appendChild( layerNode );
|
|
}
|
|
|
|
// note: Only SLD 1.0 version is generated because seems none is using SE1.1.0 at least for rasters
|
|
if ( rlayer )
|
|
{
|
|
// Create the root element
|
|
root.setAttribute( QStringLiteral( "version" ), QStringLiteral( "1.0.0" ) );
|
|
root.setAttribute( QStringLiteral( "xmlns:gml" ), QStringLiteral( "http://www.opengis.net/gml" ) );
|
|
root.setAttribute( QStringLiteral( "xmlns:ogc" ), QStringLiteral( "http://www.opengis.net/ogc" ) );
|
|
root.setAttribute( QStringLiteral( "xmlns:sld" ), QStringLiteral( "http://www.opengis.net/sld" ) );
|
|
myDocument.appendChild( root );
|
|
|
|
// Create the NamedLayer element
|
|
layerNode = myDocument.createElement( QStringLiteral( "UserLayer" ) );
|
|
root.appendChild( layerNode );
|
|
}
|
|
|
|
QVariantMap props = exportContext.extraProperties();
|
|
|
|
QVariant context;
|
|
context.setValue( exportContext );
|
|
|
|
// TODO -- move this to proper members of QgsSldExportContext
|
|
props[ QStringLiteral( "SldExportContext" ) ] = context;
|
|
|
|
if ( hasScaleBasedVisibility() )
|
|
{
|
|
props[ QStringLiteral( "scaleMinDenom" ) ] = QString::number( mMinScale );
|
|
props[ QStringLiteral( "scaleMaxDenom" ) ] = QString::number( mMaxScale );
|
|
}
|
|
exportContext.setExtraProperties( props );
|
|
|
|
if ( vlayer )
|
|
{
|
|
if ( !vlayer->writeSld( layerNode, myDocument, exportContext ) )
|
|
{
|
|
return myDocument;
|
|
}
|
|
}
|
|
else if ( rlayer )
|
|
{
|
|
if ( !rlayer->writeSld( layerNode, myDocument, exportContext ) )
|
|
{
|
|
return myDocument;
|
|
}
|
|
}
|
|
|
|
return myDocument;
|
|
}
|
|
|
|
QString QgsMapLayer::saveSldStyle( const QString &uri, bool &resultFlag ) const
|
|
{
|
|
QgsSldExportContext context;
|
|
context.setExportFilePath( uri );
|
|
return saveSldStyleV2( resultFlag, context );
|
|
}
|
|
|
|
QString QgsMapLayer::saveSldStyleV2( bool &resultFlag, QgsSldExportContext &exportContext ) const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
const QgsMapLayer *mlayer = qobject_cast<const QgsMapLayer *>( this );
|
|
|
|
const QString uri { exportContext.exportFilePath() };
|
|
|
|
// check if the uri is a file or ends with .sld,
|
|
// which indicates that it should become one
|
|
QString filename;
|
|
if ( mlayer->providerType() == QLatin1String( "ogr" ) )
|
|
{
|
|
QStringList theURIParts = uri.split( '|' );
|
|
filename = theURIParts[0];
|
|
}
|
|
else if ( mlayer->providerType() == QLatin1String( "gpx" ) )
|
|
{
|
|
QStringList theURIParts = uri.split( '?' );
|
|
filename = theURIParts[0];
|
|
}
|
|
else if ( mlayer->providerType() == QLatin1String( "delimitedtext" ) )
|
|
{
|
|
filename = QUrl::fromEncoded( uri.toLatin1() ).toLocalFile();
|
|
// toLocalFile() returns an empty string if theURI is a plain Windows-path, e.g. "C:/style.qml"
|
|
if ( filename.isEmpty() )
|
|
filename = uri;
|
|
}
|
|
else
|
|
{
|
|
filename = uri;
|
|
}
|
|
|
|
const QFileInfo myFileInfo( filename );
|
|
if ( myFileInfo.exists() || filename.endsWith( QLatin1String( ".sld" ), Qt::CaseInsensitive ) )
|
|
{
|
|
const QFileInfo myDirInfo( myFileInfo.path() ); //excludes file name
|
|
if ( !myDirInfo.isWritable() )
|
|
{
|
|
resultFlag = false;
|
|
return tr( "The directory containing your dataset needs to be writable!" );
|
|
}
|
|
|
|
// now construct the file name for our .sld style file
|
|
const QString myFileName = myFileInfo.path() + QDir::separator() + myFileInfo.completeBaseName() + ".sld";
|
|
|
|
QgsSldExportContext context { exportContext };
|
|
context.setExportFilePath( myFileName );
|
|
|
|
QDomDocument myDocument = mlayer->exportSldStyleV3( context );
|
|
|
|
if ( !context.errors().empty() )
|
|
{
|
|
resultFlag = false;
|
|
return context.errors().join( '\n' );
|
|
}
|
|
|
|
QFile myFile( myFileName );
|
|
if ( myFile.open( QFile::WriteOnly | QFile::Truncate ) )
|
|
{
|
|
QTextStream myFileStream( &myFile );
|
|
// save as utf-8 with 2 spaces for indents
|
|
myDocument.save( myFileStream, 2 );
|
|
myFile.close();
|
|
resultFlag = true;
|
|
return tr( "Created default style file as %1" ).arg( myFileName );
|
|
}
|
|
}
|
|
|
|
resultFlag = false;
|
|
return tr( "ERROR: Failed to created SLD style file as %1. Check file permissions and retry." ).arg( filename );
|
|
|
|
}
|
|
|
|
QString QgsMapLayer::loadSldStyle( const QString &uri, bool &resultFlag )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
resultFlag = false;
|
|
|
|
QDomDocument myDocument;
|
|
|
|
// location of problem associated with errorMsg
|
|
int line = 0, column = 0;
|
|
QString myErrorMessage;
|
|
|
|
QFile myFile( uri );
|
|
if ( myFile.open( QFile::ReadOnly ) )
|
|
{
|
|
// read file
|
|
#if QT_VERSION >= QT_VERSION_CHECK( 6, 5, 0 )
|
|
QXmlStreamReader xmlReader( &myFile );
|
|
xmlReader.addExtraNamespaceDeclaration( QXmlStreamNamespaceDeclaration( QStringLiteral( "sld" ), QStringLiteral( "http://www.opengis.net/sld" ) ) );
|
|
xmlReader.addExtraNamespaceDeclaration( QXmlStreamNamespaceDeclaration( QStringLiteral( "fes" ), QStringLiteral( "http://www.opengis.net/fes/2.0" ) ) );
|
|
xmlReader.addExtraNamespaceDeclaration( QXmlStreamNamespaceDeclaration( QStringLiteral( "ogc" ), QStringLiteral( "http://www.opengis.net/ogc" ) ) );
|
|
const QDomDocument::ParseResult result = myDocument.setContent( &xmlReader, QDomDocument::ParseOption::UseNamespaceProcessing );
|
|
if ( result )
|
|
{
|
|
resultFlag = true;
|
|
}
|
|
else
|
|
{
|
|
myErrorMessage = result.errorMessage;
|
|
line = result.errorLine;
|
|
column = result.errorColumn;
|
|
}
|
|
#else
|
|
resultFlag = myDocument.setContent( &myFile, true, &myErrorMessage, &line, &column );
|
|
#endif
|
|
if ( !resultFlag )
|
|
myErrorMessage = tr( "%1 at line %2 column %3" ).arg( myErrorMessage ).arg( line ).arg( column );
|
|
myFile.close();
|
|
}
|
|
else
|
|
{
|
|
myErrorMessage = tr( "Unable to open file %1" ).arg( uri );
|
|
}
|
|
|
|
if ( !resultFlag )
|
|
{
|
|
return myErrorMessage;
|
|
}
|
|
|
|
// check for root SLD element
|
|
const QDomElement myRoot = myDocument.firstChildElement( QStringLiteral( "StyledLayerDescriptor" ) );
|
|
if ( myRoot.isNull() )
|
|
{
|
|
myErrorMessage = QStringLiteral( "Error: StyledLayerDescriptor element not found in %1" ).arg( uri );
|
|
resultFlag = false;
|
|
return myErrorMessage;
|
|
}
|
|
|
|
// now get the style node out and pass it over to the layer
|
|
// to deserialise...
|
|
const QDomElement namedLayerElem = myRoot.firstChildElement( QStringLiteral( "NamedLayer" ) );
|
|
if ( namedLayerElem.isNull() )
|
|
{
|
|
myErrorMessage = QStringLiteral( "Info: NamedLayer element not found." );
|
|
resultFlag = false;
|
|
return myErrorMessage;
|
|
}
|
|
|
|
QString errorMsg;
|
|
resultFlag = readSld( namedLayerElem, errorMsg );
|
|
if ( !resultFlag )
|
|
{
|
|
myErrorMessage = tr( "Loading style file %1 failed because:\n%2" ).arg( uri, errorMsg );
|
|
return myErrorMessage;
|
|
}
|
|
|
|
return QString();
|
|
}
|
|
|
|
bool QgsMapLayer::readStyle( const QDomNode &node, QString &errorMessage, QgsReadWriteContext &context, QgsMapLayer::StyleCategories categories )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
Q_UNUSED( node )
|
|
Q_UNUSED( errorMessage )
|
|
Q_UNUSED( context )
|
|
Q_UNUSED( categories )
|
|
return false;
|
|
}
|
|
|
|
bool QgsMapLayer::writeStyle( QDomNode &node, QDomDocument &doc, QString &errorMessage,
|
|
const QgsReadWriteContext &context, QgsMapLayer::StyleCategories categories ) const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
Q_UNUSED( node )
|
|
Q_UNUSED( doc )
|
|
Q_UNUSED( errorMessage )
|
|
Q_UNUSED( context )
|
|
Q_UNUSED( categories )
|
|
return false;
|
|
}
|
|
|
|
|
|
void QgsMapLayer::setDataSource( const QString &dataSource, const QString &baseName, const QString &provider,
|
|
bool loadDefaultStyleFlag )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
const QgsDataProvider::ProviderOptions options;
|
|
|
|
Qgis::DataProviderReadFlags flags;
|
|
if ( loadDefaultStyleFlag )
|
|
{
|
|
flags |= Qgis::DataProviderReadFlag::LoadDefaultStyle;
|
|
}
|
|
|
|
if ( mReadFlags & QgsMapLayer::FlagTrustLayerMetadata )
|
|
{
|
|
flags |= Qgis::DataProviderReadFlag::TrustDataSource;
|
|
}
|
|
setDataSource( dataSource,
|
|
baseName.isEmpty() ? mLayerName : baseName,
|
|
provider.isEmpty() ? mProviderKey : provider,
|
|
options, flags );
|
|
}
|
|
|
|
void QgsMapLayer::setDataSource( const QString &dataSource, const QString &baseName, const QString &provider,
|
|
const QgsDataProvider::ProviderOptions &options, bool loadDefaultStyleFlag )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
Qgis::DataProviderReadFlags flags;
|
|
if ( loadDefaultStyleFlag )
|
|
{
|
|
flags |= Qgis::DataProviderReadFlag::LoadDefaultStyle;
|
|
}
|
|
|
|
if ( mReadFlags & QgsMapLayer::FlagTrustLayerMetadata )
|
|
{
|
|
flags |= Qgis::DataProviderReadFlag::TrustDataSource;
|
|
}
|
|
setDataSource( dataSource, baseName, provider, options, flags );
|
|
}
|
|
|
|
void QgsMapLayer::setDataSource( const QString &dataSource, const QString &baseName, const QString &provider,
|
|
const QgsDataProvider::ProviderOptions &options, Qgis::DataProviderReadFlags flags )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( ( mReadFlags & QgsMapLayer::FlagTrustLayerMetadata ) &&
|
|
!( flags & Qgis::DataProviderReadFlag::TrustDataSource ) )
|
|
{
|
|
flags |= Qgis::DataProviderReadFlag::TrustDataSource;
|
|
}
|
|
setDataSourcePrivate( dataSource, baseName, provider, options, flags );
|
|
emit dataSourceChanged();
|
|
emit dataChanged();
|
|
triggerRepaint();
|
|
}
|
|
|
|
|
|
void QgsMapLayer::setDataSourcePrivate( const QString &dataSource, const QString &baseName, const QString &provider,
|
|
const QgsDataProvider::ProviderOptions &options, Qgis::DataProviderReadFlags flags )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
Q_UNUSED( dataSource )
|
|
Q_UNUSED( baseName )
|
|
Q_UNUSED( provider )
|
|
Q_UNUSED( options )
|
|
Q_UNUSED( flags )
|
|
}
|
|
|
|
|
|
QString QgsMapLayer::providerType() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return mProviderKey;
|
|
}
|
|
|
|
void QgsMapLayer::readCommonStyle( const QDomElement &layerElement, const QgsReadWriteContext &context,
|
|
QgsMapLayer::StyleCategories categories )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( categories.testFlag( Symbology3D ) )
|
|
{
|
|
const QgsReadWriteContextCategoryPopper p = context.enterCategory( tr( "3D Symbology" ) );
|
|
|
|
QgsAbstract3DRenderer *r3D = nullptr;
|
|
QDomElement renderer3DElem = layerElement.firstChildElement( QStringLiteral( "renderer-3d" ) );
|
|
if ( !renderer3DElem.isNull() )
|
|
{
|
|
const QString type3D = renderer3DElem.attribute( QStringLiteral( "type" ) );
|
|
Qgs3DRendererAbstractMetadata *meta3D = QgsApplication::renderer3DRegistry()->rendererMetadata( type3D );
|
|
if ( meta3D )
|
|
{
|
|
r3D = meta3D->createRenderer( renderer3DElem, context );
|
|
}
|
|
}
|
|
setRenderer3D( r3D );
|
|
}
|
|
|
|
if ( categories.testFlag( CustomProperties ) )
|
|
{
|
|
// read custom properties before passing reading further to a subclass, so that
|
|
// the subclass can also read custom properties
|
|
readCustomProperties( layerElement );
|
|
}
|
|
|
|
// use scale dependent visibility flag
|
|
if ( categories.testFlag( Rendering ) )
|
|
{
|
|
setScaleBasedVisibility( layerElement.attribute( QStringLiteral( "hasScaleBasedVisibilityFlag" ) ).toInt() == 1 );
|
|
if ( layerElement.hasAttribute( QStringLiteral( "minimumScale" ) ) )
|
|
{
|
|
// older element, when scales were reversed
|
|
setMaximumScale( layerElement.attribute( QStringLiteral( "minimumScale" ) ).toDouble() );
|
|
setMinimumScale( layerElement.attribute( QStringLiteral( "maximumScale" ) ).toDouble() );
|
|
}
|
|
else
|
|
{
|
|
setMaximumScale( layerElement.attribute( QStringLiteral( "maxScale" ) ).toDouble() );
|
|
setMinimumScale( layerElement.attribute( QStringLiteral( "minScale" ) ).toDouble() );
|
|
}
|
|
if ( layerElement.hasAttribute( QStringLiteral( "autoRefreshMode" ) ) )
|
|
{
|
|
setAutoRefreshMode( qgsEnumKeyToValue( layerElement.attribute( QStringLiteral( "autoRefreshMode" ) ), Qgis::AutoRefreshMode::Disabled ) );
|
|
setAutoRefreshInterval( layerElement.attribute( QStringLiteral( "autoRefreshTime" ) ).toInt() );
|
|
}
|
|
}
|
|
|
|
if ( categories.testFlag( LayerConfiguration ) )
|
|
{
|
|
// flags
|
|
const QDomElement flagsElem = layerElement.firstChildElement( QStringLiteral( "flags" ) );
|
|
LayerFlags flags = mFlags;
|
|
const auto enumMap = qgsEnumMap<QgsMapLayer::LayerFlag>();
|
|
for ( auto it = enumMap.constBegin(); it != enumMap.constEnd(); ++it )
|
|
{
|
|
const QDomNode flagNode = flagsElem.namedItem( it.value() );
|
|
if ( flagNode.isNull() )
|
|
continue;
|
|
const bool flagValue = flagNode.toElement().text() == "1" ? true : false;
|
|
if ( flags.testFlag( it.key() ) && !flagValue )
|
|
flags &= ~it.key();
|
|
else if ( !flags.testFlag( it.key() ) && flagValue )
|
|
flags |= it.key();
|
|
}
|
|
setFlags( flags );
|
|
}
|
|
|
|
if ( categories.testFlag( Temporal ) )
|
|
{
|
|
const QgsReadWriteContextCategoryPopper p = context.enterCategory( tr( "Temporal" ) );
|
|
|
|
if ( QgsMapLayerTemporalProperties *properties = temporalProperties() )
|
|
properties->readXml( layerElement.toElement(), context );
|
|
}
|
|
|
|
if ( categories.testFlag( Elevation ) )
|
|
{
|
|
const QgsReadWriteContextCategoryPopper p = context.enterCategory( tr( "Elevation" ) );
|
|
|
|
if ( QgsMapLayerElevationProperties *properties = elevationProperties() )
|
|
properties->readXml( layerElement.toElement(), context );
|
|
}
|
|
|
|
if ( categories.testFlag( Notes ) )
|
|
{
|
|
const QDomElement notesElem = layerElement.firstChildElement( QStringLiteral( "userNotes" ) );
|
|
if ( !notesElem.isNull() )
|
|
{
|
|
const QString notes = notesElem.attribute( QStringLiteral( "value" ) );
|
|
QgsLayerNotesUtils::setLayerNotes( this, notes );
|
|
}
|
|
}
|
|
}
|
|
|
|
QUndoStack *QgsMapLayer::undoStack()
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return mUndoStack;
|
|
}
|
|
|
|
QUndoStack *QgsMapLayer::undoStackStyles()
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return mUndoStackStyles;
|
|
}
|
|
|
|
QStringList QgsMapLayer::customPropertyKeys() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return mCustomProperties.keys();
|
|
}
|
|
|
|
void QgsMapLayer::setCustomProperty( const QString &key, const QVariant &value )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( !mCustomProperties.contains( key ) || mCustomProperties.value( key ) != value )
|
|
{
|
|
mCustomProperties.setValue( key, value );
|
|
emit customPropertyChanged( key );
|
|
}
|
|
}
|
|
|
|
void QgsMapLayer::setCustomProperties( const QgsObjectCustomProperties &properties )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
mCustomProperties = properties;
|
|
for ( const QString &key : mCustomProperties.keys() )
|
|
{
|
|
emit customPropertyChanged( key );
|
|
}
|
|
}
|
|
|
|
const QgsObjectCustomProperties &QgsMapLayer::customProperties() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return mCustomProperties;
|
|
}
|
|
|
|
QVariant QgsMapLayer::customProperty( const QString &value, const QVariant &defaultValue ) const
|
|
{
|
|
// non fatal for now -- the "rasterize" processing algorithm is not thread safe and calls this
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
|
|
|
|
return mCustomProperties.value( value, defaultValue );
|
|
}
|
|
|
|
void QgsMapLayer::removeCustomProperty( const QString &key )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( mCustomProperties.contains( key ) )
|
|
{
|
|
mCustomProperties.remove( key );
|
|
emit customPropertyChanged( key );
|
|
}
|
|
}
|
|
|
|
int QgsMapLayer::listStylesInDatabase( QStringList &ids, QStringList &names, QStringList &descriptions, QString &msgError )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return QgsProviderRegistry::instance()->listStyles( mProviderKey, mDataSource, ids, names, descriptions, msgError );
|
|
}
|
|
|
|
QString QgsMapLayer::getStyleFromDatabase( const QString &styleId, QString &msgError )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return QgsProviderRegistry::instance()->getStyleById( mProviderKey, mDataSource, styleId, msgError );
|
|
}
|
|
|
|
bool QgsMapLayer::deleteStyleFromDatabase( const QString &styleId, QString &msgError )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return QgsProviderRegistry::instance()->deleteStyleById( mProviderKey, mDataSource, styleId, msgError );
|
|
}
|
|
|
|
void QgsMapLayer::saveStyleToDatabase( const QString &name, const QString &description,
|
|
bool useAsDefault, const QString &uiFileContent, QString &msgError, QgsMapLayer::StyleCategories categories )
|
|
{
|
|
saveStyleToDatabaseV2( name, description, useAsDefault, uiFileContent, msgError, categories );
|
|
}
|
|
|
|
QgsMapLayer::SaveStyleResults QgsMapLayer::saveStyleToDatabaseV2( const QString &name, const QString &description, bool useAsDefault, const QString &uiFileContent, QString &msgError, QgsMapLayer::StyleCategories categories )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
QgsMapLayer::SaveStyleResults results;
|
|
|
|
QString sldStyle, qmlStyle;
|
|
QDomDocument qmlDocument;
|
|
QgsReadWriteContext context;
|
|
exportNamedStyle( qmlDocument, msgError, context, categories );
|
|
if ( !msgError.isEmpty() )
|
|
{
|
|
results.setFlag( QgsMapLayer::SaveStyleResult::QmlGenerationFailed );
|
|
}
|
|
else
|
|
{
|
|
qmlStyle = qmlDocument.toString();
|
|
}
|
|
|
|
QgsSldExportContext sldContext;
|
|
QDomDocument sldDocument = this->exportSldStyleV3( sldContext );
|
|
if ( !sldContext.errors().empty() )
|
|
{
|
|
results.setFlag( QgsMapLayer::SaveStyleResult::SldGenerationFailed );
|
|
}
|
|
else
|
|
{
|
|
sldStyle = sldDocument.toString();
|
|
}
|
|
|
|
if ( !QgsProviderRegistry::instance()->saveStyle( mProviderKey,
|
|
mDataSource, qmlStyle, sldStyle, name, description, uiFileContent, useAsDefault, msgError ) )
|
|
{
|
|
results.setFlag( QgsMapLayer::SaveStyleResult::DatabaseWriteFailed );
|
|
}
|
|
return results;
|
|
}
|
|
|
|
QString QgsMapLayer::loadNamedStyle( const QString &theURI, bool &resultFlag, bool loadFromLocalDB, QgsMapLayer::StyleCategories categories, Qgis::LoadStyleFlags flags )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
QString returnMessage;
|
|
QString qml, errorMsg;
|
|
QString styleName;
|
|
if ( !loadFromLocalDB && dataProvider() && dataProvider()->styleStorageCapabilities().testFlag( Qgis::ProviderStyleStorageCapability::LoadFromDatabase ) )
|
|
{
|
|
qml = QgsProviderRegistry::instance()->loadStoredStyle( mProviderKey, mDataSource, styleName, errorMsg );
|
|
}
|
|
|
|
// Style was successfully loaded from provider storage
|
|
if ( !qml.isEmpty() )
|
|
{
|
|
QDomDocument myDocument( QStringLiteral( "qgis" ) );
|
|
myDocument.setContent( qml );
|
|
resultFlag = importNamedStyle( myDocument, errorMsg );
|
|
returnMessage = QObject::tr( "Loaded from Provider" );
|
|
}
|
|
else
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
bool styleExists = false;
|
|
bool styleSuccessfullyLoaded = false;
|
|
|
|
returnMessage = loadNamedProperty( theURI, PropertyType::Style, styleExists, styleSuccessfullyLoaded, categories, flags );
|
|
|
|
// TODO QGIS 4.0 -- fix API for loadNamedStyle so we can return styleExists too
|
|
( void )styleExists;
|
|
resultFlag = styleSuccessfullyLoaded;
|
|
}
|
|
|
|
if ( ! styleName.isEmpty() )
|
|
{
|
|
styleManager()->renameStyle( styleManager()->currentStyle(), styleName );
|
|
}
|
|
|
|
if ( resultFlag )
|
|
emit styleLoaded( categories );
|
|
|
|
return returnMessage;
|
|
}
|
|
|
|
QgsError QgsMapLayer::error() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return mError;
|
|
}
|
|
|
|
bool QgsMapLayer::isEditable() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return false;
|
|
}
|
|
|
|
bool QgsMapLayer::isModified() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return false;
|
|
}
|
|
|
|
bool QgsMapLayer::isSpatial() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return true;
|
|
}
|
|
|
|
bool QgsMapLayer::isTemporary() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
// invalid layers are temporary? -- who knows?!
|
|
if ( !isValid() )
|
|
return false;
|
|
|
|
if ( mProviderKey == QLatin1String( "memory" ) )
|
|
return true;
|
|
|
|
const QVariantMap sourceParts = QgsProviderRegistry::instance()->decodeUri( mProviderKey, mDataSource );
|
|
const QString path = sourceParts.value( QStringLiteral( "path" ) ).toString();
|
|
if ( path.isEmpty() )
|
|
return false;
|
|
|
|
// check if layer path is inside one of the standard temporary file locations for this platform
|
|
const QStringList tempPaths = QStandardPaths::standardLocations( QStandardPaths::TempLocation );
|
|
for ( const QString &tempPath : tempPaths )
|
|
{
|
|
if ( path.startsWith( tempPath ) )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void QgsMapLayer::setValid( bool valid )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( mValid == valid )
|
|
return;
|
|
|
|
mValid = valid;
|
|
emit isValidChanged();
|
|
}
|
|
|
|
void QgsMapLayer::setLegend( QgsMapLayerLegend *legend )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( legend == mLegend.get() )
|
|
return;
|
|
|
|
mLegend.reset( legend );
|
|
|
|
|
|
if ( mLegend )
|
|
{
|
|
mLegend->setParent( this );
|
|
connect( mLegend.get(), &QgsMapLayerLegend::itemsChanged, this, &QgsMapLayer::legendChanged, Qt::UniqueConnection );
|
|
}
|
|
|
|
emit legendChanged();
|
|
}
|
|
|
|
QgsMapLayerLegend *QgsMapLayer::legend() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return mLegend.get();
|
|
}
|
|
|
|
QgsMapLayerStyleManager *QgsMapLayer::styleManager() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return mStyleManager.get();
|
|
}
|
|
|
|
void QgsMapLayer::setRenderer3D( QgsAbstract3DRenderer *renderer )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( renderer == m3DRenderer.get() )
|
|
return;
|
|
|
|
m3DRenderer.reset( renderer );
|
|
|
|
emit renderer3DChanged();
|
|
emit repaintRequested();
|
|
trigger3DUpdate();
|
|
}
|
|
|
|
QgsAbstract3DRenderer *QgsMapLayer::renderer3D() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return m3DRenderer.get();
|
|
}
|
|
|
|
void QgsMapLayer::triggerRepaint( bool deferredUpdate )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( mRepaintRequestedFired )
|
|
return;
|
|
mRepaintRequestedFired = true;
|
|
emit repaintRequested( deferredUpdate );
|
|
mRepaintRequestedFired = false;
|
|
}
|
|
|
|
void QgsMapLayer::trigger3DUpdate()
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
emit request3DUpdate();
|
|
}
|
|
|
|
void QgsMapLayer::setMetadata( const QgsLayerMetadata &metadata )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
mMetadata = metadata;
|
|
// mMetadata.saveToLayer( this );
|
|
emit metadataChanged();
|
|
}
|
|
|
|
QString QgsMapLayer::htmlMetadata() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return QString();
|
|
}
|
|
|
|
QDateTime QgsMapLayer::timestamp() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return QDateTime();
|
|
}
|
|
|
|
void QgsMapLayer::emitStyleChanged()
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( !mBlockStyleChangedSignal )
|
|
emit styleChanged();
|
|
}
|
|
|
|
void QgsMapLayer::setExtent( const QgsRectangle &extent )
|
|
{
|
|
updateExtent( extent );
|
|
}
|
|
|
|
void QgsMapLayer::setExtent3D( const QgsBox3D &extent )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
updateExtent( extent );
|
|
}
|
|
|
|
bool QgsMapLayer::isReadOnly() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return true;
|
|
}
|
|
|
|
QString QgsMapLayer::originalXmlProperties() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return mOriginalXmlProperties;
|
|
}
|
|
|
|
void QgsMapLayer::setOriginalXmlProperties( const QString &originalXmlProperties )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
mOriginalXmlProperties = originalXmlProperties;
|
|
}
|
|
|
|
QString QgsMapLayer::generateId( const QString &layerName )
|
|
{
|
|
// Generate the unique ID of this layer
|
|
const QString uuid = QUuid::createUuid().toString();
|
|
// trim { } from uuid
|
|
QString id = layerName + '_' + uuid.mid( 1, uuid.length() - 2 );
|
|
// Tidy the ID up to avoid characters that may cause problems
|
|
// elsewhere (e.g in some parts of XML). Replaces every non-word
|
|
// character (word characters are the alphabet, numbers and
|
|
// underscore) with an underscore.
|
|
// Note that the first backslash in the regular expression is
|
|
// there for the compiler, so the pattern is actually \W
|
|
const thread_local QRegularExpression idRx( QStringLiteral( "[\\W]" ) );
|
|
id.replace( idRx, QStringLiteral( "_" ) );
|
|
return id;
|
|
}
|
|
|
|
bool QgsMapLayer::accept( QgsStyleEntityVisitorInterface * ) const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return true;
|
|
}
|
|
|
|
bool QgsMapLayer::hasMapTips() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return mapTipsEnabled() && !mMapTipTemplate.isEmpty();
|
|
}
|
|
|
|
void QgsMapLayer::setProviderType( const QString &providerType )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
mProviderKey = providerType;
|
|
}
|
|
|
|
QSet<QgsMapLayerDependency> QgsMapLayer::dependencies() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
return mDependencies;
|
|
}
|
|
|
|
bool QgsMapLayer::setDependencies( const QSet<QgsMapLayerDependency> &oDeps )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
QSet<QgsMapLayerDependency> deps;
|
|
const auto constODeps = oDeps;
|
|
for ( const QgsMapLayerDependency &dep : constODeps )
|
|
{
|
|
if ( dep.origin() == QgsMapLayerDependency::FromUser )
|
|
deps << dep;
|
|
}
|
|
|
|
mDependencies = deps;
|
|
emit dependenciesChanged();
|
|
return true;
|
|
}
|
|
|
|
void QgsMapLayer::setRefreshOnNotifyEnabled( bool enabled )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
QgsDataProvider *lDataProvider = dataProvider();
|
|
|
|
if ( !lDataProvider )
|
|
return;
|
|
|
|
if ( enabled && !isRefreshOnNotifyEnabled() )
|
|
{
|
|
lDataProvider->setListening( enabled );
|
|
connect( lDataProvider, &QgsDataProvider::notify, this, &QgsMapLayer::onNotified );
|
|
}
|
|
else if ( !enabled && isRefreshOnNotifyEnabled() )
|
|
{
|
|
// we don't want to disable provider listening because someone else could need it (e.g. actions)
|
|
disconnect( lDataProvider, &QgsDataProvider::notify, this, &QgsMapLayer::onNotified );
|
|
}
|
|
mIsRefreshOnNofifyEnabled = enabled;
|
|
}
|
|
|
|
QgsProject *QgsMapLayer::project() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( QgsMapLayerStore *store = qobject_cast<QgsMapLayerStore *>( parent() ) )
|
|
{
|
|
return qobject_cast<QgsProject *>( store->parent() );
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void QgsMapLayer::onNotified( const QString &message )
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( refreshOnNotifyMessage().isEmpty() || refreshOnNotifyMessage() == message )
|
|
{
|
|
triggerRepaint();
|
|
emit dataChanged();
|
|
}
|
|
}
|
|
|
|
QgsRectangle QgsMapLayer::wgs84Extent( bool forceRecalculate ) const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
QgsRectangle wgs84Extent;
|
|
|
|
if ( ! forceRecalculate && ! mWgs84Extent.isNull() )
|
|
{
|
|
wgs84Extent = mWgs84Extent;
|
|
}
|
|
else if ( ! mExtent2D.isNull() || ! mExtent3D.isNull() )
|
|
{
|
|
QgsCoordinateTransform transformer { crs(), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ), transformContext() };
|
|
transformer.setBallparkTransformsAreAppropriate( true );
|
|
try
|
|
{
|
|
if ( mExtent2D.isNull() )
|
|
wgs84Extent = transformer.transformBoundingBox( mExtent3D.toRectangle() );
|
|
else
|
|
wgs84Extent = transformer.transformBoundingBox( mExtent2D );
|
|
}
|
|
catch ( const QgsCsException &cse )
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Error transforming extent: %1" ).arg( cse.what() ) );
|
|
wgs84Extent = QgsRectangle();
|
|
}
|
|
}
|
|
return wgs84Extent;
|
|
}
|
|
|
|
void QgsMapLayer::updateExtent( const QgsRectangle &extent ) const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( extent == mExtent2D )
|
|
return;
|
|
|
|
mExtent2D = extent;
|
|
|
|
// do not update the wgs84 extent if we trust layer metadata
|
|
if ( mReadFlags & QgsMapLayer::ReadFlag::FlagTrustLayerMetadata )
|
|
return;
|
|
|
|
mWgs84Extent = wgs84Extent( true );
|
|
}
|
|
|
|
void QgsMapLayer::updateExtent( const QgsBox3D &extent ) const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
if ( extent == mExtent3D )
|
|
return;
|
|
|
|
if ( extent.isNull() )
|
|
{
|
|
if ( !extent.toRectangle().isNull() )
|
|
{
|
|
// bad 3D extent param but valid in 2d --> update 2D extent
|
|
updateExtent( extent.toRectangle() );
|
|
}
|
|
else
|
|
{
|
|
QgsDebugMsgLevel( QStringLiteral( "Unable to update extent with empty parameter" ), 1 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mExtent3D = extent;
|
|
|
|
// do not update the wgs84 extent if we trust layer metadata
|
|
if ( mReadFlags & QgsMapLayer::ReadFlag::FlagTrustLayerMetadata )
|
|
return;
|
|
|
|
mWgs84Extent = wgs84Extent( true );
|
|
}
|
|
}
|
|
|
|
bool QgsMapLayer::rebuildCrs3D( QString *error )
|
|
{
|
|
bool res = true;
|
|
if ( !mCRS.isValid() )
|
|
{
|
|
mCrs3D = QgsCoordinateReferenceSystem();
|
|
}
|
|
else if ( !mVerticalCrs.isValid() )
|
|
{
|
|
mCrs3D = mCRS;
|
|
}
|
|
else
|
|
{
|
|
switch ( mCRS.type() )
|
|
{
|
|
case Qgis::CrsType::Compound:
|
|
case Qgis::CrsType::Geographic3d:
|
|
case Qgis::CrsType::Geocentric:
|
|
mCrs3D = mCRS;
|
|
break;
|
|
|
|
case Qgis::CrsType::Projected:
|
|
{
|
|
QString tempError;
|
|
mCrs3D = mCRS.hasVerticalAxis() ? mCRS : QgsCoordinateReferenceSystem::createCompoundCrs( mCRS, mVerticalCrs, error ? *error : tempError );
|
|
res = mCrs3D.isValid();
|
|
break;
|
|
}
|
|
|
|
case Qgis::CrsType::Vertical:
|
|
// nonsense situation
|
|
mCrs3D = QgsCoordinateReferenceSystem();
|
|
res = false;
|
|
break;
|
|
|
|
case Qgis::CrsType::Unknown:
|
|
case Qgis::CrsType::Geodetic:
|
|
case Qgis::CrsType::Geographic2d:
|
|
case Qgis::CrsType::Temporal:
|
|
case Qgis::CrsType::Engineering:
|
|
case Qgis::CrsType::Bound:
|
|
case Qgis::CrsType::Other:
|
|
case Qgis::CrsType::DerivedProjected:
|
|
{
|
|
QString tempError;
|
|
mCrs3D = QgsCoordinateReferenceSystem::createCompoundCrs( mCRS, mVerticalCrs, error ? *error : tempError );
|
|
res = mCrs3D.isValid();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
void QgsMapLayer::invalidateWgs84Extent()
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
// do not update the wgs84 extent if we trust layer metadata
|
|
if ( mReadFlags & QgsMapLayer::ReadFlag::FlagTrustLayerMetadata )
|
|
return;
|
|
|
|
mWgs84Extent = QgsRectangle();
|
|
}
|
|
|
|
QString QgsMapLayer::generalHtmlMetadata() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
|
|
QString metadata = QStringLiteral( "<h1>" ) + tr( "General" ) + QStringLiteral( "</h1>\n<hr>\n" ) + QStringLiteral( "<table class=\"list-view\">\n" );
|
|
|
|
// name
|
|
metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) + tr( "Name" ) + QStringLiteral( "</td><td>" ) + name() + QStringLiteral( "</td></tr>\n" );
|
|
|
|
const QString lPublicSource = publicSource();
|
|
|
|
QString path;
|
|
bool isLocalPath = false;
|
|
if ( dataProvider() )
|
|
{
|
|
// local path
|
|
QVariantMap uriComponents = QgsProviderRegistry::instance()->decodeUri( dataProvider()->name(), lPublicSource );
|
|
if ( uriComponents.contains( QStringLiteral( "path" ) ) )
|
|
{
|
|
path = uriComponents[QStringLiteral( "path" )].toString();
|
|
QFileInfo fi( path );
|
|
if ( fi.exists() )
|
|
{
|
|
isLocalPath = true;
|
|
metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) + tr( "Path" ) + QStringLiteral( "</td><td>%1" ).arg( QStringLiteral( "<a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( path ).toString(), QDir::toNativeSeparators( path ) ) ) + QStringLiteral( "</td></tr>\n" );
|
|
|
|
QDateTime lastModified = fi.lastModified();
|
|
QString lastModifiedFileName;
|
|
QSet<QString> sidecarFiles = QgsFileUtils::sidecarFilesForPath( path );
|
|
if ( fi.isFile() )
|
|
{
|
|
qint64 fileSize = fi.size();
|
|
if ( !sidecarFiles.isEmpty() )
|
|
{
|
|
lastModifiedFileName = fi.fileName();
|
|
QStringList sidecarFileNames;
|
|
for ( const QString &sidecarFile : sidecarFiles )
|
|
{
|
|
QFileInfo sidecarFi( sidecarFile );
|
|
fileSize += sidecarFi.size();
|
|
if ( sidecarFi.lastModified() > lastModified )
|
|
{
|
|
lastModified = sidecarFi.lastModified();
|
|
lastModifiedFileName = sidecarFi.fileName();
|
|
}
|
|
sidecarFileNames << sidecarFi.fileName();
|
|
}
|
|
metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) + ( sidecarFiles.size() > 1 ? tr( "Sidecar files" ) : tr( "Sidecar file" ) ) + QStringLiteral( "</td><td>%1" ).arg( sidecarFileNames.join( QLatin1String( ", " ) ) ) + QStringLiteral( "</td></tr>\n" );
|
|
}
|
|
metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) + ( !sidecarFiles.isEmpty() ? tr( "Total size" ) : tr( "Size" ) ) + QStringLiteral( "</td><td>%1" ).arg( QgsFileUtils::representFileSize( fileSize ) ) + QStringLiteral( "</td></tr>\n" );
|
|
}
|
|
metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) + tr( "Last modified" ) + QStringLiteral( "</td><td>%1" ).arg( QLocale().toString( fi.lastModified() ) ) + ( !lastModifiedFileName.isEmpty() ? QStringLiteral( " (%1)" ).arg( lastModifiedFileName ) : QString() ) + QStringLiteral( "</td></tr>\n" );
|
|
}
|
|
}
|
|
if ( uriComponents.contains( QStringLiteral( "url" ) ) )
|
|
{
|
|
const QString url = uriComponents[QStringLiteral( "url" )].toString();
|
|
metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) + tr( "URL" ) + QStringLiteral( "</td><td>%1" ).arg( QStringLiteral( "<a href=\"%1\">%2</a>" ).arg( QUrl( url ).toString(), url ) ) + QStringLiteral( "</td></tr>\n" );
|
|
}
|
|
}
|
|
|
|
// data source
|
|
if ( lPublicSource != path || !isLocalPath )
|
|
metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) + tr( "Source" ) + QStringLiteral( "</td><td>%1" ).arg( lPublicSource != path ? lPublicSource : path ) + QStringLiteral( "</td></tr>\n" );
|
|
|
|
// provider
|
|
if ( dataProvider() )
|
|
metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) + tr( "Provider" ) + QStringLiteral( "</td><td>%1" ).arg( dataProvider()->name() ) + QStringLiteral( "</td></tr>\n" );
|
|
|
|
// Layer ID
|
|
metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) + tr( "Layer ID" ) + QStringLiteral( "</td><td>%1" ).arg( id() ) + QStringLiteral( "</td></tr>\n" );
|
|
|
|
metadata += QLatin1String( "</table>\n<br><br>" );
|
|
|
|
return metadata;
|
|
}
|
|
|
|
QString QgsMapLayer::customPropertyHtmlMetadata() const
|
|
{
|
|
QString metadata;
|
|
// custom properties
|
|
if ( const auto keys = customPropertyKeys(); !keys.isEmpty() )
|
|
{
|
|
metadata += QStringLiteral( "<h1>" ) + tr( "Custom properties" ) + QStringLiteral( "</h1>\n<hr>\n" );
|
|
metadata += QLatin1String( "<table class=\"list-view\">\n<tbody>" );
|
|
for ( const QString &key : keys )
|
|
{
|
|
// keys prefaced with _ are considered private/internal details
|
|
if ( key.startsWith( '_' ) )
|
|
continue;
|
|
|
|
const QVariant propValue = customProperty( key );
|
|
QString stringValue;
|
|
if ( propValue.type() == QVariant::List || propValue.type() == QVariant::StringList )
|
|
{
|
|
for ( const QString &s : propValue.toStringList() )
|
|
{
|
|
stringValue += "<p style=\"margin: 0;\">" + s.toHtmlEscaped() + "</p>";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
stringValue = propValue.toString().toHtmlEscaped();
|
|
|
|
//if the result string is empty but propValue is not, the conversion has failed
|
|
if ( stringValue.isEmpty() && !QgsVariantUtils::isNull( propValue ) )
|
|
stringValue = tr( "<i>value cannot be displayed</i>" );
|
|
}
|
|
|
|
metadata += QStringLiteral( "<tr><td class=\"highlight\">%1</td><td>%2</td></tr>" ).arg( key.toHtmlEscaped(), stringValue );
|
|
}
|
|
metadata += QLatin1String( "</tbody></table>\n" );
|
|
metadata += QLatin1String( "<br><br>\n" );
|
|
}
|
|
return metadata;
|
|
}
|
|
|
|
QString QgsMapLayer::crsHtmlMetadata() const
|
|
{
|
|
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
|
|
QString metadata;
|
|
|
|
auto addCrsInfo = [&metadata]( const QgsCoordinateReferenceSystem & c, bool includeType, bool includeOperation, bool includeCelestialBody )
|
|
{
|
|
if ( !c.isValid() )
|
|
metadata += QStringLiteral( "<tr><td colspan=\"2\" class=\"highlight\">" ) + tr( "Unknown" ) + QStringLiteral( "</td></tr>\n" );
|
|
else
|
|
{
|
|
metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) + tr( "Name" ) + QStringLiteral( "</td><td>" ) + c.userFriendlyIdentifier( Qgis::CrsIdentifierType::FullString ) + QStringLiteral( "</td></tr>\n" );
|
|
|
|
// map units
|
|
metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) + tr( "Units" ) + QStringLiteral( "</td><td>" )
|
|
+ ( c.isGeographic() ? tr( "Geographic (uses latitude and longitude for coordinates)" ) : QgsUnitTypes::toString( c.mapUnits() ) )
|
|
+ QStringLiteral( "</td></tr>\n" );
|
|
|
|
if ( includeType )
|
|
{
|
|
metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) + tr( "Type" ) + QStringLiteral( "</td><td>" ) + QgsCoordinateReferenceSystemUtils::crsTypeToString( c.type() ) + QStringLiteral( "</td></tr>\n" );
|
|
}
|
|
|
|
if ( includeOperation )
|
|
{
|
|
// operation
|
|
const QgsProjOperation operation = c.operation();
|
|
metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) + tr( "Method" ) + QStringLiteral( "</td><td>" ) + operation.description() + QStringLiteral( "</td></tr>\n" );
|
|
}
|
|
|
|
if ( includeCelestialBody )
|
|
{
|
|
// celestial body
|
|
try
|
|
{
|
|
const QString celestialBody = c.celestialBodyName();
|
|
if ( !celestialBody.isEmpty() )
|
|
{
|
|
metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) + tr( "Celestial Body" ) + QStringLiteral( "</td><td>" ) + celestialBody + QStringLiteral( "</td></tr>\n" );
|
|
}
|
|
}
|
|
catch ( QgsNotSupportedException & )
|
|
{
|
|
|
|
}
|
|
}
|
|
|
|
QString accuracyString;
|
|
// dynamic crs with no epoch?
|
|
if ( c.isDynamic() && std::isnan( c.coordinateEpoch() ) )
|
|
{
|
|
accuracyString = tr( "Based on a dynamic CRS, but no coordinate epoch is set. Coordinates are ambiguous and of limited accuracy." );
|
|
}
|
|
|
|
// based on datum ensemble?
|
|
try
|
|
{
|
|
const QgsDatumEnsemble ensemble = c.datumEnsemble();
|
|
if ( ensemble.isValid() )
|
|
{
|
|
QString id;
|
|
if ( !ensemble.code().isEmpty() )
|
|
id = QStringLiteral( "<i>%1</i> (%2:%3)" ).arg( ensemble.name(), ensemble.authority(), ensemble.code() );
|
|
else
|
|
id = QStringLiteral( "<i>%1</i>”" ).arg( ensemble.name() );
|
|
|
|
if ( ensemble.accuracy() > 0 )
|
|
{
|
|
accuracyString = tr( "Based on %1, which has a limited accuracy of <b>at best %2 meters</b>." ).arg( id ).arg( ensemble.accuracy() );
|
|
}
|
|
else
|
|
{
|
|
accuracyString = tr( "Based on %1, which has a limited accuracy." ).arg( id );
|
|
}
|
|
}
|
|
}
|
|
catch ( QgsNotSupportedException & )
|
|
{
|
|
|
|
}
|
|
|
|
if ( !accuracyString.isEmpty() )
|
|
{
|
|
metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) + tr( "Accuracy" ) + QStringLiteral( "</td><td>" ) + accuracyString + QStringLiteral( "</td></tr>\n" );
|
|
}
|
|
|
|
// static/dynamic
|
|
metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) + tr( "Reference" ) + QStringLiteral( "</td><td>%1</td></tr>\n" ).arg( c.isDynamic() ? tr( "Dynamic (relies on a datum which is not plate-fixed)" ) : tr( "Static (relies on a datum which is plate-fixed)" ) );
|
|
|
|
// coordinate epoch
|
|
if ( !std::isnan( c.coordinateEpoch() ) )
|
|
{
|
|
metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) + tr( "Coordinate Epoch" ) + QStringLiteral( "</td><td>%1</td></tr>\n" ).arg( qgsDoubleToString( c.coordinateEpoch(), 3 ) );
|
|
}
|
|
}
|
|
};
|
|
|
|
metadata += QStringLiteral( "<h1>" ) + tr( "Coordinate Reference System (CRS)" ) + QStringLiteral( "</h1>\n<hr>\n" );
|
|
metadata += QLatin1String( "<table class=\"list-view\">\n" );
|
|
addCrsInfo( crs().horizontalCrs(), true, true, true );
|
|
metadata += QLatin1String( "</table>\n<br><br>\n" );
|
|
|
|
if ( verticalCrs().isValid() )
|
|
{
|
|
metadata += QStringLiteral( "<h1>" ) + tr( "Vertical Coordinate Reference System (CRS)" ) + QStringLiteral( "</h1>\n<hr>\n" );
|
|
metadata += QLatin1String( "<table class=\"list-view\">\n" );
|
|
addCrsInfo( verticalCrs(), false, false, false );
|
|
metadata += QLatin1String( "</table>\n<br><br>\n" );
|
|
}
|
|
|
|
return metadata;
|
|
}
|