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"
91 if ( layerIds.isEmpty() )
96 const QString dbPath = QDir( offlineDataPath ).absoluteFilePath( offlineDbFile );
97 if ( createOfflineDb( dbPath, containerType ) )
100 const int rc = database.
open( dbPath );
101 if ( rc != SQLITE_OK )
103 showWarning( tr(
"Could not open the SpatiaLite database" ) );
108 createLoggingTables( database.get() );
113 for (
int i = 0; i < layerIds.count(); i++ )
121 convertToOfflineLayer( vl, database.get(), dbPath, onlySelected, containerType, layerNameSuffix );
129 if ( projectTitle.isEmpty() )
133 projectTitle +=
" (offline)"_L1;
164 QMap<int, std::shared_ptr<QgsVectorLayer>> remoteLayersByOfflineId;
165 QMap<int, QgsVectorLayer *> offlineLayersByOfflineId;
167 for ( QMap<QString, QgsMapLayer *>::iterator layer_it = mapLayers.begin() ; layer_it != mapLayers.end(); ++layer_it )
169 QgsVectorLayer *offlineLayer( qobject_cast<QgsVectorLayer *>( layer_it.value() ) );
171 if ( !offlineLayer || !offlineLayer->
isValid() )
173 QgsDebugMsgLevel( u
"Skipping offline layer %1 because it is an invalid layer"_s.arg( layer_it.key() ), 4 );
182 QString remoteName = offlineLayer->
name();
184 if ( remoteName.endsWith( remoteNameSuffix ) )
185 remoteName.chop( remoteNameSuffix.size() );
188 auto remoteLayer = std::make_shared<QgsVectorLayer>( remoteSource, remoteName, remoteProvider, options );
190 if ( ! remoteLayer->isValid() )
192 QgsDebugMsgLevel( u
"Skipping offline layer %1 because it failed to recreate its corresponding remote layer"_s.arg( offlineLayer->
id() ), 4 );
197 if ( remoteLayer->providerType().contains(
"WFS"_L1, Qt::CaseInsensitive ) )
208 const QString sql = u
"SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'"_s.arg( offlineLayer->
id() );
209 const int layerId = sqlQueryInt( database.get(), sql, -1 );
213 QgsDebugMsgLevel( u
"Skipping offline layer %1 because it failed to determine the offline editing layer id"_s.arg( offlineLayer->
id() ), 4 );
217 remoteLayersByOfflineId.insert( layerId, remoteLayer );
218 offlineLayersByOfflineId.insert( layerId, offlineLayer );
221 QgsDebugMsgLevel( u
"Found %1 offline layers in total"_s.arg( offlineLayersByOfflineId.count() ), 4 );
223 QMap<QPair<QString, QString>, std::shared_ptr<QgsTransactionGroup>> transactionGroups;
224 if ( useTransaction )
226 for (
const std::shared_ptr<QgsVectorLayer> &remoteLayer : std::as_const( remoteLayersByOfflineId ) )
229 const QPair<QString, QString> pair( remoteLayer->providerType(), connectionString );
230 std::shared_ptr<QgsTransactionGroup> transactionGroup = transactionGroups.value( pair );
232 if ( !transactionGroup )
233 transactionGroup = std::make_shared<QgsTransactionGroup>();
235 if ( !transactionGroup->addLayer( remoteLayer.get() ) )
237 QgsDebugMsgLevel( u
"Failed to add a layer %1 into transaction group, will be modified without transaction"_s.arg( remoteLayer->name() ), 4 );
241 transactionGroups.insert( pair, transactionGroup );
244 QgsDebugMsgLevel( u
"Created %1 transaction groups"_s.arg( transactionGroups.count() ), 4 );
247 const QList<int> offlineIds = remoteLayersByOfflineId.keys();
248 for (
int offlineLayerId : offlineIds )
250 std::shared_ptr<QgsVectorLayer> remoteLayer = remoteLayersByOfflineId.value( offlineLayerId );
251 QgsVectorLayer *offlineLayer = offlineLayersByOfflineId.value( offlineLayerId );
254 QgsDebugMsgLevel( u
"Failed to find offline layer %1"_s.arg( offlineLayerId ), 4 );
259 if ( !remoteLayer->startEditing() && !remoteLayer->isEditable() )
261 QgsDebugMsgLevel( u
"Failed to turn layer %1 into editing mode"_s.arg( remoteLayer->name() ), 4 );
266 const int commitNo = getCommitNo( database.get() );
269 for (
int i = 0; i < commitNo; i++ )
273 applyAttributesAdded( remoteLayer.get(), database.get(), offlineLayerId, i );
274 applyAttributeValueChanges( offlineLayer, remoteLayer.get(), database.get(), offlineLayerId, i );
275 applyGeometryChanges( remoteLayer.get(), database.get(), offlineLayerId, i );
278 applyFeaturesAdded( offlineLayer, remoteLayer.get(), database.get(), offlineLayerId );
279 applyFeaturesRemoved( remoteLayer.get(), database.get(), offlineLayerId );
283 for (
int offlineLayerId : offlineIds )
285 std::shared_ptr<QgsVectorLayer> remoteLayer = remoteLayersByOfflineId[offlineLayerId];
286 QgsVectorLayer *offlineLayer = offlineLayersByOfflineId[offlineLayerId];
288 if ( !remoteLayer->isEditable() )
291 if ( remoteLayer->commitChanges() )
294 updateFidLookup( remoteLayer.get(), database.get(), offlineLayerId );
298 sql = u
"DELETE FROM 'log_added_attrs' WHERE \"layer_id\" = %1"_s.arg( offlineLayerId );
299 sqlExec( database.get(), sql );
300 sql = u
"DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1"_s.arg( offlineLayerId );
301 sqlExec( database.get(), sql );
302 sql = u
"DELETE FROM 'log_removed_features' WHERE \"layer_id\" = %1"_s.arg( offlineLayerId );
303 sqlExec( database.get(), sql );
304 sql = u
"DELETE FROM 'log_feature_updates' WHERE \"layer_id\" = %1"_s.arg( offlineLayerId );
305 sqlExec( database.get(), sql );
306 sql = u
"DELETE FROM 'log_geometry_updates' WHERE \"layer_id\" = %1"_s.arg( offlineLayerId );
307 sqlExec( database.get(), sql );
311 showWarning( remoteLayer->commitErrors().join( QLatin1Char(
'\n' ) ) );
318 remoteLayer->reload();
319 offlineLayer->
setDataSource( remoteLayer->source(), remoteLayer->name(), remoteLayer->dataProvider()->name() );
335 const QgsFields fields = remoteLayer->fields();
336 for (
const QgsField &field : fields )
338 if ( !remoteLayer->dataProvider()->defaultValueClause( remoteLayer->fields().fieldOriginIndex( remoteLayer->fields().indexOf( field.name() ) ) ).isEmpty() )
350 const QString sql = u
"UPDATE 'log_indices' SET 'last_index' = 0 WHERE \"name\" = 'commit_no'"_s;
351 sqlExec( database.get(), sql );
355void QgsOfflineEditing::initializeSpatialMetadata(
sqlite3 *sqlite_handle )
357#ifdef HAVE_SPATIALITE
359 if ( !sqlite_handle )
362 char **results =
nullptr;
364 int ret = sqlite3_get_table( sqlite_handle,
"select count(*) from sqlite_master", &results, &rows, &columns,
nullptr );
365 if ( ret != SQLITE_OK )
370 for (
int i = 1; i <= rows; i++ )
371 count = atoi( results[( i * columns ) + 0] );
374 sqlite3_free_table( results );
379 bool above41 =
false;
380 ret = sqlite3_get_table( sqlite_handle,
"select spatialite_version()", &results, &rows, &columns,
nullptr );
381 if ( ret == SQLITE_OK && rows == 1 && columns == 1 )
383 const QString version = QString::fromUtf8( results[1] );
384 const QStringList parts = version.split(
' ', Qt::SkipEmptyParts );
385 if ( !parts.empty() )
387 const QStringList verparts = parts.at( 0 ).split(
'.', Qt::SkipEmptyParts );
388 above41 = verparts.size() >= 2 && ( verparts.at( 0 ).toInt() > 4 || ( verparts.at( 0 ).toInt() == 4 && verparts.at( 1 ).toInt() >= 1 ) );
392 sqlite3_free_table( results );
395 char *errMsg =
nullptr;
396 ret = sqlite3_exec( sqlite_handle, above41 ?
"SELECT InitSpatialMetadata(1)" :
"SELECT InitSpatialMetadata()",
nullptr,
nullptr, &errMsg );
398 if ( ret != SQLITE_OK )
400 QString errCause = tr(
"Unable to initialize SpatialMetadata:\n" );
401 errCause += QString::fromUtf8( errMsg );
402 showWarning( errCause );
403 sqlite3_free( errMsg );
406 spatial_ref_sys_init( sqlite_handle, 0 );
408 ( void )sqlite_handle;
412bool QgsOfflineEditing::createOfflineDb(
const QString &offlineDbPath, ContainerType containerType )
415 char *errMsg =
nullptr;
416 const QFile newDb( offlineDbPath );
417 if ( newDb.exists() )
419 QFile::remove( offlineDbPath );
424 const QFileInfo fullPath = QFileInfo( offlineDbPath );
425 const QDir path = fullPath.dir();
428 QDir().mkpath( path.absolutePath() );
431 const QString dbPath = newDb.fileName();
434 switch ( containerType )
438 OGRSFDriverH hGpkgDriver = OGRGetDriverByName(
"GPKG" );
441 showWarning( tr(
"Creation of database failed. GeoPackage driver not found." ) );
448 showWarning( tr(
"Creation of database failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
459 spatialite_database_unique_ptr database;
460 ret = database.
open_v2( dbPath, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
nullptr );
464 QString errCause = tr(
"Could not create a new database\n" );
466 showWarning( errCause );
470 ret = sqlite3_exec( database.get(),
"PRAGMA foreign_keys = 1",
nullptr,
nullptr, &errMsg );
471 if ( ret != SQLITE_OK )
473 showWarning( tr(
"Unable to activate FOREIGN_KEY constraints" ) );
474 sqlite3_free( errMsg );
477 initializeSpatialMetadata( database.get() );
481void QgsOfflineEditing::createLoggingTables(
sqlite3 *db )
484 QString sql = u
"CREATE TABLE 'log_indices' ('name' TEXT, 'last_index' INTEGER)"_s;
487 sql = u
"INSERT INTO 'log_indices' VALUES ('commit_no', 0)"_s;
490 sql = u
"INSERT INTO 'log_indices' VALUES ('layer_id', 0)"_s;
494 sql = u
"CREATE TABLE 'log_layer_ids' ('id' INTEGER, 'qgis_id' TEXT)"_s;
498 sql = u
"CREATE TABLE 'log_fids' ('layer_id' INTEGER, 'offline_fid' INTEGER, 'remote_fid' INTEGER, 'remote_pk' TEXT)"_s;
502 sql = u
"CREATE TABLE 'log_added_attrs' ('layer_id' INTEGER, 'commit_no' INTEGER, "_s;
503 sql +=
"'name' TEXT, 'type' INTEGER, 'length' INTEGER, 'precision' INTEGER, 'comment' TEXT)"_L1;
507 sql = u
"CREATE TABLE 'log_added_features' ('layer_id' INTEGER, 'fid' INTEGER)"_s;
511 sql = u
"CREATE TABLE 'log_removed_features' ('layer_id' INTEGER, 'fid' INTEGER)"_s;
515 sql = u
"CREATE TABLE 'log_feature_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'attr' INTEGER, 'value' TEXT)"_s;
519 sql = u
"CREATE TABLE 'log_geometry_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'geom_wkt' TEXT)"_s;
527void QgsOfflineEditing::convertToOfflineLayer(
QgsVectorLayer *layer,
sqlite3 *db,
const QString &offlineDbPath,
bool onlySelected, ContainerType containerType,
const QString &layerNameSuffix )
529 if ( !layer || !layer->
isValid() )
531 QgsDebugMsgLevel( u
"Layer %1 is invalid and cannot be copied"_s.arg( layer ? layer->
id() : u
"<UNKNOWN>"_s ), 4 );
535 const QString tableName = layer->
id();
539 std::unique_ptr<QgsVectorLayer> newLayer;
541 switch ( containerType )
545#ifdef HAVE_SPATIALITE
547 QString sql = u
"CREATE TABLE '%1' ("_s.arg( tableName );
550 for (
const auto &field : providerFields )
553 const QMetaType::Type type = field.type();
554 if ( type == QMetaType::Type::Int || type == QMetaType::Type::LongLong )
556 dataType = u
"INTEGER"_s;
558 else if ( type == QMetaType::Type::Double )
560 dataType = u
"REAL"_s;
562 else if ( type == QMetaType::Type::QString )
564 dataType = u
"TEXT"_s;
566 else if ( type == QMetaType::Type::QStringList || type == QMetaType::Type::QVariantList )
568 dataType = u
"TEXT"_s;
569 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() ) );
573 showWarning( tr(
"%1: Unknown data type %2. Not using type affinity for the field." ).arg( field.name(), QVariant::typeToName( type ) ) );
576 sql += delim + u
"'%1' %2"_s.arg( field.name(), dataType );
581 int rc = sqlExec( db, sql );
592 geomType = u
"POINT"_s;
595 geomType = u
"MULTIPOINT"_s;
598 geomType = u
"LINESTRING"_s;
601 geomType = u
"MULTILINESTRING"_s;
604 geomType = u
"POLYGON"_s;
607 geomType = u
"MULTIPOLYGON"_s;
614 QString zmInfo = u
"XY"_s;
623 if ( layer->
crs().
authid().startsWith(
"EPSG:"_L1, Qt::CaseInsensitive ) )
625 epsgCode = layer->
crs().
authid().mid( 5 );
630 showWarning( tr(
"Layer %1 has unsupported Coordinate Reference System (%2)." ).arg( layer->
name(), layer->
crs().
authid() ) );
633 const QString sqlAddGeom = u
"SELECT AddGeometryColumn('%1', 'Geometry', %2, '%3', '%4')"_s
634 .arg( tableName, epsgCode, geomType, zmInfo );
637 const QString sqlCreateIndex = u
"SELECT CreateSpatialIndex('%1', 'Geometry')"_s.arg( tableName );
639 if ( rc == SQLITE_OK )
641 rc = sqlExec( db, sqlAddGeom );
642 if ( rc == SQLITE_OK )
644 rc = sqlExec( db, sqlCreateIndex );
649 if ( rc != SQLITE_OK )
651 showWarning( tr(
"Filling SpatiaLite for layer %1 failed" ).arg( layer->
name() ) );
656 const QString connectionString = u
"dbname='%1' table='%2'%3 sql="_s
658 tableName, layer->
isSpatial() ?
"(Geometry)" :
"" );
660 newLayer = std::make_unique<QgsVectorLayer>( connectionString,
661 layer->
name() + layerNameSuffix, u
"spatialite"_s, options );
665 showWarning( tr(
"No Spatialite support available" ) );
673 char **options =
nullptr;
675 options = CSLSetNameValue( options,
"OVERWRITE",
"YES" );
676 options = CSLSetNameValue( options,
"IDENTIFIER", tr(
"%1 (offline)" ).arg( layer->
id() ).toUtf8().constData() );
677 options = CSLSetNameValue( options,
"DESCRIPTION", layer->
dataComment().toUtf8().constData() );
680 const QString fidBase( u
"fid"_s );
681 QString fid = fidBase;
685 fid = fidBase +
'_' + QString::number( counter );
688 if ( counter == 10000 )
690 showWarning( tr(
"Cannot make FID-name for GPKG " ) );
694 options = CSLSetNameValue( options,
"FID", fid.toUtf8().constData() );
698 options = CSLSetNameValue( options,
"GEOMETRY_COLUMN",
"geom" );
699 options = CSLSetNameValue( options,
"SPATIAL_INDEX",
"YES" );
702 OGRSFDriverH hDriver =
nullptr;
705 OGRLayerH hLayer = OGR_DS_CreateLayer( hDS.get(), tableName.toUtf8().constData(), hSRS,
static_cast<OGRwkbGeometryType
>( layer->
wkbType() ), options );
706 CSLDestroy( options );
711 showWarning( tr(
"Creation of layer failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
716 for (
const auto &field : providerFields )
718 const QString fieldName( field.name() );
719 const QMetaType::Type type = field.type();
720 OGRFieldType ogrType( OFTString );
721 OGRFieldSubType ogrSubType = OFSTNone;
722 if ( type == QMetaType::Type::Int )
723 ogrType = OFTInteger;
724 else if ( type == QMetaType::Type::LongLong )
725 ogrType = OFTInteger64;
726 else if ( type == QMetaType::Type::Double )
728 else if ( type == QMetaType::Type::QTime )
730 else if ( type == QMetaType::Type::QDate )
732 else if ( type == QMetaType::Type::QDateTime )
733 ogrType = OFTDateTime;
734 else if ( type == QMetaType::Type::Bool )
736 ogrType = OFTInteger;
737 ogrSubType = OFSTBoolean;
739 else if ( type == QMetaType::Type::QStringList || type == QMetaType::Type::QVariantList )
742 ogrSubType = OFSTJSON;
743 showWarning( tr(
"Field '%1' from layer %2 has been converted from a list to a JSON-formatted string value." ).arg( fieldName, layer->
name() ) );
748 const int ogrWidth = field.length();
751 OGR_Fld_SetWidth( fld.get(), ogrWidth );
752 if ( ogrSubType != OFSTNone )
753 OGR_Fld_SetSubType( fld.get(), ogrSubType );
755 if ( OGR_L_CreateField( hLayer, fld.get(),
true ) != OGRERR_NONE )
757 showWarning( tr(
"Creation of field %1 failed (OGR error: %2)" )
758 .arg( fieldName, QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
766 OGR_L_ResetReading( hLayer );
767 if ( CPLGetLastErrorType() != CE_None )
769 const QString msg( tr(
"Creation of layer failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
775 const QString uri = u
"%1|layername=%2|option:QGIS_FORCE_WAL=ON"_s.arg( offlineDbPath, tableName );
777 newLayer = std::make_unique<QgsVectorLayer>( uri, layer->
name() + layerNameSuffix, u
"ogr"_s, layerOptions );
782 if ( newLayer && newLayer->isValid() )
786 newLayer->startEditing();
789 QgsFeatureRequest req;
794 if ( !selectedFids.isEmpty() )
808 long long featureCount = 1;
809 const int remotePkIdx = getLayerPkIdx( layer );
811 QList<QgsFeatureId> remoteFeatureIds;
812 QStringList remoteFeaturePks;
815 remoteFeatureIds << f.
id();
816 remoteFeaturePks << ( remotePkIdx >= 0 ? f.
attribute( remotePkIdx ).toString() : QString() );
823 QgsAttributes newAttrs( containerType ==
GPKG ? attrs.count() + 1 : attrs.count() );
824 for (
int it = 0; it < attrs.count(); ++it )
826 const QVariant attr = attrs.at( it );
827 newAttrs[column++] = attr;
831 newLayer->addFeature( f );
835 if ( newLayer->commitChanges() )
841 const int layerId = getOrCreateLayerId( db, layer->
id() );
842 QList<QgsFeatureId> offlineFeatureIds;
847 offlineFeatureIds << f.
id();
851 sqlExec( db, u
"BEGIN"_s );
852 const int remoteCount = remoteFeatureIds.size();
853 for (
int i = 0; i < remoteCount; i++ )
856 if ( i < offlineFeatureIds.count() )
858 addFidLookup( db, layerId, offlineFeatureIds.at( i ), remoteFeatureIds.at( i ), remoteFeaturePks.at( i ) );
862 showWarning( tr(
"Feature cannot be copied to the offline layer, please check if the online layer '%1' is still accessible." ).arg( layer->
name() ) );
867 sqlExec( db, u
"COMMIT"_s );
871 showWarning( newLayer->commitErrors().join( QLatin1Char(
'\n' ) ) );
884 const QgsFields fields = layer->
fields();
885 QStringList notNullFieldNames;
886 for (
const QgsField &field : fields )
890 notNullFieldNames << field.name();
894 layer->
setDataSource( newLayer->source(), newLayer->name(), newLayer->dataProvider()->name() );
896 for (
const QgsField &field : fields )
906 if ( notNullFieldNames.contains( field.name() ) )
908 notNullFieldNames.removeAll( field.name() );
919void QgsOfflineEditing::applyAttributesAdded(
QgsVectorLayer *remoteLayer,
sqlite3 *db,
int layerId,
int commitNo )
921 Q_ASSERT( remoteLayer );
923 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 );
924 QList<QgsField> fields = sqlQueryAttributesAdded( db, sql );
926 const QgsVectorDataProvider *provider = remoteLayer->
dataProvider();
927 const QList<QgsVectorDataProvider::NativeType> nativeTypes = provider->
nativeTypes();
930 QMap < QMetaType::Type, QString > typeNameLookup;
931 for (
int i = 0; i < nativeTypes.size(); i++ )
933 const QgsVectorDataProvider::NativeType nativeType = nativeTypes.at( i );
939 for (
int i = 0; i < fields.size(); i++ )
942 QgsField field = fields[i];
943 if ( typeNameLookup.contains( field.
type() ) )
945 const QString typeName = typeNameLookup[ field.
type()];
951 showWarning( u
"Could not add attribute '%1' of type %2"_s.arg( field.
name() ).arg( field.
type() ) );
960 Q_ASSERT( offlineLayer );
961 Q_ASSERT( remoteLayer );
963 const QString sql = u
"SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1"_s.arg( layerId );
964 const QList<int> featureIdInts = sqlQueryInts( db, sql );
966 for (
const int id : featureIdInts )
975 QgsFeatureIterator it = offlineLayer->
getFeatures( QgsFeatureRequest().setFilterFids( newFeatureIds ) );
986 const int newAttrsCount = remoteLayer->
fields().
count();
987 for ( QgsFeatureList::iterator it = features.begin(); it != features.end(); ++it )
991 const QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
992 QgsAttributes newAttrs( newAttrsCount );
993 const QgsAttributes attrs = it->attributes();
994 for (
int it = 0; it < attrs.count(); ++it )
996 const int remoteAttributeIndex = attrLookup.value( it, -1 );
998 if ( remoteAttributeIndex == -1 )
1000 QVariant attr = attrs.at( it );
1001 if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QMetaType::Type::QStringList )
1003 if ( attr.userType() == QMetaType::Type::QStringList || attr.userType() == QMetaType::Type::QVariantList )
1005 attr = attr.toStringList();
1012 else if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QMetaType::Type::QVariantList )
1014 if ( attr.userType() == QMetaType::Type::QStringList || attr.userType() == QMetaType::Type::QVariantList )
1016 attr = attr.toList();
1023 newAttrs[ remoteAttributeIndex ] = attr;
1034void QgsOfflineEditing::applyFeaturesRemoved(
QgsVectorLayer *remoteLayer,
sqlite3 *db,
int layerId )
1036 Q_ASSERT( remoteLayer );
1038 const QString sql = u
"SELECT \"fid\" FROM 'log_removed_features' WHERE \"layer_id\" = %1"_s.arg( layerId );
1039 const QgsFeatureIds values = sqlQueryFeaturesRemoved( db, sql );
1044 for ( QgsFeatureIds::const_iterator it = values.constBegin(); it != values.constEnd(); ++it )
1046 const QgsFeatureId fid = remoteFid( db, layerId, *it, remoteLayer );
1055 Q_ASSERT( offlineLayer );
1056 Q_ASSERT( remoteLayer );
1058 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 );
1059 const AttributeValueChanges values = sqlQueryAttributeValueChanges( db, sql );
1063 QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
1065 for (
int i = 0; i < values.size(); i++ )
1067 const QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid, remoteLayer );
1068 QgsDebugMsgLevel( u
"Offline changeAttributeValue %1 = %2"_s.arg( attrLookup[ values.at( i ).attr ] ).arg( values.at( i ).value ), 4 );
1070 const int remoteAttributeIndex = attrLookup[ values.at( i ).attr ];
1071 QVariant attr = values.at( i ).value;
1072 if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QMetaType::Type::QStringList )
1076 else if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QMetaType::Type::QVariantList )
1087void QgsOfflineEditing::applyGeometryChanges(
QgsVectorLayer *remoteLayer,
sqlite3 *db,
int layerId,
int commitNo )
1089 Q_ASSERT( remoteLayer );
1091 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 );
1092 const GeometryChanges values = sqlQueryGeometryChanges( db, sql );
1096 for (
int i = 0; i < values.size(); i++ )
1098 const QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid, remoteLayer );
1108 Q_ASSERT( remoteLayer );
1114 QMap < QgsFeatureId, QString > newRemoteFids;
1121 const int remotePkIdx = getLayerPkIdx( remoteLayer );
1126 if ( offlineFid( db, layerId, f.
id() ) == -1 )
1128 newRemoteFids[ f.
id()] = remotePkIdx >= 0 ? f.
attribute( remotePkIdx ).toString() : QString();
1136 const QString sql = u
"SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1"_s.arg( layerId );
1137 const QList<int> newOfflineFids = sqlQueryInts( db, sql );
1139 if ( newRemoteFids.size() != newOfflineFids.size() )
1147 sqlExec( db, u
"BEGIN"_s );
1148 for ( QMap<QgsFeatureId, QString>::const_iterator it = newRemoteFids.constBegin(); it != newRemoteFids.constEnd(); ++it )
1150 addFidLookup( db, layerId, newOfflineFids.at( i++ ), it.key(), it.value() );
1152 sqlExec( db, u
"COMMIT"_s );
1159 Q_ASSERT( offlineLayer );
1160 Q_ASSERT( remoteLayer );
1164 QMap <
int ,
int > attrLookup;
1167 for (
int i = 0; i < offlineAttrs.size(); i++ )
1176void QgsOfflineEditing::showWarning(
const QString &message )
1178 emit
warning( tr(
"Offline Editing Plugin" ), message );
1183 sqlite3_database_unique_ptr database;
1185 if ( !dbPath.isEmpty() )
1188 const int rc = database.
open( absoluteDbPath );
1189 if ( rc != SQLITE_OK )
1191 QgsDebugError( u
"Could not open the SpatiaLite logging database"_s );
1192 showWarning( tr(
"Could not open the SpatiaLite logging database" ) );
1202int QgsOfflineEditing::getOrCreateLayerId(
sqlite3 *db,
const QString &qgisLayerId )
1204 QString sql = u
"SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'"_s.arg( qgisLayerId );
1205 int layerId = sqlQueryInt( db, sql, -1 );
1206 if ( layerId == -1 )
1209 sql = u
"SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'layer_id'"_s;
1210 const int newLayerId = sqlQueryInt( db, sql, -1 );
1213 sql = u
"INSERT INTO 'log_layer_ids' VALUES (%1, '%2')"_s.arg( newLayerId ).arg( qgisLayerId );
1218 sql = u
"UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'layer_id'"_s.arg( newLayerId + 1 );
1221 layerId = newLayerId;
1227int QgsOfflineEditing::getCommitNo(
sqlite3 *db )
1229 const QString sql = u
"SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'commit_no'"_s;
1230 return sqlQueryInt( db, sql, -1 );
1233void QgsOfflineEditing::increaseCommitNo(
sqlite3 *db )
1235 const QString sql = u
"UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'commit_no'"_s.arg( getCommitNo( db ) + 1 );
1241 const QString sql = u
"INSERT INTO 'log_fids' VALUES ( %1, %2, %3, %4 )"_s.arg( layerId ).arg( offlineFid ).arg( remoteFid ).arg( sqlEscape( remotePk ) );
1247 const int pkIdx = getLayerPkIdx( remoteLayer );
1251 const QString sql = u
"SELECT \"remote_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2"_s.arg( layerId ).arg( offlineFid );
1252 return sqlQueryInt( db, sql, -1 );
1255 const QString sql = u
"SELECT \"remote_pk\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2"_s.arg( layerId ).arg( offlineFid );
1256 QString defaultValue;
1257 const QString pkValue = sqlQueryStr( db, sql, defaultValue );
1259 if ( pkValue.isNull() )
1264 const QString pkFieldName = remoteLayer->
fields().
at( pkIdx ).
name();
1265 QgsFeatureIterator fit = remoteLayer->
getFeatures( u
" %1 = %2 "_s.arg( pkFieldName ).arg( sqlEscape( pkValue ) ) );
1275 const QString sql = u
"SELECT \"offline_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"remote_fid\" = %2"_s.arg( layerId ).arg( remoteFid );
1276 return sqlQueryInt( db, sql, -1 );
1281 const QString sql = u
"SELECT COUNT(\"fid\") FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2"_s.arg( layerId ).arg( fid );
1282 return ( sqlQueryInt( db, sql, 0 ) > 0 );
1285int QgsOfflineEditing::sqlExec(
sqlite3 *db,
const QString &sql )
1287 char *errmsg =
nullptr;
1288 const int rc = sqlite3_exec( db, sql.toUtf8(),
nullptr,
nullptr, &errmsg );
1289 if ( rc != SQLITE_OK )
1291 showWarning( errmsg );
1296QString QgsOfflineEditing::sqlQueryStr(
sqlite3 *db,
const QString &sql, QString &defaultValue )
1298 sqlite3_stmt *stmt =
nullptr;
1299 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1301 showWarning( sqlite3_errmsg( db ) );
1302 return defaultValue;
1305 QString value = defaultValue;
1306 const int ret = sqlite3_step( stmt );
1307 if ( ret == SQLITE_ROW )
1309 value = QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 0 ) ) );
1311 sqlite3_finalize( stmt );
1316int QgsOfflineEditing::sqlQueryInt(
sqlite3 *db,
const QString &sql,
int defaultValue )
1318 sqlite3_stmt *stmt =
nullptr;
1319 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1321 showWarning( sqlite3_errmsg( db ) );
1322 return defaultValue;
1325 int value = defaultValue;
1326 const int ret = sqlite3_step( stmt );
1327 if ( ret == SQLITE_ROW )
1329 value = sqlite3_column_int( stmt, 0 );
1331 sqlite3_finalize( stmt );
1336QList<int> QgsOfflineEditing::sqlQueryInts(
sqlite3 *db,
const QString &sql )
1340 sqlite3_stmt *stmt =
nullptr;
1341 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1343 showWarning( sqlite3_errmsg( db ) );
1347 int ret = sqlite3_step( stmt );
1348 while ( ret == SQLITE_ROW )
1350 values << sqlite3_column_int( stmt, 0 );
1352 ret = sqlite3_step( stmt );
1354 sqlite3_finalize( stmt );
1359QList<QgsField> QgsOfflineEditing::sqlQueryAttributesAdded(
sqlite3 *db,
const QString &sql )
1361 QList<QgsField> values;
1363 sqlite3_stmt *stmt =
nullptr;
1364 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1366 showWarning( sqlite3_errmsg( db ) );
1370 int ret = sqlite3_step( stmt );
1371 while ( ret == SQLITE_ROW )
1373 const QgsField field( QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 0 ) ) ),
1374 static_cast< QMetaType::Type
>( sqlite3_column_int( stmt, 1 ) ),
1376 sqlite3_column_int( stmt, 2 ),
1377 sqlite3_column_int( stmt, 3 ),
1378 QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 4 ) ) ) );
1381 ret = sqlite3_step( stmt );
1383 sqlite3_finalize( stmt );
1392 sqlite3_stmt *stmt =
nullptr;
1393 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1395 showWarning( sqlite3_errmsg( db ) );
1399 int ret = sqlite3_step( stmt );
1400 while ( ret == SQLITE_ROW )
1402 values << sqlite3_column_int( stmt, 0 );
1404 ret = sqlite3_step( stmt );
1406 sqlite3_finalize( stmt );
1411QgsOfflineEditing::AttributeValueChanges QgsOfflineEditing::sqlQueryAttributeValueChanges(
sqlite3 *db,
const QString &sql )
1413 AttributeValueChanges values;
1415 sqlite3_stmt *stmt =
nullptr;
1416 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1418 showWarning( sqlite3_errmsg( db ) );
1422 int ret = sqlite3_step( stmt );
1423 while ( ret == SQLITE_ROW )
1425 AttributeValueChange change;
1426 change.fid = sqlite3_column_int( stmt, 0 );
1427 change.attr = sqlite3_column_int( stmt, 1 );
1428 change.value = QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 2 ) ) );
1431 ret = sqlite3_step( stmt );
1433 sqlite3_finalize( stmt );
1438QgsOfflineEditing::GeometryChanges QgsOfflineEditing::sqlQueryGeometryChanges(
sqlite3 *db,
const QString &sql )
1440 GeometryChanges values;
1442 sqlite3_stmt *stmt =
nullptr;
1443 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1445 showWarning( sqlite3_errmsg( db ) );
1449 int ret = sqlite3_step( stmt );
1450 while ( ret == SQLITE_ROW )
1452 GeometryChange change;
1453 change.fid = sqlite3_column_int( stmt, 0 );
1454 change.geom_wkt = QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 1 ) ) );
1457 ret = sqlite3_step( stmt );
1459 sqlite3_finalize( stmt );
1464void QgsOfflineEditing::committedAttributesAdded(
const QString &qgisLayerId,
const QList<QgsField> &addedAttributes )
1466 const sqlite3_database_unique_ptr database = openLoggingDb();
1471 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1472 const int commitNo = getCommitNo( database.get() );
1474 for (
const QgsField &field : addedAttributes )
1476 const QString sql = u
"INSERT INTO 'log_added_attrs' VALUES ( %1, %2, '%3', %4, %5, %6, '%7' )"_s
1479 .arg( field.
name() )
1480 .arg( field.
type() )
1484 sqlExec( database.get(), sql );
1487 increaseCommitNo( database.get() );
1490void QgsOfflineEditing::committedFeaturesAdded(
const QString &qgisLayerId,
const QgsFeatureList &addedFeatures )
1492 const sqlite3_database_unique_ptr database = openLoggingDb();
1497 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1501 const QString dataSourceString = layer->
source();
1502 const QgsDataSourceUri uri = QgsDataSourceUri( dataSourceString );
1507 if ( !offlinePath.contains(
".gpkg" ) )
1509 tableName = uri.
table();
1514 const QVariantMap decodedUri = ogrProviderMetaData->
decodeUri( dataSourceString );
1515 tableName = decodedUri.value( u
"layerName"_s ).toString();
1516 if ( tableName.isEmpty() )
1518 showWarning( tr(
"Could not deduce table name from data source %1." ).arg( dataSourceString ) );
1523 const QString sql = u
"SELECT ROWID FROM '%1' ORDER BY ROWID DESC LIMIT %2"_s.arg( tableName ).arg( addedFeatures.size() );
1524 const QList<int> newFeatureIds = sqlQueryInts( database.get(), sql );
1525 for (
int i = newFeatureIds.size() - 1; i >= 0; i-- )
1527 const QString sql = u
"INSERT INTO 'log_added_features' VALUES ( %1, %2 )"_s
1529 .arg( newFeatureIds.at( i ) );
1530 sqlExec( database.get(), sql );
1534void QgsOfflineEditing::committedFeaturesRemoved(
const QString &qgisLayerId,
const QgsFeatureIds &deletedFeatureIds )
1536 const sqlite3_database_unique_ptr database = openLoggingDb();
1541 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1545 if ( isAddedFeature( database.get(), layerId,
id ) )
1548 const QString sql = u
"DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2"_s.arg( layerId ).arg(
id );
1549 sqlExec( database.get(), sql );
1553 const QString sql = u
"INSERT INTO 'log_removed_features' VALUES ( %1, %2)"_s
1556 sqlExec( database.get(), sql );
1561void QgsOfflineEditing::committedAttributeValuesChanges(
const QString &qgisLayerId,
const QgsChangedAttributesMap &changedAttrsMap )
1563 const sqlite3_database_unique_ptr database = openLoggingDb();
1568 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1569 const int commitNo = getCommitNo( database.get() );
1571 for ( QgsChangedAttributesMap::const_iterator cit = changedAttrsMap.begin(); cit != changedAttrsMap.end(); ++cit )
1574 if ( isAddedFeature( database.get(), layerId, fid ) )
1580 for ( QgsAttributeMap::const_iterator it = attrMap.constBegin(); it != attrMap.constEnd(); ++it )
1582 QString value = it.value().userType() == QMetaType::Type::QStringList || it.value().userType() == QMetaType::Type::QVariantList ?
QgsJsonUtils::encodeValue( it.value() ) : it.value().toString();
1583 value.replace(
"'"_L1,
"''"_L1 );
1584 const QString sql = u
"INSERT INTO 'log_feature_updates' VALUES ( %1, %2, %3, %4, '%5' )"_s
1590 sqlExec( database.get(), sql );
1594 increaseCommitNo( database.get() );
1597void QgsOfflineEditing::committedGeometriesChanges(
const QString &qgisLayerId,
const QgsGeometryMap &changedGeometries )
1599 const sqlite3_database_unique_ptr database = openLoggingDb();
1604 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1605 const int commitNo = getCommitNo( database.get() );
1607 for ( QgsGeometryMap::const_iterator it = changedGeometries.begin(); it != changedGeometries.end(); ++it )
1610 if ( isAddedFeature( database.get(), layerId, fid ) )
1615 const QgsGeometry geom = it.value();
1616 const QString sql = u
"INSERT INTO 'log_geometry_updates' VALUES ( %1, %2, %3, '%4' )"_s
1620 .arg( geom.
asWkt() );
1621 sqlExec( database.get(), sql );
1626 increaseCommitNo( database.get() );
1629void QgsOfflineEditing::startListenFeatureChanges()
1631 QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( sender() );
1638 QgsVectorLayerEditBuffer *editBuffer = vLayer->
editBuffer();
1640 this, &QgsOfflineEditing::committedAttributesAdded );
1642 this, &QgsOfflineEditing::committedAttributeValuesChanges );
1644 this, &QgsOfflineEditing::committedGeometriesChanges );
1647 this, &QgsOfflineEditing::committedFeaturesAdded );
1649 this, &QgsOfflineEditing::committedFeaturesRemoved );
1652void QgsOfflineEditing::stopListenFeatureChanges()
1654 QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( sender() );
1661 QgsVectorLayerEditBuffer *editBuffer = vLayer->
editBuffer();
1663 this, &QgsOfflineEditing::committedAttributesAdded );
1665 this, &QgsOfflineEditing::committedAttributeValuesChanges );
1667 this, &QgsOfflineEditing::committedGeometriesChanges );
1670 this, &QgsOfflineEditing::committedFeaturesAdded );
1672 this, &QgsOfflineEditing::committedFeaturesRemoved );
1675void QgsOfflineEditing::setupLayer(
QgsMapLayer *layer )
1679 if ( QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( layer ) )
1690int QgsOfflineEditing::getLayerPkIdx(
const QgsVectorLayer *layer )
const
1693 if ( pkAttrs.length() == 1 )
1695 const QgsField pkField = layer->
fields().
at( pkAttrs[0] );
1696 const QMetaType::Type pkType = pkField.
type();
1698 if ( pkType == QMetaType::Type::QString )
1707QString QgsOfflineEditing::sqlEscape( QString value )
const
1709 if ( value.isNull() )
1712 value.replace(
"'",
"''" );
1714 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.