QGIS API Documentation  2.18.21-Las Palmas (9fba24a)
qgsofflineediting.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  offline_editing.cpp
3 
4  Offline Editing Plugin
5  a QGIS plugin
6  --------------------------------------
7  Date : 22-Jul-2010
8  Copyright : (C) 2010 by Sourcepole
9  Email : info at sourcepole.ch
10  ***************************************************************************
11  * *
12  * This program is free software; you can redistribute it and/or modify *
13  * it under the terms of the GNU General Public License as published by *
14  * the Free Software Foundation; either version 2 of the License, or *
15  * (at your option) any later version. *
16  * *
17  ***************************************************************************/
18 
19 
20 #include "qgsapplication.h"
21 #include "qgsdatasourceuri.h"
22 #include "qgsgeometry.h"
23 #include "qgslayertreegroup.h"
24 #include "qgslayertreelayer.h"
25 #include "qgsmaplayer.h"
26 #include "qgsmaplayerregistry.h"
27 #include "qgsofflineediting.h"
28 #include "qgsproject.h"
29 #include "qgsvectordataprovider.h"
32 #include "qgsslconnect.h"
34 
35 #include <QDir>
36 #include <QDomDocument>
37 #include <QDomNode>
38 #include <QFile>
39 #include <QMessageBox>
40 
41 extern "C"
42 {
43 #include <sqlite3.h>
44 #include <spatialite.h>
45 }
46 
47 // TODO: DEBUG
48 #include <QDebug>
49 // END
50 
51 #define CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE "isOfflineEditable"
52 #define CUSTOM_PROPERTY_REMOTE_SOURCE "remoteSource"
53 #define CUSTOM_PROPERTY_REMOTE_PROVIDER "remoteProvider"
54 #define PROJECT_ENTRY_SCOPE_OFFLINE "OfflineEditingPlugin"
55 #define PROJECT_ENTRY_KEY_OFFLINE_DB_PATH "/OfflineDbPath"
56 
58  syncError( false )
59 {
60  connect( QgsMapLayerRegistry::instance(), SIGNAL( layerWasAdded( QgsMapLayer* ) ), this, SLOT( layerAdded( QgsMapLayer* ) ) );
61 }
62 
64 {
65 }
66 
82 bool QgsOfflineEditing::convertToOfflineProject( const QString& offlineDataPath, const QString& offlineDbFile, const QStringList& layerIds, bool onlySelected )
83 {
84  if ( layerIds.isEmpty() )
85  {
86  return false;
87  }
88  QString dbPath = QDir( offlineDataPath ).absoluteFilePath( offlineDbFile );
89  if ( createSpatialiteDB( dbPath ) )
90  {
91  sqlite3* db;
92  int rc = QgsSLConnect::sqlite3_open( dbPath.toUtf8().constData(), &db );
93  if ( rc != SQLITE_OK )
94  {
95  showWarning( tr( "Could not open the spatialite database" ) );
96  }
97  else
98  {
99  // create logging tables
100  createLoggingTables( db );
101 
102  emit progressStarted();
103 
104  QMap<QString, QgsVectorJoinList > joinInfoBuffer;
105  QMap<QString, QgsVectorLayer*> layerIdMapping;
106 
107  Q_FOREACH ( const QString& layerId, layerIds )
108  {
109  QgsMapLayer* layer = QgsMapLayerRegistry::instance()->mapLayer( layerId );
110  QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( layer );
111  if ( !vl )
112  continue;
113  QgsVectorJoinList joins = vl->vectorJoins();
114 
115  // Layer names will be appended an _offline suffix
116  // Join fields are prefixed with the layer name and we do not want the
117  // field name to change so we stabilize the field name by defining a
118  // custom prefix with the layername without _offline suffix.
119  QgsVectorJoinList::iterator joinIt = joins.begin();
120  while ( joinIt != joins.end() )
121  {
122  if ( joinIt->prefix.isNull() )
123  {
124  QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( joinIt->joinLayerId ) );
125 
126  if ( vl )
127  joinIt->prefix = vl->name() + '_';
128  }
129  ++joinIt;
130  }
131  joinInfoBuffer.insert( vl->id(), joins );
132  }
133 
134  // copy selected vector layers to SpatiaLite
135  for ( int i = 0; i < layerIds.count(); i++ )
136  {
137  emit layerProgressUpdated( i + 1, layerIds.count() );
138 
139  QgsMapLayer* layer = QgsMapLayerRegistry::instance()->mapLayer( layerIds.at( i ) );
140  QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( layer );
141  if ( vl )
142  {
143  QString origLayerId = vl->id();
144  QgsVectorLayer* newLayer = copyVectorLayer( vl, db, dbPath, onlySelected );
145 
146  if ( newLayer )
147  {
148  layerIdMapping.insert( origLayerId, newLayer );
149  // remove remote layer
151  QStringList() << origLayerId );
152  }
153  }
154  }
155 
156  // restore join info on new spatialite layer
158  for ( it = joinInfoBuffer.constBegin(); it != joinInfoBuffer.constEnd(); ++it )
159  {
160  QgsVectorLayer* newLayer = layerIdMapping.value( it.key() );
161 
162  if ( newLayer )
163  {
164  Q_FOREACH ( QgsVectorJoinInfo join, it.value() )
165  {
166  QgsVectorLayer* newJoinedLayer = layerIdMapping.value( join.joinLayerId );
167  if ( newJoinedLayer )
168  {
169  // If the layer has been offline'd, update join information
170  join.joinLayerId = newJoinedLayer->id();
171  }
172  newLayer->addJoin( join );
173  }
174  }
175  }
176 
177 
178  emit progressStopped();
179 
181 
182  // save offline project
183  QString projectTitle = QgsProject::instance()->title();
184  if ( projectTitle.isEmpty() )
185  {
186  projectTitle = QFileInfo( QgsProject::instance()->fileName() ).fileName();
187  }
188  projectTitle += " (offline)";
189  QgsProject::instance()->setTitle( projectTitle );
190 
192 
193  return true;
194  }
195  }
196 
197  return false;
198 }
199 
201 {
203 }
204 
206 {
207  // open logging db
208  sqlite3* db = openLoggingDb();
209  if ( !db )
210  {
211  return;
212  }
213 
214  emit progressStarted();
215 
216  // restore and sync remote layers
217  QList<QgsMapLayer*> offlineLayers;
219  for ( QMap<QString, QgsMapLayer*>::iterator layer_it = mapLayers.begin() ; layer_it != mapLayers.end(); ++layer_it )
220  {
221  QgsMapLayer* layer = layer_it.value();
223  {
224  offlineLayers << layer;
225  }
226  }
227 
228  QgsDebugMsgLevel( QString( "Found %1 offline layers" ).arg( offlineLayers.count() ), 4 );
229  for ( int l = 0; l < offlineLayers.count(); l++ )
230  {
231  QgsMapLayer* layer = offlineLayers.at( l );
232 
233  emit layerProgressUpdated( l + 1, offlineLayers.count() );
234 
235  QString remoteSource = layer->customProperty( CUSTOM_PROPERTY_REMOTE_SOURCE, "" ).toString();
236  QString remoteProvider = layer->customProperty( CUSTOM_PROPERTY_REMOTE_PROVIDER, "" ).toString();
237  QString remoteName = layer->name();
238  remoteName.remove( QRegExp( " \\(offline\\)$" ) );
239 
240  QgsVectorLayer* remoteLayer = new QgsVectorLayer( remoteSource, remoteName, remoteProvider );
241  if ( remoteLayer->isValid() )
242  {
243  // Rebuild WFS cache to get feature id<->GML fid mapping
244  if ( remoteLayer->dataProvider()->name().contains( "WFS", Qt::CaseInsensitive ) )
245  {
246  QgsFeatureIterator fit = remoteLayer->getFeatures();
247  QgsFeature f;
248  while ( fit.nextFeature( f ) )
249  {
250  }
251  }
252  // TODO: only add remote layer if there are log entries?
253 
254  QgsVectorLayer* offlineLayer = qobject_cast<QgsVectorLayer*>( layer );
255 
256  // register this layer with the central layers registry
258 
259  // copy style
260  copySymbology( offlineLayer, remoteLayer );
261  updateRelations( offlineLayer, remoteLayer );
262  updateVisibilityPresets( offlineLayer, remoteLayer );
263 
264  // apply layer edit log
265  syncError = false;
266  QString qgisLayerId = layer->id();
267  QString sql = QString( "SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
268  int layerId = sqlQueryInt( db, sql, -1 );
269  if ( layerId != -1 )
270  {
271  remoteLayer->startEditing();
272 
273  // TODO: only get commitNos of this layer?
274  int commitNo = getCommitNo( db );
275  QgsDebugMsgLevel( QString( "Found %1 commits" ).arg( commitNo ), 4 );
276  for ( int i = 0; i < commitNo; i++ )
277  {
278  QgsDebugMsgLevel( "Apply commits chronologically", 4 );
279  // apply commits chronologically
280  applyAttributesAdded( remoteLayer, db, layerId, i );
281  if ( !syncError )
282  applyAttributeValueChanges( offlineLayer, remoteLayer, db, layerId, i );
283  if ( !syncError )
284  applyGeometryChanges( remoteLayer, db, layerId, i );
285  }
286 
287  if ( !syncError )
288  applyFeaturesAdded( offlineLayer, remoteLayer, db, layerId );
289  if ( !syncError )
290  applyFeaturesRemoved( remoteLayer, db, layerId );
291 
292  if ( !syncError )
293  {
294  if ( remoteLayer->commitChanges() )
295  {
296  // update fid lookup
297  updateFidLookup( remoteLayer, db, layerId );
298 
299  // clear edit log for this layer
300  sql = QString( "DELETE FROM 'log_added_attrs' WHERE \"layer_id\" = %1" ).arg( layerId );
301  sqlExec( db, sql );
302  sql = QString( "DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
303  sqlExec( db, sql );
304  sql = QString( "DELETE FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
305  sqlExec( db, sql );
306  sql = QString( "DELETE FROM 'log_feature_updates' WHERE \"layer_id\" = %1" ).arg( layerId );
307  sqlExec( db, sql );
308  sql = QString( "DELETE FROM 'log_geometry_updates' WHERE \"layer_id\" = %1" ).arg( layerId );
309  sqlExec( db, sql );
310  }
311  else
312  {
313  showWarning( remoteLayer->commitErrors().join( "\n" ) );
314  }
315  }
316  else
317  {
318  remoteLayer->rollBack();
319  showWarning( tr( "Synchronization failed" ) );
320  }
321  }
322  else
323  {
324  QgsDebugMsg( "Could not find the layer id in the edit logs!" );
325  }
326  // Invalidate the connection to force a reload if the project is put offline
327  // again with the same path
328  offlineLayer->dataProvider()->invalidateConnections( QgsDataSourceURI( offlineLayer->source() ).database() );
329  // remove offline layer
331 
332 
333  // disable offline project
334  QString projectTitle = QgsProject::instance()->title();
335  projectTitle.remove( QRegExp( " \\(offline\\)$" ) );
336  QgsProject::instance()->setTitle( projectTitle );
338  remoteLayer->reload(); //update with other changes
339  }
340  else
341  {
342  QgsDebugMsg( "Remote layer is not valid!" );
343  }
344  }
345 
346  // reset commitNo
347  QString sql = QString( "UPDATE 'log_indices' SET 'last_index' = 0 WHERE \"name\" = 'commit_no'" );
348  sqlExec( db, sql );
349 
350  emit progressStopped();
351 
352  sqlite3_close( db );
353 }
354 
355 void QgsOfflineEditing::initializeSpatialMetadata( sqlite3 *sqlite_handle )
356 {
357  // attempting to perform self-initialization for a newly created DB
358  if ( !sqlite_handle )
359  return;
360  // checking if this DB is really empty
361  char **results;
362  int rows, columns;
363  int ret = sqlite3_get_table( sqlite_handle, "select count(*) from sqlite_master", &results, &rows, &columns, nullptr );
364  if ( ret != SQLITE_OK )
365  return;
366  int count = 0;
367  if ( rows >= 1 )
368  {
369  for ( int i = 1; i <= rows; i++ )
370  count = atoi( results[( i * columns ) + 0] );
371  }
372 
373  sqlite3_free_table( results );
374 
375  if ( count > 0 )
376  return;
377 
378  bool above41 = false;
379  ret = sqlite3_get_table( sqlite_handle, "select spatialite_version()", &results, &rows, &columns, nullptr );
380  if ( ret == SQLITE_OK && rows == 1 && columns == 1 )
381  {
382  QString version = QString::fromUtf8( results[1] );
383  QStringList parts = version.split( ' ', QString::SkipEmptyParts );
384  if ( parts.size() >= 1 )
385  {
386  QStringList verparts = parts[0].split( '.', QString::SkipEmptyParts );
387  above41 = verparts.size() >= 2 && ( verparts[0].toInt() > 4 || ( verparts[0].toInt() == 4 && verparts[1].toInt() >= 1 ) );
388  }
389  }
390 
391  sqlite3_free_table( results );
392 
393  // all right, it's empty: proceding to initialize
394  char *errMsg = nullptr;
395  ret = sqlite3_exec( sqlite_handle, above41 ? "SELECT InitSpatialMetadata(1)" : "SELECT InitSpatialMetadata()", nullptr, nullptr, &errMsg );
396 
397  if ( ret != SQLITE_OK )
398  {
399  QString errCause = tr( "Unable to initialize SpatialMetadata:\n" );
400  errCause += QString::fromUtf8( errMsg );
401  showWarning( errCause );
402  sqlite3_free( errMsg );
403  return;
404  }
405  spatial_ref_sys_init( sqlite_handle, 0 );
406 }
407 
408 bool QgsOfflineEditing::createSpatialiteDB( const QString& offlineDbPath )
409 {
410  int ret;
411  sqlite3 *sqlite_handle;
412  char *errMsg = nullptr;
413  QFile newDb( offlineDbPath );
414  if ( newDb.exists() )
415  {
416  QFile::remove( offlineDbPath );
417  }
418 
419  // see also QgsNewSpatialiteLayerDialog::createDb()
420 
421  QFileInfo fullPath = QFileInfo( offlineDbPath );
422  QDir path = fullPath.dir();
423 
424  // Must be sure there is destination directory ~/.qgis
425  QDir().mkpath( path.absolutePath() );
426 
427  // creating/opening the new database
428  QString dbPath = newDb.fileName();
429  ret = QgsSLConnect::sqlite3_open_v2( dbPath.toUtf8().constData(), &sqlite_handle, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr );
430  if ( ret )
431  {
432  // an error occurred
433  QString errCause = tr( "Could not create a new database\n" );
434  errCause += QString::fromUtf8( sqlite3_errmsg( sqlite_handle ) );
435  sqlite3_close( sqlite_handle );
436  showWarning( errCause );
437  return false;
438  }
439  // activating Foreign Key constraints
440  ret = sqlite3_exec( sqlite_handle, "PRAGMA foreign_keys = 1", nullptr, nullptr, &errMsg );
441  if ( ret != SQLITE_OK )
442  {
443  showWarning( tr( "Unable to activate FOREIGN_KEY constraints" ) );
444  sqlite3_free( errMsg );
445  QgsSLConnect::sqlite3_close( sqlite_handle );
446  return false;
447  }
448  initializeSpatialMetadata( sqlite_handle );
449 
450  // all done: closing the DB connection
451  QgsSLConnect::sqlite3_close( sqlite_handle );
452 
453  return true;
454 }
455 
456 void QgsOfflineEditing::createLoggingTables( sqlite3* db )
457 {
458  // indices
459  QString sql = "CREATE TABLE 'log_indices' ('name' TEXT, 'last_index' INTEGER)";
460  sqlExec( db, sql );
461 
462  sql = "INSERT INTO 'log_indices' VALUES ('commit_no', 0)";
463  sqlExec( db, sql );
464 
465  sql = "INSERT INTO 'log_indices' VALUES ('layer_id', 0)";
466  sqlExec( db, sql );
467 
468  // layername <-> layer id
469  sql = "CREATE TABLE 'log_layer_ids' ('id' INTEGER, 'qgis_id' TEXT)";
470  sqlExec( db, sql );
471 
472  // offline fid <-> remote fid
473  sql = "CREATE TABLE 'log_fids' ('layer_id' INTEGER, 'offline_fid' INTEGER, 'remote_fid' INTEGER)";
474  sqlExec( db, sql );
475 
476  // added attributes
477  sql = "CREATE TABLE 'log_added_attrs' ('layer_id' INTEGER, 'commit_no' INTEGER, ";
478  sql += "'name' TEXT, 'type' INTEGER, 'length' INTEGER, 'precision' INTEGER, 'comment' TEXT)";
479  sqlExec( db, sql );
480 
481  // added features
482  sql = "CREATE TABLE 'log_added_features' ('layer_id' INTEGER, 'fid' INTEGER)";
483  sqlExec( db, sql );
484 
485  // removed features
486  sql = "CREATE TABLE 'log_removed_features' ('layer_id' INTEGER, 'fid' INTEGER)";
487  sqlExec( db, sql );
488 
489  // feature updates
490  sql = "CREATE TABLE 'log_feature_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'attr' INTEGER, 'value' TEXT)";
491  sqlExec( db, sql );
492 
493  // geometry updates
494  sql = "CREATE TABLE 'log_geometry_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'geom_wkt' TEXT)";
495  sqlExec( db, sql );
496 
497  /* TODO: other logging tables
498  - attr delete (not supported by SpatiaLite provider)
499  */
500 }
501 
502 QgsVectorLayer* QgsOfflineEditing::copyVectorLayer( QgsVectorLayer* layer, sqlite3* db, const QString& offlineDbPath , bool onlySelected )
503 {
504  if ( !layer )
505  return nullptr;
506 
507  QString tableName = layer->id();
508  QgsDebugMsgLevel( QString( "Creating offline table %1 ..." ).arg( tableName ), 4 );
509 
510  // create table
511  QString sql = QString( "CREATE TABLE '%1' (" ).arg( tableName );
512  QString delim = "";
513  Q_FOREACH ( const QgsField& field, layer->dataProvider()->fields() )
514  {
515  QString dataType = "";
516  QVariant::Type type = field.type();
517  if ( type == QVariant::Int || type == QVariant::LongLong )
518  {
519  dataType = "INTEGER";
520  }
521  else if ( type == QVariant::Double )
522  {
523  dataType = "REAL";
524  }
525  else if ( type == QVariant::String )
526  {
527  dataType = "TEXT";
528  }
529  else
530  {
531  showWarning( tr( "%1: Unknown data type %2. Not using type affinity for the field." ).arg( field.name(), QVariant::typeToName( type ) ) );
532  }
533 
534  sql += delim + QString( "'%1' %2" ).arg( field.name(), dataType );
535  delim = ',';
536  }
537  sql += ')';
538 
539  int rc = sqlExec( db, sql );
540 
541  // add geometry column
542  if ( layer->hasGeometryType() )
543  {
544  QString geomType = "";
545  switch ( QGis::flatType( layer->wkbType() ) )
546  {
547  case QGis::WKBPoint:
548  geomType = "POINT";
549  break;
550  case QGis::WKBMultiPoint:
551  geomType = "MULTIPOINT";
552  break;
553  case QGis::WKBLineString:
554  geomType = "LINESTRING";
555  break;
557  geomType = "MULTILINESTRING";
558  break;
559  case QGis::WKBPolygon:
560  geomType = "POLYGON";
561  break;
563  geomType = "MULTIPOLYGON";
564  break;
565  default:
566  showWarning( tr( "QGIS wkbType %1 not supported" ).arg( layer->wkbType() ) );
567  break;
568  };
569 
570  if ( QGis::flatType( layer->wkbType() ) != layer->wkbType() )
571  showWarning( tr( "Will drop Z and M values from layer %1 in offline copy." ).arg( layer->name() ) );
572 
573  QString sqlAddGeom = QString( "SELECT AddGeometryColumn('%1', 'Geometry', %2, '%3', 2)" )
574  .arg( tableName )
575  .arg( layer->crs().authid().startsWith( "EPSG:", Qt::CaseInsensitive ) ? layer->crs().authid().mid( 5 ).toLong() : 0 )
576  .arg( geomType );
577 
578  // create spatial index
579  QString sqlCreateIndex = QString( "SELECT CreateSpatialIndex('%1', 'Geometry')" ).arg( tableName );
580 
581  if ( rc == SQLITE_OK )
582  {
583  rc = sqlExec( db, sqlAddGeom );
584  if ( rc == SQLITE_OK )
585  {
586  rc = sqlExec( db, sqlCreateIndex );
587  }
588  }
589  }
590 
591  if ( rc == SQLITE_OK )
592  {
593  // add new layer
594  QString connectionString = QString( "dbname='%1' table='%2'%3 sql=" )
595  .arg( offlineDbPath,
596  tableName, layer->hasGeometryType() ? "(Geometry)" : "" );
597  QgsVectorLayer* newLayer = new QgsVectorLayer( connectionString,
598  layer->name() + " (offline)", "spatialite" );
599  if ( newLayer->isValid() )
600  {
601  // mark as offline layer
603 
604  // store original layer source
607 
608  // register this layer with the central layers registry
610  QList<QgsMapLayer *>() << newLayer );
611 
612  // copy style
614  bool hasLabels = layer->hasLabelsEnabled();
616  if ( !hasLabels )
617  {
618  // NOTE: copy symbology before adding the layer so it is displayed correctly
619  copySymbology( layer, newLayer );
620  }
621 
623  // Find the parent group of the original layer
624  QgsLayerTreeLayer* layerTreeLayer = layerTreeRoot->findLayer( layer->id() );
625  if ( layerTreeLayer )
626  {
627  QgsLayerTreeGroup* parentTreeGroup = qobject_cast<QgsLayerTreeGroup*>( layerTreeLayer->parent() );
628  if ( parentTreeGroup )
629  {
630  int index = parentTreeGroup->children().indexOf( layerTreeLayer );
631  // Move the new layer from the root group to the new group
632  QgsLayerTreeLayer* newLayerTreeLayer = layerTreeRoot->findLayer( newLayer->id() );
633  if ( newLayerTreeLayer )
634  {
635  QgsLayerTreeNode* newLayerTreeLayerClone = newLayerTreeLayer->clone();
636  QgsLayerTreeGroup* grp = qobject_cast<QgsLayerTreeGroup*>( newLayerTreeLayer->parent() );
637  parentTreeGroup->insertChildNode( index, newLayerTreeLayerClone );
638  if ( grp )
639  grp->removeChildNode( newLayerTreeLayer );
640  }
641  }
642  }
643 
644  if ( hasLabels )
645  {
646  // NOTE: copy symbology of layers with labels enabled after adding to project, as it will crash otherwise (WORKAROUND)
647  copySymbology( layer, newLayer );
648  }
649 
650  updateRelations( layer, newLayer );
651  updateVisibilityPresets( layer, newLayer );
652  // copy features
653  newLayer->startEditing();
654  QgsFeature f;
655 
656  QgsFeatureRequest req;
657 
658  if ( onlySelected )
659  {
660  QgsFeatureIds selectedFids = layer->selectedFeaturesIds();
661  if ( !selectedFids.isEmpty() )
662  req.setFilterFids( selectedFids );
663  }
664 
665  // NOTE: force feature recount for PostGIS layer, else only visible features are counted, before iterating over all features (WORKAROUND)
666  layer->setSubsetString( layer->subsetString() );
667 
668  QgsFeatureIterator fit = layer->dataProvider()->getFeatures( req );
669 
671  {
673  }
674  else
675  {
677  }
678  int featureCount = 1;
679 
680  QList<QgsFeatureId> remoteFeatureIds;
681  while ( fit.nextFeature( f ) )
682  {
683  remoteFeatureIds << f.id();
684 
685  // NOTE: Spatialite provider ignores position of geometry column
686  // fill gap in QgsAttributeMap if geometry column is not last (WORKAROUND)
687  int column = 0;
688  QgsAttributes attrs = f.attributes();
689  QgsAttributes newAttrs( attrs.count() );
690  for ( int it = 0; it < attrs.count(); ++it )
691  {
692  newAttrs[column++] = attrs.at( it );
693  }
694  f.setAttributes( newAttrs );
695 
696  // The spatialite provider doesn't properly handle Z and M values
697  if ( f.geometry() && f.geometry()->geometry() )
698  {
699  QgsAbstractGeometryV2* geom = f.geometry()->geometry()->clone();
700  geom->dropZValue();
701  geom->dropMValue();
702  f.geometry()->setGeometry( geom );
703  }
704 
705  if ( !newLayer->addFeature( f, false ) )
706  return nullptr;
707 
708  emit progressUpdated( featureCount++ );
709  }
710  if ( newLayer->commitChanges() )
711  {
713  featureCount = 1;
714 
715  // update feature id lookup
716  int layerId = getOrCreateLayerId( db, newLayer->id() );
717  QList<QgsFeatureId> offlineFeatureIds;
718 
719  QgsFeatureIterator fit = newLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( QgsAttributeList() ) );
720  while ( fit.nextFeature( f ) )
721  {
722  offlineFeatureIds << f.id();
723  }
724 
725  // NOTE: insert fids in this loop, as the db is locked during newLayer->nextFeature()
726  sqlExec( db, "BEGIN" );
727  int remoteCount = remoteFeatureIds.size();
728  for ( int i = 0; i < remoteCount; i++ )
729  {
730  // Check if the online feature has been fetched (WFS download aborted for some reason)
731  if ( i < offlineFeatureIds.count() )
732  {
733  addFidLookup( db, layerId, offlineFeatureIds.at( i ), remoteFeatureIds.at( i ) );
734  }
735  else
736  {
737  showWarning( tr( "Feature cannot be copied to the offline layer, please check if the online layer '%1' is still accessible." ).arg( layer->name() ) );
738  return nullptr;
739  }
740  emit progressUpdated( featureCount++ );
741  }
742  sqlExec( db, "COMMIT" );
743  }
744  else
745  {
746  showWarning( newLayer->commitErrors().join( "\n" ) );
747  }
748  }
749  return newLayer;
750  }
751  return nullptr;
752 }
753 
754 void QgsOfflineEditing::applyAttributesAdded( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId, int commitNo )
755 {
756  QString sql = QString( "SELECT \"name\", \"type\", \"length\", \"precision\", \"comment\" FROM 'log_added_attrs' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo );
757  QList<QgsField> fields = sqlQueryAttributesAdded( db, sql );
758 
759  const QgsVectorDataProvider* provider = remoteLayer->dataProvider();
760  QList<QgsVectorDataProvider::NativeType> nativeTypes = provider->nativeTypes();
761 
762  // NOTE: uses last matching QVariant::Type of nativeTypes
763  QMap < QVariant::Type, QString /*typeName*/ > typeNameLookup;
764  for ( int i = 0; i < nativeTypes.size(); i++ )
765  {
766  QgsVectorDataProvider::NativeType nativeType = nativeTypes.at( i );
767  typeNameLookup[ nativeType.mType ] = nativeType.mTypeName;
768  }
769 
771 
772  for ( int i = 0; i < fields.size(); i++ )
773  {
774  // lookup typename from layer provider
775  QgsField field = fields[i];
776  if ( typeNameLookup.contains( field.type() ) )
777  {
778  QString typeName = typeNameLookup[ field.type()];
779  field.setTypeName( typeName );
780  if ( !remoteLayer->addAttribute( field ) )
781  {
782  syncError = true;
783  return;
784  }
785  }
786  else
787  {
788  showWarning( QString( "Could not add attribute '%1' of type %2" ).arg( field.name() ).arg( field.type() ) );
789  }
790 
791  emit progressUpdated( i + 1 );
792  }
793 }
794 
795 void QgsOfflineEditing::applyFeaturesAdded( QgsVectorLayer* offlineLayer, QgsVectorLayer* remoteLayer, sqlite3* db, int layerId )
796 {
797  QString sql = QString( "SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
798  QList<int> newFeatureIds = sqlQueryInts( db, sql );
799 
800  QgsFields remoteFlds = remoteLayer->fields();
801 
802  QgsExpressionContext context;
805  << QgsExpressionContextUtils::layerScope( remoteLayer );
806 
807  // get new features from offline layer
808  QgsFeatureList features;
809  for ( int i = 0; i < newFeatureIds.size(); i++ )
810  {
811  QgsFeature feature;
812  if ( offlineLayer->getFeatures( QgsFeatureRequest().setFilterFid( newFeatureIds.at( i ) ) ).nextFeature( feature ) )
813  {
814  features << feature;
815  }
816  }
817 
818  // copy features to remote layer
820 
821  int i = 1;
822  int newAttrsCount = remoteLayer->fields().count();
823  for ( QgsFeatureList::iterator it = features.begin(); it != features.end(); ++it )
824  {
825  QgsFeature f = *it;
826 
827  // NOTE: Spatialite provider ignores position of geometry column
828  // restore gap in QgsAttributeMap if geometry column is not last (WORKAROUND)
829  QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
830  QgsAttributes newAttrs( newAttrsCount );
831  QgsAttributes attrs = f.attributes();
832  for ( int it = 0; it < attrs.count(); ++it )
833  {
834  newAttrs[ attrLookup[ it ] ] = attrs.at( it );
835  }
836 
837  // try to use default value from the provider
838  // (important especially e.g. for postgis primary key generated from a sequence)
839  for ( int k = 0; k < newAttrs.count(); ++k )
840  {
841  if ( !newAttrs.at( k ).isNull() )
842  continue;
843 
844  if ( !remoteLayer->defaultValueExpression( k ).isEmpty() )
845  newAttrs[k] = remoteLayer->defaultValue( k, f, &context );
846  else if ( remoteFlds.fieldOrigin( k ) == QgsFields::OriginProvider )
847  newAttrs[k] = remoteLayer->dataProvider()->defaultValue( remoteFlds.fieldOriginIndex( k ) );
848  }
849 
850  f.setAttributes( newAttrs );
851 
852  if ( !remoteLayer->addFeature( f, false ) )
853  {
854  syncError = true;
855  return;
856  }
857 
858  emit progressUpdated( i++ );
859  }
860 }
861 
862 void QgsOfflineEditing::applyFeaturesRemoved( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId )
863 {
864  QString sql = QString( "SELECT \"fid\" FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
865  QgsFeatureIds values = sqlQueryFeaturesRemoved( db, sql );
866 
868 
869  int i = 1;
870  for ( QgsFeatureIds::const_iterator it = values.begin(); it != values.end(); ++it )
871  {
872  QgsFeatureId fid = remoteFid( db, layerId, *it );
873  if ( !remoteLayer->deleteFeature( fid ) )
874  {
875  syncError = true;
876  return;
877  }
878 
879  emit progressUpdated( i++ );
880  }
881 }
882 
883 void QgsOfflineEditing::applyAttributeValueChanges( QgsVectorLayer* offlineLayer, QgsVectorLayer* remoteLayer, sqlite3* db, int layerId, int commitNo )
884 {
885  QString sql = QString( "SELECT \"fid\", \"attr\", \"value\" FROM 'log_feature_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2 " ).arg( layerId ).arg( commitNo );
886  AttributeValueChanges values = sqlQueryAttributeValueChanges( db, sql );
887 
889 
890  QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
891 
892  for ( int i = 0; i < values.size(); i++ )
893  {
894  QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid );
895  QgsDebugMsgLevel( QString( "Offline changeAttributeValue %1 = %2" ).arg( QString( attrLookup[ values.at( i ).attr ] ), values.at( i ).value ), 4 );
896  if ( !remoteLayer->changeAttributeValue( fid, attrLookup[ values.at( i ).attr ], values.at( i ).value ) )
897  {
898  syncError = true;
899  return;
900  }
901 
902  emit progressUpdated( i + 1 );
903  }
904 }
905 
906 void QgsOfflineEditing::applyGeometryChanges( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId, int commitNo )
907 {
908  QString sql = QString( "SELECT \"fid\", \"geom_wkt\" FROM 'log_geometry_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo );
909  GeometryChanges values = sqlQueryGeometryChanges( db, sql );
910 
912 
913  for ( int i = 0; i < values.size(); i++ )
914  {
915  QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid );
916  if ( !remoteLayer->changeGeometry( fid, QgsGeometry::fromWkt( values.at( i ).geom_wkt ) ) )
917  {
918  syncError = true;
919  return;
920  }
921 
922  emit progressUpdated( i + 1 );
923  }
924 }
925 
926 void QgsOfflineEditing::updateFidLookup( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId )
927 {
928  // update fid lookup for added features
929 
930  // get remote added fids
931  // NOTE: use QMap for sorted fids
932  QMap < QgsFeatureId, bool /*dummy*/ > newRemoteFids;
933  QgsFeature f;
934 
935  QgsFeatureIterator fit = remoteLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( QgsAttributeList() ) );
936 
938 
939  int i = 1;
940  while ( fit.nextFeature( f ) )
941  {
942  if ( offlineFid( db, layerId, f.id() ) == -1 )
943  {
944  newRemoteFids[ f.id()] = true;
945  }
946 
947  emit progressUpdated( i++ );
948  }
949 
950  // get local added fids
951  // NOTE: fids are sorted
952  QString sql = QString( "SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
953  QList<int> newOfflineFids = sqlQueryInts( db, sql );
954 
955  if ( newRemoteFids.size() != newOfflineFids.size() )
956  {
957  //showWarning( QString( "Different number of new features on offline layer (%1) and remote layer (%2)" ).arg(newOfflineFids.size()).arg(newRemoteFids.size()) );
958  }
959  else
960  {
961  // add new fid lookups
962  i = 0;
963  sqlExec( db, "BEGIN" );
964  for ( QMap<QgsFeatureId, bool>::const_iterator it = newRemoteFids.begin(); it != newRemoteFids.end(); ++it )
965  {
966  addFidLookup( db, layerId, newOfflineFids.at( i++ ), it.key() );
967  }
968  sqlExec( db, "COMMIT" );
969  }
970 }
971 
972 void QgsOfflineEditing::copySymbology( QgsVectorLayer* sourceLayer, QgsVectorLayer* targetLayer )
973 {
974  QString error;
975  QDomDocument doc;
976  sourceLayer->exportNamedStyle( doc, error );
977 
978  if ( error.isEmpty() )
979  {
980  targetLayer->importNamedStyle( doc, error );
981  }
982  if ( !error.isEmpty() )
983  {
984  showWarning( error );
985  }
986 }
987 
988 void QgsOfflineEditing::updateRelations( QgsVectorLayer* sourceLayer, QgsVectorLayer* targetLayer )
989 {
991  QList<QgsRelation> relations;
992  relations = relationManager->referencedRelations( sourceLayer );
993 
994  Q_FOREACH ( QgsRelation relation, relations )
995  {
996  relationManager->removeRelation( relation );
997  relation.setReferencedLayer( targetLayer->id() );
998  relationManager->addRelation( relation );
999  }
1000 
1001  relations = relationManager->referencingRelations( sourceLayer );
1002 
1003  Q_FOREACH ( QgsRelation relation, relations )
1004  {
1005  relationManager->removeRelation( relation );
1006  relation.setReferencingLayer( targetLayer->id() );
1007  relationManager->addRelation( relation );
1008  }
1009 }
1010 
1011 void QgsOfflineEditing::updateVisibilityPresets( QgsVectorLayer* sourceLayer, QgsVectorLayer* targetLayer )
1012 {
1014  QStringList visibilityPresets = presetCollection->presets();
1015 
1016  Q_FOREACH ( const QString& preset, visibilityPresets )
1017  {
1018  QgsVisibilityPresetCollection::PresetRecord record = presetCollection->presetState( preset );
1019 
1020  if ( record.mVisibleLayerIDs.removeOne( sourceLayer->id() ) )
1021  record.mVisibleLayerIDs.append( targetLayer->id() );
1022 
1023  QString style = record.mPerLayerCurrentStyle.value( sourceLayer->id() );
1024  if ( !style.isNull() )
1025  {
1026  record.mPerLayerCurrentStyle.remove( sourceLayer->id() );
1027  record.mPerLayerCurrentStyle.insert( targetLayer->id(), style );
1028  }
1029 
1030  if ( !record.mPerLayerCheckedLegendSymbols.contains( sourceLayer->id() ) )
1031  {
1032  QSet<QString> checkedSymbols = record.mPerLayerCheckedLegendSymbols.value( sourceLayer->id() );
1033  record.mPerLayerCheckedLegendSymbols.remove( sourceLayer->id() );
1034  record.mPerLayerCheckedLegendSymbols.insert( targetLayer->id(), checkedSymbols );
1035  }
1036 
1037  QgsProject::instance()->visibilityPresetCollection()->update( preset, record );
1038  }
1039 }
1040 
1041 // NOTE: use this to map column indices in case the remote geometry column is not last
1042 QMap<int, int> QgsOfflineEditing::attributeLookup( QgsVectorLayer* offlineLayer, QgsVectorLayer* remoteLayer )
1043 {
1044  const QgsAttributeList& offlineAttrs = offlineLayer->attributeList();
1045  const QgsAttributeList& remoteAttrs = remoteLayer->attributeList();
1046 
1047  QMap < int /*offline attr*/, int /*remote attr*/ > attrLookup;
1048  // NOTE: use size of remoteAttrs, as offlineAttrs can have new attributes not yet synced
1049  for ( int i = 0; i < remoteAttrs.size(); i++ )
1050  {
1051  attrLookup.insert( offlineAttrs.at( i ), remoteAttrs.at( i ) );
1052  }
1053 
1054  return attrLookup;
1055 }
1056 
1057 void QgsOfflineEditing::showWarning( const QString& message )
1058 {
1059  emit warning( tr( "Offline Editing Plugin" ), message );
1060 }
1061 
1062 sqlite3* QgsOfflineEditing::openLoggingDb()
1063 {
1064  sqlite3* db = nullptr;
1066  if ( !dbPath.isEmpty() )
1067  {
1068  QString absoluteDbPath = QgsProject::instance()->readPath( dbPath );
1069  int rc = sqlite3_open( absoluteDbPath.toUtf8().constData(), &db );
1070  if ( rc != SQLITE_OK )
1071  {
1072  QgsDebugMsg( "Could not open the spatialite logging database" );
1073  showWarning( tr( "Could not open the spatialite logging database" ) );
1074  sqlite3_close( db );
1075  db = nullptr;
1076  }
1077  }
1078  else
1079  {
1080  QgsDebugMsg( "dbPath is empty!" );
1081  }
1082  return db;
1083 }
1084 
1085 int QgsOfflineEditing::getOrCreateLayerId( sqlite3* db, const QString& qgisLayerId )
1086 {
1087  QString sql = QString( "SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
1088  int layerId = sqlQueryInt( db, sql, -1 );
1089  if ( layerId == -1 )
1090  {
1091  // next layer id
1092  sql = "SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'layer_id'";
1093  int newLayerId = sqlQueryInt( db, sql, -1 );
1094 
1095  // insert layer
1096  sql = QString( "INSERT INTO 'log_layer_ids' VALUES (%1, '%2')" ).arg( newLayerId ).arg( qgisLayerId );
1097  sqlExec( db, sql );
1098 
1099  // increase layer_id
1100  // TODO: use trigger for auto increment?
1101  sql = QString( "UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'layer_id'" ).arg( newLayerId + 1 );
1102  sqlExec( db, sql );
1103 
1104  layerId = newLayerId;
1105  }
1106 
1107  return layerId;
1108 }
1109 
1110 int QgsOfflineEditing::getCommitNo( sqlite3* db )
1111 {
1112  QString sql = "SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'commit_no'";
1113  return sqlQueryInt( db, sql, -1 );
1114 }
1115 
1116 void QgsOfflineEditing::increaseCommitNo( sqlite3* db )
1117 {
1118  QString sql = QString( "UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'commit_no'" ).arg( getCommitNo( db ) + 1 );
1119  sqlExec( db, sql );
1120 }
1121 
1122 void QgsOfflineEditing::addFidLookup( sqlite3* db, int layerId, QgsFeatureId offlineFid, QgsFeatureId remoteFid )
1123 {
1124  QString sql = QString( "INSERT INTO 'log_fids' VALUES ( %1, %2, %3 )" ).arg( layerId ).arg( offlineFid ).arg( remoteFid );
1125  sqlExec( db, sql );
1126 }
1127 
1128 QgsFeatureId QgsOfflineEditing::remoteFid( sqlite3* db, int layerId, QgsFeatureId offlineFid )
1129 {
1130  QString sql = QString( "SELECT \"remote_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2" ).arg( layerId ).arg( offlineFid );
1131  return sqlQueryInt( db, sql, -1 );
1132 }
1133 
1134 QgsFeatureId QgsOfflineEditing::offlineFid( sqlite3* db, int layerId, QgsFeatureId remoteFid )
1135 {
1136  QString sql = QString( "SELECT \"offline_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"remote_fid\" = %2" ).arg( layerId ).arg( remoteFid );
1137  return sqlQueryInt( db, sql, -1 );
1138 }
1139 
1140 bool QgsOfflineEditing::isAddedFeature( sqlite3* db, int layerId, QgsFeatureId fid )
1141 {
1142  QString sql = QString( "SELECT COUNT(\"fid\") FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( fid );
1143  return ( sqlQueryInt( db, sql, 0 ) > 0 );
1144 }
1145 
1146 int QgsOfflineEditing::sqlExec( sqlite3* db, const QString& sql )
1147 {
1148  char * errmsg;
1149  int rc = sqlite3_exec( db, sql.toUtf8(), nullptr, nullptr, &errmsg );
1150  if ( rc != SQLITE_OK )
1151  {
1152  showWarning( errmsg );
1153  }
1154  return rc;
1155 }
1156 
1157 int QgsOfflineEditing::sqlQueryInt( sqlite3* db, const QString& sql, int defaultValue )
1158 {
1159  sqlite3_stmt* stmt = nullptr;
1160  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1161  {
1162  showWarning( sqlite3_errmsg( db ) );
1163  return defaultValue;
1164  }
1165 
1166  int value = defaultValue;
1167  int ret = sqlite3_step( stmt );
1168  if ( ret == SQLITE_ROW )
1169  {
1170  value = sqlite3_column_int( stmt, 0 );
1171  }
1172  sqlite3_finalize( stmt );
1173 
1174  return value;
1175 }
1176 
1177 QList<int> QgsOfflineEditing::sqlQueryInts( sqlite3* db, const QString& sql )
1178 {
1179  QList<int> values;
1180 
1181  sqlite3_stmt* stmt = nullptr;
1182  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1183  {
1184  showWarning( sqlite3_errmsg( db ) );
1185  return values;
1186  }
1187 
1188  int ret = sqlite3_step( stmt );
1189  while ( ret == SQLITE_ROW )
1190  {
1191  values << sqlite3_column_int( stmt, 0 );
1192 
1193  ret = sqlite3_step( stmt );
1194  }
1195  sqlite3_finalize( stmt );
1196 
1197  return values;
1198 }
1199 
1200 QList<QgsField> QgsOfflineEditing::sqlQueryAttributesAdded( sqlite3* db, const QString& sql )
1201 {
1202  QList<QgsField> values;
1203 
1204  sqlite3_stmt* stmt = nullptr;
1205  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1206  {
1207  showWarning( sqlite3_errmsg( db ) );
1208  return values;
1209  }
1210 
1211  int ret = sqlite3_step( stmt );
1212  while ( ret == SQLITE_ROW )
1213  {
1214  QgsField field( QString( reinterpret_cast< const char* >( sqlite3_column_text( stmt, 0 ) ) ),
1215  static_cast< QVariant::Type >( sqlite3_column_int( stmt, 1 ) ),
1216  "", // typeName
1217  sqlite3_column_int( stmt, 2 ),
1218  sqlite3_column_int( stmt, 3 ),
1219  QString( reinterpret_cast< const char* >( sqlite3_column_text( stmt, 4 ) ) ) );
1220  values << field;
1221 
1222  ret = sqlite3_step( stmt );
1223  }
1224  sqlite3_finalize( stmt );
1225 
1226  return values;
1227 }
1228 
1229 QgsFeatureIds QgsOfflineEditing::sqlQueryFeaturesRemoved( sqlite3* db, const QString& sql )
1230 {
1231  QgsFeatureIds values;
1232 
1233  sqlite3_stmt* stmt = nullptr;
1234  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1235  {
1236  showWarning( sqlite3_errmsg( db ) );
1237  return values;
1238  }
1239 
1240  int ret = sqlite3_step( stmt );
1241  while ( ret == SQLITE_ROW )
1242  {
1243  values << sqlite3_column_int( stmt, 0 );
1244 
1245  ret = sqlite3_step( stmt );
1246  }
1247  sqlite3_finalize( stmt );
1248 
1249  return values;
1250 }
1251 
1252 QgsOfflineEditing::AttributeValueChanges QgsOfflineEditing::sqlQueryAttributeValueChanges( sqlite3* db, const QString& sql )
1253 {
1254  AttributeValueChanges values;
1255 
1256  sqlite3_stmt* stmt = nullptr;
1257  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1258  {
1259  showWarning( sqlite3_errmsg( db ) );
1260  return values;
1261  }
1262 
1263  int ret = sqlite3_step( stmt );
1264  while ( ret == SQLITE_ROW )
1265  {
1266  AttributeValueChange change;
1267  change.fid = sqlite3_column_int( stmt, 0 );
1268  change.attr = sqlite3_column_int( stmt, 1 );
1269  change.value = QString( reinterpret_cast< const char* >( sqlite3_column_text( stmt, 2 ) ) );
1270  values << change;
1271 
1272  ret = sqlite3_step( stmt );
1273  }
1274  sqlite3_finalize( stmt );
1275 
1276  return values;
1277 }
1278 
1279 QgsOfflineEditing::GeometryChanges QgsOfflineEditing::sqlQueryGeometryChanges( sqlite3* db, const QString& sql )
1280 {
1281  GeometryChanges values;
1282 
1283  sqlite3_stmt* stmt = nullptr;
1284  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1285  {
1286  showWarning( sqlite3_errmsg( db ) );
1287  return values;
1288  }
1289 
1290  int ret = sqlite3_step( stmt );
1291  while ( ret == SQLITE_ROW )
1292  {
1293  GeometryChange change;
1294  change.fid = sqlite3_column_int( stmt, 0 );
1295  change.geom_wkt = QString( reinterpret_cast< const char* >( sqlite3_column_text( stmt, 1 ) ) );
1296  values << change;
1297 
1298  ret = sqlite3_step( stmt );
1299  }
1300  sqlite3_finalize( stmt );
1301 
1302  return values;
1303 }
1304 
1305 void QgsOfflineEditing::committedAttributesAdded( const QString& qgisLayerId, const QList<QgsField>& addedAttributes )
1306 {
1307  sqlite3* db = openLoggingDb();
1308  if ( !db )
1309  return;
1310 
1311  // insert log
1312  int layerId = getOrCreateLayerId( db, qgisLayerId );
1313  int commitNo = getCommitNo( db );
1314 
1315  for ( QList<QgsField>::const_iterator it = addedAttributes.begin(); it != addedAttributes.end(); ++it )
1316  {
1317  QgsField field = *it;
1318  QString sql = QString( "INSERT INTO 'log_added_attrs' VALUES ( %1, %2, '%3', %4, %5, %6, '%7' )" )
1319  .arg( layerId )
1320  .arg( commitNo )
1321  .arg( field.name() )
1322  .arg( field.type() )
1323  .arg( field.length() )
1324  .arg( field.precision() )
1325  .arg( field.comment() );
1326  sqlExec( db, sql );
1327  }
1328 
1329  increaseCommitNo( db );
1330  sqlite3_close( db );
1331 }
1332 
1333 void QgsOfflineEditing::committedFeaturesAdded( const QString& qgisLayerId, const QgsFeatureList& addedFeatures )
1334 {
1335  sqlite3* db = openLoggingDb();
1336  if ( !db )
1337  return;
1338 
1339  // insert log
1340  int layerId = getOrCreateLayerId( db, qgisLayerId );
1341 
1342  // get new feature ids from db
1343  QgsMapLayer* layer = QgsMapLayerRegistry::instance()->mapLayer( qgisLayerId );
1344  QgsDataSourceURI uri = QgsDataSourceURI( layer->source() );
1345 
1346  // only store feature ids
1347  QString sql = QString( "SELECT ROWID FROM '%1' ORDER BY ROWID DESC LIMIT %2" ).arg( uri.table() ).arg( addedFeatures.size() );
1348  QList<int> newFeatureIds = sqlQueryInts( db, sql );
1349  for ( int i = newFeatureIds.size() - 1; i >= 0; i-- )
1350  {
1351  QString sql = QString( "INSERT INTO 'log_added_features' VALUES ( %1, %2 )" )
1352  .arg( layerId )
1353  .arg( newFeatureIds.at( i ) );
1354  sqlExec( db, sql );
1355  }
1356 
1357  sqlite3_close( db );
1358 }
1359 
1360 void QgsOfflineEditing::committedFeaturesRemoved( const QString& qgisLayerId, const QgsFeatureIds& deletedFeatureIds )
1361 {
1362  sqlite3* db = openLoggingDb();
1363  if ( !db )
1364  return;
1365 
1366  // insert log
1367  int layerId = getOrCreateLayerId( db, qgisLayerId );
1368 
1369  for ( QgsFeatureIds::const_iterator it = deletedFeatureIds.begin(); it != deletedFeatureIds.end(); ++it )
1370  {
1371  if ( isAddedFeature( db, layerId, *it ) )
1372  {
1373  // remove from added features log
1374  QString sql = QString( "DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( *it );
1375  sqlExec( db, sql );
1376  }
1377  else
1378  {
1379  QString sql = QString( "INSERT INTO 'log_removed_features' VALUES ( %1, %2)" )
1380  .arg( layerId )
1381  .arg( *it );
1382  sqlExec( db, sql );
1383  }
1384  }
1385 
1386  sqlite3_close( db );
1387 }
1388 
1389 void QgsOfflineEditing::committedAttributeValuesChanges( const QString& qgisLayerId, const QgsChangedAttributesMap& changedAttrsMap )
1390 {
1391  sqlite3* db = openLoggingDb();
1392  if ( !db )
1393  return;
1394 
1395  // insert log
1396  int layerId = getOrCreateLayerId( db, qgisLayerId );
1397  int commitNo = getCommitNo( db );
1398 
1399  for ( QgsChangedAttributesMap::const_iterator cit = changedAttrsMap.begin(); cit != changedAttrsMap.end(); ++cit )
1400  {
1401  QgsFeatureId fid = cit.key();
1402  if ( isAddedFeature( db, layerId, fid ) )
1403  {
1404  // skip added features
1405  continue;
1406  }
1407  QgsAttributeMap attrMap = cit.value();
1408  for ( QgsAttributeMap::const_iterator it = attrMap.begin(); it != attrMap.end(); ++it )
1409  {
1410  QString sql = QString( "INSERT INTO 'log_feature_updates' VALUES ( %1, %2, %3, %4, '%5' )" )
1411  .arg( layerId )
1412  .arg( commitNo )
1413  .arg( fid )
1414  .arg( it.key() ) // attr
1415  .arg( it.value().toString() ); // value
1416  sqlExec( db, sql );
1417  }
1418  }
1419 
1420  increaseCommitNo( db );
1421  sqlite3_close( db );
1422 }
1423 
1424 void QgsOfflineEditing::committedGeometriesChanges( const QString& qgisLayerId, const QgsGeometryMap& changedGeometries )
1425 {
1426  sqlite3* db = openLoggingDb();
1427  if ( !db )
1428  return;
1429 
1430  // insert log
1431  int layerId = getOrCreateLayerId( db, qgisLayerId );
1432  int commitNo = getCommitNo( db );
1433 
1434  for ( QgsGeometryMap::const_iterator it = changedGeometries.begin(); it != changedGeometries.end(); ++it )
1435  {
1436  QgsFeatureId fid = it.key();
1437  if ( isAddedFeature( db, layerId, fid ) )
1438  {
1439  // skip added features
1440  continue;
1441  }
1442  QgsGeometry geom = it.value();
1443  QString sql = QString( "INSERT INTO 'log_geometry_updates' VALUES ( %1, %2, %3, '%4' )" )
1444  .arg( layerId )
1445  .arg( commitNo )
1446  .arg( fid )
1447  .arg( geom.exportToWkt() );
1448  sqlExec( db, sql );
1449 
1450  // TODO: use WKB instead of WKT?
1451  }
1452 
1453  increaseCommitNo( db );
1454  sqlite3_close( db );
1455 }
1456 
1457 void QgsOfflineEditing::startListenFeatureChanges()
1458 {
1459  QgsVectorLayer* vLayer = qobject_cast<QgsVectorLayer *>( sender() );
1460  // enable logging, check if editBuffer is not null
1461  if ( vLayer->editBuffer() )
1462  {
1463  connect( vLayer->editBuffer(), SIGNAL( committedAttributesAdded( const QString&, const QList<QgsField>& ) ),
1464  this, SLOT( committedAttributesAdded( const QString&, const QList<QgsField>& ) ) );
1465  connect( vLayer->editBuffer(), SIGNAL( committedAttributeValuesChanges( const QString&, const QgsChangedAttributesMap& ) ),
1466  this, SLOT( committedAttributeValuesChanges( const QString&, const QgsChangedAttributesMap& ) ) );
1467  connect( vLayer->editBuffer(), SIGNAL( committedGeometriesChanges( const QString&, const QgsGeometryMap& ) ),
1468  this, SLOT( committedGeometriesChanges( const QString&, const QgsGeometryMap& ) ) );
1469  }
1470  connect( vLayer, SIGNAL( committedFeaturesAdded( const QString&, const QgsFeatureList& ) ),
1471  this, SLOT( committedFeaturesAdded( const QString&, const QgsFeatureList& ) ) );
1472  connect( vLayer, SIGNAL( committedFeaturesRemoved( const QString&, const QgsFeatureIds& ) ),
1473  this, SLOT( committedFeaturesRemoved( const QString&, const QgsFeatureIds& ) ) );
1474 }
1475 
1476 void QgsOfflineEditing::stopListenFeatureChanges()
1477 {
1478  QgsVectorLayer* vLayer = qobject_cast<QgsVectorLayer *>( sender() );
1479  // disable logging, check if editBuffer is not null
1480  if ( vLayer->editBuffer() )
1481  {
1482  disconnect( vLayer->editBuffer(), SIGNAL( committedAttributesAdded( const QString&, const QList<QgsField>& ) ),
1483  this, SLOT( committedAttributesAdded( const QString&, const QList<QgsField>& ) ) );
1484  disconnect( vLayer->editBuffer(), SIGNAL( committedAttributeValuesChanges( const QString&, const QgsChangedAttributesMap& ) ),
1485  this, SLOT( committedAttributeValuesChanges( const QString&, const QgsChangedAttributesMap& ) ) );
1486  disconnect( vLayer->editBuffer(), SIGNAL( committedGeometriesChanges( const QString&, const QgsGeometryMap& ) ),
1487  this, SLOT( committedGeometriesChanges( const QString&, const QgsGeometryMap& ) ) );
1488  }
1489  disconnect( vLayer, SIGNAL( committedFeaturesAdded( const QString&, const QgsFeatureList& ) ),
1490  this, SLOT( committedFeaturesAdded( const QString&, const QgsFeatureList& ) ) );
1491  disconnect( vLayer, SIGNAL( committedFeaturesRemoved( const QString&, const QgsFeatureIds& ) ),
1492  this, SLOT( committedFeaturesRemoved( const QString&, const QgsFeatureIds& ) ) );
1493 }
1494 
1495 void QgsOfflineEditing::layerAdded( QgsMapLayer* layer )
1496 {
1497  // detect offline layer
1499  {
1500  QgsVectorLayer* vLayer = qobject_cast<QgsVectorLayer *>( layer );
1501  connect( vLayer, SIGNAL( editingStarted() ), this, SLOT( startListenFeatureChanges() ) );
1502  connect( vLayer, SIGNAL( editingStopped() ), this, SLOT( stopListenFeatureChanges() ) );
1503  }
1504 }
1505 
1506 
virtual QString subsetString()
Get the string (typically sql) used to define a subset of the layer.
static WkbType flatType(WkbType type)
Map 2d+ to 2d type.
Definition: qgis.cpp:401
Layer tree group node serves as a container for layers and further groups.
Wrapper for iterator of features from vector data provider or vector layer.
void layerProgressUpdated(int layer, int numLayers)
Emit a signal that the next layer of numLayers has started processing.
static unsigned index
bool addJoin(const QgsVectorJoinInfo &joinInfo)
Joins another vector layer to this layer.
Base class for all map layer types.
Definition: qgsmaplayer.h:49
QGis::WkbType wkbType() const
Returns the WKBType or WKBUnknown in case of error.
virtual bool dropMValue()=0
Drops any measure values which exist in the geometry.
Filter using feature IDs.
QgsAttributes attributes() const
Returns the feature&#39;s attributes.
Definition: qgsfeature.cpp:110
QString readEntry(const QString &scope, const QString &key, const QString &def=QString::null, bool *ok=nullptr) const
FieldOrigin fieldOrigin(int fieldIdx) const
Get field&#39;s origin (value from an enumeration)
Definition: qgsfield.cpp:448
bool contains(const Key &key) const
QString name
Definition: qgsfield.h:52
int precision
Definition: qgsfield.h:50
#define PROJECT_ENTRY_KEY_OFFLINE_DB_PATH
bool remove()
bool deleteFeature(QgsFeatureId fid)
Delete a feature from the layer (but does not commit it)
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
int size() const
QObject * sender() const
QgsMapLayer * mapLayer(const QString &theLayerId) const
Retrieve a pointer to a registered layer by layer ID.
QStringList split(const QString &sep, SplitBehavior behavior, Qt::CaseSensitivity cs) const
#define CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest())
Query the provider for features specified in request.
bool commitChanges()
Attempts to commit any changes to disk.
void setReferencingLayer(const QString &id)
Set the referencing (child) layer id.
bool startEditing()
Make layer editable.
const_iterator constBegin() const
const T & at(int i) const
void setCustomProperty(const QString &key, const QVariant &value)
Set a custom property for layer.
const QgsCoordinateReferenceSystem & crs() const
Returns layer&#39;s spatial reference system.
QString fileName() const
void setGeometry(QgsAbstractGeometryV2 *geometry)
Sets the underlying geometry store.
QString comment
Definition: qgsfield.h:51
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:515
#define CUSTOM_PROPERTY_REMOTE_SOURCE
FilterType filterType() const
Return the filter type which is currently set on this request.
Abstract base class for all geometries.
Container of fields for a vector layer.
Definition: qgsfield.h:252
QMap< QString, QgsMapLayer * > mapLayers() const
Returns a map of all registered layers by layer ID.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:76
void setAttributes(const QgsAttributes &attrs)
Sets the feature&#39;s attributes.
Definition: qgsfeature.cpp:115
void removeChildNode(QgsLayerTreeNode *node)
Remove a child node from this group. The node will be deleted.
void removeMapLayers(const QStringList &theLayerIds)
Remove a set of layers from the registry by layer ID.
virtual bool dropZValue()=0
Drops any z-dimensions which exist in the geometry.
QgsLayerTreeGroup * layerTreeRoot() const
Return pointer to the root (invisible) node of the project&#39;s layer tree.
field comes from the underlying data provider of the vector layer (originIndex = index in provider&#39;s ...
Definition: qgsfield.h:259
QString join(const QString &separator) const
bool exists() const
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:187
QString & remove(int position, int n)
bool addFeature(QgsFeature &feature, bool alsoUpdateExtent=true)
Adds a feature.
QgsRelationManager * relationManager() const
const QList< QgsVectorJoinInfo > vectorJoins() const
bool isOfflineProject() const
Return true if current project is offline.
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
int count() const
Return number of items.
Definition: qgsfield.cpp:402
QString tr(const char *sourceText, const char *disambiguation, int n)
static int sqlite3_close(sqlite3 *)
virtual QString name() const =0
Return a provider name.
bool convertToOfflineProject(const QString &offlineDataPath, const QString &offlineDbFile, const QStringList &layerIds, bool onlySelected=false)
Convert current project for offline editing.
int size() const
bool isNull() const
QgsFields fields() const
Returns the list of fields of this layer.
int length
Definition: qgsfield.h:49
int indexOf(const T &value, int from) const
int fieldOriginIndex(int fieldIdx) const
Get field&#39;s origin index (its meaning is specific to each type of origin)
Definition: qgsfield.cpp:456
bool hasGeometryType() const
Returns true if this is a geometry layer and false in case of NoGeometry (table only) or UnknownGeome...
long featureCount(QgsSymbolV2 *symbol)
Number of features rendered with specified symbol.
static int sqlite3_open(const char *filename, sqlite3 **ppDb)
void update(const QString &name, const PresetRecord &state)
Updates a preset within the collection.
bool writeEntry(const QString &scope, const QString &key, bool value)
void progressUpdated(int progress)
Emit a signal with the progress of the current mode.
QgsVectorLayerEditBuffer * editBuffer()
Buffer with uncommitted editing operations. Only valid after editing has been turned on...
int count(const T &value) const
void append(const T &value)
QList< QgsRelation > referencingRelations(QgsVectorLayer *layer=nullptr, int fieldIdx=-2) const
Get all relations where the specified layer (and field) is the referencing part (i.e.
QString fromUtf8(const char *str, int size)
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context...
QString id() const
Get this layer&#39;s unique ID, this ID is used to access this layer from map layer registry.
void progressStopped()
Emit a signal that processing of all layers has finished.
bool rollBack(bool deleteBuffer=true)
Stop editing and discard the edits.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:34
QList< QgsMapLayer * > addMapLayers(const QList< QgsMapLayer *> &theMapLayers, bool addToLegend=true, bool takeOwnership=true)
Add a list of layers to the map of loaded layers.
QMap< QString, QString > mPerLayerCurrentStyle
For layers that use multiple styles - which one is currently selected.
QString fileName() const
Q_DECL_DEPRECATED void title(const QString &title)
Every project has an associated title string.
Definition: qgsproject.h:91
void setTypeName(const QString &typeName)
Set the field type.
Definition: qgsfield.cpp:143
static int sqlite3_open_v2(const char *filename, sqlite3 **ppDb, int flags, const char *zVfs)
bool isEmpty() const
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
QgsLayerTreeNode * parent()
Get pointer to the parent. If parent is a null pointer, the node is a root node.
bool isEmpty() const
const_iterator constEnd() const
const char * constData() const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const
virtual long featureCount() const =0
Number of features in the layer.
This class wraps a request for features to a vector layer (or directly its vector data provider)...
QList< int > QgsAttributeList
QStringList mVisibleLayerIDs
Ordered list of layers that are visible.
This class is a base class for nodes in a layer tree.
virtual QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest())=0
Query the provider for features specified in request.
bool removeEntry(const QString &scope, const QString &key)
Remove the given key.
QgsAttributeList attributeList() const
Returns list of attribute indexes.
bool changeGeometry(QgsFeatureId fid, QgsGeometry *geom)
Change feature&#39;s geometry.
QString exportToWkt(int precision=17) const
Exports the geometry to WKT.
QDir dir() const
QList< QgsRelation > referencedRelations(QgsVectorLayer *layer=nullptr) const
Get all relations where this layer is the referenced part (i.e.
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:44
virtual bool importNamedStyle(QDomDocument &doc, QString &errorMsg)
Import the properties of this layer from a QDomDocument.
iterator end()
const QList< NativeType > & nativeTypes() const
Returns the names of the supported types.
QList< QgsLayerTreeNode * > children()
Get list of children of the node. Children are owned by the parent.
bool isValid()
Return the status of the layer.
#define PROJECT_ENTRY_SCOPE_OFFLINE
iterator begin()
Q_DECL_DEPRECATED bool changeAttributeValue(QgsFeatureId fid, int field, const QVariant &value, bool emitSignal)
Changes an attribute value (but does not commit it)
virtual QVariant defaultValue(int fieldId, bool forceLazyEval=false)
Returns the default value for field specified by fieldId.
const QStringList & commitErrors()
void setReferencedLayer(const QString &id)
Set the referenced (parent) layer id.
iterator end()
QgsFeatureId id() const
Get the feature ID for this feature.
Definition: qgsfeature.cpp:65
Class for storing the component parts of a PostgreSQL/RDBMS datasource URI.
struct sqlite3 sqlite3
iterator begin()
bool contains(QChar ch, Qt::CaseSensitivity cs) const
virtual void reload() override
Synchronises with changes in the datasource.
QgsGeometry * geometry()
Get the geometry object associated with this feature.
Definition: qgsfeature.cpp:76
QgsLayerTreeLayer * findLayer(const QString &layerId) const
Find layer node representing the map layer specified by its ID. Searches recursively the whole sub-tr...
QString defaultValueExpression(int index) const
Returns the expression used when calculating the default value for a field.
const char * typeToName(Type typ)
const QgsFeatureIds & selectedFeaturesIds() const
Return reference to identifiers of selected features.
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:516
const Key key(const T &value) const
long toLong(bool *ok, int base) const
PresetRecord presetState(const QString &name) const
Returns the recorded state of a preset.
virtual void exportNamedStyle(QDomDocument &doc, QString &errorMsg)
Export the properties of this layer as named style in a QDomDocument.
const T & at(int i) const
void warning(const QString &title, const QString &message)
Emitted when a warning needs to be displayed.
virtual const QgsFields & fields() const =0
Return a map of indexes with field names for this layer.
static QgsMapLayerRegistry * instance()
Returns the instance pointer, creating the object on the first call.
QgsAbstractGeometryV2 * geometry() const
Returns the underlying geometry store.
QString mid(int position, int n) const
QgsFeatureRequest & setFilterFids(const QgsFeatureIds &fids)
Set feature IDs that should be fetched.
void insertChildNode(int index, QgsLayerTreeNode *node)
Insert existing node at specified position. The node must not have a parent yet. The node will be own...
Individual preset record of visible layers and styles.
QString source() const
Returns the source for the layer.
QString absolutePath() const
void synchronize()
Synchronize to remote layers.
This class manages a set of relations between layers.
iterator end()
QString readPath(QString filename, const QString &relativeBasePath=QString()) const
Turn filename read from the project file to an absolute path.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:382
QString table() const
Returns the table.
int count(const T &value) const
void setTitle(const QString &title)
Sets the project&#39;s title.
Definition: qgsproject.cpp:391
QString absoluteFilePath(const QString &fileName) const
QStringList split(const QString &sep, const QString &str, bool allowEmptyEntries)
virtual bool setSubsetString(const QString &subset)
Set the string (typically sql) used to define a subset of the layer.
bool toBool() const
bool isEmpty() const
void progressModeSet(QgsOfflineEditing::ProgressMode mode, int maximum)
Emit a signal that sets the mode for the progress of the current operation.
qint64 QgsFeatureId
Definition: qgsfeature.h:31
Container class that allows storage of visibility presets consisting of visible map layers and layer ...
QString name
Read property of QString layerName.
Definition: qgsmaplayer.h:53
iterator insert(const Key &key, const T &value)
void removeRelation(const QString &id)
Remove a relation.
QVariant customProperty(const QString &value, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer.
static QgsExpressionContextScope * projectScope()
Creates a new scope which contains variables and functions relating to the current QGIS project...
static QgsGeometry * fromWkt(const QString &wkt)
Creates a new geometry from a WKT string.
QgsVectorDataProvider * dataProvider()
Returns the data provider.
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
QString providerType() const
Return the provider type for this layer.
bool nextFeature(QgsFeature &f)
This is the base class for vector data providers.
virtual QgsAbstractGeometryV2 * clone() const =0
Clones the geometry by performing a deep copy.
QMap< QString, QSet< QString > > mPerLayerCheckedLegendSymbols
For layers that have checkable legend symbols and not all symbols are checked - list which ones are...
Q_DECL_DEPRECATED bool hasLabelsEnabled() const
Label is on.
Geometry is not required. It may still be returned if e.g. required for a filter condition.
virtual void invalidateConnections(const QString &connection)
Invalidate connections corresponding to specified name.
A vector of attributes.
Definition: qgsfeature.h:115
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QStringList presets() const
Returns a list of existing preset names.
QgsVisibilityPresetCollection * visibilityPresetCollection()
Returns pointer to the project&#39;s visibility preset collection.
Represents a vector layer which manages a vector based data sets.
QVariant::Type type() const
Gets variant type of the field as it will be retrieved from data source.
Definition: qgsfield.cpp:97
bool addAttribute(const QgsField &field)
Add an attribute field (but does not commit it) returns true if the field was added.
QString arg(qlonglong a, int fieldWidth, int base, const QChar &fillChar) const
QString toString() const
bool removeOne(const T &value)
#define CUSTOM_PROPERTY_REMOTE_PROVIDER
QString joinLayerId
Source layer.
void progressStarted()
Emit a signal that processing has started.
iterator begin()
virtual QgsLayerTreeLayer * clone() const override
Create a copy of the node. Returns new instance.
QString authid() const
Returns the authority identifier for the CRS, which includes both the authority (eg EPSG) and the CRS...
bool mkpath(const QString &dirPath) const
Layer tree node points to a map layer.
const T value(const Key &key) const
QVariant defaultValue(int index, const QgsFeature &feature=QgsFeature(), QgsExpressionContext *context=nullptr) const
Returns the calculated default value for the specified field index.
int remove(const Key &key)
void addRelation(const QgsRelation &relation)
Add a relation.
QByteArray toUtf8() const