40 #include <QDomDocument> 43 #include <QMessageBox> 48 #include <spatialite.h> 55 #define CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE "isOfflineEditable" 56 #define CUSTOM_PROPERTY_REMOTE_SOURCE "remoteSource" 57 #define CUSTOM_PROPERTY_REMOTE_PROVIDER "remoteProvider" 58 #define PROJECT_ENTRY_SCOPE_OFFLINE "OfflineEditingPlugin" 59 #define PROJECT_ENTRY_KEY_OFFLINE_DB_PATH "/OfflineDbPath" 83 if ( layerIds.isEmpty() )
87 QString dbPath = QDir( offlineDataPath ).absoluteFilePath( offlineDbFile );
88 if ( createSpatialiteDB( dbPath ) )
91 int rc = database.
open( dbPath );
92 if ( rc != SQLITE_OK )
94 showWarning( tr(
"Could not open the SpatiaLite database" ) );
99 createLoggingTables( database.get() );
103 QMap<QString, QgsVectorJoinList > joinInfoBuffer;
104 QMap<QString, QgsVectorLayer *> layerIdMapping;
106 Q_FOREACH (
const QString &layerId, layerIds )
118 QgsVectorJoinList::iterator joinIt = joins.begin();
119 while ( joinIt != joins.end() )
121 if ( joinIt->prefix().isNull() )
126 joinIt->setPrefix( vl->
name() +
'_' );
130 joinInfoBuffer.insert( vl->
id(), joins );
134 for (
int i = 0; i < layerIds.count(); i++ )
142 QString origLayerId = vl->
id();
143 QgsVectorLayer *newLayer = copyVectorLayer( vl, database.get(), dbPath, onlySelected );
146 layerIdMapping.insert( origLayerId, newLayer );
149 QStringList() << origLayerId );
155 QMap<QString, QgsVectorJoinList >::ConstIterator it;
156 for ( it = joinInfoBuffer.constBegin(); it != joinInfoBuffer.constEnd(); ++it )
165 if ( newJoinedLayer )
180 if ( projectTitle.isEmpty() )
184 projectTitle += QLatin1String(
" (offline)" );
213 QList<QgsMapLayer *> offlineLayers;
215 for ( QMap<QString, QgsMapLayer *>::iterator layer_it = mapLayers.begin() ; layer_it != mapLayers.end(); ++layer_it )
220 offlineLayers << layer;
224 QgsDebugMsgLevel( QString(
"Found %1 offline layers" ).arg( offlineLayers.count() ), 4 );
225 for (
int l = 0; l < offlineLayers.count(); l++ )
233 QString remoteName = layer->
name();
234 remoteName.remove( QRegExp(
" \\(offline\\)$" ) );
240 if ( remoteLayer->
dataProvider()->
name().contains( QLatin1String(
"WFS" ), Qt::CaseInsensitive ) )
256 copySymbology( offlineLayer, remoteLayer );
257 updateRelations( offlineLayer, remoteLayer );
258 updateMapThemes( offlineLayer, remoteLayer );
259 updateLayerOrder( offlineLayer, remoteLayer );
262 QString qgisLayerId = layer->
id();
263 QString sql = QStringLiteral(
"SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
264 int layerId = sqlQueryInt( database.get(), sql, -1 );
270 int commitNo = getCommitNo( database.get() );
272 for (
int i = 0; i < commitNo; i++ )
276 applyAttributesAdded( remoteLayer, database.get(), layerId, i );
277 applyAttributeValueChanges( offlineLayer, remoteLayer, database.get(), layerId, i );
278 applyGeometryChanges( remoteLayer, database.get(), layerId, i );
281 applyFeaturesAdded( offlineLayer, remoteLayer, database.get(), layerId );
282 applyFeaturesRemoved( remoteLayer, database.get(), layerId );
287 updateFidLookup( remoteLayer, database.get(), layerId );
290 sql = QStringLiteral(
"DELETE FROM 'log_added_attrs' WHERE \"layer_id\" = %1" ).arg( layerId );
291 sqlExec( database.get(), sql );
292 sql = QStringLiteral(
"DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
293 sqlExec( database.get(), sql );
294 sql = QStringLiteral(
"DELETE FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
295 sqlExec( database.get(), sql );
296 sql = QStringLiteral(
"DELETE FROM 'log_feature_updates' WHERE \"layer_id\" = %1" ).arg( layerId );
297 sqlExec( database.get(), sql );
298 sql = QStringLiteral(
"DELETE FROM 'log_geometry_updates' WHERE \"layer_id\" = %1" ).arg( layerId );
299 sqlExec( database.get(), sql );
303 showWarning( remoteLayer->
commitErrors().join( QStringLiteral(
"\n" ) ) );
308 QgsDebugMsg(
"Could not find the layer id in the edit logs!" );
319 projectTitle.remove( QRegExp(
" \\(offline\\)$" ) );
331 QString sql = QStringLiteral(
"UPDATE 'log_indices' SET 'last_index' = 0 WHERE \"name\" = 'commit_no'" );
332 sqlExec( database.get(), sql );
337 void QgsOfflineEditing::initializeSpatialMetadata(
sqlite3 *sqlite_handle )
340 if ( !sqlite_handle )
343 char **results =
nullptr;
345 int ret = sqlite3_get_table( sqlite_handle,
"select count(*) from sqlite_master", &results, &rows, &columns,
nullptr );
346 if ( ret != SQLITE_OK )
351 for (
int i = 1; i <= rows; i++ )
352 count = atoi( results[( i * columns ) + 0] );
355 sqlite3_free_table( results );
360 bool above41 =
false;
361 ret = sqlite3_get_table( sqlite_handle,
"select spatialite_version()", &results, &rows, &columns,
nullptr );
362 if ( ret == SQLITE_OK && rows == 1 && columns == 1 )
364 QString version = QString::fromUtf8( results[1] );
365 QStringList parts = version.split(
' ', QString::SkipEmptyParts );
366 if ( !parts.empty() )
368 QStringList verparts = parts.at( 0 ).split(
'.', QString::SkipEmptyParts );
369 above41 = verparts.size() >= 2 && ( verparts.at( 0 ).toInt() > 4 || ( verparts.at( 0 ).toInt() == 4 && verparts.at( 1 ).toInt() >= 1 ) );
373 sqlite3_free_table( results );
376 char *errMsg =
nullptr;
377 ret = sqlite3_exec( sqlite_handle, above41 ?
"SELECT InitSpatialMetadata(1)" :
"SELECT InitSpatialMetadata()",
nullptr,
nullptr, &errMsg );
379 if ( ret != SQLITE_OK )
381 QString errCause = tr(
"Unable to initialize SpatialMetadata:\n" );
382 errCause += QString::fromUtf8( errMsg );
383 showWarning( errCause );
384 sqlite3_free( errMsg );
387 spatial_ref_sys_init( sqlite_handle, 0 );
390 bool QgsOfflineEditing::createSpatialiteDB(
const QString &offlineDbPath )
393 char *errMsg =
nullptr;
394 QFile newDb( offlineDbPath );
395 if ( newDb.exists() )
397 QFile::remove( offlineDbPath );
402 QFileInfo fullPath = QFileInfo( offlineDbPath );
403 QDir path = fullPath.dir();
406 QDir().mkpath( path.absolutePath() );
409 QString dbPath = newDb.fileName();
411 ret = database.
open_v2( dbPath, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
nullptr );
415 QString errCause = tr(
"Could not create a new database\n" );
417 showWarning( errCause );
421 ret = sqlite3_exec( database.get(),
"PRAGMA foreign_keys = 1",
nullptr,
nullptr, &errMsg );
422 if ( ret != SQLITE_OK )
424 showWarning( tr(
"Unable to activate FOREIGN_KEY constraints" ) );
425 sqlite3_free( errMsg );
428 initializeSpatialMetadata( database.get() );
432 void QgsOfflineEditing::createLoggingTables(
sqlite3 *db )
435 QString sql = QStringLiteral(
"CREATE TABLE 'log_indices' ('name' TEXT, 'last_index' INTEGER)" );
438 sql = QStringLiteral(
"INSERT INTO 'log_indices' VALUES ('commit_no', 0)" );
441 sql = QStringLiteral(
"INSERT INTO 'log_indices' VALUES ('layer_id', 0)" );
445 sql = QStringLiteral(
"CREATE TABLE 'log_layer_ids' ('id' INTEGER, 'qgis_id' TEXT)" );
449 sql = QStringLiteral(
"CREATE TABLE 'log_fids' ('layer_id' INTEGER, 'offline_fid' INTEGER, 'remote_fid' INTEGER)" );
453 sql = QStringLiteral(
"CREATE TABLE 'log_added_attrs' ('layer_id' INTEGER, 'commit_no' INTEGER, " );
454 sql += QLatin1String(
"'name' TEXT, 'type' INTEGER, 'length' INTEGER, 'precision' INTEGER, 'comment' TEXT)" );
458 sql = QStringLiteral(
"CREATE TABLE 'log_added_features' ('layer_id' INTEGER, 'fid' INTEGER)" );
462 sql = QStringLiteral(
"CREATE TABLE 'log_removed_features' ('layer_id' INTEGER, 'fid' INTEGER)" );
466 sql = QStringLiteral(
"CREATE TABLE 'log_feature_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'attr' INTEGER, 'value' TEXT)" );
470 sql = QStringLiteral(
"CREATE TABLE 'log_geometry_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'geom_wkt' TEXT)" );
483 QString tableName = layer->
id();
484 QgsDebugMsgLevel( QString(
"Creating offline table %1 ..." ).arg( tableName ), 4 );
487 QString sql = QStringLiteral(
"CREATE TABLE '%1' (" ).arg( tableName );
490 for (
const auto &field : providerFields )
493 QVariant::Type type = field.type();
494 if ( type == QVariant::Int || type == QVariant::LongLong )
496 dataType = QStringLiteral(
"INTEGER" );
498 else if ( type == QVariant::Double )
500 dataType = QStringLiteral(
"REAL" );
502 else if ( type == QVariant::String )
504 dataType = QStringLiteral(
"TEXT" );
508 showWarning( tr(
"%1: Unknown data type %2. Not using type affinity for the field." ).arg( field.name(), QVariant::typeToName( type ) ) );
511 sql += delim + QStringLiteral(
"'%1' %2" ).arg( field.name(), dataType );
516 int rc = sqlExec( db, sql );
525 geomType = QStringLiteral(
"POINT" );
528 geomType = QStringLiteral(
"MULTIPOINT" );
531 geomType = QStringLiteral(
"LINESTRING" );
534 geomType = QStringLiteral(
"MULTILINESTRING" );
537 geomType = QStringLiteral(
"POLYGON" );
540 geomType = QStringLiteral(
"MULTIPOLYGON" );
543 showWarning( tr(
"QGIS wkbType %1 not supported" ).arg( layer->
wkbType() ) );
546 QString sqlAddGeom = QStringLiteral(
"SELECT AddGeometryColumn('%1', 'Geometry', %2, '%3', 2)" )
548 .arg( layer->
crs().
authid().startsWith( QLatin1String(
"EPSG:" ), Qt::CaseInsensitive ) ? layer->
crs().
authid().mid( 5 ).toLong() : 0 )
552 QString sqlCreateIndex = QStringLiteral(
"SELECT CreateSpatialIndex('%1', 'Geometry')" ).arg( tableName );
554 if ( rc == SQLITE_OK )
556 rc = sqlExec( db, sqlAddGeom );
557 if ( rc == SQLITE_OK )
559 rc = sqlExec( db, sqlCreateIndex );
564 if ( rc == SQLITE_OK )
567 QString connectionString = QStringLiteral(
"dbname='%1' table='%2'%3 sql=" )
569 tableName, layer->
isSpatial() ?
"(Geometry)" :
"" );
571 layer->
name() +
" (offline)", QStringLiteral(
"spatialite" ) );
584 if ( !selectedFids.isEmpty() )
598 int featureCount = 1;
600 QList<QgsFeatureId> remoteFeatureIds;
603 remoteFeatureIds << f.
id();
610 for (
int it = 0; it < attrs.count(); ++it )
612 newAttrs[column++] = attrs.at( it );
626 int layerId = getOrCreateLayerId( db, newLayer->
id() );
627 QList<QgsFeatureId> offlineFeatureIds;
632 offlineFeatureIds << f.
id();
636 sqlExec( db, QStringLiteral(
"BEGIN" ) );
637 int remoteCount = remoteFeatureIds.size();
638 for (
int i = 0; i < remoteCount; i++ )
641 if ( i < offlineFeatureIds.count() )
643 addFidLookup( db, layerId, offlineFeatureIds.at( i ), remoteFeatureIds.at( i ) );
647 showWarning( tr(
"Feature cannot be copied to the offline layer, please check if the online layer '%1' is still accessible." ).arg( layer->
name() ) );
652 sqlExec( db, QStringLiteral(
"COMMIT" ) );
656 showWarning( newLayer->
commitErrors().join( QStringLiteral(
"\n" ) ) );
668 QList<QgsMapLayer *>() << newLayer );
671 copySymbology( layer, newLayer );
676 if ( layerTreeLayer )
679 if ( parentTreeGroup )
681 int index = parentTreeGroup->
children().indexOf( layerTreeLayer );
684 if ( newLayerTreeLayer )
695 updateRelations( layer, newLayer );
696 updateMapThemes( layer, newLayer );
697 updateLayerOrder( layer, newLayer );
706 void QgsOfflineEditing::applyAttributesAdded(
QgsVectorLayer *remoteLayer,
sqlite3 *db,
int layerId,
int commitNo )
708 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 );
709 QList<QgsField> fields = sqlQueryAttributesAdded( db, sql );
712 QList<QgsVectorDataProvider::NativeType> nativeTypes = provider->
nativeTypes();
715 QMap < QVariant::Type, QString > typeNameLookup;
716 for (
int i = 0; i < nativeTypes.size(); i++ )
724 for (
int i = 0; i < fields.size(); i++ )
728 if ( typeNameLookup.contains( field.
type() ) )
730 QString typeName = typeNameLookup[ field.
type()];
736 showWarning( QStringLiteral(
"Could not add attribute '%1' of type %2" ).arg( field.
name() ).arg( field.
type() ) );
745 QString sql = QStringLiteral(
"SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
746 QList<int> featureIdInts = sqlQueryInts( db, sql );
748 Q_FOREACH (
int id, featureIdInts )
769 for ( QgsFeatureList::iterator it = features.begin(); it != features.end(); ++it )
773 QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
776 for (
int it = 0; it < attrs.count(); ++it )
778 newAttrs[ attrLookup[ it ] ] = attrs.at( it );
789 void QgsOfflineEditing::applyFeaturesRemoved(
QgsVectorLayer *remoteLayer,
sqlite3 *db,
int layerId )
791 QString sql = QStringLiteral(
"SELECT \"fid\" FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
797 for ( QgsFeatureIds::const_iterator it = values.constBegin(); it != values.constEnd(); ++it )
808 QString sql = QStringLiteral(
"SELECT \"fid\", \"attr\", \"value\" FROM 'log_feature_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2 " ).arg( layerId ).arg( commitNo );
809 AttributeValueChanges values = sqlQueryAttributeValueChanges( db, sql );
813 QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
815 for (
int i = 0; i < values.size(); i++ )
817 QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid );
818 QgsDebugMsgLevel( QString(
"Offline changeAttributeValue %1 = %2" ).arg( QString( attrLookup[ values.at( i ).attr ] ), values.at( i ).value ), 4 );
819 remoteLayer->
changeAttributeValue( fid, attrLookup[ values.at( i ).attr ], values.at( i ).value );
825 void QgsOfflineEditing::applyGeometryChanges(
QgsVectorLayer *remoteLayer,
sqlite3 *db,
int layerId,
int commitNo )
827 QString sql = QStringLiteral(
"SELECT \"fid\", \"geom_wkt\" FROM 'log_geometry_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo );
828 GeometryChanges values = sqlQueryGeometryChanges( db, sql );
832 for (
int i = 0; i < values.size(); i++ )
834 QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid );
858 if ( offlineFid( db, layerId, f.
id() ) == -1 )
860 newRemoteFids[ f.
id()] =
true;
868 QString sql = QStringLiteral(
"SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
869 QList<int> newOfflineFids = sqlQueryInts( db, sql );
871 if ( newRemoteFids.size() != newOfflineFids.size() )
879 sqlExec( db, QStringLiteral(
"BEGIN" ) );
880 for ( QMap<QgsFeatureId, bool>::const_iterator it = newRemoteFids.constBegin(); it != newRemoteFids.constEnd(); ++it )
882 addFidLookup( db, layerId, newOfflineFids.at( i++ ), it.key() );
884 sqlExec( db, QStringLiteral(
"COMMIT" ) );
894 if ( error.isEmpty() )
898 if ( !error.isEmpty() )
900 showWarning( error );
907 QList<QgsRelation> relations;
930 QStringList mapThemeNames = mapThemeCollection->
mapThemes();
932 Q_FOREACH (
const QString &mapThemeName, mapThemeNames )
938 if ( layerRecord.
layer() == sourceLayer )
940 layerRecord.
setLayer( targetLayer );
954 auto iterator = layerOrder.begin();
956 while ( iterator != layerOrder.end() )
958 if ( *iterator == targetLayer )
960 iterator = layerOrder.erase( iterator );
961 if ( iterator == layerOrder.end() )
965 if ( *iterator == sourceLayer )
967 *iterator = targetLayer;
982 QMap <
int ,
int > attrLookup;
984 for (
int i = 0; i < remoteAttrs.size(); i++ )
986 attrLookup.insert( offlineAttrs.at( i ), remoteAttrs.at( i ) );
992 void QgsOfflineEditing::showWarning(
const QString &message )
994 emit
warning( tr(
"Offline Editing Plugin" ), message );
1001 if ( !dbPath.isEmpty() )
1004 int rc = database.
open( absoluteDbPath );
1005 if ( rc != SQLITE_OK )
1007 QgsDebugMsg(
"Could not open the SpatiaLite logging database" );
1008 showWarning( tr(
"Could not open the SpatiaLite logging database" ) );
1018 int QgsOfflineEditing::getOrCreateLayerId(
sqlite3 *db,
const QString &qgisLayerId )
1020 QString sql = QStringLiteral(
"SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
1021 int layerId = sqlQueryInt( db, sql, -1 );
1022 if ( layerId == -1 )
1025 sql = QStringLiteral(
"SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'layer_id'" );
1026 int newLayerId = sqlQueryInt( db, sql, -1 );
1029 sql = QStringLiteral(
"INSERT INTO 'log_layer_ids' VALUES (%1, '%2')" ).arg( newLayerId ).arg( qgisLayerId );
1034 sql = QStringLiteral(
"UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'layer_id'" ).arg( newLayerId + 1 );
1037 layerId = newLayerId;
1043 int QgsOfflineEditing::getCommitNo(
sqlite3 *db )
1045 QString sql = QStringLiteral(
"SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'commit_no'" );
1046 return sqlQueryInt( db, sql, -1 );
1049 void QgsOfflineEditing::increaseCommitNo(
sqlite3 *db )
1051 QString sql = QStringLiteral(
"UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'commit_no'" ).arg( getCommitNo( db ) + 1 );
1057 QString sql = QStringLiteral(
"INSERT INTO 'log_fids' VALUES ( %1, %2, %3 )" ).arg( layerId ).arg( offlineFid ).arg( remoteFid );
1063 QString sql = QStringLiteral(
"SELECT \"remote_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2" ).arg( layerId ).arg( offlineFid );
1064 return sqlQueryInt( db, sql, -1 );
1069 QString sql = QStringLiteral(
"SELECT \"offline_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"remote_fid\" = %2" ).arg( layerId ).arg( remoteFid );
1070 return sqlQueryInt( db, sql, -1 );
1075 QString sql = QStringLiteral(
"SELECT COUNT(\"fid\") FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( fid );
1076 return ( sqlQueryInt( db, sql, 0 ) > 0 );
1079 int QgsOfflineEditing::sqlExec(
sqlite3 *db,
const QString &sql )
1081 char *errmsg =
nullptr;
1082 int rc = sqlite3_exec( db, sql.toUtf8(),
nullptr,
nullptr, &errmsg );
1083 if ( rc != SQLITE_OK )
1085 showWarning( errmsg );
1090 int QgsOfflineEditing::sqlQueryInt(
sqlite3 *db,
const QString &sql,
int defaultValue )
1092 sqlite3_stmt *stmt =
nullptr;
1093 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1095 showWarning( sqlite3_errmsg( db ) );
1096 return defaultValue;
1099 int value = defaultValue;
1100 int ret = sqlite3_step( stmt );
1101 if ( ret == SQLITE_ROW )
1103 value = sqlite3_column_int( stmt, 0 );
1105 sqlite3_finalize( stmt );
1110 QList<int> QgsOfflineEditing::sqlQueryInts(
sqlite3 *db,
const QString &sql )
1114 sqlite3_stmt *stmt =
nullptr;
1115 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1117 showWarning( sqlite3_errmsg( db ) );
1121 int ret = sqlite3_step( stmt );
1122 while ( ret == SQLITE_ROW )
1124 values << sqlite3_column_int( stmt, 0 );
1126 ret = sqlite3_step( stmt );
1128 sqlite3_finalize( stmt );
1133 QList<QgsField> QgsOfflineEditing::sqlQueryAttributesAdded(
sqlite3 *db,
const QString &sql )
1135 QList<QgsField> values;
1137 sqlite3_stmt *stmt =
nullptr;
1138 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1140 showWarning( sqlite3_errmsg( db ) );
1144 int ret = sqlite3_step( stmt );
1145 while ( ret == SQLITE_ROW )
1147 QgsField field( QString( reinterpret_cast< const char * >( sqlite3_column_text( stmt, 0 ) ) ),
1148 static_cast< QVariant::Type >( sqlite3_column_int( stmt, 1 ) ),
1149 QLatin1String(
"" ),
1150 sqlite3_column_int( stmt, 2 ),
1151 sqlite3_column_int( stmt, 3 ),
1152 QString( reinterpret_cast< const char * >( sqlite3_column_text( stmt, 4 ) ) ) );
1155 ret = sqlite3_step( stmt );
1157 sqlite3_finalize( stmt );
1166 sqlite3_stmt *stmt =
nullptr;
1167 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1169 showWarning( sqlite3_errmsg( db ) );
1173 int ret = sqlite3_step( stmt );
1174 while ( ret == SQLITE_ROW )
1176 values << sqlite3_column_int( stmt, 0 );
1178 ret = sqlite3_step( stmt );
1180 sqlite3_finalize( stmt );
1185 QgsOfflineEditing::AttributeValueChanges QgsOfflineEditing::sqlQueryAttributeValueChanges(
sqlite3 *db,
const QString &sql )
1187 AttributeValueChanges values;
1189 sqlite3_stmt *stmt =
nullptr;
1190 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1192 showWarning( sqlite3_errmsg( db ) );
1196 int ret = sqlite3_step( stmt );
1197 while ( ret == SQLITE_ROW )
1199 AttributeValueChange change;
1200 change.fid = sqlite3_column_int( stmt, 0 );
1201 change.attr = sqlite3_column_int( stmt, 1 );
1202 change.value = QString( reinterpret_cast< const char * >( sqlite3_column_text( stmt, 2 ) ) );
1205 ret = sqlite3_step( stmt );
1207 sqlite3_finalize( stmt );
1212 QgsOfflineEditing::GeometryChanges QgsOfflineEditing::sqlQueryGeometryChanges(
sqlite3 *db,
const QString &sql )
1214 GeometryChanges values;
1216 sqlite3_stmt *stmt =
nullptr;
1217 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1219 showWarning( sqlite3_errmsg( db ) );
1223 int ret = sqlite3_step( stmt );
1224 while ( ret == SQLITE_ROW )
1226 GeometryChange change;
1227 change.fid = sqlite3_column_int( stmt, 0 );
1228 change.geom_wkt = QString( reinterpret_cast< const char * >( sqlite3_column_text( stmt, 1 ) ) );
1231 ret = sqlite3_step( stmt );
1233 sqlite3_finalize( stmt );
1238 void QgsOfflineEditing::committedAttributesAdded(
const QString &qgisLayerId,
const QList<QgsField> &addedAttributes )
1241 if ( !database.get() )
1245 int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1246 int commitNo = getCommitNo( database.get() );
1248 for ( QList<QgsField>::const_iterator it = addedAttributes.begin(); it != addedAttributes.end(); ++it )
1251 QString sql = QStringLiteral(
"INSERT INTO 'log_added_attrs' VALUES ( %1, %2, '%3', %4, %5, %6, '%7' )" )
1254 .arg( field.
name() )
1255 .arg( field.
type() )
1259 sqlExec( database.get(), sql );
1262 increaseCommitNo( database.get() );
1263 sqlite3_close( database.get() );
1266 void QgsOfflineEditing::committedFeaturesAdded(
const QString &qgisLayerId,
const QgsFeatureList &addedFeatures )
1269 if ( !database.get() )
1273 int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1280 QString sql = QStringLiteral(
"SELECT ROWID FROM '%1' ORDER BY ROWID DESC LIMIT %2" ).arg( uri.
table() ).arg( addedFeatures.size() );
1281 QList<int> newFeatureIds = sqlQueryInts( database.get(), sql );
1282 for (
int i = newFeatureIds.size() - 1; i >= 0; i-- )
1284 QString sql = QStringLiteral(
"INSERT INTO 'log_added_features' VALUES ( %1, %2 )" )
1286 .arg( newFeatureIds.at( i ) );
1287 sqlExec( database.get(), sql );
1290 sqlite3_close( database.get() );
1293 void QgsOfflineEditing::committedFeaturesRemoved(
const QString &qgisLayerId,
const QgsFeatureIds &deletedFeatureIds )
1296 if ( !database.get() )
1300 int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1302 for ( QgsFeatureIds::const_iterator it = deletedFeatureIds.begin(); it != deletedFeatureIds.end(); ++it )
1304 if ( isAddedFeature( database.get(), layerId, *it ) )
1307 QString sql = QStringLiteral(
"DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( *it );
1308 sqlExec( database.get(), sql );
1312 QString sql = QStringLiteral(
"INSERT INTO 'log_removed_features' VALUES ( %1, %2)" )
1315 sqlExec( database.get(), sql );
1319 sqlite3_close( database.get() );
1322 void QgsOfflineEditing::committedAttributeValuesChanges(
const QString &qgisLayerId,
const QgsChangedAttributesMap &changedAttrsMap )
1325 if ( !database.get() )
1329 int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1330 int commitNo = getCommitNo( database.get() );
1332 for ( QgsChangedAttributesMap::const_iterator cit = changedAttrsMap.begin(); cit != changedAttrsMap.end(); ++cit )
1335 if ( isAddedFeature( database.get(), layerId, fid ) )
1341 for ( QgsAttributeMap::const_iterator it = attrMap.constBegin(); it != attrMap.constEnd(); ++it )
1343 QString sql = QStringLiteral(
"INSERT INTO 'log_feature_updates' VALUES ( %1, %2, %3, %4, '%5' )" )
1348 .arg( it.value().toString() );
1349 sqlExec( database.get(), sql );
1353 increaseCommitNo( database.get() );
1354 sqlite3_close( database.get() );
1357 void QgsOfflineEditing::committedGeometriesChanges(
const QString &qgisLayerId,
const QgsGeometryMap &changedGeometries )
1360 if ( !database.get() )
1364 int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1365 int commitNo = getCommitNo( database.get() );
1367 for ( QgsGeometryMap::const_iterator it = changedGeometries.begin(); it != changedGeometries.end(); ++it )
1370 if ( isAddedFeature( database.get(), layerId, fid ) )
1376 QString sql = QStringLiteral(
"INSERT INTO 'log_geometry_updates' VALUES ( %1, %2, %3, '%4' )" )
1380 .arg( geom.
asWkt() );
1381 sqlExec( database.get(), sql );
1386 increaseCommitNo( database.get() );
1387 sqlite3_close( database.get() );
1390 void QgsOfflineEditing::startListenFeatureChanges()
1398 this, &QgsOfflineEditing::committedAttributesAdded );
1400 this, &QgsOfflineEditing::committedAttributeValuesChanges );
1402 this, &QgsOfflineEditing::committedGeometriesChanges );
1405 this, &QgsOfflineEditing::committedFeaturesAdded );
1407 this, &QgsOfflineEditing::committedFeaturesRemoved );
1410 void QgsOfflineEditing::stopListenFeatureChanges()
1418 this, &QgsOfflineEditing::committedAttributesAdded );
1420 this, &QgsOfflineEditing::committedAttributeValuesChanges );
1422 this, &QgsOfflineEditing::committedGeometriesChanges );
1425 this, &QgsOfflineEditing::committedFeaturesAdded );
1427 this, &QgsOfflineEditing::committedFeaturesRemoved );
1430 void QgsOfflineEditing::layerAdded(
QgsMapLayer *layer )
Layer tree group node serves as a container for layers and further groups.
void setJoinLayer(QgsVectorLayer *layer)
Sets weak reference to the joined layer.
Wrapper for iterator of features from vector data provider or vector layer.
QMap< QgsFeatureId, QgsGeometry > QgsGeometryMap
void layerProgressUpdated(int layer, int numLayers)
Is emitted whenever a new layer is being processed.
int open(const QString &path)
Opens the database at the specified file path.
bool addJoin(const QgsVectorLayerJoinInfo &joinInfo)
Joins another vector layer to this layer.
int open_v2(const QString &path, int flags, const char *zVfs)
Opens the database at the specified file path.
QList< QgsMapLayer * > addMapLayers(const QList< QgsMapLayer *> &mapLayers, bool addToLegend=true, bool takeOwnership=true)
Add a list of layers to the map of loaded layers.
QgsMapLayer * layer() const
Returns map layer or null if the layer does not exist anymore.
Base class for all map layer types.
void setLayer(QgsMapLayer *layer)
Set the map layer for this record.
QString table() const
Returns the table.
static QgsFeature createFeature(QgsVectorLayer *layer, const QgsGeometry &geometry=QgsGeometry(), const QgsAttributeMap &attributes=QgsAttributeMap(), QgsExpressionContext *context=nullptr)
Creates a new feature ready for insertion into a layer.
Filter using feature IDs.
QString readEntry(const QString &scope, const QString &key, const QString &def=QString(), bool *ok=nullptr) const
#define PROJECT_ENTRY_KEY_OFFLINE_DB_PATH
bool deleteFeature(QgsFeatureId fid)
Deletes a feature from the layer (but does not commit it).
void committedAttributesAdded(const QString &layerId, const QList< QgsField > &addedAttributes)
QSet< QgsFeatureId > QgsFeatureIds
QList< QgsRelation > referencingRelations(const QgsVectorLayer *layer=nullptr, int fieldIdx=-2) const
Get 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
#define CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE
Individual map theme record of visible layers and styles.
bool commitChanges()
Attempts to commit to the underlying data provider any buffered changes made since the last to call t...
void setReferencingLayer(const QString &id)
Set the referencing (child) layer id.
void update(const QString &name, const QgsMapThemeCollection::MapThemeRecord &state)
Updates a map theme within the collection.
bool startEditing()
Makes the layer editable.
void setCustomProperty(const QString &key, const QVariant &value)
Set a custom property for layer.
#define CUSTOM_PROPERTY_REMOTE_SOURCE
FilterType filterType() const
Return the filter type which is currently set on this request.
Container of fields for a vector layer.
A geometry is the spatial representation of a feature.
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
void removeChildNode(QgsLayerTreeNode *node)
Remove a child node from this group.
QMap< QString, QgsMapLayer * > mapLayers() const
Returns a map of all registered layers by layer ID.
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...
QList< QgsVectorDataProvider::NativeType > nativeTypes() const
Returns the names of the supported types.
bool isValid() const
Return the status of the layer.
bool isOfflineProject() const
Return true if current project is offline.
void committedFeaturesRemoved(const QString &layerId, const QgsFeatureIds &deletedFeatureIds)
This signal is emitted, when features are deleted from the provider.
int count() const
Return number of items.
QList< QgsMapLayer * > customLayerOrder() const
The order in which layers will be rendered on the canvas.
virtual QString name() const =0
Return a provider name.
bool convertToOfflineProject(const QString &offlineDataPath, const QString &offlineDbFile, const QStringList &layerIds, bool onlySelected=false)
Convert current project for offline editing.
void removeLayerRecord(QgsMapLayer *layer)
Removes a record for layer if present.
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...
const QList< QgsVectorLayerJoinInfo > vectorJoins() const
QString id() const
Returns the layer's unique ID, which is used to access this layer from QgsProject.
QList< QgsLayerTreeNode * > children()
Get list of children of the node. Children are owned by the parent.
QgsMapThemeCollection mapThemeCollection
void progressStopped()
Emitted when the processing of all layers has finished.
const QgsFeatureIds & selectedFeatureIds() const
Return reference to identifiers of selected features.
QgsFields fields() const override
Returns the list of fields of this layer.
Unique pointer for spatialite databases, which automatically closes the database when the pointer goe...
#define QgsDebugMsgLevel(str, level)
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.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
QgsLayerTreeNode * parent()
Get pointer to the parent. If parent is a null pointer, the node is a root node.
void committedFeaturesAdded(const QString &layerId, const QgsFeatureList &addedFeatures)
This signal is emitted, when features are added to the provider.
QgsWkbTypes::Type wkbType() const override
Returns the WKBType or WKBUnknown in case of error.
Defines left outer join from our vector layer to some other vector layer.
QMap< int, QVariant > QgsAttributeMap
void editingStopped()
Is emitted, when edited changes successfully have been written to the data provider.
This class wraps a request for features to a vector layer (or directly its vector data provider)...
bool isSpatial() const override
Returns true if this is a geometry layer and false in case of NoGeometry (table only) or UnknownGeome...
QgsCoordinateReferenceSystem crs() const
Returns the layer's spatial reference system.
This class is a base class for nodes in a layer tree.
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
Get all relations where this layer is the referenced part (i.e.
Encapsulate a field in an attribute table or data source.
virtual bool importNamedStyle(QDomDocument &doc, QString &errorMsg)
Import the properties of this layer from a QDomDocument.
QgsRelationManager relationManager
void editingStarted()
Is emitted, when editing on this layer has started.
int open(const QString &path)
Opens the database at the specified file path.
#define PROJECT_ENTRY_SCOPE_OFFLINE
void setReferencedLayer(const QString &id)
Set the referenced (parent) layer id.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const override
Query the layer for features specified in request.
void reload() override
Synchronises with changes in the datasource.
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
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
Return pointer to the root (invisible) node of the project's layer tree.
static QgsGeometry fromWkt(const QString &wkt)
Creates a new geometry from a WKT string.
void warning(const QString &title, const QString &message)
Emitted when a warning needs to be displayed.
QgsFeatureRequest & setFilterFids(const QgsFeatureIds &fids)
Set 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
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.
void setTitle(const QString &title)
Sets the project's title.
QgsVectorDataProvider * dataProvider() override
Returns the layer's data provider.
void progressModeSet(QgsOfflineEditing::ProgressMode mode, int maximum)
Is emitted when the mode for the progress of the current operation is set.
QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
void removeMapLayers(const QStringList &layerIds)
Remove a set of layers from the registry by layer ID.
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's title.
QList< int > QgsAttributeList
QgsLayerTreeLayer * findLayer(QgsMapLayer *layer) const
Find layer node representing the map layer.
QString providerType() const
Return the provider type for this layer.
QList< QgsVectorLayerJoinInfo > QgsVectorJoinList
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.
Class for storing the component parts of a PostgreSQL/RDBMS datasource URI.
bool changeGeometry(QgsFeatureId fid, const QgsGeometry &geometry, bool skipDefaultValue=false)
Changes a feature's geometry within the layer's edit buffer (but does not immediately commit the chan...
virtual void invalidateConnections(const QString &connection)
Invalidate connections corresponding to specified name.
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.
#define CUSTOM_PROPERTY_REMOTE_PROVIDER
void progressStarted()
The signal is emitted when the process has started.
QgsLayerTreeLayer * clone() const override
Create a copy of the node. Returns new instance.
bool addFeature(QgsFeature &feature, QgsFeatureSink::Flags flags=nullptr) override
Adds a single feature to the sink.
void addLayerRecord(const QgsMapThemeCollection::MapThemeLayerRecord &record)
Add a new record for a layer.
QString authid() const
Returns the authority identifier for the CRS.
QString readPath(const QString &filename) const
Turn filename read from the project file to an absolute path.
void layerWasAdded(QgsMapLayer *layer)
Emitted when a layer was added to the registry.
Layer tree node points to a map layer.
QString joinLayerId() const
ID of the joined layer - may be used to resolve reference to the joined layer.
void addRelation(const QgsRelation &relation)
Add a relation.
QString errorMessage() const
Returns the most recent error message encountered by the database.
virtual void exportNamedStyle(QDomDocument &doc, QString &errorMsg) const
Export the properties of this layer as named style in a QDomDocument.