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 const QVariant attr = attrs.at( it );
824 newAttrs[column++] = attr;
828 newLayer->addFeature( f );
832 if ( newLayer->commitChanges() )
838 const int layerId = getOrCreateLayerId( db, layer->
id() );
839 QList<QgsFeatureId> offlineFeatureIds;
844 offlineFeatureIds << f.
id();
848 sqlExec( db, QStringLiteral(
"BEGIN" ) );
849 const int remoteCount = remoteFeatureIds.size();
850 for (
int i = 0; i < remoteCount; i++ )
853 if ( i < offlineFeatureIds.count() )
855 addFidLookup( db, layerId, offlineFeatureIds.at( i ), remoteFeatureIds.at( i ), remoteFeaturePks.at( i ) );
859 showWarning( tr(
"Feature cannot be copied to the offline layer, please check if the online layer '%1' is still accessible." ).arg( layer->
name() ) );
864 sqlExec( db, QStringLiteral(
"COMMIT" ) );
868 showWarning( newLayer->commitErrors().join( QLatin1Char(
'\n' ) ) );
882 QStringList notNullFieldNames;
883 for (
const QgsField &field : fields )
887 notNullFieldNames << field.name();
891 layer->
setDataSource( newLayer->source(), newLayer->name(), newLayer->dataProvider()->name() );
893 for (
const QgsField &field : fields )
903 if ( notNullFieldNames.contains( field.name() ) )
905 notNullFieldNames.removeAll( field.name() );
916void QgsOfflineEditing::applyAttributesAdded(
QgsVectorLayer *remoteLayer,
sqlite3 *db,
int layerId,
int commitNo )
918 Q_ASSERT( remoteLayer );
920 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 );
921 QList<QgsField> fields = sqlQueryAttributesAdded( db, sql );
924 const QList<QgsVectorDataProvider::NativeType> nativeTypes = provider->
nativeTypes();
927 QMap < QVariant::Type, QString > typeNameLookup;
928 for (
int i = 0; i < nativeTypes.size(); i++ )
936 for (
int i = 0; i < fields.size(); i++ )
940 if ( typeNameLookup.contains( field.
type() ) )
948 showWarning( QStringLiteral(
"Could not add attribute '%1' of type %2" ).arg( field.
name() ).arg( field.
type() ) );
957 Q_ASSERT( offlineLayer );
958 Q_ASSERT( remoteLayer );
960 const QString sql = QStringLiteral(
"SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
961 const QList<int> featureIdInts = sqlQueryInts( db, sql );
963 for (
const int id : featureIdInts )
983 const int newAttrsCount = remoteLayer->
fields().
count();
984 for ( QgsFeatureList::iterator it = features.begin(); it != features.end(); ++it )
988 const QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
991 for (
int it = 0; it < attrs.count(); ++it )
993 const int remoteAttributeIndex = attrLookup.value( it, -1 );
995 if ( remoteAttributeIndex == -1 )
997 QVariant attr = attrs.at( it );
998 if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QVariant::StringList )
1000 if ( attr.type() == QVariant::StringList || attr.type() == QVariant::List )
1002 attr = attr.toStringList();
1009 else if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QVariant::List )
1011 if ( attr.type() == QVariant::StringList || attr.type() == QVariant::List )
1013 attr = attr.toList();
1020 newAttrs[ remoteAttributeIndex ] = attr;
1031void QgsOfflineEditing::applyFeaturesRemoved(
QgsVectorLayer *remoteLayer,
sqlite3 *db,
int layerId )
1033 Q_ASSERT( remoteLayer );
1035 const QString sql = QStringLiteral(
"SELECT \"fid\" FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
1036 const QgsFeatureIds values = sqlQueryFeaturesRemoved( db, sql );
1041 for ( QgsFeatureIds::const_iterator it = values.constBegin(); it != values.constEnd(); ++it )
1043 const QgsFeatureId fid = remoteFid( db, layerId, *it, remoteLayer );
1052 Q_ASSERT( offlineLayer );
1053 Q_ASSERT( remoteLayer );
1055 const QString sql = QStringLiteral(
"SELECT \"fid\", \"attr\", \"value\" FROM 'log_feature_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2 " ).arg( layerId ).arg( commitNo );
1056 const AttributeValueChanges values = sqlQueryAttributeValueChanges( db, sql );
1060 QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
1062 for (
int i = 0; i < values.size(); i++ )
1064 const QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid, remoteLayer );
1065 QgsDebugMsgLevel( QStringLiteral(
"Offline changeAttributeValue %1 = %2" ).arg( attrLookup[ values.at( i ).attr ] ).arg( values.at( i ).value ), 4 );
1067 const int remoteAttributeIndex = attrLookup[ values.at( i ).attr ];
1068 QVariant attr = values.at( i ).value;
1069 if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QVariant::StringList )
1073 else if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QVariant::List )
1084void QgsOfflineEditing::applyGeometryChanges(
QgsVectorLayer *remoteLayer,
sqlite3 *db,
int layerId,
int commitNo )
1086 Q_ASSERT( remoteLayer );
1088 const QString sql = QStringLiteral(
"SELECT \"fid\", \"geom_wkt\" FROM 'log_geometry_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo );
1089 const GeometryChanges values = sqlQueryGeometryChanges( db, sql );
1093 for (
int i = 0; i < values.size(); i++ )
1095 const QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid, remoteLayer );
1105 Q_ASSERT( remoteLayer );
1111 QMap < QgsFeatureId, QString > newRemoteFids;
1118 const int remotePkIdx = getLayerPkIdx( remoteLayer );
1123 if ( offlineFid( db, layerId, f.
id() ) == -1 )
1125 newRemoteFids[ f.
id()] = remotePkIdx >= 0 ? f.
attribute( remotePkIdx ).toString() : QString();
1133 const QString sql = QStringLiteral(
"SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
1134 const QList<int> newOfflineFids = sqlQueryInts( db, sql );
1136 if ( newRemoteFids.size() != newOfflineFids.size() )
1144 sqlExec( db, QStringLiteral(
"BEGIN" ) );
1145 for ( QMap<QgsFeatureId, QString>::const_iterator it = newRemoteFids.constBegin(); it != newRemoteFids.constEnd(); ++it )
1147 addFidLookup( db, layerId, newOfflineFids.at( i++ ), it.key(), it.value() );
1149 sqlExec( db, QStringLiteral(
"COMMIT" ) );
1156 Q_ASSERT( offlineLayer );
1157 Q_ASSERT( remoteLayer );
1161 QMap <
int ,
int > attrLookup;
1164 for (
int i = 0; i < offlineAttrs.size(); i++ )
1173void QgsOfflineEditing::showWarning(
const QString &message )
1175 emit
warning( tr(
"Offline Editing Plugin" ), message );
1182 if ( !dbPath.isEmpty() )
1185 const int rc = database.
open( absoluteDbPath );
1186 if ( rc != SQLITE_OK )
1188 QgsDebugError( QStringLiteral(
"Could not open the SpatiaLite logging database" ) );
1189 showWarning( tr(
"Could not open the SpatiaLite logging database" ) );
1199int QgsOfflineEditing::getOrCreateLayerId(
sqlite3 *db,
const QString &qgisLayerId )
1201 QString sql = QStringLiteral(
"SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
1202 int layerId = sqlQueryInt( db, sql, -1 );
1203 if ( layerId == -1 )
1206 sql = QStringLiteral(
"SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'layer_id'" );
1207 const int newLayerId = sqlQueryInt( db, sql, -1 );
1210 sql = QStringLiteral(
"INSERT INTO 'log_layer_ids' VALUES (%1, '%2')" ).arg( newLayerId ).arg( qgisLayerId );
1215 sql = QStringLiteral(
"UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'layer_id'" ).arg( newLayerId + 1 );
1218 layerId = newLayerId;
1224int QgsOfflineEditing::getCommitNo(
sqlite3 *db )
1226 const QString sql = QStringLiteral(
"SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'commit_no'" );
1227 return sqlQueryInt( db, sql, -1 );
1230void QgsOfflineEditing::increaseCommitNo(
sqlite3 *db )
1232 const QString sql = QStringLiteral(
"UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'commit_no'" ).arg( getCommitNo( db ) + 1 );
1238 const QString sql = QStringLiteral(
"INSERT INTO 'log_fids' VALUES ( %1, %2, %3, %4 )" ).arg( layerId ).arg( offlineFid ).arg( remoteFid ).arg( sqlEscape( remotePk ) );
1244 const int pkIdx = getLayerPkIdx( remoteLayer );
1248 const QString sql = QStringLiteral(
"SELECT \"remote_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2" ).arg( layerId ).arg( offlineFid );
1249 return sqlQueryInt( db, sql, -1 );
1252 const QString sql = QStringLiteral(
"SELECT \"remote_pk\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2" ).arg( layerId ).arg( offlineFid );
1253 QString defaultValue;
1254 const QString pkValue = sqlQueryStr( db, sql, defaultValue );
1256 if ( pkValue.isNull() )
1261 const QString pkFieldName = remoteLayer->
fields().
at( pkIdx ).
name();
1272 const QString sql = QStringLiteral(
"SELECT \"offline_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"remote_fid\" = %2" ).arg( layerId ).arg( remoteFid );
1273 return sqlQueryInt( db, sql, -1 );
1278 const QString sql = QStringLiteral(
"SELECT COUNT(\"fid\") FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( fid );
1279 return ( sqlQueryInt( db, sql, 0 ) > 0 );
1282int QgsOfflineEditing::sqlExec(
sqlite3 *db,
const QString &sql )
1284 char *errmsg =
nullptr;
1285 const int rc = sqlite3_exec( db, sql.toUtf8(),
nullptr,
nullptr, &errmsg );
1286 if ( rc != SQLITE_OK )
1288 showWarning( errmsg );
1293QString QgsOfflineEditing::sqlQueryStr(
sqlite3 *db,
const QString &sql, QString &defaultValue )
1295 sqlite3_stmt *stmt =
nullptr;
1296 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1298 showWarning( sqlite3_errmsg( db ) );
1299 return defaultValue;
1302 QString value = defaultValue;
1303 const int ret = sqlite3_step( stmt );
1304 if ( ret == SQLITE_ROW )
1306 value = QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 0 ) ) );
1308 sqlite3_finalize( stmt );
1313int QgsOfflineEditing::sqlQueryInt(
sqlite3 *db,
const QString &sql,
int defaultValue )
1315 sqlite3_stmt *stmt =
nullptr;
1316 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1318 showWarning( sqlite3_errmsg( db ) );
1319 return defaultValue;
1322 int value = defaultValue;
1323 const int ret = sqlite3_step( stmt );
1324 if ( ret == SQLITE_ROW )
1326 value = sqlite3_column_int( stmt, 0 );
1328 sqlite3_finalize( stmt );
1333QList<int> QgsOfflineEditing::sqlQueryInts(
sqlite3 *db,
const QString &sql )
1337 sqlite3_stmt *stmt =
nullptr;
1338 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1340 showWarning( sqlite3_errmsg( db ) );
1344 int ret = sqlite3_step( stmt );
1345 while ( ret == SQLITE_ROW )
1347 values << sqlite3_column_int( stmt, 0 );
1349 ret = sqlite3_step( stmt );
1351 sqlite3_finalize( stmt );
1356QList<QgsField> QgsOfflineEditing::sqlQueryAttributesAdded(
sqlite3 *db,
const QString &sql )
1358 QList<QgsField> values;
1360 sqlite3_stmt *stmt =
nullptr;
1361 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1363 showWarning( sqlite3_errmsg( db ) );
1367 int ret = sqlite3_step( stmt );
1368 while ( ret == SQLITE_ROW )
1370 const QgsField field( QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 0 ) ) ),
1371 static_cast< QVariant::Type
>( sqlite3_column_int( stmt, 1 ) ),
1373 sqlite3_column_int( stmt, 2 ),
1374 sqlite3_column_int( stmt, 3 ),
1375 QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 4 ) ) ) );
1378 ret = sqlite3_step( stmt );
1380 sqlite3_finalize( stmt );
1389 sqlite3_stmt *stmt =
nullptr;
1390 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1392 showWarning( sqlite3_errmsg( db ) );
1396 int ret = sqlite3_step( stmt );
1397 while ( ret == SQLITE_ROW )
1399 values << sqlite3_column_int( stmt, 0 );
1401 ret = sqlite3_step( stmt );
1403 sqlite3_finalize( stmt );
1408QgsOfflineEditing::AttributeValueChanges QgsOfflineEditing::sqlQueryAttributeValueChanges(
sqlite3 *db,
const QString &sql )
1410 AttributeValueChanges values;
1412 sqlite3_stmt *stmt =
nullptr;
1413 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1415 showWarning( sqlite3_errmsg( db ) );
1419 int ret = sqlite3_step( stmt );
1420 while ( ret == SQLITE_ROW )
1422 AttributeValueChange change;
1423 change.fid = sqlite3_column_int( stmt, 0 );
1424 change.attr = sqlite3_column_int( stmt, 1 );
1425 change.value = QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 2 ) ) );
1428 ret = sqlite3_step( stmt );
1430 sqlite3_finalize( stmt );
1435QgsOfflineEditing::GeometryChanges QgsOfflineEditing::sqlQueryGeometryChanges(
sqlite3 *db,
const QString &sql )
1437 GeometryChanges values;
1439 sqlite3_stmt *stmt =
nullptr;
1440 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1442 showWarning( sqlite3_errmsg( db ) );
1446 int ret = sqlite3_step( stmt );
1447 while ( ret == SQLITE_ROW )
1449 GeometryChange change;
1450 change.fid = sqlite3_column_int( stmt, 0 );
1451 change.geom_wkt = QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 1 ) ) );
1454 ret = sqlite3_step( stmt );
1456 sqlite3_finalize( stmt );
1461void QgsOfflineEditing::committedAttributesAdded(
const QString &qgisLayerId,
const QList<QgsField> &addedAttributes )
1468 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1469 const int commitNo = getCommitNo( database.get() );
1471 for (
const QgsField &field : addedAttributes )
1473 const QString sql = QStringLiteral(
"INSERT INTO 'log_added_attrs' VALUES ( %1, %2, '%3', %4, %5, %6, '%7' )" )
1476 .arg( field.
name() )
1477 .arg( field.
type() )
1481 sqlExec( database.get(), sql );
1484 increaseCommitNo( database.get() );
1487void QgsOfflineEditing::committedFeaturesAdded(
const QString &qgisLayerId,
const QgsFeatureList &addedFeatures )
1494 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1498 const QString dataSourceString = layer->
source();
1504 if ( !offlinePath.contains(
".gpkg" ) )
1506 tableName = uri.
table();
1511 const QVariantMap decodedUri = ogrProviderMetaData->
decodeUri( dataSourceString );
1512 tableName = decodedUri.value( QStringLiteral(
"layerName" ) ).toString();
1513 if ( tableName.isEmpty() )
1515 showWarning( tr(
"Could not deduce table name from data source %1." ).arg( dataSourceString ) );
1520 const QString sql = QStringLiteral(
"SELECT ROWID FROM '%1' ORDER BY ROWID DESC LIMIT %2" ).arg( tableName ).arg( addedFeatures.size() );
1521 const QList<int> newFeatureIds = sqlQueryInts( database.get(), sql );
1522 for (
int i = newFeatureIds.size() - 1; i >= 0; i-- )
1524 const QString sql = QStringLiteral(
"INSERT INTO 'log_added_features' VALUES ( %1, %2 )" )
1526 .arg( newFeatureIds.at( i ) );
1527 sqlExec( database.get(), sql );
1531void QgsOfflineEditing::committedFeaturesRemoved(
const QString &qgisLayerId,
const QgsFeatureIds &deletedFeatureIds )
1538 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1542 if ( isAddedFeature( database.get(), layerId,
id ) )
1545 const QString sql = QStringLiteral(
"DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg(
id );
1546 sqlExec( database.get(), sql );
1550 const QString sql = QStringLiteral(
"INSERT INTO 'log_removed_features' VALUES ( %1, %2)" )
1553 sqlExec( database.get(), sql );
1558void QgsOfflineEditing::committedAttributeValuesChanges(
const QString &qgisLayerId,
const QgsChangedAttributesMap &changedAttrsMap )
1565 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1566 const int commitNo = getCommitNo( database.get() );
1568 for ( QgsChangedAttributesMap::const_iterator cit = changedAttrsMap.begin(); cit != changedAttrsMap.end(); ++cit )
1571 if ( isAddedFeature( database.get(), layerId, fid ) )
1577 for ( QgsAttributeMap::const_iterator it = attrMap.constBegin(); it != attrMap.constEnd(); ++it )
1579 QString value = it.value().type() == QVariant::StringList || it.value().type() == QVariant::List ?
QgsJsonUtils::encodeValue( it.value() ) : it.value().toString();
1580 value.replace( QLatin1String(
"'" ), QLatin1String(
"''" ) );
1581 const QString sql = QStringLiteral(
"INSERT INTO 'log_feature_updates' VALUES ( %1, %2, %3, %4, '%5' )" )
1587 sqlExec( database.get(), sql );
1591 increaseCommitNo( database.get() );
1594void QgsOfflineEditing::committedGeometriesChanges(
const QString &qgisLayerId,
const QgsGeometryMap &changedGeometries )
1601 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1602 const int commitNo = getCommitNo( database.get() );
1604 for ( QgsGeometryMap::const_iterator it = changedGeometries.begin(); it != changedGeometries.end(); ++it )
1607 if ( isAddedFeature( database.get(), layerId, fid ) )
1613 const QString sql = QStringLiteral(
"INSERT INTO 'log_geometry_updates' VALUES ( %1, %2, %3, '%4' )" )
1617 .arg( geom.
asWkt() );
1618 sqlExec( database.get(), sql );
1623 increaseCommitNo( database.get() );
1626void QgsOfflineEditing::startListenFeatureChanges()
1628 QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( sender() );
1637 this, &QgsOfflineEditing::committedAttributesAdded );
1639 this, &QgsOfflineEditing::committedAttributeValuesChanges );
1641 this, &QgsOfflineEditing::committedGeometriesChanges );
1644 this, &QgsOfflineEditing::committedFeaturesAdded );
1646 this, &QgsOfflineEditing::committedFeaturesRemoved );
1649void QgsOfflineEditing::stopListenFeatureChanges()
1651 QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( sender() );
1660 this, &QgsOfflineEditing::committedAttributesAdded );
1662 this, &QgsOfflineEditing::committedAttributeValuesChanges );
1664 this, &QgsOfflineEditing::committedGeometriesChanges );
1667 this, &QgsOfflineEditing::committedFeaturesAdded );
1669 this, &QgsOfflineEditing::committedFeaturesRemoved );
1672void QgsOfflineEditing::setupLayer(
QgsMapLayer *layer )
1676 if (
QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( layer ) )
1687int QgsOfflineEditing::getLayerPkIdx(
const QgsVectorLayer *layer )
const
1690 if ( pkAttrs.length() == 1 )
1693 const QVariant::Type pkType = pkField.
type();
1695 if ( pkType == QVariant::String )
1704QString QgsOfflineEditing::sqlEscape( QString value )
const
1706 if ( value.isNull() )
1707 return QStringLiteral(
"NULL" );
1709 value.replace(
"'",
"''" );
1711 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.