48#include <QDomDocument>
51#include <QRegularExpression>
53#include <ogr_srs_api.h>
63#include <spatialite.h>
67#define CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE "isOfflineEditable"
68#define CUSTOM_PROPERTY_REMOTE_SOURCE "remoteSource"
69#define CUSTOM_PROPERTY_REMOTE_PROVIDER "remoteProvider"
70#define CUSTOM_SHOW_FEATURE_COUNT "showFeatureCount"
71#define CUSTOM_PROPERTY_ORIGINAL_LAYERID "remoteLayerId"
72#define CUSTOM_PROPERTY_LAYERNAME_SUFFIX "layerNameSuffix"
73#define PROJECT_ENTRY_SCOPE_OFFLINE "OfflineEditingPlugin"
74#define PROJECT_ENTRY_KEY_OFFLINE_DB_PATH "/OfflineDbPath"
95 if ( layerIds.isEmpty() )
100 const QString dbPath = QDir( offlineDataPath ).absoluteFilePath( offlineDbFile );
101 if ( createOfflineDb( dbPath, containerType ) )
104 const int rc = database.
open( dbPath );
105 if ( rc != SQLITE_OK )
107 showWarning( tr(
"Could not open the SpatiaLite database" ) );
112 createLoggingTables( database.get() );
117 for (
int i = 0; i < layerIds.count(); i++ )
125 convertToOfflineLayer( vl, database.get(), dbPath, onlySelected, containerType, layerNameSuffix );
133 if ( projectTitle.isEmpty() )
137 projectTitle += QLatin1String(
" (offline)" );
168 QMap<int, std::shared_ptr<QgsVectorLayer>> remoteLayersByOfflineId;
169 QMap<int, QgsVectorLayer *> offlineLayersByOfflineId;
171 for ( QMap<QString, QgsMapLayer *>::iterator layer_it = mapLayers.begin() ; layer_it != mapLayers.end(); ++layer_it )
173 QgsVectorLayer *offlineLayer( qobject_cast<QgsVectorLayer *>( layer_it.value() ) );
175 if ( !offlineLayer || !offlineLayer->
isValid() )
177 QgsDebugMsgLevel( QStringLiteral(
"Skipping offline layer %1 because it is an invalid layer" ).arg( layer_it.key() ), 4 );
186 QString remoteName = offlineLayer->
name();
188 if ( remoteName.endsWith( remoteNameSuffix ) )
189 remoteName.chop( remoteNameSuffix.size() );
192 std::shared_ptr<QgsVectorLayer> remoteLayer = std::make_shared<QgsVectorLayer>( remoteSource, remoteName, remoteProvider, options );
194 if ( ! remoteLayer->isValid() )
196 QgsDebugMsgLevel( QStringLiteral(
"Skipping offline layer %1 because it failed to recreate its corresponding remote layer" ).arg( offlineLayer->
id() ), 4 );
201 if ( remoteLayer->providerType().contains( QLatin1String(
"WFS" ), Qt::CaseInsensitive ) )
212 const QString sql = QStringLiteral(
"SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( offlineLayer->
id() );
213 const int layerId = sqlQueryInt( database.get(), sql, -1 );
217 QgsDebugMsgLevel( QStringLiteral(
"Skipping offline layer %1 because it failed to determine the offline editing layer id" ).arg( offlineLayer->
id() ), 4 );
221 remoteLayersByOfflineId.insert( layerId, remoteLayer );
222 offlineLayersByOfflineId.insert( layerId, offlineLayer );
225 QgsDebugMsgLevel( QStringLiteral(
"Found %1 offline layers in total" ).arg( offlineLayersByOfflineId.count() ), 4 );
227 QMap<QPair<QString, QString>, std::shared_ptr<QgsTransactionGroup>> transactionGroups;
228 if ( useTransaction )
230 for (
const std::shared_ptr<QgsVectorLayer> &remoteLayer : std::as_const( remoteLayersByOfflineId ) )
233 const QPair<QString, QString> pair( remoteLayer->providerType(), connectionString );
234 std::shared_ptr<QgsTransactionGroup> transactionGroup = transactionGroups.value( pair );
236 if ( !transactionGroup.get() )
237 transactionGroup = std::make_shared<QgsTransactionGroup>();
239 if ( !transactionGroup->addLayer( remoteLayer.get() ) )
241 QgsDebugMsgLevel( QStringLiteral(
"Failed to add a layer %1 into transaction group, will be modified without transaction" ).arg( remoteLayer->name() ), 4 );
245 transactionGroups.insert( pair, transactionGroup );
248 QgsDebugMsgLevel( QStringLiteral(
"Created %1 transaction groups" ).arg( transactionGroups.count() ), 4 );
251 const QList<int> offlineIds = remoteLayersByOfflineId.keys();
252 for (
int offlineLayerId : offlineIds )
254 std::shared_ptr<QgsVectorLayer> remoteLayer = remoteLayersByOfflineId.value( offlineLayerId );
255 QgsVectorLayer *offlineLayer = offlineLayersByOfflineId.value( offlineLayerId );
258 if ( !remoteLayer->startEditing() && !remoteLayer->isEditable() )
260 QgsDebugMsgLevel( QStringLiteral(
"Failed to turn layer %1 into editing mode" ).arg( remoteLayer->name() ), 4 );
265 const int commitNo = getCommitNo( database.get() );
266 QgsDebugMsgLevel( QStringLiteral(
"Found %1 commits" ).arg( commitNo ), 4 );
268 for (
int i = 0; i < commitNo; i++ )
270 QgsDebugMsgLevel( QStringLiteral(
"Apply commits chronologically from %1" ).arg( offlineLayer->
name() ), 4 );
272 applyAttributesAdded( remoteLayer.get(), database.get(), offlineLayerId, i );
273 applyAttributeValueChanges( offlineLayer, remoteLayer.get(), database.get(), offlineLayerId, i );
274 applyGeometryChanges( remoteLayer.get(), database.get(), offlineLayerId, i );
277 applyFeaturesAdded( offlineLayer, remoteLayer.get(), database.get(), offlineLayerId );
278 applyFeaturesRemoved( remoteLayer.get(), database.get(), offlineLayerId );
282 for (
int offlineLayerId : offlineIds )
284 std::shared_ptr<QgsVectorLayer> remoteLayer = remoteLayersByOfflineId[offlineLayerId];
285 QgsVectorLayer *offlineLayer = offlineLayersByOfflineId[offlineLayerId];
287 if ( !remoteLayer->isEditable() )
290 if ( remoteLayer->commitChanges() )
293 updateFidLookup( remoteLayer.get(), database.get(), offlineLayerId );
297 sql = QStringLiteral(
"DELETE FROM 'log_added_attrs' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
298 sqlExec( database.get(), sql );
299 sql = QStringLiteral(
"DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
300 sqlExec( database.get(), sql );
301 sql = QStringLiteral(
"DELETE FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
302 sqlExec( database.get(), sql );
303 sql = QStringLiteral(
"DELETE FROM 'log_feature_updates' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
304 sqlExec( database.get(), sql );
305 sql = QStringLiteral(
"DELETE FROM 'log_geometry_updates' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
306 sqlExec( database.get(), sql );
310 showWarning( remoteLayer->commitErrors().join( QLatin1Char(
'\n' ) ) );
317 remoteLayer->reload();
318 offlineLayer->
setDataSource( remoteLayer->source(), remoteLayer->name(), remoteLayer->dataProvider()->name() );
334 const QgsFields fields = remoteLayer->fields();
337 if ( !remoteLayer->dataProvider()->defaultValueClause( remoteLayer->fields().fieldOriginIndex( remoteLayer->fields().indexOf(
field.
name() ) ) ).isEmpty() )
349 const QString sql = QStringLiteral(
"UPDATE 'log_indices' SET 'last_index' = 0 WHERE \"name\" = 'commit_no'" );
350 sqlExec( database.get(), sql );
354void QgsOfflineEditing::initializeSpatialMetadata(
sqlite3 *sqlite_handle )
356#ifdef HAVE_SPATIALITE
358 if ( !sqlite_handle )
361 char **results =
nullptr;
363 int ret = sqlite3_get_table( sqlite_handle,
"select count(*) from sqlite_master", &results, &rows, &columns,
nullptr );
364 if ( ret != SQLITE_OK )
369 for (
int i = 1; i <= rows; i++ )
370 count = atoi( results[( i * columns ) + 0] );
373 sqlite3_free_table( results );
378 bool above41 =
false;
379 ret = sqlite3_get_table( sqlite_handle,
"select spatialite_version()", &results, &rows, &columns,
nullptr );
380 if ( ret == SQLITE_OK && rows == 1 && columns == 1 )
382 const QString version = QString::fromUtf8( results[1] );
383#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
384 QStringList parts = version.split(
' ', QString::SkipEmptyParts );
386 const QStringList parts = version.split(
' ', Qt::SkipEmptyParts );
388 if ( !parts.empty() )
390#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
391 QStringList verparts = parts.at( 0 ).split(
'.', QString::SkipEmptyParts );
393 const QStringList verparts = parts.at( 0 ).split(
'.', Qt::SkipEmptyParts );
395 above41 = verparts.size() >= 2 && ( verparts.at( 0 ).toInt() > 4 || ( verparts.at( 0 ).toInt() == 4 && verparts.at( 1 ).toInt() >= 1 ) );
399 sqlite3_free_table( results );
402 char *errMsg =
nullptr;
403 ret = sqlite3_exec( sqlite_handle, above41 ?
"SELECT InitSpatialMetadata(1)" :
"SELECT InitSpatialMetadata()",
nullptr,
nullptr, &errMsg );
405 if ( ret != SQLITE_OK )
407 QString errCause = tr(
"Unable to initialize SpatialMetadata:\n" );
408 errCause += QString::fromUtf8( errMsg );
409 showWarning( errCause );
410 sqlite3_free( errMsg );
413 spatial_ref_sys_init( sqlite_handle, 0 );
415 ( void )sqlite_handle;
419bool QgsOfflineEditing::createOfflineDb(
const QString &offlineDbPath, ContainerType containerType )
422 char *errMsg =
nullptr;
423 const QFile newDb( offlineDbPath );
424 if ( newDb.exists() )
426 QFile::remove( offlineDbPath );
431 const QFileInfo fullPath = QFileInfo( offlineDbPath );
432 const QDir path = fullPath.dir();
435 QDir().mkpath( path.absolutePath() );
438 const QString dbPath = newDb.fileName();
441 switch ( containerType )
445 OGRSFDriverH hGpkgDriver = OGRGetDriverByName(
"GPKG" );
448 showWarning( tr(
"Creation of database failed. GeoPackage driver not found." ) );
455 showWarning( tr(
"Creation of database failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
467 ret = database.
open_v2( dbPath, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
nullptr );
471 QString errCause = tr(
"Could not create a new database\n" );
473 showWarning( errCause );
477 ret = sqlite3_exec( database.get(),
"PRAGMA foreign_keys = 1",
nullptr,
nullptr, &errMsg );
478 if ( ret != SQLITE_OK )
480 showWarning( tr(
"Unable to activate FOREIGN_KEY constraints" ) );
481 sqlite3_free( errMsg );
484 initializeSpatialMetadata( database.get() );
488void QgsOfflineEditing::createLoggingTables(
sqlite3 *db )
491 QString sql = QStringLiteral(
"CREATE TABLE 'log_indices' ('name' TEXT, 'last_index' INTEGER)" );
494 sql = QStringLiteral(
"INSERT INTO 'log_indices' VALUES ('commit_no', 0)" );
497 sql = QStringLiteral(
"INSERT INTO 'log_indices' VALUES ('layer_id', 0)" );
501 sql = QStringLiteral(
"CREATE TABLE 'log_layer_ids' ('id' INTEGER, 'qgis_id' TEXT)" );
505 sql = QStringLiteral(
"CREATE TABLE 'log_fids' ('layer_id' INTEGER, 'offline_fid' INTEGER, 'remote_fid' INTEGER, 'remote_pk' TEXT)" );
509 sql = QStringLiteral(
"CREATE TABLE 'log_added_attrs' ('layer_id' INTEGER, 'commit_no' INTEGER, " );
510 sql += QLatin1String(
"'name' TEXT, 'type' INTEGER, 'length' INTEGER, 'precision' INTEGER, 'comment' TEXT)" );
514 sql = QStringLiteral(
"CREATE TABLE 'log_added_features' ('layer_id' INTEGER, 'fid' INTEGER)" );
518 sql = QStringLiteral(
"CREATE TABLE 'log_removed_features' ('layer_id' INTEGER, 'fid' INTEGER)" );
522 sql = QStringLiteral(
"CREATE TABLE 'log_feature_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'attr' INTEGER, 'value' TEXT)" );
526 sql = QStringLiteral(
"CREATE TABLE 'log_geometry_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'geom_wkt' TEXT)" );
534void QgsOfflineEditing::convertToOfflineLayer(
QgsVectorLayer *layer,
sqlite3 *db,
const QString &offlineDbPath,
bool onlySelected, ContainerType containerType,
const QString &layerNameSuffix )
536 if ( !layer || !layer->
isValid() )
538 QgsDebugMsgLevel( QStringLiteral(
"Layer %1 is invalid and cannot be copied" ).arg( layer ? layer->
id() : QStringLiteral(
"<UNKNOWN>" ) ), 4 );
542 const QString tableName = layer->
id();
543 QgsDebugMsgLevel( QStringLiteral(
"Creating offline table %1 ..." ).arg( tableName ), 4 );
546 std::unique_ptr<QgsVectorLayer> newLayer;
548 switch ( containerType )
552#ifdef HAVE_SPATIALITE
554 QString sql = QStringLiteral(
"CREATE TABLE '%1' (" ).arg( tableName );
557 for (
const auto &
field : providerFields )
560 const QVariant::Type type =
field.
type();
561 if ( type == QVariant::Int || type == QVariant::LongLong )
563 dataType = QStringLiteral(
"INTEGER" );
565 else if ( type == QVariant::Double )
567 dataType = QStringLiteral(
"REAL" );
569 else if ( type == QVariant::String )
571 dataType = QStringLiteral(
"TEXT" );
573 else if ( type == QVariant::StringList || type == QVariant::List )
575 dataType = QStringLiteral(
"TEXT" );
576 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() ) );
580 showWarning( tr(
"%1: Unknown data type %2. Not using type affinity for the field." ).arg(
field.
name(), QVariant::typeToName( type ) ) );
583 sql += delim + QStringLiteral(
"'%1' %2" ).arg(
field.
name(), dataType );
588 int rc = sqlExec( db, sql );
599 geomType = QStringLiteral(
"POINT" );
602 geomType = QStringLiteral(
"MULTIPOINT" );
605 geomType = QStringLiteral(
"LINESTRING" );
608 geomType = QStringLiteral(
"MULTILINESTRING" );
611 geomType = QStringLiteral(
"POLYGON" );
614 geomType = QStringLiteral(
"MULTIPOLYGON" );
621 QString zmInfo = QStringLiteral(
"XY" );
630 if ( layer->
crs().
authid().startsWith( QLatin1String(
"EPSG:" ), Qt::CaseInsensitive ) )
632 epsgCode = layer->
crs().
authid().mid( 5 );
637 showWarning( tr(
"Layer %1 has unsupported Coordinate Reference System (%2)." ).arg( layer->
name(), layer->
crs().
authid() ) );
640 const QString sqlAddGeom = QStringLiteral(
"SELECT AddGeometryColumn('%1', 'Geometry', %2, '%3', '%4')" )
641 .arg( tableName, epsgCode, geomType, zmInfo );
644 const QString sqlCreateIndex = QStringLiteral(
"SELECT CreateSpatialIndex('%1', 'Geometry')" ).arg( tableName );
646 if ( rc == SQLITE_OK )
648 rc = sqlExec( db, sqlAddGeom );
649 if ( rc == SQLITE_OK )
651 rc = sqlExec( db, sqlCreateIndex );
656 if ( rc != SQLITE_OK )
658 showWarning( tr(
"Filling SpatiaLite for layer %1 failed" ).arg( layer->
name() ) );
663 const QString connectionString = QStringLiteral(
"dbname='%1' table='%2'%3 sql=" )
665 tableName, layer->
isSpatial() ?
"(Geometry)" :
"" );
667 newLayer = std::make_unique<QgsVectorLayer>( connectionString,
668 layer->
name() + layerNameSuffix, QStringLiteral(
"spatialite" ), options );
672 showWarning( tr(
"No Spatialite support available" ) );
680 char **options =
nullptr;
682 options = CSLSetNameValue( options,
"OVERWRITE",
"YES" );
683 options = CSLSetNameValue( options,
"IDENTIFIER", tr(
"%1 (offline)" ).arg( layer->
id() ).toUtf8().constData() );
684 options = CSLSetNameValue( options,
"DESCRIPTION", layer->
dataComment().toUtf8().constData() );
687 const QString fidBase( QStringLiteral(
"fid" ) );
688 QString fid = fidBase;
692 fid = fidBase +
'_' + QString::number( counter );
695 if ( counter == 10000 )
697 showWarning( tr(
"Cannot make FID-name for GPKG " ) );
701 options = CSLSetNameValue( options,
"FID", fid.toUtf8().constData() );
705 options = CSLSetNameValue( options,
"GEOMETRY_COLUMN",
"geom" );
706 options = CSLSetNameValue( options,
"SPATIAL_INDEX",
"YES" );
709 OGRSFDriverH hDriver =
nullptr;
712 OGRLayerH hLayer = OGR_DS_CreateLayer( hDS.get(), tableName.toUtf8().constData(), hSRS,
static_cast<OGRwkbGeometryType
>( layer->
wkbType() ), options );
713 CSLDestroy( options );
718 showWarning( tr(
"Creation of layer failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
723 for (
const auto &
field : providerFields )
726 const QVariant::Type type =
field.
type();
727 OGRFieldType ogrType( OFTString );
728 OGRFieldSubType ogrSubType = OFSTNone;
729 if ( type == QVariant::Int )
730 ogrType = OFTInteger;
731 else if ( type == QVariant::LongLong )
732 ogrType = OFTInteger64;
733 else if ( type == QVariant::Double )
735 else if ( type == QVariant::Time )
737 else if ( type == QVariant::Date )
739 else if ( type == QVariant::DateTime )
740 ogrType = OFTDateTime;
741 else if ( type == QVariant::Bool )
743 ogrType = OFTInteger;
744 ogrSubType = OFSTBoolean;
746 else if ( type == QVariant::StringList || type == QVariant::List )
749 ogrSubType = OFSTJSON;
750 showWarning( tr(
"Field '%1' from layer %2 has been converted from a list to a JSON-formatted string value." ).arg( fieldName, layer->
name() ) );
758 OGR_Fld_SetWidth( fld.get(), ogrWidth );
759 if ( ogrSubType != OFSTNone )
760 OGR_Fld_SetSubType( fld.get(), ogrSubType );
762 if ( OGR_L_CreateField( hLayer, fld.get(),
true ) != OGRERR_NONE )
764 showWarning( tr(
"Creation of field %1 failed (OGR error: %2)" )
765 .arg( fieldName, QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
773 OGR_L_ResetReading( hLayer );
774 if ( CPLGetLastErrorType() != CE_None )
776 const QString msg( tr(
"Creation of layer failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
782 const QString uri = QStringLiteral(
"%1|layername=%2|option:QGIS_FORCE_WAL=ON" ).arg( offlineDbPath, tableName );
784 newLayer = std::make_unique<QgsVectorLayer>( uri, layer->
name() + layerNameSuffix, QStringLiteral(
"ogr" ), layerOptions );
789 if ( newLayer && newLayer->isValid() )
793 newLayer->startEditing();
801 if ( !selectedFids.isEmpty() )
815 long long featureCount = 1;
816 const int remotePkIdx = getLayerPkIdx( layer );
818 QList<QgsFeatureId> remoteFeatureIds;
819 QStringList remoteFeaturePks;
822 remoteFeatureIds << f.
id();
823 remoteFeaturePks << ( remotePkIdx >= 0 ? f.
attribute( remotePkIdx ).toString() : QString() );
830 QgsAttributes newAttrs( containerType ==
GPKG ? attrs.count() + 1 : attrs.count() );
831 for (
int it = 0; it < attrs.count(); ++it )
833 QVariant attr = attrs.at( it );
838 newAttrs[column++] = attr;
842 newLayer->addFeature( f );
846 if ( newLayer->commitChanges() )
852 const int layerId = getOrCreateLayerId( db, layer->
id() );
853 QList<QgsFeatureId> offlineFeatureIds;
858 offlineFeatureIds << f.
id();
862 sqlExec( db, QStringLiteral(
"BEGIN" ) );
863 const int remoteCount = remoteFeatureIds.size();
864 for (
int i = 0; i < remoteCount; i++ )
867 if ( i < offlineFeatureIds.count() )
869 addFidLookup( db, layerId, offlineFeatureIds.at( i ), remoteFeatureIds.at( i ), remoteFeaturePks.at( i ) );
873 showWarning( tr(
"Feature cannot be copied to the offline layer, please check if the online layer '%1' is still accessible." ).arg( layer->
name() ) );
878 sqlExec( db, QStringLiteral(
"COMMIT" ) );
882 showWarning( newLayer->commitErrors().join( QLatin1Char(
'\n' ) ) );
896 QStringList notNullFieldNames;
905 layer->
setDataSource( newLayer->source(), newLayer->name(), newLayer->dataProvider()->name() );
917 if ( notNullFieldNames.contains(
field.
name() ) )
919 notNullFieldNames.removeAll(
field.
name() );
930void QgsOfflineEditing::applyAttributesAdded(
QgsVectorLayer *remoteLayer,
sqlite3 *db,
int layerId,
int commitNo )
932 Q_ASSERT( remoteLayer );
934 const QString sql = QStringLiteral(
"SELECT \"name\", \"type\", \"length\", \"precision\", \"comment\" FROM 'log_added_attrs' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo );
935 QList<QgsField> fields = sqlQueryAttributesAdded( db, sql );
938 const QList<QgsVectorDataProvider::NativeType> nativeTypes = provider->
nativeTypes();
941 QMap < QVariant::Type, QString > typeNameLookup;
942 for (
int i = 0; i < nativeTypes.size(); i++ )
950 for (
int i = 0; i < fields.size(); i++ )
954 if ( typeNameLookup.contains(
field.
type() ) )
962 showWarning( QStringLiteral(
"Could not add attribute '%1' of type %2" ).arg(
field.
name() ).arg(
field.
type() ) );
971 Q_ASSERT( offlineLayer );
972 Q_ASSERT( remoteLayer );
974 const QString sql = QStringLiteral(
"SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
975 const QList<int> featureIdInts = sqlQueryInts( db, sql );
977 for (
const int id : featureIdInts )
997 const int newAttrsCount = remoteLayer->
fields().
count();
998 for ( QgsFeatureList::iterator it = features.begin(); it != features.end(); ++it )
1002 const QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
1005 for (
int it = 0; it < attrs.count(); ++it )
1007 const int remoteAttributeIndex = attrLookup.value( it, -1 );
1009 if ( remoteAttributeIndex == -1 )
1011 QVariant attr = attrs.at( it );
1012 if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QVariant::StringList )
1014 if ( attr.type() == QVariant::StringList || attr.type() == QVariant::List )
1016 attr = attr.toStringList();
1023 else if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QVariant::List )
1025 if ( attr.type() == QVariant::StringList || attr.type() == QVariant::List )
1027 attr = attr.toList();
1034 newAttrs[ remoteAttributeIndex ] = attr;
1045void QgsOfflineEditing::applyFeaturesRemoved(
QgsVectorLayer *remoteLayer,
sqlite3 *db,
int layerId )
1047 Q_ASSERT( remoteLayer );
1049 const QString sql = QStringLiteral(
"SELECT \"fid\" FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
1050 const QgsFeatureIds values = sqlQueryFeaturesRemoved( db, sql );
1055 for ( QgsFeatureIds::const_iterator it = values.constBegin(); it != values.constEnd(); ++it )
1057 const QgsFeatureId fid = remoteFid( db, layerId, *it, remoteLayer );
1066 Q_ASSERT( offlineLayer );
1067 Q_ASSERT( remoteLayer );
1069 const QString sql = QStringLiteral(
"SELECT \"fid\", \"attr\", \"value\" FROM 'log_feature_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2 " ).arg( layerId ).arg( commitNo );
1070 const AttributeValueChanges values = sqlQueryAttributeValueChanges( db, sql );
1074 QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
1076 for (
int i = 0; i < values.size(); i++ )
1078 const QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid, remoteLayer );
1079 QgsDebugMsgLevel( QStringLiteral(
"Offline changeAttributeValue %1 = %2" ).arg( attrLookup[ values.at( i ).attr ] ).arg( values.at( i ).value ), 4 );
1081 const int remoteAttributeIndex = attrLookup[ values.at( i ).attr ];
1082 QVariant attr = values.at( i ).value;
1083 if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QVariant::StringList )
1087 else if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QVariant::List )
1098void QgsOfflineEditing::applyGeometryChanges(
QgsVectorLayer *remoteLayer,
sqlite3 *db,
int layerId,
int commitNo )
1100 Q_ASSERT( remoteLayer );
1102 const QString sql = QStringLiteral(
"SELECT \"fid\", \"geom_wkt\" FROM 'log_geometry_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo );
1103 const GeometryChanges values = sqlQueryGeometryChanges( db, sql );
1107 for (
int i = 0; i < values.size(); i++ )
1109 const QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid, remoteLayer );
1119 Q_ASSERT( remoteLayer );
1125 QMap < QgsFeatureId, QString > newRemoteFids;
1132 const int remotePkIdx = getLayerPkIdx( remoteLayer );
1137 if ( offlineFid( db, layerId, f.
id() ) == -1 )
1139 newRemoteFids[ f.
id()] = remotePkIdx >= 0 ? f.
attribute( remotePkIdx ).toString() : QString();
1147 const QString sql = QStringLiteral(
"SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
1148 const QList<int> newOfflineFids = sqlQueryInts( db, sql );
1150 if ( newRemoteFids.size() != newOfflineFids.size() )
1158 sqlExec( db, QStringLiteral(
"BEGIN" ) );
1159 for ( QMap<QgsFeatureId, QString>::const_iterator it = newRemoteFids.constBegin(); it != newRemoteFids.constEnd(); ++it )
1161 addFidLookup( db, layerId, newOfflineFids.at( i++ ), it.key(), it.value() );
1163 sqlExec( db, QStringLiteral(
"COMMIT" ) );
1170 Q_ASSERT( offlineLayer );
1171 Q_ASSERT( remoteLayer );
1175 QMap <
int ,
int > attrLookup;
1178 for (
int i = 0; i < offlineAttrs.size(); i++ )
1187void QgsOfflineEditing::showWarning(
const QString &message )
1189 emit
warning( tr(
"Offline Editing Plugin" ), message );
1196 if ( !dbPath.isEmpty() )
1199 const int rc = database.
open( absoluteDbPath );
1200 if ( rc != SQLITE_OK )
1202 QgsDebugMsg( QStringLiteral(
"Could not open the SpatiaLite logging database" ) );
1203 showWarning( tr(
"Could not open the SpatiaLite logging database" ) );
1208 QgsDebugMsg( QStringLiteral(
"dbPath is empty!" ) );
1213int QgsOfflineEditing::getOrCreateLayerId(
sqlite3 *db,
const QString &qgisLayerId )
1215 QString sql = QStringLiteral(
"SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
1216 int layerId = sqlQueryInt( db, sql, -1 );
1217 if ( layerId == -1 )
1220 sql = QStringLiteral(
"SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'layer_id'" );
1221 const int newLayerId = sqlQueryInt( db, sql, -1 );
1224 sql = QStringLiteral(
"INSERT INTO 'log_layer_ids' VALUES (%1, '%2')" ).arg( newLayerId ).arg( qgisLayerId );
1229 sql = QStringLiteral(
"UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'layer_id'" ).arg( newLayerId + 1 );
1232 layerId = newLayerId;
1238int QgsOfflineEditing::getCommitNo(
sqlite3 *db )
1240 const QString sql = QStringLiteral(
"SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'commit_no'" );
1241 return sqlQueryInt( db, sql, -1 );
1244void QgsOfflineEditing::increaseCommitNo(
sqlite3 *db )
1246 const QString sql = QStringLiteral(
"UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'commit_no'" ).arg( getCommitNo( db ) + 1 );
1252 const QString sql = QStringLiteral(
"INSERT INTO 'log_fids' VALUES ( %1, %2, %3, %4 )" ).arg( layerId ).arg( offlineFid ).arg( remoteFid ).arg( sqlEscape( remotePk ) );
1258 const int pkIdx = getLayerPkIdx( remoteLayer );
1262 const QString sql = QStringLiteral(
"SELECT \"remote_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2" ).arg( layerId ).arg( offlineFid );
1263 return sqlQueryInt( db, sql, -1 );
1266 const QString sql = QStringLiteral(
"SELECT \"remote_pk\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2" ).arg( layerId ).arg( offlineFid );
1267 QString defaultValue;
1268 const QString pkValue = sqlQueryStr( db, sql, defaultValue );
1270 if ( pkValue.isNull() )
1275 const QString pkFieldName = remoteLayer->
fields().
at( pkIdx ).
name();
1286 const QString sql = QStringLiteral(
"SELECT \"offline_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"remote_fid\" = %2" ).arg( layerId ).arg( remoteFid );
1287 return sqlQueryInt( db, sql, -1 );
1292 const QString sql = QStringLiteral(
"SELECT COUNT(\"fid\") FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( fid );
1293 return ( sqlQueryInt( db, sql, 0 ) > 0 );
1296int QgsOfflineEditing::sqlExec(
sqlite3 *db,
const QString &sql )
1298 char *errmsg =
nullptr;
1299 const int rc = sqlite3_exec( db, sql.toUtf8(),
nullptr,
nullptr, &errmsg );
1300 if ( rc != SQLITE_OK )
1302 showWarning( errmsg );
1307QString QgsOfflineEditing::sqlQueryStr(
sqlite3 *db,
const QString &sql, QString &defaultValue )
1309 sqlite3_stmt *stmt =
nullptr;
1310 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1312 showWarning( sqlite3_errmsg( db ) );
1313 return defaultValue;
1316 QString value = defaultValue;
1317 const int ret = sqlite3_step( stmt );
1318 if ( ret == SQLITE_ROW )
1320 value = QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 0 ) ) );
1322 sqlite3_finalize( stmt );
1327int QgsOfflineEditing::sqlQueryInt(
sqlite3 *db,
const QString &sql,
int defaultValue )
1329 sqlite3_stmt *stmt =
nullptr;
1330 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1332 showWarning( sqlite3_errmsg( db ) );
1333 return defaultValue;
1336 int value = defaultValue;
1337 const int ret = sqlite3_step( stmt );
1338 if ( ret == SQLITE_ROW )
1340 value = sqlite3_column_int( stmt, 0 );
1342 sqlite3_finalize( stmt );
1347QList<int> QgsOfflineEditing::sqlQueryInts(
sqlite3 *db,
const QString &sql )
1351 sqlite3_stmt *stmt =
nullptr;
1352 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1354 showWarning( sqlite3_errmsg( db ) );
1358 int ret = sqlite3_step( stmt );
1359 while ( ret == SQLITE_ROW )
1361 values << sqlite3_column_int( stmt, 0 );
1363 ret = sqlite3_step( stmt );
1365 sqlite3_finalize( stmt );
1370QList<QgsField> QgsOfflineEditing::sqlQueryAttributesAdded(
sqlite3 *db,
const QString &sql )
1372 QList<QgsField> values;
1374 sqlite3_stmt *stmt =
nullptr;
1375 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1377 showWarning( sqlite3_errmsg( db ) );
1381 int ret = sqlite3_step( stmt );
1382 while ( ret == SQLITE_ROW )
1384 const QgsField field( QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 0 ) ) ),
1385 static_cast< QVariant::Type
>( sqlite3_column_int( stmt, 1 ) ),
1387 sqlite3_column_int( stmt, 2 ),
1388 sqlite3_column_int( stmt, 3 ),
1389 QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 4 ) ) ) );
1392 ret = sqlite3_step( stmt );
1394 sqlite3_finalize( stmt );
1403 sqlite3_stmt *stmt =
nullptr;
1404 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1406 showWarning( sqlite3_errmsg( db ) );
1410 int ret = sqlite3_step( stmt );
1411 while ( ret == SQLITE_ROW )
1413 values << sqlite3_column_int( stmt, 0 );
1415 ret = sqlite3_step( stmt );
1417 sqlite3_finalize( stmt );
1422QgsOfflineEditing::AttributeValueChanges QgsOfflineEditing::sqlQueryAttributeValueChanges(
sqlite3 *db,
const QString &sql )
1424 AttributeValueChanges values;
1426 sqlite3_stmt *stmt =
nullptr;
1427 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1429 showWarning( sqlite3_errmsg( db ) );
1433 int ret = sqlite3_step( stmt );
1434 while ( ret == SQLITE_ROW )
1436 AttributeValueChange change;
1437 change.fid = sqlite3_column_int( stmt, 0 );
1438 change.attr = sqlite3_column_int( stmt, 1 );
1439 change.value = QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 2 ) ) );
1442 ret = sqlite3_step( stmt );
1444 sqlite3_finalize( stmt );
1449QgsOfflineEditing::GeometryChanges QgsOfflineEditing::sqlQueryGeometryChanges(
sqlite3 *db,
const QString &sql )
1451 GeometryChanges values;
1453 sqlite3_stmt *stmt =
nullptr;
1454 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1456 showWarning( sqlite3_errmsg( db ) );
1460 int ret = sqlite3_step( stmt );
1461 while ( ret == SQLITE_ROW )
1463 GeometryChange change;
1464 change.fid = sqlite3_column_int( stmt, 0 );
1465 change.geom_wkt = QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 1 ) ) );
1468 ret = sqlite3_step( stmt );
1470 sqlite3_finalize( stmt );
1475void QgsOfflineEditing::committedAttributesAdded(
const QString &qgisLayerId,
const QList<QgsField> &addedAttributes )
1482 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1483 const int commitNo = getCommitNo( database.get() );
1487 const QString sql = QStringLiteral(
"INSERT INTO 'log_added_attrs' VALUES ( %1, %2, '%3', %4, %5, %6, '%7' )" )
1495 sqlExec( database.get(), sql );
1498 increaseCommitNo( database.get() );
1501void QgsOfflineEditing::committedFeaturesAdded(
const QString &qgisLayerId,
const QgsFeatureList &addedFeatures )
1508 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1512 const QString dataSourceString = layer->
source();
1518 if ( !offlinePath.contains(
".gpkg" ) )
1520 tableName = uri.
table();
1525 const QVariantMap decodedUri = ogrProviderMetaData->
decodeUri( dataSourceString );
1526 tableName = decodedUri.value( QStringLiteral(
"layerName" ) ).toString();
1527 if ( tableName.isEmpty() )
1529 showWarning( tr(
"Could not deduce table name from data source %1." ).arg( dataSourceString ) );
1534 const QString sql = QStringLiteral(
"SELECT ROWID FROM '%1' ORDER BY ROWID DESC LIMIT %2" ).arg( tableName ).arg( addedFeatures.size() );
1535 const QList<int> newFeatureIds = sqlQueryInts( database.get(), sql );
1536 for (
int i = newFeatureIds.size() - 1; i >= 0; i-- )
1538 const QString sql = QStringLiteral(
"INSERT INTO 'log_added_features' VALUES ( %1, %2 )" )
1540 .arg( newFeatureIds.at( i ) );
1541 sqlExec( database.get(), sql );
1545void QgsOfflineEditing::committedFeaturesRemoved(
const QString &qgisLayerId,
const QgsFeatureIds &deletedFeatureIds )
1552 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1556 if ( isAddedFeature( database.get(), layerId,
id ) )
1559 const QString sql = QStringLiteral(
"DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg(
id );
1560 sqlExec( database.get(), sql );
1564 const QString sql = QStringLiteral(
"INSERT INTO 'log_removed_features' VALUES ( %1, %2)" )
1567 sqlExec( database.get(), sql );
1572void QgsOfflineEditing::committedAttributeValuesChanges(
const QString &qgisLayerId,
const QgsChangedAttributesMap &changedAttrsMap )
1579 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1580 const int commitNo = getCommitNo( database.get() );
1582 for ( QgsChangedAttributesMap::const_iterator cit = changedAttrsMap.begin(); cit != changedAttrsMap.end(); ++cit )
1585 if ( isAddedFeature( database.get(), layerId, fid ) )
1591 for ( QgsAttributeMap::const_iterator it = attrMap.constBegin(); it != attrMap.constEnd(); ++it )
1593 QString value = it.value().type() == QVariant::StringList || it.value().type() == QVariant::List ?
QgsJsonUtils::encodeValue( it.value() ) : it.value().toString();
1594 value.replace( QLatin1String(
"'" ), QLatin1String(
"''" ) );
1595 const QString sql = QStringLiteral(
"INSERT INTO 'log_feature_updates' VALUES ( %1, %2, %3, %4, '%5' )" )
1601 sqlExec( database.get(), sql );
1605 increaseCommitNo( database.get() );
1608void QgsOfflineEditing::committedGeometriesChanges(
const QString &qgisLayerId,
const QgsGeometryMap &changedGeometries )
1615 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1616 const int commitNo = getCommitNo( database.get() );
1618 for ( QgsGeometryMap::const_iterator it = changedGeometries.begin(); it != changedGeometries.end(); ++it )
1621 if ( isAddedFeature( database.get(), layerId, fid ) )
1627 const QString sql = QStringLiteral(
"INSERT INTO 'log_geometry_updates' VALUES ( %1, %2, %3, '%4' )" )
1631 .arg( geom.
asWkt() );
1632 sqlExec( database.get(), sql );
1637 increaseCommitNo( database.get() );
1640void QgsOfflineEditing::startListenFeatureChanges()
1642 QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( sender() );
1651 this, &QgsOfflineEditing::committedAttributesAdded );
1653 this, &QgsOfflineEditing::committedAttributeValuesChanges );
1655 this, &QgsOfflineEditing::committedGeometriesChanges );
1658 this, &QgsOfflineEditing::committedFeaturesAdded );
1660 this, &QgsOfflineEditing::committedFeaturesRemoved );
1663void QgsOfflineEditing::stopListenFeatureChanges()
1665 QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( sender() );
1674 this, &QgsOfflineEditing::committedAttributesAdded );
1676 this, &QgsOfflineEditing::committedAttributeValuesChanges );
1678 this, &QgsOfflineEditing::committedGeometriesChanges );
1681 this, &QgsOfflineEditing::committedFeaturesAdded );
1683 this, &QgsOfflineEditing::committedFeaturesRemoved );
1686void QgsOfflineEditing::setupLayer(
QgsMapLayer *layer )
1690 if (
QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( layer ) )
1701int QgsOfflineEditing::getLayerPkIdx(
const QgsVectorLayer *layer )
const
1704 if ( pkAttrs.length() == 1 )
1707 const QVariant::Type pkType = pkField.
type();
1709 if ( pkType == QVariant::String )
1718QString QgsOfflineEditing::sqlEscape( QString value )
const
1720 if ( value.isNull() )
1721 return QStringLiteral(
"NULL" );
1723 value.replace(
"'",
"''" );
1725 return QStringLiteral(
"'%1'" ).arg( value );
virtual void invalidateConnections(const QString &connection)
Invalidate connections corresponding to specified name.
Class for storing the component parts of a RDBMS 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.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
This class wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setFilterFids(const QgsFeatureIds &fids)
Sets the feature IDs that should be fetched.
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
FilterType filterType() const
Returns the attribute/ID filter type which is currently set on this request.
@ FilterFids
Filter using feature IDs.
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.
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.
Q_GADGET Constraints constraints
Encapsulate a field in an attribute table or data source.
QVariant::Type subType() const
If the field is a collection, gets its element's type.
QgsFieldConstraints constraints
void setTypeName(const QString &typeName)
Set the field type.
Container of fields for a vector layer.
int indexOf(const QString &fieldName) const
Gets the field index from the field name.
int count() const
Returns number of items.
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).
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
A geometry is the spatial representation of a feature.
static QgsGeometry fromWkt(const QString &wkt)
Creates a new geometry from a WKT string.
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, QVariant::Type type=QVariant::Invalid)
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
QString id() const
Returns the layer's unique ID, which is used to access this layer from QgsProject.
Q_INVOKABLE void setCustomProperty(const QString &key, const QVariant &value)
Set a custom property for layer.
void setDataSource(const QString &dataSource, const QString &baseName, const QString &provider, bool loadDefaultStyleFlag=false)
Updates the data source of the 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=QStringLiteral(" (offline)"))
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.
QString title() const
Returns the project's title.
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.
This is a container for configuration of the snapping of the project.
QString connectionString() const
Returns the connection string of the transaction.
This is the base class for vector data providers.
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)
void committedAttributesAdded(const QString &layerId, const QList< QgsField > &addedAttributes)
void committedGeometriesChanges(const QString &layerId, const QgsGeometryMap &changedGeometries)
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 data sets.
Q_INVOKABLE QgsWkbTypes::Type wkbType() const FINAL
Returns the WKBType or WKBUnknown in case of error.
void committedFeaturesAdded(const QString &layerId, const QgsFeatureList &addedFeatures)
Emitted when features are added to the provider if not in transaction mode.
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.
bool isSpatial() const FINAL
Returns true if this is a geometry layer and false in case of NoGeometry (table only) or UnknownGeome...
void setFieldConstraint(int index, QgsFieldConstraints::Constraint constraint, QgsFieldConstraints::ConstraintStrength strength=QgsFieldConstraints::ConstraintStrengthHard)
Sets a constraint for a specified field index.
bool deleteFeature(QgsFeatureId fid, DeleteContext *context=nullptr)
Deletes a feature from the layer (but does not commit it).
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
QgsAttributeList attributeList() const
Returns list of attribute indexes.
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.
QgsExpressionContext createExpressionContext() const FINAL
This method needs to be reimplemented in all classes which implement this interface and return an exp...
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.
QgsVectorDataProvider * dataProvider() FINAL
Returns the layer's data provider, it may be nullptr.
bool changeAttributeValue(QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue=QVariant(), bool skipDefaultValues=false)
Changes an attribute value for a feature (but does not immediately commit the changes).
bool addFeature(QgsFeature &feature, QgsFeatureSink::Flags flags=QgsFeatureSink::Flags()) FINAL
Adds a single feature to the sink.
QgsAttributeList primaryKeyAttributes() const
Returns the list of attributes which make up the layer's primary keys.
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 bool hasM(Type type) SIP_HOLDGIL
Tests whether a WKB type contains m values.
Type
The WKB type describes the number of dimensions a geometry has.
static QString displayString(Type type) SIP_HOLDGIL
Returns a non-translated display string type for a WKB type, e.g., the geometry name used in WKT geom...
static Type flatType(Type type) SIP_HOLDGIL
Returns the flat type for a WKB type.
static bool hasZ(Type type) SIP_HOLDGIL
Tests whether a WKB type contains the z-dimension.
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
void * OGRSpatialReferenceH
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 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.