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