38#include <QDomDocument>
41#include <QRegularExpression>
43#include <ogr_srs_api.h>
53#include <spatialite.h>
57#define CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE "isOfflineEditable"
58#define CUSTOM_PROPERTY_REMOTE_SOURCE "remoteSource"
59#define CUSTOM_PROPERTY_REMOTE_PROVIDER "remoteProvider"
60#define CUSTOM_SHOW_FEATURE_COUNT "showFeatureCount"
61#define CUSTOM_PROPERTY_ORIGINAL_LAYERID "remoteLayerId"
62#define CUSTOM_PROPERTY_LAYERNAME_SUFFIX "layerNameSuffix"
63#define PROJECT_ENTRY_SCOPE_OFFLINE "OfflineEditingPlugin"
64#define PROJECT_ENTRY_KEY_OFFLINE_DB_PATH "/OfflineDbPath"
85 if ( layerIds.isEmpty() )
90 const QString dbPath = QDir( offlineDataPath ).absoluteFilePath( offlineDbFile );
91 if ( createOfflineDb( dbPath, containerType ) )
94 const int rc = database.
open( dbPath );
95 if ( rc != SQLITE_OK )
97 showWarning( tr(
"Could not open the SpatiaLite database" ) );
102 createLoggingTables( database.get() );
107 for (
int i = 0; i < layerIds.count(); i++ )
115 convertToOfflineLayer( vl, database.get(), dbPath, onlySelected, containerType, layerNameSuffix );
123 if ( projectTitle.isEmpty() )
127 projectTitle += QLatin1String(
" (offline)" );
158 QMap<int, std::shared_ptr<QgsVectorLayer>> remoteLayersByOfflineId;
159 QMap<int, QgsVectorLayer *> offlineLayersByOfflineId;
161 for ( QMap<QString, QgsMapLayer *>::iterator layer_it = mapLayers.begin() ; layer_it != mapLayers.end(); ++layer_it )
163 QgsVectorLayer *offlineLayer( qobject_cast<QgsVectorLayer *>( layer_it.value() ) );
165 if ( !offlineLayer || !offlineLayer->
isValid() )
167 QgsDebugMsgLevel( QStringLiteral(
"Skipping offline layer %1 because it is an invalid layer" ).arg( layer_it.key() ), 4 );
176 QString remoteName = offlineLayer->
name();
178 if ( remoteName.endsWith( remoteNameSuffix ) )
179 remoteName.chop( remoteNameSuffix.size() );
182 std::shared_ptr<QgsVectorLayer> remoteLayer = std::make_shared<QgsVectorLayer>( remoteSource, remoteName, remoteProvider, options );
184 if ( ! remoteLayer->isValid() )
186 QgsDebugMsgLevel( QStringLiteral(
"Skipping offline layer %1 because it failed to recreate its corresponding remote layer" ).arg( offlineLayer->
id() ), 4 );
191 if ( remoteLayer->providerType().contains( QLatin1String(
"WFS" ), Qt::CaseInsensitive ) )
202 const QString sql = QStringLiteral(
"SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( offlineLayer->
id() );
203 const int layerId = sqlQueryInt( database.get(), sql, -1 );
207 QgsDebugMsgLevel( QStringLiteral(
"Skipping offline layer %1 because it failed to determine the offline editing layer id" ).arg( offlineLayer->
id() ), 4 );
211 remoteLayersByOfflineId.insert( layerId, remoteLayer );
212 offlineLayersByOfflineId.insert( layerId, offlineLayer );
215 QgsDebugMsgLevel( QStringLiteral(
"Found %1 offline layers in total" ).arg( offlineLayersByOfflineId.count() ), 4 );
217 QMap<QPair<QString, QString>, std::shared_ptr<QgsTransactionGroup>> transactionGroups;
218 if ( useTransaction )
220 for (
const std::shared_ptr<QgsVectorLayer> &remoteLayer : std::as_const( remoteLayersByOfflineId ) )
223 const QPair<QString, QString> pair( remoteLayer->providerType(), connectionString );
224 std::shared_ptr<QgsTransactionGroup> transactionGroup = transactionGroups.value( pair );
226 if ( !transactionGroup.get() )
227 transactionGroup = std::make_shared<QgsTransactionGroup>();
229 if ( !transactionGroup->addLayer( remoteLayer.get() ) )
231 QgsDebugMsgLevel( QStringLiteral(
"Failed to add a layer %1 into transaction group, will be modified without transaction" ).arg( remoteLayer->name() ), 4 );
235 transactionGroups.insert( pair, transactionGroup );
238 QgsDebugMsgLevel( QStringLiteral(
"Created %1 transaction groups" ).arg( transactionGroups.count() ), 4 );
241 const QList<int> offlineIds = remoteLayersByOfflineId.keys();
242 for (
int offlineLayerId : offlineIds )
244 std::shared_ptr<QgsVectorLayer> remoteLayer = remoteLayersByOfflineId.value( offlineLayerId );
245 QgsVectorLayer *offlineLayer = offlineLayersByOfflineId.value( offlineLayerId );
248 if ( !remoteLayer->startEditing() && !remoteLayer->isEditable() )
250 QgsDebugMsgLevel( QStringLiteral(
"Failed to turn layer %1 into editing mode" ).arg( remoteLayer->name() ), 4 );
255 const int commitNo = getCommitNo( database.get() );
256 QgsDebugMsgLevel( QStringLiteral(
"Found %1 commits" ).arg( commitNo ), 4 );
258 for (
int i = 0; i < commitNo; i++ )
260 QgsDebugMsgLevel( QStringLiteral(
"Apply commits chronologically from %1" ).arg( offlineLayer->
name() ), 4 );
262 applyAttributesAdded( remoteLayer.get(), database.get(), offlineLayerId, i );
263 applyAttributeValueChanges( offlineLayer, remoteLayer.get(), database.get(), offlineLayerId, i );
264 applyGeometryChanges( remoteLayer.get(), database.get(), offlineLayerId, i );
267 applyFeaturesAdded( offlineLayer, remoteLayer.get(), database.get(), offlineLayerId );
268 applyFeaturesRemoved( remoteLayer.get(), database.get(), offlineLayerId );
272 for (
int offlineLayerId : offlineIds )
274 std::shared_ptr<QgsVectorLayer> remoteLayer = remoteLayersByOfflineId[offlineLayerId];
275 QgsVectorLayer *offlineLayer = offlineLayersByOfflineId[offlineLayerId];
277 if ( !remoteLayer->isEditable() )
280 if ( remoteLayer->commitChanges() )
283 updateFidLookup( remoteLayer.get(), database.get(), offlineLayerId );
287 sql = QStringLiteral(
"DELETE FROM 'log_added_attrs' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
288 sqlExec( database.get(), sql );
289 sql = QStringLiteral(
"DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
290 sqlExec( database.get(), sql );
291 sql = QStringLiteral(
"DELETE FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
292 sqlExec( database.get(), sql );
293 sql = QStringLiteral(
"DELETE FROM 'log_feature_updates' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
294 sqlExec( database.get(), sql );
295 sql = QStringLiteral(
"DELETE FROM 'log_geometry_updates' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
296 sqlExec( database.get(), sql );
300 showWarning( remoteLayer->commitErrors().join( QLatin1Char(
'\n' ) ) );
307 remoteLayer->reload();
308 offlineLayer->
setDataSource( remoteLayer->source(), remoteLayer->name(), remoteLayer->dataProvider()->name() );
324 const QgsFields fields = remoteLayer->fields();
325 for (
const QgsField &field : fields )
327 if ( !remoteLayer->dataProvider()->defaultValueClause( remoteLayer->fields().fieldOriginIndex( remoteLayer->fields().indexOf( field.name() ) ) ).isEmpty() )
339 const QString sql = QStringLiteral(
"UPDATE 'log_indices' SET 'last_index' = 0 WHERE \"name\" = 'commit_no'" );
340 sqlExec( database.get(), sql );
344void QgsOfflineEditing::initializeSpatialMetadata(
sqlite3 *sqlite_handle )
346#ifdef HAVE_SPATIALITE
348 if ( !sqlite_handle )
351 char **results =
nullptr;
353 int ret = sqlite3_get_table( sqlite_handle,
"select count(*) from sqlite_master", &results, &rows, &columns,
nullptr );
354 if ( ret != SQLITE_OK )
359 for (
int i = 1; i <= rows; i++ )
360 count = atoi( results[( i * columns ) + 0] );
363 sqlite3_free_table( results );
368 bool above41 =
false;
369 ret = sqlite3_get_table( sqlite_handle,
"select spatialite_version()", &results, &rows, &columns,
nullptr );
370 if ( ret == SQLITE_OK && rows == 1 && columns == 1 )
372 const QString version = QString::fromUtf8( results[1] );
373#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
374 QStringList parts = version.split(
' ', QString::SkipEmptyParts );
376 const QStringList parts = version.split(
' ', Qt::SkipEmptyParts );
378 if ( !parts.empty() )
380#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
381 QStringList verparts = parts.at( 0 ).split(
'.', QString::SkipEmptyParts );
383 const QStringList verparts = parts.at( 0 ).split(
'.', Qt::SkipEmptyParts );
385 above41 = verparts.size() >= 2 && ( verparts.at( 0 ).toInt() > 4 || ( verparts.at( 0 ).toInt() == 4 && verparts.at( 1 ).toInt() >= 1 ) );
389 sqlite3_free_table( results );
392 char *errMsg =
nullptr;
393 ret = sqlite3_exec( sqlite_handle, above41 ?
"SELECT InitSpatialMetadata(1)" :
"SELECT InitSpatialMetadata()", nullptr, nullptr, &errMsg );
395 if ( ret != SQLITE_OK )
397 QString errCause = tr(
"Unable to initialize SpatialMetadata:\n" );
398 errCause += QString::fromUtf8( errMsg );
399 showWarning( errCause );
400 sqlite3_free( errMsg );
403 spatial_ref_sys_init( sqlite_handle, 0 );
405 ( void )sqlite_handle;
409bool QgsOfflineEditing::createOfflineDb(
const QString &offlineDbPath, ContainerType containerType )
412 char *errMsg =
nullptr;
413 const QFile newDb( offlineDbPath );
414 if ( newDb.exists() )
416 QFile::remove( offlineDbPath );
421 const QFileInfo fullPath = QFileInfo( offlineDbPath );
422 const QDir path = fullPath.dir();
425 QDir().mkpath( path.absolutePath() );
428 const QString dbPath = newDb.fileName();
431 switch ( containerType )
435 OGRSFDriverH hGpkgDriver = OGRGetDriverByName(
"GPKG" );
438 showWarning( tr(
"Creation of database failed. GeoPackage driver not found." ) );
445 showWarning( tr(
"Creation of database failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
457 ret = database.
open_v2( dbPath, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
nullptr );
461 QString errCause = tr(
"Could not create a new database\n" );
463 showWarning( errCause );
467 ret = sqlite3_exec( database.get(),
"PRAGMA foreign_keys = 1",
nullptr,
nullptr, &errMsg );
468 if ( ret != SQLITE_OK )
470 showWarning( tr(
"Unable to activate FOREIGN_KEY constraints" ) );
471 sqlite3_free( errMsg );
474 initializeSpatialMetadata( database.get() );
478void QgsOfflineEditing::createLoggingTables(
sqlite3 *db )
481 QString sql = QStringLiteral(
"CREATE TABLE 'log_indices' ('name' TEXT, 'last_index' INTEGER)" );
484 sql = QStringLiteral(
"INSERT INTO 'log_indices' VALUES ('commit_no', 0)" );
487 sql = QStringLiteral(
"INSERT INTO 'log_indices' VALUES ('layer_id', 0)" );
491 sql = QStringLiteral(
"CREATE TABLE 'log_layer_ids' ('id' INTEGER, 'qgis_id' TEXT)" );
495 sql = QStringLiteral(
"CREATE TABLE 'log_fids' ('layer_id' INTEGER, 'offline_fid' INTEGER, 'remote_fid' INTEGER, 'remote_pk' TEXT)" );
499 sql = QStringLiteral(
"CREATE TABLE 'log_added_attrs' ('layer_id' INTEGER, 'commit_no' INTEGER, " );
500 sql += QLatin1String(
"'name' TEXT, 'type' INTEGER, 'length' INTEGER, 'precision' INTEGER, 'comment' TEXT)" );
504 sql = QStringLiteral(
"CREATE TABLE 'log_added_features' ('layer_id' INTEGER, 'fid' INTEGER)" );
508 sql = QStringLiteral(
"CREATE TABLE 'log_removed_features' ('layer_id' INTEGER, 'fid' INTEGER)" );
512 sql = QStringLiteral(
"CREATE TABLE 'log_feature_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'attr' INTEGER, 'value' TEXT)" );
516 sql = QStringLiteral(
"CREATE TABLE 'log_geometry_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'geom_wkt' TEXT)" );
524void QgsOfflineEditing::convertToOfflineLayer(
QgsVectorLayer *layer,
sqlite3 *db,
const QString &offlineDbPath,
bool onlySelected, ContainerType containerType,
const QString &layerNameSuffix )
526 if ( !layer || !layer->
isValid() )
528 QgsDebugMsgLevel( QStringLiteral(
"Layer %1 is invalid and cannot be copied" ).arg( layer ? layer->
id() : QStringLiteral(
"<UNKNOWN>" ) ), 4 );
532 const QString tableName = layer->
id();
533 QgsDebugMsgLevel( QStringLiteral(
"Creating offline table %1 ..." ).arg( tableName ), 4 );
536 std::unique_ptr<QgsVectorLayer> newLayer;
538 switch ( containerType )
542#ifdef HAVE_SPATIALITE
544 QString sql = QStringLiteral(
"CREATE TABLE '%1' (" ).arg( tableName );
547 for (
const auto &field : providerFields )
550 const QVariant::Type type = field.type();
551 if ( type == QVariant::Int || type == QVariant::LongLong )
553 dataType = QStringLiteral(
"INTEGER" );
555 else if ( type == QVariant::Double )
557 dataType = QStringLiteral(
"REAL" );
559 else if ( type == QVariant::String )
561 dataType = QStringLiteral(
"TEXT" );
563 else if ( type == QVariant::StringList || type == QVariant::List )
565 dataType = QStringLiteral(
"TEXT" );
566 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() ) );
570 showWarning( tr(
"%1: Unknown data type %2. Not using type affinity for the field." ).arg( field.name(), QVariant::typeToName( type ) ) );
573 sql += delim + QStringLiteral(
"'%1' %2" ).arg( field.name(), dataType );
578 int rc = sqlExec( db, sql );
589 geomType = QStringLiteral(
"POINT" );
592 geomType = QStringLiteral(
"MULTIPOINT" );
595 geomType = QStringLiteral(
"LINESTRING" );
598 geomType = QStringLiteral(
"MULTILINESTRING" );
601 geomType = QStringLiteral(
"POLYGON" );
604 geomType = QStringLiteral(
"MULTIPOLYGON" );
611 QString zmInfo = QStringLiteral(
"XY" );
620 if ( layer->
crs().
authid().startsWith( QLatin1String(
"EPSG:" ), Qt::CaseInsensitive ) )
622 epsgCode = layer->
crs().
authid().mid( 5 );
627 showWarning( tr(
"Layer %1 has unsupported Coordinate Reference System (%2)." ).arg( layer->
name(), layer->
crs().
authid() ) );
630 const QString sqlAddGeom = QStringLiteral(
"SELECT AddGeometryColumn('%1', 'Geometry', %2, '%3', '%4')" )
631 .arg( tableName, epsgCode, geomType, zmInfo );
634 const QString sqlCreateIndex = QStringLiteral(
"SELECT CreateSpatialIndex('%1', 'Geometry')" ).arg( tableName );
636 if ( rc == SQLITE_OK )
638 rc = sqlExec( db, sqlAddGeom );
639 if ( rc == SQLITE_OK )
641 rc = sqlExec( db, sqlCreateIndex );
646 if ( rc != SQLITE_OK )
648 showWarning( tr(
"Filling SpatiaLite for layer %1 failed" ).arg( layer->
name() ) );
653 const QString connectionString = QStringLiteral(
"dbname='%1' table='%2'%3 sql=" )
655 tableName, layer->
isSpatial() ?
"(Geometry)" :
"" );
657 newLayer = std::make_unique<QgsVectorLayer>( connectionString,
658 layer->
name() + layerNameSuffix, QStringLiteral(
"spatialite" ), options );
662 showWarning( tr(
"No Spatialite support available" ) );
670 char **options =
nullptr;
672 options = CSLSetNameValue( options,
"OVERWRITE",
"YES" );
673 options = CSLSetNameValue( options,
"IDENTIFIER", tr(
"%1 (offline)" ).arg( layer->
id() ).toUtf8().constData() );
674 options = CSLSetNameValue( options,
"DESCRIPTION", layer->
dataComment().toUtf8().constData() );
677 const QString fidBase( QStringLiteral(
"fid" ) );
678 QString fid = fidBase;
682 fid = fidBase +
'_' + QString::number( counter );
685 if ( counter == 10000 )
687 showWarning( tr(
"Cannot make FID-name for GPKG " ) );
691 options = CSLSetNameValue( options,
"FID", fid.toUtf8().constData() );
695 options = CSLSetNameValue( options,
"GEOMETRY_COLUMN",
"geom" );
696 options = CSLSetNameValue( options,
"SPATIAL_INDEX",
"YES" );
699 OGRSFDriverH hDriver =
nullptr;
702 OGRLayerH hLayer = OGR_DS_CreateLayer( hDS.get(), tableName.toUtf8().constData(), hSRS,
static_cast<OGRwkbGeometryType
>( layer->
wkbType() ), options );
703 CSLDestroy( options );
708 showWarning( tr(
"Creation of layer failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
713 for (
const auto &field : providerFields )
715 const QString fieldName( field.name() );
716 const QVariant::Type type = field.type();
717 OGRFieldType ogrType( OFTString );
718 OGRFieldSubType ogrSubType = OFSTNone;
719 if ( type == QVariant::Int )
720 ogrType = OFTInteger;
721 else if ( type == QVariant::LongLong )
722 ogrType = OFTInteger64;
723 else if ( type == QVariant::Double )
725 else if ( type == QVariant::Time )
727 else if ( type == QVariant::Date )
729 else if ( type == QVariant::DateTime )
730 ogrType = OFTDateTime;
731 else if ( type == QVariant::Bool )
733 ogrType = OFTInteger;
734 ogrSubType = OFSTBoolean;
736 else if ( type == QVariant::StringList || type == QVariant::List )
739 ogrSubType = OFSTJSON;
740 showWarning( tr(
"Field '%1' from layer %2 has been converted from a list to a JSON-formatted string value." ).arg( fieldName, layer->
name() ) );
745 const int ogrWidth = field.length();
748 OGR_Fld_SetWidth( fld.get(), ogrWidth );
749 if ( ogrSubType != OFSTNone )
750 OGR_Fld_SetSubType( fld.get(), ogrSubType );
752 if ( OGR_L_CreateField( hLayer, fld.get(),
true ) != OGRERR_NONE )
754 showWarning( tr(
"Creation of field %1 failed (OGR error: %2)" )
755 .arg( fieldName, QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
763 OGR_L_ResetReading( hLayer );
764 if ( CPLGetLastErrorType() != CE_None )
766 const QString msg( tr(
"Creation of layer failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
772 const QString uri = QStringLiteral(
"%1|layername=%2|option:QGIS_FORCE_WAL=ON" ).arg( offlineDbPath, tableName );
774 newLayer = std::make_unique<QgsVectorLayer>( uri, layer->
name() + layerNameSuffix, QStringLiteral(
"ogr" ), layerOptions );
779 if ( newLayer && newLayer->isValid() )
783 newLayer->startEditing();
791 if ( !selectedFids.isEmpty() )
805 long long featureCount = 1;
806 const int remotePkIdx = getLayerPkIdx( layer );
808 QList<QgsFeatureId> remoteFeatureIds;
809 QStringList remoteFeaturePks;
812 remoteFeatureIds << f.
id();
813 remoteFeaturePks << ( remotePkIdx >= 0 ? f.
attribute( remotePkIdx ).toString() : QString() );
820 QgsAttributes newAttrs( containerType ==
GPKG ? attrs.count() + 1 : attrs.count() );
821 for (
int it = 0; it < attrs.count(); ++it )
823 QVariant attr = attrs.at( it );
828 newAttrs[column++] = attr;
832 newLayer->addFeature( f );
836 if ( newLayer->commitChanges() )
842 const int layerId = getOrCreateLayerId( db, layer->
id() );
843 QList<QgsFeatureId> offlineFeatureIds;
848 offlineFeatureIds << f.
id();
852 sqlExec( db, QStringLiteral(
"BEGIN" ) );
853 const int remoteCount = remoteFeatureIds.size();
854 for (
int i = 0; i < remoteCount; i++ )
857 if ( i < offlineFeatureIds.count() )
859 addFidLookup( db, layerId, offlineFeatureIds.at( i ), remoteFeatureIds.at( i ), remoteFeaturePks.at( i ) );
863 showWarning( tr(
"Feature cannot be copied to the offline layer, please check if the online layer '%1' is still accessible." ).arg( layer->
name() ) );
868 sqlExec( db, QStringLiteral(
"COMMIT" ) );
872 showWarning( newLayer->commitErrors().join( QLatin1Char(
'\n' ) ) );
886 QStringList notNullFieldNames;
887 for (
const QgsField &field : fields )
891 notNullFieldNames << field.name();
895 layer->
setDataSource( newLayer->source(), newLayer->name(), newLayer->dataProvider()->name() );
897 for (
const QgsField &field : fields )
907 if ( notNullFieldNames.contains( field.name() ) )
909 notNullFieldNames.removeAll( field.name() );
920void QgsOfflineEditing::applyAttributesAdded(
QgsVectorLayer *remoteLayer,
sqlite3 *db,
int layerId,
int commitNo )
922 Q_ASSERT( remoteLayer );
924 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 );
925 QList<QgsField> fields = sqlQueryAttributesAdded( db, sql );
928 const QList<QgsVectorDataProvider::NativeType> nativeTypes = provider->
nativeTypes();
931 QMap < QVariant::Type, QString > typeNameLookup;
932 for (
int i = 0; i < nativeTypes.size(); i++ )
940 for (
int i = 0; i < fields.size(); i++ )
944 if ( typeNameLookup.contains( field.
type() ) )
952 showWarning( QStringLiteral(
"Could not add attribute '%1' of type %2" ).arg( field.
name() ).arg( field.
type() ) );
961 Q_ASSERT( offlineLayer );
962 Q_ASSERT( remoteLayer );
964 const QString sql = QStringLiteral(
"SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
965 const QList<int> featureIdInts = sqlQueryInts( db, sql );
967 for (
const int id : featureIdInts )
987 const int newAttrsCount = remoteLayer->
fields().
count();
988 for ( QgsFeatureList::iterator it = features.begin(); it != features.end(); ++it )
992 const QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
995 for (
int it = 0; it < attrs.count(); ++it )
997 const int remoteAttributeIndex = attrLookup.value( it, -1 );
999 if ( remoteAttributeIndex == -1 )
1001 QVariant attr = attrs.at( it );
1002 if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QVariant::StringList )
1004 if ( attr.type() == QVariant::StringList || attr.type() == QVariant::List )
1006 attr = attr.toStringList();
1013 else if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QVariant::List )
1015 if ( attr.type() == QVariant::StringList || attr.type() == QVariant::List )
1017 attr = attr.toList();
1024 newAttrs[ remoteAttributeIndex ] = attr;
1035void QgsOfflineEditing::applyFeaturesRemoved(
QgsVectorLayer *remoteLayer,
sqlite3 *db,
int layerId )
1037 Q_ASSERT( remoteLayer );
1039 const QString sql = QStringLiteral(
"SELECT \"fid\" FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
1040 const QgsFeatureIds values = sqlQueryFeaturesRemoved( db, sql );
1045 for ( QgsFeatureIds::const_iterator it = values.constBegin(); it != values.constEnd(); ++it )
1047 const QgsFeatureId fid = remoteFid( db, layerId, *it, remoteLayer );
1056 Q_ASSERT( offlineLayer );
1057 Q_ASSERT( remoteLayer );
1059 const QString sql = QStringLiteral(
"SELECT \"fid\", \"attr\", \"value\" FROM 'log_feature_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2 " ).arg( layerId ).arg( commitNo );
1060 const AttributeValueChanges values = sqlQueryAttributeValueChanges( db, sql );
1064 QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
1066 for (
int i = 0; i < values.size(); i++ )
1068 const QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid, remoteLayer );
1069 QgsDebugMsgLevel( QStringLiteral(
"Offline changeAttributeValue %1 = %2" ).arg( attrLookup[ values.at( i ).attr ] ).arg( values.at( i ).value ), 4 );
1071 const int remoteAttributeIndex = attrLookup[ values.at( i ).attr ];
1072 QVariant attr = values.at( i ).value;
1073 if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QVariant::StringList )
1077 else if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QVariant::List )
1088void QgsOfflineEditing::applyGeometryChanges(
QgsVectorLayer *remoteLayer,
sqlite3 *db,
int layerId,
int commitNo )
1090 Q_ASSERT( remoteLayer );
1092 const QString sql = QStringLiteral(
"SELECT \"fid\", \"geom_wkt\" FROM 'log_geometry_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo );
1093 const GeometryChanges values = sqlQueryGeometryChanges( db, sql );
1097 for (
int i = 0; i < values.size(); i++ )
1099 const QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid, remoteLayer );
1109 Q_ASSERT( remoteLayer );
1115 QMap < QgsFeatureId, QString > newRemoteFids;
1122 const int remotePkIdx = getLayerPkIdx( remoteLayer );
1127 if ( offlineFid( db, layerId, f.
id() ) == -1 )
1129 newRemoteFids[ f.
id()] = remotePkIdx >= 0 ? f.
attribute( remotePkIdx ).toString() : QString();
1137 const QString sql = QStringLiteral(
"SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
1138 const QList<int> newOfflineFids = sqlQueryInts( db, sql );
1140 if ( newRemoteFids.size() != newOfflineFids.size() )
1148 sqlExec( db, QStringLiteral(
"BEGIN" ) );
1149 for ( QMap<QgsFeatureId, QString>::const_iterator it = newRemoteFids.constBegin(); it != newRemoteFids.constEnd(); ++it )
1151 addFidLookup( db, layerId, newOfflineFids.at( i++ ), it.key(), it.value() );
1153 sqlExec( db, QStringLiteral(
"COMMIT" ) );
1160 Q_ASSERT( offlineLayer );
1161 Q_ASSERT( remoteLayer );
1165 QMap <
int ,
int > attrLookup;
1168 for (
int i = 0; i < offlineAttrs.size(); i++ )
1177void QgsOfflineEditing::showWarning(
const QString &message )
1179 emit
warning( tr(
"Offline Editing Plugin" ), message );
1186 if ( !dbPath.isEmpty() )
1189 const int rc = database.
open( absoluteDbPath );
1190 if ( rc != SQLITE_OK )
1192 QgsDebugError( QStringLiteral(
"Could not open the SpatiaLite logging database" ) );
1193 showWarning( tr(
"Could not open the SpatiaLite logging database" ) );
1203int QgsOfflineEditing::getOrCreateLayerId(
sqlite3 *db,
const QString &qgisLayerId )
1205 QString sql = QStringLiteral(
"SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
1206 int layerId = sqlQueryInt( db, sql, -1 );
1207 if ( layerId == -1 )
1210 sql = QStringLiteral(
"SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'layer_id'" );
1211 const int newLayerId = sqlQueryInt( db, sql, -1 );
1214 sql = QStringLiteral(
"INSERT INTO 'log_layer_ids' VALUES (%1, '%2')" ).arg( newLayerId ).arg( qgisLayerId );
1219 sql = QStringLiteral(
"UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'layer_id'" ).arg( newLayerId + 1 );
1222 layerId = newLayerId;
1228int QgsOfflineEditing::getCommitNo(
sqlite3 *db )
1230 const QString sql = QStringLiteral(
"SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'commit_no'" );
1231 return sqlQueryInt( db, sql, -1 );
1234void QgsOfflineEditing::increaseCommitNo(
sqlite3 *db )
1236 const QString sql = QStringLiteral(
"UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'commit_no'" ).arg( getCommitNo( db ) + 1 );
1242 const QString sql = QStringLiteral(
"INSERT INTO 'log_fids' VALUES ( %1, %2, %3, %4 )" ).arg( layerId ).arg( offlineFid ).arg( remoteFid ).arg( sqlEscape( remotePk ) );
1248 const int pkIdx = getLayerPkIdx( remoteLayer );
1252 const QString sql = QStringLiteral(
"SELECT \"remote_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2" ).arg( layerId ).arg( offlineFid );
1253 return sqlQueryInt( db, sql, -1 );
1256 const QString sql = QStringLiteral(
"SELECT \"remote_pk\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2" ).arg( layerId ).arg( offlineFid );
1257 QString defaultValue;
1258 const QString pkValue = sqlQueryStr( db, sql, defaultValue );
1260 if ( pkValue.isNull() )
1265 const QString pkFieldName = remoteLayer->
fields().
at( pkIdx ).
name();
1276 const QString sql = QStringLiteral(
"SELECT \"offline_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"remote_fid\" = %2" ).arg( layerId ).arg( remoteFid );
1277 return sqlQueryInt( db, sql, -1 );
1282 const QString sql = QStringLiteral(
"SELECT COUNT(\"fid\") FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( fid );
1283 return ( sqlQueryInt( db, sql, 0 ) > 0 );
1286int QgsOfflineEditing::sqlExec(
sqlite3 *db,
const QString &sql )
1288 char *errmsg =
nullptr;
1289 const int rc = sqlite3_exec( db, sql.toUtf8(),
nullptr,
nullptr, &errmsg );
1290 if ( rc != SQLITE_OK )
1292 showWarning( errmsg );
1297QString QgsOfflineEditing::sqlQueryStr(
sqlite3 *db,
const QString &sql, QString &defaultValue )
1299 sqlite3_stmt *stmt =
nullptr;
1300 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1302 showWarning( sqlite3_errmsg( db ) );
1303 return defaultValue;
1306 QString value = defaultValue;
1307 const int ret = sqlite3_step( stmt );
1308 if ( ret == SQLITE_ROW )
1310 value = QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 0 ) ) );
1312 sqlite3_finalize( stmt );
1317int QgsOfflineEditing::sqlQueryInt(
sqlite3 *db,
const QString &sql,
int defaultValue )
1319 sqlite3_stmt *stmt =
nullptr;
1320 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1322 showWarning( sqlite3_errmsg( db ) );
1323 return defaultValue;
1326 int value = defaultValue;
1327 const int ret = sqlite3_step( stmt );
1328 if ( ret == SQLITE_ROW )
1330 value = sqlite3_column_int( stmt, 0 );
1332 sqlite3_finalize( stmt );
1337QList<int> QgsOfflineEditing::sqlQueryInts(
sqlite3 *db,
const QString &sql )
1341 sqlite3_stmt *stmt =
nullptr;
1342 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1344 showWarning( sqlite3_errmsg( db ) );
1348 int ret = sqlite3_step( stmt );
1349 while ( ret == SQLITE_ROW )
1351 values << sqlite3_column_int( stmt, 0 );
1353 ret = sqlite3_step( stmt );
1355 sqlite3_finalize( stmt );
1360QList<QgsField> QgsOfflineEditing::sqlQueryAttributesAdded(
sqlite3 *db,
const QString &sql )
1362 QList<QgsField> values;
1364 sqlite3_stmt *stmt =
nullptr;
1365 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1367 showWarning( sqlite3_errmsg( db ) );
1371 int ret = sqlite3_step( stmt );
1372 while ( ret == SQLITE_ROW )
1374 const QgsField field( QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 0 ) ) ),
1375 static_cast< QVariant::Type
>( sqlite3_column_int( stmt, 1 ) ),
1377 sqlite3_column_int( stmt, 2 ),
1378 sqlite3_column_int( stmt, 3 ),
1379 QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 4 ) ) ) );
1382 ret = sqlite3_step( stmt );
1384 sqlite3_finalize( stmt );
1393 sqlite3_stmt *stmt =
nullptr;
1394 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1396 showWarning( sqlite3_errmsg( db ) );
1400 int ret = sqlite3_step( stmt );
1401 while ( ret == SQLITE_ROW )
1403 values << sqlite3_column_int( stmt, 0 );
1405 ret = sqlite3_step( stmt );
1407 sqlite3_finalize( stmt );
1412QgsOfflineEditing::AttributeValueChanges QgsOfflineEditing::sqlQueryAttributeValueChanges(
sqlite3 *db,
const QString &sql )
1414 AttributeValueChanges values;
1416 sqlite3_stmt *stmt =
nullptr;
1417 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1419 showWarning( sqlite3_errmsg( db ) );
1423 int ret = sqlite3_step( stmt );
1424 while ( ret == SQLITE_ROW )
1426 AttributeValueChange change;
1427 change.fid = sqlite3_column_int( stmt, 0 );
1428 change.attr = sqlite3_column_int( stmt, 1 );
1429 change.value = QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 2 ) ) );
1432 ret = sqlite3_step( stmt );
1434 sqlite3_finalize( stmt );
1439QgsOfflineEditing::GeometryChanges QgsOfflineEditing::sqlQueryGeometryChanges(
sqlite3 *db,
const QString &sql )
1441 GeometryChanges values;
1443 sqlite3_stmt *stmt =
nullptr;
1444 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1446 showWarning( sqlite3_errmsg( db ) );
1450 int ret = sqlite3_step( stmt );
1451 while ( ret == SQLITE_ROW )
1453 GeometryChange change;
1454 change.fid = sqlite3_column_int( stmt, 0 );
1455 change.geom_wkt = QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 1 ) ) );
1458 ret = sqlite3_step( stmt );
1460 sqlite3_finalize( stmt );
1465void QgsOfflineEditing::committedAttributesAdded(
const QString &qgisLayerId,
const QList<QgsField> &addedAttributes )
1472 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1473 const int commitNo = getCommitNo( database.get() );
1475 for (
const QgsField &field : addedAttributes )
1477 const QString sql = QStringLiteral(
"INSERT INTO 'log_added_attrs' VALUES ( %1, %2, '%3', %4, %5, %6, '%7' )" )
1480 .arg( field.
name() )
1481 .arg( field.
type() )
1485 sqlExec( database.get(), sql );
1488 increaseCommitNo( database.get() );
1491void QgsOfflineEditing::committedFeaturesAdded(
const QString &qgisLayerId,
const QgsFeatureList &addedFeatures )
1498 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1502 const QString dataSourceString = layer->
source();
1508 if ( !offlinePath.contains(
".gpkg" ) )
1510 tableName = uri.
table();
1515 const QVariantMap decodedUri = ogrProviderMetaData->
decodeUri( dataSourceString );
1516 tableName = decodedUri.value( QStringLiteral(
"layerName" ) ).toString();
1517 if ( tableName.isEmpty() )
1519 showWarning( tr(
"Could not deduce table name from data source %1." ).arg( dataSourceString ) );
1524 const QString sql = QStringLiteral(
"SELECT ROWID FROM '%1' ORDER BY ROWID DESC LIMIT %2" ).arg( tableName ).arg( addedFeatures.size() );
1525 const QList<int> newFeatureIds = sqlQueryInts( database.get(), sql );
1526 for (
int i = newFeatureIds.size() - 1; i >= 0; i-- )
1528 const QString sql = QStringLiteral(
"INSERT INTO 'log_added_features' VALUES ( %1, %2 )" )
1530 .arg( newFeatureIds.at( i ) );
1531 sqlExec( database.get(), sql );
1535void QgsOfflineEditing::committedFeaturesRemoved(
const QString &qgisLayerId,
const QgsFeatureIds &deletedFeatureIds )
1542 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1546 if ( isAddedFeature( database.get(), layerId,
id ) )
1549 const QString sql = QStringLiteral(
"DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg(
id );
1550 sqlExec( database.get(), sql );
1554 const QString sql = QStringLiteral(
"INSERT INTO 'log_removed_features' VALUES ( %1, %2)" )
1557 sqlExec( database.get(), sql );
1562void QgsOfflineEditing::committedAttributeValuesChanges(
const QString &qgisLayerId,
const QgsChangedAttributesMap &changedAttrsMap )
1569 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1570 const int commitNo = getCommitNo( database.get() );
1572 for ( QgsChangedAttributesMap::const_iterator cit = changedAttrsMap.begin(); cit != changedAttrsMap.end(); ++cit )
1575 if ( isAddedFeature( database.get(), layerId, fid ) )
1581 for ( QgsAttributeMap::const_iterator it = attrMap.constBegin(); it != attrMap.constEnd(); ++it )
1583 QString value = it.value().type() == QVariant::StringList || it.value().type() == QVariant::List ?
QgsJsonUtils::encodeValue( it.value() ) : it.value().toString();
1584 value.replace( QLatin1String(
"'" ), QLatin1String(
"''" ) );
1585 const QString sql = QStringLiteral(
"INSERT INTO 'log_feature_updates' VALUES ( %1, %2, %3, %4, '%5' )" )
1591 sqlExec( database.get(), sql );
1595 increaseCommitNo( database.get() );
1598void QgsOfflineEditing::committedGeometriesChanges(
const QString &qgisLayerId,
const QgsGeometryMap &changedGeometries )
1605 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1606 const int commitNo = getCommitNo( database.get() );
1608 for ( QgsGeometryMap::const_iterator it = changedGeometries.begin(); it != changedGeometries.end(); ++it )
1611 if ( isAddedFeature( database.get(), layerId, fid ) )
1617 const QString sql = QStringLiteral(
"INSERT INTO 'log_geometry_updates' VALUES ( %1, %2, %3, '%4' )" )
1621 .arg( geom.
asWkt() );
1622 sqlExec( database.get(), sql );
1627 increaseCommitNo( database.get() );
1630void QgsOfflineEditing::startListenFeatureChanges()
1632 QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( sender() );
1641 this, &QgsOfflineEditing::committedAttributesAdded );
1643 this, &QgsOfflineEditing::committedAttributeValuesChanges );
1645 this, &QgsOfflineEditing::committedGeometriesChanges );
1648 this, &QgsOfflineEditing::committedFeaturesAdded );
1650 this, &QgsOfflineEditing::committedFeaturesRemoved );
1653void QgsOfflineEditing::stopListenFeatureChanges()
1655 QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( sender() );
1664 this, &QgsOfflineEditing::committedAttributesAdded );
1666 this, &QgsOfflineEditing::committedAttributeValuesChanges );
1668 this, &QgsOfflineEditing::committedGeometriesChanges );
1671 this, &QgsOfflineEditing::committedFeaturesAdded );
1673 this, &QgsOfflineEditing::committedFeaturesRemoved );
1676void QgsOfflineEditing::setupLayer(
QgsMapLayer *layer )
1680 if (
QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( layer ) )
1691int QgsOfflineEditing::getLayerPkIdx(
const QgsVectorLayer *layer )
const
1694 if ( pkAttrs.length() == 1 )
1697 const QVariant::Type pkType = pkField.
type();
1699 if ( pkType == QVariant::String )
1708QString QgsOfflineEditing::sqlEscape( QString value )
const
1710 if ( value.isNull() )
1711 return QStringLiteral(
"NULL" );
1713 value.replace(
"'",
"''" );
1715 return QStringLiteral(
"'%1'" ).arg( value );
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.
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.
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.
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.
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 Qgis::WkbType wkbType() const FINAL
Returns the WKBType or WKBUnknown in case of error.
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 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 bool hasZ(Qgis::WkbType type)
Tests whether a WKB type contains the z-dimension.
static 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
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 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.