40 #include <QDomDocument> 43 #include <QMessageBox> 48 #include <spatialite.h> 51 #define CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE "isOfflineEditable" 52 #define CUSTOM_PROPERTY_REMOTE_SOURCE "remoteSource" 53 #define CUSTOM_PROPERTY_REMOTE_PROVIDER "remoteProvider" 54 #define PROJECT_ENTRY_SCOPE_OFFLINE "OfflineEditingPlugin" 55 #define PROJECT_ENTRY_KEY_OFFLINE_DB_PATH "/OfflineDbPath" 79 if ( layerIds.isEmpty() )
83 QString dbPath = QDir( offlineDataPath ).absoluteFilePath( offlineDbFile );
84 if ( createSpatialiteDB( dbPath ) )
87 int rc = database.
open( dbPath );
88 if ( rc != SQLITE_OK )
90 showWarning( tr(
"Could not open the SpatiaLite database" ) );
95 createLoggingTables( database.get() );
99 QMap<QString, QgsVectorJoinList > joinInfoBuffer;
100 QMap<QString, QgsVectorLayer *> layerIdMapping;
102 Q_FOREACH (
const QString &layerId, layerIds )
114 QgsVectorJoinList::iterator joinIt = joins.begin();
115 while ( joinIt != joins.end() )
117 if ( joinIt->prefix().isNull() )
122 joinIt->setPrefix( vl->
name() +
'_' );
126 joinInfoBuffer.insert( vl->
id(), joins );
130 for (
int i = 0; i < layerIds.count(); i++ )
138 QString origLayerId = vl->
id();
139 QgsVectorLayer *newLayer = copyVectorLayer( vl, database.get(), dbPath, onlySelected );
142 layerIdMapping.insert( origLayerId, newLayer );
145 QStringList() << origLayerId );
151 QMap<QString, QgsVectorJoinList >::ConstIterator it;
152 for ( it = joinInfoBuffer.constBegin(); it != joinInfoBuffer.constEnd(); ++it )
161 if ( newJoinedLayer )
176 if ( projectTitle.isEmpty() )
180 projectTitle += QLatin1String(
" (offline)" );
209 QList<QgsMapLayer *> offlineLayers;
211 for ( QMap<QString, QgsMapLayer *>::iterator layer_it = mapLayers.begin() ; layer_it != mapLayers.end(); ++layer_it )
216 offlineLayers << layer;
220 QgsDebugMsgLevel( QString(
"Found %1 offline layers" ).arg( offlineLayers.count() ), 4 );
221 for (
int l = 0; l < offlineLayers.count(); l++ )
229 QString remoteName = layer->
name();
230 remoteName.remove( QRegExp(
" \\(offline\\)$" ) );
236 if ( remoteLayer->
dataProvider()->
name().contains( QLatin1String(
"WFS" ), Qt::CaseInsensitive ) )
252 copySymbology( offlineLayer, remoteLayer );
253 updateRelations( offlineLayer, remoteLayer );
254 updateMapThemes( offlineLayer, remoteLayer );
255 updateLayerOrder( offlineLayer, remoteLayer );
258 QString qgisLayerId = layer->
id();
259 QString sql = QStringLiteral(
"SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
260 int layerId = sqlQueryInt( database.get(), sql, -1 );
266 int commitNo = getCommitNo( database.get() );
268 for (
int i = 0; i < commitNo; i++ )
272 applyAttributesAdded( remoteLayer, database.get(), layerId, i );
273 applyAttributeValueChanges( offlineLayer, remoteLayer, database.get(), layerId, i );
274 applyGeometryChanges( remoteLayer, database.get(), layerId, i );
277 applyFeaturesAdded( offlineLayer, remoteLayer, database.get(), layerId );
278 applyFeaturesRemoved( remoteLayer, database.get(), layerId );
283 updateFidLookup( remoteLayer, database.get(), layerId );
286 sql = QStringLiteral(
"DELETE FROM 'log_added_attrs' WHERE \"layer_id\" = %1" ).arg( layerId );
287 sqlExec( database.get(), sql );
288 sql = QStringLiteral(
"DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
289 sqlExec( database.get(), sql );
290 sql = QStringLiteral(
"DELETE FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
291 sqlExec( database.get(), sql );
292 sql = QStringLiteral(
"DELETE FROM 'log_feature_updates' WHERE \"layer_id\" = %1" ).arg( layerId );
293 sqlExec( database.get(), sql );
294 sql = QStringLiteral(
"DELETE FROM 'log_geometry_updates' WHERE \"layer_id\" = %1" ).arg( layerId );
295 sqlExec( database.get(), sql );
299 showWarning( remoteLayer->
commitErrors().join( QStringLiteral(
"\n" ) ) );
304 QgsDebugMsg(
"Could not find the layer id in the edit logs!" );
315 projectTitle.remove( QRegExp(
" \\(offline\\)$" ) );
327 QString sql = QStringLiteral(
"UPDATE 'log_indices' SET 'last_index' = 0 WHERE \"name\" = 'commit_no'" );
328 sqlExec( database.get(), sql );
333 void QgsOfflineEditing::initializeSpatialMetadata(
sqlite3 *sqlite_handle )
336 if ( !sqlite_handle )
339 char **results =
nullptr;
341 int ret = sqlite3_get_table( sqlite_handle,
"select count(*) from sqlite_master", &results, &rows, &columns,
nullptr );
342 if ( ret != SQLITE_OK )
347 for (
int i = 1; i <= rows; i++ )
348 count = atoi( results[( i * columns ) + 0] );
351 sqlite3_free_table( results );
356 bool above41 =
false;
357 ret = sqlite3_get_table( sqlite_handle,
"select spatialite_version()", &results, &rows, &columns,
nullptr );
358 if ( ret == SQLITE_OK && rows == 1 && columns == 1 )
360 QString version = QString::fromUtf8( results[1] );
361 QStringList parts = version.split(
' ', QString::SkipEmptyParts );
362 if ( !parts.empty() )
364 QStringList verparts = parts.at( 0 ).split(
'.', QString::SkipEmptyParts );
365 above41 = verparts.size() >= 2 && ( verparts.at( 0 ).toInt() > 4 || ( verparts.at( 0 ).toInt() == 4 && verparts.at( 1 ).toInt() >= 1 ) );
369 sqlite3_free_table( results );
372 char *errMsg =
nullptr;
373 ret = sqlite3_exec( sqlite_handle, above41 ?
"SELECT InitSpatialMetadata(1)" :
"SELECT InitSpatialMetadata()",
nullptr,
nullptr, &errMsg );
375 if ( ret != SQLITE_OK )
377 QString errCause = tr(
"Unable to initialize SpatialMetadata:\n" );
378 errCause += QString::fromUtf8( errMsg );
379 showWarning( errCause );
380 sqlite3_free( errMsg );
383 spatial_ref_sys_init( sqlite_handle, 0 );
386 bool QgsOfflineEditing::createSpatialiteDB(
const QString &offlineDbPath )
389 char *errMsg =
nullptr;
390 QFile newDb( offlineDbPath );
391 if ( newDb.exists() )
393 QFile::remove( offlineDbPath );
398 QFileInfo fullPath = QFileInfo( offlineDbPath );
399 QDir path = fullPath.dir();
402 QDir().mkpath( path.absolutePath() );
405 QString dbPath = newDb.fileName();
407 ret = database.
open_v2( dbPath, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
nullptr );
411 QString errCause = tr(
"Could not create a new database\n" );
413 showWarning( errCause );
417 ret = sqlite3_exec( database.get(),
"PRAGMA foreign_keys = 1",
nullptr,
nullptr, &errMsg );
418 if ( ret != SQLITE_OK )
420 showWarning( tr(
"Unable to activate FOREIGN_KEY constraints" ) );
421 sqlite3_free( errMsg );
424 initializeSpatialMetadata( database.get() );
428 void QgsOfflineEditing::createLoggingTables(
sqlite3 *db )
431 QString sql = QStringLiteral(
"CREATE TABLE 'log_indices' ('name' TEXT, 'last_index' INTEGER)" );
434 sql = QStringLiteral(
"INSERT INTO 'log_indices' VALUES ('commit_no', 0)" );
437 sql = QStringLiteral(
"INSERT INTO 'log_indices' VALUES ('layer_id', 0)" );
441 sql = QStringLiteral(
"CREATE TABLE 'log_layer_ids' ('id' INTEGER, 'qgis_id' TEXT)" );
445 sql = QStringLiteral(
"CREATE TABLE 'log_fids' ('layer_id' INTEGER, 'offline_fid' INTEGER, 'remote_fid' INTEGER)" );
449 sql = QStringLiteral(
"CREATE TABLE 'log_added_attrs' ('layer_id' INTEGER, 'commit_no' INTEGER, " );
450 sql += QLatin1String(
"'name' TEXT, 'type' INTEGER, 'length' INTEGER, 'precision' INTEGER, 'comment' TEXT)" );
454 sql = QStringLiteral(
"CREATE TABLE 'log_added_features' ('layer_id' INTEGER, 'fid' INTEGER)" );
458 sql = QStringLiteral(
"CREATE TABLE 'log_removed_features' ('layer_id' INTEGER, 'fid' INTEGER)" );
462 sql = QStringLiteral(
"CREATE TABLE 'log_feature_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'attr' INTEGER, 'value' TEXT)" );
466 sql = QStringLiteral(
"CREATE TABLE 'log_geometry_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'geom_wkt' TEXT)" );
479 QString tableName = layer->
id();
480 QgsDebugMsgLevel( QString(
"Creating offline table %1 ..." ).arg( tableName ), 4 );
483 QString sql = QStringLiteral(
"CREATE TABLE '%1' (" ).arg( tableName );
486 for (
const auto &field : providerFields )
489 QVariant::Type type = field.type();
490 if ( type == QVariant::Int || type == QVariant::LongLong )
492 dataType = QStringLiteral(
"INTEGER" );
494 else if ( type == QVariant::Double )
496 dataType = QStringLiteral(
"REAL" );
498 else if ( type == QVariant::String )
500 dataType = QStringLiteral(
"TEXT" );
504 showWarning( tr(
"%1: Unknown data type %2. Not using type affinity for the field." ).arg( field.name(), QVariant::typeToName( type ) ) );
507 sql += delim + QStringLiteral(
"'%1' %2" ).arg( field.name(), dataType );
512 int rc = sqlExec( db, sql );
523 geomType = QStringLiteral(
"POINT" );
526 geomType = QStringLiteral(
"MULTIPOINT" );
529 geomType = QStringLiteral(
"LINESTRING" );
532 geomType = QStringLiteral(
"MULTILINESTRING" );
535 geomType = QStringLiteral(
"POLYGON" );
538 geomType = QStringLiteral(
"MULTIPOLYGON" );
545 QString zmInfo = QStringLiteral(
"XY" );
554 if ( layer->
crs().
authid().startsWith( QLatin1String(
"EPSG:" ), Qt::CaseInsensitive ) )
556 epsgCode = layer->
crs().
authid().mid( 5 );
561 showWarning( tr(
"Layer %1 has unsupported Coordinate Reference System (%2)." ).arg( layer->
name(), layer->
crs().
authid() ) );
564 QString sqlAddGeom = QStringLiteral(
"SELECT AddGeometryColumn('%1', 'Geometry', %2, '%3', '%4')" )
565 .arg( tableName, epsgCode, geomType, zmInfo );
568 QString sqlCreateIndex = QStringLiteral(
"SELECT CreateSpatialIndex('%1', 'Geometry')" ).arg( tableName );
570 if ( rc == SQLITE_OK )
572 rc = sqlExec( db, sqlAddGeom );
573 if ( rc == SQLITE_OK )
575 rc = sqlExec( db, sqlCreateIndex );
580 if ( rc == SQLITE_OK )
583 QString connectionString = QStringLiteral(
"dbname='%1' table='%2'%3 sql=" )
585 tableName, layer->
isSpatial() ?
"(Geometry)" :
"" );
587 layer->
name() +
" (offline)", QStringLiteral(
"spatialite" ) );
600 if ( !selectedFids.isEmpty() )
614 int featureCount = 1;
616 QList<QgsFeatureId> remoteFeatureIds;
619 remoteFeatureIds << f.
id();
626 for (
int it = 0; it < attrs.count(); ++it )
628 newAttrs[column++] = attrs.at( it );
642 int layerId = getOrCreateLayerId( db, newLayer->
id() );
643 QList<QgsFeatureId> offlineFeatureIds;
648 offlineFeatureIds << f.
id();
652 sqlExec( db, QStringLiteral(
"BEGIN" ) );
653 int remoteCount = remoteFeatureIds.size();
654 for (
int i = 0; i < remoteCount; i++ )
657 if ( i < offlineFeatureIds.count() )
659 addFidLookup( db, layerId, offlineFeatureIds.at( i ), remoteFeatureIds.at( i ) );
663 showWarning( tr(
"Feature cannot be copied to the offline layer, please check if the online layer '%1' is still accessible." ).arg( layer->
name() ) );
668 sqlExec( db, QStringLiteral(
"COMMIT" ) );
672 showWarning( newLayer->
commitErrors().join( QStringLiteral(
"\n" ) ) );
684 QList<QgsMapLayer *>() << newLayer );
687 copySymbology( layer, newLayer );
692 if ( layerTreeLayer )
695 if ( parentTreeGroup )
697 int index = parentTreeGroup->
children().indexOf( layerTreeLayer );
700 if ( newLayerTreeLayer )
711 updateRelations( layer, newLayer );
712 updateMapThemes( layer, newLayer );
713 updateLayerOrder( layer, newLayer );
722 void QgsOfflineEditing::applyAttributesAdded(
QgsVectorLayer *remoteLayer,
sqlite3 *db,
int layerId,
int commitNo )
724 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 );
725 QList<QgsField> fields = sqlQueryAttributesAdded( db, sql );
728 QList<QgsVectorDataProvider::NativeType> nativeTypes = provider->
nativeTypes();
731 QMap < QVariant::Type, QString > typeNameLookup;
732 for (
int i = 0; i < nativeTypes.size(); i++ )
740 for (
int i = 0; i < fields.size(); i++ )
744 if ( typeNameLookup.contains( field.
type() ) )
746 QString typeName = typeNameLookup[ field.
type()];
752 showWarning( QStringLiteral(
"Could not add attribute '%1' of type %2" ).arg( field.
name() ).arg( field.
type() ) );
761 QString sql = QStringLiteral(
"SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
762 QList<int> featureIdInts = sqlQueryInts( db, sql );
764 Q_FOREACH (
int id, featureIdInts )
785 for ( QgsFeatureList::iterator it = features.begin(); it != features.end(); ++it )
789 QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
792 for (
int it = 0; it < attrs.count(); ++it )
794 newAttrs[ attrLookup[ it ] ] = attrs.at( it );
805 void QgsOfflineEditing::applyFeaturesRemoved(
QgsVectorLayer *remoteLayer,
sqlite3 *db,
int layerId )
807 QString sql = QStringLiteral(
"SELECT \"fid\" FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
813 for ( QgsFeatureIds::const_iterator it = values.constBegin(); it != values.constEnd(); ++it )
824 QString sql = QStringLiteral(
"SELECT \"fid\", \"attr\", \"value\" FROM 'log_feature_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2 " ).arg( layerId ).arg( commitNo );
825 AttributeValueChanges values = sqlQueryAttributeValueChanges( db, sql );
829 QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
831 for (
int i = 0; i < values.size(); i++ )
833 QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid );
834 QgsDebugMsgLevel( QString(
"Offline changeAttributeValue %1 = %2" ).arg( QString( attrLookup[ values.at( i ).attr ] ), values.at( i ).value ), 4 );
835 remoteLayer->
changeAttributeValue( fid, attrLookup[ values.at( i ).attr ], values.at( i ).value );
841 void QgsOfflineEditing::applyGeometryChanges(
QgsVectorLayer *remoteLayer,
sqlite3 *db,
int layerId,
int commitNo )
843 QString sql = QStringLiteral(
"SELECT \"fid\", \"geom_wkt\" FROM 'log_geometry_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo );
844 GeometryChanges values = sqlQueryGeometryChanges( db, sql );
848 for (
int i = 0; i < values.size(); i++ )
850 QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid );
874 if ( offlineFid( db, layerId, f.
id() ) == -1 )
876 newRemoteFids[ f.
id()] =
true;
884 QString sql = QStringLiteral(
"SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
885 QList<int> newOfflineFids = sqlQueryInts( db, sql );
887 if ( newRemoteFids.size() != newOfflineFids.size() )
895 sqlExec( db, QStringLiteral(
"BEGIN" ) );
896 for ( QMap<QgsFeatureId, bool>::const_iterator it = newRemoteFids.constBegin(); it != newRemoteFids.constEnd(); ++it )
898 addFidLookup( db, layerId, newOfflineFids.at( i++ ), it.key() );
900 sqlExec( db, QStringLiteral(
"COMMIT" ) );
910 if ( error.isEmpty() )
914 if ( !error.isEmpty() )
916 showWarning( error );
923 QList<QgsRelation> relations;
946 QStringList mapThemeNames = mapThemeCollection->
mapThemes();
948 Q_FOREACH (
const QString &mapThemeName, mapThemeNames )
954 if ( layerRecord.
layer() == sourceLayer )
956 layerRecord.
setLayer( targetLayer );
970 auto iterator = layerOrder.begin();
972 while ( iterator != layerOrder.end() )
974 if ( *iterator == targetLayer )
976 iterator = layerOrder.erase( iterator );
977 if ( iterator == layerOrder.end() )
981 if ( *iterator == sourceLayer )
983 *iterator = targetLayer;
998 QMap <
int ,
int > attrLookup;
1000 for (
int i = 0; i < remoteAttrs.size(); i++ )
1002 attrLookup.insert( offlineAttrs.at( i ), remoteAttrs.at( i ) );
1008 void QgsOfflineEditing::showWarning(
const QString &message )
1010 emit
warning( tr(
"Offline Editing Plugin" ), message );
1017 if ( !dbPath.isEmpty() )
1020 int rc = database.
open( absoluteDbPath );
1021 if ( rc != SQLITE_OK )
1023 QgsDebugMsg(
"Could not open the SpatiaLite logging database" );
1024 showWarning( tr(
"Could not open the SpatiaLite logging database" ) );
1034 int QgsOfflineEditing::getOrCreateLayerId(
sqlite3 *db,
const QString &qgisLayerId )
1036 QString sql = QStringLiteral(
"SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
1037 int layerId = sqlQueryInt( db, sql, -1 );
1038 if ( layerId == -1 )
1041 sql = QStringLiteral(
"SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'layer_id'" );
1042 int newLayerId = sqlQueryInt( db, sql, -1 );
1045 sql = QStringLiteral(
"INSERT INTO 'log_layer_ids' VALUES (%1, '%2')" ).arg( newLayerId ).arg( qgisLayerId );
1050 sql = QStringLiteral(
"UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'layer_id'" ).arg( newLayerId + 1 );
1053 layerId = newLayerId;
1059 int QgsOfflineEditing::getCommitNo(
sqlite3 *db )
1061 QString sql = QStringLiteral(
"SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'commit_no'" );
1062 return sqlQueryInt( db, sql, -1 );
1065 void QgsOfflineEditing::increaseCommitNo(
sqlite3 *db )
1067 QString sql = QStringLiteral(
"UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'commit_no'" ).arg( getCommitNo( db ) + 1 );
1073 QString sql = QStringLiteral(
"INSERT INTO 'log_fids' VALUES ( %1, %2, %3 )" ).arg( layerId ).arg( offlineFid ).arg( remoteFid );
1079 QString sql = QStringLiteral(
"SELECT \"remote_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2" ).arg( layerId ).arg( offlineFid );
1080 return sqlQueryInt( db, sql, -1 );
1085 QString sql = QStringLiteral(
"SELECT \"offline_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"remote_fid\" = %2" ).arg( layerId ).arg( remoteFid );
1086 return sqlQueryInt( db, sql, -1 );
1091 QString sql = QStringLiteral(
"SELECT COUNT(\"fid\") FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( fid );
1092 return ( sqlQueryInt( db, sql, 0 ) > 0 );
1095 int QgsOfflineEditing::sqlExec(
sqlite3 *db,
const QString &sql )
1097 char *errmsg =
nullptr;
1098 int rc = sqlite3_exec( db, sql.toUtf8(),
nullptr,
nullptr, &errmsg );
1099 if ( rc != SQLITE_OK )
1101 showWarning( errmsg );
1106 int QgsOfflineEditing::sqlQueryInt(
sqlite3 *db,
const QString &sql,
int defaultValue )
1108 sqlite3_stmt *stmt =
nullptr;
1109 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1111 showWarning( sqlite3_errmsg( db ) );
1112 return defaultValue;
1115 int value = defaultValue;
1116 int ret = sqlite3_step( stmt );
1117 if ( ret == SQLITE_ROW )
1119 value = sqlite3_column_int( stmt, 0 );
1121 sqlite3_finalize( stmt );
1126 QList<int> QgsOfflineEditing::sqlQueryInts(
sqlite3 *db,
const QString &sql )
1130 sqlite3_stmt *stmt =
nullptr;
1131 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1133 showWarning( sqlite3_errmsg( db ) );
1137 int ret = sqlite3_step( stmt );
1138 while ( ret == SQLITE_ROW )
1140 values << sqlite3_column_int( stmt, 0 );
1142 ret = sqlite3_step( stmt );
1144 sqlite3_finalize( stmt );
1149 QList<QgsField> QgsOfflineEditing::sqlQueryAttributesAdded(
sqlite3 *db,
const QString &sql )
1151 QList<QgsField> values;
1153 sqlite3_stmt *stmt =
nullptr;
1154 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1156 showWarning( sqlite3_errmsg( db ) );
1160 int ret = sqlite3_step( stmt );
1161 while ( ret == SQLITE_ROW )
1163 QgsField field( QString( reinterpret_cast< const char * >( sqlite3_column_text( stmt, 0 ) ) ),
1164 static_cast< QVariant::Type >( sqlite3_column_int( stmt, 1 ) ),
1165 QLatin1String(
"" ),
1166 sqlite3_column_int( stmt, 2 ),
1167 sqlite3_column_int( stmt, 3 ),
1168 QString( reinterpret_cast< const char * >( sqlite3_column_text( stmt, 4 ) ) ) );
1171 ret = sqlite3_step( stmt );
1173 sqlite3_finalize( stmt );
1182 sqlite3_stmt *stmt =
nullptr;
1183 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1185 showWarning( sqlite3_errmsg( db ) );
1189 int ret = sqlite3_step( stmt );
1190 while ( ret == SQLITE_ROW )
1192 values << sqlite3_column_int( stmt, 0 );
1194 ret = sqlite3_step( stmt );
1196 sqlite3_finalize( stmt );
1201 QgsOfflineEditing::AttributeValueChanges QgsOfflineEditing::sqlQueryAttributeValueChanges(
sqlite3 *db,
const QString &sql )
1203 AttributeValueChanges values;
1205 sqlite3_stmt *stmt =
nullptr;
1206 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1208 showWarning( sqlite3_errmsg( db ) );
1212 int ret = sqlite3_step( stmt );
1213 while ( ret == SQLITE_ROW )
1215 AttributeValueChange change;
1216 change.fid = sqlite3_column_int( stmt, 0 );
1217 change.attr = sqlite3_column_int( stmt, 1 );
1218 change.value = QString( reinterpret_cast< const char * >( sqlite3_column_text( stmt, 2 ) ) );
1221 ret = sqlite3_step( stmt );
1223 sqlite3_finalize( stmt );
1228 QgsOfflineEditing::GeometryChanges QgsOfflineEditing::sqlQueryGeometryChanges(
sqlite3 *db,
const QString &sql )
1230 GeometryChanges values;
1232 sqlite3_stmt *stmt =
nullptr;
1233 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1235 showWarning( sqlite3_errmsg( db ) );
1239 int ret = sqlite3_step( stmt );
1240 while ( ret == SQLITE_ROW )
1242 GeometryChange change;
1243 change.fid = sqlite3_column_int( stmt, 0 );
1244 change.geom_wkt = QString( reinterpret_cast< const char * >( sqlite3_column_text( stmt, 1 ) ) );
1247 ret = sqlite3_step( stmt );
1249 sqlite3_finalize( stmt );
1254 void QgsOfflineEditing::committedAttributesAdded(
const QString &qgisLayerId,
const QList<QgsField> &addedAttributes )
1257 if ( !database.get() )
1261 int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1262 int commitNo = getCommitNo( database.get() );
1264 for ( QList<QgsField>::const_iterator it = addedAttributes.begin(); it != addedAttributes.end(); ++it )
1267 QString sql = QStringLiteral(
"INSERT INTO 'log_added_attrs' VALUES ( %1, %2, '%3', %4, %5, %6, '%7' )" )
1270 .arg( field.
name() )
1271 .arg( field.
type() )
1275 sqlExec( database.get(), sql );
1278 increaseCommitNo( database.get() );
1279 sqlite3_close( database.get() );
1282 void QgsOfflineEditing::committedFeaturesAdded(
const QString &qgisLayerId,
const QgsFeatureList &addedFeatures )
1285 if ( !database.get() )
1289 int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1296 QString sql = QStringLiteral(
"SELECT ROWID FROM '%1' ORDER BY ROWID DESC LIMIT %2" ).arg( uri.
table() ).arg( addedFeatures.size() );
1297 QList<int> newFeatureIds = sqlQueryInts( database.get(), sql );
1298 for (
int i = newFeatureIds.size() - 1; i >= 0; i-- )
1300 QString sql = QStringLiteral(
"INSERT INTO 'log_added_features' VALUES ( %1, %2 )" )
1302 .arg( newFeatureIds.at( i ) );
1303 sqlExec( database.get(), sql );
1306 sqlite3_close( database.get() );
1309 void QgsOfflineEditing::committedFeaturesRemoved(
const QString &qgisLayerId,
const QgsFeatureIds &deletedFeatureIds )
1312 if ( !database.get() )
1316 int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1318 for ( QgsFeatureIds::const_iterator it = deletedFeatureIds.begin(); it != deletedFeatureIds.end(); ++it )
1320 if ( isAddedFeature( database.get(), layerId, *it ) )
1323 QString sql = QStringLiteral(
"DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( *it );
1324 sqlExec( database.get(), sql );
1328 QString sql = QStringLiteral(
"INSERT INTO 'log_removed_features' VALUES ( %1, %2)" )
1331 sqlExec( database.get(), sql );
1335 sqlite3_close( database.get() );
1338 void QgsOfflineEditing::committedAttributeValuesChanges(
const QString &qgisLayerId,
const QgsChangedAttributesMap &changedAttrsMap )
1341 if ( !database.get() )
1345 int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1346 int commitNo = getCommitNo( database.get() );
1348 for ( QgsChangedAttributesMap::const_iterator cit = changedAttrsMap.begin(); cit != changedAttrsMap.end(); ++cit )
1351 if ( isAddedFeature( database.get(), layerId, fid ) )
1357 for ( QgsAttributeMap::const_iterator it = attrMap.constBegin(); it != attrMap.constEnd(); ++it )
1359 QString sql = QStringLiteral(
"INSERT INTO 'log_feature_updates' VALUES ( %1, %2, %3, %4, '%5' )" )
1364 .arg( it.value().toString() );
1365 sqlExec( database.get(), sql );
1369 increaseCommitNo( database.get() );
1370 sqlite3_close( database.get() );
1373 void QgsOfflineEditing::committedGeometriesChanges(
const QString &qgisLayerId,
const QgsGeometryMap &changedGeometries )
1376 if ( !database.get() )
1380 int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1381 int commitNo = getCommitNo( database.get() );
1383 for ( QgsGeometryMap::const_iterator it = changedGeometries.begin(); it != changedGeometries.end(); ++it )
1386 if ( isAddedFeature( database.get(), layerId, fid ) )
1392 QString sql = QStringLiteral(
"INSERT INTO 'log_geometry_updates' VALUES ( %1, %2, %3, '%4' )" )
1396 .arg( geom.
asWkt() );
1397 sqlExec( database.get(), sql );
1402 increaseCommitNo( database.get() );
1403 sqlite3_close( database.get() );
1406 void QgsOfflineEditing::startListenFeatureChanges()
1414 this, &QgsOfflineEditing::committedAttributesAdded );
1416 this, &QgsOfflineEditing::committedAttributeValuesChanges );
1418 this, &QgsOfflineEditing::committedGeometriesChanges );
1421 this, &QgsOfflineEditing::committedFeaturesAdded );
1423 this, &QgsOfflineEditing::committedFeaturesRemoved );
1426 void QgsOfflineEditing::stopListenFeatureChanges()
1434 this, &QgsOfflineEditing::committedAttributesAdded );
1436 this, &QgsOfflineEditing::committedAttributeValuesChanges );
1438 this, &QgsOfflineEditing::committedGeometriesChanges );
1441 this, &QgsOfflineEditing::committedFeaturesAdded );
1443 this, &QgsOfflineEditing::committedFeaturesRemoved );
1446 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)
Sets 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
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
#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
Returns 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
Returns the status of the layer.
bool isOfflineProject() const
Returns true if current project is offline.
void committedFeaturesRemoved(const QString &layerId, const QgsFeatureIds &deletedFeatureIds)
This signal is emitted, when features are deleted from the provider.
int count() const
Returns number of items.
static bool hasZ(Type type)
Tests whether a WKB type contains the z-dimension.
QList< QgsMapLayer * > customLayerOrder() const
The order in which layers will be rendered on the canvas.
virtual QString name() const =0
Returns 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...
Type
The WKB type describes the number of dimensions a geometry has.
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()
Gets 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
Returns a list of the selected features IDs in this layer.
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()
Gets pointer to the parent. If parent is a null pointer, the node is a root node. ...
void committedFeaturesAdded(const QString &layerId, const QgsFeatureList &addedFeatures)
This signal is emitted, when features are added to the provider.
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
Gets 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
Returns 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)
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
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.
static QString displayString(Type type)
Returns a display string type for a WKB type, e.g., the geometry name used in WKT geometry representa...
void progressModeSet(QgsOfflineEditing::ProgressMode mode, int maximum)
Is emitted when the mode for the progress of the current operation is set.
QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
void removeMapLayers(const QStringList &layerIds)
Remove a set of layers from the registry by layer ID.
static bool hasM(Type type)
Tests whether a WKB type contains m values.
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
Returns 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.
static Type flatType(Type type)
Returns the flat type for a WKB type.
#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.