QGIS API Documentation  2.4.0-Chugiak
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
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 <qgsmaplayer.h>
24 #include <qgsmaplayerregistry.h>
25 #include <qgsofflineediting.h>
26 #include <qgsproject.h>
27 #include <qgsvectordataprovider.h>
29 
30 #include <QDir>
31 #include <QDomDocument>
32 #include <QDomNode>
33 #include <QFile>
34 #include <QMessageBox>
35 
36 extern "C"
37 {
38 #include <sqlite3.h>
39 #include <spatialite.h>
40 }
41 
42 // TODO: DEBUG
43 #include <QDebug>
44 // END
45 
46 #define CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE "isOfflineEditable"
47 #define CUSTOM_PROPERTY_REMOTE_SOURCE "remoteSource"
48 #define CUSTOM_PROPERTY_REMOTE_PROVIDER "remoteProvider"
49 #define PROJECT_ENTRY_SCOPE_OFFLINE "OfflineEditingPlugin"
50 #define PROJECT_ENTRY_KEY_OFFLINE_DB_PATH "/OfflineDbPath"
51 
53 {
54  connect( QgsMapLayerRegistry::instance(), SIGNAL( layerWasAdded( QgsMapLayer* ) ), this, SLOT( layerAdded( QgsMapLayer* ) ) );
55 }
56 
58 {
59 }
60 
65 bool QgsOfflineEditing::convertToOfflineProject( const QString& offlineDataPath, const QString& offlineDbFile, const QStringList& layerIds )
66 {
67  if ( layerIds.isEmpty() )
68  {
69  return false;
70  }
71  QString dbPath = QDir( offlineDataPath ).absoluteFilePath( offlineDbFile );
72  if ( createSpatialiteDB( dbPath ) )
73  {
74  spatialite_init( 0 );
75  sqlite3* db;
76  int rc = sqlite3_open( dbPath.toUtf8().constData(), &db );
77  if ( rc != SQLITE_OK )
78  {
79  showWarning( tr( "Could not open the spatialite database" ) );
80  }
81  else
82  {
83  // create logging tables
84  createLoggingTables( db );
85 
86  emit progressStarted();
87 
88  // copy selected vector layers to SpatiaLite
89  for ( int i = 0; i < layerIds.count(); i++ )
90  {
91  emit layerProgressUpdated( i + 1, layerIds.count() );
92 
93  QgsMapLayer* layer = QgsMapLayerRegistry::instance()->mapLayer( layerIds.at( i ) );
94  copyVectorLayer( qobject_cast<QgsVectorLayer*>( layer ), db, dbPath );
95  }
96 
97  emit progressStopped();
98 
99  sqlite3_close( db );
100 
101  // save offline project
102  QString projectTitle = QgsProject::instance()->title();
103  if ( projectTitle.isEmpty() )
104  {
105  projectTitle = QFileInfo( QgsProject::instance()->fileName() ).fileName();
106  }
107  projectTitle += " (offline)";
108  QgsProject::instance()->title( projectTitle );
109 
111 
112  return true;
113  }
114  }
115 
116  return false;
117 
118  // Workflow:
119  // copy layers to spatialite
120  // create spatialite db at offlineDataPath
121  // create table for each layer
122  // add new spatialite layer
123  // copy features
124  // save as offline project
125  // mark offline layers
126  // remove remote layers
127  // mark as offline project
128 }
129 
131 {
133 }
134 
136 {
137  // open logging db
138  sqlite3* db = openLoggingDb();
139  if ( db == NULL )
140  {
141  return;
142  }
143 
144  emit progressStarted();
145 
146  // restore and sync remote layers
147  QList<QgsMapLayer*> offlineLayers;
148  QMap<QString, QgsMapLayer*> mapLayers = QgsMapLayerRegistry::instance()->mapLayers();
149  for ( QMap<QString, QgsMapLayer*>::iterator layer_it = mapLayers.begin() ; layer_it != mapLayers.end(); ++layer_it )
150  {
151  QgsMapLayer* layer = layer_it.value();
152  if ( layer->customProperty( CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE, false ).toBool() )
153  {
154  offlineLayers << layer;
155  }
156  }
157 
158  for ( int l = 0; l < offlineLayers.count(); l++ )
159  {
160  QgsMapLayer* layer = offlineLayers[l];
161 
162  emit layerProgressUpdated( l + 1, offlineLayers.count() );
163 
164  QString remoteSource = layer->customProperty( CUSTOM_PROPERTY_REMOTE_SOURCE, "" ).toString();
165  QString remoteProvider = layer->customProperty( CUSTOM_PROPERTY_REMOTE_PROVIDER, "" ).toString();
166  QString remoteName = layer->name();
167  remoteName.remove( QRegExp( " \\(offline\\)$" ) );
168 
169  QgsVectorLayer* remoteLayer = new QgsVectorLayer( remoteSource, remoteName, remoteProvider );
170  if ( remoteLayer->isValid() )
171  {
172  // TODO: only add remote layer if there are log entries?
173 
174  QgsVectorLayer* offlineLayer = qobject_cast<QgsVectorLayer*>( layer );
175 
176  // copy style
177  copySymbology( offlineLayer, remoteLayer );
178 
179  // register this layer with the central layers registry
181  QList<QgsMapLayer *>() << remoteLayer, true );
182 
183  // apply layer edit log
184  QString qgisLayerId = layer->id();
185  QString sql = QString( "SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
186  int layerId = sqlQueryInt( db, sql, -1 );
187  if ( layerId != -1 )
188  {
189  remoteLayer->startEditing();
190 
191  // TODO: only get commitNos of this layer?
192  int commitNo = getCommitNo( db );
193  for ( int i = 0; i < commitNo; i++ )
194  {
195  // apply commits chronologically
196  applyAttributesAdded( remoteLayer, db, layerId, i );
197  applyAttributeValueChanges( offlineLayer, remoteLayer, db, layerId, i );
198  applyGeometryChanges( remoteLayer, db, layerId, i );
199  }
200 
201  applyFeaturesAdded( offlineLayer, remoteLayer, db, layerId );
202  applyFeaturesRemoved( remoteLayer, db, layerId );
203 
204  if ( remoteLayer->commitChanges() )
205  {
206  // update fid lookup
207  updateFidLookup( remoteLayer, db, layerId );
208 
209  // clear edit log for this layer
210  sql = QString( "DELETE FROM 'log_added_attrs' WHERE \"layer_id\" = %1" ).arg( layerId );
211  sqlExec( db, sql );
212  sql = QString( "DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
213  sqlExec( db, sql );
214  sql = QString( "DELETE FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
215  sqlExec( db, sql );
216  sql = QString( "DELETE FROM 'log_feature_updates' WHERE \"layer_id\" = %1" ).arg( layerId );
217  sqlExec( db, sql );
218  sql = QString( "DELETE FROM 'log_geometry_updates' WHERE \"layer_id\" = %1" ).arg( layerId );
219  sqlExec( db, sql );
220 
221  // reset commitNo
222  QString sql = QString( "UPDATE 'log_indices' SET 'last_index' = 0 WHERE \"name\" = 'commit_no'" );
223  sqlExec( db, sql );
224  }
225  else
226  {
227  showWarning( remoteLayer->commitErrors().join( "\n" ) );
228  }
229  }
230 
231  // remove offline layer
233  ( QStringList() << qgisLayerId ) );
234 
235  // disable offline project
236  QString projectTitle = QgsProject::instance()->title();
237  projectTitle.remove( QRegExp( " \\(offline\\)$" ) );
238  QgsProject::instance()->title( projectTitle );
240  remoteLayer->reload(); //update with other changes
241  }
242  }
243 
244  emit progressStopped();
245 
246  sqlite3_close( db );
247 }
248 
250 {
251  // attempting to perform self-initialization for a newly created DB
252  if ( !sqlite_handle )
253  return;
254  // checking if this DB is really empty
255  char **results;
256  int rows, columns;
257  int ret = sqlite3_get_table( sqlite_handle, "select count(*) from sqlite_master", &results, &rows, &columns, NULL );
258  if ( ret != SQLITE_OK )
259  return;
260  int count = 0;
261  if ( rows >= 1 )
262  {
263  for ( int i = 1; i <= rows; i++ )
264  count = atoi( results[( i * columns ) + 0] );
265  }
266 
267  sqlite3_free_table( results );
268 
269  if ( count > 0 )
270  return;
271 
272  bool above41 = false;
273  ret = sqlite3_get_table( sqlite_handle, "select spatialite_version()", &results, &rows, &columns, NULL );
274  if ( ret == SQLITE_OK && rows == 1 && columns == 1 )
275  {
276  QString version = QString::fromUtf8( results[1] );
277  QStringList parts = version.split( " ", QString::SkipEmptyParts );
278  if ( parts.size() >= 1 )
279  {
280  QStringList verparts = parts[0].split( ".", QString::SkipEmptyParts );
281  above41 = verparts.size() >= 2 && ( verparts[0].toInt() > 4 || ( verparts[0].toInt() == 4 && verparts[1].toInt() >= 1 ) );
282  }
283  }
284 
285  sqlite3_free_table( results );
286 
287  // all right, it's empty: proceding to initialize
288  char *errMsg = 0;
289  ret = sqlite3_exec( sqlite_handle, above41 ? "SELECT InitSpatialMetadata(1)" : "SELECT InitSpatialMetadata()", NULL, NULL, &errMsg );
290 
291  if ( ret != SQLITE_OK )
292  {
293  QString errCause = tr( "Unable to initialize SpatialMetadata:\n" );
294  errCause += QString::fromUtf8( errMsg );
295  showWarning( errCause );
296  sqlite3_free( errMsg );
297  return;
298  }
299  spatial_ref_sys_init( sqlite_handle, 0 );
300 }
301 
302 bool QgsOfflineEditing::createSpatialiteDB( const QString& offlineDbPath )
303 {
304  int ret;
305  sqlite3 *sqlite_handle;
306  char *errMsg = NULL;
307  QFile newDb( offlineDbPath );
308  if ( newDb.exists() )
309  {
310  QFile::remove( offlineDbPath );
311  }
312 
313  // see also QgsNewSpatialiteLayerDialog::createDb()
314 
315  QFileInfo fullPath = QFileInfo( offlineDbPath );
316  QDir path = fullPath.dir();
317 
318  // Must be sure there is destination directory ~/.qgis
319  QDir().mkpath( path.absolutePath( ) );
320 
321  // creating/opening the new database
322  QString dbPath = newDb.fileName();
323  spatialite_init( 0 );
324  ret = sqlite3_open_v2( dbPath.toUtf8().constData(), &sqlite_handle, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL );
325  if ( ret )
326  {
327  // an error occurred
328  QString errCause = tr( "Could not create a new database\n" );
329  errCause += QString::fromUtf8( sqlite3_errmsg( sqlite_handle ) );
330  sqlite3_close( sqlite_handle );
331  showWarning( errCause );
332  return false;
333  }
334  // activating Foreign Key constraints
335  ret = sqlite3_exec( sqlite_handle, "PRAGMA foreign_keys = 1", NULL, 0, &errMsg );
336  if ( ret != SQLITE_OK )
337  {
338  showWarning( tr( "Unable to activate FOREIGN_KEY constraints" ) );
339  sqlite3_free( errMsg );
340  sqlite3_close( sqlite_handle );
341  return false;
342  }
343  initializeSpatialMetadata( sqlite_handle );
344 
345  // all done: closing the DB connection
346  sqlite3_close( sqlite_handle );
347 
348  return true;
349 }
350 
352 {
353  // indices
354  QString sql = "CREATE TABLE 'log_indices' ('name' TEXT, 'last_index' INTEGER)";
355  sqlExec( db, sql );
356 
357  sql = "INSERT INTO 'log_indices' VALUES ('commit_no', 0)";
358  sqlExec( db, sql );
359 
360  sql = "INSERT INTO 'log_indices' VALUES ('layer_id', 0)";
361  sqlExec( db, sql );
362 
363  // layername <-> layer id
364  sql = "CREATE TABLE 'log_layer_ids' ('id' INTEGER, 'qgis_id' TEXT)";
365  sqlExec( db, sql );
366 
367  // offline fid <-> remote fid
368  sql = "CREATE TABLE 'log_fids' ('layer_id' INTEGER, 'offline_fid' INTEGER, 'remote_fid' INTEGER)";
369  sqlExec( db, sql );
370 
371  // added attributes
372  sql = "CREATE TABLE 'log_added_attrs' ('layer_id' INTEGER, 'commit_no' INTEGER, ";
373  sql += "'name' TEXT, 'type' INTEGER, 'length' INTEGER, 'precision' INTEGER, 'comment' TEXT)";
374  sqlExec( db, sql );
375 
376  // added features
377  sql = "CREATE TABLE 'log_added_features' ('layer_id' INTEGER, 'fid' INTEGER)";
378  sqlExec( db, sql );
379 
380  // removed features
381  sql = "CREATE TABLE 'log_removed_features' ('layer_id' INTEGER, 'fid' INTEGER)";
382  sqlExec( db, sql );
383 
384  // feature updates
385  sql = "CREATE TABLE 'log_feature_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'attr' INTEGER, 'value' TEXT)";
386  sqlExec( db, sql );
387 
388  // geometry updates
389  sql = "CREATE TABLE 'log_geometry_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'geom_wkt' TEXT)";
390  sqlExec( db, sql );
391 
392  /* TODO: other logging tables
393  - attr delete (not supported by SpatiaLite provider)
394  */
395 }
396 
397 void QgsOfflineEditing::copyVectorLayer( QgsVectorLayer* layer, sqlite3* db, const QString& offlineDbPath )
398 {
399  if ( layer == NULL )
400  {
401  return;
402  }
403 
404  QString tableName = layer->name();
405 
406  // create table
407  QString sql = QString( "CREATE TABLE '%1' (" ).arg( tableName );
408  QString delim = "";
409  const QgsFields& fields = layer->dataProvider()->fields();
410  for ( int idx = 0; idx < fields.count(); ++idx )
411  {
412  QString dataType = "";
413  QVariant::Type type = fields[idx].type();
414  if ( type == QVariant::Int || type == QVariant::LongLong )
415  {
416  dataType = "INTEGER";
417  }
418  else if ( type == QVariant::Double )
419  {
420  dataType = "REAL";
421  }
422  else if ( type == QVariant::String )
423  {
424  dataType = "TEXT";
425  }
426  else
427  {
428  showWarning( tr( "Unknown data type %1" ).arg( type ) );
429  }
430 
431  sql += delim + QString( "'%1' %2" ).arg( fields[idx].name() ).arg( dataType );
432  delim = ",";
433  }
434  sql += ")";
435 
436  // add geometry column
437  QString geomType = "";
438  switch ( layer->wkbType() )
439  {
440  case QGis::WKBPoint:
441  geomType = "POINT";
442  break;
443  case QGis::WKBMultiPoint:
444  geomType = "MULTIPOINT";
445  break;
446  case QGis::WKBLineString:
447  geomType = "LINESTRING";
448  break;
450  geomType = "MULTILINESTRING";
451  break;
452  case QGis::WKBPolygon:
453  geomType = "POLYGON";
454  break;
456  geomType = "MULTIPOLYGON";
457  break;
458  default:
459  showWarning( tr( "QGIS wkbType %1 not supported" ).arg( layer->wkbType() ) );
460  break;
461  };
462  QString sqlAddGeom = QString( "SELECT AddGeometryColumn('%1', 'Geometry', %2, '%3', 2)" )
463  .arg( tableName )
464  .arg( layer->crs().authid().startsWith( "EPSG:", Qt::CaseInsensitive ) ? layer->crs().authid().mid( 5 ).toLong() : 0 )
465  .arg( geomType );
466 
467  // create spatial index
468  QString sqlCreateIndex = QString( "SELECT CreateSpatialIndex('%1', 'Geometry')" ).arg( tableName );
469 
470  int rc = sqlExec( db, sql );
471  if ( rc == SQLITE_OK )
472  {
473  rc = sqlExec( db, sqlAddGeom );
474  if ( rc == SQLITE_OK )
475  {
476  rc = sqlExec( db, sqlCreateIndex );
477  }
478  }
479 
480  if ( rc == SQLITE_OK )
481  {
482  // add new layer
483  QgsVectorLayer* newLayer = new QgsVectorLayer( QString( "dbname='%1' table='%2'(Geometry) sql=" )
484  .arg( offlineDbPath ).arg( tableName ), tableName + " (offline)", "spatialite" );
485  if ( newLayer->isValid() )
486  {
487  // mark as offline layer
489 
490  // store original layer source
493 
494  // copy style
495  bool hasLabels = layer->hasLabelsEnabled();
496  if ( !hasLabels )
497  {
498  // NOTE: copy symbology before adding the layer so it is displayed correctly
499  copySymbology( layer, newLayer );
500  }
501 
502  // register this layer with the central layers registry
504  QList<QgsMapLayer *>() << newLayer );
505 
506  if ( hasLabels )
507  {
508  // NOTE: copy symbology of layers with labels enabled after adding to project, as it will crash otherwise (WORKAROUND)
509  copySymbology( layer, newLayer );
510  }
511 
512  // TODO: layer order
513 
514  // copy features
515  newLayer->startEditing();
516  QgsFeature f;
517 
518  // NOTE: force feature recount for PostGIS layer, else only visible features are counted, before iterating over all features (WORKAROUND)
519  layer->setSubsetString( "" );
520 
521  QgsFeatureIterator fit = layer->getFeatures();
522 
524  int featureCount = 1;
525 
526  QList<QgsFeatureId> remoteFeatureIds;
527  while ( fit.nextFeature( f ) )
528  {
529  remoteFeatureIds << f.id();
530 
531  // NOTE: Spatialite provider ignores position of geometry column
532  // fill gap in QgsAttributeMap if geometry column is not last (WORKAROUND)
533  int column = 0;
534  QgsAttributes attrs = f.attributes();
535  QgsAttributes newAttrs( attrs.count() );
536  for ( int it = 0; it < attrs.count(); ++it )
537  {
538  newAttrs[column++] = attrs[it];
539  }
540  f.setAttributes( newAttrs );
541 
542  newLayer->addFeature( f, false );
543 
544  emit progressUpdated( featureCount++ );
545  }
546  if ( newLayer->commitChanges() )
547  {
549  featureCount = 1;
550 
551  // update feature id lookup
552  int layerId = getOrCreateLayerId( db, newLayer->id() );
553  QList<QgsFeatureId> offlineFeatureIds;
554 
555  QgsFeatureIterator fit = newLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( QgsAttributeList() ) );
556  while ( fit.nextFeature( f ) )
557  {
558  offlineFeatureIds << f.id();
559  }
560 
561  // NOTE: insert fids in this loop, as the db is locked during newLayer->nextFeature()
562  sqlExec( db, "BEGIN" );
563  int remoteCount = remoteFeatureIds.size();
564  for ( int i = 0; i < remoteCount; i++ )
565  {
566  addFidLookup( db, layerId, offlineFeatureIds.at( i ), remoteFeatureIds.at( remoteCount - ( i + 1 ) ) );
567  emit progressUpdated( featureCount++ );
568  }
569  sqlExec( db, "COMMIT" );
570  }
571  else
572  {
573  showWarning( newLayer->commitErrors().join( "\n" ) );
574  }
575 
576  // remove remote layer
578  QStringList() << layer->id() );
579  }
580  }
581 }
582 
583 void QgsOfflineEditing::applyAttributesAdded( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId, int commitNo )
584 {
585  QString sql = QString( "SELECT \"name\", \"type\", \"length\", \"precision\", \"comment\" FROM 'log_added_attrs' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo );
586  QList<QgsField> fields = sqlQueryAttributesAdded( db, sql );
587 
588  const QgsVectorDataProvider* provider = remoteLayer->dataProvider();
589  QList<QgsVectorDataProvider::NativeType> nativeTypes = provider->nativeTypes();
590 
591  // NOTE: uses last matching QVariant::Type of nativeTypes
592  QMap < QVariant::Type, QString /*typeName*/ > typeNameLookup;
593  for ( int i = 0; i < nativeTypes.size(); i++ )
594  {
595  QgsVectorDataProvider::NativeType nativeType = nativeTypes.at( i );
596  typeNameLookup[ nativeType.mType ] = nativeType.mTypeName;
597  }
598 
599  emit progressModeSet( QgsOfflineEditing::AddFields, fields.size() );
600 
601  for ( int i = 0; i < fields.size(); i++ )
602  {
603  // lookup typename from layer provider
604  QgsField field = fields[i];
605  if ( typeNameLookup.contains( field.type() ) )
606  {
607  QString typeName = typeNameLookup[ field.type()];
608  field.setTypeName( typeName );
609  remoteLayer->addAttribute( field );
610  }
611  else
612  {
613  showWarning( QString( "Could not add attribute '%1' of type %2" ).arg( field.name() ).arg( field.type() ) );
614  }
615 
616  emit progressUpdated( i + 1 );
617  }
618 }
619 
620 void QgsOfflineEditing::applyFeaturesAdded( QgsVectorLayer* offlineLayer, QgsVectorLayer* remoteLayer, sqlite3* db, int layerId )
621 {
622  QString sql = QString( "SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
623  QList<int> newFeatureIds = sqlQueryInts( db, sql );
624 
625  // get default value for each field
626  const QgsFields& remoteFlds = remoteLayer->pendingFields();
627  QVector<QVariant> defaultValues( remoteFlds.count() );
628  for ( int i = 0; i < remoteFlds.count(); ++i )
629  {
630  if ( remoteFlds.fieldOrigin( i ) == QgsFields::OriginProvider )
631  defaultValues[i] = remoteLayer->dataProvider()->defaultValue( remoteFlds.fieldOriginIndex( i ) );
632  }
633 
634  // get new features from offline layer
635  QgsFeatureList features;
636  for ( int i = 0; i < newFeatureIds.size(); i++ )
637  {
638  QgsFeature feature;
639  if ( offlineLayer->getFeatures( QgsFeatureRequest().setFilterFid( newFeatureIds.at( i ) ) ).nextFeature( feature ) )
640  {
641  features << feature;
642  }
643  }
644 
645  // copy features to remote layer
646  emit progressModeSet( QgsOfflineEditing::AddFeatures, features.size() );
647 
648  int i = 1;
649  int newAttrsCount = remoteLayer->pendingFields().count();
650  for ( QgsFeatureList::iterator it = features.begin(); it != features.end(); ++it )
651  {
652  QgsFeature f = *it;
653 
654  // NOTE: Spatialite provider ignores position of geometry column
655  // restore gap in QgsAttributeMap if geometry column is not last (WORKAROUND)
656  QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
657  QgsAttributes newAttrs( newAttrsCount );
658  QgsAttributes attrs = f.attributes();
659  for ( int it = 0; it < attrs.count(); ++it )
660  {
661  newAttrs[ attrLookup[ it ] ] = attrs[ it ];
662  }
663 
664  // try to use default value from the provider
665  // (important especially e.g. for postgis primary key generated from a sequence)
666  for ( int k = 0; k < newAttrs.count(); ++k )
667  {
668  if ( newAttrs[k].isNull() && !defaultValues[k].isNull() )
669  newAttrs[k] = defaultValues[k];
670  }
671 
672  f.setAttributes( newAttrs );
673 
674  remoteLayer->addFeature( f, false );
675 
676  emit progressUpdated( i++ );
677  }
678 }
679 
680 void QgsOfflineEditing::applyFeaturesRemoved( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId )
681 {
682  QString sql = QString( "SELECT \"fid\" FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
683  QgsFeatureIds values = sqlQueryFeaturesRemoved( db, sql );
684 
685  emit progressModeSet( QgsOfflineEditing::RemoveFeatures, values.size() );
686 
687  int i = 1;
688  for ( QgsFeatureIds::const_iterator it = values.begin(); it != values.end(); ++it )
689  {
690  QgsFeatureId fid = remoteFid( db, layerId, *it );
691  remoteLayer->deleteFeature( fid );
692 
693  emit progressUpdated( i++ );
694  }
695 }
696 
697 void QgsOfflineEditing::applyAttributeValueChanges( QgsVectorLayer* offlineLayer, QgsVectorLayer* remoteLayer, sqlite3* db, int layerId, int commitNo )
698 {
699  QString sql = QString( "SELECT \"fid\", \"attr\", \"value\" FROM 'log_feature_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2 " ).arg( layerId ).arg( commitNo );
701 
702  emit progressModeSet( QgsOfflineEditing::UpdateFeatures, values.size() );
703 
704  QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
705 
706  for ( int i = 0; i < values.size(); i++ )
707  {
708  QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid );
709 
710  remoteLayer->changeAttributeValue( fid, attrLookup[ values.at( i ).attr ], values.at( i ).value );
711 
712  emit progressUpdated( i + 1 );
713  }
714 }
715 
716 void QgsOfflineEditing::applyGeometryChanges( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId, int commitNo )
717 {
718  QString sql = QString( "SELECT \"fid\", \"geom_wkt\" FROM 'log_geometry_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo );
719  GeometryChanges values = sqlQueryGeometryChanges( db, sql );
720 
722 
723  for ( int i = 0; i < values.size(); i++ )
724  {
725  QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid );
726  remoteLayer->changeGeometry( fid, QgsGeometry::fromWkt( values.at( i ).geom_wkt ) );
727 
728  emit progressUpdated( i + 1 );
729  }
730 }
731 
732 void QgsOfflineEditing::updateFidLookup( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId )
733 {
734  // update fid lookup for added features
735 
736  // get remote added fids
737  // NOTE: use QMap for sorted fids
738  QMap < QgsFeatureId, bool /*dummy*/ > newRemoteFids;
739  QgsFeature f;
740 
741  QgsFeatureIterator fit = remoteLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( QgsAttributeList() ) );
742 
744 
745  int i = 1;
746  while ( fit.nextFeature( f ) )
747  {
748  if ( offlineFid( db, layerId, f.id() ) == -1 )
749  {
750  newRemoteFids[ f.id()] = true;
751  }
752 
753  emit progressUpdated( i++ );
754  }
755 
756  // get local added fids
757  // NOTE: fids are sorted
758  QString sql = QString( "SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
759  QList<int> newOfflineFids = sqlQueryInts( db, sql );
760 
761  if ( newRemoteFids.size() != newOfflineFids.size() )
762  {
763  //showWarning( QString( "Different number of new features on offline layer (%1) and remote layer (%2)" ).arg(newOfflineFids.size()).arg(newRemoteFids.size()) );
764  }
765  else
766  {
767  // add new fid lookups
768  i = 0;
769  sqlExec( db, "BEGIN" );
770  for ( QMap<QgsFeatureId, bool>::const_iterator it = newRemoteFids.begin(); it != newRemoteFids.end(); ++it )
771  {
772  addFidLookup( db, layerId, newOfflineFids.at( i++ ), it.key() );
773  }
774  sqlExec( db, "COMMIT" );
775  }
776 }
777 
778 void QgsOfflineEditing::copySymbology( const QgsVectorLayer* sourceLayer, QgsVectorLayer* targetLayer )
779 {
780  QString error;
781  QDomDocument doc;
782  QDomElement node = doc.createElement( "symbology" );
783  doc.appendChild( node );
784  sourceLayer->writeSymbology( node, doc, error );
785 
786  if ( error.isEmpty() )
787  {
788  targetLayer->readSymbology( node, error );
789  }
790  if ( !error.isEmpty() )
791  {
792  showWarning( error );
793  }
794 }
795 
796 // NOTE: use this to map column indices in case the remote geometry column is not last
797 QMap<int, int> QgsOfflineEditing::attributeLookup( QgsVectorLayer* offlineLayer, QgsVectorLayer* remoteLayer )
798 {
799  const QgsAttributeList& offlineAttrs = offlineLayer->pendingAllAttributesList();
800  const QgsAttributeList& remoteAttrs = remoteLayer->pendingAllAttributesList();
801 
802  QMap < int /*offline attr*/, int /*remote attr*/ > attrLookup;
803  // NOTE: use size of remoteAttrs, as offlineAttrs can have new attributes not yet synced
804  for ( int i = 0; i < remoteAttrs.size(); i++ )
805  {
806  attrLookup.insert( offlineAttrs.at( i ), remoteAttrs.at( i ) );
807  }
808 
809  return attrLookup;
810 }
811 
812 void QgsOfflineEditing::showWarning( const QString& message )
813 {
814  QMessageBox::warning( NULL, tr( "Offline Editing Plugin" ), message );
815 }
816 
818 {
819  sqlite3* db = NULL;
821  if ( !dbPath.isEmpty() )
822  {
823  int rc = sqlite3_open( dbPath.toUtf8().constData(), &db );
824  if ( rc != SQLITE_OK )
825  {
826  showWarning( tr( "Could not open the spatialite logging database" ) );
827  sqlite3_close( db );
828  db = NULL;
829  }
830  }
831  return db;
832 }
833 
834 int QgsOfflineEditing::getOrCreateLayerId( sqlite3* db, const QString& qgisLayerId )
835 {
836  QString sql = QString( "SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
837  int layerId = sqlQueryInt( db, sql, -1 );
838  if ( layerId == -1 )
839  {
840  // next layer id
841  sql = "SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'layer_id'";
842  int newLayerId = sqlQueryInt( db, sql, -1 );
843 
844  // insert layer
845  sql = QString( "INSERT INTO 'log_layer_ids' VALUES (%1, '%2')" ).arg( newLayerId ).arg( qgisLayerId );
846  sqlExec( db, sql );
847 
848  // increase layer_id
849  // TODO: use trigger for auto increment?
850  sql = QString( "UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'layer_id'" ).arg( newLayerId + 1 );
851  sqlExec( db, sql );
852 
853  layerId = newLayerId;
854  }
855 
856  return layerId;
857 }
858 
860 {
861  QString sql = "SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'commit_no'";
862  return sqlQueryInt( db, sql, -1 );
863 }
864 
866 {
867  QString sql = QString( "UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'commit_no'" ).arg( getCommitNo( db ) + 1 );
868  sqlExec( db, sql );
869 }
870 
871 void QgsOfflineEditing::addFidLookup( sqlite3* db, int layerId, QgsFeatureId offlineFid, QgsFeatureId remoteFid )
872 {
873  QString sql = QString( "INSERT INTO 'log_fids' VALUES ( %1, %2, %3 )" ).arg( layerId ).arg( offlineFid ).arg( remoteFid );
874  sqlExec( db, sql );
875 }
876 
878 {
879  QString sql = QString( "SELECT \"remote_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2" ).arg( layerId ).arg( offlineFid );
880  return sqlQueryInt( db, sql, -1 );
881 }
882 
884 {
885  QString sql = QString( "SELECT \"offline_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"remote_fid\" = %2" ).arg( layerId ).arg( remoteFid );
886  return sqlQueryInt( db, sql, -1 );
887 }
888 
890 {
891  QString sql = QString( "SELECT COUNT(\"fid\") FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( fid );
892  return ( sqlQueryInt( db, sql, 0 ) > 0 );
893 }
894 
895 int QgsOfflineEditing::sqlExec( sqlite3* db, const QString& sql )
896 {
897  char * errmsg;
898  int rc = sqlite3_exec( db, sql.toUtf8(), NULL, NULL, &errmsg );
899  if ( rc != SQLITE_OK )
900  {
901  showWarning( errmsg );
902  }
903  return rc;
904 }
905 
906 int QgsOfflineEditing::sqlQueryInt( sqlite3* db, const QString& sql, int defaultValue )
907 {
908  sqlite3_stmt* stmt = NULL;
909  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
910  {
911  showWarning( sqlite3_errmsg( db ) );
912  return defaultValue;
913  }
914 
915  int value = defaultValue;
916  int ret = sqlite3_step( stmt );
917  if ( ret == SQLITE_ROW )
918  {
919  value = sqlite3_column_int( stmt, 0 );
920  }
921  sqlite3_finalize( stmt );
922 
923  return value;
924 }
925 
926 QList<int> QgsOfflineEditing::sqlQueryInts( sqlite3* db, const QString& sql )
927 {
928  QList<int> values;
929 
930  sqlite3_stmt* stmt = NULL;
931  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
932  {
933  showWarning( sqlite3_errmsg( db ) );
934  return values;
935  }
936 
937  int ret = sqlite3_step( stmt );
938  while ( ret == SQLITE_ROW )
939  {
940  values << sqlite3_column_int( stmt, 0 );
941 
942  ret = sqlite3_step( stmt );
943  }
944  sqlite3_finalize( stmt );
945 
946  return values;
947 }
948 
949 QList<QgsField> QgsOfflineEditing::sqlQueryAttributesAdded( sqlite3* db, const QString& sql )
950 {
951  QList<QgsField> values;
952 
953  sqlite3_stmt* stmt = NULL;
954  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
955  {
956  showWarning( sqlite3_errmsg( db ) );
957  return values;
958  }
959 
960  int ret = sqlite3_step( stmt );
961  while ( ret == SQLITE_ROW )
962  {
963  QgsField field( QString(( const char* )sqlite3_column_text( stmt, 0 ) ),
964  ( QVariant::Type )sqlite3_column_int( stmt, 1 ),
965  "", // typeName
966  sqlite3_column_int( stmt, 2 ),
967  sqlite3_column_int( stmt, 3 ),
968  QString(( const char* )sqlite3_column_text( stmt, 4 ) ) );
969  values << field;
970 
971  ret = sqlite3_step( stmt );
972  }
973  sqlite3_finalize( stmt );
974 
975  return values;
976 }
977 
979 {
980  QgsFeatureIds values;
981 
982  sqlite3_stmt* stmt = NULL;
983  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
984  {
985  showWarning( sqlite3_errmsg( db ) );
986  return values;
987  }
988 
989  int ret = sqlite3_step( stmt );
990  while ( ret == SQLITE_ROW )
991  {
992  values << sqlite3_column_int( stmt, 0 );
993 
994  ret = sqlite3_step( stmt );
995  }
996  sqlite3_finalize( stmt );
997 
998  return values;
999 }
1000 
1002 {
1003  AttributeValueChanges values;
1004 
1005  sqlite3_stmt* stmt = NULL;
1006  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
1007  {
1008  showWarning( sqlite3_errmsg( db ) );
1009  return values;
1010  }
1011 
1012  int ret = sqlite3_step( stmt );
1013  while ( ret == SQLITE_ROW )
1014  {
1015  AttributeValueChange change;
1016  change.fid = sqlite3_column_int( stmt, 0 );
1017  change.attr = sqlite3_column_int( stmt, 1 );
1018  change.value = QString(( const char* )sqlite3_column_text( stmt, 2 ) );
1019  values << change;
1020 
1021  ret = sqlite3_step( stmt );
1022  }
1023  sqlite3_finalize( stmt );
1024 
1025  return values;
1026 }
1027 
1029 {
1030  GeometryChanges values;
1031 
1032  sqlite3_stmt* stmt = NULL;
1033  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
1034  {
1035  showWarning( sqlite3_errmsg( db ) );
1036  return values;
1037  }
1038 
1039  int ret = sqlite3_step( stmt );
1040  while ( ret == SQLITE_ROW )
1041  {
1042  GeometryChange change;
1043  change.fid = sqlite3_column_int( stmt, 0 );
1044  change.geom_wkt = QString(( const char* )sqlite3_column_text( stmt, 1 ) );
1045  values << change;
1046 
1047  ret = sqlite3_step( stmt );
1048  }
1049  sqlite3_finalize( stmt );
1050 
1051  return values;
1052 }
1053 
1054 void QgsOfflineEditing::committedAttributesAdded( const QString& qgisLayerId, const QList<QgsField>& addedAttributes )
1055 {
1056  sqlite3* db = openLoggingDb();
1057  if ( db == NULL )
1058  {
1059  return;
1060  }
1061 
1062  // insert log
1063  int layerId = getOrCreateLayerId( db, qgisLayerId );
1064  int commitNo = getCommitNo( db );
1065 
1066  for ( QList<QgsField>::const_iterator it = addedAttributes.begin(); it != addedAttributes.end(); ++it )
1067  {
1068  QgsField field = *it;
1069  QString sql = QString( "INSERT INTO 'log_added_attrs' VALUES ( %1, %2, '%3', %4, %5, %6, '%7' )" )
1070  .arg( layerId )
1071  .arg( commitNo )
1072  .arg( field.name() )
1073  .arg( field.type() )
1074  .arg( field.length() )
1075  .arg( field.precision() )
1076  .arg( field.comment() );
1077  sqlExec( db, sql );
1078  }
1079 
1080  increaseCommitNo( db );
1081  sqlite3_close( db );
1082 }
1083 
1084 void QgsOfflineEditing::committedFeaturesAdded( const QString& qgisLayerId, const QgsFeatureList& addedFeatures )
1085 {
1086  sqlite3* db = openLoggingDb();
1087  if ( db == NULL )
1088  {
1089  return;
1090  }
1091 
1092  // insert log
1093  int layerId = getOrCreateLayerId( db, qgisLayerId );
1094 
1095  // get new feature ids from db
1096  QgsMapLayer* layer = QgsMapLayerRegistry::instance()->mapLayer( qgisLayerId );
1097  QgsDataSourceURI uri = QgsDataSourceURI( layer->source() );
1098 
1099  // only store feature ids
1100  QString sql = QString( "SELECT ROWID FROM '%1' ORDER BY ROWID DESC LIMIT %2" ).arg( uri.table() ).arg( addedFeatures.size() );
1101  QList<int> newFeatureIds = sqlQueryInts( db, sql );
1102  for ( int i = newFeatureIds.size() - 1; i >= 0; i-- )
1103  {
1104  QString sql = QString( "INSERT INTO 'log_added_features' VALUES ( %1, %2 )" )
1105  .arg( layerId )
1106  .arg( newFeatureIds.at( i ) );
1107  sqlExec( db, sql );
1108  }
1109 
1110  sqlite3_close( db );
1111 }
1112 
1113 void QgsOfflineEditing::committedFeaturesRemoved( const QString& qgisLayerId, const QgsFeatureIds& deletedFeatureIds )
1114 {
1115  sqlite3* db = openLoggingDb();
1116  if ( db == NULL )
1117  {
1118  return;
1119  }
1120 
1121  // insert log
1122  int layerId = getOrCreateLayerId( db, qgisLayerId );
1123 
1124  for ( QgsFeatureIds::const_iterator it = deletedFeatureIds.begin(); it != deletedFeatureIds.end(); ++it )
1125  {
1126  if ( isAddedFeature( db, layerId, *it ) )
1127  {
1128  // remove from added features log
1129  QString sql = QString( "DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( *it );
1130  sqlExec( db, sql );
1131  }
1132  else
1133  {
1134  QString sql = QString( "INSERT INTO 'log_removed_features' VALUES ( %1, %2)" )
1135  .arg( layerId )
1136  .arg( *it );
1137  sqlExec( db, sql );
1138  }
1139  }
1140 
1141  sqlite3_close( db );
1142 }
1143 
1144 void QgsOfflineEditing::committedAttributeValuesChanges( const QString& qgisLayerId, const QgsChangedAttributesMap& changedAttrsMap )
1145 {
1146  sqlite3* db = openLoggingDb();
1147  if ( db == NULL )
1148  {
1149  return;
1150  }
1151 
1152  // insert log
1153  int layerId = getOrCreateLayerId( db, qgisLayerId );
1154  int commitNo = getCommitNo( db );
1155 
1156  for ( QgsChangedAttributesMap::const_iterator cit = changedAttrsMap.begin(); cit != changedAttrsMap.end(); ++cit )
1157  {
1158  QgsFeatureId fid = cit.key();
1159  if ( isAddedFeature( db, layerId, fid ) )
1160  {
1161  // skip added features
1162  continue;
1163  }
1164  QgsAttributeMap attrMap = cit.value();
1165  for ( QgsAttributeMap::const_iterator it = attrMap.begin(); it != attrMap.end(); ++it )
1166  {
1167  QString sql = QString( "INSERT INTO 'log_feature_updates' VALUES ( %1, %2, %3, %4, '%5' )" )
1168  .arg( layerId )
1169  .arg( commitNo )
1170  .arg( fid )
1171  .arg( it.key() ) // attr
1172  .arg( it.value().toString() ); // value
1173  sqlExec( db, sql );
1174  }
1175  }
1176 
1177  increaseCommitNo( db );
1178  sqlite3_close( db );
1179 }
1180 
1181 void QgsOfflineEditing::committedGeometriesChanges( const QString& qgisLayerId, const QgsGeometryMap& changedGeometries )
1182 {
1183  sqlite3* db = openLoggingDb();
1184  if ( db == NULL )
1185  {
1186  return;
1187  }
1188 
1189  // insert log
1190  int layerId = getOrCreateLayerId( db, qgisLayerId );
1191  int commitNo = getCommitNo( db );
1192 
1193  for ( QgsGeometryMap::const_iterator it = changedGeometries.begin(); it != changedGeometries.end(); ++it )
1194  {
1195  QgsFeatureId fid = it.key();
1196  if ( isAddedFeature( db, layerId, fid ) )
1197  {
1198  // skip added features
1199  continue;
1200  }
1201  QgsGeometry geom = it.value();
1202  QString sql = QString( "INSERT INTO 'log_geometry_updates' VALUES ( %1, %2, %3, '%4' )" )
1203  .arg( layerId )
1204  .arg( commitNo )
1205  .arg( fid )
1206  .arg( geom.exportToWkt() );
1207  sqlExec( db, sql );
1208 
1209  // TODO: use WKB instead of WKT?
1210  }
1211 
1212  increaseCommitNo( db );
1213  sqlite3_close( db );
1214 }
1215 
1217 {
1218  QgsVectorLayer* vLayer = qobject_cast<QgsVectorLayer *>( sender() );
1219  // enable logging
1220  connect( vLayer->editBuffer(), SIGNAL( committedAttributesAdded( const QString&, const QList<QgsField>& ) ),
1221  this, SLOT( committedAttributesAdded( const QString&, const QList<QgsField>& ) ) );
1222  connect( vLayer, SIGNAL( committedFeaturesAdded( const QString&, const QgsFeatureList& ) ),
1223  this, SLOT( committedFeaturesAdded( const QString&, const QgsFeatureList& ) ) );
1224  connect( vLayer, SIGNAL( committedFeaturesRemoved( const QString&, const QgsFeatureIds& ) ),
1225  this, SLOT( committedFeaturesRemoved( const QString&, const QgsFeatureIds& ) ) );
1226  connect( vLayer->editBuffer(), SIGNAL( committedAttributeValuesChanges( const QString&, const QgsChangedAttributesMap& ) ),
1227  this, SLOT( committedAttributeValuesChanges( const QString&, const QgsChangedAttributesMap& ) ) );
1228  connect( vLayer->editBuffer(), SIGNAL( committedGeometriesChanges( const QString&, const QgsGeometryMap& ) ),
1229  this, SLOT( committedGeometriesChanges( const QString&, const QgsGeometryMap& ) ) );
1230 }
1231 
1233 {
1234  QgsVectorLayer* vLayer = qobject_cast<QgsVectorLayer *>( sender() );
1235  // disable logging
1236  disconnect( vLayer->editBuffer(), SIGNAL( committedAttributesAdded( const QString&, const QList<QgsField>& ) ),
1237  this, SLOT( committedAttributesAdded( const QString&, const QList<QgsField>& ) ) );
1238  disconnect( vLayer, SIGNAL( committedFeaturesAdded( const QString&, const QgsFeatureList& ) ),
1239  this, SLOT( committedFeaturesAdded( const QString&, const QgsFeatureList& ) ) );
1240  disconnect( vLayer, SIGNAL( committedFeaturesRemoved( const QString&, const QgsFeatureIds& ) ),
1241  this, SLOT( committedFeaturesRemoved( const QString&, const QgsFeatureIds& ) ) );
1242  disconnect( vLayer->editBuffer(), SIGNAL( committedAttributeValuesChanges( const QString&, const QgsChangedAttributesMap& ) ),
1243  this, SLOT( committedAttributeValuesChanges( const QString&, const QgsChangedAttributesMap& ) ) );
1244  disconnect( vLayer->editBuffer(), SIGNAL( committedGeometriesChanges( const QString&, const QgsGeometryMap& ) ),
1245  this, SLOT( committedGeometriesChanges( const QString&, const QgsGeometryMap& ) ) );
1246 }
1247 
1249 {
1250  // detect offline layer
1251  if ( layer->customProperty( CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE, false ).toBool() )
1252  {
1253  QgsVectorLayer* vLayer = qobject_cast<QgsVectorLayer *>( layer );
1254  connect( vLayer, SIGNAL( editingStarted() ), this, SLOT( startListenFeatureChanges() ) );
1255  connect( vLayer, SIGNAL( editingStopped() ), this, SLOT( stopListenFeatureChanges() ) );
1256  }
1257 }
1258 
1259 
QgsFeatureId id() const
Get the feature id for this feature.
Definition: qgsfeature.cpp:100
const QString & name() const
Gets the name of the field.
Definition: qgsfield.cpp:58
void increaseCommitNo(sqlite3 *db)
Wrapper for iterator of features from vector data provider or vector layer.
QMap< QgsFeatureId, QgsGeometry > QgsGeometryMap
Definition: qgsfeature.h:323
void layerProgressUpdated(int layer, int numLayers)
emit a signal that the next layer of numLayers has started processing
Base class for all map layer types.
Definition: qgsmaplayer.h:47
bool createSpatialiteDB(const QString &offlineDbPath)
QMap< int, QVariant > QgsAttributeMap
Definition: qgsfeature.h:98
#define PROJECT_ENTRY_KEY_OFFLINE_DB_PATH
bool convertToOfflineProject(const QString &offlineDataPath, const QString &offlineDbFile, const QStringList &layerIds)
convert current project for offline editing
void setTypeName(const QString &typ)
Set the field type.
Definition: qgsfield.cpp:98
bool isAddedFeature(sqlite3 *db, int layerId, QgsFeatureId fid)
void updateFidLookup(QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId)
bool deleteFeature(QgsFeatureId fid)
delete a feature from the layer (but does not commit it)
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeature.h:325
QList< QgsFeature > QgsFeatureList
Definition: qgsfeature.h:330
#define CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest())
Query the provider for features specified in request.
bool commitChanges()
Attempts to commit any changes to disk.
void addFidLookup(sqlite3 *db, int layerId, QgsFeatureId offlineFid, QgsFeatureId remoteFid)
bool startEditing()
Make layer editable.
void setCustomProperty(const QString &key, const QVariant &value)
Set a custom property for layer.
AttributeValueChanges sqlQueryAttributeValueChanges(sqlite3 *db, const QString &sql)
void committedGeometriesChanges(const QString &qgisLayerId, const QgsGeometryMap &changedGeometries)
#define CUSTOM_PROPERTY_REMOTE_SOURCE
int precision() const
Gets the precision of the field.
Definition: qgsfield.cpp:78
GeometryChanges sqlQueryGeometryChanges(sqlite3 *db, const QString &sql)
Container of fields for a vector layer.
Definition: qgsfield.h:161
void setAttributes(const QgsAttributes &attrs)
Definition: qgsfeature.h:144
bool addFeature(QgsFeature &f, bool alsoUpdateExtent=true)
Adds a feature.
field comes from the underlying data provider of the vector layer (originIndex = index in provider's ...
Definition: qgsfield.h:168
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:113
QList< int > sqlQueryInts(sqlite3 *db, const QString &sql)
void createLoggingTables(sqlite3 *db)
void applyGeometryChanges(QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId, int commitNo)
QGis::WkbType wkbType() const
Returns the WKBType or WKBUnknown in case of error.
bool isOfflineProject()
return true if current project is offline
const QString & name() const
Get the display name of the layer.
QgsFeatureIds sqlQueryFeaturesRemoved(sqlite3 *db, const QString &sql)
bool writeEntry(const QString &scope, const QString &key, bool value)
void progressUpdated(int progress)
emit a signal with the progress of the current mode
QgsVectorLayerEditBuffer * editBuffer()
Buffer with uncommitted editing operations. Only valid after editing has been turned on...
virtual void reload()
Synchronises with changes in the datasource.
QString exportToWkt() const
Exports the geometry to mWkt.
void progressStopped()
emit a signal that processing of all layers has finished
void initializeSpatialMetadata(sqlite3 *sqlite_handle)
void applyFeaturesRemoved(QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId)
const QString & source() const
Returns the source for the layer.
QList< QgsMapLayer * > addMapLayers(QList< QgsMapLayer * > theMapLayers, bool addToLegend=true, bool takeOwnership=true)
Add a list of layers to the map of loaded layers.
QMap< int, int > attributeLookup(QgsVectorLayer *offlineLayer, QgsVectorLayer *remoteLayer)
int getOrCreateLayerId(sqlite3 *db, const QString &qgisLayerId)
int fieldOriginIndex(int fieldIdx) const
Get field's origin index (its meaning is specific to each type of origin)
Definition: qgsfield.h:217
const QList< NativeType > & nativeTypes() const
Returns the names of the supported types.
This class wraps a request for features to a vector layer (or directly its vector data provider)...
QList< int > QgsAttributeList
Q_DECL_DEPRECATED bool changeAttributeValue(QgsFeatureId fid, int field, QVariant value, bool emitSignal)
Changes an attribute value (but does not commit it)
const QgsAttributes & attributes() const
Definition: qgsfeature.h:142
QString id() const
Get this layer's unique ID, this ID is used to access this layer from map layer registry.
Definition: qgsmaplayer.cpp:92
bool removeEntry(const QString &scope, const QString &key)
remove the given key
int count() const
Return number of items.
Definition: qgsfield.h:195
bool changeGeometry(QgsFeatureId fid, QgsGeometry *geom)
change feature's geometry
QgsFeatureId offlineFid(sqlite3 *db, int layerId, QgsFeatureId remoteFid)
int sqlExec(sqlite3 *db, const QString &sql)
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:31
void applyFeaturesAdded(QgsVectorLayer *offlineLayer, QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId)
QVariant customProperty(const QString &value, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer.
bool isValid()
void layerAdded(QgsMapLayer *layer)
QList< QgsField > sqlQueryAttributesAdded(sqlite3 *db, const QString &sql)
#define PROJECT_ENTRY_SCOPE_OFFLINE
const QStringList & commitErrors()
QgsFeatureId remoteFid(sqlite3 *db, int layerId, QgsFeatureId offlineFid)
bool readSymbology(const QDomNode &node, QString &errorMessage)
Read the symbology for the current layer from the Dom node supplied.
Class for storing the component parts of a PostgreSQL/RDBMS datasource URI.
struct sqlite3 sqlite3
QList< GeometryChange > GeometryChanges
void copyVectorLayer(QgsVectorLayer *layer, sqlite3 *db, const QString &offlineDbPath)
QString providerType() const
Return the provider type for this layer.
QList< AttributeValueChange > AttributeValueChanges
virtual long featureCount() const
Number of features in the layer.
virtual const QgsFields & fields() const =0
Return a map of indexes with field names for this layer.
QString readEntry(const QString &scope, const QString &key, const QString &def=QString::null, bool *ok=0) const
static QgsMapLayerRegistry * instance()
Returns the instance pointer, creating the object on the first call.
QgsAttributeList pendingAllAttributesList()
returns list of attributes
virtual QVariant defaultValue(int fieldId)
Returns the default value for field specified by fieldId.
QMap< QgsFeatureId, QgsAttributeMap > QgsChangedAttributesMap
Definition: qgsfeature.h:320
void removeMapLayers(QStringList theLayerIds)
Remove a set of layers from the registry.
void title(const QString &title)
Every project has an associated title string.
Definition: qgsproject.cpp:371
QVector< QVariant > QgsAttributes
Definition: qgsfeature.h:100
void synchronize()
synchronize to remote layers
FieldOrigin fieldOrigin(int fieldIdx) const
Get field's origin (value from an enumeration)
Definition: qgsfield.h:215
void committedFeaturesAdded(const QString &qgisLayerId, const QgsFeatureList &addedFeatures)
virtual bool setSubsetString(QString subset)
Set the string (typically sql) used to define a subset of the layer.
int getCommitNo(sqlite3 *db)
static QgsProject * instance()
access to canonical QgsProject instance
Definition: qgsproject.cpp:362
QString table() const
int length() const
Gets the length of the field.
Definition: qgsfield.cpp:73
void committedAttributesAdded(const QString &qgisLayerId, const QList< QgsField > &addedAttributes)
bool hasLabelsEnabled() const
Label is on.
const QMap< QString, QgsMapLayer * > & mapLayers()
Retrieve the mapLayers collection (mainly intended for use by projection)
const QString & comment() const
Returns the field comment.
Definition: qgsfield.cpp:83
void progressModeSet(QgsOfflineEditing::ProgressMode mode, int maximum)
emit a signal that sets the mode for the progress of the current operation
qint64 QgsFeatureId
Definition: qgsfeature.h:30
static QgsGeometry * fromWkt(QString wkt)
static method that creates geometry from Wkt
const QgsCoordinateReferenceSystem & crs() const
Returns layer's spatial reference system.
QgsMapLayer * mapLayer(QString theLayerId)
Retrieve a pointer to a loaded layer by id.
void copySymbology(const QgsVectorLayer *sourceLayer, QgsVectorLayer *targetLayer)
int sqlQueryInt(sqlite3 *db, const QString &sql, int defaultValue)
const QgsFields & pendingFields() const
returns field list in the to-be-committed state
QgsVectorDataProvider * dataProvider()
Returns the data provider.
bool nextFeature(QgsFeature &f)
This is the base class for vector data providers.
Geometry is not required. It may still be returned if e.g. required for a filter condition.
void applyAttributesAdded(QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId, int commitNo)
bool writeSymbology(QDomNode &node, QDomDocument &doc, QString &errorMessage) const
Write the symbology for the layer into the docment provided.
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 ...
void applyAttributeValueChanges(QgsVectorLayer *offlineLayer, QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId, int commitNo)
#define CUSTOM_PROPERTY_REMOTE_PROVIDER
void committedFeaturesRemoved(const QString &qgisLayerId, const QgsFeatureIds &deletedFeatureIds)
void progressStarted()
emit a signal that processing has started
bool isNull(const QVariant &v)
void showWarning(const QString &message)
QVariant::Type type() const
Gets variant type of the field as it will be retrieved from data source.
Definition: qgsfield.cpp:63
#define tr(sourceText)
void committedAttributeValuesChanges(const QString &qgisLayerId, const QgsChangedAttributesMap &changedAttrsMap)