21#include <ogr_srs_api.h>
41#include <QDomDocument>
44#include <QRegularExpression>
47#include "moc_qgsofflineediting.cpp"
49using namespace Qt::StringLiterals;
59#include <spatialite.h>
63#define CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE "isOfflineEditable"
64#define CUSTOM_PROPERTY_REMOTE_SOURCE "remoteSource"
65#define CUSTOM_PROPERTY_REMOTE_PROVIDER "remoteProvider"
66#define CUSTOM_SHOW_FEATURE_COUNT "showFeatureCount"
67#define CUSTOM_PROPERTY_ORIGINAL_LAYERID "remoteLayerId"
68#define CUSTOM_PROPERTY_LAYERNAME_SUFFIX "layerNameSuffix"
69#define PROJECT_ENTRY_SCOPE_OFFLINE "OfflineEditingPlugin"
70#define PROJECT_ENTRY_KEY_OFFLINE_DB_PATH "/OfflineDbPath"
90 const QString &offlineDataPath,
const QString &offlineDbFile,
const QStringList &layerIds,
bool onlySelected,
ContainerType containerType,
const QString &layerNameSuffix
93 if ( layerIds.isEmpty() )
98 const QString dbPath = QDir( offlineDataPath ).absoluteFilePath( offlineDbFile );
99 if ( createOfflineDb( dbPath, containerType ) )
102 const int rc = database.
open( dbPath );
103 if ( rc != SQLITE_OK )
105 showWarning( tr(
"Could not open the SpatiaLite database" ) );
110 createLoggingTables( database.get() );
115 for (
int i = 0; i < layerIds.count(); i++ )
123 convertToOfflineLayer( vl, database.get(), dbPath, onlySelected, containerType, layerNameSuffix );
131 if ( projectTitle.isEmpty() )
135 projectTitle +=
" (offline)"_L1;
166 QMap<int, std::shared_ptr<QgsVectorLayer>> remoteLayersByOfflineId;
167 QMap<int, QgsVectorLayer *> offlineLayersByOfflineId;
169 for ( QMap<QString, QgsMapLayer *>::iterator layer_it = mapLayers.begin(); layer_it != mapLayers.end(); ++layer_it )
171 QgsVectorLayer *offlineLayer( qobject_cast<QgsVectorLayer *>( layer_it.value() ) );
173 if ( !offlineLayer || !offlineLayer->
isValid() )
175 QgsDebugMsgLevel( u
"Skipping offline layer %1 because it is an invalid layer"_s.arg( layer_it.key() ), 4 );
184 QString remoteName = offlineLayer->
name();
186 if ( remoteName.endsWith( remoteNameSuffix ) )
187 remoteName.chop( remoteNameSuffix.size() );
190 auto remoteLayer = std::make_shared<QgsVectorLayer>( remoteSource, remoteName, remoteProvider, options );
192 if ( !remoteLayer->isValid() )
194 QgsDebugMsgLevel( u
"Skipping offline layer %1 because it failed to recreate its corresponding remote layer"_s.arg( offlineLayer->
id() ), 4 );
199 if ( remoteLayer->providerType().contains(
"WFS"_L1, Qt::CaseInsensitive ) )
210 const QString sql = u
"SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'"_s.arg( offlineLayer->
id() );
211 const int layerId = sqlQueryInt( database.get(), sql, -1 );
215 QgsDebugMsgLevel( u
"Skipping offline layer %1 because it failed to determine the offline editing layer id"_s.arg( offlineLayer->
id() ), 4 );
219 remoteLayersByOfflineId.insert( layerId, remoteLayer );
220 offlineLayersByOfflineId.insert( layerId, offlineLayer );
223 QgsDebugMsgLevel( u
"Found %1 offline layers in total"_s.arg( offlineLayersByOfflineId.count() ), 4 );
225 QMap<QPair<QString, QString>, std::shared_ptr<QgsTransactionGroup>> transactionGroups;
226 if ( useTransaction )
228 for (
const std::shared_ptr<QgsVectorLayer> &remoteLayer : std::as_const( remoteLayersByOfflineId ) )
231 const QPair<QString, QString> pair( remoteLayer->providerType(), connectionString );
232 std::shared_ptr<QgsTransactionGroup> transactionGroup = transactionGroups.value( pair );
234 if ( !transactionGroup )
235 transactionGroup = std::make_shared<QgsTransactionGroup>();
237 if ( !transactionGroup->addLayer( remoteLayer.get() ) )
239 QgsDebugMsgLevel( u
"Failed to add a layer %1 into transaction group, will be modified without transaction"_s.arg( remoteLayer->name() ), 4 );
243 transactionGroups.insert( pair, transactionGroup );
246 QgsDebugMsgLevel( u
"Created %1 transaction groups"_s.arg( transactionGroups.count() ), 4 );
249 const QList<int> offlineIds = remoteLayersByOfflineId.keys();
250 for (
int offlineLayerId : offlineIds )
252 std::shared_ptr<QgsVectorLayer> remoteLayer = remoteLayersByOfflineId.value( offlineLayerId );
253 QgsVectorLayer *offlineLayer = offlineLayersByOfflineId.value( offlineLayerId );
256 QgsDebugMsgLevel( u
"Failed to find offline layer %1"_s.arg( offlineLayerId ), 4 );
261 if ( !remoteLayer->startEditing() && !remoteLayer->isEditable() )
263 QgsDebugMsgLevel( u
"Failed to turn layer %1 into editing mode"_s.arg( remoteLayer->name() ), 4 );
268 const int commitNo = getCommitNo( database.get() );
271 for (
int i = 0; i < commitNo; i++ )
275 applyAttributesAdded( remoteLayer.get(), database.get(), offlineLayerId, i );
276 applyAttributeValueChanges( offlineLayer, remoteLayer.get(), database.get(), offlineLayerId, i );
277 applyGeometryChanges( remoteLayer.get(), database.get(), offlineLayerId, i );
280 applyFeaturesAdded( offlineLayer, remoteLayer.get(), database.get(), offlineLayerId );
281 applyFeaturesRemoved( remoteLayer.get(), database.get(), offlineLayerId );
285 for (
int offlineLayerId : offlineIds )
287 std::shared_ptr<QgsVectorLayer> remoteLayer = remoteLayersByOfflineId[offlineLayerId];
288 QgsVectorLayer *offlineLayer = offlineLayersByOfflineId[offlineLayerId];
290 if ( !remoteLayer->isEditable() )
293 if ( remoteLayer->commitChanges() )
296 updateFidLookup( remoteLayer.get(), database.get(), offlineLayerId );
300 sql = u
"DELETE FROM 'log_added_attrs' WHERE \"layer_id\" = %1"_s.arg( offlineLayerId );
301 sqlExec( database.get(), sql );
302 sql = u
"DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1"_s.arg( offlineLayerId );
303 sqlExec( database.get(), sql );
304 sql = u
"DELETE FROM 'log_removed_features' WHERE \"layer_id\" = %1"_s.arg( offlineLayerId );
305 sqlExec( database.get(), sql );
306 sql = u
"DELETE FROM 'log_feature_updates' WHERE \"layer_id\" = %1"_s.arg( offlineLayerId );
307 sqlExec( database.get(), sql );
308 sql = u
"DELETE FROM 'log_geometry_updates' WHERE \"layer_id\" = %1"_s.arg( offlineLayerId );
309 sqlExec( database.get(), sql );
313 showWarning( remoteLayer->commitErrors().join( QLatin1Char(
'\n' ) ) );
320 remoteLayer->reload();
321 offlineLayer->
setDataSource( remoteLayer->source(), remoteLayer->name(), remoteLayer->dataProvider()->name() );
337 const QgsFields fields = remoteLayer->fields();
338 for (
const QgsField &field : fields )
340 if ( !remoteLayer->dataProvider()->defaultValueClause( remoteLayer->fields().fieldOriginIndex( remoteLayer->fields().indexOf( field.name() ) ) ).isEmpty() )
352 const QString sql = u
"UPDATE 'log_indices' SET 'last_index' = 0 WHERE \"name\" = 'commit_no'"_s;
353 sqlExec( database.get(), sql );
357void QgsOfflineEditing::initializeSpatialMetadata(
sqlite3 *sqlite_handle )
359#ifdef HAVE_SPATIALITE
361 if ( !sqlite_handle )
364 char **results =
nullptr;
366 int ret = sqlite3_get_table( sqlite_handle,
"select count(*) from sqlite_master", &results, &rows, &columns,
nullptr );
367 if ( ret != SQLITE_OK )
372 for (
int i = 1; i <= rows; i++ )
373 count = atoi( results[( i * columns ) + 0] );
376 sqlite3_free_table( results );
381 bool above41 =
false;
382 ret = sqlite3_get_table( sqlite_handle,
"select spatialite_version()", &results, &rows, &columns,
nullptr );
383 if ( ret == SQLITE_OK && rows == 1 && columns == 1 )
385 const QString version = QString::fromUtf8( results[1] );
386 const QStringList parts = version.split(
' ', Qt::SkipEmptyParts );
387 if ( !parts.empty() )
389 const QStringList verparts = parts.at( 0 ).split(
'.', Qt::SkipEmptyParts );
390 above41 = verparts.size() >= 2 && ( verparts.at( 0 ).toInt() > 4 || ( verparts.at( 0 ).toInt() == 4 && verparts.at( 1 ).toInt() >= 1 ) );
394 sqlite3_free_table( results );
397 char *errMsg =
nullptr;
398 ret = sqlite3_exec( sqlite_handle, above41 ?
"SELECT InitSpatialMetadata(1)" :
"SELECT InitSpatialMetadata()",
nullptr,
nullptr, &errMsg );
400 if ( ret != SQLITE_OK )
402 QString errCause = tr(
"Unable to initialize SpatialMetadata:\n" );
403 errCause += QString::fromUtf8( errMsg );
404 showWarning( errCause );
405 sqlite3_free( errMsg );
408 spatial_ref_sys_init( sqlite_handle, 0 );
410 ( void ) sqlite_handle;
414bool QgsOfflineEditing::createOfflineDb(
const QString &offlineDbPath, ContainerType containerType )
417 char *errMsg =
nullptr;
418 const QFile newDb( offlineDbPath );
419 if ( newDb.exists() )
421 QFile::remove( offlineDbPath );
426 const QFileInfo fullPath = QFileInfo( offlineDbPath );
427 const QDir path = fullPath.dir();
430 QDir().mkpath( path.absolutePath() );
433 const QString dbPath = newDb.fileName();
436 switch ( containerType )
440 OGRSFDriverH hGpkgDriver = OGRGetDriverByName(
"GPKG" );
443 showWarning( tr(
"Creation of database failed. GeoPackage driver not found." ) );
450 showWarning( tr(
"Creation of database failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
461 spatialite_database_unique_ptr database;
462 ret = database.
open_v2( dbPath, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
nullptr );
466 QString errCause = tr(
"Could not create a new database\n" );
468 showWarning( errCause );
472 ret = sqlite3_exec( database.get(),
"PRAGMA foreign_keys = 1",
nullptr,
nullptr, &errMsg );
473 if ( ret != SQLITE_OK )
475 showWarning( tr(
"Unable to activate FOREIGN_KEY constraints" ) );
476 sqlite3_free( errMsg );
479 initializeSpatialMetadata( database.get() );
483void QgsOfflineEditing::createLoggingTables(
sqlite3 *db )
486 QString sql = u
"CREATE TABLE 'log_indices' ('name' TEXT, 'last_index' INTEGER)"_s;
489 sql = u
"INSERT INTO 'log_indices' VALUES ('commit_no', 0)"_s;
492 sql = u
"INSERT INTO 'log_indices' VALUES ('layer_id', 0)"_s;
496 sql = u
"CREATE TABLE 'log_layer_ids' ('id' INTEGER, 'qgis_id' TEXT)"_s;
500 sql = u
"CREATE TABLE 'log_fids' ('layer_id' INTEGER, 'offline_fid' INTEGER, 'remote_fid' INTEGER, 'remote_pk' TEXT)"_s;
504 sql = u
"CREATE TABLE 'log_added_attrs' ('layer_id' INTEGER, 'commit_no' INTEGER, "_s;
505 sql +=
"'name' TEXT, 'type' INTEGER, 'length' INTEGER, 'precision' INTEGER, 'comment' TEXT)"_L1;
509 sql = u
"CREATE TABLE 'log_added_features' ('layer_id' INTEGER, 'fid' INTEGER)"_s;
513 sql = u
"CREATE TABLE 'log_removed_features' ('layer_id' INTEGER, 'fid' INTEGER)"_s;
517 sql = u
"CREATE TABLE 'log_feature_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'attr' INTEGER, 'value' TEXT)"_s;
521 sql = u
"CREATE TABLE 'log_geometry_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'geom_wkt' TEXT)"_s;
529void QgsOfflineEditing::convertToOfflineLayer(
QgsVectorLayer *layer,
sqlite3 *db,
const QString &offlineDbPath,
bool onlySelected, ContainerType containerType,
const QString &layerNameSuffix )
531 if ( !layer || !layer->
isValid() )
533 QgsDebugMsgLevel( u
"Layer %1 is invalid and cannot be copied"_s.arg( layer ? layer->
id() : u
"<UNKNOWN>"_s ), 4 );
537 const QString tableName = layer->
id();
541 std::unique_ptr<QgsVectorLayer> newLayer;
543 switch ( containerType )
547#ifdef HAVE_SPATIALITE
549 QString sql = u
"CREATE TABLE '%1' ("_s.arg( tableName );
552 for (
const auto &field : providerFields )
555 const QMetaType::Type type = field.type();
556 if ( type == QMetaType::Type::Int || type == QMetaType::Type::LongLong )
558 dataType = u
"INTEGER"_s;
560 else if ( type == QMetaType::Type::Double )
562 dataType = u
"REAL"_s;
564 else if ( type == QMetaType::Type::QString )
566 dataType = u
"TEXT"_s;
568 else if ( type == QMetaType::Type::QStringList || type == QMetaType::Type::QVariantList )
570 dataType = u
"TEXT"_s;
571 showWarning( tr(
"Field '%1' from layer %2 has been converted from a list to a string of comma-separated values." ).arg( field.name(), layer->
name() ) );
575 showWarning( tr(
"%1: Unknown data type %2. Not using type affinity for the field." ).arg( field.name(), QVariant::typeToName( type ) ) );
578 sql += delim + u
"'%1' %2"_s.arg( field.name(), dataType );
583 int rc = sqlExec( db, sql );
594 geomType = u
"POINT"_s;
597 geomType = u
"MULTIPOINT"_s;
600 geomType = u
"LINESTRING"_s;
603 geomType = u
"MULTILINESTRING"_s;
606 geomType = u
"POLYGON"_s;
609 geomType = u
"MULTIPOLYGON"_s;
616 QString zmInfo = u
"XY"_s;
625 if ( layer->
crs().
authid().startsWith(
"EPSG:"_L1, Qt::CaseInsensitive ) )
627 epsgCode = layer->
crs().
authid().mid( 5 );
632 showWarning( tr(
"Layer %1 has unsupported Coordinate Reference System (%2)." ).arg( layer->
name(), layer->
crs().
authid() ) );
635 const QString sqlAddGeom = u
"SELECT AddGeometryColumn('%1', 'Geometry', %2, '%3', '%4')"_s.arg( tableName, epsgCode, geomType, zmInfo );
638 const QString sqlCreateIndex = u
"SELECT CreateSpatialIndex('%1', 'Geometry')"_s.arg( tableName );
640 if ( rc == SQLITE_OK )
642 rc = sqlExec( db, sqlAddGeom );
643 if ( rc == SQLITE_OK )
645 rc = sqlExec( db, sqlCreateIndex );
650 if ( rc != SQLITE_OK )
652 showWarning( tr(
"Filling SpatiaLite for layer %1 failed" ).arg( layer->
name() ) );
657 const QString connectionString = u
"dbname='%1' table='%2'%3 sql="_s.arg( offlineDbPath, tableName, layer->
isSpatial() ?
"(Geometry)" :
"" );
659 newLayer = std::make_unique<QgsVectorLayer>( connectionString, layer->
name() + layerNameSuffix, u
"spatialite"_s, options );
663 showWarning( tr(
"No Spatialite support available" ) );
671 char **options =
nullptr;
673 options = CSLSetNameValue( options,
"OVERWRITE",
"YES" );
674 options = CSLSetNameValue( options,
"IDENTIFIER", tr(
"%1 (offline)" ).arg( layer->
id() ).toUtf8().constData() );
675 options = CSLSetNameValue( options,
"DESCRIPTION", layer->
dataComment().toUtf8().constData() );
678 const QString fidBase( u
"fid"_s );
679 QString fid = fidBase;
683 fid = fidBase +
'_' + QString::number( counter );
686 if ( counter == 10000 )
688 showWarning( tr(
"Cannot make FID-name for GPKG " ) );
692 options = CSLSetNameValue( options,
"FID", fid.toUtf8().constData() );
696 options = CSLSetNameValue( options,
"GEOMETRY_COLUMN",
"geom" );
697 options = CSLSetNameValue( options,
"SPATIAL_INDEX",
"YES" );
700 OGRSFDriverH hDriver =
nullptr;
703 OGRLayerH hLayer = OGR_DS_CreateLayer( hDS.get(), tableName.toUtf8().constData(), hSRS,
static_cast<OGRwkbGeometryType
>( layer->
wkbType() ), options );
704 CSLDestroy( options );
709 showWarning( tr(
"Creation of layer failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
714 for (
const auto &field : providerFields )
716 const QString fieldName( field.name() );
717 const QMetaType::Type type = field.type();
718 OGRFieldType ogrType( OFTString );
719 OGRFieldSubType ogrSubType = OFSTNone;
720 if ( type == QMetaType::Type::Int )
721 ogrType = OFTInteger;
722 else if ( type == QMetaType::Type::LongLong )
723 ogrType = OFTInteger64;
724 else if ( type == QMetaType::Type::Double )
726 else if ( type == QMetaType::Type::QTime )
728 else if ( type == QMetaType::Type::QDate )
730 else if ( type == QMetaType::Type::QDateTime )
731 ogrType = OFTDateTime;
732 else if ( type == QMetaType::Type::Bool )
734 ogrType = OFTInteger;
735 ogrSubType = OFSTBoolean;
737 else if ( type == QMetaType::Type::QStringList || type == QMetaType::Type::QVariantList )
740 ogrSubType = OFSTJSON;
741 showWarning( tr(
"Field '%1' from layer %2 has been converted from a list to a JSON-formatted string value." ).arg( fieldName, layer->
name() ) );
746 const int ogrWidth = field.length();
749 OGR_Fld_SetWidth( fld.get(), ogrWidth );
750 if ( ogrSubType != OFSTNone )
751 OGR_Fld_SetSubType( fld.get(), ogrSubType );
753 if ( OGR_L_CreateField( hLayer, fld.get(),
true ) != OGRERR_NONE )
755 showWarning( tr(
"Creation of field %1 failed (OGR error: %2)" ).arg( fieldName, QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
763 OGR_L_ResetReading( hLayer );
764 if ( CPLGetLastErrorType() != CE_None )
766 const QString msg( tr(
"Creation of layer failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
772 const QString uri = u
"%1|layername=%2|option:QGIS_FORCE_WAL=ON"_s.arg( offlineDbPath, tableName );
774 newLayer = std::make_unique<QgsVectorLayer>( uri, layer->
name() + layerNameSuffix, u
"ogr"_s, layerOptions );
779 if ( newLayer && newLayer->isValid() )
782 newLayer->startEditing();
785 QgsFeatureRequest req;
790 if ( !selectedFids.isEmpty() )
804 long long featureCount = 1;
805 const int remotePkIdx = getLayerPkIdx( layer );
807 QList<QgsFeatureId> remoteFeatureIds;
808 QStringList remoteFeaturePks;
811 remoteFeatureIds << f.
id();
812 remoteFeaturePks << ( remotePkIdx >= 0 ? f.
attribute( remotePkIdx ).toString() : QString() );
819 QgsAttributes newAttrs( containerType ==
GPKG ? attrs.count() + 1 : attrs.count() );
820 for (
int it = 0; it < attrs.count(); ++it )
822 const QVariant attr = attrs.at( it );
823 newAttrs[column++] = attr;
827 newLayer->addFeature( f );
831 if ( newLayer->commitChanges() )
837 const int layerId = getOrCreateLayerId( db, layer->
id() );
838 QList<QgsFeatureId> offlineFeatureIds;
843 offlineFeatureIds << f.
id();
847 sqlExec( db, u
"BEGIN"_s );
848 const int remoteCount = remoteFeatureIds.size();
849 for (
int i = 0; i < remoteCount; i++ )
852 if ( i < offlineFeatureIds.count() )
854 addFidLookup( db, layerId, offlineFeatureIds.at( i ), remoteFeatureIds.at( i ), remoteFeaturePks.at( i ) );
858 showWarning( tr(
"Feature cannot be copied to the offline layer, please check if the online layer '%1' is still accessible." ).arg( layer->
name() ) );
863 sqlExec( db, u
"COMMIT"_s );
867 showWarning( newLayer->commitErrors().join( QLatin1Char(
'\n' ) ) );
880 const QgsFields fields = layer->
fields();
881 QStringList notNullFieldNames;
882 for (
const QgsField &field : fields )
886 notNullFieldNames << field.name();
890 layer->
setDataSource( newLayer->source(), newLayer->name(), newLayer->dataProvider()->name() );
892 for (
const QgsField &field : fields )
902 if ( notNullFieldNames.contains( field.name() ) )
904 notNullFieldNames.removeAll( field.name() );
915void QgsOfflineEditing::applyAttributesAdded(
QgsVectorLayer *remoteLayer,
sqlite3 *db,
int layerId,
int commitNo )
917 Q_ASSERT( remoteLayer );
919 const QString sql = u
"SELECT \"name\", \"type\", \"length\", \"precision\", \"comment\" FROM 'log_added_attrs' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2"_s.arg( layerId ).arg( commitNo );
920 QList<QgsField> fields = sqlQueryAttributesAdded( db, sql );
922 const QgsVectorDataProvider *provider = remoteLayer->
dataProvider();
923 const QList<QgsVectorDataProvider::NativeType> nativeTypes = provider->
nativeTypes();
926 QMap< QMetaType::Type, QString > typeNameLookup;
927 for (
int i = 0; i < nativeTypes.size(); i++ )
929 const QgsVectorDataProvider::NativeType nativeType = nativeTypes.at( i );
935 for (
int i = 0; i < fields.size(); i++ )
938 QgsField field = fields[i];
939 if ( typeNameLookup.contains( field.
type() ) )
941 const QString typeName = typeNameLookup[field.
type()];
947 showWarning( u
"Could not add attribute '%1' of type %2"_s.arg( field.
name() ).arg( field.
type() ) );
956 Q_ASSERT( offlineLayer );
957 Q_ASSERT( remoteLayer );
959 const QString sql = u
"SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1"_s.arg( layerId );
960 const QList<int> featureIdInts = sqlQueryInts( db, sql );
962 for (
const int id : featureIdInts )
971 QgsFeatureIterator it = offlineLayer->
getFeatures( QgsFeatureRequest().setFilterFids( newFeatureIds ) );
982 const int newAttrsCount = remoteLayer->
fields().
count();
983 for ( QgsFeatureList::iterator it = features.begin(); it != features.end(); ++it )
987 const QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
988 QgsAttributes newAttrs( newAttrsCount );
989 const QgsAttributes attrs = it->attributes();
990 for (
int it = 0; it < attrs.count(); ++it )
992 const int remoteAttributeIndex = attrLookup.value( it, -1 );
994 if ( remoteAttributeIndex == -1 )
996 QVariant attr = attrs.at( it );
997 if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QMetaType::Type::QStringList )
999 if ( attr.userType() == QMetaType::Type::QStringList || attr.userType() == QMetaType::Type::QVariantList )
1001 attr = attr.toStringList();
1008 else if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QMetaType::Type::QVariantList )
1010 if ( attr.userType() == QMetaType::Type::QStringList || attr.userType() == QMetaType::Type::QVariantList )
1012 attr = attr.toList();
1019 newAttrs[remoteAttributeIndex] = attr;
1030void QgsOfflineEditing::applyFeaturesRemoved(
QgsVectorLayer *remoteLayer,
sqlite3 *db,
int layerId )
1032 Q_ASSERT( remoteLayer );
1034 const QString sql = u
"SELECT \"fid\" FROM 'log_removed_features' WHERE \"layer_id\" = %1"_s.arg( layerId );
1035 const QgsFeatureIds values = sqlQueryFeaturesRemoved( db, sql );
1040 for ( QgsFeatureIds::const_iterator it = values.constBegin(); it != values.constEnd(); ++it )
1042 const QgsFeatureId fid = remoteFid( db, layerId, *it, remoteLayer );
1051 Q_ASSERT( offlineLayer );
1052 Q_ASSERT( remoteLayer );
1054 const QString sql = u
"SELECT \"fid\", \"attr\", \"value\" FROM 'log_feature_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2 "_s.arg( layerId ).arg( commitNo );
1055 const AttributeValueChanges values = sqlQueryAttributeValueChanges( db, sql );
1059 QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
1061 for (
int i = 0; i < values.size(); i++ )
1063 const QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid, remoteLayer );
1064 QgsDebugMsgLevel( u
"Offline changeAttributeValue %1 = %2"_s.arg( attrLookup[values.at( i ).attr] ).arg( values.at( i ).value ), 4 );
1066 const int remoteAttributeIndex = attrLookup[values.at( i ).attr];
1067 QVariant attr = values.at( i ).value;
1068 if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QMetaType::Type::QStringList )
1072 else if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QMetaType::Type::QVariantList )
1083void QgsOfflineEditing::applyGeometryChanges(
QgsVectorLayer *remoteLayer,
sqlite3 *db,
int layerId,
int commitNo )
1085 Q_ASSERT( remoteLayer );
1087 const QString sql = u
"SELECT \"fid\", \"geom_wkt\" FROM 'log_geometry_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2"_s.arg( layerId ).arg( commitNo );
1088 const GeometryChanges values = sqlQueryGeometryChanges( db, sql );
1092 for (
int i = 0; i < values.size(); i++ )
1094 const QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid, remoteLayer );
1104 Q_ASSERT( remoteLayer );
1110 QMap< QgsFeatureId, QString > newRemoteFids;
1117 const int remotePkIdx = getLayerPkIdx( remoteLayer );
1122 if ( offlineFid( db, layerId, f.
id() ) == -1 )
1124 newRemoteFids[f.
id()] = remotePkIdx >= 0 ? f.
attribute( remotePkIdx ).toString() : QString();
1132 const QString sql = u
"SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1"_s.arg( layerId );
1133 const QList<int> newOfflineFids = sqlQueryInts( db, sql );
1135 if ( newRemoteFids.size() != newOfflineFids.size() )
1143 sqlExec( db, u
"BEGIN"_s );
1144 for ( QMap<QgsFeatureId, QString>::const_iterator it = newRemoteFids.constBegin(); it != newRemoteFids.constEnd(); ++it )
1146 addFidLookup( db, layerId, newOfflineFids.at( i++ ), it.key(), it.value() );
1148 sqlExec( db, u
"COMMIT"_s );
1155 Q_ASSERT( offlineLayer );
1156 Q_ASSERT( remoteLayer );
1160 QMap<
int ,
int > attrLookup;
1163 for (
int i = 0; i < offlineAttrs.size(); i++ )
1172void QgsOfflineEditing::showWarning(
const QString &message )
1174 emit
warning( tr(
"Offline Editing Plugin" ), message );
1179 sqlite3_database_unique_ptr database;
1181 if ( !dbPath.isEmpty() )
1184 const int rc = database.
open( absoluteDbPath );
1185 if ( rc != SQLITE_OK )
1187 QgsDebugError( u
"Could not open the SpatiaLite logging database"_s );
1188 showWarning( tr(
"Could not open the SpatiaLite logging database" ) );
1198int QgsOfflineEditing::getOrCreateLayerId(
sqlite3 *db,
const QString &qgisLayerId )
1200 QString sql = u
"SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'"_s.arg( qgisLayerId );
1201 int layerId = sqlQueryInt( db, sql, -1 );
1202 if ( layerId == -1 )
1205 sql = u
"SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'layer_id'"_s;
1206 const int newLayerId = sqlQueryInt( db, sql, -1 );
1209 sql = u
"INSERT INTO 'log_layer_ids' VALUES (%1, '%2')"_s.arg( newLayerId ).arg( qgisLayerId );
1214 sql = u
"UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'layer_id'"_s.arg( newLayerId + 1 );
1217 layerId = newLayerId;
1223int QgsOfflineEditing::getCommitNo(
sqlite3 *db )
1225 const QString sql = u
"SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'commit_no'"_s;
1226 return sqlQueryInt( db, sql, -1 );
1229void QgsOfflineEditing::increaseCommitNo(
sqlite3 *db )
1231 const QString sql = u
"UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'commit_no'"_s.arg( getCommitNo( db ) + 1 );
1237 const QString sql = u
"INSERT INTO 'log_fids' VALUES ( %1, %2, %3, %4 )"_s.arg( layerId ).arg( offlineFid ).arg( remoteFid ).arg( sqlEscape( remotePk ) );
1243 const int pkIdx = getLayerPkIdx( remoteLayer );
1247 const QString sql = u
"SELECT \"remote_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2"_s.arg( layerId ).arg( offlineFid );
1248 return sqlQueryInt( db, sql, -1 );
1251 const QString sql = u
"SELECT \"remote_pk\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2"_s.arg( layerId ).arg( offlineFid );
1252 QString defaultValue;
1253 const QString pkValue = sqlQueryStr( db, sql, defaultValue );
1255 if ( pkValue.isNull() )
1260 const QString pkFieldName = remoteLayer->
fields().
at( pkIdx ).
name();
1261 QgsFeatureIterator fit = remoteLayer->
getFeatures( u
" %1 = %2 "_s.arg( pkFieldName ).arg( sqlEscape( pkValue ) ) );
1271 const QString sql = u
"SELECT \"offline_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"remote_fid\" = %2"_s.arg( layerId ).arg( remoteFid );
1272 return sqlQueryInt( db, sql, -1 );
1277 const QString sql = u
"SELECT COUNT(\"fid\") FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2"_s.arg( layerId ).arg( fid );
1278 return ( sqlQueryInt( db, sql, 0 ) > 0 );
1281int QgsOfflineEditing::sqlExec(
sqlite3 *db,
const QString &sql )
1283 char *errmsg =
nullptr;
1284 const int rc = sqlite3_exec( db, sql.toUtf8(),
nullptr,
nullptr, &errmsg );
1285 if ( rc != SQLITE_OK )
1287 showWarning( errmsg );
1292QString QgsOfflineEditing::sqlQueryStr(
sqlite3 *db,
const QString &sql, QString &defaultValue )
1294 sqlite3_stmt *stmt =
nullptr;
1295 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1297 showWarning( sqlite3_errmsg( db ) );
1298 return defaultValue;
1301 QString value = defaultValue;
1302 const int ret = sqlite3_step( stmt );
1303 if ( ret == SQLITE_ROW )
1305 value = QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 0 ) ) );
1307 sqlite3_finalize( stmt );
1312int QgsOfflineEditing::sqlQueryInt(
sqlite3 *db,
const QString &sql,
int defaultValue )
1314 sqlite3_stmt *stmt =
nullptr;
1315 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1317 showWarning( sqlite3_errmsg( db ) );
1318 return defaultValue;
1321 int value = defaultValue;
1322 const int ret = sqlite3_step( stmt );
1323 if ( ret == SQLITE_ROW )
1325 value = sqlite3_column_int( stmt, 0 );
1327 sqlite3_finalize( stmt );
1332QList<int> QgsOfflineEditing::sqlQueryInts(
sqlite3 *db,
const QString &sql )
1336 sqlite3_stmt *stmt =
nullptr;
1337 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1339 showWarning( sqlite3_errmsg( db ) );
1343 int ret = sqlite3_step( stmt );
1344 while ( ret == SQLITE_ROW )
1346 values << sqlite3_column_int( stmt, 0 );
1348 ret = sqlite3_step( stmt );
1350 sqlite3_finalize( stmt );
1355QList<QgsField> QgsOfflineEditing::sqlQueryAttributesAdded(
sqlite3 *db,
const QString &sql )
1357 QList<QgsField> values;
1359 sqlite3_stmt *stmt =
nullptr;
1360 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1362 showWarning( sqlite3_errmsg( db ) );
1366 int ret = sqlite3_step( stmt );
1367 while ( ret == SQLITE_ROW )
1369 const QgsField field(
1370 QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 0 ) ) ),
1371 static_cast< QMetaType::Type
>( sqlite3_column_int( stmt, 1 ) ),
1373 sqlite3_column_int( stmt, 2 ),
1374 sqlite3_column_int( stmt, 3 ),
1375 QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 4 ) ) )
1379 ret = sqlite3_step( stmt );
1381 sqlite3_finalize( stmt );
1390 sqlite3_stmt *stmt =
nullptr;
1391 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1393 showWarning( sqlite3_errmsg( db ) );
1397 int ret = sqlite3_step( stmt );
1398 while ( ret == SQLITE_ROW )
1400 values << sqlite3_column_int( stmt, 0 );
1402 ret = sqlite3_step( stmt );
1404 sqlite3_finalize( stmt );
1409QgsOfflineEditing::AttributeValueChanges QgsOfflineEditing::sqlQueryAttributeValueChanges(
sqlite3 *db,
const QString &sql )
1411 AttributeValueChanges values;
1413 sqlite3_stmt *stmt =
nullptr;
1414 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1416 showWarning( sqlite3_errmsg( db ) );
1420 int ret = sqlite3_step( stmt );
1421 while ( ret == SQLITE_ROW )
1423 AttributeValueChange change;
1424 change.fid = sqlite3_column_int( stmt, 0 );
1425 change.attr = sqlite3_column_int( stmt, 1 );
1426 change.value = QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 2 ) ) );
1429 ret = sqlite3_step( stmt );
1431 sqlite3_finalize( stmt );
1436QgsOfflineEditing::GeometryChanges QgsOfflineEditing::sqlQueryGeometryChanges(
sqlite3 *db,
const QString &sql )
1438 GeometryChanges values;
1440 sqlite3_stmt *stmt =
nullptr;
1441 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1443 showWarning( sqlite3_errmsg( db ) );
1447 int ret = sqlite3_step( stmt );
1448 while ( ret == SQLITE_ROW )
1450 GeometryChange change;
1451 change.fid = sqlite3_column_int( stmt, 0 );
1452 change.geom_wkt = QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 1 ) ) );
1455 ret = sqlite3_step( stmt );
1457 sqlite3_finalize( stmt );
1462void QgsOfflineEditing::committedAttributesAdded(
const QString &qgisLayerId,
const QList<QgsField> &addedAttributes )
1464 const sqlite3_database_unique_ptr database = openLoggingDb();
1469 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1470 const int commitNo = getCommitNo( database.get() );
1472 for (
const QgsField &field : addedAttributes )
1474 const QString sql = u
"INSERT INTO 'log_added_attrs' VALUES ( %1, %2, '%3', %4, %5, %6, '%7' )"_s.arg( layerId )
1476 .arg( field.
name() )
1477 .arg( field.
type() )
1481 sqlExec( database.get(), sql );
1484 increaseCommitNo( database.get() );
1487void QgsOfflineEditing::committedFeaturesAdded(
const QString &qgisLayerId,
const QgsFeatureList &addedFeatures )
1489 const sqlite3_database_unique_ptr database = openLoggingDb();
1494 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1498 const QString dataSourceString = layer->
source();
1499 const QgsDataSourceUri uri = QgsDataSourceUri( dataSourceString );
1504 if ( !offlinePath.contains(
".gpkg" ) )
1506 tableName = uri.
table();
1511 const QVariantMap decodedUri = ogrProviderMetaData->
decodeUri( dataSourceString );
1512 tableName = decodedUri.value( u
"layerName"_s ).toString();
1513 if ( tableName.isEmpty() )
1515 showWarning( tr(
"Could not deduce table name from data source %1." ).arg( dataSourceString ) );
1520 const QString sql = u
"SELECT ROWID FROM '%1' ORDER BY ROWID DESC LIMIT %2"_s.arg( tableName ).arg( addedFeatures.size() );
1521 const QList<int> newFeatureIds = sqlQueryInts( database.get(), sql );
1522 for (
int i = newFeatureIds.size() - 1; i >= 0; i-- )
1524 const QString sql = u
"INSERT INTO 'log_added_features' VALUES ( %1, %2 )"_s.arg( layerId ).arg( newFeatureIds.at( i ) );
1525 sqlExec( database.get(), sql );
1529void QgsOfflineEditing::committedFeaturesRemoved(
const QString &qgisLayerId,
const QgsFeatureIds &deletedFeatureIds )
1531 const sqlite3_database_unique_ptr database = openLoggingDb();
1536 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1540 if ( isAddedFeature( database.get(), layerId,
id ) )
1543 const QString sql = u
"DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2"_s.arg( layerId ).arg(
id );
1544 sqlExec( database.get(), sql );
1548 const QString sql = u
"INSERT INTO 'log_removed_features' VALUES ( %1, %2)"_s.arg( layerId ).arg(
id );
1549 sqlExec( database.get(), sql );
1554void QgsOfflineEditing::committedAttributeValuesChanges(
const QString &qgisLayerId,
const QgsChangedAttributesMap &changedAttrsMap )
1556 const sqlite3_database_unique_ptr database = openLoggingDb();
1561 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1562 const int commitNo = getCommitNo( database.get() );
1564 for ( QgsChangedAttributesMap::const_iterator cit = changedAttrsMap.begin(); cit != changedAttrsMap.end(); ++cit )
1567 if ( isAddedFeature( database.get(), layerId, fid ) )
1573 for ( QgsAttributeMap::const_iterator it = attrMap.constBegin(); it != attrMap.constEnd(); ++it )
1575 QString value = it.value().userType() == QMetaType::Type::QStringList || it.value().userType() == QMetaType::Type::QVariantList ?
QgsJsonUtils::encodeValue( it.value() ) : it.value().toString();
1576 value.replace(
"'"_L1,
"''"_L1 );
1577 const QString sql = u
"INSERT INTO 'log_feature_updates' VALUES ( %1, %2, %3, %4, '%5' )"_s.arg( layerId )
1582 sqlExec( database.get(), sql );
1586 increaseCommitNo( database.get() );
1589void QgsOfflineEditing::committedGeometriesChanges(
const QString &qgisLayerId,
const QgsGeometryMap &changedGeometries )
1591 const sqlite3_database_unique_ptr database = openLoggingDb();
1596 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1597 const int commitNo = getCommitNo( database.get() );
1599 for ( QgsGeometryMap::const_iterator it = changedGeometries.begin(); it != changedGeometries.end(); ++it )
1602 if ( isAddedFeature( database.get(), layerId, fid ) )
1607 const QgsGeometry geom = it.value();
1608 const QString sql = u
"INSERT INTO 'log_geometry_updates' VALUES ( %1, %2, %3, '%4' )"_s.arg( layerId ).arg( commitNo ).arg( fid ).arg( geom.
asWkt() );
1609 sqlExec( database.get(), sql );
1614 increaseCommitNo( database.get() );
1617void QgsOfflineEditing::startListenFeatureChanges()
1619 QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( sender() );
1626 QgsVectorLayerEditBuffer *editBuffer = vLayer->
editBuffer();
1635void QgsOfflineEditing::stopListenFeatureChanges()
1637 QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( sender() );
1644 QgsVectorLayerEditBuffer *editBuffer = vLayer->
editBuffer();
1653void QgsOfflineEditing::setupLayer(
QgsMapLayer *layer )
1657 if ( QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( layer ) )
1668int QgsOfflineEditing::getLayerPkIdx(
const QgsVectorLayer *layer )
const
1671 if ( pkAttrs.length() == 1 )
1673 const QgsField pkField = layer->
fields().
at( pkAttrs[0] );
1674 const QMetaType::Type pkType = pkField.
type();
1676 if ( pkType == QMetaType::Type::QString )
1685QString QgsOfflineEditing::sqlEscape( QString value )
const
1687 if ( value.isNull() )
1690 value.replace(
"'",
"''" );
1692 return u
"'%1'"_s.arg( value );
@ Fids
Filter using feature IDs.
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
WkbType
The WKB type describes the number of dimensions a geometry has.
@ MultiPolygon
MultiPolygon.
@ MultiLineString
MultiLineString.
virtual void invalidateConnections(const QString &connection)
Invalidate connections corresponding to specified name.
Stores the component parts of a data source URI (e.g.
QString table() const
Returns the table name stored in the URI.
QString database() const
Returns the database name stored in the URI.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
QgsFeatureRequest & setFilterFids(const QgsFeatureIds &fids)
Sets the feature IDs that should be fetched.
Qgis::FeatureRequestFilterType filterType() const
Returns the attribute/ID filter type which is currently set on this request.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
Q_INVOKABLE QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
@ ConstraintNotNull
Field may not be null.
@ ConstraintUnique
Field must have a unique value.
Encapsulate a field in an attribute table or data source.
QMetaType::Type subType() const
If the field is a collection, gets its element's type.
void setTypeName(const QString &typeName)
Set the field type.
Container of fields for a vector layer.
Q_INVOKABLE int indexOf(const QString &fieldName) const
Gets the field index from the field name.
QgsField field(int fieldIdx) const
Returns the field at particular index (must be in range 0..N-1).
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
int fieldOriginIndex(int fieldIdx) const
Returns the field's origin index (its meaning is specific to each type of origin).
Q_INVOKABLE int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
static Q_INVOKABLE QgsGeometry fromWkt(const QString &wkt)
Creates a new geometry from a WKT string.
Q_INVOKABLE QString asWkt(int precision=17) const
Exports the geometry to WKT.
static Q_INVOKABLE QString encodeValue(const QVariant &value)
Encodes a value to a JSON string representation, adding appropriate quotations and escaping where req...
static Q_INVOKABLE QVariantList parseArray(const QString &json, QMetaType::Type type=QMetaType::Type::UnknownType)
Parse a simple array (depth=1).
Base class for all map layer types.
void editingStopped()
Emitted when edited changes have been successfully written to the data provider.
QString source() const
Returns the source for the layer.
Q_INVOKABLE QVariant customProperty(const QString &value, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer.
QString providerType() const
Returns the provider type (provider key) for this layer.
void removeCustomProperty(const QString &key)
Remove a custom property from layer.
void editingStarted()
Emitted when editing on this layer has started.
QgsCoordinateReferenceSystem crs
void setDataSource(const QString &dataSource, const QString &baseName=QString(), const QString &provider=QString(), bool loadDefaultStyleFlag=false)
Updates the data source of the layer.
Q_INVOKABLE void setCustomProperty(const QString &key, const QVariant &value)
Set a custom property for layer.
void progressModeSet(QgsOfflineEditing::ProgressMode mode, long long maximum)
Emitted when the mode for the progress of the current operation is set.
void progressUpdated(long long progress)
Emitted with the progress of the current mode.
void layerProgressUpdated(int layer, int numLayers)
Emitted whenever a new layer is being processed.
bool isOfflineProject() const
Returns true if current project is offline.
bool convertToOfflineProject(const QString &offlineDataPath, const QString &offlineDbFile, const QStringList &layerIds, bool onlySelected=false, ContainerType containerType=SpatiaLite, const QString &layerNameSuffix=u" (offline)"_s)
Convert current project for offline editing.
void warning(const QString &title, const QString &message)
Emitted when a warning needs to be displayed.
void progressStopped()
Emitted when the processing of all layers has finished.
void synchronize(bool useTransaction=false)
Synchronize to remote layers.
ContainerType
Type of offline database container file.
void progressStarted()
Emitted when the process has started.
static OGRSpatialReferenceH crsToOGRSpatialReference(const QgsCoordinateReferenceSystem &crs)
Returns a OGRSpatialReferenceH corresponding to the specified crs object.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Q_INVOKABLE QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
void layerWasAdded(QgsMapLayer *layer)
Emitted when a layer was added to the registry.
QgsSnappingConfig snappingConfig
QString readEntry(const QString &scope, const QString &key, const QString &def=QString(), bool *ok=nullptr) const
Reads a string from the specified scope and key.
QgsCoordinateTransformContext transformContext
void setTitle(const QString &title)
Sets the project's title.
bool writeEntry(const QString &scope, const QString &key, bool value)
Write a boolean value to the project file.
QString readPath(const QString &filename) const
Transforms a filename read from the project file to an absolute path.
QMap< QString, QgsMapLayer * > mapLayers(const bool validOnly=false) const
Returns a map of all registered layers by layer ID.
bool removeEntry(const QString &scope, const QString &key)
Remove the given key from the specified scope.
static QgsProviderRegistry * instance(const QString &pluginPath=QString())
Means of accessing canonical single instance.
QgsProviderMetadata * providerMetadata(const QString &providerKey) const
Returns metadata of the provider or nullptr if not found.
Stores configuration of snapping settings for the project.
QString connectionString() const
Returns the connection string of the transaction.
long long featureCount() const override=0
Number of features in the layer.
QList< QgsVectorDataProvider::NativeType > nativeTypes() const
Returns the names of the supported types.
virtual QString defaultValueClause(int fieldIndex) const
Returns any default value clauses which are present at the provider for a specified field index.
QgsFields fields() const override=0
Returns the fields associated with this data provider.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const override=0
Query the provider for features specified in request.
void committedAttributeValuesChanges(const QString &layerId, const QgsChangedAttributesMap &changedAttributesValues)
Emitted after feature attribute value changes have been committed to the layer.
void committedAttributesAdded(const QString &layerId, const QList< QgsField > &addedAttributes)
Emitted after attribute addition has been committed to the layer.
void committedGeometriesChanges(const QString &layerId, const QgsGeometryMap &changedGeometries)
Emitted after feature geometry changes have been committed to the layer.
static QgsFeature createFeature(const QgsVectorLayer *layer, const QgsGeometry &geometry=QgsGeometry(), const QgsAttributeMap &attributes=QgsAttributeMap(), QgsExpressionContext *context=nullptr)
Creates a new feature ready for insertion into a layer.
Represents a vector layer which manages a vector based dataset.
void committedFeaturesAdded(const QString &layerId, const QgsFeatureList &addedFeatures)
Emitted when features are added to the provider if not in transaction mode.
Q_INVOKABLE QgsAttributeList attributeList() const
Returns list of attribute indexes.
QgsExpressionContext createExpressionContext() const final
This method needs to be reimplemented in all classes which implement this interface and return an exp...
Q_INVOKABLE bool changeAttributeValue(QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue=QVariant(), bool skipDefaultValues=false, QgsVectorLayerToolsContext *context=nullptr)
Changes an attribute value for a feature (but does not immediately commit the changes).
Q_INVOKABLE bool addAttribute(const QgsField &field)
Add an attribute field (but does not commit it) returns true if the field was added.
long long featureCount(const QString &legendKey) const
Number of features rendered with specified legend key.
void setFieldConstraint(int index, QgsFieldConstraints::Constraint constraint, QgsFieldConstraints::ConstraintStrength strength=QgsFieldConstraints::ConstraintStrengthHard)
Sets a constraint for a specified field index.
bool isSpatial() const final
Returns true if this is a geometry layer and false in case of NoGeometry (table only) or UnknownGeome...
Q_INVOKABLE bool deleteFeature(QgsFeatureId fid, QgsVectorLayer::DeleteContext *context=nullptr)
Deletes a feature from the layer (but does not commit it).
void removeFieldConstraint(int index, QgsFieldConstraints::Constraint constraint)
Removes a constraint for a specified field index.
void committedFeaturesRemoved(const QString &layerId, const QgsFeatureIds &deletedFeatureIds)
Emitted when features are deleted from the provider if not in transaction mode.
Q_INVOKABLE Qgis::WkbType wkbType() const final
Returns the WKBType or WKBUnknown in case of error.
Q_INVOKABLE const QgsFeatureIds & selectedFeatureIds() const
Returns a list of the selected features IDs in this layer.
QString dataComment() const
Returns a description for this layer as defined in the data provider.
Q_INVOKABLE QgsVectorLayerEditBuffer * editBuffer()
Buffer with uncommitted editing operations. Only valid after editing has been turned on.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const final
Queries the layer for features specified in request.
QgsAttributeList primaryKeyAttributes() const
Returns the list of attributes which make up the layer's primary keys.
bool addFeature(QgsFeature &feature, QgsFeatureSink::Flags flags=QgsFeatureSink::Flags()) final
Adds a single feature to the sink.
QgsVectorDataProvider * dataProvider() final
Returns the layer's data provider, it may be nullptr.
Q_INVOKABLE bool changeGeometry(QgsFeatureId fid, QgsGeometry &geometry, bool skipDefaultValue=false)
Changes a feature's geometry within the layer's edit buffer (but does not immediately commit the chan...
static Q_INVOKABLE QString displayString(Qgis::WkbType type)
Returns a non-translated display string type for a WKB type, e.g., the geometry name used in WKT geom...
static Q_INVOKABLE bool hasZ(Qgis::WkbType type)
Tests whether a WKB type contains the z-dimension.
static Q_INVOKABLE bool hasM(Qgis::WkbType type)
Tests whether a WKB type contains m values.
static Qgis::WkbType flatType(Qgis::WkbType type)
Returns the flat type for a WKB type.
Unique pointer for spatialite databases, which automatically closes the database when the pointer goe...
int open(const QString &path)
Opens the database at the specified file path.
int open_v2(const QString &path, int flags, const char *zVfs)
Opens the database at the specified file path.
QString errorMessage() const
Returns the most recent error message encountered by the database.
Unique pointer for sqlite3 databases, which automatically closes the database when the pointer goes o...
int open(const QString &path)
Opens the database at the specified file path.
std::unique_ptr< std::remove_pointer< OGRDataSourceH >::type, OGRDataSourceDeleter > ogr_datasource_unique_ptr
Scoped OGR data source.
std::unique_ptr< std::remove_pointer< OGRFieldDefnH >::type, OGRFldDeleter > ogr_field_def_unique_ptr
Scoped OGR field definition.
QMap< int, QVariant > QgsAttributeMap
QMap< QgsFeatureId, QgsGeometry > QgsGeometryMap
QMap< QgsFeatureId, QgsAttributeMap > QgsChangedAttributesMap
QList< QgsFeature > QgsFeatureList
QSet< QgsFeatureId > QgsFeatureIds
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
QList< int > QgsAttributeList
#define QgsDebugMsgLevel(str, level)
#define QgsDebugError(str)
#define CUSTOM_PROPERTY_ORIGINAL_LAYERID
#define PROJECT_ENTRY_SCOPE_OFFLINE
#define CUSTOM_PROPERTY_REMOTE_PROVIDER
#define CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE
#define CUSTOM_PROPERTY_LAYERNAME_SUFFIX
#define CUSTOM_PROPERTY_REMOTE_SOURCE
#define PROJECT_ENTRY_KEY_OFFLINE_DB_PATH
Setting options for loading vector layers.