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