QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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 "qgsofflineediting.h"
27 #include "qgsproject.h"
28 #include "qgsvectordataprovider.h"
31 #include "qgsspatialiteutils.h"
32 #include "qgsfeatureiterator.h"
33 #include "qgslogger.h"
34 #include "qgsvectorlayerutils.h"
35 #include "qgsrelationmanager.h"
36 #include "qgsmapthemecollection.h"
37 #include "qgslayertree.h"
38 #include "qgsogrutils.h"
39 #include "qgsvectorfilewriter.h"
40 #include "qgsvectorlayer.h"
41 
42 #include <QDir>
43 #include <QDomDocument>
44 #include <QDomNode>
45 #include <QFile>
46 #include <QMessageBox>
47 
48 #include <ogr_srs_api.h>
49 
50 extern "C"
51 {
52 #include <sqlite3.h>
53 #include <spatialite.h>
54 }
55 
56 #define CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE "isOfflineEditable"
57 #define CUSTOM_PROPERTY_REMOTE_SOURCE "remoteSource"
58 #define CUSTOM_PROPERTY_REMOTE_PROVIDER "remoteProvider"
59 #define CUSTOM_SHOW_FEATURE_COUNT "showFeatureCount"
60 #define PROJECT_ENTRY_SCOPE_OFFLINE "OfflineEditingPlugin"
61 #define PROJECT_ENTRY_KEY_OFFLINE_DB_PATH "/OfflineDbPath"
62 
64 {
65  connect( QgsProject::instance(), &QgsProject::layerWasAdded, this, &QgsOfflineEditing::layerAdded );
66 }
67 
83 bool QgsOfflineEditing::convertToOfflineProject( const QString &offlineDataPath, const QString &offlineDbFile, const QStringList &layerIds, bool onlySelected, ContainerType containerType )
84 {
85  if ( layerIds.isEmpty() )
86  {
87  return false;
88  }
89 
90  QString dbPath = QDir( offlineDataPath ).absoluteFilePath( offlineDbFile );
91  if ( createOfflineDb( dbPath, containerType ) )
92  {
94  int rc = database.open( dbPath );
95  if ( rc != SQLITE_OK )
96  {
97  showWarning( tr( "Could not open the SpatiaLite database" ) );
98  }
99  else
100  {
101  // create logging tables
102  createLoggingTables( database.get() );
103 
104  emit progressStarted();
105 
106  QMap<QString, QgsVectorJoinList > joinInfoBuffer;
107  QMap<QString, QgsVectorLayer *> layerIdMapping;
108 
109  Q_FOREACH ( const QString &layerId, layerIds )
110  {
111  QgsMapLayer *layer = QgsProject::instance()->mapLayer( layerId );
112  QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
113  if ( !vl )
114  continue;
115  QgsVectorJoinList joins = vl->vectorJoins();
116 
117  // Layer names will be appended an _offline suffix
118  // Join fields are prefixed with the layer name and we do not want the
119  // field name to change so we stabilize the field name by defining a
120  // custom prefix with the layername without _offline suffix.
121  QgsVectorJoinList::iterator joinIt = joins.begin();
122  while ( joinIt != joins.end() )
123  {
124  if ( joinIt->prefix().isNull() )
125  {
126  QgsVectorLayer *vl = joinIt->joinLayer();
127 
128  if ( vl )
129  joinIt->setPrefix( vl->name() + '_' );
130  }
131  ++joinIt;
132  }
133  joinInfoBuffer.insert( vl->id(), joins );
134  }
135 
137 
138  // copy selected vector layers to SpatiaLite
139  for ( int i = 0; i < layerIds.count(); i++ )
140  {
141  emit layerProgressUpdated( i + 1, layerIds.count() );
142 
143  QgsMapLayer *layer = QgsProject::instance()->mapLayer( layerIds.at( i ) );
144  QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
145  if ( vl )
146  {
147  QString origLayerId = vl->id();
148  QgsVectorLayer *newLayer = copyVectorLayer( vl, database.get(), dbPath, onlySelected, containerType );
149  if ( newLayer )
150  {
151  layerIdMapping.insert( origLayerId, newLayer );
152  //append individual layer setting on snapping settings
153  snappingConfig.setIndividualLayerSettings( newLayer, snappingConfig.individualLayerSettings( vl ) );
154  snappingConfig.removeLayers( QList<QgsMapLayer *>() << vl );
155 
156  // remove remote layer
158  QStringList() << origLayerId );
159  }
160  }
161  }
162 
163  QgsProject::instance()->setSnappingConfig( snappingConfig );
164 
165  // restore join info on new SpatiaLite layer
166  QMap<QString, QgsVectorJoinList >::ConstIterator it;
167  for ( it = joinInfoBuffer.constBegin(); it != joinInfoBuffer.constEnd(); ++it )
168  {
169  QgsVectorLayer *newLayer = layerIdMapping.value( it.key() );
170 
171  if ( newLayer )
172  {
173  Q_FOREACH ( QgsVectorLayerJoinInfo join, it.value() )
174  {
175  QgsVectorLayer *newJoinedLayer = layerIdMapping.value( join.joinLayerId() );
176  if ( newJoinedLayer )
177  {
178  // If the layer has been offline'd, update join information
179  join.setJoinLayer( newJoinedLayer );
180  }
181  newLayer->addJoin( join );
182  }
183  }
184  }
185 
186  emit progressStopped();
187 
188  // save offline project
189  QString projectTitle = QgsProject::instance()->title();
190  if ( projectTitle.isEmpty() )
191  {
192  projectTitle = QFileInfo( QgsProject::instance()->fileName() ).fileName();
193  }
194  projectTitle += QLatin1String( " (offline)" );
195  QgsProject::instance()->setTitle( projectTitle );
196 
198 
199  return true;
200  }
201  }
202 
203  return false;
204 }
205 
207 {
209 }
210 
212 {
213  // open logging db
214  sqlite3_database_unique_ptr database = openLoggingDb();
215  if ( !database )
216  {
217  return;
218  }
219 
220  emit progressStarted();
221 
223 
224  // restore and sync remote layers
225  QList<QgsMapLayer *> offlineLayers;
226  QMap<QString, QgsMapLayer *> mapLayers = QgsProject::instance()->mapLayers();
227  for ( QMap<QString, QgsMapLayer *>::iterator layer_it = mapLayers.begin() ; layer_it != mapLayers.end(); ++layer_it )
228  {
229  QgsMapLayer *layer = layer_it.value();
230  if ( layer->customProperty( CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE, false ).toBool() )
231  {
232  offlineLayers << layer;
233  }
234  }
235 
236  QgsDebugMsgLevel( QStringLiteral( "Found %1 offline layers" ).arg( offlineLayers.count() ), 4 );
237  for ( int l = 0; l < offlineLayers.count(); l++ )
238  {
239  QgsMapLayer *layer = offlineLayers.at( l );
240 
241  emit layerProgressUpdated( l + 1, offlineLayers.count() );
242 
243  QString remoteSource = layer->customProperty( CUSTOM_PROPERTY_REMOTE_SOURCE, "" ).toString();
244  QString remoteProvider = layer->customProperty( CUSTOM_PROPERTY_REMOTE_PROVIDER, "" ).toString();
245  QString remoteName = layer->name();
246  remoteName.remove( QRegExp( " \\(offline\\)$" ) );
247 
248  QgsVectorLayer *remoteLayer = new QgsVectorLayer( remoteSource, remoteName, remoteProvider );
249  if ( remoteLayer->isValid() )
250  {
251  // Rebuild WFS cache to get feature id<->GML fid mapping
252  if ( remoteLayer->dataProvider()->name().contains( QLatin1String( "WFS" ), Qt::CaseInsensitive ) )
253  {
254  QgsFeatureIterator fit = remoteLayer->getFeatures();
255  QgsFeature f;
256  while ( fit.nextFeature( f ) )
257  {
258  }
259  }
260  // TODO: only add remote layer if there are log entries?
261 
262  QgsVectorLayer *offlineLayer = qobject_cast<QgsVectorLayer *>( layer );
263 
264  // register this layer with the central layers registry
265  QgsProject::instance()->addMapLayers( QList<QgsMapLayer *>() << remoteLayer, true );
266 
267  // copy style
268  copySymbology( offlineLayer, remoteLayer );
269  updateRelations( offlineLayer, remoteLayer );
270  updateMapThemes( offlineLayer, remoteLayer );
271  updateLayerOrder( offlineLayer, remoteLayer );
272 
273  //append individual layer setting on snapping settings
274  snappingConfig.setIndividualLayerSettings( remoteLayer, snappingConfig.individualLayerSettings( offlineLayer ) );
275  snappingConfig.removeLayers( QList<QgsMapLayer *>() << offlineLayer );
276 
277  //set QgsLayerTreeNode properties back
278  QgsLayerTreeLayer *layerTreeLayer = QgsProject::instance()->layerTreeRoot()->findLayer( offlineLayer->id() );
279  QgsLayerTreeLayer *newLayerTreeLayer = QgsProject::instance()->layerTreeRoot()->findLayer( remoteLayer->id() );
281 
282  // apply layer edit log
283  QString qgisLayerId = layer->id();
284  QString sql = QStringLiteral( "SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
285  int layerId = sqlQueryInt( database.get(), sql, -1 );
286  if ( layerId != -1 )
287  {
288  remoteLayer->startEditing();
289 
290  // TODO: only get commitNos of this layer?
291  int commitNo = getCommitNo( database.get() );
292  QgsDebugMsgLevel( QStringLiteral( "Found %1 commits" ).arg( commitNo ), 4 );
293  for ( int i = 0; i < commitNo; i++ )
294  {
295  QgsDebugMsgLevel( QStringLiteral( "Apply commits chronologically" ), 4 );
296  // apply commits chronologically
297  applyAttributesAdded( remoteLayer, database.get(), layerId, i );
298  applyAttributeValueChanges( offlineLayer, remoteLayer, database.get(), layerId, i );
299  applyGeometryChanges( remoteLayer, database.get(), layerId, i );
300  }
301 
302  applyFeaturesAdded( offlineLayer, remoteLayer, database.get(), layerId );
303  applyFeaturesRemoved( remoteLayer, database.get(), layerId );
304 
305  if ( remoteLayer->commitChanges() )
306  {
307  // update fid lookup
308  updateFidLookup( remoteLayer, database.get(), layerId );
309 
310  // clear edit log for this layer
311  sql = QStringLiteral( "DELETE FROM 'log_added_attrs' WHERE \"layer_id\" = %1" ).arg( layerId );
312  sqlExec( database.get(), sql );
313  sql = QStringLiteral( "DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
314  sqlExec( database.get(), sql );
315  sql = QStringLiteral( "DELETE FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
316  sqlExec( database.get(), sql );
317  sql = QStringLiteral( "DELETE FROM 'log_feature_updates' WHERE \"layer_id\" = %1" ).arg( layerId );
318  sqlExec( database.get(), sql );
319  sql = QStringLiteral( "DELETE FROM 'log_geometry_updates' WHERE \"layer_id\" = %1" ).arg( layerId );
320  sqlExec( database.get(), sql );
321  }
322  else
323  {
324  showWarning( remoteLayer->commitErrors().join( QStringLiteral( "\n" ) ) );
325  }
326  }
327  else
328  {
329  QgsDebugMsg( QStringLiteral( "Could not find the layer id in the edit logs!" ) );
330  }
331  // Invalidate the connection to force a reload if the project is put offline
332  // again with the same path
333  offlineLayer->dataProvider()->invalidateConnections( QgsDataSourceUri( offlineLayer->source() ).database() );
334  // remove offline layer
335  QgsProject::instance()->removeMapLayers( QStringList() << qgisLayerId );
336 
337 
338  // disable offline project
339  QString projectTitle = QgsProject::instance()->title();
340  projectTitle.remove( QRegExp( " \\(offline\\)$" ) );
341  QgsProject::instance()->setTitle( projectTitle );
343  remoteLayer->reload(); //update with other changes
344  }
345  else
346  {
347  QgsDebugMsg( QStringLiteral( "Remote layer is not valid!" ) );
348  }
349  }
350 
351  // reset commitNo
352  QString sql = QStringLiteral( "UPDATE 'log_indices' SET 'last_index' = 0 WHERE \"name\" = 'commit_no'" );
353  sqlExec( database.get(), sql );
354 
355  QgsProject::instance()->setSnappingConfig( snappingConfig );
356 
357  emit progressStopped();
358 }
359 
360 void QgsOfflineEditing::initializeSpatialMetadata( sqlite3 *sqlite_handle )
361 {
362  // attempting to perform self-initialization for a newly created DB
363  if ( !sqlite_handle )
364  return;
365  // checking if this DB is really empty
366  char **results = nullptr;
367  int rows, columns;
368  int ret = sqlite3_get_table( sqlite_handle, "select count(*) from sqlite_master", &results, &rows, &columns, nullptr );
369  if ( ret != SQLITE_OK )
370  return;
371  int count = 0;
372  if ( rows >= 1 )
373  {
374  for ( int i = 1; i <= rows; i++ )
375  count = atoi( results[( i * columns ) + 0] );
376  }
377 
378  sqlite3_free_table( results );
379 
380  if ( count > 0 )
381  return;
382 
383  bool above41 = false;
384  ret = sqlite3_get_table( sqlite_handle, "select spatialite_version()", &results, &rows, &columns, nullptr );
385  if ( ret == SQLITE_OK && rows == 1 && columns == 1 )
386  {
387  QString version = QString::fromUtf8( results[1] );
388  QStringList parts = version.split( ' ', QString::SkipEmptyParts );
389  if ( !parts.empty() )
390  {
391  QStringList verparts = parts.at( 0 ).split( '.', QString::SkipEmptyParts );
392  above41 = verparts.size() >= 2 && ( verparts.at( 0 ).toInt() > 4 || ( verparts.at( 0 ).toInt() == 4 && verparts.at( 1 ).toInt() >= 1 ) );
393  }
394  }
395 
396  sqlite3_free_table( results );
397 
398  // all right, it's empty: proceeding to initialize
399  char *errMsg = nullptr;
400  ret = sqlite3_exec( sqlite_handle, above41 ? "SELECT InitSpatialMetadata(1)" : "SELECT InitSpatialMetadata()", nullptr, nullptr, &errMsg );
401 
402  if ( ret != SQLITE_OK )
403  {
404  QString errCause = tr( "Unable to initialize SpatialMetadata:\n" );
405  errCause += QString::fromUtf8( errMsg );
406  showWarning( errCause );
407  sqlite3_free( errMsg );
408  return;
409  }
410  spatial_ref_sys_init( sqlite_handle, 0 );
411 }
412 
413 bool QgsOfflineEditing::createOfflineDb( const QString &offlineDbPath, ContainerType containerType )
414 {
415  int ret;
416  char *errMsg = nullptr;
417  QFile newDb( offlineDbPath );
418  if ( newDb.exists() )
419  {
420  QFile::remove( offlineDbPath );
421  }
422 
423  // see also QgsNewSpatialiteLayerDialog::createDb()
424 
425  QFileInfo fullPath = QFileInfo( offlineDbPath );
426  QDir path = fullPath.dir();
427 
428  // Must be sure there is destination directory ~/.qgis
429  QDir().mkpath( path.absolutePath() );
430 
431  // creating/opening the new database
432  QString dbPath = newDb.fileName();
433 
434  // creating geopackage
435  switch ( containerType )
436  {
437  case GPKG:
438  {
439  OGRSFDriverH hGpkgDriver = OGRGetDriverByName( "GPKG" );
440  if ( !hGpkgDriver )
441  {
442  showWarning( tr( "Creation of database failed. GeoPackage driver not found." ) );
443  return false;
444  }
445 
446  gdal::ogr_datasource_unique_ptr hDS( OGR_Dr_CreateDataSource( hGpkgDriver, dbPath.toUtf8().constData(), nullptr ) );
447  if ( !hDS )
448  {
449  showWarning( tr( "Creation of database failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
450  return false;
451  }
452  break;
453  }
454  case SpatiaLite:
455  {
456  break;
457  }
458  }
459 
461  ret = database.open_v2( dbPath, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr );
462  if ( ret )
463  {
464  // an error occurred
465  QString errCause = tr( "Could not create a new database\n" );
466  errCause += database.errorMessage();
467  showWarning( errCause );
468  return false;
469  }
470  // activating Foreign Key constraints
471  ret = sqlite3_exec( database.get(), "PRAGMA foreign_keys = 1", nullptr, nullptr, &errMsg );
472  if ( ret != SQLITE_OK )
473  {
474  showWarning( tr( "Unable to activate FOREIGN_KEY constraints" ) );
475  sqlite3_free( errMsg );
476  return false;
477  }
478  initializeSpatialMetadata( database.get() );
479  return true;
480 }
481 
482 void QgsOfflineEditing::createLoggingTables( sqlite3 *db )
483 {
484  // indices
485  QString sql = QStringLiteral( "CREATE TABLE 'log_indices' ('name' TEXT, 'last_index' INTEGER)" );
486  sqlExec( db, sql );
487 
488  sql = QStringLiteral( "INSERT INTO 'log_indices' VALUES ('commit_no', 0)" );
489  sqlExec( db, sql );
490 
491  sql = QStringLiteral( "INSERT INTO 'log_indices' VALUES ('layer_id', 0)" );
492  sqlExec( db, sql );
493 
494  // layername <-> layer id
495  sql = QStringLiteral( "CREATE TABLE 'log_layer_ids' ('id' INTEGER, 'qgis_id' TEXT)" );
496  sqlExec( db, sql );
497 
498  // offline fid <-> remote fid
499  sql = QStringLiteral( "CREATE TABLE 'log_fids' ('layer_id' INTEGER, 'offline_fid' INTEGER, 'remote_fid' INTEGER)" );
500  sqlExec( db, sql );
501 
502  // added attributes
503  sql = QStringLiteral( "CREATE TABLE 'log_added_attrs' ('layer_id' INTEGER, 'commit_no' INTEGER, " );
504  sql += QLatin1String( "'name' TEXT, 'type' INTEGER, 'length' INTEGER, 'precision' INTEGER, 'comment' TEXT)" );
505  sqlExec( db, sql );
506 
507  // added features
508  sql = QStringLiteral( "CREATE TABLE 'log_added_features' ('layer_id' INTEGER, 'fid' INTEGER)" );
509  sqlExec( db, sql );
510 
511  // removed features
512  sql = QStringLiteral( "CREATE TABLE 'log_removed_features' ('layer_id' INTEGER, 'fid' INTEGER)" );
513  sqlExec( db, sql );
514 
515  // feature updates
516  sql = QStringLiteral( "CREATE TABLE 'log_feature_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'attr' INTEGER, 'value' TEXT)" );
517  sqlExec( db, sql );
518 
519  // geometry updates
520  sql = QStringLiteral( "CREATE TABLE 'log_geometry_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'geom_wkt' TEXT)" );
521  sqlExec( db, sql );
522 
523  /* TODO: other logging tables
524  - attr delete (not supported by SpatiaLite provider)
525  */
526 }
527 
528 QgsVectorLayer *QgsOfflineEditing::copyVectorLayer( QgsVectorLayer *layer, sqlite3 *db, const QString &offlineDbPath, bool onlySelected, ContainerType containerType )
529 {
530  if ( !layer )
531  return nullptr;
532 
533  QString tableName = layer->id();
534  QgsDebugMsgLevel( QStringLiteral( "Creating offline table %1 ..." ).arg( tableName ), 4 );
535 
536  // new layer
537  QgsVectorLayer *newLayer = nullptr;
538 
539  switch ( containerType )
540  {
541  case SpatiaLite:
542  {
543  // create table
544  QString sql = QStringLiteral( "CREATE TABLE '%1' (" ).arg( tableName );
545  QString delim;
546  const QgsFields providerFields = layer->dataProvider()->fields();
547  for ( const auto &field : providerFields )
548  {
549  QString dataType;
550  QVariant::Type type = field.type();
551  if ( type == QVariant::Int || type == QVariant::LongLong )
552  {
553  dataType = QStringLiteral( "INTEGER" );
554  }
555  else if ( type == QVariant::Double )
556  {
557  dataType = QStringLiteral( "REAL" );
558  }
559  else if ( type == QVariant::String )
560  {
561  dataType = QStringLiteral( "TEXT" );
562  }
563  else
564  {
565  showWarning( tr( "%1: Unknown data type %2. Not using type affinity for the field." ).arg( field.name(), QVariant::typeToName( type ) ) );
566  }
567 
568  sql += delim + QStringLiteral( "'%1' %2" ).arg( field.name(), dataType );
569  delim = ',';
570  }
571  sql += ')';
572 
573  int rc = sqlExec( db, sql );
574 
575  // add geometry column
576  if ( layer->isSpatial() )
577  {
578  const QgsWkbTypes::Type sourceWkbType = layer->wkbType();
579 
580  QString geomType;
581  switch ( QgsWkbTypes::flatType( sourceWkbType ) )
582  {
583  case QgsWkbTypes::Point:
584  geomType = QStringLiteral( "POINT" );
585  break;
587  geomType = QStringLiteral( "MULTIPOINT" );
588  break;
590  geomType = QStringLiteral( "LINESTRING" );
591  break;
593  geomType = QStringLiteral( "MULTILINESTRING" );
594  break;
596  geomType = QStringLiteral( "POLYGON" );
597  break;
599  geomType = QStringLiteral( "MULTIPOLYGON" );
600  break;
601  default:
602  showWarning( tr( "Layer %1 has unsupported geometry type %2." ).arg( layer->name(), QgsWkbTypes::displayString( layer->wkbType() ) ) );
603  break;
604  };
605 
606  QString zmInfo = QStringLiteral( "XY" );
607 
608  if ( QgsWkbTypes::hasZ( sourceWkbType ) )
609  zmInfo += 'Z';
610  if ( QgsWkbTypes::hasM( sourceWkbType ) )
611  zmInfo += 'M';
612 
613  QString epsgCode;
614 
615  if ( layer->crs().authid().startsWith( QLatin1String( "EPSG:" ), Qt::CaseInsensitive ) )
616  {
617  epsgCode = layer->crs().authid().mid( 5 );
618  }
619  else
620  {
621  epsgCode = '0';
622  showWarning( tr( "Layer %1 has unsupported Coordinate Reference System (%2)." ).arg( layer->name(), layer->crs().authid() ) );
623  }
624 
625  QString sqlAddGeom = QStringLiteral( "SELECT AddGeometryColumn('%1', 'Geometry', %2, '%3', '%4')" )
626  .arg( tableName, epsgCode, geomType, zmInfo );
627 
628  // create spatial index
629  QString sqlCreateIndex = QStringLiteral( "SELECT CreateSpatialIndex('%1', 'Geometry')" ).arg( tableName );
630 
631  if ( rc == SQLITE_OK )
632  {
633  rc = sqlExec( db, sqlAddGeom );
634  if ( rc == SQLITE_OK )
635  {
636  rc = sqlExec( db, sqlCreateIndex );
637  }
638  }
639  }
640 
641  if ( rc != SQLITE_OK )
642  {
643  showWarning( tr( "Filling SpatiaLite for layer %1 failed" ).arg( layer->name() ) );
644  return nullptr;
645  }
646 
647  // add new layer
648  QString connectionString = QStringLiteral( "dbname='%1' table='%2'%3 sql=" )
649  .arg( offlineDbPath,
650  tableName, layer->isSpatial() ? "(Geometry)" : "" );
651  newLayer = new QgsVectorLayer( connectionString,
652  layer->name() + " (offline)", QStringLiteral( "spatialite" ) );
653  break;
654  }
655  case GPKG:
656  {
657  // Set options
658  char **options = nullptr;
659 
660  options = CSLSetNameValue( options, "OVERWRITE", "YES" );
661  options = CSLSetNameValue( options, "IDENTIFIER", tr( "%1 (offline)" ).arg( layer->id() ).toUtf8().constData() );
662  options = CSLSetNameValue( options, "DESCRIPTION", layer->dataComment().toUtf8().constData() );
663 
664  //the FID-name should not exist in the original data
665  QString fidBase( QStringLiteral( "fid" ) );
666  QString fid = fidBase;
667  int counter = 1;
668  while ( layer->dataProvider()->fields().lookupField( fid ) >= 0 && counter < 10000 )
669  {
670  fid = fidBase + '_' + QString::number( counter );
671  counter++;
672  }
673  if ( counter == 10000 )
674  {
675  showWarning( tr( "Cannot make FID-name for GPKG " ) );
676  return nullptr;
677  }
678 
679  options = CSLSetNameValue( options, "FID", fid.toUtf8().constData() );
680 
681  if ( layer->isSpatial() )
682  {
683  options = CSLSetNameValue( options, "GEOMETRY_COLUMN", "geom" );
684  options = CSLSetNameValue( options, "SPATIAL_INDEX", "YES" );
685  }
686 
687  OGRSFDriverH hDriver = nullptr;
688  OGRSpatialReferenceH hSRS = OSRNewSpatialReference( layer->crs().toWkt().toLocal8Bit().data() );
689  gdal::ogr_datasource_unique_ptr hDS( OGROpen( offlineDbPath.toUtf8().constData(), true, &hDriver ) );
690  OGRLayerH hLayer = OGR_DS_CreateLayer( hDS.get(), tableName.toUtf8().constData(), hSRS, static_cast<OGRwkbGeometryType>( layer->wkbType() ), options );
691  CSLDestroy( options );
692  if ( hSRS )
693  OSRRelease( hSRS );
694  if ( !hLayer )
695  {
696  showWarning( tr( "Creation of layer failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
697  return nullptr;
698  }
699 
700  const QgsFields providerFields = layer->dataProvider()->fields();
701  for ( const auto &field : providerFields )
702  {
703  const QString fieldName( field.name() );
704  const QVariant::Type type = field.type();
705  OGRFieldType ogrType( OFTString );
706  if ( type == QVariant::Int )
707  ogrType = OFTInteger;
708  else if ( type == QVariant::LongLong )
709  ogrType = OFTInteger64;
710  else if ( type == QVariant::Double )
711  ogrType = OFTReal;
712  else if ( type == QVariant::Time )
713  ogrType = OFTTime;
714  else if ( type == QVariant::Date )
715  ogrType = OFTDate;
716  else if ( type == QVariant::DateTime )
717  ogrType = OFTDateTime;
718  else
719  ogrType = OFTString;
720 
721  int ogrWidth = field.length();
722 
723  gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( fieldName.toUtf8().constData(), ogrType ) );
724  OGR_Fld_SetWidth( fld.get(), ogrWidth );
725 
726  if ( OGR_L_CreateField( hLayer, fld.get(), true ) != OGRERR_NONE )
727  {
728  showWarning( tr( "Creation of field %1 failed (OGR error: %2)" )
729  .arg( fieldName, QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
730  return nullptr;
731  }
732  }
733 
734  // In GDAL >= 2.0, the driver implements a deferred creation strategy, so
735  // issue a command that will force table creation
736  CPLErrorReset();
737  OGR_L_ResetReading( hLayer );
738  if ( CPLGetLastErrorType() != CE_None )
739  {
740  QString msg( tr( "Creation of layer failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
741  showWarning( msg );
742  return nullptr;
743  }
744  hDS.reset();
745 
746  QString uri = QStringLiteral( "%1|layername=%2" ).arg( offlineDbPath, tableName );
747  newLayer = new QgsVectorLayer( uri, layer->name() + " (offline)", QStringLiteral( "ogr" ) );
748  break;
749  }
750  }
751 
752  if ( newLayer->isValid() )
753  {
754 
755  // copy features
756  newLayer->startEditing();
757  QgsFeature f;
758 
759  QgsFeatureRequest req;
760 
761  if ( onlySelected )
762  {
763  QgsFeatureIds selectedFids = layer->selectedFeatureIds();
764  if ( !selectedFids.isEmpty() )
765  req.setFilterFids( selectedFids );
766  }
767 
768  QgsFeatureIterator fit = layer->dataProvider()->getFeatures( req );
769 
771  {
773  }
774  else
775  {
777  }
778  int featureCount = 1;
779 
780  QList<QgsFeatureId> remoteFeatureIds;
781  while ( fit.nextFeature( f ) )
782  {
783  remoteFeatureIds << f.id();
784 
785  // NOTE: SpatiaLite provider ignores position of geometry column
786  // fill gap in QgsAttributeMap if geometry column is not last (WORKAROUND)
787  int column = 0;
788  QgsAttributes attrs = f.attributes();
789  // on GPKG newAttrs has an addition FID attribute, so we have to add a dummy in the original set
790  QgsAttributes newAttrs( containerType == GPKG ? attrs.count() + 1 : attrs.count() );
791  for ( int it = 0; it < attrs.count(); ++it )
792  {
793  newAttrs[column++] = attrs.at( it );
794  }
795  f.setAttributes( newAttrs );
796 
797  newLayer->addFeature( f );
798 
799  emit progressUpdated( featureCount++ );
800  }
801  if ( newLayer->commitChanges() )
802  {
804  featureCount = 1;
805 
806  // update feature id lookup
807  int layerId = getOrCreateLayerId( db, newLayer->id() );
808  QList<QgsFeatureId> offlineFeatureIds;
809 
810  QgsFeatureIterator fit = newLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setNoAttributes() );
811  while ( fit.nextFeature( f ) )
812  {
813  offlineFeatureIds << f.id();
814  }
815 
816  // NOTE: insert fids in this loop, as the db is locked during newLayer->nextFeature()
817  sqlExec( db, QStringLiteral( "BEGIN" ) );
818  int remoteCount = remoteFeatureIds.size();
819  for ( int i = 0; i < remoteCount; i++ )
820  {
821  // Check if the online feature has been fetched (WFS download aborted for some reason)
822  if ( i < offlineFeatureIds.count() )
823  {
824  addFidLookup( db, layerId, offlineFeatureIds.at( i ), remoteFeatureIds.at( i ) );
825  }
826  else
827  {
828  showWarning( tr( "Feature cannot be copied to the offline layer, please check if the online layer '%1' is still accessible." ).arg( layer->name() ) );
829  return nullptr;
830  }
831  emit progressUpdated( featureCount++ );
832  }
833  sqlExec( db, QStringLiteral( "COMMIT" ) );
834  }
835  else
836  {
837  showWarning( newLayer->commitErrors().join( QStringLiteral( "\n" ) ) );
838  }
839 
840  // mark as offline layer
842 
843  // store original layer source
846 
847  // register this layer with the central layers registry
849  QList<QgsMapLayer *>() << newLayer );
850 
851  // copy style
852  copySymbology( layer, newLayer );
853 
854  //remove constrainst of fields that use defaultValueClauses from provider on original
855  const auto fields = layer->fields();
856  for ( const QgsField &field : fields )
857  {
858  if ( !layer->dataProvider()->defaultValueClause( layer->fields().fieldOriginIndex( layer->fields().indexOf( field.name() ) ) ).isEmpty() )
859  {
860  newLayer->removeFieldConstraint( newLayer->fields().indexOf( field.name() ), QgsFieldConstraints::ConstraintNotNull );
861  }
862  }
863 
865  // Find the parent group of the original layer
866  QgsLayerTreeLayer *layerTreeLayer = layerTreeRoot->findLayer( layer->id() );
867  if ( layerTreeLayer )
868  {
869  QgsLayerTreeGroup *parentTreeGroup = qobject_cast<QgsLayerTreeGroup *>( layerTreeLayer->parent() );
870  if ( parentTreeGroup )
871  {
872  int index = parentTreeGroup->children().indexOf( layerTreeLayer );
873  // Move the new layer from the root group to the new group
874  QgsLayerTreeLayer *newLayerTreeLayer = layerTreeRoot->findLayer( newLayer->id() );
875  if ( newLayerTreeLayer )
876  {
877  QgsLayerTreeNode *newLayerTreeLayerClone = newLayerTreeLayer->clone();
878  //copy the showFeatureCount property to the new node
879  newLayerTreeLayerClone->setCustomProperty( CUSTOM_SHOW_FEATURE_COUNT, layerTreeLayer->customProperty( CUSTOM_SHOW_FEATURE_COUNT ) );
880  newLayerTreeLayerClone->setItemVisibilityChecked( layerTreeLayer->isVisible() );
881  QgsLayerTreeGroup *grp = qobject_cast<QgsLayerTreeGroup *>( newLayerTreeLayer->parent() );
882  parentTreeGroup->insertChildNode( index, newLayerTreeLayerClone );
883  if ( grp )
884  grp->removeChildNode( newLayerTreeLayer );
885  }
886  }
887  }
888 
889  updateRelations( layer, newLayer );
890  updateMapThemes( layer, newLayer );
891  updateLayerOrder( layer, newLayer );
892 
893 
894 
895  }
896  return newLayer;
897 }
898 
899 void QgsOfflineEditing::applyAttributesAdded( QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId, int commitNo )
900 {
901  QString sql = QStringLiteral( "SELECT \"name\", \"type\", \"length\", \"precision\", \"comment\" FROM 'log_added_attrs' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo );
902  QList<QgsField> fields = sqlQueryAttributesAdded( db, sql );
903 
904  const QgsVectorDataProvider *provider = remoteLayer->dataProvider();
905  QList<QgsVectorDataProvider::NativeType> nativeTypes = provider->nativeTypes();
906 
907  // NOTE: uses last matching QVariant::Type of nativeTypes
908  QMap < QVariant::Type, QString /*typeName*/ > typeNameLookup;
909  for ( int i = 0; i < nativeTypes.size(); i++ )
910  {
911  QgsVectorDataProvider::NativeType nativeType = nativeTypes.at( i );
912  typeNameLookup[ nativeType.mType ] = nativeType.mTypeName;
913  }
914 
915  emit progressModeSet( QgsOfflineEditing::AddFields, fields.size() );
916 
917  for ( int i = 0; i < fields.size(); i++ )
918  {
919  // lookup typename from layer provider
920  QgsField field = fields[i];
921  if ( typeNameLookup.contains( field.type() ) )
922  {
923  QString typeName = typeNameLookup[ field.type()];
924  field.setTypeName( typeName );
925  remoteLayer->addAttribute( field );
926  }
927  else
928  {
929  showWarning( QStringLiteral( "Could not add attribute '%1' of type %2" ).arg( field.name() ).arg( field.type() ) );
930  }
931 
932  emit progressUpdated( i + 1 );
933  }
934 }
935 
936 void QgsOfflineEditing::applyFeaturesAdded( QgsVectorLayer *offlineLayer, QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId )
937 {
938  QString sql = QStringLiteral( "SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
939  QList<int> featureIdInts = sqlQueryInts( db, sql );
940  QgsFeatureIds newFeatureIds;
941  Q_FOREACH ( int id, featureIdInts )
942  {
943  newFeatureIds << id;
944  }
945 
946  QgsExpressionContext context = remoteLayer->createExpressionContext();
947 
948  // get new features from offline layer
949  QgsFeatureList features;
950  QgsFeatureIterator it = offlineLayer->getFeatures( QgsFeatureRequest().setFilterFids( newFeatureIds ) );
951  QgsFeature feature;
952  while ( it.nextFeature( feature ) )
953  {
954  features << feature;
955  }
956 
957  // copy features to remote layer
958  emit progressModeSet( QgsOfflineEditing::AddFeatures, features.size() );
959 
960  int i = 1;
961  int newAttrsCount = remoteLayer->fields().count();
962  for ( QgsFeatureList::iterator it = features.begin(); it != features.end(); ++it )
963  {
964  // NOTE: SpatiaLite provider ignores position of geometry column
965  // restore gap in QgsAttributeMap if geometry column is not last (WORKAROUND)
966  QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
967  QgsAttributes newAttrs( newAttrsCount );
968  QgsAttributes attrs = it->attributes();
969  for ( int it = 0; it < attrs.count(); ++it )
970  {
971  newAttrs[ attrLookup[ it ] ] = attrs.at( it );
972  }
973 
974  // respect constraints and provider default values
975  QgsFeature f = QgsVectorLayerUtils::createFeature( remoteLayer, it->geometry(), newAttrs.toMap(), &context );
976  remoteLayer->addFeature( f );
977 
978  emit progressUpdated( i++ );
979  }
980 }
981 
982 void QgsOfflineEditing::applyFeaturesRemoved( QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId )
983 {
984  QString sql = QStringLiteral( "SELECT \"fid\" FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
985  QgsFeatureIds values = sqlQueryFeaturesRemoved( db, sql );
986 
987  emit progressModeSet( QgsOfflineEditing::RemoveFeatures, values.size() );
988 
989  int i = 1;
990  for ( QgsFeatureIds::const_iterator it = values.constBegin(); it != values.constEnd(); ++it )
991  {
992  QgsFeatureId fid = remoteFid( db, layerId, *it );
993  remoteLayer->deleteFeature( fid );
994 
995  emit progressUpdated( i++ );
996  }
997 }
998 
999 void QgsOfflineEditing::applyAttributeValueChanges( QgsVectorLayer *offlineLayer, QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId, int commitNo )
1000 {
1001  QString sql = QStringLiteral( "SELECT \"fid\", \"attr\", \"value\" FROM 'log_feature_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2 " ).arg( layerId ).arg( commitNo );
1002  AttributeValueChanges values = sqlQueryAttributeValueChanges( db, sql );
1003 
1004  emit progressModeSet( QgsOfflineEditing::UpdateFeatures, values.size() );
1005 
1006  QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
1007 
1008  for ( int i = 0; i < values.size(); i++ )
1009  {
1010  QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid );
1011  QgsDebugMsgLevel( QStringLiteral( "Offline changeAttributeValue %1 = %2" ).arg( QString( attrLookup[ values.at( i ).attr ] ), values.at( i ).value ), 4 );
1012  remoteLayer->changeAttributeValue( fid, attrLookup[ values.at( i ).attr ], values.at( i ).value );
1013 
1014  emit progressUpdated( i + 1 );
1015  }
1016 }
1017 
1018 void QgsOfflineEditing::applyGeometryChanges( QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId, int commitNo )
1019 {
1020  QString sql = QStringLiteral( "SELECT \"fid\", \"geom_wkt\" FROM 'log_geometry_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo );
1021  GeometryChanges values = sqlQueryGeometryChanges( db, sql );
1022 
1024 
1025  for ( int i = 0; i < values.size(); i++ )
1026  {
1027  QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid );
1028  QgsGeometry newGeom = QgsGeometry::fromWkt( values.at( i ).geom_wkt );
1029  remoteLayer->changeGeometry( fid, newGeom );
1030 
1031  emit progressUpdated( i + 1 );
1032  }
1033 }
1034 
1035 void QgsOfflineEditing::updateFidLookup( QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId )
1036 {
1037  // update fid lookup for added features
1038 
1039  // get remote added fids
1040  // NOTE: use QMap for sorted fids
1041  QMap < QgsFeatureId, bool /*dummy*/ > newRemoteFids;
1042  QgsFeature f;
1043 
1044  QgsFeatureIterator fit = remoteLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setNoAttributes() );
1045 
1047 
1048  int i = 1;
1049  while ( fit.nextFeature( f ) )
1050  {
1051  if ( offlineFid( db, layerId, f.id() ) == -1 )
1052  {
1053  newRemoteFids[ f.id()] = true;
1054  }
1055 
1056  emit progressUpdated( i++ );
1057  }
1058 
1059  // get local added fids
1060  // NOTE: fids are sorted
1061  QString sql = QStringLiteral( "SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
1062  QList<int> newOfflineFids = sqlQueryInts( db, sql );
1063 
1064  if ( newRemoteFids.size() != newOfflineFids.size() )
1065  {
1066  //showWarning( QString( "Different number of new features on offline layer (%1) and remote layer (%2)" ).arg(newOfflineFids.size()).arg(newRemoteFids.size()) );
1067  }
1068  else
1069  {
1070  // add new fid lookups
1071  i = 0;
1072  sqlExec( db, QStringLiteral( "BEGIN" ) );
1073  for ( QMap<QgsFeatureId, bool>::const_iterator it = newRemoteFids.constBegin(); it != newRemoteFids.constEnd(); ++it )
1074  {
1075  addFidLookup( db, layerId, newOfflineFids.at( i++ ), it.key() );
1076  }
1077  sqlExec( db, QStringLiteral( "COMMIT" ) );
1078  }
1079 }
1080 
1081 void QgsOfflineEditing::copySymbology( QgsVectorLayer *sourceLayer, QgsVectorLayer *targetLayer )
1082 {
1083  QString error;
1084  QDomDocument doc;
1085  QgsReadWriteContext context;
1086  QgsMapLayer::StyleCategories categories = static_cast<QgsMapLayer::StyleCategories>( QgsMapLayer::AllStyleCategories ) & ~QgsMapLayer::CustomProperties;
1087  sourceLayer->exportNamedStyle( doc, error, context, categories );
1088 
1089  if ( error.isEmpty() )
1090  {
1091  targetLayer->importNamedStyle( doc, error, categories );
1092  }
1093  if ( !error.isEmpty() )
1094  {
1095  showWarning( error );
1096  }
1097 }
1098 
1099 void QgsOfflineEditing::updateRelations( QgsVectorLayer *sourceLayer, QgsVectorLayer *targetLayer )
1100 {
1102  QList<QgsRelation> relations;
1103  relations = relationManager->referencedRelations( sourceLayer );
1104 
1105  Q_FOREACH ( QgsRelation relation, relations )
1106  {
1107  relationManager->removeRelation( relation );
1108  relation.setReferencedLayer( targetLayer->id() );
1109  relationManager->addRelation( relation );
1110  }
1111 
1112  relations = relationManager->referencingRelations( sourceLayer );
1113 
1114  Q_FOREACH ( QgsRelation relation, relations )
1115  {
1116  relationManager->removeRelation( relation );
1117  relation.setReferencingLayer( targetLayer->id() );
1118  relationManager->addRelation( relation );
1119  }
1120 }
1121 
1122 void QgsOfflineEditing::updateMapThemes( QgsVectorLayer *sourceLayer, QgsVectorLayer *targetLayer )
1123 {
1125  QStringList mapThemeNames = mapThemeCollection->mapThemes();
1126 
1127  Q_FOREACH ( const QString &mapThemeName, mapThemeNames )
1128  {
1129  QgsMapThemeCollection::MapThemeRecord record = mapThemeCollection->mapThemeState( mapThemeName );
1130 
1131  Q_FOREACH ( QgsMapThemeCollection::MapThemeLayerRecord layerRecord, record.layerRecords() )
1132  {
1133  if ( layerRecord.layer() == sourceLayer )
1134  {
1135  layerRecord.setLayer( targetLayer );
1136  record.removeLayerRecord( sourceLayer );
1137  record.addLayerRecord( layerRecord );
1138  }
1139  }
1140 
1141  QgsProject::instance()->mapThemeCollection()->update( mapThemeName, record );
1142  }
1143 }
1144 
1145 void QgsOfflineEditing::updateLayerOrder( QgsVectorLayer *sourceLayer, QgsVectorLayer *targetLayer )
1146 {
1147  QList<QgsMapLayer *> layerOrder = QgsProject::instance()->layerTreeRoot()->customLayerOrder();
1148 
1149  auto iterator = layerOrder.begin();
1150 
1151  while ( iterator != layerOrder.end() )
1152  {
1153  if ( *iterator == targetLayer )
1154  {
1155  iterator = layerOrder.erase( iterator );
1156  if ( iterator == layerOrder.end() )
1157  break;
1158  }
1159 
1160  if ( *iterator == sourceLayer )
1161  {
1162  *iterator = targetLayer;
1163  }
1164 
1165  ++iterator;
1166  }
1167 
1169 }
1170 
1171 // NOTE: use this to map column indices in case the remote geometry column is not last
1172 QMap<int, int> QgsOfflineEditing::attributeLookup( QgsVectorLayer *offlineLayer, QgsVectorLayer *remoteLayer )
1173 {
1174  const QgsAttributeList &offlineAttrs = offlineLayer->attributeList();
1175 
1176  QMap < int /*offline attr*/, int /*remote attr*/ > attrLookup;
1177  // NOTE: though offlineAttrs can have new attributes not yet synced, we take the amount of offlineAttrs
1178  // because we anyway only add mapping for the fields existing in remoteLayer (this because it could contain fid on 0)
1179  for ( int i = 0; i < offlineAttrs.size(); i++ )
1180  {
1181  if ( remoteLayer->fields().lookupField( offlineLayer->fields().field( i ).name() ) >= 0 )
1182  attrLookup.insert( offlineAttrs.at( i ), remoteLayer->fields().indexOf( offlineLayer->fields().field( i ).name() ) );
1183  }
1184 
1185  return attrLookup;
1186 }
1187 
1188 void QgsOfflineEditing::showWarning( const QString &message )
1189 {
1190  emit warning( tr( "Offline Editing Plugin" ), message );
1191 }
1192 
1193 sqlite3_database_unique_ptr QgsOfflineEditing::openLoggingDb()
1194 {
1195  sqlite3_database_unique_ptr database;
1197  if ( !dbPath.isEmpty() )
1198  {
1199  QString absoluteDbPath = QgsProject::instance()->readPath( dbPath );
1200  int rc = database.open( absoluteDbPath );
1201  if ( rc != SQLITE_OK )
1202  {
1203  QgsDebugMsg( QStringLiteral( "Could not open the SpatiaLite logging database" ) );
1204  showWarning( tr( "Could not open the SpatiaLite logging database" ) );
1205  }
1206  }
1207  else
1208  {
1209  QgsDebugMsg( QStringLiteral( "dbPath is empty!" ) );
1210  }
1211  return database;
1212 }
1213 
1214 int QgsOfflineEditing::getOrCreateLayerId( sqlite3 *db, const QString &qgisLayerId )
1215 {
1216  QString sql = QStringLiteral( "SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
1217  int layerId = sqlQueryInt( db, sql, -1 );
1218  if ( layerId == -1 )
1219  {
1220  // next layer id
1221  sql = QStringLiteral( "SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'layer_id'" );
1222  int newLayerId = sqlQueryInt( db, sql, -1 );
1223 
1224  // insert layer
1225  sql = QStringLiteral( "INSERT INTO 'log_layer_ids' VALUES (%1, '%2')" ).arg( newLayerId ).arg( qgisLayerId );
1226  sqlExec( db, sql );
1227 
1228  // increase layer_id
1229  // TODO: use trigger for auto increment?
1230  sql = QStringLiteral( "UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'layer_id'" ).arg( newLayerId + 1 );
1231  sqlExec( db, sql );
1232 
1233  layerId = newLayerId;
1234  }
1235 
1236  return layerId;
1237 }
1238 
1239 int QgsOfflineEditing::getCommitNo( sqlite3 *db )
1240 {
1241  QString sql = QStringLiteral( "SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'commit_no'" );
1242  return sqlQueryInt( db, sql, -1 );
1243 }
1244 
1245 void QgsOfflineEditing::increaseCommitNo( sqlite3 *db )
1246 {
1247  QString sql = QStringLiteral( "UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'commit_no'" ).arg( getCommitNo( db ) + 1 );
1248  sqlExec( db, sql );
1249 }
1250 
1251 void QgsOfflineEditing::addFidLookup( sqlite3 *db, int layerId, QgsFeatureId offlineFid, QgsFeatureId remoteFid )
1252 {
1253  QString sql = QStringLiteral( "INSERT INTO 'log_fids' VALUES ( %1, %2, %3 )" ).arg( layerId ).arg( offlineFid ).arg( remoteFid );
1254  sqlExec( db, sql );
1255 }
1256 
1257 QgsFeatureId QgsOfflineEditing::remoteFid( sqlite3 *db, int layerId, QgsFeatureId offlineFid )
1258 {
1259  QString sql = QStringLiteral( "SELECT \"remote_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2" ).arg( layerId ).arg( offlineFid );
1260  return sqlQueryInt( db, sql, -1 );
1261 }
1262 
1263 QgsFeatureId QgsOfflineEditing::offlineFid( sqlite3 *db, int layerId, QgsFeatureId remoteFid )
1264 {
1265  QString sql = QStringLiteral( "SELECT \"offline_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"remote_fid\" = %2" ).arg( layerId ).arg( remoteFid );
1266  return sqlQueryInt( db, sql, -1 );
1267 }
1268 
1269 bool QgsOfflineEditing::isAddedFeature( sqlite3 *db, int layerId, QgsFeatureId fid )
1270 {
1271  QString sql = QStringLiteral( "SELECT COUNT(\"fid\") FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( fid );
1272  return ( sqlQueryInt( db, sql, 0 ) > 0 );
1273 }
1274 
1275 int QgsOfflineEditing::sqlExec( sqlite3 *db, const QString &sql )
1276 {
1277  char *errmsg = nullptr;
1278  int rc = sqlite3_exec( db, sql.toUtf8(), nullptr, nullptr, &errmsg );
1279  if ( rc != SQLITE_OK )
1280  {
1281  showWarning( errmsg );
1282  }
1283  return rc;
1284 }
1285 
1286 int QgsOfflineEditing::sqlQueryInt( sqlite3 *db, const QString &sql, int defaultValue )
1287 {
1288  sqlite3_stmt *stmt = nullptr;
1289  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1290  {
1291  showWarning( sqlite3_errmsg( db ) );
1292  return defaultValue;
1293  }
1294 
1295  int value = defaultValue;
1296  int ret = sqlite3_step( stmt );
1297  if ( ret == SQLITE_ROW )
1298  {
1299  value = sqlite3_column_int( stmt, 0 );
1300  }
1301  sqlite3_finalize( stmt );
1302 
1303  return value;
1304 }
1305 
1306 QList<int> QgsOfflineEditing::sqlQueryInts( sqlite3 *db, const QString &sql )
1307 {
1308  QList<int> values;
1309 
1310  sqlite3_stmt *stmt = nullptr;
1311  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1312  {
1313  showWarning( sqlite3_errmsg( db ) );
1314  return values;
1315  }
1316 
1317  int ret = sqlite3_step( stmt );
1318  while ( ret == SQLITE_ROW )
1319  {
1320  values << sqlite3_column_int( stmt, 0 );
1321 
1322  ret = sqlite3_step( stmt );
1323  }
1324  sqlite3_finalize( stmt );
1325 
1326  return values;
1327 }
1328 
1329 QList<QgsField> QgsOfflineEditing::sqlQueryAttributesAdded( sqlite3 *db, const QString &sql )
1330 {
1331  QList<QgsField> values;
1332 
1333  sqlite3_stmt *stmt = nullptr;
1334  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1335  {
1336  showWarning( sqlite3_errmsg( db ) );
1337  return values;
1338  }
1339 
1340  int ret = sqlite3_step( stmt );
1341  while ( ret == SQLITE_ROW )
1342  {
1343  QgsField field( QString( reinterpret_cast< const char * >( sqlite3_column_text( stmt, 0 ) ) ),
1344  static_cast< QVariant::Type >( sqlite3_column_int( stmt, 1 ) ),
1345  QString(), // typeName
1346  sqlite3_column_int( stmt, 2 ),
1347  sqlite3_column_int( stmt, 3 ),
1348  QString( reinterpret_cast< const char * >( sqlite3_column_text( stmt, 4 ) ) ) );
1349  values << field;
1350 
1351  ret = sqlite3_step( stmt );
1352  }
1353  sqlite3_finalize( stmt );
1354 
1355  return values;
1356 }
1357 
1358 QgsFeatureIds QgsOfflineEditing::sqlQueryFeaturesRemoved( sqlite3 *db, const QString &sql )
1359 {
1360  QgsFeatureIds values;
1361 
1362  sqlite3_stmt *stmt = nullptr;
1363  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1364  {
1365  showWarning( sqlite3_errmsg( db ) );
1366  return values;
1367  }
1368 
1369  int ret = sqlite3_step( stmt );
1370  while ( ret == SQLITE_ROW )
1371  {
1372  values << sqlite3_column_int( stmt, 0 );
1373 
1374  ret = sqlite3_step( stmt );
1375  }
1376  sqlite3_finalize( stmt );
1377 
1378  return values;
1379 }
1380 
1381 QgsOfflineEditing::AttributeValueChanges QgsOfflineEditing::sqlQueryAttributeValueChanges( sqlite3 *db, const QString &sql )
1382 {
1383  AttributeValueChanges values;
1384 
1385  sqlite3_stmt *stmt = nullptr;
1386  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1387  {
1388  showWarning( sqlite3_errmsg( db ) );
1389  return values;
1390  }
1391 
1392  int ret = sqlite3_step( stmt );
1393  while ( ret == SQLITE_ROW )
1394  {
1395  AttributeValueChange change;
1396  change.fid = sqlite3_column_int( stmt, 0 );
1397  change.attr = sqlite3_column_int( stmt, 1 );
1398  change.value = QString( reinterpret_cast< const char * >( sqlite3_column_text( stmt, 2 ) ) );
1399  values << change;
1400 
1401  ret = sqlite3_step( stmt );
1402  }
1403  sqlite3_finalize( stmt );
1404 
1405  return values;
1406 }
1407 
1408 QgsOfflineEditing::GeometryChanges QgsOfflineEditing::sqlQueryGeometryChanges( sqlite3 *db, const QString &sql )
1409 {
1410  GeometryChanges values;
1411 
1412  sqlite3_stmt *stmt = nullptr;
1413  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1414  {
1415  showWarning( sqlite3_errmsg( db ) );
1416  return values;
1417  }
1418 
1419  int ret = sqlite3_step( stmt );
1420  while ( ret == SQLITE_ROW )
1421  {
1422  GeometryChange change;
1423  change.fid = sqlite3_column_int( stmt, 0 );
1424  change.geom_wkt = QString( reinterpret_cast< const char * >( sqlite3_column_text( stmt, 1 ) ) );
1425  values << change;
1426 
1427  ret = sqlite3_step( stmt );
1428  }
1429  sqlite3_finalize( stmt );
1430 
1431  return values;
1432 }
1433 
1434 void QgsOfflineEditing::committedAttributesAdded( const QString &qgisLayerId, const QList<QgsField> &addedAttributes )
1435 {
1436  sqlite3_database_unique_ptr database = openLoggingDb();
1437  if ( !database )
1438  return;
1439 
1440  // insert log
1441  int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1442  int commitNo = getCommitNo( database.get() );
1443 
1444  for ( QList<QgsField>::const_iterator it = addedAttributes.begin(); it != addedAttributes.end(); ++it )
1445  {
1446  QgsField field = *it;
1447  QString sql = QStringLiteral( "INSERT INTO 'log_added_attrs' VALUES ( %1, %2, '%3', %4, %5, %6, '%7' )" )
1448  .arg( layerId )
1449  .arg( commitNo )
1450  .arg( field.name() )
1451  .arg( field.type() )
1452  .arg( field.length() )
1453  .arg( field.precision() )
1454  .arg( field.comment() );
1455  sqlExec( database.get(), sql );
1456  }
1457 
1458  increaseCommitNo( database.get() );
1459 }
1460 
1461 void QgsOfflineEditing::committedFeaturesAdded( const QString &qgisLayerId, const QgsFeatureList &addedFeatures )
1462 {
1463  sqlite3_database_unique_ptr database = openLoggingDb();
1464  if ( !database )
1465  return;
1466 
1467  // insert log
1468  int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1469 
1470  // get new feature ids from db
1471  QgsMapLayer *layer = QgsProject::instance()->mapLayer( qgisLayerId );
1472  QgsDataSourceUri uri = QgsDataSourceUri( layer->source() );
1473 
1475  QString tableName;
1476 
1477  if ( !offlinePath.contains( ".gpkg" ) )
1478  {
1479  tableName = uri.table();
1480  }
1481  else
1482  {
1483  tableName = uri.param( offlinePath + "|layername" );
1484  }
1485 
1486  // only store feature ids
1487  QString sql = QStringLiteral( "SELECT ROWID FROM '%1' ORDER BY ROWID DESC LIMIT %2" ).arg( tableName ).arg( addedFeatures.size() );
1488  QList<int> newFeatureIds = sqlQueryInts( database.get(), sql );
1489  for ( int i = newFeatureIds.size() - 1; i >= 0; i-- )
1490  {
1491  QString sql = QStringLiteral( "INSERT INTO 'log_added_features' VALUES ( %1, %2 )" )
1492  .arg( layerId )
1493  .arg( newFeatureIds.at( i ) );
1494  sqlExec( database.get(), sql );
1495  }
1496 }
1497 
1498 void QgsOfflineEditing::committedFeaturesRemoved( const QString &qgisLayerId, const QgsFeatureIds &deletedFeatureIds )
1499 {
1500  sqlite3_database_unique_ptr database = openLoggingDb();
1501  if ( !database )
1502  return;
1503 
1504  // insert log
1505  int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1506 
1507  for ( QgsFeatureIds::const_iterator it = deletedFeatureIds.begin(); it != deletedFeatureIds.end(); ++it )
1508  {
1509  if ( isAddedFeature( database.get(), layerId, *it ) )
1510  {
1511  // remove from added features log
1512  QString sql = QStringLiteral( "DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( *it );
1513  sqlExec( database.get(), sql );
1514  }
1515  else
1516  {
1517  QString sql = QStringLiteral( "INSERT INTO 'log_removed_features' VALUES ( %1, %2)" )
1518  .arg( layerId )
1519  .arg( *it );
1520  sqlExec( database.get(), sql );
1521  }
1522  }
1523 }
1524 
1525 void QgsOfflineEditing::committedAttributeValuesChanges( const QString &qgisLayerId, const QgsChangedAttributesMap &changedAttrsMap )
1526 {
1527  sqlite3_database_unique_ptr database = openLoggingDb();
1528  if ( !database )
1529  return;
1530 
1531  // insert log
1532  int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1533  int commitNo = getCommitNo( database.get() );
1534 
1535  for ( QgsChangedAttributesMap::const_iterator cit = changedAttrsMap.begin(); cit != changedAttrsMap.end(); ++cit )
1536  {
1537  QgsFeatureId fid = cit.key();
1538  if ( isAddedFeature( database.get(), layerId, fid ) )
1539  {
1540  // skip added features
1541  continue;
1542  }
1543  QgsAttributeMap attrMap = cit.value();
1544  for ( QgsAttributeMap::const_iterator it = attrMap.constBegin(); it != attrMap.constEnd(); ++it )
1545  {
1546  QString sql = QStringLiteral( "INSERT INTO 'log_feature_updates' VALUES ( %1, %2, %3, %4, '%5' )" )
1547  .arg( layerId )
1548  .arg( commitNo )
1549  .arg( fid )
1550  .arg( it.key() ) // attr
1551  .arg( it.value().toString() ); // value
1552  sqlExec( database.get(), sql );
1553  }
1554  }
1555 
1556  increaseCommitNo( database.get() );
1557 }
1558 
1559 void QgsOfflineEditing::committedGeometriesChanges( const QString &qgisLayerId, const QgsGeometryMap &changedGeometries )
1560 {
1561  sqlite3_database_unique_ptr database = openLoggingDb();
1562  if ( !database )
1563  return;
1564 
1565  // insert log
1566  int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1567  int commitNo = getCommitNo( database.get() );
1568 
1569  for ( QgsGeometryMap::const_iterator it = changedGeometries.begin(); it != changedGeometries.end(); ++it )
1570  {
1571  QgsFeatureId fid = it.key();
1572  if ( isAddedFeature( database.get(), layerId, fid ) )
1573  {
1574  // skip added features
1575  continue;
1576  }
1577  QgsGeometry geom = it.value();
1578  QString sql = QStringLiteral( "INSERT INTO 'log_geometry_updates' VALUES ( %1, %2, %3, '%4' )" )
1579  .arg( layerId )
1580  .arg( commitNo )
1581  .arg( fid )
1582  .arg( geom.asWkt() );
1583  sqlExec( database.get(), sql );
1584 
1585  // TODO: use WKB instead of WKT?
1586  }
1587 
1588  increaseCommitNo( database.get() );
1589 }
1590 
1591 void QgsOfflineEditing::startListenFeatureChanges()
1592 {
1593  QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( sender() );
1594  // enable logging, check if editBuffer is not null
1595  if ( vLayer->editBuffer() )
1596  {
1597  QgsVectorLayerEditBuffer *editBuffer = vLayer->editBuffer();
1599  this, &QgsOfflineEditing::committedAttributesAdded );
1601  this, &QgsOfflineEditing::committedAttributeValuesChanges );
1603  this, &QgsOfflineEditing::committedGeometriesChanges );
1604  }
1605  connect( vLayer, &QgsVectorLayer::committedFeaturesAdded,
1606  this, &QgsOfflineEditing::committedFeaturesAdded );
1607  connect( vLayer, &QgsVectorLayer::committedFeaturesRemoved,
1608  this, &QgsOfflineEditing::committedFeaturesRemoved );
1609 }
1610 
1611 void QgsOfflineEditing::stopListenFeatureChanges()
1612 {
1613  QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( sender() );
1614  // disable logging, check if editBuffer is not null
1615  if ( vLayer->editBuffer() )
1616  {
1617  QgsVectorLayerEditBuffer *editBuffer = vLayer->editBuffer();
1618  disconnect( editBuffer, &QgsVectorLayerEditBuffer::committedAttributesAdded,
1619  this, &QgsOfflineEditing::committedAttributesAdded );
1621  this, &QgsOfflineEditing::committedAttributeValuesChanges );
1623  this, &QgsOfflineEditing::committedGeometriesChanges );
1624  }
1625  disconnect( vLayer, &QgsVectorLayer::committedFeaturesAdded,
1626  this, &QgsOfflineEditing::committedFeaturesAdded );
1627  disconnect( vLayer, &QgsVectorLayer::committedFeaturesRemoved,
1628  this, &QgsOfflineEditing::committedFeaturesRemoved );
1629 }
1630 
1631 void QgsOfflineEditing::layerAdded( QgsMapLayer *layer )
1632 {
1633  // detect offline layer
1634  if ( layer->customProperty( CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE, false ).toBool() )
1635  {
1636  QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( layer );
1637  connect( vLayer, &QgsVectorLayer::editingStarted, this, &QgsOfflineEditing::startListenFeatureChanges );
1638  connect( vLayer, &QgsVectorLayer::editingStopped, this, &QgsOfflineEditing::stopListenFeatureChanges );
1639  }
1640 }
1641 
1642 
const QList< QgsVectorLayerJoinInfo > vectorJoins() const
Layer tree group node serves as a container for layers and further groups.
void setJoinLayer(QgsVectorLayer *layer)
Sets weak reference to the joined layer.
QgsFeatureId id
Definition: qgsfeature.h:64
The class is used as a container of context for various read/write operations on other objects...
Wrapper for iterator of features from vector data provider or vector layer.
QMap< QgsFeatureId, QgsGeometry > QgsGeometryMap
Definition: qgsfeature.h:566
void layerProgressUpdated(int layer, int numLayers)
Is emitted whenever a new layer is being processed.
int open(const QString &path)
Opens the database at the specified file path.
bool addJoin(const QgsVectorLayerJoinInfo &joinInfo)
Joins another vector layer to this layer.
int open_v2(const QString &path, int flags, const char *zVfs)
Opens the database at the specified file path.
Base class for all map layer types.
Definition: qgsmaplayer.h:63
void setLayer(QgsMapLayer *layer)
Sets the map layer for this record.
QgsAttributeList attributeList() const
Returns list of attribute indexes.
QString title() const
Returns the project&#39;s title.
Definition: qgsproject.cpp:431
Filter using feature IDs.
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:34
QString joinLayerId() const
ID of the joined layer - may be used to resolve reference to the joined layer.
bool isValid() const
Returns the status of the layer.
QString name
Definition: qgsfield.h:57
int precision
Definition: qgsfield.h:54
void removeFieldConstraint(int index, QgsFieldConstraints::Constraint constraint)
Removes a constraint for a specified field index.
virtual bool importNamedStyle(QDomDocument &doc, QString &errorMsg, QgsMapLayer::StyleCategories categories=QgsMapLayer::AllStyleCategories)
Import the properties of this layer from a QDomDocument.
#define PROJECT_ENTRY_KEY_OFFLINE_DB_PATH
void setSnappingConfig(const QgsSnappingConfig &snappingConfig)
The snapping configuration for this project.
Definition: qgsproject.cpp:843
QVariant customProperty(const QString &key, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer. Properties are stored in a map and saved in project file...
QString readPath(const QString &filename) const
Turn filename read from the project file to an absolute path.
QStringList commitErrors() const
Returns a list containing any error messages generated when attempting to commit changes to the layer...
QgsWkbTypes::Type wkbType() const FINAL
Returns the WKBType or WKBUnknown in case of error.
bool deleteFeature(QgsFeatureId fid)
Deletes a feature from the layer (but does not commit it).
void committedAttributesAdded(const QString &layerId, const QList< QgsField > &addedAttributes)
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QList< QgsFeature > QgsFeatureList
Definition: qgsfeature.h:571
#define CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE
Individual map theme record of visible layers and styles.
bool commitChanges()
Attempts to commit to the underlying data provider any buffered changes made since the last to call t...
void setReferencingLayer(const QString &id)
Set the referencing (child) layer id.
void update(const QString &name, const QgsMapThemeCollection::MapThemeRecord &state)
Updates a map theme within the collection.
bool startEditing()
Makes the layer editable.
void setCustomProperty(const QString &key, const QVariant &value)
Set a custom property for layer.
QString toWkt() const
Returns a WKT representation of this CRS.
QString comment
Definition: qgsfield.h:56
#define CUSTOM_PROPERTY_REMOTE_SOURCE
qint64 QgsFeatureId
Definition: qgsfeatureid.h:25
Container of fields for a vector layer.
Definition: qgsfields.h:42
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:106
void setAttributes(const QgsAttributes &attrs)
Sets the feature&#39;s attributes.
Definition: qgsfeature.cpp:127
void removeChildNode(QgsLayerTreeNode *node)
Remove a child node from this group.
QString source() const
Returns the source for the layer.
Individual record of a visible layer in a map theme record.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:55
QList< QgsRelation > referencingRelations(const QgsVectorLayer *layer=nullptr, int fieldIdx=-2) const
Gets all relations where the specified layer (and field) is the referencing part (i.e.
QString readEntry(const QString &scope, const QString &key, const QString &def=QString(), bool *ok=nullptr) const
void committedFeaturesRemoved(const QString &layerId, const QgsFeatureIds &deletedFeatureIds)
This signal is emitted, when features are deleted from the provider.
bool isSpatial() const FINAL
Returns true if this is a geometry layer and false in case of NoGeometry (table only) or UnknownGeome...
static bool hasZ(Type type)
Tests whether a WKB type contains the z-dimension.
Definition: qgswkbtypes.h:906
virtual QString name() const =0
Returns a provider name.
void setIndividualLayerSettings(QgsVectorLayer *vl, const QgsSnappingConfig::IndividualLayerSettings &individualLayerSettings)
Sets individual layer snappings settings (applied if mode is AdvancedConfiguration) ...
void removeLayerRecord(QgsMapLayer *layer)
Removes a record for layer if present.
QString param(const QString &key) const
Gets generic param (generic mode)
int length
Definition: qgsfield.h:53
bool isVisible() const
Returns whether a node is really visible (ie checked and all its ancestors checked as well) ...
bool writeEntry(const QString &scope, const QString &key, bool value)
Write a boolean entry to the project file.
void progressUpdated(int progress)
Emitted with the progress of the current mode.
void committedGeometriesChanges(const QString &layerId, const QgsGeometryMap &changedGeometries)
QgsVectorLayerEditBuffer * editBuffer()
Buffer with uncommitted editing operations. Only valid after editing has been turned on...
Type
The WKB type describes the number of dimensions a geometry has.
Definition: qgswkbtypes.h:68
void setCustomLayerOrder(const QList< QgsMapLayer * > &customLayerOrder)
The order in which layers will be rendered on the canvas.
QList< QgsLayerTreeNode * > children()
Gets list of children of the node. Children are owned by the parent.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
QgsMapThemeCollection mapThemeCollection
Definition: qgsproject.h:98
void progressStopped()
Emitted when the processing of all layers has finished.
bool changeGeometry(QgsFeatureId fid, QgsGeometry &geometry, bool skipDefaultValue=false)
Changes a feature&#39;s geometry within the layer&#39;s edit buffer (but does not immediately commit the chan...
Unique pointer for spatialite databases, which automatically closes the database when the pointer goe...
QString errorMessage() const
Returns the most recent error message encountered by the database.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
void reload() FINAL
Synchronises with changes in the datasource.
QgsFields fields() const override=0
Returns the fields associated with this data provider.
void setTypeName(const QString &typeName)
Set the field type.
Definition: qgsfield.cpp:151
const QString & typeName
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
QgsLayerTreeNode * parent()
Gets pointer to the parent. If parent is a null pointer, the node is a root node. ...
void committedFeaturesAdded(const QString &layerId, const QgsFeatureList &addedFeatures)
This signal is emitted, when features are added to the provider.
static QgsFeature createFeature(const QgsVectorLayer *layer, const QgsGeometry &geometry=QgsGeometry(), const QgsAttributeMap &attributes=QgsAttributeMap(), QgsExpressionContext *context=nullptr)
Creates a new feature ready for insertion into a layer.
Defines left outer join from our vector layer to some other vector layer.
QMap< int, QVariant > QgsAttributeMap
Definition: qgsattributes.h:38
int fieldOriginIndex(int fieldIdx) const
Gets field&#39;s origin index (its meaning is specific to each type of origin)
Definition: qgsfields.cpp:197
void editingStopped()
Is emitted, when edited changes successfully have been written to the data provider.
This class wraps a request for features to a vector layer (or directly its vector data provider)...
QList< QgsVectorDataProvider::NativeType > nativeTypes() const
Returns the names of the supported types.
This class is a base class for nodes in a layer tree.
ContainerType
Type of offline database container file.
long featureCount(const QString &legendKey) const
Number of features rendered with specified legend key.
QString id() const
Returns the layer&#39;s unique ID, which is used to access this layer from QgsProject.
bool removeEntry(const QString &scope, const QString &key)
Remove the given key.
int count() const
Returns number of items.
Definition: qgsfields.cpp:133
FilterType filterType() const
Returns the filter type which is currently set on this request.
QHash< QgsVectorLayer *, QgsSnappingConfig::IndividualLayerSettings > individualLayerSettings() const
Returns individual snapping settings for all layers.
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:48
const QgsFeatureIds & selectedFeatureIds() const
Returns a list of the selected features IDs in this layer.
QVariant customProperty(const QString &value, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer.
int lookupField(const QString &fieldName) const
Looks up field&#39;s index from the field name.
Definition: qgsfields.cpp:320
QgsRelationManager relationManager
Definition: qgsproject.h:100
int indexOf(const QString &fieldName) const
Gets the field index from the field name.
Definition: qgsfields.cpp:207
QgsField field(int fieldIdx) const
Gets field at particular index (must be in range 0..N-1)
Definition: qgsfields.cpp:168
void editingStarted()
Is emitted, when editing on this layer has started.
QgsExpressionContext createExpressionContext() const FINAL
This method needs to be reimplemented in all classes which implement this interface and return an exp...
int open(const QString &path)
Opens the database at the specified file path.
#define PROJECT_ENTRY_SCOPE_OFFLINE
void setReferencedLayer(const QString &id)
Set the referenced (parent) layer id.
struct sqlite3 sqlite3
QList< QgsMapLayer * > customLayerOrder() const
The order in which layers will be rendered on the canvas.
Unique pointer for sqlite3 databases, which automatically closes the database when the pointer goes o...
#define CUSTOM_SHOW_FEATURE_COUNT
bool removeLayers(const QList< QgsMapLayer * > &layers)
Removes the specified layers from the individual layer configuration.
static QgsGeometry fromWkt(const QString &wkt)
Creates a new geometry from a WKT string.
QgsMapLayer * layer() const
Returns map layer or null if the layer does not exist anymore.
bool convertToOfflineProject(const QString &offlineDataPath, const QString &offlineDbFile, const QStringList &layerIds, bool onlySelected=false, ContainerType containerType=SpatiaLite)
Convert current project for offline editing.
QList< QgsRelation > referencedRelations(QgsVectorLayer *layer=nullptr) const
Gets all relations where this layer is the referenced part (i.e.
QString providerType() const
Returns the provider type for this layer.
void warning(const QString &title, const QString &message)
Emitted when a warning needs to be displayed.
Custom properties (by plugins for instance)
Definition: qgsmaplayer.h:155
QgsMapThemeCollection::MapThemeRecord mapThemeState(const QString &name) const
Returns the recorded state of a map theme.
QString asWkt(int precision=17) const
Exports the geometry to WKT.
QgsFeatureRequest & setFilterFids(const QgsFeatureIds &fids)
Sets feature IDs that should be fetched.
void insertChildNode(int index, QgsLayerTreeNode *node)
Insert existing node at specified position.
QMap< QgsFeatureId, QgsAttributeMap > QgsChangedAttributesMap
Definition: qgsfeature.h:557
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const override=0
Query the provider for features specified in request.
long featureCount() const override=0
Number of features in the layer.
QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
void synchronize()
Synchronize to remote layers.
This class manages a set of relations between layers.
void committedAttributeValuesChanges(const QString &layerId, const QgsChangedAttributesMap &changedAttributesValues)
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:411
std::unique_ptr< std::remove_pointer< OGRFieldDefnH >::type, OGRFldDeleter > ogr_field_def_unique_ptr
Scoped OGR field definition.
Definition: qgsogrutils.h:124
QgsAttributeMap toMap() const
Returns a QgsAttributeMap of the attribute values.
void setTitle(const QString &title)
Sets the project&#39;s title.
Definition: qgsproject.cpp:420
QList< QgsMapThemeCollection::MapThemeLayerRecord > layerRecords() const
Returns a list of records for all visible layer belonging to the theme.
bool addFeature(QgsFeature &feature, QgsFeatureSink::Flags flags=nullptr) FINAL
Adds a single feature to the sink.
QString authid() const
Returns the authority identifier for the CRS.
static QString displayString(Type type)
Returns a display string type for a WKB type, e.g., the geometry name used in WKT geometry representa...
void progressModeSet(QgsOfflineEditing::ProgressMode mode, int maximum)
Is emitted when the mode for the progress of the current operation is set.
void setItemVisibilityChecked(bool checked)
Check or uncheck a node (independently of its ancestors or children)
void removeMapLayers(const QStringList &layerIds)
Remove a set of layers from the registry by layer ID.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Query the layer for features specified in request.
QString name
Definition: qgsmaplayer.h:67
static bool hasM(Type type)
Tests whether a WKB type contains m values.
Definition: qgswkbtypes.h:956
virtual void exportNamedStyle(QDomDocument &doc, QString &errorMsg, const QgsReadWriteContext &context=QgsReadWriteContext(), QgsMapLayer::StyleCategories categories=QgsMapLayer::AllStyleCategories) const
Export the properties of this layer as named style in a QDomDocument.
bool changeAttributeValue(QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue=QVariant(), bool skipDefaultValues=false)
Changes an attribute value for a feature (but does not immediately commit the changes).
QString dataComment() const
Returns a description for this layer as defined in the data provider.
void removeRelation(const QString &id)
Remove a relation.
Container class that allows storage of map themes consisting of visible map layers and layer styles...
This is a container for configuration of the snapping of the project.
QgsVectorDataProvider * dataProvider() FINAL
Returns the layer&#39;s data provider.
QgsLayerTree * layerTreeRoot() const
Returns pointer to the root (invisible) node of the project&#39;s layer tree.
QList< int > QgsAttributeList
Definition: qgsfield.h:27
QList< QgsVectorLayerJoinInfo > QgsVectorJoinList
bool nextFeature(QgsFeature &f)
This is the base class for vector data providers.
std::unique_ptr< std::remove_pointer< OGRDataSourceH >::type, OGRDataSourceDeleter > ogr_datasource_unique_ptr
Scoped OGR data source.
Definition: qgsogrutils.h:114
Geometry is not required. It may still be returned if e.g. required for a filter condition.
bool isOfflineProject() const
Returns true if current project is offline.
Class for storing the component parts of a PostgreSQL/RDBMS datasource URI.
virtual void invalidateConnections(const QString &connection)
Invalidate connections corresponding to specified name.
A vector of attributes.
Definition: qgsattributes.h:57
virtual QString defaultValueClause(int fieldIndex) const
Returns any default value clauses which are present at the provider for a specified field index...
Represents a vector layer which manages a vector based data sets.
QgsLayerTreeLayer * findLayer(QgsMapLayer *layer) const
Find layer node representing the map layer.
bool addAttribute(const QgsField &field)
Add an attribute field (but does not commit it) returns true if the field was added.
QString table() const
Returns the table.
static Type flatType(Type type)
Returns the flat type for a WKB type.
Definition: qgswkbtypes.h:565
QList< QgsMapLayer * > addMapLayers(const QList< QgsMapLayer * > &mapLayers, bool addToLegend=true, bool takeOwnership=true)
Add a list of layers to the map of loaded layers.
QgsSnappingConfig snappingConfig
Definition: qgsproject.h:99
#define CUSTOM_PROPERTY_REMOTE_PROVIDER
void progressStarted()
The signal is emitted when the process has started.
QMap< QString, QgsMapLayer * > mapLayers() const
Returns a map of all registered layers by layer ID.
QgsLayerTreeLayer * clone() const override
Create a copy of the node. Returns new instance.
QVariant::Type type
Definition: qgsfield.h:55
void addLayerRecord(const QgsMapThemeCollection::MapThemeLayerRecord &record)
Add a new record for a layer.
QgsAttributes attributes
Definition: qgsfeature.h:65
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:70
void * OGRSpatialReferenceH
void layerWasAdded(QgsMapLayer *layer)
Emitted when a layer was added to the registry.
void setCustomProperty(const QString &key, const QVariant &value)
Sets a custom property for the node. Properties are stored in a map and saved in project file...
Layer tree node points to a map layer.
void addRelation(const QgsRelation &relation)
Add a relation.