1
/***************************************************************************
2
qgsmaplayer.cpp - description
4
begin : Fri Jun 28 2002
5
copyright : (C) 2002 by Gary E.Sherman
6
email : sherman at mrcc.com
7
***************************************************************************/
9
/***************************************************************************
11
* This program is free software; you can redistribute it and/or modify *
12
* it under the terms of the GNU General Public License as published by *
13
* the Free Software Foundation; either version 2 of the License, or *
14
* (at your option) any later version. *
16
***************************************************************************/
23
#include <QSettings> // TODO: get rid of it [MD]
27
#include <QDomDocument>
28
#include <QDomElement>
29
#include <QDomImplementation>
30
#include <QTextStream>
34
#include "qgslogger.h"
35
#include "qgsrectangle.h"
36
#include "qgssymbol.h"
37
#include "qgsmaplayer.h"
38
#include "qgscoordinatereferencesystem.h"
39
#include "qgsapplication.h"
40
#include "qgsproject.h"
41
#include "qgslogger.h"
42
#include "qgsdatasourceuri.h"
43
#include "qgsvectorlayer.h"
45
QgsMapLayer::QgsMapLayer( QgsMapLayer::LayerType type,
48
mTransparencyLevel( 255 ), // 0 is completely transparent
49
mValid( FALSE ), // assume the layer is invalid
50
mDataSource( source ),
55
QgsDebugMsg( "lyrname is '" + lyrname + "'" );
57
mCRS = new QgsCoordinateReferenceSystem();
59
// Set the display name = internal name
60
mLayerName = capitaliseLayerName( lyrname );
61
QgsDebugMsg( "layerName is '" + mLayerName + "'" );
63
// Generate the unique ID of this layer
64
QDateTime dt = QDateTime::currentDateTime();
65
mID = lyrname + dt.toString( "yyyyMMddhhmmsszzz" );
66
// Tidy the ID up to avoid characters that may cause problems
67
// elsewhere (e.g in some parts of XML). Replaces every non-word
68
// character (word characters are the alphabet, numbers and
69
// underscore) with an underscore.
70
// Note that the first backslashe in the regular expression is
71
// there for the compiler, so the pattern is actually \W
72
mID.replace( QRegExp( "[\\W]" ), "_" );
74
//set some generous defaults for scale based visibility
76
mMaxScale = 100000000;
77
mScaleBasedVisibility = false;
83
QgsMapLayer::~QgsMapLayer()
92
QgsMapLayer::LayerType QgsMapLayer::type() const
97
/** Get this layer's unique ID */
98
QString QgsMapLayer::getLayerID() const
103
/** Write property of QString layerName. */
104
void QgsMapLayer::setLayerName( const QString & _newVal )
106
QgsDebugMsg( "new name is '" + _newVal + "'" );
107
mLayerName = capitaliseLayerName( _newVal );
108
emit layerNameChanged();
111
/** Read property of QString layerName. */
112
QString const & QgsMapLayer::name() const
114
QgsDebugMsgLevel( "returning name '" + mLayerName + "'", 3 );
118
QString QgsMapLayer::publicSource() const
120
// Redo this every time we're asked for it, as we don't know if
121
// dataSource has changed.
122
QString safeName = QgsDataSourceURI::removePassword( mDataSource );
126
QString const & QgsMapLayer::source() const
131
QgsRectangle QgsMapLayer::extent() const
136
bool QgsMapLayer::draw( QgsRenderContext& rendererContext )
141
void QgsMapLayer::drawLabels( QgsRenderContext& rendererContext )
143
// QgsDebugMsg("entered.");
146
bool QgsMapLayer::readXML( QDomNode & layer_node )
148
QgsCoordinateReferenceSystem savedCRS;
149
CUSTOM_CRS_VALIDATION savedValidation;
152
QDomElement element = layer_node.toElement();
159
mnl = layer_node.namedItem( "provider" );
160
mne = mnl.toElement();
161
provider = mne.text();
164
mnl = layer_node.namedItem( "datasource" );
165
mne = mnl.toElement();
166
mDataSource = mne.text();
168
if ( provider == "spatialite" )
170
QgsDataSourceURI uri( mDataSource );
171
uri.setDatabase( QgsProject::instance()->readPath( uri.database() ) );
172
mDataSource = uri.uri();
174
else if ( provider == "ogr" )
176
QStringList theURIParts = mDataSource.split( "|" );
177
theURIParts[0] = QgsProject::instance()->readPath( theURIParts[0] );
178
mDataSource = theURIParts.join( "|" );
182
mDataSource = QgsProject::instance()->readPath( mDataSource );
185
// Set the CRS from project file, asking the user if necessary.
186
// Make it the saved CRS to have WMS layer projected correctly.
187
// We will still overwrite whatever GDAL etc picks up anyway
188
// further down this function.
189
QDomNode srsNode = layer_node.namedItem( "srs" );
190
mCRS->readXML( srsNode );
194
// Do not validate any projections in children, they will be overwritten anyway.
195
// No need to ask the user for a projections when it is overwritten, is there?
196
savedValidation = QgsCoordinateReferenceSystem::customSrsValidation();
197
QgsCoordinateReferenceSystem::setCustomSrsValidation( NULL );
199
// now let the children grab what they need from the Dom node.
200
layerError = !readXml( layer_node );
202
// overwrite CRS with what we read from project file before the raster/vector
203
// file readnig functions changed it. They will if projections is specfied in the file.
204
// FIXME: is this necessary?
205
QgsCoordinateReferenceSystem::setCustomSrsValidation( savedValidation );
208
// Abort if any error in layer, such as not found.
214
// the internal name is just the data source basename
215
//QFileInfo dataSourceFileInfo( mDataSource );
216
//internalName = dataSourceFileInfo.baseName();
219
mnl = layer_node.namedItem( "id" );
220
if ( ! mnl.isNull() )
222
mne = mnl.toElement();
223
if ( ! mne.isNull() && mne.text().length() > 10 ) // should be at least 17 (yyyyMMddhhmmsszzz)
229
// use scale dependent visibility flag
230
toggleScaleBasedVisibility( element.attribute( "hasScaleBasedVisibilityFlag" ).toInt() == 1 );
231
setMinimumScale( element.attribute( "minimumScale" ).toFloat() );
232
setMaximumScale( element.attribute( "maximumScale" ).toFloat() );
235
mnl = layer_node.namedItem( "layername" );
236
mne = mnl.toElement();
237
setLayerName( mne.text() );
239
//read transparency level
240
QDomNode transparencyNode = layer_node.namedItem( "transparencyLevelInt" );
241
if ( ! transparencyNode.isNull() )
243
// set transparency level only if it's in project
244
// (otherwise it sets the layer transparent)
245
QDomElement myElement = transparencyNode.toElement();
246
setTransparency( myElement.text().toInt() );
249
readCustomProperties( layer_node );
252
} // void QgsMapLayer::readXML
255
bool QgsMapLayer::readXml( QDomNode & layer_node )
257
// NOP by default; children will over-ride with behavior specific to them
260
} // void QgsMapLayer::readXml
264
bool QgsMapLayer::writeXML( QDomNode & layer_node, QDomDocument & document )
266
// general layer metadata
267
QDomElement maplayer = document.createElement( "maplayer" );
269
// use scale dependent visibility flag
270
maplayer.setAttribute( "hasScaleBasedVisibilityFlag", hasScaleBasedVisibility() ? 1 : 0 );
271
maplayer.setAttribute( "minimumScale", minimumScale() );
272
maplayer.setAttribute( "maximumScale", maximumScale() );
275
QDomElement id = document.createElement( "id" );
276
QDomText idText = document.createTextNode( getLayerID() );
277
id.appendChild( idText );
279
maplayer.appendChild( id );
282
QDomElement dataSource = document.createElement( "datasource" );
284
QString src = source();
286
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( this );
287
if ( vlayer && vlayer->providerType() == "spatialite" )
289
QgsDataSourceURI uri( src );
290
QString database = QgsProject::instance()->writePath( uri.database() );
291
uri.setConnection( uri.host(), uri.port(), database, uri.username(), uri.password() );
294
else if ( vlayer && vlayer->providerType() == "ogr" )
296
QStringList theURIParts = src.split( "|" );
297
theURIParts[0] = QgsProject::instance()->writePath( theURIParts[0] );
298
src = theURIParts.join( "|" );
302
src = QgsProject::instance()->writePath( src );
305
QDomText dataSourceText = document.createTextNode( src );
306
dataSource.appendChild( dataSourceText );
308
maplayer.appendChild( dataSource );
312
QDomElement layerName = document.createElement( "layername" );
313
QDomText layerNameText = document.createTextNode( name() );
314
layerName.appendChild( layerNameText );
316
maplayer.appendChild( layerName );
319
// This is no longer stored in the project file. It is superfluous since the layers
320
// are written and read in the proper order.
322
// spatial reference system id
323
QDomElement mySrsElement = document.createElement( "srs" );
324
mCRS->writeXML( mySrsElement, document );
325
maplayer.appendChild( mySrsElement );
327
// <transparencyLevelInt>
328
QDomElement transparencyLevelIntElement = document.createElement( "transparencyLevelInt" );
329
QDomText transparencyLevelIntText = document.createTextNode( QString::number( getTransparency() ) );
330
transparencyLevelIntElement.appendChild( transparencyLevelIntText );
331
maplayer.appendChild( transparencyLevelIntElement );
332
// now append layer node to map layer node
334
layer_node.appendChild( maplayer );
336
writeCustomProperties( maplayer, document );
338
return writeXml( maplayer, document );
340
} // bool QgsMapLayer::writeXML
344
bool QgsMapLayer::writeXml( QDomNode & layer_node, QDomDocument & document )
346
// NOP by default; children will over-ride with behavior specific to them
349
} // void QgsMapLayer::writeXml
354
bool QgsMapLayer::isValid()
360
void QgsMapLayer::invalidTransformInput()
362
QgsDebugMsg( "called" );
363
// TODO: emit a signal - it will be used to update legend
367
QString QgsMapLayer::lastErrorTitle()
372
QString QgsMapLayer::lastError()
377
void QgsMapLayer::connectNotify( const char * signal )
379
QgsDebugMsgLevel( "QgsMapLayer connected to " + QString( signal ), 3 );
380
} // QgsMapLayer::connectNotify
384
void QgsMapLayer::toggleScaleBasedVisibility( bool theVisibilityFlag )
386
mScaleBasedVisibility = theVisibilityFlag;
389
bool QgsMapLayer::hasScaleBasedVisibility()
391
return mScaleBasedVisibility;
394
void QgsMapLayer::setMinimumScale( float theMinScale )
396
mMinScale = theMinScale;
399
float QgsMapLayer::minimumScale()
405
void QgsMapLayer::setMaximumScale( float theMaxScale )
407
mMaxScale = theMaxScale;
410
float QgsMapLayer::maximumScale()
416
QStringList QgsMapLayer::subLayers()
418
return QStringList(); // Empty
421
void QgsMapLayer::setLayerOrder( QStringList layers )
426
void QgsMapLayer::setSubLayerVisibility( QString name, bool vis )
431
const QgsCoordinateReferenceSystem& QgsMapLayer::crs()
436
const QgsCoordinateReferenceSystem& QgsMapLayer::srs()
438
// This will be dropped in QGIS 2.0 due to conflicting name
439
// Please use crs() in the future
443
void QgsMapLayer::setCrs( const QgsCoordinateReferenceSystem& srs, bool emitSignal )
447
emit layerCrsChanged();
450
unsigned int QgsMapLayer::getTransparency()
452
return mTransparencyLevel;
455
void QgsMapLayer::setTransparency( unsigned int theInt )
457
mTransparencyLevel = theInt;
460
QString QgsMapLayer::capitaliseLayerName( const QString name )
462
// Capitalise the first letter of the layer name if requested
464
bool capitaliseLayerName =
465
settings.value( "qgis/capitaliseLayerName", QVariant( false ) ).toBool();
467
QString layerName( name );
469
if ( capitaliseLayerName )
470
layerName = layerName.left( 1 ).toUpper() + layerName.mid( 1 );
475
QString QgsMapLayer::loadDefaultStyle( bool & theResultFlag )
477
QString myURI = publicSource();
478
QFileInfo myFileInfo( myURI );
480
if ( myFileInfo.exists() )
482
// get the file name for our .qml style file
483
key = myFileInfo.path() + QDir::separator() + myFileInfo.completeBaseName() + ".qml";
489
return loadNamedStyle( key, theResultFlag );
492
bool QgsMapLayer::loadNamedStyleFromDb( const QString db, const QString theURI, QString &qml )
494
bool theResultFlag = false;
496
// read from database
498
sqlite3_stmt *myPreparedStatement;
502
QgsDebugMsg( QString( "Trying to load style for \"%1\" from \"%2\"" ).arg( theURI ).arg( db ) );
504
if ( !QFile( db ).exists() )
507
myResult = sqlite3_open( db.toUtf8().data(), &myDatabase );
508
if ( myResult != SQLITE_OK )
513
QString mySql = "select qml from tbl_styles where style=?";
514
myResult = sqlite3_prepare( myDatabase, mySql.toUtf8().data(), mySql.toUtf8().length(), &myPreparedStatement, &myTail );
515
if ( myResult == SQLITE_OK )
517
QByteArray param = theURI.toUtf8();
519
if ( sqlite3_bind_text( myPreparedStatement, 1, param.data(), param.length(), SQLITE_STATIC ) == SQLITE_OK &&
520
sqlite3_step( myPreparedStatement ) == SQLITE_ROW )
522
qml = QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, 0 ) );
523
theResultFlag = true;
526
sqlite3_finalize( myPreparedStatement );
529
sqlite3_close( myDatabase );
531
return theResultFlag;
534
QString QgsMapLayer::loadNamedStyle( const QString theURI, bool &theResultFlag )
536
theResultFlag = false;
538
QDomDocument myDocument( "qgis" );
540
// location of problem associated with errorMsg
542
QString myErrorMessage;
544
QFile myFile( theURI );
545
if ( myFile.open( QFile::ReadOnly ) )
548
theResultFlag = myDocument.setContent( &myFile, &myErrorMessage, &line, &column );
549
if ( !theResultFlag )
550
myErrorMessage = tr( "%1 at line %2 column %3" ).arg( myErrorMessage ).arg( line ).arg( column );
555
QFileInfo project( QgsProject::instance()->fileName() );
556
QgsDebugMsg( QString( "project fileName: %1" ).arg( project.absoluteFilePath() ) );
559
if ( loadNamedStyleFromDb( QDir( QgsApplication::qgisSettingsDirPath() ).absoluteFilePath( "qgis.qmldb" ), theURI, qml ) ||
560
( project.exists() && loadNamedStyleFromDb( project.absoluteDir().absoluteFilePath( project.baseName() + ".qmldb" ), theURI, qml ) ) ||
561
loadNamedStyleFromDb( QDir( QgsApplication::pkgDataPath() ).absoluteFilePath( "resources/qgis.qmldb" ), theURI, qml ) )
563
theResultFlag = myDocument.setContent( qml, &myErrorMessage, &line, &column );
564
if ( !theResultFlag )
566
myErrorMessage = tr( "%1 at line %2 column %3" ).arg( myErrorMessage ).arg( line ).arg( column );
571
myErrorMessage = tr( "style not found in database" );
575
if ( !theResultFlag )
577
return myErrorMessage;
580
// now get the layer node out and pass it over to the layer
582
QDomElement myRoot = myDocument.firstChildElement( "qgis" );
583
if ( myRoot.isNull() )
585
myErrorMessage = "Error: qgis element could not be found in " + theURI;
586
theResultFlag = false;
587
return myErrorMessage;
590
// use scale dependent visibility flag
591
toggleScaleBasedVisibility( myRoot.attribute( "hasScaleBasedVisibilityFlag" ).toInt() == 1 );
592
setMinimumScale( myRoot.attribute( "minimumScale" ).toFloat() );
593
setMaximumScale( myRoot.attribute( "maximumScale" ).toFloat() );
595
//read transparency level
596
QDomNode transparencyNode = myRoot.namedItem( "transparencyLevelInt" );
597
if ( ! transparencyNode.isNull() )
599
// set transparency level only if it's in project
600
// (otherwise it sets the layer transparent)
601
QDomElement myElement = transparencyNode.toElement();
602
setTransparency( myElement.text().toInt() );
606
theResultFlag = readSymbology( myRoot, errorMsg );
607
if ( !theResultFlag )
609
myErrorMessage = tr( "Loading style file %1 failed because:\n%2" ).arg( theURI ).arg( errorMsg );
610
return myErrorMessage;
616
QString QgsMapLayer::saveDefaultStyle( bool & theResultFlag )
618
return saveNamedStyle( publicSource(), theResultFlag );
621
QString QgsMapLayer::saveNamedStyle( const QString theURI, bool & theResultFlag )
623
QString myErrorMessage;
625
QDomImplementation DomImplementation;
626
QDomDocumentType documentType =
627
DomImplementation.createDocumentType(
628
"qgis", "http://mrcc.com/qgis.dtd", "SYSTEM" );
629
QDomDocument myDocument( documentType );
630
QDomElement myRootNode = myDocument.createElement( "qgis" );
631
myRootNode.setAttribute( "version", QString( "%1" ).arg( QGis::QGIS_VERSION ) );
632
myDocument.appendChild( myRootNode );
634
// use scale dependent visibility flag
635
myRootNode.setAttribute( "hasScaleBasedVisibilityFlag", hasScaleBasedVisibility() ? 1 : 0 );
636
myRootNode.setAttribute( "minimumScale", minimumScale() );
637
myRootNode.setAttribute( "maximumScale", maximumScale() );
639
// <transparencyLevelInt>
640
QDomElement transparencyLevelIntElement = myDocument.createElement( "transparencyLevelInt" );
641
QDomText transparencyLevelIntText = myDocument.createTextNode( QString::number( getTransparency() ) );
642
transparencyLevelIntElement.appendChild( transparencyLevelIntText );
643
myRootNode.appendChild( transparencyLevelIntElement );
644
// now append layer node to map layer node
647
if ( !writeSymbology( myRootNode, myDocument, errorMsg ) )
649
return tr( "Could not save symbology because:\n%1" ).arg( errorMsg );
652
// check if the uri is a file or ends with .qml,
653
// which indicates that it should become one
654
// everything else goes to the database
657
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( this );
658
if ( vlayer && vlayer->providerType() == "ogr" )
660
QStringList theURIParts = theURI.split( "|" );
661
filename = theURIParts[0];
668
QFileInfo myFileInfo( filename );
669
if ( myFileInfo.exists() || filename.endsWith( ".qml", Qt::CaseInsensitive ) )
671
QFileInfo myDirInfo( myFileInfo.path() ); //excludes file name
672
if ( !myDirInfo.isWritable() )
674
return tr( "The directory containing your dataset needs to be writeable!" );
677
// now construct the file name for our .qml style file
678
QString myFileName = myFileInfo.path() + QDir::separator() + myFileInfo.completeBaseName() + ".qml";
680
QFile myFile( myFileName );
681
if ( myFile.open( QFile::WriteOnly | QFile::Truncate ) )
683
QTextStream myFileStream( &myFile );
684
// save as utf-8 with 2 spaces for indents
685
myDocument.save( myFileStream, 2 );
687
theResultFlag = true;
688
return tr( "Created default style file as %1" ).arg( myFileName );
692
theResultFlag = false;
693
return tr( "ERROR: Failed to created default style file as %1. Check file permissions and retry." ).arg( myFileName );
698
QString qml = myDocument.toString();
700
// read from database
702
sqlite3_stmt *myPreparedStatement;
706
myResult = sqlite3_open( QDir( QgsApplication::qgisSettingsDirPath() ).absoluteFilePath( "qgis.qmldb" ).toUtf8().data(), &myDatabase );
707
if ( myResult != SQLITE_OK )
709
return tr( "User database could not be opened." );
712
QByteArray param0 = theURI.toUtf8();
713
QByteArray param1 = qml.toUtf8();
715
QString mySql = "create table if not exists tbl_styles(style varchar primary key,qml varchar)";
716
myResult = sqlite3_prepare( myDatabase, mySql.toUtf8().data(), mySql.toUtf8().length(), &myPreparedStatement, &myTail );
717
if ( myResult == SQLITE_OK )
719
if ( sqlite3_step( myPreparedStatement ) != SQLITE_DONE )
721
sqlite3_finalize( myPreparedStatement );
722
sqlite3_close( myDatabase );
723
theResultFlag = false;
724
return tr( "The style table could not be created." );
728
sqlite3_finalize( myPreparedStatement );
730
mySql = "insert into tbl_styles(style,qml) values (?,?)";
731
myResult = sqlite3_prepare( myDatabase, mySql.toUtf8().data(), mySql.toUtf8().length(), &myPreparedStatement, &myTail );
732
if ( myResult == SQLITE_OK )
734
if ( sqlite3_bind_text( myPreparedStatement, 1, param0.data(), param0.length(), SQLITE_STATIC ) == SQLITE_OK &&
735
sqlite3_bind_text( myPreparedStatement, 2, param1.data(), param1.length(), SQLITE_STATIC ) == SQLITE_OK &&
736
sqlite3_step( myPreparedStatement ) == SQLITE_DONE )
738
theResultFlag = true;
739
myErrorMessage = tr( "The style %1 was saved to database" ).arg( theURI );
743
sqlite3_finalize( myPreparedStatement );
745
if ( !theResultFlag )
747
QString mySql = "update tbl_styles set qml=? where style=?";
748
myResult = sqlite3_prepare( myDatabase, mySql.toUtf8().data(), mySql.toUtf8().length(), &myPreparedStatement, &myTail );
749
if ( myResult == SQLITE_OK )
751
if ( sqlite3_bind_text( myPreparedStatement, 2, param0.data(), param0.length(), SQLITE_STATIC ) == SQLITE_OK &&
752
sqlite3_bind_text( myPreparedStatement, 1, param1.data(), param1.length(), SQLITE_STATIC ) == SQLITE_OK &&
753
sqlite3_step( myPreparedStatement ) == SQLITE_DONE )
755
theResultFlag = true;
756
myErrorMessage = tr( "The style %1 was updated in the database." ).arg( theURI );
760
theResultFlag = false;
761
myErrorMessage = tr( "The style %1 could not be updated in the database." ).arg( theURI );
767
theResultFlag = false;
768
myErrorMessage = tr( "The style %1 could not be inserted into database." ).arg( theURI );
771
sqlite3_finalize( myPreparedStatement );
774
sqlite3_close( myDatabase );
777
return myErrorMessage;
783
QUndoStack* QgsMapLayer::undoStack()
789
void QgsMapLayer::setCustomProperty( const QString& key, const QVariant& value )
791
mCustomProperties[key] = value;
794
QVariant QgsMapLayer::customProperty( const QString& value, const QVariant& defaultValue ) const
796
return mCustomProperties.value( value, defaultValue );
799
void QgsMapLayer::removeCustomProperty( const QString& key )
801
mCustomProperties.remove( key );
804
void QgsMapLayer::readCustomProperties( QDomNode & layerNode )
806
QDomNode propsNode = layerNode.namedItem( "customproperties" );
807
if ( propsNode.isNull() ) // no properties stored...
810
mCustomProperties.clear();
812
QDomNodeList nodes = propsNode.childNodes();
814
for ( int i = 0; i < nodes.size(); i++ )
816
QDomNode propNode = nodes.at( i );
817
if ( propNode.isNull() || propNode.nodeName() != "property" )
819
QDomElement propElement = propNode.toElement();
821
QString key = propElement.attribute( "key" );
822
QString value = propElement.attribute( "value" );
823
mCustomProperties[key] = QVariant( value );
828
void QgsMapLayer::writeCustomProperties( QDomNode & layerNode, QDomDocument & doc )
830
QDomElement propsElement = doc.createElement( "customproperties" );
832
for ( QMap<QString, QVariant>::const_iterator it = mCustomProperties.begin(); it != mCustomProperties.end(); ++it )
834
QDomElement propElement = doc.createElement( "property" );
835
propElement.setAttribute( "key", it.key() );
836
propElement.setAttribute( "value", it.value().toString() );
837
propsElement.appendChild( propElement );
840
layerNode.appendChild( propsElement );
843
void QgsMapLayer::setCacheImage( QImage * thepImage )
845
QgsDebugMsg( "cache Image set!" );
850
mpCacheImage = thepImage;